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"
138 #define N_ATTACH_COLS (N_COL_COLUMNS)
142 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE,
143 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER,
144 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER,
145 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD,
146 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD,
147 COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE,
148 COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE,
149 COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE,
150 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER,
151 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER,
152 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD,
153 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD,
154 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE,
155 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE_N,
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 entire line"),
662 compose_advanced_action_cb,
663 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE_N,
665 {N_("/_Edit/A_dvanced/Delete to end of line"),
667 compose_advanced_action_cb,
668 COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END,
670 {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
672 "<control>F", compose_find_cb, 0, NULL},
673 {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
674 {N_("/_Edit/_Wrap current paragraph"),
675 "<control>L", compose_wrap_cb, 0, NULL},
676 {N_("/_Edit/Wrap all long _lines"),
677 "<control><alt>L", compose_wrap_cb, 1, NULL},
678 {N_("/_Edit/Aut_o wrapping"), "<shift><control>L", compose_toggle_autowrap_cb, 0, "<ToggleItem>"},
679 {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
680 {N_("/_Edit/Edit with e_xternal editor"),
681 "<shift><control>X", compose_ext_editor_cb, 0, NULL},
683 {N_("/_Spelling"), NULL, NULL, 0, "<Branch>"},
684 {N_("/_Spelling/_Check all or check selection"),
685 NULL, compose_check_all, 0, NULL},
686 {N_("/_Spelling/_Highlight all misspelled words"),
687 NULL, compose_highlight_all, 0, NULL},
688 {N_("/_Spelling/Check _backwards misspelled word"),
689 NULL, compose_check_backwards , 0, NULL},
690 {N_("/_Spelling/_Forward to next misspelled word"),
691 NULL, compose_check_forwards_go, 0, NULL},
692 {N_("/_Spelling/---"), NULL, NULL, 0, "<Separator>"},
693 {N_("/_Spelling/Options"),
694 NULL, NULL, 0, "<Branch>"},
696 {N_("/_Options"), NULL, NULL, 0, "<Branch>"},
697 {N_("/_Options/Reply _mode"), NULL, NULL, 0, "<Branch>"},
698 {N_("/_Options/Reply _mode/_Normal"), NULL, compose_reply_change_mode, COMPOSE_REPLY, "<RadioItem>"},
699 {N_("/_Options/Reply _mode/_All"), NULL, compose_reply_change_mode, COMPOSE_REPLY_TO_ALL, "/Options/Reply mode/Normal"},
700 {N_("/_Options/Reply _mode/_Sender"), NULL, compose_reply_change_mode, COMPOSE_REPLY_TO_SENDER, "/Options/Reply mode/Normal"},
701 {N_("/_Options/Reply _mode/_Mailing-list"), NULL, compose_reply_change_mode, COMPOSE_REPLY_TO_LIST, "/Options/Reply mode/Normal"},
702 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
703 {N_("/_Options/Privacy _System"), NULL, NULL, 0, "<Branch>"},
704 {N_("/_Options/Privacy _System/None"), NULL, NULL, 0, "<RadioItem>"},
705 {N_("/_Options/Si_gn"), NULL, compose_toggle_sign_cb , 0, "<ToggleItem>"},
706 {N_("/_Options/_Encrypt"), NULL, compose_toggle_encrypt_cb, 0, "<ToggleItem>"},
707 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
708 {N_("/_Options/_Priority"), NULL, NULL, 0, "<Branch>"},
709 {N_("/_Options/Priority/_Highest"), NULL, compose_set_priority_cb, PRIORITY_HIGHEST, "<RadioItem>"},
710 {N_("/_Options/Priority/Hi_gh"), NULL, compose_set_priority_cb, PRIORITY_HIGH, "/Options/Priority/Highest"},
711 {N_("/_Options/Priority/_Normal"), NULL, compose_set_priority_cb, PRIORITY_NORMAL, "/Options/Priority/Highest"},
712 {N_("/_Options/Priority/Lo_w"), NULL, compose_set_priority_cb, PRIORITY_LOW, "/Options/Priority/Highest"},
713 {N_("/_Options/Priority/_Lowest"), NULL, compose_set_priority_cb, PRIORITY_LOWEST, "/Options/Priority/Highest"},
714 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
715 {N_("/_Options/_Request Return Receipt"), NULL, compose_toggle_return_receipt_cb, 0, "<ToggleItem>"},
716 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
717 {N_("/_Options/Remo_ve references"), NULL, compose_toggle_remove_refs_cb, 0, "<ToggleItem>"},
718 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
720 #define ENC_ACTION(action) \
721 NULL, compose_set_encoding_cb, action, \
722 "/Options/Character encoding/Automatic"
724 {N_("/_Options/Character _encoding"), NULL, NULL, 0, "<Branch>"},
725 {N_("/_Options/Character _encoding/_Automatic"),
726 NULL, compose_set_encoding_cb, C_AUTO, "<RadioItem>"},
727 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
729 {N_("/_Options/Character _encoding/7bit ASCII (US-ASC_II)"),
730 ENC_ACTION(C_US_ASCII)},
731 {N_("/_Options/Character _encoding/Unicode (_UTF-8)"),
732 ENC_ACTION(C_UTF_8)},
733 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
735 {N_("/_Options/Character _encoding/Western European"), NULL, NULL, 0, "<Branch>"},
736 {N_("/_Options/Character _encoding/Western European/ISO-8859-_1"),
737 ENC_ACTION(C_ISO_8859_1)},
738 {N_("/_Options/Character _encoding/Western European/ISO-8859-15"),
739 ENC_ACTION(C_ISO_8859_15)},
740 {N_("/_Options/Character _encoding/Western European/Windows-1252"),
741 ENC_ACTION(C_WINDOWS_1252)},
743 {N_("/_Options/Character _encoding/Central European (ISO-8859-_2)"),
744 ENC_ACTION(C_ISO_8859_2)},
746 {N_("/_Options/Character _encoding/Baltic"), NULL, NULL, 0, "<Branch>"},
747 {N_("/_Options/Character _encoding/Baltic/ISO-8859-13"),
748 ENC_ACTION(C_ISO_8859_13)},
749 {N_("/_Options/Character _encoding/Baltic/ISO-8859-_4"),
750 ENC_ACTION(C_ISO_8859_4)},
752 {N_("/_Options/Character _encoding/Greek (ISO-8859-_7)"),
753 ENC_ACTION(C_ISO_8859_7)},
755 {N_("/_Options/Character _encoding/Hebrew"), NULL, NULL, 0, "<Branch>"},
756 {N_("/_Options/Character _encoding/Hebrew/ISO-8859-_8"),
757 ENC_ACTION(C_ISO_8859_8)},
758 {N_("/_Options/Character _encoding/Hebrew/Windows-1255"),
759 ENC_ACTION(C_WINDOWS_1255)},
761 {N_("/_Options/Character _encoding/Arabic"), NULL, NULL, 0, "<Branch>"},
762 {N_("/_Options/Character _encoding/Arabic/ISO-8859-_6"),
763 ENC_ACTION(C_ISO_8859_6)},
764 {N_("/_Options/Character _encoding/Arabic/Windows-1256"),
765 ENC_ACTION(C_CP1256)},
767 {N_("/_Options/Character _encoding/Turkish (ISO-8859-_9)"),
768 ENC_ACTION(C_ISO_8859_9)},
770 {N_("/_Options/Character _encoding/Cyrillic"), NULL, NULL, 0, "<Branch>"},
771 {N_("/_Options/Character _encoding/Cyrillic/ISO-8859-_5"),
772 ENC_ACTION(C_ISO_8859_5)},
773 {N_("/_Options/Character _encoding/Cyrillic/KOI8-_R"),
774 ENC_ACTION(C_KOI8_R)},
775 {N_("/_Options/Character _encoding/Cyrillic/KOI8-U"),
776 ENC_ACTION(C_KOI8_U)},
777 {N_("/_Options/Character _encoding/Cyrillic/Windows-1251"),
778 ENC_ACTION(C_WINDOWS_1251)},
780 {N_("/_Options/Character _encoding/Japanese"), NULL, NULL, 0, "<Branch>"},
781 {N_("/_Options/Character _encoding/Japanese/ISO-2022-_JP"),
782 ENC_ACTION(C_ISO_2022_JP)},
783 {N_("/_Options/Character _encoding/Japanese/ISO-2022-JP-2"),
784 ENC_ACTION(C_ISO_2022_JP_2)},
785 {N_("/_Options/Character _encoding/Japanese/_EUC-JP"),
786 ENC_ACTION(C_EUC_JP)},
787 {N_("/_Options/Character _encoding/Japanese/_Shift__JIS"),
788 ENC_ACTION(C_SHIFT_JIS)},
790 {N_("/_Options/Character _encoding/Chinese"), NULL, NULL, 0, "<Branch>"},
791 {N_("/_Options/Character _encoding/Chinese/Simplified (_GB2312)"),
792 ENC_ACTION(C_GB2312)},
793 {N_("/_Options/Character _encoding/Chinese/Simplified (GBK)"),
795 {N_("/_Options/Character _encoding/Chinese/Traditional (_Big5)"),
797 {N_("/_Options/Character _encoding/Chinese/Traditional (EUC-_TW)"),
798 ENC_ACTION(C_EUC_TW)},
800 {N_("/_Options/Character _encoding/Korean"), NULL, NULL, 0, "<Branch>"},
801 {N_("/_Options/Character _encoding/Korean/EUC-_KR"),
802 ENC_ACTION(C_EUC_KR)},
803 {N_("/_Options/Character _encoding/Korean/ISO-2022-KR"),
804 ENC_ACTION(C_ISO_2022_KR)},
806 {N_("/_Options/Character _encoding/Thai"), NULL, NULL, 0, "<Branch>"},
807 {N_("/_Options/Character _encoding/Thai/TIS-620"),
808 ENC_ACTION(C_TIS_620)},
809 {N_("/_Options/Character _encoding/Thai/Windows-874"),
810 ENC_ACTION(C_WINDOWS_874)},
812 {N_("/_Tools"), NULL, NULL, 0, "<Branch>"},
813 {N_("/_Tools/Show _ruler"), NULL, compose_toggle_ruler_cb, 0, "<ToggleItem>"},
814 {N_("/_Tools/_Address book"), "<shift><control>A", compose_address_cb , 0, NULL},
815 {N_("/_Tools/_Template"), NULL, NULL, 0, "<Branch>"},
816 {N_("/_Tools/Actio_ns"), NULL, NULL, 0, "<Branch>"},
817 {N_("/_Help"), NULL, NULL, 0, "<Branch>"},
818 {N_("/_Help/_About"), NULL, about_show, 0, NULL}
821 static GtkTargetEntry compose_mime_types[] =
823 {"text/uri-list", 0, 0},
824 {"UTF8_STRING", 0, 0},
828 static gboolean compose_put_existing_to_front(MsgInfo *info)
830 GList *compose_list = compose_get_compose_list();
834 for (elem = compose_list; elem != NULL && elem->data != NULL;
836 Compose *c = (Compose*)elem->data;
838 if (!c->targetinfo || !c->targetinfo->msgid ||
842 if (!strcmp(c->targetinfo->msgid, info->msgid)) {
843 gtkut_window_popup(c->window);
851 static GdkColor quote_color1 =
852 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
853 static GdkColor quote_color2 =
854 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
855 static GdkColor quote_color3 =
856 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
858 static GdkColor quote_bgcolor1 =
859 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
860 static GdkColor quote_bgcolor2 =
861 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
862 static GdkColor quote_bgcolor3 =
863 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
865 static GdkColor signature_color = {
872 static GdkColor uri_color = {
879 static void compose_create_tags(GtkTextView *text, Compose *compose)
881 GtkTextBuffer *buffer;
882 GdkColor black = {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
888 buffer = gtk_text_view_get_buffer(text);
890 if (prefs_common.enable_color) {
891 /* grab the quote colors, converting from an int to a GdkColor */
892 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
894 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
896 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
898 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_bgcol,
900 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_bgcol,
902 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_bgcol,
904 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
906 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
909 signature_color = quote_color1 = quote_color2 = quote_color3 =
910 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
913 if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
914 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
915 "foreground-gdk", "e_color1,
916 "paragraph-background-gdk", "e_bgcolor1,
918 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
919 "foreground-gdk", "e_color2,
920 "paragraph-background-gdk", "e_bgcolor2,
922 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
923 "foreground-gdk", "e_color3,
924 "paragraph-background-gdk", "e_bgcolor3,
927 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
928 "foreground-gdk", "e_color1,
930 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
931 "foreground-gdk", "e_color2,
933 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
934 "foreground-gdk", "e_color3,
938 compose->signature_tag = gtk_text_buffer_create_tag(buffer, "signature",
939 "foreground-gdk", &signature_color,
942 compose->uri_tag = gtk_text_buffer_create_tag(buffer, "link",
943 "foreground-gdk", &uri_color,
945 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
946 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
948 color[0] = quote_color1;
949 color[1] = quote_color2;
950 color[2] = quote_color3;
951 color[3] = quote_bgcolor1;
952 color[4] = quote_bgcolor2;
953 color[5] = quote_bgcolor3;
954 color[6] = signature_color;
955 color[7] = uri_color;
956 cmap = gdk_drawable_get_colormap(compose->window->window);
957 gdk_colormap_alloc_colors(cmap, color, 8, FALSE, TRUE, success);
959 for (i = 0; i < 8; i++) {
960 if (success[i] == FALSE) {
963 g_warning("Compose: color allocation failed.\n");
964 style = gtk_widget_get_style(GTK_WIDGET(text));
965 quote_color1 = quote_color2 = quote_color3 =
966 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 =
967 signature_color = uri_color = black;
972 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
973 GPtrArray *attach_files)
975 return compose_generic_new(account, mailto, NULL, attach_files, NULL);
978 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
980 return compose_generic_new(account, mailto, item, NULL, NULL);
983 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
985 return compose_generic_new( account, NULL, NULL, NULL, listAddress );
988 #define SCROLL_TO_CURSOR(compose) { \
989 GtkTextMark *cmark = gtk_text_buffer_get_insert( \
990 gtk_text_view_get_buffer( \
991 GTK_TEXT_VIEW(compose->text))); \
992 gtk_text_view_scroll_mark_onscreen( \
993 GTK_TEXT_VIEW(compose->text), \
997 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
998 GPtrArray *attach_files, GList *listAddress )
1001 GtkTextView *textview;
1002 GtkTextBuffer *textbuf;
1004 GtkItemFactory *ifactory;
1005 const gchar *subject_format = NULL;
1006 const gchar *body_format = NULL;
1007 gchar *mailto_from = NULL;
1008 PrefsAccount *mailto_account = NULL;
1009 MsgInfo* dummyinfo = NULL;
1011 /* check if mailto defines a from */
1012 if (mailto && *mailto != '\0') {
1013 scan_mailto_url(mailto, &mailto_from, NULL, NULL, NULL, NULL, NULL, NULL);
1014 /* mailto defines a from, check if we can get account prefs from it,
1015 if not, the account prefs will be guessed using other ways, but we'll keep
1018 mailto_account = account_find_from_address(mailto_from, TRUE);
1020 account = mailto_account;
1023 /* if no account prefs set from mailto, set if from folder prefs (if any) */
1024 if (!mailto_account && item && item->prefs && item->prefs->enable_default_account)
1025 account = account_find_from_id(item->prefs->default_account);
1027 /* if no account prefs set, fallback to the current one */
1028 if (!account) account = cur_account;
1029 g_return_val_if_fail(account != NULL, NULL);
1031 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
1033 /* override from name if mailto asked for it */
1035 gtk_entry_set_text(GTK_ENTRY(compose->from_name), mailto_from);
1036 g_free(mailto_from);
1038 /* override from name according to folder properties */
1039 if (item && item->prefs &&
1040 item->prefs->compose_with_format &&
1041 item->prefs->compose_override_from_format &&
1042 *item->prefs->compose_override_from_format != '\0') {
1047 dummyinfo = compose_msginfo_new_from_compose(compose);
1049 /* decode \-escape sequences in the internal representation of the quote format */
1050 tmp = malloc(strlen(item->prefs->compose_override_from_format)+1);
1051 pref_get_unescaped_pref(tmp, item->prefs->compose_override_from_format);
1054 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1055 compose->gtkaspell);
1057 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1059 quote_fmt_scan_string(tmp);
1062 buf = quote_fmt_get_buffer();
1064 alertpanel_error(_("New message From format error."));
1066 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1067 quote_fmt_reset_vartable();
1072 ifactory = gtk_item_factory_from_widget(compose->menubar);
1074 compose->replyinfo = NULL;
1075 compose->fwdinfo = NULL;
1077 textview = GTK_TEXT_VIEW(compose->text);
1078 textbuf = gtk_text_view_get_buffer(textview);
1079 compose_create_tags(textview, compose);
1081 undo_block(compose->undostruct);
1083 compose_set_dictionaries_from_folder_prefs(compose, item);
1086 if (account->auto_sig)
1087 compose_insert_sig(compose, FALSE);
1088 gtk_text_buffer_get_start_iter(textbuf, &iter);
1089 gtk_text_buffer_place_cursor(textbuf, &iter);
1091 if (account->protocol != A_NNTP) {
1092 if (mailto && *mailto != '\0') {
1093 compose_entries_set(compose, mailto, COMPOSE_TO);
1095 } else if (item && item->prefs->enable_default_to) {
1096 compose_entry_append(compose, item->prefs->default_to, COMPOSE_TO);
1097 compose_entry_mark_default_to(compose, item->prefs->default_to);
1099 if (item && item->ret_rcpt) {
1100 menu_set_active(ifactory, "/Options/Request Return Receipt", TRUE);
1103 if (mailto && *mailto != '\0') {
1104 if (!strchr(mailto, '@'))
1105 compose_entries_set(compose, mailto, COMPOSE_NEWSGROUPS);
1107 compose_entries_set(compose, mailto, COMPOSE_TO);
1108 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1109 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS);
1112 * CLAWS: just don't allow return receipt request, even if the user
1113 * may want to send an email. simple but foolproof.
1115 menu_set_sensitive(ifactory, "/Options/Request Return Receipt", FALSE);
1117 compose_add_field_list( compose, listAddress );
1119 if (item && item->prefs && item->prefs->compose_with_format) {
1120 subject_format = item->prefs->compose_subject_format;
1121 body_format = item->prefs->compose_body_format;
1122 } else if (account->compose_with_format) {
1123 subject_format = account->compose_subject_format;
1124 body_format = account->compose_body_format;
1125 } else if (prefs_common.compose_with_format) {
1126 subject_format = prefs_common.compose_subject_format;
1127 body_format = prefs_common.compose_body_format;
1130 if (subject_format || body_format) {
1133 && *subject_format != '\0' )
1135 gchar *subject = NULL;
1140 dummyinfo = compose_msginfo_new_from_compose(compose);
1142 /* decode \-escape sequences in the internal representation of the quote format */
1143 tmp = malloc(strlen(subject_format)+1);
1144 pref_get_unescaped_pref(tmp, subject_format);
1146 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1148 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE,
1149 compose->gtkaspell);
1151 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE);
1153 quote_fmt_scan_string(tmp);
1156 buf = quote_fmt_get_buffer();
1158 alertpanel_error(_("New message subject format error."));
1160 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1161 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1162 quote_fmt_reset_vartable();
1169 && *body_format != '\0' )
1172 GtkTextBuffer *buffer;
1173 GtkTextIter start, end;
1177 dummyinfo = compose_msginfo_new_from_compose(compose);
1179 text = GTK_TEXT_VIEW(compose->text);
1180 buffer = gtk_text_view_get_buffer(text);
1181 gtk_text_buffer_get_start_iter(buffer, &start);
1182 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1183 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1185 compose_quote_fmt(compose, dummyinfo,
1187 NULL, tmp, FALSE, TRUE,
1188 _("New message body format error at line %d."));
1189 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1190 quote_fmt_reset_vartable();
1196 procmsg_msginfo_free( dummyinfo );
1202 for (i = 0; i < attach_files->len; i++) {
1203 file = g_ptr_array_index(attach_files, i);
1204 compose_attach_append(compose, file, file, NULL);
1208 compose_show_first_last_header(compose, TRUE);
1210 /* Set save folder */
1211 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1212 gchar *folderidentifier;
1214 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
1215 folderidentifier = folder_item_get_identifier(item);
1216 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
1217 g_free(folderidentifier);
1220 gtk_widget_grab_focus(compose->header_last->entry);
1222 undo_unblock(compose->undostruct);
1224 if (prefs_common.auto_exteditor)
1225 compose_exec_ext_editor(compose);
1227 compose->draft_timeout_tag = -1;
1228 SCROLL_TO_CURSOR(compose);
1230 compose->modified = FALSE;
1231 compose_set_title(compose);
1235 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1236 gboolean override_pref)
1238 gchar *privacy = NULL;
1240 g_return_if_fail(compose != NULL);
1241 g_return_if_fail(account != NULL);
1243 if (override_pref == FALSE && account->default_encrypt_reply == FALSE)
1246 if (account->default_privacy_system
1247 && strlen(account->default_privacy_system)) {
1248 privacy = account->default_privacy_system;
1250 GSList *privacy_avail = privacy_get_system_ids();
1251 if (privacy_avail && g_slist_length(privacy_avail)) {
1252 privacy = (gchar *)(privacy_avail->data);
1255 if (privacy != NULL) {
1256 if (compose->privacy_system == NULL)
1257 compose->privacy_system = g_strdup(privacy);
1258 else if (*(compose->privacy_system) == '\0') {
1259 g_free(compose->privacy_system);
1260 compose->privacy_system = g_strdup(privacy);
1262 compose_update_privacy_system_menu_item(compose, FALSE);
1263 compose_use_encryption(compose, TRUE);
1267 static void compose_force_signing(Compose *compose, PrefsAccount *account)
1269 gchar *privacy = NULL;
1271 if (account->default_privacy_system
1272 && strlen(account->default_privacy_system)) {
1273 privacy = account->default_privacy_system;
1275 GSList *privacy_avail = privacy_get_system_ids();
1276 if (privacy_avail && g_slist_length(privacy_avail)) {
1277 privacy = (gchar *)(privacy_avail->data);
1280 if (privacy != NULL) {
1281 if (compose->privacy_system == NULL)
1282 compose->privacy_system = g_strdup(privacy);
1283 compose_update_privacy_system_menu_item(compose, FALSE);
1284 compose_use_signing(compose, TRUE);
1288 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1292 Compose *compose = NULL;
1293 GtkItemFactory *ifactory = NULL;
1295 g_return_val_if_fail(msginfo_list != NULL, NULL);
1297 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1298 g_return_val_if_fail(msginfo != NULL, NULL);
1300 list_len = g_slist_length(msginfo_list);
1304 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1305 FALSE, prefs_common.default_reply_list, FALSE, body);
1307 case COMPOSE_REPLY_WITH_QUOTE:
1308 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1309 FALSE, prefs_common.default_reply_list, FALSE, body);
1311 case COMPOSE_REPLY_WITHOUT_QUOTE:
1312 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1313 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1315 case COMPOSE_REPLY_TO_SENDER:
1316 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1317 FALSE, FALSE, TRUE, body);
1319 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1320 compose = compose_followup_and_reply_to(msginfo,
1321 COMPOSE_QUOTE_CHECK,
1322 FALSE, FALSE, body);
1324 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1325 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1326 FALSE, FALSE, TRUE, body);
1328 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1329 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1330 FALSE, FALSE, TRUE, NULL);
1332 case COMPOSE_REPLY_TO_ALL:
1333 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1334 TRUE, FALSE, FALSE, body);
1336 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1337 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1338 TRUE, FALSE, FALSE, body);
1340 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1341 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1342 TRUE, FALSE, FALSE, NULL);
1344 case COMPOSE_REPLY_TO_LIST:
1345 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1346 FALSE, TRUE, FALSE, body);
1348 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1349 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1350 FALSE, TRUE, FALSE, body);
1352 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1353 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1354 FALSE, TRUE, FALSE, NULL);
1356 case COMPOSE_FORWARD:
1357 if (prefs_common.forward_as_attachment) {
1358 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1361 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1365 case COMPOSE_FORWARD_INLINE:
1366 /* check if we reply to more than one Message */
1367 if (list_len == 1) {
1368 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1371 /* more messages FALL THROUGH */
1372 case COMPOSE_FORWARD_AS_ATTACH:
1373 compose = compose_forward_multiple(NULL, msginfo_list);
1375 case COMPOSE_REDIRECT:
1376 compose = compose_redirect(NULL, msginfo, FALSE);
1379 g_warning("compose_reply_mode(): invalid Compose Mode: %d\n", mode);
1382 if (compose == NULL) {
1383 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1386 ifactory = gtk_item_factory_from_widget(compose->menubar);
1388 compose->rmode = mode;
1389 switch (compose->rmode) {
1391 case COMPOSE_REPLY_WITH_QUOTE:
1392 case COMPOSE_REPLY_WITHOUT_QUOTE:
1393 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1394 debug_print("reply mode Normal\n");
1395 menu_set_active(ifactory, "/Options/Reply mode/Normal", TRUE);
1396 compose_reply_change_mode(compose, COMPOSE_REPLY, NULL); /* force update */
1398 case COMPOSE_REPLY_TO_SENDER:
1399 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1400 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1401 debug_print("reply mode Sender\n");
1402 menu_set_active(ifactory, "/Options/Reply mode/Sender", TRUE);
1404 case COMPOSE_REPLY_TO_ALL:
1405 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1406 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1407 debug_print("reply mode All\n");
1408 menu_set_active(ifactory, "/Options/Reply mode/All", TRUE);
1410 case COMPOSE_REPLY_TO_LIST:
1411 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1412 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1413 debug_print("reply mode List\n");
1414 menu_set_active(ifactory, "/Options/Reply mode/Mailing-list", TRUE);
1422 static Compose *compose_reply(MsgInfo *msginfo,
1423 ComposeQuoteMode quote_mode,
1429 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1430 to_sender, FALSE, body);
1433 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1434 ComposeQuoteMode quote_mode,
1439 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1440 to_sender, TRUE, body);
1443 static void compose_extract_original_charset(Compose *compose)
1445 MsgInfo *info = NULL;
1446 if (compose->replyinfo) {
1447 info = compose->replyinfo;
1448 } else if (compose->fwdinfo) {
1449 info = compose->fwdinfo;
1450 } else if (compose->targetinfo) {
1451 info = compose->targetinfo;
1454 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1455 MimeInfo *partinfo = mimeinfo;
1456 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1457 partinfo = procmime_mimeinfo_next(partinfo);
1459 compose->orig_charset =
1460 g_strdup(procmime_mimeinfo_get_parameter(
1461 partinfo, "charset"));
1463 procmime_mimeinfo_free_all(mimeinfo);
1467 #define SIGNAL_BLOCK(buffer) { \
1468 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1469 G_CALLBACK(compose_changed_cb), \
1471 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1472 G_CALLBACK(text_inserted), \
1476 #define SIGNAL_UNBLOCK(buffer) { \
1477 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1478 G_CALLBACK(compose_changed_cb), \
1480 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1481 G_CALLBACK(text_inserted), \
1485 static Compose *compose_generic_reply(MsgInfo *msginfo,
1486 ComposeQuoteMode quote_mode,
1487 gboolean to_all, gboolean to_ml,
1489 gboolean followup_and_reply_to,
1492 GtkItemFactory *ifactory;
1494 PrefsAccount *account = NULL;
1495 GtkTextView *textview;
1496 GtkTextBuffer *textbuf;
1497 gboolean quote = FALSE;
1498 const gchar *qmark = NULL;
1499 const gchar *body_fmt = NULL;
1501 g_return_val_if_fail(msginfo != NULL, NULL);
1502 g_return_val_if_fail(msginfo->folder != NULL, NULL);
1504 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1506 g_return_val_if_fail(account != NULL, NULL);
1508 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1510 compose->updating = TRUE;
1512 ifactory = gtk_item_factory_from_widget(compose->menubar);
1514 menu_set_active(ifactory, "/Options/Remove references", FALSE);
1515 menu_set_sensitive(ifactory, "/Options/Remove references", TRUE);
1517 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1518 if (!compose->replyinfo)
1519 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1521 compose_extract_original_charset(compose);
1523 if (msginfo->folder && msginfo->folder->ret_rcpt)
1524 menu_set_active(ifactory, "/Options/Request Return Receipt", TRUE);
1526 /* Set save folder */
1527 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1528 gchar *folderidentifier;
1530 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1531 folderidentifier = folder_item_get_identifier(msginfo->folder);
1532 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
1533 g_free(folderidentifier);
1536 if (compose_parse_header(compose, msginfo) < 0) {
1537 compose->updating = FALSE;
1538 compose_destroy(compose);
1542 /* override from name according to folder properties */
1543 if (msginfo->folder && msginfo->folder->prefs &&
1544 msginfo->folder->prefs->reply_with_format &&
1545 msginfo->folder->prefs->reply_override_from_format &&
1546 *msginfo->folder->prefs->reply_override_from_format != '\0') {
1551 /* decode \-escape sequences in the internal representation of the quote format */
1552 tmp = malloc(strlen(msginfo->folder->prefs->reply_override_from_format)+1);
1553 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->reply_override_from_format);
1556 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1557 compose->gtkaspell);
1559 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1561 quote_fmt_scan_string(tmp);
1564 buf = quote_fmt_get_buffer();
1566 alertpanel_error(_("Message reply From format error."));
1568 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1569 quote_fmt_reset_vartable();
1574 textview = (GTK_TEXT_VIEW(compose->text));
1575 textbuf = gtk_text_view_get_buffer(textview);
1576 compose_create_tags(textview, compose);
1578 undo_block(compose->undostruct);
1580 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1583 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1584 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1585 /* use the reply format of folder (if enabled), or the account's one
1586 (if enabled) or fallback to the global reply format, which is always
1587 enabled (even if empty), and use the relevant quotemark */
1589 if (msginfo->folder && msginfo->folder->prefs &&
1590 msginfo->folder->prefs->reply_with_format) {
1591 qmark = msginfo->folder->prefs->reply_quotemark;
1592 body_fmt = msginfo->folder->prefs->reply_body_format;
1594 } else if (account->reply_with_format) {
1595 qmark = account->reply_quotemark;
1596 body_fmt = account->reply_body_format;
1599 qmark = prefs_common.quotemark;
1600 body_fmt = prefs_common.quotefmt;
1605 /* empty quotemark is not allowed */
1606 if (qmark == NULL || *qmark == '\0')
1608 compose_quote_fmt(compose, compose->replyinfo,
1609 body_fmt, qmark, body, FALSE, TRUE,
1610 _("Message reply format error at line %d."));
1611 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1612 quote_fmt_reset_vartable();
1615 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1616 compose_force_encryption(compose, account, FALSE);
1619 SIGNAL_BLOCK(textbuf);
1621 if (account->auto_sig)
1622 compose_insert_sig(compose, FALSE);
1624 compose_wrap_all(compose);
1626 SIGNAL_UNBLOCK(textbuf);
1628 gtk_widget_grab_focus(compose->text);
1630 undo_unblock(compose->undostruct);
1632 if (prefs_common.auto_exteditor)
1633 compose_exec_ext_editor(compose);
1635 compose->modified = FALSE;
1636 compose_set_title(compose);
1638 compose->updating = FALSE;
1639 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1640 SCROLL_TO_CURSOR(compose);
1642 if (compose->deferred_destroy) {
1643 compose_destroy(compose);
1650 #define INSERT_FW_HEADER(var, hdr) \
1651 if (msginfo->var && *msginfo->var) { \
1652 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1653 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1654 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1657 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1658 gboolean as_attach, const gchar *body,
1659 gboolean no_extedit,
1663 GtkTextView *textview;
1664 GtkTextBuffer *textbuf;
1667 g_return_val_if_fail(msginfo != NULL, NULL);
1668 g_return_val_if_fail(msginfo->folder != NULL, NULL);
1671 !(account = compose_guess_forward_account_from_msginfo
1673 account = cur_account;
1675 compose = compose_create(account, msginfo->folder, COMPOSE_FORWARD, batch);
1677 compose->updating = TRUE;
1678 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1679 if (!compose->fwdinfo)
1680 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1682 compose_extract_original_charset(compose);
1684 if (msginfo->subject && *msginfo->subject) {
1685 gchar *buf, *buf2, *p;
1687 buf = p = g_strdup(msginfo->subject);
1688 p += subject_get_prefix_length(p);
1689 memmove(buf, p, strlen(p) + 1);
1691 buf2 = g_strdup_printf("Fw: %s", buf);
1692 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1698 /* override from name according to folder properties */
1699 if (msginfo->folder && msginfo->folder->prefs &&
1700 msginfo->folder->prefs->forward_with_format &&
1701 msginfo->folder->prefs->forward_override_from_format &&
1702 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1706 MsgInfo *full_msginfo = NULL;
1709 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1711 full_msginfo = procmsg_msginfo_copy(msginfo);
1713 /* decode \-escape sequences in the internal representation of the quote format */
1714 tmp = malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1715 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1718 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1719 compose->gtkaspell);
1721 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1723 quote_fmt_scan_string(tmp);
1726 buf = quote_fmt_get_buffer();
1728 alertpanel_error(_("Message forward From format error."));
1730 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1731 quote_fmt_reset_vartable();
1734 procmsg_msginfo_free(full_msginfo);
1737 textview = GTK_TEXT_VIEW(compose->text);
1738 textbuf = gtk_text_view_get_buffer(textview);
1739 compose_create_tags(textview, compose);
1741 undo_block(compose->undostruct);
1745 msgfile = procmsg_get_message_file(msginfo);
1746 if (!is_file_exist(msgfile))
1747 g_warning("%s: file not exist\n", msgfile);
1749 compose_attach_append(compose, msgfile, msgfile,
1754 const gchar *qmark = NULL;
1755 const gchar *body_fmt = prefs_common.fw_quotefmt;
1756 MsgInfo *full_msginfo;
1758 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1760 full_msginfo = procmsg_msginfo_copy(msginfo);
1762 /* use the forward format of folder (if enabled), or the account's one
1763 (if enabled) or fallback to the global forward format, which is always
1764 enabled (even if empty), and use the relevant quotemark */
1765 if (msginfo->folder && msginfo->folder->prefs &&
1766 msginfo->folder->prefs->forward_with_format) {
1767 qmark = msginfo->folder->prefs->forward_quotemark;
1768 body_fmt = msginfo->folder->prefs->forward_body_format;
1770 } else if (account->forward_with_format) {
1771 qmark = account->forward_quotemark;
1772 body_fmt = account->forward_body_format;
1775 qmark = prefs_common.fw_quotemark;
1776 body_fmt = prefs_common.fw_quotefmt;
1779 /* empty quotemark is not allowed */
1780 if (qmark == NULL || *qmark == '\0')
1783 compose_quote_fmt(compose, full_msginfo,
1784 body_fmt, qmark, body, FALSE, TRUE,
1785 _("Message forward format error at line %d."));
1786 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1787 quote_fmt_reset_vartable();
1788 compose_attach_parts(compose, msginfo);
1790 procmsg_msginfo_free(full_msginfo);
1793 SIGNAL_BLOCK(textbuf);
1795 if (account->auto_sig)
1796 compose_insert_sig(compose, FALSE);
1798 compose_wrap_all(compose);
1800 SIGNAL_UNBLOCK(textbuf);
1802 gtk_text_buffer_get_start_iter(textbuf, &iter);
1803 gtk_text_buffer_place_cursor(textbuf, &iter);
1805 gtk_widget_grab_focus(compose->header_last->entry);
1807 if (!no_extedit && prefs_common.auto_exteditor)
1808 compose_exec_ext_editor(compose);
1811 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1812 gchar *folderidentifier;
1814 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1815 folderidentifier = folder_item_get_identifier(msginfo->folder);
1816 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
1817 g_free(folderidentifier);
1820 undo_unblock(compose->undostruct);
1822 compose->modified = FALSE;
1823 compose_set_title(compose);
1825 compose->updating = FALSE;
1826 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1827 SCROLL_TO_CURSOR(compose);
1829 if (compose->deferred_destroy) {
1830 compose_destroy(compose);
1837 #undef INSERT_FW_HEADER
1839 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1842 GtkTextView *textview;
1843 GtkTextBuffer *textbuf;
1847 gboolean single_mail = TRUE;
1849 g_return_val_if_fail(msginfo_list != NULL, NULL);
1851 if (g_slist_length(msginfo_list) > 1)
1852 single_mail = FALSE;
1854 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1855 if (((MsgInfo *)msginfo->data)->folder == NULL)
1858 /* guess account from first selected message */
1860 !(account = compose_guess_forward_account_from_msginfo
1861 (msginfo_list->data)))
1862 account = cur_account;
1864 g_return_val_if_fail(account != NULL, NULL);
1866 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1867 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1868 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1871 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1873 compose->updating = TRUE;
1875 /* override from name according to folder properties */
1876 if (msginfo_list->data) {
1877 MsgInfo *msginfo = msginfo_list->data;
1879 if (msginfo->folder && msginfo->folder->prefs &&
1880 msginfo->folder->prefs->forward_with_format &&
1881 msginfo->folder->prefs->forward_override_from_format &&
1882 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1887 /* decode \-escape sequences in the internal representation of the quote format */
1888 tmp = malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1889 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1892 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1893 compose->gtkaspell);
1895 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1897 quote_fmt_scan_string(tmp);
1900 buf = quote_fmt_get_buffer();
1902 alertpanel_error(_("Message forward From format error."));
1904 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1905 quote_fmt_reset_vartable();
1911 textview = GTK_TEXT_VIEW(compose->text);
1912 textbuf = gtk_text_view_get_buffer(textview);
1913 compose_create_tags(textview, compose);
1915 undo_block(compose->undostruct);
1916 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1917 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
1919 if (!is_file_exist(msgfile))
1920 g_warning("%s: file not exist\n", msgfile);
1922 compose_attach_append(compose, msgfile, msgfile,
1928 MsgInfo *info = (MsgInfo *)msginfo_list->data;
1929 if (info->subject && *info->subject) {
1930 gchar *buf, *buf2, *p;
1932 buf = p = g_strdup(info->subject);
1933 p += subject_get_prefix_length(p);
1934 memmove(buf, p, strlen(p) + 1);
1936 buf2 = g_strdup_printf("Fw: %s", buf);
1937 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1943 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
1944 _("Fw: multiple emails"));
1947 SIGNAL_BLOCK(textbuf);
1949 if (account->auto_sig)
1950 compose_insert_sig(compose, FALSE);
1952 compose_wrap_all(compose);
1954 SIGNAL_UNBLOCK(textbuf);
1956 gtk_text_buffer_get_start_iter(textbuf, &iter);
1957 gtk_text_buffer_place_cursor(textbuf, &iter);
1959 gtk_widget_grab_focus(compose->header_last->entry);
1960 undo_unblock(compose->undostruct);
1961 compose->modified = FALSE;
1962 compose_set_title(compose);
1964 compose->updating = FALSE;
1965 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1966 SCROLL_TO_CURSOR(compose);
1968 if (compose->deferred_destroy) {
1969 compose_destroy(compose);
1976 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
1978 GtkTextIter start = *iter;
1979 GtkTextIter end_iter;
1980 int start_pos = gtk_text_iter_get_offset(&start);
1982 if (!compose->account->sig_sep)
1985 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
1986 start_pos+strlen(compose->account->sig_sep));
1988 /* check sig separator */
1989 str = gtk_text_iter_get_text(&start, &end_iter);
1990 if (!strcmp(str, compose->account->sig_sep)) {
1992 /* check end of line (\n) */
1993 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
1994 start_pos+strlen(compose->account->sig_sep));
1995 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
1996 start_pos+strlen(compose->account->sig_sep)+1);
1997 tmp = gtk_text_iter_get_text(&start, &end_iter);
1998 if (!strcmp(tmp,"\n")) {
2010 static void compose_colorize_signature(Compose *compose)
2012 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
2014 GtkTextIter end_iter;
2015 gtk_text_buffer_get_start_iter(buffer, &iter);
2016 while (gtk_text_iter_forward_line(&iter))
2017 if (compose_is_sig_separator(compose, buffer, &iter)) {
2018 gtk_text_buffer_get_end_iter(buffer, &end_iter);
2019 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
2023 #define BLOCK_WRAP() { \
2024 prev_autowrap = compose->autowrap; \
2025 buffer = gtk_text_view_get_buffer( \
2026 GTK_TEXT_VIEW(compose->text)); \
2027 compose->autowrap = FALSE; \
2029 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2030 G_CALLBACK(compose_changed_cb), \
2032 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2033 G_CALLBACK(text_inserted), \
2036 #define UNBLOCK_WRAP() { \
2037 compose->autowrap = prev_autowrap; \
2038 if (compose->autowrap) { \
2039 gint old = compose->draft_timeout_tag; \
2040 compose->draft_timeout_tag = -2; \
2041 compose_wrap_all(compose); \
2042 compose->draft_timeout_tag = old; \
2045 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2046 G_CALLBACK(compose_changed_cb), \
2048 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2049 G_CALLBACK(text_inserted), \
2053 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
2055 Compose *compose = NULL;
2056 PrefsAccount *account = NULL;
2057 GtkTextView *textview;
2058 GtkTextBuffer *textbuf;
2062 gchar buf[BUFFSIZE];
2063 gboolean use_signing = FALSE;
2064 gboolean use_encryption = FALSE;
2065 gchar *privacy_system = NULL;
2066 int priority = PRIORITY_NORMAL;
2067 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
2069 g_return_val_if_fail(msginfo != NULL, NULL);
2070 g_return_val_if_fail(msginfo->folder != NULL, NULL);
2072 if (compose_put_existing_to_front(msginfo)) {
2076 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2077 folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
2078 gchar queueheader_buf[BUFFSIZE];
2081 /* Select Account from queue headers */
2082 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2083 sizeof(queueheader_buf), "X-Claws-Account-Id:")) {
2084 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
2085 account = account_find_from_id(id);
2087 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2088 sizeof(queueheader_buf), "X-Sylpheed-Account-Id:")) {
2089 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
2090 account = account_find_from_id(id);
2092 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2093 sizeof(queueheader_buf), "NAID:")) {
2094 id = atoi(&queueheader_buf[strlen("NAID:")]);
2095 account = account_find_from_id(id);
2097 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2098 sizeof(queueheader_buf), "MAID:")) {
2099 id = atoi(&queueheader_buf[strlen("MAID:")]);
2100 account = account_find_from_id(id);
2102 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2103 sizeof(queueheader_buf), "S:")) {
2104 account = account_find_from_address(queueheader_buf, FALSE);
2106 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2107 sizeof(queueheader_buf), "X-Claws-Sign:")) {
2108 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
2109 use_signing = param;
2112 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2113 sizeof(queueheader_buf), "X-Sylpheed-Sign:")) {
2114 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
2115 use_signing = param;
2118 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2119 sizeof(queueheader_buf), "X-Claws-Encrypt:")) {
2120 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
2121 use_encryption = param;
2123 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2124 sizeof(queueheader_buf), "X-Sylpheed-Encrypt:")) {
2125 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
2126 use_encryption = param;
2128 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2129 sizeof(queueheader_buf), "X-Claws-Privacy-System:")) {
2130 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
2132 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2133 sizeof(queueheader_buf), "X-Sylpheed-Privacy-System:")) {
2134 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
2136 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2137 sizeof(queueheader_buf), "X-Priority: ")) {
2138 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
2141 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2142 sizeof(queueheader_buf), "RMID:")) {
2143 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
2144 if (tokens[0] && tokens[1] && tokens[2]) {
2145 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2146 if (orig_item != NULL) {
2147 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2152 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2153 sizeof(queueheader_buf), "FMID:")) {
2154 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
2155 if (tokens[0] && tokens[1] && tokens[2]) {
2156 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2157 if (orig_item != NULL) {
2158 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2164 account = msginfo->folder->folder->account;
2167 if (!account && prefs_common.reedit_account_autosel) {
2168 gchar from[BUFFSIZE];
2169 if (!procheader_get_header_from_msginfo(msginfo, from, sizeof(from), "FROM:")) {
2170 extract_address(from);
2171 account = account_find_from_address(from, FALSE);
2175 account = cur_account;
2177 g_return_val_if_fail(account != NULL, NULL);
2179 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2181 compose->replyinfo = replyinfo;
2182 compose->fwdinfo = fwdinfo;
2184 compose->updating = TRUE;
2185 compose->priority = priority;
2187 if (privacy_system != NULL) {
2188 compose->privacy_system = privacy_system;
2189 compose_use_signing(compose, use_signing);
2190 compose_use_encryption(compose, use_encryption);
2191 compose_update_privacy_system_menu_item(compose, FALSE);
2193 activate_privacy_system(compose, account, FALSE);
2196 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2198 compose_extract_original_charset(compose);
2200 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2201 folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
2202 gchar queueheader_buf[BUFFSIZE];
2204 /* Set message save folder */
2205 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "SCF:")) {
2208 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2209 gtk_editable_delete_text(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
2210 gtk_editable_insert_text(GTK_EDITABLE(compose->savemsg_entry), &queueheader_buf[4], strlen(&queueheader_buf[4]), &startpos);
2212 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "RRCPT:")) {
2213 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2215 GtkItemFactory *ifactory;
2216 ifactory = gtk_item_factory_from_widget(compose->menubar);
2217 menu_set_active(ifactory, "/Options/Request Return Receipt", TRUE);
2222 if (compose_parse_header(compose, msginfo) < 0) {
2223 compose->updating = FALSE;
2224 compose_destroy(compose);
2227 compose_reedit_set_entry(compose, msginfo);
2229 textview = GTK_TEXT_VIEW(compose->text);
2230 textbuf = gtk_text_view_get_buffer(textview);
2231 compose_create_tags(textview, compose);
2233 mark = gtk_text_buffer_get_insert(textbuf);
2234 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2236 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2237 G_CALLBACK(compose_changed_cb),
2240 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2241 fp = procmime_get_first_encrypted_text_content(msginfo);
2243 compose_force_encryption(compose, account, TRUE);
2246 fp = procmime_get_first_text_content(msginfo);
2249 g_warning("Can't get text part\n");
2253 gboolean prev_autowrap = compose->autowrap;
2254 GtkTextBuffer *buffer = textbuf;
2256 while (fgets(buf, sizeof(buf), fp) != NULL) {
2258 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2264 compose_attach_parts(compose, msginfo);
2266 compose_colorize_signature(compose);
2268 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2269 G_CALLBACK(compose_changed_cb),
2272 gtk_widget_grab_focus(compose->text);
2274 if (prefs_common.auto_exteditor) {
2275 compose_exec_ext_editor(compose);
2277 compose->modified = FALSE;
2278 compose_set_title(compose);
2280 compose->updating = FALSE;
2281 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2282 SCROLL_TO_CURSOR(compose);
2284 if (compose->deferred_destroy) {
2285 compose_destroy(compose);
2289 compose->sig_str = compose_get_signature_str(compose);
2294 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2299 GtkItemFactory *ifactory;
2302 g_return_val_if_fail(msginfo != NULL, NULL);
2305 account = account_get_reply_account(msginfo,
2306 prefs_common.reply_account_autosel);
2307 g_return_val_if_fail(account != NULL, NULL);
2309 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2311 compose->updating = TRUE;
2313 ifactory = gtk_item_factory_from_widget(compose->menubar);
2314 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2315 compose->replyinfo = NULL;
2316 compose->fwdinfo = NULL;
2318 compose_show_first_last_header(compose, TRUE);
2320 gtk_widget_grab_focus(compose->header_last->entry);
2322 filename = procmsg_get_message_file(msginfo);
2324 if (filename == NULL) {
2325 compose->updating = FALSE;
2326 compose_destroy(compose);
2331 compose->redirect_filename = filename;
2333 /* Set save folder */
2334 item = msginfo->folder;
2335 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2336 gchar *folderidentifier;
2338 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
2339 folderidentifier = folder_item_get_identifier(item);
2340 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
2341 g_free(folderidentifier);
2344 compose_attach_parts(compose, msginfo);
2346 if (msginfo->subject)
2347 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2349 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2351 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2352 _("Message redirect format error at line %d."));
2353 quote_fmt_reset_vartable();
2354 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2356 compose_colorize_signature(compose);
2358 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
2359 menu_set_sensitive(ifactory, "/Add...", FALSE);
2360 menu_set_sensitive(ifactory, "/Remove", FALSE);
2361 menu_set_sensitive(ifactory, "/Properties...", FALSE);
2363 ifactory = gtk_item_factory_from_widget(compose->menubar);
2364 menu_set_sensitive(ifactory, "/Message/Save", FALSE);
2365 menu_set_sensitive(ifactory, "/Message/Insert file", FALSE);
2366 menu_set_sensitive(ifactory, "/Message/Attach file", FALSE);
2367 menu_set_sensitive(ifactory, "/Message/Insert signature", FALSE);
2368 menu_set_sensitive(ifactory, "/Edit", FALSE);
2369 menu_set_sensitive(ifactory, "/Options", FALSE);
2370 menu_set_sensitive(ifactory, "/Tools/Show ruler", FALSE);
2371 menu_set_sensitive(ifactory, "/Tools/Actions", FALSE);
2373 if (compose->toolbar->draft_btn)
2374 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2375 if (compose->toolbar->insert_btn)
2376 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2377 if (compose->toolbar->attach_btn)
2378 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2379 if (compose->toolbar->sig_btn)
2380 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2381 if (compose->toolbar->exteditor_btn)
2382 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2383 if (compose->toolbar->linewrap_current_btn)
2384 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2385 if (compose->toolbar->linewrap_all_btn)
2386 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2388 compose->modified = FALSE;
2389 compose_set_title(compose);
2390 compose->updating = FALSE;
2391 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2392 SCROLL_TO_CURSOR(compose);
2394 if (compose->deferred_destroy) {
2395 compose_destroy(compose);
2402 GList *compose_get_compose_list(void)
2404 return compose_list;
2407 void compose_entry_append(Compose *compose, const gchar *address,
2408 ComposeEntryType type)
2410 const gchar *header;
2412 gboolean in_quote = FALSE;
2413 if (!address || *address == '\0') return;
2420 header = N_("Bcc:");
2422 case COMPOSE_REPLYTO:
2423 header = N_("Reply-To:");
2425 case COMPOSE_NEWSGROUPS:
2426 header = N_("Newsgroups:");
2428 case COMPOSE_FOLLOWUPTO:
2429 header = N_( "Followup-To:");
2436 header = prefs_common_translated_header_name(header);
2438 cur = begin = (gchar *)address;
2440 /* we separate the line by commas, but not if we're inside a quoted
2442 while (*cur != '\0') {
2444 in_quote = !in_quote;
2445 if (*cur == ',' && !in_quote) {
2446 gchar *tmp = g_strdup(begin);
2448 tmp[cur-begin]='\0';
2451 while (*tmp == ' ' || *tmp == '\t')
2453 compose_add_header_entry(compose, header, tmp);
2460 gchar *tmp = g_strdup(begin);
2462 tmp[cur-begin]='\0';
2465 while (*tmp == ' ' || *tmp == '\t')
2467 compose_add_header_entry(compose, header, tmp);
2472 static void compose_entry_mark_default_to(Compose *compose, const gchar *mailto)
2474 static GdkColor yellow;
2475 static GdkColor black;
2476 static gboolean yellow_initialised = FALSE;
2480 if (!yellow_initialised) {
2481 gdk_color_parse("#f5f6be", &yellow);
2482 gdk_color_parse("#000000", &black);
2483 yellow_initialised = gdk_colormap_alloc_color(
2484 gdk_colormap_get_system(), &yellow, FALSE, TRUE);
2485 yellow_initialised &= gdk_colormap_alloc_color(
2486 gdk_colormap_get_system(), &black, FALSE, TRUE);
2489 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2490 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2491 if (gtk_entry_get_text(entry) &&
2492 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2493 if (yellow_initialised) {
2494 gtk_widget_modify_base(
2495 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2496 GTK_STATE_NORMAL, &yellow);
2497 gtk_widget_modify_text(
2498 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2499 GTK_STATE_NORMAL, &black);
2505 void compose_toolbar_cb(gint action, gpointer data)
2507 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2508 Compose *compose = (Compose*)toolbar_item->parent;
2510 g_return_if_fail(compose != NULL);
2514 compose_send_cb(compose, 0, NULL);
2517 compose_send_later_cb(compose, 0, NULL);
2520 compose_draft_cb(compose, COMPOSE_QUIT_EDITING, NULL);
2523 compose_insert_file_cb(compose, 0, NULL);
2526 compose_attach_cb(compose, 0, NULL);
2529 compose_insert_sig(compose, FALSE);
2532 compose_ext_editor_cb(compose, 0, NULL);
2534 case A_LINEWRAP_CURRENT:
2535 compose_beautify_paragraph(compose, NULL, TRUE);
2537 case A_LINEWRAP_ALL:
2538 compose_wrap_all_full(compose, TRUE);
2541 compose_address_cb(compose, 0, NULL);
2544 case A_CHECK_SPELLING:
2545 compose_check_all(compose);
2553 static void compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
2558 gchar *subject = NULL;
2562 gchar **attach = NULL;
2564 /* get mailto parts but skip from */
2565 scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach);
2568 compose_entry_append(compose, to, to_type);
2570 compose_entry_append(compose, cc, COMPOSE_CC);
2572 compose_entry_append(compose, bcc, COMPOSE_BCC);
2574 if (!g_utf8_validate (subject, -1, NULL)) {
2575 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2576 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2579 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2583 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2584 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2587 gboolean prev_autowrap = compose->autowrap;
2589 compose->autowrap = FALSE;
2591 mark = gtk_text_buffer_get_insert(buffer);
2592 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2594 if (!g_utf8_validate (body, -1, NULL)) {
2595 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2596 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2599 gtk_text_buffer_insert(buffer, &iter, body, -1);
2601 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2603 compose->autowrap = prev_autowrap;
2604 if (compose->autowrap)
2605 compose_wrap_all(compose);
2609 gint i = 0, att = 0;
2610 gchar *warn_files = NULL;
2611 while (attach[i] != NULL) {
2612 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2613 if (utf8_filename) {
2614 if (compose_attach_append(compose, attach[i], utf8_filename, NULL)) {
2615 gchar *tmp = g_strdup_printf("%s%s\n",
2616 warn_files?warn_files:"",
2622 g_free(utf8_filename);
2624 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2629 alertpanel_notice(ngettext(
2630 "The following file has been attached: \n%s",
2631 "The following files have been attached: \n%s", att), warn_files);
2643 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2645 static HeaderEntry hentry[] = {{"Reply-To:", NULL, TRUE},
2646 {"Cc:", NULL, TRUE},
2647 {"References:", NULL, FALSE},
2648 {"Bcc:", NULL, TRUE},
2649 {"Newsgroups:", NULL, TRUE},
2650 {"Followup-To:", NULL, TRUE},
2651 {"List-Post:", NULL, FALSE},
2652 {"X-Priority:", NULL, FALSE},
2653 {NULL, NULL, FALSE}};
2669 g_return_val_if_fail(msginfo != NULL, -1);
2671 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2672 procheader_get_header_fields(fp, hentry);
2675 if (hentry[H_REPLY_TO].body != NULL) {
2676 if (hentry[H_REPLY_TO].body[0] != '\0') {
2678 conv_unmime_header(hentry[H_REPLY_TO].body,
2681 g_free(hentry[H_REPLY_TO].body);
2682 hentry[H_REPLY_TO].body = NULL;
2684 if (hentry[H_CC].body != NULL) {
2685 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL);
2686 g_free(hentry[H_CC].body);
2687 hentry[H_CC].body = NULL;
2689 if (hentry[H_REFERENCES].body != NULL) {
2690 if (compose->mode == COMPOSE_REEDIT)
2691 compose->references = hentry[H_REFERENCES].body;
2693 compose->references = compose_parse_references
2694 (hentry[H_REFERENCES].body, msginfo->msgid);
2695 g_free(hentry[H_REFERENCES].body);
2697 hentry[H_REFERENCES].body = NULL;
2699 if (hentry[H_BCC].body != NULL) {
2700 if (compose->mode == COMPOSE_REEDIT)
2702 conv_unmime_header(hentry[H_BCC].body, NULL);
2703 g_free(hentry[H_BCC].body);
2704 hentry[H_BCC].body = NULL;
2706 if (hentry[H_NEWSGROUPS].body != NULL) {
2707 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2708 hentry[H_NEWSGROUPS].body = NULL;
2710 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2711 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2712 compose->followup_to =
2713 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2716 g_free(hentry[H_FOLLOWUP_TO].body);
2717 hentry[H_FOLLOWUP_TO].body = NULL;
2719 if (hentry[H_LIST_POST].body != NULL) {
2722 extract_address(hentry[H_LIST_POST].body);
2723 if (hentry[H_LIST_POST].body[0] != '\0') {
2724 scan_mailto_url(hentry[H_LIST_POST].body,
2725 NULL, &to, NULL, NULL, NULL, NULL, NULL);
2727 g_free(compose->ml_post);
2728 compose->ml_post = to;
2731 g_free(hentry[H_LIST_POST].body);
2732 hentry[H_LIST_POST].body = NULL;
2735 /* CLAWS - X-Priority */
2736 if (compose->mode == COMPOSE_REEDIT)
2737 if (hentry[H_X_PRIORITY].body != NULL) {
2740 priority = atoi(hentry[H_X_PRIORITY].body);
2741 g_free(hentry[H_X_PRIORITY].body);
2743 hentry[H_X_PRIORITY].body = NULL;
2745 if (priority < PRIORITY_HIGHEST ||
2746 priority > PRIORITY_LOWEST)
2747 priority = PRIORITY_NORMAL;
2749 compose->priority = priority;
2752 if (compose->mode == COMPOSE_REEDIT) {
2753 if (msginfo->inreplyto && *msginfo->inreplyto)
2754 compose->inreplyto = g_strdup(msginfo->inreplyto);
2758 if (msginfo->msgid && *msginfo->msgid)
2759 compose->inreplyto = g_strdup(msginfo->msgid);
2761 if (!compose->references) {
2762 if (msginfo->msgid && *msginfo->msgid) {
2763 if (msginfo->inreplyto && *msginfo->inreplyto)
2764 compose->references =
2765 g_strdup_printf("<%s>\n\t<%s>",
2769 compose->references =
2770 g_strconcat("<", msginfo->msgid, ">",
2772 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
2773 compose->references =
2774 g_strconcat("<", msginfo->inreplyto, ">",
2782 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
2784 GSList *ref_id_list, *cur;
2788 ref_id_list = references_list_append(NULL, ref);
2789 if (!ref_id_list) return NULL;
2790 if (msgid && *msgid)
2791 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
2796 for (cur = ref_id_list; cur != NULL; cur = cur->next)
2797 /* "<" + Message-ID + ">" + CR+LF+TAB */
2798 len += strlen((gchar *)cur->data) + 5;
2800 if (len > MAX_REFERENCES_LEN) {
2801 /* remove second message-ID */
2802 if (ref_id_list && ref_id_list->next &&
2803 ref_id_list->next->next) {
2804 g_free(ref_id_list->next->data);
2805 ref_id_list = g_slist_remove
2806 (ref_id_list, ref_id_list->next->data);
2808 slist_free_strings(ref_id_list);
2809 g_slist_free(ref_id_list);
2816 new_ref = g_string_new("");
2817 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
2818 if (new_ref->len > 0)
2819 g_string_append(new_ref, "\n\t");
2820 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
2823 slist_free_strings(ref_id_list);
2824 g_slist_free(ref_id_list);
2826 new_ref_str = new_ref->str;
2827 g_string_free(new_ref, FALSE);
2832 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
2833 const gchar *fmt, const gchar *qmark,
2834 const gchar *body, gboolean rewrap,
2835 gboolean need_unescape,
2836 const gchar *err_msg)
2838 MsgInfo* dummyinfo = NULL;
2839 gchar *quote_str = NULL;
2841 gboolean prev_autowrap;
2842 const gchar *trimmed_body = body;
2843 gint cursor_pos = -1;
2844 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2845 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2850 SIGNAL_BLOCK(buffer);
2853 dummyinfo = compose_msginfo_new_from_compose(compose);
2854 msginfo = dummyinfo;
2857 if (qmark != NULL) {
2859 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
2860 compose->gtkaspell);
2862 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
2864 quote_fmt_scan_string(qmark);
2867 buf = quote_fmt_get_buffer();
2869 alertpanel_error(_("Quote mark format error."));
2871 Xstrdup_a(quote_str, buf, goto error)
2874 if (fmt && *fmt != '\0') {
2877 while (*trimmed_body == '\n')
2881 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
2882 compose->gtkaspell);
2884 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
2886 if (need_unescape) {
2889 /* decode \-escape sequences in the internal representation of the quote format */
2890 tmp = malloc(strlen(fmt)+1);
2891 pref_get_unescaped_pref(tmp, fmt);
2892 quote_fmt_scan_string(tmp);
2896 quote_fmt_scan_string(fmt);
2900 buf = quote_fmt_get_buffer();
2902 gint line = quote_fmt_get_line();
2903 alertpanel_error(err_msg, line);
2909 prev_autowrap = compose->autowrap;
2910 compose->autowrap = FALSE;
2912 mark = gtk_text_buffer_get_insert(buffer);
2913 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2914 if (g_utf8_validate(buf, -1, NULL)) {
2915 gtk_text_buffer_insert(buffer, &iter, buf, -1);
2917 gchar *tmpout = NULL;
2918 tmpout = conv_codeset_strdup
2919 (buf, conv_get_locale_charset_str_no_utf8(),
2921 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
2923 tmpout = g_malloc(strlen(buf)*2+1);
2924 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
2926 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
2930 cursor_pos = quote_fmt_get_cursor_pos();
2931 compose->set_cursor_pos = cursor_pos;
2932 if (cursor_pos == -1) {
2935 gtk_text_buffer_get_start_iter(buffer, &iter);
2936 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
2937 gtk_text_buffer_place_cursor(buffer, &iter);
2939 compose->autowrap = prev_autowrap;
2940 if (compose->autowrap && rewrap)
2941 compose_wrap_all(compose);
2948 SIGNAL_UNBLOCK(buffer);
2950 procmsg_msginfo_free( dummyinfo );
2955 /* if ml_post is of type addr@host and from is of type
2956 * addr-anything@host, return TRUE
2958 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
2960 gchar *left_ml = NULL;
2961 gchar *right_ml = NULL;
2962 gchar *left_from = NULL;
2963 gchar *right_from = NULL;
2964 gboolean result = FALSE;
2966 if (!ml_post || !from)
2969 left_ml = g_strdup(ml_post);
2970 if (strstr(left_ml, "@")) {
2971 right_ml = strstr(left_ml, "@")+1;
2972 *(strstr(left_ml, "@")) = '\0';
2975 left_from = g_strdup(from);
2976 if (strstr(left_from, "@")) {
2977 right_from = strstr(left_from, "@")+1;
2978 *(strstr(left_from, "@")) = '\0';
2981 if (left_ml && left_from && right_ml && right_from
2982 && !strncmp(left_from, left_ml, strlen(left_ml))
2983 && !strcmp(right_from, right_ml)) {
2992 static gboolean same_address(const gchar *addr1, const gchar *addr2)
2994 gchar *my_addr1, *my_addr2;
2996 if (!addr1 || !addr2)
2999 Xstrdup_a(my_addr1, addr1, return FALSE);
3000 Xstrdup_a(my_addr2, addr2, return FALSE);
3002 extract_address(my_addr1);
3003 extract_address(my_addr2);
3005 return !strcasecmp(my_addr1, my_addr2);
3008 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3009 gboolean to_all, gboolean to_ml,
3011 gboolean followup_and_reply_to)
3013 GSList *cc_list = NULL;
3016 gchar *replyto = NULL;
3017 GHashTable *to_table;
3019 gboolean reply_to_ml = FALSE;
3020 gboolean default_reply_to = FALSE;
3022 g_return_if_fail(compose->account != NULL);
3023 g_return_if_fail(msginfo != NULL);
3025 reply_to_ml = to_ml && compose->ml_post;
3027 default_reply_to = msginfo->folder &&
3028 msginfo->folder->prefs->enable_default_reply_to;
3030 if (compose->account->protocol != A_NNTP) {
3031 if (reply_to_ml && !default_reply_to) {
3033 gboolean is_subscr = is_subscription(compose->ml_post,
3036 /* normal answer to ml post with a reply-to */
3037 compose_entry_append(compose,
3040 if (compose->replyto
3041 && !same_address(compose->ml_post, compose->replyto))
3042 compose_entry_append(compose,
3046 /* answer to subscription confirmation */
3047 if (compose->replyto)
3048 compose_entry_append(compose,
3051 else if (msginfo->from)
3052 compose_entry_append(compose,
3057 else if (!(to_all || to_sender) && default_reply_to) {
3058 compose_entry_append(compose,
3059 msginfo->folder->prefs->default_reply_to,
3061 compose_entry_mark_default_to(compose,
3062 msginfo->folder->prefs->default_reply_to);
3067 Xstrdup_a(tmp1, msginfo->from, return);
3068 extract_address(tmp1);
3069 if (to_all || to_sender ||
3070 !account_find_from_address(tmp1, FALSE))
3071 compose_entry_append(compose,
3072 (compose->replyto && !to_sender)
3073 ? compose->replyto :
3074 msginfo->from ? msginfo->from : "",
3076 else if (!to_all && !to_sender) {
3077 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3078 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3079 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3080 if (compose->replyto) {
3081 compose_entry_append(compose,
3085 compose_entry_append(compose,
3086 msginfo->from ? msginfo->from : "",
3090 /* replying to own mail, use original recp */
3091 compose_entry_append(compose,
3092 msginfo->to ? msginfo->to : "",
3094 compose_entry_append(compose,
3095 msginfo->cc ? msginfo->cc : "",
3101 if (to_sender || (compose->followup_to &&
3102 !strncmp(compose->followup_to, "poster", 6)))
3103 compose_entry_append
3105 (compose->replyto ? compose->replyto :
3106 msginfo->from ? msginfo->from : ""),
3109 else if (followup_and_reply_to || to_all) {
3110 compose_entry_append
3112 (compose->replyto ? compose->replyto :
3113 msginfo->from ? msginfo->from : ""),
3116 compose_entry_append
3118 compose->followup_to ? compose->followup_to :
3119 compose->newsgroups ? compose->newsgroups : "",
3120 COMPOSE_NEWSGROUPS);
3123 compose_entry_append
3125 compose->followup_to ? compose->followup_to :
3126 compose->newsgroups ? compose->newsgroups : "",
3127 COMPOSE_NEWSGROUPS);
3130 if (msginfo->subject && *msginfo->subject) {
3134 buf = p = g_strdup(msginfo->subject);
3135 p += subject_get_prefix_length(p);
3136 memmove(buf, p, strlen(p) + 1);
3138 buf2 = g_strdup_printf("Re: %s", buf);
3139 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3144 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3146 if (to_ml && compose->ml_post) return;
3147 if (!to_all || compose->account->protocol == A_NNTP) return;
3149 if (compose->replyto) {
3150 Xstrdup_a(replyto, compose->replyto, return);
3151 extract_address(replyto);
3153 if (msginfo->from) {
3154 Xstrdup_a(from, msginfo->from, return);
3155 extract_address(from);
3158 if (replyto && from)
3159 cc_list = address_list_append_with_comments(cc_list, from);
3160 if (to_all && msginfo->folder &&
3161 msginfo->folder->prefs->enable_default_reply_to)
3162 cc_list = address_list_append_with_comments(cc_list,
3163 msginfo->folder->prefs->default_reply_to);
3164 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3165 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3167 to_table = g_hash_table_new(g_str_hash, g_str_equal);
3169 g_hash_table_insert(to_table, g_utf8_strdown(replyto, -1), GINT_TO_POINTER(1));
3170 if (compose->account) {
3171 g_hash_table_insert(to_table, g_utf8_strdown(compose->account->address, -1),
3172 GINT_TO_POINTER(1));
3174 /* remove address on To: and that of current account */
3175 for (cur = cc_list; cur != NULL; ) {
3176 GSList *next = cur->next;
3179 addr = g_utf8_strdown(cur->data, -1);
3180 extract_address(addr);
3182 if (GPOINTER_TO_INT(g_hash_table_lookup(to_table, addr)) == 1)
3183 cc_list = g_slist_remove(cc_list, cur->data);
3185 g_hash_table_insert(to_table, addr, GINT_TO_POINTER(1));
3189 hash_free_strings(to_table);
3190 g_hash_table_destroy(to_table);
3193 for (cur = cc_list; cur != NULL; cur = cur->next)
3194 compose_entry_append(compose, (gchar *)cur->data,
3196 slist_free_strings(cc_list);
3197 g_slist_free(cc_list);
3202 #define SET_ENTRY(entry, str) \
3205 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3208 #define SET_ADDRESS(type, str) \
3211 compose_entry_append(compose, str, type); \
3214 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3216 g_return_if_fail(msginfo != NULL);
3218 SET_ENTRY(subject_entry, msginfo->subject);
3219 SET_ENTRY(from_name, msginfo->from);
3220 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3221 SET_ADDRESS(COMPOSE_CC, compose->cc);
3222 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3223 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3224 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3225 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3227 compose_update_priority_menu_item(compose);
3228 compose_update_privacy_system_menu_item(compose, FALSE);
3229 compose_show_first_last_header(compose, TRUE);
3235 static void compose_insert_sig(Compose *compose, gboolean replace)
3237 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3238 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3240 GtkTextIter iter, iter_end;
3242 gboolean prev_autowrap;
3243 gboolean found = FALSE;
3244 gboolean exists = FALSE;
3246 g_return_if_fail(compose->account != NULL);
3250 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3251 G_CALLBACK(compose_changed_cb),
3254 mark = gtk_text_buffer_get_insert(buffer);
3255 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3256 cur_pos = gtk_text_iter_get_offset (&iter);
3258 gtk_text_buffer_get_end_iter(buffer, &iter);
3260 exists = (compose->sig_str != NULL);
3263 GtkTextIter first_iter, start_iter, end_iter;
3265 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3267 if (!exists || compose->sig_str[0] == '\0')
3270 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3271 compose->signature_tag);
3274 /* include previous \n\n */
3275 gtk_text_iter_backward_chars(&first_iter, 2);
3276 start_iter = first_iter;
3277 end_iter = first_iter;
3279 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3280 compose->signature_tag);
3281 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3282 compose->signature_tag);
3284 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3290 g_free(compose->sig_str);
3291 compose->sig_str = compose_get_signature_str(compose);
3293 cur_pos = gtk_text_iter_get_offset(&iter);
3295 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3296 g_free(compose->sig_str);
3297 compose->sig_str = NULL;
3299 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3301 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3302 gtk_text_iter_forward_chars(&iter, 2);
3303 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3304 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3306 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3307 cur_pos = gtk_text_buffer_get_char_count (buffer);
3309 /* put the cursor where it should be
3310 * either where the quote_fmt says, either before the signature */
3311 if (compose->set_cursor_pos < 0)
3312 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3314 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3315 compose->set_cursor_pos);
3317 gtk_text_buffer_place_cursor(buffer, &iter);
3318 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3319 G_CALLBACK(compose_changed_cb),
3325 static gchar *compose_get_signature_str(Compose *compose)
3327 gchar *sig_body = NULL;
3328 gchar *sig_str = NULL;
3329 gchar *utf8_sig_str = NULL;
3331 g_return_val_if_fail(compose->account != NULL, NULL);
3333 if (!compose->account->sig_path)
3336 if (compose->account->sig_type == SIG_FILE) {
3337 if (!is_file_or_fifo_exist(compose->account->sig_path)) {
3338 g_warning("can't open signature file: %s\n",
3339 compose->account->sig_path);
3344 if (compose->account->sig_type == SIG_COMMAND)
3345 sig_body = get_command_output(compose->account->sig_path);
3349 tmp = file_read_to_str(compose->account->sig_path);
3352 sig_body = normalize_newlines(tmp);
3356 if (compose->account->sig_sep) {
3357 sig_str = g_strconcat("\n\n", compose->account->sig_sep, "\n", sig_body,
3361 sig_str = g_strconcat("\n\n", sig_body, NULL);
3364 if (g_utf8_validate(sig_str, -1, NULL) == TRUE)
3365 utf8_sig_str = sig_str;
3367 utf8_sig_str = conv_codeset_strdup
3368 (sig_str, conv_get_locale_charset_str_no_utf8(),
3374 return utf8_sig_str;
3377 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3380 GtkTextBuffer *buffer;
3383 const gchar *cur_encoding;
3384 gchar buf[BUFFSIZE];
3387 gboolean prev_autowrap;
3388 gboolean badtxt = FALSE;
3390 g_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3392 if ((fp = g_fopen(file, "rb")) == NULL) {
3393 FILE_OP_ERROR(file, "fopen");
3394 return COMPOSE_INSERT_READ_ERROR;
3397 prev_autowrap = compose->autowrap;
3398 compose->autowrap = FALSE;
3400 text = GTK_TEXT_VIEW(compose->text);
3401 buffer = gtk_text_view_get_buffer(text);
3402 mark = gtk_text_buffer_get_insert(buffer);
3403 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3405 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3406 G_CALLBACK(text_inserted),
3409 cur_encoding = conv_get_locale_charset_str_no_utf8();
3411 while (fgets(buf, sizeof(buf), fp) != NULL) {
3414 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3415 str = g_strdup(buf);
3417 str = conv_codeset_strdup
3418 (buf, cur_encoding, CS_INTERNAL);
3421 /* strip <CR> if DOS/Windows file,
3422 replace <CR> with <LF> if Macintosh file. */
3425 if (len > 0 && str[len - 1] != '\n') {
3427 if (str[len] == '\r') str[len] = '\n';
3430 gtk_text_buffer_insert(buffer, &iter, str, -1);
3434 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3435 G_CALLBACK(text_inserted),
3437 compose->autowrap = prev_autowrap;
3438 if (compose->autowrap)
3439 compose_wrap_all(compose);
3444 return COMPOSE_INSERT_INVALID_CHARACTER;
3446 return COMPOSE_INSERT_SUCCESS;
3449 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3450 const gchar *filename,
3451 const gchar *content_type)
3459 GtkListStore *store;
3461 gboolean has_binary = FALSE;
3463 if (!is_file_exist(file)) {
3464 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3465 gboolean result = FALSE;
3466 if (file_from_uri && is_file_exist(file_from_uri)) {
3467 result = compose_attach_append(
3468 compose, file_from_uri,
3472 g_free(file_from_uri);
3475 alertpanel_error("File %s doesn't exist\n", filename);
3478 if ((size = get_file_size(file)) < 0) {
3479 alertpanel_error("Can't get file size of %s\n", filename);
3483 alertpanel_error(_("File %s is empty."), filename);
3486 if ((fp = g_fopen(file, "rb")) == NULL) {
3487 alertpanel_error(_("Can't read %s."), filename);
3492 ainfo = g_new0(AttachInfo, 1);
3493 auto_ainfo = g_auto_pointer_new_with_free
3494 (ainfo, (GFreeFunc) compose_attach_info_free);
3495 ainfo->file = g_strdup(file);
3498 ainfo->content_type = g_strdup(content_type);
3499 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3501 MsgFlags flags = {0, 0};
3503 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3504 ainfo->encoding = ENC_7BIT;
3506 ainfo->encoding = ENC_8BIT;
3508 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3509 if (msginfo && msginfo->subject)
3510 name = g_strdup(msginfo->subject);
3512 name = g_path_get_basename(filename ? filename : file);
3514 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3516 procmsg_msginfo_free(msginfo);
3518 if (!g_ascii_strncasecmp(content_type, "text", 4))
3519 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3521 ainfo->encoding = ENC_BASE64;
3522 name = g_path_get_basename(filename ? filename : file);
3523 ainfo->name = g_strdup(name);
3527 ainfo->content_type = procmime_get_mime_type(file);
3528 if (!ainfo->content_type) {
3529 ainfo->content_type =
3530 g_strdup("application/octet-stream");
3531 ainfo->encoding = ENC_BASE64;
3532 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text", 4))
3534 procmime_get_encoding_for_text_file(file, &has_binary);
3536 ainfo->encoding = ENC_BASE64;
3537 name = g_path_get_basename(filename ? filename : file);
3538 ainfo->name = g_strdup(name);
3542 if (ainfo->name != NULL
3543 && !strcmp(ainfo->name, ".")) {
3544 g_free(ainfo->name);
3548 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3549 g_free(ainfo->content_type);
3550 ainfo->content_type = g_strdup("application/octet-stream");
3554 size_text = to_human_readable(size);
3556 store = GTK_LIST_STORE(gtk_tree_view_get_model
3557 (GTK_TREE_VIEW(compose->attach_clist)));
3559 gtk_list_store_append(store, &iter);
3560 gtk_list_store_set(store, &iter,
3561 COL_MIMETYPE, ainfo->content_type,
3562 COL_SIZE, size_text,
3563 COL_NAME, ainfo->name,
3565 COL_AUTODATA, auto_ainfo,
3568 g_auto_pointer_free(auto_ainfo);
3569 compose_attach_update_label(compose);
3573 static void compose_use_signing(Compose *compose, gboolean use_signing)
3575 GtkItemFactory *ifactory;
3576 GtkWidget *menuitem = NULL;
3578 compose->use_signing = use_signing;
3579 ifactory = gtk_item_factory_from_widget(compose->menubar);
3580 menuitem = gtk_item_factory_get_item
3581 (ifactory, "/Options/Sign");
3582 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
3586 static void compose_use_encryption(Compose *compose, gboolean use_encryption)
3588 GtkItemFactory *ifactory;
3589 GtkWidget *menuitem = NULL;
3591 compose->use_encryption = use_encryption;
3592 ifactory = gtk_item_factory_from_widget(compose->menubar);
3593 menuitem = gtk_item_factory_get_item
3594 (ifactory, "/Options/Encrypt");
3596 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
3600 #define NEXT_PART_NOT_CHILD(info) \
3602 node = info->node; \
3603 while (node->children) \
3604 node = g_node_last_child(node); \
3605 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3608 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3612 MimeInfo *firsttext = NULL;
3613 MimeInfo *encrypted = NULL;
3616 const gchar *partname = NULL;
3618 mimeinfo = procmime_scan_message(msginfo);
3619 if (!mimeinfo) return;
3621 if (mimeinfo->node->children == NULL) {
3622 procmime_mimeinfo_free_all(mimeinfo);
3626 /* find first content part */
3627 child = (MimeInfo *) mimeinfo->node->children->data;
3628 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3629 child = (MimeInfo *)child->node->children->data;
3631 if (child->type == MIMETYPE_TEXT) {
3633 debug_print("First text part found\n");
3634 } else if (compose->mode == COMPOSE_REEDIT &&
3635 child->type == MIMETYPE_APPLICATION &&
3636 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3637 encrypted = (MimeInfo *)child->node->parent->data;
3640 child = (MimeInfo *) mimeinfo->node->children->data;
3641 while (child != NULL) {
3644 if (child == encrypted) {
3645 /* skip this part of tree */
3646 NEXT_PART_NOT_CHILD(child);
3650 if (child->type == MIMETYPE_MULTIPART) {
3651 /* get the actual content */
3652 child = procmime_mimeinfo_next(child);
3656 if (child == firsttext) {
3657 child = procmime_mimeinfo_next(child);
3661 outfile = procmime_get_tmp_file_name(child);
3662 if ((err = procmime_get_part(outfile, child)) < 0)
3663 g_warning("Can't get the part of multipart message. (%s)", strerror(-err));
3665 gchar *content_type;
3667 content_type = procmime_get_content_type_str(child->type, child->subtype);
3669 /* if we meet a pgp signature, we don't attach it, but
3670 * we force signing. */
3671 if ((strcmp(content_type, "application/pgp-signature") &&
3672 strcmp(content_type, "application/pkcs7-signature") &&
3673 strcmp(content_type, "application/x-pkcs7-signature"))
3674 || compose->mode == COMPOSE_REDIRECT) {
3675 partname = procmime_mimeinfo_get_parameter(child, "filename");
3676 if (partname == NULL)
3677 partname = procmime_mimeinfo_get_parameter(child, "name");
3678 if (partname == NULL)
3680 compose_attach_append(compose, outfile,
3681 partname, content_type);
3683 compose_force_signing(compose, compose->account);
3685 g_free(content_type);
3688 NEXT_PART_NOT_CHILD(child);
3690 procmime_mimeinfo_free_all(mimeinfo);
3693 #undef NEXT_PART_NOT_CHILD
3698 WAIT_FOR_INDENT_CHAR,
3699 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3702 /* return indent length, we allow:
3703 indent characters followed by indent characters or spaces/tabs,
3704 alphabets and numbers immediately followed by indent characters,
3705 and the repeating sequences of the above
3706 If quote ends with multiple spaces, only the first one is included. */
3707 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3708 const GtkTextIter *start, gint *len)
3710 GtkTextIter iter = *start;
3714 IndentState state = WAIT_FOR_INDENT_CHAR;
3717 gint alnum_count = 0;
3718 gint space_count = 0;
3721 if (prefs_common.quote_chars == NULL) {
3725 while (!gtk_text_iter_ends_line(&iter)) {
3726 wc = gtk_text_iter_get_char(&iter);
3727 if (g_unichar_iswide(wc))
3729 clen = g_unichar_to_utf8(wc, ch);
3733 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
3734 is_space = g_unichar_isspace(wc);
3736 if (state == WAIT_FOR_INDENT_CHAR) {
3737 if (!is_indent && !g_unichar_isalnum(wc))
3740 quote_len += alnum_count + space_count + 1;
3741 alnum_count = space_count = 0;
3742 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
3745 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
3746 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
3750 else if (is_indent) {
3751 quote_len += alnum_count + space_count + 1;
3752 alnum_count = space_count = 0;
3755 state = WAIT_FOR_INDENT_CHAR;
3759 gtk_text_iter_forward_char(&iter);
3762 if (quote_len > 0 && space_count > 0)
3768 if (quote_len > 0) {
3770 gtk_text_iter_forward_chars(&iter, quote_len);
3771 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
3777 /* return TRUE if the line is itemized */
3778 static gboolean compose_is_itemized(GtkTextBuffer *buffer,
3779 const GtkTextIter *start)
3781 GtkTextIter iter = *start;
3786 if (gtk_text_iter_ends_line(&iter))
3790 wc = gtk_text_iter_get_char(&iter);
3791 if (!g_unichar_isspace(wc))
3793 gtk_text_iter_forward_char(&iter);
3794 if (gtk_text_iter_ends_line(&iter))
3798 clen = g_unichar_to_utf8(wc, ch);
3802 if (!strchr("*-+", ch[0]))
3805 gtk_text_iter_forward_char(&iter);
3806 if (gtk_text_iter_ends_line(&iter))
3808 wc = gtk_text_iter_get_char(&iter);
3809 if (g_unichar_isspace(wc))
3815 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
3816 const GtkTextIter *start,
3817 GtkTextIter *break_pos,
3821 GtkTextIter iter = *start, line_end = *start;
3822 PangoLogAttr *attrs;
3829 gboolean can_break = FALSE;
3830 gboolean do_break = FALSE;
3831 gboolean was_white = FALSE;
3832 gboolean prev_dont_break = FALSE;
3834 gtk_text_iter_forward_to_line_end(&line_end);
3835 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
3836 len = g_utf8_strlen(str, -1);
3840 g_warning("compose_get_line_break_pos: len = 0!\n");
3844 /* g_print("breaking line: %d: %s (len = %d)\n",
3845 gtk_text_iter_get_line(&iter), str, len); */
3847 attrs = g_new(PangoLogAttr, len + 1);
3849 pango_default_break(str, -1, NULL, attrs, len + 1);
3853 /* skip quote and leading spaces */
3854 for (i = 0; *p != '\0' && i < len; i++) {
3857 wc = g_utf8_get_char(p);
3858 if (i >= quote_len && !g_unichar_isspace(wc))
3860 if (g_unichar_iswide(wc))
3862 else if (*p == '\t')
3866 p = g_utf8_next_char(p);
3869 for (; *p != '\0' && i < len; i++) {
3870 PangoLogAttr *attr = attrs + i;
3874 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
3877 was_white = attr->is_white;
3879 /* don't wrap URI */
3880 if ((uri_len = get_uri_len(p)) > 0) {
3882 if (pos > 0 && col > max_col) {
3892 wc = g_utf8_get_char(p);
3893 if (g_unichar_iswide(wc)) {
3895 if (prev_dont_break && can_break && attr->is_line_break)
3897 } else if (*p == '\t')
3901 if (pos > 0 && col > max_col) {
3906 if (*p == '-' || *p == '/')
3907 prev_dont_break = TRUE;
3909 prev_dont_break = FALSE;
3911 p = g_utf8_next_char(p);
3915 debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col);
3920 *break_pos = *start;
3921 gtk_text_iter_set_line_offset(break_pos, pos);
3926 static gboolean compose_join_next_line(Compose *compose,
3927 GtkTextBuffer *buffer,
3929 const gchar *quote_str)
3931 GtkTextIter iter_ = *iter, cur, prev, next, end;
3932 PangoLogAttr attrs[3];
3934 gchar *next_quote_str;
3937 gboolean keep_cursor = FALSE;
3939 if (!gtk_text_iter_forward_line(&iter_) ||
3940 gtk_text_iter_ends_line(&iter_)) {
3943 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
3945 if ((quote_str || next_quote_str) &&
3946 strcmp2(quote_str, next_quote_str) != 0) {
3947 g_free(next_quote_str);
3950 g_free(next_quote_str);
3953 if (quote_len > 0) {
3954 gtk_text_iter_forward_chars(&end, quote_len);
3955 if (gtk_text_iter_ends_line(&end)) {
3960 /* don't join itemized lines */
3961 if (compose_is_itemized(buffer, &end)) {
3965 /* don't join signature separator */
3966 if (compose_is_sig_separator(compose, buffer, &iter_)) {
3969 /* delete quote str */
3971 gtk_text_buffer_delete(buffer, &iter_, &end);
3973 /* don't join line breaks put by the user */
3975 gtk_text_iter_backward_char(&cur);
3976 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
3977 gtk_text_iter_forward_char(&cur);
3981 gtk_text_iter_forward_char(&cur);
3982 /* delete linebreak and extra spaces */
3983 while (gtk_text_iter_backward_char(&cur)) {
3984 wc1 = gtk_text_iter_get_char(&cur);
3985 if (!g_unichar_isspace(wc1))
3990 while (!gtk_text_iter_ends_line(&cur)) {
3991 wc1 = gtk_text_iter_get_char(&cur);
3992 if (!g_unichar_isspace(wc1))
3994 gtk_text_iter_forward_char(&cur);
3997 if (!gtk_text_iter_equal(&prev, &next)) {
4000 mark = gtk_text_buffer_get_insert(buffer);
4001 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4002 if (gtk_text_iter_equal(&prev, &cur))
4004 gtk_text_buffer_delete(buffer, &prev, &next);
4008 /* insert space if required */
4009 gtk_text_iter_backward_char(&prev);
4010 wc1 = gtk_text_iter_get_char(&prev);
4011 wc2 = gtk_text_iter_get_char(&next);
4012 gtk_text_iter_forward_char(&next);
4013 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4014 pango_default_break(str, -1, NULL, attrs, 3);
4015 if (!attrs[1].is_line_break ||
4016 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4017 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4019 gtk_text_iter_backward_char(&iter_);
4020 gtk_text_buffer_place_cursor(buffer, &iter_);
4029 #define ADD_TXT_POS(bp_, ep_, pti_) \
4030 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4031 last = last->next; \
4032 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4033 last->next = NULL; \
4035 g_warning("alloc error scanning URIs\n"); \
4038 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4040 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4041 GtkTextBuffer *buffer;
4042 GtkTextIter iter, break_pos, end_of_line;
4043 gchar *quote_str = NULL;
4045 gboolean wrap_quote = prefs_common.linewrap_quote;
4046 gboolean prev_autowrap = compose->autowrap;
4047 gint startq_offset = -1, noq_offset = -1;
4048 gint uri_start = -1, uri_stop = -1;
4049 gint nouri_start = -1, nouri_stop = -1;
4050 gint num_blocks = 0;
4051 gint quotelevel = -1;
4052 gboolean modified = force;
4053 gboolean removed = FALSE;
4054 gboolean modified_before_remove = FALSE;
4056 gboolean start = TRUE;
4061 if (compose->draft_timeout_tag == -2) {
4065 compose->autowrap = FALSE;
4067 buffer = gtk_text_view_get_buffer(text);
4068 undo_wrapping(compose->undostruct, TRUE);
4073 mark = gtk_text_buffer_get_insert(buffer);
4074 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4078 if (compose->draft_timeout_tag == -2) {
4079 if (gtk_text_iter_ends_line(&iter)) {
4080 while (gtk_text_iter_ends_line(&iter) &&
4081 gtk_text_iter_forward_line(&iter))
4084 while (gtk_text_iter_backward_line(&iter)) {
4085 if (gtk_text_iter_ends_line(&iter)) {
4086 gtk_text_iter_forward_line(&iter);
4092 /* move to line start */
4093 gtk_text_iter_set_line_offset(&iter, 0);
4095 /* go until paragraph end (empty line) */
4096 while (start || !gtk_text_iter_ends_line(&iter)) {
4097 gchar *scanpos = NULL;
4098 /* parse table - in order of priority */
4100 const gchar *needle; /* token */
4102 /* token search function */
4103 gchar *(*search) (const gchar *haystack,
4104 const gchar *needle);
4105 /* part parsing function */
4106 gboolean (*parse) (const gchar *start,
4107 const gchar *scanpos,
4111 /* part to URI function */
4112 gchar *(*build_uri) (const gchar *bp,
4116 static struct table parser[] = {
4117 {"http://", strcasestr, get_uri_part, make_uri_string},
4118 {"https://", strcasestr, get_uri_part, make_uri_string},
4119 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4120 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4121 {"www.", strcasestr, get_uri_part, make_http_string},
4122 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4123 {"@", strcasestr, get_email_part, make_email_string}
4125 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4126 gint last_index = PARSE_ELEMS;
4128 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4132 if (!prev_autowrap && num_blocks == 0) {
4134 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4135 G_CALLBACK(text_inserted),
4138 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4141 uri_start = uri_stop = -1;
4143 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
4146 debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str);
4147 if (startq_offset == -1)
4148 startq_offset = gtk_text_iter_get_offset(&iter);
4149 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4150 if (quotelevel > 2) {
4151 /* recycle colors */
4152 if (prefs_common.recycle_quote_colors)
4161 if (startq_offset == -1)
4162 noq_offset = gtk_text_iter_get_offset(&iter);
4166 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4169 if (gtk_text_iter_ends_line(&iter)) {
4171 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4172 prefs_common.linewrap_len,
4174 GtkTextIter prev, next, cur;
4176 if (prev_autowrap != FALSE || force) {
4177 compose->automatic_break = TRUE;
4179 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4180 compose->automatic_break = FALSE;
4181 } else if (quote_str && wrap_quote) {
4182 compose->automatic_break = TRUE;
4184 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4185 compose->automatic_break = FALSE;
4188 /* remove trailing spaces */
4190 gtk_text_iter_backward_char(&cur);
4192 while (!gtk_text_iter_starts_line(&cur)) {
4195 gtk_text_iter_backward_char(&cur);
4196 wc = gtk_text_iter_get_char(&cur);
4197 if (!g_unichar_isspace(wc))
4201 if (!gtk_text_iter_equal(&prev, &next)) {
4202 gtk_text_buffer_delete(buffer, &prev, &next);
4204 gtk_text_iter_forward_char(&break_pos);
4208 gtk_text_buffer_insert(buffer, &break_pos,
4212 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4214 /* move iter to current line start */
4215 gtk_text_iter_set_line_offset(&iter, 0);
4222 /* move iter to next line start */
4228 if (!prev_autowrap && num_blocks > 0) {
4230 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4231 G_CALLBACK(text_inserted),
4235 while (!gtk_text_iter_ends_line(&end_of_line)) {
4236 gtk_text_iter_forward_char(&end_of_line);
4238 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4240 nouri_start = gtk_text_iter_get_offset(&iter);
4241 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4243 walk_pos = gtk_text_iter_get_offset(&iter);
4244 /* FIXME: this looks phony. scanning for anything in the parse table */
4245 for (n = 0; n < PARSE_ELEMS; n++) {
4248 tmp = parser[n].search(walk, parser[n].needle);
4250 if (scanpos == NULL || tmp < scanpos) {
4259 /* check if URI can be parsed */
4260 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4261 (const gchar **)&ep, FALSE)
4262 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4266 strlen(parser[last_index].needle);
4269 uri_start = walk_pos + (bp - o_walk);
4270 uri_stop = walk_pos + (ep - o_walk);
4274 gtk_text_iter_forward_line(&iter);
4277 if (startq_offset != -1) {
4278 GtkTextIter startquote, endquote;
4279 gtk_text_buffer_get_iter_at_offset(
4280 buffer, &startquote, startq_offset);
4283 switch (quotelevel) {
4285 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4286 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4287 gtk_text_buffer_apply_tag_by_name(
4288 buffer, "quote0", &startquote, &endquote);
4289 gtk_text_buffer_remove_tag_by_name(
4290 buffer, "quote1", &startquote, &endquote);
4291 gtk_text_buffer_remove_tag_by_name(
4292 buffer, "quote2", &startquote, &endquote);
4297 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4298 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4299 gtk_text_buffer_apply_tag_by_name(
4300 buffer, "quote1", &startquote, &endquote);
4301 gtk_text_buffer_remove_tag_by_name(
4302 buffer, "quote0", &startquote, &endquote);
4303 gtk_text_buffer_remove_tag_by_name(
4304 buffer, "quote2", &startquote, &endquote);
4309 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4310 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4311 gtk_text_buffer_apply_tag_by_name(
4312 buffer, "quote2", &startquote, &endquote);
4313 gtk_text_buffer_remove_tag_by_name(
4314 buffer, "quote0", &startquote, &endquote);
4315 gtk_text_buffer_remove_tag_by_name(
4316 buffer, "quote1", &startquote, &endquote);
4322 } else if (noq_offset != -1) {
4323 GtkTextIter startnoquote, endnoquote;
4324 gtk_text_buffer_get_iter_at_offset(
4325 buffer, &startnoquote, noq_offset);
4328 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4329 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4330 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4331 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4332 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4333 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4334 gtk_text_buffer_remove_tag_by_name(
4335 buffer, "quote0", &startnoquote, &endnoquote);
4336 gtk_text_buffer_remove_tag_by_name(
4337 buffer, "quote1", &startnoquote, &endnoquote);
4338 gtk_text_buffer_remove_tag_by_name(
4339 buffer, "quote2", &startnoquote, &endnoquote);
4345 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4346 GtkTextIter nouri_start_iter, nouri_end_iter;
4347 gtk_text_buffer_get_iter_at_offset(
4348 buffer, &nouri_start_iter, nouri_start);
4349 gtk_text_buffer_get_iter_at_offset(
4350 buffer, &nouri_end_iter, nouri_stop);
4351 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4352 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4353 gtk_text_buffer_remove_tag_by_name(
4354 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4355 modified_before_remove = modified;
4360 if (uri_start >= 0 && uri_stop > 0) {
4361 GtkTextIter uri_start_iter, uri_end_iter, back;
4362 gtk_text_buffer_get_iter_at_offset(
4363 buffer, &uri_start_iter, uri_start);
4364 gtk_text_buffer_get_iter_at_offset(
4365 buffer, &uri_end_iter, uri_stop);
4366 back = uri_end_iter;
4367 gtk_text_iter_backward_char(&back);
4368 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4369 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4370 gtk_text_buffer_apply_tag_by_name(
4371 buffer, "link", &uri_start_iter, &uri_end_iter);
4373 if (removed && !modified_before_remove) {
4379 debug_print("not modified, out after %d lines\n", lines);
4384 debug_print("modified, out after %d lines\n", lines);
4388 undo_wrapping(compose->undostruct, FALSE);
4389 compose->autowrap = prev_autowrap;
4394 void compose_action_cb(void *data)
4396 Compose *compose = (Compose *)data;
4397 compose_wrap_all(compose);
4400 static void compose_wrap_all(Compose *compose)
4402 compose_wrap_all_full(compose, FALSE);
4405 static void compose_wrap_all_full(Compose *compose, gboolean force)
4407 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4408 GtkTextBuffer *buffer;
4410 gboolean modified = TRUE;
4412 buffer = gtk_text_view_get_buffer(text);
4414 gtk_text_buffer_get_start_iter(buffer, &iter);
4415 while (!gtk_text_iter_is_end(&iter) && modified)
4416 modified = compose_beautify_paragraph(compose, &iter, force);
4420 static void compose_set_title(Compose *compose)
4426 edited = compose->modified ? _(" [Edited]") : "";
4428 subject = gtk_editable_get_chars(
4429 GTK_EDITABLE(compose->subject_entry), 0, -1);
4432 if (subject && strlen(subject))
4433 str = g_strdup_printf(_("%s - Compose message%s"),
4436 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4438 str = g_strdup(_("Compose message"));
4441 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4447 * compose_current_mail_account:
4449 * Find a current mail account (the currently selected account, or the
4450 * default account, if a news account is currently selected). If a
4451 * mail account cannot be found, display an error message.
4453 * Return value: Mail account, or NULL if not found.
4455 static PrefsAccount *
4456 compose_current_mail_account(void)
4460 if (cur_account && cur_account->protocol != A_NNTP)
4463 ac = account_get_default();
4464 if (!ac || ac->protocol == A_NNTP) {
4465 alertpanel_error(_("Account for sending mail is not specified.\n"
4466 "Please select a mail account before sending."));
4473 #define QUOTE_IF_REQUIRED(out, str) \
4475 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4479 len = strlen(str) + 3; \
4480 if ((__tmp = alloca(len)) == NULL) { \
4481 g_warning("can't allocate memory\n"); \
4482 g_string_free(header, TRUE); \
4485 g_snprintf(__tmp, len, "\"%s\"", str); \
4490 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4491 g_warning("can't allocate memory\n"); \
4492 g_string_free(header, TRUE); \
4495 strcpy(__tmp, str); \
4501 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4503 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4507 len = strlen(str) + 3; \
4508 if ((__tmp = alloca(len)) == NULL) { \
4509 g_warning("can't allocate memory\n"); \
4512 g_snprintf(__tmp, len, "\"%s\"", str); \
4517 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4518 g_warning("can't allocate memory\n"); \
4521 strcpy(__tmp, str); \
4527 static void compose_select_account(Compose *compose, PrefsAccount *account,
4530 GtkItemFactory *ifactory;
4533 g_return_if_fail(account != NULL);
4535 compose->account = account;
4537 if (account->name && *account->name) {
4539 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4540 from = g_strdup_printf("%s <%s>",
4541 buf, account->address);
4542 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4544 from = g_strdup_printf("<%s>",
4546 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4551 compose_set_title(compose);
4553 ifactory = gtk_item_factory_from_widget(compose->menubar);
4555 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4556 menu_set_active(ifactory, "/Options/Sign", TRUE);
4558 menu_set_active(ifactory, "/Options/Sign", FALSE);
4559 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4560 menu_set_active(ifactory, "/Options/Encrypt", TRUE);
4562 menu_set_active(ifactory, "/Options/Encrypt", FALSE);
4564 activate_privacy_system(compose, account, FALSE);
4566 if (!init && compose->mode != COMPOSE_REDIRECT) {
4567 undo_block(compose->undostruct);
4568 compose_insert_sig(compose, TRUE);
4569 undo_unblock(compose->undostruct);
4573 /* use account's dict info if set */
4574 if (compose->gtkaspell) {
4575 if (account->enable_default_dictionary)
4576 gtkaspell_change_dict(compose->gtkaspell,
4577 account->default_dictionary, FALSE);
4578 if (account->enable_default_alt_dictionary)
4579 gtkaspell_change_alt_dict(compose->gtkaspell,
4580 account->default_alt_dictionary);
4581 if (account->enable_default_dictionary
4582 || account->enable_default_alt_dictionary)
4583 compose_spell_menu_changed(compose);
4588 gboolean compose_check_for_valid_recipient(Compose *compose) {
4589 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
4590 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
4591 gboolean recipient_found = FALSE;
4595 /* free to and newsgroup list */
4596 slist_free_strings(compose->to_list);
4597 g_slist_free(compose->to_list);
4598 compose->to_list = NULL;
4600 slist_free_strings(compose->newsgroup_list);
4601 g_slist_free(compose->newsgroup_list);
4602 compose->newsgroup_list = NULL;
4604 /* search header entries for to and newsgroup entries */
4605 for (list = compose->header_list; list; list = list->next) {
4608 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_BIN(((ComposeHeaderEntry *)list->data)->combo)->child), 0, -1);
4609 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4612 if (entry[0] != '\0') {
4613 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
4614 if (!strcmp(header, prefs_common_translated_header_name(*strptr))) {
4615 compose->to_list = address_list_append(compose->to_list, entry);
4616 recipient_found = TRUE;
4619 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
4620 if (!strcmp(header, prefs_common_translated_header_name(*strptr))) {
4621 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
4622 recipient_found = TRUE;
4629 return recipient_found;
4632 static gboolean compose_check_for_set_recipients(Compose *compose)
4634 if (compose->account->set_autocc && compose->account->auto_cc) {
4635 gboolean found_other = FALSE;
4637 /* search header entries for to and newsgroup entries */
4638 for (list = compose->header_list; list; list = list->next) {
4641 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4642 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_BIN(((ComposeHeaderEntry *)list->data)->combo)->child), 0, -1);
4645 if (strcmp(entry, compose->account->auto_cc)
4646 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
4656 if (compose->batch) {
4657 gtk_widget_show_all(compose->window);
4659 aval = alertpanel(_("Send"),
4660 _("The only recipient is the default CC address. Send anyway?"),
4661 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4662 if (aval != G_ALERTALTERNATE)
4666 if (compose->account->set_autobcc && compose->account->auto_bcc) {
4667 gboolean found_other = FALSE;
4669 /* search header entries for to and newsgroup entries */
4670 for (list = compose->header_list; list; list = list->next) {
4673 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4674 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_BIN(((ComposeHeaderEntry *)list->data)->combo)->child), 0, -1);
4677 if (strcmp(entry, compose->account->auto_bcc)
4678 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
4688 if (compose->batch) {
4689 gtk_widget_show_all(compose->window);
4691 aval = alertpanel(_("Send"),
4692 _("The only recipient is the default BCC address. Send anyway?"),
4693 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4694 if (aval != G_ALERTALTERNATE)
4701 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
4705 if (compose_check_for_valid_recipient(compose) == FALSE) {
4706 if (compose->batch) {
4707 gtk_widget_show_all(compose->window);
4709 alertpanel_error(_("Recipient is not specified."));
4713 if (compose_check_for_set_recipients(compose) == FALSE) {
4717 if (!compose->batch) {
4718 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
4719 if (*str == '\0' && check_everything == TRUE &&
4720 compose->mode != COMPOSE_REDIRECT) {
4722 gchar *button_label;
4725 if (compose->sending)
4726 button_label = _("+_Send");
4728 button_label = _("+_Queue");
4729 message = g_strdup_printf(_("Subject is empty. %s"),
4730 compose->sending?_("Send it anyway?"):
4731 _("Queue it anyway?"));
4733 aval = alertpanel(compose->sending?_("Send"):_("Send later"), message,
4734 GTK_STOCK_CANCEL, button_label, NULL);
4736 if (aval != G_ALERTALTERNATE)
4741 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
4747 gint compose_send(Compose *compose)
4750 FolderItem *folder = NULL;
4752 gchar *msgpath = NULL;
4753 gboolean discard_window = FALSE;
4754 gchar *errstr = NULL;
4755 gchar *tmsgid = NULL;
4756 MainWindow *mainwin = mainwindow_get_mainwindow();
4757 gboolean queued_removed = FALSE;
4759 if (prefs_common.send_dialog_invisible
4760 || compose->batch == TRUE)
4761 discard_window = TRUE;
4763 compose_allow_user_actions (compose, FALSE);
4764 compose->sending = TRUE;
4766 if (compose_check_entries(compose, TRUE) == FALSE) {
4767 if (compose->batch) {
4768 gtk_widget_show_all(compose->window);
4774 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
4777 if (compose->batch) {
4778 gtk_widget_show_all(compose->window);
4781 alertpanel_error(_("Could not queue message for sending:\n\n"
4782 "Charset conversion failed."));
4783 } else if (val == -5) {
4784 alertpanel_error(_("Could not queue message for sending:\n\n"
4785 "Couldn't get recipient encryption key."));
4786 } else if (val == -6) {
4788 } else if (val == -3) {
4789 if (privacy_peek_error())
4790 alertpanel_error(_("Could not queue message for sending:\n\n"
4791 "Signature failed: %s"), privacy_get_error());
4792 } else if (val == -2 && errno != 0) {
4793 alertpanel_error(_("Could not queue message for sending:\n\n%s."), strerror(errno));
4795 alertpanel_error(_("Could not queue message for sending."));
4800 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
4801 if (discard_window) {
4802 compose->sending = FALSE;
4803 compose_close(compose);
4804 /* No more compose access in the normal codepath
4805 * after this point! */
4810 alertpanel_error(_("The message was queued but could not be "
4811 "sent.\nUse \"Send queued messages\" from "
4812 "the main window to retry."));
4813 if (!discard_window) {
4820 if (msgpath == NULL) {
4821 msgpath = folder_item_fetch_msg(folder, msgnum);
4822 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
4825 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
4829 if (!discard_window) {
4831 if (!queued_removed)
4832 folder_item_remove_msg(folder, msgnum);
4833 folder_item_scan(folder);
4835 /* make sure we delete that */
4836 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
4838 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
4839 folder_item_remove_msg(folder, tmp->msgnum);
4840 procmsg_msginfo_free(tmp);
4847 if (!queued_removed)
4848 folder_item_remove_msg(folder, msgnum);
4849 folder_item_scan(folder);
4851 /* make sure we delete that */
4852 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
4854 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
4855 folder_item_remove_msg(folder, tmp->msgnum);
4856 procmsg_msginfo_free(tmp);
4859 if (!discard_window) {
4860 compose->sending = FALSE;
4861 compose_allow_user_actions (compose, TRUE);
4862 compose_close(compose);
4866 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
4867 "the main window to retry."), errstr);
4870 alertpanel_error_log(_("The message was queued but could not be "
4871 "sent.\nUse \"Send queued messages\" from "
4872 "the main window to retry."));
4874 if (!discard_window) {
4883 toolbar_main_set_sensitive(mainwin);
4884 main_window_set_menu_sensitive(mainwin);
4890 compose_allow_user_actions (compose, TRUE);
4891 compose->sending = FALSE;
4892 compose->modified = TRUE;
4893 toolbar_main_set_sensitive(mainwin);
4894 main_window_set_menu_sensitive(mainwin);
4899 static gboolean compose_use_attach(Compose *compose)
4901 GtkTreeModel *model = gtk_tree_view_get_model
4902 (GTK_TREE_VIEW(compose->attach_clist));
4903 return gtk_tree_model_iter_n_children(model, NULL) > 0;
4906 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
4909 gchar buf[BUFFSIZE];
4911 gboolean first_to_address;
4912 gboolean first_cc_address;
4914 ComposeHeaderEntry *headerentry;
4915 const gchar *headerentryname;
4916 const gchar *cc_hdr;
4917 const gchar *to_hdr;
4918 gboolean err = FALSE;
4920 debug_print("Writing redirect header\n");
4922 cc_hdr = prefs_common_translated_header_name("Cc:");
4923 to_hdr = prefs_common_translated_header_name("To:");
4925 first_to_address = TRUE;
4926 for (list = compose->header_list; list; list = list->next) {
4927 headerentry = ((ComposeHeaderEntry *)list->data);
4928 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_BIN(headerentry->combo)->child));
4930 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
4931 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
4932 Xstrdup_a(str, entstr, return -1);
4934 if (str[0] != '\0') {
4935 compose_convert_header
4936 (compose, buf, sizeof(buf), str,
4937 strlen("Resent-To") + 2, TRUE);
4939 if (first_to_address) {
4940 err |= (fprintf(fp, "Resent-To: ") < 0);
4941 first_to_address = FALSE;
4943 err |= (fprintf(fp, ",") < 0);
4945 err |= (fprintf(fp, "%s", buf) < 0);
4949 if (!first_to_address) {
4950 err |= (fprintf(fp, "\n") < 0);
4953 first_cc_address = TRUE;
4954 for (list = compose->header_list; list; list = list->next) {
4955 headerentry = ((ComposeHeaderEntry *)list->data);
4956 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_BIN(headerentry->combo)->child));
4958 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
4959 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
4960 Xstrdup_a(str, strg, return -1);
4962 if (str[0] != '\0') {
4963 compose_convert_header
4964 (compose, buf, sizeof(buf), str,
4965 strlen("Resent-Cc") + 2, TRUE);
4967 if (first_cc_address) {
4968 err |= (fprintf(fp, "Resent-Cc: ") < 0);
4969 first_cc_address = FALSE;
4971 err |= (fprintf(fp, ",") < 0);
4973 err |= (fprintf(fp, "%s", buf) < 0);
4977 if (!first_cc_address) {
4978 err |= (fprintf(fp, "\n") < 0);
4981 return (err ? -1:0);
4984 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
4986 gchar buf[BUFFSIZE];
4988 const gchar *entstr;
4989 /* struct utsname utsbuf; */
4990 gboolean err = FALSE;
4992 g_return_val_if_fail(fp != NULL, -1);
4993 g_return_val_if_fail(compose->account != NULL, -1);
4994 g_return_val_if_fail(compose->account->address != NULL, -1);
4997 get_rfc822_date(buf, sizeof(buf));
4998 err |= (fprintf(fp, "Resent-Date: %s\n", buf) < 0);
5001 if (compose->account->name && *compose->account->name) {
5002 compose_convert_header
5003 (compose, buf, sizeof(buf), compose->account->name,
5004 strlen("From: "), TRUE);
5005 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5006 buf, compose->account->address) < 0);
5008 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5011 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5012 if (*entstr != '\0') {
5013 Xstrdup_a(str, entstr, return -1);
5016 compose_convert_header(compose, buf, sizeof(buf), str,
5017 strlen("Subject: "), FALSE);
5018 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5022 /* Resent-Message-ID */
5023 if (compose->account->set_domain && compose->account->domain) {
5024 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
5025 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
5026 g_snprintf(buf, sizeof(buf), "%s",
5027 strchr(compose->account->address, '@') ?
5028 strchr(compose->account->address, '@')+1 :
5029 compose->account->address);
5031 g_snprintf(buf, sizeof(buf), "%s", "");
5034 if (compose->account->gen_msgid) {
5035 generate_msgid(buf, sizeof(buf));
5036 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", buf) < 0);
5037 compose->msgid = g_strdup(buf);
5039 compose->msgid = NULL;
5042 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5045 /* separator between header and body */
5046 err |= (fputs("\n", fp) == EOF);
5048 return (err ? -1:0);
5051 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5055 gchar buf[BUFFSIZE];
5057 gboolean skip = FALSE;
5058 gboolean err = FALSE;
5059 gchar *not_included[]={
5060 "Return-Path:", "Delivered-To:", "Received:",
5061 "Subject:", "X-UIDL:", "AF:",
5062 "NF:", "PS:", "SRH:",
5063 "SFN:", "DSR:", "MID:",
5064 "CFG:", "PT:", "S:",
5065 "RQ:", "SSV:", "NSV:",
5066 "SSH:", "R:", "MAID:",
5067 "NAID:", "RMID:", "FMID:",
5068 "SCF:", "RRCPT:", "NG:",
5069 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5070 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5071 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5072 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5075 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
5076 FILE_OP_ERROR(compose->redirect_filename, "fopen");
5080 while (procheader_get_one_field_asis(buf, sizeof(buf), fp) != -1) {
5082 for (i = 0; not_included[i] != NULL; i++) {
5083 if (g_ascii_strncasecmp(buf, not_included[i],
5084 strlen(not_included[i])) == 0) {
5091 if (fputs(buf, fdest) == -1)
5094 if (!prefs_common.redirect_keep_from) {
5095 if (g_ascii_strncasecmp(buf, "From:",
5096 strlen("From:")) == 0) {
5097 err |= (fputs(" (by way of ", fdest) == EOF);
5098 if (compose->account->name
5099 && *compose->account->name) {
5100 compose_convert_header
5101 (compose, buf, sizeof(buf),
5102 compose->account->name,
5105 err |= (fprintf(fdest, "%s <%s>",
5107 compose->account->address) < 0);
5109 err |= (fprintf(fdest, "%s",
5110 compose->account->address) < 0);
5111 err |= (fputs(")", fdest) == EOF);
5115 if (fputs("\n", fdest) == -1)
5122 if (compose_redirect_write_headers(compose, fdest))
5125 while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
5126 if (fwrite(buf, sizeof(gchar), len, fdest) != len)
5139 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5141 GtkTextBuffer *buffer;
5142 GtkTextIter start, end;
5145 const gchar *out_codeset;
5146 EncodingType encoding;
5147 MimeInfo *mimemsg, *mimetext;
5150 if (action == COMPOSE_WRITE_FOR_SEND)
5151 attach_parts = TRUE;
5153 /* create message MimeInfo */
5154 mimemsg = procmime_mimeinfo_new();
5155 mimemsg->type = MIMETYPE_MESSAGE;
5156 mimemsg->subtype = g_strdup("rfc822");
5157 mimemsg->content = MIMECONTENT_MEM;
5158 mimemsg->tmp = TRUE; /* must free content later */
5159 mimemsg->data.mem = compose_get_header(compose);
5161 /* Create text part MimeInfo */
5162 /* get all composed text */
5163 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5164 gtk_text_buffer_get_start_iter(buffer, &start);
5165 gtk_text_buffer_get_end_iter(buffer, &end);
5166 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5167 if (is_ascii_str(chars)) {
5170 out_codeset = CS_US_ASCII;
5171 encoding = ENC_7BIT;
5173 const gchar *src_codeset = CS_INTERNAL;
5175 out_codeset = conv_get_charset_str(compose->out_encoding);
5178 gchar *test_conv_global_out = NULL;
5179 gchar *test_conv_reply = NULL;
5181 /* automatic mode. be automatic. */
5182 codeconv_set_strict(TRUE);
5184 out_codeset = conv_get_outgoing_charset_str();
5186 debug_print("trying to convert to %s\n", out_codeset);
5187 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5190 if (!test_conv_global_out && compose->orig_charset
5191 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5192 out_codeset = compose->orig_charset;
5193 debug_print("failure; trying to convert to %s\n", out_codeset);
5194 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5197 if (!test_conv_global_out && !test_conv_reply) {
5199 out_codeset = CS_INTERNAL;
5200 debug_print("failure; finally using %s\n", out_codeset);
5202 g_free(test_conv_global_out);
5203 g_free(test_conv_reply);
5204 codeconv_set_strict(FALSE);
5207 if (!g_ascii_strcasecmp(out_codeset, CS_US_ASCII))
5208 out_codeset = CS_ISO_8859_1;
5210 if (prefs_common.encoding_method == CTE_BASE64)
5211 encoding = ENC_BASE64;
5212 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5213 encoding = ENC_QUOTED_PRINTABLE;
5214 else if (prefs_common.encoding_method == CTE_8BIT)
5215 encoding = ENC_8BIT;
5217 encoding = procmime_get_encoding_for_charset(out_codeset);
5219 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5220 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5222 if (action == COMPOSE_WRITE_FOR_SEND) {
5223 codeconv_set_strict(TRUE);
5224 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5225 codeconv_set_strict(FALSE);
5231 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5232 "to the specified %s charset.\n"
5233 "Send it as %s?"), out_codeset, src_codeset);
5234 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL, _("+_Send"), NULL, FALSE,
5235 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5238 if (aval != G_ALERTALTERNATE) {
5243 out_codeset = src_codeset;
5249 out_codeset = src_codeset;
5255 if (encoding == ENC_8BIT || encoding == ENC_7BIT) {
5256 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5257 strstr(buf, "\nFrom ") != NULL) {
5258 encoding = ENC_QUOTED_PRINTABLE;
5262 mimetext = procmime_mimeinfo_new();
5263 mimetext->content = MIMECONTENT_MEM;
5264 mimetext->tmp = TRUE; /* must free content later */
5265 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5266 * and free the data, which we need later. */
5267 mimetext->data.mem = g_strdup(buf);
5268 mimetext->type = MIMETYPE_TEXT;
5269 mimetext->subtype = g_strdup("plain");
5270 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5271 g_strdup(out_codeset));
5273 /* protect trailing spaces when signing message */
5274 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5275 privacy_system_can_sign(compose->privacy_system)) {
5276 encoding = ENC_QUOTED_PRINTABLE;
5279 debug_print("main text: %zd bytes encoded as %s in %d\n",
5280 strlen(buf), out_codeset, encoding);
5282 /* check for line length limit */
5283 if (action == COMPOSE_WRITE_FOR_SEND &&
5284 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5285 check_line_length(buf, 1000, &line) < 0) {
5289 msg = g_strdup_printf
5290 (_("Line %d exceeds the line length limit (998 bytes).\n"
5291 "The contents of the message might be broken on the way to the delivery.\n"
5293 "Send it anyway?"), line + 1);
5294 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5296 if (aval != G_ALERTALTERNATE) {
5302 if (encoding != ENC_UNKNOWN)
5303 procmime_encode_content(mimetext, encoding);
5305 /* append attachment parts */
5306 if (compose_use_attach(compose) && attach_parts) {
5307 MimeInfo *mimempart;
5308 gchar *boundary = NULL;
5309 mimempart = procmime_mimeinfo_new();
5310 mimempart->content = MIMECONTENT_EMPTY;
5311 mimempart->type = MIMETYPE_MULTIPART;
5312 mimempart->subtype = g_strdup("mixed");
5316 boundary = generate_mime_boundary(NULL);
5317 } while (strstr(buf, boundary) != NULL);
5319 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5322 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5324 g_node_append(mimempart->node, mimetext->node);
5325 g_node_append(mimemsg->node, mimempart->node);
5327 compose_add_attachments(compose, mimempart);
5329 g_node_append(mimemsg->node, mimetext->node);
5333 /* sign message if sending */
5334 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5335 privacy_system_can_sign(compose->privacy_system))
5336 if (!privacy_sign(compose->privacy_system, mimemsg, compose->account))
5339 procmime_write_mimeinfo(mimemsg, fp);
5341 procmime_mimeinfo_free_all(mimemsg);
5346 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5348 GtkTextBuffer *buffer;
5349 GtkTextIter start, end;
5354 if ((fp = g_fopen(file, "wb")) == NULL) {
5355 FILE_OP_ERROR(file, "fopen");
5359 /* chmod for security */
5360 if (change_file_mode_rw(fp, file) < 0) {
5361 FILE_OP_ERROR(file, "chmod");
5362 g_warning("can't change file mode\n");
5365 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5366 gtk_text_buffer_get_start_iter(buffer, &start);
5367 gtk_text_buffer_get_end_iter(buffer, &end);
5368 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5370 chars = conv_codeset_strdup
5371 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5374 if (!chars) return -1;
5377 len = strlen(chars);
5378 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5379 FILE_OP_ERROR(file, "fwrite");
5388 if (fclose(fp) == EOF) {
5389 FILE_OP_ERROR(file, "fclose");
5396 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5399 MsgInfo *msginfo = compose->targetinfo;
5401 g_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5402 if (!msginfo) return -1;
5404 if (!force && MSG_IS_LOCKED(msginfo->flags))
5407 item = msginfo->folder;
5408 g_return_val_if_fail(item != NULL, -1);
5410 if (procmsg_msg_exist(msginfo) &&
5411 (folder_has_parent_of_type(item, F_QUEUE) ||
5412 folder_has_parent_of_type(item, F_DRAFT)
5413 || msginfo == compose->autosaved_draft)) {
5414 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5415 g_warning("can't remove the old message\n");
5418 debug_print("removed reedit target %d\n", msginfo->msgnum);
5425 static void compose_remove_draft(Compose *compose)
5428 MsgInfo *msginfo = compose->targetinfo;
5429 drafts = account_get_special_folder(compose->account, F_DRAFT);
5431 if (procmsg_msg_exist(msginfo)) {
5432 folder_item_remove_msg(drafts, msginfo->msgnum);
5437 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5438 gboolean remove_reedit_target)
5440 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5443 static gboolean compose_warn_encryption(Compose *compose)
5445 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5446 AlertValue val = G_ALERTALTERNATE;
5448 if (warning == NULL)
5451 val = alertpanel_full(_("Encryption warning"), warning,
5452 GTK_STOCK_CANCEL, _("+C_ontinue"), NULL,
5453 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5454 if (val & G_ALERTDISABLE) {
5455 val &= ~G_ALERTDISABLE;
5456 if (val == G_ALERTALTERNATE)
5457 privacy_inhibit_encrypt_warning(compose->privacy_system,
5461 if (val == G_ALERTALTERNATE) {
5468 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
5469 gchar **msgpath, gboolean check_subject,
5470 gboolean remove_reedit_target)
5477 static gboolean lock = FALSE;
5478 PrefsAccount *mailac = NULL, *newsac = NULL;
5479 gboolean err = FALSE;
5481 debug_print("queueing message...\n");
5482 g_return_val_if_fail(compose->account != NULL, -1);
5486 if (compose_check_entries(compose, check_subject) == FALSE) {
5488 if (compose->batch) {
5489 gtk_widget_show_all(compose->window);
5494 if (!compose->to_list && !compose->newsgroup_list) {
5495 g_warning("can't get recipient list.");
5500 if (compose->to_list) {
5501 if (compose->account->protocol != A_NNTP)
5502 mailac = compose->account;
5503 else if (cur_account && cur_account->protocol != A_NNTP)
5504 mailac = cur_account;
5505 else if (!(mailac = compose_current_mail_account())) {
5507 alertpanel_error(_("No account for sending mails available!"));
5512 if (compose->newsgroup_list) {
5513 if (compose->account->protocol == A_NNTP)
5514 newsac = compose->account;
5515 else if (!newsac->protocol != A_NNTP) {
5517 alertpanel_error(_("No account for posting news available!"));
5522 /* write queue header */
5523 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
5524 G_DIR_SEPARATOR, compose, (guint) rand());
5525 debug_print("queuing to %s\n", tmp);
5526 if ((fp = g_fopen(tmp, "wb")) == NULL) {
5527 FILE_OP_ERROR(tmp, "fopen");
5533 if (change_file_mode_rw(fp, tmp) < 0) {
5534 FILE_OP_ERROR(tmp, "chmod");
5535 g_warning("can't change file mode\n");
5538 /* queueing variables */
5539 err |= (fprintf(fp, "AF:\n") < 0);
5540 err |= (fprintf(fp, "NF:0\n") < 0);
5541 err |= (fprintf(fp, "PS:10\n") < 0);
5542 err |= (fprintf(fp, "SRH:1\n") < 0);
5543 err |= (fprintf(fp, "SFN:\n") < 0);
5544 err |= (fprintf(fp, "DSR:\n") < 0);
5546 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
5548 err |= (fprintf(fp, "MID:\n") < 0);
5549 err |= (fprintf(fp, "CFG:\n") < 0);
5550 err |= (fprintf(fp, "PT:0\n") < 0);
5551 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
5552 err |= (fprintf(fp, "RQ:\n") < 0);
5554 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
5556 err |= (fprintf(fp, "SSV:\n") < 0);
5558 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
5560 err |= (fprintf(fp, "NSV:\n") < 0);
5561 err |= (fprintf(fp, "SSH:\n") < 0);
5562 /* write recepient list */
5563 if (compose->to_list) {
5564 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
5565 for (cur = compose->to_list->next; cur != NULL;
5567 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
5568 err |= (fprintf(fp, "\n") < 0);
5570 /* write newsgroup list */
5571 if (compose->newsgroup_list) {
5572 err |= (fprintf(fp, "NG:") < 0);
5573 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
5574 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
5575 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
5576 err |= (fprintf(fp, "\n") < 0);
5578 /* Sylpheed account IDs */
5580 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
5582 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
5585 if (compose->privacy_system != NULL) {
5586 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
5587 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
5588 if (compose->use_encryption) {
5590 if (!compose_warn_encryption(compose)) {
5597 if (mailac && mailac->encrypt_to_self) {
5598 GSList *tmp_list = g_slist_copy(compose->to_list);
5599 tmp_list = g_slist_append(tmp_list, compose->account->address);
5600 encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
5601 g_slist_free(tmp_list);
5603 encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
5605 if (encdata != NULL) {
5606 if (strcmp(encdata, "_DONT_ENCRYPT_")) {
5607 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5608 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
5610 } /* else we finally dont want to encrypt */
5612 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5613 /* and if encdata was null, it means there's been a problem in
5625 /* Save copy folder */
5626 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
5627 gchar *savefolderid;
5629 savefolderid = gtk_editable_get_chars(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
5630 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
5631 g_free(savefolderid);
5633 /* Save copy folder */
5634 if (compose->return_receipt) {
5635 err |= (fprintf(fp, "RRCPT:1\n") < 0);
5637 /* Message-ID of message replying to */
5638 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
5641 folderid = folder_item_get_identifier(compose->replyinfo->folder);
5642 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
5645 /* Message-ID of message forwarding to */
5646 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
5649 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
5650 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
5654 /* end of headers */
5655 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
5657 if (compose->redirect_filename != NULL) {
5658 if (compose_redirect_write_to_file(compose, fp) < 0) {
5667 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
5672 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
5676 g_warning("failed to write queue message\n");
5683 if (fclose(fp) == EOF) {
5684 FILE_OP_ERROR(tmp, "fclose");
5691 if (item && *item) {
5694 queue = account_get_special_folder(compose->account, F_QUEUE);
5697 g_warning("can't find queue folder\n");
5703 folder_item_scan(queue);
5704 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
5705 g_warning("can't queue the message\n");
5712 if (msgpath == NULL) {
5718 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
5719 compose_remove_reedit_target(compose, FALSE);
5722 if ((msgnum != NULL) && (item != NULL)) {
5730 static void compose_add_attachments(Compose *compose, MimeInfo *parent)
5733 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
5735 struct stat statbuf;
5736 gchar *type, *subtype;
5737 GtkTreeModel *model;
5740 model = gtk_tree_view_get_model(tree_view);
5742 if (!gtk_tree_model_get_iter_first(model, &iter))
5745 gtk_tree_model_get(model, &iter,
5749 mimepart = procmime_mimeinfo_new();
5750 mimepart->content = MIMECONTENT_FILE;
5751 mimepart->data.filename = g_strdup(ainfo->file);
5752 mimepart->tmp = FALSE; /* or we destroy our attachment */
5753 mimepart->offset = 0;
5755 stat(ainfo->file, &statbuf);
5756 mimepart->length = statbuf.st_size;
5758 type = g_strdup(ainfo->content_type);
5760 if (!strchr(type, '/')) {
5762 type = g_strdup("application/octet-stream");
5765 subtype = strchr(type, '/') + 1;
5766 *(subtype - 1) = '\0';
5767 mimepart->type = procmime_get_media_type(type);
5768 mimepart->subtype = g_strdup(subtype);
5771 if (mimepart->type == MIMETYPE_MESSAGE &&
5772 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
5773 mimepart->disposition = DISPOSITIONTYPE_INLINE;
5776 g_hash_table_insert(mimepart->typeparameters,
5777 g_strdup("name"), g_strdup(ainfo->name));
5778 g_hash_table_insert(mimepart->dispositionparameters,
5779 g_strdup("filename"), g_strdup(ainfo->name));
5780 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
5784 if (compose->use_signing) {
5785 if (ainfo->encoding == ENC_7BIT)
5786 ainfo->encoding = ENC_QUOTED_PRINTABLE;
5787 else if (ainfo->encoding == ENC_8BIT)
5788 ainfo->encoding = ENC_BASE64;
5791 procmime_encode_content(mimepart, ainfo->encoding);
5793 g_node_append(parent->node, mimepart->node);
5794 } while (gtk_tree_model_iter_next(model, &iter));
5797 #define IS_IN_CUSTOM_HEADER(header) \
5798 (compose->account->add_customhdr && \
5799 custom_header_find(compose->account->customhdr_list, header) != NULL)
5801 static void compose_add_headerfield_from_headerlist(Compose *compose,
5803 const gchar *fieldname,
5804 const gchar *seperator)
5806 gchar *str, *fieldname_w_colon;
5807 gboolean add_field = FALSE;
5809 ComposeHeaderEntry *headerentry;
5810 const gchar *headerentryname;
5811 const gchar *trans_fieldname;
5814 if (IS_IN_CUSTOM_HEADER(fieldname))
5817 debug_print("Adding %s-fields\n", fieldname);
5819 fieldstr = g_string_sized_new(64);
5821 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
5822 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
5824 for (list = compose->header_list; list; list = list->next) {
5825 headerentry = ((ComposeHeaderEntry *)list->data);
5826 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_BIN(headerentry->combo)->child));
5828 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
5829 str = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
5831 if (str[0] != '\0') {
5833 g_string_append(fieldstr, seperator);
5834 g_string_append(fieldstr, str);
5843 buf = g_new0(gchar, fieldstr->len * 4 + 256);
5844 compose_convert_header
5845 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
5846 strlen(fieldname) + 2, TRUE);
5847 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
5851 g_free(fieldname_w_colon);
5852 g_string_free(fieldstr, TRUE);
5857 static gchar *compose_get_header(Compose *compose)
5859 gchar buf[BUFFSIZE];
5860 const gchar *entry_str;
5864 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
5866 gchar *from_name = NULL, *from_address = NULL;
5869 g_return_val_if_fail(compose->account != NULL, NULL);
5870 g_return_val_if_fail(compose->account->address != NULL, NULL);
5872 header = g_string_sized_new(64);
5875 get_rfc822_date(buf, sizeof(buf));
5876 g_string_append_printf(header, "Date: %s\n", buf);
5880 if (compose->account->name && *compose->account->name) {
5882 QUOTE_IF_REQUIRED(buf, compose->account->name);
5883 tmp = g_strdup_printf("%s <%s>",
5884 buf, compose->account->address);
5886 tmp = g_strdup_printf("%s",
5887 compose->account->address);
5889 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
5890 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
5892 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
5893 from_address = g_strdup(compose->account->address);
5895 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5896 /* extract name and address */
5897 if (strstr(spec, " <") && strstr(spec, ">")) {
5898 from_address = g_strdup(strrchr(spec, '<')+1);
5899 *(strrchr(from_address, '>')) = '\0';
5900 from_name = g_strdup(spec);
5901 *(strrchr(from_name, '<')) = '\0';
5904 from_address = g_strdup(spec);
5911 if (from_name && *from_name) {
5912 compose_convert_header
5913 (compose, buf, sizeof(buf), from_name,
5914 strlen("From: "), TRUE);
5915 QUOTE_IF_REQUIRED(name, buf);
5917 g_string_append_printf(header, "From: %s <%s>\n",
5918 name, from_address);
5920 g_string_append_printf(header, "From: %s\n", from_address);
5923 g_free(from_address);
5926 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
5929 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
5932 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
5935 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
5938 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
5940 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
5943 compose_convert_header(compose, buf, sizeof(buf), str,
5944 strlen("Subject: "), FALSE);
5945 g_string_append_printf(header, "Subject: %s\n", buf);
5951 if (compose->account->set_domain && compose->account->domain) {
5952 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
5953 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
5954 g_snprintf(buf, sizeof(buf), "%s",
5955 strchr(compose->account->address, '@') ?
5956 strchr(compose->account->address, '@')+1 :
5957 compose->account->address);
5959 g_snprintf(buf, sizeof(buf), "%s", "");
5962 if (compose->account->gen_msgid) {
5963 generate_msgid(buf, sizeof(buf));
5964 g_string_append_printf(header, "Message-ID: <%s>\n", buf);
5965 compose->msgid = g_strdup(buf);
5967 compose->msgid = NULL;
5970 if (compose->remove_references == FALSE) {
5972 if (compose->inreplyto && compose->to_list)
5973 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
5976 if (compose->references)
5977 g_string_append_printf(header, "References: %s\n", compose->references);
5981 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
5984 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
5987 if (compose->account->organization &&
5988 strlen(compose->account->organization) &&
5989 !IS_IN_CUSTOM_HEADER("Organization")) {
5990 compose_convert_header(compose, buf, sizeof(buf),
5991 compose->account->organization,
5992 strlen("Organization: "), FALSE);
5993 g_string_append_printf(header, "Organization: %s\n", buf);
5996 /* Program version and system info */
5997 if (g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
5998 !compose->newsgroup_list) {
5999 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6001 gtk_major_version, gtk_minor_version, gtk_micro_version,
6004 if (g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6005 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6007 gtk_major_version, gtk_minor_version, gtk_micro_version,
6011 /* custom headers */
6012 if (compose->account->add_customhdr) {
6015 for (cur = compose->account->customhdr_list; cur != NULL;
6017 CustomHeader *chdr = (CustomHeader *)cur->data;
6019 if (custom_header_is_allowed(chdr->name)) {
6020 compose_convert_header
6021 (compose, buf, sizeof(buf),
6022 chdr->value ? chdr->value : "",
6023 strlen(chdr->name) + 2, FALSE);
6024 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6030 switch (compose->priority) {
6031 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6032 "X-Priority: 1 (Highest)\n");
6034 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6035 "X-Priority: 2 (High)\n");
6037 case PRIORITY_NORMAL: break;
6038 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6039 "X-Priority: 4 (Low)\n");
6041 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6042 "X-Priority: 5 (Lowest)\n");
6044 default: debug_print("compose: priority unknown : %d\n",
6048 /* Request Return Receipt */
6049 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To")) {
6050 if (compose->return_receipt) {
6051 if (compose->account->name
6052 && *compose->account->name) {
6053 compose_convert_header(compose, buf, sizeof(buf),
6054 compose->account->name,
6055 strlen("Disposition-Notification-To: "),
6057 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, compose->account->address);
6059 g_string_append_printf(header, "Disposition-Notification-To: %s\n", compose->account->address);
6063 /* get special headers */
6064 for (list = compose->header_list; list; list = list->next) {
6065 ComposeHeaderEntry *headerentry;
6068 gchar *headername_wcolon;
6069 const gchar *headername_trans;
6072 gboolean standard_header = FALSE;
6074 headerentry = ((ComposeHeaderEntry *)list->data);
6076 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(GTK_BIN(headerentry->combo)->child)));
6078 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6083 if (!strstr(tmp, ":")) {
6084 headername_wcolon = g_strconcat(tmp, ":", NULL);
6085 headername = g_strdup(tmp);
6087 headername_wcolon = g_strdup(tmp);
6088 headername = g_strdup(strtok(tmp, ":"));
6092 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6093 Xstrdup_a(headervalue, entry_str, return NULL);
6094 subst_char(headervalue, '\r', ' ');
6095 subst_char(headervalue, '\n', ' ');
6096 string = std_headers;
6097 while (*string != NULL) {
6098 headername_trans = prefs_common_translated_header_name(*string);
6099 if (!strcmp(headername_trans, headername_wcolon))
6100 standard_header = TRUE;
6103 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6104 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
6107 g_free(headername_wcolon);
6111 g_string_free(header, FALSE);
6116 #undef IS_IN_CUSTOM_HEADER
6118 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6119 gint header_len, gboolean addr_field)
6121 gchar *tmpstr = NULL;
6122 const gchar *out_codeset = NULL;
6124 g_return_if_fail(src != NULL);
6125 g_return_if_fail(dest != NULL);
6127 if (len < 1) return;
6129 tmpstr = g_strdup(src);
6131 subst_char(tmpstr, '\n', ' ');
6132 subst_char(tmpstr, '\r', ' ');
6135 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6136 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6137 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6142 codeconv_set_strict(TRUE);
6143 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6144 conv_get_charset_str(compose->out_encoding));
6145 codeconv_set_strict(FALSE);
6147 if (!dest || *dest == '\0') {
6148 gchar *test_conv_global_out = NULL;
6149 gchar *test_conv_reply = NULL;
6151 /* automatic mode. be automatic. */
6152 codeconv_set_strict(TRUE);
6154 out_codeset = conv_get_outgoing_charset_str();
6156 debug_print("trying to convert to %s\n", out_codeset);
6157 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6160 if (!test_conv_global_out && compose->orig_charset
6161 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6162 out_codeset = compose->orig_charset;
6163 debug_print("failure; trying to convert to %s\n", out_codeset);
6164 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6167 if (!test_conv_global_out && !test_conv_reply) {
6169 out_codeset = CS_INTERNAL;
6170 debug_print("finally using %s\n", out_codeset);
6172 g_free(test_conv_global_out);
6173 g_free(test_conv_reply);
6174 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6176 codeconv_set_strict(FALSE);
6181 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
6185 g_return_if_fail(user_data != NULL);
6187 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6188 g_strstrip(address);
6189 if (*address != '\0') {
6190 gchar *name = procheader_get_fromname(address);
6191 extract_address(address);
6192 addressbook_add_contact(name, address, NULL, NULL);
6197 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
6199 GtkWidget *menuitem;
6202 g_return_if_fail(menu != NULL);
6203 g_return_if_fail(GTK_IS_MENU_SHELL(menu));
6205 menuitem = gtk_separator_menu_item_new();
6206 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6207 gtk_widget_show(menuitem);
6209 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
6210 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6212 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
6213 g_strstrip(address);
6214 if (*address == '\0') {
6215 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
6218 g_signal_connect(G_OBJECT(menuitem), "activate",
6219 G_CALLBACK(compose_add_to_addressbook_cb), entry);
6220 gtk_widget_show(menuitem);
6223 static void compose_create_header_entry(Compose *compose)
6225 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6230 const gchar *header = NULL;
6231 ComposeHeaderEntry *headerentry;
6232 gboolean standard_header = FALSE;
6234 headerentry = g_new0(ComposeHeaderEntry, 1);
6237 combo = gtk_combo_box_entry_new_text();
6239 while(*string != NULL) {
6240 gtk_combo_box_append_text(GTK_COMBO_BOX(combo),
6241 (gchar*)prefs_common_translated_header_name(*string));
6244 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
6245 g_signal_connect(G_OBJECT(GTK_BIN(combo)->child), "grab_focus",
6246 G_CALLBACK(compose_grab_focus_cb), compose);
6247 gtk_widget_show(combo);
6248 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
6249 compose->header_nextrow, compose->header_nextrow+1,
6250 GTK_SHRINK, GTK_FILL, 0, 0);
6251 if (compose->header_last) {
6252 const gchar *last_header_entry = gtk_entry_get_text(
6253 GTK_ENTRY(GTK_BIN(compose->header_last->combo)->child));
6255 while (*string != NULL) {
6256 if (!strcmp(*string, last_header_entry))
6257 standard_header = TRUE;
6260 if (standard_header)
6261 header = gtk_entry_get_text(GTK_ENTRY(GTK_BIN(compose->header_last->combo)->child));
6263 if (!compose->header_last || !standard_header) {
6264 switch(compose->account->protocol) {
6266 header = prefs_common_translated_header_name("Newsgroups:");
6269 header = prefs_common_translated_header_name("To:");
6274 gtk_entry_set_text(GTK_ENTRY(GTK_BIN(combo)->child), header);
6276 g_signal_connect_after(G_OBJECT(GTK_BIN(combo)->child), "grab_focus",
6277 G_CALLBACK(compose_grab_focus_cb), compose);
6280 entry = gtk_entry_new();
6281 gtk_widget_show(entry);
6282 gtk_tooltips_set_tip(compose->tooltips, entry,
6283 _("Use <tab> to autocomplete from addressbook"), NULL);
6284 gtk_table_attach(GTK_TABLE(compose->header_table), entry, 1, 2,
6285 compose->header_nextrow, compose->header_nextrow+1,
6286 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
6288 g_signal_connect(G_OBJECT(entry), "key-press-event",
6289 G_CALLBACK(compose_headerentry_key_press_event_cb),
6291 g_signal_connect(G_OBJECT(entry), "changed",
6292 G_CALLBACK(compose_headerentry_changed_cb),
6294 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
6295 G_CALLBACK(compose_grab_focus_cb), compose);
6298 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6299 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6300 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6301 g_signal_connect(G_OBJECT(entry), "drag_data_received",
6302 G_CALLBACK(compose_header_drag_received_cb),
6304 g_signal_connect(G_OBJECT(entry), "drag-drop",
6305 G_CALLBACK(compose_drag_drop),
6307 g_signal_connect(G_OBJECT(entry), "populate-popup",
6308 G_CALLBACK(compose_entry_popup_extend),
6311 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
6313 headerentry->compose = compose;
6314 headerentry->combo = combo;
6315 headerentry->entry = entry;
6316 headerentry->headernum = compose->header_nextrow;
6318 compose->header_nextrow++;
6319 compose->header_last = headerentry;
6320 compose->header_list =
6321 g_slist_append(compose->header_list,
6325 static void compose_add_header_entry(Compose *compose, const gchar *header, gchar *text)
6327 ComposeHeaderEntry *last_header;
6329 last_header = compose->header_last;
6331 gtk_entry_set_text(GTK_ENTRY(GTK_BIN(last_header->combo)->child), header);
6332 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
6335 static void compose_remove_header_entries(Compose *compose)
6338 for (list = compose->header_list; list; list = list->next) {
6339 ComposeHeaderEntry *headerentry =
6340 (ComposeHeaderEntry *)list->data;
6341 gtk_widget_destroy(headerentry->combo);
6342 gtk_widget_destroy(headerentry->entry);
6343 g_free(headerentry);
6345 compose->header_last = NULL;
6346 g_slist_free(compose->header_list);
6347 compose->header_list = NULL;
6348 compose->header_nextrow = 1;
6349 compose_create_header_entry(compose);
6352 static GtkWidget *compose_create_header(Compose *compose)
6354 GtkWidget *from_optmenu_hbox;
6355 GtkWidget *header_scrolledwin;
6356 GtkWidget *header_table;
6360 /* header labels and entries */
6361 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
6362 gtk_widget_show(header_scrolledwin);
6363 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
6365 header_table = gtk_table_new(2, 2, FALSE);
6366 gtk_widget_show(header_table);
6367 gtk_container_set_border_width(GTK_CONTAINER(header_table), BORDER_WIDTH);
6368 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
6369 gtk_viewport_set_shadow_type(GTK_VIEWPORT(GTK_BIN(header_scrolledwin)->child), GTK_SHADOW_NONE);
6372 /* option menu for selecting accounts */
6373 from_optmenu_hbox = compose_account_option_menu_create(compose);
6374 gtk_table_attach(GTK_TABLE(header_table), from_optmenu_hbox,
6375 0, 2, count, count + 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
6378 compose->header_table = header_table;
6379 compose->header_list = NULL;
6380 compose->header_nextrow = count;
6382 compose_create_header_entry(compose);
6384 compose->table = NULL;
6386 return header_scrolledwin ;
6389 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
6391 Compose *compose = (Compose *)data;
6392 GdkEventButton event;
6395 event.time = gtk_get_current_event_time();
6397 return attach_button_pressed(compose->attach_clist, &event, compose);
6400 static GtkWidget *compose_create_attach(Compose *compose)
6402 GtkWidget *attach_scrwin;
6403 GtkWidget *attach_clist;
6405 GtkListStore *store;
6406 GtkCellRenderer *renderer;
6407 GtkTreeViewColumn *column;
6408 GtkTreeSelection *selection;
6410 /* attachment list */
6411 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
6412 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
6413 GTK_POLICY_AUTOMATIC,
6414 GTK_POLICY_AUTOMATIC);
6415 gtk_widget_set_size_request(attach_scrwin, -1, 80);
6417 store = gtk_list_store_new(N_ATTACH_COLS,
6422 G_TYPE_AUTO_POINTER,
6424 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
6425 (GTK_TREE_MODEL(store)));
6426 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
6427 g_object_unref(store);
6429 renderer = gtk_cell_renderer_text_new();
6430 column = gtk_tree_view_column_new_with_attributes
6431 (_("Mime type"), renderer, "text",
6432 COL_MIMETYPE, NULL);
6433 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6435 renderer = gtk_cell_renderer_text_new();
6436 column = gtk_tree_view_column_new_with_attributes
6437 (_("Size"), renderer, "text",
6439 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6441 renderer = gtk_cell_renderer_text_new();
6442 column = gtk_tree_view_column_new_with_attributes
6443 (_("Name"), renderer, "text",
6445 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6447 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
6448 prefs_common.use_stripes_everywhere);
6449 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
6450 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
6452 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
6453 G_CALLBACK(attach_selected), compose);
6454 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
6455 G_CALLBACK(attach_button_pressed), compose);
6457 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
6458 G_CALLBACK(popup_attach_button_pressed), compose);
6460 gtk_widget_tap_and_hold_setup(GTK_WIDGET(attach_clist), NULL, NULL,
6461 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
6462 g_signal_connect(G_OBJECT(attach_clist), "tap-and-hold",
6463 G_CALLBACK(popup_attach_button_pressed), compose);
6465 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
6466 G_CALLBACK(attach_key_pressed), compose);
6469 gtk_drag_dest_set(attach_clist,
6470 GTK_DEST_DEFAULT_ALL, compose_mime_types,
6471 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6472 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6473 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
6474 G_CALLBACK(compose_attach_drag_received_cb),
6476 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
6477 G_CALLBACK(compose_drag_drop),
6480 compose->attach_scrwin = attach_scrwin;
6481 compose->attach_clist = attach_clist;
6483 return attach_scrwin;
6486 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
6487 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
6489 static GtkWidget *compose_create_others(Compose *compose)
6492 GtkWidget *savemsg_checkbtn;
6493 GtkWidget *savemsg_entry;
6494 GtkWidget *savemsg_select;
6497 gchar *folderidentifier;
6499 /* Table for settings */
6500 table = gtk_table_new(3, 1, FALSE);
6501 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
6502 gtk_widget_show(table);
6503 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
6506 /* Save Message to folder */
6507 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
6508 gtk_widget_show(savemsg_checkbtn);
6509 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6510 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6511 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
6513 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
6514 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
6516 savemsg_entry = gtk_entry_new();
6517 gtk_widget_show(savemsg_entry);
6518 gtk_table_attach_defaults(GTK_TABLE(table), savemsg_entry, 1, 2, rowcount, rowcount + 1);
6519 gtk_editable_set_editable(GTK_EDITABLE(savemsg_entry), prefs_common.savemsg);
6520 g_signal_connect_after(G_OBJECT(savemsg_entry), "grab_focus",
6521 G_CALLBACK(compose_grab_focus_cb), compose);
6522 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6523 folderidentifier = folder_item_get_identifier(account_get_special_folder
6524 (compose->account, F_OUTBOX));
6525 gtk_entry_set_text(GTK_ENTRY(savemsg_entry), folderidentifier);
6526 g_free(folderidentifier);
6529 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
6530 gtk_widget_show(savemsg_select);
6531 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6532 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
6533 G_CALLBACK(compose_savemsg_select_cb),
6538 compose->savemsg_checkbtn = savemsg_checkbtn;
6539 compose->savemsg_entry = savemsg_entry;
6544 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
6546 gtk_editable_set_editable(GTK_EDITABLE(compose->savemsg_entry),
6547 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
6550 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
6555 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE);
6558 path = folder_item_get_identifier(dest);
6560 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), path);
6564 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
6565 GdkAtom clip, GtkTextIter *insert_place);
6568 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
6572 GtkTextBuffer *buffer = GTK_TEXT_VIEW(text)->buffer;
6574 if (event->button == 3) {
6576 GtkTextIter sel_start, sel_end;
6577 gboolean stuff_selected;
6579 /* move the cursor to allow GtkAspell to check the word
6580 * under the mouse */
6581 if (event->x && event->y) {
6582 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6583 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6585 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6588 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
6589 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
6592 stuff_selected = gtk_text_buffer_get_selection_bounds(
6594 &sel_start, &sel_end);
6596 gtk_text_buffer_place_cursor (buffer, &iter);
6597 /* reselect stuff */
6599 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
6600 gtk_text_buffer_select_range(buffer,
6601 &sel_start, &sel_end);
6603 return FALSE; /* pass the event so that the right-click goes through */
6606 if (event->button == 2) {
6611 /* get the middle-click position to paste at the correct place */
6612 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6613 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6615 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6618 entry_paste_clipboard(compose, text,
6619 prefs_common.linewrap_pastes,
6620 GDK_SELECTION_PRIMARY, &iter);
6628 static void compose_spell_menu_changed(void *data)
6630 Compose *compose = (Compose *)data;
6632 GtkWidget *menuitem;
6633 GtkWidget *parent_item;
6634 GtkMenu *menu = GTK_MENU(gtk_menu_new());
6635 GtkItemFactory *ifactory = gtk_item_factory_from_widget(compose->menubar);
6638 if (compose->gtkaspell == NULL)
6641 parent_item = gtk_item_factory_get_item(ifactory,
6642 "/Spelling/Options");
6644 /* setting the submenu removes /Spelling/Options from the factory
6645 * so we need to save it */
6647 if (parent_item == NULL) {
6648 parent_item = compose->aspell_options_menu;
6649 gtk_menu_item_remove_submenu(GTK_MENU_ITEM(parent_item));
6651 compose->aspell_options_menu = parent_item;
6653 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
6655 spell_menu = g_slist_reverse(spell_menu);
6656 for (items = spell_menu;
6657 items; items = items->next) {
6658 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
6659 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
6660 gtk_widget_show(GTK_WIDGET(menuitem));
6662 g_slist_free(spell_menu);
6664 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
6669 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
6671 Compose *compose = (Compose *)data;
6672 GdkEventButton event;
6675 event.time = gtk_get_current_event_time();
6679 return text_clicked(compose->text, &event, compose);
6682 static gboolean compose_force_window_origin = TRUE;
6683 static Compose *compose_create(PrefsAccount *account,
6692 GtkWidget *handlebox;
6694 GtkWidget *notebook;
6696 GtkWidget *attach_hbox;
6697 GtkWidget *attach_lab1;
6698 GtkWidget *attach_lab2;
6703 GtkWidget *subject_hbox;
6704 GtkWidget *subject_frame;
6705 GtkWidget *subject_entry;
6709 GtkWidget *edit_vbox;
6710 GtkWidget *ruler_hbox;
6712 GtkWidget *scrolledwin;
6714 GtkTextBuffer *buffer;
6715 GtkClipboard *clipboard;
6717 UndoMain *undostruct;
6719 gchar *titles[N_ATTACH_COLS];
6720 guint n_menu_entries;
6721 GtkWidget *popupmenu;
6722 GtkItemFactory *popupfactory;
6723 GtkItemFactory *ifactory;
6724 GtkWidget *tmpl_menu;
6726 GtkWidget *menuitem;
6729 GtkAspell * gtkaspell = NULL;
6732 static GdkGeometry geometry;
6734 g_return_val_if_fail(account != NULL, NULL);
6736 debug_print("Creating compose window...\n");
6737 compose = g_new0(Compose, 1);
6739 titles[COL_MIMETYPE] = _("MIME type");
6740 titles[COL_SIZE] = _("Size");
6741 titles[COL_NAME] = _("Name");
6743 compose->batch = batch;
6744 compose->account = account;
6745 compose->folder = folder;
6747 compose->mutex = g_mutex_new();
6748 compose->set_cursor_pos = -1;
6750 compose->tooltips = gtk_tooltips_new();
6752 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
6754 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
6755 gtk_widget_set_size_request(window, -1, prefs_common.compose_height);
6757 if (!geometry.max_width) {
6758 geometry.max_width = gdk_screen_width();
6759 geometry.max_height = gdk_screen_height();
6762 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
6763 &geometry, GDK_HINT_MAX_SIZE);
6764 if (!geometry.min_width) {
6765 geometry.min_width = 600;
6766 geometry.min_height = 480;
6768 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
6769 &geometry, GDK_HINT_MIN_SIZE);
6772 if (compose_force_window_origin)
6773 gtk_widget_set_uposition(window, prefs_common.compose_x,
6774 prefs_common.compose_y);
6776 g_signal_connect(G_OBJECT(window), "delete_event",
6777 G_CALLBACK(compose_delete_cb), compose);
6778 MANAGE_WINDOW_SIGNALS_CONNECT(window);
6779 gtk_widget_realize(window);
6781 gtkut_widget_set_composer_icon(window);
6783 vbox = gtk_vbox_new(FALSE, 0);
6784 gtk_container_add(GTK_CONTAINER(window), vbox);
6786 n_menu_entries = sizeof(compose_entries) / sizeof(compose_entries[0]);
6787 menubar = menubar_create(window, compose_entries,
6788 n_menu_entries, "<Compose>", compose);
6789 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
6791 if (prefs_common.toolbar_detachable) {
6792 handlebox = gtk_handle_box_new();
6794 handlebox = gtk_hbox_new(FALSE, 0);
6796 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
6798 gtk_widget_realize(handlebox);
6800 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, window,
6803 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
6807 vbox2 = gtk_vbox_new(FALSE, 2);
6808 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
6809 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
6812 notebook = gtk_notebook_new();
6813 gtk_widget_set_size_request(notebook, -1, 130);
6814 gtk_widget_show(notebook);
6816 /* header labels and entries */
6817 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6818 compose_create_header(compose),
6819 gtk_label_new_with_mnemonic(_("Hea_der")));
6820 /* attachment list */
6821 attach_hbox = gtk_hbox_new(FALSE, 0);
6822 gtk_widget_show(attach_hbox);
6824 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
6825 gtk_widget_show(attach_lab1);
6826 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
6828 attach_lab2 = gtk_label_new("");
6829 gtk_widget_show(attach_lab2);
6830 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
6832 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6833 compose_create_attach(compose),
6836 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6837 compose_create_others(compose),
6838 gtk_label_new_with_mnemonic(_("Othe_rs")));
6841 subject_hbox = gtk_hbox_new(FALSE, 0);
6842 gtk_widget_show(subject_hbox);
6844 subject_frame = gtk_frame_new(NULL);
6845 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
6846 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
6847 gtk_widget_show(subject_frame);
6849 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
6850 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
6851 gtk_widget_show(subject);
6853 label = gtk_label_new(_("Subject:"));
6854 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
6855 gtk_widget_show(label);
6857 subject_entry = gtk_entry_new();
6858 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
6859 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
6860 G_CALLBACK(compose_grab_focus_cb), compose);
6861 gtk_widget_show(subject_entry);
6862 compose->subject_entry = subject_entry;
6863 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
6865 edit_vbox = gtk_vbox_new(FALSE, 0);
6867 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
6870 ruler_hbox = gtk_hbox_new(FALSE, 0);
6871 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
6873 ruler = gtk_shruler_new();
6874 gtk_ruler_set_range(GTK_RULER(ruler), 0.0, 100.0, 1.0, 100.0);
6875 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
6879 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
6880 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
6881 GTK_POLICY_AUTOMATIC,
6882 GTK_POLICY_AUTOMATIC);
6883 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
6885 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
6886 gtk_widget_set_size_request(scrolledwin, prefs_common.compose_width, -1);
6888 text = gtk_text_view_new();
6889 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
6890 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
6891 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
6892 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
6893 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
6895 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
6897 g_signal_connect_after(G_OBJECT(text), "size_allocate",
6898 G_CALLBACK(compose_edit_size_alloc),
6900 g_signal_connect(G_OBJECT(buffer), "changed",
6901 G_CALLBACK(compose_changed_cb), compose);
6902 g_signal_connect(G_OBJECT(text), "grab_focus",
6903 G_CALLBACK(compose_grab_focus_cb), compose);
6904 g_signal_connect(G_OBJECT(buffer), "insert_text",
6905 G_CALLBACK(text_inserted), compose);
6906 g_signal_connect(G_OBJECT(text), "button_press_event",
6907 G_CALLBACK(text_clicked), compose);
6909 g_signal_connect(G_OBJECT(text), "popup-menu",
6910 G_CALLBACK(compose_popup_menu), compose);
6912 gtk_widget_tap_and_hold_setup(GTK_WIDGET(text), NULL, NULL,
6913 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
6914 g_signal_connect(G_OBJECT(text), "tap-and-hold",
6915 G_CALLBACK(compose_popup_menu), compose);
6917 g_signal_connect(G_OBJECT(subject_entry), "changed",
6918 G_CALLBACK(compose_changed_cb), compose);
6921 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6922 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6923 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6924 g_signal_connect(G_OBJECT(text), "drag_data_received",
6925 G_CALLBACK(compose_insert_drag_received_cb),
6927 g_signal_connect(G_OBJECT(text), "drag-drop",
6928 G_CALLBACK(compose_drag_drop),
6930 gtk_widget_show_all(vbox);
6932 /* pane between attach clist and text */
6933 paned = gtk_vpaned_new();
6934 gtk_paned_set_gutter_size(GTK_PANED(paned), 12);
6935 gtk_container_add(GTK_CONTAINER(vbox2), paned);
6937 if( maemo_mainwindow_is_fullscreen(mainwindow_get_mainwindow()->window) )
6938 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 300 : 280);
6940 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 250 : 230);
6942 gtk_paned_add1(GTK_PANED(paned), notebook);
6943 gtk_paned_add2(GTK_PANED(paned), edit_vbox);
6944 gtk_widget_show_all(paned);
6947 if (prefs_common.textfont) {
6948 PangoFontDescription *font_desc;
6950 font_desc = pango_font_description_from_string
6951 (prefs_common.textfont);
6953 gtk_widget_modify_font(text, font_desc);
6954 pango_font_description_free(font_desc);
6958 n_entries = sizeof(compose_popup_entries) /
6959 sizeof(compose_popup_entries[0]);
6960 popupmenu = menu_create_items(compose_popup_entries, n_entries,
6961 "<Compose>", &popupfactory,
6964 ifactory = gtk_item_factory_from_widget(menubar);
6965 menu_set_sensitive(ifactory, "/Edit/Undo", FALSE);
6966 menu_set_sensitive(ifactory, "/Edit/Redo", FALSE);
6967 menu_set_sensitive(ifactory, "/Options/Remove references", FALSE);
6969 tmpl_menu = gtk_item_factory_get_item(ifactory, "/Tools/Template");
6971 undostruct = undo_init(text);
6972 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
6975 address_completion_start(window);
6977 compose->window = window;
6978 compose->vbox = vbox;
6979 compose->menubar = menubar;
6980 compose->handlebox = handlebox;
6982 compose->vbox2 = vbox2;
6984 compose->paned = paned;
6986 compose->attach_label = attach_lab2;
6988 compose->notebook = notebook;
6989 compose->edit_vbox = edit_vbox;
6990 compose->ruler_hbox = ruler_hbox;
6991 compose->ruler = ruler;
6992 compose->scrolledwin = scrolledwin;
6993 compose->text = text;
6995 compose->focused_editable = NULL;
6997 compose->popupmenu = popupmenu;
6998 compose->popupfactory = popupfactory;
7000 compose->tmpl_menu = tmpl_menu;
7002 compose->mode = mode;
7003 compose->rmode = mode;
7005 compose->targetinfo = NULL;
7006 compose->replyinfo = NULL;
7007 compose->fwdinfo = NULL;
7009 compose->replyto = NULL;
7011 compose->bcc = NULL;
7012 compose->followup_to = NULL;
7014 compose->ml_post = NULL;
7016 compose->inreplyto = NULL;
7017 compose->references = NULL;
7018 compose->msgid = NULL;
7019 compose->boundary = NULL;
7021 compose->autowrap = prefs_common.autowrap;
7023 compose->use_signing = FALSE;
7024 compose->use_encryption = FALSE;
7025 compose->privacy_system = NULL;
7027 compose->modified = FALSE;
7029 compose->return_receipt = FALSE;
7031 compose->to_list = NULL;
7032 compose->newsgroup_list = NULL;
7034 compose->undostruct = undostruct;
7036 compose->sig_str = NULL;
7038 compose->exteditor_file = NULL;
7039 compose->exteditor_pid = -1;
7040 compose->exteditor_tag = -1;
7041 compose->draft_timeout_tag = -2; /* inhibit auto-drafting while loading */
7044 menu_set_sensitive(ifactory, "/Spelling", FALSE);
7045 if (mode != COMPOSE_REDIRECT) {
7046 if (prefs_common.enable_aspell && prefs_common.dictionary &&
7047 strcmp(prefs_common.dictionary, "")) {
7048 gtkaspell = gtkaspell_new(prefs_common.aspell_path,
7049 prefs_common.dictionary,
7050 prefs_common.alt_dictionary,
7051 conv_get_locale_charset_str(),
7052 prefs_common.misspelled_col,
7053 prefs_common.check_while_typing,
7054 prefs_common.recheck_when_changing_dict,
7055 prefs_common.use_alternate,
7056 prefs_common.use_both_dicts,
7057 GTK_TEXT_VIEW(text),
7058 GTK_WINDOW(compose->window),
7059 compose_spell_menu_changed,
7062 alertpanel_error(_("Spell checker could not "
7064 gtkaspell_checkers_strerror());
7065 gtkaspell_checkers_reset_error();
7067 if (!gtkaspell_set_sug_mode(gtkaspell,
7068 prefs_common.aspell_sugmode)) {
7069 debug_print("Aspell: could not set "
7070 "suggestion mode %s\n",
7071 gtkaspell_checkers_strerror());
7072 gtkaspell_checkers_reset_error();
7075 menu_set_sensitive(ifactory, "/Spelling", TRUE);
7079 compose->gtkaspell = gtkaspell;
7080 compose_spell_menu_changed(compose);
7083 compose_select_account(compose, account, TRUE);
7085 menu_set_active(ifactory, "/Edit/Auto wrapping", prefs_common.autowrap);
7086 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
7087 compose_entry_append(compose, account->auto_cc, COMPOSE_CC);
7089 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
7090 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC);
7092 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
7093 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO);
7095 menu_set_sensitive(ifactory, "/Options/Reply mode", compose->mode == COMPOSE_REPLY);
7097 if (account->protocol != A_NNTP)
7098 gtk_entry_set_text(GTK_ENTRY(GTK_BIN(compose->header_last->combo)->child),
7099 prefs_common_translated_header_name("To:"));
7101 gtk_entry_set_text(GTK_ENTRY(GTK_BIN(compose->header_last->combo)->child),
7102 prefs_common_translated_header_name("Newsgroups:"));
7104 addressbook_set_target_compose(compose);
7106 if (mode != COMPOSE_REDIRECT)
7107 compose_set_template_menu(compose);
7109 menuitem = gtk_item_factory_get_item(ifactory, "/Tools/Template");
7110 menu_set_sensitive(ifactory, "/Tools/Template", FALSE);
7113 compose_list = g_list_append(compose_list, compose);
7115 if (!prefs_common.show_ruler)
7116 gtk_widget_hide(ruler_hbox);
7118 menuitem = gtk_item_factory_get_item(ifactory, "/Tools/Show ruler");
7119 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
7120 prefs_common.show_ruler);
7123 compose->priority = PRIORITY_NORMAL;
7124 compose_update_priority_menu_item(compose);
7126 compose_set_out_encoding(compose);
7129 compose_update_actions_menu(compose);
7131 /* Privacy Systems menu */
7132 compose_update_privacy_systems_menu(compose);
7134 activate_privacy_system(compose, account, TRUE);
7135 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
7137 gtk_widget_realize(window);
7139 gtk_widget_show(window);
7141 maemo_window_full_screen_if_needed(GTK_WINDOW(window));
7142 maemo_connect_key_press_to_mainwindow(GTK_WINDOW(window));
7149 static GtkWidget *compose_account_option_menu_create(Compose *compose)
7154 GtkWidget *optmenubox;
7157 GtkWidget *from_name = NULL;
7159 gint num = 0, def_menu = 0;
7161 accounts = account_get_list();
7162 g_return_val_if_fail(accounts != NULL, NULL);
7164 optmenubox = gtk_event_box_new();
7165 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
7166 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
7168 hbox = gtk_hbox_new(FALSE, 6);
7169 from_name = gtk_entry_new();
7171 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
7172 G_CALLBACK(compose_grab_focus_cb), compose);
7174 for (; accounts != NULL; accounts = accounts->next, num++) {
7175 PrefsAccount *ac = (PrefsAccount *)accounts->data;
7176 gchar *name, *from = NULL;
7178 if (ac == compose->account) def_menu = num;
7180 name = g_markup_printf_escaped(_("From: <i>%s</i>"),
7183 if (ac == compose->account) {
7184 if (ac->name && *ac->name) {
7186 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
7187 from = g_strdup_printf("%s <%s>",
7189 gtk_entry_set_text(GTK_ENTRY(from_name), from);
7191 from = g_strdup_printf("%s",
7193 gtk_entry_set_text(GTK_ENTRY(from_name), from);
7196 COMBOBOX_ADD(menu, name, ac->account_id);
7201 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
7203 g_signal_connect(G_OBJECT(optmenu), "changed",
7204 G_CALLBACK(account_activated),
7206 g_signal_connect(G_OBJECT(from_name), "populate-popup",
7207 G_CALLBACK(compose_entry_popup_extend),
7210 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
7211 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
7213 gtk_tooltips_set_tip(compose->tooltips, optmenubox,
7214 _("Account to use for this email"), NULL);
7215 gtk_tooltips_set_tip(compose->tooltips, from_name,
7216 _("Sender address to be used"), NULL);
7218 compose->from_name = from_name;
7223 static void compose_set_priority_cb(gpointer data,
7227 Compose *compose = (Compose *) data;
7228 compose->priority = action;
7231 static void compose_reply_change_mode(gpointer data,
7235 Compose *compose = (Compose *) data;
7236 gboolean was_modified = compose->modified;
7238 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
7240 g_return_if_fail(compose->replyinfo != NULL);
7242 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
7244 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
7246 if (action == COMPOSE_REPLY_TO_ALL)
7248 if (action == COMPOSE_REPLY_TO_SENDER)
7250 if (action == COMPOSE_REPLY_TO_LIST)
7253 compose_remove_header_entries(compose);
7254 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
7255 if (compose->account->set_autocc && compose->account->auto_cc)
7256 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC);
7258 if (compose->account->set_autobcc && compose->account->auto_bcc)
7259 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC);
7261 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
7262 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO);
7263 compose_show_first_last_header(compose, TRUE);
7264 compose->modified = was_modified;
7265 compose_set_title(compose);
7268 static void compose_update_priority_menu_item(Compose * compose)
7270 GtkItemFactory *ifactory;
7271 GtkWidget *menuitem = NULL;
7273 ifactory = gtk_item_factory_from_widget(compose->menubar);
7275 switch (compose->priority) {
7276 case PRIORITY_HIGHEST:
7277 menuitem = gtk_item_factory_get_item
7278 (ifactory, "/Options/Priority/Highest");
7281 menuitem = gtk_item_factory_get_item
7282 (ifactory, "/Options/Priority/High");
7284 case PRIORITY_NORMAL:
7285 menuitem = gtk_item_factory_get_item
7286 (ifactory, "/Options/Priority/Normal");
7289 menuitem = gtk_item_factory_get_item
7290 (ifactory, "/Options/Priority/Low");
7292 case PRIORITY_LOWEST:
7293 menuitem = gtk_item_factory_get_item
7294 (ifactory, "/Options/Priority/Lowest");
7297 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
7300 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
7302 Compose *compose = (Compose *) data;
7304 GtkItemFactory *ifactory;
7305 gboolean can_sign = FALSE, can_encrypt = FALSE;
7307 g_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
7309 if (!GTK_CHECK_MENU_ITEM(widget)->active)
7312 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
7313 g_free(compose->privacy_system);
7314 compose->privacy_system = NULL;
7315 if (systemid != NULL) {
7316 compose->privacy_system = g_strdup(systemid);
7318 can_sign = privacy_system_can_sign(systemid);
7319 can_encrypt = privacy_system_can_encrypt(systemid);
7322 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
7324 ifactory = gtk_item_factory_from_widget(compose->menubar);
7325 menu_set_sensitive(ifactory, "/Options/Sign", can_sign);
7326 menu_set_sensitive(ifactory, "/Options/Encrypt", can_encrypt);
7329 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
7331 static gchar *branch_path = "/Options/Privacy System";
7332 GtkItemFactory *ifactory;
7333 GtkWidget *menuitem = NULL;
7335 gboolean can_sign = FALSE, can_encrypt = FALSE;
7336 gboolean found = FALSE;
7338 ifactory = gtk_item_factory_from_widget(compose->menubar);
7340 if (compose->privacy_system != NULL) {
7342 menuitem = gtk_item_factory_get_widget(ifactory, branch_path);
7343 g_return_if_fail(menuitem != NULL);
7345 amenu = GTK_MENU_SHELL(menuitem)->children;
7347 while (amenu != NULL) {
7348 GList *alist = amenu->next;
7350 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
7351 if (systemid != NULL) {
7352 if (strcmp(systemid, compose->privacy_system) == 0) {
7353 menuitem = GTK_WIDGET(amenu->data);
7355 can_sign = privacy_system_can_sign(systemid);
7356 can_encrypt = privacy_system_can_encrypt(systemid);
7360 } else if (strlen(compose->privacy_system) == 0) {
7361 menuitem = GTK_WIDGET(amenu->data);
7364 can_encrypt = FALSE;
7371 if (menuitem != NULL)
7372 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
7374 if (warn && !found && strlen(compose->privacy_system)) {
7375 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
7376 "will not be able to sign or encrypt this message."),
7377 compose->privacy_system);
7381 menu_set_sensitive(ifactory, "/Options/Sign", can_sign);
7382 menu_set_sensitive(ifactory, "/Options/Encrypt", can_encrypt);
7385 static void compose_set_out_encoding(Compose *compose)
7387 GtkItemFactoryEntry *entry;
7388 GtkItemFactory *ifactory;
7389 CharSet out_encoding;
7390 gchar *path, *p, *q;
7393 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
7394 ifactory = gtk_item_factory_from_widget(compose->menubar);
7396 for (entry = compose_entries; entry->callback != compose_address_cb;
7398 if (entry->callback == compose_set_encoding_cb &&
7399 (CharSet)entry->callback_action == out_encoding) {
7400 p = q = path = g_strdup(entry->path);
7412 item = gtk_item_factory_get_item(ifactory, path);
7413 gtk_widget_activate(item);
7420 static void compose_set_template_menu(Compose *compose)
7422 GSList *tmpl_list, *cur;
7426 tmpl_list = template_get_config();
7428 menu = gtk_menu_new();
7430 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
7431 Template *tmpl = (Template *)cur->data;
7433 item = gtk_menu_item_new_with_label(tmpl->name);
7434 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
7435 g_signal_connect(G_OBJECT(item), "activate",
7436 G_CALLBACK(compose_template_activate_cb),
7438 g_object_set_data(G_OBJECT(item), "template", tmpl);
7439 gtk_widget_show(item);
7442 gtk_widget_show(menu);
7443 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
7446 void compose_update_actions_menu(Compose *compose)
7448 GtkItemFactory *ifactory;
7450 ifactory = gtk_item_factory_from_widget(compose->menubar);
7451 action_update_compose_menu(ifactory, "/Tools/Actions", compose);
7454 static void compose_update_privacy_systems_menu(Compose *compose)
7456 static gchar *branch_path = "/Options/Privacy System";
7457 GtkItemFactory *ifactory;
7458 GtkWidget *menuitem;
7459 GSList *systems, *cur;
7462 GtkWidget *system_none;
7465 ifactory = gtk_item_factory_from_widget(compose->menubar);
7467 /* remove old entries */
7468 menuitem = gtk_item_factory_get_widget(ifactory, branch_path);
7469 g_return_if_fail(menuitem != NULL);
7471 amenu = GTK_MENU_SHELL(menuitem)->children->next;
7472 while (amenu != NULL) {
7473 GList *alist = amenu->next;
7474 gtk_widget_destroy(GTK_WIDGET(amenu->data));
7478 system_none = gtk_item_factory_get_widget(ifactory,
7479 "/Options/Privacy System/None");
7481 g_signal_connect(G_OBJECT(system_none), "activate",
7482 G_CALLBACK(compose_set_privacy_system_cb), compose);
7484 systems = privacy_get_system_ids();
7485 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
7486 gchar *systemid = cur->data;
7488 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
7489 widget = gtk_radio_menu_item_new_with_label(group,
7490 privacy_system_get_name(systemid));
7491 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
7492 g_strdup(systemid), g_free);
7493 g_signal_connect(G_OBJECT(widget), "activate",
7494 G_CALLBACK(compose_set_privacy_system_cb), compose);
7496 gtk_menu_append(GTK_MENU(system_none->parent), widget);
7497 gtk_widget_show(widget);
7500 g_slist_free(systems);
7503 void compose_reflect_prefs_all(void)
7508 for (cur = compose_list; cur != NULL; cur = cur->next) {
7509 compose = (Compose *)cur->data;
7510 compose_set_template_menu(compose);
7514 void compose_reflect_prefs_pixmap_theme(void)
7519 for (cur = compose_list; cur != NULL; cur = cur->next) {
7520 compose = (Compose *)cur->data;
7521 toolbar_update(TOOLBAR_COMPOSE, compose);
7525 static const gchar *compose_quote_char_from_context(Compose *compose)
7527 const gchar *qmark = NULL;
7529 g_return_val_if_fail(compose != NULL, NULL);
7531 switch (compose->mode) {
7532 /* use forward-specific quote char */
7533 case COMPOSE_FORWARD:
7534 case COMPOSE_FORWARD_AS_ATTACH:
7535 case COMPOSE_FORWARD_INLINE:
7536 if (compose->folder && compose->folder->prefs &&
7537 compose->folder->prefs->forward_with_format)
7538 qmark = compose->folder->prefs->forward_quotemark;
7539 else if (compose->account->forward_with_format)
7540 qmark = compose->account->forward_quotemark;
7542 qmark = prefs_common.fw_quotemark;
7545 /* use reply-specific quote char in all other modes */
7547 if (compose->folder && compose->folder->prefs &&
7548 compose->folder->prefs->reply_with_format)
7549 qmark = compose->folder->prefs->reply_quotemark;
7550 else if (compose->account->reply_with_format)
7551 qmark = compose->account->reply_quotemark;
7553 qmark = prefs_common.quotemark;
7557 if (qmark == NULL || *qmark == '\0')
7563 static void compose_template_apply(Compose *compose, Template *tmpl,
7567 GtkTextBuffer *buffer;
7571 gchar *parsed_str = NULL;
7572 gint cursor_pos = 0;
7573 const gchar *err_msg = _("Template body format error at line %d.");
7576 /* process the body */
7578 text = GTK_TEXT_VIEW(compose->text);
7579 buffer = gtk_text_view_get_buffer(text);
7582 qmark = compose_quote_char_from_context(compose);
7584 if (compose->replyinfo != NULL) {
7587 gtk_text_buffer_set_text(buffer, "", -1);
7588 mark = gtk_text_buffer_get_insert(buffer);
7589 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7591 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
7592 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
7594 } else if (compose->fwdinfo != NULL) {
7597 gtk_text_buffer_set_text(buffer, "", -1);
7598 mark = gtk_text_buffer_get_insert(buffer);
7599 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7601 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
7602 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
7605 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
7607 GtkTextIter start, end;
7610 gtk_text_buffer_get_start_iter(buffer, &start);
7611 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
7612 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
7614 /* clear the buffer now */
7616 gtk_text_buffer_set_text(buffer, "", -1);
7618 parsed_str = compose_quote_fmt(compose, dummyinfo,
7619 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
7620 procmsg_msginfo_free( dummyinfo );
7626 gtk_text_buffer_set_text(buffer, "", -1);
7627 mark = gtk_text_buffer_get_insert(buffer);
7628 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7631 if (replace && parsed_str && compose->account->auto_sig)
7632 compose_insert_sig(compose, FALSE);
7634 if (replace && parsed_str) {
7635 gtk_text_buffer_get_start_iter(buffer, &iter);
7636 gtk_text_buffer_place_cursor(buffer, &iter);
7640 cursor_pos = quote_fmt_get_cursor_pos();
7641 compose->set_cursor_pos = cursor_pos;
7642 if (cursor_pos == -1)
7644 gtk_text_buffer_get_start_iter(buffer, &iter);
7645 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
7646 gtk_text_buffer_place_cursor(buffer, &iter);
7649 /* process the other fields */
7651 compose_template_apply_fields(compose, tmpl);
7652 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
7653 quote_fmt_reset_vartable();
7654 compose_changed_cb(NULL, compose);
7657 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
7659 MsgInfo* dummyinfo = NULL;
7660 MsgInfo *msginfo = NULL;
7663 if (compose->replyinfo != NULL)
7664 msginfo = compose->replyinfo;
7665 else if (compose->fwdinfo != NULL)
7666 msginfo = compose->fwdinfo;
7668 dummyinfo = compose_msginfo_new_from_compose(compose);
7669 msginfo = dummyinfo;
7672 if (tmpl->from && *tmpl->from != '\0') {
7674 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
7675 compose->gtkaspell);
7677 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
7679 quote_fmt_scan_string(tmpl->from);
7682 buf = quote_fmt_get_buffer();
7684 alertpanel_error(_("Template From format error."));
7686 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
7690 if (tmpl->to && *tmpl->to != '\0') {
7692 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
7693 compose->gtkaspell);
7695 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
7697 quote_fmt_scan_string(tmpl->to);
7700 buf = quote_fmt_get_buffer();
7702 alertpanel_error(_("Template To format error."));
7704 compose_entry_append(compose, buf, COMPOSE_TO);
7708 if (tmpl->cc && *tmpl->cc != '\0') {
7710 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
7711 compose->gtkaspell);
7713 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
7715 quote_fmt_scan_string(tmpl->cc);
7718 buf = quote_fmt_get_buffer();
7720 alertpanel_error(_("Template Cc format error."));
7722 compose_entry_append(compose, buf, COMPOSE_CC);
7726 if (tmpl->bcc && *tmpl->bcc != '\0') {
7728 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
7729 compose->gtkaspell);
7731 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
7733 quote_fmt_scan_string(tmpl->bcc);
7736 buf = quote_fmt_get_buffer();
7738 alertpanel_error(_("Template Bcc format error."));
7740 compose_entry_append(compose, buf, COMPOSE_BCC);
7744 /* process the subject */
7745 if (tmpl->subject && *tmpl->subject != '\0') {
7747 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
7748 compose->gtkaspell);
7750 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
7752 quote_fmt_scan_string(tmpl->subject);
7755 buf = quote_fmt_get_buffer();
7757 alertpanel_error(_("Template subject format error."));
7759 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
7763 procmsg_msginfo_free( dummyinfo );
7766 static void compose_destroy(Compose *compose)
7768 GtkTextBuffer *buffer;
7769 GtkClipboard *clipboard;
7771 compose_list = g_list_remove(compose_list, compose);
7773 if (compose->updating) {
7774 debug_print("danger, not destroying anything now\n");
7775 compose->deferred_destroy = TRUE;
7778 /* NOTE: address_completion_end() does nothing with the window
7779 * however this may change. */
7780 address_completion_end(compose->window);
7782 slist_free_strings(compose->to_list);
7783 g_slist_free(compose->to_list);
7784 slist_free_strings(compose->newsgroup_list);
7785 g_slist_free(compose->newsgroup_list);
7786 slist_free_strings(compose->header_list);
7787 g_slist_free(compose->header_list);
7789 procmsg_msginfo_free(compose->targetinfo);
7790 procmsg_msginfo_free(compose->replyinfo);
7791 procmsg_msginfo_free(compose->fwdinfo);
7793 g_free(compose->replyto);
7794 g_free(compose->cc);
7795 g_free(compose->bcc);
7796 g_free(compose->newsgroups);
7797 g_free(compose->followup_to);
7799 g_free(compose->ml_post);
7801 g_free(compose->inreplyto);
7802 g_free(compose->references);
7803 g_free(compose->msgid);
7804 g_free(compose->boundary);
7806 g_free(compose->redirect_filename);
7807 if (compose->undostruct)
7808 undo_destroy(compose->undostruct);
7810 g_free(compose->sig_str);
7812 g_free(compose->exteditor_file);
7814 g_free(compose->orig_charset);
7816 g_free(compose->privacy_system);
7818 if (addressbook_get_target_compose() == compose)
7819 addressbook_set_target_compose(NULL);
7822 if (compose->gtkaspell) {
7823 gtkaspell_delete(compose->gtkaspell);
7824 compose->gtkaspell = NULL;
7828 prefs_common.compose_width = compose->scrolledwin->allocation.width;
7829 prefs_common.compose_height = compose->window->allocation.height;
7831 if (!gtk_widget_get_parent(compose->paned))
7832 gtk_widget_destroy(compose->paned);
7833 gtk_widget_destroy(compose->popupmenu);
7835 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
7836 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7837 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
7839 gtk_widget_destroy(compose->window);
7840 toolbar_destroy(compose->toolbar);
7841 g_free(compose->toolbar);
7842 g_mutex_free(compose->mutex);
7846 static void compose_attach_info_free(AttachInfo *ainfo)
7848 g_free(ainfo->file);
7849 g_free(ainfo->content_type);
7850 g_free(ainfo->name);
7854 static void compose_attach_update_label(Compose *compose)
7859 GtkTreeModel *model;
7864 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
7865 if(!gtk_tree_model_get_iter_first(model, &iter)) {
7866 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
7870 while(gtk_tree_model_iter_next(model, &iter))
7873 text = g_strdup_printf("(%d)", i);
7874 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
7878 static void compose_attach_remove_selected(Compose *compose)
7880 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
7881 GtkTreeSelection *selection;
7883 GtkTreeModel *model;
7885 selection = gtk_tree_view_get_selection(tree_view);
7886 sel = gtk_tree_selection_get_selected_rows(selection, &model);
7891 for (cur = sel; cur != NULL; cur = cur->next) {
7892 GtkTreePath *path = cur->data;
7893 GtkTreeRowReference *ref = gtk_tree_row_reference_new
7896 gtk_tree_path_free(path);
7899 for (cur = sel; cur != NULL; cur = cur->next) {
7900 GtkTreeRowReference *ref = cur->data;
7901 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
7904 if (gtk_tree_model_get_iter(model, &iter, path))
7905 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
7907 gtk_tree_path_free(path);
7908 gtk_tree_row_reference_free(ref);
7912 compose_attach_update_label(compose);
7915 static struct _AttachProperty
7918 GtkWidget *mimetype_entry;
7919 GtkWidget *encoding_optmenu;
7920 GtkWidget *path_entry;
7921 GtkWidget *filename_entry;
7923 GtkWidget *cancel_btn;
7926 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
7928 gtk_tree_path_free((GtkTreePath *)ptr);
7931 static void compose_attach_property(Compose *compose)
7933 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
7935 GtkComboBox *optmenu;
7936 GtkTreeSelection *selection;
7938 GtkTreeModel *model;
7941 static gboolean cancelled;
7943 /* only if one selected */
7944 selection = gtk_tree_view_get_selection(tree_view);
7945 if (gtk_tree_selection_count_selected_rows(selection) != 1)
7948 sel = gtk_tree_selection_get_selected_rows(selection, &model);
7952 path = (GtkTreePath *) sel->data;
7953 gtk_tree_model_get_iter(model, &iter, path);
7954 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
7957 g_list_foreach(sel, gtk_tree_path_free_, NULL);
7963 if (!attach_prop.window)
7964 compose_attach_property_create(&cancelled);
7965 gtk_widget_grab_focus(attach_prop.ok_btn);
7966 gtk_widget_show(attach_prop.window);
7967 manage_window_set_transient(GTK_WINDOW(attach_prop.window));
7969 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
7970 if (ainfo->encoding == ENC_UNKNOWN)
7971 combobox_select_by_data(optmenu, ENC_BASE64);
7973 combobox_select_by_data(optmenu, ainfo->encoding);
7975 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
7976 ainfo->content_type ? ainfo->content_type : "");
7977 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
7978 ainfo->file ? ainfo->file : "");
7979 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
7980 ainfo->name ? ainfo->name : "");
7983 const gchar *entry_text;
7985 gchar *cnttype = NULL;
7992 gtk_widget_hide(attach_prop.window);
7997 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
7998 if (*entry_text != '\0') {
8001 text = g_strstrip(g_strdup(entry_text));
8002 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
8003 cnttype = g_strdup(text);
8006 alertpanel_error(_("Invalid MIME type."));
8012 ainfo->encoding = combobox_get_active_data(optmenu);
8014 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
8015 if (*entry_text != '\0') {
8016 if (is_file_exist(entry_text) &&
8017 (size = get_file_size(entry_text)) > 0)
8018 file = g_strdup(entry_text);
8021 (_("File doesn't exist or is empty."));
8027 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
8028 if (*entry_text != '\0') {
8029 g_free(ainfo->name);
8030 ainfo->name = g_strdup(entry_text);
8034 g_free(ainfo->content_type);
8035 ainfo->content_type = cnttype;
8038 g_free(ainfo->file);
8044 /* update tree store */
8045 text = to_human_readable(ainfo->size);
8046 gtk_tree_model_get_iter(model, &iter, path);
8047 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
8048 COL_MIMETYPE, ainfo->content_type,
8050 COL_NAME, ainfo->name,
8056 gtk_tree_path_free(path);
8059 #define SET_LABEL_AND_ENTRY(str, entry, top) \
8061 label = gtk_label_new(str); \
8062 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
8063 GTK_FILL, 0, 0, 0); \
8064 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
8066 entry = gtk_entry_new(); \
8067 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
8068 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
8071 static void compose_attach_property_create(gboolean *cancelled)
8077 GtkWidget *mimetype_entry;
8080 GtkListStore *optmenu_menu;
8081 GtkWidget *path_entry;
8082 GtkWidget *filename_entry;
8085 GtkWidget *cancel_btn;
8086 GList *mime_type_list, *strlist;
8089 debug_print("Creating attach_property window...\n");
8091 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
8092 gtk_widget_set_size_request(window, 480, -1);
8093 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
8094 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
8095 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
8096 gtk_window_set_modal(GTK_WINDOW(window), TRUE);
8097 g_signal_connect(G_OBJECT(window), "delete_event",
8098 G_CALLBACK(attach_property_delete_event),
8100 g_signal_connect(G_OBJECT(window), "key_press_event",
8101 G_CALLBACK(attach_property_key_pressed),
8104 vbox = gtk_vbox_new(FALSE, 8);
8105 gtk_container_add(GTK_CONTAINER(window), vbox);
8107 table = gtk_table_new(4, 2, FALSE);
8108 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
8109 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
8110 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
8112 label = gtk_label_new(_("MIME type"));
8113 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
8115 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
8116 mimetype_entry = gtk_combo_box_entry_new_text();
8117 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
8118 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
8120 /* stuff with list */
8121 mime_type_list = procmime_get_mime_type_list();
8123 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
8124 MimeType *type = (MimeType *) mime_type_list->data;
8127 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
8129 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
8132 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
8133 (GCompareFunc)strcmp2);
8136 for (mime_type_list = strlist; mime_type_list != NULL;
8137 mime_type_list = mime_type_list->next) {
8138 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
8139 g_free(mime_type_list->data);
8141 g_list_free(strlist);
8142 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
8143 mimetype_entry = GTK_BIN(mimetype_entry)->child;
8145 label = gtk_label_new(_("Encoding"));
8146 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
8148 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
8150 hbox = gtk_hbox_new(FALSE, 0);
8151 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
8152 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
8154 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
8155 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8157 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
8158 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
8159 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
8160 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
8161 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
8163 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
8165 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
8166 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
8168 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
8169 &ok_btn, GTK_STOCK_OK,
8171 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
8172 gtk_widget_grab_default(ok_btn);
8174 g_signal_connect(G_OBJECT(ok_btn), "clicked",
8175 G_CALLBACK(attach_property_ok),
8177 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
8178 G_CALLBACK(attach_property_cancel),
8181 gtk_widget_show_all(vbox);
8183 attach_prop.window = window;
8184 attach_prop.mimetype_entry = mimetype_entry;
8185 attach_prop.encoding_optmenu = optmenu;
8186 attach_prop.path_entry = path_entry;
8187 attach_prop.filename_entry = filename_entry;
8188 attach_prop.ok_btn = ok_btn;
8189 attach_prop.cancel_btn = cancel_btn;
8192 #undef SET_LABEL_AND_ENTRY
8194 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
8200 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
8206 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
8207 gboolean *cancelled)
8215 static gboolean attach_property_key_pressed(GtkWidget *widget,
8217 gboolean *cancelled)
8219 if (event && event->keyval == GDK_Escape) {
8226 static void compose_exec_ext_editor(Compose *compose)
8233 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
8234 G_DIR_SEPARATOR, compose);
8236 if (pipe(pipe_fds) < 0) {
8242 if ((pid = fork()) < 0) {
8249 /* close the write side of the pipe */
8252 compose->exteditor_file = g_strdup(tmp);
8253 compose->exteditor_pid = pid;
8255 compose_set_ext_editor_sensitive(compose, FALSE);
8257 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
8258 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
8262 } else { /* process-monitoring process */
8268 /* close the read side of the pipe */
8271 if (compose_write_body_to_file(compose, tmp) < 0) {
8272 fd_write_all(pipe_fds[1], "2\n", 2);
8276 pid_ed = compose_exec_ext_editor_real(tmp);
8278 fd_write_all(pipe_fds[1], "1\n", 2);
8282 /* wait until editor is terminated */
8283 waitpid(pid_ed, NULL, 0);
8285 fd_write_all(pipe_fds[1], "0\n", 2);
8292 #endif /* G_OS_UNIX */
8296 static gint compose_exec_ext_editor_real(const gchar *file)
8303 g_return_val_if_fail(file != NULL, -1);
8305 if ((pid = fork()) < 0) {
8310 if (pid != 0) return pid;
8312 /* grandchild process */
8314 if (setpgid(0, getppid()))
8317 if (prefs_common_get_ext_editor_cmd() &&
8318 (p = strchr(prefs_common_get_ext_editor_cmd(), '%')) &&
8319 *(p + 1) == 's' && !strchr(p + 2, '%')) {
8320 g_snprintf(buf, sizeof(buf), prefs_common_get_ext_editor_cmd(), file);
8322 if (prefs_common_get_ext_editor_cmd())
8323 g_warning("External editor command line is invalid: '%s'\n",
8324 prefs_common_get_ext_editor_cmd());
8325 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
8328 cmdline = strsplit_with_quote(buf, " ", 1024);
8329 execvp(cmdline[0], cmdline);
8332 g_strfreev(cmdline);
8337 static gboolean compose_ext_editor_kill(Compose *compose)
8339 pid_t pgid = compose->exteditor_pid * -1;
8342 ret = kill(pgid, 0);
8344 if (ret == 0 || (ret == -1 && EPERM == errno)) {
8348 msg = g_strdup_printf
8349 (_("The external editor is still working.\n"
8350 "Force terminating the process?\n"
8351 "process group id: %d"), -pgid);
8352 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
8353 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
8357 if (val == G_ALERTALTERNATE) {
8358 g_source_remove(compose->exteditor_tag);
8359 g_io_channel_shutdown(compose->exteditor_ch,
8361 g_io_channel_unref(compose->exteditor_ch);
8363 if (kill(pgid, SIGTERM) < 0) perror("kill");
8364 waitpid(compose->exteditor_pid, NULL, 0);
8366 g_warning("Terminated process group id: %d", -pgid);
8367 g_warning("Temporary file: %s",
8368 compose->exteditor_file);
8370 compose_set_ext_editor_sensitive(compose, TRUE);
8372 g_free(compose->exteditor_file);
8373 compose->exteditor_file = NULL;
8374 compose->exteditor_pid = -1;
8375 compose->exteditor_ch = NULL;
8376 compose->exteditor_tag = -1;
8384 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
8388 Compose *compose = (Compose *)data;
8391 debug_print(_("Compose: input from monitoring process\n"));
8393 g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL);
8395 g_io_channel_shutdown(source, FALSE, NULL);
8396 g_io_channel_unref(source);
8398 waitpid(compose->exteditor_pid, NULL, 0);
8400 if (buf[0] == '0') { /* success */
8401 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
8402 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
8404 gtk_text_buffer_set_text(buffer, "", -1);
8405 compose_insert_file(compose, compose->exteditor_file);
8406 compose_changed_cb(NULL, compose);
8408 if (g_unlink(compose->exteditor_file) < 0)
8409 FILE_OP_ERROR(compose->exteditor_file, "unlink");
8410 } else if (buf[0] == '1') { /* failed */
8411 g_warning("Couldn't exec external editor\n");
8412 if (g_unlink(compose->exteditor_file) < 0)
8413 FILE_OP_ERROR(compose->exteditor_file, "unlink");
8414 } else if (buf[0] == '2') {
8415 g_warning("Couldn't write to file\n");
8416 } else if (buf[0] == '3') {
8417 g_warning("Pipe read failed\n");
8420 compose_set_ext_editor_sensitive(compose, TRUE);
8422 g_free(compose->exteditor_file);
8423 compose->exteditor_file = NULL;
8424 compose->exteditor_pid = -1;
8425 compose->exteditor_ch = NULL;
8426 compose->exteditor_tag = -1;
8431 static void compose_set_ext_editor_sensitive(Compose *compose,
8434 GtkItemFactory *ifactory;
8436 ifactory = gtk_item_factory_from_widget(compose->menubar);
8438 menu_set_sensitive(ifactory, "/Message/Send", sensitive);
8439 menu_set_sensitive(ifactory, "/Message/Send later", sensitive);
8440 menu_set_sensitive(ifactory, "/Message/Insert file", sensitive);
8441 menu_set_sensitive(ifactory, "/Message/Insert signature", sensitive);
8442 menu_set_sensitive(ifactory, "/Edit/Wrap current paragraph", sensitive);
8443 menu_set_sensitive(ifactory, "/Edit/Wrap all long lines", sensitive);
8444 menu_set_sensitive(ifactory, "/Edit/Edit with external editor",
8447 gtk_widget_set_sensitive(compose->text, sensitive);
8448 if (compose->toolbar->send_btn)
8449 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
8450 if (compose->toolbar->sendl_btn)
8451 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
8452 if (compose->toolbar->draft_btn)
8453 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
8454 if (compose->toolbar->insert_btn)
8455 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
8456 if (compose->toolbar->sig_btn)
8457 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
8458 if (compose->toolbar->exteditor_btn)
8459 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
8460 if (compose->toolbar->linewrap_current_btn)
8461 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
8462 if (compose->toolbar->linewrap_all_btn)
8463 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
8465 #endif /* G_OS_UNIX */
8468 * compose_undo_state_changed:
8470 * Change the sensivity of the menuentries undo and redo
8472 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
8473 gint redo_state, gpointer data)
8475 GtkWidget *widget = GTK_WIDGET(data);
8476 GtkItemFactory *ifactory;
8478 g_return_if_fail(widget != NULL);
8480 ifactory = gtk_item_factory_from_widget(widget);
8482 switch (undo_state) {
8483 case UNDO_STATE_TRUE:
8484 if (!undostruct->undo_state) {
8485 undostruct->undo_state = TRUE;
8486 menu_set_sensitive(ifactory, "/Edit/Undo", TRUE);
8489 case UNDO_STATE_FALSE:
8490 if (undostruct->undo_state) {
8491 undostruct->undo_state = FALSE;
8492 menu_set_sensitive(ifactory, "/Edit/Undo", FALSE);
8495 case UNDO_STATE_UNCHANGED:
8497 case UNDO_STATE_REFRESH:
8498 menu_set_sensitive(ifactory, "/Edit/Undo",
8499 undostruct->undo_state);
8502 g_warning("Undo state not recognized");
8506 switch (redo_state) {
8507 case UNDO_STATE_TRUE:
8508 if (!undostruct->redo_state) {
8509 undostruct->redo_state = TRUE;
8510 menu_set_sensitive(ifactory, "/Edit/Redo", TRUE);
8513 case UNDO_STATE_FALSE:
8514 if (undostruct->redo_state) {
8515 undostruct->redo_state = FALSE;
8516 menu_set_sensitive(ifactory, "/Edit/Redo", FALSE);
8519 case UNDO_STATE_UNCHANGED:
8521 case UNDO_STATE_REFRESH:
8522 menu_set_sensitive(ifactory, "/Edit/Redo",
8523 undostruct->redo_state);
8526 g_warning("Redo state not recognized");
8531 /* callback functions */
8533 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
8534 * includes "non-client" (windows-izm) in calculation, so this calculation
8535 * may not be accurate.
8537 static gboolean compose_edit_size_alloc(GtkEditable *widget,
8538 GtkAllocation *allocation,
8539 GtkSHRuler *shruler)
8541 if (prefs_common.show_ruler) {
8542 gint char_width = 0, char_height = 0;
8543 gint line_width_in_chars;
8545 gtkut_get_font_size(GTK_WIDGET(widget),
8546 &char_width, &char_height);
8547 line_width_in_chars =
8548 (allocation->width - allocation->x) / char_width;
8550 /* got the maximum */
8551 gtk_ruler_set_range(GTK_RULER(shruler),
8552 0.0, line_width_in_chars, 0,
8553 /*line_width_in_chars*/ char_width);
8559 static void account_activated(GtkComboBox *optmenu, gpointer data)
8561 Compose *compose = (Compose *)data;
8564 gchar *folderidentifier;
8565 gint account_id = 0;
8569 /* Get ID of active account in the combo box */
8570 menu = gtk_combo_box_get_model(optmenu);
8571 gtk_combo_box_get_active_iter(optmenu, &iter);
8572 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
8574 ac = account_find_from_id(account_id);
8575 g_return_if_fail(ac != NULL);
8577 if (ac != compose->account)
8578 compose_select_account(compose, ac, FALSE);
8580 /* Set message save folder */
8581 if (account_get_special_folder(compose->account, F_OUTBOX)) {
8582 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
8584 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
8585 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
8587 gtk_editable_delete_text(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
8588 if (account_get_special_folder(compose->account, F_OUTBOX)) {
8589 folderidentifier = folder_item_get_identifier(account_get_special_folder
8590 (compose->account, F_OUTBOX));
8591 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
8592 g_free(folderidentifier);
8596 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
8597 GtkTreeViewColumn *column, Compose *compose)
8599 compose_attach_property(compose);
8602 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
8605 Compose *compose = (Compose *)data;
8606 GtkTreeSelection *attach_selection;
8607 gint attach_nr_selected;
8608 GtkItemFactory *ifactory;
8610 if (!event) return FALSE;
8612 if (event->button == 3) {
8613 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
8614 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
8615 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
8617 if (attach_nr_selected > 0)
8619 menu_set_sensitive(ifactory, "/Remove", TRUE);
8620 menu_set_sensitive(ifactory, "/Properties...", TRUE);
8622 menu_set_sensitive(ifactory, "/Remove", FALSE);
8623 menu_set_sensitive(ifactory, "/Properties...", FALSE);
8626 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
8627 NULL, NULL, event->button, event->time);
8634 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
8637 Compose *compose = (Compose *)data;
8639 if (!event) return FALSE;
8641 switch (event->keyval) {
8643 compose_attach_remove_selected(compose);
8649 static void compose_allow_user_actions (Compose *compose, gboolean allow)
8651 GtkItemFactory *ifactory = gtk_item_factory_from_widget(compose->menubar);
8652 toolbar_comp_set_sensitive(compose, allow);
8653 menu_set_sensitive(ifactory, "/Message", allow);
8654 menu_set_sensitive(ifactory, "/Edit", allow);
8656 menu_set_sensitive(ifactory, "/Spelling", allow);
8658 menu_set_sensitive(ifactory, "/Options", allow);
8659 menu_set_sensitive(ifactory, "/Tools", allow);
8660 menu_set_sensitive(ifactory, "/Help", allow);
8662 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
8666 static void compose_send_cb(gpointer data, guint action, GtkWidget *widget)
8668 Compose *compose = (Compose *)data;
8670 if (prefs_common.work_offline &&
8671 !inc_offline_should_override(TRUE,
8672 _("Claws Mail needs network access in order "
8673 "to send this email.")))
8676 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
8677 g_source_remove(compose->draft_timeout_tag);
8678 compose->draft_timeout_tag = -1;
8681 compose_send(compose);
8684 static void compose_send_later_cb(gpointer data, guint action,
8687 Compose *compose = (Compose *)data;
8691 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
8695 compose_close(compose);
8696 } else if (val == -1) {
8697 alertpanel_error(_("Could not queue message."));
8698 } else if (val == -2) {
8699 alertpanel_error(_("Could not queue message:\n\n%s."), strerror(errno));
8700 } else if (val == -3) {
8701 if (privacy_peek_error())
8702 alertpanel_error(_("Could not queue message for sending:\n\n"
8703 "Signature failed: %s"), privacy_get_error());
8704 } else if (val == -4) {
8705 alertpanel_error(_("Could not queue message for sending:\n\n"
8706 "Charset conversion failed."));
8707 } else if (val == -5) {
8708 alertpanel_error(_("Could not queue message for sending:\n\n"
8709 "Couldn't get recipient encryption key."));
8710 } else if (val == -6) {
8713 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
8716 #define DRAFTED_AT_EXIT "drafted_at_exit"
8717 static void compose_register_draft(MsgInfo *info)
8719 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8720 DRAFTED_AT_EXIT, NULL);
8721 FILE *fp = fopen(filepath, "ab");
8724 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
8732 gboolean compose_draft (gpointer data, guint action)
8734 Compose *compose = (Compose *)data;
8738 MsgFlags flag = {0, 0};
8739 static gboolean lock = FALSE;
8740 MsgInfo *newmsginfo;
8742 gboolean target_locked = FALSE;
8743 gboolean err = FALSE;
8745 if (lock) return FALSE;
8747 if (compose->sending)
8750 draft = account_get_special_folder(compose->account, F_DRAFT);
8751 g_return_val_if_fail(draft != NULL, FALSE);
8753 if (!g_mutex_trylock(compose->mutex)) {
8754 /* we don't want to lock the mutex once it's available,
8755 * because as the only other part of compose.c locking
8756 * it is compose_close - which means once unlocked,
8757 * the compose struct will be freed */
8758 debug_print("couldn't lock mutex, probably sending\n");
8764 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
8765 G_DIR_SEPARATOR, compose);
8766 if ((fp = g_fopen(tmp, "wb")) == NULL) {
8767 FILE_OP_ERROR(tmp, "fopen");
8771 /* chmod for security */
8772 if (change_file_mode_rw(fp, tmp) < 0) {
8773 FILE_OP_ERROR(tmp, "chmod");
8774 g_warning("can't change file mode\n");
8777 /* Save draft infos */
8778 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
8779 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
8781 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
8782 gchar *savefolderid;
8784 savefolderid = gtk_editable_get_chars(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
8785 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
8786 g_free(savefolderid);
8788 if (compose->return_receipt) {
8789 err |= (fprintf(fp, "RRCPT:1\n") < 0);
8791 if (compose->privacy_system) {
8792 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
8793 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
8794 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
8797 /* Message-ID of message replying to */
8798 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
8801 folderid = folder_item_get_identifier(compose->replyinfo->folder);
8802 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
8805 /* Message-ID of message forwarding to */
8806 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
8809 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
8810 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
8814 /* end of headers */
8815 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
8822 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
8826 if (fclose(fp) == EOF) {
8830 if (compose->targetinfo) {
8831 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
8832 flag.perm_flags = target_locked?MSG_LOCKED:0;
8834 flag.tmp_flags = MSG_DRAFT;
8836 folder_item_scan(draft);
8837 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
8838 MsgInfo *tmpinfo = NULL;
8839 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
8840 if (compose->msgid) {
8841 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
8844 msgnum = tmpinfo->msgnum;
8845 procmsg_msginfo_free(tmpinfo);
8846 debug_print("got draft msgnum %d from scanning\n", msgnum);
8848 debug_print("didn't get draft msgnum after scanning\n");
8851 debug_print("got draft msgnum %d from adding\n", msgnum);
8857 if (action != COMPOSE_AUTO_SAVE) {
8858 if (action != COMPOSE_DRAFT_FOR_EXIT)
8859 alertpanel_error(_("Could not save draft."));
8862 gtkut_window_popup(compose->window);
8863 val = alertpanel_full(_("Could not save draft"),
8864 _("Could not save draft.\n"
8865 "Do you want to cancel exit or discard this email?"),
8866 _("_Cancel exit"), _("_Discard email"), NULL,
8867 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
8868 if (val == G_ALERTALTERNATE) {
8870 g_mutex_unlock(compose->mutex); /* must be done before closing */
8871 compose_close(compose);
8875 g_mutex_unlock(compose->mutex); /* must be done before closing */
8884 if (compose->mode == COMPOSE_REEDIT) {
8885 compose_remove_reedit_target(compose, TRUE);
8888 newmsginfo = folder_item_get_msginfo(draft, msgnum);
8891 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
8893 procmsg_msginfo_set_flags(newmsginfo, MSG_LOCKED, MSG_DRAFT);
8895 procmsg_msginfo_set_flags(newmsginfo, 0, MSG_DRAFT);
8896 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
8897 procmsg_msginfo_set_flags(newmsginfo, 0,
8898 MSG_HAS_ATTACHMENT);
8900 if (action == COMPOSE_DRAFT_FOR_EXIT) {
8901 compose_register_draft(newmsginfo);
8903 procmsg_msginfo_free(newmsginfo);
8906 folder_item_scan(draft);
8908 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
8910 g_mutex_unlock(compose->mutex); /* must be done before closing */
8911 compose_close(compose);
8917 path = folder_item_fetch_msg(draft, msgnum);
8919 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
8922 if (g_stat(path, &s) < 0) {
8923 FILE_OP_ERROR(path, "stat");
8929 procmsg_msginfo_free(compose->targetinfo);
8930 compose->targetinfo = procmsg_msginfo_new();
8931 compose->targetinfo->msgnum = msgnum;
8932 compose->targetinfo->size = s.st_size;
8933 compose->targetinfo->mtime = s.st_mtime;
8934 compose->targetinfo->folder = draft;
8936 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
8937 compose->mode = COMPOSE_REEDIT;
8939 if (action == COMPOSE_AUTO_SAVE) {
8940 compose->autosaved_draft = compose->targetinfo;
8942 compose->modified = FALSE;
8943 compose_set_title(compose);
8947 g_mutex_unlock(compose->mutex);
8951 void compose_clear_exit_drafts(void)
8953 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8954 DRAFTED_AT_EXIT, NULL);
8955 if (is_file_exist(filepath))
8961 void compose_reopen_exit_drafts(void)
8963 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8964 DRAFTED_AT_EXIT, NULL);
8965 FILE *fp = fopen(filepath, "rb");
8969 while (fgets(buf, sizeof(buf), fp)) {
8970 gchar **parts = g_strsplit(buf, "\t", 2);
8971 const gchar *folder = parts[0];
8972 int msgnum = parts[1] ? atoi(parts[1]):-1;
8974 if (folder && *folder && msgnum > -1) {
8975 FolderItem *item = folder_find_item_from_identifier(folder);
8976 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
8978 compose_reedit(info, FALSE);
8985 compose_clear_exit_drafts();
8988 static void compose_draft_cb(gpointer data, guint action, GtkWidget *widget)
8990 compose_draft(data, action);
8993 static void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
8995 if (compose && file_list) {
8998 for ( tmp = file_list; tmp; tmp = tmp->next) {
8999 gchar *file = (gchar *) tmp->data;
9000 gchar *utf8_filename = conv_filename_to_utf8(file);
9001 compose_attach_append(compose, file, utf8_filename, NULL);
9002 compose_changed_cb(NULL, compose);
9007 g_free(utf8_filename);
9012 static void compose_attach_cb(gpointer data, guint action, GtkWidget *widget)
9014 Compose *compose = (Compose *)data;
9017 if (compose->redirect_filename != NULL)
9020 file_list = filesel_select_multiple_files_open(_("Select file"));
9023 compose_attach_from_list(compose, file_list, TRUE);
9024 g_list_free(file_list);
9028 static void compose_insert_file_cb(gpointer data, guint action,
9031 Compose *compose = (Compose *)data;
9034 file_list = filesel_select_multiple_files_open(_("Select file"));
9039 for ( tmp = file_list; tmp; tmp = tmp->next) {
9040 gchar *file = (gchar *) tmp->data;
9041 gchar *filedup = g_strdup(file);
9042 gchar *shortfile = g_path_get_basename(filedup);
9043 ComposeInsertResult res;
9045 res = compose_insert_file(compose, file);
9046 if (res == COMPOSE_INSERT_READ_ERROR) {
9047 alertpanel_error(_("File '%s' could not be read."), shortfile);
9048 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
9049 alertpanel_error(_("File '%s' contained invalid characters\n"
9050 "for the current encoding, insertion may be incorrect."), shortfile);
9056 g_list_free(file_list);
9060 static void compose_insert_sig_cb(gpointer data, guint action,
9063 Compose *compose = (Compose *)data;
9065 compose_insert_sig(compose, FALSE);
9068 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
9072 Compose *compose = (Compose *)data;
9074 gtkut_widget_get_uposition(widget, &x, &y);
9075 prefs_common.compose_x = x;
9076 prefs_common.compose_y = y;
9078 if (compose->sending || compose->updating)
9080 compose_close_cb(compose, 0, NULL);
9084 void compose_close_toolbar(Compose *compose)
9086 compose_close_cb(compose, 0, NULL);
9089 static void compose_close_cb(gpointer data, guint action, GtkWidget *widget)
9091 Compose *compose = (Compose *)data;
9095 if (compose->exteditor_tag != -1) {
9096 if (!compose_ext_editor_kill(compose))
9101 if (compose->modified) {
9102 if (!g_mutex_trylock(compose->mutex)) {
9103 /* we don't want to lock the mutex once it's available,
9104 * because as the only other part of compose.c locking
9105 * it is compose_close - which means once unlocked,
9106 * the compose struct will be freed */
9107 debug_print("couldn't lock mutex, probably sending\n");
9110 val = alertpanel(_("Discard message"),
9111 _("This message has been modified. Discard it?"),
9112 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
9113 g_mutex_unlock(compose->mutex);
9115 case G_ALERTDEFAULT:
9116 if (prefs_common.autosave)
9117 compose_remove_draft(compose);
9119 case G_ALERTALTERNATE:
9120 compose_draft_cb(data, COMPOSE_QUIT_EDITING, NULL);
9127 compose_close(compose);
9130 static void compose_set_encoding_cb(gpointer data, guint action,
9133 Compose *compose = (Compose *)data;
9135 if (GTK_CHECK_MENU_ITEM(widget)->active)
9136 compose->out_encoding = (CharSet)action;
9139 static void compose_address_cb(gpointer data, guint action, GtkWidget *widget)
9141 Compose *compose = (Compose *)data;
9143 addressbook_open(compose);
9146 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
9148 Compose *compose = (Compose *)data;
9153 tmpl = g_object_get_data(G_OBJECT(widget), "template");
9154 g_return_if_fail(tmpl != NULL);
9156 msg = g_strdup_printf(_("Do you want to apply the template '%s' ?"),
9158 val = alertpanel(_("Apply template"), msg,
9159 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
9162 if (val == G_ALERTDEFAULT)
9163 compose_template_apply(compose, tmpl, TRUE);
9164 else if (val == G_ALERTALTERNATE)
9165 compose_template_apply(compose, tmpl, FALSE);
9168 static void compose_ext_editor_cb(gpointer data, guint action,
9171 Compose *compose = (Compose *)data;
9173 compose_exec_ext_editor(compose);
9176 static void compose_undo_cb(Compose *compose)
9178 gboolean prev_autowrap = compose->autowrap;
9180 compose->autowrap = FALSE;
9181 undo_undo(compose->undostruct);
9182 compose->autowrap = prev_autowrap;
9185 static void compose_redo_cb(Compose *compose)
9187 gboolean prev_autowrap = compose->autowrap;
9189 compose->autowrap = FALSE;
9190 undo_redo(compose->undostruct);
9191 compose->autowrap = prev_autowrap;
9194 static void entry_cut_clipboard(GtkWidget *entry)
9196 if (GTK_IS_EDITABLE(entry))
9197 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
9198 else if (GTK_IS_TEXT_VIEW(entry))
9199 gtk_text_buffer_cut_clipboard(
9200 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
9201 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
9205 static void entry_copy_clipboard(GtkWidget *entry)
9207 if (GTK_IS_EDITABLE(entry))
9208 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
9209 else if (GTK_IS_TEXT_VIEW(entry))
9210 gtk_text_buffer_copy_clipboard(
9211 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
9212 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
9215 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
9216 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
9218 if (GTK_IS_TEXT_VIEW(entry)) {
9219 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
9220 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
9221 GtkTextIter start_iter, end_iter;
9223 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
9225 if (contents == NULL)
9228 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
9230 /* we shouldn't delete the selection when middle-click-pasting, or we
9231 * can't mid-click-paste our own selection */
9232 if (clip != GDK_SELECTION_PRIMARY) {
9233 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
9236 if (insert_place == NULL) {
9237 /* if insert_place isn't specified, insert at the cursor.
9238 * used for Ctrl-V pasting */
9239 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
9240 start = gtk_text_iter_get_offset(&start_iter);
9241 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
9243 /* if insert_place is specified, paste here.
9244 * used for mid-click-pasting */
9245 start = gtk_text_iter_get_offset(insert_place);
9246 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
9250 /* paste unwrapped: mark the paste so it's not wrapped later */
9251 end = start + strlen(contents);
9252 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
9253 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
9254 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
9255 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
9256 /* rewrap paragraph now (after a mid-click-paste) */
9257 mark_start = gtk_text_buffer_get_insert(buffer);
9258 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
9259 gtk_text_iter_backward_char(&start_iter);
9260 compose_beautify_paragraph(compose, &start_iter, TRUE);
9262 } else if (GTK_IS_EDITABLE(entry))
9263 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
9267 static void entry_allsel(GtkWidget *entry)
9269 if (GTK_IS_EDITABLE(entry))
9270 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
9271 else if (GTK_IS_TEXT_VIEW(entry)) {
9272 GtkTextIter startiter, enditer;
9273 GtkTextBuffer *textbuf;
9275 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
9276 gtk_text_buffer_get_start_iter(textbuf, &startiter);
9277 gtk_text_buffer_get_end_iter(textbuf, &enditer);
9279 gtk_text_buffer_move_mark_by_name(textbuf,
9280 "selection_bound", &startiter);
9281 gtk_text_buffer_move_mark_by_name(textbuf,
9282 "insert", &enditer);
9286 static void compose_cut_cb(Compose *compose)
9288 if (compose->focused_editable
9290 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9293 entry_cut_clipboard(compose->focused_editable);
9296 static void compose_copy_cb(Compose *compose)
9298 if (compose->focused_editable
9300 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9303 entry_copy_clipboard(compose->focused_editable);
9306 static void compose_paste_cb(Compose *compose)
9309 GtkTextBuffer *buffer;
9311 if (compose->focused_editable &&
9312 GTK_WIDGET_HAS_FOCUS(compose->focused_editable))
9313 entry_paste_clipboard(compose, compose->focused_editable,
9314 prefs_common.linewrap_pastes,
9315 GDK_SELECTION_CLIPBOARD, NULL);
9319 static void compose_paste_as_quote_cb(Compose *compose)
9321 gint wrap_quote = prefs_common.linewrap_quote;
9322 if (compose->focused_editable
9324 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9327 /* let text_insert() (called directly or at a later time
9328 * after the gtk_editable_paste_clipboard) know that
9329 * text is to be inserted as a quotation. implemented
9330 * by using a simple refcount... */
9331 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
9332 G_OBJECT(compose->focused_editable),
9333 "paste_as_quotation"));
9334 g_object_set_data(G_OBJECT(compose->focused_editable),
9335 "paste_as_quotation",
9336 GINT_TO_POINTER(paste_as_quotation + 1));
9337 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
9338 entry_paste_clipboard(compose, compose->focused_editable,
9339 prefs_common.linewrap_pastes,
9340 GDK_SELECTION_CLIPBOARD, NULL);
9341 prefs_common.linewrap_quote = wrap_quote;
9345 static void compose_paste_no_wrap_cb(Compose *compose)
9348 GtkTextBuffer *buffer;
9350 if (compose->focused_editable
9352 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9355 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
9356 GDK_SELECTION_CLIPBOARD, NULL);
9360 static void compose_paste_wrap_cb(Compose *compose)
9363 GtkTextBuffer *buffer;
9365 if (compose->focused_editable
9367 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9370 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
9371 GDK_SELECTION_CLIPBOARD, NULL);
9375 static void compose_allsel_cb(Compose *compose)
9377 if (compose->focused_editable
9379 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9382 entry_allsel(compose->focused_editable);
9385 static void textview_move_beginning_of_line (GtkTextView *text)
9387 GtkTextBuffer *buffer;
9391 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9393 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9394 mark = gtk_text_buffer_get_insert(buffer);
9395 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9396 gtk_text_iter_set_line_offset(&ins, 0);
9397 gtk_text_buffer_place_cursor(buffer, &ins);
9400 static void textview_move_forward_character (GtkTextView *text)
9402 GtkTextBuffer *buffer;
9406 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9408 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9409 mark = gtk_text_buffer_get_insert(buffer);
9410 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9411 if (gtk_text_iter_forward_cursor_position(&ins))
9412 gtk_text_buffer_place_cursor(buffer, &ins);
9415 static void textview_move_backward_character (GtkTextView *text)
9417 GtkTextBuffer *buffer;
9421 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9423 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9424 mark = gtk_text_buffer_get_insert(buffer);
9425 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9426 if (gtk_text_iter_backward_cursor_position(&ins))
9427 gtk_text_buffer_place_cursor(buffer, &ins);
9430 static void textview_move_forward_word (GtkTextView *text)
9432 GtkTextBuffer *buffer;
9437 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9439 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9440 mark = gtk_text_buffer_get_insert(buffer);
9441 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9442 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
9443 if (gtk_text_iter_forward_word_ends(&ins, count)) {
9444 gtk_text_iter_backward_word_start(&ins);
9445 gtk_text_buffer_place_cursor(buffer, &ins);
9449 static void textview_move_backward_word (GtkTextView *text)
9451 GtkTextBuffer *buffer;
9456 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9458 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9459 mark = gtk_text_buffer_get_insert(buffer);
9460 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9461 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
9462 if (gtk_text_iter_backward_word_starts(&ins, 1))
9463 gtk_text_buffer_place_cursor(buffer, &ins);
9466 static void textview_move_end_of_line (GtkTextView *text)
9468 GtkTextBuffer *buffer;
9472 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9474 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9475 mark = gtk_text_buffer_get_insert(buffer);
9476 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9477 if (gtk_text_iter_forward_to_line_end(&ins))
9478 gtk_text_buffer_place_cursor(buffer, &ins);
9481 static void textview_move_next_line (GtkTextView *text)
9483 GtkTextBuffer *buffer;
9488 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9490 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9491 mark = gtk_text_buffer_get_insert(buffer);
9492 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9493 offset = gtk_text_iter_get_line_offset(&ins);
9494 if (gtk_text_iter_forward_line(&ins)) {
9495 gtk_text_iter_set_line_offset(&ins, offset);
9496 gtk_text_buffer_place_cursor(buffer, &ins);
9500 static void textview_move_previous_line (GtkTextView *text)
9502 GtkTextBuffer *buffer;
9507 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9509 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9510 mark = gtk_text_buffer_get_insert(buffer);
9511 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9512 offset = gtk_text_iter_get_line_offset(&ins);
9513 if (gtk_text_iter_backward_line(&ins)) {
9514 gtk_text_iter_set_line_offset(&ins, offset);
9515 gtk_text_buffer_place_cursor(buffer, &ins);
9519 static void textview_delete_forward_character (GtkTextView *text)
9521 GtkTextBuffer *buffer;
9523 GtkTextIter ins, end_iter;
9525 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9527 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9528 mark = gtk_text_buffer_get_insert(buffer);
9529 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9531 if (gtk_text_iter_forward_char(&end_iter)) {
9532 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9536 static void textview_delete_backward_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_backward_char(&end_iter)) {
9549 gtk_text_buffer_delete(buffer, &end_iter, &ins);
9553 static void textview_delete_forward_word (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_forward_word_end(&end_iter)) {
9566 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9570 static void textview_delete_backward_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_backward_word_start(&end_iter)) {
9583 gtk_text_buffer_delete(buffer, &end_iter, &ins);
9587 static void textview_delete_line (GtkTextView *text)
9589 GtkTextBuffer *buffer;
9591 GtkTextIter ins, start_iter, end_iter;
9594 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9596 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9597 mark = gtk_text_buffer_get_insert(buffer);
9598 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9601 gtk_text_iter_set_line_offset(&start_iter, 0);
9604 if (gtk_text_iter_ends_line(&end_iter))
9605 found = gtk_text_iter_forward_char(&end_iter);
9607 found = gtk_text_iter_forward_to_line_end(&end_iter);
9610 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
9613 static void textview_delete_to_line_end (GtkTextView *text)
9615 GtkTextBuffer *buffer;
9617 GtkTextIter ins, end_iter;
9620 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9622 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9623 mark = gtk_text_buffer_get_insert(buffer);
9624 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9626 if (gtk_text_iter_ends_line(&end_iter))
9627 found = gtk_text_iter_forward_char(&end_iter);
9629 found = gtk_text_iter_forward_to_line_end(&end_iter);
9631 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9634 static void compose_advanced_action_cb(Compose *compose,
9635 ComposeCallAdvancedAction action)
9637 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9639 void (*do_action) (GtkTextView *text);
9640 } action_table[] = {
9641 {textview_move_beginning_of_line},
9642 {textview_move_forward_character},
9643 {textview_move_backward_character},
9644 {textview_move_forward_word},
9645 {textview_move_backward_word},
9646 {textview_move_end_of_line},
9647 {textview_move_next_line},
9648 {textview_move_previous_line},
9649 {textview_delete_forward_character},
9650 {textview_delete_backward_character},
9651 {textview_delete_forward_word},
9652 {textview_delete_backward_word},
9653 {textview_delete_line},
9654 {NULL}, /* gtk_stext_delete_line_n */
9655 {textview_delete_to_line_end}
9658 if (!GTK_WIDGET_HAS_FOCUS(text)) return;
9660 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
9661 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
9662 if (action_table[action].do_action)
9663 action_table[action].do_action(text);
9665 g_warning("Not implemented yet.");
9669 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
9673 if (GTK_IS_EDITABLE(widget)) {
9674 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
9675 gtk_editable_set_position(GTK_EDITABLE(widget),
9678 if (widget->parent && widget->parent->parent
9679 && widget->parent->parent->parent) {
9680 if (GTK_IS_SCROLLED_WINDOW(widget->parent->parent->parent)) {
9681 gint y = widget->allocation.y;
9682 gint height = widget->allocation.height;
9683 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
9684 (GTK_SCROLLED_WINDOW(widget->parent->parent->parent));
9686 if (y < (int)shown->value) {
9687 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown), y - 1);
9689 if (y + height > (int)shown->value + (int)shown->page_size) {
9690 if (y - height - 1 < (int)shown->upper - (int)shown->page_size) {
9691 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
9692 y + height - (int)shown->page_size - 1);
9694 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
9695 (int)shown->upper - (int)shown->page_size - 1);
9702 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
9703 compose->focused_editable = widget;
9706 if (GTK_IS_TEXT_VIEW(widget)
9707 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
9708 gtk_widget_ref(compose->notebook);
9709 gtk_widget_ref(compose->edit_vbox);
9710 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
9711 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
9712 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
9713 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
9714 gtk_widget_unref(compose->notebook);
9715 gtk_widget_unref(compose->edit_vbox);
9716 g_signal_handlers_block_by_func(G_OBJECT(widget),
9717 G_CALLBACK(compose_grab_focus_cb),
9719 gtk_widget_grab_focus(widget);
9720 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
9721 G_CALLBACK(compose_grab_focus_cb),
9723 } else if (!GTK_IS_TEXT_VIEW(widget)
9724 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
9725 gtk_widget_ref(compose->notebook);
9726 gtk_widget_ref(compose->edit_vbox);
9727 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
9728 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
9729 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
9730 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
9731 gtk_widget_unref(compose->notebook);
9732 gtk_widget_unref(compose->edit_vbox);
9733 g_signal_handlers_block_by_func(G_OBJECT(widget),
9734 G_CALLBACK(compose_grab_focus_cb),
9736 gtk_widget_grab_focus(widget);
9737 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
9738 G_CALLBACK(compose_grab_focus_cb),
9744 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
9746 compose->modified = TRUE;
9748 compose_set_title(compose);
9752 static void compose_wrap_cb(gpointer data, guint action, GtkWidget *widget)
9754 Compose *compose = (Compose *)data;
9757 compose_wrap_all_full(compose, TRUE);
9759 compose_beautify_paragraph(compose, NULL, TRUE);
9762 static void compose_find_cb(gpointer data, guint action, GtkWidget *widget)
9764 Compose *compose = (Compose *)data;
9766 message_search_compose(compose);
9769 static void compose_toggle_autowrap_cb(gpointer data, guint action,
9772 Compose *compose = (Compose *)data;
9773 compose->autowrap = GTK_CHECK_MENU_ITEM(widget)->active;
9774 if (compose->autowrap)
9775 compose_wrap_all_full(compose, TRUE);
9776 compose->autowrap = GTK_CHECK_MENU_ITEM(widget)->active;
9779 static void compose_toggle_sign_cb(gpointer data, guint action,
9782 Compose *compose = (Compose *)data;
9784 if (GTK_CHECK_MENU_ITEM(widget)->active)
9785 compose->use_signing = TRUE;
9787 compose->use_signing = FALSE;
9790 static void compose_toggle_encrypt_cb(gpointer data, guint action,
9793 Compose *compose = (Compose *)data;
9795 if (GTK_CHECK_MENU_ITEM(widget)->active)
9796 compose->use_encryption = TRUE;
9798 compose->use_encryption = FALSE;
9801 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
9803 g_free(compose->privacy_system);
9805 compose->privacy_system = g_strdup(account->default_privacy_system);
9806 compose_update_privacy_system_menu_item(compose, warn);
9809 static void compose_toggle_ruler_cb(gpointer data, guint action,
9812 Compose *compose = (Compose *)data;
9814 if (GTK_CHECK_MENU_ITEM(widget)->active) {
9815 gtk_widget_show(compose->ruler_hbox);
9816 prefs_common.show_ruler = TRUE;
9818 gtk_widget_hide(compose->ruler_hbox);
9819 gtk_widget_queue_resize(compose->edit_vbox);
9820 prefs_common.show_ruler = FALSE;
9824 static void compose_attach_drag_received_cb (GtkWidget *widget,
9825 GdkDragContext *context,
9828 GtkSelectionData *data,
9833 Compose *compose = (Compose *)user_data;
9836 if (gdk_atom_name(data->type) &&
9837 !strcmp(gdk_atom_name(data->type), "text/uri-list")
9838 && gtk_drag_get_source_widget(context) !=
9839 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
9840 list = uri_list_extract_filenames((const gchar *)data->data);
9841 for (tmp = list; tmp != NULL; tmp = tmp->next) {
9842 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
9843 compose_attach_append
9844 (compose, (const gchar *)tmp->data,
9845 utf8_filename, NULL);
9846 g_free(utf8_filename);
9848 if (list) compose_changed_cb(NULL, compose);
9849 list_free_strings(list);
9851 } else if (gtk_drag_get_source_widget(context)
9852 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
9853 /* comes from our summaryview */
9854 SummaryView * summaryview = NULL;
9855 GSList * list = NULL, *cur = NULL;
9857 if (mainwindow_get_mainwindow())
9858 summaryview = mainwindow_get_mainwindow()->summaryview;
9861 list = summary_get_selected_msg_list(summaryview);
9863 for (cur = list; cur; cur = cur->next) {
9864 MsgInfo *msginfo = (MsgInfo *)cur->data;
9867 file = procmsg_get_message_file_full(msginfo,
9870 compose_attach_append(compose, (const gchar *)file,
9871 (const gchar *)file, "message/rfc822");
9879 static gboolean compose_drag_drop(GtkWidget *widget,
9880 GdkDragContext *drag_context,
9882 guint time, gpointer user_data)
9884 /* not handling this signal makes compose_insert_drag_received_cb
9889 static void compose_insert_drag_received_cb (GtkWidget *widget,
9890 GdkDragContext *drag_context,
9893 GtkSelectionData *data,
9898 Compose *compose = (Compose *)user_data;
9901 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
9903 if (gdk_atom_name(data->type) && !strcmp(gdk_atom_name(data->type), "text/uri-list")) {
9904 AlertValue val = G_ALERTDEFAULT;
9906 list = uri_list_extract_filenames((const gchar *)data->data);
9908 if (list == NULL && strstr((gchar *)(data->data), "://")) {
9909 /* Assume a list of no files, and data has ://, is a remote link */
9910 gchar *tmpdata = g_strstrip(g_strdup((const gchar *)data->data));
9911 gchar *tmpfile = get_tmp_file();
9912 str_write_to_file(tmpdata, tmpfile);
9914 compose_insert_file(compose, tmpfile);
9917 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9918 compose_beautify_paragraph(compose, NULL, TRUE);
9921 switch (prefs_common.compose_dnd_mode) {
9922 case COMPOSE_DND_ASK:
9923 val = alertpanel_full(_("Insert or attach?"),
9924 _("Do you want to insert the contents of the file(s) "
9925 "into the message body, or attach it to the email?"),
9926 GTK_STOCK_CANCEL, _("+_Insert"), _("_Attach"),
9927 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
9929 case COMPOSE_DND_INSERT:
9930 val = G_ALERTALTERNATE;
9932 case COMPOSE_DND_ATTACH:
9936 /* unexpected case */
9937 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
9940 if (val & G_ALERTDISABLE) {
9941 val &= ~G_ALERTDISABLE;
9942 /* remember what action to perform by default, only if we don't click Cancel */
9943 if (val == G_ALERTALTERNATE)
9944 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
9945 else if (val == G_ALERTOTHER)
9946 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
9949 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
9950 gtk_drag_finish(drag_context, FALSE, FALSE, time);
9951 list_free_strings(list);
9954 } else if (val == G_ALERTOTHER) {
9955 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
9956 list_free_strings(list);
9961 for (tmp = list; tmp != NULL; tmp = tmp->next) {
9962 compose_insert_file(compose, (const gchar *)tmp->data);
9964 list_free_strings(list);
9966 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9969 #if GTK_CHECK_VERSION(2, 8, 0)
9970 /* do nothing, handled by GTK */
9972 gchar *tmpfile = get_tmp_file();
9973 str_write_to_file((const gchar *)data->data, tmpfile);
9974 compose_insert_file(compose, tmpfile);
9977 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9981 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9984 static void compose_header_drag_received_cb (GtkWidget *widget,
9985 GdkDragContext *drag_context,
9988 GtkSelectionData *data,
9993 GtkEditable *entry = (GtkEditable *)user_data;
9994 gchar *email = (gchar *)data->data;
9996 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
9999 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
10000 gchar *decoded=g_new(gchar, strlen(email));
10003 email += strlen("mailto:");
10004 decode_uri(decoded, email); /* will fit */
10005 gtk_editable_delete_text(entry, 0, -1);
10006 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
10007 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10011 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10014 static void compose_toggle_return_receipt_cb(gpointer data, guint action,
10017 Compose *compose = (Compose *)data;
10019 if (GTK_CHECK_MENU_ITEM(widget)->active)
10020 compose->return_receipt = TRUE;
10022 compose->return_receipt = FALSE;
10025 static void compose_toggle_remove_refs_cb(gpointer data, guint action,
10028 Compose *compose = (Compose *)data;
10030 if (GTK_CHECK_MENU_ITEM(widget)->active)
10031 compose->remove_references = TRUE;
10033 compose->remove_references = FALSE;
10036 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
10037 GdkEventKey *event,
10038 ComposeHeaderEntry *headerentry)
10040 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
10041 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
10042 !(event->state & GDK_MODIFIER_MASK) &&
10043 (event->keyval == GDK_BackSpace) &&
10044 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
10045 gtk_container_remove
10046 (GTK_CONTAINER(headerentry->compose->header_table),
10047 headerentry->combo);
10048 gtk_container_remove
10049 (GTK_CONTAINER(headerentry->compose->header_table),
10050 headerentry->entry);
10051 headerentry->compose->header_list =
10052 g_slist_remove(headerentry->compose->header_list,
10054 g_free(headerentry);
10055 } else if (event->keyval == GDK_Tab) {
10056 if (headerentry->compose->header_last == headerentry) {
10057 /* Override default next focus, and give it to subject_entry
10058 * instead of notebook tabs
10060 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
10061 gtk_widget_grab_focus(headerentry->compose->subject_entry);
10068 static gboolean compose_headerentry_changed_cb(GtkWidget *entry,
10069 ComposeHeaderEntry *headerentry)
10071 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
10072 compose_create_header_entry(headerentry->compose);
10073 g_signal_handlers_disconnect_matched
10074 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
10075 0, 0, NULL, NULL, headerentry);
10077 /* Automatically scroll down */
10078 compose_show_first_last_header(headerentry->compose, FALSE);
10084 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
10086 GtkAdjustment *vadj;
10088 g_return_if_fail(compose);
10089 g_return_if_fail(GTK_IS_WIDGET(compose->header_table));
10090 g_return_if_fail(GTK_IS_VIEWPORT(compose->header_table->parent));
10092 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(compose->header_table->parent));
10093 gtk_adjustment_set_value(vadj, (show_first ? vadj->lower : vadj->upper));
10094 gtk_adjustment_changed(vadj);
10097 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
10098 const gchar *text, gint len, Compose *compose)
10100 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
10101 (G_OBJECT(compose->text), "paste_as_quotation"));
10104 g_return_if_fail(text != NULL);
10106 g_signal_handlers_block_by_func(G_OBJECT(buffer),
10107 G_CALLBACK(text_inserted),
10109 if (paste_as_quotation) {
10111 const gchar *qmark;
10113 GtkTextIter start_iter;
10116 len = strlen(text);
10118 new_text = g_strndup(text, len);
10120 qmark = compose_quote_char_from_context(compose);
10122 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
10123 gtk_text_buffer_place_cursor(buffer, iter);
10125 pos = gtk_text_iter_get_offset(iter);
10127 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
10128 _("Quote format error at line %d."));
10129 quote_fmt_reset_vartable();
10131 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
10132 GINT_TO_POINTER(paste_as_quotation - 1));
10134 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
10135 gtk_text_buffer_place_cursor(buffer, iter);
10136 gtk_text_buffer_delete_mark(buffer, mark);
10138 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
10139 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
10140 compose_beautify_paragraph(compose, &start_iter, FALSE);
10141 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
10142 gtk_text_buffer_delete_mark(buffer, mark);
10144 if (strcmp(text, "\n") || compose->automatic_break
10145 || gtk_text_iter_starts_line(iter))
10146 gtk_text_buffer_insert(buffer, iter, text, len);
10148 /* check if the preceding is just whitespace or quote */
10149 GtkTextIter start_line;
10150 gchar *tmp = NULL, *quote = NULL;
10151 gint quote_len = 0, is_normal = 0;
10152 start_line = *iter;
10153 gtk_text_iter_set_line_offset(&start_line, 0);
10154 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
10156 if (*tmp == '\0') {
10159 quote = compose_get_quote_str(buffer, &start_line, "e_len);
10167 gtk_text_buffer_insert(buffer, iter, text, len);
10169 gtk_text_buffer_insert_with_tags_by_name(buffer,
10170 iter, text, len, "no_join", NULL);
10175 if (!paste_as_quotation) {
10176 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
10177 compose_beautify_paragraph(compose, iter, FALSE);
10178 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
10179 gtk_text_buffer_delete_mark(buffer, mark);
10182 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
10183 G_CALLBACK(text_inserted),
10185 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
10187 if (prefs_common.autosave &&
10188 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
10189 compose->draft_timeout_tag != -2 /* disabled while loading */)
10190 compose->draft_timeout_tag = g_timeout_add
10191 (500, (GtkFunction) compose_defer_auto_save_draft, compose);
10193 static gint compose_defer_auto_save_draft(Compose *compose)
10195 compose->draft_timeout_tag = -1;
10196 compose_draft_cb((gpointer)compose, COMPOSE_AUTO_SAVE, NULL);
10201 static void compose_check_all(Compose *compose)
10203 if (compose->gtkaspell)
10204 gtkaspell_check_all(compose->gtkaspell);
10207 static void compose_highlight_all(Compose *compose)
10209 if (compose->gtkaspell)
10210 gtkaspell_highlight_all(compose->gtkaspell);
10213 static void compose_check_backwards(Compose *compose)
10215 if (compose->gtkaspell)
10216 gtkaspell_check_backwards(compose->gtkaspell);
10218 GtkItemFactory *ifactory;
10219 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
10220 menu_set_sensitive(ifactory, "/Edit/Check backwards misspelled word", FALSE);
10221 menu_set_sensitive(ifactory, "/Edit/Forward to next misspelled word", FALSE);
10225 static void compose_check_forwards_go(Compose *compose)
10227 if (compose->gtkaspell)
10228 gtkaspell_check_forwards_go(compose->gtkaspell);
10230 GtkItemFactory *ifactory;
10231 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
10232 menu_set_sensitive(ifactory, "/Edit/Check backwards misspelled word", FALSE);
10233 menu_set_sensitive(ifactory, "/Edit/Forward to next misspelled word", FALSE);
10239 *\brief Guess originating forward account from MsgInfo and several
10240 * "common preference" settings. Return NULL if no guess.
10242 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
10244 PrefsAccount *account = NULL;
10246 g_return_val_if_fail(msginfo, NULL);
10247 g_return_val_if_fail(msginfo->folder, NULL);
10248 g_return_val_if_fail(msginfo->folder->prefs, NULL);
10250 if (msginfo->folder->prefs->enable_default_account)
10251 account = account_find_from_id(msginfo->folder->prefs->default_account);
10254 account = msginfo->folder->folder->account;
10256 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
10258 Xstrdup_a(to, msginfo->to, return NULL);
10259 extract_address(to);
10260 account = account_find_from_address(to, FALSE);
10263 if (!account && prefs_common.forward_account_autosel) {
10264 gchar cc[BUFFSIZE];
10265 if (!procheader_get_header_from_msginfo
10266 (msginfo, cc,sizeof cc , "Cc:")) {
10267 gchar *buf = cc + strlen("Cc:");
10268 extract_address(buf);
10269 account = account_find_from_address(buf, FALSE);
10273 if (!account && prefs_common.forward_account_autosel) {
10274 gchar deliveredto[BUFFSIZE];
10275 if (!procheader_get_header_from_msginfo
10276 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
10277 gchar *buf = deliveredto + strlen("Delivered-To:");
10278 extract_address(buf);
10279 account = account_find_from_address(buf, FALSE);
10286 gboolean compose_close(Compose *compose)
10290 if (!g_mutex_trylock(compose->mutex)) {
10291 /* we have to wait for the (possibly deferred by auto-save)
10292 * drafting to be done, before destroying the compose under
10294 debug_print("waiting for drafting to finish...\n");
10295 compose_allow_user_actions(compose, FALSE);
10296 g_timeout_add (500, (GSourceFunc) compose_close, compose);
10299 g_return_val_if_fail(compose, FALSE);
10300 gtkut_widget_get_uposition(compose->window, &x, &y);
10301 prefs_common.compose_x = x;
10302 prefs_common.compose_y = y;
10303 g_mutex_unlock(compose->mutex);
10304 compose_destroy(compose);
10309 * Add entry field for each address in list.
10310 * \param compose E-Mail composition object.
10311 * \param listAddress List of (formatted) E-Mail addresses.
10313 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
10316 node = listAddress;
10318 addr = ( gchar * ) node->data;
10319 compose_entry_append( compose, addr, COMPOSE_TO );
10320 node = g_list_next( node );
10324 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
10325 guint action, gboolean opening_multiple)
10327 gchar *body = NULL;
10328 GSList *new_msglist = NULL;
10329 MsgInfo *tmp_msginfo = NULL;
10330 gboolean originally_enc = FALSE;
10331 Compose *compose = NULL;
10333 g_return_if_fail(msgview != NULL);
10335 g_return_if_fail(msginfo_list != NULL);
10337 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
10338 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
10339 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
10341 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
10342 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
10343 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
10344 orig_msginfo, mimeinfo);
10345 if (tmp_msginfo != NULL) {
10346 new_msglist = g_slist_append(NULL, tmp_msginfo);
10348 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
10349 tmp_msginfo->folder = orig_msginfo->folder;
10350 tmp_msginfo->msgnum = orig_msginfo->msgnum;
10351 if (orig_msginfo->tags)
10352 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
10357 if (!opening_multiple)
10358 body = messageview_get_selection(msgview);
10361 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
10362 procmsg_msginfo_free(tmp_msginfo);
10363 g_slist_free(new_msglist);
10365 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
10367 if (compose && originally_enc) {
10368 compose_force_encryption(compose, compose->account, FALSE);
10374 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
10377 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
10378 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
10379 GSList *cur = msginfo_list;
10380 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
10381 "messages. Opening the windows "
10382 "could take some time. Do you "
10383 "want to continue?"),
10384 g_slist_length(msginfo_list));
10385 if (g_slist_length(msginfo_list) > 9
10386 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
10387 != G_ALERTALTERNATE) {
10392 /* We'll open multiple compose windows */
10393 /* let the WM place the next windows */
10394 compose_force_window_origin = FALSE;
10395 for (; cur; cur = cur->next) {
10397 tmplist.data = cur->data;
10398 tmplist.next = NULL;
10399 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
10401 compose_force_window_origin = TRUE;
10403 /* forwarding multiple mails as attachments is done via a
10404 * single compose window */
10405 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
10409 void compose_set_position(Compose *compose, gint pos)
10411 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10413 gtkut_text_view_set_position(text, pos);
10416 gboolean compose_search_string(Compose *compose,
10417 const gchar *str, gboolean case_sens)
10419 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10421 return gtkut_text_view_search_string(text, str, case_sens);
10424 gboolean compose_search_string_backward(Compose *compose,
10425 const gchar *str, gboolean case_sens)
10427 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10429 return gtkut_text_view_search_string_backward(text, str, case_sens);
10432 /* allocate a msginfo structure and populate its data from a compose data structure */
10433 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
10435 MsgInfo *newmsginfo;
10437 gchar buf[BUFFSIZE];
10439 g_return_val_if_fail( compose != NULL, NULL );
10441 newmsginfo = procmsg_msginfo_new();
10444 get_rfc822_date(buf, sizeof(buf));
10445 newmsginfo->date = g_strdup(buf);
10448 if (compose->from_name) {
10449 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
10450 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
10454 if (compose->subject_entry)
10455 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
10457 /* to, cc, reply-to, newsgroups */
10458 for (list = compose->header_list; list; list = list->next) {
10459 gchar *header = gtk_editable_get_chars(
10461 GTK_BIN(((ComposeHeaderEntry *)list->data)->combo)->child), 0, -1);
10462 gchar *entry = gtk_editable_get_chars(
10463 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
10465 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
10466 if ( newmsginfo->to == NULL ) {
10467 newmsginfo->to = g_strdup(entry);
10468 } else if (entry && *entry) {
10469 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
10470 g_free(newmsginfo->to);
10471 newmsginfo->to = tmp;
10474 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
10475 if ( newmsginfo->cc == NULL ) {
10476 newmsginfo->cc = g_strdup(entry);
10477 } else if (entry && *entry) {
10478 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
10479 g_free(newmsginfo->cc);
10480 newmsginfo->cc = tmp;
10483 if ( strcasecmp(header,
10484 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
10485 if ( newmsginfo->newsgroups == NULL ) {
10486 newmsginfo->newsgroups = g_strdup(entry);
10487 } else if (entry && *entry) {
10488 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
10489 g_free(newmsginfo->newsgroups);
10490 newmsginfo->newsgroups = tmp;
10498 /* other data is unset */
10504 /* update compose's dictionaries from folder dict settings */
10505 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
10506 FolderItem *folder_item)
10508 g_return_if_fail(compose != NULL);
10510 if (compose->gtkaspell && folder_item && folder_item->prefs) {
10511 FolderItemPrefs *prefs = folder_item->prefs;
10513 if (prefs->enable_default_dictionary)
10514 gtkaspell_change_dict(compose->gtkaspell,
10515 prefs->default_dictionary, FALSE);
10516 if (folder_item->prefs->enable_default_alt_dictionary)
10517 gtkaspell_change_alt_dict(compose->gtkaspell,
10518 prefs->default_alt_dictionary);
10519 if (prefs->enable_default_dictionary
10520 || prefs->enable_default_alt_dictionary)
10521 compose_spell_menu_changed(compose);