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 body_fmt = gettext(prefs_common.quotefmt);
1600 /* empty quotemark is not allowed */
1601 if (qmark == NULL || *qmark == '\0')
1603 compose_quote_fmt(compose, compose->replyinfo,
1604 body_fmt, qmark, body, FALSE, TRUE,
1605 _("Message reply format error at line %d."));
1606 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1607 quote_fmt_reset_vartable();
1610 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1611 compose_force_encryption(compose, account, FALSE);
1614 SIGNAL_BLOCK(textbuf);
1616 if (account->auto_sig)
1617 compose_insert_sig(compose, FALSE);
1619 compose_wrap_all(compose);
1621 SIGNAL_UNBLOCK(textbuf);
1623 gtk_widget_grab_focus(compose->text);
1625 undo_unblock(compose->undostruct);
1627 if (prefs_common.auto_exteditor)
1628 compose_exec_ext_editor(compose);
1630 compose->modified = FALSE;
1631 compose_set_title(compose);
1633 compose->updating = FALSE;
1634 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1635 SCROLL_TO_CURSOR(compose);
1637 if (compose->deferred_destroy) {
1638 compose_destroy(compose);
1645 #define INSERT_FW_HEADER(var, hdr) \
1646 if (msginfo->var && *msginfo->var) { \
1647 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1648 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1649 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1652 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1653 gboolean as_attach, const gchar *body,
1654 gboolean no_extedit,
1658 GtkTextView *textview;
1659 GtkTextBuffer *textbuf;
1662 g_return_val_if_fail(msginfo != NULL, NULL);
1663 g_return_val_if_fail(msginfo->folder != NULL, NULL);
1666 !(account = compose_guess_forward_account_from_msginfo
1668 account = cur_account;
1670 compose = compose_create(account, msginfo->folder, COMPOSE_FORWARD, batch);
1672 compose->updating = TRUE;
1673 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1674 if (!compose->fwdinfo)
1675 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1677 compose_extract_original_charset(compose);
1679 if (msginfo->subject && *msginfo->subject) {
1680 gchar *buf, *buf2, *p;
1682 buf = p = g_strdup(msginfo->subject);
1683 p += subject_get_prefix_length(p);
1684 memmove(buf, p, strlen(p) + 1);
1686 buf2 = g_strdup_printf("Fw: %s", buf);
1687 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1693 /* override from name according to folder properties */
1694 if (msginfo->folder && msginfo->folder->prefs &&
1695 msginfo->folder->prefs->forward_with_format &&
1696 msginfo->folder->prefs->forward_override_from_format &&
1697 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1701 MsgInfo *full_msginfo = NULL;
1704 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1706 full_msginfo = procmsg_msginfo_copy(msginfo);
1708 /* decode \-escape sequences in the internal representation of the quote format */
1709 tmp = malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1710 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1713 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1714 compose->gtkaspell);
1716 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1718 quote_fmt_scan_string(tmp);
1721 buf = quote_fmt_get_buffer();
1723 alertpanel_error(_("Message forward From format error."));
1725 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1726 quote_fmt_reset_vartable();
1729 procmsg_msginfo_free(full_msginfo);
1732 textview = GTK_TEXT_VIEW(compose->text);
1733 textbuf = gtk_text_view_get_buffer(textview);
1734 compose_create_tags(textview, compose);
1736 undo_block(compose->undostruct);
1740 msgfile = procmsg_get_message_file(msginfo);
1741 if (!is_file_exist(msgfile))
1742 g_warning("%s: file not exist\n", msgfile);
1744 compose_attach_append(compose, msgfile, msgfile,
1749 const gchar *qmark = NULL;
1750 const gchar *body_fmt = gettext(prefs_common.fw_quotefmt);
1751 MsgInfo *full_msginfo;
1753 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1755 full_msginfo = procmsg_msginfo_copy(msginfo);
1757 /* use the forward format of folder (if enabled), or the account's one
1758 (if enabled) or fallback to the global forward format, which is always
1759 enabled (even if empty), and use the relevant quotemark */
1760 if (msginfo->folder && msginfo->folder->prefs &&
1761 msginfo->folder->prefs->forward_with_format) {
1762 qmark = msginfo->folder->prefs->forward_quotemark;
1763 body_fmt = msginfo->folder->prefs->forward_body_format;
1765 } else if (account->forward_with_format) {
1766 qmark = account->forward_quotemark;
1767 body_fmt = account->forward_body_format;
1770 qmark = prefs_common.fw_quotemark;
1771 body_fmt = gettext(prefs_common.fw_quotefmt);
1774 /* empty quotemark is not allowed */
1775 if (qmark == NULL || *qmark == '\0')
1778 compose_quote_fmt(compose, full_msginfo,
1779 body_fmt, qmark, body, FALSE, TRUE,
1780 _("Message forward format error at line %d."));
1781 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1782 quote_fmt_reset_vartable();
1783 compose_attach_parts(compose, msginfo);
1785 procmsg_msginfo_free(full_msginfo);
1788 SIGNAL_BLOCK(textbuf);
1790 if (account->auto_sig)
1791 compose_insert_sig(compose, FALSE);
1793 compose_wrap_all(compose);
1795 SIGNAL_UNBLOCK(textbuf);
1797 gtk_text_buffer_get_start_iter(textbuf, &iter);
1798 gtk_text_buffer_place_cursor(textbuf, &iter);
1800 gtk_widget_grab_focus(compose->header_last->entry);
1802 if (!no_extedit && prefs_common.auto_exteditor)
1803 compose_exec_ext_editor(compose);
1806 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1807 gchar *folderidentifier;
1809 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1810 folderidentifier = folder_item_get_identifier(msginfo->folder);
1811 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
1812 g_free(folderidentifier);
1815 undo_unblock(compose->undostruct);
1817 compose->modified = FALSE;
1818 compose_set_title(compose);
1820 compose->updating = FALSE;
1821 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1822 SCROLL_TO_CURSOR(compose);
1824 if (compose->deferred_destroy) {
1825 compose_destroy(compose);
1832 #undef INSERT_FW_HEADER
1834 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1837 GtkTextView *textview;
1838 GtkTextBuffer *textbuf;
1842 gboolean single_mail = TRUE;
1844 g_return_val_if_fail(msginfo_list != NULL, NULL);
1846 if (g_slist_length(msginfo_list) > 1)
1847 single_mail = FALSE;
1849 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1850 if (((MsgInfo *)msginfo->data)->folder == NULL)
1853 /* guess account from first selected message */
1855 !(account = compose_guess_forward_account_from_msginfo
1856 (msginfo_list->data)))
1857 account = cur_account;
1859 g_return_val_if_fail(account != NULL, NULL);
1861 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1862 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1863 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1866 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1868 compose->updating = TRUE;
1870 /* override from name according to folder properties */
1871 if (msginfo_list->data) {
1872 MsgInfo *msginfo = msginfo_list->data;
1874 if (msginfo->folder && msginfo->folder->prefs &&
1875 msginfo->folder->prefs->forward_with_format &&
1876 msginfo->folder->prefs->forward_override_from_format &&
1877 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1882 /* decode \-escape sequences in the internal representation of the quote format */
1883 tmp = malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1884 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1887 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1888 compose->gtkaspell);
1890 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1892 quote_fmt_scan_string(tmp);
1895 buf = quote_fmt_get_buffer();
1897 alertpanel_error(_("Message forward From format error."));
1899 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1900 quote_fmt_reset_vartable();
1906 textview = GTK_TEXT_VIEW(compose->text);
1907 textbuf = gtk_text_view_get_buffer(textview);
1908 compose_create_tags(textview, compose);
1910 undo_block(compose->undostruct);
1911 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1912 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
1914 if (!is_file_exist(msgfile))
1915 g_warning("%s: file not exist\n", msgfile);
1917 compose_attach_append(compose, msgfile, msgfile,
1923 MsgInfo *info = (MsgInfo *)msginfo_list->data;
1924 if (info->subject && *info->subject) {
1925 gchar *buf, *buf2, *p;
1927 buf = p = g_strdup(info->subject);
1928 p += subject_get_prefix_length(p);
1929 memmove(buf, p, strlen(p) + 1);
1931 buf2 = g_strdup_printf("Fw: %s", buf);
1932 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1938 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
1939 _("Fw: multiple emails"));
1942 SIGNAL_BLOCK(textbuf);
1944 if (account->auto_sig)
1945 compose_insert_sig(compose, FALSE);
1947 compose_wrap_all(compose);
1949 SIGNAL_UNBLOCK(textbuf);
1951 gtk_text_buffer_get_start_iter(textbuf, &iter);
1952 gtk_text_buffer_place_cursor(textbuf, &iter);
1954 gtk_widget_grab_focus(compose->header_last->entry);
1955 undo_unblock(compose->undostruct);
1956 compose->modified = FALSE;
1957 compose_set_title(compose);
1959 compose->updating = FALSE;
1960 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1961 SCROLL_TO_CURSOR(compose);
1963 if (compose->deferred_destroy) {
1964 compose_destroy(compose);
1971 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
1973 GtkTextIter start = *iter;
1974 GtkTextIter end_iter;
1975 int start_pos = gtk_text_iter_get_offset(&start);
1977 if (!compose->account->sig_sep)
1980 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
1981 start_pos+strlen(compose->account->sig_sep));
1983 /* check sig separator */
1984 str = gtk_text_iter_get_text(&start, &end_iter);
1985 if (!strcmp(str, compose->account->sig_sep)) {
1987 /* check end of line (\n) */
1988 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
1989 start_pos+strlen(compose->account->sig_sep));
1990 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
1991 start_pos+strlen(compose->account->sig_sep)+1);
1992 tmp = gtk_text_iter_get_text(&start, &end_iter);
1993 if (!strcmp(tmp,"\n")) {
2005 static void compose_colorize_signature(Compose *compose)
2007 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
2009 GtkTextIter end_iter;
2010 gtk_text_buffer_get_start_iter(buffer, &iter);
2011 while (gtk_text_iter_forward_line(&iter))
2012 if (compose_is_sig_separator(compose, buffer, &iter)) {
2013 gtk_text_buffer_get_end_iter(buffer, &end_iter);
2014 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
2018 #define BLOCK_WRAP() { \
2019 prev_autowrap = compose->autowrap; \
2020 buffer = gtk_text_view_get_buffer( \
2021 GTK_TEXT_VIEW(compose->text)); \
2022 compose->autowrap = FALSE; \
2024 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2025 G_CALLBACK(compose_changed_cb), \
2027 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2028 G_CALLBACK(text_inserted), \
2031 #define UNBLOCK_WRAP() { \
2032 compose->autowrap = prev_autowrap; \
2033 if (compose->autowrap) { \
2034 gint old = compose->draft_timeout_tag; \
2035 compose->draft_timeout_tag = -2; \
2036 compose_wrap_all(compose); \
2037 compose->draft_timeout_tag = old; \
2040 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2041 G_CALLBACK(compose_changed_cb), \
2043 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2044 G_CALLBACK(text_inserted), \
2048 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
2050 Compose *compose = NULL;
2051 PrefsAccount *account = NULL;
2052 GtkTextView *textview;
2053 GtkTextBuffer *textbuf;
2057 gchar buf[BUFFSIZE];
2058 gboolean use_signing = FALSE;
2059 gboolean use_encryption = FALSE;
2060 gchar *privacy_system = NULL;
2061 int priority = PRIORITY_NORMAL;
2062 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
2064 g_return_val_if_fail(msginfo != NULL, NULL);
2065 g_return_val_if_fail(msginfo->folder != NULL, NULL);
2067 if (compose_put_existing_to_front(msginfo)) {
2071 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2072 folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
2073 gchar queueheader_buf[BUFFSIZE];
2076 /* Select Account from queue headers */
2077 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2078 sizeof(queueheader_buf), "X-Claws-Account-Id:")) {
2079 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
2080 account = account_find_from_id(id);
2082 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2083 sizeof(queueheader_buf), "X-Sylpheed-Account-Id:")) {
2084 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
2085 account = account_find_from_id(id);
2087 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2088 sizeof(queueheader_buf), "NAID:")) {
2089 id = atoi(&queueheader_buf[strlen("NAID:")]);
2090 account = account_find_from_id(id);
2092 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2093 sizeof(queueheader_buf), "MAID:")) {
2094 id = atoi(&queueheader_buf[strlen("MAID:")]);
2095 account = account_find_from_id(id);
2097 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2098 sizeof(queueheader_buf), "S:")) {
2099 account = account_find_from_address(queueheader_buf, FALSE);
2101 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2102 sizeof(queueheader_buf), "X-Claws-Sign:")) {
2103 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
2104 use_signing = param;
2107 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2108 sizeof(queueheader_buf), "X-Sylpheed-Sign:")) {
2109 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
2110 use_signing = param;
2113 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2114 sizeof(queueheader_buf), "X-Claws-Encrypt:")) {
2115 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
2116 use_encryption = param;
2118 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2119 sizeof(queueheader_buf), "X-Sylpheed-Encrypt:")) {
2120 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
2121 use_encryption = param;
2123 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2124 sizeof(queueheader_buf), "X-Claws-Privacy-System:")) {
2125 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
2127 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2128 sizeof(queueheader_buf), "X-Sylpheed-Privacy-System:")) {
2129 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
2131 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2132 sizeof(queueheader_buf), "X-Priority: ")) {
2133 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
2136 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2137 sizeof(queueheader_buf), "RMID:")) {
2138 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
2139 if (tokens[0] && tokens[1] && tokens[2]) {
2140 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2141 if (orig_item != NULL) {
2142 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2147 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2148 sizeof(queueheader_buf), "FMID:")) {
2149 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\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 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2159 account = msginfo->folder->folder->account;
2162 if (!account && prefs_common.reedit_account_autosel) {
2163 gchar from[BUFFSIZE];
2164 if (!procheader_get_header_from_msginfo(msginfo, from, sizeof(from), "FROM:")) {
2165 extract_address(from);
2166 account = account_find_from_address(from, FALSE);
2170 account = cur_account;
2172 g_return_val_if_fail(account != NULL, NULL);
2174 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2176 compose->replyinfo = replyinfo;
2177 compose->fwdinfo = fwdinfo;
2179 compose->updating = TRUE;
2180 compose->priority = priority;
2182 if (privacy_system != NULL) {
2183 compose->privacy_system = privacy_system;
2184 compose_use_signing(compose, use_signing);
2185 compose_use_encryption(compose, use_encryption);
2186 compose_update_privacy_system_menu_item(compose, FALSE);
2188 activate_privacy_system(compose, account, FALSE);
2191 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2193 compose_extract_original_charset(compose);
2195 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2196 folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
2197 gchar queueheader_buf[BUFFSIZE];
2199 /* Set message save folder */
2200 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "SCF:")) {
2203 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2204 gtk_editable_delete_text(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
2205 gtk_editable_insert_text(GTK_EDITABLE(compose->savemsg_entry), &queueheader_buf[4], strlen(&queueheader_buf[4]), &startpos);
2207 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "RRCPT:")) {
2208 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2210 GtkItemFactory *ifactory;
2211 ifactory = gtk_item_factory_from_widget(compose->menubar);
2212 menu_set_active(ifactory, "/Options/Request Return Receipt", TRUE);
2217 if (compose_parse_header(compose, msginfo) < 0) {
2218 compose->updating = FALSE;
2219 compose_destroy(compose);
2222 compose_reedit_set_entry(compose, msginfo);
2224 textview = GTK_TEXT_VIEW(compose->text);
2225 textbuf = gtk_text_view_get_buffer(textview);
2226 compose_create_tags(textview, compose);
2228 mark = gtk_text_buffer_get_insert(textbuf);
2229 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2231 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2232 G_CALLBACK(compose_changed_cb),
2235 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2236 fp = procmime_get_first_encrypted_text_content(msginfo);
2238 compose_force_encryption(compose, account, TRUE);
2241 fp = procmime_get_first_text_content(msginfo);
2244 g_warning("Can't get text part\n");
2248 gboolean prev_autowrap = compose->autowrap;
2249 GtkTextBuffer *buffer = textbuf;
2251 while (fgets(buf, sizeof(buf), fp) != NULL) {
2253 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2259 compose_attach_parts(compose, msginfo);
2261 compose_colorize_signature(compose);
2263 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2264 G_CALLBACK(compose_changed_cb),
2267 gtk_widget_grab_focus(compose->text);
2269 if (prefs_common.auto_exteditor) {
2270 compose_exec_ext_editor(compose);
2272 compose->modified = FALSE;
2273 compose_set_title(compose);
2275 compose->updating = FALSE;
2276 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2277 SCROLL_TO_CURSOR(compose);
2279 if (compose->deferred_destroy) {
2280 compose_destroy(compose);
2284 compose->sig_str = compose_get_signature_str(compose);
2289 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2294 GtkItemFactory *ifactory;
2297 g_return_val_if_fail(msginfo != NULL, NULL);
2300 account = account_get_reply_account(msginfo,
2301 prefs_common.reply_account_autosel);
2302 g_return_val_if_fail(account != NULL, NULL);
2304 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2306 compose->updating = TRUE;
2308 ifactory = gtk_item_factory_from_widget(compose->menubar);
2309 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2310 compose->replyinfo = NULL;
2311 compose->fwdinfo = NULL;
2313 compose_show_first_last_header(compose, TRUE);
2315 gtk_widget_grab_focus(compose->header_last->entry);
2317 filename = procmsg_get_message_file(msginfo);
2319 if (filename == NULL) {
2320 compose->updating = FALSE;
2321 compose_destroy(compose);
2326 compose->redirect_filename = filename;
2328 /* Set save folder */
2329 item = msginfo->folder;
2330 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2331 gchar *folderidentifier;
2333 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
2334 folderidentifier = folder_item_get_identifier(item);
2335 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
2336 g_free(folderidentifier);
2339 compose_attach_parts(compose, msginfo);
2341 if (msginfo->subject)
2342 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2344 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2346 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2347 _("Message redirect format error at line %d."));
2348 quote_fmt_reset_vartable();
2349 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2351 compose_colorize_signature(compose);
2353 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
2354 menu_set_sensitive(ifactory, "/Add...", FALSE);
2355 menu_set_sensitive(ifactory, "/Remove", FALSE);
2356 menu_set_sensitive(ifactory, "/Properties...", FALSE);
2358 ifactory = gtk_item_factory_from_widget(compose->menubar);
2359 menu_set_sensitive(ifactory, "/Message/Save", FALSE);
2360 menu_set_sensitive(ifactory, "/Message/Insert file", FALSE);
2361 menu_set_sensitive(ifactory, "/Message/Attach file", FALSE);
2362 menu_set_sensitive(ifactory, "/Message/Insert signature", FALSE);
2363 menu_set_sensitive(ifactory, "/Edit", FALSE);
2364 menu_set_sensitive(ifactory, "/Options", FALSE);
2365 menu_set_sensitive(ifactory, "/Tools/Show ruler", FALSE);
2366 menu_set_sensitive(ifactory, "/Tools/Actions", FALSE);
2368 if (compose->toolbar->draft_btn)
2369 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2370 if (compose->toolbar->insert_btn)
2371 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2372 if (compose->toolbar->attach_btn)
2373 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2374 if (compose->toolbar->sig_btn)
2375 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2376 if (compose->toolbar->exteditor_btn)
2377 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2378 if (compose->toolbar->linewrap_current_btn)
2379 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2380 if (compose->toolbar->linewrap_all_btn)
2381 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2383 compose->modified = FALSE;
2384 compose_set_title(compose);
2385 compose->updating = FALSE;
2386 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2387 SCROLL_TO_CURSOR(compose);
2389 if (compose->deferred_destroy) {
2390 compose_destroy(compose);
2397 GList *compose_get_compose_list(void)
2399 return compose_list;
2402 void compose_entry_append(Compose *compose, const gchar *address,
2403 ComposeEntryType type)
2405 const gchar *header;
2407 gboolean in_quote = FALSE;
2408 if (!address || *address == '\0') return;
2415 header = N_("Bcc:");
2417 case COMPOSE_REPLYTO:
2418 header = N_("Reply-To:");
2420 case COMPOSE_NEWSGROUPS:
2421 header = N_("Newsgroups:");
2423 case COMPOSE_FOLLOWUPTO:
2424 header = N_( "Followup-To:");
2431 header = prefs_common_translated_header_name(header);
2433 cur = begin = (gchar *)address;
2435 /* we separate the line by commas, but not if we're inside a quoted
2437 while (*cur != '\0') {
2439 in_quote = !in_quote;
2440 if (*cur == ',' && !in_quote) {
2441 gchar *tmp = g_strdup(begin);
2443 tmp[cur-begin]='\0';
2446 while (*tmp == ' ' || *tmp == '\t')
2448 compose_add_header_entry(compose, header, tmp);
2455 gchar *tmp = g_strdup(begin);
2457 tmp[cur-begin]='\0';
2460 while (*tmp == ' ' || *tmp == '\t')
2462 compose_add_header_entry(compose, header, tmp);
2467 static void compose_entry_mark_default_to(Compose *compose, const gchar *mailto)
2469 static GdkColor yellow;
2470 static GdkColor black;
2471 static gboolean yellow_initialised = FALSE;
2475 if (!yellow_initialised) {
2476 gdk_color_parse("#f5f6be", &yellow);
2477 gdk_color_parse("#000000", &black);
2478 yellow_initialised = gdk_colormap_alloc_color(
2479 gdk_colormap_get_system(), &yellow, FALSE, TRUE);
2480 yellow_initialised &= gdk_colormap_alloc_color(
2481 gdk_colormap_get_system(), &black, FALSE, TRUE);
2484 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2485 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2486 if (gtk_entry_get_text(entry) &&
2487 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2488 if (yellow_initialised) {
2489 gtk_widget_modify_base(
2490 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2491 GTK_STATE_NORMAL, &yellow);
2492 gtk_widget_modify_text(
2493 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2494 GTK_STATE_NORMAL, &black);
2500 void compose_toolbar_cb(gint action, gpointer data)
2502 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2503 Compose *compose = (Compose*)toolbar_item->parent;
2505 g_return_if_fail(compose != NULL);
2509 compose_send_cb(compose, 0, NULL);
2512 compose_send_later_cb(compose, 0, NULL);
2515 compose_draft_cb(compose, COMPOSE_QUIT_EDITING, NULL);
2518 compose_insert_file_cb(compose, 0, NULL);
2521 compose_attach_cb(compose, 0, NULL);
2524 compose_insert_sig(compose, FALSE);
2527 compose_ext_editor_cb(compose, 0, NULL);
2529 case A_LINEWRAP_CURRENT:
2530 compose_beautify_paragraph(compose, NULL, TRUE);
2532 case A_LINEWRAP_ALL:
2533 compose_wrap_all_full(compose, TRUE);
2536 compose_address_cb(compose, 0, NULL);
2539 case A_CHECK_SPELLING:
2540 compose_check_all(compose);
2548 static void compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
2553 gchar *subject = NULL;
2557 gchar **attach = NULL;
2559 /* get mailto parts but skip from */
2560 scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach);
2563 compose_entry_append(compose, to, to_type);
2565 compose_entry_append(compose, cc, COMPOSE_CC);
2567 compose_entry_append(compose, bcc, COMPOSE_BCC);
2569 if (!g_utf8_validate (subject, -1, NULL)) {
2570 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2571 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2574 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2578 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2579 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2582 gboolean prev_autowrap = compose->autowrap;
2584 compose->autowrap = FALSE;
2586 mark = gtk_text_buffer_get_insert(buffer);
2587 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2589 if (!g_utf8_validate (body, -1, NULL)) {
2590 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2591 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2594 gtk_text_buffer_insert(buffer, &iter, body, -1);
2596 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2598 compose->autowrap = prev_autowrap;
2599 if (compose->autowrap)
2600 compose_wrap_all(compose);
2604 gint i = 0, att = 0;
2605 gchar *warn_files = NULL;
2606 while (attach[i] != NULL) {
2607 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2608 if (utf8_filename) {
2609 if (compose_attach_append(compose, attach[i], utf8_filename, NULL)) {
2610 gchar *tmp = g_strdup_printf("%s%s\n",
2611 warn_files?warn_files:"",
2617 g_free(utf8_filename);
2619 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2624 alertpanel_notice(ngettext(
2625 "The following file has been attached: \n%s",
2626 "The following files have been attached: \n%s", att), warn_files);
2638 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2640 static HeaderEntry hentry[] = {{"Reply-To:", NULL, TRUE},
2641 {"Cc:", NULL, TRUE},
2642 {"References:", NULL, FALSE},
2643 {"Bcc:", NULL, TRUE},
2644 {"Newsgroups:", NULL, TRUE},
2645 {"Followup-To:", NULL, TRUE},
2646 {"List-Post:", NULL, FALSE},
2647 {"X-Priority:", NULL, FALSE},
2648 {NULL, NULL, FALSE}};
2664 g_return_val_if_fail(msginfo != NULL, -1);
2666 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2667 procheader_get_header_fields(fp, hentry);
2670 if (hentry[H_REPLY_TO].body != NULL) {
2671 if (hentry[H_REPLY_TO].body[0] != '\0') {
2673 conv_unmime_header(hentry[H_REPLY_TO].body,
2676 g_free(hentry[H_REPLY_TO].body);
2677 hentry[H_REPLY_TO].body = NULL;
2679 if (hentry[H_CC].body != NULL) {
2680 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL);
2681 g_free(hentry[H_CC].body);
2682 hentry[H_CC].body = NULL;
2684 if (hentry[H_REFERENCES].body != NULL) {
2685 if (compose->mode == COMPOSE_REEDIT)
2686 compose->references = hentry[H_REFERENCES].body;
2688 compose->references = compose_parse_references
2689 (hentry[H_REFERENCES].body, msginfo->msgid);
2690 g_free(hentry[H_REFERENCES].body);
2692 hentry[H_REFERENCES].body = NULL;
2694 if (hentry[H_BCC].body != NULL) {
2695 if (compose->mode == COMPOSE_REEDIT)
2697 conv_unmime_header(hentry[H_BCC].body, NULL);
2698 g_free(hentry[H_BCC].body);
2699 hentry[H_BCC].body = NULL;
2701 if (hentry[H_NEWSGROUPS].body != NULL) {
2702 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2703 hentry[H_NEWSGROUPS].body = NULL;
2705 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2706 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2707 compose->followup_to =
2708 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2711 g_free(hentry[H_FOLLOWUP_TO].body);
2712 hentry[H_FOLLOWUP_TO].body = NULL;
2714 if (hentry[H_LIST_POST].body != NULL) {
2717 extract_address(hentry[H_LIST_POST].body);
2718 if (hentry[H_LIST_POST].body[0] != '\0') {
2719 scan_mailto_url(hentry[H_LIST_POST].body,
2720 NULL, &to, NULL, NULL, NULL, NULL, NULL);
2722 g_free(compose->ml_post);
2723 compose->ml_post = to;
2726 g_free(hentry[H_LIST_POST].body);
2727 hentry[H_LIST_POST].body = NULL;
2730 /* CLAWS - X-Priority */
2731 if (compose->mode == COMPOSE_REEDIT)
2732 if (hentry[H_X_PRIORITY].body != NULL) {
2735 priority = atoi(hentry[H_X_PRIORITY].body);
2736 g_free(hentry[H_X_PRIORITY].body);
2738 hentry[H_X_PRIORITY].body = NULL;
2740 if (priority < PRIORITY_HIGHEST ||
2741 priority > PRIORITY_LOWEST)
2742 priority = PRIORITY_NORMAL;
2744 compose->priority = priority;
2747 if (compose->mode == COMPOSE_REEDIT) {
2748 if (msginfo->inreplyto && *msginfo->inreplyto)
2749 compose->inreplyto = g_strdup(msginfo->inreplyto);
2753 if (msginfo->msgid && *msginfo->msgid)
2754 compose->inreplyto = g_strdup(msginfo->msgid);
2756 if (!compose->references) {
2757 if (msginfo->msgid && *msginfo->msgid) {
2758 if (msginfo->inreplyto && *msginfo->inreplyto)
2759 compose->references =
2760 g_strdup_printf("<%s>\n\t<%s>",
2764 compose->references =
2765 g_strconcat("<", msginfo->msgid, ">",
2767 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
2768 compose->references =
2769 g_strconcat("<", msginfo->inreplyto, ">",
2777 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
2779 GSList *ref_id_list, *cur;
2783 ref_id_list = references_list_append(NULL, ref);
2784 if (!ref_id_list) return NULL;
2785 if (msgid && *msgid)
2786 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
2791 for (cur = ref_id_list; cur != NULL; cur = cur->next)
2792 /* "<" + Message-ID + ">" + CR+LF+TAB */
2793 len += strlen((gchar *)cur->data) + 5;
2795 if (len > MAX_REFERENCES_LEN) {
2796 /* remove second message-ID */
2797 if (ref_id_list && ref_id_list->next &&
2798 ref_id_list->next->next) {
2799 g_free(ref_id_list->next->data);
2800 ref_id_list = g_slist_remove
2801 (ref_id_list, ref_id_list->next->data);
2803 slist_free_strings(ref_id_list);
2804 g_slist_free(ref_id_list);
2811 new_ref = g_string_new("");
2812 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
2813 if (new_ref->len > 0)
2814 g_string_append(new_ref, "\n\t");
2815 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
2818 slist_free_strings(ref_id_list);
2819 g_slist_free(ref_id_list);
2821 new_ref_str = new_ref->str;
2822 g_string_free(new_ref, FALSE);
2827 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
2828 const gchar *fmt, const gchar *qmark,
2829 const gchar *body, gboolean rewrap,
2830 gboolean need_unescape,
2831 const gchar *err_msg)
2833 MsgInfo* dummyinfo = NULL;
2834 gchar *quote_str = NULL;
2836 gboolean prev_autowrap;
2837 const gchar *trimmed_body = body;
2838 gint cursor_pos = -1;
2839 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2840 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2845 SIGNAL_BLOCK(buffer);
2848 dummyinfo = compose_msginfo_new_from_compose(compose);
2849 msginfo = dummyinfo;
2852 if (qmark != NULL) {
2854 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
2855 compose->gtkaspell);
2857 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
2859 quote_fmt_scan_string(qmark);
2862 buf = quote_fmt_get_buffer();
2864 alertpanel_error(_("Quote mark format error."));
2866 Xstrdup_a(quote_str, buf, goto error)
2869 if (fmt && *fmt != '\0') {
2872 while (*trimmed_body == '\n')
2876 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
2877 compose->gtkaspell);
2879 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
2881 if (need_unescape) {
2884 /* decode \-escape sequences in the internal representation of the quote format */
2885 tmp = malloc(strlen(fmt)+1);
2886 pref_get_unescaped_pref(tmp, fmt);
2887 quote_fmt_scan_string(tmp);
2891 quote_fmt_scan_string(fmt);
2895 buf = quote_fmt_get_buffer();
2897 gint line = quote_fmt_get_line();
2898 alertpanel_error(err_msg, line);
2904 prev_autowrap = compose->autowrap;
2905 compose->autowrap = FALSE;
2907 mark = gtk_text_buffer_get_insert(buffer);
2908 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2909 if (g_utf8_validate(buf, -1, NULL)) {
2910 gtk_text_buffer_insert(buffer, &iter, buf, -1);
2912 gchar *tmpout = NULL;
2913 tmpout = conv_codeset_strdup
2914 (buf, conv_get_locale_charset_str_no_utf8(),
2916 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
2918 tmpout = g_malloc(strlen(buf)*2+1);
2919 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
2921 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
2925 cursor_pos = quote_fmt_get_cursor_pos();
2926 if (cursor_pos == -1)
2927 cursor_pos = gtk_text_iter_get_offset(&iter);
2928 compose->set_cursor_pos = cursor_pos;
2930 gtk_text_buffer_get_start_iter(buffer, &iter);
2931 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
2932 gtk_text_buffer_place_cursor(buffer, &iter);
2934 compose->autowrap = prev_autowrap;
2935 if (compose->autowrap && rewrap)
2936 compose_wrap_all(compose);
2943 SIGNAL_UNBLOCK(buffer);
2945 procmsg_msginfo_free( dummyinfo );
2950 /* if ml_post is of type addr@host and from is of type
2951 * addr-anything@host, return TRUE
2953 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
2955 gchar *left_ml = NULL;
2956 gchar *right_ml = NULL;
2957 gchar *left_from = NULL;
2958 gchar *right_from = NULL;
2959 gboolean result = FALSE;
2961 if (!ml_post || !from)
2964 left_ml = g_strdup(ml_post);
2965 if (strstr(left_ml, "@")) {
2966 right_ml = strstr(left_ml, "@")+1;
2967 *(strstr(left_ml, "@")) = '\0';
2970 left_from = g_strdup(from);
2971 if (strstr(left_from, "@")) {
2972 right_from = strstr(left_from, "@")+1;
2973 *(strstr(left_from, "@")) = '\0';
2976 if (left_ml && left_from && right_ml && right_from
2977 && !strncmp(left_from, left_ml, strlen(left_ml))
2978 && !strcmp(right_from, right_ml)) {
2987 static gboolean same_address(const gchar *addr1, const gchar *addr2)
2989 gchar *my_addr1, *my_addr2;
2991 if (!addr1 || !addr2)
2994 Xstrdup_a(my_addr1, addr1, return FALSE);
2995 Xstrdup_a(my_addr2, addr2, return FALSE);
2997 extract_address(my_addr1);
2998 extract_address(my_addr2);
3000 return !strcasecmp(my_addr1, my_addr2);
3003 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3004 gboolean to_all, gboolean to_ml,
3006 gboolean followup_and_reply_to)
3008 GSList *cc_list = NULL;
3011 gchar *replyto = NULL;
3012 GHashTable *to_table;
3014 gboolean reply_to_ml = FALSE;
3015 gboolean default_reply_to = FALSE;
3017 g_return_if_fail(compose->account != NULL);
3018 g_return_if_fail(msginfo != NULL);
3020 reply_to_ml = to_ml && compose->ml_post;
3022 default_reply_to = msginfo->folder &&
3023 msginfo->folder->prefs->enable_default_reply_to;
3025 if (compose->account->protocol != A_NNTP) {
3026 if (reply_to_ml && !default_reply_to) {
3028 gboolean is_subscr = is_subscription(compose->ml_post,
3031 /* normal answer to ml post with a reply-to */
3032 compose_entry_append(compose,
3035 if (compose->replyto
3036 && !same_address(compose->ml_post, compose->replyto))
3037 compose_entry_append(compose,
3041 /* answer to subscription confirmation */
3042 if (compose->replyto)
3043 compose_entry_append(compose,
3046 else if (msginfo->from)
3047 compose_entry_append(compose,
3052 else if (!(to_all || to_sender) && default_reply_to) {
3053 compose_entry_append(compose,
3054 msginfo->folder->prefs->default_reply_to,
3056 compose_entry_mark_default_to(compose,
3057 msginfo->folder->prefs->default_reply_to);
3062 Xstrdup_a(tmp1, msginfo->from, return);
3063 extract_address(tmp1);
3064 if (to_all || to_sender ||
3065 !account_find_from_address(tmp1, FALSE))
3066 compose_entry_append(compose,
3067 (compose->replyto && !to_sender)
3068 ? compose->replyto :
3069 msginfo->from ? msginfo->from : "",
3071 else if (!to_all && !to_sender) {
3072 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3073 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3074 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3075 if (compose->replyto) {
3076 compose_entry_append(compose,
3080 compose_entry_append(compose,
3081 msginfo->from ? msginfo->from : "",
3085 /* replying to own mail, use original recp */
3086 compose_entry_append(compose,
3087 msginfo->to ? msginfo->to : "",
3089 compose_entry_append(compose,
3090 msginfo->cc ? msginfo->cc : "",
3096 if (to_sender || (compose->followup_to &&
3097 !strncmp(compose->followup_to, "poster", 6)))
3098 compose_entry_append
3100 (compose->replyto ? compose->replyto :
3101 msginfo->from ? msginfo->from : ""),
3104 else if (followup_and_reply_to || to_all) {
3105 compose_entry_append
3107 (compose->replyto ? compose->replyto :
3108 msginfo->from ? msginfo->from : ""),
3111 compose_entry_append
3113 compose->followup_to ? compose->followup_to :
3114 compose->newsgroups ? compose->newsgroups : "",
3115 COMPOSE_NEWSGROUPS);
3118 compose_entry_append
3120 compose->followup_to ? compose->followup_to :
3121 compose->newsgroups ? compose->newsgroups : "",
3122 COMPOSE_NEWSGROUPS);
3125 if (msginfo->subject && *msginfo->subject) {
3129 buf = p = g_strdup(msginfo->subject);
3130 p += subject_get_prefix_length(p);
3131 memmove(buf, p, strlen(p) + 1);
3133 buf2 = g_strdup_printf("Re: %s", buf);
3134 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3139 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3141 if (to_ml && compose->ml_post) return;
3142 if (!to_all || compose->account->protocol == A_NNTP) return;
3144 if (compose->replyto) {
3145 Xstrdup_a(replyto, compose->replyto, return);
3146 extract_address(replyto);
3148 if (msginfo->from) {
3149 Xstrdup_a(from, msginfo->from, return);
3150 extract_address(from);
3153 if (replyto && from)
3154 cc_list = address_list_append_with_comments(cc_list, from);
3155 if (to_all && msginfo->folder &&
3156 msginfo->folder->prefs->enable_default_reply_to)
3157 cc_list = address_list_append_with_comments(cc_list,
3158 msginfo->folder->prefs->default_reply_to);
3159 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3160 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3162 to_table = g_hash_table_new(g_str_hash, g_str_equal);
3164 g_hash_table_insert(to_table, g_utf8_strdown(replyto, -1), GINT_TO_POINTER(1));
3165 if (compose->account) {
3166 g_hash_table_insert(to_table, g_utf8_strdown(compose->account->address, -1),
3167 GINT_TO_POINTER(1));
3169 /* remove address on To: and that of current account */
3170 for (cur = cc_list; cur != NULL; ) {
3171 GSList *next = cur->next;
3174 addr = g_utf8_strdown(cur->data, -1);
3175 extract_address(addr);
3177 if (GPOINTER_TO_INT(g_hash_table_lookup(to_table, addr)) == 1)
3178 cc_list = g_slist_remove(cc_list, cur->data);
3180 g_hash_table_insert(to_table, addr, GINT_TO_POINTER(1));
3184 hash_free_strings(to_table);
3185 g_hash_table_destroy(to_table);
3188 for (cur = cc_list; cur != NULL; cur = cur->next)
3189 compose_entry_append(compose, (gchar *)cur->data,
3191 slist_free_strings(cc_list);
3192 g_slist_free(cc_list);
3197 #define SET_ENTRY(entry, str) \
3200 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3203 #define SET_ADDRESS(type, str) \
3206 compose_entry_append(compose, str, type); \
3209 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3211 g_return_if_fail(msginfo != NULL);
3213 SET_ENTRY(subject_entry, msginfo->subject);
3214 SET_ENTRY(from_name, msginfo->from);
3215 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3216 SET_ADDRESS(COMPOSE_CC, compose->cc);
3217 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3218 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3219 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3220 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3222 compose_update_priority_menu_item(compose);
3223 compose_update_privacy_system_menu_item(compose, FALSE);
3224 compose_show_first_last_header(compose, TRUE);
3230 static void compose_insert_sig(Compose *compose, gboolean replace)
3232 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3233 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3235 GtkTextIter iter, iter_end;
3236 gint cur_pos, ins_pos;
3237 gboolean prev_autowrap;
3238 gboolean found = FALSE;
3239 gboolean exists = FALSE;
3241 g_return_if_fail(compose->account != NULL);
3245 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3246 G_CALLBACK(compose_changed_cb),
3249 mark = gtk_text_buffer_get_insert(buffer);
3250 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3251 cur_pos = gtk_text_iter_get_offset (&iter);
3254 gtk_text_buffer_get_end_iter(buffer, &iter);
3256 exists = (compose->sig_str != NULL);
3259 GtkTextIter first_iter, start_iter, end_iter;
3261 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3263 if (!exists || compose->sig_str[0] == '\0')
3266 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3267 compose->signature_tag);
3270 /* include previous \n\n */
3271 gtk_text_iter_backward_chars(&first_iter, 1);
3272 start_iter = first_iter;
3273 end_iter = first_iter;
3275 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3276 compose->signature_tag);
3277 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3278 compose->signature_tag);
3280 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3286 g_free(compose->sig_str);
3287 compose->sig_str = compose_get_signature_str(compose);
3289 cur_pos = gtk_text_iter_get_offset(&iter);
3291 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3292 g_free(compose->sig_str);
3293 compose->sig_str = NULL;
3295 if (compose->sig_inserted == FALSE)
3296 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3297 compose->sig_inserted = TRUE;
3299 cur_pos = gtk_text_iter_get_offset(&iter);
3300 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3302 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3303 gtk_text_iter_forward_chars(&iter, 1);
3304 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3305 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3307 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3308 cur_pos = gtk_text_buffer_get_char_count (buffer);
3311 /* put the cursor where it should be
3312 * either where the quote_fmt says, either where it was */
3313 if (compose->set_cursor_pos < 0)
3314 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3316 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3317 compose->set_cursor_pos);
3319 gtk_text_buffer_place_cursor(buffer, &iter);
3320 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3321 G_CALLBACK(compose_changed_cb),
3327 static gchar *compose_get_signature_str(Compose *compose)
3329 gchar *sig_body = NULL;
3330 gchar *sig_str = NULL;
3331 gchar *utf8_sig_str = NULL;
3333 g_return_val_if_fail(compose->account != NULL, NULL);
3335 if (!compose->account->sig_path)
3338 if (compose->account->sig_type == SIG_FILE) {
3339 if (!is_file_or_fifo_exist(compose->account->sig_path)) {
3340 g_warning("can't open signature file: %s\n",
3341 compose->account->sig_path);
3346 if (compose->account->sig_type == SIG_COMMAND)
3347 sig_body = get_command_output(compose->account->sig_path);
3351 tmp = file_read_to_str(compose->account->sig_path);
3354 sig_body = normalize_newlines(tmp);
3358 if (compose->account->sig_sep) {
3359 sig_str = g_strconcat("\n", compose->account->sig_sep, "\n", sig_body,
3363 sig_str = g_strconcat("\n", sig_body, NULL);
3366 if (g_utf8_validate(sig_str, -1, NULL) == TRUE)
3367 utf8_sig_str = sig_str;
3369 utf8_sig_str = conv_codeset_strdup
3370 (sig_str, conv_get_locale_charset_str_no_utf8(),
3376 return utf8_sig_str;
3379 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3382 GtkTextBuffer *buffer;
3385 const gchar *cur_encoding;
3386 gchar buf[BUFFSIZE];
3389 gboolean prev_autowrap;
3390 gboolean badtxt = FALSE;
3392 g_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3394 if ((fp = g_fopen(file, "rb")) == NULL) {
3395 FILE_OP_ERROR(file, "fopen");
3396 return COMPOSE_INSERT_READ_ERROR;
3399 prev_autowrap = compose->autowrap;
3400 compose->autowrap = FALSE;
3402 text = GTK_TEXT_VIEW(compose->text);
3403 buffer = gtk_text_view_get_buffer(text);
3404 mark = gtk_text_buffer_get_insert(buffer);
3405 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3407 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3408 G_CALLBACK(text_inserted),
3411 cur_encoding = conv_get_locale_charset_str_no_utf8();
3413 while (fgets(buf, sizeof(buf), fp) != NULL) {
3416 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3417 str = g_strdup(buf);
3419 str = conv_codeset_strdup
3420 (buf, cur_encoding, CS_INTERNAL);
3423 /* strip <CR> if DOS/Windows file,
3424 replace <CR> with <LF> if Macintosh file. */
3427 if (len > 0 && str[len - 1] != '\n') {
3429 if (str[len] == '\r') str[len] = '\n';
3432 gtk_text_buffer_insert(buffer, &iter, str, -1);
3436 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3437 G_CALLBACK(text_inserted),
3439 compose->autowrap = prev_autowrap;
3440 if (compose->autowrap)
3441 compose_wrap_all(compose);
3446 return COMPOSE_INSERT_INVALID_CHARACTER;
3448 return COMPOSE_INSERT_SUCCESS;
3451 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3452 const gchar *filename,
3453 const gchar *content_type)
3461 GtkListStore *store;
3463 gboolean has_binary = FALSE;
3465 if (!is_file_exist(file)) {
3466 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3467 gboolean result = FALSE;
3468 if (file_from_uri && is_file_exist(file_from_uri)) {
3469 result = compose_attach_append(
3470 compose, file_from_uri,
3474 g_free(file_from_uri);
3477 alertpanel_error("File %s doesn't exist\n", filename);
3480 if ((size = get_file_size(file)) < 0) {
3481 alertpanel_error("Can't get file size of %s\n", filename);
3485 alertpanel_error(_("File %s is empty."), filename);
3488 if ((fp = g_fopen(file, "rb")) == NULL) {
3489 alertpanel_error(_("Can't read %s."), filename);
3494 ainfo = g_new0(AttachInfo, 1);
3495 auto_ainfo = g_auto_pointer_new_with_free
3496 (ainfo, (GFreeFunc) compose_attach_info_free);
3497 ainfo->file = g_strdup(file);
3500 ainfo->content_type = g_strdup(content_type);
3501 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3503 MsgFlags flags = {0, 0};
3505 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3506 ainfo->encoding = ENC_7BIT;
3508 ainfo->encoding = ENC_8BIT;
3510 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3511 if (msginfo && msginfo->subject)
3512 name = g_strdup(msginfo->subject);
3514 name = g_path_get_basename(filename ? filename : file);
3516 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3518 procmsg_msginfo_free(msginfo);
3520 if (!g_ascii_strncasecmp(content_type, "text", 4))
3521 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3523 ainfo->encoding = ENC_BASE64;
3524 name = g_path_get_basename(filename ? filename : file);
3525 ainfo->name = g_strdup(name);
3529 ainfo->content_type = procmime_get_mime_type(file);
3530 if (!ainfo->content_type) {
3531 ainfo->content_type =
3532 g_strdup("application/octet-stream");
3533 ainfo->encoding = ENC_BASE64;
3534 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text", 4))
3536 procmime_get_encoding_for_text_file(file, &has_binary);
3538 ainfo->encoding = ENC_BASE64;
3539 name = g_path_get_basename(filename ? filename : file);
3540 ainfo->name = g_strdup(name);
3544 if (ainfo->name != NULL
3545 && !strcmp(ainfo->name, ".")) {
3546 g_free(ainfo->name);
3550 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3551 g_free(ainfo->content_type);
3552 ainfo->content_type = g_strdup("application/octet-stream");
3555 ainfo->size = (goffset)size;
3556 size_text = to_human_readable((goffset)size);
3558 store = GTK_LIST_STORE(gtk_tree_view_get_model
3559 (GTK_TREE_VIEW(compose->attach_clist)));
3561 gtk_list_store_append(store, &iter);
3562 gtk_list_store_set(store, &iter,
3563 COL_MIMETYPE, ainfo->content_type,
3564 COL_SIZE, size_text,
3565 COL_NAME, ainfo->name,
3567 COL_AUTODATA, auto_ainfo,
3570 g_auto_pointer_free(auto_ainfo);
3571 compose_attach_update_label(compose);
3575 static void compose_use_signing(Compose *compose, gboolean use_signing)
3577 GtkItemFactory *ifactory;
3578 GtkWidget *menuitem = NULL;
3580 compose->use_signing = use_signing;
3581 ifactory = gtk_item_factory_from_widget(compose->menubar);
3582 menuitem = gtk_item_factory_get_item
3583 (ifactory, "/Options/Sign");
3584 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
3588 static void compose_use_encryption(Compose *compose, gboolean use_encryption)
3590 GtkItemFactory *ifactory;
3591 GtkWidget *menuitem = NULL;
3593 compose->use_encryption = use_encryption;
3594 ifactory = gtk_item_factory_from_widget(compose->menubar);
3595 menuitem = gtk_item_factory_get_item
3596 (ifactory, "/Options/Encrypt");
3598 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
3602 #define NEXT_PART_NOT_CHILD(info) \
3604 node = info->node; \
3605 while (node->children) \
3606 node = g_node_last_child(node); \
3607 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3610 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3614 MimeInfo *firsttext = NULL;
3615 MimeInfo *encrypted = NULL;
3618 const gchar *partname = NULL;
3620 mimeinfo = procmime_scan_message(msginfo);
3621 if (!mimeinfo) return;
3623 if (mimeinfo->node->children == NULL) {
3624 procmime_mimeinfo_free_all(mimeinfo);
3628 /* find first content part */
3629 child = (MimeInfo *) mimeinfo->node->children->data;
3630 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3631 child = (MimeInfo *)child->node->children->data;
3633 if (child->type == MIMETYPE_TEXT) {
3635 debug_print("First text part found\n");
3636 } else if (compose->mode == COMPOSE_REEDIT &&
3637 child->type == MIMETYPE_APPLICATION &&
3638 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3639 encrypted = (MimeInfo *)child->node->parent->data;
3642 child = (MimeInfo *) mimeinfo->node->children->data;
3643 while (child != NULL) {
3646 if (child == encrypted) {
3647 /* skip this part of tree */
3648 NEXT_PART_NOT_CHILD(child);
3652 if (child->type == MIMETYPE_MULTIPART) {
3653 /* get the actual content */
3654 child = procmime_mimeinfo_next(child);
3658 if (child == firsttext) {
3659 child = procmime_mimeinfo_next(child);
3663 outfile = procmime_get_tmp_file_name(child);
3664 if ((err = procmime_get_part(outfile, child)) < 0)
3665 g_warning("Can't get the part of multipart message. (%s)", strerror(-err));
3667 gchar *content_type;
3669 content_type = procmime_get_content_type_str(child->type, child->subtype);
3671 /* if we meet a pgp signature, we don't attach it, but
3672 * we force signing. */
3673 if ((strcmp(content_type, "application/pgp-signature") &&
3674 strcmp(content_type, "application/pkcs7-signature") &&
3675 strcmp(content_type, "application/x-pkcs7-signature"))
3676 || compose->mode == COMPOSE_REDIRECT) {
3677 partname = procmime_mimeinfo_get_parameter(child, "filename");
3678 if (partname == NULL)
3679 partname = procmime_mimeinfo_get_parameter(child, "name");
3680 if (partname == NULL)
3682 compose_attach_append(compose, outfile,
3683 partname, content_type);
3685 compose_force_signing(compose, compose->account);
3687 g_free(content_type);
3690 NEXT_PART_NOT_CHILD(child);
3692 procmime_mimeinfo_free_all(mimeinfo);
3695 #undef NEXT_PART_NOT_CHILD
3700 WAIT_FOR_INDENT_CHAR,
3701 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3704 /* return indent length, we allow:
3705 indent characters followed by indent characters or spaces/tabs,
3706 alphabets and numbers immediately followed by indent characters,
3707 and the repeating sequences of the above
3708 If quote ends with multiple spaces, only the first one is included. */
3709 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3710 const GtkTextIter *start, gint *len)
3712 GtkTextIter iter = *start;
3716 IndentState state = WAIT_FOR_INDENT_CHAR;
3719 gint alnum_count = 0;
3720 gint space_count = 0;
3723 if (prefs_common.quote_chars == NULL) {
3727 while (!gtk_text_iter_ends_line(&iter)) {
3728 wc = gtk_text_iter_get_char(&iter);
3729 if (g_unichar_iswide(wc))
3731 clen = g_unichar_to_utf8(wc, ch);
3735 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
3736 is_space = g_unichar_isspace(wc);
3738 if (state == WAIT_FOR_INDENT_CHAR) {
3739 if (!is_indent && !g_unichar_isalnum(wc))
3742 quote_len += alnum_count + space_count + 1;
3743 alnum_count = space_count = 0;
3744 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
3747 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
3748 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
3752 else if (is_indent) {
3753 quote_len += alnum_count + space_count + 1;
3754 alnum_count = space_count = 0;
3757 state = WAIT_FOR_INDENT_CHAR;
3761 gtk_text_iter_forward_char(&iter);
3764 if (quote_len > 0 && space_count > 0)
3770 if (quote_len > 0) {
3772 gtk_text_iter_forward_chars(&iter, quote_len);
3773 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
3779 /* return TRUE if the line is itemized */
3780 static gboolean compose_is_itemized(GtkTextBuffer *buffer,
3781 const GtkTextIter *start)
3783 GtkTextIter iter = *start;
3788 if (gtk_text_iter_ends_line(&iter))
3792 wc = gtk_text_iter_get_char(&iter);
3793 if (!g_unichar_isspace(wc))
3795 gtk_text_iter_forward_char(&iter);
3796 if (gtk_text_iter_ends_line(&iter))
3800 clen = g_unichar_to_utf8(wc, ch);
3804 if (!strchr("*-+", ch[0]))
3807 gtk_text_iter_forward_char(&iter);
3808 if (gtk_text_iter_ends_line(&iter))
3810 wc = gtk_text_iter_get_char(&iter);
3811 if (g_unichar_isspace(wc))
3817 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
3818 const GtkTextIter *start,
3819 GtkTextIter *break_pos,
3823 GtkTextIter iter = *start, line_end = *start;
3824 PangoLogAttr *attrs;
3831 gboolean can_break = FALSE;
3832 gboolean do_break = FALSE;
3833 gboolean was_white = FALSE;
3834 gboolean prev_dont_break = FALSE;
3836 gtk_text_iter_forward_to_line_end(&line_end);
3837 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
3838 len = g_utf8_strlen(str, -1);
3842 g_warning("compose_get_line_break_pos: len = 0!\n");
3846 /* g_print("breaking line: %d: %s (len = %d)\n",
3847 gtk_text_iter_get_line(&iter), str, len); */
3849 attrs = g_new(PangoLogAttr, len + 1);
3851 pango_default_break(str, -1, NULL, attrs, len + 1);
3855 /* skip quote and leading spaces */
3856 for (i = 0; *p != '\0' && i < len; i++) {
3859 wc = g_utf8_get_char(p);
3860 if (i >= quote_len && !g_unichar_isspace(wc))
3862 if (g_unichar_iswide(wc))
3864 else if (*p == '\t')
3868 p = g_utf8_next_char(p);
3871 for (; *p != '\0' && i < len; i++) {
3872 PangoLogAttr *attr = attrs + i;
3876 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
3879 was_white = attr->is_white;
3881 /* don't wrap URI */
3882 if ((uri_len = get_uri_len(p)) > 0) {
3884 if (pos > 0 && col > max_col) {
3894 wc = g_utf8_get_char(p);
3895 if (g_unichar_iswide(wc)) {
3897 if (prev_dont_break && can_break && attr->is_line_break)
3899 } else if (*p == '\t')
3903 if (pos > 0 && col > max_col) {
3908 if (*p == '-' || *p == '/')
3909 prev_dont_break = TRUE;
3911 prev_dont_break = FALSE;
3913 p = g_utf8_next_char(p);
3917 debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col);
3922 *break_pos = *start;
3923 gtk_text_iter_set_line_offset(break_pos, pos);
3928 static gboolean compose_join_next_line(Compose *compose,
3929 GtkTextBuffer *buffer,
3931 const gchar *quote_str)
3933 GtkTextIter iter_ = *iter, cur, prev, next, end;
3934 PangoLogAttr attrs[3];
3936 gchar *next_quote_str;
3939 gboolean keep_cursor = FALSE;
3941 if (!gtk_text_iter_forward_line(&iter_) ||
3942 gtk_text_iter_ends_line(&iter_)) {
3945 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
3947 if ((quote_str || next_quote_str) &&
3948 strcmp2(quote_str, next_quote_str) != 0) {
3949 g_free(next_quote_str);
3952 g_free(next_quote_str);
3955 if (quote_len > 0) {
3956 gtk_text_iter_forward_chars(&end, quote_len);
3957 if (gtk_text_iter_ends_line(&end)) {
3962 /* don't join itemized lines */
3963 if (compose_is_itemized(buffer, &end)) {
3967 /* don't join signature separator */
3968 if (compose_is_sig_separator(compose, buffer, &iter_)) {
3971 /* delete quote str */
3973 gtk_text_buffer_delete(buffer, &iter_, &end);
3975 /* don't join line breaks put by the user */
3977 gtk_text_iter_backward_char(&cur);
3978 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
3979 gtk_text_iter_forward_char(&cur);
3983 gtk_text_iter_forward_char(&cur);
3984 /* delete linebreak and extra spaces */
3985 while (gtk_text_iter_backward_char(&cur)) {
3986 wc1 = gtk_text_iter_get_char(&cur);
3987 if (!g_unichar_isspace(wc1))
3992 while (!gtk_text_iter_ends_line(&cur)) {
3993 wc1 = gtk_text_iter_get_char(&cur);
3994 if (!g_unichar_isspace(wc1))
3996 gtk_text_iter_forward_char(&cur);
3999 if (!gtk_text_iter_equal(&prev, &next)) {
4002 mark = gtk_text_buffer_get_insert(buffer);
4003 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4004 if (gtk_text_iter_equal(&prev, &cur))
4006 gtk_text_buffer_delete(buffer, &prev, &next);
4010 /* insert space if required */
4011 gtk_text_iter_backward_char(&prev);
4012 wc1 = gtk_text_iter_get_char(&prev);
4013 wc2 = gtk_text_iter_get_char(&next);
4014 gtk_text_iter_forward_char(&next);
4015 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4016 pango_default_break(str, -1, NULL, attrs, 3);
4017 if (!attrs[1].is_line_break ||
4018 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4019 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4021 gtk_text_iter_backward_char(&iter_);
4022 gtk_text_buffer_place_cursor(buffer, &iter_);
4031 #define ADD_TXT_POS(bp_, ep_, pti_) \
4032 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4033 last = last->next; \
4034 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4035 last->next = NULL; \
4037 g_warning("alloc error scanning URIs\n"); \
4040 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4042 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4043 GtkTextBuffer *buffer;
4044 GtkTextIter iter, break_pos, end_of_line;
4045 gchar *quote_str = NULL;
4047 gboolean wrap_quote = prefs_common.linewrap_quote;
4048 gboolean prev_autowrap = compose->autowrap;
4049 gint startq_offset = -1, noq_offset = -1;
4050 gint uri_start = -1, uri_stop = -1;
4051 gint nouri_start = -1, nouri_stop = -1;
4052 gint num_blocks = 0;
4053 gint quotelevel = -1;
4054 gboolean modified = force;
4055 gboolean removed = FALSE;
4056 gboolean modified_before_remove = FALSE;
4058 gboolean start = TRUE;
4063 if (compose->draft_timeout_tag == -2) {
4067 compose->autowrap = FALSE;
4069 buffer = gtk_text_view_get_buffer(text);
4070 undo_wrapping(compose->undostruct, TRUE);
4075 mark = gtk_text_buffer_get_insert(buffer);
4076 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4080 if (compose->draft_timeout_tag == -2) {
4081 if (gtk_text_iter_ends_line(&iter)) {
4082 while (gtk_text_iter_ends_line(&iter) &&
4083 gtk_text_iter_forward_line(&iter))
4086 while (gtk_text_iter_backward_line(&iter)) {
4087 if (gtk_text_iter_ends_line(&iter)) {
4088 gtk_text_iter_forward_line(&iter);
4094 /* move to line start */
4095 gtk_text_iter_set_line_offset(&iter, 0);
4097 /* go until paragraph end (empty line) */
4098 while (start || !gtk_text_iter_ends_line(&iter)) {
4099 gchar *scanpos = NULL;
4100 /* parse table - in order of priority */
4102 const gchar *needle; /* token */
4104 /* token search function */
4105 gchar *(*search) (const gchar *haystack,
4106 const gchar *needle);
4107 /* part parsing function */
4108 gboolean (*parse) (const gchar *start,
4109 const gchar *scanpos,
4113 /* part to URI function */
4114 gchar *(*build_uri) (const gchar *bp,
4118 static struct table parser[] = {
4119 {"http://", strcasestr, get_uri_part, make_uri_string},
4120 {"https://", strcasestr, get_uri_part, make_uri_string},
4121 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4122 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4123 {"www.", strcasestr, get_uri_part, make_http_string},
4124 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4125 {"@", strcasestr, get_email_part, make_email_string}
4127 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4128 gint last_index = PARSE_ELEMS;
4130 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4134 if (!prev_autowrap && num_blocks == 0) {
4136 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4137 G_CALLBACK(text_inserted),
4140 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4143 uri_start = uri_stop = -1;
4145 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
4148 debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str);
4149 if (startq_offset == -1)
4150 startq_offset = gtk_text_iter_get_offset(&iter);
4151 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4152 if (quotelevel > 2) {
4153 /* recycle colors */
4154 if (prefs_common.recycle_quote_colors)
4163 if (startq_offset == -1)
4164 noq_offset = gtk_text_iter_get_offset(&iter);
4168 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4171 if (gtk_text_iter_ends_line(&iter)) {
4173 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4174 prefs_common.linewrap_len,
4176 GtkTextIter prev, next, cur;
4178 if (prev_autowrap != FALSE || force) {
4179 compose->automatic_break = TRUE;
4181 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4182 compose->automatic_break = FALSE;
4183 } else if (quote_str && wrap_quote) {
4184 compose->automatic_break = TRUE;
4186 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4187 compose->automatic_break = FALSE;
4190 /* remove trailing spaces */
4192 gtk_text_iter_backward_char(&cur);
4194 while (!gtk_text_iter_starts_line(&cur)) {
4197 gtk_text_iter_backward_char(&cur);
4198 wc = gtk_text_iter_get_char(&cur);
4199 if (!g_unichar_isspace(wc))
4203 if (!gtk_text_iter_equal(&prev, &next)) {
4204 gtk_text_buffer_delete(buffer, &prev, &next);
4206 gtk_text_iter_forward_char(&break_pos);
4210 gtk_text_buffer_insert(buffer, &break_pos,
4214 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4216 /* move iter to current line start */
4217 gtk_text_iter_set_line_offset(&iter, 0);
4224 /* move iter to next line start */
4230 if (!prev_autowrap && num_blocks > 0) {
4232 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4233 G_CALLBACK(text_inserted),
4237 while (!gtk_text_iter_ends_line(&end_of_line)) {
4238 gtk_text_iter_forward_char(&end_of_line);
4240 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4242 nouri_start = gtk_text_iter_get_offset(&iter);
4243 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4245 walk_pos = gtk_text_iter_get_offset(&iter);
4246 /* FIXME: this looks phony. scanning for anything in the parse table */
4247 for (n = 0; n < PARSE_ELEMS; n++) {
4250 tmp = parser[n].search(walk, parser[n].needle);
4252 if (scanpos == NULL || tmp < scanpos) {
4261 /* check if URI can be parsed */
4262 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4263 (const gchar **)&ep, FALSE)
4264 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4268 strlen(parser[last_index].needle);
4271 uri_start = walk_pos + (bp - o_walk);
4272 uri_stop = walk_pos + (ep - o_walk);
4276 gtk_text_iter_forward_line(&iter);
4279 if (startq_offset != -1) {
4280 GtkTextIter startquote, endquote;
4281 gtk_text_buffer_get_iter_at_offset(
4282 buffer, &startquote, startq_offset);
4285 switch (quotelevel) {
4287 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4288 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4289 gtk_text_buffer_apply_tag_by_name(
4290 buffer, "quote0", &startquote, &endquote);
4291 gtk_text_buffer_remove_tag_by_name(
4292 buffer, "quote1", &startquote, &endquote);
4293 gtk_text_buffer_remove_tag_by_name(
4294 buffer, "quote2", &startquote, &endquote);
4299 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4300 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4301 gtk_text_buffer_apply_tag_by_name(
4302 buffer, "quote1", &startquote, &endquote);
4303 gtk_text_buffer_remove_tag_by_name(
4304 buffer, "quote0", &startquote, &endquote);
4305 gtk_text_buffer_remove_tag_by_name(
4306 buffer, "quote2", &startquote, &endquote);
4311 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4312 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4313 gtk_text_buffer_apply_tag_by_name(
4314 buffer, "quote2", &startquote, &endquote);
4315 gtk_text_buffer_remove_tag_by_name(
4316 buffer, "quote0", &startquote, &endquote);
4317 gtk_text_buffer_remove_tag_by_name(
4318 buffer, "quote1", &startquote, &endquote);
4324 } else if (noq_offset != -1) {
4325 GtkTextIter startnoquote, endnoquote;
4326 gtk_text_buffer_get_iter_at_offset(
4327 buffer, &startnoquote, noq_offset);
4330 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4331 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4332 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4333 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4334 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4335 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4336 gtk_text_buffer_remove_tag_by_name(
4337 buffer, "quote0", &startnoquote, &endnoquote);
4338 gtk_text_buffer_remove_tag_by_name(
4339 buffer, "quote1", &startnoquote, &endnoquote);
4340 gtk_text_buffer_remove_tag_by_name(
4341 buffer, "quote2", &startnoquote, &endnoquote);
4347 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4348 GtkTextIter nouri_start_iter, nouri_end_iter;
4349 gtk_text_buffer_get_iter_at_offset(
4350 buffer, &nouri_start_iter, nouri_start);
4351 gtk_text_buffer_get_iter_at_offset(
4352 buffer, &nouri_end_iter, nouri_stop);
4353 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4354 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4355 gtk_text_buffer_remove_tag_by_name(
4356 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4357 modified_before_remove = modified;
4362 if (uri_start >= 0 && uri_stop > 0) {
4363 GtkTextIter uri_start_iter, uri_end_iter, back;
4364 gtk_text_buffer_get_iter_at_offset(
4365 buffer, &uri_start_iter, uri_start);
4366 gtk_text_buffer_get_iter_at_offset(
4367 buffer, &uri_end_iter, uri_stop);
4368 back = uri_end_iter;
4369 gtk_text_iter_backward_char(&back);
4370 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4371 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4372 gtk_text_buffer_apply_tag_by_name(
4373 buffer, "link", &uri_start_iter, &uri_end_iter);
4375 if (removed && !modified_before_remove) {
4381 debug_print("not modified, out after %d lines\n", lines);
4386 debug_print("modified, out after %d lines\n", lines);
4390 undo_wrapping(compose->undostruct, FALSE);
4391 compose->autowrap = prev_autowrap;
4396 void compose_action_cb(void *data)
4398 Compose *compose = (Compose *)data;
4399 compose_wrap_all(compose);
4402 static void compose_wrap_all(Compose *compose)
4404 compose_wrap_all_full(compose, FALSE);
4407 static void compose_wrap_all_full(Compose *compose, gboolean force)
4409 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4410 GtkTextBuffer *buffer;
4412 gboolean modified = TRUE;
4414 buffer = gtk_text_view_get_buffer(text);
4416 gtk_text_buffer_get_start_iter(buffer, &iter);
4417 while (!gtk_text_iter_is_end(&iter) && modified)
4418 modified = compose_beautify_paragraph(compose, &iter, force);
4422 static void compose_set_title(Compose *compose)
4428 edited = compose->modified ? _(" [Edited]") : "";
4430 subject = gtk_editable_get_chars(
4431 GTK_EDITABLE(compose->subject_entry), 0, -1);
4433 #ifndef GENERIC_UMPC
4434 if (subject && strlen(subject))
4435 str = g_strdup_printf(_("%s - Compose message%s"),
4438 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4440 str = g_strdup(_("Compose message"));
4443 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4449 * compose_current_mail_account:
4451 * Find a current mail account (the currently selected account, or the
4452 * default account, if a news account is currently selected). If a
4453 * mail account cannot be found, display an error message.
4455 * Return value: Mail account, or NULL if not found.
4457 static PrefsAccount *
4458 compose_current_mail_account(void)
4462 if (cur_account && cur_account->protocol != A_NNTP)
4465 ac = account_get_default();
4466 if (!ac || ac->protocol == A_NNTP) {
4467 alertpanel_error(_("Account for sending mail is not specified.\n"
4468 "Please select a mail account before sending."));
4475 #define QUOTE_IF_REQUIRED(out, str) \
4477 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4481 len = strlen(str) + 3; \
4482 if ((__tmp = alloca(len)) == NULL) { \
4483 g_warning("can't allocate memory\n"); \
4484 g_string_free(header, TRUE); \
4487 g_snprintf(__tmp, len, "\"%s\"", str); \
4492 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4493 g_warning("can't allocate memory\n"); \
4494 g_string_free(header, TRUE); \
4497 strcpy(__tmp, str); \
4503 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4505 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4509 len = strlen(str) + 3; \
4510 if ((__tmp = alloca(len)) == NULL) { \
4511 g_warning("can't allocate memory\n"); \
4514 g_snprintf(__tmp, len, "\"%s\"", str); \
4519 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4520 g_warning("can't allocate memory\n"); \
4523 strcpy(__tmp, str); \
4529 static void compose_select_account(Compose *compose, PrefsAccount *account,
4532 GtkItemFactory *ifactory;
4535 g_return_if_fail(account != NULL);
4537 compose->account = account;
4539 if (account->name && *account->name) {
4541 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4542 from = g_strdup_printf("%s <%s>",
4543 buf, account->address);
4544 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4546 from = g_strdup_printf("<%s>",
4548 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4553 compose_set_title(compose);
4555 ifactory = gtk_item_factory_from_widget(compose->menubar);
4557 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4558 menu_set_active(ifactory, "/Options/Sign", TRUE);
4560 menu_set_active(ifactory, "/Options/Sign", FALSE);
4561 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4562 menu_set_active(ifactory, "/Options/Encrypt", TRUE);
4564 menu_set_active(ifactory, "/Options/Encrypt", FALSE);
4566 activate_privacy_system(compose, account, FALSE);
4568 if (!init && compose->mode != COMPOSE_REDIRECT) {
4569 undo_block(compose->undostruct);
4570 compose_insert_sig(compose, TRUE);
4571 undo_unblock(compose->undostruct);
4575 /* use account's dict info if set */
4576 if (compose->gtkaspell) {
4577 if (account->enable_default_dictionary)
4578 gtkaspell_change_dict(compose->gtkaspell,
4579 account->default_dictionary, FALSE);
4580 if (account->enable_default_alt_dictionary)
4581 gtkaspell_change_alt_dict(compose->gtkaspell,
4582 account->default_alt_dictionary);
4583 if (account->enable_default_dictionary
4584 || account->enable_default_alt_dictionary)
4585 compose_spell_menu_changed(compose);
4590 gboolean compose_check_for_valid_recipient(Compose *compose) {
4591 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
4592 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
4593 gboolean recipient_found = FALSE;
4597 /* free to and newsgroup list */
4598 slist_free_strings(compose->to_list);
4599 g_slist_free(compose->to_list);
4600 compose->to_list = NULL;
4602 slist_free_strings(compose->newsgroup_list);
4603 g_slist_free(compose->newsgroup_list);
4604 compose->newsgroup_list = NULL;
4606 /* search header entries for to and newsgroup entries */
4607 for (list = compose->header_list; list; list = list->next) {
4610 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_BIN(((ComposeHeaderEntry *)list->data)->combo)->child), 0, -1);
4611 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4614 if (entry[0] != '\0') {
4615 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
4616 if (!strcmp(header, prefs_common_translated_header_name(*strptr))) {
4617 compose->to_list = address_list_append(compose->to_list, entry);
4618 recipient_found = TRUE;
4621 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
4622 if (!strcmp(header, prefs_common_translated_header_name(*strptr))) {
4623 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
4624 recipient_found = TRUE;
4631 return recipient_found;
4634 static gboolean compose_check_for_set_recipients(Compose *compose)
4636 if (compose->account->set_autocc && compose->account->auto_cc) {
4637 gboolean found_other = FALSE;
4639 /* search header entries for to and newsgroup entries */
4640 for (list = compose->header_list; list; list = list->next) {
4643 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4644 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_BIN(((ComposeHeaderEntry *)list->data)->combo)->child), 0, -1);
4647 if (strcmp(entry, compose->account->auto_cc)
4648 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
4658 if (compose->batch) {
4659 gtk_widget_show_all(compose->window);
4661 aval = alertpanel(_("Send"),
4662 _("The only recipient is the default CC address. Send anyway?"),
4663 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4664 if (aval != G_ALERTALTERNATE)
4668 if (compose->account->set_autobcc && compose->account->auto_bcc) {
4669 gboolean found_other = FALSE;
4671 /* search header entries for to and newsgroup entries */
4672 for (list = compose->header_list; list; list = list->next) {
4675 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4676 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_BIN(((ComposeHeaderEntry *)list->data)->combo)->child), 0, -1);
4679 if (strcmp(entry, compose->account->auto_bcc)
4680 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
4690 if (compose->batch) {
4691 gtk_widget_show_all(compose->window);
4693 aval = alertpanel(_("Send"),
4694 _("The only recipient is the default BCC address. Send anyway?"),
4695 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4696 if (aval != G_ALERTALTERNATE)
4703 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
4707 if (compose_check_for_valid_recipient(compose) == FALSE) {
4708 if (compose->batch) {
4709 gtk_widget_show_all(compose->window);
4711 alertpanel_error(_("Recipient is not specified."));
4715 if (compose_check_for_set_recipients(compose) == FALSE) {
4719 if (!compose->batch) {
4720 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
4721 if (*str == '\0' && check_everything == TRUE &&
4722 compose->mode != COMPOSE_REDIRECT) {
4724 gchar *button_label;
4727 if (compose->sending)
4728 button_label = _("+_Send");
4730 button_label = _("+_Queue");
4731 message = g_strdup_printf(_("Subject is empty. %s"),
4732 compose->sending?_("Send it anyway?"):
4733 _("Queue it anyway?"));
4735 aval = alertpanel(compose->sending?_("Send"):_("Send later"), message,
4736 GTK_STOCK_CANCEL, button_label, NULL);
4738 if (aval != G_ALERTALTERNATE)
4743 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
4749 gint compose_send(Compose *compose)
4752 FolderItem *folder = NULL;
4754 gchar *msgpath = NULL;
4755 gboolean discard_window = FALSE;
4756 gchar *errstr = NULL;
4757 gchar *tmsgid = NULL;
4758 MainWindow *mainwin = mainwindow_get_mainwindow();
4759 gboolean queued_removed = FALSE;
4761 if (prefs_common.send_dialog_invisible
4762 || compose->batch == TRUE)
4763 discard_window = TRUE;
4765 compose_allow_user_actions (compose, FALSE);
4766 compose->sending = TRUE;
4768 if (compose_check_entries(compose, TRUE) == FALSE) {
4769 if (compose->batch) {
4770 gtk_widget_show_all(compose->window);
4776 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
4779 if (compose->batch) {
4780 gtk_widget_show_all(compose->window);
4783 alertpanel_error(_("Could not queue message for sending:\n\n"
4784 "Charset conversion failed."));
4785 } else if (val == -5) {
4786 alertpanel_error(_("Could not queue message for sending:\n\n"
4787 "Couldn't get recipient encryption key."));
4788 } else if (val == -6) {
4790 } else if (val == -3) {
4791 if (privacy_peek_error())
4792 alertpanel_error(_("Could not queue message for sending:\n\n"
4793 "Signature failed: %s"), privacy_get_error());
4794 } else if (val == -2 && errno != 0) {
4795 alertpanel_error(_("Could not queue message for sending:\n\n%s."), strerror(errno));
4797 alertpanel_error(_("Could not queue message for sending."));
4802 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
4803 if (discard_window) {
4804 compose->sending = FALSE;
4805 compose_close(compose);
4806 /* No more compose access in the normal codepath
4807 * after this point! */
4812 alertpanel_error(_("The message was queued but could not be "
4813 "sent.\nUse \"Send queued messages\" from "
4814 "the main window to retry."));
4815 if (!discard_window) {
4822 if (msgpath == NULL) {
4823 msgpath = folder_item_fetch_msg(folder, msgnum);
4824 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
4827 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
4828 claws_unlink(msgpath);
4831 if (!discard_window) {
4833 if (!queued_removed)
4834 folder_item_remove_msg(folder, msgnum);
4835 folder_item_scan(folder);
4837 /* make sure we delete that */
4838 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
4840 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
4841 folder_item_remove_msg(folder, tmp->msgnum);
4842 procmsg_msginfo_free(tmp);
4849 if (!queued_removed)
4850 folder_item_remove_msg(folder, msgnum);
4851 folder_item_scan(folder);
4853 /* make sure we delete that */
4854 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
4856 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
4857 folder_item_remove_msg(folder, tmp->msgnum);
4858 procmsg_msginfo_free(tmp);
4861 if (!discard_window) {
4862 compose->sending = FALSE;
4863 compose_allow_user_actions (compose, TRUE);
4864 compose_close(compose);
4868 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
4869 "the main window to retry."), errstr);
4872 alertpanel_error_log(_("The message was queued but could not be "
4873 "sent.\nUse \"Send queued messages\" from "
4874 "the main window to retry."));
4876 if (!discard_window) {
4885 toolbar_main_set_sensitive(mainwin);
4886 main_window_set_menu_sensitive(mainwin);
4892 compose_allow_user_actions (compose, TRUE);
4893 compose->sending = FALSE;
4894 compose->modified = TRUE;
4895 toolbar_main_set_sensitive(mainwin);
4896 main_window_set_menu_sensitive(mainwin);
4901 static gboolean compose_use_attach(Compose *compose)
4903 GtkTreeModel *model = gtk_tree_view_get_model
4904 (GTK_TREE_VIEW(compose->attach_clist));
4905 return gtk_tree_model_iter_n_children(model, NULL) > 0;
4908 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
4911 gchar buf[BUFFSIZE];
4913 gboolean first_to_address;
4914 gboolean first_cc_address;
4916 ComposeHeaderEntry *headerentry;
4917 const gchar *headerentryname;
4918 const gchar *cc_hdr;
4919 const gchar *to_hdr;
4920 gboolean err = FALSE;
4922 debug_print("Writing redirect header\n");
4924 cc_hdr = prefs_common_translated_header_name("Cc:");
4925 to_hdr = prefs_common_translated_header_name("To:");
4927 first_to_address = TRUE;
4928 for (list = compose->header_list; list; list = list->next) {
4929 headerentry = ((ComposeHeaderEntry *)list->data);
4930 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_BIN(headerentry->combo)->child));
4932 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
4933 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
4934 Xstrdup_a(str, entstr, return -1);
4936 if (str[0] != '\0') {
4937 compose_convert_header
4938 (compose, buf, sizeof(buf), str,
4939 strlen("Resent-To") + 2, TRUE);
4941 if (first_to_address) {
4942 err |= (fprintf(fp, "Resent-To: ") < 0);
4943 first_to_address = FALSE;
4945 err |= (fprintf(fp, ",") < 0);
4947 err |= (fprintf(fp, "%s", buf) < 0);
4951 if (!first_to_address) {
4952 err |= (fprintf(fp, "\n") < 0);
4955 first_cc_address = TRUE;
4956 for (list = compose->header_list; list; list = list->next) {
4957 headerentry = ((ComposeHeaderEntry *)list->data);
4958 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_BIN(headerentry->combo)->child));
4960 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
4961 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
4962 Xstrdup_a(str, strg, return -1);
4964 if (str[0] != '\0') {
4965 compose_convert_header
4966 (compose, buf, sizeof(buf), str,
4967 strlen("Resent-Cc") + 2, TRUE);
4969 if (first_cc_address) {
4970 err |= (fprintf(fp, "Resent-Cc: ") < 0);
4971 first_cc_address = FALSE;
4973 err |= (fprintf(fp, ",") < 0);
4975 err |= (fprintf(fp, "%s", buf) < 0);
4979 if (!first_cc_address) {
4980 err |= (fprintf(fp, "\n") < 0);
4983 return (err ? -1:0);
4986 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
4988 gchar buf[BUFFSIZE];
4990 const gchar *entstr;
4991 /* struct utsname utsbuf; */
4992 gboolean err = FALSE;
4994 g_return_val_if_fail(fp != NULL, -1);
4995 g_return_val_if_fail(compose->account != NULL, -1);
4996 g_return_val_if_fail(compose->account->address != NULL, -1);
4999 get_rfc822_date(buf, sizeof(buf));
5000 err |= (fprintf(fp, "Resent-Date: %s\n", buf) < 0);
5003 if (compose->account->name && *compose->account->name) {
5004 compose_convert_header
5005 (compose, buf, sizeof(buf), compose->account->name,
5006 strlen("From: "), TRUE);
5007 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5008 buf, compose->account->address) < 0);
5010 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5013 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5014 if (*entstr != '\0') {
5015 Xstrdup_a(str, entstr, return -1);
5018 compose_convert_header(compose, buf, sizeof(buf), str,
5019 strlen("Subject: "), FALSE);
5020 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5024 /* Resent-Message-ID */
5025 if (compose->account->set_domain && compose->account->domain) {
5026 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
5027 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
5028 g_snprintf(buf, sizeof(buf), "%s",
5029 strchr(compose->account->address, '@') ?
5030 strchr(compose->account->address, '@')+1 :
5031 compose->account->address);
5033 g_snprintf(buf, sizeof(buf), "%s", "");
5036 if (compose->account->gen_msgid) {
5037 generate_msgid(buf, sizeof(buf));
5038 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", buf) < 0);
5039 compose->msgid = g_strdup(buf);
5041 compose->msgid = NULL;
5044 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5047 /* separator between header and body */
5048 err |= (fputs("\n", fp) == EOF);
5050 return (err ? -1:0);
5053 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5057 gchar buf[BUFFSIZE];
5059 gboolean skip = FALSE;
5060 gboolean err = FALSE;
5061 gchar *not_included[]={
5062 "Return-Path:", "Delivered-To:", "Received:",
5063 "Subject:", "X-UIDL:", "AF:",
5064 "NF:", "PS:", "SRH:",
5065 "SFN:", "DSR:", "MID:",
5066 "CFG:", "PT:", "S:",
5067 "RQ:", "SSV:", "NSV:",
5068 "SSH:", "R:", "MAID:",
5069 "NAID:", "RMID:", "FMID:",
5070 "SCF:", "RRCPT:", "NG:",
5071 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5072 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5073 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5074 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5077 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
5078 FILE_OP_ERROR(compose->redirect_filename, "fopen");
5082 while (procheader_get_one_field_asis(buf, sizeof(buf), fp) != -1) {
5084 for (i = 0; not_included[i] != NULL; i++) {
5085 if (g_ascii_strncasecmp(buf, not_included[i],
5086 strlen(not_included[i])) == 0) {
5093 if (fputs(buf, fdest) == -1)
5096 if (!prefs_common.redirect_keep_from) {
5097 if (g_ascii_strncasecmp(buf, "From:",
5098 strlen("From:")) == 0) {
5099 err |= (fputs(" (by way of ", fdest) == EOF);
5100 if (compose->account->name
5101 && *compose->account->name) {
5102 compose_convert_header
5103 (compose, buf, sizeof(buf),
5104 compose->account->name,
5107 err |= (fprintf(fdest, "%s <%s>",
5109 compose->account->address) < 0);
5111 err |= (fprintf(fdest, "%s",
5112 compose->account->address) < 0);
5113 err |= (fputs(")", fdest) == EOF);
5117 if (fputs("\n", fdest) == -1)
5124 if (compose_redirect_write_headers(compose, fdest))
5127 while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
5128 if (fwrite(buf, sizeof(gchar), len, fdest) != len)
5141 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5143 GtkTextBuffer *buffer;
5144 GtkTextIter start, end;
5147 const gchar *out_codeset;
5148 EncodingType encoding = ENC_UNKNOWN;
5149 MimeInfo *mimemsg, *mimetext;
5151 const gchar *src_codeset = CS_INTERNAL;
5153 if (action == COMPOSE_WRITE_FOR_SEND)
5154 attach_parts = TRUE;
5156 /* create message MimeInfo */
5157 mimemsg = procmime_mimeinfo_new();
5158 mimemsg->type = MIMETYPE_MESSAGE;
5159 mimemsg->subtype = g_strdup("rfc822");
5160 mimemsg->content = MIMECONTENT_MEM;
5161 mimemsg->tmp = TRUE; /* must free content later */
5162 mimemsg->data.mem = compose_get_header(compose);
5164 /* Create text part MimeInfo */
5165 /* get all composed text */
5166 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5167 gtk_text_buffer_get_start_iter(buffer, &start);
5168 gtk_text_buffer_get_end_iter(buffer, &end);
5169 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5171 out_codeset = conv_get_charset_str(compose->out_encoding);
5173 if (!out_codeset && is_ascii_str(chars)) {
5174 out_codeset = CS_US_ASCII;
5175 } else if (prefs_common.outgoing_fallback_to_ascii &&
5176 is_ascii_str(chars)) {
5177 out_codeset = CS_US_ASCII;
5178 encoding = ENC_7BIT;
5182 gchar *test_conv_global_out = NULL;
5183 gchar *test_conv_reply = NULL;
5185 /* automatic mode. be automatic. */
5186 codeconv_set_strict(TRUE);
5188 out_codeset = conv_get_outgoing_charset_str();
5190 debug_print("trying to convert to %s\n", out_codeset);
5191 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5194 if (!test_conv_global_out && compose->orig_charset
5195 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5196 out_codeset = compose->orig_charset;
5197 debug_print("failure; trying to convert to %s\n", out_codeset);
5198 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5201 if (!test_conv_global_out && !test_conv_reply) {
5203 out_codeset = CS_INTERNAL;
5204 debug_print("failure; finally using %s\n", out_codeset);
5206 g_free(test_conv_global_out);
5207 g_free(test_conv_reply);
5208 codeconv_set_strict(FALSE);
5211 if (encoding == ENC_UNKNOWN) {
5212 if (prefs_common.encoding_method == CTE_BASE64)
5213 encoding = ENC_BASE64;
5214 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5215 encoding = ENC_QUOTED_PRINTABLE;
5216 else if (prefs_common.encoding_method == CTE_8BIT)
5217 encoding = ENC_8BIT;
5219 encoding = procmime_get_encoding_for_charset(out_codeset);
5222 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5223 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5225 if (action == COMPOSE_WRITE_FOR_SEND) {
5226 codeconv_set_strict(TRUE);
5227 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5228 codeconv_set_strict(FALSE);
5234 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5235 "to the specified %s charset.\n"
5236 "Send it as %s?"), out_codeset, src_codeset);
5237 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL, _("+_Send"), NULL, FALSE,
5238 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5241 if (aval != G_ALERTALTERNATE) {
5246 out_codeset = src_codeset;
5252 out_codeset = src_codeset;
5257 if (encoding == ENC_8BIT || encoding == ENC_7BIT) {
5258 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5259 strstr(buf, "\nFrom ") != NULL) {
5260 encoding = ENC_QUOTED_PRINTABLE;
5264 mimetext = procmime_mimeinfo_new();
5265 mimetext->content = MIMECONTENT_MEM;
5266 mimetext->tmp = TRUE; /* must free content later */
5267 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5268 * and free the data, which we need later. */
5269 mimetext->data.mem = g_strdup(buf);
5270 mimetext->type = MIMETYPE_TEXT;
5271 mimetext->subtype = g_strdup("plain");
5272 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5273 g_strdup(out_codeset));
5275 /* protect trailing spaces when signing message */
5276 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5277 privacy_system_can_sign(compose->privacy_system)) {
5278 encoding = ENC_QUOTED_PRINTABLE;
5281 debug_print("main text: %zd bytes encoded as %s in %d\n",
5282 strlen(buf), out_codeset, encoding);
5284 /* check for line length limit */
5285 if (action == COMPOSE_WRITE_FOR_SEND &&
5286 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5287 check_line_length(buf, 1000, &line) < 0) {
5291 msg = g_strdup_printf
5292 (_("Line %d exceeds the line length limit (998 bytes).\n"
5293 "The contents of the message might be broken on the way to the delivery.\n"
5295 "Send it anyway?"), line + 1);
5296 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5298 if (aval != G_ALERTALTERNATE) {
5304 if (encoding != ENC_UNKNOWN)
5305 procmime_encode_content(mimetext, encoding);
5307 /* append attachment parts */
5308 if (compose_use_attach(compose) && attach_parts) {
5309 MimeInfo *mimempart;
5310 gchar *boundary = NULL;
5311 mimempart = procmime_mimeinfo_new();
5312 mimempart->content = MIMECONTENT_EMPTY;
5313 mimempart->type = MIMETYPE_MULTIPART;
5314 mimempart->subtype = g_strdup("mixed");
5318 boundary = generate_mime_boundary(NULL);
5319 } while (strstr(buf, boundary) != NULL);
5321 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5324 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5326 g_node_append(mimempart->node, mimetext->node);
5327 g_node_append(mimemsg->node, mimempart->node);
5329 compose_add_attachments(compose, mimempart);
5331 g_node_append(mimemsg->node, mimetext->node);
5335 /* sign message if sending */
5336 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5337 privacy_system_can_sign(compose->privacy_system))
5338 if (!privacy_sign(compose->privacy_system, mimemsg, compose->account))
5341 procmime_write_mimeinfo(mimemsg, fp);
5343 procmime_mimeinfo_free_all(mimemsg);
5348 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5350 GtkTextBuffer *buffer;
5351 GtkTextIter start, end;
5356 if ((fp = g_fopen(file, "wb")) == NULL) {
5357 FILE_OP_ERROR(file, "fopen");
5361 /* chmod for security */
5362 if (change_file_mode_rw(fp, file) < 0) {
5363 FILE_OP_ERROR(file, "chmod");
5364 g_warning("can't change file mode\n");
5367 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5368 gtk_text_buffer_get_start_iter(buffer, &start);
5369 gtk_text_buffer_get_end_iter(buffer, &end);
5370 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5372 chars = conv_codeset_strdup
5373 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5376 if (!chars) return -1;
5379 len = strlen(chars);
5380 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5381 FILE_OP_ERROR(file, "fwrite");
5390 if (fclose(fp) == EOF) {
5391 FILE_OP_ERROR(file, "fclose");
5398 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5401 MsgInfo *msginfo = compose->targetinfo;
5403 g_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5404 if (!msginfo) return -1;
5406 if (!force && MSG_IS_LOCKED(msginfo->flags))
5409 item = msginfo->folder;
5410 g_return_val_if_fail(item != NULL, -1);
5412 if (procmsg_msg_exist(msginfo) &&
5413 (folder_has_parent_of_type(item, F_QUEUE) ||
5414 folder_has_parent_of_type(item, F_DRAFT)
5415 || msginfo == compose->autosaved_draft)) {
5416 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5417 g_warning("can't remove the old message\n");
5420 debug_print("removed reedit target %d\n", msginfo->msgnum);
5427 static void compose_remove_draft(Compose *compose)
5430 MsgInfo *msginfo = compose->targetinfo;
5431 drafts = account_get_special_folder(compose->account, F_DRAFT);
5433 if (procmsg_msg_exist(msginfo)) {
5434 folder_item_remove_msg(drafts, msginfo->msgnum);
5439 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5440 gboolean remove_reedit_target)
5442 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5445 static gboolean compose_warn_encryption(Compose *compose)
5447 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5448 AlertValue val = G_ALERTALTERNATE;
5450 if (warning == NULL)
5453 val = alertpanel_full(_("Encryption warning"), warning,
5454 GTK_STOCK_CANCEL, _("+C_ontinue"), NULL,
5455 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5456 if (val & G_ALERTDISABLE) {
5457 val &= ~G_ALERTDISABLE;
5458 if (val == G_ALERTALTERNATE)
5459 privacy_inhibit_encrypt_warning(compose->privacy_system,
5463 if (val == G_ALERTALTERNATE) {
5470 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
5471 gchar **msgpath, gboolean check_subject,
5472 gboolean remove_reedit_target)
5479 static gboolean lock = FALSE;
5480 PrefsAccount *mailac = NULL, *newsac = NULL;
5481 gboolean err = FALSE;
5483 debug_print("queueing message...\n");
5484 g_return_val_if_fail(compose->account != NULL, -1);
5488 if (compose_check_entries(compose, check_subject) == FALSE) {
5490 if (compose->batch) {
5491 gtk_widget_show_all(compose->window);
5496 if (!compose->to_list && !compose->newsgroup_list) {
5497 g_warning("can't get recipient list.");
5502 if (compose->to_list) {
5503 if (compose->account->protocol != A_NNTP)
5504 mailac = compose->account;
5505 else if (cur_account && cur_account->protocol != A_NNTP)
5506 mailac = cur_account;
5507 else if (!(mailac = compose_current_mail_account())) {
5509 alertpanel_error(_("No account for sending mails available!"));
5514 if (compose->newsgroup_list) {
5515 if (compose->account->protocol == A_NNTP)
5516 newsac = compose->account;
5517 else if (!newsac->protocol != A_NNTP) {
5519 alertpanel_error(_("No account for posting news available!"));
5524 /* write queue header */
5525 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
5526 G_DIR_SEPARATOR, compose, (guint) rand());
5527 debug_print("queuing to %s\n", tmp);
5528 if ((fp = g_fopen(tmp, "wb")) == NULL) {
5529 FILE_OP_ERROR(tmp, "fopen");
5535 if (change_file_mode_rw(fp, tmp) < 0) {
5536 FILE_OP_ERROR(tmp, "chmod");
5537 g_warning("can't change file mode\n");
5540 /* queueing variables */
5541 err |= (fprintf(fp, "AF:\n") < 0);
5542 err |= (fprintf(fp, "NF:0\n") < 0);
5543 err |= (fprintf(fp, "PS:10\n") < 0);
5544 err |= (fprintf(fp, "SRH:1\n") < 0);
5545 err |= (fprintf(fp, "SFN:\n") < 0);
5546 err |= (fprintf(fp, "DSR:\n") < 0);
5548 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
5550 err |= (fprintf(fp, "MID:\n") < 0);
5551 err |= (fprintf(fp, "CFG:\n") < 0);
5552 err |= (fprintf(fp, "PT:0\n") < 0);
5553 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
5554 err |= (fprintf(fp, "RQ:\n") < 0);
5556 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
5558 err |= (fprintf(fp, "SSV:\n") < 0);
5560 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
5562 err |= (fprintf(fp, "NSV:\n") < 0);
5563 err |= (fprintf(fp, "SSH:\n") < 0);
5564 /* write recepient list */
5565 if (compose->to_list) {
5566 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
5567 for (cur = compose->to_list->next; cur != NULL;
5569 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
5570 err |= (fprintf(fp, "\n") < 0);
5572 /* write newsgroup list */
5573 if (compose->newsgroup_list) {
5574 err |= (fprintf(fp, "NG:") < 0);
5575 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
5576 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
5577 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
5578 err |= (fprintf(fp, "\n") < 0);
5580 /* Sylpheed account IDs */
5582 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
5584 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
5587 if (compose->privacy_system != NULL) {
5588 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
5589 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
5590 if (compose->use_encryption) {
5592 if (!compose_warn_encryption(compose)) {
5599 if (mailac && mailac->encrypt_to_self) {
5600 GSList *tmp_list = g_slist_copy(compose->to_list);
5601 tmp_list = g_slist_append(tmp_list, compose->account->address);
5602 encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
5603 g_slist_free(tmp_list);
5605 encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
5607 if (encdata != NULL) {
5608 if (strcmp(encdata, "_DONT_ENCRYPT_")) {
5609 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5610 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
5612 } /* else we finally dont want to encrypt */
5614 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5615 /* and if encdata was null, it means there's been a problem in
5627 /* Save copy folder */
5628 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
5629 gchar *savefolderid;
5631 savefolderid = gtk_editable_get_chars(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
5632 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
5633 g_free(savefolderid);
5635 /* Save copy folder */
5636 if (compose->return_receipt) {
5637 err |= (fprintf(fp, "RRCPT:1\n") < 0);
5639 /* Message-ID of message replying to */
5640 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
5643 folderid = folder_item_get_identifier(compose->replyinfo->folder);
5644 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
5647 /* Message-ID of message forwarding to */
5648 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
5651 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
5652 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
5656 /* end of headers */
5657 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
5659 if (compose->redirect_filename != NULL) {
5660 if (compose_redirect_write_to_file(compose, fp) < 0) {
5669 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
5674 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
5678 g_warning("failed to write queue message\n");
5685 if (fclose(fp) == EOF) {
5686 FILE_OP_ERROR(tmp, "fclose");
5693 if (item && *item) {
5696 queue = account_get_special_folder(compose->account, F_QUEUE);
5699 g_warning("can't find queue folder\n");
5705 folder_item_scan(queue);
5706 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
5707 g_warning("can't queue the message\n");
5714 if (msgpath == NULL) {
5720 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
5721 compose_remove_reedit_target(compose, FALSE);
5724 if ((msgnum != NULL) && (item != NULL)) {
5732 static void compose_add_attachments(Compose *compose, MimeInfo *parent)
5735 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
5737 struct stat statbuf;
5738 gchar *type, *subtype;
5739 GtkTreeModel *model;
5742 model = gtk_tree_view_get_model(tree_view);
5744 if (!gtk_tree_model_get_iter_first(model, &iter))
5747 gtk_tree_model_get(model, &iter,
5751 mimepart = procmime_mimeinfo_new();
5752 mimepart->content = MIMECONTENT_FILE;
5753 mimepart->data.filename = g_strdup(ainfo->file);
5754 mimepart->tmp = FALSE; /* or we destroy our attachment */
5755 mimepart->offset = 0;
5757 stat(ainfo->file, &statbuf);
5758 mimepart->length = statbuf.st_size;
5760 type = g_strdup(ainfo->content_type);
5762 if (!strchr(type, '/')) {
5764 type = g_strdup("application/octet-stream");
5767 subtype = strchr(type, '/') + 1;
5768 *(subtype - 1) = '\0';
5769 mimepart->type = procmime_get_media_type(type);
5770 mimepart->subtype = g_strdup(subtype);
5773 if (mimepart->type == MIMETYPE_MESSAGE &&
5774 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
5775 mimepart->disposition = DISPOSITIONTYPE_INLINE;
5778 g_hash_table_insert(mimepart->typeparameters,
5779 g_strdup("name"), g_strdup(ainfo->name));
5780 g_hash_table_insert(mimepart->dispositionparameters,
5781 g_strdup("filename"), g_strdup(ainfo->name));
5782 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
5786 if (compose->use_signing) {
5787 if (ainfo->encoding == ENC_7BIT)
5788 ainfo->encoding = ENC_QUOTED_PRINTABLE;
5789 else if (ainfo->encoding == ENC_8BIT)
5790 ainfo->encoding = ENC_BASE64;
5793 procmime_encode_content(mimepart, ainfo->encoding);
5795 g_node_append(parent->node, mimepart->node);
5796 } while (gtk_tree_model_iter_next(model, &iter));
5799 #define IS_IN_CUSTOM_HEADER(header) \
5800 (compose->account->add_customhdr && \
5801 custom_header_find(compose->account->customhdr_list, header) != NULL)
5803 static void compose_add_headerfield_from_headerlist(Compose *compose,
5805 const gchar *fieldname,
5806 const gchar *seperator)
5808 gchar *str, *fieldname_w_colon;
5809 gboolean add_field = FALSE;
5811 ComposeHeaderEntry *headerentry;
5812 const gchar *headerentryname;
5813 const gchar *trans_fieldname;
5816 if (IS_IN_CUSTOM_HEADER(fieldname))
5819 debug_print("Adding %s-fields\n", fieldname);
5821 fieldstr = g_string_sized_new(64);
5823 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
5824 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
5826 for (list = compose->header_list; list; list = list->next) {
5827 headerentry = ((ComposeHeaderEntry *)list->data);
5828 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_BIN(headerentry->combo)->child));
5830 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
5831 str = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
5833 if (str[0] != '\0') {
5835 g_string_append(fieldstr, seperator);
5836 g_string_append(fieldstr, str);
5845 buf = g_new0(gchar, fieldstr->len * 4 + 256);
5846 compose_convert_header
5847 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
5848 strlen(fieldname) + 2, TRUE);
5849 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
5853 g_free(fieldname_w_colon);
5854 g_string_free(fieldstr, TRUE);
5859 static gchar *compose_get_header(Compose *compose)
5861 gchar buf[BUFFSIZE];
5862 const gchar *entry_str;
5866 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
5868 gchar *from_name = NULL, *from_address = NULL;
5871 g_return_val_if_fail(compose->account != NULL, NULL);
5872 g_return_val_if_fail(compose->account->address != NULL, NULL);
5874 header = g_string_sized_new(64);
5877 get_rfc822_date(buf, sizeof(buf));
5878 g_string_append_printf(header, "Date: %s\n", buf);
5882 if (compose->account->name && *compose->account->name) {
5884 QUOTE_IF_REQUIRED(buf, compose->account->name);
5885 tmp = g_strdup_printf("%s <%s>",
5886 buf, compose->account->address);
5888 tmp = g_strdup_printf("%s",
5889 compose->account->address);
5891 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
5892 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
5894 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
5895 from_address = g_strdup(compose->account->address);
5897 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5898 /* extract name and address */
5899 if (strstr(spec, " <") && strstr(spec, ">")) {
5900 from_address = g_strdup(strrchr(spec, '<')+1);
5901 *(strrchr(from_address, '>')) = '\0';
5902 from_name = g_strdup(spec);
5903 *(strrchr(from_name, '<')) = '\0';
5906 from_address = g_strdup(spec);
5913 if (from_name && *from_name) {
5914 compose_convert_header
5915 (compose, buf, sizeof(buf), from_name,
5916 strlen("From: "), TRUE);
5917 QUOTE_IF_REQUIRED(name, buf);
5919 g_string_append_printf(header, "From: %s <%s>\n",
5920 name, from_address);
5922 g_string_append_printf(header, "From: %s\n", from_address);
5925 g_free(from_address);
5928 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
5931 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
5934 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
5937 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
5940 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
5942 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
5945 compose_convert_header(compose, buf, sizeof(buf), str,
5946 strlen("Subject: "), FALSE);
5947 g_string_append_printf(header, "Subject: %s\n", buf);
5953 if (compose->account->set_domain && compose->account->domain) {
5954 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
5955 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
5956 g_snprintf(buf, sizeof(buf), "%s",
5957 strchr(compose->account->address, '@') ?
5958 strchr(compose->account->address, '@')+1 :
5959 compose->account->address);
5961 g_snprintf(buf, sizeof(buf), "%s", "");
5964 if (compose->account->gen_msgid) {
5965 generate_msgid(buf, sizeof(buf));
5966 g_string_append_printf(header, "Message-ID: <%s>\n", buf);
5967 compose->msgid = g_strdup(buf);
5969 compose->msgid = NULL;
5972 if (compose->remove_references == FALSE) {
5974 if (compose->inreplyto && compose->to_list)
5975 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
5978 if (compose->references)
5979 g_string_append_printf(header, "References: %s\n", compose->references);
5983 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
5986 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
5989 if (compose->account->organization &&
5990 strlen(compose->account->organization) &&
5991 !IS_IN_CUSTOM_HEADER("Organization")) {
5992 compose_convert_header(compose, buf, sizeof(buf),
5993 compose->account->organization,
5994 strlen("Organization: "), FALSE);
5995 g_string_append_printf(header, "Organization: %s\n", buf);
5998 /* Program version and system info */
5999 if (g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6000 !compose->newsgroup_list) {
6001 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6003 gtk_major_version, gtk_minor_version, gtk_micro_version,
6006 if (g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6007 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6009 gtk_major_version, gtk_minor_version, gtk_micro_version,
6013 /* custom headers */
6014 if (compose->account->add_customhdr) {
6017 for (cur = compose->account->customhdr_list; cur != NULL;
6019 CustomHeader *chdr = (CustomHeader *)cur->data;
6021 if (custom_header_is_allowed(chdr->name)) {
6022 compose_convert_header
6023 (compose, buf, sizeof(buf),
6024 chdr->value ? chdr->value : "",
6025 strlen(chdr->name) + 2, FALSE);
6026 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6031 /* Automatic Faces and X-Faces */
6032 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6033 g_string_append_printf(header, "X-Face: %s\n", buf);
6035 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6036 g_string_append_printf(header, "X-Face: %s\n", buf);
6038 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6039 g_string_append_printf(header, "Face: %s\n", buf);
6041 else if (get_default_face (buf, sizeof(buf)) == 0) {
6042 g_string_append_printf(header, "Face: %s\n", buf);
6046 switch (compose->priority) {
6047 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6048 "X-Priority: 1 (Highest)\n");
6050 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6051 "X-Priority: 2 (High)\n");
6053 case PRIORITY_NORMAL: break;
6054 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6055 "X-Priority: 4 (Low)\n");
6057 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6058 "X-Priority: 5 (Lowest)\n");
6060 default: debug_print("compose: priority unknown : %d\n",
6064 /* Request Return Receipt */
6065 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To")) {
6066 if (compose->return_receipt) {
6067 if (compose->account->name
6068 && *compose->account->name) {
6069 compose_convert_header(compose, buf, sizeof(buf),
6070 compose->account->name,
6071 strlen("Disposition-Notification-To: "),
6073 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, compose->account->address);
6075 g_string_append_printf(header, "Disposition-Notification-To: %s\n", compose->account->address);
6079 /* get special headers */
6080 for (list = compose->header_list; list; list = list->next) {
6081 ComposeHeaderEntry *headerentry;
6084 gchar *headername_wcolon;
6085 const gchar *headername_trans;
6088 gboolean standard_header = FALSE;
6090 headerentry = ((ComposeHeaderEntry *)list->data);
6092 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(GTK_BIN(headerentry->combo)->child)));
6094 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6099 if (!strstr(tmp, ":")) {
6100 headername_wcolon = g_strconcat(tmp, ":", NULL);
6101 headername = g_strdup(tmp);
6103 headername_wcolon = g_strdup(tmp);
6104 headername = g_strdup(strtok(tmp, ":"));
6108 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6109 Xstrdup_a(headervalue, entry_str, return NULL);
6110 subst_char(headervalue, '\r', ' ');
6111 subst_char(headervalue, '\n', ' ');
6112 string = std_headers;
6113 while (*string != NULL) {
6114 headername_trans = prefs_common_translated_header_name(*string);
6115 if (!strcmp(headername_trans, headername_wcolon))
6116 standard_header = TRUE;
6119 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6120 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
6123 g_free(headername_wcolon);
6127 g_string_free(header, FALSE);
6132 #undef IS_IN_CUSTOM_HEADER
6134 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6135 gint header_len, gboolean addr_field)
6137 gchar *tmpstr = NULL;
6138 const gchar *out_codeset = NULL;
6140 g_return_if_fail(src != NULL);
6141 g_return_if_fail(dest != NULL);
6143 if (len < 1) return;
6145 tmpstr = g_strdup(src);
6147 subst_char(tmpstr, '\n', ' ');
6148 subst_char(tmpstr, '\r', ' ');
6151 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6152 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6153 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6158 codeconv_set_strict(TRUE);
6159 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6160 conv_get_charset_str(compose->out_encoding));
6161 codeconv_set_strict(FALSE);
6163 if (!dest || *dest == '\0') {
6164 gchar *test_conv_global_out = NULL;
6165 gchar *test_conv_reply = NULL;
6167 /* automatic mode. be automatic. */
6168 codeconv_set_strict(TRUE);
6170 out_codeset = conv_get_outgoing_charset_str();
6172 debug_print("trying to convert to %s\n", out_codeset);
6173 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6176 if (!test_conv_global_out && compose->orig_charset
6177 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6178 out_codeset = compose->orig_charset;
6179 debug_print("failure; trying to convert to %s\n", out_codeset);
6180 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6183 if (!test_conv_global_out && !test_conv_reply) {
6185 out_codeset = CS_INTERNAL;
6186 debug_print("finally using %s\n", out_codeset);
6188 g_free(test_conv_global_out);
6189 g_free(test_conv_reply);
6190 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6192 codeconv_set_strict(FALSE);
6197 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
6201 g_return_if_fail(user_data != NULL);
6203 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6204 g_strstrip(address);
6205 if (*address != '\0') {
6206 gchar *name = procheader_get_fromname(address);
6207 extract_address(address);
6208 addressbook_add_contact(name, address, NULL, NULL);
6213 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
6215 GtkWidget *menuitem;
6218 g_return_if_fail(menu != NULL);
6219 g_return_if_fail(GTK_IS_MENU_SHELL(menu));
6221 menuitem = gtk_separator_menu_item_new();
6222 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6223 gtk_widget_show(menuitem);
6225 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
6226 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6228 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
6229 g_strstrip(address);
6230 if (*address == '\0') {
6231 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
6234 g_signal_connect(G_OBJECT(menuitem), "activate",
6235 G_CALLBACK(compose_add_to_addressbook_cb), entry);
6236 gtk_widget_show(menuitem);
6239 static void compose_create_header_entry(Compose *compose)
6241 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6246 const gchar *header = NULL;
6247 ComposeHeaderEntry *headerentry;
6248 gboolean standard_header = FALSE;
6250 headerentry = g_new0(ComposeHeaderEntry, 1);
6253 combo = gtk_combo_box_entry_new_text();
6255 while(*string != NULL) {
6256 gtk_combo_box_append_text(GTK_COMBO_BOX(combo),
6257 (gchar*)prefs_common_translated_header_name(*string));
6260 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
6261 g_signal_connect(G_OBJECT(GTK_BIN(combo)->child), "grab_focus",
6262 G_CALLBACK(compose_grab_focus_cb), compose);
6263 gtk_widget_show(combo);
6264 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
6265 compose->header_nextrow, compose->header_nextrow+1,
6266 GTK_SHRINK, GTK_FILL, 0, 0);
6267 if (compose->header_last) {
6268 const gchar *last_header_entry = gtk_entry_get_text(
6269 GTK_ENTRY(GTK_BIN(compose->header_last->combo)->child));
6271 while (*string != NULL) {
6272 if (!strcmp(*string, last_header_entry))
6273 standard_header = TRUE;
6276 if (standard_header)
6277 header = gtk_entry_get_text(GTK_ENTRY(GTK_BIN(compose->header_last->combo)->child));
6279 if (!compose->header_last || !standard_header) {
6280 switch(compose->account->protocol) {
6282 header = prefs_common_translated_header_name("Newsgroups:");
6285 header = prefs_common_translated_header_name("To:");
6290 gtk_entry_set_text(GTK_ENTRY(GTK_BIN(combo)->child), header);
6292 g_signal_connect_after(G_OBJECT(GTK_BIN(combo)->child), "grab_focus",
6293 G_CALLBACK(compose_grab_focus_cb), compose);
6296 entry = gtk_entry_new();
6297 gtk_widget_show(entry);
6298 gtk_tooltips_set_tip(compose->tooltips, entry,
6299 _("Use <tab> to autocomplete from addressbook"), NULL);
6300 gtk_table_attach(GTK_TABLE(compose->header_table), entry, 1, 2,
6301 compose->header_nextrow, compose->header_nextrow+1,
6302 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
6304 g_signal_connect(G_OBJECT(entry), "key-press-event",
6305 G_CALLBACK(compose_headerentry_key_press_event_cb),
6307 g_signal_connect(G_OBJECT(entry), "changed",
6308 G_CALLBACK(compose_headerentry_changed_cb),
6310 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
6311 G_CALLBACK(compose_grab_focus_cb), compose);
6314 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6315 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6316 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6317 g_signal_connect(G_OBJECT(entry), "drag_data_received",
6318 G_CALLBACK(compose_header_drag_received_cb),
6320 g_signal_connect(G_OBJECT(entry), "drag-drop",
6321 G_CALLBACK(compose_drag_drop),
6323 g_signal_connect(G_OBJECT(entry), "populate-popup",
6324 G_CALLBACK(compose_entry_popup_extend),
6327 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
6329 headerentry->compose = compose;
6330 headerentry->combo = combo;
6331 headerentry->entry = entry;
6332 headerentry->headernum = compose->header_nextrow;
6334 compose->header_nextrow++;
6335 compose->header_last = headerentry;
6336 compose->header_list =
6337 g_slist_append(compose->header_list,
6341 static void compose_add_header_entry(Compose *compose, const gchar *header, gchar *text)
6343 ComposeHeaderEntry *last_header;
6345 last_header = compose->header_last;
6347 gtk_entry_set_text(GTK_ENTRY(GTK_BIN(last_header->combo)->child), header);
6348 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
6351 static void compose_remove_header_entries(Compose *compose)
6354 for (list = compose->header_list; list; list = list->next) {
6355 ComposeHeaderEntry *headerentry =
6356 (ComposeHeaderEntry *)list->data;
6357 gtk_widget_destroy(headerentry->combo);
6358 gtk_widget_destroy(headerentry->entry);
6359 g_free(headerentry);
6361 compose->header_last = NULL;
6362 g_slist_free(compose->header_list);
6363 compose->header_list = NULL;
6364 compose->header_nextrow = 1;
6365 compose_create_header_entry(compose);
6368 static GtkWidget *compose_create_header(Compose *compose)
6370 GtkWidget *from_optmenu_hbox;
6371 GtkWidget *header_scrolledwin;
6372 GtkWidget *header_table;
6376 /* header labels and entries */
6377 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
6378 gtk_widget_show(header_scrolledwin);
6379 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
6381 header_table = gtk_table_new(2, 2, FALSE);
6382 gtk_widget_show(header_table);
6383 gtk_container_set_border_width(GTK_CONTAINER(header_table), BORDER_WIDTH);
6384 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
6385 gtk_viewport_set_shadow_type(GTK_VIEWPORT(GTK_BIN(header_scrolledwin)->child), GTK_SHADOW_NONE);
6388 /* option menu for selecting accounts */
6389 from_optmenu_hbox = compose_account_option_menu_create(compose);
6390 gtk_table_attach(GTK_TABLE(header_table), from_optmenu_hbox,
6391 0, 2, count, count + 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
6394 compose->header_table = header_table;
6395 compose->header_list = NULL;
6396 compose->header_nextrow = count;
6398 compose_create_header_entry(compose);
6400 compose->table = NULL;
6402 return header_scrolledwin ;
6405 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
6407 Compose *compose = (Compose *)data;
6408 GdkEventButton event;
6411 event.time = gtk_get_current_event_time();
6413 return attach_button_pressed(compose->attach_clist, &event, compose);
6416 static GtkWidget *compose_create_attach(Compose *compose)
6418 GtkWidget *attach_scrwin;
6419 GtkWidget *attach_clist;
6421 GtkListStore *store;
6422 GtkCellRenderer *renderer;
6423 GtkTreeViewColumn *column;
6424 GtkTreeSelection *selection;
6426 /* attachment list */
6427 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
6428 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
6429 GTK_POLICY_AUTOMATIC,
6430 GTK_POLICY_AUTOMATIC);
6431 gtk_widget_set_size_request(attach_scrwin, -1, 80);
6433 store = gtk_list_store_new(N_ATTACH_COLS,
6438 G_TYPE_AUTO_POINTER,
6440 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
6441 (GTK_TREE_MODEL(store)));
6442 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
6443 g_object_unref(store);
6445 renderer = gtk_cell_renderer_text_new();
6446 column = gtk_tree_view_column_new_with_attributes
6447 (_("Mime type"), renderer, "text",
6448 COL_MIMETYPE, NULL);
6449 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6451 renderer = gtk_cell_renderer_text_new();
6452 column = gtk_tree_view_column_new_with_attributes
6453 (_("Size"), renderer, "text",
6455 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6457 renderer = gtk_cell_renderer_text_new();
6458 column = gtk_tree_view_column_new_with_attributes
6459 (_("Name"), renderer, "text",
6461 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6463 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
6464 prefs_common.use_stripes_everywhere);
6465 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
6466 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
6468 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
6469 G_CALLBACK(attach_selected), compose);
6470 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
6471 G_CALLBACK(attach_button_pressed), compose);
6473 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
6474 G_CALLBACK(popup_attach_button_pressed), compose);
6476 gtk_widget_tap_and_hold_setup(GTK_WIDGET(attach_clist), NULL, NULL,
6477 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
6478 g_signal_connect(G_OBJECT(attach_clist), "tap-and-hold",
6479 G_CALLBACK(popup_attach_button_pressed), compose);
6481 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
6482 G_CALLBACK(attach_key_pressed), compose);
6485 gtk_drag_dest_set(attach_clist,
6486 GTK_DEST_DEFAULT_ALL, compose_mime_types,
6487 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6488 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6489 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
6490 G_CALLBACK(compose_attach_drag_received_cb),
6492 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
6493 G_CALLBACK(compose_drag_drop),
6496 compose->attach_scrwin = attach_scrwin;
6497 compose->attach_clist = attach_clist;
6499 return attach_scrwin;
6502 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
6503 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
6505 static GtkWidget *compose_create_others(Compose *compose)
6508 GtkWidget *savemsg_checkbtn;
6509 GtkWidget *savemsg_entry;
6510 GtkWidget *savemsg_select;
6513 gchar *folderidentifier;
6515 /* Table for settings */
6516 table = gtk_table_new(3, 1, FALSE);
6517 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
6518 gtk_widget_show(table);
6519 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
6522 /* Save Message to folder */
6523 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
6524 gtk_widget_show(savemsg_checkbtn);
6525 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6526 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6527 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
6529 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
6530 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
6532 savemsg_entry = gtk_entry_new();
6533 gtk_widget_show(savemsg_entry);
6534 gtk_table_attach_defaults(GTK_TABLE(table), savemsg_entry, 1, 2, rowcount, rowcount + 1);
6535 gtk_editable_set_editable(GTK_EDITABLE(savemsg_entry), prefs_common.savemsg);
6536 g_signal_connect_after(G_OBJECT(savemsg_entry), "grab_focus",
6537 G_CALLBACK(compose_grab_focus_cb), compose);
6538 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6539 folderidentifier = folder_item_get_identifier(account_get_special_folder
6540 (compose->account, F_OUTBOX));
6541 gtk_entry_set_text(GTK_ENTRY(savemsg_entry), folderidentifier);
6542 g_free(folderidentifier);
6545 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
6546 gtk_widget_show(savemsg_select);
6547 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6548 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
6549 G_CALLBACK(compose_savemsg_select_cb),
6554 compose->savemsg_checkbtn = savemsg_checkbtn;
6555 compose->savemsg_entry = savemsg_entry;
6560 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
6562 gtk_editable_set_editable(GTK_EDITABLE(compose->savemsg_entry),
6563 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
6566 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
6571 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE);
6574 path = folder_item_get_identifier(dest);
6576 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), path);
6580 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
6581 GdkAtom clip, GtkTextIter *insert_place);
6584 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
6588 GtkTextBuffer *buffer = GTK_TEXT_VIEW(text)->buffer;
6590 if (event->button == 3) {
6592 GtkTextIter sel_start, sel_end;
6593 gboolean stuff_selected;
6595 /* move the cursor to allow GtkAspell to check the word
6596 * under the mouse */
6597 if (event->x && event->y) {
6598 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6599 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6601 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6604 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
6605 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
6608 stuff_selected = gtk_text_buffer_get_selection_bounds(
6610 &sel_start, &sel_end);
6612 gtk_text_buffer_place_cursor (buffer, &iter);
6613 /* reselect stuff */
6615 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
6616 gtk_text_buffer_select_range(buffer,
6617 &sel_start, &sel_end);
6619 return FALSE; /* pass the event so that the right-click goes through */
6622 if (event->button == 2) {
6627 /* get the middle-click position to paste at the correct place */
6628 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6629 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6631 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6634 entry_paste_clipboard(compose, text,
6635 prefs_common.linewrap_pastes,
6636 GDK_SELECTION_PRIMARY, &iter);
6644 static void compose_spell_menu_changed(void *data)
6646 Compose *compose = (Compose *)data;
6648 GtkWidget *menuitem;
6649 GtkWidget *parent_item;
6650 GtkMenu *menu = GTK_MENU(gtk_menu_new());
6651 GtkItemFactory *ifactory = gtk_item_factory_from_widget(compose->menubar);
6654 if (compose->gtkaspell == NULL)
6657 parent_item = gtk_item_factory_get_item(ifactory,
6658 "/Spelling/Options");
6660 /* setting the submenu removes /Spelling/Options from the factory
6661 * so we need to save it */
6663 if (parent_item == NULL) {
6664 parent_item = compose->aspell_options_menu;
6665 gtk_menu_item_remove_submenu(GTK_MENU_ITEM(parent_item));
6667 compose->aspell_options_menu = parent_item;
6669 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
6671 spell_menu = g_slist_reverse(spell_menu);
6672 for (items = spell_menu;
6673 items; items = items->next) {
6674 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
6675 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
6676 gtk_widget_show(GTK_WIDGET(menuitem));
6678 g_slist_free(spell_menu);
6680 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
6685 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
6687 Compose *compose = (Compose *)data;
6688 GdkEventButton event;
6691 event.time = gtk_get_current_event_time();
6695 return text_clicked(compose->text, &event, compose);
6698 static gboolean compose_force_window_origin = TRUE;
6699 static Compose *compose_create(PrefsAccount *account,
6708 GtkWidget *handlebox;
6710 GtkWidget *notebook;
6712 GtkWidget *attach_hbox;
6713 GtkWidget *attach_lab1;
6714 GtkWidget *attach_lab2;
6719 GtkWidget *subject_hbox;
6720 GtkWidget *subject_frame;
6721 GtkWidget *subject_entry;
6725 GtkWidget *edit_vbox;
6726 GtkWidget *ruler_hbox;
6728 GtkWidget *scrolledwin;
6730 GtkTextBuffer *buffer;
6731 GtkClipboard *clipboard;
6733 UndoMain *undostruct;
6735 gchar *titles[N_ATTACH_COLS];
6736 guint n_menu_entries;
6737 GtkWidget *popupmenu;
6738 GtkItemFactory *popupfactory;
6739 GtkItemFactory *ifactory;
6740 GtkWidget *tmpl_menu;
6742 GtkWidget *menuitem;
6745 GtkAspell * gtkaspell = NULL;
6748 static GdkGeometry geometry;
6750 g_return_val_if_fail(account != NULL, NULL);
6752 debug_print("Creating compose window...\n");
6753 compose = g_new0(Compose, 1);
6755 titles[COL_MIMETYPE] = _("MIME type");
6756 titles[COL_SIZE] = _("Size");
6757 titles[COL_NAME] = _("Name");
6759 compose->batch = batch;
6760 compose->account = account;
6761 compose->folder = folder;
6763 compose->mutex = g_mutex_new();
6764 compose->set_cursor_pos = -1;
6766 compose->tooltips = gtk_tooltips_new();
6768 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
6770 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
6771 gtk_widget_set_size_request(window, -1, prefs_common.compose_height);
6773 if (!geometry.max_width) {
6774 geometry.max_width = gdk_screen_width();
6775 geometry.max_height = gdk_screen_height();
6778 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
6779 &geometry, GDK_HINT_MAX_SIZE);
6780 if (!geometry.min_width) {
6781 geometry.min_width = 600;
6782 geometry.min_height = 480;
6784 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
6785 &geometry, GDK_HINT_MIN_SIZE);
6787 #ifndef GENERIC_UMPC
6788 if (compose_force_window_origin)
6789 gtk_widget_set_uposition(window, prefs_common.compose_x,
6790 prefs_common.compose_y);
6792 g_signal_connect(G_OBJECT(window), "delete_event",
6793 G_CALLBACK(compose_delete_cb), compose);
6794 MANAGE_WINDOW_SIGNALS_CONNECT(window);
6795 gtk_widget_realize(window);
6797 gtkut_widget_set_composer_icon(window);
6799 vbox = gtk_vbox_new(FALSE, 0);
6800 gtk_container_add(GTK_CONTAINER(window), vbox);
6802 n_menu_entries = sizeof(compose_entries) / sizeof(compose_entries[0]);
6803 menubar = menubar_create(window, compose_entries,
6804 n_menu_entries, "<Compose>", compose);
6805 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
6807 if (prefs_common.toolbar_detachable) {
6808 handlebox = gtk_handle_box_new();
6810 handlebox = gtk_hbox_new(FALSE, 0);
6812 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
6814 gtk_widget_realize(handlebox);
6816 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, window,
6819 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
6823 vbox2 = gtk_vbox_new(FALSE, 2);
6824 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
6825 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
6828 notebook = gtk_notebook_new();
6829 gtk_widget_set_size_request(notebook, -1, 130);
6830 gtk_widget_show(notebook);
6832 /* header labels and entries */
6833 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6834 compose_create_header(compose),
6835 gtk_label_new_with_mnemonic(_("Hea_der")));
6836 /* attachment list */
6837 attach_hbox = gtk_hbox_new(FALSE, 0);
6838 gtk_widget_show(attach_hbox);
6840 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
6841 gtk_widget_show(attach_lab1);
6842 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
6844 attach_lab2 = gtk_label_new("");
6845 gtk_widget_show(attach_lab2);
6846 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
6848 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6849 compose_create_attach(compose),
6852 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6853 compose_create_others(compose),
6854 gtk_label_new_with_mnemonic(_("Othe_rs")));
6857 subject_hbox = gtk_hbox_new(FALSE, 0);
6858 gtk_widget_show(subject_hbox);
6860 subject_frame = gtk_frame_new(NULL);
6861 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
6862 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
6863 gtk_widget_show(subject_frame);
6865 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
6866 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
6867 gtk_widget_show(subject);
6869 label = gtk_label_new(_("Subject:"));
6870 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
6871 gtk_widget_show(label);
6873 subject_entry = gtk_entry_new();
6874 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
6875 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
6876 G_CALLBACK(compose_grab_focus_cb), compose);
6877 gtk_widget_show(subject_entry);
6878 compose->subject_entry = subject_entry;
6879 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
6881 edit_vbox = gtk_vbox_new(FALSE, 0);
6883 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
6886 ruler_hbox = gtk_hbox_new(FALSE, 0);
6887 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
6889 ruler = gtk_shruler_new();
6890 gtk_ruler_set_range(GTK_RULER(ruler), 0.0, 100.0, 1.0, 100.0);
6891 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
6895 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
6896 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
6897 GTK_POLICY_AUTOMATIC,
6898 GTK_POLICY_AUTOMATIC);
6899 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
6901 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
6902 gtk_widget_set_size_request(scrolledwin, prefs_common.compose_width, -1);
6904 text = gtk_text_view_new();
6905 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
6906 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
6907 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
6908 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
6909 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
6911 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
6913 g_signal_connect_after(G_OBJECT(text), "size_allocate",
6914 G_CALLBACK(compose_edit_size_alloc),
6916 g_signal_connect(G_OBJECT(buffer), "changed",
6917 G_CALLBACK(compose_changed_cb), compose);
6918 g_signal_connect(G_OBJECT(text), "grab_focus",
6919 G_CALLBACK(compose_grab_focus_cb), compose);
6920 g_signal_connect(G_OBJECT(buffer), "insert_text",
6921 G_CALLBACK(text_inserted), compose);
6922 g_signal_connect(G_OBJECT(text), "button_press_event",
6923 G_CALLBACK(text_clicked), compose);
6925 g_signal_connect(G_OBJECT(text), "popup-menu",
6926 G_CALLBACK(compose_popup_menu), compose);
6928 gtk_widget_tap_and_hold_setup(GTK_WIDGET(text), NULL, NULL,
6929 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
6930 g_signal_connect(G_OBJECT(text), "tap-and-hold",
6931 G_CALLBACK(compose_popup_menu), compose);
6933 g_signal_connect(G_OBJECT(subject_entry), "changed",
6934 G_CALLBACK(compose_changed_cb), compose);
6937 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6938 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6939 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6940 g_signal_connect(G_OBJECT(text), "drag_data_received",
6941 G_CALLBACK(compose_insert_drag_received_cb),
6943 g_signal_connect(G_OBJECT(text), "drag-drop",
6944 G_CALLBACK(compose_drag_drop),
6946 gtk_widget_show_all(vbox);
6948 /* pane between attach clist and text */
6949 paned = gtk_vpaned_new();
6950 gtk_paned_set_gutter_size(GTK_PANED(paned), 12);
6951 gtk_container_add(GTK_CONTAINER(vbox2), paned);
6953 if( maemo_mainwindow_is_fullscreen(mainwindow_get_mainwindow()->window) )
6954 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 300 : 280);
6956 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 250 : 230);
6958 gtk_paned_add1(GTK_PANED(paned), notebook);
6959 gtk_paned_add2(GTK_PANED(paned), edit_vbox);
6960 gtk_widget_show_all(paned);
6963 if (prefs_common.textfont) {
6964 PangoFontDescription *font_desc;
6966 font_desc = pango_font_description_from_string
6967 (prefs_common.textfont);
6969 gtk_widget_modify_font(text, font_desc);
6970 pango_font_description_free(font_desc);
6974 n_entries = sizeof(compose_popup_entries) /
6975 sizeof(compose_popup_entries[0]);
6976 popupmenu = menu_create_items(compose_popup_entries, n_entries,
6977 "<Compose>", &popupfactory,
6980 ifactory = gtk_item_factory_from_widget(menubar);
6981 menu_set_sensitive(ifactory, "/Edit/Undo", FALSE);
6982 menu_set_sensitive(ifactory, "/Edit/Redo", FALSE);
6983 menu_set_sensitive(ifactory, "/Options/Remove references", FALSE);
6985 tmpl_menu = gtk_item_factory_get_item(ifactory, "/Tools/Template");
6987 undostruct = undo_init(text);
6988 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
6991 address_completion_start(window);
6993 compose->window = window;
6994 compose->vbox = vbox;
6995 compose->menubar = menubar;
6996 compose->handlebox = handlebox;
6998 compose->vbox2 = vbox2;
7000 compose->paned = paned;
7002 compose->attach_label = attach_lab2;
7004 compose->notebook = notebook;
7005 compose->edit_vbox = edit_vbox;
7006 compose->ruler_hbox = ruler_hbox;
7007 compose->ruler = ruler;
7008 compose->scrolledwin = scrolledwin;
7009 compose->text = text;
7011 compose->focused_editable = NULL;
7013 compose->popupmenu = popupmenu;
7014 compose->popupfactory = popupfactory;
7016 compose->tmpl_menu = tmpl_menu;
7018 compose->mode = mode;
7019 compose->rmode = mode;
7021 compose->targetinfo = NULL;
7022 compose->replyinfo = NULL;
7023 compose->fwdinfo = NULL;
7025 compose->replyto = NULL;
7027 compose->bcc = NULL;
7028 compose->followup_to = NULL;
7030 compose->ml_post = NULL;
7032 compose->inreplyto = NULL;
7033 compose->references = NULL;
7034 compose->msgid = NULL;
7035 compose->boundary = NULL;
7037 compose->autowrap = prefs_common.autowrap;
7039 compose->use_signing = FALSE;
7040 compose->use_encryption = FALSE;
7041 compose->privacy_system = NULL;
7043 compose->modified = FALSE;
7045 compose->return_receipt = FALSE;
7047 compose->to_list = NULL;
7048 compose->newsgroup_list = NULL;
7050 compose->undostruct = undostruct;
7052 compose->sig_str = NULL;
7054 compose->exteditor_file = NULL;
7055 compose->exteditor_pid = -1;
7056 compose->exteditor_tag = -1;
7057 compose->draft_timeout_tag = -2; /* inhibit auto-drafting while loading */
7060 menu_set_sensitive(ifactory, "/Spelling", FALSE);
7061 if (mode != COMPOSE_REDIRECT) {
7062 if (prefs_common.enable_aspell && prefs_common.dictionary &&
7063 strcmp(prefs_common.dictionary, "")) {
7064 gtkaspell = gtkaspell_new(prefs_common.aspell_path,
7065 prefs_common.dictionary,
7066 prefs_common.alt_dictionary,
7067 conv_get_locale_charset_str(),
7068 prefs_common.misspelled_col,
7069 prefs_common.check_while_typing,
7070 prefs_common.recheck_when_changing_dict,
7071 prefs_common.use_alternate,
7072 prefs_common.use_both_dicts,
7073 GTK_TEXT_VIEW(text),
7074 GTK_WINDOW(compose->window),
7075 compose_spell_menu_changed,
7078 alertpanel_error(_("Spell checker could not "
7080 gtkaspell_checkers_strerror());
7081 gtkaspell_checkers_reset_error();
7083 if (!gtkaspell_set_sug_mode(gtkaspell,
7084 prefs_common.aspell_sugmode)) {
7085 debug_print("Aspell: could not set "
7086 "suggestion mode %s\n",
7087 gtkaspell_checkers_strerror());
7088 gtkaspell_checkers_reset_error();
7091 menu_set_sensitive(ifactory, "/Spelling", TRUE);
7095 compose->gtkaspell = gtkaspell;
7096 compose_spell_menu_changed(compose);
7099 compose_select_account(compose, account, TRUE);
7101 menu_set_active(ifactory, "/Edit/Auto wrapping", prefs_common.autowrap);
7102 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
7103 compose_entry_append(compose, account->auto_cc, COMPOSE_CC);
7105 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
7106 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC);
7108 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
7109 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO);
7111 menu_set_sensitive(ifactory, "/Options/Reply mode", compose->mode == COMPOSE_REPLY);
7113 if (account->protocol != A_NNTP)
7114 gtk_entry_set_text(GTK_ENTRY(GTK_BIN(compose->header_last->combo)->child),
7115 prefs_common_translated_header_name("To:"));
7117 gtk_entry_set_text(GTK_ENTRY(GTK_BIN(compose->header_last->combo)->child),
7118 prefs_common_translated_header_name("Newsgroups:"));
7120 addressbook_set_target_compose(compose);
7122 if (mode != COMPOSE_REDIRECT)
7123 compose_set_template_menu(compose);
7125 menuitem = gtk_item_factory_get_item(ifactory, "/Tools/Template");
7126 menu_set_sensitive(ifactory, "/Tools/Template", FALSE);
7129 compose_list = g_list_append(compose_list, compose);
7131 if (!prefs_common.show_ruler)
7132 gtk_widget_hide(ruler_hbox);
7134 menuitem = gtk_item_factory_get_item(ifactory, "/Tools/Show ruler");
7135 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
7136 prefs_common.show_ruler);
7139 compose->priority = PRIORITY_NORMAL;
7140 compose_update_priority_menu_item(compose);
7142 compose_set_out_encoding(compose);
7145 compose_update_actions_menu(compose);
7147 /* Privacy Systems menu */
7148 compose_update_privacy_systems_menu(compose);
7150 activate_privacy_system(compose, account, TRUE);
7151 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
7153 gtk_widget_realize(window);
7155 gtk_widget_show(window);
7157 maemo_window_full_screen_if_needed(GTK_WINDOW(window));
7158 maemo_connect_key_press_to_mainwindow(GTK_WINDOW(window));
7165 static GtkWidget *compose_account_option_menu_create(Compose *compose)
7170 GtkWidget *optmenubox;
7173 GtkWidget *from_name = NULL;
7175 gint num = 0, def_menu = 0;
7177 accounts = account_get_list();
7178 g_return_val_if_fail(accounts != NULL, NULL);
7180 optmenubox = gtk_event_box_new();
7181 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
7182 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
7184 hbox = gtk_hbox_new(FALSE, 6);
7185 from_name = gtk_entry_new();
7187 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
7188 G_CALLBACK(compose_grab_focus_cb), compose);
7190 for (; accounts != NULL; accounts = accounts->next, num++) {
7191 PrefsAccount *ac = (PrefsAccount *)accounts->data;
7192 gchar *name, *from = NULL;
7194 if (ac == compose->account) def_menu = num;
7196 name = g_markup_printf_escaped(_("From: <i>%s</i>"),
7199 if (ac == compose->account) {
7200 if (ac->name && *ac->name) {
7202 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
7203 from = g_strdup_printf("%s <%s>",
7205 gtk_entry_set_text(GTK_ENTRY(from_name), from);
7207 from = g_strdup_printf("%s",
7209 gtk_entry_set_text(GTK_ENTRY(from_name), from);
7212 COMBOBOX_ADD(menu, name, ac->account_id);
7217 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
7219 g_signal_connect(G_OBJECT(optmenu), "changed",
7220 G_CALLBACK(account_activated),
7222 g_signal_connect(G_OBJECT(from_name), "populate-popup",
7223 G_CALLBACK(compose_entry_popup_extend),
7226 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
7227 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
7229 gtk_tooltips_set_tip(compose->tooltips, optmenubox,
7230 _("Account to use for this email"), NULL);
7231 gtk_tooltips_set_tip(compose->tooltips, from_name,
7232 _("Sender address to be used"), NULL);
7234 compose->from_name = from_name;
7239 static void compose_set_priority_cb(gpointer data,
7243 Compose *compose = (Compose *) data;
7244 compose->priority = action;
7247 static void compose_reply_change_mode(gpointer data,
7251 Compose *compose = (Compose *) data;
7252 gboolean was_modified = compose->modified;
7254 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
7256 g_return_if_fail(compose->replyinfo != NULL);
7258 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
7260 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
7262 if (action == COMPOSE_REPLY_TO_ALL)
7264 if (action == COMPOSE_REPLY_TO_SENDER)
7266 if (action == COMPOSE_REPLY_TO_LIST)
7269 compose_remove_header_entries(compose);
7270 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
7271 if (compose->account->set_autocc && compose->account->auto_cc)
7272 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC);
7274 if (compose->account->set_autobcc && compose->account->auto_bcc)
7275 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC);
7277 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
7278 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO);
7279 compose_show_first_last_header(compose, TRUE);
7280 compose->modified = was_modified;
7281 compose_set_title(compose);
7284 static void compose_update_priority_menu_item(Compose * compose)
7286 GtkItemFactory *ifactory;
7287 GtkWidget *menuitem = NULL;
7289 ifactory = gtk_item_factory_from_widget(compose->menubar);
7291 switch (compose->priority) {
7292 case PRIORITY_HIGHEST:
7293 menuitem = gtk_item_factory_get_item
7294 (ifactory, "/Options/Priority/Highest");
7297 menuitem = gtk_item_factory_get_item
7298 (ifactory, "/Options/Priority/High");
7300 case PRIORITY_NORMAL:
7301 menuitem = gtk_item_factory_get_item
7302 (ifactory, "/Options/Priority/Normal");
7305 menuitem = gtk_item_factory_get_item
7306 (ifactory, "/Options/Priority/Low");
7308 case PRIORITY_LOWEST:
7309 menuitem = gtk_item_factory_get_item
7310 (ifactory, "/Options/Priority/Lowest");
7313 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
7316 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
7318 Compose *compose = (Compose *) data;
7320 GtkItemFactory *ifactory;
7321 gboolean can_sign = FALSE, can_encrypt = FALSE;
7323 g_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
7325 if (!GTK_CHECK_MENU_ITEM(widget)->active)
7328 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
7329 g_free(compose->privacy_system);
7330 compose->privacy_system = NULL;
7331 if (systemid != NULL) {
7332 compose->privacy_system = g_strdup(systemid);
7334 can_sign = privacy_system_can_sign(systemid);
7335 can_encrypt = privacy_system_can_encrypt(systemid);
7338 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
7340 ifactory = gtk_item_factory_from_widget(compose->menubar);
7341 menu_set_sensitive(ifactory, "/Options/Sign", can_sign);
7342 menu_set_sensitive(ifactory, "/Options/Encrypt", can_encrypt);
7345 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
7347 static gchar *branch_path = "/Options/Privacy System";
7348 GtkItemFactory *ifactory;
7349 GtkWidget *menuitem = NULL;
7351 gboolean can_sign = FALSE, can_encrypt = FALSE;
7352 gboolean found = FALSE;
7354 ifactory = gtk_item_factory_from_widget(compose->menubar);
7356 if (compose->privacy_system != NULL) {
7358 menuitem = gtk_item_factory_get_widget(ifactory, branch_path);
7359 g_return_if_fail(menuitem != NULL);
7361 amenu = GTK_MENU_SHELL(menuitem)->children;
7363 while (amenu != NULL) {
7364 GList *alist = amenu->next;
7366 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
7367 if (systemid != NULL) {
7368 if (strcmp(systemid, compose->privacy_system) == 0) {
7369 menuitem = GTK_WIDGET(amenu->data);
7371 can_sign = privacy_system_can_sign(systemid);
7372 can_encrypt = privacy_system_can_encrypt(systemid);
7376 } else if (strlen(compose->privacy_system) == 0) {
7377 menuitem = GTK_WIDGET(amenu->data);
7380 can_encrypt = FALSE;
7387 if (menuitem != NULL)
7388 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
7390 if (warn && !found && strlen(compose->privacy_system)) {
7391 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
7392 "will not be able to sign or encrypt this message."),
7393 compose->privacy_system);
7397 menu_set_sensitive(ifactory, "/Options/Sign", can_sign);
7398 menu_set_sensitive(ifactory, "/Options/Encrypt", can_encrypt);
7401 static void compose_set_out_encoding(Compose *compose)
7403 GtkItemFactoryEntry *entry;
7404 GtkItemFactory *ifactory;
7405 CharSet out_encoding;
7406 gchar *path, *p, *q;
7409 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
7410 ifactory = gtk_item_factory_from_widget(compose->menubar);
7412 for (entry = compose_entries; entry->callback != compose_address_cb;
7414 if (entry->callback == compose_set_encoding_cb &&
7415 (CharSet)entry->callback_action == out_encoding) {
7416 p = q = path = g_strdup(entry->path);
7428 item = gtk_item_factory_get_item(ifactory, path);
7429 gtk_widget_activate(item);
7436 static void compose_set_template_menu(Compose *compose)
7438 GSList *tmpl_list, *cur;
7442 tmpl_list = template_get_config();
7444 menu = gtk_menu_new();
7446 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
7447 Template *tmpl = (Template *)cur->data;
7449 item = gtk_menu_item_new_with_label(tmpl->name);
7450 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
7451 g_signal_connect(G_OBJECT(item), "activate",
7452 G_CALLBACK(compose_template_activate_cb),
7454 g_object_set_data(G_OBJECT(item), "template", tmpl);
7455 gtk_widget_show(item);
7458 gtk_widget_show(menu);
7459 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
7462 void compose_update_actions_menu(Compose *compose)
7464 GtkItemFactory *ifactory;
7466 ifactory = gtk_item_factory_from_widget(compose->menubar);
7467 action_update_compose_menu(ifactory, "/Tools/Actions", compose);
7470 static void compose_update_privacy_systems_menu(Compose *compose)
7472 static gchar *branch_path = "/Options/Privacy System";
7473 GtkItemFactory *ifactory;
7474 GtkWidget *menuitem;
7475 GSList *systems, *cur;
7478 GtkWidget *system_none;
7481 ifactory = gtk_item_factory_from_widget(compose->menubar);
7483 /* remove old entries */
7484 menuitem = gtk_item_factory_get_widget(ifactory, branch_path);
7485 g_return_if_fail(menuitem != NULL);
7487 amenu = GTK_MENU_SHELL(menuitem)->children->next;
7488 while (amenu != NULL) {
7489 GList *alist = amenu->next;
7490 gtk_widget_destroy(GTK_WIDGET(amenu->data));
7494 system_none = gtk_item_factory_get_widget(ifactory,
7495 "/Options/Privacy System/None");
7497 g_signal_connect(G_OBJECT(system_none), "activate",
7498 G_CALLBACK(compose_set_privacy_system_cb), compose);
7500 systems = privacy_get_system_ids();
7501 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
7502 gchar *systemid = cur->data;
7504 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
7505 widget = gtk_radio_menu_item_new_with_label(group,
7506 privacy_system_get_name(systemid));
7507 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
7508 g_strdup(systemid), g_free);
7509 g_signal_connect(G_OBJECT(widget), "activate",
7510 G_CALLBACK(compose_set_privacy_system_cb), compose);
7512 gtk_menu_append(GTK_MENU(system_none->parent), widget);
7513 gtk_widget_show(widget);
7516 g_slist_free(systems);
7519 void compose_reflect_prefs_all(void)
7524 for (cur = compose_list; cur != NULL; cur = cur->next) {
7525 compose = (Compose *)cur->data;
7526 compose_set_template_menu(compose);
7530 void compose_reflect_prefs_pixmap_theme(void)
7535 for (cur = compose_list; cur != NULL; cur = cur->next) {
7536 compose = (Compose *)cur->data;
7537 toolbar_update(TOOLBAR_COMPOSE, compose);
7541 static const gchar *compose_quote_char_from_context(Compose *compose)
7543 const gchar *qmark = NULL;
7545 g_return_val_if_fail(compose != NULL, NULL);
7547 switch (compose->mode) {
7548 /* use forward-specific quote char */
7549 case COMPOSE_FORWARD:
7550 case COMPOSE_FORWARD_AS_ATTACH:
7551 case COMPOSE_FORWARD_INLINE:
7552 if (compose->folder && compose->folder->prefs &&
7553 compose->folder->prefs->forward_with_format)
7554 qmark = compose->folder->prefs->forward_quotemark;
7555 else if (compose->account->forward_with_format)
7556 qmark = compose->account->forward_quotemark;
7558 qmark = prefs_common.fw_quotemark;
7561 /* use reply-specific quote char in all other modes */
7563 if (compose->folder && compose->folder->prefs &&
7564 compose->folder->prefs->reply_with_format)
7565 qmark = compose->folder->prefs->reply_quotemark;
7566 else if (compose->account->reply_with_format)
7567 qmark = compose->account->reply_quotemark;
7569 qmark = prefs_common.quotemark;
7573 if (qmark == NULL || *qmark == '\0')
7579 static void compose_template_apply(Compose *compose, Template *tmpl,
7583 GtkTextBuffer *buffer;
7587 gchar *parsed_str = NULL;
7588 gint cursor_pos = 0;
7589 const gchar *err_msg = _("Template body format error at line %d.");
7592 /* process the body */
7594 text = GTK_TEXT_VIEW(compose->text);
7595 buffer = gtk_text_view_get_buffer(text);
7598 qmark = compose_quote_char_from_context(compose);
7600 if (compose->replyinfo != NULL) {
7603 gtk_text_buffer_set_text(buffer, "", -1);
7604 mark = gtk_text_buffer_get_insert(buffer);
7605 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7607 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
7608 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
7610 } else if (compose->fwdinfo != NULL) {
7613 gtk_text_buffer_set_text(buffer, "", -1);
7614 mark = gtk_text_buffer_get_insert(buffer);
7615 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7617 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
7618 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
7621 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
7623 GtkTextIter start, end;
7626 gtk_text_buffer_get_start_iter(buffer, &start);
7627 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
7628 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
7630 /* clear the buffer now */
7632 gtk_text_buffer_set_text(buffer, "", -1);
7634 parsed_str = compose_quote_fmt(compose, dummyinfo,
7635 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
7636 procmsg_msginfo_free( dummyinfo );
7642 gtk_text_buffer_set_text(buffer, "", -1);
7643 mark = gtk_text_buffer_get_insert(buffer);
7644 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7647 if (replace && parsed_str && compose->account->auto_sig)
7648 compose_insert_sig(compose, FALSE);
7650 if (replace && parsed_str) {
7651 gtk_text_buffer_get_start_iter(buffer, &iter);
7652 gtk_text_buffer_place_cursor(buffer, &iter);
7656 cursor_pos = quote_fmt_get_cursor_pos();
7657 compose->set_cursor_pos = cursor_pos;
7658 if (cursor_pos == -1)
7660 gtk_text_buffer_get_start_iter(buffer, &iter);
7661 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
7662 gtk_text_buffer_place_cursor(buffer, &iter);
7665 /* process the other fields */
7667 compose_template_apply_fields(compose, tmpl);
7668 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
7669 quote_fmt_reset_vartable();
7670 compose_changed_cb(NULL, compose);
7673 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
7675 MsgInfo* dummyinfo = NULL;
7676 MsgInfo *msginfo = NULL;
7679 if (compose->replyinfo != NULL)
7680 msginfo = compose->replyinfo;
7681 else if (compose->fwdinfo != NULL)
7682 msginfo = compose->fwdinfo;
7684 dummyinfo = compose_msginfo_new_from_compose(compose);
7685 msginfo = dummyinfo;
7688 if (tmpl->from && *tmpl->from != '\0') {
7690 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
7691 compose->gtkaspell);
7693 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
7695 quote_fmt_scan_string(tmpl->from);
7698 buf = quote_fmt_get_buffer();
7700 alertpanel_error(_("Template From format error."));
7702 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
7706 if (tmpl->to && *tmpl->to != '\0') {
7708 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
7709 compose->gtkaspell);
7711 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
7713 quote_fmt_scan_string(tmpl->to);
7716 buf = quote_fmt_get_buffer();
7718 alertpanel_error(_("Template To format error."));
7720 compose_entry_append(compose, buf, COMPOSE_TO);
7724 if (tmpl->cc && *tmpl->cc != '\0') {
7726 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
7727 compose->gtkaspell);
7729 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
7731 quote_fmt_scan_string(tmpl->cc);
7734 buf = quote_fmt_get_buffer();
7736 alertpanel_error(_("Template Cc format error."));
7738 compose_entry_append(compose, buf, COMPOSE_CC);
7742 if (tmpl->bcc && *tmpl->bcc != '\0') {
7744 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
7745 compose->gtkaspell);
7747 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
7749 quote_fmt_scan_string(tmpl->bcc);
7752 buf = quote_fmt_get_buffer();
7754 alertpanel_error(_("Template Bcc format error."));
7756 compose_entry_append(compose, buf, COMPOSE_BCC);
7760 /* process the subject */
7761 if (tmpl->subject && *tmpl->subject != '\0') {
7763 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
7764 compose->gtkaspell);
7766 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
7768 quote_fmt_scan_string(tmpl->subject);
7771 buf = quote_fmt_get_buffer();
7773 alertpanel_error(_("Template subject format error."));
7775 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
7779 procmsg_msginfo_free( dummyinfo );
7782 static void compose_destroy(Compose *compose)
7784 GtkTextBuffer *buffer;
7785 GtkClipboard *clipboard;
7787 compose_list = g_list_remove(compose_list, compose);
7789 if (compose->updating) {
7790 debug_print("danger, not destroying anything now\n");
7791 compose->deferred_destroy = TRUE;
7794 /* NOTE: address_completion_end() does nothing with the window
7795 * however this may change. */
7796 address_completion_end(compose->window);
7798 slist_free_strings(compose->to_list);
7799 g_slist_free(compose->to_list);
7800 slist_free_strings(compose->newsgroup_list);
7801 g_slist_free(compose->newsgroup_list);
7802 slist_free_strings(compose->header_list);
7803 g_slist_free(compose->header_list);
7805 procmsg_msginfo_free(compose->targetinfo);
7806 procmsg_msginfo_free(compose->replyinfo);
7807 procmsg_msginfo_free(compose->fwdinfo);
7809 g_free(compose->replyto);
7810 g_free(compose->cc);
7811 g_free(compose->bcc);
7812 g_free(compose->newsgroups);
7813 g_free(compose->followup_to);
7815 g_free(compose->ml_post);
7817 g_free(compose->inreplyto);
7818 g_free(compose->references);
7819 g_free(compose->msgid);
7820 g_free(compose->boundary);
7822 g_free(compose->redirect_filename);
7823 if (compose->undostruct)
7824 undo_destroy(compose->undostruct);
7826 g_free(compose->sig_str);
7828 g_free(compose->exteditor_file);
7830 g_free(compose->orig_charset);
7832 g_free(compose->privacy_system);
7834 if (addressbook_get_target_compose() == compose)
7835 addressbook_set_target_compose(NULL);
7838 if (compose->gtkaspell) {
7839 gtkaspell_delete(compose->gtkaspell);
7840 compose->gtkaspell = NULL;
7844 prefs_common.compose_width = compose->scrolledwin->allocation.width;
7845 prefs_common.compose_height = compose->window->allocation.height;
7847 if (!gtk_widget_get_parent(compose->paned))
7848 gtk_widget_destroy(compose->paned);
7849 gtk_widget_destroy(compose->popupmenu);
7851 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
7852 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7853 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
7855 gtk_widget_destroy(compose->window);
7856 toolbar_destroy(compose->toolbar);
7857 g_free(compose->toolbar);
7858 g_mutex_free(compose->mutex);
7862 static void compose_attach_info_free(AttachInfo *ainfo)
7864 g_free(ainfo->file);
7865 g_free(ainfo->content_type);
7866 g_free(ainfo->name);
7870 static void compose_attach_update_label(Compose *compose)
7875 GtkTreeModel *model;
7880 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
7881 if(!gtk_tree_model_get_iter_first(model, &iter)) {
7882 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
7886 while(gtk_tree_model_iter_next(model, &iter))
7889 text = g_strdup_printf("(%d)", i);
7890 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
7894 static void compose_attach_remove_selected(Compose *compose)
7896 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
7897 GtkTreeSelection *selection;
7899 GtkTreeModel *model;
7901 selection = gtk_tree_view_get_selection(tree_view);
7902 sel = gtk_tree_selection_get_selected_rows(selection, &model);
7907 for (cur = sel; cur != NULL; cur = cur->next) {
7908 GtkTreePath *path = cur->data;
7909 GtkTreeRowReference *ref = gtk_tree_row_reference_new
7912 gtk_tree_path_free(path);
7915 for (cur = sel; cur != NULL; cur = cur->next) {
7916 GtkTreeRowReference *ref = cur->data;
7917 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
7920 if (gtk_tree_model_get_iter(model, &iter, path))
7921 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
7923 gtk_tree_path_free(path);
7924 gtk_tree_row_reference_free(ref);
7928 compose_attach_update_label(compose);
7931 static struct _AttachProperty
7934 GtkWidget *mimetype_entry;
7935 GtkWidget *encoding_optmenu;
7936 GtkWidget *path_entry;
7937 GtkWidget *filename_entry;
7939 GtkWidget *cancel_btn;
7942 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
7944 gtk_tree_path_free((GtkTreePath *)ptr);
7947 static void compose_attach_property(Compose *compose)
7949 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
7951 GtkComboBox *optmenu;
7952 GtkTreeSelection *selection;
7954 GtkTreeModel *model;
7957 static gboolean cancelled;
7959 /* only if one selected */
7960 selection = gtk_tree_view_get_selection(tree_view);
7961 if (gtk_tree_selection_count_selected_rows(selection) != 1)
7964 sel = gtk_tree_selection_get_selected_rows(selection, &model);
7968 path = (GtkTreePath *) sel->data;
7969 gtk_tree_model_get_iter(model, &iter, path);
7970 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
7973 g_list_foreach(sel, gtk_tree_path_free_, NULL);
7979 if (!attach_prop.window)
7980 compose_attach_property_create(&cancelled);
7981 gtk_widget_grab_focus(attach_prop.ok_btn);
7982 gtk_widget_show(attach_prop.window);
7983 manage_window_set_transient(GTK_WINDOW(attach_prop.window));
7985 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
7986 if (ainfo->encoding == ENC_UNKNOWN)
7987 combobox_select_by_data(optmenu, ENC_BASE64);
7989 combobox_select_by_data(optmenu, ainfo->encoding);
7991 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
7992 ainfo->content_type ? ainfo->content_type : "");
7993 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
7994 ainfo->file ? ainfo->file : "");
7995 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
7996 ainfo->name ? ainfo->name : "");
7999 const gchar *entry_text;
8001 gchar *cnttype = NULL;
8008 gtk_widget_hide(attach_prop.window);
8013 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
8014 if (*entry_text != '\0') {
8017 text = g_strstrip(g_strdup(entry_text));
8018 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
8019 cnttype = g_strdup(text);
8022 alertpanel_error(_("Invalid MIME type."));
8028 ainfo->encoding = combobox_get_active_data(optmenu);
8030 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
8031 if (*entry_text != '\0') {
8032 if (is_file_exist(entry_text) &&
8033 (size = get_file_size(entry_text)) > 0)
8034 file = g_strdup(entry_text);
8037 (_("File doesn't exist or is empty."));
8043 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
8044 if (*entry_text != '\0') {
8045 g_free(ainfo->name);
8046 ainfo->name = g_strdup(entry_text);
8050 g_free(ainfo->content_type);
8051 ainfo->content_type = cnttype;
8054 g_free(ainfo->file);
8058 ainfo->size = (goffset)size;
8060 /* update tree store */
8061 text = to_human_readable(ainfo->size);
8062 gtk_tree_model_get_iter(model, &iter, path);
8063 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
8064 COL_MIMETYPE, ainfo->content_type,
8066 COL_NAME, ainfo->name,
8072 gtk_tree_path_free(path);
8075 #define SET_LABEL_AND_ENTRY(str, entry, top) \
8077 label = gtk_label_new(str); \
8078 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
8079 GTK_FILL, 0, 0, 0); \
8080 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
8082 entry = gtk_entry_new(); \
8083 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
8084 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
8087 static void compose_attach_property_create(gboolean *cancelled)
8093 GtkWidget *mimetype_entry;
8096 GtkListStore *optmenu_menu;
8097 GtkWidget *path_entry;
8098 GtkWidget *filename_entry;
8101 GtkWidget *cancel_btn;
8102 GList *mime_type_list, *strlist;
8105 debug_print("Creating attach_property window...\n");
8107 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
8108 gtk_widget_set_size_request(window, 480, -1);
8109 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
8110 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
8111 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
8112 gtk_window_set_modal(GTK_WINDOW(window), TRUE);
8113 g_signal_connect(G_OBJECT(window), "delete_event",
8114 G_CALLBACK(attach_property_delete_event),
8116 g_signal_connect(G_OBJECT(window), "key_press_event",
8117 G_CALLBACK(attach_property_key_pressed),
8120 vbox = gtk_vbox_new(FALSE, 8);
8121 gtk_container_add(GTK_CONTAINER(window), vbox);
8123 table = gtk_table_new(4, 2, FALSE);
8124 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
8125 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
8126 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
8128 label = gtk_label_new(_("MIME type"));
8129 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
8131 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
8132 mimetype_entry = gtk_combo_box_entry_new_text();
8133 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
8134 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
8136 /* stuff with list */
8137 mime_type_list = procmime_get_mime_type_list();
8139 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
8140 MimeType *type = (MimeType *) mime_type_list->data;
8143 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
8145 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
8148 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
8149 (GCompareFunc)strcmp2);
8152 for (mime_type_list = strlist; mime_type_list != NULL;
8153 mime_type_list = mime_type_list->next) {
8154 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
8155 g_free(mime_type_list->data);
8157 g_list_free(strlist);
8158 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
8159 mimetype_entry = GTK_BIN(mimetype_entry)->child;
8161 label = gtk_label_new(_("Encoding"));
8162 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
8164 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
8166 hbox = gtk_hbox_new(FALSE, 0);
8167 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
8168 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
8170 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
8171 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8173 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
8174 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
8175 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
8176 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
8177 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
8179 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
8181 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
8182 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
8184 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
8185 &ok_btn, GTK_STOCK_OK,
8187 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
8188 gtk_widget_grab_default(ok_btn);
8190 g_signal_connect(G_OBJECT(ok_btn), "clicked",
8191 G_CALLBACK(attach_property_ok),
8193 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
8194 G_CALLBACK(attach_property_cancel),
8197 gtk_widget_show_all(vbox);
8199 attach_prop.window = window;
8200 attach_prop.mimetype_entry = mimetype_entry;
8201 attach_prop.encoding_optmenu = optmenu;
8202 attach_prop.path_entry = path_entry;
8203 attach_prop.filename_entry = filename_entry;
8204 attach_prop.ok_btn = ok_btn;
8205 attach_prop.cancel_btn = cancel_btn;
8208 #undef SET_LABEL_AND_ENTRY
8210 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
8216 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
8222 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
8223 gboolean *cancelled)
8231 static gboolean attach_property_key_pressed(GtkWidget *widget,
8233 gboolean *cancelled)
8235 if (event && event->keyval == GDK_Escape) {
8242 static void compose_exec_ext_editor(Compose *compose)
8249 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
8250 G_DIR_SEPARATOR, compose);
8252 if (pipe(pipe_fds) < 0) {
8258 if ((pid = fork()) < 0) {
8265 /* close the write side of the pipe */
8268 compose->exteditor_file = g_strdup(tmp);
8269 compose->exteditor_pid = pid;
8271 compose_set_ext_editor_sensitive(compose, FALSE);
8273 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
8274 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
8278 } else { /* process-monitoring process */
8284 /* close the read side of the pipe */
8287 if (compose_write_body_to_file(compose, tmp) < 0) {
8288 fd_write_all(pipe_fds[1], "2\n", 2);
8292 pid_ed = compose_exec_ext_editor_real(tmp);
8294 fd_write_all(pipe_fds[1], "1\n", 2);
8298 /* wait until editor is terminated */
8299 waitpid(pid_ed, NULL, 0);
8301 fd_write_all(pipe_fds[1], "0\n", 2);
8308 #endif /* G_OS_UNIX */
8312 static gint compose_exec_ext_editor_real(const gchar *file)
8319 g_return_val_if_fail(file != NULL, -1);
8321 if ((pid = fork()) < 0) {
8326 if (pid != 0) return pid;
8328 /* grandchild process */
8330 if (setpgid(0, getppid()))
8333 if (prefs_common_get_ext_editor_cmd() &&
8334 (p = strchr(prefs_common_get_ext_editor_cmd(), '%')) &&
8335 *(p + 1) == 's' && !strchr(p + 2, '%')) {
8336 g_snprintf(buf, sizeof(buf), prefs_common_get_ext_editor_cmd(), file);
8338 if (prefs_common_get_ext_editor_cmd())
8339 g_warning("External editor command line is invalid: '%s'\n",
8340 prefs_common_get_ext_editor_cmd());
8341 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
8344 cmdline = strsplit_with_quote(buf, " ", 1024);
8345 execvp(cmdline[0], cmdline);
8348 g_strfreev(cmdline);
8353 static gboolean compose_ext_editor_kill(Compose *compose)
8355 pid_t pgid = compose->exteditor_pid * -1;
8358 ret = kill(pgid, 0);
8360 if (ret == 0 || (ret == -1 && EPERM == errno)) {
8364 msg = g_strdup_printf
8365 (_("The external editor is still working.\n"
8366 "Force terminating the process?\n"
8367 "process group id: %d"), -pgid);
8368 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
8369 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
8373 if (val == G_ALERTALTERNATE) {
8374 g_source_remove(compose->exteditor_tag);
8375 g_io_channel_shutdown(compose->exteditor_ch,
8377 g_io_channel_unref(compose->exteditor_ch);
8379 if (kill(pgid, SIGTERM) < 0) perror("kill");
8380 waitpid(compose->exteditor_pid, NULL, 0);
8382 g_warning("Terminated process group id: %d", -pgid);
8383 g_warning("Temporary file: %s",
8384 compose->exteditor_file);
8386 compose_set_ext_editor_sensitive(compose, TRUE);
8388 g_free(compose->exteditor_file);
8389 compose->exteditor_file = NULL;
8390 compose->exteditor_pid = -1;
8391 compose->exteditor_ch = NULL;
8392 compose->exteditor_tag = -1;
8400 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
8404 Compose *compose = (Compose *)data;
8407 debug_print(_("Compose: input from monitoring process\n"));
8409 g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL);
8411 g_io_channel_shutdown(source, FALSE, NULL);
8412 g_io_channel_unref(source);
8414 waitpid(compose->exteditor_pid, NULL, 0);
8416 if (buf[0] == '0') { /* success */
8417 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
8418 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
8420 gtk_text_buffer_set_text(buffer, "", -1);
8421 compose_insert_file(compose, compose->exteditor_file);
8422 compose_changed_cb(NULL, compose);
8424 if (claws_unlink(compose->exteditor_file) < 0)
8425 FILE_OP_ERROR(compose->exteditor_file, "unlink");
8426 } else if (buf[0] == '1') { /* failed */
8427 g_warning("Couldn't exec external editor\n");
8428 if (claws_unlink(compose->exteditor_file) < 0)
8429 FILE_OP_ERROR(compose->exteditor_file, "unlink");
8430 } else if (buf[0] == '2') {
8431 g_warning("Couldn't write to file\n");
8432 } else if (buf[0] == '3') {
8433 g_warning("Pipe read failed\n");
8436 compose_set_ext_editor_sensitive(compose, TRUE);
8438 g_free(compose->exteditor_file);
8439 compose->exteditor_file = NULL;
8440 compose->exteditor_pid = -1;
8441 compose->exteditor_ch = NULL;
8442 compose->exteditor_tag = -1;
8447 static void compose_set_ext_editor_sensitive(Compose *compose,
8450 GtkItemFactory *ifactory;
8452 ifactory = gtk_item_factory_from_widget(compose->menubar);
8454 menu_set_sensitive(ifactory, "/Message/Send", sensitive);
8455 menu_set_sensitive(ifactory, "/Message/Send later", sensitive);
8456 menu_set_sensitive(ifactory, "/Message/Insert file", sensitive);
8457 menu_set_sensitive(ifactory, "/Message/Insert signature", sensitive);
8458 menu_set_sensitive(ifactory, "/Edit/Wrap current paragraph", sensitive);
8459 menu_set_sensitive(ifactory, "/Edit/Wrap all long lines", sensitive);
8460 menu_set_sensitive(ifactory, "/Edit/Edit with external editor",
8463 gtk_widget_set_sensitive(compose->text, sensitive);
8464 if (compose->toolbar->send_btn)
8465 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
8466 if (compose->toolbar->sendl_btn)
8467 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
8468 if (compose->toolbar->draft_btn)
8469 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
8470 if (compose->toolbar->insert_btn)
8471 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
8472 if (compose->toolbar->sig_btn)
8473 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
8474 if (compose->toolbar->exteditor_btn)
8475 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
8476 if (compose->toolbar->linewrap_current_btn)
8477 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
8478 if (compose->toolbar->linewrap_all_btn)
8479 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
8481 #endif /* G_OS_UNIX */
8484 * compose_undo_state_changed:
8486 * Change the sensivity of the menuentries undo and redo
8488 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
8489 gint redo_state, gpointer data)
8491 GtkWidget *widget = GTK_WIDGET(data);
8492 GtkItemFactory *ifactory;
8494 g_return_if_fail(widget != NULL);
8496 ifactory = gtk_item_factory_from_widget(widget);
8498 switch (undo_state) {
8499 case UNDO_STATE_TRUE:
8500 if (!undostruct->undo_state) {
8501 undostruct->undo_state = TRUE;
8502 menu_set_sensitive(ifactory, "/Edit/Undo", TRUE);
8505 case UNDO_STATE_FALSE:
8506 if (undostruct->undo_state) {
8507 undostruct->undo_state = FALSE;
8508 menu_set_sensitive(ifactory, "/Edit/Undo", FALSE);
8511 case UNDO_STATE_UNCHANGED:
8513 case UNDO_STATE_REFRESH:
8514 menu_set_sensitive(ifactory, "/Edit/Undo",
8515 undostruct->undo_state);
8518 g_warning("Undo state not recognized");
8522 switch (redo_state) {
8523 case UNDO_STATE_TRUE:
8524 if (!undostruct->redo_state) {
8525 undostruct->redo_state = TRUE;
8526 menu_set_sensitive(ifactory, "/Edit/Redo", TRUE);
8529 case UNDO_STATE_FALSE:
8530 if (undostruct->redo_state) {
8531 undostruct->redo_state = FALSE;
8532 menu_set_sensitive(ifactory, "/Edit/Redo", FALSE);
8535 case UNDO_STATE_UNCHANGED:
8537 case UNDO_STATE_REFRESH:
8538 menu_set_sensitive(ifactory, "/Edit/Redo",
8539 undostruct->redo_state);
8542 g_warning("Redo state not recognized");
8547 /* callback functions */
8549 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
8550 * includes "non-client" (windows-izm) in calculation, so this calculation
8551 * may not be accurate.
8553 static gboolean compose_edit_size_alloc(GtkEditable *widget,
8554 GtkAllocation *allocation,
8555 GtkSHRuler *shruler)
8557 if (prefs_common.show_ruler) {
8558 gint char_width = 0, char_height = 0;
8559 gint line_width_in_chars;
8561 gtkut_get_font_size(GTK_WIDGET(widget),
8562 &char_width, &char_height);
8563 line_width_in_chars =
8564 (allocation->width - allocation->x) / char_width;
8566 /* got the maximum */
8567 gtk_ruler_set_range(GTK_RULER(shruler),
8568 0.0, line_width_in_chars, 0,
8569 /*line_width_in_chars*/ char_width);
8575 static void account_activated(GtkComboBox *optmenu, gpointer data)
8577 Compose *compose = (Compose *)data;
8580 gchar *folderidentifier;
8581 gint account_id = 0;
8585 /* Get ID of active account in the combo box */
8586 menu = gtk_combo_box_get_model(optmenu);
8587 gtk_combo_box_get_active_iter(optmenu, &iter);
8588 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
8590 ac = account_find_from_id(account_id);
8591 g_return_if_fail(ac != NULL);
8593 if (ac != compose->account)
8594 compose_select_account(compose, ac, FALSE);
8596 /* Set message save folder */
8597 if (account_get_special_folder(compose->account, F_OUTBOX)) {
8598 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
8600 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
8601 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
8603 gtk_editable_delete_text(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
8604 if (account_get_special_folder(compose->account, F_OUTBOX)) {
8605 folderidentifier = folder_item_get_identifier(account_get_special_folder
8606 (compose->account, F_OUTBOX));
8607 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
8608 g_free(folderidentifier);
8612 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
8613 GtkTreeViewColumn *column, Compose *compose)
8615 compose_attach_property(compose);
8618 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
8621 Compose *compose = (Compose *)data;
8622 GtkTreeSelection *attach_selection;
8623 gint attach_nr_selected;
8624 GtkItemFactory *ifactory;
8626 if (!event) return FALSE;
8628 if (event->button == 3) {
8629 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
8630 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
8631 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
8633 if (attach_nr_selected > 0)
8635 menu_set_sensitive(ifactory, "/Remove", TRUE);
8636 menu_set_sensitive(ifactory, "/Properties...", TRUE);
8638 menu_set_sensitive(ifactory, "/Remove", FALSE);
8639 menu_set_sensitive(ifactory, "/Properties...", FALSE);
8642 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
8643 NULL, NULL, event->button, event->time);
8650 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
8653 Compose *compose = (Compose *)data;
8655 if (!event) return FALSE;
8657 switch (event->keyval) {
8659 compose_attach_remove_selected(compose);
8665 static void compose_allow_user_actions (Compose *compose, gboolean allow)
8667 GtkItemFactory *ifactory = gtk_item_factory_from_widget(compose->menubar);
8668 toolbar_comp_set_sensitive(compose, allow);
8669 menu_set_sensitive(ifactory, "/Message", allow);
8670 menu_set_sensitive(ifactory, "/Edit", allow);
8672 menu_set_sensitive(ifactory, "/Spelling", allow);
8674 menu_set_sensitive(ifactory, "/Options", allow);
8675 menu_set_sensitive(ifactory, "/Tools", allow);
8676 menu_set_sensitive(ifactory, "/Help", allow);
8678 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
8682 static void compose_send_cb(gpointer data, guint action, GtkWidget *widget)
8684 Compose *compose = (Compose *)data;
8686 if (prefs_common.work_offline &&
8687 !inc_offline_should_override(TRUE,
8688 _("Claws Mail needs network access in order "
8689 "to send this email.")))
8692 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
8693 g_source_remove(compose->draft_timeout_tag);
8694 compose->draft_timeout_tag = -1;
8697 compose_send(compose);
8700 static void compose_send_later_cb(gpointer data, guint action,
8703 Compose *compose = (Compose *)data;
8707 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
8711 compose_close(compose);
8712 } else if (val == -1) {
8713 alertpanel_error(_("Could not queue message."));
8714 } else if (val == -2) {
8715 alertpanel_error(_("Could not queue message:\n\n%s."), strerror(errno));
8716 } else if (val == -3) {
8717 if (privacy_peek_error())
8718 alertpanel_error(_("Could not queue message for sending:\n\n"
8719 "Signature failed: %s"), privacy_get_error());
8720 } else if (val == -4) {
8721 alertpanel_error(_("Could not queue message for sending:\n\n"
8722 "Charset conversion failed."));
8723 } else if (val == -5) {
8724 alertpanel_error(_("Could not queue message for sending:\n\n"
8725 "Couldn't get recipient encryption key."));
8726 } else if (val == -6) {
8729 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
8732 #define DRAFTED_AT_EXIT "drafted_at_exit"
8733 static void compose_register_draft(MsgInfo *info)
8735 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8736 DRAFTED_AT_EXIT, NULL);
8737 FILE *fp = fopen(filepath, "ab");
8740 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
8748 gboolean compose_draft (gpointer data, guint action)
8750 Compose *compose = (Compose *)data;
8754 MsgFlags flag = {0, 0};
8755 static gboolean lock = FALSE;
8756 MsgInfo *newmsginfo;
8758 gboolean target_locked = FALSE;
8759 gboolean err = FALSE;
8761 if (lock) return FALSE;
8763 if (compose->sending)
8766 draft = account_get_special_folder(compose->account, F_DRAFT);
8767 g_return_val_if_fail(draft != NULL, FALSE);
8769 if (!g_mutex_trylock(compose->mutex)) {
8770 /* we don't want to lock the mutex once it's available,
8771 * because as the only other part of compose.c locking
8772 * it is compose_close - which means once unlocked,
8773 * the compose struct will be freed */
8774 debug_print("couldn't lock mutex, probably sending\n");
8780 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
8781 G_DIR_SEPARATOR, compose);
8782 if ((fp = g_fopen(tmp, "wb")) == NULL) {
8783 FILE_OP_ERROR(tmp, "fopen");
8787 /* chmod for security */
8788 if (change_file_mode_rw(fp, tmp) < 0) {
8789 FILE_OP_ERROR(tmp, "chmod");
8790 g_warning("can't change file mode\n");
8793 /* Save draft infos */
8794 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
8795 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
8797 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
8798 gchar *savefolderid;
8800 savefolderid = gtk_editable_get_chars(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
8801 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
8802 g_free(savefolderid);
8804 if (compose->return_receipt) {
8805 err |= (fprintf(fp, "RRCPT:1\n") < 0);
8807 if (compose->privacy_system) {
8808 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
8809 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
8810 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
8813 /* Message-ID of message replying to */
8814 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
8817 folderid = folder_item_get_identifier(compose->replyinfo->folder);
8818 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
8821 /* Message-ID of message forwarding to */
8822 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
8825 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
8826 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
8830 /* end of headers */
8831 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
8838 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
8842 if (fclose(fp) == EOF) {
8846 if (compose->targetinfo) {
8847 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
8848 flag.perm_flags = target_locked?MSG_LOCKED:0;
8850 flag.tmp_flags = MSG_DRAFT;
8852 folder_item_scan(draft);
8853 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
8854 MsgInfo *tmpinfo = NULL;
8855 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
8856 if (compose->msgid) {
8857 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
8860 msgnum = tmpinfo->msgnum;
8861 procmsg_msginfo_free(tmpinfo);
8862 debug_print("got draft msgnum %d from scanning\n", msgnum);
8864 debug_print("didn't get draft msgnum after scanning\n");
8867 debug_print("got draft msgnum %d from adding\n", msgnum);
8873 if (action != COMPOSE_AUTO_SAVE) {
8874 if (action != COMPOSE_DRAFT_FOR_EXIT)
8875 alertpanel_error(_("Could not save draft."));
8878 gtkut_window_popup(compose->window);
8879 val = alertpanel_full(_("Could not save draft"),
8880 _("Could not save draft.\n"
8881 "Do you want to cancel exit or discard this email?"),
8882 _("_Cancel exit"), _("_Discard email"), NULL,
8883 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
8884 if (val == G_ALERTALTERNATE) {
8886 g_mutex_unlock(compose->mutex); /* must be done before closing */
8887 compose_close(compose);
8891 g_mutex_unlock(compose->mutex); /* must be done before closing */
8900 if (compose->mode == COMPOSE_REEDIT) {
8901 compose_remove_reedit_target(compose, TRUE);
8904 newmsginfo = folder_item_get_msginfo(draft, msgnum);
8907 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
8909 procmsg_msginfo_set_flags(newmsginfo, MSG_LOCKED, MSG_DRAFT);
8911 procmsg_msginfo_set_flags(newmsginfo, 0, MSG_DRAFT);
8912 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
8913 procmsg_msginfo_set_flags(newmsginfo, 0,
8914 MSG_HAS_ATTACHMENT);
8916 if (action == COMPOSE_DRAFT_FOR_EXIT) {
8917 compose_register_draft(newmsginfo);
8919 procmsg_msginfo_free(newmsginfo);
8922 folder_item_scan(draft);
8924 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
8926 g_mutex_unlock(compose->mutex); /* must be done before closing */
8927 compose_close(compose);
8933 path = folder_item_fetch_msg(draft, msgnum);
8935 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
8938 if (g_stat(path, &s) < 0) {
8939 FILE_OP_ERROR(path, "stat");
8945 procmsg_msginfo_free(compose->targetinfo);
8946 compose->targetinfo = procmsg_msginfo_new();
8947 compose->targetinfo->msgnum = msgnum;
8948 compose->targetinfo->size = (goffset)s.st_size;
8949 compose->targetinfo->mtime = s.st_mtime;
8950 compose->targetinfo->folder = draft;
8952 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
8953 compose->mode = COMPOSE_REEDIT;
8955 if (action == COMPOSE_AUTO_SAVE) {
8956 compose->autosaved_draft = compose->targetinfo;
8958 compose->modified = FALSE;
8959 compose_set_title(compose);
8963 g_mutex_unlock(compose->mutex);
8967 void compose_clear_exit_drafts(void)
8969 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8970 DRAFTED_AT_EXIT, NULL);
8971 if (is_file_exist(filepath))
8972 claws_unlink(filepath);
8977 void compose_reopen_exit_drafts(void)
8979 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8980 DRAFTED_AT_EXIT, NULL);
8981 FILE *fp = fopen(filepath, "rb");
8985 while (fgets(buf, sizeof(buf), fp)) {
8986 gchar **parts = g_strsplit(buf, "\t", 2);
8987 const gchar *folder = parts[0];
8988 int msgnum = parts[1] ? atoi(parts[1]):-1;
8990 if (folder && *folder && msgnum > -1) {
8991 FolderItem *item = folder_find_item_from_identifier(folder);
8992 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
8994 compose_reedit(info, FALSE);
9001 compose_clear_exit_drafts();
9004 static void compose_draft_cb(gpointer data, guint action, GtkWidget *widget)
9006 compose_draft(data, action);
9009 static void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
9011 if (compose && file_list) {
9014 for ( tmp = file_list; tmp; tmp = tmp->next) {
9015 gchar *file = (gchar *) tmp->data;
9016 gchar *utf8_filename = conv_filename_to_utf8(file);
9017 compose_attach_append(compose, file, utf8_filename, NULL);
9018 compose_changed_cb(NULL, compose);
9023 g_free(utf8_filename);
9028 static void compose_attach_cb(gpointer data, guint action, GtkWidget *widget)
9030 Compose *compose = (Compose *)data;
9033 if (compose->redirect_filename != NULL)
9036 file_list = filesel_select_multiple_files_open(_("Select file"));
9039 compose_attach_from_list(compose, file_list, TRUE);
9040 g_list_free(file_list);
9044 static void compose_insert_file_cb(gpointer data, guint action,
9047 Compose *compose = (Compose *)data;
9050 file_list = filesel_select_multiple_files_open(_("Select file"));
9055 for ( tmp = file_list; tmp; tmp = tmp->next) {
9056 gchar *file = (gchar *) tmp->data;
9057 gchar *filedup = g_strdup(file);
9058 gchar *shortfile = g_path_get_basename(filedup);
9059 ComposeInsertResult res;
9061 res = compose_insert_file(compose, file);
9062 if (res == COMPOSE_INSERT_READ_ERROR) {
9063 alertpanel_error(_("File '%s' could not be read."), shortfile);
9064 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
9065 alertpanel_error(_("File '%s' contained invalid characters\n"
9066 "for the current encoding, insertion may be incorrect."), shortfile);
9072 g_list_free(file_list);
9076 static void compose_insert_sig_cb(gpointer data, guint action,
9079 Compose *compose = (Compose *)data;
9081 compose_insert_sig(compose, FALSE);
9084 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
9088 Compose *compose = (Compose *)data;
9090 gtkut_widget_get_uposition(widget, &x, &y);
9091 prefs_common.compose_x = x;
9092 prefs_common.compose_y = y;
9094 if (compose->sending || compose->updating)
9096 compose_close_cb(compose, 0, NULL);
9100 void compose_close_toolbar(Compose *compose)
9102 compose_close_cb(compose, 0, NULL);
9105 static void compose_close_cb(gpointer data, guint action, GtkWidget *widget)
9107 Compose *compose = (Compose *)data;
9111 if (compose->exteditor_tag != -1) {
9112 if (!compose_ext_editor_kill(compose))
9117 if (compose->modified) {
9118 if (!g_mutex_trylock(compose->mutex)) {
9119 /* we don't want to lock the mutex once it's available,
9120 * because as the only other part of compose.c locking
9121 * it is compose_close - which means once unlocked,
9122 * the compose struct will be freed */
9123 debug_print("couldn't lock mutex, probably sending\n");
9126 val = alertpanel(_("Discard message"),
9127 _("This message has been modified. Discard it?"),
9128 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
9129 g_mutex_unlock(compose->mutex);
9131 case G_ALERTDEFAULT:
9132 if (prefs_common.autosave)
9133 compose_remove_draft(compose);
9135 case G_ALERTALTERNATE:
9136 compose_draft_cb(data, COMPOSE_QUIT_EDITING, NULL);
9143 compose_close(compose);
9146 static void compose_set_encoding_cb(gpointer data, guint action,
9149 Compose *compose = (Compose *)data;
9151 if (GTK_CHECK_MENU_ITEM(widget)->active)
9152 compose->out_encoding = (CharSet)action;
9155 static void compose_address_cb(gpointer data, guint action, GtkWidget *widget)
9157 Compose *compose = (Compose *)data;
9159 addressbook_open(compose);
9162 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
9164 Compose *compose = (Compose *)data;
9169 tmpl = g_object_get_data(G_OBJECT(widget), "template");
9170 g_return_if_fail(tmpl != NULL);
9172 msg = g_strdup_printf(_("Do you want to apply the template '%s' ?"),
9174 val = alertpanel(_("Apply template"), msg,
9175 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
9178 if (val == G_ALERTDEFAULT)
9179 compose_template_apply(compose, tmpl, TRUE);
9180 else if (val == G_ALERTALTERNATE)
9181 compose_template_apply(compose, tmpl, FALSE);
9184 static void compose_ext_editor_cb(gpointer data, guint action,
9187 Compose *compose = (Compose *)data;
9189 compose_exec_ext_editor(compose);
9192 static void compose_undo_cb(Compose *compose)
9194 gboolean prev_autowrap = compose->autowrap;
9196 compose->autowrap = FALSE;
9197 undo_undo(compose->undostruct);
9198 compose->autowrap = prev_autowrap;
9201 static void compose_redo_cb(Compose *compose)
9203 gboolean prev_autowrap = compose->autowrap;
9205 compose->autowrap = FALSE;
9206 undo_redo(compose->undostruct);
9207 compose->autowrap = prev_autowrap;
9210 static void entry_cut_clipboard(GtkWidget *entry)
9212 if (GTK_IS_EDITABLE(entry))
9213 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
9214 else if (GTK_IS_TEXT_VIEW(entry))
9215 gtk_text_buffer_cut_clipboard(
9216 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
9217 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
9221 static void entry_copy_clipboard(GtkWidget *entry)
9223 if (GTK_IS_EDITABLE(entry))
9224 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
9225 else if (GTK_IS_TEXT_VIEW(entry))
9226 gtk_text_buffer_copy_clipboard(
9227 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
9228 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
9231 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
9232 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
9234 if (GTK_IS_TEXT_VIEW(entry)) {
9235 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
9236 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
9237 GtkTextIter start_iter, end_iter;
9239 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
9241 if (contents == NULL)
9244 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
9246 /* we shouldn't delete the selection when middle-click-pasting, or we
9247 * can't mid-click-paste our own selection */
9248 if (clip != GDK_SELECTION_PRIMARY) {
9249 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
9252 if (insert_place == NULL) {
9253 /* if insert_place isn't specified, insert at the cursor.
9254 * used for Ctrl-V pasting */
9255 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
9256 start = gtk_text_iter_get_offset(&start_iter);
9257 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
9259 /* if insert_place is specified, paste here.
9260 * used for mid-click-pasting */
9261 start = gtk_text_iter_get_offset(insert_place);
9262 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
9266 /* paste unwrapped: mark the paste so it's not wrapped later */
9267 end = start + strlen(contents);
9268 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
9269 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
9270 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
9271 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
9272 /* rewrap paragraph now (after a mid-click-paste) */
9273 mark_start = gtk_text_buffer_get_insert(buffer);
9274 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
9275 gtk_text_iter_backward_char(&start_iter);
9276 compose_beautify_paragraph(compose, &start_iter, TRUE);
9278 } else if (GTK_IS_EDITABLE(entry))
9279 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
9281 compose->modified = TRUE;
9284 static void entry_allsel(GtkWidget *entry)
9286 if (GTK_IS_EDITABLE(entry))
9287 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
9288 else if (GTK_IS_TEXT_VIEW(entry)) {
9289 GtkTextIter startiter, enditer;
9290 GtkTextBuffer *textbuf;
9292 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
9293 gtk_text_buffer_get_start_iter(textbuf, &startiter);
9294 gtk_text_buffer_get_end_iter(textbuf, &enditer);
9296 gtk_text_buffer_move_mark_by_name(textbuf,
9297 "selection_bound", &startiter);
9298 gtk_text_buffer_move_mark_by_name(textbuf,
9299 "insert", &enditer);
9303 static void compose_cut_cb(Compose *compose)
9305 if (compose->focused_editable
9306 #ifndef GENERIC_UMPC
9307 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9310 entry_cut_clipboard(compose->focused_editable);
9313 static void compose_copy_cb(Compose *compose)
9315 if (compose->focused_editable
9316 #ifndef GENERIC_UMPC
9317 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9320 entry_copy_clipboard(compose->focused_editable);
9323 static void compose_paste_cb(Compose *compose)
9326 GtkTextBuffer *buffer;
9328 if (compose->focused_editable &&
9329 GTK_WIDGET_HAS_FOCUS(compose->focused_editable))
9330 entry_paste_clipboard(compose, compose->focused_editable,
9331 prefs_common.linewrap_pastes,
9332 GDK_SELECTION_CLIPBOARD, NULL);
9336 static void compose_paste_as_quote_cb(Compose *compose)
9338 gint wrap_quote = prefs_common.linewrap_quote;
9339 if (compose->focused_editable
9340 #ifndef GENERIC_UMPC
9341 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9344 /* let text_insert() (called directly or at a later time
9345 * after the gtk_editable_paste_clipboard) know that
9346 * text is to be inserted as a quotation. implemented
9347 * by using a simple refcount... */
9348 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
9349 G_OBJECT(compose->focused_editable),
9350 "paste_as_quotation"));
9351 g_object_set_data(G_OBJECT(compose->focused_editable),
9352 "paste_as_quotation",
9353 GINT_TO_POINTER(paste_as_quotation + 1));
9354 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
9355 entry_paste_clipboard(compose, compose->focused_editable,
9356 prefs_common.linewrap_pastes,
9357 GDK_SELECTION_CLIPBOARD, NULL);
9358 prefs_common.linewrap_quote = wrap_quote;
9362 static void compose_paste_no_wrap_cb(Compose *compose)
9365 GtkTextBuffer *buffer;
9367 if (compose->focused_editable
9368 #ifndef GENERIC_UMPC
9369 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9372 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
9373 GDK_SELECTION_CLIPBOARD, NULL);
9377 static void compose_paste_wrap_cb(Compose *compose)
9380 GtkTextBuffer *buffer;
9382 if (compose->focused_editable
9383 #ifndef GENERIC_UMPC
9384 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9387 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
9388 GDK_SELECTION_CLIPBOARD, NULL);
9392 static void compose_allsel_cb(Compose *compose)
9394 if (compose->focused_editable
9395 #ifndef GENERIC_UMPC
9396 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9399 entry_allsel(compose->focused_editable);
9402 static void textview_move_beginning_of_line (GtkTextView *text)
9404 GtkTextBuffer *buffer;
9408 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9410 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9411 mark = gtk_text_buffer_get_insert(buffer);
9412 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9413 gtk_text_iter_set_line_offset(&ins, 0);
9414 gtk_text_buffer_place_cursor(buffer, &ins);
9417 static void textview_move_forward_character (GtkTextView *text)
9419 GtkTextBuffer *buffer;
9423 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9425 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9426 mark = gtk_text_buffer_get_insert(buffer);
9427 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9428 if (gtk_text_iter_forward_cursor_position(&ins))
9429 gtk_text_buffer_place_cursor(buffer, &ins);
9432 static void textview_move_backward_character (GtkTextView *text)
9434 GtkTextBuffer *buffer;
9438 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9440 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9441 mark = gtk_text_buffer_get_insert(buffer);
9442 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9443 if (gtk_text_iter_backward_cursor_position(&ins))
9444 gtk_text_buffer_place_cursor(buffer, &ins);
9447 static void textview_move_forward_word (GtkTextView *text)
9449 GtkTextBuffer *buffer;
9454 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9456 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9457 mark = gtk_text_buffer_get_insert(buffer);
9458 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9459 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
9460 if (gtk_text_iter_forward_word_ends(&ins, count)) {
9461 gtk_text_iter_backward_word_start(&ins);
9462 gtk_text_buffer_place_cursor(buffer, &ins);
9466 static void textview_move_backward_word (GtkTextView *text)
9468 GtkTextBuffer *buffer;
9473 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9475 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9476 mark = gtk_text_buffer_get_insert(buffer);
9477 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9478 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
9479 if (gtk_text_iter_backward_word_starts(&ins, 1))
9480 gtk_text_buffer_place_cursor(buffer, &ins);
9483 static void textview_move_end_of_line (GtkTextView *text)
9485 GtkTextBuffer *buffer;
9489 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9491 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9492 mark = gtk_text_buffer_get_insert(buffer);
9493 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9494 if (gtk_text_iter_forward_to_line_end(&ins))
9495 gtk_text_buffer_place_cursor(buffer, &ins);
9498 static void textview_move_next_line (GtkTextView *text)
9500 GtkTextBuffer *buffer;
9505 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9507 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9508 mark = gtk_text_buffer_get_insert(buffer);
9509 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9510 offset = gtk_text_iter_get_line_offset(&ins);
9511 if (gtk_text_iter_forward_line(&ins)) {
9512 gtk_text_iter_set_line_offset(&ins, offset);
9513 gtk_text_buffer_place_cursor(buffer, &ins);
9517 static void textview_move_previous_line (GtkTextView *text)
9519 GtkTextBuffer *buffer;
9524 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9526 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9527 mark = gtk_text_buffer_get_insert(buffer);
9528 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9529 offset = gtk_text_iter_get_line_offset(&ins);
9530 if (gtk_text_iter_backward_line(&ins)) {
9531 gtk_text_iter_set_line_offset(&ins, offset);
9532 gtk_text_buffer_place_cursor(buffer, &ins);
9536 static void textview_delete_forward_character (GtkTextView *text)
9538 GtkTextBuffer *buffer;
9540 GtkTextIter ins, end_iter;
9542 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9544 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9545 mark = gtk_text_buffer_get_insert(buffer);
9546 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9548 if (gtk_text_iter_forward_char(&end_iter)) {
9549 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9553 static void textview_delete_backward_character (GtkTextView *text)
9555 GtkTextBuffer *buffer;
9557 GtkTextIter ins, end_iter;
9559 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9561 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9562 mark = gtk_text_buffer_get_insert(buffer);
9563 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9565 if (gtk_text_iter_backward_char(&end_iter)) {
9566 gtk_text_buffer_delete(buffer, &end_iter, &ins);
9570 static void textview_delete_forward_word (GtkTextView *text)
9572 GtkTextBuffer *buffer;
9574 GtkTextIter ins, end_iter;
9576 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9578 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9579 mark = gtk_text_buffer_get_insert(buffer);
9580 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9582 if (gtk_text_iter_forward_word_end(&end_iter)) {
9583 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9587 static void textview_delete_backward_word (GtkTextView *text)
9589 GtkTextBuffer *buffer;
9591 GtkTextIter ins, end_iter;
9593 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9595 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9596 mark = gtk_text_buffer_get_insert(buffer);
9597 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9599 if (gtk_text_iter_backward_word_start(&end_iter)) {
9600 gtk_text_buffer_delete(buffer, &end_iter, &ins);
9604 static void textview_delete_line (GtkTextView *text)
9606 GtkTextBuffer *buffer;
9608 GtkTextIter ins, start_iter, end_iter;
9610 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9612 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9613 mark = gtk_text_buffer_get_insert(buffer);
9614 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9617 gtk_text_iter_set_line_offset(&start_iter, 0);
9620 if (gtk_text_iter_ends_line(&end_iter)){
9621 if (!gtk_text_iter_forward_char(&end_iter))
9622 gtk_text_iter_backward_char(&start_iter);
9625 gtk_text_iter_forward_to_line_end(&end_iter);
9626 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
9629 static void textview_delete_to_line_end (GtkTextView *text)
9631 GtkTextBuffer *buffer;
9633 GtkTextIter ins, end_iter;
9635 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9637 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9638 mark = gtk_text_buffer_get_insert(buffer);
9639 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9641 if (gtk_text_iter_ends_line(&end_iter))
9642 gtk_text_iter_forward_char(&end_iter);
9644 gtk_text_iter_forward_to_line_end(&end_iter);
9645 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9648 static void compose_advanced_action_cb(Compose *compose,
9649 ComposeCallAdvancedAction action)
9651 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9653 void (*do_action) (GtkTextView *text);
9654 } action_table[] = {
9655 {textview_move_beginning_of_line},
9656 {textview_move_forward_character},
9657 {textview_move_backward_character},
9658 {textview_move_forward_word},
9659 {textview_move_backward_word},
9660 {textview_move_end_of_line},
9661 {textview_move_next_line},
9662 {textview_move_previous_line},
9663 {textview_delete_forward_character},
9664 {textview_delete_backward_character},
9665 {textview_delete_forward_word},
9666 {textview_delete_backward_word},
9667 {textview_delete_line},
9668 {textview_delete_to_line_end}
9671 if (!GTK_WIDGET_HAS_FOCUS(text)) return;
9673 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
9674 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
9675 if (action_table[action].do_action)
9676 action_table[action].do_action(text);
9678 g_warning("Not implemented yet.");
9682 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
9686 if (GTK_IS_EDITABLE(widget)) {
9687 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
9688 gtk_editable_set_position(GTK_EDITABLE(widget),
9691 if (widget->parent && widget->parent->parent
9692 && widget->parent->parent->parent) {
9693 if (GTK_IS_SCROLLED_WINDOW(widget->parent->parent->parent)) {
9694 gint y = widget->allocation.y;
9695 gint height = widget->allocation.height;
9696 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
9697 (GTK_SCROLLED_WINDOW(widget->parent->parent->parent));
9699 if (y < (int)shown->value) {
9700 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown), y - 1);
9702 if (y + height > (int)shown->value + (int)shown->page_size) {
9703 if (y - height - 1 < (int)shown->upper - (int)shown->page_size) {
9704 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
9705 y + height - (int)shown->page_size - 1);
9707 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
9708 (int)shown->upper - (int)shown->page_size - 1);
9715 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
9716 compose->focused_editable = widget;
9719 if (GTK_IS_TEXT_VIEW(widget)
9720 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
9721 gtk_widget_ref(compose->notebook);
9722 gtk_widget_ref(compose->edit_vbox);
9723 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
9724 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
9725 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
9726 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
9727 gtk_widget_unref(compose->notebook);
9728 gtk_widget_unref(compose->edit_vbox);
9729 g_signal_handlers_block_by_func(G_OBJECT(widget),
9730 G_CALLBACK(compose_grab_focus_cb),
9732 gtk_widget_grab_focus(widget);
9733 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
9734 G_CALLBACK(compose_grab_focus_cb),
9736 } else if (!GTK_IS_TEXT_VIEW(widget)
9737 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
9738 gtk_widget_ref(compose->notebook);
9739 gtk_widget_ref(compose->edit_vbox);
9740 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
9741 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
9742 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
9743 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
9744 gtk_widget_unref(compose->notebook);
9745 gtk_widget_unref(compose->edit_vbox);
9746 g_signal_handlers_block_by_func(G_OBJECT(widget),
9747 G_CALLBACK(compose_grab_focus_cb),
9749 gtk_widget_grab_focus(widget);
9750 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
9751 G_CALLBACK(compose_grab_focus_cb),
9757 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
9759 compose->modified = TRUE;
9760 #ifndef GENERIC_UMPC
9761 compose_set_title(compose);
9765 static void compose_wrap_cb(gpointer data, guint action, GtkWidget *widget)
9767 Compose *compose = (Compose *)data;
9770 compose_wrap_all_full(compose, TRUE);
9772 compose_beautify_paragraph(compose, NULL, TRUE);
9775 static void compose_find_cb(gpointer data, guint action, GtkWidget *widget)
9777 Compose *compose = (Compose *)data;
9779 message_search_compose(compose);
9782 static void compose_toggle_autowrap_cb(gpointer data, guint action,
9785 Compose *compose = (Compose *)data;
9786 compose->autowrap = GTK_CHECK_MENU_ITEM(widget)->active;
9787 if (compose->autowrap)
9788 compose_wrap_all_full(compose, TRUE);
9789 compose->autowrap = GTK_CHECK_MENU_ITEM(widget)->active;
9792 static void compose_toggle_sign_cb(gpointer data, guint action,
9795 Compose *compose = (Compose *)data;
9797 if (GTK_CHECK_MENU_ITEM(widget)->active)
9798 compose->use_signing = TRUE;
9800 compose->use_signing = FALSE;
9803 static void compose_toggle_encrypt_cb(gpointer data, guint action,
9806 Compose *compose = (Compose *)data;
9808 if (GTK_CHECK_MENU_ITEM(widget)->active)
9809 compose->use_encryption = TRUE;
9811 compose->use_encryption = FALSE;
9814 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
9816 g_free(compose->privacy_system);
9818 compose->privacy_system = g_strdup(account->default_privacy_system);
9819 compose_update_privacy_system_menu_item(compose, warn);
9822 static void compose_toggle_ruler_cb(gpointer data, guint action,
9825 Compose *compose = (Compose *)data;
9827 if (GTK_CHECK_MENU_ITEM(widget)->active) {
9828 gtk_widget_show(compose->ruler_hbox);
9829 prefs_common.show_ruler = TRUE;
9831 gtk_widget_hide(compose->ruler_hbox);
9832 gtk_widget_queue_resize(compose->edit_vbox);
9833 prefs_common.show_ruler = FALSE;
9837 static void compose_attach_drag_received_cb (GtkWidget *widget,
9838 GdkDragContext *context,
9841 GtkSelectionData *data,
9846 Compose *compose = (Compose *)user_data;
9849 if (gdk_atom_name(data->type) &&
9850 !strcmp(gdk_atom_name(data->type), "text/uri-list")
9851 && gtk_drag_get_source_widget(context) !=
9852 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
9853 list = uri_list_extract_filenames((const gchar *)data->data);
9854 for (tmp = list; tmp != NULL; tmp = tmp->next) {
9855 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
9856 compose_attach_append
9857 (compose, (const gchar *)tmp->data,
9858 utf8_filename, NULL);
9859 g_free(utf8_filename);
9861 if (list) compose_changed_cb(NULL, compose);
9862 list_free_strings(list);
9864 } else if (gtk_drag_get_source_widget(context)
9865 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
9866 /* comes from our summaryview */
9867 SummaryView * summaryview = NULL;
9868 GSList * list = NULL, *cur = NULL;
9870 if (mainwindow_get_mainwindow())
9871 summaryview = mainwindow_get_mainwindow()->summaryview;
9874 list = summary_get_selected_msg_list(summaryview);
9876 for (cur = list; cur; cur = cur->next) {
9877 MsgInfo *msginfo = (MsgInfo *)cur->data;
9880 file = procmsg_get_message_file_full(msginfo,
9883 compose_attach_append(compose, (const gchar *)file,
9884 (const gchar *)file, "message/rfc822");
9892 static gboolean compose_drag_drop(GtkWidget *widget,
9893 GdkDragContext *drag_context,
9895 guint time, gpointer user_data)
9897 /* not handling this signal makes compose_insert_drag_received_cb
9902 static void compose_insert_drag_received_cb (GtkWidget *widget,
9903 GdkDragContext *drag_context,
9906 GtkSelectionData *data,
9911 Compose *compose = (Compose *)user_data;
9914 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
9916 if (gdk_atom_name(data->type) && !strcmp(gdk_atom_name(data->type), "text/uri-list")) {
9917 AlertValue val = G_ALERTDEFAULT;
9919 list = uri_list_extract_filenames((const gchar *)data->data);
9921 if (list == NULL && strstr((gchar *)(data->data), "://")) {
9922 /* Assume a list of no files, and data has ://, is a remote link */
9923 gchar *tmpdata = g_strstrip(g_strdup((const gchar *)data->data));
9924 gchar *tmpfile = get_tmp_file();
9925 str_write_to_file(tmpdata, tmpfile);
9927 compose_insert_file(compose, tmpfile);
9928 claws_unlink(tmpfile);
9930 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9931 compose_beautify_paragraph(compose, NULL, TRUE);
9934 switch (prefs_common.compose_dnd_mode) {
9935 case COMPOSE_DND_ASK:
9936 val = alertpanel_full(_("Insert or attach?"),
9937 _("Do you want to insert the contents of the file(s) "
9938 "into the message body, or attach it to the email?"),
9939 GTK_STOCK_CANCEL, _("+_Insert"), _("_Attach"),
9940 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
9942 case COMPOSE_DND_INSERT:
9943 val = G_ALERTALTERNATE;
9945 case COMPOSE_DND_ATTACH:
9949 /* unexpected case */
9950 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
9953 if (val & G_ALERTDISABLE) {
9954 val &= ~G_ALERTDISABLE;
9955 /* remember what action to perform by default, only if we don't click Cancel */
9956 if (val == G_ALERTALTERNATE)
9957 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
9958 else if (val == G_ALERTOTHER)
9959 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
9962 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
9963 gtk_drag_finish(drag_context, FALSE, FALSE, time);
9964 list_free_strings(list);
9967 } else if (val == G_ALERTOTHER) {
9968 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
9969 list_free_strings(list);
9974 for (tmp = list; tmp != NULL; tmp = tmp->next) {
9975 compose_insert_file(compose, (const gchar *)tmp->data);
9977 list_free_strings(list);
9979 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9982 #if GTK_CHECK_VERSION(2, 8, 0)
9983 /* do nothing, handled by GTK */
9985 gchar *tmpfile = get_tmp_file();
9986 str_write_to_file((const gchar *)data->data, tmpfile);
9987 compose_insert_file(compose, tmpfile);
9988 claws_unlink(tmpfile);
9990 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9994 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9997 static void compose_header_drag_received_cb (GtkWidget *widget,
9998 GdkDragContext *drag_context,
10001 GtkSelectionData *data,
10004 gpointer user_data)
10006 GtkEditable *entry = (GtkEditable *)user_data;
10007 gchar *email = (gchar *)data->data;
10009 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
10012 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
10013 gchar *decoded=g_new(gchar, strlen(email));
10016 email += strlen("mailto:");
10017 decode_uri(decoded, email); /* will fit */
10018 gtk_editable_delete_text(entry, 0, -1);
10019 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
10020 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10024 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10027 static void compose_toggle_return_receipt_cb(gpointer data, guint action,
10030 Compose *compose = (Compose *)data;
10032 if (GTK_CHECK_MENU_ITEM(widget)->active)
10033 compose->return_receipt = TRUE;
10035 compose->return_receipt = FALSE;
10038 static void compose_toggle_remove_refs_cb(gpointer data, guint action,
10041 Compose *compose = (Compose *)data;
10043 if (GTK_CHECK_MENU_ITEM(widget)->active)
10044 compose->remove_references = TRUE;
10046 compose->remove_references = FALSE;
10049 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
10050 GdkEventKey *event,
10051 ComposeHeaderEntry *headerentry)
10053 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
10054 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
10055 !(event->state & GDK_MODIFIER_MASK) &&
10056 (event->keyval == GDK_BackSpace) &&
10057 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
10058 gtk_container_remove
10059 (GTK_CONTAINER(headerentry->compose->header_table),
10060 headerentry->combo);
10061 gtk_container_remove
10062 (GTK_CONTAINER(headerentry->compose->header_table),
10063 headerentry->entry);
10064 headerentry->compose->header_list =
10065 g_slist_remove(headerentry->compose->header_list,
10067 g_free(headerentry);
10068 } else if (event->keyval == GDK_Tab) {
10069 if (headerentry->compose->header_last == headerentry) {
10070 /* Override default next focus, and give it to subject_entry
10071 * instead of notebook tabs
10073 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
10074 gtk_widget_grab_focus(headerentry->compose->subject_entry);
10081 static gboolean compose_headerentry_changed_cb(GtkWidget *entry,
10082 ComposeHeaderEntry *headerentry)
10084 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
10085 compose_create_header_entry(headerentry->compose);
10086 g_signal_handlers_disconnect_matched
10087 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
10088 0, 0, NULL, NULL, headerentry);
10090 /* Automatically scroll down */
10091 compose_show_first_last_header(headerentry->compose, FALSE);
10097 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
10099 GtkAdjustment *vadj;
10101 g_return_if_fail(compose);
10102 g_return_if_fail(GTK_IS_WIDGET(compose->header_table));
10103 g_return_if_fail(GTK_IS_VIEWPORT(compose->header_table->parent));
10105 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(compose->header_table->parent));
10106 gtk_adjustment_set_value(vadj, (show_first ? vadj->lower : vadj->upper));
10107 gtk_adjustment_changed(vadj);
10110 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
10111 const gchar *text, gint len, Compose *compose)
10113 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
10114 (G_OBJECT(compose->text), "paste_as_quotation"));
10117 g_return_if_fail(text != NULL);
10119 g_signal_handlers_block_by_func(G_OBJECT(buffer),
10120 G_CALLBACK(text_inserted),
10122 if (paste_as_quotation) {
10124 const gchar *qmark;
10126 GtkTextIter start_iter;
10129 len = strlen(text);
10131 new_text = g_strndup(text, len);
10133 qmark = compose_quote_char_from_context(compose);
10135 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
10136 gtk_text_buffer_place_cursor(buffer, iter);
10138 pos = gtk_text_iter_get_offset(iter);
10140 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
10141 _("Quote format error at line %d."));
10142 quote_fmt_reset_vartable();
10144 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
10145 GINT_TO_POINTER(paste_as_quotation - 1));
10147 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
10148 gtk_text_buffer_place_cursor(buffer, iter);
10149 gtk_text_buffer_delete_mark(buffer, mark);
10151 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
10152 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
10153 compose_beautify_paragraph(compose, &start_iter, FALSE);
10154 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
10155 gtk_text_buffer_delete_mark(buffer, mark);
10157 if (strcmp(text, "\n") || compose->automatic_break
10158 || gtk_text_iter_starts_line(iter))
10159 gtk_text_buffer_insert(buffer, iter, text, len);
10161 /* check if the preceding is just whitespace or quote */
10162 GtkTextIter start_line;
10163 gchar *tmp = NULL, *quote = NULL;
10164 gint quote_len = 0, is_normal = 0;
10165 start_line = *iter;
10166 gtk_text_iter_set_line_offset(&start_line, 0);
10167 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
10169 if (*tmp == '\0') {
10172 quote = compose_get_quote_str(buffer, &start_line, "e_len);
10180 gtk_text_buffer_insert(buffer, iter, text, len);
10182 gtk_text_buffer_insert_with_tags_by_name(buffer,
10183 iter, text, len, "no_join", NULL);
10188 if (!paste_as_quotation) {
10189 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
10190 compose_beautify_paragraph(compose, iter, FALSE);
10191 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
10192 gtk_text_buffer_delete_mark(buffer, mark);
10195 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
10196 G_CALLBACK(text_inserted),
10198 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
10200 if (prefs_common.autosave &&
10201 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
10202 compose->draft_timeout_tag != -2 /* disabled while loading */)
10203 compose->draft_timeout_tag = g_timeout_add
10204 (500, (GtkFunction) compose_defer_auto_save_draft, compose);
10206 static gint compose_defer_auto_save_draft(Compose *compose)
10208 compose->draft_timeout_tag = -1;
10209 compose_draft_cb((gpointer)compose, COMPOSE_AUTO_SAVE, NULL);
10214 static void compose_check_all(Compose *compose)
10216 if (compose->gtkaspell)
10217 gtkaspell_check_all(compose->gtkaspell);
10220 static void compose_highlight_all(Compose *compose)
10222 if (compose->gtkaspell)
10223 gtkaspell_highlight_all(compose->gtkaspell);
10226 static void compose_check_backwards(Compose *compose)
10228 if (compose->gtkaspell)
10229 gtkaspell_check_backwards(compose->gtkaspell);
10231 GtkItemFactory *ifactory;
10232 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
10233 menu_set_sensitive(ifactory, "/Edit/Check backwards misspelled word", FALSE);
10234 menu_set_sensitive(ifactory, "/Edit/Forward to next misspelled word", FALSE);
10238 static void compose_check_forwards_go(Compose *compose)
10240 if (compose->gtkaspell)
10241 gtkaspell_check_forwards_go(compose->gtkaspell);
10243 GtkItemFactory *ifactory;
10244 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
10245 menu_set_sensitive(ifactory, "/Edit/Check backwards misspelled word", FALSE);
10246 menu_set_sensitive(ifactory, "/Edit/Forward to next misspelled word", FALSE);
10252 *\brief Guess originating forward account from MsgInfo and several
10253 * "common preference" settings. Return NULL if no guess.
10255 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
10257 PrefsAccount *account = NULL;
10259 g_return_val_if_fail(msginfo, NULL);
10260 g_return_val_if_fail(msginfo->folder, NULL);
10261 g_return_val_if_fail(msginfo->folder->prefs, NULL);
10263 if (msginfo->folder->prefs->enable_default_account)
10264 account = account_find_from_id(msginfo->folder->prefs->default_account);
10267 account = msginfo->folder->folder->account;
10269 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
10271 Xstrdup_a(to, msginfo->to, return NULL);
10272 extract_address(to);
10273 account = account_find_from_address(to, FALSE);
10276 if (!account && prefs_common.forward_account_autosel) {
10277 gchar cc[BUFFSIZE];
10278 if (!procheader_get_header_from_msginfo
10279 (msginfo, cc,sizeof cc , "Cc:")) {
10280 gchar *buf = cc + strlen("Cc:");
10281 extract_address(buf);
10282 account = account_find_from_address(buf, FALSE);
10286 if (!account && prefs_common.forward_account_autosel) {
10287 gchar deliveredto[BUFFSIZE];
10288 if (!procheader_get_header_from_msginfo
10289 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
10290 gchar *buf = deliveredto + strlen("Delivered-To:");
10291 extract_address(buf);
10292 account = account_find_from_address(buf, FALSE);
10299 gboolean compose_close(Compose *compose)
10303 if (!g_mutex_trylock(compose->mutex)) {
10304 /* we have to wait for the (possibly deferred by auto-save)
10305 * drafting to be done, before destroying the compose under
10307 debug_print("waiting for drafting to finish...\n");
10308 compose_allow_user_actions(compose, FALSE);
10309 g_timeout_add (500, (GSourceFunc) compose_close, compose);
10312 g_return_val_if_fail(compose, FALSE);
10313 gtkut_widget_get_uposition(compose->window, &x, &y);
10314 prefs_common.compose_x = x;
10315 prefs_common.compose_y = y;
10316 g_mutex_unlock(compose->mutex);
10317 compose_destroy(compose);
10322 * Add entry field for each address in list.
10323 * \param compose E-Mail composition object.
10324 * \param listAddress List of (formatted) E-Mail addresses.
10326 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
10329 node = listAddress;
10331 addr = ( gchar * ) node->data;
10332 compose_entry_append( compose, addr, COMPOSE_TO );
10333 node = g_list_next( node );
10337 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
10338 guint action, gboolean opening_multiple)
10340 gchar *body = NULL;
10341 GSList *new_msglist = NULL;
10342 MsgInfo *tmp_msginfo = NULL;
10343 gboolean originally_enc = FALSE;
10344 Compose *compose = NULL;
10346 g_return_if_fail(msgview != NULL);
10348 g_return_if_fail(msginfo_list != NULL);
10350 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
10351 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
10352 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
10354 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
10355 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
10356 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
10357 orig_msginfo, mimeinfo);
10358 if (tmp_msginfo != NULL) {
10359 new_msglist = g_slist_append(NULL, tmp_msginfo);
10361 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
10362 tmp_msginfo->folder = orig_msginfo->folder;
10363 tmp_msginfo->msgnum = orig_msginfo->msgnum;
10364 if (orig_msginfo->tags)
10365 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
10370 if (!opening_multiple)
10371 body = messageview_get_selection(msgview);
10374 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
10375 procmsg_msginfo_free(tmp_msginfo);
10376 g_slist_free(new_msglist);
10378 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
10380 if (compose && originally_enc) {
10381 compose_force_encryption(compose, compose->account, FALSE);
10387 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
10390 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
10391 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
10392 GSList *cur = msginfo_list;
10393 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
10394 "messages. Opening the windows "
10395 "could take some time. Do you "
10396 "want to continue?"),
10397 g_slist_length(msginfo_list));
10398 if (g_slist_length(msginfo_list) > 9
10399 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
10400 != G_ALERTALTERNATE) {
10405 /* We'll open multiple compose windows */
10406 /* let the WM place the next windows */
10407 compose_force_window_origin = FALSE;
10408 for (; cur; cur = cur->next) {
10410 tmplist.data = cur->data;
10411 tmplist.next = NULL;
10412 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
10414 compose_force_window_origin = TRUE;
10416 /* forwarding multiple mails as attachments is done via a
10417 * single compose window */
10418 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
10422 void compose_set_position(Compose *compose, gint pos)
10424 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10426 gtkut_text_view_set_position(text, pos);
10429 gboolean compose_search_string(Compose *compose,
10430 const gchar *str, gboolean case_sens)
10432 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10434 return gtkut_text_view_search_string(text, str, case_sens);
10437 gboolean compose_search_string_backward(Compose *compose,
10438 const gchar *str, gboolean case_sens)
10440 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10442 return gtkut_text_view_search_string_backward(text, str, case_sens);
10445 /* allocate a msginfo structure and populate its data from a compose data structure */
10446 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
10448 MsgInfo *newmsginfo;
10450 gchar buf[BUFFSIZE];
10452 g_return_val_if_fail( compose != NULL, NULL );
10454 newmsginfo = procmsg_msginfo_new();
10457 get_rfc822_date(buf, sizeof(buf));
10458 newmsginfo->date = g_strdup(buf);
10461 if (compose->from_name) {
10462 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
10463 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
10467 if (compose->subject_entry)
10468 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
10470 /* to, cc, reply-to, newsgroups */
10471 for (list = compose->header_list; list; list = list->next) {
10472 gchar *header = gtk_editable_get_chars(
10474 GTK_BIN(((ComposeHeaderEntry *)list->data)->combo)->child), 0, -1);
10475 gchar *entry = gtk_editable_get_chars(
10476 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
10478 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
10479 if ( newmsginfo->to == NULL ) {
10480 newmsginfo->to = g_strdup(entry);
10481 } else if (entry && *entry) {
10482 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
10483 g_free(newmsginfo->to);
10484 newmsginfo->to = tmp;
10487 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
10488 if ( newmsginfo->cc == NULL ) {
10489 newmsginfo->cc = g_strdup(entry);
10490 } else if (entry && *entry) {
10491 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
10492 g_free(newmsginfo->cc);
10493 newmsginfo->cc = tmp;
10496 if ( strcasecmp(header,
10497 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
10498 if ( newmsginfo->newsgroups == NULL ) {
10499 newmsginfo->newsgroups = g_strdup(entry);
10500 } else if (entry && *entry) {
10501 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
10502 g_free(newmsginfo->newsgroups);
10503 newmsginfo->newsgroups = tmp;
10511 /* other data is unset */
10517 /* update compose's dictionaries from folder dict settings */
10518 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
10519 FolderItem *folder_item)
10521 g_return_if_fail(compose != NULL);
10523 if (compose->gtkaspell && folder_item && folder_item->prefs) {
10524 FolderItemPrefs *prefs = folder_item->prefs;
10526 if (prefs->enable_default_dictionary)
10527 gtkaspell_change_dict(compose->gtkaspell,
10528 prefs->default_dictionary, FALSE);
10529 if (folder_item->prefs->enable_default_alt_dictionary)
10530 gtkaspell_change_alt_dict(compose->gtkaspell,
10531 prefs->default_alt_dictionary);
10532 if (prefs->enable_default_dictionary
10533 || prefs->enable_default_alt_dictionary)
10534 compose_spell_menu_changed(compose);