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)
2382 gchar *subject = NULL;
2386 gchar *attach = NULL;
2388 scan_mailto_url(mailto, &to, &cc, NULL, &subject, &body, &attach);
2391 compose_entry_append(compose, to, COMPOSE_TO);
2393 compose_entry_append(compose, cc, COMPOSE_CC);
2395 if (!g_utf8_validate (subject, -1, NULL)) {
2396 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2397 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2400 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2404 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2405 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2408 gboolean prev_autowrap = compose->autowrap;
2410 compose->autowrap = FALSE;
2412 mark = gtk_text_buffer_get_insert(buffer);
2413 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2415 if (!g_utf8_validate (body, -1, NULL)) {
2416 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2417 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2420 gtk_text_buffer_insert(buffer, &iter, body, -1);
2422 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2424 compose->autowrap = prev_autowrap;
2425 if (compose->autowrap)
2426 compose_wrap_all(compose);
2430 gchar *utf8_filename = conv_filename_to_utf8(attach);
2431 if (utf8_filename) {
2432 if (compose_attach_append(compose, attach, utf8_filename, NULL)) {
2433 alertpanel_notice(_("The file '%s' has been attached."), attach);
2435 g_free(utf8_filename);
2437 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2447 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2449 static HeaderEntry hentry[] = {{"Reply-To:", NULL, TRUE},
2450 {"Cc:", NULL, TRUE},
2451 {"References:", NULL, FALSE},
2452 {"Bcc:", NULL, TRUE},
2453 {"Newsgroups:", NULL, TRUE},
2454 {"Followup-To:", NULL, TRUE},
2455 {"List-Post:", NULL, FALSE},
2456 {"X-Priority:", NULL, FALSE},
2457 {NULL, NULL, FALSE}};
2473 g_return_val_if_fail(msginfo != NULL, -1);
2475 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2476 procheader_get_header_fields(fp, hentry);
2479 if (hentry[H_REPLY_TO].body != NULL) {
2480 if (hentry[H_REPLY_TO].body[0] != '\0') {
2482 conv_unmime_header(hentry[H_REPLY_TO].body,
2485 g_free(hentry[H_REPLY_TO].body);
2486 hentry[H_REPLY_TO].body = NULL;
2488 if (hentry[H_CC].body != NULL) {
2489 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL);
2490 g_free(hentry[H_CC].body);
2491 hentry[H_CC].body = NULL;
2493 if (hentry[H_REFERENCES].body != NULL) {
2494 if (compose->mode == COMPOSE_REEDIT)
2495 compose->references = hentry[H_REFERENCES].body;
2497 compose->references = compose_parse_references
2498 (hentry[H_REFERENCES].body, msginfo->msgid);
2499 g_free(hentry[H_REFERENCES].body);
2501 hentry[H_REFERENCES].body = NULL;
2503 if (hentry[H_BCC].body != NULL) {
2504 if (compose->mode == COMPOSE_REEDIT)
2506 conv_unmime_header(hentry[H_BCC].body, NULL);
2507 g_free(hentry[H_BCC].body);
2508 hentry[H_BCC].body = NULL;
2510 if (hentry[H_NEWSGROUPS].body != NULL) {
2511 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2512 hentry[H_NEWSGROUPS].body = NULL;
2514 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2515 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2516 compose->followup_to =
2517 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2520 g_free(hentry[H_FOLLOWUP_TO].body);
2521 hentry[H_FOLLOWUP_TO].body = NULL;
2523 if (hentry[H_LIST_POST].body != NULL) {
2526 extract_address(hentry[H_LIST_POST].body);
2527 if (hentry[H_LIST_POST].body[0] != '\0') {
2528 scan_mailto_url(hentry[H_LIST_POST].body,
2529 &to, NULL, NULL, NULL, NULL, NULL);
2531 g_free(compose->ml_post);
2532 compose->ml_post = to;
2535 g_free(hentry[H_LIST_POST].body);
2536 hentry[H_LIST_POST].body = NULL;
2539 /* CLAWS - X-Priority */
2540 if (compose->mode == COMPOSE_REEDIT)
2541 if (hentry[H_X_PRIORITY].body != NULL) {
2544 priority = atoi(hentry[H_X_PRIORITY].body);
2545 g_free(hentry[H_X_PRIORITY].body);
2547 hentry[H_X_PRIORITY].body = NULL;
2549 if (priority < PRIORITY_HIGHEST ||
2550 priority > PRIORITY_LOWEST)
2551 priority = PRIORITY_NORMAL;
2553 compose->priority = priority;
2556 if (compose->mode == COMPOSE_REEDIT) {
2557 if (msginfo->inreplyto && *msginfo->inreplyto)
2558 compose->inreplyto = g_strdup(msginfo->inreplyto);
2562 if (msginfo->msgid && *msginfo->msgid)
2563 compose->inreplyto = g_strdup(msginfo->msgid);
2565 if (!compose->references) {
2566 if (msginfo->msgid && *msginfo->msgid) {
2567 if (msginfo->inreplyto && *msginfo->inreplyto)
2568 compose->references =
2569 g_strdup_printf("<%s>\n\t<%s>",
2573 compose->references =
2574 g_strconcat("<", msginfo->msgid, ">",
2576 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
2577 compose->references =
2578 g_strconcat("<", msginfo->inreplyto, ">",
2586 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
2588 GSList *ref_id_list, *cur;
2592 ref_id_list = references_list_append(NULL, ref);
2593 if (!ref_id_list) return NULL;
2594 if (msgid && *msgid)
2595 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
2600 for (cur = ref_id_list; cur != NULL; cur = cur->next)
2601 /* "<" + Message-ID + ">" + CR+LF+TAB */
2602 len += strlen((gchar *)cur->data) + 5;
2604 if (len > MAX_REFERENCES_LEN) {
2605 /* remove second message-ID */
2606 if (ref_id_list && ref_id_list->next &&
2607 ref_id_list->next->next) {
2608 g_free(ref_id_list->next->data);
2609 ref_id_list = g_slist_remove
2610 (ref_id_list, ref_id_list->next->data);
2612 slist_free_strings(ref_id_list);
2613 g_slist_free(ref_id_list);
2620 new_ref = g_string_new("");
2621 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
2622 if (new_ref->len > 0)
2623 g_string_append(new_ref, "\n\t");
2624 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
2627 slist_free_strings(ref_id_list);
2628 g_slist_free(ref_id_list);
2630 new_ref_str = new_ref->str;
2631 g_string_free(new_ref, FALSE);
2636 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
2637 const gchar *fmt, const gchar *qmark,
2638 const gchar *body, gboolean rewrap,
2639 gboolean need_unescape,
2640 const gchar *err_msg)
2642 MsgInfo* dummyinfo = NULL;
2643 gchar *quote_str = NULL;
2645 gboolean prev_autowrap;
2646 const gchar *trimmed_body = body;
2647 gint cursor_pos = -1;
2648 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2649 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2654 SIGNAL_BLOCK(buffer);
2657 dummyinfo = compose_msginfo_new_from_compose(compose);
2658 msginfo = dummyinfo;
2661 if (qmark != NULL) {
2663 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
2664 compose->gtkaspell);
2666 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
2668 quote_fmt_scan_string(qmark);
2671 buf = quote_fmt_get_buffer();
2673 alertpanel_error(_("Quote mark format error."));
2675 Xstrdup_a(quote_str, buf, goto error)
2678 if (fmt && *fmt != '\0') {
2681 while (*trimmed_body == '\n')
2685 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account,
2686 compose->gtkaspell);
2688 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account);
2690 if (need_unescape) {
2693 /* decode \-escape sequences in the internal representation of the quote format */
2694 tmp = malloc(strlen(fmt)+1);
2695 pref_get_unescaped_pref(tmp, fmt);
2696 quote_fmt_scan_string(tmp);
2700 quote_fmt_scan_string(fmt);
2704 buf = quote_fmt_get_buffer();
2706 gint line = quote_fmt_get_line();
2707 alertpanel_error(err_msg, line);
2713 prev_autowrap = compose->autowrap;
2714 compose->autowrap = FALSE;
2716 mark = gtk_text_buffer_get_insert(buffer);
2717 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2718 if (g_utf8_validate(buf, -1, NULL)) {
2719 gtk_text_buffer_insert(buffer, &iter, buf, -1);
2721 gchar *tmpout = NULL;
2722 tmpout = conv_codeset_strdup
2723 (buf, conv_get_locale_charset_str_no_utf8(),
2725 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
2727 tmpout = g_malloc(strlen(buf)*2+1);
2728 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
2730 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
2734 cursor_pos = quote_fmt_get_cursor_pos();
2735 compose->set_cursor_pos = cursor_pos;
2736 if (cursor_pos == -1) {
2739 gtk_text_buffer_get_start_iter(buffer, &iter);
2740 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
2741 gtk_text_buffer_place_cursor(buffer, &iter);
2743 compose->autowrap = prev_autowrap;
2744 if (compose->autowrap && rewrap)
2745 compose_wrap_all(compose);
2752 SIGNAL_UNBLOCK(buffer);
2754 procmsg_msginfo_free( dummyinfo );
2759 /* if ml_post is of type addr@host and from is of type
2760 * addr-anything@host, return TRUE
2762 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
2764 gchar *left_ml = NULL;
2765 gchar *right_ml = NULL;
2766 gchar *left_from = NULL;
2767 gchar *right_from = NULL;
2768 gboolean result = FALSE;
2770 if (!ml_post || !from)
2773 left_ml = g_strdup(ml_post);
2774 if (strstr(left_ml, "@")) {
2775 right_ml = strstr(left_ml, "@")+1;
2776 *(strstr(left_ml, "@")) = '\0';
2779 left_from = g_strdup(from);
2780 if (strstr(left_from, "@")) {
2781 right_from = strstr(left_from, "@")+1;
2782 *(strstr(left_from, "@")) = '\0';
2785 if (left_ml && left_from && right_ml && right_from
2786 && !strncmp(left_from, left_ml, strlen(left_ml))
2787 && !strcmp(right_from, right_ml)) {
2796 static gboolean same_address(const gchar *addr1, const gchar *addr2)
2798 gchar *my_addr1, *my_addr2;
2800 if (!addr1 || !addr2)
2803 Xstrdup_a(my_addr1, addr1, return FALSE);
2804 Xstrdup_a(my_addr2, addr2, return FALSE);
2806 extract_address(my_addr1);
2807 extract_address(my_addr2);
2809 return !strcasecmp(my_addr1, my_addr2);
2812 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
2813 gboolean to_all, gboolean to_ml,
2815 gboolean followup_and_reply_to)
2817 GSList *cc_list = NULL;
2820 gchar *replyto = NULL;
2821 GHashTable *to_table;
2823 gboolean reply_to_ml = FALSE;
2824 gboolean default_reply_to = FALSE;
2826 g_return_if_fail(compose->account != NULL);
2827 g_return_if_fail(msginfo != NULL);
2829 reply_to_ml = to_ml && compose->ml_post;
2831 default_reply_to = msginfo->folder &&
2832 msginfo->folder->prefs->enable_default_reply_to;
2834 if (compose->account->protocol != A_NNTP) {
2835 if (reply_to_ml && !default_reply_to) {
2837 gboolean is_subscr = is_subscription(compose->ml_post,
2840 /* normal answer to ml post with a reply-to */
2841 compose_entry_append(compose,
2844 if (compose->replyto
2845 && !same_address(compose->ml_post, compose->replyto))
2846 compose_entry_append(compose,
2850 /* answer to subscription confirmation */
2851 if (compose->replyto)
2852 compose_entry_append(compose,
2855 else if (msginfo->from)
2856 compose_entry_append(compose,
2861 else if (!(to_all || to_sender) && default_reply_to) {
2862 compose_entry_append(compose,
2863 msginfo->folder->prefs->default_reply_to,
2865 compose_entry_mark_default_to(compose,
2866 msginfo->folder->prefs->default_reply_to);
2871 Xstrdup_a(tmp1, msginfo->from, return);
2872 extract_address(tmp1);
2873 if (to_all || to_sender ||
2874 !account_find_from_address(tmp1))
2875 compose_entry_append(compose,
2876 (compose->replyto && !to_sender)
2877 ? compose->replyto :
2878 msginfo->from ? msginfo->from : "",
2880 else if (!to_all && !to_sender) {
2881 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
2882 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
2883 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
2884 compose_entry_append(compose,
2885 msginfo->from ? msginfo->from : "",
2888 /* replying to own mail, use original recp */
2889 compose_entry_append(compose,
2890 msginfo->to ? msginfo->to : "",
2892 compose_entry_append(compose,
2893 msginfo->cc ? msginfo->cc : "",
2899 if (to_sender || (compose->followup_to &&
2900 !strncmp(compose->followup_to, "poster", 6)))
2901 compose_entry_append
2903 (compose->replyto ? compose->replyto :
2904 msginfo->from ? msginfo->from : ""),
2907 else if (followup_and_reply_to || to_all) {
2908 compose_entry_append
2910 (compose->replyto ? compose->replyto :
2911 msginfo->from ? msginfo->from : ""),
2914 compose_entry_append
2916 compose->followup_to ? compose->followup_to :
2917 compose->newsgroups ? compose->newsgroups : "",
2918 COMPOSE_NEWSGROUPS);
2921 compose_entry_append
2923 compose->followup_to ? compose->followup_to :
2924 compose->newsgroups ? compose->newsgroups : "",
2925 COMPOSE_NEWSGROUPS);
2928 if (msginfo->subject && *msginfo->subject) {
2932 buf = p = g_strdup(msginfo->subject);
2933 p += subject_get_prefix_length(p);
2934 memmove(buf, p, strlen(p) + 1);
2936 buf2 = g_strdup_printf("Re: %s", buf);
2937 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
2942 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
2944 if (to_ml && compose->ml_post) return;
2945 if (!to_all || compose->account->protocol == A_NNTP) return;
2947 if (compose->replyto) {
2948 Xstrdup_a(replyto, compose->replyto, return);
2949 extract_address(replyto);
2951 if (msginfo->from) {
2952 Xstrdup_a(from, msginfo->from, return);
2953 extract_address(from);
2956 if (replyto && from)
2957 cc_list = address_list_append_with_comments(cc_list, from);
2958 if (to_all && msginfo->folder &&
2959 msginfo->folder->prefs->enable_default_reply_to)
2960 cc_list = address_list_append_with_comments(cc_list,
2961 msginfo->folder->prefs->default_reply_to);
2962 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
2963 cc_list = address_list_append_with_comments(cc_list, compose->cc);
2965 to_table = g_hash_table_new(g_str_hash, g_str_equal);
2967 g_hash_table_insert(to_table, g_utf8_strdown(replyto, -1), GINT_TO_POINTER(1));
2968 if (compose->account) {
2969 g_hash_table_insert(to_table, g_utf8_strdown(compose->account->address, -1),
2970 GINT_TO_POINTER(1));
2972 /* remove address on To: and that of current account */
2973 for (cur = cc_list; cur != NULL; ) {
2974 GSList *next = cur->next;
2977 addr = g_utf8_strdown(cur->data, -1);
2978 extract_address(addr);
2980 if (GPOINTER_TO_INT(g_hash_table_lookup(to_table, addr)) == 1)
2981 cc_list = g_slist_remove(cc_list, cur->data);
2983 g_hash_table_insert(to_table, addr, GINT_TO_POINTER(1));
2987 hash_free_strings(to_table);
2988 g_hash_table_destroy(to_table);
2991 for (cur = cc_list; cur != NULL; cur = cur->next)
2992 compose_entry_append(compose, (gchar *)cur->data,
2994 slist_free_strings(cc_list);
2995 g_slist_free(cc_list);
3000 #define SET_ENTRY(entry, str) \
3003 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3006 #define SET_ADDRESS(type, str) \
3009 compose_entry_append(compose, str, type); \
3012 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3014 g_return_if_fail(msginfo != NULL);
3016 SET_ENTRY(subject_entry, msginfo->subject);
3017 SET_ENTRY(from_name, msginfo->from);
3018 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3019 SET_ADDRESS(COMPOSE_CC, compose->cc);
3020 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3021 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3022 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3023 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3025 compose_update_priority_menu_item(compose);
3026 compose_update_privacy_system_menu_item(compose, FALSE);
3027 compose_show_first_last_header(compose, TRUE);
3033 static void compose_insert_sig(Compose *compose, gboolean replace)
3035 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3036 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3038 GtkTextIter iter, iter_end;
3040 gboolean prev_autowrap;
3041 gboolean found = FALSE;
3042 gboolean exists = FALSE;
3044 g_return_if_fail(compose->account != NULL);
3048 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3049 G_CALLBACK(compose_changed_cb),
3052 mark = gtk_text_buffer_get_insert(buffer);
3053 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3054 cur_pos = gtk_text_iter_get_offset (&iter);
3056 gtk_text_buffer_get_end_iter(buffer, &iter);
3058 exists = (compose->sig_str != NULL);
3061 GtkTextIter first_iter, start_iter, end_iter;
3063 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3065 if (!exists || compose->sig_str[0] == '\0')
3068 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3069 compose->signature_tag);
3072 /* include previous \n\n */
3073 gtk_text_iter_backward_chars(&first_iter, 2);
3074 start_iter = first_iter;
3075 end_iter = first_iter;
3077 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3078 compose->signature_tag);
3079 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3080 compose->signature_tag);
3082 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3088 g_free(compose->sig_str);
3089 compose->sig_str = compose_get_signature_str(compose);
3091 cur_pos = gtk_text_iter_get_offset(&iter);
3093 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3094 g_free(compose->sig_str);
3095 compose->sig_str = NULL;
3097 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3099 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3100 gtk_text_iter_forward_chars(&iter, 2);
3101 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3102 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3104 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3105 cur_pos = gtk_text_buffer_get_char_count (buffer);
3107 /* put the cursor where it should be
3108 * either where the quote_fmt says, either before the signature */
3109 if (compose->set_cursor_pos < 0)
3110 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3112 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3113 compose->set_cursor_pos);
3115 gtk_text_buffer_place_cursor(buffer, &iter);
3116 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3117 G_CALLBACK(compose_changed_cb),
3123 static gchar *compose_get_signature_str(Compose *compose)
3125 gchar *sig_body = NULL;
3126 gchar *sig_str = NULL;
3127 gchar *utf8_sig_str = NULL;
3129 g_return_val_if_fail(compose->account != NULL, NULL);
3131 if (!compose->account->sig_path)
3134 if (compose->account->sig_type == SIG_FILE) {
3135 if (!is_file_or_fifo_exist(compose->account->sig_path)) {
3136 g_warning("can't open signature file: %s\n",
3137 compose->account->sig_path);
3142 if (compose->account->sig_type == SIG_COMMAND)
3143 sig_body = get_command_output(compose->account->sig_path);
3147 tmp = file_read_to_str(compose->account->sig_path);
3150 sig_body = normalize_newlines(tmp);
3154 if (compose->account->sig_sep) {
3155 sig_str = g_strconcat("\n\n", compose->account->sig_sep, "\n", sig_body,
3159 sig_str = g_strconcat("\n\n", sig_body, NULL);
3162 if (g_utf8_validate(sig_str, -1, NULL) == TRUE)
3163 utf8_sig_str = sig_str;
3165 utf8_sig_str = conv_codeset_strdup
3166 (sig_str, conv_get_locale_charset_str_no_utf8(),
3172 return utf8_sig_str;
3175 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3178 GtkTextBuffer *buffer;
3181 const gchar *cur_encoding;
3182 gchar buf[BUFFSIZE];
3185 gboolean prev_autowrap;
3186 gboolean badtxt = FALSE;
3188 g_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3190 if ((fp = g_fopen(file, "rb")) == NULL) {
3191 FILE_OP_ERROR(file, "fopen");
3192 return COMPOSE_INSERT_READ_ERROR;
3195 prev_autowrap = compose->autowrap;
3196 compose->autowrap = FALSE;
3198 text = GTK_TEXT_VIEW(compose->text);
3199 buffer = gtk_text_view_get_buffer(text);
3200 mark = gtk_text_buffer_get_insert(buffer);
3201 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3203 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3204 G_CALLBACK(text_inserted),
3207 cur_encoding = conv_get_locale_charset_str_no_utf8();
3209 while (fgets(buf, sizeof(buf), fp) != NULL) {
3212 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3213 str = g_strdup(buf);
3215 str = conv_codeset_strdup
3216 (buf, cur_encoding, CS_INTERNAL);
3219 /* strip <CR> if DOS/Windows file,
3220 replace <CR> with <LF> if Macintosh file. */
3223 if (len > 0 && str[len - 1] != '\n') {
3225 if (str[len] == '\r') str[len] = '\n';
3228 gtk_text_buffer_insert(buffer, &iter, str, -1);
3232 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3233 G_CALLBACK(text_inserted),
3235 compose->autowrap = prev_autowrap;
3236 if (compose->autowrap)
3237 compose_wrap_all(compose);
3242 return COMPOSE_INSERT_INVALID_CHARACTER;
3244 return COMPOSE_INSERT_SUCCESS;
3247 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3248 const gchar *filename,
3249 const gchar *content_type)
3257 GtkListStore *store;
3259 gboolean has_binary = FALSE;
3261 if (!is_file_exist(file)) {
3262 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3263 gboolean result = FALSE;
3264 if (file_from_uri && is_file_exist(file_from_uri)) {
3265 result = compose_attach_append(
3266 compose, file_from_uri,
3270 g_free(file_from_uri);
3273 alertpanel_error("File %s doesn't exist\n", filename);
3276 if ((size = get_file_size(file)) < 0) {
3277 alertpanel_error("Can't get file size of %s\n", filename);
3281 alertpanel_error(_("File %s is empty."), filename);
3284 if ((fp = g_fopen(file, "rb")) == NULL) {
3285 alertpanel_error(_("Can't read %s."), filename);
3290 ainfo = g_new0(AttachInfo, 1);
3291 auto_ainfo = g_auto_pointer_new_with_free
3292 (ainfo, (GFreeFunc) compose_attach_info_free);
3293 ainfo->file = g_strdup(file);
3296 ainfo->content_type = g_strdup(content_type);
3297 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3299 MsgFlags flags = {0, 0};
3301 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3302 ainfo->encoding = ENC_7BIT;
3304 ainfo->encoding = ENC_8BIT;
3306 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3307 if (msginfo && msginfo->subject)
3308 name = g_strdup(msginfo->subject);
3310 name = g_path_get_basename(filename ? filename : file);
3312 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3314 procmsg_msginfo_free(msginfo);
3316 if (!g_ascii_strncasecmp(content_type, "text", 4))
3317 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3319 ainfo->encoding = ENC_BASE64;
3320 name = g_path_get_basename(filename ? filename : file);
3321 ainfo->name = g_strdup(name);
3325 ainfo->content_type = procmime_get_mime_type(file);
3326 if (!ainfo->content_type) {
3327 ainfo->content_type =
3328 g_strdup("application/octet-stream");
3329 ainfo->encoding = ENC_BASE64;
3330 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text", 4))
3332 procmime_get_encoding_for_text_file(file, &has_binary);
3334 ainfo->encoding = ENC_BASE64;
3335 name = g_path_get_basename(filename ? filename : file);
3336 ainfo->name = g_strdup(name);
3340 if (ainfo->name != NULL
3341 && !strcmp(ainfo->name, ".")) {
3342 g_free(ainfo->name);
3346 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3347 g_free(ainfo->content_type);
3348 ainfo->content_type = g_strdup("application/octet-stream");
3352 size_text = to_human_readable(size);
3354 store = GTK_LIST_STORE(gtk_tree_view_get_model
3355 (GTK_TREE_VIEW(compose->attach_clist)));
3357 gtk_list_store_append(store, &iter);
3358 gtk_list_store_set(store, &iter,
3359 COL_MIMETYPE, ainfo->content_type,
3360 COL_SIZE, size_text,
3361 COL_NAME, ainfo->name,
3363 COL_AUTODATA, auto_ainfo,
3366 g_auto_pointer_free(auto_ainfo);
3367 compose_attach_update_label(compose);
3371 static void compose_use_signing(Compose *compose, gboolean use_signing)
3373 GtkItemFactory *ifactory;
3374 GtkWidget *menuitem = NULL;
3376 compose->use_signing = use_signing;
3377 ifactory = gtk_item_factory_from_widget(compose->menubar);
3378 menuitem = gtk_item_factory_get_item
3379 (ifactory, "/Options/Sign");
3380 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
3384 static void compose_use_encryption(Compose *compose, gboolean use_encryption)
3386 GtkItemFactory *ifactory;
3387 GtkWidget *menuitem = NULL;
3389 compose->use_encryption = use_encryption;
3390 ifactory = gtk_item_factory_from_widget(compose->menubar);
3391 menuitem = gtk_item_factory_get_item
3392 (ifactory, "/Options/Encrypt");
3394 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
3398 #define NEXT_PART_NOT_CHILD(info) \
3400 node = info->node; \
3401 while (node->children) \
3402 node = g_node_last_child(node); \
3403 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3406 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3410 MimeInfo *firsttext = NULL;
3411 MimeInfo *encrypted = NULL;
3414 const gchar *partname = NULL;
3416 mimeinfo = procmime_scan_message(msginfo);
3417 if (!mimeinfo) return;
3419 if (mimeinfo->node->children == NULL) {
3420 procmime_mimeinfo_free_all(mimeinfo);
3424 /* find first content part */
3425 child = (MimeInfo *) mimeinfo->node->children->data;
3426 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3427 child = (MimeInfo *)child->node->children->data;
3429 if (child->type == MIMETYPE_TEXT) {
3431 debug_print("First text part found\n");
3432 } else if (compose->mode == COMPOSE_REEDIT &&
3433 child->type == MIMETYPE_APPLICATION &&
3434 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3435 encrypted = (MimeInfo *)child->node->parent->data;
3438 child = (MimeInfo *) mimeinfo->node->children->data;
3439 while (child != NULL) {
3442 if (child == encrypted) {
3443 /* skip this part of tree */
3444 NEXT_PART_NOT_CHILD(child);
3448 if (child->type == MIMETYPE_MULTIPART) {
3449 /* get the actual content */
3450 child = procmime_mimeinfo_next(child);
3454 if (child == firsttext) {
3455 child = procmime_mimeinfo_next(child);
3459 outfile = procmime_get_tmp_file_name(child);
3460 if ((err = procmime_get_part(outfile, child)) < 0)
3461 g_warning("Can't get the part of multipart message. (%s)", strerror(-err));
3463 gchar *content_type;
3465 content_type = procmime_get_content_type_str(child->type, child->subtype);
3467 /* if we meet a pgp signature, we don't attach it, but
3468 * we force signing. */
3469 if ((strcmp(content_type, "application/pgp-signature") &&
3470 strcmp(content_type, "application/pkcs7-signature") &&
3471 strcmp(content_type, "application/x-pkcs7-signature"))
3472 || compose->mode == COMPOSE_REDIRECT) {
3473 partname = procmime_mimeinfo_get_parameter(child, "filename");
3474 if (partname == NULL)
3475 partname = procmime_mimeinfo_get_parameter(child, "name");
3476 if (partname == NULL)
3478 compose_attach_append(compose, outfile,
3479 partname, content_type);
3481 compose_force_signing(compose, compose->account);
3483 g_free(content_type);
3486 NEXT_PART_NOT_CHILD(child);
3488 procmime_mimeinfo_free_all(mimeinfo);
3491 #undef NEXT_PART_NOT_CHILD
3496 WAIT_FOR_INDENT_CHAR,
3497 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3500 /* return indent length, we allow:
3501 indent characters followed by indent characters or spaces/tabs,
3502 alphabets and numbers immediately followed by indent characters,
3503 and the repeating sequences of the above
3504 If quote ends with multiple spaces, only the first one is included. */
3505 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3506 const GtkTextIter *start, gint *len)
3508 GtkTextIter iter = *start;
3512 IndentState state = WAIT_FOR_INDENT_CHAR;
3515 gint alnum_count = 0;
3516 gint space_count = 0;
3519 if (prefs_common.quote_chars == NULL) {
3523 while (!gtk_text_iter_ends_line(&iter)) {
3524 wc = gtk_text_iter_get_char(&iter);
3525 if (g_unichar_iswide(wc))
3527 clen = g_unichar_to_utf8(wc, ch);
3531 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
3532 is_space = g_unichar_isspace(wc);
3534 if (state == WAIT_FOR_INDENT_CHAR) {
3535 if (!is_indent && !g_unichar_isalnum(wc))
3538 quote_len += alnum_count + space_count + 1;
3539 alnum_count = space_count = 0;
3540 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
3543 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
3544 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
3548 else if (is_indent) {
3549 quote_len += alnum_count + space_count + 1;
3550 alnum_count = space_count = 0;
3553 state = WAIT_FOR_INDENT_CHAR;
3557 gtk_text_iter_forward_char(&iter);
3560 if (quote_len > 0 && space_count > 0)
3566 if (quote_len > 0) {
3568 gtk_text_iter_forward_chars(&iter, quote_len);
3569 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
3575 /* return TRUE if the line is itemized */
3576 static gboolean compose_is_itemized(GtkTextBuffer *buffer,
3577 const GtkTextIter *start)
3579 GtkTextIter iter = *start;
3584 if (gtk_text_iter_ends_line(&iter))
3588 wc = gtk_text_iter_get_char(&iter);
3589 if (!g_unichar_isspace(wc))
3591 gtk_text_iter_forward_char(&iter);
3592 if (gtk_text_iter_ends_line(&iter))
3596 clen = g_unichar_to_utf8(wc, ch);
3600 if (!strchr("*-+", ch[0]))
3603 gtk_text_iter_forward_char(&iter);
3604 if (gtk_text_iter_ends_line(&iter))
3606 wc = gtk_text_iter_get_char(&iter);
3607 if (g_unichar_isspace(wc))
3613 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
3614 const GtkTextIter *start,
3615 GtkTextIter *break_pos,
3619 GtkTextIter iter = *start, line_end = *start;
3620 PangoLogAttr *attrs;
3627 gboolean can_break = FALSE;
3628 gboolean do_break = FALSE;
3629 gboolean was_white = FALSE;
3630 gboolean prev_dont_break = FALSE;
3632 gtk_text_iter_forward_to_line_end(&line_end);
3633 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
3634 len = g_utf8_strlen(str, -1);
3638 g_warning("compose_get_line_break_pos: len = 0!\n");
3642 /* g_print("breaking line: %d: %s (len = %d)\n",
3643 gtk_text_iter_get_line(&iter), str, len); */
3645 attrs = g_new(PangoLogAttr, len + 1);
3647 pango_default_break(str, -1, NULL, attrs, len + 1);
3651 /* skip quote and leading spaces */
3652 for (i = 0; *p != '\0' && i < len; i++) {
3655 wc = g_utf8_get_char(p);
3656 if (i >= quote_len && !g_unichar_isspace(wc))
3658 if (g_unichar_iswide(wc))
3660 else if (*p == '\t')
3664 p = g_utf8_next_char(p);
3667 for (; *p != '\0' && i < len; i++) {
3668 PangoLogAttr *attr = attrs + i;
3672 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
3675 was_white = attr->is_white;
3677 /* don't wrap URI */
3678 if ((uri_len = get_uri_len(p)) > 0) {
3680 if (pos > 0 && col > max_col) {
3690 wc = g_utf8_get_char(p);
3691 if (g_unichar_iswide(wc)) {
3693 if (prev_dont_break && can_break && attr->is_line_break)
3695 } else if (*p == '\t')
3699 if (pos > 0 && col > max_col) {
3704 if (*p == '-' || *p == '/')
3705 prev_dont_break = TRUE;
3707 prev_dont_break = FALSE;
3709 p = g_utf8_next_char(p);
3713 debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col);
3718 *break_pos = *start;
3719 gtk_text_iter_set_line_offset(break_pos, pos);
3724 static gboolean compose_join_next_line(Compose *compose,
3725 GtkTextBuffer *buffer,
3727 const gchar *quote_str)
3729 GtkTextIter iter_ = *iter, cur, prev, next, end;
3730 PangoLogAttr attrs[3];
3732 gchar *next_quote_str;
3735 gboolean keep_cursor = FALSE;
3737 if (!gtk_text_iter_forward_line(&iter_) ||
3738 gtk_text_iter_ends_line(&iter_))
3741 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
3743 if ((quote_str || next_quote_str) &&
3744 strcmp2(quote_str, next_quote_str) != 0) {
3745 g_free(next_quote_str);
3748 g_free(next_quote_str);
3751 if (quote_len > 0) {
3752 gtk_text_iter_forward_chars(&end, quote_len);
3753 if (gtk_text_iter_ends_line(&end))
3757 /* don't join itemized lines */
3758 if (compose_is_itemized(buffer, &end))
3761 /* don't join signature separator */
3762 if (compose_is_sig_separator(compose, buffer, &iter_))
3765 /* delete quote str */
3767 gtk_text_buffer_delete(buffer, &iter_, &end);
3769 /* don't join line breaks put by the user */
3771 gtk_text_iter_backward_char(&cur);
3772 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
3773 gtk_text_iter_forward_char(&cur);
3777 gtk_text_iter_forward_char(&cur);
3778 /* delete linebreak and extra spaces */
3779 while (gtk_text_iter_backward_char(&cur)) {
3780 wc1 = gtk_text_iter_get_char(&cur);
3781 if (!g_unichar_isspace(wc1))
3786 while (!gtk_text_iter_ends_line(&cur)) {
3787 wc1 = gtk_text_iter_get_char(&cur);
3788 if (!g_unichar_isspace(wc1))
3790 gtk_text_iter_forward_char(&cur);
3793 if (!gtk_text_iter_equal(&prev, &next)) {
3796 mark = gtk_text_buffer_get_insert(buffer);
3797 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
3798 if (gtk_text_iter_equal(&prev, &cur))
3800 gtk_text_buffer_delete(buffer, &prev, &next);
3804 /* insert space if required */
3805 gtk_text_iter_backward_char(&prev);
3806 wc1 = gtk_text_iter_get_char(&prev);
3807 wc2 = gtk_text_iter_get_char(&next);
3808 gtk_text_iter_forward_char(&next);
3809 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
3810 pango_default_break(str, -1, NULL, attrs, 3);
3811 if (!attrs[1].is_line_break ||
3812 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
3813 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
3815 gtk_text_iter_backward_char(&iter_);
3816 gtk_text_buffer_place_cursor(buffer, &iter_);
3825 #define ADD_TXT_POS(bp_, ep_, pti_) \
3826 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
3827 last = last->next; \
3828 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
3829 last->next = NULL; \
3831 g_warning("alloc error scanning URIs\n"); \
3834 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
3836 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3837 GtkTextBuffer *buffer;
3838 GtkTextIter iter, break_pos, end_of_line;
3839 gchar *quote_str = NULL;
3841 gboolean wrap_quote = prefs_common.linewrap_quote;
3842 gboolean prev_autowrap = compose->autowrap;
3843 gint startq_offset = -1, noq_offset = -1;
3844 gint uri_start = -1, uri_stop = -1;
3845 gint nouri_start = -1, nouri_stop = -1;
3846 gint num_blocks = 0;
3847 gint quotelevel = -1;
3848 gboolean modified = force;
3849 gboolean removed = FALSE;
3850 gboolean modified_before_remove = FALSE;
3852 gboolean start = TRUE;
3857 if (compose->draft_timeout_tag == -2) {
3861 compose->autowrap = FALSE;
3863 buffer = gtk_text_view_get_buffer(text);
3864 undo_wrapping(compose->undostruct, TRUE);
3869 mark = gtk_text_buffer_get_insert(buffer);
3870 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3874 if (compose->draft_timeout_tag == -2) {
3875 if (gtk_text_iter_ends_line(&iter)) {
3876 while (gtk_text_iter_ends_line(&iter) &&
3877 gtk_text_iter_forward_line(&iter))
3880 while (gtk_text_iter_backward_line(&iter)) {
3881 if (gtk_text_iter_ends_line(&iter)) {
3882 gtk_text_iter_forward_line(&iter);
3888 /* move to line start */
3889 gtk_text_iter_set_line_offset(&iter, 0);
3891 /* go until paragraph end (empty line) */
3892 while (start || !gtk_text_iter_ends_line(&iter)) {
3893 gchar *scanpos = NULL;
3894 /* parse table - in order of priority */
3896 const gchar *needle; /* token */
3898 /* token search function */
3899 gchar *(*search) (const gchar *haystack,
3900 const gchar *needle);
3901 /* part parsing function */
3902 gboolean (*parse) (const gchar *start,
3903 const gchar *scanpos,
3907 /* part to URI function */
3908 gchar *(*build_uri) (const gchar *bp,
3912 static struct table parser[] = {
3913 {"http://", strcasestr, get_uri_part, make_uri_string},
3914 {"https://", strcasestr, get_uri_part, make_uri_string},
3915 {"ftp://", strcasestr, get_uri_part, make_uri_string},
3916 {"sftp://", strcasestr, get_uri_part, make_uri_string},
3917 {"www.", strcasestr, get_uri_part, make_http_string},
3918 {"mailto:", strcasestr, get_uri_part, make_uri_string},
3919 {"@", strcasestr, get_email_part, make_email_string}
3921 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
3922 gint last_index = PARSE_ELEMS;
3924 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
3928 if (!prev_autowrap && num_blocks == 0) {
3930 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3931 G_CALLBACK(text_inserted),
3934 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
3937 uri_start = uri_stop = -1;
3939 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
3942 debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str);
3943 if (startq_offset == -1)
3944 startq_offset = gtk_text_iter_get_offset(&iter);
3945 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
3946 if (quotelevel > 2) {
3947 /* recycle colors */
3948 if (prefs_common.recycle_quote_colors)
3957 if (startq_offset == -1)
3958 noq_offset = gtk_text_iter_get_offset(&iter);
3962 if (prev_autowrap == FALSE && !force && !wrap_quote) {
3965 if (gtk_text_iter_ends_line(&iter)) {
3967 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
3968 prefs_common.linewrap_len,
3970 GtkTextIter prev, next, cur;
3972 if (prev_autowrap != FALSE || force) {
3973 compose->automatic_break = TRUE;
3975 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
3976 compose->automatic_break = FALSE;
3977 } else if (quote_str && wrap_quote) {
3978 compose->automatic_break = TRUE;
3980 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
3981 compose->automatic_break = FALSE;
3984 /* remove trailing spaces */
3986 gtk_text_iter_backward_char(&cur);
3988 while (!gtk_text_iter_starts_line(&cur)) {
3991 gtk_text_iter_backward_char(&cur);
3992 wc = gtk_text_iter_get_char(&cur);
3993 if (!g_unichar_isspace(wc))
3997 if (!gtk_text_iter_equal(&prev, &next)) {
3998 gtk_text_buffer_delete(buffer, &prev, &next);
4000 gtk_text_iter_forward_char(&break_pos);
4004 gtk_text_buffer_insert(buffer, &break_pos,
4008 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4010 /* move iter to current line start */
4011 gtk_text_iter_set_line_offset(&iter, 0);
4018 /* move iter to next line start */
4024 if (!prev_autowrap && num_blocks > 0) {
4026 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4027 G_CALLBACK(text_inserted),
4031 while (!gtk_text_iter_ends_line(&end_of_line)) {
4032 gtk_text_iter_forward_char(&end_of_line);
4034 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4036 nouri_start = gtk_text_iter_get_offset(&iter);
4037 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4039 walk_pos = gtk_text_iter_get_offset(&iter);
4040 /* FIXME: this looks phony. scanning for anything in the parse table */
4041 for (n = 0; n < PARSE_ELEMS; n++) {
4044 tmp = parser[n].search(walk, parser[n].needle);
4046 if (scanpos == NULL || tmp < scanpos) {
4055 /* check if URI can be parsed */
4056 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4057 (const gchar **)&ep, FALSE)
4058 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4062 strlen(parser[last_index].needle);
4065 uri_start = walk_pos + (bp - o_walk);
4066 uri_stop = walk_pos + (ep - o_walk);
4070 gtk_text_iter_forward_line(&iter);
4073 if (startq_offset != -1) {
4074 GtkTextIter startquote, endquote;
4075 gtk_text_buffer_get_iter_at_offset(
4076 buffer, &startquote, startq_offset);
4079 switch (quotelevel) {
4081 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4082 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4083 gtk_text_buffer_apply_tag_by_name(
4084 buffer, "quote0", &startquote, &endquote);
4085 gtk_text_buffer_remove_tag_by_name(
4086 buffer, "quote1", &startquote, &endquote);
4087 gtk_text_buffer_remove_tag_by_name(
4088 buffer, "quote2", &startquote, &endquote);
4093 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4094 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4095 gtk_text_buffer_apply_tag_by_name(
4096 buffer, "quote1", &startquote, &endquote);
4097 gtk_text_buffer_remove_tag_by_name(
4098 buffer, "quote0", &startquote, &endquote);
4099 gtk_text_buffer_remove_tag_by_name(
4100 buffer, "quote2", &startquote, &endquote);
4105 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4106 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4107 gtk_text_buffer_apply_tag_by_name(
4108 buffer, "quote2", &startquote, &endquote);
4109 gtk_text_buffer_remove_tag_by_name(
4110 buffer, "quote0", &startquote, &endquote);
4111 gtk_text_buffer_remove_tag_by_name(
4112 buffer, "quote1", &startquote, &endquote);
4118 } else if (noq_offset != -1) {
4119 GtkTextIter startnoquote, endnoquote;
4120 gtk_text_buffer_get_iter_at_offset(
4121 buffer, &startnoquote, noq_offset);
4124 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4125 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4126 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4127 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4128 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4129 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4130 gtk_text_buffer_remove_tag_by_name(
4131 buffer, "quote0", &startnoquote, &endnoquote);
4132 gtk_text_buffer_remove_tag_by_name(
4133 buffer, "quote1", &startnoquote, &endnoquote);
4134 gtk_text_buffer_remove_tag_by_name(
4135 buffer, "quote2", &startnoquote, &endnoquote);
4141 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4142 GtkTextIter nouri_start_iter, nouri_end_iter;
4143 gtk_text_buffer_get_iter_at_offset(
4144 buffer, &nouri_start_iter, nouri_start);
4145 gtk_text_buffer_get_iter_at_offset(
4146 buffer, &nouri_end_iter, nouri_stop);
4147 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4148 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4149 gtk_text_buffer_remove_tag_by_name(
4150 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4151 modified_before_remove = modified;
4156 if (uri_start > 0 && uri_stop > 0) {
4157 GtkTextIter uri_start_iter, uri_end_iter, back;
4158 gtk_text_buffer_get_iter_at_offset(
4159 buffer, &uri_start_iter, uri_start);
4160 gtk_text_buffer_get_iter_at_offset(
4161 buffer, &uri_end_iter, uri_stop);
4162 back = uri_end_iter;
4163 gtk_text_iter_backward_char(&back);
4164 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4165 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4166 gtk_text_buffer_apply_tag_by_name(
4167 buffer, "link", &uri_start_iter, &uri_end_iter);
4169 if (removed && !modified_before_remove) {
4175 debug_print("not modified, out after %d lines\n", lines);
4180 debug_print("modified, out after %d lines\n", lines);
4184 undo_wrapping(compose->undostruct, FALSE);
4185 compose->autowrap = prev_autowrap;
4190 void compose_action_cb(void *data)
4192 Compose *compose = (Compose *)data;
4193 compose_wrap_all(compose);
4196 static void compose_wrap_all(Compose *compose)
4198 compose_wrap_all_full(compose, FALSE);
4201 static void compose_wrap_all_full(Compose *compose, gboolean force)
4203 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4204 GtkTextBuffer *buffer;
4206 gboolean modified = TRUE;
4208 buffer = gtk_text_view_get_buffer(text);
4210 gtk_text_buffer_get_start_iter(buffer, &iter);
4211 while (!gtk_text_iter_is_end(&iter) && modified)
4212 modified = compose_beautify_paragraph(compose, &iter, force);
4216 static void compose_set_title(Compose *compose)
4222 edited = compose->modified ? _(" [Edited]") : "";
4224 subject = gtk_editable_get_chars(
4225 GTK_EDITABLE(compose->subject_entry), 0, -1);
4228 if (subject && strlen(subject))
4229 str = g_strdup_printf(_("%s - Compose message%s"),
4232 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4234 str = g_strdup(_("Compose message"));
4237 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4243 * compose_current_mail_account:
4245 * Find a current mail account (the currently selected account, or the
4246 * default account, if a news account is currently selected). If a
4247 * mail account cannot be found, display an error message.
4249 * Return value: Mail account, or NULL if not found.
4251 static PrefsAccount *
4252 compose_current_mail_account(void)
4256 if (cur_account && cur_account->protocol != A_NNTP)
4259 ac = account_get_default();
4260 if (!ac || ac->protocol == A_NNTP) {
4261 alertpanel_error(_("Account for sending mail is not specified.\n"
4262 "Please select a mail account before sending."));
4269 #define QUOTE_IF_REQUIRED(out, str) \
4271 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4275 len = strlen(str) + 3; \
4276 if ((__tmp = alloca(len)) == NULL) { \
4277 g_warning("can't allocate memory\n"); \
4278 g_string_free(header, TRUE); \
4281 g_snprintf(__tmp, len, "\"%s\"", str); \
4286 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4287 g_warning("can't allocate memory\n"); \
4288 g_string_free(header, TRUE); \
4291 strcpy(__tmp, str); \
4297 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4299 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4303 len = strlen(str) + 3; \
4304 if ((__tmp = alloca(len)) == NULL) { \
4305 g_warning("can't allocate memory\n"); \
4308 g_snprintf(__tmp, len, "\"%s\"", str); \
4313 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4314 g_warning("can't allocate memory\n"); \
4317 strcpy(__tmp, str); \
4323 static void compose_select_account(Compose *compose, PrefsAccount *account,
4326 GtkItemFactory *ifactory;
4329 g_return_if_fail(account != NULL);
4331 compose->account = account;
4333 if (account->name && *account->name) {
4335 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4336 from = g_strdup_printf("%s <%s>",
4337 buf, account->address);
4338 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4340 from = g_strdup_printf("<%s>",
4342 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4347 compose_set_title(compose);
4349 ifactory = gtk_item_factory_from_widget(compose->menubar);
4351 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4352 menu_set_active(ifactory, "/Options/Sign", TRUE);
4354 menu_set_active(ifactory, "/Options/Sign", FALSE);
4355 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4356 menu_set_active(ifactory, "/Options/Encrypt", TRUE);
4358 menu_set_active(ifactory, "/Options/Encrypt", FALSE);
4360 activate_privacy_system(compose, account, FALSE);
4362 if (!init && compose->mode != COMPOSE_REDIRECT) {
4363 undo_block(compose->undostruct);
4364 compose_insert_sig(compose, TRUE);
4365 undo_unblock(compose->undostruct);
4369 /* use account's dict info if set */
4370 if (compose->gtkaspell) {
4371 if (account->enable_default_dictionary)
4372 gtkaspell_change_dict(compose->gtkaspell,
4373 account->default_dictionary, FALSE);
4374 if (account->enable_default_alt_dictionary)
4375 gtkaspell_change_alt_dict(compose->gtkaspell,
4376 account->default_alt_dictionary);
4377 if (account->enable_default_dictionary
4378 || account->enable_default_alt_dictionary)
4379 compose_spell_menu_changed(compose);
4384 gboolean compose_check_for_valid_recipient(Compose *compose) {
4385 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
4386 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
4387 gboolean recipient_found = FALSE;
4391 /* free to and newsgroup list */
4392 slist_free_strings(compose->to_list);
4393 g_slist_free(compose->to_list);
4394 compose->to_list = NULL;
4396 slist_free_strings(compose->newsgroup_list);
4397 g_slist_free(compose->newsgroup_list);
4398 compose->newsgroup_list = NULL;
4400 /* search header entries for to and newsgroup entries */
4401 for (list = compose->header_list; list; list = list->next) {
4404 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_BIN(((ComposeHeaderEntry *)list->data)->combo)->child), 0, -1);
4405 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4408 if (entry[0] != '\0') {
4409 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
4410 if (!strcmp(header, prefs_common_translated_header_name(*strptr))) {
4411 compose->to_list = address_list_append(compose->to_list, entry);
4412 recipient_found = TRUE;
4415 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
4416 if (!strcmp(header, prefs_common_translated_header_name(*strptr))) {
4417 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
4418 recipient_found = TRUE;
4425 return recipient_found;
4428 static gboolean compose_check_for_set_recipients(Compose *compose)
4430 if (compose->account->set_autocc && compose->account->auto_cc) {
4431 gboolean found_other = FALSE;
4433 /* search header entries for to and newsgroup entries */
4434 for (list = compose->header_list; list; list = list->next) {
4437 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4438 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_BIN(((ComposeHeaderEntry *)list->data)->combo)->child), 0, -1);
4440 if (strcmp(entry, compose->account->auto_cc)
4441 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
4451 if (compose->batch) {
4452 gtk_widget_show_all(compose->window);
4454 aval = alertpanel(_("Send"),
4455 _("The only recipient is the default CC address. Send anyway?"),
4456 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4457 if (aval != G_ALERTALTERNATE)
4461 if (compose->account->set_autobcc && compose->account->auto_bcc) {
4462 gboolean found_other = FALSE;
4464 /* search header entries for to and newsgroup entries */
4465 for (list = compose->header_list; list; list = list->next) {
4468 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4469 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_BIN(((ComposeHeaderEntry *)list->data)->combo)->child), 0, -1);
4471 if (strcmp(entry, compose->account->auto_bcc)
4472 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
4482 if (compose->batch) {
4483 gtk_widget_show_all(compose->window);
4485 aval = alertpanel(_("Send"),
4486 _("The only recipient is the default BCC address. Send anyway?"),
4487 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4488 if (aval != G_ALERTALTERNATE)
4495 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
4499 if (compose_check_for_valid_recipient(compose) == FALSE) {
4500 if (compose->batch) {
4501 gtk_widget_show_all(compose->window);
4503 alertpanel_error(_("Recipient is not specified."));
4507 if (compose_check_for_set_recipients(compose) == FALSE) {
4511 if (!compose->batch) {
4512 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
4513 if (*str == '\0' && check_everything == TRUE &&
4514 compose->mode != COMPOSE_REDIRECT) {
4516 gchar *button_label;
4519 if (compose->sending)
4520 button_label = _("+_Send");
4522 button_label = _("+_Queue");
4523 message = g_strdup_printf(_("Subject is empty. %s it anyway?"),
4524 compose->sending?_("Send"):_("Queue"));
4526 aval = alertpanel(compose->sending?_("Send"):_("Send later"), message,
4527 GTK_STOCK_CANCEL, button_label, NULL);
4529 if (aval != G_ALERTALTERNATE)
4534 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
4540 gint compose_send(Compose *compose)
4543 FolderItem *folder = NULL;
4545 gchar *msgpath = NULL;
4546 gboolean discard_window = FALSE;
4547 gchar *errstr = NULL;
4548 gchar *tmsgid = NULL;
4549 MainWindow *mainwin = mainwindow_get_mainwindow();
4550 gboolean queued_removed = FALSE;
4552 if (prefs_common.send_dialog_invisible
4553 || compose->batch == TRUE)
4554 discard_window = TRUE;
4556 compose_allow_user_actions (compose, FALSE);
4557 compose->sending = TRUE;
4559 if (compose_check_entries(compose, TRUE) == FALSE) {
4560 if (compose->batch) {
4561 gtk_widget_show_all(compose->window);
4567 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
4570 if (compose->batch) {
4571 gtk_widget_show_all(compose->window);
4574 alertpanel_error(_("Could not queue message for sending:\n\n"
4575 "Charset conversion failed."));
4576 } else if (val == -5) {
4577 alertpanel_error(_("Could not queue message for sending:\n\n"
4578 "Couldn't get recipient encryption key."));
4579 } else if (val == -6) {
4581 } else if (val == -3) {
4582 if (privacy_peek_error())
4583 alertpanel_error(_("Could not queue message for sending:\n\n"
4584 "Signature failed: %s"), privacy_get_error());
4585 } else if (val == -2 && errno != 0) {
4586 alertpanel_error(_("Could not queue message for sending:\n\n%s."), strerror(errno));
4588 alertpanel_error(_("Could not queue message for sending."));
4593 tmsgid = g_strdup(compose->msgid);
4594 if (discard_window) {
4595 compose->sending = FALSE;
4596 compose_close(compose);
4597 /* No more compose access in the normal codepath
4598 * after this point! */
4603 alertpanel_error(_("The message was queued but could not be "
4604 "sent.\nUse \"Send queued messages\" from "
4605 "the main window to retry."));
4606 if (!discard_window) {
4613 if (msgpath == NULL) {
4614 msgpath = folder_item_fetch_msg(folder, msgnum);
4615 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
4618 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
4622 if (!discard_window) {
4624 if (!queued_removed)
4625 folder_item_remove_msg(folder, msgnum);
4626 folder_item_scan(folder);
4628 /* make sure we delete that */
4629 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
4631 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
4632 folder_item_remove_msg(folder, tmp->msgnum);
4633 procmsg_msginfo_free(tmp);
4640 if (!queued_removed)
4641 folder_item_remove_msg(folder, msgnum);
4642 folder_item_scan(folder);
4644 /* make sure we delete that */
4645 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
4647 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
4648 folder_item_remove_msg(folder, tmp->msgnum);
4649 procmsg_msginfo_free(tmp);
4652 if (!discard_window) {
4653 compose->sending = FALSE;
4654 compose_allow_user_actions (compose, TRUE);
4655 compose_close(compose);
4659 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
4660 "the main window to retry."), errstr);
4663 alertpanel_error_log(_("The message was queued but could not be "
4664 "sent.\nUse \"Send queued messages\" from "
4665 "the main window to retry."));
4667 if (!discard_window) {
4676 toolbar_main_set_sensitive(mainwin);
4677 main_window_set_menu_sensitive(mainwin);
4683 compose_allow_user_actions (compose, TRUE);
4684 compose->sending = FALSE;
4685 compose->modified = TRUE;
4686 toolbar_main_set_sensitive(mainwin);
4687 main_window_set_menu_sensitive(mainwin);
4692 static gboolean compose_use_attach(Compose *compose)
4694 GtkTreeModel *model = gtk_tree_view_get_model
4695 (GTK_TREE_VIEW(compose->attach_clist));
4696 return gtk_tree_model_iter_n_children(model, NULL) > 0;
4699 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
4702 gchar buf[BUFFSIZE];
4704 gboolean first_to_address;
4705 gboolean first_cc_address;
4707 ComposeHeaderEntry *headerentry;
4708 const gchar *headerentryname;
4709 const gchar *cc_hdr;
4710 const gchar *to_hdr;
4711 gboolean err = FALSE;
4713 debug_print("Writing redirect header\n");
4715 cc_hdr = prefs_common_translated_header_name("Cc:");
4716 to_hdr = prefs_common_translated_header_name("To:");
4718 first_to_address = TRUE;
4719 for (list = compose->header_list; list; list = list->next) {
4720 headerentry = ((ComposeHeaderEntry *)list->data);
4721 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_BIN(headerentry->combo)->child));
4723 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
4724 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
4725 Xstrdup_a(str, entstr, return -1);
4727 if (str[0] != '\0') {
4728 compose_convert_header
4729 (compose, buf, sizeof(buf), str,
4730 strlen("Resent-To") + 2, TRUE);
4732 if (first_to_address) {
4733 err |= (fprintf(fp, "Resent-To: ") < 0);
4734 first_to_address = FALSE;
4736 err |= (fprintf(fp, ",") < 0);
4738 err |= (fprintf(fp, "%s", buf) < 0);
4742 if (!first_to_address) {
4743 err |= (fprintf(fp, "\n") < 0);
4746 first_cc_address = TRUE;
4747 for (list = compose->header_list; list; list = list->next) {
4748 headerentry = ((ComposeHeaderEntry *)list->data);
4749 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_BIN(headerentry->combo)->child));
4751 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
4752 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
4753 Xstrdup_a(str, strg, return -1);
4755 if (str[0] != '\0') {
4756 compose_convert_header
4757 (compose, buf, sizeof(buf), str,
4758 strlen("Resent-Cc") + 2, TRUE);
4760 if (first_cc_address) {
4761 err |= (fprintf(fp, "Resent-Cc: ") < 0);
4762 first_cc_address = FALSE;
4764 err |= (fprintf(fp, ",") < 0);
4766 err |= (fprintf(fp, "%s", buf) < 0);
4770 if (!first_cc_address) {
4771 err |= (fprintf(fp, "\n") < 0);
4774 return (err ? -1:0);
4777 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
4779 gchar buf[BUFFSIZE];
4781 const gchar *entstr;
4782 /* struct utsname utsbuf; */
4783 gboolean err = FALSE;
4785 g_return_val_if_fail(fp != NULL, -1);
4786 g_return_val_if_fail(compose->account != NULL, -1);
4787 g_return_val_if_fail(compose->account->address != NULL, -1);
4790 get_rfc822_date(buf, sizeof(buf));
4791 err |= (fprintf(fp, "Resent-Date: %s\n", buf) < 0);
4794 if (compose->account->name && *compose->account->name) {
4795 compose_convert_header
4796 (compose, buf, sizeof(buf), compose->account->name,
4797 strlen("From: "), TRUE);
4798 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
4799 buf, compose->account->address) < 0);
4801 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
4804 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
4805 if (*entstr != '\0') {
4806 Xstrdup_a(str, entstr, return -1);
4809 compose_convert_header(compose, buf, sizeof(buf), str,
4810 strlen("Subject: "), FALSE);
4811 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
4815 /* Resent-Message-ID */
4816 if (compose->account->set_domain && compose->account->domain) {
4817 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
4818 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
4819 g_snprintf(buf, sizeof(buf), "%s",
4820 strchr(compose->account->address, '@') ?
4821 strchr(compose->account->address, '@')+1 :
4822 compose->account->address);
4824 g_snprintf(buf, sizeof(buf), "%s", "");
4826 generate_msgid(buf, sizeof(buf));
4827 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", buf) < 0);
4828 compose->msgid = g_strdup(buf);
4830 if (compose_redirect_write_headers_from_headerlist(compose, fp))
4833 /* separator between header and body */
4834 err |= (fputs("\n", fp) == EOF);
4836 return (err ? -1:0);
4839 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
4843 gchar buf[BUFFSIZE];
4845 gboolean skip = FALSE;
4846 gboolean err = FALSE;
4847 gchar *not_included[]={
4848 "Return-Path:", "Delivered-To:", "Received:",
4849 "Subject:", "X-UIDL:", "AF:",
4850 "NF:", "PS:", "SRH:",
4851 "SFN:", "DSR:", "MID:",
4852 "CFG:", "PT:", "S:",
4853 "RQ:", "SSV:", "NSV:",
4854 "SSH:", "R:", "MAID:",
4855 "NAID:", "RMID:", "FMID:",
4856 "SCF:", "RRCPT:", "NG:",
4857 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
4858 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
4859 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
4860 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
4863 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
4864 FILE_OP_ERROR(compose->redirect_filename, "fopen");
4868 while (procheader_get_one_field_asis(buf, sizeof(buf), fp) != -1) {
4870 for (i = 0; not_included[i] != NULL; i++) {
4871 if (g_ascii_strncasecmp(buf, not_included[i],
4872 strlen(not_included[i])) == 0) {
4879 if (fputs(buf, fdest) == -1)
4882 if (!prefs_common.redirect_keep_from) {
4883 if (g_ascii_strncasecmp(buf, "From:",
4884 strlen("From:")) == 0) {
4885 err |= (fputs(" (by way of ", fdest) == EOF);
4886 if (compose->account->name
4887 && *compose->account->name) {
4888 compose_convert_header
4889 (compose, buf, sizeof(buf),
4890 compose->account->name,
4893 err |= (fprintf(fdest, "%s <%s>",
4895 compose->account->address) < 0);
4897 err |= (fprintf(fdest, "%s",
4898 compose->account->address) < 0);
4899 err |= (fputs(")", fdest) == EOF);
4903 if (fputs("\n", fdest) == -1)
4910 if (compose_redirect_write_headers(compose, fdest))
4913 while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
4914 if (fwrite(buf, sizeof(gchar), len, fdest) != len)
4927 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
4929 GtkTextBuffer *buffer;
4930 GtkTextIter start, end;
4933 const gchar *out_codeset;
4934 EncodingType encoding;
4935 MimeInfo *mimemsg, *mimetext;
4938 if (action == COMPOSE_WRITE_FOR_SEND)
4939 attach_parts = TRUE;
4941 /* create message MimeInfo */
4942 mimemsg = procmime_mimeinfo_new();
4943 mimemsg->type = MIMETYPE_MESSAGE;
4944 mimemsg->subtype = g_strdup("rfc822");
4945 mimemsg->content = MIMECONTENT_MEM;
4946 mimemsg->tmp = TRUE; /* must free content later */
4947 mimemsg->data.mem = compose_get_header(compose);
4949 /* Create text part MimeInfo */
4950 /* get all composed text */
4951 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
4952 gtk_text_buffer_get_start_iter(buffer, &start);
4953 gtk_text_buffer_get_end_iter(buffer, &end);
4954 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
4955 if (is_ascii_str(chars)) {
4958 out_codeset = CS_US_ASCII;
4959 encoding = ENC_7BIT;
4961 const gchar *src_codeset = CS_INTERNAL;
4963 out_codeset = conv_get_charset_str(compose->out_encoding);
4966 gchar *test_conv_global_out = NULL;
4967 gchar *test_conv_reply = NULL;
4969 /* automatic mode. be automatic. */
4970 codeconv_set_strict(TRUE);
4972 out_codeset = conv_get_outgoing_charset_str();
4974 debug_print("trying to convert to %s\n", out_codeset);
4975 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
4978 if (!test_conv_global_out && compose->orig_charset
4979 && strcmp(compose->orig_charset, CS_US_ASCII)) {
4980 out_codeset = compose->orig_charset;
4981 debug_print("failure; trying to convert to %s\n", out_codeset);
4982 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
4985 if (!test_conv_global_out && !test_conv_reply) {
4987 out_codeset = CS_INTERNAL;
4988 debug_print("failure; finally using %s\n", out_codeset);
4990 g_free(test_conv_global_out);
4991 g_free(test_conv_reply);
4992 codeconv_set_strict(FALSE);
4995 if (!g_ascii_strcasecmp(out_codeset, CS_US_ASCII))
4996 out_codeset = CS_ISO_8859_1;
4998 if (prefs_common.encoding_method == CTE_BASE64)
4999 encoding = ENC_BASE64;
5000 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5001 encoding = ENC_QUOTED_PRINTABLE;
5002 else if (prefs_common.encoding_method == CTE_8BIT)
5003 encoding = ENC_8BIT;
5005 encoding = procmime_get_encoding_for_charset(out_codeset);
5007 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5008 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5010 if (action == COMPOSE_WRITE_FOR_SEND) {
5011 codeconv_set_strict(TRUE);
5012 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5013 codeconv_set_strict(FALSE);
5019 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5020 "to the specified %s charset.\n"
5021 "Send it as %s?"), out_codeset, src_codeset);
5022 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL, _("+_Send"), NULL, FALSE,
5023 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5026 if (aval != G_ALERTALTERNATE) {
5031 out_codeset = src_codeset;
5037 out_codeset = src_codeset;
5043 if (encoding == ENC_8BIT || encoding == ENC_7BIT) {
5044 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5045 strstr(buf, "\nFrom ") != NULL) {
5046 encoding = ENC_QUOTED_PRINTABLE;
5050 mimetext = procmime_mimeinfo_new();
5051 mimetext->content = MIMECONTENT_MEM;
5052 mimetext->tmp = TRUE; /* must free content later */
5053 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5054 * and free the data, which we need later. */
5055 mimetext->data.mem = g_strdup(buf);
5056 mimetext->type = MIMETYPE_TEXT;
5057 mimetext->subtype = g_strdup("plain");
5058 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5059 g_strdup(out_codeset));
5061 /* protect trailing spaces when signing message */
5062 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5063 privacy_system_can_sign(compose->privacy_system)) {
5064 encoding = ENC_QUOTED_PRINTABLE;
5067 debug_print("main text: %zd bytes encoded as %s in %d\n",
5068 strlen(buf), out_codeset, encoding);
5070 /* check for line length limit */
5071 if (action == COMPOSE_WRITE_FOR_SEND &&
5072 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5073 check_line_length(buf, 1000, &line) < 0) {
5077 msg = g_strdup_printf
5078 (_("Line %d exceeds the line length limit (998 bytes).\n"
5079 "The contents of the message might be broken on the way to the delivery.\n"
5081 "Send it anyway?"), line + 1);
5082 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5084 if (aval != G_ALERTALTERNATE) {
5090 if (encoding != ENC_UNKNOWN)
5091 procmime_encode_content(mimetext, encoding);
5093 /* append attachment parts */
5094 if (compose_use_attach(compose) && attach_parts) {
5095 MimeInfo *mimempart;
5096 gchar *boundary = NULL;
5097 mimempart = procmime_mimeinfo_new();
5098 mimempart->content = MIMECONTENT_EMPTY;
5099 mimempart->type = MIMETYPE_MULTIPART;
5100 mimempart->subtype = g_strdup("mixed");
5104 boundary = generate_mime_boundary(NULL);
5105 } while (strstr(buf, boundary) != NULL);
5107 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5110 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5112 g_node_append(mimempart->node, mimetext->node);
5113 g_node_append(mimemsg->node, mimempart->node);
5115 compose_add_attachments(compose, mimempart);
5117 g_node_append(mimemsg->node, mimetext->node);
5121 /* sign message if sending */
5122 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5123 privacy_system_can_sign(compose->privacy_system))
5124 if (!privacy_sign(compose->privacy_system, mimemsg, compose->account))
5127 procmime_write_mimeinfo(mimemsg, fp);
5129 procmime_mimeinfo_free_all(mimemsg);
5134 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5136 GtkTextBuffer *buffer;
5137 GtkTextIter start, end;
5142 if ((fp = g_fopen(file, "wb")) == NULL) {
5143 FILE_OP_ERROR(file, "fopen");
5147 /* chmod for security */
5148 if (change_file_mode_rw(fp, file) < 0) {
5149 FILE_OP_ERROR(file, "chmod");
5150 g_warning("can't change file mode\n");
5153 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5154 gtk_text_buffer_get_start_iter(buffer, &start);
5155 gtk_text_buffer_get_end_iter(buffer, &end);
5156 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5158 chars = conv_codeset_strdup
5159 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5162 if (!chars) return -1;
5165 len = strlen(chars);
5166 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5167 FILE_OP_ERROR(file, "fwrite");
5176 if (fclose(fp) == EOF) {
5177 FILE_OP_ERROR(file, "fclose");
5184 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5187 MsgInfo *msginfo = compose->targetinfo;
5189 g_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5190 if (!msginfo) return -1;
5192 if (!force && MSG_IS_LOCKED(msginfo->flags))
5195 item = msginfo->folder;
5196 g_return_val_if_fail(item != NULL, -1);
5198 if (procmsg_msg_exist(msginfo) &&
5199 (folder_has_parent_of_type(item, F_QUEUE) ||
5200 folder_has_parent_of_type(item, F_DRAFT)
5201 || msginfo == compose->autosaved_draft)) {
5202 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5203 g_warning("can't remove the old message\n");
5206 debug_print("removed reedit target %d\n", msginfo->msgnum);
5213 static void compose_remove_draft(Compose *compose)
5216 MsgInfo *msginfo = compose->targetinfo;
5217 drafts = account_get_special_folder(compose->account, F_DRAFT);
5219 if (procmsg_msg_exist(msginfo)) {
5220 folder_item_remove_msg(drafts, msginfo->msgnum);
5225 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5226 gboolean remove_reedit_target)
5228 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5231 static gboolean compose_warn_encryption(Compose *compose)
5233 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5234 AlertValue val = G_ALERTALTERNATE;
5236 if (warning == NULL)
5239 val = alertpanel_full(_("Encryption warning"), warning,
5240 GTK_STOCK_CANCEL, _("+C_ontinue"), NULL,
5241 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5242 if (val & G_ALERTDISABLE) {
5243 val &= ~G_ALERTDISABLE;
5244 if (val == G_ALERTALTERNATE)
5245 privacy_inhibit_encrypt_warning(compose->privacy_system,
5249 if (val == G_ALERTALTERNATE) {
5256 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
5257 gchar **msgpath, gboolean check_subject,
5258 gboolean remove_reedit_target)
5265 static gboolean lock = FALSE;
5266 PrefsAccount *mailac = NULL, *newsac = NULL;
5267 gboolean err = FALSE;
5269 debug_print("queueing message...\n");
5270 g_return_val_if_fail(compose->account != NULL, -1);
5274 if (compose_check_entries(compose, check_subject) == FALSE) {
5276 if (compose->batch) {
5277 gtk_widget_show_all(compose->window);
5282 if (!compose->to_list && !compose->newsgroup_list) {
5283 g_warning("can't get recipient list.");
5288 if (compose->to_list) {
5289 if (compose->account->protocol != A_NNTP)
5290 mailac = compose->account;
5291 else if (cur_account && cur_account->protocol != A_NNTP)
5292 mailac = cur_account;
5293 else if (!(mailac = compose_current_mail_account())) {
5295 alertpanel_error(_("No account for sending mails available!"));
5300 if (compose->newsgroup_list) {
5301 if (compose->account->protocol == A_NNTP)
5302 newsac = compose->account;
5303 else if (!newsac->protocol != A_NNTP) {
5305 alertpanel_error(_("No account for posting news available!"));
5310 /* write queue header */
5311 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
5312 G_DIR_SEPARATOR, compose, (guint) rand());
5313 debug_print("queuing to %s\n", tmp);
5314 if ((fp = g_fopen(tmp, "wb")) == NULL) {
5315 FILE_OP_ERROR(tmp, "fopen");
5321 if (change_file_mode_rw(fp, tmp) < 0) {
5322 FILE_OP_ERROR(tmp, "chmod");
5323 g_warning("can't change file mode\n");
5326 /* queueing variables */
5327 err |= (fprintf(fp, "AF:\n") < 0);
5328 err |= (fprintf(fp, "NF:0\n") < 0);
5329 err |= (fprintf(fp, "PS:10\n") < 0);
5330 err |= (fprintf(fp, "SRH:1\n") < 0);
5331 err |= (fprintf(fp, "SFN:\n") < 0);
5332 err |= (fprintf(fp, "DSR:\n") < 0);
5334 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
5336 err |= (fprintf(fp, "MID:\n") < 0);
5337 err |= (fprintf(fp, "CFG:\n") < 0);
5338 err |= (fprintf(fp, "PT:0\n") < 0);
5339 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
5340 err |= (fprintf(fp, "RQ:\n") < 0);
5342 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
5344 err |= (fprintf(fp, "SSV:\n") < 0);
5346 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
5348 err |= (fprintf(fp, "NSV:\n") < 0);
5349 err |= (fprintf(fp, "SSH:\n") < 0);
5350 /* write recepient list */
5351 if (compose->to_list) {
5352 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
5353 for (cur = compose->to_list->next; cur != NULL;
5355 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
5356 err |= (fprintf(fp, "\n") < 0);
5358 /* write newsgroup list */
5359 if (compose->newsgroup_list) {
5360 err |= (fprintf(fp, "NG:") < 0);
5361 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
5362 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
5363 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
5364 err |= (fprintf(fp, "\n") < 0);
5366 /* Sylpheed account IDs */
5368 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
5370 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
5373 if (compose->privacy_system != NULL) {
5374 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
5375 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
5376 if (compose->use_encryption) {
5378 if (!compose_warn_encryption(compose)) {
5385 if (mailac && mailac->encrypt_to_self) {
5386 GSList *tmp_list = g_slist_copy(compose->to_list);
5387 tmp_list = g_slist_append(tmp_list, compose->account->address);
5388 encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
5389 g_slist_free(tmp_list);
5391 encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
5393 if (encdata != NULL) {
5394 if (strcmp(encdata, "_DONT_ENCRYPT_")) {
5395 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5396 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
5398 } /* else we finally dont want to encrypt */
5400 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5401 /* and if encdata was null, it means there's been a problem in
5413 /* Save copy folder */
5414 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
5415 gchar *savefolderid;
5417 savefolderid = gtk_editable_get_chars(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
5418 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
5419 g_free(savefolderid);
5421 /* Save copy folder */
5422 if (compose->return_receipt) {
5423 err |= (fprintf(fp, "RRCPT:1\n") < 0);
5425 /* Message-ID of message replying to */
5426 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
5429 folderid = folder_item_get_identifier(compose->replyinfo->folder);
5430 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
5433 /* Message-ID of message forwarding to */
5434 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
5437 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
5438 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
5442 /* end of headers */
5443 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
5445 if (compose->redirect_filename != NULL) {
5446 if (compose_redirect_write_to_file(compose, fp) < 0) {
5455 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
5460 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
5464 g_warning("failed to write queue message\n");
5471 if (fclose(fp) == EOF) {
5472 FILE_OP_ERROR(tmp, "fclose");
5479 if (item && *item) {
5482 queue = account_get_special_folder(compose->account, F_QUEUE);
5485 g_warning("can't find queue folder\n");
5491 folder_item_scan(queue);
5492 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
5493 g_warning("can't queue the message\n");
5500 if (msgpath == NULL) {
5506 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
5507 compose_remove_reedit_target(compose, FALSE);
5510 if ((msgnum != NULL) && (item != NULL)) {
5518 static void compose_add_attachments(Compose *compose, MimeInfo *parent)
5521 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
5523 struct stat statbuf;
5524 gchar *type, *subtype;
5525 GtkTreeModel *model;
5528 model = gtk_tree_view_get_model(tree_view);
5530 if (!gtk_tree_model_get_iter_first(model, &iter))
5533 gtk_tree_model_get(model, &iter,
5537 mimepart = procmime_mimeinfo_new();
5538 mimepart->content = MIMECONTENT_FILE;
5539 mimepart->data.filename = g_strdup(ainfo->file);
5540 mimepart->tmp = FALSE; /* or we destroy our attachment */
5541 mimepart->offset = 0;
5543 stat(ainfo->file, &statbuf);
5544 mimepart->length = statbuf.st_size;
5546 type = g_strdup(ainfo->content_type);
5548 if (!strchr(type, '/')) {
5550 type = g_strdup("application/octet-stream");
5553 subtype = strchr(type, '/') + 1;
5554 *(subtype - 1) = '\0';
5555 mimepart->type = procmime_get_media_type(type);
5556 mimepart->subtype = g_strdup(subtype);
5559 if (mimepart->type == MIMETYPE_MESSAGE &&
5560 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
5561 mimepart->disposition = DISPOSITIONTYPE_INLINE;
5564 g_hash_table_insert(mimepart->typeparameters,
5565 g_strdup("name"), g_strdup(ainfo->name));
5566 g_hash_table_insert(mimepart->dispositionparameters,
5567 g_strdup("filename"), g_strdup(ainfo->name));
5568 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
5572 if (compose->use_signing) {
5573 if (ainfo->encoding == ENC_7BIT)
5574 ainfo->encoding = ENC_QUOTED_PRINTABLE;
5575 else if (ainfo->encoding == ENC_8BIT)
5576 ainfo->encoding = ENC_BASE64;
5579 procmime_encode_content(mimepart, ainfo->encoding);
5581 g_node_append(parent->node, mimepart->node);
5582 } while (gtk_tree_model_iter_next(model, &iter));
5585 #define IS_IN_CUSTOM_HEADER(header) \
5586 (compose->account->add_customhdr && \
5587 custom_header_find(compose->account->customhdr_list, header) != NULL)
5589 static void compose_add_headerfield_from_headerlist(Compose *compose,
5591 const gchar *fieldname,
5592 const gchar *seperator)
5594 gchar *str, *fieldname_w_colon;
5595 gboolean add_field = FALSE;
5597 ComposeHeaderEntry *headerentry;
5598 const gchar *headerentryname;
5599 const gchar *trans_fieldname;
5602 if (IS_IN_CUSTOM_HEADER(fieldname))
5605 debug_print("Adding %s-fields\n", fieldname);
5607 fieldstr = g_string_sized_new(64);
5609 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
5610 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
5612 for (list = compose->header_list; list; list = list->next) {
5613 headerentry = ((ComposeHeaderEntry *)list->data);
5614 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_BIN(headerentry->combo)->child));
5616 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
5617 str = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
5619 if (str[0] != '\0') {
5621 g_string_append(fieldstr, seperator);
5622 g_string_append(fieldstr, str);
5631 buf = g_new0(gchar, fieldstr->len * 4 + 256);
5632 compose_convert_header
5633 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
5634 strlen(fieldname) + 2, TRUE);
5635 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
5639 g_free(fieldname_w_colon);
5640 g_string_free(fieldstr, TRUE);
5645 static gchar *compose_get_header(Compose *compose)
5647 gchar buf[BUFFSIZE];
5648 const gchar *entry_str;
5652 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
5654 gchar *from_name = NULL, *from_address = NULL;
5657 g_return_val_if_fail(compose->account != NULL, NULL);
5658 g_return_val_if_fail(compose->account->address != NULL, NULL);
5660 header = g_string_sized_new(64);
5663 get_rfc822_date(buf, sizeof(buf));
5664 g_string_append_printf(header, "Date: %s\n", buf);
5668 if (compose->account->name && *compose->account->name) {
5670 QUOTE_IF_REQUIRED(buf, compose->account->name);
5671 tmp = g_strdup_printf("%s <%s>",
5672 buf, compose->account->address);
5674 tmp = g_strdup_printf("%s",
5675 compose->account->address);
5677 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
5678 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
5680 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
5681 from_address = g_strdup(compose->account->address);
5683 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5684 /* extract name and address */
5685 if (strstr(spec, " <") && strstr(spec, ">")) {
5686 from_address = g_strdup(strrchr(spec, '<')+1);
5687 *(strrchr(from_address, '>')) = '\0';
5688 from_name = g_strdup(spec);
5689 *(strrchr(from_name, '<')) = '\0';
5692 from_address = g_strdup(spec);
5699 if (from_name && *from_name) {
5700 compose_convert_header
5701 (compose, buf, sizeof(buf), from_name,
5702 strlen("From: "), TRUE);
5703 QUOTE_IF_REQUIRED(name, buf);
5705 g_string_append_printf(header, "From: %s <%s>\n",
5706 name, from_address);
5708 g_string_append_printf(header, "From: %s\n", from_address);
5711 g_free(from_address);
5714 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
5717 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
5720 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
5723 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
5726 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
5728 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
5731 compose_convert_header(compose, buf, sizeof(buf), str,
5732 strlen("Subject: "), FALSE);
5733 g_string_append_printf(header, "Subject: %s\n", buf);
5739 if (compose->account->set_domain && compose->account->domain) {
5740 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
5741 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
5742 g_snprintf(buf, sizeof(buf), "%s",
5743 strchr(compose->account->address, '@') ?
5744 strchr(compose->account->address, '@')+1 :
5745 compose->account->address);
5747 g_snprintf(buf, sizeof(buf), "%s", "");
5749 generate_msgid(buf, sizeof(buf));
5750 g_string_append_printf(header, "Message-ID: <%s>\n", buf);
5751 compose->msgid = g_strdup(buf);
5753 if (compose->remove_references == FALSE) {
5755 if (compose->inreplyto && compose->to_list)
5756 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
5759 if (compose->references)
5760 g_string_append_printf(header, "References: %s\n", compose->references);
5764 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
5767 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
5770 if (compose->account->organization &&
5771 strlen(compose->account->organization) &&
5772 !IS_IN_CUSTOM_HEADER("Organization")) {
5773 compose_convert_header(compose, buf, sizeof(buf),
5774 compose->account->organization,
5775 strlen("Organization: "), FALSE);
5776 g_string_append_printf(header, "Organization: %s\n", buf);
5779 /* Program version and system info */
5780 if (g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
5781 !compose->newsgroup_list) {
5782 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
5784 gtk_major_version, gtk_minor_version, gtk_micro_version,
5787 if (g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
5788 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
5790 gtk_major_version, gtk_minor_version, gtk_micro_version,
5794 /* custom headers */
5795 if (compose->account->add_customhdr) {
5798 for (cur = compose->account->customhdr_list; cur != NULL;
5800 CustomHeader *chdr = (CustomHeader *)cur->data;
5802 if (custom_header_is_allowed(chdr->name)) {
5803 compose_convert_header
5804 (compose, buf, sizeof(buf),
5805 chdr->value ? chdr->value : "",
5806 strlen(chdr->name) + 2, FALSE);
5807 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
5813 switch (compose->priority) {
5814 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
5815 "X-Priority: 1 (Highest)\n");
5817 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
5818 "X-Priority: 2 (High)\n");
5820 case PRIORITY_NORMAL: break;
5821 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
5822 "X-Priority: 4 (Low)\n");
5824 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
5825 "X-Priority: 5 (Lowest)\n");
5827 default: debug_print("compose: priority unknown : %d\n",
5831 /* Request Return Receipt */
5832 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To")) {
5833 if (compose->return_receipt) {
5834 if (compose->account->name
5835 && *compose->account->name) {
5836 compose_convert_header(compose, buf, sizeof(buf),
5837 compose->account->name,
5838 strlen("Disposition-Notification-To: "),
5840 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, compose->account->address);
5842 g_string_append_printf(header, "Disposition-Notification-To: %s\n", compose->account->address);
5846 /* get special headers */
5847 for (list = compose->header_list; list; list = list->next) {
5848 ComposeHeaderEntry *headerentry;
5851 gchar *headername_wcolon;
5852 const gchar *headername_trans;
5855 gboolean standard_header = FALSE;
5857 headerentry = ((ComposeHeaderEntry *)list->data);
5859 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(GTK_BIN(headerentry->combo)->child)));
5860 if (strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
5865 if (!strstr(tmp, ":")) {
5866 headername_wcolon = g_strconcat(tmp, ":", NULL);
5867 headername = g_strdup(tmp);
5869 headername_wcolon = g_strdup(tmp);
5870 headername = g_strdup(strtok(tmp, ":"));
5874 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5875 Xstrdup_a(headervalue, entry_str, return NULL);
5876 subst_char(headervalue, '\r', ' ');
5877 subst_char(headervalue, '\n', ' ');
5878 string = std_headers;
5879 while (*string != NULL) {
5880 headername_trans = prefs_common_translated_header_name(*string);
5881 if (!strcmp(headername_trans, headername_wcolon))
5882 standard_header = TRUE;
5885 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
5886 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
5889 g_free(headername_wcolon);
5893 g_string_free(header, FALSE);
5898 #undef IS_IN_CUSTOM_HEADER
5900 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
5901 gint header_len, gboolean addr_field)
5903 gchar *tmpstr = NULL;
5904 const gchar *out_codeset = NULL;
5906 g_return_if_fail(src != NULL);
5907 g_return_if_fail(dest != NULL);
5909 if (len < 1) return;
5911 tmpstr = g_strdup(src);
5913 subst_char(tmpstr, '\n', ' ');
5914 subst_char(tmpstr, '\r', ' ');
5917 if (!g_utf8_validate(tmpstr, -1, NULL)) {
5918 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
5919 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
5924 codeconv_set_strict(TRUE);
5925 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
5926 conv_get_charset_str(compose->out_encoding));
5927 codeconv_set_strict(FALSE);
5929 if (!dest || *dest == '\0') {
5930 gchar *test_conv_global_out = NULL;
5931 gchar *test_conv_reply = NULL;
5933 /* automatic mode. be automatic. */
5934 codeconv_set_strict(TRUE);
5936 out_codeset = conv_get_outgoing_charset_str();
5938 debug_print("trying to convert to %s\n", out_codeset);
5939 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
5942 if (!test_conv_global_out && compose->orig_charset
5943 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5944 out_codeset = compose->orig_charset;
5945 debug_print("failure; trying to convert to %s\n", out_codeset);
5946 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
5949 if (!test_conv_global_out && !test_conv_reply) {
5951 out_codeset = CS_INTERNAL;
5952 debug_print("finally using %s\n", out_codeset);
5954 g_free(test_conv_global_out);
5955 g_free(test_conv_reply);
5956 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
5958 codeconv_set_strict(FALSE);
5963 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
5967 g_return_if_fail(user_data != NULL);
5969 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
5970 g_strstrip(address);
5971 if (*address != '\0') {
5972 gchar *name = procheader_get_fromname(address);
5973 extract_address(address);
5974 addressbook_add_contact(name, address, NULL, NULL);
5979 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
5981 GtkWidget *menuitem;
5984 g_return_if_fail(menu != NULL);
5985 g_return_if_fail(GTK_IS_MENU_SHELL(menu));
5987 menuitem = gtk_separator_menu_item_new();
5988 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
5989 gtk_widget_show(menuitem);
5991 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
5992 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
5994 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
5995 g_strstrip(address);
5996 if (*address == '\0') {
5997 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
6000 g_signal_connect(G_OBJECT(menuitem), "activate",
6001 G_CALLBACK(compose_add_to_addressbook_cb), entry);
6002 gtk_widget_show(menuitem);
6005 static void compose_create_header_entry(Compose *compose)
6007 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6012 const gchar *header = NULL;
6013 ComposeHeaderEntry *headerentry;
6014 gboolean standard_header = FALSE;
6016 headerentry = g_new0(ComposeHeaderEntry, 1);
6019 combo = gtk_combo_box_entry_new_text();
6021 while(*string != NULL) {
6022 gtk_combo_box_append_text(GTK_COMBO_BOX(combo),
6023 (gchar*)prefs_common_translated_header_name(*string));
6026 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
6027 g_signal_connect(G_OBJECT(GTK_BIN(combo)->child), "grab_focus",
6028 G_CALLBACK(compose_grab_focus_cb), compose);
6029 gtk_widget_show(combo);
6030 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
6031 compose->header_nextrow, compose->header_nextrow+1,
6032 GTK_SHRINK, GTK_FILL, 0, 0);
6033 if (compose->header_last) {
6034 const gchar *last_header_entry = gtk_entry_get_text(
6035 GTK_ENTRY(GTK_BIN(compose->header_last->combo)->child));
6037 while (*string != NULL) {
6038 if (!strcmp(*string, last_header_entry))
6039 standard_header = TRUE;
6042 if (standard_header)
6043 header = gtk_entry_get_text(GTK_ENTRY(GTK_BIN(compose->header_last->combo)->child));
6045 if (!compose->header_last || !standard_header) {
6046 switch(compose->account->protocol) {
6048 header = prefs_common_translated_header_name("Newsgroups:");
6051 header = prefs_common_translated_header_name("To:");
6056 gtk_entry_set_text(GTK_ENTRY(GTK_BIN(combo)->child), header);
6058 g_signal_connect_after(G_OBJECT(GTK_BIN(combo)->child), "grab_focus",
6059 G_CALLBACK(compose_grab_focus_cb), compose);
6062 entry = gtk_entry_new();
6063 gtk_widget_show(entry);
6064 gtk_tooltips_set_tip(compose->tooltips, entry,
6065 _("Use <tab> to autocomplete from addressbook"), NULL);
6066 gtk_table_attach(GTK_TABLE(compose->header_table), entry, 1, 2,
6067 compose->header_nextrow, compose->header_nextrow+1,
6068 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
6070 g_signal_connect(G_OBJECT(entry), "key-press-event",
6071 G_CALLBACK(compose_headerentry_key_press_event_cb),
6073 g_signal_connect(G_OBJECT(entry), "changed",
6074 G_CALLBACK(compose_headerentry_changed_cb),
6076 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
6077 G_CALLBACK(compose_grab_focus_cb), compose);
6080 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6081 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6082 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6083 g_signal_connect(G_OBJECT(entry), "drag_data_received",
6084 G_CALLBACK(compose_header_drag_received_cb),
6086 g_signal_connect(G_OBJECT(entry), "drag-drop",
6087 G_CALLBACK(compose_drag_drop),
6089 g_signal_connect(G_OBJECT(entry), "populate-popup",
6090 G_CALLBACK(compose_entry_popup_extend),
6093 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
6095 headerentry->compose = compose;
6096 headerentry->combo = combo;
6097 headerentry->entry = entry;
6098 headerentry->headernum = compose->header_nextrow;
6100 compose->header_nextrow++;
6101 compose->header_last = headerentry;
6102 compose->header_list =
6103 g_slist_append(compose->header_list,
6107 static void compose_add_header_entry(Compose *compose, const gchar *header, gchar *text)
6109 ComposeHeaderEntry *last_header;
6111 last_header = compose->header_last;
6113 gtk_entry_set_text(GTK_ENTRY(GTK_BIN(last_header->combo)->child), header);
6114 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
6117 static void compose_remove_header_entries(Compose *compose)
6120 for (list = compose->header_list; list; list = list->next) {
6121 ComposeHeaderEntry *headerentry =
6122 (ComposeHeaderEntry *)list->data;
6123 gtk_widget_destroy(headerentry->combo);
6124 gtk_widget_destroy(headerentry->entry);
6125 g_free(headerentry);
6127 compose->header_last = NULL;
6128 g_slist_free(compose->header_list);
6129 compose->header_list = NULL;
6130 compose->header_nextrow = 1;
6131 compose_create_header_entry(compose);
6134 static GtkWidget *compose_create_header(Compose *compose)
6136 GtkWidget *from_optmenu_hbox;
6137 GtkWidget *header_scrolledwin;
6138 GtkWidget *header_table;
6142 /* header labels and entries */
6143 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
6144 gtk_widget_show(header_scrolledwin);
6145 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
6147 header_table = gtk_table_new(2, 2, FALSE);
6148 gtk_widget_show(header_table);
6149 gtk_container_set_border_width(GTK_CONTAINER(header_table), BORDER_WIDTH);
6150 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
6151 gtk_viewport_set_shadow_type(GTK_VIEWPORT(GTK_BIN(header_scrolledwin)->child), GTK_SHADOW_NONE);
6154 /* option menu for selecting accounts */
6155 from_optmenu_hbox = compose_account_option_menu_create(compose);
6156 gtk_table_attach(GTK_TABLE(header_table), from_optmenu_hbox,
6157 0, 2, count, count + 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
6160 compose->header_table = header_table;
6161 compose->header_list = NULL;
6162 compose->header_nextrow = count;
6164 compose_create_header_entry(compose);
6166 compose->table = NULL;
6168 return header_scrolledwin ;
6171 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
6173 Compose *compose = (Compose *)data;
6174 GdkEventButton event;
6177 event.time = gtk_get_current_event_time();
6179 return attach_button_pressed(compose->attach_clist, &event, compose);
6182 static GtkWidget *compose_create_attach(Compose *compose)
6184 GtkWidget *attach_scrwin;
6185 GtkWidget *attach_clist;
6187 GtkListStore *store;
6188 GtkCellRenderer *renderer;
6189 GtkTreeViewColumn *column;
6190 GtkTreeSelection *selection;
6192 /* attachment list */
6193 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
6194 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
6195 GTK_POLICY_AUTOMATIC,
6196 GTK_POLICY_AUTOMATIC);
6197 gtk_widget_set_size_request(attach_scrwin, -1, 80);
6199 store = gtk_list_store_new(N_ATTACH_COLS,
6204 G_TYPE_AUTO_POINTER,
6206 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
6207 (GTK_TREE_MODEL(store)));
6208 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
6209 g_object_unref(store);
6211 renderer = gtk_cell_renderer_text_new();
6212 column = gtk_tree_view_column_new_with_attributes
6213 (_("Mime type"), renderer, "text",
6214 COL_MIMETYPE, NULL);
6215 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6217 renderer = gtk_cell_renderer_text_new();
6218 column = gtk_tree_view_column_new_with_attributes
6219 (_("Size"), renderer, "text",
6221 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6223 renderer = gtk_cell_renderer_text_new();
6224 column = gtk_tree_view_column_new_with_attributes
6225 (_("Name"), renderer, "text",
6227 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6229 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
6230 prefs_common.use_stripes_everywhere);
6231 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
6232 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
6234 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
6235 G_CALLBACK(attach_selected), compose);
6236 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
6237 G_CALLBACK(attach_button_pressed), compose);
6239 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
6240 G_CALLBACK(popup_attach_button_pressed), compose);
6242 gtk_widget_tap_and_hold_setup(GTK_WIDGET(attach_clist), NULL, NULL,
6243 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
6244 g_signal_connect(G_OBJECT(attach_clist), "tap-and-hold",
6245 G_CALLBACK(popup_attach_button_pressed), compose);
6247 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
6248 G_CALLBACK(attach_key_pressed), compose);
6251 gtk_drag_dest_set(attach_clist,
6252 GTK_DEST_DEFAULT_ALL, compose_mime_types,
6253 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6254 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6255 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
6256 G_CALLBACK(compose_attach_drag_received_cb),
6258 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
6259 G_CALLBACK(compose_drag_drop),
6262 compose->attach_scrwin = attach_scrwin;
6263 compose->attach_clist = attach_clist;
6265 return attach_scrwin;
6268 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
6269 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
6271 static GtkWidget *compose_create_others(Compose *compose)
6274 GtkWidget *savemsg_checkbtn;
6275 GtkWidget *savemsg_entry;
6276 GtkWidget *savemsg_select;
6279 gchar *folderidentifier;
6281 /* Table for settings */
6282 table = gtk_table_new(3, 1, FALSE);
6283 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
6284 gtk_widget_show(table);
6285 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
6288 /* Save Message to folder */
6289 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
6290 gtk_widget_show(savemsg_checkbtn);
6291 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6292 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6293 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
6295 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
6296 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
6298 savemsg_entry = gtk_entry_new();
6299 gtk_widget_show(savemsg_entry);
6300 gtk_table_attach_defaults(GTK_TABLE(table), savemsg_entry, 1, 2, rowcount, rowcount + 1);
6301 gtk_editable_set_editable(GTK_EDITABLE(savemsg_entry), prefs_common.savemsg);
6302 g_signal_connect_after(G_OBJECT(savemsg_entry), "grab_focus",
6303 G_CALLBACK(compose_grab_focus_cb), compose);
6304 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6305 folderidentifier = folder_item_get_identifier(account_get_special_folder
6306 (compose->account, F_OUTBOX));
6307 gtk_entry_set_text(GTK_ENTRY(savemsg_entry), folderidentifier);
6308 g_free(folderidentifier);
6311 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
6312 gtk_widget_show(savemsg_select);
6313 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6314 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
6315 G_CALLBACK(compose_savemsg_select_cb),
6320 compose->savemsg_checkbtn = savemsg_checkbtn;
6321 compose->savemsg_entry = savemsg_entry;
6326 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
6328 gtk_editable_set_editable(GTK_EDITABLE(compose->savemsg_entry),
6329 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
6332 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
6337 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL);
6340 path = folder_item_get_identifier(dest);
6342 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), path);
6346 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
6347 GdkAtom clip, GtkTextIter *insert_place);
6350 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
6354 GtkTextBuffer *buffer;
6356 if (event->button == 3) {
6358 GtkTextIter sel_start, sel_end;
6359 gboolean stuff_selected;
6361 /* move the cursor to allow GtkAspell to check the word
6362 * under the mouse */
6363 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6364 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6366 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6369 stuff_selected = gtk_text_buffer_get_selection_bounds(
6370 GTK_TEXT_VIEW(text)->buffer,
6371 &sel_start, &sel_end);
6373 gtk_text_buffer_place_cursor (GTK_TEXT_VIEW(text)->buffer, &iter);
6374 /* reselect stuff */
6376 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
6377 gtk_text_buffer_select_range(GTK_TEXT_VIEW(text)->buffer,
6378 &sel_start, &sel_end);
6380 return FALSE; /* pass the event so that the right-click goes through */
6383 if (event->button == 2) {
6388 /* get the middle-click position to paste at the correct place */
6389 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6390 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6392 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6395 entry_paste_clipboard(compose, text,
6396 prefs_common.linewrap_pastes,
6397 GDK_SELECTION_PRIMARY, &iter);
6405 static void compose_spell_menu_changed(void *data)
6407 Compose *compose = (Compose *)data;
6409 GtkWidget *menuitem;
6410 GtkWidget *parent_item;
6411 GtkMenu *menu = GTK_MENU(gtk_menu_new());
6412 GtkItemFactory *ifactory = gtk_item_factory_from_widget(compose->menubar);
6415 if (compose->gtkaspell == NULL)
6418 parent_item = gtk_item_factory_get_item(ifactory,
6419 "/Spelling/Options");
6421 /* setting the submenu removes /Spelling/Options from the factory
6422 * so we need to save it */
6424 if (parent_item == NULL) {
6425 parent_item = compose->aspell_options_menu;
6426 gtk_menu_item_remove_submenu(GTK_MENU_ITEM(parent_item));
6428 compose->aspell_options_menu = parent_item;
6430 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
6432 spell_menu = g_slist_reverse(spell_menu);
6433 for (items = spell_menu;
6434 items; items = items->next) {
6435 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
6436 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
6437 gtk_widget_show(GTK_WIDGET(menuitem));
6439 g_slist_free(spell_menu);
6441 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
6446 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
6448 Compose *compose = (Compose *)data;
6449 GdkEventButton event;
6452 event.time = gtk_get_current_event_time();
6454 return text_clicked(compose->text, &event, compose);
6457 static gboolean compose_force_window_origin = TRUE;
6458 static Compose *compose_create(PrefsAccount *account,
6467 GtkWidget *handlebox;
6469 GtkWidget *notebook;
6471 GtkWidget *attach_hbox;
6472 GtkWidget *attach_lab1;
6473 GtkWidget *attach_lab2;
6478 GtkWidget *subject_hbox;
6479 GtkWidget *subject_frame;
6480 GtkWidget *subject_entry;
6484 GtkWidget *edit_vbox;
6485 GtkWidget *ruler_hbox;
6487 GtkWidget *scrolledwin;
6489 GtkTextBuffer *buffer;
6490 GtkClipboard *clipboard;
6492 UndoMain *undostruct;
6494 gchar *titles[N_ATTACH_COLS];
6495 guint n_menu_entries;
6496 GtkWidget *popupmenu;
6497 GtkItemFactory *popupfactory;
6498 GtkItemFactory *ifactory;
6499 GtkWidget *tmpl_menu;
6501 GtkWidget *menuitem;
6504 GtkAspell * gtkaspell = NULL;
6507 static GdkGeometry geometry;
6509 g_return_val_if_fail(account != NULL, NULL);
6511 debug_print("Creating compose window...\n");
6512 compose = g_new0(Compose, 1);
6514 titles[COL_MIMETYPE] = _("MIME type");
6515 titles[COL_SIZE] = _("Size");
6516 titles[COL_NAME] = _("Name");
6518 compose->batch = batch;
6519 compose->account = account;
6520 compose->folder = folder;
6522 compose->mutex = g_mutex_new();
6523 compose->set_cursor_pos = -1;
6525 compose->tooltips = gtk_tooltips_new();
6527 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
6529 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
6530 gtk_widget_set_size_request(window, -1, prefs_common.compose_height);
6532 if (!geometry.max_width) {
6533 geometry.max_width = gdk_screen_width();
6534 geometry.max_height = gdk_screen_height();
6537 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
6538 &geometry, GDK_HINT_MAX_SIZE);
6539 if (!geometry.min_width) {
6540 geometry.min_width = 600;
6541 geometry.min_height = 480;
6543 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
6544 &geometry, GDK_HINT_MIN_SIZE);
6547 if (compose_force_window_origin)
6548 gtk_widget_set_uposition(window, prefs_common.compose_x,
6549 prefs_common.compose_y);
6551 g_signal_connect(G_OBJECT(window), "delete_event",
6552 G_CALLBACK(compose_delete_cb), compose);
6553 MANAGE_WINDOW_SIGNALS_CONNECT(window);
6554 gtk_widget_realize(window);
6556 gtkut_widget_set_composer_icon(window);
6558 vbox = gtk_vbox_new(FALSE, 0);
6559 gtk_container_add(GTK_CONTAINER(window), vbox);
6561 n_menu_entries = sizeof(compose_entries) / sizeof(compose_entries[0]);
6562 menubar = menubar_create(window, compose_entries,
6563 n_menu_entries, "<Compose>", compose);
6564 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
6566 if (prefs_common.toolbar_detachable) {
6567 handlebox = gtk_handle_box_new();
6569 handlebox = gtk_hbox_new(FALSE, 0);
6571 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
6573 gtk_widget_realize(handlebox);
6575 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, window,
6578 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
6582 vbox2 = gtk_vbox_new(FALSE, 2);
6583 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
6584 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
6587 notebook = gtk_notebook_new();
6588 gtk_widget_set_size_request(notebook, -1, 130);
6589 gtk_widget_show(notebook);
6591 /* header labels and entries */
6592 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6593 compose_create_header(compose),
6594 gtk_label_new_with_mnemonic(_("Hea_der")));
6595 /* attachment list */
6596 attach_hbox = gtk_hbox_new(FALSE, 0);
6597 gtk_widget_show(attach_hbox);
6599 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
6600 gtk_widget_show(attach_lab1);
6601 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
6603 attach_lab2 = gtk_label_new("");
6604 gtk_widget_show(attach_lab2);
6605 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
6607 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6608 compose_create_attach(compose),
6611 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6612 compose_create_others(compose),
6613 gtk_label_new_with_mnemonic(_("Othe_rs")));
6616 subject_hbox = gtk_hbox_new(FALSE, 0);
6617 gtk_widget_show(subject_hbox);
6619 subject_frame = gtk_frame_new(NULL);
6620 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
6621 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
6622 gtk_widget_show(subject_frame);
6624 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
6625 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
6626 gtk_widget_show(subject);
6628 label = gtk_label_new(_("Subject:"));
6629 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
6630 gtk_widget_show(label);
6632 subject_entry = gtk_entry_new();
6633 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
6634 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
6635 G_CALLBACK(compose_grab_focus_cb), compose);
6636 gtk_widget_show(subject_entry);
6637 compose->subject_entry = subject_entry;
6638 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
6640 edit_vbox = gtk_vbox_new(FALSE, 0);
6642 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
6645 ruler_hbox = gtk_hbox_new(FALSE, 0);
6646 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
6648 ruler = gtk_shruler_new();
6649 gtk_ruler_set_range(GTK_RULER(ruler), 0.0, 100.0, 1.0, 100.0);
6650 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
6654 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
6655 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
6656 GTK_POLICY_AUTOMATIC,
6657 GTK_POLICY_AUTOMATIC);
6658 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
6660 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
6661 gtk_widget_set_size_request(scrolledwin, prefs_common.compose_width, -1);
6663 text = gtk_text_view_new();
6664 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
6665 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
6666 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
6667 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
6668 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
6670 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
6672 g_signal_connect_after(G_OBJECT(text), "size_allocate",
6673 G_CALLBACK(compose_edit_size_alloc),
6675 g_signal_connect(G_OBJECT(buffer), "changed",
6676 G_CALLBACK(compose_changed_cb), compose);
6677 g_signal_connect(G_OBJECT(text), "grab_focus",
6678 G_CALLBACK(compose_grab_focus_cb), compose);
6679 g_signal_connect(G_OBJECT(buffer), "insert_text",
6680 G_CALLBACK(text_inserted), compose);
6681 g_signal_connect(G_OBJECT(text), "button_press_event",
6682 G_CALLBACK(text_clicked), compose);
6684 g_signal_connect(G_OBJECT(text), "popup-menu",
6685 G_CALLBACK(compose_popup_menu), compose);
6687 gtk_widget_tap_and_hold_setup(GTK_WIDGET(text), NULL, NULL,
6688 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
6689 g_signal_connect(G_OBJECT(text), "tap-and-hold",
6690 G_CALLBACK(compose_popup_menu), compose);
6692 g_signal_connect(G_OBJECT(subject_entry), "changed",
6693 G_CALLBACK(compose_changed_cb), compose);
6696 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6697 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6698 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6699 g_signal_connect(G_OBJECT(text), "drag_data_received",
6700 G_CALLBACK(compose_insert_drag_received_cb),
6702 g_signal_connect(G_OBJECT(text), "drag-drop",
6703 G_CALLBACK(compose_drag_drop),
6705 gtk_widget_show_all(vbox);
6707 /* pane between attach clist and text */
6708 paned = gtk_vpaned_new();
6709 gtk_paned_set_gutter_size(GTK_PANED(paned), 12);
6710 gtk_container_add(GTK_CONTAINER(vbox2), paned);
6712 if( maemo_mainwindow_is_fullscreen(mainwindow_get_mainwindow()->window) )
6713 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 300 : 280);
6715 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 250 : 230);
6717 gtk_paned_add1(GTK_PANED(paned), notebook);
6718 gtk_paned_add2(GTK_PANED(paned), edit_vbox);
6719 gtk_widget_show_all(paned);
6722 if (prefs_common.textfont) {
6723 PangoFontDescription *font_desc;
6725 font_desc = pango_font_description_from_string
6726 (prefs_common.textfont);
6728 gtk_widget_modify_font(text, font_desc);
6729 pango_font_description_free(font_desc);
6733 n_entries = sizeof(compose_popup_entries) /
6734 sizeof(compose_popup_entries[0]);
6735 popupmenu = menu_create_items(compose_popup_entries, n_entries,
6736 "<Compose>", &popupfactory,
6739 ifactory = gtk_item_factory_from_widget(menubar);
6740 menu_set_sensitive(ifactory, "/Edit/Undo", FALSE);
6741 menu_set_sensitive(ifactory, "/Edit/Redo", FALSE);
6742 menu_set_sensitive(ifactory, "/Options/Remove references", FALSE);
6744 tmpl_menu = gtk_item_factory_get_item(ifactory, "/Tools/Template");
6746 undostruct = undo_init(text);
6747 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
6750 address_completion_start(window);
6752 compose->window = window;
6753 compose->vbox = vbox;
6754 compose->menubar = menubar;
6755 compose->handlebox = handlebox;
6757 compose->vbox2 = vbox2;
6759 compose->paned = paned;
6761 compose->attach_label = attach_lab2;
6763 compose->notebook = notebook;
6764 compose->edit_vbox = edit_vbox;
6765 compose->ruler_hbox = ruler_hbox;
6766 compose->ruler = ruler;
6767 compose->scrolledwin = scrolledwin;
6768 compose->text = text;
6770 compose->focused_editable = NULL;
6772 compose->popupmenu = popupmenu;
6773 compose->popupfactory = popupfactory;
6775 compose->tmpl_menu = tmpl_menu;
6777 compose->mode = mode;
6778 compose->rmode = mode;
6780 compose->targetinfo = NULL;
6781 compose->replyinfo = NULL;
6782 compose->fwdinfo = NULL;
6784 compose->replyto = NULL;
6786 compose->bcc = NULL;
6787 compose->followup_to = NULL;
6789 compose->ml_post = NULL;
6791 compose->inreplyto = NULL;
6792 compose->references = NULL;
6793 compose->msgid = NULL;
6794 compose->boundary = NULL;
6796 compose->autowrap = prefs_common.autowrap;
6798 compose->use_signing = FALSE;
6799 compose->use_encryption = FALSE;
6800 compose->privacy_system = NULL;
6802 compose->modified = FALSE;
6804 compose->return_receipt = FALSE;
6806 compose->to_list = NULL;
6807 compose->newsgroup_list = NULL;
6809 compose->undostruct = undostruct;
6811 compose->sig_str = NULL;
6813 compose->exteditor_file = NULL;
6814 compose->exteditor_pid = -1;
6815 compose->exteditor_tag = -1;
6816 compose->draft_timeout_tag = -2; /* inhibit auto-drafting while loading */
6819 menu_set_sensitive(ifactory, "/Spelling", FALSE);
6820 if (mode != COMPOSE_REDIRECT) {
6821 if (prefs_common.enable_aspell && prefs_common.dictionary &&
6822 strcmp(prefs_common.dictionary, "")) {
6823 gtkaspell = gtkaspell_new(prefs_common.aspell_path,
6824 prefs_common.dictionary,
6825 prefs_common.alt_dictionary,
6826 conv_get_locale_charset_str(),
6827 prefs_common.misspelled_col,
6828 prefs_common.check_while_typing,
6829 prefs_common.recheck_when_changing_dict,
6830 prefs_common.use_alternate,
6831 prefs_common.use_both_dicts,
6832 GTK_TEXT_VIEW(text),
6833 GTK_WINDOW(compose->window),
6834 compose_spell_menu_changed,
6837 alertpanel_error(_("Spell checker could not "
6839 gtkaspell_checkers_strerror());
6840 gtkaspell_checkers_reset_error();
6842 if (!gtkaspell_set_sug_mode(gtkaspell,
6843 prefs_common.aspell_sugmode)) {
6844 debug_print("Aspell: could not set "
6845 "suggestion mode %s\n",
6846 gtkaspell_checkers_strerror());
6847 gtkaspell_checkers_reset_error();
6850 menu_set_sensitive(ifactory, "/Spelling", TRUE);
6854 compose->gtkaspell = gtkaspell;
6855 compose_spell_menu_changed(compose);
6858 compose_select_account(compose, account, TRUE);
6860 menu_set_active(ifactory, "/Edit/Auto wrapping", prefs_common.autowrap);
6861 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
6862 compose_entry_append(compose, account->auto_cc, COMPOSE_CC);
6864 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
6865 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC);
6867 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
6868 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO);
6870 menu_set_sensitive(ifactory, "/Options/Reply mode", compose->mode == COMPOSE_REPLY);
6872 if (account->protocol != A_NNTP)
6873 gtk_entry_set_text(GTK_ENTRY(GTK_BIN(compose->header_last->combo)->child),
6874 prefs_common_translated_header_name("To:"));
6876 gtk_entry_set_text(GTK_ENTRY(GTK_BIN(compose->header_last->combo)->child),
6877 prefs_common_translated_header_name("Newsgroups:"));
6879 addressbook_set_target_compose(compose);
6881 if (mode != COMPOSE_REDIRECT)
6882 compose_set_template_menu(compose);
6884 menuitem = gtk_item_factory_get_item(ifactory, "/Tools/Template");
6885 menu_set_sensitive(ifactory, "/Tools/Template", FALSE);
6888 compose_list = g_list_append(compose_list, compose);
6890 if (!prefs_common.show_ruler)
6891 gtk_widget_hide(ruler_hbox);
6893 menuitem = gtk_item_factory_get_item(ifactory, "/Tools/Show ruler");
6894 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
6895 prefs_common.show_ruler);
6898 compose->priority = PRIORITY_NORMAL;
6899 compose_update_priority_menu_item(compose);
6901 compose_set_out_encoding(compose);
6904 compose_update_actions_menu(compose);
6906 /* Privacy Systems menu */
6907 compose_update_privacy_systems_menu(compose);
6909 activate_privacy_system(compose, account, TRUE);
6910 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
6912 gtk_widget_realize(window);
6914 gtk_widget_show(window);
6916 maemo_window_full_screen_if_needed(GTK_WINDOW(window));
6917 maemo_connect_key_press_to_mainwindow(GTK_WINDOW(window));
6924 static GtkWidget *compose_account_option_menu_create(Compose *compose)
6929 GtkWidget *optmenubox;
6932 GtkWidget *from_name = NULL;
6934 gint num = 0, def_menu = 0;
6936 accounts = account_get_list();
6937 g_return_val_if_fail(accounts != NULL, NULL);
6939 optmenubox = gtk_event_box_new();
6940 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
6941 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
6943 hbox = gtk_hbox_new(FALSE, 6);
6944 from_name = gtk_entry_new();
6946 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
6947 G_CALLBACK(compose_grab_focus_cb), compose);
6949 for (; accounts != NULL; accounts = accounts->next, num++) {
6950 PrefsAccount *ac = (PrefsAccount *)accounts->data;
6951 gchar *name, *from = NULL;
6953 if (ac == compose->account) def_menu = num;
6955 name = g_markup_printf_escaped(_("From: <i>%s</i>"),
6958 if (ac == compose->account) {
6959 if (ac->name && *ac->name) {
6961 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
6962 from = g_strdup_printf("%s <%s>",
6964 gtk_entry_set_text(GTK_ENTRY(from_name), from);
6966 from = g_strdup_printf("%s",
6968 gtk_entry_set_text(GTK_ENTRY(from_name), from);
6971 COMBOBOX_ADD(menu, name, ac->account_id);
6976 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
6978 g_signal_connect(G_OBJECT(optmenu), "changed",
6979 G_CALLBACK(account_activated),
6981 g_signal_connect(G_OBJECT(from_name), "populate-popup",
6982 G_CALLBACK(compose_entry_popup_extend),
6985 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
6986 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
6988 gtk_tooltips_set_tip(compose->tooltips, optmenubox,
6989 _("Account to use for this email"), NULL);
6990 gtk_tooltips_set_tip(compose->tooltips, from_name,
6991 _("Sender address to be used"), NULL);
6993 compose->from_name = from_name;
6998 static void compose_set_priority_cb(gpointer data,
7002 Compose *compose = (Compose *) data;
7003 compose->priority = action;
7006 static void compose_reply_change_mode(gpointer data,
7010 Compose *compose = (Compose *) data;
7011 gboolean was_modified = compose->modified;
7013 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
7015 g_return_if_fail(compose->replyinfo != NULL);
7017 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
7019 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
7021 if (action == COMPOSE_REPLY_TO_ALL)
7023 if (action == COMPOSE_REPLY_TO_SENDER)
7025 if (action == COMPOSE_REPLY_TO_LIST)
7028 compose_remove_header_entries(compose);
7029 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
7030 if (compose->account->set_autocc && compose->account->auto_cc)
7031 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC);
7033 if (compose->account->set_autobcc && compose->account->auto_bcc)
7034 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC);
7036 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
7037 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO);
7038 compose_show_first_last_header(compose, TRUE);
7039 compose->modified = was_modified;
7040 compose_set_title(compose);
7043 static void compose_update_priority_menu_item(Compose * compose)
7045 GtkItemFactory *ifactory;
7046 GtkWidget *menuitem = NULL;
7048 ifactory = gtk_item_factory_from_widget(compose->menubar);
7050 switch (compose->priority) {
7051 case PRIORITY_HIGHEST:
7052 menuitem = gtk_item_factory_get_item
7053 (ifactory, "/Options/Priority/Highest");
7056 menuitem = gtk_item_factory_get_item
7057 (ifactory, "/Options/Priority/High");
7059 case PRIORITY_NORMAL:
7060 menuitem = gtk_item_factory_get_item
7061 (ifactory, "/Options/Priority/Normal");
7064 menuitem = gtk_item_factory_get_item
7065 (ifactory, "/Options/Priority/Low");
7067 case PRIORITY_LOWEST:
7068 menuitem = gtk_item_factory_get_item
7069 (ifactory, "/Options/Priority/Lowest");
7072 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
7075 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
7077 Compose *compose = (Compose *) data;
7079 GtkItemFactory *ifactory;
7080 gboolean can_sign = FALSE, can_encrypt = FALSE;
7082 g_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
7084 if (!GTK_CHECK_MENU_ITEM(widget)->active)
7087 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
7088 g_free(compose->privacy_system);
7089 compose->privacy_system = NULL;
7090 if (systemid != NULL) {
7091 compose->privacy_system = g_strdup(systemid);
7093 can_sign = privacy_system_can_sign(systemid);
7094 can_encrypt = privacy_system_can_encrypt(systemid);
7097 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
7099 ifactory = gtk_item_factory_from_widget(compose->menubar);
7100 menu_set_sensitive(ifactory, "/Options/Sign", can_sign);
7101 menu_set_sensitive(ifactory, "/Options/Encrypt", can_encrypt);
7104 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
7106 static gchar *branch_path = "/Options/Privacy System";
7107 GtkItemFactory *ifactory;
7108 GtkWidget *menuitem = NULL;
7110 gboolean can_sign = FALSE, can_encrypt = FALSE;
7111 gboolean found = FALSE;
7113 ifactory = gtk_item_factory_from_widget(compose->menubar);
7115 if (compose->privacy_system != NULL) {
7118 menuitem = gtk_item_factory_get_widget(ifactory, branch_path);
7119 g_return_if_fail(menuitem != NULL);
7121 amenu = GTK_MENU_SHELL(menuitem)->children;
7123 while (amenu != NULL) {
7124 GList *alist = amenu->next;
7126 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
7127 if (systemid != NULL) {
7128 if (strcmp(systemid, compose->privacy_system) == 0) {
7129 menuitem = GTK_WIDGET(amenu->data);
7131 can_sign = privacy_system_can_sign(systemid);
7132 can_encrypt = privacy_system_can_encrypt(systemid);
7136 } else if (strlen(compose->privacy_system) == 0) {
7137 menuitem = GTK_WIDGET(amenu->data);
7140 can_encrypt = FALSE;
7147 if (menuitem != NULL)
7148 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
7150 if (warn && !found && strlen(compose->privacy_system)) {
7151 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
7152 "will not be able to sign or encrypt this message."),
7153 compose->privacy_system);
7157 menu_set_sensitive(ifactory, "/Options/Sign", can_sign);
7158 menu_set_sensitive(ifactory, "/Options/Encrypt", can_encrypt);
7161 static void compose_set_out_encoding(Compose *compose)
7163 GtkItemFactoryEntry *entry;
7164 GtkItemFactory *ifactory;
7165 CharSet out_encoding;
7166 gchar *path, *p, *q;
7169 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
7170 ifactory = gtk_item_factory_from_widget(compose->menubar);
7172 for (entry = compose_entries; entry->callback != compose_address_cb;
7174 if (entry->callback == compose_set_encoding_cb &&
7175 (CharSet)entry->callback_action == out_encoding) {
7176 p = q = path = g_strdup(entry->path);
7188 item = gtk_item_factory_get_item(ifactory, path);
7189 gtk_widget_activate(item);
7196 static void compose_set_template_menu(Compose *compose)
7198 GSList *tmpl_list, *cur;
7202 tmpl_list = template_get_config();
7204 menu = gtk_menu_new();
7206 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
7207 Template *tmpl = (Template *)cur->data;
7209 item = gtk_menu_item_new_with_label(tmpl->name);
7210 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
7211 g_signal_connect(G_OBJECT(item), "activate",
7212 G_CALLBACK(compose_template_activate_cb),
7214 g_object_set_data(G_OBJECT(item), "template", tmpl);
7215 gtk_widget_show(item);
7218 gtk_widget_show(menu);
7219 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
7222 void compose_update_actions_menu(Compose *compose)
7224 GtkItemFactory *ifactory;
7226 ifactory = gtk_item_factory_from_widget(compose->menubar);
7227 action_update_compose_menu(ifactory, "/Tools/Actions", compose);
7230 static void compose_update_privacy_systems_menu(Compose *compose)
7232 static gchar *branch_path = "/Options/Privacy System";
7233 GtkItemFactory *ifactory;
7234 GtkWidget *menuitem;
7235 GSList *systems, *cur;
7238 GtkWidget *system_none;
7241 ifactory = gtk_item_factory_from_widget(compose->menubar);
7243 /* remove old entries */
7244 menuitem = gtk_item_factory_get_widget(ifactory, branch_path);
7245 g_return_if_fail(menuitem != NULL);
7247 amenu = GTK_MENU_SHELL(menuitem)->children->next;
7248 while (amenu != NULL) {
7249 GList *alist = amenu->next;
7250 gtk_widget_destroy(GTK_WIDGET(amenu->data));
7254 system_none = gtk_item_factory_get_widget(ifactory,
7255 "/Options/Privacy System/None");
7257 g_signal_connect(G_OBJECT(system_none), "activate",
7258 G_CALLBACK(compose_set_privacy_system_cb), compose);
7260 systems = privacy_get_system_ids();
7261 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
7262 gchar *systemid = cur->data;
7264 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
7265 widget = gtk_radio_menu_item_new_with_label(group,
7266 privacy_system_get_name(systemid));
7267 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
7268 g_strdup(systemid), g_free);
7269 g_signal_connect(G_OBJECT(widget), "activate",
7270 G_CALLBACK(compose_set_privacy_system_cb), compose);
7272 gtk_menu_append(GTK_MENU(system_none->parent), widget);
7273 gtk_widget_show(widget);
7276 g_slist_free(systems);
7279 void compose_reflect_prefs_all(void)
7284 for (cur = compose_list; cur != NULL; cur = cur->next) {
7285 compose = (Compose *)cur->data;
7286 compose_set_template_menu(compose);
7290 void compose_reflect_prefs_pixmap_theme(void)
7295 for (cur = compose_list; cur != NULL; cur = cur->next) {
7296 compose = (Compose *)cur->data;
7297 toolbar_update(TOOLBAR_COMPOSE, compose);
7301 static const gchar *compose_quote_char_from_context(Compose *compose)
7303 const gchar *qmark = NULL;
7305 g_return_val_if_fail(compose != NULL, NULL);
7307 switch (compose->mode) {
7308 /* use forward-specific quote char */
7309 case COMPOSE_FORWARD:
7310 case COMPOSE_FORWARD_AS_ATTACH:
7311 case COMPOSE_FORWARD_INLINE:
7312 if (compose->folder && compose->folder->prefs &&
7313 compose->folder->prefs->forward_with_format)
7314 qmark = compose->folder->prefs->forward_quotemark;
7315 else if (compose->account->forward_with_format)
7316 qmark = compose->account->forward_quotemark;
7318 qmark = prefs_common.fw_quotemark;
7321 /* use reply-specific quote char in all other modes */
7323 if (compose->folder && compose->folder->prefs &&
7324 compose->folder->prefs->reply_with_format)
7325 qmark = compose->folder->prefs->reply_quotemark;
7326 else if (compose->account->reply_with_format)
7327 qmark = compose->account->reply_quotemark;
7329 qmark = prefs_common.quotemark;
7333 if (qmark == NULL || *qmark == '\0')
7339 static void compose_template_apply(Compose *compose, Template *tmpl,
7343 GtkTextBuffer *buffer;
7347 gchar *parsed_str = NULL;
7348 gint cursor_pos = 0;
7349 const gchar *err_msg = _("Template body format error at line %d.");
7352 /* process the body */
7354 text = GTK_TEXT_VIEW(compose->text);
7355 buffer = gtk_text_view_get_buffer(text);
7358 qmark = compose_quote_char_from_context(compose);
7360 if (compose->replyinfo != NULL) {
7363 gtk_text_buffer_set_text(buffer, "", -1);
7364 mark = gtk_text_buffer_get_insert(buffer);
7365 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7367 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
7368 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
7370 } else if (compose->fwdinfo != NULL) {
7373 gtk_text_buffer_set_text(buffer, "", -1);
7374 mark = gtk_text_buffer_get_insert(buffer);
7375 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7377 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
7378 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
7381 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
7383 GtkTextIter start, end;
7386 gtk_text_buffer_get_start_iter(buffer, &start);
7387 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
7388 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
7390 /* clear the buffer now */
7392 gtk_text_buffer_set_text(buffer, "", -1);
7394 parsed_str = compose_quote_fmt(compose, dummyinfo,
7395 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
7396 procmsg_msginfo_free( dummyinfo );
7402 gtk_text_buffer_set_text(buffer, "", -1);
7403 mark = gtk_text_buffer_get_insert(buffer);
7404 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7407 if (replace && parsed_str && compose->account->auto_sig)
7408 compose_insert_sig(compose, FALSE);
7410 if (replace && parsed_str) {
7411 gtk_text_buffer_get_start_iter(buffer, &iter);
7412 gtk_text_buffer_place_cursor(buffer, &iter);
7416 cursor_pos = quote_fmt_get_cursor_pos();
7417 compose->set_cursor_pos = cursor_pos;
7418 if (cursor_pos == -1)
7420 gtk_text_buffer_get_start_iter(buffer, &iter);
7421 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
7422 gtk_text_buffer_place_cursor(buffer, &iter);
7425 /* process the other fields */
7427 compose_template_apply_fields(compose, tmpl);
7428 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
7429 quote_fmt_reset_vartable();
7430 compose_changed_cb(NULL, compose);
7433 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
7435 MsgInfo* dummyinfo = NULL;
7436 MsgInfo *msginfo = NULL;
7439 if (compose->replyinfo != NULL)
7440 msginfo = compose->replyinfo;
7441 else if (compose->fwdinfo != NULL)
7442 msginfo = compose->fwdinfo;
7444 dummyinfo = compose_msginfo_new_from_compose(compose);
7445 msginfo = dummyinfo;
7448 if (tmpl->to && *tmpl->to != '\0') {
7450 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7451 compose->gtkaspell);
7453 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7455 quote_fmt_scan_string(tmpl->to);
7458 buf = quote_fmt_get_buffer();
7460 alertpanel_error(_("Template To format error."));
7462 compose_entry_append(compose, buf, COMPOSE_TO);
7466 if (tmpl->cc && *tmpl->cc != '\0') {
7468 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7469 compose->gtkaspell);
7471 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7473 quote_fmt_scan_string(tmpl->cc);
7476 buf = quote_fmt_get_buffer();
7478 alertpanel_error(_("Template Cc format error."));
7480 compose_entry_append(compose, buf, COMPOSE_CC);
7484 if (tmpl->bcc && *tmpl->bcc != '\0') {
7486 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7487 compose->gtkaspell);
7489 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7491 quote_fmt_scan_string(tmpl->bcc);
7494 buf = quote_fmt_get_buffer();
7496 alertpanel_error(_("Template Bcc format error."));
7498 compose_entry_append(compose, buf, COMPOSE_BCC);
7502 /* process the subject */
7503 if (tmpl->subject && *tmpl->subject != '\0') {
7505 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7506 compose->gtkaspell);
7508 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7510 quote_fmt_scan_string(tmpl->subject);
7513 buf = quote_fmt_get_buffer();
7515 alertpanel_error(_("Template subject format error."));
7517 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
7521 procmsg_msginfo_free( dummyinfo );
7524 static void compose_destroy(Compose *compose)
7526 GtkTextBuffer *buffer;
7527 GtkClipboard *clipboard;
7529 compose_list = g_list_remove(compose_list, compose);
7531 if (compose->updating) {
7532 debug_print("danger, not destroying anything now\n");
7533 compose->deferred_destroy = TRUE;
7536 /* NOTE: address_completion_end() does nothing with the window
7537 * however this may change. */
7538 address_completion_end(compose->window);
7540 slist_free_strings(compose->to_list);
7541 g_slist_free(compose->to_list);
7542 slist_free_strings(compose->newsgroup_list);
7543 g_slist_free(compose->newsgroup_list);
7544 slist_free_strings(compose->header_list);
7545 g_slist_free(compose->header_list);
7547 procmsg_msginfo_free(compose->targetinfo);
7548 procmsg_msginfo_free(compose->replyinfo);
7549 procmsg_msginfo_free(compose->fwdinfo);
7551 g_free(compose->replyto);
7552 g_free(compose->cc);
7553 g_free(compose->bcc);
7554 g_free(compose->newsgroups);
7555 g_free(compose->followup_to);
7557 g_free(compose->ml_post);
7559 g_free(compose->inreplyto);
7560 g_free(compose->references);
7561 g_free(compose->msgid);
7562 g_free(compose->boundary);
7564 g_free(compose->redirect_filename);
7565 if (compose->undostruct)
7566 undo_destroy(compose->undostruct);
7568 g_free(compose->sig_str);
7570 g_free(compose->exteditor_file);
7572 g_free(compose->orig_charset);
7574 g_free(compose->privacy_system);
7576 if (addressbook_get_target_compose() == compose)
7577 addressbook_set_target_compose(NULL);
7580 if (compose->gtkaspell) {
7581 gtkaspell_delete(compose->gtkaspell);
7582 compose->gtkaspell = NULL;
7586 prefs_common.compose_width = compose->scrolledwin->allocation.width;
7587 prefs_common.compose_height = compose->window->allocation.height;
7589 if (!gtk_widget_get_parent(compose->paned))
7590 gtk_widget_destroy(compose->paned);
7591 gtk_widget_destroy(compose->popupmenu);
7593 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
7594 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7595 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
7597 gtk_widget_destroy(compose->window);
7598 toolbar_destroy(compose->toolbar);
7599 g_free(compose->toolbar);
7600 g_mutex_free(compose->mutex);
7604 static void compose_attach_info_free(AttachInfo *ainfo)
7606 g_free(ainfo->file);
7607 g_free(ainfo->content_type);
7608 g_free(ainfo->name);
7612 static void compose_attach_update_label(Compose *compose)
7617 GtkTreeModel *model;
7622 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
7623 if(!gtk_tree_model_get_iter_first(model, &iter)) {
7624 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
7628 while(gtk_tree_model_iter_next(model, &iter))
7631 text = g_strdup_printf("(%d)", i);
7632 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
7636 static void compose_attach_remove_selected(Compose *compose)
7638 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
7639 GtkTreeSelection *selection;
7641 GtkTreeModel *model;
7643 selection = gtk_tree_view_get_selection(tree_view);
7644 sel = gtk_tree_selection_get_selected_rows(selection, &model);
7649 for (cur = sel; cur != NULL; cur = cur->next) {
7650 GtkTreePath *path = cur->data;
7651 GtkTreeRowReference *ref = gtk_tree_row_reference_new
7654 gtk_tree_path_free(path);
7657 for (cur = sel; cur != NULL; cur = cur->next) {
7658 GtkTreeRowReference *ref = cur->data;
7659 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
7662 if (gtk_tree_model_get_iter(model, &iter, path))
7663 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
7665 gtk_tree_path_free(path);
7666 gtk_tree_row_reference_free(ref);
7670 compose_attach_update_label(compose);
7673 static struct _AttachProperty
7676 GtkWidget *mimetype_entry;
7677 GtkWidget *encoding_optmenu;
7678 GtkWidget *path_entry;
7679 GtkWidget *filename_entry;
7681 GtkWidget *cancel_btn;
7684 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
7686 gtk_tree_path_free((GtkTreePath *)ptr);
7689 static void compose_attach_property(Compose *compose)
7691 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
7693 GtkComboBox *optmenu;
7694 GtkTreeSelection *selection;
7696 GtkTreeModel *model;
7699 static gboolean cancelled;
7701 /* only if one selected */
7702 selection = gtk_tree_view_get_selection(tree_view);
7703 if (gtk_tree_selection_count_selected_rows(selection) != 1)
7706 sel = gtk_tree_selection_get_selected_rows(selection, &model);
7710 path = (GtkTreePath *) sel->data;
7711 gtk_tree_model_get_iter(model, &iter, path);
7712 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
7715 g_list_foreach(sel, gtk_tree_path_free_, NULL);
7721 if (!attach_prop.window)
7722 compose_attach_property_create(&cancelled);
7723 gtk_widget_grab_focus(attach_prop.ok_btn);
7724 gtk_widget_show(attach_prop.window);
7725 manage_window_set_transient(GTK_WINDOW(attach_prop.window));
7727 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
7728 if (ainfo->encoding == ENC_UNKNOWN)
7729 combobox_select_by_data(optmenu, ENC_BASE64);
7731 combobox_select_by_data(optmenu, ainfo->encoding);
7733 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
7734 ainfo->content_type ? ainfo->content_type : "");
7735 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
7736 ainfo->file ? ainfo->file : "");
7737 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
7738 ainfo->name ? ainfo->name : "");
7741 const gchar *entry_text;
7743 gchar *cnttype = NULL;
7750 gtk_widget_hide(attach_prop.window);
7755 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
7756 if (*entry_text != '\0') {
7759 text = g_strstrip(g_strdup(entry_text));
7760 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
7761 cnttype = g_strdup(text);
7764 alertpanel_error(_("Invalid MIME type."));
7770 ainfo->encoding = combobox_get_active_data(optmenu);
7772 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
7773 if (*entry_text != '\0') {
7774 if (is_file_exist(entry_text) &&
7775 (size = get_file_size(entry_text)) > 0)
7776 file = g_strdup(entry_text);
7779 (_("File doesn't exist or is empty."));
7785 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
7786 if (*entry_text != '\0') {
7787 g_free(ainfo->name);
7788 ainfo->name = g_strdup(entry_text);
7792 g_free(ainfo->content_type);
7793 ainfo->content_type = cnttype;
7796 g_free(ainfo->file);
7802 /* update tree store */
7803 text = to_human_readable(ainfo->size);
7804 gtk_tree_model_get_iter(model, &iter, path);
7805 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
7806 COL_MIMETYPE, ainfo->content_type,
7808 COL_NAME, ainfo->name,
7814 gtk_tree_path_free(path);
7817 #define SET_LABEL_AND_ENTRY(str, entry, top) \
7819 label = gtk_label_new(str); \
7820 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
7821 GTK_FILL, 0, 0, 0); \
7822 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
7824 entry = gtk_entry_new(); \
7825 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
7826 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
7829 static void compose_attach_property_create(gboolean *cancelled)
7835 GtkWidget *mimetype_entry;
7838 GtkListStore *optmenu_menu;
7839 GtkWidget *path_entry;
7840 GtkWidget *filename_entry;
7843 GtkWidget *cancel_btn;
7844 GList *mime_type_list, *strlist;
7847 debug_print("Creating attach_property window...\n");
7849 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
7850 gtk_widget_set_size_request(window, 480, -1);
7851 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
7852 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
7853 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
7854 gtk_window_set_modal(GTK_WINDOW(window), TRUE);
7855 g_signal_connect(G_OBJECT(window), "delete_event",
7856 G_CALLBACK(attach_property_delete_event),
7858 g_signal_connect(G_OBJECT(window), "key_press_event",
7859 G_CALLBACK(attach_property_key_pressed),
7862 vbox = gtk_vbox_new(FALSE, 8);
7863 gtk_container_add(GTK_CONTAINER(window), vbox);
7865 table = gtk_table_new(4, 2, FALSE);
7866 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
7867 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
7868 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
7870 label = gtk_label_new(_("MIME type"));
7871 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
7873 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
7874 mimetype_entry = gtk_combo_box_entry_new_text();
7875 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
7876 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
7878 /* stuff with list */
7879 mime_type_list = procmime_get_mime_type_list();
7881 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
7882 MimeType *type = (MimeType *) mime_type_list->data;
7885 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
7887 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
7890 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
7891 (GCompareFunc)strcmp2);
7894 for (mime_type_list = strlist; mime_type_list != NULL;
7895 mime_type_list = mime_type_list->next) {
7896 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
7897 g_free(mime_type_list->data);
7899 g_list_free(strlist);
7900 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
7901 mimetype_entry = GTK_BIN(mimetype_entry)->child;
7903 label = gtk_label_new(_("Encoding"));
7904 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
7906 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
7908 hbox = gtk_hbox_new(FALSE, 0);
7909 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
7910 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
7912 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
7913 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
7915 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
7916 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
7917 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
7918 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
7919 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
7921 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
7923 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
7924 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
7926 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
7927 &ok_btn, GTK_STOCK_OK,
7929 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
7930 gtk_widget_grab_default(ok_btn);
7932 g_signal_connect(G_OBJECT(ok_btn), "clicked",
7933 G_CALLBACK(attach_property_ok),
7935 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
7936 G_CALLBACK(attach_property_cancel),
7939 gtk_widget_show_all(vbox);
7941 attach_prop.window = window;
7942 attach_prop.mimetype_entry = mimetype_entry;
7943 attach_prop.encoding_optmenu = optmenu;
7944 attach_prop.path_entry = path_entry;
7945 attach_prop.filename_entry = filename_entry;
7946 attach_prop.ok_btn = ok_btn;
7947 attach_prop.cancel_btn = cancel_btn;
7950 #undef SET_LABEL_AND_ENTRY
7952 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
7958 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
7964 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
7965 gboolean *cancelled)
7973 static gboolean attach_property_key_pressed(GtkWidget *widget,
7975 gboolean *cancelled)
7977 if (event && event->keyval == GDK_Escape) {
7984 static void compose_exec_ext_editor(Compose *compose)
7991 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
7992 G_DIR_SEPARATOR, compose);
7994 if (pipe(pipe_fds) < 0) {
8000 if ((pid = fork()) < 0) {
8007 /* close the write side of the pipe */
8010 compose->exteditor_file = g_strdup(tmp);
8011 compose->exteditor_pid = pid;
8013 compose_set_ext_editor_sensitive(compose, FALSE);
8015 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
8016 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
8020 } else { /* process-monitoring process */
8026 /* close the read side of the pipe */
8029 if (compose_write_body_to_file(compose, tmp) < 0) {
8030 fd_write_all(pipe_fds[1], "2\n", 2);
8034 pid_ed = compose_exec_ext_editor_real(tmp);
8036 fd_write_all(pipe_fds[1], "1\n", 2);
8040 /* wait until editor is terminated */
8041 waitpid(pid_ed, NULL, 0);
8043 fd_write_all(pipe_fds[1], "0\n", 2);
8050 #endif /* G_OS_UNIX */
8054 static gint compose_exec_ext_editor_real(const gchar *file)
8061 g_return_val_if_fail(file != NULL, -1);
8063 if ((pid = fork()) < 0) {
8068 if (pid != 0) return pid;
8070 /* grandchild process */
8072 if (setpgid(0, getppid()))
8075 if (prefs_common.ext_editor_cmd &&
8076 (p = strchr(prefs_common.ext_editor_cmd, '%')) &&
8077 *(p + 1) == 's' && !strchr(p + 2, '%')) {
8078 g_snprintf(buf, sizeof(buf), prefs_common.ext_editor_cmd, file);
8080 if (prefs_common.ext_editor_cmd)
8081 g_warning("External editor command line is invalid: '%s'\n",
8082 prefs_common.ext_editor_cmd);
8083 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
8086 cmdline = strsplit_with_quote(buf, " ", 1024);
8087 execvp(cmdline[0], cmdline);
8090 g_strfreev(cmdline);
8095 static gboolean compose_ext_editor_kill(Compose *compose)
8097 pid_t pgid = compose->exteditor_pid * -1;
8100 ret = kill(pgid, 0);
8102 if (ret == 0 || (ret == -1 && EPERM == errno)) {
8106 msg = g_strdup_printf
8107 (_("The external editor is still working.\n"
8108 "Force terminating the process?\n"
8109 "process group id: %d"), -pgid);
8110 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
8111 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
8115 if (val == G_ALERTALTERNATE) {
8116 g_source_remove(compose->exteditor_tag);
8117 g_io_channel_shutdown(compose->exteditor_ch,
8119 g_io_channel_unref(compose->exteditor_ch);
8121 if (kill(pgid, SIGTERM) < 0) perror("kill");
8122 waitpid(compose->exteditor_pid, NULL, 0);
8124 g_warning("Terminated process group id: %d", -pgid);
8125 g_warning("Temporary file: %s",
8126 compose->exteditor_file);
8128 compose_set_ext_editor_sensitive(compose, TRUE);
8130 g_free(compose->exteditor_file);
8131 compose->exteditor_file = NULL;
8132 compose->exteditor_pid = -1;
8133 compose->exteditor_ch = NULL;
8134 compose->exteditor_tag = -1;
8142 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
8146 Compose *compose = (Compose *)data;
8149 debug_print(_("Compose: input from monitoring process\n"));
8151 g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL);
8153 g_io_channel_shutdown(source, FALSE, NULL);
8154 g_io_channel_unref(source);
8156 waitpid(compose->exteditor_pid, NULL, 0);
8158 if (buf[0] == '0') { /* success */
8159 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
8160 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
8162 gtk_text_buffer_set_text(buffer, "", -1);
8163 compose_insert_file(compose, compose->exteditor_file);
8164 compose_changed_cb(NULL, compose);
8166 if (g_unlink(compose->exteditor_file) < 0)
8167 FILE_OP_ERROR(compose->exteditor_file, "unlink");
8168 } else if (buf[0] == '1') { /* failed */
8169 g_warning("Couldn't exec external editor\n");
8170 if (g_unlink(compose->exteditor_file) < 0)
8171 FILE_OP_ERROR(compose->exteditor_file, "unlink");
8172 } else if (buf[0] == '2') {
8173 g_warning("Couldn't write to file\n");
8174 } else if (buf[0] == '3') {
8175 g_warning("Pipe read failed\n");
8178 compose_set_ext_editor_sensitive(compose, TRUE);
8180 g_free(compose->exteditor_file);
8181 compose->exteditor_file = NULL;
8182 compose->exteditor_pid = -1;
8183 compose->exteditor_ch = NULL;
8184 compose->exteditor_tag = -1;
8189 static void compose_set_ext_editor_sensitive(Compose *compose,
8192 GtkItemFactory *ifactory;
8194 ifactory = gtk_item_factory_from_widget(compose->menubar);
8196 menu_set_sensitive(ifactory, "/Message/Send", sensitive);
8197 menu_set_sensitive(ifactory, "/Message/Send later", sensitive);
8198 menu_set_sensitive(ifactory, "/Message/Insert file", sensitive);
8199 menu_set_sensitive(ifactory, "/Message/Insert signature", sensitive);
8200 menu_set_sensitive(ifactory, "/Edit/Wrap current paragraph", sensitive);
8201 menu_set_sensitive(ifactory, "/Edit/Wrap all long lines", sensitive);
8202 menu_set_sensitive(ifactory, "/Edit/Edit with external editor",
8205 gtk_widget_set_sensitive(compose->text, sensitive);
8206 if (compose->toolbar->send_btn)
8207 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
8208 if (compose->toolbar->sendl_btn)
8209 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
8210 if (compose->toolbar->draft_btn)
8211 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
8212 if (compose->toolbar->insert_btn)
8213 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
8214 if (compose->toolbar->sig_btn)
8215 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
8216 if (compose->toolbar->exteditor_btn)
8217 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
8218 if (compose->toolbar->linewrap_current_btn)
8219 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
8220 if (compose->toolbar->linewrap_all_btn)
8221 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
8223 #endif /* G_OS_UNIX */
8226 * compose_undo_state_changed:
8228 * Change the sensivity of the menuentries undo and redo
8230 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
8231 gint redo_state, gpointer data)
8233 GtkWidget *widget = GTK_WIDGET(data);
8234 GtkItemFactory *ifactory;
8236 g_return_if_fail(widget != NULL);
8238 ifactory = gtk_item_factory_from_widget(widget);
8240 switch (undo_state) {
8241 case UNDO_STATE_TRUE:
8242 if (!undostruct->undo_state) {
8243 undostruct->undo_state = TRUE;
8244 menu_set_sensitive(ifactory, "/Edit/Undo", TRUE);
8247 case UNDO_STATE_FALSE:
8248 if (undostruct->undo_state) {
8249 undostruct->undo_state = FALSE;
8250 menu_set_sensitive(ifactory, "/Edit/Undo", FALSE);
8253 case UNDO_STATE_UNCHANGED:
8255 case UNDO_STATE_REFRESH:
8256 menu_set_sensitive(ifactory, "/Edit/Undo",
8257 undostruct->undo_state);
8260 g_warning("Undo state not recognized");
8264 switch (redo_state) {
8265 case UNDO_STATE_TRUE:
8266 if (!undostruct->redo_state) {
8267 undostruct->redo_state = TRUE;
8268 menu_set_sensitive(ifactory, "/Edit/Redo", TRUE);
8271 case UNDO_STATE_FALSE:
8272 if (undostruct->redo_state) {
8273 undostruct->redo_state = FALSE;
8274 menu_set_sensitive(ifactory, "/Edit/Redo", FALSE);
8277 case UNDO_STATE_UNCHANGED:
8279 case UNDO_STATE_REFRESH:
8280 menu_set_sensitive(ifactory, "/Edit/Redo",
8281 undostruct->redo_state);
8284 g_warning("Redo state not recognized");
8289 /* callback functions */
8291 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
8292 * includes "non-client" (windows-izm) in calculation, so this calculation
8293 * may not be accurate.
8295 static gboolean compose_edit_size_alloc(GtkEditable *widget,
8296 GtkAllocation *allocation,
8297 GtkSHRuler *shruler)
8299 if (prefs_common.show_ruler) {
8300 gint char_width = 0, char_height = 0;
8301 gint line_width_in_chars;
8303 gtkut_get_font_size(GTK_WIDGET(widget),
8304 &char_width, &char_height);
8305 line_width_in_chars =
8306 (allocation->width - allocation->x) / char_width;
8308 /* got the maximum */
8309 gtk_ruler_set_range(GTK_RULER(shruler),
8310 0.0, line_width_in_chars, 0,
8311 /*line_width_in_chars*/ char_width);
8317 static void account_activated(GtkComboBox *optmenu, gpointer data)
8319 Compose *compose = (Compose *)data;
8322 gchar *folderidentifier;
8323 gint account_id = 0;
8327 /* Get ID of active account in the combo box */
8328 menu = gtk_combo_box_get_model(optmenu);
8329 gtk_combo_box_get_active_iter(optmenu, &iter);
8330 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
8332 ac = account_find_from_id(account_id);
8333 g_return_if_fail(ac != NULL);
8335 if (ac != compose->account)
8336 compose_select_account(compose, ac, FALSE);
8338 /* Set message save folder */
8339 if (account_get_special_folder(compose->account, F_OUTBOX)) {
8340 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
8342 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
8343 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
8345 gtk_editable_delete_text(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
8346 if (account_get_special_folder(compose->account, F_OUTBOX)) {
8347 folderidentifier = folder_item_get_identifier(account_get_special_folder
8348 (compose->account, F_OUTBOX));
8349 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
8350 g_free(folderidentifier);
8354 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
8355 GtkTreeViewColumn *column, Compose *compose)
8357 compose_attach_property(compose);
8360 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
8363 Compose *compose = (Compose *)data;
8364 GtkTreeSelection *attach_selection;
8365 gint attach_nr_selected;
8366 GtkItemFactory *ifactory;
8368 if (!event) return FALSE;
8370 if (event->button == 3) {
8371 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
8372 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
8373 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
8375 if (attach_nr_selected > 0)
8377 menu_set_sensitive(ifactory, "/Remove", TRUE);
8378 menu_set_sensitive(ifactory, "/Properties...", TRUE);
8380 menu_set_sensitive(ifactory, "/Remove", FALSE);
8381 menu_set_sensitive(ifactory, "/Properties...", FALSE);
8384 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
8385 NULL, NULL, event->button, event->time);
8392 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
8395 Compose *compose = (Compose *)data;
8397 if (!event) return FALSE;
8399 switch (event->keyval) {
8401 compose_attach_remove_selected(compose);
8407 static void compose_allow_user_actions (Compose *compose, gboolean allow)
8409 GtkItemFactory *ifactory = gtk_item_factory_from_widget(compose->menubar);
8410 toolbar_comp_set_sensitive(compose, allow);
8411 menu_set_sensitive(ifactory, "/Message", allow);
8412 menu_set_sensitive(ifactory, "/Edit", allow);
8414 menu_set_sensitive(ifactory, "/Spelling", allow);
8416 menu_set_sensitive(ifactory, "/Options", allow);
8417 menu_set_sensitive(ifactory, "/Tools", allow);
8418 menu_set_sensitive(ifactory, "/Help", allow);
8420 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
8424 static void compose_send_cb(gpointer data, guint action, GtkWidget *widget)
8426 Compose *compose = (Compose *)data;
8428 if (prefs_common.work_offline &&
8429 !inc_offline_should_override(TRUE,
8430 _("Claws Mail needs network access in order "
8431 "to send this email.")))
8434 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
8435 g_source_remove(compose->draft_timeout_tag);
8436 compose->draft_timeout_tag = -1;
8439 compose_send(compose);
8442 static void compose_send_later_cb(gpointer data, guint action,
8445 Compose *compose = (Compose *)data;
8449 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
8453 compose_close(compose);
8454 } else if (val == -1) {
8455 alertpanel_error(_("Could not queue message."));
8456 } else if (val == -2) {
8457 alertpanel_error(_("Could not queue message:\n\n%s."), strerror(errno));
8458 } else if (val == -3) {
8459 if (privacy_peek_error())
8460 alertpanel_error(_("Could not queue message for sending:\n\n"
8461 "Signature failed: %s"), privacy_get_error());
8462 } else if (val == -4) {
8463 alertpanel_error(_("Could not queue message for sending:\n\n"
8464 "Charset conversion failed."));
8465 } else if (val == -5) {
8466 alertpanel_error(_("Could not queue message for sending:\n\n"
8467 "Couldn't get recipient encryption key."));
8468 } else if (val == -6) {
8471 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
8474 #define DRAFTED_AT_EXIT "drafted_at_exit"
8475 static void compose_register_draft(MsgInfo *info)
8477 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8478 DRAFTED_AT_EXIT, NULL);
8479 FILE *fp = fopen(filepath, "ab");
8482 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
8490 gboolean compose_draft (gpointer data, guint action)
8492 Compose *compose = (Compose *)data;
8496 MsgFlags flag = {0, 0};
8497 static gboolean lock = FALSE;
8498 MsgInfo *newmsginfo;
8500 gboolean target_locked = FALSE;
8501 gboolean err = FALSE;
8503 if (lock) return FALSE;
8505 if (compose->sending)
8508 draft = account_get_special_folder(compose->account, F_DRAFT);
8509 g_return_val_if_fail(draft != NULL, FALSE);
8511 if (!g_mutex_trylock(compose->mutex)) {
8512 /* we don't want to lock the mutex once it's available,
8513 * because as the only other part of compose.c locking
8514 * it is compose_close - which means once unlocked,
8515 * the compose struct will be freed */
8516 debug_print("couldn't lock mutex, probably sending\n");
8522 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
8523 G_DIR_SEPARATOR, compose);
8524 if ((fp = g_fopen(tmp, "wb")) == NULL) {
8525 FILE_OP_ERROR(tmp, "fopen");
8529 /* chmod for security */
8530 if (change_file_mode_rw(fp, tmp) < 0) {
8531 FILE_OP_ERROR(tmp, "chmod");
8532 g_warning("can't change file mode\n");
8535 /* Save draft infos */
8536 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
8537 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
8539 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
8540 gchar *savefolderid;
8542 savefolderid = gtk_editable_get_chars(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
8543 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
8544 g_free(savefolderid);
8546 if (compose->return_receipt) {
8547 err |= (fprintf(fp, "RRCPT:1\n") < 0);
8549 if (compose->privacy_system) {
8550 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
8551 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
8552 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
8555 /* Message-ID of message replying to */
8556 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
8559 folderid = folder_item_get_identifier(compose->replyinfo->folder);
8560 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
8563 /* Message-ID of message forwarding to */
8564 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
8567 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
8568 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
8572 /* end of headers */
8573 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
8580 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
8584 if (fclose(fp) == EOF) {
8588 if (compose->targetinfo) {
8589 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
8590 flag.perm_flags = target_locked?MSG_LOCKED:0;
8592 flag.tmp_flags = MSG_DRAFT;
8594 folder_item_scan(draft);
8595 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
8596 MsgInfo *tmpinfo = NULL;
8597 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
8598 if (compose->msgid) {
8599 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
8602 msgnum = tmpinfo->msgnum;
8603 procmsg_msginfo_free(tmpinfo);
8604 debug_print("got draft msgnum %d from scanning\n", msgnum);
8606 debug_print("didn't get draft msgnum after scanning\n");
8609 debug_print("got draft msgnum %d from adding\n", msgnum);
8615 if (action != COMPOSE_AUTO_SAVE) {
8616 if (action != COMPOSE_DRAFT_FOR_EXIT)
8617 alertpanel_error(_("Could not save draft."));
8620 gtkut_window_popup(compose->window);
8621 val = alertpanel_full(_("Could not save draft"),
8622 _("Could not save draft.\n"
8623 "Do you want to cancel exit or discard this email?"),
8624 _("_Cancel exit"), _("_Discard email"), NULL,
8625 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
8626 if (val == G_ALERTALTERNATE) {
8628 g_mutex_unlock(compose->mutex); /* must be done before closing */
8629 compose_close(compose);
8633 g_mutex_unlock(compose->mutex); /* must be done before closing */
8642 if (compose->mode == COMPOSE_REEDIT) {
8643 compose_remove_reedit_target(compose, TRUE);
8646 newmsginfo = folder_item_get_msginfo(draft, msgnum);
8649 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
8651 procmsg_msginfo_set_flags(newmsginfo, MSG_LOCKED, MSG_DRAFT);
8653 procmsg_msginfo_set_flags(newmsginfo, 0, MSG_DRAFT);
8654 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
8655 procmsg_msginfo_set_flags(newmsginfo, 0,
8656 MSG_HAS_ATTACHMENT);
8658 if (action == COMPOSE_DRAFT_FOR_EXIT) {
8659 compose_register_draft(newmsginfo);
8661 procmsg_msginfo_free(newmsginfo);
8664 folder_item_scan(draft);
8666 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
8668 g_mutex_unlock(compose->mutex); /* must be done before closing */
8669 compose_close(compose);
8675 path = folder_item_fetch_msg(draft, msgnum);
8677 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
8680 if (g_stat(path, &s) < 0) {
8681 FILE_OP_ERROR(path, "stat");
8687 procmsg_msginfo_free(compose->targetinfo);
8688 compose->targetinfo = procmsg_msginfo_new();
8689 compose->targetinfo->msgnum = msgnum;
8690 compose->targetinfo->size = s.st_size;
8691 compose->targetinfo->mtime = s.st_mtime;
8692 compose->targetinfo->folder = draft;
8694 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
8695 compose->mode = COMPOSE_REEDIT;
8697 if (action == COMPOSE_AUTO_SAVE) {
8698 compose->autosaved_draft = compose->targetinfo;
8700 compose->modified = FALSE;
8701 compose_set_title(compose);
8705 g_mutex_unlock(compose->mutex);
8709 void compose_clear_exit_drafts(void)
8711 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8712 DRAFTED_AT_EXIT, NULL);
8713 if (is_file_exist(filepath))
8719 void compose_reopen_exit_drafts(void)
8721 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8722 DRAFTED_AT_EXIT, NULL);
8723 FILE *fp = fopen(filepath, "rb");
8727 while (fgets(buf, sizeof(buf), fp)) {
8728 gchar **parts = g_strsplit(buf, "\t", 2);
8729 const gchar *folder = parts[0];
8730 int msgnum = parts[1] ? atoi(parts[1]):-1;
8732 if (folder && *folder && msgnum > -1) {
8733 FolderItem *item = folder_find_item_from_identifier(folder);
8734 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
8736 compose_reedit(info, FALSE);
8743 compose_clear_exit_drafts();
8746 static void compose_draft_cb(gpointer data, guint action, GtkWidget *widget)
8748 compose_draft(data, action);
8751 static void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
8753 if (compose && file_list) {
8756 for ( tmp = file_list; tmp; tmp = tmp->next) {
8757 gchar *file = (gchar *) tmp->data;
8758 gchar *utf8_filename = conv_filename_to_utf8(file);
8759 compose_attach_append(compose, file, utf8_filename, NULL);
8760 compose_changed_cb(NULL, compose);
8765 g_free(utf8_filename);
8770 static void compose_attach_cb(gpointer data, guint action, GtkWidget *widget)
8772 Compose *compose = (Compose *)data;
8775 if (compose->redirect_filename != NULL)
8778 file_list = filesel_select_multiple_files_open(_("Select file"));
8781 compose_attach_from_list(compose, file_list, TRUE);
8782 g_list_free(file_list);
8786 static void compose_insert_file_cb(gpointer data, guint action,
8789 Compose *compose = (Compose *)data;
8792 file_list = filesel_select_multiple_files_open(_("Select file"));
8797 for ( tmp = file_list; tmp; tmp = tmp->next) {
8798 gchar *file = (gchar *) tmp->data;
8799 gchar *filedup = g_strdup(file);
8800 gchar *shortfile = g_path_get_basename(filedup);
8801 ComposeInsertResult res;
8803 res = compose_insert_file(compose, file);
8804 if (res == COMPOSE_INSERT_READ_ERROR) {
8805 alertpanel_error(_("File '%s' could not be read."), shortfile);
8806 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
8807 alertpanel_error(_("File '%s' contained invalid characters\n"
8808 "for the current encoding, insertion may be incorrect."), shortfile);
8814 g_list_free(file_list);
8818 static void compose_insert_sig_cb(gpointer data, guint action,
8821 Compose *compose = (Compose *)data;
8823 compose_insert_sig(compose, FALSE);
8826 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
8830 Compose *compose = (Compose *)data;
8832 gtkut_widget_get_uposition(widget, &x, &y);
8833 prefs_common.compose_x = x;
8834 prefs_common.compose_y = y;
8836 if (compose->sending || compose->updating)
8838 compose_close_cb(compose, 0, NULL);
8842 void compose_close_toolbar(Compose *compose)
8844 compose_close_cb(compose, 0, NULL);
8847 static void compose_close_cb(gpointer data, guint action, GtkWidget *widget)
8849 Compose *compose = (Compose *)data;
8853 if (compose->exteditor_tag != -1) {
8854 if (!compose_ext_editor_kill(compose))
8859 if (compose->modified) {
8860 val = alertpanel(_("Discard message"),
8861 _("This message has been modified. Discard it?"),
8862 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
8865 case G_ALERTDEFAULT:
8866 if (prefs_common.autosave)
8867 compose_remove_draft(compose);
8869 case G_ALERTALTERNATE:
8870 compose_draft_cb(data, COMPOSE_QUIT_EDITING, NULL);
8877 compose_close(compose);
8880 static void compose_set_encoding_cb(gpointer data, guint action,
8883 Compose *compose = (Compose *)data;
8885 if (GTK_CHECK_MENU_ITEM(widget)->active)
8886 compose->out_encoding = (CharSet)action;
8889 static void compose_address_cb(gpointer data, guint action, GtkWidget *widget)
8891 Compose *compose = (Compose *)data;
8893 addressbook_open(compose);
8896 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
8898 Compose *compose = (Compose *)data;
8903 tmpl = g_object_get_data(G_OBJECT(widget), "template");
8904 g_return_if_fail(tmpl != NULL);
8906 msg = g_strdup_printf(_("Do you want to apply the template '%s' ?"),
8908 val = alertpanel(_("Apply template"), msg,
8909 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
8912 if (val == G_ALERTDEFAULT)
8913 compose_template_apply(compose, tmpl, TRUE);
8914 else if (val == G_ALERTALTERNATE)
8915 compose_template_apply(compose, tmpl, FALSE);
8918 static void compose_ext_editor_cb(gpointer data, guint action,
8921 Compose *compose = (Compose *)data;
8923 compose_exec_ext_editor(compose);
8926 static void compose_undo_cb(Compose *compose)
8928 gboolean prev_autowrap = compose->autowrap;
8930 compose->autowrap = FALSE;
8931 undo_undo(compose->undostruct);
8932 compose->autowrap = prev_autowrap;
8935 static void compose_redo_cb(Compose *compose)
8937 gboolean prev_autowrap = compose->autowrap;
8939 compose->autowrap = FALSE;
8940 undo_redo(compose->undostruct);
8941 compose->autowrap = prev_autowrap;
8944 static void entry_cut_clipboard(GtkWidget *entry)
8946 if (GTK_IS_EDITABLE(entry))
8947 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
8948 else if (GTK_IS_TEXT_VIEW(entry))
8949 gtk_text_buffer_cut_clipboard(
8950 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
8951 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
8955 static void entry_copy_clipboard(GtkWidget *entry)
8957 if (GTK_IS_EDITABLE(entry))
8958 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
8959 else if (GTK_IS_TEXT_VIEW(entry))
8960 gtk_text_buffer_copy_clipboard(
8961 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
8962 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
8965 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
8966 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
8968 if (GTK_IS_TEXT_VIEW(entry)) {
8969 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
8970 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
8971 GtkTextIter start_iter, end_iter;
8973 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
8975 if (contents == NULL)
8978 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
8980 /* we shouldn't delete the selection when middle-click-pasting, or we
8981 * can't mid-click-paste our own selection */
8982 if (clip != GDK_SELECTION_PRIMARY) {
8983 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
8986 if (insert_place == NULL) {
8987 /* if insert_place isn't specified, insert at the cursor.
8988 * used for Ctrl-V pasting */
8989 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
8990 start = gtk_text_iter_get_offset(&start_iter);
8991 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
8993 /* if insert_place is specified, paste here.
8994 * used for mid-click-pasting */
8995 start = gtk_text_iter_get_offset(insert_place);
8996 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
9000 /* paste unwrapped: mark the paste so it's not wrapped later */
9001 end = start + strlen(contents);
9002 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
9003 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
9004 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
9005 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
9006 /* rewrap paragraph now (after a mid-click-paste) */
9007 mark_start = gtk_text_buffer_get_insert(buffer);
9008 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
9009 gtk_text_iter_backward_char(&start_iter);
9010 compose_beautify_paragraph(compose, &start_iter, TRUE);
9012 } else if (GTK_IS_EDITABLE(entry))
9013 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
9017 static void entry_allsel(GtkWidget *entry)
9019 if (GTK_IS_EDITABLE(entry))
9020 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
9021 else if (GTK_IS_TEXT_VIEW(entry)) {
9022 GtkTextIter startiter, enditer;
9023 GtkTextBuffer *textbuf;
9025 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
9026 gtk_text_buffer_get_start_iter(textbuf, &startiter);
9027 gtk_text_buffer_get_end_iter(textbuf, &enditer);
9029 gtk_text_buffer_move_mark_by_name(textbuf,
9030 "selection_bound", &startiter);
9031 gtk_text_buffer_move_mark_by_name(textbuf,
9032 "insert", &enditer);
9036 static void compose_cut_cb(Compose *compose)
9038 if (compose->focused_editable
9040 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9043 entry_cut_clipboard(compose->focused_editable);
9046 static void compose_copy_cb(Compose *compose)
9048 if (compose->focused_editable
9050 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9053 entry_copy_clipboard(compose->focused_editable);
9056 static void compose_paste_cb(Compose *compose)
9059 GtkTextBuffer *buffer;
9061 if (compose->focused_editable &&
9062 GTK_WIDGET_HAS_FOCUS(compose->focused_editable))
9063 entry_paste_clipboard(compose, compose->focused_editable,
9064 prefs_common.linewrap_pastes,
9065 GDK_SELECTION_CLIPBOARD, NULL);
9069 static void compose_paste_as_quote_cb(Compose *compose)
9071 gint wrap_quote = prefs_common.linewrap_quote;
9072 if (compose->focused_editable
9074 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9077 /* let text_insert() (called directly or at a later time
9078 * after the gtk_editable_paste_clipboard) know that
9079 * text is to be inserted as a quotation. implemented
9080 * by using a simple refcount... */
9081 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
9082 G_OBJECT(compose->focused_editable),
9083 "paste_as_quotation"));
9084 g_object_set_data(G_OBJECT(compose->focused_editable),
9085 "paste_as_quotation",
9086 GINT_TO_POINTER(paste_as_quotation + 1));
9087 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
9088 entry_paste_clipboard(compose, compose->focused_editable,
9089 prefs_common.linewrap_pastes,
9090 GDK_SELECTION_CLIPBOARD, NULL);
9091 prefs_common.linewrap_quote = wrap_quote;
9095 static void compose_paste_no_wrap_cb(Compose *compose)
9098 GtkTextBuffer *buffer;
9100 if (compose->focused_editable
9102 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9105 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
9106 GDK_SELECTION_CLIPBOARD, NULL);
9110 static void compose_paste_wrap_cb(Compose *compose)
9113 GtkTextBuffer *buffer;
9115 if (compose->focused_editable
9117 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9120 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
9121 GDK_SELECTION_CLIPBOARD, NULL);
9125 static void compose_allsel_cb(Compose *compose)
9127 if (compose->focused_editable
9129 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9132 entry_allsel(compose->focused_editable);
9135 static void textview_move_beginning_of_line (GtkTextView *text)
9137 GtkTextBuffer *buffer;
9141 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9143 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9144 mark = gtk_text_buffer_get_insert(buffer);
9145 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9146 gtk_text_iter_set_line_offset(&ins, 0);
9147 gtk_text_buffer_place_cursor(buffer, &ins);
9150 static void textview_move_forward_character (GtkTextView *text)
9152 GtkTextBuffer *buffer;
9156 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9158 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9159 mark = gtk_text_buffer_get_insert(buffer);
9160 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9161 if (gtk_text_iter_forward_cursor_position(&ins))
9162 gtk_text_buffer_place_cursor(buffer, &ins);
9165 static void textview_move_backward_character (GtkTextView *text)
9167 GtkTextBuffer *buffer;
9171 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9173 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9174 mark = gtk_text_buffer_get_insert(buffer);
9175 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9176 if (gtk_text_iter_backward_cursor_position(&ins))
9177 gtk_text_buffer_place_cursor(buffer, &ins);
9180 static void textview_move_forward_word (GtkTextView *text)
9182 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 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
9193 if (gtk_text_iter_forward_word_ends(&ins, count)) {
9194 gtk_text_iter_backward_word_start(&ins);
9195 gtk_text_buffer_place_cursor(buffer, &ins);
9199 static void textview_move_backward_word (GtkTextView *text)
9201 GtkTextBuffer *buffer;
9206 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9208 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9209 mark = gtk_text_buffer_get_insert(buffer);
9210 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9211 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
9212 if (gtk_text_iter_backward_word_starts(&ins, 1))
9213 gtk_text_buffer_place_cursor(buffer, &ins);
9216 static void textview_move_end_of_line (GtkTextView *text)
9218 GtkTextBuffer *buffer;
9222 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9224 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9225 mark = gtk_text_buffer_get_insert(buffer);
9226 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9227 if (gtk_text_iter_forward_to_line_end(&ins))
9228 gtk_text_buffer_place_cursor(buffer, &ins);
9231 static void textview_move_next_line (GtkTextView *text)
9233 GtkTextBuffer *buffer;
9238 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9240 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9241 mark = gtk_text_buffer_get_insert(buffer);
9242 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9243 offset = gtk_text_iter_get_line_offset(&ins);
9244 if (gtk_text_iter_forward_line(&ins)) {
9245 gtk_text_iter_set_line_offset(&ins, offset);
9246 gtk_text_buffer_place_cursor(buffer, &ins);
9250 static void textview_move_previous_line (GtkTextView *text)
9252 GtkTextBuffer *buffer;
9257 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9259 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9260 mark = gtk_text_buffer_get_insert(buffer);
9261 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9262 offset = gtk_text_iter_get_line_offset(&ins);
9263 if (gtk_text_iter_backward_line(&ins)) {
9264 gtk_text_iter_set_line_offset(&ins, offset);
9265 gtk_text_buffer_place_cursor(buffer, &ins);
9269 static void textview_delete_forward_character (GtkTextView *text)
9271 GtkTextBuffer *buffer;
9273 GtkTextIter ins, end_iter;
9275 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9277 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9278 mark = gtk_text_buffer_get_insert(buffer);
9279 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9281 if (gtk_text_iter_forward_char(&end_iter)) {
9282 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9286 static void textview_delete_backward_character (GtkTextView *text)
9288 GtkTextBuffer *buffer;
9290 GtkTextIter ins, end_iter;
9292 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9294 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9295 mark = gtk_text_buffer_get_insert(buffer);
9296 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9298 if (gtk_text_iter_backward_char(&end_iter)) {
9299 gtk_text_buffer_delete(buffer, &end_iter, &ins);
9303 static void textview_delete_forward_word (GtkTextView *text)
9305 GtkTextBuffer *buffer;
9307 GtkTextIter ins, end_iter;
9309 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9311 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9312 mark = gtk_text_buffer_get_insert(buffer);
9313 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9315 if (gtk_text_iter_forward_word_end(&end_iter)) {
9316 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9320 static void textview_delete_backward_word (GtkTextView *text)
9322 GtkTextBuffer *buffer;
9324 GtkTextIter ins, end_iter;
9326 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9328 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9329 mark = gtk_text_buffer_get_insert(buffer);
9330 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9332 if (gtk_text_iter_backward_word_start(&end_iter)) {
9333 gtk_text_buffer_delete(buffer, &end_iter, &ins);
9337 static void textview_delete_line (GtkTextView *text)
9339 GtkTextBuffer *buffer;
9341 GtkTextIter ins, start_iter, end_iter;
9344 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9346 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9347 mark = gtk_text_buffer_get_insert(buffer);
9348 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9351 gtk_text_iter_set_line_offset(&start_iter, 0);
9354 if (gtk_text_iter_ends_line(&end_iter))
9355 found = gtk_text_iter_forward_char(&end_iter);
9357 found = gtk_text_iter_forward_to_line_end(&end_iter);
9360 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
9363 static void textview_delete_to_line_end (GtkTextView *text)
9365 GtkTextBuffer *buffer;
9367 GtkTextIter ins, end_iter;
9370 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9372 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9373 mark = gtk_text_buffer_get_insert(buffer);
9374 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9376 if (gtk_text_iter_ends_line(&end_iter))
9377 found = gtk_text_iter_forward_char(&end_iter);
9379 found = gtk_text_iter_forward_to_line_end(&end_iter);
9381 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9384 static void compose_advanced_action_cb(Compose *compose,
9385 ComposeCallAdvancedAction action)
9387 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9389 void (*do_action) (GtkTextView *text);
9390 } action_table[] = {
9391 {textview_move_beginning_of_line},
9392 {textview_move_forward_character},
9393 {textview_move_backward_character},
9394 {textview_move_forward_word},
9395 {textview_move_backward_word},
9396 {textview_move_end_of_line},
9397 {textview_move_next_line},
9398 {textview_move_previous_line},
9399 {textview_delete_forward_character},
9400 {textview_delete_backward_character},
9401 {textview_delete_forward_word},
9402 {textview_delete_backward_word},
9403 {textview_delete_line},
9404 {NULL}, /* gtk_stext_delete_line_n */
9405 {textview_delete_to_line_end}
9408 if (!GTK_WIDGET_HAS_FOCUS(text)) return;
9410 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
9411 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
9412 if (action_table[action].do_action)
9413 action_table[action].do_action(text);
9415 g_warning("Not implemented yet.");
9419 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
9423 if (GTK_IS_EDITABLE(widget)) {
9424 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
9425 gtk_editable_set_position(GTK_EDITABLE(widget),
9428 if (widget->parent && widget->parent->parent
9429 && widget->parent->parent->parent) {
9430 if (GTK_IS_SCROLLED_WINDOW(widget->parent->parent->parent)) {
9431 gint y = widget->allocation.y;
9432 gint height = widget->allocation.height;
9433 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
9434 (GTK_SCROLLED_WINDOW(widget->parent->parent->parent));
9436 if (y < (int)shown->value) {
9437 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown), y - 1);
9439 if (y + height > (int)shown->value + (int)shown->page_size) {
9440 if (y - height - 1 < (int)shown->upper - (int)shown->page_size) {
9441 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
9442 y + height - (int)shown->page_size - 1);
9444 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
9445 (int)shown->upper - (int)shown->page_size - 1);
9452 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
9453 compose->focused_editable = widget;
9456 if (GTK_IS_TEXT_VIEW(widget)
9457 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
9458 gtk_widget_ref(compose->notebook);
9459 gtk_widget_ref(compose->edit_vbox);
9460 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
9461 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
9462 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
9463 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
9464 gtk_widget_unref(compose->notebook);
9465 gtk_widget_unref(compose->edit_vbox);
9466 g_signal_handlers_block_by_func(G_OBJECT(widget),
9467 G_CALLBACK(compose_grab_focus_cb),
9469 gtk_widget_grab_focus(widget);
9470 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
9471 G_CALLBACK(compose_grab_focus_cb),
9473 } else if (!GTK_IS_TEXT_VIEW(widget)
9474 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
9475 gtk_widget_ref(compose->notebook);
9476 gtk_widget_ref(compose->edit_vbox);
9477 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
9478 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
9479 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
9480 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
9481 gtk_widget_unref(compose->notebook);
9482 gtk_widget_unref(compose->edit_vbox);
9483 g_signal_handlers_block_by_func(G_OBJECT(widget),
9484 G_CALLBACK(compose_grab_focus_cb),
9486 gtk_widget_grab_focus(widget);
9487 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
9488 G_CALLBACK(compose_grab_focus_cb),
9494 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
9496 compose->modified = TRUE;
9498 compose_set_title(compose);
9502 static void compose_wrap_cb(gpointer data, guint action, GtkWidget *widget)
9504 Compose *compose = (Compose *)data;
9507 compose_wrap_all_full(compose, TRUE);
9509 compose_beautify_paragraph(compose, NULL, TRUE);
9512 static void compose_find_cb(gpointer data, guint action, GtkWidget *widget)
9514 Compose *compose = (Compose *)data;
9516 message_search_compose(compose);
9519 static void compose_toggle_autowrap_cb(gpointer data, guint action,
9522 Compose *compose = (Compose *)data;
9523 compose->autowrap = GTK_CHECK_MENU_ITEM(widget)->active;
9524 if (compose->autowrap)
9525 compose_wrap_all_full(compose, TRUE);
9526 compose->autowrap = GTK_CHECK_MENU_ITEM(widget)->active;
9529 static void compose_toggle_sign_cb(gpointer data, guint action,
9532 Compose *compose = (Compose *)data;
9534 if (GTK_CHECK_MENU_ITEM(widget)->active)
9535 compose->use_signing = TRUE;
9537 compose->use_signing = FALSE;
9540 static void compose_toggle_encrypt_cb(gpointer data, guint action,
9543 Compose *compose = (Compose *)data;
9545 if (GTK_CHECK_MENU_ITEM(widget)->active)
9546 compose->use_encryption = TRUE;
9548 compose->use_encryption = FALSE;
9551 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
9553 g_free(compose->privacy_system);
9555 compose->privacy_system = g_strdup(account->default_privacy_system);
9556 compose_update_privacy_system_menu_item(compose, warn);
9559 static void compose_toggle_ruler_cb(gpointer data, guint action,
9562 Compose *compose = (Compose *)data;
9564 if (GTK_CHECK_MENU_ITEM(widget)->active) {
9565 gtk_widget_show(compose->ruler_hbox);
9566 prefs_common.show_ruler = TRUE;
9568 gtk_widget_hide(compose->ruler_hbox);
9569 gtk_widget_queue_resize(compose->edit_vbox);
9570 prefs_common.show_ruler = FALSE;
9574 static void compose_attach_drag_received_cb (GtkWidget *widget,
9575 GdkDragContext *context,
9578 GtkSelectionData *data,
9583 Compose *compose = (Compose *)user_data;
9586 if (gdk_atom_name(data->type) &&
9587 !strcmp(gdk_atom_name(data->type), "text/uri-list")
9588 && gtk_drag_get_source_widget(context) !=
9589 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
9590 list = uri_list_extract_filenames((const gchar *)data->data);
9591 for (tmp = list; tmp != NULL; tmp = tmp->next) {
9592 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
9593 compose_attach_append
9594 (compose, (const gchar *)tmp->data,
9595 utf8_filename, NULL);
9596 g_free(utf8_filename);
9598 if (list) compose_changed_cb(NULL, compose);
9599 list_free_strings(list);
9601 } else if (gtk_drag_get_source_widget(context)
9602 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
9603 /* comes from our summaryview */
9604 SummaryView * summaryview = NULL;
9605 GSList * list = NULL, *cur = NULL;
9607 if (mainwindow_get_mainwindow())
9608 summaryview = mainwindow_get_mainwindow()->summaryview;
9611 list = summary_get_selected_msg_list(summaryview);
9613 for (cur = list; cur; cur = cur->next) {
9614 MsgInfo *msginfo = (MsgInfo *)cur->data;
9617 file = procmsg_get_message_file_full(msginfo,
9620 compose_attach_append(compose, (const gchar *)file,
9621 (const gchar *)file, "message/rfc822");
9629 static gboolean compose_drag_drop(GtkWidget *widget,
9630 GdkDragContext *drag_context,
9632 guint time, gpointer user_data)
9634 /* not handling this signal makes compose_insert_drag_received_cb
9639 static void compose_insert_drag_received_cb (GtkWidget *widget,
9640 GdkDragContext *drag_context,
9643 GtkSelectionData *data,
9648 Compose *compose = (Compose *)user_data;
9651 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
9653 if (gdk_atom_name(data->type) && !strcmp(gdk_atom_name(data->type), "text/uri-list")) {
9654 AlertValue val = G_ALERTDEFAULT;
9656 switch (prefs_common.compose_dnd_mode) {
9657 case COMPOSE_DND_ASK:
9658 val = alertpanel_full(_("Insert or attach?"),
9659 _("Do you want to insert the contents of the file(s) "
9660 "into the message body, or attach it to the email?"),
9661 GTK_STOCK_CANCEL, _("+_Insert"), _("_Attach"),
9662 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
9664 case COMPOSE_DND_INSERT:
9665 val = G_ALERTALTERNATE;
9667 case COMPOSE_DND_ATTACH:
9671 /* unexpected case */
9672 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
9675 if (val & G_ALERTDISABLE) {
9676 val &= ~G_ALERTDISABLE;
9677 /* remember what action to perform by default, only if we don't click Cancel */
9678 if (val == G_ALERTALTERNATE)
9679 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
9680 else if (val == G_ALERTOTHER)
9681 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
9684 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
9685 gtk_drag_finish(drag_context, FALSE, FALSE, time);
9687 } else if (val == G_ALERTOTHER) {
9688 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
9691 list = uri_list_extract_filenames((const gchar *)data->data);
9692 for (tmp = list; tmp != NULL; tmp = tmp->next) {
9693 compose_insert_file(compose, (const gchar *)tmp->data);
9695 list_free_strings(list);
9697 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9700 #if GTK_CHECK_VERSION(2, 8, 0)
9701 /* do nothing, handled by GTK */
9703 gchar *tmpfile = get_tmp_file();
9704 str_write_to_file((const gchar *)data->data, tmpfile);
9705 compose_insert_file(compose, tmpfile);
9708 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9712 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9715 static void compose_header_drag_received_cb (GtkWidget *widget,
9716 GdkDragContext *drag_context,
9719 GtkSelectionData *data,
9724 GtkEditable *entry = (GtkEditable *)user_data;
9725 gchar *email = (gchar *)data->data;
9727 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
9730 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
9731 gchar *decoded=g_new(gchar, strlen(email));
9734 email += strlen("mailto:");
9735 decode_uri(decoded, email); /* will fit */
9736 gtk_editable_delete_text(entry, 0, -1);
9737 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
9738 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9742 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9745 static void compose_toggle_return_receipt_cb(gpointer data, guint action,
9748 Compose *compose = (Compose *)data;
9750 if (GTK_CHECK_MENU_ITEM(widget)->active)
9751 compose->return_receipt = TRUE;
9753 compose->return_receipt = FALSE;
9756 static void compose_toggle_remove_refs_cb(gpointer data, guint action,
9759 Compose *compose = (Compose *)data;
9761 if (GTK_CHECK_MENU_ITEM(widget)->active)
9762 compose->remove_references = TRUE;
9764 compose->remove_references = FALSE;
9767 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
9769 ComposeHeaderEntry *headerentry)
9771 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
9772 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
9773 !(event->state & GDK_MODIFIER_MASK) &&
9774 (event->keyval == GDK_BackSpace) &&
9775 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
9776 gtk_container_remove
9777 (GTK_CONTAINER(headerentry->compose->header_table),
9778 headerentry->combo);
9779 gtk_container_remove
9780 (GTK_CONTAINER(headerentry->compose->header_table),
9781 headerentry->entry);
9782 headerentry->compose->header_list =
9783 g_slist_remove(headerentry->compose->header_list,
9785 g_free(headerentry);
9786 } else if (event->keyval == GDK_Tab) {
9787 if (headerentry->compose->header_last == headerentry) {
9788 /* Override default next focus, and give it to subject_entry
9789 * instead of notebook tabs
9791 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
9792 gtk_widget_grab_focus(headerentry->compose->subject_entry);
9799 static gboolean compose_headerentry_changed_cb(GtkWidget *entry,
9800 ComposeHeaderEntry *headerentry)
9802 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
9803 compose_create_header_entry(headerentry->compose);
9804 g_signal_handlers_disconnect_matched
9805 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
9806 0, 0, NULL, NULL, headerentry);
9808 /* Automatically scroll down */
9809 compose_show_first_last_header(headerentry->compose, FALSE);
9815 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
9817 GtkAdjustment *vadj;
9819 g_return_if_fail(compose);
9820 g_return_if_fail(GTK_IS_WIDGET(compose->header_table));
9821 g_return_if_fail(GTK_IS_VIEWPORT(compose->header_table->parent));
9823 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(compose->header_table->parent));
9824 gtk_adjustment_set_value(vadj, (show_first ? vadj->lower : vadj->upper));
9825 gtk_adjustment_changed(vadj);
9828 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
9829 const gchar *text, gint len, Compose *compose)
9831 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
9832 (G_OBJECT(compose->text), "paste_as_quotation"));
9835 g_return_if_fail(text != NULL);
9837 g_signal_handlers_block_by_func(G_OBJECT(buffer),
9838 G_CALLBACK(text_inserted),
9840 if (paste_as_quotation) {
9844 GtkTextIter start_iter;
9849 new_text = g_strndup(text, len);
9851 qmark = compose_quote_char_from_context(compose);
9853 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
9854 gtk_text_buffer_place_cursor(buffer, iter);
9856 pos = gtk_text_iter_get_offset(iter);
9858 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
9859 _("Quote format error at line %d."));
9860 quote_fmt_reset_vartable();
9862 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
9863 GINT_TO_POINTER(paste_as_quotation - 1));
9865 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
9866 gtk_text_buffer_place_cursor(buffer, iter);
9867 gtk_text_buffer_delete_mark(buffer, mark);
9869 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
9870 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
9871 compose_beautify_paragraph(compose, &start_iter, FALSE);
9872 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
9873 gtk_text_buffer_delete_mark(buffer, mark);
9875 if (strcmp(text, "\n") || compose->automatic_break
9876 || gtk_text_iter_starts_line(iter))
9877 gtk_text_buffer_insert(buffer, iter, text, len);
9879 debug_print("insert nowrap \\n\n");
9880 gtk_text_buffer_insert_with_tags_by_name(buffer,
9881 iter, text, len, "no_join", NULL);
9885 if (!paste_as_quotation) {
9886 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
9887 compose_beautify_paragraph(compose, iter, FALSE);
9888 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
9889 gtk_text_buffer_delete_mark(buffer, mark);
9892 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
9893 G_CALLBACK(text_inserted),
9895 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
9897 if (prefs_common.autosave &&
9898 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
9899 compose->draft_timeout_tag != -2 /* disabled while loading */)
9900 compose->draft_timeout_tag = g_timeout_add
9901 (500, (GtkFunction) compose_defer_auto_save_draft, compose);
9903 static gint compose_defer_auto_save_draft(Compose *compose)
9905 compose->draft_timeout_tag = -1;
9906 compose_draft_cb((gpointer)compose, COMPOSE_AUTO_SAVE, NULL);
9911 static void compose_check_all(Compose *compose)
9913 if (compose->gtkaspell)
9914 gtkaspell_check_all(compose->gtkaspell);
9917 static void compose_highlight_all(Compose *compose)
9919 if (compose->gtkaspell)
9920 gtkaspell_highlight_all(compose->gtkaspell);
9923 static void compose_check_backwards(Compose *compose)
9925 if (compose->gtkaspell)
9926 gtkaspell_check_backwards(compose->gtkaspell);
9928 GtkItemFactory *ifactory;
9929 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
9930 menu_set_sensitive(ifactory, "/Edit/Check backwards misspelled word", FALSE);
9931 menu_set_sensitive(ifactory, "/Edit/Forward to next misspelled word", FALSE);
9935 static void compose_check_forwards_go(Compose *compose)
9937 if (compose->gtkaspell)
9938 gtkaspell_check_forwards_go(compose->gtkaspell);
9940 GtkItemFactory *ifactory;
9941 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
9942 menu_set_sensitive(ifactory, "/Edit/Check backwards misspelled word", FALSE);
9943 menu_set_sensitive(ifactory, "/Edit/Forward to next misspelled word", FALSE);
9949 *\brief Guess originating forward account from MsgInfo and several
9950 * "common preference" settings. Return NULL if no guess.
9952 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
9954 PrefsAccount *account = NULL;
9956 g_return_val_if_fail(msginfo, NULL);
9957 g_return_val_if_fail(msginfo->folder, NULL);
9958 g_return_val_if_fail(msginfo->folder->prefs, NULL);
9960 if (msginfo->folder->prefs->enable_default_account)
9961 account = account_find_from_id(msginfo->folder->prefs->default_account);
9964 account = msginfo->folder->folder->account;
9966 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
9968 Xstrdup_a(to, msginfo->to, return NULL);
9969 extract_address(to);
9970 account = account_find_from_address(to);
9973 if (!account && prefs_common.forward_account_autosel) {
9975 if (!procheader_get_header_from_msginfo
9976 (msginfo, cc,sizeof cc , "Cc:")) {
9977 gchar *buf = cc + strlen("Cc:");
9978 extract_address(buf);
9979 account = account_find_from_address(buf);
9983 if (!account && prefs_common.forward_account_autosel) {
9984 gchar deliveredto[BUFFSIZE];
9985 if (!procheader_get_header_from_msginfo
9986 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
9987 gchar *buf = deliveredto + strlen("Delivered-To:");
9988 extract_address(buf);
9989 account = account_find_from_address(buf);
9996 gboolean compose_close(Compose *compose)
10000 if (!g_mutex_trylock(compose->mutex)) {
10001 /* we have to wait for the (possibly deferred by auto-save)
10002 * drafting to be done, before destroying the compose under
10004 debug_print("waiting for drafting to finish...\n");
10005 compose_allow_user_actions(compose, FALSE);
10006 g_timeout_add (500, (GSourceFunc) compose_close, compose);
10009 g_return_val_if_fail(compose, FALSE);
10010 gtkut_widget_get_uposition(compose->window, &x, &y);
10011 prefs_common.compose_x = x;
10012 prefs_common.compose_y = y;
10013 g_mutex_unlock(compose->mutex);
10014 compose_destroy(compose);
10019 * Add entry field for each address in list.
10020 * \param compose E-Mail composition object.
10021 * \param listAddress List of (formatted) E-Mail addresses.
10023 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
10026 node = listAddress;
10028 addr = ( gchar * ) node->data;
10029 compose_entry_append( compose, addr, COMPOSE_TO );
10030 node = g_list_next( node );
10034 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
10035 guint action, gboolean opening_multiple)
10037 gchar *body = NULL;
10038 GSList *new_msglist = NULL;
10039 MsgInfo *tmp_msginfo = NULL;
10040 gboolean originally_enc = FALSE;
10041 Compose *compose = NULL;
10043 g_return_if_fail(msgview != NULL);
10045 g_return_if_fail(msginfo_list != NULL);
10047 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
10048 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
10049 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
10051 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
10052 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
10053 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
10054 orig_msginfo, mimeinfo);
10055 if (tmp_msginfo != NULL) {
10056 new_msglist = g_slist_append(NULL, tmp_msginfo);
10058 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
10059 tmp_msginfo->folder = orig_msginfo->folder;
10060 tmp_msginfo->msgnum = orig_msginfo->msgnum;
10061 if (orig_msginfo->tags)
10062 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
10067 if (!opening_multiple)
10068 body = messageview_get_selection(msgview);
10071 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
10072 procmsg_msginfo_free(tmp_msginfo);
10073 g_slist_free(new_msglist);
10075 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
10077 if (compose && originally_enc) {
10078 compose_force_encryption(compose, compose->account, FALSE);
10084 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
10087 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
10088 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
10089 GSList *cur = msginfo_list;
10090 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
10091 "messages. Opening the windows "
10092 "could take some time. Do you "
10093 "want to continue?"),
10094 g_slist_length(msginfo_list));
10095 if (g_slist_length(msginfo_list) > 9
10096 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
10097 != G_ALERTALTERNATE) {
10102 /* We'll open multiple compose windows */
10103 /* let the WM place the next windows */
10104 compose_force_window_origin = FALSE;
10105 for (; cur; cur = cur->next) {
10107 tmplist.data = cur->data;
10108 tmplist.next = NULL;
10109 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
10111 compose_force_window_origin = TRUE;
10113 /* forwarding multiple mails as attachments is done via a
10114 * single compose window */
10115 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
10119 void compose_set_position(Compose *compose, gint pos)
10121 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10123 gtkut_text_view_set_position(text, pos);
10126 gboolean compose_search_string(Compose *compose,
10127 const gchar *str, gboolean case_sens)
10129 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10131 return gtkut_text_view_search_string(text, str, case_sens);
10134 gboolean compose_search_string_backward(Compose *compose,
10135 const gchar *str, gboolean case_sens)
10137 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10139 return gtkut_text_view_search_string_backward(text, str, case_sens);
10142 /* allocate a msginfo structure and populate its data from a compose data structure */
10143 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
10145 MsgInfo *newmsginfo;
10147 gchar buf[BUFFSIZE];
10149 g_return_val_if_fail( compose != NULL, NULL );
10151 newmsginfo = procmsg_msginfo_new();
10154 get_rfc822_date(buf, sizeof(buf));
10155 newmsginfo->date = g_strdup(buf);
10158 if (compose->from_name) {
10159 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
10160 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
10164 if (compose->subject_entry)
10165 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
10167 /* to, cc, reply-to, newsgroups */
10168 for (list = compose->header_list; list; list = list->next) {
10169 gchar *header = gtk_editable_get_chars(
10171 GTK_BIN(((ComposeHeaderEntry *)list->data)->combo)->child), 0, -1);
10172 gchar *entry = gtk_editable_get_chars(
10173 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
10175 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
10176 if ( newmsginfo->to == NULL ) {
10177 newmsginfo->to = g_strdup(entry);
10178 } else if (entry && *entry) {
10179 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
10180 g_free(newmsginfo->to);
10181 newmsginfo->to = tmp;
10184 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
10185 if ( newmsginfo->cc == NULL ) {
10186 newmsginfo->cc = g_strdup(entry);
10187 } else if (entry && *entry) {
10188 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
10189 g_free(newmsginfo->cc);
10190 newmsginfo->cc = tmp;
10193 if ( strcasecmp(header,
10194 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
10195 if ( newmsginfo->newsgroups == NULL ) {
10196 newmsginfo->newsgroups = g_strdup(entry);
10197 } else if (entry && *entry) {
10198 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
10199 g_free(newmsginfo->newsgroups);
10200 newmsginfo->newsgroups = tmp;
10208 /* other data is unset */
10214 /* update compose's dictionaries from folder dict settings */
10215 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
10216 FolderItem *folder_item)
10218 g_return_if_fail(compose != NULL);
10220 if (compose->gtkaspell && folder_item && folder_item->prefs) {
10221 FolderItemPrefs *prefs = folder_item->prefs;
10223 if (prefs->enable_default_dictionary)
10224 gtkaspell_change_dict(compose->gtkaspell,
10225 prefs->default_dictionary, FALSE);
10226 if (folder_item->prefs->enable_default_alt_dictionary)
10227 gtkaspell_change_alt_dict(compose->gtkaspell,
10228 prefs->default_alt_dictionary);
10229 if (prefs->enable_default_dictionary
10230 || prefs->enable_default_alt_dictionary)
10231 compose_spell_menu_changed(compose);