2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2007 Hiroyuki Yamamoto and the Claws Mail team
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
26 #ifndef PANGO_ENABLE_ENGINE
27 # define PANGO_ENABLE_ENGINE
31 #include <glib/gi18n.h>
32 #include <gdk/gdkkeysyms.h>
33 #include <gtk/gtkmain.h>
34 #include <gtk/gtkmenu.h>
35 #include <gtk/gtkmenuitem.h>
36 #include <gtk/gtkitemfactory.h>
37 #include <gtk/gtkcheckmenuitem.h>
38 #include <gtk/gtkoptionmenu.h>
39 #include <gtk/gtkwidget.h>
40 #include <gtk/gtkvpaned.h>
41 #include <gtk/gtkentry.h>
42 #include <gtk/gtkeditable.h>
43 #include <gtk/gtkwindow.h>
44 #include <gtk/gtksignal.h>
45 #include <gtk/gtkvbox.h>
46 #include <gtk/gtkcontainer.h>
47 #include <gtk/gtkhandlebox.h>
48 #include <gtk/gtktoolbar.h>
49 #include <gtk/gtktable.h>
50 #include <gtk/gtkhbox.h>
51 #include <gtk/gtklabel.h>
52 #include <gtk/gtkscrolledwindow.h>
53 #include <gtk/gtktreeview.h>
54 #include <gtk/gtkliststore.h>
55 #include <gtk/gtktreeselection.h>
56 #include <gtk/gtktreemodel.h>
58 #include <gtk/gtkdnd.h>
59 #include <gtk/gtkclipboard.h>
60 #include <pango/pango-break.h>
65 #include <sys/types.h>
71 # include <sys/wait.h>
75 #ifndef G_OS_WIN32 /* fixme we should have a configure test. */
79 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
86 #include "mainwindow.h"
88 #include "addressbook.h"
89 #include "folderview.h"
92 #include "stock_pixmap.h"
93 #include "send_message.h"
96 #include "customheader.h"
97 #include "prefs_common.h"
98 #include "prefs_account.h"
102 #include "procheader.h"
103 #include "procmime.h"
104 #include "statusbar.h"
107 #include "quoted-printable.h"
108 #include "codeconv.h"
110 #include "gtkutils.h"
112 #include "alertpanel.h"
113 #include "manage_window.h"
114 #include "gtkshruler.h"
116 #include "addr_compl.h"
117 #include "quote_fmt.h"
119 #include "foldersel.h"
122 #include "message_search.h"
123 #include "combobox.h"
127 #include "autofaces.h"
139 #define N_ATTACH_COLS (N_COL_COLUMNS)
143 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE,
144 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER,
145 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER,
146 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD,
147 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD,
148 COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE,
149 COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE,
150 COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE,
151 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER,
152 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER,
153 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD,
154 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD,
155 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE,
156 COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END
157 } ComposeCallAdvancedAction;
161 PRIORITY_HIGHEST = 1,
170 COMPOSE_INSERT_SUCCESS,
171 COMPOSE_INSERT_READ_ERROR,
172 COMPOSE_INSERT_INVALID_CHARACTER,
173 COMPOSE_INSERT_NO_FILE
174 } ComposeInsertResult;
178 COMPOSE_WRITE_FOR_SEND,
179 COMPOSE_WRITE_FOR_STORE
184 COMPOSE_QUOTE_FORCED,
189 #define B64_LINE_SIZE 57
190 #define B64_BUFFSIZE 77
192 #define MAX_REFERENCES_LEN 999
194 static GList *compose_list = NULL;
196 static Compose *compose_generic_new (PrefsAccount *account,
199 GPtrArray *attach_files,
200 GList *listAddress );
202 static Compose *compose_create (PrefsAccount *account,
207 static void compose_entry_mark_default_to (Compose *compose,
208 const gchar *address);
209 static Compose *compose_followup_and_reply_to (MsgInfo *msginfo,
210 ComposeQuoteMode quote_mode,
214 static Compose *compose_forward_multiple (PrefsAccount *account,
215 GSList *msginfo_list);
216 static Compose *compose_reply (MsgInfo *msginfo,
217 ComposeQuoteMode quote_mode,
222 static Compose *compose_reply_mode (ComposeMode mode,
223 GSList *msginfo_list,
225 static void compose_template_apply_fields(Compose *compose, Template *tmpl);
226 static void compose_update_privacy_systems_menu(Compose *compose);
228 static GtkWidget *compose_account_option_menu_create
230 static void compose_set_out_encoding (Compose *compose);
231 static void compose_set_template_menu (Compose *compose);
232 static void compose_template_apply (Compose *compose,
235 static void compose_destroy (Compose *compose);
237 static void compose_entries_set (Compose *compose,
239 ComposeEntryType to_type);
240 static gint compose_parse_header (Compose *compose,
242 static gchar *compose_parse_references (const gchar *ref,
245 static gchar *compose_quote_fmt (Compose *compose,
251 gboolean need_unescape,
252 const gchar *err_msg);
254 static void compose_reply_set_entry (Compose *compose,
260 followup_and_reply_to);
261 static void compose_reedit_set_entry (Compose *compose,
264 static void compose_insert_sig (Compose *compose,
266 static gchar *compose_get_signature_str (Compose *compose);
267 static ComposeInsertResult compose_insert_file (Compose *compose,
270 static gboolean compose_attach_append (Compose *compose,
273 const gchar *content_type);
274 static void compose_attach_parts (Compose *compose,
277 static gboolean compose_beautify_paragraph (Compose *compose,
278 GtkTextIter *par_iter,
280 static void compose_wrap_all (Compose *compose);
281 static void compose_wrap_all_full (Compose *compose,
284 static void compose_set_title (Compose *compose);
285 static void compose_select_account (Compose *compose,
286 PrefsAccount *account,
289 static PrefsAccount *compose_current_mail_account(void);
290 /* static gint compose_send (Compose *compose); */
291 static gboolean compose_check_for_valid_recipient
293 static gboolean compose_check_entries (Compose *compose,
294 gboolean check_everything);
295 static gint compose_write_to_file (Compose *compose,
298 gboolean attach_parts);
299 static gint compose_write_body_to_file (Compose *compose,
301 static gint compose_remove_reedit_target (Compose *compose,
303 static void compose_remove_draft (Compose *compose);
304 static gint compose_queue_sub (Compose *compose,
308 gboolean check_subject,
309 gboolean remove_reedit_target);
310 static void compose_add_attachments (Compose *compose,
312 static gchar *compose_get_header (Compose *compose);
314 static void compose_convert_header (Compose *compose,
319 gboolean addr_field);
321 static void compose_attach_info_free (AttachInfo *ainfo);
322 static void compose_attach_remove_selected (Compose *compose);
324 static void compose_attach_property (Compose *compose);
325 static void compose_attach_property_create (gboolean *cancelled);
326 static void attach_property_ok (GtkWidget *widget,
327 gboolean *cancelled);
328 static void attach_property_cancel (GtkWidget *widget,
329 gboolean *cancelled);
330 static gint attach_property_delete_event (GtkWidget *widget,
332 gboolean *cancelled);
333 static gboolean attach_property_key_pressed (GtkWidget *widget,
335 gboolean *cancelled);
337 static void compose_exec_ext_editor (Compose *compose);
339 static gint compose_exec_ext_editor_real (const gchar *file);
340 static gboolean compose_ext_editor_kill (Compose *compose);
341 static gboolean compose_input_cb (GIOChannel *source,
342 GIOCondition condition,
344 static void compose_set_ext_editor_sensitive (Compose *compose,
346 #endif /* G_OS_UNIX */
348 static void compose_undo_state_changed (UndoMain *undostruct,
353 static void compose_create_header_entry (Compose *compose);
354 static void compose_add_header_entry (Compose *compose, const gchar *header, gchar *text);
355 static void compose_remove_header_entries(Compose *compose);
357 static void compose_update_priority_menu_item(Compose * compose);
359 static void compose_spell_menu_changed (void *data);
361 static void compose_add_field_list ( Compose *compose,
362 GList *listAddress );
364 /* callback functions */
366 static gboolean compose_edit_size_alloc (GtkEditable *widget,
367 GtkAllocation *allocation,
368 GtkSHRuler *shruler);
369 static void account_activated (GtkComboBox *optmenu,
371 static void attach_selected (GtkTreeView *tree_view,
372 GtkTreePath *tree_path,
373 GtkTreeViewColumn *column,
375 static gboolean attach_button_pressed (GtkWidget *widget,
376 GdkEventButton *event,
378 static gboolean attach_key_pressed (GtkWidget *widget,
381 static void compose_send_cb (gpointer data,
384 static void compose_send_later_cb (gpointer data,
388 static void compose_draft_cb (gpointer data,
392 static void compose_attach_cb (gpointer data,
395 static void compose_insert_file_cb (gpointer data,
398 static void compose_insert_sig_cb (gpointer data,
402 static void compose_close_cb (gpointer data,
406 static void compose_set_encoding_cb (gpointer data,
410 static void compose_address_cb (gpointer data,
413 static void compose_template_activate_cb(GtkWidget *widget,
416 static void compose_ext_editor_cb (gpointer data,
420 static gint compose_delete_cb (GtkWidget *widget,
424 static void compose_undo_cb (Compose *compose);
425 static void compose_redo_cb (Compose *compose);
426 static void compose_cut_cb (Compose *compose);
427 static void compose_copy_cb (Compose *compose);
428 static void compose_paste_cb (Compose *compose);
429 static void compose_paste_as_quote_cb (Compose *compose);
430 static void compose_paste_no_wrap_cb (Compose *compose);
431 static void compose_paste_wrap_cb (Compose *compose);
432 static void compose_allsel_cb (Compose *compose);
434 static void compose_advanced_action_cb (Compose *compose,
435 ComposeCallAdvancedAction action);
437 static void compose_grab_focus_cb (GtkWidget *widget,
440 static void compose_changed_cb (GtkTextBuffer *textbuf,
443 static void compose_wrap_cb (gpointer data,
446 static void compose_find_cb (gpointer data,
449 static void compose_toggle_autowrap_cb (gpointer data,
453 static void compose_toggle_ruler_cb (gpointer data,
456 static void compose_toggle_sign_cb (gpointer data,
459 static void compose_toggle_encrypt_cb (gpointer data,
462 static void compose_set_privacy_system_cb(GtkWidget *widget,
464 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn);
465 static void activate_privacy_system (Compose *compose,
466 PrefsAccount *account,
468 static void compose_use_signing(Compose *compose, gboolean use_signing);
469 static void compose_use_encryption(Compose *compose, gboolean use_encryption);
470 static void compose_toggle_return_receipt_cb(gpointer data, guint action,
472 static void compose_toggle_remove_refs_cb(gpointer data, guint action,
474 static void compose_set_priority_cb (gpointer data,
477 static void compose_reply_change_mode (gpointer data,
481 static void compose_attach_drag_received_cb (GtkWidget *widget,
482 GdkDragContext *drag_context,
485 GtkSelectionData *data,
489 static void compose_insert_drag_received_cb (GtkWidget *widget,
490 GdkDragContext *drag_context,
493 GtkSelectionData *data,
497 static void compose_header_drag_received_cb (GtkWidget *widget,
498 GdkDragContext *drag_context,
501 GtkSelectionData *data,
506 static gboolean compose_drag_drop (GtkWidget *widget,
507 GdkDragContext *drag_context,
509 guint time, gpointer user_data);
511 static void text_inserted (GtkTextBuffer *buffer,
516 static Compose *compose_generic_reply(MsgInfo *msginfo,
517 ComposeQuoteMode quote_mode,
521 gboolean followup_and_reply_to,
524 static gboolean compose_headerentry_changed_cb (GtkWidget *entry,
525 ComposeHeaderEntry *headerentry);
526 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
528 ComposeHeaderEntry *headerentry);
530 static void compose_show_first_last_header (Compose *compose, gboolean show_first);
532 static void compose_allow_user_actions (Compose *compose, gboolean allow);
535 static void compose_check_all (Compose *compose);
536 static void compose_highlight_all (Compose *compose);
537 static void compose_check_backwards (Compose *compose);
538 static void compose_check_forwards_go (Compose *compose);
541 static gint compose_defer_auto_save_draft (Compose *compose);
542 static PrefsAccount *compose_guess_forward_account_from_msginfo (MsgInfo *msginfo);
544 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose);
547 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
548 FolderItem *folder_item);
550 static void compose_attach_update_label(Compose *compose);
552 static void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data);
554 static GtkItemFactoryEntry compose_popup_entries[] =
556 {N_("/_Add..."), NULL, compose_attach_cb, 0, NULL},
557 {N_("/_Remove"), NULL, compose_attach_remove_selected, 0, NULL},
558 {"/---", NULL, NULL, 0, "<Separator>"},
559 {N_("/_Properties..."), NULL, compose_attach_property, 0, NULL}
562 static GtkItemFactoryEntry compose_entries[] =
564 {N_("/_Message"), NULL, NULL, 0, "<Branch>"},
565 {N_("/_Message/S_end"), "<control>Return",
566 compose_send_cb, 0, NULL},
567 {N_("/_Message/Send _later"), "<shift><control>S",
568 compose_send_later_cb, 0, NULL},
569 {N_("/_Message/---"), NULL, NULL, 0, "<Separator>"},
570 {N_("/_Message/_Attach file"), "<control>M", compose_attach_cb, 0, NULL},
571 {N_("/_Message/_Insert file"), "<control>I", compose_insert_file_cb, 0, NULL},
572 {N_("/_Message/Insert si_gnature"), "<control>G", compose_insert_sig_cb, 0, NULL},
573 {N_("/_Message/---"), NULL, NULL, 0, "<Separator>"},
574 {N_("/_Message/_Save"),
575 "<control>S", compose_draft_cb, COMPOSE_KEEP_EDITING, NULL},
576 {N_("/_Message/---"), NULL, NULL, 0, "<Separator>"},
577 {N_("/_Message/_Close"), "<control>W", compose_close_cb, 0, NULL},
579 {N_("/_Edit"), NULL, NULL, 0, "<Branch>"},
580 {N_("/_Edit/_Undo"), "<control>Z", compose_undo_cb, 0, NULL},
581 {N_("/_Edit/_Redo"), "<control>Y", compose_redo_cb, 0, NULL},
582 {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
583 {N_("/_Edit/Cu_t"), "<control>X", compose_cut_cb, 0, NULL},
584 {N_("/_Edit/_Copy"), "<control>C", compose_copy_cb, 0, NULL},
585 {N_("/_Edit/_Paste"), "<control>V", compose_paste_cb, 0, NULL},
586 {N_("/_Edit/Special paste"), NULL, NULL, 0, "<Branch>"},
587 {N_("/_Edit/Special paste/as _quotation"),
588 NULL, compose_paste_as_quote_cb, 0, NULL},
589 {N_("/_Edit/Special paste/_wrapped"),
590 NULL, compose_paste_wrap_cb, 0, NULL},
591 {N_("/_Edit/Special paste/_unwrapped"),
592 NULL, compose_paste_no_wrap_cb, 0, NULL},
593 {N_("/_Edit/Select _all"), "<control>A", compose_allsel_cb, 0, NULL},
594 {N_("/_Edit/A_dvanced"), NULL, NULL, 0, "<Branch>"},
595 {N_("/_Edit/A_dvanced/Move a character backward"),
597 compose_advanced_action_cb,
598 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER,
600 {N_("/_Edit/A_dvanced/Move a character forward"),
602 compose_advanced_action_cb,
603 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER,
605 {N_("/_Edit/A_dvanced/Move a word backward"),
607 compose_advanced_action_cb,
608 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD,
610 {N_("/_Edit/A_dvanced/Move a word forward"),
612 compose_advanced_action_cb,
613 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD,
615 {N_("/_Edit/A_dvanced/Move to beginning of line"),
616 NULL, /* "<control>A" */
617 compose_advanced_action_cb,
618 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE,
620 {N_("/_Edit/A_dvanced/Move to end of line"),
622 compose_advanced_action_cb,
623 COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE,
625 {N_("/_Edit/A_dvanced/Move to previous line"),
627 compose_advanced_action_cb,
628 COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE,
630 {N_("/_Edit/A_dvanced/Move to next line"),
632 compose_advanced_action_cb,
633 COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE,
635 {N_("/_Edit/A_dvanced/Delete a character backward"),
637 compose_advanced_action_cb,
638 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER,
640 {N_("/_Edit/A_dvanced/Delete a character forward"),
642 compose_advanced_action_cb,
643 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER,
645 {N_("/_Edit/A_dvanced/Delete a word backward"),
646 NULL, /* "<control>W" */
647 compose_advanced_action_cb,
648 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD,
650 {N_("/_Edit/A_dvanced/Delete a word forward"),
651 NULL, /* "<alt>D", */
652 compose_advanced_action_cb,
653 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD,
655 {N_("/_Edit/A_dvanced/Delete line"),
657 compose_advanced_action_cb,
658 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE,
660 {N_("/_Edit/A_dvanced/Delete to end of line"),
662 compose_advanced_action_cb,
663 COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END,
665 {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
667 "<control>F", compose_find_cb, 0, NULL},
668 {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
669 {N_("/_Edit/_Wrap current paragraph"),
670 "<control>L", compose_wrap_cb, 0, NULL},
671 {N_("/_Edit/Wrap all long _lines"),
672 "<control><alt>L", compose_wrap_cb, 1, NULL},
673 {N_("/_Edit/Aut_o wrapping"), "<shift><control>L", compose_toggle_autowrap_cb, 0, "<ToggleItem>"},
674 {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
675 {N_("/_Edit/Edit with e_xternal editor"),
676 "<shift><control>X", compose_ext_editor_cb, 0, NULL},
678 {N_("/_Spelling"), NULL, NULL, 0, "<Branch>"},
679 {N_("/_Spelling/_Check all or check selection"),
680 NULL, compose_check_all, 0, NULL},
681 {N_("/_Spelling/_Highlight all misspelled words"),
682 NULL, compose_highlight_all, 0, NULL},
683 {N_("/_Spelling/Check _backwards misspelled word"),
684 NULL, compose_check_backwards , 0, NULL},
685 {N_("/_Spelling/_Forward to next misspelled word"),
686 NULL, compose_check_forwards_go, 0, NULL},
687 {N_("/_Spelling/---"), NULL, NULL, 0, "<Separator>"},
688 {N_("/_Spelling/Options"),
689 NULL, NULL, 0, "<Branch>"},
691 {N_("/_Options"), NULL, NULL, 0, "<Branch>"},
692 {N_("/_Options/Reply _mode"), NULL, NULL, 0, "<Branch>"},
693 {N_("/_Options/Reply _mode/_Normal"), NULL, compose_reply_change_mode, COMPOSE_REPLY, "<RadioItem>"},
694 {N_("/_Options/Reply _mode/_All"), NULL, compose_reply_change_mode, COMPOSE_REPLY_TO_ALL, "/Options/Reply mode/Normal"},
695 {N_("/_Options/Reply _mode/_Sender"), NULL, compose_reply_change_mode, COMPOSE_REPLY_TO_SENDER, "/Options/Reply mode/Normal"},
696 {N_("/_Options/Reply _mode/_Mailing-list"), NULL, compose_reply_change_mode, COMPOSE_REPLY_TO_LIST, "/Options/Reply mode/Normal"},
697 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
698 {N_("/_Options/Privacy _System"), NULL, NULL, 0, "<Branch>"},
699 {N_("/_Options/Privacy _System/None"), NULL, NULL, 0, "<RadioItem>"},
700 {N_("/_Options/Si_gn"), NULL, compose_toggle_sign_cb , 0, "<ToggleItem>"},
701 {N_("/_Options/_Encrypt"), NULL, compose_toggle_encrypt_cb, 0, "<ToggleItem>"},
702 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
703 {N_("/_Options/_Priority"), NULL, NULL, 0, "<Branch>"},
704 {N_("/_Options/Priority/_Highest"), NULL, compose_set_priority_cb, PRIORITY_HIGHEST, "<RadioItem>"},
705 {N_("/_Options/Priority/Hi_gh"), NULL, compose_set_priority_cb, PRIORITY_HIGH, "/Options/Priority/Highest"},
706 {N_("/_Options/Priority/_Normal"), NULL, compose_set_priority_cb, PRIORITY_NORMAL, "/Options/Priority/Highest"},
707 {N_("/_Options/Priority/Lo_w"), NULL, compose_set_priority_cb, PRIORITY_LOW, "/Options/Priority/Highest"},
708 {N_("/_Options/Priority/_Lowest"), NULL, compose_set_priority_cb, PRIORITY_LOWEST, "/Options/Priority/Highest"},
709 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
710 {N_("/_Options/_Request Return Receipt"), NULL, compose_toggle_return_receipt_cb, 0, "<ToggleItem>"},
711 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
712 {N_("/_Options/Remo_ve references"), NULL, compose_toggle_remove_refs_cb, 0, "<ToggleItem>"},
713 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
715 #define ENC_ACTION(action) \
716 NULL, compose_set_encoding_cb, action, \
717 "/Options/Character encoding/Automatic"
719 {N_("/_Options/Character _encoding"), NULL, NULL, 0, "<Branch>"},
720 {N_("/_Options/Character _encoding/_Automatic"),
721 NULL, compose_set_encoding_cb, C_AUTO, "<RadioItem>"},
722 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
724 {N_("/_Options/Character _encoding/7bit ASCII (US-ASC_II)"),
725 ENC_ACTION(C_US_ASCII)},
726 {N_("/_Options/Character _encoding/Unicode (_UTF-8)"),
727 ENC_ACTION(C_UTF_8)},
728 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
730 {N_("/_Options/Character _encoding/Western European"), NULL, NULL, 0, "<Branch>"},
731 {N_("/_Options/Character _encoding/Western European/ISO-8859-_1"),
732 ENC_ACTION(C_ISO_8859_1)},
733 {N_("/_Options/Character _encoding/Western European/ISO-8859-15"),
734 ENC_ACTION(C_ISO_8859_15)},
735 {N_("/_Options/Character _encoding/Western European/Windows-1252"),
736 ENC_ACTION(C_WINDOWS_1252)},
738 {N_("/_Options/Character _encoding/Central European (ISO-8859-_2)"),
739 ENC_ACTION(C_ISO_8859_2)},
741 {N_("/_Options/Character _encoding/Baltic"), NULL, NULL, 0, "<Branch>"},
742 {N_("/_Options/Character _encoding/Baltic/ISO-8859-13"),
743 ENC_ACTION(C_ISO_8859_13)},
744 {N_("/_Options/Character _encoding/Baltic/ISO-8859-_4"),
745 ENC_ACTION(C_ISO_8859_4)},
747 {N_("/_Options/Character _encoding/Greek (ISO-8859-_7)"),
748 ENC_ACTION(C_ISO_8859_7)},
750 {N_("/_Options/Character _encoding/Hebrew"), NULL, NULL, 0, "<Branch>"},
751 {N_("/_Options/Character _encoding/Hebrew/ISO-8859-_8"),
752 ENC_ACTION(C_ISO_8859_8)},
753 {N_("/_Options/Character _encoding/Hebrew/Windows-1255"),
754 ENC_ACTION(C_WINDOWS_1255)},
756 {N_("/_Options/Character _encoding/Arabic"), NULL, NULL, 0, "<Branch>"},
757 {N_("/_Options/Character _encoding/Arabic/ISO-8859-_6"),
758 ENC_ACTION(C_ISO_8859_6)},
759 {N_("/_Options/Character _encoding/Arabic/Windows-1256"),
760 ENC_ACTION(C_CP1256)},
762 {N_("/_Options/Character _encoding/Turkish (ISO-8859-_9)"),
763 ENC_ACTION(C_ISO_8859_9)},
765 {N_("/_Options/Character _encoding/Cyrillic"), NULL, NULL, 0, "<Branch>"},
766 {N_("/_Options/Character _encoding/Cyrillic/ISO-8859-_5"),
767 ENC_ACTION(C_ISO_8859_5)},
768 {N_("/_Options/Character _encoding/Cyrillic/KOI8-_R"),
769 ENC_ACTION(C_KOI8_R)},
770 {N_("/_Options/Character _encoding/Cyrillic/KOI8-U"),
771 ENC_ACTION(C_KOI8_U)},
772 {N_("/_Options/Character _encoding/Cyrillic/Windows-1251"),
773 ENC_ACTION(C_WINDOWS_1251)},
775 {N_("/_Options/Character _encoding/Japanese"), NULL, NULL, 0, "<Branch>"},
776 {N_("/_Options/Character _encoding/Japanese/ISO-2022-_JP"),
777 ENC_ACTION(C_ISO_2022_JP)},
778 {N_("/_Options/Character _encoding/Japanese/ISO-2022-JP-2"),
779 ENC_ACTION(C_ISO_2022_JP_2)},
780 {N_("/_Options/Character _encoding/Japanese/_EUC-JP"),
781 ENC_ACTION(C_EUC_JP)},
782 {N_("/_Options/Character _encoding/Japanese/_Shift__JIS"),
783 ENC_ACTION(C_SHIFT_JIS)},
785 {N_("/_Options/Character _encoding/Chinese"), NULL, NULL, 0, "<Branch>"},
786 {N_("/_Options/Character _encoding/Chinese/Simplified (_GB2312)"),
787 ENC_ACTION(C_GB2312)},
788 {N_("/_Options/Character _encoding/Chinese/Simplified (GBK)"),
790 {N_("/_Options/Character _encoding/Chinese/Traditional (_Big5)"),
792 {N_("/_Options/Character _encoding/Chinese/Traditional (EUC-_TW)"),
793 ENC_ACTION(C_EUC_TW)},
795 {N_("/_Options/Character _encoding/Korean"), NULL, NULL, 0, "<Branch>"},
796 {N_("/_Options/Character _encoding/Korean/EUC-_KR"),
797 ENC_ACTION(C_EUC_KR)},
798 {N_("/_Options/Character _encoding/Korean/ISO-2022-KR"),
799 ENC_ACTION(C_ISO_2022_KR)},
801 {N_("/_Options/Character _encoding/Thai"), NULL, NULL, 0, "<Branch>"},
802 {N_("/_Options/Character _encoding/Thai/TIS-620"),
803 ENC_ACTION(C_TIS_620)},
804 {N_("/_Options/Character _encoding/Thai/Windows-874"),
805 ENC_ACTION(C_WINDOWS_874)},
807 {N_("/_Tools"), NULL, NULL, 0, "<Branch>"},
808 {N_("/_Tools/Show _ruler"), NULL, compose_toggle_ruler_cb, 0, "<ToggleItem>"},
809 {N_("/_Tools/_Address book"), "<shift><control>A", compose_address_cb , 0, NULL},
810 {N_("/_Tools/_Template"), NULL, NULL, 0, "<Branch>"},
811 {N_("/_Tools/Actio_ns"), NULL, NULL, 0, "<Branch>"},
812 {N_("/_Help"), NULL, NULL, 0, "<Branch>"},
813 {N_("/_Help/_About"), NULL, about_show, 0, NULL}
816 static GtkTargetEntry compose_mime_types[] =
818 {"text/uri-list", 0, 0},
819 {"UTF8_STRING", 0, 0},
823 static gboolean compose_put_existing_to_front(MsgInfo *info)
825 GList *compose_list = compose_get_compose_list();
829 for (elem = compose_list; elem != NULL && elem->data != NULL;
831 Compose *c = (Compose*)elem->data;
833 if (!c->targetinfo || !c->targetinfo->msgid ||
837 if (!strcmp(c->targetinfo->msgid, info->msgid)) {
838 gtkut_window_popup(c->window);
846 static GdkColor quote_color1 =
847 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
848 static GdkColor quote_color2 =
849 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
850 static GdkColor quote_color3 =
851 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
853 static GdkColor quote_bgcolor1 =
854 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
855 static GdkColor quote_bgcolor2 =
856 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
857 static GdkColor quote_bgcolor3 =
858 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
860 static GdkColor signature_color = {
867 static GdkColor uri_color = {
874 static void compose_create_tags(GtkTextView *text, Compose *compose)
876 GtkTextBuffer *buffer;
877 GdkColor black = {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
883 buffer = gtk_text_view_get_buffer(text);
885 if (prefs_common.enable_color) {
886 /* grab the quote colors, converting from an int to a GdkColor */
887 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
889 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
891 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
893 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_bgcol,
895 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_bgcol,
897 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_bgcol,
899 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
901 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
904 signature_color = quote_color1 = quote_color2 = quote_color3 =
905 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
908 if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
909 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
910 "foreground-gdk", "e_color1,
911 "paragraph-background-gdk", "e_bgcolor1,
913 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
914 "foreground-gdk", "e_color2,
915 "paragraph-background-gdk", "e_bgcolor2,
917 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
918 "foreground-gdk", "e_color3,
919 "paragraph-background-gdk", "e_bgcolor3,
922 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
923 "foreground-gdk", "e_color1,
925 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
926 "foreground-gdk", "e_color2,
928 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
929 "foreground-gdk", "e_color3,
933 compose->signature_tag = gtk_text_buffer_create_tag(buffer, "signature",
934 "foreground-gdk", &signature_color,
937 compose->uri_tag = gtk_text_buffer_create_tag(buffer, "link",
938 "foreground-gdk", &uri_color,
940 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
941 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
943 color[0] = quote_color1;
944 color[1] = quote_color2;
945 color[2] = quote_color3;
946 color[3] = quote_bgcolor1;
947 color[4] = quote_bgcolor2;
948 color[5] = quote_bgcolor3;
949 color[6] = signature_color;
950 color[7] = uri_color;
951 cmap = gdk_drawable_get_colormap(compose->window->window);
952 gdk_colormap_alloc_colors(cmap, color, 8, FALSE, TRUE, success);
954 for (i = 0; i < 8; i++) {
955 if (success[i] == FALSE) {
958 g_warning("Compose: color allocation failed.\n");
959 style = gtk_widget_get_style(GTK_WIDGET(text));
960 quote_color1 = quote_color2 = quote_color3 =
961 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 =
962 signature_color = uri_color = black;
967 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
968 GPtrArray *attach_files)
970 return compose_generic_new(account, mailto, NULL, attach_files, NULL);
973 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
975 return compose_generic_new(account, mailto, item, NULL, NULL);
978 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
980 return compose_generic_new( account, NULL, NULL, NULL, listAddress );
983 #define SCROLL_TO_CURSOR(compose) { \
984 GtkTextMark *cmark = gtk_text_buffer_get_insert( \
985 gtk_text_view_get_buffer( \
986 GTK_TEXT_VIEW(compose->text))); \
987 gtk_text_view_scroll_mark_onscreen( \
988 GTK_TEXT_VIEW(compose->text), \
992 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
993 GPtrArray *attach_files, GList *listAddress )
996 GtkTextView *textview;
997 GtkTextBuffer *textbuf;
999 GtkItemFactory *ifactory;
1000 const gchar *subject_format = NULL;
1001 const gchar *body_format = NULL;
1002 gchar *mailto_from = NULL;
1003 PrefsAccount *mailto_account = NULL;
1004 MsgInfo* dummyinfo = NULL;
1006 /* check if mailto defines a from */
1007 if (mailto && *mailto != '\0') {
1008 scan_mailto_url(mailto, &mailto_from, NULL, NULL, NULL, NULL, NULL, NULL);
1009 /* mailto defines a from, check if we can get account prefs from it,
1010 if not, the account prefs will be guessed using other ways, but we'll keep
1013 mailto_account = account_find_from_address(mailto_from, TRUE);
1015 account = mailto_account;
1018 /* if no account prefs set from mailto, set if from folder prefs (if any) */
1019 if (!mailto_account && item && item->prefs && item->prefs->enable_default_account)
1020 account = account_find_from_id(item->prefs->default_account);
1022 /* if no account prefs set, fallback to the current one */
1023 if (!account) account = cur_account;
1024 g_return_val_if_fail(account != NULL, NULL);
1026 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
1028 /* override from name if mailto asked for it */
1030 gtk_entry_set_text(GTK_ENTRY(compose->from_name), mailto_from);
1031 g_free(mailto_from);
1033 /* override from name according to folder properties */
1034 if (item && item->prefs &&
1035 item->prefs->compose_with_format &&
1036 item->prefs->compose_override_from_format &&
1037 *item->prefs->compose_override_from_format != '\0') {
1042 dummyinfo = compose_msginfo_new_from_compose(compose);
1044 /* decode \-escape sequences in the internal representation of the quote format */
1045 tmp = malloc(strlen(item->prefs->compose_override_from_format)+1);
1046 pref_get_unescaped_pref(tmp, item->prefs->compose_override_from_format);
1049 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1050 compose->gtkaspell);
1052 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1054 quote_fmt_scan_string(tmp);
1057 buf = quote_fmt_get_buffer();
1059 alertpanel_error(_("New message From format error."));
1061 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1062 quote_fmt_reset_vartable();
1067 ifactory = gtk_item_factory_from_widget(compose->menubar);
1069 compose->replyinfo = NULL;
1070 compose->fwdinfo = NULL;
1072 textview = GTK_TEXT_VIEW(compose->text);
1073 textbuf = gtk_text_view_get_buffer(textview);
1074 compose_create_tags(textview, compose);
1076 undo_block(compose->undostruct);
1078 compose_set_dictionaries_from_folder_prefs(compose, item);
1081 if (account->auto_sig)
1082 compose_insert_sig(compose, FALSE);
1083 gtk_text_buffer_get_start_iter(textbuf, &iter);
1084 gtk_text_buffer_place_cursor(textbuf, &iter);
1086 if (account->protocol != A_NNTP) {
1087 if (mailto && *mailto != '\0') {
1088 compose_entries_set(compose, mailto, COMPOSE_TO);
1090 } else if (item && item->prefs->enable_default_to) {
1091 compose_entry_append(compose, item->prefs->default_to, COMPOSE_TO);
1092 compose_entry_mark_default_to(compose, item->prefs->default_to);
1094 if (item && item->ret_rcpt) {
1095 menu_set_active(ifactory, "/Options/Request Return Receipt", TRUE);
1098 if (mailto && *mailto != '\0') {
1099 if (!strchr(mailto, '@'))
1100 compose_entries_set(compose, mailto, COMPOSE_NEWSGROUPS);
1102 compose_entries_set(compose, mailto, COMPOSE_TO);
1103 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1104 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS);
1107 * CLAWS: just don't allow return receipt request, even if the user
1108 * may want to send an email. simple but foolproof.
1110 menu_set_sensitive(ifactory, "/Options/Request Return Receipt", FALSE);
1112 compose_add_field_list( compose, listAddress );
1114 if (item && item->prefs && item->prefs->compose_with_format) {
1115 subject_format = item->prefs->compose_subject_format;
1116 body_format = item->prefs->compose_body_format;
1117 } else if (account->compose_with_format) {
1118 subject_format = account->compose_subject_format;
1119 body_format = account->compose_body_format;
1120 } else if (prefs_common.compose_with_format) {
1121 subject_format = prefs_common.compose_subject_format;
1122 body_format = prefs_common.compose_body_format;
1125 if (subject_format || body_format) {
1128 && *subject_format != '\0' )
1130 gchar *subject = NULL;
1135 dummyinfo = compose_msginfo_new_from_compose(compose);
1137 /* decode \-escape sequences in the internal representation of the quote format */
1138 tmp = malloc(strlen(subject_format)+1);
1139 pref_get_unescaped_pref(tmp, subject_format);
1141 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1143 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE,
1144 compose->gtkaspell);
1146 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE);
1148 quote_fmt_scan_string(tmp);
1151 buf = quote_fmt_get_buffer();
1153 alertpanel_error(_("New message subject format error."));
1155 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1156 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1157 quote_fmt_reset_vartable();
1164 && *body_format != '\0' )
1167 GtkTextBuffer *buffer;
1168 GtkTextIter start, end;
1172 dummyinfo = compose_msginfo_new_from_compose(compose);
1174 text = GTK_TEXT_VIEW(compose->text);
1175 buffer = gtk_text_view_get_buffer(text);
1176 gtk_text_buffer_get_start_iter(buffer, &start);
1177 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1178 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1180 compose_quote_fmt(compose, dummyinfo,
1182 NULL, tmp, FALSE, TRUE,
1183 _("New message body format error at line %d."));
1184 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1185 quote_fmt_reset_vartable();
1191 procmsg_msginfo_free( dummyinfo );
1197 for (i = 0; i < attach_files->len; i++) {
1198 file = g_ptr_array_index(attach_files, i);
1199 compose_attach_append(compose, file, file, NULL);
1203 compose_show_first_last_header(compose, TRUE);
1205 /* Set save folder */
1206 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1207 gchar *folderidentifier;
1209 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
1210 folderidentifier = folder_item_get_identifier(item);
1211 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
1212 g_free(folderidentifier);
1215 gtk_widget_grab_focus(compose->header_last->entry);
1217 undo_unblock(compose->undostruct);
1219 if (prefs_common.auto_exteditor)
1220 compose_exec_ext_editor(compose);
1222 compose->draft_timeout_tag = -1;
1223 SCROLL_TO_CURSOR(compose);
1225 compose->modified = FALSE;
1226 compose_set_title(compose);
1230 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1231 gboolean override_pref)
1233 gchar *privacy = NULL;
1235 g_return_if_fail(compose != NULL);
1236 g_return_if_fail(account != NULL);
1238 if (override_pref == FALSE && account->default_encrypt_reply == FALSE)
1241 if (account->default_privacy_system
1242 && strlen(account->default_privacy_system)) {
1243 privacy = account->default_privacy_system;
1245 GSList *privacy_avail = privacy_get_system_ids();
1246 if (privacy_avail && g_slist_length(privacy_avail)) {
1247 privacy = (gchar *)(privacy_avail->data);
1250 if (privacy != NULL) {
1251 if (compose->privacy_system == NULL)
1252 compose->privacy_system = g_strdup(privacy);
1253 else if (*(compose->privacy_system) == '\0') {
1254 g_free(compose->privacy_system);
1255 compose->privacy_system = g_strdup(privacy);
1257 compose_update_privacy_system_menu_item(compose, FALSE);
1258 compose_use_encryption(compose, TRUE);
1262 static void compose_force_signing(Compose *compose, PrefsAccount *account)
1264 gchar *privacy = NULL;
1266 if (account->default_privacy_system
1267 && strlen(account->default_privacy_system)) {
1268 privacy = account->default_privacy_system;
1270 GSList *privacy_avail = privacy_get_system_ids();
1271 if (privacy_avail && g_slist_length(privacy_avail)) {
1272 privacy = (gchar *)(privacy_avail->data);
1275 if (privacy != NULL) {
1276 if (compose->privacy_system == NULL)
1277 compose->privacy_system = g_strdup(privacy);
1278 compose_update_privacy_system_menu_item(compose, FALSE);
1279 compose_use_signing(compose, TRUE);
1283 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1287 Compose *compose = NULL;
1288 GtkItemFactory *ifactory = NULL;
1290 g_return_val_if_fail(msginfo_list != NULL, NULL);
1292 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1293 g_return_val_if_fail(msginfo != NULL, NULL);
1295 list_len = g_slist_length(msginfo_list);
1299 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1300 FALSE, prefs_common.default_reply_list, FALSE, body);
1302 case COMPOSE_REPLY_WITH_QUOTE:
1303 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1304 FALSE, prefs_common.default_reply_list, FALSE, body);
1306 case COMPOSE_REPLY_WITHOUT_QUOTE:
1307 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1308 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1310 case COMPOSE_REPLY_TO_SENDER:
1311 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1312 FALSE, FALSE, TRUE, body);
1314 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1315 compose = compose_followup_and_reply_to(msginfo,
1316 COMPOSE_QUOTE_CHECK,
1317 FALSE, FALSE, body);
1319 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1320 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1321 FALSE, FALSE, TRUE, body);
1323 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1324 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1325 FALSE, FALSE, TRUE, NULL);
1327 case COMPOSE_REPLY_TO_ALL:
1328 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1329 TRUE, FALSE, FALSE, body);
1331 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1332 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1333 TRUE, FALSE, FALSE, body);
1335 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1336 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1337 TRUE, FALSE, FALSE, NULL);
1339 case COMPOSE_REPLY_TO_LIST:
1340 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1341 FALSE, TRUE, FALSE, body);
1343 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1344 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1345 FALSE, TRUE, FALSE, body);
1347 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1348 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1349 FALSE, TRUE, FALSE, NULL);
1351 case COMPOSE_FORWARD:
1352 if (prefs_common.forward_as_attachment) {
1353 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1356 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1360 case COMPOSE_FORWARD_INLINE:
1361 /* check if we reply to more than one Message */
1362 if (list_len == 1) {
1363 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1366 /* more messages FALL THROUGH */
1367 case COMPOSE_FORWARD_AS_ATTACH:
1368 compose = compose_forward_multiple(NULL, msginfo_list);
1370 case COMPOSE_REDIRECT:
1371 compose = compose_redirect(NULL, msginfo, FALSE);
1374 g_warning("compose_reply_mode(): invalid Compose Mode: %d\n", mode);
1377 if (compose == NULL) {
1378 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1381 ifactory = gtk_item_factory_from_widget(compose->menubar);
1383 compose->rmode = mode;
1384 switch (compose->rmode) {
1386 case COMPOSE_REPLY_WITH_QUOTE:
1387 case COMPOSE_REPLY_WITHOUT_QUOTE:
1388 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1389 debug_print("reply mode Normal\n");
1390 menu_set_active(ifactory, "/Options/Reply mode/Normal", TRUE);
1391 compose_reply_change_mode(compose, COMPOSE_REPLY, NULL); /* force update */
1393 case COMPOSE_REPLY_TO_SENDER:
1394 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1395 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1396 debug_print("reply mode Sender\n");
1397 menu_set_active(ifactory, "/Options/Reply mode/Sender", TRUE);
1399 case COMPOSE_REPLY_TO_ALL:
1400 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1401 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1402 debug_print("reply mode All\n");
1403 menu_set_active(ifactory, "/Options/Reply mode/All", TRUE);
1405 case COMPOSE_REPLY_TO_LIST:
1406 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1407 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1408 debug_print("reply mode List\n");
1409 menu_set_active(ifactory, "/Options/Reply mode/Mailing-list", TRUE);
1417 static Compose *compose_reply(MsgInfo *msginfo,
1418 ComposeQuoteMode quote_mode,
1424 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1425 to_sender, FALSE, body);
1428 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1429 ComposeQuoteMode quote_mode,
1434 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1435 to_sender, TRUE, body);
1438 static void compose_extract_original_charset(Compose *compose)
1440 MsgInfo *info = NULL;
1441 if (compose->replyinfo) {
1442 info = compose->replyinfo;
1443 } else if (compose->fwdinfo) {
1444 info = compose->fwdinfo;
1445 } else if (compose->targetinfo) {
1446 info = compose->targetinfo;
1449 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1450 MimeInfo *partinfo = mimeinfo;
1451 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1452 partinfo = procmime_mimeinfo_next(partinfo);
1454 compose->orig_charset =
1455 g_strdup(procmime_mimeinfo_get_parameter(
1456 partinfo, "charset"));
1458 procmime_mimeinfo_free_all(mimeinfo);
1462 #define SIGNAL_BLOCK(buffer) { \
1463 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1464 G_CALLBACK(compose_changed_cb), \
1466 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1467 G_CALLBACK(text_inserted), \
1471 #define SIGNAL_UNBLOCK(buffer) { \
1472 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1473 G_CALLBACK(compose_changed_cb), \
1475 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1476 G_CALLBACK(text_inserted), \
1480 static Compose *compose_generic_reply(MsgInfo *msginfo,
1481 ComposeQuoteMode quote_mode,
1482 gboolean to_all, gboolean to_ml,
1484 gboolean followup_and_reply_to,
1487 GtkItemFactory *ifactory;
1489 PrefsAccount *account = NULL;
1490 GtkTextView *textview;
1491 GtkTextBuffer *textbuf;
1492 gboolean quote = FALSE;
1493 const gchar *qmark = NULL;
1494 const gchar *body_fmt = NULL;
1496 g_return_val_if_fail(msginfo != NULL, NULL);
1497 g_return_val_if_fail(msginfo->folder != NULL, NULL);
1499 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1501 g_return_val_if_fail(account != NULL, NULL);
1503 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1505 compose->updating = TRUE;
1507 ifactory = gtk_item_factory_from_widget(compose->menubar);
1509 menu_set_active(ifactory, "/Options/Remove references", FALSE);
1510 menu_set_sensitive(ifactory, "/Options/Remove references", TRUE);
1512 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1513 if (!compose->replyinfo)
1514 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1516 compose_extract_original_charset(compose);
1518 if (msginfo->folder && msginfo->folder->ret_rcpt)
1519 menu_set_active(ifactory, "/Options/Request Return Receipt", TRUE);
1521 /* Set save folder */
1522 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1523 gchar *folderidentifier;
1525 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1526 folderidentifier = folder_item_get_identifier(msginfo->folder);
1527 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
1528 g_free(folderidentifier);
1531 if (compose_parse_header(compose, msginfo) < 0) {
1532 compose->updating = FALSE;
1533 compose_destroy(compose);
1537 /* override from name according to folder properties */
1538 if (msginfo->folder && msginfo->folder->prefs &&
1539 msginfo->folder->prefs->reply_with_format &&
1540 msginfo->folder->prefs->reply_override_from_format &&
1541 *msginfo->folder->prefs->reply_override_from_format != '\0') {
1546 /* decode \-escape sequences in the internal representation of the quote format */
1547 tmp = malloc(strlen(msginfo->folder->prefs->reply_override_from_format)+1);
1548 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->reply_override_from_format);
1551 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1552 compose->gtkaspell);
1554 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1556 quote_fmt_scan_string(tmp);
1559 buf = quote_fmt_get_buffer();
1561 alertpanel_error(_("Message reply From format error."));
1563 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1564 quote_fmt_reset_vartable();
1569 textview = (GTK_TEXT_VIEW(compose->text));
1570 textbuf = gtk_text_view_get_buffer(textview);
1571 compose_create_tags(textview, compose);
1573 undo_block(compose->undostruct);
1575 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1578 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1579 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1580 /* use the reply format of folder (if enabled), or the account's one
1581 (if enabled) or fallback to the global reply format, which is always
1582 enabled (even if empty), and use the relevant quotemark */
1584 if (msginfo->folder && msginfo->folder->prefs &&
1585 msginfo->folder->prefs->reply_with_format) {
1586 qmark = msginfo->folder->prefs->reply_quotemark;
1587 body_fmt = msginfo->folder->prefs->reply_body_format;
1589 } else if (account->reply_with_format) {
1590 qmark = account->reply_quotemark;
1591 body_fmt = account->reply_body_format;
1594 qmark = prefs_common.quotemark;
1595 body_fmt = gettext(prefs_common.quotefmt);
1600 /* empty quotemark is not allowed */
1601 if (qmark == NULL || *qmark == '\0')
1603 compose_quote_fmt(compose, compose->replyinfo,
1604 body_fmt, qmark, body, FALSE, TRUE,
1605 _("Message reply format error at line %d."));
1606 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1607 quote_fmt_reset_vartable();
1610 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1611 compose_force_encryption(compose, account, FALSE);
1614 SIGNAL_BLOCK(textbuf);
1616 if (account->auto_sig)
1617 compose_insert_sig(compose, FALSE);
1619 compose_wrap_all(compose);
1621 SIGNAL_UNBLOCK(textbuf);
1623 gtk_widget_grab_focus(compose->text);
1625 undo_unblock(compose->undostruct);
1627 if (prefs_common.auto_exteditor)
1628 compose_exec_ext_editor(compose);
1630 compose->modified = FALSE;
1631 compose_set_title(compose);
1633 compose->updating = FALSE;
1634 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1635 SCROLL_TO_CURSOR(compose);
1637 if (compose->deferred_destroy) {
1638 compose_destroy(compose);
1645 #define INSERT_FW_HEADER(var, hdr) \
1646 if (msginfo->var && *msginfo->var) { \
1647 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1648 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1649 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1652 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1653 gboolean as_attach, const gchar *body,
1654 gboolean no_extedit,
1658 GtkTextView *textview;
1659 GtkTextBuffer *textbuf;
1662 g_return_val_if_fail(msginfo != NULL, NULL);
1663 g_return_val_if_fail(msginfo->folder != NULL, NULL);
1666 !(account = compose_guess_forward_account_from_msginfo
1668 account = cur_account;
1670 compose = compose_create(account, msginfo->folder, COMPOSE_FORWARD, batch);
1672 compose->updating = TRUE;
1673 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1674 if (!compose->fwdinfo)
1675 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1677 compose_extract_original_charset(compose);
1679 if (msginfo->subject && *msginfo->subject) {
1680 gchar *buf, *buf2, *p;
1682 buf = p = g_strdup(msginfo->subject);
1683 p += subject_get_prefix_length(p);
1684 memmove(buf, p, strlen(p) + 1);
1686 buf2 = g_strdup_printf("Fw: %s", buf);
1687 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1693 /* override from name according to folder properties */
1694 if (msginfo->folder && msginfo->folder->prefs &&
1695 msginfo->folder->prefs->forward_with_format &&
1696 msginfo->folder->prefs->forward_override_from_format &&
1697 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1701 MsgInfo *full_msginfo = NULL;
1704 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1706 full_msginfo = procmsg_msginfo_copy(msginfo);
1708 /* decode \-escape sequences in the internal representation of the quote format */
1709 tmp = malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1710 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1713 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1714 compose->gtkaspell);
1716 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1718 quote_fmt_scan_string(tmp);
1721 buf = quote_fmt_get_buffer();
1723 alertpanel_error(_("Message forward From format error."));
1725 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1726 quote_fmt_reset_vartable();
1729 procmsg_msginfo_free(full_msginfo);
1732 textview = GTK_TEXT_VIEW(compose->text);
1733 textbuf = gtk_text_view_get_buffer(textview);
1734 compose_create_tags(textview, compose);
1736 undo_block(compose->undostruct);
1740 msgfile = procmsg_get_message_file(msginfo);
1741 if (!is_file_exist(msgfile))
1742 g_warning("%s: file not exist\n", msgfile);
1744 compose_attach_append(compose, msgfile, msgfile,
1749 const gchar *qmark = NULL;
1750 const gchar *body_fmt = gettext(prefs_common.fw_quotefmt);
1751 MsgInfo *full_msginfo;
1753 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1755 full_msginfo = procmsg_msginfo_copy(msginfo);
1757 /* use the forward format of folder (if enabled), or the account's one
1758 (if enabled) or fallback to the global forward format, which is always
1759 enabled (even if empty), and use the relevant quotemark */
1760 if (msginfo->folder && msginfo->folder->prefs &&
1761 msginfo->folder->prefs->forward_with_format) {
1762 qmark = msginfo->folder->prefs->forward_quotemark;
1763 body_fmt = msginfo->folder->prefs->forward_body_format;
1765 } else if (account->forward_with_format) {
1766 qmark = account->forward_quotemark;
1767 body_fmt = account->forward_body_format;
1770 qmark = prefs_common.fw_quotemark;
1771 body_fmt = gettext(prefs_common.fw_quotefmt);
1774 /* empty quotemark is not allowed */
1775 if (qmark == NULL || *qmark == '\0')
1778 compose_quote_fmt(compose, full_msginfo,
1779 body_fmt, qmark, body, FALSE, TRUE,
1780 _("Message forward format error at line %d."));
1781 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1782 quote_fmt_reset_vartable();
1783 compose_attach_parts(compose, msginfo);
1785 procmsg_msginfo_free(full_msginfo);
1788 SIGNAL_BLOCK(textbuf);
1790 if (account->auto_sig)
1791 compose_insert_sig(compose, FALSE);
1793 compose_wrap_all(compose);
1795 SIGNAL_UNBLOCK(textbuf);
1797 gtk_text_buffer_get_start_iter(textbuf, &iter);
1798 gtk_text_buffer_place_cursor(textbuf, &iter);
1800 gtk_widget_grab_focus(compose->header_last->entry);
1802 if (!no_extedit && prefs_common.auto_exteditor)
1803 compose_exec_ext_editor(compose);
1806 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1807 gchar *folderidentifier;
1809 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1810 folderidentifier = folder_item_get_identifier(msginfo->folder);
1811 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
1812 g_free(folderidentifier);
1815 undo_unblock(compose->undostruct);
1817 compose->modified = FALSE;
1818 compose_set_title(compose);
1820 compose->updating = FALSE;
1821 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1822 SCROLL_TO_CURSOR(compose);
1824 if (compose->deferred_destroy) {
1825 compose_destroy(compose);
1832 #undef INSERT_FW_HEADER
1834 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1837 GtkTextView *textview;
1838 GtkTextBuffer *textbuf;
1842 gboolean single_mail = TRUE;
1844 g_return_val_if_fail(msginfo_list != NULL, NULL);
1846 if (g_slist_length(msginfo_list) > 1)
1847 single_mail = FALSE;
1849 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1850 if (((MsgInfo *)msginfo->data)->folder == NULL)
1853 /* guess account from first selected message */
1855 !(account = compose_guess_forward_account_from_msginfo
1856 (msginfo_list->data)))
1857 account = cur_account;
1859 g_return_val_if_fail(account != NULL, NULL);
1861 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1862 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1863 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1866 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1868 compose->updating = TRUE;
1870 /* override from name according to folder properties */
1871 if (msginfo_list->data) {
1872 MsgInfo *msginfo = msginfo_list->data;
1874 if (msginfo->folder && msginfo->folder->prefs &&
1875 msginfo->folder->prefs->forward_with_format &&
1876 msginfo->folder->prefs->forward_override_from_format &&
1877 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1882 /* decode \-escape sequences in the internal representation of the quote format */
1883 tmp = malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1884 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1887 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1888 compose->gtkaspell);
1890 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1892 quote_fmt_scan_string(tmp);
1895 buf = quote_fmt_get_buffer();
1897 alertpanel_error(_("Message forward From format error."));
1899 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1900 quote_fmt_reset_vartable();
1906 textview = GTK_TEXT_VIEW(compose->text);
1907 textbuf = gtk_text_view_get_buffer(textview);
1908 compose_create_tags(textview, compose);
1910 undo_block(compose->undostruct);
1911 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1912 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
1914 if (!is_file_exist(msgfile))
1915 g_warning("%s: file not exist\n", msgfile);
1917 compose_attach_append(compose, msgfile, msgfile,
1923 MsgInfo *info = (MsgInfo *)msginfo_list->data;
1924 if (info->subject && *info->subject) {
1925 gchar *buf, *buf2, *p;
1927 buf = p = g_strdup(info->subject);
1928 p += subject_get_prefix_length(p);
1929 memmove(buf, p, strlen(p) + 1);
1931 buf2 = g_strdup_printf("Fw: %s", buf);
1932 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1938 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
1939 _("Fw: multiple emails"));
1942 SIGNAL_BLOCK(textbuf);
1944 if (account->auto_sig)
1945 compose_insert_sig(compose, FALSE);
1947 compose_wrap_all(compose);
1949 SIGNAL_UNBLOCK(textbuf);
1951 gtk_text_buffer_get_start_iter(textbuf, &iter);
1952 gtk_text_buffer_place_cursor(textbuf, &iter);
1954 gtk_widget_grab_focus(compose->header_last->entry);
1955 undo_unblock(compose->undostruct);
1956 compose->modified = FALSE;
1957 compose_set_title(compose);
1959 compose->updating = FALSE;
1960 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1961 SCROLL_TO_CURSOR(compose);
1963 if (compose->deferred_destroy) {
1964 compose_destroy(compose);
1971 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
1973 GtkTextIter start = *iter;
1974 GtkTextIter end_iter;
1975 int start_pos = gtk_text_iter_get_offset(&start);
1977 if (!compose->account->sig_sep)
1980 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
1981 start_pos+strlen(compose->account->sig_sep));
1983 /* check sig separator */
1984 str = gtk_text_iter_get_text(&start, &end_iter);
1985 if (!strcmp(str, compose->account->sig_sep)) {
1987 /* check end of line (\n) */
1988 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
1989 start_pos+strlen(compose->account->sig_sep));
1990 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
1991 start_pos+strlen(compose->account->sig_sep)+1);
1992 tmp = gtk_text_iter_get_text(&start, &end_iter);
1993 if (!strcmp(tmp,"\n")) {
2005 static void compose_colorize_signature(Compose *compose)
2007 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
2009 GtkTextIter end_iter;
2010 gtk_text_buffer_get_start_iter(buffer, &iter);
2011 while (gtk_text_iter_forward_line(&iter))
2012 if (compose_is_sig_separator(compose, buffer, &iter)) {
2013 gtk_text_buffer_get_end_iter(buffer, &end_iter);
2014 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
2018 #define BLOCK_WRAP() { \
2019 prev_autowrap = compose->autowrap; \
2020 buffer = gtk_text_view_get_buffer( \
2021 GTK_TEXT_VIEW(compose->text)); \
2022 compose->autowrap = FALSE; \
2024 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2025 G_CALLBACK(compose_changed_cb), \
2027 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2028 G_CALLBACK(text_inserted), \
2031 #define UNBLOCK_WRAP() { \
2032 compose->autowrap = prev_autowrap; \
2033 if (compose->autowrap) { \
2034 gint old = compose->draft_timeout_tag; \
2035 compose->draft_timeout_tag = -2; \
2036 compose_wrap_all(compose); \
2037 compose->draft_timeout_tag = old; \
2040 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2041 G_CALLBACK(compose_changed_cb), \
2043 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2044 G_CALLBACK(text_inserted), \
2048 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
2050 Compose *compose = NULL;
2051 PrefsAccount *account = NULL;
2052 GtkTextView *textview;
2053 GtkTextBuffer *textbuf;
2057 gchar buf[BUFFSIZE];
2058 gboolean use_signing = FALSE;
2059 gboolean use_encryption = FALSE;
2060 gchar *privacy_system = NULL;
2061 int priority = PRIORITY_NORMAL;
2062 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
2064 g_return_val_if_fail(msginfo != NULL, NULL);
2065 g_return_val_if_fail(msginfo->folder != NULL, NULL);
2067 if (compose_put_existing_to_front(msginfo)) {
2071 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2072 folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
2073 gchar queueheader_buf[BUFFSIZE];
2076 /* Select Account from queue headers */
2077 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2078 sizeof(queueheader_buf), "X-Claws-Account-Id:")) {
2079 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
2080 account = account_find_from_id(id);
2082 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2083 sizeof(queueheader_buf), "X-Sylpheed-Account-Id:")) {
2084 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
2085 account = account_find_from_id(id);
2087 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2088 sizeof(queueheader_buf), "NAID:")) {
2089 id = atoi(&queueheader_buf[strlen("NAID:")]);
2090 account = account_find_from_id(id);
2092 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2093 sizeof(queueheader_buf), "MAID:")) {
2094 id = atoi(&queueheader_buf[strlen("MAID:")]);
2095 account = account_find_from_id(id);
2097 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2098 sizeof(queueheader_buf), "S:")) {
2099 account = account_find_from_address(queueheader_buf, FALSE);
2101 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2102 sizeof(queueheader_buf), "X-Claws-Sign:")) {
2103 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
2104 use_signing = param;
2107 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2108 sizeof(queueheader_buf), "X-Sylpheed-Sign:")) {
2109 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
2110 use_signing = param;
2113 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2114 sizeof(queueheader_buf), "X-Claws-Encrypt:")) {
2115 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
2116 use_encryption = param;
2118 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2119 sizeof(queueheader_buf), "X-Sylpheed-Encrypt:")) {
2120 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
2121 use_encryption = param;
2123 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2124 sizeof(queueheader_buf), "X-Claws-Privacy-System:")) {
2125 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
2127 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2128 sizeof(queueheader_buf), "X-Sylpheed-Privacy-System:")) {
2129 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
2131 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2132 sizeof(queueheader_buf), "X-Priority: ")) {
2133 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
2136 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2137 sizeof(queueheader_buf), "RMID:")) {
2138 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
2139 if (tokens[0] && tokens[1] && tokens[2]) {
2140 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2141 if (orig_item != NULL) {
2142 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2147 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2148 sizeof(queueheader_buf), "FMID:")) {
2149 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
2150 if (tokens[0] && tokens[1] && tokens[2]) {
2151 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2152 if (orig_item != NULL) {
2153 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2159 account = msginfo->folder->folder->account;
2162 if (!account && prefs_common.reedit_account_autosel) {
2163 gchar from[BUFFSIZE];
2164 if (!procheader_get_header_from_msginfo(msginfo, from, sizeof(from), "FROM:")) {
2165 extract_address(from);
2166 account = account_find_from_address(from, FALSE);
2170 account = cur_account;
2172 g_return_val_if_fail(account != NULL, NULL);
2174 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2176 compose->replyinfo = replyinfo;
2177 compose->fwdinfo = fwdinfo;
2179 compose->updating = TRUE;
2180 compose->priority = priority;
2182 if (privacy_system != NULL) {
2183 compose->privacy_system = privacy_system;
2184 compose_use_signing(compose, use_signing);
2185 compose_use_encryption(compose, use_encryption);
2186 compose_update_privacy_system_menu_item(compose, FALSE);
2188 activate_privacy_system(compose, account, FALSE);
2191 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2193 compose_extract_original_charset(compose);
2195 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2196 folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
2197 gchar queueheader_buf[BUFFSIZE];
2199 /* Set message save folder */
2200 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "SCF:")) {
2203 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2204 gtk_editable_delete_text(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
2205 gtk_editable_insert_text(GTK_EDITABLE(compose->savemsg_entry), &queueheader_buf[4], strlen(&queueheader_buf[4]), &startpos);
2207 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "RRCPT:")) {
2208 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2210 GtkItemFactory *ifactory;
2211 ifactory = gtk_item_factory_from_widget(compose->menubar);
2212 menu_set_active(ifactory, "/Options/Request Return Receipt", TRUE);
2217 if (compose_parse_header(compose, msginfo) < 0) {
2218 compose->updating = FALSE;
2219 compose_destroy(compose);
2222 compose_reedit_set_entry(compose, msginfo);
2224 textview = GTK_TEXT_VIEW(compose->text);
2225 textbuf = gtk_text_view_get_buffer(textview);
2226 compose_create_tags(textview, compose);
2228 mark = gtk_text_buffer_get_insert(textbuf);
2229 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2231 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2232 G_CALLBACK(compose_changed_cb),
2235 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2236 fp = procmime_get_first_encrypted_text_content(msginfo);
2238 compose_force_encryption(compose, account, TRUE);
2241 fp = procmime_get_first_text_content(msginfo);
2244 g_warning("Can't get text part\n");
2248 gboolean prev_autowrap = compose->autowrap;
2249 GtkTextBuffer *buffer = textbuf;
2251 while (fgets(buf, sizeof(buf), fp) != NULL) {
2253 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2259 compose_attach_parts(compose, msginfo);
2261 compose_colorize_signature(compose);
2263 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2264 G_CALLBACK(compose_changed_cb),
2267 gtk_widget_grab_focus(compose->text);
2269 if (prefs_common.auto_exteditor) {
2270 compose_exec_ext_editor(compose);
2272 compose->modified = FALSE;
2273 compose_set_title(compose);
2275 compose->updating = FALSE;
2276 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2277 SCROLL_TO_CURSOR(compose);
2279 if (compose->deferred_destroy) {
2280 compose_destroy(compose);
2284 compose->sig_str = compose_get_signature_str(compose);
2289 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2294 GtkItemFactory *ifactory;
2297 g_return_val_if_fail(msginfo != NULL, NULL);
2300 account = account_get_reply_account(msginfo,
2301 prefs_common.reply_account_autosel);
2302 g_return_val_if_fail(account != NULL, NULL);
2304 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2306 compose->updating = TRUE;
2308 ifactory = gtk_item_factory_from_widget(compose->menubar);
2309 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2310 compose->replyinfo = NULL;
2311 compose->fwdinfo = NULL;
2313 compose_show_first_last_header(compose, TRUE);
2315 gtk_widget_grab_focus(compose->header_last->entry);
2317 filename = procmsg_get_message_file(msginfo);
2319 if (filename == NULL) {
2320 compose->updating = FALSE;
2321 compose_destroy(compose);
2326 compose->redirect_filename = filename;
2328 /* Set save folder */
2329 item = msginfo->folder;
2330 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2331 gchar *folderidentifier;
2333 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
2334 folderidentifier = folder_item_get_identifier(item);
2335 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
2336 g_free(folderidentifier);
2339 compose_attach_parts(compose, msginfo);
2341 if (msginfo->subject)
2342 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2344 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2346 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2347 _("Message redirect format error at line %d."));
2348 quote_fmt_reset_vartable();
2349 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2351 compose_colorize_signature(compose);
2353 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
2354 menu_set_sensitive(ifactory, "/Add...", FALSE);
2355 menu_set_sensitive(ifactory, "/Remove", FALSE);
2356 menu_set_sensitive(ifactory, "/Properties...", FALSE);
2358 ifactory = gtk_item_factory_from_widget(compose->menubar);
2359 menu_set_sensitive(ifactory, "/Message/Save", FALSE);
2360 menu_set_sensitive(ifactory, "/Message/Insert file", FALSE);
2361 menu_set_sensitive(ifactory, "/Message/Attach file", FALSE);
2362 menu_set_sensitive(ifactory, "/Message/Insert signature", FALSE);
2363 menu_set_sensitive(ifactory, "/Edit", FALSE);
2364 menu_set_sensitive(ifactory, "/Options", FALSE);
2365 menu_set_sensitive(ifactory, "/Tools/Show ruler", FALSE);
2366 menu_set_sensitive(ifactory, "/Tools/Actions", FALSE);
2368 if (compose->toolbar->draft_btn)
2369 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2370 if (compose->toolbar->insert_btn)
2371 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2372 if (compose->toolbar->attach_btn)
2373 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2374 if (compose->toolbar->sig_btn)
2375 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2376 if (compose->toolbar->exteditor_btn)
2377 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2378 if (compose->toolbar->linewrap_current_btn)
2379 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2380 if (compose->toolbar->linewrap_all_btn)
2381 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2383 compose->modified = FALSE;
2384 compose_set_title(compose);
2385 compose->updating = FALSE;
2386 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2387 SCROLL_TO_CURSOR(compose);
2389 if (compose->deferred_destroy) {
2390 compose_destroy(compose);
2397 GList *compose_get_compose_list(void)
2399 return compose_list;
2402 void compose_entry_append(Compose *compose, const gchar *address,
2403 ComposeEntryType type)
2405 const gchar *header;
2407 gboolean in_quote = FALSE;
2408 if (!address || *address == '\0') return;
2415 header = N_("Bcc:");
2417 case COMPOSE_REPLYTO:
2418 header = N_("Reply-To:");
2420 case COMPOSE_NEWSGROUPS:
2421 header = N_("Newsgroups:");
2423 case COMPOSE_FOLLOWUPTO:
2424 header = N_( "Followup-To:");
2431 header = prefs_common_translated_header_name(header);
2433 cur = begin = (gchar *)address;
2435 /* we separate the line by commas, but not if we're inside a quoted
2437 while (*cur != '\0') {
2439 in_quote = !in_quote;
2440 if (*cur == ',' && !in_quote) {
2441 gchar *tmp = g_strdup(begin);
2443 tmp[cur-begin]='\0';
2446 while (*tmp == ' ' || *tmp == '\t')
2448 compose_add_header_entry(compose, header, tmp);
2455 gchar *tmp = g_strdup(begin);
2457 tmp[cur-begin]='\0';
2460 while (*tmp == ' ' || *tmp == '\t')
2462 compose_add_header_entry(compose, header, tmp);
2467 static void compose_entry_mark_default_to(Compose *compose, const gchar *mailto)
2469 static GdkColor yellow;
2470 static GdkColor black;
2471 static gboolean yellow_initialised = FALSE;
2475 if (!yellow_initialised) {
2476 gdk_color_parse("#f5f6be", &yellow);
2477 gdk_color_parse("#000000", &black);
2478 yellow_initialised = gdk_colormap_alloc_color(
2479 gdk_colormap_get_system(), &yellow, FALSE, TRUE);
2480 yellow_initialised &= gdk_colormap_alloc_color(
2481 gdk_colormap_get_system(), &black, FALSE, TRUE);
2484 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2485 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2486 if (gtk_entry_get_text(entry) &&
2487 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2488 if (yellow_initialised) {
2489 gtk_widget_modify_base(
2490 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2491 GTK_STATE_NORMAL, &yellow);
2492 gtk_widget_modify_text(
2493 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2494 GTK_STATE_NORMAL, &black);
2500 void compose_toolbar_cb(gint action, gpointer data)
2502 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2503 Compose *compose = (Compose*)toolbar_item->parent;
2505 g_return_if_fail(compose != NULL);
2509 compose_send_cb(compose, 0, NULL);
2512 compose_send_later_cb(compose, 0, NULL);
2515 compose_draft_cb(compose, COMPOSE_QUIT_EDITING, NULL);
2518 compose_insert_file_cb(compose, 0, NULL);
2521 compose_attach_cb(compose, 0, NULL);
2524 compose_insert_sig(compose, FALSE);
2527 compose_ext_editor_cb(compose, 0, NULL);
2529 case A_LINEWRAP_CURRENT:
2530 compose_beautify_paragraph(compose, NULL, TRUE);
2532 case A_LINEWRAP_ALL:
2533 compose_wrap_all_full(compose, TRUE);
2536 compose_address_cb(compose, 0, NULL);
2539 case A_CHECK_SPELLING:
2540 compose_check_all(compose);
2548 static void compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
2553 gchar *subject = NULL;
2557 gchar **attach = NULL;
2559 /* get mailto parts but skip from */
2560 scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach);
2563 compose_entry_append(compose, to, to_type);
2565 compose_entry_append(compose, cc, COMPOSE_CC);
2567 compose_entry_append(compose, bcc, COMPOSE_BCC);
2569 if (!g_utf8_validate (subject, -1, NULL)) {
2570 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2571 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2574 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2578 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2579 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2582 gboolean prev_autowrap = compose->autowrap;
2584 compose->autowrap = FALSE;
2586 mark = gtk_text_buffer_get_insert(buffer);
2587 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2589 if (!g_utf8_validate (body, -1, NULL)) {
2590 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2591 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2594 gtk_text_buffer_insert(buffer, &iter, body, -1);
2596 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2598 compose->autowrap = prev_autowrap;
2599 if (compose->autowrap)
2600 compose_wrap_all(compose);
2604 gint i = 0, att = 0;
2605 gchar *warn_files = NULL;
2606 while (attach[i] != NULL) {
2607 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2608 if (utf8_filename) {
2609 if (compose_attach_append(compose, attach[i], utf8_filename, NULL)) {
2610 gchar *tmp = g_strdup_printf("%s%s\n",
2611 warn_files?warn_files:"",
2617 g_free(utf8_filename);
2619 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2624 alertpanel_notice(ngettext(
2625 "The following file has been attached: \n%s",
2626 "The following files have been attached: \n%s", att), warn_files);
2638 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2640 static HeaderEntry hentry[] = {{"Reply-To:", NULL, TRUE},
2641 {"Cc:", NULL, TRUE},
2642 {"References:", NULL, FALSE},
2643 {"Bcc:", NULL, TRUE},
2644 {"Newsgroups:", NULL, TRUE},
2645 {"Followup-To:", NULL, TRUE},
2646 {"List-Post:", NULL, FALSE},
2647 {"X-Priority:", NULL, FALSE},
2648 {NULL, NULL, FALSE}};
2664 g_return_val_if_fail(msginfo != NULL, -1);
2666 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2667 procheader_get_header_fields(fp, hentry);
2670 if (hentry[H_REPLY_TO].body != NULL) {
2671 if (hentry[H_REPLY_TO].body[0] != '\0') {
2673 conv_unmime_header(hentry[H_REPLY_TO].body,
2676 g_free(hentry[H_REPLY_TO].body);
2677 hentry[H_REPLY_TO].body = NULL;
2679 if (hentry[H_CC].body != NULL) {
2680 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL);
2681 g_free(hentry[H_CC].body);
2682 hentry[H_CC].body = NULL;
2684 if (hentry[H_REFERENCES].body != NULL) {
2685 if (compose->mode == COMPOSE_REEDIT)
2686 compose->references = hentry[H_REFERENCES].body;
2688 compose->references = compose_parse_references
2689 (hentry[H_REFERENCES].body, msginfo->msgid);
2690 g_free(hentry[H_REFERENCES].body);
2692 hentry[H_REFERENCES].body = NULL;
2694 if (hentry[H_BCC].body != NULL) {
2695 if (compose->mode == COMPOSE_REEDIT)
2697 conv_unmime_header(hentry[H_BCC].body, NULL);
2698 g_free(hentry[H_BCC].body);
2699 hentry[H_BCC].body = NULL;
2701 if (hentry[H_NEWSGROUPS].body != NULL) {
2702 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2703 hentry[H_NEWSGROUPS].body = NULL;
2705 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2706 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2707 compose->followup_to =
2708 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2711 g_free(hentry[H_FOLLOWUP_TO].body);
2712 hentry[H_FOLLOWUP_TO].body = NULL;
2714 if (hentry[H_LIST_POST].body != NULL) {
2717 extract_address(hentry[H_LIST_POST].body);
2718 if (hentry[H_LIST_POST].body[0] != '\0') {
2719 scan_mailto_url(hentry[H_LIST_POST].body,
2720 NULL, &to, NULL, NULL, NULL, NULL, NULL);
2722 g_free(compose->ml_post);
2723 compose->ml_post = to;
2726 g_free(hentry[H_LIST_POST].body);
2727 hentry[H_LIST_POST].body = NULL;
2730 /* CLAWS - X-Priority */
2731 if (compose->mode == COMPOSE_REEDIT)
2732 if (hentry[H_X_PRIORITY].body != NULL) {
2735 priority = atoi(hentry[H_X_PRIORITY].body);
2736 g_free(hentry[H_X_PRIORITY].body);
2738 hentry[H_X_PRIORITY].body = NULL;
2740 if (priority < PRIORITY_HIGHEST ||
2741 priority > PRIORITY_LOWEST)
2742 priority = PRIORITY_NORMAL;
2744 compose->priority = priority;
2747 if (compose->mode == COMPOSE_REEDIT) {
2748 if (msginfo->inreplyto && *msginfo->inreplyto)
2749 compose->inreplyto = g_strdup(msginfo->inreplyto);
2753 if (msginfo->msgid && *msginfo->msgid)
2754 compose->inreplyto = g_strdup(msginfo->msgid);
2756 if (!compose->references) {
2757 if (msginfo->msgid && *msginfo->msgid) {
2758 if (msginfo->inreplyto && *msginfo->inreplyto)
2759 compose->references =
2760 g_strdup_printf("<%s>\n\t<%s>",
2764 compose->references =
2765 g_strconcat("<", msginfo->msgid, ">",
2767 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
2768 compose->references =
2769 g_strconcat("<", msginfo->inreplyto, ">",
2777 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
2779 GSList *ref_id_list, *cur;
2783 ref_id_list = references_list_append(NULL, ref);
2784 if (!ref_id_list) return NULL;
2785 if (msgid && *msgid)
2786 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
2791 for (cur = ref_id_list; cur != NULL; cur = cur->next)
2792 /* "<" + Message-ID + ">" + CR+LF+TAB */
2793 len += strlen((gchar *)cur->data) + 5;
2795 if (len > MAX_REFERENCES_LEN) {
2796 /* remove second message-ID */
2797 if (ref_id_list && ref_id_list->next &&
2798 ref_id_list->next->next) {
2799 g_free(ref_id_list->next->data);
2800 ref_id_list = g_slist_remove
2801 (ref_id_list, ref_id_list->next->data);
2803 slist_free_strings(ref_id_list);
2804 g_slist_free(ref_id_list);
2811 new_ref = g_string_new("");
2812 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
2813 if (new_ref->len > 0)
2814 g_string_append(new_ref, "\n\t");
2815 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
2818 slist_free_strings(ref_id_list);
2819 g_slist_free(ref_id_list);
2821 new_ref_str = new_ref->str;
2822 g_string_free(new_ref, FALSE);
2827 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
2828 const gchar *fmt, const gchar *qmark,
2829 const gchar *body, gboolean rewrap,
2830 gboolean need_unescape,
2831 const gchar *err_msg)
2833 MsgInfo* dummyinfo = NULL;
2834 gchar *quote_str = NULL;
2836 gboolean prev_autowrap;
2837 const gchar *trimmed_body = body;
2838 gint cursor_pos = -1;
2839 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2840 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2845 SIGNAL_BLOCK(buffer);
2848 dummyinfo = compose_msginfo_new_from_compose(compose);
2849 msginfo = dummyinfo;
2852 if (qmark != NULL) {
2854 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
2855 compose->gtkaspell);
2857 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
2859 quote_fmt_scan_string(qmark);
2862 buf = quote_fmt_get_buffer();
2864 alertpanel_error(_("Quote mark format error."));
2866 Xstrdup_a(quote_str, buf, goto error)
2869 if (fmt && *fmt != '\0') {
2872 while (*trimmed_body == '\n')
2876 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
2877 compose->gtkaspell);
2879 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
2881 if (need_unescape) {
2884 /* decode \-escape sequences in the internal representation of the quote format */
2885 tmp = malloc(strlen(fmt)+1);
2886 pref_get_unescaped_pref(tmp, fmt);
2887 quote_fmt_scan_string(tmp);
2891 quote_fmt_scan_string(fmt);
2895 buf = quote_fmt_get_buffer();
2897 gint line = quote_fmt_get_line();
2898 alertpanel_error(err_msg, line);
2904 prev_autowrap = compose->autowrap;
2905 compose->autowrap = FALSE;
2907 mark = gtk_text_buffer_get_insert(buffer);
2908 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2909 if (g_utf8_validate(buf, -1, NULL)) {
2910 gtk_text_buffer_insert(buffer, &iter, buf, -1);
2912 gchar *tmpout = NULL;
2913 tmpout = conv_codeset_strdup
2914 (buf, conv_get_locale_charset_str_no_utf8(),
2916 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
2918 tmpout = g_malloc(strlen(buf)*2+1);
2919 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
2921 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
2925 cursor_pos = quote_fmt_get_cursor_pos();
2926 compose->set_cursor_pos = cursor_pos;
2927 if (cursor_pos == -1) {
2930 gtk_text_buffer_get_start_iter(buffer, &iter);
2931 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
2932 gtk_text_buffer_place_cursor(buffer, &iter);
2934 compose->autowrap = prev_autowrap;
2935 if (compose->autowrap && rewrap)
2936 compose_wrap_all(compose);
2943 SIGNAL_UNBLOCK(buffer);
2945 procmsg_msginfo_free( dummyinfo );
2950 /* if ml_post is of type addr@host and from is of type
2951 * addr-anything@host, return TRUE
2953 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
2955 gchar *left_ml = NULL;
2956 gchar *right_ml = NULL;
2957 gchar *left_from = NULL;
2958 gchar *right_from = NULL;
2959 gboolean result = FALSE;
2961 if (!ml_post || !from)
2964 left_ml = g_strdup(ml_post);
2965 if (strstr(left_ml, "@")) {
2966 right_ml = strstr(left_ml, "@")+1;
2967 *(strstr(left_ml, "@")) = '\0';
2970 left_from = g_strdup(from);
2971 if (strstr(left_from, "@")) {
2972 right_from = strstr(left_from, "@")+1;
2973 *(strstr(left_from, "@")) = '\0';
2976 if (left_ml && left_from && right_ml && right_from
2977 && !strncmp(left_from, left_ml, strlen(left_ml))
2978 && !strcmp(right_from, right_ml)) {
2987 static gboolean same_address(const gchar *addr1, const gchar *addr2)
2989 gchar *my_addr1, *my_addr2;
2991 if (!addr1 || !addr2)
2994 Xstrdup_a(my_addr1, addr1, return FALSE);
2995 Xstrdup_a(my_addr2, addr2, return FALSE);
2997 extract_address(my_addr1);
2998 extract_address(my_addr2);
3000 return !strcasecmp(my_addr1, my_addr2);
3003 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3004 gboolean to_all, gboolean to_ml,
3006 gboolean followup_and_reply_to)
3008 GSList *cc_list = NULL;
3011 gchar *replyto = NULL;
3012 GHashTable *to_table;
3014 gboolean reply_to_ml = FALSE;
3015 gboolean default_reply_to = FALSE;
3017 g_return_if_fail(compose->account != NULL);
3018 g_return_if_fail(msginfo != NULL);
3020 reply_to_ml = to_ml && compose->ml_post;
3022 default_reply_to = msginfo->folder &&
3023 msginfo->folder->prefs->enable_default_reply_to;
3025 if (compose->account->protocol != A_NNTP) {
3026 if (reply_to_ml && !default_reply_to) {
3028 gboolean is_subscr = is_subscription(compose->ml_post,
3031 /* normal answer to ml post with a reply-to */
3032 compose_entry_append(compose,
3035 if (compose->replyto
3036 && !same_address(compose->ml_post, compose->replyto))
3037 compose_entry_append(compose,
3041 /* answer to subscription confirmation */
3042 if (compose->replyto)
3043 compose_entry_append(compose,
3046 else if (msginfo->from)
3047 compose_entry_append(compose,
3052 else if (!(to_all || to_sender) && default_reply_to) {
3053 compose_entry_append(compose,
3054 msginfo->folder->prefs->default_reply_to,
3056 compose_entry_mark_default_to(compose,
3057 msginfo->folder->prefs->default_reply_to);
3062 Xstrdup_a(tmp1, msginfo->from, return);
3063 extract_address(tmp1);
3064 if (to_all || to_sender ||
3065 !account_find_from_address(tmp1, FALSE))
3066 compose_entry_append(compose,
3067 (compose->replyto && !to_sender)
3068 ? compose->replyto :
3069 msginfo->from ? msginfo->from : "",
3071 else if (!to_all && !to_sender) {
3072 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3073 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3074 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3075 if (compose->replyto) {
3076 compose_entry_append(compose,
3080 compose_entry_append(compose,
3081 msginfo->from ? msginfo->from : "",
3085 /* replying to own mail, use original recp */
3086 compose_entry_append(compose,
3087 msginfo->to ? msginfo->to : "",
3089 compose_entry_append(compose,
3090 msginfo->cc ? msginfo->cc : "",
3096 if (to_sender || (compose->followup_to &&
3097 !strncmp(compose->followup_to, "poster", 6)))
3098 compose_entry_append
3100 (compose->replyto ? compose->replyto :
3101 msginfo->from ? msginfo->from : ""),
3104 else if (followup_and_reply_to || to_all) {
3105 compose_entry_append
3107 (compose->replyto ? compose->replyto :
3108 msginfo->from ? msginfo->from : ""),
3111 compose_entry_append
3113 compose->followup_to ? compose->followup_to :
3114 compose->newsgroups ? compose->newsgroups : "",
3115 COMPOSE_NEWSGROUPS);
3118 compose_entry_append
3120 compose->followup_to ? compose->followup_to :
3121 compose->newsgroups ? compose->newsgroups : "",
3122 COMPOSE_NEWSGROUPS);
3125 if (msginfo->subject && *msginfo->subject) {
3129 buf = p = g_strdup(msginfo->subject);
3130 p += subject_get_prefix_length(p);
3131 memmove(buf, p, strlen(p) + 1);
3133 buf2 = g_strdup_printf("Re: %s", buf);
3134 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3139 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3141 if (to_ml && compose->ml_post) return;
3142 if (!to_all || compose->account->protocol == A_NNTP) return;
3144 if (compose->replyto) {
3145 Xstrdup_a(replyto, compose->replyto, return);
3146 extract_address(replyto);
3148 if (msginfo->from) {
3149 Xstrdup_a(from, msginfo->from, return);
3150 extract_address(from);
3153 if (replyto && from)
3154 cc_list = address_list_append_with_comments(cc_list, from);
3155 if (to_all && msginfo->folder &&
3156 msginfo->folder->prefs->enable_default_reply_to)
3157 cc_list = address_list_append_with_comments(cc_list,
3158 msginfo->folder->prefs->default_reply_to);
3159 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3160 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3162 to_table = g_hash_table_new(g_str_hash, g_str_equal);
3164 g_hash_table_insert(to_table, g_utf8_strdown(replyto, -1), GINT_TO_POINTER(1));
3165 if (compose->account) {
3166 g_hash_table_insert(to_table, g_utf8_strdown(compose->account->address, -1),
3167 GINT_TO_POINTER(1));
3169 /* remove address on To: and that of current account */
3170 for (cur = cc_list; cur != NULL; ) {
3171 GSList *next = cur->next;
3174 addr = g_utf8_strdown(cur->data, -1);
3175 extract_address(addr);
3177 if (GPOINTER_TO_INT(g_hash_table_lookup(to_table, addr)) == 1)
3178 cc_list = g_slist_remove(cc_list, cur->data);
3180 g_hash_table_insert(to_table, addr, GINT_TO_POINTER(1));
3184 hash_free_strings(to_table);
3185 g_hash_table_destroy(to_table);
3188 for (cur = cc_list; cur != NULL; cur = cur->next)
3189 compose_entry_append(compose, (gchar *)cur->data,
3191 slist_free_strings(cc_list);
3192 g_slist_free(cc_list);
3197 #define SET_ENTRY(entry, str) \
3200 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3203 #define SET_ADDRESS(type, str) \
3206 compose_entry_append(compose, str, type); \
3209 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3211 g_return_if_fail(msginfo != NULL);
3213 SET_ENTRY(subject_entry, msginfo->subject);
3214 SET_ENTRY(from_name, msginfo->from);
3215 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3216 SET_ADDRESS(COMPOSE_CC, compose->cc);
3217 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3218 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3219 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3220 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3222 compose_update_priority_menu_item(compose);
3223 compose_update_privacy_system_menu_item(compose, FALSE);
3224 compose_show_first_last_header(compose, TRUE);
3230 static void compose_insert_sig(Compose *compose, gboolean replace)
3232 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3233 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3235 GtkTextIter iter, iter_end;
3237 gboolean prev_autowrap;
3238 gboolean found = FALSE;
3239 gboolean exists = FALSE;
3241 g_return_if_fail(compose->account != NULL);
3245 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3246 G_CALLBACK(compose_changed_cb),
3249 mark = gtk_text_buffer_get_insert(buffer);
3250 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3251 cur_pos = gtk_text_iter_get_offset (&iter);
3253 gtk_text_buffer_get_end_iter(buffer, &iter);
3255 exists = (compose->sig_str != NULL);
3258 GtkTextIter first_iter, start_iter, end_iter;
3260 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3262 if (!exists || compose->sig_str[0] == '\0')
3265 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3266 compose->signature_tag);
3269 /* include previous \n\n */
3270 gtk_text_iter_backward_chars(&first_iter, 1);
3271 start_iter = first_iter;
3272 end_iter = first_iter;
3274 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3275 compose->signature_tag);
3276 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3277 compose->signature_tag);
3279 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3285 g_free(compose->sig_str);
3286 compose->sig_str = compose_get_signature_str(compose);
3288 cur_pos = gtk_text_iter_get_offset(&iter);
3290 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3291 g_free(compose->sig_str);
3292 compose->sig_str = NULL;
3294 if (compose->sig_inserted == FALSE)
3295 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3296 compose->sig_inserted = TRUE;
3298 cur_pos = gtk_text_iter_get_offset(&iter);
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, 1);
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", compose->account->sig_sep, "\n", sig_body,
3361 sig_str = g_strconcat("\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");
3553 ainfo->size = (goffset)size;
3554 size_text = to_human_readable((goffset)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);
4431 #ifndef GENERIC_UMPC
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);
4826 claws_unlink(msgpath);
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 = ENC_UNKNOWN;
5147 MimeInfo *mimemsg, *mimetext;
5149 const gchar *src_codeset = CS_INTERNAL;
5151 if (action == COMPOSE_WRITE_FOR_SEND)
5152 attach_parts = TRUE;
5154 /* create message MimeInfo */
5155 mimemsg = procmime_mimeinfo_new();
5156 mimemsg->type = MIMETYPE_MESSAGE;
5157 mimemsg->subtype = g_strdup("rfc822");
5158 mimemsg->content = MIMECONTENT_MEM;
5159 mimemsg->tmp = TRUE; /* must free content later */
5160 mimemsg->data.mem = compose_get_header(compose);
5162 /* Create text part MimeInfo */
5163 /* get all composed text */
5164 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5165 gtk_text_buffer_get_start_iter(buffer, &start);
5166 gtk_text_buffer_get_end_iter(buffer, &end);
5167 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5169 out_codeset = conv_get_charset_str(compose->out_encoding);
5171 if (!out_codeset && is_ascii_str(chars)) {
5172 out_codeset = CS_US_ASCII;
5173 } else if (prefs_common.outgoing_fallback_to_ascii &&
5174 is_ascii_str(chars)) {
5175 out_codeset = CS_US_ASCII;
5176 encoding = ENC_7BIT;
5180 gchar *test_conv_global_out = NULL;
5181 gchar *test_conv_reply = NULL;
5183 /* automatic mode. be automatic. */
5184 codeconv_set_strict(TRUE);
5186 out_codeset = conv_get_outgoing_charset_str();
5188 debug_print("trying to convert to %s\n", out_codeset);
5189 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5192 if (!test_conv_global_out && compose->orig_charset
5193 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5194 out_codeset = compose->orig_charset;
5195 debug_print("failure; trying to convert to %s\n", out_codeset);
5196 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5199 if (!test_conv_global_out && !test_conv_reply) {
5201 out_codeset = CS_INTERNAL;
5202 debug_print("failure; finally using %s\n", out_codeset);
5204 g_free(test_conv_global_out);
5205 g_free(test_conv_reply);
5206 codeconv_set_strict(FALSE);
5209 if (encoding == ENC_UNKNOWN) {
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);
5220 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5221 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5223 if (action == COMPOSE_WRITE_FOR_SEND) {
5224 codeconv_set_strict(TRUE);
5225 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5226 codeconv_set_strict(FALSE);
5232 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5233 "to the specified %s charset.\n"
5234 "Send it as %s?"), out_codeset, src_codeset);
5235 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL, _("+_Send"), NULL, FALSE,
5236 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5239 if (aval != G_ALERTALTERNATE) {
5244 out_codeset = src_codeset;
5250 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);
6029 /* Automatic Faces and X-Faces */
6030 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6031 g_string_append_printf(header, "X-Face: %s\n", buf);
6033 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6034 g_string_append_printf(header, "X-Face: %s\n", buf);
6036 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6037 g_string_append_printf(header, "Face: %s\n", buf);
6039 else if (get_default_face (buf, sizeof(buf)) == 0) {
6040 g_string_append_printf(header, "Face: %s\n", buf);
6044 switch (compose->priority) {
6045 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6046 "X-Priority: 1 (Highest)\n");
6048 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6049 "X-Priority: 2 (High)\n");
6051 case PRIORITY_NORMAL: break;
6052 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6053 "X-Priority: 4 (Low)\n");
6055 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6056 "X-Priority: 5 (Lowest)\n");
6058 default: debug_print("compose: priority unknown : %d\n",
6062 /* Request Return Receipt */
6063 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To")) {
6064 if (compose->return_receipt) {
6065 if (compose->account->name
6066 && *compose->account->name) {
6067 compose_convert_header(compose, buf, sizeof(buf),
6068 compose->account->name,
6069 strlen("Disposition-Notification-To: "),
6071 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, compose->account->address);
6073 g_string_append_printf(header, "Disposition-Notification-To: %s\n", compose->account->address);
6077 /* get special headers */
6078 for (list = compose->header_list; list; list = list->next) {
6079 ComposeHeaderEntry *headerentry;
6082 gchar *headername_wcolon;
6083 const gchar *headername_trans;
6086 gboolean standard_header = FALSE;
6088 headerentry = ((ComposeHeaderEntry *)list->data);
6090 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(GTK_BIN(headerentry->combo)->child)));
6092 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6097 if (!strstr(tmp, ":")) {
6098 headername_wcolon = g_strconcat(tmp, ":", NULL);
6099 headername = g_strdup(tmp);
6101 headername_wcolon = g_strdup(tmp);
6102 headername = g_strdup(strtok(tmp, ":"));
6106 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6107 Xstrdup_a(headervalue, entry_str, return NULL);
6108 subst_char(headervalue, '\r', ' ');
6109 subst_char(headervalue, '\n', ' ');
6110 string = std_headers;
6111 while (*string != NULL) {
6112 headername_trans = prefs_common_translated_header_name(*string);
6113 if (!strcmp(headername_trans, headername_wcolon))
6114 standard_header = TRUE;
6117 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6118 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
6121 g_free(headername_wcolon);
6125 g_string_free(header, FALSE);
6130 #undef IS_IN_CUSTOM_HEADER
6132 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6133 gint header_len, gboolean addr_field)
6135 gchar *tmpstr = NULL;
6136 const gchar *out_codeset = NULL;
6138 g_return_if_fail(src != NULL);
6139 g_return_if_fail(dest != NULL);
6141 if (len < 1) return;
6143 tmpstr = g_strdup(src);
6145 subst_char(tmpstr, '\n', ' ');
6146 subst_char(tmpstr, '\r', ' ');
6149 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6150 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6151 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6156 codeconv_set_strict(TRUE);
6157 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6158 conv_get_charset_str(compose->out_encoding));
6159 codeconv_set_strict(FALSE);
6161 if (!dest || *dest == '\0') {
6162 gchar *test_conv_global_out = NULL;
6163 gchar *test_conv_reply = NULL;
6165 /* automatic mode. be automatic. */
6166 codeconv_set_strict(TRUE);
6168 out_codeset = conv_get_outgoing_charset_str();
6170 debug_print("trying to convert to %s\n", out_codeset);
6171 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6174 if (!test_conv_global_out && compose->orig_charset
6175 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6176 out_codeset = compose->orig_charset;
6177 debug_print("failure; trying to convert to %s\n", out_codeset);
6178 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6181 if (!test_conv_global_out && !test_conv_reply) {
6183 out_codeset = CS_INTERNAL;
6184 debug_print("finally using %s\n", out_codeset);
6186 g_free(test_conv_global_out);
6187 g_free(test_conv_reply);
6188 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6190 codeconv_set_strict(FALSE);
6195 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
6199 g_return_if_fail(user_data != NULL);
6201 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6202 g_strstrip(address);
6203 if (*address != '\0') {
6204 gchar *name = procheader_get_fromname(address);
6205 extract_address(address);
6206 addressbook_add_contact(name, address, NULL, NULL);
6211 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
6213 GtkWidget *menuitem;
6216 g_return_if_fail(menu != NULL);
6217 g_return_if_fail(GTK_IS_MENU_SHELL(menu));
6219 menuitem = gtk_separator_menu_item_new();
6220 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6221 gtk_widget_show(menuitem);
6223 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
6224 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6226 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
6227 g_strstrip(address);
6228 if (*address == '\0') {
6229 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
6232 g_signal_connect(G_OBJECT(menuitem), "activate",
6233 G_CALLBACK(compose_add_to_addressbook_cb), entry);
6234 gtk_widget_show(menuitem);
6237 static void compose_create_header_entry(Compose *compose)
6239 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6244 const gchar *header = NULL;
6245 ComposeHeaderEntry *headerentry;
6246 gboolean standard_header = FALSE;
6248 headerentry = g_new0(ComposeHeaderEntry, 1);
6251 combo = gtk_combo_box_entry_new_text();
6253 while(*string != NULL) {
6254 gtk_combo_box_append_text(GTK_COMBO_BOX(combo),
6255 (gchar*)prefs_common_translated_header_name(*string));
6258 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
6259 g_signal_connect(G_OBJECT(GTK_BIN(combo)->child), "grab_focus",
6260 G_CALLBACK(compose_grab_focus_cb), compose);
6261 gtk_widget_show(combo);
6262 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
6263 compose->header_nextrow, compose->header_nextrow+1,
6264 GTK_SHRINK, GTK_FILL, 0, 0);
6265 if (compose->header_last) {
6266 const gchar *last_header_entry = gtk_entry_get_text(
6267 GTK_ENTRY(GTK_BIN(compose->header_last->combo)->child));
6269 while (*string != NULL) {
6270 if (!strcmp(*string, last_header_entry))
6271 standard_header = TRUE;
6274 if (standard_header)
6275 header = gtk_entry_get_text(GTK_ENTRY(GTK_BIN(compose->header_last->combo)->child));
6277 if (!compose->header_last || !standard_header) {
6278 switch(compose->account->protocol) {
6280 header = prefs_common_translated_header_name("Newsgroups:");
6283 header = prefs_common_translated_header_name("To:");
6288 gtk_entry_set_text(GTK_ENTRY(GTK_BIN(combo)->child), header);
6290 g_signal_connect_after(G_OBJECT(GTK_BIN(combo)->child), "grab_focus",
6291 G_CALLBACK(compose_grab_focus_cb), compose);
6294 entry = gtk_entry_new();
6295 gtk_widget_show(entry);
6296 gtk_tooltips_set_tip(compose->tooltips, entry,
6297 _("Use <tab> to autocomplete from addressbook"), NULL);
6298 gtk_table_attach(GTK_TABLE(compose->header_table), entry, 1, 2,
6299 compose->header_nextrow, compose->header_nextrow+1,
6300 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
6302 g_signal_connect(G_OBJECT(entry), "key-press-event",
6303 G_CALLBACK(compose_headerentry_key_press_event_cb),
6305 g_signal_connect(G_OBJECT(entry), "changed",
6306 G_CALLBACK(compose_headerentry_changed_cb),
6308 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
6309 G_CALLBACK(compose_grab_focus_cb), compose);
6312 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6313 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6314 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6315 g_signal_connect(G_OBJECT(entry), "drag_data_received",
6316 G_CALLBACK(compose_header_drag_received_cb),
6318 g_signal_connect(G_OBJECT(entry), "drag-drop",
6319 G_CALLBACK(compose_drag_drop),
6321 g_signal_connect(G_OBJECT(entry), "populate-popup",
6322 G_CALLBACK(compose_entry_popup_extend),
6325 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
6327 headerentry->compose = compose;
6328 headerentry->combo = combo;
6329 headerentry->entry = entry;
6330 headerentry->headernum = compose->header_nextrow;
6332 compose->header_nextrow++;
6333 compose->header_last = headerentry;
6334 compose->header_list =
6335 g_slist_append(compose->header_list,
6339 static void compose_add_header_entry(Compose *compose, const gchar *header, gchar *text)
6341 ComposeHeaderEntry *last_header;
6343 last_header = compose->header_last;
6345 gtk_entry_set_text(GTK_ENTRY(GTK_BIN(last_header->combo)->child), header);
6346 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
6349 static void compose_remove_header_entries(Compose *compose)
6352 for (list = compose->header_list; list; list = list->next) {
6353 ComposeHeaderEntry *headerentry =
6354 (ComposeHeaderEntry *)list->data;
6355 gtk_widget_destroy(headerentry->combo);
6356 gtk_widget_destroy(headerentry->entry);
6357 g_free(headerentry);
6359 compose->header_last = NULL;
6360 g_slist_free(compose->header_list);
6361 compose->header_list = NULL;
6362 compose->header_nextrow = 1;
6363 compose_create_header_entry(compose);
6366 static GtkWidget *compose_create_header(Compose *compose)
6368 GtkWidget *from_optmenu_hbox;
6369 GtkWidget *header_scrolledwin;
6370 GtkWidget *header_table;
6374 /* header labels and entries */
6375 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
6376 gtk_widget_show(header_scrolledwin);
6377 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
6379 header_table = gtk_table_new(2, 2, FALSE);
6380 gtk_widget_show(header_table);
6381 gtk_container_set_border_width(GTK_CONTAINER(header_table), BORDER_WIDTH);
6382 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
6383 gtk_viewport_set_shadow_type(GTK_VIEWPORT(GTK_BIN(header_scrolledwin)->child), GTK_SHADOW_NONE);
6386 /* option menu for selecting accounts */
6387 from_optmenu_hbox = compose_account_option_menu_create(compose);
6388 gtk_table_attach(GTK_TABLE(header_table), from_optmenu_hbox,
6389 0, 2, count, count + 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
6392 compose->header_table = header_table;
6393 compose->header_list = NULL;
6394 compose->header_nextrow = count;
6396 compose_create_header_entry(compose);
6398 compose->table = NULL;
6400 return header_scrolledwin ;
6403 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
6405 Compose *compose = (Compose *)data;
6406 GdkEventButton event;
6409 event.time = gtk_get_current_event_time();
6411 return attach_button_pressed(compose->attach_clist, &event, compose);
6414 static GtkWidget *compose_create_attach(Compose *compose)
6416 GtkWidget *attach_scrwin;
6417 GtkWidget *attach_clist;
6419 GtkListStore *store;
6420 GtkCellRenderer *renderer;
6421 GtkTreeViewColumn *column;
6422 GtkTreeSelection *selection;
6424 /* attachment list */
6425 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
6426 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
6427 GTK_POLICY_AUTOMATIC,
6428 GTK_POLICY_AUTOMATIC);
6429 gtk_widget_set_size_request(attach_scrwin, -1, 80);
6431 store = gtk_list_store_new(N_ATTACH_COLS,
6436 G_TYPE_AUTO_POINTER,
6438 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
6439 (GTK_TREE_MODEL(store)));
6440 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
6441 g_object_unref(store);
6443 renderer = gtk_cell_renderer_text_new();
6444 column = gtk_tree_view_column_new_with_attributes
6445 (_("Mime type"), renderer, "text",
6446 COL_MIMETYPE, NULL);
6447 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6449 renderer = gtk_cell_renderer_text_new();
6450 column = gtk_tree_view_column_new_with_attributes
6451 (_("Size"), renderer, "text",
6453 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6455 renderer = gtk_cell_renderer_text_new();
6456 column = gtk_tree_view_column_new_with_attributes
6457 (_("Name"), renderer, "text",
6459 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6461 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
6462 prefs_common.use_stripes_everywhere);
6463 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
6464 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
6466 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
6467 G_CALLBACK(attach_selected), compose);
6468 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
6469 G_CALLBACK(attach_button_pressed), compose);
6471 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
6472 G_CALLBACK(popup_attach_button_pressed), compose);
6474 gtk_widget_tap_and_hold_setup(GTK_WIDGET(attach_clist), NULL, NULL,
6475 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
6476 g_signal_connect(G_OBJECT(attach_clist), "tap-and-hold",
6477 G_CALLBACK(popup_attach_button_pressed), compose);
6479 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
6480 G_CALLBACK(attach_key_pressed), compose);
6483 gtk_drag_dest_set(attach_clist,
6484 GTK_DEST_DEFAULT_ALL, compose_mime_types,
6485 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6486 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6487 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
6488 G_CALLBACK(compose_attach_drag_received_cb),
6490 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
6491 G_CALLBACK(compose_drag_drop),
6494 compose->attach_scrwin = attach_scrwin;
6495 compose->attach_clist = attach_clist;
6497 return attach_scrwin;
6500 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
6501 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
6503 static GtkWidget *compose_create_others(Compose *compose)
6506 GtkWidget *savemsg_checkbtn;
6507 GtkWidget *savemsg_entry;
6508 GtkWidget *savemsg_select;
6511 gchar *folderidentifier;
6513 /* Table for settings */
6514 table = gtk_table_new(3, 1, FALSE);
6515 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
6516 gtk_widget_show(table);
6517 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
6520 /* Save Message to folder */
6521 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
6522 gtk_widget_show(savemsg_checkbtn);
6523 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6524 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6525 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
6527 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
6528 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
6530 savemsg_entry = gtk_entry_new();
6531 gtk_widget_show(savemsg_entry);
6532 gtk_table_attach_defaults(GTK_TABLE(table), savemsg_entry, 1, 2, rowcount, rowcount + 1);
6533 gtk_editable_set_editable(GTK_EDITABLE(savemsg_entry), prefs_common.savemsg);
6534 g_signal_connect_after(G_OBJECT(savemsg_entry), "grab_focus",
6535 G_CALLBACK(compose_grab_focus_cb), compose);
6536 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6537 folderidentifier = folder_item_get_identifier(account_get_special_folder
6538 (compose->account, F_OUTBOX));
6539 gtk_entry_set_text(GTK_ENTRY(savemsg_entry), folderidentifier);
6540 g_free(folderidentifier);
6543 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
6544 gtk_widget_show(savemsg_select);
6545 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6546 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
6547 G_CALLBACK(compose_savemsg_select_cb),
6552 compose->savemsg_checkbtn = savemsg_checkbtn;
6553 compose->savemsg_entry = savemsg_entry;
6558 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
6560 gtk_editable_set_editable(GTK_EDITABLE(compose->savemsg_entry),
6561 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
6564 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
6569 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE);
6572 path = folder_item_get_identifier(dest);
6574 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), path);
6578 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
6579 GdkAtom clip, GtkTextIter *insert_place);
6582 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
6586 GtkTextBuffer *buffer = GTK_TEXT_VIEW(text)->buffer;
6588 if (event->button == 3) {
6590 GtkTextIter sel_start, sel_end;
6591 gboolean stuff_selected;
6593 /* move the cursor to allow GtkAspell to check the word
6594 * under the mouse */
6595 if (event->x && event->y) {
6596 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6597 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6599 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6602 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
6603 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
6606 stuff_selected = gtk_text_buffer_get_selection_bounds(
6608 &sel_start, &sel_end);
6610 gtk_text_buffer_place_cursor (buffer, &iter);
6611 /* reselect stuff */
6613 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
6614 gtk_text_buffer_select_range(buffer,
6615 &sel_start, &sel_end);
6617 return FALSE; /* pass the event so that the right-click goes through */
6620 if (event->button == 2) {
6625 /* get the middle-click position to paste at the correct place */
6626 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6627 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6629 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6632 entry_paste_clipboard(compose, text,
6633 prefs_common.linewrap_pastes,
6634 GDK_SELECTION_PRIMARY, &iter);
6642 static void compose_spell_menu_changed(void *data)
6644 Compose *compose = (Compose *)data;
6646 GtkWidget *menuitem;
6647 GtkWidget *parent_item;
6648 GtkMenu *menu = GTK_MENU(gtk_menu_new());
6649 GtkItemFactory *ifactory = gtk_item_factory_from_widget(compose->menubar);
6652 if (compose->gtkaspell == NULL)
6655 parent_item = gtk_item_factory_get_item(ifactory,
6656 "/Spelling/Options");
6658 /* setting the submenu removes /Spelling/Options from the factory
6659 * so we need to save it */
6661 if (parent_item == NULL) {
6662 parent_item = compose->aspell_options_menu;
6663 gtk_menu_item_remove_submenu(GTK_MENU_ITEM(parent_item));
6665 compose->aspell_options_menu = parent_item;
6667 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
6669 spell_menu = g_slist_reverse(spell_menu);
6670 for (items = spell_menu;
6671 items; items = items->next) {
6672 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
6673 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
6674 gtk_widget_show(GTK_WIDGET(menuitem));
6676 g_slist_free(spell_menu);
6678 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
6683 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
6685 Compose *compose = (Compose *)data;
6686 GdkEventButton event;
6689 event.time = gtk_get_current_event_time();
6693 return text_clicked(compose->text, &event, compose);
6696 static gboolean compose_force_window_origin = TRUE;
6697 static Compose *compose_create(PrefsAccount *account,
6706 GtkWidget *handlebox;
6708 GtkWidget *notebook;
6710 GtkWidget *attach_hbox;
6711 GtkWidget *attach_lab1;
6712 GtkWidget *attach_lab2;
6717 GtkWidget *subject_hbox;
6718 GtkWidget *subject_frame;
6719 GtkWidget *subject_entry;
6723 GtkWidget *edit_vbox;
6724 GtkWidget *ruler_hbox;
6726 GtkWidget *scrolledwin;
6728 GtkTextBuffer *buffer;
6729 GtkClipboard *clipboard;
6731 UndoMain *undostruct;
6733 gchar *titles[N_ATTACH_COLS];
6734 guint n_menu_entries;
6735 GtkWidget *popupmenu;
6736 GtkItemFactory *popupfactory;
6737 GtkItemFactory *ifactory;
6738 GtkWidget *tmpl_menu;
6740 GtkWidget *menuitem;
6743 GtkAspell * gtkaspell = NULL;
6746 static GdkGeometry geometry;
6748 g_return_val_if_fail(account != NULL, NULL);
6750 debug_print("Creating compose window...\n");
6751 compose = g_new0(Compose, 1);
6753 titles[COL_MIMETYPE] = _("MIME type");
6754 titles[COL_SIZE] = _("Size");
6755 titles[COL_NAME] = _("Name");
6757 compose->batch = batch;
6758 compose->account = account;
6759 compose->folder = folder;
6761 compose->mutex = g_mutex_new();
6762 compose->set_cursor_pos = -1;
6764 compose->tooltips = gtk_tooltips_new();
6766 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
6768 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
6769 gtk_widget_set_size_request(window, -1, prefs_common.compose_height);
6771 if (!geometry.max_width) {
6772 geometry.max_width = gdk_screen_width();
6773 geometry.max_height = gdk_screen_height();
6776 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
6777 &geometry, GDK_HINT_MAX_SIZE);
6778 if (!geometry.min_width) {
6779 geometry.min_width = 600;
6780 geometry.min_height = 480;
6782 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
6783 &geometry, GDK_HINT_MIN_SIZE);
6785 #ifndef GENERIC_UMPC
6786 if (compose_force_window_origin)
6787 gtk_widget_set_uposition(window, prefs_common.compose_x,
6788 prefs_common.compose_y);
6790 g_signal_connect(G_OBJECT(window), "delete_event",
6791 G_CALLBACK(compose_delete_cb), compose);
6792 MANAGE_WINDOW_SIGNALS_CONNECT(window);
6793 gtk_widget_realize(window);
6795 gtkut_widget_set_composer_icon(window);
6797 vbox = gtk_vbox_new(FALSE, 0);
6798 gtk_container_add(GTK_CONTAINER(window), vbox);
6800 n_menu_entries = sizeof(compose_entries) / sizeof(compose_entries[0]);
6801 menubar = menubar_create(window, compose_entries,
6802 n_menu_entries, "<Compose>", compose);
6803 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
6805 if (prefs_common.toolbar_detachable) {
6806 handlebox = gtk_handle_box_new();
6808 handlebox = gtk_hbox_new(FALSE, 0);
6810 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
6812 gtk_widget_realize(handlebox);
6814 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, window,
6817 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
6821 vbox2 = gtk_vbox_new(FALSE, 2);
6822 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
6823 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
6826 notebook = gtk_notebook_new();
6827 gtk_widget_set_size_request(notebook, -1, 130);
6828 gtk_widget_show(notebook);
6830 /* header labels and entries */
6831 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6832 compose_create_header(compose),
6833 gtk_label_new_with_mnemonic(_("Hea_der")));
6834 /* attachment list */
6835 attach_hbox = gtk_hbox_new(FALSE, 0);
6836 gtk_widget_show(attach_hbox);
6838 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
6839 gtk_widget_show(attach_lab1);
6840 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
6842 attach_lab2 = gtk_label_new("");
6843 gtk_widget_show(attach_lab2);
6844 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
6846 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6847 compose_create_attach(compose),
6850 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6851 compose_create_others(compose),
6852 gtk_label_new_with_mnemonic(_("Othe_rs")));
6855 subject_hbox = gtk_hbox_new(FALSE, 0);
6856 gtk_widget_show(subject_hbox);
6858 subject_frame = gtk_frame_new(NULL);
6859 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
6860 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
6861 gtk_widget_show(subject_frame);
6863 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
6864 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
6865 gtk_widget_show(subject);
6867 label = gtk_label_new(_("Subject:"));
6868 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
6869 gtk_widget_show(label);
6871 subject_entry = gtk_entry_new();
6872 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
6873 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
6874 G_CALLBACK(compose_grab_focus_cb), compose);
6875 gtk_widget_show(subject_entry);
6876 compose->subject_entry = subject_entry;
6877 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
6879 edit_vbox = gtk_vbox_new(FALSE, 0);
6881 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
6884 ruler_hbox = gtk_hbox_new(FALSE, 0);
6885 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
6887 ruler = gtk_shruler_new();
6888 gtk_ruler_set_range(GTK_RULER(ruler), 0.0, 100.0, 1.0, 100.0);
6889 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
6893 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
6894 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
6895 GTK_POLICY_AUTOMATIC,
6896 GTK_POLICY_AUTOMATIC);
6897 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
6899 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
6900 gtk_widget_set_size_request(scrolledwin, prefs_common.compose_width, -1);
6902 text = gtk_text_view_new();
6903 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
6904 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
6905 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
6906 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
6907 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
6909 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
6911 g_signal_connect_after(G_OBJECT(text), "size_allocate",
6912 G_CALLBACK(compose_edit_size_alloc),
6914 g_signal_connect(G_OBJECT(buffer), "changed",
6915 G_CALLBACK(compose_changed_cb), compose);
6916 g_signal_connect(G_OBJECT(text), "grab_focus",
6917 G_CALLBACK(compose_grab_focus_cb), compose);
6918 g_signal_connect(G_OBJECT(buffer), "insert_text",
6919 G_CALLBACK(text_inserted), compose);
6920 g_signal_connect(G_OBJECT(text), "button_press_event",
6921 G_CALLBACK(text_clicked), compose);
6923 g_signal_connect(G_OBJECT(text), "popup-menu",
6924 G_CALLBACK(compose_popup_menu), compose);
6926 gtk_widget_tap_and_hold_setup(GTK_WIDGET(text), NULL, NULL,
6927 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
6928 g_signal_connect(G_OBJECT(text), "tap-and-hold",
6929 G_CALLBACK(compose_popup_menu), compose);
6931 g_signal_connect(G_OBJECT(subject_entry), "changed",
6932 G_CALLBACK(compose_changed_cb), compose);
6935 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6936 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6937 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6938 g_signal_connect(G_OBJECT(text), "drag_data_received",
6939 G_CALLBACK(compose_insert_drag_received_cb),
6941 g_signal_connect(G_OBJECT(text), "drag-drop",
6942 G_CALLBACK(compose_drag_drop),
6944 gtk_widget_show_all(vbox);
6946 /* pane between attach clist and text */
6947 paned = gtk_vpaned_new();
6948 gtk_paned_set_gutter_size(GTK_PANED(paned), 12);
6949 gtk_container_add(GTK_CONTAINER(vbox2), paned);
6951 if( maemo_mainwindow_is_fullscreen(mainwindow_get_mainwindow()->window) )
6952 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 300 : 280);
6954 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 250 : 230);
6956 gtk_paned_add1(GTK_PANED(paned), notebook);
6957 gtk_paned_add2(GTK_PANED(paned), edit_vbox);
6958 gtk_widget_show_all(paned);
6961 if (prefs_common.textfont) {
6962 PangoFontDescription *font_desc;
6964 font_desc = pango_font_description_from_string
6965 (prefs_common.textfont);
6967 gtk_widget_modify_font(text, font_desc);
6968 pango_font_description_free(font_desc);
6972 n_entries = sizeof(compose_popup_entries) /
6973 sizeof(compose_popup_entries[0]);
6974 popupmenu = menu_create_items(compose_popup_entries, n_entries,
6975 "<Compose>", &popupfactory,
6978 ifactory = gtk_item_factory_from_widget(menubar);
6979 menu_set_sensitive(ifactory, "/Edit/Undo", FALSE);
6980 menu_set_sensitive(ifactory, "/Edit/Redo", FALSE);
6981 menu_set_sensitive(ifactory, "/Options/Remove references", FALSE);
6983 tmpl_menu = gtk_item_factory_get_item(ifactory, "/Tools/Template");
6985 undostruct = undo_init(text);
6986 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
6989 address_completion_start(window);
6991 compose->window = window;
6992 compose->vbox = vbox;
6993 compose->menubar = menubar;
6994 compose->handlebox = handlebox;
6996 compose->vbox2 = vbox2;
6998 compose->paned = paned;
7000 compose->attach_label = attach_lab2;
7002 compose->notebook = notebook;
7003 compose->edit_vbox = edit_vbox;
7004 compose->ruler_hbox = ruler_hbox;
7005 compose->ruler = ruler;
7006 compose->scrolledwin = scrolledwin;
7007 compose->text = text;
7009 compose->focused_editable = NULL;
7011 compose->popupmenu = popupmenu;
7012 compose->popupfactory = popupfactory;
7014 compose->tmpl_menu = tmpl_menu;
7016 compose->mode = mode;
7017 compose->rmode = mode;
7019 compose->targetinfo = NULL;
7020 compose->replyinfo = NULL;
7021 compose->fwdinfo = NULL;
7023 compose->replyto = NULL;
7025 compose->bcc = NULL;
7026 compose->followup_to = NULL;
7028 compose->ml_post = NULL;
7030 compose->inreplyto = NULL;
7031 compose->references = NULL;
7032 compose->msgid = NULL;
7033 compose->boundary = NULL;
7035 compose->autowrap = prefs_common.autowrap;
7037 compose->use_signing = FALSE;
7038 compose->use_encryption = FALSE;
7039 compose->privacy_system = NULL;
7041 compose->modified = FALSE;
7043 compose->return_receipt = FALSE;
7045 compose->to_list = NULL;
7046 compose->newsgroup_list = NULL;
7048 compose->undostruct = undostruct;
7050 compose->sig_str = NULL;
7052 compose->exteditor_file = NULL;
7053 compose->exteditor_pid = -1;
7054 compose->exteditor_tag = -1;
7055 compose->draft_timeout_tag = -2; /* inhibit auto-drafting while loading */
7058 menu_set_sensitive(ifactory, "/Spelling", FALSE);
7059 if (mode != COMPOSE_REDIRECT) {
7060 if (prefs_common.enable_aspell && prefs_common.dictionary &&
7061 strcmp(prefs_common.dictionary, "")) {
7062 gtkaspell = gtkaspell_new(prefs_common.aspell_path,
7063 prefs_common.dictionary,
7064 prefs_common.alt_dictionary,
7065 conv_get_locale_charset_str(),
7066 prefs_common.misspelled_col,
7067 prefs_common.check_while_typing,
7068 prefs_common.recheck_when_changing_dict,
7069 prefs_common.use_alternate,
7070 prefs_common.use_both_dicts,
7071 GTK_TEXT_VIEW(text),
7072 GTK_WINDOW(compose->window),
7073 compose_spell_menu_changed,
7076 alertpanel_error(_("Spell checker could not "
7078 gtkaspell_checkers_strerror());
7079 gtkaspell_checkers_reset_error();
7081 if (!gtkaspell_set_sug_mode(gtkaspell,
7082 prefs_common.aspell_sugmode)) {
7083 debug_print("Aspell: could not set "
7084 "suggestion mode %s\n",
7085 gtkaspell_checkers_strerror());
7086 gtkaspell_checkers_reset_error();
7089 menu_set_sensitive(ifactory, "/Spelling", TRUE);
7093 compose->gtkaspell = gtkaspell;
7094 compose_spell_menu_changed(compose);
7097 compose_select_account(compose, account, TRUE);
7099 menu_set_active(ifactory, "/Edit/Auto wrapping", prefs_common.autowrap);
7100 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
7101 compose_entry_append(compose, account->auto_cc, COMPOSE_CC);
7103 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
7104 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC);
7106 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
7107 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO);
7109 menu_set_sensitive(ifactory, "/Options/Reply mode", compose->mode == COMPOSE_REPLY);
7111 if (account->protocol != A_NNTP)
7112 gtk_entry_set_text(GTK_ENTRY(GTK_BIN(compose->header_last->combo)->child),
7113 prefs_common_translated_header_name("To:"));
7115 gtk_entry_set_text(GTK_ENTRY(GTK_BIN(compose->header_last->combo)->child),
7116 prefs_common_translated_header_name("Newsgroups:"));
7118 addressbook_set_target_compose(compose);
7120 if (mode != COMPOSE_REDIRECT)
7121 compose_set_template_menu(compose);
7123 menuitem = gtk_item_factory_get_item(ifactory, "/Tools/Template");
7124 menu_set_sensitive(ifactory, "/Tools/Template", FALSE);
7127 compose_list = g_list_append(compose_list, compose);
7129 if (!prefs_common.show_ruler)
7130 gtk_widget_hide(ruler_hbox);
7132 menuitem = gtk_item_factory_get_item(ifactory, "/Tools/Show ruler");
7133 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
7134 prefs_common.show_ruler);
7137 compose->priority = PRIORITY_NORMAL;
7138 compose_update_priority_menu_item(compose);
7140 compose_set_out_encoding(compose);
7143 compose_update_actions_menu(compose);
7145 /* Privacy Systems menu */
7146 compose_update_privacy_systems_menu(compose);
7148 activate_privacy_system(compose, account, TRUE);
7149 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
7151 gtk_widget_realize(window);
7153 gtk_widget_show(window);
7155 maemo_window_full_screen_if_needed(GTK_WINDOW(window));
7156 maemo_connect_key_press_to_mainwindow(GTK_WINDOW(window));
7163 static GtkWidget *compose_account_option_menu_create(Compose *compose)
7168 GtkWidget *optmenubox;
7171 GtkWidget *from_name = NULL;
7173 gint num = 0, def_menu = 0;
7175 accounts = account_get_list();
7176 g_return_val_if_fail(accounts != NULL, NULL);
7178 optmenubox = gtk_event_box_new();
7179 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
7180 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
7182 hbox = gtk_hbox_new(FALSE, 6);
7183 from_name = gtk_entry_new();
7185 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
7186 G_CALLBACK(compose_grab_focus_cb), compose);
7188 for (; accounts != NULL; accounts = accounts->next, num++) {
7189 PrefsAccount *ac = (PrefsAccount *)accounts->data;
7190 gchar *name, *from = NULL;
7192 if (ac == compose->account) def_menu = num;
7194 name = g_markup_printf_escaped(_("From: <i>%s</i>"),
7197 if (ac == compose->account) {
7198 if (ac->name && *ac->name) {
7200 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
7201 from = g_strdup_printf("%s <%s>",
7203 gtk_entry_set_text(GTK_ENTRY(from_name), from);
7205 from = g_strdup_printf("%s",
7207 gtk_entry_set_text(GTK_ENTRY(from_name), from);
7210 COMBOBOX_ADD(menu, name, ac->account_id);
7215 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
7217 g_signal_connect(G_OBJECT(optmenu), "changed",
7218 G_CALLBACK(account_activated),
7220 g_signal_connect(G_OBJECT(from_name), "populate-popup",
7221 G_CALLBACK(compose_entry_popup_extend),
7224 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
7225 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
7227 gtk_tooltips_set_tip(compose->tooltips, optmenubox,
7228 _("Account to use for this email"), NULL);
7229 gtk_tooltips_set_tip(compose->tooltips, from_name,
7230 _("Sender address to be used"), NULL);
7232 compose->from_name = from_name;
7237 static void compose_set_priority_cb(gpointer data,
7241 Compose *compose = (Compose *) data;
7242 compose->priority = action;
7245 static void compose_reply_change_mode(gpointer data,
7249 Compose *compose = (Compose *) data;
7250 gboolean was_modified = compose->modified;
7252 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
7254 g_return_if_fail(compose->replyinfo != NULL);
7256 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
7258 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
7260 if (action == COMPOSE_REPLY_TO_ALL)
7262 if (action == COMPOSE_REPLY_TO_SENDER)
7264 if (action == COMPOSE_REPLY_TO_LIST)
7267 compose_remove_header_entries(compose);
7268 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
7269 if (compose->account->set_autocc && compose->account->auto_cc)
7270 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC);
7272 if (compose->account->set_autobcc && compose->account->auto_bcc)
7273 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC);
7275 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
7276 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO);
7277 compose_show_first_last_header(compose, TRUE);
7278 compose->modified = was_modified;
7279 compose_set_title(compose);
7282 static void compose_update_priority_menu_item(Compose * compose)
7284 GtkItemFactory *ifactory;
7285 GtkWidget *menuitem = NULL;
7287 ifactory = gtk_item_factory_from_widget(compose->menubar);
7289 switch (compose->priority) {
7290 case PRIORITY_HIGHEST:
7291 menuitem = gtk_item_factory_get_item
7292 (ifactory, "/Options/Priority/Highest");
7295 menuitem = gtk_item_factory_get_item
7296 (ifactory, "/Options/Priority/High");
7298 case PRIORITY_NORMAL:
7299 menuitem = gtk_item_factory_get_item
7300 (ifactory, "/Options/Priority/Normal");
7303 menuitem = gtk_item_factory_get_item
7304 (ifactory, "/Options/Priority/Low");
7306 case PRIORITY_LOWEST:
7307 menuitem = gtk_item_factory_get_item
7308 (ifactory, "/Options/Priority/Lowest");
7311 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
7314 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
7316 Compose *compose = (Compose *) data;
7318 GtkItemFactory *ifactory;
7319 gboolean can_sign = FALSE, can_encrypt = FALSE;
7321 g_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
7323 if (!GTK_CHECK_MENU_ITEM(widget)->active)
7326 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
7327 g_free(compose->privacy_system);
7328 compose->privacy_system = NULL;
7329 if (systemid != NULL) {
7330 compose->privacy_system = g_strdup(systemid);
7332 can_sign = privacy_system_can_sign(systemid);
7333 can_encrypt = privacy_system_can_encrypt(systemid);
7336 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
7338 ifactory = gtk_item_factory_from_widget(compose->menubar);
7339 menu_set_sensitive(ifactory, "/Options/Sign", can_sign);
7340 menu_set_sensitive(ifactory, "/Options/Encrypt", can_encrypt);
7343 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
7345 static gchar *branch_path = "/Options/Privacy System";
7346 GtkItemFactory *ifactory;
7347 GtkWidget *menuitem = NULL;
7349 gboolean can_sign = FALSE, can_encrypt = FALSE;
7350 gboolean found = FALSE;
7352 ifactory = gtk_item_factory_from_widget(compose->menubar);
7354 if (compose->privacy_system != NULL) {
7356 menuitem = gtk_item_factory_get_widget(ifactory, branch_path);
7357 g_return_if_fail(menuitem != NULL);
7359 amenu = GTK_MENU_SHELL(menuitem)->children;
7361 while (amenu != NULL) {
7362 GList *alist = amenu->next;
7364 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
7365 if (systemid != NULL) {
7366 if (strcmp(systemid, compose->privacy_system) == 0) {
7367 menuitem = GTK_WIDGET(amenu->data);
7369 can_sign = privacy_system_can_sign(systemid);
7370 can_encrypt = privacy_system_can_encrypt(systemid);
7374 } else if (strlen(compose->privacy_system) == 0) {
7375 menuitem = GTK_WIDGET(amenu->data);
7378 can_encrypt = FALSE;
7385 if (menuitem != NULL)
7386 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
7388 if (warn && !found && strlen(compose->privacy_system)) {
7389 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
7390 "will not be able to sign or encrypt this message."),
7391 compose->privacy_system);
7395 menu_set_sensitive(ifactory, "/Options/Sign", can_sign);
7396 menu_set_sensitive(ifactory, "/Options/Encrypt", can_encrypt);
7399 static void compose_set_out_encoding(Compose *compose)
7401 GtkItemFactoryEntry *entry;
7402 GtkItemFactory *ifactory;
7403 CharSet out_encoding;
7404 gchar *path, *p, *q;
7407 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
7408 ifactory = gtk_item_factory_from_widget(compose->menubar);
7410 for (entry = compose_entries; entry->callback != compose_address_cb;
7412 if (entry->callback == compose_set_encoding_cb &&
7413 (CharSet)entry->callback_action == out_encoding) {
7414 p = q = path = g_strdup(entry->path);
7426 item = gtk_item_factory_get_item(ifactory, path);
7427 gtk_widget_activate(item);
7434 static void compose_set_template_menu(Compose *compose)
7436 GSList *tmpl_list, *cur;
7440 tmpl_list = template_get_config();
7442 menu = gtk_menu_new();
7444 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
7445 Template *tmpl = (Template *)cur->data;
7447 item = gtk_menu_item_new_with_label(tmpl->name);
7448 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
7449 g_signal_connect(G_OBJECT(item), "activate",
7450 G_CALLBACK(compose_template_activate_cb),
7452 g_object_set_data(G_OBJECT(item), "template", tmpl);
7453 gtk_widget_show(item);
7456 gtk_widget_show(menu);
7457 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
7460 void compose_update_actions_menu(Compose *compose)
7462 GtkItemFactory *ifactory;
7464 ifactory = gtk_item_factory_from_widget(compose->menubar);
7465 action_update_compose_menu(ifactory, "/Tools/Actions", compose);
7468 static void compose_update_privacy_systems_menu(Compose *compose)
7470 static gchar *branch_path = "/Options/Privacy System";
7471 GtkItemFactory *ifactory;
7472 GtkWidget *menuitem;
7473 GSList *systems, *cur;
7476 GtkWidget *system_none;
7479 ifactory = gtk_item_factory_from_widget(compose->menubar);
7481 /* remove old entries */
7482 menuitem = gtk_item_factory_get_widget(ifactory, branch_path);
7483 g_return_if_fail(menuitem != NULL);
7485 amenu = GTK_MENU_SHELL(menuitem)->children->next;
7486 while (amenu != NULL) {
7487 GList *alist = amenu->next;
7488 gtk_widget_destroy(GTK_WIDGET(amenu->data));
7492 system_none = gtk_item_factory_get_widget(ifactory,
7493 "/Options/Privacy System/None");
7495 g_signal_connect(G_OBJECT(system_none), "activate",
7496 G_CALLBACK(compose_set_privacy_system_cb), compose);
7498 systems = privacy_get_system_ids();
7499 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
7500 gchar *systemid = cur->data;
7502 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
7503 widget = gtk_radio_menu_item_new_with_label(group,
7504 privacy_system_get_name(systemid));
7505 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
7506 g_strdup(systemid), g_free);
7507 g_signal_connect(G_OBJECT(widget), "activate",
7508 G_CALLBACK(compose_set_privacy_system_cb), compose);
7510 gtk_menu_append(GTK_MENU(system_none->parent), widget);
7511 gtk_widget_show(widget);
7514 g_slist_free(systems);
7517 void compose_reflect_prefs_all(void)
7522 for (cur = compose_list; cur != NULL; cur = cur->next) {
7523 compose = (Compose *)cur->data;
7524 compose_set_template_menu(compose);
7528 void compose_reflect_prefs_pixmap_theme(void)
7533 for (cur = compose_list; cur != NULL; cur = cur->next) {
7534 compose = (Compose *)cur->data;
7535 toolbar_update(TOOLBAR_COMPOSE, compose);
7539 static const gchar *compose_quote_char_from_context(Compose *compose)
7541 const gchar *qmark = NULL;
7543 g_return_val_if_fail(compose != NULL, NULL);
7545 switch (compose->mode) {
7546 /* use forward-specific quote char */
7547 case COMPOSE_FORWARD:
7548 case COMPOSE_FORWARD_AS_ATTACH:
7549 case COMPOSE_FORWARD_INLINE:
7550 if (compose->folder && compose->folder->prefs &&
7551 compose->folder->prefs->forward_with_format)
7552 qmark = compose->folder->prefs->forward_quotemark;
7553 else if (compose->account->forward_with_format)
7554 qmark = compose->account->forward_quotemark;
7556 qmark = prefs_common.fw_quotemark;
7559 /* use reply-specific quote char in all other modes */
7561 if (compose->folder && compose->folder->prefs &&
7562 compose->folder->prefs->reply_with_format)
7563 qmark = compose->folder->prefs->reply_quotemark;
7564 else if (compose->account->reply_with_format)
7565 qmark = compose->account->reply_quotemark;
7567 qmark = prefs_common.quotemark;
7571 if (qmark == NULL || *qmark == '\0')
7577 static void compose_template_apply(Compose *compose, Template *tmpl,
7581 GtkTextBuffer *buffer;
7585 gchar *parsed_str = NULL;
7586 gint cursor_pos = 0;
7587 const gchar *err_msg = _("Template body format error at line %d.");
7590 /* process the body */
7592 text = GTK_TEXT_VIEW(compose->text);
7593 buffer = gtk_text_view_get_buffer(text);
7596 qmark = compose_quote_char_from_context(compose);
7598 if (compose->replyinfo != NULL) {
7601 gtk_text_buffer_set_text(buffer, "", -1);
7602 mark = gtk_text_buffer_get_insert(buffer);
7603 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7605 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
7606 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
7608 } else if (compose->fwdinfo != NULL) {
7611 gtk_text_buffer_set_text(buffer, "", -1);
7612 mark = gtk_text_buffer_get_insert(buffer);
7613 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7615 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
7616 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
7619 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
7621 GtkTextIter start, end;
7624 gtk_text_buffer_get_start_iter(buffer, &start);
7625 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
7626 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
7628 /* clear the buffer now */
7630 gtk_text_buffer_set_text(buffer, "", -1);
7632 parsed_str = compose_quote_fmt(compose, dummyinfo,
7633 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
7634 procmsg_msginfo_free( dummyinfo );
7640 gtk_text_buffer_set_text(buffer, "", -1);
7641 mark = gtk_text_buffer_get_insert(buffer);
7642 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7645 if (replace && parsed_str && compose->account->auto_sig)
7646 compose_insert_sig(compose, FALSE);
7648 if (replace && parsed_str) {
7649 gtk_text_buffer_get_start_iter(buffer, &iter);
7650 gtk_text_buffer_place_cursor(buffer, &iter);
7654 cursor_pos = quote_fmt_get_cursor_pos();
7655 compose->set_cursor_pos = cursor_pos;
7656 if (cursor_pos == -1)
7658 gtk_text_buffer_get_start_iter(buffer, &iter);
7659 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
7660 gtk_text_buffer_place_cursor(buffer, &iter);
7663 /* process the other fields */
7665 compose_template_apply_fields(compose, tmpl);
7666 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
7667 quote_fmt_reset_vartable();
7668 compose_changed_cb(NULL, compose);
7671 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
7673 MsgInfo* dummyinfo = NULL;
7674 MsgInfo *msginfo = NULL;
7677 if (compose->replyinfo != NULL)
7678 msginfo = compose->replyinfo;
7679 else if (compose->fwdinfo != NULL)
7680 msginfo = compose->fwdinfo;
7682 dummyinfo = compose_msginfo_new_from_compose(compose);
7683 msginfo = dummyinfo;
7686 if (tmpl->from && *tmpl->from != '\0') {
7688 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
7689 compose->gtkaspell);
7691 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
7693 quote_fmt_scan_string(tmpl->from);
7696 buf = quote_fmt_get_buffer();
7698 alertpanel_error(_("Template From format error."));
7700 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
7704 if (tmpl->to && *tmpl->to != '\0') {
7706 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
7707 compose->gtkaspell);
7709 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
7711 quote_fmt_scan_string(tmpl->to);
7714 buf = quote_fmt_get_buffer();
7716 alertpanel_error(_("Template To format error."));
7718 compose_entry_append(compose, buf, COMPOSE_TO);
7722 if (tmpl->cc && *tmpl->cc != '\0') {
7724 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
7725 compose->gtkaspell);
7727 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
7729 quote_fmt_scan_string(tmpl->cc);
7732 buf = quote_fmt_get_buffer();
7734 alertpanel_error(_("Template Cc format error."));
7736 compose_entry_append(compose, buf, COMPOSE_CC);
7740 if (tmpl->bcc && *tmpl->bcc != '\0') {
7742 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
7743 compose->gtkaspell);
7745 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
7747 quote_fmt_scan_string(tmpl->bcc);
7750 buf = quote_fmt_get_buffer();
7752 alertpanel_error(_("Template Bcc format error."));
7754 compose_entry_append(compose, buf, COMPOSE_BCC);
7758 /* process the subject */
7759 if (tmpl->subject && *tmpl->subject != '\0') {
7761 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
7762 compose->gtkaspell);
7764 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
7766 quote_fmt_scan_string(tmpl->subject);
7769 buf = quote_fmt_get_buffer();
7771 alertpanel_error(_("Template subject format error."));
7773 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
7777 procmsg_msginfo_free( dummyinfo );
7780 static void compose_destroy(Compose *compose)
7782 GtkTextBuffer *buffer;
7783 GtkClipboard *clipboard;
7785 compose_list = g_list_remove(compose_list, compose);
7787 if (compose->updating) {
7788 debug_print("danger, not destroying anything now\n");
7789 compose->deferred_destroy = TRUE;
7792 /* NOTE: address_completion_end() does nothing with the window
7793 * however this may change. */
7794 address_completion_end(compose->window);
7796 slist_free_strings(compose->to_list);
7797 g_slist_free(compose->to_list);
7798 slist_free_strings(compose->newsgroup_list);
7799 g_slist_free(compose->newsgroup_list);
7800 slist_free_strings(compose->header_list);
7801 g_slist_free(compose->header_list);
7803 procmsg_msginfo_free(compose->targetinfo);
7804 procmsg_msginfo_free(compose->replyinfo);
7805 procmsg_msginfo_free(compose->fwdinfo);
7807 g_free(compose->replyto);
7808 g_free(compose->cc);
7809 g_free(compose->bcc);
7810 g_free(compose->newsgroups);
7811 g_free(compose->followup_to);
7813 g_free(compose->ml_post);
7815 g_free(compose->inreplyto);
7816 g_free(compose->references);
7817 g_free(compose->msgid);
7818 g_free(compose->boundary);
7820 g_free(compose->redirect_filename);
7821 if (compose->undostruct)
7822 undo_destroy(compose->undostruct);
7824 g_free(compose->sig_str);
7826 g_free(compose->exteditor_file);
7828 g_free(compose->orig_charset);
7830 g_free(compose->privacy_system);
7832 if (addressbook_get_target_compose() == compose)
7833 addressbook_set_target_compose(NULL);
7836 if (compose->gtkaspell) {
7837 gtkaspell_delete(compose->gtkaspell);
7838 compose->gtkaspell = NULL;
7842 prefs_common.compose_width = compose->scrolledwin->allocation.width;
7843 prefs_common.compose_height = compose->window->allocation.height;
7845 if (!gtk_widget_get_parent(compose->paned))
7846 gtk_widget_destroy(compose->paned);
7847 gtk_widget_destroy(compose->popupmenu);
7849 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
7850 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7851 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
7853 gtk_widget_destroy(compose->window);
7854 toolbar_destroy(compose->toolbar);
7855 g_free(compose->toolbar);
7856 g_mutex_free(compose->mutex);
7860 static void compose_attach_info_free(AttachInfo *ainfo)
7862 g_free(ainfo->file);
7863 g_free(ainfo->content_type);
7864 g_free(ainfo->name);
7868 static void compose_attach_update_label(Compose *compose)
7873 GtkTreeModel *model;
7878 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
7879 if(!gtk_tree_model_get_iter_first(model, &iter)) {
7880 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
7884 while(gtk_tree_model_iter_next(model, &iter))
7887 text = g_strdup_printf("(%d)", i);
7888 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
7892 static void compose_attach_remove_selected(Compose *compose)
7894 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
7895 GtkTreeSelection *selection;
7897 GtkTreeModel *model;
7899 selection = gtk_tree_view_get_selection(tree_view);
7900 sel = gtk_tree_selection_get_selected_rows(selection, &model);
7905 for (cur = sel; cur != NULL; cur = cur->next) {
7906 GtkTreePath *path = cur->data;
7907 GtkTreeRowReference *ref = gtk_tree_row_reference_new
7910 gtk_tree_path_free(path);
7913 for (cur = sel; cur != NULL; cur = cur->next) {
7914 GtkTreeRowReference *ref = cur->data;
7915 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
7918 if (gtk_tree_model_get_iter(model, &iter, path))
7919 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
7921 gtk_tree_path_free(path);
7922 gtk_tree_row_reference_free(ref);
7926 compose_attach_update_label(compose);
7929 static struct _AttachProperty
7932 GtkWidget *mimetype_entry;
7933 GtkWidget *encoding_optmenu;
7934 GtkWidget *path_entry;
7935 GtkWidget *filename_entry;
7937 GtkWidget *cancel_btn;
7940 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
7942 gtk_tree_path_free((GtkTreePath *)ptr);
7945 static void compose_attach_property(Compose *compose)
7947 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
7949 GtkComboBox *optmenu;
7950 GtkTreeSelection *selection;
7952 GtkTreeModel *model;
7955 static gboolean cancelled;
7957 /* only if one selected */
7958 selection = gtk_tree_view_get_selection(tree_view);
7959 if (gtk_tree_selection_count_selected_rows(selection) != 1)
7962 sel = gtk_tree_selection_get_selected_rows(selection, &model);
7966 path = (GtkTreePath *) sel->data;
7967 gtk_tree_model_get_iter(model, &iter, path);
7968 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
7971 g_list_foreach(sel, gtk_tree_path_free_, NULL);
7977 if (!attach_prop.window)
7978 compose_attach_property_create(&cancelled);
7979 gtk_widget_grab_focus(attach_prop.ok_btn);
7980 gtk_widget_show(attach_prop.window);
7981 manage_window_set_transient(GTK_WINDOW(attach_prop.window));
7983 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
7984 if (ainfo->encoding == ENC_UNKNOWN)
7985 combobox_select_by_data(optmenu, ENC_BASE64);
7987 combobox_select_by_data(optmenu, ainfo->encoding);
7989 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
7990 ainfo->content_type ? ainfo->content_type : "");
7991 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
7992 ainfo->file ? ainfo->file : "");
7993 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
7994 ainfo->name ? ainfo->name : "");
7997 const gchar *entry_text;
7999 gchar *cnttype = NULL;
8006 gtk_widget_hide(attach_prop.window);
8011 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
8012 if (*entry_text != '\0') {
8015 text = g_strstrip(g_strdup(entry_text));
8016 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
8017 cnttype = g_strdup(text);
8020 alertpanel_error(_("Invalid MIME type."));
8026 ainfo->encoding = combobox_get_active_data(optmenu);
8028 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
8029 if (*entry_text != '\0') {
8030 if (is_file_exist(entry_text) &&
8031 (size = get_file_size(entry_text)) > 0)
8032 file = g_strdup(entry_text);
8035 (_("File doesn't exist or is empty."));
8041 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
8042 if (*entry_text != '\0') {
8043 g_free(ainfo->name);
8044 ainfo->name = g_strdup(entry_text);
8048 g_free(ainfo->content_type);
8049 ainfo->content_type = cnttype;
8052 g_free(ainfo->file);
8056 ainfo->size = (goffset)size;
8058 /* update tree store */
8059 text = to_human_readable(ainfo->size);
8060 gtk_tree_model_get_iter(model, &iter, path);
8061 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
8062 COL_MIMETYPE, ainfo->content_type,
8064 COL_NAME, ainfo->name,
8070 gtk_tree_path_free(path);
8073 #define SET_LABEL_AND_ENTRY(str, entry, top) \
8075 label = gtk_label_new(str); \
8076 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
8077 GTK_FILL, 0, 0, 0); \
8078 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
8080 entry = gtk_entry_new(); \
8081 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
8082 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
8085 static void compose_attach_property_create(gboolean *cancelled)
8091 GtkWidget *mimetype_entry;
8094 GtkListStore *optmenu_menu;
8095 GtkWidget *path_entry;
8096 GtkWidget *filename_entry;
8099 GtkWidget *cancel_btn;
8100 GList *mime_type_list, *strlist;
8103 debug_print("Creating attach_property window...\n");
8105 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
8106 gtk_widget_set_size_request(window, 480, -1);
8107 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
8108 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
8109 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
8110 gtk_window_set_modal(GTK_WINDOW(window), TRUE);
8111 g_signal_connect(G_OBJECT(window), "delete_event",
8112 G_CALLBACK(attach_property_delete_event),
8114 g_signal_connect(G_OBJECT(window), "key_press_event",
8115 G_CALLBACK(attach_property_key_pressed),
8118 vbox = gtk_vbox_new(FALSE, 8);
8119 gtk_container_add(GTK_CONTAINER(window), vbox);
8121 table = gtk_table_new(4, 2, FALSE);
8122 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
8123 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
8124 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
8126 label = gtk_label_new(_("MIME type"));
8127 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
8129 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
8130 mimetype_entry = gtk_combo_box_entry_new_text();
8131 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
8132 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
8134 /* stuff with list */
8135 mime_type_list = procmime_get_mime_type_list();
8137 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
8138 MimeType *type = (MimeType *) mime_type_list->data;
8141 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
8143 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
8146 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
8147 (GCompareFunc)strcmp2);
8150 for (mime_type_list = strlist; mime_type_list != NULL;
8151 mime_type_list = mime_type_list->next) {
8152 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
8153 g_free(mime_type_list->data);
8155 g_list_free(strlist);
8156 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
8157 mimetype_entry = GTK_BIN(mimetype_entry)->child;
8159 label = gtk_label_new(_("Encoding"));
8160 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
8162 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
8164 hbox = gtk_hbox_new(FALSE, 0);
8165 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
8166 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
8168 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
8169 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8171 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
8172 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
8173 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
8174 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
8175 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
8177 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
8179 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
8180 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
8182 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
8183 &ok_btn, GTK_STOCK_OK,
8185 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
8186 gtk_widget_grab_default(ok_btn);
8188 g_signal_connect(G_OBJECT(ok_btn), "clicked",
8189 G_CALLBACK(attach_property_ok),
8191 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
8192 G_CALLBACK(attach_property_cancel),
8195 gtk_widget_show_all(vbox);
8197 attach_prop.window = window;
8198 attach_prop.mimetype_entry = mimetype_entry;
8199 attach_prop.encoding_optmenu = optmenu;
8200 attach_prop.path_entry = path_entry;
8201 attach_prop.filename_entry = filename_entry;
8202 attach_prop.ok_btn = ok_btn;
8203 attach_prop.cancel_btn = cancel_btn;
8206 #undef SET_LABEL_AND_ENTRY
8208 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
8214 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
8220 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
8221 gboolean *cancelled)
8229 static gboolean attach_property_key_pressed(GtkWidget *widget,
8231 gboolean *cancelled)
8233 if (event && event->keyval == GDK_Escape) {
8240 static void compose_exec_ext_editor(Compose *compose)
8247 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
8248 G_DIR_SEPARATOR, compose);
8250 if (pipe(pipe_fds) < 0) {
8256 if ((pid = fork()) < 0) {
8263 /* close the write side of the pipe */
8266 compose->exteditor_file = g_strdup(tmp);
8267 compose->exteditor_pid = pid;
8269 compose_set_ext_editor_sensitive(compose, FALSE);
8271 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
8272 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
8276 } else { /* process-monitoring process */
8282 /* close the read side of the pipe */
8285 if (compose_write_body_to_file(compose, tmp) < 0) {
8286 fd_write_all(pipe_fds[1], "2\n", 2);
8290 pid_ed = compose_exec_ext_editor_real(tmp);
8292 fd_write_all(pipe_fds[1], "1\n", 2);
8296 /* wait until editor is terminated */
8297 waitpid(pid_ed, NULL, 0);
8299 fd_write_all(pipe_fds[1], "0\n", 2);
8306 #endif /* G_OS_UNIX */
8310 static gint compose_exec_ext_editor_real(const gchar *file)
8317 g_return_val_if_fail(file != NULL, -1);
8319 if ((pid = fork()) < 0) {
8324 if (pid != 0) return pid;
8326 /* grandchild process */
8328 if (setpgid(0, getppid()))
8331 if (prefs_common_get_ext_editor_cmd() &&
8332 (p = strchr(prefs_common_get_ext_editor_cmd(), '%')) &&
8333 *(p + 1) == 's' && !strchr(p + 2, '%')) {
8334 g_snprintf(buf, sizeof(buf), prefs_common_get_ext_editor_cmd(), file);
8336 if (prefs_common_get_ext_editor_cmd())
8337 g_warning("External editor command line is invalid: '%s'\n",
8338 prefs_common_get_ext_editor_cmd());
8339 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
8342 cmdline = strsplit_with_quote(buf, " ", 1024);
8343 execvp(cmdline[0], cmdline);
8346 g_strfreev(cmdline);
8351 static gboolean compose_ext_editor_kill(Compose *compose)
8353 pid_t pgid = compose->exteditor_pid * -1;
8356 ret = kill(pgid, 0);
8358 if (ret == 0 || (ret == -1 && EPERM == errno)) {
8362 msg = g_strdup_printf
8363 (_("The external editor is still working.\n"
8364 "Force terminating the process?\n"
8365 "process group id: %d"), -pgid);
8366 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
8367 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
8371 if (val == G_ALERTALTERNATE) {
8372 g_source_remove(compose->exteditor_tag);
8373 g_io_channel_shutdown(compose->exteditor_ch,
8375 g_io_channel_unref(compose->exteditor_ch);
8377 if (kill(pgid, SIGTERM) < 0) perror("kill");
8378 waitpid(compose->exteditor_pid, NULL, 0);
8380 g_warning("Terminated process group id: %d", -pgid);
8381 g_warning("Temporary file: %s",
8382 compose->exteditor_file);
8384 compose_set_ext_editor_sensitive(compose, TRUE);
8386 g_free(compose->exteditor_file);
8387 compose->exteditor_file = NULL;
8388 compose->exteditor_pid = -1;
8389 compose->exteditor_ch = NULL;
8390 compose->exteditor_tag = -1;
8398 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
8402 Compose *compose = (Compose *)data;
8405 debug_print(_("Compose: input from monitoring process\n"));
8407 g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL);
8409 g_io_channel_shutdown(source, FALSE, NULL);
8410 g_io_channel_unref(source);
8412 waitpid(compose->exteditor_pid, NULL, 0);
8414 if (buf[0] == '0') { /* success */
8415 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
8416 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
8418 gtk_text_buffer_set_text(buffer, "", -1);
8419 compose_insert_file(compose, compose->exteditor_file);
8420 compose_changed_cb(NULL, compose);
8422 if (claws_unlink(compose->exteditor_file) < 0)
8423 FILE_OP_ERROR(compose->exteditor_file, "unlink");
8424 } else if (buf[0] == '1') { /* failed */
8425 g_warning("Couldn't exec external editor\n");
8426 if (claws_unlink(compose->exteditor_file) < 0)
8427 FILE_OP_ERROR(compose->exteditor_file, "unlink");
8428 } else if (buf[0] == '2') {
8429 g_warning("Couldn't write to file\n");
8430 } else if (buf[0] == '3') {
8431 g_warning("Pipe read failed\n");
8434 compose_set_ext_editor_sensitive(compose, TRUE);
8436 g_free(compose->exteditor_file);
8437 compose->exteditor_file = NULL;
8438 compose->exteditor_pid = -1;
8439 compose->exteditor_ch = NULL;
8440 compose->exteditor_tag = -1;
8445 static void compose_set_ext_editor_sensitive(Compose *compose,
8448 GtkItemFactory *ifactory;
8450 ifactory = gtk_item_factory_from_widget(compose->menubar);
8452 menu_set_sensitive(ifactory, "/Message/Send", sensitive);
8453 menu_set_sensitive(ifactory, "/Message/Send later", sensitive);
8454 menu_set_sensitive(ifactory, "/Message/Insert file", sensitive);
8455 menu_set_sensitive(ifactory, "/Message/Insert signature", sensitive);
8456 menu_set_sensitive(ifactory, "/Edit/Wrap current paragraph", sensitive);
8457 menu_set_sensitive(ifactory, "/Edit/Wrap all long lines", sensitive);
8458 menu_set_sensitive(ifactory, "/Edit/Edit with external editor",
8461 gtk_widget_set_sensitive(compose->text, sensitive);
8462 if (compose->toolbar->send_btn)
8463 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
8464 if (compose->toolbar->sendl_btn)
8465 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
8466 if (compose->toolbar->draft_btn)
8467 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
8468 if (compose->toolbar->insert_btn)
8469 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
8470 if (compose->toolbar->sig_btn)
8471 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
8472 if (compose->toolbar->exteditor_btn)
8473 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
8474 if (compose->toolbar->linewrap_current_btn)
8475 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
8476 if (compose->toolbar->linewrap_all_btn)
8477 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
8479 #endif /* G_OS_UNIX */
8482 * compose_undo_state_changed:
8484 * Change the sensivity of the menuentries undo and redo
8486 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
8487 gint redo_state, gpointer data)
8489 GtkWidget *widget = GTK_WIDGET(data);
8490 GtkItemFactory *ifactory;
8492 g_return_if_fail(widget != NULL);
8494 ifactory = gtk_item_factory_from_widget(widget);
8496 switch (undo_state) {
8497 case UNDO_STATE_TRUE:
8498 if (!undostruct->undo_state) {
8499 undostruct->undo_state = TRUE;
8500 menu_set_sensitive(ifactory, "/Edit/Undo", TRUE);
8503 case UNDO_STATE_FALSE:
8504 if (undostruct->undo_state) {
8505 undostruct->undo_state = FALSE;
8506 menu_set_sensitive(ifactory, "/Edit/Undo", FALSE);
8509 case UNDO_STATE_UNCHANGED:
8511 case UNDO_STATE_REFRESH:
8512 menu_set_sensitive(ifactory, "/Edit/Undo",
8513 undostruct->undo_state);
8516 g_warning("Undo state not recognized");
8520 switch (redo_state) {
8521 case UNDO_STATE_TRUE:
8522 if (!undostruct->redo_state) {
8523 undostruct->redo_state = TRUE;
8524 menu_set_sensitive(ifactory, "/Edit/Redo", TRUE);
8527 case UNDO_STATE_FALSE:
8528 if (undostruct->redo_state) {
8529 undostruct->redo_state = FALSE;
8530 menu_set_sensitive(ifactory, "/Edit/Redo", FALSE);
8533 case UNDO_STATE_UNCHANGED:
8535 case UNDO_STATE_REFRESH:
8536 menu_set_sensitive(ifactory, "/Edit/Redo",
8537 undostruct->redo_state);
8540 g_warning("Redo state not recognized");
8545 /* callback functions */
8547 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
8548 * includes "non-client" (windows-izm) in calculation, so this calculation
8549 * may not be accurate.
8551 static gboolean compose_edit_size_alloc(GtkEditable *widget,
8552 GtkAllocation *allocation,
8553 GtkSHRuler *shruler)
8555 if (prefs_common.show_ruler) {
8556 gint char_width = 0, char_height = 0;
8557 gint line_width_in_chars;
8559 gtkut_get_font_size(GTK_WIDGET(widget),
8560 &char_width, &char_height);
8561 line_width_in_chars =
8562 (allocation->width - allocation->x) / char_width;
8564 /* got the maximum */
8565 gtk_ruler_set_range(GTK_RULER(shruler),
8566 0.0, line_width_in_chars, 0,
8567 /*line_width_in_chars*/ char_width);
8573 static void account_activated(GtkComboBox *optmenu, gpointer data)
8575 Compose *compose = (Compose *)data;
8578 gchar *folderidentifier;
8579 gint account_id = 0;
8583 /* Get ID of active account in the combo box */
8584 menu = gtk_combo_box_get_model(optmenu);
8585 gtk_combo_box_get_active_iter(optmenu, &iter);
8586 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
8588 ac = account_find_from_id(account_id);
8589 g_return_if_fail(ac != NULL);
8591 if (ac != compose->account)
8592 compose_select_account(compose, ac, FALSE);
8594 /* Set message save folder */
8595 if (account_get_special_folder(compose->account, F_OUTBOX)) {
8596 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
8598 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
8599 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
8601 gtk_editable_delete_text(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
8602 if (account_get_special_folder(compose->account, F_OUTBOX)) {
8603 folderidentifier = folder_item_get_identifier(account_get_special_folder
8604 (compose->account, F_OUTBOX));
8605 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
8606 g_free(folderidentifier);
8610 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
8611 GtkTreeViewColumn *column, Compose *compose)
8613 compose_attach_property(compose);
8616 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
8619 Compose *compose = (Compose *)data;
8620 GtkTreeSelection *attach_selection;
8621 gint attach_nr_selected;
8622 GtkItemFactory *ifactory;
8624 if (!event) return FALSE;
8626 if (event->button == 3) {
8627 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
8628 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
8629 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
8631 if (attach_nr_selected > 0)
8633 menu_set_sensitive(ifactory, "/Remove", TRUE);
8634 menu_set_sensitive(ifactory, "/Properties...", TRUE);
8636 menu_set_sensitive(ifactory, "/Remove", FALSE);
8637 menu_set_sensitive(ifactory, "/Properties...", FALSE);
8640 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
8641 NULL, NULL, event->button, event->time);
8648 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
8651 Compose *compose = (Compose *)data;
8653 if (!event) return FALSE;
8655 switch (event->keyval) {
8657 compose_attach_remove_selected(compose);
8663 static void compose_allow_user_actions (Compose *compose, gboolean allow)
8665 GtkItemFactory *ifactory = gtk_item_factory_from_widget(compose->menubar);
8666 toolbar_comp_set_sensitive(compose, allow);
8667 menu_set_sensitive(ifactory, "/Message", allow);
8668 menu_set_sensitive(ifactory, "/Edit", allow);
8670 menu_set_sensitive(ifactory, "/Spelling", allow);
8672 menu_set_sensitive(ifactory, "/Options", allow);
8673 menu_set_sensitive(ifactory, "/Tools", allow);
8674 menu_set_sensitive(ifactory, "/Help", allow);
8676 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
8680 static void compose_send_cb(gpointer data, guint action, GtkWidget *widget)
8682 Compose *compose = (Compose *)data;
8684 if (prefs_common.work_offline &&
8685 !inc_offline_should_override(TRUE,
8686 _("Claws Mail needs network access in order "
8687 "to send this email.")))
8690 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
8691 g_source_remove(compose->draft_timeout_tag);
8692 compose->draft_timeout_tag = -1;
8695 compose_send(compose);
8698 static void compose_send_later_cb(gpointer data, guint action,
8701 Compose *compose = (Compose *)data;
8705 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
8709 compose_close(compose);
8710 } else if (val == -1) {
8711 alertpanel_error(_("Could not queue message."));
8712 } else if (val == -2) {
8713 alertpanel_error(_("Could not queue message:\n\n%s."), strerror(errno));
8714 } else if (val == -3) {
8715 if (privacy_peek_error())
8716 alertpanel_error(_("Could not queue message for sending:\n\n"
8717 "Signature failed: %s"), privacy_get_error());
8718 } else if (val == -4) {
8719 alertpanel_error(_("Could not queue message for sending:\n\n"
8720 "Charset conversion failed."));
8721 } else if (val == -5) {
8722 alertpanel_error(_("Could not queue message for sending:\n\n"
8723 "Couldn't get recipient encryption key."));
8724 } else if (val == -6) {
8727 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
8730 #define DRAFTED_AT_EXIT "drafted_at_exit"
8731 static void compose_register_draft(MsgInfo *info)
8733 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8734 DRAFTED_AT_EXIT, NULL);
8735 FILE *fp = fopen(filepath, "ab");
8738 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
8746 gboolean compose_draft (gpointer data, guint action)
8748 Compose *compose = (Compose *)data;
8752 MsgFlags flag = {0, 0};
8753 static gboolean lock = FALSE;
8754 MsgInfo *newmsginfo;
8756 gboolean target_locked = FALSE;
8757 gboolean err = FALSE;
8759 if (lock) return FALSE;
8761 if (compose->sending)
8764 draft = account_get_special_folder(compose->account, F_DRAFT);
8765 g_return_val_if_fail(draft != NULL, FALSE);
8767 if (!g_mutex_trylock(compose->mutex)) {
8768 /* we don't want to lock the mutex once it's available,
8769 * because as the only other part of compose.c locking
8770 * it is compose_close - which means once unlocked,
8771 * the compose struct will be freed */
8772 debug_print("couldn't lock mutex, probably sending\n");
8778 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
8779 G_DIR_SEPARATOR, compose);
8780 if ((fp = g_fopen(tmp, "wb")) == NULL) {
8781 FILE_OP_ERROR(tmp, "fopen");
8785 /* chmod for security */
8786 if (change_file_mode_rw(fp, tmp) < 0) {
8787 FILE_OP_ERROR(tmp, "chmod");
8788 g_warning("can't change file mode\n");
8791 /* Save draft infos */
8792 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
8793 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
8795 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
8796 gchar *savefolderid;
8798 savefolderid = gtk_editable_get_chars(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
8799 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
8800 g_free(savefolderid);
8802 if (compose->return_receipt) {
8803 err |= (fprintf(fp, "RRCPT:1\n") < 0);
8805 if (compose->privacy_system) {
8806 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
8807 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
8808 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
8811 /* Message-ID of message replying to */
8812 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
8815 folderid = folder_item_get_identifier(compose->replyinfo->folder);
8816 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
8819 /* Message-ID of message forwarding to */
8820 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
8823 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
8824 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
8828 /* end of headers */
8829 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
8836 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
8840 if (fclose(fp) == EOF) {
8844 if (compose->targetinfo) {
8845 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
8846 flag.perm_flags = target_locked?MSG_LOCKED:0;
8848 flag.tmp_flags = MSG_DRAFT;
8850 folder_item_scan(draft);
8851 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
8852 MsgInfo *tmpinfo = NULL;
8853 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
8854 if (compose->msgid) {
8855 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
8858 msgnum = tmpinfo->msgnum;
8859 procmsg_msginfo_free(tmpinfo);
8860 debug_print("got draft msgnum %d from scanning\n", msgnum);
8862 debug_print("didn't get draft msgnum after scanning\n");
8865 debug_print("got draft msgnum %d from adding\n", msgnum);
8871 if (action != COMPOSE_AUTO_SAVE) {
8872 if (action != COMPOSE_DRAFT_FOR_EXIT)
8873 alertpanel_error(_("Could not save draft."));
8876 gtkut_window_popup(compose->window);
8877 val = alertpanel_full(_("Could not save draft"),
8878 _("Could not save draft.\n"
8879 "Do you want to cancel exit or discard this email?"),
8880 _("_Cancel exit"), _("_Discard email"), NULL,
8881 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
8882 if (val == G_ALERTALTERNATE) {
8884 g_mutex_unlock(compose->mutex); /* must be done before closing */
8885 compose_close(compose);
8889 g_mutex_unlock(compose->mutex); /* must be done before closing */
8898 if (compose->mode == COMPOSE_REEDIT) {
8899 compose_remove_reedit_target(compose, TRUE);
8902 newmsginfo = folder_item_get_msginfo(draft, msgnum);
8905 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
8907 procmsg_msginfo_set_flags(newmsginfo, MSG_LOCKED, MSG_DRAFT);
8909 procmsg_msginfo_set_flags(newmsginfo, 0, MSG_DRAFT);
8910 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
8911 procmsg_msginfo_set_flags(newmsginfo, 0,
8912 MSG_HAS_ATTACHMENT);
8914 if (action == COMPOSE_DRAFT_FOR_EXIT) {
8915 compose_register_draft(newmsginfo);
8917 procmsg_msginfo_free(newmsginfo);
8920 folder_item_scan(draft);
8922 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
8924 g_mutex_unlock(compose->mutex); /* must be done before closing */
8925 compose_close(compose);
8931 path = folder_item_fetch_msg(draft, msgnum);
8933 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
8936 if (g_stat(path, &s) < 0) {
8937 FILE_OP_ERROR(path, "stat");
8943 procmsg_msginfo_free(compose->targetinfo);
8944 compose->targetinfo = procmsg_msginfo_new();
8945 compose->targetinfo->msgnum = msgnum;
8946 compose->targetinfo->size = (goffset)s.st_size;
8947 compose->targetinfo->mtime = s.st_mtime;
8948 compose->targetinfo->folder = draft;
8950 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
8951 compose->mode = COMPOSE_REEDIT;
8953 if (action == COMPOSE_AUTO_SAVE) {
8954 compose->autosaved_draft = compose->targetinfo;
8956 compose->modified = FALSE;
8957 compose_set_title(compose);
8961 g_mutex_unlock(compose->mutex);
8965 void compose_clear_exit_drafts(void)
8967 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8968 DRAFTED_AT_EXIT, NULL);
8969 if (is_file_exist(filepath))
8970 claws_unlink(filepath);
8975 void compose_reopen_exit_drafts(void)
8977 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8978 DRAFTED_AT_EXIT, NULL);
8979 FILE *fp = fopen(filepath, "rb");
8983 while (fgets(buf, sizeof(buf), fp)) {
8984 gchar **parts = g_strsplit(buf, "\t", 2);
8985 const gchar *folder = parts[0];
8986 int msgnum = parts[1] ? atoi(parts[1]):-1;
8988 if (folder && *folder && msgnum > -1) {
8989 FolderItem *item = folder_find_item_from_identifier(folder);
8990 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
8992 compose_reedit(info, FALSE);
8999 compose_clear_exit_drafts();
9002 static void compose_draft_cb(gpointer data, guint action, GtkWidget *widget)
9004 compose_draft(data, action);
9007 static void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
9009 if (compose && file_list) {
9012 for ( tmp = file_list; tmp; tmp = tmp->next) {
9013 gchar *file = (gchar *) tmp->data;
9014 gchar *utf8_filename = conv_filename_to_utf8(file);
9015 compose_attach_append(compose, file, utf8_filename, NULL);
9016 compose_changed_cb(NULL, compose);
9021 g_free(utf8_filename);
9026 static void compose_attach_cb(gpointer data, guint action, GtkWidget *widget)
9028 Compose *compose = (Compose *)data;
9031 if (compose->redirect_filename != NULL)
9034 file_list = filesel_select_multiple_files_open(_("Select file"));
9037 compose_attach_from_list(compose, file_list, TRUE);
9038 g_list_free(file_list);
9042 static void compose_insert_file_cb(gpointer data, guint action,
9045 Compose *compose = (Compose *)data;
9048 file_list = filesel_select_multiple_files_open(_("Select file"));
9053 for ( tmp = file_list; tmp; tmp = tmp->next) {
9054 gchar *file = (gchar *) tmp->data;
9055 gchar *filedup = g_strdup(file);
9056 gchar *shortfile = g_path_get_basename(filedup);
9057 ComposeInsertResult res;
9059 res = compose_insert_file(compose, file);
9060 if (res == COMPOSE_INSERT_READ_ERROR) {
9061 alertpanel_error(_("File '%s' could not be read."), shortfile);
9062 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
9063 alertpanel_error(_("File '%s' contained invalid characters\n"
9064 "for the current encoding, insertion may be incorrect."), shortfile);
9070 g_list_free(file_list);
9074 static void compose_insert_sig_cb(gpointer data, guint action,
9077 Compose *compose = (Compose *)data;
9079 compose_insert_sig(compose, FALSE);
9082 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
9086 Compose *compose = (Compose *)data;
9088 gtkut_widget_get_uposition(widget, &x, &y);
9089 prefs_common.compose_x = x;
9090 prefs_common.compose_y = y;
9092 if (compose->sending || compose->updating)
9094 compose_close_cb(compose, 0, NULL);
9098 void compose_close_toolbar(Compose *compose)
9100 compose_close_cb(compose, 0, NULL);
9103 static void compose_close_cb(gpointer data, guint action, GtkWidget *widget)
9105 Compose *compose = (Compose *)data;
9109 if (compose->exteditor_tag != -1) {
9110 if (!compose_ext_editor_kill(compose))
9115 if (compose->modified) {
9116 if (!g_mutex_trylock(compose->mutex)) {
9117 /* we don't want to lock the mutex once it's available,
9118 * because as the only other part of compose.c locking
9119 * it is compose_close - which means once unlocked,
9120 * the compose struct will be freed */
9121 debug_print("couldn't lock mutex, probably sending\n");
9124 val = alertpanel(_("Discard message"),
9125 _("This message has been modified. Discard it?"),
9126 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
9127 g_mutex_unlock(compose->mutex);
9129 case G_ALERTDEFAULT:
9130 if (prefs_common.autosave)
9131 compose_remove_draft(compose);
9133 case G_ALERTALTERNATE:
9134 compose_draft_cb(data, COMPOSE_QUIT_EDITING, NULL);
9141 compose_close(compose);
9144 static void compose_set_encoding_cb(gpointer data, guint action,
9147 Compose *compose = (Compose *)data;
9149 if (GTK_CHECK_MENU_ITEM(widget)->active)
9150 compose->out_encoding = (CharSet)action;
9153 static void compose_address_cb(gpointer data, guint action, GtkWidget *widget)
9155 Compose *compose = (Compose *)data;
9157 addressbook_open(compose);
9160 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
9162 Compose *compose = (Compose *)data;
9167 tmpl = g_object_get_data(G_OBJECT(widget), "template");
9168 g_return_if_fail(tmpl != NULL);
9170 msg = g_strdup_printf(_("Do you want to apply the template '%s' ?"),
9172 val = alertpanel(_("Apply template"), msg,
9173 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
9176 if (val == G_ALERTDEFAULT)
9177 compose_template_apply(compose, tmpl, TRUE);
9178 else if (val == G_ALERTALTERNATE)
9179 compose_template_apply(compose, tmpl, FALSE);
9182 static void compose_ext_editor_cb(gpointer data, guint action,
9185 Compose *compose = (Compose *)data;
9187 compose_exec_ext_editor(compose);
9190 static void compose_undo_cb(Compose *compose)
9192 gboolean prev_autowrap = compose->autowrap;
9194 compose->autowrap = FALSE;
9195 undo_undo(compose->undostruct);
9196 compose->autowrap = prev_autowrap;
9199 static void compose_redo_cb(Compose *compose)
9201 gboolean prev_autowrap = compose->autowrap;
9203 compose->autowrap = FALSE;
9204 undo_redo(compose->undostruct);
9205 compose->autowrap = prev_autowrap;
9208 static void entry_cut_clipboard(GtkWidget *entry)
9210 if (GTK_IS_EDITABLE(entry))
9211 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
9212 else if (GTK_IS_TEXT_VIEW(entry))
9213 gtk_text_buffer_cut_clipboard(
9214 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
9215 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
9219 static void entry_copy_clipboard(GtkWidget *entry)
9221 if (GTK_IS_EDITABLE(entry))
9222 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
9223 else if (GTK_IS_TEXT_VIEW(entry))
9224 gtk_text_buffer_copy_clipboard(
9225 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
9226 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
9229 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
9230 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
9232 if (GTK_IS_TEXT_VIEW(entry)) {
9233 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
9234 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
9235 GtkTextIter start_iter, end_iter;
9237 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
9239 if (contents == NULL)
9242 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
9244 /* we shouldn't delete the selection when middle-click-pasting, or we
9245 * can't mid-click-paste our own selection */
9246 if (clip != GDK_SELECTION_PRIMARY) {
9247 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
9250 if (insert_place == NULL) {
9251 /* if insert_place isn't specified, insert at the cursor.
9252 * used for Ctrl-V pasting */
9253 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
9254 start = gtk_text_iter_get_offset(&start_iter);
9255 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
9257 /* if insert_place is specified, paste here.
9258 * used for mid-click-pasting */
9259 start = gtk_text_iter_get_offset(insert_place);
9260 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
9264 /* paste unwrapped: mark the paste so it's not wrapped later */
9265 end = start + strlen(contents);
9266 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
9267 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
9268 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
9269 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
9270 /* rewrap paragraph now (after a mid-click-paste) */
9271 mark_start = gtk_text_buffer_get_insert(buffer);
9272 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
9273 gtk_text_iter_backward_char(&start_iter);
9274 compose_beautify_paragraph(compose, &start_iter, TRUE);
9276 } else if (GTK_IS_EDITABLE(entry))
9277 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
9279 compose->modified = TRUE;
9282 static void entry_allsel(GtkWidget *entry)
9284 if (GTK_IS_EDITABLE(entry))
9285 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
9286 else if (GTK_IS_TEXT_VIEW(entry)) {
9287 GtkTextIter startiter, enditer;
9288 GtkTextBuffer *textbuf;
9290 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
9291 gtk_text_buffer_get_start_iter(textbuf, &startiter);
9292 gtk_text_buffer_get_end_iter(textbuf, &enditer);
9294 gtk_text_buffer_move_mark_by_name(textbuf,
9295 "selection_bound", &startiter);
9296 gtk_text_buffer_move_mark_by_name(textbuf,
9297 "insert", &enditer);
9301 static void compose_cut_cb(Compose *compose)
9303 if (compose->focused_editable
9304 #ifndef GENERIC_UMPC
9305 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9308 entry_cut_clipboard(compose->focused_editable);
9311 static void compose_copy_cb(Compose *compose)
9313 if (compose->focused_editable
9314 #ifndef GENERIC_UMPC
9315 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9318 entry_copy_clipboard(compose->focused_editable);
9321 static void compose_paste_cb(Compose *compose)
9324 GtkTextBuffer *buffer;
9326 if (compose->focused_editable &&
9327 GTK_WIDGET_HAS_FOCUS(compose->focused_editable))
9328 entry_paste_clipboard(compose, compose->focused_editable,
9329 prefs_common.linewrap_pastes,
9330 GDK_SELECTION_CLIPBOARD, NULL);
9334 static void compose_paste_as_quote_cb(Compose *compose)
9336 gint wrap_quote = prefs_common.linewrap_quote;
9337 if (compose->focused_editable
9338 #ifndef GENERIC_UMPC
9339 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9342 /* let text_insert() (called directly or at a later time
9343 * after the gtk_editable_paste_clipboard) know that
9344 * text is to be inserted as a quotation. implemented
9345 * by using a simple refcount... */
9346 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
9347 G_OBJECT(compose->focused_editable),
9348 "paste_as_quotation"));
9349 g_object_set_data(G_OBJECT(compose->focused_editable),
9350 "paste_as_quotation",
9351 GINT_TO_POINTER(paste_as_quotation + 1));
9352 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
9353 entry_paste_clipboard(compose, compose->focused_editable,
9354 prefs_common.linewrap_pastes,
9355 GDK_SELECTION_CLIPBOARD, NULL);
9356 prefs_common.linewrap_quote = wrap_quote;
9360 static void compose_paste_no_wrap_cb(Compose *compose)
9363 GtkTextBuffer *buffer;
9365 if (compose->focused_editable
9366 #ifndef GENERIC_UMPC
9367 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9370 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
9371 GDK_SELECTION_CLIPBOARD, NULL);
9375 static void compose_paste_wrap_cb(Compose *compose)
9378 GtkTextBuffer *buffer;
9380 if (compose->focused_editable
9381 #ifndef GENERIC_UMPC
9382 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9385 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
9386 GDK_SELECTION_CLIPBOARD, NULL);
9390 static void compose_allsel_cb(Compose *compose)
9392 if (compose->focused_editable
9393 #ifndef GENERIC_UMPC
9394 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9397 entry_allsel(compose->focused_editable);
9400 static void textview_move_beginning_of_line (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 gtk_text_iter_set_line_offset(&ins, 0);
9412 gtk_text_buffer_place_cursor(buffer, &ins);
9415 static void textview_move_forward_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_forward_cursor_position(&ins))
9427 gtk_text_buffer_place_cursor(buffer, &ins);
9430 static void textview_move_backward_character (GtkTextView *text)
9432 GtkTextBuffer *buffer;
9436 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9438 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9439 mark = gtk_text_buffer_get_insert(buffer);
9440 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9441 if (gtk_text_iter_backward_cursor_position(&ins))
9442 gtk_text_buffer_place_cursor(buffer, &ins);
9445 static void textview_move_forward_word (GtkTextView *text)
9447 GtkTextBuffer *buffer;
9452 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9454 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9455 mark = gtk_text_buffer_get_insert(buffer);
9456 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9457 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
9458 if (gtk_text_iter_forward_word_ends(&ins, count)) {
9459 gtk_text_iter_backward_word_start(&ins);
9460 gtk_text_buffer_place_cursor(buffer, &ins);
9464 static void textview_move_backward_word (GtkTextView *text)
9466 GtkTextBuffer *buffer;
9471 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9473 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9474 mark = gtk_text_buffer_get_insert(buffer);
9475 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9476 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
9477 if (gtk_text_iter_backward_word_starts(&ins, 1))
9478 gtk_text_buffer_place_cursor(buffer, &ins);
9481 static void textview_move_end_of_line (GtkTextView *text)
9483 GtkTextBuffer *buffer;
9487 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9489 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9490 mark = gtk_text_buffer_get_insert(buffer);
9491 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9492 if (gtk_text_iter_forward_to_line_end(&ins))
9493 gtk_text_buffer_place_cursor(buffer, &ins);
9496 static void textview_move_next_line (GtkTextView *text)
9498 GtkTextBuffer *buffer;
9503 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9505 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9506 mark = gtk_text_buffer_get_insert(buffer);
9507 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9508 offset = gtk_text_iter_get_line_offset(&ins);
9509 if (gtk_text_iter_forward_line(&ins)) {
9510 gtk_text_iter_set_line_offset(&ins, offset);
9511 gtk_text_buffer_place_cursor(buffer, &ins);
9515 static void textview_move_previous_line (GtkTextView *text)
9517 GtkTextBuffer *buffer;
9522 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9524 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9525 mark = gtk_text_buffer_get_insert(buffer);
9526 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9527 offset = gtk_text_iter_get_line_offset(&ins);
9528 if (gtk_text_iter_backward_line(&ins)) {
9529 gtk_text_iter_set_line_offset(&ins, offset);
9530 gtk_text_buffer_place_cursor(buffer, &ins);
9534 static void textview_delete_forward_character (GtkTextView *text)
9536 GtkTextBuffer *buffer;
9538 GtkTextIter ins, end_iter;
9540 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9542 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9543 mark = gtk_text_buffer_get_insert(buffer);
9544 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9546 if (gtk_text_iter_forward_char(&end_iter)) {
9547 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9551 static void textview_delete_backward_character (GtkTextView *text)
9553 GtkTextBuffer *buffer;
9555 GtkTextIter ins, end_iter;
9557 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9559 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9560 mark = gtk_text_buffer_get_insert(buffer);
9561 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9563 if (gtk_text_iter_backward_char(&end_iter)) {
9564 gtk_text_buffer_delete(buffer, &end_iter, &ins);
9568 static void textview_delete_forward_word (GtkTextView *text)
9570 GtkTextBuffer *buffer;
9572 GtkTextIter ins, end_iter;
9574 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9576 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9577 mark = gtk_text_buffer_get_insert(buffer);
9578 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9580 if (gtk_text_iter_forward_word_end(&end_iter)) {
9581 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9585 static void textview_delete_backward_word (GtkTextView *text)
9587 GtkTextBuffer *buffer;
9589 GtkTextIter ins, end_iter;
9591 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9593 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9594 mark = gtk_text_buffer_get_insert(buffer);
9595 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9597 if (gtk_text_iter_backward_word_start(&end_iter)) {
9598 gtk_text_buffer_delete(buffer, &end_iter, &ins);
9602 static void textview_delete_line (GtkTextView *text)
9604 GtkTextBuffer *buffer;
9606 GtkTextIter ins, start_iter, end_iter;
9608 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9610 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9611 mark = gtk_text_buffer_get_insert(buffer);
9612 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9615 gtk_text_iter_set_line_offset(&start_iter, 0);
9618 if (gtk_text_iter_ends_line(&end_iter)){
9619 if (!gtk_text_iter_forward_char(&end_iter))
9620 gtk_text_iter_backward_char(&start_iter);
9623 gtk_text_iter_forward_to_line_end(&end_iter);
9624 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
9627 static void textview_delete_to_line_end (GtkTextView *text)
9629 GtkTextBuffer *buffer;
9631 GtkTextIter ins, end_iter;
9633 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9635 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9636 mark = gtk_text_buffer_get_insert(buffer);
9637 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9639 if (gtk_text_iter_ends_line(&end_iter))
9640 gtk_text_iter_forward_char(&end_iter);
9642 gtk_text_iter_forward_to_line_end(&end_iter);
9643 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9646 static void compose_advanced_action_cb(Compose *compose,
9647 ComposeCallAdvancedAction action)
9649 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9651 void (*do_action) (GtkTextView *text);
9652 } action_table[] = {
9653 {textview_move_beginning_of_line},
9654 {textview_move_forward_character},
9655 {textview_move_backward_character},
9656 {textview_move_forward_word},
9657 {textview_move_backward_word},
9658 {textview_move_end_of_line},
9659 {textview_move_next_line},
9660 {textview_move_previous_line},
9661 {textview_delete_forward_character},
9662 {textview_delete_backward_character},
9663 {textview_delete_forward_word},
9664 {textview_delete_backward_word},
9665 {textview_delete_line},
9666 {textview_delete_to_line_end}
9669 if (!GTK_WIDGET_HAS_FOCUS(text)) return;
9671 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
9672 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
9673 if (action_table[action].do_action)
9674 action_table[action].do_action(text);
9676 g_warning("Not implemented yet.");
9680 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
9684 if (GTK_IS_EDITABLE(widget)) {
9685 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
9686 gtk_editable_set_position(GTK_EDITABLE(widget),
9689 if (widget->parent && widget->parent->parent
9690 && widget->parent->parent->parent) {
9691 if (GTK_IS_SCROLLED_WINDOW(widget->parent->parent->parent)) {
9692 gint y = widget->allocation.y;
9693 gint height = widget->allocation.height;
9694 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
9695 (GTK_SCROLLED_WINDOW(widget->parent->parent->parent));
9697 if (y < (int)shown->value) {
9698 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown), y - 1);
9700 if (y + height > (int)shown->value + (int)shown->page_size) {
9701 if (y - height - 1 < (int)shown->upper - (int)shown->page_size) {
9702 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
9703 y + height - (int)shown->page_size - 1);
9705 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
9706 (int)shown->upper - (int)shown->page_size - 1);
9713 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
9714 compose->focused_editable = widget;
9717 if (GTK_IS_TEXT_VIEW(widget)
9718 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
9719 gtk_widget_ref(compose->notebook);
9720 gtk_widget_ref(compose->edit_vbox);
9721 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
9722 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
9723 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
9724 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
9725 gtk_widget_unref(compose->notebook);
9726 gtk_widget_unref(compose->edit_vbox);
9727 g_signal_handlers_block_by_func(G_OBJECT(widget),
9728 G_CALLBACK(compose_grab_focus_cb),
9730 gtk_widget_grab_focus(widget);
9731 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
9732 G_CALLBACK(compose_grab_focus_cb),
9734 } else if (!GTK_IS_TEXT_VIEW(widget)
9735 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
9736 gtk_widget_ref(compose->notebook);
9737 gtk_widget_ref(compose->edit_vbox);
9738 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
9739 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
9740 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
9741 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
9742 gtk_widget_unref(compose->notebook);
9743 gtk_widget_unref(compose->edit_vbox);
9744 g_signal_handlers_block_by_func(G_OBJECT(widget),
9745 G_CALLBACK(compose_grab_focus_cb),
9747 gtk_widget_grab_focus(widget);
9748 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
9749 G_CALLBACK(compose_grab_focus_cb),
9755 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
9757 compose->modified = TRUE;
9758 #ifndef GENERIC_UMPC
9759 compose_set_title(compose);
9763 static void compose_wrap_cb(gpointer data, guint action, GtkWidget *widget)
9765 Compose *compose = (Compose *)data;
9768 compose_wrap_all_full(compose, TRUE);
9770 compose_beautify_paragraph(compose, NULL, TRUE);
9773 static void compose_find_cb(gpointer data, guint action, GtkWidget *widget)
9775 Compose *compose = (Compose *)data;
9777 message_search_compose(compose);
9780 static void compose_toggle_autowrap_cb(gpointer data, guint action,
9783 Compose *compose = (Compose *)data;
9784 compose->autowrap = GTK_CHECK_MENU_ITEM(widget)->active;
9785 if (compose->autowrap)
9786 compose_wrap_all_full(compose, TRUE);
9787 compose->autowrap = GTK_CHECK_MENU_ITEM(widget)->active;
9790 static void compose_toggle_sign_cb(gpointer data, guint action,
9793 Compose *compose = (Compose *)data;
9795 if (GTK_CHECK_MENU_ITEM(widget)->active)
9796 compose->use_signing = TRUE;
9798 compose->use_signing = FALSE;
9801 static void compose_toggle_encrypt_cb(gpointer data, guint action,
9804 Compose *compose = (Compose *)data;
9806 if (GTK_CHECK_MENU_ITEM(widget)->active)
9807 compose->use_encryption = TRUE;
9809 compose->use_encryption = FALSE;
9812 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
9814 g_free(compose->privacy_system);
9816 compose->privacy_system = g_strdup(account->default_privacy_system);
9817 compose_update_privacy_system_menu_item(compose, warn);
9820 static void compose_toggle_ruler_cb(gpointer data, guint action,
9823 Compose *compose = (Compose *)data;
9825 if (GTK_CHECK_MENU_ITEM(widget)->active) {
9826 gtk_widget_show(compose->ruler_hbox);
9827 prefs_common.show_ruler = TRUE;
9829 gtk_widget_hide(compose->ruler_hbox);
9830 gtk_widget_queue_resize(compose->edit_vbox);
9831 prefs_common.show_ruler = FALSE;
9835 static void compose_attach_drag_received_cb (GtkWidget *widget,
9836 GdkDragContext *context,
9839 GtkSelectionData *data,
9844 Compose *compose = (Compose *)user_data;
9847 if (gdk_atom_name(data->type) &&
9848 !strcmp(gdk_atom_name(data->type), "text/uri-list")
9849 && gtk_drag_get_source_widget(context) !=
9850 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
9851 list = uri_list_extract_filenames((const gchar *)data->data);
9852 for (tmp = list; tmp != NULL; tmp = tmp->next) {
9853 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
9854 compose_attach_append
9855 (compose, (const gchar *)tmp->data,
9856 utf8_filename, NULL);
9857 g_free(utf8_filename);
9859 if (list) compose_changed_cb(NULL, compose);
9860 list_free_strings(list);
9862 } else if (gtk_drag_get_source_widget(context)
9863 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
9864 /* comes from our summaryview */
9865 SummaryView * summaryview = NULL;
9866 GSList * list = NULL, *cur = NULL;
9868 if (mainwindow_get_mainwindow())
9869 summaryview = mainwindow_get_mainwindow()->summaryview;
9872 list = summary_get_selected_msg_list(summaryview);
9874 for (cur = list; cur; cur = cur->next) {
9875 MsgInfo *msginfo = (MsgInfo *)cur->data;
9878 file = procmsg_get_message_file_full(msginfo,
9881 compose_attach_append(compose, (const gchar *)file,
9882 (const gchar *)file, "message/rfc822");
9890 static gboolean compose_drag_drop(GtkWidget *widget,
9891 GdkDragContext *drag_context,
9893 guint time, gpointer user_data)
9895 /* not handling this signal makes compose_insert_drag_received_cb
9900 static void compose_insert_drag_received_cb (GtkWidget *widget,
9901 GdkDragContext *drag_context,
9904 GtkSelectionData *data,
9909 Compose *compose = (Compose *)user_data;
9912 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
9914 if (gdk_atom_name(data->type) && !strcmp(gdk_atom_name(data->type), "text/uri-list")) {
9915 AlertValue val = G_ALERTDEFAULT;
9917 list = uri_list_extract_filenames((const gchar *)data->data);
9919 if (list == NULL && strstr((gchar *)(data->data), "://")) {
9920 /* Assume a list of no files, and data has ://, is a remote link */
9921 gchar *tmpdata = g_strstrip(g_strdup((const gchar *)data->data));
9922 gchar *tmpfile = get_tmp_file();
9923 str_write_to_file(tmpdata, tmpfile);
9925 compose_insert_file(compose, tmpfile);
9926 claws_unlink(tmpfile);
9928 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9929 compose_beautify_paragraph(compose, NULL, TRUE);
9932 switch (prefs_common.compose_dnd_mode) {
9933 case COMPOSE_DND_ASK:
9934 val = alertpanel_full(_("Insert or attach?"),
9935 _("Do you want to insert the contents of the file(s) "
9936 "into the message body, or attach it to the email?"),
9937 GTK_STOCK_CANCEL, _("+_Insert"), _("_Attach"),
9938 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
9940 case COMPOSE_DND_INSERT:
9941 val = G_ALERTALTERNATE;
9943 case COMPOSE_DND_ATTACH:
9947 /* unexpected case */
9948 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
9951 if (val & G_ALERTDISABLE) {
9952 val &= ~G_ALERTDISABLE;
9953 /* remember what action to perform by default, only if we don't click Cancel */
9954 if (val == G_ALERTALTERNATE)
9955 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
9956 else if (val == G_ALERTOTHER)
9957 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
9960 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
9961 gtk_drag_finish(drag_context, FALSE, FALSE, time);
9962 list_free_strings(list);
9965 } else if (val == G_ALERTOTHER) {
9966 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
9967 list_free_strings(list);
9972 for (tmp = list; tmp != NULL; tmp = tmp->next) {
9973 compose_insert_file(compose, (const gchar *)tmp->data);
9975 list_free_strings(list);
9977 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9980 #if GTK_CHECK_VERSION(2, 8, 0)
9981 /* do nothing, handled by GTK */
9983 gchar *tmpfile = get_tmp_file();
9984 str_write_to_file((const gchar *)data->data, tmpfile);
9985 compose_insert_file(compose, tmpfile);
9986 claws_unlink(tmpfile);
9988 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9992 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9995 static void compose_header_drag_received_cb (GtkWidget *widget,
9996 GdkDragContext *drag_context,
9999 GtkSelectionData *data,
10002 gpointer user_data)
10004 GtkEditable *entry = (GtkEditable *)user_data;
10005 gchar *email = (gchar *)data->data;
10007 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
10010 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
10011 gchar *decoded=g_new(gchar, strlen(email));
10014 email += strlen("mailto:");
10015 decode_uri(decoded, email); /* will fit */
10016 gtk_editable_delete_text(entry, 0, -1);
10017 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
10018 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10022 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10025 static void compose_toggle_return_receipt_cb(gpointer data, guint action,
10028 Compose *compose = (Compose *)data;
10030 if (GTK_CHECK_MENU_ITEM(widget)->active)
10031 compose->return_receipt = TRUE;
10033 compose->return_receipt = FALSE;
10036 static void compose_toggle_remove_refs_cb(gpointer data, guint action,
10039 Compose *compose = (Compose *)data;
10041 if (GTK_CHECK_MENU_ITEM(widget)->active)
10042 compose->remove_references = TRUE;
10044 compose->remove_references = FALSE;
10047 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
10048 GdkEventKey *event,
10049 ComposeHeaderEntry *headerentry)
10051 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
10052 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
10053 !(event->state & GDK_MODIFIER_MASK) &&
10054 (event->keyval == GDK_BackSpace) &&
10055 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
10056 gtk_container_remove
10057 (GTK_CONTAINER(headerentry->compose->header_table),
10058 headerentry->combo);
10059 gtk_container_remove
10060 (GTK_CONTAINER(headerentry->compose->header_table),
10061 headerentry->entry);
10062 headerentry->compose->header_list =
10063 g_slist_remove(headerentry->compose->header_list,
10065 g_free(headerentry);
10066 } else if (event->keyval == GDK_Tab) {
10067 if (headerentry->compose->header_last == headerentry) {
10068 /* Override default next focus, and give it to subject_entry
10069 * instead of notebook tabs
10071 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
10072 gtk_widget_grab_focus(headerentry->compose->subject_entry);
10079 static gboolean compose_headerentry_changed_cb(GtkWidget *entry,
10080 ComposeHeaderEntry *headerentry)
10082 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
10083 compose_create_header_entry(headerentry->compose);
10084 g_signal_handlers_disconnect_matched
10085 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
10086 0, 0, NULL, NULL, headerentry);
10088 /* Automatically scroll down */
10089 compose_show_first_last_header(headerentry->compose, FALSE);
10095 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
10097 GtkAdjustment *vadj;
10099 g_return_if_fail(compose);
10100 g_return_if_fail(GTK_IS_WIDGET(compose->header_table));
10101 g_return_if_fail(GTK_IS_VIEWPORT(compose->header_table->parent));
10103 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(compose->header_table->parent));
10104 gtk_adjustment_set_value(vadj, (show_first ? vadj->lower : vadj->upper));
10105 gtk_adjustment_changed(vadj);
10108 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
10109 const gchar *text, gint len, Compose *compose)
10111 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
10112 (G_OBJECT(compose->text), "paste_as_quotation"));
10115 g_return_if_fail(text != NULL);
10117 g_signal_handlers_block_by_func(G_OBJECT(buffer),
10118 G_CALLBACK(text_inserted),
10120 if (paste_as_quotation) {
10122 const gchar *qmark;
10124 GtkTextIter start_iter;
10127 len = strlen(text);
10129 new_text = g_strndup(text, len);
10131 qmark = compose_quote_char_from_context(compose);
10133 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
10134 gtk_text_buffer_place_cursor(buffer, iter);
10136 pos = gtk_text_iter_get_offset(iter);
10138 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
10139 _("Quote format error at line %d."));
10140 quote_fmt_reset_vartable();
10142 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
10143 GINT_TO_POINTER(paste_as_quotation - 1));
10145 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
10146 gtk_text_buffer_place_cursor(buffer, iter);
10147 gtk_text_buffer_delete_mark(buffer, mark);
10149 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
10150 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
10151 compose_beautify_paragraph(compose, &start_iter, FALSE);
10152 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
10153 gtk_text_buffer_delete_mark(buffer, mark);
10155 if (strcmp(text, "\n") || compose->automatic_break
10156 || gtk_text_iter_starts_line(iter))
10157 gtk_text_buffer_insert(buffer, iter, text, len);
10159 /* check if the preceding is just whitespace or quote */
10160 GtkTextIter start_line;
10161 gchar *tmp = NULL, *quote = NULL;
10162 gint quote_len = 0, is_normal = 0;
10163 start_line = *iter;
10164 gtk_text_iter_set_line_offset(&start_line, 0);
10165 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
10167 if (*tmp == '\0') {
10170 quote = compose_get_quote_str(buffer, &start_line, "e_len);
10178 gtk_text_buffer_insert(buffer, iter, text, len);
10180 gtk_text_buffer_insert_with_tags_by_name(buffer,
10181 iter, text, len, "no_join", NULL);
10186 if (!paste_as_quotation) {
10187 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
10188 compose_beautify_paragraph(compose, iter, FALSE);
10189 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
10190 gtk_text_buffer_delete_mark(buffer, mark);
10193 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
10194 G_CALLBACK(text_inserted),
10196 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
10198 if (prefs_common.autosave &&
10199 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
10200 compose->draft_timeout_tag != -2 /* disabled while loading */)
10201 compose->draft_timeout_tag = g_timeout_add
10202 (500, (GtkFunction) compose_defer_auto_save_draft, compose);
10204 static gint compose_defer_auto_save_draft(Compose *compose)
10206 compose->draft_timeout_tag = -1;
10207 compose_draft_cb((gpointer)compose, COMPOSE_AUTO_SAVE, NULL);
10212 static void compose_check_all(Compose *compose)
10214 if (compose->gtkaspell)
10215 gtkaspell_check_all(compose->gtkaspell);
10218 static void compose_highlight_all(Compose *compose)
10220 if (compose->gtkaspell)
10221 gtkaspell_highlight_all(compose->gtkaspell);
10224 static void compose_check_backwards(Compose *compose)
10226 if (compose->gtkaspell)
10227 gtkaspell_check_backwards(compose->gtkaspell);
10229 GtkItemFactory *ifactory;
10230 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
10231 menu_set_sensitive(ifactory, "/Edit/Check backwards misspelled word", FALSE);
10232 menu_set_sensitive(ifactory, "/Edit/Forward to next misspelled word", FALSE);
10236 static void compose_check_forwards_go(Compose *compose)
10238 if (compose->gtkaspell)
10239 gtkaspell_check_forwards_go(compose->gtkaspell);
10241 GtkItemFactory *ifactory;
10242 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
10243 menu_set_sensitive(ifactory, "/Edit/Check backwards misspelled word", FALSE);
10244 menu_set_sensitive(ifactory, "/Edit/Forward to next misspelled word", FALSE);
10250 *\brief Guess originating forward account from MsgInfo and several
10251 * "common preference" settings. Return NULL if no guess.
10253 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
10255 PrefsAccount *account = NULL;
10257 g_return_val_if_fail(msginfo, NULL);
10258 g_return_val_if_fail(msginfo->folder, NULL);
10259 g_return_val_if_fail(msginfo->folder->prefs, NULL);
10261 if (msginfo->folder->prefs->enable_default_account)
10262 account = account_find_from_id(msginfo->folder->prefs->default_account);
10265 account = msginfo->folder->folder->account;
10267 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
10269 Xstrdup_a(to, msginfo->to, return NULL);
10270 extract_address(to);
10271 account = account_find_from_address(to, FALSE);
10274 if (!account && prefs_common.forward_account_autosel) {
10275 gchar cc[BUFFSIZE];
10276 if (!procheader_get_header_from_msginfo
10277 (msginfo, cc,sizeof cc , "Cc:")) {
10278 gchar *buf = cc + strlen("Cc:");
10279 extract_address(buf);
10280 account = account_find_from_address(buf, FALSE);
10284 if (!account && prefs_common.forward_account_autosel) {
10285 gchar deliveredto[BUFFSIZE];
10286 if (!procheader_get_header_from_msginfo
10287 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
10288 gchar *buf = deliveredto + strlen("Delivered-To:");
10289 extract_address(buf);
10290 account = account_find_from_address(buf, FALSE);
10297 gboolean compose_close(Compose *compose)
10301 if (!g_mutex_trylock(compose->mutex)) {
10302 /* we have to wait for the (possibly deferred by auto-save)
10303 * drafting to be done, before destroying the compose under
10305 debug_print("waiting for drafting to finish...\n");
10306 compose_allow_user_actions(compose, FALSE);
10307 g_timeout_add (500, (GSourceFunc) compose_close, compose);
10310 g_return_val_if_fail(compose, FALSE);
10311 gtkut_widget_get_uposition(compose->window, &x, &y);
10312 prefs_common.compose_x = x;
10313 prefs_common.compose_y = y;
10314 g_mutex_unlock(compose->mutex);
10315 compose_destroy(compose);
10320 * Add entry field for each address in list.
10321 * \param compose E-Mail composition object.
10322 * \param listAddress List of (formatted) E-Mail addresses.
10324 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
10327 node = listAddress;
10329 addr = ( gchar * ) node->data;
10330 compose_entry_append( compose, addr, COMPOSE_TO );
10331 node = g_list_next( node );
10335 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
10336 guint action, gboolean opening_multiple)
10338 gchar *body = NULL;
10339 GSList *new_msglist = NULL;
10340 MsgInfo *tmp_msginfo = NULL;
10341 gboolean originally_enc = FALSE;
10342 Compose *compose = NULL;
10344 g_return_if_fail(msgview != NULL);
10346 g_return_if_fail(msginfo_list != NULL);
10348 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
10349 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
10350 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
10352 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
10353 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
10354 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
10355 orig_msginfo, mimeinfo);
10356 if (tmp_msginfo != NULL) {
10357 new_msglist = g_slist_append(NULL, tmp_msginfo);
10359 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
10360 tmp_msginfo->folder = orig_msginfo->folder;
10361 tmp_msginfo->msgnum = orig_msginfo->msgnum;
10362 if (orig_msginfo->tags)
10363 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
10368 if (!opening_multiple)
10369 body = messageview_get_selection(msgview);
10372 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
10373 procmsg_msginfo_free(tmp_msginfo);
10374 g_slist_free(new_msglist);
10376 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
10378 if (compose && originally_enc) {
10379 compose_force_encryption(compose, compose->account, FALSE);
10385 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
10388 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
10389 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
10390 GSList *cur = msginfo_list;
10391 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
10392 "messages. Opening the windows "
10393 "could take some time. Do you "
10394 "want to continue?"),
10395 g_slist_length(msginfo_list));
10396 if (g_slist_length(msginfo_list) > 9
10397 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
10398 != G_ALERTALTERNATE) {
10403 /* We'll open multiple compose windows */
10404 /* let the WM place the next windows */
10405 compose_force_window_origin = FALSE;
10406 for (; cur; cur = cur->next) {
10408 tmplist.data = cur->data;
10409 tmplist.next = NULL;
10410 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
10412 compose_force_window_origin = TRUE;
10414 /* forwarding multiple mails as attachments is done via a
10415 * single compose window */
10416 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
10420 void compose_set_position(Compose *compose, gint pos)
10422 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10424 gtkut_text_view_set_position(text, pos);
10427 gboolean compose_search_string(Compose *compose,
10428 const gchar *str, gboolean case_sens)
10430 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10432 return gtkut_text_view_search_string(text, str, case_sens);
10435 gboolean compose_search_string_backward(Compose *compose,
10436 const gchar *str, gboolean case_sens)
10438 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10440 return gtkut_text_view_search_string_backward(text, str, case_sens);
10443 /* allocate a msginfo structure and populate its data from a compose data structure */
10444 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
10446 MsgInfo *newmsginfo;
10448 gchar buf[BUFFSIZE];
10450 g_return_val_if_fail( compose != NULL, NULL );
10452 newmsginfo = procmsg_msginfo_new();
10455 get_rfc822_date(buf, sizeof(buf));
10456 newmsginfo->date = g_strdup(buf);
10459 if (compose->from_name) {
10460 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
10461 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
10465 if (compose->subject_entry)
10466 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
10468 /* to, cc, reply-to, newsgroups */
10469 for (list = compose->header_list; list; list = list->next) {
10470 gchar *header = gtk_editable_get_chars(
10472 GTK_BIN(((ComposeHeaderEntry *)list->data)->combo)->child), 0, -1);
10473 gchar *entry = gtk_editable_get_chars(
10474 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
10476 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
10477 if ( newmsginfo->to == NULL ) {
10478 newmsginfo->to = g_strdup(entry);
10479 } else if (entry && *entry) {
10480 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
10481 g_free(newmsginfo->to);
10482 newmsginfo->to = tmp;
10485 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
10486 if ( newmsginfo->cc == NULL ) {
10487 newmsginfo->cc = g_strdup(entry);
10488 } else if (entry && *entry) {
10489 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
10490 g_free(newmsginfo->cc);
10491 newmsginfo->cc = tmp;
10494 if ( strcasecmp(header,
10495 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
10496 if ( newmsginfo->newsgroups == NULL ) {
10497 newmsginfo->newsgroups = g_strdup(entry);
10498 } else if (entry && *entry) {
10499 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
10500 g_free(newmsginfo->newsgroups);
10501 newmsginfo->newsgroups = tmp;
10509 /* other data is unset */
10515 /* update compose's dictionaries from folder dict settings */
10516 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
10517 FolderItem *folder_item)
10519 g_return_if_fail(compose != NULL);
10521 if (compose->gtkaspell && folder_item && folder_item->prefs) {
10522 FolderItemPrefs *prefs = folder_item->prefs;
10524 if (prefs->enable_default_dictionary)
10525 gtkaspell_change_dict(compose->gtkaspell,
10526 prefs->default_dictionary, FALSE);
10527 if (folder_item->prefs->enable_default_alt_dictionary)
10528 gtkaspell_change_alt_dict(compose->gtkaspell,
10529 prefs->default_alt_dictionary);
10530 if (prefs->enable_default_dictionary
10531 || prefs->enable_default_alt_dictionary)
10532 compose_spell_menu_changed(compose);