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);
549 static void compose_attach_update_label(Compose *compose);
551 static void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data);
553 static GtkItemFactoryEntry compose_popup_entries[] =
555 {N_("/_Add..."), NULL, compose_attach_cb, 0, NULL},
556 {N_("/_Remove"), NULL, compose_attach_remove_selected, 0, NULL},
557 {"/---", NULL, NULL, 0, "<Separator>"},
558 {N_("/_Properties..."), NULL, compose_attach_property, 0, NULL}
561 static GtkItemFactoryEntry compose_entries[] =
563 {N_("/_Message"), NULL, NULL, 0, "<Branch>"},
564 {N_("/_Message/S_end"), "<control>Return",
565 compose_send_cb, 0, NULL},
566 {N_("/_Message/Send _later"), "<shift><control>S",
567 compose_send_later_cb, 0, NULL},
568 {N_("/_Message/---"), NULL, NULL, 0, "<Separator>"},
569 {N_("/_Message/_Attach file"), "<control>M", compose_attach_cb, 0, NULL},
570 {N_("/_Message/_Insert file"), "<control>I", compose_insert_file_cb, 0, NULL},
571 {N_("/_Message/Insert si_gnature"), "<control>G", compose_insert_sig_cb, 0, NULL},
572 {N_("/_Message/---"), NULL, NULL, 0, "<Separator>"},
573 {N_("/_Message/_Save"),
574 "<control>S", compose_draft_cb, COMPOSE_KEEP_EDITING, NULL},
575 {N_("/_Message/---"), NULL, NULL, 0, "<Separator>"},
576 {N_("/_Message/_Close"), "<control>W", compose_close_cb, 0, NULL},
578 {N_("/_Edit"), NULL, NULL, 0, "<Branch>"},
579 {N_("/_Edit/_Undo"), "<control>Z", compose_undo_cb, 0, NULL},
580 {N_("/_Edit/_Redo"), "<control>Y", compose_redo_cb, 0, NULL},
581 {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
582 {N_("/_Edit/Cu_t"), "<control>X", compose_cut_cb, 0, NULL},
583 {N_("/_Edit/_Copy"), "<control>C", compose_copy_cb, 0, NULL},
584 {N_("/_Edit/_Paste"), "<control>V", compose_paste_cb, 0, NULL},
585 {N_("/_Edit/Special paste"), NULL, NULL, 0, "<Branch>"},
586 {N_("/_Edit/Special paste/as _quotation"),
587 NULL, compose_paste_as_quote_cb, 0, NULL},
588 {N_("/_Edit/Special paste/_wrapped"),
589 NULL, compose_paste_wrap_cb, 0, NULL},
590 {N_("/_Edit/Special paste/_unwrapped"),
591 NULL, compose_paste_no_wrap_cb, 0, NULL},
592 {N_("/_Edit/Select _all"), "<control>A", compose_allsel_cb, 0, NULL},
593 {N_("/_Edit/A_dvanced"), NULL, NULL, 0, "<Branch>"},
594 {N_("/_Edit/A_dvanced/Move a character backward"),
596 compose_advanced_action_cb,
597 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER,
599 {N_("/_Edit/A_dvanced/Move a character forward"),
601 compose_advanced_action_cb,
602 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER,
604 {N_("/_Edit/A_dvanced/Move a word backward"),
606 compose_advanced_action_cb,
607 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD,
609 {N_("/_Edit/A_dvanced/Move a word forward"),
611 compose_advanced_action_cb,
612 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD,
614 {N_("/_Edit/A_dvanced/Move to beginning of line"),
615 NULL, /* "<control>A" */
616 compose_advanced_action_cb,
617 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE,
619 {N_("/_Edit/A_dvanced/Move to end of line"),
621 compose_advanced_action_cb,
622 COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE,
624 {N_("/_Edit/A_dvanced/Move to previous line"),
626 compose_advanced_action_cb,
627 COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE,
629 {N_("/_Edit/A_dvanced/Move to next line"),
631 compose_advanced_action_cb,
632 COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE,
634 {N_("/_Edit/A_dvanced/Delete a character backward"),
636 compose_advanced_action_cb,
637 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER,
639 {N_("/_Edit/A_dvanced/Delete a character forward"),
641 compose_advanced_action_cb,
642 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER,
644 {N_("/_Edit/A_dvanced/Delete a word backward"),
645 NULL, /* "<control>W" */
646 compose_advanced_action_cb,
647 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD,
649 {N_("/_Edit/A_dvanced/Delete a word forward"),
650 NULL, /* "<alt>D", */
651 compose_advanced_action_cb,
652 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD,
654 {N_("/_Edit/A_dvanced/Delete line"),
656 compose_advanced_action_cb,
657 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE,
659 {N_("/_Edit/A_dvanced/Delete entire line"),
661 compose_advanced_action_cb,
662 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE_N,
664 {N_("/_Edit/A_dvanced/Delete to end of line"),
666 compose_advanced_action_cb,
667 COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END,
669 {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
671 "<control>F", compose_find_cb, 0, NULL},
672 {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
673 {N_("/_Edit/_Wrap current paragraph"),
674 "<control>L", compose_wrap_cb, 0, NULL},
675 {N_("/_Edit/Wrap all long _lines"),
676 "<control><alt>L", compose_wrap_cb, 1, NULL},
677 {N_("/_Edit/Aut_o wrapping"), "<shift><control>L", compose_toggle_autowrap_cb, 0, "<ToggleItem>"},
678 {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
679 {N_("/_Edit/Edit with e_xternal editor"),
680 "<shift><control>X", compose_ext_editor_cb, 0, NULL},
682 {N_("/_Spelling"), NULL, NULL, 0, "<Branch>"},
683 {N_("/_Spelling/_Check all or check selection"),
684 NULL, compose_check_all, 0, NULL},
685 {N_("/_Spelling/_Highlight all misspelled words"),
686 NULL, compose_highlight_all, 0, NULL},
687 {N_("/_Spelling/Check _backwards misspelled word"),
688 NULL, compose_check_backwards , 0, NULL},
689 {N_("/_Spelling/_Forward to next misspelled word"),
690 NULL, compose_check_forwards_go, 0, NULL},
691 {N_("/_Spelling/---"), NULL, NULL, 0, "<Separator>"},
692 {N_("/_Spelling/Options"),
693 NULL, NULL, 0, "<Branch>"},
695 {N_("/_Options"), NULL, NULL, 0, "<Branch>"},
696 {N_("/_Options/Reply _mode"), NULL, NULL, 0, "<Branch>"},
697 {N_("/_Options/Reply _mode/_Normal"), NULL, compose_reply_change_mode, COMPOSE_REPLY, "<RadioItem>"},
698 {N_("/_Options/Reply _mode/_All"), NULL, compose_reply_change_mode, COMPOSE_REPLY_TO_ALL, "/Options/Reply mode/Normal"},
699 {N_("/_Options/Reply _mode/_Sender"), NULL, compose_reply_change_mode, COMPOSE_REPLY_TO_SENDER, "/Options/Reply mode/Normal"},
700 {N_("/_Options/Reply _mode/_Mailing-list"), NULL, compose_reply_change_mode, COMPOSE_REPLY_TO_LIST, "/Options/Reply mode/Normal"},
701 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
702 {N_("/_Options/Privacy _System"), NULL, NULL, 0, "<Branch>"},
703 {N_("/_Options/Privacy _System/None"), NULL, NULL, 0, "<RadioItem>"},
704 {N_("/_Options/Si_gn"), NULL, compose_toggle_sign_cb , 0, "<ToggleItem>"},
705 {N_("/_Options/_Encrypt"), NULL, compose_toggle_encrypt_cb, 0, "<ToggleItem>"},
706 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
707 {N_("/_Options/_Priority"), NULL, NULL, 0, "<Branch>"},
708 {N_("/_Options/Priority/_Highest"), NULL, compose_set_priority_cb, PRIORITY_HIGHEST, "<RadioItem>"},
709 {N_("/_Options/Priority/Hi_gh"), NULL, compose_set_priority_cb, PRIORITY_HIGH, "/Options/Priority/Highest"},
710 {N_("/_Options/Priority/_Normal"), NULL, compose_set_priority_cb, PRIORITY_NORMAL, "/Options/Priority/Highest"},
711 {N_("/_Options/Priority/Lo_w"), NULL, compose_set_priority_cb, PRIORITY_LOW, "/Options/Priority/Highest"},
712 {N_("/_Options/Priority/_Lowest"), NULL, compose_set_priority_cb, PRIORITY_LOWEST, "/Options/Priority/Highest"},
713 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
714 {N_("/_Options/_Request Return Receipt"), NULL, compose_toggle_return_receipt_cb, 0, "<ToggleItem>"},
715 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
716 {N_("/_Options/Remo_ve references"), NULL, compose_toggle_remove_refs_cb, 0, "<ToggleItem>"},
717 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
719 #define ENC_ACTION(action) \
720 NULL, compose_set_encoding_cb, action, \
721 "/Options/Character encoding/Automatic"
723 {N_("/_Options/Character _encoding"), NULL, NULL, 0, "<Branch>"},
724 {N_("/_Options/Character _encoding/_Automatic"),
725 NULL, compose_set_encoding_cb, C_AUTO, "<RadioItem>"},
726 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
728 {N_("/_Options/Character _encoding/7bit ASCII (US-ASC_II)"),
729 ENC_ACTION(C_US_ASCII)},
730 {N_("/_Options/Character _encoding/Unicode (_UTF-8)"),
731 ENC_ACTION(C_UTF_8)},
732 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
734 {N_("/_Options/Character _encoding/Western European"), NULL, NULL, 0, "<Branch>"},
735 {N_("/_Options/Character _encoding/Western European/ISO-8859-_1"),
736 ENC_ACTION(C_ISO_8859_1)},
737 {N_("/_Options/Character _encoding/Western European/ISO-8859-15"),
738 ENC_ACTION(C_ISO_8859_15)},
739 {N_("/_Options/Character _encoding/Western European/Windows-1252"),
740 ENC_ACTION(C_WINDOWS_1252)},
742 {N_("/_Options/Character _encoding/Central European (ISO-8859-_2)"),
743 ENC_ACTION(C_ISO_8859_2)},
745 {N_("/_Options/Character _encoding/Baltic"), NULL, NULL, 0, "<Branch>"},
746 {N_("/_Options/Character _encoding/Baltic/ISO-8859-13"),
747 ENC_ACTION(C_ISO_8859_13)},
748 {N_("/_Options/Character _encoding/Baltic/ISO-8859-_4"),
749 ENC_ACTION(C_ISO_8859_4)},
751 {N_("/_Options/Character _encoding/Greek (ISO-8859-_7)"),
752 ENC_ACTION(C_ISO_8859_7)},
754 {N_("/_Options/Character _encoding/Hebrew"), NULL, NULL, 0, "<Branch>"},
755 {N_("/_Options/Character _encoding/Hebrew/ISO-8859-_8"),
756 ENC_ACTION(C_ISO_8859_8)},
757 {N_("/_Options/Character _encoding/Hebrew/Windows-1255"),
758 ENC_ACTION(C_WINDOWS_1255)},
760 {N_("/_Options/Character _encoding/Arabic"), NULL, NULL, 0, "<Branch>"},
761 {N_("/_Options/Character _encoding/Arabic/ISO-8859-_6"),
762 ENC_ACTION(C_ISO_8859_6)},
763 {N_("/_Options/Character _encoding/Arabic/Windows-1256"),
764 ENC_ACTION(C_CP1256)},
766 {N_("/_Options/Character _encoding/Turkish (ISO-8859-_9)"),
767 ENC_ACTION(C_ISO_8859_9)},
769 {N_("/_Options/Character _encoding/Cyrillic"), NULL, NULL, 0, "<Branch>"},
770 {N_("/_Options/Character _encoding/Cyrillic/ISO-8859-_5"),
771 ENC_ACTION(C_ISO_8859_5)},
772 {N_("/_Options/Character _encoding/Cyrillic/KOI8-_R"),
773 ENC_ACTION(C_KOI8_R)},
774 {N_("/_Options/Character _encoding/Cyrillic/KOI8-U"),
775 ENC_ACTION(C_KOI8_U)},
776 {N_("/_Options/Character _encoding/Cyrillic/Windows-1251"),
777 ENC_ACTION(C_WINDOWS_1251)},
779 {N_("/_Options/Character _encoding/Japanese"), NULL, NULL, 0, "<Branch>"},
780 {N_("/_Options/Character _encoding/Japanese/ISO-2022-_JP"),
781 ENC_ACTION(C_ISO_2022_JP)},
782 {N_("/_Options/Character _encoding/Japanese/ISO-2022-JP-2"),
783 ENC_ACTION(C_ISO_2022_JP_2)},
784 {N_("/_Options/Character _encoding/Japanese/_EUC-JP"),
785 ENC_ACTION(C_EUC_JP)},
786 {N_("/_Options/Character _encoding/Japanese/_Shift__JIS"),
787 ENC_ACTION(C_SHIFT_JIS)},
789 {N_("/_Options/Character _encoding/Chinese"), NULL, NULL, 0, "<Branch>"},
790 {N_("/_Options/Character _encoding/Chinese/Simplified (_GB2312)"),
791 ENC_ACTION(C_GB2312)},
792 {N_("/_Options/Character _encoding/Chinese/Simplified (GBK)"),
794 {N_("/_Options/Character _encoding/Chinese/Traditional (_Big5)"),
796 {N_("/_Options/Character _encoding/Chinese/Traditional (EUC-_TW)"),
797 ENC_ACTION(C_EUC_TW)},
799 {N_("/_Options/Character _encoding/Korean"), NULL, NULL, 0, "<Branch>"},
800 {N_("/_Options/Character _encoding/Korean/EUC-_KR"),
801 ENC_ACTION(C_EUC_KR)},
802 {N_("/_Options/Character _encoding/Korean/ISO-2022-KR"),
803 ENC_ACTION(C_ISO_2022_KR)},
805 {N_("/_Options/Character _encoding/Thai"), NULL, NULL, 0, "<Branch>"},
806 {N_("/_Options/Character _encoding/Thai/TIS-620"),
807 ENC_ACTION(C_TIS_620)},
808 {N_("/_Options/Character _encoding/Thai/Windows-874"),
809 ENC_ACTION(C_WINDOWS_874)},
811 {N_("/_Tools"), NULL, NULL, 0, "<Branch>"},
812 {N_("/_Tools/Show _ruler"), NULL, compose_toggle_ruler_cb, 0, "<ToggleItem>"},
813 {N_("/_Tools/_Address book"), "<shift><control>A", compose_address_cb , 0, NULL},
814 {N_("/_Tools/_Template"), NULL, NULL, 0, "<Branch>"},
815 {N_("/_Tools/Actio_ns"), NULL, NULL, 0, "<Branch>"},
816 {N_("/_Help"), NULL, NULL, 0, "<Branch>"},
817 {N_("/_Help/_About"), NULL, about_show, 0, NULL}
820 static GtkTargetEntry compose_mime_types[] =
822 {"text/uri-list", 0, 0},
823 {"UTF8_STRING", 0, 0},
827 static gboolean compose_put_existing_to_front(MsgInfo *info)
829 GList *compose_list = compose_get_compose_list();
833 for (elem = compose_list; elem != NULL && elem->data != NULL;
835 Compose *c = (Compose*)elem->data;
837 if (!c->targetinfo || !c->targetinfo->msgid ||
841 if (!strcmp(c->targetinfo->msgid, info->msgid)) {
842 gtkut_window_popup(c->window);
850 static GdkColor quote_color1 =
851 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
852 static GdkColor quote_color2 =
853 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
854 static GdkColor quote_color3 =
855 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
857 static GdkColor quote_bgcolor1 =
858 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
859 static GdkColor quote_bgcolor2 =
860 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
861 static GdkColor quote_bgcolor3 =
862 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
864 static GdkColor signature_color = {
871 static GdkColor uri_color = {
878 static void compose_create_tags(GtkTextView *text, Compose *compose)
880 GtkTextBuffer *buffer;
881 GdkColor black = {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
887 buffer = gtk_text_view_get_buffer(text);
889 if (prefs_common.enable_color) {
890 /* grab the quote colors, converting from an int to a GdkColor */
891 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
893 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
895 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
897 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_bgcol,
899 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_bgcol,
901 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_bgcol,
903 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
905 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
908 signature_color = quote_color1 = quote_color2 = quote_color3 =
909 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
912 if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
913 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
914 "foreground-gdk", "e_color1,
915 "paragraph-background-gdk", "e_bgcolor1,
917 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
918 "foreground-gdk", "e_color2,
919 "paragraph-background-gdk", "e_bgcolor2,
921 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
922 "foreground-gdk", "e_color3,
923 "paragraph-background-gdk", "e_bgcolor3,
926 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
927 "foreground-gdk", "e_color1,
929 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
930 "foreground-gdk", "e_color2,
932 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
933 "foreground-gdk", "e_color3,
937 compose->signature_tag = gtk_text_buffer_create_tag(buffer, "signature",
938 "foreground-gdk", &signature_color,
941 compose->uri_tag = gtk_text_buffer_create_tag(buffer, "link",
942 "foreground-gdk", &uri_color,
944 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
945 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
947 color[0] = quote_color1;
948 color[1] = quote_color2;
949 color[2] = quote_color3;
950 color[3] = quote_bgcolor1;
951 color[4] = quote_bgcolor2;
952 color[5] = quote_bgcolor3;
953 color[6] = signature_color;
954 color[7] = uri_color;
955 cmap = gdk_drawable_get_colormap(compose->window->window);
956 gdk_colormap_alloc_colors(cmap, color, 8, FALSE, TRUE, success);
958 for (i = 0; i < 8; i++) {
959 if (success[i] == FALSE) {
962 g_warning("Compose: color allocation failed.\n");
963 style = gtk_widget_get_style(GTK_WIDGET(text));
964 quote_color1 = quote_color2 = quote_color3 =
965 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 =
966 signature_color = uri_color = black;
971 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
972 GPtrArray *attach_files)
974 return compose_generic_new(account, mailto, NULL, attach_files, NULL);
977 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
979 return compose_generic_new(account, mailto, item, NULL, NULL);
982 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
984 return compose_generic_new( account, NULL, NULL, NULL, listAddress );
987 #define SCROLL_TO_CURSOR(compose) { \
988 GtkTextMark *cmark = gtk_text_buffer_get_insert( \
989 gtk_text_view_get_buffer( \
990 GTK_TEXT_VIEW(compose->text))); \
991 gtk_text_view_scroll_mark_onscreen( \
992 GTK_TEXT_VIEW(compose->text), \
996 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
997 GPtrArray *attach_files, GList *listAddress )
1000 GtkTextView *textview;
1001 GtkTextBuffer *textbuf;
1003 GtkItemFactory *ifactory;
1004 const gchar *subject_format = NULL;
1005 const gchar *body_format = NULL;
1007 if (item && item->prefs && item->prefs->enable_default_account)
1008 account = account_find_from_id(item->prefs->default_account);
1010 if (!account) account = cur_account;
1011 g_return_val_if_fail(account != NULL, NULL);
1013 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
1015 ifactory = gtk_item_factory_from_widget(compose->menubar);
1017 compose->replyinfo = NULL;
1018 compose->fwdinfo = NULL;
1020 textview = GTK_TEXT_VIEW(compose->text);
1021 textbuf = gtk_text_view_get_buffer(textview);
1022 compose_create_tags(textview, compose);
1024 undo_block(compose->undostruct);
1026 compose_set_dictionaries_from_folder_prefs(compose, item);
1029 if (account->auto_sig)
1030 compose_insert_sig(compose, FALSE);
1031 gtk_text_buffer_get_start_iter(textbuf, &iter);
1032 gtk_text_buffer_place_cursor(textbuf, &iter);
1034 if (account->protocol != A_NNTP) {
1035 if (mailto && *mailto != '\0') {
1036 compose_entries_set(compose, mailto);
1038 } else if (item && item->prefs->enable_default_to) {
1039 compose_entry_append(compose, item->prefs->default_to, COMPOSE_TO);
1040 compose_entry_mark_default_to(compose, item->prefs->default_to);
1042 if (item && item->ret_rcpt) {
1043 menu_set_active(ifactory, "/Options/Request Return Receipt", TRUE);
1047 compose_entry_append(compose, mailto, COMPOSE_NEWSGROUPS);
1048 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1049 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS);
1052 * CLAWS: just don't allow return receipt request, even if the user
1053 * may want to send an email. simple but foolproof.
1055 menu_set_sensitive(ifactory, "/Options/Request Return Receipt", FALSE);
1057 compose_add_field_list( compose, listAddress );
1059 if (item && item->prefs && item->prefs->compose_with_format) {
1060 subject_format = item->prefs->compose_subject_format;
1061 body_format = item->prefs->compose_body_format;
1062 } else if (account->compose_with_format) {
1063 subject_format = account->compose_subject_format;
1064 body_format = account->compose_body_format;
1065 } else if (prefs_common.compose_with_format) {
1066 subject_format = prefs_common.compose_subject_format;
1067 body_format = prefs_common.compose_body_format;
1070 if (subject_format || body_format) {
1071 MsgInfo* dummyinfo = NULL;
1074 && *subject_format != '\0' )
1076 gchar *subject = NULL;
1080 dummyinfo = compose_msginfo_new_from_compose(compose);
1082 /* decode \-escape sequences in the internal representation of the quote format */
1083 tmp = malloc(strlen(subject_format)+1);
1084 pref_get_unescaped_pref(tmp, subject_format);
1086 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1088 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account,
1089 compose->gtkaspell);
1091 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account);
1093 quote_fmt_scan_string(tmp);
1096 buf = quote_fmt_get_buffer();
1098 alertpanel_error(_("New message subject format error."));
1100 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1101 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1102 quote_fmt_reset_vartable();
1109 && *body_format != '\0' )
1112 GtkTextBuffer *buffer;
1113 GtkTextIter start, end;
1116 if ( dummyinfo == NULL )
1117 dummyinfo = compose_msginfo_new_from_compose(compose);
1119 text = GTK_TEXT_VIEW(compose->text);
1120 buffer = gtk_text_view_get_buffer(text);
1121 gtk_text_buffer_get_start_iter(buffer, &start);
1122 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1123 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1125 compose_quote_fmt(compose, dummyinfo,
1127 NULL, tmp, FALSE, TRUE,
1128 _("New message body format error at line %d."));
1129 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1130 quote_fmt_reset_vartable();
1135 procmsg_msginfo_free( dummyinfo );
1142 for (i = 0; i < attach_files->len; i++) {
1143 file = g_ptr_array_index(attach_files, i);
1144 compose_attach_append(compose, file, file, NULL);
1148 compose_show_first_last_header(compose, TRUE);
1150 /* Set save folder */
1151 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1152 gchar *folderidentifier;
1154 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
1155 folderidentifier = folder_item_get_identifier(item);
1156 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
1157 g_free(folderidentifier);
1160 gtk_widget_grab_focus(compose->header_last->entry);
1162 undo_unblock(compose->undostruct);
1164 if (prefs_common.auto_exteditor)
1165 compose_exec_ext_editor(compose);
1167 compose->draft_timeout_tag = -1;
1168 SCROLL_TO_CURSOR(compose);
1170 compose->modified = FALSE;
1171 compose_set_title(compose);
1175 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1176 gboolean override_pref)
1178 gchar *privacy = NULL;
1180 g_return_if_fail(compose != NULL);
1181 g_return_if_fail(account != NULL);
1183 if (override_pref == FALSE && account->default_encrypt_reply == FALSE)
1186 if (account->default_privacy_system
1187 && strlen(account->default_privacy_system)) {
1188 privacy = account->default_privacy_system;
1190 GSList *privacy_avail = privacy_get_system_ids();
1191 if (privacy_avail && g_slist_length(privacy_avail)) {
1192 privacy = (gchar *)(privacy_avail->data);
1195 if (privacy != NULL) {
1196 if (compose->privacy_system == NULL)
1197 compose->privacy_system = g_strdup(privacy);
1198 else if (*(compose->privacy_system) == '\0') {
1199 g_free(compose->privacy_system);
1200 compose->privacy_system = g_strdup(privacy);
1202 compose_update_privacy_system_menu_item(compose, FALSE);
1203 compose_use_encryption(compose, TRUE);
1207 static void compose_force_signing(Compose *compose, PrefsAccount *account)
1209 gchar *privacy = NULL;
1211 if (account->default_privacy_system
1212 && strlen(account->default_privacy_system)) {
1213 privacy = account->default_privacy_system;
1215 GSList *privacy_avail = privacy_get_system_ids();
1216 if (privacy_avail && g_slist_length(privacy_avail)) {
1217 privacy = (gchar *)(privacy_avail->data);
1220 if (privacy != NULL) {
1221 if (compose->privacy_system == NULL)
1222 compose->privacy_system = g_strdup(privacy);
1223 compose_update_privacy_system_menu_item(compose, FALSE);
1224 compose_use_signing(compose, TRUE);
1228 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1232 Compose *compose = NULL;
1233 GtkItemFactory *ifactory = NULL;
1235 g_return_val_if_fail(msginfo_list != NULL, NULL);
1237 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1238 g_return_val_if_fail(msginfo != NULL, NULL);
1240 list_len = g_slist_length(msginfo_list);
1244 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1245 FALSE, prefs_common.default_reply_list, FALSE, body);
1247 case COMPOSE_REPLY_WITH_QUOTE:
1248 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1249 FALSE, prefs_common.default_reply_list, FALSE, body);
1251 case COMPOSE_REPLY_WITHOUT_QUOTE:
1252 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1253 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1255 case COMPOSE_REPLY_TO_SENDER:
1256 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1257 FALSE, FALSE, TRUE, body);
1259 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1260 compose = compose_followup_and_reply_to(msginfo,
1261 COMPOSE_QUOTE_CHECK,
1262 FALSE, FALSE, body);
1264 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1265 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1266 FALSE, FALSE, TRUE, body);
1268 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1269 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1270 FALSE, FALSE, TRUE, NULL);
1272 case COMPOSE_REPLY_TO_ALL:
1273 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1274 TRUE, FALSE, FALSE, body);
1276 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1277 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1278 TRUE, FALSE, FALSE, body);
1280 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1281 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1282 TRUE, FALSE, FALSE, NULL);
1284 case COMPOSE_REPLY_TO_LIST:
1285 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1286 FALSE, TRUE, FALSE, body);
1288 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1289 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1290 FALSE, TRUE, FALSE, body);
1292 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1293 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1294 FALSE, TRUE, FALSE, NULL);
1296 case COMPOSE_FORWARD:
1297 if (prefs_common.forward_as_attachment) {
1298 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1301 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1305 case COMPOSE_FORWARD_INLINE:
1306 /* check if we reply to more than one Message */
1307 if (list_len == 1) {
1308 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1311 /* more messages FALL THROUGH */
1312 case COMPOSE_FORWARD_AS_ATTACH:
1313 compose = compose_forward_multiple(NULL, msginfo_list);
1315 case COMPOSE_REDIRECT:
1316 compose = compose_redirect(NULL, msginfo, FALSE);
1319 g_warning("compose_reply_mode(): invalid Compose Mode: %d\n", mode);
1322 if (compose == NULL) {
1323 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1326 ifactory = gtk_item_factory_from_widget(compose->menubar);
1328 compose->rmode = mode;
1329 switch (compose->rmode) {
1331 case COMPOSE_REPLY_WITH_QUOTE:
1332 case COMPOSE_REPLY_WITHOUT_QUOTE:
1333 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1334 debug_print("reply mode Normal\n");
1335 menu_set_active(ifactory, "/Options/Reply mode/Normal", TRUE);
1336 compose_reply_change_mode(compose, COMPOSE_REPLY, NULL); /* force update */
1338 case COMPOSE_REPLY_TO_SENDER:
1339 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1340 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1341 debug_print("reply mode Sender\n");
1342 menu_set_active(ifactory, "/Options/Reply mode/Sender", TRUE);
1344 case COMPOSE_REPLY_TO_ALL:
1345 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1346 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1347 debug_print("reply mode All\n");
1348 menu_set_active(ifactory, "/Options/Reply mode/All", TRUE);
1350 case COMPOSE_REPLY_TO_LIST:
1351 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1352 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1353 debug_print("reply mode List\n");
1354 menu_set_active(ifactory, "/Options/Reply mode/Mailing-list", TRUE);
1362 static Compose *compose_reply(MsgInfo *msginfo,
1363 ComposeQuoteMode quote_mode,
1369 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1370 to_sender, FALSE, body);
1373 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1374 ComposeQuoteMode quote_mode,
1379 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1380 to_sender, TRUE, body);
1383 static void compose_extract_original_charset(Compose *compose)
1385 MsgInfo *info = NULL;
1386 if (compose->replyinfo) {
1387 info = compose->replyinfo;
1388 } else if (compose->fwdinfo) {
1389 info = compose->fwdinfo;
1390 } else if (compose->targetinfo) {
1391 info = compose->targetinfo;
1394 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1395 MimeInfo *partinfo = mimeinfo;
1396 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1397 partinfo = procmime_mimeinfo_next(partinfo);
1399 compose->orig_charset =
1400 g_strdup(procmime_mimeinfo_get_parameter(
1401 partinfo, "charset"));
1403 procmime_mimeinfo_free_all(mimeinfo);
1407 #define SIGNAL_BLOCK(buffer) { \
1408 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1409 G_CALLBACK(compose_changed_cb), \
1411 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1412 G_CALLBACK(text_inserted), \
1416 #define SIGNAL_UNBLOCK(buffer) { \
1417 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1418 G_CALLBACK(compose_changed_cb), \
1420 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1421 G_CALLBACK(text_inserted), \
1425 static Compose *compose_generic_reply(MsgInfo *msginfo,
1426 ComposeQuoteMode quote_mode,
1427 gboolean to_all, gboolean to_ml,
1429 gboolean followup_and_reply_to,
1432 GtkItemFactory *ifactory;
1434 PrefsAccount *account = NULL;
1435 GtkTextView *textview;
1436 GtkTextBuffer *textbuf;
1437 gboolean quote = FALSE;
1438 const gchar *qmark = NULL;
1439 const gchar *body_fmt = NULL;
1441 g_return_val_if_fail(msginfo != NULL, NULL);
1442 g_return_val_if_fail(msginfo->folder != NULL, NULL);
1444 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1446 g_return_val_if_fail(account != NULL, NULL);
1448 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1450 compose->updating = TRUE;
1452 ifactory = gtk_item_factory_from_widget(compose->menubar);
1454 menu_set_active(ifactory, "/Options/Remove references", FALSE);
1455 menu_set_sensitive(ifactory, "/Options/Remove references", TRUE);
1457 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1458 if (!compose->replyinfo)
1459 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1461 compose_extract_original_charset(compose);
1463 if (msginfo->folder && msginfo->folder->ret_rcpt)
1464 menu_set_active(ifactory, "/Options/Request Return Receipt", TRUE);
1466 /* Set save folder */
1467 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1468 gchar *folderidentifier;
1470 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1471 folderidentifier = folder_item_get_identifier(msginfo->folder);
1472 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
1473 g_free(folderidentifier);
1476 if (compose_parse_header(compose, msginfo) < 0) return NULL;
1478 textview = (GTK_TEXT_VIEW(compose->text));
1479 textbuf = gtk_text_view_get_buffer(textview);
1480 compose_create_tags(textview, compose);
1482 undo_block(compose->undostruct);
1484 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1487 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1488 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1489 /* use the reply format of folder (if enabled), or the account's one
1490 (if enabled) or fallback to the global reply format, which is always
1491 enabled (even if empty), and use the relevant quotemark */
1493 if (msginfo->folder && msginfo->folder->prefs &&
1494 msginfo->folder->prefs->reply_with_format) {
1495 qmark = msginfo->folder->prefs->reply_quotemark;
1496 body_fmt = msginfo->folder->prefs->reply_body_format;
1498 } else if (account->reply_with_format) {
1499 qmark = account->reply_quotemark;
1500 body_fmt = account->reply_body_format;
1503 qmark = prefs_common.quotemark;
1504 body_fmt = prefs_common.quotefmt;
1509 /* empty quotemark is not allowed */
1510 if (qmark == NULL || *qmark == '\0')
1512 compose_quote_fmt(compose, compose->replyinfo,
1513 body_fmt, qmark, body, FALSE, TRUE,
1514 _("Message reply format error at line %d."));
1515 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1516 quote_fmt_reset_vartable();
1519 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1520 compose_force_encryption(compose, account, FALSE);
1523 SIGNAL_BLOCK(textbuf);
1525 if (account->auto_sig)
1526 compose_insert_sig(compose, FALSE);
1528 compose_wrap_all(compose);
1530 SIGNAL_UNBLOCK(textbuf);
1532 gtk_widget_grab_focus(compose->text);
1534 undo_unblock(compose->undostruct);
1536 if (prefs_common.auto_exteditor)
1537 compose_exec_ext_editor(compose);
1539 compose->modified = FALSE;
1540 compose_set_title(compose);
1542 compose->updating = FALSE;
1543 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1544 SCROLL_TO_CURSOR(compose);
1546 if (compose->deferred_destroy) {
1547 compose_destroy(compose);
1554 #define INSERT_FW_HEADER(var, hdr) \
1555 if (msginfo->var && *msginfo->var) { \
1556 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1557 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1558 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1561 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1562 gboolean as_attach, const gchar *body,
1563 gboolean no_extedit,
1567 GtkTextView *textview;
1568 GtkTextBuffer *textbuf;
1571 g_return_val_if_fail(msginfo != NULL, NULL);
1572 g_return_val_if_fail(msginfo->folder != NULL, NULL);
1575 !(account = compose_guess_forward_account_from_msginfo
1577 account = cur_account;
1579 compose = compose_create(account, msginfo->folder, COMPOSE_FORWARD, batch);
1581 compose->updating = TRUE;
1582 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1583 if (!compose->fwdinfo)
1584 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1586 compose_extract_original_charset(compose);
1588 if (msginfo->subject && *msginfo->subject) {
1589 gchar *buf, *buf2, *p;
1591 buf = p = g_strdup(msginfo->subject);
1592 p += subject_get_prefix_length(p);
1593 memmove(buf, p, strlen(p) + 1);
1595 buf2 = g_strdup_printf("Fw: %s", buf);
1596 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1602 textview = GTK_TEXT_VIEW(compose->text);
1603 textbuf = gtk_text_view_get_buffer(textview);
1604 compose_create_tags(textview, compose);
1606 undo_block(compose->undostruct);
1610 msgfile = procmsg_get_message_file(msginfo);
1611 if (!is_file_exist(msgfile))
1612 g_warning("%s: file not exist\n", msgfile);
1614 compose_attach_append(compose, msgfile, msgfile,
1619 const gchar *qmark = NULL;
1620 const gchar *body_fmt = prefs_common.fw_quotefmt;
1621 MsgInfo *full_msginfo;
1623 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1625 full_msginfo = procmsg_msginfo_copy(msginfo);
1627 /* use the forward format of folder (if enabled), or the account's one
1628 (if enabled) or fallback to the global forward format, which is always
1629 enabled (even if empty), and use the relevant quotemark */
1630 if (msginfo->folder && msginfo->folder->prefs &&
1631 msginfo->folder->prefs->forward_with_format) {
1632 qmark = msginfo->folder->prefs->forward_quotemark;
1633 body_fmt = msginfo->folder->prefs->forward_body_format;
1635 } else if (account->forward_with_format) {
1636 qmark = account->forward_quotemark;
1637 body_fmt = account->forward_body_format;
1640 qmark = prefs_common.fw_quotemark;
1641 body_fmt = prefs_common.fw_quotefmt;
1644 /* empty quotemark is not allowed */
1645 if (qmark == NULL || *qmark == '\0')
1648 compose_quote_fmt(compose, full_msginfo,
1649 body_fmt, qmark, body, FALSE, TRUE,
1650 _("Message forward format error at line %d."));
1651 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1652 quote_fmt_reset_vartable();
1653 compose_attach_parts(compose, msginfo);
1655 procmsg_msginfo_free(full_msginfo);
1658 SIGNAL_BLOCK(textbuf);
1660 if (account->auto_sig)
1661 compose_insert_sig(compose, FALSE);
1663 compose_wrap_all(compose);
1665 SIGNAL_UNBLOCK(textbuf);
1667 gtk_text_buffer_get_start_iter(textbuf, &iter);
1668 gtk_text_buffer_place_cursor(textbuf, &iter);
1670 gtk_widget_grab_focus(compose->header_last->entry);
1672 if (!no_extedit && prefs_common.auto_exteditor)
1673 compose_exec_ext_editor(compose);
1676 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1677 gchar *folderidentifier;
1679 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1680 folderidentifier = folder_item_get_identifier(msginfo->folder);
1681 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
1682 g_free(folderidentifier);
1685 undo_unblock(compose->undostruct);
1687 compose->modified = FALSE;
1688 compose_set_title(compose);
1690 compose->updating = FALSE;
1691 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1692 SCROLL_TO_CURSOR(compose);
1694 if (compose->deferred_destroy) {
1695 compose_destroy(compose);
1702 #undef INSERT_FW_HEADER
1704 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1707 GtkTextView *textview;
1708 GtkTextBuffer *textbuf;
1712 gboolean single_mail = TRUE;
1714 g_return_val_if_fail(msginfo_list != NULL, NULL);
1716 if (g_slist_length(msginfo_list) > 1)
1717 single_mail = FALSE;
1719 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1720 if (((MsgInfo *)msginfo->data)->folder == NULL)
1723 /* guess account from first selected message */
1725 !(account = compose_guess_forward_account_from_msginfo
1726 (msginfo_list->data)))
1727 account = cur_account;
1729 g_return_val_if_fail(account != NULL, NULL);
1731 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1732 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1733 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1736 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1738 compose->updating = TRUE;
1740 textview = GTK_TEXT_VIEW(compose->text);
1741 textbuf = gtk_text_view_get_buffer(textview);
1742 compose_create_tags(textview, compose);
1744 undo_block(compose->undostruct);
1745 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1746 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
1748 if (!is_file_exist(msgfile))
1749 g_warning("%s: file not exist\n", msgfile);
1751 compose_attach_append(compose, msgfile, msgfile,
1757 MsgInfo *info = (MsgInfo *)msginfo_list->data;
1758 if (info->subject && *info->subject) {
1759 gchar *buf, *buf2, *p;
1761 buf = p = g_strdup(info->subject);
1762 p += subject_get_prefix_length(p);
1763 memmove(buf, p, strlen(p) + 1);
1765 buf2 = g_strdup_printf("Fw: %s", buf);
1766 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1772 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
1773 _("Fw: multiple emails"));
1776 SIGNAL_BLOCK(textbuf);
1778 if (account->auto_sig)
1779 compose_insert_sig(compose, FALSE);
1781 compose_wrap_all(compose);
1783 SIGNAL_UNBLOCK(textbuf);
1785 gtk_text_buffer_get_start_iter(textbuf, &iter);
1786 gtk_text_buffer_place_cursor(textbuf, &iter);
1788 gtk_widget_grab_focus(compose->header_last->entry);
1789 undo_unblock(compose->undostruct);
1790 compose->modified = FALSE;
1791 compose_set_title(compose);
1793 compose->updating = FALSE;
1794 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1795 SCROLL_TO_CURSOR(compose);
1797 if (compose->deferred_destroy) {
1798 compose_destroy(compose);
1805 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
1807 GtkTextIter start = *iter;
1808 GtkTextIter end_iter;
1809 int start_pos = gtk_text_iter_get_offset(&start);
1811 if (!compose->account->sig_sep)
1814 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
1815 start_pos+strlen(compose->account->sig_sep));
1817 /* check sig separator */
1818 str = gtk_text_iter_get_text(&start, &end_iter);
1819 if (!strcmp(str, compose->account->sig_sep)) {
1821 /* check end of line (\n) */
1822 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
1823 start_pos+strlen(compose->account->sig_sep));
1824 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
1825 start_pos+strlen(compose->account->sig_sep)+1);
1826 tmp = gtk_text_iter_get_text(&start, &end_iter);
1827 if (!strcmp(tmp,"\n")) {
1839 static void compose_colorize_signature(Compose *compose)
1841 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
1843 GtkTextIter end_iter;
1844 gtk_text_buffer_get_start_iter(buffer, &iter);
1845 while (gtk_text_iter_forward_line(&iter))
1846 if (compose_is_sig_separator(compose, buffer, &iter)) {
1847 gtk_text_buffer_get_end_iter(buffer, &end_iter);
1848 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
1852 #define BLOCK_WRAP() { \
1853 prev_autowrap = compose->autowrap; \
1854 buffer = gtk_text_view_get_buffer( \
1855 GTK_TEXT_VIEW(compose->text)); \
1856 compose->autowrap = FALSE; \
1858 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1859 G_CALLBACK(compose_changed_cb), \
1861 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1862 G_CALLBACK(text_inserted), \
1865 #define UNBLOCK_WRAP() { \
1866 compose->autowrap = prev_autowrap; \
1867 if (compose->autowrap) { \
1868 gint old = compose->draft_timeout_tag; \
1869 compose->draft_timeout_tag = -2; \
1870 compose_wrap_all(compose); \
1871 compose->draft_timeout_tag = old; \
1874 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1875 G_CALLBACK(compose_changed_cb), \
1877 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1878 G_CALLBACK(text_inserted), \
1882 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
1884 Compose *compose = NULL;
1885 PrefsAccount *account = NULL;
1886 GtkTextView *textview;
1887 GtkTextBuffer *textbuf;
1891 gchar buf[BUFFSIZE];
1892 gboolean use_signing = FALSE;
1893 gboolean use_encryption = FALSE;
1894 gchar *privacy_system = NULL;
1895 int priority = PRIORITY_NORMAL;
1896 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
1898 g_return_val_if_fail(msginfo != NULL, NULL);
1899 g_return_val_if_fail(msginfo->folder != NULL, NULL);
1901 if (compose_put_existing_to_front(msginfo)) {
1905 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
1906 folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
1907 gchar queueheader_buf[BUFFSIZE];
1910 /* Select Account from queue headers */
1911 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1912 sizeof(queueheader_buf), "X-Claws-Account-Id:")) {
1913 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
1914 account = account_find_from_id(id);
1916 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1917 sizeof(queueheader_buf), "X-Sylpheed-Account-Id:")) {
1918 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
1919 account = account_find_from_id(id);
1921 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1922 sizeof(queueheader_buf), "NAID:")) {
1923 id = atoi(&queueheader_buf[strlen("NAID:")]);
1924 account = account_find_from_id(id);
1926 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1927 sizeof(queueheader_buf), "MAID:")) {
1928 id = atoi(&queueheader_buf[strlen("MAID:")]);
1929 account = account_find_from_id(id);
1931 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1932 sizeof(queueheader_buf), "S:")) {
1933 account = account_find_from_address(queueheader_buf);
1935 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1936 sizeof(queueheader_buf), "X-Claws-Sign:")) {
1937 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
1938 use_signing = param;
1941 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1942 sizeof(queueheader_buf), "X-Sylpheed-Sign:")) {
1943 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
1944 use_signing = param;
1947 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1948 sizeof(queueheader_buf), "X-Claws-Encrypt:")) {
1949 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
1950 use_encryption = param;
1952 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1953 sizeof(queueheader_buf), "X-Sylpheed-Encrypt:")) {
1954 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
1955 use_encryption = param;
1957 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1958 sizeof(queueheader_buf), "X-Claws-Privacy-System:")) {
1959 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
1961 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1962 sizeof(queueheader_buf), "X-Sylpheed-Privacy-System:")) {
1963 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
1965 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1966 sizeof(queueheader_buf), "X-Priority: ")) {
1967 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
1970 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1971 sizeof(queueheader_buf), "RMID:")) {
1972 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
1973 if (tokens[0] && tokens[1] && tokens[2]) {
1974 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
1975 if (orig_item != NULL) {
1976 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
1981 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1982 sizeof(queueheader_buf), "FMID:")) {
1983 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
1984 if (tokens[0] && tokens[1] && tokens[2]) {
1985 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
1986 if (orig_item != NULL) {
1987 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
1993 account = msginfo->folder->folder->account;
1996 if (!account && prefs_common.reedit_account_autosel) {
1997 gchar from[BUFFSIZE];
1998 if (!procheader_get_header_from_msginfo(msginfo, from, sizeof(from), "FROM:")) {
1999 extract_address(from);
2000 account = account_find_from_address(from);
2004 account = cur_account;
2006 g_return_val_if_fail(account != NULL, NULL);
2008 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2010 compose->replyinfo = replyinfo;
2011 compose->fwdinfo = fwdinfo;
2013 compose->updating = TRUE;
2014 compose->priority = priority;
2016 if (privacy_system != NULL) {
2017 compose->privacy_system = privacy_system;
2018 compose_use_signing(compose, use_signing);
2019 compose_use_encryption(compose, use_encryption);
2020 compose_update_privacy_system_menu_item(compose, FALSE);
2022 activate_privacy_system(compose, account, FALSE);
2025 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2027 compose_extract_original_charset(compose);
2029 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2030 folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
2031 gchar queueheader_buf[BUFFSIZE];
2033 /* Set message save folder */
2034 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "SCF:")) {
2037 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2038 gtk_editable_delete_text(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
2039 gtk_editable_insert_text(GTK_EDITABLE(compose->savemsg_entry), &queueheader_buf[4], strlen(&queueheader_buf[4]), &startpos);
2041 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "RRCPT:")) {
2042 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2044 GtkItemFactory *ifactory;
2045 ifactory = gtk_item_factory_from_widget(compose->menubar);
2046 menu_set_active(ifactory, "/Options/Request Return Receipt", TRUE);
2051 if (compose_parse_header(compose, msginfo) < 0) {
2052 compose->updating = FALSE;
2053 compose_destroy(compose);
2056 compose_reedit_set_entry(compose, msginfo);
2058 textview = GTK_TEXT_VIEW(compose->text);
2059 textbuf = gtk_text_view_get_buffer(textview);
2060 compose_create_tags(textview, compose);
2062 mark = gtk_text_buffer_get_insert(textbuf);
2063 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2065 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2066 G_CALLBACK(compose_changed_cb),
2069 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2070 fp = procmime_get_first_encrypted_text_content(msginfo);
2072 compose_force_encryption(compose, account, TRUE);
2075 fp = procmime_get_first_text_content(msginfo);
2078 g_warning("Can't get text part\n");
2082 gboolean prev_autowrap = compose->autowrap;
2083 GtkTextBuffer *buffer = textbuf;
2085 while (fgets(buf, sizeof(buf), fp) != NULL) {
2087 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2093 compose_attach_parts(compose, msginfo);
2095 compose_colorize_signature(compose);
2097 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2098 G_CALLBACK(compose_changed_cb),
2101 gtk_widget_grab_focus(compose->text);
2103 if (prefs_common.auto_exteditor) {
2104 compose_exec_ext_editor(compose);
2106 compose->modified = FALSE;
2107 compose_set_title(compose);
2109 compose->updating = FALSE;
2110 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2111 SCROLL_TO_CURSOR(compose);
2113 if (compose->deferred_destroy) {
2114 compose_destroy(compose);
2118 compose->sig_str = compose_get_signature_str(compose);
2123 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2128 GtkItemFactory *ifactory;
2131 g_return_val_if_fail(msginfo != NULL, NULL);
2134 account = account_get_reply_account(msginfo,
2135 prefs_common.reply_account_autosel);
2136 g_return_val_if_fail(account != NULL, NULL);
2138 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2140 compose->updating = TRUE;
2142 ifactory = gtk_item_factory_from_widget(compose->menubar);
2143 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2144 compose->replyinfo = NULL;
2145 compose->fwdinfo = NULL;
2147 compose_show_first_last_header(compose, TRUE);
2149 gtk_widget_grab_focus(compose->header_last->entry);
2151 filename = procmsg_get_message_file(msginfo);
2153 if (filename == NULL) {
2154 compose->updating = FALSE;
2155 compose_destroy(compose);
2160 compose->redirect_filename = filename;
2162 /* Set save folder */
2163 item = msginfo->folder;
2164 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2165 gchar *folderidentifier;
2167 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
2168 folderidentifier = folder_item_get_identifier(item);
2169 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
2170 g_free(folderidentifier);
2173 compose_attach_parts(compose, msginfo);
2175 if (msginfo->subject)
2176 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2178 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2180 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2181 _("Message redirect format error at line %d."));
2182 quote_fmt_reset_vartable();
2183 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2185 compose_colorize_signature(compose);
2187 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
2188 menu_set_sensitive(ifactory, "/Add...", FALSE);
2189 menu_set_sensitive(ifactory, "/Remove", FALSE);
2190 menu_set_sensitive(ifactory, "/Properties...", FALSE);
2192 ifactory = gtk_item_factory_from_widget(compose->menubar);
2193 menu_set_sensitive(ifactory, "/Message/Save", FALSE);
2194 menu_set_sensitive(ifactory, "/Message/Insert file", FALSE);
2195 menu_set_sensitive(ifactory, "/Message/Attach file", FALSE);
2196 menu_set_sensitive(ifactory, "/Message/Insert signature", FALSE);
2197 menu_set_sensitive(ifactory, "/Edit", FALSE);
2198 menu_set_sensitive(ifactory, "/Options", FALSE);
2199 menu_set_sensitive(ifactory, "/Tools/Show ruler", FALSE);
2200 menu_set_sensitive(ifactory, "/Tools/Actions", FALSE);
2202 if (compose->toolbar->draft_btn)
2203 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2204 if (compose->toolbar->insert_btn)
2205 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2206 if (compose->toolbar->attach_btn)
2207 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2208 if (compose->toolbar->sig_btn)
2209 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2210 if (compose->toolbar->exteditor_btn)
2211 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2212 if (compose->toolbar->linewrap_current_btn)
2213 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2214 if (compose->toolbar->linewrap_all_btn)
2215 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2217 compose->modified = FALSE;
2218 compose_set_title(compose);
2219 compose->updating = FALSE;
2220 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2221 SCROLL_TO_CURSOR(compose);
2223 if (compose->deferred_destroy) {
2224 compose_destroy(compose);
2231 GList *compose_get_compose_list(void)
2233 return compose_list;
2236 void compose_entry_append(Compose *compose, const gchar *address,
2237 ComposeEntryType type)
2239 const gchar *header;
2241 gboolean in_quote = FALSE;
2242 if (!address || *address == '\0') return;
2249 header = N_("Bcc:");
2251 case COMPOSE_REPLYTO:
2252 header = N_("Reply-To:");
2254 case COMPOSE_NEWSGROUPS:
2255 header = N_("Newsgroups:");
2257 case COMPOSE_FOLLOWUPTO:
2258 header = N_( "Followup-To:");
2265 header = prefs_common_translated_header_name(header);
2267 cur = begin = (gchar *)address;
2269 /* we separate the line by commas, but not if we're inside a quoted
2271 while (*cur != '\0') {
2273 in_quote = !in_quote;
2274 if (*cur == ',' && !in_quote) {
2275 gchar *tmp = g_strdup(begin);
2277 tmp[cur-begin]='\0';
2280 while (*tmp == ' ' || *tmp == '\t')
2282 compose_add_header_entry(compose, header, tmp);
2289 gchar *tmp = g_strdup(begin);
2291 tmp[cur-begin]='\0';
2294 while (*tmp == ' ' || *tmp == '\t')
2296 compose_add_header_entry(compose, header, tmp);
2301 static void compose_entry_mark_default_to(Compose *compose, const gchar *mailto)
2303 static GdkColor yellow;
2304 static GdkColor black;
2305 static gboolean yellow_initialised = FALSE;
2309 if (!yellow_initialised) {
2310 gdk_color_parse("#f5f6be", &yellow);
2311 gdk_color_parse("#000000", &black);
2312 yellow_initialised = gdk_colormap_alloc_color(
2313 gdk_colormap_get_system(), &yellow, FALSE, TRUE);
2314 yellow_initialised &= gdk_colormap_alloc_color(
2315 gdk_colormap_get_system(), &black, FALSE, TRUE);
2318 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2319 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2320 if (gtk_entry_get_text(entry) &&
2321 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2322 if (yellow_initialised) {
2323 gtk_widget_modify_base(
2324 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2325 GTK_STATE_NORMAL, &yellow);
2326 gtk_widget_modify_text(
2327 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2328 GTK_STATE_NORMAL, &black);
2334 void compose_toolbar_cb(gint action, gpointer data)
2336 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2337 Compose *compose = (Compose*)toolbar_item->parent;
2339 g_return_if_fail(compose != NULL);
2343 compose_send_cb(compose, 0, NULL);
2346 compose_send_later_cb(compose, 0, NULL);
2349 compose_draft_cb(compose, COMPOSE_QUIT_EDITING, NULL);
2352 compose_insert_file_cb(compose, 0, NULL);
2355 compose_attach_cb(compose, 0, NULL);
2358 compose_insert_sig(compose, FALSE);
2361 compose_ext_editor_cb(compose, 0, NULL);
2363 case A_LINEWRAP_CURRENT:
2364 compose_beautify_paragraph(compose, NULL, TRUE);
2366 case A_LINEWRAP_ALL:
2367 compose_wrap_all_full(compose, TRUE);
2370 compose_address_cb(compose, 0, NULL);
2373 case A_CHECK_SPELLING:
2374 compose_check_all(compose);
2382 static void compose_entries_set(Compose *compose, const gchar *mailto)
2387 gchar *subject = NULL;
2391 gchar **attach = NULL;
2393 scan_mailto_url(mailto, &to, &cc, &bcc, &subject, &body, &attach);
2396 compose_entry_append(compose, to, COMPOSE_TO);
2398 compose_entry_append(compose, cc, COMPOSE_CC);
2400 compose_entry_append(compose, bcc, COMPOSE_BCC);
2402 if (!g_utf8_validate (subject, -1, NULL)) {
2403 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2404 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2407 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2411 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2412 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2415 gboolean prev_autowrap = compose->autowrap;
2417 compose->autowrap = FALSE;
2419 mark = gtk_text_buffer_get_insert(buffer);
2420 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2422 if (!g_utf8_validate (body, -1, NULL)) {
2423 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2424 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2427 gtk_text_buffer_insert(buffer, &iter, body, -1);
2429 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2431 compose->autowrap = prev_autowrap;
2432 if (compose->autowrap)
2433 compose_wrap_all(compose);
2437 gint i = 0, att = 0;
2438 gchar *warn_files = NULL;
2439 while (attach[i] != NULL) {
2440 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2441 if (utf8_filename) {
2442 if (compose_attach_append(compose, attach[i], utf8_filename, NULL)) {
2443 gchar *tmp = g_strdup_printf("%s%s\n",
2444 warn_files?warn_files:"",
2450 g_free(utf8_filename);
2452 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2457 alertpanel_notice(ngettext(
2458 "The following file has been attached: \n%s",
2459 "The following files have been attached: \n%s", att), warn_files);
2471 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2473 static HeaderEntry hentry[] = {{"Reply-To:", NULL, TRUE},
2474 {"Cc:", NULL, TRUE},
2475 {"References:", NULL, FALSE},
2476 {"Bcc:", NULL, TRUE},
2477 {"Newsgroups:", NULL, TRUE},
2478 {"Followup-To:", NULL, TRUE},
2479 {"List-Post:", NULL, FALSE},
2480 {"X-Priority:", NULL, FALSE},
2481 {NULL, NULL, FALSE}};
2497 g_return_val_if_fail(msginfo != NULL, -1);
2499 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2500 procheader_get_header_fields(fp, hentry);
2503 if (hentry[H_REPLY_TO].body != NULL) {
2504 if (hentry[H_REPLY_TO].body[0] != '\0') {
2506 conv_unmime_header(hentry[H_REPLY_TO].body,
2509 g_free(hentry[H_REPLY_TO].body);
2510 hentry[H_REPLY_TO].body = NULL;
2512 if (hentry[H_CC].body != NULL) {
2513 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL);
2514 g_free(hentry[H_CC].body);
2515 hentry[H_CC].body = NULL;
2517 if (hentry[H_REFERENCES].body != NULL) {
2518 if (compose->mode == COMPOSE_REEDIT)
2519 compose->references = hentry[H_REFERENCES].body;
2521 compose->references = compose_parse_references
2522 (hentry[H_REFERENCES].body, msginfo->msgid);
2523 g_free(hentry[H_REFERENCES].body);
2525 hentry[H_REFERENCES].body = NULL;
2527 if (hentry[H_BCC].body != NULL) {
2528 if (compose->mode == COMPOSE_REEDIT)
2530 conv_unmime_header(hentry[H_BCC].body, NULL);
2531 g_free(hentry[H_BCC].body);
2532 hentry[H_BCC].body = NULL;
2534 if (hentry[H_NEWSGROUPS].body != NULL) {
2535 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2536 hentry[H_NEWSGROUPS].body = NULL;
2538 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2539 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2540 compose->followup_to =
2541 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2544 g_free(hentry[H_FOLLOWUP_TO].body);
2545 hentry[H_FOLLOWUP_TO].body = NULL;
2547 if (hentry[H_LIST_POST].body != NULL) {
2550 extract_address(hentry[H_LIST_POST].body);
2551 if (hentry[H_LIST_POST].body[0] != '\0') {
2552 scan_mailto_url(hentry[H_LIST_POST].body,
2553 &to, NULL, NULL, NULL, NULL, NULL);
2555 g_free(compose->ml_post);
2556 compose->ml_post = to;
2559 g_free(hentry[H_LIST_POST].body);
2560 hentry[H_LIST_POST].body = NULL;
2563 /* CLAWS - X-Priority */
2564 if (compose->mode == COMPOSE_REEDIT)
2565 if (hentry[H_X_PRIORITY].body != NULL) {
2568 priority = atoi(hentry[H_X_PRIORITY].body);
2569 g_free(hentry[H_X_PRIORITY].body);
2571 hentry[H_X_PRIORITY].body = NULL;
2573 if (priority < PRIORITY_HIGHEST ||
2574 priority > PRIORITY_LOWEST)
2575 priority = PRIORITY_NORMAL;
2577 compose->priority = priority;
2580 if (compose->mode == COMPOSE_REEDIT) {
2581 if (msginfo->inreplyto && *msginfo->inreplyto)
2582 compose->inreplyto = g_strdup(msginfo->inreplyto);
2586 if (msginfo->msgid && *msginfo->msgid)
2587 compose->inreplyto = g_strdup(msginfo->msgid);
2589 if (!compose->references) {
2590 if (msginfo->msgid && *msginfo->msgid) {
2591 if (msginfo->inreplyto && *msginfo->inreplyto)
2592 compose->references =
2593 g_strdup_printf("<%s>\n\t<%s>",
2597 compose->references =
2598 g_strconcat("<", msginfo->msgid, ">",
2600 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
2601 compose->references =
2602 g_strconcat("<", msginfo->inreplyto, ">",
2610 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
2612 GSList *ref_id_list, *cur;
2616 ref_id_list = references_list_append(NULL, ref);
2617 if (!ref_id_list) return NULL;
2618 if (msgid && *msgid)
2619 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
2624 for (cur = ref_id_list; cur != NULL; cur = cur->next)
2625 /* "<" + Message-ID + ">" + CR+LF+TAB */
2626 len += strlen((gchar *)cur->data) + 5;
2628 if (len > MAX_REFERENCES_LEN) {
2629 /* remove second message-ID */
2630 if (ref_id_list && ref_id_list->next &&
2631 ref_id_list->next->next) {
2632 g_free(ref_id_list->next->data);
2633 ref_id_list = g_slist_remove
2634 (ref_id_list, ref_id_list->next->data);
2636 slist_free_strings(ref_id_list);
2637 g_slist_free(ref_id_list);
2644 new_ref = g_string_new("");
2645 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
2646 if (new_ref->len > 0)
2647 g_string_append(new_ref, "\n\t");
2648 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
2651 slist_free_strings(ref_id_list);
2652 g_slist_free(ref_id_list);
2654 new_ref_str = new_ref->str;
2655 g_string_free(new_ref, FALSE);
2660 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
2661 const gchar *fmt, const gchar *qmark,
2662 const gchar *body, gboolean rewrap,
2663 gboolean need_unescape,
2664 const gchar *err_msg)
2666 MsgInfo* dummyinfo = NULL;
2667 gchar *quote_str = NULL;
2669 gboolean prev_autowrap;
2670 const gchar *trimmed_body = body;
2671 gint cursor_pos = -1;
2672 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2673 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2678 SIGNAL_BLOCK(buffer);
2681 dummyinfo = compose_msginfo_new_from_compose(compose);
2682 msginfo = dummyinfo;
2685 if (qmark != NULL) {
2687 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
2688 compose->gtkaspell);
2690 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
2692 quote_fmt_scan_string(qmark);
2695 buf = quote_fmt_get_buffer();
2697 alertpanel_error(_("Quote mark format error."));
2699 Xstrdup_a(quote_str, buf, goto error)
2702 if (fmt && *fmt != '\0') {
2705 while (*trimmed_body == '\n')
2709 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account,
2710 compose->gtkaspell);
2712 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account);
2714 if (need_unescape) {
2717 /* decode \-escape sequences in the internal representation of the quote format */
2718 tmp = malloc(strlen(fmt)+1);
2719 pref_get_unescaped_pref(tmp, fmt);
2720 quote_fmt_scan_string(tmp);
2724 quote_fmt_scan_string(fmt);
2728 buf = quote_fmt_get_buffer();
2730 gint line = quote_fmt_get_line();
2731 alertpanel_error(err_msg, line);
2737 prev_autowrap = compose->autowrap;
2738 compose->autowrap = FALSE;
2740 mark = gtk_text_buffer_get_insert(buffer);
2741 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2742 if (g_utf8_validate(buf, -1, NULL)) {
2743 gtk_text_buffer_insert(buffer, &iter, buf, -1);
2745 gchar *tmpout = NULL;
2746 tmpout = conv_codeset_strdup
2747 (buf, conv_get_locale_charset_str_no_utf8(),
2749 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
2751 tmpout = g_malloc(strlen(buf)*2+1);
2752 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
2754 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
2758 cursor_pos = quote_fmt_get_cursor_pos();
2759 compose->set_cursor_pos = cursor_pos;
2760 if (cursor_pos == -1) {
2763 gtk_text_buffer_get_start_iter(buffer, &iter);
2764 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
2765 gtk_text_buffer_place_cursor(buffer, &iter);
2767 compose->autowrap = prev_autowrap;
2768 if (compose->autowrap && rewrap)
2769 compose_wrap_all(compose);
2776 SIGNAL_UNBLOCK(buffer);
2778 procmsg_msginfo_free( dummyinfo );
2783 /* if ml_post is of type addr@host and from is of type
2784 * addr-anything@host, return TRUE
2786 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
2788 gchar *left_ml = NULL;
2789 gchar *right_ml = NULL;
2790 gchar *left_from = NULL;
2791 gchar *right_from = NULL;
2792 gboolean result = FALSE;
2794 if (!ml_post || !from)
2797 left_ml = g_strdup(ml_post);
2798 if (strstr(left_ml, "@")) {
2799 right_ml = strstr(left_ml, "@")+1;
2800 *(strstr(left_ml, "@")) = '\0';
2803 left_from = g_strdup(from);
2804 if (strstr(left_from, "@")) {
2805 right_from = strstr(left_from, "@")+1;
2806 *(strstr(left_from, "@")) = '\0';
2809 if (left_ml && left_from && right_ml && right_from
2810 && !strncmp(left_from, left_ml, strlen(left_ml))
2811 && !strcmp(right_from, right_ml)) {
2820 static gboolean same_address(const gchar *addr1, const gchar *addr2)
2822 gchar *my_addr1, *my_addr2;
2824 if (!addr1 || !addr2)
2827 Xstrdup_a(my_addr1, addr1, return FALSE);
2828 Xstrdup_a(my_addr2, addr2, return FALSE);
2830 extract_address(my_addr1);
2831 extract_address(my_addr2);
2833 return !strcasecmp(my_addr1, my_addr2);
2836 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
2837 gboolean to_all, gboolean to_ml,
2839 gboolean followup_and_reply_to)
2841 GSList *cc_list = NULL;
2844 gchar *replyto = NULL;
2845 GHashTable *to_table;
2847 gboolean reply_to_ml = FALSE;
2848 gboolean default_reply_to = FALSE;
2850 g_return_if_fail(compose->account != NULL);
2851 g_return_if_fail(msginfo != NULL);
2853 reply_to_ml = to_ml && compose->ml_post;
2855 default_reply_to = msginfo->folder &&
2856 msginfo->folder->prefs->enable_default_reply_to;
2858 if (compose->account->protocol != A_NNTP) {
2859 if (reply_to_ml && !default_reply_to) {
2861 gboolean is_subscr = is_subscription(compose->ml_post,
2864 /* normal answer to ml post with a reply-to */
2865 compose_entry_append(compose,
2868 if (compose->replyto
2869 && !same_address(compose->ml_post, compose->replyto))
2870 compose_entry_append(compose,
2874 /* answer to subscription confirmation */
2875 if (compose->replyto)
2876 compose_entry_append(compose,
2879 else if (msginfo->from)
2880 compose_entry_append(compose,
2885 else if (!(to_all || to_sender) && default_reply_to) {
2886 compose_entry_append(compose,
2887 msginfo->folder->prefs->default_reply_to,
2889 compose_entry_mark_default_to(compose,
2890 msginfo->folder->prefs->default_reply_to);
2895 Xstrdup_a(tmp1, msginfo->from, return);
2896 extract_address(tmp1);
2897 if (to_all || to_sender ||
2898 !account_find_from_address(tmp1))
2899 compose_entry_append(compose,
2900 (compose->replyto && !to_sender)
2901 ? compose->replyto :
2902 msginfo->from ? msginfo->from : "",
2904 else if (!to_all && !to_sender) {
2905 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
2906 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
2907 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
2908 if (compose->replyto) {
2909 compose_entry_append(compose,
2913 compose_entry_append(compose,
2914 msginfo->from ? msginfo->from : "",
2918 /* replying to own mail, use original recp */
2919 compose_entry_append(compose,
2920 msginfo->to ? msginfo->to : "",
2922 compose_entry_append(compose,
2923 msginfo->cc ? msginfo->cc : "",
2929 if (to_sender || (compose->followup_to &&
2930 !strncmp(compose->followup_to, "poster", 6)))
2931 compose_entry_append
2933 (compose->replyto ? compose->replyto :
2934 msginfo->from ? msginfo->from : ""),
2937 else if (followup_and_reply_to || to_all) {
2938 compose_entry_append
2940 (compose->replyto ? compose->replyto :
2941 msginfo->from ? msginfo->from : ""),
2944 compose_entry_append
2946 compose->followup_to ? compose->followup_to :
2947 compose->newsgroups ? compose->newsgroups : "",
2948 COMPOSE_NEWSGROUPS);
2951 compose_entry_append
2953 compose->followup_to ? compose->followup_to :
2954 compose->newsgroups ? compose->newsgroups : "",
2955 COMPOSE_NEWSGROUPS);
2958 if (msginfo->subject && *msginfo->subject) {
2962 buf = p = g_strdup(msginfo->subject);
2963 p += subject_get_prefix_length(p);
2964 memmove(buf, p, strlen(p) + 1);
2966 buf2 = g_strdup_printf("Re: %s", buf);
2967 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
2972 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
2974 if (to_ml && compose->ml_post) return;
2975 if (!to_all || compose->account->protocol == A_NNTP) return;
2977 if (compose->replyto) {
2978 Xstrdup_a(replyto, compose->replyto, return);
2979 extract_address(replyto);
2981 if (msginfo->from) {
2982 Xstrdup_a(from, msginfo->from, return);
2983 extract_address(from);
2986 if (replyto && from)
2987 cc_list = address_list_append_with_comments(cc_list, from);
2988 if (to_all && msginfo->folder &&
2989 msginfo->folder->prefs->enable_default_reply_to)
2990 cc_list = address_list_append_with_comments(cc_list,
2991 msginfo->folder->prefs->default_reply_to);
2992 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
2993 cc_list = address_list_append_with_comments(cc_list, compose->cc);
2995 to_table = g_hash_table_new(g_str_hash, g_str_equal);
2997 g_hash_table_insert(to_table, g_utf8_strdown(replyto, -1), GINT_TO_POINTER(1));
2998 if (compose->account) {
2999 g_hash_table_insert(to_table, g_utf8_strdown(compose->account->address, -1),
3000 GINT_TO_POINTER(1));
3002 /* remove address on To: and that of current account */
3003 for (cur = cc_list; cur != NULL; ) {
3004 GSList *next = cur->next;
3007 addr = g_utf8_strdown(cur->data, -1);
3008 extract_address(addr);
3010 if (GPOINTER_TO_INT(g_hash_table_lookup(to_table, addr)) == 1)
3011 cc_list = g_slist_remove(cc_list, cur->data);
3013 g_hash_table_insert(to_table, addr, GINT_TO_POINTER(1));
3017 hash_free_strings(to_table);
3018 g_hash_table_destroy(to_table);
3021 for (cur = cc_list; cur != NULL; cur = cur->next)
3022 compose_entry_append(compose, (gchar *)cur->data,
3024 slist_free_strings(cc_list);
3025 g_slist_free(cc_list);
3030 #define SET_ENTRY(entry, str) \
3033 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3036 #define SET_ADDRESS(type, str) \
3039 compose_entry_append(compose, str, type); \
3042 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3044 g_return_if_fail(msginfo != NULL);
3046 SET_ENTRY(subject_entry, msginfo->subject);
3047 SET_ENTRY(from_name, msginfo->from);
3048 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3049 SET_ADDRESS(COMPOSE_CC, compose->cc);
3050 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3051 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3052 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3053 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3055 compose_update_priority_menu_item(compose);
3056 compose_update_privacy_system_menu_item(compose, FALSE);
3057 compose_show_first_last_header(compose, TRUE);
3063 static void compose_insert_sig(Compose *compose, gboolean replace)
3065 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3066 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3068 GtkTextIter iter, iter_end;
3070 gboolean prev_autowrap;
3071 gboolean found = FALSE;
3072 gboolean exists = FALSE;
3074 g_return_if_fail(compose->account != NULL);
3078 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3079 G_CALLBACK(compose_changed_cb),
3082 mark = gtk_text_buffer_get_insert(buffer);
3083 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3084 cur_pos = gtk_text_iter_get_offset (&iter);
3086 gtk_text_buffer_get_end_iter(buffer, &iter);
3088 exists = (compose->sig_str != NULL);
3091 GtkTextIter first_iter, start_iter, end_iter;
3093 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3095 if (!exists || compose->sig_str[0] == '\0')
3098 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3099 compose->signature_tag);
3102 /* include previous \n\n */
3103 gtk_text_iter_backward_chars(&first_iter, 2);
3104 start_iter = first_iter;
3105 end_iter = first_iter;
3107 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3108 compose->signature_tag);
3109 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3110 compose->signature_tag);
3112 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3118 g_free(compose->sig_str);
3119 compose->sig_str = compose_get_signature_str(compose);
3121 cur_pos = gtk_text_iter_get_offset(&iter);
3123 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3124 g_free(compose->sig_str);
3125 compose->sig_str = NULL;
3127 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3129 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3130 gtk_text_iter_forward_chars(&iter, 2);
3131 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3132 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3134 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3135 cur_pos = gtk_text_buffer_get_char_count (buffer);
3137 /* put the cursor where it should be
3138 * either where the quote_fmt says, either before the signature */
3139 if (compose->set_cursor_pos < 0)
3140 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3142 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3143 compose->set_cursor_pos);
3145 gtk_text_buffer_place_cursor(buffer, &iter);
3146 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3147 G_CALLBACK(compose_changed_cb),
3153 static gchar *compose_get_signature_str(Compose *compose)
3155 gchar *sig_body = NULL;
3156 gchar *sig_str = NULL;
3157 gchar *utf8_sig_str = NULL;
3159 g_return_val_if_fail(compose->account != NULL, NULL);
3161 if (!compose->account->sig_path)
3164 if (compose->account->sig_type == SIG_FILE) {
3165 if (!is_file_or_fifo_exist(compose->account->sig_path)) {
3166 g_warning("can't open signature file: %s\n",
3167 compose->account->sig_path);
3172 if (compose->account->sig_type == SIG_COMMAND)
3173 sig_body = get_command_output(compose->account->sig_path);
3177 tmp = file_read_to_str(compose->account->sig_path);
3180 sig_body = normalize_newlines(tmp);
3184 if (compose->account->sig_sep) {
3185 sig_str = g_strconcat("\n\n", compose->account->sig_sep, "\n", sig_body,
3189 sig_str = g_strconcat("\n\n", sig_body, NULL);
3192 if (g_utf8_validate(sig_str, -1, NULL) == TRUE)
3193 utf8_sig_str = sig_str;
3195 utf8_sig_str = conv_codeset_strdup
3196 (sig_str, conv_get_locale_charset_str_no_utf8(),
3202 return utf8_sig_str;
3205 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3208 GtkTextBuffer *buffer;
3211 const gchar *cur_encoding;
3212 gchar buf[BUFFSIZE];
3215 gboolean prev_autowrap;
3216 gboolean badtxt = FALSE;
3218 g_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3220 if ((fp = g_fopen(file, "rb")) == NULL) {
3221 FILE_OP_ERROR(file, "fopen");
3222 return COMPOSE_INSERT_READ_ERROR;
3225 prev_autowrap = compose->autowrap;
3226 compose->autowrap = FALSE;
3228 text = GTK_TEXT_VIEW(compose->text);
3229 buffer = gtk_text_view_get_buffer(text);
3230 mark = gtk_text_buffer_get_insert(buffer);
3231 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3233 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3234 G_CALLBACK(text_inserted),
3237 cur_encoding = conv_get_locale_charset_str_no_utf8();
3239 while (fgets(buf, sizeof(buf), fp) != NULL) {
3242 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3243 str = g_strdup(buf);
3245 str = conv_codeset_strdup
3246 (buf, cur_encoding, CS_INTERNAL);
3249 /* strip <CR> if DOS/Windows file,
3250 replace <CR> with <LF> if Macintosh file. */
3253 if (len > 0 && str[len - 1] != '\n') {
3255 if (str[len] == '\r') str[len] = '\n';
3258 gtk_text_buffer_insert(buffer, &iter, str, -1);
3262 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3263 G_CALLBACK(text_inserted),
3265 compose->autowrap = prev_autowrap;
3266 if (compose->autowrap)
3267 compose_wrap_all(compose);
3272 return COMPOSE_INSERT_INVALID_CHARACTER;
3274 return COMPOSE_INSERT_SUCCESS;
3277 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3278 const gchar *filename,
3279 const gchar *content_type)
3287 GtkListStore *store;
3289 gboolean has_binary = FALSE;
3291 if (!is_file_exist(file)) {
3292 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3293 gboolean result = FALSE;
3294 if (file_from_uri && is_file_exist(file_from_uri)) {
3295 result = compose_attach_append(
3296 compose, file_from_uri,
3300 g_free(file_from_uri);
3303 alertpanel_error("File %s doesn't exist\n", filename);
3306 if ((size = get_file_size(file)) < 0) {
3307 alertpanel_error("Can't get file size of %s\n", filename);
3311 alertpanel_error(_("File %s is empty."), filename);
3314 if ((fp = g_fopen(file, "rb")) == NULL) {
3315 alertpanel_error(_("Can't read %s."), filename);
3320 ainfo = g_new0(AttachInfo, 1);
3321 auto_ainfo = g_auto_pointer_new_with_free
3322 (ainfo, (GFreeFunc) compose_attach_info_free);
3323 ainfo->file = g_strdup(file);
3326 ainfo->content_type = g_strdup(content_type);
3327 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3329 MsgFlags flags = {0, 0};
3331 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3332 ainfo->encoding = ENC_7BIT;
3334 ainfo->encoding = ENC_8BIT;
3336 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3337 if (msginfo && msginfo->subject)
3338 name = g_strdup(msginfo->subject);
3340 name = g_path_get_basename(filename ? filename : file);
3342 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3344 procmsg_msginfo_free(msginfo);
3346 if (!g_ascii_strncasecmp(content_type, "text", 4))
3347 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3349 ainfo->encoding = ENC_BASE64;
3350 name = g_path_get_basename(filename ? filename : file);
3351 ainfo->name = g_strdup(name);
3355 ainfo->content_type = procmime_get_mime_type(file);
3356 if (!ainfo->content_type) {
3357 ainfo->content_type =
3358 g_strdup("application/octet-stream");
3359 ainfo->encoding = ENC_BASE64;
3360 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text", 4))
3362 procmime_get_encoding_for_text_file(file, &has_binary);
3364 ainfo->encoding = ENC_BASE64;
3365 name = g_path_get_basename(filename ? filename : file);
3366 ainfo->name = g_strdup(name);
3370 if (ainfo->name != NULL
3371 && !strcmp(ainfo->name, ".")) {
3372 g_free(ainfo->name);
3376 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3377 g_free(ainfo->content_type);
3378 ainfo->content_type = g_strdup("application/octet-stream");
3382 size_text = to_human_readable(size);
3384 store = GTK_LIST_STORE(gtk_tree_view_get_model
3385 (GTK_TREE_VIEW(compose->attach_clist)));
3387 gtk_list_store_append(store, &iter);
3388 gtk_list_store_set(store, &iter,
3389 COL_MIMETYPE, ainfo->content_type,
3390 COL_SIZE, size_text,
3391 COL_NAME, ainfo->name,
3393 COL_AUTODATA, auto_ainfo,
3396 g_auto_pointer_free(auto_ainfo);
3397 compose_attach_update_label(compose);
3401 static void compose_use_signing(Compose *compose, gboolean use_signing)
3403 GtkItemFactory *ifactory;
3404 GtkWidget *menuitem = NULL;
3406 compose->use_signing = use_signing;
3407 ifactory = gtk_item_factory_from_widget(compose->menubar);
3408 menuitem = gtk_item_factory_get_item
3409 (ifactory, "/Options/Sign");
3410 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
3414 static void compose_use_encryption(Compose *compose, gboolean use_encryption)
3416 GtkItemFactory *ifactory;
3417 GtkWidget *menuitem = NULL;
3419 compose->use_encryption = use_encryption;
3420 ifactory = gtk_item_factory_from_widget(compose->menubar);
3421 menuitem = gtk_item_factory_get_item
3422 (ifactory, "/Options/Encrypt");
3424 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
3428 #define NEXT_PART_NOT_CHILD(info) \
3430 node = info->node; \
3431 while (node->children) \
3432 node = g_node_last_child(node); \
3433 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3436 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3440 MimeInfo *firsttext = NULL;
3441 MimeInfo *encrypted = NULL;
3444 const gchar *partname = NULL;
3446 mimeinfo = procmime_scan_message(msginfo);
3447 if (!mimeinfo) return;
3449 if (mimeinfo->node->children == NULL) {
3450 procmime_mimeinfo_free_all(mimeinfo);
3454 /* find first content part */
3455 child = (MimeInfo *) mimeinfo->node->children->data;
3456 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3457 child = (MimeInfo *)child->node->children->data;
3459 if (child->type == MIMETYPE_TEXT) {
3461 debug_print("First text part found\n");
3462 } else if (compose->mode == COMPOSE_REEDIT &&
3463 child->type == MIMETYPE_APPLICATION &&
3464 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3465 encrypted = (MimeInfo *)child->node->parent->data;
3468 child = (MimeInfo *) mimeinfo->node->children->data;
3469 while (child != NULL) {
3472 if (child == encrypted) {
3473 /* skip this part of tree */
3474 NEXT_PART_NOT_CHILD(child);
3478 if (child->type == MIMETYPE_MULTIPART) {
3479 /* get the actual content */
3480 child = procmime_mimeinfo_next(child);
3484 if (child == firsttext) {
3485 child = procmime_mimeinfo_next(child);
3489 outfile = procmime_get_tmp_file_name(child);
3490 if ((err = procmime_get_part(outfile, child)) < 0)
3491 g_warning("Can't get the part of multipart message. (%s)", strerror(-err));
3493 gchar *content_type;
3495 content_type = procmime_get_content_type_str(child->type, child->subtype);
3497 /* if we meet a pgp signature, we don't attach it, but
3498 * we force signing. */
3499 if ((strcmp(content_type, "application/pgp-signature") &&
3500 strcmp(content_type, "application/pkcs7-signature") &&
3501 strcmp(content_type, "application/x-pkcs7-signature"))
3502 || compose->mode == COMPOSE_REDIRECT) {
3503 partname = procmime_mimeinfo_get_parameter(child, "filename");
3504 if (partname == NULL)
3505 partname = procmime_mimeinfo_get_parameter(child, "name");
3506 if (partname == NULL)
3508 compose_attach_append(compose, outfile,
3509 partname, content_type);
3511 compose_force_signing(compose, compose->account);
3513 g_free(content_type);
3516 NEXT_PART_NOT_CHILD(child);
3518 procmime_mimeinfo_free_all(mimeinfo);
3521 #undef NEXT_PART_NOT_CHILD
3526 WAIT_FOR_INDENT_CHAR,
3527 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3530 /* return indent length, we allow:
3531 indent characters followed by indent characters or spaces/tabs,
3532 alphabets and numbers immediately followed by indent characters,
3533 and the repeating sequences of the above
3534 If quote ends with multiple spaces, only the first one is included. */
3535 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3536 const GtkTextIter *start, gint *len)
3538 GtkTextIter iter = *start;
3542 IndentState state = WAIT_FOR_INDENT_CHAR;
3545 gint alnum_count = 0;
3546 gint space_count = 0;
3549 if (prefs_common.quote_chars == NULL) {
3553 while (!gtk_text_iter_ends_line(&iter)) {
3554 wc = gtk_text_iter_get_char(&iter);
3555 if (g_unichar_iswide(wc))
3557 clen = g_unichar_to_utf8(wc, ch);
3561 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
3562 is_space = g_unichar_isspace(wc);
3564 if (state == WAIT_FOR_INDENT_CHAR) {
3565 if (!is_indent && !g_unichar_isalnum(wc))
3568 quote_len += alnum_count + space_count + 1;
3569 alnum_count = space_count = 0;
3570 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
3573 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
3574 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
3578 else if (is_indent) {
3579 quote_len += alnum_count + space_count + 1;
3580 alnum_count = space_count = 0;
3583 state = WAIT_FOR_INDENT_CHAR;
3587 gtk_text_iter_forward_char(&iter);
3590 if (quote_len > 0 && space_count > 0)
3596 if (quote_len > 0) {
3598 gtk_text_iter_forward_chars(&iter, quote_len);
3599 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
3605 /* return TRUE if the line is itemized */
3606 static gboolean compose_is_itemized(GtkTextBuffer *buffer,
3607 const GtkTextIter *start)
3609 GtkTextIter iter = *start;
3614 if (gtk_text_iter_ends_line(&iter))
3618 wc = gtk_text_iter_get_char(&iter);
3619 if (!g_unichar_isspace(wc))
3621 gtk_text_iter_forward_char(&iter);
3622 if (gtk_text_iter_ends_line(&iter))
3626 clen = g_unichar_to_utf8(wc, ch);
3630 if (!strchr("*-+", ch[0]))
3633 gtk_text_iter_forward_char(&iter);
3634 if (gtk_text_iter_ends_line(&iter))
3636 wc = gtk_text_iter_get_char(&iter);
3637 if (g_unichar_isspace(wc))
3643 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
3644 const GtkTextIter *start,
3645 GtkTextIter *break_pos,
3649 GtkTextIter iter = *start, line_end = *start;
3650 PangoLogAttr *attrs;
3657 gboolean can_break = FALSE;
3658 gboolean do_break = FALSE;
3659 gboolean was_white = FALSE;
3660 gboolean prev_dont_break = FALSE;
3662 gtk_text_iter_forward_to_line_end(&line_end);
3663 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
3664 len = g_utf8_strlen(str, -1);
3668 g_warning("compose_get_line_break_pos: len = 0!\n");
3672 /* g_print("breaking line: %d: %s (len = %d)\n",
3673 gtk_text_iter_get_line(&iter), str, len); */
3675 attrs = g_new(PangoLogAttr, len + 1);
3677 pango_default_break(str, -1, NULL, attrs, len + 1);
3681 /* skip quote and leading spaces */
3682 for (i = 0; *p != '\0' && i < len; i++) {
3685 wc = g_utf8_get_char(p);
3686 if (i >= quote_len && !g_unichar_isspace(wc))
3688 if (g_unichar_iswide(wc))
3690 else if (*p == '\t')
3694 p = g_utf8_next_char(p);
3697 for (; *p != '\0' && i < len; i++) {
3698 PangoLogAttr *attr = attrs + i;
3702 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
3705 was_white = attr->is_white;
3707 /* don't wrap URI */
3708 if ((uri_len = get_uri_len(p)) > 0) {
3710 if (pos > 0 && col > max_col) {
3720 wc = g_utf8_get_char(p);
3721 if (g_unichar_iswide(wc)) {
3723 if (prev_dont_break && can_break && attr->is_line_break)
3725 } else if (*p == '\t')
3729 if (pos > 0 && col > max_col) {
3734 if (*p == '-' || *p == '/')
3735 prev_dont_break = TRUE;
3737 prev_dont_break = FALSE;
3739 p = g_utf8_next_char(p);
3743 debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col);
3748 *break_pos = *start;
3749 gtk_text_iter_set_line_offset(break_pos, pos);
3754 static gboolean compose_join_next_line(Compose *compose,
3755 GtkTextBuffer *buffer,
3757 const gchar *quote_str)
3759 GtkTextIter iter_ = *iter, cur, prev, next, end;
3760 PangoLogAttr attrs[3];
3762 gchar *next_quote_str;
3765 gboolean keep_cursor = FALSE;
3767 if (!gtk_text_iter_forward_line(&iter_) ||
3768 gtk_text_iter_ends_line(&iter_))
3771 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
3773 if ((quote_str || next_quote_str) &&
3774 strcmp2(quote_str, next_quote_str) != 0) {
3775 g_free(next_quote_str);
3778 g_free(next_quote_str);
3781 if (quote_len > 0) {
3782 gtk_text_iter_forward_chars(&end, quote_len);
3783 if (gtk_text_iter_ends_line(&end))
3787 /* don't join itemized lines */
3788 if (compose_is_itemized(buffer, &end))
3791 /* don't join signature separator */
3792 if (compose_is_sig_separator(compose, buffer, &iter_))
3795 /* delete quote str */
3797 gtk_text_buffer_delete(buffer, &iter_, &end);
3799 /* don't join line breaks put by the user */
3801 gtk_text_iter_backward_char(&cur);
3802 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
3803 gtk_text_iter_forward_char(&cur);
3807 gtk_text_iter_forward_char(&cur);
3808 /* delete linebreak and extra spaces */
3809 while (gtk_text_iter_backward_char(&cur)) {
3810 wc1 = gtk_text_iter_get_char(&cur);
3811 if (!g_unichar_isspace(wc1))
3816 while (!gtk_text_iter_ends_line(&cur)) {
3817 wc1 = gtk_text_iter_get_char(&cur);
3818 if (!g_unichar_isspace(wc1))
3820 gtk_text_iter_forward_char(&cur);
3823 if (!gtk_text_iter_equal(&prev, &next)) {
3826 mark = gtk_text_buffer_get_insert(buffer);
3827 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
3828 if (gtk_text_iter_equal(&prev, &cur))
3830 gtk_text_buffer_delete(buffer, &prev, &next);
3834 /* insert space if required */
3835 gtk_text_iter_backward_char(&prev);
3836 wc1 = gtk_text_iter_get_char(&prev);
3837 wc2 = gtk_text_iter_get_char(&next);
3838 gtk_text_iter_forward_char(&next);
3839 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
3840 pango_default_break(str, -1, NULL, attrs, 3);
3841 if (!attrs[1].is_line_break ||
3842 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
3843 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
3845 gtk_text_iter_backward_char(&iter_);
3846 gtk_text_buffer_place_cursor(buffer, &iter_);
3855 #define ADD_TXT_POS(bp_, ep_, pti_) \
3856 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
3857 last = last->next; \
3858 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
3859 last->next = NULL; \
3861 g_warning("alloc error scanning URIs\n"); \
3864 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
3866 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3867 GtkTextBuffer *buffer;
3868 GtkTextIter iter, break_pos, end_of_line;
3869 gchar *quote_str = NULL;
3871 gboolean wrap_quote = prefs_common.linewrap_quote;
3872 gboolean prev_autowrap = compose->autowrap;
3873 gint startq_offset = -1, noq_offset = -1;
3874 gint uri_start = -1, uri_stop = -1;
3875 gint nouri_start = -1, nouri_stop = -1;
3876 gint num_blocks = 0;
3877 gint quotelevel = -1;
3878 gboolean modified = force;
3879 gboolean removed = FALSE;
3880 gboolean modified_before_remove = FALSE;
3882 gboolean start = TRUE;
3887 if (compose->draft_timeout_tag == -2) {
3891 compose->autowrap = FALSE;
3893 buffer = gtk_text_view_get_buffer(text);
3894 undo_wrapping(compose->undostruct, TRUE);
3899 mark = gtk_text_buffer_get_insert(buffer);
3900 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3904 if (compose->draft_timeout_tag == -2) {
3905 if (gtk_text_iter_ends_line(&iter)) {
3906 while (gtk_text_iter_ends_line(&iter) &&
3907 gtk_text_iter_forward_line(&iter))
3910 while (gtk_text_iter_backward_line(&iter)) {
3911 if (gtk_text_iter_ends_line(&iter)) {
3912 gtk_text_iter_forward_line(&iter);
3918 /* move to line start */
3919 gtk_text_iter_set_line_offset(&iter, 0);
3921 /* go until paragraph end (empty line) */
3922 while (start || !gtk_text_iter_ends_line(&iter)) {
3923 gchar *scanpos = NULL;
3924 /* parse table - in order of priority */
3926 const gchar *needle; /* token */
3928 /* token search function */
3929 gchar *(*search) (const gchar *haystack,
3930 const gchar *needle);
3931 /* part parsing function */
3932 gboolean (*parse) (const gchar *start,
3933 const gchar *scanpos,
3937 /* part to URI function */
3938 gchar *(*build_uri) (const gchar *bp,
3942 static struct table parser[] = {
3943 {"http://", strcasestr, get_uri_part, make_uri_string},
3944 {"https://", strcasestr, get_uri_part, make_uri_string},
3945 {"ftp://", strcasestr, get_uri_part, make_uri_string},
3946 {"sftp://", strcasestr, get_uri_part, make_uri_string},
3947 {"www.", strcasestr, get_uri_part, make_http_string},
3948 {"mailto:", strcasestr, get_uri_part, make_uri_string},
3949 {"@", strcasestr, get_email_part, make_email_string}
3951 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
3952 gint last_index = PARSE_ELEMS;
3954 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
3958 if (!prev_autowrap && num_blocks == 0) {
3960 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3961 G_CALLBACK(text_inserted),
3964 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
3967 uri_start = uri_stop = -1;
3969 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
3972 debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str);
3973 if (startq_offset == -1)
3974 startq_offset = gtk_text_iter_get_offset(&iter);
3975 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
3976 if (quotelevel > 2) {
3977 /* recycle colors */
3978 if (prefs_common.recycle_quote_colors)
3987 if (startq_offset == -1)
3988 noq_offset = gtk_text_iter_get_offset(&iter);
3992 if (prev_autowrap == FALSE && !force && !wrap_quote) {
3995 if (gtk_text_iter_ends_line(&iter)) {
3997 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
3998 prefs_common.linewrap_len,
4000 GtkTextIter prev, next, cur;
4002 if (prev_autowrap != FALSE || force) {
4003 compose->automatic_break = TRUE;
4005 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4006 compose->automatic_break = FALSE;
4007 } else if (quote_str && wrap_quote) {
4008 compose->automatic_break = TRUE;
4010 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4011 compose->automatic_break = FALSE;
4014 /* remove trailing spaces */
4016 gtk_text_iter_backward_char(&cur);
4018 while (!gtk_text_iter_starts_line(&cur)) {
4021 gtk_text_iter_backward_char(&cur);
4022 wc = gtk_text_iter_get_char(&cur);
4023 if (!g_unichar_isspace(wc))
4027 if (!gtk_text_iter_equal(&prev, &next)) {
4028 gtk_text_buffer_delete(buffer, &prev, &next);
4030 gtk_text_iter_forward_char(&break_pos);
4034 gtk_text_buffer_insert(buffer, &break_pos,
4038 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4040 /* move iter to current line start */
4041 gtk_text_iter_set_line_offset(&iter, 0);
4048 /* move iter to next line start */
4054 if (!prev_autowrap && num_blocks > 0) {
4056 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4057 G_CALLBACK(text_inserted),
4061 while (!gtk_text_iter_ends_line(&end_of_line)) {
4062 gtk_text_iter_forward_char(&end_of_line);
4064 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4066 nouri_start = gtk_text_iter_get_offset(&iter);
4067 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4069 walk_pos = gtk_text_iter_get_offset(&iter);
4070 /* FIXME: this looks phony. scanning for anything in the parse table */
4071 for (n = 0; n < PARSE_ELEMS; n++) {
4074 tmp = parser[n].search(walk, parser[n].needle);
4076 if (scanpos == NULL || tmp < scanpos) {
4085 /* check if URI can be parsed */
4086 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4087 (const gchar **)&ep, FALSE)
4088 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4092 strlen(parser[last_index].needle);
4095 uri_start = walk_pos + (bp - o_walk);
4096 uri_stop = walk_pos + (ep - o_walk);
4100 gtk_text_iter_forward_line(&iter);
4103 if (startq_offset != -1) {
4104 GtkTextIter startquote, endquote;
4105 gtk_text_buffer_get_iter_at_offset(
4106 buffer, &startquote, startq_offset);
4109 switch (quotelevel) {
4111 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4112 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4113 gtk_text_buffer_apply_tag_by_name(
4114 buffer, "quote0", &startquote, &endquote);
4115 gtk_text_buffer_remove_tag_by_name(
4116 buffer, "quote1", &startquote, &endquote);
4117 gtk_text_buffer_remove_tag_by_name(
4118 buffer, "quote2", &startquote, &endquote);
4123 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4124 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4125 gtk_text_buffer_apply_tag_by_name(
4126 buffer, "quote1", &startquote, &endquote);
4127 gtk_text_buffer_remove_tag_by_name(
4128 buffer, "quote0", &startquote, &endquote);
4129 gtk_text_buffer_remove_tag_by_name(
4130 buffer, "quote2", &startquote, &endquote);
4135 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4136 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4137 gtk_text_buffer_apply_tag_by_name(
4138 buffer, "quote2", &startquote, &endquote);
4139 gtk_text_buffer_remove_tag_by_name(
4140 buffer, "quote0", &startquote, &endquote);
4141 gtk_text_buffer_remove_tag_by_name(
4142 buffer, "quote1", &startquote, &endquote);
4148 } else if (noq_offset != -1) {
4149 GtkTextIter startnoquote, endnoquote;
4150 gtk_text_buffer_get_iter_at_offset(
4151 buffer, &startnoquote, noq_offset);
4154 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4155 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4156 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4157 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4158 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4159 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4160 gtk_text_buffer_remove_tag_by_name(
4161 buffer, "quote0", &startnoquote, &endnoquote);
4162 gtk_text_buffer_remove_tag_by_name(
4163 buffer, "quote1", &startnoquote, &endnoquote);
4164 gtk_text_buffer_remove_tag_by_name(
4165 buffer, "quote2", &startnoquote, &endnoquote);
4171 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4172 GtkTextIter nouri_start_iter, nouri_end_iter;
4173 gtk_text_buffer_get_iter_at_offset(
4174 buffer, &nouri_start_iter, nouri_start);
4175 gtk_text_buffer_get_iter_at_offset(
4176 buffer, &nouri_end_iter, nouri_stop);
4177 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4178 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4179 gtk_text_buffer_remove_tag_by_name(
4180 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4181 modified_before_remove = modified;
4186 if (uri_start >= 0 && uri_stop > 0) {
4187 GtkTextIter uri_start_iter, uri_end_iter, back;
4188 gtk_text_buffer_get_iter_at_offset(
4189 buffer, &uri_start_iter, uri_start);
4190 gtk_text_buffer_get_iter_at_offset(
4191 buffer, &uri_end_iter, uri_stop);
4192 back = uri_end_iter;
4193 gtk_text_iter_backward_char(&back);
4194 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4195 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4196 gtk_text_buffer_apply_tag_by_name(
4197 buffer, "link", &uri_start_iter, &uri_end_iter);
4199 if (removed && !modified_before_remove) {
4205 debug_print("not modified, out after %d lines\n", lines);
4210 debug_print("modified, out after %d lines\n", lines);
4214 undo_wrapping(compose->undostruct, FALSE);
4215 compose->autowrap = prev_autowrap;
4220 void compose_action_cb(void *data)
4222 Compose *compose = (Compose *)data;
4223 compose_wrap_all(compose);
4226 static void compose_wrap_all(Compose *compose)
4228 compose_wrap_all_full(compose, FALSE);
4231 static void compose_wrap_all_full(Compose *compose, gboolean force)
4233 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4234 GtkTextBuffer *buffer;
4236 gboolean modified = TRUE;
4238 buffer = gtk_text_view_get_buffer(text);
4240 gtk_text_buffer_get_start_iter(buffer, &iter);
4241 while (!gtk_text_iter_is_end(&iter) && modified)
4242 modified = compose_beautify_paragraph(compose, &iter, force);
4246 static void compose_set_title(Compose *compose)
4252 edited = compose->modified ? _(" [Edited]") : "";
4254 subject = gtk_editable_get_chars(
4255 GTK_EDITABLE(compose->subject_entry), 0, -1);
4258 if (subject && strlen(subject))
4259 str = g_strdup_printf(_("%s - Compose message%s"),
4262 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4264 str = g_strdup(_("Compose message"));
4267 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4273 * compose_current_mail_account:
4275 * Find a current mail account (the currently selected account, or the
4276 * default account, if a news account is currently selected). If a
4277 * mail account cannot be found, display an error message.
4279 * Return value: Mail account, or NULL if not found.
4281 static PrefsAccount *
4282 compose_current_mail_account(void)
4286 if (cur_account && cur_account->protocol != A_NNTP)
4289 ac = account_get_default();
4290 if (!ac || ac->protocol == A_NNTP) {
4291 alertpanel_error(_("Account for sending mail is not specified.\n"
4292 "Please select a mail account before sending."));
4299 #define QUOTE_IF_REQUIRED(out, str) \
4301 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4305 len = strlen(str) + 3; \
4306 if ((__tmp = alloca(len)) == NULL) { \
4307 g_warning("can't allocate memory\n"); \
4308 g_string_free(header, TRUE); \
4311 g_snprintf(__tmp, len, "\"%s\"", str); \
4316 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4317 g_warning("can't allocate memory\n"); \
4318 g_string_free(header, TRUE); \
4321 strcpy(__tmp, str); \
4327 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4329 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4333 len = strlen(str) + 3; \
4334 if ((__tmp = alloca(len)) == NULL) { \
4335 g_warning("can't allocate memory\n"); \
4338 g_snprintf(__tmp, len, "\"%s\"", str); \
4343 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4344 g_warning("can't allocate memory\n"); \
4347 strcpy(__tmp, str); \
4353 static void compose_select_account(Compose *compose, PrefsAccount *account,
4356 GtkItemFactory *ifactory;
4359 g_return_if_fail(account != NULL);
4361 compose->account = account;
4363 if (account->name && *account->name) {
4365 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4366 from = g_strdup_printf("%s <%s>",
4367 buf, account->address);
4368 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4370 from = g_strdup_printf("<%s>",
4372 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4377 compose_set_title(compose);
4379 ifactory = gtk_item_factory_from_widget(compose->menubar);
4381 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4382 menu_set_active(ifactory, "/Options/Sign", TRUE);
4384 menu_set_active(ifactory, "/Options/Sign", FALSE);
4385 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4386 menu_set_active(ifactory, "/Options/Encrypt", TRUE);
4388 menu_set_active(ifactory, "/Options/Encrypt", FALSE);
4390 activate_privacy_system(compose, account, FALSE);
4392 if (!init && compose->mode != COMPOSE_REDIRECT) {
4393 undo_block(compose->undostruct);
4394 compose_insert_sig(compose, TRUE);
4395 undo_unblock(compose->undostruct);
4399 /* use account's dict info if set */
4400 if (compose->gtkaspell) {
4401 if (account->enable_default_dictionary)
4402 gtkaspell_change_dict(compose->gtkaspell,
4403 account->default_dictionary, FALSE);
4404 if (account->enable_default_alt_dictionary)
4405 gtkaspell_change_alt_dict(compose->gtkaspell,
4406 account->default_alt_dictionary);
4407 if (account->enable_default_dictionary
4408 || account->enable_default_alt_dictionary)
4409 compose_spell_menu_changed(compose);
4414 gboolean compose_check_for_valid_recipient(Compose *compose) {
4415 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
4416 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
4417 gboolean recipient_found = FALSE;
4421 /* free to and newsgroup list */
4422 slist_free_strings(compose->to_list);
4423 g_slist_free(compose->to_list);
4424 compose->to_list = NULL;
4426 slist_free_strings(compose->newsgroup_list);
4427 g_slist_free(compose->newsgroup_list);
4428 compose->newsgroup_list = NULL;
4430 /* search header entries for to and newsgroup entries */
4431 for (list = compose->header_list; list; list = list->next) {
4434 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_BIN(((ComposeHeaderEntry *)list->data)->combo)->child), 0, -1);
4435 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4438 if (entry[0] != '\0') {
4439 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
4440 if (!strcmp(header, prefs_common_translated_header_name(*strptr))) {
4441 compose->to_list = address_list_append(compose->to_list, entry);
4442 recipient_found = TRUE;
4445 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
4446 if (!strcmp(header, prefs_common_translated_header_name(*strptr))) {
4447 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
4448 recipient_found = TRUE;
4455 return recipient_found;
4458 static gboolean compose_check_for_set_recipients(Compose *compose)
4460 if (compose->account->set_autocc && compose->account->auto_cc) {
4461 gboolean found_other = FALSE;
4463 /* search header entries for to and newsgroup entries */
4464 for (list = compose->header_list; list; list = list->next) {
4467 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4468 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_BIN(((ComposeHeaderEntry *)list->data)->combo)->child), 0, -1);
4471 if (strcmp(entry, compose->account->auto_cc)
4472 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
4482 if (compose->batch) {
4483 gtk_widget_show_all(compose->window);
4485 aval = alertpanel(_("Send"),
4486 _("The only recipient is the default CC address. Send anyway?"),
4487 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4488 if (aval != G_ALERTALTERNATE)
4492 if (compose->account->set_autobcc && compose->account->auto_bcc) {
4493 gboolean found_other = FALSE;
4495 /* search header entries for to and newsgroup entries */
4496 for (list = compose->header_list; list; list = list->next) {
4499 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4500 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_BIN(((ComposeHeaderEntry *)list->data)->combo)->child), 0, -1);
4503 if (strcmp(entry, compose->account->auto_bcc)
4504 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
4514 if (compose->batch) {
4515 gtk_widget_show_all(compose->window);
4517 aval = alertpanel(_("Send"),
4518 _("The only recipient is the default BCC address. Send anyway?"),
4519 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4520 if (aval != G_ALERTALTERNATE)
4527 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
4531 if (compose_check_for_valid_recipient(compose) == FALSE) {
4532 if (compose->batch) {
4533 gtk_widget_show_all(compose->window);
4535 alertpanel_error(_("Recipient is not specified."));
4539 if (compose_check_for_set_recipients(compose) == FALSE) {
4543 if (!compose->batch) {
4544 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
4545 if (*str == '\0' && check_everything == TRUE &&
4546 compose->mode != COMPOSE_REDIRECT) {
4548 gchar *button_label;
4551 if (compose->sending)
4552 button_label = _("+_Send");
4554 button_label = _("+_Queue");
4555 message = g_strdup_printf(_("Subject is empty. %s"),
4556 compose->sending?_("Send it anyway?"):
4557 _("Queue it anyway?"));
4559 aval = alertpanel(compose->sending?_("Send"):_("Send later"), message,
4560 GTK_STOCK_CANCEL, button_label, NULL);
4562 if (aval != G_ALERTALTERNATE)
4567 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
4573 gint compose_send(Compose *compose)
4576 FolderItem *folder = NULL;
4578 gchar *msgpath = NULL;
4579 gboolean discard_window = FALSE;
4580 gchar *errstr = NULL;
4581 gchar *tmsgid = NULL;
4582 MainWindow *mainwin = mainwindow_get_mainwindow();
4583 gboolean queued_removed = FALSE;
4585 if (prefs_common.send_dialog_invisible
4586 || compose->batch == TRUE)
4587 discard_window = TRUE;
4589 compose_allow_user_actions (compose, FALSE);
4590 compose->sending = TRUE;
4592 if (compose_check_entries(compose, TRUE) == FALSE) {
4593 if (compose->batch) {
4594 gtk_widget_show_all(compose->window);
4600 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
4603 if (compose->batch) {
4604 gtk_widget_show_all(compose->window);
4607 alertpanel_error(_("Could not queue message for sending:\n\n"
4608 "Charset conversion failed."));
4609 } else if (val == -5) {
4610 alertpanel_error(_("Could not queue message for sending:\n\n"
4611 "Couldn't get recipient encryption key."));
4612 } else if (val == -6) {
4614 } else if (val == -3) {
4615 if (privacy_peek_error())
4616 alertpanel_error(_("Could not queue message for sending:\n\n"
4617 "Signature failed: %s"), privacy_get_error());
4618 } else if (val == -2 && errno != 0) {
4619 alertpanel_error(_("Could not queue message for sending:\n\n%s."), strerror(errno));
4621 alertpanel_error(_("Could not queue message for sending."));
4626 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
4627 if (discard_window) {
4628 compose->sending = FALSE;
4629 compose_close(compose);
4630 /* No more compose access in the normal codepath
4631 * after this point! */
4636 alertpanel_error(_("The message was queued but could not be "
4637 "sent.\nUse \"Send queued messages\" from "
4638 "the main window to retry."));
4639 if (!discard_window) {
4646 if (msgpath == NULL) {
4647 msgpath = folder_item_fetch_msg(folder, msgnum);
4648 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
4651 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
4655 if (!discard_window) {
4657 if (!queued_removed)
4658 folder_item_remove_msg(folder, msgnum);
4659 folder_item_scan(folder);
4661 /* make sure we delete that */
4662 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
4664 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
4665 folder_item_remove_msg(folder, tmp->msgnum);
4666 procmsg_msginfo_free(tmp);
4673 if (!queued_removed)
4674 folder_item_remove_msg(folder, msgnum);
4675 folder_item_scan(folder);
4677 /* make sure we delete that */
4678 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
4680 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
4681 folder_item_remove_msg(folder, tmp->msgnum);
4682 procmsg_msginfo_free(tmp);
4685 if (!discard_window) {
4686 compose->sending = FALSE;
4687 compose_allow_user_actions (compose, TRUE);
4688 compose_close(compose);
4692 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
4693 "the main window to retry."), errstr);
4696 alertpanel_error_log(_("The message was queued but could not be "
4697 "sent.\nUse \"Send queued messages\" from "
4698 "the main window to retry."));
4700 if (!discard_window) {
4709 toolbar_main_set_sensitive(mainwin);
4710 main_window_set_menu_sensitive(mainwin);
4716 compose_allow_user_actions (compose, TRUE);
4717 compose->sending = FALSE;
4718 compose->modified = TRUE;
4719 toolbar_main_set_sensitive(mainwin);
4720 main_window_set_menu_sensitive(mainwin);
4725 static gboolean compose_use_attach(Compose *compose)
4727 GtkTreeModel *model = gtk_tree_view_get_model
4728 (GTK_TREE_VIEW(compose->attach_clist));
4729 return gtk_tree_model_iter_n_children(model, NULL) > 0;
4732 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
4735 gchar buf[BUFFSIZE];
4737 gboolean first_to_address;
4738 gboolean first_cc_address;
4740 ComposeHeaderEntry *headerentry;
4741 const gchar *headerentryname;
4742 const gchar *cc_hdr;
4743 const gchar *to_hdr;
4744 gboolean err = FALSE;
4746 debug_print("Writing redirect header\n");
4748 cc_hdr = prefs_common_translated_header_name("Cc:");
4749 to_hdr = prefs_common_translated_header_name("To:");
4751 first_to_address = TRUE;
4752 for (list = compose->header_list; list; list = list->next) {
4753 headerentry = ((ComposeHeaderEntry *)list->data);
4754 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_BIN(headerentry->combo)->child));
4756 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
4757 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
4758 Xstrdup_a(str, entstr, return -1);
4760 if (str[0] != '\0') {
4761 compose_convert_header
4762 (compose, buf, sizeof(buf), str,
4763 strlen("Resent-To") + 2, TRUE);
4765 if (first_to_address) {
4766 err |= (fprintf(fp, "Resent-To: ") < 0);
4767 first_to_address = FALSE;
4769 err |= (fprintf(fp, ",") < 0);
4771 err |= (fprintf(fp, "%s", buf) < 0);
4775 if (!first_to_address) {
4776 err |= (fprintf(fp, "\n") < 0);
4779 first_cc_address = TRUE;
4780 for (list = compose->header_list; list; list = list->next) {
4781 headerentry = ((ComposeHeaderEntry *)list->data);
4782 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_BIN(headerentry->combo)->child));
4784 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
4785 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
4786 Xstrdup_a(str, strg, return -1);
4788 if (str[0] != '\0') {
4789 compose_convert_header
4790 (compose, buf, sizeof(buf), str,
4791 strlen("Resent-Cc") + 2, TRUE);
4793 if (first_cc_address) {
4794 err |= (fprintf(fp, "Resent-Cc: ") < 0);
4795 first_cc_address = FALSE;
4797 err |= (fprintf(fp, ",") < 0);
4799 err |= (fprintf(fp, "%s", buf) < 0);
4803 if (!first_cc_address) {
4804 err |= (fprintf(fp, "\n") < 0);
4807 return (err ? -1:0);
4810 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
4812 gchar buf[BUFFSIZE];
4814 const gchar *entstr;
4815 /* struct utsname utsbuf; */
4816 gboolean err = FALSE;
4818 g_return_val_if_fail(fp != NULL, -1);
4819 g_return_val_if_fail(compose->account != NULL, -1);
4820 g_return_val_if_fail(compose->account->address != NULL, -1);
4823 get_rfc822_date(buf, sizeof(buf));
4824 err |= (fprintf(fp, "Resent-Date: %s\n", buf) < 0);
4827 if (compose->account->name && *compose->account->name) {
4828 compose_convert_header
4829 (compose, buf, sizeof(buf), compose->account->name,
4830 strlen("From: "), TRUE);
4831 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
4832 buf, compose->account->address) < 0);
4834 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
4837 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
4838 if (*entstr != '\0') {
4839 Xstrdup_a(str, entstr, return -1);
4842 compose_convert_header(compose, buf, sizeof(buf), str,
4843 strlen("Subject: "), FALSE);
4844 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
4848 /* Resent-Message-ID */
4849 if (compose->account->set_domain && compose->account->domain) {
4850 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
4851 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
4852 g_snprintf(buf, sizeof(buf), "%s",
4853 strchr(compose->account->address, '@') ?
4854 strchr(compose->account->address, '@')+1 :
4855 compose->account->address);
4857 g_snprintf(buf, sizeof(buf), "%s", "");
4860 if (compose->account->gen_msgid) {
4861 generate_msgid(buf, sizeof(buf));
4862 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", buf) < 0);
4863 compose->msgid = g_strdup(buf);
4865 compose->msgid = NULL;
4868 if (compose_redirect_write_headers_from_headerlist(compose, fp))
4871 /* separator between header and body */
4872 err |= (fputs("\n", fp) == EOF);
4874 return (err ? -1:0);
4877 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
4881 gchar buf[BUFFSIZE];
4883 gboolean skip = FALSE;
4884 gboolean err = FALSE;
4885 gchar *not_included[]={
4886 "Return-Path:", "Delivered-To:", "Received:",
4887 "Subject:", "X-UIDL:", "AF:",
4888 "NF:", "PS:", "SRH:",
4889 "SFN:", "DSR:", "MID:",
4890 "CFG:", "PT:", "S:",
4891 "RQ:", "SSV:", "NSV:",
4892 "SSH:", "R:", "MAID:",
4893 "NAID:", "RMID:", "FMID:",
4894 "SCF:", "RRCPT:", "NG:",
4895 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
4896 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
4897 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
4898 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
4901 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
4902 FILE_OP_ERROR(compose->redirect_filename, "fopen");
4906 while (procheader_get_one_field_asis(buf, sizeof(buf), fp) != -1) {
4908 for (i = 0; not_included[i] != NULL; i++) {
4909 if (g_ascii_strncasecmp(buf, not_included[i],
4910 strlen(not_included[i])) == 0) {
4917 if (fputs(buf, fdest) == -1)
4920 if (!prefs_common.redirect_keep_from) {
4921 if (g_ascii_strncasecmp(buf, "From:",
4922 strlen("From:")) == 0) {
4923 err |= (fputs(" (by way of ", fdest) == EOF);
4924 if (compose->account->name
4925 && *compose->account->name) {
4926 compose_convert_header
4927 (compose, buf, sizeof(buf),
4928 compose->account->name,
4931 err |= (fprintf(fdest, "%s <%s>",
4933 compose->account->address) < 0);
4935 err |= (fprintf(fdest, "%s",
4936 compose->account->address) < 0);
4937 err |= (fputs(")", fdest) == EOF);
4941 if (fputs("\n", fdest) == -1)
4948 if (compose_redirect_write_headers(compose, fdest))
4951 while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
4952 if (fwrite(buf, sizeof(gchar), len, fdest) != len)
4965 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
4967 GtkTextBuffer *buffer;
4968 GtkTextIter start, end;
4971 const gchar *out_codeset;
4972 EncodingType encoding;
4973 MimeInfo *mimemsg, *mimetext;
4976 if (action == COMPOSE_WRITE_FOR_SEND)
4977 attach_parts = TRUE;
4979 /* create message MimeInfo */
4980 mimemsg = procmime_mimeinfo_new();
4981 mimemsg->type = MIMETYPE_MESSAGE;
4982 mimemsg->subtype = g_strdup("rfc822");
4983 mimemsg->content = MIMECONTENT_MEM;
4984 mimemsg->tmp = TRUE; /* must free content later */
4985 mimemsg->data.mem = compose_get_header(compose);
4987 /* Create text part MimeInfo */
4988 /* get all composed text */
4989 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
4990 gtk_text_buffer_get_start_iter(buffer, &start);
4991 gtk_text_buffer_get_end_iter(buffer, &end);
4992 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
4993 if (is_ascii_str(chars)) {
4996 out_codeset = CS_US_ASCII;
4997 encoding = ENC_7BIT;
4999 const gchar *src_codeset = CS_INTERNAL;
5001 out_codeset = conv_get_charset_str(compose->out_encoding);
5004 gchar *test_conv_global_out = NULL;
5005 gchar *test_conv_reply = NULL;
5007 /* automatic mode. be automatic. */
5008 codeconv_set_strict(TRUE);
5010 out_codeset = conv_get_outgoing_charset_str();
5012 debug_print("trying to convert to %s\n", out_codeset);
5013 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5016 if (!test_conv_global_out && compose->orig_charset
5017 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5018 out_codeset = compose->orig_charset;
5019 debug_print("failure; trying to convert to %s\n", out_codeset);
5020 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5023 if (!test_conv_global_out && !test_conv_reply) {
5025 out_codeset = CS_INTERNAL;
5026 debug_print("failure; finally using %s\n", out_codeset);
5028 g_free(test_conv_global_out);
5029 g_free(test_conv_reply);
5030 codeconv_set_strict(FALSE);
5033 if (!g_ascii_strcasecmp(out_codeset, CS_US_ASCII))
5034 out_codeset = CS_ISO_8859_1;
5036 if (prefs_common.encoding_method == CTE_BASE64)
5037 encoding = ENC_BASE64;
5038 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5039 encoding = ENC_QUOTED_PRINTABLE;
5040 else if (prefs_common.encoding_method == CTE_8BIT)
5041 encoding = ENC_8BIT;
5043 encoding = procmime_get_encoding_for_charset(out_codeset);
5045 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5046 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5048 if (action == COMPOSE_WRITE_FOR_SEND) {
5049 codeconv_set_strict(TRUE);
5050 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5051 codeconv_set_strict(FALSE);
5057 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5058 "to the specified %s charset.\n"
5059 "Send it as %s?"), out_codeset, src_codeset);
5060 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL, _("+_Send"), NULL, FALSE,
5061 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5064 if (aval != G_ALERTALTERNATE) {
5069 out_codeset = src_codeset;
5075 out_codeset = src_codeset;
5081 if (encoding == ENC_8BIT || encoding == ENC_7BIT) {
5082 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5083 strstr(buf, "\nFrom ") != NULL) {
5084 encoding = ENC_QUOTED_PRINTABLE;
5088 mimetext = procmime_mimeinfo_new();
5089 mimetext->content = MIMECONTENT_MEM;
5090 mimetext->tmp = TRUE; /* must free content later */
5091 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5092 * and free the data, which we need later. */
5093 mimetext->data.mem = g_strdup(buf);
5094 mimetext->type = MIMETYPE_TEXT;
5095 mimetext->subtype = g_strdup("plain");
5096 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5097 g_strdup(out_codeset));
5099 /* protect trailing spaces when signing message */
5100 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5101 privacy_system_can_sign(compose->privacy_system)) {
5102 encoding = ENC_QUOTED_PRINTABLE;
5105 debug_print("main text: %zd bytes encoded as %s in %d\n",
5106 strlen(buf), out_codeset, encoding);
5108 /* check for line length limit */
5109 if (action == COMPOSE_WRITE_FOR_SEND &&
5110 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5111 check_line_length(buf, 1000, &line) < 0) {
5115 msg = g_strdup_printf
5116 (_("Line %d exceeds the line length limit (998 bytes).\n"
5117 "The contents of the message might be broken on the way to the delivery.\n"
5119 "Send it anyway?"), line + 1);
5120 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5122 if (aval != G_ALERTALTERNATE) {
5128 if (encoding != ENC_UNKNOWN)
5129 procmime_encode_content(mimetext, encoding);
5131 /* append attachment parts */
5132 if (compose_use_attach(compose) && attach_parts) {
5133 MimeInfo *mimempart;
5134 gchar *boundary = NULL;
5135 mimempart = procmime_mimeinfo_new();
5136 mimempart->content = MIMECONTENT_EMPTY;
5137 mimempart->type = MIMETYPE_MULTIPART;
5138 mimempart->subtype = g_strdup("mixed");
5142 boundary = generate_mime_boundary(NULL);
5143 } while (strstr(buf, boundary) != NULL);
5145 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5148 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5150 g_node_append(mimempart->node, mimetext->node);
5151 g_node_append(mimemsg->node, mimempart->node);
5153 compose_add_attachments(compose, mimempart);
5155 g_node_append(mimemsg->node, mimetext->node);
5159 /* sign message if sending */
5160 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5161 privacy_system_can_sign(compose->privacy_system))
5162 if (!privacy_sign(compose->privacy_system, mimemsg, compose->account))
5165 procmime_write_mimeinfo(mimemsg, fp);
5167 procmime_mimeinfo_free_all(mimemsg);
5172 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5174 GtkTextBuffer *buffer;
5175 GtkTextIter start, end;
5180 if ((fp = g_fopen(file, "wb")) == NULL) {
5181 FILE_OP_ERROR(file, "fopen");
5185 /* chmod for security */
5186 if (change_file_mode_rw(fp, file) < 0) {
5187 FILE_OP_ERROR(file, "chmod");
5188 g_warning("can't change file mode\n");
5191 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5192 gtk_text_buffer_get_start_iter(buffer, &start);
5193 gtk_text_buffer_get_end_iter(buffer, &end);
5194 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5196 chars = conv_codeset_strdup
5197 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5200 if (!chars) return -1;
5203 len = strlen(chars);
5204 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5205 FILE_OP_ERROR(file, "fwrite");
5214 if (fclose(fp) == EOF) {
5215 FILE_OP_ERROR(file, "fclose");
5222 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5225 MsgInfo *msginfo = compose->targetinfo;
5227 g_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5228 if (!msginfo) return -1;
5230 if (!force && MSG_IS_LOCKED(msginfo->flags))
5233 item = msginfo->folder;
5234 g_return_val_if_fail(item != NULL, -1);
5236 if (procmsg_msg_exist(msginfo) &&
5237 (folder_has_parent_of_type(item, F_QUEUE) ||
5238 folder_has_parent_of_type(item, F_DRAFT)
5239 || msginfo == compose->autosaved_draft)) {
5240 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5241 g_warning("can't remove the old message\n");
5244 debug_print("removed reedit target %d\n", msginfo->msgnum);
5251 static void compose_remove_draft(Compose *compose)
5254 MsgInfo *msginfo = compose->targetinfo;
5255 drafts = account_get_special_folder(compose->account, F_DRAFT);
5257 if (procmsg_msg_exist(msginfo)) {
5258 folder_item_remove_msg(drafts, msginfo->msgnum);
5263 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5264 gboolean remove_reedit_target)
5266 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5269 static gboolean compose_warn_encryption(Compose *compose)
5271 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5272 AlertValue val = G_ALERTALTERNATE;
5274 if (warning == NULL)
5277 val = alertpanel_full(_("Encryption warning"), warning,
5278 GTK_STOCK_CANCEL, _("+C_ontinue"), NULL,
5279 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5280 if (val & G_ALERTDISABLE) {
5281 val &= ~G_ALERTDISABLE;
5282 if (val == G_ALERTALTERNATE)
5283 privacy_inhibit_encrypt_warning(compose->privacy_system,
5287 if (val == G_ALERTALTERNATE) {
5294 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
5295 gchar **msgpath, gboolean check_subject,
5296 gboolean remove_reedit_target)
5303 static gboolean lock = FALSE;
5304 PrefsAccount *mailac = NULL, *newsac = NULL;
5305 gboolean err = FALSE;
5307 debug_print("queueing message...\n");
5308 g_return_val_if_fail(compose->account != NULL, -1);
5312 if (compose_check_entries(compose, check_subject) == FALSE) {
5314 if (compose->batch) {
5315 gtk_widget_show_all(compose->window);
5320 if (!compose->to_list && !compose->newsgroup_list) {
5321 g_warning("can't get recipient list.");
5326 if (compose->to_list) {
5327 if (compose->account->protocol != A_NNTP)
5328 mailac = compose->account;
5329 else if (cur_account && cur_account->protocol != A_NNTP)
5330 mailac = cur_account;
5331 else if (!(mailac = compose_current_mail_account())) {
5333 alertpanel_error(_("No account for sending mails available!"));
5338 if (compose->newsgroup_list) {
5339 if (compose->account->protocol == A_NNTP)
5340 newsac = compose->account;
5341 else if (!newsac->protocol != A_NNTP) {
5343 alertpanel_error(_("No account for posting news available!"));
5348 /* write queue header */
5349 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
5350 G_DIR_SEPARATOR, compose, (guint) rand());
5351 debug_print("queuing to %s\n", tmp);
5352 if ((fp = g_fopen(tmp, "wb")) == NULL) {
5353 FILE_OP_ERROR(tmp, "fopen");
5359 if (change_file_mode_rw(fp, tmp) < 0) {
5360 FILE_OP_ERROR(tmp, "chmod");
5361 g_warning("can't change file mode\n");
5364 /* queueing variables */
5365 err |= (fprintf(fp, "AF:\n") < 0);
5366 err |= (fprintf(fp, "NF:0\n") < 0);
5367 err |= (fprintf(fp, "PS:10\n") < 0);
5368 err |= (fprintf(fp, "SRH:1\n") < 0);
5369 err |= (fprintf(fp, "SFN:\n") < 0);
5370 err |= (fprintf(fp, "DSR:\n") < 0);
5372 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
5374 err |= (fprintf(fp, "MID:\n") < 0);
5375 err |= (fprintf(fp, "CFG:\n") < 0);
5376 err |= (fprintf(fp, "PT:0\n") < 0);
5377 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
5378 err |= (fprintf(fp, "RQ:\n") < 0);
5380 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
5382 err |= (fprintf(fp, "SSV:\n") < 0);
5384 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
5386 err |= (fprintf(fp, "NSV:\n") < 0);
5387 err |= (fprintf(fp, "SSH:\n") < 0);
5388 /* write recepient list */
5389 if (compose->to_list) {
5390 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
5391 for (cur = compose->to_list->next; cur != NULL;
5393 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
5394 err |= (fprintf(fp, "\n") < 0);
5396 /* write newsgroup list */
5397 if (compose->newsgroup_list) {
5398 err |= (fprintf(fp, "NG:") < 0);
5399 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
5400 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
5401 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
5402 err |= (fprintf(fp, "\n") < 0);
5404 /* Sylpheed account IDs */
5406 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
5408 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
5411 if (compose->privacy_system != NULL) {
5412 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
5413 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
5414 if (compose->use_encryption) {
5416 if (!compose_warn_encryption(compose)) {
5423 if (mailac && mailac->encrypt_to_self) {
5424 GSList *tmp_list = g_slist_copy(compose->to_list);
5425 tmp_list = g_slist_append(tmp_list, compose->account->address);
5426 encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
5427 g_slist_free(tmp_list);
5429 encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
5431 if (encdata != NULL) {
5432 if (strcmp(encdata, "_DONT_ENCRYPT_")) {
5433 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5434 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
5436 } /* else we finally dont want to encrypt */
5438 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5439 /* and if encdata was null, it means there's been a problem in
5451 /* Save copy folder */
5452 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
5453 gchar *savefolderid;
5455 savefolderid = gtk_editable_get_chars(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
5456 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
5457 g_free(savefolderid);
5459 /* Save copy folder */
5460 if (compose->return_receipt) {
5461 err |= (fprintf(fp, "RRCPT:1\n") < 0);
5463 /* Message-ID of message replying to */
5464 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
5467 folderid = folder_item_get_identifier(compose->replyinfo->folder);
5468 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
5471 /* Message-ID of message forwarding to */
5472 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
5475 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
5476 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
5480 /* end of headers */
5481 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
5483 if (compose->redirect_filename != NULL) {
5484 if (compose_redirect_write_to_file(compose, fp) < 0) {
5493 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
5498 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
5502 g_warning("failed to write queue message\n");
5509 if (fclose(fp) == EOF) {
5510 FILE_OP_ERROR(tmp, "fclose");
5517 if (item && *item) {
5520 queue = account_get_special_folder(compose->account, F_QUEUE);
5523 g_warning("can't find queue folder\n");
5529 folder_item_scan(queue);
5530 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
5531 g_warning("can't queue the message\n");
5538 if (msgpath == NULL) {
5544 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
5545 compose_remove_reedit_target(compose, FALSE);
5548 if ((msgnum != NULL) && (item != NULL)) {
5556 static void compose_add_attachments(Compose *compose, MimeInfo *parent)
5559 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
5561 struct stat statbuf;
5562 gchar *type, *subtype;
5563 GtkTreeModel *model;
5566 model = gtk_tree_view_get_model(tree_view);
5568 if (!gtk_tree_model_get_iter_first(model, &iter))
5571 gtk_tree_model_get(model, &iter,
5575 mimepart = procmime_mimeinfo_new();
5576 mimepart->content = MIMECONTENT_FILE;
5577 mimepart->data.filename = g_strdup(ainfo->file);
5578 mimepart->tmp = FALSE; /* or we destroy our attachment */
5579 mimepart->offset = 0;
5581 stat(ainfo->file, &statbuf);
5582 mimepart->length = statbuf.st_size;
5584 type = g_strdup(ainfo->content_type);
5586 if (!strchr(type, '/')) {
5588 type = g_strdup("application/octet-stream");
5591 subtype = strchr(type, '/') + 1;
5592 *(subtype - 1) = '\0';
5593 mimepart->type = procmime_get_media_type(type);
5594 mimepart->subtype = g_strdup(subtype);
5597 if (mimepart->type == MIMETYPE_MESSAGE &&
5598 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
5599 mimepart->disposition = DISPOSITIONTYPE_INLINE;
5602 g_hash_table_insert(mimepart->typeparameters,
5603 g_strdup("name"), g_strdup(ainfo->name));
5604 g_hash_table_insert(mimepart->dispositionparameters,
5605 g_strdup("filename"), g_strdup(ainfo->name));
5606 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
5610 if (compose->use_signing) {
5611 if (ainfo->encoding == ENC_7BIT)
5612 ainfo->encoding = ENC_QUOTED_PRINTABLE;
5613 else if (ainfo->encoding == ENC_8BIT)
5614 ainfo->encoding = ENC_BASE64;
5617 procmime_encode_content(mimepart, ainfo->encoding);
5619 g_node_append(parent->node, mimepart->node);
5620 } while (gtk_tree_model_iter_next(model, &iter));
5623 #define IS_IN_CUSTOM_HEADER(header) \
5624 (compose->account->add_customhdr && \
5625 custom_header_find(compose->account->customhdr_list, header) != NULL)
5627 static void compose_add_headerfield_from_headerlist(Compose *compose,
5629 const gchar *fieldname,
5630 const gchar *seperator)
5632 gchar *str, *fieldname_w_colon;
5633 gboolean add_field = FALSE;
5635 ComposeHeaderEntry *headerentry;
5636 const gchar *headerentryname;
5637 const gchar *trans_fieldname;
5640 if (IS_IN_CUSTOM_HEADER(fieldname))
5643 debug_print("Adding %s-fields\n", fieldname);
5645 fieldstr = g_string_sized_new(64);
5647 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
5648 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
5650 for (list = compose->header_list; list; list = list->next) {
5651 headerentry = ((ComposeHeaderEntry *)list->data);
5652 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_BIN(headerentry->combo)->child));
5654 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
5655 str = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
5657 if (str[0] != '\0') {
5659 g_string_append(fieldstr, seperator);
5660 g_string_append(fieldstr, str);
5669 buf = g_new0(gchar, fieldstr->len * 4 + 256);
5670 compose_convert_header
5671 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
5672 strlen(fieldname) + 2, TRUE);
5673 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
5677 g_free(fieldname_w_colon);
5678 g_string_free(fieldstr, TRUE);
5683 static gchar *compose_get_header(Compose *compose)
5685 gchar buf[BUFFSIZE];
5686 const gchar *entry_str;
5690 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
5692 gchar *from_name = NULL, *from_address = NULL;
5695 g_return_val_if_fail(compose->account != NULL, NULL);
5696 g_return_val_if_fail(compose->account->address != NULL, NULL);
5698 header = g_string_sized_new(64);
5701 get_rfc822_date(buf, sizeof(buf));
5702 g_string_append_printf(header, "Date: %s\n", buf);
5706 if (compose->account->name && *compose->account->name) {
5708 QUOTE_IF_REQUIRED(buf, compose->account->name);
5709 tmp = g_strdup_printf("%s <%s>",
5710 buf, compose->account->address);
5712 tmp = g_strdup_printf("%s",
5713 compose->account->address);
5715 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
5716 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
5718 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
5719 from_address = g_strdup(compose->account->address);
5721 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5722 /* extract name and address */
5723 if (strstr(spec, " <") && strstr(spec, ">")) {
5724 from_address = g_strdup(strrchr(spec, '<')+1);
5725 *(strrchr(from_address, '>')) = '\0';
5726 from_name = g_strdup(spec);
5727 *(strrchr(from_name, '<')) = '\0';
5730 from_address = g_strdup(spec);
5737 if (from_name && *from_name) {
5738 compose_convert_header
5739 (compose, buf, sizeof(buf), from_name,
5740 strlen("From: "), TRUE);
5741 QUOTE_IF_REQUIRED(name, buf);
5743 g_string_append_printf(header, "From: %s <%s>\n",
5744 name, from_address);
5746 g_string_append_printf(header, "From: %s\n", from_address);
5749 g_free(from_address);
5752 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
5755 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
5758 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
5761 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
5764 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
5766 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
5769 compose_convert_header(compose, buf, sizeof(buf), str,
5770 strlen("Subject: "), FALSE);
5771 g_string_append_printf(header, "Subject: %s\n", buf);
5777 if (compose->account->set_domain && compose->account->domain) {
5778 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
5779 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
5780 g_snprintf(buf, sizeof(buf), "%s",
5781 strchr(compose->account->address, '@') ?
5782 strchr(compose->account->address, '@')+1 :
5783 compose->account->address);
5785 g_snprintf(buf, sizeof(buf), "%s", "");
5788 if (compose->account->gen_msgid) {
5789 generate_msgid(buf, sizeof(buf));
5790 g_string_append_printf(header, "Message-ID: <%s>\n", buf);
5791 compose->msgid = g_strdup(buf);
5793 compose->msgid = NULL;
5796 if (compose->remove_references == FALSE) {
5798 if (compose->inreplyto && compose->to_list)
5799 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
5802 if (compose->references)
5803 g_string_append_printf(header, "References: %s\n", compose->references);
5807 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
5810 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
5813 if (compose->account->organization &&
5814 strlen(compose->account->organization) &&
5815 !IS_IN_CUSTOM_HEADER("Organization")) {
5816 compose_convert_header(compose, buf, sizeof(buf),
5817 compose->account->organization,
5818 strlen("Organization: "), FALSE);
5819 g_string_append_printf(header, "Organization: %s\n", buf);
5822 /* Program version and system info */
5823 if (g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
5824 !compose->newsgroup_list) {
5825 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
5827 gtk_major_version, gtk_minor_version, gtk_micro_version,
5830 if (g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
5831 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
5833 gtk_major_version, gtk_minor_version, gtk_micro_version,
5837 /* custom headers */
5838 if (compose->account->add_customhdr) {
5841 for (cur = compose->account->customhdr_list; cur != NULL;
5843 CustomHeader *chdr = (CustomHeader *)cur->data;
5845 if (custom_header_is_allowed(chdr->name)) {
5846 compose_convert_header
5847 (compose, buf, sizeof(buf),
5848 chdr->value ? chdr->value : "",
5849 strlen(chdr->name) + 2, FALSE);
5850 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
5856 switch (compose->priority) {
5857 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
5858 "X-Priority: 1 (Highest)\n");
5860 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
5861 "X-Priority: 2 (High)\n");
5863 case PRIORITY_NORMAL: break;
5864 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
5865 "X-Priority: 4 (Low)\n");
5867 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
5868 "X-Priority: 5 (Lowest)\n");
5870 default: debug_print("compose: priority unknown : %d\n",
5874 /* Request Return Receipt */
5875 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To")) {
5876 if (compose->return_receipt) {
5877 if (compose->account->name
5878 && *compose->account->name) {
5879 compose_convert_header(compose, buf, sizeof(buf),
5880 compose->account->name,
5881 strlen("Disposition-Notification-To: "),
5883 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, compose->account->address);
5885 g_string_append_printf(header, "Disposition-Notification-To: %s\n", compose->account->address);
5889 /* get special headers */
5890 for (list = compose->header_list; list; list = list->next) {
5891 ComposeHeaderEntry *headerentry;
5894 gchar *headername_wcolon;
5895 const gchar *headername_trans;
5898 gboolean standard_header = FALSE;
5900 headerentry = ((ComposeHeaderEntry *)list->data);
5902 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(GTK_BIN(headerentry->combo)->child)));
5904 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
5909 if (!strstr(tmp, ":")) {
5910 headername_wcolon = g_strconcat(tmp, ":", NULL);
5911 headername = g_strdup(tmp);
5913 headername_wcolon = g_strdup(tmp);
5914 headername = g_strdup(strtok(tmp, ":"));
5918 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5919 Xstrdup_a(headervalue, entry_str, return NULL);
5920 subst_char(headervalue, '\r', ' ');
5921 subst_char(headervalue, '\n', ' ');
5922 string = std_headers;
5923 while (*string != NULL) {
5924 headername_trans = prefs_common_translated_header_name(*string);
5925 if (!strcmp(headername_trans, headername_wcolon))
5926 standard_header = TRUE;
5929 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
5930 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
5933 g_free(headername_wcolon);
5937 g_string_free(header, FALSE);
5942 #undef IS_IN_CUSTOM_HEADER
5944 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
5945 gint header_len, gboolean addr_field)
5947 gchar *tmpstr = NULL;
5948 const gchar *out_codeset = NULL;
5950 g_return_if_fail(src != NULL);
5951 g_return_if_fail(dest != NULL);
5953 if (len < 1) return;
5955 tmpstr = g_strdup(src);
5957 subst_char(tmpstr, '\n', ' ');
5958 subst_char(tmpstr, '\r', ' ');
5961 if (!g_utf8_validate(tmpstr, -1, NULL)) {
5962 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
5963 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
5968 codeconv_set_strict(TRUE);
5969 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
5970 conv_get_charset_str(compose->out_encoding));
5971 codeconv_set_strict(FALSE);
5973 if (!dest || *dest == '\0') {
5974 gchar *test_conv_global_out = NULL;
5975 gchar *test_conv_reply = NULL;
5977 /* automatic mode. be automatic. */
5978 codeconv_set_strict(TRUE);
5980 out_codeset = conv_get_outgoing_charset_str();
5982 debug_print("trying to convert to %s\n", out_codeset);
5983 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
5986 if (!test_conv_global_out && compose->orig_charset
5987 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5988 out_codeset = compose->orig_charset;
5989 debug_print("failure; trying to convert to %s\n", out_codeset);
5990 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
5993 if (!test_conv_global_out && !test_conv_reply) {
5995 out_codeset = CS_INTERNAL;
5996 debug_print("finally using %s\n", out_codeset);
5998 g_free(test_conv_global_out);
5999 g_free(test_conv_reply);
6000 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6002 codeconv_set_strict(FALSE);
6007 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
6011 g_return_if_fail(user_data != NULL);
6013 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6014 g_strstrip(address);
6015 if (*address != '\0') {
6016 gchar *name = procheader_get_fromname(address);
6017 extract_address(address);
6018 addressbook_add_contact(name, address, NULL, NULL);
6023 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
6025 GtkWidget *menuitem;
6028 g_return_if_fail(menu != NULL);
6029 g_return_if_fail(GTK_IS_MENU_SHELL(menu));
6031 menuitem = gtk_separator_menu_item_new();
6032 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6033 gtk_widget_show(menuitem);
6035 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
6036 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6038 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
6039 g_strstrip(address);
6040 if (*address == '\0') {
6041 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
6044 g_signal_connect(G_OBJECT(menuitem), "activate",
6045 G_CALLBACK(compose_add_to_addressbook_cb), entry);
6046 gtk_widget_show(menuitem);
6049 static void compose_create_header_entry(Compose *compose)
6051 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6056 const gchar *header = NULL;
6057 ComposeHeaderEntry *headerentry;
6058 gboolean standard_header = FALSE;
6060 headerentry = g_new0(ComposeHeaderEntry, 1);
6063 combo = gtk_combo_box_entry_new_text();
6065 while(*string != NULL) {
6066 gtk_combo_box_append_text(GTK_COMBO_BOX(combo),
6067 (gchar*)prefs_common_translated_header_name(*string));
6070 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
6071 g_signal_connect(G_OBJECT(GTK_BIN(combo)->child), "grab_focus",
6072 G_CALLBACK(compose_grab_focus_cb), compose);
6073 gtk_widget_show(combo);
6074 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
6075 compose->header_nextrow, compose->header_nextrow+1,
6076 GTK_SHRINK, GTK_FILL, 0, 0);
6077 if (compose->header_last) {
6078 const gchar *last_header_entry = gtk_entry_get_text(
6079 GTK_ENTRY(GTK_BIN(compose->header_last->combo)->child));
6081 while (*string != NULL) {
6082 if (!strcmp(*string, last_header_entry))
6083 standard_header = TRUE;
6086 if (standard_header)
6087 header = gtk_entry_get_text(GTK_ENTRY(GTK_BIN(compose->header_last->combo)->child));
6089 if (!compose->header_last || !standard_header) {
6090 switch(compose->account->protocol) {
6092 header = prefs_common_translated_header_name("Newsgroups:");
6095 header = prefs_common_translated_header_name("To:");
6100 gtk_entry_set_text(GTK_ENTRY(GTK_BIN(combo)->child), header);
6102 g_signal_connect_after(G_OBJECT(GTK_BIN(combo)->child), "grab_focus",
6103 G_CALLBACK(compose_grab_focus_cb), compose);
6106 entry = gtk_entry_new();
6107 gtk_widget_show(entry);
6108 gtk_tooltips_set_tip(compose->tooltips, entry,
6109 _("Use <tab> to autocomplete from addressbook"), NULL);
6110 gtk_table_attach(GTK_TABLE(compose->header_table), entry, 1, 2,
6111 compose->header_nextrow, compose->header_nextrow+1,
6112 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
6114 g_signal_connect(G_OBJECT(entry), "key-press-event",
6115 G_CALLBACK(compose_headerentry_key_press_event_cb),
6117 g_signal_connect(G_OBJECT(entry), "changed",
6118 G_CALLBACK(compose_headerentry_changed_cb),
6120 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
6121 G_CALLBACK(compose_grab_focus_cb), compose);
6124 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6125 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6126 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6127 g_signal_connect(G_OBJECT(entry), "drag_data_received",
6128 G_CALLBACK(compose_header_drag_received_cb),
6130 g_signal_connect(G_OBJECT(entry), "drag-drop",
6131 G_CALLBACK(compose_drag_drop),
6133 g_signal_connect(G_OBJECT(entry), "populate-popup",
6134 G_CALLBACK(compose_entry_popup_extend),
6137 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
6139 headerentry->compose = compose;
6140 headerentry->combo = combo;
6141 headerentry->entry = entry;
6142 headerentry->headernum = compose->header_nextrow;
6144 compose->header_nextrow++;
6145 compose->header_last = headerentry;
6146 compose->header_list =
6147 g_slist_append(compose->header_list,
6151 static void compose_add_header_entry(Compose *compose, const gchar *header, gchar *text)
6153 ComposeHeaderEntry *last_header;
6155 last_header = compose->header_last;
6157 gtk_entry_set_text(GTK_ENTRY(GTK_BIN(last_header->combo)->child), header);
6158 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
6161 static void compose_remove_header_entries(Compose *compose)
6164 for (list = compose->header_list; list; list = list->next) {
6165 ComposeHeaderEntry *headerentry =
6166 (ComposeHeaderEntry *)list->data;
6167 gtk_widget_destroy(headerentry->combo);
6168 gtk_widget_destroy(headerentry->entry);
6169 g_free(headerentry);
6171 compose->header_last = NULL;
6172 g_slist_free(compose->header_list);
6173 compose->header_list = NULL;
6174 compose->header_nextrow = 1;
6175 compose_create_header_entry(compose);
6178 static GtkWidget *compose_create_header(Compose *compose)
6180 GtkWidget *from_optmenu_hbox;
6181 GtkWidget *header_scrolledwin;
6182 GtkWidget *header_table;
6186 /* header labels and entries */
6187 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
6188 gtk_widget_show(header_scrolledwin);
6189 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
6191 header_table = gtk_table_new(2, 2, FALSE);
6192 gtk_widget_show(header_table);
6193 gtk_container_set_border_width(GTK_CONTAINER(header_table), BORDER_WIDTH);
6194 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
6195 gtk_viewport_set_shadow_type(GTK_VIEWPORT(GTK_BIN(header_scrolledwin)->child), GTK_SHADOW_NONE);
6198 /* option menu for selecting accounts */
6199 from_optmenu_hbox = compose_account_option_menu_create(compose);
6200 gtk_table_attach(GTK_TABLE(header_table), from_optmenu_hbox,
6201 0, 2, count, count + 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
6204 compose->header_table = header_table;
6205 compose->header_list = NULL;
6206 compose->header_nextrow = count;
6208 compose_create_header_entry(compose);
6210 compose->table = NULL;
6212 return header_scrolledwin ;
6215 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
6217 Compose *compose = (Compose *)data;
6218 GdkEventButton event;
6221 event.time = gtk_get_current_event_time();
6223 return attach_button_pressed(compose->attach_clist, &event, compose);
6226 static GtkWidget *compose_create_attach(Compose *compose)
6228 GtkWidget *attach_scrwin;
6229 GtkWidget *attach_clist;
6231 GtkListStore *store;
6232 GtkCellRenderer *renderer;
6233 GtkTreeViewColumn *column;
6234 GtkTreeSelection *selection;
6236 /* attachment list */
6237 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
6238 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
6239 GTK_POLICY_AUTOMATIC,
6240 GTK_POLICY_AUTOMATIC);
6241 gtk_widget_set_size_request(attach_scrwin, -1, 80);
6243 store = gtk_list_store_new(N_ATTACH_COLS,
6248 G_TYPE_AUTO_POINTER,
6250 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
6251 (GTK_TREE_MODEL(store)));
6252 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
6253 g_object_unref(store);
6255 renderer = gtk_cell_renderer_text_new();
6256 column = gtk_tree_view_column_new_with_attributes
6257 (_("Mime type"), renderer, "text",
6258 COL_MIMETYPE, NULL);
6259 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6261 renderer = gtk_cell_renderer_text_new();
6262 column = gtk_tree_view_column_new_with_attributes
6263 (_("Size"), renderer, "text",
6265 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6267 renderer = gtk_cell_renderer_text_new();
6268 column = gtk_tree_view_column_new_with_attributes
6269 (_("Name"), renderer, "text",
6271 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6273 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
6274 prefs_common.use_stripes_everywhere);
6275 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
6276 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
6278 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
6279 G_CALLBACK(attach_selected), compose);
6280 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
6281 G_CALLBACK(attach_button_pressed), compose);
6283 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
6284 G_CALLBACK(popup_attach_button_pressed), compose);
6286 gtk_widget_tap_and_hold_setup(GTK_WIDGET(attach_clist), NULL, NULL,
6287 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
6288 g_signal_connect(G_OBJECT(attach_clist), "tap-and-hold",
6289 G_CALLBACK(popup_attach_button_pressed), compose);
6291 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
6292 G_CALLBACK(attach_key_pressed), compose);
6295 gtk_drag_dest_set(attach_clist,
6296 GTK_DEST_DEFAULT_ALL, compose_mime_types,
6297 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6298 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6299 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
6300 G_CALLBACK(compose_attach_drag_received_cb),
6302 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
6303 G_CALLBACK(compose_drag_drop),
6306 compose->attach_scrwin = attach_scrwin;
6307 compose->attach_clist = attach_clist;
6309 return attach_scrwin;
6312 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
6313 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
6315 static GtkWidget *compose_create_others(Compose *compose)
6318 GtkWidget *savemsg_checkbtn;
6319 GtkWidget *savemsg_entry;
6320 GtkWidget *savemsg_select;
6323 gchar *folderidentifier;
6325 /* Table for settings */
6326 table = gtk_table_new(3, 1, FALSE);
6327 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
6328 gtk_widget_show(table);
6329 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
6332 /* Save Message to folder */
6333 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
6334 gtk_widget_show(savemsg_checkbtn);
6335 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6336 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6337 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
6339 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
6340 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
6342 savemsg_entry = gtk_entry_new();
6343 gtk_widget_show(savemsg_entry);
6344 gtk_table_attach_defaults(GTK_TABLE(table), savemsg_entry, 1, 2, rowcount, rowcount + 1);
6345 gtk_editable_set_editable(GTK_EDITABLE(savemsg_entry), prefs_common.savemsg);
6346 g_signal_connect_after(G_OBJECT(savemsg_entry), "grab_focus",
6347 G_CALLBACK(compose_grab_focus_cb), compose);
6348 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6349 folderidentifier = folder_item_get_identifier(account_get_special_folder
6350 (compose->account, F_OUTBOX));
6351 gtk_entry_set_text(GTK_ENTRY(savemsg_entry), folderidentifier);
6352 g_free(folderidentifier);
6355 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
6356 gtk_widget_show(savemsg_select);
6357 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6358 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
6359 G_CALLBACK(compose_savemsg_select_cb),
6364 compose->savemsg_checkbtn = savemsg_checkbtn;
6365 compose->savemsg_entry = savemsg_entry;
6370 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
6372 gtk_editable_set_editable(GTK_EDITABLE(compose->savemsg_entry),
6373 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
6376 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
6381 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL);
6384 path = folder_item_get_identifier(dest);
6386 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), path);
6390 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
6391 GdkAtom clip, GtkTextIter *insert_place);
6394 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
6398 GtkTextBuffer *buffer = GTK_TEXT_VIEW(text)->buffer;
6400 if (event->button == 3) {
6402 GtkTextIter sel_start, sel_end;
6403 gboolean stuff_selected;
6405 /* move the cursor to allow GtkAspell to check the word
6406 * under the mouse */
6407 if (event->x && event->y) {
6408 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6409 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6411 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6414 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
6415 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
6418 stuff_selected = gtk_text_buffer_get_selection_bounds(
6420 &sel_start, &sel_end);
6422 gtk_text_buffer_place_cursor (buffer, &iter);
6423 /* reselect stuff */
6425 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
6426 gtk_text_buffer_select_range(buffer,
6427 &sel_start, &sel_end);
6429 return FALSE; /* pass the event so that the right-click goes through */
6432 if (event->button == 2) {
6437 /* get the middle-click position to paste at the correct place */
6438 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6439 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6441 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6444 entry_paste_clipboard(compose, text,
6445 prefs_common.linewrap_pastes,
6446 GDK_SELECTION_PRIMARY, &iter);
6454 static void compose_spell_menu_changed(void *data)
6456 Compose *compose = (Compose *)data;
6458 GtkWidget *menuitem;
6459 GtkWidget *parent_item;
6460 GtkMenu *menu = GTK_MENU(gtk_menu_new());
6461 GtkItemFactory *ifactory = gtk_item_factory_from_widget(compose->menubar);
6464 if (compose->gtkaspell == NULL)
6467 parent_item = gtk_item_factory_get_item(ifactory,
6468 "/Spelling/Options");
6470 /* setting the submenu removes /Spelling/Options from the factory
6471 * so we need to save it */
6473 if (parent_item == NULL) {
6474 parent_item = compose->aspell_options_menu;
6475 gtk_menu_item_remove_submenu(GTK_MENU_ITEM(parent_item));
6477 compose->aspell_options_menu = parent_item;
6479 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
6481 spell_menu = g_slist_reverse(spell_menu);
6482 for (items = spell_menu;
6483 items; items = items->next) {
6484 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
6485 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
6486 gtk_widget_show(GTK_WIDGET(menuitem));
6488 g_slist_free(spell_menu);
6490 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
6495 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
6497 Compose *compose = (Compose *)data;
6498 GdkEventButton event;
6501 event.time = gtk_get_current_event_time();
6505 return text_clicked(compose->text, &event, compose);
6508 static gboolean compose_force_window_origin = TRUE;
6509 static Compose *compose_create(PrefsAccount *account,
6518 GtkWidget *handlebox;
6520 GtkWidget *notebook;
6522 GtkWidget *attach_hbox;
6523 GtkWidget *attach_lab1;
6524 GtkWidget *attach_lab2;
6529 GtkWidget *subject_hbox;
6530 GtkWidget *subject_frame;
6531 GtkWidget *subject_entry;
6535 GtkWidget *edit_vbox;
6536 GtkWidget *ruler_hbox;
6538 GtkWidget *scrolledwin;
6540 GtkTextBuffer *buffer;
6541 GtkClipboard *clipboard;
6543 UndoMain *undostruct;
6545 gchar *titles[N_ATTACH_COLS];
6546 guint n_menu_entries;
6547 GtkWidget *popupmenu;
6548 GtkItemFactory *popupfactory;
6549 GtkItemFactory *ifactory;
6550 GtkWidget *tmpl_menu;
6552 GtkWidget *menuitem;
6555 GtkAspell * gtkaspell = NULL;
6558 static GdkGeometry geometry;
6560 g_return_val_if_fail(account != NULL, NULL);
6562 debug_print("Creating compose window...\n");
6563 compose = g_new0(Compose, 1);
6565 titles[COL_MIMETYPE] = _("MIME type");
6566 titles[COL_SIZE] = _("Size");
6567 titles[COL_NAME] = _("Name");
6569 compose->batch = batch;
6570 compose->account = account;
6571 compose->folder = folder;
6573 compose->mutex = g_mutex_new();
6574 compose->set_cursor_pos = -1;
6576 compose->tooltips = gtk_tooltips_new();
6578 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
6580 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
6581 gtk_widget_set_size_request(window, -1, prefs_common.compose_height);
6583 if (!geometry.max_width) {
6584 geometry.max_width = gdk_screen_width();
6585 geometry.max_height = gdk_screen_height();
6588 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
6589 &geometry, GDK_HINT_MAX_SIZE);
6590 if (!geometry.min_width) {
6591 geometry.min_width = 600;
6592 geometry.min_height = 480;
6594 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
6595 &geometry, GDK_HINT_MIN_SIZE);
6598 if (compose_force_window_origin)
6599 gtk_widget_set_uposition(window, prefs_common.compose_x,
6600 prefs_common.compose_y);
6602 g_signal_connect(G_OBJECT(window), "delete_event",
6603 G_CALLBACK(compose_delete_cb), compose);
6604 MANAGE_WINDOW_SIGNALS_CONNECT(window);
6605 gtk_widget_realize(window);
6607 gtkut_widget_set_composer_icon(window);
6609 vbox = gtk_vbox_new(FALSE, 0);
6610 gtk_container_add(GTK_CONTAINER(window), vbox);
6612 n_menu_entries = sizeof(compose_entries) / sizeof(compose_entries[0]);
6613 menubar = menubar_create(window, compose_entries,
6614 n_menu_entries, "<Compose>", compose);
6615 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
6617 if (prefs_common.toolbar_detachable) {
6618 handlebox = gtk_handle_box_new();
6620 handlebox = gtk_hbox_new(FALSE, 0);
6622 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
6624 gtk_widget_realize(handlebox);
6626 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, window,
6629 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
6633 vbox2 = gtk_vbox_new(FALSE, 2);
6634 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
6635 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
6638 notebook = gtk_notebook_new();
6639 gtk_widget_set_size_request(notebook, -1, 130);
6640 gtk_widget_show(notebook);
6642 /* header labels and entries */
6643 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6644 compose_create_header(compose),
6645 gtk_label_new_with_mnemonic(_("Hea_der")));
6646 /* attachment list */
6647 attach_hbox = gtk_hbox_new(FALSE, 0);
6648 gtk_widget_show(attach_hbox);
6650 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
6651 gtk_widget_show(attach_lab1);
6652 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
6654 attach_lab2 = gtk_label_new("");
6655 gtk_widget_show(attach_lab2);
6656 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
6658 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6659 compose_create_attach(compose),
6662 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6663 compose_create_others(compose),
6664 gtk_label_new_with_mnemonic(_("Othe_rs")));
6667 subject_hbox = gtk_hbox_new(FALSE, 0);
6668 gtk_widget_show(subject_hbox);
6670 subject_frame = gtk_frame_new(NULL);
6671 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
6672 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
6673 gtk_widget_show(subject_frame);
6675 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
6676 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
6677 gtk_widget_show(subject);
6679 label = gtk_label_new(_("Subject:"));
6680 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
6681 gtk_widget_show(label);
6683 subject_entry = gtk_entry_new();
6684 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
6685 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
6686 G_CALLBACK(compose_grab_focus_cb), compose);
6687 gtk_widget_show(subject_entry);
6688 compose->subject_entry = subject_entry;
6689 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
6691 edit_vbox = gtk_vbox_new(FALSE, 0);
6693 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
6696 ruler_hbox = gtk_hbox_new(FALSE, 0);
6697 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
6699 ruler = gtk_shruler_new();
6700 gtk_ruler_set_range(GTK_RULER(ruler), 0.0, 100.0, 1.0, 100.0);
6701 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
6705 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
6706 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
6707 GTK_POLICY_AUTOMATIC,
6708 GTK_POLICY_AUTOMATIC);
6709 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
6711 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
6712 gtk_widget_set_size_request(scrolledwin, prefs_common.compose_width, -1);
6714 text = gtk_text_view_new();
6715 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
6716 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
6717 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
6718 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
6719 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
6721 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
6723 g_signal_connect_after(G_OBJECT(text), "size_allocate",
6724 G_CALLBACK(compose_edit_size_alloc),
6726 g_signal_connect(G_OBJECT(buffer), "changed",
6727 G_CALLBACK(compose_changed_cb), compose);
6728 g_signal_connect(G_OBJECT(text), "grab_focus",
6729 G_CALLBACK(compose_grab_focus_cb), compose);
6730 g_signal_connect(G_OBJECT(buffer), "insert_text",
6731 G_CALLBACK(text_inserted), compose);
6732 g_signal_connect(G_OBJECT(text), "button_press_event",
6733 G_CALLBACK(text_clicked), compose);
6735 g_signal_connect(G_OBJECT(text), "popup-menu",
6736 G_CALLBACK(compose_popup_menu), compose);
6738 gtk_widget_tap_and_hold_setup(GTK_WIDGET(text), NULL, NULL,
6739 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
6740 g_signal_connect(G_OBJECT(text), "tap-and-hold",
6741 G_CALLBACK(compose_popup_menu), compose);
6743 g_signal_connect(G_OBJECT(subject_entry), "changed",
6744 G_CALLBACK(compose_changed_cb), compose);
6747 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6748 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6749 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6750 g_signal_connect(G_OBJECT(text), "drag_data_received",
6751 G_CALLBACK(compose_insert_drag_received_cb),
6753 g_signal_connect(G_OBJECT(text), "drag-drop",
6754 G_CALLBACK(compose_drag_drop),
6756 gtk_widget_show_all(vbox);
6758 /* pane between attach clist and text */
6759 paned = gtk_vpaned_new();
6760 gtk_paned_set_gutter_size(GTK_PANED(paned), 12);
6761 gtk_container_add(GTK_CONTAINER(vbox2), paned);
6763 if( maemo_mainwindow_is_fullscreen(mainwindow_get_mainwindow()->window) )
6764 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 300 : 280);
6766 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 250 : 230);
6768 gtk_paned_add1(GTK_PANED(paned), notebook);
6769 gtk_paned_add2(GTK_PANED(paned), edit_vbox);
6770 gtk_widget_show_all(paned);
6773 if (prefs_common.textfont) {
6774 PangoFontDescription *font_desc;
6776 font_desc = pango_font_description_from_string
6777 (prefs_common.textfont);
6779 gtk_widget_modify_font(text, font_desc);
6780 pango_font_description_free(font_desc);
6784 n_entries = sizeof(compose_popup_entries) /
6785 sizeof(compose_popup_entries[0]);
6786 popupmenu = menu_create_items(compose_popup_entries, n_entries,
6787 "<Compose>", &popupfactory,
6790 ifactory = gtk_item_factory_from_widget(menubar);
6791 menu_set_sensitive(ifactory, "/Edit/Undo", FALSE);
6792 menu_set_sensitive(ifactory, "/Edit/Redo", FALSE);
6793 menu_set_sensitive(ifactory, "/Options/Remove references", FALSE);
6795 tmpl_menu = gtk_item_factory_get_item(ifactory, "/Tools/Template");
6797 undostruct = undo_init(text);
6798 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
6801 address_completion_start(window);
6803 compose->window = window;
6804 compose->vbox = vbox;
6805 compose->menubar = menubar;
6806 compose->handlebox = handlebox;
6808 compose->vbox2 = vbox2;
6810 compose->paned = paned;
6812 compose->attach_label = attach_lab2;
6814 compose->notebook = notebook;
6815 compose->edit_vbox = edit_vbox;
6816 compose->ruler_hbox = ruler_hbox;
6817 compose->ruler = ruler;
6818 compose->scrolledwin = scrolledwin;
6819 compose->text = text;
6821 compose->focused_editable = NULL;
6823 compose->popupmenu = popupmenu;
6824 compose->popupfactory = popupfactory;
6826 compose->tmpl_menu = tmpl_menu;
6828 compose->mode = mode;
6829 compose->rmode = mode;
6831 compose->targetinfo = NULL;
6832 compose->replyinfo = NULL;
6833 compose->fwdinfo = NULL;
6835 compose->replyto = NULL;
6837 compose->bcc = NULL;
6838 compose->followup_to = NULL;
6840 compose->ml_post = NULL;
6842 compose->inreplyto = NULL;
6843 compose->references = NULL;
6844 compose->msgid = NULL;
6845 compose->boundary = NULL;
6847 compose->autowrap = prefs_common.autowrap;
6849 compose->use_signing = FALSE;
6850 compose->use_encryption = FALSE;
6851 compose->privacy_system = NULL;
6853 compose->modified = FALSE;
6855 compose->return_receipt = FALSE;
6857 compose->to_list = NULL;
6858 compose->newsgroup_list = NULL;
6860 compose->undostruct = undostruct;
6862 compose->sig_str = NULL;
6864 compose->exteditor_file = NULL;
6865 compose->exteditor_pid = -1;
6866 compose->exteditor_tag = -1;
6867 compose->draft_timeout_tag = -2; /* inhibit auto-drafting while loading */
6870 menu_set_sensitive(ifactory, "/Spelling", FALSE);
6871 if (mode != COMPOSE_REDIRECT) {
6872 if (prefs_common.enable_aspell && prefs_common.dictionary &&
6873 strcmp(prefs_common.dictionary, "")) {
6874 gtkaspell = gtkaspell_new(prefs_common.aspell_path,
6875 prefs_common.dictionary,
6876 prefs_common.alt_dictionary,
6877 conv_get_locale_charset_str(),
6878 prefs_common.misspelled_col,
6879 prefs_common.check_while_typing,
6880 prefs_common.recheck_when_changing_dict,
6881 prefs_common.use_alternate,
6882 prefs_common.use_both_dicts,
6883 GTK_TEXT_VIEW(text),
6884 GTK_WINDOW(compose->window),
6885 compose_spell_menu_changed,
6888 alertpanel_error(_("Spell checker could not "
6890 gtkaspell_checkers_strerror());
6891 gtkaspell_checkers_reset_error();
6893 if (!gtkaspell_set_sug_mode(gtkaspell,
6894 prefs_common.aspell_sugmode)) {
6895 debug_print("Aspell: could not set "
6896 "suggestion mode %s\n",
6897 gtkaspell_checkers_strerror());
6898 gtkaspell_checkers_reset_error();
6901 menu_set_sensitive(ifactory, "/Spelling", TRUE);
6905 compose->gtkaspell = gtkaspell;
6906 compose_spell_menu_changed(compose);
6909 compose_select_account(compose, account, TRUE);
6911 menu_set_active(ifactory, "/Edit/Auto wrapping", prefs_common.autowrap);
6912 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
6913 compose_entry_append(compose, account->auto_cc, COMPOSE_CC);
6915 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
6916 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC);
6918 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
6919 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO);
6921 menu_set_sensitive(ifactory, "/Options/Reply mode", compose->mode == COMPOSE_REPLY);
6923 if (account->protocol != A_NNTP)
6924 gtk_entry_set_text(GTK_ENTRY(GTK_BIN(compose->header_last->combo)->child),
6925 prefs_common_translated_header_name("To:"));
6927 gtk_entry_set_text(GTK_ENTRY(GTK_BIN(compose->header_last->combo)->child),
6928 prefs_common_translated_header_name("Newsgroups:"));
6930 addressbook_set_target_compose(compose);
6932 if (mode != COMPOSE_REDIRECT)
6933 compose_set_template_menu(compose);
6935 menuitem = gtk_item_factory_get_item(ifactory, "/Tools/Template");
6936 menu_set_sensitive(ifactory, "/Tools/Template", FALSE);
6939 compose_list = g_list_append(compose_list, compose);
6941 if (!prefs_common.show_ruler)
6942 gtk_widget_hide(ruler_hbox);
6944 menuitem = gtk_item_factory_get_item(ifactory, "/Tools/Show ruler");
6945 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
6946 prefs_common.show_ruler);
6949 compose->priority = PRIORITY_NORMAL;
6950 compose_update_priority_menu_item(compose);
6952 compose_set_out_encoding(compose);
6955 compose_update_actions_menu(compose);
6957 /* Privacy Systems menu */
6958 compose_update_privacy_systems_menu(compose);
6960 activate_privacy_system(compose, account, TRUE);
6961 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
6963 gtk_widget_realize(window);
6965 gtk_widget_show(window);
6967 maemo_window_full_screen_if_needed(GTK_WINDOW(window));
6968 maemo_connect_key_press_to_mainwindow(GTK_WINDOW(window));
6975 static GtkWidget *compose_account_option_menu_create(Compose *compose)
6980 GtkWidget *optmenubox;
6983 GtkWidget *from_name = NULL;
6985 gint num = 0, def_menu = 0;
6987 accounts = account_get_list();
6988 g_return_val_if_fail(accounts != NULL, NULL);
6990 optmenubox = gtk_event_box_new();
6991 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
6992 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
6994 hbox = gtk_hbox_new(FALSE, 6);
6995 from_name = gtk_entry_new();
6997 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
6998 G_CALLBACK(compose_grab_focus_cb), compose);
7000 for (; accounts != NULL; accounts = accounts->next, num++) {
7001 PrefsAccount *ac = (PrefsAccount *)accounts->data;
7002 gchar *name, *from = NULL;
7004 if (ac == compose->account) def_menu = num;
7006 name = g_markup_printf_escaped(_("From: <i>%s</i>"),
7009 if (ac == compose->account) {
7010 if (ac->name && *ac->name) {
7012 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
7013 from = g_strdup_printf("%s <%s>",
7015 gtk_entry_set_text(GTK_ENTRY(from_name), from);
7017 from = g_strdup_printf("%s",
7019 gtk_entry_set_text(GTK_ENTRY(from_name), from);
7022 COMBOBOX_ADD(menu, name, ac->account_id);
7027 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
7029 g_signal_connect(G_OBJECT(optmenu), "changed",
7030 G_CALLBACK(account_activated),
7032 g_signal_connect(G_OBJECT(from_name), "populate-popup",
7033 G_CALLBACK(compose_entry_popup_extend),
7036 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
7037 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
7039 gtk_tooltips_set_tip(compose->tooltips, optmenubox,
7040 _("Account to use for this email"), NULL);
7041 gtk_tooltips_set_tip(compose->tooltips, from_name,
7042 _("Sender address to be used"), NULL);
7044 compose->from_name = from_name;
7049 static void compose_set_priority_cb(gpointer data,
7053 Compose *compose = (Compose *) data;
7054 compose->priority = action;
7057 static void compose_reply_change_mode(gpointer data,
7061 Compose *compose = (Compose *) data;
7062 gboolean was_modified = compose->modified;
7064 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
7066 g_return_if_fail(compose->replyinfo != NULL);
7068 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
7070 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
7072 if (action == COMPOSE_REPLY_TO_ALL)
7074 if (action == COMPOSE_REPLY_TO_SENDER)
7076 if (action == COMPOSE_REPLY_TO_LIST)
7079 compose_remove_header_entries(compose);
7080 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
7081 if (compose->account->set_autocc && compose->account->auto_cc)
7082 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC);
7084 if (compose->account->set_autobcc && compose->account->auto_bcc)
7085 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC);
7087 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
7088 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO);
7089 compose_show_first_last_header(compose, TRUE);
7090 compose->modified = was_modified;
7091 compose_set_title(compose);
7094 static void compose_update_priority_menu_item(Compose * compose)
7096 GtkItemFactory *ifactory;
7097 GtkWidget *menuitem = NULL;
7099 ifactory = gtk_item_factory_from_widget(compose->menubar);
7101 switch (compose->priority) {
7102 case PRIORITY_HIGHEST:
7103 menuitem = gtk_item_factory_get_item
7104 (ifactory, "/Options/Priority/Highest");
7107 menuitem = gtk_item_factory_get_item
7108 (ifactory, "/Options/Priority/High");
7110 case PRIORITY_NORMAL:
7111 menuitem = gtk_item_factory_get_item
7112 (ifactory, "/Options/Priority/Normal");
7115 menuitem = gtk_item_factory_get_item
7116 (ifactory, "/Options/Priority/Low");
7118 case PRIORITY_LOWEST:
7119 menuitem = gtk_item_factory_get_item
7120 (ifactory, "/Options/Priority/Lowest");
7123 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
7126 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
7128 Compose *compose = (Compose *) data;
7130 GtkItemFactory *ifactory;
7131 gboolean can_sign = FALSE, can_encrypt = FALSE;
7133 g_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
7135 if (!GTK_CHECK_MENU_ITEM(widget)->active)
7138 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
7139 g_free(compose->privacy_system);
7140 compose->privacy_system = NULL;
7141 if (systemid != NULL) {
7142 compose->privacy_system = g_strdup(systemid);
7144 can_sign = privacy_system_can_sign(systemid);
7145 can_encrypt = privacy_system_can_encrypt(systemid);
7148 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
7150 ifactory = gtk_item_factory_from_widget(compose->menubar);
7151 menu_set_sensitive(ifactory, "/Options/Sign", can_sign);
7152 menu_set_sensitive(ifactory, "/Options/Encrypt", can_encrypt);
7155 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
7157 static gchar *branch_path = "/Options/Privacy System";
7158 GtkItemFactory *ifactory;
7159 GtkWidget *menuitem = NULL;
7161 gboolean can_sign = FALSE, can_encrypt = FALSE;
7162 gboolean found = FALSE;
7164 ifactory = gtk_item_factory_from_widget(compose->menubar);
7166 if (compose->privacy_system != NULL) {
7168 menuitem = gtk_item_factory_get_widget(ifactory, branch_path);
7169 g_return_if_fail(menuitem != NULL);
7171 amenu = GTK_MENU_SHELL(menuitem)->children;
7173 while (amenu != NULL) {
7174 GList *alist = amenu->next;
7176 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
7177 if (systemid != NULL) {
7178 if (strcmp(systemid, compose->privacy_system) == 0) {
7179 menuitem = GTK_WIDGET(amenu->data);
7181 can_sign = privacy_system_can_sign(systemid);
7182 can_encrypt = privacy_system_can_encrypt(systemid);
7186 } else if (strlen(compose->privacy_system) == 0) {
7187 menuitem = GTK_WIDGET(amenu->data);
7190 can_encrypt = FALSE;
7197 if (menuitem != NULL)
7198 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
7200 if (warn && !found && strlen(compose->privacy_system)) {
7201 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
7202 "will not be able to sign or encrypt this message."),
7203 compose->privacy_system);
7207 menu_set_sensitive(ifactory, "/Options/Sign", can_sign);
7208 menu_set_sensitive(ifactory, "/Options/Encrypt", can_encrypt);
7211 static void compose_set_out_encoding(Compose *compose)
7213 GtkItemFactoryEntry *entry;
7214 GtkItemFactory *ifactory;
7215 CharSet out_encoding;
7216 gchar *path, *p, *q;
7219 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
7220 ifactory = gtk_item_factory_from_widget(compose->menubar);
7222 for (entry = compose_entries; entry->callback != compose_address_cb;
7224 if (entry->callback == compose_set_encoding_cb &&
7225 (CharSet)entry->callback_action == out_encoding) {
7226 p = q = path = g_strdup(entry->path);
7238 item = gtk_item_factory_get_item(ifactory, path);
7239 gtk_widget_activate(item);
7246 static void compose_set_template_menu(Compose *compose)
7248 GSList *tmpl_list, *cur;
7252 tmpl_list = template_get_config();
7254 menu = gtk_menu_new();
7256 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
7257 Template *tmpl = (Template *)cur->data;
7259 item = gtk_menu_item_new_with_label(tmpl->name);
7260 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
7261 g_signal_connect(G_OBJECT(item), "activate",
7262 G_CALLBACK(compose_template_activate_cb),
7264 g_object_set_data(G_OBJECT(item), "template", tmpl);
7265 gtk_widget_show(item);
7268 gtk_widget_show(menu);
7269 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
7272 void compose_update_actions_menu(Compose *compose)
7274 GtkItemFactory *ifactory;
7276 ifactory = gtk_item_factory_from_widget(compose->menubar);
7277 action_update_compose_menu(ifactory, "/Tools/Actions", compose);
7280 static void compose_update_privacy_systems_menu(Compose *compose)
7282 static gchar *branch_path = "/Options/Privacy System";
7283 GtkItemFactory *ifactory;
7284 GtkWidget *menuitem;
7285 GSList *systems, *cur;
7288 GtkWidget *system_none;
7291 ifactory = gtk_item_factory_from_widget(compose->menubar);
7293 /* remove old entries */
7294 menuitem = gtk_item_factory_get_widget(ifactory, branch_path);
7295 g_return_if_fail(menuitem != NULL);
7297 amenu = GTK_MENU_SHELL(menuitem)->children->next;
7298 while (amenu != NULL) {
7299 GList *alist = amenu->next;
7300 gtk_widget_destroy(GTK_WIDGET(amenu->data));
7304 system_none = gtk_item_factory_get_widget(ifactory,
7305 "/Options/Privacy System/None");
7307 g_signal_connect(G_OBJECT(system_none), "activate",
7308 G_CALLBACK(compose_set_privacy_system_cb), compose);
7310 systems = privacy_get_system_ids();
7311 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
7312 gchar *systemid = cur->data;
7314 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
7315 widget = gtk_radio_menu_item_new_with_label(group,
7316 privacy_system_get_name(systemid));
7317 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
7318 g_strdup(systemid), g_free);
7319 g_signal_connect(G_OBJECT(widget), "activate",
7320 G_CALLBACK(compose_set_privacy_system_cb), compose);
7322 gtk_menu_append(GTK_MENU(system_none->parent), widget);
7323 gtk_widget_show(widget);
7326 g_slist_free(systems);
7329 void compose_reflect_prefs_all(void)
7334 for (cur = compose_list; cur != NULL; cur = cur->next) {
7335 compose = (Compose *)cur->data;
7336 compose_set_template_menu(compose);
7340 void compose_reflect_prefs_pixmap_theme(void)
7345 for (cur = compose_list; cur != NULL; cur = cur->next) {
7346 compose = (Compose *)cur->data;
7347 toolbar_update(TOOLBAR_COMPOSE, compose);
7351 static const gchar *compose_quote_char_from_context(Compose *compose)
7353 const gchar *qmark = NULL;
7355 g_return_val_if_fail(compose != NULL, NULL);
7357 switch (compose->mode) {
7358 /* use forward-specific quote char */
7359 case COMPOSE_FORWARD:
7360 case COMPOSE_FORWARD_AS_ATTACH:
7361 case COMPOSE_FORWARD_INLINE:
7362 if (compose->folder && compose->folder->prefs &&
7363 compose->folder->prefs->forward_with_format)
7364 qmark = compose->folder->prefs->forward_quotemark;
7365 else if (compose->account->forward_with_format)
7366 qmark = compose->account->forward_quotemark;
7368 qmark = prefs_common.fw_quotemark;
7371 /* use reply-specific quote char in all other modes */
7373 if (compose->folder && compose->folder->prefs &&
7374 compose->folder->prefs->reply_with_format)
7375 qmark = compose->folder->prefs->reply_quotemark;
7376 else if (compose->account->reply_with_format)
7377 qmark = compose->account->reply_quotemark;
7379 qmark = prefs_common.quotemark;
7383 if (qmark == NULL || *qmark == '\0')
7389 static void compose_template_apply(Compose *compose, Template *tmpl,
7393 GtkTextBuffer *buffer;
7397 gchar *parsed_str = NULL;
7398 gint cursor_pos = 0;
7399 const gchar *err_msg = _("Template body format error at line %d.");
7402 /* process the body */
7404 text = GTK_TEXT_VIEW(compose->text);
7405 buffer = gtk_text_view_get_buffer(text);
7408 qmark = compose_quote_char_from_context(compose);
7410 if (compose->replyinfo != NULL) {
7413 gtk_text_buffer_set_text(buffer, "", -1);
7414 mark = gtk_text_buffer_get_insert(buffer);
7415 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7417 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
7418 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
7420 } else if (compose->fwdinfo != NULL) {
7423 gtk_text_buffer_set_text(buffer, "", -1);
7424 mark = gtk_text_buffer_get_insert(buffer);
7425 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7427 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
7428 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
7431 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
7433 GtkTextIter start, end;
7436 gtk_text_buffer_get_start_iter(buffer, &start);
7437 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
7438 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
7440 /* clear the buffer now */
7442 gtk_text_buffer_set_text(buffer, "", -1);
7444 parsed_str = compose_quote_fmt(compose, dummyinfo,
7445 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
7446 procmsg_msginfo_free( dummyinfo );
7452 gtk_text_buffer_set_text(buffer, "", -1);
7453 mark = gtk_text_buffer_get_insert(buffer);
7454 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7457 if (replace && parsed_str && compose->account->auto_sig)
7458 compose_insert_sig(compose, FALSE);
7460 if (replace && parsed_str) {
7461 gtk_text_buffer_get_start_iter(buffer, &iter);
7462 gtk_text_buffer_place_cursor(buffer, &iter);
7466 cursor_pos = quote_fmt_get_cursor_pos();
7467 compose->set_cursor_pos = cursor_pos;
7468 if (cursor_pos == -1)
7470 gtk_text_buffer_get_start_iter(buffer, &iter);
7471 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
7472 gtk_text_buffer_place_cursor(buffer, &iter);
7475 /* process the other fields */
7477 compose_template_apply_fields(compose, tmpl);
7478 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
7479 quote_fmt_reset_vartable();
7480 compose_changed_cb(NULL, compose);
7483 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
7485 MsgInfo* dummyinfo = NULL;
7486 MsgInfo *msginfo = NULL;
7489 if (compose->replyinfo != NULL)
7490 msginfo = compose->replyinfo;
7491 else if (compose->fwdinfo != NULL)
7492 msginfo = compose->fwdinfo;
7494 dummyinfo = compose_msginfo_new_from_compose(compose);
7495 msginfo = dummyinfo;
7498 if (tmpl->to && *tmpl->to != '\0') {
7500 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7501 compose->gtkaspell);
7503 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7505 quote_fmt_scan_string(tmpl->to);
7508 buf = quote_fmt_get_buffer();
7510 alertpanel_error(_("Template To format error."));
7512 compose_entry_append(compose, buf, COMPOSE_TO);
7516 if (tmpl->cc && *tmpl->cc != '\0') {
7518 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7519 compose->gtkaspell);
7521 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7523 quote_fmt_scan_string(tmpl->cc);
7526 buf = quote_fmt_get_buffer();
7528 alertpanel_error(_("Template Cc format error."));
7530 compose_entry_append(compose, buf, COMPOSE_CC);
7534 if (tmpl->bcc && *tmpl->bcc != '\0') {
7536 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7537 compose->gtkaspell);
7539 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7541 quote_fmt_scan_string(tmpl->bcc);
7544 buf = quote_fmt_get_buffer();
7546 alertpanel_error(_("Template Bcc format error."));
7548 compose_entry_append(compose, buf, COMPOSE_BCC);
7552 /* process the subject */
7553 if (tmpl->subject && *tmpl->subject != '\0') {
7555 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7556 compose->gtkaspell);
7558 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7560 quote_fmt_scan_string(tmpl->subject);
7563 buf = quote_fmt_get_buffer();
7565 alertpanel_error(_("Template subject format error."));
7567 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
7571 procmsg_msginfo_free( dummyinfo );
7574 static void compose_destroy(Compose *compose)
7576 GtkTextBuffer *buffer;
7577 GtkClipboard *clipboard;
7579 compose_list = g_list_remove(compose_list, compose);
7581 if (compose->updating) {
7582 debug_print("danger, not destroying anything now\n");
7583 compose->deferred_destroy = TRUE;
7586 /* NOTE: address_completion_end() does nothing with the window
7587 * however this may change. */
7588 address_completion_end(compose->window);
7590 slist_free_strings(compose->to_list);
7591 g_slist_free(compose->to_list);
7592 slist_free_strings(compose->newsgroup_list);
7593 g_slist_free(compose->newsgroup_list);
7594 slist_free_strings(compose->header_list);
7595 g_slist_free(compose->header_list);
7597 procmsg_msginfo_free(compose->targetinfo);
7598 procmsg_msginfo_free(compose->replyinfo);
7599 procmsg_msginfo_free(compose->fwdinfo);
7601 g_free(compose->replyto);
7602 g_free(compose->cc);
7603 g_free(compose->bcc);
7604 g_free(compose->newsgroups);
7605 g_free(compose->followup_to);
7607 g_free(compose->ml_post);
7609 g_free(compose->inreplyto);
7610 g_free(compose->references);
7611 g_free(compose->msgid);
7612 g_free(compose->boundary);
7614 g_free(compose->redirect_filename);
7615 if (compose->undostruct)
7616 undo_destroy(compose->undostruct);
7618 g_free(compose->sig_str);
7620 g_free(compose->exteditor_file);
7622 g_free(compose->orig_charset);
7624 g_free(compose->privacy_system);
7626 if (addressbook_get_target_compose() == compose)
7627 addressbook_set_target_compose(NULL);
7630 if (compose->gtkaspell) {
7631 gtkaspell_delete(compose->gtkaspell);
7632 compose->gtkaspell = NULL;
7636 prefs_common.compose_width = compose->scrolledwin->allocation.width;
7637 prefs_common.compose_height = compose->window->allocation.height;
7639 if (!gtk_widget_get_parent(compose->paned))
7640 gtk_widget_destroy(compose->paned);
7641 gtk_widget_destroy(compose->popupmenu);
7643 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
7644 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7645 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
7647 gtk_widget_destroy(compose->window);
7648 toolbar_destroy(compose->toolbar);
7649 g_free(compose->toolbar);
7650 g_mutex_free(compose->mutex);
7654 static void compose_attach_info_free(AttachInfo *ainfo)
7656 g_free(ainfo->file);
7657 g_free(ainfo->content_type);
7658 g_free(ainfo->name);
7662 static void compose_attach_update_label(Compose *compose)
7667 GtkTreeModel *model;
7672 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
7673 if(!gtk_tree_model_get_iter_first(model, &iter)) {
7674 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
7678 while(gtk_tree_model_iter_next(model, &iter))
7681 text = g_strdup_printf("(%d)", i);
7682 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
7686 static void compose_attach_remove_selected(Compose *compose)
7688 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
7689 GtkTreeSelection *selection;
7691 GtkTreeModel *model;
7693 selection = gtk_tree_view_get_selection(tree_view);
7694 sel = gtk_tree_selection_get_selected_rows(selection, &model);
7699 for (cur = sel; cur != NULL; cur = cur->next) {
7700 GtkTreePath *path = cur->data;
7701 GtkTreeRowReference *ref = gtk_tree_row_reference_new
7704 gtk_tree_path_free(path);
7707 for (cur = sel; cur != NULL; cur = cur->next) {
7708 GtkTreeRowReference *ref = cur->data;
7709 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
7712 if (gtk_tree_model_get_iter(model, &iter, path))
7713 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
7715 gtk_tree_path_free(path);
7716 gtk_tree_row_reference_free(ref);
7720 compose_attach_update_label(compose);
7723 static struct _AttachProperty
7726 GtkWidget *mimetype_entry;
7727 GtkWidget *encoding_optmenu;
7728 GtkWidget *path_entry;
7729 GtkWidget *filename_entry;
7731 GtkWidget *cancel_btn;
7734 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
7736 gtk_tree_path_free((GtkTreePath *)ptr);
7739 static void compose_attach_property(Compose *compose)
7741 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
7743 GtkComboBox *optmenu;
7744 GtkTreeSelection *selection;
7746 GtkTreeModel *model;
7749 static gboolean cancelled;
7751 /* only if one selected */
7752 selection = gtk_tree_view_get_selection(tree_view);
7753 if (gtk_tree_selection_count_selected_rows(selection) != 1)
7756 sel = gtk_tree_selection_get_selected_rows(selection, &model);
7760 path = (GtkTreePath *) sel->data;
7761 gtk_tree_model_get_iter(model, &iter, path);
7762 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
7765 g_list_foreach(sel, gtk_tree_path_free_, NULL);
7771 if (!attach_prop.window)
7772 compose_attach_property_create(&cancelled);
7773 gtk_widget_grab_focus(attach_prop.ok_btn);
7774 gtk_widget_show(attach_prop.window);
7775 manage_window_set_transient(GTK_WINDOW(attach_prop.window));
7777 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
7778 if (ainfo->encoding == ENC_UNKNOWN)
7779 combobox_select_by_data(optmenu, ENC_BASE64);
7781 combobox_select_by_data(optmenu, ainfo->encoding);
7783 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
7784 ainfo->content_type ? ainfo->content_type : "");
7785 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
7786 ainfo->file ? ainfo->file : "");
7787 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
7788 ainfo->name ? ainfo->name : "");
7791 const gchar *entry_text;
7793 gchar *cnttype = NULL;
7800 gtk_widget_hide(attach_prop.window);
7805 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
7806 if (*entry_text != '\0') {
7809 text = g_strstrip(g_strdup(entry_text));
7810 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
7811 cnttype = g_strdup(text);
7814 alertpanel_error(_("Invalid MIME type."));
7820 ainfo->encoding = combobox_get_active_data(optmenu);
7822 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
7823 if (*entry_text != '\0') {
7824 if (is_file_exist(entry_text) &&
7825 (size = get_file_size(entry_text)) > 0)
7826 file = g_strdup(entry_text);
7829 (_("File doesn't exist or is empty."));
7835 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
7836 if (*entry_text != '\0') {
7837 g_free(ainfo->name);
7838 ainfo->name = g_strdup(entry_text);
7842 g_free(ainfo->content_type);
7843 ainfo->content_type = cnttype;
7846 g_free(ainfo->file);
7852 /* update tree store */
7853 text = to_human_readable(ainfo->size);
7854 gtk_tree_model_get_iter(model, &iter, path);
7855 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
7856 COL_MIMETYPE, ainfo->content_type,
7858 COL_NAME, ainfo->name,
7864 gtk_tree_path_free(path);
7867 #define SET_LABEL_AND_ENTRY(str, entry, top) \
7869 label = gtk_label_new(str); \
7870 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
7871 GTK_FILL, 0, 0, 0); \
7872 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
7874 entry = gtk_entry_new(); \
7875 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
7876 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
7879 static void compose_attach_property_create(gboolean *cancelled)
7885 GtkWidget *mimetype_entry;
7888 GtkListStore *optmenu_menu;
7889 GtkWidget *path_entry;
7890 GtkWidget *filename_entry;
7893 GtkWidget *cancel_btn;
7894 GList *mime_type_list, *strlist;
7897 debug_print("Creating attach_property window...\n");
7899 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
7900 gtk_widget_set_size_request(window, 480, -1);
7901 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
7902 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
7903 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
7904 gtk_window_set_modal(GTK_WINDOW(window), TRUE);
7905 g_signal_connect(G_OBJECT(window), "delete_event",
7906 G_CALLBACK(attach_property_delete_event),
7908 g_signal_connect(G_OBJECT(window), "key_press_event",
7909 G_CALLBACK(attach_property_key_pressed),
7912 vbox = gtk_vbox_new(FALSE, 8);
7913 gtk_container_add(GTK_CONTAINER(window), vbox);
7915 table = gtk_table_new(4, 2, FALSE);
7916 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
7917 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
7918 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
7920 label = gtk_label_new(_("MIME type"));
7921 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
7923 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
7924 mimetype_entry = gtk_combo_box_entry_new_text();
7925 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
7926 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
7928 /* stuff with list */
7929 mime_type_list = procmime_get_mime_type_list();
7931 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
7932 MimeType *type = (MimeType *) mime_type_list->data;
7935 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
7937 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
7940 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
7941 (GCompareFunc)strcmp2);
7944 for (mime_type_list = strlist; mime_type_list != NULL;
7945 mime_type_list = mime_type_list->next) {
7946 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
7947 g_free(mime_type_list->data);
7949 g_list_free(strlist);
7950 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
7951 mimetype_entry = GTK_BIN(mimetype_entry)->child;
7953 label = gtk_label_new(_("Encoding"));
7954 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
7956 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
7958 hbox = gtk_hbox_new(FALSE, 0);
7959 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
7960 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
7962 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
7963 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
7965 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
7966 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
7967 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
7968 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
7969 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
7971 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
7973 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
7974 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
7976 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
7977 &ok_btn, GTK_STOCK_OK,
7979 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
7980 gtk_widget_grab_default(ok_btn);
7982 g_signal_connect(G_OBJECT(ok_btn), "clicked",
7983 G_CALLBACK(attach_property_ok),
7985 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
7986 G_CALLBACK(attach_property_cancel),
7989 gtk_widget_show_all(vbox);
7991 attach_prop.window = window;
7992 attach_prop.mimetype_entry = mimetype_entry;
7993 attach_prop.encoding_optmenu = optmenu;
7994 attach_prop.path_entry = path_entry;
7995 attach_prop.filename_entry = filename_entry;
7996 attach_prop.ok_btn = ok_btn;
7997 attach_prop.cancel_btn = cancel_btn;
8000 #undef SET_LABEL_AND_ENTRY
8002 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
8008 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
8014 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
8015 gboolean *cancelled)
8023 static gboolean attach_property_key_pressed(GtkWidget *widget,
8025 gboolean *cancelled)
8027 if (event && event->keyval == GDK_Escape) {
8034 static void compose_exec_ext_editor(Compose *compose)
8041 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
8042 G_DIR_SEPARATOR, compose);
8044 if (pipe(pipe_fds) < 0) {
8050 if ((pid = fork()) < 0) {
8057 /* close the write side of the pipe */
8060 compose->exteditor_file = g_strdup(tmp);
8061 compose->exteditor_pid = pid;
8063 compose_set_ext_editor_sensitive(compose, FALSE);
8065 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
8066 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
8070 } else { /* process-monitoring process */
8076 /* close the read side of the pipe */
8079 if (compose_write_body_to_file(compose, tmp) < 0) {
8080 fd_write_all(pipe_fds[1], "2\n", 2);
8084 pid_ed = compose_exec_ext_editor_real(tmp);
8086 fd_write_all(pipe_fds[1], "1\n", 2);
8090 /* wait until editor is terminated */
8091 waitpid(pid_ed, NULL, 0);
8093 fd_write_all(pipe_fds[1], "0\n", 2);
8100 #endif /* G_OS_UNIX */
8104 static gint compose_exec_ext_editor_real(const gchar *file)
8111 g_return_val_if_fail(file != NULL, -1);
8113 if ((pid = fork()) < 0) {
8118 if (pid != 0) return pid;
8120 /* grandchild process */
8122 if (setpgid(0, getppid()))
8125 if (prefs_common_get_ext_editor_cmd() &&
8126 (p = strchr(prefs_common_get_ext_editor_cmd(), '%')) &&
8127 *(p + 1) == 's' && !strchr(p + 2, '%')) {
8128 g_snprintf(buf, sizeof(buf), prefs_common_get_ext_editor_cmd(), file);
8130 if (prefs_common_get_ext_editor_cmd())
8131 g_warning("External editor command line is invalid: '%s'\n",
8132 prefs_common_get_ext_editor_cmd());
8133 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
8136 cmdline = strsplit_with_quote(buf, " ", 1024);
8137 execvp(cmdline[0], cmdline);
8140 g_strfreev(cmdline);
8145 static gboolean compose_ext_editor_kill(Compose *compose)
8147 pid_t pgid = compose->exteditor_pid * -1;
8150 ret = kill(pgid, 0);
8152 if (ret == 0 || (ret == -1 && EPERM == errno)) {
8156 msg = g_strdup_printf
8157 (_("The external editor is still working.\n"
8158 "Force terminating the process?\n"
8159 "process group id: %d"), -pgid);
8160 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
8161 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
8165 if (val == G_ALERTALTERNATE) {
8166 g_source_remove(compose->exteditor_tag);
8167 g_io_channel_shutdown(compose->exteditor_ch,
8169 g_io_channel_unref(compose->exteditor_ch);
8171 if (kill(pgid, SIGTERM) < 0) perror("kill");
8172 waitpid(compose->exteditor_pid, NULL, 0);
8174 g_warning("Terminated process group id: %d", -pgid);
8175 g_warning("Temporary file: %s",
8176 compose->exteditor_file);
8178 compose_set_ext_editor_sensitive(compose, TRUE);
8180 g_free(compose->exteditor_file);
8181 compose->exteditor_file = NULL;
8182 compose->exteditor_pid = -1;
8183 compose->exteditor_ch = NULL;
8184 compose->exteditor_tag = -1;
8192 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
8196 Compose *compose = (Compose *)data;
8199 debug_print(_("Compose: input from monitoring process\n"));
8201 g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL);
8203 g_io_channel_shutdown(source, FALSE, NULL);
8204 g_io_channel_unref(source);
8206 waitpid(compose->exteditor_pid, NULL, 0);
8208 if (buf[0] == '0') { /* success */
8209 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
8210 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
8212 gtk_text_buffer_set_text(buffer, "", -1);
8213 compose_insert_file(compose, compose->exteditor_file);
8214 compose_changed_cb(NULL, compose);
8216 if (g_unlink(compose->exteditor_file) < 0)
8217 FILE_OP_ERROR(compose->exteditor_file, "unlink");
8218 } else if (buf[0] == '1') { /* failed */
8219 g_warning("Couldn't exec external editor\n");
8220 if (g_unlink(compose->exteditor_file) < 0)
8221 FILE_OP_ERROR(compose->exteditor_file, "unlink");
8222 } else if (buf[0] == '2') {
8223 g_warning("Couldn't write to file\n");
8224 } else if (buf[0] == '3') {
8225 g_warning("Pipe read failed\n");
8228 compose_set_ext_editor_sensitive(compose, TRUE);
8230 g_free(compose->exteditor_file);
8231 compose->exteditor_file = NULL;
8232 compose->exteditor_pid = -1;
8233 compose->exteditor_ch = NULL;
8234 compose->exteditor_tag = -1;
8239 static void compose_set_ext_editor_sensitive(Compose *compose,
8242 GtkItemFactory *ifactory;
8244 ifactory = gtk_item_factory_from_widget(compose->menubar);
8246 menu_set_sensitive(ifactory, "/Message/Send", sensitive);
8247 menu_set_sensitive(ifactory, "/Message/Send later", sensitive);
8248 menu_set_sensitive(ifactory, "/Message/Insert file", sensitive);
8249 menu_set_sensitive(ifactory, "/Message/Insert signature", sensitive);
8250 menu_set_sensitive(ifactory, "/Edit/Wrap current paragraph", sensitive);
8251 menu_set_sensitive(ifactory, "/Edit/Wrap all long lines", sensitive);
8252 menu_set_sensitive(ifactory, "/Edit/Edit with external editor",
8255 gtk_widget_set_sensitive(compose->text, sensitive);
8256 if (compose->toolbar->send_btn)
8257 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
8258 if (compose->toolbar->sendl_btn)
8259 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
8260 if (compose->toolbar->draft_btn)
8261 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
8262 if (compose->toolbar->insert_btn)
8263 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
8264 if (compose->toolbar->sig_btn)
8265 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
8266 if (compose->toolbar->exteditor_btn)
8267 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
8268 if (compose->toolbar->linewrap_current_btn)
8269 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
8270 if (compose->toolbar->linewrap_all_btn)
8271 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
8273 #endif /* G_OS_UNIX */
8276 * compose_undo_state_changed:
8278 * Change the sensivity of the menuentries undo and redo
8280 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
8281 gint redo_state, gpointer data)
8283 GtkWidget *widget = GTK_WIDGET(data);
8284 GtkItemFactory *ifactory;
8286 g_return_if_fail(widget != NULL);
8288 ifactory = gtk_item_factory_from_widget(widget);
8290 switch (undo_state) {
8291 case UNDO_STATE_TRUE:
8292 if (!undostruct->undo_state) {
8293 undostruct->undo_state = TRUE;
8294 menu_set_sensitive(ifactory, "/Edit/Undo", TRUE);
8297 case UNDO_STATE_FALSE:
8298 if (undostruct->undo_state) {
8299 undostruct->undo_state = FALSE;
8300 menu_set_sensitive(ifactory, "/Edit/Undo", FALSE);
8303 case UNDO_STATE_UNCHANGED:
8305 case UNDO_STATE_REFRESH:
8306 menu_set_sensitive(ifactory, "/Edit/Undo",
8307 undostruct->undo_state);
8310 g_warning("Undo state not recognized");
8314 switch (redo_state) {
8315 case UNDO_STATE_TRUE:
8316 if (!undostruct->redo_state) {
8317 undostruct->redo_state = TRUE;
8318 menu_set_sensitive(ifactory, "/Edit/Redo", TRUE);
8321 case UNDO_STATE_FALSE:
8322 if (undostruct->redo_state) {
8323 undostruct->redo_state = FALSE;
8324 menu_set_sensitive(ifactory, "/Edit/Redo", FALSE);
8327 case UNDO_STATE_UNCHANGED:
8329 case UNDO_STATE_REFRESH:
8330 menu_set_sensitive(ifactory, "/Edit/Redo",
8331 undostruct->redo_state);
8334 g_warning("Redo state not recognized");
8339 /* callback functions */
8341 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
8342 * includes "non-client" (windows-izm) in calculation, so this calculation
8343 * may not be accurate.
8345 static gboolean compose_edit_size_alloc(GtkEditable *widget,
8346 GtkAllocation *allocation,
8347 GtkSHRuler *shruler)
8349 if (prefs_common.show_ruler) {
8350 gint char_width = 0, char_height = 0;
8351 gint line_width_in_chars;
8353 gtkut_get_font_size(GTK_WIDGET(widget),
8354 &char_width, &char_height);
8355 line_width_in_chars =
8356 (allocation->width - allocation->x) / char_width;
8358 /* got the maximum */
8359 gtk_ruler_set_range(GTK_RULER(shruler),
8360 0.0, line_width_in_chars, 0,
8361 /*line_width_in_chars*/ char_width);
8367 static void account_activated(GtkComboBox *optmenu, gpointer data)
8369 Compose *compose = (Compose *)data;
8372 gchar *folderidentifier;
8373 gint account_id = 0;
8377 /* Get ID of active account in the combo box */
8378 menu = gtk_combo_box_get_model(optmenu);
8379 gtk_combo_box_get_active_iter(optmenu, &iter);
8380 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
8382 ac = account_find_from_id(account_id);
8383 g_return_if_fail(ac != NULL);
8385 if (ac != compose->account)
8386 compose_select_account(compose, ac, FALSE);
8388 /* Set message save folder */
8389 if (account_get_special_folder(compose->account, F_OUTBOX)) {
8390 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
8392 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
8393 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
8395 gtk_editable_delete_text(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
8396 if (account_get_special_folder(compose->account, F_OUTBOX)) {
8397 folderidentifier = folder_item_get_identifier(account_get_special_folder
8398 (compose->account, F_OUTBOX));
8399 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
8400 g_free(folderidentifier);
8404 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
8405 GtkTreeViewColumn *column, Compose *compose)
8407 compose_attach_property(compose);
8410 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
8413 Compose *compose = (Compose *)data;
8414 GtkTreeSelection *attach_selection;
8415 gint attach_nr_selected;
8416 GtkItemFactory *ifactory;
8418 if (!event) return FALSE;
8420 if (event->button == 3) {
8421 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
8422 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
8423 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
8425 if (attach_nr_selected > 0)
8427 menu_set_sensitive(ifactory, "/Remove", TRUE);
8428 menu_set_sensitive(ifactory, "/Properties...", TRUE);
8430 menu_set_sensitive(ifactory, "/Remove", FALSE);
8431 menu_set_sensitive(ifactory, "/Properties...", FALSE);
8434 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
8435 NULL, NULL, event->button, event->time);
8442 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
8445 Compose *compose = (Compose *)data;
8447 if (!event) return FALSE;
8449 switch (event->keyval) {
8451 compose_attach_remove_selected(compose);
8457 static void compose_allow_user_actions (Compose *compose, gboolean allow)
8459 GtkItemFactory *ifactory = gtk_item_factory_from_widget(compose->menubar);
8460 toolbar_comp_set_sensitive(compose, allow);
8461 menu_set_sensitive(ifactory, "/Message", allow);
8462 menu_set_sensitive(ifactory, "/Edit", allow);
8464 menu_set_sensitive(ifactory, "/Spelling", allow);
8466 menu_set_sensitive(ifactory, "/Options", allow);
8467 menu_set_sensitive(ifactory, "/Tools", allow);
8468 menu_set_sensitive(ifactory, "/Help", allow);
8470 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
8474 static void compose_send_cb(gpointer data, guint action, GtkWidget *widget)
8476 Compose *compose = (Compose *)data;
8478 if (prefs_common.work_offline &&
8479 !inc_offline_should_override(TRUE,
8480 _("Claws Mail needs network access in order "
8481 "to send this email.")))
8484 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
8485 g_source_remove(compose->draft_timeout_tag);
8486 compose->draft_timeout_tag = -1;
8489 compose_send(compose);
8492 static void compose_send_later_cb(gpointer data, guint action,
8495 Compose *compose = (Compose *)data;
8499 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
8503 compose_close(compose);
8504 } else if (val == -1) {
8505 alertpanel_error(_("Could not queue message."));
8506 } else if (val == -2) {
8507 alertpanel_error(_("Could not queue message:\n\n%s."), strerror(errno));
8508 } else if (val == -3) {
8509 if (privacy_peek_error())
8510 alertpanel_error(_("Could not queue message for sending:\n\n"
8511 "Signature failed: %s"), privacy_get_error());
8512 } else if (val == -4) {
8513 alertpanel_error(_("Could not queue message for sending:\n\n"
8514 "Charset conversion failed."));
8515 } else if (val == -5) {
8516 alertpanel_error(_("Could not queue message for sending:\n\n"
8517 "Couldn't get recipient encryption key."));
8518 } else if (val == -6) {
8521 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
8524 #define DRAFTED_AT_EXIT "drafted_at_exit"
8525 static void compose_register_draft(MsgInfo *info)
8527 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8528 DRAFTED_AT_EXIT, NULL);
8529 FILE *fp = fopen(filepath, "ab");
8532 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
8540 gboolean compose_draft (gpointer data, guint action)
8542 Compose *compose = (Compose *)data;
8546 MsgFlags flag = {0, 0};
8547 static gboolean lock = FALSE;
8548 MsgInfo *newmsginfo;
8550 gboolean target_locked = FALSE;
8551 gboolean err = FALSE;
8553 if (lock) return FALSE;
8555 if (compose->sending)
8558 draft = account_get_special_folder(compose->account, F_DRAFT);
8559 g_return_val_if_fail(draft != NULL, FALSE);
8561 if (!g_mutex_trylock(compose->mutex)) {
8562 /* we don't want to lock the mutex once it's available,
8563 * because as the only other part of compose.c locking
8564 * it is compose_close - which means once unlocked,
8565 * the compose struct will be freed */
8566 debug_print("couldn't lock mutex, probably sending\n");
8572 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
8573 G_DIR_SEPARATOR, compose);
8574 if ((fp = g_fopen(tmp, "wb")) == NULL) {
8575 FILE_OP_ERROR(tmp, "fopen");
8579 /* chmod for security */
8580 if (change_file_mode_rw(fp, tmp) < 0) {
8581 FILE_OP_ERROR(tmp, "chmod");
8582 g_warning("can't change file mode\n");
8585 /* Save draft infos */
8586 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
8587 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
8589 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
8590 gchar *savefolderid;
8592 savefolderid = gtk_editable_get_chars(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
8593 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
8594 g_free(savefolderid);
8596 if (compose->return_receipt) {
8597 err |= (fprintf(fp, "RRCPT:1\n") < 0);
8599 if (compose->privacy_system) {
8600 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
8601 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
8602 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
8605 /* Message-ID of message replying to */
8606 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
8609 folderid = folder_item_get_identifier(compose->replyinfo->folder);
8610 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
8613 /* Message-ID of message forwarding to */
8614 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
8617 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
8618 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
8622 /* end of headers */
8623 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
8630 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
8634 if (fclose(fp) == EOF) {
8638 if (compose->targetinfo) {
8639 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
8640 flag.perm_flags = target_locked?MSG_LOCKED:0;
8642 flag.tmp_flags = MSG_DRAFT;
8644 folder_item_scan(draft);
8645 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
8646 MsgInfo *tmpinfo = NULL;
8647 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
8648 if (compose->msgid) {
8649 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
8652 msgnum = tmpinfo->msgnum;
8653 procmsg_msginfo_free(tmpinfo);
8654 debug_print("got draft msgnum %d from scanning\n", msgnum);
8656 debug_print("didn't get draft msgnum after scanning\n");
8659 debug_print("got draft msgnum %d from adding\n", msgnum);
8665 if (action != COMPOSE_AUTO_SAVE) {
8666 if (action != COMPOSE_DRAFT_FOR_EXIT)
8667 alertpanel_error(_("Could not save draft."));
8670 gtkut_window_popup(compose->window);
8671 val = alertpanel_full(_("Could not save draft"),
8672 _("Could not save draft.\n"
8673 "Do you want to cancel exit or discard this email?"),
8674 _("_Cancel exit"), _("_Discard email"), NULL,
8675 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
8676 if (val == G_ALERTALTERNATE) {
8678 g_mutex_unlock(compose->mutex); /* must be done before closing */
8679 compose_close(compose);
8683 g_mutex_unlock(compose->mutex); /* must be done before closing */
8692 if (compose->mode == COMPOSE_REEDIT) {
8693 compose_remove_reedit_target(compose, TRUE);
8696 newmsginfo = folder_item_get_msginfo(draft, msgnum);
8699 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
8701 procmsg_msginfo_set_flags(newmsginfo, MSG_LOCKED, MSG_DRAFT);
8703 procmsg_msginfo_set_flags(newmsginfo, 0, MSG_DRAFT);
8704 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
8705 procmsg_msginfo_set_flags(newmsginfo, 0,
8706 MSG_HAS_ATTACHMENT);
8708 if (action == COMPOSE_DRAFT_FOR_EXIT) {
8709 compose_register_draft(newmsginfo);
8711 procmsg_msginfo_free(newmsginfo);
8714 folder_item_scan(draft);
8716 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
8718 g_mutex_unlock(compose->mutex); /* must be done before closing */
8719 compose_close(compose);
8725 path = folder_item_fetch_msg(draft, msgnum);
8727 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
8730 if (g_stat(path, &s) < 0) {
8731 FILE_OP_ERROR(path, "stat");
8737 procmsg_msginfo_free(compose->targetinfo);
8738 compose->targetinfo = procmsg_msginfo_new();
8739 compose->targetinfo->msgnum = msgnum;
8740 compose->targetinfo->size = s.st_size;
8741 compose->targetinfo->mtime = s.st_mtime;
8742 compose->targetinfo->folder = draft;
8744 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
8745 compose->mode = COMPOSE_REEDIT;
8747 if (action == COMPOSE_AUTO_SAVE) {
8748 compose->autosaved_draft = compose->targetinfo;
8750 compose->modified = FALSE;
8751 compose_set_title(compose);
8755 g_mutex_unlock(compose->mutex);
8759 void compose_clear_exit_drafts(void)
8761 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8762 DRAFTED_AT_EXIT, NULL);
8763 if (is_file_exist(filepath))
8769 void compose_reopen_exit_drafts(void)
8771 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8772 DRAFTED_AT_EXIT, NULL);
8773 FILE *fp = fopen(filepath, "rb");
8777 while (fgets(buf, sizeof(buf), fp)) {
8778 gchar **parts = g_strsplit(buf, "\t", 2);
8779 const gchar *folder = parts[0];
8780 int msgnum = parts[1] ? atoi(parts[1]):-1;
8782 if (folder && *folder && msgnum > -1) {
8783 FolderItem *item = folder_find_item_from_identifier(folder);
8784 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
8786 compose_reedit(info, FALSE);
8793 compose_clear_exit_drafts();
8796 static void compose_draft_cb(gpointer data, guint action, GtkWidget *widget)
8798 compose_draft(data, action);
8801 static void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
8803 if (compose && file_list) {
8806 for ( tmp = file_list; tmp; tmp = tmp->next) {
8807 gchar *file = (gchar *) tmp->data;
8808 gchar *utf8_filename = conv_filename_to_utf8(file);
8809 compose_attach_append(compose, file, utf8_filename, NULL);
8810 compose_changed_cb(NULL, compose);
8815 g_free(utf8_filename);
8820 static void compose_attach_cb(gpointer data, guint action, GtkWidget *widget)
8822 Compose *compose = (Compose *)data;
8825 if (compose->redirect_filename != NULL)
8828 file_list = filesel_select_multiple_files_open(_("Select file"));
8831 compose_attach_from_list(compose, file_list, TRUE);
8832 g_list_free(file_list);
8836 static void compose_insert_file_cb(gpointer data, guint action,
8839 Compose *compose = (Compose *)data;
8842 file_list = filesel_select_multiple_files_open(_("Select file"));
8847 for ( tmp = file_list; tmp; tmp = tmp->next) {
8848 gchar *file = (gchar *) tmp->data;
8849 gchar *filedup = g_strdup(file);
8850 gchar *shortfile = g_path_get_basename(filedup);
8851 ComposeInsertResult res;
8853 res = compose_insert_file(compose, file);
8854 if (res == COMPOSE_INSERT_READ_ERROR) {
8855 alertpanel_error(_("File '%s' could not be read."), shortfile);
8856 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
8857 alertpanel_error(_("File '%s' contained invalid characters\n"
8858 "for the current encoding, insertion may be incorrect."), shortfile);
8864 g_list_free(file_list);
8868 static void compose_insert_sig_cb(gpointer data, guint action,
8871 Compose *compose = (Compose *)data;
8873 compose_insert_sig(compose, FALSE);
8876 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
8880 Compose *compose = (Compose *)data;
8882 gtkut_widget_get_uposition(widget, &x, &y);
8883 prefs_common.compose_x = x;
8884 prefs_common.compose_y = y;
8886 if (compose->sending || compose->updating)
8888 compose_close_cb(compose, 0, NULL);
8892 void compose_close_toolbar(Compose *compose)
8894 compose_close_cb(compose, 0, NULL);
8897 static void compose_close_cb(gpointer data, guint action, GtkWidget *widget)
8899 Compose *compose = (Compose *)data;
8903 if (compose->exteditor_tag != -1) {
8904 if (!compose_ext_editor_kill(compose))
8909 if (compose->modified) {
8910 val = alertpanel(_("Discard message"),
8911 _("This message has been modified. Discard it?"),
8912 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
8915 case G_ALERTDEFAULT:
8916 if (prefs_common.autosave)
8917 compose_remove_draft(compose);
8919 case G_ALERTALTERNATE:
8920 compose_draft_cb(data, COMPOSE_QUIT_EDITING, NULL);
8927 compose_close(compose);
8930 static void compose_set_encoding_cb(gpointer data, guint action,
8933 Compose *compose = (Compose *)data;
8935 if (GTK_CHECK_MENU_ITEM(widget)->active)
8936 compose->out_encoding = (CharSet)action;
8939 static void compose_address_cb(gpointer data, guint action, GtkWidget *widget)
8941 Compose *compose = (Compose *)data;
8943 addressbook_open(compose);
8946 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
8948 Compose *compose = (Compose *)data;
8953 tmpl = g_object_get_data(G_OBJECT(widget), "template");
8954 g_return_if_fail(tmpl != NULL);
8956 msg = g_strdup_printf(_("Do you want to apply the template '%s' ?"),
8958 val = alertpanel(_("Apply template"), msg,
8959 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
8962 if (val == G_ALERTDEFAULT)
8963 compose_template_apply(compose, tmpl, TRUE);
8964 else if (val == G_ALERTALTERNATE)
8965 compose_template_apply(compose, tmpl, FALSE);
8968 static void compose_ext_editor_cb(gpointer data, guint action,
8971 Compose *compose = (Compose *)data;
8973 compose_exec_ext_editor(compose);
8976 static void compose_undo_cb(Compose *compose)
8978 gboolean prev_autowrap = compose->autowrap;
8980 compose->autowrap = FALSE;
8981 undo_undo(compose->undostruct);
8982 compose->autowrap = prev_autowrap;
8985 static void compose_redo_cb(Compose *compose)
8987 gboolean prev_autowrap = compose->autowrap;
8989 compose->autowrap = FALSE;
8990 undo_redo(compose->undostruct);
8991 compose->autowrap = prev_autowrap;
8994 static void entry_cut_clipboard(GtkWidget *entry)
8996 if (GTK_IS_EDITABLE(entry))
8997 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
8998 else if (GTK_IS_TEXT_VIEW(entry))
8999 gtk_text_buffer_cut_clipboard(
9000 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
9001 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
9005 static void entry_copy_clipboard(GtkWidget *entry)
9007 if (GTK_IS_EDITABLE(entry))
9008 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
9009 else if (GTK_IS_TEXT_VIEW(entry))
9010 gtk_text_buffer_copy_clipboard(
9011 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
9012 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
9015 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
9016 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
9018 if (GTK_IS_TEXT_VIEW(entry)) {
9019 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
9020 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
9021 GtkTextIter start_iter, end_iter;
9023 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
9025 if (contents == NULL)
9028 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
9030 /* we shouldn't delete the selection when middle-click-pasting, or we
9031 * can't mid-click-paste our own selection */
9032 if (clip != GDK_SELECTION_PRIMARY) {
9033 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
9036 if (insert_place == NULL) {
9037 /* if insert_place isn't specified, insert at the cursor.
9038 * used for Ctrl-V pasting */
9039 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
9040 start = gtk_text_iter_get_offset(&start_iter);
9041 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
9043 /* if insert_place is specified, paste here.
9044 * used for mid-click-pasting */
9045 start = gtk_text_iter_get_offset(insert_place);
9046 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
9050 /* paste unwrapped: mark the paste so it's not wrapped later */
9051 end = start + strlen(contents);
9052 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
9053 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
9054 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
9055 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
9056 /* rewrap paragraph now (after a mid-click-paste) */
9057 mark_start = gtk_text_buffer_get_insert(buffer);
9058 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
9059 gtk_text_iter_backward_char(&start_iter);
9060 compose_beautify_paragraph(compose, &start_iter, TRUE);
9062 } else if (GTK_IS_EDITABLE(entry))
9063 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
9067 static void entry_allsel(GtkWidget *entry)
9069 if (GTK_IS_EDITABLE(entry))
9070 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
9071 else if (GTK_IS_TEXT_VIEW(entry)) {
9072 GtkTextIter startiter, enditer;
9073 GtkTextBuffer *textbuf;
9075 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
9076 gtk_text_buffer_get_start_iter(textbuf, &startiter);
9077 gtk_text_buffer_get_end_iter(textbuf, &enditer);
9079 gtk_text_buffer_move_mark_by_name(textbuf,
9080 "selection_bound", &startiter);
9081 gtk_text_buffer_move_mark_by_name(textbuf,
9082 "insert", &enditer);
9086 static void compose_cut_cb(Compose *compose)
9088 if (compose->focused_editable
9090 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9093 entry_cut_clipboard(compose->focused_editable);
9096 static void compose_copy_cb(Compose *compose)
9098 if (compose->focused_editable
9100 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9103 entry_copy_clipboard(compose->focused_editable);
9106 static void compose_paste_cb(Compose *compose)
9109 GtkTextBuffer *buffer;
9111 if (compose->focused_editable &&
9112 GTK_WIDGET_HAS_FOCUS(compose->focused_editable))
9113 entry_paste_clipboard(compose, compose->focused_editable,
9114 prefs_common.linewrap_pastes,
9115 GDK_SELECTION_CLIPBOARD, NULL);
9119 static void compose_paste_as_quote_cb(Compose *compose)
9121 gint wrap_quote = prefs_common.linewrap_quote;
9122 if (compose->focused_editable
9124 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9127 /* let text_insert() (called directly or at a later time
9128 * after the gtk_editable_paste_clipboard) know that
9129 * text is to be inserted as a quotation. implemented
9130 * by using a simple refcount... */
9131 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
9132 G_OBJECT(compose->focused_editable),
9133 "paste_as_quotation"));
9134 g_object_set_data(G_OBJECT(compose->focused_editable),
9135 "paste_as_quotation",
9136 GINT_TO_POINTER(paste_as_quotation + 1));
9137 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
9138 entry_paste_clipboard(compose, compose->focused_editable,
9139 prefs_common.linewrap_pastes,
9140 GDK_SELECTION_CLIPBOARD, NULL);
9141 prefs_common.linewrap_quote = wrap_quote;
9145 static void compose_paste_no_wrap_cb(Compose *compose)
9148 GtkTextBuffer *buffer;
9150 if (compose->focused_editable
9152 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9155 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
9156 GDK_SELECTION_CLIPBOARD, NULL);
9160 static void compose_paste_wrap_cb(Compose *compose)
9163 GtkTextBuffer *buffer;
9165 if (compose->focused_editable
9167 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9170 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
9171 GDK_SELECTION_CLIPBOARD, NULL);
9175 static void compose_allsel_cb(Compose *compose)
9177 if (compose->focused_editable
9179 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9182 entry_allsel(compose->focused_editable);
9185 static void textview_move_beginning_of_line (GtkTextView *text)
9187 GtkTextBuffer *buffer;
9191 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9193 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9194 mark = gtk_text_buffer_get_insert(buffer);
9195 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9196 gtk_text_iter_set_line_offset(&ins, 0);
9197 gtk_text_buffer_place_cursor(buffer, &ins);
9200 static void textview_move_forward_character (GtkTextView *text)
9202 GtkTextBuffer *buffer;
9206 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9208 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9209 mark = gtk_text_buffer_get_insert(buffer);
9210 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9211 if (gtk_text_iter_forward_cursor_position(&ins))
9212 gtk_text_buffer_place_cursor(buffer, &ins);
9215 static void textview_move_backward_character (GtkTextView *text)
9217 GtkTextBuffer *buffer;
9221 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9223 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9224 mark = gtk_text_buffer_get_insert(buffer);
9225 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9226 if (gtk_text_iter_backward_cursor_position(&ins))
9227 gtk_text_buffer_place_cursor(buffer, &ins);
9230 static void textview_move_forward_word (GtkTextView *text)
9232 GtkTextBuffer *buffer;
9237 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9239 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9240 mark = gtk_text_buffer_get_insert(buffer);
9241 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9242 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
9243 if (gtk_text_iter_forward_word_ends(&ins, count)) {
9244 gtk_text_iter_backward_word_start(&ins);
9245 gtk_text_buffer_place_cursor(buffer, &ins);
9249 static void textview_move_backward_word (GtkTextView *text)
9251 GtkTextBuffer *buffer;
9256 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9258 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9259 mark = gtk_text_buffer_get_insert(buffer);
9260 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9261 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
9262 if (gtk_text_iter_backward_word_starts(&ins, 1))
9263 gtk_text_buffer_place_cursor(buffer, &ins);
9266 static void textview_move_end_of_line (GtkTextView *text)
9268 GtkTextBuffer *buffer;
9272 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9274 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9275 mark = gtk_text_buffer_get_insert(buffer);
9276 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9277 if (gtk_text_iter_forward_to_line_end(&ins))
9278 gtk_text_buffer_place_cursor(buffer, &ins);
9281 static void textview_move_next_line (GtkTextView *text)
9283 GtkTextBuffer *buffer;
9288 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9290 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9291 mark = gtk_text_buffer_get_insert(buffer);
9292 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9293 offset = gtk_text_iter_get_line_offset(&ins);
9294 if (gtk_text_iter_forward_line(&ins)) {
9295 gtk_text_iter_set_line_offset(&ins, offset);
9296 gtk_text_buffer_place_cursor(buffer, &ins);
9300 static void textview_move_previous_line (GtkTextView *text)
9302 GtkTextBuffer *buffer;
9307 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9309 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9310 mark = gtk_text_buffer_get_insert(buffer);
9311 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9312 offset = gtk_text_iter_get_line_offset(&ins);
9313 if (gtk_text_iter_backward_line(&ins)) {
9314 gtk_text_iter_set_line_offset(&ins, offset);
9315 gtk_text_buffer_place_cursor(buffer, &ins);
9319 static void textview_delete_forward_character (GtkTextView *text)
9321 GtkTextBuffer *buffer;
9323 GtkTextIter ins, end_iter;
9325 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9327 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9328 mark = gtk_text_buffer_get_insert(buffer);
9329 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9331 if (gtk_text_iter_forward_char(&end_iter)) {
9332 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9336 static void textview_delete_backward_character (GtkTextView *text)
9338 GtkTextBuffer *buffer;
9340 GtkTextIter ins, end_iter;
9342 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9344 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9345 mark = gtk_text_buffer_get_insert(buffer);
9346 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9348 if (gtk_text_iter_backward_char(&end_iter)) {
9349 gtk_text_buffer_delete(buffer, &end_iter, &ins);
9353 static void textview_delete_forward_word (GtkTextView *text)
9355 GtkTextBuffer *buffer;
9357 GtkTextIter ins, end_iter;
9359 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9361 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9362 mark = gtk_text_buffer_get_insert(buffer);
9363 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9365 if (gtk_text_iter_forward_word_end(&end_iter)) {
9366 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9370 static void textview_delete_backward_word (GtkTextView *text)
9372 GtkTextBuffer *buffer;
9374 GtkTextIter ins, end_iter;
9376 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9378 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9379 mark = gtk_text_buffer_get_insert(buffer);
9380 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9382 if (gtk_text_iter_backward_word_start(&end_iter)) {
9383 gtk_text_buffer_delete(buffer, &end_iter, &ins);
9387 static void textview_delete_line (GtkTextView *text)
9389 GtkTextBuffer *buffer;
9391 GtkTextIter ins, start_iter, end_iter;
9394 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9396 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9397 mark = gtk_text_buffer_get_insert(buffer);
9398 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9401 gtk_text_iter_set_line_offset(&start_iter, 0);
9404 if (gtk_text_iter_ends_line(&end_iter))
9405 found = gtk_text_iter_forward_char(&end_iter);
9407 found = gtk_text_iter_forward_to_line_end(&end_iter);
9410 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
9413 static void textview_delete_to_line_end (GtkTextView *text)
9415 GtkTextBuffer *buffer;
9417 GtkTextIter ins, end_iter;
9420 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9422 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9423 mark = gtk_text_buffer_get_insert(buffer);
9424 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9426 if (gtk_text_iter_ends_line(&end_iter))
9427 found = gtk_text_iter_forward_char(&end_iter);
9429 found = gtk_text_iter_forward_to_line_end(&end_iter);
9431 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9434 static void compose_advanced_action_cb(Compose *compose,
9435 ComposeCallAdvancedAction action)
9437 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9439 void (*do_action) (GtkTextView *text);
9440 } action_table[] = {
9441 {textview_move_beginning_of_line},
9442 {textview_move_forward_character},
9443 {textview_move_backward_character},
9444 {textview_move_forward_word},
9445 {textview_move_backward_word},
9446 {textview_move_end_of_line},
9447 {textview_move_next_line},
9448 {textview_move_previous_line},
9449 {textview_delete_forward_character},
9450 {textview_delete_backward_character},
9451 {textview_delete_forward_word},
9452 {textview_delete_backward_word},
9453 {textview_delete_line},
9454 {NULL}, /* gtk_stext_delete_line_n */
9455 {textview_delete_to_line_end}
9458 if (!GTK_WIDGET_HAS_FOCUS(text)) return;
9460 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
9461 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
9462 if (action_table[action].do_action)
9463 action_table[action].do_action(text);
9465 g_warning("Not implemented yet.");
9469 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
9473 if (GTK_IS_EDITABLE(widget)) {
9474 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
9475 gtk_editable_set_position(GTK_EDITABLE(widget),
9478 if (widget->parent && widget->parent->parent
9479 && widget->parent->parent->parent) {
9480 if (GTK_IS_SCROLLED_WINDOW(widget->parent->parent->parent)) {
9481 gint y = widget->allocation.y;
9482 gint height = widget->allocation.height;
9483 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
9484 (GTK_SCROLLED_WINDOW(widget->parent->parent->parent));
9486 if (y < (int)shown->value) {
9487 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown), y - 1);
9489 if (y + height > (int)shown->value + (int)shown->page_size) {
9490 if (y - height - 1 < (int)shown->upper - (int)shown->page_size) {
9491 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
9492 y + height - (int)shown->page_size - 1);
9494 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
9495 (int)shown->upper - (int)shown->page_size - 1);
9502 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
9503 compose->focused_editable = widget;
9506 if (GTK_IS_TEXT_VIEW(widget)
9507 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
9508 gtk_widget_ref(compose->notebook);
9509 gtk_widget_ref(compose->edit_vbox);
9510 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
9511 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
9512 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
9513 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
9514 gtk_widget_unref(compose->notebook);
9515 gtk_widget_unref(compose->edit_vbox);
9516 g_signal_handlers_block_by_func(G_OBJECT(widget),
9517 G_CALLBACK(compose_grab_focus_cb),
9519 gtk_widget_grab_focus(widget);
9520 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
9521 G_CALLBACK(compose_grab_focus_cb),
9523 } else if (!GTK_IS_TEXT_VIEW(widget)
9524 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
9525 gtk_widget_ref(compose->notebook);
9526 gtk_widget_ref(compose->edit_vbox);
9527 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
9528 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
9529 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
9530 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
9531 gtk_widget_unref(compose->notebook);
9532 gtk_widget_unref(compose->edit_vbox);
9533 g_signal_handlers_block_by_func(G_OBJECT(widget),
9534 G_CALLBACK(compose_grab_focus_cb),
9536 gtk_widget_grab_focus(widget);
9537 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
9538 G_CALLBACK(compose_grab_focus_cb),
9544 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
9546 compose->modified = TRUE;
9548 compose_set_title(compose);
9552 static void compose_wrap_cb(gpointer data, guint action, GtkWidget *widget)
9554 Compose *compose = (Compose *)data;
9557 compose_wrap_all_full(compose, TRUE);
9559 compose_beautify_paragraph(compose, NULL, TRUE);
9562 static void compose_find_cb(gpointer data, guint action, GtkWidget *widget)
9564 Compose *compose = (Compose *)data;
9566 message_search_compose(compose);
9569 static void compose_toggle_autowrap_cb(gpointer data, guint action,
9572 Compose *compose = (Compose *)data;
9573 compose->autowrap = GTK_CHECK_MENU_ITEM(widget)->active;
9574 if (compose->autowrap)
9575 compose_wrap_all_full(compose, TRUE);
9576 compose->autowrap = GTK_CHECK_MENU_ITEM(widget)->active;
9579 static void compose_toggle_sign_cb(gpointer data, guint action,
9582 Compose *compose = (Compose *)data;
9584 if (GTK_CHECK_MENU_ITEM(widget)->active)
9585 compose->use_signing = TRUE;
9587 compose->use_signing = FALSE;
9590 static void compose_toggle_encrypt_cb(gpointer data, guint action,
9593 Compose *compose = (Compose *)data;
9595 if (GTK_CHECK_MENU_ITEM(widget)->active)
9596 compose->use_encryption = TRUE;
9598 compose->use_encryption = FALSE;
9601 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
9603 g_free(compose->privacy_system);
9605 compose->privacy_system = g_strdup(account->default_privacy_system);
9606 compose_update_privacy_system_menu_item(compose, warn);
9609 static void compose_toggle_ruler_cb(gpointer data, guint action,
9612 Compose *compose = (Compose *)data;
9614 if (GTK_CHECK_MENU_ITEM(widget)->active) {
9615 gtk_widget_show(compose->ruler_hbox);
9616 prefs_common.show_ruler = TRUE;
9618 gtk_widget_hide(compose->ruler_hbox);
9619 gtk_widget_queue_resize(compose->edit_vbox);
9620 prefs_common.show_ruler = FALSE;
9624 static void compose_attach_drag_received_cb (GtkWidget *widget,
9625 GdkDragContext *context,
9628 GtkSelectionData *data,
9633 Compose *compose = (Compose *)user_data;
9636 if (gdk_atom_name(data->type) &&
9637 !strcmp(gdk_atom_name(data->type), "text/uri-list")
9638 && gtk_drag_get_source_widget(context) !=
9639 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
9640 list = uri_list_extract_filenames((const gchar *)data->data);
9641 for (tmp = list; tmp != NULL; tmp = tmp->next) {
9642 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
9643 compose_attach_append
9644 (compose, (const gchar *)tmp->data,
9645 utf8_filename, NULL);
9646 g_free(utf8_filename);
9648 if (list) compose_changed_cb(NULL, compose);
9649 list_free_strings(list);
9651 } else if (gtk_drag_get_source_widget(context)
9652 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
9653 /* comes from our summaryview */
9654 SummaryView * summaryview = NULL;
9655 GSList * list = NULL, *cur = NULL;
9657 if (mainwindow_get_mainwindow())
9658 summaryview = mainwindow_get_mainwindow()->summaryview;
9661 list = summary_get_selected_msg_list(summaryview);
9663 for (cur = list; cur; cur = cur->next) {
9664 MsgInfo *msginfo = (MsgInfo *)cur->data;
9667 file = procmsg_get_message_file_full(msginfo,
9670 compose_attach_append(compose, (const gchar *)file,
9671 (const gchar *)file, "message/rfc822");
9679 static gboolean compose_drag_drop(GtkWidget *widget,
9680 GdkDragContext *drag_context,
9682 guint time, gpointer user_data)
9684 /* not handling this signal makes compose_insert_drag_received_cb
9689 static void compose_insert_drag_received_cb (GtkWidget *widget,
9690 GdkDragContext *drag_context,
9693 GtkSelectionData *data,
9698 Compose *compose = (Compose *)user_data;
9701 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
9703 if (gdk_atom_name(data->type) && !strcmp(gdk_atom_name(data->type), "text/uri-list")) {
9704 AlertValue val = G_ALERTDEFAULT;
9706 list = uri_list_extract_filenames((const gchar *)data->data);
9708 if (list == NULL && strstr((gchar *)(data->data), "://")) {
9709 /* Assume a list of no files, and data has ://, is a remote link */
9710 gchar *tmpdata = g_strstrip(g_strdup((const gchar *)data->data));
9711 gchar *tmpfile = get_tmp_file();
9712 str_write_to_file(tmpdata, tmpfile);
9714 compose_insert_file(compose, tmpfile);
9717 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9718 compose_beautify_paragraph(compose, NULL, TRUE);
9721 switch (prefs_common.compose_dnd_mode) {
9722 case COMPOSE_DND_ASK:
9723 val = alertpanel_full(_("Insert or attach?"),
9724 _("Do you want to insert the contents of the file(s) "
9725 "into the message body, or attach it to the email?"),
9726 GTK_STOCK_CANCEL, _("+_Insert"), _("_Attach"),
9727 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
9729 case COMPOSE_DND_INSERT:
9730 val = G_ALERTALTERNATE;
9732 case COMPOSE_DND_ATTACH:
9736 /* unexpected case */
9737 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
9740 if (val & G_ALERTDISABLE) {
9741 val &= ~G_ALERTDISABLE;
9742 /* remember what action to perform by default, only if we don't click Cancel */
9743 if (val == G_ALERTALTERNATE)
9744 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
9745 else if (val == G_ALERTOTHER)
9746 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
9749 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
9750 gtk_drag_finish(drag_context, FALSE, FALSE, time);
9751 list_free_strings(list);
9754 } else if (val == G_ALERTOTHER) {
9755 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
9756 list_free_strings(list);
9761 for (tmp = list; tmp != NULL; tmp = tmp->next) {
9762 compose_insert_file(compose, (const gchar *)tmp->data);
9764 list_free_strings(list);
9766 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9769 #if GTK_CHECK_VERSION(2, 8, 0)
9770 /* do nothing, handled by GTK */
9772 gchar *tmpfile = get_tmp_file();
9773 str_write_to_file((const gchar *)data->data, tmpfile);
9774 compose_insert_file(compose, tmpfile);
9777 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9781 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9784 static void compose_header_drag_received_cb (GtkWidget *widget,
9785 GdkDragContext *drag_context,
9788 GtkSelectionData *data,
9793 GtkEditable *entry = (GtkEditable *)user_data;
9794 gchar *email = (gchar *)data->data;
9796 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
9799 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
9800 gchar *decoded=g_new(gchar, strlen(email));
9803 email += strlen("mailto:");
9804 decode_uri(decoded, email); /* will fit */
9805 gtk_editable_delete_text(entry, 0, -1);
9806 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
9807 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9811 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9814 static void compose_toggle_return_receipt_cb(gpointer data, guint action,
9817 Compose *compose = (Compose *)data;
9819 if (GTK_CHECK_MENU_ITEM(widget)->active)
9820 compose->return_receipt = TRUE;
9822 compose->return_receipt = FALSE;
9825 static void compose_toggle_remove_refs_cb(gpointer data, guint action,
9828 Compose *compose = (Compose *)data;
9830 if (GTK_CHECK_MENU_ITEM(widget)->active)
9831 compose->remove_references = TRUE;
9833 compose->remove_references = FALSE;
9836 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
9838 ComposeHeaderEntry *headerentry)
9840 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
9841 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
9842 !(event->state & GDK_MODIFIER_MASK) &&
9843 (event->keyval == GDK_BackSpace) &&
9844 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
9845 gtk_container_remove
9846 (GTK_CONTAINER(headerentry->compose->header_table),
9847 headerentry->combo);
9848 gtk_container_remove
9849 (GTK_CONTAINER(headerentry->compose->header_table),
9850 headerentry->entry);
9851 headerentry->compose->header_list =
9852 g_slist_remove(headerentry->compose->header_list,
9854 g_free(headerentry);
9855 } else if (event->keyval == GDK_Tab) {
9856 if (headerentry->compose->header_last == headerentry) {
9857 /* Override default next focus, and give it to subject_entry
9858 * instead of notebook tabs
9860 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
9861 gtk_widget_grab_focus(headerentry->compose->subject_entry);
9868 static gboolean compose_headerentry_changed_cb(GtkWidget *entry,
9869 ComposeHeaderEntry *headerentry)
9871 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
9872 compose_create_header_entry(headerentry->compose);
9873 g_signal_handlers_disconnect_matched
9874 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
9875 0, 0, NULL, NULL, headerentry);
9877 /* Automatically scroll down */
9878 compose_show_first_last_header(headerentry->compose, FALSE);
9884 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
9886 GtkAdjustment *vadj;
9888 g_return_if_fail(compose);
9889 g_return_if_fail(GTK_IS_WIDGET(compose->header_table));
9890 g_return_if_fail(GTK_IS_VIEWPORT(compose->header_table->parent));
9892 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(compose->header_table->parent));
9893 gtk_adjustment_set_value(vadj, (show_first ? vadj->lower : vadj->upper));
9894 gtk_adjustment_changed(vadj);
9897 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
9898 const gchar *text, gint len, Compose *compose)
9900 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
9901 (G_OBJECT(compose->text), "paste_as_quotation"));
9904 g_return_if_fail(text != NULL);
9906 g_signal_handlers_block_by_func(G_OBJECT(buffer),
9907 G_CALLBACK(text_inserted),
9909 if (paste_as_quotation) {
9913 GtkTextIter start_iter;
9918 new_text = g_strndup(text, len);
9920 qmark = compose_quote_char_from_context(compose);
9922 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
9923 gtk_text_buffer_place_cursor(buffer, iter);
9925 pos = gtk_text_iter_get_offset(iter);
9927 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
9928 _("Quote format error at line %d."));
9929 quote_fmt_reset_vartable();
9931 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
9932 GINT_TO_POINTER(paste_as_quotation - 1));
9934 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
9935 gtk_text_buffer_place_cursor(buffer, iter);
9936 gtk_text_buffer_delete_mark(buffer, mark);
9938 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
9939 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
9940 compose_beautify_paragraph(compose, &start_iter, FALSE);
9941 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
9942 gtk_text_buffer_delete_mark(buffer, mark);
9944 if (strcmp(text, "\n") || compose->automatic_break
9945 || gtk_text_iter_starts_line(iter))
9946 gtk_text_buffer_insert(buffer, iter, text, len);
9948 debug_print("insert nowrap \\n\n");
9949 gtk_text_buffer_insert_with_tags_by_name(buffer,
9950 iter, text, len, "no_join", NULL);
9954 if (!paste_as_quotation) {
9955 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
9956 compose_beautify_paragraph(compose, iter, FALSE);
9957 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
9958 gtk_text_buffer_delete_mark(buffer, mark);
9961 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
9962 G_CALLBACK(text_inserted),
9964 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
9966 if (prefs_common.autosave &&
9967 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
9968 compose->draft_timeout_tag != -2 /* disabled while loading */)
9969 compose->draft_timeout_tag = g_timeout_add
9970 (500, (GtkFunction) compose_defer_auto_save_draft, compose);
9972 static gint compose_defer_auto_save_draft(Compose *compose)
9974 compose->draft_timeout_tag = -1;
9975 compose_draft_cb((gpointer)compose, COMPOSE_AUTO_SAVE, NULL);
9980 static void compose_check_all(Compose *compose)
9982 if (compose->gtkaspell)
9983 gtkaspell_check_all(compose->gtkaspell);
9986 static void compose_highlight_all(Compose *compose)
9988 if (compose->gtkaspell)
9989 gtkaspell_highlight_all(compose->gtkaspell);
9992 static void compose_check_backwards(Compose *compose)
9994 if (compose->gtkaspell)
9995 gtkaspell_check_backwards(compose->gtkaspell);
9997 GtkItemFactory *ifactory;
9998 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
9999 menu_set_sensitive(ifactory, "/Edit/Check backwards misspelled word", FALSE);
10000 menu_set_sensitive(ifactory, "/Edit/Forward to next misspelled word", FALSE);
10004 static void compose_check_forwards_go(Compose *compose)
10006 if (compose->gtkaspell)
10007 gtkaspell_check_forwards_go(compose->gtkaspell);
10009 GtkItemFactory *ifactory;
10010 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
10011 menu_set_sensitive(ifactory, "/Edit/Check backwards misspelled word", FALSE);
10012 menu_set_sensitive(ifactory, "/Edit/Forward to next misspelled word", FALSE);
10018 *\brief Guess originating forward account from MsgInfo and several
10019 * "common preference" settings. Return NULL if no guess.
10021 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
10023 PrefsAccount *account = NULL;
10025 g_return_val_if_fail(msginfo, NULL);
10026 g_return_val_if_fail(msginfo->folder, NULL);
10027 g_return_val_if_fail(msginfo->folder->prefs, NULL);
10029 if (msginfo->folder->prefs->enable_default_account)
10030 account = account_find_from_id(msginfo->folder->prefs->default_account);
10033 account = msginfo->folder->folder->account;
10035 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
10037 Xstrdup_a(to, msginfo->to, return NULL);
10038 extract_address(to);
10039 account = account_find_from_address(to);
10042 if (!account && prefs_common.forward_account_autosel) {
10043 gchar cc[BUFFSIZE];
10044 if (!procheader_get_header_from_msginfo
10045 (msginfo, cc,sizeof cc , "Cc:")) {
10046 gchar *buf = cc + strlen("Cc:");
10047 extract_address(buf);
10048 account = account_find_from_address(buf);
10052 if (!account && prefs_common.forward_account_autosel) {
10053 gchar deliveredto[BUFFSIZE];
10054 if (!procheader_get_header_from_msginfo
10055 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
10056 gchar *buf = deliveredto + strlen("Delivered-To:");
10057 extract_address(buf);
10058 account = account_find_from_address(buf);
10065 gboolean compose_close(Compose *compose)
10069 if (!g_mutex_trylock(compose->mutex)) {
10070 /* we have to wait for the (possibly deferred by auto-save)
10071 * drafting to be done, before destroying the compose under
10073 debug_print("waiting for drafting to finish...\n");
10074 compose_allow_user_actions(compose, FALSE);
10075 g_timeout_add (500, (GSourceFunc) compose_close, compose);
10078 g_return_val_if_fail(compose, FALSE);
10079 gtkut_widget_get_uposition(compose->window, &x, &y);
10080 prefs_common.compose_x = x;
10081 prefs_common.compose_y = y;
10082 g_mutex_unlock(compose->mutex);
10083 compose_destroy(compose);
10088 * Add entry field for each address in list.
10089 * \param compose E-Mail composition object.
10090 * \param listAddress List of (formatted) E-Mail addresses.
10092 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
10095 node = listAddress;
10097 addr = ( gchar * ) node->data;
10098 compose_entry_append( compose, addr, COMPOSE_TO );
10099 node = g_list_next( node );
10103 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
10104 guint action, gboolean opening_multiple)
10106 gchar *body = NULL;
10107 GSList *new_msglist = NULL;
10108 MsgInfo *tmp_msginfo = NULL;
10109 gboolean originally_enc = FALSE;
10110 Compose *compose = NULL;
10112 g_return_if_fail(msgview != NULL);
10114 g_return_if_fail(msginfo_list != NULL);
10116 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
10117 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
10118 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
10120 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
10121 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
10122 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
10123 orig_msginfo, mimeinfo);
10124 if (tmp_msginfo != NULL) {
10125 new_msglist = g_slist_append(NULL, tmp_msginfo);
10127 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
10128 tmp_msginfo->folder = orig_msginfo->folder;
10129 tmp_msginfo->msgnum = orig_msginfo->msgnum;
10130 if (orig_msginfo->tags)
10131 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
10136 if (!opening_multiple)
10137 body = messageview_get_selection(msgview);
10140 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
10141 procmsg_msginfo_free(tmp_msginfo);
10142 g_slist_free(new_msglist);
10144 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
10146 if (compose && originally_enc) {
10147 compose_force_encryption(compose, compose->account, FALSE);
10153 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
10156 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
10157 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
10158 GSList *cur = msginfo_list;
10159 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
10160 "messages. Opening the windows "
10161 "could take some time. Do you "
10162 "want to continue?"),
10163 g_slist_length(msginfo_list));
10164 if (g_slist_length(msginfo_list) > 9
10165 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
10166 != G_ALERTALTERNATE) {
10171 /* We'll open multiple compose windows */
10172 /* let the WM place the next windows */
10173 compose_force_window_origin = FALSE;
10174 for (; cur; cur = cur->next) {
10176 tmplist.data = cur->data;
10177 tmplist.next = NULL;
10178 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
10180 compose_force_window_origin = TRUE;
10182 /* forwarding multiple mails as attachments is done via a
10183 * single compose window */
10184 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
10188 void compose_set_position(Compose *compose, gint pos)
10190 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10192 gtkut_text_view_set_position(text, pos);
10195 gboolean compose_search_string(Compose *compose,
10196 const gchar *str, gboolean case_sens)
10198 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10200 return gtkut_text_view_search_string(text, str, case_sens);
10203 gboolean compose_search_string_backward(Compose *compose,
10204 const gchar *str, gboolean case_sens)
10206 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10208 return gtkut_text_view_search_string_backward(text, str, case_sens);
10211 /* allocate a msginfo structure and populate its data from a compose data structure */
10212 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
10214 MsgInfo *newmsginfo;
10216 gchar buf[BUFFSIZE];
10218 g_return_val_if_fail( compose != NULL, NULL );
10220 newmsginfo = procmsg_msginfo_new();
10223 get_rfc822_date(buf, sizeof(buf));
10224 newmsginfo->date = g_strdup(buf);
10227 if (compose->from_name) {
10228 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
10229 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
10233 if (compose->subject_entry)
10234 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
10236 /* to, cc, reply-to, newsgroups */
10237 for (list = compose->header_list; list; list = list->next) {
10238 gchar *header = gtk_editable_get_chars(
10240 GTK_BIN(((ComposeHeaderEntry *)list->data)->combo)->child), 0, -1);
10241 gchar *entry = gtk_editable_get_chars(
10242 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
10244 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
10245 if ( newmsginfo->to == NULL ) {
10246 newmsginfo->to = g_strdup(entry);
10247 } else if (entry && *entry) {
10248 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
10249 g_free(newmsginfo->to);
10250 newmsginfo->to = tmp;
10253 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
10254 if ( newmsginfo->cc == NULL ) {
10255 newmsginfo->cc = g_strdup(entry);
10256 } else if (entry && *entry) {
10257 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
10258 g_free(newmsginfo->cc);
10259 newmsginfo->cc = tmp;
10262 if ( strcasecmp(header,
10263 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
10264 if ( newmsginfo->newsgroups == NULL ) {
10265 newmsginfo->newsgroups = g_strdup(entry);
10266 } else if (entry && *entry) {
10267 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
10268 g_free(newmsginfo->newsgroups);
10269 newmsginfo->newsgroups = tmp;
10277 /* other data is unset */
10283 /* update compose's dictionaries from folder dict settings */
10284 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
10285 FolderItem *folder_item)
10287 g_return_if_fail(compose != NULL);
10289 if (compose->gtkaspell && folder_item && folder_item->prefs) {
10290 FolderItemPrefs *prefs = folder_item->prefs;
10292 if (prefs->enable_default_dictionary)
10293 gtkaspell_change_dict(compose->gtkaspell,
10294 prefs->default_dictionary, FALSE);
10295 if (folder_item->prefs->enable_default_alt_dictionary)
10296 gtkaspell_change_alt_dict(compose->gtkaspell,
10297 prefs->default_alt_dictionary);
10298 if (prefs->enable_default_dictionary
10299 || prefs->enable_default_alt_dictionary)
10300 compose_spell_menu_changed(compose);