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) return NULL;
1504 textview = (GTK_TEXT_VIEW(compose->text));
1505 textbuf = gtk_text_view_get_buffer(textview);
1506 compose_create_tags(textview, compose);
1508 undo_block(compose->undostruct);
1510 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1513 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1514 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1515 /* use the reply format of folder (if enabled), or the account's one
1516 (if enabled) or fallback to the global reply format, which is always
1517 enabled (even if empty), and use the relevant quotemark */
1519 if (msginfo->folder && msginfo->folder->prefs &&
1520 msginfo->folder->prefs->reply_with_format) {
1521 qmark = msginfo->folder->prefs->reply_quotemark;
1522 body_fmt = msginfo->folder->prefs->reply_body_format;
1524 } else if (account->reply_with_format) {
1525 qmark = account->reply_quotemark;
1526 body_fmt = account->reply_body_format;
1529 qmark = prefs_common.quotemark;
1530 body_fmt = prefs_common.quotefmt;
1535 /* empty quotemark is not allowed */
1536 if (qmark == NULL || *qmark == '\0')
1538 compose_quote_fmt(compose, compose->replyinfo,
1539 body_fmt, qmark, body, FALSE, TRUE,
1540 _("Message reply format error at line %d."));
1541 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1542 quote_fmt_reset_vartable();
1545 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1546 compose_force_encryption(compose, account, FALSE);
1549 SIGNAL_BLOCK(textbuf);
1551 if (account->auto_sig)
1552 compose_insert_sig(compose, FALSE);
1554 compose_wrap_all(compose);
1556 SIGNAL_UNBLOCK(textbuf);
1558 gtk_widget_grab_focus(compose->text);
1560 undo_unblock(compose->undostruct);
1562 if (prefs_common.auto_exteditor)
1563 compose_exec_ext_editor(compose);
1565 compose->modified = FALSE;
1566 compose_set_title(compose);
1568 compose->updating = FALSE;
1569 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1570 SCROLL_TO_CURSOR(compose);
1572 if (compose->deferred_destroy) {
1573 compose_destroy(compose);
1580 #define INSERT_FW_HEADER(var, hdr) \
1581 if (msginfo->var && *msginfo->var) { \
1582 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1583 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1584 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1587 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1588 gboolean as_attach, const gchar *body,
1589 gboolean no_extedit,
1593 GtkTextView *textview;
1594 GtkTextBuffer *textbuf;
1597 g_return_val_if_fail(msginfo != NULL, NULL);
1598 g_return_val_if_fail(msginfo->folder != NULL, NULL);
1601 !(account = compose_guess_forward_account_from_msginfo
1603 account = cur_account;
1605 compose = compose_create(account, msginfo->folder, COMPOSE_FORWARD, batch);
1607 compose->updating = TRUE;
1608 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1609 if (!compose->fwdinfo)
1610 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1612 compose_extract_original_charset(compose);
1614 if (msginfo->subject && *msginfo->subject) {
1615 gchar *buf, *buf2, *p;
1617 buf = p = g_strdup(msginfo->subject);
1618 p += subject_get_prefix_length(p);
1619 memmove(buf, p, strlen(p) + 1);
1621 buf2 = g_strdup_printf("Fw: %s", buf);
1622 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1628 textview = GTK_TEXT_VIEW(compose->text);
1629 textbuf = gtk_text_view_get_buffer(textview);
1630 compose_create_tags(textview, compose);
1632 undo_block(compose->undostruct);
1636 msgfile = procmsg_get_message_file(msginfo);
1637 if (!is_file_exist(msgfile))
1638 g_warning("%s: file not exist\n", msgfile);
1640 compose_attach_append(compose, msgfile, msgfile,
1645 const gchar *qmark = NULL;
1646 const gchar *body_fmt = prefs_common.fw_quotefmt;
1647 MsgInfo *full_msginfo;
1649 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1651 full_msginfo = procmsg_msginfo_copy(msginfo);
1653 /* use the forward format of folder (if enabled), or the account's one
1654 (if enabled) or fallback to the global forward format, which is always
1655 enabled (even if empty), and use the relevant quotemark */
1656 if (msginfo->folder && msginfo->folder->prefs &&
1657 msginfo->folder->prefs->forward_with_format) {
1658 qmark = msginfo->folder->prefs->forward_quotemark;
1659 body_fmt = msginfo->folder->prefs->forward_body_format;
1661 } else if (account->forward_with_format) {
1662 qmark = account->forward_quotemark;
1663 body_fmt = account->forward_body_format;
1666 qmark = prefs_common.fw_quotemark;
1667 body_fmt = prefs_common.fw_quotefmt;
1670 /* empty quotemark is not allowed */
1671 if (qmark == NULL || *qmark == '\0')
1674 compose_quote_fmt(compose, full_msginfo,
1675 body_fmt, qmark, body, FALSE, TRUE,
1676 _("Message forward format error at line %d."));
1677 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1678 quote_fmt_reset_vartable();
1679 compose_attach_parts(compose, msginfo);
1681 procmsg_msginfo_free(full_msginfo);
1684 SIGNAL_BLOCK(textbuf);
1686 if (account->auto_sig)
1687 compose_insert_sig(compose, FALSE);
1689 compose_wrap_all(compose);
1691 SIGNAL_UNBLOCK(textbuf);
1693 gtk_text_buffer_get_start_iter(textbuf, &iter);
1694 gtk_text_buffer_place_cursor(textbuf, &iter);
1696 gtk_widget_grab_focus(compose->header_last->entry);
1698 if (!no_extedit && prefs_common.auto_exteditor)
1699 compose_exec_ext_editor(compose);
1702 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1703 gchar *folderidentifier;
1705 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1706 folderidentifier = folder_item_get_identifier(msginfo->folder);
1707 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
1708 g_free(folderidentifier);
1711 undo_unblock(compose->undostruct);
1713 compose->modified = FALSE;
1714 compose_set_title(compose);
1716 compose->updating = FALSE;
1717 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1718 SCROLL_TO_CURSOR(compose);
1720 if (compose->deferred_destroy) {
1721 compose_destroy(compose);
1728 #undef INSERT_FW_HEADER
1730 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1733 GtkTextView *textview;
1734 GtkTextBuffer *textbuf;
1738 gboolean single_mail = TRUE;
1740 g_return_val_if_fail(msginfo_list != NULL, NULL);
1742 if (g_slist_length(msginfo_list) > 1)
1743 single_mail = FALSE;
1745 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1746 if (((MsgInfo *)msginfo->data)->folder == NULL)
1749 /* guess account from first selected message */
1751 !(account = compose_guess_forward_account_from_msginfo
1752 (msginfo_list->data)))
1753 account = cur_account;
1755 g_return_val_if_fail(account != NULL, NULL);
1757 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1758 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1759 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1762 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1764 compose->updating = TRUE;
1766 textview = GTK_TEXT_VIEW(compose->text);
1767 textbuf = gtk_text_view_get_buffer(textview);
1768 compose_create_tags(textview, compose);
1770 undo_block(compose->undostruct);
1771 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1772 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
1774 if (!is_file_exist(msgfile))
1775 g_warning("%s: file not exist\n", msgfile);
1777 compose_attach_append(compose, msgfile, msgfile,
1783 MsgInfo *info = (MsgInfo *)msginfo_list->data;
1784 if (info->subject && *info->subject) {
1785 gchar *buf, *buf2, *p;
1787 buf = p = g_strdup(info->subject);
1788 p += subject_get_prefix_length(p);
1789 memmove(buf, p, strlen(p) + 1);
1791 buf2 = g_strdup_printf("Fw: %s", buf);
1792 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1798 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
1799 _("Fw: multiple emails"));
1802 SIGNAL_BLOCK(textbuf);
1804 if (account->auto_sig)
1805 compose_insert_sig(compose, FALSE);
1807 compose_wrap_all(compose);
1809 SIGNAL_UNBLOCK(textbuf);
1811 gtk_text_buffer_get_start_iter(textbuf, &iter);
1812 gtk_text_buffer_place_cursor(textbuf, &iter);
1814 gtk_widget_grab_focus(compose->header_last->entry);
1815 undo_unblock(compose->undostruct);
1816 compose->modified = FALSE;
1817 compose_set_title(compose);
1819 compose->updating = FALSE;
1820 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1821 SCROLL_TO_CURSOR(compose);
1823 if (compose->deferred_destroy) {
1824 compose_destroy(compose);
1831 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
1833 GtkTextIter start = *iter;
1834 GtkTextIter end_iter;
1835 int start_pos = gtk_text_iter_get_offset(&start);
1837 if (!compose->account->sig_sep)
1840 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
1841 start_pos+strlen(compose->account->sig_sep));
1843 /* check sig separator */
1844 str = gtk_text_iter_get_text(&start, &end_iter);
1845 if (!strcmp(str, compose->account->sig_sep)) {
1847 /* check end of line (\n) */
1848 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
1849 start_pos+strlen(compose->account->sig_sep));
1850 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
1851 start_pos+strlen(compose->account->sig_sep)+1);
1852 tmp = gtk_text_iter_get_text(&start, &end_iter);
1853 if (!strcmp(tmp,"\n")) {
1865 static void compose_colorize_signature(Compose *compose)
1867 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
1869 GtkTextIter end_iter;
1870 gtk_text_buffer_get_start_iter(buffer, &iter);
1871 while (gtk_text_iter_forward_line(&iter))
1872 if (compose_is_sig_separator(compose, buffer, &iter)) {
1873 gtk_text_buffer_get_end_iter(buffer, &end_iter);
1874 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
1878 #define BLOCK_WRAP() { \
1879 prev_autowrap = compose->autowrap; \
1880 buffer = gtk_text_view_get_buffer( \
1881 GTK_TEXT_VIEW(compose->text)); \
1882 compose->autowrap = FALSE; \
1884 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1885 G_CALLBACK(compose_changed_cb), \
1887 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1888 G_CALLBACK(text_inserted), \
1891 #define UNBLOCK_WRAP() { \
1892 compose->autowrap = prev_autowrap; \
1893 if (compose->autowrap) { \
1894 gint old = compose->draft_timeout_tag; \
1895 compose->draft_timeout_tag = -2; \
1896 compose_wrap_all(compose); \
1897 compose->draft_timeout_tag = old; \
1900 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1901 G_CALLBACK(compose_changed_cb), \
1903 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1904 G_CALLBACK(text_inserted), \
1908 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
1910 Compose *compose = NULL;
1911 PrefsAccount *account = NULL;
1912 GtkTextView *textview;
1913 GtkTextBuffer *textbuf;
1917 gchar buf[BUFFSIZE];
1918 gboolean use_signing = FALSE;
1919 gboolean use_encryption = FALSE;
1920 gchar *privacy_system = NULL;
1921 int priority = PRIORITY_NORMAL;
1922 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
1924 g_return_val_if_fail(msginfo != NULL, NULL);
1925 g_return_val_if_fail(msginfo->folder != NULL, NULL);
1927 if (compose_put_existing_to_front(msginfo)) {
1931 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
1932 folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
1933 gchar queueheader_buf[BUFFSIZE];
1936 /* Select Account from queue headers */
1937 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1938 sizeof(queueheader_buf), "X-Claws-Account-Id:")) {
1939 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
1940 account = account_find_from_id(id);
1942 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1943 sizeof(queueheader_buf), "X-Sylpheed-Account-Id:")) {
1944 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
1945 account = account_find_from_id(id);
1947 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1948 sizeof(queueheader_buf), "NAID:")) {
1949 id = atoi(&queueheader_buf[strlen("NAID:")]);
1950 account = account_find_from_id(id);
1952 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1953 sizeof(queueheader_buf), "MAID:")) {
1954 id = atoi(&queueheader_buf[strlen("MAID:")]);
1955 account = account_find_from_id(id);
1957 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1958 sizeof(queueheader_buf), "S:")) {
1959 account = account_find_from_address(queueheader_buf, FALSE);
1961 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1962 sizeof(queueheader_buf), "X-Claws-Sign:")) {
1963 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
1964 use_signing = param;
1967 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1968 sizeof(queueheader_buf), "X-Sylpheed-Sign:")) {
1969 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
1970 use_signing = param;
1973 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1974 sizeof(queueheader_buf), "X-Claws-Encrypt:")) {
1975 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
1976 use_encryption = param;
1978 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1979 sizeof(queueheader_buf), "X-Sylpheed-Encrypt:")) {
1980 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
1981 use_encryption = param;
1983 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1984 sizeof(queueheader_buf), "X-Claws-Privacy-System:")) {
1985 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
1987 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1988 sizeof(queueheader_buf), "X-Sylpheed-Privacy-System:")) {
1989 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
1991 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1992 sizeof(queueheader_buf), "X-Priority: ")) {
1993 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
1996 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1997 sizeof(queueheader_buf), "RMID:")) {
1998 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
1999 if (tokens[0] && tokens[1] && tokens[2]) {
2000 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2001 if (orig_item != NULL) {
2002 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2007 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2008 sizeof(queueheader_buf), "FMID:")) {
2009 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
2010 if (tokens[0] && tokens[1] && tokens[2]) {
2011 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2012 if (orig_item != NULL) {
2013 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2019 account = msginfo->folder->folder->account;
2022 if (!account && prefs_common.reedit_account_autosel) {
2023 gchar from[BUFFSIZE];
2024 if (!procheader_get_header_from_msginfo(msginfo, from, sizeof(from), "FROM:")) {
2025 extract_address(from);
2026 account = account_find_from_address(from, FALSE);
2030 account = cur_account;
2032 g_return_val_if_fail(account != NULL, NULL);
2034 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2036 compose->replyinfo = replyinfo;
2037 compose->fwdinfo = fwdinfo;
2039 compose->updating = TRUE;
2040 compose->priority = priority;
2042 if (privacy_system != NULL) {
2043 compose->privacy_system = privacy_system;
2044 compose_use_signing(compose, use_signing);
2045 compose_use_encryption(compose, use_encryption);
2046 compose_update_privacy_system_menu_item(compose, FALSE);
2048 activate_privacy_system(compose, account, FALSE);
2051 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2053 compose_extract_original_charset(compose);
2055 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2056 folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
2057 gchar queueheader_buf[BUFFSIZE];
2059 /* Set message save folder */
2060 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "SCF:")) {
2063 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2064 gtk_editable_delete_text(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
2065 gtk_editable_insert_text(GTK_EDITABLE(compose->savemsg_entry), &queueheader_buf[4], strlen(&queueheader_buf[4]), &startpos);
2067 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "RRCPT:")) {
2068 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2070 GtkItemFactory *ifactory;
2071 ifactory = gtk_item_factory_from_widget(compose->menubar);
2072 menu_set_active(ifactory, "/Options/Request Return Receipt", TRUE);
2077 if (compose_parse_header(compose, msginfo) < 0) {
2078 compose->updating = FALSE;
2079 compose_destroy(compose);
2082 compose_reedit_set_entry(compose, msginfo);
2084 textview = GTK_TEXT_VIEW(compose->text);
2085 textbuf = gtk_text_view_get_buffer(textview);
2086 compose_create_tags(textview, compose);
2088 mark = gtk_text_buffer_get_insert(textbuf);
2089 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2091 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2092 G_CALLBACK(compose_changed_cb),
2095 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2096 fp = procmime_get_first_encrypted_text_content(msginfo);
2098 compose_force_encryption(compose, account, TRUE);
2101 fp = procmime_get_first_text_content(msginfo);
2104 g_warning("Can't get text part\n");
2108 gboolean prev_autowrap = compose->autowrap;
2109 GtkTextBuffer *buffer = textbuf;
2111 while (fgets(buf, sizeof(buf), fp) != NULL) {
2113 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2119 compose_attach_parts(compose, msginfo);
2121 compose_colorize_signature(compose);
2123 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2124 G_CALLBACK(compose_changed_cb),
2127 gtk_widget_grab_focus(compose->text);
2129 if (prefs_common.auto_exteditor) {
2130 compose_exec_ext_editor(compose);
2132 compose->modified = FALSE;
2133 compose_set_title(compose);
2135 compose->updating = FALSE;
2136 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2137 SCROLL_TO_CURSOR(compose);
2139 if (compose->deferred_destroy) {
2140 compose_destroy(compose);
2144 compose->sig_str = compose_get_signature_str(compose);
2149 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2154 GtkItemFactory *ifactory;
2157 g_return_val_if_fail(msginfo != NULL, NULL);
2160 account = account_get_reply_account(msginfo,
2161 prefs_common.reply_account_autosel);
2162 g_return_val_if_fail(account != NULL, NULL);
2164 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2166 compose->updating = TRUE;
2168 ifactory = gtk_item_factory_from_widget(compose->menubar);
2169 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2170 compose->replyinfo = NULL;
2171 compose->fwdinfo = NULL;
2173 compose_show_first_last_header(compose, TRUE);
2175 gtk_widget_grab_focus(compose->header_last->entry);
2177 filename = procmsg_get_message_file(msginfo);
2179 if (filename == NULL) {
2180 compose->updating = FALSE;
2181 compose_destroy(compose);
2186 compose->redirect_filename = filename;
2188 /* Set save folder */
2189 item = msginfo->folder;
2190 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2191 gchar *folderidentifier;
2193 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
2194 folderidentifier = folder_item_get_identifier(item);
2195 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
2196 g_free(folderidentifier);
2199 compose_attach_parts(compose, msginfo);
2201 if (msginfo->subject)
2202 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2204 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2206 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2207 _("Message redirect format error at line %d."));
2208 quote_fmt_reset_vartable();
2209 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2211 compose_colorize_signature(compose);
2213 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
2214 menu_set_sensitive(ifactory, "/Add...", FALSE);
2215 menu_set_sensitive(ifactory, "/Remove", FALSE);
2216 menu_set_sensitive(ifactory, "/Properties...", FALSE);
2218 ifactory = gtk_item_factory_from_widget(compose->menubar);
2219 menu_set_sensitive(ifactory, "/Message/Save", FALSE);
2220 menu_set_sensitive(ifactory, "/Message/Insert file", FALSE);
2221 menu_set_sensitive(ifactory, "/Message/Attach file", FALSE);
2222 menu_set_sensitive(ifactory, "/Message/Insert signature", FALSE);
2223 menu_set_sensitive(ifactory, "/Edit", FALSE);
2224 menu_set_sensitive(ifactory, "/Options", FALSE);
2225 menu_set_sensitive(ifactory, "/Tools/Show ruler", FALSE);
2226 menu_set_sensitive(ifactory, "/Tools/Actions", FALSE);
2228 if (compose->toolbar->draft_btn)
2229 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2230 if (compose->toolbar->insert_btn)
2231 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2232 if (compose->toolbar->attach_btn)
2233 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2234 if (compose->toolbar->sig_btn)
2235 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2236 if (compose->toolbar->exteditor_btn)
2237 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2238 if (compose->toolbar->linewrap_current_btn)
2239 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2240 if (compose->toolbar->linewrap_all_btn)
2241 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2243 compose->modified = FALSE;
2244 compose_set_title(compose);
2245 compose->updating = FALSE;
2246 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2247 SCROLL_TO_CURSOR(compose);
2249 if (compose->deferred_destroy) {
2250 compose_destroy(compose);
2257 GList *compose_get_compose_list(void)
2259 return compose_list;
2262 void compose_entry_append(Compose *compose, const gchar *address,
2263 ComposeEntryType type)
2265 const gchar *header;
2267 gboolean in_quote = FALSE;
2268 if (!address || *address == '\0') return;
2275 header = N_("Bcc:");
2277 case COMPOSE_REPLYTO:
2278 header = N_("Reply-To:");
2280 case COMPOSE_NEWSGROUPS:
2281 header = N_("Newsgroups:");
2283 case COMPOSE_FOLLOWUPTO:
2284 header = N_( "Followup-To:");
2291 header = prefs_common_translated_header_name(header);
2293 cur = begin = (gchar *)address;
2295 /* we separate the line by commas, but not if we're inside a quoted
2297 while (*cur != '\0') {
2299 in_quote = !in_quote;
2300 if (*cur == ',' && !in_quote) {
2301 gchar *tmp = g_strdup(begin);
2303 tmp[cur-begin]='\0';
2306 while (*tmp == ' ' || *tmp == '\t')
2308 compose_add_header_entry(compose, header, tmp);
2315 gchar *tmp = g_strdup(begin);
2317 tmp[cur-begin]='\0';
2320 while (*tmp == ' ' || *tmp == '\t')
2322 compose_add_header_entry(compose, header, tmp);
2327 static void compose_entry_mark_default_to(Compose *compose, const gchar *mailto)
2329 static GdkColor yellow;
2330 static GdkColor black;
2331 static gboolean yellow_initialised = FALSE;
2335 if (!yellow_initialised) {
2336 gdk_color_parse("#f5f6be", &yellow);
2337 gdk_color_parse("#000000", &black);
2338 yellow_initialised = gdk_colormap_alloc_color(
2339 gdk_colormap_get_system(), &yellow, FALSE, TRUE);
2340 yellow_initialised &= gdk_colormap_alloc_color(
2341 gdk_colormap_get_system(), &black, FALSE, TRUE);
2344 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2345 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2346 if (gtk_entry_get_text(entry) &&
2347 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2348 if (yellow_initialised) {
2349 gtk_widget_modify_base(
2350 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2351 GTK_STATE_NORMAL, &yellow);
2352 gtk_widget_modify_text(
2353 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2354 GTK_STATE_NORMAL, &black);
2360 void compose_toolbar_cb(gint action, gpointer data)
2362 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2363 Compose *compose = (Compose*)toolbar_item->parent;
2365 g_return_if_fail(compose != NULL);
2369 compose_send_cb(compose, 0, NULL);
2372 compose_send_later_cb(compose, 0, NULL);
2375 compose_draft_cb(compose, COMPOSE_QUIT_EDITING, NULL);
2378 compose_insert_file_cb(compose, 0, NULL);
2381 compose_attach_cb(compose, 0, NULL);
2384 compose_insert_sig(compose, FALSE);
2387 compose_ext_editor_cb(compose, 0, NULL);
2389 case A_LINEWRAP_CURRENT:
2390 compose_beautify_paragraph(compose, NULL, TRUE);
2392 case A_LINEWRAP_ALL:
2393 compose_wrap_all_full(compose, TRUE);
2396 compose_address_cb(compose, 0, NULL);
2399 case A_CHECK_SPELLING:
2400 compose_check_all(compose);
2408 static void compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
2413 gchar *subject = NULL;
2417 gchar **attach = NULL;
2419 /* get mailto parts but skip from */
2420 scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach);
2423 compose_entry_append(compose, to, to_type);
2425 compose_entry_append(compose, cc, COMPOSE_CC);
2427 compose_entry_append(compose, bcc, COMPOSE_BCC);
2429 if (!g_utf8_validate (subject, -1, NULL)) {
2430 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2431 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2434 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2438 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2439 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2442 gboolean prev_autowrap = compose->autowrap;
2444 compose->autowrap = FALSE;
2446 mark = gtk_text_buffer_get_insert(buffer);
2447 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2449 if (!g_utf8_validate (body, -1, NULL)) {
2450 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2451 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2454 gtk_text_buffer_insert(buffer, &iter, body, -1);
2456 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2458 compose->autowrap = prev_autowrap;
2459 if (compose->autowrap)
2460 compose_wrap_all(compose);
2464 gint i = 0, att = 0;
2465 gchar *warn_files = NULL;
2466 while (attach[i] != NULL) {
2467 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2468 if (utf8_filename) {
2469 if (compose_attach_append(compose, attach[i], utf8_filename, NULL)) {
2470 gchar *tmp = g_strdup_printf("%s%s\n",
2471 warn_files?warn_files:"",
2477 g_free(utf8_filename);
2479 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2484 alertpanel_notice(ngettext(
2485 "The following file has been attached: \n%s",
2486 "The following files have been attached: \n%s", att), warn_files);
2498 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2500 static HeaderEntry hentry[] = {{"Reply-To:", NULL, TRUE},
2501 {"Cc:", NULL, TRUE},
2502 {"References:", NULL, FALSE},
2503 {"Bcc:", NULL, TRUE},
2504 {"Newsgroups:", NULL, TRUE},
2505 {"Followup-To:", NULL, TRUE},
2506 {"List-Post:", NULL, FALSE},
2507 {"X-Priority:", NULL, FALSE},
2508 {NULL, NULL, FALSE}};
2524 g_return_val_if_fail(msginfo != NULL, -1);
2526 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2527 procheader_get_header_fields(fp, hentry);
2530 if (hentry[H_REPLY_TO].body != NULL) {
2531 if (hentry[H_REPLY_TO].body[0] != '\0') {
2533 conv_unmime_header(hentry[H_REPLY_TO].body,
2536 g_free(hentry[H_REPLY_TO].body);
2537 hentry[H_REPLY_TO].body = NULL;
2539 if (hentry[H_CC].body != NULL) {
2540 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL);
2541 g_free(hentry[H_CC].body);
2542 hentry[H_CC].body = NULL;
2544 if (hentry[H_REFERENCES].body != NULL) {
2545 if (compose->mode == COMPOSE_REEDIT)
2546 compose->references = hentry[H_REFERENCES].body;
2548 compose->references = compose_parse_references
2549 (hentry[H_REFERENCES].body, msginfo->msgid);
2550 g_free(hentry[H_REFERENCES].body);
2552 hentry[H_REFERENCES].body = NULL;
2554 if (hentry[H_BCC].body != NULL) {
2555 if (compose->mode == COMPOSE_REEDIT)
2557 conv_unmime_header(hentry[H_BCC].body, NULL);
2558 g_free(hentry[H_BCC].body);
2559 hentry[H_BCC].body = NULL;
2561 if (hentry[H_NEWSGROUPS].body != NULL) {
2562 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2563 hentry[H_NEWSGROUPS].body = NULL;
2565 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2566 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2567 compose->followup_to =
2568 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2571 g_free(hentry[H_FOLLOWUP_TO].body);
2572 hentry[H_FOLLOWUP_TO].body = NULL;
2574 if (hentry[H_LIST_POST].body != NULL) {
2577 extract_address(hentry[H_LIST_POST].body);
2578 if (hentry[H_LIST_POST].body[0] != '\0') {
2579 scan_mailto_url(hentry[H_LIST_POST].body,
2580 NULL, &to, NULL, NULL, NULL, NULL, NULL);
2582 g_free(compose->ml_post);
2583 compose->ml_post = to;
2586 g_free(hentry[H_LIST_POST].body);
2587 hentry[H_LIST_POST].body = NULL;
2590 /* CLAWS - X-Priority */
2591 if (compose->mode == COMPOSE_REEDIT)
2592 if (hentry[H_X_PRIORITY].body != NULL) {
2595 priority = atoi(hentry[H_X_PRIORITY].body);
2596 g_free(hentry[H_X_PRIORITY].body);
2598 hentry[H_X_PRIORITY].body = NULL;
2600 if (priority < PRIORITY_HIGHEST ||
2601 priority > PRIORITY_LOWEST)
2602 priority = PRIORITY_NORMAL;
2604 compose->priority = priority;
2607 if (compose->mode == COMPOSE_REEDIT) {
2608 if (msginfo->inreplyto && *msginfo->inreplyto)
2609 compose->inreplyto = g_strdup(msginfo->inreplyto);
2613 if (msginfo->msgid && *msginfo->msgid)
2614 compose->inreplyto = g_strdup(msginfo->msgid);
2616 if (!compose->references) {
2617 if (msginfo->msgid && *msginfo->msgid) {
2618 if (msginfo->inreplyto && *msginfo->inreplyto)
2619 compose->references =
2620 g_strdup_printf("<%s>\n\t<%s>",
2624 compose->references =
2625 g_strconcat("<", msginfo->msgid, ">",
2627 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
2628 compose->references =
2629 g_strconcat("<", msginfo->inreplyto, ">",
2637 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
2639 GSList *ref_id_list, *cur;
2643 ref_id_list = references_list_append(NULL, ref);
2644 if (!ref_id_list) return NULL;
2645 if (msgid && *msgid)
2646 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
2651 for (cur = ref_id_list; cur != NULL; cur = cur->next)
2652 /* "<" + Message-ID + ">" + CR+LF+TAB */
2653 len += strlen((gchar *)cur->data) + 5;
2655 if (len > MAX_REFERENCES_LEN) {
2656 /* remove second message-ID */
2657 if (ref_id_list && ref_id_list->next &&
2658 ref_id_list->next->next) {
2659 g_free(ref_id_list->next->data);
2660 ref_id_list = g_slist_remove
2661 (ref_id_list, ref_id_list->next->data);
2663 slist_free_strings(ref_id_list);
2664 g_slist_free(ref_id_list);
2671 new_ref = g_string_new("");
2672 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
2673 if (new_ref->len > 0)
2674 g_string_append(new_ref, "\n\t");
2675 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
2678 slist_free_strings(ref_id_list);
2679 g_slist_free(ref_id_list);
2681 new_ref_str = new_ref->str;
2682 g_string_free(new_ref, FALSE);
2687 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
2688 const gchar *fmt, const gchar *qmark,
2689 const gchar *body, gboolean rewrap,
2690 gboolean need_unescape,
2691 const gchar *err_msg)
2693 MsgInfo* dummyinfo = NULL;
2694 gchar *quote_str = NULL;
2696 gboolean prev_autowrap;
2697 const gchar *trimmed_body = body;
2698 gint cursor_pos = -1;
2699 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2700 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2705 SIGNAL_BLOCK(buffer);
2708 dummyinfo = compose_msginfo_new_from_compose(compose);
2709 msginfo = dummyinfo;
2712 if (qmark != NULL) {
2714 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
2715 compose->gtkaspell);
2717 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
2719 quote_fmt_scan_string(qmark);
2722 buf = quote_fmt_get_buffer();
2724 alertpanel_error(_("Quote mark format error."));
2726 Xstrdup_a(quote_str, buf, goto error)
2729 if (fmt && *fmt != '\0') {
2732 while (*trimmed_body == '\n')
2736 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
2737 compose->gtkaspell);
2739 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
2741 if (need_unescape) {
2744 /* decode \-escape sequences in the internal representation of the quote format */
2745 tmp = malloc(strlen(fmt)+1);
2746 pref_get_unescaped_pref(tmp, fmt);
2747 quote_fmt_scan_string(tmp);
2751 quote_fmt_scan_string(fmt);
2755 buf = quote_fmt_get_buffer();
2757 gint line = quote_fmt_get_line();
2758 alertpanel_error(err_msg, line);
2764 prev_autowrap = compose->autowrap;
2765 compose->autowrap = FALSE;
2767 mark = gtk_text_buffer_get_insert(buffer);
2768 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2769 if (g_utf8_validate(buf, -1, NULL)) {
2770 gtk_text_buffer_insert(buffer, &iter, buf, -1);
2772 gchar *tmpout = NULL;
2773 tmpout = conv_codeset_strdup
2774 (buf, conv_get_locale_charset_str_no_utf8(),
2776 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
2778 tmpout = g_malloc(strlen(buf)*2+1);
2779 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
2781 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
2785 cursor_pos = quote_fmt_get_cursor_pos();
2786 compose->set_cursor_pos = cursor_pos;
2787 if (cursor_pos == -1) {
2790 gtk_text_buffer_get_start_iter(buffer, &iter);
2791 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
2792 gtk_text_buffer_place_cursor(buffer, &iter);
2794 compose->autowrap = prev_autowrap;
2795 if (compose->autowrap && rewrap)
2796 compose_wrap_all(compose);
2803 SIGNAL_UNBLOCK(buffer);
2805 procmsg_msginfo_free( dummyinfo );
2810 /* if ml_post is of type addr@host and from is of type
2811 * addr-anything@host, return TRUE
2813 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
2815 gchar *left_ml = NULL;
2816 gchar *right_ml = NULL;
2817 gchar *left_from = NULL;
2818 gchar *right_from = NULL;
2819 gboolean result = FALSE;
2821 if (!ml_post || !from)
2824 left_ml = g_strdup(ml_post);
2825 if (strstr(left_ml, "@")) {
2826 right_ml = strstr(left_ml, "@")+1;
2827 *(strstr(left_ml, "@")) = '\0';
2830 left_from = g_strdup(from);
2831 if (strstr(left_from, "@")) {
2832 right_from = strstr(left_from, "@")+1;
2833 *(strstr(left_from, "@")) = '\0';
2836 if (left_ml && left_from && right_ml && right_from
2837 && !strncmp(left_from, left_ml, strlen(left_ml))
2838 && !strcmp(right_from, right_ml)) {
2847 static gboolean same_address(const gchar *addr1, const gchar *addr2)
2849 gchar *my_addr1, *my_addr2;
2851 if (!addr1 || !addr2)
2854 Xstrdup_a(my_addr1, addr1, return FALSE);
2855 Xstrdup_a(my_addr2, addr2, return FALSE);
2857 extract_address(my_addr1);
2858 extract_address(my_addr2);
2860 return !strcasecmp(my_addr1, my_addr2);
2863 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
2864 gboolean to_all, gboolean to_ml,
2866 gboolean followup_and_reply_to)
2868 GSList *cc_list = NULL;
2871 gchar *replyto = NULL;
2872 GHashTable *to_table;
2874 gboolean reply_to_ml = FALSE;
2875 gboolean default_reply_to = FALSE;
2877 g_return_if_fail(compose->account != NULL);
2878 g_return_if_fail(msginfo != NULL);
2880 reply_to_ml = to_ml && compose->ml_post;
2882 default_reply_to = msginfo->folder &&
2883 msginfo->folder->prefs->enable_default_reply_to;
2885 if (compose->account->protocol != A_NNTP) {
2886 if (reply_to_ml && !default_reply_to) {
2888 gboolean is_subscr = is_subscription(compose->ml_post,
2891 /* normal answer to ml post with a reply-to */
2892 compose_entry_append(compose,
2895 if (compose->replyto
2896 && !same_address(compose->ml_post, compose->replyto))
2897 compose_entry_append(compose,
2901 /* answer to subscription confirmation */
2902 if (compose->replyto)
2903 compose_entry_append(compose,
2906 else if (msginfo->from)
2907 compose_entry_append(compose,
2912 else if (!(to_all || to_sender) && default_reply_to) {
2913 compose_entry_append(compose,
2914 msginfo->folder->prefs->default_reply_to,
2916 compose_entry_mark_default_to(compose,
2917 msginfo->folder->prefs->default_reply_to);
2922 Xstrdup_a(tmp1, msginfo->from, return);
2923 extract_address(tmp1);
2924 if (to_all || to_sender ||
2925 !account_find_from_address(tmp1, FALSE))
2926 compose_entry_append(compose,
2927 (compose->replyto && !to_sender)
2928 ? compose->replyto :
2929 msginfo->from ? msginfo->from : "",
2931 else if (!to_all && !to_sender) {
2932 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
2933 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
2934 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
2935 if (compose->replyto) {
2936 compose_entry_append(compose,
2940 compose_entry_append(compose,
2941 msginfo->from ? msginfo->from : "",
2945 /* replying to own mail, use original recp */
2946 compose_entry_append(compose,
2947 msginfo->to ? msginfo->to : "",
2949 compose_entry_append(compose,
2950 msginfo->cc ? msginfo->cc : "",
2956 if (to_sender || (compose->followup_to &&
2957 !strncmp(compose->followup_to, "poster", 6)))
2958 compose_entry_append
2960 (compose->replyto ? compose->replyto :
2961 msginfo->from ? msginfo->from : ""),
2964 else if (followup_and_reply_to || to_all) {
2965 compose_entry_append
2967 (compose->replyto ? compose->replyto :
2968 msginfo->from ? msginfo->from : ""),
2971 compose_entry_append
2973 compose->followup_to ? compose->followup_to :
2974 compose->newsgroups ? compose->newsgroups : "",
2975 COMPOSE_NEWSGROUPS);
2978 compose_entry_append
2980 compose->followup_to ? compose->followup_to :
2981 compose->newsgroups ? compose->newsgroups : "",
2982 COMPOSE_NEWSGROUPS);
2985 if (msginfo->subject && *msginfo->subject) {
2989 buf = p = g_strdup(msginfo->subject);
2990 p += subject_get_prefix_length(p);
2991 memmove(buf, p, strlen(p) + 1);
2993 buf2 = g_strdup_printf("Re: %s", buf);
2994 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
2999 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3001 if (to_ml && compose->ml_post) return;
3002 if (!to_all || compose->account->protocol == A_NNTP) return;
3004 if (compose->replyto) {
3005 Xstrdup_a(replyto, compose->replyto, return);
3006 extract_address(replyto);
3008 if (msginfo->from) {
3009 Xstrdup_a(from, msginfo->from, return);
3010 extract_address(from);
3013 if (replyto && from)
3014 cc_list = address_list_append_with_comments(cc_list, from);
3015 if (to_all && msginfo->folder &&
3016 msginfo->folder->prefs->enable_default_reply_to)
3017 cc_list = address_list_append_with_comments(cc_list,
3018 msginfo->folder->prefs->default_reply_to);
3019 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3020 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3022 to_table = g_hash_table_new(g_str_hash, g_str_equal);
3024 g_hash_table_insert(to_table, g_utf8_strdown(replyto, -1), GINT_TO_POINTER(1));
3025 if (compose->account) {
3026 g_hash_table_insert(to_table, g_utf8_strdown(compose->account->address, -1),
3027 GINT_TO_POINTER(1));
3029 /* remove address on To: and that of current account */
3030 for (cur = cc_list; cur != NULL; ) {
3031 GSList *next = cur->next;
3034 addr = g_utf8_strdown(cur->data, -1);
3035 extract_address(addr);
3037 if (GPOINTER_TO_INT(g_hash_table_lookup(to_table, addr)) == 1)
3038 cc_list = g_slist_remove(cc_list, cur->data);
3040 g_hash_table_insert(to_table, addr, GINT_TO_POINTER(1));
3044 hash_free_strings(to_table);
3045 g_hash_table_destroy(to_table);
3048 for (cur = cc_list; cur != NULL; cur = cur->next)
3049 compose_entry_append(compose, (gchar *)cur->data,
3051 slist_free_strings(cc_list);
3052 g_slist_free(cc_list);
3057 #define SET_ENTRY(entry, str) \
3060 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3063 #define SET_ADDRESS(type, str) \
3066 compose_entry_append(compose, str, type); \
3069 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3071 g_return_if_fail(msginfo != NULL);
3073 SET_ENTRY(subject_entry, msginfo->subject);
3074 SET_ENTRY(from_name, msginfo->from);
3075 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3076 SET_ADDRESS(COMPOSE_CC, compose->cc);
3077 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3078 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3079 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3080 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3082 compose_update_priority_menu_item(compose);
3083 compose_update_privacy_system_menu_item(compose, FALSE);
3084 compose_show_first_last_header(compose, TRUE);
3090 static void compose_insert_sig(Compose *compose, gboolean replace)
3092 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3093 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3095 GtkTextIter iter, iter_end;
3097 gboolean prev_autowrap;
3098 gboolean found = FALSE;
3099 gboolean exists = FALSE;
3101 g_return_if_fail(compose->account != NULL);
3105 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3106 G_CALLBACK(compose_changed_cb),
3109 mark = gtk_text_buffer_get_insert(buffer);
3110 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3111 cur_pos = gtk_text_iter_get_offset (&iter);
3113 gtk_text_buffer_get_end_iter(buffer, &iter);
3115 exists = (compose->sig_str != NULL);
3118 GtkTextIter first_iter, start_iter, end_iter;
3120 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3122 if (!exists || compose->sig_str[0] == '\0')
3125 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3126 compose->signature_tag);
3129 /* include previous \n\n */
3130 gtk_text_iter_backward_chars(&first_iter, 2);
3131 start_iter = first_iter;
3132 end_iter = first_iter;
3134 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3135 compose->signature_tag);
3136 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3137 compose->signature_tag);
3139 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3145 g_free(compose->sig_str);
3146 compose->sig_str = compose_get_signature_str(compose);
3148 cur_pos = gtk_text_iter_get_offset(&iter);
3150 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3151 g_free(compose->sig_str);
3152 compose->sig_str = NULL;
3154 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3156 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3157 gtk_text_iter_forward_chars(&iter, 2);
3158 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3159 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3161 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3162 cur_pos = gtk_text_buffer_get_char_count (buffer);
3164 /* put the cursor where it should be
3165 * either where the quote_fmt says, either before the signature */
3166 if (compose->set_cursor_pos < 0)
3167 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3169 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3170 compose->set_cursor_pos);
3172 gtk_text_buffer_place_cursor(buffer, &iter);
3173 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3174 G_CALLBACK(compose_changed_cb),
3180 static gchar *compose_get_signature_str(Compose *compose)
3182 gchar *sig_body = NULL;
3183 gchar *sig_str = NULL;
3184 gchar *utf8_sig_str = NULL;
3186 g_return_val_if_fail(compose->account != NULL, NULL);
3188 if (!compose->account->sig_path)
3191 if (compose->account->sig_type == SIG_FILE) {
3192 if (!is_file_or_fifo_exist(compose->account->sig_path)) {
3193 g_warning("can't open signature file: %s\n",
3194 compose->account->sig_path);
3199 if (compose->account->sig_type == SIG_COMMAND)
3200 sig_body = get_command_output(compose->account->sig_path);
3204 tmp = file_read_to_str(compose->account->sig_path);
3207 sig_body = normalize_newlines(tmp);
3211 if (compose->account->sig_sep) {
3212 sig_str = g_strconcat("\n\n", compose->account->sig_sep, "\n", sig_body,
3216 sig_str = g_strconcat("\n\n", sig_body, NULL);
3219 if (g_utf8_validate(sig_str, -1, NULL) == TRUE)
3220 utf8_sig_str = sig_str;
3222 utf8_sig_str = conv_codeset_strdup
3223 (sig_str, conv_get_locale_charset_str_no_utf8(),
3229 return utf8_sig_str;
3232 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3235 GtkTextBuffer *buffer;
3238 const gchar *cur_encoding;
3239 gchar buf[BUFFSIZE];
3242 gboolean prev_autowrap;
3243 gboolean badtxt = FALSE;
3245 g_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3247 if ((fp = g_fopen(file, "rb")) == NULL) {
3248 FILE_OP_ERROR(file, "fopen");
3249 return COMPOSE_INSERT_READ_ERROR;
3252 prev_autowrap = compose->autowrap;
3253 compose->autowrap = FALSE;
3255 text = GTK_TEXT_VIEW(compose->text);
3256 buffer = gtk_text_view_get_buffer(text);
3257 mark = gtk_text_buffer_get_insert(buffer);
3258 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3260 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3261 G_CALLBACK(text_inserted),
3264 cur_encoding = conv_get_locale_charset_str_no_utf8();
3266 while (fgets(buf, sizeof(buf), fp) != NULL) {
3269 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3270 str = g_strdup(buf);
3272 str = conv_codeset_strdup
3273 (buf, cur_encoding, CS_INTERNAL);
3276 /* strip <CR> if DOS/Windows file,
3277 replace <CR> with <LF> if Macintosh file. */
3280 if (len > 0 && str[len - 1] != '\n') {
3282 if (str[len] == '\r') str[len] = '\n';
3285 gtk_text_buffer_insert(buffer, &iter, str, -1);
3289 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3290 G_CALLBACK(text_inserted),
3292 compose->autowrap = prev_autowrap;
3293 if (compose->autowrap)
3294 compose_wrap_all(compose);
3299 return COMPOSE_INSERT_INVALID_CHARACTER;
3301 return COMPOSE_INSERT_SUCCESS;
3304 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3305 const gchar *filename,
3306 const gchar *content_type)
3314 GtkListStore *store;
3316 gboolean has_binary = FALSE;
3318 if (!is_file_exist(file)) {
3319 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3320 gboolean result = FALSE;
3321 if (file_from_uri && is_file_exist(file_from_uri)) {
3322 result = compose_attach_append(
3323 compose, file_from_uri,
3327 g_free(file_from_uri);
3330 alertpanel_error("File %s doesn't exist\n", filename);
3333 if ((size = get_file_size(file)) < 0) {
3334 alertpanel_error("Can't get file size of %s\n", filename);
3338 alertpanel_error(_("File %s is empty."), filename);
3341 if ((fp = g_fopen(file, "rb")) == NULL) {
3342 alertpanel_error(_("Can't read %s."), filename);
3347 ainfo = g_new0(AttachInfo, 1);
3348 auto_ainfo = g_auto_pointer_new_with_free
3349 (ainfo, (GFreeFunc) compose_attach_info_free);
3350 ainfo->file = g_strdup(file);
3353 ainfo->content_type = g_strdup(content_type);
3354 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3356 MsgFlags flags = {0, 0};
3358 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3359 ainfo->encoding = ENC_7BIT;
3361 ainfo->encoding = ENC_8BIT;
3363 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3364 if (msginfo && msginfo->subject)
3365 name = g_strdup(msginfo->subject);
3367 name = g_path_get_basename(filename ? filename : file);
3369 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3371 procmsg_msginfo_free(msginfo);
3373 if (!g_ascii_strncasecmp(content_type, "text", 4))
3374 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3376 ainfo->encoding = ENC_BASE64;
3377 name = g_path_get_basename(filename ? filename : file);
3378 ainfo->name = g_strdup(name);
3382 ainfo->content_type = procmime_get_mime_type(file);
3383 if (!ainfo->content_type) {
3384 ainfo->content_type =
3385 g_strdup("application/octet-stream");
3386 ainfo->encoding = ENC_BASE64;
3387 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text", 4))
3389 procmime_get_encoding_for_text_file(file, &has_binary);
3391 ainfo->encoding = ENC_BASE64;
3392 name = g_path_get_basename(filename ? filename : file);
3393 ainfo->name = g_strdup(name);
3397 if (ainfo->name != NULL
3398 && !strcmp(ainfo->name, ".")) {
3399 g_free(ainfo->name);
3403 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3404 g_free(ainfo->content_type);
3405 ainfo->content_type = g_strdup("application/octet-stream");
3409 size_text = to_human_readable(size);
3411 store = GTK_LIST_STORE(gtk_tree_view_get_model
3412 (GTK_TREE_VIEW(compose->attach_clist)));
3414 gtk_list_store_append(store, &iter);
3415 gtk_list_store_set(store, &iter,
3416 COL_MIMETYPE, ainfo->content_type,
3417 COL_SIZE, size_text,
3418 COL_NAME, ainfo->name,
3420 COL_AUTODATA, auto_ainfo,
3423 g_auto_pointer_free(auto_ainfo);
3424 compose_attach_update_label(compose);
3428 static void compose_use_signing(Compose *compose, gboolean use_signing)
3430 GtkItemFactory *ifactory;
3431 GtkWidget *menuitem = NULL;
3433 compose->use_signing = use_signing;
3434 ifactory = gtk_item_factory_from_widget(compose->menubar);
3435 menuitem = gtk_item_factory_get_item
3436 (ifactory, "/Options/Sign");
3437 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
3441 static void compose_use_encryption(Compose *compose, gboolean use_encryption)
3443 GtkItemFactory *ifactory;
3444 GtkWidget *menuitem = NULL;
3446 compose->use_encryption = use_encryption;
3447 ifactory = gtk_item_factory_from_widget(compose->menubar);
3448 menuitem = gtk_item_factory_get_item
3449 (ifactory, "/Options/Encrypt");
3451 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
3455 #define NEXT_PART_NOT_CHILD(info) \
3457 node = info->node; \
3458 while (node->children) \
3459 node = g_node_last_child(node); \
3460 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3463 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3467 MimeInfo *firsttext = NULL;
3468 MimeInfo *encrypted = NULL;
3471 const gchar *partname = NULL;
3473 mimeinfo = procmime_scan_message(msginfo);
3474 if (!mimeinfo) return;
3476 if (mimeinfo->node->children == NULL) {
3477 procmime_mimeinfo_free_all(mimeinfo);
3481 /* find first content part */
3482 child = (MimeInfo *) mimeinfo->node->children->data;
3483 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3484 child = (MimeInfo *)child->node->children->data;
3486 if (child->type == MIMETYPE_TEXT) {
3488 debug_print("First text part found\n");
3489 } else if (compose->mode == COMPOSE_REEDIT &&
3490 child->type == MIMETYPE_APPLICATION &&
3491 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3492 encrypted = (MimeInfo *)child->node->parent->data;
3495 child = (MimeInfo *) mimeinfo->node->children->data;
3496 while (child != NULL) {
3499 if (child == encrypted) {
3500 /* skip this part of tree */
3501 NEXT_PART_NOT_CHILD(child);
3505 if (child->type == MIMETYPE_MULTIPART) {
3506 /* get the actual content */
3507 child = procmime_mimeinfo_next(child);
3511 if (child == firsttext) {
3512 child = procmime_mimeinfo_next(child);
3516 outfile = procmime_get_tmp_file_name(child);
3517 if ((err = procmime_get_part(outfile, child)) < 0)
3518 g_warning("Can't get the part of multipart message. (%s)", strerror(-err));
3520 gchar *content_type;
3522 content_type = procmime_get_content_type_str(child->type, child->subtype);
3524 /* if we meet a pgp signature, we don't attach it, but
3525 * we force signing. */
3526 if ((strcmp(content_type, "application/pgp-signature") &&
3527 strcmp(content_type, "application/pkcs7-signature") &&
3528 strcmp(content_type, "application/x-pkcs7-signature"))
3529 || compose->mode == COMPOSE_REDIRECT) {
3530 partname = procmime_mimeinfo_get_parameter(child, "filename");
3531 if (partname == NULL)
3532 partname = procmime_mimeinfo_get_parameter(child, "name");
3533 if (partname == NULL)
3535 compose_attach_append(compose, outfile,
3536 partname, content_type);
3538 compose_force_signing(compose, compose->account);
3540 g_free(content_type);
3543 NEXT_PART_NOT_CHILD(child);
3545 procmime_mimeinfo_free_all(mimeinfo);
3548 #undef NEXT_PART_NOT_CHILD
3553 WAIT_FOR_INDENT_CHAR,
3554 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3557 /* return indent length, we allow:
3558 indent characters followed by indent characters or spaces/tabs,
3559 alphabets and numbers immediately followed by indent characters,
3560 and the repeating sequences of the above
3561 If quote ends with multiple spaces, only the first one is included. */
3562 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3563 const GtkTextIter *start, gint *len)
3565 GtkTextIter iter = *start;
3569 IndentState state = WAIT_FOR_INDENT_CHAR;
3572 gint alnum_count = 0;
3573 gint space_count = 0;
3576 if (prefs_common.quote_chars == NULL) {
3580 while (!gtk_text_iter_ends_line(&iter)) {
3581 wc = gtk_text_iter_get_char(&iter);
3582 if (g_unichar_iswide(wc))
3584 clen = g_unichar_to_utf8(wc, ch);
3588 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
3589 is_space = g_unichar_isspace(wc);
3591 if (state == WAIT_FOR_INDENT_CHAR) {
3592 if (!is_indent && !g_unichar_isalnum(wc))
3595 quote_len += alnum_count + space_count + 1;
3596 alnum_count = space_count = 0;
3597 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
3600 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
3601 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
3605 else if (is_indent) {
3606 quote_len += alnum_count + space_count + 1;
3607 alnum_count = space_count = 0;
3610 state = WAIT_FOR_INDENT_CHAR;
3614 gtk_text_iter_forward_char(&iter);
3617 if (quote_len > 0 && space_count > 0)
3623 if (quote_len > 0) {
3625 gtk_text_iter_forward_chars(&iter, quote_len);
3626 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
3632 /* return TRUE if the line is itemized */
3633 static gboolean compose_is_itemized(GtkTextBuffer *buffer,
3634 const GtkTextIter *start)
3636 GtkTextIter iter = *start;
3641 if (gtk_text_iter_ends_line(&iter))
3645 wc = gtk_text_iter_get_char(&iter);
3646 if (!g_unichar_isspace(wc))
3648 gtk_text_iter_forward_char(&iter);
3649 if (gtk_text_iter_ends_line(&iter))
3653 clen = g_unichar_to_utf8(wc, ch);
3657 if (!strchr("*-+", ch[0]))
3660 gtk_text_iter_forward_char(&iter);
3661 if (gtk_text_iter_ends_line(&iter))
3663 wc = gtk_text_iter_get_char(&iter);
3664 if (g_unichar_isspace(wc))
3670 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
3671 const GtkTextIter *start,
3672 GtkTextIter *break_pos,
3676 GtkTextIter iter = *start, line_end = *start;
3677 PangoLogAttr *attrs;
3684 gboolean can_break = FALSE;
3685 gboolean do_break = FALSE;
3686 gboolean was_white = FALSE;
3687 gboolean prev_dont_break = FALSE;
3689 gtk_text_iter_forward_to_line_end(&line_end);
3690 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
3691 len = g_utf8_strlen(str, -1);
3695 g_warning("compose_get_line_break_pos: len = 0!\n");
3699 /* g_print("breaking line: %d: %s (len = %d)\n",
3700 gtk_text_iter_get_line(&iter), str, len); */
3702 attrs = g_new(PangoLogAttr, len + 1);
3704 pango_default_break(str, -1, NULL, attrs, len + 1);
3708 /* skip quote and leading spaces */
3709 for (i = 0; *p != '\0' && i < len; i++) {
3712 wc = g_utf8_get_char(p);
3713 if (i >= quote_len && !g_unichar_isspace(wc))
3715 if (g_unichar_iswide(wc))
3717 else if (*p == '\t')
3721 p = g_utf8_next_char(p);
3724 for (; *p != '\0' && i < len; i++) {
3725 PangoLogAttr *attr = attrs + i;
3729 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
3732 was_white = attr->is_white;
3734 /* don't wrap URI */
3735 if ((uri_len = get_uri_len(p)) > 0) {
3737 if (pos > 0 && col > max_col) {
3747 wc = g_utf8_get_char(p);
3748 if (g_unichar_iswide(wc)) {
3750 if (prev_dont_break && can_break && attr->is_line_break)
3752 } else if (*p == '\t')
3756 if (pos > 0 && col > max_col) {
3761 if (*p == '-' || *p == '/')
3762 prev_dont_break = TRUE;
3764 prev_dont_break = FALSE;
3766 p = g_utf8_next_char(p);
3770 debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col);
3775 *break_pos = *start;
3776 gtk_text_iter_set_line_offset(break_pos, pos);
3781 static gboolean compose_join_next_line(Compose *compose,
3782 GtkTextBuffer *buffer,
3784 const gchar *quote_str)
3786 GtkTextIter iter_ = *iter, cur, prev, next, end;
3787 PangoLogAttr attrs[3];
3789 gchar *next_quote_str;
3792 gboolean keep_cursor = FALSE;
3794 if (!gtk_text_iter_forward_line(&iter_) ||
3795 gtk_text_iter_ends_line(&iter_)) {
3798 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
3800 if ((quote_str || next_quote_str) &&
3801 strcmp2(quote_str, next_quote_str) != 0) {
3802 g_free(next_quote_str);
3805 g_free(next_quote_str);
3808 if (quote_len > 0) {
3809 gtk_text_iter_forward_chars(&end, quote_len);
3810 if (gtk_text_iter_ends_line(&end)) {
3815 /* don't join itemized lines */
3816 if (compose_is_itemized(buffer, &end)) {
3820 /* don't join signature separator */
3821 if (compose_is_sig_separator(compose, buffer, &iter_)) {
3824 /* delete quote str */
3826 gtk_text_buffer_delete(buffer, &iter_, &end);
3828 /* don't join line breaks put by the user */
3830 gtk_text_iter_backward_char(&cur);
3831 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
3832 gtk_text_iter_forward_char(&cur);
3836 gtk_text_iter_forward_char(&cur);
3837 /* delete linebreak and extra spaces */
3838 while (gtk_text_iter_backward_char(&cur)) {
3839 wc1 = gtk_text_iter_get_char(&cur);
3840 if (!g_unichar_isspace(wc1))
3845 while (!gtk_text_iter_ends_line(&cur)) {
3846 wc1 = gtk_text_iter_get_char(&cur);
3847 if (!g_unichar_isspace(wc1))
3849 gtk_text_iter_forward_char(&cur);
3852 if (!gtk_text_iter_equal(&prev, &next)) {
3855 mark = gtk_text_buffer_get_insert(buffer);
3856 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
3857 if (gtk_text_iter_equal(&prev, &cur))
3859 gtk_text_buffer_delete(buffer, &prev, &next);
3863 /* insert space if required */
3864 gtk_text_iter_backward_char(&prev);
3865 wc1 = gtk_text_iter_get_char(&prev);
3866 wc2 = gtk_text_iter_get_char(&next);
3867 gtk_text_iter_forward_char(&next);
3868 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
3869 pango_default_break(str, -1, NULL, attrs, 3);
3870 if (!attrs[1].is_line_break ||
3871 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
3872 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
3874 gtk_text_iter_backward_char(&iter_);
3875 gtk_text_buffer_place_cursor(buffer, &iter_);
3884 #define ADD_TXT_POS(bp_, ep_, pti_) \
3885 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
3886 last = last->next; \
3887 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
3888 last->next = NULL; \
3890 g_warning("alloc error scanning URIs\n"); \
3893 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
3895 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3896 GtkTextBuffer *buffer;
3897 GtkTextIter iter, break_pos, end_of_line;
3898 gchar *quote_str = NULL;
3900 gboolean wrap_quote = prefs_common.linewrap_quote;
3901 gboolean prev_autowrap = compose->autowrap;
3902 gint startq_offset = -1, noq_offset = -1;
3903 gint uri_start = -1, uri_stop = -1;
3904 gint nouri_start = -1, nouri_stop = -1;
3905 gint num_blocks = 0;
3906 gint quotelevel = -1;
3907 gboolean modified = force;
3908 gboolean removed = FALSE;
3909 gboolean modified_before_remove = FALSE;
3911 gboolean start = TRUE;
3916 if (compose->draft_timeout_tag == -2) {
3920 compose->autowrap = FALSE;
3922 buffer = gtk_text_view_get_buffer(text);
3923 undo_wrapping(compose->undostruct, TRUE);
3928 mark = gtk_text_buffer_get_insert(buffer);
3929 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3933 if (compose->draft_timeout_tag == -2) {
3934 if (gtk_text_iter_ends_line(&iter)) {
3935 while (gtk_text_iter_ends_line(&iter) &&
3936 gtk_text_iter_forward_line(&iter))
3939 while (gtk_text_iter_backward_line(&iter)) {
3940 if (gtk_text_iter_ends_line(&iter)) {
3941 gtk_text_iter_forward_line(&iter);
3947 /* move to line start */
3948 gtk_text_iter_set_line_offset(&iter, 0);
3950 /* go until paragraph end (empty line) */
3951 while (start || !gtk_text_iter_ends_line(&iter)) {
3952 gchar *scanpos = NULL;
3953 /* parse table - in order of priority */
3955 const gchar *needle; /* token */
3957 /* token search function */
3958 gchar *(*search) (const gchar *haystack,
3959 const gchar *needle);
3960 /* part parsing function */
3961 gboolean (*parse) (const gchar *start,
3962 const gchar *scanpos,
3966 /* part to URI function */
3967 gchar *(*build_uri) (const gchar *bp,
3971 static struct table parser[] = {
3972 {"http://", strcasestr, get_uri_part, make_uri_string},
3973 {"https://", strcasestr, get_uri_part, make_uri_string},
3974 {"ftp://", strcasestr, get_uri_part, make_uri_string},
3975 {"sftp://", strcasestr, get_uri_part, make_uri_string},
3976 {"www.", strcasestr, get_uri_part, make_http_string},
3977 {"mailto:", strcasestr, get_uri_part, make_uri_string},
3978 {"@", strcasestr, get_email_part, make_email_string}
3980 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
3981 gint last_index = PARSE_ELEMS;
3983 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
3987 if (!prev_autowrap && num_blocks == 0) {
3989 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3990 G_CALLBACK(text_inserted),
3993 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
3996 uri_start = uri_stop = -1;
3998 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
4001 debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str);
4002 if (startq_offset == -1)
4003 startq_offset = gtk_text_iter_get_offset(&iter);
4004 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4005 if (quotelevel > 2) {
4006 /* recycle colors */
4007 if (prefs_common.recycle_quote_colors)
4016 if (startq_offset == -1)
4017 noq_offset = gtk_text_iter_get_offset(&iter);
4021 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4024 if (gtk_text_iter_ends_line(&iter)) {
4026 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4027 prefs_common.linewrap_len,
4029 GtkTextIter prev, next, cur;
4031 if (prev_autowrap != FALSE || force) {
4032 compose->automatic_break = TRUE;
4034 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4035 compose->automatic_break = FALSE;
4036 } else if (quote_str && wrap_quote) {
4037 compose->automatic_break = TRUE;
4039 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4040 compose->automatic_break = FALSE;
4043 /* remove trailing spaces */
4045 gtk_text_iter_backward_char(&cur);
4047 while (!gtk_text_iter_starts_line(&cur)) {
4050 gtk_text_iter_backward_char(&cur);
4051 wc = gtk_text_iter_get_char(&cur);
4052 if (!g_unichar_isspace(wc))
4056 if (!gtk_text_iter_equal(&prev, &next)) {
4057 gtk_text_buffer_delete(buffer, &prev, &next);
4059 gtk_text_iter_forward_char(&break_pos);
4063 gtk_text_buffer_insert(buffer, &break_pos,
4067 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4069 /* move iter to current line start */
4070 gtk_text_iter_set_line_offset(&iter, 0);
4077 /* move iter to next line start */
4083 if (!prev_autowrap && num_blocks > 0) {
4085 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4086 G_CALLBACK(text_inserted),
4090 while (!gtk_text_iter_ends_line(&end_of_line)) {
4091 gtk_text_iter_forward_char(&end_of_line);
4093 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4095 nouri_start = gtk_text_iter_get_offset(&iter);
4096 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4098 walk_pos = gtk_text_iter_get_offset(&iter);
4099 /* FIXME: this looks phony. scanning for anything in the parse table */
4100 for (n = 0; n < PARSE_ELEMS; n++) {
4103 tmp = parser[n].search(walk, parser[n].needle);
4105 if (scanpos == NULL || tmp < scanpos) {
4114 /* check if URI can be parsed */
4115 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4116 (const gchar **)&ep, FALSE)
4117 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4121 strlen(parser[last_index].needle);
4124 uri_start = walk_pos + (bp - o_walk);
4125 uri_stop = walk_pos + (ep - o_walk);
4129 gtk_text_iter_forward_line(&iter);
4132 if (startq_offset != -1) {
4133 GtkTextIter startquote, endquote;
4134 gtk_text_buffer_get_iter_at_offset(
4135 buffer, &startquote, startq_offset);
4138 switch (quotelevel) {
4140 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4141 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4142 gtk_text_buffer_apply_tag_by_name(
4143 buffer, "quote0", &startquote, &endquote);
4144 gtk_text_buffer_remove_tag_by_name(
4145 buffer, "quote1", &startquote, &endquote);
4146 gtk_text_buffer_remove_tag_by_name(
4147 buffer, "quote2", &startquote, &endquote);
4152 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4153 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4154 gtk_text_buffer_apply_tag_by_name(
4155 buffer, "quote1", &startquote, &endquote);
4156 gtk_text_buffer_remove_tag_by_name(
4157 buffer, "quote0", &startquote, &endquote);
4158 gtk_text_buffer_remove_tag_by_name(
4159 buffer, "quote2", &startquote, &endquote);
4164 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4165 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4166 gtk_text_buffer_apply_tag_by_name(
4167 buffer, "quote2", &startquote, &endquote);
4168 gtk_text_buffer_remove_tag_by_name(
4169 buffer, "quote0", &startquote, &endquote);
4170 gtk_text_buffer_remove_tag_by_name(
4171 buffer, "quote1", &startquote, &endquote);
4177 } else if (noq_offset != -1) {
4178 GtkTextIter startnoquote, endnoquote;
4179 gtk_text_buffer_get_iter_at_offset(
4180 buffer, &startnoquote, noq_offset);
4183 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4184 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4185 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4186 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4187 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4188 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4189 gtk_text_buffer_remove_tag_by_name(
4190 buffer, "quote0", &startnoquote, &endnoquote);
4191 gtk_text_buffer_remove_tag_by_name(
4192 buffer, "quote1", &startnoquote, &endnoquote);
4193 gtk_text_buffer_remove_tag_by_name(
4194 buffer, "quote2", &startnoquote, &endnoquote);
4200 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4201 GtkTextIter nouri_start_iter, nouri_end_iter;
4202 gtk_text_buffer_get_iter_at_offset(
4203 buffer, &nouri_start_iter, nouri_start);
4204 gtk_text_buffer_get_iter_at_offset(
4205 buffer, &nouri_end_iter, nouri_stop);
4206 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4207 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4208 gtk_text_buffer_remove_tag_by_name(
4209 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4210 modified_before_remove = modified;
4215 if (uri_start >= 0 && uri_stop > 0) {
4216 GtkTextIter uri_start_iter, uri_end_iter, back;
4217 gtk_text_buffer_get_iter_at_offset(
4218 buffer, &uri_start_iter, uri_start);
4219 gtk_text_buffer_get_iter_at_offset(
4220 buffer, &uri_end_iter, uri_stop);
4221 back = uri_end_iter;
4222 gtk_text_iter_backward_char(&back);
4223 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4224 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4225 gtk_text_buffer_apply_tag_by_name(
4226 buffer, "link", &uri_start_iter, &uri_end_iter);
4228 if (removed && !modified_before_remove) {
4234 debug_print("not modified, out after %d lines\n", lines);
4239 debug_print("modified, out after %d lines\n", lines);
4243 undo_wrapping(compose->undostruct, FALSE);
4244 compose->autowrap = prev_autowrap;
4249 void compose_action_cb(void *data)
4251 Compose *compose = (Compose *)data;
4252 compose_wrap_all(compose);
4255 static void compose_wrap_all(Compose *compose)
4257 compose_wrap_all_full(compose, FALSE);
4260 static void compose_wrap_all_full(Compose *compose, gboolean force)
4262 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4263 GtkTextBuffer *buffer;
4265 gboolean modified = TRUE;
4267 buffer = gtk_text_view_get_buffer(text);
4269 gtk_text_buffer_get_start_iter(buffer, &iter);
4270 while (!gtk_text_iter_is_end(&iter) && modified)
4271 modified = compose_beautify_paragraph(compose, &iter, force);
4275 static void compose_set_title(Compose *compose)
4281 edited = compose->modified ? _(" [Edited]") : "";
4283 subject = gtk_editable_get_chars(
4284 GTK_EDITABLE(compose->subject_entry), 0, -1);
4287 if (subject && strlen(subject))
4288 str = g_strdup_printf(_("%s - Compose message%s"),
4291 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4293 str = g_strdup(_("Compose message"));
4296 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4302 * compose_current_mail_account:
4304 * Find a current mail account (the currently selected account, or the
4305 * default account, if a news account is currently selected). If a
4306 * mail account cannot be found, display an error message.
4308 * Return value: Mail account, or NULL if not found.
4310 static PrefsAccount *
4311 compose_current_mail_account(void)
4315 if (cur_account && cur_account->protocol != A_NNTP)
4318 ac = account_get_default();
4319 if (!ac || ac->protocol == A_NNTP) {
4320 alertpanel_error(_("Account for sending mail is not specified.\n"
4321 "Please select a mail account before sending."));
4328 #define QUOTE_IF_REQUIRED(out, str) \
4330 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4334 len = strlen(str) + 3; \
4335 if ((__tmp = alloca(len)) == NULL) { \
4336 g_warning("can't allocate memory\n"); \
4337 g_string_free(header, TRUE); \
4340 g_snprintf(__tmp, len, "\"%s\"", str); \
4345 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4346 g_warning("can't allocate memory\n"); \
4347 g_string_free(header, TRUE); \
4350 strcpy(__tmp, str); \
4356 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4358 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4362 len = strlen(str) + 3; \
4363 if ((__tmp = alloca(len)) == NULL) { \
4364 g_warning("can't allocate memory\n"); \
4367 g_snprintf(__tmp, len, "\"%s\"", str); \
4372 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4373 g_warning("can't allocate memory\n"); \
4376 strcpy(__tmp, str); \
4382 static void compose_select_account(Compose *compose, PrefsAccount *account,
4385 GtkItemFactory *ifactory;
4388 g_return_if_fail(account != NULL);
4390 compose->account = account;
4392 if (account->name && *account->name) {
4394 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4395 from = g_strdup_printf("%s <%s>",
4396 buf, account->address);
4397 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4399 from = g_strdup_printf("<%s>",
4401 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4406 compose_set_title(compose);
4408 ifactory = gtk_item_factory_from_widget(compose->menubar);
4410 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4411 menu_set_active(ifactory, "/Options/Sign", TRUE);
4413 menu_set_active(ifactory, "/Options/Sign", FALSE);
4414 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4415 menu_set_active(ifactory, "/Options/Encrypt", TRUE);
4417 menu_set_active(ifactory, "/Options/Encrypt", FALSE);
4419 activate_privacy_system(compose, account, FALSE);
4421 if (!init && compose->mode != COMPOSE_REDIRECT) {
4422 undo_block(compose->undostruct);
4423 compose_insert_sig(compose, TRUE);
4424 undo_unblock(compose->undostruct);
4428 /* use account's dict info if set */
4429 if (compose->gtkaspell) {
4430 if (account->enable_default_dictionary)
4431 gtkaspell_change_dict(compose->gtkaspell,
4432 account->default_dictionary, FALSE);
4433 if (account->enable_default_alt_dictionary)
4434 gtkaspell_change_alt_dict(compose->gtkaspell,
4435 account->default_alt_dictionary);
4436 if (account->enable_default_dictionary
4437 || account->enable_default_alt_dictionary)
4438 compose_spell_menu_changed(compose);
4443 gboolean compose_check_for_valid_recipient(Compose *compose) {
4444 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
4445 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
4446 gboolean recipient_found = FALSE;
4450 /* free to and newsgroup list */
4451 slist_free_strings(compose->to_list);
4452 g_slist_free(compose->to_list);
4453 compose->to_list = NULL;
4455 slist_free_strings(compose->newsgroup_list);
4456 g_slist_free(compose->newsgroup_list);
4457 compose->newsgroup_list = NULL;
4459 /* search header entries for to and newsgroup entries */
4460 for (list = compose->header_list; list; list = list->next) {
4463 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_BIN(((ComposeHeaderEntry *)list->data)->combo)->child), 0, -1);
4464 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4467 if (entry[0] != '\0') {
4468 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
4469 if (!strcmp(header, prefs_common_translated_header_name(*strptr))) {
4470 compose->to_list = address_list_append(compose->to_list, entry);
4471 recipient_found = TRUE;
4474 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
4475 if (!strcmp(header, prefs_common_translated_header_name(*strptr))) {
4476 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
4477 recipient_found = TRUE;
4484 return recipient_found;
4487 static gboolean compose_check_for_set_recipients(Compose *compose)
4489 if (compose->account->set_autocc && compose->account->auto_cc) {
4490 gboolean found_other = FALSE;
4492 /* search header entries for to and newsgroup entries */
4493 for (list = compose->header_list; list; list = list->next) {
4496 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4497 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_BIN(((ComposeHeaderEntry *)list->data)->combo)->child), 0, -1);
4500 if (strcmp(entry, compose->account->auto_cc)
4501 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
4511 if (compose->batch) {
4512 gtk_widget_show_all(compose->window);
4514 aval = alertpanel(_("Send"),
4515 _("The only recipient is the default CC address. Send anyway?"),
4516 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4517 if (aval != G_ALERTALTERNATE)
4521 if (compose->account->set_autobcc && compose->account->auto_bcc) {
4522 gboolean found_other = FALSE;
4524 /* search header entries for to and newsgroup entries */
4525 for (list = compose->header_list; list; list = list->next) {
4528 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4529 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_BIN(((ComposeHeaderEntry *)list->data)->combo)->child), 0, -1);
4532 if (strcmp(entry, compose->account->auto_bcc)
4533 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
4543 if (compose->batch) {
4544 gtk_widget_show_all(compose->window);
4546 aval = alertpanel(_("Send"),
4547 _("The only recipient is the default BCC address. Send anyway?"),
4548 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4549 if (aval != G_ALERTALTERNATE)
4556 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
4560 if (compose_check_for_valid_recipient(compose) == FALSE) {
4561 if (compose->batch) {
4562 gtk_widget_show_all(compose->window);
4564 alertpanel_error(_("Recipient is not specified."));
4568 if (compose_check_for_set_recipients(compose) == FALSE) {
4572 if (!compose->batch) {
4573 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
4574 if (*str == '\0' && check_everything == TRUE &&
4575 compose->mode != COMPOSE_REDIRECT) {
4577 gchar *button_label;
4580 if (compose->sending)
4581 button_label = _("+_Send");
4583 button_label = _("+_Queue");
4584 message = g_strdup_printf(_("Subject is empty. %s"),
4585 compose->sending?_("Send it anyway?"):
4586 _("Queue it anyway?"));
4588 aval = alertpanel(compose->sending?_("Send"):_("Send later"), message,
4589 GTK_STOCK_CANCEL, button_label, NULL);
4591 if (aval != G_ALERTALTERNATE)
4596 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
4602 gint compose_send(Compose *compose)
4605 FolderItem *folder = NULL;
4607 gchar *msgpath = NULL;
4608 gboolean discard_window = FALSE;
4609 gchar *errstr = NULL;
4610 gchar *tmsgid = NULL;
4611 MainWindow *mainwin = mainwindow_get_mainwindow();
4612 gboolean queued_removed = FALSE;
4614 if (prefs_common.send_dialog_invisible
4615 || compose->batch == TRUE)
4616 discard_window = TRUE;
4618 compose_allow_user_actions (compose, FALSE);
4619 compose->sending = TRUE;
4621 if (compose_check_entries(compose, TRUE) == FALSE) {
4622 if (compose->batch) {
4623 gtk_widget_show_all(compose->window);
4629 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
4632 if (compose->batch) {
4633 gtk_widget_show_all(compose->window);
4636 alertpanel_error(_("Could not queue message for sending:\n\n"
4637 "Charset conversion failed."));
4638 } else if (val == -5) {
4639 alertpanel_error(_("Could not queue message for sending:\n\n"
4640 "Couldn't get recipient encryption key."));
4641 } else if (val == -6) {
4643 } else if (val == -3) {
4644 if (privacy_peek_error())
4645 alertpanel_error(_("Could not queue message for sending:\n\n"
4646 "Signature failed: %s"), privacy_get_error());
4647 } else if (val == -2 && errno != 0) {
4648 alertpanel_error(_("Could not queue message for sending:\n\n%s."), strerror(errno));
4650 alertpanel_error(_("Could not queue message for sending."));
4655 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
4656 if (discard_window) {
4657 compose->sending = FALSE;
4658 compose_close(compose);
4659 /* No more compose access in the normal codepath
4660 * after this point! */
4665 alertpanel_error(_("The message was queued but could not be "
4666 "sent.\nUse \"Send queued messages\" from "
4667 "the main window to retry."));
4668 if (!discard_window) {
4675 if (msgpath == NULL) {
4676 msgpath = folder_item_fetch_msg(folder, msgnum);
4677 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
4680 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
4684 if (!discard_window) {
4686 if (!queued_removed)
4687 folder_item_remove_msg(folder, msgnum);
4688 folder_item_scan(folder);
4690 /* make sure we delete that */
4691 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
4693 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
4694 folder_item_remove_msg(folder, tmp->msgnum);
4695 procmsg_msginfo_free(tmp);
4702 if (!queued_removed)
4703 folder_item_remove_msg(folder, msgnum);
4704 folder_item_scan(folder);
4706 /* make sure we delete that */
4707 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
4709 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
4710 folder_item_remove_msg(folder, tmp->msgnum);
4711 procmsg_msginfo_free(tmp);
4714 if (!discard_window) {
4715 compose->sending = FALSE;
4716 compose_allow_user_actions (compose, TRUE);
4717 compose_close(compose);
4721 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
4722 "the main window to retry."), errstr);
4725 alertpanel_error_log(_("The message was queued but could not be "
4726 "sent.\nUse \"Send queued messages\" from "
4727 "the main window to retry."));
4729 if (!discard_window) {
4738 toolbar_main_set_sensitive(mainwin);
4739 main_window_set_menu_sensitive(mainwin);
4745 compose_allow_user_actions (compose, TRUE);
4746 compose->sending = FALSE;
4747 compose->modified = TRUE;
4748 toolbar_main_set_sensitive(mainwin);
4749 main_window_set_menu_sensitive(mainwin);
4754 static gboolean compose_use_attach(Compose *compose)
4756 GtkTreeModel *model = gtk_tree_view_get_model
4757 (GTK_TREE_VIEW(compose->attach_clist));
4758 return gtk_tree_model_iter_n_children(model, NULL) > 0;
4761 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
4764 gchar buf[BUFFSIZE];
4766 gboolean first_to_address;
4767 gboolean first_cc_address;
4769 ComposeHeaderEntry *headerentry;
4770 const gchar *headerentryname;
4771 const gchar *cc_hdr;
4772 const gchar *to_hdr;
4773 gboolean err = FALSE;
4775 debug_print("Writing redirect header\n");
4777 cc_hdr = prefs_common_translated_header_name("Cc:");
4778 to_hdr = prefs_common_translated_header_name("To:");
4780 first_to_address = TRUE;
4781 for (list = compose->header_list; list; list = list->next) {
4782 headerentry = ((ComposeHeaderEntry *)list->data);
4783 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_BIN(headerentry->combo)->child));
4785 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
4786 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
4787 Xstrdup_a(str, entstr, return -1);
4789 if (str[0] != '\0') {
4790 compose_convert_header
4791 (compose, buf, sizeof(buf), str,
4792 strlen("Resent-To") + 2, TRUE);
4794 if (first_to_address) {
4795 err |= (fprintf(fp, "Resent-To: ") < 0);
4796 first_to_address = FALSE;
4798 err |= (fprintf(fp, ",") < 0);
4800 err |= (fprintf(fp, "%s", buf) < 0);
4804 if (!first_to_address) {
4805 err |= (fprintf(fp, "\n") < 0);
4808 first_cc_address = TRUE;
4809 for (list = compose->header_list; list; list = list->next) {
4810 headerentry = ((ComposeHeaderEntry *)list->data);
4811 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_BIN(headerentry->combo)->child));
4813 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
4814 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
4815 Xstrdup_a(str, strg, return -1);
4817 if (str[0] != '\0') {
4818 compose_convert_header
4819 (compose, buf, sizeof(buf), str,
4820 strlen("Resent-Cc") + 2, TRUE);
4822 if (first_cc_address) {
4823 err |= (fprintf(fp, "Resent-Cc: ") < 0);
4824 first_cc_address = FALSE;
4826 err |= (fprintf(fp, ",") < 0);
4828 err |= (fprintf(fp, "%s", buf) < 0);
4832 if (!first_cc_address) {
4833 err |= (fprintf(fp, "\n") < 0);
4836 return (err ? -1:0);
4839 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
4841 gchar buf[BUFFSIZE];
4843 const gchar *entstr;
4844 /* struct utsname utsbuf; */
4845 gboolean err = FALSE;
4847 g_return_val_if_fail(fp != NULL, -1);
4848 g_return_val_if_fail(compose->account != NULL, -1);
4849 g_return_val_if_fail(compose->account->address != NULL, -1);
4852 get_rfc822_date(buf, sizeof(buf));
4853 err |= (fprintf(fp, "Resent-Date: %s\n", buf) < 0);
4856 if (compose->account->name && *compose->account->name) {
4857 compose_convert_header
4858 (compose, buf, sizeof(buf), compose->account->name,
4859 strlen("From: "), TRUE);
4860 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
4861 buf, compose->account->address) < 0);
4863 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
4866 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
4867 if (*entstr != '\0') {
4868 Xstrdup_a(str, entstr, return -1);
4871 compose_convert_header(compose, buf, sizeof(buf), str,
4872 strlen("Subject: "), FALSE);
4873 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
4877 /* Resent-Message-ID */
4878 if (compose->account->set_domain && compose->account->domain) {
4879 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
4880 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
4881 g_snprintf(buf, sizeof(buf), "%s",
4882 strchr(compose->account->address, '@') ?
4883 strchr(compose->account->address, '@')+1 :
4884 compose->account->address);
4886 g_snprintf(buf, sizeof(buf), "%s", "");
4889 if (compose->account->gen_msgid) {
4890 generate_msgid(buf, sizeof(buf));
4891 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", buf) < 0);
4892 compose->msgid = g_strdup(buf);
4894 compose->msgid = NULL;
4897 if (compose_redirect_write_headers_from_headerlist(compose, fp))
4900 /* separator between header and body */
4901 err |= (fputs("\n", fp) == EOF);
4903 return (err ? -1:0);
4906 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
4910 gchar buf[BUFFSIZE];
4912 gboolean skip = FALSE;
4913 gboolean err = FALSE;
4914 gchar *not_included[]={
4915 "Return-Path:", "Delivered-To:", "Received:",
4916 "Subject:", "X-UIDL:", "AF:",
4917 "NF:", "PS:", "SRH:",
4918 "SFN:", "DSR:", "MID:",
4919 "CFG:", "PT:", "S:",
4920 "RQ:", "SSV:", "NSV:",
4921 "SSH:", "R:", "MAID:",
4922 "NAID:", "RMID:", "FMID:",
4923 "SCF:", "RRCPT:", "NG:",
4924 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
4925 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
4926 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
4927 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
4930 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
4931 FILE_OP_ERROR(compose->redirect_filename, "fopen");
4935 while (procheader_get_one_field_asis(buf, sizeof(buf), fp) != -1) {
4937 for (i = 0; not_included[i] != NULL; i++) {
4938 if (g_ascii_strncasecmp(buf, not_included[i],
4939 strlen(not_included[i])) == 0) {
4946 if (fputs(buf, fdest) == -1)
4949 if (!prefs_common.redirect_keep_from) {
4950 if (g_ascii_strncasecmp(buf, "From:",
4951 strlen("From:")) == 0) {
4952 err |= (fputs(" (by way of ", fdest) == EOF);
4953 if (compose->account->name
4954 && *compose->account->name) {
4955 compose_convert_header
4956 (compose, buf, sizeof(buf),
4957 compose->account->name,
4960 err |= (fprintf(fdest, "%s <%s>",
4962 compose->account->address) < 0);
4964 err |= (fprintf(fdest, "%s",
4965 compose->account->address) < 0);
4966 err |= (fputs(")", fdest) == EOF);
4970 if (fputs("\n", fdest) == -1)
4977 if (compose_redirect_write_headers(compose, fdest))
4980 while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
4981 if (fwrite(buf, sizeof(gchar), len, fdest) != len)
4994 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
4996 GtkTextBuffer *buffer;
4997 GtkTextIter start, end;
5000 const gchar *out_codeset;
5001 EncodingType encoding;
5002 MimeInfo *mimemsg, *mimetext;
5005 if (action == COMPOSE_WRITE_FOR_SEND)
5006 attach_parts = TRUE;
5008 /* create message MimeInfo */
5009 mimemsg = procmime_mimeinfo_new();
5010 mimemsg->type = MIMETYPE_MESSAGE;
5011 mimemsg->subtype = g_strdup("rfc822");
5012 mimemsg->content = MIMECONTENT_MEM;
5013 mimemsg->tmp = TRUE; /* must free content later */
5014 mimemsg->data.mem = compose_get_header(compose);
5016 /* Create text part MimeInfo */
5017 /* get all composed text */
5018 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5019 gtk_text_buffer_get_start_iter(buffer, &start);
5020 gtk_text_buffer_get_end_iter(buffer, &end);
5021 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5022 if (is_ascii_str(chars)) {
5025 out_codeset = CS_US_ASCII;
5026 encoding = ENC_7BIT;
5028 const gchar *src_codeset = CS_INTERNAL;
5030 out_codeset = conv_get_charset_str(compose->out_encoding);
5033 gchar *test_conv_global_out = NULL;
5034 gchar *test_conv_reply = NULL;
5036 /* automatic mode. be automatic. */
5037 codeconv_set_strict(TRUE);
5039 out_codeset = conv_get_outgoing_charset_str();
5041 debug_print("trying to convert to %s\n", out_codeset);
5042 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5045 if (!test_conv_global_out && compose->orig_charset
5046 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5047 out_codeset = compose->orig_charset;
5048 debug_print("failure; trying to convert to %s\n", out_codeset);
5049 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5052 if (!test_conv_global_out && !test_conv_reply) {
5054 out_codeset = CS_INTERNAL;
5055 debug_print("failure; finally using %s\n", out_codeset);
5057 g_free(test_conv_global_out);
5058 g_free(test_conv_reply);
5059 codeconv_set_strict(FALSE);
5062 if (!g_ascii_strcasecmp(out_codeset, CS_US_ASCII))
5063 out_codeset = CS_ISO_8859_1;
5065 if (prefs_common.encoding_method == CTE_BASE64)
5066 encoding = ENC_BASE64;
5067 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5068 encoding = ENC_QUOTED_PRINTABLE;
5069 else if (prefs_common.encoding_method == CTE_8BIT)
5070 encoding = ENC_8BIT;
5072 encoding = procmime_get_encoding_for_charset(out_codeset);
5074 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5075 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5077 if (action == COMPOSE_WRITE_FOR_SEND) {
5078 codeconv_set_strict(TRUE);
5079 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5080 codeconv_set_strict(FALSE);
5086 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5087 "to the specified %s charset.\n"
5088 "Send it as %s?"), out_codeset, src_codeset);
5089 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL, _("+_Send"), NULL, FALSE,
5090 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5093 if (aval != G_ALERTALTERNATE) {
5098 out_codeset = src_codeset;
5104 out_codeset = src_codeset;
5110 if (encoding == ENC_8BIT || encoding == ENC_7BIT) {
5111 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5112 strstr(buf, "\nFrom ") != NULL) {
5113 encoding = ENC_QUOTED_PRINTABLE;
5117 mimetext = procmime_mimeinfo_new();
5118 mimetext->content = MIMECONTENT_MEM;
5119 mimetext->tmp = TRUE; /* must free content later */
5120 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5121 * and free the data, which we need later. */
5122 mimetext->data.mem = g_strdup(buf);
5123 mimetext->type = MIMETYPE_TEXT;
5124 mimetext->subtype = g_strdup("plain");
5125 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5126 g_strdup(out_codeset));
5128 /* protect trailing spaces when signing message */
5129 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5130 privacy_system_can_sign(compose->privacy_system)) {
5131 encoding = ENC_QUOTED_PRINTABLE;
5134 debug_print("main text: %zd bytes encoded as %s in %d\n",
5135 strlen(buf), out_codeset, encoding);
5137 /* check for line length limit */
5138 if (action == COMPOSE_WRITE_FOR_SEND &&
5139 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5140 check_line_length(buf, 1000, &line) < 0) {
5144 msg = g_strdup_printf
5145 (_("Line %d exceeds the line length limit (998 bytes).\n"
5146 "The contents of the message might be broken on the way to the delivery.\n"
5148 "Send it anyway?"), line + 1);
5149 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5151 if (aval != G_ALERTALTERNATE) {
5157 if (encoding != ENC_UNKNOWN)
5158 procmime_encode_content(mimetext, encoding);
5160 /* append attachment parts */
5161 if (compose_use_attach(compose) && attach_parts) {
5162 MimeInfo *mimempart;
5163 gchar *boundary = NULL;
5164 mimempart = procmime_mimeinfo_new();
5165 mimempart->content = MIMECONTENT_EMPTY;
5166 mimempart->type = MIMETYPE_MULTIPART;
5167 mimempart->subtype = g_strdup("mixed");
5171 boundary = generate_mime_boundary(NULL);
5172 } while (strstr(buf, boundary) != NULL);
5174 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5177 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5179 g_node_append(mimempart->node, mimetext->node);
5180 g_node_append(mimemsg->node, mimempart->node);
5182 compose_add_attachments(compose, mimempart);
5184 g_node_append(mimemsg->node, mimetext->node);
5188 /* sign message if sending */
5189 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5190 privacy_system_can_sign(compose->privacy_system))
5191 if (!privacy_sign(compose->privacy_system, mimemsg, compose->account))
5194 procmime_write_mimeinfo(mimemsg, fp);
5196 procmime_mimeinfo_free_all(mimemsg);
5201 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5203 GtkTextBuffer *buffer;
5204 GtkTextIter start, end;
5209 if ((fp = g_fopen(file, "wb")) == NULL) {
5210 FILE_OP_ERROR(file, "fopen");
5214 /* chmod for security */
5215 if (change_file_mode_rw(fp, file) < 0) {
5216 FILE_OP_ERROR(file, "chmod");
5217 g_warning("can't change file mode\n");
5220 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5221 gtk_text_buffer_get_start_iter(buffer, &start);
5222 gtk_text_buffer_get_end_iter(buffer, &end);
5223 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5225 chars = conv_codeset_strdup
5226 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5229 if (!chars) return -1;
5232 len = strlen(chars);
5233 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5234 FILE_OP_ERROR(file, "fwrite");
5243 if (fclose(fp) == EOF) {
5244 FILE_OP_ERROR(file, "fclose");
5251 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5254 MsgInfo *msginfo = compose->targetinfo;
5256 g_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5257 if (!msginfo) return -1;
5259 if (!force && MSG_IS_LOCKED(msginfo->flags))
5262 item = msginfo->folder;
5263 g_return_val_if_fail(item != NULL, -1);
5265 if (procmsg_msg_exist(msginfo) &&
5266 (folder_has_parent_of_type(item, F_QUEUE) ||
5267 folder_has_parent_of_type(item, F_DRAFT)
5268 || msginfo == compose->autosaved_draft)) {
5269 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5270 g_warning("can't remove the old message\n");
5273 debug_print("removed reedit target %d\n", msginfo->msgnum);
5280 static void compose_remove_draft(Compose *compose)
5283 MsgInfo *msginfo = compose->targetinfo;
5284 drafts = account_get_special_folder(compose->account, F_DRAFT);
5286 if (procmsg_msg_exist(msginfo)) {
5287 folder_item_remove_msg(drafts, msginfo->msgnum);
5292 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5293 gboolean remove_reedit_target)
5295 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5298 static gboolean compose_warn_encryption(Compose *compose)
5300 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5301 AlertValue val = G_ALERTALTERNATE;
5303 if (warning == NULL)
5306 val = alertpanel_full(_("Encryption warning"), warning,
5307 GTK_STOCK_CANCEL, _("+C_ontinue"), NULL,
5308 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5309 if (val & G_ALERTDISABLE) {
5310 val &= ~G_ALERTDISABLE;
5311 if (val == G_ALERTALTERNATE)
5312 privacy_inhibit_encrypt_warning(compose->privacy_system,
5316 if (val == G_ALERTALTERNATE) {
5323 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
5324 gchar **msgpath, gboolean check_subject,
5325 gboolean remove_reedit_target)
5332 static gboolean lock = FALSE;
5333 PrefsAccount *mailac = NULL, *newsac = NULL;
5334 gboolean err = FALSE;
5336 debug_print("queueing message...\n");
5337 g_return_val_if_fail(compose->account != NULL, -1);
5341 if (compose_check_entries(compose, check_subject) == FALSE) {
5343 if (compose->batch) {
5344 gtk_widget_show_all(compose->window);
5349 if (!compose->to_list && !compose->newsgroup_list) {
5350 g_warning("can't get recipient list.");
5355 if (compose->to_list) {
5356 if (compose->account->protocol != A_NNTP)
5357 mailac = compose->account;
5358 else if (cur_account && cur_account->protocol != A_NNTP)
5359 mailac = cur_account;
5360 else if (!(mailac = compose_current_mail_account())) {
5362 alertpanel_error(_("No account for sending mails available!"));
5367 if (compose->newsgroup_list) {
5368 if (compose->account->protocol == A_NNTP)
5369 newsac = compose->account;
5370 else if (!newsac->protocol != A_NNTP) {
5372 alertpanel_error(_("No account for posting news available!"));
5377 /* write queue header */
5378 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
5379 G_DIR_SEPARATOR, compose, (guint) rand());
5380 debug_print("queuing to %s\n", tmp);
5381 if ((fp = g_fopen(tmp, "wb")) == NULL) {
5382 FILE_OP_ERROR(tmp, "fopen");
5388 if (change_file_mode_rw(fp, tmp) < 0) {
5389 FILE_OP_ERROR(tmp, "chmod");
5390 g_warning("can't change file mode\n");
5393 /* queueing variables */
5394 err |= (fprintf(fp, "AF:\n") < 0);
5395 err |= (fprintf(fp, "NF:0\n") < 0);
5396 err |= (fprintf(fp, "PS:10\n") < 0);
5397 err |= (fprintf(fp, "SRH:1\n") < 0);
5398 err |= (fprintf(fp, "SFN:\n") < 0);
5399 err |= (fprintf(fp, "DSR:\n") < 0);
5401 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
5403 err |= (fprintf(fp, "MID:\n") < 0);
5404 err |= (fprintf(fp, "CFG:\n") < 0);
5405 err |= (fprintf(fp, "PT:0\n") < 0);
5406 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
5407 err |= (fprintf(fp, "RQ:\n") < 0);
5409 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
5411 err |= (fprintf(fp, "SSV:\n") < 0);
5413 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
5415 err |= (fprintf(fp, "NSV:\n") < 0);
5416 err |= (fprintf(fp, "SSH:\n") < 0);
5417 /* write recepient list */
5418 if (compose->to_list) {
5419 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
5420 for (cur = compose->to_list->next; cur != NULL;
5422 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
5423 err |= (fprintf(fp, "\n") < 0);
5425 /* write newsgroup list */
5426 if (compose->newsgroup_list) {
5427 err |= (fprintf(fp, "NG:") < 0);
5428 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
5429 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
5430 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
5431 err |= (fprintf(fp, "\n") < 0);
5433 /* Sylpheed account IDs */
5435 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
5437 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
5440 if (compose->privacy_system != NULL) {
5441 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
5442 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
5443 if (compose->use_encryption) {
5445 if (!compose_warn_encryption(compose)) {
5452 if (mailac && mailac->encrypt_to_self) {
5453 GSList *tmp_list = g_slist_copy(compose->to_list);
5454 tmp_list = g_slist_append(tmp_list, compose->account->address);
5455 encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
5456 g_slist_free(tmp_list);
5458 encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
5460 if (encdata != NULL) {
5461 if (strcmp(encdata, "_DONT_ENCRYPT_")) {
5462 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5463 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
5465 } /* else we finally dont want to encrypt */
5467 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5468 /* and if encdata was null, it means there's been a problem in
5480 /* Save copy folder */
5481 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
5482 gchar *savefolderid;
5484 savefolderid = gtk_editable_get_chars(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
5485 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
5486 g_free(savefolderid);
5488 /* Save copy folder */
5489 if (compose->return_receipt) {
5490 err |= (fprintf(fp, "RRCPT:1\n") < 0);
5492 /* Message-ID of message replying to */
5493 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
5496 folderid = folder_item_get_identifier(compose->replyinfo->folder);
5497 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
5500 /* Message-ID of message forwarding to */
5501 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
5504 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
5505 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
5509 /* end of headers */
5510 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
5512 if (compose->redirect_filename != NULL) {
5513 if (compose_redirect_write_to_file(compose, fp) < 0) {
5522 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
5527 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
5531 g_warning("failed to write queue message\n");
5538 if (fclose(fp) == EOF) {
5539 FILE_OP_ERROR(tmp, "fclose");
5546 if (item && *item) {
5549 queue = account_get_special_folder(compose->account, F_QUEUE);
5552 g_warning("can't find queue folder\n");
5558 folder_item_scan(queue);
5559 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
5560 g_warning("can't queue the message\n");
5567 if (msgpath == NULL) {
5573 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
5574 compose_remove_reedit_target(compose, FALSE);
5577 if ((msgnum != NULL) && (item != NULL)) {
5585 static void compose_add_attachments(Compose *compose, MimeInfo *parent)
5588 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
5590 struct stat statbuf;
5591 gchar *type, *subtype;
5592 GtkTreeModel *model;
5595 model = gtk_tree_view_get_model(tree_view);
5597 if (!gtk_tree_model_get_iter_first(model, &iter))
5600 gtk_tree_model_get(model, &iter,
5604 mimepart = procmime_mimeinfo_new();
5605 mimepart->content = MIMECONTENT_FILE;
5606 mimepart->data.filename = g_strdup(ainfo->file);
5607 mimepart->tmp = FALSE; /* or we destroy our attachment */
5608 mimepart->offset = 0;
5610 stat(ainfo->file, &statbuf);
5611 mimepart->length = statbuf.st_size;
5613 type = g_strdup(ainfo->content_type);
5615 if (!strchr(type, '/')) {
5617 type = g_strdup("application/octet-stream");
5620 subtype = strchr(type, '/') + 1;
5621 *(subtype - 1) = '\0';
5622 mimepart->type = procmime_get_media_type(type);
5623 mimepart->subtype = g_strdup(subtype);
5626 if (mimepart->type == MIMETYPE_MESSAGE &&
5627 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
5628 mimepart->disposition = DISPOSITIONTYPE_INLINE;
5631 g_hash_table_insert(mimepart->typeparameters,
5632 g_strdup("name"), g_strdup(ainfo->name));
5633 g_hash_table_insert(mimepart->dispositionparameters,
5634 g_strdup("filename"), g_strdup(ainfo->name));
5635 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
5639 if (compose->use_signing) {
5640 if (ainfo->encoding == ENC_7BIT)
5641 ainfo->encoding = ENC_QUOTED_PRINTABLE;
5642 else if (ainfo->encoding == ENC_8BIT)
5643 ainfo->encoding = ENC_BASE64;
5646 procmime_encode_content(mimepart, ainfo->encoding);
5648 g_node_append(parent->node, mimepart->node);
5649 } while (gtk_tree_model_iter_next(model, &iter));
5652 #define IS_IN_CUSTOM_HEADER(header) \
5653 (compose->account->add_customhdr && \
5654 custom_header_find(compose->account->customhdr_list, header) != NULL)
5656 static void compose_add_headerfield_from_headerlist(Compose *compose,
5658 const gchar *fieldname,
5659 const gchar *seperator)
5661 gchar *str, *fieldname_w_colon;
5662 gboolean add_field = FALSE;
5664 ComposeHeaderEntry *headerentry;
5665 const gchar *headerentryname;
5666 const gchar *trans_fieldname;
5669 if (IS_IN_CUSTOM_HEADER(fieldname))
5672 debug_print("Adding %s-fields\n", fieldname);
5674 fieldstr = g_string_sized_new(64);
5676 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
5677 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
5679 for (list = compose->header_list; list; list = list->next) {
5680 headerentry = ((ComposeHeaderEntry *)list->data);
5681 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_BIN(headerentry->combo)->child));
5683 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
5684 str = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
5686 if (str[0] != '\0') {
5688 g_string_append(fieldstr, seperator);
5689 g_string_append(fieldstr, str);
5698 buf = g_new0(gchar, fieldstr->len * 4 + 256);
5699 compose_convert_header
5700 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
5701 strlen(fieldname) + 2, TRUE);
5702 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
5706 g_free(fieldname_w_colon);
5707 g_string_free(fieldstr, TRUE);
5712 static gchar *compose_get_header(Compose *compose)
5714 gchar buf[BUFFSIZE];
5715 const gchar *entry_str;
5719 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
5721 gchar *from_name = NULL, *from_address = NULL;
5724 g_return_val_if_fail(compose->account != NULL, NULL);
5725 g_return_val_if_fail(compose->account->address != NULL, NULL);
5727 header = g_string_sized_new(64);
5730 get_rfc822_date(buf, sizeof(buf));
5731 g_string_append_printf(header, "Date: %s\n", buf);
5735 if (compose->account->name && *compose->account->name) {
5737 QUOTE_IF_REQUIRED(buf, compose->account->name);
5738 tmp = g_strdup_printf("%s <%s>",
5739 buf, compose->account->address);
5741 tmp = g_strdup_printf("%s",
5742 compose->account->address);
5744 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
5745 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
5747 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
5748 from_address = g_strdup(compose->account->address);
5750 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5751 /* extract name and address */
5752 if (strstr(spec, " <") && strstr(spec, ">")) {
5753 from_address = g_strdup(strrchr(spec, '<')+1);
5754 *(strrchr(from_address, '>')) = '\0';
5755 from_name = g_strdup(spec);
5756 *(strrchr(from_name, '<')) = '\0';
5759 from_address = g_strdup(spec);
5766 if (from_name && *from_name) {
5767 compose_convert_header
5768 (compose, buf, sizeof(buf), from_name,
5769 strlen("From: "), TRUE);
5770 QUOTE_IF_REQUIRED(name, buf);
5772 g_string_append_printf(header, "From: %s <%s>\n",
5773 name, from_address);
5775 g_string_append_printf(header, "From: %s\n", from_address);
5778 g_free(from_address);
5781 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
5784 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
5787 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
5790 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
5793 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
5795 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
5798 compose_convert_header(compose, buf, sizeof(buf), str,
5799 strlen("Subject: "), FALSE);
5800 g_string_append_printf(header, "Subject: %s\n", buf);
5806 if (compose->account->set_domain && compose->account->domain) {
5807 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
5808 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
5809 g_snprintf(buf, sizeof(buf), "%s",
5810 strchr(compose->account->address, '@') ?
5811 strchr(compose->account->address, '@')+1 :
5812 compose->account->address);
5814 g_snprintf(buf, sizeof(buf), "%s", "");
5817 if (compose->account->gen_msgid) {
5818 generate_msgid(buf, sizeof(buf));
5819 g_string_append_printf(header, "Message-ID: <%s>\n", buf);
5820 compose->msgid = g_strdup(buf);
5822 compose->msgid = NULL;
5825 if (compose->remove_references == FALSE) {
5827 if (compose->inreplyto && compose->to_list)
5828 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
5831 if (compose->references)
5832 g_string_append_printf(header, "References: %s\n", compose->references);
5836 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
5839 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
5842 if (compose->account->organization &&
5843 strlen(compose->account->organization) &&
5844 !IS_IN_CUSTOM_HEADER("Organization")) {
5845 compose_convert_header(compose, buf, sizeof(buf),
5846 compose->account->organization,
5847 strlen("Organization: "), FALSE);
5848 g_string_append_printf(header, "Organization: %s\n", buf);
5851 /* Program version and system info */
5852 if (g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
5853 !compose->newsgroup_list) {
5854 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
5856 gtk_major_version, gtk_minor_version, gtk_micro_version,
5859 if (g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
5860 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
5862 gtk_major_version, gtk_minor_version, gtk_micro_version,
5866 /* custom headers */
5867 if (compose->account->add_customhdr) {
5870 for (cur = compose->account->customhdr_list; cur != NULL;
5872 CustomHeader *chdr = (CustomHeader *)cur->data;
5874 if (custom_header_is_allowed(chdr->name)) {
5875 compose_convert_header
5876 (compose, buf, sizeof(buf),
5877 chdr->value ? chdr->value : "",
5878 strlen(chdr->name) + 2, FALSE);
5879 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
5885 switch (compose->priority) {
5886 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
5887 "X-Priority: 1 (Highest)\n");
5889 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
5890 "X-Priority: 2 (High)\n");
5892 case PRIORITY_NORMAL: break;
5893 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
5894 "X-Priority: 4 (Low)\n");
5896 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
5897 "X-Priority: 5 (Lowest)\n");
5899 default: debug_print("compose: priority unknown : %d\n",
5903 /* Request Return Receipt */
5904 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To")) {
5905 if (compose->return_receipt) {
5906 if (compose->account->name
5907 && *compose->account->name) {
5908 compose_convert_header(compose, buf, sizeof(buf),
5909 compose->account->name,
5910 strlen("Disposition-Notification-To: "),
5912 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, compose->account->address);
5914 g_string_append_printf(header, "Disposition-Notification-To: %s\n", compose->account->address);
5918 /* get special headers */
5919 for (list = compose->header_list; list; list = list->next) {
5920 ComposeHeaderEntry *headerentry;
5923 gchar *headername_wcolon;
5924 const gchar *headername_trans;
5927 gboolean standard_header = FALSE;
5929 headerentry = ((ComposeHeaderEntry *)list->data);
5931 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(GTK_BIN(headerentry->combo)->child)));
5933 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
5938 if (!strstr(tmp, ":")) {
5939 headername_wcolon = g_strconcat(tmp, ":", NULL);
5940 headername = g_strdup(tmp);
5942 headername_wcolon = g_strdup(tmp);
5943 headername = g_strdup(strtok(tmp, ":"));
5947 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5948 Xstrdup_a(headervalue, entry_str, return NULL);
5949 subst_char(headervalue, '\r', ' ');
5950 subst_char(headervalue, '\n', ' ');
5951 string = std_headers;
5952 while (*string != NULL) {
5953 headername_trans = prefs_common_translated_header_name(*string);
5954 if (!strcmp(headername_trans, headername_wcolon))
5955 standard_header = TRUE;
5958 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
5959 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
5962 g_free(headername_wcolon);
5966 g_string_free(header, FALSE);
5971 #undef IS_IN_CUSTOM_HEADER
5973 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
5974 gint header_len, gboolean addr_field)
5976 gchar *tmpstr = NULL;
5977 const gchar *out_codeset = NULL;
5979 g_return_if_fail(src != NULL);
5980 g_return_if_fail(dest != NULL);
5982 if (len < 1) return;
5984 tmpstr = g_strdup(src);
5986 subst_char(tmpstr, '\n', ' ');
5987 subst_char(tmpstr, '\r', ' ');
5990 if (!g_utf8_validate(tmpstr, -1, NULL)) {
5991 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
5992 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
5997 codeconv_set_strict(TRUE);
5998 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
5999 conv_get_charset_str(compose->out_encoding));
6000 codeconv_set_strict(FALSE);
6002 if (!dest || *dest == '\0') {
6003 gchar *test_conv_global_out = NULL;
6004 gchar *test_conv_reply = NULL;
6006 /* automatic mode. be automatic. */
6007 codeconv_set_strict(TRUE);
6009 out_codeset = conv_get_outgoing_charset_str();
6011 debug_print("trying to convert to %s\n", out_codeset);
6012 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6015 if (!test_conv_global_out && compose->orig_charset
6016 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6017 out_codeset = compose->orig_charset;
6018 debug_print("failure; trying to convert to %s\n", out_codeset);
6019 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6022 if (!test_conv_global_out && !test_conv_reply) {
6024 out_codeset = CS_INTERNAL;
6025 debug_print("finally using %s\n", out_codeset);
6027 g_free(test_conv_global_out);
6028 g_free(test_conv_reply);
6029 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6031 codeconv_set_strict(FALSE);
6036 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
6040 g_return_if_fail(user_data != NULL);
6042 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6043 g_strstrip(address);
6044 if (*address != '\0') {
6045 gchar *name = procheader_get_fromname(address);
6046 extract_address(address);
6047 addressbook_add_contact(name, address, NULL, NULL);
6052 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
6054 GtkWidget *menuitem;
6057 g_return_if_fail(menu != NULL);
6058 g_return_if_fail(GTK_IS_MENU_SHELL(menu));
6060 menuitem = gtk_separator_menu_item_new();
6061 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6062 gtk_widget_show(menuitem);
6064 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
6065 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6067 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
6068 g_strstrip(address);
6069 if (*address == '\0') {
6070 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
6073 g_signal_connect(G_OBJECT(menuitem), "activate",
6074 G_CALLBACK(compose_add_to_addressbook_cb), entry);
6075 gtk_widget_show(menuitem);
6078 static void compose_create_header_entry(Compose *compose)
6080 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6085 const gchar *header = NULL;
6086 ComposeHeaderEntry *headerentry;
6087 gboolean standard_header = FALSE;
6089 headerentry = g_new0(ComposeHeaderEntry, 1);
6092 combo = gtk_combo_box_entry_new_text();
6094 while(*string != NULL) {
6095 gtk_combo_box_append_text(GTK_COMBO_BOX(combo),
6096 (gchar*)prefs_common_translated_header_name(*string));
6099 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
6100 g_signal_connect(G_OBJECT(GTK_BIN(combo)->child), "grab_focus",
6101 G_CALLBACK(compose_grab_focus_cb), compose);
6102 gtk_widget_show(combo);
6103 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
6104 compose->header_nextrow, compose->header_nextrow+1,
6105 GTK_SHRINK, GTK_FILL, 0, 0);
6106 if (compose->header_last) {
6107 const gchar *last_header_entry = gtk_entry_get_text(
6108 GTK_ENTRY(GTK_BIN(compose->header_last->combo)->child));
6110 while (*string != NULL) {
6111 if (!strcmp(*string, last_header_entry))
6112 standard_header = TRUE;
6115 if (standard_header)
6116 header = gtk_entry_get_text(GTK_ENTRY(GTK_BIN(compose->header_last->combo)->child));
6118 if (!compose->header_last || !standard_header) {
6119 switch(compose->account->protocol) {
6121 header = prefs_common_translated_header_name("Newsgroups:");
6124 header = prefs_common_translated_header_name("To:");
6129 gtk_entry_set_text(GTK_ENTRY(GTK_BIN(combo)->child), header);
6131 g_signal_connect_after(G_OBJECT(GTK_BIN(combo)->child), "grab_focus",
6132 G_CALLBACK(compose_grab_focus_cb), compose);
6135 entry = gtk_entry_new();
6136 gtk_widget_show(entry);
6137 gtk_tooltips_set_tip(compose->tooltips, entry,
6138 _("Use <tab> to autocomplete from addressbook"), NULL);
6139 gtk_table_attach(GTK_TABLE(compose->header_table), entry, 1, 2,
6140 compose->header_nextrow, compose->header_nextrow+1,
6141 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
6143 g_signal_connect(G_OBJECT(entry), "key-press-event",
6144 G_CALLBACK(compose_headerentry_key_press_event_cb),
6146 g_signal_connect(G_OBJECT(entry), "changed",
6147 G_CALLBACK(compose_headerentry_changed_cb),
6149 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
6150 G_CALLBACK(compose_grab_focus_cb), compose);
6153 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6154 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6155 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6156 g_signal_connect(G_OBJECT(entry), "drag_data_received",
6157 G_CALLBACK(compose_header_drag_received_cb),
6159 g_signal_connect(G_OBJECT(entry), "drag-drop",
6160 G_CALLBACK(compose_drag_drop),
6162 g_signal_connect(G_OBJECT(entry), "populate-popup",
6163 G_CALLBACK(compose_entry_popup_extend),
6166 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
6168 headerentry->compose = compose;
6169 headerentry->combo = combo;
6170 headerentry->entry = entry;
6171 headerentry->headernum = compose->header_nextrow;
6173 compose->header_nextrow++;
6174 compose->header_last = headerentry;
6175 compose->header_list =
6176 g_slist_append(compose->header_list,
6180 static void compose_add_header_entry(Compose *compose, const gchar *header, gchar *text)
6182 ComposeHeaderEntry *last_header;
6184 last_header = compose->header_last;
6186 gtk_entry_set_text(GTK_ENTRY(GTK_BIN(last_header->combo)->child), header);
6187 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
6190 static void compose_remove_header_entries(Compose *compose)
6193 for (list = compose->header_list; list; list = list->next) {
6194 ComposeHeaderEntry *headerentry =
6195 (ComposeHeaderEntry *)list->data;
6196 gtk_widget_destroy(headerentry->combo);
6197 gtk_widget_destroy(headerentry->entry);
6198 g_free(headerentry);
6200 compose->header_last = NULL;
6201 g_slist_free(compose->header_list);
6202 compose->header_list = NULL;
6203 compose->header_nextrow = 1;
6204 compose_create_header_entry(compose);
6207 static GtkWidget *compose_create_header(Compose *compose)
6209 GtkWidget *from_optmenu_hbox;
6210 GtkWidget *header_scrolledwin;
6211 GtkWidget *header_table;
6215 /* header labels and entries */
6216 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
6217 gtk_widget_show(header_scrolledwin);
6218 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
6220 header_table = gtk_table_new(2, 2, FALSE);
6221 gtk_widget_show(header_table);
6222 gtk_container_set_border_width(GTK_CONTAINER(header_table), BORDER_WIDTH);
6223 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
6224 gtk_viewport_set_shadow_type(GTK_VIEWPORT(GTK_BIN(header_scrolledwin)->child), GTK_SHADOW_NONE);
6227 /* option menu for selecting accounts */
6228 from_optmenu_hbox = compose_account_option_menu_create(compose);
6229 gtk_table_attach(GTK_TABLE(header_table), from_optmenu_hbox,
6230 0, 2, count, count + 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
6233 compose->header_table = header_table;
6234 compose->header_list = NULL;
6235 compose->header_nextrow = count;
6237 compose_create_header_entry(compose);
6239 compose->table = NULL;
6241 return header_scrolledwin ;
6244 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
6246 Compose *compose = (Compose *)data;
6247 GdkEventButton event;
6250 event.time = gtk_get_current_event_time();
6252 return attach_button_pressed(compose->attach_clist, &event, compose);
6255 static GtkWidget *compose_create_attach(Compose *compose)
6257 GtkWidget *attach_scrwin;
6258 GtkWidget *attach_clist;
6260 GtkListStore *store;
6261 GtkCellRenderer *renderer;
6262 GtkTreeViewColumn *column;
6263 GtkTreeSelection *selection;
6265 /* attachment list */
6266 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
6267 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
6268 GTK_POLICY_AUTOMATIC,
6269 GTK_POLICY_AUTOMATIC);
6270 gtk_widget_set_size_request(attach_scrwin, -1, 80);
6272 store = gtk_list_store_new(N_ATTACH_COLS,
6277 G_TYPE_AUTO_POINTER,
6279 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
6280 (GTK_TREE_MODEL(store)));
6281 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
6282 g_object_unref(store);
6284 renderer = gtk_cell_renderer_text_new();
6285 column = gtk_tree_view_column_new_with_attributes
6286 (_("Mime type"), renderer, "text",
6287 COL_MIMETYPE, NULL);
6288 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6290 renderer = gtk_cell_renderer_text_new();
6291 column = gtk_tree_view_column_new_with_attributes
6292 (_("Size"), renderer, "text",
6294 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6296 renderer = gtk_cell_renderer_text_new();
6297 column = gtk_tree_view_column_new_with_attributes
6298 (_("Name"), renderer, "text",
6300 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6302 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
6303 prefs_common.use_stripes_everywhere);
6304 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
6305 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
6307 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
6308 G_CALLBACK(attach_selected), compose);
6309 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
6310 G_CALLBACK(attach_button_pressed), compose);
6312 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
6313 G_CALLBACK(popup_attach_button_pressed), compose);
6315 gtk_widget_tap_and_hold_setup(GTK_WIDGET(attach_clist), NULL, NULL,
6316 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
6317 g_signal_connect(G_OBJECT(attach_clist), "tap-and-hold",
6318 G_CALLBACK(popup_attach_button_pressed), compose);
6320 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
6321 G_CALLBACK(attach_key_pressed), compose);
6324 gtk_drag_dest_set(attach_clist,
6325 GTK_DEST_DEFAULT_ALL, compose_mime_types,
6326 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6327 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6328 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
6329 G_CALLBACK(compose_attach_drag_received_cb),
6331 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
6332 G_CALLBACK(compose_drag_drop),
6335 compose->attach_scrwin = attach_scrwin;
6336 compose->attach_clist = attach_clist;
6338 return attach_scrwin;
6341 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
6342 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
6344 static GtkWidget *compose_create_others(Compose *compose)
6347 GtkWidget *savemsg_checkbtn;
6348 GtkWidget *savemsg_entry;
6349 GtkWidget *savemsg_select;
6352 gchar *folderidentifier;
6354 /* Table for settings */
6355 table = gtk_table_new(3, 1, FALSE);
6356 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
6357 gtk_widget_show(table);
6358 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
6361 /* Save Message to folder */
6362 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
6363 gtk_widget_show(savemsg_checkbtn);
6364 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6365 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6366 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
6368 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
6369 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
6371 savemsg_entry = gtk_entry_new();
6372 gtk_widget_show(savemsg_entry);
6373 gtk_table_attach_defaults(GTK_TABLE(table), savemsg_entry, 1, 2, rowcount, rowcount + 1);
6374 gtk_editable_set_editable(GTK_EDITABLE(savemsg_entry), prefs_common.savemsg);
6375 g_signal_connect_after(G_OBJECT(savemsg_entry), "grab_focus",
6376 G_CALLBACK(compose_grab_focus_cb), compose);
6377 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6378 folderidentifier = folder_item_get_identifier(account_get_special_folder
6379 (compose->account, F_OUTBOX));
6380 gtk_entry_set_text(GTK_ENTRY(savemsg_entry), folderidentifier);
6381 g_free(folderidentifier);
6384 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
6385 gtk_widget_show(savemsg_select);
6386 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6387 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
6388 G_CALLBACK(compose_savemsg_select_cb),
6393 compose->savemsg_checkbtn = savemsg_checkbtn;
6394 compose->savemsg_entry = savemsg_entry;
6399 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
6401 gtk_editable_set_editable(GTK_EDITABLE(compose->savemsg_entry),
6402 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
6405 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
6410 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL);
6413 path = folder_item_get_identifier(dest);
6415 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), path);
6419 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
6420 GdkAtom clip, GtkTextIter *insert_place);
6423 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
6427 GtkTextBuffer *buffer = GTK_TEXT_VIEW(text)->buffer;
6429 if (event->button == 3) {
6431 GtkTextIter sel_start, sel_end;
6432 gboolean stuff_selected;
6434 /* move the cursor to allow GtkAspell to check the word
6435 * under the mouse */
6436 if (event->x && event->y) {
6437 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6438 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6440 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6443 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
6444 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
6447 stuff_selected = gtk_text_buffer_get_selection_bounds(
6449 &sel_start, &sel_end);
6451 gtk_text_buffer_place_cursor (buffer, &iter);
6452 /* reselect stuff */
6454 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
6455 gtk_text_buffer_select_range(buffer,
6456 &sel_start, &sel_end);
6458 return FALSE; /* pass the event so that the right-click goes through */
6461 if (event->button == 2) {
6466 /* get the middle-click position to paste at the correct place */
6467 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6468 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6470 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6473 entry_paste_clipboard(compose, text,
6474 prefs_common.linewrap_pastes,
6475 GDK_SELECTION_PRIMARY, &iter);
6483 static void compose_spell_menu_changed(void *data)
6485 Compose *compose = (Compose *)data;
6487 GtkWidget *menuitem;
6488 GtkWidget *parent_item;
6489 GtkMenu *menu = GTK_MENU(gtk_menu_new());
6490 GtkItemFactory *ifactory = gtk_item_factory_from_widget(compose->menubar);
6493 if (compose->gtkaspell == NULL)
6496 parent_item = gtk_item_factory_get_item(ifactory,
6497 "/Spelling/Options");
6499 /* setting the submenu removes /Spelling/Options from the factory
6500 * so we need to save it */
6502 if (parent_item == NULL) {
6503 parent_item = compose->aspell_options_menu;
6504 gtk_menu_item_remove_submenu(GTK_MENU_ITEM(parent_item));
6506 compose->aspell_options_menu = parent_item;
6508 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
6510 spell_menu = g_slist_reverse(spell_menu);
6511 for (items = spell_menu;
6512 items; items = items->next) {
6513 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
6514 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
6515 gtk_widget_show(GTK_WIDGET(menuitem));
6517 g_slist_free(spell_menu);
6519 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
6524 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
6526 Compose *compose = (Compose *)data;
6527 GdkEventButton event;
6530 event.time = gtk_get_current_event_time();
6534 return text_clicked(compose->text, &event, compose);
6537 static gboolean compose_force_window_origin = TRUE;
6538 static Compose *compose_create(PrefsAccount *account,
6547 GtkWidget *handlebox;
6549 GtkWidget *notebook;
6551 GtkWidget *attach_hbox;
6552 GtkWidget *attach_lab1;
6553 GtkWidget *attach_lab2;
6558 GtkWidget *subject_hbox;
6559 GtkWidget *subject_frame;
6560 GtkWidget *subject_entry;
6564 GtkWidget *edit_vbox;
6565 GtkWidget *ruler_hbox;
6567 GtkWidget *scrolledwin;
6569 GtkTextBuffer *buffer;
6570 GtkClipboard *clipboard;
6572 UndoMain *undostruct;
6574 gchar *titles[N_ATTACH_COLS];
6575 guint n_menu_entries;
6576 GtkWidget *popupmenu;
6577 GtkItemFactory *popupfactory;
6578 GtkItemFactory *ifactory;
6579 GtkWidget *tmpl_menu;
6581 GtkWidget *menuitem;
6584 GtkAspell * gtkaspell = NULL;
6587 static GdkGeometry geometry;
6589 g_return_val_if_fail(account != NULL, NULL);
6591 debug_print("Creating compose window...\n");
6592 compose = g_new0(Compose, 1);
6594 titles[COL_MIMETYPE] = _("MIME type");
6595 titles[COL_SIZE] = _("Size");
6596 titles[COL_NAME] = _("Name");
6598 compose->batch = batch;
6599 compose->account = account;
6600 compose->folder = folder;
6602 compose->mutex = g_mutex_new();
6603 compose->set_cursor_pos = -1;
6605 compose->tooltips = gtk_tooltips_new();
6607 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
6609 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
6610 gtk_widget_set_size_request(window, -1, prefs_common.compose_height);
6612 if (!geometry.max_width) {
6613 geometry.max_width = gdk_screen_width();
6614 geometry.max_height = gdk_screen_height();
6617 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
6618 &geometry, GDK_HINT_MAX_SIZE);
6619 if (!geometry.min_width) {
6620 geometry.min_width = 600;
6621 geometry.min_height = 480;
6623 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
6624 &geometry, GDK_HINT_MIN_SIZE);
6627 if (compose_force_window_origin)
6628 gtk_widget_set_uposition(window, prefs_common.compose_x,
6629 prefs_common.compose_y);
6631 g_signal_connect(G_OBJECT(window), "delete_event",
6632 G_CALLBACK(compose_delete_cb), compose);
6633 MANAGE_WINDOW_SIGNALS_CONNECT(window);
6634 gtk_widget_realize(window);
6636 gtkut_widget_set_composer_icon(window);
6638 vbox = gtk_vbox_new(FALSE, 0);
6639 gtk_container_add(GTK_CONTAINER(window), vbox);
6641 n_menu_entries = sizeof(compose_entries) / sizeof(compose_entries[0]);
6642 menubar = menubar_create(window, compose_entries,
6643 n_menu_entries, "<Compose>", compose);
6644 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
6646 if (prefs_common.toolbar_detachable) {
6647 handlebox = gtk_handle_box_new();
6649 handlebox = gtk_hbox_new(FALSE, 0);
6651 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
6653 gtk_widget_realize(handlebox);
6655 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, window,
6658 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
6662 vbox2 = gtk_vbox_new(FALSE, 2);
6663 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
6664 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
6667 notebook = gtk_notebook_new();
6668 gtk_widget_set_size_request(notebook, -1, 130);
6669 gtk_widget_show(notebook);
6671 /* header labels and entries */
6672 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6673 compose_create_header(compose),
6674 gtk_label_new_with_mnemonic(_("Hea_der")));
6675 /* attachment list */
6676 attach_hbox = gtk_hbox_new(FALSE, 0);
6677 gtk_widget_show(attach_hbox);
6679 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
6680 gtk_widget_show(attach_lab1);
6681 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
6683 attach_lab2 = gtk_label_new("");
6684 gtk_widget_show(attach_lab2);
6685 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
6687 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6688 compose_create_attach(compose),
6691 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6692 compose_create_others(compose),
6693 gtk_label_new_with_mnemonic(_("Othe_rs")));
6696 subject_hbox = gtk_hbox_new(FALSE, 0);
6697 gtk_widget_show(subject_hbox);
6699 subject_frame = gtk_frame_new(NULL);
6700 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
6701 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
6702 gtk_widget_show(subject_frame);
6704 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
6705 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
6706 gtk_widget_show(subject);
6708 label = gtk_label_new(_("Subject:"));
6709 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
6710 gtk_widget_show(label);
6712 subject_entry = gtk_entry_new();
6713 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
6714 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
6715 G_CALLBACK(compose_grab_focus_cb), compose);
6716 gtk_widget_show(subject_entry);
6717 compose->subject_entry = subject_entry;
6718 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
6720 edit_vbox = gtk_vbox_new(FALSE, 0);
6722 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
6725 ruler_hbox = gtk_hbox_new(FALSE, 0);
6726 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
6728 ruler = gtk_shruler_new();
6729 gtk_ruler_set_range(GTK_RULER(ruler), 0.0, 100.0, 1.0, 100.0);
6730 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
6734 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
6735 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
6736 GTK_POLICY_AUTOMATIC,
6737 GTK_POLICY_AUTOMATIC);
6738 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
6740 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
6741 gtk_widget_set_size_request(scrolledwin, prefs_common.compose_width, -1);
6743 text = gtk_text_view_new();
6744 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
6745 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
6746 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
6747 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
6748 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
6750 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
6752 g_signal_connect_after(G_OBJECT(text), "size_allocate",
6753 G_CALLBACK(compose_edit_size_alloc),
6755 g_signal_connect(G_OBJECT(buffer), "changed",
6756 G_CALLBACK(compose_changed_cb), compose);
6757 g_signal_connect(G_OBJECT(text), "grab_focus",
6758 G_CALLBACK(compose_grab_focus_cb), compose);
6759 g_signal_connect(G_OBJECT(buffer), "insert_text",
6760 G_CALLBACK(text_inserted), compose);
6761 g_signal_connect(G_OBJECT(text), "button_press_event",
6762 G_CALLBACK(text_clicked), compose);
6764 g_signal_connect(G_OBJECT(text), "popup-menu",
6765 G_CALLBACK(compose_popup_menu), compose);
6767 gtk_widget_tap_and_hold_setup(GTK_WIDGET(text), NULL, NULL,
6768 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
6769 g_signal_connect(G_OBJECT(text), "tap-and-hold",
6770 G_CALLBACK(compose_popup_menu), compose);
6772 g_signal_connect(G_OBJECT(subject_entry), "changed",
6773 G_CALLBACK(compose_changed_cb), compose);
6776 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6777 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6778 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6779 g_signal_connect(G_OBJECT(text), "drag_data_received",
6780 G_CALLBACK(compose_insert_drag_received_cb),
6782 g_signal_connect(G_OBJECT(text), "drag-drop",
6783 G_CALLBACK(compose_drag_drop),
6785 gtk_widget_show_all(vbox);
6787 /* pane between attach clist and text */
6788 paned = gtk_vpaned_new();
6789 gtk_paned_set_gutter_size(GTK_PANED(paned), 12);
6790 gtk_container_add(GTK_CONTAINER(vbox2), paned);
6792 if( maemo_mainwindow_is_fullscreen(mainwindow_get_mainwindow()->window) )
6793 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 300 : 280);
6795 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 250 : 230);
6797 gtk_paned_add1(GTK_PANED(paned), notebook);
6798 gtk_paned_add2(GTK_PANED(paned), edit_vbox);
6799 gtk_widget_show_all(paned);
6802 if (prefs_common.textfont) {
6803 PangoFontDescription *font_desc;
6805 font_desc = pango_font_description_from_string
6806 (prefs_common.textfont);
6808 gtk_widget_modify_font(text, font_desc);
6809 pango_font_description_free(font_desc);
6813 n_entries = sizeof(compose_popup_entries) /
6814 sizeof(compose_popup_entries[0]);
6815 popupmenu = menu_create_items(compose_popup_entries, n_entries,
6816 "<Compose>", &popupfactory,
6819 ifactory = gtk_item_factory_from_widget(menubar);
6820 menu_set_sensitive(ifactory, "/Edit/Undo", FALSE);
6821 menu_set_sensitive(ifactory, "/Edit/Redo", FALSE);
6822 menu_set_sensitive(ifactory, "/Options/Remove references", FALSE);
6824 tmpl_menu = gtk_item_factory_get_item(ifactory, "/Tools/Template");
6826 undostruct = undo_init(text);
6827 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
6830 address_completion_start(window);
6832 compose->window = window;
6833 compose->vbox = vbox;
6834 compose->menubar = menubar;
6835 compose->handlebox = handlebox;
6837 compose->vbox2 = vbox2;
6839 compose->paned = paned;
6841 compose->attach_label = attach_lab2;
6843 compose->notebook = notebook;
6844 compose->edit_vbox = edit_vbox;
6845 compose->ruler_hbox = ruler_hbox;
6846 compose->ruler = ruler;
6847 compose->scrolledwin = scrolledwin;
6848 compose->text = text;
6850 compose->focused_editable = NULL;
6852 compose->popupmenu = popupmenu;
6853 compose->popupfactory = popupfactory;
6855 compose->tmpl_menu = tmpl_menu;
6857 compose->mode = mode;
6858 compose->rmode = mode;
6860 compose->targetinfo = NULL;
6861 compose->replyinfo = NULL;
6862 compose->fwdinfo = NULL;
6864 compose->replyto = NULL;
6866 compose->bcc = NULL;
6867 compose->followup_to = NULL;
6869 compose->ml_post = NULL;
6871 compose->inreplyto = NULL;
6872 compose->references = NULL;
6873 compose->msgid = NULL;
6874 compose->boundary = NULL;
6876 compose->autowrap = prefs_common.autowrap;
6878 compose->use_signing = FALSE;
6879 compose->use_encryption = FALSE;
6880 compose->privacy_system = NULL;
6882 compose->modified = FALSE;
6884 compose->return_receipt = FALSE;
6886 compose->to_list = NULL;
6887 compose->newsgroup_list = NULL;
6889 compose->undostruct = undostruct;
6891 compose->sig_str = NULL;
6893 compose->exteditor_file = NULL;
6894 compose->exteditor_pid = -1;
6895 compose->exteditor_tag = -1;
6896 compose->draft_timeout_tag = -2; /* inhibit auto-drafting while loading */
6899 menu_set_sensitive(ifactory, "/Spelling", FALSE);
6900 if (mode != COMPOSE_REDIRECT) {
6901 if (prefs_common.enable_aspell && prefs_common.dictionary &&
6902 strcmp(prefs_common.dictionary, "")) {
6903 gtkaspell = gtkaspell_new(prefs_common.aspell_path,
6904 prefs_common.dictionary,
6905 prefs_common.alt_dictionary,
6906 conv_get_locale_charset_str(),
6907 prefs_common.misspelled_col,
6908 prefs_common.check_while_typing,
6909 prefs_common.recheck_when_changing_dict,
6910 prefs_common.use_alternate,
6911 prefs_common.use_both_dicts,
6912 GTK_TEXT_VIEW(text),
6913 GTK_WINDOW(compose->window),
6914 compose_spell_menu_changed,
6917 alertpanel_error(_("Spell checker could not "
6919 gtkaspell_checkers_strerror());
6920 gtkaspell_checkers_reset_error();
6922 if (!gtkaspell_set_sug_mode(gtkaspell,
6923 prefs_common.aspell_sugmode)) {
6924 debug_print("Aspell: could not set "
6925 "suggestion mode %s\n",
6926 gtkaspell_checkers_strerror());
6927 gtkaspell_checkers_reset_error();
6930 menu_set_sensitive(ifactory, "/Spelling", TRUE);
6934 compose->gtkaspell = gtkaspell;
6935 compose_spell_menu_changed(compose);
6938 compose_select_account(compose, account, TRUE);
6940 menu_set_active(ifactory, "/Edit/Auto wrapping", prefs_common.autowrap);
6941 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
6942 compose_entry_append(compose, account->auto_cc, COMPOSE_CC);
6944 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
6945 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC);
6947 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
6948 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO);
6950 menu_set_sensitive(ifactory, "/Options/Reply mode", compose->mode == COMPOSE_REPLY);
6952 if (account->protocol != A_NNTP)
6953 gtk_entry_set_text(GTK_ENTRY(GTK_BIN(compose->header_last->combo)->child),
6954 prefs_common_translated_header_name("To:"));
6956 gtk_entry_set_text(GTK_ENTRY(GTK_BIN(compose->header_last->combo)->child),
6957 prefs_common_translated_header_name("Newsgroups:"));
6959 addressbook_set_target_compose(compose);
6961 if (mode != COMPOSE_REDIRECT)
6962 compose_set_template_menu(compose);
6964 menuitem = gtk_item_factory_get_item(ifactory, "/Tools/Template");
6965 menu_set_sensitive(ifactory, "/Tools/Template", FALSE);
6968 compose_list = g_list_append(compose_list, compose);
6970 if (!prefs_common.show_ruler)
6971 gtk_widget_hide(ruler_hbox);
6973 menuitem = gtk_item_factory_get_item(ifactory, "/Tools/Show ruler");
6974 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
6975 prefs_common.show_ruler);
6978 compose->priority = PRIORITY_NORMAL;
6979 compose_update_priority_menu_item(compose);
6981 compose_set_out_encoding(compose);
6984 compose_update_actions_menu(compose);
6986 /* Privacy Systems menu */
6987 compose_update_privacy_systems_menu(compose);
6989 activate_privacy_system(compose, account, TRUE);
6990 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
6992 gtk_widget_realize(window);
6994 gtk_widget_show(window);
6996 maemo_window_full_screen_if_needed(GTK_WINDOW(window));
6997 maemo_connect_key_press_to_mainwindow(GTK_WINDOW(window));
7004 static GtkWidget *compose_account_option_menu_create(Compose *compose)
7009 GtkWidget *optmenubox;
7012 GtkWidget *from_name = NULL;
7014 gint num = 0, def_menu = 0;
7016 accounts = account_get_list();
7017 g_return_val_if_fail(accounts != NULL, NULL);
7019 optmenubox = gtk_event_box_new();
7020 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
7021 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
7023 hbox = gtk_hbox_new(FALSE, 6);
7024 from_name = gtk_entry_new();
7026 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
7027 G_CALLBACK(compose_grab_focus_cb), compose);
7029 for (; accounts != NULL; accounts = accounts->next, num++) {
7030 PrefsAccount *ac = (PrefsAccount *)accounts->data;
7031 gchar *name, *from = NULL;
7033 if (ac == compose->account) def_menu = num;
7035 name = g_markup_printf_escaped(_("From: <i>%s</i>"),
7038 if (ac == compose->account) {
7039 if (ac->name && *ac->name) {
7041 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
7042 from = g_strdup_printf("%s <%s>",
7044 gtk_entry_set_text(GTK_ENTRY(from_name), from);
7046 from = g_strdup_printf("%s",
7048 gtk_entry_set_text(GTK_ENTRY(from_name), from);
7051 COMBOBOX_ADD(menu, name, ac->account_id);
7056 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
7058 g_signal_connect(G_OBJECT(optmenu), "changed",
7059 G_CALLBACK(account_activated),
7061 g_signal_connect(G_OBJECT(from_name), "populate-popup",
7062 G_CALLBACK(compose_entry_popup_extend),
7065 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
7066 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
7068 gtk_tooltips_set_tip(compose->tooltips, optmenubox,
7069 _("Account to use for this email"), NULL);
7070 gtk_tooltips_set_tip(compose->tooltips, from_name,
7071 _("Sender address to be used"), NULL);
7073 compose->from_name = from_name;
7078 static void compose_set_priority_cb(gpointer data,
7082 Compose *compose = (Compose *) data;
7083 compose->priority = action;
7086 static void compose_reply_change_mode(gpointer data,
7090 Compose *compose = (Compose *) data;
7091 gboolean was_modified = compose->modified;
7093 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
7095 g_return_if_fail(compose->replyinfo != NULL);
7097 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
7099 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
7101 if (action == COMPOSE_REPLY_TO_ALL)
7103 if (action == COMPOSE_REPLY_TO_SENDER)
7105 if (action == COMPOSE_REPLY_TO_LIST)
7108 compose_remove_header_entries(compose);
7109 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
7110 if (compose->account->set_autocc && compose->account->auto_cc)
7111 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC);
7113 if (compose->account->set_autobcc && compose->account->auto_bcc)
7114 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC);
7116 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
7117 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO);
7118 compose_show_first_last_header(compose, TRUE);
7119 compose->modified = was_modified;
7120 compose_set_title(compose);
7123 static void compose_update_priority_menu_item(Compose * compose)
7125 GtkItemFactory *ifactory;
7126 GtkWidget *menuitem = NULL;
7128 ifactory = gtk_item_factory_from_widget(compose->menubar);
7130 switch (compose->priority) {
7131 case PRIORITY_HIGHEST:
7132 menuitem = gtk_item_factory_get_item
7133 (ifactory, "/Options/Priority/Highest");
7136 menuitem = gtk_item_factory_get_item
7137 (ifactory, "/Options/Priority/High");
7139 case PRIORITY_NORMAL:
7140 menuitem = gtk_item_factory_get_item
7141 (ifactory, "/Options/Priority/Normal");
7144 menuitem = gtk_item_factory_get_item
7145 (ifactory, "/Options/Priority/Low");
7147 case PRIORITY_LOWEST:
7148 menuitem = gtk_item_factory_get_item
7149 (ifactory, "/Options/Priority/Lowest");
7152 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
7155 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
7157 Compose *compose = (Compose *) data;
7159 GtkItemFactory *ifactory;
7160 gboolean can_sign = FALSE, can_encrypt = FALSE;
7162 g_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
7164 if (!GTK_CHECK_MENU_ITEM(widget)->active)
7167 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
7168 g_free(compose->privacy_system);
7169 compose->privacy_system = NULL;
7170 if (systemid != NULL) {
7171 compose->privacy_system = g_strdup(systemid);
7173 can_sign = privacy_system_can_sign(systemid);
7174 can_encrypt = privacy_system_can_encrypt(systemid);
7177 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
7179 ifactory = gtk_item_factory_from_widget(compose->menubar);
7180 menu_set_sensitive(ifactory, "/Options/Sign", can_sign);
7181 menu_set_sensitive(ifactory, "/Options/Encrypt", can_encrypt);
7184 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
7186 static gchar *branch_path = "/Options/Privacy System";
7187 GtkItemFactory *ifactory;
7188 GtkWidget *menuitem = NULL;
7190 gboolean can_sign = FALSE, can_encrypt = FALSE;
7191 gboolean found = FALSE;
7193 ifactory = gtk_item_factory_from_widget(compose->menubar);
7195 if (compose->privacy_system != NULL) {
7197 menuitem = gtk_item_factory_get_widget(ifactory, branch_path);
7198 g_return_if_fail(menuitem != NULL);
7200 amenu = GTK_MENU_SHELL(menuitem)->children;
7202 while (amenu != NULL) {
7203 GList *alist = amenu->next;
7205 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
7206 if (systemid != NULL) {
7207 if (strcmp(systemid, compose->privacy_system) == 0) {
7208 menuitem = GTK_WIDGET(amenu->data);
7210 can_sign = privacy_system_can_sign(systemid);
7211 can_encrypt = privacy_system_can_encrypt(systemid);
7215 } else if (strlen(compose->privacy_system) == 0) {
7216 menuitem = GTK_WIDGET(amenu->data);
7219 can_encrypt = FALSE;
7226 if (menuitem != NULL)
7227 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
7229 if (warn && !found && strlen(compose->privacy_system)) {
7230 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
7231 "will not be able to sign or encrypt this message."),
7232 compose->privacy_system);
7236 menu_set_sensitive(ifactory, "/Options/Sign", can_sign);
7237 menu_set_sensitive(ifactory, "/Options/Encrypt", can_encrypt);
7240 static void compose_set_out_encoding(Compose *compose)
7242 GtkItemFactoryEntry *entry;
7243 GtkItemFactory *ifactory;
7244 CharSet out_encoding;
7245 gchar *path, *p, *q;
7248 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
7249 ifactory = gtk_item_factory_from_widget(compose->menubar);
7251 for (entry = compose_entries; entry->callback != compose_address_cb;
7253 if (entry->callback == compose_set_encoding_cb &&
7254 (CharSet)entry->callback_action == out_encoding) {
7255 p = q = path = g_strdup(entry->path);
7267 item = gtk_item_factory_get_item(ifactory, path);
7268 gtk_widget_activate(item);
7275 static void compose_set_template_menu(Compose *compose)
7277 GSList *tmpl_list, *cur;
7281 tmpl_list = template_get_config();
7283 menu = gtk_menu_new();
7285 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
7286 Template *tmpl = (Template *)cur->data;
7288 item = gtk_menu_item_new_with_label(tmpl->name);
7289 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
7290 g_signal_connect(G_OBJECT(item), "activate",
7291 G_CALLBACK(compose_template_activate_cb),
7293 g_object_set_data(G_OBJECT(item), "template", tmpl);
7294 gtk_widget_show(item);
7297 gtk_widget_show(menu);
7298 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
7301 void compose_update_actions_menu(Compose *compose)
7303 GtkItemFactory *ifactory;
7305 ifactory = gtk_item_factory_from_widget(compose->menubar);
7306 action_update_compose_menu(ifactory, "/Tools/Actions", compose);
7309 static void compose_update_privacy_systems_menu(Compose *compose)
7311 static gchar *branch_path = "/Options/Privacy System";
7312 GtkItemFactory *ifactory;
7313 GtkWidget *menuitem;
7314 GSList *systems, *cur;
7317 GtkWidget *system_none;
7320 ifactory = gtk_item_factory_from_widget(compose->menubar);
7322 /* remove old entries */
7323 menuitem = gtk_item_factory_get_widget(ifactory, branch_path);
7324 g_return_if_fail(menuitem != NULL);
7326 amenu = GTK_MENU_SHELL(menuitem)->children->next;
7327 while (amenu != NULL) {
7328 GList *alist = amenu->next;
7329 gtk_widget_destroy(GTK_WIDGET(amenu->data));
7333 system_none = gtk_item_factory_get_widget(ifactory,
7334 "/Options/Privacy System/None");
7336 g_signal_connect(G_OBJECT(system_none), "activate",
7337 G_CALLBACK(compose_set_privacy_system_cb), compose);
7339 systems = privacy_get_system_ids();
7340 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
7341 gchar *systemid = cur->data;
7343 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
7344 widget = gtk_radio_menu_item_new_with_label(group,
7345 privacy_system_get_name(systemid));
7346 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
7347 g_strdup(systemid), g_free);
7348 g_signal_connect(G_OBJECT(widget), "activate",
7349 G_CALLBACK(compose_set_privacy_system_cb), compose);
7351 gtk_menu_append(GTK_MENU(system_none->parent), widget);
7352 gtk_widget_show(widget);
7355 g_slist_free(systems);
7358 void compose_reflect_prefs_all(void)
7363 for (cur = compose_list; cur != NULL; cur = cur->next) {
7364 compose = (Compose *)cur->data;
7365 compose_set_template_menu(compose);
7369 void compose_reflect_prefs_pixmap_theme(void)
7374 for (cur = compose_list; cur != NULL; cur = cur->next) {
7375 compose = (Compose *)cur->data;
7376 toolbar_update(TOOLBAR_COMPOSE, compose);
7380 static const gchar *compose_quote_char_from_context(Compose *compose)
7382 const gchar *qmark = NULL;
7384 g_return_val_if_fail(compose != NULL, NULL);
7386 switch (compose->mode) {
7387 /* use forward-specific quote char */
7388 case COMPOSE_FORWARD:
7389 case COMPOSE_FORWARD_AS_ATTACH:
7390 case COMPOSE_FORWARD_INLINE:
7391 if (compose->folder && compose->folder->prefs &&
7392 compose->folder->prefs->forward_with_format)
7393 qmark = compose->folder->prefs->forward_quotemark;
7394 else if (compose->account->forward_with_format)
7395 qmark = compose->account->forward_quotemark;
7397 qmark = prefs_common.fw_quotemark;
7400 /* use reply-specific quote char in all other modes */
7402 if (compose->folder && compose->folder->prefs &&
7403 compose->folder->prefs->reply_with_format)
7404 qmark = compose->folder->prefs->reply_quotemark;
7405 else if (compose->account->reply_with_format)
7406 qmark = compose->account->reply_quotemark;
7408 qmark = prefs_common.quotemark;
7412 if (qmark == NULL || *qmark == '\0')
7418 static void compose_template_apply(Compose *compose, Template *tmpl,
7422 GtkTextBuffer *buffer;
7426 gchar *parsed_str = NULL;
7427 gint cursor_pos = 0;
7428 const gchar *err_msg = _("Template body format error at line %d.");
7431 /* process the body */
7433 text = GTK_TEXT_VIEW(compose->text);
7434 buffer = gtk_text_view_get_buffer(text);
7437 qmark = compose_quote_char_from_context(compose);
7439 if (compose->replyinfo != NULL) {
7442 gtk_text_buffer_set_text(buffer, "", -1);
7443 mark = gtk_text_buffer_get_insert(buffer);
7444 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7446 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
7447 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
7449 } else if (compose->fwdinfo != NULL) {
7452 gtk_text_buffer_set_text(buffer, "", -1);
7453 mark = gtk_text_buffer_get_insert(buffer);
7454 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7456 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
7457 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
7460 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
7462 GtkTextIter start, end;
7465 gtk_text_buffer_get_start_iter(buffer, &start);
7466 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
7467 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
7469 /* clear the buffer now */
7471 gtk_text_buffer_set_text(buffer, "", -1);
7473 parsed_str = compose_quote_fmt(compose, dummyinfo,
7474 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
7475 procmsg_msginfo_free( dummyinfo );
7481 gtk_text_buffer_set_text(buffer, "", -1);
7482 mark = gtk_text_buffer_get_insert(buffer);
7483 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7486 if (replace && parsed_str && compose->account->auto_sig)
7487 compose_insert_sig(compose, FALSE);
7489 if (replace && parsed_str) {
7490 gtk_text_buffer_get_start_iter(buffer, &iter);
7491 gtk_text_buffer_place_cursor(buffer, &iter);
7495 cursor_pos = quote_fmt_get_cursor_pos();
7496 compose->set_cursor_pos = cursor_pos;
7497 if (cursor_pos == -1)
7499 gtk_text_buffer_get_start_iter(buffer, &iter);
7500 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
7501 gtk_text_buffer_place_cursor(buffer, &iter);
7504 /* process the other fields */
7506 compose_template_apply_fields(compose, tmpl);
7507 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
7508 quote_fmt_reset_vartable();
7509 compose_changed_cb(NULL, compose);
7512 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
7514 MsgInfo* dummyinfo = NULL;
7515 MsgInfo *msginfo = NULL;
7518 if (compose->replyinfo != NULL)
7519 msginfo = compose->replyinfo;
7520 else if (compose->fwdinfo != NULL)
7521 msginfo = compose->fwdinfo;
7523 dummyinfo = compose_msginfo_new_from_compose(compose);
7524 msginfo = dummyinfo;
7527 if (tmpl->to && *tmpl->to != '\0') {
7529 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
7530 compose->gtkaspell);
7532 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
7534 quote_fmt_scan_string(tmpl->to);
7537 buf = quote_fmt_get_buffer();
7539 alertpanel_error(_("Template To format error."));
7541 compose_entry_append(compose, buf, COMPOSE_TO);
7545 if (tmpl->cc && *tmpl->cc != '\0') {
7547 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
7548 compose->gtkaspell);
7550 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
7552 quote_fmt_scan_string(tmpl->cc);
7555 buf = quote_fmt_get_buffer();
7557 alertpanel_error(_("Template Cc format error."));
7559 compose_entry_append(compose, buf, COMPOSE_CC);
7563 if (tmpl->bcc && *tmpl->bcc != '\0') {
7565 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
7566 compose->gtkaspell);
7568 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
7570 quote_fmt_scan_string(tmpl->bcc);
7573 buf = quote_fmt_get_buffer();
7575 alertpanel_error(_("Template Bcc format error."));
7577 compose_entry_append(compose, buf, COMPOSE_BCC);
7581 /* process the subject */
7582 if (tmpl->subject && *tmpl->subject != '\0') {
7584 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
7585 compose->gtkaspell);
7587 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
7589 quote_fmt_scan_string(tmpl->subject);
7592 buf = quote_fmt_get_buffer();
7594 alertpanel_error(_("Template subject format error."));
7596 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
7600 procmsg_msginfo_free( dummyinfo );
7603 static void compose_destroy(Compose *compose)
7605 GtkTextBuffer *buffer;
7606 GtkClipboard *clipboard;
7608 compose_list = g_list_remove(compose_list, compose);
7610 if (compose->updating) {
7611 debug_print("danger, not destroying anything now\n");
7612 compose->deferred_destroy = TRUE;
7615 /* NOTE: address_completion_end() does nothing with the window
7616 * however this may change. */
7617 address_completion_end(compose->window);
7619 slist_free_strings(compose->to_list);
7620 g_slist_free(compose->to_list);
7621 slist_free_strings(compose->newsgroup_list);
7622 g_slist_free(compose->newsgroup_list);
7623 slist_free_strings(compose->header_list);
7624 g_slist_free(compose->header_list);
7626 procmsg_msginfo_free(compose->targetinfo);
7627 procmsg_msginfo_free(compose->replyinfo);
7628 procmsg_msginfo_free(compose->fwdinfo);
7630 g_free(compose->replyto);
7631 g_free(compose->cc);
7632 g_free(compose->bcc);
7633 g_free(compose->newsgroups);
7634 g_free(compose->followup_to);
7636 g_free(compose->ml_post);
7638 g_free(compose->inreplyto);
7639 g_free(compose->references);
7640 g_free(compose->msgid);
7641 g_free(compose->boundary);
7643 g_free(compose->redirect_filename);
7644 if (compose->undostruct)
7645 undo_destroy(compose->undostruct);
7647 g_free(compose->sig_str);
7649 g_free(compose->exteditor_file);
7651 g_free(compose->orig_charset);
7653 g_free(compose->privacy_system);
7655 if (addressbook_get_target_compose() == compose)
7656 addressbook_set_target_compose(NULL);
7659 if (compose->gtkaspell) {
7660 gtkaspell_delete(compose->gtkaspell);
7661 compose->gtkaspell = NULL;
7665 prefs_common.compose_width = compose->scrolledwin->allocation.width;
7666 prefs_common.compose_height = compose->window->allocation.height;
7668 if (!gtk_widget_get_parent(compose->paned))
7669 gtk_widget_destroy(compose->paned);
7670 gtk_widget_destroy(compose->popupmenu);
7672 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
7673 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7674 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
7676 gtk_widget_destroy(compose->window);
7677 toolbar_destroy(compose->toolbar);
7678 g_free(compose->toolbar);
7679 g_mutex_free(compose->mutex);
7683 static void compose_attach_info_free(AttachInfo *ainfo)
7685 g_free(ainfo->file);
7686 g_free(ainfo->content_type);
7687 g_free(ainfo->name);
7691 static void compose_attach_update_label(Compose *compose)
7696 GtkTreeModel *model;
7701 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
7702 if(!gtk_tree_model_get_iter_first(model, &iter)) {
7703 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
7707 while(gtk_tree_model_iter_next(model, &iter))
7710 text = g_strdup_printf("(%d)", i);
7711 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
7715 static void compose_attach_remove_selected(Compose *compose)
7717 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
7718 GtkTreeSelection *selection;
7720 GtkTreeModel *model;
7722 selection = gtk_tree_view_get_selection(tree_view);
7723 sel = gtk_tree_selection_get_selected_rows(selection, &model);
7728 for (cur = sel; cur != NULL; cur = cur->next) {
7729 GtkTreePath *path = cur->data;
7730 GtkTreeRowReference *ref = gtk_tree_row_reference_new
7733 gtk_tree_path_free(path);
7736 for (cur = sel; cur != NULL; cur = cur->next) {
7737 GtkTreeRowReference *ref = cur->data;
7738 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
7741 if (gtk_tree_model_get_iter(model, &iter, path))
7742 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
7744 gtk_tree_path_free(path);
7745 gtk_tree_row_reference_free(ref);
7749 compose_attach_update_label(compose);
7752 static struct _AttachProperty
7755 GtkWidget *mimetype_entry;
7756 GtkWidget *encoding_optmenu;
7757 GtkWidget *path_entry;
7758 GtkWidget *filename_entry;
7760 GtkWidget *cancel_btn;
7763 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
7765 gtk_tree_path_free((GtkTreePath *)ptr);
7768 static void compose_attach_property(Compose *compose)
7770 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
7772 GtkComboBox *optmenu;
7773 GtkTreeSelection *selection;
7775 GtkTreeModel *model;
7778 static gboolean cancelled;
7780 /* only if one selected */
7781 selection = gtk_tree_view_get_selection(tree_view);
7782 if (gtk_tree_selection_count_selected_rows(selection) != 1)
7785 sel = gtk_tree_selection_get_selected_rows(selection, &model);
7789 path = (GtkTreePath *) sel->data;
7790 gtk_tree_model_get_iter(model, &iter, path);
7791 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
7794 g_list_foreach(sel, gtk_tree_path_free_, NULL);
7800 if (!attach_prop.window)
7801 compose_attach_property_create(&cancelled);
7802 gtk_widget_grab_focus(attach_prop.ok_btn);
7803 gtk_widget_show(attach_prop.window);
7804 manage_window_set_transient(GTK_WINDOW(attach_prop.window));
7806 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
7807 if (ainfo->encoding == ENC_UNKNOWN)
7808 combobox_select_by_data(optmenu, ENC_BASE64);
7810 combobox_select_by_data(optmenu, ainfo->encoding);
7812 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
7813 ainfo->content_type ? ainfo->content_type : "");
7814 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
7815 ainfo->file ? ainfo->file : "");
7816 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
7817 ainfo->name ? ainfo->name : "");
7820 const gchar *entry_text;
7822 gchar *cnttype = NULL;
7829 gtk_widget_hide(attach_prop.window);
7834 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
7835 if (*entry_text != '\0') {
7838 text = g_strstrip(g_strdup(entry_text));
7839 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
7840 cnttype = g_strdup(text);
7843 alertpanel_error(_("Invalid MIME type."));
7849 ainfo->encoding = combobox_get_active_data(optmenu);
7851 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
7852 if (*entry_text != '\0') {
7853 if (is_file_exist(entry_text) &&
7854 (size = get_file_size(entry_text)) > 0)
7855 file = g_strdup(entry_text);
7858 (_("File doesn't exist or is empty."));
7864 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
7865 if (*entry_text != '\0') {
7866 g_free(ainfo->name);
7867 ainfo->name = g_strdup(entry_text);
7871 g_free(ainfo->content_type);
7872 ainfo->content_type = cnttype;
7875 g_free(ainfo->file);
7881 /* update tree store */
7882 text = to_human_readable(ainfo->size);
7883 gtk_tree_model_get_iter(model, &iter, path);
7884 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
7885 COL_MIMETYPE, ainfo->content_type,
7887 COL_NAME, ainfo->name,
7893 gtk_tree_path_free(path);
7896 #define SET_LABEL_AND_ENTRY(str, entry, top) \
7898 label = gtk_label_new(str); \
7899 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
7900 GTK_FILL, 0, 0, 0); \
7901 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
7903 entry = gtk_entry_new(); \
7904 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
7905 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
7908 static void compose_attach_property_create(gboolean *cancelled)
7914 GtkWidget *mimetype_entry;
7917 GtkListStore *optmenu_menu;
7918 GtkWidget *path_entry;
7919 GtkWidget *filename_entry;
7922 GtkWidget *cancel_btn;
7923 GList *mime_type_list, *strlist;
7926 debug_print("Creating attach_property window...\n");
7928 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
7929 gtk_widget_set_size_request(window, 480, -1);
7930 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
7931 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
7932 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
7933 gtk_window_set_modal(GTK_WINDOW(window), TRUE);
7934 g_signal_connect(G_OBJECT(window), "delete_event",
7935 G_CALLBACK(attach_property_delete_event),
7937 g_signal_connect(G_OBJECT(window), "key_press_event",
7938 G_CALLBACK(attach_property_key_pressed),
7941 vbox = gtk_vbox_new(FALSE, 8);
7942 gtk_container_add(GTK_CONTAINER(window), vbox);
7944 table = gtk_table_new(4, 2, FALSE);
7945 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
7946 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
7947 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
7949 label = gtk_label_new(_("MIME type"));
7950 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
7952 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
7953 mimetype_entry = gtk_combo_box_entry_new_text();
7954 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
7955 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
7957 /* stuff with list */
7958 mime_type_list = procmime_get_mime_type_list();
7960 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
7961 MimeType *type = (MimeType *) mime_type_list->data;
7964 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
7966 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
7969 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
7970 (GCompareFunc)strcmp2);
7973 for (mime_type_list = strlist; mime_type_list != NULL;
7974 mime_type_list = mime_type_list->next) {
7975 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
7976 g_free(mime_type_list->data);
7978 g_list_free(strlist);
7979 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
7980 mimetype_entry = GTK_BIN(mimetype_entry)->child;
7982 label = gtk_label_new(_("Encoding"));
7983 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
7985 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
7987 hbox = gtk_hbox_new(FALSE, 0);
7988 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
7989 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
7991 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
7992 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
7994 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
7995 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
7996 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
7997 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
7998 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
8000 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
8002 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
8003 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
8005 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
8006 &ok_btn, GTK_STOCK_OK,
8008 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
8009 gtk_widget_grab_default(ok_btn);
8011 g_signal_connect(G_OBJECT(ok_btn), "clicked",
8012 G_CALLBACK(attach_property_ok),
8014 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
8015 G_CALLBACK(attach_property_cancel),
8018 gtk_widget_show_all(vbox);
8020 attach_prop.window = window;
8021 attach_prop.mimetype_entry = mimetype_entry;
8022 attach_prop.encoding_optmenu = optmenu;
8023 attach_prop.path_entry = path_entry;
8024 attach_prop.filename_entry = filename_entry;
8025 attach_prop.ok_btn = ok_btn;
8026 attach_prop.cancel_btn = cancel_btn;
8029 #undef SET_LABEL_AND_ENTRY
8031 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
8037 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
8043 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
8044 gboolean *cancelled)
8052 static gboolean attach_property_key_pressed(GtkWidget *widget,
8054 gboolean *cancelled)
8056 if (event && event->keyval == GDK_Escape) {
8063 static void compose_exec_ext_editor(Compose *compose)
8070 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
8071 G_DIR_SEPARATOR, compose);
8073 if (pipe(pipe_fds) < 0) {
8079 if ((pid = fork()) < 0) {
8086 /* close the write side of the pipe */
8089 compose->exteditor_file = g_strdup(tmp);
8090 compose->exteditor_pid = pid;
8092 compose_set_ext_editor_sensitive(compose, FALSE);
8094 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
8095 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
8099 } else { /* process-monitoring process */
8105 /* close the read side of the pipe */
8108 if (compose_write_body_to_file(compose, tmp) < 0) {
8109 fd_write_all(pipe_fds[1], "2\n", 2);
8113 pid_ed = compose_exec_ext_editor_real(tmp);
8115 fd_write_all(pipe_fds[1], "1\n", 2);
8119 /* wait until editor is terminated */
8120 waitpid(pid_ed, NULL, 0);
8122 fd_write_all(pipe_fds[1], "0\n", 2);
8129 #endif /* G_OS_UNIX */
8133 static gint compose_exec_ext_editor_real(const gchar *file)
8140 g_return_val_if_fail(file != NULL, -1);
8142 if ((pid = fork()) < 0) {
8147 if (pid != 0) return pid;
8149 /* grandchild process */
8151 if (setpgid(0, getppid()))
8154 if (prefs_common_get_ext_editor_cmd() &&
8155 (p = strchr(prefs_common_get_ext_editor_cmd(), '%')) &&
8156 *(p + 1) == 's' && !strchr(p + 2, '%')) {
8157 g_snprintf(buf, sizeof(buf), prefs_common_get_ext_editor_cmd(), file);
8159 if (prefs_common_get_ext_editor_cmd())
8160 g_warning("External editor command line is invalid: '%s'\n",
8161 prefs_common_get_ext_editor_cmd());
8162 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
8165 cmdline = strsplit_with_quote(buf, " ", 1024);
8166 execvp(cmdline[0], cmdline);
8169 g_strfreev(cmdline);
8174 static gboolean compose_ext_editor_kill(Compose *compose)
8176 pid_t pgid = compose->exteditor_pid * -1;
8179 ret = kill(pgid, 0);
8181 if (ret == 0 || (ret == -1 && EPERM == errno)) {
8185 msg = g_strdup_printf
8186 (_("The external editor is still working.\n"
8187 "Force terminating the process?\n"
8188 "process group id: %d"), -pgid);
8189 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
8190 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
8194 if (val == G_ALERTALTERNATE) {
8195 g_source_remove(compose->exteditor_tag);
8196 g_io_channel_shutdown(compose->exteditor_ch,
8198 g_io_channel_unref(compose->exteditor_ch);
8200 if (kill(pgid, SIGTERM) < 0) perror("kill");
8201 waitpid(compose->exteditor_pid, NULL, 0);
8203 g_warning("Terminated process group id: %d", -pgid);
8204 g_warning("Temporary file: %s",
8205 compose->exteditor_file);
8207 compose_set_ext_editor_sensitive(compose, TRUE);
8209 g_free(compose->exteditor_file);
8210 compose->exteditor_file = NULL;
8211 compose->exteditor_pid = -1;
8212 compose->exteditor_ch = NULL;
8213 compose->exteditor_tag = -1;
8221 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
8225 Compose *compose = (Compose *)data;
8228 debug_print(_("Compose: input from monitoring process\n"));
8230 g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL);
8232 g_io_channel_shutdown(source, FALSE, NULL);
8233 g_io_channel_unref(source);
8235 waitpid(compose->exteditor_pid, NULL, 0);
8237 if (buf[0] == '0') { /* success */
8238 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
8239 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
8241 gtk_text_buffer_set_text(buffer, "", -1);
8242 compose_insert_file(compose, compose->exteditor_file);
8243 compose_changed_cb(NULL, compose);
8245 if (g_unlink(compose->exteditor_file) < 0)
8246 FILE_OP_ERROR(compose->exteditor_file, "unlink");
8247 } else if (buf[0] == '1') { /* failed */
8248 g_warning("Couldn't exec external editor\n");
8249 if (g_unlink(compose->exteditor_file) < 0)
8250 FILE_OP_ERROR(compose->exteditor_file, "unlink");
8251 } else if (buf[0] == '2') {
8252 g_warning("Couldn't write to file\n");
8253 } else if (buf[0] == '3') {
8254 g_warning("Pipe read failed\n");
8257 compose_set_ext_editor_sensitive(compose, TRUE);
8259 g_free(compose->exteditor_file);
8260 compose->exteditor_file = NULL;
8261 compose->exteditor_pid = -1;
8262 compose->exteditor_ch = NULL;
8263 compose->exteditor_tag = -1;
8268 static void compose_set_ext_editor_sensitive(Compose *compose,
8271 GtkItemFactory *ifactory;
8273 ifactory = gtk_item_factory_from_widget(compose->menubar);
8275 menu_set_sensitive(ifactory, "/Message/Send", sensitive);
8276 menu_set_sensitive(ifactory, "/Message/Send later", sensitive);
8277 menu_set_sensitive(ifactory, "/Message/Insert file", sensitive);
8278 menu_set_sensitive(ifactory, "/Message/Insert signature", sensitive);
8279 menu_set_sensitive(ifactory, "/Edit/Wrap current paragraph", sensitive);
8280 menu_set_sensitive(ifactory, "/Edit/Wrap all long lines", sensitive);
8281 menu_set_sensitive(ifactory, "/Edit/Edit with external editor",
8284 gtk_widget_set_sensitive(compose->text, sensitive);
8285 if (compose->toolbar->send_btn)
8286 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
8287 if (compose->toolbar->sendl_btn)
8288 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
8289 if (compose->toolbar->draft_btn)
8290 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
8291 if (compose->toolbar->insert_btn)
8292 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
8293 if (compose->toolbar->sig_btn)
8294 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
8295 if (compose->toolbar->exteditor_btn)
8296 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
8297 if (compose->toolbar->linewrap_current_btn)
8298 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
8299 if (compose->toolbar->linewrap_all_btn)
8300 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
8302 #endif /* G_OS_UNIX */
8305 * compose_undo_state_changed:
8307 * Change the sensivity of the menuentries undo and redo
8309 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
8310 gint redo_state, gpointer data)
8312 GtkWidget *widget = GTK_WIDGET(data);
8313 GtkItemFactory *ifactory;
8315 g_return_if_fail(widget != NULL);
8317 ifactory = gtk_item_factory_from_widget(widget);
8319 switch (undo_state) {
8320 case UNDO_STATE_TRUE:
8321 if (!undostruct->undo_state) {
8322 undostruct->undo_state = TRUE;
8323 menu_set_sensitive(ifactory, "/Edit/Undo", TRUE);
8326 case UNDO_STATE_FALSE:
8327 if (undostruct->undo_state) {
8328 undostruct->undo_state = FALSE;
8329 menu_set_sensitive(ifactory, "/Edit/Undo", FALSE);
8332 case UNDO_STATE_UNCHANGED:
8334 case UNDO_STATE_REFRESH:
8335 menu_set_sensitive(ifactory, "/Edit/Undo",
8336 undostruct->undo_state);
8339 g_warning("Undo state not recognized");
8343 switch (redo_state) {
8344 case UNDO_STATE_TRUE:
8345 if (!undostruct->redo_state) {
8346 undostruct->redo_state = TRUE;
8347 menu_set_sensitive(ifactory, "/Edit/Redo", TRUE);
8350 case UNDO_STATE_FALSE:
8351 if (undostruct->redo_state) {
8352 undostruct->redo_state = FALSE;
8353 menu_set_sensitive(ifactory, "/Edit/Redo", FALSE);
8356 case UNDO_STATE_UNCHANGED:
8358 case UNDO_STATE_REFRESH:
8359 menu_set_sensitive(ifactory, "/Edit/Redo",
8360 undostruct->redo_state);
8363 g_warning("Redo state not recognized");
8368 /* callback functions */
8370 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
8371 * includes "non-client" (windows-izm) in calculation, so this calculation
8372 * may not be accurate.
8374 static gboolean compose_edit_size_alloc(GtkEditable *widget,
8375 GtkAllocation *allocation,
8376 GtkSHRuler *shruler)
8378 if (prefs_common.show_ruler) {
8379 gint char_width = 0, char_height = 0;
8380 gint line_width_in_chars;
8382 gtkut_get_font_size(GTK_WIDGET(widget),
8383 &char_width, &char_height);
8384 line_width_in_chars =
8385 (allocation->width - allocation->x) / char_width;
8387 /* got the maximum */
8388 gtk_ruler_set_range(GTK_RULER(shruler),
8389 0.0, line_width_in_chars, 0,
8390 /*line_width_in_chars*/ char_width);
8396 static void account_activated(GtkComboBox *optmenu, gpointer data)
8398 Compose *compose = (Compose *)data;
8401 gchar *folderidentifier;
8402 gint account_id = 0;
8406 /* Get ID of active account in the combo box */
8407 menu = gtk_combo_box_get_model(optmenu);
8408 gtk_combo_box_get_active_iter(optmenu, &iter);
8409 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
8411 ac = account_find_from_id(account_id);
8412 g_return_if_fail(ac != NULL);
8414 if (ac != compose->account)
8415 compose_select_account(compose, ac, FALSE);
8417 /* Set message save folder */
8418 if (account_get_special_folder(compose->account, F_OUTBOX)) {
8419 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
8421 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
8422 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
8424 gtk_editable_delete_text(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
8425 if (account_get_special_folder(compose->account, F_OUTBOX)) {
8426 folderidentifier = folder_item_get_identifier(account_get_special_folder
8427 (compose->account, F_OUTBOX));
8428 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
8429 g_free(folderidentifier);
8433 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
8434 GtkTreeViewColumn *column, Compose *compose)
8436 compose_attach_property(compose);
8439 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
8442 Compose *compose = (Compose *)data;
8443 GtkTreeSelection *attach_selection;
8444 gint attach_nr_selected;
8445 GtkItemFactory *ifactory;
8447 if (!event) return FALSE;
8449 if (event->button == 3) {
8450 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
8451 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
8452 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
8454 if (attach_nr_selected > 0)
8456 menu_set_sensitive(ifactory, "/Remove", TRUE);
8457 menu_set_sensitive(ifactory, "/Properties...", TRUE);
8459 menu_set_sensitive(ifactory, "/Remove", FALSE);
8460 menu_set_sensitive(ifactory, "/Properties...", FALSE);
8463 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
8464 NULL, NULL, event->button, event->time);
8471 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
8474 Compose *compose = (Compose *)data;
8476 if (!event) return FALSE;
8478 switch (event->keyval) {
8480 compose_attach_remove_selected(compose);
8486 static void compose_allow_user_actions (Compose *compose, gboolean allow)
8488 GtkItemFactory *ifactory = gtk_item_factory_from_widget(compose->menubar);
8489 toolbar_comp_set_sensitive(compose, allow);
8490 menu_set_sensitive(ifactory, "/Message", allow);
8491 menu_set_sensitive(ifactory, "/Edit", allow);
8493 menu_set_sensitive(ifactory, "/Spelling", allow);
8495 menu_set_sensitive(ifactory, "/Options", allow);
8496 menu_set_sensitive(ifactory, "/Tools", allow);
8497 menu_set_sensitive(ifactory, "/Help", allow);
8499 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
8503 static void compose_send_cb(gpointer data, guint action, GtkWidget *widget)
8505 Compose *compose = (Compose *)data;
8507 if (prefs_common.work_offline &&
8508 !inc_offline_should_override(TRUE,
8509 _("Claws Mail needs network access in order "
8510 "to send this email.")))
8513 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
8514 g_source_remove(compose->draft_timeout_tag);
8515 compose->draft_timeout_tag = -1;
8518 compose_send(compose);
8521 static void compose_send_later_cb(gpointer data, guint action,
8524 Compose *compose = (Compose *)data;
8528 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
8532 compose_close(compose);
8533 } else if (val == -1) {
8534 alertpanel_error(_("Could not queue message."));
8535 } else if (val == -2) {
8536 alertpanel_error(_("Could not queue message:\n\n%s."), strerror(errno));
8537 } else if (val == -3) {
8538 if (privacy_peek_error())
8539 alertpanel_error(_("Could not queue message for sending:\n\n"
8540 "Signature failed: %s"), privacy_get_error());
8541 } else if (val == -4) {
8542 alertpanel_error(_("Could not queue message for sending:\n\n"
8543 "Charset conversion failed."));
8544 } else if (val == -5) {
8545 alertpanel_error(_("Could not queue message for sending:\n\n"
8546 "Couldn't get recipient encryption key."));
8547 } else if (val == -6) {
8550 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
8553 #define DRAFTED_AT_EXIT "drafted_at_exit"
8554 static void compose_register_draft(MsgInfo *info)
8556 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8557 DRAFTED_AT_EXIT, NULL);
8558 FILE *fp = fopen(filepath, "ab");
8561 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
8569 gboolean compose_draft (gpointer data, guint action)
8571 Compose *compose = (Compose *)data;
8575 MsgFlags flag = {0, 0};
8576 static gboolean lock = FALSE;
8577 MsgInfo *newmsginfo;
8579 gboolean target_locked = FALSE;
8580 gboolean err = FALSE;
8582 if (lock) return FALSE;
8584 if (compose->sending)
8587 draft = account_get_special_folder(compose->account, F_DRAFT);
8588 g_return_val_if_fail(draft != NULL, FALSE);
8590 if (!g_mutex_trylock(compose->mutex)) {
8591 /* we don't want to lock the mutex once it's available,
8592 * because as the only other part of compose.c locking
8593 * it is compose_close - which means once unlocked,
8594 * the compose struct will be freed */
8595 debug_print("couldn't lock mutex, probably sending\n");
8601 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
8602 G_DIR_SEPARATOR, compose);
8603 if ((fp = g_fopen(tmp, "wb")) == NULL) {
8604 FILE_OP_ERROR(tmp, "fopen");
8608 /* chmod for security */
8609 if (change_file_mode_rw(fp, tmp) < 0) {
8610 FILE_OP_ERROR(tmp, "chmod");
8611 g_warning("can't change file mode\n");
8614 /* Save draft infos */
8615 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
8616 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
8618 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
8619 gchar *savefolderid;
8621 savefolderid = gtk_editable_get_chars(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
8622 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
8623 g_free(savefolderid);
8625 if (compose->return_receipt) {
8626 err |= (fprintf(fp, "RRCPT:1\n") < 0);
8628 if (compose->privacy_system) {
8629 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
8630 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
8631 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
8634 /* Message-ID of message replying to */
8635 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
8638 folderid = folder_item_get_identifier(compose->replyinfo->folder);
8639 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
8642 /* Message-ID of message forwarding to */
8643 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
8646 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
8647 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
8651 /* end of headers */
8652 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
8659 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
8663 if (fclose(fp) == EOF) {
8667 if (compose->targetinfo) {
8668 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
8669 flag.perm_flags = target_locked?MSG_LOCKED:0;
8671 flag.tmp_flags = MSG_DRAFT;
8673 folder_item_scan(draft);
8674 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
8675 MsgInfo *tmpinfo = NULL;
8676 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
8677 if (compose->msgid) {
8678 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
8681 msgnum = tmpinfo->msgnum;
8682 procmsg_msginfo_free(tmpinfo);
8683 debug_print("got draft msgnum %d from scanning\n", msgnum);
8685 debug_print("didn't get draft msgnum after scanning\n");
8688 debug_print("got draft msgnum %d from adding\n", msgnum);
8694 if (action != COMPOSE_AUTO_SAVE) {
8695 if (action != COMPOSE_DRAFT_FOR_EXIT)
8696 alertpanel_error(_("Could not save draft."));
8699 gtkut_window_popup(compose->window);
8700 val = alertpanel_full(_("Could not save draft"),
8701 _("Could not save draft.\n"
8702 "Do you want to cancel exit or discard this email?"),
8703 _("_Cancel exit"), _("_Discard email"), NULL,
8704 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
8705 if (val == G_ALERTALTERNATE) {
8707 g_mutex_unlock(compose->mutex); /* must be done before closing */
8708 compose_close(compose);
8712 g_mutex_unlock(compose->mutex); /* must be done before closing */
8721 if (compose->mode == COMPOSE_REEDIT) {
8722 compose_remove_reedit_target(compose, TRUE);
8725 newmsginfo = folder_item_get_msginfo(draft, msgnum);
8728 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
8730 procmsg_msginfo_set_flags(newmsginfo, MSG_LOCKED, MSG_DRAFT);
8732 procmsg_msginfo_set_flags(newmsginfo, 0, MSG_DRAFT);
8733 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
8734 procmsg_msginfo_set_flags(newmsginfo, 0,
8735 MSG_HAS_ATTACHMENT);
8737 if (action == COMPOSE_DRAFT_FOR_EXIT) {
8738 compose_register_draft(newmsginfo);
8740 procmsg_msginfo_free(newmsginfo);
8743 folder_item_scan(draft);
8745 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
8747 g_mutex_unlock(compose->mutex); /* must be done before closing */
8748 compose_close(compose);
8754 path = folder_item_fetch_msg(draft, msgnum);
8756 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
8759 if (g_stat(path, &s) < 0) {
8760 FILE_OP_ERROR(path, "stat");
8766 procmsg_msginfo_free(compose->targetinfo);
8767 compose->targetinfo = procmsg_msginfo_new();
8768 compose->targetinfo->msgnum = msgnum;
8769 compose->targetinfo->size = s.st_size;
8770 compose->targetinfo->mtime = s.st_mtime;
8771 compose->targetinfo->folder = draft;
8773 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
8774 compose->mode = COMPOSE_REEDIT;
8776 if (action == COMPOSE_AUTO_SAVE) {
8777 compose->autosaved_draft = compose->targetinfo;
8779 compose->modified = FALSE;
8780 compose_set_title(compose);
8784 g_mutex_unlock(compose->mutex);
8788 void compose_clear_exit_drafts(void)
8790 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8791 DRAFTED_AT_EXIT, NULL);
8792 if (is_file_exist(filepath))
8798 void compose_reopen_exit_drafts(void)
8800 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8801 DRAFTED_AT_EXIT, NULL);
8802 FILE *fp = fopen(filepath, "rb");
8806 while (fgets(buf, sizeof(buf), fp)) {
8807 gchar **parts = g_strsplit(buf, "\t", 2);
8808 const gchar *folder = parts[0];
8809 int msgnum = parts[1] ? atoi(parts[1]):-1;
8811 if (folder && *folder && msgnum > -1) {
8812 FolderItem *item = folder_find_item_from_identifier(folder);
8813 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
8815 compose_reedit(info, FALSE);
8822 compose_clear_exit_drafts();
8825 static void compose_draft_cb(gpointer data, guint action, GtkWidget *widget)
8827 compose_draft(data, action);
8830 static void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
8832 if (compose && file_list) {
8835 for ( tmp = file_list; tmp; tmp = tmp->next) {
8836 gchar *file = (gchar *) tmp->data;
8837 gchar *utf8_filename = conv_filename_to_utf8(file);
8838 compose_attach_append(compose, file, utf8_filename, NULL);
8839 compose_changed_cb(NULL, compose);
8844 g_free(utf8_filename);
8849 static void compose_attach_cb(gpointer data, guint action, GtkWidget *widget)
8851 Compose *compose = (Compose *)data;
8854 if (compose->redirect_filename != NULL)
8857 file_list = filesel_select_multiple_files_open(_("Select file"));
8860 compose_attach_from_list(compose, file_list, TRUE);
8861 g_list_free(file_list);
8865 static void compose_insert_file_cb(gpointer data, guint action,
8868 Compose *compose = (Compose *)data;
8871 file_list = filesel_select_multiple_files_open(_("Select file"));
8876 for ( tmp = file_list; tmp; tmp = tmp->next) {
8877 gchar *file = (gchar *) tmp->data;
8878 gchar *filedup = g_strdup(file);
8879 gchar *shortfile = g_path_get_basename(filedup);
8880 ComposeInsertResult res;
8882 res = compose_insert_file(compose, file);
8883 if (res == COMPOSE_INSERT_READ_ERROR) {
8884 alertpanel_error(_("File '%s' could not be read."), shortfile);
8885 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
8886 alertpanel_error(_("File '%s' contained invalid characters\n"
8887 "for the current encoding, insertion may be incorrect."), shortfile);
8893 g_list_free(file_list);
8897 static void compose_insert_sig_cb(gpointer data, guint action,
8900 Compose *compose = (Compose *)data;
8902 compose_insert_sig(compose, FALSE);
8905 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
8909 Compose *compose = (Compose *)data;
8911 gtkut_widget_get_uposition(widget, &x, &y);
8912 prefs_common.compose_x = x;
8913 prefs_common.compose_y = y;
8915 if (compose->sending || compose->updating)
8917 compose_close_cb(compose, 0, NULL);
8921 void compose_close_toolbar(Compose *compose)
8923 compose_close_cb(compose, 0, NULL);
8926 static void compose_close_cb(gpointer data, guint action, GtkWidget *widget)
8928 Compose *compose = (Compose *)data;
8932 if (compose->exteditor_tag != -1) {
8933 if (!compose_ext_editor_kill(compose))
8938 if (compose->modified) {
8939 val = alertpanel(_("Discard message"),
8940 _("This message has been modified. Discard it?"),
8941 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
8944 case G_ALERTDEFAULT:
8945 if (prefs_common.autosave)
8946 compose_remove_draft(compose);
8948 case G_ALERTALTERNATE:
8949 compose_draft_cb(data, COMPOSE_QUIT_EDITING, NULL);
8956 compose_close(compose);
8959 static void compose_set_encoding_cb(gpointer data, guint action,
8962 Compose *compose = (Compose *)data;
8964 if (GTK_CHECK_MENU_ITEM(widget)->active)
8965 compose->out_encoding = (CharSet)action;
8968 static void compose_address_cb(gpointer data, guint action, GtkWidget *widget)
8970 Compose *compose = (Compose *)data;
8972 addressbook_open(compose);
8975 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
8977 Compose *compose = (Compose *)data;
8982 tmpl = g_object_get_data(G_OBJECT(widget), "template");
8983 g_return_if_fail(tmpl != NULL);
8985 msg = g_strdup_printf(_("Do you want to apply the template '%s' ?"),
8987 val = alertpanel(_("Apply template"), msg,
8988 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
8991 if (val == G_ALERTDEFAULT)
8992 compose_template_apply(compose, tmpl, TRUE);
8993 else if (val == G_ALERTALTERNATE)
8994 compose_template_apply(compose, tmpl, FALSE);
8997 static void compose_ext_editor_cb(gpointer data, guint action,
9000 Compose *compose = (Compose *)data;
9002 compose_exec_ext_editor(compose);
9005 static void compose_undo_cb(Compose *compose)
9007 gboolean prev_autowrap = compose->autowrap;
9009 compose->autowrap = FALSE;
9010 undo_undo(compose->undostruct);
9011 compose->autowrap = prev_autowrap;
9014 static void compose_redo_cb(Compose *compose)
9016 gboolean prev_autowrap = compose->autowrap;
9018 compose->autowrap = FALSE;
9019 undo_redo(compose->undostruct);
9020 compose->autowrap = prev_autowrap;
9023 static void entry_cut_clipboard(GtkWidget *entry)
9025 if (GTK_IS_EDITABLE(entry))
9026 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
9027 else if (GTK_IS_TEXT_VIEW(entry))
9028 gtk_text_buffer_cut_clipboard(
9029 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
9030 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
9034 static void entry_copy_clipboard(GtkWidget *entry)
9036 if (GTK_IS_EDITABLE(entry))
9037 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
9038 else if (GTK_IS_TEXT_VIEW(entry))
9039 gtk_text_buffer_copy_clipboard(
9040 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
9041 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
9044 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
9045 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
9047 if (GTK_IS_TEXT_VIEW(entry)) {
9048 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
9049 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
9050 GtkTextIter start_iter, end_iter;
9052 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
9054 if (contents == NULL)
9057 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
9059 /* we shouldn't delete the selection when middle-click-pasting, or we
9060 * can't mid-click-paste our own selection */
9061 if (clip != GDK_SELECTION_PRIMARY) {
9062 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
9065 if (insert_place == NULL) {
9066 /* if insert_place isn't specified, insert at the cursor.
9067 * used for Ctrl-V pasting */
9068 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
9069 start = gtk_text_iter_get_offset(&start_iter);
9070 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
9072 /* if insert_place is specified, paste here.
9073 * used for mid-click-pasting */
9074 start = gtk_text_iter_get_offset(insert_place);
9075 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
9079 /* paste unwrapped: mark the paste so it's not wrapped later */
9080 end = start + strlen(contents);
9081 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
9082 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
9083 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
9084 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
9085 /* rewrap paragraph now (after a mid-click-paste) */
9086 mark_start = gtk_text_buffer_get_insert(buffer);
9087 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
9088 gtk_text_iter_backward_char(&start_iter);
9089 compose_beautify_paragraph(compose, &start_iter, TRUE);
9091 } else if (GTK_IS_EDITABLE(entry))
9092 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
9096 static void entry_allsel(GtkWidget *entry)
9098 if (GTK_IS_EDITABLE(entry))
9099 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
9100 else if (GTK_IS_TEXT_VIEW(entry)) {
9101 GtkTextIter startiter, enditer;
9102 GtkTextBuffer *textbuf;
9104 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
9105 gtk_text_buffer_get_start_iter(textbuf, &startiter);
9106 gtk_text_buffer_get_end_iter(textbuf, &enditer);
9108 gtk_text_buffer_move_mark_by_name(textbuf,
9109 "selection_bound", &startiter);
9110 gtk_text_buffer_move_mark_by_name(textbuf,
9111 "insert", &enditer);
9115 static void compose_cut_cb(Compose *compose)
9117 if (compose->focused_editable
9119 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9122 entry_cut_clipboard(compose->focused_editable);
9125 static void compose_copy_cb(Compose *compose)
9127 if (compose->focused_editable
9129 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9132 entry_copy_clipboard(compose->focused_editable);
9135 static void compose_paste_cb(Compose *compose)
9138 GtkTextBuffer *buffer;
9140 if (compose->focused_editable &&
9141 GTK_WIDGET_HAS_FOCUS(compose->focused_editable))
9142 entry_paste_clipboard(compose, compose->focused_editable,
9143 prefs_common.linewrap_pastes,
9144 GDK_SELECTION_CLIPBOARD, NULL);
9148 static void compose_paste_as_quote_cb(Compose *compose)
9150 gint wrap_quote = prefs_common.linewrap_quote;
9151 if (compose->focused_editable
9153 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9156 /* let text_insert() (called directly or at a later time
9157 * after the gtk_editable_paste_clipboard) know that
9158 * text is to be inserted as a quotation. implemented
9159 * by using a simple refcount... */
9160 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
9161 G_OBJECT(compose->focused_editable),
9162 "paste_as_quotation"));
9163 g_object_set_data(G_OBJECT(compose->focused_editable),
9164 "paste_as_quotation",
9165 GINT_TO_POINTER(paste_as_quotation + 1));
9166 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
9167 entry_paste_clipboard(compose, compose->focused_editable,
9168 prefs_common.linewrap_pastes,
9169 GDK_SELECTION_CLIPBOARD, NULL);
9170 prefs_common.linewrap_quote = wrap_quote;
9174 static void compose_paste_no_wrap_cb(Compose *compose)
9177 GtkTextBuffer *buffer;
9179 if (compose->focused_editable
9181 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9184 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
9185 GDK_SELECTION_CLIPBOARD, NULL);
9189 static void compose_paste_wrap_cb(Compose *compose)
9192 GtkTextBuffer *buffer;
9194 if (compose->focused_editable
9196 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9199 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
9200 GDK_SELECTION_CLIPBOARD, NULL);
9204 static void compose_allsel_cb(Compose *compose)
9206 if (compose->focused_editable
9208 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9211 entry_allsel(compose->focused_editable);
9214 static void textview_move_beginning_of_line (GtkTextView *text)
9216 GtkTextBuffer *buffer;
9220 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9222 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9223 mark = gtk_text_buffer_get_insert(buffer);
9224 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9225 gtk_text_iter_set_line_offset(&ins, 0);
9226 gtk_text_buffer_place_cursor(buffer, &ins);
9229 static void textview_move_forward_character (GtkTextView *text)
9231 GtkTextBuffer *buffer;
9235 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9237 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9238 mark = gtk_text_buffer_get_insert(buffer);
9239 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9240 if (gtk_text_iter_forward_cursor_position(&ins))
9241 gtk_text_buffer_place_cursor(buffer, &ins);
9244 static void textview_move_backward_character (GtkTextView *text)
9246 GtkTextBuffer *buffer;
9250 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9252 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9253 mark = gtk_text_buffer_get_insert(buffer);
9254 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9255 if (gtk_text_iter_backward_cursor_position(&ins))
9256 gtk_text_buffer_place_cursor(buffer, &ins);
9259 static void textview_move_forward_word (GtkTextView *text)
9261 GtkTextBuffer *buffer;
9266 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9268 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9269 mark = gtk_text_buffer_get_insert(buffer);
9270 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9271 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
9272 if (gtk_text_iter_forward_word_ends(&ins, count)) {
9273 gtk_text_iter_backward_word_start(&ins);
9274 gtk_text_buffer_place_cursor(buffer, &ins);
9278 static void textview_move_backward_word (GtkTextView *text)
9280 GtkTextBuffer *buffer;
9285 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9287 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9288 mark = gtk_text_buffer_get_insert(buffer);
9289 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9290 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
9291 if (gtk_text_iter_backward_word_starts(&ins, 1))
9292 gtk_text_buffer_place_cursor(buffer, &ins);
9295 static void textview_move_end_of_line (GtkTextView *text)
9297 GtkTextBuffer *buffer;
9301 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9303 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9304 mark = gtk_text_buffer_get_insert(buffer);
9305 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9306 if (gtk_text_iter_forward_to_line_end(&ins))
9307 gtk_text_buffer_place_cursor(buffer, &ins);
9310 static void textview_move_next_line (GtkTextView *text)
9312 GtkTextBuffer *buffer;
9317 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9319 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9320 mark = gtk_text_buffer_get_insert(buffer);
9321 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9322 offset = gtk_text_iter_get_line_offset(&ins);
9323 if (gtk_text_iter_forward_line(&ins)) {
9324 gtk_text_iter_set_line_offset(&ins, offset);
9325 gtk_text_buffer_place_cursor(buffer, &ins);
9329 static void textview_move_previous_line (GtkTextView *text)
9331 GtkTextBuffer *buffer;
9336 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9338 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9339 mark = gtk_text_buffer_get_insert(buffer);
9340 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9341 offset = gtk_text_iter_get_line_offset(&ins);
9342 if (gtk_text_iter_backward_line(&ins)) {
9343 gtk_text_iter_set_line_offset(&ins, offset);
9344 gtk_text_buffer_place_cursor(buffer, &ins);
9348 static void textview_delete_forward_character (GtkTextView *text)
9350 GtkTextBuffer *buffer;
9352 GtkTextIter ins, end_iter;
9354 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9356 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9357 mark = gtk_text_buffer_get_insert(buffer);
9358 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9360 if (gtk_text_iter_forward_char(&end_iter)) {
9361 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9365 static void textview_delete_backward_character (GtkTextView *text)
9367 GtkTextBuffer *buffer;
9369 GtkTextIter ins, end_iter;
9371 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9373 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9374 mark = gtk_text_buffer_get_insert(buffer);
9375 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9377 if (gtk_text_iter_backward_char(&end_iter)) {
9378 gtk_text_buffer_delete(buffer, &end_iter, &ins);
9382 static void textview_delete_forward_word (GtkTextView *text)
9384 GtkTextBuffer *buffer;
9386 GtkTextIter ins, end_iter;
9388 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9390 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9391 mark = gtk_text_buffer_get_insert(buffer);
9392 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9394 if (gtk_text_iter_forward_word_end(&end_iter)) {
9395 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9399 static void textview_delete_backward_word (GtkTextView *text)
9401 GtkTextBuffer *buffer;
9403 GtkTextIter ins, end_iter;
9405 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9407 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9408 mark = gtk_text_buffer_get_insert(buffer);
9409 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9411 if (gtk_text_iter_backward_word_start(&end_iter)) {
9412 gtk_text_buffer_delete(buffer, &end_iter, &ins);
9416 static void textview_delete_line (GtkTextView *text)
9418 GtkTextBuffer *buffer;
9420 GtkTextIter ins, start_iter, end_iter;
9423 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9425 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9426 mark = gtk_text_buffer_get_insert(buffer);
9427 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9430 gtk_text_iter_set_line_offset(&start_iter, 0);
9433 if (gtk_text_iter_ends_line(&end_iter))
9434 found = gtk_text_iter_forward_char(&end_iter);
9436 found = gtk_text_iter_forward_to_line_end(&end_iter);
9439 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
9442 static void textview_delete_to_line_end (GtkTextView *text)
9444 GtkTextBuffer *buffer;
9446 GtkTextIter ins, end_iter;
9449 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9451 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9452 mark = gtk_text_buffer_get_insert(buffer);
9453 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9455 if (gtk_text_iter_ends_line(&end_iter))
9456 found = gtk_text_iter_forward_char(&end_iter);
9458 found = gtk_text_iter_forward_to_line_end(&end_iter);
9460 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9463 static void compose_advanced_action_cb(Compose *compose,
9464 ComposeCallAdvancedAction action)
9466 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9468 void (*do_action) (GtkTextView *text);
9469 } action_table[] = {
9470 {textview_move_beginning_of_line},
9471 {textview_move_forward_character},
9472 {textview_move_backward_character},
9473 {textview_move_forward_word},
9474 {textview_move_backward_word},
9475 {textview_move_end_of_line},
9476 {textview_move_next_line},
9477 {textview_move_previous_line},
9478 {textview_delete_forward_character},
9479 {textview_delete_backward_character},
9480 {textview_delete_forward_word},
9481 {textview_delete_backward_word},
9482 {textview_delete_line},
9483 {NULL}, /* gtk_stext_delete_line_n */
9484 {textview_delete_to_line_end}
9487 if (!GTK_WIDGET_HAS_FOCUS(text)) return;
9489 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
9490 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
9491 if (action_table[action].do_action)
9492 action_table[action].do_action(text);
9494 g_warning("Not implemented yet.");
9498 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
9502 if (GTK_IS_EDITABLE(widget)) {
9503 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
9504 gtk_editable_set_position(GTK_EDITABLE(widget),
9507 if (widget->parent && widget->parent->parent
9508 && widget->parent->parent->parent) {
9509 if (GTK_IS_SCROLLED_WINDOW(widget->parent->parent->parent)) {
9510 gint y = widget->allocation.y;
9511 gint height = widget->allocation.height;
9512 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
9513 (GTK_SCROLLED_WINDOW(widget->parent->parent->parent));
9515 if (y < (int)shown->value) {
9516 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown), y - 1);
9518 if (y + height > (int)shown->value + (int)shown->page_size) {
9519 if (y - height - 1 < (int)shown->upper - (int)shown->page_size) {
9520 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
9521 y + height - (int)shown->page_size - 1);
9523 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
9524 (int)shown->upper - (int)shown->page_size - 1);
9531 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
9532 compose->focused_editable = widget;
9535 if (GTK_IS_TEXT_VIEW(widget)
9536 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
9537 gtk_widget_ref(compose->notebook);
9538 gtk_widget_ref(compose->edit_vbox);
9539 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
9540 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
9541 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
9542 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
9543 gtk_widget_unref(compose->notebook);
9544 gtk_widget_unref(compose->edit_vbox);
9545 g_signal_handlers_block_by_func(G_OBJECT(widget),
9546 G_CALLBACK(compose_grab_focus_cb),
9548 gtk_widget_grab_focus(widget);
9549 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
9550 G_CALLBACK(compose_grab_focus_cb),
9552 } else if (!GTK_IS_TEXT_VIEW(widget)
9553 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
9554 gtk_widget_ref(compose->notebook);
9555 gtk_widget_ref(compose->edit_vbox);
9556 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
9557 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
9558 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
9559 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
9560 gtk_widget_unref(compose->notebook);
9561 gtk_widget_unref(compose->edit_vbox);
9562 g_signal_handlers_block_by_func(G_OBJECT(widget),
9563 G_CALLBACK(compose_grab_focus_cb),
9565 gtk_widget_grab_focus(widget);
9566 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
9567 G_CALLBACK(compose_grab_focus_cb),
9573 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
9575 compose->modified = TRUE;
9577 compose_set_title(compose);
9581 static void compose_wrap_cb(gpointer data, guint action, GtkWidget *widget)
9583 Compose *compose = (Compose *)data;
9586 compose_wrap_all_full(compose, TRUE);
9588 compose_beautify_paragraph(compose, NULL, TRUE);
9591 static void compose_find_cb(gpointer data, guint action, GtkWidget *widget)
9593 Compose *compose = (Compose *)data;
9595 message_search_compose(compose);
9598 static void compose_toggle_autowrap_cb(gpointer data, guint action,
9601 Compose *compose = (Compose *)data;
9602 compose->autowrap = GTK_CHECK_MENU_ITEM(widget)->active;
9603 if (compose->autowrap)
9604 compose_wrap_all_full(compose, TRUE);
9605 compose->autowrap = GTK_CHECK_MENU_ITEM(widget)->active;
9608 static void compose_toggle_sign_cb(gpointer data, guint action,
9611 Compose *compose = (Compose *)data;
9613 if (GTK_CHECK_MENU_ITEM(widget)->active)
9614 compose->use_signing = TRUE;
9616 compose->use_signing = FALSE;
9619 static void compose_toggle_encrypt_cb(gpointer data, guint action,
9622 Compose *compose = (Compose *)data;
9624 if (GTK_CHECK_MENU_ITEM(widget)->active)
9625 compose->use_encryption = TRUE;
9627 compose->use_encryption = FALSE;
9630 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
9632 g_free(compose->privacy_system);
9634 compose->privacy_system = g_strdup(account->default_privacy_system);
9635 compose_update_privacy_system_menu_item(compose, warn);
9638 static void compose_toggle_ruler_cb(gpointer data, guint action,
9641 Compose *compose = (Compose *)data;
9643 if (GTK_CHECK_MENU_ITEM(widget)->active) {
9644 gtk_widget_show(compose->ruler_hbox);
9645 prefs_common.show_ruler = TRUE;
9647 gtk_widget_hide(compose->ruler_hbox);
9648 gtk_widget_queue_resize(compose->edit_vbox);
9649 prefs_common.show_ruler = FALSE;
9653 static void compose_attach_drag_received_cb (GtkWidget *widget,
9654 GdkDragContext *context,
9657 GtkSelectionData *data,
9662 Compose *compose = (Compose *)user_data;
9665 if (gdk_atom_name(data->type) &&
9666 !strcmp(gdk_atom_name(data->type), "text/uri-list")
9667 && gtk_drag_get_source_widget(context) !=
9668 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
9669 list = uri_list_extract_filenames((const gchar *)data->data);
9670 for (tmp = list; tmp != NULL; tmp = tmp->next) {
9671 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
9672 compose_attach_append
9673 (compose, (const gchar *)tmp->data,
9674 utf8_filename, NULL);
9675 g_free(utf8_filename);
9677 if (list) compose_changed_cb(NULL, compose);
9678 list_free_strings(list);
9680 } else if (gtk_drag_get_source_widget(context)
9681 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
9682 /* comes from our summaryview */
9683 SummaryView * summaryview = NULL;
9684 GSList * list = NULL, *cur = NULL;
9686 if (mainwindow_get_mainwindow())
9687 summaryview = mainwindow_get_mainwindow()->summaryview;
9690 list = summary_get_selected_msg_list(summaryview);
9692 for (cur = list; cur; cur = cur->next) {
9693 MsgInfo *msginfo = (MsgInfo *)cur->data;
9696 file = procmsg_get_message_file_full(msginfo,
9699 compose_attach_append(compose, (const gchar *)file,
9700 (const gchar *)file, "message/rfc822");
9708 static gboolean compose_drag_drop(GtkWidget *widget,
9709 GdkDragContext *drag_context,
9711 guint time, gpointer user_data)
9713 /* not handling this signal makes compose_insert_drag_received_cb
9718 static void compose_insert_drag_received_cb (GtkWidget *widget,
9719 GdkDragContext *drag_context,
9722 GtkSelectionData *data,
9727 Compose *compose = (Compose *)user_data;
9730 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
9732 if (gdk_atom_name(data->type) && !strcmp(gdk_atom_name(data->type), "text/uri-list")) {
9733 AlertValue val = G_ALERTDEFAULT;
9735 list = uri_list_extract_filenames((const gchar *)data->data);
9737 if (list == NULL && strstr((gchar *)(data->data), "://")) {
9738 /* Assume a list of no files, and data has ://, is a remote link */
9739 gchar *tmpdata = g_strstrip(g_strdup((const gchar *)data->data));
9740 gchar *tmpfile = get_tmp_file();
9741 str_write_to_file(tmpdata, tmpfile);
9743 compose_insert_file(compose, tmpfile);
9746 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9747 compose_beautify_paragraph(compose, NULL, TRUE);
9750 switch (prefs_common.compose_dnd_mode) {
9751 case COMPOSE_DND_ASK:
9752 val = alertpanel_full(_("Insert or attach?"),
9753 _("Do you want to insert the contents of the file(s) "
9754 "into the message body, or attach it to the email?"),
9755 GTK_STOCK_CANCEL, _("+_Insert"), _("_Attach"),
9756 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
9758 case COMPOSE_DND_INSERT:
9759 val = G_ALERTALTERNATE;
9761 case COMPOSE_DND_ATTACH:
9765 /* unexpected case */
9766 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
9769 if (val & G_ALERTDISABLE) {
9770 val &= ~G_ALERTDISABLE;
9771 /* remember what action to perform by default, only if we don't click Cancel */
9772 if (val == G_ALERTALTERNATE)
9773 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
9774 else if (val == G_ALERTOTHER)
9775 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
9778 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
9779 gtk_drag_finish(drag_context, FALSE, FALSE, time);
9780 list_free_strings(list);
9783 } else if (val == G_ALERTOTHER) {
9784 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
9785 list_free_strings(list);
9790 for (tmp = list; tmp != NULL; tmp = tmp->next) {
9791 compose_insert_file(compose, (const gchar *)tmp->data);
9793 list_free_strings(list);
9795 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9798 #if GTK_CHECK_VERSION(2, 8, 0)
9799 /* do nothing, handled by GTK */
9801 gchar *tmpfile = get_tmp_file();
9802 str_write_to_file((const gchar *)data->data, tmpfile);
9803 compose_insert_file(compose, tmpfile);
9806 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9810 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9813 static void compose_header_drag_received_cb (GtkWidget *widget,
9814 GdkDragContext *drag_context,
9817 GtkSelectionData *data,
9822 GtkEditable *entry = (GtkEditable *)user_data;
9823 gchar *email = (gchar *)data->data;
9825 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
9828 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
9829 gchar *decoded=g_new(gchar, strlen(email));
9832 email += strlen("mailto:");
9833 decode_uri(decoded, email); /* will fit */
9834 gtk_editable_delete_text(entry, 0, -1);
9835 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
9836 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9840 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9843 static void compose_toggle_return_receipt_cb(gpointer data, guint action,
9846 Compose *compose = (Compose *)data;
9848 if (GTK_CHECK_MENU_ITEM(widget)->active)
9849 compose->return_receipt = TRUE;
9851 compose->return_receipt = FALSE;
9854 static void compose_toggle_remove_refs_cb(gpointer data, guint action,
9857 Compose *compose = (Compose *)data;
9859 if (GTK_CHECK_MENU_ITEM(widget)->active)
9860 compose->remove_references = TRUE;
9862 compose->remove_references = FALSE;
9865 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
9867 ComposeHeaderEntry *headerentry)
9869 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
9870 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
9871 !(event->state & GDK_MODIFIER_MASK) &&
9872 (event->keyval == GDK_BackSpace) &&
9873 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
9874 gtk_container_remove
9875 (GTK_CONTAINER(headerentry->compose->header_table),
9876 headerentry->combo);
9877 gtk_container_remove
9878 (GTK_CONTAINER(headerentry->compose->header_table),
9879 headerentry->entry);
9880 headerentry->compose->header_list =
9881 g_slist_remove(headerentry->compose->header_list,
9883 g_free(headerentry);
9884 } else if (event->keyval == GDK_Tab) {
9885 if (headerentry->compose->header_last == headerentry) {
9886 /* Override default next focus, and give it to subject_entry
9887 * instead of notebook tabs
9889 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
9890 gtk_widget_grab_focus(headerentry->compose->subject_entry);
9897 static gboolean compose_headerentry_changed_cb(GtkWidget *entry,
9898 ComposeHeaderEntry *headerentry)
9900 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
9901 compose_create_header_entry(headerentry->compose);
9902 g_signal_handlers_disconnect_matched
9903 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
9904 0, 0, NULL, NULL, headerentry);
9906 /* Automatically scroll down */
9907 compose_show_first_last_header(headerentry->compose, FALSE);
9913 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
9915 GtkAdjustment *vadj;
9917 g_return_if_fail(compose);
9918 g_return_if_fail(GTK_IS_WIDGET(compose->header_table));
9919 g_return_if_fail(GTK_IS_VIEWPORT(compose->header_table->parent));
9921 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(compose->header_table->parent));
9922 gtk_adjustment_set_value(vadj, (show_first ? vadj->lower : vadj->upper));
9923 gtk_adjustment_changed(vadj);
9926 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
9927 const gchar *text, gint len, Compose *compose)
9929 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
9930 (G_OBJECT(compose->text), "paste_as_quotation"));
9933 g_return_if_fail(text != NULL);
9935 g_signal_handlers_block_by_func(G_OBJECT(buffer),
9936 G_CALLBACK(text_inserted),
9938 if (paste_as_quotation) {
9942 GtkTextIter start_iter;
9947 new_text = g_strndup(text, len);
9949 qmark = compose_quote_char_from_context(compose);
9951 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
9952 gtk_text_buffer_place_cursor(buffer, iter);
9954 pos = gtk_text_iter_get_offset(iter);
9956 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
9957 _("Quote format error at line %d."));
9958 quote_fmt_reset_vartable();
9960 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
9961 GINT_TO_POINTER(paste_as_quotation - 1));
9963 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
9964 gtk_text_buffer_place_cursor(buffer, iter);
9965 gtk_text_buffer_delete_mark(buffer, mark);
9967 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
9968 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
9969 compose_beautify_paragraph(compose, &start_iter, FALSE);
9970 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
9971 gtk_text_buffer_delete_mark(buffer, mark);
9973 if (strcmp(text, "\n") || compose->automatic_break
9974 || gtk_text_iter_starts_line(iter))
9975 gtk_text_buffer_insert(buffer, iter, text, len);
9977 /* check if the preceding is just whitespace or quote */
9978 GtkTextIter start_line;
9979 gchar *tmp = NULL, *quote = NULL;
9980 gint quote_len = 0, is_normal = 0;
9982 gtk_text_iter_set_line_offset(&start_line, 0);
9983 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
9988 quote = compose_get_quote_str(buffer, &start_line, "e_len);
9996 gtk_text_buffer_insert(buffer, iter, text, len);
9998 gtk_text_buffer_insert_with_tags_by_name(buffer,
9999 iter, text, len, "no_join", NULL);
10004 if (!paste_as_quotation) {
10005 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
10006 compose_beautify_paragraph(compose, iter, FALSE);
10007 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
10008 gtk_text_buffer_delete_mark(buffer, mark);
10011 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
10012 G_CALLBACK(text_inserted),
10014 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
10016 if (prefs_common.autosave &&
10017 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
10018 compose->draft_timeout_tag != -2 /* disabled while loading */)
10019 compose->draft_timeout_tag = g_timeout_add
10020 (500, (GtkFunction) compose_defer_auto_save_draft, compose);
10022 static gint compose_defer_auto_save_draft(Compose *compose)
10024 compose->draft_timeout_tag = -1;
10025 compose_draft_cb((gpointer)compose, COMPOSE_AUTO_SAVE, NULL);
10030 static void compose_check_all(Compose *compose)
10032 if (compose->gtkaspell)
10033 gtkaspell_check_all(compose->gtkaspell);
10036 static void compose_highlight_all(Compose *compose)
10038 if (compose->gtkaspell)
10039 gtkaspell_highlight_all(compose->gtkaspell);
10042 static void compose_check_backwards(Compose *compose)
10044 if (compose->gtkaspell)
10045 gtkaspell_check_backwards(compose->gtkaspell);
10047 GtkItemFactory *ifactory;
10048 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
10049 menu_set_sensitive(ifactory, "/Edit/Check backwards misspelled word", FALSE);
10050 menu_set_sensitive(ifactory, "/Edit/Forward to next misspelled word", FALSE);
10054 static void compose_check_forwards_go(Compose *compose)
10056 if (compose->gtkaspell)
10057 gtkaspell_check_forwards_go(compose->gtkaspell);
10059 GtkItemFactory *ifactory;
10060 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
10061 menu_set_sensitive(ifactory, "/Edit/Check backwards misspelled word", FALSE);
10062 menu_set_sensitive(ifactory, "/Edit/Forward to next misspelled word", FALSE);
10068 *\brief Guess originating forward account from MsgInfo and several
10069 * "common preference" settings. Return NULL if no guess.
10071 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
10073 PrefsAccount *account = NULL;
10075 g_return_val_if_fail(msginfo, NULL);
10076 g_return_val_if_fail(msginfo->folder, NULL);
10077 g_return_val_if_fail(msginfo->folder->prefs, NULL);
10079 if (msginfo->folder->prefs->enable_default_account)
10080 account = account_find_from_id(msginfo->folder->prefs->default_account);
10083 account = msginfo->folder->folder->account;
10085 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
10087 Xstrdup_a(to, msginfo->to, return NULL);
10088 extract_address(to);
10089 account = account_find_from_address(to, FALSE);
10092 if (!account && prefs_common.forward_account_autosel) {
10093 gchar cc[BUFFSIZE];
10094 if (!procheader_get_header_from_msginfo
10095 (msginfo, cc,sizeof cc , "Cc:")) {
10096 gchar *buf = cc + strlen("Cc:");
10097 extract_address(buf);
10098 account = account_find_from_address(buf, FALSE);
10102 if (!account && prefs_common.forward_account_autosel) {
10103 gchar deliveredto[BUFFSIZE];
10104 if (!procheader_get_header_from_msginfo
10105 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
10106 gchar *buf = deliveredto + strlen("Delivered-To:");
10107 extract_address(buf);
10108 account = account_find_from_address(buf, FALSE);
10115 gboolean compose_close(Compose *compose)
10119 if (!g_mutex_trylock(compose->mutex)) {
10120 /* we have to wait for the (possibly deferred by auto-save)
10121 * drafting to be done, before destroying the compose under
10123 debug_print("waiting for drafting to finish...\n");
10124 compose_allow_user_actions(compose, FALSE);
10125 g_timeout_add (500, (GSourceFunc) compose_close, compose);
10128 g_return_val_if_fail(compose, FALSE);
10129 gtkut_widget_get_uposition(compose->window, &x, &y);
10130 prefs_common.compose_x = x;
10131 prefs_common.compose_y = y;
10132 g_mutex_unlock(compose->mutex);
10133 compose_destroy(compose);
10138 * Add entry field for each address in list.
10139 * \param compose E-Mail composition object.
10140 * \param listAddress List of (formatted) E-Mail addresses.
10142 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
10145 node = listAddress;
10147 addr = ( gchar * ) node->data;
10148 compose_entry_append( compose, addr, COMPOSE_TO );
10149 node = g_list_next( node );
10153 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
10154 guint action, gboolean opening_multiple)
10156 gchar *body = NULL;
10157 GSList *new_msglist = NULL;
10158 MsgInfo *tmp_msginfo = NULL;
10159 gboolean originally_enc = FALSE;
10160 Compose *compose = NULL;
10162 g_return_if_fail(msgview != NULL);
10164 g_return_if_fail(msginfo_list != NULL);
10166 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
10167 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
10168 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
10170 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
10171 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
10172 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
10173 orig_msginfo, mimeinfo);
10174 if (tmp_msginfo != NULL) {
10175 new_msglist = g_slist_append(NULL, tmp_msginfo);
10177 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
10178 tmp_msginfo->folder = orig_msginfo->folder;
10179 tmp_msginfo->msgnum = orig_msginfo->msgnum;
10180 if (orig_msginfo->tags)
10181 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
10186 if (!opening_multiple)
10187 body = messageview_get_selection(msgview);
10190 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
10191 procmsg_msginfo_free(tmp_msginfo);
10192 g_slist_free(new_msglist);
10194 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
10196 if (compose && originally_enc) {
10197 compose_force_encryption(compose, compose->account, FALSE);
10203 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
10206 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
10207 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
10208 GSList *cur = msginfo_list;
10209 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
10210 "messages. Opening the windows "
10211 "could take some time. Do you "
10212 "want to continue?"),
10213 g_slist_length(msginfo_list));
10214 if (g_slist_length(msginfo_list) > 9
10215 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
10216 != G_ALERTALTERNATE) {
10221 /* We'll open multiple compose windows */
10222 /* let the WM place the next windows */
10223 compose_force_window_origin = FALSE;
10224 for (; cur; cur = cur->next) {
10226 tmplist.data = cur->data;
10227 tmplist.next = NULL;
10228 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
10230 compose_force_window_origin = TRUE;
10232 /* forwarding multiple mails as attachments is done via a
10233 * single compose window */
10234 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
10238 void compose_set_position(Compose *compose, gint pos)
10240 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10242 gtkut_text_view_set_position(text, pos);
10245 gboolean compose_search_string(Compose *compose,
10246 const gchar *str, gboolean case_sens)
10248 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10250 return gtkut_text_view_search_string(text, str, case_sens);
10253 gboolean compose_search_string_backward(Compose *compose,
10254 const gchar *str, gboolean case_sens)
10256 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10258 return gtkut_text_view_search_string_backward(text, str, case_sens);
10261 /* allocate a msginfo structure and populate its data from a compose data structure */
10262 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
10264 MsgInfo *newmsginfo;
10266 gchar buf[BUFFSIZE];
10268 g_return_val_if_fail( compose != NULL, NULL );
10270 newmsginfo = procmsg_msginfo_new();
10273 get_rfc822_date(buf, sizeof(buf));
10274 newmsginfo->date = g_strdup(buf);
10277 if (compose->from_name) {
10278 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
10279 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
10283 if (compose->subject_entry)
10284 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
10286 /* to, cc, reply-to, newsgroups */
10287 for (list = compose->header_list; list; list = list->next) {
10288 gchar *header = gtk_editable_get_chars(
10290 GTK_BIN(((ComposeHeaderEntry *)list->data)->combo)->child), 0, -1);
10291 gchar *entry = gtk_editable_get_chars(
10292 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
10294 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
10295 if ( newmsginfo->to == NULL ) {
10296 newmsginfo->to = g_strdup(entry);
10297 } else if (entry && *entry) {
10298 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
10299 g_free(newmsginfo->to);
10300 newmsginfo->to = tmp;
10303 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
10304 if ( newmsginfo->cc == NULL ) {
10305 newmsginfo->cc = g_strdup(entry);
10306 } else if (entry && *entry) {
10307 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
10308 g_free(newmsginfo->cc);
10309 newmsginfo->cc = tmp;
10312 if ( strcasecmp(header,
10313 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
10314 if ( newmsginfo->newsgroups == NULL ) {
10315 newmsginfo->newsgroups = g_strdup(entry);
10316 } else if (entry && *entry) {
10317 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
10318 g_free(newmsginfo->newsgroups);
10319 newmsginfo->newsgroups = tmp;
10327 /* other data is unset */
10333 /* update compose's dictionaries from folder dict settings */
10334 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
10335 FolderItem *folder_item)
10337 g_return_if_fail(compose != NULL);
10339 if (compose->gtkaspell && folder_item && folder_item->prefs) {
10340 FolderItemPrefs *prefs = folder_item->prefs;
10342 if (prefs->enable_default_dictionary)
10343 gtkaspell_change_dict(compose->gtkaspell,
10344 prefs->default_dictionary, FALSE);
10345 if (folder_item->prefs->enable_default_alt_dictionary)
10346 gtkaspell_change_alt_dict(compose->gtkaspell,
10347 prefs->default_alt_dictionary);
10348 if (prefs->enable_default_dictionary
10349 || prefs->enable_default_alt_dictionary)
10350 compose_spell_menu_changed(compose);