2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2007 Hiroyuki Yamamoto and the Claws Mail team
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
26 #ifndef PANGO_ENABLE_ENGINE
27 # define PANGO_ENABLE_ENGINE
31 #include <glib/gi18n.h>
32 #include <gdk/gdkkeysyms.h>
33 #include <gtk/gtkmain.h>
34 #include <gtk/gtkmenu.h>
35 #include <gtk/gtkmenuitem.h>
36 #include <gtk/gtkitemfactory.h>
37 #include <gtk/gtkcheckmenuitem.h>
38 #include <gtk/gtkoptionmenu.h>
39 #include <gtk/gtkwidget.h>
40 #include <gtk/gtkvpaned.h>
41 #include <gtk/gtkentry.h>
42 #include <gtk/gtkeditable.h>
43 #include <gtk/gtkwindow.h>
44 #include <gtk/gtksignal.h>
45 #include <gtk/gtkvbox.h>
46 #include <gtk/gtkcontainer.h>
47 #include <gtk/gtkhandlebox.h>
48 #include <gtk/gtktoolbar.h>
49 #include <gtk/gtktable.h>
50 #include <gtk/gtkhbox.h>
51 #include <gtk/gtklabel.h>
52 #include <gtk/gtkscrolledwindow.h>
53 #include <gtk/gtktreeview.h>
54 #include <gtk/gtkliststore.h>
55 #include <gtk/gtktreeselection.h>
56 #include <gtk/gtktreemodel.h>
58 #include <gtk/gtkdnd.h>
59 #include <gtk/gtkclipboard.h>
60 #include <pango/pango-break.h>
65 #include <sys/types.h>
71 # include <sys/wait.h>
75 #ifndef G_OS_WIN32 /* fixme we should have a configure test. */
79 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
86 #include "mainwindow.h"
88 #include "addressbook.h"
89 #include "folderview.h"
92 #include "stock_pixmap.h"
93 #include "send_message.h"
96 #include "customheader.h"
97 #include "prefs_common.h"
98 #include "prefs_account.h"
102 #include "procheader.h"
103 #include "procmime.h"
104 #include "statusbar.h"
107 #include "quoted-printable.h"
108 #include "codeconv.h"
110 #include "gtkutils.h"
112 #include "alertpanel.h"
113 #include "manage_window.h"
114 #include "gtkshruler.h"
116 #include "addr_compl.h"
117 #include "quote_fmt.h"
119 #include "foldersel.h"
122 #include "message_search.h"
123 #include "combobox.h"
138 #define N_ATTACH_COLS (N_COL_COLUMNS)
142 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE,
143 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER,
144 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER,
145 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD,
146 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD,
147 COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE,
148 COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE,
149 COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE,
150 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER,
151 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER,
152 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD,
153 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD,
154 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE,
155 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE_N,
156 COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END
157 } ComposeCallAdvancedAction;
161 PRIORITY_HIGHEST = 1,
170 COMPOSE_INSERT_SUCCESS,
171 COMPOSE_INSERT_READ_ERROR,
172 COMPOSE_INSERT_INVALID_CHARACTER,
173 COMPOSE_INSERT_NO_FILE
174 } ComposeInsertResult;
178 COMPOSE_WRITE_FOR_SEND,
179 COMPOSE_WRITE_FOR_STORE
184 COMPOSE_QUOTE_FORCED,
189 #define B64_LINE_SIZE 57
190 #define B64_BUFFSIZE 77
192 #define MAX_REFERENCES_LEN 999
194 static GList *compose_list = NULL;
196 static Compose *compose_generic_new (PrefsAccount *account,
199 GPtrArray *attach_files,
200 GList *listAddress );
202 static Compose *compose_create (PrefsAccount *account,
207 static void compose_entry_mark_default_to (Compose *compose,
208 const gchar *address);
209 static Compose *compose_followup_and_reply_to (MsgInfo *msginfo,
210 ComposeQuoteMode quote_mode,
214 static Compose *compose_forward_multiple (PrefsAccount *account,
215 GSList *msginfo_list);
216 static Compose *compose_reply (MsgInfo *msginfo,
217 ComposeQuoteMode quote_mode,
222 static Compose *compose_reply_mode (ComposeMode mode,
223 GSList *msginfo_list,
225 static void compose_template_apply_fields(Compose *compose, Template *tmpl);
226 static void compose_update_privacy_systems_menu(Compose *compose);
228 static GtkWidget *compose_account_option_menu_create
230 static void compose_set_out_encoding (Compose *compose);
231 static void compose_set_template_menu (Compose *compose);
232 static void compose_template_apply (Compose *compose,
235 static void compose_destroy (Compose *compose);
237 static void compose_entries_set (Compose *compose,
239 ComposeEntryType to_type);
240 static gint compose_parse_header (Compose *compose,
242 static gchar *compose_parse_references (const gchar *ref,
245 static gchar *compose_quote_fmt (Compose *compose,
251 gboolean need_unescape,
252 const gchar *err_msg);
254 static void compose_reply_set_entry (Compose *compose,
260 followup_and_reply_to);
261 static void compose_reedit_set_entry (Compose *compose,
264 static void compose_insert_sig (Compose *compose,
266 static gchar *compose_get_signature_str (Compose *compose);
267 static ComposeInsertResult compose_insert_file (Compose *compose,
270 static gboolean compose_attach_append (Compose *compose,
273 const gchar *content_type);
274 static void compose_attach_parts (Compose *compose,
277 static gboolean compose_beautify_paragraph (Compose *compose,
278 GtkTextIter *par_iter,
280 static void compose_wrap_all (Compose *compose);
281 static void compose_wrap_all_full (Compose *compose,
284 static void compose_set_title (Compose *compose);
285 static void compose_select_account (Compose *compose,
286 PrefsAccount *account,
289 static PrefsAccount *compose_current_mail_account(void);
290 /* static gint compose_send (Compose *compose); */
291 static gboolean compose_check_for_valid_recipient
293 static gboolean compose_check_entries (Compose *compose,
294 gboolean check_everything);
295 static gint compose_write_to_file (Compose *compose,
298 gboolean attach_parts);
299 static gint compose_write_body_to_file (Compose *compose,
301 static gint compose_remove_reedit_target (Compose *compose,
303 static void compose_remove_draft (Compose *compose);
304 static gint compose_queue_sub (Compose *compose,
308 gboolean check_subject,
309 gboolean remove_reedit_target);
310 static void compose_add_attachments (Compose *compose,
312 static gchar *compose_get_header (Compose *compose);
314 static void compose_convert_header (Compose *compose,
319 gboolean addr_field);
321 static void compose_attach_info_free (AttachInfo *ainfo);
322 static void compose_attach_remove_selected (Compose *compose);
324 static void compose_attach_property (Compose *compose);
325 static void compose_attach_property_create (gboolean *cancelled);
326 static void attach_property_ok (GtkWidget *widget,
327 gboolean *cancelled);
328 static void attach_property_cancel (GtkWidget *widget,
329 gboolean *cancelled);
330 static gint attach_property_delete_event (GtkWidget *widget,
332 gboolean *cancelled);
333 static gboolean attach_property_key_pressed (GtkWidget *widget,
335 gboolean *cancelled);
337 static void compose_exec_ext_editor (Compose *compose);
339 static gint compose_exec_ext_editor_real (const gchar *file);
340 static gboolean compose_ext_editor_kill (Compose *compose);
341 static gboolean compose_input_cb (GIOChannel *source,
342 GIOCondition condition,
344 static void compose_set_ext_editor_sensitive (Compose *compose,
346 #endif /* G_OS_UNIX */
348 static void compose_undo_state_changed (UndoMain *undostruct,
353 static void compose_create_header_entry (Compose *compose);
354 static void compose_add_header_entry (Compose *compose, const gchar *header, gchar *text);
355 static void compose_remove_header_entries(Compose *compose);
357 static void compose_update_priority_menu_item(Compose * compose);
359 static void compose_spell_menu_changed (void *data);
361 static void compose_add_field_list ( Compose *compose,
362 GList *listAddress );
364 /* callback functions */
366 static gboolean compose_edit_size_alloc (GtkEditable *widget,
367 GtkAllocation *allocation,
368 GtkSHRuler *shruler);
369 static void account_activated (GtkComboBox *optmenu,
371 static void attach_selected (GtkTreeView *tree_view,
372 GtkTreePath *tree_path,
373 GtkTreeViewColumn *column,
375 static gboolean attach_button_pressed (GtkWidget *widget,
376 GdkEventButton *event,
378 static gboolean attach_key_pressed (GtkWidget *widget,
381 static void compose_send_cb (gpointer data,
384 static void compose_send_later_cb (gpointer data,
388 static void compose_draft_cb (gpointer data,
392 static void compose_attach_cb (gpointer data,
395 static void compose_insert_file_cb (gpointer data,
398 static void compose_insert_sig_cb (gpointer data,
402 static void compose_close_cb (gpointer data,
406 static void compose_set_encoding_cb (gpointer data,
410 static void compose_address_cb (gpointer data,
413 static void compose_template_activate_cb(GtkWidget *widget,
416 static void compose_ext_editor_cb (gpointer data,
420 static gint compose_delete_cb (GtkWidget *widget,
424 static void compose_undo_cb (Compose *compose);
425 static void compose_redo_cb (Compose *compose);
426 static void compose_cut_cb (Compose *compose);
427 static void compose_copy_cb (Compose *compose);
428 static void compose_paste_cb (Compose *compose);
429 static void compose_paste_as_quote_cb (Compose *compose);
430 static void compose_paste_no_wrap_cb (Compose *compose);
431 static void compose_paste_wrap_cb (Compose *compose);
432 static void compose_allsel_cb (Compose *compose);
434 static void compose_advanced_action_cb (Compose *compose,
435 ComposeCallAdvancedAction action);
437 static void compose_grab_focus_cb (GtkWidget *widget,
440 static void compose_changed_cb (GtkTextBuffer *textbuf,
443 static void compose_wrap_cb (gpointer data,
446 static void compose_find_cb (gpointer data,
449 static void compose_toggle_autowrap_cb (gpointer data,
453 static void compose_toggle_ruler_cb (gpointer data,
456 static void compose_toggle_sign_cb (gpointer data,
459 static void compose_toggle_encrypt_cb (gpointer data,
462 static void compose_set_privacy_system_cb(GtkWidget *widget,
464 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn);
465 static void activate_privacy_system (Compose *compose,
466 PrefsAccount *account,
468 static void compose_use_signing(Compose *compose, gboolean use_signing);
469 static void compose_use_encryption(Compose *compose, gboolean use_encryption);
470 static void compose_toggle_return_receipt_cb(gpointer data, guint action,
472 static void compose_toggle_remove_refs_cb(gpointer data, guint action,
474 static void compose_set_priority_cb (gpointer data,
477 static void compose_reply_change_mode (gpointer data,
481 static void compose_attach_drag_received_cb (GtkWidget *widget,
482 GdkDragContext *drag_context,
485 GtkSelectionData *data,
489 static void compose_insert_drag_received_cb (GtkWidget *widget,
490 GdkDragContext *drag_context,
493 GtkSelectionData *data,
497 static void compose_header_drag_received_cb (GtkWidget *widget,
498 GdkDragContext *drag_context,
501 GtkSelectionData *data,
506 static gboolean compose_drag_drop (GtkWidget *widget,
507 GdkDragContext *drag_context,
509 guint time, gpointer user_data);
511 static void text_inserted (GtkTextBuffer *buffer,
516 static Compose *compose_generic_reply(MsgInfo *msginfo,
517 ComposeQuoteMode quote_mode,
521 gboolean followup_and_reply_to,
524 static gboolean compose_headerentry_changed_cb (GtkWidget *entry,
525 ComposeHeaderEntry *headerentry);
526 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
528 ComposeHeaderEntry *headerentry);
530 static void compose_show_first_last_header (Compose *compose, gboolean show_first);
532 static void compose_allow_user_actions (Compose *compose, gboolean allow);
535 static void compose_check_all (Compose *compose);
536 static void compose_highlight_all (Compose *compose);
537 static void compose_check_backwards (Compose *compose);
538 static void compose_check_forwards_go (Compose *compose);
541 static gint compose_defer_auto_save_draft (Compose *compose);
542 static PrefsAccount *compose_guess_forward_account_from_msginfo (MsgInfo *msginfo);
544 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose);
547 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
548 FolderItem *folder_item);
550 static void compose_attach_update_label(Compose *compose);
552 static void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data);
554 static GtkItemFactoryEntry compose_popup_entries[] =
556 {N_("/_Add..."), NULL, compose_attach_cb, 0, NULL},
557 {N_("/_Remove"), NULL, compose_attach_remove_selected, 0, NULL},
558 {"/---", NULL, NULL, 0, "<Separator>"},
559 {N_("/_Properties..."), NULL, compose_attach_property, 0, NULL}
562 static GtkItemFactoryEntry compose_entries[] =
564 {N_("/_Message"), NULL, NULL, 0, "<Branch>"},
565 {N_("/_Message/S_end"), "<control>Return",
566 compose_send_cb, 0, NULL},
567 {N_("/_Message/Send _later"), "<shift><control>S",
568 compose_send_later_cb, 0, NULL},
569 {N_("/_Message/---"), NULL, NULL, 0, "<Separator>"},
570 {N_("/_Message/_Attach file"), "<control>M", compose_attach_cb, 0, NULL},
571 {N_("/_Message/_Insert file"), "<control>I", compose_insert_file_cb, 0, NULL},
572 {N_("/_Message/Insert si_gnature"), "<control>G", compose_insert_sig_cb, 0, NULL},
573 {N_("/_Message/---"), NULL, NULL, 0, "<Separator>"},
574 {N_("/_Message/_Save"),
575 "<control>S", compose_draft_cb, COMPOSE_KEEP_EDITING, NULL},
576 {N_("/_Message/---"), NULL, NULL, 0, "<Separator>"},
577 {N_("/_Message/_Close"), "<control>W", compose_close_cb, 0, NULL},
579 {N_("/_Edit"), NULL, NULL, 0, "<Branch>"},
580 {N_("/_Edit/_Undo"), "<control>Z", compose_undo_cb, 0, NULL},
581 {N_("/_Edit/_Redo"), "<control>Y", compose_redo_cb, 0, NULL},
582 {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
583 {N_("/_Edit/Cu_t"), "<control>X", compose_cut_cb, 0, NULL},
584 {N_("/_Edit/_Copy"), "<control>C", compose_copy_cb, 0, NULL},
585 {N_("/_Edit/_Paste"), "<control>V", compose_paste_cb, 0, NULL},
586 {N_("/_Edit/Special paste"), NULL, NULL, 0, "<Branch>"},
587 {N_("/_Edit/Special paste/as _quotation"),
588 NULL, compose_paste_as_quote_cb, 0, NULL},
589 {N_("/_Edit/Special paste/_wrapped"),
590 NULL, compose_paste_wrap_cb, 0, NULL},
591 {N_("/_Edit/Special paste/_unwrapped"),
592 NULL, compose_paste_no_wrap_cb, 0, NULL},
593 {N_("/_Edit/Select _all"), "<control>A", compose_allsel_cb, 0, NULL},
594 {N_("/_Edit/A_dvanced"), NULL, NULL, 0, "<Branch>"},
595 {N_("/_Edit/A_dvanced/Move a character backward"),
597 compose_advanced_action_cb,
598 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER,
600 {N_("/_Edit/A_dvanced/Move a character forward"),
602 compose_advanced_action_cb,
603 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER,
605 {N_("/_Edit/A_dvanced/Move a word backward"),
607 compose_advanced_action_cb,
608 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD,
610 {N_("/_Edit/A_dvanced/Move a word forward"),
612 compose_advanced_action_cb,
613 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD,
615 {N_("/_Edit/A_dvanced/Move to beginning of line"),
616 NULL, /* "<control>A" */
617 compose_advanced_action_cb,
618 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE,
620 {N_("/_Edit/A_dvanced/Move to end of line"),
622 compose_advanced_action_cb,
623 COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE,
625 {N_("/_Edit/A_dvanced/Move to previous line"),
627 compose_advanced_action_cb,
628 COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE,
630 {N_("/_Edit/A_dvanced/Move to next line"),
632 compose_advanced_action_cb,
633 COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE,
635 {N_("/_Edit/A_dvanced/Delete a character backward"),
637 compose_advanced_action_cb,
638 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER,
640 {N_("/_Edit/A_dvanced/Delete a character forward"),
642 compose_advanced_action_cb,
643 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER,
645 {N_("/_Edit/A_dvanced/Delete a word backward"),
646 NULL, /* "<control>W" */
647 compose_advanced_action_cb,
648 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD,
650 {N_("/_Edit/A_dvanced/Delete a word forward"),
651 NULL, /* "<alt>D", */
652 compose_advanced_action_cb,
653 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD,
655 {N_("/_Edit/A_dvanced/Delete line"),
657 compose_advanced_action_cb,
658 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE,
660 {N_("/_Edit/A_dvanced/Delete entire line"),
662 compose_advanced_action_cb,
663 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE_N,
665 {N_("/_Edit/A_dvanced/Delete to end of line"),
667 compose_advanced_action_cb,
668 COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END,
670 {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
672 "<control>F", compose_find_cb, 0, NULL},
673 {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
674 {N_("/_Edit/_Wrap current paragraph"),
675 "<control>L", compose_wrap_cb, 0, NULL},
676 {N_("/_Edit/Wrap all long _lines"),
677 "<control><alt>L", compose_wrap_cb, 1, NULL},
678 {N_("/_Edit/Aut_o wrapping"), "<shift><control>L", compose_toggle_autowrap_cb, 0, "<ToggleItem>"},
679 {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
680 {N_("/_Edit/Edit with e_xternal editor"),
681 "<shift><control>X", compose_ext_editor_cb, 0, NULL},
683 {N_("/_Spelling"), NULL, NULL, 0, "<Branch>"},
684 {N_("/_Spelling/_Check all or check selection"),
685 NULL, compose_check_all, 0, NULL},
686 {N_("/_Spelling/_Highlight all misspelled words"),
687 NULL, compose_highlight_all, 0, NULL},
688 {N_("/_Spelling/Check _backwards misspelled word"),
689 NULL, compose_check_backwards , 0, NULL},
690 {N_("/_Spelling/_Forward to next misspelled word"),
691 NULL, compose_check_forwards_go, 0, NULL},
692 {N_("/_Spelling/---"), NULL, NULL, 0, "<Separator>"},
693 {N_("/_Spelling/Options"),
694 NULL, NULL, 0, "<Branch>"},
696 {N_("/_Options"), NULL, NULL, 0, "<Branch>"},
697 {N_("/_Options/Reply _mode"), NULL, NULL, 0, "<Branch>"},
698 {N_("/_Options/Reply _mode/_Normal"), NULL, compose_reply_change_mode, COMPOSE_REPLY, "<RadioItem>"},
699 {N_("/_Options/Reply _mode/_All"), NULL, compose_reply_change_mode, COMPOSE_REPLY_TO_ALL, "/Options/Reply mode/Normal"},
700 {N_("/_Options/Reply _mode/_Sender"), NULL, compose_reply_change_mode, COMPOSE_REPLY_TO_SENDER, "/Options/Reply mode/Normal"},
701 {N_("/_Options/Reply _mode/_Mailing-list"), NULL, compose_reply_change_mode, COMPOSE_REPLY_TO_LIST, "/Options/Reply mode/Normal"},
702 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
703 {N_("/_Options/Privacy _System"), NULL, NULL, 0, "<Branch>"},
704 {N_("/_Options/Privacy _System/None"), NULL, NULL, 0, "<RadioItem>"},
705 {N_("/_Options/Si_gn"), NULL, compose_toggle_sign_cb , 0, "<ToggleItem>"},
706 {N_("/_Options/_Encrypt"), NULL, compose_toggle_encrypt_cb, 0, "<ToggleItem>"},
707 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
708 {N_("/_Options/_Priority"), NULL, NULL, 0, "<Branch>"},
709 {N_("/_Options/Priority/_Highest"), NULL, compose_set_priority_cb, PRIORITY_HIGHEST, "<RadioItem>"},
710 {N_("/_Options/Priority/Hi_gh"), NULL, compose_set_priority_cb, PRIORITY_HIGH, "/Options/Priority/Highest"},
711 {N_("/_Options/Priority/_Normal"), NULL, compose_set_priority_cb, PRIORITY_NORMAL, "/Options/Priority/Highest"},
712 {N_("/_Options/Priority/Lo_w"), NULL, compose_set_priority_cb, PRIORITY_LOW, "/Options/Priority/Highest"},
713 {N_("/_Options/Priority/_Lowest"), NULL, compose_set_priority_cb, PRIORITY_LOWEST, "/Options/Priority/Highest"},
714 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
715 {N_("/_Options/_Request Return Receipt"), NULL, compose_toggle_return_receipt_cb, 0, "<ToggleItem>"},
716 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
717 {N_("/_Options/Remo_ve references"), NULL, compose_toggle_remove_refs_cb, 0, "<ToggleItem>"},
718 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
720 #define ENC_ACTION(action) \
721 NULL, compose_set_encoding_cb, action, \
722 "/Options/Character encoding/Automatic"
724 {N_("/_Options/Character _encoding"), NULL, NULL, 0, "<Branch>"},
725 {N_("/_Options/Character _encoding/_Automatic"),
726 NULL, compose_set_encoding_cb, C_AUTO, "<RadioItem>"},
727 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
729 {N_("/_Options/Character _encoding/7bit ASCII (US-ASC_II)"),
730 ENC_ACTION(C_US_ASCII)},
731 {N_("/_Options/Character _encoding/Unicode (_UTF-8)"),
732 ENC_ACTION(C_UTF_8)},
733 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
735 {N_("/_Options/Character _encoding/Western European"), NULL, NULL, 0, "<Branch>"},
736 {N_("/_Options/Character _encoding/Western European/ISO-8859-_1"),
737 ENC_ACTION(C_ISO_8859_1)},
738 {N_("/_Options/Character _encoding/Western European/ISO-8859-15"),
739 ENC_ACTION(C_ISO_8859_15)},
740 {N_("/_Options/Character _encoding/Western European/Windows-1252"),
741 ENC_ACTION(C_WINDOWS_1252)},
743 {N_("/_Options/Character _encoding/Central European (ISO-8859-_2)"),
744 ENC_ACTION(C_ISO_8859_2)},
746 {N_("/_Options/Character _encoding/Baltic"), NULL, NULL, 0, "<Branch>"},
747 {N_("/_Options/Character _encoding/Baltic/ISO-8859-13"),
748 ENC_ACTION(C_ISO_8859_13)},
749 {N_("/_Options/Character _encoding/Baltic/ISO-8859-_4"),
750 ENC_ACTION(C_ISO_8859_4)},
752 {N_("/_Options/Character _encoding/Greek (ISO-8859-_7)"),
753 ENC_ACTION(C_ISO_8859_7)},
755 {N_("/_Options/Character _encoding/Hebrew"), NULL, NULL, 0, "<Branch>"},
756 {N_("/_Options/Character _encoding/Hebrew/ISO-8859-_8"),
757 ENC_ACTION(C_ISO_8859_8)},
758 {N_("/_Options/Character _encoding/Hebrew/Windows-1255"),
759 ENC_ACTION(C_WINDOWS_1255)},
761 {N_("/_Options/Character _encoding/Arabic"), NULL, NULL, 0, "<Branch>"},
762 {N_("/_Options/Character _encoding/Arabic/ISO-8859-_6"),
763 ENC_ACTION(C_ISO_8859_6)},
764 {N_("/_Options/Character _encoding/Arabic/Windows-1256"),
765 ENC_ACTION(C_CP1256)},
767 {N_("/_Options/Character _encoding/Turkish (ISO-8859-_9)"),
768 ENC_ACTION(C_ISO_8859_9)},
770 {N_("/_Options/Character _encoding/Cyrillic"), NULL, NULL, 0, "<Branch>"},
771 {N_("/_Options/Character _encoding/Cyrillic/ISO-8859-_5"),
772 ENC_ACTION(C_ISO_8859_5)},
773 {N_("/_Options/Character _encoding/Cyrillic/KOI8-_R"),
774 ENC_ACTION(C_KOI8_R)},
775 {N_("/_Options/Character _encoding/Cyrillic/KOI8-U"),
776 ENC_ACTION(C_KOI8_U)},
777 {N_("/_Options/Character _encoding/Cyrillic/Windows-1251"),
778 ENC_ACTION(C_WINDOWS_1251)},
780 {N_("/_Options/Character _encoding/Japanese"), NULL, NULL, 0, "<Branch>"},
781 {N_("/_Options/Character _encoding/Japanese/ISO-2022-_JP"),
782 ENC_ACTION(C_ISO_2022_JP)},
783 {N_("/_Options/Character _encoding/Japanese/ISO-2022-JP-2"),
784 ENC_ACTION(C_ISO_2022_JP_2)},
785 {N_("/_Options/Character _encoding/Japanese/_EUC-JP"),
786 ENC_ACTION(C_EUC_JP)},
787 {N_("/_Options/Character _encoding/Japanese/_Shift__JIS"),
788 ENC_ACTION(C_SHIFT_JIS)},
790 {N_("/_Options/Character _encoding/Chinese"), NULL, NULL, 0, "<Branch>"},
791 {N_("/_Options/Character _encoding/Chinese/Simplified (_GB2312)"),
792 ENC_ACTION(C_GB2312)},
793 {N_("/_Options/Character _encoding/Chinese/Simplified (GBK)"),
795 {N_("/_Options/Character _encoding/Chinese/Traditional (_Big5)"),
797 {N_("/_Options/Character _encoding/Chinese/Traditional (EUC-_TW)"),
798 ENC_ACTION(C_EUC_TW)},
800 {N_("/_Options/Character _encoding/Korean"), NULL, NULL, 0, "<Branch>"},
801 {N_("/_Options/Character _encoding/Korean/EUC-_KR"),
802 ENC_ACTION(C_EUC_KR)},
803 {N_("/_Options/Character _encoding/Korean/ISO-2022-KR"),
804 ENC_ACTION(C_ISO_2022_KR)},
806 {N_("/_Options/Character _encoding/Thai"), NULL, NULL, 0, "<Branch>"},
807 {N_("/_Options/Character _encoding/Thai/TIS-620"),
808 ENC_ACTION(C_TIS_620)},
809 {N_("/_Options/Character _encoding/Thai/Windows-874"),
810 ENC_ACTION(C_WINDOWS_874)},
812 {N_("/_Tools"), NULL, NULL, 0, "<Branch>"},
813 {N_("/_Tools/Show _ruler"), NULL, compose_toggle_ruler_cb, 0, "<ToggleItem>"},
814 {N_("/_Tools/_Address book"), "<shift><control>A", compose_address_cb , 0, NULL},
815 {N_("/_Tools/_Template"), NULL, NULL, 0, "<Branch>"},
816 {N_("/_Tools/Actio_ns"), NULL, NULL, 0, "<Branch>"},
817 {N_("/_Help"), NULL, NULL, 0, "<Branch>"},
818 {N_("/_Help/_About"), NULL, about_show, 0, NULL}
821 static GtkTargetEntry compose_mime_types[] =
823 {"text/uri-list", 0, 0},
824 {"UTF8_STRING", 0, 0},
828 static gboolean compose_put_existing_to_front(MsgInfo *info)
830 GList *compose_list = compose_get_compose_list();
834 for (elem = compose_list; elem != NULL && elem->data != NULL;
836 Compose *c = (Compose*)elem->data;
838 if (!c->targetinfo || !c->targetinfo->msgid ||
842 if (!strcmp(c->targetinfo->msgid, info->msgid)) {
843 gtkut_window_popup(c->window);
851 static GdkColor quote_color1 =
852 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
853 static GdkColor quote_color2 =
854 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
855 static GdkColor quote_color3 =
856 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
858 static GdkColor quote_bgcolor1 =
859 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
860 static GdkColor quote_bgcolor2 =
861 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
862 static GdkColor quote_bgcolor3 =
863 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
865 static GdkColor signature_color = {
872 static GdkColor uri_color = {
879 static void compose_create_tags(GtkTextView *text, Compose *compose)
881 GtkTextBuffer *buffer;
882 GdkColor black = {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
888 buffer = gtk_text_view_get_buffer(text);
890 if (prefs_common.enable_color) {
891 /* grab the quote colors, converting from an int to a GdkColor */
892 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
894 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
896 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
898 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_bgcol,
900 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_bgcol,
902 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_bgcol,
904 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
906 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
909 signature_color = quote_color1 = quote_color2 = quote_color3 =
910 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
913 if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
914 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
915 "foreground-gdk", "e_color1,
916 "paragraph-background-gdk", "e_bgcolor1,
918 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
919 "foreground-gdk", "e_color2,
920 "paragraph-background-gdk", "e_bgcolor2,
922 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
923 "foreground-gdk", "e_color3,
924 "paragraph-background-gdk", "e_bgcolor3,
927 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
928 "foreground-gdk", "e_color1,
930 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
931 "foreground-gdk", "e_color2,
933 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
934 "foreground-gdk", "e_color3,
938 compose->signature_tag = gtk_text_buffer_create_tag(buffer, "signature",
939 "foreground-gdk", &signature_color,
942 compose->uri_tag = gtk_text_buffer_create_tag(buffer, "link",
943 "foreground-gdk", &uri_color,
945 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
946 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
948 color[0] = quote_color1;
949 color[1] = quote_color2;
950 color[2] = quote_color3;
951 color[3] = quote_bgcolor1;
952 color[4] = quote_bgcolor2;
953 color[5] = quote_bgcolor3;
954 color[6] = signature_color;
955 color[7] = uri_color;
956 cmap = gdk_drawable_get_colormap(compose->window->window);
957 gdk_colormap_alloc_colors(cmap, color, 8, FALSE, TRUE, success);
959 for (i = 0; i < 8; i++) {
960 if (success[i] == FALSE) {
963 g_warning("Compose: color allocation failed.\n");
964 style = gtk_widget_get_style(GTK_WIDGET(text));
965 quote_color1 = quote_color2 = quote_color3 =
966 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 =
967 signature_color = uri_color = black;
972 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
973 GPtrArray *attach_files)
975 return compose_generic_new(account, mailto, NULL, attach_files, NULL);
978 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
980 return compose_generic_new(account, mailto, item, NULL, NULL);
983 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
985 return compose_generic_new( account, NULL, NULL, NULL, listAddress );
988 #define SCROLL_TO_CURSOR(compose) { \
989 GtkTextMark *cmark = gtk_text_buffer_get_insert( \
990 gtk_text_view_get_buffer( \
991 GTK_TEXT_VIEW(compose->text))); \
992 gtk_text_view_scroll_mark_onscreen( \
993 GTK_TEXT_VIEW(compose->text), \
997 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
998 GPtrArray *attach_files, GList *listAddress )
1001 GtkTextView *textview;
1002 GtkTextBuffer *textbuf;
1004 GtkItemFactory *ifactory;
1005 const gchar *subject_format = NULL;
1006 const gchar *body_format = NULL;
1007 gchar *mailto_from = NULL;
1008 PrefsAccount *mailto_account = NULL;
1010 /* check if mailto defines a from */
1011 if (mailto && *mailto != '\0') {
1012 scan_mailto_url(mailto, &mailto_from, NULL, NULL, NULL, NULL, NULL, NULL);
1013 /* mailto defines a from, check if we can get account prefs from it,
1014 if not, the account prefs will be guessed using other ways, but we'll keep
1017 mailto_account = account_find_from_address(mailto_from, TRUE);
1019 account = mailto_account;
1022 /* if no account prefs set from mailto, set if from folder prefs (if any) */
1023 if (!mailto_account && item && item->prefs && item->prefs->enable_default_account)
1024 account = account_find_from_id(item->prefs->default_account);
1026 /* if no account prefs set, fallback to the current one */
1027 if (!account) account = cur_account;
1028 g_return_val_if_fail(account != NULL, NULL);
1030 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
1032 /* override from name if mailto asked for it */
1034 gtk_entry_set_text(GTK_ENTRY(compose->from_name), mailto_from);
1035 g_free(mailto_from);
1038 ifactory = gtk_item_factory_from_widget(compose->menubar);
1040 compose->replyinfo = NULL;
1041 compose->fwdinfo = NULL;
1043 textview = GTK_TEXT_VIEW(compose->text);
1044 textbuf = gtk_text_view_get_buffer(textview);
1045 compose_create_tags(textview, compose);
1047 undo_block(compose->undostruct);
1049 compose_set_dictionaries_from_folder_prefs(compose, item);
1052 if (account->auto_sig)
1053 compose_insert_sig(compose, FALSE);
1054 gtk_text_buffer_get_start_iter(textbuf, &iter);
1055 gtk_text_buffer_place_cursor(textbuf, &iter);
1057 if (account->protocol != A_NNTP) {
1058 if (mailto && *mailto != '\0') {
1059 compose_entries_set(compose, mailto, COMPOSE_TO);
1061 } else if (item && item->prefs->enable_default_to) {
1062 compose_entry_append(compose, item->prefs->default_to, COMPOSE_TO);
1063 compose_entry_mark_default_to(compose, item->prefs->default_to);
1065 if (item && item->ret_rcpt) {
1066 menu_set_active(ifactory, "/Options/Request Return Receipt", TRUE);
1069 if (mailto && *mailto != '\0') {
1070 if (!strchr(mailto, '@'))
1071 compose_entries_set(compose, mailto, COMPOSE_NEWSGROUPS);
1073 compose_entries_set(compose, mailto, COMPOSE_TO);
1074 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1075 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS);
1078 * CLAWS: just don't allow return receipt request, even if the user
1079 * may want to send an email. simple but foolproof.
1081 menu_set_sensitive(ifactory, "/Options/Request Return Receipt", FALSE);
1083 compose_add_field_list( compose, listAddress );
1085 if (item && item->prefs && item->prefs->compose_with_format) {
1086 subject_format = item->prefs->compose_subject_format;
1087 body_format = item->prefs->compose_body_format;
1088 } else if (account->compose_with_format) {
1089 subject_format = account->compose_subject_format;
1090 body_format = account->compose_body_format;
1091 } else if (prefs_common.compose_with_format) {
1092 subject_format = prefs_common.compose_subject_format;
1093 body_format = prefs_common.compose_body_format;
1096 if (subject_format || body_format) {
1097 MsgInfo* dummyinfo = NULL;
1100 && *subject_format != '\0' )
1102 gchar *subject = NULL;
1106 dummyinfo = compose_msginfo_new_from_compose(compose);
1108 /* decode \-escape sequences in the internal representation of the quote format */
1109 tmp = malloc(strlen(subject_format)+1);
1110 pref_get_unescaped_pref(tmp, subject_format);
1112 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1114 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE,
1115 compose->gtkaspell);
1117 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE);
1119 quote_fmt_scan_string(tmp);
1122 buf = quote_fmt_get_buffer();
1124 alertpanel_error(_("New message subject format error."));
1126 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1127 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1128 quote_fmt_reset_vartable();
1135 && *body_format != '\0' )
1138 GtkTextBuffer *buffer;
1139 GtkTextIter start, end;
1142 if ( dummyinfo == NULL )
1143 dummyinfo = compose_msginfo_new_from_compose(compose);
1145 text = GTK_TEXT_VIEW(compose->text);
1146 buffer = gtk_text_view_get_buffer(text);
1147 gtk_text_buffer_get_start_iter(buffer, &start);
1148 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1149 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1151 compose_quote_fmt(compose, dummyinfo,
1153 NULL, tmp, FALSE, TRUE,
1154 _("New message body format error at line %d."));
1155 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1156 quote_fmt_reset_vartable();
1161 procmsg_msginfo_free( dummyinfo );
1168 for (i = 0; i < attach_files->len; i++) {
1169 file = g_ptr_array_index(attach_files, i);
1170 compose_attach_append(compose, file, file, NULL);
1174 compose_show_first_last_header(compose, TRUE);
1176 /* Set save folder */
1177 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1178 gchar *folderidentifier;
1180 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
1181 folderidentifier = folder_item_get_identifier(item);
1182 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
1183 g_free(folderidentifier);
1186 gtk_widget_grab_focus(compose->header_last->entry);
1188 undo_unblock(compose->undostruct);
1190 if (prefs_common.auto_exteditor)
1191 compose_exec_ext_editor(compose);
1193 compose->draft_timeout_tag = -1;
1194 SCROLL_TO_CURSOR(compose);
1196 compose->modified = FALSE;
1197 compose_set_title(compose);
1201 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1202 gboolean override_pref)
1204 gchar *privacy = NULL;
1206 g_return_if_fail(compose != NULL);
1207 g_return_if_fail(account != NULL);
1209 if (override_pref == FALSE && account->default_encrypt_reply == FALSE)
1212 if (account->default_privacy_system
1213 && strlen(account->default_privacy_system)) {
1214 privacy = account->default_privacy_system;
1216 GSList *privacy_avail = privacy_get_system_ids();
1217 if (privacy_avail && g_slist_length(privacy_avail)) {
1218 privacy = (gchar *)(privacy_avail->data);
1221 if (privacy != NULL) {
1222 if (compose->privacy_system == NULL)
1223 compose->privacy_system = g_strdup(privacy);
1224 else if (*(compose->privacy_system) == '\0') {
1225 g_free(compose->privacy_system);
1226 compose->privacy_system = g_strdup(privacy);
1228 compose_update_privacy_system_menu_item(compose, FALSE);
1229 compose_use_encryption(compose, TRUE);
1233 static void compose_force_signing(Compose *compose, PrefsAccount *account)
1235 gchar *privacy = NULL;
1237 if (account->default_privacy_system
1238 && strlen(account->default_privacy_system)) {
1239 privacy = account->default_privacy_system;
1241 GSList *privacy_avail = privacy_get_system_ids();
1242 if (privacy_avail && g_slist_length(privacy_avail)) {
1243 privacy = (gchar *)(privacy_avail->data);
1246 if (privacy != NULL) {
1247 if (compose->privacy_system == NULL)
1248 compose->privacy_system = g_strdup(privacy);
1249 compose_update_privacy_system_menu_item(compose, FALSE);
1250 compose_use_signing(compose, TRUE);
1254 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1258 Compose *compose = NULL;
1259 GtkItemFactory *ifactory = NULL;
1261 g_return_val_if_fail(msginfo_list != NULL, NULL);
1263 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1264 g_return_val_if_fail(msginfo != NULL, NULL);
1266 list_len = g_slist_length(msginfo_list);
1270 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1271 FALSE, prefs_common.default_reply_list, FALSE, body);
1273 case COMPOSE_REPLY_WITH_QUOTE:
1274 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1275 FALSE, prefs_common.default_reply_list, FALSE, body);
1277 case COMPOSE_REPLY_WITHOUT_QUOTE:
1278 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1279 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1281 case COMPOSE_REPLY_TO_SENDER:
1282 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1283 FALSE, FALSE, TRUE, body);
1285 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1286 compose = compose_followup_and_reply_to(msginfo,
1287 COMPOSE_QUOTE_CHECK,
1288 FALSE, FALSE, body);
1290 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1291 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1292 FALSE, FALSE, TRUE, body);
1294 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1295 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1296 FALSE, FALSE, TRUE, NULL);
1298 case COMPOSE_REPLY_TO_ALL:
1299 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1300 TRUE, FALSE, FALSE, body);
1302 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1303 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1304 TRUE, FALSE, FALSE, body);
1306 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1307 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1308 TRUE, FALSE, FALSE, NULL);
1310 case COMPOSE_REPLY_TO_LIST:
1311 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1312 FALSE, TRUE, FALSE, body);
1314 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1315 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1316 FALSE, TRUE, FALSE, body);
1318 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1319 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1320 FALSE, TRUE, FALSE, NULL);
1322 case COMPOSE_FORWARD:
1323 if (prefs_common.forward_as_attachment) {
1324 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1327 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1331 case COMPOSE_FORWARD_INLINE:
1332 /* check if we reply to more than one Message */
1333 if (list_len == 1) {
1334 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1337 /* more messages FALL THROUGH */
1338 case COMPOSE_FORWARD_AS_ATTACH:
1339 compose = compose_forward_multiple(NULL, msginfo_list);
1341 case COMPOSE_REDIRECT:
1342 compose = compose_redirect(NULL, msginfo, FALSE);
1345 g_warning("compose_reply_mode(): invalid Compose Mode: %d\n", mode);
1348 if (compose == NULL) {
1349 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1352 ifactory = gtk_item_factory_from_widget(compose->menubar);
1354 compose->rmode = mode;
1355 switch (compose->rmode) {
1357 case COMPOSE_REPLY_WITH_QUOTE:
1358 case COMPOSE_REPLY_WITHOUT_QUOTE:
1359 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1360 debug_print("reply mode Normal\n");
1361 menu_set_active(ifactory, "/Options/Reply mode/Normal", TRUE);
1362 compose_reply_change_mode(compose, COMPOSE_REPLY, NULL); /* force update */
1364 case COMPOSE_REPLY_TO_SENDER:
1365 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1366 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1367 debug_print("reply mode Sender\n");
1368 menu_set_active(ifactory, "/Options/Reply mode/Sender", TRUE);
1370 case COMPOSE_REPLY_TO_ALL:
1371 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1372 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1373 debug_print("reply mode All\n");
1374 menu_set_active(ifactory, "/Options/Reply mode/All", TRUE);
1376 case COMPOSE_REPLY_TO_LIST:
1377 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1378 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1379 debug_print("reply mode List\n");
1380 menu_set_active(ifactory, "/Options/Reply mode/Mailing-list", TRUE);
1388 static Compose *compose_reply(MsgInfo *msginfo,
1389 ComposeQuoteMode quote_mode,
1395 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1396 to_sender, FALSE, body);
1399 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1400 ComposeQuoteMode quote_mode,
1405 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1406 to_sender, TRUE, body);
1409 static void compose_extract_original_charset(Compose *compose)
1411 MsgInfo *info = NULL;
1412 if (compose->replyinfo) {
1413 info = compose->replyinfo;
1414 } else if (compose->fwdinfo) {
1415 info = compose->fwdinfo;
1416 } else if (compose->targetinfo) {
1417 info = compose->targetinfo;
1420 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1421 MimeInfo *partinfo = mimeinfo;
1422 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1423 partinfo = procmime_mimeinfo_next(partinfo);
1425 compose->orig_charset =
1426 g_strdup(procmime_mimeinfo_get_parameter(
1427 partinfo, "charset"));
1429 procmime_mimeinfo_free_all(mimeinfo);
1433 #define SIGNAL_BLOCK(buffer) { \
1434 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1435 G_CALLBACK(compose_changed_cb), \
1437 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1438 G_CALLBACK(text_inserted), \
1442 #define SIGNAL_UNBLOCK(buffer) { \
1443 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1444 G_CALLBACK(compose_changed_cb), \
1446 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1447 G_CALLBACK(text_inserted), \
1451 static Compose *compose_generic_reply(MsgInfo *msginfo,
1452 ComposeQuoteMode quote_mode,
1453 gboolean to_all, gboolean to_ml,
1455 gboolean followup_and_reply_to,
1458 GtkItemFactory *ifactory;
1460 PrefsAccount *account = NULL;
1461 GtkTextView *textview;
1462 GtkTextBuffer *textbuf;
1463 gboolean quote = FALSE;
1464 const gchar *qmark = NULL;
1465 const gchar *body_fmt = NULL;
1467 g_return_val_if_fail(msginfo != NULL, NULL);
1468 g_return_val_if_fail(msginfo->folder != NULL, NULL);
1470 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1472 g_return_val_if_fail(account != NULL, NULL);
1474 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1476 compose->updating = TRUE;
1478 ifactory = gtk_item_factory_from_widget(compose->menubar);
1480 menu_set_active(ifactory, "/Options/Remove references", FALSE);
1481 menu_set_sensitive(ifactory, "/Options/Remove references", TRUE);
1483 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1484 if (!compose->replyinfo)
1485 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1487 compose_extract_original_charset(compose);
1489 if (msginfo->folder && msginfo->folder->ret_rcpt)
1490 menu_set_active(ifactory, "/Options/Request Return Receipt", TRUE);
1492 /* Set save folder */
1493 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1494 gchar *folderidentifier;
1496 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1497 folderidentifier = folder_item_get_identifier(msginfo->folder);
1498 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
1499 g_free(folderidentifier);
1502 if (compose_parse_header(compose, msginfo) < 0) {
1503 compose->updating = FALSE;
1504 compose_destroy(compose);
1508 textview = (GTK_TEXT_VIEW(compose->text));
1509 textbuf = gtk_text_view_get_buffer(textview);
1510 compose_create_tags(textview, compose);
1512 undo_block(compose->undostruct);
1514 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1517 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1518 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1519 /* use the reply format of folder (if enabled), or the account's one
1520 (if enabled) or fallback to the global reply format, which is always
1521 enabled (even if empty), and use the relevant quotemark */
1523 if (msginfo->folder && msginfo->folder->prefs &&
1524 msginfo->folder->prefs->reply_with_format) {
1525 qmark = msginfo->folder->prefs->reply_quotemark;
1526 body_fmt = msginfo->folder->prefs->reply_body_format;
1528 } else if (account->reply_with_format) {
1529 qmark = account->reply_quotemark;
1530 body_fmt = account->reply_body_format;
1533 qmark = prefs_common.quotemark;
1534 body_fmt = prefs_common.quotefmt;
1539 /* empty quotemark is not allowed */
1540 if (qmark == NULL || *qmark == '\0')
1542 compose_quote_fmt(compose, compose->replyinfo,
1543 body_fmt, qmark, body, FALSE, TRUE,
1544 _("Message reply format error at line %d."));
1545 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1546 quote_fmt_reset_vartable();
1549 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1550 compose_force_encryption(compose, account, FALSE);
1553 SIGNAL_BLOCK(textbuf);
1555 if (account->auto_sig)
1556 compose_insert_sig(compose, FALSE);
1558 compose_wrap_all(compose);
1560 SIGNAL_UNBLOCK(textbuf);
1562 gtk_widget_grab_focus(compose->text);
1564 undo_unblock(compose->undostruct);
1566 if (prefs_common.auto_exteditor)
1567 compose_exec_ext_editor(compose);
1569 compose->modified = FALSE;
1570 compose_set_title(compose);
1572 compose->updating = FALSE;
1573 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1574 SCROLL_TO_CURSOR(compose);
1576 if (compose->deferred_destroy) {
1577 compose_destroy(compose);
1584 #define INSERT_FW_HEADER(var, hdr) \
1585 if (msginfo->var && *msginfo->var) { \
1586 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1587 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1588 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1591 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1592 gboolean as_attach, const gchar *body,
1593 gboolean no_extedit,
1597 GtkTextView *textview;
1598 GtkTextBuffer *textbuf;
1601 g_return_val_if_fail(msginfo != NULL, NULL);
1602 g_return_val_if_fail(msginfo->folder != NULL, NULL);
1605 !(account = compose_guess_forward_account_from_msginfo
1607 account = cur_account;
1609 compose = compose_create(account, msginfo->folder, COMPOSE_FORWARD, batch);
1611 compose->updating = TRUE;
1612 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1613 if (!compose->fwdinfo)
1614 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1616 compose_extract_original_charset(compose);
1618 if (msginfo->subject && *msginfo->subject) {
1619 gchar *buf, *buf2, *p;
1621 buf = p = g_strdup(msginfo->subject);
1622 p += subject_get_prefix_length(p);
1623 memmove(buf, p, strlen(p) + 1);
1625 buf2 = g_strdup_printf("Fw: %s", buf);
1626 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1632 textview = GTK_TEXT_VIEW(compose->text);
1633 textbuf = gtk_text_view_get_buffer(textview);
1634 compose_create_tags(textview, compose);
1636 undo_block(compose->undostruct);
1640 msgfile = procmsg_get_message_file(msginfo);
1641 if (!is_file_exist(msgfile))
1642 g_warning("%s: file not exist\n", msgfile);
1644 compose_attach_append(compose, msgfile, msgfile,
1649 const gchar *qmark = NULL;
1650 const gchar *body_fmt = prefs_common.fw_quotefmt;
1651 MsgInfo *full_msginfo;
1653 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1655 full_msginfo = procmsg_msginfo_copy(msginfo);
1657 /* use the forward format of folder (if enabled), or the account's one
1658 (if enabled) or fallback to the global forward format, which is always
1659 enabled (even if empty), and use the relevant quotemark */
1660 if (msginfo->folder && msginfo->folder->prefs &&
1661 msginfo->folder->prefs->forward_with_format) {
1662 qmark = msginfo->folder->prefs->forward_quotemark;
1663 body_fmt = msginfo->folder->prefs->forward_body_format;
1665 } else if (account->forward_with_format) {
1666 qmark = account->forward_quotemark;
1667 body_fmt = account->forward_body_format;
1670 qmark = prefs_common.fw_quotemark;
1671 body_fmt = prefs_common.fw_quotefmt;
1674 /* empty quotemark is not allowed */
1675 if (qmark == NULL || *qmark == '\0')
1678 compose_quote_fmt(compose, full_msginfo,
1679 body_fmt, qmark, body, FALSE, TRUE,
1680 _("Message forward format error at line %d."));
1681 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1682 quote_fmt_reset_vartable();
1683 compose_attach_parts(compose, msginfo);
1685 procmsg_msginfo_free(full_msginfo);
1688 SIGNAL_BLOCK(textbuf);
1690 if (account->auto_sig)
1691 compose_insert_sig(compose, FALSE);
1693 compose_wrap_all(compose);
1695 SIGNAL_UNBLOCK(textbuf);
1697 gtk_text_buffer_get_start_iter(textbuf, &iter);
1698 gtk_text_buffer_place_cursor(textbuf, &iter);
1700 gtk_widget_grab_focus(compose->header_last->entry);
1702 if (!no_extedit && prefs_common.auto_exteditor)
1703 compose_exec_ext_editor(compose);
1706 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1707 gchar *folderidentifier;
1709 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1710 folderidentifier = folder_item_get_identifier(msginfo->folder);
1711 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
1712 g_free(folderidentifier);
1715 undo_unblock(compose->undostruct);
1717 compose->modified = FALSE;
1718 compose_set_title(compose);
1720 compose->updating = FALSE;
1721 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1722 SCROLL_TO_CURSOR(compose);
1724 if (compose->deferred_destroy) {
1725 compose_destroy(compose);
1732 #undef INSERT_FW_HEADER
1734 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1737 GtkTextView *textview;
1738 GtkTextBuffer *textbuf;
1742 gboolean single_mail = TRUE;
1744 g_return_val_if_fail(msginfo_list != NULL, NULL);
1746 if (g_slist_length(msginfo_list) > 1)
1747 single_mail = FALSE;
1749 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1750 if (((MsgInfo *)msginfo->data)->folder == NULL)
1753 /* guess account from first selected message */
1755 !(account = compose_guess_forward_account_from_msginfo
1756 (msginfo_list->data)))
1757 account = cur_account;
1759 g_return_val_if_fail(account != NULL, NULL);
1761 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1762 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1763 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1766 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1768 compose->updating = TRUE;
1770 textview = GTK_TEXT_VIEW(compose->text);
1771 textbuf = gtk_text_view_get_buffer(textview);
1772 compose_create_tags(textview, compose);
1774 undo_block(compose->undostruct);
1775 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1776 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
1778 if (!is_file_exist(msgfile))
1779 g_warning("%s: file not exist\n", msgfile);
1781 compose_attach_append(compose, msgfile, msgfile,
1787 MsgInfo *info = (MsgInfo *)msginfo_list->data;
1788 if (info->subject && *info->subject) {
1789 gchar *buf, *buf2, *p;
1791 buf = p = g_strdup(info->subject);
1792 p += subject_get_prefix_length(p);
1793 memmove(buf, p, strlen(p) + 1);
1795 buf2 = g_strdup_printf("Fw: %s", buf);
1796 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1802 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
1803 _("Fw: multiple emails"));
1806 SIGNAL_BLOCK(textbuf);
1808 if (account->auto_sig)
1809 compose_insert_sig(compose, FALSE);
1811 compose_wrap_all(compose);
1813 SIGNAL_UNBLOCK(textbuf);
1815 gtk_text_buffer_get_start_iter(textbuf, &iter);
1816 gtk_text_buffer_place_cursor(textbuf, &iter);
1818 gtk_widget_grab_focus(compose->header_last->entry);
1819 undo_unblock(compose->undostruct);
1820 compose->modified = FALSE;
1821 compose_set_title(compose);
1823 compose->updating = FALSE;
1824 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1825 SCROLL_TO_CURSOR(compose);
1827 if (compose->deferred_destroy) {
1828 compose_destroy(compose);
1835 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
1837 GtkTextIter start = *iter;
1838 GtkTextIter end_iter;
1839 int start_pos = gtk_text_iter_get_offset(&start);
1841 if (!compose->account->sig_sep)
1844 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
1845 start_pos+strlen(compose->account->sig_sep));
1847 /* check sig separator */
1848 str = gtk_text_iter_get_text(&start, &end_iter);
1849 if (!strcmp(str, compose->account->sig_sep)) {
1851 /* check end of line (\n) */
1852 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
1853 start_pos+strlen(compose->account->sig_sep));
1854 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
1855 start_pos+strlen(compose->account->sig_sep)+1);
1856 tmp = gtk_text_iter_get_text(&start, &end_iter);
1857 if (!strcmp(tmp,"\n")) {
1869 static void compose_colorize_signature(Compose *compose)
1871 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
1873 GtkTextIter end_iter;
1874 gtk_text_buffer_get_start_iter(buffer, &iter);
1875 while (gtk_text_iter_forward_line(&iter))
1876 if (compose_is_sig_separator(compose, buffer, &iter)) {
1877 gtk_text_buffer_get_end_iter(buffer, &end_iter);
1878 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
1882 #define BLOCK_WRAP() { \
1883 prev_autowrap = compose->autowrap; \
1884 buffer = gtk_text_view_get_buffer( \
1885 GTK_TEXT_VIEW(compose->text)); \
1886 compose->autowrap = FALSE; \
1888 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1889 G_CALLBACK(compose_changed_cb), \
1891 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1892 G_CALLBACK(text_inserted), \
1895 #define UNBLOCK_WRAP() { \
1896 compose->autowrap = prev_autowrap; \
1897 if (compose->autowrap) { \
1898 gint old = compose->draft_timeout_tag; \
1899 compose->draft_timeout_tag = -2; \
1900 compose_wrap_all(compose); \
1901 compose->draft_timeout_tag = old; \
1904 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1905 G_CALLBACK(compose_changed_cb), \
1907 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1908 G_CALLBACK(text_inserted), \
1912 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
1914 Compose *compose = NULL;
1915 PrefsAccount *account = NULL;
1916 GtkTextView *textview;
1917 GtkTextBuffer *textbuf;
1921 gchar buf[BUFFSIZE];
1922 gboolean use_signing = FALSE;
1923 gboolean use_encryption = FALSE;
1924 gchar *privacy_system = NULL;
1925 int priority = PRIORITY_NORMAL;
1926 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
1928 g_return_val_if_fail(msginfo != NULL, NULL);
1929 g_return_val_if_fail(msginfo->folder != NULL, NULL);
1931 if (compose_put_existing_to_front(msginfo)) {
1935 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
1936 folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
1937 gchar queueheader_buf[BUFFSIZE];
1940 /* Select Account from queue headers */
1941 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1942 sizeof(queueheader_buf), "X-Claws-Account-Id:")) {
1943 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
1944 account = account_find_from_id(id);
1946 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1947 sizeof(queueheader_buf), "X-Sylpheed-Account-Id:")) {
1948 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
1949 account = account_find_from_id(id);
1951 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1952 sizeof(queueheader_buf), "NAID:")) {
1953 id = atoi(&queueheader_buf[strlen("NAID:")]);
1954 account = account_find_from_id(id);
1956 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1957 sizeof(queueheader_buf), "MAID:")) {
1958 id = atoi(&queueheader_buf[strlen("MAID:")]);
1959 account = account_find_from_id(id);
1961 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1962 sizeof(queueheader_buf), "S:")) {
1963 account = account_find_from_address(queueheader_buf, FALSE);
1965 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1966 sizeof(queueheader_buf), "X-Claws-Sign:")) {
1967 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
1968 use_signing = param;
1971 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1972 sizeof(queueheader_buf), "X-Sylpheed-Sign:")) {
1973 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
1974 use_signing = param;
1977 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1978 sizeof(queueheader_buf), "X-Claws-Encrypt:")) {
1979 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
1980 use_encryption = param;
1982 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1983 sizeof(queueheader_buf), "X-Sylpheed-Encrypt:")) {
1984 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
1985 use_encryption = param;
1987 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1988 sizeof(queueheader_buf), "X-Claws-Privacy-System:")) {
1989 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
1991 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1992 sizeof(queueheader_buf), "X-Sylpheed-Privacy-System:")) {
1993 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
1995 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1996 sizeof(queueheader_buf), "X-Priority: ")) {
1997 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
2000 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2001 sizeof(queueheader_buf), "RMID:")) {
2002 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
2003 if (tokens[0] && tokens[1] && tokens[2]) {
2004 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2005 if (orig_item != NULL) {
2006 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2011 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2012 sizeof(queueheader_buf), "FMID:")) {
2013 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
2014 if (tokens[0] && tokens[1] && tokens[2]) {
2015 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2016 if (orig_item != NULL) {
2017 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2023 account = msginfo->folder->folder->account;
2026 if (!account && prefs_common.reedit_account_autosel) {
2027 gchar from[BUFFSIZE];
2028 if (!procheader_get_header_from_msginfo(msginfo, from, sizeof(from), "FROM:")) {
2029 extract_address(from);
2030 account = account_find_from_address(from, FALSE);
2034 account = cur_account;
2036 g_return_val_if_fail(account != NULL, NULL);
2038 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2040 compose->replyinfo = replyinfo;
2041 compose->fwdinfo = fwdinfo;
2043 compose->updating = TRUE;
2044 compose->priority = priority;
2046 if (privacy_system != NULL) {
2047 compose->privacy_system = privacy_system;
2048 compose_use_signing(compose, use_signing);
2049 compose_use_encryption(compose, use_encryption);
2050 compose_update_privacy_system_menu_item(compose, FALSE);
2052 activate_privacy_system(compose, account, FALSE);
2055 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2057 compose_extract_original_charset(compose);
2059 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2060 folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
2061 gchar queueheader_buf[BUFFSIZE];
2063 /* Set message save folder */
2064 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "SCF:")) {
2067 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2068 gtk_editable_delete_text(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
2069 gtk_editable_insert_text(GTK_EDITABLE(compose->savemsg_entry), &queueheader_buf[4], strlen(&queueheader_buf[4]), &startpos);
2071 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "RRCPT:")) {
2072 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2074 GtkItemFactory *ifactory;
2075 ifactory = gtk_item_factory_from_widget(compose->menubar);
2076 menu_set_active(ifactory, "/Options/Request Return Receipt", TRUE);
2081 if (compose_parse_header(compose, msginfo) < 0) {
2082 compose->updating = FALSE;
2083 compose_destroy(compose);
2086 compose_reedit_set_entry(compose, msginfo);
2088 textview = GTK_TEXT_VIEW(compose->text);
2089 textbuf = gtk_text_view_get_buffer(textview);
2090 compose_create_tags(textview, compose);
2092 mark = gtk_text_buffer_get_insert(textbuf);
2093 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2095 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2096 G_CALLBACK(compose_changed_cb),
2099 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2100 fp = procmime_get_first_encrypted_text_content(msginfo);
2102 compose_force_encryption(compose, account, TRUE);
2105 fp = procmime_get_first_text_content(msginfo);
2108 g_warning("Can't get text part\n");
2112 gboolean prev_autowrap = compose->autowrap;
2113 GtkTextBuffer *buffer = textbuf;
2115 while (fgets(buf, sizeof(buf), fp) != NULL) {
2117 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2123 compose_attach_parts(compose, msginfo);
2125 compose_colorize_signature(compose);
2127 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2128 G_CALLBACK(compose_changed_cb),
2131 gtk_widget_grab_focus(compose->text);
2133 if (prefs_common.auto_exteditor) {
2134 compose_exec_ext_editor(compose);
2136 compose->modified = FALSE;
2137 compose_set_title(compose);
2139 compose->updating = FALSE;
2140 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2141 SCROLL_TO_CURSOR(compose);
2143 if (compose->deferred_destroy) {
2144 compose_destroy(compose);
2148 compose->sig_str = compose_get_signature_str(compose);
2153 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2158 GtkItemFactory *ifactory;
2161 g_return_val_if_fail(msginfo != NULL, NULL);
2164 account = account_get_reply_account(msginfo,
2165 prefs_common.reply_account_autosel);
2166 g_return_val_if_fail(account != NULL, NULL);
2168 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2170 compose->updating = TRUE;
2172 ifactory = gtk_item_factory_from_widget(compose->menubar);
2173 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2174 compose->replyinfo = NULL;
2175 compose->fwdinfo = NULL;
2177 compose_show_first_last_header(compose, TRUE);
2179 gtk_widget_grab_focus(compose->header_last->entry);
2181 filename = procmsg_get_message_file(msginfo);
2183 if (filename == NULL) {
2184 compose->updating = FALSE;
2185 compose_destroy(compose);
2190 compose->redirect_filename = filename;
2192 /* Set save folder */
2193 item = msginfo->folder;
2194 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2195 gchar *folderidentifier;
2197 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
2198 folderidentifier = folder_item_get_identifier(item);
2199 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
2200 g_free(folderidentifier);
2203 compose_attach_parts(compose, msginfo);
2205 if (msginfo->subject)
2206 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2208 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2210 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2211 _("Message redirect format error at line %d."));
2212 quote_fmt_reset_vartable();
2213 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2215 compose_colorize_signature(compose);
2217 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
2218 menu_set_sensitive(ifactory, "/Add...", FALSE);
2219 menu_set_sensitive(ifactory, "/Remove", FALSE);
2220 menu_set_sensitive(ifactory, "/Properties...", FALSE);
2222 ifactory = gtk_item_factory_from_widget(compose->menubar);
2223 menu_set_sensitive(ifactory, "/Message/Save", FALSE);
2224 menu_set_sensitive(ifactory, "/Message/Insert file", FALSE);
2225 menu_set_sensitive(ifactory, "/Message/Attach file", FALSE);
2226 menu_set_sensitive(ifactory, "/Message/Insert signature", FALSE);
2227 menu_set_sensitive(ifactory, "/Edit", FALSE);
2228 menu_set_sensitive(ifactory, "/Options", FALSE);
2229 menu_set_sensitive(ifactory, "/Tools/Show ruler", FALSE);
2230 menu_set_sensitive(ifactory, "/Tools/Actions", FALSE);
2232 if (compose->toolbar->draft_btn)
2233 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2234 if (compose->toolbar->insert_btn)
2235 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2236 if (compose->toolbar->attach_btn)
2237 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2238 if (compose->toolbar->sig_btn)
2239 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2240 if (compose->toolbar->exteditor_btn)
2241 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2242 if (compose->toolbar->linewrap_current_btn)
2243 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2244 if (compose->toolbar->linewrap_all_btn)
2245 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2247 compose->modified = FALSE;
2248 compose_set_title(compose);
2249 compose->updating = FALSE;
2250 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2251 SCROLL_TO_CURSOR(compose);
2253 if (compose->deferred_destroy) {
2254 compose_destroy(compose);
2261 GList *compose_get_compose_list(void)
2263 return compose_list;
2266 void compose_entry_append(Compose *compose, const gchar *address,
2267 ComposeEntryType type)
2269 const gchar *header;
2271 gboolean in_quote = FALSE;
2272 if (!address || *address == '\0') return;
2279 header = N_("Bcc:");
2281 case COMPOSE_REPLYTO:
2282 header = N_("Reply-To:");
2284 case COMPOSE_NEWSGROUPS:
2285 header = N_("Newsgroups:");
2287 case COMPOSE_FOLLOWUPTO:
2288 header = N_( "Followup-To:");
2295 header = prefs_common_translated_header_name(header);
2297 cur = begin = (gchar *)address;
2299 /* we separate the line by commas, but not if we're inside a quoted
2301 while (*cur != '\0') {
2303 in_quote = !in_quote;
2304 if (*cur == ',' && !in_quote) {
2305 gchar *tmp = g_strdup(begin);
2307 tmp[cur-begin]='\0';
2310 while (*tmp == ' ' || *tmp == '\t')
2312 compose_add_header_entry(compose, header, tmp);
2319 gchar *tmp = g_strdup(begin);
2321 tmp[cur-begin]='\0';
2324 while (*tmp == ' ' || *tmp == '\t')
2326 compose_add_header_entry(compose, header, tmp);
2331 static void compose_entry_mark_default_to(Compose *compose, const gchar *mailto)
2333 static GdkColor yellow;
2334 static GdkColor black;
2335 static gboolean yellow_initialised = FALSE;
2339 if (!yellow_initialised) {
2340 gdk_color_parse("#f5f6be", &yellow);
2341 gdk_color_parse("#000000", &black);
2342 yellow_initialised = gdk_colormap_alloc_color(
2343 gdk_colormap_get_system(), &yellow, FALSE, TRUE);
2344 yellow_initialised &= gdk_colormap_alloc_color(
2345 gdk_colormap_get_system(), &black, FALSE, TRUE);
2348 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2349 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2350 if (gtk_entry_get_text(entry) &&
2351 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2352 if (yellow_initialised) {
2353 gtk_widget_modify_base(
2354 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2355 GTK_STATE_NORMAL, &yellow);
2356 gtk_widget_modify_text(
2357 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2358 GTK_STATE_NORMAL, &black);
2364 void compose_toolbar_cb(gint action, gpointer data)
2366 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2367 Compose *compose = (Compose*)toolbar_item->parent;
2369 g_return_if_fail(compose != NULL);
2373 compose_send_cb(compose, 0, NULL);
2376 compose_send_later_cb(compose, 0, NULL);
2379 compose_draft_cb(compose, COMPOSE_QUIT_EDITING, NULL);
2382 compose_insert_file_cb(compose, 0, NULL);
2385 compose_attach_cb(compose, 0, NULL);
2388 compose_insert_sig(compose, FALSE);
2391 compose_ext_editor_cb(compose, 0, NULL);
2393 case A_LINEWRAP_CURRENT:
2394 compose_beautify_paragraph(compose, NULL, TRUE);
2396 case A_LINEWRAP_ALL:
2397 compose_wrap_all_full(compose, TRUE);
2400 compose_address_cb(compose, 0, NULL);
2403 case A_CHECK_SPELLING:
2404 compose_check_all(compose);
2412 static void compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
2417 gchar *subject = NULL;
2421 gchar **attach = NULL;
2423 /* get mailto parts but skip from */
2424 scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach);
2427 compose_entry_append(compose, to, to_type);
2429 compose_entry_append(compose, cc, COMPOSE_CC);
2431 compose_entry_append(compose, bcc, COMPOSE_BCC);
2433 if (!g_utf8_validate (subject, -1, NULL)) {
2434 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2435 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2438 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2442 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2443 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2446 gboolean prev_autowrap = compose->autowrap;
2448 compose->autowrap = FALSE;
2450 mark = gtk_text_buffer_get_insert(buffer);
2451 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2453 if (!g_utf8_validate (body, -1, NULL)) {
2454 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2455 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2458 gtk_text_buffer_insert(buffer, &iter, body, -1);
2460 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2462 compose->autowrap = prev_autowrap;
2463 if (compose->autowrap)
2464 compose_wrap_all(compose);
2468 gint i = 0, att = 0;
2469 gchar *warn_files = NULL;
2470 while (attach[i] != NULL) {
2471 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2472 if (utf8_filename) {
2473 if (compose_attach_append(compose, attach[i], utf8_filename, NULL)) {
2474 gchar *tmp = g_strdup_printf("%s%s\n",
2475 warn_files?warn_files:"",
2481 g_free(utf8_filename);
2483 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2488 alertpanel_notice(ngettext(
2489 "The following file has been attached: \n%s",
2490 "The following files have been attached: \n%s", att), warn_files);
2502 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2504 static HeaderEntry hentry[] = {{"Reply-To:", NULL, TRUE},
2505 {"Cc:", NULL, TRUE},
2506 {"References:", NULL, FALSE},
2507 {"Bcc:", NULL, TRUE},
2508 {"Newsgroups:", NULL, TRUE},
2509 {"Followup-To:", NULL, TRUE},
2510 {"List-Post:", NULL, FALSE},
2511 {"X-Priority:", NULL, FALSE},
2512 {NULL, NULL, FALSE}};
2528 g_return_val_if_fail(msginfo != NULL, -1);
2530 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2531 procheader_get_header_fields(fp, hentry);
2534 if (hentry[H_REPLY_TO].body != NULL) {
2535 if (hentry[H_REPLY_TO].body[0] != '\0') {
2537 conv_unmime_header(hentry[H_REPLY_TO].body,
2540 g_free(hentry[H_REPLY_TO].body);
2541 hentry[H_REPLY_TO].body = NULL;
2543 if (hentry[H_CC].body != NULL) {
2544 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL);
2545 g_free(hentry[H_CC].body);
2546 hentry[H_CC].body = NULL;
2548 if (hentry[H_REFERENCES].body != NULL) {
2549 if (compose->mode == COMPOSE_REEDIT)
2550 compose->references = hentry[H_REFERENCES].body;
2552 compose->references = compose_parse_references
2553 (hentry[H_REFERENCES].body, msginfo->msgid);
2554 g_free(hentry[H_REFERENCES].body);
2556 hentry[H_REFERENCES].body = NULL;
2558 if (hentry[H_BCC].body != NULL) {
2559 if (compose->mode == COMPOSE_REEDIT)
2561 conv_unmime_header(hentry[H_BCC].body, NULL);
2562 g_free(hentry[H_BCC].body);
2563 hentry[H_BCC].body = NULL;
2565 if (hentry[H_NEWSGROUPS].body != NULL) {
2566 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2567 hentry[H_NEWSGROUPS].body = NULL;
2569 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2570 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2571 compose->followup_to =
2572 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2575 g_free(hentry[H_FOLLOWUP_TO].body);
2576 hentry[H_FOLLOWUP_TO].body = NULL;
2578 if (hentry[H_LIST_POST].body != NULL) {
2581 extract_address(hentry[H_LIST_POST].body);
2582 if (hentry[H_LIST_POST].body[0] != '\0') {
2583 scan_mailto_url(hentry[H_LIST_POST].body,
2584 NULL, &to, NULL, NULL, NULL, NULL, NULL);
2586 g_free(compose->ml_post);
2587 compose->ml_post = to;
2590 g_free(hentry[H_LIST_POST].body);
2591 hentry[H_LIST_POST].body = NULL;
2594 /* CLAWS - X-Priority */
2595 if (compose->mode == COMPOSE_REEDIT)
2596 if (hentry[H_X_PRIORITY].body != NULL) {
2599 priority = atoi(hentry[H_X_PRIORITY].body);
2600 g_free(hentry[H_X_PRIORITY].body);
2602 hentry[H_X_PRIORITY].body = NULL;
2604 if (priority < PRIORITY_HIGHEST ||
2605 priority > PRIORITY_LOWEST)
2606 priority = PRIORITY_NORMAL;
2608 compose->priority = priority;
2611 if (compose->mode == COMPOSE_REEDIT) {
2612 if (msginfo->inreplyto && *msginfo->inreplyto)
2613 compose->inreplyto = g_strdup(msginfo->inreplyto);
2617 if (msginfo->msgid && *msginfo->msgid)
2618 compose->inreplyto = g_strdup(msginfo->msgid);
2620 if (!compose->references) {
2621 if (msginfo->msgid && *msginfo->msgid) {
2622 if (msginfo->inreplyto && *msginfo->inreplyto)
2623 compose->references =
2624 g_strdup_printf("<%s>\n\t<%s>",
2628 compose->references =
2629 g_strconcat("<", msginfo->msgid, ">",
2631 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
2632 compose->references =
2633 g_strconcat("<", msginfo->inreplyto, ">",
2641 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
2643 GSList *ref_id_list, *cur;
2647 ref_id_list = references_list_append(NULL, ref);
2648 if (!ref_id_list) return NULL;
2649 if (msgid && *msgid)
2650 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
2655 for (cur = ref_id_list; cur != NULL; cur = cur->next)
2656 /* "<" + Message-ID + ">" + CR+LF+TAB */
2657 len += strlen((gchar *)cur->data) + 5;
2659 if (len > MAX_REFERENCES_LEN) {
2660 /* remove second message-ID */
2661 if (ref_id_list && ref_id_list->next &&
2662 ref_id_list->next->next) {
2663 g_free(ref_id_list->next->data);
2664 ref_id_list = g_slist_remove
2665 (ref_id_list, ref_id_list->next->data);
2667 slist_free_strings(ref_id_list);
2668 g_slist_free(ref_id_list);
2675 new_ref = g_string_new("");
2676 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
2677 if (new_ref->len > 0)
2678 g_string_append(new_ref, "\n\t");
2679 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
2682 slist_free_strings(ref_id_list);
2683 g_slist_free(ref_id_list);
2685 new_ref_str = new_ref->str;
2686 g_string_free(new_ref, FALSE);
2691 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
2692 const gchar *fmt, const gchar *qmark,
2693 const gchar *body, gboolean rewrap,
2694 gboolean need_unescape,
2695 const gchar *err_msg)
2697 MsgInfo* dummyinfo = NULL;
2698 gchar *quote_str = NULL;
2700 gboolean prev_autowrap;
2701 const gchar *trimmed_body = body;
2702 gint cursor_pos = -1;
2703 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2704 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2709 SIGNAL_BLOCK(buffer);
2712 dummyinfo = compose_msginfo_new_from_compose(compose);
2713 msginfo = dummyinfo;
2716 if (qmark != NULL) {
2718 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
2719 compose->gtkaspell);
2721 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
2723 quote_fmt_scan_string(qmark);
2726 buf = quote_fmt_get_buffer();
2728 alertpanel_error(_("Quote mark format error."));
2730 Xstrdup_a(quote_str, buf, goto error)
2733 if (fmt && *fmt != '\0') {
2736 while (*trimmed_body == '\n')
2740 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
2741 compose->gtkaspell);
2743 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
2745 if (need_unescape) {
2748 /* decode \-escape sequences in the internal representation of the quote format */
2749 tmp = malloc(strlen(fmt)+1);
2750 pref_get_unescaped_pref(tmp, fmt);
2751 quote_fmt_scan_string(tmp);
2755 quote_fmt_scan_string(fmt);
2759 buf = quote_fmt_get_buffer();
2761 gint line = quote_fmt_get_line();
2762 alertpanel_error(err_msg, line);
2768 prev_autowrap = compose->autowrap;
2769 compose->autowrap = FALSE;
2771 mark = gtk_text_buffer_get_insert(buffer);
2772 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2773 if (g_utf8_validate(buf, -1, NULL)) {
2774 gtk_text_buffer_insert(buffer, &iter, buf, -1);
2776 gchar *tmpout = NULL;
2777 tmpout = conv_codeset_strdup
2778 (buf, conv_get_locale_charset_str_no_utf8(),
2780 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
2782 tmpout = g_malloc(strlen(buf)*2+1);
2783 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
2785 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
2789 cursor_pos = quote_fmt_get_cursor_pos();
2790 compose->set_cursor_pos = cursor_pos;
2791 if (cursor_pos == -1) {
2794 gtk_text_buffer_get_start_iter(buffer, &iter);
2795 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
2796 gtk_text_buffer_place_cursor(buffer, &iter);
2798 compose->autowrap = prev_autowrap;
2799 if (compose->autowrap && rewrap)
2800 compose_wrap_all(compose);
2807 SIGNAL_UNBLOCK(buffer);
2809 procmsg_msginfo_free( dummyinfo );
2814 /* if ml_post is of type addr@host and from is of type
2815 * addr-anything@host, return TRUE
2817 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
2819 gchar *left_ml = NULL;
2820 gchar *right_ml = NULL;
2821 gchar *left_from = NULL;
2822 gchar *right_from = NULL;
2823 gboolean result = FALSE;
2825 if (!ml_post || !from)
2828 left_ml = g_strdup(ml_post);
2829 if (strstr(left_ml, "@")) {
2830 right_ml = strstr(left_ml, "@")+1;
2831 *(strstr(left_ml, "@")) = '\0';
2834 left_from = g_strdup(from);
2835 if (strstr(left_from, "@")) {
2836 right_from = strstr(left_from, "@")+1;
2837 *(strstr(left_from, "@")) = '\0';
2840 if (left_ml && left_from && right_ml && right_from
2841 && !strncmp(left_from, left_ml, strlen(left_ml))
2842 && !strcmp(right_from, right_ml)) {
2851 static gboolean same_address(const gchar *addr1, const gchar *addr2)
2853 gchar *my_addr1, *my_addr2;
2855 if (!addr1 || !addr2)
2858 Xstrdup_a(my_addr1, addr1, return FALSE);
2859 Xstrdup_a(my_addr2, addr2, return FALSE);
2861 extract_address(my_addr1);
2862 extract_address(my_addr2);
2864 return !strcasecmp(my_addr1, my_addr2);
2867 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
2868 gboolean to_all, gboolean to_ml,
2870 gboolean followup_and_reply_to)
2872 GSList *cc_list = NULL;
2875 gchar *replyto = NULL;
2876 GHashTable *to_table;
2878 gboolean reply_to_ml = FALSE;
2879 gboolean default_reply_to = FALSE;
2881 g_return_if_fail(compose->account != NULL);
2882 g_return_if_fail(msginfo != NULL);
2884 reply_to_ml = to_ml && compose->ml_post;
2886 default_reply_to = msginfo->folder &&
2887 msginfo->folder->prefs->enable_default_reply_to;
2889 if (compose->account->protocol != A_NNTP) {
2890 if (reply_to_ml && !default_reply_to) {
2892 gboolean is_subscr = is_subscription(compose->ml_post,
2895 /* normal answer to ml post with a reply-to */
2896 compose_entry_append(compose,
2899 if (compose->replyto
2900 && !same_address(compose->ml_post, compose->replyto))
2901 compose_entry_append(compose,
2905 /* answer to subscription confirmation */
2906 if (compose->replyto)
2907 compose_entry_append(compose,
2910 else if (msginfo->from)
2911 compose_entry_append(compose,
2916 else if (!(to_all || to_sender) && default_reply_to) {
2917 compose_entry_append(compose,
2918 msginfo->folder->prefs->default_reply_to,
2920 compose_entry_mark_default_to(compose,
2921 msginfo->folder->prefs->default_reply_to);
2926 Xstrdup_a(tmp1, msginfo->from, return);
2927 extract_address(tmp1);
2928 if (to_all || to_sender ||
2929 !account_find_from_address(tmp1, FALSE))
2930 compose_entry_append(compose,
2931 (compose->replyto && !to_sender)
2932 ? compose->replyto :
2933 msginfo->from ? msginfo->from : "",
2935 else if (!to_all && !to_sender) {
2936 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
2937 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
2938 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
2939 if (compose->replyto) {
2940 compose_entry_append(compose,
2944 compose_entry_append(compose,
2945 msginfo->from ? msginfo->from : "",
2949 /* replying to own mail, use original recp */
2950 compose_entry_append(compose,
2951 msginfo->to ? msginfo->to : "",
2953 compose_entry_append(compose,
2954 msginfo->cc ? msginfo->cc : "",
2960 if (to_sender || (compose->followup_to &&
2961 !strncmp(compose->followup_to, "poster", 6)))
2962 compose_entry_append
2964 (compose->replyto ? compose->replyto :
2965 msginfo->from ? msginfo->from : ""),
2968 else if (followup_and_reply_to || to_all) {
2969 compose_entry_append
2971 (compose->replyto ? compose->replyto :
2972 msginfo->from ? msginfo->from : ""),
2975 compose_entry_append
2977 compose->followup_to ? compose->followup_to :
2978 compose->newsgroups ? compose->newsgroups : "",
2979 COMPOSE_NEWSGROUPS);
2982 compose_entry_append
2984 compose->followup_to ? compose->followup_to :
2985 compose->newsgroups ? compose->newsgroups : "",
2986 COMPOSE_NEWSGROUPS);
2989 if (msginfo->subject && *msginfo->subject) {
2993 buf = p = g_strdup(msginfo->subject);
2994 p += subject_get_prefix_length(p);
2995 memmove(buf, p, strlen(p) + 1);
2997 buf2 = g_strdup_printf("Re: %s", buf);
2998 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3003 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3005 if (to_ml && compose->ml_post) return;
3006 if (!to_all || compose->account->protocol == A_NNTP) return;
3008 if (compose->replyto) {
3009 Xstrdup_a(replyto, compose->replyto, return);
3010 extract_address(replyto);
3012 if (msginfo->from) {
3013 Xstrdup_a(from, msginfo->from, return);
3014 extract_address(from);
3017 if (replyto && from)
3018 cc_list = address_list_append_with_comments(cc_list, from);
3019 if (to_all && msginfo->folder &&
3020 msginfo->folder->prefs->enable_default_reply_to)
3021 cc_list = address_list_append_with_comments(cc_list,
3022 msginfo->folder->prefs->default_reply_to);
3023 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3024 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3026 to_table = g_hash_table_new(g_str_hash, g_str_equal);
3028 g_hash_table_insert(to_table, g_utf8_strdown(replyto, -1), GINT_TO_POINTER(1));
3029 if (compose->account) {
3030 g_hash_table_insert(to_table, g_utf8_strdown(compose->account->address, -1),
3031 GINT_TO_POINTER(1));
3033 /* remove address on To: and that of current account */
3034 for (cur = cc_list; cur != NULL; ) {
3035 GSList *next = cur->next;
3038 addr = g_utf8_strdown(cur->data, -1);
3039 extract_address(addr);
3041 if (GPOINTER_TO_INT(g_hash_table_lookup(to_table, addr)) == 1)
3042 cc_list = g_slist_remove(cc_list, cur->data);
3044 g_hash_table_insert(to_table, addr, GINT_TO_POINTER(1));
3048 hash_free_strings(to_table);
3049 g_hash_table_destroy(to_table);
3052 for (cur = cc_list; cur != NULL; cur = cur->next)
3053 compose_entry_append(compose, (gchar *)cur->data,
3055 slist_free_strings(cc_list);
3056 g_slist_free(cc_list);
3061 #define SET_ENTRY(entry, str) \
3064 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3067 #define SET_ADDRESS(type, str) \
3070 compose_entry_append(compose, str, type); \
3073 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3075 g_return_if_fail(msginfo != NULL);
3077 SET_ENTRY(subject_entry, msginfo->subject);
3078 SET_ENTRY(from_name, msginfo->from);
3079 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3080 SET_ADDRESS(COMPOSE_CC, compose->cc);
3081 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3082 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3083 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3084 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3086 compose_update_priority_menu_item(compose);
3087 compose_update_privacy_system_menu_item(compose, FALSE);
3088 compose_show_first_last_header(compose, TRUE);
3094 static void compose_insert_sig(Compose *compose, gboolean replace)
3096 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3097 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3099 GtkTextIter iter, iter_end;
3101 gboolean prev_autowrap;
3102 gboolean found = FALSE;
3103 gboolean exists = FALSE;
3105 g_return_if_fail(compose->account != NULL);
3109 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3110 G_CALLBACK(compose_changed_cb),
3113 mark = gtk_text_buffer_get_insert(buffer);
3114 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3115 cur_pos = gtk_text_iter_get_offset (&iter);
3117 gtk_text_buffer_get_end_iter(buffer, &iter);
3119 exists = (compose->sig_str != NULL);
3122 GtkTextIter first_iter, start_iter, end_iter;
3124 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3126 if (!exists || compose->sig_str[0] == '\0')
3129 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3130 compose->signature_tag);
3133 /* include previous \n\n */
3134 gtk_text_iter_backward_chars(&first_iter, 2);
3135 start_iter = first_iter;
3136 end_iter = first_iter;
3138 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3139 compose->signature_tag);
3140 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3141 compose->signature_tag);
3143 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3149 g_free(compose->sig_str);
3150 compose->sig_str = compose_get_signature_str(compose);
3152 cur_pos = gtk_text_iter_get_offset(&iter);
3154 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3155 g_free(compose->sig_str);
3156 compose->sig_str = NULL;
3158 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3160 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3161 gtk_text_iter_forward_chars(&iter, 2);
3162 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3163 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3165 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3166 cur_pos = gtk_text_buffer_get_char_count (buffer);
3168 /* put the cursor where it should be
3169 * either where the quote_fmt says, either before the signature */
3170 if (compose->set_cursor_pos < 0)
3171 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3173 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3174 compose->set_cursor_pos);
3176 gtk_text_buffer_place_cursor(buffer, &iter);
3177 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3178 G_CALLBACK(compose_changed_cb),
3184 static gchar *compose_get_signature_str(Compose *compose)
3186 gchar *sig_body = NULL;
3187 gchar *sig_str = NULL;
3188 gchar *utf8_sig_str = NULL;
3190 g_return_val_if_fail(compose->account != NULL, NULL);
3192 if (!compose->account->sig_path)
3195 if (compose->account->sig_type == SIG_FILE) {
3196 if (!is_file_or_fifo_exist(compose->account->sig_path)) {
3197 g_warning("can't open signature file: %s\n",
3198 compose->account->sig_path);
3203 if (compose->account->sig_type == SIG_COMMAND)
3204 sig_body = get_command_output(compose->account->sig_path);
3208 tmp = file_read_to_str(compose->account->sig_path);
3211 sig_body = normalize_newlines(tmp);
3215 if (compose->account->sig_sep) {
3216 sig_str = g_strconcat("\n\n", compose->account->sig_sep, "\n", sig_body,
3220 sig_str = g_strconcat("\n\n", sig_body, NULL);
3223 if (g_utf8_validate(sig_str, -1, NULL) == TRUE)
3224 utf8_sig_str = sig_str;
3226 utf8_sig_str = conv_codeset_strdup
3227 (sig_str, conv_get_locale_charset_str_no_utf8(),
3233 return utf8_sig_str;
3236 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3239 GtkTextBuffer *buffer;
3242 const gchar *cur_encoding;
3243 gchar buf[BUFFSIZE];
3246 gboolean prev_autowrap;
3247 gboolean badtxt = FALSE;
3249 g_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3251 if ((fp = g_fopen(file, "rb")) == NULL) {
3252 FILE_OP_ERROR(file, "fopen");
3253 return COMPOSE_INSERT_READ_ERROR;
3256 prev_autowrap = compose->autowrap;
3257 compose->autowrap = FALSE;
3259 text = GTK_TEXT_VIEW(compose->text);
3260 buffer = gtk_text_view_get_buffer(text);
3261 mark = gtk_text_buffer_get_insert(buffer);
3262 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3264 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3265 G_CALLBACK(text_inserted),
3268 cur_encoding = conv_get_locale_charset_str_no_utf8();
3270 while (fgets(buf, sizeof(buf), fp) != NULL) {
3273 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3274 str = g_strdup(buf);
3276 str = conv_codeset_strdup
3277 (buf, cur_encoding, CS_INTERNAL);
3280 /* strip <CR> if DOS/Windows file,
3281 replace <CR> with <LF> if Macintosh file. */
3284 if (len > 0 && str[len - 1] != '\n') {
3286 if (str[len] == '\r') str[len] = '\n';
3289 gtk_text_buffer_insert(buffer, &iter, str, -1);
3293 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3294 G_CALLBACK(text_inserted),
3296 compose->autowrap = prev_autowrap;
3297 if (compose->autowrap)
3298 compose_wrap_all(compose);
3303 return COMPOSE_INSERT_INVALID_CHARACTER;
3305 return COMPOSE_INSERT_SUCCESS;
3308 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3309 const gchar *filename,
3310 const gchar *content_type)
3318 GtkListStore *store;
3320 gboolean has_binary = FALSE;
3322 if (!is_file_exist(file)) {
3323 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3324 gboolean result = FALSE;
3325 if (file_from_uri && is_file_exist(file_from_uri)) {
3326 result = compose_attach_append(
3327 compose, file_from_uri,
3331 g_free(file_from_uri);
3334 alertpanel_error("File %s doesn't exist\n", filename);
3337 if ((size = get_file_size(file)) < 0) {
3338 alertpanel_error("Can't get file size of %s\n", filename);
3342 alertpanel_error(_("File %s is empty."), filename);
3345 if ((fp = g_fopen(file, "rb")) == NULL) {
3346 alertpanel_error(_("Can't read %s."), filename);
3351 ainfo = g_new0(AttachInfo, 1);
3352 auto_ainfo = g_auto_pointer_new_with_free
3353 (ainfo, (GFreeFunc) compose_attach_info_free);
3354 ainfo->file = g_strdup(file);
3357 ainfo->content_type = g_strdup(content_type);
3358 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3360 MsgFlags flags = {0, 0};
3362 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3363 ainfo->encoding = ENC_7BIT;
3365 ainfo->encoding = ENC_8BIT;
3367 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3368 if (msginfo && msginfo->subject)
3369 name = g_strdup(msginfo->subject);
3371 name = g_path_get_basename(filename ? filename : file);
3373 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3375 procmsg_msginfo_free(msginfo);
3377 if (!g_ascii_strncasecmp(content_type, "text", 4))
3378 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3380 ainfo->encoding = ENC_BASE64;
3381 name = g_path_get_basename(filename ? filename : file);
3382 ainfo->name = g_strdup(name);
3386 ainfo->content_type = procmime_get_mime_type(file);
3387 if (!ainfo->content_type) {
3388 ainfo->content_type =
3389 g_strdup("application/octet-stream");
3390 ainfo->encoding = ENC_BASE64;
3391 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text", 4))
3393 procmime_get_encoding_for_text_file(file, &has_binary);
3395 ainfo->encoding = ENC_BASE64;
3396 name = g_path_get_basename(filename ? filename : file);
3397 ainfo->name = g_strdup(name);
3401 if (ainfo->name != NULL
3402 && !strcmp(ainfo->name, ".")) {
3403 g_free(ainfo->name);
3407 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3408 g_free(ainfo->content_type);
3409 ainfo->content_type = g_strdup("application/octet-stream");
3413 size_text = to_human_readable(size);
3415 store = GTK_LIST_STORE(gtk_tree_view_get_model
3416 (GTK_TREE_VIEW(compose->attach_clist)));
3418 gtk_list_store_append(store, &iter);
3419 gtk_list_store_set(store, &iter,
3420 COL_MIMETYPE, ainfo->content_type,
3421 COL_SIZE, size_text,
3422 COL_NAME, ainfo->name,
3424 COL_AUTODATA, auto_ainfo,
3427 g_auto_pointer_free(auto_ainfo);
3428 compose_attach_update_label(compose);
3432 static void compose_use_signing(Compose *compose, gboolean use_signing)
3434 GtkItemFactory *ifactory;
3435 GtkWidget *menuitem = NULL;
3437 compose->use_signing = use_signing;
3438 ifactory = gtk_item_factory_from_widget(compose->menubar);
3439 menuitem = gtk_item_factory_get_item
3440 (ifactory, "/Options/Sign");
3441 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
3445 static void compose_use_encryption(Compose *compose, gboolean use_encryption)
3447 GtkItemFactory *ifactory;
3448 GtkWidget *menuitem = NULL;
3450 compose->use_encryption = use_encryption;
3451 ifactory = gtk_item_factory_from_widget(compose->menubar);
3452 menuitem = gtk_item_factory_get_item
3453 (ifactory, "/Options/Encrypt");
3455 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
3459 #define NEXT_PART_NOT_CHILD(info) \
3461 node = info->node; \
3462 while (node->children) \
3463 node = g_node_last_child(node); \
3464 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3467 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3471 MimeInfo *firsttext = NULL;
3472 MimeInfo *encrypted = NULL;
3475 const gchar *partname = NULL;
3477 mimeinfo = procmime_scan_message(msginfo);
3478 if (!mimeinfo) return;
3480 if (mimeinfo->node->children == NULL) {
3481 procmime_mimeinfo_free_all(mimeinfo);
3485 /* find first content part */
3486 child = (MimeInfo *) mimeinfo->node->children->data;
3487 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3488 child = (MimeInfo *)child->node->children->data;
3490 if (child->type == MIMETYPE_TEXT) {
3492 debug_print("First text part found\n");
3493 } else if (compose->mode == COMPOSE_REEDIT &&
3494 child->type == MIMETYPE_APPLICATION &&
3495 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3496 encrypted = (MimeInfo *)child->node->parent->data;
3499 child = (MimeInfo *) mimeinfo->node->children->data;
3500 while (child != NULL) {
3503 if (child == encrypted) {
3504 /* skip this part of tree */
3505 NEXT_PART_NOT_CHILD(child);
3509 if (child->type == MIMETYPE_MULTIPART) {
3510 /* get the actual content */
3511 child = procmime_mimeinfo_next(child);
3515 if (child == firsttext) {
3516 child = procmime_mimeinfo_next(child);
3520 outfile = procmime_get_tmp_file_name(child);
3521 if ((err = procmime_get_part(outfile, child)) < 0)
3522 g_warning("Can't get the part of multipart message. (%s)", strerror(-err));
3524 gchar *content_type;
3526 content_type = procmime_get_content_type_str(child->type, child->subtype);
3528 /* if we meet a pgp signature, we don't attach it, but
3529 * we force signing. */
3530 if ((strcmp(content_type, "application/pgp-signature") &&
3531 strcmp(content_type, "application/pkcs7-signature") &&
3532 strcmp(content_type, "application/x-pkcs7-signature"))
3533 || compose->mode == COMPOSE_REDIRECT) {
3534 partname = procmime_mimeinfo_get_parameter(child, "filename");
3535 if (partname == NULL)
3536 partname = procmime_mimeinfo_get_parameter(child, "name");
3537 if (partname == NULL)
3539 compose_attach_append(compose, outfile,
3540 partname, content_type);
3542 compose_force_signing(compose, compose->account);
3544 g_free(content_type);
3547 NEXT_PART_NOT_CHILD(child);
3549 procmime_mimeinfo_free_all(mimeinfo);
3552 #undef NEXT_PART_NOT_CHILD
3557 WAIT_FOR_INDENT_CHAR,
3558 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3561 /* return indent length, we allow:
3562 indent characters followed by indent characters or spaces/tabs,
3563 alphabets and numbers immediately followed by indent characters,
3564 and the repeating sequences of the above
3565 If quote ends with multiple spaces, only the first one is included. */
3566 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3567 const GtkTextIter *start, gint *len)
3569 GtkTextIter iter = *start;
3573 IndentState state = WAIT_FOR_INDENT_CHAR;
3576 gint alnum_count = 0;
3577 gint space_count = 0;
3580 if (prefs_common.quote_chars == NULL) {
3584 while (!gtk_text_iter_ends_line(&iter)) {
3585 wc = gtk_text_iter_get_char(&iter);
3586 if (g_unichar_iswide(wc))
3588 clen = g_unichar_to_utf8(wc, ch);
3592 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
3593 is_space = g_unichar_isspace(wc);
3595 if (state == WAIT_FOR_INDENT_CHAR) {
3596 if (!is_indent && !g_unichar_isalnum(wc))
3599 quote_len += alnum_count + space_count + 1;
3600 alnum_count = space_count = 0;
3601 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
3604 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
3605 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
3609 else if (is_indent) {
3610 quote_len += alnum_count + space_count + 1;
3611 alnum_count = space_count = 0;
3614 state = WAIT_FOR_INDENT_CHAR;
3618 gtk_text_iter_forward_char(&iter);
3621 if (quote_len > 0 && space_count > 0)
3627 if (quote_len > 0) {
3629 gtk_text_iter_forward_chars(&iter, quote_len);
3630 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
3636 /* return TRUE if the line is itemized */
3637 static gboolean compose_is_itemized(GtkTextBuffer *buffer,
3638 const GtkTextIter *start)
3640 GtkTextIter iter = *start;
3645 if (gtk_text_iter_ends_line(&iter))
3649 wc = gtk_text_iter_get_char(&iter);
3650 if (!g_unichar_isspace(wc))
3652 gtk_text_iter_forward_char(&iter);
3653 if (gtk_text_iter_ends_line(&iter))
3657 clen = g_unichar_to_utf8(wc, ch);
3661 if (!strchr("*-+", ch[0]))
3664 gtk_text_iter_forward_char(&iter);
3665 if (gtk_text_iter_ends_line(&iter))
3667 wc = gtk_text_iter_get_char(&iter);
3668 if (g_unichar_isspace(wc))
3674 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
3675 const GtkTextIter *start,
3676 GtkTextIter *break_pos,
3680 GtkTextIter iter = *start, line_end = *start;
3681 PangoLogAttr *attrs;
3688 gboolean can_break = FALSE;
3689 gboolean do_break = FALSE;
3690 gboolean was_white = FALSE;
3691 gboolean prev_dont_break = FALSE;
3693 gtk_text_iter_forward_to_line_end(&line_end);
3694 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
3695 len = g_utf8_strlen(str, -1);
3699 g_warning("compose_get_line_break_pos: len = 0!\n");
3703 /* g_print("breaking line: %d: %s (len = %d)\n",
3704 gtk_text_iter_get_line(&iter), str, len); */
3706 attrs = g_new(PangoLogAttr, len + 1);
3708 pango_default_break(str, -1, NULL, attrs, len + 1);
3712 /* skip quote and leading spaces */
3713 for (i = 0; *p != '\0' && i < len; i++) {
3716 wc = g_utf8_get_char(p);
3717 if (i >= quote_len && !g_unichar_isspace(wc))
3719 if (g_unichar_iswide(wc))
3721 else if (*p == '\t')
3725 p = g_utf8_next_char(p);
3728 for (; *p != '\0' && i < len; i++) {
3729 PangoLogAttr *attr = attrs + i;
3733 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
3736 was_white = attr->is_white;
3738 /* don't wrap URI */
3739 if ((uri_len = get_uri_len(p)) > 0) {
3741 if (pos > 0 && col > max_col) {
3751 wc = g_utf8_get_char(p);
3752 if (g_unichar_iswide(wc)) {
3754 if (prev_dont_break && can_break && attr->is_line_break)
3756 } else if (*p == '\t')
3760 if (pos > 0 && col > max_col) {
3765 if (*p == '-' || *p == '/')
3766 prev_dont_break = TRUE;
3768 prev_dont_break = FALSE;
3770 p = g_utf8_next_char(p);
3774 debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col);
3779 *break_pos = *start;
3780 gtk_text_iter_set_line_offset(break_pos, pos);
3785 static gboolean compose_join_next_line(Compose *compose,
3786 GtkTextBuffer *buffer,
3788 const gchar *quote_str)
3790 GtkTextIter iter_ = *iter, cur, prev, next, end;
3791 PangoLogAttr attrs[3];
3793 gchar *next_quote_str;
3796 gboolean keep_cursor = FALSE;
3798 if (!gtk_text_iter_forward_line(&iter_) ||
3799 gtk_text_iter_ends_line(&iter_)) {
3802 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
3804 if ((quote_str || next_quote_str) &&
3805 strcmp2(quote_str, next_quote_str) != 0) {
3806 g_free(next_quote_str);
3809 g_free(next_quote_str);
3812 if (quote_len > 0) {
3813 gtk_text_iter_forward_chars(&end, quote_len);
3814 if (gtk_text_iter_ends_line(&end)) {
3819 /* don't join itemized lines */
3820 if (compose_is_itemized(buffer, &end)) {
3824 /* don't join signature separator */
3825 if (compose_is_sig_separator(compose, buffer, &iter_)) {
3828 /* delete quote str */
3830 gtk_text_buffer_delete(buffer, &iter_, &end);
3832 /* don't join line breaks put by the user */
3834 gtk_text_iter_backward_char(&cur);
3835 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
3836 gtk_text_iter_forward_char(&cur);
3840 gtk_text_iter_forward_char(&cur);
3841 /* delete linebreak and extra spaces */
3842 while (gtk_text_iter_backward_char(&cur)) {
3843 wc1 = gtk_text_iter_get_char(&cur);
3844 if (!g_unichar_isspace(wc1))
3849 while (!gtk_text_iter_ends_line(&cur)) {
3850 wc1 = gtk_text_iter_get_char(&cur);
3851 if (!g_unichar_isspace(wc1))
3853 gtk_text_iter_forward_char(&cur);
3856 if (!gtk_text_iter_equal(&prev, &next)) {
3859 mark = gtk_text_buffer_get_insert(buffer);
3860 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
3861 if (gtk_text_iter_equal(&prev, &cur))
3863 gtk_text_buffer_delete(buffer, &prev, &next);
3867 /* insert space if required */
3868 gtk_text_iter_backward_char(&prev);
3869 wc1 = gtk_text_iter_get_char(&prev);
3870 wc2 = gtk_text_iter_get_char(&next);
3871 gtk_text_iter_forward_char(&next);
3872 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
3873 pango_default_break(str, -1, NULL, attrs, 3);
3874 if (!attrs[1].is_line_break ||
3875 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
3876 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
3878 gtk_text_iter_backward_char(&iter_);
3879 gtk_text_buffer_place_cursor(buffer, &iter_);
3888 #define ADD_TXT_POS(bp_, ep_, pti_) \
3889 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
3890 last = last->next; \
3891 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
3892 last->next = NULL; \
3894 g_warning("alloc error scanning URIs\n"); \
3897 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
3899 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3900 GtkTextBuffer *buffer;
3901 GtkTextIter iter, break_pos, end_of_line;
3902 gchar *quote_str = NULL;
3904 gboolean wrap_quote = prefs_common.linewrap_quote;
3905 gboolean prev_autowrap = compose->autowrap;
3906 gint startq_offset = -1, noq_offset = -1;
3907 gint uri_start = -1, uri_stop = -1;
3908 gint nouri_start = -1, nouri_stop = -1;
3909 gint num_blocks = 0;
3910 gint quotelevel = -1;
3911 gboolean modified = force;
3912 gboolean removed = FALSE;
3913 gboolean modified_before_remove = FALSE;
3915 gboolean start = TRUE;
3920 if (compose->draft_timeout_tag == -2) {
3924 compose->autowrap = FALSE;
3926 buffer = gtk_text_view_get_buffer(text);
3927 undo_wrapping(compose->undostruct, TRUE);
3932 mark = gtk_text_buffer_get_insert(buffer);
3933 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3937 if (compose->draft_timeout_tag == -2) {
3938 if (gtk_text_iter_ends_line(&iter)) {
3939 while (gtk_text_iter_ends_line(&iter) &&
3940 gtk_text_iter_forward_line(&iter))
3943 while (gtk_text_iter_backward_line(&iter)) {
3944 if (gtk_text_iter_ends_line(&iter)) {
3945 gtk_text_iter_forward_line(&iter);
3951 /* move to line start */
3952 gtk_text_iter_set_line_offset(&iter, 0);
3954 /* go until paragraph end (empty line) */
3955 while (start || !gtk_text_iter_ends_line(&iter)) {
3956 gchar *scanpos = NULL;
3957 /* parse table - in order of priority */
3959 const gchar *needle; /* token */
3961 /* token search function */
3962 gchar *(*search) (const gchar *haystack,
3963 const gchar *needle);
3964 /* part parsing function */
3965 gboolean (*parse) (const gchar *start,
3966 const gchar *scanpos,
3970 /* part to URI function */
3971 gchar *(*build_uri) (const gchar *bp,
3975 static struct table parser[] = {
3976 {"http://", strcasestr, get_uri_part, make_uri_string},
3977 {"https://", strcasestr, get_uri_part, make_uri_string},
3978 {"ftp://", strcasestr, get_uri_part, make_uri_string},
3979 {"sftp://", strcasestr, get_uri_part, make_uri_string},
3980 {"www.", strcasestr, get_uri_part, make_http_string},
3981 {"mailto:", strcasestr, get_uri_part, make_uri_string},
3982 {"@", strcasestr, get_email_part, make_email_string}
3984 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
3985 gint last_index = PARSE_ELEMS;
3987 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
3991 if (!prev_autowrap && num_blocks == 0) {
3993 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3994 G_CALLBACK(text_inserted),
3997 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4000 uri_start = uri_stop = -1;
4002 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
4005 debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str);
4006 if (startq_offset == -1)
4007 startq_offset = gtk_text_iter_get_offset(&iter);
4008 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4009 if (quotelevel > 2) {
4010 /* recycle colors */
4011 if (prefs_common.recycle_quote_colors)
4020 if (startq_offset == -1)
4021 noq_offset = gtk_text_iter_get_offset(&iter);
4025 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4028 if (gtk_text_iter_ends_line(&iter)) {
4030 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4031 prefs_common.linewrap_len,
4033 GtkTextIter prev, next, cur;
4035 if (prev_autowrap != FALSE || force) {
4036 compose->automatic_break = TRUE;
4038 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4039 compose->automatic_break = FALSE;
4040 } else if (quote_str && wrap_quote) {
4041 compose->automatic_break = TRUE;
4043 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4044 compose->automatic_break = FALSE;
4047 /* remove trailing spaces */
4049 gtk_text_iter_backward_char(&cur);
4051 while (!gtk_text_iter_starts_line(&cur)) {
4054 gtk_text_iter_backward_char(&cur);
4055 wc = gtk_text_iter_get_char(&cur);
4056 if (!g_unichar_isspace(wc))
4060 if (!gtk_text_iter_equal(&prev, &next)) {
4061 gtk_text_buffer_delete(buffer, &prev, &next);
4063 gtk_text_iter_forward_char(&break_pos);
4067 gtk_text_buffer_insert(buffer, &break_pos,
4071 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4073 /* move iter to current line start */
4074 gtk_text_iter_set_line_offset(&iter, 0);
4081 /* move iter to next line start */
4087 if (!prev_autowrap && num_blocks > 0) {
4089 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4090 G_CALLBACK(text_inserted),
4094 while (!gtk_text_iter_ends_line(&end_of_line)) {
4095 gtk_text_iter_forward_char(&end_of_line);
4097 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4099 nouri_start = gtk_text_iter_get_offset(&iter);
4100 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4102 walk_pos = gtk_text_iter_get_offset(&iter);
4103 /* FIXME: this looks phony. scanning for anything in the parse table */
4104 for (n = 0; n < PARSE_ELEMS; n++) {
4107 tmp = parser[n].search(walk, parser[n].needle);
4109 if (scanpos == NULL || tmp < scanpos) {
4118 /* check if URI can be parsed */
4119 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4120 (const gchar **)&ep, FALSE)
4121 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4125 strlen(parser[last_index].needle);
4128 uri_start = walk_pos + (bp - o_walk);
4129 uri_stop = walk_pos + (ep - o_walk);
4133 gtk_text_iter_forward_line(&iter);
4136 if (startq_offset != -1) {
4137 GtkTextIter startquote, endquote;
4138 gtk_text_buffer_get_iter_at_offset(
4139 buffer, &startquote, startq_offset);
4142 switch (quotelevel) {
4144 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4145 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4146 gtk_text_buffer_apply_tag_by_name(
4147 buffer, "quote0", &startquote, &endquote);
4148 gtk_text_buffer_remove_tag_by_name(
4149 buffer, "quote1", &startquote, &endquote);
4150 gtk_text_buffer_remove_tag_by_name(
4151 buffer, "quote2", &startquote, &endquote);
4156 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4157 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4158 gtk_text_buffer_apply_tag_by_name(
4159 buffer, "quote1", &startquote, &endquote);
4160 gtk_text_buffer_remove_tag_by_name(
4161 buffer, "quote0", &startquote, &endquote);
4162 gtk_text_buffer_remove_tag_by_name(
4163 buffer, "quote2", &startquote, &endquote);
4168 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4169 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4170 gtk_text_buffer_apply_tag_by_name(
4171 buffer, "quote2", &startquote, &endquote);
4172 gtk_text_buffer_remove_tag_by_name(
4173 buffer, "quote0", &startquote, &endquote);
4174 gtk_text_buffer_remove_tag_by_name(
4175 buffer, "quote1", &startquote, &endquote);
4181 } else if (noq_offset != -1) {
4182 GtkTextIter startnoquote, endnoquote;
4183 gtk_text_buffer_get_iter_at_offset(
4184 buffer, &startnoquote, noq_offset);
4187 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4188 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4189 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4190 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4191 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4192 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4193 gtk_text_buffer_remove_tag_by_name(
4194 buffer, "quote0", &startnoquote, &endnoquote);
4195 gtk_text_buffer_remove_tag_by_name(
4196 buffer, "quote1", &startnoquote, &endnoquote);
4197 gtk_text_buffer_remove_tag_by_name(
4198 buffer, "quote2", &startnoquote, &endnoquote);
4204 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4205 GtkTextIter nouri_start_iter, nouri_end_iter;
4206 gtk_text_buffer_get_iter_at_offset(
4207 buffer, &nouri_start_iter, nouri_start);
4208 gtk_text_buffer_get_iter_at_offset(
4209 buffer, &nouri_end_iter, nouri_stop);
4210 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4211 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4212 gtk_text_buffer_remove_tag_by_name(
4213 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4214 modified_before_remove = modified;
4219 if (uri_start >= 0 && uri_stop > 0) {
4220 GtkTextIter uri_start_iter, uri_end_iter, back;
4221 gtk_text_buffer_get_iter_at_offset(
4222 buffer, &uri_start_iter, uri_start);
4223 gtk_text_buffer_get_iter_at_offset(
4224 buffer, &uri_end_iter, uri_stop);
4225 back = uri_end_iter;
4226 gtk_text_iter_backward_char(&back);
4227 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4228 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4229 gtk_text_buffer_apply_tag_by_name(
4230 buffer, "link", &uri_start_iter, &uri_end_iter);
4232 if (removed && !modified_before_remove) {
4238 debug_print("not modified, out after %d lines\n", lines);
4243 debug_print("modified, out after %d lines\n", lines);
4247 undo_wrapping(compose->undostruct, FALSE);
4248 compose->autowrap = prev_autowrap;
4253 void compose_action_cb(void *data)
4255 Compose *compose = (Compose *)data;
4256 compose_wrap_all(compose);
4259 static void compose_wrap_all(Compose *compose)
4261 compose_wrap_all_full(compose, FALSE);
4264 static void compose_wrap_all_full(Compose *compose, gboolean force)
4266 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4267 GtkTextBuffer *buffer;
4269 gboolean modified = TRUE;
4271 buffer = gtk_text_view_get_buffer(text);
4273 gtk_text_buffer_get_start_iter(buffer, &iter);
4274 while (!gtk_text_iter_is_end(&iter) && modified)
4275 modified = compose_beautify_paragraph(compose, &iter, force);
4279 static void compose_set_title(Compose *compose)
4285 edited = compose->modified ? _(" [Edited]") : "";
4287 subject = gtk_editable_get_chars(
4288 GTK_EDITABLE(compose->subject_entry), 0, -1);
4291 if (subject && strlen(subject))
4292 str = g_strdup_printf(_("%s - Compose message%s"),
4295 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4297 str = g_strdup(_("Compose message"));
4300 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4306 * compose_current_mail_account:
4308 * Find a current mail account (the currently selected account, or the
4309 * default account, if a news account is currently selected). If a
4310 * mail account cannot be found, display an error message.
4312 * Return value: Mail account, or NULL if not found.
4314 static PrefsAccount *
4315 compose_current_mail_account(void)
4319 if (cur_account && cur_account->protocol != A_NNTP)
4322 ac = account_get_default();
4323 if (!ac || ac->protocol == A_NNTP) {
4324 alertpanel_error(_("Account for sending mail is not specified.\n"
4325 "Please select a mail account before sending."));
4332 #define QUOTE_IF_REQUIRED(out, str) \
4334 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4338 len = strlen(str) + 3; \
4339 if ((__tmp = alloca(len)) == NULL) { \
4340 g_warning("can't allocate memory\n"); \
4341 g_string_free(header, TRUE); \
4344 g_snprintf(__tmp, len, "\"%s\"", str); \
4349 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4350 g_warning("can't allocate memory\n"); \
4351 g_string_free(header, TRUE); \
4354 strcpy(__tmp, str); \
4360 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4362 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4366 len = strlen(str) + 3; \
4367 if ((__tmp = alloca(len)) == NULL) { \
4368 g_warning("can't allocate memory\n"); \
4371 g_snprintf(__tmp, len, "\"%s\"", str); \
4376 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4377 g_warning("can't allocate memory\n"); \
4380 strcpy(__tmp, str); \
4386 static void compose_select_account(Compose *compose, PrefsAccount *account,
4389 GtkItemFactory *ifactory;
4392 g_return_if_fail(account != NULL);
4394 compose->account = account;
4396 if (account->name && *account->name) {
4398 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4399 from = g_strdup_printf("%s <%s>",
4400 buf, account->address);
4401 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4403 from = g_strdup_printf("<%s>",
4405 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4410 compose_set_title(compose);
4412 ifactory = gtk_item_factory_from_widget(compose->menubar);
4414 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4415 menu_set_active(ifactory, "/Options/Sign", TRUE);
4417 menu_set_active(ifactory, "/Options/Sign", FALSE);
4418 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4419 menu_set_active(ifactory, "/Options/Encrypt", TRUE);
4421 menu_set_active(ifactory, "/Options/Encrypt", FALSE);
4423 activate_privacy_system(compose, account, FALSE);
4425 if (!init && compose->mode != COMPOSE_REDIRECT) {
4426 undo_block(compose->undostruct);
4427 compose_insert_sig(compose, TRUE);
4428 undo_unblock(compose->undostruct);
4432 /* use account's dict info if set */
4433 if (compose->gtkaspell) {
4434 if (account->enable_default_dictionary)
4435 gtkaspell_change_dict(compose->gtkaspell,
4436 account->default_dictionary, FALSE);
4437 if (account->enable_default_alt_dictionary)
4438 gtkaspell_change_alt_dict(compose->gtkaspell,
4439 account->default_alt_dictionary);
4440 if (account->enable_default_dictionary
4441 || account->enable_default_alt_dictionary)
4442 compose_spell_menu_changed(compose);
4447 gboolean compose_check_for_valid_recipient(Compose *compose) {
4448 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
4449 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
4450 gboolean recipient_found = FALSE;
4454 /* free to and newsgroup list */
4455 slist_free_strings(compose->to_list);
4456 g_slist_free(compose->to_list);
4457 compose->to_list = NULL;
4459 slist_free_strings(compose->newsgroup_list);
4460 g_slist_free(compose->newsgroup_list);
4461 compose->newsgroup_list = NULL;
4463 /* search header entries for to and newsgroup entries */
4464 for (list = compose->header_list; list; list = list->next) {
4467 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_BIN(((ComposeHeaderEntry *)list->data)->combo)->child), 0, -1);
4468 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4471 if (entry[0] != '\0') {
4472 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
4473 if (!strcmp(header, prefs_common_translated_header_name(*strptr))) {
4474 compose->to_list = address_list_append(compose->to_list, entry);
4475 recipient_found = TRUE;
4478 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
4479 if (!strcmp(header, prefs_common_translated_header_name(*strptr))) {
4480 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
4481 recipient_found = TRUE;
4488 return recipient_found;
4491 static gboolean compose_check_for_set_recipients(Compose *compose)
4493 if (compose->account->set_autocc && compose->account->auto_cc) {
4494 gboolean found_other = FALSE;
4496 /* search header entries for to and newsgroup entries */
4497 for (list = compose->header_list; list; list = list->next) {
4500 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4501 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_BIN(((ComposeHeaderEntry *)list->data)->combo)->child), 0, -1);
4504 if (strcmp(entry, compose->account->auto_cc)
4505 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
4515 if (compose->batch) {
4516 gtk_widget_show_all(compose->window);
4518 aval = alertpanel(_("Send"),
4519 _("The only recipient is the default CC address. Send anyway?"),
4520 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4521 if (aval != G_ALERTALTERNATE)
4525 if (compose->account->set_autobcc && compose->account->auto_bcc) {
4526 gboolean found_other = FALSE;
4528 /* search header entries for to and newsgroup entries */
4529 for (list = compose->header_list; list; list = list->next) {
4532 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4533 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_BIN(((ComposeHeaderEntry *)list->data)->combo)->child), 0, -1);
4536 if (strcmp(entry, compose->account->auto_bcc)
4537 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
4547 if (compose->batch) {
4548 gtk_widget_show_all(compose->window);
4550 aval = alertpanel(_("Send"),
4551 _("The only recipient is the default BCC address. Send anyway?"),
4552 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4553 if (aval != G_ALERTALTERNATE)
4560 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
4564 if (compose_check_for_valid_recipient(compose) == FALSE) {
4565 if (compose->batch) {
4566 gtk_widget_show_all(compose->window);
4568 alertpanel_error(_("Recipient is not specified."));
4572 if (compose_check_for_set_recipients(compose) == FALSE) {
4576 if (!compose->batch) {
4577 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
4578 if (*str == '\0' && check_everything == TRUE &&
4579 compose->mode != COMPOSE_REDIRECT) {
4581 gchar *button_label;
4584 if (compose->sending)
4585 button_label = _("+_Send");
4587 button_label = _("+_Queue");
4588 message = g_strdup_printf(_("Subject is empty. %s"),
4589 compose->sending?_("Send it anyway?"):
4590 _("Queue it anyway?"));
4592 aval = alertpanel(compose->sending?_("Send"):_("Send later"), message,
4593 GTK_STOCK_CANCEL, button_label, NULL);
4595 if (aval != G_ALERTALTERNATE)
4600 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
4606 gint compose_send(Compose *compose)
4609 FolderItem *folder = NULL;
4611 gchar *msgpath = NULL;
4612 gboolean discard_window = FALSE;
4613 gchar *errstr = NULL;
4614 gchar *tmsgid = NULL;
4615 MainWindow *mainwin = mainwindow_get_mainwindow();
4616 gboolean queued_removed = FALSE;
4618 if (prefs_common.send_dialog_invisible
4619 || compose->batch == TRUE)
4620 discard_window = TRUE;
4622 compose_allow_user_actions (compose, FALSE);
4623 compose->sending = TRUE;
4625 if (compose_check_entries(compose, TRUE) == FALSE) {
4626 if (compose->batch) {
4627 gtk_widget_show_all(compose->window);
4633 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
4636 if (compose->batch) {
4637 gtk_widget_show_all(compose->window);
4640 alertpanel_error(_("Could not queue message for sending:\n\n"
4641 "Charset conversion failed."));
4642 } else if (val == -5) {
4643 alertpanel_error(_("Could not queue message for sending:\n\n"
4644 "Couldn't get recipient encryption key."));
4645 } else if (val == -6) {
4647 } else if (val == -3) {
4648 if (privacy_peek_error())
4649 alertpanel_error(_("Could not queue message for sending:\n\n"
4650 "Signature failed: %s"), privacy_get_error());
4651 } else if (val == -2 && errno != 0) {
4652 alertpanel_error(_("Could not queue message for sending:\n\n%s."), strerror(errno));
4654 alertpanel_error(_("Could not queue message for sending."));
4659 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
4660 if (discard_window) {
4661 compose->sending = FALSE;
4662 compose_close(compose);
4663 /* No more compose access in the normal codepath
4664 * after this point! */
4669 alertpanel_error(_("The message was queued but could not be "
4670 "sent.\nUse \"Send queued messages\" from "
4671 "the main window to retry."));
4672 if (!discard_window) {
4679 if (msgpath == NULL) {
4680 msgpath = folder_item_fetch_msg(folder, msgnum);
4681 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
4684 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
4688 if (!discard_window) {
4690 if (!queued_removed)
4691 folder_item_remove_msg(folder, msgnum);
4692 folder_item_scan(folder);
4694 /* make sure we delete that */
4695 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
4697 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
4698 folder_item_remove_msg(folder, tmp->msgnum);
4699 procmsg_msginfo_free(tmp);
4706 if (!queued_removed)
4707 folder_item_remove_msg(folder, msgnum);
4708 folder_item_scan(folder);
4710 /* make sure we delete that */
4711 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
4713 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
4714 folder_item_remove_msg(folder, tmp->msgnum);
4715 procmsg_msginfo_free(tmp);
4718 if (!discard_window) {
4719 compose->sending = FALSE;
4720 compose_allow_user_actions (compose, TRUE);
4721 compose_close(compose);
4725 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
4726 "the main window to retry."), errstr);
4729 alertpanel_error_log(_("The message was queued but could not be "
4730 "sent.\nUse \"Send queued messages\" from "
4731 "the main window to retry."));
4733 if (!discard_window) {
4742 toolbar_main_set_sensitive(mainwin);
4743 main_window_set_menu_sensitive(mainwin);
4749 compose_allow_user_actions (compose, TRUE);
4750 compose->sending = FALSE;
4751 compose->modified = TRUE;
4752 toolbar_main_set_sensitive(mainwin);
4753 main_window_set_menu_sensitive(mainwin);
4758 static gboolean compose_use_attach(Compose *compose)
4760 GtkTreeModel *model = gtk_tree_view_get_model
4761 (GTK_TREE_VIEW(compose->attach_clist));
4762 return gtk_tree_model_iter_n_children(model, NULL) > 0;
4765 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
4768 gchar buf[BUFFSIZE];
4770 gboolean first_to_address;
4771 gboolean first_cc_address;
4773 ComposeHeaderEntry *headerentry;
4774 const gchar *headerentryname;
4775 const gchar *cc_hdr;
4776 const gchar *to_hdr;
4777 gboolean err = FALSE;
4779 debug_print("Writing redirect header\n");
4781 cc_hdr = prefs_common_translated_header_name("Cc:");
4782 to_hdr = prefs_common_translated_header_name("To:");
4784 first_to_address = TRUE;
4785 for (list = compose->header_list; list; list = list->next) {
4786 headerentry = ((ComposeHeaderEntry *)list->data);
4787 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_BIN(headerentry->combo)->child));
4789 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
4790 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
4791 Xstrdup_a(str, entstr, return -1);
4793 if (str[0] != '\0') {
4794 compose_convert_header
4795 (compose, buf, sizeof(buf), str,
4796 strlen("Resent-To") + 2, TRUE);
4798 if (first_to_address) {
4799 err |= (fprintf(fp, "Resent-To: ") < 0);
4800 first_to_address = FALSE;
4802 err |= (fprintf(fp, ",") < 0);
4804 err |= (fprintf(fp, "%s", buf) < 0);
4808 if (!first_to_address) {
4809 err |= (fprintf(fp, "\n") < 0);
4812 first_cc_address = TRUE;
4813 for (list = compose->header_list; list; list = list->next) {
4814 headerentry = ((ComposeHeaderEntry *)list->data);
4815 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_BIN(headerentry->combo)->child));
4817 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
4818 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
4819 Xstrdup_a(str, strg, return -1);
4821 if (str[0] != '\0') {
4822 compose_convert_header
4823 (compose, buf, sizeof(buf), str,
4824 strlen("Resent-Cc") + 2, TRUE);
4826 if (first_cc_address) {
4827 err |= (fprintf(fp, "Resent-Cc: ") < 0);
4828 first_cc_address = FALSE;
4830 err |= (fprintf(fp, ",") < 0);
4832 err |= (fprintf(fp, "%s", buf) < 0);
4836 if (!first_cc_address) {
4837 err |= (fprintf(fp, "\n") < 0);
4840 return (err ? -1:0);
4843 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
4845 gchar buf[BUFFSIZE];
4847 const gchar *entstr;
4848 /* struct utsname utsbuf; */
4849 gboolean err = FALSE;
4851 g_return_val_if_fail(fp != NULL, -1);
4852 g_return_val_if_fail(compose->account != NULL, -1);
4853 g_return_val_if_fail(compose->account->address != NULL, -1);
4856 get_rfc822_date(buf, sizeof(buf));
4857 err |= (fprintf(fp, "Resent-Date: %s\n", buf) < 0);
4860 if (compose->account->name && *compose->account->name) {
4861 compose_convert_header
4862 (compose, buf, sizeof(buf), compose->account->name,
4863 strlen("From: "), TRUE);
4864 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
4865 buf, compose->account->address) < 0);
4867 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
4870 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
4871 if (*entstr != '\0') {
4872 Xstrdup_a(str, entstr, return -1);
4875 compose_convert_header(compose, buf, sizeof(buf), str,
4876 strlen("Subject: "), FALSE);
4877 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
4881 /* Resent-Message-ID */
4882 if (compose->account->set_domain && compose->account->domain) {
4883 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
4884 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
4885 g_snprintf(buf, sizeof(buf), "%s",
4886 strchr(compose->account->address, '@') ?
4887 strchr(compose->account->address, '@')+1 :
4888 compose->account->address);
4890 g_snprintf(buf, sizeof(buf), "%s", "");
4893 if (compose->account->gen_msgid) {
4894 generate_msgid(buf, sizeof(buf));
4895 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", buf) < 0);
4896 compose->msgid = g_strdup(buf);
4898 compose->msgid = NULL;
4901 if (compose_redirect_write_headers_from_headerlist(compose, fp))
4904 /* separator between header and body */
4905 err |= (fputs("\n", fp) == EOF);
4907 return (err ? -1:0);
4910 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
4914 gchar buf[BUFFSIZE];
4916 gboolean skip = FALSE;
4917 gboolean err = FALSE;
4918 gchar *not_included[]={
4919 "Return-Path:", "Delivered-To:", "Received:",
4920 "Subject:", "X-UIDL:", "AF:",
4921 "NF:", "PS:", "SRH:",
4922 "SFN:", "DSR:", "MID:",
4923 "CFG:", "PT:", "S:",
4924 "RQ:", "SSV:", "NSV:",
4925 "SSH:", "R:", "MAID:",
4926 "NAID:", "RMID:", "FMID:",
4927 "SCF:", "RRCPT:", "NG:",
4928 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
4929 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
4930 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
4931 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
4934 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
4935 FILE_OP_ERROR(compose->redirect_filename, "fopen");
4939 while (procheader_get_one_field_asis(buf, sizeof(buf), fp) != -1) {
4941 for (i = 0; not_included[i] != NULL; i++) {
4942 if (g_ascii_strncasecmp(buf, not_included[i],
4943 strlen(not_included[i])) == 0) {
4950 if (fputs(buf, fdest) == -1)
4953 if (!prefs_common.redirect_keep_from) {
4954 if (g_ascii_strncasecmp(buf, "From:",
4955 strlen("From:")) == 0) {
4956 err |= (fputs(" (by way of ", fdest) == EOF);
4957 if (compose->account->name
4958 && *compose->account->name) {
4959 compose_convert_header
4960 (compose, buf, sizeof(buf),
4961 compose->account->name,
4964 err |= (fprintf(fdest, "%s <%s>",
4966 compose->account->address) < 0);
4968 err |= (fprintf(fdest, "%s",
4969 compose->account->address) < 0);
4970 err |= (fputs(")", fdest) == EOF);
4974 if (fputs("\n", fdest) == -1)
4981 if (compose_redirect_write_headers(compose, fdest))
4984 while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
4985 if (fwrite(buf, sizeof(gchar), len, fdest) != len)
4998 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5000 GtkTextBuffer *buffer;
5001 GtkTextIter start, end;
5004 const gchar *out_codeset;
5005 EncodingType encoding;
5006 MimeInfo *mimemsg, *mimetext;
5009 if (action == COMPOSE_WRITE_FOR_SEND)
5010 attach_parts = TRUE;
5012 /* create message MimeInfo */
5013 mimemsg = procmime_mimeinfo_new();
5014 mimemsg->type = MIMETYPE_MESSAGE;
5015 mimemsg->subtype = g_strdup("rfc822");
5016 mimemsg->content = MIMECONTENT_MEM;
5017 mimemsg->tmp = TRUE; /* must free content later */
5018 mimemsg->data.mem = compose_get_header(compose);
5020 /* Create text part MimeInfo */
5021 /* get all composed text */
5022 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5023 gtk_text_buffer_get_start_iter(buffer, &start);
5024 gtk_text_buffer_get_end_iter(buffer, &end);
5025 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5026 if (is_ascii_str(chars)) {
5029 out_codeset = CS_US_ASCII;
5030 encoding = ENC_7BIT;
5032 const gchar *src_codeset = CS_INTERNAL;
5034 out_codeset = conv_get_charset_str(compose->out_encoding);
5037 gchar *test_conv_global_out = NULL;
5038 gchar *test_conv_reply = NULL;
5040 /* automatic mode. be automatic. */
5041 codeconv_set_strict(TRUE);
5043 out_codeset = conv_get_outgoing_charset_str();
5045 debug_print("trying to convert to %s\n", out_codeset);
5046 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5049 if (!test_conv_global_out && compose->orig_charset
5050 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5051 out_codeset = compose->orig_charset;
5052 debug_print("failure; trying to convert to %s\n", out_codeset);
5053 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5056 if (!test_conv_global_out && !test_conv_reply) {
5058 out_codeset = CS_INTERNAL;
5059 debug_print("failure; finally using %s\n", out_codeset);
5061 g_free(test_conv_global_out);
5062 g_free(test_conv_reply);
5063 codeconv_set_strict(FALSE);
5066 if (!g_ascii_strcasecmp(out_codeset, CS_US_ASCII))
5067 out_codeset = CS_ISO_8859_1;
5069 if (prefs_common.encoding_method == CTE_BASE64)
5070 encoding = ENC_BASE64;
5071 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5072 encoding = ENC_QUOTED_PRINTABLE;
5073 else if (prefs_common.encoding_method == CTE_8BIT)
5074 encoding = ENC_8BIT;
5076 encoding = procmime_get_encoding_for_charset(out_codeset);
5078 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5079 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5081 if (action == COMPOSE_WRITE_FOR_SEND) {
5082 codeconv_set_strict(TRUE);
5083 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5084 codeconv_set_strict(FALSE);
5090 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5091 "to the specified %s charset.\n"
5092 "Send it as %s?"), out_codeset, src_codeset);
5093 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL, _("+_Send"), NULL, FALSE,
5094 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5097 if (aval != G_ALERTALTERNATE) {
5102 out_codeset = src_codeset;
5108 out_codeset = src_codeset;
5114 if (encoding == ENC_8BIT || encoding == ENC_7BIT) {
5115 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5116 strstr(buf, "\nFrom ") != NULL) {
5117 encoding = ENC_QUOTED_PRINTABLE;
5121 mimetext = procmime_mimeinfo_new();
5122 mimetext->content = MIMECONTENT_MEM;
5123 mimetext->tmp = TRUE; /* must free content later */
5124 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5125 * and free the data, which we need later. */
5126 mimetext->data.mem = g_strdup(buf);
5127 mimetext->type = MIMETYPE_TEXT;
5128 mimetext->subtype = g_strdup("plain");
5129 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5130 g_strdup(out_codeset));
5132 /* protect trailing spaces when signing message */
5133 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5134 privacy_system_can_sign(compose->privacy_system)) {
5135 encoding = ENC_QUOTED_PRINTABLE;
5138 debug_print("main text: %zd bytes encoded as %s in %d\n",
5139 strlen(buf), out_codeset, encoding);
5141 /* check for line length limit */
5142 if (action == COMPOSE_WRITE_FOR_SEND &&
5143 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5144 check_line_length(buf, 1000, &line) < 0) {
5148 msg = g_strdup_printf
5149 (_("Line %d exceeds the line length limit (998 bytes).\n"
5150 "The contents of the message might be broken on the way to the delivery.\n"
5152 "Send it anyway?"), line + 1);
5153 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5155 if (aval != G_ALERTALTERNATE) {
5161 if (encoding != ENC_UNKNOWN)
5162 procmime_encode_content(mimetext, encoding);
5164 /* append attachment parts */
5165 if (compose_use_attach(compose) && attach_parts) {
5166 MimeInfo *mimempart;
5167 gchar *boundary = NULL;
5168 mimempart = procmime_mimeinfo_new();
5169 mimempart->content = MIMECONTENT_EMPTY;
5170 mimempart->type = MIMETYPE_MULTIPART;
5171 mimempart->subtype = g_strdup("mixed");
5175 boundary = generate_mime_boundary(NULL);
5176 } while (strstr(buf, boundary) != NULL);
5178 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5181 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5183 g_node_append(mimempart->node, mimetext->node);
5184 g_node_append(mimemsg->node, mimempart->node);
5186 compose_add_attachments(compose, mimempart);
5188 g_node_append(mimemsg->node, mimetext->node);
5192 /* sign message if sending */
5193 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5194 privacy_system_can_sign(compose->privacy_system))
5195 if (!privacy_sign(compose->privacy_system, mimemsg, compose->account))
5198 procmime_write_mimeinfo(mimemsg, fp);
5200 procmime_mimeinfo_free_all(mimemsg);
5205 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5207 GtkTextBuffer *buffer;
5208 GtkTextIter start, end;
5213 if ((fp = g_fopen(file, "wb")) == NULL) {
5214 FILE_OP_ERROR(file, "fopen");
5218 /* chmod for security */
5219 if (change_file_mode_rw(fp, file) < 0) {
5220 FILE_OP_ERROR(file, "chmod");
5221 g_warning("can't change file mode\n");
5224 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5225 gtk_text_buffer_get_start_iter(buffer, &start);
5226 gtk_text_buffer_get_end_iter(buffer, &end);
5227 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5229 chars = conv_codeset_strdup
5230 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5233 if (!chars) return -1;
5236 len = strlen(chars);
5237 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5238 FILE_OP_ERROR(file, "fwrite");
5247 if (fclose(fp) == EOF) {
5248 FILE_OP_ERROR(file, "fclose");
5255 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5258 MsgInfo *msginfo = compose->targetinfo;
5260 g_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5261 if (!msginfo) return -1;
5263 if (!force && MSG_IS_LOCKED(msginfo->flags))
5266 item = msginfo->folder;
5267 g_return_val_if_fail(item != NULL, -1);
5269 if (procmsg_msg_exist(msginfo) &&
5270 (folder_has_parent_of_type(item, F_QUEUE) ||
5271 folder_has_parent_of_type(item, F_DRAFT)
5272 || msginfo == compose->autosaved_draft)) {
5273 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5274 g_warning("can't remove the old message\n");
5277 debug_print("removed reedit target %d\n", msginfo->msgnum);
5284 static void compose_remove_draft(Compose *compose)
5287 MsgInfo *msginfo = compose->targetinfo;
5288 drafts = account_get_special_folder(compose->account, F_DRAFT);
5290 if (procmsg_msg_exist(msginfo)) {
5291 folder_item_remove_msg(drafts, msginfo->msgnum);
5296 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5297 gboolean remove_reedit_target)
5299 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5302 static gboolean compose_warn_encryption(Compose *compose)
5304 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5305 AlertValue val = G_ALERTALTERNATE;
5307 if (warning == NULL)
5310 val = alertpanel_full(_("Encryption warning"), warning,
5311 GTK_STOCK_CANCEL, _("+C_ontinue"), NULL,
5312 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5313 if (val & G_ALERTDISABLE) {
5314 val &= ~G_ALERTDISABLE;
5315 if (val == G_ALERTALTERNATE)
5316 privacy_inhibit_encrypt_warning(compose->privacy_system,
5320 if (val == G_ALERTALTERNATE) {
5327 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
5328 gchar **msgpath, gboolean check_subject,
5329 gboolean remove_reedit_target)
5336 static gboolean lock = FALSE;
5337 PrefsAccount *mailac = NULL, *newsac = NULL;
5338 gboolean err = FALSE;
5340 debug_print("queueing message...\n");
5341 g_return_val_if_fail(compose->account != NULL, -1);
5345 if (compose_check_entries(compose, check_subject) == FALSE) {
5347 if (compose->batch) {
5348 gtk_widget_show_all(compose->window);
5353 if (!compose->to_list && !compose->newsgroup_list) {
5354 g_warning("can't get recipient list.");
5359 if (compose->to_list) {
5360 if (compose->account->protocol != A_NNTP)
5361 mailac = compose->account;
5362 else if (cur_account && cur_account->protocol != A_NNTP)
5363 mailac = cur_account;
5364 else if (!(mailac = compose_current_mail_account())) {
5366 alertpanel_error(_("No account for sending mails available!"));
5371 if (compose->newsgroup_list) {
5372 if (compose->account->protocol == A_NNTP)
5373 newsac = compose->account;
5374 else if (!newsac->protocol != A_NNTP) {
5376 alertpanel_error(_("No account for posting news available!"));
5381 /* write queue header */
5382 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
5383 G_DIR_SEPARATOR, compose, (guint) rand());
5384 debug_print("queuing to %s\n", tmp);
5385 if ((fp = g_fopen(tmp, "wb")) == NULL) {
5386 FILE_OP_ERROR(tmp, "fopen");
5392 if (change_file_mode_rw(fp, tmp) < 0) {
5393 FILE_OP_ERROR(tmp, "chmod");
5394 g_warning("can't change file mode\n");
5397 /* queueing variables */
5398 err |= (fprintf(fp, "AF:\n") < 0);
5399 err |= (fprintf(fp, "NF:0\n") < 0);
5400 err |= (fprintf(fp, "PS:10\n") < 0);
5401 err |= (fprintf(fp, "SRH:1\n") < 0);
5402 err |= (fprintf(fp, "SFN:\n") < 0);
5403 err |= (fprintf(fp, "DSR:\n") < 0);
5405 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
5407 err |= (fprintf(fp, "MID:\n") < 0);
5408 err |= (fprintf(fp, "CFG:\n") < 0);
5409 err |= (fprintf(fp, "PT:0\n") < 0);
5410 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
5411 err |= (fprintf(fp, "RQ:\n") < 0);
5413 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
5415 err |= (fprintf(fp, "SSV:\n") < 0);
5417 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
5419 err |= (fprintf(fp, "NSV:\n") < 0);
5420 err |= (fprintf(fp, "SSH:\n") < 0);
5421 /* write recepient list */
5422 if (compose->to_list) {
5423 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
5424 for (cur = compose->to_list->next; cur != NULL;
5426 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
5427 err |= (fprintf(fp, "\n") < 0);
5429 /* write newsgroup list */
5430 if (compose->newsgroup_list) {
5431 err |= (fprintf(fp, "NG:") < 0);
5432 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
5433 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
5434 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
5435 err |= (fprintf(fp, "\n") < 0);
5437 /* Sylpheed account IDs */
5439 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
5441 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
5444 if (compose->privacy_system != NULL) {
5445 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
5446 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
5447 if (compose->use_encryption) {
5449 if (!compose_warn_encryption(compose)) {
5456 if (mailac && mailac->encrypt_to_self) {
5457 GSList *tmp_list = g_slist_copy(compose->to_list);
5458 tmp_list = g_slist_append(tmp_list, compose->account->address);
5459 encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
5460 g_slist_free(tmp_list);
5462 encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
5464 if (encdata != NULL) {
5465 if (strcmp(encdata, "_DONT_ENCRYPT_")) {
5466 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5467 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
5469 } /* else we finally dont want to encrypt */
5471 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5472 /* and if encdata was null, it means there's been a problem in
5484 /* Save copy folder */
5485 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
5486 gchar *savefolderid;
5488 savefolderid = gtk_editable_get_chars(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
5489 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
5490 g_free(savefolderid);
5492 /* Save copy folder */
5493 if (compose->return_receipt) {
5494 err |= (fprintf(fp, "RRCPT:1\n") < 0);
5496 /* Message-ID of message replying to */
5497 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
5500 folderid = folder_item_get_identifier(compose->replyinfo->folder);
5501 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
5504 /* Message-ID of message forwarding to */
5505 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
5508 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
5509 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
5513 /* end of headers */
5514 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
5516 if (compose->redirect_filename != NULL) {
5517 if (compose_redirect_write_to_file(compose, fp) < 0) {
5526 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
5531 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
5535 g_warning("failed to write queue message\n");
5542 if (fclose(fp) == EOF) {
5543 FILE_OP_ERROR(tmp, "fclose");
5550 if (item && *item) {
5553 queue = account_get_special_folder(compose->account, F_QUEUE);
5556 g_warning("can't find queue folder\n");
5562 folder_item_scan(queue);
5563 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
5564 g_warning("can't queue the message\n");
5571 if (msgpath == NULL) {
5577 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
5578 compose_remove_reedit_target(compose, FALSE);
5581 if ((msgnum != NULL) && (item != NULL)) {
5589 static void compose_add_attachments(Compose *compose, MimeInfo *parent)
5592 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
5594 struct stat statbuf;
5595 gchar *type, *subtype;
5596 GtkTreeModel *model;
5599 model = gtk_tree_view_get_model(tree_view);
5601 if (!gtk_tree_model_get_iter_first(model, &iter))
5604 gtk_tree_model_get(model, &iter,
5608 mimepart = procmime_mimeinfo_new();
5609 mimepart->content = MIMECONTENT_FILE;
5610 mimepart->data.filename = g_strdup(ainfo->file);
5611 mimepart->tmp = FALSE; /* or we destroy our attachment */
5612 mimepart->offset = 0;
5614 stat(ainfo->file, &statbuf);
5615 mimepart->length = statbuf.st_size;
5617 type = g_strdup(ainfo->content_type);
5619 if (!strchr(type, '/')) {
5621 type = g_strdup("application/octet-stream");
5624 subtype = strchr(type, '/') + 1;
5625 *(subtype - 1) = '\0';
5626 mimepart->type = procmime_get_media_type(type);
5627 mimepart->subtype = g_strdup(subtype);
5630 if (mimepart->type == MIMETYPE_MESSAGE &&
5631 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
5632 mimepart->disposition = DISPOSITIONTYPE_INLINE;
5635 g_hash_table_insert(mimepart->typeparameters,
5636 g_strdup("name"), g_strdup(ainfo->name));
5637 g_hash_table_insert(mimepart->dispositionparameters,
5638 g_strdup("filename"), g_strdup(ainfo->name));
5639 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
5643 if (compose->use_signing) {
5644 if (ainfo->encoding == ENC_7BIT)
5645 ainfo->encoding = ENC_QUOTED_PRINTABLE;
5646 else if (ainfo->encoding == ENC_8BIT)
5647 ainfo->encoding = ENC_BASE64;
5650 procmime_encode_content(mimepart, ainfo->encoding);
5652 g_node_append(parent->node, mimepart->node);
5653 } while (gtk_tree_model_iter_next(model, &iter));
5656 #define IS_IN_CUSTOM_HEADER(header) \
5657 (compose->account->add_customhdr && \
5658 custom_header_find(compose->account->customhdr_list, header) != NULL)
5660 static void compose_add_headerfield_from_headerlist(Compose *compose,
5662 const gchar *fieldname,
5663 const gchar *seperator)
5665 gchar *str, *fieldname_w_colon;
5666 gboolean add_field = FALSE;
5668 ComposeHeaderEntry *headerentry;
5669 const gchar *headerentryname;
5670 const gchar *trans_fieldname;
5673 if (IS_IN_CUSTOM_HEADER(fieldname))
5676 debug_print("Adding %s-fields\n", fieldname);
5678 fieldstr = g_string_sized_new(64);
5680 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
5681 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
5683 for (list = compose->header_list; list; list = list->next) {
5684 headerentry = ((ComposeHeaderEntry *)list->data);
5685 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_BIN(headerentry->combo)->child));
5687 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
5688 str = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
5690 if (str[0] != '\0') {
5692 g_string_append(fieldstr, seperator);
5693 g_string_append(fieldstr, str);
5702 buf = g_new0(gchar, fieldstr->len * 4 + 256);
5703 compose_convert_header
5704 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
5705 strlen(fieldname) + 2, TRUE);
5706 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
5710 g_free(fieldname_w_colon);
5711 g_string_free(fieldstr, TRUE);
5716 static gchar *compose_get_header(Compose *compose)
5718 gchar buf[BUFFSIZE];
5719 const gchar *entry_str;
5723 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
5725 gchar *from_name = NULL, *from_address = NULL;
5728 g_return_val_if_fail(compose->account != NULL, NULL);
5729 g_return_val_if_fail(compose->account->address != NULL, NULL);
5731 header = g_string_sized_new(64);
5734 get_rfc822_date(buf, sizeof(buf));
5735 g_string_append_printf(header, "Date: %s\n", buf);
5739 if (compose->account->name && *compose->account->name) {
5741 QUOTE_IF_REQUIRED(buf, compose->account->name);
5742 tmp = g_strdup_printf("%s <%s>",
5743 buf, compose->account->address);
5745 tmp = g_strdup_printf("%s",
5746 compose->account->address);
5748 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
5749 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
5751 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
5752 from_address = g_strdup(compose->account->address);
5754 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5755 /* extract name and address */
5756 if (strstr(spec, " <") && strstr(spec, ">")) {
5757 from_address = g_strdup(strrchr(spec, '<')+1);
5758 *(strrchr(from_address, '>')) = '\0';
5759 from_name = g_strdup(spec);
5760 *(strrchr(from_name, '<')) = '\0';
5763 from_address = g_strdup(spec);
5770 if (from_name && *from_name) {
5771 compose_convert_header
5772 (compose, buf, sizeof(buf), from_name,
5773 strlen("From: "), TRUE);
5774 QUOTE_IF_REQUIRED(name, buf);
5776 g_string_append_printf(header, "From: %s <%s>\n",
5777 name, from_address);
5779 g_string_append_printf(header, "From: %s\n", from_address);
5782 g_free(from_address);
5785 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
5788 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
5791 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
5794 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
5797 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
5799 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
5802 compose_convert_header(compose, buf, sizeof(buf), str,
5803 strlen("Subject: "), FALSE);
5804 g_string_append_printf(header, "Subject: %s\n", buf);
5810 if (compose->account->set_domain && compose->account->domain) {
5811 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
5812 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
5813 g_snprintf(buf, sizeof(buf), "%s",
5814 strchr(compose->account->address, '@') ?
5815 strchr(compose->account->address, '@')+1 :
5816 compose->account->address);
5818 g_snprintf(buf, sizeof(buf), "%s", "");
5821 if (compose->account->gen_msgid) {
5822 generate_msgid(buf, sizeof(buf));
5823 g_string_append_printf(header, "Message-ID: <%s>\n", buf);
5824 compose->msgid = g_strdup(buf);
5826 compose->msgid = NULL;
5829 if (compose->remove_references == FALSE) {
5831 if (compose->inreplyto && compose->to_list)
5832 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
5835 if (compose->references)
5836 g_string_append_printf(header, "References: %s\n", compose->references);
5840 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
5843 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
5846 if (compose->account->organization &&
5847 strlen(compose->account->organization) &&
5848 !IS_IN_CUSTOM_HEADER("Organization")) {
5849 compose_convert_header(compose, buf, sizeof(buf),
5850 compose->account->organization,
5851 strlen("Organization: "), FALSE);
5852 g_string_append_printf(header, "Organization: %s\n", buf);
5855 /* Program version and system info */
5856 if (g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
5857 !compose->newsgroup_list) {
5858 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
5860 gtk_major_version, gtk_minor_version, gtk_micro_version,
5863 if (g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
5864 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
5866 gtk_major_version, gtk_minor_version, gtk_micro_version,
5870 /* custom headers */
5871 if (compose->account->add_customhdr) {
5874 for (cur = compose->account->customhdr_list; cur != NULL;
5876 CustomHeader *chdr = (CustomHeader *)cur->data;
5878 if (custom_header_is_allowed(chdr->name)) {
5879 compose_convert_header
5880 (compose, buf, sizeof(buf),
5881 chdr->value ? chdr->value : "",
5882 strlen(chdr->name) + 2, FALSE);
5883 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
5889 switch (compose->priority) {
5890 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
5891 "X-Priority: 1 (Highest)\n");
5893 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
5894 "X-Priority: 2 (High)\n");
5896 case PRIORITY_NORMAL: break;
5897 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
5898 "X-Priority: 4 (Low)\n");
5900 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
5901 "X-Priority: 5 (Lowest)\n");
5903 default: debug_print("compose: priority unknown : %d\n",
5907 /* Request Return Receipt */
5908 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To")) {
5909 if (compose->return_receipt) {
5910 if (compose->account->name
5911 && *compose->account->name) {
5912 compose_convert_header(compose, buf, sizeof(buf),
5913 compose->account->name,
5914 strlen("Disposition-Notification-To: "),
5916 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, compose->account->address);
5918 g_string_append_printf(header, "Disposition-Notification-To: %s\n", compose->account->address);
5922 /* get special headers */
5923 for (list = compose->header_list; list; list = list->next) {
5924 ComposeHeaderEntry *headerentry;
5927 gchar *headername_wcolon;
5928 const gchar *headername_trans;
5931 gboolean standard_header = FALSE;
5933 headerentry = ((ComposeHeaderEntry *)list->data);
5935 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(GTK_BIN(headerentry->combo)->child)));
5937 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
5942 if (!strstr(tmp, ":")) {
5943 headername_wcolon = g_strconcat(tmp, ":", NULL);
5944 headername = g_strdup(tmp);
5946 headername_wcolon = g_strdup(tmp);
5947 headername = g_strdup(strtok(tmp, ":"));
5951 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5952 Xstrdup_a(headervalue, entry_str, return NULL);
5953 subst_char(headervalue, '\r', ' ');
5954 subst_char(headervalue, '\n', ' ');
5955 string = std_headers;
5956 while (*string != NULL) {
5957 headername_trans = prefs_common_translated_header_name(*string);
5958 if (!strcmp(headername_trans, headername_wcolon))
5959 standard_header = TRUE;
5962 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
5963 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
5966 g_free(headername_wcolon);
5970 g_string_free(header, FALSE);
5975 #undef IS_IN_CUSTOM_HEADER
5977 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
5978 gint header_len, gboolean addr_field)
5980 gchar *tmpstr = NULL;
5981 const gchar *out_codeset = NULL;
5983 g_return_if_fail(src != NULL);
5984 g_return_if_fail(dest != NULL);
5986 if (len < 1) return;
5988 tmpstr = g_strdup(src);
5990 subst_char(tmpstr, '\n', ' ');
5991 subst_char(tmpstr, '\r', ' ');
5994 if (!g_utf8_validate(tmpstr, -1, NULL)) {
5995 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
5996 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6001 codeconv_set_strict(TRUE);
6002 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6003 conv_get_charset_str(compose->out_encoding));
6004 codeconv_set_strict(FALSE);
6006 if (!dest || *dest == '\0') {
6007 gchar *test_conv_global_out = NULL;
6008 gchar *test_conv_reply = NULL;
6010 /* automatic mode. be automatic. */
6011 codeconv_set_strict(TRUE);
6013 out_codeset = conv_get_outgoing_charset_str();
6015 debug_print("trying to convert to %s\n", out_codeset);
6016 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6019 if (!test_conv_global_out && compose->orig_charset
6020 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6021 out_codeset = compose->orig_charset;
6022 debug_print("failure; trying to convert to %s\n", out_codeset);
6023 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6026 if (!test_conv_global_out && !test_conv_reply) {
6028 out_codeset = CS_INTERNAL;
6029 debug_print("finally using %s\n", out_codeset);
6031 g_free(test_conv_global_out);
6032 g_free(test_conv_reply);
6033 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6035 codeconv_set_strict(FALSE);
6040 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
6044 g_return_if_fail(user_data != NULL);
6046 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6047 g_strstrip(address);
6048 if (*address != '\0') {
6049 gchar *name = procheader_get_fromname(address);
6050 extract_address(address);
6051 addressbook_add_contact(name, address, NULL, NULL);
6056 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
6058 GtkWidget *menuitem;
6061 g_return_if_fail(menu != NULL);
6062 g_return_if_fail(GTK_IS_MENU_SHELL(menu));
6064 menuitem = gtk_separator_menu_item_new();
6065 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6066 gtk_widget_show(menuitem);
6068 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
6069 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6071 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
6072 g_strstrip(address);
6073 if (*address == '\0') {
6074 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
6077 g_signal_connect(G_OBJECT(menuitem), "activate",
6078 G_CALLBACK(compose_add_to_addressbook_cb), entry);
6079 gtk_widget_show(menuitem);
6082 static void compose_create_header_entry(Compose *compose)
6084 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6089 const gchar *header = NULL;
6090 ComposeHeaderEntry *headerentry;
6091 gboolean standard_header = FALSE;
6093 headerentry = g_new0(ComposeHeaderEntry, 1);
6096 combo = gtk_combo_box_entry_new_text();
6098 while(*string != NULL) {
6099 gtk_combo_box_append_text(GTK_COMBO_BOX(combo),
6100 (gchar*)prefs_common_translated_header_name(*string));
6103 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
6104 g_signal_connect(G_OBJECT(GTK_BIN(combo)->child), "grab_focus",
6105 G_CALLBACK(compose_grab_focus_cb), compose);
6106 gtk_widget_show(combo);
6107 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
6108 compose->header_nextrow, compose->header_nextrow+1,
6109 GTK_SHRINK, GTK_FILL, 0, 0);
6110 if (compose->header_last) {
6111 const gchar *last_header_entry = gtk_entry_get_text(
6112 GTK_ENTRY(GTK_BIN(compose->header_last->combo)->child));
6114 while (*string != NULL) {
6115 if (!strcmp(*string, last_header_entry))
6116 standard_header = TRUE;
6119 if (standard_header)
6120 header = gtk_entry_get_text(GTK_ENTRY(GTK_BIN(compose->header_last->combo)->child));
6122 if (!compose->header_last || !standard_header) {
6123 switch(compose->account->protocol) {
6125 header = prefs_common_translated_header_name("Newsgroups:");
6128 header = prefs_common_translated_header_name("To:");
6133 gtk_entry_set_text(GTK_ENTRY(GTK_BIN(combo)->child), header);
6135 g_signal_connect_after(G_OBJECT(GTK_BIN(combo)->child), "grab_focus",
6136 G_CALLBACK(compose_grab_focus_cb), compose);
6139 entry = gtk_entry_new();
6140 gtk_widget_show(entry);
6141 gtk_tooltips_set_tip(compose->tooltips, entry,
6142 _("Use <tab> to autocomplete from addressbook"), NULL);
6143 gtk_table_attach(GTK_TABLE(compose->header_table), entry, 1, 2,
6144 compose->header_nextrow, compose->header_nextrow+1,
6145 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
6147 g_signal_connect(G_OBJECT(entry), "key-press-event",
6148 G_CALLBACK(compose_headerentry_key_press_event_cb),
6150 g_signal_connect(G_OBJECT(entry), "changed",
6151 G_CALLBACK(compose_headerentry_changed_cb),
6153 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
6154 G_CALLBACK(compose_grab_focus_cb), compose);
6157 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6158 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6159 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6160 g_signal_connect(G_OBJECT(entry), "drag_data_received",
6161 G_CALLBACK(compose_header_drag_received_cb),
6163 g_signal_connect(G_OBJECT(entry), "drag-drop",
6164 G_CALLBACK(compose_drag_drop),
6166 g_signal_connect(G_OBJECT(entry), "populate-popup",
6167 G_CALLBACK(compose_entry_popup_extend),
6170 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
6172 headerentry->compose = compose;
6173 headerentry->combo = combo;
6174 headerentry->entry = entry;
6175 headerentry->headernum = compose->header_nextrow;
6177 compose->header_nextrow++;
6178 compose->header_last = headerentry;
6179 compose->header_list =
6180 g_slist_append(compose->header_list,
6184 static void compose_add_header_entry(Compose *compose, const gchar *header, gchar *text)
6186 ComposeHeaderEntry *last_header;
6188 last_header = compose->header_last;
6190 gtk_entry_set_text(GTK_ENTRY(GTK_BIN(last_header->combo)->child), header);
6191 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
6194 static void compose_remove_header_entries(Compose *compose)
6197 for (list = compose->header_list; list; list = list->next) {
6198 ComposeHeaderEntry *headerentry =
6199 (ComposeHeaderEntry *)list->data;
6200 gtk_widget_destroy(headerentry->combo);
6201 gtk_widget_destroy(headerentry->entry);
6202 g_free(headerentry);
6204 compose->header_last = NULL;
6205 g_slist_free(compose->header_list);
6206 compose->header_list = NULL;
6207 compose->header_nextrow = 1;
6208 compose_create_header_entry(compose);
6211 static GtkWidget *compose_create_header(Compose *compose)
6213 GtkWidget *from_optmenu_hbox;
6214 GtkWidget *header_scrolledwin;
6215 GtkWidget *header_table;
6219 /* header labels and entries */
6220 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
6221 gtk_widget_show(header_scrolledwin);
6222 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
6224 header_table = gtk_table_new(2, 2, FALSE);
6225 gtk_widget_show(header_table);
6226 gtk_container_set_border_width(GTK_CONTAINER(header_table), BORDER_WIDTH);
6227 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
6228 gtk_viewport_set_shadow_type(GTK_VIEWPORT(GTK_BIN(header_scrolledwin)->child), GTK_SHADOW_NONE);
6231 /* option menu for selecting accounts */
6232 from_optmenu_hbox = compose_account_option_menu_create(compose);
6233 gtk_table_attach(GTK_TABLE(header_table), from_optmenu_hbox,
6234 0, 2, count, count + 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
6237 compose->header_table = header_table;
6238 compose->header_list = NULL;
6239 compose->header_nextrow = count;
6241 compose_create_header_entry(compose);
6243 compose->table = NULL;
6245 return header_scrolledwin ;
6248 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
6250 Compose *compose = (Compose *)data;
6251 GdkEventButton event;
6254 event.time = gtk_get_current_event_time();
6256 return attach_button_pressed(compose->attach_clist, &event, compose);
6259 static GtkWidget *compose_create_attach(Compose *compose)
6261 GtkWidget *attach_scrwin;
6262 GtkWidget *attach_clist;
6264 GtkListStore *store;
6265 GtkCellRenderer *renderer;
6266 GtkTreeViewColumn *column;
6267 GtkTreeSelection *selection;
6269 /* attachment list */
6270 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
6271 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
6272 GTK_POLICY_AUTOMATIC,
6273 GTK_POLICY_AUTOMATIC);
6274 gtk_widget_set_size_request(attach_scrwin, -1, 80);
6276 store = gtk_list_store_new(N_ATTACH_COLS,
6281 G_TYPE_AUTO_POINTER,
6283 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
6284 (GTK_TREE_MODEL(store)));
6285 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
6286 g_object_unref(store);
6288 renderer = gtk_cell_renderer_text_new();
6289 column = gtk_tree_view_column_new_with_attributes
6290 (_("Mime type"), renderer, "text",
6291 COL_MIMETYPE, NULL);
6292 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6294 renderer = gtk_cell_renderer_text_new();
6295 column = gtk_tree_view_column_new_with_attributes
6296 (_("Size"), renderer, "text",
6298 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6300 renderer = gtk_cell_renderer_text_new();
6301 column = gtk_tree_view_column_new_with_attributes
6302 (_("Name"), renderer, "text",
6304 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6306 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
6307 prefs_common.use_stripes_everywhere);
6308 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
6309 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
6311 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
6312 G_CALLBACK(attach_selected), compose);
6313 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
6314 G_CALLBACK(attach_button_pressed), compose);
6316 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
6317 G_CALLBACK(popup_attach_button_pressed), compose);
6319 gtk_widget_tap_and_hold_setup(GTK_WIDGET(attach_clist), NULL, NULL,
6320 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
6321 g_signal_connect(G_OBJECT(attach_clist), "tap-and-hold",
6322 G_CALLBACK(popup_attach_button_pressed), compose);
6324 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
6325 G_CALLBACK(attach_key_pressed), compose);
6328 gtk_drag_dest_set(attach_clist,
6329 GTK_DEST_DEFAULT_ALL, compose_mime_types,
6330 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6331 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6332 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
6333 G_CALLBACK(compose_attach_drag_received_cb),
6335 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
6336 G_CALLBACK(compose_drag_drop),
6339 compose->attach_scrwin = attach_scrwin;
6340 compose->attach_clist = attach_clist;
6342 return attach_scrwin;
6345 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
6346 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
6348 static GtkWidget *compose_create_others(Compose *compose)
6351 GtkWidget *savemsg_checkbtn;
6352 GtkWidget *savemsg_entry;
6353 GtkWidget *savemsg_select;
6356 gchar *folderidentifier;
6358 /* Table for settings */
6359 table = gtk_table_new(3, 1, FALSE);
6360 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
6361 gtk_widget_show(table);
6362 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
6365 /* Save Message to folder */
6366 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
6367 gtk_widget_show(savemsg_checkbtn);
6368 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6369 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6370 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
6372 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
6373 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
6375 savemsg_entry = gtk_entry_new();
6376 gtk_widget_show(savemsg_entry);
6377 gtk_table_attach_defaults(GTK_TABLE(table), savemsg_entry, 1, 2, rowcount, rowcount + 1);
6378 gtk_editable_set_editable(GTK_EDITABLE(savemsg_entry), prefs_common.savemsg);
6379 g_signal_connect_after(G_OBJECT(savemsg_entry), "grab_focus",
6380 G_CALLBACK(compose_grab_focus_cb), compose);
6381 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6382 folderidentifier = folder_item_get_identifier(account_get_special_folder
6383 (compose->account, F_OUTBOX));
6384 gtk_entry_set_text(GTK_ENTRY(savemsg_entry), folderidentifier);
6385 g_free(folderidentifier);
6388 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
6389 gtk_widget_show(savemsg_select);
6390 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6391 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
6392 G_CALLBACK(compose_savemsg_select_cb),
6397 compose->savemsg_checkbtn = savemsg_checkbtn;
6398 compose->savemsg_entry = savemsg_entry;
6403 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
6405 gtk_editable_set_editable(GTK_EDITABLE(compose->savemsg_entry),
6406 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
6409 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
6414 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE);
6417 path = folder_item_get_identifier(dest);
6419 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), path);
6423 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
6424 GdkAtom clip, GtkTextIter *insert_place);
6427 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
6431 GtkTextBuffer *buffer = GTK_TEXT_VIEW(text)->buffer;
6433 if (event->button == 3) {
6435 GtkTextIter sel_start, sel_end;
6436 gboolean stuff_selected;
6438 /* move the cursor to allow GtkAspell to check the word
6439 * under the mouse */
6440 if (event->x && event->y) {
6441 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6442 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6444 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6447 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
6448 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
6451 stuff_selected = gtk_text_buffer_get_selection_bounds(
6453 &sel_start, &sel_end);
6455 gtk_text_buffer_place_cursor (buffer, &iter);
6456 /* reselect stuff */
6458 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
6459 gtk_text_buffer_select_range(buffer,
6460 &sel_start, &sel_end);
6462 return FALSE; /* pass the event so that the right-click goes through */
6465 if (event->button == 2) {
6470 /* get the middle-click position to paste at the correct place */
6471 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6472 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6474 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6477 entry_paste_clipboard(compose, text,
6478 prefs_common.linewrap_pastes,
6479 GDK_SELECTION_PRIMARY, &iter);
6487 static void compose_spell_menu_changed(void *data)
6489 Compose *compose = (Compose *)data;
6491 GtkWidget *menuitem;
6492 GtkWidget *parent_item;
6493 GtkMenu *menu = GTK_MENU(gtk_menu_new());
6494 GtkItemFactory *ifactory = gtk_item_factory_from_widget(compose->menubar);
6497 if (compose->gtkaspell == NULL)
6500 parent_item = gtk_item_factory_get_item(ifactory,
6501 "/Spelling/Options");
6503 /* setting the submenu removes /Spelling/Options from the factory
6504 * so we need to save it */
6506 if (parent_item == NULL) {
6507 parent_item = compose->aspell_options_menu;
6508 gtk_menu_item_remove_submenu(GTK_MENU_ITEM(parent_item));
6510 compose->aspell_options_menu = parent_item;
6512 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
6514 spell_menu = g_slist_reverse(spell_menu);
6515 for (items = spell_menu;
6516 items; items = items->next) {
6517 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
6518 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
6519 gtk_widget_show(GTK_WIDGET(menuitem));
6521 g_slist_free(spell_menu);
6523 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
6528 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
6530 Compose *compose = (Compose *)data;
6531 GdkEventButton event;
6534 event.time = gtk_get_current_event_time();
6538 return text_clicked(compose->text, &event, compose);
6541 static gboolean compose_force_window_origin = TRUE;
6542 static Compose *compose_create(PrefsAccount *account,
6551 GtkWidget *handlebox;
6553 GtkWidget *notebook;
6555 GtkWidget *attach_hbox;
6556 GtkWidget *attach_lab1;
6557 GtkWidget *attach_lab2;
6562 GtkWidget *subject_hbox;
6563 GtkWidget *subject_frame;
6564 GtkWidget *subject_entry;
6568 GtkWidget *edit_vbox;
6569 GtkWidget *ruler_hbox;
6571 GtkWidget *scrolledwin;
6573 GtkTextBuffer *buffer;
6574 GtkClipboard *clipboard;
6576 UndoMain *undostruct;
6578 gchar *titles[N_ATTACH_COLS];
6579 guint n_menu_entries;
6580 GtkWidget *popupmenu;
6581 GtkItemFactory *popupfactory;
6582 GtkItemFactory *ifactory;
6583 GtkWidget *tmpl_menu;
6585 GtkWidget *menuitem;
6588 GtkAspell * gtkaspell = NULL;
6591 static GdkGeometry geometry;
6593 g_return_val_if_fail(account != NULL, NULL);
6595 debug_print("Creating compose window...\n");
6596 compose = g_new0(Compose, 1);
6598 titles[COL_MIMETYPE] = _("MIME type");
6599 titles[COL_SIZE] = _("Size");
6600 titles[COL_NAME] = _("Name");
6602 compose->batch = batch;
6603 compose->account = account;
6604 compose->folder = folder;
6606 compose->mutex = g_mutex_new();
6607 compose->set_cursor_pos = -1;
6609 compose->tooltips = gtk_tooltips_new();
6611 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
6613 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
6614 gtk_widget_set_size_request(window, -1, prefs_common.compose_height);
6616 if (!geometry.max_width) {
6617 geometry.max_width = gdk_screen_width();
6618 geometry.max_height = gdk_screen_height();
6621 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
6622 &geometry, GDK_HINT_MAX_SIZE);
6623 if (!geometry.min_width) {
6624 geometry.min_width = 600;
6625 geometry.min_height = 480;
6627 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
6628 &geometry, GDK_HINT_MIN_SIZE);
6631 if (compose_force_window_origin)
6632 gtk_widget_set_uposition(window, prefs_common.compose_x,
6633 prefs_common.compose_y);
6635 g_signal_connect(G_OBJECT(window), "delete_event",
6636 G_CALLBACK(compose_delete_cb), compose);
6637 MANAGE_WINDOW_SIGNALS_CONNECT(window);
6638 gtk_widget_realize(window);
6640 gtkut_widget_set_composer_icon(window);
6642 vbox = gtk_vbox_new(FALSE, 0);
6643 gtk_container_add(GTK_CONTAINER(window), vbox);
6645 n_menu_entries = sizeof(compose_entries) / sizeof(compose_entries[0]);
6646 menubar = menubar_create(window, compose_entries,
6647 n_menu_entries, "<Compose>", compose);
6648 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
6650 if (prefs_common.toolbar_detachable) {
6651 handlebox = gtk_handle_box_new();
6653 handlebox = gtk_hbox_new(FALSE, 0);
6655 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
6657 gtk_widget_realize(handlebox);
6659 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, window,
6662 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
6666 vbox2 = gtk_vbox_new(FALSE, 2);
6667 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
6668 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
6671 notebook = gtk_notebook_new();
6672 gtk_widget_set_size_request(notebook, -1, 130);
6673 gtk_widget_show(notebook);
6675 /* header labels and entries */
6676 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6677 compose_create_header(compose),
6678 gtk_label_new_with_mnemonic(_("Hea_der")));
6679 /* attachment list */
6680 attach_hbox = gtk_hbox_new(FALSE, 0);
6681 gtk_widget_show(attach_hbox);
6683 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
6684 gtk_widget_show(attach_lab1);
6685 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
6687 attach_lab2 = gtk_label_new("");
6688 gtk_widget_show(attach_lab2);
6689 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
6691 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6692 compose_create_attach(compose),
6695 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6696 compose_create_others(compose),
6697 gtk_label_new_with_mnemonic(_("Othe_rs")));
6700 subject_hbox = gtk_hbox_new(FALSE, 0);
6701 gtk_widget_show(subject_hbox);
6703 subject_frame = gtk_frame_new(NULL);
6704 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
6705 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
6706 gtk_widget_show(subject_frame);
6708 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
6709 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
6710 gtk_widget_show(subject);
6712 label = gtk_label_new(_("Subject:"));
6713 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
6714 gtk_widget_show(label);
6716 subject_entry = gtk_entry_new();
6717 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
6718 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
6719 G_CALLBACK(compose_grab_focus_cb), compose);
6720 gtk_widget_show(subject_entry);
6721 compose->subject_entry = subject_entry;
6722 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
6724 edit_vbox = gtk_vbox_new(FALSE, 0);
6726 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
6729 ruler_hbox = gtk_hbox_new(FALSE, 0);
6730 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
6732 ruler = gtk_shruler_new();
6733 gtk_ruler_set_range(GTK_RULER(ruler), 0.0, 100.0, 1.0, 100.0);
6734 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
6738 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
6739 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
6740 GTK_POLICY_AUTOMATIC,
6741 GTK_POLICY_AUTOMATIC);
6742 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
6744 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
6745 gtk_widget_set_size_request(scrolledwin, prefs_common.compose_width, -1);
6747 text = gtk_text_view_new();
6748 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
6749 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
6750 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
6751 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
6752 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
6754 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
6756 g_signal_connect_after(G_OBJECT(text), "size_allocate",
6757 G_CALLBACK(compose_edit_size_alloc),
6759 g_signal_connect(G_OBJECT(buffer), "changed",
6760 G_CALLBACK(compose_changed_cb), compose);
6761 g_signal_connect(G_OBJECT(text), "grab_focus",
6762 G_CALLBACK(compose_grab_focus_cb), compose);
6763 g_signal_connect(G_OBJECT(buffer), "insert_text",
6764 G_CALLBACK(text_inserted), compose);
6765 g_signal_connect(G_OBJECT(text), "button_press_event",
6766 G_CALLBACK(text_clicked), compose);
6768 g_signal_connect(G_OBJECT(text), "popup-menu",
6769 G_CALLBACK(compose_popup_menu), compose);
6771 gtk_widget_tap_and_hold_setup(GTK_WIDGET(text), NULL, NULL,
6772 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
6773 g_signal_connect(G_OBJECT(text), "tap-and-hold",
6774 G_CALLBACK(compose_popup_menu), compose);
6776 g_signal_connect(G_OBJECT(subject_entry), "changed",
6777 G_CALLBACK(compose_changed_cb), compose);
6780 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6781 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6782 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6783 g_signal_connect(G_OBJECT(text), "drag_data_received",
6784 G_CALLBACK(compose_insert_drag_received_cb),
6786 g_signal_connect(G_OBJECT(text), "drag-drop",
6787 G_CALLBACK(compose_drag_drop),
6789 gtk_widget_show_all(vbox);
6791 /* pane between attach clist and text */
6792 paned = gtk_vpaned_new();
6793 gtk_paned_set_gutter_size(GTK_PANED(paned), 12);
6794 gtk_container_add(GTK_CONTAINER(vbox2), paned);
6796 if( maemo_mainwindow_is_fullscreen(mainwindow_get_mainwindow()->window) )
6797 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 300 : 280);
6799 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 250 : 230);
6801 gtk_paned_add1(GTK_PANED(paned), notebook);
6802 gtk_paned_add2(GTK_PANED(paned), edit_vbox);
6803 gtk_widget_show_all(paned);
6806 if (prefs_common.textfont) {
6807 PangoFontDescription *font_desc;
6809 font_desc = pango_font_description_from_string
6810 (prefs_common.textfont);
6812 gtk_widget_modify_font(text, font_desc);
6813 pango_font_description_free(font_desc);
6817 n_entries = sizeof(compose_popup_entries) /
6818 sizeof(compose_popup_entries[0]);
6819 popupmenu = menu_create_items(compose_popup_entries, n_entries,
6820 "<Compose>", &popupfactory,
6823 ifactory = gtk_item_factory_from_widget(menubar);
6824 menu_set_sensitive(ifactory, "/Edit/Undo", FALSE);
6825 menu_set_sensitive(ifactory, "/Edit/Redo", FALSE);
6826 menu_set_sensitive(ifactory, "/Options/Remove references", FALSE);
6828 tmpl_menu = gtk_item_factory_get_item(ifactory, "/Tools/Template");
6830 undostruct = undo_init(text);
6831 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
6834 address_completion_start(window);
6836 compose->window = window;
6837 compose->vbox = vbox;
6838 compose->menubar = menubar;
6839 compose->handlebox = handlebox;
6841 compose->vbox2 = vbox2;
6843 compose->paned = paned;
6845 compose->attach_label = attach_lab2;
6847 compose->notebook = notebook;
6848 compose->edit_vbox = edit_vbox;
6849 compose->ruler_hbox = ruler_hbox;
6850 compose->ruler = ruler;
6851 compose->scrolledwin = scrolledwin;
6852 compose->text = text;
6854 compose->focused_editable = NULL;
6856 compose->popupmenu = popupmenu;
6857 compose->popupfactory = popupfactory;
6859 compose->tmpl_menu = tmpl_menu;
6861 compose->mode = mode;
6862 compose->rmode = mode;
6864 compose->targetinfo = NULL;
6865 compose->replyinfo = NULL;
6866 compose->fwdinfo = NULL;
6868 compose->replyto = NULL;
6870 compose->bcc = NULL;
6871 compose->followup_to = NULL;
6873 compose->ml_post = NULL;
6875 compose->inreplyto = NULL;
6876 compose->references = NULL;
6877 compose->msgid = NULL;
6878 compose->boundary = NULL;
6880 compose->autowrap = prefs_common.autowrap;
6882 compose->use_signing = FALSE;
6883 compose->use_encryption = FALSE;
6884 compose->privacy_system = NULL;
6886 compose->modified = FALSE;
6888 compose->return_receipt = FALSE;
6890 compose->to_list = NULL;
6891 compose->newsgroup_list = NULL;
6893 compose->undostruct = undostruct;
6895 compose->sig_str = NULL;
6897 compose->exteditor_file = NULL;
6898 compose->exteditor_pid = -1;
6899 compose->exteditor_tag = -1;
6900 compose->draft_timeout_tag = -2; /* inhibit auto-drafting while loading */
6903 menu_set_sensitive(ifactory, "/Spelling", FALSE);
6904 if (mode != COMPOSE_REDIRECT) {
6905 if (prefs_common.enable_aspell && prefs_common.dictionary &&
6906 strcmp(prefs_common.dictionary, "")) {
6907 gtkaspell = gtkaspell_new(prefs_common.aspell_path,
6908 prefs_common.dictionary,
6909 prefs_common.alt_dictionary,
6910 conv_get_locale_charset_str(),
6911 prefs_common.misspelled_col,
6912 prefs_common.check_while_typing,
6913 prefs_common.recheck_when_changing_dict,
6914 prefs_common.use_alternate,
6915 prefs_common.use_both_dicts,
6916 GTK_TEXT_VIEW(text),
6917 GTK_WINDOW(compose->window),
6918 compose_spell_menu_changed,
6921 alertpanel_error(_("Spell checker could not "
6923 gtkaspell_checkers_strerror());
6924 gtkaspell_checkers_reset_error();
6926 if (!gtkaspell_set_sug_mode(gtkaspell,
6927 prefs_common.aspell_sugmode)) {
6928 debug_print("Aspell: could not set "
6929 "suggestion mode %s\n",
6930 gtkaspell_checkers_strerror());
6931 gtkaspell_checkers_reset_error();
6934 menu_set_sensitive(ifactory, "/Spelling", TRUE);
6938 compose->gtkaspell = gtkaspell;
6939 compose_spell_menu_changed(compose);
6942 compose_select_account(compose, account, TRUE);
6944 menu_set_active(ifactory, "/Edit/Auto wrapping", prefs_common.autowrap);
6945 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
6946 compose_entry_append(compose, account->auto_cc, COMPOSE_CC);
6948 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
6949 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC);
6951 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
6952 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO);
6954 menu_set_sensitive(ifactory, "/Options/Reply mode", compose->mode == COMPOSE_REPLY);
6956 if (account->protocol != A_NNTP)
6957 gtk_entry_set_text(GTK_ENTRY(GTK_BIN(compose->header_last->combo)->child),
6958 prefs_common_translated_header_name("To:"));
6960 gtk_entry_set_text(GTK_ENTRY(GTK_BIN(compose->header_last->combo)->child),
6961 prefs_common_translated_header_name("Newsgroups:"));
6963 addressbook_set_target_compose(compose);
6965 if (mode != COMPOSE_REDIRECT)
6966 compose_set_template_menu(compose);
6968 menuitem = gtk_item_factory_get_item(ifactory, "/Tools/Template");
6969 menu_set_sensitive(ifactory, "/Tools/Template", FALSE);
6972 compose_list = g_list_append(compose_list, compose);
6974 if (!prefs_common.show_ruler)
6975 gtk_widget_hide(ruler_hbox);
6977 menuitem = gtk_item_factory_get_item(ifactory, "/Tools/Show ruler");
6978 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
6979 prefs_common.show_ruler);
6982 compose->priority = PRIORITY_NORMAL;
6983 compose_update_priority_menu_item(compose);
6985 compose_set_out_encoding(compose);
6988 compose_update_actions_menu(compose);
6990 /* Privacy Systems menu */
6991 compose_update_privacy_systems_menu(compose);
6993 activate_privacy_system(compose, account, TRUE);
6994 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
6996 gtk_widget_realize(window);
6998 gtk_widget_show(window);
7000 maemo_window_full_screen_if_needed(GTK_WINDOW(window));
7001 maemo_connect_key_press_to_mainwindow(GTK_WINDOW(window));
7008 static GtkWidget *compose_account_option_menu_create(Compose *compose)
7013 GtkWidget *optmenubox;
7016 GtkWidget *from_name = NULL;
7018 gint num = 0, def_menu = 0;
7020 accounts = account_get_list();
7021 g_return_val_if_fail(accounts != NULL, NULL);
7023 optmenubox = gtk_event_box_new();
7024 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
7025 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
7027 hbox = gtk_hbox_new(FALSE, 6);
7028 from_name = gtk_entry_new();
7030 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
7031 G_CALLBACK(compose_grab_focus_cb), compose);
7033 for (; accounts != NULL; accounts = accounts->next, num++) {
7034 PrefsAccount *ac = (PrefsAccount *)accounts->data;
7035 gchar *name, *from = NULL;
7037 if (ac == compose->account) def_menu = num;
7039 name = g_markup_printf_escaped(_("From: <i>%s</i>"),
7042 if (ac == compose->account) {
7043 if (ac->name && *ac->name) {
7045 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
7046 from = g_strdup_printf("%s <%s>",
7048 gtk_entry_set_text(GTK_ENTRY(from_name), from);
7050 from = g_strdup_printf("%s",
7052 gtk_entry_set_text(GTK_ENTRY(from_name), from);
7055 COMBOBOX_ADD(menu, name, ac->account_id);
7060 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
7062 g_signal_connect(G_OBJECT(optmenu), "changed",
7063 G_CALLBACK(account_activated),
7065 g_signal_connect(G_OBJECT(from_name), "populate-popup",
7066 G_CALLBACK(compose_entry_popup_extend),
7069 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
7070 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
7072 gtk_tooltips_set_tip(compose->tooltips, optmenubox,
7073 _("Account to use for this email"), NULL);
7074 gtk_tooltips_set_tip(compose->tooltips, from_name,
7075 _("Sender address to be used"), NULL);
7077 compose->from_name = from_name;
7082 static void compose_set_priority_cb(gpointer data,
7086 Compose *compose = (Compose *) data;
7087 compose->priority = action;
7090 static void compose_reply_change_mode(gpointer data,
7094 Compose *compose = (Compose *) data;
7095 gboolean was_modified = compose->modified;
7097 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
7099 g_return_if_fail(compose->replyinfo != NULL);
7101 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
7103 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
7105 if (action == COMPOSE_REPLY_TO_ALL)
7107 if (action == COMPOSE_REPLY_TO_SENDER)
7109 if (action == COMPOSE_REPLY_TO_LIST)
7112 compose_remove_header_entries(compose);
7113 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
7114 if (compose->account->set_autocc && compose->account->auto_cc)
7115 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC);
7117 if (compose->account->set_autobcc && compose->account->auto_bcc)
7118 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC);
7120 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
7121 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO);
7122 compose_show_first_last_header(compose, TRUE);
7123 compose->modified = was_modified;
7124 compose_set_title(compose);
7127 static void compose_update_priority_menu_item(Compose * compose)
7129 GtkItemFactory *ifactory;
7130 GtkWidget *menuitem = NULL;
7132 ifactory = gtk_item_factory_from_widget(compose->menubar);
7134 switch (compose->priority) {
7135 case PRIORITY_HIGHEST:
7136 menuitem = gtk_item_factory_get_item
7137 (ifactory, "/Options/Priority/Highest");
7140 menuitem = gtk_item_factory_get_item
7141 (ifactory, "/Options/Priority/High");
7143 case PRIORITY_NORMAL:
7144 menuitem = gtk_item_factory_get_item
7145 (ifactory, "/Options/Priority/Normal");
7148 menuitem = gtk_item_factory_get_item
7149 (ifactory, "/Options/Priority/Low");
7151 case PRIORITY_LOWEST:
7152 menuitem = gtk_item_factory_get_item
7153 (ifactory, "/Options/Priority/Lowest");
7156 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
7159 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
7161 Compose *compose = (Compose *) data;
7163 GtkItemFactory *ifactory;
7164 gboolean can_sign = FALSE, can_encrypt = FALSE;
7166 g_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
7168 if (!GTK_CHECK_MENU_ITEM(widget)->active)
7171 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
7172 g_free(compose->privacy_system);
7173 compose->privacy_system = NULL;
7174 if (systemid != NULL) {
7175 compose->privacy_system = g_strdup(systemid);
7177 can_sign = privacy_system_can_sign(systemid);
7178 can_encrypt = privacy_system_can_encrypt(systemid);
7181 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
7183 ifactory = gtk_item_factory_from_widget(compose->menubar);
7184 menu_set_sensitive(ifactory, "/Options/Sign", can_sign);
7185 menu_set_sensitive(ifactory, "/Options/Encrypt", can_encrypt);
7188 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
7190 static gchar *branch_path = "/Options/Privacy System";
7191 GtkItemFactory *ifactory;
7192 GtkWidget *menuitem = NULL;
7194 gboolean can_sign = FALSE, can_encrypt = FALSE;
7195 gboolean found = FALSE;
7197 ifactory = gtk_item_factory_from_widget(compose->menubar);
7199 if (compose->privacy_system != NULL) {
7201 menuitem = gtk_item_factory_get_widget(ifactory, branch_path);
7202 g_return_if_fail(menuitem != NULL);
7204 amenu = GTK_MENU_SHELL(menuitem)->children;
7206 while (amenu != NULL) {
7207 GList *alist = amenu->next;
7209 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
7210 if (systemid != NULL) {
7211 if (strcmp(systemid, compose->privacy_system) == 0) {
7212 menuitem = GTK_WIDGET(amenu->data);
7214 can_sign = privacy_system_can_sign(systemid);
7215 can_encrypt = privacy_system_can_encrypt(systemid);
7219 } else if (strlen(compose->privacy_system) == 0) {
7220 menuitem = GTK_WIDGET(amenu->data);
7223 can_encrypt = FALSE;
7230 if (menuitem != NULL)
7231 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
7233 if (warn && !found && strlen(compose->privacy_system)) {
7234 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
7235 "will not be able to sign or encrypt this message."),
7236 compose->privacy_system);
7240 menu_set_sensitive(ifactory, "/Options/Sign", can_sign);
7241 menu_set_sensitive(ifactory, "/Options/Encrypt", can_encrypt);
7244 static void compose_set_out_encoding(Compose *compose)
7246 GtkItemFactoryEntry *entry;
7247 GtkItemFactory *ifactory;
7248 CharSet out_encoding;
7249 gchar *path, *p, *q;
7252 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
7253 ifactory = gtk_item_factory_from_widget(compose->menubar);
7255 for (entry = compose_entries; entry->callback != compose_address_cb;
7257 if (entry->callback == compose_set_encoding_cb &&
7258 (CharSet)entry->callback_action == out_encoding) {
7259 p = q = path = g_strdup(entry->path);
7271 item = gtk_item_factory_get_item(ifactory, path);
7272 gtk_widget_activate(item);
7279 static void compose_set_template_menu(Compose *compose)
7281 GSList *tmpl_list, *cur;
7285 tmpl_list = template_get_config();
7287 menu = gtk_menu_new();
7289 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
7290 Template *tmpl = (Template *)cur->data;
7292 item = gtk_menu_item_new_with_label(tmpl->name);
7293 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
7294 g_signal_connect(G_OBJECT(item), "activate",
7295 G_CALLBACK(compose_template_activate_cb),
7297 g_object_set_data(G_OBJECT(item), "template", tmpl);
7298 gtk_widget_show(item);
7301 gtk_widget_show(menu);
7302 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
7305 void compose_update_actions_menu(Compose *compose)
7307 GtkItemFactory *ifactory;
7309 ifactory = gtk_item_factory_from_widget(compose->menubar);
7310 action_update_compose_menu(ifactory, "/Tools/Actions", compose);
7313 static void compose_update_privacy_systems_menu(Compose *compose)
7315 static gchar *branch_path = "/Options/Privacy System";
7316 GtkItemFactory *ifactory;
7317 GtkWidget *menuitem;
7318 GSList *systems, *cur;
7321 GtkWidget *system_none;
7324 ifactory = gtk_item_factory_from_widget(compose->menubar);
7326 /* remove old entries */
7327 menuitem = gtk_item_factory_get_widget(ifactory, branch_path);
7328 g_return_if_fail(menuitem != NULL);
7330 amenu = GTK_MENU_SHELL(menuitem)->children->next;
7331 while (amenu != NULL) {
7332 GList *alist = amenu->next;
7333 gtk_widget_destroy(GTK_WIDGET(amenu->data));
7337 system_none = gtk_item_factory_get_widget(ifactory,
7338 "/Options/Privacy System/None");
7340 g_signal_connect(G_OBJECT(system_none), "activate",
7341 G_CALLBACK(compose_set_privacy_system_cb), compose);
7343 systems = privacy_get_system_ids();
7344 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
7345 gchar *systemid = cur->data;
7347 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
7348 widget = gtk_radio_menu_item_new_with_label(group,
7349 privacy_system_get_name(systemid));
7350 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
7351 g_strdup(systemid), g_free);
7352 g_signal_connect(G_OBJECT(widget), "activate",
7353 G_CALLBACK(compose_set_privacy_system_cb), compose);
7355 gtk_menu_append(GTK_MENU(system_none->parent), widget);
7356 gtk_widget_show(widget);
7359 g_slist_free(systems);
7362 void compose_reflect_prefs_all(void)
7367 for (cur = compose_list; cur != NULL; cur = cur->next) {
7368 compose = (Compose *)cur->data;
7369 compose_set_template_menu(compose);
7373 void compose_reflect_prefs_pixmap_theme(void)
7378 for (cur = compose_list; cur != NULL; cur = cur->next) {
7379 compose = (Compose *)cur->data;
7380 toolbar_update(TOOLBAR_COMPOSE, compose);
7384 static const gchar *compose_quote_char_from_context(Compose *compose)
7386 const gchar *qmark = NULL;
7388 g_return_val_if_fail(compose != NULL, NULL);
7390 switch (compose->mode) {
7391 /* use forward-specific quote char */
7392 case COMPOSE_FORWARD:
7393 case COMPOSE_FORWARD_AS_ATTACH:
7394 case COMPOSE_FORWARD_INLINE:
7395 if (compose->folder && compose->folder->prefs &&
7396 compose->folder->prefs->forward_with_format)
7397 qmark = compose->folder->prefs->forward_quotemark;
7398 else if (compose->account->forward_with_format)
7399 qmark = compose->account->forward_quotemark;
7401 qmark = prefs_common.fw_quotemark;
7404 /* use reply-specific quote char in all other modes */
7406 if (compose->folder && compose->folder->prefs &&
7407 compose->folder->prefs->reply_with_format)
7408 qmark = compose->folder->prefs->reply_quotemark;
7409 else if (compose->account->reply_with_format)
7410 qmark = compose->account->reply_quotemark;
7412 qmark = prefs_common.quotemark;
7416 if (qmark == NULL || *qmark == '\0')
7422 static void compose_template_apply(Compose *compose, Template *tmpl,
7426 GtkTextBuffer *buffer;
7430 gchar *parsed_str = NULL;
7431 gint cursor_pos = 0;
7432 const gchar *err_msg = _("Template body format error at line %d.");
7435 /* process the body */
7437 text = GTK_TEXT_VIEW(compose->text);
7438 buffer = gtk_text_view_get_buffer(text);
7441 qmark = compose_quote_char_from_context(compose);
7443 if (compose->replyinfo != NULL) {
7446 gtk_text_buffer_set_text(buffer, "", -1);
7447 mark = gtk_text_buffer_get_insert(buffer);
7448 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7450 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
7451 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
7453 } else if (compose->fwdinfo != NULL) {
7456 gtk_text_buffer_set_text(buffer, "", -1);
7457 mark = gtk_text_buffer_get_insert(buffer);
7458 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7460 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
7461 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
7464 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
7466 GtkTextIter start, end;
7469 gtk_text_buffer_get_start_iter(buffer, &start);
7470 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
7471 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
7473 /* clear the buffer now */
7475 gtk_text_buffer_set_text(buffer, "", -1);
7477 parsed_str = compose_quote_fmt(compose, dummyinfo,
7478 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
7479 procmsg_msginfo_free( dummyinfo );
7485 gtk_text_buffer_set_text(buffer, "", -1);
7486 mark = gtk_text_buffer_get_insert(buffer);
7487 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7490 if (replace && parsed_str && compose->account->auto_sig)
7491 compose_insert_sig(compose, FALSE);
7493 if (replace && parsed_str) {
7494 gtk_text_buffer_get_start_iter(buffer, &iter);
7495 gtk_text_buffer_place_cursor(buffer, &iter);
7499 cursor_pos = quote_fmt_get_cursor_pos();
7500 compose->set_cursor_pos = cursor_pos;
7501 if (cursor_pos == -1)
7503 gtk_text_buffer_get_start_iter(buffer, &iter);
7504 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
7505 gtk_text_buffer_place_cursor(buffer, &iter);
7508 /* process the other fields */
7510 compose_template_apply_fields(compose, tmpl);
7511 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
7512 quote_fmt_reset_vartable();
7513 compose_changed_cb(NULL, compose);
7516 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
7518 MsgInfo* dummyinfo = NULL;
7519 MsgInfo *msginfo = NULL;
7522 if (compose->replyinfo != NULL)
7523 msginfo = compose->replyinfo;
7524 else if (compose->fwdinfo != NULL)
7525 msginfo = compose->fwdinfo;
7527 dummyinfo = compose_msginfo_new_from_compose(compose);
7528 msginfo = dummyinfo;
7531 if (tmpl->to && *tmpl->to != '\0') {
7533 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
7534 compose->gtkaspell);
7536 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
7538 quote_fmt_scan_string(tmpl->to);
7541 buf = quote_fmt_get_buffer();
7543 alertpanel_error(_("Template To format error."));
7545 compose_entry_append(compose, buf, COMPOSE_TO);
7549 if (tmpl->cc && *tmpl->cc != '\0') {
7551 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
7552 compose->gtkaspell);
7554 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
7556 quote_fmt_scan_string(tmpl->cc);
7559 buf = quote_fmt_get_buffer();
7561 alertpanel_error(_("Template Cc format error."));
7563 compose_entry_append(compose, buf, COMPOSE_CC);
7567 if (tmpl->bcc && *tmpl->bcc != '\0') {
7569 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
7570 compose->gtkaspell);
7572 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
7574 quote_fmt_scan_string(tmpl->bcc);
7577 buf = quote_fmt_get_buffer();
7579 alertpanel_error(_("Template Bcc format error."));
7581 compose_entry_append(compose, buf, COMPOSE_BCC);
7585 /* process the subject */
7586 if (tmpl->subject && *tmpl->subject != '\0') {
7588 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
7589 compose->gtkaspell);
7591 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
7593 quote_fmt_scan_string(tmpl->subject);
7596 buf = quote_fmt_get_buffer();
7598 alertpanel_error(_("Template subject format error."));
7600 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
7604 procmsg_msginfo_free( dummyinfo );
7607 static void compose_destroy(Compose *compose)
7609 GtkTextBuffer *buffer;
7610 GtkClipboard *clipboard;
7612 compose_list = g_list_remove(compose_list, compose);
7614 if (compose->updating) {
7615 debug_print("danger, not destroying anything now\n");
7616 compose->deferred_destroy = TRUE;
7619 /* NOTE: address_completion_end() does nothing with the window
7620 * however this may change. */
7621 address_completion_end(compose->window);
7623 slist_free_strings(compose->to_list);
7624 g_slist_free(compose->to_list);
7625 slist_free_strings(compose->newsgroup_list);
7626 g_slist_free(compose->newsgroup_list);
7627 slist_free_strings(compose->header_list);
7628 g_slist_free(compose->header_list);
7630 procmsg_msginfo_free(compose->targetinfo);
7631 procmsg_msginfo_free(compose->replyinfo);
7632 procmsg_msginfo_free(compose->fwdinfo);
7634 g_free(compose->replyto);
7635 g_free(compose->cc);
7636 g_free(compose->bcc);
7637 g_free(compose->newsgroups);
7638 g_free(compose->followup_to);
7640 g_free(compose->ml_post);
7642 g_free(compose->inreplyto);
7643 g_free(compose->references);
7644 g_free(compose->msgid);
7645 g_free(compose->boundary);
7647 g_free(compose->redirect_filename);
7648 if (compose->undostruct)
7649 undo_destroy(compose->undostruct);
7651 g_free(compose->sig_str);
7653 g_free(compose->exteditor_file);
7655 g_free(compose->orig_charset);
7657 g_free(compose->privacy_system);
7659 if (addressbook_get_target_compose() == compose)
7660 addressbook_set_target_compose(NULL);
7663 if (compose->gtkaspell) {
7664 gtkaspell_delete(compose->gtkaspell);
7665 compose->gtkaspell = NULL;
7669 prefs_common.compose_width = compose->scrolledwin->allocation.width;
7670 prefs_common.compose_height = compose->window->allocation.height;
7672 if (!gtk_widget_get_parent(compose->paned))
7673 gtk_widget_destroy(compose->paned);
7674 gtk_widget_destroy(compose->popupmenu);
7676 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
7677 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7678 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
7680 gtk_widget_destroy(compose->window);
7681 toolbar_destroy(compose->toolbar);
7682 g_free(compose->toolbar);
7683 g_mutex_free(compose->mutex);
7687 static void compose_attach_info_free(AttachInfo *ainfo)
7689 g_free(ainfo->file);
7690 g_free(ainfo->content_type);
7691 g_free(ainfo->name);
7695 static void compose_attach_update_label(Compose *compose)
7700 GtkTreeModel *model;
7705 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
7706 if(!gtk_tree_model_get_iter_first(model, &iter)) {
7707 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
7711 while(gtk_tree_model_iter_next(model, &iter))
7714 text = g_strdup_printf("(%d)", i);
7715 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
7719 static void compose_attach_remove_selected(Compose *compose)
7721 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
7722 GtkTreeSelection *selection;
7724 GtkTreeModel *model;
7726 selection = gtk_tree_view_get_selection(tree_view);
7727 sel = gtk_tree_selection_get_selected_rows(selection, &model);
7732 for (cur = sel; cur != NULL; cur = cur->next) {
7733 GtkTreePath *path = cur->data;
7734 GtkTreeRowReference *ref = gtk_tree_row_reference_new
7737 gtk_tree_path_free(path);
7740 for (cur = sel; cur != NULL; cur = cur->next) {
7741 GtkTreeRowReference *ref = cur->data;
7742 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
7745 if (gtk_tree_model_get_iter(model, &iter, path))
7746 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
7748 gtk_tree_path_free(path);
7749 gtk_tree_row_reference_free(ref);
7753 compose_attach_update_label(compose);
7756 static struct _AttachProperty
7759 GtkWidget *mimetype_entry;
7760 GtkWidget *encoding_optmenu;
7761 GtkWidget *path_entry;
7762 GtkWidget *filename_entry;
7764 GtkWidget *cancel_btn;
7767 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
7769 gtk_tree_path_free((GtkTreePath *)ptr);
7772 static void compose_attach_property(Compose *compose)
7774 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
7776 GtkComboBox *optmenu;
7777 GtkTreeSelection *selection;
7779 GtkTreeModel *model;
7782 static gboolean cancelled;
7784 /* only if one selected */
7785 selection = gtk_tree_view_get_selection(tree_view);
7786 if (gtk_tree_selection_count_selected_rows(selection) != 1)
7789 sel = gtk_tree_selection_get_selected_rows(selection, &model);
7793 path = (GtkTreePath *) sel->data;
7794 gtk_tree_model_get_iter(model, &iter, path);
7795 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
7798 g_list_foreach(sel, gtk_tree_path_free_, NULL);
7804 if (!attach_prop.window)
7805 compose_attach_property_create(&cancelled);
7806 gtk_widget_grab_focus(attach_prop.ok_btn);
7807 gtk_widget_show(attach_prop.window);
7808 manage_window_set_transient(GTK_WINDOW(attach_prop.window));
7810 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
7811 if (ainfo->encoding == ENC_UNKNOWN)
7812 combobox_select_by_data(optmenu, ENC_BASE64);
7814 combobox_select_by_data(optmenu, ainfo->encoding);
7816 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
7817 ainfo->content_type ? ainfo->content_type : "");
7818 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
7819 ainfo->file ? ainfo->file : "");
7820 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
7821 ainfo->name ? ainfo->name : "");
7824 const gchar *entry_text;
7826 gchar *cnttype = NULL;
7833 gtk_widget_hide(attach_prop.window);
7838 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
7839 if (*entry_text != '\0') {
7842 text = g_strstrip(g_strdup(entry_text));
7843 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
7844 cnttype = g_strdup(text);
7847 alertpanel_error(_("Invalid MIME type."));
7853 ainfo->encoding = combobox_get_active_data(optmenu);
7855 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
7856 if (*entry_text != '\0') {
7857 if (is_file_exist(entry_text) &&
7858 (size = get_file_size(entry_text)) > 0)
7859 file = g_strdup(entry_text);
7862 (_("File doesn't exist or is empty."));
7868 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
7869 if (*entry_text != '\0') {
7870 g_free(ainfo->name);
7871 ainfo->name = g_strdup(entry_text);
7875 g_free(ainfo->content_type);
7876 ainfo->content_type = cnttype;
7879 g_free(ainfo->file);
7885 /* update tree store */
7886 text = to_human_readable(ainfo->size);
7887 gtk_tree_model_get_iter(model, &iter, path);
7888 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
7889 COL_MIMETYPE, ainfo->content_type,
7891 COL_NAME, ainfo->name,
7897 gtk_tree_path_free(path);
7900 #define SET_LABEL_AND_ENTRY(str, entry, top) \
7902 label = gtk_label_new(str); \
7903 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
7904 GTK_FILL, 0, 0, 0); \
7905 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
7907 entry = gtk_entry_new(); \
7908 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
7909 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
7912 static void compose_attach_property_create(gboolean *cancelled)
7918 GtkWidget *mimetype_entry;
7921 GtkListStore *optmenu_menu;
7922 GtkWidget *path_entry;
7923 GtkWidget *filename_entry;
7926 GtkWidget *cancel_btn;
7927 GList *mime_type_list, *strlist;
7930 debug_print("Creating attach_property window...\n");
7932 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
7933 gtk_widget_set_size_request(window, 480, -1);
7934 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
7935 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
7936 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
7937 gtk_window_set_modal(GTK_WINDOW(window), TRUE);
7938 g_signal_connect(G_OBJECT(window), "delete_event",
7939 G_CALLBACK(attach_property_delete_event),
7941 g_signal_connect(G_OBJECT(window), "key_press_event",
7942 G_CALLBACK(attach_property_key_pressed),
7945 vbox = gtk_vbox_new(FALSE, 8);
7946 gtk_container_add(GTK_CONTAINER(window), vbox);
7948 table = gtk_table_new(4, 2, FALSE);
7949 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
7950 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
7951 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
7953 label = gtk_label_new(_("MIME type"));
7954 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
7956 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
7957 mimetype_entry = gtk_combo_box_entry_new_text();
7958 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
7959 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
7961 /* stuff with list */
7962 mime_type_list = procmime_get_mime_type_list();
7964 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
7965 MimeType *type = (MimeType *) mime_type_list->data;
7968 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
7970 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
7973 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
7974 (GCompareFunc)strcmp2);
7977 for (mime_type_list = strlist; mime_type_list != NULL;
7978 mime_type_list = mime_type_list->next) {
7979 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
7980 g_free(mime_type_list->data);
7982 g_list_free(strlist);
7983 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
7984 mimetype_entry = GTK_BIN(mimetype_entry)->child;
7986 label = gtk_label_new(_("Encoding"));
7987 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
7989 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
7991 hbox = gtk_hbox_new(FALSE, 0);
7992 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
7993 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
7995 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
7996 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
7998 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
7999 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
8000 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
8001 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
8002 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
8004 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
8006 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
8007 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
8009 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
8010 &ok_btn, GTK_STOCK_OK,
8012 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
8013 gtk_widget_grab_default(ok_btn);
8015 g_signal_connect(G_OBJECT(ok_btn), "clicked",
8016 G_CALLBACK(attach_property_ok),
8018 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
8019 G_CALLBACK(attach_property_cancel),
8022 gtk_widget_show_all(vbox);
8024 attach_prop.window = window;
8025 attach_prop.mimetype_entry = mimetype_entry;
8026 attach_prop.encoding_optmenu = optmenu;
8027 attach_prop.path_entry = path_entry;
8028 attach_prop.filename_entry = filename_entry;
8029 attach_prop.ok_btn = ok_btn;
8030 attach_prop.cancel_btn = cancel_btn;
8033 #undef SET_LABEL_AND_ENTRY
8035 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
8041 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
8047 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
8048 gboolean *cancelled)
8056 static gboolean attach_property_key_pressed(GtkWidget *widget,
8058 gboolean *cancelled)
8060 if (event && event->keyval == GDK_Escape) {
8067 static void compose_exec_ext_editor(Compose *compose)
8074 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
8075 G_DIR_SEPARATOR, compose);
8077 if (pipe(pipe_fds) < 0) {
8083 if ((pid = fork()) < 0) {
8090 /* close the write side of the pipe */
8093 compose->exteditor_file = g_strdup(tmp);
8094 compose->exteditor_pid = pid;
8096 compose_set_ext_editor_sensitive(compose, FALSE);
8098 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
8099 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
8103 } else { /* process-monitoring process */
8109 /* close the read side of the pipe */
8112 if (compose_write_body_to_file(compose, tmp) < 0) {
8113 fd_write_all(pipe_fds[1], "2\n", 2);
8117 pid_ed = compose_exec_ext_editor_real(tmp);
8119 fd_write_all(pipe_fds[1], "1\n", 2);
8123 /* wait until editor is terminated */
8124 waitpid(pid_ed, NULL, 0);
8126 fd_write_all(pipe_fds[1], "0\n", 2);
8133 #endif /* G_OS_UNIX */
8137 static gint compose_exec_ext_editor_real(const gchar *file)
8144 g_return_val_if_fail(file != NULL, -1);
8146 if ((pid = fork()) < 0) {
8151 if (pid != 0) return pid;
8153 /* grandchild process */
8155 if (setpgid(0, getppid()))
8158 if (prefs_common_get_ext_editor_cmd() &&
8159 (p = strchr(prefs_common_get_ext_editor_cmd(), '%')) &&
8160 *(p + 1) == 's' && !strchr(p + 2, '%')) {
8161 g_snprintf(buf, sizeof(buf), prefs_common_get_ext_editor_cmd(), file);
8163 if (prefs_common_get_ext_editor_cmd())
8164 g_warning("External editor command line is invalid: '%s'\n",
8165 prefs_common_get_ext_editor_cmd());
8166 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
8169 cmdline = strsplit_with_quote(buf, " ", 1024);
8170 execvp(cmdline[0], cmdline);
8173 g_strfreev(cmdline);
8178 static gboolean compose_ext_editor_kill(Compose *compose)
8180 pid_t pgid = compose->exteditor_pid * -1;
8183 ret = kill(pgid, 0);
8185 if (ret == 0 || (ret == -1 && EPERM == errno)) {
8189 msg = g_strdup_printf
8190 (_("The external editor is still working.\n"
8191 "Force terminating the process?\n"
8192 "process group id: %d"), -pgid);
8193 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
8194 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
8198 if (val == G_ALERTALTERNATE) {
8199 g_source_remove(compose->exteditor_tag);
8200 g_io_channel_shutdown(compose->exteditor_ch,
8202 g_io_channel_unref(compose->exteditor_ch);
8204 if (kill(pgid, SIGTERM) < 0) perror("kill");
8205 waitpid(compose->exteditor_pid, NULL, 0);
8207 g_warning("Terminated process group id: %d", -pgid);
8208 g_warning("Temporary file: %s",
8209 compose->exteditor_file);
8211 compose_set_ext_editor_sensitive(compose, TRUE);
8213 g_free(compose->exteditor_file);
8214 compose->exteditor_file = NULL;
8215 compose->exteditor_pid = -1;
8216 compose->exteditor_ch = NULL;
8217 compose->exteditor_tag = -1;
8225 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
8229 Compose *compose = (Compose *)data;
8232 debug_print(_("Compose: input from monitoring process\n"));
8234 g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL);
8236 g_io_channel_shutdown(source, FALSE, NULL);
8237 g_io_channel_unref(source);
8239 waitpid(compose->exteditor_pid, NULL, 0);
8241 if (buf[0] == '0') { /* success */
8242 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
8243 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
8245 gtk_text_buffer_set_text(buffer, "", -1);
8246 compose_insert_file(compose, compose->exteditor_file);
8247 compose_changed_cb(NULL, compose);
8249 if (g_unlink(compose->exteditor_file) < 0)
8250 FILE_OP_ERROR(compose->exteditor_file, "unlink");
8251 } else if (buf[0] == '1') { /* failed */
8252 g_warning("Couldn't exec external editor\n");
8253 if (g_unlink(compose->exteditor_file) < 0)
8254 FILE_OP_ERROR(compose->exteditor_file, "unlink");
8255 } else if (buf[0] == '2') {
8256 g_warning("Couldn't write to file\n");
8257 } else if (buf[0] == '3') {
8258 g_warning("Pipe read failed\n");
8261 compose_set_ext_editor_sensitive(compose, TRUE);
8263 g_free(compose->exteditor_file);
8264 compose->exteditor_file = NULL;
8265 compose->exteditor_pid = -1;
8266 compose->exteditor_ch = NULL;
8267 compose->exteditor_tag = -1;
8272 static void compose_set_ext_editor_sensitive(Compose *compose,
8275 GtkItemFactory *ifactory;
8277 ifactory = gtk_item_factory_from_widget(compose->menubar);
8279 menu_set_sensitive(ifactory, "/Message/Send", sensitive);
8280 menu_set_sensitive(ifactory, "/Message/Send later", sensitive);
8281 menu_set_sensitive(ifactory, "/Message/Insert file", sensitive);
8282 menu_set_sensitive(ifactory, "/Message/Insert signature", sensitive);
8283 menu_set_sensitive(ifactory, "/Edit/Wrap current paragraph", sensitive);
8284 menu_set_sensitive(ifactory, "/Edit/Wrap all long lines", sensitive);
8285 menu_set_sensitive(ifactory, "/Edit/Edit with external editor",
8288 gtk_widget_set_sensitive(compose->text, sensitive);
8289 if (compose->toolbar->send_btn)
8290 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
8291 if (compose->toolbar->sendl_btn)
8292 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
8293 if (compose->toolbar->draft_btn)
8294 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
8295 if (compose->toolbar->insert_btn)
8296 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
8297 if (compose->toolbar->sig_btn)
8298 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
8299 if (compose->toolbar->exteditor_btn)
8300 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
8301 if (compose->toolbar->linewrap_current_btn)
8302 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
8303 if (compose->toolbar->linewrap_all_btn)
8304 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
8306 #endif /* G_OS_UNIX */
8309 * compose_undo_state_changed:
8311 * Change the sensivity of the menuentries undo and redo
8313 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
8314 gint redo_state, gpointer data)
8316 GtkWidget *widget = GTK_WIDGET(data);
8317 GtkItemFactory *ifactory;
8319 g_return_if_fail(widget != NULL);
8321 ifactory = gtk_item_factory_from_widget(widget);
8323 switch (undo_state) {
8324 case UNDO_STATE_TRUE:
8325 if (!undostruct->undo_state) {
8326 undostruct->undo_state = TRUE;
8327 menu_set_sensitive(ifactory, "/Edit/Undo", TRUE);
8330 case UNDO_STATE_FALSE:
8331 if (undostruct->undo_state) {
8332 undostruct->undo_state = FALSE;
8333 menu_set_sensitive(ifactory, "/Edit/Undo", FALSE);
8336 case UNDO_STATE_UNCHANGED:
8338 case UNDO_STATE_REFRESH:
8339 menu_set_sensitive(ifactory, "/Edit/Undo",
8340 undostruct->undo_state);
8343 g_warning("Undo state not recognized");
8347 switch (redo_state) {
8348 case UNDO_STATE_TRUE:
8349 if (!undostruct->redo_state) {
8350 undostruct->redo_state = TRUE;
8351 menu_set_sensitive(ifactory, "/Edit/Redo", TRUE);
8354 case UNDO_STATE_FALSE:
8355 if (undostruct->redo_state) {
8356 undostruct->redo_state = FALSE;
8357 menu_set_sensitive(ifactory, "/Edit/Redo", FALSE);
8360 case UNDO_STATE_UNCHANGED:
8362 case UNDO_STATE_REFRESH:
8363 menu_set_sensitive(ifactory, "/Edit/Redo",
8364 undostruct->redo_state);
8367 g_warning("Redo state not recognized");
8372 /* callback functions */
8374 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
8375 * includes "non-client" (windows-izm) in calculation, so this calculation
8376 * may not be accurate.
8378 static gboolean compose_edit_size_alloc(GtkEditable *widget,
8379 GtkAllocation *allocation,
8380 GtkSHRuler *shruler)
8382 if (prefs_common.show_ruler) {
8383 gint char_width = 0, char_height = 0;
8384 gint line_width_in_chars;
8386 gtkut_get_font_size(GTK_WIDGET(widget),
8387 &char_width, &char_height);
8388 line_width_in_chars =
8389 (allocation->width - allocation->x) / char_width;
8391 /* got the maximum */
8392 gtk_ruler_set_range(GTK_RULER(shruler),
8393 0.0, line_width_in_chars, 0,
8394 /*line_width_in_chars*/ char_width);
8400 static void account_activated(GtkComboBox *optmenu, gpointer data)
8402 Compose *compose = (Compose *)data;
8405 gchar *folderidentifier;
8406 gint account_id = 0;
8410 /* Get ID of active account in the combo box */
8411 menu = gtk_combo_box_get_model(optmenu);
8412 gtk_combo_box_get_active_iter(optmenu, &iter);
8413 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
8415 ac = account_find_from_id(account_id);
8416 g_return_if_fail(ac != NULL);
8418 if (ac != compose->account)
8419 compose_select_account(compose, ac, FALSE);
8421 /* Set message save folder */
8422 if (account_get_special_folder(compose->account, F_OUTBOX)) {
8423 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
8425 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
8426 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
8428 gtk_editable_delete_text(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
8429 if (account_get_special_folder(compose->account, F_OUTBOX)) {
8430 folderidentifier = folder_item_get_identifier(account_get_special_folder
8431 (compose->account, F_OUTBOX));
8432 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
8433 g_free(folderidentifier);
8437 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
8438 GtkTreeViewColumn *column, Compose *compose)
8440 compose_attach_property(compose);
8443 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
8446 Compose *compose = (Compose *)data;
8447 GtkTreeSelection *attach_selection;
8448 gint attach_nr_selected;
8449 GtkItemFactory *ifactory;
8451 if (!event) return FALSE;
8453 if (event->button == 3) {
8454 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
8455 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
8456 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
8458 if (attach_nr_selected > 0)
8460 menu_set_sensitive(ifactory, "/Remove", TRUE);
8461 menu_set_sensitive(ifactory, "/Properties...", TRUE);
8463 menu_set_sensitive(ifactory, "/Remove", FALSE);
8464 menu_set_sensitive(ifactory, "/Properties...", FALSE);
8467 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
8468 NULL, NULL, event->button, event->time);
8475 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
8478 Compose *compose = (Compose *)data;
8480 if (!event) return FALSE;
8482 switch (event->keyval) {
8484 compose_attach_remove_selected(compose);
8490 static void compose_allow_user_actions (Compose *compose, gboolean allow)
8492 GtkItemFactory *ifactory = gtk_item_factory_from_widget(compose->menubar);
8493 toolbar_comp_set_sensitive(compose, allow);
8494 menu_set_sensitive(ifactory, "/Message", allow);
8495 menu_set_sensitive(ifactory, "/Edit", allow);
8497 menu_set_sensitive(ifactory, "/Spelling", allow);
8499 menu_set_sensitive(ifactory, "/Options", allow);
8500 menu_set_sensitive(ifactory, "/Tools", allow);
8501 menu_set_sensitive(ifactory, "/Help", allow);
8503 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
8507 static void compose_send_cb(gpointer data, guint action, GtkWidget *widget)
8509 Compose *compose = (Compose *)data;
8511 if (prefs_common.work_offline &&
8512 !inc_offline_should_override(TRUE,
8513 _("Claws Mail needs network access in order "
8514 "to send this email.")))
8517 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
8518 g_source_remove(compose->draft_timeout_tag);
8519 compose->draft_timeout_tag = -1;
8522 compose_send(compose);
8525 static void compose_send_later_cb(gpointer data, guint action,
8528 Compose *compose = (Compose *)data;
8532 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
8536 compose_close(compose);
8537 } else if (val == -1) {
8538 alertpanel_error(_("Could not queue message."));
8539 } else if (val == -2) {
8540 alertpanel_error(_("Could not queue message:\n\n%s."), strerror(errno));
8541 } else if (val == -3) {
8542 if (privacy_peek_error())
8543 alertpanel_error(_("Could not queue message for sending:\n\n"
8544 "Signature failed: %s"), privacy_get_error());
8545 } else if (val == -4) {
8546 alertpanel_error(_("Could not queue message for sending:\n\n"
8547 "Charset conversion failed."));
8548 } else if (val == -5) {
8549 alertpanel_error(_("Could not queue message for sending:\n\n"
8550 "Couldn't get recipient encryption key."));
8551 } else if (val == -6) {
8554 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
8557 #define DRAFTED_AT_EXIT "drafted_at_exit"
8558 static void compose_register_draft(MsgInfo *info)
8560 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8561 DRAFTED_AT_EXIT, NULL);
8562 FILE *fp = fopen(filepath, "ab");
8565 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
8573 gboolean compose_draft (gpointer data, guint action)
8575 Compose *compose = (Compose *)data;
8579 MsgFlags flag = {0, 0};
8580 static gboolean lock = FALSE;
8581 MsgInfo *newmsginfo;
8583 gboolean target_locked = FALSE;
8584 gboolean err = FALSE;
8586 if (lock) return FALSE;
8588 if (compose->sending)
8591 draft = account_get_special_folder(compose->account, F_DRAFT);
8592 g_return_val_if_fail(draft != NULL, FALSE);
8594 if (!g_mutex_trylock(compose->mutex)) {
8595 /* we don't want to lock the mutex once it's available,
8596 * because as the only other part of compose.c locking
8597 * it is compose_close - which means once unlocked,
8598 * the compose struct will be freed */
8599 debug_print("couldn't lock mutex, probably sending\n");
8605 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
8606 G_DIR_SEPARATOR, compose);
8607 if ((fp = g_fopen(tmp, "wb")) == NULL) {
8608 FILE_OP_ERROR(tmp, "fopen");
8612 /* chmod for security */
8613 if (change_file_mode_rw(fp, tmp) < 0) {
8614 FILE_OP_ERROR(tmp, "chmod");
8615 g_warning("can't change file mode\n");
8618 /* Save draft infos */
8619 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
8620 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
8622 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
8623 gchar *savefolderid;
8625 savefolderid = gtk_editable_get_chars(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
8626 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
8627 g_free(savefolderid);
8629 if (compose->return_receipt) {
8630 err |= (fprintf(fp, "RRCPT:1\n") < 0);
8632 if (compose->privacy_system) {
8633 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
8634 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
8635 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
8638 /* Message-ID of message replying to */
8639 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
8642 folderid = folder_item_get_identifier(compose->replyinfo->folder);
8643 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
8646 /* Message-ID of message forwarding to */
8647 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
8650 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
8651 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
8655 /* end of headers */
8656 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
8663 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
8667 if (fclose(fp) == EOF) {
8671 if (compose->targetinfo) {
8672 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
8673 flag.perm_flags = target_locked?MSG_LOCKED:0;
8675 flag.tmp_flags = MSG_DRAFT;
8677 folder_item_scan(draft);
8678 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
8679 MsgInfo *tmpinfo = NULL;
8680 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
8681 if (compose->msgid) {
8682 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
8685 msgnum = tmpinfo->msgnum;
8686 procmsg_msginfo_free(tmpinfo);
8687 debug_print("got draft msgnum %d from scanning\n", msgnum);
8689 debug_print("didn't get draft msgnum after scanning\n");
8692 debug_print("got draft msgnum %d from adding\n", msgnum);
8698 if (action != COMPOSE_AUTO_SAVE) {
8699 if (action != COMPOSE_DRAFT_FOR_EXIT)
8700 alertpanel_error(_("Could not save draft."));
8703 gtkut_window_popup(compose->window);
8704 val = alertpanel_full(_("Could not save draft"),
8705 _("Could not save draft.\n"
8706 "Do you want to cancel exit or discard this email?"),
8707 _("_Cancel exit"), _("_Discard email"), NULL,
8708 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
8709 if (val == G_ALERTALTERNATE) {
8711 g_mutex_unlock(compose->mutex); /* must be done before closing */
8712 compose_close(compose);
8716 g_mutex_unlock(compose->mutex); /* must be done before closing */
8725 if (compose->mode == COMPOSE_REEDIT) {
8726 compose_remove_reedit_target(compose, TRUE);
8729 newmsginfo = folder_item_get_msginfo(draft, msgnum);
8732 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
8734 procmsg_msginfo_set_flags(newmsginfo, MSG_LOCKED, MSG_DRAFT);
8736 procmsg_msginfo_set_flags(newmsginfo, 0, MSG_DRAFT);
8737 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
8738 procmsg_msginfo_set_flags(newmsginfo, 0,
8739 MSG_HAS_ATTACHMENT);
8741 if (action == COMPOSE_DRAFT_FOR_EXIT) {
8742 compose_register_draft(newmsginfo);
8744 procmsg_msginfo_free(newmsginfo);
8747 folder_item_scan(draft);
8749 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
8751 g_mutex_unlock(compose->mutex); /* must be done before closing */
8752 compose_close(compose);
8758 path = folder_item_fetch_msg(draft, msgnum);
8760 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
8763 if (g_stat(path, &s) < 0) {
8764 FILE_OP_ERROR(path, "stat");
8770 procmsg_msginfo_free(compose->targetinfo);
8771 compose->targetinfo = procmsg_msginfo_new();
8772 compose->targetinfo->msgnum = msgnum;
8773 compose->targetinfo->size = s.st_size;
8774 compose->targetinfo->mtime = s.st_mtime;
8775 compose->targetinfo->folder = draft;
8777 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
8778 compose->mode = COMPOSE_REEDIT;
8780 if (action == COMPOSE_AUTO_SAVE) {
8781 compose->autosaved_draft = compose->targetinfo;
8783 compose->modified = FALSE;
8784 compose_set_title(compose);
8788 g_mutex_unlock(compose->mutex);
8792 void compose_clear_exit_drafts(void)
8794 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8795 DRAFTED_AT_EXIT, NULL);
8796 if (is_file_exist(filepath))
8802 void compose_reopen_exit_drafts(void)
8804 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8805 DRAFTED_AT_EXIT, NULL);
8806 FILE *fp = fopen(filepath, "rb");
8810 while (fgets(buf, sizeof(buf), fp)) {
8811 gchar **parts = g_strsplit(buf, "\t", 2);
8812 const gchar *folder = parts[0];
8813 int msgnum = parts[1] ? atoi(parts[1]):-1;
8815 if (folder && *folder && msgnum > -1) {
8816 FolderItem *item = folder_find_item_from_identifier(folder);
8817 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
8819 compose_reedit(info, FALSE);
8826 compose_clear_exit_drafts();
8829 static void compose_draft_cb(gpointer data, guint action, GtkWidget *widget)
8831 compose_draft(data, action);
8834 static void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
8836 if (compose && file_list) {
8839 for ( tmp = file_list; tmp; tmp = tmp->next) {
8840 gchar *file = (gchar *) tmp->data;
8841 gchar *utf8_filename = conv_filename_to_utf8(file);
8842 compose_attach_append(compose, file, utf8_filename, NULL);
8843 compose_changed_cb(NULL, compose);
8848 g_free(utf8_filename);
8853 static void compose_attach_cb(gpointer data, guint action, GtkWidget *widget)
8855 Compose *compose = (Compose *)data;
8858 if (compose->redirect_filename != NULL)
8861 file_list = filesel_select_multiple_files_open(_("Select file"));
8864 compose_attach_from_list(compose, file_list, TRUE);
8865 g_list_free(file_list);
8869 static void compose_insert_file_cb(gpointer data, guint action,
8872 Compose *compose = (Compose *)data;
8875 file_list = filesel_select_multiple_files_open(_("Select file"));
8880 for ( tmp = file_list; tmp; tmp = tmp->next) {
8881 gchar *file = (gchar *) tmp->data;
8882 gchar *filedup = g_strdup(file);
8883 gchar *shortfile = g_path_get_basename(filedup);
8884 ComposeInsertResult res;
8886 res = compose_insert_file(compose, file);
8887 if (res == COMPOSE_INSERT_READ_ERROR) {
8888 alertpanel_error(_("File '%s' could not be read."), shortfile);
8889 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
8890 alertpanel_error(_("File '%s' contained invalid characters\n"
8891 "for the current encoding, insertion may be incorrect."), shortfile);
8897 g_list_free(file_list);
8901 static void compose_insert_sig_cb(gpointer data, guint action,
8904 Compose *compose = (Compose *)data;
8906 compose_insert_sig(compose, FALSE);
8909 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
8913 Compose *compose = (Compose *)data;
8915 gtkut_widget_get_uposition(widget, &x, &y);
8916 prefs_common.compose_x = x;
8917 prefs_common.compose_y = y;
8919 if (compose->sending || compose->updating)
8921 compose_close_cb(compose, 0, NULL);
8925 void compose_close_toolbar(Compose *compose)
8927 compose_close_cb(compose, 0, NULL);
8930 static void compose_close_cb(gpointer data, guint action, GtkWidget *widget)
8932 Compose *compose = (Compose *)data;
8936 if (compose->exteditor_tag != -1) {
8937 if (!compose_ext_editor_kill(compose))
8942 if (compose->modified) {
8943 val = alertpanel(_("Discard message"),
8944 _("This message has been modified. Discard it?"),
8945 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
8948 case G_ALERTDEFAULT:
8949 if (prefs_common.autosave)
8950 compose_remove_draft(compose);
8952 case G_ALERTALTERNATE:
8953 compose_draft_cb(data, COMPOSE_QUIT_EDITING, NULL);
8960 compose_close(compose);
8963 static void compose_set_encoding_cb(gpointer data, guint action,
8966 Compose *compose = (Compose *)data;
8968 if (GTK_CHECK_MENU_ITEM(widget)->active)
8969 compose->out_encoding = (CharSet)action;
8972 static void compose_address_cb(gpointer data, guint action, GtkWidget *widget)
8974 Compose *compose = (Compose *)data;
8976 addressbook_open(compose);
8979 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
8981 Compose *compose = (Compose *)data;
8986 tmpl = g_object_get_data(G_OBJECT(widget), "template");
8987 g_return_if_fail(tmpl != NULL);
8989 msg = g_strdup_printf(_("Do you want to apply the template '%s' ?"),
8991 val = alertpanel(_("Apply template"), msg,
8992 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
8995 if (val == G_ALERTDEFAULT)
8996 compose_template_apply(compose, tmpl, TRUE);
8997 else if (val == G_ALERTALTERNATE)
8998 compose_template_apply(compose, tmpl, FALSE);
9001 static void compose_ext_editor_cb(gpointer data, guint action,
9004 Compose *compose = (Compose *)data;
9006 compose_exec_ext_editor(compose);
9009 static void compose_undo_cb(Compose *compose)
9011 gboolean prev_autowrap = compose->autowrap;
9013 compose->autowrap = FALSE;
9014 undo_undo(compose->undostruct);
9015 compose->autowrap = prev_autowrap;
9018 static void compose_redo_cb(Compose *compose)
9020 gboolean prev_autowrap = compose->autowrap;
9022 compose->autowrap = FALSE;
9023 undo_redo(compose->undostruct);
9024 compose->autowrap = prev_autowrap;
9027 static void entry_cut_clipboard(GtkWidget *entry)
9029 if (GTK_IS_EDITABLE(entry))
9030 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
9031 else if (GTK_IS_TEXT_VIEW(entry))
9032 gtk_text_buffer_cut_clipboard(
9033 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
9034 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
9038 static void entry_copy_clipboard(GtkWidget *entry)
9040 if (GTK_IS_EDITABLE(entry))
9041 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
9042 else if (GTK_IS_TEXT_VIEW(entry))
9043 gtk_text_buffer_copy_clipboard(
9044 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
9045 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
9048 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
9049 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
9051 if (GTK_IS_TEXT_VIEW(entry)) {
9052 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
9053 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
9054 GtkTextIter start_iter, end_iter;
9056 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
9058 if (contents == NULL)
9061 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
9063 /* we shouldn't delete the selection when middle-click-pasting, or we
9064 * can't mid-click-paste our own selection */
9065 if (clip != GDK_SELECTION_PRIMARY) {
9066 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
9069 if (insert_place == NULL) {
9070 /* if insert_place isn't specified, insert at the cursor.
9071 * used for Ctrl-V pasting */
9072 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
9073 start = gtk_text_iter_get_offset(&start_iter);
9074 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
9076 /* if insert_place is specified, paste here.
9077 * used for mid-click-pasting */
9078 start = gtk_text_iter_get_offset(insert_place);
9079 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
9083 /* paste unwrapped: mark the paste so it's not wrapped later */
9084 end = start + strlen(contents);
9085 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
9086 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
9087 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
9088 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
9089 /* rewrap paragraph now (after a mid-click-paste) */
9090 mark_start = gtk_text_buffer_get_insert(buffer);
9091 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
9092 gtk_text_iter_backward_char(&start_iter);
9093 compose_beautify_paragraph(compose, &start_iter, TRUE);
9095 } else if (GTK_IS_EDITABLE(entry))
9096 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
9100 static void entry_allsel(GtkWidget *entry)
9102 if (GTK_IS_EDITABLE(entry))
9103 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
9104 else if (GTK_IS_TEXT_VIEW(entry)) {
9105 GtkTextIter startiter, enditer;
9106 GtkTextBuffer *textbuf;
9108 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
9109 gtk_text_buffer_get_start_iter(textbuf, &startiter);
9110 gtk_text_buffer_get_end_iter(textbuf, &enditer);
9112 gtk_text_buffer_move_mark_by_name(textbuf,
9113 "selection_bound", &startiter);
9114 gtk_text_buffer_move_mark_by_name(textbuf,
9115 "insert", &enditer);
9119 static void compose_cut_cb(Compose *compose)
9121 if (compose->focused_editable
9123 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9126 entry_cut_clipboard(compose->focused_editable);
9129 static void compose_copy_cb(Compose *compose)
9131 if (compose->focused_editable
9133 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9136 entry_copy_clipboard(compose->focused_editable);
9139 static void compose_paste_cb(Compose *compose)
9142 GtkTextBuffer *buffer;
9144 if (compose->focused_editable &&
9145 GTK_WIDGET_HAS_FOCUS(compose->focused_editable))
9146 entry_paste_clipboard(compose, compose->focused_editable,
9147 prefs_common.linewrap_pastes,
9148 GDK_SELECTION_CLIPBOARD, NULL);
9152 static void compose_paste_as_quote_cb(Compose *compose)
9154 gint wrap_quote = prefs_common.linewrap_quote;
9155 if (compose->focused_editable
9157 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9160 /* let text_insert() (called directly or at a later time
9161 * after the gtk_editable_paste_clipboard) know that
9162 * text is to be inserted as a quotation. implemented
9163 * by using a simple refcount... */
9164 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
9165 G_OBJECT(compose->focused_editable),
9166 "paste_as_quotation"));
9167 g_object_set_data(G_OBJECT(compose->focused_editable),
9168 "paste_as_quotation",
9169 GINT_TO_POINTER(paste_as_quotation + 1));
9170 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
9171 entry_paste_clipboard(compose, compose->focused_editable,
9172 prefs_common.linewrap_pastes,
9173 GDK_SELECTION_CLIPBOARD, NULL);
9174 prefs_common.linewrap_quote = wrap_quote;
9178 static void compose_paste_no_wrap_cb(Compose *compose)
9181 GtkTextBuffer *buffer;
9183 if (compose->focused_editable
9185 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9188 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
9189 GDK_SELECTION_CLIPBOARD, NULL);
9193 static void compose_paste_wrap_cb(Compose *compose)
9196 GtkTextBuffer *buffer;
9198 if (compose->focused_editable
9200 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9203 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
9204 GDK_SELECTION_CLIPBOARD, NULL);
9208 static void compose_allsel_cb(Compose *compose)
9210 if (compose->focused_editable
9212 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9215 entry_allsel(compose->focused_editable);
9218 static void textview_move_beginning_of_line (GtkTextView *text)
9220 GtkTextBuffer *buffer;
9224 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9226 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9227 mark = gtk_text_buffer_get_insert(buffer);
9228 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9229 gtk_text_iter_set_line_offset(&ins, 0);
9230 gtk_text_buffer_place_cursor(buffer, &ins);
9233 static void textview_move_forward_character (GtkTextView *text)
9235 GtkTextBuffer *buffer;
9239 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9241 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9242 mark = gtk_text_buffer_get_insert(buffer);
9243 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9244 if (gtk_text_iter_forward_cursor_position(&ins))
9245 gtk_text_buffer_place_cursor(buffer, &ins);
9248 static void textview_move_backward_character (GtkTextView *text)
9250 GtkTextBuffer *buffer;
9254 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9256 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9257 mark = gtk_text_buffer_get_insert(buffer);
9258 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9259 if (gtk_text_iter_backward_cursor_position(&ins))
9260 gtk_text_buffer_place_cursor(buffer, &ins);
9263 static void textview_move_forward_word (GtkTextView *text)
9265 GtkTextBuffer *buffer;
9270 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9272 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9273 mark = gtk_text_buffer_get_insert(buffer);
9274 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9275 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
9276 if (gtk_text_iter_forward_word_ends(&ins, count)) {
9277 gtk_text_iter_backward_word_start(&ins);
9278 gtk_text_buffer_place_cursor(buffer, &ins);
9282 static void textview_move_backward_word (GtkTextView *text)
9284 GtkTextBuffer *buffer;
9289 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9291 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9292 mark = gtk_text_buffer_get_insert(buffer);
9293 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9294 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
9295 if (gtk_text_iter_backward_word_starts(&ins, 1))
9296 gtk_text_buffer_place_cursor(buffer, &ins);
9299 static void textview_move_end_of_line (GtkTextView *text)
9301 GtkTextBuffer *buffer;
9305 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9307 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9308 mark = gtk_text_buffer_get_insert(buffer);
9309 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9310 if (gtk_text_iter_forward_to_line_end(&ins))
9311 gtk_text_buffer_place_cursor(buffer, &ins);
9314 static void textview_move_next_line (GtkTextView *text)
9316 GtkTextBuffer *buffer;
9321 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9323 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9324 mark = gtk_text_buffer_get_insert(buffer);
9325 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9326 offset = gtk_text_iter_get_line_offset(&ins);
9327 if (gtk_text_iter_forward_line(&ins)) {
9328 gtk_text_iter_set_line_offset(&ins, offset);
9329 gtk_text_buffer_place_cursor(buffer, &ins);
9333 static void textview_move_previous_line (GtkTextView *text)
9335 GtkTextBuffer *buffer;
9340 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9342 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9343 mark = gtk_text_buffer_get_insert(buffer);
9344 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9345 offset = gtk_text_iter_get_line_offset(&ins);
9346 if (gtk_text_iter_backward_line(&ins)) {
9347 gtk_text_iter_set_line_offset(&ins, offset);
9348 gtk_text_buffer_place_cursor(buffer, &ins);
9352 static void textview_delete_forward_character (GtkTextView *text)
9354 GtkTextBuffer *buffer;
9356 GtkTextIter ins, end_iter;
9358 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9360 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9361 mark = gtk_text_buffer_get_insert(buffer);
9362 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9364 if (gtk_text_iter_forward_char(&end_iter)) {
9365 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9369 static void textview_delete_backward_character (GtkTextView *text)
9371 GtkTextBuffer *buffer;
9373 GtkTextIter ins, end_iter;
9375 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9377 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9378 mark = gtk_text_buffer_get_insert(buffer);
9379 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9381 if (gtk_text_iter_backward_char(&end_iter)) {
9382 gtk_text_buffer_delete(buffer, &end_iter, &ins);
9386 static void textview_delete_forward_word (GtkTextView *text)
9388 GtkTextBuffer *buffer;
9390 GtkTextIter ins, end_iter;
9392 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9394 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9395 mark = gtk_text_buffer_get_insert(buffer);
9396 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9398 if (gtk_text_iter_forward_word_end(&end_iter)) {
9399 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9403 static void textview_delete_backward_word (GtkTextView *text)
9405 GtkTextBuffer *buffer;
9407 GtkTextIter ins, end_iter;
9409 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9411 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9412 mark = gtk_text_buffer_get_insert(buffer);
9413 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9415 if (gtk_text_iter_backward_word_start(&end_iter)) {
9416 gtk_text_buffer_delete(buffer, &end_iter, &ins);
9420 static void textview_delete_line (GtkTextView *text)
9422 GtkTextBuffer *buffer;
9424 GtkTextIter ins, start_iter, end_iter;
9427 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9429 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9430 mark = gtk_text_buffer_get_insert(buffer);
9431 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9434 gtk_text_iter_set_line_offset(&start_iter, 0);
9437 if (gtk_text_iter_ends_line(&end_iter))
9438 found = gtk_text_iter_forward_char(&end_iter);
9440 found = gtk_text_iter_forward_to_line_end(&end_iter);
9443 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
9446 static void textview_delete_to_line_end (GtkTextView *text)
9448 GtkTextBuffer *buffer;
9450 GtkTextIter ins, end_iter;
9453 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9455 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9456 mark = gtk_text_buffer_get_insert(buffer);
9457 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9459 if (gtk_text_iter_ends_line(&end_iter))
9460 found = gtk_text_iter_forward_char(&end_iter);
9462 found = gtk_text_iter_forward_to_line_end(&end_iter);
9464 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9467 static void compose_advanced_action_cb(Compose *compose,
9468 ComposeCallAdvancedAction action)
9470 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9472 void (*do_action) (GtkTextView *text);
9473 } action_table[] = {
9474 {textview_move_beginning_of_line},
9475 {textview_move_forward_character},
9476 {textview_move_backward_character},
9477 {textview_move_forward_word},
9478 {textview_move_backward_word},
9479 {textview_move_end_of_line},
9480 {textview_move_next_line},
9481 {textview_move_previous_line},
9482 {textview_delete_forward_character},
9483 {textview_delete_backward_character},
9484 {textview_delete_forward_word},
9485 {textview_delete_backward_word},
9486 {textview_delete_line},
9487 {NULL}, /* gtk_stext_delete_line_n */
9488 {textview_delete_to_line_end}
9491 if (!GTK_WIDGET_HAS_FOCUS(text)) return;
9493 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
9494 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
9495 if (action_table[action].do_action)
9496 action_table[action].do_action(text);
9498 g_warning("Not implemented yet.");
9502 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
9506 if (GTK_IS_EDITABLE(widget)) {
9507 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
9508 gtk_editable_set_position(GTK_EDITABLE(widget),
9511 if (widget->parent && widget->parent->parent
9512 && widget->parent->parent->parent) {
9513 if (GTK_IS_SCROLLED_WINDOW(widget->parent->parent->parent)) {
9514 gint y = widget->allocation.y;
9515 gint height = widget->allocation.height;
9516 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
9517 (GTK_SCROLLED_WINDOW(widget->parent->parent->parent));
9519 if (y < (int)shown->value) {
9520 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown), y - 1);
9522 if (y + height > (int)shown->value + (int)shown->page_size) {
9523 if (y - height - 1 < (int)shown->upper - (int)shown->page_size) {
9524 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
9525 y + height - (int)shown->page_size - 1);
9527 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
9528 (int)shown->upper - (int)shown->page_size - 1);
9535 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
9536 compose->focused_editable = widget;
9539 if (GTK_IS_TEXT_VIEW(widget)
9540 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
9541 gtk_widget_ref(compose->notebook);
9542 gtk_widget_ref(compose->edit_vbox);
9543 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
9544 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
9545 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
9546 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
9547 gtk_widget_unref(compose->notebook);
9548 gtk_widget_unref(compose->edit_vbox);
9549 g_signal_handlers_block_by_func(G_OBJECT(widget),
9550 G_CALLBACK(compose_grab_focus_cb),
9552 gtk_widget_grab_focus(widget);
9553 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
9554 G_CALLBACK(compose_grab_focus_cb),
9556 } else if (!GTK_IS_TEXT_VIEW(widget)
9557 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
9558 gtk_widget_ref(compose->notebook);
9559 gtk_widget_ref(compose->edit_vbox);
9560 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
9561 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
9562 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
9563 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
9564 gtk_widget_unref(compose->notebook);
9565 gtk_widget_unref(compose->edit_vbox);
9566 g_signal_handlers_block_by_func(G_OBJECT(widget),
9567 G_CALLBACK(compose_grab_focus_cb),
9569 gtk_widget_grab_focus(widget);
9570 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
9571 G_CALLBACK(compose_grab_focus_cb),
9577 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
9579 compose->modified = TRUE;
9581 compose_set_title(compose);
9585 static void compose_wrap_cb(gpointer data, guint action, GtkWidget *widget)
9587 Compose *compose = (Compose *)data;
9590 compose_wrap_all_full(compose, TRUE);
9592 compose_beautify_paragraph(compose, NULL, TRUE);
9595 static void compose_find_cb(gpointer data, guint action, GtkWidget *widget)
9597 Compose *compose = (Compose *)data;
9599 message_search_compose(compose);
9602 static void compose_toggle_autowrap_cb(gpointer data, guint action,
9605 Compose *compose = (Compose *)data;
9606 compose->autowrap = GTK_CHECK_MENU_ITEM(widget)->active;
9607 if (compose->autowrap)
9608 compose_wrap_all_full(compose, TRUE);
9609 compose->autowrap = GTK_CHECK_MENU_ITEM(widget)->active;
9612 static void compose_toggle_sign_cb(gpointer data, guint action,
9615 Compose *compose = (Compose *)data;
9617 if (GTK_CHECK_MENU_ITEM(widget)->active)
9618 compose->use_signing = TRUE;
9620 compose->use_signing = FALSE;
9623 static void compose_toggle_encrypt_cb(gpointer data, guint action,
9626 Compose *compose = (Compose *)data;
9628 if (GTK_CHECK_MENU_ITEM(widget)->active)
9629 compose->use_encryption = TRUE;
9631 compose->use_encryption = FALSE;
9634 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
9636 g_free(compose->privacy_system);
9638 compose->privacy_system = g_strdup(account->default_privacy_system);
9639 compose_update_privacy_system_menu_item(compose, warn);
9642 static void compose_toggle_ruler_cb(gpointer data, guint action,
9645 Compose *compose = (Compose *)data;
9647 if (GTK_CHECK_MENU_ITEM(widget)->active) {
9648 gtk_widget_show(compose->ruler_hbox);
9649 prefs_common.show_ruler = TRUE;
9651 gtk_widget_hide(compose->ruler_hbox);
9652 gtk_widget_queue_resize(compose->edit_vbox);
9653 prefs_common.show_ruler = FALSE;
9657 static void compose_attach_drag_received_cb (GtkWidget *widget,
9658 GdkDragContext *context,
9661 GtkSelectionData *data,
9666 Compose *compose = (Compose *)user_data;
9669 if (gdk_atom_name(data->type) &&
9670 !strcmp(gdk_atom_name(data->type), "text/uri-list")
9671 && gtk_drag_get_source_widget(context) !=
9672 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
9673 list = uri_list_extract_filenames((const gchar *)data->data);
9674 for (tmp = list; tmp != NULL; tmp = tmp->next) {
9675 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
9676 compose_attach_append
9677 (compose, (const gchar *)tmp->data,
9678 utf8_filename, NULL);
9679 g_free(utf8_filename);
9681 if (list) compose_changed_cb(NULL, compose);
9682 list_free_strings(list);
9684 } else if (gtk_drag_get_source_widget(context)
9685 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
9686 /* comes from our summaryview */
9687 SummaryView * summaryview = NULL;
9688 GSList * list = NULL, *cur = NULL;
9690 if (mainwindow_get_mainwindow())
9691 summaryview = mainwindow_get_mainwindow()->summaryview;
9694 list = summary_get_selected_msg_list(summaryview);
9696 for (cur = list; cur; cur = cur->next) {
9697 MsgInfo *msginfo = (MsgInfo *)cur->data;
9700 file = procmsg_get_message_file_full(msginfo,
9703 compose_attach_append(compose, (const gchar *)file,
9704 (const gchar *)file, "message/rfc822");
9712 static gboolean compose_drag_drop(GtkWidget *widget,
9713 GdkDragContext *drag_context,
9715 guint time, gpointer user_data)
9717 /* not handling this signal makes compose_insert_drag_received_cb
9722 static void compose_insert_drag_received_cb (GtkWidget *widget,
9723 GdkDragContext *drag_context,
9726 GtkSelectionData *data,
9731 Compose *compose = (Compose *)user_data;
9734 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
9736 if (gdk_atom_name(data->type) && !strcmp(gdk_atom_name(data->type), "text/uri-list")) {
9737 AlertValue val = G_ALERTDEFAULT;
9739 list = uri_list_extract_filenames((const gchar *)data->data);
9741 if (list == NULL && strstr((gchar *)(data->data), "://")) {
9742 /* Assume a list of no files, and data has ://, is a remote link */
9743 gchar *tmpdata = g_strstrip(g_strdup((const gchar *)data->data));
9744 gchar *tmpfile = get_tmp_file();
9745 str_write_to_file(tmpdata, tmpfile);
9747 compose_insert_file(compose, tmpfile);
9750 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9751 compose_beautify_paragraph(compose, NULL, TRUE);
9754 switch (prefs_common.compose_dnd_mode) {
9755 case COMPOSE_DND_ASK:
9756 val = alertpanel_full(_("Insert or attach?"),
9757 _("Do you want to insert the contents of the file(s) "
9758 "into the message body, or attach it to the email?"),
9759 GTK_STOCK_CANCEL, _("+_Insert"), _("_Attach"),
9760 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
9762 case COMPOSE_DND_INSERT:
9763 val = G_ALERTALTERNATE;
9765 case COMPOSE_DND_ATTACH:
9769 /* unexpected case */
9770 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
9773 if (val & G_ALERTDISABLE) {
9774 val &= ~G_ALERTDISABLE;
9775 /* remember what action to perform by default, only if we don't click Cancel */
9776 if (val == G_ALERTALTERNATE)
9777 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
9778 else if (val == G_ALERTOTHER)
9779 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
9782 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
9783 gtk_drag_finish(drag_context, FALSE, FALSE, time);
9784 list_free_strings(list);
9787 } else if (val == G_ALERTOTHER) {
9788 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
9789 list_free_strings(list);
9794 for (tmp = list; tmp != NULL; tmp = tmp->next) {
9795 compose_insert_file(compose, (const gchar *)tmp->data);
9797 list_free_strings(list);
9799 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9802 #if GTK_CHECK_VERSION(2, 8, 0)
9803 /* do nothing, handled by GTK */
9805 gchar *tmpfile = get_tmp_file();
9806 str_write_to_file((const gchar *)data->data, tmpfile);
9807 compose_insert_file(compose, tmpfile);
9810 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9814 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9817 static void compose_header_drag_received_cb (GtkWidget *widget,
9818 GdkDragContext *drag_context,
9821 GtkSelectionData *data,
9826 GtkEditable *entry = (GtkEditable *)user_data;
9827 gchar *email = (gchar *)data->data;
9829 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
9832 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
9833 gchar *decoded=g_new(gchar, strlen(email));
9836 email += strlen("mailto:");
9837 decode_uri(decoded, email); /* will fit */
9838 gtk_editable_delete_text(entry, 0, -1);
9839 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
9840 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9844 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9847 static void compose_toggle_return_receipt_cb(gpointer data, guint action,
9850 Compose *compose = (Compose *)data;
9852 if (GTK_CHECK_MENU_ITEM(widget)->active)
9853 compose->return_receipt = TRUE;
9855 compose->return_receipt = FALSE;
9858 static void compose_toggle_remove_refs_cb(gpointer data, guint action,
9861 Compose *compose = (Compose *)data;
9863 if (GTK_CHECK_MENU_ITEM(widget)->active)
9864 compose->remove_references = TRUE;
9866 compose->remove_references = FALSE;
9869 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
9871 ComposeHeaderEntry *headerentry)
9873 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
9874 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
9875 !(event->state & GDK_MODIFIER_MASK) &&
9876 (event->keyval == GDK_BackSpace) &&
9877 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
9878 gtk_container_remove
9879 (GTK_CONTAINER(headerentry->compose->header_table),
9880 headerentry->combo);
9881 gtk_container_remove
9882 (GTK_CONTAINER(headerentry->compose->header_table),
9883 headerentry->entry);
9884 headerentry->compose->header_list =
9885 g_slist_remove(headerentry->compose->header_list,
9887 g_free(headerentry);
9888 } else if (event->keyval == GDK_Tab) {
9889 if (headerentry->compose->header_last == headerentry) {
9890 /* Override default next focus, and give it to subject_entry
9891 * instead of notebook tabs
9893 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
9894 gtk_widget_grab_focus(headerentry->compose->subject_entry);
9901 static gboolean compose_headerentry_changed_cb(GtkWidget *entry,
9902 ComposeHeaderEntry *headerentry)
9904 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
9905 compose_create_header_entry(headerentry->compose);
9906 g_signal_handlers_disconnect_matched
9907 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
9908 0, 0, NULL, NULL, headerentry);
9910 /* Automatically scroll down */
9911 compose_show_first_last_header(headerentry->compose, FALSE);
9917 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
9919 GtkAdjustment *vadj;
9921 g_return_if_fail(compose);
9922 g_return_if_fail(GTK_IS_WIDGET(compose->header_table));
9923 g_return_if_fail(GTK_IS_VIEWPORT(compose->header_table->parent));
9925 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(compose->header_table->parent));
9926 gtk_adjustment_set_value(vadj, (show_first ? vadj->lower : vadj->upper));
9927 gtk_adjustment_changed(vadj);
9930 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
9931 const gchar *text, gint len, Compose *compose)
9933 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
9934 (G_OBJECT(compose->text), "paste_as_quotation"));
9937 g_return_if_fail(text != NULL);
9939 g_signal_handlers_block_by_func(G_OBJECT(buffer),
9940 G_CALLBACK(text_inserted),
9942 if (paste_as_quotation) {
9946 GtkTextIter start_iter;
9951 new_text = g_strndup(text, len);
9953 qmark = compose_quote_char_from_context(compose);
9955 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
9956 gtk_text_buffer_place_cursor(buffer, iter);
9958 pos = gtk_text_iter_get_offset(iter);
9960 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
9961 _("Quote format error at line %d."));
9962 quote_fmt_reset_vartable();
9964 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
9965 GINT_TO_POINTER(paste_as_quotation - 1));
9967 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
9968 gtk_text_buffer_place_cursor(buffer, iter);
9969 gtk_text_buffer_delete_mark(buffer, mark);
9971 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
9972 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
9973 compose_beautify_paragraph(compose, &start_iter, FALSE);
9974 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
9975 gtk_text_buffer_delete_mark(buffer, mark);
9977 if (strcmp(text, "\n") || compose->automatic_break
9978 || gtk_text_iter_starts_line(iter))
9979 gtk_text_buffer_insert(buffer, iter, text, len);
9981 /* check if the preceding is just whitespace or quote */
9982 GtkTextIter start_line;
9983 gchar *tmp = NULL, *quote = NULL;
9984 gint quote_len = 0, is_normal = 0;
9986 gtk_text_iter_set_line_offset(&start_line, 0);
9987 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
9992 quote = compose_get_quote_str(buffer, &start_line, "e_len);
10000 gtk_text_buffer_insert(buffer, iter, text, len);
10002 gtk_text_buffer_insert_with_tags_by_name(buffer,
10003 iter, text, len, "no_join", NULL);
10008 if (!paste_as_quotation) {
10009 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
10010 compose_beautify_paragraph(compose, iter, FALSE);
10011 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
10012 gtk_text_buffer_delete_mark(buffer, mark);
10015 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
10016 G_CALLBACK(text_inserted),
10018 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
10020 if (prefs_common.autosave &&
10021 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
10022 compose->draft_timeout_tag != -2 /* disabled while loading */)
10023 compose->draft_timeout_tag = g_timeout_add
10024 (500, (GtkFunction) compose_defer_auto_save_draft, compose);
10026 static gint compose_defer_auto_save_draft(Compose *compose)
10028 compose->draft_timeout_tag = -1;
10029 compose_draft_cb((gpointer)compose, COMPOSE_AUTO_SAVE, NULL);
10034 static void compose_check_all(Compose *compose)
10036 if (compose->gtkaspell)
10037 gtkaspell_check_all(compose->gtkaspell);
10040 static void compose_highlight_all(Compose *compose)
10042 if (compose->gtkaspell)
10043 gtkaspell_highlight_all(compose->gtkaspell);
10046 static void compose_check_backwards(Compose *compose)
10048 if (compose->gtkaspell)
10049 gtkaspell_check_backwards(compose->gtkaspell);
10051 GtkItemFactory *ifactory;
10052 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
10053 menu_set_sensitive(ifactory, "/Edit/Check backwards misspelled word", FALSE);
10054 menu_set_sensitive(ifactory, "/Edit/Forward to next misspelled word", FALSE);
10058 static void compose_check_forwards_go(Compose *compose)
10060 if (compose->gtkaspell)
10061 gtkaspell_check_forwards_go(compose->gtkaspell);
10063 GtkItemFactory *ifactory;
10064 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
10065 menu_set_sensitive(ifactory, "/Edit/Check backwards misspelled word", FALSE);
10066 menu_set_sensitive(ifactory, "/Edit/Forward to next misspelled word", FALSE);
10072 *\brief Guess originating forward account from MsgInfo and several
10073 * "common preference" settings. Return NULL if no guess.
10075 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
10077 PrefsAccount *account = NULL;
10079 g_return_val_if_fail(msginfo, NULL);
10080 g_return_val_if_fail(msginfo->folder, NULL);
10081 g_return_val_if_fail(msginfo->folder->prefs, NULL);
10083 if (msginfo->folder->prefs->enable_default_account)
10084 account = account_find_from_id(msginfo->folder->prefs->default_account);
10087 account = msginfo->folder->folder->account;
10089 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
10091 Xstrdup_a(to, msginfo->to, return NULL);
10092 extract_address(to);
10093 account = account_find_from_address(to, FALSE);
10096 if (!account && prefs_common.forward_account_autosel) {
10097 gchar cc[BUFFSIZE];
10098 if (!procheader_get_header_from_msginfo
10099 (msginfo, cc,sizeof cc , "Cc:")) {
10100 gchar *buf = cc + strlen("Cc:");
10101 extract_address(buf);
10102 account = account_find_from_address(buf, FALSE);
10106 if (!account && prefs_common.forward_account_autosel) {
10107 gchar deliveredto[BUFFSIZE];
10108 if (!procheader_get_header_from_msginfo
10109 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
10110 gchar *buf = deliveredto + strlen("Delivered-To:");
10111 extract_address(buf);
10112 account = account_find_from_address(buf, FALSE);
10119 gboolean compose_close(Compose *compose)
10123 if (!g_mutex_trylock(compose->mutex)) {
10124 /* we have to wait for the (possibly deferred by auto-save)
10125 * drafting to be done, before destroying the compose under
10127 debug_print("waiting for drafting to finish...\n");
10128 compose_allow_user_actions(compose, FALSE);
10129 g_timeout_add (500, (GSourceFunc) compose_close, compose);
10132 g_return_val_if_fail(compose, FALSE);
10133 gtkut_widget_get_uposition(compose->window, &x, &y);
10134 prefs_common.compose_x = x;
10135 prefs_common.compose_y = y;
10136 g_mutex_unlock(compose->mutex);
10137 compose_destroy(compose);
10142 * Add entry field for each address in list.
10143 * \param compose E-Mail composition object.
10144 * \param listAddress List of (formatted) E-Mail addresses.
10146 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
10149 node = listAddress;
10151 addr = ( gchar * ) node->data;
10152 compose_entry_append( compose, addr, COMPOSE_TO );
10153 node = g_list_next( node );
10157 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
10158 guint action, gboolean opening_multiple)
10160 gchar *body = NULL;
10161 GSList *new_msglist = NULL;
10162 MsgInfo *tmp_msginfo = NULL;
10163 gboolean originally_enc = FALSE;
10164 Compose *compose = NULL;
10166 g_return_if_fail(msgview != NULL);
10168 g_return_if_fail(msginfo_list != NULL);
10170 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
10171 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
10172 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
10174 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
10175 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
10176 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
10177 orig_msginfo, mimeinfo);
10178 if (tmp_msginfo != NULL) {
10179 new_msglist = g_slist_append(NULL, tmp_msginfo);
10181 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
10182 tmp_msginfo->folder = orig_msginfo->folder;
10183 tmp_msginfo->msgnum = orig_msginfo->msgnum;
10184 if (orig_msginfo->tags)
10185 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
10190 if (!opening_multiple)
10191 body = messageview_get_selection(msgview);
10194 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
10195 procmsg_msginfo_free(tmp_msginfo);
10196 g_slist_free(new_msglist);
10198 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
10200 if (compose && originally_enc) {
10201 compose_force_encryption(compose, compose->account, FALSE);
10207 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
10210 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
10211 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
10212 GSList *cur = msginfo_list;
10213 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
10214 "messages. Opening the windows "
10215 "could take some time. Do you "
10216 "want to continue?"),
10217 g_slist_length(msginfo_list));
10218 if (g_slist_length(msginfo_list) > 9
10219 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
10220 != G_ALERTALTERNATE) {
10225 /* We'll open multiple compose windows */
10226 /* let the WM place the next windows */
10227 compose_force_window_origin = FALSE;
10228 for (; cur; cur = cur->next) {
10230 tmplist.data = cur->data;
10231 tmplist.next = NULL;
10232 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
10234 compose_force_window_origin = TRUE;
10236 /* forwarding multiple mails as attachments is done via a
10237 * single compose window */
10238 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
10242 void compose_set_position(Compose *compose, gint pos)
10244 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10246 gtkut_text_view_set_position(text, pos);
10249 gboolean compose_search_string(Compose *compose,
10250 const gchar *str, gboolean case_sens)
10252 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10254 return gtkut_text_view_search_string(text, str, case_sens);
10257 gboolean compose_search_string_backward(Compose *compose,
10258 const gchar *str, gboolean case_sens)
10260 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10262 return gtkut_text_view_search_string_backward(text, str, case_sens);
10265 /* allocate a msginfo structure and populate its data from a compose data structure */
10266 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
10268 MsgInfo *newmsginfo;
10270 gchar buf[BUFFSIZE];
10272 g_return_val_if_fail( compose != NULL, NULL );
10274 newmsginfo = procmsg_msginfo_new();
10277 get_rfc822_date(buf, sizeof(buf));
10278 newmsginfo->date = g_strdup(buf);
10281 if (compose->from_name) {
10282 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
10283 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
10287 if (compose->subject_entry)
10288 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
10290 /* to, cc, reply-to, newsgroups */
10291 for (list = compose->header_list; list; list = list->next) {
10292 gchar *header = gtk_editable_get_chars(
10294 GTK_BIN(((ComposeHeaderEntry *)list->data)->combo)->child), 0, -1);
10295 gchar *entry = gtk_editable_get_chars(
10296 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
10298 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
10299 if ( newmsginfo->to == NULL ) {
10300 newmsginfo->to = g_strdup(entry);
10301 } else if (entry && *entry) {
10302 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
10303 g_free(newmsginfo->to);
10304 newmsginfo->to = tmp;
10307 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
10308 if ( newmsginfo->cc == NULL ) {
10309 newmsginfo->cc = g_strdup(entry);
10310 } else if (entry && *entry) {
10311 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
10312 g_free(newmsginfo->cc);
10313 newmsginfo->cc = tmp;
10316 if ( strcasecmp(header,
10317 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
10318 if ( newmsginfo->newsgroups == NULL ) {
10319 newmsginfo->newsgroups = g_strdup(entry);
10320 } else if (entry && *entry) {
10321 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
10322 g_free(newmsginfo->newsgroups);
10323 newmsginfo->newsgroups = tmp;
10331 /* other data is unset */
10337 /* update compose's dictionaries from folder dict settings */
10338 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
10339 FolderItem *folder_item)
10341 g_return_if_fail(compose != NULL);
10343 if (compose->gtkaspell && folder_item && folder_item->prefs) {
10344 FolderItemPrefs *prefs = folder_item->prefs;
10346 if (prefs->enable_default_dictionary)
10347 gtkaspell_change_dict(compose->gtkaspell,
10348 prefs->default_dictionary, FALSE);
10349 if (folder_item->prefs->enable_default_alt_dictionary)
10350 gtkaspell_change_alt_dict(compose->gtkaspell,
10351 prefs->default_alt_dictionary);
10352 if (prefs->enable_default_dictionary
10353 || prefs->enable_default_alt_dictionary)
10354 compose_spell_menu_changed(compose);