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 compose_update_privacy_system_menu_item(compose, FALSE);
1199 compose_use_encryption(compose, TRUE);
1203 static void compose_force_signing(Compose *compose, PrefsAccount *account)
1205 gchar *privacy = NULL;
1207 if (account->default_privacy_system
1208 && strlen(account->default_privacy_system)) {
1209 privacy = account->default_privacy_system;
1211 GSList *privacy_avail = privacy_get_system_ids();
1212 if (privacy_avail && g_slist_length(privacy_avail)) {
1213 privacy = (gchar *)(privacy_avail->data);
1216 if (privacy != NULL) {
1217 if (compose->privacy_system == NULL)
1218 compose->privacy_system = g_strdup(privacy);
1219 compose_update_privacy_system_menu_item(compose, FALSE);
1220 compose_use_signing(compose, TRUE);
1224 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1228 Compose *compose = NULL;
1229 GtkItemFactory *ifactory = NULL;
1231 g_return_val_if_fail(msginfo_list != NULL, NULL);
1233 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1234 g_return_val_if_fail(msginfo != NULL, NULL);
1236 list_len = g_slist_length(msginfo_list);
1240 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1241 FALSE, prefs_common.default_reply_list, FALSE, body);
1243 case COMPOSE_REPLY_WITH_QUOTE:
1244 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1245 FALSE, prefs_common.default_reply_list, FALSE, body);
1247 case COMPOSE_REPLY_WITHOUT_QUOTE:
1248 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1249 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1251 case COMPOSE_REPLY_TO_SENDER:
1252 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1253 FALSE, FALSE, TRUE, body);
1255 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1256 compose = compose_followup_and_reply_to(msginfo,
1257 COMPOSE_QUOTE_CHECK,
1258 FALSE, FALSE, body);
1260 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1261 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1262 FALSE, FALSE, TRUE, body);
1264 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1265 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1266 FALSE, FALSE, TRUE, NULL);
1268 case COMPOSE_REPLY_TO_ALL:
1269 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1270 TRUE, FALSE, FALSE, body);
1272 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1273 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1274 TRUE, FALSE, FALSE, body);
1276 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1277 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1278 TRUE, FALSE, FALSE, NULL);
1280 case COMPOSE_REPLY_TO_LIST:
1281 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1282 FALSE, TRUE, FALSE, body);
1284 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1285 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1286 FALSE, TRUE, FALSE, body);
1288 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1289 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1290 FALSE, TRUE, FALSE, NULL);
1292 case COMPOSE_FORWARD:
1293 if (prefs_common.forward_as_attachment) {
1294 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1297 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1301 case COMPOSE_FORWARD_INLINE:
1302 /* check if we reply to more than one Message */
1303 if (list_len == 1) {
1304 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1307 /* more messages FALL THROUGH */
1308 case COMPOSE_FORWARD_AS_ATTACH:
1309 compose = compose_forward_multiple(NULL, msginfo_list);
1311 case COMPOSE_REDIRECT:
1312 compose = compose_redirect(NULL, msginfo, FALSE);
1315 g_warning("compose_reply_mode(): invalid Compose Mode: %d\n", mode);
1318 if (compose == NULL) {
1319 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1322 ifactory = gtk_item_factory_from_widget(compose->menubar);
1324 compose->rmode = mode;
1325 switch (compose->rmode) {
1327 case COMPOSE_REPLY_WITH_QUOTE:
1328 case COMPOSE_REPLY_WITHOUT_QUOTE:
1329 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1330 debug_print("reply mode Normal\n");
1331 menu_set_active(ifactory, "/Options/Reply mode/Normal", TRUE);
1332 compose_reply_change_mode(compose, COMPOSE_REPLY, NULL); /* force update */
1334 case COMPOSE_REPLY_TO_SENDER:
1335 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1336 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1337 debug_print("reply mode Sender\n");
1338 menu_set_active(ifactory, "/Options/Reply mode/Sender", TRUE);
1340 case COMPOSE_REPLY_TO_ALL:
1341 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1342 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1343 debug_print("reply mode All\n");
1344 menu_set_active(ifactory, "/Options/Reply mode/All", TRUE);
1346 case COMPOSE_REPLY_TO_LIST:
1347 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1348 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1349 debug_print("reply mode List\n");
1350 menu_set_active(ifactory, "/Options/Reply mode/Mailing-list", TRUE);
1358 static Compose *compose_reply(MsgInfo *msginfo,
1359 ComposeQuoteMode quote_mode,
1365 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1366 to_sender, FALSE, body);
1369 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1370 ComposeQuoteMode quote_mode,
1375 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1376 to_sender, TRUE, body);
1379 static void compose_extract_original_charset(Compose *compose)
1381 MsgInfo *info = NULL;
1382 if (compose->replyinfo) {
1383 info = compose->replyinfo;
1384 } else if (compose->fwdinfo) {
1385 info = compose->fwdinfo;
1386 } else if (compose->targetinfo) {
1387 info = compose->targetinfo;
1390 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1391 MimeInfo *partinfo = mimeinfo;
1392 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1393 partinfo = procmime_mimeinfo_next(partinfo);
1395 compose->orig_charset =
1396 g_strdup(procmime_mimeinfo_get_parameter(
1397 partinfo, "charset"));
1399 procmime_mimeinfo_free_all(mimeinfo);
1403 #define SIGNAL_BLOCK(buffer) { \
1404 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1405 G_CALLBACK(compose_changed_cb), \
1407 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1408 G_CALLBACK(text_inserted), \
1412 #define SIGNAL_UNBLOCK(buffer) { \
1413 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1414 G_CALLBACK(compose_changed_cb), \
1416 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1417 G_CALLBACK(text_inserted), \
1421 static Compose *compose_generic_reply(MsgInfo *msginfo,
1422 ComposeQuoteMode quote_mode,
1423 gboolean to_all, gboolean to_ml,
1425 gboolean followup_and_reply_to,
1428 GtkItemFactory *ifactory;
1430 PrefsAccount *account = NULL;
1431 GtkTextView *textview;
1432 GtkTextBuffer *textbuf;
1433 gboolean quote = FALSE;
1434 const gchar *qmark = NULL;
1435 const gchar *body_fmt = NULL;
1437 g_return_val_if_fail(msginfo != NULL, NULL);
1438 g_return_val_if_fail(msginfo->folder != NULL, NULL);
1440 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1442 g_return_val_if_fail(account != NULL, NULL);
1444 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1446 compose->updating = TRUE;
1448 ifactory = gtk_item_factory_from_widget(compose->menubar);
1450 menu_set_active(ifactory, "/Options/Remove references", FALSE);
1451 menu_set_sensitive(ifactory, "/Options/Remove references", TRUE);
1453 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1454 if (!compose->replyinfo)
1455 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1457 compose_extract_original_charset(compose);
1459 if (msginfo->folder && msginfo->folder->ret_rcpt)
1460 menu_set_active(ifactory, "/Options/Request Return Receipt", TRUE);
1462 /* Set save folder */
1463 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1464 gchar *folderidentifier;
1466 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1467 folderidentifier = folder_item_get_identifier(msginfo->folder);
1468 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
1469 g_free(folderidentifier);
1472 if (compose_parse_header(compose, msginfo) < 0) return NULL;
1474 textview = (GTK_TEXT_VIEW(compose->text));
1475 textbuf = gtk_text_view_get_buffer(textview);
1476 compose_create_tags(textview, compose);
1478 undo_block(compose->undostruct);
1480 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1483 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1484 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1485 /* use the reply format of folder (if enabled), or the account's one
1486 (if enabled) or fallback to the global reply format, which is always
1487 enabled (even if empty), and use the relevant quotemark */
1489 if (msginfo->folder && msginfo->folder->prefs &&
1490 msginfo->folder->prefs->reply_with_format) {
1491 qmark = msginfo->folder->prefs->reply_quotemark;
1492 body_fmt = msginfo->folder->prefs->reply_body_format;
1494 } else if (account->reply_with_format) {
1495 qmark = account->reply_quotemark;
1496 body_fmt = account->reply_body_format;
1499 qmark = prefs_common.quotemark;
1500 body_fmt = prefs_common.quotefmt;
1505 /* empty quotemark is not allowed */
1506 if (qmark == NULL || *qmark == '\0')
1508 compose_quote_fmt(compose, compose->replyinfo,
1509 body_fmt, qmark, body, FALSE, TRUE,
1510 _("Message reply format error at line %d."));
1511 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1512 quote_fmt_reset_vartable();
1515 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1516 compose_force_encryption(compose, account, FALSE);
1519 SIGNAL_BLOCK(textbuf);
1521 if (account->auto_sig)
1522 compose_insert_sig(compose, FALSE);
1524 compose_wrap_all(compose);
1526 SIGNAL_UNBLOCK(textbuf);
1528 gtk_widget_grab_focus(compose->text);
1530 undo_unblock(compose->undostruct);
1532 if (prefs_common.auto_exteditor)
1533 compose_exec_ext_editor(compose);
1535 compose->modified = FALSE;
1536 compose_set_title(compose);
1538 compose->updating = FALSE;
1539 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1540 SCROLL_TO_CURSOR(compose);
1542 if (compose->deferred_destroy) {
1543 compose_destroy(compose);
1550 #define INSERT_FW_HEADER(var, hdr) \
1551 if (msginfo->var && *msginfo->var) { \
1552 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1553 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1554 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1557 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1558 gboolean as_attach, const gchar *body,
1559 gboolean no_extedit,
1563 GtkTextView *textview;
1564 GtkTextBuffer *textbuf;
1567 g_return_val_if_fail(msginfo != NULL, NULL);
1568 g_return_val_if_fail(msginfo->folder != NULL, NULL);
1571 !(account = compose_guess_forward_account_from_msginfo
1573 account = cur_account;
1575 compose = compose_create(account, msginfo->folder, COMPOSE_FORWARD, batch);
1577 compose->updating = TRUE;
1578 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1579 if (!compose->fwdinfo)
1580 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1582 compose_extract_original_charset(compose);
1584 if (msginfo->subject && *msginfo->subject) {
1585 gchar *buf, *buf2, *p;
1587 buf = p = g_strdup(msginfo->subject);
1588 p += subject_get_prefix_length(p);
1589 memmove(buf, p, strlen(p) + 1);
1591 buf2 = g_strdup_printf("Fw: %s", buf);
1592 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1598 textview = GTK_TEXT_VIEW(compose->text);
1599 textbuf = gtk_text_view_get_buffer(textview);
1600 compose_create_tags(textview, compose);
1602 undo_block(compose->undostruct);
1606 msgfile = procmsg_get_message_file(msginfo);
1607 if (!is_file_exist(msgfile))
1608 g_warning("%s: file not exist\n", msgfile);
1610 compose_attach_append(compose, msgfile, msgfile,
1615 const gchar *qmark = NULL;
1616 const gchar *body_fmt = prefs_common.fw_quotefmt;
1617 MsgInfo *full_msginfo;
1619 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1621 full_msginfo = procmsg_msginfo_copy(msginfo);
1623 /* use the forward format of folder (if enabled), or the account's one
1624 (if enabled) or fallback to the global forward format, which is always
1625 enabled (even if empty), and use the relevant quotemark */
1626 if (msginfo->folder && msginfo->folder->prefs &&
1627 msginfo->folder->prefs->forward_with_format) {
1628 qmark = msginfo->folder->prefs->forward_quotemark;
1629 body_fmt = msginfo->folder->prefs->forward_body_format;
1631 } else if (account->forward_with_format) {
1632 qmark = account->forward_quotemark;
1633 body_fmt = account->forward_body_format;
1636 qmark = prefs_common.fw_quotemark;
1637 body_fmt = prefs_common.fw_quotefmt;
1640 /* empty quotemark is not allowed */
1641 if (qmark == NULL || *qmark == '\0')
1644 compose_quote_fmt(compose, full_msginfo,
1645 body_fmt, qmark, body, FALSE, TRUE,
1646 _("Message forward format error at line %d."));
1647 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1648 quote_fmt_reset_vartable();
1649 compose_attach_parts(compose, msginfo);
1651 procmsg_msginfo_free(full_msginfo);
1654 SIGNAL_BLOCK(textbuf);
1656 if (account->auto_sig)
1657 compose_insert_sig(compose, FALSE);
1659 compose_wrap_all(compose);
1661 SIGNAL_UNBLOCK(textbuf);
1663 gtk_text_buffer_get_start_iter(textbuf, &iter);
1664 gtk_text_buffer_place_cursor(textbuf, &iter);
1666 gtk_widget_grab_focus(compose->header_last->entry);
1668 if (!no_extedit && prefs_common.auto_exteditor)
1669 compose_exec_ext_editor(compose);
1672 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1673 gchar *folderidentifier;
1675 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1676 folderidentifier = folder_item_get_identifier(msginfo->folder);
1677 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
1678 g_free(folderidentifier);
1681 undo_unblock(compose->undostruct);
1683 compose->modified = FALSE;
1684 compose_set_title(compose);
1686 compose->updating = FALSE;
1687 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1688 SCROLL_TO_CURSOR(compose);
1690 if (compose->deferred_destroy) {
1691 compose_destroy(compose);
1698 #undef INSERT_FW_HEADER
1700 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1703 GtkTextView *textview;
1704 GtkTextBuffer *textbuf;
1708 gboolean single_mail = TRUE;
1710 g_return_val_if_fail(msginfo_list != NULL, NULL);
1712 if (g_slist_length(msginfo_list) > 1)
1713 single_mail = FALSE;
1715 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1716 if (((MsgInfo *)msginfo->data)->folder == NULL)
1719 /* guess account from first selected message */
1721 !(account = compose_guess_forward_account_from_msginfo
1722 (msginfo_list->data)))
1723 account = cur_account;
1725 g_return_val_if_fail(account != NULL, NULL);
1727 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1728 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1729 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1732 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1734 compose->updating = TRUE;
1736 textview = GTK_TEXT_VIEW(compose->text);
1737 textbuf = gtk_text_view_get_buffer(textview);
1738 compose_create_tags(textview, compose);
1740 undo_block(compose->undostruct);
1741 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1742 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
1744 if (!is_file_exist(msgfile))
1745 g_warning("%s: file not exist\n", msgfile);
1747 compose_attach_append(compose, msgfile, msgfile,
1753 MsgInfo *info = (MsgInfo *)msginfo_list->data;
1754 if (info->subject && *info->subject) {
1755 gchar *buf, *buf2, *p;
1757 buf = p = g_strdup(info->subject);
1758 p += subject_get_prefix_length(p);
1759 memmove(buf, p, strlen(p) + 1);
1761 buf2 = g_strdup_printf("Fw: %s", buf);
1762 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1768 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
1769 _("Fw: multiple emails"));
1772 SIGNAL_BLOCK(textbuf);
1774 if (account->auto_sig)
1775 compose_insert_sig(compose, FALSE);
1777 compose_wrap_all(compose);
1779 SIGNAL_UNBLOCK(textbuf);
1781 gtk_text_buffer_get_start_iter(textbuf, &iter);
1782 gtk_text_buffer_place_cursor(textbuf, &iter);
1784 gtk_widget_grab_focus(compose->header_last->entry);
1785 undo_unblock(compose->undostruct);
1786 compose->modified = FALSE;
1787 compose_set_title(compose);
1789 compose->updating = FALSE;
1790 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1791 SCROLL_TO_CURSOR(compose);
1793 if (compose->deferred_destroy) {
1794 compose_destroy(compose);
1801 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
1803 GtkTextIter start = *iter;
1804 GtkTextIter end_iter;
1805 int start_pos = gtk_text_iter_get_offset(&start);
1807 if (!compose->account->sig_sep)
1810 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
1811 start_pos+strlen(compose->account->sig_sep));
1813 /* check sig separator */
1814 str = gtk_text_iter_get_text(&start, &end_iter);
1815 if (!strcmp(str, compose->account->sig_sep)) {
1817 /* check end of line (\n) */
1818 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
1819 start_pos+strlen(compose->account->sig_sep));
1820 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
1821 start_pos+strlen(compose->account->sig_sep)+1);
1822 tmp = gtk_text_iter_get_text(&start, &end_iter);
1823 if (!strcmp(tmp,"\n")) {
1835 static void compose_colorize_signature(Compose *compose)
1837 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
1839 GtkTextIter end_iter;
1840 gtk_text_buffer_get_start_iter(buffer, &iter);
1841 while (gtk_text_iter_forward_line(&iter))
1842 if (compose_is_sig_separator(compose, buffer, &iter)) {
1843 gtk_text_buffer_get_end_iter(buffer, &end_iter);
1844 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
1848 #define BLOCK_WRAP() { \
1849 prev_autowrap = compose->autowrap; \
1850 buffer = gtk_text_view_get_buffer( \
1851 GTK_TEXT_VIEW(compose->text)); \
1852 compose->autowrap = FALSE; \
1854 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1855 G_CALLBACK(compose_changed_cb), \
1857 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1858 G_CALLBACK(text_inserted), \
1861 #define UNBLOCK_WRAP() { \
1862 compose->autowrap = prev_autowrap; \
1863 if (compose->autowrap) { \
1864 gint old = compose->draft_timeout_tag; \
1865 compose->draft_timeout_tag = -2; \
1866 compose_wrap_all(compose); \
1867 compose->draft_timeout_tag = old; \
1870 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1871 G_CALLBACK(compose_changed_cb), \
1873 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1874 G_CALLBACK(text_inserted), \
1878 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
1880 Compose *compose = NULL;
1881 PrefsAccount *account = NULL;
1882 GtkTextView *textview;
1883 GtkTextBuffer *textbuf;
1887 gchar buf[BUFFSIZE];
1888 gboolean use_signing = FALSE;
1889 gboolean use_encryption = FALSE;
1890 gchar *privacy_system = NULL;
1891 int priority = PRIORITY_NORMAL;
1892 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
1894 g_return_val_if_fail(msginfo != NULL, NULL);
1895 g_return_val_if_fail(msginfo->folder != NULL, NULL);
1897 if (compose_put_existing_to_front(msginfo)) {
1901 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
1902 folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
1903 gchar queueheader_buf[BUFFSIZE];
1906 /* Select Account from queue headers */
1907 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1908 sizeof(queueheader_buf), "X-Claws-Account-Id:")) {
1909 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
1910 account = account_find_from_id(id);
1912 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1913 sizeof(queueheader_buf), "X-Sylpheed-Account-Id:")) {
1914 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
1915 account = account_find_from_id(id);
1917 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1918 sizeof(queueheader_buf), "NAID:")) {
1919 id = atoi(&queueheader_buf[strlen("NAID:")]);
1920 account = account_find_from_id(id);
1922 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1923 sizeof(queueheader_buf), "MAID:")) {
1924 id = atoi(&queueheader_buf[strlen("MAID:")]);
1925 account = account_find_from_id(id);
1927 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1928 sizeof(queueheader_buf), "S:")) {
1929 account = account_find_from_address(queueheader_buf);
1931 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1932 sizeof(queueheader_buf), "X-Claws-Sign:")) {
1933 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
1934 use_signing = param;
1937 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1938 sizeof(queueheader_buf), "X-Sylpheed-Sign:")) {
1939 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
1940 use_signing = param;
1943 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1944 sizeof(queueheader_buf), "X-Claws-Encrypt:")) {
1945 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
1946 use_encryption = param;
1948 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1949 sizeof(queueheader_buf), "X-Sylpheed-Encrypt:")) {
1950 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
1951 use_encryption = param;
1953 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1954 sizeof(queueheader_buf), "X-Claws-Privacy-System:")) {
1955 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
1957 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1958 sizeof(queueheader_buf), "X-Sylpheed-Privacy-System:")) {
1959 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
1961 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1962 sizeof(queueheader_buf), "X-Priority: ")) {
1963 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
1966 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1967 sizeof(queueheader_buf), "RMID:")) {
1968 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
1969 if (tokens[0] && tokens[1] && tokens[2]) {
1970 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
1971 if (orig_item != NULL) {
1972 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
1977 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1978 sizeof(queueheader_buf), "FMID:")) {
1979 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
1980 if (tokens[0] && tokens[1] && tokens[2]) {
1981 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
1982 if (orig_item != NULL) {
1983 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
1989 account = msginfo->folder->folder->account;
1992 if (!account && prefs_common.reedit_account_autosel) {
1993 gchar from[BUFFSIZE];
1994 if (!procheader_get_header_from_msginfo(msginfo, from, sizeof(from), "FROM:")) {
1995 extract_address(from);
1996 account = account_find_from_address(from);
2000 account = cur_account;
2002 g_return_val_if_fail(account != NULL, NULL);
2004 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2006 compose->replyinfo = replyinfo;
2007 compose->fwdinfo = fwdinfo;
2009 compose->updating = TRUE;
2010 compose->priority = priority;
2012 if (privacy_system != NULL) {
2013 compose->privacy_system = privacy_system;
2014 compose_use_signing(compose, use_signing);
2015 compose_use_encryption(compose, use_encryption);
2016 compose_update_privacy_system_menu_item(compose, FALSE);
2018 activate_privacy_system(compose, account, FALSE);
2021 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2023 compose_extract_original_charset(compose);
2025 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2026 folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
2027 gchar queueheader_buf[BUFFSIZE];
2029 /* Set message save folder */
2030 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "SCF:")) {
2033 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2034 gtk_editable_delete_text(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
2035 gtk_editable_insert_text(GTK_EDITABLE(compose->savemsg_entry), &queueheader_buf[4], strlen(&queueheader_buf[4]), &startpos);
2037 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "RRCPT:")) {
2038 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2040 GtkItemFactory *ifactory;
2041 ifactory = gtk_item_factory_from_widget(compose->menubar);
2042 menu_set_active(ifactory, "/Options/Request Return Receipt", TRUE);
2047 if (compose_parse_header(compose, msginfo) < 0) {
2048 compose->updating = FALSE;
2049 compose_destroy(compose);
2052 compose_reedit_set_entry(compose, msginfo);
2054 textview = GTK_TEXT_VIEW(compose->text);
2055 textbuf = gtk_text_view_get_buffer(textview);
2056 compose_create_tags(textview, compose);
2058 mark = gtk_text_buffer_get_insert(textbuf);
2059 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2061 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2062 G_CALLBACK(compose_changed_cb),
2065 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2066 fp = procmime_get_first_encrypted_text_content(msginfo);
2068 compose_force_encryption(compose, account, TRUE);
2071 fp = procmime_get_first_text_content(msginfo);
2074 g_warning("Can't get text part\n");
2078 gboolean prev_autowrap = compose->autowrap;
2079 GtkTextBuffer *buffer = textbuf;
2081 while (fgets(buf, sizeof(buf), fp) != NULL) {
2083 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2089 compose_attach_parts(compose, msginfo);
2091 compose_colorize_signature(compose);
2093 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2094 G_CALLBACK(compose_changed_cb),
2097 gtk_widget_grab_focus(compose->text);
2099 if (prefs_common.auto_exteditor) {
2100 compose_exec_ext_editor(compose);
2102 compose->modified = FALSE;
2103 compose_set_title(compose);
2105 compose->updating = FALSE;
2106 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2107 SCROLL_TO_CURSOR(compose);
2109 if (compose->deferred_destroy) {
2110 compose_destroy(compose);
2114 compose->sig_str = compose_get_signature_str(compose);
2119 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2124 GtkItemFactory *ifactory;
2127 g_return_val_if_fail(msginfo != NULL, NULL);
2130 account = account_get_reply_account(msginfo,
2131 prefs_common.reply_account_autosel);
2132 g_return_val_if_fail(account != NULL, NULL);
2134 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2136 compose->updating = TRUE;
2138 ifactory = gtk_item_factory_from_widget(compose->menubar);
2139 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2140 compose->replyinfo = NULL;
2141 compose->fwdinfo = NULL;
2143 compose_show_first_last_header(compose, TRUE);
2145 gtk_widget_grab_focus(compose->header_last->entry);
2147 filename = procmsg_get_message_file(msginfo);
2149 if (filename == NULL) {
2150 compose->updating = FALSE;
2151 compose_destroy(compose);
2156 compose->redirect_filename = filename;
2158 /* Set save folder */
2159 item = msginfo->folder;
2160 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2161 gchar *folderidentifier;
2163 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
2164 folderidentifier = folder_item_get_identifier(item);
2165 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
2166 g_free(folderidentifier);
2169 compose_attach_parts(compose, msginfo);
2171 if (msginfo->subject)
2172 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2174 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2176 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2177 _("Message redirect format error at line %d."));
2178 quote_fmt_reset_vartable();
2179 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2181 compose_colorize_signature(compose);
2183 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
2184 menu_set_sensitive(ifactory, "/Add...", FALSE);
2185 menu_set_sensitive(ifactory, "/Remove", FALSE);
2186 menu_set_sensitive(ifactory, "/Properties...", FALSE);
2188 ifactory = gtk_item_factory_from_widget(compose->menubar);
2189 menu_set_sensitive(ifactory, "/Message/Save", FALSE);
2190 menu_set_sensitive(ifactory, "/Message/Insert file", FALSE);
2191 menu_set_sensitive(ifactory, "/Message/Attach file", FALSE);
2192 menu_set_sensitive(ifactory, "/Message/Insert signature", FALSE);
2193 menu_set_sensitive(ifactory, "/Edit", FALSE);
2194 menu_set_sensitive(ifactory, "/Options", FALSE);
2195 menu_set_sensitive(ifactory, "/Tools/Show ruler", FALSE);
2196 menu_set_sensitive(ifactory, "/Tools/Actions", FALSE);
2198 if (compose->toolbar->draft_btn)
2199 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2200 if (compose->toolbar->insert_btn)
2201 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2202 if (compose->toolbar->attach_btn)
2203 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2204 if (compose->toolbar->sig_btn)
2205 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2206 if (compose->toolbar->exteditor_btn)
2207 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2208 if (compose->toolbar->linewrap_current_btn)
2209 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2210 if (compose->toolbar->linewrap_all_btn)
2211 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2213 compose->modified = FALSE;
2214 compose_set_title(compose);
2215 compose->updating = FALSE;
2216 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2217 SCROLL_TO_CURSOR(compose);
2219 if (compose->deferred_destroy) {
2220 compose_destroy(compose);
2227 GList *compose_get_compose_list(void)
2229 return compose_list;
2232 void compose_entry_append(Compose *compose, const gchar *address,
2233 ComposeEntryType type)
2235 const gchar *header;
2237 gboolean in_quote = FALSE;
2238 if (!address || *address == '\0') return;
2245 header = N_("Bcc:");
2247 case COMPOSE_REPLYTO:
2248 header = N_("Reply-To:");
2250 case COMPOSE_NEWSGROUPS:
2251 header = N_("Newsgroups:");
2253 case COMPOSE_FOLLOWUPTO:
2254 header = N_( "Followup-To:");
2261 header = prefs_common_translated_header_name(header);
2263 cur = begin = (gchar *)address;
2265 /* we separate the line by commas, but not if we're inside a quoted
2267 while (*cur != '\0') {
2269 in_quote = !in_quote;
2270 if (*cur == ',' && !in_quote) {
2271 gchar *tmp = g_strdup(begin);
2273 tmp[cur-begin]='\0';
2276 while (*tmp == ' ' || *tmp == '\t')
2278 compose_add_header_entry(compose, header, tmp);
2285 gchar *tmp = g_strdup(begin);
2287 tmp[cur-begin]='\0';
2290 while (*tmp == ' ' || *tmp == '\t')
2292 compose_add_header_entry(compose, header, tmp);
2297 static void compose_entry_mark_default_to(Compose *compose, const gchar *mailto)
2299 static GdkColor yellow;
2300 static GdkColor black;
2301 static gboolean yellow_initialised = FALSE;
2305 if (!yellow_initialised) {
2306 gdk_color_parse("#f5f6be", &yellow);
2307 gdk_color_parse("#000000", &black);
2308 yellow_initialised = gdk_colormap_alloc_color(
2309 gdk_colormap_get_system(), &yellow, FALSE, TRUE);
2310 yellow_initialised &= gdk_colormap_alloc_color(
2311 gdk_colormap_get_system(), &black, FALSE, TRUE);
2314 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2315 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2316 if (gtk_entry_get_text(entry) &&
2317 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2318 if (yellow_initialised) {
2319 gtk_widget_modify_base(
2320 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2321 GTK_STATE_NORMAL, &yellow);
2322 gtk_widget_modify_text(
2323 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2324 GTK_STATE_NORMAL, &black);
2330 void compose_toolbar_cb(gint action, gpointer data)
2332 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2333 Compose *compose = (Compose*)toolbar_item->parent;
2335 g_return_if_fail(compose != NULL);
2339 compose_send_cb(compose, 0, NULL);
2342 compose_send_later_cb(compose, 0, NULL);
2345 compose_draft_cb(compose, COMPOSE_QUIT_EDITING, NULL);
2348 compose_insert_file_cb(compose, 0, NULL);
2351 compose_attach_cb(compose, 0, NULL);
2354 compose_insert_sig(compose, FALSE);
2357 compose_ext_editor_cb(compose, 0, NULL);
2359 case A_LINEWRAP_CURRENT:
2360 compose_beautify_paragraph(compose, NULL, TRUE);
2362 case A_LINEWRAP_ALL:
2363 compose_wrap_all_full(compose, TRUE);
2366 compose_address_cb(compose, 0, NULL);
2369 case A_CHECK_SPELLING:
2370 compose_check_all(compose);
2378 static void compose_entries_set(Compose *compose, const gchar *mailto)
2383 gchar *subject = NULL;
2387 gchar **attach = NULL;
2389 scan_mailto_url(mailto, &to, &cc, &bcc, &subject, &body, &attach);
2392 compose_entry_append(compose, to, COMPOSE_TO);
2394 compose_entry_append(compose, cc, COMPOSE_CC);
2396 compose_entry_append(compose, bcc, COMPOSE_BCC);
2398 if (!g_utf8_validate (subject, -1, NULL)) {
2399 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2400 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2403 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2407 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2408 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2411 gboolean prev_autowrap = compose->autowrap;
2413 compose->autowrap = FALSE;
2415 mark = gtk_text_buffer_get_insert(buffer);
2416 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2418 if (!g_utf8_validate (body, -1, NULL)) {
2419 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2420 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2423 gtk_text_buffer_insert(buffer, &iter, body, -1);
2425 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2427 compose->autowrap = prev_autowrap;
2428 if (compose->autowrap)
2429 compose_wrap_all(compose);
2433 gint i = 0, att = 0;
2434 gchar *warn_files = NULL;
2435 while (attach[i] != NULL) {
2436 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2437 if (utf8_filename) {
2438 if (compose_attach_append(compose, attach[i], utf8_filename, NULL)) {
2439 gchar *tmp = g_strdup_printf("%s%s\n",
2440 warn_files?warn_files:"",
2446 g_free(utf8_filename);
2448 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2453 alertpanel_notice(ngettext(
2454 "The following file has been attached: \n%s",
2455 "The following files have been attached: \n%s", att), warn_files);
2467 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2469 static HeaderEntry hentry[] = {{"Reply-To:", NULL, TRUE},
2470 {"Cc:", NULL, TRUE},
2471 {"References:", NULL, FALSE},
2472 {"Bcc:", NULL, TRUE},
2473 {"Newsgroups:", NULL, TRUE},
2474 {"Followup-To:", NULL, TRUE},
2475 {"List-Post:", NULL, FALSE},
2476 {"X-Priority:", NULL, FALSE},
2477 {NULL, NULL, FALSE}};
2493 g_return_val_if_fail(msginfo != NULL, -1);
2495 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2496 procheader_get_header_fields(fp, hentry);
2499 if (hentry[H_REPLY_TO].body != NULL) {
2500 if (hentry[H_REPLY_TO].body[0] != '\0') {
2502 conv_unmime_header(hentry[H_REPLY_TO].body,
2505 g_free(hentry[H_REPLY_TO].body);
2506 hentry[H_REPLY_TO].body = NULL;
2508 if (hentry[H_CC].body != NULL) {
2509 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL);
2510 g_free(hentry[H_CC].body);
2511 hentry[H_CC].body = NULL;
2513 if (hentry[H_REFERENCES].body != NULL) {
2514 if (compose->mode == COMPOSE_REEDIT)
2515 compose->references = hentry[H_REFERENCES].body;
2517 compose->references = compose_parse_references
2518 (hentry[H_REFERENCES].body, msginfo->msgid);
2519 g_free(hentry[H_REFERENCES].body);
2521 hentry[H_REFERENCES].body = NULL;
2523 if (hentry[H_BCC].body != NULL) {
2524 if (compose->mode == COMPOSE_REEDIT)
2526 conv_unmime_header(hentry[H_BCC].body, NULL);
2527 g_free(hentry[H_BCC].body);
2528 hentry[H_BCC].body = NULL;
2530 if (hentry[H_NEWSGROUPS].body != NULL) {
2531 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2532 hentry[H_NEWSGROUPS].body = NULL;
2534 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2535 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2536 compose->followup_to =
2537 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2540 g_free(hentry[H_FOLLOWUP_TO].body);
2541 hentry[H_FOLLOWUP_TO].body = NULL;
2543 if (hentry[H_LIST_POST].body != NULL) {
2546 extract_address(hentry[H_LIST_POST].body);
2547 if (hentry[H_LIST_POST].body[0] != '\0') {
2548 scan_mailto_url(hentry[H_LIST_POST].body,
2549 &to, NULL, NULL, NULL, NULL, NULL);
2551 g_free(compose->ml_post);
2552 compose->ml_post = to;
2555 g_free(hentry[H_LIST_POST].body);
2556 hentry[H_LIST_POST].body = NULL;
2559 /* CLAWS - X-Priority */
2560 if (compose->mode == COMPOSE_REEDIT)
2561 if (hentry[H_X_PRIORITY].body != NULL) {
2564 priority = atoi(hentry[H_X_PRIORITY].body);
2565 g_free(hentry[H_X_PRIORITY].body);
2567 hentry[H_X_PRIORITY].body = NULL;
2569 if (priority < PRIORITY_HIGHEST ||
2570 priority > PRIORITY_LOWEST)
2571 priority = PRIORITY_NORMAL;
2573 compose->priority = priority;
2576 if (compose->mode == COMPOSE_REEDIT) {
2577 if (msginfo->inreplyto && *msginfo->inreplyto)
2578 compose->inreplyto = g_strdup(msginfo->inreplyto);
2582 if (msginfo->msgid && *msginfo->msgid)
2583 compose->inreplyto = g_strdup(msginfo->msgid);
2585 if (!compose->references) {
2586 if (msginfo->msgid && *msginfo->msgid) {
2587 if (msginfo->inreplyto && *msginfo->inreplyto)
2588 compose->references =
2589 g_strdup_printf("<%s>\n\t<%s>",
2593 compose->references =
2594 g_strconcat("<", msginfo->msgid, ">",
2596 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
2597 compose->references =
2598 g_strconcat("<", msginfo->inreplyto, ">",
2606 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
2608 GSList *ref_id_list, *cur;
2612 ref_id_list = references_list_append(NULL, ref);
2613 if (!ref_id_list) return NULL;
2614 if (msgid && *msgid)
2615 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
2620 for (cur = ref_id_list; cur != NULL; cur = cur->next)
2621 /* "<" + Message-ID + ">" + CR+LF+TAB */
2622 len += strlen((gchar *)cur->data) + 5;
2624 if (len > MAX_REFERENCES_LEN) {
2625 /* remove second message-ID */
2626 if (ref_id_list && ref_id_list->next &&
2627 ref_id_list->next->next) {
2628 g_free(ref_id_list->next->data);
2629 ref_id_list = g_slist_remove
2630 (ref_id_list, ref_id_list->next->data);
2632 slist_free_strings(ref_id_list);
2633 g_slist_free(ref_id_list);
2640 new_ref = g_string_new("");
2641 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
2642 if (new_ref->len > 0)
2643 g_string_append(new_ref, "\n\t");
2644 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
2647 slist_free_strings(ref_id_list);
2648 g_slist_free(ref_id_list);
2650 new_ref_str = new_ref->str;
2651 g_string_free(new_ref, FALSE);
2656 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
2657 const gchar *fmt, const gchar *qmark,
2658 const gchar *body, gboolean rewrap,
2659 gboolean need_unescape,
2660 const gchar *err_msg)
2662 MsgInfo* dummyinfo = NULL;
2663 gchar *quote_str = NULL;
2665 gboolean prev_autowrap;
2666 const gchar *trimmed_body = body;
2667 gint cursor_pos = -1;
2668 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2669 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2674 SIGNAL_BLOCK(buffer);
2677 dummyinfo = compose_msginfo_new_from_compose(compose);
2678 msginfo = dummyinfo;
2681 if (qmark != NULL) {
2683 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
2684 compose->gtkaspell);
2686 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
2688 quote_fmt_scan_string(qmark);
2691 buf = quote_fmt_get_buffer();
2693 alertpanel_error(_("Quote mark format error."));
2695 Xstrdup_a(quote_str, buf, goto error)
2698 if (fmt && *fmt != '\0') {
2701 while (*trimmed_body == '\n')
2705 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account,
2706 compose->gtkaspell);
2708 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account);
2710 if (need_unescape) {
2713 /* decode \-escape sequences in the internal representation of the quote format */
2714 tmp = malloc(strlen(fmt)+1);
2715 pref_get_unescaped_pref(tmp, fmt);
2716 quote_fmt_scan_string(tmp);
2720 quote_fmt_scan_string(fmt);
2724 buf = quote_fmt_get_buffer();
2726 gint line = quote_fmt_get_line();
2727 alertpanel_error(err_msg, line);
2733 prev_autowrap = compose->autowrap;
2734 compose->autowrap = FALSE;
2736 mark = gtk_text_buffer_get_insert(buffer);
2737 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2738 if (g_utf8_validate(buf, -1, NULL)) {
2739 gtk_text_buffer_insert(buffer, &iter, buf, -1);
2741 gchar *tmpout = NULL;
2742 tmpout = conv_codeset_strdup
2743 (buf, conv_get_locale_charset_str_no_utf8(),
2745 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
2747 tmpout = g_malloc(strlen(buf)*2+1);
2748 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
2750 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
2754 cursor_pos = quote_fmt_get_cursor_pos();
2755 compose->set_cursor_pos = cursor_pos;
2756 if (cursor_pos == -1) {
2759 gtk_text_buffer_get_start_iter(buffer, &iter);
2760 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
2761 gtk_text_buffer_place_cursor(buffer, &iter);
2763 compose->autowrap = prev_autowrap;
2764 if (compose->autowrap && rewrap)
2765 compose_wrap_all(compose);
2772 SIGNAL_UNBLOCK(buffer);
2774 procmsg_msginfo_free( dummyinfo );
2779 /* if ml_post is of type addr@host and from is of type
2780 * addr-anything@host, return TRUE
2782 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
2784 gchar *left_ml = NULL;
2785 gchar *right_ml = NULL;
2786 gchar *left_from = NULL;
2787 gchar *right_from = NULL;
2788 gboolean result = FALSE;
2790 if (!ml_post || !from)
2793 left_ml = g_strdup(ml_post);
2794 if (strstr(left_ml, "@")) {
2795 right_ml = strstr(left_ml, "@")+1;
2796 *(strstr(left_ml, "@")) = '\0';
2799 left_from = g_strdup(from);
2800 if (strstr(left_from, "@")) {
2801 right_from = strstr(left_from, "@")+1;
2802 *(strstr(left_from, "@")) = '\0';
2805 if (left_ml && left_from && right_ml && right_from
2806 && !strncmp(left_from, left_ml, strlen(left_ml))
2807 && !strcmp(right_from, right_ml)) {
2816 static gboolean same_address(const gchar *addr1, const gchar *addr2)
2818 gchar *my_addr1, *my_addr2;
2820 if (!addr1 || !addr2)
2823 Xstrdup_a(my_addr1, addr1, return FALSE);
2824 Xstrdup_a(my_addr2, addr2, return FALSE);
2826 extract_address(my_addr1);
2827 extract_address(my_addr2);
2829 return !strcasecmp(my_addr1, my_addr2);
2832 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
2833 gboolean to_all, gboolean to_ml,
2835 gboolean followup_and_reply_to)
2837 GSList *cc_list = NULL;
2840 gchar *replyto = NULL;
2841 GHashTable *to_table;
2843 gboolean reply_to_ml = FALSE;
2844 gboolean default_reply_to = FALSE;
2846 g_return_if_fail(compose->account != NULL);
2847 g_return_if_fail(msginfo != NULL);
2849 reply_to_ml = to_ml && compose->ml_post;
2851 default_reply_to = msginfo->folder &&
2852 msginfo->folder->prefs->enable_default_reply_to;
2854 if (compose->account->protocol != A_NNTP) {
2855 if (reply_to_ml && !default_reply_to) {
2857 gboolean is_subscr = is_subscription(compose->ml_post,
2860 /* normal answer to ml post with a reply-to */
2861 compose_entry_append(compose,
2864 if (compose->replyto
2865 && !same_address(compose->ml_post, compose->replyto))
2866 compose_entry_append(compose,
2870 /* answer to subscription confirmation */
2871 if (compose->replyto)
2872 compose_entry_append(compose,
2875 else if (msginfo->from)
2876 compose_entry_append(compose,
2881 else if (!(to_all || to_sender) && default_reply_to) {
2882 compose_entry_append(compose,
2883 msginfo->folder->prefs->default_reply_to,
2885 compose_entry_mark_default_to(compose,
2886 msginfo->folder->prefs->default_reply_to);
2891 Xstrdup_a(tmp1, msginfo->from, return);
2892 extract_address(tmp1);
2893 if (to_all || to_sender ||
2894 !account_find_from_address(tmp1))
2895 compose_entry_append(compose,
2896 (compose->replyto && !to_sender)
2897 ? compose->replyto :
2898 msginfo->from ? msginfo->from : "",
2900 else if (!to_all && !to_sender) {
2901 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
2902 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
2903 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
2904 compose_entry_append(compose,
2905 msginfo->from ? msginfo->from : "",
2908 /* replying to own mail, use original recp */
2909 compose_entry_append(compose,
2910 msginfo->to ? msginfo->to : "",
2912 compose_entry_append(compose,
2913 msginfo->cc ? msginfo->cc : "",
2919 if (to_sender || (compose->followup_to &&
2920 !strncmp(compose->followup_to, "poster", 6)))
2921 compose_entry_append
2923 (compose->replyto ? compose->replyto :
2924 msginfo->from ? msginfo->from : ""),
2927 else if (followup_and_reply_to || to_all) {
2928 compose_entry_append
2930 (compose->replyto ? compose->replyto :
2931 msginfo->from ? msginfo->from : ""),
2934 compose_entry_append
2936 compose->followup_to ? compose->followup_to :
2937 compose->newsgroups ? compose->newsgroups : "",
2938 COMPOSE_NEWSGROUPS);
2941 compose_entry_append
2943 compose->followup_to ? compose->followup_to :
2944 compose->newsgroups ? compose->newsgroups : "",
2945 COMPOSE_NEWSGROUPS);
2948 if (msginfo->subject && *msginfo->subject) {
2952 buf = p = g_strdup(msginfo->subject);
2953 p += subject_get_prefix_length(p);
2954 memmove(buf, p, strlen(p) + 1);
2956 buf2 = g_strdup_printf("Re: %s", buf);
2957 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
2962 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
2964 if (to_ml && compose->ml_post) return;
2965 if (!to_all || compose->account->protocol == A_NNTP) return;
2967 if (compose->replyto) {
2968 Xstrdup_a(replyto, compose->replyto, return);
2969 extract_address(replyto);
2971 if (msginfo->from) {
2972 Xstrdup_a(from, msginfo->from, return);
2973 extract_address(from);
2976 if (replyto && from)
2977 cc_list = address_list_append_with_comments(cc_list, from);
2978 if (to_all && msginfo->folder &&
2979 msginfo->folder->prefs->enable_default_reply_to)
2980 cc_list = address_list_append_with_comments(cc_list,
2981 msginfo->folder->prefs->default_reply_to);
2982 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
2983 cc_list = address_list_append_with_comments(cc_list, compose->cc);
2985 to_table = g_hash_table_new(g_str_hash, g_str_equal);
2987 g_hash_table_insert(to_table, g_utf8_strdown(replyto, -1), GINT_TO_POINTER(1));
2988 if (compose->account) {
2989 g_hash_table_insert(to_table, g_utf8_strdown(compose->account->address, -1),
2990 GINT_TO_POINTER(1));
2992 /* remove address on To: and that of current account */
2993 for (cur = cc_list; cur != NULL; ) {
2994 GSList *next = cur->next;
2997 addr = g_utf8_strdown(cur->data, -1);
2998 extract_address(addr);
3000 if (GPOINTER_TO_INT(g_hash_table_lookup(to_table, addr)) == 1)
3001 cc_list = g_slist_remove(cc_list, cur->data);
3003 g_hash_table_insert(to_table, addr, GINT_TO_POINTER(1));
3007 hash_free_strings(to_table);
3008 g_hash_table_destroy(to_table);
3011 for (cur = cc_list; cur != NULL; cur = cur->next)
3012 compose_entry_append(compose, (gchar *)cur->data,
3014 slist_free_strings(cc_list);
3015 g_slist_free(cc_list);
3020 #define SET_ENTRY(entry, str) \
3023 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3026 #define SET_ADDRESS(type, str) \
3029 compose_entry_append(compose, str, type); \
3032 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3034 g_return_if_fail(msginfo != NULL);
3036 SET_ENTRY(subject_entry, msginfo->subject);
3037 SET_ENTRY(from_name, msginfo->from);
3038 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3039 SET_ADDRESS(COMPOSE_CC, compose->cc);
3040 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3041 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3042 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3043 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3045 compose_update_priority_menu_item(compose);
3046 compose_update_privacy_system_menu_item(compose, FALSE);
3047 compose_show_first_last_header(compose, TRUE);
3053 static void compose_insert_sig(Compose *compose, gboolean replace)
3055 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3056 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3058 GtkTextIter iter, iter_end;
3060 gboolean prev_autowrap;
3061 gboolean found = FALSE;
3062 gboolean exists = FALSE;
3064 g_return_if_fail(compose->account != NULL);
3068 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3069 G_CALLBACK(compose_changed_cb),
3072 mark = gtk_text_buffer_get_insert(buffer);
3073 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3074 cur_pos = gtk_text_iter_get_offset (&iter);
3076 gtk_text_buffer_get_end_iter(buffer, &iter);
3078 exists = (compose->sig_str != NULL);
3081 GtkTextIter first_iter, start_iter, end_iter;
3083 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3085 if (!exists || compose->sig_str[0] == '\0')
3088 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3089 compose->signature_tag);
3092 /* include previous \n\n */
3093 gtk_text_iter_backward_chars(&first_iter, 2);
3094 start_iter = first_iter;
3095 end_iter = first_iter;
3097 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3098 compose->signature_tag);
3099 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3100 compose->signature_tag);
3102 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3108 g_free(compose->sig_str);
3109 compose->sig_str = compose_get_signature_str(compose);
3111 cur_pos = gtk_text_iter_get_offset(&iter);
3113 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3114 g_free(compose->sig_str);
3115 compose->sig_str = NULL;
3117 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3119 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3120 gtk_text_iter_forward_chars(&iter, 2);
3121 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3122 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3124 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3125 cur_pos = gtk_text_buffer_get_char_count (buffer);
3127 /* put the cursor where it should be
3128 * either where the quote_fmt says, either before the signature */
3129 if (compose->set_cursor_pos < 0)
3130 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3132 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3133 compose->set_cursor_pos);
3135 gtk_text_buffer_place_cursor(buffer, &iter);
3136 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3137 G_CALLBACK(compose_changed_cb),
3143 static gchar *compose_get_signature_str(Compose *compose)
3145 gchar *sig_body = NULL;
3146 gchar *sig_str = NULL;
3147 gchar *utf8_sig_str = NULL;
3149 g_return_val_if_fail(compose->account != NULL, NULL);
3151 if (!compose->account->sig_path)
3154 if (compose->account->sig_type == SIG_FILE) {
3155 if (!is_file_or_fifo_exist(compose->account->sig_path)) {
3156 g_warning("can't open signature file: %s\n",
3157 compose->account->sig_path);
3162 if (compose->account->sig_type == SIG_COMMAND)
3163 sig_body = get_command_output(compose->account->sig_path);
3167 tmp = file_read_to_str(compose->account->sig_path);
3170 sig_body = normalize_newlines(tmp);
3174 if (compose->account->sig_sep) {
3175 sig_str = g_strconcat("\n\n", compose->account->sig_sep, "\n", sig_body,
3179 sig_str = g_strconcat("\n\n", sig_body, NULL);
3182 if (g_utf8_validate(sig_str, -1, NULL) == TRUE)
3183 utf8_sig_str = sig_str;
3185 utf8_sig_str = conv_codeset_strdup
3186 (sig_str, conv_get_locale_charset_str_no_utf8(),
3192 return utf8_sig_str;
3195 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3198 GtkTextBuffer *buffer;
3201 const gchar *cur_encoding;
3202 gchar buf[BUFFSIZE];
3205 gboolean prev_autowrap;
3206 gboolean badtxt = FALSE;
3208 g_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3210 if ((fp = g_fopen(file, "rb")) == NULL) {
3211 FILE_OP_ERROR(file, "fopen");
3212 return COMPOSE_INSERT_READ_ERROR;
3215 prev_autowrap = compose->autowrap;
3216 compose->autowrap = FALSE;
3218 text = GTK_TEXT_VIEW(compose->text);
3219 buffer = gtk_text_view_get_buffer(text);
3220 mark = gtk_text_buffer_get_insert(buffer);
3221 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3223 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3224 G_CALLBACK(text_inserted),
3227 cur_encoding = conv_get_locale_charset_str_no_utf8();
3229 while (fgets(buf, sizeof(buf), fp) != NULL) {
3232 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3233 str = g_strdup(buf);
3235 str = conv_codeset_strdup
3236 (buf, cur_encoding, CS_INTERNAL);
3239 /* strip <CR> if DOS/Windows file,
3240 replace <CR> with <LF> if Macintosh file. */
3243 if (len > 0 && str[len - 1] != '\n') {
3245 if (str[len] == '\r') str[len] = '\n';
3248 gtk_text_buffer_insert(buffer, &iter, str, -1);
3252 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3253 G_CALLBACK(text_inserted),
3255 compose->autowrap = prev_autowrap;
3256 if (compose->autowrap)
3257 compose_wrap_all(compose);
3262 return COMPOSE_INSERT_INVALID_CHARACTER;
3264 return COMPOSE_INSERT_SUCCESS;
3267 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3268 const gchar *filename,
3269 const gchar *content_type)
3277 GtkListStore *store;
3279 gboolean has_binary = FALSE;
3281 if (!is_file_exist(file)) {
3282 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3283 gboolean result = FALSE;
3284 if (file_from_uri && is_file_exist(file_from_uri)) {
3285 result = compose_attach_append(
3286 compose, file_from_uri,
3290 g_free(file_from_uri);
3293 alertpanel_error("File %s doesn't exist\n", filename);
3296 if ((size = get_file_size(file)) < 0) {
3297 alertpanel_error("Can't get file size of %s\n", filename);
3301 alertpanel_error(_("File %s is empty."), filename);
3304 if ((fp = g_fopen(file, "rb")) == NULL) {
3305 alertpanel_error(_("Can't read %s."), filename);
3310 ainfo = g_new0(AttachInfo, 1);
3311 auto_ainfo = g_auto_pointer_new_with_free
3312 (ainfo, (GFreeFunc) compose_attach_info_free);
3313 ainfo->file = g_strdup(file);
3316 ainfo->content_type = g_strdup(content_type);
3317 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3319 MsgFlags flags = {0, 0};
3321 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3322 ainfo->encoding = ENC_7BIT;
3324 ainfo->encoding = ENC_8BIT;
3326 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3327 if (msginfo && msginfo->subject)
3328 name = g_strdup(msginfo->subject);
3330 name = g_path_get_basename(filename ? filename : file);
3332 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3334 procmsg_msginfo_free(msginfo);
3336 if (!g_ascii_strncasecmp(content_type, "text", 4))
3337 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3339 ainfo->encoding = ENC_BASE64;
3340 name = g_path_get_basename(filename ? filename : file);
3341 ainfo->name = g_strdup(name);
3345 ainfo->content_type = procmime_get_mime_type(file);
3346 if (!ainfo->content_type) {
3347 ainfo->content_type =
3348 g_strdup("application/octet-stream");
3349 ainfo->encoding = ENC_BASE64;
3350 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text", 4))
3352 procmime_get_encoding_for_text_file(file, &has_binary);
3354 ainfo->encoding = ENC_BASE64;
3355 name = g_path_get_basename(filename ? filename : file);
3356 ainfo->name = g_strdup(name);
3360 if (ainfo->name != NULL
3361 && !strcmp(ainfo->name, ".")) {
3362 g_free(ainfo->name);
3366 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3367 g_free(ainfo->content_type);
3368 ainfo->content_type = g_strdup("application/octet-stream");
3372 size_text = to_human_readable(size);
3374 store = GTK_LIST_STORE(gtk_tree_view_get_model
3375 (GTK_TREE_VIEW(compose->attach_clist)));
3377 gtk_list_store_append(store, &iter);
3378 gtk_list_store_set(store, &iter,
3379 COL_MIMETYPE, ainfo->content_type,
3380 COL_SIZE, size_text,
3381 COL_NAME, ainfo->name,
3383 COL_AUTODATA, auto_ainfo,
3386 g_auto_pointer_free(auto_ainfo);
3387 compose_attach_update_label(compose);
3391 static void compose_use_signing(Compose *compose, gboolean use_signing)
3393 GtkItemFactory *ifactory;
3394 GtkWidget *menuitem = NULL;
3396 compose->use_signing = use_signing;
3397 ifactory = gtk_item_factory_from_widget(compose->menubar);
3398 menuitem = gtk_item_factory_get_item
3399 (ifactory, "/Options/Sign");
3400 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
3404 static void compose_use_encryption(Compose *compose, gboolean use_encryption)
3406 GtkItemFactory *ifactory;
3407 GtkWidget *menuitem = NULL;
3409 compose->use_encryption = use_encryption;
3410 ifactory = gtk_item_factory_from_widget(compose->menubar);
3411 menuitem = gtk_item_factory_get_item
3412 (ifactory, "/Options/Encrypt");
3414 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
3418 #define NEXT_PART_NOT_CHILD(info) \
3420 node = info->node; \
3421 while (node->children) \
3422 node = g_node_last_child(node); \
3423 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3426 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3430 MimeInfo *firsttext = NULL;
3431 MimeInfo *encrypted = NULL;
3434 const gchar *partname = NULL;
3436 mimeinfo = procmime_scan_message(msginfo);
3437 if (!mimeinfo) return;
3439 if (mimeinfo->node->children == NULL) {
3440 procmime_mimeinfo_free_all(mimeinfo);
3444 /* find first content part */
3445 child = (MimeInfo *) mimeinfo->node->children->data;
3446 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3447 child = (MimeInfo *)child->node->children->data;
3449 if (child->type == MIMETYPE_TEXT) {
3451 debug_print("First text part found\n");
3452 } else if (compose->mode == COMPOSE_REEDIT &&
3453 child->type == MIMETYPE_APPLICATION &&
3454 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3455 encrypted = (MimeInfo *)child->node->parent->data;
3458 child = (MimeInfo *) mimeinfo->node->children->data;
3459 while (child != NULL) {
3462 if (child == encrypted) {
3463 /* skip this part of tree */
3464 NEXT_PART_NOT_CHILD(child);
3468 if (child->type == MIMETYPE_MULTIPART) {
3469 /* get the actual content */
3470 child = procmime_mimeinfo_next(child);
3474 if (child == firsttext) {
3475 child = procmime_mimeinfo_next(child);
3479 outfile = procmime_get_tmp_file_name(child);
3480 if ((err = procmime_get_part(outfile, child)) < 0)
3481 g_warning("Can't get the part of multipart message. (%s)", strerror(-err));
3483 gchar *content_type;
3485 content_type = procmime_get_content_type_str(child->type, child->subtype);
3487 /* if we meet a pgp signature, we don't attach it, but
3488 * we force signing. */
3489 if ((strcmp(content_type, "application/pgp-signature") &&
3490 strcmp(content_type, "application/pkcs7-signature") &&
3491 strcmp(content_type, "application/x-pkcs7-signature"))
3492 || compose->mode == COMPOSE_REDIRECT) {
3493 partname = procmime_mimeinfo_get_parameter(child, "filename");
3494 if (partname == NULL)
3495 partname = procmime_mimeinfo_get_parameter(child, "name");
3496 if (partname == NULL)
3498 compose_attach_append(compose, outfile,
3499 partname, content_type);
3501 compose_force_signing(compose, compose->account);
3503 g_free(content_type);
3506 NEXT_PART_NOT_CHILD(child);
3508 procmime_mimeinfo_free_all(mimeinfo);
3511 #undef NEXT_PART_NOT_CHILD
3516 WAIT_FOR_INDENT_CHAR,
3517 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3520 /* return indent length, we allow:
3521 indent characters followed by indent characters or spaces/tabs,
3522 alphabets and numbers immediately followed by indent characters,
3523 and the repeating sequences of the above
3524 If quote ends with multiple spaces, only the first one is included. */
3525 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3526 const GtkTextIter *start, gint *len)
3528 GtkTextIter iter = *start;
3532 IndentState state = WAIT_FOR_INDENT_CHAR;
3535 gint alnum_count = 0;
3536 gint space_count = 0;
3539 if (prefs_common.quote_chars == NULL) {
3543 while (!gtk_text_iter_ends_line(&iter)) {
3544 wc = gtk_text_iter_get_char(&iter);
3545 if (g_unichar_iswide(wc))
3547 clen = g_unichar_to_utf8(wc, ch);
3551 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
3552 is_space = g_unichar_isspace(wc);
3554 if (state == WAIT_FOR_INDENT_CHAR) {
3555 if (!is_indent && !g_unichar_isalnum(wc))
3558 quote_len += alnum_count + space_count + 1;
3559 alnum_count = space_count = 0;
3560 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
3563 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
3564 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
3568 else if (is_indent) {
3569 quote_len += alnum_count + space_count + 1;
3570 alnum_count = space_count = 0;
3573 state = WAIT_FOR_INDENT_CHAR;
3577 gtk_text_iter_forward_char(&iter);
3580 if (quote_len > 0 && space_count > 0)
3586 if (quote_len > 0) {
3588 gtk_text_iter_forward_chars(&iter, quote_len);
3589 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
3595 /* return TRUE if the line is itemized */
3596 static gboolean compose_is_itemized(GtkTextBuffer *buffer,
3597 const GtkTextIter *start)
3599 GtkTextIter iter = *start;
3604 if (gtk_text_iter_ends_line(&iter))
3608 wc = gtk_text_iter_get_char(&iter);
3609 if (!g_unichar_isspace(wc))
3611 gtk_text_iter_forward_char(&iter);
3612 if (gtk_text_iter_ends_line(&iter))
3616 clen = g_unichar_to_utf8(wc, ch);
3620 if (!strchr("*-+", ch[0]))
3623 gtk_text_iter_forward_char(&iter);
3624 if (gtk_text_iter_ends_line(&iter))
3626 wc = gtk_text_iter_get_char(&iter);
3627 if (g_unichar_isspace(wc))
3633 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
3634 const GtkTextIter *start,
3635 GtkTextIter *break_pos,
3639 GtkTextIter iter = *start, line_end = *start;
3640 PangoLogAttr *attrs;
3647 gboolean can_break = FALSE;
3648 gboolean do_break = FALSE;
3649 gboolean was_white = FALSE;
3650 gboolean prev_dont_break = FALSE;
3652 gtk_text_iter_forward_to_line_end(&line_end);
3653 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
3654 len = g_utf8_strlen(str, -1);
3658 g_warning("compose_get_line_break_pos: len = 0!\n");
3662 /* g_print("breaking line: %d: %s (len = %d)\n",
3663 gtk_text_iter_get_line(&iter), str, len); */
3665 attrs = g_new(PangoLogAttr, len + 1);
3667 pango_default_break(str, -1, NULL, attrs, len + 1);
3671 /* skip quote and leading spaces */
3672 for (i = 0; *p != '\0' && i < len; i++) {
3675 wc = g_utf8_get_char(p);
3676 if (i >= quote_len && !g_unichar_isspace(wc))
3678 if (g_unichar_iswide(wc))
3680 else if (*p == '\t')
3684 p = g_utf8_next_char(p);
3687 for (; *p != '\0' && i < len; i++) {
3688 PangoLogAttr *attr = attrs + i;
3692 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
3695 was_white = attr->is_white;
3697 /* don't wrap URI */
3698 if ((uri_len = get_uri_len(p)) > 0) {
3700 if (pos > 0 && col > max_col) {
3710 wc = g_utf8_get_char(p);
3711 if (g_unichar_iswide(wc)) {
3713 if (prev_dont_break && can_break && attr->is_line_break)
3715 } else if (*p == '\t')
3719 if (pos > 0 && col > max_col) {
3724 if (*p == '-' || *p == '/')
3725 prev_dont_break = TRUE;
3727 prev_dont_break = FALSE;
3729 p = g_utf8_next_char(p);
3733 debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col);
3738 *break_pos = *start;
3739 gtk_text_iter_set_line_offset(break_pos, pos);
3744 static gboolean compose_join_next_line(Compose *compose,
3745 GtkTextBuffer *buffer,
3747 const gchar *quote_str)
3749 GtkTextIter iter_ = *iter, cur, prev, next, end;
3750 PangoLogAttr attrs[3];
3752 gchar *next_quote_str;
3755 gboolean keep_cursor = FALSE;
3757 if (!gtk_text_iter_forward_line(&iter_) ||
3758 gtk_text_iter_ends_line(&iter_))
3761 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
3763 if ((quote_str || next_quote_str) &&
3764 strcmp2(quote_str, next_quote_str) != 0) {
3765 g_free(next_quote_str);
3768 g_free(next_quote_str);
3771 if (quote_len > 0) {
3772 gtk_text_iter_forward_chars(&end, quote_len);
3773 if (gtk_text_iter_ends_line(&end))
3777 /* don't join itemized lines */
3778 if (compose_is_itemized(buffer, &end))
3781 /* don't join signature separator */
3782 if (compose_is_sig_separator(compose, buffer, &iter_))
3785 /* delete quote str */
3787 gtk_text_buffer_delete(buffer, &iter_, &end);
3789 /* don't join line breaks put by the user */
3791 gtk_text_iter_backward_char(&cur);
3792 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
3793 gtk_text_iter_forward_char(&cur);
3797 gtk_text_iter_forward_char(&cur);
3798 /* delete linebreak and extra spaces */
3799 while (gtk_text_iter_backward_char(&cur)) {
3800 wc1 = gtk_text_iter_get_char(&cur);
3801 if (!g_unichar_isspace(wc1))
3806 while (!gtk_text_iter_ends_line(&cur)) {
3807 wc1 = gtk_text_iter_get_char(&cur);
3808 if (!g_unichar_isspace(wc1))
3810 gtk_text_iter_forward_char(&cur);
3813 if (!gtk_text_iter_equal(&prev, &next)) {
3816 mark = gtk_text_buffer_get_insert(buffer);
3817 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
3818 if (gtk_text_iter_equal(&prev, &cur))
3820 gtk_text_buffer_delete(buffer, &prev, &next);
3824 /* insert space if required */
3825 gtk_text_iter_backward_char(&prev);
3826 wc1 = gtk_text_iter_get_char(&prev);
3827 wc2 = gtk_text_iter_get_char(&next);
3828 gtk_text_iter_forward_char(&next);
3829 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
3830 pango_default_break(str, -1, NULL, attrs, 3);
3831 if (!attrs[1].is_line_break ||
3832 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
3833 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
3835 gtk_text_iter_backward_char(&iter_);
3836 gtk_text_buffer_place_cursor(buffer, &iter_);
3845 #define ADD_TXT_POS(bp_, ep_, pti_) \
3846 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
3847 last = last->next; \
3848 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
3849 last->next = NULL; \
3851 g_warning("alloc error scanning URIs\n"); \
3854 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
3856 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3857 GtkTextBuffer *buffer;
3858 GtkTextIter iter, break_pos, end_of_line;
3859 gchar *quote_str = NULL;
3861 gboolean wrap_quote = prefs_common.linewrap_quote;
3862 gboolean prev_autowrap = compose->autowrap;
3863 gint startq_offset = -1, noq_offset = -1;
3864 gint uri_start = -1, uri_stop = -1;
3865 gint nouri_start = -1, nouri_stop = -1;
3866 gint num_blocks = 0;
3867 gint quotelevel = -1;
3868 gboolean modified = force;
3869 gboolean removed = FALSE;
3870 gboolean modified_before_remove = FALSE;
3872 gboolean start = TRUE;
3877 if (compose->draft_timeout_tag == -2) {
3881 compose->autowrap = FALSE;
3883 buffer = gtk_text_view_get_buffer(text);
3884 undo_wrapping(compose->undostruct, TRUE);
3889 mark = gtk_text_buffer_get_insert(buffer);
3890 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3894 if (compose->draft_timeout_tag == -2) {
3895 if (gtk_text_iter_ends_line(&iter)) {
3896 while (gtk_text_iter_ends_line(&iter) &&
3897 gtk_text_iter_forward_line(&iter))
3900 while (gtk_text_iter_backward_line(&iter)) {
3901 if (gtk_text_iter_ends_line(&iter)) {
3902 gtk_text_iter_forward_line(&iter);
3908 /* move to line start */
3909 gtk_text_iter_set_line_offset(&iter, 0);
3911 /* go until paragraph end (empty line) */
3912 while (start || !gtk_text_iter_ends_line(&iter)) {
3913 gchar *scanpos = NULL;
3914 /* parse table - in order of priority */
3916 const gchar *needle; /* token */
3918 /* token search function */
3919 gchar *(*search) (const gchar *haystack,
3920 const gchar *needle);
3921 /* part parsing function */
3922 gboolean (*parse) (const gchar *start,
3923 const gchar *scanpos,
3927 /* part to URI function */
3928 gchar *(*build_uri) (const gchar *bp,
3932 static struct table parser[] = {
3933 {"http://", strcasestr, get_uri_part, make_uri_string},
3934 {"https://", strcasestr, get_uri_part, make_uri_string},
3935 {"ftp://", strcasestr, get_uri_part, make_uri_string},
3936 {"sftp://", strcasestr, get_uri_part, make_uri_string},
3937 {"www.", strcasestr, get_uri_part, make_http_string},
3938 {"mailto:", strcasestr, get_uri_part, make_uri_string},
3939 {"@", strcasestr, get_email_part, make_email_string}
3941 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
3942 gint last_index = PARSE_ELEMS;
3944 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
3948 if (!prev_autowrap && num_blocks == 0) {
3950 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3951 G_CALLBACK(text_inserted),
3954 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
3957 uri_start = uri_stop = -1;
3959 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
3962 debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str);
3963 if (startq_offset == -1)
3964 startq_offset = gtk_text_iter_get_offset(&iter);
3965 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
3966 if (quotelevel > 2) {
3967 /* recycle colors */
3968 if (prefs_common.recycle_quote_colors)
3977 if (startq_offset == -1)
3978 noq_offset = gtk_text_iter_get_offset(&iter);
3982 if (prev_autowrap == FALSE && !force && !wrap_quote) {
3985 if (gtk_text_iter_ends_line(&iter)) {
3987 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
3988 prefs_common.linewrap_len,
3990 GtkTextIter prev, next, cur;
3992 if (prev_autowrap != FALSE || force) {
3993 compose->automatic_break = TRUE;
3995 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
3996 compose->automatic_break = FALSE;
3997 } else if (quote_str && wrap_quote) {
3998 compose->automatic_break = TRUE;
4000 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4001 compose->automatic_break = FALSE;
4004 /* remove trailing spaces */
4006 gtk_text_iter_backward_char(&cur);
4008 while (!gtk_text_iter_starts_line(&cur)) {
4011 gtk_text_iter_backward_char(&cur);
4012 wc = gtk_text_iter_get_char(&cur);
4013 if (!g_unichar_isspace(wc))
4017 if (!gtk_text_iter_equal(&prev, &next)) {
4018 gtk_text_buffer_delete(buffer, &prev, &next);
4020 gtk_text_iter_forward_char(&break_pos);
4024 gtk_text_buffer_insert(buffer, &break_pos,
4028 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4030 /* move iter to current line start */
4031 gtk_text_iter_set_line_offset(&iter, 0);
4038 /* move iter to next line start */
4044 if (!prev_autowrap && num_blocks > 0) {
4046 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4047 G_CALLBACK(text_inserted),
4051 while (!gtk_text_iter_ends_line(&end_of_line)) {
4052 gtk_text_iter_forward_char(&end_of_line);
4054 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4056 nouri_start = gtk_text_iter_get_offset(&iter);
4057 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4059 walk_pos = gtk_text_iter_get_offset(&iter);
4060 /* FIXME: this looks phony. scanning for anything in the parse table */
4061 for (n = 0; n < PARSE_ELEMS; n++) {
4064 tmp = parser[n].search(walk, parser[n].needle);
4066 if (scanpos == NULL || tmp < scanpos) {
4075 /* check if URI can be parsed */
4076 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4077 (const gchar **)&ep, FALSE)
4078 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4082 strlen(parser[last_index].needle);
4085 uri_start = walk_pos + (bp - o_walk);
4086 uri_stop = walk_pos + (ep - o_walk);
4090 gtk_text_iter_forward_line(&iter);
4093 if (startq_offset != -1) {
4094 GtkTextIter startquote, endquote;
4095 gtk_text_buffer_get_iter_at_offset(
4096 buffer, &startquote, startq_offset);
4099 switch (quotelevel) {
4101 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4102 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4103 gtk_text_buffer_apply_tag_by_name(
4104 buffer, "quote0", &startquote, &endquote);
4105 gtk_text_buffer_remove_tag_by_name(
4106 buffer, "quote1", &startquote, &endquote);
4107 gtk_text_buffer_remove_tag_by_name(
4108 buffer, "quote2", &startquote, &endquote);
4113 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4114 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4115 gtk_text_buffer_apply_tag_by_name(
4116 buffer, "quote1", &startquote, &endquote);
4117 gtk_text_buffer_remove_tag_by_name(
4118 buffer, "quote0", &startquote, &endquote);
4119 gtk_text_buffer_remove_tag_by_name(
4120 buffer, "quote2", &startquote, &endquote);
4125 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4126 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4127 gtk_text_buffer_apply_tag_by_name(
4128 buffer, "quote2", &startquote, &endquote);
4129 gtk_text_buffer_remove_tag_by_name(
4130 buffer, "quote0", &startquote, &endquote);
4131 gtk_text_buffer_remove_tag_by_name(
4132 buffer, "quote1", &startquote, &endquote);
4138 } else if (noq_offset != -1) {
4139 GtkTextIter startnoquote, endnoquote;
4140 gtk_text_buffer_get_iter_at_offset(
4141 buffer, &startnoquote, noq_offset);
4144 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4145 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4146 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4147 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4148 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4149 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4150 gtk_text_buffer_remove_tag_by_name(
4151 buffer, "quote0", &startnoquote, &endnoquote);
4152 gtk_text_buffer_remove_tag_by_name(
4153 buffer, "quote1", &startnoquote, &endnoquote);
4154 gtk_text_buffer_remove_tag_by_name(
4155 buffer, "quote2", &startnoquote, &endnoquote);
4161 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4162 GtkTextIter nouri_start_iter, nouri_end_iter;
4163 gtk_text_buffer_get_iter_at_offset(
4164 buffer, &nouri_start_iter, nouri_start);
4165 gtk_text_buffer_get_iter_at_offset(
4166 buffer, &nouri_end_iter, nouri_stop);
4167 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4168 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4169 gtk_text_buffer_remove_tag_by_name(
4170 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4171 modified_before_remove = modified;
4176 if (uri_start > 0 && uri_stop > 0) {
4177 GtkTextIter uri_start_iter, uri_end_iter, back;
4178 gtk_text_buffer_get_iter_at_offset(
4179 buffer, &uri_start_iter, uri_start);
4180 gtk_text_buffer_get_iter_at_offset(
4181 buffer, &uri_end_iter, uri_stop);
4182 back = uri_end_iter;
4183 gtk_text_iter_backward_char(&back);
4184 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4185 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4186 gtk_text_buffer_apply_tag_by_name(
4187 buffer, "link", &uri_start_iter, &uri_end_iter);
4189 if (removed && !modified_before_remove) {
4195 debug_print("not modified, out after %d lines\n", lines);
4200 debug_print("modified, out after %d lines\n", lines);
4204 undo_wrapping(compose->undostruct, FALSE);
4205 compose->autowrap = prev_autowrap;
4210 void compose_action_cb(void *data)
4212 Compose *compose = (Compose *)data;
4213 compose_wrap_all(compose);
4216 static void compose_wrap_all(Compose *compose)
4218 compose_wrap_all_full(compose, FALSE);
4221 static void compose_wrap_all_full(Compose *compose, gboolean force)
4223 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4224 GtkTextBuffer *buffer;
4226 gboolean modified = TRUE;
4228 buffer = gtk_text_view_get_buffer(text);
4230 gtk_text_buffer_get_start_iter(buffer, &iter);
4231 while (!gtk_text_iter_is_end(&iter) && modified)
4232 modified = compose_beautify_paragraph(compose, &iter, force);
4236 static void compose_set_title(Compose *compose)
4242 edited = compose->modified ? _(" [Edited]") : "";
4244 subject = gtk_editable_get_chars(
4245 GTK_EDITABLE(compose->subject_entry), 0, -1);
4248 if (subject && strlen(subject))
4249 str = g_strdup_printf(_("%s - Compose message%s"),
4252 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4254 str = g_strdup(_("Compose message"));
4257 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4263 * compose_current_mail_account:
4265 * Find a current mail account (the currently selected account, or the
4266 * default account, if a news account is currently selected). If a
4267 * mail account cannot be found, display an error message.
4269 * Return value: Mail account, or NULL if not found.
4271 static PrefsAccount *
4272 compose_current_mail_account(void)
4276 if (cur_account && cur_account->protocol != A_NNTP)
4279 ac = account_get_default();
4280 if (!ac || ac->protocol == A_NNTP) {
4281 alertpanel_error(_("Account for sending mail is not specified.\n"
4282 "Please select a mail account before sending."));
4289 #define QUOTE_IF_REQUIRED(out, str) \
4291 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4295 len = strlen(str) + 3; \
4296 if ((__tmp = alloca(len)) == NULL) { \
4297 g_warning("can't allocate memory\n"); \
4298 g_string_free(header, TRUE); \
4301 g_snprintf(__tmp, len, "\"%s\"", str); \
4306 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4307 g_warning("can't allocate memory\n"); \
4308 g_string_free(header, TRUE); \
4311 strcpy(__tmp, str); \
4317 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4319 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4323 len = strlen(str) + 3; \
4324 if ((__tmp = alloca(len)) == NULL) { \
4325 g_warning("can't allocate memory\n"); \
4328 g_snprintf(__tmp, len, "\"%s\"", str); \
4333 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4334 g_warning("can't allocate memory\n"); \
4337 strcpy(__tmp, str); \
4343 static void compose_select_account(Compose *compose, PrefsAccount *account,
4346 GtkItemFactory *ifactory;
4349 g_return_if_fail(account != NULL);
4351 compose->account = account;
4353 if (account->name && *account->name) {
4355 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4356 from = g_strdup_printf("%s <%s>",
4357 buf, account->address);
4358 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4360 from = g_strdup_printf("<%s>",
4362 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4367 compose_set_title(compose);
4369 ifactory = gtk_item_factory_from_widget(compose->menubar);
4371 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4372 menu_set_active(ifactory, "/Options/Sign", TRUE);
4374 menu_set_active(ifactory, "/Options/Sign", FALSE);
4375 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4376 menu_set_active(ifactory, "/Options/Encrypt", TRUE);
4378 menu_set_active(ifactory, "/Options/Encrypt", FALSE);
4380 activate_privacy_system(compose, account, FALSE);
4382 if (!init && compose->mode != COMPOSE_REDIRECT) {
4383 undo_block(compose->undostruct);
4384 compose_insert_sig(compose, TRUE);
4385 undo_unblock(compose->undostruct);
4389 /* use account's dict info if set */
4390 if (compose->gtkaspell) {
4391 if (account->enable_default_dictionary)
4392 gtkaspell_change_dict(compose->gtkaspell,
4393 account->default_dictionary, FALSE);
4394 if (account->enable_default_alt_dictionary)
4395 gtkaspell_change_alt_dict(compose->gtkaspell,
4396 account->default_alt_dictionary);
4397 if (account->enable_default_dictionary
4398 || account->enable_default_alt_dictionary)
4399 compose_spell_menu_changed(compose);
4404 gboolean compose_check_for_valid_recipient(Compose *compose) {
4405 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
4406 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
4407 gboolean recipient_found = FALSE;
4411 /* free to and newsgroup list */
4412 slist_free_strings(compose->to_list);
4413 g_slist_free(compose->to_list);
4414 compose->to_list = NULL;
4416 slist_free_strings(compose->newsgroup_list);
4417 g_slist_free(compose->newsgroup_list);
4418 compose->newsgroup_list = NULL;
4420 /* search header entries for to and newsgroup entries */
4421 for (list = compose->header_list; list; list = list->next) {
4424 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_BIN(((ComposeHeaderEntry *)list->data)->combo)->child), 0, -1);
4425 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4428 if (entry[0] != '\0') {
4429 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
4430 if (!strcmp(header, prefs_common_translated_header_name(*strptr))) {
4431 compose->to_list = address_list_append(compose->to_list, entry);
4432 recipient_found = TRUE;
4435 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
4436 if (!strcmp(header, prefs_common_translated_header_name(*strptr))) {
4437 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
4438 recipient_found = TRUE;
4445 return recipient_found;
4448 static gboolean compose_check_for_set_recipients(Compose *compose)
4450 if (compose->account->set_autocc && compose->account->auto_cc) {
4451 gboolean found_other = FALSE;
4453 /* search header entries for to and newsgroup entries */
4454 for (list = compose->header_list; list; list = list->next) {
4457 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4458 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_BIN(((ComposeHeaderEntry *)list->data)->combo)->child), 0, -1);
4460 if (strcmp(entry, compose->account->auto_cc)
4461 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
4471 if (compose->batch) {
4472 gtk_widget_show_all(compose->window);
4474 aval = alertpanel(_("Send"),
4475 _("The only recipient is the default CC address. Send anyway?"),
4476 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4477 if (aval != G_ALERTALTERNATE)
4481 if (compose->account->set_autobcc && compose->account->auto_bcc) {
4482 gboolean found_other = FALSE;
4484 /* search header entries for to and newsgroup entries */
4485 for (list = compose->header_list; list; list = list->next) {
4488 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4489 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_BIN(((ComposeHeaderEntry *)list->data)->combo)->child), 0, -1);
4491 if (strcmp(entry, compose->account->auto_bcc)
4492 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
4502 if (compose->batch) {
4503 gtk_widget_show_all(compose->window);
4505 aval = alertpanel(_("Send"),
4506 _("The only recipient is the default BCC address. Send anyway?"),
4507 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4508 if (aval != G_ALERTALTERNATE)
4515 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
4519 if (compose_check_for_valid_recipient(compose) == FALSE) {
4520 if (compose->batch) {
4521 gtk_widget_show_all(compose->window);
4523 alertpanel_error(_("Recipient is not specified."));
4527 if (compose_check_for_set_recipients(compose) == FALSE) {
4531 if (!compose->batch) {
4532 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
4533 if (*str == '\0' && check_everything == TRUE &&
4534 compose->mode != COMPOSE_REDIRECT) {
4536 gchar *button_label;
4539 if (compose->sending)
4540 button_label = _("+_Send");
4542 button_label = _("+_Queue");
4543 message = g_strdup_printf(_("Subject is empty. %s"),
4544 compose->sending?_("Send it anyway?"):
4545 _("Queue it anyway?"));
4547 aval = alertpanel(compose->sending?_("Send"):_("Send later"), message,
4548 GTK_STOCK_CANCEL, button_label, NULL);
4550 if (aval != G_ALERTALTERNATE)
4555 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
4561 gint compose_send(Compose *compose)
4564 FolderItem *folder = NULL;
4566 gchar *msgpath = NULL;
4567 gboolean discard_window = FALSE;
4568 gchar *errstr = NULL;
4569 gchar *tmsgid = NULL;
4570 MainWindow *mainwin = mainwindow_get_mainwindow();
4571 gboolean queued_removed = FALSE;
4573 if (prefs_common.send_dialog_invisible
4574 || compose->batch == TRUE)
4575 discard_window = TRUE;
4577 compose_allow_user_actions (compose, FALSE);
4578 compose->sending = TRUE;
4580 if (compose_check_entries(compose, TRUE) == FALSE) {
4581 if (compose->batch) {
4582 gtk_widget_show_all(compose->window);
4588 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
4591 if (compose->batch) {
4592 gtk_widget_show_all(compose->window);
4595 alertpanel_error(_("Could not queue message for sending:\n\n"
4596 "Charset conversion failed."));
4597 } else if (val == -5) {
4598 alertpanel_error(_("Could not queue message for sending:\n\n"
4599 "Couldn't get recipient encryption key."));
4600 } else if (val == -6) {
4602 } else if (val == -3) {
4603 if (privacy_peek_error())
4604 alertpanel_error(_("Could not queue message for sending:\n\n"
4605 "Signature failed: %s"), privacy_get_error());
4606 } else if (val == -2 && errno != 0) {
4607 alertpanel_error(_("Could not queue message for sending:\n\n%s."), strerror(errno));
4609 alertpanel_error(_("Could not queue message for sending."));
4614 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
4615 if (discard_window) {
4616 compose->sending = FALSE;
4617 compose_close(compose);
4618 /* No more compose access in the normal codepath
4619 * after this point! */
4624 alertpanel_error(_("The message was queued but could not be "
4625 "sent.\nUse \"Send queued messages\" from "
4626 "the main window to retry."));
4627 if (!discard_window) {
4634 if (msgpath == NULL) {
4635 msgpath = folder_item_fetch_msg(folder, msgnum);
4636 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
4639 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
4643 if (!discard_window) {
4645 if (!queued_removed)
4646 folder_item_remove_msg(folder, msgnum);
4647 folder_item_scan(folder);
4649 /* make sure we delete that */
4650 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
4652 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
4653 folder_item_remove_msg(folder, tmp->msgnum);
4654 procmsg_msginfo_free(tmp);
4661 if (!queued_removed)
4662 folder_item_remove_msg(folder, msgnum);
4663 folder_item_scan(folder);
4665 /* make sure we delete that */
4666 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
4668 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
4669 folder_item_remove_msg(folder, tmp->msgnum);
4670 procmsg_msginfo_free(tmp);
4673 if (!discard_window) {
4674 compose->sending = FALSE;
4675 compose_allow_user_actions (compose, TRUE);
4676 compose_close(compose);
4680 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
4681 "the main window to retry."), errstr);
4684 alertpanel_error_log(_("The message was queued but could not be "
4685 "sent.\nUse \"Send queued messages\" from "
4686 "the main window to retry."));
4688 if (!discard_window) {
4697 toolbar_main_set_sensitive(mainwin);
4698 main_window_set_menu_sensitive(mainwin);
4704 compose_allow_user_actions (compose, TRUE);
4705 compose->sending = FALSE;
4706 compose->modified = TRUE;
4707 toolbar_main_set_sensitive(mainwin);
4708 main_window_set_menu_sensitive(mainwin);
4713 static gboolean compose_use_attach(Compose *compose)
4715 GtkTreeModel *model = gtk_tree_view_get_model
4716 (GTK_TREE_VIEW(compose->attach_clist));
4717 return gtk_tree_model_iter_n_children(model, NULL) > 0;
4720 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
4723 gchar buf[BUFFSIZE];
4725 gboolean first_to_address;
4726 gboolean first_cc_address;
4728 ComposeHeaderEntry *headerentry;
4729 const gchar *headerentryname;
4730 const gchar *cc_hdr;
4731 const gchar *to_hdr;
4732 gboolean err = FALSE;
4734 debug_print("Writing redirect header\n");
4736 cc_hdr = prefs_common_translated_header_name("Cc:");
4737 to_hdr = prefs_common_translated_header_name("To:");
4739 first_to_address = TRUE;
4740 for (list = compose->header_list; list; list = list->next) {
4741 headerentry = ((ComposeHeaderEntry *)list->data);
4742 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_BIN(headerentry->combo)->child));
4744 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
4745 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
4746 Xstrdup_a(str, entstr, return -1);
4748 if (str[0] != '\0') {
4749 compose_convert_header
4750 (compose, buf, sizeof(buf), str,
4751 strlen("Resent-To") + 2, TRUE);
4753 if (first_to_address) {
4754 err |= (fprintf(fp, "Resent-To: ") < 0);
4755 first_to_address = FALSE;
4757 err |= (fprintf(fp, ",") < 0);
4759 err |= (fprintf(fp, "%s", buf) < 0);
4763 if (!first_to_address) {
4764 err |= (fprintf(fp, "\n") < 0);
4767 first_cc_address = TRUE;
4768 for (list = compose->header_list; list; list = list->next) {
4769 headerentry = ((ComposeHeaderEntry *)list->data);
4770 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_BIN(headerentry->combo)->child));
4772 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
4773 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
4774 Xstrdup_a(str, strg, return -1);
4776 if (str[0] != '\0') {
4777 compose_convert_header
4778 (compose, buf, sizeof(buf), str,
4779 strlen("Resent-Cc") + 2, TRUE);
4781 if (first_cc_address) {
4782 err |= (fprintf(fp, "Resent-Cc: ") < 0);
4783 first_cc_address = FALSE;
4785 err |= (fprintf(fp, ",") < 0);
4787 err |= (fprintf(fp, "%s", buf) < 0);
4791 if (!first_cc_address) {
4792 err |= (fprintf(fp, "\n") < 0);
4795 return (err ? -1:0);
4798 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
4800 gchar buf[BUFFSIZE];
4802 const gchar *entstr;
4803 /* struct utsname utsbuf; */
4804 gboolean err = FALSE;
4806 g_return_val_if_fail(fp != NULL, -1);
4807 g_return_val_if_fail(compose->account != NULL, -1);
4808 g_return_val_if_fail(compose->account->address != NULL, -1);
4811 get_rfc822_date(buf, sizeof(buf));
4812 err |= (fprintf(fp, "Resent-Date: %s\n", buf) < 0);
4815 if (compose->account->name && *compose->account->name) {
4816 compose_convert_header
4817 (compose, buf, sizeof(buf), compose->account->name,
4818 strlen("From: "), TRUE);
4819 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
4820 buf, compose->account->address) < 0);
4822 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
4825 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
4826 if (*entstr != '\0') {
4827 Xstrdup_a(str, entstr, return -1);
4830 compose_convert_header(compose, buf, sizeof(buf), str,
4831 strlen("Subject: "), FALSE);
4832 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
4836 /* Resent-Message-ID */
4837 if (compose->account->set_domain && compose->account->domain) {
4838 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
4839 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
4840 g_snprintf(buf, sizeof(buf), "%s",
4841 strchr(compose->account->address, '@') ?
4842 strchr(compose->account->address, '@')+1 :
4843 compose->account->address);
4845 g_snprintf(buf, sizeof(buf), "%s", "");
4848 if (compose->account->gen_msgid) {
4849 generate_msgid(buf, sizeof(buf));
4850 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", buf) < 0);
4851 compose->msgid = g_strdup(buf);
4853 compose->msgid = NULL;
4856 if (compose_redirect_write_headers_from_headerlist(compose, fp))
4859 /* separator between header and body */
4860 err |= (fputs("\n", fp) == EOF);
4862 return (err ? -1:0);
4865 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
4869 gchar buf[BUFFSIZE];
4871 gboolean skip = FALSE;
4872 gboolean err = FALSE;
4873 gchar *not_included[]={
4874 "Return-Path:", "Delivered-To:", "Received:",
4875 "Subject:", "X-UIDL:", "AF:",
4876 "NF:", "PS:", "SRH:",
4877 "SFN:", "DSR:", "MID:",
4878 "CFG:", "PT:", "S:",
4879 "RQ:", "SSV:", "NSV:",
4880 "SSH:", "R:", "MAID:",
4881 "NAID:", "RMID:", "FMID:",
4882 "SCF:", "RRCPT:", "NG:",
4883 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
4884 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
4885 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
4886 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
4889 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
4890 FILE_OP_ERROR(compose->redirect_filename, "fopen");
4894 while (procheader_get_one_field_asis(buf, sizeof(buf), fp) != -1) {
4896 for (i = 0; not_included[i] != NULL; i++) {
4897 if (g_ascii_strncasecmp(buf, not_included[i],
4898 strlen(not_included[i])) == 0) {
4905 if (fputs(buf, fdest) == -1)
4908 if (!prefs_common.redirect_keep_from) {
4909 if (g_ascii_strncasecmp(buf, "From:",
4910 strlen("From:")) == 0) {
4911 err |= (fputs(" (by way of ", fdest) == EOF);
4912 if (compose->account->name
4913 && *compose->account->name) {
4914 compose_convert_header
4915 (compose, buf, sizeof(buf),
4916 compose->account->name,
4919 err |= (fprintf(fdest, "%s <%s>",
4921 compose->account->address) < 0);
4923 err |= (fprintf(fdest, "%s",
4924 compose->account->address) < 0);
4925 err |= (fputs(")", fdest) == EOF);
4929 if (fputs("\n", fdest) == -1)
4936 if (compose_redirect_write_headers(compose, fdest))
4939 while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
4940 if (fwrite(buf, sizeof(gchar), len, fdest) != len)
4953 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
4955 GtkTextBuffer *buffer;
4956 GtkTextIter start, end;
4959 const gchar *out_codeset;
4960 EncodingType encoding;
4961 MimeInfo *mimemsg, *mimetext;
4964 if (action == COMPOSE_WRITE_FOR_SEND)
4965 attach_parts = TRUE;
4967 /* create message MimeInfo */
4968 mimemsg = procmime_mimeinfo_new();
4969 mimemsg->type = MIMETYPE_MESSAGE;
4970 mimemsg->subtype = g_strdup("rfc822");
4971 mimemsg->content = MIMECONTENT_MEM;
4972 mimemsg->tmp = TRUE; /* must free content later */
4973 mimemsg->data.mem = compose_get_header(compose);
4975 /* Create text part MimeInfo */
4976 /* get all composed text */
4977 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
4978 gtk_text_buffer_get_start_iter(buffer, &start);
4979 gtk_text_buffer_get_end_iter(buffer, &end);
4980 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
4981 if (is_ascii_str(chars)) {
4984 out_codeset = CS_US_ASCII;
4985 encoding = ENC_7BIT;
4987 const gchar *src_codeset = CS_INTERNAL;
4989 out_codeset = conv_get_charset_str(compose->out_encoding);
4992 gchar *test_conv_global_out = NULL;
4993 gchar *test_conv_reply = NULL;
4995 /* automatic mode. be automatic. */
4996 codeconv_set_strict(TRUE);
4998 out_codeset = conv_get_outgoing_charset_str();
5000 debug_print("trying to convert to %s\n", out_codeset);
5001 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5004 if (!test_conv_global_out && compose->orig_charset
5005 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5006 out_codeset = compose->orig_charset;
5007 debug_print("failure; trying to convert to %s\n", out_codeset);
5008 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5011 if (!test_conv_global_out && !test_conv_reply) {
5013 out_codeset = CS_INTERNAL;
5014 debug_print("failure; finally using %s\n", out_codeset);
5016 g_free(test_conv_global_out);
5017 g_free(test_conv_reply);
5018 codeconv_set_strict(FALSE);
5021 if (!g_ascii_strcasecmp(out_codeset, CS_US_ASCII))
5022 out_codeset = CS_ISO_8859_1;
5024 if (prefs_common.encoding_method == CTE_BASE64)
5025 encoding = ENC_BASE64;
5026 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5027 encoding = ENC_QUOTED_PRINTABLE;
5028 else if (prefs_common.encoding_method == CTE_8BIT)
5029 encoding = ENC_8BIT;
5031 encoding = procmime_get_encoding_for_charset(out_codeset);
5033 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5034 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5036 if (action == COMPOSE_WRITE_FOR_SEND) {
5037 codeconv_set_strict(TRUE);
5038 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5039 codeconv_set_strict(FALSE);
5045 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5046 "to the specified %s charset.\n"
5047 "Send it as %s?"), out_codeset, src_codeset);
5048 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL, _("+_Send"), NULL, FALSE,
5049 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5052 if (aval != G_ALERTALTERNATE) {
5057 out_codeset = src_codeset;
5063 out_codeset = src_codeset;
5069 if (encoding == ENC_8BIT || encoding == ENC_7BIT) {
5070 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5071 strstr(buf, "\nFrom ") != NULL) {
5072 encoding = ENC_QUOTED_PRINTABLE;
5076 mimetext = procmime_mimeinfo_new();
5077 mimetext->content = MIMECONTENT_MEM;
5078 mimetext->tmp = TRUE; /* must free content later */
5079 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5080 * and free the data, which we need later. */
5081 mimetext->data.mem = g_strdup(buf);
5082 mimetext->type = MIMETYPE_TEXT;
5083 mimetext->subtype = g_strdup("plain");
5084 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5085 g_strdup(out_codeset));
5087 /* protect trailing spaces when signing message */
5088 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5089 privacy_system_can_sign(compose->privacy_system)) {
5090 encoding = ENC_QUOTED_PRINTABLE;
5093 debug_print("main text: %zd bytes encoded as %s in %d\n",
5094 strlen(buf), out_codeset, encoding);
5096 /* check for line length limit */
5097 if (action == COMPOSE_WRITE_FOR_SEND &&
5098 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5099 check_line_length(buf, 1000, &line) < 0) {
5103 msg = g_strdup_printf
5104 (_("Line %d exceeds the line length limit (998 bytes).\n"
5105 "The contents of the message might be broken on the way to the delivery.\n"
5107 "Send it anyway?"), line + 1);
5108 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5110 if (aval != G_ALERTALTERNATE) {
5116 if (encoding != ENC_UNKNOWN)
5117 procmime_encode_content(mimetext, encoding);
5119 /* append attachment parts */
5120 if (compose_use_attach(compose) && attach_parts) {
5121 MimeInfo *mimempart;
5122 gchar *boundary = NULL;
5123 mimempart = procmime_mimeinfo_new();
5124 mimempart->content = MIMECONTENT_EMPTY;
5125 mimempart->type = MIMETYPE_MULTIPART;
5126 mimempart->subtype = g_strdup("mixed");
5130 boundary = generate_mime_boundary(NULL);
5131 } while (strstr(buf, boundary) != NULL);
5133 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5136 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5138 g_node_append(mimempart->node, mimetext->node);
5139 g_node_append(mimemsg->node, mimempart->node);
5141 compose_add_attachments(compose, mimempart);
5143 g_node_append(mimemsg->node, mimetext->node);
5147 /* sign message if sending */
5148 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5149 privacy_system_can_sign(compose->privacy_system))
5150 if (!privacy_sign(compose->privacy_system, mimemsg, compose->account))
5153 procmime_write_mimeinfo(mimemsg, fp);
5155 procmime_mimeinfo_free_all(mimemsg);
5160 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5162 GtkTextBuffer *buffer;
5163 GtkTextIter start, end;
5168 if ((fp = g_fopen(file, "wb")) == NULL) {
5169 FILE_OP_ERROR(file, "fopen");
5173 /* chmod for security */
5174 if (change_file_mode_rw(fp, file) < 0) {
5175 FILE_OP_ERROR(file, "chmod");
5176 g_warning("can't change file mode\n");
5179 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5180 gtk_text_buffer_get_start_iter(buffer, &start);
5181 gtk_text_buffer_get_end_iter(buffer, &end);
5182 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5184 chars = conv_codeset_strdup
5185 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5188 if (!chars) return -1;
5191 len = strlen(chars);
5192 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5193 FILE_OP_ERROR(file, "fwrite");
5202 if (fclose(fp) == EOF) {
5203 FILE_OP_ERROR(file, "fclose");
5210 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5213 MsgInfo *msginfo = compose->targetinfo;
5215 g_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5216 if (!msginfo) return -1;
5218 if (!force && MSG_IS_LOCKED(msginfo->flags))
5221 item = msginfo->folder;
5222 g_return_val_if_fail(item != NULL, -1);
5224 if (procmsg_msg_exist(msginfo) &&
5225 (folder_has_parent_of_type(item, F_QUEUE) ||
5226 folder_has_parent_of_type(item, F_DRAFT)
5227 || msginfo == compose->autosaved_draft)) {
5228 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5229 g_warning("can't remove the old message\n");
5232 debug_print("removed reedit target %d\n", msginfo->msgnum);
5239 static void compose_remove_draft(Compose *compose)
5242 MsgInfo *msginfo = compose->targetinfo;
5243 drafts = account_get_special_folder(compose->account, F_DRAFT);
5245 if (procmsg_msg_exist(msginfo)) {
5246 folder_item_remove_msg(drafts, msginfo->msgnum);
5251 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5252 gboolean remove_reedit_target)
5254 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5257 static gboolean compose_warn_encryption(Compose *compose)
5259 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5260 AlertValue val = G_ALERTALTERNATE;
5262 if (warning == NULL)
5265 val = alertpanel_full(_("Encryption warning"), warning,
5266 GTK_STOCK_CANCEL, _("+C_ontinue"), NULL,
5267 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5268 if (val & G_ALERTDISABLE) {
5269 val &= ~G_ALERTDISABLE;
5270 if (val == G_ALERTALTERNATE)
5271 privacy_inhibit_encrypt_warning(compose->privacy_system,
5275 if (val == G_ALERTALTERNATE) {
5282 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
5283 gchar **msgpath, gboolean check_subject,
5284 gboolean remove_reedit_target)
5291 static gboolean lock = FALSE;
5292 PrefsAccount *mailac = NULL, *newsac = NULL;
5293 gboolean err = FALSE;
5295 debug_print("queueing message...\n");
5296 g_return_val_if_fail(compose->account != NULL, -1);
5300 if (compose_check_entries(compose, check_subject) == FALSE) {
5302 if (compose->batch) {
5303 gtk_widget_show_all(compose->window);
5308 if (!compose->to_list && !compose->newsgroup_list) {
5309 g_warning("can't get recipient list.");
5314 if (compose->to_list) {
5315 if (compose->account->protocol != A_NNTP)
5316 mailac = compose->account;
5317 else if (cur_account && cur_account->protocol != A_NNTP)
5318 mailac = cur_account;
5319 else if (!(mailac = compose_current_mail_account())) {
5321 alertpanel_error(_("No account for sending mails available!"));
5326 if (compose->newsgroup_list) {
5327 if (compose->account->protocol == A_NNTP)
5328 newsac = compose->account;
5329 else if (!newsac->protocol != A_NNTP) {
5331 alertpanel_error(_("No account for posting news available!"));
5336 /* write queue header */
5337 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
5338 G_DIR_SEPARATOR, compose, (guint) rand());
5339 debug_print("queuing to %s\n", tmp);
5340 if ((fp = g_fopen(tmp, "wb")) == NULL) {
5341 FILE_OP_ERROR(tmp, "fopen");
5347 if (change_file_mode_rw(fp, tmp) < 0) {
5348 FILE_OP_ERROR(tmp, "chmod");
5349 g_warning("can't change file mode\n");
5352 /* queueing variables */
5353 err |= (fprintf(fp, "AF:\n") < 0);
5354 err |= (fprintf(fp, "NF:0\n") < 0);
5355 err |= (fprintf(fp, "PS:10\n") < 0);
5356 err |= (fprintf(fp, "SRH:1\n") < 0);
5357 err |= (fprintf(fp, "SFN:\n") < 0);
5358 err |= (fprintf(fp, "DSR:\n") < 0);
5360 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
5362 err |= (fprintf(fp, "MID:\n") < 0);
5363 err |= (fprintf(fp, "CFG:\n") < 0);
5364 err |= (fprintf(fp, "PT:0\n") < 0);
5365 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
5366 err |= (fprintf(fp, "RQ:\n") < 0);
5368 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
5370 err |= (fprintf(fp, "SSV:\n") < 0);
5372 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
5374 err |= (fprintf(fp, "NSV:\n") < 0);
5375 err |= (fprintf(fp, "SSH:\n") < 0);
5376 /* write recepient list */
5377 if (compose->to_list) {
5378 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
5379 for (cur = compose->to_list->next; cur != NULL;
5381 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
5382 err |= (fprintf(fp, "\n") < 0);
5384 /* write newsgroup list */
5385 if (compose->newsgroup_list) {
5386 err |= (fprintf(fp, "NG:") < 0);
5387 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
5388 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
5389 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
5390 err |= (fprintf(fp, "\n") < 0);
5392 /* Sylpheed account IDs */
5394 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
5396 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
5399 if (compose->privacy_system != NULL) {
5400 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
5401 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
5402 if (compose->use_encryption) {
5404 if (!compose_warn_encryption(compose)) {
5411 if (mailac && mailac->encrypt_to_self) {
5412 GSList *tmp_list = g_slist_copy(compose->to_list);
5413 tmp_list = g_slist_append(tmp_list, compose->account->address);
5414 encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
5415 g_slist_free(tmp_list);
5417 encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
5419 if (encdata != NULL) {
5420 if (strcmp(encdata, "_DONT_ENCRYPT_")) {
5421 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5422 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
5424 } /* else we finally dont want to encrypt */
5426 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5427 /* and if encdata was null, it means there's been a problem in
5439 /* Save copy folder */
5440 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
5441 gchar *savefolderid;
5443 savefolderid = gtk_editable_get_chars(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
5444 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
5445 g_free(savefolderid);
5447 /* Save copy folder */
5448 if (compose->return_receipt) {
5449 err |= (fprintf(fp, "RRCPT:1\n") < 0);
5451 /* Message-ID of message replying to */
5452 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
5455 folderid = folder_item_get_identifier(compose->replyinfo->folder);
5456 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
5459 /* Message-ID of message forwarding to */
5460 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
5463 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
5464 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
5468 /* end of headers */
5469 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
5471 if (compose->redirect_filename != NULL) {
5472 if (compose_redirect_write_to_file(compose, fp) < 0) {
5481 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
5486 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
5490 g_warning("failed to write queue message\n");
5497 if (fclose(fp) == EOF) {
5498 FILE_OP_ERROR(tmp, "fclose");
5505 if (item && *item) {
5508 queue = account_get_special_folder(compose->account, F_QUEUE);
5511 g_warning("can't find queue folder\n");
5517 folder_item_scan(queue);
5518 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
5519 g_warning("can't queue the message\n");
5526 if (msgpath == NULL) {
5532 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
5533 compose_remove_reedit_target(compose, FALSE);
5536 if ((msgnum != NULL) && (item != NULL)) {
5544 static void compose_add_attachments(Compose *compose, MimeInfo *parent)
5547 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
5549 struct stat statbuf;
5550 gchar *type, *subtype;
5551 GtkTreeModel *model;
5554 model = gtk_tree_view_get_model(tree_view);
5556 if (!gtk_tree_model_get_iter_first(model, &iter))
5559 gtk_tree_model_get(model, &iter,
5563 mimepart = procmime_mimeinfo_new();
5564 mimepart->content = MIMECONTENT_FILE;
5565 mimepart->data.filename = g_strdup(ainfo->file);
5566 mimepart->tmp = FALSE; /* or we destroy our attachment */
5567 mimepart->offset = 0;
5569 stat(ainfo->file, &statbuf);
5570 mimepart->length = statbuf.st_size;
5572 type = g_strdup(ainfo->content_type);
5574 if (!strchr(type, '/')) {
5576 type = g_strdup("application/octet-stream");
5579 subtype = strchr(type, '/') + 1;
5580 *(subtype - 1) = '\0';
5581 mimepart->type = procmime_get_media_type(type);
5582 mimepart->subtype = g_strdup(subtype);
5585 if (mimepart->type == MIMETYPE_MESSAGE &&
5586 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
5587 mimepart->disposition = DISPOSITIONTYPE_INLINE;
5590 g_hash_table_insert(mimepart->typeparameters,
5591 g_strdup("name"), g_strdup(ainfo->name));
5592 g_hash_table_insert(mimepart->dispositionparameters,
5593 g_strdup("filename"), g_strdup(ainfo->name));
5594 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
5598 if (compose->use_signing) {
5599 if (ainfo->encoding == ENC_7BIT)
5600 ainfo->encoding = ENC_QUOTED_PRINTABLE;
5601 else if (ainfo->encoding == ENC_8BIT)
5602 ainfo->encoding = ENC_BASE64;
5605 procmime_encode_content(mimepart, ainfo->encoding);
5607 g_node_append(parent->node, mimepart->node);
5608 } while (gtk_tree_model_iter_next(model, &iter));
5611 #define IS_IN_CUSTOM_HEADER(header) \
5612 (compose->account->add_customhdr && \
5613 custom_header_find(compose->account->customhdr_list, header) != NULL)
5615 static void compose_add_headerfield_from_headerlist(Compose *compose,
5617 const gchar *fieldname,
5618 const gchar *seperator)
5620 gchar *str, *fieldname_w_colon;
5621 gboolean add_field = FALSE;
5623 ComposeHeaderEntry *headerentry;
5624 const gchar *headerentryname;
5625 const gchar *trans_fieldname;
5628 if (IS_IN_CUSTOM_HEADER(fieldname))
5631 debug_print("Adding %s-fields\n", fieldname);
5633 fieldstr = g_string_sized_new(64);
5635 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
5636 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
5638 for (list = compose->header_list; list; list = list->next) {
5639 headerentry = ((ComposeHeaderEntry *)list->data);
5640 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_BIN(headerentry->combo)->child));
5642 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
5643 str = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
5645 if (str[0] != '\0') {
5647 g_string_append(fieldstr, seperator);
5648 g_string_append(fieldstr, str);
5657 buf = g_new0(gchar, fieldstr->len * 4 + 256);
5658 compose_convert_header
5659 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
5660 strlen(fieldname) + 2, TRUE);
5661 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
5665 g_free(fieldname_w_colon);
5666 g_string_free(fieldstr, TRUE);
5671 static gchar *compose_get_header(Compose *compose)
5673 gchar buf[BUFFSIZE];
5674 const gchar *entry_str;
5678 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
5680 gchar *from_name = NULL, *from_address = NULL;
5683 g_return_val_if_fail(compose->account != NULL, NULL);
5684 g_return_val_if_fail(compose->account->address != NULL, NULL);
5686 header = g_string_sized_new(64);
5689 get_rfc822_date(buf, sizeof(buf));
5690 g_string_append_printf(header, "Date: %s\n", buf);
5694 if (compose->account->name && *compose->account->name) {
5696 QUOTE_IF_REQUIRED(buf, compose->account->name);
5697 tmp = g_strdup_printf("%s <%s>",
5698 buf, compose->account->address);
5700 tmp = g_strdup_printf("%s",
5701 compose->account->address);
5703 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
5704 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
5706 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
5707 from_address = g_strdup(compose->account->address);
5709 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5710 /* extract name and address */
5711 if (strstr(spec, " <") && strstr(spec, ">")) {
5712 from_address = g_strdup(strrchr(spec, '<')+1);
5713 *(strrchr(from_address, '>')) = '\0';
5714 from_name = g_strdup(spec);
5715 *(strrchr(from_name, '<')) = '\0';
5718 from_address = g_strdup(spec);
5725 if (from_name && *from_name) {
5726 compose_convert_header
5727 (compose, buf, sizeof(buf), from_name,
5728 strlen("From: "), TRUE);
5729 QUOTE_IF_REQUIRED(name, buf);
5731 g_string_append_printf(header, "From: %s <%s>\n",
5732 name, from_address);
5734 g_string_append_printf(header, "From: %s\n", from_address);
5737 g_free(from_address);
5740 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
5743 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
5746 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
5749 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
5752 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
5754 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
5757 compose_convert_header(compose, buf, sizeof(buf), str,
5758 strlen("Subject: "), FALSE);
5759 g_string_append_printf(header, "Subject: %s\n", buf);
5765 if (compose->account->set_domain && compose->account->domain) {
5766 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
5767 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
5768 g_snprintf(buf, sizeof(buf), "%s",
5769 strchr(compose->account->address, '@') ?
5770 strchr(compose->account->address, '@')+1 :
5771 compose->account->address);
5773 g_snprintf(buf, sizeof(buf), "%s", "");
5776 if (compose->account->gen_msgid) {
5777 generate_msgid(buf, sizeof(buf));
5778 g_string_append_printf(header, "Message-ID: <%s>\n", buf);
5779 compose->msgid = g_strdup(buf);
5781 compose->msgid = NULL;
5784 if (compose->remove_references == FALSE) {
5786 if (compose->inreplyto && compose->to_list)
5787 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
5790 if (compose->references)
5791 g_string_append_printf(header, "References: %s\n", compose->references);
5795 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
5798 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
5801 if (compose->account->organization &&
5802 strlen(compose->account->organization) &&
5803 !IS_IN_CUSTOM_HEADER("Organization")) {
5804 compose_convert_header(compose, buf, sizeof(buf),
5805 compose->account->organization,
5806 strlen("Organization: "), FALSE);
5807 g_string_append_printf(header, "Organization: %s\n", buf);
5810 /* Program version and system info */
5811 if (g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
5812 !compose->newsgroup_list) {
5813 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
5815 gtk_major_version, gtk_minor_version, gtk_micro_version,
5818 if (g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
5819 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
5821 gtk_major_version, gtk_minor_version, gtk_micro_version,
5825 /* custom headers */
5826 if (compose->account->add_customhdr) {
5829 for (cur = compose->account->customhdr_list; cur != NULL;
5831 CustomHeader *chdr = (CustomHeader *)cur->data;
5833 if (custom_header_is_allowed(chdr->name)) {
5834 compose_convert_header
5835 (compose, buf, sizeof(buf),
5836 chdr->value ? chdr->value : "",
5837 strlen(chdr->name) + 2, FALSE);
5838 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
5844 switch (compose->priority) {
5845 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
5846 "X-Priority: 1 (Highest)\n");
5848 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
5849 "X-Priority: 2 (High)\n");
5851 case PRIORITY_NORMAL: break;
5852 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
5853 "X-Priority: 4 (Low)\n");
5855 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
5856 "X-Priority: 5 (Lowest)\n");
5858 default: debug_print("compose: priority unknown : %d\n",
5862 /* Request Return Receipt */
5863 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To")) {
5864 if (compose->return_receipt) {
5865 if (compose->account->name
5866 && *compose->account->name) {
5867 compose_convert_header(compose, buf, sizeof(buf),
5868 compose->account->name,
5869 strlen("Disposition-Notification-To: "),
5871 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, compose->account->address);
5873 g_string_append_printf(header, "Disposition-Notification-To: %s\n", compose->account->address);
5877 /* get special headers */
5878 for (list = compose->header_list; list; list = list->next) {
5879 ComposeHeaderEntry *headerentry;
5882 gchar *headername_wcolon;
5883 const gchar *headername_trans;
5886 gboolean standard_header = FALSE;
5888 headerentry = ((ComposeHeaderEntry *)list->data);
5890 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(GTK_BIN(headerentry->combo)->child)));
5891 if (strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
5896 if (!strstr(tmp, ":")) {
5897 headername_wcolon = g_strconcat(tmp, ":", NULL);
5898 headername = g_strdup(tmp);
5900 headername_wcolon = g_strdup(tmp);
5901 headername = g_strdup(strtok(tmp, ":"));
5905 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5906 Xstrdup_a(headervalue, entry_str, return NULL);
5907 subst_char(headervalue, '\r', ' ');
5908 subst_char(headervalue, '\n', ' ');
5909 string = std_headers;
5910 while (*string != NULL) {
5911 headername_trans = prefs_common_translated_header_name(*string);
5912 if (!strcmp(headername_trans, headername_wcolon))
5913 standard_header = TRUE;
5916 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
5917 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
5920 g_free(headername_wcolon);
5924 g_string_free(header, FALSE);
5929 #undef IS_IN_CUSTOM_HEADER
5931 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
5932 gint header_len, gboolean addr_field)
5934 gchar *tmpstr = NULL;
5935 const gchar *out_codeset = NULL;
5937 g_return_if_fail(src != NULL);
5938 g_return_if_fail(dest != NULL);
5940 if (len < 1) return;
5942 tmpstr = g_strdup(src);
5944 subst_char(tmpstr, '\n', ' ');
5945 subst_char(tmpstr, '\r', ' ');
5948 if (!g_utf8_validate(tmpstr, -1, NULL)) {
5949 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
5950 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
5955 codeconv_set_strict(TRUE);
5956 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
5957 conv_get_charset_str(compose->out_encoding));
5958 codeconv_set_strict(FALSE);
5960 if (!dest || *dest == '\0') {
5961 gchar *test_conv_global_out = NULL;
5962 gchar *test_conv_reply = NULL;
5964 /* automatic mode. be automatic. */
5965 codeconv_set_strict(TRUE);
5967 out_codeset = conv_get_outgoing_charset_str();
5969 debug_print("trying to convert to %s\n", out_codeset);
5970 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
5973 if (!test_conv_global_out && compose->orig_charset
5974 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5975 out_codeset = compose->orig_charset;
5976 debug_print("failure; trying to convert to %s\n", out_codeset);
5977 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
5980 if (!test_conv_global_out && !test_conv_reply) {
5982 out_codeset = CS_INTERNAL;
5983 debug_print("finally using %s\n", out_codeset);
5985 g_free(test_conv_global_out);
5986 g_free(test_conv_reply);
5987 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
5989 codeconv_set_strict(FALSE);
5994 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
5998 g_return_if_fail(user_data != NULL);
6000 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6001 g_strstrip(address);
6002 if (*address != '\0') {
6003 gchar *name = procheader_get_fromname(address);
6004 extract_address(address);
6005 addressbook_add_contact(name, address, NULL, NULL);
6010 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
6012 GtkWidget *menuitem;
6015 g_return_if_fail(menu != NULL);
6016 g_return_if_fail(GTK_IS_MENU_SHELL(menu));
6018 menuitem = gtk_separator_menu_item_new();
6019 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6020 gtk_widget_show(menuitem);
6022 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
6023 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6025 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
6026 g_strstrip(address);
6027 if (*address == '\0') {
6028 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
6031 g_signal_connect(G_OBJECT(menuitem), "activate",
6032 G_CALLBACK(compose_add_to_addressbook_cb), entry);
6033 gtk_widget_show(menuitem);
6036 static void compose_create_header_entry(Compose *compose)
6038 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6043 const gchar *header = NULL;
6044 ComposeHeaderEntry *headerentry;
6045 gboolean standard_header = FALSE;
6047 headerentry = g_new0(ComposeHeaderEntry, 1);
6050 combo = gtk_combo_box_entry_new_text();
6052 while(*string != NULL) {
6053 gtk_combo_box_append_text(GTK_COMBO_BOX(combo),
6054 (gchar*)prefs_common_translated_header_name(*string));
6057 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
6058 g_signal_connect(G_OBJECT(GTK_BIN(combo)->child), "grab_focus",
6059 G_CALLBACK(compose_grab_focus_cb), compose);
6060 gtk_widget_show(combo);
6061 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
6062 compose->header_nextrow, compose->header_nextrow+1,
6063 GTK_SHRINK, GTK_FILL, 0, 0);
6064 if (compose->header_last) {
6065 const gchar *last_header_entry = gtk_entry_get_text(
6066 GTK_ENTRY(GTK_BIN(compose->header_last->combo)->child));
6068 while (*string != NULL) {
6069 if (!strcmp(*string, last_header_entry))
6070 standard_header = TRUE;
6073 if (standard_header)
6074 header = gtk_entry_get_text(GTK_ENTRY(GTK_BIN(compose->header_last->combo)->child));
6076 if (!compose->header_last || !standard_header) {
6077 switch(compose->account->protocol) {
6079 header = prefs_common_translated_header_name("Newsgroups:");
6082 header = prefs_common_translated_header_name("To:");
6087 gtk_entry_set_text(GTK_ENTRY(GTK_BIN(combo)->child), header);
6089 g_signal_connect_after(G_OBJECT(GTK_BIN(combo)->child), "grab_focus",
6090 G_CALLBACK(compose_grab_focus_cb), compose);
6093 entry = gtk_entry_new();
6094 gtk_widget_show(entry);
6095 gtk_tooltips_set_tip(compose->tooltips, entry,
6096 _("Use <tab> to autocomplete from addressbook"), NULL);
6097 gtk_table_attach(GTK_TABLE(compose->header_table), entry, 1, 2,
6098 compose->header_nextrow, compose->header_nextrow+1,
6099 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
6101 g_signal_connect(G_OBJECT(entry), "key-press-event",
6102 G_CALLBACK(compose_headerentry_key_press_event_cb),
6104 g_signal_connect(G_OBJECT(entry), "changed",
6105 G_CALLBACK(compose_headerentry_changed_cb),
6107 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
6108 G_CALLBACK(compose_grab_focus_cb), compose);
6111 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6112 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6113 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6114 g_signal_connect(G_OBJECT(entry), "drag_data_received",
6115 G_CALLBACK(compose_header_drag_received_cb),
6117 g_signal_connect(G_OBJECT(entry), "drag-drop",
6118 G_CALLBACK(compose_drag_drop),
6120 g_signal_connect(G_OBJECT(entry), "populate-popup",
6121 G_CALLBACK(compose_entry_popup_extend),
6124 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
6126 headerentry->compose = compose;
6127 headerentry->combo = combo;
6128 headerentry->entry = entry;
6129 headerentry->headernum = compose->header_nextrow;
6131 compose->header_nextrow++;
6132 compose->header_last = headerentry;
6133 compose->header_list =
6134 g_slist_append(compose->header_list,
6138 static void compose_add_header_entry(Compose *compose, const gchar *header, gchar *text)
6140 ComposeHeaderEntry *last_header;
6142 last_header = compose->header_last;
6144 gtk_entry_set_text(GTK_ENTRY(GTK_BIN(last_header->combo)->child), header);
6145 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
6148 static void compose_remove_header_entries(Compose *compose)
6151 for (list = compose->header_list; list; list = list->next) {
6152 ComposeHeaderEntry *headerentry =
6153 (ComposeHeaderEntry *)list->data;
6154 gtk_widget_destroy(headerentry->combo);
6155 gtk_widget_destroy(headerentry->entry);
6156 g_free(headerentry);
6158 compose->header_last = NULL;
6159 g_slist_free(compose->header_list);
6160 compose->header_list = NULL;
6161 compose->header_nextrow = 1;
6162 compose_create_header_entry(compose);
6165 static GtkWidget *compose_create_header(Compose *compose)
6167 GtkWidget *from_optmenu_hbox;
6168 GtkWidget *header_scrolledwin;
6169 GtkWidget *header_table;
6173 /* header labels and entries */
6174 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
6175 gtk_widget_show(header_scrolledwin);
6176 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
6178 header_table = gtk_table_new(2, 2, FALSE);
6179 gtk_widget_show(header_table);
6180 gtk_container_set_border_width(GTK_CONTAINER(header_table), BORDER_WIDTH);
6181 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
6182 gtk_viewport_set_shadow_type(GTK_VIEWPORT(GTK_BIN(header_scrolledwin)->child), GTK_SHADOW_NONE);
6185 /* option menu for selecting accounts */
6186 from_optmenu_hbox = compose_account_option_menu_create(compose);
6187 gtk_table_attach(GTK_TABLE(header_table), from_optmenu_hbox,
6188 0, 2, count, count + 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
6191 compose->header_table = header_table;
6192 compose->header_list = NULL;
6193 compose->header_nextrow = count;
6195 compose_create_header_entry(compose);
6197 compose->table = NULL;
6199 return header_scrolledwin ;
6202 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
6204 Compose *compose = (Compose *)data;
6205 GdkEventButton event;
6208 event.time = gtk_get_current_event_time();
6210 return attach_button_pressed(compose->attach_clist, &event, compose);
6213 static GtkWidget *compose_create_attach(Compose *compose)
6215 GtkWidget *attach_scrwin;
6216 GtkWidget *attach_clist;
6218 GtkListStore *store;
6219 GtkCellRenderer *renderer;
6220 GtkTreeViewColumn *column;
6221 GtkTreeSelection *selection;
6223 /* attachment list */
6224 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
6225 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
6226 GTK_POLICY_AUTOMATIC,
6227 GTK_POLICY_AUTOMATIC);
6228 gtk_widget_set_size_request(attach_scrwin, -1, 80);
6230 store = gtk_list_store_new(N_ATTACH_COLS,
6235 G_TYPE_AUTO_POINTER,
6237 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
6238 (GTK_TREE_MODEL(store)));
6239 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
6240 g_object_unref(store);
6242 renderer = gtk_cell_renderer_text_new();
6243 column = gtk_tree_view_column_new_with_attributes
6244 (_("Mime type"), renderer, "text",
6245 COL_MIMETYPE, NULL);
6246 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6248 renderer = gtk_cell_renderer_text_new();
6249 column = gtk_tree_view_column_new_with_attributes
6250 (_("Size"), renderer, "text",
6252 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6254 renderer = gtk_cell_renderer_text_new();
6255 column = gtk_tree_view_column_new_with_attributes
6256 (_("Name"), renderer, "text",
6258 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6260 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
6261 prefs_common.use_stripes_everywhere);
6262 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
6263 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
6265 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
6266 G_CALLBACK(attach_selected), compose);
6267 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
6268 G_CALLBACK(attach_button_pressed), compose);
6270 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
6271 G_CALLBACK(popup_attach_button_pressed), compose);
6273 gtk_widget_tap_and_hold_setup(GTK_WIDGET(attach_clist), NULL, NULL,
6274 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
6275 g_signal_connect(G_OBJECT(attach_clist), "tap-and-hold",
6276 G_CALLBACK(popup_attach_button_pressed), compose);
6278 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
6279 G_CALLBACK(attach_key_pressed), compose);
6282 gtk_drag_dest_set(attach_clist,
6283 GTK_DEST_DEFAULT_ALL, compose_mime_types,
6284 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6285 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6286 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
6287 G_CALLBACK(compose_attach_drag_received_cb),
6289 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
6290 G_CALLBACK(compose_drag_drop),
6293 compose->attach_scrwin = attach_scrwin;
6294 compose->attach_clist = attach_clist;
6296 return attach_scrwin;
6299 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
6300 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
6302 static GtkWidget *compose_create_others(Compose *compose)
6305 GtkWidget *savemsg_checkbtn;
6306 GtkWidget *savemsg_entry;
6307 GtkWidget *savemsg_select;
6310 gchar *folderidentifier;
6312 /* Table for settings */
6313 table = gtk_table_new(3, 1, FALSE);
6314 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
6315 gtk_widget_show(table);
6316 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
6319 /* Save Message to folder */
6320 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
6321 gtk_widget_show(savemsg_checkbtn);
6322 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6323 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6324 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
6326 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
6327 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
6329 savemsg_entry = gtk_entry_new();
6330 gtk_widget_show(savemsg_entry);
6331 gtk_table_attach_defaults(GTK_TABLE(table), savemsg_entry, 1, 2, rowcount, rowcount + 1);
6332 gtk_editable_set_editable(GTK_EDITABLE(savemsg_entry), prefs_common.savemsg);
6333 g_signal_connect_after(G_OBJECT(savemsg_entry), "grab_focus",
6334 G_CALLBACK(compose_grab_focus_cb), compose);
6335 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6336 folderidentifier = folder_item_get_identifier(account_get_special_folder
6337 (compose->account, F_OUTBOX));
6338 gtk_entry_set_text(GTK_ENTRY(savemsg_entry), folderidentifier);
6339 g_free(folderidentifier);
6342 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
6343 gtk_widget_show(savemsg_select);
6344 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6345 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
6346 G_CALLBACK(compose_savemsg_select_cb),
6351 compose->savemsg_checkbtn = savemsg_checkbtn;
6352 compose->savemsg_entry = savemsg_entry;
6357 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
6359 gtk_editable_set_editable(GTK_EDITABLE(compose->savemsg_entry),
6360 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
6363 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
6368 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL);
6371 path = folder_item_get_identifier(dest);
6373 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), path);
6377 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
6378 GdkAtom clip, GtkTextIter *insert_place);
6381 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
6385 GtkTextBuffer *buffer;
6387 if (event->button == 3) {
6389 GtkTextIter sel_start, sel_end;
6390 gboolean stuff_selected;
6392 /* move the cursor to allow GtkAspell to check the word
6393 * under the mouse */
6394 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6395 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6397 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6400 stuff_selected = gtk_text_buffer_get_selection_bounds(
6401 GTK_TEXT_VIEW(text)->buffer,
6402 &sel_start, &sel_end);
6404 gtk_text_buffer_place_cursor (GTK_TEXT_VIEW(text)->buffer, &iter);
6405 /* reselect stuff */
6407 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
6408 gtk_text_buffer_select_range(GTK_TEXT_VIEW(text)->buffer,
6409 &sel_start, &sel_end);
6411 return FALSE; /* pass the event so that the right-click goes through */
6414 if (event->button == 2) {
6419 /* get the middle-click position to paste at the correct place */
6420 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6421 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6423 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6426 entry_paste_clipboard(compose, text,
6427 prefs_common.linewrap_pastes,
6428 GDK_SELECTION_PRIMARY, &iter);
6436 static void compose_spell_menu_changed(void *data)
6438 Compose *compose = (Compose *)data;
6440 GtkWidget *menuitem;
6441 GtkWidget *parent_item;
6442 GtkMenu *menu = GTK_MENU(gtk_menu_new());
6443 GtkItemFactory *ifactory = gtk_item_factory_from_widget(compose->menubar);
6446 if (compose->gtkaspell == NULL)
6449 parent_item = gtk_item_factory_get_item(ifactory,
6450 "/Spelling/Options");
6452 /* setting the submenu removes /Spelling/Options from the factory
6453 * so we need to save it */
6455 if (parent_item == NULL) {
6456 parent_item = compose->aspell_options_menu;
6457 gtk_menu_item_remove_submenu(GTK_MENU_ITEM(parent_item));
6459 compose->aspell_options_menu = parent_item;
6461 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
6463 spell_menu = g_slist_reverse(spell_menu);
6464 for (items = spell_menu;
6465 items; items = items->next) {
6466 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
6467 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
6468 gtk_widget_show(GTK_WIDGET(menuitem));
6470 g_slist_free(spell_menu);
6472 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
6477 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
6479 Compose *compose = (Compose *)data;
6480 GdkEventButton event;
6483 event.time = gtk_get_current_event_time();
6485 return text_clicked(compose->text, &event, compose);
6488 static gboolean compose_force_window_origin = TRUE;
6489 static Compose *compose_create(PrefsAccount *account,
6498 GtkWidget *handlebox;
6500 GtkWidget *notebook;
6502 GtkWidget *attach_hbox;
6503 GtkWidget *attach_lab1;
6504 GtkWidget *attach_lab2;
6509 GtkWidget *subject_hbox;
6510 GtkWidget *subject_frame;
6511 GtkWidget *subject_entry;
6515 GtkWidget *edit_vbox;
6516 GtkWidget *ruler_hbox;
6518 GtkWidget *scrolledwin;
6520 GtkTextBuffer *buffer;
6521 GtkClipboard *clipboard;
6523 UndoMain *undostruct;
6525 gchar *titles[N_ATTACH_COLS];
6526 guint n_menu_entries;
6527 GtkWidget *popupmenu;
6528 GtkItemFactory *popupfactory;
6529 GtkItemFactory *ifactory;
6530 GtkWidget *tmpl_menu;
6532 GtkWidget *menuitem;
6535 GtkAspell * gtkaspell = NULL;
6538 static GdkGeometry geometry;
6540 g_return_val_if_fail(account != NULL, NULL);
6542 debug_print("Creating compose window...\n");
6543 compose = g_new0(Compose, 1);
6545 titles[COL_MIMETYPE] = _("MIME type");
6546 titles[COL_SIZE] = _("Size");
6547 titles[COL_NAME] = _("Name");
6549 compose->batch = batch;
6550 compose->account = account;
6551 compose->folder = folder;
6553 compose->mutex = g_mutex_new();
6554 compose->set_cursor_pos = -1;
6556 compose->tooltips = gtk_tooltips_new();
6558 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
6560 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
6561 gtk_widget_set_size_request(window, -1, prefs_common.compose_height);
6563 if (!geometry.max_width) {
6564 geometry.max_width = gdk_screen_width();
6565 geometry.max_height = gdk_screen_height();
6568 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
6569 &geometry, GDK_HINT_MAX_SIZE);
6570 if (!geometry.min_width) {
6571 geometry.min_width = 600;
6572 geometry.min_height = 480;
6574 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
6575 &geometry, GDK_HINT_MIN_SIZE);
6578 if (compose_force_window_origin)
6579 gtk_widget_set_uposition(window, prefs_common.compose_x,
6580 prefs_common.compose_y);
6582 g_signal_connect(G_OBJECT(window), "delete_event",
6583 G_CALLBACK(compose_delete_cb), compose);
6584 MANAGE_WINDOW_SIGNALS_CONNECT(window);
6585 gtk_widget_realize(window);
6587 gtkut_widget_set_composer_icon(window);
6589 vbox = gtk_vbox_new(FALSE, 0);
6590 gtk_container_add(GTK_CONTAINER(window), vbox);
6592 n_menu_entries = sizeof(compose_entries) / sizeof(compose_entries[0]);
6593 menubar = menubar_create(window, compose_entries,
6594 n_menu_entries, "<Compose>", compose);
6595 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
6597 if (prefs_common.toolbar_detachable) {
6598 handlebox = gtk_handle_box_new();
6600 handlebox = gtk_hbox_new(FALSE, 0);
6602 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
6604 gtk_widget_realize(handlebox);
6606 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, window,
6609 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
6613 vbox2 = gtk_vbox_new(FALSE, 2);
6614 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
6615 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
6618 notebook = gtk_notebook_new();
6619 gtk_widget_set_size_request(notebook, -1, 130);
6620 gtk_widget_show(notebook);
6622 /* header labels and entries */
6623 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6624 compose_create_header(compose),
6625 gtk_label_new_with_mnemonic(_("Hea_der")));
6626 /* attachment list */
6627 attach_hbox = gtk_hbox_new(FALSE, 0);
6628 gtk_widget_show(attach_hbox);
6630 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
6631 gtk_widget_show(attach_lab1);
6632 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
6634 attach_lab2 = gtk_label_new("");
6635 gtk_widget_show(attach_lab2);
6636 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
6638 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6639 compose_create_attach(compose),
6642 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6643 compose_create_others(compose),
6644 gtk_label_new_with_mnemonic(_("Othe_rs")));
6647 subject_hbox = gtk_hbox_new(FALSE, 0);
6648 gtk_widget_show(subject_hbox);
6650 subject_frame = gtk_frame_new(NULL);
6651 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
6652 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
6653 gtk_widget_show(subject_frame);
6655 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
6656 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
6657 gtk_widget_show(subject);
6659 label = gtk_label_new(_("Subject:"));
6660 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
6661 gtk_widget_show(label);
6663 subject_entry = gtk_entry_new();
6664 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
6665 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
6666 G_CALLBACK(compose_grab_focus_cb), compose);
6667 gtk_widget_show(subject_entry);
6668 compose->subject_entry = subject_entry;
6669 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
6671 edit_vbox = gtk_vbox_new(FALSE, 0);
6673 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
6676 ruler_hbox = gtk_hbox_new(FALSE, 0);
6677 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
6679 ruler = gtk_shruler_new();
6680 gtk_ruler_set_range(GTK_RULER(ruler), 0.0, 100.0, 1.0, 100.0);
6681 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
6685 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
6686 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
6687 GTK_POLICY_AUTOMATIC,
6688 GTK_POLICY_AUTOMATIC);
6689 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
6691 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
6692 gtk_widget_set_size_request(scrolledwin, prefs_common.compose_width, -1);
6694 text = gtk_text_view_new();
6695 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
6696 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
6697 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
6698 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
6699 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
6701 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
6703 g_signal_connect_after(G_OBJECT(text), "size_allocate",
6704 G_CALLBACK(compose_edit_size_alloc),
6706 g_signal_connect(G_OBJECT(buffer), "changed",
6707 G_CALLBACK(compose_changed_cb), compose);
6708 g_signal_connect(G_OBJECT(text), "grab_focus",
6709 G_CALLBACK(compose_grab_focus_cb), compose);
6710 g_signal_connect(G_OBJECT(buffer), "insert_text",
6711 G_CALLBACK(text_inserted), compose);
6712 g_signal_connect(G_OBJECT(text), "button_press_event",
6713 G_CALLBACK(text_clicked), compose);
6715 g_signal_connect(G_OBJECT(text), "popup-menu",
6716 G_CALLBACK(compose_popup_menu), compose);
6718 gtk_widget_tap_and_hold_setup(GTK_WIDGET(text), NULL, NULL,
6719 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
6720 g_signal_connect(G_OBJECT(text), "tap-and-hold",
6721 G_CALLBACK(compose_popup_menu), compose);
6723 g_signal_connect(G_OBJECT(subject_entry), "changed",
6724 G_CALLBACK(compose_changed_cb), compose);
6727 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6728 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6729 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6730 g_signal_connect(G_OBJECT(text), "drag_data_received",
6731 G_CALLBACK(compose_insert_drag_received_cb),
6733 g_signal_connect(G_OBJECT(text), "drag-drop",
6734 G_CALLBACK(compose_drag_drop),
6736 gtk_widget_show_all(vbox);
6738 /* pane between attach clist and text */
6739 paned = gtk_vpaned_new();
6740 gtk_paned_set_gutter_size(GTK_PANED(paned), 12);
6741 gtk_container_add(GTK_CONTAINER(vbox2), paned);
6743 if( maemo_mainwindow_is_fullscreen(mainwindow_get_mainwindow()->window) )
6744 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 300 : 280);
6746 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 250 : 230);
6748 gtk_paned_add1(GTK_PANED(paned), notebook);
6749 gtk_paned_add2(GTK_PANED(paned), edit_vbox);
6750 gtk_widget_show_all(paned);
6753 if (prefs_common.textfont) {
6754 PangoFontDescription *font_desc;
6756 font_desc = pango_font_description_from_string
6757 (prefs_common.textfont);
6759 gtk_widget_modify_font(text, font_desc);
6760 pango_font_description_free(font_desc);
6764 n_entries = sizeof(compose_popup_entries) /
6765 sizeof(compose_popup_entries[0]);
6766 popupmenu = menu_create_items(compose_popup_entries, n_entries,
6767 "<Compose>", &popupfactory,
6770 ifactory = gtk_item_factory_from_widget(menubar);
6771 menu_set_sensitive(ifactory, "/Edit/Undo", FALSE);
6772 menu_set_sensitive(ifactory, "/Edit/Redo", FALSE);
6773 menu_set_sensitive(ifactory, "/Options/Remove references", FALSE);
6775 tmpl_menu = gtk_item_factory_get_item(ifactory, "/Tools/Template");
6777 undostruct = undo_init(text);
6778 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
6781 address_completion_start(window);
6783 compose->window = window;
6784 compose->vbox = vbox;
6785 compose->menubar = menubar;
6786 compose->handlebox = handlebox;
6788 compose->vbox2 = vbox2;
6790 compose->paned = paned;
6792 compose->attach_label = attach_lab2;
6794 compose->notebook = notebook;
6795 compose->edit_vbox = edit_vbox;
6796 compose->ruler_hbox = ruler_hbox;
6797 compose->ruler = ruler;
6798 compose->scrolledwin = scrolledwin;
6799 compose->text = text;
6801 compose->focused_editable = NULL;
6803 compose->popupmenu = popupmenu;
6804 compose->popupfactory = popupfactory;
6806 compose->tmpl_menu = tmpl_menu;
6808 compose->mode = mode;
6809 compose->rmode = mode;
6811 compose->targetinfo = NULL;
6812 compose->replyinfo = NULL;
6813 compose->fwdinfo = NULL;
6815 compose->replyto = NULL;
6817 compose->bcc = NULL;
6818 compose->followup_to = NULL;
6820 compose->ml_post = NULL;
6822 compose->inreplyto = NULL;
6823 compose->references = NULL;
6824 compose->msgid = NULL;
6825 compose->boundary = NULL;
6827 compose->autowrap = prefs_common.autowrap;
6829 compose->use_signing = FALSE;
6830 compose->use_encryption = FALSE;
6831 compose->privacy_system = NULL;
6833 compose->modified = FALSE;
6835 compose->return_receipt = FALSE;
6837 compose->to_list = NULL;
6838 compose->newsgroup_list = NULL;
6840 compose->undostruct = undostruct;
6842 compose->sig_str = NULL;
6844 compose->exteditor_file = NULL;
6845 compose->exteditor_pid = -1;
6846 compose->exteditor_tag = -1;
6847 compose->draft_timeout_tag = -2; /* inhibit auto-drafting while loading */
6850 menu_set_sensitive(ifactory, "/Spelling", FALSE);
6851 if (mode != COMPOSE_REDIRECT) {
6852 if (prefs_common.enable_aspell && prefs_common.dictionary &&
6853 strcmp(prefs_common.dictionary, "")) {
6854 gtkaspell = gtkaspell_new(prefs_common.aspell_path,
6855 prefs_common.dictionary,
6856 prefs_common.alt_dictionary,
6857 conv_get_locale_charset_str(),
6858 prefs_common.misspelled_col,
6859 prefs_common.check_while_typing,
6860 prefs_common.recheck_when_changing_dict,
6861 prefs_common.use_alternate,
6862 prefs_common.use_both_dicts,
6863 GTK_TEXT_VIEW(text),
6864 GTK_WINDOW(compose->window),
6865 compose_spell_menu_changed,
6868 alertpanel_error(_("Spell checker could not "
6870 gtkaspell_checkers_strerror());
6871 gtkaspell_checkers_reset_error();
6873 if (!gtkaspell_set_sug_mode(gtkaspell,
6874 prefs_common.aspell_sugmode)) {
6875 debug_print("Aspell: could not set "
6876 "suggestion mode %s\n",
6877 gtkaspell_checkers_strerror());
6878 gtkaspell_checkers_reset_error();
6881 menu_set_sensitive(ifactory, "/Spelling", TRUE);
6885 compose->gtkaspell = gtkaspell;
6886 compose_spell_menu_changed(compose);
6889 compose_select_account(compose, account, TRUE);
6891 menu_set_active(ifactory, "/Edit/Auto wrapping", prefs_common.autowrap);
6892 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
6893 compose_entry_append(compose, account->auto_cc, COMPOSE_CC);
6895 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
6896 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC);
6898 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
6899 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO);
6901 menu_set_sensitive(ifactory, "/Options/Reply mode", compose->mode == COMPOSE_REPLY);
6903 if (account->protocol != A_NNTP)
6904 gtk_entry_set_text(GTK_ENTRY(GTK_BIN(compose->header_last->combo)->child),
6905 prefs_common_translated_header_name("To:"));
6907 gtk_entry_set_text(GTK_ENTRY(GTK_BIN(compose->header_last->combo)->child),
6908 prefs_common_translated_header_name("Newsgroups:"));
6910 addressbook_set_target_compose(compose);
6912 if (mode != COMPOSE_REDIRECT)
6913 compose_set_template_menu(compose);
6915 menuitem = gtk_item_factory_get_item(ifactory, "/Tools/Template");
6916 menu_set_sensitive(ifactory, "/Tools/Template", FALSE);
6919 compose_list = g_list_append(compose_list, compose);
6921 if (!prefs_common.show_ruler)
6922 gtk_widget_hide(ruler_hbox);
6924 menuitem = gtk_item_factory_get_item(ifactory, "/Tools/Show ruler");
6925 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
6926 prefs_common.show_ruler);
6929 compose->priority = PRIORITY_NORMAL;
6930 compose_update_priority_menu_item(compose);
6932 compose_set_out_encoding(compose);
6935 compose_update_actions_menu(compose);
6937 /* Privacy Systems menu */
6938 compose_update_privacy_systems_menu(compose);
6940 activate_privacy_system(compose, account, TRUE);
6941 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
6943 gtk_widget_realize(window);
6945 gtk_widget_show(window);
6947 maemo_window_full_screen_if_needed(GTK_WINDOW(window));
6948 maemo_connect_key_press_to_mainwindow(GTK_WINDOW(window));
6955 static GtkWidget *compose_account_option_menu_create(Compose *compose)
6960 GtkWidget *optmenubox;
6963 GtkWidget *from_name = NULL;
6965 gint num = 0, def_menu = 0;
6967 accounts = account_get_list();
6968 g_return_val_if_fail(accounts != NULL, NULL);
6970 optmenubox = gtk_event_box_new();
6971 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
6972 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
6974 hbox = gtk_hbox_new(FALSE, 6);
6975 from_name = gtk_entry_new();
6977 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
6978 G_CALLBACK(compose_grab_focus_cb), compose);
6980 for (; accounts != NULL; accounts = accounts->next, num++) {
6981 PrefsAccount *ac = (PrefsAccount *)accounts->data;
6982 gchar *name, *from = NULL;
6984 if (ac == compose->account) def_menu = num;
6986 name = g_markup_printf_escaped(_("From: <i>%s</i>"),
6989 if (ac == compose->account) {
6990 if (ac->name && *ac->name) {
6992 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
6993 from = g_strdup_printf("%s <%s>",
6995 gtk_entry_set_text(GTK_ENTRY(from_name), from);
6997 from = g_strdup_printf("%s",
6999 gtk_entry_set_text(GTK_ENTRY(from_name), from);
7002 COMBOBOX_ADD(menu, name, ac->account_id);
7007 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
7009 g_signal_connect(G_OBJECT(optmenu), "changed",
7010 G_CALLBACK(account_activated),
7012 g_signal_connect(G_OBJECT(from_name), "populate-popup",
7013 G_CALLBACK(compose_entry_popup_extend),
7016 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
7017 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
7019 gtk_tooltips_set_tip(compose->tooltips, optmenubox,
7020 _("Account to use for this email"), NULL);
7021 gtk_tooltips_set_tip(compose->tooltips, from_name,
7022 _("Sender address to be used"), NULL);
7024 compose->from_name = from_name;
7029 static void compose_set_priority_cb(gpointer data,
7033 Compose *compose = (Compose *) data;
7034 compose->priority = action;
7037 static void compose_reply_change_mode(gpointer data,
7041 Compose *compose = (Compose *) data;
7042 gboolean was_modified = compose->modified;
7044 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
7046 g_return_if_fail(compose->replyinfo != NULL);
7048 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
7050 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
7052 if (action == COMPOSE_REPLY_TO_ALL)
7054 if (action == COMPOSE_REPLY_TO_SENDER)
7056 if (action == COMPOSE_REPLY_TO_LIST)
7059 compose_remove_header_entries(compose);
7060 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
7061 if (compose->account->set_autocc && compose->account->auto_cc)
7062 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC);
7064 if (compose->account->set_autobcc && compose->account->auto_bcc)
7065 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC);
7067 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
7068 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO);
7069 compose_show_first_last_header(compose, TRUE);
7070 compose->modified = was_modified;
7071 compose_set_title(compose);
7074 static void compose_update_priority_menu_item(Compose * compose)
7076 GtkItemFactory *ifactory;
7077 GtkWidget *menuitem = NULL;
7079 ifactory = gtk_item_factory_from_widget(compose->menubar);
7081 switch (compose->priority) {
7082 case PRIORITY_HIGHEST:
7083 menuitem = gtk_item_factory_get_item
7084 (ifactory, "/Options/Priority/Highest");
7087 menuitem = gtk_item_factory_get_item
7088 (ifactory, "/Options/Priority/High");
7090 case PRIORITY_NORMAL:
7091 menuitem = gtk_item_factory_get_item
7092 (ifactory, "/Options/Priority/Normal");
7095 menuitem = gtk_item_factory_get_item
7096 (ifactory, "/Options/Priority/Low");
7098 case PRIORITY_LOWEST:
7099 menuitem = gtk_item_factory_get_item
7100 (ifactory, "/Options/Priority/Lowest");
7103 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
7106 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
7108 Compose *compose = (Compose *) data;
7110 GtkItemFactory *ifactory;
7111 gboolean can_sign = FALSE, can_encrypt = FALSE;
7113 g_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
7115 if (!GTK_CHECK_MENU_ITEM(widget)->active)
7118 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
7119 g_free(compose->privacy_system);
7120 compose->privacy_system = NULL;
7121 if (systemid != NULL) {
7122 compose->privacy_system = g_strdup(systemid);
7124 can_sign = privacy_system_can_sign(systemid);
7125 can_encrypt = privacy_system_can_encrypt(systemid);
7128 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
7130 ifactory = gtk_item_factory_from_widget(compose->menubar);
7131 menu_set_sensitive(ifactory, "/Options/Sign", can_sign);
7132 menu_set_sensitive(ifactory, "/Options/Encrypt", can_encrypt);
7135 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
7137 static gchar *branch_path = "/Options/Privacy System";
7138 GtkItemFactory *ifactory;
7139 GtkWidget *menuitem = NULL;
7141 gboolean can_sign = FALSE, can_encrypt = FALSE;
7142 gboolean found = FALSE;
7144 ifactory = gtk_item_factory_from_widget(compose->menubar);
7146 if (compose->privacy_system != NULL) {
7149 menuitem = gtk_item_factory_get_widget(ifactory, branch_path);
7150 g_return_if_fail(menuitem != NULL);
7152 amenu = GTK_MENU_SHELL(menuitem)->children;
7154 while (amenu != NULL) {
7155 GList *alist = amenu->next;
7157 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
7158 if (systemid != NULL) {
7159 if (strcmp(systemid, compose->privacy_system) == 0) {
7160 menuitem = GTK_WIDGET(amenu->data);
7162 can_sign = privacy_system_can_sign(systemid);
7163 can_encrypt = privacy_system_can_encrypt(systemid);
7167 } else if (strlen(compose->privacy_system) == 0) {
7168 menuitem = GTK_WIDGET(amenu->data);
7171 can_encrypt = FALSE;
7178 if (menuitem != NULL)
7179 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
7181 if (warn && !found && strlen(compose->privacy_system)) {
7182 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
7183 "will not be able to sign or encrypt this message."),
7184 compose->privacy_system);
7188 menu_set_sensitive(ifactory, "/Options/Sign", can_sign);
7189 menu_set_sensitive(ifactory, "/Options/Encrypt", can_encrypt);
7192 static void compose_set_out_encoding(Compose *compose)
7194 GtkItemFactoryEntry *entry;
7195 GtkItemFactory *ifactory;
7196 CharSet out_encoding;
7197 gchar *path, *p, *q;
7200 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
7201 ifactory = gtk_item_factory_from_widget(compose->menubar);
7203 for (entry = compose_entries; entry->callback != compose_address_cb;
7205 if (entry->callback == compose_set_encoding_cb &&
7206 (CharSet)entry->callback_action == out_encoding) {
7207 p = q = path = g_strdup(entry->path);
7219 item = gtk_item_factory_get_item(ifactory, path);
7220 gtk_widget_activate(item);
7227 static void compose_set_template_menu(Compose *compose)
7229 GSList *tmpl_list, *cur;
7233 tmpl_list = template_get_config();
7235 menu = gtk_menu_new();
7237 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
7238 Template *tmpl = (Template *)cur->data;
7240 item = gtk_menu_item_new_with_label(tmpl->name);
7241 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
7242 g_signal_connect(G_OBJECT(item), "activate",
7243 G_CALLBACK(compose_template_activate_cb),
7245 g_object_set_data(G_OBJECT(item), "template", tmpl);
7246 gtk_widget_show(item);
7249 gtk_widget_show(menu);
7250 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
7253 void compose_update_actions_menu(Compose *compose)
7255 GtkItemFactory *ifactory;
7257 ifactory = gtk_item_factory_from_widget(compose->menubar);
7258 action_update_compose_menu(ifactory, "/Tools/Actions", compose);
7261 static void compose_update_privacy_systems_menu(Compose *compose)
7263 static gchar *branch_path = "/Options/Privacy System";
7264 GtkItemFactory *ifactory;
7265 GtkWidget *menuitem;
7266 GSList *systems, *cur;
7269 GtkWidget *system_none;
7272 ifactory = gtk_item_factory_from_widget(compose->menubar);
7274 /* remove old entries */
7275 menuitem = gtk_item_factory_get_widget(ifactory, branch_path);
7276 g_return_if_fail(menuitem != NULL);
7278 amenu = GTK_MENU_SHELL(menuitem)->children->next;
7279 while (amenu != NULL) {
7280 GList *alist = amenu->next;
7281 gtk_widget_destroy(GTK_WIDGET(amenu->data));
7285 system_none = gtk_item_factory_get_widget(ifactory,
7286 "/Options/Privacy System/None");
7288 g_signal_connect(G_OBJECT(system_none), "activate",
7289 G_CALLBACK(compose_set_privacy_system_cb), compose);
7291 systems = privacy_get_system_ids();
7292 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
7293 gchar *systemid = cur->data;
7295 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
7296 widget = gtk_radio_menu_item_new_with_label(group,
7297 privacy_system_get_name(systemid));
7298 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
7299 g_strdup(systemid), g_free);
7300 g_signal_connect(G_OBJECT(widget), "activate",
7301 G_CALLBACK(compose_set_privacy_system_cb), compose);
7303 gtk_menu_append(GTK_MENU(system_none->parent), widget);
7304 gtk_widget_show(widget);
7307 g_slist_free(systems);
7310 void compose_reflect_prefs_all(void)
7315 for (cur = compose_list; cur != NULL; cur = cur->next) {
7316 compose = (Compose *)cur->data;
7317 compose_set_template_menu(compose);
7321 void compose_reflect_prefs_pixmap_theme(void)
7326 for (cur = compose_list; cur != NULL; cur = cur->next) {
7327 compose = (Compose *)cur->data;
7328 toolbar_update(TOOLBAR_COMPOSE, compose);
7332 static const gchar *compose_quote_char_from_context(Compose *compose)
7334 const gchar *qmark = NULL;
7336 g_return_val_if_fail(compose != NULL, NULL);
7338 switch (compose->mode) {
7339 /* use forward-specific quote char */
7340 case COMPOSE_FORWARD:
7341 case COMPOSE_FORWARD_AS_ATTACH:
7342 case COMPOSE_FORWARD_INLINE:
7343 if (compose->folder && compose->folder->prefs &&
7344 compose->folder->prefs->forward_with_format)
7345 qmark = compose->folder->prefs->forward_quotemark;
7346 else if (compose->account->forward_with_format)
7347 qmark = compose->account->forward_quotemark;
7349 qmark = prefs_common.fw_quotemark;
7352 /* use reply-specific quote char in all other modes */
7354 if (compose->folder && compose->folder->prefs &&
7355 compose->folder->prefs->reply_with_format)
7356 qmark = compose->folder->prefs->reply_quotemark;
7357 else if (compose->account->reply_with_format)
7358 qmark = compose->account->reply_quotemark;
7360 qmark = prefs_common.quotemark;
7364 if (qmark == NULL || *qmark == '\0')
7370 static void compose_template_apply(Compose *compose, Template *tmpl,
7374 GtkTextBuffer *buffer;
7378 gchar *parsed_str = NULL;
7379 gint cursor_pos = 0;
7380 const gchar *err_msg = _("Template body format error at line %d.");
7383 /* process the body */
7385 text = GTK_TEXT_VIEW(compose->text);
7386 buffer = gtk_text_view_get_buffer(text);
7389 qmark = compose_quote_char_from_context(compose);
7391 if (compose->replyinfo != NULL) {
7394 gtk_text_buffer_set_text(buffer, "", -1);
7395 mark = gtk_text_buffer_get_insert(buffer);
7396 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7398 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
7399 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
7401 } else if (compose->fwdinfo != NULL) {
7404 gtk_text_buffer_set_text(buffer, "", -1);
7405 mark = gtk_text_buffer_get_insert(buffer);
7406 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7408 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
7409 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
7412 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
7414 GtkTextIter start, end;
7417 gtk_text_buffer_get_start_iter(buffer, &start);
7418 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
7419 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
7421 /* clear the buffer now */
7423 gtk_text_buffer_set_text(buffer, "", -1);
7425 parsed_str = compose_quote_fmt(compose, dummyinfo,
7426 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
7427 procmsg_msginfo_free( dummyinfo );
7433 gtk_text_buffer_set_text(buffer, "", -1);
7434 mark = gtk_text_buffer_get_insert(buffer);
7435 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7438 if (replace && parsed_str && compose->account->auto_sig)
7439 compose_insert_sig(compose, FALSE);
7441 if (replace && parsed_str) {
7442 gtk_text_buffer_get_start_iter(buffer, &iter);
7443 gtk_text_buffer_place_cursor(buffer, &iter);
7447 cursor_pos = quote_fmt_get_cursor_pos();
7448 compose->set_cursor_pos = cursor_pos;
7449 if (cursor_pos == -1)
7451 gtk_text_buffer_get_start_iter(buffer, &iter);
7452 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
7453 gtk_text_buffer_place_cursor(buffer, &iter);
7456 /* process the other fields */
7458 compose_template_apply_fields(compose, tmpl);
7459 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
7460 quote_fmt_reset_vartable();
7461 compose_changed_cb(NULL, compose);
7464 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
7466 MsgInfo* dummyinfo = NULL;
7467 MsgInfo *msginfo = NULL;
7470 if (compose->replyinfo != NULL)
7471 msginfo = compose->replyinfo;
7472 else if (compose->fwdinfo != NULL)
7473 msginfo = compose->fwdinfo;
7475 dummyinfo = compose_msginfo_new_from_compose(compose);
7476 msginfo = dummyinfo;
7479 if (tmpl->to && *tmpl->to != '\0') {
7481 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7482 compose->gtkaspell);
7484 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7486 quote_fmt_scan_string(tmpl->to);
7489 buf = quote_fmt_get_buffer();
7491 alertpanel_error(_("Template To format error."));
7493 compose_entry_append(compose, buf, COMPOSE_TO);
7497 if (tmpl->cc && *tmpl->cc != '\0') {
7499 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7500 compose->gtkaspell);
7502 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7504 quote_fmt_scan_string(tmpl->cc);
7507 buf = quote_fmt_get_buffer();
7509 alertpanel_error(_("Template Cc format error."));
7511 compose_entry_append(compose, buf, COMPOSE_CC);
7515 if (tmpl->bcc && *tmpl->bcc != '\0') {
7517 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7518 compose->gtkaspell);
7520 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7522 quote_fmt_scan_string(tmpl->bcc);
7525 buf = quote_fmt_get_buffer();
7527 alertpanel_error(_("Template Bcc format error."));
7529 compose_entry_append(compose, buf, COMPOSE_BCC);
7533 /* process the subject */
7534 if (tmpl->subject && *tmpl->subject != '\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->subject);
7544 buf = quote_fmt_get_buffer();
7546 alertpanel_error(_("Template subject format error."));
7548 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
7552 procmsg_msginfo_free( dummyinfo );
7555 static void compose_destroy(Compose *compose)
7557 GtkTextBuffer *buffer;
7558 GtkClipboard *clipboard;
7560 compose_list = g_list_remove(compose_list, compose);
7562 if (compose->updating) {
7563 debug_print("danger, not destroying anything now\n");
7564 compose->deferred_destroy = TRUE;
7567 /* NOTE: address_completion_end() does nothing with the window
7568 * however this may change. */
7569 address_completion_end(compose->window);
7571 slist_free_strings(compose->to_list);
7572 g_slist_free(compose->to_list);
7573 slist_free_strings(compose->newsgroup_list);
7574 g_slist_free(compose->newsgroup_list);
7575 slist_free_strings(compose->header_list);
7576 g_slist_free(compose->header_list);
7578 procmsg_msginfo_free(compose->targetinfo);
7579 procmsg_msginfo_free(compose->replyinfo);
7580 procmsg_msginfo_free(compose->fwdinfo);
7582 g_free(compose->replyto);
7583 g_free(compose->cc);
7584 g_free(compose->bcc);
7585 g_free(compose->newsgroups);
7586 g_free(compose->followup_to);
7588 g_free(compose->ml_post);
7590 g_free(compose->inreplyto);
7591 g_free(compose->references);
7592 g_free(compose->msgid);
7593 g_free(compose->boundary);
7595 g_free(compose->redirect_filename);
7596 if (compose->undostruct)
7597 undo_destroy(compose->undostruct);
7599 g_free(compose->sig_str);
7601 g_free(compose->exteditor_file);
7603 g_free(compose->orig_charset);
7605 g_free(compose->privacy_system);
7607 if (addressbook_get_target_compose() == compose)
7608 addressbook_set_target_compose(NULL);
7611 if (compose->gtkaspell) {
7612 gtkaspell_delete(compose->gtkaspell);
7613 compose->gtkaspell = NULL;
7617 prefs_common.compose_width = compose->scrolledwin->allocation.width;
7618 prefs_common.compose_height = compose->window->allocation.height;
7620 if (!gtk_widget_get_parent(compose->paned))
7621 gtk_widget_destroy(compose->paned);
7622 gtk_widget_destroy(compose->popupmenu);
7624 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
7625 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7626 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
7628 gtk_widget_destroy(compose->window);
7629 toolbar_destroy(compose->toolbar);
7630 g_free(compose->toolbar);
7631 g_mutex_free(compose->mutex);
7635 static void compose_attach_info_free(AttachInfo *ainfo)
7637 g_free(ainfo->file);
7638 g_free(ainfo->content_type);
7639 g_free(ainfo->name);
7643 static void compose_attach_update_label(Compose *compose)
7648 GtkTreeModel *model;
7653 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
7654 if(!gtk_tree_model_get_iter_first(model, &iter)) {
7655 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
7659 while(gtk_tree_model_iter_next(model, &iter))
7662 text = g_strdup_printf("(%d)", i);
7663 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
7667 static void compose_attach_remove_selected(Compose *compose)
7669 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
7670 GtkTreeSelection *selection;
7672 GtkTreeModel *model;
7674 selection = gtk_tree_view_get_selection(tree_view);
7675 sel = gtk_tree_selection_get_selected_rows(selection, &model);
7680 for (cur = sel; cur != NULL; cur = cur->next) {
7681 GtkTreePath *path = cur->data;
7682 GtkTreeRowReference *ref = gtk_tree_row_reference_new
7685 gtk_tree_path_free(path);
7688 for (cur = sel; cur != NULL; cur = cur->next) {
7689 GtkTreeRowReference *ref = cur->data;
7690 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
7693 if (gtk_tree_model_get_iter(model, &iter, path))
7694 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
7696 gtk_tree_path_free(path);
7697 gtk_tree_row_reference_free(ref);
7701 compose_attach_update_label(compose);
7704 static struct _AttachProperty
7707 GtkWidget *mimetype_entry;
7708 GtkWidget *encoding_optmenu;
7709 GtkWidget *path_entry;
7710 GtkWidget *filename_entry;
7712 GtkWidget *cancel_btn;
7715 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
7717 gtk_tree_path_free((GtkTreePath *)ptr);
7720 static void compose_attach_property(Compose *compose)
7722 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
7724 GtkComboBox *optmenu;
7725 GtkTreeSelection *selection;
7727 GtkTreeModel *model;
7730 static gboolean cancelled;
7732 /* only if one selected */
7733 selection = gtk_tree_view_get_selection(tree_view);
7734 if (gtk_tree_selection_count_selected_rows(selection) != 1)
7737 sel = gtk_tree_selection_get_selected_rows(selection, &model);
7741 path = (GtkTreePath *) sel->data;
7742 gtk_tree_model_get_iter(model, &iter, path);
7743 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
7746 g_list_foreach(sel, gtk_tree_path_free_, NULL);
7752 if (!attach_prop.window)
7753 compose_attach_property_create(&cancelled);
7754 gtk_widget_grab_focus(attach_prop.ok_btn);
7755 gtk_widget_show(attach_prop.window);
7756 manage_window_set_transient(GTK_WINDOW(attach_prop.window));
7758 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
7759 if (ainfo->encoding == ENC_UNKNOWN)
7760 combobox_select_by_data(optmenu, ENC_BASE64);
7762 combobox_select_by_data(optmenu, ainfo->encoding);
7764 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
7765 ainfo->content_type ? ainfo->content_type : "");
7766 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
7767 ainfo->file ? ainfo->file : "");
7768 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
7769 ainfo->name ? ainfo->name : "");
7772 const gchar *entry_text;
7774 gchar *cnttype = NULL;
7781 gtk_widget_hide(attach_prop.window);
7786 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
7787 if (*entry_text != '\0') {
7790 text = g_strstrip(g_strdup(entry_text));
7791 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
7792 cnttype = g_strdup(text);
7795 alertpanel_error(_("Invalid MIME type."));
7801 ainfo->encoding = combobox_get_active_data(optmenu);
7803 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
7804 if (*entry_text != '\0') {
7805 if (is_file_exist(entry_text) &&
7806 (size = get_file_size(entry_text)) > 0)
7807 file = g_strdup(entry_text);
7810 (_("File doesn't exist or is empty."));
7816 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
7817 if (*entry_text != '\0') {
7818 g_free(ainfo->name);
7819 ainfo->name = g_strdup(entry_text);
7823 g_free(ainfo->content_type);
7824 ainfo->content_type = cnttype;
7827 g_free(ainfo->file);
7833 /* update tree store */
7834 text = to_human_readable(ainfo->size);
7835 gtk_tree_model_get_iter(model, &iter, path);
7836 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
7837 COL_MIMETYPE, ainfo->content_type,
7839 COL_NAME, ainfo->name,
7845 gtk_tree_path_free(path);
7848 #define SET_LABEL_AND_ENTRY(str, entry, top) \
7850 label = gtk_label_new(str); \
7851 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
7852 GTK_FILL, 0, 0, 0); \
7853 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
7855 entry = gtk_entry_new(); \
7856 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
7857 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
7860 static void compose_attach_property_create(gboolean *cancelled)
7866 GtkWidget *mimetype_entry;
7869 GtkListStore *optmenu_menu;
7870 GtkWidget *path_entry;
7871 GtkWidget *filename_entry;
7874 GtkWidget *cancel_btn;
7875 GList *mime_type_list, *strlist;
7878 debug_print("Creating attach_property window...\n");
7880 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
7881 gtk_widget_set_size_request(window, 480, -1);
7882 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
7883 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
7884 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
7885 gtk_window_set_modal(GTK_WINDOW(window), TRUE);
7886 g_signal_connect(G_OBJECT(window), "delete_event",
7887 G_CALLBACK(attach_property_delete_event),
7889 g_signal_connect(G_OBJECT(window), "key_press_event",
7890 G_CALLBACK(attach_property_key_pressed),
7893 vbox = gtk_vbox_new(FALSE, 8);
7894 gtk_container_add(GTK_CONTAINER(window), vbox);
7896 table = gtk_table_new(4, 2, FALSE);
7897 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
7898 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
7899 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
7901 label = gtk_label_new(_("MIME type"));
7902 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
7904 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
7905 mimetype_entry = gtk_combo_box_entry_new_text();
7906 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
7907 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
7909 /* stuff with list */
7910 mime_type_list = procmime_get_mime_type_list();
7912 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
7913 MimeType *type = (MimeType *) mime_type_list->data;
7916 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
7918 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
7921 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
7922 (GCompareFunc)strcmp2);
7925 for (mime_type_list = strlist; mime_type_list != NULL;
7926 mime_type_list = mime_type_list->next) {
7927 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
7928 g_free(mime_type_list->data);
7930 g_list_free(strlist);
7931 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
7932 mimetype_entry = GTK_BIN(mimetype_entry)->child;
7934 label = gtk_label_new(_("Encoding"));
7935 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
7937 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
7939 hbox = gtk_hbox_new(FALSE, 0);
7940 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
7941 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
7943 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
7944 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
7946 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
7947 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
7948 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
7949 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
7950 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
7952 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
7954 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
7955 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
7957 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
7958 &ok_btn, GTK_STOCK_OK,
7960 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
7961 gtk_widget_grab_default(ok_btn);
7963 g_signal_connect(G_OBJECT(ok_btn), "clicked",
7964 G_CALLBACK(attach_property_ok),
7966 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
7967 G_CALLBACK(attach_property_cancel),
7970 gtk_widget_show_all(vbox);
7972 attach_prop.window = window;
7973 attach_prop.mimetype_entry = mimetype_entry;
7974 attach_prop.encoding_optmenu = optmenu;
7975 attach_prop.path_entry = path_entry;
7976 attach_prop.filename_entry = filename_entry;
7977 attach_prop.ok_btn = ok_btn;
7978 attach_prop.cancel_btn = cancel_btn;
7981 #undef SET_LABEL_AND_ENTRY
7983 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
7989 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
7995 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
7996 gboolean *cancelled)
8004 static gboolean attach_property_key_pressed(GtkWidget *widget,
8006 gboolean *cancelled)
8008 if (event && event->keyval == GDK_Escape) {
8015 static void compose_exec_ext_editor(Compose *compose)
8022 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
8023 G_DIR_SEPARATOR, compose);
8025 if (pipe(pipe_fds) < 0) {
8031 if ((pid = fork()) < 0) {
8038 /* close the write side of the pipe */
8041 compose->exteditor_file = g_strdup(tmp);
8042 compose->exteditor_pid = pid;
8044 compose_set_ext_editor_sensitive(compose, FALSE);
8046 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
8047 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
8051 } else { /* process-monitoring process */
8057 /* close the read side of the pipe */
8060 if (compose_write_body_to_file(compose, tmp) < 0) {
8061 fd_write_all(pipe_fds[1], "2\n", 2);
8065 pid_ed = compose_exec_ext_editor_real(tmp);
8067 fd_write_all(pipe_fds[1], "1\n", 2);
8071 /* wait until editor is terminated */
8072 waitpid(pid_ed, NULL, 0);
8074 fd_write_all(pipe_fds[1], "0\n", 2);
8081 #endif /* G_OS_UNIX */
8085 static gint compose_exec_ext_editor_real(const gchar *file)
8092 g_return_val_if_fail(file != NULL, -1);
8094 if ((pid = fork()) < 0) {
8099 if (pid != 0) return pid;
8101 /* grandchild process */
8103 if (setpgid(0, getppid()))
8106 if (prefs_common_get_ext_editor_cmd() &&
8107 (p = strchr(prefs_common_get_ext_editor_cmd(), '%')) &&
8108 *(p + 1) == 's' && !strchr(p + 2, '%')) {
8109 g_snprintf(buf, sizeof(buf), prefs_common_get_ext_editor_cmd(), file);
8111 if (prefs_common_get_ext_editor_cmd())
8112 g_warning("External editor command line is invalid: '%s'\n",
8113 prefs_common_get_ext_editor_cmd());
8114 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
8117 cmdline = strsplit_with_quote(buf, " ", 1024);
8118 execvp(cmdline[0], cmdline);
8121 g_strfreev(cmdline);
8126 static gboolean compose_ext_editor_kill(Compose *compose)
8128 pid_t pgid = compose->exteditor_pid * -1;
8131 ret = kill(pgid, 0);
8133 if (ret == 0 || (ret == -1 && EPERM == errno)) {
8137 msg = g_strdup_printf
8138 (_("The external editor is still working.\n"
8139 "Force terminating the process?\n"
8140 "process group id: %d"), -pgid);
8141 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
8142 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
8146 if (val == G_ALERTALTERNATE) {
8147 g_source_remove(compose->exteditor_tag);
8148 g_io_channel_shutdown(compose->exteditor_ch,
8150 g_io_channel_unref(compose->exteditor_ch);
8152 if (kill(pgid, SIGTERM) < 0) perror("kill");
8153 waitpid(compose->exteditor_pid, NULL, 0);
8155 g_warning("Terminated process group id: %d", -pgid);
8156 g_warning("Temporary file: %s",
8157 compose->exteditor_file);
8159 compose_set_ext_editor_sensitive(compose, TRUE);
8161 g_free(compose->exteditor_file);
8162 compose->exteditor_file = NULL;
8163 compose->exteditor_pid = -1;
8164 compose->exteditor_ch = NULL;
8165 compose->exteditor_tag = -1;
8173 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
8177 Compose *compose = (Compose *)data;
8180 debug_print(_("Compose: input from monitoring process\n"));
8182 g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL);
8184 g_io_channel_shutdown(source, FALSE, NULL);
8185 g_io_channel_unref(source);
8187 waitpid(compose->exteditor_pid, NULL, 0);
8189 if (buf[0] == '0') { /* success */
8190 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
8191 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
8193 gtk_text_buffer_set_text(buffer, "", -1);
8194 compose_insert_file(compose, compose->exteditor_file);
8195 compose_changed_cb(NULL, compose);
8197 if (g_unlink(compose->exteditor_file) < 0)
8198 FILE_OP_ERROR(compose->exteditor_file, "unlink");
8199 } else if (buf[0] == '1') { /* failed */
8200 g_warning("Couldn't exec external editor\n");
8201 if (g_unlink(compose->exteditor_file) < 0)
8202 FILE_OP_ERROR(compose->exteditor_file, "unlink");
8203 } else if (buf[0] == '2') {
8204 g_warning("Couldn't write to file\n");
8205 } else if (buf[0] == '3') {
8206 g_warning("Pipe read failed\n");
8209 compose_set_ext_editor_sensitive(compose, TRUE);
8211 g_free(compose->exteditor_file);
8212 compose->exteditor_file = NULL;
8213 compose->exteditor_pid = -1;
8214 compose->exteditor_ch = NULL;
8215 compose->exteditor_tag = -1;
8220 static void compose_set_ext_editor_sensitive(Compose *compose,
8223 GtkItemFactory *ifactory;
8225 ifactory = gtk_item_factory_from_widget(compose->menubar);
8227 menu_set_sensitive(ifactory, "/Message/Send", sensitive);
8228 menu_set_sensitive(ifactory, "/Message/Send later", sensitive);
8229 menu_set_sensitive(ifactory, "/Message/Insert file", sensitive);
8230 menu_set_sensitive(ifactory, "/Message/Insert signature", sensitive);
8231 menu_set_sensitive(ifactory, "/Edit/Wrap current paragraph", sensitive);
8232 menu_set_sensitive(ifactory, "/Edit/Wrap all long lines", sensitive);
8233 menu_set_sensitive(ifactory, "/Edit/Edit with external editor",
8236 gtk_widget_set_sensitive(compose->text, sensitive);
8237 if (compose->toolbar->send_btn)
8238 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
8239 if (compose->toolbar->sendl_btn)
8240 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
8241 if (compose->toolbar->draft_btn)
8242 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
8243 if (compose->toolbar->insert_btn)
8244 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
8245 if (compose->toolbar->sig_btn)
8246 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
8247 if (compose->toolbar->exteditor_btn)
8248 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
8249 if (compose->toolbar->linewrap_current_btn)
8250 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
8251 if (compose->toolbar->linewrap_all_btn)
8252 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
8254 #endif /* G_OS_UNIX */
8257 * compose_undo_state_changed:
8259 * Change the sensivity of the menuentries undo and redo
8261 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
8262 gint redo_state, gpointer data)
8264 GtkWidget *widget = GTK_WIDGET(data);
8265 GtkItemFactory *ifactory;
8267 g_return_if_fail(widget != NULL);
8269 ifactory = gtk_item_factory_from_widget(widget);
8271 switch (undo_state) {
8272 case UNDO_STATE_TRUE:
8273 if (!undostruct->undo_state) {
8274 undostruct->undo_state = TRUE;
8275 menu_set_sensitive(ifactory, "/Edit/Undo", TRUE);
8278 case UNDO_STATE_FALSE:
8279 if (undostruct->undo_state) {
8280 undostruct->undo_state = FALSE;
8281 menu_set_sensitive(ifactory, "/Edit/Undo", FALSE);
8284 case UNDO_STATE_UNCHANGED:
8286 case UNDO_STATE_REFRESH:
8287 menu_set_sensitive(ifactory, "/Edit/Undo",
8288 undostruct->undo_state);
8291 g_warning("Undo state not recognized");
8295 switch (redo_state) {
8296 case UNDO_STATE_TRUE:
8297 if (!undostruct->redo_state) {
8298 undostruct->redo_state = TRUE;
8299 menu_set_sensitive(ifactory, "/Edit/Redo", TRUE);
8302 case UNDO_STATE_FALSE:
8303 if (undostruct->redo_state) {
8304 undostruct->redo_state = FALSE;
8305 menu_set_sensitive(ifactory, "/Edit/Redo", FALSE);
8308 case UNDO_STATE_UNCHANGED:
8310 case UNDO_STATE_REFRESH:
8311 menu_set_sensitive(ifactory, "/Edit/Redo",
8312 undostruct->redo_state);
8315 g_warning("Redo state not recognized");
8320 /* callback functions */
8322 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
8323 * includes "non-client" (windows-izm) in calculation, so this calculation
8324 * may not be accurate.
8326 static gboolean compose_edit_size_alloc(GtkEditable *widget,
8327 GtkAllocation *allocation,
8328 GtkSHRuler *shruler)
8330 if (prefs_common.show_ruler) {
8331 gint char_width = 0, char_height = 0;
8332 gint line_width_in_chars;
8334 gtkut_get_font_size(GTK_WIDGET(widget),
8335 &char_width, &char_height);
8336 line_width_in_chars =
8337 (allocation->width - allocation->x) / char_width;
8339 /* got the maximum */
8340 gtk_ruler_set_range(GTK_RULER(shruler),
8341 0.0, line_width_in_chars, 0,
8342 /*line_width_in_chars*/ char_width);
8348 static void account_activated(GtkComboBox *optmenu, gpointer data)
8350 Compose *compose = (Compose *)data;
8353 gchar *folderidentifier;
8354 gint account_id = 0;
8358 /* Get ID of active account in the combo box */
8359 menu = gtk_combo_box_get_model(optmenu);
8360 gtk_combo_box_get_active_iter(optmenu, &iter);
8361 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
8363 ac = account_find_from_id(account_id);
8364 g_return_if_fail(ac != NULL);
8366 if (ac != compose->account)
8367 compose_select_account(compose, ac, FALSE);
8369 /* Set message save folder */
8370 if (account_get_special_folder(compose->account, F_OUTBOX)) {
8371 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
8373 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
8374 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
8376 gtk_editable_delete_text(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
8377 if (account_get_special_folder(compose->account, F_OUTBOX)) {
8378 folderidentifier = folder_item_get_identifier(account_get_special_folder
8379 (compose->account, F_OUTBOX));
8380 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
8381 g_free(folderidentifier);
8385 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
8386 GtkTreeViewColumn *column, Compose *compose)
8388 compose_attach_property(compose);
8391 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
8394 Compose *compose = (Compose *)data;
8395 GtkTreeSelection *attach_selection;
8396 gint attach_nr_selected;
8397 GtkItemFactory *ifactory;
8399 if (!event) return FALSE;
8401 if (event->button == 3) {
8402 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
8403 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
8404 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
8406 if (attach_nr_selected > 0)
8408 menu_set_sensitive(ifactory, "/Remove", TRUE);
8409 menu_set_sensitive(ifactory, "/Properties...", TRUE);
8411 menu_set_sensitive(ifactory, "/Remove", FALSE);
8412 menu_set_sensitive(ifactory, "/Properties...", FALSE);
8415 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
8416 NULL, NULL, event->button, event->time);
8423 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
8426 Compose *compose = (Compose *)data;
8428 if (!event) return FALSE;
8430 switch (event->keyval) {
8432 compose_attach_remove_selected(compose);
8438 static void compose_allow_user_actions (Compose *compose, gboolean allow)
8440 GtkItemFactory *ifactory = gtk_item_factory_from_widget(compose->menubar);
8441 toolbar_comp_set_sensitive(compose, allow);
8442 menu_set_sensitive(ifactory, "/Message", allow);
8443 menu_set_sensitive(ifactory, "/Edit", allow);
8445 menu_set_sensitive(ifactory, "/Spelling", allow);
8447 menu_set_sensitive(ifactory, "/Options", allow);
8448 menu_set_sensitive(ifactory, "/Tools", allow);
8449 menu_set_sensitive(ifactory, "/Help", allow);
8451 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
8455 static void compose_send_cb(gpointer data, guint action, GtkWidget *widget)
8457 Compose *compose = (Compose *)data;
8459 if (prefs_common.work_offline &&
8460 !inc_offline_should_override(TRUE,
8461 _("Claws Mail needs network access in order "
8462 "to send this email.")))
8465 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
8466 g_source_remove(compose->draft_timeout_tag);
8467 compose->draft_timeout_tag = -1;
8470 compose_send(compose);
8473 static void compose_send_later_cb(gpointer data, guint action,
8476 Compose *compose = (Compose *)data;
8480 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
8484 compose_close(compose);
8485 } else if (val == -1) {
8486 alertpanel_error(_("Could not queue message."));
8487 } else if (val == -2) {
8488 alertpanel_error(_("Could not queue message:\n\n%s."), strerror(errno));
8489 } else if (val == -3) {
8490 if (privacy_peek_error())
8491 alertpanel_error(_("Could not queue message for sending:\n\n"
8492 "Signature failed: %s"), privacy_get_error());
8493 } else if (val == -4) {
8494 alertpanel_error(_("Could not queue message for sending:\n\n"
8495 "Charset conversion failed."));
8496 } else if (val == -5) {
8497 alertpanel_error(_("Could not queue message for sending:\n\n"
8498 "Couldn't get recipient encryption key."));
8499 } else if (val == -6) {
8502 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
8505 #define DRAFTED_AT_EXIT "drafted_at_exit"
8506 static void compose_register_draft(MsgInfo *info)
8508 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8509 DRAFTED_AT_EXIT, NULL);
8510 FILE *fp = fopen(filepath, "ab");
8513 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
8521 gboolean compose_draft (gpointer data, guint action)
8523 Compose *compose = (Compose *)data;
8527 MsgFlags flag = {0, 0};
8528 static gboolean lock = FALSE;
8529 MsgInfo *newmsginfo;
8531 gboolean target_locked = FALSE;
8532 gboolean err = FALSE;
8534 if (lock) return FALSE;
8536 if (compose->sending)
8539 draft = account_get_special_folder(compose->account, F_DRAFT);
8540 g_return_val_if_fail(draft != NULL, FALSE);
8542 if (!g_mutex_trylock(compose->mutex)) {
8543 /* we don't want to lock the mutex once it's available,
8544 * because as the only other part of compose.c locking
8545 * it is compose_close - which means once unlocked,
8546 * the compose struct will be freed */
8547 debug_print("couldn't lock mutex, probably sending\n");
8553 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
8554 G_DIR_SEPARATOR, compose);
8555 if ((fp = g_fopen(tmp, "wb")) == NULL) {
8556 FILE_OP_ERROR(tmp, "fopen");
8560 /* chmod for security */
8561 if (change_file_mode_rw(fp, tmp) < 0) {
8562 FILE_OP_ERROR(tmp, "chmod");
8563 g_warning("can't change file mode\n");
8566 /* Save draft infos */
8567 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
8568 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
8570 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
8571 gchar *savefolderid;
8573 savefolderid = gtk_editable_get_chars(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
8574 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
8575 g_free(savefolderid);
8577 if (compose->return_receipt) {
8578 err |= (fprintf(fp, "RRCPT:1\n") < 0);
8580 if (compose->privacy_system) {
8581 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
8582 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
8583 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
8586 /* Message-ID of message replying to */
8587 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
8590 folderid = folder_item_get_identifier(compose->replyinfo->folder);
8591 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
8594 /* Message-ID of message forwarding to */
8595 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
8598 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
8599 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
8603 /* end of headers */
8604 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
8611 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
8615 if (fclose(fp) == EOF) {
8619 if (compose->targetinfo) {
8620 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
8621 flag.perm_flags = target_locked?MSG_LOCKED:0;
8623 flag.tmp_flags = MSG_DRAFT;
8625 folder_item_scan(draft);
8626 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
8627 MsgInfo *tmpinfo = NULL;
8628 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
8629 if (compose->msgid) {
8630 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
8633 msgnum = tmpinfo->msgnum;
8634 procmsg_msginfo_free(tmpinfo);
8635 debug_print("got draft msgnum %d from scanning\n", msgnum);
8637 debug_print("didn't get draft msgnum after scanning\n");
8640 debug_print("got draft msgnum %d from adding\n", msgnum);
8646 if (action != COMPOSE_AUTO_SAVE) {
8647 if (action != COMPOSE_DRAFT_FOR_EXIT)
8648 alertpanel_error(_("Could not save draft."));
8651 gtkut_window_popup(compose->window);
8652 val = alertpanel_full(_("Could not save draft"),
8653 _("Could not save draft.\n"
8654 "Do you want to cancel exit or discard this email?"),
8655 _("_Cancel exit"), _("_Discard email"), NULL,
8656 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
8657 if (val == G_ALERTALTERNATE) {
8659 g_mutex_unlock(compose->mutex); /* must be done before closing */
8660 compose_close(compose);
8664 g_mutex_unlock(compose->mutex); /* must be done before closing */
8673 if (compose->mode == COMPOSE_REEDIT) {
8674 compose_remove_reedit_target(compose, TRUE);
8677 newmsginfo = folder_item_get_msginfo(draft, msgnum);
8680 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
8682 procmsg_msginfo_set_flags(newmsginfo, MSG_LOCKED, MSG_DRAFT);
8684 procmsg_msginfo_set_flags(newmsginfo, 0, MSG_DRAFT);
8685 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
8686 procmsg_msginfo_set_flags(newmsginfo, 0,
8687 MSG_HAS_ATTACHMENT);
8689 if (action == COMPOSE_DRAFT_FOR_EXIT) {
8690 compose_register_draft(newmsginfo);
8692 procmsg_msginfo_free(newmsginfo);
8695 folder_item_scan(draft);
8697 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
8699 g_mutex_unlock(compose->mutex); /* must be done before closing */
8700 compose_close(compose);
8706 path = folder_item_fetch_msg(draft, msgnum);
8708 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
8711 if (g_stat(path, &s) < 0) {
8712 FILE_OP_ERROR(path, "stat");
8718 procmsg_msginfo_free(compose->targetinfo);
8719 compose->targetinfo = procmsg_msginfo_new();
8720 compose->targetinfo->msgnum = msgnum;
8721 compose->targetinfo->size = s.st_size;
8722 compose->targetinfo->mtime = s.st_mtime;
8723 compose->targetinfo->folder = draft;
8725 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
8726 compose->mode = COMPOSE_REEDIT;
8728 if (action == COMPOSE_AUTO_SAVE) {
8729 compose->autosaved_draft = compose->targetinfo;
8731 compose->modified = FALSE;
8732 compose_set_title(compose);
8736 g_mutex_unlock(compose->mutex);
8740 void compose_clear_exit_drafts(void)
8742 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8743 DRAFTED_AT_EXIT, NULL);
8744 if (is_file_exist(filepath))
8750 void compose_reopen_exit_drafts(void)
8752 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8753 DRAFTED_AT_EXIT, NULL);
8754 FILE *fp = fopen(filepath, "rb");
8758 while (fgets(buf, sizeof(buf), fp)) {
8759 gchar **parts = g_strsplit(buf, "\t", 2);
8760 const gchar *folder = parts[0];
8761 int msgnum = parts[1] ? atoi(parts[1]):-1;
8763 if (folder && *folder && msgnum > -1) {
8764 FolderItem *item = folder_find_item_from_identifier(folder);
8765 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
8767 compose_reedit(info, FALSE);
8774 compose_clear_exit_drafts();
8777 static void compose_draft_cb(gpointer data, guint action, GtkWidget *widget)
8779 compose_draft(data, action);
8782 static void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
8784 if (compose && file_list) {
8787 for ( tmp = file_list; tmp; tmp = tmp->next) {
8788 gchar *file = (gchar *) tmp->data;
8789 gchar *utf8_filename = conv_filename_to_utf8(file);
8790 compose_attach_append(compose, file, utf8_filename, NULL);
8791 compose_changed_cb(NULL, compose);
8796 g_free(utf8_filename);
8801 static void compose_attach_cb(gpointer data, guint action, GtkWidget *widget)
8803 Compose *compose = (Compose *)data;
8806 if (compose->redirect_filename != NULL)
8809 file_list = filesel_select_multiple_files_open(_("Select file"));
8812 compose_attach_from_list(compose, file_list, TRUE);
8813 g_list_free(file_list);
8817 static void compose_insert_file_cb(gpointer data, guint action,
8820 Compose *compose = (Compose *)data;
8823 file_list = filesel_select_multiple_files_open(_("Select file"));
8828 for ( tmp = file_list; tmp; tmp = tmp->next) {
8829 gchar *file = (gchar *) tmp->data;
8830 gchar *filedup = g_strdup(file);
8831 gchar *shortfile = g_path_get_basename(filedup);
8832 ComposeInsertResult res;
8834 res = compose_insert_file(compose, file);
8835 if (res == COMPOSE_INSERT_READ_ERROR) {
8836 alertpanel_error(_("File '%s' could not be read."), shortfile);
8837 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
8838 alertpanel_error(_("File '%s' contained invalid characters\n"
8839 "for the current encoding, insertion may be incorrect."), shortfile);
8845 g_list_free(file_list);
8849 static void compose_insert_sig_cb(gpointer data, guint action,
8852 Compose *compose = (Compose *)data;
8854 compose_insert_sig(compose, FALSE);
8857 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
8861 Compose *compose = (Compose *)data;
8863 gtkut_widget_get_uposition(widget, &x, &y);
8864 prefs_common.compose_x = x;
8865 prefs_common.compose_y = y;
8867 if (compose->sending || compose->updating)
8869 compose_close_cb(compose, 0, NULL);
8873 void compose_close_toolbar(Compose *compose)
8875 compose_close_cb(compose, 0, NULL);
8878 static void compose_close_cb(gpointer data, guint action, GtkWidget *widget)
8880 Compose *compose = (Compose *)data;
8884 if (compose->exteditor_tag != -1) {
8885 if (!compose_ext_editor_kill(compose))
8890 if (compose->modified) {
8891 val = alertpanel(_("Discard message"),
8892 _("This message has been modified. Discard it?"),
8893 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
8896 case G_ALERTDEFAULT:
8897 if (prefs_common.autosave)
8898 compose_remove_draft(compose);
8900 case G_ALERTALTERNATE:
8901 compose_draft_cb(data, COMPOSE_QUIT_EDITING, NULL);
8908 compose_close(compose);
8911 static void compose_set_encoding_cb(gpointer data, guint action,
8914 Compose *compose = (Compose *)data;
8916 if (GTK_CHECK_MENU_ITEM(widget)->active)
8917 compose->out_encoding = (CharSet)action;
8920 static void compose_address_cb(gpointer data, guint action, GtkWidget *widget)
8922 Compose *compose = (Compose *)data;
8924 addressbook_open(compose);
8927 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
8929 Compose *compose = (Compose *)data;
8934 tmpl = g_object_get_data(G_OBJECT(widget), "template");
8935 g_return_if_fail(tmpl != NULL);
8937 msg = g_strdup_printf(_("Do you want to apply the template '%s' ?"),
8939 val = alertpanel(_("Apply template"), msg,
8940 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
8943 if (val == G_ALERTDEFAULT)
8944 compose_template_apply(compose, tmpl, TRUE);
8945 else if (val == G_ALERTALTERNATE)
8946 compose_template_apply(compose, tmpl, FALSE);
8949 static void compose_ext_editor_cb(gpointer data, guint action,
8952 Compose *compose = (Compose *)data;
8954 compose_exec_ext_editor(compose);
8957 static void compose_undo_cb(Compose *compose)
8959 gboolean prev_autowrap = compose->autowrap;
8961 compose->autowrap = FALSE;
8962 undo_undo(compose->undostruct);
8963 compose->autowrap = prev_autowrap;
8966 static void compose_redo_cb(Compose *compose)
8968 gboolean prev_autowrap = compose->autowrap;
8970 compose->autowrap = FALSE;
8971 undo_redo(compose->undostruct);
8972 compose->autowrap = prev_autowrap;
8975 static void entry_cut_clipboard(GtkWidget *entry)
8977 if (GTK_IS_EDITABLE(entry))
8978 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
8979 else if (GTK_IS_TEXT_VIEW(entry))
8980 gtk_text_buffer_cut_clipboard(
8981 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
8982 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
8986 static void entry_copy_clipboard(GtkWidget *entry)
8988 if (GTK_IS_EDITABLE(entry))
8989 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
8990 else if (GTK_IS_TEXT_VIEW(entry))
8991 gtk_text_buffer_copy_clipboard(
8992 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
8993 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
8996 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
8997 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
8999 if (GTK_IS_TEXT_VIEW(entry)) {
9000 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
9001 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
9002 GtkTextIter start_iter, end_iter;
9004 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
9006 if (contents == NULL)
9009 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
9011 /* we shouldn't delete the selection when middle-click-pasting, or we
9012 * can't mid-click-paste our own selection */
9013 if (clip != GDK_SELECTION_PRIMARY) {
9014 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
9017 if (insert_place == NULL) {
9018 /* if insert_place isn't specified, insert at the cursor.
9019 * used for Ctrl-V pasting */
9020 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
9021 start = gtk_text_iter_get_offset(&start_iter);
9022 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
9024 /* if insert_place is specified, paste here.
9025 * used for mid-click-pasting */
9026 start = gtk_text_iter_get_offset(insert_place);
9027 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
9031 /* paste unwrapped: mark the paste so it's not wrapped later */
9032 end = start + strlen(contents);
9033 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
9034 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
9035 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
9036 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
9037 /* rewrap paragraph now (after a mid-click-paste) */
9038 mark_start = gtk_text_buffer_get_insert(buffer);
9039 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
9040 gtk_text_iter_backward_char(&start_iter);
9041 compose_beautify_paragraph(compose, &start_iter, TRUE);
9043 } else if (GTK_IS_EDITABLE(entry))
9044 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
9048 static void entry_allsel(GtkWidget *entry)
9050 if (GTK_IS_EDITABLE(entry))
9051 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
9052 else if (GTK_IS_TEXT_VIEW(entry)) {
9053 GtkTextIter startiter, enditer;
9054 GtkTextBuffer *textbuf;
9056 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
9057 gtk_text_buffer_get_start_iter(textbuf, &startiter);
9058 gtk_text_buffer_get_end_iter(textbuf, &enditer);
9060 gtk_text_buffer_move_mark_by_name(textbuf,
9061 "selection_bound", &startiter);
9062 gtk_text_buffer_move_mark_by_name(textbuf,
9063 "insert", &enditer);
9067 static void compose_cut_cb(Compose *compose)
9069 if (compose->focused_editable
9071 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9074 entry_cut_clipboard(compose->focused_editable);
9077 static void compose_copy_cb(Compose *compose)
9079 if (compose->focused_editable
9081 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9084 entry_copy_clipboard(compose->focused_editable);
9087 static void compose_paste_cb(Compose *compose)
9090 GtkTextBuffer *buffer;
9092 if (compose->focused_editable &&
9093 GTK_WIDGET_HAS_FOCUS(compose->focused_editable))
9094 entry_paste_clipboard(compose, compose->focused_editable,
9095 prefs_common.linewrap_pastes,
9096 GDK_SELECTION_CLIPBOARD, NULL);
9100 static void compose_paste_as_quote_cb(Compose *compose)
9102 gint wrap_quote = prefs_common.linewrap_quote;
9103 if (compose->focused_editable
9105 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9108 /* let text_insert() (called directly or at a later time
9109 * after the gtk_editable_paste_clipboard) know that
9110 * text is to be inserted as a quotation. implemented
9111 * by using a simple refcount... */
9112 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
9113 G_OBJECT(compose->focused_editable),
9114 "paste_as_quotation"));
9115 g_object_set_data(G_OBJECT(compose->focused_editable),
9116 "paste_as_quotation",
9117 GINT_TO_POINTER(paste_as_quotation + 1));
9118 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
9119 entry_paste_clipboard(compose, compose->focused_editable,
9120 prefs_common.linewrap_pastes,
9121 GDK_SELECTION_CLIPBOARD, NULL);
9122 prefs_common.linewrap_quote = wrap_quote;
9126 static void compose_paste_no_wrap_cb(Compose *compose)
9129 GtkTextBuffer *buffer;
9131 if (compose->focused_editable
9133 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9136 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
9137 GDK_SELECTION_CLIPBOARD, NULL);
9141 static void compose_paste_wrap_cb(Compose *compose)
9144 GtkTextBuffer *buffer;
9146 if (compose->focused_editable
9148 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9151 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
9152 GDK_SELECTION_CLIPBOARD, NULL);
9156 static void compose_allsel_cb(Compose *compose)
9158 if (compose->focused_editable
9160 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9163 entry_allsel(compose->focused_editable);
9166 static void textview_move_beginning_of_line (GtkTextView *text)
9168 GtkTextBuffer *buffer;
9172 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9174 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9175 mark = gtk_text_buffer_get_insert(buffer);
9176 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9177 gtk_text_iter_set_line_offset(&ins, 0);
9178 gtk_text_buffer_place_cursor(buffer, &ins);
9181 static void textview_move_forward_character (GtkTextView *text)
9183 GtkTextBuffer *buffer;
9187 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9189 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9190 mark = gtk_text_buffer_get_insert(buffer);
9191 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9192 if (gtk_text_iter_forward_cursor_position(&ins))
9193 gtk_text_buffer_place_cursor(buffer, &ins);
9196 static void textview_move_backward_character (GtkTextView *text)
9198 GtkTextBuffer *buffer;
9202 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9204 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9205 mark = gtk_text_buffer_get_insert(buffer);
9206 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9207 if (gtk_text_iter_backward_cursor_position(&ins))
9208 gtk_text_buffer_place_cursor(buffer, &ins);
9211 static void textview_move_forward_word (GtkTextView *text)
9213 GtkTextBuffer *buffer;
9218 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9220 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9221 mark = gtk_text_buffer_get_insert(buffer);
9222 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9223 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
9224 if (gtk_text_iter_forward_word_ends(&ins, count)) {
9225 gtk_text_iter_backward_word_start(&ins);
9226 gtk_text_buffer_place_cursor(buffer, &ins);
9230 static void textview_move_backward_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_backward_word_starts(&ins, 1))
9244 gtk_text_buffer_place_cursor(buffer, &ins);
9247 static void textview_move_end_of_line (GtkTextView *text)
9249 GtkTextBuffer *buffer;
9253 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9255 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9256 mark = gtk_text_buffer_get_insert(buffer);
9257 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9258 if (gtk_text_iter_forward_to_line_end(&ins))
9259 gtk_text_buffer_place_cursor(buffer, &ins);
9262 static void textview_move_next_line (GtkTextView *text)
9264 GtkTextBuffer *buffer;
9269 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9271 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9272 mark = gtk_text_buffer_get_insert(buffer);
9273 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9274 offset = gtk_text_iter_get_line_offset(&ins);
9275 if (gtk_text_iter_forward_line(&ins)) {
9276 gtk_text_iter_set_line_offset(&ins, offset);
9277 gtk_text_buffer_place_cursor(buffer, &ins);
9281 static void textview_move_previous_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_backward_line(&ins)) {
9295 gtk_text_iter_set_line_offset(&ins, offset);
9296 gtk_text_buffer_place_cursor(buffer, &ins);
9300 static void textview_delete_forward_character (GtkTextView *text)
9302 GtkTextBuffer *buffer;
9304 GtkTextIter ins, end_iter;
9306 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9308 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9309 mark = gtk_text_buffer_get_insert(buffer);
9310 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9312 if (gtk_text_iter_forward_char(&end_iter)) {
9313 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9317 static void textview_delete_backward_character (GtkTextView *text)
9319 GtkTextBuffer *buffer;
9321 GtkTextIter ins, end_iter;
9323 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9325 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9326 mark = gtk_text_buffer_get_insert(buffer);
9327 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9329 if (gtk_text_iter_backward_char(&end_iter)) {
9330 gtk_text_buffer_delete(buffer, &end_iter, &ins);
9334 static void textview_delete_forward_word (GtkTextView *text)
9336 GtkTextBuffer *buffer;
9338 GtkTextIter ins, end_iter;
9340 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9342 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9343 mark = gtk_text_buffer_get_insert(buffer);
9344 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9346 if (gtk_text_iter_forward_word_end(&end_iter)) {
9347 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9351 static void textview_delete_backward_word (GtkTextView *text)
9353 GtkTextBuffer *buffer;
9355 GtkTextIter ins, end_iter;
9357 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9359 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9360 mark = gtk_text_buffer_get_insert(buffer);
9361 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9363 if (gtk_text_iter_backward_word_start(&end_iter)) {
9364 gtk_text_buffer_delete(buffer, &end_iter, &ins);
9368 static void textview_delete_line (GtkTextView *text)
9370 GtkTextBuffer *buffer;
9372 GtkTextIter ins, start_iter, end_iter;
9375 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9377 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9378 mark = gtk_text_buffer_get_insert(buffer);
9379 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9382 gtk_text_iter_set_line_offset(&start_iter, 0);
9385 if (gtk_text_iter_ends_line(&end_iter))
9386 found = gtk_text_iter_forward_char(&end_iter);
9388 found = gtk_text_iter_forward_to_line_end(&end_iter);
9391 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
9394 static void textview_delete_to_line_end (GtkTextView *text)
9396 GtkTextBuffer *buffer;
9398 GtkTextIter ins, end_iter;
9401 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9403 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9404 mark = gtk_text_buffer_get_insert(buffer);
9405 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9407 if (gtk_text_iter_ends_line(&end_iter))
9408 found = gtk_text_iter_forward_char(&end_iter);
9410 found = gtk_text_iter_forward_to_line_end(&end_iter);
9412 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9415 static void compose_advanced_action_cb(Compose *compose,
9416 ComposeCallAdvancedAction action)
9418 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9420 void (*do_action) (GtkTextView *text);
9421 } action_table[] = {
9422 {textview_move_beginning_of_line},
9423 {textview_move_forward_character},
9424 {textview_move_backward_character},
9425 {textview_move_forward_word},
9426 {textview_move_backward_word},
9427 {textview_move_end_of_line},
9428 {textview_move_next_line},
9429 {textview_move_previous_line},
9430 {textview_delete_forward_character},
9431 {textview_delete_backward_character},
9432 {textview_delete_forward_word},
9433 {textview_delete_backward_word},
9434 {textview_delete_line},
9435 {NULL}, /* gtk_stext_delete_line_n */
9436 {textview_delete_to_line_end}
9439 if (!GTK_WIDGET_HAS_FOCUS(text)) return;
9441 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
9442 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
9443 if (action_table[action].do_action)
9444 action_table[action].do_action(text);
9446 g_warning("Not implemented yet.");
9450 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
9454 if (GTK_IS_EDITABLE(widget)) {
9455 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
9456 gtk_editable_set_position(GTK_EDITABLE(widget),
9459 if (widget->parent && widget->parent->parent
9460 && widget->parent->parent->parent) {
9461 if (GTK_IS_SCROLLED_WINDOW(widget->parent->parent->parent)) {
9462 gint y = widget->allocation.y;
9463 gint height = widget->allocation.height;
9464 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
9465 (GTK_SCROLLED_WINDOW(widget->parent->parent->parent));
9467 if (y < (int)shown->value) {
9468 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown), y - 1);
9470 if (y + height > (int)shown->value + (int)shown->page_size) {
9471 if (y - height - 1 < (int)shown->upper - (int)shown->page_size) {
9472 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
9473 y + height - (int)shown->page_size - 1);
9475 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
9476 (int)shown->upper - (int)shown->page_size - 1);
9483 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
9484 compose->focused_editable = widget;
9487 if (GTK_IS_TEXT_VIEW(widget)
9488 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
9489 gtk_widget_ref(compose->notebook);
9490 gtk_widget_ref(compose->edit_vbox);
9491 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
9492 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
9493 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
9494 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
9495 gtk_widget_unref(compose->notebook);
9496 gtk_widget_unref(compose->edit_vbox);
9497 g_signal_handlers_block_by_func(G_OBJECT(widget),
9498 G_CALLBACK(compose_grab_focus_cb),
9500 gtk_widget_grab_focus(widget);
9501 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
9502 G_CALLBACK(compose_grab_focus_cb),
9504 } else if (!GTK_IS_TEXT_VIEW(widget)
9505 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
9506 gtk_widget_ref(compose->notebook);
9507 gtk_widget_ref(compose->edit_vbox);
9508 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
9509 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
9510 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
9511 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
9512 gtk_widget_unref(compose->notebook);
9513 gtk_widget_unref(compose->edit_vbox);
9514 g_signal_handlers_block_by_func(G_OBJECT(widget),
9515 G_CALLBACK(compose_grab_focus_cb),
9517 gtk_widget_grab_focus(widget);
9518 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
9519 G_CALLBACK(compose_grab_focus_cb),
9525 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
9527 compose->modified = TRUE;
9529 compose_set_title(compose);
9533 static void compose_wrap_cb(gpointer data, guint action, GtkWidget *widget)
9535 Compose *compose = (Compose *)data;
9538 compose_wrap_all_full(compose, TRUE);
9540 compose_beautify_paragraph(compose, NULL, TRUE);
9543 static void compose_find_cb(gpointer data, guint action, GtkWidget *widget)
9545 Compose *compose = (Compose *)data;
9547 message_search_compose(compose);
9550 static void compose_toggle_autowrap_cb(gpointer data, guint action,
9553 Compose *compose = (Compose *)data;
9554 compose->autowrap = GTK_CHECK_MENU_ITEM(widget)->active;
9555 if (compose->autowrap)
9556 compose_wrap_all_full(compose, TRUE);
9557 compose->autowrap = GTK_CHECK_MENU_ITEM(widget)->active;
9560 static void compose_toggle_sign_cb(gpointer data, guint action,
9563 Compose *compose = (Compose *)data;
9565 if (GTK_CHECK_MENU_ITEM(widget)->active)
9566 compose->use_signing = TRUE;
9568 compose->use_signing = FALSE;
9571 static void compose_toggle_encrypt_cb(gpointer data, guint action,
9574 Compose *compose = (Compose *)data;
9576 if (GTK_CHECK_MENU_ITEM(widget)->active)
9577 compose->use_encryption = TRUE;
9579 compose->use_encryption = FALSE;
9582 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
9584 g_free(compose->privacy_system);
9586 compose->privacy_system = g_strdup(account->default_privacy_system);
9587 compose_update_privacy_system_menu_item(compose, warn);
9590 static void compose_toggle_ruler_cb(gpointer data, guint action,
9593 Compose *compose = (Compose *)data;
9595 if (GTK_CHECK_MENU_ITEM(widget)->active) {
9596 gtk_widget_show(compose->ruler_hbox);
9597 prefs_common.show_ruler = TRUE;
9599 gtk_widget_hide(compose->ruler_hbox);
9600 gtk_widget_queue_resize(compose->edit_vbox);
9601 prefs_common.show_ruler = FALSE;
9605 static void compose_attach_drag_received_cb (GtkWidget *widget,
9606 GdkDragContext *context,
9609 GtkSelectionData *data,
9614 Compose *compose = (Compose *)user_data;
9617 if (gdk_atom_name(data->type) &&
9618 !strcmp(gdk_atom_name(data->type), "text/uri-list")
9619 && gtk_drag_get_source_widget(context) !=
9620 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
9621 list = uri_list_extract_filenames((const gchar *)data->data);
9622 for (tmp = list; tmp != NULL; tmp = tmp->next) {
9623 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
9624 compose_attach_append
9625 (compose, (const gchar *)tmp->data,
9626 utf8_filename, NULL);
9627 g_free(utf8_filename);
9629 if (list) compose_changed_cb(NULL, compose);
9630 list_free_strings(list);
9632 } else if (gtk_drag_get_source_widget(context)
9633 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
9634 /* comes from our summaryview */
9635 SummaryView * summaryview = NULL;
9636 GSList * list = NULL, *cur = NULL;
9638 if (mainwindow_get_mainwindow())
9639 summaryview = mainwindow_get_mainwindow()->summaryview;
9642 list = summary_get_selected_msg_list(summaryview);
9644 for (cur = list; cur; cur = cur->next) {
9645 MsgInfo *msginfo = (MsgInfo *)cur->data;
9648 file = procmsg_get_message_file_full(msginfo,
9651 compose_attach_append(compose, (const gchar *)file,
9652 (const gchar *)file, "message/rfc822");
9660 static gboolean compose_drag_drop(GtkWidget *widget,
9661 GdkDragContext *drag_context,
9663 guint time, gpointer user_data)
9665 /* not handling this signal makes compose_insert_drag_received_cb
9670 static void compose_insert_drag_received_cb (GtkWidget *widget,
9671 GdkDragContext *drag_context,
9674 GtkSelectionData *data,
9679 Compose *compose = (Compose *)user_data;
9682 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
9684 if (gdk_atom_name(data->type) && !strcmp(gdk_atom_name(data->type), "text/uri-list")) {
9685 AlertValue val = G_ALERTDEFAULT;
9687 list = uri_list_extract_filenames((const gchar *)data->data);
9689 if (list == NULL && strstr((gchar *)(data->data), "://")) {
9690 /* Assume a list of no files, and data has ://, is a remote link */
9691 gchar *tmpdata = g_strstrip(g_strdup((const gchar *)data->data));
9692 gchar *tmpfile = get_tmp_file();
9693 str_write_to_file(tmpdata, tmpfile);
9695 compose_insert_file(compose, tmpfile);
9698 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9699 compose_beautify_paragraph(compose, NULL, TRUE);
9702 switch (prefs_common.compose_dnd_mode) {
9703 case COMPOSE_DND_ASK:
9704 val = alertpanel_full(_("Insert or attach?"),
9705 _("Do you want to insert the contents of the file(s) "
9706 "into the message body, or attach it to the email?"),
9707 GTK_STOCK_CANCEL, _("+_Insert"), _("_Attach"),
9708 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
9710 case COMPOSE_DND_INSERT:
9711 val = G_ALERTALTERNATE;
9713 case COMPOSE_DND_ATTACH:
9717 /* unexpected case */
9718 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
9721 if (val & G_ALERTDISABLE) {
9722 val &= ~G_ALERTDISABLE;
9723 /* remember what action to perform by default, only if we don't click Cancel */
9724 if (val == G_ALERTALTERNATE)
9725 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
9726 else if (val == G_ALERTOTHER)
9727 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
9730 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
9731 gtk_drag_finish(drag_context, FALSE, FALSE, time);
9732 list_free_strings(list);
9735 } else if (val == G_ALERTOTHER) {
9736 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
9737 list_free_strings(list);
9742 for (tmp = list; tmp != NULL; tmp = tmp->next) {
9743 compose_insert_file(compose, (const gchar *)tmp->data);
9745 list_free_strings(list);
9747 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9750 #if GTK_CHECK_VERSION(2, 8, 0)
9751 /* do nothing, handled by GTK */
9753 gchar *tmpfile = get_tmp_file();
9754 str_write_to_file((const gchar *)data->data, tmpfile);
9755 compose_insert_file(compose, tmpfile);
9758 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9762 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9765 static void compose_header_drag_received_cb (GtkWidget *widget,
9766 GdkDragContext *drag_context,
9769 GtkSelectionData *data,
9774 GtkEditable *entry = (GtkEditable *)user_data;
9775 gchar *email = (gchar *)data->data;
9777 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
9780 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
9781 gchar *decoded=g_new(gchar, strlen(email));
9784 email += strlen("mailto:");
9785 decode_uri(decoded, email); /* will fit */
9786 gtk_editable_delete_text(entry, 0, -1);
9787 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
9788 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9792 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9795 static void compose_toggle_return_receipt_cb(gpointer data, guint action,
9798 Compose *compose = (Compose *)data;
9800 if (GTK_CHECK_MENU_ITEM(widget)->active)
9801 compose->return_receipt = TRUE;
9803 compose->return_receipt = FALSE;
9806 static void compose_toggle_remove_refs_cb(gpointer data, guint action,
9809 Compose *compose = (Compose *)data;
9811 if (GTK_CHECK_MENU_ITEM(widget)->active)
9812 compose->remove_references = TRUE;
9814 compose->remove_references = FALSE;
9817 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
9819 ComposeHeaderEntry *headerentry)
9821 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
9822 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
9823 !(event->state & GDK_MODIFIER_MASK) &&
9824 (event->keyval == GDK_BackSpace) &&
9825 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
9826 gtk_container_remove
9827 (GTK_CONTAINER(headerentry->compose->header_table),
9828 headerentry->combo);
9829 gtk_container_remove
9830 (GTK_CONTAINER(headerentry->compose->header_table),
9831 headerentry->entry);
9832 headerentry->compose->header_list =
9833 g_slist_remove(headerentry->compose->header_list,
9835 g_free(headerentry);
9836 } else if (event->keyval == GDK_Tab) {
9837 if (headerentry->compose->header_last == headerentry) {
9838 /* Override default next focus, and give it to subject_entry
9839 * instead of notebook tabs
9841 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
9842 gtk_widget_grab_focus(headerentry->compose->subject_entry);
9849 static gboolean compose_headerentry_changed_cb(GtkWidget *entry,
9850 ComposeHeaderEntry *headerentry)
9852 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
9853 compose_create_header_entry(headerentry->compose);
9854 g_signal_handlers_disconnect_matched
9855 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
9856 0, 0, NULL, NULL, headerentry);
9858 /* Automatically scroll down */
9859 compose_show_first_last_header(headerentry->compose, FALSE);
9865 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
9867 GtkAdjustment *vadj;
9869 g_return_if_fail(compose);
9870 g_return_if_fail(GTK_IS_WIDGET(compose->header_table));
9871 g_return_if_fail(GTK_IS_VIEWPORT(compose->header_table->parent));
9873 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(compose->header_table->parent));
9874 gtk_adjustment_set_value(vadj, (show_first ? vadj->lower : vadj->upper));
9875 gtk_adjustment_changed(vadj);
9878 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
9879 const gchar *text, gint len, Compose *compose)
9881 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
9882 (G_OBJECT(compose->text), "paste_as_quotation"));
9885 g_return_if_fail(text != NULL);
9887 g_signal_handlers_block_by_func(G_OBJECT(buffer),
9888 G_CALLBACK(text_inserted),
9890 if (paste_as_quotation) {
9894 GtkTextIter start_iter;
9899 new_text = g_strndup(text, len);
9901 qmark = compose_quote_char_from_context(compose);
9903 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
9904 gtk_text_buffer_place_cursor(buffer, iter);
9906 pos = gtk_text_iter_get_offset(iter);
9908 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
9909 _("Quote format error at line %d."));
9910 quote_fmt_reset_vartable();
9912 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
9913 GINT_TO_POINTER(paste_as_quotation - 1));
9915 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
9916 gtk_text_buffer_place_cursor(buffer, iter);
9917 gtk_text_buffer_delete_mark(buffer, mark);
9919 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
9920 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
9921 compose_beautify_paragraph(compose, &start_iter, FALSE);
9922 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
9923 gtk_text_buffer_delete_mark(buffer, mark);
9925 if (strcmp(text, "\n") || compose->automatic_break
9926 || gtk_text_iter_starts_line(iter))
9927 gtk_text_buffer_insert(buffer, iter, text, len);
9929 debug_print("insert nowrap \\n\n");
9930 gtk_text_buffer_insert_with_tags_by_name(buffer,
9931 iter, text, len, "no_join", NULL);
9935 if (!paste_as_quotation) {
9936 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
9937 compose_beautify_paragraph(compose, iter, FALSE);
9938 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
9939 gtk_text_buffer_delete_mark(buffer, mark);
9942 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
9943 G_CALLBACK(text_inserted),
9945 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
9947 if (prefs_common.autosave &&
9948 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
9949 compose->draft_timeout_tag != -2 /* disabled while loading */)
9950 compose->draft_timeout_tag = g_timeout_add
9951 (500, (GtkFunction) compose_defer_auto_save_draft, compose);
9953 static gint compose_defer_auto_save_draft(Compose *compose)
9955 compose->draft_timeout_tag = -1;
9956 compose_draft_cb((gpointer)compose, COMPOSE_AUTO_SAVE, NULL);
9961 static void compose_check_all(Compose *compose)
9963 if (compose->gtkaspell)
9964 gtkaspell_check_all(compose->gtkaspell);
9967 static void compose_highlight_all(Compose *compose)
9969 if (compose->gtkaspell)
9970 gtkaspell_highlight_all(compose->gtkaspell);
9973 static void compose_check_backwards(Compose *compose)
9975 if (compose->gtkaspell)
9976 gtkaspell_check_backwards(compose->gtkaspell);
9978 GtkItemFactory *ifactory;
9979 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
9980 menu_set_sensitive(ifactory, "/Edit/Check backwards misspelled word", FALSE);
9981 menu_set_sensitive(ifactory, "/Edit/Forward to next misspelled word", FALSE);
9985 static void compose_check_forwards_go(Compose *compose)
9987 if (compose->gtkaspell)
9988 gtkaspell_check_forwards_go(compose->gtkaspell);
9990 GtkItemFactory *ifactory;
9991 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
9992 menu_set_sensitive(ifactory, "/Edit/Check backwards misspelled word", FALSE);
9993 menu_set_sensitive(ifactory, "/Edit/Forward to next misspelled word", FALSE);
9999 *\brief Guess originating forward account from MsgInfo and several
10000 * "common preference" settings. Return NULL if no guess.
10002 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
10004 PrefsAccount *account = NULL;
10006 g_return_val_if_fail(msginfo, NULL);
10007 g_return_val_if_fail(msginfo->folder, NULL);
10008 g_return_val_if_fail(msginfo->folder->prefs, NULL);
10010 if (msginfo->folder->prefs->enable_default_account)
10011 account = account_find_from_id(msginfo->folder->prefs->default_account);
10014 account = msginfo->folder->folder->account;
10016 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
10018 Xstrdup_a(to, msginfo->to, return NULL);
10019 extract_address(to);
10020 account = account_find_from_address(to);
10023 if (!account && prefs_common.forward_account_autosel) {
10024 gchar cc[BUFFSIZE];
10025 if (!procheader_get_header_from_msginfo
10026 (msginfo, cc,sizeof cc , "Cc:")) {
10027 gchar *buf = cc + strlen("Cc:");
10028 extract_address(buf);
10029 account = account_find_from_address(buf);
10033 if (!account && prefs_common.forward_account_autosel) {
10034 gchar deliveredto[BUFFSIZE];
10035 if (!procheader_get_header_from_msginfo
10036 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
10037 gchar *buf = deliveredto + strlen("Delivered-To:");
10038 extract_address(buf);
10039 account = account_find_from_address(buf);
10046 gboolean compose_close(Compose *compose)
10050 if (!g_mutex_trylock(compose->mutex)) {
10051 /* we have to wait for the (possibly deferred by auto-save)
10052 * drafting to be done, before destroying the compose under
10054 debug_print("waiting for drafting to finish...\n");
10055 compose_allow_user_actions(compose, FALSE);
10056 g_timeout_add (500, (GSourceFunc) compose_close, compose);
10059 g_return_val_if_fail(compose, FALSE);
10060 gtkut_widget_get_uposition(compose->window, &x, &y);
10061 prefs_common.compose_x = x;
10062 prefs_common.compose_y = y;
10063 g_mutex_unlock(compose->mutex);
10064 compose_destroy(compose);
10069 * Add entry field for each address in list.
10070 * \param compose E-Mail composition object.
10071 * \param listAddress List of (formatted) E-Mail addresses.
10073 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
10076 node = listAddress;
10078 addr = ( gchar * ) node->data;
10079 compose_entry_append( compose, addr, COMPOSE_TO );
10080 node = g_list_next( node );
10084 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
10085 guint action, gboolean opening_multiple)
10087 gchar *body = NULL;
10088 GSList *new_msglist = NULL;
10089 MsgInfo *tmp_msginfo = NULL;
10090 gboolean originally_enc = FALSE;
10091 Compose *compose = NULL;
10093 g_return_if_fail(msgview != NULL);
10095 g_return_if_fail(msginfo_list != NULL);
10097 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
10098 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
10099 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
10101 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
10102 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
10103 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
10104 orig_msginfo, mimeinfo);
10105 if (tmp_msginfo != NULL) {
10106 new_msglist = g_slist_append(NULL, tmp_msginfo);
10108 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
10109 tmp_msginfo->folder = orig_msginfo->folder;
10110 tmp_msginfo->msgnum = orig_msginfo->msgnum;
10111 if (orig_msginfo->tags)
10112 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
10117 if (!opening_multiple)
10118 body = messageview_get_selection(msgview);
10121 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
10122 procmsg_msginfo_free(tmp_msginfo);
10123 g_slist_free(new_msglist);
10125 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
10127 if (compose && originally_enc) {
10128 compose_force_encryption(compose, compose->account, FALSE);
10134 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
10137 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
10138 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
10139 GSList *cur = msginfo_list;
10140 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
10141 "messages. Opening the windows "
10142 "could take some time. Do you "
10143 "want to continue?"),
10144 g_slist_length(msginfo_list));
10145 if (g_slist_length(msginfo_list) > 9
10146 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
10147 != G_ALERTALTERNATE) {
10152 /* We'll open multiple compose windows */
10153 /* let the WM place the next windows */
10154 compose_force_window_origin = FALSE;
10155 for (; cur; cur = cur->next) {
10157 tmplist.data = cur->data;
10158 tmplist.next = NULL;
10159 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
10161 compose_force_window_origin = TRUE;
10163 /* forwarding multiple mails as attachments is done via a
10164 * single compose window */
10165 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
10169 void compose_set_position(Compose *compose, gint pos)
10171 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10173 gtkut_text_view_set_position(text, pos);
10176 gboolean compose_search_string(Compose *compose,
10177 const gchar *str, gboolean case_sens)
10179 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10181 return gtkut_text_view_search_string(text, str, case_sens);
10184 gboolean compose_search_string_backward(Compose *compose,
10185 const gchar *str, gboolean case_sens)
10187 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10189 return gtkut_text_view_search_string_backward(text, str, case_sens);
10192 /* allocate a msginfo structure and populate its data from a compose data structure */
10193 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
10195 MsgInfo *newmsginfo;
10197 gchar buf[BUFFSIZE];
10199 g_return_val_if_fail( compose != NULL, NULL );
10201 newmsginfo = procmsg_msginfo_new();
10204 get_rfc822_date(buf, sizeof(buf));
10205 newmsginfo->date = g_strdup(buf);
10208 if (compose->from_name) {
10209 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
10210 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
10214 if (compose->subject_entry)
10215 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
10217 /* to, cc, reply-to, newsgroups */
10218 for (list = compose->header_list; list; list = list->next) {
10219 gchar *header = gtk_editable_get_chars(
10221 GTK_BIN(((ComposeHeaderEntry *)list->data)->combo)->child), 0, -1);
10222 gchar *entry = gtk_editable_get_chars(
10223 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
10225 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
10226 if ( newmsginfo->to == NULL ) {
10227 newmsginfo->to = g_strdup(entry);
10228 } else if (entry && *entry) {
10229 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
10230 g_free(newmsginfo->to);
10231 newmsginfo->to = tmp;
10234 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
10235 if ( newmsginfo->cc == NULL ) {
10236 newmsginfo->cc = g_strdup(entry);
10237 } else if (entry && *entry) {
10238 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
10239 g_free(newmsginfo->cc);
10240 newmsginfo->cc = tmp;
10243 if ( strcasecmp(header,
10244 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
10245 if ( newmsginfo->newsgroups == NULL ) {
10246 newmsginfo->newsgroups = g_strdup(entry);
10247 } else if (entry && *entry) {
10248 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
10249 g_free(newmsginfo->newsgroups);
10250 newmsginfo->newsgroups = tmp;
10258 /* other data is unset */
10264 /* update compose's dictionaries from folder dict settings */
10265 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
10266 FolderItem *folder_item)
10268 g_return_if_fail(compose != NULL);
10270 if (compose->gtkaspell && folder_item && folder_item->prefs) {
10271 FolderItemPrefs *prefs = folder_item->prefs;
10273 if (prefs->enable_default_dictionary)
10274 gtkaspell_change_dict(compose->gtkaspell,
10275 prefs->default_dictionary, FALSE);
10276 if (folder_item->prefs->enable_default_alt_dictionary)
10277 gtkaspell_change_alt_dict(compose->gtkaspell,
10278 prefs->default_alt_dictionary);
10279 if (prefs->enable_default_dictionary
10280 || prefs->enable_default_alt_dictionary)
10281 compose_spell_menu_changed(compose);