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 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 gchar *msg = g_strdup_printf(err_msg, line);
2675 alertpanel_error(msg);
2682 prev_autowrap = compose->autowrap;
2683 compose->autowrap = FALSE;
2685 mark = gtk_text_buffer_get_insert(buffer);
2686 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2687 if (g_utf8_validate(buf, -1, NULL)) {
2688 gtk_text_buffer_insert(buffer, &iter, buf, -1);
2690 gchar *tmpout = NULL;
2691 tmpout = conv_codeset_strdup
2692 (buf, conv_get_locale_charset_str_no_utf8(),
2694 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
2696 tmpout = g_malloc(strlen(buf)*2+1);
2697 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
2699 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
2703 cursor_pos = quote_fmt_get_cursor_pos();
2704 compose->set_cursor_pos = cursor_pos;
2705 if (cursor_pos == -1) {
2708 gtk_text_buffer_get_start_iter(buffer, &iter);
2709 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
2710 gtk_text_buffer_place_cursor(buffer, &iter);
2712 compose->autowrap = prev_autowrap;
2713 if (compose->autowrap && rewrap)
2714 compose_wrap_all(compose);
2721 SIGNAL_UNBLOCK(buffer);
2723 procmsg_msginfo_free( dummyinfo );
2728 /* if ml_post is of type addr@host and from is of type
2729 * addr-anything@host, return TRUE
2731 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
2733 gchar *left_ml = NULL;
2734 gchar *right_ml = NULL;
2735 gchar *left_from = NULL;
2736 gchar *right_from = NULL;
2737 gboolean result = FALSE;
2739 if (!ml_post || !from)
2742 left_ml = g_strdup(ml_post);
2743 if (strstr(left_ml, "@")) {
2744 right_ml = strstr(left_ml, "@")+1;
2745 *(strstr(left_ml, "@")) = '\0';
2748 left_from = g_strdup(from);
2749 if (strstr(left_from, "@")) {
2750 right_from = strstr(left_from, "@")+1;
2751 *(strstr(left_from, "@")) = '\0';
2754 if (left_ml && left_from && right_ml && right_from
2755 && !strncmp(left_from, left_ml, strlen(left_ml))
2756 && !strcmp(right_from, right_ml)) {
2765 static gboolean same_address(const gchar *addr1, const gchar *addr2)
2767 gchar *my_addr1, *my_addr2;
2769 if (!addr1 || !addr2)
2772 Xstrdup_a(my_addr1, addr1, return FALSE);
2773 Xstrdup_a(my_addr2, addr2, return FALSE);
2775 extract_address(my_addr1);
2776 extract_address(my_addr2);
2778 return !strcasecmp(my_addr1, my_addr2);
2781 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
2782 gboolean to_all, gboolean to_ml,
2784 gboolean followup_and_reply_to)
2786 GSList *cc_list = NULL;
2789 gchar *replyto = NULL;
2790 GHashTable *to_table;
2792 gboolean reply_to_ml = FALSE;
2793 gboolean default_reply_to = FALSE;
2795 g_return_if_fail(compose->account != NULL);
2796 g_return_if_fail(msginfo != NULL);
2798 reply_to_ml = to_ml && compose->ml_post;
2800 default_reply_to = msginfo->folder &&
2801 msginfo->folder->prefs->enable_default_reply_to;
2803 if (compose->account->protocol != A_NNTP) {
2804 if (reply_to_ml && !default_reply_to) {
2806 gboolean is_subscr = is_subscription(compose->ml_post,
2809 /* normal answer to ml post with a reply-to */
2810 compose_entry_append(compose,
2813 if (compose->replyto
2814 && !same_address(compose->ml_post, compose->replyto))
2815 compose_entry_append(compose,
2819 /* answer to subscription confirmation */
2820 if (compose->replyto)
2821 compose_entry_append(compose,
2824 else if (msginfo->from)
2825 compose_entry_append(compose,
2830 else if (!(to_all || to_sender) && default_reply_to) {
2831 compose_entry_append(compose,
2832 msginfo->folder->prefs->default_reply_to,
2834 compose_entry_mark_default_to(compose,
2835 msginfo->folder->prefs->default_reply_to);
2840 Xstrdup_a(tmp1, msginfo->from, return);
2841 extract_address(tmp1);
2842 if (to_all || to_sender ||
2843 !account_find_from_address(tmp1))
2844 compose_entry_append(compose,
2845 (compose->replyto && !to_sender)
2846 ? compose->replyto :
2847 msginfo->from ? msginfo->from : "",
2849 else if (!to_all && !to_sender) {
2850 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
2851 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
2852 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
2853 compose_entry_append(compose,
2854 msginfo->from ? msginfo->from : "",
2857 /* replying to own mail, use original recp */
2858 compose_entry_append(compose,
2859 msginfo->to ? msginfo->to : "",
2861 compose_entry_append(compose,
2862 msginfo->cc ? msginfo->cc : "",
2868 if (to_sender || (compose->followup_to &&
2869 !strncmp(compose->followup_to, "poster", 6)))
2870 compose_entry_append
2872 (compose->replyto ? compose->replyto :
2873 msginfo->from ? msginfo->from : ""),
2876 else if (followup_and_reply_to || to_all) {
2877 compose_entry_append
2879 (compose->replyto ? compose->replyto :
2880 msginfo->from ? msginfo->from : ""),
2883 compose_entry_append
2885 compose->followup_to ? compose->followup_to :
2886 compose->newsgroups ? compose->newsgroups : "",
2887 COMPOSE_NEWSGROUPS);
2890 compose_entry_append
2892 compose->followup_to ? compose->followup_to :
2893 compose->newsgroups ? compose->newsgroups : "",
2894 COMPOSE_NEWSGROUPS);
2897 if (msginfo->subject && *msginfo->subject) {
2901 buf = p = g_strdup(msginfo->subject);
2902 p += subject_get_prefix_length(p);
2903 memmove(buf, p, strlen(p) + 1);
2905 buf2 = g_strdup_printf("Re: %s", buf);
2906 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
2911 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
2913 if (to_ml && compose->ml_post) return;
2914 if (!to_all || compose->account->protocol == A_NNTP) return;
2916 if (compose->replyto) {
2917 Xstrdup_a(replyto, compose->replyto, return);
2918 extract_address(replyto);
2920 if (msginfo->from) {
2921 Xstrdup_a(from, msginfo->from, return);
2922 extract_address(from);
2925 if (replyto && from)
2926 cc_list = address_list_append_with_comments(cc_list, from);
2927 if (to_all && msginfo->folder &&
2928 msginfo->folder->prefs->enable_default_reply_to)
2929 cc_list = address_list_append_with_comments(cc_list,
2930 msginfo->folder->prefs->default_reply_to);
2931 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
2932 cc_list = address_list_append_with_comments(cc_list, compose->cc);
2934 to_table = g_hash_table_new(g_str_hash, g_str_equal);
2936 g_hash_table_insert(to_table, g_utf8_strdown(replyto, -1), GINT_TO_POINTER(1));
2937 if (compose->account) {
2938 g_hash_table_insert(to_table, g_utf8_strdown(compose->account->address, -1),
2939 GINT_TO_POINTER(1));
2941 /* remove address on To: and that of current account */
2942 for (cur = cc_list; cur != NULL; ) {
2943 GSList *next = cur->next;
2946 addr = g_utf8_strdown(cur->data, -1);
2947 extract_address(addr);
2949 if (GPOINTER_TO_INT(g_hash_table_lookup(to_table, addr)) == 1)
2950 cc_list = g_slist_remove(cc_list, cur->data);
2952 g_hash_table_insert(to_table, addr, GINT_TO_POINTER(1));
2956 hash_free_strings(to_table);
2957 g_hash_table_destroy(to_table);
2960 for (cur = cc_list; cur != NULL; cur = cur->next)
2961 compose_entry_append(compose, (gchar *)cur->data,
2963 slist_free_strings(cc_list);
2964 g_slist_free(cc_list);
2969 #define SET_ENTRY(entry, str) \
2972 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
2975 #define SET_ADDRESS(type, str) \
2978 compose_entry_append(compose, str, type); \
2981 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
2983 g_return_if_fail(msginfo != NULL);
2985 SET_ENTRY(subject_entry, msginfo->subject);
2986 SET_ENTRY(from_name, msginfo->from);
2987 SET_ADDRESS(COMPOSE_TO, msginfo->to);
2988 SET_ADDRESS(COMPOSE_CC, compose->cc);
2989 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
2990 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
2991 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
2992 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
2994 compose_update_priority_menu_item(compose);
2995 compose_update_privacy_system_menu_item(compose, FALSE);
2996 compose_show_first_last_header(compose, TRUE);
3002 static void compose_insert_sig(Compose *compose, gboolean replace)
3004 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3005 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3007 GtkTextIter iter, iter_end;
3009 gchar *search = NULL;
3010 gboolean prev_autowrap;
3011 gboolean found = FALSE, shift = FALSE;
3014 g_return_if_fail(compose->account != NULL);
3018 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3019 G_CALLBACK(compose_changed_cb),
3022 mark = gtk_text_buffer_get_insert(buffer);
3023 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3024 cur_pos = gtk_text_iter_get_offset (&iter);
3026 gtk_text_buffer_get_end_iter(buffer, &iter);
3028 search = compose->sig_str;
3030 if (replace && search) {
3031 GtkTextIter first_iter, start_iter, end_iter;
3033 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3035 if (compose->sig_str[0] == '\0')
3038 found = gtk_text_iter_forward_search(&first_iter,
3040 GTK_TEXT_SEARCH_TEXT_ONLY,
3041 &start_iter, &end_iter,
3045 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3049 if (replace && !found && search && strlen(search) > 2
3050 && search[0] == '\n' && search[1] == '\n') {
3056 g_free(compose->sig_str);
3057 compose->sig_str = compose_get_signature_str(compose);
3058 if (!compose->sig_str || (replace && !compose->account->auto_sig))
3059 compose->sig_str = g_strdup("");
3061 cur_pos = gtk_text_iter_get_offset(&iter);
3063 gtk_text_buffer_insert(buffer, &iter, compose->sig_str + 1, -1);
3065 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3067 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3068 gtk_text_iter_forward_char(&iter);
3069 gtk_text_iter_forward_char(&iter);
3070 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3071 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3073 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3074 cur_pos = gtk_text_buffer_get_char_count (buffer);
3076 /* put the cursor where it should be
3077 * either where the quote_fmt says, either before the signature */
3078 if (compose->set_cursor_pos < 0)
3079 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3081 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3082 compose->set_cursor_pos);
3084 gtk_text_buffer_place_cursor(buffer, &iter);
3085 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3086 G_CALLBACK(compose_changed_cb),
3092 static gchar *compose_get_signature_str(Compose *compose)
3094 gchar *sig_body = NULL;
3095 gchar *sig_str = NULL;
3096 gchar *utf8_sig_str = NULL;
3098 g_return_val_if_fail(compose->account != NULL, NULL);
3100 if (!compose->account->sig_path)
3103 if (compose->account->sig_type == SIG_FILE) {
3104 if (!is_file_or_fifo_exist(compose->account->sig_path)) {
3105 g_warning("can't open signature file: %s\n",
3106 compose->account->sig_path);
3111 if (compose->account->sig_type == SIG_COMMAND)
3112 sig_body = get_command_output(compose->account->sig_path);
3116 tmp = file_read_to_str(compose->account->sig_path);
3119 sig_body = normalize_newlines(tmp);
3123 if (compose->account->sig_sep) {
3124 sig_str = g_strconcat("\n\n", compose->account->sig_sep, "\n", sig_body,
3128 sig_str = g_strconcat("\n\n", sig_body, NULL);
3131 if (g_utf8_validate(sig_str, -1, NULL) == TRUE)
3132 utf8_sig_str = sig_str;
3134 utf8_sig_str = conv_codeset_strdup
3135 (sig_str, conv_get_locale_charset_str_no_utf8(),
3141 return utf8_sig_str;
3144 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3147 GtkTextBuffer *buffer;
3150 const gchar *cur_encoding;
3151 gchar buf[BUFFSIZE];
3154 gboolean prev_autowrap;
3155 gboolean badtxt = FALSE;
3157 g_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3159 if ((fp = g_fopen(file, "rb")) == NULL) {
3160 FILE_OP_ERROR(file, "fopen");
3161 return COMPOSE_INSERT_READ_ERROR;
3164 prev_autowrap = compose->autowrap;
3165 compose->autowrap = FALSE;
3167 text = GTK_TEXT_VIEW(compose->text);
3168 buffer = gtk_text_view_get_buffer(text);
3169 mark = gtk_text_buffer_get_insert(buffer);
3170 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3172 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3173 G_CALLBACK(text_inserted),
3176 cur_encoding = conv_get_locale_charset_str_no_utf8();
3178 while (fgets(buf, sizeof(buf), fp) != NULL) {
3181 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3182 str = g_strdup(buf);
3184 str = conv_codeset_strdup
3185 (buf, cur_encoding, CS_INTERNAL);
3188 /* strip <CR> if DOS/Windows file,
3189 replace <CR> with <LF> if Macintosh file. */
3192 if (len > 0 && str[len - 1] != '\n') {
3194 if (str[len] == '\r') str[len] = '\n';
3197 gtk_text_buffer_insert(buffer, &iter, str, -1);
3201 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3202 G_CALLBACK(text_inserted),
3204 compose->autowrap = prev_autowrap;
3205 if (compose->autowrap)
3206 compose_wrap_all(compose);
3211 return COMPOSE_INSERT_INVALID_CHARACTER;
3213 return COMPOSE_INSERT_SUCCESS;
3216 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3217 const gchar *filename,
3218 const gchar *content_type)
3226 GtkListStore *store;
3228 gboolean has_binary = FALSE;
3230 if (!is_file_exist(file)) {
3231 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3232 gboolean result = FALSE;
3233 if (file_from_uri && is_file_exist(file_from_uri)) {
3234 result = compose_attach_append(
3235 compose, file_from_uri,
3239 g_free(file_from_uri);
3242 alertpanel_error("File %s doesn't exist\n", filename);
3245 if ((size = get_file_size(file)) < 0) {
3246 alertpanel_error("Can't get file size of %s\n", filename);
3250 alertpanel_error(_("File %s is empty."), filename);
3253 if ((fp = g_fopen(file, "rb")) == NULL) {
3254 alertpanel_error(_("Can't read %s."), filename);
3259 ainfo = g_new0(AttachInfo, 1);
3260 auto_ainfo = g_auto_pointer_new_with_free
3261 (ainfo, (GFreeFunc) compose_attach_info_free);
3262 ainfo->file = g_strdup(file);
3265 ainfo->content_type = g_strdup(content_type);
3266 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3268 MsgFlags flags = {0, 0};
3270 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3271 ainfo->encoding = ENC_7BIT;
3273 ainfo->encoding = ENC_8BIT;
3275 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3276 if (msginfo && msginfo->subject)
3277 name = g_strdup(msginfo->subject);
3279 name = g_path_get_basename(filename ? filename : file);
3281 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3283 procmsg_msginfo_free(msginfo);
3285 if (!g_ascii_strncasecmp(content_type, "text", 4))
3286 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3288 ainfo->encoding = ENC_BASE64;
3289 name = g_path_get_basename(filename ? filename : file);
3290 ainfo->name = g_strdup(name);
3294 ainfo->content_type = procmime_get_mime_type(file);
3295 if (!ainfo->content_type) {
3296 ainfo->content_type =
3297 g_strdup("application/octet-stream");
3298 ainfo->encoding = ENC_BASE64;
3299 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text", 4))
3301 procmime_get_encoding_for_text_file(file, &has_binary);
3303 ainfo->encoding = ENC_BASE64;
3304 name = g_path_get_basename(filename ? filename : file);
3305 ainfo->name = g_strdup(name);
3309 if (ainfo->name != NULL
3310 && !strcmp(ainfo->name, ".")) {
3311 g_free(ainfo->name);
3315 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3316 g_free(ainfo->content_type);
3317 ainfo->content_type = g_strdup("application/octet-stream");
3321 size_text = to_human_readable(size);
3323 store = GTK_LIST_STORE(gtk_tree_view_get_model
3324 (GTK_TREE_VIEW(compose->attach_clist)));
3326 gtk_list_store_append(store, &iter);
3327 gtk_list_store_set(store, &iter,
3328 COL_MIMETYPE, ainfo->content_type,
3329 COL_SIZE, size_text,
3330 COL_NAME, ainfo->name,
3332 COL_AUTODATA, auto_ainfo,
3335 g_auto_pointer_free(auto_ainfo);
3339 static void compose_use_signing(Compose *compose, gboolean use_signing)
3341 GtkItemFactory *ifactory;
3342 GtkWidget *menuitem = NULL;
3344 compose->use_signing = use_signing;
3345 ifactory = gtk_item_factory_from_widget(compose->menubar);
3346 menuitem = gtk_item_factory_get_item
3347 (ifactory, "/Options/Sign");
3348 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
3352 static void compose_use_encryption(Compose *compose, gboolean use_encryption)
3354 GtkItemFactory *ifactory;
3355 GtkWidget *menuitem = NULL;
3357 compose->use_encryption = use_encryption;
3358 ifactory = gtk_item_factory_from_widget(compose->menubar);
3359 menuitem = gtk_item_factory_get_item
3360 (ifactory, "/Options/Encrypt");
3362 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
3366 #define NEXT_PART_NOT_CHILD(info) \
3368 node = info->node; \
3369 while (node->children) \
3370 node = g_node_last_child(node); \
3371 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3374 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3378 MimeInfo *firsttext = NULL;
3379 MimeInfo *encrypted = NULL;
3382 const gchar *partname = NULL;
3384 mimeinfo = procmime_scan_message(msginfo);
3385 if (!mimeinfo) return;
3387 if (mimeinfo->node->children == NULL) {
3388 procmime_mimeinfo_free_all(mimeinfo);
3392 /* find first content part */
3393 child = (MimeInfo *) mimeinfo->node->children->data;
3394 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3395 child = (MimeInfo *)child->node->children->data;
3397 if (child->type == MIMETYPE_TEXT) {
3399 debug_print("First text part found\n");
3400 } else if (compose->mode == COMPOSE_REEDIT &&
3401 child->type == MIMETYPE_APPLICATION &&
3402 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3403 encrypted = (MimeInfo *)child->node->parent->data;
3406 child = (MimeInfo *) mimeinfo->node->children->data;
3407 while (child != NULL) {
3410 if (child == encrypted) {
3411 /* skip this part of tree */
3412 NEXT_PART_NOT_CHILD(child);
3416 if (child->type == MIMETYPE_MULTIPART) {
3417 /* get the actual content */
3418 child = procmime_mimeinfo_next(child);
3422 if (child == firsttext) {
3423 child = procmime_mimeinfo_next(child);
3427 outfile = procmime_get_tmp_file_name(child);
3428 if ((err = procmime_get_part(outfile, child)) < 0)
3429 g_warning("Can't get the part of multipart message. (%s)", strerror(-err));
3431 gchar *content_type;
3433 content_type = procmime_get_content_type_str(child->type, child->subtype);
3435 /* if we meet a pgp signature, we don't attach it, but
3436 * we force signing. */
3437 if ((strcmp(content_type, "application/pgp-signature") &&
3438 strcmp(content_type, "application/pkcs7-signature") &&
3439 strcmp(content_type, "application/x-pkcs7-signature"))
3440 || compose->mode == COMPOSE_REDIRECT) {
3441 partname = procmime_mimeinfo_get_parameter(child, "filename");
3442 if (partname == NULL)
3443 partname = procmime_mimeinfo_get_parameter(child, "name");
3444 if (partname == NULL)
3446 compose_attach_append(compose, outfile,
3447 partname, content_type);
3449 compose_force_signing(compose, compose->account);
3451 g_free(content_type);
3454 NEXT_PART_NOT_CHILD(child);
3456 procmime_mimeinfo_free_all(mimeinfo);
3459 #undef NEXT_PART_NOT_CHILD
3464 WAIT_FOR_INDENT_CHAR,
3465 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3468 /* return indent length, we allow:
3469 indent characters followed by indent characters or spaces/tabs,
3470 alphabets and numbers immediately followed by indent characters,
3471 and the repeating sequences of the above
3472 If quote ends with multiple spaces, only the first one is included. */
3473 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3474 const GtkTextIter *start, gint *len)
3476 GtkTextIter iter = *start;
3480 IndentState state = WAIT_FOR_INDENT_CHAR;
3483 gint alnum_count = 0;
3484 gint space_count = 0;
3487 if (prefs_common.quote_chars == NULL) {
3491 while (!gtk_text_iter_ends_line(&iter)) {
3492 wc = gtk_text_iter_get_char(&iter);
3493 if (g_unichar_iswide(wc))
3495 clen = g_unichar_to_utf8(wc, ch);
3499 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
3500 is_space = g_unichar_isspace(wc);
3502 if (state == WAIT_FOR_INDENT_CHAR) {
3503 if (!is_indent && !g_unichar_isalnum(wc))
3506 quote_len += alnum_count + space_count + 1;
3507 alnum_count = space_count = 0;
3508 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
3511 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
3512 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
3516 else if (is_indent) {
3517 quote_len += alnum_count + space_count + 1;
3518 alnum_count = space_count = 0;
3521 state = WAIT_FOR_INDENT_CHAR;
3525 gtk_text_iter_forward_char(&iter);
3528 if (quote_len > 0 && space_count > 0)
3534 if (quote_len > 0) {
3536 gtk_text_iter_forward_chars(&iter, quote_len);
3537 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
3543 /* return TRUE if the line is itemized */
3544 static gboolean compose_is_itemized(GtkTextBuffer *buffer,
3545 const GtkTextIter *start)
3547 GtkTextIter iter = *start;
3552 if (gtk_text_iter_ends_line(&iter))
3556 wc = gtk_text_iter_get_char(&iter);
3557 if (!g_unichar_isspace(wc))
3559 gtk_text_iter_forward_char(&iter);
3560 if (gtk_text_iter_ends_line(&iter))
3564 clen = g_unichar_to_utf8(wc, ch);
3568 if (!strchr("*-+", ch[0]))
3571 gtk_text_iter_forward_char(&iter);
3572 if (gtk_text_iter_ends_line(&iter))
3574 wc = gtk_text_iter_get_char(&iter);
3575 if (g_unichar_isspace(wc))
3581 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
3582 const GtkTextIter *start,
3583 GtkTextIter *break_pos,
3587 GtkTextIter iter = *start, line_end = *start;
3588 PangoLogAttr *attrs;
3595 gboolean can_break = FALSE;
3596 gboolean do_break = FALSE;
3597 gboolean was_white = FALSE;
3598 gboolean prev_dont_break = FALSE;
3600 gtk_text_iter_forward_to_line_end(&line_end);
3601 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
3602 len = g_utf8_strlen(str, -1);
3606 g_warning("compose_get_line_break_pos: len = 0!\n");
3610 /* g_print("breaking line: %d: %s (len = %d)\n",
3611 gtk_text_iter_get_line(&iter), str, len); */
3613 attrs = g_new(PangoLogAttr, len + 1);
3615 pango_default_break(str, -1, NULL, attrs, len + 1);
3619 /* skip quote and leading spaces */
3620 for (i = 0; *p != '\0' && i < len; i++) {
3623 wc = g_utf8_get_char(p);
3624 if (i >= quote_len && !g_unichar_isspace(wc))
3626 if (g_unichar_iswide(wc))
3628 else if (*p == '\t')
3632 p = g_utf8_next_char(p);
3635 for (; *p != '\0' && i < len; i++) {
3636 PangoLogAttr *attr = attrs + i;
3640 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
3643 was_white = attr->is_white;
3645 /* don't wrap URI */
3646 if ((uri_len = get_uri_len(p)) > 0) {
3648 if (pos > 0 && col > max_col) {
3658 wc = g_utf8_get_char(p);
3659 if (g_unichar_iswide(wc)) {
3661 if (prev_dont_break && can_break && attr->is_line_break)
3663 } else if (*p == '\t')
3667 if (pos > 0 && col > max_col) {
3672 if (*p == '-' || *p == '/')
3673 prev_dont_break = TRUE;
3675 prev_dont_break = FALSE;
3677 p = g_utf8_next_char(p);
3681 debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col);
3686 *break_pos = *start;
3687 gtk_text_iter_set_line_offset(break_pos, pos);
3692 static gboolean compose_join_next_line(Compose *compose,
3693 GtkTextBuffer *buffer,
3695 const gchar *quote_str)
3697 GtkTextIter iter_ = *iter, cur, prev, next, end;
3698 PangoLogAttr attrs[3];
3700 gchar *next_quote_str;
3703 gboolean keep_cursor = FALSE;
3705 if (!gtk_text_iter_forward_line(&iter_) ||
3706 gtk_text_iter_ends_line(&iter_))
3709 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
3711 if ((quote_str || next_quote_str) &&
3712 strcmp2(quote_str, next_quote_str) != 0) {
3713 g_free(next_quote_str);
3716 g_free(next_quote_str);
3719 if (quote_len > 0) {
3720 gtk_text_iter_forward_chars(&end, quote_len);
3721 if (gtk_text_iter_ends_line(&end))
3725 /* don't join itemized lines */
3726 if (compose_is_itemized(buffer, &end))
3729 /* don't join signature separator */
3730 if (compose_is_sig_separator(compose, buffer, &iter_))
3733 /* delete quote str */
3735 gtk_text_buffer_delete(buffer, &iter_, &end);
3737 /* don't join line breaks put by the user */
3739 gtk_text_iter_backward_char(&cur);
3740 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
3741 gtk_text_iter_forward_char(&cur);
3745 gtk_text_iter_forward_char(&cur);
3746 /* delete linebreak and extra spaces */
3747 while (gtk_text_iter_backward_char(&cur)) {
3748 wc1 = gtk_text_iter_get_char(&cur);
3749 if (!g_unichar_isspace(wc1))
3754 while (!gtk_text_iter_ends_line(&cur)) {
3755 wc1 = gtk_text_iter_get_char(&cur);
3756 if (!g_unichar_isspace(wc1))
3758 gtk_text_iter_forward_char(&cur);
3761 if (!gtk_text_iter_equal(&prev, &next)) {
3764 mark = gtk_text_buffer_get_insert(buffer);
3765 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
3766 if (gtk_text_iter_equal(&prev, &cur))
3768 gtk_text_buffer_delete(buffer, &prev, &next);
3772 /* insert space if required */
3773 gtk_text_iter_backward_char(&prev);
3774 wc1 = gtk_text_iter_get_char(&prev);
3775 wc2 = gtk_text_iter_get_char(&next);
3776 gtk_text_iter_forward_char(&next);
3777 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
3778 pango_default_break(str, -1, NULL, attrs, 3);
3779 if (!attrs[1].is_line_break ||
3780 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
3781 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
3783 gtk_text_iter_backward_char(&iter_);
3784 gtk_text_buffer_place_cursor(buffer, &iter_);
3793 #define ADD_TXT_POS(bp_, ep_, pti_) \
3794 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
3795 last = last->next; \
3796 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
3797 last->next = NULL; \
3799 g_warning("alloc error scanning URIs\n"); \
3802 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
3804 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3805 GtkTextBuffer *buffer;
3806 GtkTextIter iter, break_pos, end_of_line;
3807 gchar *quote_str = NULL;
3809 gboolean wrap_quote = prefs_common.linewrap_quote;
3810 gboolean prev_autowrap = compose->autowrap;
3811 gint startq_offset = -1, noq_offset = -1;
3812 gint uri_start = -1, uri_stop = -1;
3813 gint nouri_start = -1, nouri_stop = -1;
3814 gint num_blocks = 0;
3815 gint quotelevel = -1;
3816 gboolean modified = force;
3817 gboolean removed = FALSE;
3818 gboolean modified_before_remove = FALSE;
3820 gboolean start = TRUE;
3825 if (compose->draft_timeout_tag == -2) {
3829 compose->autowrap = FALSE;
3831 buffer = gtk_text_view_get_buffer(text);
3832 undo_wrapping(compose->undostruct, TRUE);
3837 mark = gtk_text_buffer_get_insert(buffer);
3838 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3842 if (compose->draft_timeout_tag == -2) {
3843 if (gtk_text_iter_ends_line(&iter)) {
3844 while (gtk_text_iter_ends_line(&iter) &&
3845 gtk_text_iter_forward_line(&iter))
3848 while (gtk_text_iter_backward_line(&iter)) {
3849 if (gtk_text_iter_ends_line(&iter)) {
3850 gtk_text_iter_forward_line(&iter);
3856 /* move to line start */
3857 gtk_text_iter_set_line_offset(&iter, 0);
3859 /* go until paragraph end (empty line) */
3860 while (start || !gtk_text_iter_ends_line(&iter)) {
3861 gchar *scanpos = NULL;
3862 /* parse table - in order of priority */
3864 const gchar *needle; /* token */
3866 /* token search function */
3867 gchar *(*search) (const gchar *haystack,
3868 const gchar *needle);
3869 /* part parsing function */
3870 gboolean (*parse) (const gchar *start,
3871 const gchar *scanpos,
3875 /* part to URI function */
3876 gchar *(*build_uri) (const gchar *bp,
3880 static struct table parser[] = {
3881 {"http://", strcasestr, get_uri_part, make_uri_string},
3882 {"https://", strcasestr, get_uri_part, make_uri_string},
3883 {"ftp://", strcasestr, get_uri_part, make_uri_string},
3884 {"sftp://", strcasestr, get_uri_part, make_uri_string},
3885 {"www.", strcasestr, get_uri_part, make_http_string},
3886 {"mailto:", strcasestr, get_uri_part, make_uri_string},
3887 {"@", strcasestr, get_email_part, make_email_string}
3889 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
3890 gint last_index = PARSE_ELEMS;
3892 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
3896 if (!prev_autowrap && num_blocks == 0) {
3898 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3899 G_CALLBACK(text_inserted),
3902 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
3905 uri_start = uri_stop = -1;
3907 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
3910 debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str);
3911 if (startq_offset == -1)
3912 startq_offset = gtk_text_iter_get_offset(&iter);
3913 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
3914 if (quotelevel > 2) {
3915 /* recycle colors */
3916 if (prefs_common.recycle_quote_colors)
3925 if (startq_offset == -1)
3926 noq_offset = gtk_text_iter_get_offset(&iter);
3930 if (prev_autowrap == FALSE && !force && !wrap_quote) {
3933 if (gtk_text_iter_ends_line(&iter)) {
3935 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
3936 prefs_common.linewrap_len,
3938 GtkTextIter prev, next, cur;
3940 if (prev_autowrap != FALSE || force) {
3941 compose->automatic_break = TRUE;
3943 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
3944 compose->automatic_break = FALSE;
3945 } else if (quote_str && wrap_quote) {
3946 compose->automatic_break = TRUE;
3948 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
3949 compose->automatic_break = FALSE;
3952 /* remove trailing spaces */
3954 gtk_text_iter_backward_char(&cur);
3956 while (!gtk_text_iter_starts_line(&cur)) {
3959 gtk_text_iter_backward_char(&cur);
3960 wc = gtk_text_iter_get_char(&cur);
3961 if (!g_unichar_isspace(wc))
3965 if (!gtk_text_iter_equal(&prev, &next)) {
3966 gtk_text_buffer_delete(buffer, &prev, &next);
3968 gtk_text_iter_forward_char(&break_pos);
3972 gtk_text_buffer_insert(buffer, &break_pos,
3976 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
3978 /* move iter to current line start */
3979 gtk_text_iter_set_line_offset(&iter, 0);
3986 /* move iter to next line start */
3992 if (!prev_autowrap && num_blocks > 0) {
3994 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3995 G_CALLBACK(text_inserted),
3999 while (!gtk_text_iter_ends_line(&end_of_line)) {
4000 gtk_text_iter_forward_char(&end_of_line);
4002 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4004 nouri_start = gtk_text_iter_get_offset(&iter);
4005 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4007 walk_pos = gtk_text_iter_get_offset(&iter);
4008 /* FIXME: this looks phony. scanning for anything in the parse table */
4009 for (n = 0; n < PARSE_ELEMS; n++) {
4012 tmp = parser[n].search(walk, parser[n].needle);
4014 if (scanpos == NULL || tmp < scanpos) {
4023 /* check if URI can be parsed */
4024 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4025 (const gchar **)&ep, FALSE)
4026 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4030 strlen(parser[last_index].needle);
4033 uri_start = walk_pos + (bp - o_walk);
4034 uri_stop = walk_pos + (ep - o_walk);
4038 gtk_text_iter_forward_line(&iter);
4041 if (startq_offset != -1) {
4042 GtkTextIter startquote, endquote;
4043 gtk_text_buffer_get_iter_at_offset(
4044 buffer, &startquote, startq_offset);
4047 switch (quotelevel) {
4049 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4050 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4051 gtk_text_buffer_apply_tag_by_name(
4052 buffer, "quote0", &startquote, &endquote);
4053 gtk_text_buffer_remove_tag_by_name(
4054 buffer, "quote1", &startquote, &endquote);
4055 gtk_text_buffer_remove_tag_by_name(
4056 buffer, "quote2", &startquote, &endquote);
4061 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4062 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4063 gtk_text_buffer_apply_tag_by_name(
4064 buffer, "quote1", &startquote, &endquote);
4065 gtk_text_buffer_remove_tag_by_name(
4066 buffer, "quote0", &startquote, &endquote);
4067 gtk_text_buffer_remove_tag_by_name(
4068 buffer, "quote2", &startquote, &endquote);
4073 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4074 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4075 gtk_text_buffer_apply_tag_by_name(
4076 buffer, "quote2", &startquote, &endquote);
4077 gtk_text_buffer_remove_tag_by_name(
4078 buffer, "quote0", &startquote, &endquote);
4079 gtk_text_buffer_remove_tag_by_name(
4080 buffer, "quote1", &startquote, &endquote);
4086 } else if (noq_offset != -1) {
4087 GtkTextIter startnoquote, endnoquote;
4088 gtk_text_buffer_get_iter_at_offset(
4089 buffer, &startnoquote, noq_offset);
4092 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4093 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4094 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4095 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4096 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4097 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4098 gtk_text_buffer_remove_tag_by_name(
4099 buffer, "quote0", &startnoquote, &endnoquote);
4100 gtk_text_buffer_remove_tag_by_name(
4101 buffer, "quote1", &startnoquote, &endnoquote);
4102 gtk_text_buffer_remove_tag_by_name(
4103 buffer, "quote2", &startnoquote, &endnoquote);
4109 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4110 GtkTextIter nouri_start_iter, nouri_end_iter;
4111 gtk_text_buffer_get_iter_at_offset(
4112 buffer, &nouri_start_iter, nouri_start);
4113 gtk_text_buffer_get_iter_at_offset(
4114 buffer, &nouri_end_iter, nouri_stop);
4115 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4116 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4117 gtk_text_buffer_remove_tag_by_name(
4118 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4119 modified_before_remove = modified;
4124 if (uri_start > 0 && uri_stop > 0) {
4125 GtkTextIter uri_start_iter, uri_end_iter, back;
4126 gtk_text_buffer_get_iter_at_offset(
4127 buffer, &uri_start_iter, uri_start);
4128 gtk_text_buffer_get_iter_at_offset(
4129 buffer, &uri_end_iter, uri_stop);
4130 back = uri_end_iter;
4131 gtk_text_iter_backward_char(&back);
4132 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4133 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4134 gtk_text_buffer_apply_tag_by_name(
4135 buffer, "link", &uri_start_iter, &uri_end_iter);
4137 if (removed && !modified_before_remove) {
4143 debug_print("not modified, out after %d lines\n", lines);
4148 debug_print("modified, out after %d lines\n", lines);
4152 undo_wrapping(compose->undostruct, FALSE);
4153 compose->autowrap = prev_autowrap;
4158 void compose_action_cb(void *data)
4160 Compose *compose = (Compose *)data;
4161 compose_wrap_all(compose);
4164 static void compose_wrap_all(Compose *compose)
4166 compose_wrap_all_full(compose, FALSE);
4169 static void compose_wrap_all_full(Compose *compose, gboolean force)
4171 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4172 GtkTextBuffer *buffer;
4174 gboolean modified = TRUE;
4176 buffer = gtk_text_view_get_buffer(text);
4178 gtk_text_buffer_get_start_iter(buffer, &iter);
4179 while (!gtk_text_iter_is_end(&iter) && modified)
4180 modified = compose_beautify_paragraph(compose, &iter, force);
4184 static void compose_set_title(Compose *compose)
4190 edited = compose->modified ? _(" [Edited]") : "";
4192 subject = gtk_editable_get_chars(
4193 GTK_EDITABLE(compose->subject_entry), 0, -1);
4196 if (subject && strlen(subject))
4197 str = g_strdup_printf(_("%s - Compose message%s"),
4200 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4202 str = g_strdup(_("Compose message"));
4205 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4211 * compose_current_mail_account:
4213 * Find a current mail account (the currently selected account, or the
4214 * default account, if a news account is currently selected). If a
4215 * mail account cannot be found, display an error message.
4217 * Return value: Mail account, or NULL if not found.
4219 static PrefsAccount *
4220 compose_current_mail_account(void)
4224 if (cur_account && cur_account->protocol != A_NNTP)
4227 ac = account_get_default();
4228 if (!ac || ac->protocol == A_NNTP) {
4229 alertpanel_error(_("Account for sending mail is not specified.\n"
4230 "Please select a mail account before sending."));
4237 #define QUOTE_IF_REQUIRED(out, str) \
4239 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4243 len = strlen(str) + 3; \
4244 if ((__tmp = alloca(len)) == NULL) { \
4245 g_warning("can't allocate memory\n"); \
4246 g_string_free(header, TRUE); \
4249 g_snprintf(__tmp, len, "\"%s\"", str); \
4254 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4255 g_warning("can't allocate memory\n"); \
4256 g_string_free(header, TRUE); \
4259 strcpy(__tmp, str); \
4265 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4267 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4271 len = strlen(str) + 3; \
4272 if ((__tmp = alloca(len)) == NULL) { \
4273 g_warning("can't allocate memory\n"); \
4276 g_snprintf(__tmp, len, "\"%s\"", str); \
4281 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4282 g_warning("can't allocate memory\n"); \
4285 strcpy(__tmp, str); \
4291 static void compose_select_account(Compose *compose, PrefsAccount *account,
4294 GtkItemFactory *ifactory;
4297 g_return_if_fail(account != NULL);
4299 compose->account = account;
4301 if (account->name && *account->name) {
4303 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4304 from = g_strdup_printf("%s <%s>",
4305 buf, account->address);
4306 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4308 from = g_strdup_printf("<%s>",
4310 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4315 compose_set_title(compose);
4317 ifactory = gtk_item_factory_from_widget(compose->menubar);
4319 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4320 menu_set_active(ifactory, "/Options/Sign", TRUE);
4322 menu_set_active(ifactory, "/Options/Sign", FALSE);
4323 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4324 menu_set_active(ifactory, "/Options/Encrypt", TRUE);
4326 menu_set_active(ifactory, "/Options/Encrypt", FALSE);
4328 activate_privacy_system(compose, account, FALSE);
4330 if (!init && compose->mode != COMPOSE_REDIRECT) {
4331 undo_block(compose->undostruct);
4332 compose_insert_sig(compose, TRUE);
4333 undo_unblock(compose->undostruct);
4337 /* use account's dict info if set */
4338 if (compose->gtkaspell) {
4339 if (account->enable_default_dictionary)
4340 gtkaspell_change_dict(compose->gtkaspell,
4341 account->default_dictionary, FALSE);
4342 if (account->enable_default_alt_dictionary)
4343 gtkaspell_change_alt_dict(compose->gtkaspell,
4344 account->default_alt_dictionary);
4345 if (account->enable_default_dictionary
4346 || account->enable_default_alt_dictionary)
4347 compose_spell_menu_changed(compose);
4352 gboolean compose_check_for_valid_recipient(Compose *compose) {
4353 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
4354 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
4355 gboolean recipient_found = FALSE;
4359 /* free to and newsgroup list */
4360 slist_free_strings(compose->to_list);
4361 g_slist_free(compose->to_list);
4362 compose->to_list = NULL;
4364 slist_free_strings(compose->newsgroup_list);
4365 g_slist_free(compose->newsgroup_list);
4366 compose->newsgroup_list = NULL;
4368 /* search header entries for to and newsgroup entries */
4369 for (list = compose->header_list; list; list = list->next) {
4372 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_COMBO(((ComposeHeaderEntry *)list->data)->combo)->entry), 0, -1);
4373 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4376 if (entry[0] != '\0') {
4377 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
4378 if (!strcmp(header, prefs_common_translated_header_name(*strptr))) {
4379 compose->to_list = address_list_append(compose->to_list, entry);
4380 recipient_found = TRUE;
4383 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
4384 if (!strcmp(header, prefs_common_translated_header_name(*strptr))) {
4385 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
4386 recipient_found = TRUE;
4393 return recipient_found;
4396 static gboolean compose_check_for_set_recipients(Compose *compose)
4398 if (compose->account->set_autocc && compose->account->auto_cc) {
4399 gboolean found_other = FALSE;
4401 /* search header entries for to and newsgroup entries */
4402 for (list = compose->header_list; list; list = list->next) {
4405 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4406 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_COMBO(((ComposeHeaderEntry *)list->data)->combo)->entry), 0, -1);
4408 if (strcmp(entry, compose->account->auto_cc)
4409 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
4419 if (compose->batch) {
4420 gtk_widget_show_all(compose->window);
4422 aval = alertpanel(_("Send"),
4423 _("The only recipient is the default CC address. Send anyway?"),
4424 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4425 if (aval != G_ALERTALTERNATE)
4429 if (compose->account->set_autobcc && compose->account->auto_bcc) {
4430 gboolean found_other = FALSE;
4432 /* search header entries for to and newsgroup entries */
4433 for (list = compose->header_list; list; list = list->next) {
4436 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4437 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_COMBO(((ComposeHeaderEntry *)list->data)->combo)->entry), 0, -1);
4439 if (strcmp(entry, compose->account->auto_bcc)
4440 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
4450 if (compose->batch) {
4451 gtk_widget_show_all(compose->window);
4453 aval = alertpanel(_("Send"),
4454 _("The only recipient is the default BCC address. Send anyway?"),
4455 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4456 if (aval != G_ALERTALTERNATE)
4463 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
4467 if (compose_check_for_valid_recipient(compose) == FALSE) {
4468 if (compose->batch) {
4469 gtk_widget_show_all(compose->window);
4471 alertpanel_error(_("Recipient is not specified."));
4475 if (compose_check_for_set_recipients(compose) == FALSE) {
4479 if (!compose->batch) {
4480 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
4481 if (*str == '\0' && check_everything == TRUE &&
4482 compose->mode != COMPOSE_REDIRECT) {
4485 aval = alertpanel(_("Send"),
4486 _("Subject is empty. Send it anyway?"),
4487 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4488 if (aval != G_ALERTALTERNATE)
4493 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
4499 gint compose_send(Compose *compose)
4502 FolderItem *folder = NULL;
4504 gchar *msgpath = NULL;
4505 gboolean discard_window = FALSE;
4506 gchar *errstr = NULL;
4507 gchar *tmsgid = NULL;
4508 MainWindow *mainwin = mainwindow_get_mainwindow();
4509 gboolean queued_removed = FALSE;
4511 if (prefs_common.send_dialog_mode != SEND_DIALOG_ALWAYS
4512 || compose->batch == TRUE)
4513 discard_window = TRUE;
4515 compose_allow_user_actions (compose, FALSE);
4516 compose->sending = TRUE;
4518 if (compose_check_entries(compose, TRUE) == FALSE) {
4519 if (compose->batch) {
4520 gtk_widget_show_all(compose->window);
4526 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
4529 if (compose->batch) {
4530 gtk_widget_show_all(compose->window);
4533 alertpanel_error(_("Could not queue message for sending:\n\n"
4534 "Charset conversion failed."));
4535 } else if (val == -5) {
4536 alertpanel_error(_("Could not queue message for sending:\n\n"
4537 "Couldn't get recipient encryption key."));
4538 } else if (val == -6) {
4540 } else if (val == -3) {
4541 if (privacy_peek_error())
4542 alertpanel_error(_("Could not queue message for sending:\n\n"
4543 "Signature failed: %s"), privacy_get_error());
4544 } else if (val == -2 && errno != 0) {
4545 alertpanel_error(_("Could not queue message for sending:\n\n%s."), strerror(errno));
4547 alertpanel_error(_("Could not queue message for sending."));
4552 tmsgid = g_strdup(compose->msgid);
4553 if (discard_window) {
4554 compose->sending = FALSE;
4555 compose_close(compose);
4556 /* No more compose access in the normal codepath
4557 * after this point! */
4562 alertpanel_error(_("The message was queued but could not be "
4563 "sent.\nUse \"Send queued messages\" from "
4564 "the main window to retry."));
4565 if (!discard_window) {
4572 if (msgpath == NULL) {
4573 msgpath = folder_item_fetch_msg(folder, msgnum);
4574 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
4577 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
4581 if (!discard_window) {
4583 if (!queued_removed)
4584 folder_item_remove_msg(folder, msgnum);
4585 folder_item_scan(folder);
4587 /* make sure we delete that */
4588 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
4590 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
4591 folder_item_remove_msg(folder, tmp->msgnum);
4592 procmsg_msginfo_free(tmp);
4599 if (!queued_removed)
4600 folder_item_remove_msg(folder, msgnum);
4601 folder_item_scan(folder);
4603 /* make sure we delete that */
4604 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
4606 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
4607 folder_item_remove_msg(folder, tmp->msgnum);
4608 procmsg_msginfo_free(tmp);
4611 if (!discard_window) {
4612 compose->sending = FALSE;
4613 compose_allow_user_actions (compose, TRUE);
4614 compose_close(compose);
4618 gchar *tmp = g_strdup_printf(_("%s\nUse \"Send queued messages\" from "
4619 "the main window to retry."), errstr);
4621 alertpanel_error_log(tmp);
4624 alertpanel_error_log(_("The message was queued but could not be "
4625 "sent.\nUse \"Send queued messages\" from "
4626 "the main window to retry."));
4628 if (!discard_window) {
4637 toolbar_main_set_sensitive(mainwin);
4638 main_window_set_menu_sensitive(mainwin);
4644 compose_allow_user_actions (compose, TRUE);
4645 compose->sending = FALSE;
4646 compose->modified = TRUE;
4647 toolbar_main_set_sensitive(mainwin);
4648 main_window_set_menu_sensitive(mainwin);
4653 static gboolean compose_use_attach(Compose *compose)
4655 GtkTreeModel *model = gtk_tree_view_get_model
4656 (GTK_TREE_VIEW(compose->attach_clist));
4657 return gtk_tree_model_iter_n_children(model, NULL) > 0;
4660 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
4663 gchar buf[BUFFSIZE];
4665 gboolean first_to_address;
4666 gboolean first_cc_address;
4668 ComposeHeaderEntry *headerentry;
4669 const gchar *headerentryname;
4670 const gchar *cc_hdr;
4671 const gchar *to_hdr;
4673 debug_print("Writing redirect header\n");
4675 cc_hdr = prefs_common_translated_header_name("Cc:");
4676 to_hdr = prefs_common_translated_header_name("To:");
4678 first_to_address = TRUE;
4679 for (list = compose->header_list; list; list = list->next) {
4680 headerentry = ((ComposeHeaderEntry *)list->data);
4681 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(headerentry->combo)->entry));
4683 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
4684 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
4685 Xstrdup_a(str, entstr, return -1);
4687 if (str[0] != '\0') {
4688 compose_convert_header
4689 (compose, buf, sizeof(buf), str,
4690 strlen("Resent-To") + 2, TRUE);
4692 if (first_to_address) {
4693 fprintf(fp, "Resent-To: ");
4694 first_to_address = FALSE;
4698 fprintf(fp, "%s", buf);
4702 if (!first_to_address) {
4706 first_cc_address = TRUE;
4707 for (list = compose->header_list; list; list = list->next) {
4708 headerentry = ((ComposeHeaderEntry *)list->data);
4709 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(headerentry->combo)->entry));
4711 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
4712 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
4713 Xstrdup_a(str, strg, return -1);
4715 if (str[0] != '\0') {
4716 compose_convert_header
4717 (compose, buf, sizeof(buf), str,
4718 strlen("Resent-Cc") + 2, TRUE);
4720 if (first_cc_address) {
4721 fprintf(fp, "Resent-Cc: ");
4722 first_cc_address = FALSE;
4726 fprintf(fp, "%s", buf);
4730 if (!first_cc_address) {
4737 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
4739 gchar buf[BUFFSIZE];
4741 const gchar *entstr;
4742 /* struct utsname utsbuf; */
4744 g_return_val_if_fail(fp != NULL, -1);
4745 g_return_val_if_fail(compose->account != NULL, -1);
4746 g_return_val_if_fail(compose->account->address != NULL, -1);
4749 get_rfc822_date(buf, sizeof(buf));
4750 fprintf(fp, "Resent-Date: %s\n", buf);
4753 if (compose->account->name && *compose->account->name) {
4754 compose_convert_header
4755 (compose, buf, sizeof(buf), compose->account->name,
4756 strlen("From: "), TRUE);
4757 fprintf(fp, "Resent-From: %s <%s>\n",
4758 buf, compose->account->address);
4760 fprintf(fp, "Resent-From: %s\n", compose->account->address);
4763 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
4764 if (*entstr != '\0') {
4765 Xstrdup_a(str, entstr, return -1);
4768 compose_convert_header(compose, buf, sizeof(buf), str,
4769 strlen("Subject: "), FALSE);
4770 fprintf(fp, "Subject: %s\n", buf);
4774 /* Resent-Message-ID */
4775 if (compose->account->gen_msgid) {
4776 generate_msgid(buf, sizeof(buf));
4777 fprintf(fp, "Resent-Message-ID: <%s>\n", buf);
4778 compose->msgid = g_strdup(buf);
4781 compose_redirect_write_headers_from_headerlist(compose, fp);
4783 /* separator between header and body */
4789 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
4793 gchar buf[BUFFSIZE];
4795 gboolean skip = FALSE;
4796 gchar *not_included[]={
4797 "Return-Path:", "Delivered-To:", "Received:",
4798 "Subject:", "X-UIDL:", "AF:",
4799 "NF:", "PS:", "SRH:",
4800 "SFN:", "DSR:", "MID:",
4801 "CFG:", "PT:", "S:",
4802 "RQ:", "SSV:", "NSV:",
4803 "SSH:", "R:", "MAID:",
4804 "NAID:", "RMID:", "FMID:",
4805 "SCF:", "RRCPT:", "NG:",
4806 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
4807 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
4808 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
4809 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
4812 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
4813 FILE_OP_ERROR(compose->redirect_filename, "fopen");
4817 while (procheader_get_one_field_asis(buf, sizeof(buf), fp) != -1) {
4819 for (i = 0; not_included[i] != NULL; i++) {
4820 if (g_ascii_strncasecmp(buf, not_included[i],
4821 strlen(not_included[i])) == 0) {
4828 if (fputs(buf, fdest) == -1)
4831 if (!prefs_common.redirect_keep_from) {
4832 if (g_ascii_strncasecmp(buf, "From:",
4833 strlen("From:")) == 0) {
4834 fputs(" (by way of ", fdest);
4835 if (compose->account->name
4836 && *compose->account->name) {
4837 compose_convert_header
4838 (compose, buf, sizeof(buf),
4839 compose->account->name,
4842 fprintf(fdest, "%s <%s>",
4844 compose->account->address);
4846 fprintf(fdest, "%s",
4847 compose->account->address);
4852 if (fputs("\n", fdest) == -1)
4856 compose_redirect_write_headers(compose, fdest);
4858 while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
4859 if (fwrite(buf, sizeof(gchar), len, fdest) != len)
4872 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
4874 GtkTextBuffer *buffer;
4875 GtkTextIter start, end;
4878 const gchar *out_codeset;
4879 EncodingType encoding;
4880 MimeInfo *mimemsg, *mimetext;
4883 if (action == COMPOSE_WRITE_FOR_SEND)
4884 attach_parts = TRUE;
4886 /* create message MimeInfo */
4887 mimemsg = procmime_mimeinfo_new();
4888 mimemsg->type = MIMETYPE_MESSAGE;
4889 mimemsg->subtype = g_strdup("rfc822");
4890 mimemsg->content = MIMECONTENT_MEM;
4891 mimemsg->tmp = TRUE; /* must free content later */
4892 mimemsg->data.mem = compose_get_header(compose);
4894 /* Create text part MimeInfo */
4895 /* get all composed text */
4896 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
4897 gtk_text_buffer_get_start_iter(buffer, &start);
4898 gtk_text_buffer_get_end_iter(buffer, &end);
4899 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
4900 if (is_ascii_str(chars)) {
4903 out_codeset = CS_US_ASCII;
4904 encoding = ENC_7BIT;
4906 const gchar *src_codeset = CS_INTERNAL;
4908 out_codeset = conv_get_charset_str(compose->out_encoding);
4911 gchar *test_conv_global_out = NULL;
4912 gchar *test_conv_reply = NULL;
4914 /* automatic mode. be automatic. */
4915 codeconv_set_strict(TRUE);
4917 out_codeset = conv_get_outgoing_charset_str();
4919 debug_print("trying to convert to %s\n", out_codeset);
4920 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
4923 if (!test_conv_global_out && compose->orig_charset
4924 && strcmp(compose->orig_charset, CS_US_ASCII)) {
4925 out_codeset = compose->orig_charset;
4926 debug_print("failure; trying to convert to %s\n", out_codeset);
4927 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
4930 if (!test_conv_global_out && !test_conv_reply) {
4932 out_codeset = CS_INTERNAL;
4933 debug_print("failure; finally using %s\n", out_codeset);
4935 g_free(test_conv_global_out);
4936 g_free(test_conv_reply);
4937 codeconv_set_strict(FALSE);
4940 if (!g_ascii_strcasecmp(out_codeset, CS_US_ASCII))
4941 out_codeset = CS_ISO_8859_1;
4943 if (prefs_common.encoding_method == CTE_BASE64)
4944 encoding = ENC_BASE64;
4945 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
4946 encoding = ENC_QUOTED_PRINTABLE;
4947 else if (prefs_common.encoding_method == CTE_8BIT)
4948 encoding = ENC_8BIT;
4950 encoding = procmime_get_encoding_for_charset(out_codeset);
4952 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
4953 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
4955 if (action == COMPOSE_WRITE_FOR_SEND) {
4956 codeconv_set_strict(TRUE);
4957 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
4958 codeconv_set_strict(FALSE);
4964 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
4965 "to the specified %s charset.\n"
4966 "Send it as %s?"), out_codeset, src_codeset);
4967 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL, _("+_Send"), NULL, FALSE,
4968 NULL, ALERT_ERROR, G_ALERTDEFAULT);
4971 if (aval != G_ALERTALTERNATE) {
4976 out_codeset = src_codeset;
4982 out_codeset = src_codeset;
4988 if (encoding == ENC_8BIT || encoding == ENC_7BIT) {
4989 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
4990 strstr(buf, "\nFrom ") != NULL) {
4991 encoding = ENC_QUOTED_PRINTABLE;
4995 mimetext = procmime_mimeinfo_new();
4996 mimetext->content = MIMECONTENT_MEM;
4997 mimetext->tmp = TRUE; /* must free content later */
4998 /* dup'ed because procmime_encode_content can turn it into a tmpfile
4999 * and free the data, which we need later. */
5000 mimetext->data.mem = g_strdup(buf);
5001 mimetext->type = MIMETYPE_TEXT;
5002 mimetext->subtype = g_strdup("plain");
5003 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5004 g_strdup(out_codeset));
5006 /* protect trailing spaces when signing message */
5007 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5008 privacy_system_can_sign(compose->privacy_system)) {
5009 encoding = ENC_QUOTED_PRINTABLE;
5012 debug_print("main text: %d bytes encoded as %s in %d\n",
5013 strlen(buf), out_codeset, encoding);
5015 /* check for line length limit */
5016 if (action == COMPOSE_WRITE_FOR_SEND &&
5017 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5018 check_line_length(buf, 1000, &line) < 0) {
5022 msg = g_strdup_printf
5023 (_("Line %d exceeds the line length limit (998 bytes).\n"
5024 "The contents of the message might be broken on the way to the delivery.\n"
5026 "Send it anyway?"), line + 1);
5027 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5029 if (aval != G_ALERTALTERNATE) {
5035 if (encoding != ENC_UNKNOWN)
5036 procmime_encode_content(mimetext, encoding);
5038 /* append attachment parts */
5039 if (compose_use_attach(compose) && attach_parts) {
5040 MimeInfo *mimempart;
5041 gchar *boundary = NULL;
5042 mimempart = procmime_mimeinfo_new();
5043 mimempart->content = MIMECONTENT_EMPTY;
5044 mimempart->type = MIMETYPE_MULTIPART;
5045 mimempart->subtype = g_strdup("mixed");
5049 boundary = generate_mime_boundary(NULL);
5050 } while (strstr(buf, boundary) != NULL);
5052 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5055 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5057 g_node_append(mimempart->node, mimetext->node);
5058 g_node_append(mimemsg->node, mimempart->node);
5060 compose_add_attachments(compose, mimempart);
5062 g_node_append(mimemsg->node, mimetext->node);
5066 /* sign message if sending */
5067 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5068 privacy_system_can_sign(compose->privacy_system))
5069 if (!privacy_sign(compose->privacy_system, mimemsg, compose->account))
5072 procmime_write_mimeinfo(mimemsg, fp);
5074 procmime_mimeinfo_free_all(mimemsg);
5079 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5081 GtkTextBuffer *buffer;
5082 GtkTextIter start, end;
5087 if ((fp = g_fopen(file, "wb")) == NULL) {
5088 FILE_OP_ERROR(file, "fopen");
5092 /* chmod for security */
5093 if (change_file_mode_rw(fp, file) < 0) {
5094 FILE_OP_ERROR(file, "chmod");
5095 g_warning("can't change file mode\n");
5098 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5099 gtk_text_buffer_get_start_iter(buffer, &start);
5100 gtk_text_buffer_get_end_iter(buffer, &end);
5101 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5103 chars = conv_codeset_strdup
5104 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5107 if (!chars) return -1;
5110 len = strlen(chars);
5111 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5112 FILE_OP_ERROR(file, "fwrite");
5121 if (fclose(fp) == EOF) {
5122 FILE_OP_ERROR(file, "fclose");
5129 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5132 MsgInfo *msginfo = compose->targetinfo;
5134 g_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5135 if (!msginfo) return -1;
5137 if (!force && MSG_IS_LOCKED(msginfo->flags))
5140 item = msginfo->folder;
5141 g_return_val_if_fail(item != NULL, -1);
5143 if (procmsg_msg_exist(msginfo) &&
5144 (folder_has_parent_of_type(item, F_QUEUE) ||
5145 folder_has_parent_of_type(item, F_DRAFT)
5146 || msginfo == compose->autosaved_draft)) {
5147 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5148 g_warning("can't remove the old message\n");
5151 debug_print("removed reedit target %d\n", msginfo->msgnum);
5158 static void compose_remove_draft(Compose *compose)
5161 MsgInfo *msginfo = compose->targetinfo;
5162 drafts = account_get_special_folder(compose->account, F_DRAFT);
5164 if (procmsg_msg_exist(msginfo)) {
5165 folder_item_remove_msg(drafts, msginfo->msgnum);
5170 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5171 gboolean remove_reedit_target)
5173 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5176 static gboolean compose_warn_encryption(Compose *compose)
5178 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5179 AlertValue val = G_ALERTALTERNATE;
5181 if (warning == NULL)
5184 val = alertpanel_full(_("Encryption warning"), warning,
5185 GTK_STOCK_CANCEL, _("+C_ontinue"), NULL,
5186 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5187 if (val & G_ALERTDISABLE) {
5188 val &= ~G_ALERTDISABLE;
5189 if (val == G_ALERTALTERNATE)
5190 privacy_inhibit_encrypt_warning(compose->privacy_system,
5194 if (val == G_ALERTALTERNATE) {
5201 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
5202 gchar **msgpath, gboolean check_subject,
5203 gboolean remove_reedit_target)
5210 static gboolean lock = FALSE;
5211 PrefsAccount *mailac = NULL, *newsac = NULL;
5213 debug_print("queueing message...\n");
5214 g_return_val_if_fail(compose->account != NULL, -1);
5218 if (compose_check_entries(compose, check_subject) == FALSE) {
5220 if (compose->batch) {
5221 gtk_widget_show_all(compose->window);
5226 if (!compose->to_list && !compose->newsgroup_list) {
5227 g_warning("can't get recipient list.");
5232 if (compose->to_list) {
5233 if (compose->account->protocol != A_NNTP)
5234 mailac = compose->account;
5235 else if (cur_account && cur_account->protocol != A_NNTP)
5236 mailac = cur_account;
5237 else if (!(mailac = compose_current_mail_account())) {
5239 alertpanel_error(_("No account for sending mails available!"));
5244 if (compose->newsgroup_list) {
5245 if (compose->account->protocol == A_NNTP)
5246 newsac = compose->account;
5247 else if (!newsac->protocol != A_NNTP) {
5249 alertpanel_error(_("No account for posting news available!"));
5254 /* write queue header */
5255 tmp = g_strdup_printf("%s%cqueue.%p", get_tmp_dir(),
5256 G_DIR_SEPARATOR, compose);
5257 if ((fp = g_fopen(tmp, "wb")) == NULL) {
5258 FILE_OP_ERROR(tmp, "fopen");
5264 if (change_file_mode_rw(fp, tmp) < 0) {
5265 FILE_OP_ERROR(tmp, "chmod");
5266 g_warning("can't change file mode\n");
5269 /* queueing variables */
5270 fprintf(fp, "AF:\n");
5271 fprintf(fp, "NF:0\n");
5272 fprintf(fp, "PS:10\n");
5273 fprintf(fp, "SRH:1\n");
5274 fprintf(fp, "SFN:\n");
5275 fprintf(fp, "DSR:\n");
5277 fprintf(fp, "MID:<%s>\n", compose->msgid);
5279 fprintf(fp, "MID:\n");
5280 fprintf(fp, "CFG:\n");
5281 fprintf(fp, "PT:0\n");
5282 fprintf(fp, "S:%s\n", compose->account->address);
5283 fprintf(fp, "RQ:\n");
5285 fprintf(fp, "SSV:%s\n", mailac->smtp_server);
5287 fprintf(fp, "SSV:\n");
5289 fprintf(fp, "NSV:%s\n", newsac->nntp_server);
5291 fprintf(fp, "NSV:\n");
5292 fprintf(fp, "SSH:\n");
5293 /* write recepient list */
5294 if (compose->to_list) {
5295 fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data);
5296 for (cur = compose->to_list->next; cur != NULL;
5298 fprintf(fp, ",<%s>", (gchar *)cur->data);
5301 /* write newsgroup list */
5302 if (compose->newsgroup_list) {
5304 fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data);
5305 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
5306 fprintf(fp, ",%s", (gchar *)cur->data);
5309 /* Sylpheed account IDs */
5311 fprintf(fp, "MAID:%d\n", mailac->account_id);
5313 fprintf(fp, "NAID:%d\n", newsac->account_id);
5316 if (compose->privacy_system != NULL) {
5317 fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system);
5318 fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing);
5319 if (compose->use_encryption) {
5321 if (!compose_warn_encryption(compose)) {
5328 if (mailac && mailac->encrypt_to_self) {
5329 GSList *tmp_list = g_slist_copy(compose->to_list);
5330 tmp_list = g_slist_append(tmp_list, compose->account->address);
5331 encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
5332 g_slist_free(tmp_list);
5334 encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
5336 if (encdata != NULL) {
5337 if (strcmp(encdata, "_DONT_ENCRYPT_")) {
5338 fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption);
5339 fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
5341 } /* else we finally dont want to encrypt */
5343 fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption);
5344 /* and if encdata was null, it means there's been a problem in
5356 /* Save copy folder */
5357 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
5358 gchar *savefolderid;
5360 savefolderid = gtk_editable_get_chars(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
5361 fprintf(fp, "SCF:%s\n", savefolderid);
5362 g_free(savefolderid);
5364 /* Save copy folder */
5365 if (compose->return_receipt) {
5366 fprintf(fp, "RRCPT:1\n");
5368 /* Message-ID of message replying to */
5369 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
5372 folderid = folder_item_get_identifier(compose->replyinfo->folder);
5373 fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid);
5376 /* Message-ID of message forwarding to */
5377 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
5380 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
5381 fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid);
5385 /* end of headers */
5386 fprintf(fp, "X-Claws-End-Special-Headers: 1\n");
5388 if (compose->redirect_filename != NULL) {
5389 if (compose_redirect_write_to_file(compose, fp) < 0) {
5398 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
5403 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
5407 if (fclose(fp) == EOF) {
5408 FILE_OP_ERROR(tmp, "fclose");
5415 if (item && *item) {
5418 queue = account_get_special_folder(compose->account, F_QUEUE);
5421 g_warning("can't find queue folder\n");
5427 folder_item_scan(queue);
5428 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
5429 g_warning("can't queue the message\n");
5436 if (msgpath == NULL) {
5442 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
5443 compose_remove_reedit_target(compose, FALSE);
5446 if ((msgnum != NULL) && (item != NULL)) {
5454 static void compose_add_attachments(Compose *compose, MimeInfo *parent)
5457 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
5459 struct stat statbuf;
5460 gchar *type, *subtype;
5461 GtkTreeModel *model;
5464 model = gtk_tree_view_get_model(tree_view);
5466 if (!gtk_tree_model_get_iter_first(model, &iter))
5469 gtk_tree_model_get(model, &iter,
5473 mimepart = procmime_mimeinfo_new();
5474 mimepart->content = MIMECONTENT_FILE;
5475 mimepart->data.filename = g_strdup(ainfo->file);
5476 mimepart->tmp = FALSE; /* or we destroy our attachment */
5477 mimepart->offset = 0;
5479 stat(ainfo->file, &statbuf);
5480 mimepart->length = statbuf.st_size;
5482 type = g_strdup(ainfo->content_type);
5484 if (!strchr(type, '/')) {
5486 type = g_strdup("application/octet-stream");
5489 subtype = strchr(type, '/') + 1;
5490 *(subtype - 1) = '\0';
5491 mimepart->type = procmime_get_media_type(type);
5492 mimepart->subtype = g_strdup(subtype);
5495 if (mimepart->type == MIMETYPE_MESSAGE &&
5496 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
5497 mimepart->disposition = DISPOSITIONTYPE_INLINE;
5500 g_hash_table_insert(mimepart->typeparameters,
5501 g_strdup("name"), g_strdup(ainfo->name));
5502 g_hash_table_insert(mimepart->dispositionparameters,
5503 g_strdup("filename"), g_strdup(ainfo->name));
5504 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
5508 if (compose->use_signing) {
5509 if (ainfo->encoding == ENC_7BIT)
5510 ainfo->encoding = ENC_QUOTED_PRINTABLE;
5511 else if (ainfo->encoding == ENC_8BIT)
5512 ainfo->encoding = ENC_BASE64;
5515 procmime_encode_content(mimepart, ainfo->encoding);
5517 g_node_append(parent->node, mimepart->node);
5518 } while (gtk_tree_model_iter_next(model, &iter));
5521 #define IS_IN_CUSTOM_HEADER(header) \
5522 (compose->account->add_customhdr && \
5523 custom_header_find(compose->account->customhdr_list, header) != NULL)
5525 static void compose_add_headerfield_from_headerlist(Compose *compose,
5527 const gchar *fieldname,
5528 const gchar *seperator)
5530 gchar *str, *fieldname_w_colon;
5531 gboolean add_field = FALSE;
5533 ComposeHeaderEntry *headerentry;
5534 const gchar *headerentryname;
5535 const gchar *trans_fieldname;
5538 if (IS_IN_CUSTOM_HEADER(fieldname))
5541 debug_print("Adding %s-fields\n", fieldname);
5543 fieldstr = g_string_sized_new(64);
5545 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
5546 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
5548 for (list = compose->header_list; list; list = list->next) {
5549 headerentry = ((ComposeHeaderEntry *)list->data);
5550 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(headerentry->combo)->entry));
5552 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
5553 str = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
5555 if (str[0] != '\0') {
5557 g_string_append(fieldstr, seperator);
5558 g_string_append(fieldstr, str);
5567 buf = g_new0(gchar, fieldstr->len * 4 + 256);
5568 compose_convert_header
5569 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
5570 strlen(fieldname) + 2, TRUE);
5571 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
5575 g_free(fieldname_w_colon);
5576 g_string_free(fieldstr, TRUE);
5581 static gchar *compose_get_header(Compose *compose)
5583 gchar buf[BUFFSIZE];
5584 const gchar *entry_str;
5588 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
5590 gchar *from_name = NULL, *from_address = NULL;
5593 g_return_val_if_fail(compose->account != NULL, NULL);
5594 g_return_val_if_fail(compose->account->address != NULL, NULL);
5596 header = g_string_sized_new(64);
5599 get_rfc822_date(buf, sizeof(buf));
5600 g_string_append_printf(header, "Date: %s\n", buf);
5604 if (compose->account->name && *compose->account->name) {
5606 QUOTE_IF_REQUIRED(buf, compose->account->name);
5607 tmp = g_strdup_printf("%s <%s>",
5608 buf, compose->account->address);
5610 tmp = g_strdup_printf("%s",
5611 compose->account->address);
5613 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
5614 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
5616 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
5617 from_address = g_strdup(compose->account->address);
5619 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5620 /* extract name and address */
5621 if (strstr(spec, " <") && strstr(spec, ">")) {
5622 from_address = g_strdup(strrchr(spec, '<')+1);
5623 *(strrchr(from_address, '>')) = '\0';
5624 from_name = g_strdup(spec);
5625 *(strrchr(from_name, '<')) = '\0';
5628 from_address = g_strdup(spec);
5635 if (from_name && *from_name) {
5636 compose_convert_header
5637 (compose, buf, sizeof(buf), from_name,
5638 strlen("From: "), TRUE);
5639 QUOTE_IF_REQUIRED(name, buf);
5641 g_string_append_printf(header, "From: %s <%s>\n",
5642 name, from_address);
5644 g_string_append_printf(header, "From: %s\n", from_address);
5647 g_free(from_address);
5650 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
5653 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
5656 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
5659 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
5662 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
5664 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
5667 compose_convert_header(compose, buf, sizeof(buf), str,
5668 strlen("Subject: "), FALSE);
5669 g_string_append_printf(header, "Subject: %s\n", buf);
5675 if (compose->account->gen_msgid) {
5676 generate_msgid(buf, sizeof(buf));
5677 g_string_append_printf(header, "Message-ID: <%s>\n", buf);
5678 compose->msgid = g_strdup(buf);
5681 if (compose->remove_references == FALSE) {
5683 if (compose->inreplyto && compose->to_list)
5684 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
5687 if (compose->references)
5688 g_string_append_printf(header, "References: %s\n", compose->references);
5692 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
5695 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
5698 if (compose->account->organization &&
5699 strlen(compose->account->organization) &&
5700 !IS_IN_CUSTOM_HEADER("Organization")) {
5701 compose_convert_header(compose, buf, sizeof(buf),
5702 compose->account->organization,
5703 strlen("Organization: "), FALSE);
5704 g_string_append_printf(header, "Organization: %s\n", buf);
5707 /* Program version and system info */
5708 if (g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
5709 !compose->newsgroup_list) {
5710 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
5712 gtk_major_version, gtk_minor_version, gtk_micro_version,
5715 if (g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
5716 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
5718 gtk_major_version, gtk_minor_version, gtk_micro_version,
5722 /* custom headers */
5723 if (compose->account->add_customhdr) {
5726 for (cur = compose->account->customhdr_list; cur != NULL;
5728 CustomHeader *chdr = (CustomHeader *)cur->data;
5730 if (custom_header_is_allowed(chdr->name)) {
5731 compose_convert_header
5732 (compose, buf, sizeof(buf),
5733 chdr->value ? chdr->value : "",
5734 strlen(chdr->name) + 2, FALSE);
5735 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
5741 switch (compose->priority) {
5742 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
5743 "X-Priority: 1 (Highest)\n");
5745 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
5746 "X-Priority: 2 (High)\n");
5748 case PRIORITY_NORMAL: break;
5749 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
5750 "X-Priority: 4 (Low)\n");
5752 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
5753 "X-Priority: 5 (Lowest)\n");
5755 default: debug_print("compose: priority unknown : %d\n",
5759 /* Request Return Receipt */
5760 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To")) {
5761 if (compose->return_receipt) {
5762 if (compose->account->name
5763 && *compose->account->name) {
5764 compose_convert_header(compose, buf, sizeof(buf),
5765 compose->account->name,
5766 strlen("Disposition-Notification-To: "),
5768 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, compose->account->address);
5770 g_string_append_printf(header, "Disposition-Notification-To: %s\n", compose->account->address);
5774 /* get special headers */
5775 for (list = compose->header_list; list; list = list->next) {
5776 ComposeHeaderEntry *headerentry;
5779 gchar *headername_wcolon;
5780 const gchar *headername_trans;
5783 gboolean standard_header = FALSE;
5785 headerentry = ((ComposeHeaderEntry *)list->data);
5787 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(headerentry->combo)->entry)));
5788 if (strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
5793 if (!strstr(tmp, ":")) {
5794 headername_wcolon = g_strconcat(tmp, ":", NULL);
5795 headername = g_strdup(tmp);
5797 headername_wcolon = g_strdup(tmp);
5798 headername = g_strdup(strtok(tmp, ":"));
5802 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5803 Xstrdup_a(headervalue, entry_str, return NULL);
5804 subst_char(headervalue, '\r', ' ');
5805 subst_char(headervalue, '\n', ' ');
5806 string = std_headers;
5807 while (*string != NULL) {
5808 headername_trans = prefs_common_translated_header_name(*string);
5809 if (!strcmp(headername_trans, headername_wcolon))
5810 standard_header = TRUE;
5813 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
5814 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
5817 g_free(headername_wcolon);
5821 g_string_free(header, FALSE);
5826 #undef IS_IN_CUSTOM_HEADER
5828 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
5829 gint header_len, gboolean addr_field)
5831 gchar *tmpstr = NULL;
5832 const gchar *out_codeset = NULL;
5834 g_return_if_fail(src != NULL);
5835 g_return_if_fail(dest != NULL);
5837 if (len < 1) return;
5839 tmpstr = g_strdup(src);
5841 subst_char(tmpstr, '\n', ' ');
5842 subst_char(tmpstr, '\r', ' ');
5845 if (!g_utf8_validate(tmpstr, -1, NULL)) {
5846 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
5847 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
5852 codeconv_set_strict(TRUE);
5853 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
5854 conv_get_charset_str(compose->out_encoding));
5855 codeconv_set_strict(FALSE);
5857 if (!dest || *dest == '\0') {
5858 gchar *test_conv_global_out = NULL;
5859 gchar *test_conv_reply = NULL;
5861 /* automatic mode. be automatic. */
5862 codeconv_set_strict(TRUE);
5864 out_codeset = conv_get_outgoing_charset_str();
5866 debug_print("trying to convert to %s\n", out_codeset);
5867 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
5870 if (!test_conv_global_out && compose->orig_charset
5871 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5872 out_codeset = compose->orig_charset;
5873 debug_print("failure; trying to convert to %s\n", out_codeset);
5874 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
5877 if (!test_conv_global_out && !test_conv_reply) {
5879 out_codeset = CS_INTERNAL;
5880 debug_print("finally using %s\n", out_codeset);
5882 g_free(test_conv_global_out);
5883 g_free(test_conv_reply);
5884 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
5886 codeconv_set_strict(FALSE);
5891 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
5895 g_return_if_fail(user_data != NULL);
5897 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
5898 g_strstrip(address);
5899 if (*address != '\0') {
5900 gchar *name = procheader_get_fromname(address);
5901 extract_address(address);
5902 addressbook_add_contact(name, address, NULL);
5907 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
5909 GtkWidget *menuitem;
5912 g_return_if_fail(menu != NULL);
5913 g_return_if_fail(GTK_IS_MENU_SHELL(menu));
5915 menuitem = gtk_separator_menu_item_new();
5916 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
5917 gtk_widget_show(menuitem);
5919 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
5920 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
5922 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
5923 g_strstrip(address);
5924 if (*address == '\0') {
5925 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
5928 g_signal_connect(G_OBJECT(menuitem), "activate",
5929 G_CALLBACK(compose_add_to_addressbook_cb), entry);
5930 gtk_widget_show(menuitem);
5933 static void compose_create_header_entry(Compose *compose)
5935 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
5939 GList *combo_list = NULL;
5941 const gchar *header = NULL;
5942 ComposeHeaderEntry *headerentry;
5943 gboolean standard_header = FALSE;
5945 headerentry = g_new0(ComposeHeaderEntry, 1);
5948 combo = gtk_combo_new();
5950 while(*string != NULL) {
5951 combo_list = g_list_append(combo_list, (gchar*)prefs_common_translated_header_name(*string));
5954 gtk_combo_set_popdown_strings(GTK_COMBO(combo), combo_list);
5955 g_list_free(combo_list);
5956 gtk_editable_set_editable(GTK_EDITABLE(GTK_COMBO(combo)->entry), TRUE);
5957 g_signal_connect(G_OBJECT(GTK_COMBO(combo)->entry), "grab_focus",
5958 G_CALLBACK(compose_grab_focus_cb), compose);
5959 gtk_widget_show(combo);
5960 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
5961 compose->header_nextrow, compose->header_nextrow+1,
5962 GTK_SHRINK, GTK_FILL, 0, 0);
5963 if (compose->header_last) {
5964 const gchar *last_header_entry = gtk_entry_get_text(
5965 GTK_ENTRY(GTK_COMBO(compose->header_last->combo)->entry));
5967 while (*string != NULL) {
5968 if (!strcmp(*string, last_header_entry))
5969 standard_header = TRUE;
5972 if (standard_header)
5973 header = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(compose->header_last->combo)->entry));
5975 if (!compose->header_last || !standard_header) {
5976 switch(compose->account->protocol) {
5978 header = prefs_common_translated_header_name("Newsgroups:");
5981 header = prefs_common_translated_header_name("To:");
5986 gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(combo)->entry), header);
5988 g_signal_connect_after(G_OBJECT(GTK_COMBO(combo)->entry), "grab_focus",
5989 G_CALLBACK(compose_grab_focus_cb), compose);
5992 entry = gtk_entry_new();
5993 gtk_widget_show(entry);
5994 gtk_tooltips_set_tip(compose->tooltips, entry,
5995 _("Use <tab> to autocomplete from addressbook"), NULL);
5996 gtk_table_attach(GTK_TABLE(compose->header_table), entry, 1, 2,
5997 compose->header_nextrow, compose->header_nextrow+1,
5998 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
6000 g_signal_connect(G_OBJECT(entry), "key-press-event",
6001 G_CALLBACK(compose_headerentry_key_press_event_cb),
6003 g_signal_connect(G_OBJECT(entry), "changed",
6004 G_CALLBACK(compose_headerentry_changed_cb),
6006 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
6007 G_CALLBACK(compose_grab_focus_cb), compose);
6010 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6011 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6012 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6013 g_signal_connect(G_OBJECT(entry), "drag_data_received",
6014 G_CALLBACK(compose_header_drag_received_cb),
6016 g_signal_connect(G_OBJECT(entry), "drag-drop",
6017 G_CALLBACK(compose_drag_drop),
6019 g_signal_connect(G_OBJECT(entry), "populate-popup",
6020 G_CALLBACK(compose_entry_popup_extend),
6023 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
6025 headerentry->compose = compose;
6026 headerentry->combo = combo;
6027 headerentry->entry = entry;
6028 headerentry->headernum = compose->header_nextrow;
6030 compose->header_nextrow++;
6031 compose->header_last = headerentry;
6032 compose->header_list =
6033 g_slist_append(compose->header_list,
6037 static void compose_add_header_entry(Compose *compose, const gchar *header, gchar *text)
6039 ComposeHeaderEntry *last_header;
6041 last_header = compose->header_last;
6043 gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(last_header->combo)->entry), header);
6044 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
6047 static void compose_remove_header_entries(Compose *compose)
6050 for (list = compose->header_list; list; list = list->next) {
6051 ComposeHeaderEntry *headerentry =
6052 (ComposeHeaderEntry *)list->data;
6053 gtk_widget_destroy(headerentry->combo);
6054 gtk_widget_destroy(headerentry->entry);
6055 g_free(headerentry);
6057 compose->header_last = NULL;
6058 g_slist_free(compose->header_list);
6059 compose->header_list = NULL;
6060 compose->header_nextrow = 1;
6061 compose_create_header_entry(compose);
6064 static GtkWidget *compose_create_header(Compose *compose)
6066 GtkWidget *from_optmenu_hbox;
6067 GtkWidget *header_scrolledwin;
6068 GtkWidget *header_table;
6072 /* header labels and entries */
6073 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
6074 gtk_widget_show(header_scrolledwin);
6075 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
6077 header_table = gtk_table_new(2, 2, FALSE);
6078 gtk_widget_show(header_table);
6079 gtk_container_set_border_width(GTK_CONTAINER(header_table), BORDER_WIDTH);
6080 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
6081 gtk_viewport_set_shadow_type(GTK_VIEWPORT(GTK_BIN(header_scrolledwin)->child), GTK_SHADOW_ETCHED_IN);
6084 /* option menu for selecting accounts */
6085 from_optmenu_hbox = compose_account_option_menu_create(compose);
6086 gtk_table_attach(GTK_TABLE(header_table), from_optmenu_hbox,
6087 0, 2, count, count + 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
6090 compose->header_table = header_table;
6091 compose->header_list = NULL;
6092 compose->header_nextrow = count;
6094 compose_create_header_entry(compose);
6096 compose->table = NULL;
6098 return header_scrolledwin ;
6101 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
6103 Compose *compose = (Compose *)data;
6104 GdkEventButton event;
6107 event.time = gtk_get_current_event_time();
6109 return attach_button_pressed(compose->attach_clist, &event, compose);
6112 static GtkWidget *compose_create_attach(Compose *compose)
6114 GtkWidget *attach_scrwin;
6115 GtkWidget *attach_clist;
6117 GtkListStore *store;
6118 GtkCellRenderer *renderer;
6119 GtkTreeViewColumn *column;
6120 GtkTreeSelection *selection;
6122 /* attachment list */
6123 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
6124 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
6125 GTK_POLICY_AUTOMATIC,
6126 GTK_POLICY_AUTOMATIC);
6127 gtk_widget_set_size_request(attach_scrwin, -1, 80);
6129 store = gtk_list_store_new(N_ATTACH_COLS,
6134 G_TYPE_AUTO_POINTER,
6136 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
6137 (GTK_TREE_MODEL(store)));
6138 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
6139 g_object_unref(store);
6141 renderer = gtk_cell_renderer_text_new();
6142 column = gtk_tree_view_column_new_with_attributes
6143 (_("Mime type"), renderer, "text",
6144 COL_MIMETYPE, NULL);
6145 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6147 renderer = gtk_cell_renderer_text_new();
6148 column = gtk_tree_view_column_new_with_attributes
6149 (_("Size"), renderer, "text",
6151 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6153 renderer = gtk_cell_renderer_text_new();
6154 column = gtk_tree_view_column_new_with_attributes
6155 (_("Name"), renderer, "text",
6157 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6159 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
6160 prefs_common.use_stripes_everywhere);
6161 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
6162 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
6164 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
6165 G_CALLBACK(attach_selected), compose);
6166 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
6167 G_CALLBACK(attach_button_pressed), compose);
6169 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
6170 G_CALLBACK(popup_attach_button_pressed), compose);
6172 gtk_widget_tap_and_hold_setup(GTK_WIDGET(attach_clist), NULL, NULL,
6173 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
6174 g_signal_connect(G_OBJECT(attach_clist), "tap-and-hold",
6175 G_CALLBACK(popup_attach_button_pressed), compose);
6177 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
6178 G_CALLBACK(attach_key_pressed), compose);
6181 gtk_drag_dest_set(attach_clist,
6182 GTK_DEST_DEFAULT_ALL, compose_mime_types,
6183 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6184 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6185 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
6186 G_CALLBACK(compose_attach_drag_received_cb),
6188 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
6189 G_CALLBACK(compose_drag_drop),
6192 compose->attach_scrwin = attach_scrwin;
6193 compose->attach_clist = attach_clist;
6195 return attach_scrwin;
6198 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
6199 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
6201 static GtkWidget *compose_create_others(Compose *compose)
6204 GtkWidget *savemsg_checkbtn;
6205 GtkWidget *savemsg_entry;
6206 GtkWidget *savemsg_select;
6209 gchar *folderidentifier;
6211 /* Table for settings */
6212 table = gtk_table_new(3, 1, FALSE);
6213 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
6214 gtk_widget_show(table);
6215 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
6218 /* Save Message to folder */
6219 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
6220 gtk_widget_show(savemsg_checkbtn);
6221 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6222 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6223 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
6225 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
6226 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
6228 savemsg_entry = gtk_entry_new();
6229 gtk_widget_show(savemsg_entry);
6230 gtk_table_attach_defaults(GTK_TABLE(table), savemsg_entry, 1, 2, rowcount, rowcount + 1);
6231 gtk_editable_set_editable(GTK_EDITABLE(savemsg_entry), prefs_common.savemsg);
6232 g_signal_connect_after(G_OBJECT(savemsg_entry), "grab_focus",
6233 G_CALLBACK(compose_grab_focus_cb), compose);
6234 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6235 folderidentifier = folder_item_get_identifier(account_get_special_folder
6236 (compose->account, F_OUTBOX));
6237 gtk_entry_set_text(GTK_ENTRY(savemsg_entry), folderidentifier);
6238 g_free(folderidentifier);
6241 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
6242 gtk_widget_show(savemsg_select);
6243 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6244 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
6245 G_CALLBACK(compose_savemsg_select_cb),
6250 compose->savemsg_checkbtn = savemsg_checkbtn;
6251 compose->savemsg_entry = savemsg_entry;
6256 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
6258 gtk_editable_set_editable(GTK_EDITABLE(compose->savemsg_entry),
6259 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
6262 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
6267 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL);
6270 path = folder_item_get_identifier(dest);
6272 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), path);
6276 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
6277 GdkAtom clip, GtkTextIter *insert_place);
6280 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
6284 GtkTextBuffer *buffer;
6286 if (event->button == 3) {
6288 GtkTextIter sel_start, sel_end;
6289 gboolean stuff_selected;
6291 /* move the cursor to allow GtkAspell to check the word
6292 * under the mouse */
6293 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6294 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6296 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6299 stuff_selected = gtk_text_buffer_get_selection_bounds(
6300 GTK_TEXT_VIEW(text)->buffer,
6301 &sel_start, &sel_end);
6303 gtk_text_buffer_place_cursor (GTK_TEXT_VIEW(text)->buffer, &iter);
6304 /* reselect stuff */
6306 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
6307 gtk_text_buffer_select_range(GTK_TEXT_VIEW(text)->buffer,
6308 &sel_start, &sel_end);
6310 return FALSE; /* pass the event so that the right-click goes through */
6313 if (event->button == 2) {
6318 /* get the middle-click position to paste at the correct place */
6319 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6320 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6322 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6325 entry_paste_clipboard(compose, text,
6326 prefs_common.linewrap_pastes,
6327 GDK_SELECTION_PRIMARY, &iter);
6335 static void compose_spell_menu_changed(void *data)
6337 Compose *compose = (Compose *)data;
6339 GtkWidget *menuitem;
6340 GtkWidget *parent_item;
6341 GtkMenu *menu = GTK_MENU(gtk_menu_new());
6342 GtkItemFactory *ifactory = gtk_item_factory_from_widget(compose->menubar);
6345 if (compose->gtkaspell == NULL)
6348 parent_item = gtk_item_factory_get_item(ifactory,
6349 "/Spelling/Options");
6351 /* setting the submenu removes /Spelling/Options from the factory
6352 * so we need to save it */
6354 if (parent_item == NULL) {
6355 parent_item = compose->aspell_options_menu;
6356 gtk_menu_item_remove_submenu(GTK_MENU_ITEM(parent_item));
6358 compose->aspell_options_menu = parent_item;
6360 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
6362 spell_menu = g_slist_reverse(spell_menu);
6363 for (items = spell_menu;
6364 items; items = items->next) {
6365 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
6366 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
6367 gtk_widget_show(GTK_WIDGET(menuitem));
6369 g_slist_free(spell_menu);
6371 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
6376 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
6378 Compose *compose = (Compose *)data;
6379 GdkEventButton event;
6382 event.time = gtk_get_current_event_time();
6384 return text_clicked(compose->text, &event, compose);
6387 static gboolean compose_force_window_origin = TRUE;
6388 static Compose *compose_create(PrefsAccount *account,
6397 GtkWidget *handlebox;
6399 GtkWidget *notebook;
6404 GtkWidget *subject_hbox;
6405 GtkWidget *subject_frame;
6406 GtkWidget *subject_entry;
6410 GtkWidget *edit_vbox;
6411 GtkWidget *ruler_hbox;
6413 GtkWidget *scrolledwin;
6415 GtkTextBuffer *buffer;
6416 GtkClipboard *clipboard;
6418 UndoMain *undostruct;
6420 gchar *titles[N_ATTACH_COLS];
6421 guint n_menu_entries;
6422 GtkWidget *popupmenu;
6423 GtkItemFactory *popupfactory;
6424 GtkItemFactory *ifactory;
6425 GtkWidget *tmpl_menu;
6427 GtkWidget *menuitem;
6430 GtkAspell * gtkaspell = NULL;
6433 static GdkGeometry geometry;
6435 g_return_val_if_fail(account != NULL, NULL);
6437 debug_print("Creating compose window...\n");
6438 compose = g_new0(Compose, 1);
6440 titles[COL_MIMETYPE] = _("MIME type");
6441 titles[COL_SIZE] = _("Size");
6442 titles[COL_NAME] = _("Name");
6444 compose->batch = batch;
6445 compose->account = account;
6446 compose->folder = folder;
6448 compose->mutex = g_mutex_new();
6449 compose->set_cursor_pos = -1;
6451 compose->tooltips = gtk_tooltips_new();
6453 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
6455 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
6456 gtk_widget_set_size_request(window, -1, prefs_common.compose_height);
6458 if (!geometry.max_width) {
6459 geometry.max_width = gdk_screen_width();
6460 geometry.max_height = gdk_screen_height();
6463 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
6464 &geometry, GDK_HINT_MAX_SIZE);
6465 if (!geometry.min_width) {
6466 geometry.min_width = 600;
6467 geometry.min_height = 480;
6469 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
6470 &geometry, GDK_HINT_MIN_SIZE);
6473 if (compose_force_window_origin)
6474 gtk_widget_set_uposition(window, prefs_common.compose_x,
6475 prefs_common.compose_y);
6477 g_signal_connect(G_OBJECT(window), "delete_event",
6478 G_CALLBACK(compose_delete_cb), compose);
6479 MANAGE_WINDOW_SIGNALS_CONNECT(window);
6480 gtk_widget_realize(window);
6482 gtkut_widget_set_composer_icon(window);
6484 vbox = gtk_vbox_new(FALSE, 0);
6485 gtk_container_add(GTK_CONTAINER(window), vbox);
6487 n_menu_entries = sizeof(compose_entries) / sizeof(compose_entries[0]);
6488 menubar = menubar_create(window, compose_entries,
6489 n_menu_entries, "<Compose>", compose);
6490 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
6492 if (prefs_common.toolbar_detachable) {
6493 handlebox = gtk_handle_box_new();
6495 handlebox = gtk_hbox_new(FALSE, 0);
6497 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
6499 gtk_widget_realize(handlebox);
6501 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, window,
6504 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
6508 vbox2 = gtk_vbox_new(FALSE, 2);
6509 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
6510 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
6513 notebook = gtk_notebook_new();
6514 gtk_widget_set_size_request(notebook, -1, 130);
6515 gtk_widget_show(notebook);
6517 /* header labels and entries */
6518 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6519 compose_create_header(compose),
6520 gtk_label_new_with_mnemonic(_("Hea_der")));
6521 /* attachment list */
6522 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6523 compose_create_attach(compose),
6524 gtk_label_new_with_mnemonic(_("_Attachments")));
6526 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6527 compose_create_others(compose),
6528 gtk_label_new_with_mnemonic(_("Othe_rs")));
6531 subject_hbox = gtk_hbox_new(FALSE, 0);
6532 gtk_widget_show(subject_hbox);
6534 subject_frame = gtk_frame_new(NULL);
6535 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
6536 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
6537 gtk_widget_show(subject_frame);
6539 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
6540 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
6541 gtk_widget_show(subject);
6543 label = gtk_label_new(_("Subject:"));
6544 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
6545 gtk_widget_show(label);
6547 subject_entry = gtk_entry_new();
6548 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
6549 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
6550 G_CALLBACK(compose_grab_focus_cb), compose);
6551 gtk_widget_show(subject_entry);
6552 compose->subject_entry = subject_entry;
6553 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
6555 edit_vbox = gtk_vbox_new(FALSE, 0);
6557 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
6560 ruler_hbox = gtk_hbox_new(FALSE, 0);
6561 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
6563 ruler = gtk_shruler_new();
6564 gtk_ruler_set_range(GTK_RULER(ruler), 0.0, 100.0, 1.0, 100.0);
6565 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
6569 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
6570 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
6571 GTK_POLICY_AUTOMATIC,
6572 GTK_POLICY_AUTOMATIC);
6573 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
6575 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
6576 gtk_widget_set_size_request(scrolledwin, prefs_common.compose_width, -1);
6578 text = gtk_text_view_new();
6579 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
6580 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
6581 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
6582 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
6583 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
6585 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
6587 g_signal_connect_after(G_OBJECT(text), "size_allocate",
6588 G_CALLBACK(compose_edit_size_alloc),
6590 g_signal_connect(G_OBJECT(buffer), "changed",
6591 G_CALLBACK(compose_changed_cb), compose);
6592 g_signal_connect(G_OBJECT(text), "grab_focus",
6593 G_CALLBACK(compose_grab_focus_cb), compose);
6594 g_signal_connect(G_OBJECT(buffer), "insert_text",
6595 G_CALLBACK(text_inserted), compose);
6596 g_signal_connect(G_OBJECT(text), "button_press_event",
6597 G_CALLBACK(text_clicked), compose);
6599 g_signal_connect(G_OBJECT(text), "popup-menu",
6600 G_CALLBACK(compose_popup_menu), compose);
6602 gtk_widget_tap_and_hold_setup(GTK_WIDGET(text), NULL, NULL,
6603 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
6604 g_signal_connect(G_OBJECT(text), "tap-and-hold",
6605 G_CALLBACK(compose_popup_menu), compose);
6607 g_signal_connect(G_OBJECT(subject_entry), "changed",
6608 G_CALLBACK(compose_changed_cb), compose);
6611 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6612 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6613 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6614 g_signal_connect(G_OBJECT(text), "drag_data_received",
6615 G_CALLBACK(compose_insert_drag_received_cb),
6617 g_signal_connect(G_OBJECT(text), "drag-drop",
6618 G_CALLBACK(compose_drag_drop),
6620 gtk_widget_show_all(vbox);
6622 /* pane between attach clist and text */
6623 paned = gtk_vpaned_new();
6624 gtk_paned_set_gutter_size(GTK_PANED(paned), 12);
6625 gtk_container_add(GTK_CONTAINER(vbox2), paned);
6627 if( maemo_mainwindow_is_fullscreen(mainwindow_get_mainwindow()->window) )
6628 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 300 : 280);
6630 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 250 : 230);
6632 gtk_paned_add1(GTK_PANED(paned), notebook);
6633 gtk_paned_add2(GTK_PANED(paned), edit_vbox);
6634 gtk_widget_show_all(paned);
6637 if (prefs_common.textfont) {
6638 PangoFontDescription *font_desc;
6640 font_desc = pango_font_description_from_string
6641 (prefs_common.textfont);
6643 gtk_widget_modify_font(text, font_desc);
6644 pango_font_description_free(font_desc);
6648 n_entries = sizeof(compose_popup_entries) /
6649 sizeof(compose_popup_entries[0]);
6650 popupmenu = menu_create_items(compose_popup_entries, n_entries,
6651 "<Compose>", &popupfactory,
6654 ifactory = gtk_item_factory_from_widget(menubar);
6655 menu_set_sensitive(ifactory, "/Edit/Undo", FALSE);
6656 menu_set_sensitive(ifactory, "/Edit/Redo", FALSE);
6657 menu_set_sensitive(ifactory, "/Options/Remove references", FALSE);
6659 tmpl_menu = gtk_item_factory_get_item(ifactory, "/Tools/Template");
6661 undostruct = undo_init(text);
6662 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
6665 address_completion_start(window);
6667 compose->window = window;
6668 compose->vbox = vbox;
6669 compose->menubar = menubar;
6670 compose->handlebox = handlebox;
6672 compose->vbox2 = vbox2;
6674 compose->paned = paned;
6676 compose->notebook = notebook;
6677 compose->edit_vbox = edit_vbox;
6678 compose->ruler_hbox = ruler_hbox;
6679 compose->ruler = ruler;
6680 compose->scrolledwin = scrolledwin;
6681 compose->text = text;
6683 compose->focused_editable = NULL;
6685 compose->popupmenu = popupmenu;
6686 compose->popupfactory = popupfactory;
6688 compose->tmpl_menu = tmpl_menu;
6690 compose->mode = mode;
6691 compose->rmode = mode;
6693 compose->targetinfo = NULL;
6694 compose->replyinfo = NULL;
6695 compose->fwdinfo = NULL;
6697 compose->replyto = NULL;
6699 compose->bcc = NULL;
6700 compose->followup_to = NULL;
6702 compose->ml_post = NULL;
6704 compose->inreplyto = NULL;
6705 compose->references = NULL;
6706 compose->msgid = NULL;
6707 compose->boundary = NULL;
6709 compose->autowrap = prefs_common.autowrap;
6711 compose->use_signing = FALSE;
6712 compose->use_encryption = FALSE;
6713 compose->privacy_system = NULL;
6715 compose->modified = FALSE;
6717 compose->return_receipt = FALSE;
6719 compose->to_list = NULL;
6720 compose->newsgroup_list = NULL;
6722 compose->undostruct = undostruct;
6724 compose->sig_str = NULL;
6726 compose->exteditor_file = NULL;
6727 compose->exteditor_pid = -1;
6728 compose->exteditor_tag = -1;
6729 compose->draft_timeout_tag = -2; /* inhibit auto-drafting while loading */
6732 menu_set_sensitive(ifactory, "/Spelling", FALSE);
6733 if (mode != COMPOSE_REDIRECT) {
6734 if (prefs_common.enable_aspell && prefs_common.dictionary &&
6735 strcmp(prefs_common.dictionary, "")) {
6736 gtkaspell = gtkaspell_new(prefs_common.aspell_path,
6737 prefs_common.dictionary,
6738 prefs_common.alt_dictionary,
6739 conv_get_locale_charset_str(),
6740 prefs_common.misspelled_col,
6741 prefs_common.check_while_typing,
6742 prefs_common.recheck_when_changing_dict,
6743 prefs_common.use_alternate,
6744 prefs_common.use_both_dicts,
6745 GTK_TEXT_VIEW(text),
6746 GTK_WINDOW(compose->window),
6747 compose_spell_menu_changed,
6750 alertpanel_error(_("Spell checker could not "
6752 gtkaspell_checkers_strerror());
6753 gtkaspell_checkers_reset_error();
6755 if (!gtkaspell_set_sug_mode(gtkaspell,
6756 prefs_common.aspell_sugmode)) {
6757 debug_print("Aspell: could not set "
6758 "suggestion mode %s\n",
6759 gtkaspell_checkers_strerror());
6760 gtkaspell_checkers_reset_error();
6763 menu_set_sensitive(ifactory, "/Spelling", TRUE);
6767 compose->gtkaspell = gtkaspell;
6768 compose_spell_menu_changed(compose);
6771 compose_select_account(compose, account, TRUE);
6773 menu_set_active(ifactory, "/Edit/Auto wrapping", prefs_common.autowrap);
6774 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
6775 compose_entry_append(compose, account->auto_cc, COMPOSE_CC);
6777 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
6778 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC);
6780 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
6781 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO);
6783 menu_set_sensitive(ifactory, "/Options/Reply mode", compose->mode == COMPOSE_REPLY);
6785 if (account->protocol != A_NNTP)
6786 gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(compose->header_last->combo)->entry),
6787 prefs_common_translated_header_name("To:"));
6789 gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(compose->header_last->combo)->entry),
6790 prefs_common_translated_header_name("Newsgroups:"));
6792 addressbook_set_target_compose(compose);
6794 if (mode != COMPOSE_REDIRECT)
6795 compose_set_template_menu(compose);
6797 menuitem = gtk_item_factory_get_item(ifactory, "/Tools/Template");
6798 menu_set_sensitive(ifactory, "/Tools/Template", FALSE);
6801 compose_list = g_list_append(compose_list, compose);
6803 if (!prefs_common.show_ruler)
6804 gtk_widget_hide(ruler_hbox);
6806 menuitem = gtk_item_factory_get_item(ifactory, "/Tools/Show ruler");
6807 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
6808 prefs_common.show_ruler);
6811 compose->priority = PRIORITY_NORMAL;
6812 compose_update_priority_menu_item(compose);
6814 compose_set_out_encoding(compose);
6817 compose_update_actions_menu(compose);
6819 /* Privacy Systems menu */
6820 compose_update_privacy_systems_menu(compose);
6822 activate_privacy_system(compose, account, TRUE);
6823 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
6825 gtk_widget_realize(window);
6827 gtk_widget_show(window);
6829 maemo_window_full_screen_if_needed(GTK_WINDOW(window));
6830 maemo_connect_key_press_to_mainwindow(GTK_WINDOW(window));
6837 static GtkWidget *compose_account_option_menu_create(Compose *compose)
6842 GtkWidget *optmenubox;
6845 GtkWidget *from_name = NULL;
6847 gint num = 0, def_menu = 0;
6849 accounts = account_get_list();
6850 g_return_val_if_fail(accounts != NULL, NULL);
6852 optmenubox = gtk_event_box_new();
6853 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
6854 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
6856 hbox = gtk_hbox_new(FALSE, 6);
6857 from_name = gtk_entry_new();
6859 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
6860 G_CALLBACK(compose_grab_focus_cb), compose);
6862 for (; accounts != NULL; accounts = accounts->next, num++) {
6863 PrefsAccount *ac = (PrefsAccount *)accounts->data;
6864 gchar *name, *from = NULL;
6866 if (ac == compose->account) def_menu = num;
6868 name = g_markup_printf_escaped(_("From: <i>%s</i>"),
6871 if (ac == compose->account) {
6872 if (ac->name && *ac->name) {
6874 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
6875 from = g_strdup_printf("%s <%s>",
6877 gtk_entry_set_text(GTK_ENTRY(from_name), from);
6879 from = g_strdup_printf("%s",
6881 gtk_entry_set_text(GTK_ENTRY(from_name), from);
6884 COMBOBOX_ADD(menu, name, ac->account_id);
6889 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
6891 g_signal_connect(G_OBJECT(optmenu), "changed",
6892 G_CALLBACK(account_activated),
6894 g_signal_connect(G_OBJECT(from_name), "populate-popup",
6895 G_CALLBACK(compose_entry_popup_extend),
6898 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
6899 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
6901 gtk_tooltips_set_tip(compose->tooltips, optmenubox,
6902 _("Account to use for this email"), NULL);
6903 gtk_tooltips_set_tip(compose->tooltips, from_name,
6904 _("Sender address to be used"), NULL);
6906 compose->from_name = from_name;
6911 static void compose_set_priority_cb(gpointer data,
6915 Compose *compose = (Compose *) data;
6916 compose->priority = action;
6919 static void compose_reply_change_mode(gpointer data,
6923 Compose *compose = (Compose *) data;
6924 gboolean was_modified = compose->modified;
6926 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
6928 g_return_if_fail(compose->replyinfo != NULL);
6930 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
6932 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
6934 if (action == COMPOSE_REPLY_TO_ALL)
6936 if (action == COMPOSE_REPLY_TO_SENDER)
6938 if (action == COMPOSE_REPLY_TO_LIST)
6941 compose_remove_header_entries(compose);
6942 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
6943 if (compose->account->set_autocc && compose->account->auto_cc)
6944 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC);
6946 if (compose->account->set_autobcc && compose->account->auto_bcc)
6947 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC);
6949 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
6950 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO);
6951 compose_show_first_last_header(compose, TRUE);
6952 compose->modified = was_modified;
6953 compose_set_title(compose);
6956 static void compose_update_priority_menu_item(Compose * compose)
6958 GtkItemFactory *ifactory;
6959 GtkWidget *menuitem = NULL;
6961 ifactory = gtk_item_factory_from_widget(compose->menubar);
6963 switch (compose->priority) {
6964 case PRIORITY_HIGHEST:
6965 menuitem = gtk_item_factory_get_item
6966 (ifactory, "/Options/Priority/Highest");
6969 menuitem = gtk_item_factory_get_item
6970 (ifactory, "/Options/Priority/High");
6972 case PRIORITY_NORMAL:
6973 menuitem = gtk_item_factory_get_item
6974 (ifactory, "/Options/Priority/Normal");
6977 menuitem = gtk_item_factory_get_item
6978 (ifactory, "/Options/Priority/Low");
6980 case PRIORITY_LOWEST:
6981 menuitem = gtk_item_factory_get_item
6982 (ifactory, "/Options/Priority/Lowest");
6985 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
6988 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
6990 Compose *compose = (Compose *) data;
6992 GtkItemFactory *ifactory;
6993 gboolean can_sign = FALSE, can_encrypt = FALSE;
6995 g_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
6997 if (!GTK_CHECK_MENU_ITEM(widget)->active)
7000 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
7001 g_free(compose->privacy_system);
7002 compose->privacy_system = NULL;
7003 if (systemid != NULL) {
7004 compose->privacy_system = g_strdup(systemid);
7006 can_sign = privacy_system_can_sign(systemid);
7007 can_encrypt = privacy_system_can_encrypt(systemid);
7010 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
7012 ifactory = gtk_item_factory_from_widget(compose->menubar);
7013 menu_set_sensitive(ifactory, "/Options/Sign", can_sign);
7014 menu_set_sensitive(ifactory, "/Options/Encrypt", can_encrypt);
7017 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
7019 static gchar *branch_path = "/Options/Privacy System";
7020 GtkItemFactory *ifactory;
7021 GtkWidget *menuitem = NULL;
7023 gboolean can_sign = FALSE, can_encrypt = FALSE;
7024 gboolean found = FALSE;
7026 ifactory = gtk_item_factory_from_widget(compose->menubar);
7028 if (compose->privacy_system != NULL) {
7031 menuitem = gtk_item_factory_get_widget(ifactory, branch_path);
7032 g_return_if_fail(menuitem != NULL);
7034 amenu = GTK_MENU_SHELL(menuitem)->children;
7036 while (amenu != NULL) {
7037 GList *alist = amenu->next;
7039 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
7040 if (systemid != NULL) {
7041 if (strcmp(systemid, compose->privacy_system) == 0) {
7042 menuitem = GTK_WIDGET(amenu->data);
7044 can_sign = privacy_system_can_sign(systemid);
7045 can_encrypt = privacy_system_can_encrypt(systemid);
7049 } else if (strlen(compose->privacy_system) == 0) {
7050 menuitem = GTK_WIDGET(amenu->data);
7053 can_encrypt = FALSE;
7060 if (menuitem != NULL)
7061 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
7063 if (warn && !found && strlen(compose->privacy_system)) {
7064 gchar *tmp = g_strdup_printf(
7065 _("The privacy system '%s' cannot be loaded. You "
7066 "will not be able to sign or encrypt this message."),
7067 compose->privacy_system);
7068 alertpanel_warning(tmp);
7073 menu_set_sensitive(ifactory, "/Options/Sign", can_sign);
7074 menu_set_sensitive(ifactory, "/Options/Encrypt", can_encrypt);
7077 static void compose_set_out_encoding(Compose *compose)
7079 GtkItemFactoryEntry *entry;
7080 GtkItemFactory *ifactory;
7081 CharSet out_encoding;
7082 gchar *path, *p, *q;
7085 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
7086 ifactory = gtk_item_factory_from_widget(compose->menubar);
7088 for (entry = compose_entries; entry->callback != compose_address_cb;
7090 if (entry->callback == compose_set_encoding_cb &&
7091 (CharSet)entry->callback_action == out_encoding) {
7092 p = q = path = g_strdup(entry->path);
7104 item = gtk_item_factory_get_item(ifactory, path);
7105 gtk_widget_activate(item);
7112 static void compose_set_template_menu(Compose *compose)
7114 GSList *tmpl_list, *cur;
7118 tmpl_list = template_get_config();
7120 menu = gtk_menu_new();
7122 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
7123 Template *tmpl = (Template *)cur->data;
7125 item = gtk_menu_item_new_with_label(tmpl->name);
7126 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
7127 g_signal_connect(G_OBJECT(item), "activate",
7128 G_CALLBACK(compose_template_activate_cb),
7130 g_object_set_data(G_OBJECT(item), "template", tmpl);
7131 gtk_widget_show(item);
7134 gtk_widget_show(menu);
7135 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
7138 void compose_update_actions_menu(Compose *compose)
7140 GtkItemFactory *ifactory;
7142 ifactory = gtk_item_factory_from_widget(compose->menubar);
7143 action_update_compose_menu(ifactory, "/Tools/Actions", compose);
7146 static void compose_update_privacy_systems_menu(Compose *compose)
7148 static gchar *branch_path = "/Options/Privacy System";
7149 GtkItemFactory *ifactory;
7150 GtkWidget *menuitem;
7151 GSList *systems, *cur;
7154 GtkWidget *system_none;
7157 ifactory = gtk_item_factory_from_widget(compose->menubar);
7159 /* remove old entries */
7160 menuitem = gtk_item_factory_get_widget(ifactory, branch_path);
7161 g_return_if_fail(menuitem != NULL);
7163 amenu = GTK_MENU_SHELL(menuitem)->children->next;
7164 while (amenu != NULL) {
7165 GList *alist = amenu->next;
7166 gtk_widget_destroy(GTK_WIDGET(amenu->data));
7170 system_none = gtk_item_factory_get_widget(ifactory,
7171 "/Options/Privacy System/None");
7173 g_signal_connect(G_OBJECT(system_none), "activate",
7174 G_CALLBACK(compose_set_privacy_system_cb), compose);
7176 systems = privacy_get_system_ids();
7177 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
7178 gchar *systemid = cur->data;
7180 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
7181 widget = gtk_radio_menu_item_new_with_label(group,
7182 privacy_system_get_name(systemid));
7183 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
7184 g_strdup(systemid), g_free);
7185 g_signal_connect(G_OBJECT(widget), "activate",
7186 G_CALLBACK(compose_set_privacy_system_cb), compose);
7188 gtk_menu_append(GTK_MENU(system_none->parent), widget);
7189 gtk_widget_show(widget);
7192 g_slist_free(systems);
7195 void compose_reflect_prefs_all(void)
7200 for (cur = compose_list; cur != NULL; cur = cur->next) {
7201 compose = (Compose *)cur->data;
7202 compose_set_template_menu(compose);
7206 void compose_reflect_prefs_pixmap_theme(void)
7211 for (cur = compose_list; cur != NULL; cur = cur->next) {
7212 compose = (Compose *)cur->data;
7213 toolbar_update(TOOLBAR_COMPOSE, compose);
7217 static const gchar *compose_quote_char_from_context(Compose *compose)
7219 const gchar *qmark = NULL;
7221 g_return_val_if_fail(compose != NULL, NULL);
7223 switch (compose->mode) {
7224 /* use forward-specific quote char */
7225 case COMPOSE_FORWARD:
7226 case COMPOSE_FORWARD_AS_ATTACH:
7227 case COMPOSE_FORWARD_INLINE:
7228 if (compose->folder && compose->folder->prefs &&
7229 compose->folder->prefs->forward_with_format)
7230 qmark = compose->folder->prefs->forward_quotemark;
7231 else if (compose->account->forward_with_format)
7232 qmark = compose->account->forward_quotemark;
7234 qmark = prefs_common.fw_quotemark;
7237 /* use reply-specific quote char in all other modes */
7239 if (compose->folder && compose->folder->prefs &&
7240 compose->folder->prefs->reply_with_format)
7241 qmark = compose->folder->prefs->reply_quotemark;
7242 else if (compose->account->reply_with_format)
7243 qmark = compose->account->reply_quotemark;
7245 qmark = prefs_common.quotemark;
7249 if (qmark == NULL || *qmark == '\0')
7255 static void compose_template_apply(Compose *compose, Template *tmpl,
7259 GtkTextBuffer *buffer;
7263 gchar *parsed_str = NULL;
7264 gint cursor_pos = 0;
7265 const gchar *err_msg = _("Template body format error at line %d.");
7268 /* process the body */
7270 text = GTK_TEXT_VIEW(compose->text);
7271 buffer = gtk_text_view_get_buffer(text);
7274 qmark = compose_quote_char_from_context(compose);
7276 if (compose->replyinfo != NULL) {
7279 gtk_text_buffer_set_text(buffer, "", -1);
7280 mark = gtk_text_buffer_get_insert(buffer);
7281 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7283 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
7284 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
7286 } else if (compose->fwdinfo != NULL) {
7289 gtk_text_buffer_set_text(buffer, "", -1);
7290 mark = gtk_text_buffer_get_insert(buffer);
7291 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7293 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
7294 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
7297 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
7299 GtkTextIter start, end;
7302 gtk_text_buffer_get_start_iter(buffer, &start);
7303 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
7304 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
7306 /* clear the buffer now */
7308 gtk_text_buffer_set_text(buffer, "", -1);
7310 parsed_str = compose_quote_fmt(compose, dummyinfo,
7311 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
7312 procmsg_msginfo_free( dummyinfo );
7318 gtk_text_buffer_set_text(buffer, "", -1);
7319 mark = gtk_text_buffer_get_insert(buffer);
7320 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7323 if (replace && parsed_str && compose->account->auto_sig)
7324 compose_insert_sig(compose, FALSE);
7326 if (replace && parsed_str) {
7327 gtk_text_buffer_get_start_iter(buffer, &iter);
7328 gtk_text_buffer_place_cursor(buffer, &iter);
7332 cursor_pos = quote_fmt_get_cursor_pos();
7333 compose->set_cursor_pos = cursor_pos;
7334 if (cursor_pos == -1)
7336 gtk_text_buffer_get_start_iter(buffer, &iter);
7337 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
7338 gtk_text_buffer_place_cursor(buffer, &iter);
7341 /* process the other fields */
7343 compose_template_apply_fields(compose, tmpl);
7344 quote_fmt_reset_vartable();
7345 compose_changed_cb(NULL, compose);
7348 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
7350 MsgInfo* dummyinfo = NULL;
7351 MsgInfo *msginfo = NULL;
7354 if (compose->replyinfo != NULL)
7355 msginfo = compose->replyinfo;
7356 else if (compose->fwdinfo != NULL)
7357 msginfo = compose->fwdinfo;
7359 dummyinfo = compose_msginfo_new_from_compose(compose);
7360 msginfo = dummyinfo;
7363 if (tmpl->to && *tmpl->to != '\0') {
7365 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7366 compose->gtkaspell);
7368 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7370 quote_fmt_scan_string(tmpl->to);
7373 buf = quote_fmt_get_buffer();
7375 alertpanel_error(_("Template To format error."));
7377 compose_entry_append(compose, buf, COMPOSE_TO);
7381 if (tmpl->cc && *tmpl->cc != '\0') {
7383 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7384 compose->gtkaspell);
7386 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7388 quote_fmt_scan_string(tmpl->cc);
7391 buf = quote_fmt_get_buffer();
7393 alertpanel_error(_("Template Cc format error."));
7395 compose_entry_append(compose, buf, COMPOSE_CC);
7399 if (tmpl->bcc && *tmpl->bcc != '\0') {
7401 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7402 compose->gtkaspell);
7404 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7406 quote_fmt_scan_string(tmpl->bcc);
7409 buf = quote_fmt_get_buffer();
7411 alertpanel_error(_("Template Bcc format error."));
7413 compose_entry_append(compose, buf, COMPOSE_BCC);
7417 /* process the subject */
7418 if (tmpl->subject && *tmpl->subject != '\0') {
7420 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7421 compose->gtkaspell);
7423 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7425 quote_fmt_scan_string(tmpl->subject);
7428 buf = quote_fmt_get_buffer();
7430 alertpanel_error(_("Template subject format error."));
7432 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
7436 procmsg_msginfo_free( dummyinfo );
7439 static void compose_destroy(Compose *compose)
7441 GtkTextBuffer *buffer;
7442 GtkClipboard *clipboard;
7444 compose_list = g_list_remove(compose_list, compose);
7446 if (compose->updating) {
7447 debug_print("danger, not destroying anything now\n");
7448 compose->deferred_destroy = TRUE;
7451 /* NOTE: address_completion_end() does nothing with the window
7452 * however this may change. */
7453 address_completion_end(compose->window);
7455 slist_free_strings(compose->to_list);
7456 g_slist_free(compose->to_list);
7457 slist_free_strings(compose->newsgroup_list);
7458 g_slist_free(compose->newsgroup_list);
7459 slist_free_strings(compose->header_list);
7460 g_slist_free(compose->header_list);
7462 procmsg_msginfo_free(compose->targetinfo);
7463 procmsg_msginfo_free(compose->replyinfo);
7464 procmsg_msginfo_free(compose->fwdinfo);
7466 g_free(compose->replyto);
7467 g_free(compose->cc);
7468 g_free(compose->bcc);
7469 g_free(compose->newsgroups);
7470 g_free(compose->followup_to);
7472 g_free(compose->ml_post);
7474 g_free(compose->inreplyto);
7475 g_free(compose->references);
7476 g_free(compose->msgid);
7477 g_free(compose->boundary);
7479 g_free(compose->redirect_filename);
7480 if (compose->undostruct)
7481 undo_destroy(compose->undostruct);
7483 g_free(compose->sig_str);
7485 g_free(compose->exteditor_file);
7487 g_free(compose->orig_charset);
7489 g_free(compose->privacy_system);
7491 if (addressbook_get_target_compose() == compose)
7492 addressbook_set_target_compose(NULL);
7495 if (compose->gtkaspell) {
7496 gtkaspell_delete(compose->gtkaspell);
7497 compose->gtkaspell = NULL;
7501 prefs_common.compose_width = compose->scrolledwin->allocation.width;
7502 prefs_common.compose_height = compose->window->allocation.height;
7504 if (!gtk_widget_get_parent(compose->paned))
7505 gtk_widget_destroy(compose->paned);
7506 gtk_widget_destroy(compose->popupmenu);
7508 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
7509 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7510 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
7512 gtk_widget_destroy(compose->window);
7513 toolbar_destroy(compose->toolbar);
7514 g_free(compose->toolbar);
7515 g_mutex_free(compose->mutex);
7519 static void compose_attach_info_free(AttachInfo *ainfo)
7521 g_free(ainfo->file);
7522 g_free(ainfo->content_type);
7523 g_free(ainfo->name);
7527 static void compose_attach_remove_selected(Compose *compose)
7529 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
7530 GtkTreeSelection *selection;
7532 GtkTreeModel *model;
7534 selection = gtk_tree_view_get_selection(tree_view);
7535 sel = gtk_tree_selection_get_selected_rows(selection, &model);
7540 for (cur = sel; cur != NULL; cur = cur->next) {
7541 GtkTreePath *path = cur->data;
7542 GtkTreeRowReference *ref = gtk_tree_row_reference_new
7545 gtk_tree_path_free(path);
7548 for (cur = sel; cur != NULL; cur = cur->next) {
7549 GtkTreeRowReference *ref = cur->data;
7550 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
7553 if (gtk_tree_model_get_iter(model, &iter, path))
7554 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
7556 gtk_tree_path_free(path);
7557 gtk_tree_row_reference_free(ref);
7563 static struct _AttachProperty
7566 GtkWidget *mimetype_entry;
7567 GtkWidget *encoding_optmenu;
7568 GtkWidget *path_entry;
7569 GtkWidget *filename_entry;
7571 GtkWidget *cancel_btn;
7574 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
7576 gtk_tree_path_free((GtkTreePath *)ptr);
7579 static void compose_attach_property(Compose *compose)
7581 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
7583 GtkComboBox *optmenu;
7584 GtkTreeSelection *selection;
7586 GtkTreeModel *model;
7589 static gboolean cancelled;
7591 /* only if one selected */
7592 selection = gtk_tree_view_get_selection(tree_view);
7593 if (gtk_tree_selection_count_selected_rows(selection) != 1)
7596 sel = gtk_tree_selection_get_selected_rows(selection, &model);
7600 path = (GtkTreePath *) sel->data;
7601 gtk_tree_model_get_iter(model, &iter, path);
7602 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
7605 g_list_foreach(sel, gtk_tree_path_free_, NULL);
7611 if (!attach_prop.window)
7612 compose_attach_property_create(&cancelled);
7613 gtk_widget_grab_focus(attach_prop.ok_btn);
7614 gtk_widget_show(attach_prop.window);
7615 manage_window_set_transient(GTK_WINDOW(attach_prop.window));
7617 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
7618 if (ainfo->encoding == ENC_UNKNOWN)
7619 combobox_select_by_data(optmenu, ENC_BASE64);
7621 combobox_select_by_data(optmenu, ainfo->encoding);
7623 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
7624 ainfo->content_type ? ainfo->content_type : "");
7625 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
7626 ainfo->file ? ainfo->file : "");
7627 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
7628 ainfo->name ? ainfo->name : "");
7631 const gchar *entry_text;
7633 gchar *cnttype = NULL;
7640 gtk_widget_hide(attach_prop.window);
7645 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
7646 if (*entry_text != '\0') {
7649 text = g_strstrip(g_strdup(entry_text));
7650 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
7651 cnttype = g_strdup(text);
7654 alertpanel_error(_("Invalid MIME type."));
7660 ainfo->encoding = combobox_get_active_data(optmenu);
7662 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
7663 if (*entry_text != '\0') {
7664 if (is_file_exist(entry_text) &&
7665 (size = get_file_size(entry_text)) > 0)
7666 file = g_strdup(entry_text);
7669 (_("File doesn't exist or is empty."));
7675 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
7676 if (*entry_text != '\0') {
7677 g_free(ainfo->name);
7678 ainfo->name = g_strdup(entry_text);
7682 g_free(ainfo->content_type);
7683 ainfo->content_type = cnttype;
7686 g_free(ainfo->file);
7692 /* update tree store */
7693 text = to_human_readable(ainfo->size);
7694 gtk_tree_model_get_iter(model, &iter, path);
7695 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
7696 COL_MIMETYPE, ainfo->content_type,
7698 COL_NAME, ainfo->name,
7704 gtk_tree_path_free(path);
7707 #define SET_LABEL_AND_ENTRY(str, entry, top) \
7709 label = gtk_label_new(str); \
7710 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
7711 GTK_FILL, 0, 0, 0); \
7712 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
7714 entry = gtk_entry_new(); \
7715 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
7716 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
7719 static void compose_attach_property_create(gboolean *cancelled)
7725 GtkWidget *mimetype_entry;
7728 GtkListStore *optmenu_menu;
7729 GtkWidget *path_entry;
7730 GtkWidget *filename_entry;
7733 GtkWidget *cancel_btn;
7734 GList *mime_type_list, *strlist;
7737 debug_print("Creating attach_property window...\n");
7739 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
7740 gtk_widget_set_size_request(window, 480, -1);
7741 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
7742 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
7743 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
7744 gtk_window_set_modal(GTK_WINDOW(window), TRUE);
7745 g_signal_connect(G_OBJECT(window), "delete_event",
7746 G_CALLBACK(attach_property_delete_event),
7748 g_signal_connect(G_OBJECT(window), "key_press_event",
7749 G_CALLBACK(attach_property_key_pressed),
7752 vbox = gtk_vbox_new(FALSE, 8);
7753 gtk_container_add(GTK_CONTAINER(window), vbox);
7755 table = gtk_table_new(4, 2, FALSE);
7756 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
7757 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
7758 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
7760 label = gtk_label_new(_("MIME type"));
7761 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
7763 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
7764 mimetype_entry = gtk_combo_new();
7765 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
7766 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
7768 /* stuff with list */
7769 mime_type_list = procmime_get_mime_type_list();
7771 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
7772 MimeType *type = (MimeType *) mime_type_list->data;
7775 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
7777 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
7780 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
7781 (GCompareFunc)strcmp2);
7784 gtk_combo_set_popdown_strings(GTK_COMBO(mimetype_entry), strlist);
7786 for (mime_type_list = strlist; mime_type_list != NULL;
7787 mime_type_list = mime_type_list->next)
7788 g_free(mime_type_list->data);
7789 g_list_free(strlist);
7791 mimetype_entry = GTK_COMBO(mimetype_entry)->entry;
7793 label = gtk_label_new(_("Encoding"));
7794 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
7796 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
7798 hbox = gtk_hbox_new(FALSE, 0);
7799 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
7800 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
7802 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
7803 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
7805 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
7806 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
7807 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
7808 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
7809 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
7811 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
7813 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
7814 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
7816 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
7817 &ok_btn, GTK_STOCK_OK,
7819 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
7820 gtk_widget_grab_default(ok_btn);
7822 g_signal_connect(G_OBJECT(ok_btn), "clicked",
7823 G_CALLBACK(attach_property_ok),
7825 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
7826 G_CALLBACK(attach_property_cancel),
7829 gtk_widget_show_all(vbox);
7831 attach_prop.window = window;
7832 attach_prop.mimetype_entry = mimetype_entry;
7833 attach_prop.encoding_optmenu = optmenu;
7834 attach_prop.path_entry = path_entry;
7835 attach_prop.filename_entry = filename_entry;
7836 attach_prop.ok_btn = ok_btn;
7837 attach_prop.cancel_btn = cancel_btn;
7840 #undef SET_LABEL_AND_ENTRY
7842 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
7848 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
7854 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
7855 gboolean *cancelled)
7863 static gboolean attach_property_key_pressed(GtkWidget *widget,
7865 gboolean *cancelled)
7867 if (event && event->keyval == GDK_Escape) {
7874 static void compose_exec_ext_editor(Compose *compose)
7881 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
7882 G_DIR_SEPARATOR, compose);
7884 if (pipe(pipe_fds) < 0) {
7890 if ((pid = fork()) < 0) {
7897 /* close the write side of the pipe */
7900 compose->exteditor_file = g_strdup(tmp);
7901 compose->exteditor_pid = pid;
7903 compose_set_ext_editor_sensitive(compose, FALSE);
7905 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
7906 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
7910 } else { /* process-monitoring process */
7916 /* close the read side of the pipe */
7919 if (compose_write_body_to_file(compose, tmp) < 0) {
7920 fd_write_all(pipe_fds[1], "2\n", 2);
7924 pid_ed = compose_exec_ext_editor_real(tmp);
7926 fd_write_all(pipe_fds[1], "1\n", 2);
7930 /* wait until editor is terminated */
7931 waitpid(pid_ed, NULL, 0);
7933 fd_write_all(pipe_fds[1], "0\n", 2);
7940 #endif /* G_OS_UNIX */
7944 static gint compose_exec_ext_editor_real(const gchar *file)
7951 g_return_val_if_fail(file != NULL, -1);
7953 if ((pid = fork()) < 0) {
7958 if (pid != 0) return pid;
7960 /* grandchild process */
7962 if (setpgid(0, getppid()))
7965 if (prefs_common.ext_editor_cmd &&
7966 (p = strchr(prefs_common.ext_editor_cmd, '%')) &&
7967 *(p + 1) == 's' && !strchr(p + 2, '%')) {
7968 g_snprintf(buf, sizeof(buf), prefs_common.ext_editor_cmd, file);
7970 if (prefs_common.ext_editor_cmd)
7971 g_warning("External editor command line is invalid: '%s'\n",
7972 prefs_common.ext_editor_cmd);
7973 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
7976 cmdline = strsplit_with_quote(buf, " ", 1024);
7977 execvp(cmdline[0], cmdline);
7980 g_strfreev(cmdline);
7985 static gboolean compose_ext_editor_kill(Compose *compose)
7987 pid_t pgid = compose->exteditor_pid * -1;
7990 ret = kill(pgid, 0);
7992 if (ret == 0 || (ret == -1 && EPERM == errno)) {
7996 msg = g_strdup_printf
7997 (_("The external editor is still working.\n"
7998 "Force terminating the process?\n"
7999 "process group id: %d"), -pgid);
8000 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
8001 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
8005 if (val == G_ALERTALTERNATE) {
8006 g_source_remove(compose->exteditor_tag);
8007 g_io_channel_shutdown(compose->exteditor_ch,
8009 g_io_channel_unref(compose->exteditor_ch);
8011 if (kill(pgid, SIGTERM) < 0) perror("kill");
8012 waitpid(compose->exteditor_pid, NULL, 0);
8014 g_warning("Terminated process group id: %d", -pgid);
8015 g_warning("Temporary file: %s",
8016 compose->exteditor_file);
8018 compose_set_ext_editor_sensitive(compose, TRUE);
8020 g_free(compose->exteditor_file);
8021 compose->exteditor_file = NULL;
8022 compose->exteditor_pid = -1;
8023 compose->exteditor_ch = NULL;
8024 compose->exteditor_tag = -1;
8032 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
8036 Compose *compose = (Compose *)data;
8039 debug_print(_("Compose: input from monitoring process\n"));
8041 g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL);
8043 g_io_channel_shutdown(source, FALSE, NULL);
8044 g_io_channel_unref(source);
8046 waitpid(compose->exteditor_pid, NULL, 0);
8048 if (buf[0] == '0') { /* success */
8049 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
8050 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
8052 gtk_text_buffer_set_text(buffer, "", -1);
8053 compose_insert_file(compose, compose->exteditor_file);
8054 compose_changed_cb(NULL, compose);
8056 if (g_unlink(compose->exteditor_file) < 0)
8057 FILE_OP_ERROR(compose->exteditor_file, "unlink");
8058 } else if (buf[0] == '1') { /* failed */
8059 g_warning("Couldn't exec external editor\n");
8060 if (g_unlink(compose->exteditor_file) < 0)
8061 FILE_OP_ERROR(compose->exteditor_file, "unlink");
8062 } else if (buf[0] == '2') {
8063 g_warning("Couldn't write to file\n");
8064 } else if (buf[0] == '3') {
8065 g_warning("Pipe read failed\n");
8068 compose_set_ext_editor_sensitive(compose, TRUE);
8070 g_free(compose->exteditor_file);
8071 compose->exteditor_file = NULL;
8072 compose->exteditor_pid = -1;
8073 compose->exteditor_ch = NULL;
8074 compose->exteditor_tag = -1;
8079 static void compose_set_ext_editor_sensitive(Compose *compose,
8082 GtkItemFactory *ifactory;
8084 ifactory = gtk_item_factory_from_widget(compose->menubar);
8086 menu_set_sensitive(ifactory, "/Message/Send", sensitive);
8087 menu_set_sensitive(ifactory, "/Message/Send later", sensitive);
8088 menu_set_sensitive(ifactory, "/Message/Insert file", sensitive);
8089 menu_set_sensitive(ifactory, "/Message/Insert signature", sensitive);
8090 menu_set_sensitive(ifactory, "/Edit/Wrap current paragraph", sensitive);
8091 menu_set_sensitive(ifactory, "/Edit/Wrap all long lines", sensitive);
8092 menu_set_sensitive(ifactory, "/Edit/Edit with external editor",
8095 gtk_widget_set_sensitive(compose->text, sensitive);
8096 if (compose->toolbar->send_btn)
8097 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
8098 if (compose->toolbar->sendl_btn)
8099 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
8100 if (compose->toolbar->draft_btn)
8101 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
8102 if (compose->toolbar->insert_btn)
8103 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
8104 if (compose->toolbar->sig_btn)
8105 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
8106 if (compose->toolbar->exteditor_btn)
8107 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
8108 if (compose->toolbar->linewrap_current_btn)
8109 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
8110 if (compose->toolbar->linewrap_all_btn)
8111 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
8113 #endif /* G_OS_UNIX */
8116 * compose_undo_state_changed:
8118 * Change the sensivity of the menuentries undo and redo
8120 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
8121 gint redo_state, gpointer data)
8123 GtkWidget *widget = GTK_WIDGET(data);
8124 GtkItemFactory *ifactory;
8126 g_return_if_fail(widget != NULL);
8128 ifactory = gtk_item_factory_from_widget(widget);
8130 switch (undo_state) {
8131 case UNDO_STATE_TRUE:
8132 if (!undostruct->undo_state) {
8133 undostruct->undo_state = TRUE;
8134 menu_set_sensitive(ifactory, "/Edit/Undo", TRUE);
8137 case UNDO_STATE_FALSE:
8138 if (undostruct->undo_state) {
8139 undostruct->undo_state = FALSE;
8140 menu_set_sensitive(ifactory, "/Edit/Undo", FALSE);
8143 case UNDO_STATE_UNCHANGED:
8145 case UNDO_STATE_REFRESH:
8146 menu_set_sensitive(ifactory, "/Edit/Undo",
8147 undostruct->undo_state);
8150 g_warning("Undo state not recognized");
8154 switch (redo_state) {
8155 case UNDO_STATE_TRUE:
8156 if (!undostruct->redo_state) {
8157 undostruct->redo_state = TRUE;
8158 menu_set_sensitive(ifactory, "/Edit/Redo", TRUE);
8161 case UNDO_STATE_FALSE:
8162 if (undostruct->redo_state) {
8163 undostruct->redo_state = FALSE;
8164 menu_set_sensitive(ifactory, "/Edit/Redo", FALSE);
8167 case UNDO_STATE_UNCHANGED:
8169 case UNDO_STATE_REFRESH:
8170 menu_set_sensitive(ifactory, "/Edit/Redo",
8171 undostruct->redo_state);
8174 g_warning("Redo state not recognized");
8179 /* callback functions */
8181 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
8182 * includes "non-client" (windows-izm) in calculation, so this calculation
8183 * may not be accurate.
8185 static gboolean compose_edit_size_alloc(GtkEditable *widget,
8186 GtkAllocation *allocation,
8187 GtkSHRuler *shruler)
8189 if (prefs_common.show_ruler) {
8190 gint char_width = 0, char_height = 0;
8191 gint line_width_in_chars;
8193 gtkut_get_font_size(GTK_WIDGET(widget),
8194 &char_width, &char_height);
8195 line_width_in_chars =
8196 (allocation->width - allocation->x) / char_width;
8198 /* got the maximum */
8199 gtk_ruler_set_range(GTK_RULER(shruler),
8200 0.0, line_width_in_chars, 0,
8201 /*line_width_in_chars*/ char_width);
8207 static void account_activated(GtkComboBox *optmenu, gpointer data)
8209 Compose *compose = (Compose *)data;
8212 gchar *folderidentifier;
8213 gint account_id = 0;
8217 /* Get ID of active account in the combo box */
8218 menu = gtk_combo_box_get_model(optmenu);
8219 gtk_combo_box_get_active_iter(optmenu, &iter);
8220 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
8222 ac = account_find_from_id(account_id);
8223 g_return_if_fail(ac != NULL);
8225 if (ac != compose->account)
8226 compose_select_account(compose, ac, FALSE);
8228 /* Set message save folder */
8229 if (account_get_special_folder(compose->account, F_OUTBOX)) {
8230 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
8232 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
8233 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
8235 gtk_editable_delete_text(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
8236 if (account_get_special_folder(compose->account, F_OUTBOX)) {
8237 folderidentifier = folder_item_get_identifier(account_get_special_folder
8238 (compose->account, F_OUTBOX));
8239 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
8240 g_free(folderidentifier);
8244 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
8245 GtkTreeViewColumn *column, Compose *compose)
8247 compose_attach_property(compose);
8250 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
8253 Compose *compose = (Compose *)data;
8254 GtkTreeSelection *attach_selection;
8255 gint attach_nr_selected;
8256 GtkItemFactory *ifactory;
8258 if (!event) return FALSE;
8260 if (event->button == 3) {
8261 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
8262 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
8263 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
8265 if (attach_nr_selected > 0)
8267 menu_set_sensitive(ifactory, "/Remove", TRUE);
8268 menu_set_sensitive(ifactory, "/Properties...", TRUE);
8270 menu_set_sensitive(ifactory, "/Remove", FALSE);
8271 menu_set_sensitive(ifactory, "/Properties...", FALSE);
8274 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
8275 NULL, NULL, event->button, event->time);
8282 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
8285 Compose *compose = (Compose *)data;
8287 if (!event) return FALSE;
8289 switch (event->keyval) {
8291 compose_attach_remove_selected(compose);
8297 static void compose_allow_user_actions (Compose *compose, gboolean allow)
8299 GtkItemFactory *ifactory = gtk_item_factory_from_widget(compose->menubar);
8300 toolbar_comp_set_sensitive(compose, allow);
8301 menu_set_sensitive(ifactory, "/Message", allow);
8302 menu_set_sensitive(ifactory, "/Edit", allow);
8304 menu_set_sensitive(ifactory, "/Spelling", allow);
8306 menu_set_sensitive(ifactory, "/Options", allow);
8307 menu_set_sensitive(ifactory, "/Tools", allow);
8308 menu_set_sensitive(ifactory, "/Help", allow);
8310 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
8314 static void compose_send_cb(gpointer data, guint action, GtkWidget *widget)
8316 Compose *compose = (Compose *)data;
8318 if (prefs_common.work_offline &&
8319 !inc_offline_should_override(TRUE,
8320 _("Claws Mail needs network access in order "
8321 "to send this email.")))
8324 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
8325 g_source_remove(compose->draft_timeout_tag);
8326 compose->draft_timeout_tag = -1;
8329 compose_send(compose);
8332 static void compose_send_later_cb(gpointer data, guint action,
8335 Compose *compose = (Compose *)data;
8339 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
8343 compose_close(compose);
8344 } else if (val == -1) {
8345 alertpanel_error(_("Could not queue message."));
8346 } else if (val == -2) {
8347 alertpanel_error(_("Could not queue message:\n\n%s."), strerror(errno));
8348 } else if (val == -3) {
8349 if (privacy_peek_error())
8350 alertpanel_error(_("Could not queue message for sending:\n\n"
8351 "Signature failed: %s"), privacy_get_error());
8352 } else if (val == -4) {
8353 alertpanel_error(_("Could not queue message for sending:\n\n"
8354 "Charset conversion failed."));
8355 } else if (val == -5) {
8356 alertpanel_error(_("Could not queue message for sending:\n\n"
8357 "Couldn't get recipient encryption key."));
8358 } else if (val == -6) {
8361 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
8364 #define DRAFTED_AT_EXIT "drafted_at_exit"
8365 static void compose_register_draft(MsgInfo *info)
8367 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8368 DRAFTED_AT_EXIT, NULL);
8369 FILE *fp = fopen(filepath, "ab");
8372 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
8380 gboolean compose_draft (gpointer data, guint action)
8382 Compose *compose = (Compose *)data;
8386 MsgFlags flag = {0, 0};
8387 static gboolean lock = FALSE;
8388 MsgInfo *newmsginfo;
8390 gboolean target_locked = FALSE;
8392 if (lock) return FALSE;
8394 if (compose->sending)
8397 draft = account_get_special_folder(compose->account, F_DRAFT);
8398 g_return_val_if_fail(draft != NULL, FALSE);
8400 if (!g_mutex_trylock(compose->mutex)) {
8401 /* we don't want to lock the mutex once it's available,
8402 * because as the only other part of compose.c locking
8403 * it is compose_close - which means once unlocked,
8404 * the compose struct will be freed */
8405 debug_print("couldn't lock mutex, probably sending\n");
8411 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
8412 G_DIR_SEPARATOR, compose);
8413 if ((fp = g_fopen(tmp, "wb")) == NULL) {
8414 FILE_OP_ERROR(tmp, "fopen");
8418 /* chmod for security */
8419 if (change_file_mode_rw(fp, tmp) < 0) {
8420 FILE_OP_ERROR(tmp, "chmod");
8421 g_warning("can't change file mode\n");
8424 /* Save draft infos */
8425 fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id);
8426 fprintf(fp, "S:%s\n", compose->account->address);
8428 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
8429 gchar *savefolderid;
8431 savefolderid = gtk_editable_get_chars(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
8432 fprintf(fp, "SCF:%s\n", savefolderid);
8433 g_free(savefolderid);
8435 if (compose->return_receipt) {
8436 fprintf(fp, "RRCPT:1\n");
8438 if (compose->privacy_system) {
8439 fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing);
8440 fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption);
8441 fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system);
8444 /* Message-ID of message replying to */
8445 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
8448 folderid = folder_item_get_identifier(compose->replyinfo->folder);
8449 fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid);
8452 /* Message-ID of message forwarding to */
8453 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
8456 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
8457 fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid);
8461 /* end of headers */
8462 fprintf(fp, "X-Claws-End-Special-Headers: 1\n");
8464 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
8472 if (compose->targetinfo) {
8473 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
8474 flag.perm_flags = target_locked?MSG_LOCKED:0;
8476 flag.tmp_flags = MSG_DRAFT;
8478 folder_item_scan(draft);
8479 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
8480 MsgInfo *tmpinfo = NULL;
8481 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
8482 if (compose->msgid) {
8483 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
8486 msgnum = tmpinfo->msgnum;
8487 procmsg_msginfo_free(tmpinfo);
8488 debug_print("got draft msgnum %d from scanning\n", msgnum);
8490 debug_print("didn't get draft msgnum after scanning\n");
8493 debug_print("got draft msgnum %d from adding\n", msgnum);
8498 if (action != COMPOSE_AUTO_SAVE) {
8499 if (action != COMPOSE_DRAFT_FOR_EXIT)
8500 alertpanel_error(_("Could not save draft."));
8503 gtkut_window_popup(compose->window);
8504 val = alertpanel_full(_("Could not save draft"),
8505 _("Could not save draft.\n"
8506 "Do you want to cancel exit or discard this email?"),
8507 _("_Cancel exit"), _("_Discard email"), NULL,
8508 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
8509 if (val == G_ALERTALTERNATE) {
8511 g_mutex_unlock(compose->mutex); /* must be done before closing */
8512 compose_close(compose);
8516 g_mutex_unlock(compose->mutex); /* must be done before closing */
8525 if (compose->mode == COMPOSE_REEDIT) {
8526 compose_remove_reedit_target(compose, TRUE);
8529 newmsginfo = folder_item_get_msginfo(draft, msgnum);
8532 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
8534 procmsg_msginfo_set_flags(newmsginfo, MSG_LOCKED, MSG_DRAFT);
8536 procmsg_msginfo_set_flags(newmsginfo, 0, MSG_DRAFT);
8537 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
8538 procmsg_msginfo_set_flags(newmsginfo, 0,
8539 MSG_HAS_ATTACHMENT);
8541 if (action == COMPOSE_DRAFT_FOR_EXIT) {
8542 compose_register_draft(newmsginfo);
8544 procmsg_msginfo_free(newmsginfo);
8547 folder_item_scan(draft);
8549 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
8551 g_mutex_unlock(compose->mutex); /* must be done before closing */
8552 compose_close(compose);
8558 path = folder_item_fetch_msg(draft, msgnum);
8560 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
8563 if (g_stat(path, &s) < 0) {
8564 FILE_OP_ERROR(path, "stat");
8570 procmsg_msginfo_free(compose->targetinfo);
8571 compose->targetinfo = procmsg_msginfo_new();
8572 compose->targetinfo->msgnum = msgnum;
8573 compose->targetinfo->size = s.st_size;
8574 compose->targetinfo->mtime = s.st_mtime;
8575 compose->targetinfo->folder = draft;
8577 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
8578 compose->mode = COMPOSE_REEDIT;
8580 if (action == COMPOSE_AUTO_SAVE) {
8581 compose->autosaved_draft = compose->targetinfo;
8583 compose->modified = FALSE;
8584 compose_set_title(compose);
8588 g_mutex_unlock(compose->mutex);
8592 void compose_clear_exit_drafts(void)
8594 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8595 DRAFTED_AT_EXIT, NULL);
8596 if (is_file_exist(filepath))
8602 void compose_reopen_exit_drafts(void)
8604 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8605 DRAFTED_AT_EXIT, NULL);
8606 FILE *fp = fopen(filepath, "rb");
8610 while (fgets(buf, sizeof(buf), fp)) {
8611 gchar **parts = g_strsplit(buf, "\t", 2);
8612 const gchar *folder = parts[0];
8613 int msgnum = parts[1] ? atoi(parts[1]):-1;
8615 if (folder && *folder && msgnum > -1) {
8616 FolderItem *item = folder_find_item_from_identifier(folder);
8617 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
8619 compose_reedit(info, FALSE);
8626 compose_clear_exit_drafts();
8629 static void compose_draft_cb(gpointer data, guint action, GtkWidget *widget)
8631 compose_draft(data, action);
8634 static void compose_attach_cb(gpointer data, guint action, GtkWidget *widget)
8636 Compose *compose = (Compose *)data;
8639 if (compose->redirect_filename != NULL)
8642 file_list = filesel_select_multiple_files_open(_("Select file"));
8647 for ( tmp = file_list; tmp; tmp = tmp->next) {
8648 gchar *file = (gchar *) tmp->data;
8649 gchar *utf8_filename = conv_filename_to_utf8(file);
8650 compose_attach_append(compose, file, utf8_filename, NULL);
8651 compose_changed_cb(NULL, compose);
8653 g_free(utf8_filename);
8655 g_list_free(file_list);
8659 static void compose_insert_file_cb(gpointer data, guint action,
8662 Compose *compose = (Compose *)data;
8665 file_list = filesel_select_multiple_files_open(_("Select file"));
8670 for ( tmp = file_list; tmp; tmp = tmp->next) {
8671 gchar *file = (gchar *) tmp->data;
8672 gchar *filedup = g_strdup(file);
8673 gchar *shortfile = g_path_get_basename(filedup);
8674 ComposeInsertResult res;
8676 res = compose_insert_file(compose, file);
8677 if (res == COMPOSE_INSERT_READ_ERROR) {
8678 alertpanel_error(_("File '%s' could not be read."), shortfile);
8679 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
8680 alertpanel_error(_("File '%s' contained invalid characters\n"
8681 "for the current encoding, insertion may be incorrect."), shortfile);
8687 g_list_free(file_list);
8691 static void compose_insert_sig_cb(gpointer data, guint action,
8694 Compose *compose = (Compose *)data;
8696 compose_insert_sig(compose, FALSE);
8699 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
8703 Compose *compose = (Compose *)data;
8705 gtkut_widget_get_uposition(widget, &x, &y);
8706 prefs_common.compose_x = x;
8707 prefs_common.compose_y = y;
8709 if (compose->sending || compose->updating)
8711 compose_close_cb(compose, 0, NULL);
8715 void compose_close_toolbar(Compose *compose)
8717 compose_close_cb(compose, 0, NULL);
8720 static void compose_close_cb(gpointer data, guint action, GtkWidget *widget)
8722 Compose *compose = (Compose *)data;
8726 if (compose->exteditor_tag != -1) {
8727 if (!compose_ext_editor_kill(compose))
8732 if (compose->modified) {
8733 val = alertpanel(_("Discard message"),
8734 _("This message has been modified. Discard it?"),
8735 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
8738 case G_ALERTDEFAULT:
8739 if (prefs_common.autosave)
8740 compose_remove_draft(compose);
8742 case G_ALERTALTERNATE:
8743 compose_draft_cb(data, COMPOSE_QUIT_EDITING, NULL);
8750 compose_close(compose);
8753 static void compose_set_encoding_cb(gpointer data, guint action,
8756 Compose *compose = (Compose *)data;
8758 if (GTK_CHECK_MENU_ITEM(widget)->active)
8759 compose->out_encoding = (CharSet)action;
8762 static void compose_address_cb(gpointer data, guint action, GtkWidget *widget)
8764 Compose *compose = (Compose *)data;
8766 addressbook_open(compose);
8769 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
8771 Compose *compose = (Compose *)data;
8776 tmpl = g_object_get_data(G_OBJECT(widget), "template");
8777 g_return_if_fail(tmpl != NULL);
8779 msg = g_strdup_printf(_("Do you want to apply the template '%s' ?"),
8781 val = alertpanel(_("Apply template"), msg,
8782 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
8785 if (val == G_ALERTDEFAULT)
8786 compose_template_apply(compose, tmpl, TRUE);
8787 else if (val == G_ALERTALTERNATE)
8788 compose_template_apply(compose, tmpl, FALSE);
8791 static void compose_ext_editor_cb(gpointer data, guint action,
8794 Compose *compose = (Compose *)data;
8796 compose_exec_ext_editor(compose);
8799 static void compose_undo_cb(Compose *compose)
8801 gboolean prev_autowrap = compose->autowrap;
8803 compose->autowrap = FALSE;
8804 undo_undo(compose->undostruct);
8805 compose->autowrap = prev_autowrap;
8808 static void compose_redo_cb(Compose *compose)
8810 gboolean prev_autowrap = compose->autowrap;
8812 compose->autowrap = FALSE;
8813 undo_redo(compose->undostruct);
8814 compose->autowrap = prev_autowrap;
8817 static void entry_cut_clipboard(GtkWidget *entry)
8819 if (GTK_IS_EDITABLE(entry))
8820 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
8821 else if (GTK_IS_TEXT_VIEW(entry))
8822 gtk_text_buffer_cut_clipboard(
8823 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
8824 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
8828 static void entry_copy_clipboard(GtkWidget *entry)
8830 if (GTK_IS_EDITABLE(entry))
8831 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
8832 else if (GTK_IS_TEXT_VIEW(entry))
8833 gtk_text_buffer_copy_clipboard(
8834 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
8835 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
8838 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
8839 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
8841 if (GTK_IS_TEXT_VIEW(entry)) {
8842 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
8843 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
8844 GtkTextIter start_iter, end_iter;
8846 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
8848 if (contents == NULL)
8851 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
8853 /* we shouldn't delete the selection when middle-click-pasting, or we
8854 * can't mid-click-paste our own selection */
8855 if (clip != GDK_SELECTION_PRIMARY) {
8856 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
8859 if (insert_place == NULL) {
8860 /* if insert_place isn't specified, insert at the cursor.
8861 * used for Ctrl-V pasting */
8862 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
8863 start = gtk_text_iter_get_offset(&start_iter);
8864 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
8866 /* if insert_place is specified, paste here.
8867 * used for mid-click-pasting */
8868 start = gtk_text_iter_get_offset(insert_place);
8869 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
8873 /* paste unwrapped: mark the paste so it's not wrapped later */
8874 end = start + strlen(contents);
8875 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
8876 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
8877 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
8878 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
8879 /* rewrap paragraph now (after a mid-click-paste) */
8880 mark_start = gtk_text_buffer_get_insert(buffer);
8881 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
8882 gtk_text_iter_backward_char(&start_iter);
8883 compose_beautify_paragraph(compose, &start_iter, TRUE);
8885 } else if (GTK_IS_EDITABLE(entry))
8886 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
8890 static void entry_allsel(GtkWidget *entry)
8892 if (GTK_IS_EDITABLE(entry))
8893 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
8894 else if (GTK_IS_TEXT_VIEW(entry)) {
8895 GtkTextIter startiter, enditer;
8896 GtkTextBuffer *textbuf;
8898 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
8899 gtk_text_buffer_get_start_iter(textbuf, &startiter);
8900 gtk_text_buffer_get_end_iter(textbuf, &enditer);
8902 gtk_text_buffer_move_mark_by_name(textbuf,
8903 "selection_bound", &startiter);
8904 gtk_text_buffer_move_mark_by_name(textbuf,
8905 "insert", &enditer);
8909 static void compose_cut_cb(Compose *compose)
8911 if (compose->focused_editable
8913 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
8916 entry_cut_clipboard(compose->focused_editable);
8919 static void compose_copy_cb(Compose *compose)
8921 if (compose->focused_editable
8923 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
8926 entry_copy_clipboard(compose->focused_editable);
8929 static void compose_paste_cb(Compose *compose)
8932 GtkTextBuffer *buffer;
8934 if (compose->focused_editable &&
8935 GTK_WIDGET_HAS_FOCUS(compose->focused_editable))
8936 entry_paste_clipboard(compose, compose->focused_editable,
8937 prefs_common.linewrap_pastes,
8938 GDK_SELECTION_CLIPBOARD, NULL);
8942 static void compose_paste_as_quote_cb(Compose *compose)
8944 gint wrap_quote = prefs_common.linewrap_quote;
8945 if (compose->focused_editable
8947 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
8950 /* let text_insert() (called directly or at a later time
8951 * after the gtk_editable_paste_clipboard) know that
8952 * text is to be inserted as a quotation. implemented
8953 * by using a simple refcount... */
8954 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
8955 G_OBJECT(compose->focused_editable),
8956 "paste_as_quotation"));
8957 g_object_set_data(G_OBJECT(compose->focused_editable),
8958 "paste_as_quotation",
8959 GINT_TO_POINTER(paste_as_quotation + 1));
8960 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
8961 entry_paste_clipboard(compose, compose->focused_editable,
8962 prefs_common.linewrap_pastes,
8963 GDK_SELECTION_CLIPBOARD, NULL);
8964 prefs_common.linewrap_quote = wrap_quote;
8968 static void compose_paste_no_wrap_cb(Compose *compose)
8971 GtkTextBuffer *buffer;
8973 if (compose->focused_editable
8975 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
8978 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
8979 GDK_SELECTION_CLIPBOARD, NULL);
8983 static void compose_paste_wrap_cb(Compose *compose)
8986 GtkTextBuffer *buffer;
8988 if (compose->focused_editable
8990 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
8993 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
8994 GDK_SELECTION_CLIPBOARD, NULL);
8998 static void compose_allsel_cb(Compose *compose)
9000 if (compose->focused_editable
9002 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9005 entry_allsel(compose->focused_editable);
9008 static void textview_move_beginning_of_line (GtkTextView *text)
9010 GtkTextBuffer *buffer;
9014 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9016 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9017 mark = gtk_text_buffer_get_insert(buffer);
9018 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9019 gtk_text_iter_set_line_offset(&ins, 0);
9020 gtk_text_buffer_place_cursor(buffer, &ins);
9023 static void textview_move_forward_character (GtkTextView *text)
9025 GtkTextBuffer *buffer;
9029 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9031 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9032 mark = gtk_text_buffer_get_insert(buffer);
9033 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9034 if (gtk_text_iter_forward_cursor_position(&ins))
9035 gtk_text_buffer_place_cursor(buffer, &ins);
9038 static void textview_move_backward_character (GtkTextView *text)
9040 GtkTextBuffer *buffer;
9044 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9046 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9047 mark = gtk_text_buffer_get_insert(buffer);
9048 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9049 if (gtk_text_iter_backward_cursor_position(&ins))
9050 gtk_text_buffer_place_cursor(buffer, &ins);
9053 static void textview_move_forward_word (GtkTextView *text)
9055 GtkTextBuffer *buffer;
9060 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9062 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9063 mark = gtk_text_buffer_get_insert(buffer);
9064 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9065 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
9066 if (gtk_text_iter_forward_word_ends(&ins, count)) {
9067 gtk_text_iter_backward_word_start(&ins);
9068 gtk_text_buffer_place_cursor(buffer, &ins);
9072 static void textview_move_backward_word (GtkTextView *text)
9074 GtkTextBuffer *buffer;
9079 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9081 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9082 mark = gtk_text_buffer_get_insert(buffer);
9083 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9084 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
9085 if (gtk_text_iter_backward_word_starts(&ins, 1))
9086 gtk_text_buffer_place_cursor(buffer, &ins);
9089 static void textview_move_end_of_line (GtkTextView *text)
9091 GtkTextBuffer *buffer;
9095 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9097 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9098 mark = gtk_text_buffer_get_insert(buffer);
9099 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9100 if (gtk_text_iter_forward_to_line_end(&ins))
9101 gtk_text_buffer_place_cursor(buffer, &ins);
9104 static void textview_move_next_line (GtkTextView *text)
9106 GtkTextBuffer *buffer;
9111 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9113 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9114 mark = gtk_text_buffer_get_insert(buffer);
9115 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9116 offset = gtk_text_iter_get_line_offset(&ins);
9117 if (gtk_text_iter_forward_line(&ins)) {
9118 gtk_text_iter_set_line_offset(&ins, offset);
9119 gtk_text_buffer_place_cursor(buffer, &ins);
9123 static void textview_move_previous_line (GtkTextView *text)
9125 GtkTextBuffer *buffer;
9130 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9132 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9133 mark = gtk_text_buffer_get_insert(buffer);
9134 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9135 offset = gtk_text_iter_get_line_offset(&ins);
9136 if (gtk_text_iter_backward_line(&ins)) {
9137 gtk_text_iter_set_line_offset(&ins, offset);
9138 gtk_text_buffer_place_cursor(buffer, &ins);
9142 static void textview_delete_forward_character (GtkTextView *text)
9144 GtkTextBuffer *buffer;
9146 GtkTextIter ins, end_iter;
9148 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9150 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9151 mark = gtk_text_buffer_get_insert(buffer);
9152 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9154 if (gtk_text_iter_forward_char(&end_iter)) {
9155 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9159 static void textview_delete_backward_character (GtkTextView *text)
9161 GtkTextBuffer *buffer;
9163 GtkTextIter ins, end_iter;
9165 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9167 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9168 mark = gtk_text_buffer_get_insert(buffer);
9169 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9171 if (gtk_text_iter_backward_char(&end_iter)) {
9172 gtk_text_buffer_delete(buffer, &end_iter, &ins);
9176 static void textview_delete_forward_word (GtkTextView *text)
9178 GtkTextBuffer *buffer;
9180 GtkTextIter ins, end_iter;
9182 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9184 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9185 mark = gtk_text_buffer_get_insert(buffer);
9186 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9188 if (gtk_text_iter_forward_word_end(&end_iter)) {
9189 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9193 static void textview_delete_backward_word (GtkTextView *text)
9195 GtkTextBuffer *buffer;
9197 GtkTextIter ins, end_iter;
9199 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9201 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9202 mark = gtk_text_buffer_get_insert(buffer);
9203 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9205 if (gtk_text_iter_backward_word_start(&end_iter)) {
9206 gtk_text_buffer_delete(buffer, &end_iter, &ins);
9210 static void textview_delete_line (GtkTextView *text)
9212 GtkTextBuffer *buffer;
9214 GtkTextIter ins, start_iter, end_iter;
9217 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9219 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9220 mark = gtk_text_buffer_get_insert(buffer);
9221 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9224 gtk_text_iter_set_line_offset(&start_iter, 0);
9227 if (gtk_text_iter_ends_line(&end_iter))
9228 found = gtk_text_iter_forward_char(&end_iter);
9230 found = gtk_text_iter_forward_to_line_end(&end_iter);
9233 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
9236 static void textview_delete_to_line_end (GtkTextView *text)
9238 GtkTextBuffer *buffer;
9240 GtkTextIter ins, end_iter;
9243 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9245 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9246 mark = gtk_text_buffer_get_insert(buffer);
9247 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9249 if (gtk_text_iter_ends_line(&end_iter))
9250 found = gtk_text_iter_forward_char(&end_iter);
9252 found = gtk_text_iter_forward_to_line_end(&end_iter);
9254 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9257 static void compose_advanced_action_cb(Compose *compose,
9258 ComposeCallAdvancedAction action)
9260 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9262 void (*do_action) (GtkTextView *text);
9263 } action_table[] = {
9264 {textview_move_beginning_of_line},
9265 {textview_move_forward_character},
9266 {textview_move_backward_character},
9267 {textview_move_forward_word},
9268 {textview_move_backward_word},
9269 {textview_move_end_of_line},
9270 {textview_move_next_line},
9271 {textview_move_previous_line},
9272 {textview_delete_forward_character},
9273 {textview_delete_backward_character},
9274 {textview_delete_forward_word},
9275 {textview_delete_backward_word},
9276 {textview_delete_line},
9277 {NULL}, /* gtk_stext_delete_line_n */
9278 {textview_delete_to_line_end}
9281 if (!GTK_WIDGET_HAS_FOCUS(text)) return;
9283 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
9284 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
9285 if (action_table[action].do_action)
9286 action_table[action].do_action(text);
9288 g_warning("Not implemented yet.");
9292 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
9296 if (GTK_IS_EDITABLE(widget)) {
9297 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
9298 gtk_editable_set_position(GTK_EDITABLE(widget),
9301 if (widget->parent && widget->parent->parent
9302 && widget->parent->parent->parent) {
9303 if (GTK_IS_SCROLLED_WINDOW(widget->parent->parent->parent)) {
9304 gint y = widget->allocation.y;
9305 gint height = widget->allocation.height;
9306 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
9307 (GTK_SCROLLED_WINDOW(widget->parent->parent->parent));
9309 if (y < (int)shown->value) {
9310 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown), y - 1);
9312 if (y + height > (int)shown->value + (int)shown->page_size) {
9313 if (y - height - 1 < (int)shown->upper - (int)shown->page_size) {
9314 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
9315 y + height - (int)shown->page_size - 1);
9317 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
9318 (int)shown->upper - (int)shown->page_size - 1);
9325 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
9326 compose->focused_editable = widget;
9329 if (GTK_IS_TEXT_VIEW(widget)
9330 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
9331 gtk_widget_ref(compose->notebook);
9332 gtk_widget_ref(compose->edit_vbox);
9333 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
9334 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
9335 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
9336 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
9337 gtk_widget_unref(compose->notebook);
9338 gtk_widget_unref(compose->edit_vbox);
9339 g_signal_handlers_block_by_func(G_OBJECT(widget),
9340 G_CALLBACK(compose_grab_focus_cb),
9342 gtk_widget_grab_focus(widget);
9343 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
9344 G_CALLBACK(compose_grab_focus_cb),
9346 } else if (!GTK_IS_TEXT_VIEW(widget)
9347 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
9348 gtk_widget_ref(compose->notebook);
9349 gtk_widget_ref(compose->edit_vbox);
9350 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
9351 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
9352 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
9353 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
9354 gtk_widget_unref(compose->notebook);
9355 gtk_widget_unref(compose->edit_vbox);
9356 g_signal_handlers_block_by_func(G_OBJECT(widget),
9357 G_CALLBACK(compose_grab_focus_cb),
9359 gtk_widget_grab_focus(widget);
9360 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
9361 G_CALLBACK(compose_grab_focus_cb),
9367 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
9369 compose->modified = TRUE;
9371 compose_set_title(compose);
9375 static void compose_wrap_cb(gpointer data, guint action, GtkWidget *widget)
9377 Compose *compose = (Compose *)data;
9380 compose_wrap_all_full(compose, TRUE);
9382 compose_beautify_paragraph(compose, NULL, TRUE);
9385 static void compose_find_cb(gpointer data, guint action, GtkWidget *widget)
9387 Compose *compose = (Compose *)data;
9389 message_search_compose(compose);
9392 static void compose_toggle_autowrap_cb(gpointer data, guint action,
9395 Compose *compose = (Compose *)data;
9396 compose->autowrap = GTK_CHECK_MENU_ITEM(widget)->active;
9397 if (compose->autowrap)
9398 compose_wrap_all_full(compose, TRUE);
9399 compose->autowrap = GTK_CHECK_MENU_ITEM(widget)->active;
9402 static void compose_toggle_sign_cb(gpointer data, guint action,
9405 Compose *compose = (Compose *)data;
9407 if (GTK_CHECK_MENU_ITEM(widget)->active)
9408 compose->use_signing = TRUE;
9410 compose->use_signing = FALSE;
9413 static void compose_toggle_encrypt_cb(gpointer data, guint action,
9416 Compose *compose = (Compose *)data;
9418 if (GTK_CHECK_MENU_ITEM(widget)->active)
9419 compose->use_encryption = TRUE;
9421 compose->use_encryption = FALSE;
9424 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
9426 g_free(compose->privacy_system);
9428 compose->privacy_system = g_strdup(account->default_privacy_system);
9429 compose_update_privacy_system_menu_item(compose, warn);
9432 static void compose_toggle_ruler_cb(gpointer data, guint action,
9435 Compose *compose = (Compose *)data;
9437 if (GTK_CHECK_MENU_ITEM(widget)->active) {
9438 gtk_widget_show(compose->ruler_hbox);
9439 prefs_common.show_ruler = TRUE;
9441 gtk_widget_hide(compose->ruler_hbox);
9442 gtk_widget_queue_resize(compose->edit_vbox);
9443 prefs_common.show_ruler = FALSE;
9447 static void compose_attach_drag_received_cb (GtkWidget *widget,
9448 GdkDragContext *context,
9451 GtkSelectionData *data,
9456 Compose *compose = (Compose *)user_data;
9459 if (gdk_atom_name(data->type) &&
9460 !strcmp(gdk_atom_name(data->type), "text/uri-list")
9461 && gtk_drag_get_source_widget(context) !=
9462 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
9463 list = uri_list_extract_filenames((const gchar *)data->data);
9464 for (tmp = list; tmp != NULL; tmp = tmp->next) {
9465 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
9466 compose_attach_append
9467 (compose, (const gchar *)tmp->data,
9468 utf8_filename, NULL);
9469 g_free(utf8_filename);
9471 if (list) compose_changed_cb(NULL, compose);
9472 list_free_strings(list);
9474 } else if (gtk_drag_get_source_widget(context)
9475 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
9476 /* comes from our summaryview */
9477 SummaryView * summaryview = NULL;
9478 GSList * list = NULL, *cur = NULL;
9480 if (mainwindow_get_mainwindow())
9481 summaryview = mainwindow_get_mainwindow()->summaryview;
9484 list = summary_get_selected_msg_list(summaryview);
9486 for (cur = list; cur; cur = cur->next) {
9487 MsgInfo *msginfo = (MsgInfo *)cur->data;
9490 file = procmsg_get_message_file_full(msginfo,
9493 compose_attach_append(compose, (const gchar *)file,
9494 (const gchar *)file, "message/rfc822");
9502 static gboolean compose_drag_drop(GtkWidget *widget,
9503 GdkDragContext *drag_context,
9505 guint time, gpointer user_data)
9507 /* not handling this signal makes compose_insert_drag_received_cb
9512 static void compose_insert_drag_received_cb (GtkWidget *widget,
9513 GdkDragContext *drag_context,
9516 GtkSelectionData *data,
9521 Compose *compose = (Compose *)user_data;
9524 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
9526 if (gdk_atom_name(data->type) && !strcmp(gdk_atom_name(data->type), "text/uri-list")) {
9527 AlertValue val = G_ALERTDEFAULT;
9529 switch (prefs_common.compose_dnd_mode) {
9530 case COMPOSE_DND_ASK:
9531 val = alertpanel_full(_("Insert or attach?"),
9532 _("Do you want to insert the contents of the file(s) "
9533 "into the message body, or attach it to the email?"),
9534 GTK_STOCK_CANCEL, _("+_Insert"), _("_Attach"),
9535 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
9537 case COMPOSE_DND_INSERT:
9538 val = G_ALERTALTERNATE;
9540 case COMPOSE_DND_ATTACH:
9544 /* unexpected case */
9545 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
9548 if (val & G_ALERTDISABLE) {
9549 val &= ~G_ALERTDISABLE;
9550 /* remember what action to perform by default, only if we don't click Cancel */
9551 if (val == G_ALERTALTERNATE)
9552 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
9553 else if (val == G_ALERTOTHER)
9554 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
9557 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
9558 gtk_drag_finish(drag_context, FALSE, FALSE, time);
9560 } else if (val == G_ALERTOTHER) {
9561 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
9564 list = uri_list_extract_filenames((const gchar *)data->data);
9565 for (tmp = list; tmp != NULL; tmp = tmp->next) {
9566 compose_insert_file(compose, (const gchar *)tmp->data);
9568 list_free_strings(list);
9570 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9573 #if GTK_CHECK_VERSION(2, 8, 0)
9574 /* do nothing, handled by GTK */
9576 gchar *tmpfile = get_tmp_file();
9577 str_write_to_file((const gchar *)data->data, tmpfile);
9578 compose_insert_file(compose, tmpfile);
9581 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9585 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9588 static void compose_header_drag_received_cb (GtkWidget *widget,
9589 GdkDragContext *drag_context,
9592 GtkSelectionData *data,
9597 GtkEditable *entry = (GtkEditable *)user_data;
9598 gchar *email = (gchar *)data->data;
9600 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
9603 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
9604 gchar *decoded=g_new(gchar, strlen(email));
9607 email += strlen("mailto:");
9608 decode_uri(decoded, email); /* will fit */
9609 gtk_editable_delete_text(entry, 0, -1);
9610 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
9611 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9615 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9618 static void compose_toggle_return_receipt_cb(gpointer data, guint action,
9621 Compose *compose = (Compose *)data;
9623 if (GTK_CHECK_MENU_ITEM(widget)->active)
9624 compose->return_receipt = TRUE;
9626 compose->return_receipt = FALSE;
9629 static void compose_toggle_remove_refs_cb(gpointer data, guint action,
9632 Compose *compose = (Compose *)data;
9634 if (GTK_CHECK_MENU_ITEM(widget)->active)
9635 compose->remove_references = TRUE;
9637 compose->remove_references = FALSE;
9640 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
9642 ComposeHeaderEntry *headerentry)
9644 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
9645 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
9646 !(event->state & GDK_MODIFIER_MASK) &&
9647 (event->keyval == GDK_BackSpace) &&
9648 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
9649 gtk_container_remove
9650 (GTK_CONTAINER(headerentry->compose->header_table),
9651 headerentry->combo);
9652 gtk_container_remove
9653 (GTK_CONTAINER(headerentry->compose->header_table),
9654 headerentry->entry);
9655 headerentry->compose->header_list =
9656 g_slist_remove(headerentry->compose->header_list,
9658 g_free(headerentry);
9659 } else if (event->keyval == GDK_Tab) {
9660 if (headerentry->compose->header_last == headerentry) {
9661 /* Override default next focus, and give it to subject_entry
9662 * instead of notebook tabs
9664 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
9665 gtk_widget_grab_focus(headerentry->compose->subject_entry);
9672 static gboolean compose_headerentry_changed_cb(GtkWidget *entry,
9673 ComposeHeaderEntry *headerentry)
9675 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
9676 compose_create_header_entry(headerentry->compose);
9677 g_signal_handlers_disconnect_matched
9678 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
9679 0, 0, NULL, NULL, headerentry);
9681 /* Automatically scroll down */
9682 compose_show_first_last_header(headerentry->compose, FALSE);
9688 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
9690 GtkAdjustment *vadj;
9692 g_return_if_fail(compose);
9693 g_return_if_fail(GTK_IS_WIDGET(compose->header_table));
9694 g_return_if_fail(GTK_IS_VIEWPORT(compose->header_table->parent));
9696 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(compose->header_table->parent));
9697 gtk_adjustment_set_value(vadj, (show_first ? vadj->lower : vadj->upper));
9700 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
9701 const gchar *text, gint len, Compose *compose)
9703 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
9704 (G_OBJECT(compose->text), "paste_as_quotation"));
9707 g_return_if_fail(text != NULL);
9709 g_signal_handlers_block_by_func(G_OBJECT(buffer),
9710 G_CALLBACK(text_inserted),
9712 if (paste_as_quotation) {
9719 new_text = g_strndup(text, len);
9721 qmark = compose_quote_char_from_context(compose);
9723 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
9724 gtk_text_buffer_place_cursor(buffer, iter);
9726 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
9727 _("Quote format error at line %d."));
9728 quote_fmt_reset_vartable();
9730 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
9731 GINT_TO_POINTER(paste_as_quotation - 1));
9733 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
9734 gtk_text_buffer_place_cursor(buffer, iter);
9736 if (strcmp(text, "\n") || compose->automatic_break
9737 || gtk_text_iter_starts_line(iter))
9738 gtk_text_buffer_insert(buffer, iter, text, len);
9740 debug_print("insert nowrap \\n\n");
9741 gtk_text_buffer_insert_with_tags_by_name(buffer,
9742 iter, text, len, "no_join", NULL);
9746 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
9748 compose_beautify_paragraph(compose, iter, FALSE);
9750 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
9751 gtk_text_buffer_delete_mark(buffer, mark);
9753 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
9754 G_CALLBACK(text_inserted),
9756 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
9758 if (prefs_common.autosave &&
9759 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
9760 compose->draft_timeout_tag != -2 /* disabled while loading */)
9761 compose->draft_timeout_tag = g_timeout_add
9762 (500, (GtkFunction) compose_defer_auto_save_draft, compose);
9764 static gint compose_defer_auto_save_draft(Compose *compose)
9766 compose->draft_timeout_tag = -1;
9767 compose_draft_cb((gpointer)compose, COMPOSE_AUTO_SAVE, NULL);
9772 static void compose_check_all(Compose *compose)
9774 if (compose->gtkaspell)
9775 gtkaspell_check_all(compose->gtkaspell);
9778 static void compose_highlight_all(Compose *compose)
9780 if (compose->gtkaspell)
9781 gtkaspell_highlight_all(compose->gtkaspell);
9784 static void compose_check_backwards(Compose *compose)
9786 if (compose->gtkaspell)
9787 gtkaspell_check_backwards(compose->gtkaspell);
9789 GtkItemFactory *ifactory;
9790 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
9791 menu_set_sensitive(ifactory, "/Edit/Check backwards misspelled word", FALSE);
9792 menu_set_sensitive(ifactory, "/Edit/Forward to next misspelled word", FALSE);
9796 static void compose_check_forwards_go(Compose *compose)
9798 if (compose->gtkaspell)
9799 gtkaspell_check_forwards_go(compose->gtkaspell);
9801 GtkItemFactory *ifactory;
9802 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
9803 menu_set_sensitive(ifactory, "/Edit/Check backwards misspelled word", FALSE);
9804 menu_set_sensitive(ifactory, "/Edit/Forward to next misspelled word", FALSE);
9810 *\brief Guess originating forward account from MsgInfo and several
9811 * "common preference" settings. Return NULL if no guess.
9813 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
9815 PrefsAccount *account = NULL;
9817 g_return_val_if_fail(msginfo, NULL);
9818 g_return_val_if_fail(msginfo->folder, NULL);
9819 g_return_val_if_fail(msginfo->folder->prefs, NULL);
9821 if (msginfo->folder->prefs->enable_default_account)
9822 account = account_find_from_id(msginfo->folder->prefs->default_account);
9825 account = msginfo->folder->folder->account;
9827 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
9829 Xstrdup_a(to, msginfo->to, return NULL);
9830 extract_address(to);
9831 account = account_find_from_address(to);
9834 if (!account && prefs_common.forward_account_autosel) {
9836 if (!procheader_get_header_from_msginfo
9837 (msginfo, cc,sizeof cc , "Cc:")) {
9838 gchar *buf = cc + strlen("Cc:");
9839 extract_address(buf);
9840 account = account_find_from_address(buf);
9844 if (!account && prefs_common.forward_account_autosel) {
9845 gchar deliveredto[BUFFSIZE];
9846 if (!procheader_get_header_from_msginfo
9847 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
9848 gchar *buf = deliveredto + strlen("Delivered-To:");
9849 extract_address(buf);
9850 account = account_find_from_address(buf);
9857 gboolean compose_close(Compose *compose)
9861 if (!g_mutex_trylock(compose->mutex)) {
9862 /* we have to wait for the (possibly deferred by auto-save)
9863 * drafting to be done, before destroying the compose under
9865 debug_print("waiting for drafting to finish...\n");
9866 compose_allow_user_actions(compose, FALSE);
9867 g_timeout_add (500, (GSourceFunc) compose_close, compose);
9870 g_return_val_if_fail(compose, FALSE);
9871 gtkut_widget_get_uposition(compose->window, &x, &y);
9872 prefs_common.compose_x = x;
9873 prefs_common.compose_y = y;
9874 g_mutex_unlock(compose->mutex);
9875 compose_destroy(compose);
9880 * Add entry field for each address in list.
9881 * \param compose E-Mail composition object.
9882 * \param listAddress List of (formatted) E-Mail addresses.
9884 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
9889 addr = ( gchar * ) node->data;
9890 compose_entry_append( compose, addr, COMPOSE_TO );
9891 node = g_list_next( node );
9895 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
9896 guint action, gboolean opening_multiple)
9899 GSList *new_msglist = NULL;
9900 MsgInfo *tmp_msginfo = NULL;
9901 gboolean originally_enc = FALSE;
9902 Compose *compose = NULL;
9904 g_return_if_fail(msgview != NULL);
9906 g_return_if_fail(msginfo_list != NULL);
9908 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
9909 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
9910 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
9912 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
9913 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
9914 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
9915 orig_msginfo, mimeinfo);
9916 if (tmp_msginfo != NULL) {
9917 new_msglist = g_slist_append(NULL, tmp_msginfo);
9919 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
9920 tmp_msginfo->folder = orig_msginfo->folder;
9921 tmp_msginfo->msgnum = orig_msginfo->msgnum;
9922 if (orig_msginfo->tags)
9923 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
9928 if (!opening_multiple)
9929 body = messageview_get_selection(msgview);
9932 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
9933 procmsg_msginfo_free(tmp_msginfo);
9934 g_slist_free(new_msglist);
9936 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
9938 if (originally_enc) {
9939 compose_force_encryption(compose, compose->account, FALSE);
9945 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
9948 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
9949 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
9950 GSList *cur = msginfo_list;
9951 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
9952 "messages. Opening the windows "
9953 "could take some time. Do you "
9954 "want to continue?"),
9955 g_slist_length(msginfo_list));
9956 if (g_slist_length(msginfo_list) > 9
9957 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
9958 != G_ALERTALTERNATE) {
9963 /* We'll open multiple compose windows */
9964 /* let the WM place the next windows */
9965 compose_force_window_origin = FALSE;
9966 for (; cur; cur = cur->next) {
9968 tmplist.data = cur->data;
9969 tmplist.next = NULL;
9970 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
9972 compose_force_window_origin = TRUE;
9974 /* forwarding multiple mails as attachments is done via a
9975 * single compose window */
9976 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
9980 void compose_set_position(Compose *compose, gint pos)
9982 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9984 gtkut_text_view_set_position(text, pos);
9987 gboolean compose_search_string(Compose *compose,
9988 const gchar *str, gboolean case_sens)
9990 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9992 return gtkut_text_view_search_string(text, str, case_sens);
9995 gboolean compose_search_string_backward(Compose *compose,
9996 const gchar *str, gboolean case_sens)
9998 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10000 return gtkut_text_view_search_string_backward(text, str, case_sens);
10003 /* allocate a msginfo structure and populate its data from a compose data structure */
10004 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
10006 MsgInfo *newmsginfo;
10008 gchar buf[BUFFSIZE];
10010 g_return_val_if_fail( compose != NULL, NULL );
10012 newmsginfo = procmsg_msginfo_new();
10015 get_rfc822_date(buf, sizeof(buf));
10016 newmsginfo->date = g_strdup(buf);
10019 if (compose->from_name) {
10020 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
10021 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
10025 if (compose->subject_entry)
10026 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
10028 /* to, cc, reply-to, newsgroups */
10029 for (list = compose->header_list; list; list = list->next) {
10030 gchar *header = gtk_editable_get_chars(
10032 GTK_COMBO(((ComposeHeaderEntry *)list->data)->combo)->entry), 0, -1);
10033 gchar *entry = gtk_editable_get_chars(
10034 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
10036 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
10037 if ( newmsginfo->to == NULL ) {
10038 newmsginfo->to = g_strdup(entry);
10039 } else if (entry && *entry) {
10040 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
10041 g_free(newmsginfo->to);
10042 newmsginfo->to = tmp;
10045 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
10046 if ( newmsginfo->cc == NULL ) {
10047 newmsginfo->cc = g_strdup(entry);
10048 } else if (entry && *entry) {
10049 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
10050 g_free(newmsginfo->cc);
10051 newmsginfo->cc = tmp;
10054 if ( strcasecmp(header,
10055 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
10056 if ( newmsginfo->newsgroups == NULL ) {
10057 newmsginfo->newsgroups = g_strdup(entry);
10058 } else if (entry && *entry) {
10059 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
10060 g_free(newmsginfo->newsgroups);
10061 newmsginfo->newsgroups = tmp;
10069 /* other data is unset */
10075 /* update compose's dictionaries from folder dict settings */
10076 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
10077 FolderItem *folder_item)
10079 g_return_if_fail(compose != NULL);
10081 if (compose->gtkaspell && folder_item && folder_item->prefs) {
10082 FolderItemPrefs *prefs = folder_item->prefs;
10084 if (prefs->enable_default_dictionary)
10085 gtkaspell_change_dict(compose->gtkaspell,
10086 prefs->default_dictionary, FALSE);
10087 if (folder_item->prefs->enable_default_alt_dictionary)
10088 gtkaspell_change_alt_dict(compose->gtkaspell,
10089 prefs->default_alt_dictionary);
10090 if (prefs->enable_default_dictionary
10091 || prefs->enable_default_alt_dictionary)
10092 compose_spell_menu_changed(compose);