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,
238 const gchar *mailto);
239 static gint compose_parse_header (Compose *compose,
241 static gchar *compose_parse_references (const gchar *ref,
244 static gchar *compose_quote_fmt (Compose *compose,
250 gboolean need_unescape,
251 const gchar *err_msg);
253 static void compose_reply_set_entry (Compose *compose,
259 followup_and_reply_to);
260 static void compose_reedit_set_entry (Compose *compose,
263 static void compose_insert_sig (Compose *compose,
265 static gchar *compose_get_signature_str (Compose *compose);
266 static ComposeInsertResult compose_insert_file (Compose *compose,
269 static gboolean compose_attach_append (Compose *compose,
272 const gchar *content_type);
273 static void compose_attach_parts (Compose *compose,
276 static gboolean compose_beautify_paragraph (Compose *compose,
277 GtkTextIter *par_iter,
279 static void compose_wrap_all (Compose *compose);
280 static void compose_wrap_all_full (Compose *compose,
283 static void compose_set_title (Compose *compose);
284 static void compose_select_account (Compose *compose,
285 PrefsAccount *account,
288 static PrefsAccount *compose_current_mail_account(void);
289 /* static gint compose_send (Compose *compose); */
290 static gboolean compose_check_for_valid_recipient
292 static gboolean compose_check_entries (Compose *compose,
293 gboolean check_everything);
294 static gint compose_write_to_file (Compose *compose,
297 gboolean attach_parts);
298 static gint compose_write_body_to_file (Compose *compose,
300 static gint compose_remove_reedit_target (Compose *compose,
302 static void compose_remove_draft (Compose *compose);
303 static gint compose_queue_sub (Compose *compose,
307 gboolean check_subject,
308 gboolean remove_reedit_target);
309 static void compose_add_attachments (Compose *compose,
311 static gchar *compose_get_header (Compose *compose);
313 static void compose_convert_header (Compose *compose,
318 gboolean addr_field);
320 static void compose_attach_info_free (AttachInfo *ainfo);
321 static void compose_attach_remove_selected (Compose *compose);
323 static void compose_attach_property (Compose *compose);
324 static void compose_attach_property_create (gboolean *cancelled);
325 static void attach_property_ok (GtkWidget *widget,
326 gboolean *cancelled);
327 static void attach_property_cancel (GtkWidget *widget,
328 gboolean *cancelled);
329 static gint attach_property_delete_event (GtkWidget *widget,
331 gboolean *cancelled);
332 static gboolean attach_property_key_pressed (GtkWidget *widget,
334 gboolean *cancelled);
336 static void compose_exec_ext_editor (Compose *compose);
338 static gint compose_exec_ext_editor_real (const gchar *file);
339 static gboolean compose_ext_editor_kill (Compose *compose);
340 static gboolean compose_input_cb (GIOChannel *source,
341 GIOCondition condition,
343 static void compose_set_ext_editor_sensitive (Compose *compose,
345 #endif /* G_OS_UNIX */
347 static void compose_undo_state_changed (UndoMain *undostruct,
352 static void compose_create_header_entry (Compose *compose);
353 static void compose_add_header_entry (Compose *compose, const gchar *header, gchar *text);
354 static void compose_remove_header_entries(Compose *compose);
356 static void compose_update_priority_menu_item(Compose * compose);
358 static void compose_spell_menu_changed (void *data);
360 static void compose_add_field_list ( Compose *compose,
361 GList *listAddress );
363 /* callback functions */
365 static gboolean compose_edit_size_alloc (GtkEditable *widget,
366 GtkAllocation *allocation,
367 GtkSHRuler *shruler);
368 static void account_activated (GtkComboBox *optmenu,
370 static void attach_selected (GtkTreeView *tree_view,
371 GtkTreePath *tree_path,
372 GtkTreeViewColumn *column,
374 static gboolean attach_button_pressed (GtkWidget *widget,
375 GdkEventButton *event,
377 static gboolean attach_key_pressed (GtkWidget *widget,
380 static void compose_send_cb (gpointer data,
383 static void compose_send_later_cb (gpointer data,
387 static void compose_draft_cb (gpointer data,
391 static void compose_attach_cb (gpointer data,
394 static void compose_insert_file_cb (gpointer data,
397 static void compose_insert_sig_cb (gpointer data,
401 static void compose_close_cb (gpointer data,
405 static void compose_set_encoding_cb (gpointer data,
409 static void compose_address_cb (gpointer data,
412 static void compose_template_activate_cb(GtkWidget *widget,
415 static void compose_ext_editor_cb (gpointer data,
419 static gint compose_delete_cb (GtkWidget *widget,
423 static void compose_undo_cb (Compose *compose);
424 static void compose_redo_cb (Compose *compose);
425 static void compose_cut_cb (Compose *compose);
426 static void compose_copy_cb (Compose *compose);
427 static void compose_paste_cb (Compose *compose);
428 static void compose_paste_as_quote_cb (Compose *compose);
429 static void compose_paste_no_wrap_cb (Compose *compose);
430 static void compose_paste_wrap_cb (Compose *compose);
431 static void compose_allsel_cb (Compose *compose);
433 static void compose_advanced_action_cb (Compose *compose,
434 ComposeCallAdvancedAction action);
436 static void compose_grab_focus_cb (GtkWidget *widget,
439 static void compose_changed_cb (GtkTextBuffer *textbuf,
442 static void compose_wrap_cb (gpointer data,
445 static void compose_find_cb (gpointer data,
448 static void compose_toggle_autowrap_cb (gpointer data,
452 static void compose_toggle_ruler_cb (gpointer data,
455 static void compose_toggle_sign_cb (gpointer data,
458 static void compose_toggle_encrypt_cb (gpointer data,
461 static void compose_set_privacy_system_cb(GtkWidget *widget,
463 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn);
464 static void activate_privacy_system (Compose *compose,
465 PrefsAccount *account,
467 static void compose_use_signing(Compose *compose, gboolean use_signing);
468 static void compose_use_encryption(Compose *compose, gboolean use_encryption);
469 static void compose_toggle_return_receipt_cb(gpointer data, guint action,
471 static void compose_toggle_remove_refs_cb(gpointer data, guint action,
473 static void compose_set_priority_cb (gpointer data,
476 static void compose_reply_change_mode (gpointer data,
480 static void compose_attach_drag_received_cb (GtkWidget *widget,
481 GdkDragContext *drag_context,
484 GtkSelectionData *data,
488 static void compose_insert_drag_received_cb (GtkWidget *widget,
489 GdkDragContext *drag_context,
492 GtkSelectionData *data,
496 static void compose_header_drag_received_cb (GtkWidget *widget,
497 GdkDragContext *drag_context,
500 GtkSelectionData *data,
505 static gboolean compose_drag_drop (GtkWidget *widget,
506 GdkDragContext *drag_context,
508 guint time, gpointer user_data);
510 static void text_inserted (GtkTextBuffer *buffer,
515 static Compose *compose_generic_reply(MsgInfo *msginfo,
516 ComposeQuoteMode quote_mode,
520 gboolean followup_and_reply_to,
523 static gboolean compose_headerentry_changed_cb (GtkWidget *entry,
524 ComposeHeaderEntry *headerentry);
525 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
527 ComposeHeaderEntry *headerentry);
529 static void compose_show_first_last_header (Compose *compose, gboolean show_first);
531 static void compose_allow_user_actions (Compose *compose, gboolean allow);
534 static void compose_check_all (Compose *compose);
535 static void compose_highlight_all (Compose *compose);
536 static void compose_check_backwards (Compose *compose);
537 static void compose_check_forwards_go (Compose *compose);
540 static gint compose_defer_auto_save_draft (Compose *compose);
541 static PrefsAccount *compose_guess_forward_account_from_msginfo (MsgInfo *msginfo);
543 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose);
546 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
547 FolderItem *folder_item);
550 static GtkItemFactoryEntry compose_popup_entries[] =
552 {N_("/_Add..."), NULL, compose_attach_cb, 0, NULL},
553 {N_("/_Remove"), NULL, compose_attach_remove_selected, 0, NULL},
554 {"/---", NULL, NULL, 0, "<Separator>"},
555 {N_("/_Properties..."), NULL, compose_attach_property, 0, NULL}
558 static GtkItemFactoryEntry compose_entries[] =
560 {N_("/_Message"), NULL, NULL, 0, "<Branch>"},
561 {N_("/_Message/S_end"), "<control>Return",
562 compose_send_cb, 0, NULL},
563 {N_("/_Message/Send _later"), "<shift><control>S",
564 compose_send_later_cb, 0, NULL},
565 {N_("/_Message/---"), NULL, NULL, 0, "<Separator>"},
566 {N_("/_Message/_Attach file"), "<control>M", compose_attach_cb, 0, NULL},
567 {N_("/_Message/_Insert file"), "<control>I", compose_insert_file_cb, 0, NULL},
568 {N_("/_Message/Insert si_gnature"), "<control>G", compose_insert_sig_cb, 0, NULL},
569 {N_("/_Message/---"), NULL, NULL, 0, "<Separator>"},
570 {N_("/_Message/_Save"),
571 "<control>S", compose_draft_cb, COMPOSE_KEEP_EDITING, NULL},
572 {N_("/_Message/---"), NULL, NULL, 0, "<Separator>"},
573 {N_("/_Message/_Close"), "<control>W", compose_close_cb, 0, NULL},
575 {N_("/_Edit"), NULL, NULL, 0, "<Branch>"},
576 {N_("/_Edit/_Undo"), "<control>Z", compose_undo_cb, 0, NULL},
577 {N_("/_Edit/_Redo"), "<control>Y", compose_redo_cb, 0, NULL},
578 {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
579 {N_("/_Edit/Cu_t"), "<control>X", compose_cut_cb, 0, NULL},
580 {N_("/_Edit/_Copy"), "<control>C", compose_copy_cb, 0, NULL},
581 {N_("/_Edit/_Paste"), "<control>V", compose_paste_cb, 0, NULL},
582 {N_("/_Edit/Special paste"), NULL, NULL, 0, "<Branch>"},
583 {N_("/_Edit/Special paste/as _quotation"),
584 NULL, compose_paste_as_quote_cb, 0, NULL},
585 {N_("/_Edit/Special paste/_wrapped"),
586 NULL, compose_paste_wrap_cb, 0, NULL},
587 {N_("/_Edit/Special paste/_unwrapped"),
588 NULL, compose_paste_no_wrap_cb, 0, NULL},
589 {N_("/_Edit/Select _all"), "<control>A", compose_allsel_cb, 0, NULL},
590 {N_("/_Edit/A_dvanced"), NULL, NULL, 0, "<Branch>"},
591 {N_("/_Edit/A_dvanced/Move a character backward"),
593 compose_advanced_action_cb,
594 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER,
596 {N_("/_Edit/A_dvanced/Move a character forward"),
598 compose_advanced_action_cb,
599 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER,
601 {N_("/_Edit/A_dvanced/Move a word backward"),
603 compose_advanced_action_cb,
604 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD,
606 {N_("/_Edit/A_dvanced/Move a word forward"),
608 compose_advanced_action_cb,
609 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD,
611 {N_("/_Edit/A_dvanced/Move to beginning of line"),
612 NULL, /* "<control>A" */
613 compose_advanced_action_cb,
614 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE,
616 {N_("/_Edit/A_dvanced/Move to end of line"),
618 compose_advanced_action_cb,
619 COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE,
621 {N_("/_Edit/A_dvanced/Move to previous line"),
623 compose_advanced_action_cb,
624 COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE,
626 {N_("/_Edit/A_dvanced/Move to next line"),
628 compose_advanced_action_cb,
629 COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE,
631 {N_("/_Edit/A_dvanced/Delete a character backward"),
633 compose_advanced_action_cb,
634 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER,
636 {N_("/_Edit/A_dvanced/Delete a character forward"),
638 compose_advanced_action_cb,
639 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER,
641 {N_("/_Edit/A_dvanced/Delete a word backward"),
642 NULL, /* "<control>W" */
643 compose_advanced_action_cb,
644 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD,
646 {N_("/_Edit/A_dvanced/Delete a word forward"),
647 NULL, /* "<alt>D", */
648 compose_advanced_action_cb,
649 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD,
651 {N_("/_Edit/A_dvanced/Delete line"),
653 compose_advanced_action_cb,
654 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE,
656 {N_("/_Edit/A_dvanced/Delete entire line"),
658 compose_advanced_action_cb,
659 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE_N,
661 {N_("/_Edit/A_dvanced/Delete to end of line"),
663 compose_advanced_action_cb,
664 COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END,
666 {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
668 "<control>F", compose_find_cb, 0, NULL},
669 {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
670 {N_("/_Edit/_Wrap current paragraph"),
671 "<control>L", compose_wrap_cb, 0, NULL},
672 {N_("/_Edit/Wrap all long _lines"),
673 "<control><alt>L", compose_wrap_cb, 1, NULL},
674 {N_("/_Edit/Aut_o wrapping"), "<shift><control>L", compose_toggle_autowrap_cb, 0, "<ToggleItem>"},
675 {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
676 {N_("/_Edit/Edit with e_xternal editor"),
677 "<shift><control>X", compose_ext_editor_cb, 0, NULL},
679 {N_("/_Spelling"), NULL, NULL, 0, "<Branch>"},
680 {N_("/_Spelling/_Check all or check selection"),
681 NULL, compose_check_all, 0, NULL},
682 {N_("/_Spelling/_Highlight all misspelled words"),
683 NULL, compose_highlight_all, 0, NULL},
684 {N_("/_Spelling/Check _backwards misspelled word"),
685 NULL, compose_check_backwards , 0, NULL},
686 {N_("/_Spelling/_Forward to next misspelled word"),
687 NULL, compose_check_forwards_go, 0, NULL},
688 {N_("/_Spelling/---"), NULL, NULL, 0, "<Separator>"},
689 {N_("/_Spelling/Options"),
690 NULL, NULL, 0, "<Branch>"},
692 {N_("/_Options"), NULL, NULL, 0, "<Branch>"},
693 {N_("/_Options/Reply _mode"), NULL, NULL, 0, "<Branch>"},
694 {N_("/_Options/Reply _mode/_Normal"), NULL, compose_reply_change_mode, COMPOSE_REPLY, "<RadioItem>"},
695 {N_("/_Options/Reply _mode/_All"), NULL, compose_reply_change_mode, COMPOSE_REPLY_TO_ALL, "/Options/Reply mode/Normal"},
696 {N_("/_Options/Reply _mode/_Sender"), NULL, compose_reply_change_mode, COMPOSE_REPLY_TO_SENDER, "/Options/Reply mode/Normal"},
697 {N_("/_Options/Reply _mode/_Mailing-list"), NULL, compose_reply_change_mode, COMPOSE_REPLY_TO_LIST, "/Options/Reply mode/Normal"},
698 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
699 {N_("/_Options/Privacy _System"), NULL, NULL, 0, "<Branch>"},
700 {N_("/_Options/Privacy _System/None"), NULL, NULL, 0, "<RadioItem>"},
701 {N_("/_Options/Si_gn"), NULL, compose_toggle_sign_cb , 0, "<ToggleItem>"},
702 {N_("/_Options/_Encrypt"), NULL, compose_toggle_encrypt_cb, 0, "<ToggleItem>"},
703 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
704 {N_("/_Options/_Priority"), NULL, NULL, 0, "<Branch>"},
705 {N_("/_Options/Priority/_Highest"), NULL, compose_set_priority_cb, PRIORITY_HIGHEST, "<RadioItem>"},
706 {N_("/_Options/Priority/Hi_gh"), NULL, compose_set_priority_cb, PRIORITY_HIGH, "/Options/Priority/Highest"},
707 {N_("/_Options/Priority/_Normal"), NULL, compose_set_priority_cb, PRIORITY_NORMAL, "/Options/Priority/Highest"},
708 {N_("/_Options/Priority/Lo_w"), NULL, compose_set_priority_cb, PRIORITY_LOW, "/Options/Priority/Highest"},
709 {N_("/_Options/Priority/_Lowest"), NULL, compose_set_priority_cb, PRIORITY_LOWEST, "/Options/Priority/Highest"},
710 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
711 {N_("/_Options/_Request Return Receipt"), NULL, compose_toggle_return_receipt_cb, 0, "<ToggleItem>"},
712 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
713 {N_("/_Options/Remo_ve references"), NULL, compose_toggle_remove_refs_cb, 0, "<ToggleItem>"},
714 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
716 #define ENC_ACTION(action) \
717 NULL, compose_set_encoding_cb, action, \
718 "/Options/Character encoding/Automatic"
720 {N_("/_Options/Character _encoding"), NULL, NULL, 0, "<Branch>"},
721 {N_("/_Options/Character _encoding/_Automatic"),
722 NULL, compose_set_encoding_cb, C_AUTO, "<RadioItem>"},
723 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
725 {N_("/_Options/Character _encoding/7bit ascii (US-ASC_II)"),
726 ENC_ACTION(C_US_ASCII)},
727 {N_("/_Options/Character _encoding/Unicode (_UTF-8)"),
728 ENC_ACTION(C_UTF_8)},
729 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
731 {N_("/_Options/Character _encoding/Western European (ISO-8859-_1)"),
732 ENC_ACTION(C_ISO_8859_1)},
733 {N_("/_Options/Character _encoding/Western European (ISO-8859-15)"),
734 ENC_ACTION(C_ISO_8859_15)},
735 {N_("/_Options/Character _encoding/Western European (Windows-1252)"),
736 ENC_ACTION(C_WINDOWS_1252)},
737 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
739 {N_("/_Options/Character _encoding/Central European (ISO-8859-_2)"),
740 ENC_ACTION(C_ISO_8859_2)},
741 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
743 {N_("/_Options/Character _encoding/_Baltic (ISO-8859-13)"),
744 ENC_ACTION(C_ISO_8859_13)},
745 {N_("/_Options/Character _encoding/Baltic (ISO-8859-_4)"),
746 ENC_ACTION(C_ISO_8859_4)},
747 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
749 {N_("/_Options/Character _encoding/Greek (ISO-8859-_7)"),
750 ENC_ACTION(C_ISO_8859_7)},
751 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
753 {N_("/_Options/Character _encoding/Hebrew (ISO-8859-_8)"),
754 ENC_ACTION(C_ISO_8859_8)},
755 {N_("/_Options/Character _encoding/Hebrew (Windows-1255)"),
756 ENC_ACTION(C_WINDOWS_1255)},
757 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
759 {N_("/_Options/Character _encoding/Arabic (ISO-8859-_6)"),
760 ENC_ACTION(C_ISO_8859_6)},
761 {N_("/_Options/Character _encoding/Arabic (Windows-1256)"),
762 ENC_ACTION(C_CP1256)},
763 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
765 {N_("/_Options/Character _encoding/Turkish (ISO-8859-_9)"),
766 ENC_ACTION(C_ISO_8859_9)},
767 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
769 {N_("/_Options/Character _encoding/Cyrillic (ISO-8859-_5)"),
770 ENC_ACTION(C_ISO_8859_5)},
771 {N_("/_Options/Character _encoding/Cyrillic (KOI8-_R)"),
772 ENC_ACTION(C_KOI8_R)},
773 {N_("/_Options/Character _encoding/Cyrillic (KOI8-U)"),
774 ENC_ACTION(C_KOI8_U)},
775 {N_("/_Options/Character _encoding/Cyrillic (Windows-1251)"),
776 ENC_ACTION(C_WINDOWS_1251)},
777 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
779 {N_("/_Options/Character _encoding/Japanese (ISO-2022-_JP)"),
780 ENC_ACTION(C_ISO_2022_JP)},
781 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
783 {N_("/_Options/Character _encoding/Simplified Chinese (_GB2312)"),
784 ENC_ACTION(C_GB2312)},
785 {N_("/_Options/Character _encoding/Simplified Chinese (GBK)"),
787 {N_("/_Options/Character _encoding/Traditional Chinese (_Big5)"),
789 {N_("/_Options/Character _encoding/Traditional Chinese (EUC-_TW)"),
790 ENC_ACTION(C_EUC_TW)},
791 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
793 {N_("/_Options/Character _encoding/Korean (EUC-_KR)"),
794 ENC_ACTION(C_EUC_KR)},
795 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
797 {N_("/_Options/Character _encoding/Thai (TIS-620)"),
798 ENC_ACTION(C_TIS_620)},
799 {N_("/_Options/Character _encoding/Thai (Windows-874)"),
800 ENC_ACTION(C_WINDOWS_874)},
802 {N_("/_Tools"), NULL, NULL, 0, "<Branch>"},
803 {N_("/_Tools/Show _ruler"), NULL, compose_toggle_ruler_cb, 0, "<ToggleItem>"},
804 {N_("/_Tools/_Address book"), "<shift><control>A", compose_address_cb , 0, NULL},
805 {N_("/_Tools/_Template"), NULL, NULL, 0, "<Branch>"},
806 {N_("/_Tools/Actio_ns"), NULL, NULL, 0, "<Branch>"},
807 {N_("/_Help"), NULL, NULL, 0, "<Branch>"},
808 {N_("/_Help/_About"), NULL, about_show, 0, NULL}
811 static GtkTargetEntry compose_mime_types[] =
813 {"text/uri-list", 0, 0},
814 {"UTF8_STRING", 0, 0},
818 static gboolean compose_put_existing_to_front(MsgInfo *info)
820 GList *compose_list = compose_get_compose_list();
824 for (elem = compose_list; elem != NULL && elem->data != NULL;
826 Compose *c = (Compose*)elem->data;
828 if (!c->targetinfo || !c->targetinfo->msgid ||
832 if (!strcmp(c->targetinfo->msgid, info->msgid)) {
833 gtkut_window_popup(c->window);
841 static GdkColor quote_color1 =
842 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
843 static GdkColor quote_color2 =
844 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
845 static GdkColor quote_color3 =
846 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
848 static GdkColor quote_bgcolor1 =
849 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
850 static GdkColor quote_bgcolor2 =
851 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
852 static GdkColor quote_bgcolor3 =
853 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
855 static GdkColor signature_color = {
862 static GdkColor uri_color = {
869 static void compose_create_tags(GtkTextView *text, Compose *compose)
871 GtkTextBuffer *buffer;
872 GdkColor black = {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
878 buffer = gtk_text_view_get_buffer(text);
880 if (prefs_common.enable_color) {
881 /* grab the quote colors, converting from an int to a GdkColor */
882 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
884 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
886 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
888 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_bgcol,
890 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_bgcol,
892 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_bgcol,
894 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
896 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
899 signature_color = quote_color1 = quote_color2 = quote_color3 =
900 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
903 if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
904 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
905 "foreground-gdk", "e_color1,
906 "paragraph-background-gdk", "e_bgcolor1,
908 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
909 "foreground-gdk", "e_color2,
910 "paragraph-background-gdk", "e_bgcolor2,
912 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
913 "foreground-gdk", "e_color3,
914 "paragraph-background-gdk", "e_bgcolor3,
917 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
918 "foreground-gdk", "e_color1,
920 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
921 "foreground-gdk", "e_color2,
923 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
924 "foreground-gdk", "e_color3,
928 compose->signature_tag = gtk_text_buffer_create_tag(buffer, "signature",
929 "foreground-gdk", &signature_color,
932 compose->uri_tag = gtk_text_buffer_create_tag(buffer, "link",
933 "foreground-gdk", &uri_color,
935 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
936 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
938 color[0] = quote_color1;
939 color[1] = quote_color2;
940 color[2] = quote_color3;
941 color[3] = quote_bgcolor1;
942 color[4] = quote_bgcolor2;
943 color[5] = quote_bgcolor3;
944 color[6] = signature_color;
945 color[7] = uri_color;
946 cmap = gdk_drawable_get_colormap(compose->window->window);
947 gdk_colormap_alloc_colors(cmap, color, 8, FALSE, TRUE, success);
949 for (i = 0; i < 8; i++) {
950 if (success[i] == FALSE) {
953 g_warning("Compose: color allocation failed.\n");
954 style = gtk_widget_get_style(GTK_WIDGET(text));
955 quote_color1 = quote_color2 = quote_color3 =
956 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 =
957 signature_color = uri_color = black;
962 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
963 GPtrArray *attach_files)
965 return compose_generic_new(account, mailto, NULL, attach_files, NULL);
968 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
970 return compose_generic_new(account, mailto, item, NULL, NULL);
973 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
975 return compose_generic_new( account, NULL, NULL, NULL, listAddress );
978 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
979 GPtrArray *attach_files, GList *listAddress )
982 GtkTextView *textview;
983 GtkTextBuffer *textbuf;
985 GtkItemFactory *ifactory;
986 const gchar *subject_format = NULL;
987 const gchar *body_format = NULL;
989 if (item && item->prefs && item->prefs->enable_default_account)
990 account = account_find_from_id(item->prefs->default_account);
992 if (!account) account = cur_account;
993 g_return_val_if_fail(account != NULL, NULL);
995 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
997 ifactory = gtk_item_factory_from_widget(compose->menubar);
999 compose->replyinfo = NULL;
1000 compose->fwdinfo = NULL;
1002 textview = GTK_TEXT_VIEW(compose->text);
1003 textbuf = gtk_text_view_get_buffer(textview);
1004 compose_create_tags(textview, compose);
1006 undo_block(compose->undostruct);
1008 compose_set_dictionaries_from_folder_prefs(compose, item);
1011 if (account->auto_sig)
1012 compose_insert_sig(compose, FALSE);
1013 gtk_text_buffer_get_start_iter(textbuf, &iter);
1014 gtk_text_buffer_place_cursor(textbuf, &iter);
1016 if (account->protocol != A_NNTP) {
1017 if (mailto && *mailto != '\0') {
1018 compose_entries_set(compose, mailto);
1020 } else if (item && item->prefs->enable_default_to) {
1021 compose_entry_append(compose, item->prefs->default_to, COMPOSE_TO);
1022 compose_entry_mark_default_to(compose, item->prefs->default_to);
1024 if (item && item->ret_rcpt) {
1025 menu_set_active(ifactory, "/Options/Request Return Receipt", TRUE);
1029 compose_entry_append(compose, mailto, COMPOSE_NEWSGROUPS);
1030 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1031 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS);
1034 * CLAWS: just don't allow return receipt request, even if the user
1035 * may want to send an email. simple but foolproof.
1037 menu_set_sensitive(ifactory, "/Options/Request Return Receipt", FALSE);
1039 compose_add_field_list( compose, listAddress );
1041 if (item && item->prefs && item->prefs->compose_with_format) {
1042 subject_format = item->prefs->compose_subject_format;
1043 body_format = item->prefs->compose_body_format;
1044 } else if (account->compose_with_format) {
1045 subject_format = account->compose_subject_format;
1046 body_format = account->compose_body_format;
1047 } else if (prefs_common.compose_with_format) {
1048 subject_format = prefs_common.compose_subject_format;
1049 body_format = prefs_common.compose_body_format;
1052 if (subject_format || body_format) {
1053 MsgInfo* dummyinfo = NULL;
1056 && *subject_format != '\0' )
1058 gchar *subject = NULL;
1062 dummyinfo = compose_msginfo_new_from_compose(compose);
1064 /* decode \-escape sequences in the internal representation of the quote format */
1065 tmp = malloc(strlen(subject_format)+1);
1066 pref_get_unescaped_pref(tmp, subject_format);
1068 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1070 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account,
1071 compose->gtkaspell);
1073 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account);
1075 quote_fmt_scan_string(tmp);
1078 buf = quote_fmt_get_buffer();
1080 alertpanel_error(_("New message subject format error."));
1082 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1083 quote_fmt_reset_vartable();
1090 && *body_format != '\0' )
1093 GtkTextBuffer *buffer;
1094 GtkTextIter start, end;
1097 if ( dummyinfo == NULL )
1098 dummyinfo = compose_msginfo_new_from_compose(compose);
1100 text = GTK_TEXT_VIEW(compose->text);
1101 buffer = gtk_text_view_get_buffer(text);
1102 gtk_text_buffer_get_start_iter(buffer, &start);
1103 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1104 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1106 compose_quote_fmt(compose, dummyinfo,
1108 NULL, tmp, FALSE, TRUE,
1109 _("New message body format error at line %d."));
1110 quote_fmt_reset_vartable();
1115 procmsg_msginfo_free( dummyinfo );
1122 for (i = 0; i < attach_files->len; i++) {
1123 file = g_ptr_array_index(attach_files, i);
1124 compose_attach_append(compose, file, file, NULL);
1128 compose_show_first_last_header(compose, TRUE);
1130 /* Set save folder */
1131 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1132 gchar *folderidentifier;
1134 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
1135 folderidentifier = folder_item_get_identifier(item);
1136 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
1137 g_free(folderidentifier);
1140 gtk_widget_grab_focus(compose->header_last->entry);
1142 undo_unblock(compose->undostruct);
1144 if (prefs_common.auto_exteditor)
1145 compose_exec_ext_editor(compose);
1147 compose->draft_timeout_tag = -1;
1148 compose->modified = FALSE;
1149 compose_set_title(compose);
1153 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1154 gboolean override_pref)
1156 gchar *privacy = NULL;
1158 g_return_if_fail(compose != NULL);
1159 g_return_if_fail(account != NULL);
1161 if (override_pref == FALSE && account->default_encrypt_reply == FALSE)
1164 if (account->default_privacy_system
1165 && strlen(account->default_privacy_system)) {
1166 privacy = account->default_privacy_system;
1168 GSList *privacy_avail = privacy_get_system_ids();
1169 if (privacy_avail && g_slist_length(privacy_avail)) {
1170 privacy = (gchar *)(privacy_avail->data);
1173 if (privacy != NULL) {
1174 if (compose->privacy_system == NULL)
1175 compose->privacy_system = g_strdup(privacy);
1176 compose_update_privacy_system_menu_item(compose, FALSE);
1177 compose_use_encryption(compose, TRUE);
1181 static void compose_force_signing(Compose *compose, PrefsAccount *account)
1183 gchar *privacy = NULL;
1185 if (account->default_privacy_system
1186 && strlen(account->default_privacy_system)) {
1187 privacy = account->default_privacy_system;
1189 GSList *privacy_avail = privacy_get_system_ids();
1190 if (privacy_avail && g_slist_length(privacy_avail)) {
1191 privacy = (gchar *)(privacy_avail->data);
1194 if (privacy != NULL) {
1195 if (compose->privacy_system == NULL)
1196 compose->privacy_system = g_strdup(privacy);
1197 compose_update_privacy_system_menu_item(compose, FALSE);
1198 compose_use_signing(compose, TRUE);
1202 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1206 Compose *compose = NULL;
1207 GtkItemFactory *ifactory = NULL;
1209 g_return_val_if_fail(msginfo_list != NULL, NULL);
1211 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1212 g_return_val_if_fail(msginfo != NULL, NULL);
1214 list_len = g_slist_length(msginfo_list);
1218 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1219 FALSE, prefs_common.default_reply_list, FALSE, body);
1221 case COMPOSE_REPLY_WITH_QUOTE:
1222 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1223 FALSE, prefs_common.default_reply_list, FALSE, body);
1225 case COMPOSE_REPLY_WITHOUT_QUOTE:
1226 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1227 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1229 case COMPOSE_REPLY_TO_SENDER:
1230 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1231 FALSE, FALSE, TRUE, body);
1233 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1234 compose = compose_followup_and_reply_to(msginfo,
1235 COMPOSE_QUOTE_CHECK,
1236 FALSE, FALSE, body);
1238 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1239 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1240 FALSE, FALSE, TRUE, body);
1242 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1243 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1244 FALSE, FALSE, TRUE, NULL);
1246 case COMPOSE_REPLY_TO_ALL:
1247 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1248 TRUE, FALSE, FALSE, body);
1250 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1251 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1252 TRUE, FALSE, FALSE, body);
1254 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1255 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1256 TRUE, FALSE, FALSE, NULL);
1258 case COMPOSE_REPLY_TO_LIST:
1259 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1260 FALSE, TRUE, FALSE, body);
1262 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1263 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1264 FALSE, TRUE, FALSE, body);
1266 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1267 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1268 FALSE, TRUE, FALSE, NULL);
1270 case COMPOSE_FORWARD:
1271 if (prefs_common.forward_as_attachment) {
1272 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1275 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1279 case COMPOSE_FORWARD_INLINE:
1280 /* check if we reply to more than one Message */
1281 if (list_len == 1) {
1282 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1285 /* more messages FALL THROUGH */
1286 case COMPOSE_FORWARD_AS_ATTACH:
1287 compose = compose_forward_multiple(NULL, msginfo_list);
1289 case COMPOSE_REDIRECT:
1290 compose = compose_redirect(NULL, msginfo, FALSE);
1293 g_warning("compose_reply_mode(): invalid Compose Mode: %d\n", mode);
1296 ifactory = gtk_item_factory_from_widget(compose->menubar);
1298 compose->rmode = mode;
1299 switch (compose->rmode) {
1301 case COMPOSE_REPLY_WITH_QUOTE:
1302 case COMPOSE_REPLY_WITHOUT_QUOTE:
1303 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1304 debug_print("reply mode Normal\n");
1305 menu_set_active(ifactory, "/Options/Reply mode/Normal", TRUE);
1306 compose_reply_change_mode(compose, COMPOSE_REPLY, NULL); /* force update */
1308 case COMPOSE_REPLY_TO_SENDER:
1309 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1310 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1311 debug_print("reply mode Sender\n");
1312 menu_set_active(ifactory, "/Options/Reply mode/Sender", TRUE);
1314 case COMPOSE_REPLY_TO_ALL:
1315 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1316 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1317 debug_print("reply mode All\n");
1318 menu_set_active(ifactory, "/Options/Reply mode/All", TRUE);
1320 case COMPOSE_REPLY_TO_LIST:
1321 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1322 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1323 debug_print("reply mode List\n");
1324 menu_set_active(ifactory, "/Options/Reply mode/Mailing-list", TRUE);
1332 static Compose *compose_reply(MsgInfo *msginfo,
1333 ComposeQuoteMode quote_mode,
1339 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1340 to_sender, FALSE, body);
1343 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1344 ComposeQuoteMode quote_mode,
1349 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1350 to_sender, TRUE, body);
1353 static void compose_extract_original_charset(Compose *compose)
1355 MsgInfo *info = NULL;
1356 if (compose->replyinfo) {
1357 info = compose->replyinfo;
1358 } else if (compose->fwdinfo) {
1359 info = compose->fwdinfo;
1360 } else if (compose->targetinfo) {
1361 info = compose->targetinfo;
1364 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1365 MimeInfo *partinfo = mimeinfo;
1366 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1367 partinfo = procmime_mimeinfo_next(partinfo);
1369 compose->orig_charset =
1370 g_strdup(procmime_mimeinfo_get_parameter(
1371 partinfo, "charset"));
1373 procmime_mimeinfo_free_all(mimeinfo);
1377 #define SIGNAL_BLOCK(buffer) { \
1378 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1379 G_CALLBACK(compose_changed_cb), \
1381 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1382 G_CALLBACK(text_inserted), \
1386 #define SIGNAL_UNBLOCK(buffer) { \
1387 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1388 G_CALLBACK(compose_changed_cb), \
1390 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1391 G_CALLBACK(text_inserted), \
1395 static Compose *compose_generic_reply(MsgInfo *msginfo,
1396 ComposeQuoteMode quote_mode,
1397 gboolean to_all, gboolean to_ml,
1399 gboolean followup_and_reply_to,
1402 GtkItemFactory *ifactory;
1404 PrefsAccount *account = NULL;
1405 GtkTextView *textview;
1406 GtkTextBuffer *textbuf;
1407 gboolean quote = FALSE;
1408 const gchar *qmark = NULL;
1409 const gchar *body_fmt = NULL;
1411 g_return_val_if_fail(msginfo != NULL, NULL);
1412 g_return_val_if_fail(msginfo->folder != NULL, NULL);
1414 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1416 g_return_val_if_fail(account != NULL, NULL);
1418 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1420 compose->updating = TRUE;
1422 ifactory = gtk_item_factory_from_widget(compose->menubar);
1424 menu_set_active(ifactory, "/Options/Remove references", FALSE);
1425 menu_set_sensitive(ifactory, "/Options/Remove references", TRUE);
1427 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1428 if (!compose->replyinfo)
1429 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1431 compose_extract_original_charset(compose);
1433 if (msginfo->folder && msginfo->folder->ret_rcpt)
1434 menu_set_active(ifactory, "/Options/Request Return Receipt", TRUE);
1436 /* Set save folder */
1437 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1438 gchar *folderidentifier;
1440 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1441 folderidentifier = folder_item_get_identifier(msginfo->folder);
1442 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
1443 g_free(folderidentifier);
1446 if (compose_parse_header(compose, msginfo) < 0) return NULL;
1448 textview = (GTK_TEXT_VIEW(compose->text));
1449 textbuf = gtk_text_view_get_buffer(textview);
1450 compose_create_tags(textview, compose);
1452 undo_block(compose->undostruct);
1454 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1457 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1458 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1459 /* use the reply format of folder (if enabled), or the account's one
1460 (if enabled) or fallback to the global reply format, which is always
1461 enabled (even if empty), and use the relevant quotemark */
1463 if (msginfo->folder && msginfo->folder->prefs &&
1464 msginfo->folder->prefs->reply_with_format) {
1465 qmark = msginfo->folder->prefs->reply_quotemark;
1466 body_fmt = msginfo->folder->prefs->reply_body_format;
1468 } else if (account->reply_with_format) {
1469 qmark = account->reply_quotemark;
1470 body_fmt = account->reply_body_format;
1473 qmark = prefs_common.quotemark;
1474 body_fmt = prefs_common.quotefmt;
1479 /* empty quotemark is not allowed */
1480 if (qmark == NULL || *qmark == '\0')
1482 compose_quote_fmt(compose, compose->replyinfo,
1483 body_fmt, qmark, body, FALSE, TRUE,
1484 _("Message reply format error at line %d."));
1485 quote_fmt_reset_vartable();
1488 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1489 compose_force_encryption(compose, account, FALSE);
1492 SIGNAL_BLOCK(textbuf);
1494 if (account->auto_sig)
1495 compose_insert_sig(compose, FALSE);
1497 compose_wrap_all(compose);
1499 SIGNAL_UNBLOCK(textbuf);
1501 gtk_widget_grab_focus(compose->text);
1503 undo_unblock(compose->undostruct);
1505 if (prefs_common.auto_exteditor)
1506 compose_exec_ext_editor(compose);
1508 compose->modified = FALSE;
1509 compose_set_title(compose);
1511 compose->updating = FALSE;
1512 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1514 if (compose->deferred_destroy) {
1515 compose_destroy(compose);
1522 #define INSERT_FW_HEADER(var, hdr) \
1523 if (msginfo->var && *msginfo->var) { \
1524 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1525 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1526 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1529 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1530 gboolean as_attach, const gchar *body,
1531 gboolean no_extedit,
1535 GtkTextView *textview;
1536 GtkTextBuffer *textbuf;
1539 g_return_val_if_fail(msginfo != NULL, NULL);
1540 g_return_val_if_fail(msginfo->folder != NULL, NULL);
1543 !(account = compose_guess_forward_account_from_msginfo
1545 account = cur_account;
1547 compose = compose_create(account, msginfo->folder, COMPOSE_FORWARD, batch);
1549 compose->updating = TRUE;
1550 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1551 if (!compose->fwdinfo)
1552 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1554 compose_extract_original_charset(compose);
1556 if (msginfo->subject && *msginfo->subject) {
1557 gchar *buf, *buf2, *p;
1559 buf = p = g_strdup(msginfo->subject);
1560 p += subject_get_prefix_length(p);
1561 memmove(buf, p, strlen(p) + 1);
1563 buf2 = g_strdup_printf("Fw: %s", buf);
1564 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1570 textview = GTK_TEXT_VIEW(compose->text);
1571 textbuf = gtk_text_view_get_buffer(textview);
1572 compose_create_tags(textview, compose);
1574 undo_block(compose->undostruct);
1578 msgfile = procmsg_get_message_file(msginfo);
1579 if (!is_file_exist(msgfile))
1580 g_warning("%s: file not exist\n", msgfile);
1582 compose_attach_append(compose, msgfile, msgfile,
1587 const gchar *qmark = NULL;
1588 const gchar *body_fmt = prefs_common.fw_quotefmt;
1589 MsgInfo *full_msginfo;
1591 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1593 full_msginfo = procmsg_msginfo_copy(msginfo);
1595 /* use the forward format of folder (if enabled), or the account's one
1596 (if enabled) or fallback to the global forward format, which is always
1597 enabled (even if empty), and use the relevant quotemark */
1598 if (msginfo->folder && msginfo->folder->prefs &&
1599 msginfo->folder->prefs->forward_with_format) {
1600 qmark = msginfo->folder->prefs->forward_quotemark;
1601 body_fmt = msginfo->folder->prefs->forward_body_format;
1603 } else if (account->forward_with_format) {
1604 qmark = account->forward_quotemark;
1605 body_fmt = account->forward_body_format;
1608 qmark = prefs_common.fw_quotemark;
1609 body_fmt = prefs_common.fw_quotefmt;
1612 /* empty quotemark is not allowed */
1613 if (qmark == NULL || *qmark == '\0')
1616 compose_quote_fmt(compose, full_msginfo,
1617 body_fmt, qmark, body, FALSE, TRUE,
1618 _("Message forward format error at line %d."));
1619 quote_fmt_reset_vartable();
1620 compose_attach_parts(compose, msginfo);
1622 procmsg_msginfo_free(full_msginfo);
1625 SIGNAL_BLOCK(textbuf);
1627 if (account->auto_sig)
1628 compose_insert_sig(compose, FALSE);
1630 compose_wrap_all(compose);
1632 SIGNAL_UNBLOCK(textbuf);
1634 gtk_text_buffer_get_start_iter(textbuf, &iter);
1635 gtk_text_buffer_place_cursor(textbuf, &iter);
1637 gtk_widget_grab_focus(compose->header_last->entry);
1639 if (!no_extedit && prefs_common.auto_exteditor)
1640 compose_exec_ext_editor(compose);
1643 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1644 gchar *folderidentifier;
1646 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1647 folderidentifier = folder_item_get_identifier(msginfo->folder);
1648 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
1649 g_free(folderidentifier);
1652 undo_unblock(compose->undostruct);
1654 compose->modified = FALSE;
1655 compose_set_title(compose);
1657 compose->updating = FALSE;
1658 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1660 if (compose->deferred_destroy) {
1661 compose_destroy(compose);
1668 #undef INSERT_FW_HEADER
1670 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1673 GtkTextView *textview;
1674 GtkTextBuffer *textbuf;
1678 gboolean single_mail = TRUE;
1680 g_return_val_if_fail(msginfo_list != NULL, NULL);
1682 if (g_slist_length(msginfo_list) > 1)
1683 single_mail = FALSE;
1685 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1686 if (((MsgInfo *)msginfo->data)->folder == NULL)
1689 /* guess account from first selected message */
1691 !(account = compose_guess_forward_account_from_msginfo
1692 (msginfo_list->data)))
1693 account = cur_account;
1695 g_return_val_if_fail(account != NULL, NULL);
1697 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1698 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1699 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1702 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1704 compose->updating = TRUE;
1706 textview = GTK_TEXT_VIEW(compose->text);
1707 textbuf = gtk_text_view_get_buffer(textview);
1708 compose_create_tags(textview, compose);
1710 undo_block(compose->undostruct);
1711 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1712 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
1714 if (!is_file_exist(msgfile))
1715 g_warning("%s: file not exist\n", msgfile);
1717 compose_attach_append(compose, msgfile, msgfile,
1723 MsgInfo *info = (MsgInfo *)msginfo_list->data;
1724 if (info->subject && *info->subject) {
1725 gchar *buf, *buf2, *p;
1727 buf = p = g_strdup(info->subject);
1728 p += subject_get_prefix_length(p);
1729 memmove(buf, p, strlen(p) + 1);
1731 buf2 = g_strdup_printf("Fw: %s", buf);
1732 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1738 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
1739 _("Fw: multiple emails"));
1742 SIGNAL_BLOCK(textbuf);
1744 if (account->auto_sig)
1745 compose_insert_sig(compose, FALSE);
1747 compose_wrap_all(compose);
1749 SIGNAL_UNBLOCK(textbuf);
1751 gtk_text_buffer_get_start_iter(textbuf, &iter);
1752 gtk_text_buffer_place_cursor(textbuf, &iter);
1754 gtk_widget_grab_focus(compose->header_last->entry);
1755 undo_unblock(compose->undostruct);
1756 compose->modified = FALSE;
1757 compose_set_title(compose);
1759 compose->updating = FALSE;
1760 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1762 if (compose->deferred_destroy) {
1763 compose_destroy(compose);
1770 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
1772 GtkTextIter start = *iter;
1773 GtkTextIter end_iter;
1774 int start_pos = gtk_text_iter_get_offset(&start);
1776 if (!compose->account->sig_sep)
1779 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
1780 start_pos+strlen(compose->account->sig_sep));
1782 /* check sig separator */
1783 str = gtk_text_iter_get_text(&start, &end_iter);
1784 if (!strcmp(str, compose->account->sig_sep)) {
1786 /* check end of line (\n) */
1787 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
1788 start_pos+strlen(compose->account->sig_sep));
1789 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
1790 start_pos+strlen(compose->account->sig_sep)+1);
1791 tmp = gtk_text_iter_get_text(&start, &end_iter);
1792 if (!strcmp(tmp,"\n")) {
1804 static void compose_colorize_signature(Compose *compose)
1806 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
1808 GtkTextIter end_iter;
1809 gtk_text_buffer_get_start_iter(buffer, &iter);
1810 while (gtk_text_iter_forward_line(&iter))
1811 if (compose_is_sig_separator(compose, buffer, &iter)) {
1812 gtk_text_buffer_get_end_iter(buffer, &end_iter);
1813 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
1817 #define BLOCK_WRAP() { \
1818 prev_autowrap = compose->autowrap; \
1819 buffer = gtk_text_view_get_buffer( \
1820 GTK_TEXT_VIEW(compose->text)); \
1821 compose->autowrap = FALSE; \
1823 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1824 G_CALLBACK(compose_changed_cb), \
1826 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1827 G_CALLBACK(text_inserted), \
1830 #define UNBLOCK_WRAP() { \
1831 compose->autowrap = prev_autowrap; \
1832 if (compose->autowrap) { \
1833 gint old = compose->draft_timeout_tag; \
1834 compose->draft_timeout_tag = -2; \
1835 compose_wrap_all(compose); \
1836 compose->draft_timeout_tag = old; \
1839 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1840 G_CALLBACK(compose_changed_cb), \
1842 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1843 G_CALLBACK(text_inserted), \
1847 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
1849 Compose *compose = NULL;
1850 PrefsAccount *account = NULL;
1851 GtkTextView *textview;
1852 GtkTextBuffer *textbuf;
1856 gchar buf[BUFFSIZE];
1857 gboolean use_signing = FALSE;
1858 gboolean use_encryption = FALSE;
1859 gchar *privacy_system = NULL;
1860 int priority = PRIORITY_NORMAL;
1861 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
1863 g_return_val_if_fail(msginfo != NULL, NULL);
1864 g_return_val_if_fail(msginfo->folder != NULL, NULL);
1866 if (compose_put_existing_to_front(msginfo)) {
1870 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
1871 folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
1872 gchar queueheader_buf[BUFFSIZE];
1875 /* Select Account from queue headers */
1876 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1877 sizeof(queueheader_buf), "X-Claws-Account-Id:")) {
1878 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
1879 account = account_find_from_id(id);
1881 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1882 sizeof(queueheader_buf), "X-Sylpheed-Account-Id:")) {
1883 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
1884 account = account_find_from_id(id);
1886 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1887 sizeof(queueheader_buf), "NAID:")) {
1888 id = atoi(&queueheader_buf[strlen("NAID:")]);
1889 account = account_find_from_id(id);
1891 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1892 sizeof(queueheader_buf), "MAID:")) {
1893 id = atoi(&queueheader_buf[strlen("MAID:")]);
1894 account = account_find_from_id(id);
1896 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1897 sizeof(queueheader_buf), "S:")) {
1898 account = account_find_from_address(queueheader_buf);
1900 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1901 sizeof(queueheader_buf), "X-Claws-Sign:")) {
1902 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
1903 use_signing = param;
1906 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1907 sizeof(queueheader_buf), "X-Sylpheed-Sign:")) {
1908 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
1909 use_signing = param;
1912 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1913 sizeof(queueheader_buf), "X-Claws-Encrypt:")) {
1914 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
1915 use_encryption = param;
1917 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1918 sizeof(queueheader_buf), "X-Sylpheed-Encrypt:")) {
1919 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
1920 use_encryption = param;
1922 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1923 sizeof(queueheader_buf), "X-Claws-Privacy-System:")) {
1924 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
1926 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1927 sizeof(queueheader_buf), "X-Sylpheed-Privacy-System:")) {
1928 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
1930 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1931 sizeof(queueheader_buf), "X-Priority: ")) {
1932 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
1935 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1936 sizeof(queueheader_buf), "RMID:")) {
1937 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
1938 if (tokens[0] && tokens[1] && tokens[2]) {
1939 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
1940 if (orig_item != NULL) {
1941 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
1946 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1947 sizeof(queueheader_buf), "FMID:")) {
1948 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
1949 if (tokens[0] && tokens[1] && tokens[2]) {
1950 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
1951 if (orig_item != NULL) {
1952 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
1958 account = msginfo->folder->folder->account;
1961 if (!account && prefs_common.reedit_account_autosel) {
1962 gchar from[BUFFSIZE];
1963 if (!procheader_get_header_from_msginfo(msginfo, from, sizeof(from), "FROM:")) {
1964 extract_address(from);
1965 account = account_find_from_address(from);
1969 account = cur_account;
1971 g_return_val_if_fail(account != NULL, NULL);
1973 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
1975 compose->replyinfo = replyinfo;
1976 compose->fwdinfo = fwdinfo;
1978 compose->updating = TRUE;
1979 compose->priority = priority;
1981 if (privacy_system != NULL) {
1982 compose->privacy_system = privacy_system;
1983 compose_use_signing(compose, use_signing);
1984 compose_use_encryption(compose, use_encryption);
1985 compose_update_privacy_system_menu_item(compose, FALSE);
1987 activate_privacy_system(compose, account, FALSE);
1990 compose->targetinfo = procmsg_msginfo_copy(msginfo);
1992 compose_extract_original_charset(compose);
1994 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
1995 folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
1996 gchar queueheader_buf[BUFFSIZE];
1998 /* Set message save folder */
1999 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "SCF:")) {
2002 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2003 gtk_editable_delete_text(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
2004 gtk_editable_insert_text(GTK_EDITABLE(compose->savemsg_entry), &queueheader_buf[4], strlen(&queueheader_buf[4]), &startpos);
2006 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "RRCPT:")) {
2007 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2009 GtkItemFactory *ifactory;
2010 ifactory = gtk_item_factory_from_widget(compose->menubar);
2011 menu_set_active(ifactory, "/Options/Request Return Receipt", TRUE);
2016 if (compose_parse_header(compose, msginfo) < 0) {
2017 compose->updating = FALSE;
2018 compose_destroy(compose);
2021 compose_reedit_set_entry(compose, msginfo);
2023 textview = GTK_TEXT_VIEW(compose->text);
2024 textbuf = gtk_text_view_get_buffer(textview);
2025 compose_create_tags(textview, compose);
2027 mark = gtk_text_buffer_get_insert(textbuf);
2028 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2030 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2031 G_CALLBACK(compose_changed_cb),
2034 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2035 fp = procmime_get_first_encrypted_text_content(msginfo);
2037 compose_force_encryption(compose, account, TRUE);
2040 fp = procmime_get_first_text_content(msginfo);
2043 g_warning("Can't get text part\n");
2047 gboolean prev_autowrap = compose->autowrap;
2048 GtkTextBuffer *buffer = textbuf;
2050 while (fgets(buf, sizeof(buf), fp) != NULL) {
2052 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2058 compose_attach_parts(compose, msginfo);
2060 compose_colorize_signature(compose);
2062 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2063 G_CALLBACK(compose_changed_cb),
2066 gtk_widget_grab_focus(compose->text);
2068 if (prefs_common.auto_exteditor) {
2069 compose_exec_ext_editor(compose);
2071 compose->modified = FALSE;
2072 compose_set_title(compose);
2074 compose->updating = FALSE;
2075 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2077 if (compose->deferred_destroy) {
2078 compose_destroy(compose);
2082 compose->sig_str = compose_get_signature_str(compose);
2087 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2092 GtkItemFactory *ifactory;
2095 g_return_val_if_fail(msginfo != NULL, NULL);
2098 account = account_get_reply_account(msginfo,
2099 prefs_common.reply_account_autosel);
2100 g_return_val_if_fail(account != NULL, NULL);
2102 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2104 compose->updating = TRUE;
2106 ifactory = gtk_item_factory_from_widget(compose->menubar);
2107 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2108 compose->replyinfo = NULL;
2109 compose->fwdinfo = NULL;
2111 compose_show_first_last_header(compose, TRUE);
2113 gtk_widget_grab_focus(compose->header_last->entry);
2115 filename = procmsg_get_message_file(msginfo);
2117 if (filename == NULL) {
2118 compose->updating = FALSE;
2119 compose_destroy(compose);
2124 compose->redirect_filename = filename;
2126 /* Set save folder */
2127 item = msginfo->folder;
2128 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2129 gchar *folderidentifier;
2131 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
2132 folderidentifier = folder_item_get_identifier(item);
2133 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
2134 g_free(folderidentifier);
2137 compose_attach_parts(compose, msginfo);
2139 if (msginfo->subject)
2140 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2142 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2144 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2145 _("Message redirect format error at line %d."));
2146 quote_fmt_reset_vartable();
2147 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2149 compose_colorize_signature(compose);
2151 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
2152 menu_set_sensitive(ifactory, "/Add...", FALSE);
2153 menu_set_sensitive(ifactory, "/Remove", FALSE);
2154 menu_set_sensitive(ifactory, "/Properties...", FALSE);
2156 ifactory = gtk_item_factory_from_widget(compose->menubar);
2157 menu_set_sensitive(ifactory, "/Message/Save", FALSE);
2158 menu_set_sensitive(ifactory, "/Message/Insert file", FALSE);
2159 menu_set_sensitive(ifactory, "/Message/Attach file", FALSE);
2160 menu_set_sensitive(ifactory, "/Message/Insert signature", FALSE);
2161 menu_set_sensitive(ifactory, "/Edit", FALSE);
2162 menu_set_sensitive(ifactory, "/Options", FALSE);
2163 menu_set_sensitive(ifactory, "/Tools/Show ruler", FALSE);
2164 menu_set_sensitive(ifactory, "/Tools/Actions", FALSE);
2166 if (compose->toolbar->draft_btn)
2167 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2168 if (compose->toolbar->insert_btn)
2169 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2170 if (compose->toolbar->attach_btn)
2171 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2172 if (compose->toolbar->sig_btn)
2173 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2174 if (compose->toolbar->exteditor_btn)
2175 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2176 if (compose->toolbar->linewrap_current_btn)
2177 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2178 if (compose->toolbar->linewrap_all_btn)
2179 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2181 compose->modified = FALSE;
2182 compose_set_title(compose);
2183 compose->updating = FALSE;
2184 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2186 if (compose->deferred_destroy) {
2187 compose_destroy(compose);
2194 GList *compose_get_compose_list(void)
2196 return compose_list;
2199 void compose_entry_append(Compose *compose, const gchar *address,
2200 ComposeEntryType type)
2202 const gchar *header;
2204 gboolean in_quote = FALSE;
2205 if (!address || *address == '\0') return;
2212 header = N_("Bcc:");
2214 case COMPOSE_REPLYTO:
2215 header = N_("Reply-To:");
2217 case COMPOSE_NEWSGROUPS:
2218 header = N_("Newsgroups:");
2220 case COMPOSE_FOLLOWUPTO:
2221 header = N_( "Followup-To:");
2228 header = prefs_common_translated_header_name(header);
2230 cur = begin = (gchar *)address;
2232 /* we separate the line by commas, but not if we're inside a quoted
2234 while (*cur != '\0') {
2236 in_quote = !in_quote;
2237 if (*cur == ',' && !in_quote) {
2238 gchar *tmp = g_strdup(begin);
2240 tmp[cur-begin]='\0';
2243 while (*tmp == ' ' || *tmp == '\t')
2245 compose_add_header_entry(compose, header, tmp);
2252 gchar *tmp = g_strdup(begin);
2254 tmp[cur-begin]='\0';
2257 while (*tmp == ' ' || *tmp == '\t')
2259 compose_add_header_entry(compose, header, tmp);
2264 static void compose_entry_mark_default_to(Compose *compose, const gchar *mailto)
2266 static GdkColor yellow;
2267 static GdkColor black;
2268 static gboolean yellow_initialised = FALSE;
2272 if (!yellow_initialised) {
2273 gdk_color_parse("#f5f6be", &yellow);
2274 gdk_color_parse("#000000", &black);
2275 yellow_initialised = gdk_colormap_alloc_color(
2276 gdk_colormap_get_system(), &yellow, FALSE, TRUE);
2277 yellow_initialised &= gdk_colormap_alloc_color(
2278 gdk_colormap_get_system(), &black, FALSE, TRUE);
2281 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2282 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2283 if (gtk_entry_get_text(entry) &&
2284 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2285 if (yellow_initialised) {
2286 gtk_widget_modify_base(
2287 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2288 GTK_STATE_NORMAL, &yellow);
2289 gtk_widget_modify_text(
2290 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2291 GTK_STATE_NORMAL, &black);
2297 void compose_toolbar_cb(gint action, gpointer data)
2299 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2300 Compose *compose = (Compose*)toolbar_item->parent;
2302 g_return_if_fail(compose != NULL);
2306 compose_send_cb(compose, 0, NULL);
2309 compose_send_later_cb(compose, 0, NULL);
2312 compose_draft_cb(compose, COMPOSE_QUIT_EDITING, NULL);
2315 compose_insert_file_cb(compose, 0, NULL);
2318 compose_attach_cb(compose, 0, NULL);
2321 compose_insert_sig(compose, FALSE);
2324 compose_ext_editor_cb(compose, 0, NULL);
2326 case A_LINEWRAP_CURRENT:
2327 compose_beautify_paragraph(compose, NULL, TRUE);
2329 case A_LINEWRAP_ALL:
2330 compose_wrap_all_full(compose, TRUE);
2333 compose_address_cb(compose, 0, NULL);
2336 case A_CHECK_SPELLING:
2337 compose_check_all(compose);
2345 static void compose_entries_set(Compose *compose, const gchar *mailto)
2349 gchar *subject = NULL;
2353 gchar *attach = NULL;
2355 scan_mailto_url(mailto, &to, &cc, NULL, &subject, &body, &attach);
2358 compose_entry_append(compose, to, COMPOSE_TO);
2360 compose_entry_append(compose, cc, COMPOSE_CC);
2362 if (!g_utf8_validate (subject, -1, NULL)) {
2363 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2364 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2367 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2371 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2372 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2375 gboolean prev_autowrap = compose->autowrap;
2377 compose->autowrap = FALSE;
2379 mark = gtk_text_buffer_get_insert(buffer);
2380 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2382 if (!g_utf8_validate (body, -1, NULL)) {
2383 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2384 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2387 gtk_text_buffer_insert(buffer, &iter, body, -1);
2389 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2391 compose->autowrap = prev_autowrap;
2392 if (compose->autowrap)
2393 compose_wrap_all(compose);
2397 gchar *utf8_filename = conv_filename_to_utf8(attach);
2398 if (utf8_filename) {
2399 if (compose_attach_append(compose, attach, utf8_filename, NULL)) {
2400 alertpanel_notice(_("The file '%s' has been attached."), attach);
2402 g_free(utf8_filename);
2404 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2414 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2416 static HeaderEntry hentry[] = {{"Reply-To:", NULL, TRUE},
2417 {"Cc:", NULL, TRUE},
2418 {"References:", NULL, FALSE},
2419 {"Bcc:", NULL, TRUE},
2420 {"Newsgroups:", NULL, TRUE},
2421 {"Followup-To:", NULL, TRUE},
2422 {"List-Post:", NULL, FALSE},
2423 {"X-Priority:", NULL, FALSE},
2424 {NULL, NULL, FALSE}};
2440 g_return_val_if_fail(msginfo != NULL, -1);
2442 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2443 procheader_get_header_fields(fp, hentry);
2446 if (hentry[H_REPLY_TO].body != NULL) {
2447 if (hentry[H_REPLY_TO].body[0] != '\0') {
2449 conv_unmime_header(hentry[H_REPLY_TO].body,
2452 g_free(hentry[H_REPLY_TO].body);
2453 hentry[H_REPLY_TO].body = NULL;
2455 if (hentry[H_CC].body != NULL) {
2456 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL);
2457 g_free(hentry[H_CC].body);
2458 hentry[H_CC].body = NULL;
2460 if (hentry[H_REFERENCES].body != NULL) {
2461 if (compose->mode == COMPOSE_REEDIT)
2462 compose->references = hentry[H_REFERENCES].body;
2464 compose->references = compose_parse_references
2465 (hentry[H_REFERENCES].body, msginfo->msgid);
2466 g_free(hentry[H_REFERENCES].body);
2468 hentry[H_REFERENCES].body = NULL;
2470 if (hentry[H_BCC].body != NULL) {
2471 if (compose->mode == COMPOSE_REEDIT)
2473 conv_unmime_header(hentry[H_BCC].body, NULL);
2474 g_free(hentry[H_BCC].body);
2475 hentry[H_BCC].body = NULL;
2477 if (hentry[H_NEWSGROUPS].body != NULL) {
2478 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2479 hentry[H_NEWSGROUPS].body = NULL;
2481 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2482 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2483 compose->followup_to =
2484 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2487 g_free(hentry[H_FOLLOWUP_TO].body);
2488 hentry[H_FOLLOWUP_TO].body = NULL;
2490 if (hentry[H_LIST_POST].body != NULL) {
2493 extract_address(hentry[H_LIST_POST].body);
2494 if (hentry[H_LIST_POST].body[0] != '\0') {
2495 scan_mailto_url(hentry[H_LIST_POST].body,
2496 &to, NULL, NULL, NULL, NULL, NULL);
2498 g_free(compose->ml_post);
2499 compose->ml_post = to;
2502 g_free(hentry[H_LIST_POST].body);
2503 hentry[H_LIST_POST].body = NULL;
2506 /* CLAWS - X-Priority */
2507 if (compose->mode == COMPOSE_REEDIT)
2508 if (hentry[H_X_PRIORITY].body != NULL) {
2511 priority = atoi(hentry[H_X_PRIORITY].body);
2512 g_free(hentry[H_X_PRIORITY].body);
2514 hentry[H_X_PRIORITY].body = NULL;
2516 if (priority < PRIORITY_HIGHEST ||
2517 priority > PRIORITY_LOWEST)
2518 priority = PRIORITY_NORMAL;
2520 compose->priority = priority;
2523 if (compose->mode == COMPOSE_REEDIT) {
2524 if (msginfo->inreplyto && *msginfo->inreplyto)
2525 compose->inreplyto = g_strdup(msginfo->inreplyto);
2529 if (msginfo->msgid && *msginfo->msgid)
2530 compose->inreplyto = g_strdup(msginfo->msgid);
2532 if (!compose->references) {
2533 if (msginfo->msgid && *msginfo->msgid) {
2534 if (msginfo->inreplyto && *msginfo->inreplyto)
2535 compose->references =
2536 g_strdup_printf("<%s>\n\t<%s>",
2540 compose->references =
2541 g_strconcat("<", msginfo->msgid, ">",
2543 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
2544 compose->references =
2545 g_strconcat("<", msginfo->inreplyto, ">",
2553 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
2555 GSList *ref_id_list, *cur;
2559 ref_id_list = references_list_append(NULL, ref);
2560 if (!ref_id_list) return NULL;
2561 if (msgid && *msgid)
2562 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
2567 for (cur = ref_id_list; cur != NULL; cur = cur->next)
2568 /* "<" + Message-ID + ">" + CR+LF+TAB */
2569 len += strlen((gchar *)cur->data) + 5;
2571 if (len > MAX_REFERENCES_LEN) {
2572 /* remove second message-ID */
2573 if (ref_id_list && ref_id_list->next &&
2574 ref_id_list->next->next) {
2575 g_free(ref_id_list->next->data);
2576 ref_id_list = g_slist_remove
2577 (ref_id_list, ref_id_list->next->data);
2579 slist_free_strings(ref_id_list);
2580 g_slist_free(ref_id_list);
2587 new_ref = g_string_new("");
2588 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
2589 if (new_ref->len > 0)
2590 g_string_append(new_ref, "\n\t");
2591 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
2594 slist_free_strings(ref_id_list);
2595 g_slist_free(ref_id_list);
2597 new_ref_str = new_ref->str;
2598 g_string_free(new_ref, FALSE);
2603 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
2604 const gchar *fmt, const gchar *qmark,
2605 const gchar *body, gboolean rewrap,
2606 gboolean need_unescape,
2607 const gchar *err_msg)
2609 MsgInfo* dummyinfo = NULL;
2610 gchar *quote_str = NULL;
2612 gboolean prev_autowrap;
2613 const gchar *trimmed_body = body;
2614 gint cursor_pos = -1;
2615 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2616 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2621 SIGNAL_BLOCK(buffer);
2624 dummyinfo = compose_msginfo_new_from_compose(compose);
2625 msginfo = dummyinfo;
2628 if (qmark != NULL) {
2630 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
2631 compose->gtkaspell);
2633 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
2635 quote_fmt_scan_string(qmark);
2638 buf = quote_fmt_get_buffer();
2640 alertpanel_error(_("Quote mark format error."));
2642 Xstrdup_a(quote_str, buf, goto error)
2645 if (fmt && *fmt != '\0') {
2648 while (*trimmed_body == '\n')
2652 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account,
2653 compose->gtkaspell);
2655 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account);
2657 if (need_unescape) {
2660 /* decode \-escape sequences in the internal representation of the quote format */
2661 tmp = malloc(strlen(fmt)+1);
2662 pref_get_unescaped_pref(tmp, fmt);
2663 quote_fmt_scan_string(tmp);
2667 quote_fmt_scan_string(fmt);
2671 buf = quote_fmt_get_buffer();
2673 gint line = quote_fmt_get_line();
2674 alertpanel_error(err_msg, line);
2680 prev_autowrap = compose->autowrap;
2681 compose->autowrap = FALSE;
2683 mark = gtk_text_buffer_get_insert(buffer);
2684 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2685 if (g_utf8_validate(buf, -1, NULL)) {
2686 gtk_text_buffer_insert(buffer, &iter, buf, -1);
2688 gchar *tmpout = NULL;
2689 tmpout = conv_codeset_strdup
2690 (buf, conv_get_locale_charset_str_no_utf8(),
2692 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
2694 tmpout = g_malloc(strlen(buf)*2+1);
2695 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
2697 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
2701 cursor_pos = quote_fmt_get_cursor_pos();
2702 compose->set_cursor_pos = cursor_pos;
2703 if (cursor_pos == -1) {
2706 gtk_text_buffer_get_start_iter(buffer, &iter);
2707 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
2708 gtk_text_buffer_place_cursor(buffer, &iter);
2710 compose->autowrap = prev_autowrap;
2711 if (compose->autowrap && rewrap)
2712 compose_wrap_all(compose);
2719 SIGNAL_UNBLOCK(buffer);
2721 procmsg_msginfo_free( dummyinfo );
2726 /* if ml_post is of type addr@host and from is of type
2727 * addr-anything@host, return TRUE
2729 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
2731 gchar *left_ml = NULL;
2732 gchar *right_ml = NULL;
2733 gchar *left_from = NULL;
2734 gchar *right_from = NULL;
2735 gboolean result = FALSE;
2737 if (!ml_post || !from)
2740 left_ml = g_strdup(ml_post);
2741 if (strstr(left_ml, "@")) {
2742 right_ml = strstr(left_ml, "@")+1;
2743 *(strstr(left_ml, "@")) = '\0';
2746 left_from = g_strdup(from);
2747 if (strstr(left_from, "@")) {
2748 right_from = strstr(left_from, "@")+1;
2749 *(strstr(left_from, "@")) = '\0';
2752 if (left_ml && left_from && right_ml && right_from
2753 && !strncmp(left_from, left_ml, strlen(left_ml))
2754 && !strcmp(right_from, right_ml)) {
2763 static gboolean same_address(const gchar *addr1, const gchar *addr2)
2765 gchar *my_addr1, *my_addr2;
2767 if (!addr1 || !addr2)
2770 Xstrdup_a(my_addr1, addr1, return FALSE);
2771 Xstrdup_a(my_addr2, addr2, return FALSE);
2773 extract_address(my_addr1);
2774 extract_address(my_addr2);
2776 return !strcasecmp(my_addr1, my_addr2);
2779 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
2780 gboolean to_all, gboolean to_ml,
2782 gboolean followup_and_reply_to)
2784 GSList *cc_list = NULL;
2787 gchar *replyto = NULL;
2788 GHashTable *to_table;
2790 gboolean reply_to_ml = FALSE;
2791 gboolean default_reply_to = FALSE;
2793 g_return_if_fail(compose->account != NULL);
2794 g_return_if_fail(msginfo != NULL);
2796 reply_to_ml = to_ml && compose->ml_post;
2798 default_reply_to = msginfo->folder &&
2799 msginfo->folder->prefs->enable_default_reply_to;
2801 if (compose->account->protocol != A_NNTP) {
2802 if (reply_to_ml && !default_reply_to) {
2804 gboolean is_subscr = is_subscription(compose->ml_post,
2807 /* normal answer to ml post with a reply-to */
2808 compose_entry_append(compose,
2811 if (compose->replyto
2812 && !same_address(compose->ml_post, compose->replyto))
2813 compose_entry_append(compose,
2817 /* answer to subscription confirmation */
2818 if (compose->replyto)
2819 compose_entry_append(compose,
2822 else if (msginfo->from)
2823 compose_entry_append(compose,
2828 else if (!(to_all || to_sender) && default_reply_to) {
2829 compose_entry_append(compose,
2830 msginfo->folder->prefs->default_reply_to,
2832 compose_entry_mark_default_to(compose,
2833 msginfo->folder->prefs->default_reply_to);
2838 Xstrdup_a(tmp1, msginfo->from, return);
2839 extract_address(tmp1);
2840 if (to_all || to_sender ||
2841 !account_find_from_address(tmp1))
2842 compose_entry_append(compose,
2843 (compose->replyto && !to_sender)
2844 ? compose->replyto :
2845 msginfo->from ? msginfo->from : "",
2847 else if (!to_all && !to_sender) {
2848 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
2849 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
2850 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
2851 compose_entry_append(compose,
2852 msginfo->from ? msginfo->from : "",
2855 /* replying to own mail, use original recp */
2856 compose_entry_append(compose,
2857 msginfo->to ? msginfo->to : "",
2859 compose_entry_append(compose,
2860 msginfo->cc ? msginfo->cc : "",
2866 if (to_sender || (compose->followup_to &&
2867 !strncmp(compose->followup_to, "poster", 6)))
2868 compose_entry_append
2870 (compose->replyto ? compose->replyto :
2871 msginfo->from ? msginfo->from : ""),
2874 else if (followup_and_reply_to || to_all) {
2875 compose_entry_append
2877 (compose->replyto ? compose->replyto :
2878 msginfo->from ? msginfo->from : ""),
2881 compose_entry_append
2883 compose->followup_to ? compose->followup_to :
2884 compose->newsgroups ? compose->newsgroups : "",
2885 COMPOSE_NEWSGROUPS);
2888 compose_entry_append
2890 compose->followup_to ? compose->followup_to :
2891 compose->newsgroups ? compose->newsgroups : "",
2892 COMPOSE_NEWSGROUPS);
2895 if (msginfo->subject && *msginfo->subject) {
2899 buf = p = g_strdup(msginfo->subject);
2900 p += subject_get_prefix_length(p);
2901 memmove(buf, p, strlen(p) + 1);
2903 buf2 = g_strdup_printf("Re: %s", buf);
2904 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
2909 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
2911 if (to_ml && compose->ml_post) return;
2912 if (!to_all || compose->account->protocol == A_NNTP) return;
2914 if (compose->replyto) {
2915 Xstrdup_a(replyto, compose->replyto, return);
2916 extract_address(replyto);
2918 if (msginfo->from) {
2919 Xstrdup_a(from, msginfo->from, return);
2920 extract_address(from);
2923 if (replyto && from)
2924 cc_list = address_list_append_with_comments(cc_list, from);
2925 if (to_all && msginfo->folder &&
2926 msginfo->folder->prefs->enable_default_reply_to)
2927 cc_list = address_list_append_with_comments(cc_list,
2928 msginfo->folder->prefs->default_reply_to);
2929 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
2930 cc_list = address_list_append_with_comments(cc_list, compose->cc);
2932 to_table = g_hash_table_new(g_str_hash, g_str_equal);
2934 g_hash_table_insert(to_table, g_utf8_strdown(replyto, -1), GINT_TO_POINTER(1));
2935 if (compose->account) {
2936 g_hash_table_insert(to_table, g_utf8_strdown(compose->account->address, -1),
2937 GINT_TO_POINTER(1));
2939 /* remove address on To: and that of current account */
2940 for (cur = cc_list; cur != NULL; ) {
2941 GSList *next = cur->next;
2944 addr = g_utf8_strdown(cur->data, -1);
2945 extract_address(addr);
2947 if (GPOINTER_TO_INT(g_hash_table_lookup(to_table, addr)) == 1)
2948 cc_list = g_slist_remove(cc_list, cur->data);
2950 g_hash_table_insert(to_table, addr, GINT_TO_POINTER(1));
2954 hash_free_strings(to_table);
2955 g_hash_table_destroy(to_table);
2958 for (cur = cc_list; cur != NULL; cur = cur->next)
2959 compose_entry_append(compose, (gchar *)cur->data,
2961 slist_free_strings(cc_list);
2962 g_slist_free(cc_list);
2967 #define SET_ENTRY(entry, str) \
2970 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
2973 #define SET_ADDRESS(type, str) \
2976 compose_entry_append(compose, str, type); \
2979 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
2981 g_return_if_fail(msginfo != NULL);
2983 SET_ENTRY(subject_entry, msginfo->subject);
2984 SET_ENTRY(from_name, msginfo->from);
2985 SET_ADDRESS(COMPOSE_TO, msginfo->to);
2986 SET_ADDRESS(COMPOSE_CC, compose->cc);
2987 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
2988 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
2989 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
2990 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
2992 compose_update_priority_menu_item(compose);
2993 compose_update_privacy_system_menu_item(compose, FALSE);
2994 compose_show_first_last_header(compose, TRUE);
3000 static void compose_insert_sig(Compose *compose, gboolean replace)
3002 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3003 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3005 GtkTextIter iter, iter_end;
3007 gboolean prev_autowrap;
3008 gboolean found = FALSE;
3009 gboolean exists = FALSE;
3011 g_return_if_fail(compose->account != NULL);
3015 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3016 G_CALLBACK(compose_changed_cb),
3019 mark = gtk_text_buffer_get_insert(buffer);
3020 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3021 cur_pos = gtk_text_iter_get_offset (&iter);
3023 gtk_text_buffer_get_end_iter(buffer, &iter);
3025 exists = (compose->sig_str != NULL);
3028 GtkTextIter first_iter, start_iter, end_iter;
3030 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3032 if (!exists || compose->sig_str[0] == '\0')
3035 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3036 compose->signature_tag);
3039 /* include previous \n\n */
3040 gtk_text_iter_backward_chars(&first_iter, 2);
3041 start_iter = first_iter;
3042 end_iter = first_iter;
3044 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3045 compose->signature_tag);
3046 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3047 compose->signature_tag);
3049 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3055 g_free(compose->sig_str);
3056 compose->sig_str = compose_get_signature_str(compose);
3058 cur_pos = gtk_text_iter_get_offset(&iter);
3060 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3061 g_free(compose->sig_str);
3062 compose->sig_str = NULL;
3064 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3066 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3067 gtk_text_iter_forward_chars(&iter, 2);
3068 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3069 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3071 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3072 cur_pos = gtk_text_buffer_get_char_count (buffer);
3074 /* put the cursor where it should be
3075 * either where the quote_fmt says, either before the signature */
3076 if (compose->set_cursor_pos < 0)
3077 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3079 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3080 compose->set_cursor_pos);
3082 gtk_text_buffer_place_cursor(buffer, &iter);
3083 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3084 G_CALLBACK(compose_changed_cb),
3090 static gchar *compose_get_signature_str(Compose *compose)
3092 gchar *sig_body = NULL;
3093 gchar *sig_str = NULL;
3094 gchar *utf8_sig_str = NULL;
3096 g_return_val_if_fail(compose->account != NULL, NULL);
3098 if (!compose->account->sig_path)
3101 if (compose->account->sig_type == SIG_FILE) {
3102 if (!is_file_or_fifo_exist(compose->account->sig_path)) {
3103 g_warning("can't open signature file: %s\n",
3104 compose->account->sig_path);
3109 if (compose->account->sig_type == SIG_COMMAND)
3110 sig_body = get_command_output(compose->account->sig_path);
3114 tmp = file_read_to_str(compose->account->sig_path);
3117 sig_body = normalize_newlines(tmp);
3121 if (compose->account->sig_sep) {
3122 sig_str = g_strconcat("\n\n", compose->account->sig_sep, "\n", sig_body,
3126 sig_str = g_strconcat("\n\n", sig_body, NULL);
3129 if (g_utf8_validate(sig_str, -1, NULL) == TRUE)
3130 utf8_sig_str = sig_str;
3132 utf8_sig_str = conv_codeset_strdup
3133 (sig_str, conv_get_locale_charset_str_no_utf8(),
3139 return utf8_sig_str;
3142 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3145 GtkTextBuffer *buffer;
3148 const gchar *cur_encoding;
3149 gchar buf[BUFFSIZE];
3152 gboolean prev_autowrap;
3153 gboolean badtxt = FALSE;
3155 g_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3157 if ((fp = g_fopen(file, "rb")) == NULL) {
3158 FILE_OP_ERROR(file, "fopen");
3159 return COMPOSE_INSERT_READ_ERROR;
3162 prev_autowrap = compose->autowrap;
3163 compose->autowrap = FALSE;
3165 text = GTK_TEXT_VIEW(compose->text);
3166 buffer = gtk_text_view_get_buffer(text);
3167 mark = gtk_text_buffer_get_insert(buffer);
3168 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3170 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3171 G_CALLBACK(text_inserted),
3174 cur_encoding = conv_get_locale_charset_str_no_utf8();
3176 while (fgets(buf, sizeof(buf), fp) != NULL) {
3179 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3180 str = g_strdup(buf);
3182 str = conv_codeset_strdup
3183 (buf, cur_encoding, CS_INTERNAL);
3186 /* strip <CR> if DOS/Windows file,
3187 replace <CR> with <LF> if Macintosh file. */
3190 if (len > 0 && str[len - 1] != '\n') {
3192 if (str[len] == '\r') str[len] = '\n';
3195 gtk_text_buffer_insert(buffer, &iter, str, -1);
3199 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3200 G_CALLBACK(text_inserted),
3202 compose->autowrap = prev_autowrap;
3203 if (compose->autowrap)
3204 compose_wrap_all(compose);
3209 return COMPOSE_INSERT_INVALID_CHARACTER;
3211 return COMPOSE_INSERT_SUCCESS;
3214 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3215 const gchar *filename,
3216 const gchar *content_type)
3224 GtkListStore *store;
3226 gboolean has_binary = FALSE;
3228 if (!is_file_exist(file)) {
3229 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3230 gboolean result = FALSE;
3231 if (file_from_uri && is_file_exist(file_from_uri)) {
3232 result = compose_attach_append(
3233 compose, file_from_uri,
3237 g_free(file_from_uri);
3240 alertpanel_error("File %s doesn't exist\n", filename);
3243 if ((size = get_file_size(file)) < 0) {
3244 alertpanel_error("Can't get file size of %s\n", filename);
3248 alertpanel_error(_("File %s is empty."), filename);
3251 if ((fp = g_fopen(file, "rb")) == NULL) {
3252 alertpanel_error(_("Can't read %s."), filename);
3257 ainfo = g_new0(AttachInfo, 1);
3258 auto_ainfo = g_auto_pointer_new_with_free
3259 (ainfo, (GFreeFunc) compose_attach_info_free);
3260 ainfo->file = g_strdup(file);
3263 ainfo->content_type = g_strdup(content_type);
3264 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3266 MsgFlags flags = {0, 0};
3268 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3269 ainfo->encoding = ENC_7BIT;
3271 ainfo->encoding = ENC_8BIT;
3273 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3274 if (msginfo && msginfo->subject)
3275 name = g_strdup(msginfo->subject);
3277 name = g_path_get_basename(filename ? filename : file);
3279 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3281 procmsg_msginfo_free(msginfo);
3283 if (!g_ascii_strncasecmp(content_type, "text", 4))
3284 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3286 ainfo->encoding = ENC_BASE64;
3287 name = g_path_get_basename(filename ? filename : file);
3288 ainfo->name = g_strdup(name);
3292 ainfo->content_type = procmime_get_mime_type(file);
3293 if (!ainfo->content_type) {
3294 ainfo->content_type =
3295 g_strdup("application/octet-stream");
3296 ainfo->encoding = ENC_BASE64;
3297 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text", 4))
3299 procmime_get_encoding_for_text_file(file, &has_binary);
3301 ainfo->encoding = ENC_BASE64;
3302 name = g_path_get_basename(filename ? filename : file);
3303 ainfo->name = g_strdup(name);
3307 if (ainfo->name != NULL
3308 && !strcmp(ainfo->name, ".")) {
3309 g_free(ainfo->name);
3313 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3314 g_free(ainfo->content_type);
3315 ainfo->content_type = g_strdup("application/octet-stream");
3319 size_text = to_human_readable(size);
3321 store = GTK_LIST_STORE(gtk_tree_view_get_model
3322 (GTK_TREE_VIEW(compose->attach_clist)));
3324 gtk_list_store_append(store, &iter);
3325 gtk_list_store_set(store, &iter,
3326 COL_MIMETYPE, ainfo->content_type,
3327 COL_SIZE, size_text,
3328 COL_NAME, ainfo->name,
3330 COL_AUTODATA, auto_ainfo,
3333 g_auto_pointer_free(auto_ainfo);
3337 static void compose_use_signing(Compose *compose, gboolean use_signing)
3339 GtkItemFactory *ifactory;
3340 GtkWidget *menuitem = NULL;
3342 compose->use_signing = use_signing;
3343 ifactory = gtk_item_factory_from_widget(compose->menubar);
3344 menuitem = gtk_item_factory_get_item
3345 (ifactory, "/Options/Sign");
3346 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
3350 static void compose_use_encryption(Compose *compose, gboolean use_encryption)
3352 GtkItemFactory *ifactory;
3353 GtkWidget *menuitem = NULL;
3355 compose->use_encryption = use_encryption;
3356 ifactory = gtk_item_factory_from_widget(compose->menubar);
3357 menuitem = gtk_item_factory_get_item
3358 (ifactory, "/Options/Encrypt");
3360 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
3364 #define NEXT_PART_NOT_CHILD(info) \
3366 node = info->node; \
3367 while (node->children) \
3368 node = g_node_last_child(node); \
3369 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3372 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3376 MimeInfo *firsttext = NULL;
3377 MimeInfo *encrypted = NULL;
3380 const gchar *partname = NULL;
3382 mimeinfo = procmime_scan_message(msginfo);
3383 if (!mimeinfo) return;
3385 if (mimeinfo->node->children == NULL) {
3386 procmime_mimeinfo_free_all(mimeinfo);
3390 /* find first content part */
3391 child = (MimeInfo *) mimeinfo->node->children->data;
3392 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3393 child = (MimeInfo *)child->node->children->data;
3395 if (child->type == MIMETYPE_TEXT) {
3397 debug_print("First text part found\n");
3398 } else if (compose->mode == COMPOSE_REEDIT &&
3399 child->type == MIMETYPE_APPLICATION &&
3400 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3401 encrypted = (MimeInfo *)child->node->parent->data;
3404 child = (MimeInfo *) mimeinfo->node->children->data;
3405 while (child != NULL) {
3408 if (child == encrypted) {
3409 /* skip this part of tree */
3410 NEXT_PART_NOT_CHILD(child);
3414 if (child->type == MIMETYPE_MULTIPART) {
3415 /* get the actual content */
3416 child = procmime_mimeinfo_next(child);
3420 if (child == firsttext) {
3421 child = procmime_mimeinfo_next(child);
3425 outfile = procmime_get_tmp_file_name(child);
3426 if ((err = procmime_get_part(outfile, child)) < 0)
3427 g_warning("Can't get the part of multipart message. (%s)", strerror(-err));
3429 gchar *content_type;
3431 content_type = procmime_get_content_type_str(child->type, child->subtype);
3433 /* if we meet a pgp signature, we don't attach it, but
3434 * we force signing. */
3435 if ((strcmp(content_type, "application/pgp-signature") &&
3436 strcmp(content_type, "application/pkcs7-signature") &&
3437 strcmp(content_type, "application/x-pkcs7-signature"))
3438 || compose->mode == COMPOSE_REDIRECT) {
3439 partname = procmime_mimeinfo_get_parameter(child, "filename");
3440 if (partname == NULL)
3441 partname = procmime_mimeinfo_get_parameter(child, "name");
3442 if (partname == NULL)
3444 compose_attach_append(compose, outfile,
3445 partname, content_type);
3447 compose_force_signing(compose, compose->account);
3449 g_free(content_type);
3452 NEXT_PART_NOT_CHILD(child);
3454 procmime_mimeinfo_free_all(mimeinfo);
3457 #undef NEXT_PART_NOT_CHILD
3462 WAIT_FOR_INDENT_CHAR,
3463 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3466 /* return indent length, we allow:
3467 indent characters followed by indent characters or spaces/tabs,
3468 alphabets and numbers immediately followed by indent characters,
3469 and the repeating sequences of the above
3470 If quote ends with multiple spaces, only the first one is included. */
3471 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3472 const GtkTextIter *start, gint *len)
3474 GtkTextIter iter = *start;
3478 IndentState state = WAIT_FOR_INDENT_CHAR;
3481 gint alnum_count = 0;
3482 gint space_count = 0;
3485 if (prefs_common.quote_chars == NULL) {
3489 while (!gtk_text_iter_ends_line(&iter)) {
3490 wc = gtk_text_iter_get_char(&iter);
3491 if (g_unichar_iswide(wc))
3493 clen = g_unichar_to_utf8(wc, ch);
3497 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
3498 is_space = g_unichar_isspace(wc);
3500 if (state == WAIT_FOR_INDENT_CHAR) {
3501 if (!is_indent && !g_unichar_isalnum(wc))
3504 quote_len += alnum_count + space_count + 1;
3505 alnum_count = space_count = 0;
3506 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
3509 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
3510 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
3514 else if (is_indent) {
3515 quote_len += alnum_count + space_count + 1;
3516 alnum_count = space_count = 0;
3519 state = WAIT_FOR_INDENT_CHAR;
3523 gtk_text_iter_forward_char(&iter);
3526 if (quote_len > 0 && space_count > 0)
3532 if (quote_len > 0) {
3534 gtk_text_iter_forward_chars(&iter, quote_len);
3535 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
3541 /* return TRUE if the line is itemized */
3542 static gboolean compose_is_itemized(GtkTextBuffer *buffer,
3543 const GtkTextIter *start)
3545 GtkTextIter iter = *start;
3550 if (gtk_text_iter_ends_line(&iter))
3554 wc = gtk_text_iter_get_char(&iter);
3555 if (!g_unichar_isspace(wc))
3557 gtk_text_iter_forward_char(&iter);
3558 if (gtk_text_iter_ends_line(&iter))
3562 clen = g_unichar_to_utf8(wc, ch);
3566 if (!strchr("*-+", ch[0]))
3569 gtk_text_iter_forward_char(&iter);
3570 if (gtk_text_iter_ends_line(&iter))
3572 wc = gtk_text_iter_get_char(&iter);
3573 if (g_unichar_isspace(wc))
3579 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
3580 const GtkTextIter *start,
3581 GtkTextIter *break_pos,
3585 GtkTextIter iter = *start, line_end = *start;
3586 PangoLogAttr *attrs;
3593 gboolean can_break = FALSE;
3594 gboolean do_break = FALSE;
3595 gboolean was_white = FALSE;
3596 gboolean prev_dont_break = FALSE;
3598 gtk_text_iter_forward_to_line_end(&line_end);
3599 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
3600 len = g_utf8_strlen(str, -1);
3604 g_warning("compose_get_line_break_pos: len = 0!\n");
3608 /* g_print("breaking line: %d: %s (len = %d)\n",
3609 gtk_text_iter_get_line(&iter), str, len); */
3611 attrs = g_new(PangoLogAttr, len + 1);
3613 pango_default_break(str, -1, NULL, attrs, len + 1);
3617 /* skip quote and leading spaces */
3618 for (i = 0; *p != '\0' && i < len; i++) {
3621 wc = g_utf8_get_char(p);
3622 if (i >= quote_len && !g_unichar_isspace(wc))
3624 if (g_unichar_iswide(wc))
3626 else if (*p == '\t')
3630 p = g_utf8_next_char(p);
3633 for (; *p != '\0' && i < len; i++) {
3634 PangoLogAttr *attr = attrs + i;
3638 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
3641 was_white = attr->is_white;
3643 /* don't wrap URI */
3644 if ((uri_len = get_uri_len(p)) > 0) {
3646 if (pos > 0 && col > max_col) {
3656 wc = g_utf8_get_char(p);
3657 if (g_unichar_iswide(wc)) {
3659 if (prev_dont_break && can_break && attr->is_line_break)
3661 } else if (*p == '\t')
3665 if (pos > 0 && col > max_col) {
3670 if (*p == '-' || *p == '/')
3671 prev_dont_break = TRUE;
3673 prev_dont_break = FALSE;
3675 p = g_utf8_next_char(p);
3679 debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col);
3684 *break_pos = *start;
3685 gtk_text_iter_set_line_offset(break_pos, pos);
3690 static gboolean compose_join_next_line(Compose *compose,
3691 GtkTextBuffer *buffer,
3693 const gchar *quote_str)
3695 GtkTextIter iter_ = *iter, cur, prev, next, end;
3696 PangoLogAttr attrs[3];
3698 gchar *next_quote_str;
3701 gboolean keep_cursor = FALSE;
3703 if (!gtk_text_iter_forward_line(&iter_) ||
3704 gtk_text_iter_ends_line(&iter_))
3707 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
3709 if ((quote_str || next_quote_str) &&
3710 strcmp2(quote_str, next_quote_str) != 0) {
3711 g_free(next_quote_str);
3714 g_free(next_quote_str);
3717 if (quote_len > 0) {
3718 gtk_text_iter_forward_chars(&end, quote_len);
3719 if (gtk_text_iter_ends_line(&end))
3723 /* don't join itemized lines */
3724 if (compose_is_itemized(buffer, &end))
3727 /* don't join signature separator */
3728 if (compose_is_sig_separator(compose, buffer, &iter_))
3731 /* delete quote str */
3733 gtk_text_buffer_delete(buffer, &iter_, &end);
3735 /* don't join line breaks put by the user */
3737 gtk_text_iter_backward_char(&cur);
3738 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
3739 gtk_text_iter_forward_char(&cur);
3743 gtk_text_iter_forward_char(&cur);
3744 /* delete linebreak and extra spaces */
3745 while (gtk_text_iter_backward_char(&cur)) {
3746 wc1 = gtk_text_iter_get_char(&cur);
3747 if (!g_unichar_isspace(wc1))
3752 while (!gtk_text_iter_ends_line(&cur)) {
3753 wc1 = gtk_text_iter_get_char(&cur);
3754 if (!g_unichar_isspace(wc1))
3756 gtk_text_iter_forward_char(&cur);
3759 if (!gtk_text_iter_equal(&prev, &next)) {
3762 mark = gtk_text_buffer_get_insert(buffer);
3763 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
3764 if (gtk_text_iter_equal(&prev, &cur))
3766 gtk_text_buffer_delete(buffer, &prev, &next);
3770 /* insert space if required */
3771 gtk_text_iter_backward_char(&prev);
3772 wc1 = gtk_text_iter_get_char(&prev);
3773 wc2 = gtk_text_iter_get_char(&next);
3774 gtk_text_iter_forward_char(&next);
3775 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
3776 pango_default_break(str, -1, NULL, attrs, 3);
3777 if (!attrs[1].is_line_break ||
3778 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
3779 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
3781 gtk_text_iter_backward_char(&iter_);
3782 gtk_text_buffer_place_cursor(buffer, &iter_);
3791 #define ADD_TXT_POS(bp_, ep_, pti_) \
3792 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
3793 last = last->next; \
3794 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
3795 last->next = NULL; \
3797 g_warning("alloc error scanning URIs\n"); \
3800 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
3802 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3803 GtkTextBuffer *buffer;
3804 GtkTextIter iter, break_pos, end_of_line;
3805 gchar *quote_str = NULL;
3807 gboolean wrap_quote = prefs_common.linewrap_quote;
3808 gboolean prev_autowrap = compose->autowrap;
3809 gint startq_offset = -1, noq_offset = -1;
3810 gint uri_start = -1, uri_stop = -1;
3811 gint nouri_start = -1, nouri_stop = -1;
3812 gint num_blocks = 0;
3813 gint quotelevel = -1;
3814 gboolean modified = force;
3815 gboolean removed = FALSE;
3816 gboolean modified_before_remove = FALSE;
3818 gboolean start = TRUE;
3823 if (compose->draft_timeout_tag == -2) {
3827 compose->autowrap = FALSE;
3829 buffer = gtk_text_view_get_buffer(text);
3830 undo_wrapping(compose->undostruct, TRUE);
3835 mark = gtk_text_buffer_get_insert(buffer);
3836 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3840 if (compose->draft_timeout_tag == -2) {
3841 if (gtk_text_iter_ends_line(&iter)) {
3842 while (gtk_text_iter_ends_line(&iter) &&
3843 gtk_text_iter_forward_line(&iter))
3846 while (gtk_text_iter_backward_line(&iter)) {
3847 if (gtk_text_iter_ends_line(&iter)) {
3848 gtk_text_iter_forward_line(&iter);
3854 /* move to line start */
3855 gtk_text_iter_set_line_offset(&iter, 0);
3857 /* go until paragraph end (empty line) */
3858 while (start || !gtk_text_iter_ends_line(&iter)) {
3859 gchar *scanpos = NULL;
3860 /* parse table - in order of priority */
3862 const gchar *needle; /* token */
3864 /* token search function */
3865 gchar *(*search) (const gchar *haystack,
3866 const gchar *needle);
3867 /* part parsing function */
3868 gboolean (*parse) (const gchar *start,
3869 const gchar *scanpos,
3873 /* part to URI function */
3874 gchar *(*build_uri) (const gchar *bp,
3878 static struct table parser[] = {
3879 {"http://", strcasestr, get_uri_part, make_uri_string},
3880 {"https://", strcasestr, get_uri_part, make_uri_string},
3881 {"ftp://", strcasestr, get_uri_part, make_uri_string},
3882 {"sftp://", strcasestr, get_uri_part, make_uri_string},
3883 {"www.", strcasestr, get_uri_part, make_http_string},
3884 {"mailto:", strcasestr, get_uri_part, make_uri_string},
3885 {"@", strcasestr, get_email_part, make_email_string}
3887 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
3888 gint last_index = PARSE_ELEMS;
3890 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
3894 if (!prev_autowrap && num_blocks == 0) {
3896 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3897 G_CALLBACK(text_inserted),
3900 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
3903 uri_start = uri_stop = -1;
3905 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
3908 debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str);
3909 if (startq_offset == -1)
3910 startq_offset = gtk_text_iter_get_offset(&iter);
3911 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
3912 if (quotelevel > 2) {
3913 /* recycle colors */
3914 if (prefs_common.recycle_quote_colors)
3923 if (startq_offset == -1)
3924 noq_offset = gtk_text_iter_get_offset(&iter);
3928 if (prev_autowrap == FALSE && !force && !wrap_quote) {
3931 if (gtk_text_iter_ends_line(&iter)) {
3933 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
3934 prefs_common.linewrap_len,
3936 GtkTextIter prev, next, cur;
3938 if (prev_autowrap != FALSE || force) {
3939 compose->automatic_break = TRUE;
3941 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
3942 compose->automatic_break = FALSE;
3943 } else if (quote_str && wrap_quote) {
3944 compose->automatic_break = TRUE;
3946 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
3947 compose->automatic_break = FALSE;
3950 /* remove trailing spaces */
3952 gtk_text_iter_backward_char(&cur);
3954 while (!gtk_text_iter_starts_line(&cur)) {
3957 gtk_text_iter_backward_char(&cur);
3958 wc = gtk_text_iter_get_char(&cur);
3959 if (!g_unichar_isspace(wc))
3963 if (!gtk_text_iter_equal(&prev, &next)) {
3964 gtk_text_buffer_delete(buffer, &prev, &next);
3966 gtk_text_iter_forward_char(&break_pos);
3970 gtk_text_buffer_insert(buffer, &break_pos,
3974 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
3976 /* move iter to current line start */
3977 gtk_text_iter_set_line_offset(&iter, 0);
3984 /* move iter to next line start */
3990 if (!prev_autowrap && num_blocks > 0) {
3992 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3993 G_CALLBACK(text_inserted),
3997 while (!gtk_text_iter_ends_line(&end_of_line)) {
3998 gtk_text_iter_forward_char(&end_of_line);
4000 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4002 nouri_start = gtk_text_iter_get_offset(&iter);
4003 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4005 walk_pos = gtk_text_iter_get_offset(&iter);
4006 /* FIXME: this looks phony. scanning for anything in the parse table */
4007 for (n = 0; n < PARSE_ELEMS; n++) {
4010 tmp = parser[n].search(walk, parser[n].needle);
4012 if (scanpos == NULL || tmp < scanpos) {
4021 /* check if URI can be parsed */
4022 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4023 (const gchar **)&ep, FALSE)
4024 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4028 strlen(parser[last_index].needle);
4031 uri_start = walk_pos + (bp - o_walk);
4032 uri_stop = walk_pos + (ep - o_walk);
4036 gtk_text_iter_forward_line(&iter);
4039 if (startq_offset != -1) {
4040 GtkTextIter startquote, endquote;
4041 gtk_text_buffer_get_iter_at_offset(
4042 buffer, &startquote, startq_offset);
4045 switch (quotelevel) {
4047 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4048 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4049 gtk_text_buffer_apply_tag_by_name(
4050 buffer, "quote0", &startquote, &endquote);
4051 gtk_text_buffer_remove_tag_by_name(
4052 buffer, "quote1", &startquote, &endquote);
4053 gtk_text_buffer_remove_tag_by_name(
4054 buffer, "quote2", &startquote, &endquote);
4059 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4060 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4061 gtk_text_buffer_apply_tag_by_name(
4062 buffer, "quote1", &startquote, &endquote);
4063 gtk_text_buffer_remove_tag_by_name(
4064 buffer, "quote0", &startquote, &endquote);
4065 gtk_text_buffer_remove_tag_by_name(
4066 buffer, "quote2", &startquote, &endquote);
4071 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4072 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4073 gtk_text_buffer_apply_tag_by_name(
4074 buffer, "quote2", &startquote, &endquote);
4075 gtk_text_buffer_remove_tag_by_name(
4076 buffer, "quote0", &startquote, &endquote);
4077 gtk_text_buffer_remove_tag_by_name(
4078 buffer, "quote1", &startquote, &endquote);
4084 } else if (noq_offset != -1) {
4085 GtkTextIter startnoquote, endnoquote;
4086 gtk_text_buffer_get_iter_at_offset(
4087 buffer, &startnoquote, noq_offset);
4090 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4091 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4092 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4093 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4094 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4095 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4096 gtk_text_buffer_remove_tag_by_name(
4097 buffer, "quote0", &startnoquote, &endnoquote);
4098 gtk_text_buffer_remove_tag_by_name(
4099 buffer, "quote1", &startnoquote, &endnoquote);
4100 gtk_text_buffer_remove_tag_by_name(
4101 buffer, "quote2", &startnoquote, &endnoquote);
4107 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4108 GtkTextIter nouri_start_iter, nouri_end_iter;
4109 gtk_text_buffer_get_iter_at_offset(
4110 buffer, &nouri_start_iter, nouri_start);
4111 gtk_text_buffer_get_iter_at_offset(
4112 buffer, &nouri_end_iter, nouri_stop);
4113 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4114 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4115 gtk_text_buffer_remove_tag_by_name(
4116 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4117 modified_before_remove = modified;
4122 if (uri_start > 0 && uri_stop > 0) {
4123 GtkTextIter uri_start_iter, uri_end_iter, back;
4124 gtk_text_buffer_get_iter_at_offset(
4125 buffer, &uri_start_iter, uri_start);
4126 gtk_text_buffer_get_iter_at_offset(
4127 buffer, &uri_end_iter, uri_stop);
4128 back = uri_end_iter;
4129 gtk_text_iter_backward_char(&back);
4130 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4131 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4132 gtk_text_buffer_apply_tag_by_name(
4133 buffer, "link", &uri_start_iter, &uri_end_iter);
4135 if (removed && !modified_before_remove) {
4141 debug_print("not modified, out after %d lines\n", lines);
4146 debug_print("modified, out after %d lines\n", lines);
4150 undo_wrapping(compose->undostruct, FALSE);
4151 compose->autowrap = prev_autowrap;
4156 void compose_action_cb(void *data)
4158 Compose *compose = (Compose *)data;
4159 compose_wrap_all(compose);
4162 static void compose_wrap_all(Compose *compose)
4164 compose_wrap_all_full(compose, FALSE);
4167 static void compose_wrap_all_full(Compose *compose, gboolean force)
4169 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4170 GtkTextBuffer *buffer;
4172 gboolean modified = TRUE;
4174 buffer = gtk_text_view_get_buffer(text);
4176 gtk_text_buffer_get_start_iter(buffer, &iter);
4177 while (!gtk_text_iter_is_end(&iter) && modified)
4178 modified = compose_beautify_paragraph(compose, &iter, force);
4182 static void compose_set_title(Compose *compose)
4188 edited = compose->modified ? _(" [Edited]") : "";
4190 subject = gtk_editable_get_chars(
4191 GTK_EDITABLE(compose->subject_entry), 0, -1);
4194 if (subject && strlen(subject))
4195 str = g_strdup_printf(_("%s - Compose message%s"),
4198 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4200 str = g_strdup(_("Compose message"));
4203 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4209 * compose_current_mail_account:
4211 * Find a current mail account (the currently selected account, or the
4212 * default account, if a news account is currently selected). If a
4213 * mail account cannot be found, display an error message.
4215 * Return value: Mail account, or NULL if not found.
4217 static PrefsAccount *
4218 compose_current_mail_account(void)
4222 if (cur_account && cur_account->protocol != A_NNTP)
4225 ac = account_get_default();
4226 if (!ac || ac->protocol == A_NNTP) {
4227 alertpanel_error(_("Account for sending mail is not specified.\n"
4228 "Please select a mail account before sending."));
4235 #define QUOTE_IF_REQUIRED(out, str) \
4237 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4241 len = strlen(str) + 3; \
4242 if ((__tmp = alloca(len)) == NULL) { \
4243 g_warning("can't allocate memory\n"); \
4244 g_string_free(header, TRUE); \
4247 g_snprintf(__tmp, len, "\"%s\"", str); \
4252 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4253 g_warning("can't allocate memory\n"); \
4254 g_string_free(header, TRUE); \
4257 strcpy(__tmp, str); \
4263 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4265 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4269 len = strlen(str) + 3; \
4270 if ((__tmp = alloca(len)) == NULL) { \
4271 g_warning("can't allocate memory\n"); \
4274 g_snprintf(__tmp, len, "\"%s\"", str); \
4279 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4280 g_warning("can't allocate memory\n"); \
4283 strcpy(__tmp, str); \
4289 static void compose_select_account(Compose *compose, PrefsAccount *account,
4292 GtkItemFactory *ifactory;
4295 g_return_if_fail(account != NULL);
4297 compose->account = account;
4299 if (account->name && *account->name) {
4301 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4302 from = g_strdup_printf("%s <%s>",
4303 buf, account->address);
4304 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4306 from = g_strdup_printf("<%s>",
4308 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4313 compose_set_title(compose);
4315 ifactory = gtk_item_factory_from_widget(compose->menubar);
4317 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4318 menu_set_active(ifactory, "/Options/Sign", TRUE);
4320 menu_set_active(ifactory, "/Options/Sign", FALSE);
4321 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4322 menu_set_active(ifactory, "/Options/Encrypt", TRUE);
4324 menu_set_active(ifactory, "/Options/Encrypt", FALSE);
4326 activate_privacy_system(compose, account, FALSE);
4328 if (!init && compose->mode != COMPOSE_REDIRECT) {
4329 undo_block(compose->undostruct);
4330 compose_insert_sig(compose, TRUE);
4331 undo_unblock(compose->undostruct);
4335 /* use account's dict info if set */
4336 if (compose->gtkaspell) {
4337 if (account->enable_default_dictionary)
4338 gtkaspell_change_dict(compose->gtkaspell,
4339 account->default_dictionary, FALSE);
4340 if (account->enable_default_alt_dictionary)
4341 gtkaspell_change_alt_dict(compose->gtkaspell,
4342 account->default_alt_dictionary);
4343 if (account->enable_default_dictionary
4344 || account->enable_default_alt_dictionary)
4345 compose_spell_menu_changed(compose);
4350 gboolean compose_check_for_valid_recipient(Compose *compose) {
4351 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
4352 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
4353 gboolean recipient_found = FALSE;
4357 /* free to and newsgroup list */
4358 slist_free_strings(compose->to_list);
4359 g_slist_free(compose->to_list);
4360 compose->to_list = NULL;
4362 slist_free_strings(compose->newsgroup_list);
4363 g_slist_free(compose->newsgroup_list);
4364 compose->newsgroup_list = NULL;
4366 /* search header entries for to and newsgroup entries */
4367 for (list = compose->header_list; list; list = list->next) {
4370 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_COMBO(((ComposeHeaderEntry *)list->data)->combo)->entry), 0, -1);
4371 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4374 if (entry[0] != '\0') {
4375 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
4376 if (!strcmp(header, prefs_common_translated_header_name(*strptr))) {
4377 compose->to_list = address_list_append(compose->to_list, entry);
4378 recipient_found = TRUE;
4381 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
4382 if (!strcmp(header, prefs_common_translated_header_name(*strptr))) {
4383 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
4384 recipient_found = TRUE;
4391 return recipient_found;
4394 static gboolean compose_check_for_set_recipients(Compose *compose)
4396 if (compose->account->set_autocc && compose->account->auto_cc) {
4397 gboolean found_other = FALSE;
4399 /* search header entries for to and newsgroup entries */
4400 for (list = compose->header_list; list; list = list->next) {
4403 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4404 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_COMBO(((ComposeHeaderEntry *)list->data)->combo)->entry), 0, -1);
4406 if (strcmp(entry, compose->account->auto_cc)
4407 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
4417 if (compose->batch) {
4418 gtk_widget_show_all(compose->window);
4420 aval = alertpanel(_("Send"),
4421 _("The only recipient is the default CC address. Send anyway?"),
4422 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4423 if (aval != G_ALERTALTERNATE)
4427 if (compose->account->set_autobcc && compose->account->auto_bcc) {
4428 gboolean found_other = FALSE;
4430 /* search header entries for to and newsgroup entries */
4431 for (list = compose->header_list; list; list = list->next) {
4434 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4435 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_COMBO(((ComposeHeaderEntry *)list->data)->combo)->entry), 0, -1);
4437 if (strcmp(entry, compose->account->auto_bcc)
4438 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
4448 if (compose->batch) {
4449 gtk_widget_show_all(compose->window);
4451 aval = alertpanel(_("Send"),
4452 _("The only recipient is the default BCC address. Send anyway?"),
4453 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4454 if (aval != G_ALERTALTERNATE)
4461 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
4465 if (compose_check_for_valid_recipient(compose) == FALSE) {
4466 if (compose->batch) {
4467 gtk_widget_show_all(compose->window);
4469 alertpanel_error(_("Recipient is not specified."));
4473 if (compose_check_for_set_recipients(compose) == FALSE) {
4477 if (!compose->batch) {
4478 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
4479 if (*str == '\0' && check_everything == TRUE &&
4480 compose->mode != COMPOSE_REDIRECT) {
4483 aval = alertpanel(_("Send"),
4484 _("Subject is empty. Send it anyway?"),
4485 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4486 if (aval != G_ALERTALTERNATE)
4491 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
4497 gint compose_send(Compose *compose)
4500 FolderItem *folder = NULL;
4502 gchar *msgpath = NULL;
4503 gboolean discard_window = FALSE;
4504 gchar *errstr = NULL;
4505 gchar *tmsgid = NULL;
4506 MainWindow *mainwin = mainwindow_get_mainwindow();
4507 gboolean queued_removed = FALSE;
4509 if (prefs_common.send_dialog_invisible
4510 || compose->batch == TRUE)
4511 discard_window = TRUE;
4513 compose_allow_user_actions (compose, FALSE);
4514 compose->sending = TRUE;
4516 if (compose_check_entries(compose, TRUE) == FALSE) {
4517 if (compose->batch) {
4518 gtk_widget_show_all(compose->window);
4524 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
4527 if (compose->batch) {
4528 gtk_widget_show_all(compose->window);
4531 alertpanel_error(_("Could not queue message for sending:\n\n"
4532 "Charset conversion failed."));
4533 } else if (val == -5) {
4534 alertpanel_error(_("Could not queue message for sending:\n\n"
4535 "Couldn't get recipient encryption key."));
4536 } else if (val == -6) {
4538 } else if (val == -3) {
4539 if (privacy_peek_error())
4540 alertpanel_error(_("Could not queue message for sending:\n\n"
4541 "Signature failed: %s"), privacy_get_error());
4542 } else if (val == -2 && errno != 0) {
4543 alertpanel_error(_("Could not queue message for sending:\n\n%s."), strerror(errno));
4545 alertpanel_error(_("Could not queue message for sending."));
4550 tmsgid = g_strdup(compose->msgid);
4551 if (discard_window) {
4552 compose->sending = FALSE;
4553 compose_close(compose);
4554 /* No more compose access in the normal codepath
4555 * after this point! */
4560 alertpanel_error(_("The message was queued but could not be "
4561 "sent.\nUse \"Send queued messages\" from "
4562 "the main window to retry."));
4563 if (!discard_window) {
4570 if (msgpath == NULL) {
4571 msgpath = folder_item_fetch_msg(folder, msgnum);
4572 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
4575 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
4579 if (!discard_window) {
4581 if (!queued_removed)
4582 folder_item_remove_msg(folder, msgnum);
4583 folder_item_scan(folder);
4585 /* make sure we delete that */
4586 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
4588 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
4589 folder_item_remove_msg(folder, tmp->msgnum);
4590 procmsg_msginfo_free(tmp);
4597 if (!queued_removed)
4598 folder_item_remove_msg(folder, msgnum);
4599 folder_item_scan(folder);
4601 /* make sure we delete that */
4602 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
4604 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
4605 folder_item_remove_msg(folder, tmp->msgnum);
4606 procmsg_msginfo_free(tmp);
4609 if (!discard_window) {
4610 compose->sending = FALSE;
4611 compose_allow_user_actions (compose, TRUE);
4612 compose_close(compose);
4616 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
4617 "the main window to retry."), errstr);
4620 alertpanel_error_log(_("The message was queued but could not be "
4621 "sent.\nUse \"Send queued messages\" from "
4622 "the main window to retry."));
4624 if (!discard_window) {
4633 toolbar_main_set_sensitive(mainwin);
4634 main_window_set_menu_sensitive(mainwin);
4640 compose_allow_user_actions (compose, TRUE);
4641 compose->sending = FALSE;
4642 compose->modified = TRUE;
4643 toolbar_main_set_sensitive(mainwin);
4644 main_window_set_menu_sensitive(mainwin);
4649 static gboolean compose_use_attach(Compose *compose)
4651 GtkTreeModel *model = gtk_tree_view_get_model
4652 (GTK_TREE_VIEW(compose->attach_clist));
4653 return gtk_tree_model_iter_n_children(model, NULL) > 0;
4656 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
4659 gchar buf[BUFFSIZE];
4661 gboolean first_to_address;
4662 gboolean first_cc_address;
4664 ComposeHeaderEntry *headerentry;
4665 const gchar *headerentryname;
4666 const gchar *cc_hdr;
4667 const gchar *to_hdr;
4669 debug_print("Writing redirect header\n");
4671 cc_hdr = prefs_common_translated_header_name("Cc:");
4672 to_hdr = prefs_common_translated_header_name("To:");
4674 first_to_address = TRUE;
4675 for (list = compose->header_list; list; list = list->next) {
4676 headerentry = ((ComposeHeaderEntry *)list->data);
4677 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(headerentry->combo)->entry));
4679 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
4680 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
4681 Xstrdup_a(str, entstr, return -1);
4683 if (str[0] != '\0') {
4684 compose_convert_header
4685 (compose, buf, sizeof(buf), str,
4686 strlen("Resent-To") + 2, TRUE);
4688 if (first_to_address) {
4689 fprintf(fp, "Resent-To: ");
4690 first_to_address = FALSE;
4694 fprintf(fp, "%s", buf);
4698 if (!first_to_address) {
4702 first_cc_address = TRUE;
4703 for (list = compose->header_list; list; list = list->next) {
4704 headerentry = ((ComposeHeaderEntry *)list->data);
4705 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(headerentry->combo)->entry));
4707 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
4708 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
4709 Xstrdup_a(str, strg, return -1);
4711 if (str[0] != '\0') {
4712 compose_convert_header
4713 (compose, buf, sizeof(buf), str,
4714 strlen("Resent-Cc") + 2, TRUE);
4716 if (first_cc_address) {
4717 fprintf(fp, "Resent-Cc: ");
4718 first_cc_address = FALSE;
4722 fprintf(fp, "%s", buf);
4726 if (!first_cc_address) {
4733 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
4735 gchar buf[BUFFSIZE];
4737 const gchar *entstr;
4738 /* struct utsname utsbuf; */
4740 g_return_val_if_fail(fp != NULL, -1);
4741 g_return_val_if_fail(compose->account != NULL, -1);
4742 g_return_val_if_fail(compose->account->address != NULL, -1);
4745 get_rfc822_date(buf, sizeof(buf));
4746 fprintf(fp, "Resent-Date: %s\n", buf);
4749 if (compose->account->name && *compose->account->name) {
4750 compose_convert_header
4751 (compose, buf, sizeof(buf), compose->account->name,
4752 strlen("From: "), TRUE);
4753 fprintf(fp, "Resent-From: %s <%s>\n",
4754 buf, compose->account->address);
4756 fprintf(fp, "Resent-From: %s\n", compose->account->address);
4759 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
4760 if (*entstr != '\0') {
4761 Xstrdup_a(str, entstr, return -1);
4764 compose_convert_header(compose, buf, sizeof(buf), str,
4765 strlen("Subject: "), FALSE);
4766 fprintf(fp, "Subject: %s\n", buf);
4770 /* Resent-Message-ID */
4771 if (compose->account->gen_msgid) {
4772 generate_msgid(buf, sizeof(buf));
4773 fprintf(fp, "Resent-Message-ID: <%s>\n", buf);
4774 compose->msgid = g_strdup(buf);
4777 compose_redirect_write_headers_from_headerlist(compose, fp);
4779 /* separator between header and body */
4785 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
4789 gchar buf[BUFFSIZE];
4791 gboolean skip = FALSE;
4792 gchar *not_included[]={
4793 "Return-Path:", "Delivered-To:", "Received:",
4794 "Subject:", "X-UIDL:", "AF:",
4795 "NF:", "PS:", "SRH:",
4796 "SFN:", "DSR:", "MID:",
4797 "CFG:", "PT:", "S:",
4798 "RQ:", "SSV:", "NSV:",
4799 "SSH:", "R:", "MAID:",
4800 "NAID:", "RMID:", "FMID:",
4801 "SCF:", "RRCPT:", "NG:",
4802 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
4803 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
4804 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
4805 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
4808 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
4809 FILE_OP_ERROR(compose->redirect_filename, "fopen");
4813 while (procheader_get_one_field_asis(buf, sizeof(buf), fp) != -1) {
4815 for (i = 0; not_included[i] != NULL; i++) {
4816 if (g_ascii_strncasecmp(buf, not_included[i],
4817 strlen(not_included[i])) == 0) {
4824 if (fputs(buf, fdest) == -1)
4827 if (!prefs_common.redirect_keep_from) {
4828 if (g_ascii_strncasecmp(buf, "From:",
4829 strlen("From:")) == 0) {
4830 fputs(" (by way of ", fdest);
4831 if (compose->account->name
4832 && *compose->account->name) {
4833 compose_convert_header
4834 (compose, buf, sizeof(buf),
4835 compose->account->name,
4838 fprintf(fdest, "%s <%s>",
4840 compose->account->address);
4842 fprintf(fdest, "%s",
4843 compose->account->address);
4848 if (fputs("\n", fdest) == -1)
4852 compose_redirect_write_headers(compose, fdest);
4854 while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
4855 if (fwrite(buf, sizeof(gchar), len, fdest) != len)
4868 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
4870 GtkTextBuffer *buffer;
4871 GtkTextIter start, end;
4874 const gchar *out_codeset;
4875 EncodingType encoding;
4876 MimeInfo *mimemsg, *mimetext;
4879 if (action == COMPOSE_WRITE_FOR_SEND)
4880 attach_parts = TRUE;
4882 /* create message MimeInfo */
4883 mimemsg = procmime_mimeinfo_new();
4884 mimemsg->type = MIMETYPE_MESSAGE;
4885 mimemsg->subtype = g_strdup("rfc822");
4886 mimemsg->content = MIMECONTENT_MEM;
4887 mimemsg->tmp = TRUE; /* must free content later */
4888 mimemsg->data.mem = compose_get_header(compose);
4890 /* Create text part MimeInfo */
4891 /* get all composed text */
4892 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
4893 gtk_text_buffer_get_start_iter(buffer, &start);
4894 gtk_text_buffer_get_end_iter(buffer, &end);
4895 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
4896 if (is_ascii_str(chars)) {
4899 out_codeset = CS_US_ASCII;
4900 encoding = ENC_7BIT;
4902 const gchar *src_codeset = CS_INTERNAL;
4904 out_codeset = conv_get_charset_str(compose->out_encoding);
4907 gchar *test_conv_global_out = NULL;
4908 gchar *test_conv_reply = NULL;
4910 /* automatic mode. be automatic. */
4911 codeconv_set_strict(TRUE);
4913 out_codeset = conv_get_outgoing_charset_str();
4915 debug_print("trying to convert to %s\n", out_codeset);
4916 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
4919 if (!test_conv_global_out && compose->orig_charset
4920 && strcmp(compose->orig_charset, CS_US_ASCII)) {
4921 out_codeset = compose->orig_charset;
4922 debug_print("failure; trying to convert to %s\n", out_codeset);
4923 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
4926 if (!test_conv_global_out && !test_conv_reply) {
4928 out_codeset = CS_INTERNAL;
4929 debug_print("failure; finally using %s\n", out_codeset);
4931 g_free(test_conv_global_out);
4932 g_free(test_conv_reply);
4933 codeconv_set_strict(FALSE);
4936 if (!g_ascii_strcasecmp(out_codeset, CS_US_ASCII))
4937 out_codeset = CS_ISO_8859_1;
4939 if (prefs_common.encoding_method == CTE_BASE64)
4940 encoding = ENC_BASE64;
4941 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
4942 encoding = ENC_QUOTED_PRINTABLE;
4943 else if (prefs_common.encoding_method == CTE_8BIT)
4944 encoding = ENC_8BIT;
4946 encoding = procmime_get_encoding_for_charset(out_codeset);
4948 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
4949 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
4951 if (action == COMPOSE_WRITE_FOR_SEND) {
4952 codeconv_set_strict(TRUE);
4953 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
4954 codeconv_set_strict(FALSE);
4960 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
4961 "to the specified %s charset.\n"
4962 "Send it as %s?"), out_codeset, src_codeset);
4963 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL, _("+_Send"), NULL, FALSE,
4964 NULL, ALERT_ERROR, G_ALERTDEFAULT);
4967 if (aval != G_ALERTALTERNATE) {
4972 out_codeset = src_codeset;
4978 out_codeset = src_codeset;
4984 if (encoding == ENC_8BIT || encoding == ENC_7BIT) {
4985 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
4986 strstr(buf, "\nFrom ") != NULL) {
4987 encoding = ENC_QUOTED_PRINTABLE;
4991 mimetext = procmime_mimeinfo_new();
4992 mimetext->content = MIMECONTENT_MEM;
4993 mimetext->tmp = TRUE; /* must free content later */
4994 /* dup'ed because procmime_encode_content can turn it into a tmpfile
4995 * and free the data, which we need later. */
4996 mimetext->data.mem = g_strdup(buf);
4997 mimetext->type = MIMETYPE_TEXT;
4998 mimetext->subtype = g_strdup("plain");
4999 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5000 g_strdup(out_codeset));
5002 /* protect trailing spaces when signing message */
5003 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5004 privacy_system_can_sign(compose->privacy_system)) {
5005 encoding = ENC_QUOTED_PRINTABLE;
5008 debug_print("main text: %d bytes encoded as %s in %d\n",
5009 strlen(buf), out_codeset, encoding);
5011 /* check for line length limit */
5012 if (action == COMPOSE_WRITE_FOR_SEND &&
5013 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5014 check_line_length(buf, 1000, &line) < 0) {
5018 msg = g_strdup_printf
5019 (_("Line %d exceeds the line length limit (998 bytes).\n"
5020 "The contents of the message might be broken on the way to the delivery.\n"
5022 "Send it anyway?"), line + 1);
5023 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5025 if (aval != G_ALERTALTERNATE) {
5031 if (encoding != ENC_UNKNOWN)
5032 procmime_encode_content(mimetext, encoding);
5034 /* append attachment parts */
5035 if (compose_use_attach(compose) && attach_parts) {
5036 MimeInfo *mimempart;
5037 gchar *boundary = NULL;
5038 mimempart = procmime_mimeinfo_new();
5039 mimempart->content = MIMECONTENT_EMPTY;
5040 mimempart->type = MIMETYPE_MULTIPART;
5041 mimempart->subtype = g_strdup("mixed");
5045 boundary = generate_mime_boundary(NULL);
5046 } while (strstr(buf, boundary) != NULL);
5048 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5051 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5053 g_node_append(mimempart->node, mimetext->node);
5054 g_node_append(mimemsg->node, mimempart->node);
5056 compose_add_attachments(compose, mimempart);
5058 g_node_append(mimemsg->node, mimetext->node);
5062 /* sign message if sending */
5063 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5064 privacy_system_can_sign(compose->privacy_system))
5065 if (!privacy_sign(compose->privacy_system, mimemsg, compose->account))
5068 procmime_write_mimeinfo(mimemsg, fp);
5070 procmime_mimeinfo_free_all(mimemsg);
5075 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5077 GtkTextBuffer *buffer;
5078 GtkTextIter start, end;
5083 if ((fp = g_fopen(file, "wb")) == NULL) {
5084 FILE_OP_ERROR(file, "fopen");
5088 /* chmod for security */
5089 if (change_file_mode_rw(fp, file) < 0) {
5090 FILE_OP_ERROR(file, "chmod");
5091 g_warning("can't change file mode\n");
5094 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5095 gtk_text_buffer_get_start_iter(buffer, &start);
5096 gtk_text_buffer_get_end_iter(buffer, &end);
5097 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5099 chars = conv_codeset_strdup
5100 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5103 if (!chars) return -1;
5106 len = strlen(chars);
5107 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5108 FILE_OP_ERROR(file, "fwrite");
5117 if (fclose(fp) == EOF) {
5118 FILE_OP_ERROR(file, "fclose");
5125 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5128 MsgInfo *msginfo = compose->targetinfo;
5130 g_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5131 if (!msginfo) return -1;
5133 if (!force && MSG_IS_LOCKED(msginfo->flags))
5136 item = msginfo->folder;
5137 g_return_val_if_fail(item != NULL, -1);
5139 if (procmsg_msg_exist(msginfo) &&
5140 (folder_has_parent_of_type(item, F_QUEUE) ||
5141 folder_has_parent_of_type(item, F_DRAFT)
5142 || msginfo == compose->autosaved_draft)) {
5143 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5144 g_warning("can't remove the old message\n");
5147 debug_print("removed reedit target %d\n", msginfo->msgnum);
5154 static void compose_remove_draft(Compose *compose)
5157 MsgInfo *msginfo = compose->targetinfo;
5158 drafts = account_get_special_folder(compose->account, F_DRAFT);
5160 if (procmsg_msg_exist(msginfo)) {
5161 folder_item_remove_msg(drafts, msginfo->msgnum);
5166 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5167 gboolean remove_reedit_target)
5169 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5172 static gboolean compose_warn_encryption(Compose *compose)
5174 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5175 AlertValue val = G_ALERTALTERNATE;
5177 if (warning == NULL)
5180 val = alertpanel_full(_("Encryption warning"), warning,
5181 GTK_STOCK_CANCEL, _("+C_ontinue"), NULL,
5182 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5183 if (val & G_ALERTDISABLE) {
5184 val &= ~G_ALERTDISABLE;
5185 if (val == G_ALERTALTERNATE)
5186 privacy_inhibit_encrypt_warning(compose->privacy_system,
5190 if (val == G_ALERTALTERNATE) {
5197 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
5198 gchar **msgpath, gboolean check_subject,
5199 gboolean remove_reedit_target)
5206 static gboolean lock = FALSE;
5207 PrefsAccount *mailac = NULL, *newsac = NULL;
5209 debug_print("queueing message...\n");
5210 g_return_val_if_fail(compose->account != NULL, -1);
5214 if (compose_check_entries(compose, check_subject) == FALSE) {
5216 if (compose->batch) {
5217 gtk_widget_show_all(compose->window);
5222 if (!compose->to_list && !compose->newsgroup_list) {
5223 g_warning("can't get recipient list.");
5228 if (compose->to_list) {
5229 if (compose->account->protocol != A_NNTP)
5230 mailac = compose->account;
5231 else if (cur_account && cur_account->protocol != A_NNTP)
5232 mailac = cur_account;
5233 else if (!(mailac = compose_current_mail_account())) {
5235 alertpanel_error(_("No account for sending mails available!"));
5240 if (compose->newsgroup_list) {
5241 if (compose->account->protocol == A_NNTP)
5242 newsac = compose->account;
5243 else if (!newsac->protocol != A_NNTP) {
5245 alertpanel_error(_("No account for posting news available!"));
5250 /* write queue header */
5251 tmp = g_strdup_printf("%s%cqueue.%p", get_tmp_dir(),
5252 G_DIR_SEPARATOR, compose);
5253 if ((fp = g_fopen(tmp, "wb")) == NULL) {
5254 FILE_OP_ERROR(tmp, "fopen");
5260 if (change_file_mode_rw(fp, tmp) < 0) {
5261 FILE_OP_ERROR(tmp, "chmod");
5262 g_warning("can't change file mode\n");
5265 /* queueing variables */
5266 fprintf(fp, "AF:\n");
5267 fprintf(fp, "NF:0\n");
5268 fprintf(fp, "PS:10\n");
5269 fprintf(fp, "SRH:1\n");
5270 fprintf(fp, "SFN:\n");
5271 fprintf(fp, "DSR:\n");
5273 fprintf(fp, "MID:<%s>\n", compose->msgid);
5275 fprintf(fp, "MID:\n");
5276 fprintf(fp, "CFG:\n");
5277 fprintf(fp, "PT:0\n");
5278 fprintf(fp, "S:%s\n", compose->account->address);
5279 fprintf(fp, "RQ:\n");
5281 fprintf(fp, "SSV:%s\n", mailac->smtp_server);
5283 fprintf(fp, "SSV:\n");
5285 fprintf(fp, "NSV:%s\n", newsac->nntp_server);
5287 fprintf(fp, "NSV:\n");
5288 fprintf(fp, "SSH:\n");
5289 /* write recepient list */
5290 if (compose->to_list) {
5291 fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data);
5292 for (cur = compose->to_list->next; cur != NULL;
5294 fprintf(fp, ",<%s>", (gchar *)cur->data);
5297 /* write newsgroup list */
5298 if (compose->newsgroup_list) {
5300 fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data);
5301 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
5302 fprintf(fp, ",%s", (gchar *)cur->data);
5305 /* Sylpheed account IDs */
5307 fprintf(fp, "MAID:%d\n", mailac->account_id);
5309 fprintf(fp, "NAID:%d\n", newsac->account_id);
5312 if (compose->privacy_system != NULL) {
5313 fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system);
5314 fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing);
5315 if (compose->use_encryption) {
5317 if (!compose_warn_encryption(compose)) {
5324 if (mailac && mailac->encrypt_to_self) {
5325 GSList *tmp_list = g_slist_copy(compose->to_list);
5326 tmp_list = g_slist_append(tmp_list, compose->account->address);
5327 encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
5328 g_slist_free(tmp_list);
5330 encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
5332 if (encdata != NULL) {
5333 if (strcmp(encdata, "_DONT_ENCRYPT_")) {
5334 fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption);
5335 fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
5337 } /* else we finally dont want to encrypt */
5339 fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption);
5340 /* and if encdata was null, it means there's been a problem in
5352 /* Save copy folder */
5353 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
5354 gchar *savefolderid;
5356 savefolderid = gtk_editable_get_chars(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
5357 fprintf(fp, "SCF:%s\n", savefolderid);
5358 g_free(savefolderid);
5360 /* Save copy folder */
5361 if (compose->return_receipt) {
5362 fprintf(fp, "RRCPT:1\n");
5364 /* Message-ID of message replying to */
5365 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
5368 folderid = folder_item_get_identifier(compose->replyinfo->folder);
5369 fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid);
5372 /* Message-ID of message forwarding to */
5373 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
5376 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
5377 fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid);
5381 /* end of headers */
5382 fprintf(fp, "X-Claws-End-Special-Headers: 1\n");
5384 if (compose->redirect_filename != NULL) {
5385 if (compose_redirect_write_to_file(compose, fp) < 0) {
5394 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
5399 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
5403 if (fclose(fp) == EOF) {
5404 FILE_OP_ERROR(tmp, "fclose");
5411 if (item && *item) {
5414 queue = account_get_special_folder(compose->account, F_QUEUE);
5417 g_warning("can't find queue folder\n");
5423 folder_item_scan(queue);
5424 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
5425 g_warning("can't queue the message\n");
5432 if (msgpath == NULL) {
5438 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
5439 compose_remove_reedit_target(compose, FALSE);
5442 if ((msgnum != NULL) && (item != NULL)) {
5450 static void compose_add_attachments(Compose *compose, MimeInfo *parent)
5453 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
5455 struct stat statbuf;
5456 gchar *type, *subtype;
5457 GtkTreeModel *model;
5460 model = gtk_tree_view_get_model(tree_view);
5462 if (!gtk_tree_model_get_iter_first(model, &iter))
5465 gtk_tree_model_get(model, &iter,
5469 mimepart = procmime_mimeinfo_new();
5470 mimepart->content = MIMECONTENT_FILE;
5471 mimepart->data.filename = g_strdup(ainfo->file);
5472 mimepart->tmp = FALSE; /* or we destroy our attachment */
5473 mimepart->offset = 0;
5475 stat(ainfo->file, &statbuf);
5476 mimepart->length = statbuf.st_size;
5478 type = g_strdup(ainfo->content_type);
5480 if (!strchr(type, '/')) {
5482 type = g_strdup("application/octet-stream");
5485 subtype = strchr(type, '/') + 1;
5486 *(subtype - 1) = '\0';
5487 mimepart->type = procmime_get_media_type(type);
5488 mimepart->subtype = g_strdup(subtype);
5491 if (mimepart->type == MIMETYPE_MESSAGE &&
5492 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
5493 mimepart->disposition = DISPOSITIONTYPE_INLINE;
5496 g_hash_table_insert(mimepart->typeparameters,
5497 g_strdup("name"), g_strdup(ainfo->name));
5498 g_hash_table_insert(mimepart->dispositionparameters,
5499 g_strdup("filename"), g_strdup(ainfo->name));
5500 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
5504 if (compose->use_signing) {
5505 if (ainfo->encoding == ENC_7BIT)
5506 ainfo->encoding = ENC_QUOTED_PRINTABLE;
5507 else if (ainfo->encoding == ENC_8BIT)
5508 ainfo->encoding = ENC_BASE64;
5511 procmime_encode_content(mimepart, ainfo->encoding);
5513 g_node_append(parent->node, mimepart->node);
5514 } while (gtk_tree_model_iter_next(model, &iter));
5517 #define IS_IN_CUSTOM_HEADER(header) \
5518 (compose->account->add_customhdr && \
5519 custom_header_find(compose->account->customhdr_list, header) != NULL)
5521 static void compose_add_headerfield_from_headerlist(Compose *compose,
5523 const gchar *fieldname,
5524 const gchar *seperator)
5526 gchar *str, *fieldname_w_colon;
5527 gboolean add_field = FALSE;
5529 ComposeHeaderEntry *headerentry;
5530 const gchar *headerentryname;
5531 const gchar *trans_fieldname;
5534 if (IS_IN_CUSTOM_HEADER(fieldname))
5537 debug_print("Adding %s-fields\n", fieldname);
5539 fieldstr = g_string_sized_new(64);
5541 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
5542 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
5544 for (list = compose->header_list; list; list = list->next) {
5545 headerentry = ((ComposeHeaderEntry *)list->data);
5546 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(headerentry->combo)->entry));
5548 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
5549 str = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
5551 if (str[0] != '\0') {
5553 g_string_append(fieldstr, seperator);
5554 g_string_append(fieldstr, str);
5563 buf = g_new0(gchar, fieldstr->len * 4 + 256);
5564 compose_convert_header
5565 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
5566 strlen(fieldname) + 2, TRUE);
5567 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
5571 g_free(fieldname_w_colon);
5572 g_string_free(fieldstr, TRUE);
5577 static gchar *compose_get_header(Compose *compose)
5579 gchar buf[BUFFSIZE];
5580 const gchar *entry_str;
5584 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
5586 gchar *from_name = NULL, *from_address = NULL;
5589 g_return_val_if_fail(compose->account != NULL, NULL);
5590 g_return_val_if_fail(compose->account->address != NULL, NULL);
5592 header = g_string_sized_new(64);
5595 get_rfc822_date(buf, sizeof(buf));
5596 g_string_append_printf(header, "Date: %s\n", buf);
5600 if (compose->account->name && *compose->account->name) {
5602 QUOTE_IF_REQUIRED(buf, compose->account->name);
5603 tmp = g_strdup_printf("%s <%s>",
5604 buf, compose->account->address);
5606 tmp = g_strdup_printf("%s",
5607 compose->account->address);
5609 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
5610 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
5612 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
5613 from_address = g_strdup(compose->account->address);
5615 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5616 /* extract name and address */
5617 if (strstr(spec, " <") && strstr(spec, ">")) {
5618 from_address = g_strdup(strrchr(spec, '<')+1);
5619 *(strrchr(from_address, '>')) = '\0';
5620 from_name = g_strdup(spec);
5621 *(strrchr(from_name, '<')) = '\0';
5624 from_address = g_strdup(spec);
5631 if (from_name && *from_name) {
5632 compose_convert_header
5633 (compose, buf, sizeof(buf), from_name,
5634 strlen("From: "), TRUE);
5635 QUOTE_IF_REQUIRED(name, buf);
5637 g_string_append_printf(header, "From: %s <%s>\n",
5638 name, from_address);
5640 g_string_append_printf(header, "From: %s\n", from_address);
5643 g_free(from_address);
5646 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
5649 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
5652 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
5655 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
5658 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
5660 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
5663 compose_convert_header(compose, buf, sizeof(buf), str,
5664 strlen("Subject: "), FALSE);
5665 g_string_append_printf(header, "Subject: %s\n", buf);
5671 if (compose->account->gen_msgid) {
5672 generate_msgid(buf, sizeof(buf));
5673 g_string_append_printf(header, "Message-ID: <%s>\n", buf);
5674 compose->msgid = g_strdup(buf);
5677 if (compose->remove_references == FALSE) {
5679 if (compose->inreplyto && compose->to_list)
5680 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
5683 if (compose->references)
5684 g_string_append_printf(header, "References: %s\n", compose->references);
5688 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
5691 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
5694 if (compose->account->organization &&
5695 strlen(compose->account->organization) &&
5696 !IS_IN_CUSTOM_HEADER("Organization")) {
5697 compose_convert_header(compose, buf, sizeof(buf),
5698 compose->account->organization,
5699 strlen("Organization: "), FALSE);
5700 g_string_append_printf(header, "Organization: %s\n", buf);
5703 /* Program version and system info */
5704 if (g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
5705 !compose->newsgroup_list) {
5706 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
5708 gtk_major_version, gtk_minor_version, gtk_micro_version,
5711 if (g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
5712 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
5714 gtk_major_version, gtk_minor_version, gtk_micro_version,
5718 /* custom headers */
5719 if (compose->account->add_customhdr) {
5722 for (cur = compose->account->customhdr_list; cur != NULL;
5724 CustomHeader *chdr = (CustomHeader *)cur->data;
5726 if (custom_header_is_allowed(chdr->name)) {
5727 compose_convert_header
5728 (compose, buf, sizeof(buf),
5729 chdr->value ? chdr->value : "",
5730 strlen(chdr->name) + 2, FALSE);
5731 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
5737 switch (compose->priority) {
5738 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
5739 "X-Priority: 1 (Highest)\n");
5741 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
5742 "X-Priority: 2 (High)\n");
5744 case PRIORITY_NORMAL: break;
5745 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
5746 "X-Priority: 4 (Low)\n");
5748 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
5749 "X-Priority: 5 (Lowest)\n");
5751 default: debug_print("compose: priority unknown : %d\n",
5755 /* Request Return Receipt */
5756 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To")) {
5757 if (compose->return_receipt) {
5758 if (compose->account->name
5759 && *compose->account->name) {
5760 compose_convert_header(compose, buf, sizeof(buf),
5761 compose->account->name,
5762 strlen("Disposition-Notification-To: "),
5764 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, compose->account->address);
5766 g_string_append_printf(header, "Disposition-Notification-To: %s\n", compose->account->address);
5770 /* get special headers */
5771 for (list = compose->header_list; list; list = list->next) {
5772 ComposeHeaderEntry *headerentry;
5775 gchar *headername_wcolon;
5776 const gchar *headername_trans;
5779 gboolean standard_header = FALSE;
5781 headerentry = ((ComposeHeaderEntry *)list->data);
5783 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(headerentry->combo)->entry)));
5784 if (strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
5789 if (!strstr(tmp, ":")) {
5790 headername_wcolon = g_strconcat(tmp, ":", NULL);
5791 headername = g_strdup(tmp);
5793 headername_wcolon = g_strdup(tmp);
5794 headername = g_strdup(strtok(tmp, ":"));
5798 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5799 Xstrdup_a(headervalue, entry_str, return NULL);
5800 subst_char(headervalue, '\r', ' ');
5801 subst_char(headervalue, '\n', ' ');
5802 string = std_headers;
5803 while (*string != NULL) {
5804 headername_trans = prefs_common_translated_header_name(*string);
5805 if (!strcmp(headername_trans, headername_wcolon))
5806 standard_header = TRUE;
5809 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
5810 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
5813 g_free(headername_wcolon);
5817 g_string_free(header, FALSE);
5822 #undef IS_IN_CUSTOM_HEADER
5824 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
5825 gint header_len, gboolean addr_field)
5827 gchar *tmpstr = NULL;
5828 const gchar *out_codeset = NULL;
5830 g_return_if_fail(src != NULL);
5831 g_return_if_fail(dest != NULL);
5833 if (len < 1) return;
5835 tmpstr = g_strdup(src);
5837 subst_char(tmpstr, '\n', ' ');
5838 subst_char(tmpstr, '\r', ' ');
5841 if (!g_utf8_validate(tmpstr, -1, NULL)) {
5842 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
5843 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
5848 codeconv_set_strict(TRUE);
5849 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
5850 conv_get_charset_str(compose->out_encoding));
5851 codeconv_set_strict(FALSE);
5853 if (!dest || *dest == '\0') {
5854 gchar *test_conv_global_out = NULL;
5855 gchar *test_conv_reply = NULL;
5857 /* automatic mode. be automatic. */
5858 codeconv_set_strict(TRUE);
5860 out_codeset = conv_get_outgoing_charset_str();
5862 debug_print("trying to convert to %s\n", out_codeset);
5863 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
5866 if (!test_conv_global_out && compose->orig_charset
5867 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5868 out_codeset = compose->orig_charset;
5869 debug_print("failure; trying to convert to %s\n", out_codeset);
5870 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
5873 if (!test_conv_global_out && !test_conv_reply) {
5875 out_codeset = CS_INTERNAL;
5876 debug_print("finally using %s\n", out_codeset);
5878 g_free(test_conv_global_out);
5879 g_free(test_conv_reply);
5880 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
5882 codeconv_set_strict(FALSE);
5887 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
5891 g_return_if_fail(user_data != NULL);
5893 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
5894 g_strstrip(address);
5895 if (*address != '\0') {
5896 gchar *name = procheader_get_fromname(address);
5897 extract_address(address);
5898 addressbook_add_contact(name, address, NULL);
5903 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
5905 GtkWidget *menuitem;
5908 g_return_if_fail(menu != NULL);
5909 g_return_if_fail(GTK_IS_MENU_SHELL(menu));
5911 menuitem = gtk_separator_menu_item_new();
5912 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
5913 gtk_widget_show(menuitem);
5915 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
5916 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
5918 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
5919 g_strstrip(address);
5920 if (*address == '\0') {
5921 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
5924 g_signal_connect(G_OBJECT(menuitem), "activate",
5925 G_CALLBACK(compose_add_to_addressbook_cb), entry);
5926 gtk_widget_show(menuitem);
5929 static void compose_create_header_entry(Compose *compose)
5931 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
5935 GList *combo_list = NULL;
5937 const gchar *header = NULL;
5938 ComposeHeaderEntry *headerentry;
5939 gboolean standard_header = FALSE;
5941 headerentry = g_new0(ComposeHeaderEntry, 1);
5944 combo = gtk_combo_new();
5946 while(*string != NULL) {
5947 combo_list = g_list_append(combo_list, (gchar*)prefs_common_translated_header_name(*string));
5950 gtk_combo_set_popdown_strings(GTK_COMBO(combo), combo_list);
5951 g_list_free(combo_list);
5952 gtk_editable_set_editable(GTK_EDITABLE(GTK_COMBO(combo)->entry), TRUE);
5953 g_signal_connect(G_OBJECT(GTK_COMBO(combo)->entry), "grab_focus",
5954 G_CALLBACK(compose_grab_focus_cb), compose);
5955 gtk_widget_show(combo);
5956 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
5957 compose->header_nextrow, compose->header_nextrow+1,
5958 GTK_SHRINK, GTK_FILL, 0, 0);
5959 if (compose->header_last) {
5960 const gchar *last_header_entry = gtk_entry_get_text(
5961 GTK_ENTRY(GTK_COMBO(compose->header_last->combo)->entry));
5963 while (*string != NULL) {
5964 if (!strcmp(*string, last_header_entry))
5965 standard_header = TRUE;
5968 if (standard_header)
5969 header = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(compose->header_last->combo)->entry));
5971 if (!compose->header_last || !standard_header) {
5972 switch(compose->account->protocol) {
5974 header = prefs_common_translated_header_name("Newsgroups:");
5977 header = prefs_common_translated_header_name("To:");
5982 gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(combo)->entry), header);
5984 g_signal_connect_after(G_OBJECT(GTK_COMBO(combo)->entry), "grab_focus",
5985 G_CALLBACK(compose_grab_focus_cb), compose);
5988 entry = gtk_entry_new();
5989 gtk_widget_show(entry);
5990 gtk_tooltips_set_tip(compose->tooltips, entry,
5991 _("Use <tab> to autocomplete from addressbook"), NULL);
5992 gtk_table_attach(GTK_TABLE(compose->header_table), entry, 1, 2,
5993 compose->header_nextrow, compose->header_nextrow+1,
5994 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
5996 g_signal_connect(G_OBJECT(entry), "key-press-event",
5997 G_CALLBACK(compose_headerentry_key_press_event_cb),
5999 g_signal_connect(G_OBJECT(entry), "changed",
6000 G_CALLBACK(compose_headerentry_changed_cb),
6002 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
6003 G_CALLBACK(compose_grab_focus_cb), compose);
6006 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6007 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6008 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6009 g_signal_connect(G_OBJECT(entry), "drag_data_received",
6010 G_CALLBACK(compose_header_drag_received_cb),
6012 g_signal_connect(G_OBJECT(entry), "drag-drop",
6013 G_CALLBACK(compose_drag_drop),
6015 g_signal_connect(G_OBJECT(entry), "populate-popup",
6016 G_CALLBACK(compose_entry_popup_extend),
6019 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
6021 headerentry->compose = compose;
6022 headerentry->combo = combo;
6023 headerentry->entry = entry;
6024 headerentry->headernum = compose->header_nextrow;
6026 compose->header_nextrow++;
6027 compose->header_last = headerentry;
6028 compose->header_list =
6029 g_slist_append(compose->header_list,
6033 static void compose_add_header_entry(Compose *compose, const gchar *header, gchar *text)
6035 ComposeHeaderEntry *last_header;
6037 last_header = compose->header_last;
6039 gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(last_header->combo)->entry), header);
6040 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
6043 static void compose_remove_header_entries(Compose *compose)
6046 for (list = compose->header_list; list; list = list->next) {
6047 ComposeHeaderEntry *headerentry =
6048 (ComposeHeaderEntry *)list->data;
6049 gtk_widget_destroy(headerentry->combo);
6050 gtk_widget_destroy(headerentry->entry);
6051 g_free(headerentry);
6053 compose->header_last = NULL;
6054 g_slist_free(compose->header_list);
6055 compose->header_list = NULL;
6056 compose->header_nextrow = 1;
6057 compose_create_header_entry(compose);
6060 static GtkWidget *compose_create_header(Compose *compose)
6062 GtkWidget *from_optmenu_hbox;
6063 GtkWidget *header_scrolledwin;
6064 GtkWidget *header_table;
6068 /* header labels and entries */
6069 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
6070 gtk_widget_show(header_scrolledwin);
6071 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
6073 header_table = gtk_table_new(2, 2, FALSE);
6074 gtk_widget_show(header_table);
6075 gtk_container_set_border_width(GTK_CONTAINER(header_table), BORDER_WIDTH);
6076 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
6077 gtk_viewport_set_shadow_type(GTK_VIEWPORT(GTK_BIN(header_scrolledwin)->child), GTK_SHADOW_ETCHED_IN);
6080 /* option menu for selecting accounts */
6081 from_optmenu_hbox = compose_account_option_menu_create(compose);
6082 gtk_table_attach(GTK_TABLE(header_table), from_optmenu_hbox,
6083 0, 2, count, count + 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
6086 compose->header_table = header_table;
6087 compose->header_list = NULL;
6088 compose->header_nextrow = count;
6090 compose_create_header_entry(compose);
6092 compose->table = NULL;
6094 return header_scrolledwin ;
6097 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
6099 Compose *compose = (Compose *)data;
6100 GdkEventButton event;
6103 event.time = gtk_get_current_event_time();
6105 return attach_button_pressed(compose->attach_clist, &event, compose);
6108 static GtkWidget *compose_create_attach(Compose *compose)
6110 GtkWidget *attach_scrwin;
6111 GtkWidget *attach_clist;
6113 GtkListStore *store;
6114 GtkCellRenderer *renderer;
6115 GtkTreeViewColumn *column;
6116 GtkTreeSelection *selection;
6118 /* attachment list */
6119 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
6120 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
6121 GTK_POLICY_AUTOMATIC,
6122 GTK_POLICY_AUTOMATIC);
6123 gtk_widget_set_size_request(attach_scrwin, -1, 80);
6125 store = gtk_list_store_new(N_ATTACH_COLS,
6130 G_TYPE_AUTO_POINTER,
6132 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
6133 (GTK_TREE_MODEL(store)));
6134 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
6135 g_object_unref(store);
6137 renderer = gtk_cell_renderer_text_new();
6138 column = gtk_tree_view_column_new_with_attributes
6139 (_("Mime type"), renderer, "text",
6140 COL_MIMETYPE, NULL);
6141 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6143 renderer = gtk_cell_renderer_text_new();
6144 column = gtk_tree_view_column_new_with_attributes
6145 (_("Size"), renderer, "text",
6147 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6149 renderer = gtk_cell_renderer_text_new();
6150 column = gtk_tree_view_column_new_with_attributes
6151 (_("Name"), renderer, "text",
6153 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6155 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
6156 prefs_common.use_stripes_everywhere);
6157 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
6158 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
6160 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
6161 G_CALLBACK(attach_selected), compose);
6162 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
6163 G_CALLBACK(attach_button_pressed), compose);
6165 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
6166 G_CALLBACK(popup_attach_button_pressed), compose);
6168 gtk_widget_tap_and_hold_setup(GTK_WIDGET(attach_clist), NULL, NULL,
6169 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
6170 g_signal_connect(G_OBJECT(attach_clist), "tap-and-hold",
6171 G_CALLBACK(popup_attach_button_pressed), compose);
6173 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
6174 G_CALLBACK(attach_key_pressed), compose);
6177 gtk_drag_dest_set(attach_clist,
6178 GTK_DEST_DEFAULT_ALL, compose_mime_types,
6179 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6180 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6181 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
6182 G_CALLBACK(compose_attach_drag_received_cb),
6184 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
6185 G_CALLBACK(compose_drag_drop),
6188 compose->attach_scrwin = attach_scrwin;
6189 compose->attach_clist = attach_clist;
6191 return attach_scrwin;
6194 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
6195 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
6197 static GtkWidget *compose_create_others(Compose *compose)
6200 GtkWidget *savemsg_checkbtn;
6201 GtkWidget *savemsg_entry;
6202 GtkWidget *savemsg_select;
6205 gchar *folderidentifier;
6207 /* Table for settings */
6208 table = gtk_table_new(3, 1, FALSE);
6209 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
6210 gtk_widget_show(table);
6211 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
6214 /* Save Message to folder */
6215 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
6216 gtk_widget_show(savemsg_checkbtn);
6217 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6218 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6219 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
6221 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
6222 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
6224 savemsg_entry = gtk_entry_new();
6225 gtk_widget_show(savemsg_entry);
6226 gtk_table_attach_defaults(GTK_TABLE(table), savemsg_entry, 1, 2, rowcount, rowcount + 1);
6227 gtk_editable_set_editable(GTK_EDITABLE(savemsg_entry), prefs_common.savemsg);
6228 g_signal_connect_after(G_OBJECT(savemsg_entry), "grab_focus",
6229 G_CALLBACK(compose_grab_focus_cb), compose);
6230 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6231 folderidentifier = folder_item_get_identifier(account_get_special_folder
6232 (compose->account, F_OUTBOX));
6233 gtk_entry_set_text(GTK_ENTRY(savemsg_entry), folderidentifier);
6234 g_free(folderidentifier);
6237 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
6238 gtk_widget_show(savemsg_select);
6239 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6240 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
6241 G_CALLBACK(compose_savemsg_select_cb),
6246 compose->savemsg_checkbtn = savemsg_checkbtn;
6247 compose->savemsg_entry = savemsg_entry;
6252 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
6254 gtk_editable_set_editable(GTK_EDITABLE(compose->savemsg_entry),
6255 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
6258 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
6263 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL);
6266 path = folder_item_get_identifier(dest);
6268 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), path);
6272 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
6273 GdkAtom clip, GtkTextIter *insert_place);
6276 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
6280 GtkTextBuffer *buffer;
6282 if (event->button == 3) {
6284 GtkTextIter sel_start, sel_end;
6285 gboolean stuff_selected;
6287 /* move the cursor to allow GtkAspell to check the word
6288 * under the mouse */
6289 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6290 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6292 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6295 stuff_selected = gtk_text_buffer_get_selection_bounds(
6296 GTK_TEXT_VIEW(text)->buffer,
6297 &sel_start, &sel_end);
6299 gtk_text_buffer_place_cursor (GTK_TEXT_VIEW(text)->buffer, &iter);
6300 /* reselect stuff */
6302 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
6303 gtk_text_buffer_select_range(GTK_TEXT_VIEW(text)->buffer,
6304 &sel_start, &sel_end);
6306 return FALSE; /* pass the event so that the right-click goes through */
6309 if (event->button == 2) {
6314 /* get the middle-click position to paste at the correct place */
6315 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6316 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6318 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6321 entry_paste_clipboard(compose, text,
6322 prefs_common.linewrap_pastes,
6323 GDK_SELECTION_PRIMARY, &iter);
6331 static void compose_spell_menu_changed(void *data)
6333 Compose *compose = (Compose *)data;
6335 GtkWidget *menuitem;
6336 GtkWidget *parent_item;
6337 GtkMenu *menu = GTK_MENU(gtk_menu_new());
6338 GtkItemFactory *ifactory = gtk_item_factory_from_widget(compose->menubar);
6341 if (compose->gtkaspell == NULL)
6344 parent_item = gtk_item_factory_get_item(ifactory,
6345 "/Spelling/Options");
6347 /* setting the submenu removes /Spelling/Options from the factory
6348 * so we need to save it */
6350 if (parent_item == NULL) {
6351 parent_item = compose->aspell_options_menu;
6352 gtk_menu_item_remove_submenu(GTK_MENU_ITEM(parent_item));
6354 compose->aspell_options_menu = parent_item;
6356 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
6358 spell_menu = g_slist_reverse(spell_menu);
6359 for (items = spell_menu;
6360 items; items = items->next) {
6361 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
6362 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
6363 gtk_widget_show(GTK_WIDGET(menuitem));
6365 g_slist_free(spell_menu);
6367 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
6372 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
6374 Compose *compose = (Compose *)data;
6375 GdkEventButton event;
6378 event.time = gtk_get_current_event_time();
6380 return text_clicked(compose->text, &event, compose);
6383 static gboolean compose_force_window_origin = TRUE;
6384 static Compose *compose_create(PrefsAccount *account,
6393 GtkWidget *handlebox;
6395 GtkWidget *notebook;
6400 GtkWidget *subject_hbox;
6401 GtkWidget *subject_frame;
6402 GtkWidget *subject_entry;
6406 GtkWidget *edit_vbox;
6407 GtkWidget *ruler_hbox;
6409 GtkWidget *scrolledwin;
6411 GtkTextBuffer *buffer;
6412 GtkClipboard *clipboard;
6414 UndoMain *undostruct;
6416 gchar *titles[N_ATTACH_COLS];
6417 guint n_menu_entries;
6418 GtkWidget *popupmenu;
6419 GtkItemFactory *popupfactory;
6420 GtkItemFactory *ifactory;
6421 GtkWidget *tmpl_menu;
6423 GtkWidget *menuitem;
6426 GtkAspell * gtkaspell = NULL;
6429 static GdkGeometry geometry;
6431 g_return_val_if_fail(account != NULL, NULL);
6433 debug_print("Creating compose window...\n");
6434 compose = g_new0(Compose, 1);
6436 titles[COL_MIMETYPE] = _("MIME type");
6437 titles[COL_SIZE] = _("Size");
6438 titles[COL_NAME] = _("Name");
6440 compose->batch = batch;
6441 compose->account = account;
6442 compose->folder = folder;
6444 compose->mutex = g_mutex_new();
6445 compose->set_cursor_pos = -1;
6447 compose->tooltips = gtk_tooltips_new();
6449 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
6451 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
6452 gtk_widget_set_size_request(window, -1, prefs_common.compose_height);
6454 if (!geometry.max_width) {
6455 geometry.max_width = gdk_screen_width();
6456 geometry.max_height = gdk_screen_height();
6459 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
6460 &geometry, GDK_HINT_MAX_SIZE);
6461 if (!geometry.min_width) {
6462 geometry.min_width = 600;
6463 geometry.min_height = 480;
6465 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
6466 &geometry, GDK_HINT_MIN_SIZE);
6469 if (compose_force_window_origin)
6470 gtk_widget_set_uposition(window, prefs_common.compose_x,
6471 prefs_common.compose_y);
6473 g_signal_connect(G_OBJECT(window), "delete_event",
6474 G_CALLBACK(compose_delete_cb), compose);
6475 MANAGE_WINDOW_SIGNALS_CONNECT(window);
6476 gtk_widget_realize(window);
6478 gtkut_widget_set_composer_icon(window);
6480 vbox = gtk_vbox_new(FALSE, 0);
6481 gtk_container_add(GTK_CONTAINER(window), vbox);
6483 n_menu_entries = sizeof(compose_entries) / sizeof(compose_entries[0]);
6484 menubar = menubar_create(window, compose_entries,
6485 n_menu_entries, "<Compose>", compose);
6486 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
6488 if (prefs_common.toolbar_detachable) {
6489 handlebox = gtk_handle_box_new();
6491 handlebox = gtk_hbox_new(FALSE, 0);
6493 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
6495 gtk_widget_realize(handlebox);
6497 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, window,
6500 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
6504 vbox2 = gtk_vbox_new(FALSE, 2);
6505 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
6506 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
6509 notebook = gtk_notebook_new();
6510 gtk_widget_set_size_request(notebook, -1, 130);
6511 gtk_widget_show(notebook);
6513 /* header labels and entries */
6514 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6515 compose_create_header(compose),
6516 gtk_label_new_with_mnemonic(_("Hea_der")));
6517 /* attachment list */
6518 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6519 compose_create_attach(compose),
6520 gtk_label_new_with_mnemonic(_("_Attachments")));
6522 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6523 compose_create_others(compose),
6524 gtk_label_new_with_mnemonic(_("Othe_rs")));
6527 subject_hbox = gtk_hbox_new(FALSE, 0);
6528 gtk_widget_show(subject_hbox);
6530 subject_frame = gtk_frame_new(NULL);
6531 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
6532 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
6533 gtk_widget_show(subject_frame);
6535 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
6536 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
6537 gtk_widget_show(subject);
6539 label = gtk_label_new(_("Subject:"));
6540 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
6541 gtk_widget_show(label);
6543 subject_entry = gtk_entry_new();
6544 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
6545 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
6546 G_CALLBACK(compose_grab_focus_cb), compose);
6547 gtk_widget_show(subject_entry);
6548 compose->subject_entry = subject_entry;
6549 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
6551 edit_vbox = gtk_vbox_new(FALSE, 0);
6553 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
6556 ruler_hbox = gtk_hbox_new(FALSE, 0);
6557 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
6559 ruler = gtk_shruler_new();
6560 gtk_ruler_set_range(GTK_RULER(ruler), 0.0, 100.0, 1.0, 100.0);
6561 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
6565 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
6566 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
6567 GTK_POLICY_AUTOMATIC,
6568 GTK_POLICY_AUTOMATIC);
6569 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
6571 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
6572 gtk_widget_set_size_request(scrolledwin, prefs_common.compose_width, -1);
6574 text = gtk_text_view_new();
6575 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
6576 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
6577 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
6578 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
6579 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
6581 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
6583 g_signal_connect_after(G_OBJECT(text), "size_allocate",
6584 G_CALLBACK(compose_edit_size_alloc),
6586 g_signal_connect(G_OBJECT(buffer), "changed",
6587 G_CALLBACK(compose_changed_cb), compose);
6588 g_signal_connect(G_OBJECT(text), "grab_focus",
6589 G_CALLBACK(compose_grab_focus_cb), compose);
6590 g_signal_connect(G_OBJECT(buffer), "insert_text",
6591 G_CALLBACK(text_inserted), compose);
6592 g_signal_connect(G_OBJECT(text), "button_press_event",
6593 G_CALLBACK(text_clicked), compose);
6595 g_signal_connect(G_OBJECT(text), "popup-menu",
6596 G_CALLBACK(compose_popup_menu), compose);
6598 gtk_widget_tap_and_hold_setup(GTK_WIDGET(text), NULL, NULL,
6599 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
6600 g_signal_connect(G_OBJECT(text), "tap-and-hold",
6601 G_CALLBACK(compose_popup_menu), compose);
6603 g_signal_connect(G_OBJECT(subject_entry), "changed",
6604 G_CALLBACK(compose_changed_cb), compose);
6607 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6608 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6609 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6610 g_signal_connect(G_OBJECT(text), "drag_data_received",
6611 G_CALLBACK(compose_insert_drag_received_cb),
6613 g_signal_connect(G_OBJECT(text), "drag-drop",
6614 G_CALLBACK(compose_drag_drop),
6616 gtk_widget_show_all(vbox);
6618 /* pane between attach clist and text */
6619 paned = gtk_vpaned_new();
6620 gtk_paned_set_gutter_size(GTK_PANED(paned), 12);
6621 gtk_container_add(GTK_CONTAINER(vbox2), paned);
6623 if( maemo_mainwindow_is_fullscreen(mainwindow_get_mainwindow()->window) )
6624 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 300 : 280);
6626 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 250 : 230);
6628 gtk_paned_add1(GTK_PANED(paned), notebook);
6629 gtk_paned_add2(GTK_PANED(paned), edit_vbox);
6630 gtk_widget_show_all(paned);
6633 if (prefs_common.textfont) {
6634 PangoFontDescription *font_desc;
6636 font_desc = pango_font_description_from_string
6637 (prefs_common.textfont);
6639 gtk_widget_modify_font(text, font_desc);
6640 pango_font_description_free(font_desc);
6644 n_entries = sizeof(compose_popup_entries) /
6645 sizeof(compose_popup_entries[0]);
6646 popupmenu = menu_create_items(compose_popup_entries, n_entries,
6647 "<Compose>", &popupfactory,
6650 ifactory = gtk_item_factory_from_widget(menubar);
6651 menu_set_sensitive(ifactory, "/Edit/Undo", FALSE);
6652 menu_set_sensitive(ifactory, "/Edit/Redo", FALSE);
6653 menu_set_sensitive(ifactory, "/Options/Remove references", FALSE);
6655 tmpl_menu = gtk_item_factory_get_item(ifactory, "/Tools/Template");
6657 undostruct = undo_init(text);
6658 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
6661 address_completion_start(window);
6663 compose->window = window;
6664 compose->vbox = vbox;
6665 compose->menubar = menubar;
6666 compose->handlebox = handlebox;
6668 compose->vbox2 = vbox2;
6670 compose->paned = paned;
6672 compose->notebook = notebook;
6673 compose->edit_vbox = edit_vbox;
6674 compose->ruler_hbox = ruler_hbox;
6675 compose->ruler = ruler;
6676 compose->scrolledwin = scrolledwin;
6677 compose->text = text;
6679 compose->focused_editable = NULL;
6681 compose->popupmenu = popupmenu;
6682 compose->popupfactory = popupfactory;
6684 compose->tmpl_menu = tmpl_menu;
6686 compose->mode = mode;
6687 compose->rmode = mode;
6689 compose->targetinfo = NULL;
6690 compose->replyinfo = NULL;
6691 compose->fwdinfo = NULL;
6693 compose->replyto = NULL;
6695 compose->bcc = NULL;
6696 compose->followup_to = NULL;
6698 compose->ml_post = NULL;
6700 compose->inreplyto = NULL;
6701 compose->references = NULL;
6702 compose->msgid = NULL;
6703 compose->boundary = NULL;
6705 compose->autowrap = prefs_common.autowrap;
6707 compose->use_signing = FALSE;
6708 compose->use_encryption = FALSE;
6709 compose->privacy_system = NULL;
6711 compose->modified = FALSE;
6713 compose->return_receipt = FALSE;
6715 compose->to_list = NULL;
6716 compose->newsgroup_list = NULL;
6718 compose->undostruct = undostruct;
6720 compose->sig_str = NULL;
6722 compose->exteditor_file = NULL;
6723 compose->exteditor_pid = -1;
6724 compose->exteditor_tag = -1;
6725 compose->draft_timeout_tag = -2; /* inhibit auto-drafting while loading */
6728 menu_set_sensitive(ifactory, "/Spelling", FALSE);
6729 if (mode != COMPOSE_REDIRECT) {
6730 if (prefs_common.enable_aspell && prefs_common.dictionary &&
6731 strcmp(prefs_common.dictionary, "")) {
6732 gtkaspell = gtkaspell_new(prefs_common.aspell_path,
6733 prefs_common.dictionary,
6734 prefs_common.alt_dictionary,
6735 conv_get_locale_charset_str(),
6736 prefs_common.misspelled_col,
6737 prefs_common.check_while_typing,
6738 prefs_common.recheck_when_changing_dict,
6739 prefs_common.use_alternate,
6740 prefs_common.use_both_dicts,
6741 GTK_TEXT_VIEW(text),
6742 GTK_WINDOW(compose->window),
6743 compose_spell_menu_changed,
6746 alertpanel_error(_("Spell checker could not "
6748 gtkaspell_checkers_strerror());
6749 gtkaspell_checkers_reset_error();
6751 if (!gtkaspell_set_sug_mode(gtkaspell,
6752 prefs_common.aspell_sugmode)) {
6753 debug_print("Aspell: could not set "
6754 "suggestion mode %s\n",
6755 gtkaspell_checkers_strerror());
6756 gtkaspell_checkers_reset_error();
6759 menu_set_sensitive(ifactory, "/Spelling", TRUE);
6763 compose->gtkaspell = gtkaspell;
6764 compose_spell_menu_changed(compose);
6767 compose_select_account(compose, account, TRUE);
6769 menu_set_active(ifactory, "/Edit/Auto wrapping", prefs_common.autowrap);
6770 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
6771 compose_entry_append(compose, account->auto_cc, COMPOSE_CC);
6773 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
6774 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC);
6776 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
6777 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO);
6779 menu_set_sensitive(ifactory, "/Options/Reply mode", compose->mode == COMPOSE_REPLY);
6781 if (account->protocol != A_NNTP)
6782 gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(compose->header_last->combo)->entry),
6783 prefs_common_translated_header_name("To:"));
6785 gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(compose->header_last->combo)->entry),
6786 prefs_common_translated_header_name("Newsgroups:"));
6788 addressbook_set_target_compose(compose);
6790 if (mode != COMPOSE_REDIRECT)
6791 compose_set_template_menu(compose);
6793 menuitem = gtk_item_factory_get_item(ifactory, "/Tools/Template");
6794 menu_set_sensitive(ifactory, "/Tools/Template", FALSE);
6797 compose_list = g_list_append(compose_list, compose);
6799 if (!prefs_common.show_ruler)
6800 gtk_widget_hide(ruler_hbox);
6802 menuitem = gtk_item_factory_get_item(ifactory, "/Tools/Show ruler");
6803 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
6804 prefs_common.show_ruler);
6807 compose->priority = PRIORITY_NORMAL;
6808 compose_update_priority_menu_item(compose);
6810 compose_set_out_encoding(compose);
6813 compose_update_actions_menu(compose);
6815 /* Privacy Systems menu */
6816 compose_update_privacy_systems_menu(compose);
6818 activate_privacy_system(compose, account, TRUE);
6819 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
6821 gtk_widget_realize(window);
6823 gtk_widget_show(window);
6825 maemo_window_full_screen_if_needed(GTK_WINDOW(window));
6826 maemo_connect_key_press_to_mainwindow(GTK_WINDOW(window));
6833 static GtkWidget *compose_account_option_menu_create(Compose *compose)
6838 GtkWidget *optmenubox;
6841 GtkWidget *from_name = NULL;
6843 gint num = 0, def_menu = 0;
6845 accounts = account_get_list();
6846 g_return_val_if_fail(accounts != NULL, NULL);
6848 optmenubox = gtk_event_box_new();
6849 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
6850 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
6852 hbox = gtk_hbox_new(FALSE, 6);
6853 from_name = gtk_entry_new();
6855 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
6856 G_CALLBACK(compose_grab_focus_cb), compose);
6858 for (; accounts != NULL; accounts = accounts->next, num++) {
6859 PrefsAccount *ac = (PrefsAccount *)accounts->data;
6860 gchar *name, *from = NULL;
6862 if (ac == compose->account) def_menu = num;
6864 name = g_markup_printf_escaped(_("From: <i>%s</i>"),
6867 if (ac == compose->account) {
6868 if (ac->name && *ac->name) {
6870 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
6871 from = g_strdup_printf("%s <%s>",
6873 gtk_entry_set_text(GTK_ENTRY(from_name), from);
6875 from = g_strdup_printf("%s",
6877 gtk_entry_set_text(GTK_ENTRY(from_name), from);
6880 COMBOBOX_ADD(menu, name, ac->account_id);
6885 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
6887 g_signal_connect(G_OBJECT(optmenu), "changed",
6888 G_CALLBACK(account_activated),
6890 g_signal_connect(G_OBJECT(from_name), "populate-popup",
6891 G_CALLBACK(compose_entry_popup_extend),
6894 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
6895 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
6897 gtk_tooltips_set_tip(compose->tooltips, optmenubox,
6898 _("Account to use for this email"), NULL);
6899 gtk_tooltips_set_tip(compose->tooltips, from_name,
6900 _("Sender address to be used"), NULL);
6902 compose->from_name = from_name;
6907 static void compose_set_priority_cb(gpointer data,
6911 Compose *compose = (Compose *) data;
6912 compose->priority = action;
6915 static void compose_reply_change_mode(gpointer data,
6919 Compose *compose = (Compose *) data;
6920 gboolean was_modified = compose->modified;
6922 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
6924 g_return_if_fail(compose->replyinfo != NULL);
6926 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
6928 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
6930 if (action == COMPOSE_REPLY_TO_ALL)
6932 if (action == COMPOSE_REPLY_TO_SENDER)
6934 if (action == COMPOSE_REPLY_TO_LIST)
6937 compose_remove_header_entries(compose);
6938 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
6939 if (compose->account->set_autocc && compose->account->auto_cc)
6940 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC);
6942 if (compose->account->set_autobcc && compose->account->auto_bcc)
6943 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC);
6945 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
6946 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO);
6947 compose_show_first_last_header(compose, TRUE);
6948 compose->modified = was_modified;
6949 compose_set_title(compose);
6952 static void compose_update_priority_menu_item(Compose * compose)
6954 GtkItemFactory *ifactory;
6955 GtkWidget *menuitem = NULL;
6957 ifactory = gtk_item_factory_from_widget(compose->menubar);
6959 switch (compose->priority) {
6960 case PRIORITY_HIGHEST:
6961 menuitem = gtk_item_factory_get_item
6962 (ifactory, "/Options/Priority/Highest");
6965 menuitem = gtk_item_factory_get_item
6966 (ifactory, "/Options/Priority/High");
6968 case PRIORITY_NORMAL:
6969 menuitem = gtk_item_factory_get_item
6970 (ifactory, "/Options/Priority/Normal");
6973 menuitem = gtk_item_factory_get_item
6974 (ifactory, "/Options/Priority/Low");
6976 case PRIORITY_LOWEST:
6977 menuitem = gtk_item_factory_get_item
6978 (ifactory, "/Options/Priority/Lowest");
6981 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
6984 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
6986 Compose *compose = (Compose *) data;
6988 GtkItemFactory *ifactory;
6989 gboolean can_sign = FALSE, can_encrypt = FALSE;
6991 g_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
6993 if (!GTK_CHECK_MENU_ITEM(widget)->active)
6996 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
6997 g_free(compose->privacy_system);
6998 compose->privacy_system = NULL;
6999 if (systemid != NULL) {
7000 compose->privacy_system = g_strdup(systemid);
7002 can_sign = privacy_system_can_sign(systemid);
7003 can_encrypt = privacy_system_can_encrypt(systemid);
7006 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
7008 ifactory = gtk_item_factory_from_widget(compose->menubar);
7009 menu_set_sensitive(ifactory, "/Options/Sign", can_sign);
7010 menu_set_sensitive(ifactory, "/Options/Encrypt", can_encrypt);
7013 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
7015 static gchar *branch_path = "/Options/Privacy System";
7016 GtkItemFactory *ifactory;
7017 GtkWidget *menuitem = NULL;
7019 gboolean can_sign = FALSE, can_encrypt = FALSE;
7020 gboolean found = FALSE;
7022 ifactory = gtk_item_factory_from_widget(compose->menubar);
7024 if (compose->privacy_system != NULL) {
7027 menuitem = gtk_item_factory_get_widget(ifactory, branch_path);
7028 g_return_if_fail(menuitem != NULL);
7030 amenu = GTK_MENU_SHELL(menuitem)->children;
7032 while (amenu != NULL) {
7033 GList *alist = amenu->next;
7035 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
7036 if (systemid != NULL) {
7037 if (strcmp(systemid, compose->privacy_system) == 0) {
7038 menuitem = GTK_WIDGET(amenu->data);
7040 can_sign = privacy_system_can_sign(systemid);
7041 can_encrypt = privacy_system_can_encrypt(systemid);
7045 } else if (strlen(compose->privacy_system) == 0) {
7046 menuitem = GTK_WIDGET(amenu->data);
7049 can_encrypt = FALSE;
7056 if (menuitem != NULL)
7057 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
7059 if (warn && !found && strlen(compose->privacy_system)) {
7060 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
7061 "will not be able to sign or encrypt this message."),
7062 compose->privacy_system);
7066 menu_set_sensitive(ifactory, "/Options/Sign", can_sign);
7067 menu_set_sensitive(ifactory, "/Options/Encrypt", can_encrypt);
7070 static void compose_set_out_encoding(Compose *compose)
7072 GtkItemFactoryEntry *entry;
7073 GtkItemFactory *ifactory;
7074 CharSet out_encoding;
7075 gchar *path, *p, *q;
7078 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
7079 ifactory = gtk_item_factory_from_widget(compose->menubar);
7081 for (entry = compose_entries; entry->callback != compose_address_cb;
7083 if (entry->callback == compose_set_encoding_cb &&
7084 (CharSet)entry->callback_action == out_encoding) {
7085 p = q = path = g_strdup(entry->path);
7097 item = gtk_item_factory_get_item(ifactory, path);
7098 gtk_widget_activate(item);
7105 static void compose_set_template_menu(Compose *compose)
7107 GSList *tmpl_list, *cur;
7111 tmpl_list = template_get_config();
7113 menu = gtk_menu_new();
7115 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
7116 Template *tmpl = (Template *)cur->data;
7118 item = gtk_menu_item_new_with_label(tmpl->name);
7119 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
7120 g_signal_connect(G_OBJECT(item), "activate",
7121 G_CALLBACK(compose_template_activate_cb),
7123 g_object_set_data(G_OBJECT(item), "template", tmpl);
7124 gtk_widget_show(item);
7127 gtk_widget_show(menu);
7128 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
7131 void compose_update_actions_menu(Compose *compose)
7133 GtkItemFactory *ifactory;
7135 ifactory = gtk_item_factory_from_widget(compose->menubar);
7136 action_update_compose_menu(ifactory, "/Tools/Actions", compose);
7139 static void compose_update_privacy_systems_menu(Compose *compose)
7141 static gchar *branch_path = "/Options/Privacy System";
7142 GtkItemFactory *ifactory;
7143 GtkWidget *menuitem;
7144 GSList *systems, *cur;
7147 GtkWidget *system_none;
7150 ifactory = gtk_item_factory_from_widget(compose->menubar);
7152 /* remove old entries */
7153 menuitem = gtk_item_factory_get_widget(ifactory, branch_path);
7154 g_return_if_fail(menuitem != NULL);
7156 amenu = GTK_MENU_SHELL(menuitem)->children->next;
7157 while (amenu != NULL) {
7158 GList *alist = amenu->next;
7159 gtk_widget_destroy(GTK_WIDGET(amenu->data));
7163 system_none = gtk_item_factory_get_widget(ifactory,
7164 "/Options/Privacy System/None");
7166 g_signal_connect(G_OBJECT(system_none), "activate",
7167 G_CALLBACK(compose_set_privacy_system_cb), compose);
7169 systems = privacy_get_system_ids();
7170 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
7171 gchar *systemid = cur->data;
7173 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
7174 widget = gtk_radio_menu_item_new_with_label(group,
7175 privacy_system_get_name(systemid));
7176 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
7177 g_strdup(systemid), g_free);
7178 g_signal_connect(G_OBJECT(widget), "activate",
7179 G_CALLBACK(compose_set_privacy_system_cb), compose);
7181 gtk_menu_append(GTK_MENU(system_none->parent), widget);
7182 gtk_widget_show(widget);
7185 g_slist_free(systems);
7188 void compose_reflect_prefs_all(void)
7193 for (cur = compose_list; cur != NULL; cur = cur->next) {
7194 compose = (Compose *)cur->data;
7195 compose_set_template_menu(compose);
7199 void compose_reflect_prefs_pixmap_theme(void)
7204 for (cur = compose_list; cur != NULL; cur = cur->next) {
7205 compose = (Compose *)cur->data;
7206 toolbar_update(TOOLBAR_COMPOSE, compose);
7210 static const gchar *compose_quote_char_from_context(Compose *compose)
7212 const gchar *qmark = NULL;
7214 g_return_val_if_fail(compose != NULL, NULL);
7216 switch (compose->mode) {
7217 /* use forward-specific quote char */
7218 case COMPOSE_FORWARD:
7219 case COMPOSE_FORWARD_AS_ATTACH:
7220 case COMPOSE_FORWARD_INLINE:
7221 if (compose->folder && compose->folder->prefs &&
7222 compose->folder->prefs->forward_with_format)
7223 qmark = compose->folder->prefs->forward_quotemark;
7224 else if (compose->account->forward_with_format)
7225 qmark = compose->account->forward_quotemark;
7227 qmark = prefs_common.fw_quotemark;
7230 /* use reply-specific quote char in all other modes */
7232 if (compose->folder && compose->folder->prefs &&
7233 compose->folder->prefs->reply_with_format)
7234 qmark = compose->folder->prefs->reply_quotemark;
7235 else if (compose->account->reply_with_format)
7236 qmark = compose->account->reply_quotemark;
7238 qmark = prefs_common.quotemark;
7242 if (qmark == NULL || *qmark == '\0')
7248 static void compose_template_apply(Compose *compose, Template *tmpl,
7252 GtkTextBuffer *buffer;
7256 gchar *parsed_str = NULL;
7257 gint cursor_pos = 0;
7258 const gchar *err_msg = _("Template body format error at line %d.");
7261 /* process the body */
7263 text = GTK_TEXT_VIEW(compose->text);
7264 buffer = gtk_text_view_get_buffer(text);
7267 qmark = compose_quote_char_from_context(compose);
7269 if (compose->replyinfo != NULL) {
7272 gtk_text_buffer_set_text(buffer, "", -1);
7273 mark = gtk_text_buffer_get_insert(buffer);
7274 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7276 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
7277 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
7279 } else if (compose->fwdinfo != NULL) {
7282 gtk_text_buffer_set_text(buffer, "", -1);
7283 mark = gtk_text_buffer_get_insert(buffer);
7284 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7286 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
7287 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
7290 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
7292 GtkTextIter start, end;
7295 gtk_text_buffer_get_start_iter(buffer, &start);
7296 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
7297 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
7299 /* clear the buffer now */
7301 gtk_text_buffer_set_text(buffer, "", -1);
7303 parsed_str = compose_quote_fmt(compose, dummyinfo,
7304 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
7305 procmsg_msginfo_free( dummyinfo );
7311 gtk_text_buffer_set_text(buffer, "", -1);
7312 mark = gtk_text_buffer_get_insert(buffer);
7313 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7316 if (replace && parsed_str && compose->account->auto_sig)
7317 compose_insert_sig(compose, FALSE);
7319 if (replace && parsed_str) {
7320 gtk_text_buffer_get_start_iter(buffer, &iter);
7321 gtk_text_buffer_place_cursor(buffer, &iter);
7325 cursor_pos = quote_fmt_get_cursor_pos();
7326 compose->set_cursor_pos = cursor_pos;
7327 if (cursor_pos == -1)
7329 gtk_text_buffer_get_start_iter(buffer, &iter);
7330 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
7331 gtk_text_buffer_place_cursor(buffer, &iter);
7334 /* process the other fields */
7336 compose_template_apply_fields(compose, tmpl);
7337 quote_fmt_reset_vartable();
7338 compose_changed_cb(NULL, compose);
7341 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
7343 MsgInfo* dummyinfo = NULL;
7344 MsgInfo *msginfo = NULL;
7347 if (compose->replyinfo != NULL)
7348 msginfo = compose->replyinfo;
7349 else if (compose->fwdinfo != NULL)
7350 msginfo = compose->fwdinfo;
7352 dummyinfo = compose_msginfo_new_from_compose(compose);
7353 msginfo = dummyinfo;
7356 if (tmpl->to && *tmpl->to != '\0') {
7358 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7359 compose->gtkaspell);
7361 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7363 quote_fmt_scan_string(tmpl->to);
7366 buf = quote_fmt_get_buffer();
7368 alertpanel_error(_("Template To format error."));
7370 compose_entry_append(compose, buf, COMPOSE_TO);
7374 if (tmpl->cc && *tmpl->cc != '\0') {
7376 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7377 compose->gtkaspell);
7379 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7381 quote_fmt_scan_string(tmpl->cc);
7384 buf = quote_fmt_get_buffer();
7386 alertpanel_error(_("Template Cc format error."));
7388 compose_entry_append(compose, buf, COMPOSE_CC);
7392 if (tmpl->bcc && *tmpl->bcc != '\0') {
7394 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7395 compose->gtkaspell);
7397 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7399 quote_fmt_scan_string(tmpl->bcc);
7402 buf = quote_fmt_get_buffer();
7404 alertpanel_error(_("Template Bcc format error."));
7406 compose_entry_append(compose, buf, COMPOSE_BCC);
7410 /* process the subject */
7411 if (tmpl->subject && *tmpl->subject != '\0') {
7413 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7414 compose->gtkaspell);
7416 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7418 quote_fmt_scan_string(tmpl->subject);
7421 buf = quote_fmt_get_buffer();
7423 alertpanel_error(_("Template subject format error."));
7425 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
7429 procmsg_msginfo_free( dummyinfo );
7432 static void compose_destroy(Compose *compose)
7434 GtkTextBuffer *buffer;
7435 GtkClipboard *clipboard;
7437 compose_list = g_list_remove(compose_list, compose);
7439 if (compose->updating) {
7440 debug_print("danger, not destroying anything now\n");
7441 compose->deferred_destroy = TRUE;
7444 /* NOTE: address_completion_end() does nothing with the window
7445 * however this may change. */
7446 address_completion_end(compose->window);
7448 slist_free_strings(compose->to_list);
7449 g_slist_free(compose->to_list);
7450 slist_free_strings(compose->newsgroup_list);
7451 g_slist_free(compose->newsgroup_list);
7452 slist_free_strings(compose->header_list);
7453 g_slist_free(compose->header_list);
7455 procmsg_msginfo_free(compose->targetinfo);
7456 procmsg_msginfo_free(compose->replyinfo);
7457 procmsg_msginfo_free(compose->fwdinfo);
7459 g_free(compose->replyto);
7460 g_free(compose->cc);
7461 g_free(compose->bcc);
7462 g_free(compose->newsgroups);
7463 g_free(compose->followup_to);
7465 g_free(compose->ml_post);
7467 g_free(compose->inreplyto);
7468 g_free(compose->references);
7469 g_free(compose->msgid);
7470 g_free(compose->boundary);
7472 g_free(compose->redirect_filename);
7473 if (compose->undostruct)
7474 undo_destroy(compose->undostruct);
7476 g_free(compose->sig_str);
7478 g_free(compose->exteditor_file);
7480 g_free(compose->orig_charset);
7482 g_free(compose->privacy_system);
7484 if (addressbook_get_target_compose() == compose)
7485 addressbook_set_target_compose(NULL);
7488 if (compose->gtkaspell) {
7489 gtkaspell_delete(compose->gtkaspell);
7490 compose->gtkaspell = NULL;
7494 prefs_common.compose_width = compose->scrolledwin->allocation.width;
7495 prefs_common.compose_height = compose->window->allocation.height;
7497 if (!gtk_widget_get_parent(compose->paned))
7498 gtk_widget_destroy(compose->paned);
7499 gtk_widget_destroy(compose->popupmenu);
7501 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
7502 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7503 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
7505 gtk_widget_destroy(compose->window);
7506 toolbar_destroy(compose->toolbar);
7507 g_free(compose->toolbar);
7508 g_mutex_free(compose->mutex);
7512 static void compose_attach_info_free(AttachInfo *ainfo)
7514 g_free(ainfo->file);
7515 g_free(ainfo->content_type);
7516 g_free(ainfo->name);
7520 static void compose_attach_remove_selected(Compose *compose)
7522 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
7523 GtkTreeSelection *selection;
7525 GtkTreeModel *model;
7527 selection = gtk_tree_view_get_selection(tree_view);
7528 sel = gtk_tree_selection_get_selected_rows(selection, &model);
7533 for (cur = sel; cur != NULL; cur = cur->next) {
7534 GtkTreePath *path = cur->data;
7535 GtkTreeRowReference *ref = gtk_tree_row_reference_new
7538 gtk_tree_path_free(path);
7541 for (cur = sel; cur != NULL; cur = cur->next) {
7542 GtkTreeRowReference *ref = cur->data;
7543 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
7546 if (gtk_tree_model_get_iter(model, &iter, path))
7547 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
7549 gtk_tree_path_free(path);
7550 gtk_tree_row_reference_free(ref);
7556 static struct _AttachProperty
7559 GtkWidget *mimetype_entry;
7560 GtkWidget *encoding_optmenu;
7561 GtkWidget *path_entry;
7562 GtkWidget *filename_entry;
7564 GtkWidget *cancel_btn;
7567 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
7569 gtk_tree_path_free((GtkTreePath *)ptr);
7572 static void compose_attach_property(Compose *compose)
7574 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
7576 GtkComboBox *optmenu;
7577 GtkTreeSelection *selection;
7579 GtkTreeModel *model;
7582 static gboolean cancelled;
7584 /* only if one selected */
7585 selection = gtk_tree_view_get_selection(tree_view);
7586 if (gtk_tree_selection_count_selected_rows(selection) != 1)
7589 sel = gtk_tree_selection_get_selected_rows(selection, &model);
7593 path = (GtkTreePath *) sel->data;
7594 gtk_tree_model_get_iter(model, &iter, path);
7595 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
7598 g_list_foreach(sel, gtk_tree_path_free_, NULL);
7604 if (!attach_prop.window)
7605 compose_attach_property_create(&cancelled);
7606 gtk_widget_grab_focus(attach_prop.ok_btn);
7607 gtk_widget_show(attach_prop.window);
7608 manage_window_set_transient(GTK_WINDOW(attach_prop.window));
7610 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
7611 if (ainfo->encoding == ENC_UNKNOWN)
7612 combobox_select_by_data(optmenu, ENC_BASE64);
7614 combobox_select_by_data(optmenu, ainfo->encoding);
7616 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
7617 ainfo->content_type ? ainfo->content_type : "");
7618 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
7619 ainfo->file ? ainfo->file : "");
7620 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
7621 ainfo->name ? ainfo->name : "");
7624 const gchar *entry_text;
7626 gchar *cnttype = NULL;
7633 gtk_widget_hide(attach_prop.window);
7638 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
7639 if (*entry_text != '\0') {
7642 text = g_strstrip(g_strdup(entry_text));
7643 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
7644 cnttype = g_strdup(text);
7647 alertpanel_error(_("Invalid MIME type."));
7653 ainfo->encoding = combobox_get_active_data(optmenu);
7655 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
7656 if (*entry_text != '\0') {
7657 if (is_file_exist(entry_text) &&
7658 (size = get_file_size(entry_text)) > 0)
7659 file = g_strdup(entry_text);
7662 (_("File doesn't exist or is empty."));
7668 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
7669 if (*entry_text != '\0') {
7670 g_free(ainfo->name);
7671 ainfo->name = g_strdup(entry_text);
7675 g_free(ainfo->content_type);
7676 ainfo->content_type = cnttype;
7679 g_free(ainfo->file);
7685 /* update tree store */
7686 text = to_human_readable(ainfo->size);
7687 gtk_tree_model_get_iter(model, &iter, path);
7688 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
7689 COL_MIMETYPE, ainfo->content_type,
7691 COL_NAME, ainfo->name,
7697 gtk_tree_path_free(path);
7700 #define SET_LABEL_AND_ENTRY(str, entry, top) \
7702 label = gtk_label_new(str); \
7703 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
7704 GTK_FILL, 0, 0, 0); \
7705 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
7707 entry = gtk_entry_new(); \
7708 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
7709 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
7712 static void compose_attach_property_create(gboolean *cancelled)
7718 GtkWidget *mimetype_entry;
7721 GtkListStore *optmenu_menu;
7722 GtkWidget *path_entry;
7723 GtkWidget *filename_entry;
7726 GtkWidget *cancel_btn;
7727 GList *mime_type_list, *strlist;
7730 debug_print("Creating attach_property window...\n");
7732 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
7733 gtk_widget_set_size_request(window, 480, -1);
7734 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
7735 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
7736 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
7737 gtk_window_set_modal(GTK_WINDOW(window), TRUE);
7738 g_signal_connect(G_OBJECT(window), "delete_event",
7739 G_CALLBACK(attach_property_delete_event),
7741 g_signal_connect(G_OBJECT(window), "key_press_event",
7742 G_CALLBACK(attach_property_key_pressed),
7745 vbox = gtk_vbox_new(FALSE, 8);
7746 gtk_container_add(GTK_CONTAINER(window), vbox);
7748 table = gtk_table_new(4, 2, FALSE);
7749 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
7750 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
7751 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
7753 label = gtk_label_new(_("MIME type"));
7754 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
7756 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
7757 mimetype_entry = gtk_combo_new();
7758 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
7759 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
7761 /* stuff with list */
7762 mime_type_list = procmime_get_mime_type_list();
7764 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
7765 MimeType *type = (MimeType *) mime_type_list->data;
7768 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
7770 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
7773 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
7774 (GCompareFunc)strcmp2);
7777 gtk_combo_set_popdown_strings(GTK_COMBO(mimetype_entry), strlist);
7779 for (mime_type_list = strlist; mime_type_list != NULL;
7780 mime_type_list = mime_type_list->next)
7781 g_free(mime_type_list->data);
7782 g_list_free(strlist);
7784 mimetype_entry = GTK_COMBO(mimetype_entry)->entry;
7786 label = gtk_label_new(_("Encoding"));
7787 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
7789 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
7791 hbox = gtk_hbox_new(FALSE, 0);
7792 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
7793 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
7795 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
7796 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
7798 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
7799 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
7800 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
7801 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
7802 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
7804 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
7806 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
7807 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
7809 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
7810 &ok_btn, GTK_STOCK_OK,
7812 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
7813 gtk_widget_grab_default(ok_btn);
7815 g_signal_connect(G_OBJECT(ok_btn), "clicked",
7816 G_CALLBACK(attach_property_ok),
7818 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
7819 G_CALLBACK(attach_property_cancel),
7822 gtk_widget_show_all(vbox);
7824 attach_prop.window = window;
7825 attach_prop.mimetype_entry = mimetype_entry;
7826 attach_prop.encoding_optmenu = optmenu;
7827 attach_prop.path_entry = path_entry;
7828 attach_prop.filename_entry = filename_entry;
7829 attach_prop.ok_btn = ok_btn;
7830 attach_prop.cancel_btn = cancel_btn;
7833 #undef SET_LABEL_AND_ENTRY
7835 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
7841 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
7847 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
7848 gboolean *cancelled)
7856 static gboolean attach_property_key_pressed(GtkWidget *widget,
7858 gboolean *cancelled)
7860 if (event && event->keyval == GDK_Escape) {
7867 static void compose_exec_ext_editor(Compose *compose)
7874 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
7875 G_DIR_SEPARATOR, compose);
7877 if (pipe(pipe_fds) < 0) {
7883 if ((pid = fork()) < 0) {
7890 /* close the write side of the pipe */
7893 compose->exteditor_file = g_strdup(tmp);
7894 compose->exteditor_pid = pid;
7896 compose_set_ext_editor_sensitive(compose, FALSE);
7898 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
7899 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
7903 } else { /* process-monitoring process */
7909 /* close the read side of the pipe */
7912 if (compose_write_body_to_file(compose, tmp) < 0) {
7913 fd_write_all(pipe_fds[1], "2\n", 2);
7917 pid_ed = compose_exec_ext_editor_real(tmp);
7919 fd_write_all(pipe_fds[1], "1\n", 2);
7923 /* wait until editor is terminated */
7924 waitpid(pid_ed, NULL, 0);
7926 fd_write_all(pipe_fds[1], "0\n", 2);
7933 #endif /* G_OS_UNIX */
7937 static gint compose_exec_ext_editor_real(const gchar *file)
7944 g_return_val_if_fail(file != NULL, -1);
7946 if ((pid = fork()) < 0) {
7951 if (pid != 0) return pid;
7953 /* grandchild process */
7955 if (setpgid(0, getppid()))
7958 if (prefs_common.ext_editor_cmd &&
7959 (p = strchr(prefs_common.ext_editor_cmd, '%')) &&
7960 *(p + 1) == 's' && !strchr(p + 2, '%')) {
7961 g_snprintf(buf, sizeof(buf), prefs_common.ext_editor_cmd, file);
7963 if (prefs_common.ext_editor_cmd)
7964 g_warning("External editor command line is invalid: '%s'\n",
7965 prefs_common.ext_editor_cmd);
7966 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
7969 cmdline = strsplit_with_quote(buf, " ", 1024);
7970 execvp(cmdline[0], cmdline);
7973 g_strfreev(cmdline);
7978 static gboolean compose_ext_editor_kill(Compose *compose)
7980 pid_t pgid = compose->exteditor_pid * -1;
7983 ret = kill(pgid, 0);
7985 if (ret == 0 || (ret == -1 && EPERM == errno)) {
7989 msg = g_strdup_printf
7990 (_("The external editor is still working.\n"
7991 "Force terminating the process?\n"
7992 "process group id: %d"), -pgid);
7993 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
7994 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
7998 if (val == G_ALERTALTERNATE) {
7999 g_source_remove(compose->exteditor_tag);
8000 g_io_channel_shutdown(compose->exteditor_ch,
8002 g_io_channel_unref(compose->exteditor_ch);
8004 if (kill(pgid, SIGTERM) < 0) perror("kill");
8005 waitpid(compose->exteditor_pid, NULL, 0);
8007 g_warning("Terminated process group id: %d", -pgid);
8008 g_warning("Temporary file: %s",
8009 compose->exteditor_file);
8011 compose_set_ext_editor_sensitive(compose, TRUE);
8013 g_free(compose->exteditor_file);
8014 compose->exteditor_file = NULL;
8015 compose->exteditor_pid = -1;
8016 compose->exteditor_ch = NULL;
8017 compose->exteditor_tag = -1;
8025 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
8029 Compose *compose = (Compose *)data;
8032 debug_print(_("Compose: input from monitoring process\n"));
8034 g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL);
8036 g_io_channel_shutdown(source, FALSE, NULL);
8037 g_io_channel_unref(source);
8039 waitpid(compose->exteditor_pid, NULL, 0);
8041 if (buf[0] == '0') { /* success */
8042 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
8043 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
8045 gtk_text_buffer_set_text(buffer, "", -1);
8046 compose_insert_file(compose, compose->exteditor_file);
8047 compose_changed_cb(NULL, compose);
8049 if (g_unlink(compose->exteditor_file) < 0)
8050 FILE_OP_ERROR(compose->exteditor_file, "unlink");
8051 } else if (buf[0] == '1') { /* failed */
8052 g_warning("Couldn't exec external editor\n");
8053 if (g_unlink(compose->exteditor_file) < 0)
8054 FILE_OP_ERROR(compose->exteditor_file, "unlink");
8055 } else if (buf[0] == '2') {
8056 g_warning("Couldn't write to file\n");
8057 } else if (buf[0] == '3') {
8058 g_warning("Pipe read failed\n");
8061 compose_set_ext_editor_sensitive(compose, TRUE);
8063 g_free(compose->exteditor_file);
8064 compose->exteditor_file = NULL;
8065 compose->exteditor_pid = -1;
8066 compose->exteditor_ch = NULL;
8067 compose->exteditor_tag = -1;
8072 static void compose_set_ext_editor_sensitive(Compose *compose,
8075 GtkItemFactory *ifactory;
8077 ifactory = gtk_item_factory_from_widget(compose->menubar);
8079 menu_set_sensitive(ifactory, "/Message/Send", sensitive);
8080 menu_set_sensitive(ifactory, "/Message/Send later", sensitive);
8081 menu_set_sensitive(ifactory, "/Message/Insert file", sensitive);
8082 menu_set_sensitive(ifactory, "/Message/Insert signature", sensitive);
8083 menu_set_sensitive(ifactory, "/Edit/Wrap current paragraph", sensitive);
8084 menu_set_sensitive(ifactory, "/Edit/Wrap all long lines", sensitive);
8085 menu_set_sensitive(ifactory, "/Edit/Edit with external editor",
8088 gtk_widget_set_sensitive(compose->text, sensitive);
8089 if (compose->toolbar->send_btn)
8090 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
8091 if (compose->toolbar->sendl_btn)
8092 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
8093 if (compose->toolbar->draft_btn)
8094 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
8095 if (compose->toolbar->insert_btn)
8096 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
8097 if (compose->toolbar->sig_btn)
8098 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
8099 if (compose->toolbar->exteditor_btn)
8100 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
8101 if (compose->toolbar->linewrap_current_btn)
8102 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
8103 if (compose->toolbar->linewrap_all_btn)
8104 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
8106 #endif /* G_OS_UNIX */
8109 * compose_undo_state_changed:
8111 * Change the sensivity of the menuentries undo and redo
8113 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
8114 gint redo_state, gpointer data)
8116 GtkWidget *widget = GTK_WIDGET(data);
8117 GtkItemFactory *ifactory;
8119 g_return_if_fail(widget != NULL);
8121 ifactory = gtk_item_factory_from_widget(widget);
8123 switch (undo_state) {
8124 case UNDO_STATE_TRUE:
8125 if (!undostruct->undo_state) {
8126 undostruct->undo_state = TRUE;
8127 menu_set_sensitive(ifactory, "/Edit/Undo", TRUE);
8130 case UNDO_STATE_FALSE:
8131 if (undostruct->undo_state) {
8132 undostruct->undo_state = FALSE;
8133 menu_set_sensitive(ifactory, "/Edit/Undo", FALSE);
8136 case UNDO_STATE_UNCHANGED:
8138 case UNDO_STATE_REFRESH:
8139 menu_set_sensitive(ifactory, "/Edit/Undo",
8140 undostruct->undo_state);
8143 g_warning("Undo state not recognized");
8147 switch (redo_state) {
8148 case UNDO_STATE_TRUE:
8149 if (!undostruct->redo_state) {
8150 undostruct->redo_state = TRUE;
8151 menu_set_sensitive(ifactory, "/Edit/Redo", TRUE);
8154 case UNDO_STATE_FALSE:
8155 if (undostruct->redo_state) {
8156 undostruct->redo_state = FALSE;
8157 menu_set_sensitive(ifactory, "/Edit/Redo", FALSE);
8160 case UNDO_STATE_UNCHANGED:
8162 case UNDO_STATE_REFRESH:
8163 menu_set_sensitive(ifactory, "/Edit/Redo",
8164 undostruct->redo_state);
8167 g_warning("Redo state not recognized");
8172 /* callback functions */
8174 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
8175 * includes "non-client" (windows-izm) in calculation, so this calculation
8176 * may not be accurate.
8178 static gboolean compose_edit_size_alloc(GtkEditable *widget,
8179 GtkAllocation *allocation,
8180 GtkSHRuler *shruler)
8182 if (prefs_common.show_ruler) {
8183 gint char_width = 0, char_height = 0;
8184 gint line_width_in_chars;
8186 gtkut_get_font_size(GTK_WIDGET(widget),
8187 &char_width, &char_height);
8188 line_width_in_chars =
8189 (allocation->width - allocation->x) / char_width;
8191 /* got the maximum */
8192 gtk_ruler_set_range(GTK_RULER(shruler),
8193 0.0, line_width_in_chars, 0,
8194 /*line_width_in_chars*/ char_width);
8200 static void account_activated(GtkComboBox *optmenu, gpointer data)
8202 Compose *compose = (Compose *)data;
8205 gchar *folderidentifier;
8206 gint account_id = 0;
8210 /* Get ID of active account in the combo box */
8211 menu = gtk_combo_box_get_model(optmenu);
8212 gtk_combo_box_get_active_iter(optmenu, &iter);
8213 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
8215 ac = account_find_from_id(account_id);
8216 g_return_if_fail(ac != NULL);
8218 if (ac != compose->account)
8219 compose_select_account(compose, ac, FALSE);
8221 /* Set message save folder */
8222 if (account_get_special_folder(compose->account, F_OUTBOX)) {
8223 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
8225 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
8226 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
8228 gtk_editable_delete_text(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
8229 if (account_get_special_folder(compose->account, F_OUTBOX)) {
8230 folderidentifier = folder_item_get_identifier(account_get_special_folder
8231 (compose->account, F_OUTBOX));
8232 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
8233 g_free(folderidentifier);
8237 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
8238 GtkTreeViewColumn *column, Compose *compose)
8240 compose_attach_property(compose);
8243 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
8246 Compose *compose = (Compose *)data;
8247 GtkTreeSelection *attach_selection;
8248 gint attach_nr_selected;
8249 GtkItemFactory *ifactory;
8251 if (!event) return FALSE;
8253 if (event->button == 3) {
8254 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
8255 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
8256 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
8258 if (attach_nr_selected > 0)
8260 menu_set_sensitive(ifactory, "/Remove", TRUE);
8261 menu_set_sensitive(ifactory, "/Properties...", TRUE);
8263 menu_set_sensitive(ifactory, "/Remove", FALSE);
8264 menu_set_sensitive(ifactory, "/Properties...", FALSE);
8267 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
8268 NULL, NULL, event->button, event->time);
8275 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
8278 Compose *compose = (Compose *)data;
8280 if (!event) return FALSE;
8282 switch (event->keyval) {
8284 compose_attach_remove_selected(compose);
8290 static void compose_allow_user_actions (Compose *compose, gboolean allow)
8292 GtkItemFactory *ifactory = gtk_item_factory_from_widget(compose->menubar);
8293 toolbar_comp_set_sensitive(compose, allow);
8294 menu_set_sensitive(ifactory, "/Message", allow);
8295 menu_set_sensitive(ifactory, "/Edit", allow);
8297 menu_set_sensitive(ifactory, "/Spelling", allow);
8299 menu_set_sensitive(ifactory, "/Options", allow);
8300 menu_set_sensitive(ifactory, "/Tools", allow);
8301 menu_set_sensitive(ifactory, "/Help", allow);
8303 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
8307 static void compose_send_cb(gpointer data, guint action, GtkWidget *widget)
8309 Compose *compose = (Compose *)data;
8311 if (prefs_common.work_offline &&
8312 !inc_offline_should_override(TRUE,
8313 _("Claws Mail needs network access in order "
8314 "to send this email.")))
8317 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
8318 g_source_remove(compose->draft_timeout_tag);
8319 compose->draft_timeout_tag = -1;
8322 compose_send(compose);
8325 static void compose_send_later_cb(gpointer data, guint action,
8328 Compose *compose = (Compose *)data;
8332 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
8336 compose_close(compose);
8337 } else if (val == -1) {
8338 alertpanel_error(_("Could not queue message."));
8339 } else if (val == -2) {
8340 alertpanel_error(_("Could not queue message:\n\n%s."), strerror(errno));
8341 } else if (val == -3) {
8342 if (privacy_peek_error())
8343 alertpanel_error(_("Could not queue message for sending:\n\n"
8344 "Signature failed: %s"), privacy_get_error());
8345 } else if (val == -4) {
8346 alertpanel_error(_("Could not queue message for sending:\n\n"
8347 "Charset conversion failed."));
8348 } else if (val == -5) {
8349 alertpanel_error(_("Could not queue message for sending:\n\n"
8350 "Couldn't get recipient encryption key."));
8351 } else if (val == -6) {
8354 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
8357 #define DRAFTED_AT_EXIT "drafted_at_exit"
8358 static void compose_register_draft(MsgInfo *info)
8360 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8361 DRAFTED_AT_EXIT, NULL);
8362 FILE *fp = fopen(filepath, "ab");
8365 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
8373 gboolean compose_draft (gpointer data, guint action)
8375 Compose *compose = (Compose *)data;
8379 MsgFlags flag = {0, 0};
8380 static gboolean lock = FALSE;
8381 MsgInfo *newmsginfo;
8383 gboolean target_locked = FALSE;
8385 if (lock) return FALSE;
8387 if (compose->sending)
8390 draft = account_get_special_folder(compose->account, F_DRAFT);
8391 g_return_val_if_fail(draft != NULL, FALSE);
8393 if (!g_mutex_trylock(compose->mutex)) {
8394 /* we don't want to lock the mutex once it's available,
8395 * because as the only other part of compose.c locking
8396 * it is compose_close - which means once unlocked,
8397 * the compose struct will be freed */
8398 debug_print("couldn't lock mutex, probably sending\n");
8404 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
8405 G_DIR_SEPARATOR, compose);
8406 if ((fp = g_fopen(tmp, "wb")) == NULL) {
8407 FILE_OP_ERROR(tmp, "fopen");
8411 /* chmod for security */
8412 if (change_file_mode_rw(fp, tmp) < 0) {
8413 FILE_OP_ERROR(tmp, "chmod");
8414 g_warning("can't change file mode\n");
8417 /* Save draft infos */
8418 fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id);
8419 fprintf(fp, "S:%s\n", compose->account->address);
8421 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
8422 gchar *savefolderid;
8424 savefolderid = gtk_editable_get_chars(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
8425 fprintf(fp, "SCF:%s\n", savefolderid);
8426 g_free(savefolderid);
8428 if (compose->return_receipt) {
8429 fprintf(fp, "RRCPT:1\n");
8431 if (compose->privacy_system) {
8432 fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing);
8433 fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption);
8434 fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system);
8437 /* Message-ID of message replying to */
8438 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
8441 folderid = folder_item_get_identifier(compose->replyinfo->folder);
8442 fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid);
8445 /* Message-ID of message forwarding to */
8446 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
8449 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
8450 fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid);
8454 /* end of headers */
8455 fprintf(fp, "X-Claws-End-Special-Headers: 1\n");
8457 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
8465 if (compose->targetinfo) {
8466 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
8467 flag.perm_flags = target_locked?MSG_LOCKED:0;
8469 flag.tmp_flags = MSG_DRAFT;
8471 folder_item_scan(draft);
8472 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
8473 MsgInfo *tmpinfo = NULL;
8474 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
8475 if (compose->msgid) {
8476 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
8479 msgnum = tmpinfo->msgnum;
8480 procmsg_msginfo_free(tmpinfo);
8481 debug_print("got draft msgnum %d from scanning\n", msgnum);
8483 debug_print("didn't get draft msgnum after scanning\n");
8486 debug_print("got draft msgnum %d from adding\n", msgnum);
8491 if (action != COMPOSE_AUTO_SAVE) {
8492 if (action != COMPOSE_DRAFT_FOR_EXIT)
8493 alertpanel_error(_("Could not save draft."));
8496 gtkut_window_popup(compose->window);
8497 val = alertpanel_full(_("Could not save draft"),
8498 _("Could not save draft.\n"
8499 "Do you want to cancel exit or discard this email?"),
8500 _("_Cancel exit"), _("_Discard email"), NULL,
8501 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
8502 if (val == G_ALERTALTERNATE) {
8504 g_mutex_unlock(compose->mutex); /* must be done before closing */
8505 compose_close(compose);
8509 g_mutex_unlock(compose->mutex); /* must be done before closing */
8518 if (compose->mode == COMPOSE_REEDIT) {
8519 compose_remove_reedit_target(compose, TRUE);
8522 newmsginfo = folder_item_get_msginfo(draft, msgnum);
8525 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
8527 procmsg_msginfo_set_flags(newmsginfo, MSG_LOCKED, MSG_DRAFT);
8529 procmsg_msginfo_set_flags(newmsginfo, 0, MSG_DRAFT);
8530 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
8531 procmsg_msginfo_set_flags(newmsginfo, 0,
8532 MSG_HAS_ATTACHMENT);
8534 if (action == COMPOSE_DRAFT_FOR_EXIT) {
8535 compose_register_draft(newmsginfo);
8537 procmsg_msginfo_free(newmsginfo);
8540 folder_item_scan(draft);
8542 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
8544 g_mutex_unlock(compose->mutex); /* must be done before closing */
8545 compose_close(compose);
8551 path = folder_item_fetch_msg(draft, msgnum);
8553 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
8556 if (g_stat(path, &s) < 0) {
8557 FILE_OP_ERROR(path, "stat");
8563 procmsg_msginfo_free(compose->targetinfo);
8564 compose->targetinfo = procmsg_msginfo_new();
8565 compose->targetinfo->msgnum = msgnum;
8566 compose->targetinfo->size = s.st_size;
8567 compose->targetinfo->mtime = s.st_mtime;
8568 compose->targetinfo->folder = draft;
8570 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
8571 compose->mode = COMPOSE_REEDIT;
8573 if (action == COMPOSE_AUTO_SAVE) {
8574 compose->autosaved_draft = compose->targetinfo;
8576 compose->modified = FALSE;
8577 compose_set_title(compose);
8581 g_mutex_unlock(compose->mutex);
8585 void compose_clear_exit_drafts(void)
8587 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8588 DRAFTED_AT_EXIT, NULL);
8589 if (is_file_exist(filepath))
8595 void compose_reopen_exit_drafts(void)
8597 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8598 DRAFTED_AT_EXIT, NULL);
8599 FILE *fp = fopen(filepath, "rb");
8603 while (fgets(buf, sizeof(buf), fp)) {
8604 gchar **parts = g_strsplit(buf, "\t", 2);
8605 const gchar *folder = parts[0];
8606 int msgnum = parts[1] ? atoi(parts[1]):-1;
8608 if (folder && *folder && msgnum > -1) {
8609 FolderItem *item = folder_find_item_from_identifier(folder);
8610 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
8612 compose_reedit(info, FALSE);
8619 compose_clear_exit_drafts();
8622 static void compose_draft_cb(gpointer data, guint action, GtkWidget *widget)
8624 compose_draft(data, action);
8627 static void compose_attach_cb(gpointer data, guint action, GtkWidget *widget)
8629 Compose *compose = (Compose *)data;
8632 if (compose->redirect_filename != NULL)
8635 file_list = filesel_select_multiple_files_open(_("Select file"));
8640 for ( tmp = file_list; tmp; tmp = tmp->next) {
8641 gchar *file = (gchar *) tmp->data;
8642 gchar *utf8_filename = conv_filename_to_utf8(file);
8643 compose_attach_append(compose, file, utf8_filename, NULL);
8644 compose_changed_cb(NULL, compose);
8646 g_free(utf8_filename);
8648 g_list_free(file_list);
8652 static void compose_insert_file_cb(gpointer data, guint action,
8655 Compose *compose = (Compose *)data;
8658 file_list = filesel_select_multiple_files_open(_("Select file"));
8663 for ( tmp = file_list; tmp; tmp = tmp->next) {
8664 gchar *file = (gchar *) tmp->data;
8665 gchar *filedup = g_strdup(file);
8666 gchar *shortfile = g_path_get_basename(filedup);
8667 ComposeInsertResult res;
8669 res = compose_insert_file(compose, file);
8670 if (res == COMPOSE_INSERT_READ_ERROR) {
8671 alertpanel_error(_("File '%s' could not be read."), shortfile);
8672 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
8673 alertpanel_error(_("File '%s' contained invalid characters\n"
8674 "for the current encoding, insertion may be incorrect."), shortfile);
8680 g_list_free(file_list);
8684 static void compose_insert_sig_cb(gpointer data, guint action,
8687 Compose *compose = (Compose *)data;
8689 compose_insert_sig(compose, FALSE);
8692 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
8696 Compose *compose = (Compose *)data;
8698 gtkut_widget_get_uposition(widget, &x, &y);
8699 prefs_common.compose_x = x;
8700 prefs_common.compose_y = y;
8702 if (compose->sending || compose->updating)
8704 compose_close_cb(compose, 0, NULL);
8708 void compose_close_toolbar(Compose *compose)
8710 compose_close_cb(compose, 0, NULL);
8713 static void compose_close_cb(gpointer data, guint action, GtkWidget *widget)
8715 Compose *compose = (Compose *)data;
8719 if (compose->exteditor_tag != -1) {
8720 if (!compose_ext_editor_kill(compose))
8725 if (compose->modified) {
8726 val = alertpanel(_("Discard message"),
8727 _("This message has been modified. Discard it?"),
8728 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
8731 case G_ALERTDEFAULT:
8732 if (prefs_common.autosave)
8733 compose_remove_draft(compose);
8735 case G_ALERTALTERNATE:
8736 compose_draft_cb(data, COMPOSE_QUIT_EDITING, NULL);
8743 compose_close(compose);
8746 static void compose_set_encoding_cb(gpointer data, guint action,
8749 Compose *compose = (Compose *)data;
8751 if (GTK_CHECK_MENU_ITEM(widget)->active)
8752 compose->out_encoding = (CharSet)action;
8755 static void compose_address_cb(gpointer data, guint action, GtkWidget *widget)
8757 Compose *compose = (Compose *)data;
8759 addressbook_open(compose);
8762 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
8764 Compose *compose = (Compose *)data;
8769 tmpl = g_object_get_data(G_OBJECT(widget), "template");
8770 g_return_if_fail(tmpl != NULL);
8772 msg = g_strdup_printf(_("Do you want to apply the template '%s' ?"),
8774 val = alertpanel(_("Apply template"), msg,
8775 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
8778 if (val == G_ALERTDEFAULT)
8779 compose_template_apply(compose, tmpl, TRUE);
8780 else if (val == G_ALERTALTERNATE)
8781 compose_template_apply(compose, tmpl, FALSE);
8784 static void compose_ext_editor_cb(gpointer data, guint action,
8787 Compose *compose = (Compose *)data;
8789 compose_exec_ext_editor(compose);
8792 static void compose_undo_cb(Compose *compose)
8794 gboolean prev_autowrap = compose->autowrap;
8796 compose->autowrap = FALSE;
8797 undo_undo(compose->undostruct);
8798 compose->autowrap = prev_autowrap;
8801 static void compose_redo_cb(Compose *compose)
8803 gboolean prev_autowrap = compose->autowrap;
8805 compose->autowrap = FALSE;
8806 undo_redo(compose->undostruct);
8807 compose->autowrap = prev_autowrap;
8810 static void entry_cut_clipboard(GtkWidget *entry)
8812 if (GTK_IS_EDITABLE(entry))
8813 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
8814 else if (GTK_IS_TEXT_VIEW(entry))
8815 gtk_text_buffer_cut_clipboard(
8816 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
8817 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
8821 static void entry_copy_clipboard(GtkWidget *entry)
8823 if (GTK_IS_EDITABLE(entry))
8824 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
8825 else if (GTK_IS_TEXT_VIEW(entry))
8826 gtk_text_buffer_copy_clipboard(
8827 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
8828 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
8831 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
8832 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
8834 if (GTK_IS_TEXT_VIEW(entry)) {
8835 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
8836 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
8837 GtkTextIter start_iter, end_iter;
8839 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
8841 if (contents == NULL)
8844 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
8846 /* we shouldn't delete the selection when middle-click-pasting, or we
8847 * can't mid-click-paste our own selection */
8848 if (clip != GDK_SELECTION_PRIMARY) {
8849 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
8852 if (insert_place == NULL) {
8853 /* if insert_place isn't specified, insert at the cursor.
8854 * used for Ctrl-V pasting */
8855 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
8856 start = gtk_text_iter_get_offset(&start_iter);
8857 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
8859 /* if insert_place is specified, paste here.
8860 * used for mid-click-pasting */
8861 start = gtk_text_iter_get_offset(insert_place);
8862 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
8866 /* paste unwrapped: mark the paste so it's not wrapped later */
8867 end = start + strlen(contents);
8868 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
8869 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
8870 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
8871 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
8872 /* rewrap paragraph now (after a mid-click-paste) */
8873 mark_start = gtk_text_buffer_get_insert(buffer);
8874 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
8875 gtk_text_iter_backward_char(&start_iter);
8876 compose_beautify_paragraph(compose, &start_iter, TRUE);
8878 } else if (GTK_IS_EDITABLE(entry))
8879 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
8883 static void entry_allsel(GtkWidget *entry)
8885 if (GTK_IS_EDITABLE(entry))
8886 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
8887 else if (GTK_IS_TEXT_VIEW(entry)) {
8888 GtkTextIter startiter, enditer;
8889 GtkTextBuffer *textbuf;
8891 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
8892 gtk_text_buffer_get_start_iter(textbuf, &startiter);
8893 gtk_text_buffer_get_end_iter(textbuf, &enditer);
8895 gtk_text_buffer_move_mark_by_name(textbuf,
8896 "selection_bound", &startiter);
8897 gtk_text_buffer_move_mark_by_name(textbuf,
8898 "insert", &enditer);
8902 static void compose_cut_cb(Compose *compose)
8904 if (compose->focused_editable
8906 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
8909 entry_cut_clipboard(compose->focused_editable);
8912 static void compose_copy_cb(Compose *compose)
8914 if (compose->focused_editable
8916 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
8919 entry_copy_clipboard(compose->focused_editable);
8922 static void compose_paste_cb(Compose *compose)
8925 GtkTextBuffer *buffer;
8927 if (compose->focused_editable &&
8928 GTK_WIDGET_HAS_FOCUS(compose->focused_editable))
8929 entry_paste_clipboard(compose, compose->focused_editable,
8930 prefs_common.linewrap_pastes,
8931 GDK_SELECTION_CLIPBOARD, NULL);
8935 static void compose_paste_as_quote_cb(Compose *compose)
8937 gint wrap_quote = prefs_common.linewrap_quote;
8938 if (compose->focused_editable
8940 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
8943 /* let text_insert() (called directly or at a later time
8944 * after the gtk_editable_paste_clipboard) know that
8945 * text is to be inserted as a quotation. implemented
8946 * by using a simple refcount... */
8947 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
8948 G_OBJECT(compose->focused_editable),
8949 "paste_as_quotation"));
8950 g_object_set_data(G_OBJECT(compose->focused_editable),
8951 "paste_as_quotation",
8952 GINT_TO_POINTER(paste_as_quotation + 1));
8953 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
8954 entry_paste_clipboard(compose, compose->focused_editable,
8955 prefs_common.linewrap_pastes,
8956 GDK_SELECTION_CLIPBOARD, NULL);
8957 prefs_common.linewrap_quote = wrap_quote;
8961 static void compose_paste_no_wrap_cb(Compose *compose)
8964 GtkTextBuffer *buffer;
8966 if (compose->focused_editable
8968 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
8971 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
8972 GDK_SELECTION_CLIPBOARD, NULL);
8976 static void compose_paste_wrap_cb(Compose *compose)
8979 GtkTextBuffer *buffer;
8981 if (compose->focused_editable
8983 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
8986 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
8987 GDK_SELECTION_CLIPBOARD, NULL);
8991 static void compose_allsel_cb(Compose *compose)
8993 if (compose->focused_editable
8995 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
8998 entry_allsel(compose->focused_editable);
9001 static void textview_move_beginning_of_line (GtkTextView *text)
9003 GtkTextBuffer *buffer;
9007 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9009 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9010 mark = gtk_text_buffer_get_insert(buffer);
9011 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9012 gtk_text_iter_set_line_offset(&ins, 0);
9013 gtk_text_buffer_place_cursor(buffer, &ins);
9016 static void textview_move_forward_character (GtkTextView *text)
9018 GtkTextBuffer *buffer;
9022 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9024 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9025 mark = gtk_text_buffer_get_insert(buffer);
9026 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9027 if (gtk_text_iter_forward_cursor_position(&ins))
9028 gtk_text_buffer_place_cursor(buffer, &ins);
9031 static void textview_move_backward_character (GtkTextView *text)
9033 GtkTextBuffer *buffer;
9037 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9039 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9040 mark = gtk_text_buffer_get_insert(buffer);
9041 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9042 if (gtk_text_iter_backward_cursor_position(&ins))
9043 gtk_text_buffer_place_cursor(buffer, &ins);
9046 static void textview_move_forward_word (GtkTextView *text)
9048 GtkTextBuffer *buffer;
9053 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9055 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9056 mark = gtk_text_buffer_get_insert(buffer);
9057 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9058 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
9059 if (gtk_text_iter_forward_word_ends(&ins, count)) {
9060 gtk_text_iter_backward_word_start(&ins);
9061 gtk_text_buffer_place_cursor(buffer, &ins);
9065 static void textview_move_backward_word (GtkTextView *text)
9067 GtkTextBuffer *buffer;
9072 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9074 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9075 mark = gtk_text_buffer_get_insert(buffer);
9076 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9077 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
9078 if (gtk_text_iter_backward_word_starts(&ins, 1))
9079 gtk_text_buffer_place_cursor(buffer, &ins);
9082 static void textview_move_end_of_line (GtkTextView *text)
9084 GtkTextBuffer *buffer;
9088 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9090 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9091 mark = gtk_text_buffer_get_insert(buffer);
9092 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9093 if (gtk_text_iter_forward_to_line_end(&ins))
9094 gtk_text_buffer_place_cursor(buffer, &ins);
9097 static void textview_move_next_line (GtkTextView *text)
9099 GtkTextBuffer *buffer;
9104 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9106 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9107 mark = gtk_text_buffer_get_insert(buffer);
9108 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9109 offset = gtk_text_iter_get_line_offset(&ins);
9110 if (gtk_text_iter_forward_line(&ins)) {
9111 gtk_text_iter_set_line_offset(&ins, offset);
9112 gtk_text_buffer_place_cursor(buffer, &ins);
9116 static void textview_move_previous_line (GtkTextView *text)
9118 GtkTextBuffer *buffer;
9123 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9125 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9126 mark = gtk_text_buffer_get_insert(buffer);
9127 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9128 offset = gtk_text_iter_get_line_offset(&ins);
9129 if (gtk_text_iter_backward_line(&ins)) {
9130 gtk_text_iter_set_line_offset(&ins, offset);
9131 gtk_text_buffer_place_cursor(buffer, &ins);
9135 static void textview_delete_forward_character (GtkTextView *text)
9137 GtkTextBuffer *buffer;
9139 GtkTextIter ins, end_iter;
9141 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9143 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9144 mark = gtk_text_buffer_get_insert(buffer);
9145 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9147 if (gtk_text_iter_forward_char(&end_iter)) {
9148 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9152 static void textview_delete_backward_character (GtkTextView *text)
9154 GtkTextBuffer *buffer;
9156 GtkTextIter ins, end_iter;
9158 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9160 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9161 mark = gtk_text_buffer_get_insert(buffer);
9162 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9164 if (gtk_text_iter_backward_char(&end_iter)) {
9165 gtk_text_buffer_delete(buffer, &end_iter, &ins);
9169 static void textview_delete_forward_word (GtkTextView *text)
9171 GtkTextBuffer *buffer;
9173 GtkTextIter ins, end_iter;
9175 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9177 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9178 mark = gtk_text_buffer_get_insert(buffer);
9179 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9181 if (gtk_text_iter_forward_word_end(&end_iter)) {
9182 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9186 static void textview_delete_backward_word (GtkTextView *text)
9188 GtkTextBuffer *buffer;
9190 GtkTextIter ins, end_iter;
9192 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9194 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9195 mark = gtk_text_buffer_get_insert(buffer);
9196 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9198 if (gtk_text_iter_backward_word_start(&end_iter)) {
9199 gtk_text_buffer_delete(buffer, &end_iter, &ins);
9203 static void textview_delete_line (GtkTextView *text)
9205 GtkTextBuffer *buffer;
9207 GtkTextIter ins, start_iter, end_iter;
9210 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9212 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9213 mark = gtk_text_buffer_get_insert(buffer);
9214 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9217 gtk_text_iter_set_line_offset(&start_iter, 0);
9220 if (gtk_text_iter_ends_line(&end_iter))
9221 found = gtk_text_iter_forward_char(&end_iter);
9223 found = gtk_text_iter_forward_to_line_end(&end_iter);
9226 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
9229 static void textview_delete_to_line_end (GtkTextView *text)
9231 GtkTextBuffer *buffer;
9233 GtkTextIter ins, end_iter;
9236 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9238 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9239 mark = gtk_text_buffer_get_insert(buffer);
9240 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9242 if (gtk_text_iter_ends_line(&end_iter))
9243 found = gtk_text_iter_forward_char(&end_iter);
9245 found = gtk_text_iter_forward_to_line_end(&end_iter);
9247 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9250 static void compose_advanced_action_cb(Compose *compose,
9251 ComposeCallAdvancedAction action)
9253 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9255 void (*do_action) (GtkTextView *text);
9256 } action_table[] = {
9257 {textview_move_beginning_of_line},
9258 {textview_move_forward_character},
9259 {textview_move_backward_character},
9260 {textview_move_forward_word},
9261 {textview_move_backward_word},
9262 {textview_move_end_of_line},
9263 {textview_move_next_line},
9264 {textview_move_previous_line},
9265 {textview_delete_forward_character},
9266 {textview_delete_backward_character},
9267 {textview_delete_forward_word},
9268 {textview_delete_backward_word},
9269 {textview_delete_line},
9270 {NULL}, /* gtk_stext_delete_line_n */
9271 {textview_delete_to_line_end}
9274 if (!GTK_WIDGET_HAS_FOCUS(text)) return;
9276 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
9277 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
9278 if (action_table[action].do_action)
9279 action_table[action].do_action(text);
9281 g_warning("Not implemented yet.");
9285 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
9289 if (GTK_IS_EDITABLE(widget)) {
9290 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
9291 gtk_editable_set_position(GTK_EDITABLE(widget),
9294 if (widget->parent && widget->parent->parent
9295 && widget->parent->parent->parent) {
9296 if (GTK_IS_SCROLLED_WINDOW(widget->parent->parent->parent)) {
9297 gint y = widget->allocation.y;
9298 gint height = widget->allocation.height;
9299 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
9300 (GTK_SCROLLED_WINDOW(widget->parent->parent->parent));
9302 if (y < (int)shown->value) {
9303 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown), y - 1);
9305 if (y + height > (int)shown->value + (int)shown->page_size) {
9306 if (y - height - 1 < (int)shown->upper - (int)shown->page_size) {
9307 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
9308 y + height - (int)shown->page_size - 1);
9310 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
9311 (int)shown->upper - (int)shown->page_size - 1);
9318 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
9319 compose->focused_editable = widget;
9322 if (GTK_IS_TEXT_VIEW(widget)
9323 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
9324 gtk_widget_ref(compose->notebook);
9325 gtk_widget_ref(compose->edit_vbox);
9326 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
9327 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
9328 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
9329 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
9330 gtk_widget_unref(compose->notebook);
9331 gtk_widget_unref(compose->edit_vbox);
9332 g_signal_handlers_block_by_func(G_OBJECT(widget),
9333 G_CALLBACK(compose_grab_focus_cb),
9335 gtk_widget_grab_focus(widget);
9336 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
9337 G_CALLBACK(compose_grab_focus_cb),
9339 } else if (!GTK_IS_TEXT_VIEW(widget)
9340 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
9341 gtk_widget_ref(compose->notebook);
9342 gtk_widget_ref(compose->edit_vbox);
9343 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
9344 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
9345 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
9346 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
9347 gtk_widget_unref(compose->notebook);
9348 gtk_widget_unref(compose->edit_vbox);
9349 g_signal_handlers_block_by_func(G_OBJECT(widget),
9350 G_CALLBACK(compose_grab_focus_cb),
9352 gtk_widget_grab_focus(widget);
9353 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
9354 G_CALLBACK(compose_grab_focus_cb),
9360 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
9362 compose->modified = TRUE;
9364 compose_set_title(compose);
9368 static void compose_wrap_cb(gpointer data, guint action, GtkWidget *widget)
9370 Compose *compose = (Compose *)data;
9373 compose_wrap_all_full(compose, TRUE);
9375 compose_beautify_paragraph(compose, NULL, TRUE);
9378 static void compose_find_cb(gpointer data, guint action, GtkWidget *widget)
9380 Compose *compose = (Compose *)data;
9382 message_search_compose(compose);
9385 static void compose_toggle_autowrap_cb(gpointer data, guint action,
9388 Compose *compose = (Compose *)data;
9389 compose->autowrap = GTK_CHECK_MENU_ITEM(widget)->active;
9390 if (compose->autowrap)
9391 compose_wrap_all_full(compose, TRUE);
9392 compose->autowrap = GTK_CHECK_MENU_ITEM(widget)->active;
9395 static void compose_toggle_sign_cb(gpointer data, guint action,
9398 Compose *compose = (Compose *)data;
9400 if (GTK_CHECK_MENU_ITEM(widget)->active)
9401 compose->use_signing = TRUE;
9403 compose->use_signing = FALSE;
9406 static void compose_toggle_encrypt_cb(gpointer data, guint action,
9409 Compose *compose = (Compose *)data;
9411 if (GTK_CHECK_MENU_ITEM(widget)->active)
9412 compose->use_encryption = TRUE;
9414 compose->use_encryption = FALSE;
9417 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
9419 g_free(compose->privacy_system);
9421 compose->privacy_system = g_strdup(account->default_privacy_system);
9422 compose_update_privacy_system_menu_item(compose, warn);
9425 static void compose_toggle_ruler_cb(gpointer data, guint action,
9428 Compose *compose = (Compose *)data;
9430 if (GTK_CHECK_MENU_ITEM(widget)->active) {
9431 gtk_widget_show(compose->ruler_hbox);
9432 prefs_common.show_ruler = TRUE;
9434 gtk_widget_hide(compose->ruler_hbox);
9435 gtk_widget_queue_resize(compose->edit_vbox);
9436 prefs_common.show_ruler = FALSE;
9440 static void compose_attach_drag_received_cb (GtkWidget *widget,
9441 GdkDragContext *context,
9444 GtkSelectionData *data,
9449 Compose *compose = (Compose *)user_data;
9452 if (gdk_atom_name(data->type) &&
9453 !strcmp(gdk_atom_name(data->type), "text/uri-list")
9454 && gtk_drag_get_source_widget(context) !=
9455 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
9456 list = uri_list_extract_filenames((const gchar *)data->data);
9457 for (tmp = list; tmp != NULL; tmp = tmp->next) {
9458 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
9459 compose_attach_append
9460 (compose, (const gchar *)tmp->data,
9461 utf8_filename, NULL);
9462 g_free(utf8_filename);
9464 if (list) compose_changed_cb(NULL, compose);
9465 list_free_strings(list);
9467 } else if (gtk_drag_get_source_widget(context)
9468 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
9469 /* comes from our summaryview */
9470 SummaryView * summaryview = NULL;
9471 GSList * list = NULL, *cur = NULL;
9473 if (mainwindow_get_mainwindow())
9474 summaryview = mainwindow_get_mainwindow()->summaryview;
9477 list = summary_get_selected_msg_list(summaryview);
9479 for (cur = list; cur; cur = cur->next) {
9480 MsgInfo *msginfo = (MsgInfo *)cur->data;
9483 file = procmsg_get_message_file_full(msginfo,
9486 compose_attach_append(compose, (const gchar *)file,
9487 (const gchar *)file, "message/rfc822");
9495 static gboolean compose_drag_drop(GtkWidget *widget,
9496 GdkDragContext *drag_context,
9498 guint time, gpointer user_data)
9500 /* not handling this signal makes compose_insert_drag_received_cb
9505 static void compose_insert_drag_received_cb (GtkWidget *widget,
9506 GdkDragContext *drag_context,
9509 GtkSelectionData *data,
9514 Compose *compose = (Compose *)user_data;
9517 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
9519 if (gdk_atom_name(data->type) && !strcmp(gdk_atom_name(data->type), "text/uri-list")) {
9520 AlertValue val = G_ALERTDEFAULT;
9522 switch (prefs_common.compose_dnd_mode) {
9523 case COMPOSE_DND_ASK:
9524 val = alertpanel_full(_("Insert or attach?"),
9525 _("Do you want to insert the contents of the file(s) "
9526 "into the message body, or attach it to the email?"),
9527 GTK_STOCK_CANCEL, _("+_Insert"), _("_Attach"),
9528 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
9530 case COMPOSE_DND_INSERT:
9531 val = G_ALERTALTERNATE;
9533 case COMPOSE_DND_ATTACH:
9537 /* unexpected case */
9538 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
9541 if (val & G_ALERTDISABLE) {
9542 val &= ~G_ALERTDISABLE;
9543 /* remember what action to perform by default, only if we don't click Cancel */
9544 if (val == G_ALERTALTERNATE)
9545 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
9546 else if (val == G_ALERTOTHER)
9547 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
9550 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
9551 gtk_drag_finish(drag_context, FALSE, FALSE, time);
9553 } else if (val == G_ALERTOTHER) {
9554 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
9557 list = uri_list_extract_filenames((const gchar *)data->data);
9558 for (tmp = list; tmp != NULL; tmp = tmp->next) {
9559 compose_insert_file(compose, (const gchar *)tmp->data);
9561 list_free_strings(list);
9563 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9566 #if GTK_CHECK_VERSION(2, 8, 0)
9567 /* do nothing, handled by GTK */
9569 gchar *tmpfile = get_tmp_file();
9570 str_write_to_file((const gchar *)data->data, tmpfile);
9571 compose_insert_file(compose, tmpfile);
9574 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9578 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9581 static void compose_header_drag_received_cb (GtkWidget *widget,
9582 GdkDragContext *drag_context,
9585 GtkSelectionData *data,
9590 GtkEditable *entry = (GtkEditable *)user_data;
9591 gchar *email = (gchar *)data->data;
9593 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
9596 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
9597 gchar *decoded=g_new(gchar, strlen(email));
9600 email += strlen("mailto:");
9601 decode_uri(decoded, email); /* will fit */
9602 gtk_editable_delete_text(entry, 0, -1);
9603 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
9604 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9608 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9611 static void compose_toggle_return_receipt_cb(gpointer data, guint action,
9614 Compose *compose = (Compose *)data;
9616 if (GTK_CHECK_MENU_ITEM(widget)->active)
9617 compose->return_receipt = TRUE;
9619 compose->return_receipt = FALSE;
9622 static void compose_toggle_remove_refs_cb(gpointer data, guint action,
9625 Compose *compose = (Compose *)data;
9627 if (GTK_CHECK_MENU_ITEM(widget)->active)
9628 compose->remove_references = TRUE;
9630 compose->remove_references = FALSE;
9633 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
9635 ComposeHeaderEntry *headerentry)
9637 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
9638 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
9639 !(event->state & GDK_MODIFIER_MASK) &&
9640 (event->keyval == GDK_BackSpace) &&
9641 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
9642 gtk_container_remove
9643 (GTK_CONTAINER(headerentry->compose->header_table),
9644 headerentry->combo);
9645 gtk_container_remove
9646 (GTK_CONTAINER(headerentry->compose->header_table),
9647 headerentry->entry);
9648 headerentry->compose->header_list =
9649 g_slist_remove(headerentry->compose->header_list,
9651 g_free(headerentry);
9652 } else if (event->keyval == GDK_Tab) {
9653 if (headerentry->compose->header_last == headerentry) {
9654 /* Override default next focus, and give it to subject_entry
9655 * instead of notebook tabs
9657 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
9658 gtk_widget_grab_focus(headerentry->compose->subject_entry);
9665 static gboolean compose_headerentry_changed_cb(GtkWidget *entry,
9666 ComposeHeaderEntry *headerentry)
9668 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
9669 compose_create_header_entry(headerentry->compose);
9670 g_signal_handlers_disconnect_matched
9671 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
9672 0, 0, NULL, NULL, headerentry);
9674 /* Automatically scroll down */
9675 compose_show_first_last_header(headerentry->compose, FALSE);
9681 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
9683 GtkAdjustment *vadj;
9685 g_return_if_fail(compose);
9686 g_return_if_fail(GTK_IS_WIDGET(compose->header_table));
9687 g_return_if_fail(GTK_IS_VIEWPORT(compose->header_table->parent));
9689 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(compose->header_table->parent));
9690 gtk_adjustment_set_value(vadj, (show_first ? vadj->lower : vadj->upper));
9691 gtk_adjustment_changed(vadj);
9694 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
9695 const gchar *text, gint len, Compose *compose)
9697 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
9698 (G_OBJECT(compose->text), "paste_as_quotation"));
9701 g_return_if_fail(text != NULL);
9703 g_signal_handlers_block_by_func(G_OBJECT(buffer),
9704 G_CALLBACK(text_inserted),
9706 if (paste_as_quotation) {
9710 GtkTextIter start_iter;
9715 new_text = g_strndup(text, len);
9717 qmark = compose_quote_char_from_context(compose);
9719 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
9720 gtk_text_buffer_place_cursor(buffer, iter);
9722 pos = gtk_text_iter_get_offset(iter);
9724 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
9725 _("Quote format error at line %d."));
9726 quote_fmt_reset_vartable();
9728 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
9729 GINT_TO_POINTER(paste_as_quotation - 1));
9731 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
9732 gtk_text_buffer_place_cursor(buffer, iter);
9733 gtk_text_buffer_delete_mark(buffer, mark);
9735 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
9736 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
9737 compose_beautify_paragraph(compose, &start_iter, FALSE);
9738 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
9739 gtk_text_buffer_delete_mark(buffer, mark);
9741 if (strcmp(text, "\n") || compose->automatic_break
9742 || gtk_text_iter_starts_line(iter))
9743 gtk_text_buffer_insert(buffer, iter, text, len);
9745 debug_print("insert nowrap \\n\n");
9746 gtk_text_buffer_insert_with_tags_by_name(buffer,
9747 iter, text, len, "no_join", NULL);
9751 if (!paste_as_quotation) {
9752 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
9753 compose_beautify_paragraph(compose, iter, FALSE);
9754 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
9755 gtk_text_buffer_delete_mark(buffer, mark);
9758 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
9759 G_CALLBACK(text_inserted),
9761 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
9763 if (prefs_common.autosave &&
9764 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
9765 compose->draft_timeout_tag != -2 /* disabled while loading */)
9766 compose->draft_timeout_tag = g_timeout_add
9767 (500, (GtkFunction) compose_defer_auto_save_draft, compose);
9769 static gint compose_defer_auto_save_draft(Compose *compose)
9771 compose->draft_timeout_tag = -1;
9772 compose_draft_cb((gpointer)compose, COMPOSE_AUTO_SAVE, NULL);
9777 static void compose_check_all(Compose *compose)
9779 if (compose->gtkaspell)
9780 gtkaspell_check_all(compose->gtkaspell);
9783 static void compose_highlight_all(Compose *compose)
9785 if (compose->gtkaspell)
9786 gtkaspell_highlight_all(compose->gtkaspell);
9789 static void compose_check_backwards(Compose *compose)
9791 if (compose->gtkaspell)
9792 gtkaspell_check_backwards(compose->gtkaspell);
9794 GtkItemFactory *ifactory;
9795 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
9796 menu_set_sensitive(ifactory, "/Edit/Check backwards misspelled word", FALSE);
9797 menu_set_sensitive(ifactory, "/Edit/Forward to next misspelled word", FALSE);
9801 static void compose_check_forwards_go(Compose *compose)
9803 if (compose->gtkaspell)
9804 gtkaspell_check_forwards_go(compose->gtkaspell);
9806 GtkItemFactory *ifactory;
9807 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
9808 menu_set_sensitive(ifactory, "/Edit/Check backwards misspelled word", FALSE);
9809 menu_set_sensitive(ifactory, "/Edit/Forward to next misspelled word", FALSE);
9815 *\brief Guess originating forward account from MsgInfo and several
9816 * "common preference" settings. Return NULL if no guess.
9818 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
9820 PrefsAccount *account = NULL;
9822 g_return_val_if_fail(msginfo, NULL);
9823 g_return_val_if_fail(msginfo->folder, NULL);
9824 g_return_val_if_fail(msginfo->folder->prefs, NULL);
9826 if (msginfo->folder->prefs->enable_default_account)
9827 account = account_find_from_id(msginfo->folder->prefs->default_account);
9830 account = msginfo->folder->folder->account;
9832 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
9834 Xstrdup_a(to, msginfo->to, return NULL);
9835 extract_address(to);
9836 account = account_find_from_address(to);
9839 if (!account && prefs_common.forward_account_autosel) {
9841 if (!procheader_get_header_from_msginfo
9842 (msginfo, cc,sizeof cc , "Cc:")) {
9843 gchar *buf = cc + strlen("Cc:");
9844 extract_address(buf);
9845 account = account_find_from_address(buf);
9849 if (!account && prefs_common.forward_account_autosel) {
9850 gchar deliveredto[BUFFSIZE];
9851 if (!procheader_get_header_from_msginfo
9852 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
9853 gchar *buf = deliveredto + strlen("Delivered-To:");
9854 extract_address(buf);
9855 account = account_find_from_address(buf);
9862 gboolean compose_close(Compose *compose)
9866 if (!g_mutex_trylock(compose->mutex)) {
9867 /* we have to wait for the (possibly deferred by auto-save)
9868 * drafting to be done, before destroying the compose under
9870 debug_print("waiting for drafting to finish...\n");
9871 compose_allow_user_actions(compose, FALSE);
9872 g_timeout_add (500, (GSourceFunc) compose_close, compose);
9875 g_return_val_if_fail(compose, FALSE);
9876 gtkut_widget_get_uposition(compose->window, &x, &y);
9877 prefs_common.compose_x = x;
9878 prefs_common.compose_y = y;
9879 g_mutex_unlock(compose->mutex);
9880 compose_destroy(compose);
9885 * Add entry field for each address in list.
9886 * \param compose E-Mail composition object.
9887 * \param listAddress List of (formatted) E-Mail addresses.
9889 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
9894 addr = ( gchar * ) node->data;
9895 compose_entry_append( compose, addr, COMPOSE_TO );
9896 node = g_list_next( node );
9900 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
9901 guint action, gboolean opening_multiple)
9904 GSList *new_msglist = NULL;
9905 MsgInfo *tmp_msginfo = NULL;
9906 gboolean originally_enc = FALSE;
9907 Compose *compose = NULL;
9909 g_return_if_fail(msgview != NULL);
9911 g_return_if_fail(msginfo_list != NULL);
9913 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
9914 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
9915 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
9917 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
9918 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
9919 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
9920 orig_msginfo, mimeinfo);
9921 if (tmp_msginfo != NULL) {
9922 new_msglist = g_slist_append(NULL, tmp_msginfo);
9924 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
9925 tmp_msginfo->folder = orig_msginfo->folder;
9926 tmp_msginfo->msgnum = orig_msginfo->msgnum;
9927 if (orig_msginfo->tags)
9928 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
9933 if (!opening_multiple)
9934 body = messageview_get_selection(msgview);
9937 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
9938 procmsg_msginfo_free(tmp_msginfo);
9939 g_slist_free(new_msglist);
9941 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
9943 if (originally_enc) {
9944 compose_force_encryption(compose, compose->account, FALSE);
9950 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
9953 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
9954 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
9955 GSList *cur = msginfo_list;
9956 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
9957 "messages. Opening the windows "
9958 "could take some time. Do you "
9959 "want to continue?"),
9960 g_slist_length(msginfo_list));
9961 if (g_slist_length(msginfo_list) > 9
9962 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
9963 != G_ALERTALTERNATE) {
9968 /* We'll open multiple compose windows */
9969 /* let the WM place the next windows */
9970 compose_force_window_origin = FALSE;
9971 for (; cur; cur = cur->next) {
9973 tmplist.data = cur->data;
9974 tmplist.next = NULL;
9975 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
9977 compose_force_window_origin = TRUE;
9979 /* forwarding multiple mails as attachments is done via a
9980 * single compose window */
9981 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
9985 void compose_set_position(Compose *compose, gint pos)
9987 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9989 gtkut_text_view_set_position(text, pos);
9992 gboolean compose_search_string(Compose *compose,
9993 const gchar *str, gboolean case_sens)
9995 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9997 return gtkut_text_view_search_string(text, str, case_sens);
10000 gboolean compose_search_string_backward(Compose *compose,
10001 const gchar *str, gboolean case_sens)
10003 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10005 return gtkut_text_view_search_string_backward(text, str, case_sens);
10008 /* allocate a msginfo structure and populate its data from a compose data structure */
10009 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
10011 MsgInfo *newmsginfo;
10013 gchar buf[BUFFSIZE];
10015 g_return_val_if_fail( compose != NULL, NULL );
10017 newmsginfo = procmsg_msginfo_new();
10020 get_rfc822_date(buf, sizeof(buf));
10021 newmsginfo->date = g_strdup(buf);
10024 if (compose->from_name) {
10025 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
10026 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
10030 if (compose->subject_entry)
10031 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
10033 /* to, cc, reply-to, newsgroups */
10034 for (list = compose->header_list; list; list = list->next) {
10035 gchar *header = gtk_editable_get_chars(
10037 GTK_COMBO(((ComposeHeaderEntry *)list->data)->combo)->entry), 0, -1);
10038 gchar *entry = gtk_editable_get_chars(
10039 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
10041 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
10042 if ( newmsginfo->to == NULL ) {
10043 newmsginfo->to = g_strdup(entry);
10044 } else if (entry && *entry) {
10045 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
10046 g_free(newmsginfo->to);
10047 newmsginfo->to = tmp;
10050 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
10051 if ( newmsginfo->cc == NULL ) {
10052 newmsginfo->cc = g_strdup(entry);
10053 } else if (entry && *entry) {
10054 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
10055 g_free(newmsginfo->cc);
10056 newmsginfo->cc = tmp;
10059 if ( strcasecmp(header,
10060 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
10061 if ( newmsginfo->newsgroups == NULL ) {
10062 newmsginfo->newsgroups = g_strdup(entry);
10063 } else if (entry && *entry) {
10064 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
10065 g_free(newmsginfo->newsgroups);
10066 newmsginfo->newsgroups = tmp;
10074 /* other data is unset */
10080 /* update compose's dictionaries from folder dict settings */
10081 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
10082 FolderItem *folder_item)
10084 g_return_if_fail(compose != NULL);
10086 if (compose->gtkaspell && folder_item && folder_item->prefs) {
10087 FolderItemPrefs *prefs = folder_item->prefs;
10089 if (prefs->enable_default_dictionary)
10090 gtkaspell_change_dict(compose->gtkaspell,
10091 prefs->default_dictionary, FALSE);
10092 if (folder_item->prefs->enable_default_alt_dictionary)
10093 gtkaspell_change_alt_dict(compose->gtkaspell,
10094 prefs->default_alt_dictionary);
10095 if (prefs->enable_default_dictionary
10096 || prefs->enable_default_alt_dictionary)
10097 compose_spell_menu_changed(compose);