2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2007 Hiroyuki Yamamoto and the Claws Mail team
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
26 #ifndef PANGO_ENABLE_ENGINE
27 # define PANGO_ENABLE_ENGINE
31 #include <glib/gi18n.h>
32 #include <gdk/gdkkeysyms.h>
33 #include <gtk/gtkmain.h>
34 #include <gtk/gtkmenu.h>
35 #include <gtk/gtkmenuitem.h>
36 #include <gtk/gtkitemfactory.h>
37 #include <gtk/gtkcheckmenuitem.h>
38 #include <gtk/gtkoptionmenu.h>
39 #include <gtk/gtkwidget.h>
40 #include <gtk/gtkvpaned.h>
41 #include <gtk/gtkentry.h>
42 #include <gtk/gtkeditable.h>
43 #include <gtk/gtkwindow.h>
44 #include <gtk/gtksignal.h>
45 #include <gtk/gtkvbox.h>
46 #include <gtk/gtkcontainer.h>
47 #include <gtk/gtkhandlebox.h>
48 #include <gtk/gtktoolbar.h>
49 #include <gtk/gtktable.h>
50 #include <gtk/gtkhbox.h>
51 #include <gtk/gtklabel.h>
52 #include <gtk/gtkscrolledwindow.h>
53 #include <gtk/gtktreeview.h>
54 #include <gtk/gtkliststore.h>
55 #include <gtk/gtktreeselection.h>
56 #include <gtk/gtktreemodel.h>
58 #include <gtk/gtkdnd.h>
59 #include <gtk/gtkclipboard.h>
60 #include <pango/pango-break.h>
65 #include <sys/types.h>
71 # include <sys/wait.h>
75 #ifndef G_OS_WIN32 /* fixme we should have a configure test. */
79 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
86 #include "mainwindow.h"
88 #include "addressbook.h"
89 #include "folderview.h"
92 #include "stock_pixmap.h"
93 #include "send_message.h"
96 #include "customheader.h"
97 #include "prefs_common.h"
98 #include "prefs_account.h"
102 #include "procheader.h"
103 #include "procmime.h"
104 #include "statusbar.h"
107 #include "quoted-printable.h"
108 #include "codeconv.h"
110 #include "gtkutils.h"
112 #include "alertpanel.h"
113 #include "manage_window.h"
114 #include "gtkshruler.h"
116 #include "addr_compl.h"
117 #include "quote_fmt.h"
119 #include "foldersel.h"
122 #include "message_search.h"
123 #include "combobox.h"
138 #define N_ATTACH_COLS (N_COL_COLUMNS)
142 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE,
143 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER,
144 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER,
145 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD,
146 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD,
147 COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE,
148 COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE,
149 COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE,
150 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER,
151 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER,
152 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD,
153 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD,
154 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE,
155 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE_N,
156 COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END
157 } ComposeCallAdvancedAction;
161 PRIORITY_HIGHEST = 1,
170 COMPOSE_INSERT_SUCCESS,
171 COMPOSE_INSERT_READ_ERROR,
172 COMPOSE_INSERT_INVALID_CHARACTER,
173 COMPOSE_INSERT_NO_FILE
174 } ComposeInsertResult;
178 COMPOSE_WRITE_FOR_SEND,
179 COMPOSE_WRITE_FOR_STORE
184 COMPOSE_QUOTE_FORCED,
189 #define B64_LINE_SIZE 57
190 #define B64_BUFFSIZE 77
192 #define MAX_REFERENCES_LEN 999
194 static GList *compose_list = NULL;
196 static Compose *compose_generic_new (PrefsAccount *account,
199 GPtrArray *attach_files,
200 GList *listAddress );
202 static Compose *compose_create (PrefsAccount *account,
207 static void compose_entry_mark_default_to (Compose *compose,
208 const gchar *address);
209 static Compose *compose_followup_and_reply_to (MsgInfo *msginfo,
210 ComposeQuoteMode quote_mode,
214 static Compose *compose_forward_multiple (PrefsAccount *account,
215 GSList *msginfo_list);
216 static Compose *compose_reply (MsgInfo *msginfo,
217 ComposeQuoteMode quote_mode,
222 static Compose *compose_reply_mode (ComposeMode mode,
223 GSList *msginfo_list,
225 static void compose_template_apply_fields(Compose *compose, Template *tmpl);
226 static void compose_update_privacy_systems_menu(Compose *compose);
228 static GtkWidget *compose_account_option_menu_create
230 static void compose_set_out_encoding (Compose *compose);
231 static void compose_set_template_menu (Compose *compose);
232 static void compose_template_apply (Compose *compose,
235 static void compose_destroy (Compose *compose);
237 static void compose_entries_set (Compose *compose,
238 const gchar *mailto);
239 static gint compose_parse_header (Compose *compose,
241 static gchar *compose_parse_references (const gchar *ref,
244 static gchar *compose_quote_fmt (Compose *compose,
250 gboolean need_unescape,
251 const gchar *err_msg);
253 static void compose_reply_set_entry (Compose *compose,
259 followup_and_reply_to);
260 static void compose_reedit_set_entry (Compose *compose,
263 static void compose_insert_sig (Compose *compose,
265 static gchar *compose_get_signature_str (Compose *compose);
266 static ComposeInsertResult compose_insert_file (Compose *compose,
269 static gboolean compose_attach_append (Compose *compose,
272 const gchar *content_type);
273 static void compose_attach_parts (Compose *compose,
276 static gboolean compose_beautify_paragraph (Compose *compose,
277 GtkTextIter *par_iter,
279 static void compose_wrap_all (Compose *compose);
280 static void compose_wrap_all_full (Compose *compose,
283 static void compose_set_title (Compose *compose);
284 static void compose_select_account (Compose *compose,
285 PrefsAccount *account,
288 static PrefsAccount *compose_current_mail_account(void);
289 /* static gint compose_send (Compose *compose); */
290 static gboolean compose_check_for_valid_recipient
292 static gboolean compose_check_entries (Compose *compose,
293 gboolean check_everything);
294 static gint compose_write_to_file (Compose *compose,
297 gboolean attach_parts);
298 static gint compose_write_body_to_file (Compose *compose,
300 static gint compose_remove_reedit_target (Compose *compose,
302 static void compose_remove_draft (Compose *compose);
303 static gint compose_queue_sub (Compose *compose,
307 gboolean check_subject,
308 gboolean remove_reedit_target);
309 static void compose_add_attachments (Compose *compose,
311 static gchar *compose_get_header (Compose *compose);
313 static void compose_convert_header (Compose *compose,
318 gboolean addr_field);
320 static void compose_attach_info_free (AttachInfo *ainfo);
321 static void compose_attach_remove_selected (Compose *compose);
323 static void compose_attach_property (Compose *compose);
324 static void compose_attach_property_create (gboolean *cancelled);
325 static void attach_property_ok (GtkWidget *widget,
326 gboolean *cancelled);
327 static void attach_property_cancel (GtkWidget *widget,
328 gboolean *cancelled);
329 static gint attach_property_delete_event (GtkWidget *widget,
331 gboolean *cancelled);
332 static gboolean attach_property_key_pressed (GtkWidget *widget,
334 gboolean *cancelled);
336 static void compose_exec_ext_editor (Compose *compose);
338 static gint compose_exec_ext_editor_real (const gchar *file);
339 static gboolean compose_ext_editor_kill (Compose *compose);
340 static gboolean compose_input_cb (GIOChannel *source,
341 GIOCondition condition,
343 static void compose_set_ext_editor_sensitive (Compose *compose,
345 #endif /* G_OS_UNIX */
347 static void compose_undo_state_changed (UndoMain *undostruct,
352 static void compose_create_header_entry (Compose *compose);
353 static void compose_add_header_entry (Compose *compose, const gchar *header, gchar *text);
354 static void compose_remove_header_entries(Compose *compose);
356 static void compose_update_priority_menu_item(Compose * compose);
358 static void compose_spell_menu_changed (void *data);
360 static void compose_add_field_list ( Compose *compose,
361 GList *listAddress );
363 /* callback functions */
365 static gboolean compose_edit_size_alloc (GtkEditable *widget,
366 GtkAllocation *allocation,
367 GtkSHRuler *shruler);
368 static void account_activated (GtkComboBox *optmenu,
370 static void attach_selected (GtkTreeView *tree_view,
371 GtkTreePath *tree_path,
372 GtkTreeViewColumn *column,
374 static gboolean attach_button_pressed (GtkWidget *widget,
375 GdkEventButton *event,
377 static gboolean attach_key_pressed (GtkWidget *widget,
380 static void compose_send_cb (gpointer data,
383 static void compose_send_later_cb (gpointer data,
387 static void compose_draft_cb (gpointer data,
391 static void compose_attach_cb (gpointer data,
394 static void compose_insert_file_cb (gpointer data,
397 static void compose_insert_sig_cb (gpointer data,
401 static void compose_close_cb (gpointer data,
405 static void compose_set_encoding_cb (gpointer data,
409 static void compose_address_cb (gpointer data,
412 static void compose_template_activate_cb(GtkWidget *widget,
415 static void compose_ext_editor_cb (gpointer data,
419 static gint compose_delete_cb (GtkWidget *widget,
423 static void compose_undo_cb (Compose *compose);
424 static void compose_redo_cb (Compose *compose);
425 static void compose_cut_cb (Compose *compose);
426 static void compose_copy_cb (Compose *compose);
427 static void compose_paste_cb (Compose *compose);
428 static void compose_paste_as_quote_cb (Compose *compose);
429 static void compose_paste_no_wrap_cb (Compose *compose);
430 static void compose_paste_wrap_cb (Compose *compose);
431 static void compose_allsel_cb (Compose *compose);
433 static void compose_advanced_action_cb (Compose *compose,
434 ComposeCallAdvancedAction action);
436 static void compose_grab_focus_cb (GtkWidget *widget,
439 static void compose_changed_cb (GtkTextBuffer *textbuf,
442 static void compose_wrap_cb (gpointer data,
445 static void compose_find_cb (gpointer data,
448 static void compose_toggle_autowrap_cb (gpointer data,
452 static void compose_toggle_ruler_cb (gpointer data,
455 static void compose_toggle_sign_cb (gpointer data,
458 static void compose_toggle_encrypt_cb (gpointer data,
461 static void compose_set_privacy_system_cb(GtkWidget *widget,
463 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn);
464 static void activate_privacy_system (Compose *compose,
465 PrefsAccount *account,
467 static void compose_use_signing(Compose *compose, gboolean use_signing);
468 static void compose_use_encryption(Compose *compose, gboolean use_encryption);
469 static void compose_toggle_return_receipt_cb(gpointer data, guint action,
471 static void compose_toggle_remove_refs_cb(gpointer data, guint action,
473 static void compose_set_priority_cb (gpointer data,
476 static void compose_reply_change_mode (gpointer data,
480 static void compose_attach_drag_received_cb (GtkWidget *widget,
481 GdkDragContext *drag_context,
484 GtkSelectionData *data,
488 static void compose_insert_drag_received_cb (GtkWidget *widget,
489 GdkDragContext *drag_context,
492 GtkSelectionData *data,
496 static void compose_header_drag_received_cb (GtkWidget *widget,
497 GdkDragContext *drag_context,
500 GtkSelectionData *data,
505 static gboolean compose_drag_drop (GtkWidget *widget,
506 GdkDragContext *drag_context,
508 guint time, gpointer user_data);
510 static void text_inserted (GtkTextBuffer *buffer,
515 static Compose *compose_generic_reply(MsgInfo *msginfo,
516 ComposeQuoteMode quote_mode,
520 gboolean followup_and_reply_to,
523 static gboolean compose_headerentry_changed_cb (GtkWidget *entry,
524 ComposeHeaderEntry *headerentry);
525 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
527 ComposeHeaderEntry *headerentry);
529 static void compose_show_first_last_header (Compose *compose, gboolean show_first);
531 static void compose_allow_user_actions (Compose *compose, gboolean allow);
534 static void compose_check_all (Compose *compose);
535 static void compose_highlight_all (Compose *compose);
536 static void compose_check_backwards (Compose *compose);
537 static void compose_check_forwards_go (Compose *compose);
540 static gint compose_defer_auto_save_draft (Compose *compose);
541 static PrefsAccount *compose_guess_forward_account_from_msginfo (MsgInfo *msginfo);
543 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose);
546 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
547 FolderItem *folder_item);
549 static void compose_attach_update_label(Compose *compose);
551 static void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data);
553 static GtkItemFactoryEntry compose_popup_entries[] =
555 {N_("/_Add..."), NULL, compose_attach_cb, 0, NULL},
556 {N_("/_Remove"), NULL, compose_attach_remove_selected, 0, NULL},
557 {"/---", NULL, NULL, 0, "<Separator>"},
558 {N_("/_Properties..."), NULL, compose_attach_property, 0, NULL}
561 static GtkItemFactoryEntry compose_entries[] =
563 {N_("/_Message"), NULL, NULL, 0, "<Branch>"},
564 {N_("/_Message/S_end"), "<control>Return",
565 compose_send_cb, 0, NULL},
566 {N_("/_Message/Send _later"), "<shift><control>S",
567 compose_send_later_cb, 0, NULL},
568 {N_("/_Message/---"), NULL, NULL, 0, "<Separator>"},
569 {N_("/_Message/_Attach file"), "<control>M", compose_attach_cb, 0, NULL},
570 {N_("/_Message/_Insert file"), "<control>I", compose_insert_file_cb, 0, NULL},
571 {N_("/_Message/Insert si_gnature"), "<control>G", compose_insert_sig_cb, 0, NULL},
572 {N_("/_Message/---"), NULL, NULL, 0, "<Separator>"},
573 {N_("/_Message/_Save"),
574 "<control>S", compose_draft_cb, COMPOSE_KEEP_EDITING, NULL},
575 {N_("/_Message/---"), NULL, NULL, 0, "<Separator>"},
576 {N_("/_Message/_Close"), "<control>W", compose_close_cb, 0, NULL},
578 {N_("/_Edit"), NULL, NULL, 0, "<Branch>"},
579 {N_("/_Edit/_Undo"), "<control>Z", compose_undo_cb, 0, NULL},
580 {N_("/_Edit/_Redo"), "<control>Y", compose_redo_cb, 0, NULL},
581 {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
582 {N_("/_Edit/Cu_t"), "<control>X", compose_cut_cb, 0, NULL},
583 {N_("/_Edit/_Copy"), "<control>C", compose_copy_cb, 0, NULL},
584 {N_("/_Edit/_Paste"), "<control>V", compose_paste_cb, 0, NULL},
585 {N_("/_Edit/Special paste"), NULL, NULL, 0, "<Branch>"},
586 {N_("/_Edit/Special paste/as _quotation"),
587 NULL, compose_paste_as_quote_cb, 0, NULL},
588 {N_("/_Edit/Special paste/_wrapped"),
589 NULL, compose_paste_wrap_cb, 0, NULL},
590 {N_("/_Edit/Special paste/_unwrapped"),
591 NULL, compose_paste_no_wrap_cb, 0, NULL},
592 {N_("/_Edit/Select _all"), "<control>A", compose_allsel_cb, 0, NULL},
593 {N_("/_Edit/A_dvanced"), NULL, NULL, 0, "<Branch>"},
594 {N_("/_Edit/A_dvanced/Move a character backward"),
596 compose_advanced_action_cb,
597 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER,
599 {N_("/_Edit/A_dvanced/Move a character forward"),
601 compose_advanced_action_cb,
602 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER,
604 {N_("/_Edit/A_dvanced/Move a word backward"),
606 compose_advanced_action_cb,
607 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD,
609 {N_("/_Edit/A_dvanced/Move a word forward"),
611 compose_advanced_action_cb,
612 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD,
614 {N_("/_Edit/A_dvanced/Move to beginning of line"),
615 NULL, /* "<control>A" */
616 compose_advanced_action_cb,
617 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE,
619 {N_("/_Edit/A_dvanced/Move to end of line"),
621 compose_advanced_action_cb,
622 COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE,
624 {N_("/_Edit/A_dvanced/Move to previous line"),
626 compose_advanced_action_cb,
627 COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE,
629 {N_("/_Edit/A_dvanced/Move to next line"),
631 compose_advanced_action_cb,
632 COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE,
634 {N_("/_Edit/A_dvanced/Delete a character backward"),
636 compose_advanced_action_cb,
637 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER,
639 {N_("/_Edit/A_dvanced/Delete a character forward"),
641 compose_advanced_action_cb,
642 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER,
644 {N_("/_Edit/A_dvanced/Delete a word backward"),
645 NULL, /* "<control>W" */
646 compose_advanced_action_cb,
647 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD,
649 {N_("/_Edit/A_dvanced/Delete a word forward"),
650 NULL, /* "<alt>D", */
651 compose_advanced_action_cb,
652 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD,
654 {N_("/_Edit/A_dvanced/Delete line"),
656 compose_advanced_action_cb,
657 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE,
659 {N_("/_Edit/A_dvanced/Delete entire line"),
661 compose_advanced_action_cb,
662 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE_N,
664 {N_("/_Edit/A_dvanced/Delete to end of line"),
666 compose_advanced_action_cb,
667 COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END,
669 {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
671 "<control>F", compose_find_cb, 0, NULL},
672 {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
673 {N_("/_Edit/_Wrap current paragraph"),
674 "<control>L", compose_wrap_cb, 0, NULL},
675 {N_("/_Edit/Wrap all long _lines"),
676 "<control><alt>L", compose_wrap_cb, 1, NULL},
677 {N_("/_Edit/Aut_o wrapping"), "<shift><control>L", compose_toggle_autowrap_cb, 0, "<ToggleItem>"},
678 {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
679 {N_("/_Edit/Edit with e_xternal editor"),
680 "<shift><control>X", compose_ext_editor_cb, 0, NULL},
682 {N_("/_Spelling"), NULL, NULL, 0, "<Branch>"},
683 {N_("/_Spelling/_Check all or check selection"),
684 NULL, compose_check_all, 0, NULL},
685 {N_("/_Spelling/_Highlight all misspelled words"),
686 NULL, compose_highlight_all, 0, NULL},
687 {N_("/_Spelling/Check _backwards misspelled word"),
688 NULL, compose_check_backwards , 0, NULL},
689 {N_("/_Spelling/_Forward to next misspelled word"),
690 NULL, compose_check_forwards_go, 0, NULL},
691 {N_("/_Spelling/---"), NULL, NULL, 0, "<Separator>"},
692 {N_("/_Spelling/Options"),
693 NULL, NULL, 0, "<Branch>"},
695 {N_("/_Options"), NULL, NULL, 0, "<Branch>"},
696 {N_("/_Options/Reply _mode"), NULL, NULL, 0, "<Branch>"},
697 {N_("/_Options/Reply _mode/_Normal"), NULL, compose_reply_change_mode, COMPOSE_REPLY, "<RadioItem>"},
698 {N_("/_Options/Reply _mode/_All"), NULL, compose_reply_change_mode, COMPOSE_REPLY_TO_ALL, "/Options/Reply mode/Normal"},
699 {N_("/_Options/Reply _mode/_Sender"), NULL, compose_reply_change_mode, COMPOSE_REPLY_TO_SENDER, "/Options/Reply mode/Normal"},
700 {N_("/_Options/Reply _mode/_Mailing-list"), NULL, compose_reply_change_mode, COMPOSE_REPLY_TO_LIST, "/Options/Reply mode/Normal"},
701 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
702 {N_("/_Options/Privacy _System"), NULL, NULL, 0, "<Branch>"},
703 {N_("/_Options/Privacy _System/None"), NULL, NULL, 0, "<RadioItem>"},
704 {N_("/_Options/Si_gn"), NULL, compose_toggle_sign_cb , 0, "<ToggleItem>"},
705 {N_("/_Options/_Encrypt"), NULL, compose_toggle_encrypt_cb, 0, "<ToggleItem>"},
706 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
707 {N_("/_Options/_Priority"), NULL, NULL, 0, "<Branch>"},
708 {N_("/_Options/Priority/_Highest"), NULL, compose_set_priority_cb, PRIORITY_HIGHEST, "<RadioItem>"},
709 {N_("/_Options/Priority/Hi_gh"), NULL, compose_set_priority_cb, PRIORITY_HIGH, "/Options/Priority/Highest"},
710 {N_("/_Options/Priority/_Normal"), NULL, compose_set_priority_cb, PRIORITY_NORMAL, "/Options/Priority/Highest"},
711 {N_("/_Options/Priority/Lo_w"), NULL, compose_set_priority_cb, PRIORITY_LOW, "/Options/Priority/Highest"},
712 {N_("/_Options/Priority/_Lowest"), NULL, compose_set_priority_cb, PRIORITY_LOWEST, "/Options/Priority/Highest"},
713 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
714 {N_("/_Options/_Request Return Receipt"), NULL, compose_toggle_return_receipt_cb, 0, "<ToggleItem>"},
715 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
716 {N_("/_Options/Remo_ve references"), NULL, compose_toggle_remove_refs_cb, 0, "<ToggleItem>"},
717 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
719 #define ENC_ACTION(action) \
720 NULL, compose_set_encoding_cb, action, \
721 "/Options/Character encoding/Automatic"
723 {N_("/_Options/Character _encoding"), NULL, NULL, 0, "<Branch>"},
724 {N_("/_Options/Character _encoding/_Automatic"),
725 NULL, compose_set_encoding_cb, C_AUTO, "<RadioItem>"},
726 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
728 {N_("/_Options/Character _encoding/7bit ASCII (US-ASC_II)"),
729 ENC_ACTION(C_US_ASCII)},
730 {N_("/_Options/Character _encoding/Unicode (_UTF-8)"),
731 ENC_ACTION(C_UTF_8)},
732 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
734 {N_("/_Options/Character _encoding/Western European"), NULL, NULL, 0, "<Branch>"},
735 {N_("/_Options/Character _encoding/Western European/ISO-8859-_1"),
736 ENC_ACTION(C_ISO_8859_1)},
737 {N_("/_Options/Character _encoding/Western European/ISO-8859-15"),
738 ENC_ACTION(C_ISO_8859_15)},
739 {N_("/_Options/Character _encoding/Western European/Windows-1252"),
740 ENC_ACTION(C_WINDOWS_1252)},
742 {N_("/_Options/Character _encoding/Central European (ISO-8859-_2)"),
743 ENC_ACTION(C_ISO_8859_2)},
745 {N_("/_Options/Character _encoding/Baltic"), NULL, NULL, 0, "<Branch>"},
746 {N_("/_Options/Character _encoding/Baltic/ISO-8859-13"),
747 ENC_ACTION(C_ISO_8859_13)},
748 {N_("/_Options/Character _encoding/Baltic/ISO-8859-_4"),
749 ENC_ACTION(C_ISO_8859_4)},
751 {N_("/_Options/Character _encoding/Greek (ISO-8859-_7)"),
752 ENC_ACTION(C_ISO_8859_7)},
754 {N_("/_Options/Character _encoding/Hebrew"), NULL, NULL, 0, "<Branch>"},
755 {N_("/_Options/Character _encoding/Hebrew/ISO-8859-_8"),
756 ENC_ACTION(C_ISO_8859_8)},
757 {N_("/_Options/Character _encoding/Hebrew/Windows-1255"),
758 ENC_ACTION(C_WINDOWS_1255)},
760 {N_("/_Options/Character _encoding/Arabic"), NULL, NULL, 0, "<Branch>"},
761 {N_("/_Options/Character _encoding/Arabic/ISO-8859-_6"),
762 ENC_ACTION(C_ISO_8859_6)},
763 {N_("/_Options/Character _encoding/Arabic/Windows-1256"),
764 ENC_ACTION(C_CP1256)},
766 {N_("/_Options/Character _encoding/Turkish (ISO-8859-_9)"),
767 ENC_ACTION(C_ISO_8859_9)},
769 {N_("/_Options/Character _encoding/Cyrillic"), NULL, NULL, 0, "<Branch>"},
770 {N_("/_Options/Character _encoding/Cyrillic/ISO-8859-_5"),
771 ENC_ACTION(C_ISO_8859_5)},
772 {N_("/_Options/Character _encoding/Cyrillic/KOI8-_R"),
773 ENC_ACTION(C_KOI8_R)},
774 {N_("/_Options/Character _encoding/Cyrillic/KOI8-U"),
775 ENC_ACTION(C_KOI8_U)},
776 {N_("/_Options/Character _encoding/Cyrillic/Windows-1251"),
777 ENC_ACTION(C_WINDOWS_1251)},
779 {N_("/_Options/Character _encoding/Japanese"), NULL, NULL, 0, "<Branch>"},
780 {N_("/_Options/Character _encoding/Japanese/ISO-2022-_JP"),
781 ENC_ACTION(C_ISO_2022_JP)},
782 {N_("/_Options/Character _encoding/Japanese/ISO-2022-JP-2"),
783 ENC_ACTION(C_ISO_2022_JP_2)},
784 {N_("/_Options/Character _encoding/Japanese/_EUC-JP"),
785 ENC_ACTION(C_EUC_JP)},
786 {N_("/_Options/Character _encoding/Japanese/_Shift__JIS"),
787 ENC_ACTION(C_SHIFT_JIS)},
789 {N_("/_Options/Character _encoding/Chinese"), NULL, NULL, 0, "<Branch>"},
790 {N_("/_Options/Character _encoding/Chinese/Simplified (_GB2312)"),
791 ENC_ACTION(C_GB2312)},
792 {N_("/_Options/Character _encoding/Chinese/Simplified (GBK)"),
794 {N_("/_Options/Character _encoding/Chinese/Traditional (_Big5)"),
796 {N_("/_Options/Character _encoding/Chinese/Traditional (EUC-_TW)"),
797 ENC_ACTION(C_EUC_TW)},
799 {N_("/_Options/Character _encoding/Korean"), NULL, NULL, 0, "<Branch>"},
800 {N_("/_Options/Character _encoding/Korean/EUC-_KR"),
801 ENC_ACTION(C_EUC_KR)},
802 {N_("/_Options/Character _encoding/Korean/ISO-2022-KR"),
803 ENC_ACTION(C_ISO_2022_KR)},
805 {N_("/_Options/Character _encoding/Thai"), NULL, NULL, 0, "<Branch>"},
806 {N_("/_Options/Character _encoding/Thai/TIS-620"),
807 ENC_ACTION(C_TIS_620)},
808 {N_("/_Options/Character _encoding/Thai/Windows-874"),
809 ENC_ACTION(C_WINDOWS_874)},
811 {N_("/_Tools"), NULL, NULL, 0, "<Branch>"},
812 {N_("/_Tools/Show _ruler"), NULL, compose_toggle_ruler_cb, 0, "<ToggleItem>"},
813 {N_("/_Tools/_Address book"), "<shift><control>A", compose_address_cb , 0, NULL},
814 {N_("/_Tools/_Template"), NULL, NULL, 0, "<Branch>"},
815 {N_("/_Tools/Actio_ns"), NULL, NULL, 0, "<Branch>"},
816 {N_("/_Help"), NULL, NULL, 0, "<Branch>"},
817 {N_("/_Help/_About"), NULL, about_show, 0, NULL}
820 static GtkTargetEntry compose_mime_types[] =
822 {"text/uri-list", 0, 0},
823 {"UTF8_STRING", 0, 0},
827 static gboolean compose_put_existing_to_front(MsgInfo *info)
829 GList *compose_list = compose_get_compose_list();
833 for (elem = compose_list; elem != NULL && elem->data != NULL;
835 Compose *c = (Compose*)elem->data;
837 if (!c->targetinfo || !c->targetinfo->msgid ||
841 if (!strcmp(c->targetinfo->msgid, info->msgid)) {
842 gtkut_window_popup(c->window);
850 static GdkColor quote_color1 =
851 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
852 static GdkColor quote_color2 =
853 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
854 static GdkColor quote_color3 =
855 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
857 static GdkColor quote_bgcolor1 =
858 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
859 static GdkColor quote_bgcolor2 =
860 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
861 static GdkColor quote_bgcolor3 =
862 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
864 static GdkColor signature_color = {
871 static GdkColor uri_color = {
878 static void compose_create_tags(GtkTextView *text, Compose *compose)
880 GtkTextBuffer *buffer;
881 GdkColor black = {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
887 buffer = gtk_text_view_get_buffer(text);
889 if (prefs_common.enable_color) {
890 /* grab the quote colors, converting from an int to a GdkColor */
891 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
893 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
895 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
897 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_bgcol,
899 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_bgcol,
901 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_bgcol,
903 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
905 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
908 signature_color = quote_color1 = quote_color2 = quote_color3 =
909 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
912 if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
913 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
914 "foreground-gdk", "e_color1,
915 "paragraph-background-gdk", "e_bgcolor1,
917 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
918 "foreground-gdk", "e_color2,
919 "paragraph-background-gdk", "e_bgcolor2,
921 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
922 "foreground-gdk", "e_color3,
923 "paragraph-background-gdk", "e_bgcolor3,
926 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
927 "foreground-gdk", "e_color1,
929 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
930 "foreground-gdk", "e_color2,
932 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
933 "foreground-gdk", "e_color3,
937 compose->signature_tag = gtk_text_buffer_create_tag(buffer, "signature",
938 "foreground-gdk", &signature_color,
941 compose->uri_tag = gtk_text_buffer_create_tag(buffer, "link",
942 "foreground-gdk", &uri_color,
944 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
945 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
947 color[0] = quote_color1;
948 color[1] = quote_color2;
949 color[2] = quote_color3;
950 color[3] = quote_bgcolor1;
951 color[4] = quote_bgcolor2;
952 color[5] = quote_bgcolor3;
953 color[6] = signature_color;
954 color[7] = uri_color;
955 cmap = gdk_drawable_get_colormap(compose->window->window);
956 gdk_colormap_alloc_colors(cmap, color, 8, FALSE, TRUE, success);
958 for (i = 0; i < 8; i++) {
959 if (success[i] == FALSE) {
962 g_warning("Compose: color allocation failed.\n");
963 style = gtk_widget_get_style(GTK_WIDGET(text));
964 quote_color1 = quote_color2 = quote_color3 =
965 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 =
966 signature_color = uri_color = black;
971 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
972 GPtrArray *attach_files)
974 return compose_generic_new(account, mailto, NULL, attach_files, NULL);
977 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
979 return compose_generic_new(account, mailto, item, NULL, NULL);
982 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
984 return compose_generic_new( account, NULL, NULL, NULL, listAddress );
987 #define SCROLL_TO_CURSOR(compose) { \
988 GtkTextMark *cmark = gtk_text_buffer_get_insert( \
989 gtk_text_view_get_buffer( \
990 GTK_TEXT_VIEW(compose->text))); \
991 gtk_text_view_scroll_mark_onscreen( \
992 GTK_TEXT_VIEW(compose->text), \
996 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
997 GPtrArray *attach_files, GList *listAddress )
1000 GtkTextView *textview;
1001 GtkTextBuffer *textbuf;
1003 GtkItemFactory *ifactory;
1004 const gchar *subject_format = NULL;
1005 const gchar *body_format = NULL;
1007 if (item && item->prefs && item->prefs->enable_default_account)
1008 account = account_find_from_id(item->prefs->default_account);
1010 if (!account) account = cur_account;
1011 g_return_val_if_fail(account != NULL, NULL);
1013 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
1015 ifactory = gtk_item_factory_from_widget(compose->menubar);
1017 compose->replyinfo = NULL;
1018 compose->fwdinfo = NULL;
1020 textview = GTK_TEXT_VIEW(compose->text);
1021 textbuf = gtk_text_view_get_buffer(textview);
1022 compose_create_tags(textview, compose);
1024 undo_block(compose->undostruct);
1026 compose_set_dictionaries_from_folder_prefs(compose, item);
1029 if (account->auto_sig)
1030 compose_insert_sig(compose, FALSE);
1031 gtk_text_buffer_get_start_iter(textbuf, &iter);
1032 gtk_text_buffer_place_cursor(textbuf, &iter);
1034 if (account->protocol != A_NNTP) {
1035 if (mailto && *mailto != '\0') {
1036 compose_entries_set(compose, mailto);
1038 } else if (item && item->prefs->enable_default_to) {
1039 compose_entry_append(compose, item->prefs->default_to, COMPOSE_TO);
1040 compose_entry_mark_default_to(compose, item->prefs->default_to);
1042 if (item && item->ret_rcpt) {
1043 menu_set_active(ifactory, "/Options/Request Return Receipt", TRUE);
1047 compose_entry_append(compose, mailto, COMPOSE_NEWSGROUPS);
1048 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1049 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS);
1052 * CLAWS: just don't allow return receipt request, even if the user
1053 * may want to send an email. simple but foolproof.
1055 menu_set_sensitive(ifactory, "/Options/Request Return Receipt", FALSE);
1057 compose_add_field_list( compose, listAddress );
1059 if (item && item->prefs && item->prefs->compose_with_format) {
1060 subject_format = item->prefs->compose_subject_format;
1061 body_format = item->prefs->compose_body_format;
1062 } else if (account->compose_with_format) {
1063 subject_format = account->compose_subject_format;
1064 body_format = account->compose_body_format;
1065 } else if (prefs_common.compose_with_format) {
1066 subject_format = prefs_common.compose_subject_format;
1067 body_format = prefs_common.compose_body_format;
1070 if (subject_format || body_format) {
1071 MsgInfo* dummyinfo = NULL;
1074 && *subject_format != '\0' )
1076 gchar *subject = NULL;
1080 dummyinfo = compose_msginfo_new_from_compose(compose);
1082 /* decode \-escape sequences in the internal representation of the quote format */
1083 tmp = malloc(strlen(subject_format)+1);
1084 pref_get_unescaped_pref(tmp, subject_format);
1086 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1088 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account,
1089 compose->gtkaspell);
1091 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account);
1093 quote_fmt_scan_string(tmp);
1096 buf = quote_fmt_get_buffer();
1098 alertpanel_error(_("New message subject format error."));
1100 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1101 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1102 quote_fmt_reset_vartable();
1109 && *body_format != '\0' )
1112 GtkTextBuffer *buffer;
1113 GtkTextIter start, end;
1116 if ( dummyinfo == NULL )
1117 dummyinfo = compose_msginfo_new_from_compose(compose);
1119 text = GTK_TEXT_VIEW(compose->text);
1120 buffer = gtk_text_view_get_buffer(text);
1121 gtk_text_buffer_get_start_iter(buffer, &start);
1122 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1123 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1125 compose_quote_fmt(compose, dummyinfo,
1127 NULL, tmp, FALSE, TRUE,
1128 _("New message body format error at line %d."));
1129 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1130 quote_fmt_reset_vartable();
1135 procmsg_msginfo_free( dummyinfo );
1142 for (i = 0; i < attach_files->len; i++) {
1143 file = g_ptr_array_index(attach_files, i);
1144 compose_attach_append(compose, file, file, NULL);
1148 compose_show_first_last_header(compose, TRUE);
1150 /* Set save folder */
1151 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1152 gchar *folderidentifier;
1154 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
1155 folderidentifier = folder_item_get_identifier(item);
1156 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
1157 g_free(folderidentifier);
1160 gtk_widget_grab_focus(compose->header_last->entry);
1162 undo_unblock(compose->undostruct);
1164 if (prefs_common.auto_exteditor)
1165 compose_exec_ext_editor(compose);
1167 compose->draft_timeout_tag = -1;
1168 SCROLL_TO_CURSOR(compose);
1170 compose->modified = FALSE;
1171 compose_set_title(compose);
1175 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1176 gboolean override_pref)
1178 gchar *privacy = NULL;
1180 g_return_if_fail(compose != NULL);
1181 g_return_if_fail(account != NULL);
1183 if (override_pref == FALSE && account->default_encrypt_reply == FALSE)
1186 if (account->default_privacy_system
1187 && strlen(account->default_privacy_system)) {
1188 privacy = account->default_privacy_system;
1190 GSList *privacy_avail = privacy_get_system_ids();
1191 if (privacy_avail && g_slist_length(privacy_avail)) {
1192 privacy = (gchar *)(privacy_avail->data);
1195 if (privacy != NULL) {
1196 if (compose->privacy_system == NULL)
1197 compose->privacy_system = g_strdup(privacy);
1198 compose_update_privacy_system_menu_item(compose, FALSE);
1199 compose_use_encryption(compose, TRUE);
1203 static void compose_force_signing(Compose *compose, PrefsAccount *account)
1205 gchar *privacy = NULL;
1207 if (account->default_privacy_system
1208 && strlen(account->default_privacy_system)) {
1209 privacy = account->default_privacy_system;
1211 GSList *privacy_avail = privacy_get_system_ids();
1212 if (privacy_avail && g_slist_length(privacy_avail)) {
1213 privacy = (gchar *)(privacy_avail->data);
1216 if (privacy != NULL) {
1217 if (compose->privacy_system == NULL)
1218 compose->privacy_system = g_strdup(privacy);
1219 compose_update_privacy_system_menu_item(compose, FALSE);
1220 compose_use_signing(compose, TRUE);
1224 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1228 Compose *compose = NULL;
1229 GtkItemFactory *ifactory = NULL;
1231 g_return_val_if_fail(msginfo_list != NULL, NULL);
1233 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1234 g_return_val_if_fail(msginfo != NULL, NULL);
1236 list_len = g_slist_length(msginfo_list);
1240 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1241 FALSE, prefs_common.default_reply_list, FALSE, body);
1243 case COMPOSE_REPLY_WITH_QUOTE:
1244 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1245 FALSE, prefs_common.default_reply_list, FALSE, body);
1247 case COMPOSE_REPLY_WITHOUT_QUOTE:
1248 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1249 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1251 case COMPOSE_REPLY_TO_SENDER:
1252 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1253 FALSE, FALSE, TRUE, body);
1255 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1256 compose = compose_followup_and_reply_to(msginfo,
1257 COMPOSE_QUOTE_CHECK,
1258 FALSE, FALSE, body);
1260 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1261 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1262 FALSE, FALSE, TRUE, body);
1264 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1265 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1266 FALSE, FALSE, TRUE, NULL);
1268 case COMPOSE_REPLY_TO_ALL:
1269 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1270 TRUE, FALSE, FALSE, body);
1272 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1273 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1274 TRUE, FALSE, FALSE, body);
1276 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1277 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1278 TRUE, FALSE, FALSE, NULL);
1280 case COMPOSE_REPLY_TO_LIST:
1281 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1282 FALSE, TRUE, FALSE, body);
1284 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1285 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1286 FALSE, TRUE, FALSE, body);
1288 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1289 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1290 FALSE, TRUE, FALSE, NULL);
1292 case COMPOSE_FORWARD:
1293 if (prefs_common.forward_as_attachment) {
1294 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1297 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1301 case COMPOSE_FORWARD_INLINE:
1302 /* check if we reply to more than one Message */
1303 if (list_len == 1) {
1304 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1307 /* more messages FALL THROUGH */
1308 case COMPOSE_FORWARD_AS_ATTACH:
1309 compose = compose_forward_multiple(NULL, msginfo_list);
1311 case COMPOSE_REDIRECT:
1312 compose = compose_redirect(NULL, msginfo, FALSE);
1315 g_warning("compose_reply_mode(): invalid Compose Mode: %d\n", mode);
1318 if (compose == NULL) {
1319 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1322 ifactory = gtk_item_factory_from_widget(compose->menubar);
1324 compose->rmode = mode;
1325 switch (compose->rmode) {
1327 case COMPOSE_REPLY_WITH_QUOTE:
1328 case COMPOSE_REPLY_WITHOUT_QUOTE:
1329 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1330 debug_print("reply mode Normal\n");
1331 menu_set_active(ifactory, "/Options/Reply mode/Normal", TRUE);
1332 compose_reply_change_mode(compose, COMPOSE_REPLY, NULL); /* force update */
1334 case COMPOSE_REPLY_TO_SENDER:
1335 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1336 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1337 debug_print("reply mode Sender\n");
1338 menu_set_active(ifactory, "/Options/Reply mode/Sender", TRUE);
1340 case COMPOSE_REPLY_TO_ALL:
1341 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1342 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1343 debug_print("reply mode All\n");
1344 menu_set_active(ifactory, "/Options/Reply mode/All", TRUE);
1346 case COMPOSE_REPLY_TO_LIST:
1347 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1348 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1349 debug_print("reply mode List\n");
1350 menu_set_active(ifactory, "/Options/Reply mode/Mailing-list", TRUE);
1358 static Compose *compose_reply(MsgInfo *msginfo,
1359 ComposeQuoteMode quote_mode,
1365 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1366 to_sender, FALSE, body);
1369 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1370 ComposeQuoteMode quote_mode,
1375 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1376 to_sender, TRUE, body);
1379 static void compose_extract_original_charset(Compose *compose)
1381 MsgInfo *info = NULL;
1382 if (compose->replyinfo) {
1383 info = compose->replyinfo;
1384 } else if (compose->fwdinfo) {
1385 info = compose->fwdinfo;
1386 } else if (compose->targetinfo) {
1387 info = compose->targetinfo;
1390 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1391 MimeInfo *partinfo = mimeinfo;
1392 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1393 partinfo = procmime_mimeinfo_next(partinfo);
1395 compose->orig_charset =
1396 g_strdup(procmime_mimeinfo_get_parameter(
1397 partinfo, "charset"));
1399 procmime_mimeinfo_free_all(mimeinfo);
1403 #define SIGNAL_BLOCK(buffer) { \
1404 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1405 G_CALLBACK(compose_changed_cb), \
1407 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1408 G_CALLBACK(text_inserted), \
1412 #define SIGNAL_UNBLOCK(buffer) { \
1413 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1414 G_CALLBACK(compose_changed_cb), \
1416 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1417 G_CALLBACK(text_inserted), \
1421 static Compose *compose_generic_reply(MsgInfo *msginfo,
1422 ComposeQuoteMode quote_mode,
1423 gboolean to_all, gboolean to_ml,
1425 gboolean followup_and_reply_to,
1428 GtkItemFactory *ifactory;
1430 PrefsAccount *account = NULL;
1431 GtkTextView *textview;
1432 GtkTextBuffer *textbuf;
1433 gboolean quote = FALSE;
1434 const gchar *qmark = NULL;
1435 const gchar *body_fmt = NULL;
1437 g_return_val_if_fail(msginfo != NULL, NULL);
1438 g_return_val_if_fail(msginfo->folder != NULL, NULL);
1440 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1442 g_return_val_if_fail(account != NULL, NULL);
1444 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1446 compose->updating = TRUE;
1448 ifactory = gtk_item_factory_from_widget(compose->menubar);
1450 menu_set_active(ifactory, "/Options/Remove references", FALSE);
1451 menu_set_sensitive(ifactory, "/Options/Remove references", TRUE);
1453 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1454 if (!compose->replyinfo)
1455 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1457 compose_extract_original_charset(compose);
1459 if (msginfo->folder && msginfo->folder->ret_rcpt)
1460 menu_set_active(ifactory, "/Options/Request Return Receipt", TRUE);
1462 /* Set save folder */
1463 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1464 gchar *folderidentifier;
1466 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1467 folderidentifier = folder_item_get_identifier(msginfo->folder);
1468 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
1469 g_free(folderidentifier);
1472 if (compose_parse_header(compose, msginfo) < 0) return NULL;
1474 textview = (GTK_TEXT_VIEW(compose->text));
1475 textbuf = gtk_text_view_get_buffer(textview);
1476 compose_create_tags(textview, compose);
1478 undo_block(compose->undostruct);
1480 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1483 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1484 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1485 /* use the reply format of folder (if enabled), or the account's one
1486 (if enabled) or fallback to the global reply format, which is always
1487 enabled (even if empty), and use the relevant quotemark */
1489 if (msginfo->folder && msginfo->folder->prefs &&
1490 msginfo->folder->prefs->reply_with_format) {
1491 qmark = msginfo->folder->prefs->reply_quotemark;
1492 body_fmt = msginfo->folder->prefs->reply_body_format;
1494 } else if (account->reply_with_format) {
1495 qmark = account->reply_quotemark;
1496 body_fmt = account->reply_body_format;
1499 qmark = prefs_common.quotemark;
1500 body_fmt = prefs_common.quotefmt;
1505 /* empty quotemark is not allowed */
1506 if (qmark == NULL || *qmark == '\0')
1508 compose_quote_fmt(compose, compose->replyinfo,
1509 body_fmt, qmark, body, FALSE, TRUE,
1510 _("Message reply format error at line %d."));
1511 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1512 quote_fmt_reset_vartable();
1515 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1516 compose_force_encryption(compose, account, FALSE);
1519 SIGNAL_BLOCK(textbuf);
1521 if (account->auto_sig)
1522 compose_insert_sig(compose, FALSE);
1524 compose_wrap_all(compose);
1526 SIGNAL_UNBLOCK(textbuf);
1528 gtk_widget_grab_focus(compose->text);
1530 undo_unblock(compose->undostruct);
1532 if (prefs_common.auto_exteditor)
1533 compose_exec_ext_editor(compose);
1535 compose->modified = FALSE;
1536 compose_set_title(compose);
1538 compose->updating = FALSE;
1539 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1540 SCROLL_TO_CURSOR(compose);
1542 if (compose->deferred_destroy) {
1543 compose_destroy(compose);
1550 #define INSERT_FW_HEADER(var, hdr) \
1551 if (msginfo->var && *msginfo->var) { \
1552 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1553 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1554 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1557 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1558 gboolean as_attach, const gchar *body,
1559 gboolean no_extedit,
1563 GtkTextView *textview;
1564 GtkTextBuffer *textbuf;
1567 g_return_val_if_fail(msginfo != NULL, NULL);
1568 g_return_val_if_fail(msginfo->folder != NULL, NULL);
1571 !(account = compose_guess_forward_account_from_msginfo
1573 account = cur_account;
1575 compose = compose_create(account, msginfo->folder, COMPOSE_FORWARD, batch);
1577 compose->updating = TRUE;
1578 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1579 if (!compose->fwdinfo)
1580 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1582 compose_extract_original_charset(compose);
1584 if (msginfo->subject && *msginfo->subject) {
1585 gchar *buf, *buf2, *p;
1587 buf = p = g_strdup(msginfo->subject);
1588 p += subject_get_prefix_length(p);
1589 memmove(buf, p, strlen(p) + 1);
1591 buf2 = g_strdup_printf("Fw: %s", buf);
1592 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1598 textview = GTK_TEXT_VIEW(compose->text);
1599 textbuf = gtk_text_view_get_buffer(textview);
1600 compose_create_tags(textview, compose);
1602 undo_block(compose->undostruct);
1606 msgfile = procmsg_get_message_file(msginfo);
1607 if (!is_file_exist(msgfile))
1608 g_warning("%s: file not exist\n", msgfile);
1610 compose_attach_append(compose, msgfile, msgfile,
1615 const gchar *qmark = NULL;
1616 const gchar *body_fmt = prefs_common.fw_quotefmt;
1617 MsgInfo *full_msginfo;
1619 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1621 full_msginfo = procmsg_msginfo_copy(msginfo);
1623 /* use the forward format of folder (if enabled), or the account's one
1624 (if enabled) or fallback to the global forward format, which is always
1625 enabled (even if empty), and use the relevant quotemark */
1626 if (msginfo->folder && msginfo->folder->prefs &&
1627 msginfo->folder->prefs->forward_with_format) {
1628 qmark = msginfo->folder->prefs->forward_quotemark;
1629 body_fmt = msginfo->folder->prefs->forward_body_format;
1631 } else if (account->forward_with_format) {
1632 qmark = account->forward_quotemark;
1633 body_fmt = account->forward_body_format;
1636 qmark = prefs_common.fw_quotemark;
1637 body_fmt = prefs_common.fw_quotefmt;
1640 /* empty quotemark is not allowed */
1641 if (qmark == NULL || *qmark == '\0')
1644 compose_quote_fmt(compose, full_msginfo,
1645 body_fmt, qmark, body, FALSE, TRUE,
1646 _("Message forward format error at line %d."));
1647 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1648 quote_fmt_reset_vartable();
1649 compose_attach_parts(compose, msginfo);
1651 procmsg_msginfo_free(full_msginfo);
1654 SIGNAL_BLOCK(textbuf);
1656 if (account->auto_sig)
1657 compose_insert_sig(compose, FALSE);
1659 compose_wrap_all(compose);
1661 SIGNAL_UNBLOCK(textbuf);
1663 gtk_text_buffer_get_start_iter(textbuf, &iter);
1664 gtk_text_buffer_place_cursor(textbuf, &iter);
1666 gtk_widget_grab_focus(compose->header_last->entry);
1668 if (!no_extedit && prefs_common.auto_exteditor)
1669 compose_exec_ext_editor(compose);
1672 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1673 gchar *folderidentifier;
1675 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1676 folderidentifier = folder_item_get_identifier(msginfo->folder);
1677 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
1678 g_free(folderidentifier);
1681 undo_unblock(compose->undostruct);
1683 compose->modified = FALSE;
1684 compose_set_title(compose);
1686 compose->updating = FALSE;
1687 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1688 SCROLL_TO_CURSOR(compose);
1690 if (compose->deferred_destroy) {
1691 compose_destroy(compose);
1698 #undef INSERT_FW_HEADER
1700 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1703 GtkTextView *textview;
1704 GtkTextBuffer *textbuf;
1708 gboolean single_mail = TRUE;
1710 g_return_val_if_fail(msginfo_list != NULL, NULL);
1712 if (g_slist_length(msginfo_list) > 1)
1713 single_mail = FALSE;
1715 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1716 if (((MsgInfo *)msginfo->data)->folder == NULL)
1719 /* guess account from first selected message */
1721 !(account = compose_guess_forward_account_from_msginfo
1722 (msginfo_list->data)))
1723 account = cur_account;
1725 g_return_val_if_fail(account != NULL, NULL);
1727 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1728 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1729 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1732 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1734 compose->updating = TRUE;
1736 textview = GTK_TEXT_VIEW(compose->text);
1737 textbuf = gtk_text_view_get_buffer(textview);
1738 compose_create_tags(textview, compose);
1740 undo_block(compose->undostruct);
1741 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1742 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
1744 if (!is_file_exist(msgfile))
1745 g_warning("%s: file not exist\n", msgfile);
1747 compose_attach_append(compose, msgfile, msgfile,
1753 MsgInfo *info = (MsgInfo *)msginfo_list->data;
1754 if (info->subject && *info->subject) {
1755 gchar *buf, *buf2, *p;
1757 buf = p = g_strdup(info->subject);
1758 p += subject_get_prefix_length(p);
1759 memmove(buf, p, strlen(p) + 1);
1761 buf2 = g_strdup_printf("Fw: %s", buf);
1762 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1768 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
1769 _("Fw: multiple emails"));
1772 SIGNAL_BLOCK(textbuf);
1774 if (account->auto_sig)
1775 compose_insert_sig(compose, FALSE);
1777 compose_wrap_all(compose);
1779 SIGNAL_UNBLOCK(textbuf);
1781 gtk_text_buffer_get_start_iter(textbuf, &iter);
1782 gtk_text_buffer_place_cursor(textbuf, &iter);
1784 gtk_widget_grab_focus(compose->header_last->entry);
1785 undo_unblock(compose->undostruct);
1786 compose->modified = FALSE;
1787 compose_set_title(compose);
1789 compose->updating = FALSE;
1790 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1791 SCROLL_TO_CURSOR(compose);
1793 if (compose->deferred_destroy) {
1794 compose_destroy(compose);
1801 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
1803 GtkTextIter start = *iter;
1804 GtkTextIter end_iter;
1805 int start_pos = gtk_text_iter_get_offset(&start);
1807 if (!compose->account->sig_sep)
1810 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
1811 start_pos+strlen(compose->account->sig_sep));
1813 /* check sig separator */
1814 str = gtk_text_iter_get_text(&start, &end_iter);
1815 if (!strcmp(str, compose->account->sig_sep)) {
1817 /* check end of line (\n) */
1818 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
1819 start_pos+strlen(compose->account->sig_sep));
1820 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
1821 start_pos+strlen(compose->account->sig_sep)+1);
1822 tmp = gtk_text_iter_get_text(&start, &end_iter);
1823 if (!strcmp(tmp,"\n")) {
1835 static void compose_colorize_signature(Compose *compose)
1837 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
1839 GtkTextIter end_iter;
1840 gtk_text_buffer_get_start_iter(buffer, &iter);
1841 while (gtk_text_iter_forward_line(&iter))
1842 if (compose_is_sig_separator(compose, buffer, &iter)) {
1843 gtk_text_buffer_get_end_iter(buffer, &end_iter);
1844 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
1848 #define BLOCK_WRAP() { \
1849 prev_autowrap = compose->autowrap; \
1850 buffer = gtk_text_view_get_buffer( \
1851 GTK_TEXT_VIEW(compose->text)); \
1852 compose->autowrap = FALSE; \
1854 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1855 G_CALLBACK(compose_changed_cb), \
1857 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1858 G_CALLBACK(text_inserted), \
1861 #define UNBLOCK_WRAP() { \
1862 compose->autowrap = prev_autowrap; \
1863 if (compose->autowrap) { \
1864 gint old = compose->draft_timeout_tag; \
1865 compose->draft_timeout_tag = -2; \
1866 compose_wrap_all(compose); \
1867 compose->draft_timeout_tag = old; \
1870 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1871 G_CALLBACK(compose_changed_cb), \
1873 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1874 G_CALLBACK(text_inserted), \
1878 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
1880 Compose *compose = NULL;
1881 PrefsAccount *account = NULL;
1882 GtkTextView *textview;
1883 GtkTextBuffer *textbuf;
1887 gchar buf[BUFFSIZE];
1888 gboolean use_signing = FALSE;
1889 gboolean use_encryption = FALSE;
1890 gchar *privacy_system = NULL;
1891 int priority = PRIORITY_NORMAL;
1892 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
1894 g_return_val_if_fail(msginfo != NULL, NULL);
1895 g_return_val_if_fail(msginfo->folder != NULL, NULL);
1897 if (compose_put_existing_to_front(msginfo)) {
1901 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
1902 folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
1903 gchar queueheader_buf[BUFFSIZE];
1906 /* Select Account from queue headers */
1907 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1908 sizeof(queueheader_buf), "X-Claws-Account-Id:")) {
1909 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
1910 account = account_find_from_id(id);
1912 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1913 sizeof(queueheader_buf), "X-Sylpheed-Account-Id:")) {
1914 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
1915 account = account_find_from_id(id);
1917 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1918 sizeof(queueheader_buf), "NAID:")) {
1919 id = atoi(&queueheader_buf[strlen("NAID:")]);
1920 account = account_find_from_id(id);
1922 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1923 sizeof(queueheader_buf), "MAID:")) {
1924 id = atoi(&queueheader_buf[strlen("MAID:")]);
1925 account = account_find_from_id(id);
1927 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1928 sizeof(queueheader_buf), "S:")) {
1929 account = account_find_from_address(queueheader_buf);
1931 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1932 sizeof(queueheader_buf), "X-Claws-Sign:")) {
1933 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
1934 use_signing = param;
1937 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1938 sizeof(queueheader_buf), "X-Sylpheed-Sign:")) {
1939 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
1940 use_signing = param;
1943 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1944 sizeof(queueheader_buf), "X-Claws-Encrypt:")) {
1945 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
1946 use_encryption = param;
1948 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1949 sizeof(queueheader_buf), "X-Sylpheed-Encrypt:")) {
1950 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
1951 use_encryption = param;
1953 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1954 sizeof(queueheader_buf), "X-Claws-Privacy-System:")) {
1955 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
1957 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1958 sizeof(queueheader_buf), "X-Sylpheed-Privacy-System:")) {
1959 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
1961 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1962 sizeof(queueheader_buf), "X-Priority: ")) {
1963 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
1966 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1967 sizeof(queueheader_buf), "RMID:")) {
1968 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
1969 if (tokens[0] && tokens[1] && tokens[2]) {
1970 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
1971 if (orig_item != NULL) {
1972 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
1977 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1978 sizeof(queueheader_buf), "FMID:")) {
1979 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
1980 if (tokens[0] && tokens[1] && tokens[2]) {
1981 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
1982 if (orig_item != NULL) {
1983 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
1989 account = msginfo->folder->folder->account;
1992 if (!account && prefs_common.reedit_account_autosel) {
1993 gchar from[BUFFSIZE];
1994 if (!procheader_get_header_from_msginfo(msginfo, from, sizeof(from), "FROM:")) {
1995 extract_address(from);
1996 account = account_find_from_address(from);
2000 account = cur_account;
2002 g_return_val_if_fail(account != NULL, NULL);
2004 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2006 compose->replyinfo = replyinfo;
2007 compose->fwdinfo = fwdinfo;
2009 compose->updating = TRUE;
2010 compose->priority = priority;
2012 if (privacy_system != NULL) {
2013 compose->privacy_system = privacy_system;
2014 compose_use_signing(compose, use_signing);
2015 compose_use_encryption(compose, use_encryption);
2016 compose_update_privacy_system_menu_item(compose, FALSE);
2018 activate_privacy_system(compose, account, FALSE);
2021 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2023 compose_extract_original_charset(compose);
2025 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2026 folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
2027 gchar queueheader_buf[BUFFSIZE];
2029 /* Set message save folder */
2030 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "SCF:")) {
2033 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2034 gtk_editable_delete_text(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
2035 gtk_editable_insert_text(GTK_EDITABLE(compose->savemsg_entry), &queueheader_buf[4], strlen(&queueheader_buf[4]), &startpos);
2037 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "RRCPT:")) {
2038 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2040 GtkItemFactory *ifactory;
2041 ifactory = gtk_item_factory_from_widget(compose->menubar);
2042 menu_set_active(ifactory, "/Options/Request Return Receipt", TRUE);
2047 if (compose_parse_header(compose, msginfo) < 0) {
2048 compose->updating = FALSE;
2049 compose_destroy(compose);
2052 compose_reedit_set_entry(compose, msginfo);
2054 textview = GTK_TEXT_VIEW(compose->text);
2055 textbuf = gtk_text_view_get_buffer(textview);
2056 compose_create_tags(textview, compose);
2058 mark = gtk_text_buffer_get_insert(textbuf);
2059 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2061 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2062 G_CALLBACK(compose_changed_cb),
2065 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2066 fp = procmime_get_first_encrypted_text_content(msginfo);
2068 compose_force_encryption(compose, account, TRUE);
2071 fp = procmime_get_first_text_content(msginfo);
2074 g_warning("Can't get text part\n");
2078 gboolean prev_autowrap = compose->autowrap;
2079 GtkTextBuffer *buffer = textbuf;
2081 while (fgets(buf, sizeof(buf), fp) != NULL) {
2083 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2089 compose_attach_parts(compose, msginfo);
2091 compose_colorize_signature(compose);
2093 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2094 G_CALLBACK(compose_changed_cb),
2097 gtk_widget_grab_focus(compose->text);
2099 if (prefs_common.auto_exteditor) {
2100 compose_exec_ext_editor(compose);
2102 compose->modified = FALSE;
2103 compose_set_title(compose);
2105 compose->updating = FALSE;
2106 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2107 SCROLL_TO_CURSOR(compose);
2109 if (compose->deferred_destroy) {
2110 compose_destroy(compose);
2114 compose->sig_str = compose_get_signature_str(compose);
2119 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2124 GtkItemFactory *ifactory;
2127 g_return_val_if_fail(msginfo != NULL, NULL);
2130 account = account_get_reply_account(msginfo,
2131 prefs_common.reply_account_autosel);
2132 g_return_val_if_fail(account != NULL, NULL);
2134 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2136 compose->updating = TRUE;
2138 ifactory = gtk_item_factory_from_widget(compose->menubar);
2139 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2140 compose->replyinfo = NULL;
2141 compose->fwdinfo = NULL;
2143 compose_show_first_last_header(compose, TRUE);
2145 gtk_widget_grab_focus(compose->header_last->entry);
2147 filename = procmsg_get_message_file(msginfo);
2149 if (filename == NULL) {
2150 compose->updating = FALSE;
2151 compose_destroy(compose);
2156 compose->redirect_filename = filename;
2158 /* Set save folder */
2159 item = msginfo->folder;
2160 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2161 gchar *folderidentifier;
2163 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
2164 folderidentifier = folder_item_get_identifier(item);
2165 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
2166 g_free(folderidentifier);
2169 compose_attach_parts(compose, msginfo);
2171 if (msginfo->subject)
2172 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2174 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2176 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2177 _("Message redirect format error at line %d."));
2178 quote_fmt_reset_vartable();
2179 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2181 compose_colorize_signature(compose);
2183 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
2184 menu_set_sensitive(ifactory, "/Add...", FALSE);
2185 menu_set_sensitive(ifactory, "/Remove", FALSE);
2186 menu_set_sensitive(ifactory, "/Properties...", FALSE);
2188 ifactory = gtk_item_factory_from_widget(compose->menubar);
2189 menu_set_sensitive(ifactory, "/Message/Save", FALSE);
2190 menu_set_sensitive(ifactory, "/Message/Insert file", FALSE);
2191 menu_set_sensitive(ifactory, "/Message/Attach file", FALSE);
2192 menu_set_sensitive(ifactory, "/Message/Insert signature", FALSE);
2193 menu_set_sensitive(ifactory, "/Edit", FALSE);
2194 menu_set_sensitive(ifactory, "/Options", FALSE);
2195 menu_set_sensitive(ifactory, "/Tools/Show ruler", FALSE);
2196 menu_set_sensitive(ifactory, "/Tools/Actions", FALSE);
2198 if (compose->toolbar->draft_btn)
2199 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2200 if (compose->toolbar->insert_btn)
2201 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2202 if (compose->toolbar->attach_btn)
2203 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2204 if (compose->toolbar->sig_btn)
2205 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2206 if (compose->toolbar->exteditor_btn)
2207 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2208 if (compose->toolbar->linewrap_current_btn)
2209 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2210 if (compose->toolbar->linewrap_all_btn)
2211 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2213 compose->modified = FALSE;
2214 compose_set_title(compose);
2215 compose->updating = FALSE;
2216 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2217 SCROLL_TO_CURSOR(compose);
2219 if (compose->deferred_destroy) {
2220 compose_destroy(compose);
2227 GList *compose_get_compose_list(void)
2229 return compose_list;
2232 void compose_entry_append(Compose *compose, const gchar *address,
2233 ComposeEntryType type)
2235 const gchar *header;
2237 gboolean in_quote = FALSE;
2238 if (!address || *address == '\0') return;
2245 header = N_("Bcc:");
2247 case COMPOSE_REPLYTO:
2248 header = N_("Reply-To:");
2250 case COMPOSE_NEWSGROUPS:
2251 header = N_("Newsgroups:");
2253 case COMPOSE_FOLLOWUPTO:
2254 header = N_( "Followup-To:");
2261 header = prefs_common_translated_header_name(header);
2263 cur = begin = (gchar *)address;
2265 /* we separate the line by commas, but not if we're inside a quoted
2267 while (*cur != '\0') {
2269 in_quote = !in_quote;
2270 if (*cur == ',' && !in_quote) {
2271 gchar *tmp = g_strdup(begin);
2273 tmp[cur-begin]='\0';
2276 while (*tmp == ' ' || *tmp == '\t')
2278 compose_add_header_entry(compose, header, tmp);
2285 gchar *tmp = g_strdup(begin);
2287 tmp[cur-begin]='\0';
2290 while (*tmp == ' ' || *tmp == '\t')
2292 compose_add_header_entry(compose, header, tmp);
2297 static void compose_entry_mark_default_to(Compose *compose, const gchar *mailto)
2299 static GdkColor yellow;
2300 static GdkColor black;
2301 static gboolean yellow_initialised = FALSE;
2305 if (!yellow_initialised) {
2306 gdk_color_parse("#f5f6be", &yellow);
2307 gdk_color_parse("#000000", &black);
2308 yellow_initialised = gdk_colormap_alloc_color(
2309 gdk_colormap_get_system(), &yellow, FALSE, TRUE);
2310 yellow_initialised &= gdk_colormap_alloc_color(
2311 gdk_colormap_get_system(), &black, FALSE, TRUE);
2314 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2315 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2316 if (gtk_entry_get_text(entry) &&
2317 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2318 if (yellow_initialised) {
2319 gtk_widget_modify_base(
2320 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2321 GTK_STATE_NORMAL, &yellow);
2322 gtk_widget_modify_text(
2323 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2324 GTK_STATE_NORMAL, &black);
2330 void compose_toolbar_cb(gint action, gpointer data)
2332 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2333 Compose *compose = (Compose*)toolbar_item->parent;
2335 g_return_if_fail(compose != NULL);
2339 compose_send_cb(compose, 0, NULL);
2342 compose_send_later_cb(compose, 0, NULL);
2345 compose_draft_cb(compose, COMPOSE_QUIT_EDITING, NULL);
2348 compose_insert_file_cb(compose, 0, NULL);
2351 compose_attach_cb(compose, 0, NULL);
2354 compose_insert_sig(compose, FALSE);
2357 compose_ext_editor_cb(compose, 0, NULL);
2359 case A_LINEWRAP_CURRENT:
2360 compose_beautify_paragraph(compose, NULL, TRUE);
2362 case A_LINEWRAP_ALL:
2363 compose_wrap_all_full(compose, TRUE);
2366 compose_address_cb(compose, 0, NULL);
2369 case A_CHECK_SPELLING:
2370 compose_check_all(compose);
2378 static void compose_entries_set(Compose *compose, const gchar *mailto)
2383 gchar *subject = NULL;
2387 gchar **attach = NULL;
2389 scan_mailto_url(mailto, &to, &cc, &bcc, &subject, &body, &attach);
2392 compose_entry_append(compose, to, COMPOSE_TO);
2394 compose_entry_append(compose, cc, COMPOSE_CC);
2396 compose_entry_append(compose, bcc, COMPOSE_BCC);
2398 if (!g_utf8_validate (subject, -1, NULL)) {
2399 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2400 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2403 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2407 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2408 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2411 gboolean prev_autowrap = compose->autowrap;
2413 compose->autowrap = FALSE;
2415 mark = gtk_text_buffer_get_insert(buffer);
2416 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2418 if (!g_utf8_validate (body, -1, NULL)) {
2419 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2420 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2423 gtk_text_buffer_insert(buffer, &iter, body, -1);
2425 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2427 compose->autowrap = prev_autowrap;
2428 if (compose->autowrap)
2429 compose_wrap_all(compose);
2433 gint i = 0, att = 0;
2434 gchar *warn_files = NULL;
2435 while (attach[i] != NULL) {
2436 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2437 if (utf8_filename) {
2438 if (compose_attach_append(compose, attach[i], utf8_filename, NULL)) {
2439 gchar *tmp = g_strdup_printf("%s%s\n",
2440 warn_files?warn_files:"",
2446 g_free(utf8_filename);
2448 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2453 alertpanel_notice(ngettext(
2454 "The following file has been attached: \n%s",
2455 "The following files have been attached: \n%s", att), warn_files);
2467 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2469 static HeaderEntry hentry[] = {{"Reply-To:", NULL, TRUE},
2470 {"Cc:", NULL, TRUE},
2471 {"References:", NULL, FALSE},
2472 {"Bcc:", NULL, TRUE},
2473 {"Newsgroups:", NULL, TRUE},
2474 {"Followup-To:", NULL, TRUE},
2475 {"List-Post:", NULL, FALSE},
2476 {"X-Priority:", NULL, FALSE},
2477 {NULL, NULL, FALSE}};
2493 g_return_val_if_fail(msginfo != NULL, -1);
2495 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2496 procheader_get_header_fields(fp, hentry);
2499 if (hentry[H_REPLY_TO].body != NULL) {
2500 if (hentry[H_REPLY_TO].body[0] != '\0') {
2502 conv_unmime_header(hentry[H_REPLY_TO].body,
2505 g_free(hentry[H_REPLY_TO].body);
2506 hentry[H_REPLY_TO].body = NULL;
2508 if (hentry[H_CC].body != NULL) {
2509 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL);
2510 g_free(hentry[H_CC].body);
2511 hentry[H_CC].body = NULL;
2513 if (hentry[H_REFERENCES].body != NULL) {
2514 if (compose->mode == COMPOSE_REEDIT)
2515 compose->references = hentry[H_REFERENCES].body;
2517 compose->references = compose_parse_references
2518 (hentry[H_REFERENCES].body, msginfo->msgid);
2519 g_free(hentry[H_REFERENCES].body);
2521 hentry[H_REFERENCES].body = NULL;
2523 if (hentry[H_BCC].body != NULL) {
2524 if (compose->mode == COMPOSE_REEDIT)
2526 conv_unmime_header(hentry[H_BCC].body, NULL);
2527 g_free(hentry[H_BCC].body);
2528 hentry[H_BCC].body = NULL;
2530 if (hentry[H_NEWSGROUPS].body != NULL) {
2531 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2532 hentry[H_NEWSGROUPS].body = NULL;
2534 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2535 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2536 compose->followup_to =
2537 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2540 g_free(hentry[H_FOLLOWUP_TO].body);
2541 hentry[H_FOLLOWUP_TO].body = NULL;
2543 if (hentry[H_LIST_POST].body != NULL) {
2546 extract_address(hentry[H_LIST_POST].body);
2547 if (hentry[H_LIST_POST].body[0] != '\0') {
2548 scan_mailto_url(hentry[H_LIST_POST].body,
2549 &to, NULL, NULL, NULL, NULL, NULL);
2551 g_free(compose->ml_post);
2552 compose->ml_post = to;
2555 g_free(hentry[H_LIST_POST].body);
2556 hentry[H_LIST_POST].body = NULL;
2559 /* CLAWS - X-Priority */
2560 if (compose->mode == COMPOSE_REEDIT)
2561 if (hentry[H_X_PRIORITY].body != NULL) {
2564 priority = atoi(hentry[H_X_PRIORITY].body);
2565 g_free(hentry[H_X_PRIORITY].body);
2567 hentry[H_X_PRIORITY].body = NULL;
2569 if (priority < PRIORITY_HIGHEST ||
2570 priority > PRIORITY_LOWEST)
2571 priority = PRIORITY_NORMAL;
2573 compose->priority = priority;
2576 if (compose->mode == COMPOSE_REEDIT) {
2577 if (msginfo->inreplyto && *msginfo->inreplyto)
2578 compose->inreplyto = g_strdup(msginfo->inreplyto);
2582 if (msginfo->msgid && *msginfo->msgid)
2583 compose->inreplyto = g_strdup(msginfo->msgid);
2585 if (!compose->references) {
2586 if (msginfo->msgid && *msginfo->msgid) {
2587 if (msginfo->inreplyto && *msginfo->inreplyto)
2588 compose->references =
2589 g_strdup_printf("<%s>\n\t<%s>",
2593 compose->references =
2594 g_strconcat("<", msginfo->msgid, ">",
2596 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
2597 compose->references =
2598 g_strconcat("<", msginfo->inreplyto, ">",
2606 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
2608 GSList *ref_id_list, *cur;
2612 ref_id_list = references_list_append(NULL, ref);
2613 if (!ref_id_list) return NULL;
2614 if (msgid && *msgid)
2615 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
2620 for (cur = ref_id_list; cur != NULL; cur = cur->next)
2621 /* "<" + Message-ID + ">" + CR+LF+TAB */
2622 len += strlen((gchar *)cur->data) + 5;
2624 if (len > MAX_REFERENCES_LEN) {
2625 /* remove second message-ID */
2626 if (ref_id_list && ref_id_list->next &&
2627 ref_id_list->next->next) {
2628 g_free(ref_id_list->next->data);
2629 ref_id_list = g_slist_remove
2630 (ref_id_list, ref_id_list->next->data);
2632 slist_free_strings(ref_id_list);
2633 g_slist_free(ref_id_list);
2640 new_ref = g_string_new("");
2641 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
2642 if (new_ref->len > 0)
2643 g_string_append(new_ref, "\n\t");
2644 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
2647 slist_free_strings(ref_id_list);
2648 g_slist_free(ref_id_list);
2650 new_ref_str = new_ref->str;
2651 g_string_free(new_ref, FALSE);
2656 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
2657 const gchar *fmt, const gchar *qmark,
2658 const gchar *body, gboolean rewrap,
2659 gboolean need_unescape,
2660 const gchar *err_msg)
2662 MsgInfo* dummyinfo = NULL;
2663 gchar *quote_str = NULL;
2665 gboolean prev_autowrap;
2666 const gchar *trimmed_body = body;
2667 gint cursor_pos = -1;
2668 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2669 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2674 SIGNAL_BLOCK(buffer);
2677 dummyinfo = compose_msginfo_new_from_compose(compose);
2678 msginfo = dummyinfo;
2681 if (qmark != NULL) {
2683 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
2684 compose->gtkaspell);
2686 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
2688 quote_fmt_scan_string(qmark);
2691 buf = quote_fmt_get_buffer();
2693 alertpanel_error(_("Quote mark format error."));
2695 Xstrdup_a(quote_str, buf, goto error)
2698 if (fmt && *fmt != '\0') {
2701 while (*trimmed_body == '\n')
2705 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account,
2706 compose->gtkaspell);
2708 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account);
2710 if (need_unescape) {
2713 /* decode \-escape sequences in the internal representation of the quote format */
2714 tmp = malloc(strlen(fmt)+1);
2715 pref_get_unescaped_pref(tmp, fmt);
2716 quote_fmt_scan_string(tmp);
2720 quote_fmt_scan_string(fmt);
2724 buf = quote_fmt_get_buffer();
2726 gint line = quote_fmt_get_line();
2727 alertpanel_error(err_msg, line);
2733 prev_autowrap = compose->autowrap;
2734 compose->autowrap = FALSE;
2736 mark = gtk_text_buffer_get_insert(buffer);
2737 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2738 if (g_utf8_validate(buf, -1, NULL)) {
2739 gtk_text_buffer_insert(buffer, &iter, buf, -1);
2741 gchar *tmpout = NULL;
2742 tmpout = conv_codeset_strdup
2743 (buf, conv_get_locale_charset_str_no_utf8(),
2745 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
2747 tmpout = g_malloc(strlen(buf)*2+1);
2748 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
2750 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
2754 cursor_pos = quote_fmt_get_cursor_pos();
2755 compose->set_cursor_pos = cursor_pos;
2756 if (cursor_pos == -1) {
2759 gtk_text_buffer_get_start_iter(buffer, &iter);
2760 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
2761 gtk_text_buffer_place_cursor(buffer, &iter);
2763 compose->autowrap = prev_autowrap;
2764 if (compose->autowrap && rewrap)
2765 compose_wrap_all(compose);
2772 SIGNAL_UNBLOCK(buffer);
2774 procmsg_msginfo_free( dummyinfo );
2779 /* if ml_post is of type addr@host and from is of type
2780 * addr-anything@host, return TRUE
2782 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
2784 gchar *left_ml = NULL;
2785 gchar *right_ml = NULL;
2786 gchar *left_from = NULL;
2787 gchar *right_from = NULL;
2788 gboolean result = FALSE;
2790 if (!ml_post || !from)
2793 left_ml = g_strdup(ml_post);
2794 if (strstr(left_ml, "@")) {
2795 right_ml = strstr(left_ml, "@")+1;
2796 *(strstr(left_ml, "@")) = '\0';
2799 left_from = g_strdup(from);
2800 if (strstr(left_from, "@")) {
2801 right_from = strstr(left_from, "@")+1;
2802 *(strstr(left_from, "@")) = '\0';
2805 if (left_ml && left_from && right_ml && right_from
2806 && !strncmp(left_from, left_ml, strlen(left_ml))
2807 && !strcmp(right_from, right_ml)) {
2816 static gboolean same_address(const gchar *addr1, const gchar *addr2)
2818 gchar *my_addr1, *my_addr2;
2820 if (!addr1 || !addr2)
2823 Xstrdup_a(my_addr1, addr1, return FALSE);
2824 Xstrdup_a(my_addr2, addr2, return FALSE);
2826 extract_address(my_addr1);
2827 extract_address(my_addr2);
2829 return !strcasecmp(my_addr1, my_addr2);
2832 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
2833 gboolean to_all, gboolean to_ml,
2835 gboolean followup_and_reply_to)
2837 GSList *cc_list = NULL;
2840 gchar *replyto = NULL;
2841 GHashTable *to_table;
2843 gboolean reply_to_ml = FALSE;
2844 gboolean default_reply_to = FALSE;
2846 g_return_if_fail(compose->account != NULL);
2847 g_return_if_fail(msginfo != NULL);
2849 reply_to_ml = to_ml && compose->ml_post;
2851 default_reply_to = msginfo->folder &&
2852 msginfo->folder->prefs->enable_default_reply_to;
2854 if (compose->account->protocol != A_NNTP) {
2855 if (reply_to_ml && !default_reply_to) {
2857 gboolean is_subscr = is_subscription(compose->ml_post,
2860 /* normal answer to ml post with a reply-to */
2861 compose_entry_append(compose,
2864 if (compose->replyto
2865 && !same_address(compose->ml_post, compose->replyto))
2866 compose_entry_append(compose,
2870 /* answer to subscription confirmation */
2871 if (compose->replyto)
2872 compose_entry_append(compose,
2875 else if (msginfo->from)
2876 compose_entry_append(compose,
2881 else if (!(to_all || to_sender) && default_reply_to) {
2882 compose_entry_append(compose,
2883 msginfo->folder->prefs->default_reply_to,
2885 compose_entry_mark_default_to(compose,
2886 msginfo->folder->prefs->default_reply_to);
2891 Xstrdup_a(tmp1, msginfo->from, return);
2892 extract_address(tmp1);
2893 if (to_all || to_sender ||
2894 !account_find_from_address(tmp1))
2895 compose_entry_append(compose,
2896 (compose->replyto && !to_sender)
2897 ? compose->replyto :
2898 msginfo->from ? msginfo->from : "",
2900 else if (!to_all && !to_sender) {
2901 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
2902 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
2903 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
2904 compose_entry_append(compose,
2905 msginfo->from ? msginfo->from : "",
2908 /* replying to own mail, use original recp */
2909 compose_entry_append(compose,
2910 msginfo->to ? msginfo->to : "",
2912 compose_entry_append(compose,
2913 msginfo->cc ? msginfo->cc : "",
2919 if (to_sender || (compose->followup_to &&
2920 !strncmp(compose->followup_to, "poster", 6)))
2921 compose_entry_append
2923 (compose->replyto ? compose->replyto :
2924 msginfo->from ? msginfo->from : ""),
2927 else if (followup_and_reply_to || to_all) {
2928 compose_entry_append
2930 (compose->replyto ? compose->replyto :
2931 msginfo->from ? msginfo->from : ""),
2934 compose_entry_append
2936 compose->followup_to ? compose->followup_to :
2937 compose->newsgroups ? compose->newsgroups : "",
2938 COMPOSE_NEWSGROUPS);
2941 compose_entry_append
2943 compose->followup_to ? compose->followup_to :
2944 compose->newsgroups ? compose->newsgroups : "",
2945 COMPOSE_NEWSGROUPS);
2948 if (msginfo->subject && *msginfo->subject) {
2952 buf = p = g_strdup(msginfo->subject);
2953 p += subject_get_prefix_length(p);
2954 memmove(buf, p, strlen(p) + 1);
2956 buf2 = g_strdup_printf("Re: %s", buf);
2957 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
2962 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
2964 if (to_ml && compose->ml_post) return;
2965 if (!to_all || compose->account->protocol == A_NNTP) return;
2967 if (compose->replyto) {
2968 Xstrdup_a(replyto, compose->replyto, return);
2969 extract_address(replyto);
2971 if (msginfo->from) {
2972 Xstrdup_a(from, msginfo->from, return);
2973 extract_address(from);
2976 if (replyto && from)
2977 cc_list = address_list_append_with_comments(cc_list, from);
2978 if (to_all && msginfo->folder &&
2979 msginfo->folder->prefs->enable_default_reply_to)
2980 cc_list = address_list_append_with_comments(cc_list,
2981 msginfo->folder->prefs->default_reply_to);
2982 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
2983 cc_list = address_list_append_with_comments(cc_list, compose->cc);
2985 to_table = g_hash_table_new(g_str_hash, g_str_equal);
2987 g_hash_table_insert(to_table, g_utf8_strdown(replyto, -1), GINT_TO_POINTER(1));
2988 if (compose->account) {
2989 g_hash_table_insert(to_table, g_utf8_strdown(compose->account->address, -1),
2990 GINT_TO_POINTER(1));
2992 /* remove address on To: and that of current account */
2993 for (cur = cc_list; cur != NULL; ) {
2994 GSList *next = cur->next;
2997 addr = g_utf8_strdown(cur->data, -1);
2998 extract_address(addr);
3000 if (GPOINTER_TO_INT(g_hash_table_lookup(to_table, addr)) == 1)
3001 cc_list = g_slist_remove(cc_list, cur->data);
3003 g_hash_table_insert(to_table, addr, GINT_TO_POINTER(1));
3007 hash_free_strings(to_table);
3008 g_hash_table_destroy(to_table);
3011 for (cur = cc_list; cur != NULL; cur = cur->next)
3012 compose_entry_append(compose, (gchar *)cur->data,
3014 slist_free_strings(cc_list);
3015 g_slist_free(cc_list);
3020 #define SET_ENTRY(entry, str) \
3023 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3026 #define SET_ADDRESS(type, str) \
3029 compose_entry_append(compose, str, type); \
3032 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3034 g_return_if_fail(msginfo != NULL);
3036 SET_ENTRY(subject_entry, msginfo->subject);
3037 SET_ENTRY(from_name, msginfo->from);
3038 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3039 SET_ADDRESS(COMPOSE_CC, compose->cc);
3040 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3041 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3042 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3043 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3045 compose_update_priority_menu_item(compose);
3046 compose_update_privacy_system_menu_item(compose, FALSE);
3047 compose_show_first_last_header(compose, TRUE);
3053 static void compose_insert_sig(Compose *compose, gboolean replace)
3055 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3056 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3058 GtkTextIter iter, iter_end;
3060 gboolean prev_autowrap;
3061 gboolean found = FALSE;
3062 gboolean exists = FALSE;
3064 g_return_if_fail(compose->account != NULL);
3068 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3069 G_CALLBACK(compose_changed_cb),
3072 mark = gtk_text_buffer_get_insert(buffer);
3073 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3074 cur_pos = gtk_text_iter_get_offset (&iter);
3076 gtk_text_buffer_get_end_iter(buffer, &iter);
3078 exists = (compose->sig_str != NULL);
3081 GtkTextIter first_iter, start_iter, end_iter;
3083 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3085 if (!exists || compose->sig_str[0] == '\0')
3088 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3089 compose->signature_tag);
3092 /* include previous \n\n */
3093 gtk_text_iter_backward_chars(&first_iter, 2);
3094 start_iter = first_iter;
3095 end_iter = first_iter;
3097 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3098 compose->signature_tag);
3099 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3100 compose->signature_tag);
3102 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3108 g_free(compose->sig_str);
3109 compose->sig_str = compose_get_signature_str(compose);
3111 cur_pos = gtk_text_iter_get_offset(&iter);
3113 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3114 g_free(compose->sig_str);
3115 compose->sig_str = NULL;
3117 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3119 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3120 gtk_text_iter_forward_chars(&iter, 2);
3121 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3122 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3124 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3125 cur_pos = gtk_text_buffer_get_char_count (buffer);
3127 /* put the cursor where it should be
3128 * either where the quote_fmt says, either before the signature */
3129 if (compose->set_cursor_pos < 0)
3130 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3132 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3133 compose->set_cursor_pos);
3135 gtk_text_buffer_place_cursor(buffer, &iter);
3136 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3137 G_CALLBACK(compose_changed_cb),
3143 static gchar *compose_get_signature_str(Compose *compose)
3145 gchar *sig_body = NULL;
3146 gchar *sig_str = NULL;
3147 gchar *utf8_sig_str = NULL;
3149 g_return_val_if_fail(compose->account != NULL, NULL);
3151 if (!compose->account->sig_path)
3154 if (compose->account->sig_type == SIG_FILE) {
3155 if (!is_file_or_fifo_exist(compose->account->sig_path)) {
3156 g_warning("can't open signature file: %s\n",
3157 compose->account->sig_path);
3162 if (compose->account->sig_type == SIG_COMMAND)
3163 sig_body = get_command_output(compose->account->sig_path);
3167 tmp = file_read_to_str(compose->account->sig_path);
3170 sig_body = normalize_newlines(tmp);
3174 if (compose->account->sig_sep) {
3175 sig_str = g_strconcat("\n\n", compose->account->sig_sep, "\n", sig_body,
3179 sig_str = g_strconcat("\n\n", sig_body, NULL);
3182 if (g_utf8_validate(sig_str, -1, NULL) == TRUE)
3183 utf8_sig_str = sig_str;
3185 utf8_sig_str = conv_codeset_strdup
3186 (sig_str, conv_get_locale_charset_str_no_utf8(),
3192 return utf8_sig_str;
3195 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3198 GtkTextBuffer *buffer;
3201 const gchar *cur_encoding;
3202 gchar buf[BUFFSIZE];
3205 gboolean prev_autowrap;
3206 gboolean badtxt = FALSE;
3208 g_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3210 if ((fp = g_fopen(file, "rb")) == NULL) {
3211 FILE_OP_ERROR(file, "fopen");
3212 return COMPOSE_INSERT_READ_ERROR;
3215 prev_autowrap = compose->autowrap;
3216 compose->autowrap = FALSE;
3218 text = GTK_TEXT_VIEW(compose->text);
3219 buffer = gtk_text_view_get_buffer(text);
3220 mark = gtk_text_buffer_get_insert(buffer);
3221 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3223 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3224 G_CALLBACK(text_inserted),
3227 cur_encoding = conv_get_locale_charset_str_no_utf8();
3229 while (fgets(buf, sizeof(buf), fp) != NULL) {
3232 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3233 str = g_strdup(buf);
3235 str = conv_codeset_strdup
3236 (buf, cur_encoding, CS_INTERNAL);
3239 /* strip <CR> if DOS/Windows file,
3240 replace <CR> with <LF> if Macintosh file. */
3243 if (len > 0 && str[len - 1] != '\n') {
3245 if (str[len] == '\r') str[len] = '\n';
3248 gtk_text_buffer_insert(buffer, &iter, str, -1);
3252 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3253 G_CALLBACK(text_inserted),
3255 compose->autowrap = prev_autowrap;
3256 if (compose->autowrap)
3257 compose_wrap_all(compose);
3262 return COMPOSE_INSERT_INVALID_CHARACTER;
3264 return COMPOSE_INSERT_SUCCESS;
3267 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3268 const gchar *filename,
3269 const gchar *content_type)
3277 GtkListStore *store;
3279 gboolean has_binary = FALSE;
3281 if (!is_file_exist(file)) {
3282 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3283 gboolean result = FALSE;
3284 if (file_from_uri && is_file_exist(file_from_uri)) {
3285 result = compose_attach_append(
3286 compose, file_from_uri,
3290 g_free(file_from_uri);
3293 alertpanel_error("File %s doesn't exist\n", filename);
3296 if ((size = get_file_size(file)) < 0) {
3297 alertpanel_error("Can't get file size of %s\n", filename);
3301 alertpanel_error(_("File %s is empty."), filename);
3304 if ((fp = g_fopen(file, "rb")) == NULL) {
3305 alertpanel_error(_("Can't read %s."), filename);
3310 ainfo = g_new0(AttachInfo, 1);
3311 auto_ainfo = g_auto_pointer_new_with_free
3312 (ainfo, (GFreeFunc) compose_attach_info_free);
3313 ainfo->file = g_strdup(file);
3316 ainfo->content_type = g_strdup(content_type);
3317 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3319 MsgFlags flags = {0, 0};
3321 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3322 ainfo->encoding = ENC_7BIT;
3324 ainfo->encoding = ENC_8BIT;
3326 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3327 if (msginfo && msginfo->subject)
3328 name = g_strdup(msginfo->subject);
3330 name = g_path_get_basename(filename ? filename : file);
3332 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3334 procmsg_msginfo_free(msginfo);
3336 if (!g_ascii_strncasecmp(content_type, "text", 4))
3337 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3339 ainfo->encoding = ENC_BASE64;
3340 name = g_path_get_basename(filename ? filename : file);
3341 ainfo->name = g_strdup(name);
3345 ainfo->content_type = procmime_get_mime_type(file);
3346 if (!ainfo->content_type) {
3347 ainfo->content_type =
3348 g_strdup("application/octet-stream");
3349 ainfo->encoding = ENC_BASE64;
3350 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text", 4))
3352 procmime_get_encoding_for_text_file(file, &has_binary);
3354 ainfo->encoding = ENC_BASE64;
3355 name = g_path_get_basename(filename ? filename : file);
3356 ainfo->name = g_strdup(name);
3360 if (ainfo->name != NULL
3361 && !strcmp(ainfo->name, ".")) {
3362 g_free(ainfo->name);
3366 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3367 g_free(ainfo->content_type);
3368 ainfo->content_type = g_strdup("application/octet-stream");
3372 size_text = to_human_readable(size);
3374 store = GTK_LIST_STORE(gtk_tree_view_get_model
3375 (GTK_TREE_VIEW(compose->attach_clist)));
3377 gtk_list_store_append(store, &iter);
3378 gtk_list_store_set(store, &iter,
3379 COL_MIMETYPE, ainfo->content_type,
3380 COL_SIZE, size_text,
3381 COL_NAME, ainfo->name,
3383 COL_AUTODATA, auto_ainfo,
3386 g_auto_pointer_free(auto_ainfo);
3387 compose_attach_update_label(compose);
3391 static void compose_use_signing(Compose *compose, gboolean use_signing)
3393 GtkItemFactory *ifactory;
3394 GtkWidget *menuitem = NULL;
3396 compose->use_signing = use_signing;
3397 ifactory = gtk_item_factory_from_widget(compose->menubar);
3398 menuitem = gtk_item_factory_get_item
3399 (ifactory, "/Options/Sign");
3400 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
3404 static void compose_use_encryption(Compose *compose, gboolean use_encryption)
3406 GtkItemFactory *ifactory;
3407 GtkWidget *menuitem = NULL;
3409 compose->use_encryption = use_encryption;
3410 ifactory = gtk_item_factory_from_widget(compose->menubar);
3411 menuitem = gtk_item_factory_get_item
3412 (ifactory, "/Options/Encrypt");
3414 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
3418 #define NEXT_PART_NOT_CHILD(info) \
3420 node = info->node; \
3421 while (node->children) \
3422 node = g_node_last_child(node); \
3423 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3426 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3430 MimeInfo *firsttext = NULL;
3431 MimeInfo *encrypted = NULL;
3434 const gchar *partname = NULL;
3436 mimeinfo = procmime_scan_message(msginfo);
3437 if (!mimeinfo) return;
3439 if (mimeinfo->node->children == NULL) {
3440 procmime_mimeinfo_free_all(mimeinfo);
3444 /* find first content part */
3445 child = (MimeInfo *) mimeinfo->node->children->data;
3446 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3447 child = (MimeInfo *)child->node->children->data;
3449 if (child->type == MIMETYPE_TEXT) {
3451 debug_print("First text part found\n");
3452 } else if (compose->mode == COMPOSE_REEDIT &&
3453 child->type == MIMETYPE_APPLICATION &&
3454 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3455 encrypted = (MimeInfo *)child->node->parent->data;
3458 child = (MimeInfo *) mimeinfo->node->children->data;
3459 while (child != NULL) {
3462 if (child == encrypted) {
3463 /* skip this part of tree */
3464 NEXT_PART_NOT_CHILD(child);
3468 if (child->type == MIMETYPE_MULTIPART) {
3469 /* get the actual content */
3470 child = procmime_mimeinfo_next(child);
3474 if (child == firsttext) {
3475 child = procmime_mimeinfo_next(child);
3479 outfile = procmime_get_tmp_file_name(child);
3480 if ((err = procmime_get_part(outfile, child)) < 0)
3481 g_warning("Can't get the part of multipart message. (%s)", strerror(-err));
3483 gchar *content_type;
3485 content_type = procmime_get_content_type_str(child->type, child->subtype);
3487 /* if we meet a pgp signature, we don't attach it, but
3488 * we force signing. */
3489 if ((strcmp(content_type, "application/pgp-signature") &&
3490 strcmp(content_type, "application/pkcs7-signature") &&
3491 strcmp(content_type, "application/x-pkcs7-signature"))
3492 || compose->mode == COMPOSE_REDIRECT) {
3493 partname = procmime_mimeinfo_get_parameter(child, "filename");
3494 if (partname == NULL)
3495 partname = procmime_mimeinfo_get_parameter(child, "name");
3496 if (partname == NULL)
3498 compose_attach_append(compose, outfile,
3499 partname, content_type);
3501 compose_force_signing(compose, compose->account);
3503 g_free(content_type);
3506 NEXT_PART_NOT_CHILD(child);
3508 procmime_mimeinfo_free_all(mimeinfo);
3511 #undef NEXT_PART_NOT_CHILD
3516 WAIT_FOR_INDENT_CHAR,
3517 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3520 /* return indent length, we allow:
3521 indent characters followed by indent characters or spaces/tabs,
3522 alphabets and numbers immediately followed by indent characters,
3523 and the repeating sequences of the above
3524 If quote ends with multiple spaces, only the first one is included. */
3525 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3526 const GtkTextIter *start, gint *len)
3528 GtkTextIter iter = *start;
3532 IndentState state = WAIT_FOR_INDENT_CHAR;
3535 gint alnum_count = 0;
3536 gint space_count = 0;
3539 if (prefs_common.quote_chars == NULL) {
3543 while (!gtk_text_iter_ends_line(&iter)) {
3544 wc = gtk_text_iter_get_char(&iter);
3545 if (g_unichar_iswide(wc))
3547 clen = g_unichar_to_utf8(wc, ch);
3551 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
3552 is_space = g_unichar_isspace(wc);
3554 if (state == WAIT_FOR_INDENT_CHAR) {
3555 if (!is_indent && !g_unichar_isalnum(wc))
3558 quote_len += alnum_count + space_count + 1;
3559 alnum_count = space_count = 0;
3560 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
3563 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
3564 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
3568 else if (is_indent) {
3569 quote_len += alnum_count + space_count + 1;
3570 alnum_count = space_count = 0;
3573 state = WAIT_FOR_INDENT_CHAR;
3577 gtk_text_iter_forward_char(&iter);
3580 if (quote_len > 0 && space_count > 0)
3586 if (quote_len > 0) {
3588 gtk_text_iter_forward_chars(&iter, quote_len);
3589 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
3595 /* return TRUE if the line is itemized */
3596 static gboolean compose_is_itemized(GtkTextBuffer *buffer,
3597 const GtkTextIter *start)
3599 GtkTextIter iter = *start;
3604 if (gtk_text_iter_ends_line(&iter))
3608 wc = gtk_text_iter_get_char(&iter);
3609 if (!g_unichar_isspace(wc))
3611 gtk_text_iter_forward_char(&iter);
3612 if (gtk_text_iter_ends_line(&iter))
3616 clen = g_unichar_to_utf8(wc, ch);
3620 if (!strchr("*-+", ch[0]))
3623 gtk_text_iter_forward_char(&iter);
3624 if (gtk_text_iter_ends_line(&iter))
3626 wc = gtk_text_iter_get_char(&iter);
3627 if (g_unichar_isspace(wc))
3633 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
3634 const GtkTextIter *start,
3635 GtkTextIter *break_pos,
3639 GtkTextIter iter = *start, line_end = *start;
3640 PangoLogAttr *attrs;
3647 gboolean can_break = FALSE;
3648 gboolean do_break = FALSE;
3649 gboolean was_white = FALSE;
3650 gboolean prev_dont_break = FALSE;
3652 gtk_text_iter_forward_to_line_end(&line_end);
3653 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
3654 len = g_utf8_strlen(str, -1);
3658 g_warning("compose_get_line_break_pos: len = 0!\n");
3662 /* g_print("breaking line: %d: %s (len = %d)\n",
3663 gtk_text_iter_get_line(&iter), str, len); */
3665 attrs = g_new(PangoLogAttr, len + 1);
3667 pango_default_break(str, -1, NULL, attrs, len + 1);
3671 /* skip quote and leading spaces */
3672 for (i = 0; *p != '\0' && i < len; i++) {
3675 wc = g_utf8_get_char(p);
3676 if (i >= quote_len && !g_unichar_isspace(wc))
3678 if (g_unichar_iswide(wc))
3680 else if (*p == '\t')
3684 p = g_utf8_next_char(p);
3687 for (; *p != '\0' && i < len; i++) {
3688 PangoLogAttr *attr = attrs + i;
3692 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
3695 was_white = attr->is_white;
3697 /* don't wrap URI */
3698 if ((uri_len = get_uri_len(p)) > 0) {
3700 if (pos > 0 && col > max_col) {
3710 wc = g_utf8_get_char(p);
3711 if (g_unichar_iswide(wc)) {
3713 if (prev_dont_break && can_break && attr->is_line_break)
3715 } else if (*p == '\t')
3719 if (pos > 0 && col > max_col) {
3724 if (*p == '-' || *p == '/')
3725 prev_dont_break = TRUE;
3727 prev_dont_break = FALSE;
3729 p = g_utf8_next_char(p);
3733 debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col);
3738 *break_pos = *start;
3739 gtk_text_iter_set_line_offset(break_pos, pos);
3744 static gboolean compose_join_next_line(Compose *compose,
3745 GtkTextBuffer *buffer,
3747 const gchar *quote_str)
3749 GtkTextIter iter_ = *iter, cur, prev, next, end;
3750 PangoLogAttr attrs[3];
3752 gchar *next_quote_str;
3755 gboolean keep_cursor = FALSE;
3757 if (!gtk_text_iter_forward_line(&iter_) ||
3758 gtk_text_iter_ends_line(&iter_))
3761 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
3763 if ((quote_str || next_quote_str) &&
3764 strcmp2(quote_str, next_quote_str) != 0) {
3765 g_free(next_quote_str);
3768 g_free(next_quote_str);
3771 if (quote_len > 0) {
3772 gtk_text_iter_forward_chars(&end, quote_len);
3773 if (gtk_text_iter_ends_line(&end))
3777 /* don't join itemized lines */
3778 if (compose_is_itemized(buffer, &end))
3781 /* don't join signature separator */
3782 if (compose_is_sig_separator(compose, buffer, &iter_))
3785 /* delete quote str */
3787 gtk_text_buffer_delete(buffer, &iter_, &end);
3789 /* don't join line breaks put by the user */
3791 gtk_text_iter_backward_char(&cur);
3792 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
3793 gtk_text_iter_forward_char(&cur);
3797 gtk_text_iter_forward_char(&cur);
3798 /* delete linebreak and extra spaces */
3799 while (gtk_text_iter_backward_char(&cur)) {
3800 wc1 = gtk_text_iter_get_char(&cur);
3801 if (!g_unichar_isspace(wc1))
3806 while (!gtk_text_iter_ends_line(&cur)) {
3807 wc1 = gtk_text_iter_get_char(&cur);
3808 if (!g_unichar_isspace(wc1))
3810 gtk_text_iter_forward_char(&cur);
3813 if (!gtk_text_iter_equal(&prev, &next)) {
3816 mark = gtk_text_buffer_get_insert(buffer);
3817 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
3818 if (gtk_text_iter_equal(&prev, &cur))
3820 gtk_text_buffer_delete(buffer, &prev, &next);
3824 /* insert space if required */
3825 gtk_text_iter_backward_char(&prev);
3826 wc1 = gtk_text_iter_get_char(&prev);
3827 wc2 = gtk_text_iter_get_char(&next);
3828 gtk_text_iter_forward_char(&next);
3829 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
3830 pango_default_break(str, -1, NULL, attrs, 3);
3831 if (!attrs[1].is_line_break ||
3832 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
3833 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
3835 gtk_text_iter_backward_char(&iter_);
3836 gtk_text_buffer_place_cursor(buffer, &iter_);
3845 #define ADD_TXT_POS(bp_, ep_, pti_) \
3846 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
3847 last = last->next; \
3848 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
3849 last->next = NULL; \
3851 g_warning("alloc error scanning URIs\n"); \
3854 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
3856 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3857 GtkTextBuffer *buffer;
3858 GtkTextIter iter, break_pos, end_of_line;
3859 gchar *quote_str = NULL;
3861 gboolean wrap_quote = prefs_common.linewrap_quote;
3862 gboolean prev_autowrap = compose->autowrap;
3863 gint startq_offset = -1, noq_offset = -1;
3864 gint uri_start = -1, uri_stop = -1;
3865 gint nouri_start = -1, nouri_stop = -1;
3866 gint num_blocks = 0;
3867 gint quotelevel = -1;
3868 gboolean modified = force;
3869 gboolean removed = FALSE;
3870 gboolean modified_before_remove = FALSE;
3872 gboolean start = TRUE;
3877 if (compose->draft_timeout_tag == -2) {
3881 compose->autowrap = FALSE;
3883 buffer = gtk_text_view_get_buffer(text);
3884 undo_wrapping(compose->undostruct, TRUE);
3889 mark = gtk_text_buffer_get_insert(buffer);
3890 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3894 if (compose->draft_timeout_tag == -2) {
3895 if (gtk_text_iter_ends_line(&iter)) {
3896 while (gtk_text_iter_ends_line(&iter) &&
3897 gtk_text_iter_forward_line(&iter))
3900 while (gtk_text_iter_backward_line(&iter)) {
3901 if (gtk_text_iter_ends_line(&iter)) {
3902 gtk_text_iter_forward_line(&iter);
3908 /* move to line start */
3909 gtk_text_iter_set_line_offset(&iter, 0);
3911 /* go until paragraph end (empty line) */
3912 while (start || !gtk_text_iter_ends_line(&iter)) {
3913 gchar *scanpos = NULL;
3914 /* parse table - in order of priority */
3916 const gchar *needle; /* token */
3918 /* token search function */
3919 gchar *(*search) (const gchar *haystack,
3920 const gchar *needle);
3921 /* part parsing function */
3922 gboolean (*parse) (const gchar *start,
3923 const gchar *scanpos,
3927 /* part to URI function */
3928 gchar *(*build_uri) (const gchar *bp,
3932 static struct table parser[] = {
3933 {"http://", strcasestr, get_uri_part, make_uri_string},
3934 {"https://", strcasestr, get_uri_part, make_uri_string},
3935 {"ftp://", strcasestr, get_uri_part, make_uri_string},
3936 {"sftp://", strcasestr, get_uri_part, make_uri_string},
3937 {"www.", strcasestr, get_uri_part, make_http_string},
3938 {"mailto:", strcasestr, get_uri_part, make_uri_string},
3939 {"@", strcasestr, get_email_part, make_email_string}
3941 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
3942 gint last_index = PARSE_ELEMS;
3944 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
3948 if (!prev_autowrap && num_blocks == 0) {
3950 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3951 G_CALLBACK(text_inserted),
3954 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
3957 uri_start = uri_stop = -1;
3959 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
3962 debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str);
3963 if (startq_offset == -1)
3964 startq_offset = gtk_text_iter_get_offset(&iter);
3965 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
3966 if (quotelevel > 2) {
3967 /* recycle colors */
3968 if (prefs_common.recycle_quote_colors)
3977 if (startq_offset == -1)
3978 noq_offset = gtk_text_iter_get_offset(&iter);
3982 if (prev_autowrap == FALSE && !force && !wrap_quote) {
3985 if (gtk_text_iter_ends_line(&iter)) {
3987 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
3988 prefs_common.linewrap_len,
3990 GtkTextIter prev, next, cur;
3992 if (prev_autowrap != FALSE || force) {
3993 compose->automatic_break = TRUE;
3995 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
3996 compose->automatic_break = FALSE;
3997 } else if (quote_str && wrap_quote) {
3998 compose->automatic_break = TRUE;
4000 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4001 compose->automatic_break = FALSE;
4004 /* remove trailing spaces */
4006 gtk_text_iter_backward_char(&cur);
4008 while (!gtk_text_iter_starts_line(&cur)) {
4011 gtk_text_iter_backward_char(&cur);
4012 wc = gtk_text_iter_get_char(&cur);
4013 if (!g_unichar_isspace(wc))
4017 if (!gtk_text_iter_equal(&prev, &next)) {
4018 gtk_text_buffer_delete(buffer, &prev, &next);
4020 gtk_text_iter_forward_char(&break_pos);
4024 gtk_text_buffer_insert(buffer, &break_pos,
4028 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4030 /* move iter to current line start */
4031 gtk_text_iter_set_line_offset(&iter, 0);
4038 /* move iter to next line start */
4044 if (!prev_autowrap && num_blocks > 0) {
4046 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4047 G_CALLBACK(text_inserted),
4051 while (!gtk_text_iter_ends_line(&end_of_line)) {
4052 gtk_text_iter_forward_char(&end_of_line);
4054 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4056 nouri_start = gtk_text_iter_get_offset(&iter);
4057 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4059 walk_pos = gtk_text_iter_get_offset(&iter);
4060 /* FIXME: this looks phony. scanning for anything in the parse table */
4061 for (n = 0; n < PARSE_ELEMS; n++) {
4064 tmp = parser[n].search(walk, parser[n].needle);
4066 if (scanpos == NULL || tmp < scanpos) {
4075 /* check if URI can be parsed */
4076 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4077 (const gchar **)&ep, FALSE)
4078 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4082 strlen(parser[last_index].needle);
4085 uri_start = walk_pos + (bp - o_walk);
4086 uri_stop = walk_pos + (ep - o_walk);
4090 gtk_text_iter_forward_line(&iter);
4093 if (startq_offset != -1) {
4094 GtkTextIter startquote, endquote;
4095 gtk_text_buffer_get_iter_at_offset(
4096 buffer, &startquote, startq_offset);
4099 switch (quotelevel) {
4101 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4102 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4103 gtk_text_buffer_apply_tag_by_name(
4104 buffer, "quote0", &startquote, &endquote);
4105 gtk_text_buffer_remove_tag_by_name(
4106 buffer, "quote1", &startquote, &endquote);
4107 gtk_text_buffer_remove_tag_by_name(
4108 buffer, "quote2", &startquote, &endquote);
4113 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4114 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4115 gtk_text_buffer_apply_tag_by_name(
4116 buffer, "quote1", &startquote, &endquote);
4117 gtk_text_buffer_remove_tag_by_name(
4118 buffer, "quote0", &startquote, &endquote);
4119 gtk_text_buffer_remove_tag_by_name(
4120 buffer, "quote2", &startquote, &endquote);
4125 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4126 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4127 gtk_text_buffer_apply_tag_by_name(
4128 buffer, "quote2", &startquote, &endquote);
4129 gtk_text_buffer_remove_tag_by_name(
4130 buffer, "quote0", &startquote, &endquote);
4131 gtk_text_buffer_remove_tag_by_name(
4132 buffer, "quote1", &startquote, &endquote);
4138 } else if (noq_offset != -1) {
4139 GtkTextIter startnoquote, endnoquote;
4140 gtk_text_buffer_get_iter_at_offset(
4141 buffer, &startnoquote, noq_offset);
4144 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4145 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4146 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4147 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4148 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4149 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4150 gtk_text_buffer_remove_tag_by_name(
4151 buffer, "quote0", &startnoquote, &endnoquote);
4152 gtk_text_buffer_remove_tag_by_name(
4153 buffer, "quote1", &startnoquote, &endnoquote);
4154 gtk_text_buffer_remove_tag_by_name(
4155 buffer, "quote2", &startnoquote, &endnoquote);
4161 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4162 GtkTextIter nouri_start_iter, nouri_end_iter;
4163 gtk_text_buffer_get_iter_at_offset(
4164 buffer, &nouri_start_iter, nouri_start);
4165 gtk_text_buffer_get_iter_at_offset(
4166 buffer, &nouri_end_iter, nouri_stop);
4167 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4168 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4169 gtk_text_buffer_remove_tag_by_name(
4170 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4171 modified_before_remove = modified;
4176 if (uri_start > 0 && uri_stop > 0) {
4177 GtkTextIter uri_start_iter, uri_end_iter, back;
4178 gtk_text_buffer_get_iter_at_offset(
4179 buffer, &uri_start_iter, uri_start);
4180 gtk_text_buffer_get_iter_at_offset(
4181 buffer, &uri_end_iter, uri_stop);
4182 back = uri_end_iter;
4183 gtk_text_iter_backward_char(&back);
4184 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4185 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4186 gtk_text_buffer_apply_tag_by_name(
4187 buffer, "link", &uri_start_iter, &uri_end_iter);
4189 if (removed && !modified_before_remove) {
4195 debug_print("not modified, out after %d lines\n", lines);
4200 debug_print("modified, out after %d lines\n", lines);
4204 undo_wrapping(compose->undostruct, FALSE);
4205 compose->autowrap = prev_autowrap;
4210 void compose_action_cb(void *data)
4212 Compose *compose = (Compose *)data;
4213 compose_wrap_all(compose);
4216 static void compose_wrap_all(Compose *compose)
4218 compose_wrap_all_full(compose, FALSE);
4221 static void compose_wrap_all_full(Compose *compose, gboolean force)
4223 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4224 GtkTextBuffer *buffer;
4226 gboolean modified = TRUE;
4228 buffer = gtk_text_view_get_buffer(text);
4230 gtk_text_buffer_get_start_iter(buffer, &iter);
4231 while (!gtk_text_iter_is_end(&iter) && modified)
4232 modified = compose_beautify_paragraph(compose, &iter, force);
4236 static void compose_set_title(Compose *compose)
4242 edited = compose->modified ? _(" [Edited]") : "";
4244 subject = gtk_editable_get_chars(
4245 GTK_EDITABLE(compose->subject_entry), 0, -1);
4248 if (subject && strlen(subject))
4249 str = g_strdup_printf(_("%s - Compose message%s"),
4252 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4254 str = g_strdup(_("Compose message"));
4257 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4263 * compose_current_mail_account:
4265 * Find a current mail account (the currently selected account, or the
4266 * default account, if a news account is currently selected). If a
4267 * mail account cannot be found, display an error message.
4269 * Return value: Mail account, or NULL if not found.
4271 static PrefsAccount *
4272 compose_current_mail_account(void)
4276 if (cur_account && cur_account->protocol != A_NNTP)
4279 ac = account_get_default();
4280 if (!ac || ac->protocol == A_NNTP) {
4281 alertpanel_error(_("Account for sending mail is not specified.\n"
4282 "Please select a mail account before sending."));
4289 #define QUOTE_IF_REQUIRED(out, str) \
4291 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4295 len = strlen(str) + 3; \
4296 if ((__tmp = alloca(len)) == NULL) { \
4297 g_warning("can't allocate memory\n"); \
4298 g_string_free(header, TRUE); \
4301 g_snprintf(__tmp, len, "\"%s\"", str); \
4306 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4307 g_warning("can't allocate memory\n"); \
4308 g_string_free(header, TRUE); \
4311 strcpy(__tmp, str); \
4317 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4319 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4323 len = strlen(str) + 3; \
4324 if ((__tmp = alloca(len)) == NULL) { \
4325 g_warning("can't allocate memory\n"); \
4328 g_snprintf(__tmp, len, "\"%s\"", str); \
4333 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4334 g_warning("can't allocate memory\n"); \
4337 strcpy(__tmp, str); \
4343 static void compose_select_account(Compose *compose, PrefsAccount *account,
4346 GtkItemFactory *ifactory;
4349 g_return_if_fail(account != NULL);
4351 compose->account = account;
4353 if (account->name && *account->name) {
4355 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4356 from = g_strdup_printf("%s <%s>",
4357 buf, account->address);
4358 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4360 from = g_strdup_printf("<%s>",
4362 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4367 compose_set_title(compose);
4369 ifactory = gtk_item_factory_from_widget(compose->menubar);
4371 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4372 menu_set_active(ifactory, "/Options/Sign", TRUE);
4374 menu_set_active(ifactory, "/Options/Sign", FALSE);
4375 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4376 menu_set_active(ifactory, "/Options/Encrypt", TRUE);
4378 menu_set_active(ifactory, "/Options/Encrypt", FALSE);
4380 activate_privacy_system(compose, account, FALSE);
4382 if (!init && compose->mode != COMPOSE_REDIRECT) {
4383 undo_block(compose->undostruct);
4384 compose_insert_sig(compose, TRUE);
4385 undo_unblock(compose->undostruct);
4389 /* use account's dict info if set */
4390 if (compose->gtkaspell) {
4391 if (account->enable_default_dictionary)
4392 gtkaspell_change_dict(compose->gtkaspell,
4393 account->default_dictionary, FALSE);
4394 if (account->enable_default_alt_dictionary)
4395 gtkaspell_change_alt_dict(compose->gtkaspell,
4396 account->default_alt_dictionary);
4397 if (account->enable_default_dictionary
4398 || account->enable_default_alt_dictionary)
4399 compose_spell_menu_changed(compose);
4404 gboolean compose_check_for_valid_recipient(Compose *compose) {
4405 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
4406 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
4407 gboolean recipient_found = FALSE;
4411 /* free to and newsgroup list */
4412 slist_free_strings(compose->to_list);
4413 g_slist_free(compose->to_list);
4414 compose->to_list = NULL;
4416 slist_free_strings(compose->newsgroup_list);
4417 g_slist_free(compose->newsgroup_list);
4418 compose->newsgroup_list = NULL;
4420 /* search header entries for to and newsgroup entries */
4421 for (list = compose->header_list; list; list = list->next) {
4424 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_BIN(((ComposeHeaderEntry *)list->data)->combo)->child), 0, -1);
4425 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4428 if (entry[0] != '\0') {
4429 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
4430 if (!strcmp(header, prefs_common_translated_header_name(*strptr))) {
4431 compose->to_list = address_list_append(compose->to_list, entry);
4432 recipient_found = TRUE;
4435 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
4436 if (!strcmp(header, prefs_common_translated_header_name(*strptr))) {
4437 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
4438 recipient_found = TRUE;
4445 return recipient_found;
4448 static gboolean compose_check_for_set_recipients(Compose *compose)
4450 if (compose->account->set_autocc && compose->account->auto_cc) {
4451 gboolean found_other = FALSE;
4453 /* search header entries for to and newsgroup entries */
4454 for (list = compose->header_list; list; list = list->next) {
4457 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4458 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_BIN(((ComposeHeaderEntry *)list->data)->combo)->child), 0, -1);
4460 if (strcmp(entry, compose->account->auto_cc)
4461 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
4471 if (compose->batch) {
4472 gtk_widget_show_all(compose->window);
4474 aval = alertpanel(_("Send"),
4475 _("The only recipient is the default CC address. Send anyway?"),
4476 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4477 if (aval != G_ALERTALTERNATE)
4481 if (compose->account->set_autobcc && compose->account->auto_bcc) {
4482 gboolean found_other = FALSE;
4484 /* search header entries for to and newsgroup entries */
4485 for (list = compose->header_list; list; list = list->next) {
4488 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4489 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_BIN(((ComposeHeaderEntry *)list->data)->combo)->child), 0, -1);
4491 if (strcmp(entry, compose->account->auto_bcc)
4492 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
4502 if (compose->batch) {
4503 gtk_widget_show_all(compose->window);
4505 aval = alertpanel(_("Send"),
4506 _("The only recipient is the default BCC address. Send anyway?"),
4507 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4508 if (aval != G_ALERTALTERNATE)
4515 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
4519 if (compose_check_for_valid_recipient(compose) == FALSE) {
4520 if (compose->batch) {
4521 gtk_widget_show_all(compose->window);
4523 alertpanel_error(_("Recipient is not specified."));
4527 if (compose_check_for_set_recipients(compose) == FALSE) {
4531 if (!compose->batch) {
4532 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
4533 if (*str == '\0' && check_everything == TRUE &&
4534 compose->mode != COMPOSE_REDIRECT) {
4536 gchar *button_label;
4539 if (compose->sending)
4540 button_label = _("+_Send");
4542 button_label = _("+_Queue");
4543 message = g_strdup_printf(_("Subject is empty. %s it anyway?"),
4544 compose->sending?_("Send"):_("Queue"));
4546 aval = alertpanel(compose->sending?_("Send"):_("Send later"), message,
4547 GTK_STOCK_CANCEL, button_label, NULL);
4549 if (aval != G_ALERTALTERNATE)
4554 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
4560 gint compose_send(Compose *compose)
4563 FolderItem *folder = NULL;
4565 gchar *msgpath = NULL;
4566 gboolean discard_window = FALSE;
4567 gchar *errstr = NULL;
4568 gchar *tmsgid = NULL;
4569 MainWindow *mainwin = mainwindow_get_mainwindow();
4570 gboolean queued_removed = FALSE;
4572 if (prefs_common.send_dialog_invisible
4573 || compose->batch == TRUE)
4574 discard_window = TRUE;
4576 compose_allow_user_actions (compose, FALSE);
4577 compose->sending = TRUE;
4579 if (compose_check_entries(compose, TRUE) == FALSE) {
4580 if (compose->batch) {
4581 gtk_widget_show_all(compose->window);
4587 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
4590 if (compose->batch) {
4591 gtk_widget_show_all(compose->window);
4594 alertpanel_error(_("Could not queue message for sending:\n\n"
4595 "Charset conversion failed."));
4596 } else if (val == -5) {
4597 alertpanel_error(_("Could not queue message for sending:\n\n"
4598 "Couldn't get recipient encryption key."));
4599 } else if (val == -6) {
4601 } else if (val == -3) {
4602 if (privacy_peek_error())
4603 alertpanel_error(_("Could not queue message for sending:\n\n"
4604 "Signature failed: %s"), privacy_get_error());
4605 } else if (val == -2 && errno != 0) {
4606 alertpanel_error(_("Could not queue message for sending:\n\n%s."), strerror(errno));
4608 alertpanel_error(_("Could not queue message for sending."));
4613 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
4614 if (discard_window) {
4615 compose->sending = FALSE;
4616 compose_close(compose);
4617 /* No more compose access in the normal codepath
4618 * after this point! */
4623 alertpanel_error(_("The message was queued but could not be "
4624 "sent.\nUse \"Send queued messages\" from "
4625 "the main window to retry."));
4626 if (!discard_window) {
4633 if (msgpath == NULL) {
4634 msgpath = folder_item_fetch_msg(folder, msgnum);
4635 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
4638 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
4642 if (!discard_window) {
4644 if (!queued_removed)
4645 folder_item_remove_msg(folder, msgnum);
4646 folder_item_scan(folder);
4648 /* make sure we delete that */
4649 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
4651 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
4652 folder_item_remove_msg(folder, tmp->msgnum);
4653 procmsg_msginfo_free(tmp);
4660 if (!queued_removed)
4661 folder_item_remove_msg(folder, msgnum);
4662 folder_item_scan(folder);
4664 /* make sure we delete that */
4665 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
4667 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
4668 folder_item_remove_msg(folder, tmp->msgnum);
4669 procmsg_msginfo_free(tmp);
4672 if (!discard_window) {
4673 compose->sending = FALSE;
4674 compose_allow_user_actions (compose, TRUE);
4675 compose_close(compose);
4679 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
4680 "the main window to retry."), errstr);
4683 alertpanel_error_log(_("The message was queued but could not be "
4684 "sent.\nUse \"Send queued messages\" from "
4685 "the main window to retry."));
4687 if (!discard_window) {
4696 toolbar_main_set_sensitive(mainwin);
4697 main_window_set_menu_sensitive(mainwin);
4703 compose_allow_user_actions (compose, TRUE);
4704 compose->sending = FALSE;
4705 compose->modified = TRUE;
4706 toolbar_main_set_sensitive(mainwin);
4707 main_window_set_menu_sensitive(mainwin);
4712 static gboolean compose_use_attach(Compose *compose)
4714 GtkTreeModel *model = gtk_tree_view_get_model
4715 (GTK_TREE_VIEW(compose->attach_clist));
4716 return gtk_tree_model_iter_n_children(model, NULL) > 0;
4719 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
4722 gchar buf[BUFFSIZE];
4724 gboolean first_to_address;
4725 gboolean first_cc_address;
4727 ComposeHeaderEntry *headerentry;
4728 const gchar *headerentryname;
4729 const gchar *cc_hdr;
4730 const gchar *to_hdr;
4731 gboolean err = FALSE;
4733 debug_print("Writing redirect header\n");
4735 cc_hdr = prefs_common_translated_header_name("Cc:");
4736 to_hdr = prefs_common_translated_header_name("To:");
4738 first_to_address = TRUE;
4739 for (list = compose->header_list; list; list = list->next) {
4740 headerentry = ((ComposeHeaderEntry *)list->data);
4741 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_BIN(headerentry->combo)->child));
4743 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
4744 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
4745 Xstrdup_a(str, entstr, return -1);
4747 if (str[0] != '\0') {
4748 compose_convert_header
4749 (compose, buf, sizeof(buf), str,
4750 strlen("Resent-To") + 2, TRUE);
4752 if (first_to_address) {
4753 err |= (fprintf(fp, "Resent-To: ") < 0);
4754 first_to_address = FALSE;
4756 err |= (fprintf(fp, ",") < 0);
4758 err |= (fprintf(fp, "%s", buf) < 0);
4762 if (!first_to_address) {
4763 err |= (fprintf(fp, "\n") < 0);
4766 first_cc_address = TRUE;
4767 for (list = compose->header_list; list; list = list->next) {
4768 headerentry = ((ComposeHeaderEntry *)list->data);
4769 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_BIN(headerentry->combo)->child));
4771 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
4772 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
4773 Xstrdup_a(str, strg, return -1);
4775 if (str[0] != '\0') {
4776 compose_convert_header
4777 (compose, buf, sizeof(buf), str,
4778 strlen("Resent-Cc") + 2, TRUE);
4780 if (first_cc_address) {
4781 err |= (fprintf(fp, "Resent-Cc: ") < 0);
4782 first_cc_address = FALSE;
4784 err |= (fprintf(fp, ",") < 0);
4786 err |= (fprintf(fp, "%s", buf) < 0);
4790 if (!first_cc_address) {
4791 err |= (fprintf(fp, "\n") < 0);
4794 return (err ? -1:0);
4797 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
4799 gchar buf[BUFFSIZE];
4801 const gchar *entstr;
4802 /* struct utsname utsbuf; */
4803 gboolean err = FALSE;
4805 g_return_val_if_fail(fp != NULL, -1);
4806 g_return_val_if_fail(compose->account != NULL, -1);
4807 g_return_val_if_fail(compose->account->address != NULL, -1);
4810 get_rfc822_date(buf, sizeof(buf));
4811 err |= (fprintf(fp, "Resent-Date: %s\n", buf) < 0);
4814 if (compose->account->name && *compose->account->name) {
4815 compose_convert_header
4816 (compose, buf, sizeof(buf), compose->account->name,
4817 strlen("From: "), TRUE);
4818 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
4819 buf, compose->account->address) < 0);
4821 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
4824 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
4825 if (*entstr != '\0') {
4826 Xstrdup_a(str, entstr, return -1);
4829 compose_convert_header(compose, buf, sizeof(buf), str,
4830 strlen("Subject: "), FALSE);
4831 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
4835 /* Resent-Message-ID */
4836 if (compose->account->set_domain && compose->account->domain) {
4837 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
4838 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
4839 g_snprintf(buf, sizeof(buf), "%s",
4840 strchr(compose->account->address, '@') ?
4841 strchr(compose->account->address, '@')+1 :
4842 compose->account->address);
4844 g_snprintf(buf, sizeof(buf), "%s", "");
4847 if (compose->account->gen_msgid) {
4848 generate_msgid(buf, sizeof(buf));
4849 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", buf) < 0);
4850 compose->msgid = g_strdup(buf);
4852 compose->msgid = NULL;
4855 if (compose_redirect_write_headers_from_headerlist(compose, fp))
4858 /* separator between header and body */
4859 err |= (fputs("\n", fp) == EOF);
4861 return (err ? -1:0);
4864 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
4868 gchar buf[BUFFSIZE];
4870 gboolean skip = FALSE;
4871 gboolean err = FALSE;
4872 gchar *not_included[]={
4873 "Return-Path:", "Delivered-To:", "Received:",
4874 "Subject:", "X-UIDL:", "AF:",
4875 "NF:", "PS:", "SRH:",
4876 "SFN:", "DSR:", "MID:",
4877 "CFG:", "PT:", "S:",
4878 "RQ:", "SSV:", "NSV:",
4879 "SSH:", "R:", "MAID:",
4880 "NAID:", "RMID:", "FMID:",
4881 "SCF:", "RRCPT:", "NG:",
4882 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
4883 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
4884 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
4885 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
4888 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
4889 FILE_OP_ERROR(compose->redirect_filename, "fopen");
4893 while (procheader_get_one_field_asis(buf, sizeof(buf), fp) != -1) {
4895 for (i = 0; not_included[i] != NULL; i++) {
4896 if (g_ascii_strncasecmp(buf, not_included[i],
4897 strlen(not_included[i])) == 0) {
4904 if (fputs(buf, fdest) == -1)
4907 if (!prefs_common.redirect_keep_from) {
4908 if (g_ascii_strncasecmp(buf, "From:",
4909 strlen("From:")) == 0) {
4910 err |= (fputs(" (by way of ", fdest) == EOF);
4911 if (compose->account->name
4912 && *compose->account->name) {
4913 compose_convert_header
4914 (compose, buf, sizeof(buf),
4915 compose->account->name,
4918 err |= (fprintf(fdest, "%s <%s>",
4920 compose->account->address) < 0);
4922 err |= (fprintf(fdest, "%s",
4923 compose->account->address) < 0);
4924 err |= (fputs(")", fdest) == EOF);
4928 if (fputs("\n", fdest) == -1)
4935 if (compose_redirect_write_headers(compose, fdest))
4938 while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
4939 if (fwrite(buf, sizeof(gchar), len, fdest) != len)
4952 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
4954 GtkTextBuffer *buffer;
4955 GtkTextIter start, end;
4958 const gchar *out_codeset;
4959 EncodingType encoding;
4960 MimeInfo *mimemsg, *mimetext;
4963 if (action == COMPOSE_WRITE_FOR_SEND)
4964 attach_parts = TRUE;
4966 /* create message MimeInfo */
4967 mimemsg = procmime_mimeinfo_new();
4968 mimemsg->type = MIMETYPE_MESSAGE;
4969 mimemsg->subtype = g_strdup("rfc822");
4970 mimemsg->content = MIMECONTENT_MEM;
4971 mimemsg->tmp = TRUE; /* must free content later */
4972 mimemsg->data.mem = compose_get_header(compose);
4974 /* Create text part MimeInfo */
4975 /* get all composed text */
4976 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
4977 gtk_text_buffer_get_start_iter(buffer, &start);
4978 gtk_text_buffer_get_end_iter(buffer, &end);
4979 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
4980 if (is_ascii_str(chars)) {
4983 out_codeset = CS_US_ASCII;
4984 encoding = ENC_7BIT;
4986 const gchar *src_codeset = CS_INTERNAL;
4988 out_codeset = conv_get_charset_str(compose->out_encoding);
4991 gchar *test_conv_global_out = NULL;
4992 gchar *test_conv_reply = NULL;
4994 /* automatic mode. be automatic. */
4995 codeconv_set_strict(TRUE);
4997 out_codeset = conv_get_outgoing_charset_str();
4999 debug_print("trying to convert to %s\n", out_codeset);
5000 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5003 if (!test_conv_global_out && compose->orig_charset
5004 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5005 out_codeset = compose->orig_charset;
5006 debug_print("failure; trying to convert to %s\n", out_codeset);
5007 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5010 if (!test_conv_global_out && !test_conv_reply) {
5012 out_codeset = CS_INTERNAL;
5013 debug_print("failure; finally using %s\n", out_codeset);
5015 g_free(test_conv_global_out);
5016 g_free(test_conv_reply);
5017 codeconv_set_strict(FALSE);
5020 if (!g_ascii_strcasecmp(out_codeset, CS_US_ASCII))
5021 out_codeset = CS_ISO_8859_1;
5023 if (prefs_common.encoding_method == CTE_BASE64)
5024 encoding = ENC_BASE64;
5025 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5026 encoding = ENC_QUOTED_PRINTABLE;
5027 else if (prefs_common.encoding_method == CTE_8BIT)
5028 encoding = ENC_8BIT;
5030 encoding = procmime_get_encoding_for_charset(out_codeset);
5032 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5033 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5035 if (action == COMPOSE_WRITE_FOR_SEND) {
5036 codeconv_set_strict(TRUE);
5037 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5038 codeconv_set_strict(FALSE);
5044 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5045 "to the specified %s charset.\n"
5046 "Send it as %s?"), out_codeset, src_codeset);
5047 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL, _("+_Send"), NULL, FALSE,
5048 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5051 if (aval != G_ALERTALTERNATE) {
5056 out_codeset = src_codeset;
5062 out_codeset = src_codeset;
5068 if (encoding == ENC_8BIT || encoding == ENC_7BIT) {
5069 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5070 strstr(buf, "\nFrom ") != NULL) {
5071 encoding = ENC_QUOTED_PRINTABLE;
5075 mimetext = procmime_mimeinfo_new();
5076 mimetext->content = MIMECONTENT_MEM;
5077 mimetext->tmp = TRUE; /* must free content later */
5078 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5079 * and free the data, which we need later. */
5080 mimetext->data.mem = g_strdup(buf);
5081 mimetext->type = MIMETYPE_TEXT;
5082 mimetext->subtype = g_strdup("plain");
5083 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5084 g_strdup(out_codeset));
5086 /* protect trailing spaces when signing message */
5087 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5088 privacy_system_can_sign(compose->privacy_system)) {
5089 encoding = ENC_QUOTED_PRINTABLE;
5092 debug_print("main text: %zd bytes encoded as %s in %d\n",
5093 strlen(buf), out_codeset, encoding);
5095 /* check for line length limit */
5096 if (action == COMPOSE_WRITE_FOR_SEND &&
5097 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5098 check_line_length(buf, 1000, &line) < 0) {
5102 msg = g_strdup_printf
5103 (_("Line %d exceeds the line length limit (998 bytes).\n"
5104 "The contents of the message might be broken on the way to the delivery.\n"
5106 "Send it anyway?"), line + 1);
5107 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5109 if (aval != G_ALERTALTERNATE) {
5115 if (encoding != ENC_UNKNOWN)
5116 procmime_encode_content(mimetext, encoding);
5118 /* append attachment parts */
5119 if (compose_use_attach(compose) && attach_parts) {
5120 MimeInfo *mimempart;
5121 gchar *boundary = NULL;
5122 mimempart = procmime_mimeinfo_new();
5123 mimempart->content = MIMECONTENT_EMPTY;
5124 mimempart->type = MIMETYPE_MULTIPART;
5125 mimempart->subtype = g_strdup("mixed");
5129 boundary = generate_mime_boundary(NULL);
5130 } while (strstr(buf, boundary) != NULL);
5132 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5135 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5137 g_node_append(mimempart->node, mimetext->node);
5138 g_node_append(mimemsg->node, mimempart->node);
5140 compose_add_attachments(compose, mimempart);
5142 g_node_append(mimemsg->node, mimetext->node);
5146 /* sign message if sending */
5147 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5148 privacy_system_can_sign(compose->privacy_system))
5149 if (!privacy_sign(compose->privacy_system, mimemsg, compose->account))
5152 procmime_write_mimeinfo(mimemsg, fp);
5154 procmime_mimeinfo_free_all(mimemsg);
5159 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5161 GtkTextBuffer *buffer;
5162 GtkTextIter start, end;
5167 if ((fp = g_fopen(file, "wb")) == NULL) {
5168 FILE_OP_ERROR(file, "fopen");
5172 /* chmod for security */
5173 if (change_file_mode_rw(fp, file) < 0) {
5174 FILE_OP_ERROR(file, "chmod");
5175 g_warning("can't change file mode\n");
5178 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5179 gtk_text_buffer_get_start_iter(buffer, &start);
5180 gtk_text_buffer_get_end_iter(buffer, &end);
5181 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5183 chars = conv_codeset_strdup
5184 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5187 if (!chars) return -1;
5190 len = strlen(chars);
5191 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5192 FILE_OP_ERROR(file, "fwrite");
5201 if (fclose(fp) == EOF) {
5202 FILE_OP_ERROR(file, "fclose");
5209 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5212 MsgInfo *msginfo = compose->targetinfo;
5214 g_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5215 if (!msginfo) return -1;
5217 if (!force && MSG_IS_LOCKED(msginfo->flags))
5220 item = msginfo->folder;
5221 g_return_val_if_fail(item != NULL, -1);
5223 if (procmsg_msg_exist(msginfo) &&
5224 (folder_has_parent_of_type(item, F_QUEUE) ||
5225 folder_has_parent_of_type(item, F_DRAFT)
5226 || msginfo == compose->autosaved_draft)) {
5227 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5228 g_warning("can't remove the old message\n");
5231 debug_print("removed reedit target %d\n", msginfo->msgnum);
5238 static void compose_remove_draft(Compose *compose)
5241 MsgInfo *msginfo = compose->targetinfo;
5242 drafts = account_get_special_folder(compose->account, F_DRAFT);
5244 if (procmsg_msg_exist(msginfo)) {
5245 folder_item_remove_msg(drafts, msginfo->msgnum);
5250 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5251 gboolean remove_reedit_target)
5253 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5256 static gboolean compose_warn_encryption(Compose *compose)
5258 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5259 AlertValue val = G_ALERTALTERNATE;
5261 if (warning == NULL)
5264 val = alertpanel_full(_("Encryption warning"), warning,
5265 GTK_STOCK_CANCEL, _("+C_ontinue"), NULL,
5266 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5267 if (val & G_ALERTDISABLE) {
5268 val &= ~G_ALERTDISABLE;
5269 if (val == G_ALERTALTERNATE)
5270 privacy_inhibit_encrypt_warning(compose->privacy_system,
5274 if (val == G_ALERTALTERNATE) {
5281 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
5282 gchar **msgpath, gboolean check_subject,
5283 gboolean remove_reedit_target)
5290 static gboolean lock = FALSE;
5291 PrefsAccount *mailac = NULL, *newsac = NULL;
5292 gboolean err = FALSE;
5294 debug_print("queueing message...\n");
5295 g_return_val_if_fail(compose->account != NULL, -1);
5299 if (compose_check_entries(compose, check_subject) == FALSE) {
5301 if (compose->batch) {
5302 gtk_widget_show_all(compose->window);
5307 if (!compose->to_list && !compose->newsgroup_list) {
5308 g_warning("can't get recipient list.");
5313 if (compose->to_list) {
5314 if (compose->account->protocol != A_NNTP)
5315 mailac = compose->account;
5316 else if (cur_account && cur_account->protocol != A_NNTP)
5317 mailac = cur_account;
5318 else if (!(mailac = compose_current_mail_account())) {
5320 alertpanel_error(_("No account for sending mails available!"));
5325 if (compose->newsgroup_list) {
5326 if (compose->account->protocol == A_NNTP)
5327 newsac = compose->account;
5328 else if (!newsac->protocol != A_NNTP) {
5330 alertpanel_error(_("No account for posting news available!"));
5335 /* write queue header */
5336 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
5337 G_DIR_SEPARATOR, compose, (guint) rand());
5338 debug_print("queuing to %s\n", tmp);
5339 if ((fp = g_fopen(tmp, "wb")) == NULL) {
5340 FILE_OP_ERROR(tmp, "fopen");
5346 if (change_file_mode_rw(fp, tmp) < 0) {
5347 FILE_OP_ERROR(tmp, "chmod");
5348 g_warning("can't change file mode\n");
5351 /* queueing variables */
5352 err |= (fprintf(fp, "AF:\n") < 0);
5353 err |= (fprintf(fp, "NF:0\n") < 0);
5354 err |= (fprintf(fp, "PS:10\n") < 0);
5355 err |= (fprintf(fp, "SRH:1\n") < 0);
5356 err |= (fprintf(fp, "SFN:\n") < 0);
5357 err |= (fprintf(fp, "DSR:\n") < 0);
5359 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
5361 err |= (fprintf(fp, "MID:\n") < 0);
5362 err |= (fprintf(fp, "CFG:\n") < 0);
5363 err |= (fprintf(fp, "PT:0\n") < 0);
5364 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
5365 err |= (fprintf(fp, "RQ:\n") < 0);
5367 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
5369 err |= (fprintf(fp, "SSV:\n") < 0);
5371 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
5373 err |= (fprintf(fp, "NSV:\n") < 0);
5374 err |= (fprintf(fp, "SSH:\n") < 0);
5375 /* write recepient list */
5376 if (compose->to_list) {
5377 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
5378 for (cur = compose->to_list->next; cur != NULL;
5380 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
5381 err |= (fprintf(fp, "\n") < 0);
5383 /* write newsgroup list */
5384 if (compose->newsgroup_list) {
5385 err |= (fprintf(fp, "NG:") < 0);
5386 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
5387 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
5388 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
5389 err |= (fprintf(fp, "\n") < 0);
5391 /* Sylpheed account IDs */
5393 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
5395 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
5398 if (compose->privacy_system != NULL) {
5399 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
5400 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
5401 if (compose->use_encryption) {
5403 if (!compose_warn_encryption(compose)) {
5410 if (mailac && mailac->encrypt_to_self) {
5411 GSList *tmp_list = g_slist_copy(compose->to_list);
5412 tmp_list = g_slist_append(tmp_list, compose->account->address);
5413 encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
5414 g_slist_free(tmp_list);
5416 encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
5418 if (encdata != NULL) {
5419 if (strcmp(encdata, "_DONT_ENCRYPT_")) {
5420 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5421 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
5423 } /* else we finally dont want to encrypt */
5425 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5426 /* and if encdata was null, it means there's been a problem in
5438 /* Save copy folder */
5439 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
5440 gchar *savefolderid;
5442 savefolderid = gtk_editable_get_chars(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
5443 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
5444 g_free(savefolderid);
5446 /* Save copy folder */
5447 if (compose->return_receipt) {
5448 err |= (fprintf(fp, "RRCPT:1\n") < 0);
5450 /* Message-ID of message replying to */
5451 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
5454 folderid = folder_item_get_identifier(compose->replyinfo->folder);
5455 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
5458 /* Message-ID of message forwarding to */
5459 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
5462 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
5463 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
5467 /* end of headers */
5468 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
5470 if (compose->redirect_filename != NULL) {
5471 if (compose_redirect_write_to_file(compose, fp) < 0) {
5480 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
5485 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
5489 g_warning("failed to write queue message\n");
5496 if (fclose(fp) == EOF) {
5497 FILE_OP_ERROR(tmp, "fclose");
5504 if (item && *item) {
5507 queue = account_get_special_folder(compose->account, F_QUEUE);
5510 g_warning("can't find queue folder\n");
5516 folder_item_scan(queue);
5517 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
5518 g_warning("can't queue the message\n");
5525 if (msgpath == NULL) {
5531 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
5532 compose_remove_reedit_target(compose, FALSE);
5535 if ((msgnum != NULL) && (item != NULL)) {
5543 static void compose_add_attachments(Compose *compose, MimeInfo *parent)
5546 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
5548 struct stat statbuf;
5549 gchar *type, *subtype;
5550 GtkTreeModel *model;
5553 model = gtk_tree_view_get_model(tree_view);
5555 if (!gtk_tree_model_get_iter_first(model, &iter))
5558 gtk_tree_model_get(model, &iter,
5562 mimepart = procmime_mimeinfo_new();
5563 mimepart->content = MIMECONTENT_FILE;
5564 mimepart->data.filename = g_strdup(ainfo->file);
5565 mimepart->tmp = FALSE; /* or we destroy our attachment */
5566 mimepart->offset = 0;
5568 stat(ainfo->file, &statbuf);
5569 mimepart->length = statbuf.st_size;
5571 type = g_strdup(ainfo->content_type);
5573 if (!strchr(type, '/')) {
5575 type = g_strdup("application/octet-stream");
5578 subtype = strchr(type, '/') + 1;
5579 *(subtype - 1) = '\0';
5580 mimepart->type = procmime_get_media_type(type);
5581 mimepart->subtype = g_strdup(subtype);
5584 if (mimepart->type == MIMETYPE_MESSAGE &&
5585 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
5586 mimepart->disposition = DISPOSITIONTYPE_INLINE;
5589 g_hash_table_insert(mimepart->typeparameters,
5590 g_strdup("name"), g_strdup(ainfo->name));
5591 g_hash_table_insert(mimepart->dispositionparameters,
5592 g_strdup("filename"), g_strdup(ainfo->name));
5593 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
5597 if (compose->use_signing) {
5598 if (ainfo->encoding == ENC_7BIT)
5599 ainfo->encoding = ENC_QUOTED_PRINTABLE;
5600 else if (ainfo->encoding == ENC_8BIT)
5601 ainfo->encoding = ENC_BASE64;
5604 procmime_encode_content(mimepart, ainfo->encoding);
5606 g_node_append(parent->node, mimepart->node);
5607 } while (gtk_tree_model_iter_next(model, &iter));
5610 #define IS_IN_CUSTOM_HEADER(header) \
5611 (compose->account->add_customhdr && \
5612 custom_header_find(compose->account->customhdr_list, header) != NULL)
5614 static void compose_add_headerfield_from_headerlist(Compose *compose,
5616 const gchar *fieldname,
5617 const gchar *seperator)
5619 gchar *str, *fieldname_w_colon;
5620 gboolean add_field = FALSE;
5622 ComposeHeaderEntry *headerentry;
5623 const gchar *headerentryname;
5624 const gchar *trans_fieldname;
5627 if (IS_IN_CUSTOM_HEADER(fieldname))
5630 debug_print("Adding %s-fields\n", fieldname);
5632 fieldstr = g_string_sized_new(64);
5634 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
5635 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
5637 for (list = compose->header_list; list; list = list->next) {
5638 headerentry = ((ComposeHeaderEntry *)list->data);
5639 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_BIN(headerentry->combo)->child));
5641 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
5642 str = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
5644 if (str[0] != '\0') {
5646 g_string_append(fieldstr, seperator);
5647 g_string_append(fieldstr, str);
5656 buf = g_new0(gchar, fieldstr->len * 4 + 256);
5657 compose_convert_header
5658 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
5659 strlen(fieldname) + 2, TRUE);
5660 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
5664 g_free(fieldname_w_colon);
5665 g_string_free(fieldstr, TRUE);
5670 static gchar *compose_get_header(Compose *compose)
5672 gchar buf[BUFFSIZE];
5673 const gchar *entry_str;
5677 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
5679 gchar *from_name = NULL, *from_address = NULL;
5682 g_return_val_if_fail(compose->account != NULL, NULL);
5683 g_return_val_if_fail(compose->account->address != NULL, NULL);
5685 header = g_string_sized_new(64);
5688 get_rfc822_date(buf, sizeof(buf));
5689 g_string_append_printf(header, "Date: %s\n", buf);
5693 if (compose->account->name && *compose->account->name) {
5695 QUOTE_IF_REQUIRED(buf, compose->account->name);
5696 tmp = g_strdup_printf("%s <%s>",
5697 buf, compose->account->address);
5699 tmp = g_strdup_printf("%s",
5700 compose->account->address);
5702 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
5703 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
5705 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
5706 from_address = g_strdup(compose->account->address);
5708 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5709 /* extract name and address */
5710 if (strstr(spec, " <") && strstr(spec, ">")) {
5711 from_address = g_strdup(strrchr(spec, '<')+1);
5712 *(strrchr(from_address, '>')) = '\0';
5713 from_name = g_strdup(spec);
5714 *(strrchr(from_name, '<')) = '\0';
5717 from_address = g_strdup(spec);
5724 if (from_name && *from_name) {
5725 compose_convert_header
5726 (compose, buf, sizeof(buf), from_name,
5727 strlen("From: "), TRUE);
5728 QUOTE_IF_REQUIRED(name, buf);
5730 g_string_append_printf(header, "From: %s <%s>\n",
5731 name, from_address);
5733 g_string_append_printf(header, "From: %s\n", from_address);
5736 g_free(from_address);
5739 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
5742 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
5745 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
5748 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
5751 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
5753 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
5756 compose_convert_header(compose, buf, sizeof(buf), str,
5757 strlen("Subject: "), FALSE);
5758 g_string_append_printf(header, "Subject: %s\n", buf);
5764 if (compose->account->set_domain && compose->account->domain) {
5765 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
5766 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
5767 g_snprintf(buf, sizeof(buf), "%s",
5768 strchr(compose->account->address, '@') ?
5769 strchr(compose->account->address, '@')+1 :
5770 compose->account->address);
5772 g_snprintf(buf, sizeof(buf), "%s", "");
5775 if (compose->account->gen_msgid) {
5776 generate_msgid(buf, sizeof(buf));
5777 g_string_append_printf(header, "Message-ID: <%s>\n", buf);
5778 compose->msgid = g_strdup(buf);
5780 compose->msgid = NULL;
5783 if (compose->remove_references == FALSE) {
5785 if (compose->inreplyto && compose->to_list)
5786 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
5789 if (compose->references)
5790 g_string_append_printf(header, "References: %s\n", compose->references);
5794 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
5797 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
5800 if (compose->account->organization &&
5801 strlen(compose->account->organization) &&
5802 !IS_IN_CUSTOM_HEADER("Organization")) {
5803 compose_convert_header(compose, buf, sizeof(buf),
5804 compose->account->organization,
5805 strlen("Organization: "), FALSE);
5806 g_string_append_printf(header, "Organization: %s\n", buf);
5809 /* Program version and system info */
5810 if (g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
5811 !compose->newsgroup_list) {
5812 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
5814 gtk_major_version, gtk_minor_version, gtk_micro_version,
5817 if (g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
5818 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
5820 gtk_major_version, gtk_minor_version, gtk_micro_version,
5824 /* custom headers */
5825 if (compose->account->add_customhdr) {
5828 for (cur = compose->account->customhdr_list; cur != NULL;
5830 CustomHeader *chdr = (CustomHeader *)cur->data;
5832 if (custom_header_is_allowed(chdr->name)) {
5833 compose_convert_header
5834 (compose, buf, sizeof(buf),
5835 chdr->value ? chdr->value : "",
5836 strlen(chdr->name) + 2, FALSE);
5837 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
5843 switch (compose->priority) {
5844 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
5845 "X-Priority: 1 (Highest)\n");
5847 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
5848 "X-Priority: 2 (High)\n");
5850 case PRIORITY_NORMAL: break;
5851 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
5852 "X-Priority: 4 (Low)\n");
5854 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
5855 "X-Priority: 5 (Lowest)\n");
5857 default: debug_print("compose: priority unknown : %d\n",
5861 /* Request Return Receipt */
5862 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To")) {
5863 if (compose->return_receipt) {
5864 if (compose->account->name
5865 && *compose->account->name) {
5866 compose_convert_header(compose, buf, sizeof(buf),
5867 compose->account->name,
5868 strlen("Disposition-Notification-To: "),
5870 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, compose->account->address);
5872 g_string_append_printf(header, "Disposition-Notification-To: %s\n", compose->account->address);
5876 /* get special headers */
5877 for (list = compose->header_list; list; list = list->next) {
5878 ComposeHeaderEntry *headerentry;
5881 gchar *headername_wcolon;
5882 const gchar *headername_trans;
5885 gboolean standard_header = FALSE;
5887 headerentry = ((ComposeHeaderEntry *)list->data);
5889 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(GTK_BIN(headerentry->combo)->child)));
5890 if (strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
5895 if (!strstr(tmp, ":")) {
5896 headername_wcolon = g_strconcat(tmp, ":", NULL);
5897 headername = g_strdup(tmp);
5899 headername_wcolon = g_strdup(tmp);
5900 headername = g_strdup(strtok(tmp, ":"));
5904 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5905 Xstrdup_a(headervalue, entry_str, return NULL);
5906 subst_char(headervalue, '\r', ' ');
5907 subst_char(headervalue, '\n', ' ');
5908 string = std_headers;
5909 while (*string != NULL) {
5910 headername_trans = prefs_common_translated_header_name(*string);
5911 if (!strcmp(headername_trans, headername_wcolon))
5912 standard_header = TRUE;
5915 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
5916 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
5919 g_free(headername_wcolon);
5923 g_string_free(header, FALSE);
5928 #undef IS_IN_CUSTOM_HEADER
5930 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
5931 gint header_len, gboolean addr_field)
5933 gchar *tmpstr = NULL;
5934 const gchar *out_codeset = NULL;
5936 g_return_if_fail(src != NULL);
5937 g_return_if_fail(dest != NULL);
5939 if (len < 1) return;
5941 tmpstr = g_strdup(src);
5943 subst_char(tmpstr, '\n', ' ');
5944 subst_char(tmpstr, '\r', ' ');
5947 if (!g_utf8_validate(tmpstr, -1, NULL)) {
5948 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
5949 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
5954 codeconv_set_strict(TRUE);
5955 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
5956 conv_get_charset_str(compose->out_encoding));
5957 codeconv_set_strict(FALSE);
5959 if (!dest || *dest == '\0') {
5960 gchar *test_conv_global_out = NULL;
5961 gchar *test_conv_reply = NULL;
5963 /* automatic mode. be automatic. */
5964 codeconv_set_strict(TRUE);
5966 out_codeset = conv_get_outgoing_charset_str();
5968 debug_print("trying to convert to %s\n", out_codeset);
5969 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
5972 if (!test_conv_global_out && compose->orig_charset
5973 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5974 out_codeset = compose->orig_charset;
5975 debug_print("failure; trying to convert to %s\n", out_codeset);
5976 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
5979 if (!test_conv_global_out && !test_conv_reply) {
5981 out_codeset = CS_INTERNAL;
5982 debug_print("finally using %s\n", out_codeset);
5984 g_free(test_conv_global_out);
5985 g_free(test_conv_reply);
5986 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
5988 codeconv_set_strict(FALSE);
5993 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
5997 g_return_if_fail(user_data != NULL);
5999 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6000 g_strstrip(address);
6001 if (*address != '\0') {
6002 gchar *name = procheader_get_fromname(address);
6003 extract_address(address);
6004 addressbook_add_contact(name, address, NULL, NULL);
6009 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
6011 GtkWidget *menuitem;
6014 g_return_if_fail(menu != NULL);
6015 g_return_if_fail(GTK_IS_MENU_SHELL(menu));
6017 menuitem = gtk_separator_menu_item_new();
6018 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6019 gtk_widget_show(menuitem);
6021 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
6022 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6024 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
6025 g_strstrip(address);
6026 if (*address == '\0') {
6027 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
6030 g_signal_connect(G_OBJECT(menuitem), "activate",
6031 G_CALLBACK(compose_add_to_addressbook_cb), entry);
6032 gtk_widget_show(menuitem);
6035 static void compose_create_header_entry(Compose *compose)
6037 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6042 const gchar *header = NULL;
6043 ComposeHeaderEntry *headerentry;
6044 gboolean standard_header = FALSE;
6046 headerentry = g_new0(ComposeHeaderEntry, 1);
6049 combo = gtk_combo_box_entry_new_text();
6051 while(*string != NULL) {
6052 gtk_combo_box_append_text(GTK_COMBO_BOX(combo),
6053 (gchar*)prefs_common_translated_header_name(*string));
6056 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
6057 g_signal_connect(G_OBJECT(GTK_BIN(combo)->child), "grab_focus",
6058 G_CALLBACK(compose_grab_focus_cb), compose);
6059 gtk_widget_show(combo);
6060 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
6061 compose->header_nextrow, compose->header_nextrow+1,
6062 GTK_SHRINK, GTK_FILL, 0, 0);
6063 if (compose->header_last) {
6064 const gchar *last_header_entry = gtk_entry_get_text(
6065 GTK_ENTRY(GTK_BIN(compose->header_last->combo)->child));
6067 while (*string != NULL) {
6068 if (!strcmp(*string, last_header_entry))
6069 standard_header = TRUE;
6072 if (standard_header)
6073 header = gtk_entry_get_text(GTK_ENTRY(GTK_BIN(compose->header_last->combo)->child));
6075 if (!compose->header_last || !standard_header) {
6076 switch(compose->account->protocol) {
6078 header = prefs_common_translated_header_name("Newsgroups:");
6081 header = prefs_common_translated_header_name("To:");
6086 gtk_entry_set_text(GTK_ENTRY(GTK_BIN(combo)->child), header);
6088 g_signal_connect_after(G_OBJECT(GTK_BIN(combo)->child), "grab_focus",
6089 G_CALLBACK(compose_grab_focus_cb), compose);
6092 entry = gtk_entry_new();
6093 gtk_widget_show(entry);
6094 gtk_tooltips_set_tip(compose->tooltips, entry,
6095 _("Use <tab> to autocomplete from addressbook"), NULL);
6096 gtk_table_attach(GTK_TABLE(compose->header_table), entry, 1, 2,
6097 compose->header_nextrow, compose->header_nextrow+1,
6098 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
6100 g_signal_connect(G_OBJECT(entry), "key-press-event",
6101 G_CALLBACK(compose_headerentry_key_press_event_cb),
6103 g_signal_connect(G_OBJECT(entry), "changed",
6104 G_CALLBACK(compose_headerentry_changed_cb),
6106 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
6107 G_CALLBACK(compose_grab_focus_cb), compose);
6110 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6111 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6112 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6113 g_signal_connect(G_OBJECT(entry), "drag_data_received",
6114 G_CALLBACK(compose_header_drag_received_cb),
6116 g_signal_connect(G_OBJECT(entry), "drag-drop",
6117 G_CALLBACK(compose_drag_drop),
6119 g_signal_connect(G_OBJECT(entry), "populate-popup",
6120 G_CALLBACK(compose_entry_popup_extend),
6123 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
6125 headerentry->compose = compose;
6126 headerentry->combo = combo;
6127 headerentry->entry = entry;
6128 headerentry->headernum = compose->header_nextrow;
6130 compose->header_nextrow++;
6131 compose->header_last = headerentry;
6132 compose->header_list =
6133 g_slist_append(compose->header_list,
6137 static void compose_add_header_entry(Compose *compose, const gchar *header, gchar *text)
6139 ComposeHeaderEntry *last_header;
6141 last_header = compose->header_last;
6143 gtk_entry_set_text(GTK_ENTRY(GTK_BIN(last_header->combo)->child), header);
6144 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
6147 static void compose_remove_header_entries(Compose *compose)
6150 for (list = compose->header_list; list; list = list->next) {
6151 ComposeHeaderEntry *headerentry =
6152 (ComposeHeaderEntry *)list->data;
6153 gtk_widget_destroy(headerentry->combo);
6154 gtk_widget_destroy(headerentry->entry);
6155 g_free(headerentry);
6157 compose->header_last = NULL;
6158 g_slist_free(compose->header_list);
6159 compose->header_list = NULL;
6160 compose->header_nextrow = 1;
6161 compose_create_header_entry(compose);
6164 static GtkWidget *compose_create_header(Compose *compose)
6166 GtkWidget *from_optmenu_hbox;
6167 GtkWidget *header_scrolledwin;
6168 GtkWidget *header_table;
6172 /* header labels and entries */
6173 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
6174 gtk_widget_show(header_scrolledwin);
6175 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
6177 header_table = gtk_table_new(2, 2, FALSE);
6178 gtk_widget_show(header_table);
6179 gtk_container_set_border_width(GTK_CONTAINER(header_table), BORDER_WIDTH);
6180 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
6181 gtk_viewport_set_shadow_type(GTK_VIEWPORT(GTK_BIN(header_scrolledwin)->child), GTK_SHADOW_NONE);
6184 /* option menu for selecting accounts */
6185 from_optmenu_hbox = compose_account_option_menu_create(compose);
6186 gtk_table_attach(GTK_TABLE(header_table), from_optmenu_hbox,
6187 0, 2, count, count + 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
6190 compose->header_table = header_table;
6191 compose->header_list = NULL;
6192 compose->header_nextrow = count;
6194 compose_create_header_entry(compose);
6196 compose->table = NULL;
6198 return header_scrolledwin ;
6201 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
6203 Compose *compose = (Compose *)data;
6204 GdkEventButton event;
6207 event.time = gtk_get_current_event_time();
6209 return attach_button_pressed(compose->attach_clist, &event, compose);
6212 static GtkWidget *compose_create_attach(Compose *compose)
6214 GtkWidget *attach_scrwin;
6215 GtkWidget *attach_clist;
6217 GtkListStore *store;
6218 GtkCellRenderer *renderer;
6219 GtkTreeViewColumn *column;
6220 GtkTreeSelection *selection;
6222 /* attachment list */
6223 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
6224 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
6225 GTK_POLICY_AUTOMATIC,
6226 GTK_POLICY_AUTOMATIC);
6227 gtk_widget_set_size_request(attach_scrwin, -1, 80);
6229 store = gtk_list_store_new(N_ATTACH_COLS,
6234 G_TYPE_AUTO_POINTER,
6236 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
6237 (GTK_TREE_MODEL(store)));
6238 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
6239 g_object_unref(store);
6241 renderer = gtk_cell_renderer_text_new();
6242 column = gtk_tree_view_column_new_with_attributes
6243 (_("Mime type"), renderer, "text",
6244 COL_MIMETYPE, NULL);
6245 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6247 renderer = gtk_cell_renderer_text_new();
6248 column = gtk_tree_view_column_new_with_attributes
6249 (_("Size"), renderer, "text",
6251 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6253 renderer = gtk_cell_renderer_text_new();
6254 column = gtk_tree_view_column_new_with_attributes
6255 (_("Name"), renderer, "text",
6257 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6259 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
6260 prefs_common.use_stripes_everywhere);
6261 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
6262 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
6264 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
6265 G_CALLBACK(attach_selected), compose);
6266 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
6267 G_CALLBACK(attach_button_pressed), compose);
6269 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
6270 G_CALLBACK(popup_attach_button_pressed), compose);
6272 gtk_widget_tap_and_hold_setup(GTK_WIDGET(attach_clist), NULL, NULL,
6273 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
6274 g_signal_connect(G_OBJECT(attach_clist), "tap-and-hold",
6275 G_CALLBACK(popup_attach_button_pressed), compose);
6277 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
6278 G_CALLBACK(attach_key_pressed), compose);
6281 gtk_drag_dest_set(attach_clist,
6282 GTK_DEST_DEFAULT_ALL, compose_mime_types,
6283 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6284 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6285 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
6286 G_CALLBACK(compose_attach_drag_received_cb),
6288 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
6289 G_CALLBACK(compose_drag_drop),
6292 compose->attach_scrwin = attach_scrwin;
6293 compose->attach_clist = attach_clist;
6295 return attach_scrwin;
6298 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
6299 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
6301 static GtkWidget *compose_create_others(Compose *compose)
6304 GtkWidget *savemsg_checkbtn;
6305 GtkWidget *savemsg_entry;
6306 GtkWidget *savemsg_select;
6309 gchar *folderidentifier;
6311 /* Table for settings */
6312 table = gtk_table_new(3, 1, FALSE);
6313 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
6314 gtk_widget_show(table);
6315 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
6318 /* Save Message to folder */
6319 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
6320 gtk_widget_show(savemsg_checkbtn);
6321 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6322 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6323 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
6325 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
6326 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
6328 savemsg_entry = gtk_entry_new();
6329 gtk_widget_show(savemsg_entry);
6330 gtk_table_attach_defaults(GTK_TABLE(table), savemsg_entry, 1, 2, rowcount, rowcount + 1);
6331 gtk_editable_set_editable(GTK_EDITABLE(savemsg_entry), prefs_common.savemsg);
6332 g_signal_connect_after(G_OBJECT(savemsg_entry), "grab_focus",
6333 G_CALLBACK(compose_grab_focus_cb), compose);
6334 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6335 folderidentifier = folder_item_get_identifier(account_get_special_folder
6336 (compose->account, F_OUTBOX));
6337 gtk_entry_set_text(GTK_ENTRY(savemsg_entry), folderidentifier);
6338 g_free(folderidentifier);
6341 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
6342 gtk_widget_show(savemsg_select);
6343 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6344 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
6345 G_CALLBACK(compose_savemsg_select_cb),
6350 compose->savemsg_checkbtn = savemsg_checkbtn;
6351 compose->savemsg_entry = savemsg_entry;
6356 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
6358 gtk_editable_set_editable(GTK_EDITABLE(compose->savemsg_entry),
6359 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
6362 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
6367 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL);
6370 path = folder_item_get_identifier(dest);
6372 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), path);
6376 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
6377 GdkAtom clip, GtkTextIter *insert_place);
6380 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
6384 GtkTextBuffer *buffer;
6386 if (event->button == 3) {
6388 GtkTextIter sel_start, sel_end;
6389 gboolean stuff_selected;
6391 /* move the cursor to allow GtkAspell to check the word
6392 * under the mouse */
6393 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6394 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6396 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6399 stuff_selected = gtk_text_buffer_get_selection_bounds(
6400 GTK_TEXT_VIEW(text)->buffer,
6401 &sel_start, &sel_end);
6403 gtk_text_buffer_place_cursor (GTK_TEXT_VIEW(text)->buffer, &iter);
6404 /* reselect stuff */
6406 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
6407 gtk_text_buffer_select_range(GTK_TEXT_VIEW(text)->buffer,
6408 &sel_start, &sel_end);
6410 return FALSE; /* pass the event so that the right-click goes through */
6413 if (event->button == 2) {
6418 /* get the middle-click position to paste at the correct place */
6419 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6420 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6422 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6425 entry_paste_clipboard(compose, text,
6426 prefs_common.linewrap_pastes,
6427 GDK_SELECTION_PRIMARY, &iter);
6435 static void compose_spell_menu_changed(void *data)
6437 Compose *compose = (Compose *)data;
6439 GtkWidget *menuitem;
6440 GtkWidget *parent_item;
6441 GtkMenu *menu = GTK_MENU(gtk_menu_new());
6442 GtkItemFactory *ifactory = gtk_item_factory_from_widget(compose->menubar);
6445 if (compose->gtkaspell == NULL)
6448 parent_item = gtk_item_factory_get_item(ifactory,
6449 "/Spelling/Options");
6451 /* setting the submenu removes /Spelling/Options from the factory
6452 * so we need to save it */
6454 if (parent_item == NULL) {
6455 parent_item = compose->aspell_options_menu;
6456 gtk_menu_item_remove_submenu(GTK_MENU_ITEM(parent_item));
6458 compose->aspell_options_menu = parent_item;
6460 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
6462 spell_menu = g_slist_reverse(spell_menu);
6463 for (items = spell_menu;
6464 items; items = items->next) {
6465 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
6466 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
6467 gtk_widget_show(GTK_WIDGET(menuitem));
6469 g_slist_free(spell_menu);
6471 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
6476 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
6478 Compose *compose = (Compose *)data;
6479 GdkEventButton event;
6482 event.time = gtk_get_current_event_time();
6484 return text_clicked(compose->text, &event, compose);
6487 static gboolean compose_force_window_origin = TRUE;
6488 static Compose *compose_create(PrefsAccount *account,
6497 GtkWidget *handlebox;
6499 GtkWidget *notebook;
6501 GtkWidget *attach_hbox;
6502 GtkWidget *attach_lab1;
6503 GtkWidget *attach_lab2;
6508 GtkWidget *subject_hbox;
6509 GtkWidget *subject_frame;
6510 GtkWidget *subject_entry;
6514 GtkWidget *edit_vbox;
6515 GtkWidget *ruler_hbox;
6517 GtkWidget *scrolledwin;
6519 GtkTextBuffer *buffer;
6520 GtkClipboard *clipboard;
6522 UndoMain *undostruct;
6524 gchar *titles[N_ATTACH_COLS];
6525 guint n_menu_entries;
6526 GtkWidget *popupmenu;
6527 GtkItemFactory *popupfactory;
6528 GtkItemFactory *ifactory;
6529 GtkWidget *tmpl_menu;
6531 GtkWidget *menuitem;
6534 GtkAspell * gtkaspell = NULL;
6537 static GdkGeometry geometry;
6539 g_return_val_if_fail(account != NULL, NULL);
6541 debug_print("Creating compose window...\n");
6542 compose = g_new0(Compose, 1);
6544 titles[COL_MIMETYPE] = _("MIME type");
6545 titles[COL_SIZE] = _("Size");
6546 titles[COL_NAME] = _("Name");
6548 compose->batch = batch;
6549 compose->account = account;
6550 compose->folder = folder;
6552 compose->mutex = g_mutex_new();
6553 compose->set_cursor_pos = -1;
6555 compose->tooltips = gtk_tooltips_new();
6557 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
6559 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
6560 gtk_widget_set_size_request(window, -1, prefs_common.compose_height);
6562 if (!geometry.max_width) {
6563 geometry.max_width = gdk_screen_width();
6564 geometry.max_height = gdk_screen_height();
6567 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
6568 &geometry, GDK_HINT_MAX_SIZE);
6569 if (!geometry.min_width) {
6570 geometry.min_width = 600;
6571 geometry.min_height = 480;
6573 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
6574 &geometry, GDK_HINT_MIN_SIZE);
6577 if (compose_force_window_origin)
6578 gtk_widget_set_uposition(window, prefs_common.compose_x,
6579 prefs_common.compose_y);
6581 g_signal_connect(G_OBJECT(window), "delete_event",
6582 G_CALLBACK(compose_delete_cb), compose);
6583 MANAGE_WINDOW_SIGNALS_CONNECT(window);
6584 gtk_widget_realize(window);
6586 gtkut_widget_set_composer_icon(window);
6588 vbox = gtk_vbox_new(FALSE, 0);
6589 gtk_container_add(GTK_CONTAINER(window), vbox);
6591 n_menu_entries = sizeof(compose_entries) / sizeof(compose_entries[0]);
6592 menubar = menubar_create(window, compose_entries,
6593 n_menu_entries, "<Compose>", compose);
6594 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
6596 if (prefs_common.toolbar_detachable) {
6597 handlebox = gtk_handle_box_new();
6599 handlebox = gtk_hbox_new(FALSE, 0);
6601 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
6603 gtk_widget_realize(handlebox);
6605 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, window,
6608 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
6612 vbox2 = gtk_vbox_new(FALSE, 2);
6613 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
6614 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
6617 notebook = gtk_notebook_new();
6618 gtk_widget_set_size_request(notebook, -1, 130);
6619 gtk_widget_show(notebook);
6621 /* header labels and entries */
6622 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6623 compose_create_header(compose),
6624 gtk_label_new_with_mnemonic(_("Hea_der")));
6625 /* attachment list */
6626 attach_hbox = gtk_hbox_new(FALSE, 0);
6627 gtk_widget_show(attach_hbox);
6629 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
6630 gtk_widget_show(attach_lab1);
6631 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
6633 attach_lab2 = gtk_label_new("");
6634 gtk_widget_show(attach_lab2);
6635 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
6637 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6638 compose_create_attach(compose),
6641 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6642 compose_create_others(compose),
6643 gtk_label_new_with_mnemonic(_("Othe_rs")));
6646 subject_hbox = gtk_hbox_new(FALSE, 0);
6647 gtk_widget_show(subject_hbox);
6649 subject_frame = gtk_frame_new(NULL);
6650 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
6651 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
6652 gtk_widget_show(subject_frame);
6654 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
6655 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
6656 gtk_widget_show(subject);
6658 label = gtk_label_new(_("Subject:"));
6659 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
6660 gtk_widget_show(label);
6662 subject_entry = gtk_entry_new();
6663 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
6664 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
6665 G_CALLBACK(compose_grab_focus_cb), compose);
6666 gtk_widget_show(subject_entry);
6667 compose->subject_entry = subject_entry;
6668 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
6670 edit_vbox = gtk_vbox_new(FALSE, 0);
6672 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
6675 ruler_hbox = gtk_hbox_new(FALSE, 0);
6676 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
6678 ruler = gtk_shruler_new();
6679 gtk_ruler_set_range(GTK_RULER(ruler), 0.0, 100.0, 1.0, 100.0);
6680 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
6684 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
6685 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
6686 GTK_POLICY_AUTOMATIC,
6687 GTK_POLICY_AUTOMATIC);
6688 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
6690 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
6691 gtk_widget_set_size_request(scrolledwin, prefs_common.compose_width, -1);
6693 text = gtk_text_view_new();
6694 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
6695 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
6696 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
6697 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
6698 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
6700 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
6702 g_signal_connect_after(G_OBJECT(text), "size_allocate",
6703 G_CALLBACK(compose_edit_size_alloc),
6705 g_signal_connect(G_OBJECT(buffer), "changed",
6706 G_CALLBACK(compose_changed_cb), compose);
6707 g_signal_connect(G_OBJECT(text), "grab_focus",
6708 G_CALLBACK(compose_grab_focus_cb), compose);
6709 g_signal_connect(G_OBJECT(buffer), "insert_text",
6710 G_CALLBACK(text_inserted), compose);
6711 g_signal_connect(G_OBJECT(text), "button_press_event",
6712 G_CALLBACK(text_clicked), compose);
6714 g_signal_connect(G_OBJECT(text), "popup-menu",
6715 G_CALLBACK(compose_popup_menu), compose);
6717 gtk_widget_tap_and_hold_setup(GTK_WIDGET(text), NULL, NULL,
6718 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
6719 g_signal_connect(G_OBJECT(text), "tap-and-hold",
6720 G_CALLBACK(compose_popup_menu), compose);
6722 g_signal_connect(G_OBJECT(subject_entry), "changed",
6723 G_CALLBACK(compose_changed_cb), compose);
6726 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6727 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6728 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6729 g_signal_connect(G_OBJECT(text), "drag_data_received",
6730 G_CALLBACK(compose_insert_drag_received_cb),
6732 g_signal_connect(G_OBJECT(text), "drag-drop",
6733 G_CALLBACK(compose_drag_drop),
6735 gtk_widget_show_all(vbox);
6737 /* pane between attach clist and text */
6738 paned = gtk_vpaned_new();
6739 gtk_paned_set_gutter_size(GTK_PANED(paned), 12);
6740 gtk_container_add(GTK_CONTAINER(vbox2), paned);
6742 if( maemo_mainwindow_is_fullscreen(mainwindow_get_mainwindow()->window) )
6743 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 300 : 280);
6745 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 250 : 230);
6747 gtk_paned_add1(GTK_PANED(paned), notebook);
6748 gtk_paned_add2(GTK_PANED(paned), edit_vbox);
6749 gtk_widget_show_all(paned);
6752 if (prefs_common.textfont) {
6753 PangoFontDescription *font_desc;
6755 font_desc = pango_font_description_from_string
6756 (prefs_common.textfont);
6758 gtk_widget_modify_font(text, font_desc);
6759 pango_font_description_free(font_desc);
6763 n_entries = sizeof(compose_popup_entries) /
6764 sizeof(compose_popup_entries[0]);
6765 popupmenu = menu_create_items(compose_popup_entries, n_entries,
6766 "<Compose>", &popupfactory,
6769 ifactory = gtk_item_factory_from_widget(menubar);
6770 menu_set_sensitive(ifactory, "/Edit/Undo", FALSE);
6771 menu_set_sensitive(ifactory, "/Edit/Redo", FALSE);
6772 menu_set_sensitive(ifactory, "/Options/Remove references", FALSE);
6774 tmpl_menu = gtk_item_factory_get_item(ifactory, "/Tools/Template");
6776 undostruct = undo_init(text);
6777 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
6780 address_completion_start(window);
6782 compose->window = window;
6783 compose->vbox = vbox;
6784 compose->menubar = menubar;
6785 compose->handlebox = handlebox;
6787 compose->vbox2 = vbox2;
6789 compose->paned = paned;
6791 compose->attach_label = attach_lab2;
6793 compose->notebook = notebook;
6794 compose->edit_vbox = edit_vbox;
6795 compose->ruler_hbox = ruler_hbox;
6796 compose->ruler = ruler;
6797 compose->scrolledwin = scrolledwin;
6798 compose->text = text;
6800 compose->focused_editable = NULL;
6802 compose->popupmenu = popupmenu;
6803 compose->popupfactory = popupfactory;
6805 compose->tmpl_menu = tmpl_menu;
6807 compose->mode = mode;
6808 compose->rmode = mode;
6810 compose->targetinfo = NULL;
6811 compose->replyinfo = NULL;
6812 compose->fwdinfo = NULL;
6814 compose->replyto = NULL;
6816 compose->bcc = NULL;
6817 compose->followup_to = NULL;
6819 compose->ml_post = NULL;
6821 compose->inreplyto = NULL;
6822 compose->references = NULL;
6823 compose->msgid = NULL;
6824 compose->boundary = NULL;
6826 compose->autowrap = prefs_common.autowrap;
6828 compose->use_signing = FALSE;
6829 compose->use_encryption = FALSE;
6830 compose->privacy_system = NULL;
6832 compose->modified = FALSE;
6834 compose->return_receipt = FALSE;
6836 compose->to_list = NULL;
6837 compose->newsgroup_list = NULL;
6839 compose->undostruct = undostruct;
6841 compose->sig_str = NULL;
6843 compose->exteditor_file = NULL;
6844 compose->exteditor_pid = -1;
6845 compose->exteditor_tag = -1;
6846 compose->draft_timeout_tag = -2; /* inhibit auto-drafting while loading */
6849 menu_set_sensitive(ifactory, "/Spelling", FALSE);
6850 if (mode != COMPOSE_REDIRECT) {
6851 if (prefs_common.enable_aspell && prefs_common.dictionary &&
6852 strcmp(prefs_common.dictionary, "")) {
6853 gtkaspell = gtkaspell_new(prefs_common.aspell_path,
6854 prefs_common.dictionary,
6855 prefs_common.alt_dictionary,
6856 conv_get_locale_charset_str(),
6857 prefs_common.misspelled_col,
6858 prefs_common.check_while_typing,
6859 prefs_common.recheck_when_changing_dict,
6860 prefs_common.use_alternate,
6861 prefs_common.use_both_dicts,
6862 GTK_TEXT_VIEW(text),
6863 GTK_WINDOW(compose->window),
6864 compose_spell_menu_changed,
6867 alertpanel_error(_("Spell checker could not "
6869 gtkaspell_checkers_strerror());
6870 gtkaspell_checkers_reset_error();
6872 if (!gtkaspell_set_sug_mode(gtkaspell,
6873 prefs_common.aspell_sugmode)) {
6874 debug_print("Aspell: could not set "
6875 "suggestion mode %s\n",
6876 gtkaspell_checkers_strerror());
6877 gtkaspell_checkers_reset_error();
6880 menu_set_sensitive(ifactory, "/Spelling", TRUE);
6884 compose->gtkaspell = gtkaspell;
6885 compose_spell_menu_changed(compose);
6888 compose_select_account(compose, account, TRUE);
6890 menu_set_active(ifactory, "/Edit/Auto wrapping", prefs_common.autowrap);
6891 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
6892 compose_entry_append(compose, account->auto_cc, COMPOSE_CC);
6894 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
6895 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC);
6897 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
6898 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO);
6900 menu_set_sensitive(ifactory, "/Options/Reply mode", compose->mode == COMPOSE_REPLY);
6902 if (account->protocol != A_NNTP)
6903 gtk_entry_set_text(GTK_ENTRY(GTK_BIN(compose->header_last->combo)->child),
6904 prefs_common_translated_header_name("To:"));
6906 gtk_entry_set_text(GTK_ENTRY(GTK_BIN(compose->header_last->combo)->child),
6907 prefs_common_translated_header_name("Newsgroups:"));
6909 addressbook_set_target_compose(compose);
6911 if (mode != COMPOSE_REDIRECT)
6912 compose_set_template_menu(compose);
6914 menuitem = gtk_item_factory_get_item(ifactory, "/Tools/Template");
6915 menu_set_sensitive(ifactory, "/Tools/Template", FALSE);
6918 compose_list = g_list_append(compose_list, compose);
6920 if (!prefs_common.show_ruler)
6921 gtk_widget_hide(ruler_hbox);
6923 menuitem = gtk_item_factory_get_item(ifactory, "/Tools/Show ruler");
6924 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
6925 prefs_common.show_ruler);
6928 compose->priority = PRIORITY_NORMAL;
6929 compose_update_priority_menu_item(compose);
6931 compose_set_out_encoding(compose);
6934 compose_update_actions_menu(compose);
6936 /* Privacy Systems menu */
6937 compose_update_privacy_systems_menu(compose);
6939 activate_privacy_system(compose, account, TRUE);
6940 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
6942 gtk_widget_realize(window);
6944 gtk_widget_show(window);
6946 maemo_window_full_screen_if_needed(GTK_WINDOW(window));
6947 maemo_connect_key_press_to_mainwindow(GTK_WINDOW(window));
6954 static GtkWidget *compose_account_option_menu_create(Compose *compose)
6959 GtkWidget *optmenubox;
6962 GtkWidget *from_name = NULL;
6964 gint num = 0, def_menu = 0;
6966 accounts = account_get_list();
6967 g_return_val_if_fail(accounts != NULL, NULL);
6969 optmenubox = gtk_event_box_new();
6970 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
6971 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
6973 hbox = gtk_hbox_new(FALSE, 6);
6974 from_name = gtk_entry_new();
6976 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
6977 G_CALLBACK(compose_grab_focus_cb), compose);
6979 for (; accounts != NULL; accounts = accounts->next, num++) {
6980 PrefsAccount *ac = (PrefsAccount *)accounts->data;
6981 gchar *name, *from = NULL;
6983 if (ac == compose->account) def_menu = num;
6985 name = g_markup_printf_escaped(_("From: <i>%s</i>"),
6988 if (ac == compose->account) {
6989 if (ac->name && *ac->name) {
6991 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
6992 from = g_strdup_printf("%s <%s>",
6994 gtk_entry_set_text(GTK_ENTRY(from_name), from);
6996 from = g_strdup_printf("%s",
6998 gtk_entry_set_text(GTK_ENTRY(from_name), from);
7001 COMBOBOX_ADD(menu, name, ac->account_id);
7006 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
7008 g_signal_connect(G_OBJECT(optmenu), "changed",
7009 G_CALLBACK(account_activated),
7011 g_signal_connect(G_OBJECT(from_name), "populate-popup",
7012 G_CALLBACK(compose_entry_popup_extend),
7015 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
7016 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
7018 gtk_tooltips_set_tip(compose->tooltips, optmenubox,
7019 _("Account to use for this email"), NULL);
7020 gtk_tooltips_set_tip(compose->tooltips, from_name,
7021 _("Sender address to be used"), NULL);
7023 compose->from_name = from_name;
7028 static void compose_set_priority_cb(gpointer data,
7032 Compose *compose = (Compose *) data;
7033 compose->priority = action;
7036 static void compose_reply_change_mode(gpointer data,
7040 Compose *compose = (Compose *) data;
7041 gboolean was_modified = compose->modified;
7043 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
7045 g_return_if_fail(compose->replyinfo != NULL);
7047 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
7049 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
7051 if (action == COMPOSE_REPLY_TO_ALL)
7053 if (action == COMPOSE_REPLY_TO_SENDER)
7055 if (action == COMPOSE_REPLY_TO_LIST)
7058 compose_remove_header_entries(compose);
7059 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
7060 if (compose->account->set_autocc && compose->account->auto_cc)
7061 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC);
7063 if (compose->account->set_autobcc && compose->account->auto_bcc)
7064 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC);
7066 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
7067 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO);
7068 compose_show_first_last_header(compose, TRUE);
7069 compose->modified = was_modified;
7070 compose_set_title(compose);
7073 static void compose_update_priority_menu_item(Compose * compose)
7075 GtkItemFactory *ifactory;
7076 GtkWidget *menuitem = NULL;
7078 ifactory = gtk_item_factory_from_widget(compose->menubar);
7080 switch (compose->priority) {
7081 case PRIORITY_HIGHEST:
7082 menuitem = gtk_item_factory_get_item
7083 (ifactory, "/Options/Priority/Highest");
7086 menuitem = gtk_item_factory_get_item
7087 (ifactory, "/Options/Priority/High");
7089 case PRIORITY_NORMAL:
7090 menuitem = gtk_item_factory_get_item
7091 (ifactory, "/Options/Priority/Normal");
7094 menuitem = gtk_item_factory_get_item
7095 (ifactory, "/Options/Priority/Low");
7097 case PRIORITY_LOWEST:
7098 menuitem = gtk_item_factory_get_item
7099 (ifactory, "/Options/Priority/Lowest");
7102 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
7105 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
7107 Compose *compose = (Compose *) data;
7109 GtkItemFactory *ifactory;
7110 gboolean can_sign = FALSE, can_encrypt = FALSE;
7112 g_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
7114 if (!GTK_CHECK_MENU_ITEM(widget)->active)
7117 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
7118 g_free(compose->privacy_system);
7119 compose->privacy_system = NULL;
7120 if (systemid != NULL) {
7121 compose->privacy_system = g_strdup(systemid);
7123 can_sign = privacy_system_can_sign(systemid);
7124 can_encrypt = privacy_system_can_encrypt(systemid);
7127 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
7129 ifactory = gtk_item_factory_from_widget(compose->menubar);
7130 menu_set_sensitive(ifactory, "/Options/Sign", can_sign);
7131 menu_set_sensitive(ifactory, "/Options/Encrypt", can_encrypt);
7134 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
7136 static gchar *branch_path = "/Options/Privacy System";
7137 GtkItemFactory *ifactory;
7138 GtkWidget *menuitem = NULL;
7140 gboolean can_sign = FALSE, can_encrypt = FALSE;
7141 gboolean found = FALSE;
7143 ifactory = gtk_item_factory_from_widget(compose->menubar);
7145 if (compose->privacy_system != NULL) {
7148 menuitem = gtk_item_factory_get_widget(ifactory, branch_path);
7149 g_return_if_fail(menuitem != NULL);
7151 amenu = GTK_MENU_SHELL(menuitem)->children;
7153 while (amenu != NULL) {
7154 GList *alist = amenu->next;
7156 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
7157 if (systemid != NULL) {
7158 if (strcmp(systemid, compose->privacy_system) == 0) {
7159 menuitem = GTK_WIDGET(amenu->data);
7161 can_sign = privacy_system_can_sign(systemid);
7162 can_encrypt = privacy_system_can_encrypt(systemid);
7166 } else if (strlen(compose->privacy_system) == 0) {
7167 menuitem = GTK_WIDGET(amenu->data);
7170 can_encrypt = FALSE;
7177 if (menuitem != NULL)
7178 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
7180 if (warn && !found && strlen(compose->privacy_system)) {
7181 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
7182 "will not be able to sign or encrypt this message."),
7183 compose->privacy_system);
7187 menu_set_sensitive(ifactory, "/Options/Sign", can_sign);
7188 menu_set_sensitive(ifactory, "/Options/Encrypt", can_encrypt);
7191 static void compose_set_out_encoding(Compose *compose)
7193 GtkItemFactoryEntry *entry;
7194 GtkItemFactory *ifactory;
7195 CharSet out_encoding;
7196 gchar *path, *p, *q;
7199 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
7200 ifactory = gtk_item_factory_from_widget(compose->menubar);
7202 for (entry = compose_entries; entry->callback != compose_address_cb;
7204 if (entry->callback == compose_set_encoding_cb &&
7205 (CharSet)entry->callback_action == out_encoding) {
7206 p = q = path = g_strdup(entry->path);
7218 item = gtk_item_factory_get_item(ifactory, path);
7219 gtk_widget_activate(item);
7226 static void compose_set_template_menu(Compose *compose)
7228 GSList *tmpl_list, *cur;
7232 tmpl_list = template_get_config();
7234 menu = gtk_menu_new();
7236 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
7237 Template *tmpl = (Template *)cur->data;
7239 item = gtk_menu_item_new_with_label(tmpl->name);
7240 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
7241 g_signal_connect(G_OBJECT(item), "activate",
7242 G_CALLBACK(compose_template_activate_cb),
7244 g_object_set_data(G_OBJECT(item), "template", tmpl);
7245 gtk_widget_show(item);
7248 gtk_widget_show(menu);
7249 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
7252 void compose_update_actions_menu(Compose *compose)
7254 GtkItemFactory *ifactory;
7256 ifactory = gtk_item_factory_from_widget(compose->menubar);
7257 action_update_compose_menu(ifactory, "/Tools/Actions", compose);
7260 static void compose_update_privacy_systems_menu(Compose *compose)
7262 static gchar *branch_path = "/Options/Privacy System";
7263 GtkItemFactory *ifactory;
7264 GtkWidget *menuitem;
7265 GSList *systems, *cur;
7268 GtkWidget *system_none;
7271 ifactory = gtk_item_factory_from_widget(compose->menubar);
7273 /* remove old entries */
7274 menuitem = gtk_item_factory_get_widget(ifactory, branch_path);
7275 g_return_if_fail(menuitem != NULL);
7277 amenu = GTK_MENU_SHELL(menuitem)->children->next;
7278 while (amenu != NULL) {
7279 GList *alist = amenu->next;
7280 gtk_widget_destroy(GTK_WIDGET(amenu->data));
7284 system_none = gtk_item_factory_get_widget(ifactory,
7285 "/Options/Privacy System/None");
7287 g_signal_connect(G_OBJECT(system_none), "activate",
7288 G_CALLBACK(compose_set_privacy_system_cb), compose);
7290 systems = privacy_get_system_ids();
7291 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
7292 gchar *systemid = cur->data;
7294 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
7295 widget = gtk_radio_menu_item_new_with_label(group,
7296 privacy_system_get_name(systemid));
7297 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
7298 g_strdup(systemid), g_free);
7299 g_signal_connect(G_OBJECT(widget), "activate",
7300 G_CALLBACK(compose_set_privacy_system_cb), compose);
7302 gtk_menu_append(GTK_MENU(system_none->parent), widget);
7303 gtk_widget_show(widget);
7306 g_slist_free(systems);
7309 void compose_reflect_prefs_all(void)
7314 for (cur = compose_list; cur != NULL; cur = cur->next) {
7315 compose = (Compose *)cur->data;
7316 compose_set_template_menu(compose);
7320 void compose_reflect_prefs_pixmap_theme(void)
7325 for (cur = compose_list; cur != NULL; cur = cur->next) {
7326 compose = (Compose *)cur->data;
7327 toolbar_update(TOOLBAR_COMPOSE, compose);
7331 static const gchar *compose_quote_char_from_context(Compose *compose)
7333 const gchar *qmark = NULL;
7335 g_return_val_if_fail(compose != NULL, NULL);
7337 switch (compose->mode) {
7338 /* use forward-specific quote char */
7339 case COMPOSE_FORWARD:
7340 case COMPOSE_FORWARD_AS_ATTACH:
7341 case COMPOSE_FORWARD_INLINE:
7342 if (compose->folder && compose->folder->prefs &&
7343 compose->folder->prefs->forward_with_format)
7344 qmark = compose->folder->prefs->forward_quotemark;
7345 else if (compose->account->forward_with_format)
7346 qmark = compose->account->forward_quotemark;
7348 qmark = prefs_common.fw_quotemark;
7351 /* use reply-specific quote char in all other modes */
7353 if (compose->folder && compose->folder->prefs &&
7354 compose->folder->prefs->reply_with_format)
7355 qmark = compose->folder->prefs->reply_quotemark;
7356 else if (compose->account->reply_with_format)
7357 qmark = compose->account->reply_quotemark;
7359 qmark = prefs_common.quotemark;
7363 if (qmark == NULL || *qmark == '\0')
7369 static void compose_template_apply(Compose *compose, Template *tmpl,
7373 GtkTextBuffer *buffer;
7377 gchar *parsed_str = NULL;
7378 gint cursor_pos = 0;
7379 const gchar *err_msg = _("Template body format error at line %d.");
7382 /* process the body */
7384 text = GTK_TEXT_VIEW(compose->text);
7385 buffer = gtk_text_view_get_buffer(text);
7388 qmark = compose_quote_char_from_context(compose);
7390 if (compose->replyinfo != NULL) {
7393 gtk_text_buffer_set_text(buffer, "", -1);
7394 mark = gtk_text_buffer_get_insert(buffer);
7395 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7397 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
7398 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
7400 } else if (compose->fwdinfo != NULL) {
7403 gtk_text_buffer_set_text(buffer, "", -1);
7404 mark = gtk_text_buffer_get_insert(buffer);
7405 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7407 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
7408 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
7411 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
7413 GtkTextIter start, end;
7416 gtk_text_buffer_get_start_iter(buffer, &start);
7417 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
7418 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
7420 /* clear the buffer now */
7422 gtk_text_buffer_set_text(buffer, "", -1);
7424 parsed_str = compose_quote_fmt(compose, dummyinfo,
7425 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
7426 procmsg_msginfo_free( dummyinfo );
7432 gtk_text_buffer_set_text(buffer, "", -1);
7433 mark = gtk_text_buffer_get_insert(buffer);
7434 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7437 if (replace && parsed_str && compose->account->auto_sig)
7438 compose_insert_sig(compose, FALSE);
7440 if (replace && parsed_str) {
7441 gtk_text_buffer_get_start_iter(buffer, &iter);
7442 gtk_text_buffer_place_cursor(buffer, &iter);
7446 cursor_pos = quote_fmt_get_cursor_pos();
7447 compose->set_cursor_pos = cursor_pos;
7448 if (cursor_pos == -1)
7450 gtk_text_buffer_get_start_iter(buffer, &iter);
7451 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
7452 gtk_text_buffer_place_cursor(buffer, &iter);
7455 /* process the other fields */
7457 compose_template_apply_fields(compose, tmpl);
7458 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
7459 quote_fmt_reset_vartable();
7460 compose_changed_cb(NULL, compose);
7463 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
7465 MsgInfo* dummyinfo = NULL;
7466 MsgInfo *msginfo = NULL;
7469 if (compose->replyinfo != NULL)
7470 msginfo = compose->replyinfo;
7471 else if (compose->fwdinfo != NULL)
7472 msginfo = compose->fwdinfo;
7474 dummyinfo = compose_msginfo_new_from_compose(compose);
7475 msginfo = dummyinfo;
7478 if (tmpl->to && *tmpl->to != '\0') {
7480 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7481 compose->gtkaspell);
7483 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7485 quote_fmt_scan_string(tmpl->to);
7488 buf = quote_fmt_get_buffer();
7490 alertpanel_error(_("Template To format error."));
7492 compose_entry_append(compose, buf, COMPOSE_TO);
7496 if (tmpl->cc && *tmpl->cc != '\0') {
7498 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7499 compose->gtkaspell);
7501 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7503 quote_fmt_scan_string(tmpl->cc);
7506 buf = quote_fmt_get_buffer();
7508 alertpanel_error(_("Template Cc format error."));
7510 compose_entry_append(compose, buf, COMPOSE_CC);
7514 if (tmpl->bcc && *tmpl->bcc != '\0') {
7516 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7517 compose->gtkaspell);
7519 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7521 quote_fmt_scan_string(tmpl->bcc);
7524 buf = quote_fmt_get_buffer();
7526 alertpanel_error(_("Template Bcc format error."));
7528 compose_entry_append(compose, buf, COMPOSE_BCC);
7532 /* process the subject */
7533 if (tmpl->subject && *tmpl->subject != '\0') {
7535 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7536 compose->gtkaspell);
7538 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7540 quote_fmt_scan_string(tmpl->subject);
7543 buf = quote_fmt_get_buffer();
7545 alertpanel_error(_("Template subject format error."));
7547 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
7551 procmsg_msginfo_free( dummyinfo );
7554 static void compose_destroy(Compose *compose)
7556 GtkTextBuffer *buffer;
7557 GtkClipboard *clipboard;
7559 compose_list = g_list_remove(compose_list, compose);
7561 if (compose->updating) {
7562 debug_print("danger, not destroying anything now\n");
7563 compose->deferred_destroy = TRUE;
7566 /* NOTE: address_completion_end() does nothing with the window
7567 * however this may change. */
7568 address_completion_end(compose->window);
7570 slist_free_strings(compose->to_list);
7571 g_slist_free(compose->to_list);
7572 slist_free_strings(compose->newsgroup_list);
7573 g_slist_free(compose->newsgroup_list);
7574 slist_free_strings(compose->header_list);
7575 g_slist_free(compose->header_list);
7577 procmsg_msginfo_free(compose->targetinfo);
7578 procmsg_msginfo_free(compose->replyinfo);
7579 procmsg_msginfo_free(compose->fwdinfo);
7581 g_free(compose->replyto);
7582 g_free(compose->cc);
7583 g_free(compose->bcc);
7584 g_free(compose->newsgroups);
7585 g_free(compose->followup_to);
7587 g_free(compose->ml_post);
7589 g_free(compose->inreplyto);
7590 g_free(compose->references);
7591 g_free(compose->msgid);
7592 g_free(compose->boundary);
7594 g_free(compose->redirect_filename);
7595 if (compose->undostruct)
7596 undo_destroy(compose->undostruct);
7598 g_free(compose->sig_str);
7600 g_free(compose->exteditor_file);
7602 g_free(compose->orig_charset);
7604 g_free(compose->privacy_system);
7606 if (addressbook_get_target_compose() == compose)
7607 addressbook_set_target_compose(NULL);
7610 if (compose->gtkaspell) {
7611 gtkaspell_delete(compose->gtkaspell);
7612 compose->gtkaspell = NULL;
7616 prefs_common.compose_width = compose->scrolledwin->allocation.width;
7617 prefs_common.compose_height = compose->window->allocation.height;
7619 if (!gtk_widget_get_parent(compose->paned))
7620 gtk_widget_destroy(compose->paned);
7621 gtk_widget_destroy(compose->popupmenu);
7623 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
7624 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7625 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
7627 gtk_widget_destroy(compose->window);
7628 toolbar_destroy(compose->toolbar);
7629 g_free(compose->toolbar);
7630 g_mutex_free(compose->mutex);
7634 static void compose_attach_info_free(AttachInfo *ainfo)
7636 g_free(ainfo->file);
7637 g_free(ainfo->content_type);
7638 g_free(ainfo->name);
7642 static void compose_attach_update_label(Compose *compose)
7647 GtkTreeModel *model;
7652 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
7653 if(!gtk_tree_model_get_iter_first(model, &iter)) {
7654 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
7658 while(gtk_tree_model_iter_next(model, &iter))
7661 text = g_strdup_printf("(%d)", i);
7662 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
7666 static void compose_attach_remove_selected(Compose *compose)
7668 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
7669 GtkTreeSelection *selection;
7671 GtkTreeModel *model;
7673 selection = gtk_tree_view_get_selection(tree_view);
7674 sel = gtk_tree_selection_get_selected_rows(selection, &model);
7679 for (cur = sel; cur != NULL; cur = cur->next) {
7680 GtkTreePath *path = cur->data;
7681 GtkTreeRowReference *ref = gtk_tree_row_reference_new
7684 gtk_tree_path_free(path);
7687 for (cur = sel; cur != NULL; cur = cur->next) {
7688 GtkTreeRowReference *ref = cur->data;
7689 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
7692 if (gtk_tree_model_get_iter(model, &iter, path))
7693 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
7695 gtk_tree_path_free(path);
7696 gtk_tree_row_reference_free(ref);
7700 compose_attach_update_label(compose);
7703 static struct _AttachProperty
7706 GtkWidget *mimetype_entry;
7707 GtkWidget *encoding_optmenu;
7708 GtkWidget *path_entry;
7709 GtkWidget *filename_entry;
7711 GtkWidget *cancel_btn;
7714 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
7716 gtk_tree_path_free((GtkTreePath *)ptr);
7719 static void compose_attach_property(Compose *compose)
7721 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
7723 GtkComboBox *optmenu;
7724 GtkTreeSelection *selection;
7726 GtkTreeModel *model;
7729 static gboolean cancelled;
7731 /* only if one selected */
7732 selection = gtk_tree_view_get_selection(tree_view);
7733 if (gtk_tree_selection_count_selected_rows(selection) != 1)
7736 sel = gtk_tree_selection_get_selected_rows(selection, &model);
7740 path = (GtkTreePath *) sel->data;
7741 gtk_tree_model_get_iter(model, &iter, path);
7742 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
7745 g_list_foreach(sel, gtk_tree_path_free_, NULL);
7751 if (!attach_prop.window)
7752 compose_attach_property_create(&cancelled);
7753 gtk_widget_grab_focus(attach_prop.ok_btn);
7754 gtk_widget_show(attach_prop.window);
7755 manage_window_set_transient(GTK_WINDOW(attach_prop.window));
7757 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
7758 if (ainfo->encoding == ENC_UNKNOWN)
7759 combobox_select_by_data(optmenu, ENC_BASE64);
7761 combobox_select_by_data(optmenu, ainfo->encoding);
7763 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
7764 ainfo->content_type ? ainfo->content_type : "");
7765 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
7766 ainfo->file ? ainfo->file : "");
7767 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
7768 ainfo->name ? ainfo->name : "");
7771 const gchar *entry_text;
7773 gchar *cnttype = NULL;
7780 gtk_widget_hide(attach_prop.window);
7785 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
7786 if (*entry_text != '\0') {
7789 text = g_strstrip(g_strdup(entry_text));
7790 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
7791 cnttype = g_strdup(text);
7794 alertpanel_error(_("Invalid MIME type."));
7800 ainfo->encoding = combobox_get_active_data(optmenu);
7802 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
7803 if (*entry_text != '\0') {
7804 if (is_file_exist(entry_text) &&
7805 (size = get_file_size(entry_text)) > 0)
7806 file = g_strdup(entry_text);
7809 (_("File doesn't exist or is empty."));
7815 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
7816 if (*entry_text != '\0') {
7817 g_free(ainfo->name);
7818 ainfo->name = g_strdup(entry_text);
7822 g_free(ainfo->content_type);
7823 ainfo->content_type = cnttype;
7826 g_free(ainfo->file);
7832 /* update tree store */
7833 text = to_human_readable(ainfo->size);
7834 gtk_tree_model_get_iter(model, &iter, path);
7835 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
7836 COL_MIMETYPE, ainfo->content_type,
7838 COL_NAME, ainfo->name,
7844 gtk_tree_path_free(path);
7847 #define SET_LABEL_AND_ENTRY(str, entry, top) \
7849 label = gtk_label_new(str); \
7850 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
7851 GTK_FILL, 0, 0, 0); \
7852 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
7854 entry = gtk_entry_new(); \
7855 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
7856 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
7859 static void compose_attach_property_create(gboolean *cancelled)
7865 GtkWidget *mimetype_entry;
7868 GtkListStore *optmenu_menu;
7869 GtkWidget *path_entry;
7870 GtkWidget *filename_entry;
7873 GtkWidget *cancel_btn;
7874 GList *mime_type_list, *strlist;
7877 debug_print("Creating attach_property window...\n");
7879 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
7880 gtk_widget_set_size_request(window, 480, -1);
7881 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
7882 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
7883 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
7884 gtk_window_set_modal(GTK_WINDOW(window), TRUE);
7885 g_signal_connect(G_OBJECT(window), "delete_event",
7886 G_CALLBACK(attach_property_delete_event),
7888 g_signal_connect(G_OBJECT(window), "key_press_event",
7889 G_CALLBACK(attach_property_key_pressed),
7892 vbox = gtk_vbox_new(FALSE, 8);
7893 gtk_container_add(GTK_CONTAINER(window), vbox);
7895 table = gtk_table_new(4, 2, FALSE);
7896 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
7897 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
7898 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
7900 label = gtk_label_new(_("MIME type"));
7901 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
7903 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
7904 mimetype_entry = gtk_combo_box_entry_new_text();
7905 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
7906 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
7908 /* stuff with list */
7909 mime_type_list = procmime_get_mime_type_list();
7911 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
7912 MimeType *type = (MimeType *) mime_type_list->data;
7915 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
7917 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
7920 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
7921 (GCompareFunc)strcmp2);
7924 for (mime_type_list = strlist; mime_type_list != NULL;
7925 mime_type_list = mime_type_list->next) {
7926 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
7927 g_free(mime_type_list->data);
7929 g_list_free(strlist);
7930 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
7931 mimetype_entry = GTK_BIN(mimetype_entry)->child;
7933 label = gtk_label_new(_("Encoding"));
7934 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
7936 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
7938 hbox = gtk_hbox_new(FALSE, 0);
7939 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
7940 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
7942 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
7943 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
7945 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
7946 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
7947 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
7948 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
7949 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
7951 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
7953 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
7954 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
7956 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
7957 &ok_btn, GTK_STOCK_OK,
7959 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
7960 gtk_widget_grab_default(ok_btn);
7962 g_signal_connect(G_OBJECT(ok_btn), "clicked",
7963 G_CALLBACK(attach_property_ok),
7965 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
7966 G_CALLBACK(attach_property_cancel),
7969 gtk_widget_show_all(vbox);
7971 attach_prop.window = window;
7972 attach_prop.mimetype_entry = mimetype_entry;
7973 attach_prop.encoding_optmenu = optmenu;
7974 attach_prop.path_entry = path_entry;
7975 attach_prop.filename_entry = filename_entry;
7976 attach_prop.ok_btn = ok_btn;
7977 attach_prop.cancel_btn = cancel_btn;
7980 #undef SET_LABEL_AND_ENTRY
7982 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
7988 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
7994 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
7995 gboolean *cancelled)
8003 static gboolean attach_property_key_pressed(GtkWidget *widget,
8005 gboolean *cancelled)
8007 if (event && event->keyval == GDK_Escape) {
8014 static void compose_exec_ext_editor(Compose *compose)
8021 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
8022 G_DIR_SEPARATOR, compose);
8024 if (pipe(pipe_fds) < 0) {
8030 if ((pid = fork()) < 0) {
8037 /* close the write side of the pipe */
8040 compose->exteditor_file = g_strdup(tmp);
8041 compose->exteditor_pid = pid;
8043 compose_set_ext_editor_sensitive(compose, FALSE);
8045 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
8046 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
8050 } else { /* process-monitoring process */
8056 /* close the read side of the pipe */
8059 if (compose_write_body_to_file(compose, tmp) < 0) {
8060 fd_write_all(pipe_fds[1], "2\n", 2);
8064 pid_ed = compose_exec_ext_editor_real(tmp);
8066 fd_write_all(pipe_fds[1], "1\n", 2);
8070 /* wait until editor is terminated */
8071 waitpid(pid_ed, NULL, 0);
8073 fd_write_all(pipe_fds[1], "0\n", 2);
8080 #endif /* G_OS_UNIX */
8084 static gint compose_exec_ext_editor_real(const gchar *file)
8091 g_return_val_if_fail(file != NULL, -1);
8093 if ((pid = fork()) < 0) {
8098 if (pid != 0) return pid;
8100 /* grandchild process */
8102 if (setpgid(0, getppid()))
8105 if (prefs_common.ext_editor_cmd &&
8106 (p = strchr(prefs_common.ext_editor_cmd, '%')) &&
8107 *(p + 1) == 's' && !strchr(p + 2, '%')) {
8108 g_snprintf(buf, sizeof(buf), prefs_common.ext_editor_cmd, file);
8110 if (prefs_common.ext_editor_cmd)
8111 g_warning("External editor command line is invalid: '%s'\n",
8112 prefs_common.ext_editor_cmd);
8113 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
8116 cmdline = strsplit_with_quote(buf, " ", 1024);
8117 execvp(cmdline[0], cmdline);
8120 g_strfreev(cmdline);
8125 static gboolean compose_ext_editor_kill(Compose *compose)
8127 pid_t pgid = compose->exteditor_pid * -1;
8130 ret = kill(pgid, 0);
8132 if (ret == 0 || (ret == -1 && EPERM == errno)) {
8136 msg = g_strdup_printf
8137 (_("The external editor is still working.\n"
8138 "Force terminating the process?\n"
8139 "process group id: %d"), -pgid);
8140 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
8141 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
8145 if (val == G_ALERTALTERNATE) {
8146 g_source_remove(compose->exteditor_tag);
8147 g_io_channel_shutdown(compose->exteditor_ch,
8149 g_io_channel_unref(compose->exteditor_ch);
8151 if (kill(pgid, SIGTERM) < 0) perror("kill");
8152 waitpid(compose->exteditor_pid, NULL, 0);
8154 g_warning("Terminated process group id: %d", -pgid);
8155 g_warning("Temporary file: %s",
8156 compose->exteditor_file);
8158 compose_set_ext_editor_sensitive(compose, TRUE);
8160 g_free(compose->exteditor_file);
8161 compose->exteditor_file = NULL;
8162 compose->exteditor_pid = -1;
8163 compose->exteditor_ch = NULL;
8164 compose->exteditor_tag = -1;
8172 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
8176 Compose *compose = (Compose *)data;
8179 debug_print(_("Compose: input from monitoring process\n"));
8181 g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL);
8183 g_io_channel_shutdown(source, FALSE, NULL);
8184 g_io_channel_unref(source);
8186 waitpid(compose->exteditor_pid, NULL, 0);
8188 if (buf[0] == '0') { /* success */
8189 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
8190 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
8192 gtk_text_buffer_set_text(buffer, "", -1);
8193 compose_insert_file(compose, compose->exteditor_file);
8194 compose_changed_cb(NULL, compose);
8196 if (g_unlink(compose->exteditor_file) < 0)
8197 FILE_OP_ERROR(compose->exteditor_file, "unlink");
8198 } else if (buf[0] == '1') { /* failed */
8199 g_warning("Couldn't exec external editor\n");
8200 if (g_unlink(compose->exteditor_file) < 0)
8201 FILE_OP_ERROR(compose->exteditor_file, "unlink");
8202 } else if (buf[0] == '2') {
8203 g_warning("Couldn't write to file\n");
8204 } else if (buf[0] == '3') {
8205 g_warning("Pipe read failed\n");
8208 compose_set_ext_editor_sensitive(compose, TRUE);
8210 g_free(compose->exteditor_file);
8211 compose->exteditor_file = NULL;
8212 compose->exteditor_pid = -1;
8213 compose->exteditor_ch = NULL;
8214 compose->exteditor_tag = -1;
8219 static void compose_set_ext_editor_sensitive(Compose *compose,
8222 GtkItemFactory *ifactory;
8224 ifactory = gtk_item_factory_from_widget(compose->menubar);
8226 menu_set_sensitive(ifactory, "/Message/Send", sensitive);
8227 menu_set_sensitive(ifactory, "/Message/Send later", sensitive);
8228 menu_set_sensitive(ifactory, "/Message/Insert file", sensitive);
8229 menu_set_sensitive(ifactory, "/Message/Insert signature", sensitive);
8230 menu_set_sensitive(ifactory, "/Edit/Wrap current paragraph", sensitive);
8231 menu_set_sensitive(ifactory, "/Edit/Wrap all long lines", sensitive);
8232 menu_set_sensitive(ifactory, "/Edit/Edit with external editor",
8235 gtk_widget_set_sensitive(compose->text, sensitive);
8236 if (compose->toolbar->send_btn)
8237 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
8238 if (compose->toolbar->sendl_btn)
8239 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
8240 if (compose->toolbar->draft_btn)
8241 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
8242 if (compose->toolbar->insert_btn)
8243 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
8244 if (compose->toolbar->sig_btn)
8245 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
8246 if (compose->toolbar->exteditor_btn)
8247 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
8248 if (compose->toolbar->linewrap_current_btn)
8249 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
8250 if (compose->toolbar->linewrap_all_btn)
8251 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
8253 #endif /* G_OS_UNIX */
8256 * compose_undo_state_changed:
8258 * Change the sensivity of the menuentries undo and redo
8260 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
8261 gint redo_state, gpointer data)
8263 GtkWidget *widget = GTK_WIDGET(data);
8264 GtkItemFactory *ifactory;
8266 g_return_if_fail(widget != NULL);
8268 ifactory = gtk_item_factory_from_widget(widget);
8270 switch (undo_state) {
8271 case UNDO_STATE_TRUE:
8272 if (!undostruct->undo_state) {
8273 undostruct->undo_state = TRUE;
8274 menu_set_sensitive(ifactory, "/Edit/Undo", TRUE);
8277 case UNDO_STATE_FALSE:
8278 if (undostruct->undo_state) {
8279 undostruct->undo_state = FALSE;
8280 menu_set_sensitive(ifactory, "/Edit/Undo", FALSE);
8283 case UNDO_STATE_UNCHANGED:
8285 case UNDO_STATE_REFRESH:
8286 menu_set_sensitive(ifactory, "/Edit/Undo",
8287 undostruct->undo_state);
8290 g_warning("Undo state not recognized");
8294 switch (redo_state) {
8295 case UNDO_STATE_TRUE:
8296 if (!undostruct->redo_state) {
8297 undostruct->redo_state = TRUE;
8298 menu_set_sensitive(ifactory, "/Edit/Redo", TRUE);
8301 case UNDO_STATE_FALSE:
8302 if (undostruct->redo_state) {
8303 undostruct->redo_state = FALSE;
8304 menu_set_sensitive(ifactory, "/Edit/Redo", FALSE);
8307 case UNDO_STATE_UNCHANGED:
8309 case UNDO_STATE_REFRESH:
8310 menu_set_sensitive(ifactory, "/Edit/Redo",
8311 undostruct->redo_state);
8314 g_warning("Redo state not recognized");
8319 /* callback functions */
8321 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
8322 * includes "non-client" (windows-izm) in calculation, so this calculation
8323 * may not be accurate.
8325 static gboolean compose_edit_size_alloc(GtkEditable *widget,
8326 GtkAllocation *allocation,
8327 GtkSHRuler *shruler)
8329 if (prefs_common.show_ruler) {
8330 gint char_width = 0, char_height = 0;
8331 gint line_width_in_chars;
8333 gtkut_get_font_size(GTK_WIDGET(widget),
8334 &char_width, &char_height);
8335 line_width_in_chars =
8336 (allocation->width - allocation->x) / char_width;
8338 /* got the maximum */
8339 gtk_ruler_set_range(GTK_RULER(shruler),
8340 0.0, line_width_in_chars, 0,
8341 /*line_width_in_chars*/ char_width);
8347 static void account_activated(GtkComboBox *optmenu, gpointer data)
8349 Compose *compose = (Compose *)data;
8352 gchar *folderidentifier;
8353 gint account_id = 0;
8357 /* Get ID of active account in the combo box */
8358 menu = gtk_combo_box_get_model(optmenu);
8359 gtk_combo_box_get_active_iter(optmenu, &iter);
8360 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
8362 ac = account_find_from_id(account_id);
8363 g_return_if_fail(ac != NULL);
8365 if (ac != compose->account)
8366 compose_select_account(compose, ac, FALSE);
8368 /* Set message save folder */
8369 if (account_get_special_folder(compose->account, F_OUTBOX)) {
8370 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
8372 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
8373 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
8375 gtk_editable_delete_text(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
8376 if (account_get_special_folder(compose->account, F_OUTBOX)) {
8377 folderidentifier = folder_item_get_identifier(account_get_special_folder
8378 (compose->account, F_OUTBOX));
8379 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
8380 g_free(folderidentifier);
8384 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
8385 GtkTreeViewColumn *column, Compose *compose)
8387 compose_attach_property(compose);
8390 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
8393 Compose *compose = (Compose *)data;
8394 GtkTreeSelection *attach_selection;
8395 gint attach_nr_selected;
8396 GtkItemFactory *ifactory;
8398 if (!event) return FALSE;
8400 if (event->button == 3) {
8401 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
8402 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
8403 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
8405 if (attach_nr_selected > 0)
8407 menu_set_sensitive(ifactory, "/Remove", TRUE);
8408 menu_set_sensitive(ifactory, "/Properties...", TRUE);
8410 menu_set_sensitive(ifactory, "/Remove", FALSE);
8411 menu_set_sensitive(ifactory, "/Properties...", FALSE);
8414 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
8415 NULL, NULL, event->button, event->time);
8422 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
8425 Compose *compose = (Compose *)data;
8427 if (!event) return FALSE;
8429 switch (event->keyval) {
8431 compose_attach_remove_selected(compose);
8437 static void compose_allow_user_actions (Compose *compose, gboolean allow)
8439 GtkItemFactory *ifactory = gtk_item_factory_from_widget(compose->menubar);
8440 toolbar_comp_set_sensitive(compose, allow);
8441 menu_set_sensitive(ifactory, "/Message", allow);
8442 menu_set_sensitive(ifactory, "/Edit", allow);
8444 menu_set_sensitive(ifactory, "/Spelling", allow);
8446 menu_set_sensitive(ifactory, "/Options", allow);
8447 menu_set_sensitive(ifactory, "/Tools", allow);
8448 menu_set_sensitive(ifactory, "/Help", allow);
8450 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
8454 static void compose_send_cb(gpointer data, guint action, GtkWidget *widget)
8456 Compose *compose = (Compose *)data;
8458 if (prefs_common.work_offline &&
8459 !inc_offline_should_override(TRUE,
8460 _("Claws Mail needs network access in order "
8461 "to send this email.")))
8464 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
8465 g_source_remove(compose->draft_timeout_tag);
8466 compose->draft_timeout_tag = -1;
8469 compose_send(compose);
8472 static void compose_send_later_cb(gpointer data, guint action,
8475 Compose *compose = (Compose *)data;
8479 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
8483 compose_close(compose);
8484 } else if (val == -1) {
8485 alertpanel_error(_("Could not queue message."));
8486 } else if (val == -2) {
8487 alertpanel_error(_("Could not queue message:\n\n%s."), strerror(errno));
8488 } else if (val == -3) {
8489 if (privacy_peek_error())
8490 alertpanel_error(_("Could not queue message for sending:\n\n"
8491 "Signature failed: %s"), privacy_get_error());
8492 } else if (val == -4) {
8493 alertpanel_error(_("Could not queue message for sending:\n\n"
8494 "Charset conversion failed."));
8495 } else if (val == -5) {
8496 alertpanel_error(_("Could not queue message for sending:\n\n"
8497 "Couldn't get recipient encryption key."));
8498 } else if (val == -6) {
8501 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
8504 #define DRAFTED_AT_EXIT "drafted_at_exit"
8505 static void compose_register_draft(MsgInfo *info)
8507 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8508 DRAFTED_AT_EXIT, NULL);
8509 FILE *fp = fopen(filepath, "ab");
8512 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
8520 gboolean compose_draft (gpointer data, guint action)
8522 Compose *compose = (Compose *)data;
8526 MsgFlags flag = {0, 0};
8527 static gboolean lock = FALSE;
8528 MsgInfo *newmsginfo;
8530 gboolean target_locked = FALSE;
8531 gboolean err = FALSE;
8533 if (lock) return FALSE;
8535 if (compose->sending)
8538 draft = account_get_special_folder(compose->account, F_DRAFT);
8539 g_return_val_if_fail(draft != NULL, FALSE);
8541 if (!g_mutex_trylock(compose->mutex)) {
8542 /* we don't want to lock the mutex once it's available,
8543 * because as the only other part of compose.c locking
8544 * it is compose_close - which means once unlocked,
8545 * the compose struct will be freed */
8546 debug_print("couldn't lock mutex, probably sending\n");
8552 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
8553 G_DIR_SEPARATOR, compose);
8554 if ((fp = g_fopen(tmp, "wb")) == NULL) {
8555 FILE_OP_ERROR(tmp, "fopen");
8559 /* chmod for security */
8560 if (change_file_mode_rw(fp, tmp) < 0) {
8561 FILE_OP_ERROR(tmp, "chmod");
8562 g_warning("can't change file mode\n");
8565 /* Save draft infos */
8566 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
8567 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
8569 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
8570 gchar *savefolderid;
8572 savefolderid = gtk_editable_get_chars(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
8573 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
8574 g_free(savefolderid);
8576 if (compose->return_receipt) {
8577 err |= (fprintf(fp, "RRCPT:1\n") < 0);
8579 if (compose->privacy_system) {
8580 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
8581 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
8582 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
8585 /* Message-ID of message replying to */
8586 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
8589 folderid = folder_item_get_identifier(compose->replyinfo->folder);
8590 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
8593 /* Message-ID of message forwarding to */
8594 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
8597 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
8598 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
8602 /* end of headers */
8603 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
8610 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
8614 if (fclose(fp) == EOF) {
8618 if (compose->targetinfo) {
8619 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
8620 flag.perm_flags = target_locked?MSG_LOCKED:0;
8622 flag.tmp_flags = MSG_DRAFT;
8624 folder_item_scan(draft);
8625 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
8626 MsgInfo *tmpinfo = NULL;
8627 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
8628 if (compose->msgid) {
8629 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
8632 msgnum = tmpinfo->msgnum;
8633 procmsg_msginfo_free(tmpinfo);
8634 debug_print("got draft msgnum %d from scanning\n", msgnum);
8636 debug_print("didn't get draft msgnum after scanning\n");
8639 debug_print("got draft msgnum %d from adding\n", msgnum);
8645 if (action != COMPOSE_AUTO_SAVE) {
8646 if (action != COMPOSE_DRAFT_FOR_EXIT)
8647 alertpanel_error(_("Could not save draft."));
8650 gtkut_window_popup(compose->window);
8651 val = alertpanel_full(_("Could not save draft"),
8652 _("Could not save draft.\n"
8653 "Do you want to cancel exit or discard this email?"),
8654 _("_Cancel exit"), _("_Discard email"), NULL,
8655 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
8656 if (val == G_ALERTALTERNATE) {
8658 g_mutex_unlock(compose->mutex); /* must be done before closing */
8659 compose_close(compose);
8663 g_mutex_unlock(compose->mutex); /* must be done before closing */
8672 if (compose->mode == COMPOSE_REEDIT) {
8673 compose_remove_reedit_target(compose, TRUE);
8676 newmsginfo = folder_item_get_msginfo(draft, msgnum);
8679 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
8681 procmsg_msginfo_set_flags(newmsginfo, MSG_LOCKED, MSG_DRAFT);
8683 procmsg_msginfo_set_flags(newmsginfo, 0, MSG_DRAFT);
8684 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
8685 procmsg_msginfo_set_flags(newmsginfo, 0,
8686 MSG_HAS_ATTACHMENT);
8688 if (action == COMPOSE_DRAFT_FOR_EXIT) {
8689 compose_register_draft(newmsginfo);
8691 procmsg_msginfo_free(newmsginfo);
8694 folder_item_scan(draft);
8696 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
8698 g_mutex_unlock(compose->mutex); /* must be done before closing */
8699 compose_close(compose);
8705 path = folder_item_fetch_msg(draft, msgnum);
8707 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
8710 if (g_stat(path, &s) < 0) {
8711 FILE_OP_ERROR(path, "stat");
8717 procmsg_msginfo_free(compose->targetinfo);
8718 compose->targetinfo = procmsg_msginfo_new();
8719 compose->targetinfo->msgnum = msgnum;
8720 compose->targetinfo->size = s.st_size;
8721 compose->targetinfo->mtime = s.st_mtime;
8722 compose->targetinfo->folder = draft;
8724 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
8725 compose->mode = COMPOSE_REEDIT;
8727 if (action == COMPOSE_AUTO_SAVE) {
8728 compose->autosaved_draft = compose->targetinfo;
8730 compose->modified = FALSE;
8731 compose_set_title(compose);
8735 g_mutex_unlock(compose->mutex);
8739 void compose_clear_exit_drafts(void)
8741 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8742 DRAFTED_AT_EXIT, NULL);
8743 if (is_file_exist(filepath))
8749 void compose_reopen_exit_drafts(void)
8751 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8752 DRAFTED_AT_EXIT, NULL);
8753 FILE *fp = fopen(filepath, "rb");
8757 while (fgets(buf, sizeof(buf), fp)) {
8758 gchar **parts = g_strsplit(buf, "\t", 2);
8759 const gchar *folder = parts[0];
8760 int msgnum = parts[1] ? atoi(parts[1]):-1;
8762 if (folder && *folder && msgnum > -1) {
8763 FolderItem *item = folder_find_item_from_identifier(folder);
8764 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
8766 compose_reedit(info, FALSE);
8773 compose_clear_exit_drafts();
8776 static void compose_draft_cb(gpointer data, guint action, GtkWidget *widget)
8778 compose_draft(data, action);
8781 static void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
8783 if (compose && file_list) {
8786 for ( tmp = file_list; tmp; tmp = tmp->next) {
8787 gchar *file = (gchar *) tmp->data;
8788 gchar *utf8_filename = conv_filename_to_utf8(file);
8789 compose_attach_append(compose, file, utf8_filename, NULL);
8790 compose_changed_cb(NULL, compose);
8795 g_free(utf8_filename);
8800 static void compose_attach_cb(gpointer data, guint action, GtkWidget *widget)
8802 Compose *compose = (Compose *)data;
8805 if (compose->redirect_filename != NULL)
8808 file_list = filesel_select_multiple_files_open(_("Select file"));
8811 compose_attach_from_list(compose, file_list, TRUE);
8812 g_list_free(file_list);
8816 static void compose_insert_file_cb(gpointer data, guint action,
8819 Compose *compose = (Compose *)data;
8822 file_list = filesel_select_multiple_files_open(_("Select file"));
8827 for ( tmp = file_list; tmp; tmp = tmp->next) {
8828 gchar *file = (gchar *) tmp->data;
8829 gchar *filedup = g_strdup(file);
8830 gchar *shortfile = g_path_get_basename(filedup);
8831 ComposeInsertResult res;
8833 res = compose_insert_file(compose, file);
8834 if (res == COMPOSE_INSERT_READ_ERROR) {
8835 alertpanel_error(_("File '%s' could not be read."), shortfile);
8836 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
8837 alertpanel_error(_("File '%s' contained invalid characters\n"
8838 "for the current encoding, insertion may be incorrect."), shortfile);
8844 g_list_free(file_list);
8848 static void compose_insert_sig_cb(gpointer data, guint action,
8851 Compose *compose = (Compose *)data;
8853 compose_insert_sig(compose, FALSE);
8856 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
8860 Compose *compose = (Compose *)data;
8862 gtkut_widget_get_uposition(widget, &x, &y);
8863 prefs_common.compose_x = x;
8864 prefs_common.compose_y = y;
8866 if (compose->sending || compose->updating)
8868 compose_close_cb(compose, 0, NULL);
8872 void compose_close_toolbar(Compose *compose)
8874 compose_close_cb(compose, 0, NULL);
8877 static void compose_close_cb(gpointer data, guint action, GtkWidget *widget)
8879 Compose *compose = (Compose *)data;
8883 if (compose->exteditor_tag != -1) {
8884 if (!compose_ext_editor_kill(compose))
8889 if (compose->modified) {
8890 val = alertpanel(_("Discard message"),
8891 _("This message has been modified. Discard it?"),
8892 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
8895 case G_ALERTDEFAULT:
8896 if (prefs_common.autosave)
8897 compose_remove_draft(compose);
8899 case G_ALERTALTERNATE:
8900 compose_draft_cb(data, COMPOSE_QUIT_EDITING, NULL);
8907 compose_close(compose);
8910 static void compose_set_encoding_cb(gpointer data, guint action,
8913 Compose *compose = (Compose *)data;
8915 if (GTK_CHECK_MENU_ITEM(widget)->active)
8916 compose->out_encoding = (CharSet)action;
8919 static void compose_address_cb(gpointer data, guint action, GtkWidget *widget)
8921 Compose *compose = (Compose *)data;
8923 addressbook_open(compose);
8926 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
8928 Compose *compose = (Compose *)data;
8933 tmpl = g_object_get_data(G_OBJECT(widget), "template");
8934 g_return_if_fail(tmpl != NULL);
8936 msg = g_strdup_printf(_("Do you want to apply the template '%s' ?"),
8938 val = alertpanel(_("Apply template"), msg,
8939 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
8942 if (val == G_ALERTDEFAULT)
8943 compose_template_apply(compose, tmpl, TRUE);
8944 else if (val == G_ALERTALTERNATE)
8945 compose_template_apply(compose, tmpl, FALSE);
8948 static void compose_ext_editor_cb(gpointer data, guint action,
8951 Compose *compose = (Compose *)data;
8953 compose_exec_ext_editor(compose);
8956 static void compose_undo_cb(Compose *compose)
8958 gboolean prev_autowrap = compose->autowrap;
8960 compose->autowrap = FALSE;
8961 undo_undo(compose->undostruct);
8962 compose->autowrap = prev_autowrap;
8965 static void compose_redo_cb(Compose *compose)
8967 gboolean prev_autowrap = compose->autowrap;
8969 compose->autowrap = FALSE;
8970 undo_redo(compose->undostruct);
8971 compose->autowrap = prev_autowrap;
8974 static void entry_cut_clipboard(GtkWidget *entry)
8976 if (GTK_IS_EDITABLE(entry))
8977 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
8978 else if (GTK_IS_TEXT_VIEW(entry))
8979 gtk_text_buffer_cut_clipboard(
8980 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
8981 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
8985 static void entry_copy_clipboard(GtkWidget *entry)
8987 if (GTK_IS_EDITABLE(entry))
8988 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
8989 else if (GTK_IS_TEXT_VIEW(entry))
8990 gtk_text_buffer_copy_clipboard(
8991 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
8992 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
8995 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
8996 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
8998 if (GTK_IS_TEXT_VIEW(entry)) {
8999 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
9000 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
9001 GtkTextIter start_iter, end_iter;
9003 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
9005 if (contents == NULL)
9008 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
9010 /* we shouldn't delete the selection when middle-click-pasting, or we
9011 * can't mid-click-paste our own selection */
9012 if (clip != GDK_SELECTION_PRIMARY) {
9013 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
9016 if (insert_place == NULL) {
9017 /* if insert_place isn't specified, insert at the cursor.
9018 * used for Ctrl-V pasting */
9019 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
9020 start = gtk_text_iter_get_offset(&start_iter);
9021 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
9023 /* if insert_place is specified, paste here.
9024 * used for mid-click-pasting */
9025 start = gtk_text_iter_get_offset(insert_place);
9026 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
9030 /* paste unwrapped: mark the paste so it's not wrapped later */
9031 end = start + strlen(contents);
9032 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
9033 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
9034 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
9035 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
9036 /* rewrap paragraph now (after a mid-click-paste) */
9037 mark_start = gtk_text_buffer_get_insert(buffer);
9038 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
9039 gtk_text_iter_backward_char(&start_iter);
9040 compose_beautify_paragraph(compose, &start_iter, TRUE);
9042 } else if (GTK_IS_EDITABLE(entry))
9043 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
9047 static void entry_allsel(GtkWidget *entry)
9049 if (GTK_IS_EDITABLE(entry))
9050 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
9051 else if (GTK_IS_TEXT_VIEW(entry)) {
9052 GtkTextIter startiter, enditer;
9053 GtkTextBuffer *textbuf;
9055 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
9056 gtk_text_buffer_get_start_iter(textbuf, &startiter);
9057 gtk_text_buffer_get_end_iter(textbuf, &enditer);
9059 gtk_text_buffer_move_mark_by_name(textbuf,
9060 "selection_bound", &startiter);
9061 gtk_text_buffer_move_mark_by_name(textbuf,
9062 "insert", &enditer);
9066 static void compose_cut_cb(Compose *compose)
9068 if (compose->focused_editable
9070 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9073 entry_cut_clipboard(compose->focused_editable);
9076 static void compose_copy_cb(Compose *compose)
9078 if (compose->focused_editable
9080 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9083 entry_copy_clipboard(compose->focused_editable);
9086 static void compose_paste_cb(Compose *compose)
9089 GtkTextBuffer *buffer;
9091 if (compose->focused_editable &&
9092 GTK_WIDGET_HAS_FOCUS(compose->focused_editable))
9093 entry_paste_clipboard(compose, compose->focused_editable,
9094 prefs_common.linewrap_pastes,
9095 GDK_SELECTION_CLIPBOARD, NULL);
9099 static void compose_paste_as_quote_cb(Compose *compose)
9101 gint wrap_quote = prefs_common.linewrap_quote;
9102 if (compose->focused_editable
9104 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9107 /* let text_insert() (called directly or at a later time
9108 * after the gtk_editable_paste_clipboard) know that
9109 * text is to be inserted as a quotation. implemented
9110 * by using a simple refcount... */
9111 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
9112 G_OBJECT(compose->focused_editable),
9113 "paste_as_quotation"));
9114 g_object_set_data(G_OBJECT(compose->focused_editable),
9115 "paste_as_quotation",
9116 GINT_TO_POINTER(paste_as_quotation + 1));
9117 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
9118 entry_paste_clipboard(compose, compose->focused_editable,
9119 prefs_common.linewrap_pastes,
9120 GDK_SELECTION_CLIPBOARD, NULL);
9121 prefs_common.linewrap_quote = wrap_quote;
9125 static void compose_paste_no_wrap_cb(Compose *compose)
9128 GtkTextBuffer *buffer;
9130 if (compose->focused_editable
9132 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9135 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
9136 GDK_SELECTION_CLIPBOARD, NULL);
9140 static void compose_paste_wrap_cb(Compose *compose)
9143 GtkTextBuffer *buffer;
9145 if (compose->focused_editable
9147 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9150 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
9151 GDK_SELECTION_CLIPBOARD, NULL);
9155 static void compose_allsel_cb(Compose *compose)
9157 if (compose->focused_editable
9159 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9162 entry_allsel(compose->focused_editable);
9165 static void textview_move_beginning_of_line (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 gtk_text_iter_set_line_offset(&ins, 0);
9177 gtk_text_buffer_place_cursor(buffer, &ins);
9180 static void textview_move_forward_character (GtkTextView *text)
9182 GtkTextBuffer *buffer;
9186 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9188 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9189 mark = gtk_text_buffer_get_insert(buffer);
9190 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9191 if (gtk_text_iter_forward_cursor_position(&ins))
9192 gtk_text_buffer_place_cursor(buffer, &ins);
9195 static void textview_move_backward_character (GtkTextView *text)
9197 GtkTextBuffer *buffer;
9201 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9203 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9204 mark = gtk_text_buffer_get_insert(buffer);
9205 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9206 if (gtk_text_iter_backward_cursor_position(&ins))
9207 gtk_text_buffer_place_cursor(buffer, &ins);
9210 static void textview_move_forward_word (GtkTextView *text)
9212 GtkTextBuffer *buffer;
9217 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9219 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9220 mark = gtk_text_buffer_get_insert(buffer);
9221 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9222 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
9223 if (gtk_text_iter_forward_word_ends(&ins, count)) {
9224 gtk_text_iter_backward_word_start(&ins);
9225 gtk_text_buffer_place_cursor(buffer, &ins);
9229 static void textview_move_backward_word (GtkTextView *text)
9231 GtkTextBuffer *buffer;
9236 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9238 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9239 mark = gtk_text_buffer_get_insert(buffer);
9240 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9241 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
9242 if (gtk_text_iter_backward_word_starts(&ins, 1))
9243 gtk_text_buffer_place_cursor(buffer, &ins);
9246 static void textview_move_end_of_line (GtkTextView *text)
9248 GtkTextBuffer *buffer;
9252 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9254 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9255 mark = gtk_text_buffer_get_insert(buffer);
9256 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9257 if (gtk_text_iter_forward_to_line_end(&ins))
9258 gtk_text_buffer_place_cursor(buffer, &ins);
9261 static void textview_move_next_line (GtkTextView *text)
9263 GtkTextBuffer *buffer;
9268 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9270 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9271 mark = gtk_text_buffer_get_insert(buffer);
9272 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9273 offset = gtk_text_iter_get_line_offset(&ins);
9274 if (gtk_text_iter_forward_line(&ins)) {
9275 gtk_text_iter_set_line_offset(&ins, offset);
9276 gtk_text_buffer_place_cursor(buffer, &ins);
9280 static void textview_move_previous_line (GtkTextView *text)
9282 GtkTextBuffer *buffer;
9287 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9289 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9290 mark = gtk_text_buffer_get_insert(buffer);
9291 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9292 offset = gtk_text_iter_get_line_offset(&ins);
9293 if (gtk_text_iter_backward_line(&ins)) {
9294 gtk_text_iter_set_line_offset(&ins, offset);
9295 gtk_text_buffer_place_cursor(buffer, &ins);
9299 static void textview_delete_forward_character (GtkTextView *text)
9301 GtkTextBuffer *buffer;
9303 GtkTextIter ins, end_iter;
9305 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9307 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9308 mark = gtk_text_buffer_get_insert(buffer);
9309 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9311 if (gtk_text_iter_forward_char(&end_iter)) {
9312 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9316 static void textview_delete_backward_character (GtkTextView *text)
9318 GtkTextBuffer *buffer;
9320 GtkTextIter ins, end_iter;
9322 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9324 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9325 mark = gtk_text_buffer_get_insert(buffer);
9326 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9328 if (gtk_text_iter_backward_char(&end_iter)) {
9329 gtk_text_buffer_delete(buffer, &end_iter, &ins);
9333 static void textview_delete_forward_word (GtkTextView *text)
9335 GtkTextBuffer *buffer;
9337 GtkTextIter ins, end_iter;
9339 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9341 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9342 mark = gtk_text_buffer_get_insert(buffer);
9343 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9345 if (gtk_text_iter_forward_word_end(&end_iter)) {
9346 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9350 static void textview_delete_backward_word (GtkTextView *text)
9352 GtkTextBuffer *buffer;
9354 GtkTextIter ins, end_iter;
9356 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9358 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9359 mark = gtk_text_buffer_get_insert(buffer);
9360 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9362 if (gtk_text_iter_backward_word_start(&end_iter)) {
9363 gtk_text_buffer_delete(buffer, &end_iter, &ins);
9367 static void textview_delete_line (GtkTextView *text)
9369 GtkTextBuffer *buffer;
9371 GtkTextIter ins, start_iter, end_iter;
9374 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9376 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9377 mark = gtk_text_buffer_get_insert(buffer);
9378 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9381 gtk_text_iter_set_line_offset(&start_iter, 0);
9384 if (gtk_text_iter_ends_line(&end_iter))
9385 found = gtk_text_iter_forward_char(&end_iter);
9387 found = gtk_text_iter_forward_to_line_end(&end_iter);
9390 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
9393 static void textview_delete_to_line_end (GtkTextView *text)
9395 GtkTextBuffer *buffer;
9397 GtkTextIter ins, end_iter;
9400 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9402 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9403 mark = gtk_text_buffer_get_insert(buffer);
9404 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9406 if (gtk_text_iter_ends_line(&end_iter))
9407 found = gtk_text_iter_forward_char(&end_iter);
9409 found = gtk_text_iter_forward_to_line_end(&end_iter);
9411 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9414 static void compose_advanced_action_cb(Compose *compose,
9415 ComposeCallAdvancedAction action)
9417 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9419 void (*do_action) (GtkTextView *text);
9420 } action_table[] = {
9421 {textview_move_beginning_of_line},
9422 {textview_move_forward_character},
9423 {textview_move_backward_character},
9424 {textview_move_forward_word},
9425 {textview_move_backward_word},
9426 {textview_move_end_of_line},
9427 {textview_move_next_line},
9428 {textview_move_previous_line},
9429 {textview_delete_forward_character},
9430 {textview_delete_backward_character},
9431 {textview_delete_forward_word},
9432 {textview_delete_backward_word},
9433 {textview_delete_line},
9434 {NULL}, /* gtk_stext_delete_line_n */
9435 {textview_delete_to_line_end}
9438 if (!GTK_WIDGET_HAS_FOCUS(text)) return;
9440 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
9441 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
9442 if (action_table[action].do_action)
9443 action_table[action].do_action(text);
9445 g_warning("Not implemented yet.");
9449 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
9453 if (GTK_IS_EDITABLE(widget)) {
9454 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
9455 gtk_editable_set_position(GTK_EDITABLE(widget),
9458 if (widget->parent && widget->parent->parent
9459 && widget->parent->parent->parent) {
9460 if (GTK_IS_SCROLLED_WINDOW(widget->parent->parent->parent)) {
9461 gint y = widget->allocation.y;
9462 gint height = widget->allocation.height;
9463 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
9464 (GTK_SCROLLED_WINDOW(widget->parent->parent->parent));
9466 if (y < (int)shown->value) {
9467 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown), y - 1);
9469 if (y + height > (int)shown->value + (int)shown->page_size) {
9470 if (y - height - 1 < (int)shown->upper - (int)shown->page_size) {
9471 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
9472 y + height - (int)shown->page_size - 1);
9474 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
9475 (int)shown->upper - (int)shown->page_size - 1);
9482 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
9483 compose->focused_editable = widget;
9486 if (GTK_IS_TEXT_VIEW(widget)
9487 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
9488 gtk_widget_ref(compose->notebook);
9489 gtk_widget_ref(compose->edit_vbox);
9490 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
9491 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
9492 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
9493 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
9494 gtk_widget_unref(compose->notebook);
9495 gtk_widget_unref(compose->edit_vbox);
9496 g_signal_handlers_block_by_func(G_OBJECT(widget),
9497 G_CALLBACK(compose_grab_focus_cb),
9499 gtk_widget_grab_focus(widget);
9500 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
9501 G_CALLBACK(compose_grab_focus_cb),
9503 } else if (!GTK_IS_TEXT_VIEW(widget)
9504 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
9505 gtk_widget_ref(compose->notebook);
9506 gtk_widget_ref(compose->edit_vbox);
9507 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
9508 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
9509 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
9510 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
9511 gtk_widget_unref(compose->notebook);
9512 gtk_widget_unref(compose->edit_vbox);
9513 g_signal_handlers_block_by_func(G_OBJECT(widget),
9514 G_CALLBACK(compose_grab_focus_cb),
9516 gtk_widget_grab_focus(widget);
9517 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
9518 G_CALLBACK(compose_grab_focus_cb),
9524 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
9526 compose->modified = TRUE;
9528 compose_set_title(compose);
9532 static void compose_wrap_cb(gpointer data, guint action, GtkWidget *widget)
9534 Compose *compose = (Compose *)data;
9537 compose_wrap_all_full(compose, TRUE);
9539 compose_beautify_paragraph(compose, NULL, TRUE);
9542 static void compose_find_cb(gpointer data, guint action, GtkWidget *widget)
9544 Compose *compose = (Compose *)data;
9546 message_search_compose(compose);
9549 static void compose_toggle_autowrap_cb(gpointer data, guint action,
9552 Compose *compose = (Compose *)data;
9553 compose->autowrap = GTK_CHECK_MENU_ITEM(widget)->active;
9554 if (compose->autowrap)
9555 compose_wrap_all_full(compose, TRUE);
9556 compose->autowrap = GTK_CHECK_MENU_ITEM(widget)->active;
9559 static void compose_toggle_sign_cb(gpointer data, guint action,
9562 Compose *compose = (Compose *)data;
9564 if (GTK_CHECK_MENU_ITEM(widget)->active)
9565 compose->use_signing = TRUE;
9567 compose->use_signing = FALSE;
9570 static void compose_toggle_encrypt_cb(gpointer data, guint action,
9573 Compose *compose = (Compose *)data;
9575 if (GTK_CHECK_MENU_ITEM(widget)->active)
9576 compose->use_encryption = TRUE;
9578 compose->use_encryption = FALSE;
9581 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
9583 g_free(compose->privacy_system);
9585 compose->privacy_system = g_strdup(account->default_privacy_system);
9586 compose_update_privacy_system_menu_item(compose, warn);
9589 static void compose_toggle_ruler_cb(gpointer data, guint action,
9592 Compose *compose = (Compose *)data;
9594 if (GTK_CHECK_MENU_ITEM(widget)->active) {
9595 gtk_widget_show(compose->ruler_hbox);
9596 prefs_common.show_ruler = TRUE;
9598 gtk_widget_hide(compose->ruler_hbox);
9599 gtk_widget_queue_resize(compose->edit_vbox);
9600 prefs_common.show_ruler = FALSE;
9604 static void compose_attach_drag_received_cb (GtkWidget *widget,
9605 GdkDragContext *context,
9608 GtkSelectionData *data,
9613 Compose *compose = (Compose *)user_data;
9616 if (gdk_atom_name(data->type) &&
9617 !strcmp(gdk_atom_name(data->type), "text/uri-list")
9618 && gtk_drag_get_source_widget(context) !=
9619 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
9620 list = uri_list_extract_filenames((const gchar *)data->data);
9621 for (tmp = list; tmp != NULL; tmp = tmp->next) {
9622 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
9623 compose_attach_append
9624 (compose, (const gchar *)tmp->data,
9625 utf8_filename, NULL);
9626 g_free(utf8_filename);
9628 if (list) compose_changed_cb(NULL, compose);
9629 list_free_strings(list);
9631 } else if (gtk_drag_get_source_widget(context)
9632 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
9633 /* comes from our summaryview */
9634 SummaryView * summaryview = NULL;
9635 GSList * list = NULL, *cur = NULL;
9637 if (mainwindow_get_mainwindow())
9638 summaryview = mainwindow_get_mainwindow()->summaryview;
9641 list = summary_get_selected_msg_list(summaryview);
9643 for (cur = list; cur; cur = cur->next) {
9644 MsgInfo *msginfo = (MsgInfo *)cur->data;
9647 file = procmsg_get_message_file_full(msginfo,
9650 compose_attach_append(compose, (const gchar *)file,
9651 (const gchar *)file, "message/rfc822");
9659 static gboolean compose_drag_drop(GtkWidget *widget,
9660 GdkDragContext *drag_context,
9662 guint time, gpointer user_data)
9664 /* not handling this signal makes compose_insert_drag_received_cb
9669 static void compose_insert_drag_received_cb (GtkWidget *widget,
9670 GdkDragContext *drag_context,
9673 GtkSelectionData *data,
9678 Compose *compose = (Compose *)user_data;
9681 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
9683 if (gdk_atom_name(data->type) && !strcmp(gdk_atom_name(data->type), "text/uri-list")) {
9684 AlertValue val = G_ALERTDEFAULT;
9686 switch (prefs_common.compose_dnd_mode) {
9687 case COMPOSE_DND_ASK:
9688 val = alertpanel_full(_("Insert or attach?"),
9689 _("Do you want to insert the contents of the file(s) "
9690 "into the message body, or attach it to the email?"),
9691 GTK_STOCK_CANCEL, _("+_Insert"), _("_Attach"),
9692 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
9694 case COMPOSE_DND_INSERT:
9695 val = G_ALERTALTERNATE;
9697 case COMPOSE_DND_ATTACH:
9701 /* unexpected case */
9702 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
9705 if (val & G_ALERTDISABLE) {
9706 val &= ~G_ALERTDISABLE;
9707 /* remember what action to perform by default, only if we don't click Cancel */
9708 if (val == G_ALERTALTERNATE)
9709 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
9710 else if (val == G_ALERTOTHER)
9711 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
9714 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
9715 gtk_drag_finish(drag_context, FALSE, FALSE, time);
9717 } else if (val == G_ALERTOTHER) {
9718 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
9721 list = uri_list_extract_filenames((const gchar *)data->data);
9722 for (tmp = list; tmp != NULL; tmp = tmp->next) {
9723 compose_insert_file(compose, (const gchar *)tmp->data);
9725 list_free_strings(list);
9727 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9730 #if GTK_CHECK_VERSION(2, 8, 0)
9731 /* do nothing, handled by GTK */
9733 gchar *tmpfile = get_tmp_file();
9734 str_write_to_file((const gchar *)data->data, tmpfile);
9735 compose_insert_file(compose, tmpfile);
9738 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9742 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9745 static void compose_header_drag_received_cb (GtkWidget *widget,
9746 GdkDragContext *drag_context,
9749 GtkSelectionData *data,
9754 GtkEditable *entry = (GtkEditable *)user_data;
9755 gchar *email = (gchar *)data->data;
9757 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
9760 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
9761 gchar *decoded=g_new(gchar, strlen(email));
9764 email += strlen("mailto:");
9765 decode_uri(decoded, email); /* will fit */
9766 gtk_editable_delete_text(entry, 0, -1);
9767 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
9768 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9772 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9775 static void compose_toggle_return_receipt_cb(gpointer data, guint action,
9778 Compose *compose = (Compose *)data;
9780 if (GTK_CHECK_MENU_ITEM(widget)->active)
9781 compose->return_receipt = TRUE;
9783 compose->return_receipt = FALSE;
9786 static void compose_toggle_remove_refs_cb(gpointer data, guint action,
9789 Compose *compose = (Compose *)data;
9791 if (GTK_CHECK_MENU_ITEM(widget)->active)
9792 compose->remove_references = TRUE;
9794 compose->remove_references = FALSE;
9797 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
9799 ComposeHeaderEntry *headerentry)
9801 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
9802 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
9803 !(event->state & GDK_MODIFIER_MASK) &&
9804 (event->keyval == GDK_BackSpace) &&
9805 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
9806 gtk_container_remove
9807 (GTK_CONTAINER(headerentry->compose->header_table),
9808 headerentry->combo);
9809 gtk_container_remove
9810 (GTK_CONTAINER(headerentry->compose->header_table),
9811 headerentry->entry);
9812 headerentry->compose->header_list =
9813 g_slist_remove(headerentry->compose->header_list,
9815 g_free(headerentry);
9816 } else if (event->keyval == GDK_Tab) {
9817 if (headerentry->compose->header_last == headerentry) {
9818 /* Override default next focus, and give it to subject_entry
9819 * instead of notebook tabs
9821 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
9822 gtk_widget_grab_focus(headerentry->compose->subject_entry);
9829 static gboolean compose_headerentry_changed_cb(GtkWidget *entry,
9830 ComposeHeaderEntry *headerentry)
9832 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
9833 compose_create_header_entry(headerentry->compose);
9834 g_signal_handlers_disconnect_matched
9835 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
9836 0, 0, NULL, NULL, headerentry);
9838 /* Automatically scroll down */
9839 compose_show_first_last_header(headerentry->compose, FALSE);
9845 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
9847 GtkAdjustment *vadj;
9849 g_return_if_fail(compose);
9850 g_return_if_fail(GTK_IS_WIDGET(compose->header_table));
9851 g_return_if_fail(GTK_IS_VIEWPORT(compose->header_table->parent));
9853 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(compose->header_table->parent));
9854 gtk_adjustment_set_value(vadj, (show_first ? vadj->lower : vadj->upper));
9855 gtk_adjustment_changed(vadj);
9858 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
9859 const gchar *text, gint len, Compose *compose)
9861 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
9862 (G_OBJECT(compose->text), "paste_as_quotation"));
9865 g_return_if_fail(text != NULL);
9867 g_signal_handlers_block_by_func(G_OBJECT(buffer),
9868 G_CALLBACK(text_inserted),
9870 if (paste_as_quotation) {
9874 GtkTextIter start_iter;
9879 new_text = g_strndup(text, len);
9881 qmark = compose_quote_char_from_context(compose);
9883 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
9884 gtk_text_buffer_place_cursor(buffer, iter);
9886 pos = gtk_text_iter_get_offset(iter);
9888 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
9889 _("Quote format error at line %d."));
9890 quote_fmt_reset_vartable();
9892 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
9893 GINT_TO_POINTER(paste_as_quotation - 1));
9895 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
9896 gtk_text_buffer_place_cursor(buffer, iter);
9897 gtk_text_buffer_delete_mark(buffer, mark);
9899 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
9900 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
9901 compose_beautify_paragraph(compose, &start_iter, FALSE);
9902 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
9903 gtk_text_buffer_delete_mark(buffer, mark);
9905 if (strcmp(text, "\n") || compose->automatic_break
9906 || gtk_text_iter_starts_line(iter))
9907 gtk_text_buffer_insert(buffer, iter, text, len);
9909 debug_print("insert nowrap \\n\n");
9910 gtk_text_buffer_insert_with_tags_by_name(buffer,
9911 iter, text, len, "no_join", NULL);
9915 if (!paste_as_quotation) {
9916 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
9917 compose_beautify_paragraph(compose, iter, FALSE);
9918 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
9919 gtk_text_buffer_delete_mark(buffer, mark);
9922 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
9923 G_CALLBACK(text_inserted),
9925 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
9927 if (prefs_common.autosave &&
9928 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
9929 compose->draft_timeout_tag != -2 /* disabled while loading */)
9930 compose->draft_timeout_tag = g_timeout_add
9931 (500, (GtkFunction) compose_defer_auto_save_draft, compose);
9933 static gint compose_defer_auto_save_draft(Compose *compose)
9935 compose->draft_timeout_tag = -1;
9936 compose_draft_cb((gpointer)compose, COMPOSE_AUTO_SAVE, NULL);
9941 static void compose_check_all(Compose *compose)
9943 if (compose->gtkaspell)
9944 gtkaspell_check_all(compose->gtkaspell);
9947 static void compose_highlight_all(Compose *compose)
9949 if (compose->gtkaspell)
9950 gtkaspell_highlight_all(compose->gtkaspell);
9953 static void compose_check_backwards(Compose *compose)
9955 if (compose->gtkaspell)
9956 gtkaspell_check_backwards(compose->gtkaspell);
9958 GtkItemFactory *ifactory;
9959 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
9960 menu_set_sensitive(ifactory, "/Edit/Check backwards misspelled word", FALSE);
9961 menu_set_sensitive(ifactory, "/Edit/Forward to next misspelled word", FALSE);
9965 static void compose_check_forwards_go(Compose *compose)
9967 if (compose->gtkaspell)
9968 gtkaspell_check_forwards_go(compose->gtkaspell);
9970 GtkItemFactory *ifactory;
9971 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
9972 menu_set_sensitive(ifactory, "/Edit/Check backwards misspelled word", FALSE);
9973 menu_set_sensitive(ifactory, "/Edit/Forward to next misspelled word", FALSE);
9979 *\brief Guess originating forward account from MsgInfo and several
9980 * "common preference" settings. Return NULL if no guess.
9982 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
9984 PrefsAccount *account = NULL;
9986 g_return_val_if_fail(msginfo, NULL);
9987 g_return_val_if_fail(msginfo->folder, NULL);
9988 g_return_val_if_fail(msginfo->folder->prefs, NULL);
9990 if (msginfo->folder->prefs->enable_default_account)
9991 account = account_find_from_id(msginfo->folder->prefs->default_account);
9994 account = msginfo->folder->folder->account;
9996 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
9998 Xstrdup_a(to, msginfo->to, return NULL);
9999 extract_address(to);
10000 account = account_find_from_address(to);
10003 if (!account && prefs_common.forward_account_autosel) {
10004 gchar cc[BUFFSIZE];
10005 if (!procheader_get_header_from_msginfo
10006 (msginfo, cc,sizeof cc , "Cc:")) {
10007 gchar *buf = cc + strlen("Cc:");
10008 extract_address(buf);
10009 account = account_find_from_address(buf);
10013 if (!account && prefs_common.forward_account_autosel) {
10014 gchar deliveredto[BUFFSIZE];
10015 if (!procheader_get_header_from_msginfo
10016 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
10017 gchar *buf = deliveredto + strlen("Delivered-To:");
10018 extract_address(buf);
10019 account = account_find_from_address(buf);
10026 gboolean compose_close(Compose *compose)
10030 if (!g_mutex_trylock(compose->mutex)) {
10031 /* we have to wait for the (possibly deferred by auto-save)
10032 * drafting to be done, before destroying the compose under
10034 debug_print("waiting for drafting to finish...\n");
10035 compose_allow_user_actions(compose, FALSE);
10036 g_timeout_add (500, (GSourceFunc) compose_close, compose);
10039 g_return_val_if_fail(compose, FALSE);
10040 gtkut_widget_get_uposition(compose->window, &x, &y);
10041 prefs_common.compose_x = x;
10042 prefs_common.compose_y = y;
10043 g_mutex_unlock(compose->mutex);
10044 compose_destroy(compose);
10049 * Add entry field for each address in list.
10050 * \param compose E-Mail composition object.
10051 * \param listAddress List of (formatted) E-Mail addresses.
10053 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
10056 node = listAddress;
10058 addr = ( gchar * ) node->data;
10059 compose_entry_append( compose, addr, COMPOSE_TO );
10060 node = g_list_next( node );
10064 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
10065 guint action, gboolean opening_multiple)
10067 gchar *body = NULL;
10068 GSList *new_msglist = NULL;
10069 MsgInfo *tmp_msginfo = NULL;
10070 gboolean originally_enc = FALSE;
10071 Compose *compose = NULL;
10073 g_return_if_fail(msgview != NULL);
10075 g_return_if_fail(msginfo_list != NULL);
10077 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
10078 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
10079 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
10081 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
10082 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
10083 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
10084 orig_msginfo, mimeinfo);
10085 if (tmp_msginfo != NULL) {
10086 new_msglist = g_slist_append(NULL, tmp_msginfo);
10088 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
10089 tmp_msginfo->folder = orig_msginfo->folder;
10090 tmp_msginfo->msgnum = orig_msginfo->msgnum;
10091 if (orig_msginfo->tags)
10092 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
10097 if (!opening_multiple)
10098 body = messageview_get_selection(msgview);
10101 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
10102 procmsg_msginfo_free(tmp_msginfo);
10103 g_slist_free(new_msglist);
10105 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
10107 if (compose && originally_enc) {
10108 compose_force_encryption(compose, compose->account, FALSE);
10114 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
10117 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
10118 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
10119 GSList *cur = msginfo_list;
10120 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
10121 "messages. Opening the windows "
10122 "could take some time. Do you "
10123 "want to continue?"),
10124 g_slist_length(msginfo_list));
10125 if (g_slist_length(msginfo_list) > 9
10126 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
10127 != G_ALERTALTERNATE) {
10132 /* We'll open multiple compose windows */
10133 /* let the WM place the next windows */
10134 compose_force_window_origin = FALSE;
10135 for (; cur; cur = cur->next) {
10137 tmplist.data = cur->data;
10138 tmplist.next = NULL;
10139 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
10141 compose_force_window_origin = TRUE;
10143 /* forwarding multiple mails as attachments is done via a
10144 * single compose window */
10145 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
10149 void compose_set_position(Compose *compose, gint pos)
10151 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10153 gtkut_text_view_set_position(text, pos);
10156 gboolean compose_search_string(Compose *compose,
10157 const gchar *str, gboolean case_sens)
10159 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10161 return gtkut_text_view_search_string(text, str, case_sens);
10164 gboolean compose_search_string_backward(Compose *compose,
10165 const gchar *str, gboolean case_sens)
10167 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10169 return gtkut_text_view_search_string_backward(text, str, case_sens);
10172 /* allocate a msginfo structure and populate its data from a compose data structure */
10173 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
10175 MsgInfo *newmsginfo;
10177 gchar buf[BUFFSIZE];
10179 g_return_val_if_fail( compose != NULL, NULL );
10181 newmsginfo = procmsg_msginfo_new();
10184 get_rfc822_date(buf, sizeof(buf));
10185 newmsginfo->date = g_strdup(buf);
10188 if (compose->from_name) {
10189 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
10190 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
10194 if (compose->subject_entry)
10195 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
10197 /* to, cc, reply-to, newsgroups */
10198 for (list = compose->header_list; list; list = list->next) {
10199 gchar *header = gtk_editable_get_chars(
10201 GTK_BIN(((ComposeHeaderEntry *)list->data)->combo)->child), 0, -1);
10202 gchar *entry = gtk_editable_get_chars(
10203 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
10205 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
10206 if ( newmsginfo->to == NULL ) {
10207 newmsginfo->to = g_strdup(entry);
10208 } else if (entry && *entry) {
10209 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
10210 g_free(newmsginfo->to);
10211 newmsginfo->to = tmp;
10214 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
10215 if ( newmsginfo->cc == NULL ) {
10216 newmsginfo->cc = g_strdup(entry);
10217 } else if (entry && *entry) {
10218 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
10219 g_free(newmsginfo->cc);
10220 newmsginfo->cc = tmp;
10223 if ( strcasecmp(header,
10224 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
10225 if ( newmsginfo->newsgroups == NULL ) {
10226 newmsginfo->newsgroups = g_strdup(entry);
10227 } else if (entry && *entry) {
10228 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
10229 g_free(newmsginfo->newsgroups);
10230 newmsginfo->newsgroups = tmp;
10238 /* other data is unset */
10244 /* update compose's dictionaries from folder dict settings */
10245 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
10246 FolderItem *folder_item)
10248 g_return_if_fail(compose != NULL);
10250 if (compose->gtkaspell && folder_item && folder_item->prefs) {
10251 FolderItemPrefs *prefs = folder_item->prefs;
10253 if (prefs->enable_default_dictionary)
10254 gtkaspell_change_dict(compose->gtkaspell,
10255 prefs->default_dictionary, FALSE);
10256 if (folder_item->prefs->enable_default_alt_dictionary)
10257 gtkaspell_change_alt_dict(compose->gtkaspell,
10258 prefs->default_alt_dictionary);
10259 if (prefs->enable_default_dictionary
10260 || prefs->enable_default_alt_dictionary)
10261 compose_spell_menu_changed(compose);