2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2007 Hiroyuki Yamamoto and the Claws Mail team
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
26 #ifndef PANGO_ENABLE_ENGINE
27 # define PANGO_ENABLE_ENGINE
31 #include <glib/gi18n.h>
32 #include <gdk/gdkkeysyms.h>
33 #include <gtk/gtkmain.h>
34 #include <gtk/gtkmenu.h>
35 #include <gtk/gtkmenuitem.h>
36 #include <gtk/gtkitemfactory.h>
37 #include <gtk/gtkcheckmenuitem.h>
38 #include <gtk/gtkoptionmenu.h>
39 #include <gtk/gtkwidget.h>
40 #include <gtk/gtkvpaned.h>
41 #include <gtk/gtkentry.h>
42 #include <gtk/gtkeditable.h>
43 #include <gtk/gtkwindow.h>
44 #include <gtk/gtksignal.h>
45 #include <gtk/gtkvbox.h>
46 #include <gtk/gtkcontainer.h>
47 #include <gtk/gtkhandlebox.h>
48 #include <gtk/gtktoolbar.h>
49 #include <gtk/gtktable.h>
50 #include <gtk/gtkhbox.h>
51 #include <gtk/gtklabel.h>
52 #include <gtk/gtkscrolledwindow.h>
53 #include <gtk/gtktreeview.h>
54 #include <gtk/gtkliststore.h>
55 #include <gtk/gtktreeselection.h>
56 #include <gtk/gtktreemodel.h>
58 #include <gtk/gtkdnd.h>
59 #include <gtk/gtkclipboard.h>
60 #include <pango/pango-break.h>
65 #include <sys/types.h>
71 # include <sys/wait.h>
75 #ifndef G_OS_WIN32 /* fixme we should have a configure test. */
79 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
86 #include "mainwindow.h"
88 #include "addressbook.h"
89 #include "folderview.h"
92 #include "stock_pixmap.h"
93 #include "send_message.h"
96 #include "customheader.h"
97 #include "prefs_common.h"
98 #include "prefs_account.h"
102 #include "procheader.h"
103 #include "procmime.h"
104 #include "statusbar.h"
107 #include "quoted-printable.h"
108 #include "codeconv.h"
110 #include "gtkutils.h"
112 #include "alertpanel.h"
113 #include "manage_window.h"
114 #include "gtkshruler.h"
116 #include "addr_compl.h"
117 #include "quote_fmt.h"
119 #include "foldersel.h"
122 #include "message_search.h"
123 #include "combobox.h"
138 #define N_ATTACH_COLS (N_COL_COLUMNS)
142 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE,
143 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER,
144 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER,
145 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD,
146 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD,
147 COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE,
148 COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE,
149 COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE,
150 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER,
151 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER,
152 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD,
153 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD,
154 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE,
155 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE_N,
156 COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END
157 } ComposeCallAdvancedAction;
161 PRIORITY_HIGHEST = 1,
170 COMPOSE_INSERT_SUCCESS,
171 COMPOSE_INSERT_READ_ERROR,
172 COMPOSE_INSERT_INVALID_CHARACTER,
173 COMPOSE_INSERT_NO_FILE
174 } ComposeInsertResult;
178 COMPOSE_WRITE_FOR_SEND,
179 COMPOSE_WRITE_FOR_STORE
184 COMPOSE_QUOTE_FORCED,
189 #define B64_LINE_SIZE 57
190 #define B64_BUFFSIZE 77
192 #define MAX_REFERENCES_LEN 999
194 static GList *compose_list = NULL;
196 static Compose *compose_generic_new (PrefsAccount *account,
199 GPtrArray *attach_files,
200 GList *listAddress );
202 static Compose *compose_create (PrefsAccount *account,
207 static void compose_entry_mark_default_to (Compose *compose,
208 const gchar *address);
209 static Compose *compose_followup_and_reply_to (MsgInfo *msginfo,
210 ComposeQuoteMode quote_mode,
214 static Compose *compose_forward_multiple (PrefsAccount *account,
215 GSList *msginfo_list);
216 static Compose *compose_reply (MsgInfo *msginfo,
217 ComposeQuoteMode quote_mode,
222 static Compose *compose_reply_mode (ComposeMode mode,
223 GSList *msginfo_list,
225 static void compose_template_apply_fields(Compose *compose, Template *tmpl);
226 static void compose_update_privacy_systems_menu(Compose *compose);
228 static GtkWidget *compose_account_option_menu_create
230 static void compose_set_out_encoding (Compose *compose);
231 static void compose_set_template_menu (Compose *compose);
232 static void compose_template_apply (Compose *compose,
235 static void compose_destroy (Compose *compose);
237 static void compose_entries_set (Compose *compose,
238 const gchar *mailto);
239 static gint compose_parse_header (Compose *compose,
241 static gchar *compose_parse_references (const gchar *ref,
244 static gchar *compose_quote_fmt (Compose *compose,
250 gboolean need_unescape,
251 const gchar *err_msg);
253 static void compose_reply_set_entry (Compose *compose,
259 followup_and_reply_to);
260 static void compose_reedit_set_entry (Compose *compose,
263 static void compose_insert_sig (Compose *compose,
265 static gchar *compose_get_signature_str (Compose *compose);
266 static ComposeInsertResult compose_insert_file (Compose *compose,
269 static gboolean compose_attach_append (Compose *compose,
272 const gchar *content_type);
273 static void compose_attach_parts (Compose *compose,
276 static gboolean compose_beautify_paragraph (Compose *compose,
277 GtkTextIter *par_iter,
279 static void compose_wrap_all (Compose *compose);
280 static void compose_wrap_all_full (Compose *compose,
283 static void compose_set_title (Compose *compose);
284 static void compose_select_account (Compose *compose,
285 PrefsAccount *account,
288 static PrefsAccount *compose_current_mail_account(void);
289 /* static gint compose_send (Compose *compose); */
290 static gboolean compose_check_for_valid_recipient
292 static gboolean compose_check_entries (Compose *compose,
293 gboolean check_everything);
294 static gint compose_write_to_file (Compose *compose,
297 gboolean attach_parts);
298 static gint compose_write_body_to_file (Compose *compose,
300 static gint compose_remove_reedit_target (Compose *compose,
302 static void compose_remove_draft (Compose *compose);
303 static gint compose_queue_sub (Compose *compose,
307 gboolean check_subject,
308 gboolean remove_reedit_target);
309 static void compose_add_attachments (Compose *compose,
311 static gchar *compose_get_header (Compose *compose);
313 static void compose_convert_header (Compose *compose,
318 gboolean addr_field);
320 static void compose_attach_info_free (AttachInfo *ainfo);
321 static void compose_attach_remove_selected (Compose *compose);
323 static void compose_attach_property (Compose *compose);
324 static void compose_attach_property_create (gboolean *cancelled);
325 static void attach_property_ok (GtkWidget *widget,
326 gboolean *cancelled);
327 static void attach_property_cancel (GtkWidget *widget,
328 gboolean *cancelled);
329 static gint attach_property_delete_event (GtkWidget *widget,
331 gboolean *cancelled);
332 static gboolean attach_property_key_pressed (GtkWidget *widget,
334 gboolean *cancelled);
336 static void compose_exec_ext_editor (Compose *compose);
338 static gint compose_exec_ext_editor_real (const gchar *file);
339 static gboolean compose_ext_editor_kill (Compose *compose);
340 static gboolean compose_input_cb (GIOChannel *source,
341 GIOCondition condition,
343 static void compose_set_ext_editor_sensitive (Compose *compose,
345 #endif /* G_OS_UNIX */
347 static void compose_undo_state_changed (UndoMain *undostruct,
352 static void compose_create_header_entry (Compose *compose);
353 static void compose_add_header_entry (Compose *compose, const gchar *header, gchar *text);
354 static void compose_remove_header_entries(Compose *compose);
356 static void compose_update_priority_menu_item(Compose * compose);
358 static void compose_spell_menu_changed (void *data);
360 static void compose_add_field_list ( Compose *compose,
361 GList *listAddress );
363 /* callback functions */
365 static gboolean compose_edit_size_alloc (GtkEditable *widget,
366 GtkAllocation *allocation,
367 GtkSHRuler *shruler);
368 static void account_activated (GtkComboBox *optmenu,
370 static void attach_selected (GtkTreeView *tree_view,
371 GtkTreePath *tree_path,
372 GtkTreeViewColumn *column,
374 static gboolean attach_button_pressed (GtkWidget *widget,
375 GdkEventButton *event,
377 static gboolean attach_key_pressed (GtkWidget *widget,
380 static void compose_send_cb (gpointer data,
383 static void compose_send_later_cb (gpointer data,
387 static void compose_draft_cb (gpointer data,
391 static void compose_attach_cb (gpointer data,
394 static void compose_insert_file_cb (gpointer data,
397 static void compose_insert_sig_cb (gpointer data,
401 static void compose_close_cb (gpointer data,
405 static void compose_set_encoding_cb (gpointer data,
409 static void compose_address_cb (gpointer data,
412 static void compose_template_activate_cb(GtkWidget *widget,
415 static void compose_ext_editor_cb (gpointer data,
419 static gint compose_delete_cb (GtkWidget *widget,
423 static void compose_undo_cb (Compose *compose);
424 static void compose_redo_cb (Compose *compose);
425 static void compose_cut_cb (Compose *compose);
426 static void compose_copy_cb (Compose *compose);
427 static void compose_paste_cb (Compose *compose);
428 static void compose_paste_as_quote_cb (Compose *compose);
429 static void compose_paste_no_wrap_cb (Compose *compose);
430 static void compose_paste_wrap_cb (Compose *compose);
431 static void compose_allsel_cb (Compose *compose);
433 static void compose_advanced_action_cb (Compose *compose,
434 ComposeCallAdvancedAction action);
436 static void compose_grab_focus_cb (GtkWidget *widget,
439 static void compose_changed_cb (GtkTextBuffer *textbuf,
442 static void compose_wrap_cb (gpointer data,
445 static void compose_find_cb (gpointer data,
448 static void compose_toggle_autowrap_cb (gpointer data,
452 static void compose_toggle_ruler_cb (gpointer data,
455 static void compose_toggle_sign_cb (gpointer data,
458 static void compose_toggle_encrypt_cb (gpointer data,
461 static void compose_set_privacy_system_cb(GtkWidget *widget,
463 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn);
464 static void activate_privacy_system (Compose *compose,
465 PrefsAccount *account,
467 static void compose_use_signing(Compose *compose, gboolean use_signing);
468 static void compose_use_encryption(Compose *compose, gboolean use_encryption);
469 static void compose_toggle_return_receipt_cb(gpointer data, guint action,
471 static void compose_toggle_remove_refs_cb(gpointer data, guint action,
473 static void compose_set_priority_cb (gpointer data,
476 static void compose_reply_change_mode (gpointer data,
480 static void compose_attach_drag_received_cb (GtkWidget *widget,
481 GdkDragContext *drag_context,
484 GtkSelectionData *data,
488 static void compose_insert_drag_received_cb (GtkWidget *widget,
489 GdkDragContext *drag_context,
492 GtkSelectionData *data,
496 static void compose_header_drag_received_cb (GtkWidget *widget,
497 GdkDragContext *drag_context,
500 GtkSelectionData *data,
505 static gboolean compose_drag_drop (GtkWidget *widget,
506 GdkDragContext *drag_context,
508 guint time, gpointer user_data);
510 static void text_inserted (GtkTextBuffer *buffer,
515 static Compose *compose_generic_reply(MsgInfo *msginfo,
516 ComposeQuoteMode quote_mode,
520 gboolean followup_and_reply_to,
523 static gboolean compose_headerentry_changed_cb (GtkWidget *entry,
524 ComposeHeaderEntry *headerentry);
525 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
527 ComposeHeaderEntry *headerentry);
529 static void compose_show_first_last_header (Compose *compose, gboolean show_first);
531 static void compose_allow_user_actions (Compose *compose, gboolean allow);
534 static void compose_check_all (Compose *compose);
535 static void compose_highlight_all (Compose *compose);
536 static void compose_check_backwards (Compose *compose);
537 static void compose_check_forwards_go (Compose *compose);
540 static gint compose_defer_auto_save_draft (Compose *compose);
541 static PrefsAccount *compose_guess_forward_account_from_msginfo (MsgInfo *msginfo);
543 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose);
546 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
547 FolderItem *folder_item);
550 static GtkItemFactoryEntry compose_popup_entries[] =
552 {N_("/_Add..."), NULL, compose_attach_cb, 0, NULL},
553 {N_("/_Remove"), NULL, compose_attach_remove_selected, 0, NULL},
554 {"/---", NULL, NULL, 0, "<Separator>"},
555 {N_("/_Properties..."), NULL, compose_attach_property, 0, NULL}
558 static GtkItemFactoryEntry compose_entries[] =
560 {N_("/_Message"), NULL, NULL, 0, "<Branch>"},
561 {N_("/_Message/S_end"), "<control>Return",
562 compose_send_cb, 0, NULL},
563 {N_("/_Message/Send _later"), "<shift><control>S",
564 compose_send_later_cb, 0, NULL},
565 {N_("/_Message/---"), NULL, NULL, 0, "<Separator>"},
566 {N_("/_Message/_Attach file"), "<control>M", compose_attach_cb, 0, NULL},
567 {N_("/_Message/_Insert file"), "<control>I", compose_insert_file_cb, 0, NULL},
568 {N_("/_Message/Insert si_gnature"), "<control>G", compose_insert_sig_cb, 0, NULL},
569 {N_("/_Message/---"), NULL, NULL, 0, "<Separator>"},
570 {N_("/_Message/_Save"),
571 "<control>S", compose_draft_cb, COMPOSE_KEEP_EDITING, NULL},
572 {N_("/_Message/---"), NULL, NULL, 0, "<Separator>"},
573 {N_("/_Message/_Close"), "<control>W", compose_close_cb, 0, NULL},
575 {N_("/_Edit"), NULL, NULL, 0, "<Branch>"},
576 {N_("/_Edit/_Undo"), "<control>Z", compose_undo_cb, 0, NULL},
577 {N_("/_Edit/_Redo"), "<control>Y", compose_redo_cb, 0, NULL},
578 {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
579 {N_("/_Edit/Cu_t"), "<control>X", compose_cut_cb, 0, NULL},
580 {N_("/_Edit/_Copy"), "<control>C", compose_copy_cb, 0, NULL},
581 {N_("/_Edit/_Paste"), "<control>V", compose_paste_cb, 0, NULL},
582 {N_("/_Edit/Special paste"), NULL, NULL, 0, "<Branch>"},
583 {N_("/_Edit/Special paste/as _quotation"),
584 NULL, compose_paste_as_quote_cb, 0, NULL},
585 {N_("/_Edit/Special paste/_wrapped"),
586 NULL, compose_paste_wrap_cb, 0, NULL},
587 {N_("/_Edit/Special paste/_unwrapped"),
588 NULL, compose_paste_no_wrap_cb, 0, NULL},
589 {N_("/_Edit/Select _all"), "<control>A", compose_allsel_cb, 0, NULL},
590 {N_("/_Edit/A_dvanced"), NULL, NULL, 0, "<Branch>"},
591 {N_("/_Edit/A_dvanced/Move a character backward"),
593 compose_advanced_action_cb,
594 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER,
596 {N_("/_Edit/A_dvanced/Move a character forward"),
598 compose_advanced_action_cb,
599 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER,
601 {N_("/_Edit/A_dvanced/Move a word backward"),
603 compose_advanced_action_cb,
604 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD,
606 {N_("/_Edit/A_dvanced/Move a word forward"),
608 compose_advanced_action_cb,
609 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD,
611 {N_("/_Edit/A_dvanced/Move to beginning of line"),
612 NULL, /* "<control>A" */
613 compose_advanced_action_cb,
614 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE,
616 {N_("/_Edit/A_dvanced/Move to end of line"),
618 compose_advanced_action_cb,
619 COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE,
621 {N_("/_Edit/A_dvanced/Move to previous line"),
623 compose_advanced_action_cb,
624 COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE,
626 {N_("/_Edit/A_dvanced/Move to next line"),
628 compose_advanced_action_cb,
629 COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE,
631 {N_("/_Edit/A_dvanced/Delete a character backward"),
633 compose_advanced_action_cb,
634 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER,
636 {N_("/_Edit/A_dvanced/Delete a character forward"),
638 compose_advanced_action_cb,
639 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER,
641 {N_("/_Edit/A_dvanced/Delete a word backward"),
642 NULL, /* "<control>W" */
643 compose_advanced_action_cb,
644 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD,
646 {N_("/_Edit/A_dvanced/Delete a word forward"),
647 NULL, /* "<alt>D", */
648 compose_advanced_action_cb,
649 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD,
651 {N_("/_Edit/A_dvanced/Delete line"),
653 compose_advanced_action_cb,
654 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE,
656 {N_("/_Edit/A_dvanced/Delete entire line"),
658 compose_advanced_action_cb,
659 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE_N,
661 {N_("/_Edit/A_dvanced/Delete to end of line"),
663 compose_advanced_action_cb,
664 COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END,
666 {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
668 "<control>F", compose_find_cb, 0, NULL},
669 {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
670 {N_("/_Edit/_Wrap current paragraph"),
671 "<control>L", compose_wrap_cb, 0, NULL},
672 {N_("/_Edit/Wrap all long _lines"),
673 "<control><alt>L", compose_wrap_cb, 1, NULL},
674 {N_("/_Edit/Aut_o wrapping"), "<shift><control>L", compose_toggle_autowrap_cb, 0, "<ToggleItem>"},
675 {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
676 {N_("/_Edit/Edit with e_xternal editor"),
677 "<shift><control>X", compose_ext_editor_cb, 0, NULL},
679 {N_("/_Spelling"), NULL, NULL, 0, "<Branch>"},
680 {N_("/_Spelling/_Check all or check selection"),
681 NULL, compose_check_all, 0, NULL},
682 {N_("/_Spelling/_Highlight all misspelled words"),
683 NULL, compose_highlight_all, 0, NULL},
684 {N_("/_Spelling/Check _backwards misspelled word"),
685 NULL, compose_check_backwards , 0, NULL},
686 {N_("/_Spelling/_Forward to next misspelled word"),
687 NULL, compose_check_forwards_go, 0, NULL},
688 {N_("/_Spelling/---"), NULL, NULL, 0, "<Separator>"},
689 {N_("/_Spelling/Options"),
690 NULL, NULL, 0, "<Branch>"},
692 {N_("/_Options"), NULL, NULL, 0, "<Branch>"},
693 {N_("/_Options/Reply _mode"), NULL, NULL, 0, "<Branch>"},
694 {N_("/_Options/Reply _mode/_Normal"), NULL, compose_reply_change_mode, COMPOSE_REPLY, "<RadioItem>"},
695 {N_("/_Options/Reply _mode/_All"), NULL, compose_reply_change_mode, COMPOSE_REPLY_TO_ALL, "/Options/Reply mode/Normal"},
696 {N_("/_Options/Reply _mode/_Sender"), NULL, compose_reply_change_mode, COMPOSE_REPLY_TO_SENDER, "/Options/Reply mode/Normal"},
697 {N_("/_Options/Reply _mode/_Mailing-list"), NULL, compose_reply_change_mode, COMPOSE_REPLY_TO_LIST, "/Options/Reply mode/Normal"},
698 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
699 {N_("/_Options/Privacy _System"), NULL, NULL, 0, "<Branch>"},
700 {N_("/_Options/Privacy _System/None"), NULL, NULL, 0, "<RadioItem>"},
701 {N_("/_Options/Si_gn"), NULL, compose_toggle_sign_cb , 0, "<ToggleItem>"},
702 {N_("/_Options/_Encrypt"), NULL, compose_toggle_encrypt_cb, 0, "<ToggleItem>"},
703 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
704 {N_("/_Options/_Priority"), NULL, NULL, 0, "<Branch>"},
705 {N_("/_Options/Priority/_Highest"), NULL, compose_set_priority_cb, PRIORITY_HIGHEST, "<RadioItem>"},
706 {N_("/_Options/Priority/Hi_gh"), NULL, compose_set_priority_cb, PRIORITY_HIGH, "/Options/Priority/Highest"},
707 {N_("/_Options/Priority/_Normal"), NULL, compose_set_priority_cb, PRIORITY_NORMAL, "/Options/Priority/Highest"},
708 {N_("/_Options/Priority/Lo_w"), NULL, compose_set_priority_cb, PRIORITY_LOW, "/Options/Priority/Highest"},
709 {N_("/_Options/Priority/_Lowest"), NULL, compose_set_priority_cb, PRIORITY_LOWEST, "/Options/Priority/Highest"},
710 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
711 {N_("/_Options/_Request Return Receipt"), NULL, compose_toggle_return_receipt_cb, 0, "<ToggleItem>"},
712 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
713 {N_("/_Options/Remo_ve references"), NULL, compose_toggle_remove_refs_cb, 0, "<ToggleItem>"},
714 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
716 #define ENC_ACTION(action) \
717 NULL, compose_set_encoding_cb, action, \
718 "/Options/Character encoding/Automatic"
720 {N_("/_Options/Character _encoding"), NULL, NULL, 0, "<Branch>"},
721 {N_("/_Options/Character _encoding/_Automatic"),
722 NULL, compose_set_encoding_cb, C_AUTO, "<RadioItem>"},
723 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
725 {N_("/_Options/Character _encoding/7bit ascii (US-ASC_II)"),
726 ENC_ACTION(C_US_ASCII)},
727 {N_("/_Options/Character _encoding/Unicode (_UTF-8)"),
728 ENC_ACTION(C_UTF_8)},
729 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
731 {N_("/_Options/Character _encoding/Western European"), NULL, NULL, 0, "<Branch>"},
732 {N_("/_Options/Character _encoding/Western European/ISO-8859-_1"),
733 ENC_ACTION(C_ISO_8859_1)},
734 {N_("/_Options/Character _encoding/Western European/ISO-8859-15"),
735 ENC_ACTION(C_ISO_8859_15)},
736 {N_("/_Options/Character _encoding/Western European/Windows-1252"),
737 ENC_ACTION(C_WINDOWS_1252)},
739 {N_("/_Options/Character _encoding/Central European (ISO-8859-_2)"),
740 ENC_ACTION(C_ISO_8859_2)},
742 {N_("/_Options/Character _encoding/Baltic"), NULL, NULL, 0, "<Branch>"},
743 {N_("/_Options/Character _encoding/Baltic/ISO-8859-13"),
744 ENC_ACTION(C_ISO_8859_13)},
745 {N_("/_Options/Character _encoding/Baltic/ISO-8859-_4"),
746 ENC_ACTION(C_ISO_8859_4)},
748 {N_("/_Options/Character _encoding/Greek (ISO-8859-_7)"),
749 ENC_ACTION(C_ISO_8859_7)},
751 {N_("/_Options/Character _encoding/Hebrew"), NULL, NULL, 0, "<Branch>"},
752 {N_("/_Options/Character _encoding/Hebrew/ISO-8859-_8"),
753 ENC_ACTION(C_ISO_8859_8)},
754 {N_("/_Options/Character _encoding/Hebrew/Windows-1255"),
755 ENC_ACTION(C_WINDOWS_1255)},
757 {N_("/_Options/Character _encoding/Arabic"), NULL, NULL, 0, "<Branch>"},
758 {N_("/_Options/Character _encoding/Arabic/ISO-8859-_6"),
759 ENC_ACTION(C_ISO_8859_6)},
760 {N_("/_Options/Character _encoding/Arabic/Windows-1256"),
761 ENC_ACTION(C_CP1256)},
763 {N_("/_Options/Character _encoding/Turkish (ISO-8859-_9)"),
764 ENC_ACTION(C_ISO_8859_9)},
766 {N_("/_Options/Character _encoding/Cyrillic"), NULL, NULL, 0, "<Branch>"},
767 {N_("/_Options/Character _encoding/Cyrillic/ISO-8859-_5"),
768 ENC_ACTION(C_ISO_8859_5)},
769 {N_("/_Options/Character _encoding/Cyrillic/KOI8-_R"),
770 ENC_ACTION(C_KOI8_R)},
771 {N_("/_Options/Character _encoding/Cyrillic/KOI8-U"),
772 ENC_ACTION(C_KOI8_U)},
773 {N_("/_Options/Character _encoding/Cyrillic/Windows-1251"),
774 ENC_ACTION(C_WINDOWS_1251)},
776 {N_("/_Options/Character _encoding/Japanese"), NULL, NULL, 0, "<Branch>"},
777 {N_("/_Options/Character _encoding/Japanese/ISO-2022-_JP"),
778 ENC_ACTION(C_ISO_2022_JP)},
779 {N_("/_Options/Character _encoding/Japanese/ISO-2022-JP-2"),
780 ENC_ACTION(C_ISO_2022_JP_2)},
781 {N_("/_Options/Character _encoding/Japanese/_EUC-JP"),
782 ENC_ACTION(C_EUC_JP)},
783 {N_("/_Options/Character _encoding/Japanese/_Shift__JIS"),
784 ENC_ACTION(C_SHIFT_JIS)},
786 {N_("/_Options/Character _encoding/Chinese"), NULL, NULL, 0, "<Branch>"},
787 {N_("/_Options/Character _encoding/Chinese/Simplified (_GB2312)"),
788 ENC_ACTION(C_GB2312)},
789 {N_("/_Options/Character _encoding/Chinese/Simplified (GBK)"),
791 {N_("/_Options/Character _encoding/Chinese/Traditional (_Big5)"),
793 {N_("/_Options/Character _encoding/Chinese/Traditional (EUC-_TW)"),
794 ENC_ACTION(C_EUC_TW)},
796 {N_("/_Options/Character _encoding/Korean"), NULL, NULL, 0, "<Branch>"},
797 {N_("/_Options/Character _encoding/Korean/EUC-_KR"),
798 ENC_ACTION(C_EUC_KR)},
799 {N_("/_Options/Character _encoding/Korean/ISO-2022-KR"),
800 ENC_ACTION(C_ISO_2022_KR)},
802 {N_("/_Options/Character _encoding/Thai"), NULL, NULL, 0, "<Branch>"},
803 {N_("/_Options/Character _encoding/Thai/TIS-620"),
804 ENC_ACTION(C_TIS_620)},
805 {N_("/_Options/Character _encoding/Thai/Windows-874"),
806 ENC_ACTION(C_WINDOWS_874)},
808 {N_("/_Tools"), NULL, NULL, 0, "<Branch>"},
809 {N_("/_Tools/Show _ruler"), NULL, compose_toggle_ruler_cb, 0, "<ToggleItem>"},
810 {N_("/_Tools/_Address book"), "<shift><control>A", compose_address_cb , 0, NULL},
811 {N_("/_Tools/_Template"), NULL, NULL, 0, "<Branch>"},
812 {N_("/_Tools/Actio_ns"), NULL, NULL, 0, "<Branch>"},
813 {N_("/_Help"), NULL, NULL, 0, "<Branch>"},
814 {N_("/_Help/_About"), NULL, about_show, 0, NULL}
817 static GtkTargetEntry compose_mime_types[] =
819 {"text/uri-list", 0, 0},
820 {"UTF8_STRING", 0, 0},
824 static gboolean compose_put_existing_to_front(MsgInfo *info)
826 GList *compose_list = compose_get_compose_list();
830 for (elem = compose_list; elem != NULL && elem->data != NULL;
832 Compose *c = (Compose*)elem->data;
834 if (!c->targetinfo || !c->targetinfo->msgid ||
838 if (!strcmp(c->targetinfo->msgid, info->msgid)) {
839 gtkut_window_popup(c->window);
847 static GdkColor quote_color1 =
848 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
849 static GdkColor quote_color2 =
850 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
851 static GdkColor quote_color3 =
852 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
854 static GdkColor quote_bgcolor1 =
855 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
856 static GdkColor quote_bgcolor2 =
857 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
858 static GdkColor quote_bgcolor3 =
859 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
861 static GdkColor signature_color = {
868 static GdkColor uri_color = {
875 static void compose_create_tags(GtkTextView *text, Compose *compose)
877 GtkTextBuffer *buffer;
878 GdkColor black = {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
884 buffer = gtk_text_view_get_buffer(text);
886 if (prefs_common.enable_color) {
887 /* grab the quote colors, converting from an int to a GdkColor */
888 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
890 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
892 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
894 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_bgcol,
896 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_bgcol,
898 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_bgcol,
900 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
902 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
905 signature_color = quote_color1 = quote_color2 = quote_color3 =
906 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
909 if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
910 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
911 "foreground-gdk", "e_color1,
912 "paragraph-background-gdk", "e_bgcolor1,
914 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
915 "foreground-gdk", "e_color2,
916 "paragraph-background-gdk", "e_bgcolor2,
918 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
919 "foreground-gdk", "e_color3,
920 "paragraph-background-gdk", "e_bgcolor3,
923 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
924 "foreground-gdk", "e_color1,
926 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
927 "foreground-gdk", "e_color2,
929 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
930 "foreground-gdk", "e_color3,
934 compose->signature_tag = gtk_text_buffer_create_tag(buffer, "signature",
935 "foreground-gdk", &signature_color,
938 compose->uri_tag = gtk_text_buffer_create_tag(buffer, "link",
939 "foreground-gdk", &uri_color,
941 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
942 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
944 color[0] = quote_color1;
945 color[1] = quote_color2;
946 color[2] = quote_color3;
947 color[3] = quote_bgcolor1;
948 color[4] = quote_bgcolor2;
949 color[5] = quote_bgcolor3;
950 color[6] = signature_color;
951 color[7] = uri_color;
952 cmap = gdk_drawable_get_colormap(compose->window->window);
953 gdk_colormap_alloc_colors(cmap, color, 8, FALSE, TRUE, success);
955 for (i = 0; i < 8; i++) {
956 if (success[i] == FALSE) {
959 g_warning("Compose: color allocation failed.\n");
960 style = gtk_widget_get_style(GTK_WIDGET(text));
961 quote_color1 = quote_color2 = quote_color3 =
962 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 =
963 signature_color = uri_color = black;
968 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
969 GPtrArray *attach_files)
971 return compose_generic_new(account, mailto, NULL, attach_files, NULL);
974 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
976 return compose_generic_new(account, mailto, item, NULL, NULL);
979 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
981 return compose_generic_new( account, NULL, NULL, NULL, listAddress );
984 #define SCROLL_TO_CURSOR(compose) { \
985 GtkTextMark *cmark = gtk_text_buffer_get_insert( \
986 gtk_text_view_get_buffer( \
987 GTK_TEXT_VIEW(compose->text))); \
988 gtk_text_view_scroll_mark_onscreen( \
989 GTK_TEXT_VIEW(compose->text), \
993 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
994 GPtrArray *attach_files, GList *listAddress )
997 GtkTextView *textview;
998 GtkTextBuffer *textbuf;
1000 GtkItemFactory *ifactory;
1001 const gchar *subject_format = NULL;
1002 const gchar *body_format = NULL;
1004 if (item && item->prefs && item->prefs->enable_default_account)
1005 account = account_find_from_id(item->prefs->default_account);
1007 if (!account) account = cur_account;
1008 g_return_val_if_fail(account != NULL, NULL);
1010 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
1012 ifactory = gtk_item_factory_from_widget(compose->menubar);
1014 compose->replyinfo = NULL;
1015 compose->fwdinfo = NULL;
1017 textview = GTK_TEXT_VIEW(compose->text);
1018 textbuf = gtk_text_view_get_buffer(textview);
1019 compose_create_tags(textview, compose);
1021 undo_block(compose->undostruct);
1023 compose_set_dictionaries_from_folder_prefs(compose, item);
1026 if (account->auto_sig)
1027 compose_insert_sig(compose, FALSE);
1028 gtk_text_buffer_get_start_iter(textbuf, &iter);
1029 gtk_text_buffer_place_cursor(textbuf, &iter);
1031 if (account->protocol != A_NNTP) {
1032 if (mailto && *mailto != '\0') {
1033 compose_entries_set(compose, mailto);
1035 } else if (item && item->prefs->enable_default_to) {
1036 compose_entry_append(compose, item->prefs->default_to, COMPOSE_TO);
1037 compose_entry_mark_default_to(compose, item->prefs->default_to);
1039 if (item && item->ret_rcpt) {
1040 menu_set_active(ifactory, "/Options/Request Return Receipt", TRUE);
1044 compose_entry_append(compose, mailto, COMPOSE_NEWSGROUPS);
1045 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1046 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS);
1049 * CLAWS: just don't allow return receipt request, even if the user
1050 * may want to send an email. simple but foolproof.
1052 menu_set_sensitive(ifactory, "/Options/Request Return Receipt", FALSE);
1054 compose_add_field_list( compose, listAddress );
1056 if (item && item->prefs && item->prefs->compose_with_format) {
1057 subject_format = item->prefs->compose_subject_format;
1058 body_format = item->prefs->compose_body_format;
1059 } else if (account->compose_with_format) {
1060 subject_format = account->compose_subject_format;
1061 body_format = account->compose_body_format;
1062 } else if (prefs_common.compose_with_format) {
1063 subject_format = prefs_common.compose_subject_format;
1064 body_format = prefs_common.compose_body_format;
1067 if (subject_format || body_format) {
1068 MsgInfo* dummyinfo = NULL;
1071 && *subject_format != '\0' )
1073 gchar *subject = NULL;
1077 dummyinfo = compose_msginfo_new_from_compose(compose);
1079 /* decode \-escape sequences in the internal representation of the quote format */
1080 tmp = malloc(strlen(subject_format)+1);
1081 pref_get_unescaped_pref(tmp, subject_format);
1083 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1085 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account,
1086 compose->gtkaspell);
1088 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account);
1090 quote_fmt_scan_string(tmp);
1093 buf = quote_fmt_get_buffer();
1095 alertpanel_error(_("New message subject format error."));
1097 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1098 quote_fmt_reset_vartable();
1105 && *body_format != '\0' )
1108 GtkTextBuffer *buffer;
1109 GtkTextIter start, end;
1112 if ( dummyinfo == NULL )
1113 dummyinfo = compose_msginfo_new_from_compose(compose);
1115 text = GTK_TEXT_VIEW(compose->text);
1116 buffer = gtk_text_view_get_buffer(text);
1117 gtk_text_buffer_get_start_iter(buffer, &start);
1118 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1119 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1121 compose_quote_fmt(compose, dummyinfo,
1123 NULL, tmp, FALSE, TRUE,
1124 _("New message body format error at line %d."));
1125 quote_fmt_reset_vartable();
1130 procmsg_msginfo_free( dummyinfo );
1137 for (i = 0; i < attach_files->len; i++) {
1138 file = g_ptr_array_index(attach_files, i);
1139 compose_attach_append(compose, file, file, NULL);
1143 compose_show_first_last_header(compose, TRUE);
1145 /* Set save folder */
1146 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1147 gchar *folderidentifier;
1149 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
1150 folderidentifier = folder_item_get_identifier(item);
1151 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
1152 g_free(folderidentifier);
1155 gtk_widget_grab_focus(compose->header_last->entry);
1157 undo_unblock(compose->undostruct);
1159 if (prefs_common.auto_exteditor)
1160 compose_exec_ext_editor(compose);
1162 compose->draft_timeout_tag = -1;
1163 SCROLL_TO_CURSOR(compose);
1165 compose->modified = FALSE;
1166 compose_set_title(compose);
1170 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1171 gboolean override_pref)
1173 gchar *privacy = NULL;
1175 g_return_if_fail(compose != NULL);
1176 g_return_if_fail(account != NULL);
1178 if (override_pref == FALSE && account->default_encrypt_reply == FALSE)
1181 if (account->default_privacy_system
1182 && strlen(account->default_privacy_system)) {
1183 privacy = account->default_privacy_system;
1185 GSList *privacy_avail = privacy_get_system_ids();
1186 if (privacy_avail && g_slist_length(privacy_avail)) {
1187 privacy = (gchar *)(privacy_avail->data);
1190 if (privacy != NULL) {
1191 if (compose->privacy_system == NULL)
1192 compose->privacy_system = g_strdup(privacy);
1193 compose_update_privacy_system_menu_item(compose, FALSE);
1194 compose_use_encryption(compose, TRUE);
1198 static void compose_force_signing(Compose *compose, PrefsAccount *account)
1200 gchar *privacy = NULL;
1202 if (account->default_privacy_system
1203 && strlen(account->default_privacy_system)) {
1204 privacy = account->default_privacy_system;
1206 GSList *privacy_avail = privacy_get_system_ids();
1207 if (privacy_avail && g_slist_length(privacy_avail)) {
1208 privacy = (gchar *)(privacy_avail->data);
1211 if (privacy != NULL) {
1212 if (compose->privacy_system == NULL)
1213 compose->privacy_system = g_strdup(privacy);
1214 compose_update_privacy_system_menu_item(compose, FALSE);
1215 compose_use_signing(compose, TRUE);
1219 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1223 Compose *compose = NULL;
1224 GtkItemFactory *ifactory = NULL;
1226 g_return_val_if_fail(msginfo_list != NULL, NULL);
1228 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1229 g_return_val_if_fail(msginfo != NULL, NULL);
1231 list_len = g_slist_length(msginfo_list);
1235 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1236 FALSE, prefs_common.default_reply_list, FALSE, body);
1238 case COMPOSE_REPLY_WITH_QUOTE:
1239 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1240 FALSE, prefs_common.default_reply_list, FALSE, body);
1242 case COMPOSE_REPLY_WITHOUT_QUOTE:
1243 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1244 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1246 case COMPOSE_REPLY_TO_SENDER:
1247 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1248 FALSE, FALSE, TRUE, body);
1250 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1251 compose = compose_followup_and_reply_to(msginfo,
1252 COMPOSE_QUOTE_CHECK,
1253 FALSE, FALSE, body);
1255 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1256 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1257 FALSE, FALSE, TRUE, body);
1259 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1260 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1261 FALSE, FALSE, TRUE, NULL);
1263 case COMPOSE_REPLY_TO_ALL:
1264 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1265 TRUE, FALSE, FALSE, body);
1267 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1268 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1269 TRUE, FALSE, FALSE, body);
1271 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1272 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1273 TRUE, FALSE, FALSE, NULL);
1275 case COMPOSE_REPLY_TO_LIST:
1276 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1277 FALSE, TRUE, FALSE, body);
1279 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1280 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1281 FALSE, TRUE, FALSE, body);
1283 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1284 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1285 FALSE, TRUE, FALSE, NULL);
1287 case COMPOSE_FORWARD:
1288 if (prefs_common.forward_as_attachment) {
1289 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1292 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1296 case COMPOSE_FORWARD_INLINE:
1297 /* check if we reply to more than one Message */
1298 if (list_len == 1) {
1299 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1302 /* more messages FALL THROUGH */
1303 case COMPOSE_FORWARD_AS_ATTACH:
1304 compose = compose_forward_multiple(NULL, msginfo_list);
1306 case COMPOSE_REDIRECT:
1307 compose = compose_redirect(NULL, msginfo, FALSE);
1310 g_warning("compose_reply_mode(): invalid Compose Mode: %d\n", mode);
1313 if (compose == NULL) {
1314 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1317 ifactory = gtk_item_factory_from_widget(compose->menubar);
1319 compose->rmode = mode;
1320 switch (compose->rmode) {
1322 case COMPOSE_REPLY_WITH_QUOTE:
1323 case COMPOSE_REPLY_WITHOUT_QUOTE:
1324 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1325 debug_print("reply mode Normal\n");
1326 menu_set_active(ifactory, "/Options/Reply mode/Normal", TRUE);
1327 compose_reply_change_mode(compose, COMPOSE_REPLY, NULL); /* force update */
1329 case COMPOSE_REPLY_TO_SENDER:
1330 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1331 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1332 debug_print("reply mode Sender\n");
1333 menu_set_active(ifactory, "/Options/Reply mode/Sender", TRUE);
1335 case COMPOSE_REPLY_TO_ALL:
1336 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1337 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1338 debug_print("reply mode All\n");
1339 menu_set_active(ifactory, "/Options/Reply mode/All", TRUE);
1341 case COMPOSE_REPLY_TO_LIST:
1342 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1343 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1344 debug_print("reply mode List\n");
1345 menu_set_active(ifactory, "/Options/Reply mode/Mailing-list", TRUE);
1353 static Compose *compose_reply(MsgInfo *msginfo,
1354 ComposeQuoteMode quote_mode,
1360 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1361 to_sender, FALSE, body);
1364 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1365 ComposeQuoteMode quote_mode,
1370 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1371 to_sender, TRUE, body);
1374 static void compose_extract_original_charset(Compose *compose)
1376 MsgInfo *info = NULL;
1377 if (compose->replyinfo) {
1378 info = compose->replyinfo;
1379 } else if (compose->fwdinfo) {
1380 info = compose->fwdinfo;
1381 } else if (compose->targetinfo) {
1382 info = compose->targetinfo;
1385 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1386 MimeInfo *partinfo = mimeinfo;
1387 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1388 partinfo = procmime_mimeinfo_next(partinfo);
1390 compose->orig_charset =
1391 g_strdup(procmime_mimeinfo_get_parameter(
1392 partinfo, "charset"));
1394 procmime_mimeinfo_free_all(mimeinfo);
1398 #define SIGNAL_BLOCK(buffer) { \
1399 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1400 G_CALLBACK(compose_changed_cb), \
1402 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1403 G_CALLBACK(text_inserted), \
1407 #define SIGNAL_UNBLOCK(buffer) { \
1408 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1409 G_CALLBACK(compose_changed_cb), \
1411 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1412 G_CALLBACK(text_inserted), \
1416 static Compose *compose_generic_reply(MsgInfo *msginfo,
1417 ComposeQuoteMode quote_mode,
1418 gboolean to_all, gboolean to_ml,
1420 gboolean followup_and_reply_to,
1423 GtkItemFactory *ifactory;
1425 PrefsAccount *account = NULL;
1426 GtkTextView *textview;
1427 GtkTextBuffer *textbuf;
1428 gboolean quote = FALSE;
1429 const gchar *qmark = NULL;
1430 const gchar *body_fmt = NULL;
1432 g_return_val_if_fail(msginfo != NULL, NULL);
1433 g_return_val_if_fail(msginfo->folder != NULL, NULL);
1435 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1437 g_return_val_if_fail(account != NULL, NULL);
1439 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1441 compose->updating = TRUE;
1443 ifactory = gtk_item_factory_from_widget(compose->menubar);
1445 menu_set_active(ifactory, "/Options/Remove references", FALSE);
1446 menu_set_sensitive(ifactory, "/Options/Remove references", TRUE);
1448 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1449 if (!compose->replyinfo)
1450 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1452 compose_extract_original_charset(compose);
1454 if (msginfo->folder && msginfo->folder->ret_rcpt)
1455 menu_set_active(ifactory, "/Options/Request Return Receipt", TRUE);
1457 /* Set save folder */
1458 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1459 gchar *folderidentifier;
1461 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1462 folderidentifier = folder_item_get_identifier(msginfo->folder);
1463 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
1464 g_free(folderidentifier);
1467 if (compose_parse_header(compose, msginfo) < 0) return NULL;
1469 textview = (GTK_TEXT_VIEW(compose->text));
1470 textbuf = gtk_text_view_get_buffer(textview);
1471 compose_create_tags(textview, compose);
1473 undo_block(compose->undostruct);
1475 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1478 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1479 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1480 /* use the reply format of folder (if enabled), or the account's one
1481 (if enabled) or fallback to the global reply format, which is always
1482 enabled (even if empty), and use the relevant quotemark */
1484 if (msginfo->folder && msginfo->folder->prefs &&
1485 msginfo->folder->prefs->reply_with_format) {
1486 qmark = msginfo->folder->prefs->reply_quotemark;
1487 body_fmt = msginfo->folder->prefs->reply_body_format;
1489 } else if (account->reply_with_format) {
1490 qmark = account->reply_quotemark;
1491 body_fmt = account->reply_body_format;
1494 qmark = prefs_common.quotemark;
1495 body_fmt = prefs_common.quotefmt;
1500 /* empty quotemark is not allowed */
1501 if (qmark == NULL || *qmark == '\0')
1503 compose_quote_fmt(compose, compose->replyinfo,
1504 body_fmt, qmark, body, FALSE, TRUE,
1505 _("Message reply format error at line %d."));
1506 quote_fmt_reset_vartable();
1509 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1510 compose_force_encryption(compose, account, FALSE);
1513 SIGNAL_BLOCK(textbuf);
1515 if (account->auto_sig)
1516 compose_insert_sig(compose, FALSE);
1518 compose_wrap_all(compose);
1520 SIGNAL_UNBLOCK(textbuf);
1522 gtk_widget_grab_focus(compose->text);
1524 undo_unblock(compose->undostruct);
1526 if (prefs_common.auto_exteditor)
1527 compose_exec_ext_editor(compose);
1529 compose->modified = FALSE;
1530 compose_set_title(compose);
1532 compose->updating = FALSE;
1533 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1534 SCROLL_TO_CURSOR(compose);
1536 if (compose->deferred_destroy) {
1537 compose_destroy(compose);
1544 #define INSERT_FW_HEADER(var, hdr) \
1545 if (msginfo->var && *msginfo->var) { \
1546 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1547 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1548 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1551 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1552 gboolean as_attach, const gchar *body,
1553 gboolean no_extedit,
1557 GtkTextView *textview;
1558 GtkTextBuffer *textbuf;
1561 g_return_val_if_fail(msginfo != NULL, NULL);
1562 g_return_val_if_fail(msginfo->folder != NULL, NULL);
1565 !(account = compose_guess_forward_account_from_msginfo
1567 account = cur_account;
1569 compose = compose_create(account, msginfo->folder, COMPOSE_FORWARD, batch);
1571 compose->updating = TRUE;
1572 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1573 if (!compose->fwdinfo)
1574 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1576 compose_extract_original_charset(compose);
1578 if (msginfo->subject && *msginfo->subject) {
1579 gchar *buf, *buf2, *p;
1581 buf = p = g_strdup(msginfo->subject);
1582 p += subject_get_prefix_length(p);
1583 memmove(buf, p, strlen(p) + 1);
1585 buf2 = g_strdup_printf("Fw: %s", buf);
1586 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1592 textview = GTK_TEXT_VIEW(compose->text);
1593 textbuf = gtk_text_view_get_buffer(textview);
1594 compose_create_tags(textview, compose);
1596 undo_block(compose->undostruct);
1600 msgfile = procmsg_get_message_file(msginfo);
1601 if (!is_file_exist(msgfile))
1602 g_warning("%s: file not exist\n", msgfile);
1604 compose_attach_append(compose, msgfile, msgfile,
1609 const gchar *qmark = NULL;
1610 const gchar *body_fmt = prefs_common.fw_quotefmt;
1611 MsgInfo *full_msginfo;
1613 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1615 full_msginfo = procmsg_msginfo_copy(msginfo);
1617 /* use the forward format of folder (if enabled), or the account's one
1618 (if enabled) or fallback to the global forward format, which is always
1619 enabled (even if empty), and use the relevant quotemark */
1620 if (msginfo->folder && msginfo->folder->prefs &&
1621 msginfo->folder->prefs->forward_with_format) {
1622 qmark = msginfo->folder->prefs->forward_quotemark;
1623 body_fmt = msginfo->folder->prefs->forward_body_format;
1625 } else if (account->forward_with_format) {
1626 qmark = account->forward_quotemark;
1627 body_fmt = account->forward_body_format;
1630 qmark = prefs_common.fw_quotemark;
1631 body_fmt = prefs_common.fw_quotefmt;
1634 /* empty quotemark is not allowed */
1635 if (qmark == NULL || *qmark == '\0')
1638 compose_quote_fmt(compose, full_msginfo,
1639 body_fmt, qmark, body, FALSE, TRUE,
1640 _("Message forward format error at line %d."));
1641 quote_fmt_reset_vartable();
1642 compose_attach_parts(compose, msginfo);
1644 procmsg_msginfo_free(full_msginfo);
1647 SIGNAL_BLOCK(textbuf);
1649 if (account->auto_sig)
1650 compose_insert_sig(compose, FALSE);
1652 compose_wrap_all(compose);
1654 SIGNAL_UNBLOCK(textbuf);
1656 gtk_text_buffer_get_start_iter(textbuf, &iter);
1657 gtk_text_buffer_place_cursor(textbuf, &iter);
1659 gtk_widget_grab_focus(compose->header_last->entry);
1661 if (!no_extedit && prefs_common.auto_exteditor)
1662 compose_exec_ext_editor(compose);
1665 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1666 gchar *folderidentifier;
1668 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1669 folderidentifier = folder_item_get_identifier(msginfo->folder);
1670 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
1671 g_free(folderidentifier);
1674 undo_unblock(compose->undostruct);
1676 compose->modified = FALSE;
1677 compose_set_title(compose);
1679 compose->updating = FALSE;
1680 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1681 SCROLL_TO_CURSOR(compose);
1683 if (compose->deferred_destroy) {
1684 compose_destroy(compose);
1691 #undef INSERT_FW_HEADER
1693 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1696 GtkTextView *textview;
1697 GtkTextBuffer *textbuf;
1701 gboolean single_mail = TRUE;
1703 g_return_val_if_fail(msginfo_list != NULL, NULL);
1705 if (g_slist_length(msginfo_list) > 1)
1706 single_mail = FALSE;
1708 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1709 if (((MsgInfo *)msginfo->data)->folder == NULL)
1712 /* guess account from first selected message */
1714 !(account = compose_guess_forward_account_from_msginfo
1715 (msginfo_list->data)))
1716 account = cur_account;
1718 g_return_val_if_fail(account != NULL, NULL);
1720 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1721 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1722 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1725 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1727 compose->updating = TRUE;
1729 textview = GTK_TEXT_VIEW(compose->text);
1730 textbuf = gtk_text_view_get_buffer(textview);
1731 compose_create_tags(textview, compose);
1733 undo_block(compose->undostruct);
1734 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1735 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
1737 if (!is_file_exist(msgfile))
1738 g_warning("%s: file not exist\n", msgfile);
1740 compose_attach_append(compose, msgfile, msgfile,
1746 MsgInfo *info = (MsgInfo *)msginfo_list->data;
1747 if (info->subject && *info->subject) {
1748 gchar *buf, *buf2, *p;
1750 buf = p = g_strdup(info->subject);
1751 p += subject_get_prefix_length(p);
1752 memmove(buf, p, strlen(p) + 1);
1754 buf2 = g_strdup_printf("Fw: %s", buf);
1755 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1761 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
1762 _("Fw: multiple emails"));
1765 SIGNAL_BLOCK(textbuf);
1767 if (account->auto_sig)
1768 compose_insert_sig(compose, FALSE);
1770 compose_wrap_all(compose);
1772 SIGNAL_UNBLOCK(textbuf);
1774 gtk_text_buffer_get_start_iter(textbuf, &iter);
1775 gtk_text_buffer_place_cursor(textbuf, &iter);
1777 gtk_widget_grab_focus(compose->header_last->entry);
1778 undo_unblock(compose->undostruct);
1779 compose->modified = FALSE;
1780 compose_set_title(compose);
1782 compose->updating = FALSE;
1783 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1784 SCROLL_TO_CURSOR(compose);
1786 if (compose->deferred_destroy) {
1787 compose_destroy(compose);
1794 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
1796 GtkTextIter start = *iter;
1797 GtkTextIter end_iter;
1798 int start_pos = gtk_text_iter_get_offset(&start);
1800 if (!compose->account->sig_sep)
1803 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
1804 start_pos+strlen(compose->account->sig_sep));
1806 /* check sig separator */
1807 str = gtk_text_iter_get_text(&start, &end_iter);
1808 if (!strcmp(str, compose->account->sig_sep)) {
1810 /* check end of line (\n) */
1811 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
1812 start_pos+strlen(compose->account->sig_sep));
1813 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
1814 start_pos+strlen(compose->account->sig_sep)+1);
1815 tmp = gtk_text_iter_get_text(&start, &end_iter);
1816 if (!strcmp(tmp,"\n")) {
1828 static void compose_colorize_signature(Compose *compose)
1830 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
1832 GtkTextIter end_iter;
1833 gtk_text_buffer_get_start_iter(buffer, &iter);
1834 while (gtk_text_iter_forward_line(&iter))
1835 if (compose_is_sig_separator(compose, buffer, &iter)) {
1836 gtk_text_buffer_get_end_iter(buffer, &end_iter);
1837 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
1841 #define BLOCK_WRAP() { \
1842 prev_autowrap = compose->autowrap; \
1843 buffer = gtk_text_view_get_buffer( \
1844 GTK_TEXT_VIEW(compose->text)); \
1845 compose->autowrap = FALSE; \
1847 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1848 G_CALLBACK(compose_changed_cb), \
1850 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1851 G_CALLBACK(text_inserted), \
1854 #define UNBLOCK_WRAP() { \
1855 compose->autowrap = prev_autowrap; \
1856 if (compose->autowrap) { \
1857 gint old = compose->draft_timeout_tag; \
1858 compose->draft_timeout_tag = -2; \
1859 compose_wrap_all(compose); \
1860 compose->draft_timeout_tag = old; \
1863 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1864 G_CALLBACK(compose_changed_cb), \
1866 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1867 G_CALLBACK(text_inserted), \
1871 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
1873 Compose *compose = NULL;
1874 PrefsAccount *account = NULL;
1875 GtkTextView *textview;
1876 GtkTextBuffer *textbuf;
1880 gchar buf[BUFFSIZE];
1881 gboolean use_signing = FALSE;
1882 gboolean use_encryption = FALSE;
1883 gchar *privacy_system = NULL;
1884 int priority = PRIORITY_NORMAL;
1885 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
1887 g_return_val_if_fail(msginfo != NULL, NULL);
1888 g_return_val_if_fail(msginfo->folder != NULL, NULL);
1890 if (compose_put_existing_to_front(msginfo)) {
1894 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
1895 folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
1896 gchar queueheader_buf[BUFFSIZE];
1899 /* Select Account from queue headers */
1900 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1901 sizeof(queueheader_buf), "X-Claws-Account-Id:")) {
1902 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
1903 account = account_find_from_id(id);
1905 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1906 sizeof(queueheader_buf), "X-Sylpheed-Account-Id:")) {
1907 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
1908 account = account_find_from_id(id);
1910 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1911 sizeof(queueheader_buf), "NAID:")) {
1912 id = atoi(&queueheader_buf[strlen("NAID:")]);
1913 account = account_find_from_id(id);
1915 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1916 sizeof(queueheader_buf), "MAID:")) {
1917 id = atoi(&queueheader_buf[strlen("MAID:")]);
1918 account = account_find_from_id(id);
1920 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1921 sizeof(queueheader_buf), "S:")) {
1922 account = account_find_from_address(queueheader_buf);
1924 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1925 sizeof(queueheader_buf), "X-Claws-Sign:")) {
1926 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
1927 use_signing = param;
1930 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1931 sizeof(queueheader_buf), "X-Sylpheed-Sign:")) {
1932 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
1933 use_signing = param;
1936 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1937 sizeof(queueheader_buf), "X-Claws-Encrypt:")) {
1938 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
1939 use_encryption = param;
1941 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1942 sizeof(queueheader_buf), "X-Sylpheed-Encrypt:")) {
1943 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
1944 use_encryption = param;
1946 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1947 sizeof(queueheader_buf), "X-Claws-Privacy-System:")) {
1948 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
1950 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1951 sizeof(queueheader_buf), "X-Sylpheed-Privacy-System:")) {
1952 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
1954 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1955 sizeof(queueheader_buf), "X-Priority: ")) {
1956 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
1959 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1960 sizeof(queueheader_buf), "RMID:")) {
1961 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
1962 if (tokens[0] && tokens[1] && tokens[2]) {
1963 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
1964 if (orig_item != NULL) {
1965 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
1970 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1971 sizeof(queueheader_buf), "FMID:")) {
1972 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
1973 if (tokens[0] && tokens[1] && tokens[2]) {
1974 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
1975 if (orig_item != NULL) {
1976 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
1982 account = msginfo->folder->folder->account;
1985 if (!account && prefs_common.reedit_account_autosel) {
1986 gchar from[BUFFSIZE];
1987 if (!procheader_get_header_from_msginfo(msginfo, from, sizeof(from), "FROM:")) {
1988 extract_address(from);
1989 account = account_find_from_address(from);
1993 account = cur_account;
1995 g_return_val_if_fail(account != NULL, NULL);
1997 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
1999 compose->replyinfo = replyinfo;
2000 compose->fwdinfo = fwdinfo;
2002 compose->updating = TRUE;
2003 compose->priority = priority;
2005 if (privacy_system != NULL) {
2006 compose->privacy_system = privacy_system;
2007 compose_use_signing(compose, use_signing);
2008 compose_use_encryption(compose, use_encryption);
2009 compose_update_privacy_system_menu_item(compose, FALSE);
2011 activate_privacy_system(compose, account, FALSE);
2014 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2016 compose_extract_original_charset(compose);
2018 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2019 folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
2020 gchar queueheader_buf[BUFFSIZE];
2022 /* Set message save folder */
2023 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "SCF:")) {
2026 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2027 gtk_editable_delete_text(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
2028 gtk_editable_insert_text(GTK_EDITABLE(compose->savemsg_entry), &queueheader_buf[4], strlen(&queueheader_buf[4]), &startpos);
2030 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "RRCPT:")) {
2031 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2033 GtkItemFactory *ifactory;
2034 ifactory = gtk_item_factory_from_widget(compose->menubar);
2035 menu_set_active(ifactory, "/Options/Request Return Receipt", TRUE);
2040 if (compose_parse_header(compose, msginfo) < 0) {
2041 compose->updating = FALSE;
2042 compose_destroy(compose);
2045 compose_reedit_set_entry(compose, msginfo);
2047 textview = GTK_TEXT_VIEW(compose->text);
2048 textbuf = gtk_text_view_get_buffer(textview);
2049 compose_create_tags(textview, compose);
2051 mark = gtk_text_buffer_get_insert(textbuf);
2052 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2054 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2055 G_CALLBACK(compose_changed_cb),
2058 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2059 fp = procmime_get_first_encrypted_text_content(msginfo);
2061 compose_force_encryption(compose, account, TRUE);
2064 fp = procmime_get_first_text_content(msginfo);
2067 g_warning("Can't get text part\n");
2071 gboolean prev_autowrap = compose->autowrap;
2072 GtkTextBuffer *buffer = textbuf;
2074 while (fgets(buf, sizeof(buf), fp) != NULL) {
2076 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2082 compose_attach_parts(compose, msginfo);
2084 compose_colorize_signature(compose);
2086 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2087 G_CALLBACK(compose_changed_cb),
2090 gtk_widget_grab_focus(compose->text);
2092 if (prefs_common.auto_exteditor) {
2093 compose_exec_ext_editor(compose);
2095 compose->modified = FALSE;
2096 compose_set_title(compose);
2098 compose->updating = FALSE;
2099 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2100 SCROLL_TO_CURSOR(compose);
2102 if (compose->deferred_destroy) {
2103 compose_destroy(compose);
2107 compose->sig_str = compose_get_signature_str(compose);
2112 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2117 GtkItemFactory *ifactory;
2120 g_return_val_if_fail(msginfo != NULL, NULL);
2123 account = account_get_reply_account(msginfo,
2124 prefs_common.reply_account_autosel);
2125 g_return_val_if_fail(account != NULL, NULL);
2127 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2129 compose->updating = TRUE;
2131 ifactory = gtk_item_factory_from_widget(compose->menubar);
2132 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2133 compose->replyinfo = NULL;
2134 compose->fwdinfo = NULL;
2136 compose_show_first_last_header(compose, TRUE);
2138 gtk_widget_grab_focus(compose->header_last->entry);
2140 filename = procmsg_get_message_file(msginfo);
2142 if (filename == NULL) {
2143 compose->updating = FALSE;
2144 compose_destroy(compose);
2149 compose->redirect_filename = filename;
2151 /* Set save folder */
2152 item = msginfo->folder;
2153 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2154 gchar *folderidentifier;
2156 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
2157 folderidentifier = folder_item_get_identifier(item);
2158 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
2159 g_free(folderidentifier);
2162 compose_attach_parts(compose, msginfo);
2164 if (msginfo->subject)
2165 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2167 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2169 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2170 _("Message redirect format error at line %d."));
2171 quote_fmt_reset_vartable();
2172 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2174 compose_colorize_signature(compose);
2176 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
2177 menu_set_sensitive(ifactory, "/Add...", FALSE);
2178 menu_set_sensitive(ifactory, "/Remove", FALSE);
2179 menu_set_sensitive(ifactory, "/Properties...", FALSE);
2181 ifactory = gtk_item_factory_from_widget(compose->menubar);
2182 menu_set_sensitive(ifactory, "/Message/Save", FALSE);
2183 menu_set_sensitive(ifactory, "/Message/Insert file", FALSE);
2184 menu_set_sensitive(ifactory, "/Message/Attach file", FALSE);
2185 menu_set_sensitive(ifactory, "/Message/Insert signature", FALSE);
2186 menu_set_sensitive(ifactory, "/Edit", FALSE);
2187 menu_set_sensitive(ifactory, "/Options", FALSE);
2188 menu_set_sensitive(ifactory, "/Tools/Show ruler", FALSE);
2189 menu_set_sensitive(ifactory, "/Tools/Actions", FALSE);
2191 if (compose->toolbar->draft_btn)
2192 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2193 if (compose->toolbar->insert_btn)
2194 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2195 if (compose->toolbar->attach_btn)
2196 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2197 if (compose->toolbar->sig_btn)
2198 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2199 if (compose->toolbar->exteditor_btn)
2200 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2201 if (compose->toolbar->linewrap_current_btn)
2202 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2203 if (compose->toolbar->linewrap_all_btn)
2204 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2206 compose->modified = FALSE;
2207 compose_set_title(compose);
2208 compose->updating = FALSE;
2209 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2210 SCROLL_TO_CURSOR(compose);
2212 if (compose->deferred_destroy) {
2213 compose_destroy(compose);
2220 GList *compose_get_compose_list(void)
2222 return compose_list;
2225 void compose_entry_append(Compose *compose, const gchar *address,
2226 ComposeEntryType type)
2228 const gchar *header;
2230 gboolean in_quote = FALSE;
2231 if (!address || *address == '\0') return;
2238 header = N_("Bcc:");
2240 case COMPOSE_REPLYTO:
2241 header = N_("Reply-To:");
2243 case COMPOSE_NEWSGROUPS:
2244 header = N_("Newsgroups:");
2246 case COMPOSE_FOLLOWUPTO:
2247 header = N_( "Followup-To:");
2254 header = prefs_common_translated_header_name(header);
2256 cur = begin = (gchar *)address;
2258 /* we separate the line by commas, but not if we're inside a quoted
2260 while (*cur != '\0') {
2262 in_quote = !in_quote;
2263 if (*cur == ',' && !in_quote) {
2264 gchar *tmp = g_strdup(begin);
2266 tmp[cur-begin]='\0';
2269 while (*tmp == ' ' || *tmp == '\t')
2271 compose_add_header_entry(compose, header, tmp);
2278 gchar *tmp = g_strdup(begin);
2280 tmp[cur-begin]='\0';
2283 while (*tmp == ' ' || *tmp == '\t')
2285 compose_add_header_entry(compose, header, tmp);
2290 static void compose_entry_mark_default_to(Compose *compose, const gchar *mailto)
2292 static GdkColor yellow;
2293 static GdkColor black;
2294 static gboolean yellow_initialised = FALSE;
2298 if (!yellow_initialised) {
2299 gdk_color_parse("#f5f6be", &yellow);
2300 gdk_color_parse("#000000", &black);
2301 yellow_initialised = gdk_colormap_alloc_color(
2302 gdk_colormap_get_system(), &yellow, FALSE, TRUE);
2303 yellow_initialised &= gdk_colormap_alloc_color(
2304 gdk_colormap_get_system(), &black, FALSE, TRUE);
2307 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2308 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2309 if (gtk_entry_get_text(entry) &&
2310 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2311 if (yellow_initialised) {
2312 gtk_widget_modify_base(
2313 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2314 GTK_STATE_NORMAL, &yellow);
2315 gtk_widget_modify_text(
2316 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2317 GTK_STATE_NORMAL, &black);
2323 void compose_toolbar_cb(gint action, gpointer data)
2325 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2326 Compose *compose = (Compose*)toolbar_item->parent;
2328 g_return_if_fail(compose != NULL);
2332 compose_send_cb(compose, 0, NULL);
2335 compose_send_later_cb(compose, 0, NULL);
2338 compose_draft_cb(compose, COMPOSE_QUIT_EDITING, NULL);
2341 compose_insert_file_cb(compose, 0, NULL);
2344 compose_attach_cb(compose, 0, NULL);
2347 compose_insert_sig(compose, FALSE);
2350 compose_ext_editor_cb(compose, 0, NULL);
2352 case A_LINEWRAP_CURRENT:
2353 compose_beautify_paragraph(compose, NULL, TRUE);
2355 case A_LINEWRAP_ALL:
2356 compose_wrap_all_full(compose, TRUE);
2359 compose_address_cb(compose, 0, NULL);
2362 case A_CHECK_SPELLING:
2363 compose_check_all(compose);
2371 static void compose_entries_set(Compose *compose, const gchar *mailto)
2375 gchar *subject = NULL;
2379 gchar *attach = NULL;
2381 scan_mailto_url(mailto, &to, &cc, NULL, &subject, &body, &attach);
2384 compose_entry_append(compose, to, COMPOSE_TO);
2386 compose_entry_append(compose, cc, COMPOSE_CC);
2388 if (!g_utf8_validate (subject, -1, NULL)) {
2389 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2390 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2393 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2397 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2398 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2401 gboolean prev_autowrap = compose->autowrap;
2403 compose->autowrap = FALSE;
2405 mark = gtk_text_buffer_get_insert(buffer);
2406 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2408 if (!g_utf8_validate (body, -1, NULL)) {
2409 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2410 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2413 gtk_text_buffer_insert(buffer, &iter, body, -1);
2415 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2417 compose->autowrap = prev_autowrap;
2418 if (compose->autowrap)
2419 compose_wrap_all(compose);
2423 gchar *utf8_filename = conv_filename_to_utf8(attach);
2424 if (utf8_filename) {
2425 if (compose_attach_append(compose, attach, utf8_filename, NULL)) {
2426 alertpanel_notice(_("The file '%s' has been attached."), attach);
2428 g_free(utf8_filename);
2430 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2440 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2442 static HeaderEntry hentry[] = {{"Reply-To:", NULL, TRUE},
2443 {"Cc:", NULL, TRUE},
2444 {"References:", NULL, FALSE},
2445 {"Bcc:", NULL, TRUE},
2446 {"Newsgroups:", NULL, TRUE},
2447 {"Followup-To:", NULL, TRUE},
2448 {"List-Post:", NULL, FALSE},
2449 {"X-Priority:", NULL, FALSE},
2450 {NULL, NULL, FALSE}};
2466 g_return_val_if_fail(msginfo != NULL, -1);
2468 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2469 procheader_get_header_fields(fp, hentry);
2472 if (hentry[H_REPLY_TO].body != NULL) {
2473 if (hentry[H_REPLY_TO].body[0] != '\0') {
2475 conv_unmime_header(hentry[H_REPLY_TO].body,
2478 g_free(hentry[H_REPLY_TO].body);
2479 hentry[H_REPLY_TO].body = NULL;
2481 if (hentry[H_CC].body != NULL) {
2482 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL);
2483 g_free(hentry[H_CC].body);
2484 hentry[H_CC].body = NULL;
2486 if (hentry[H_REFERENCES].body != NULL) {
2487 if (compose->mode == COMPOSE_REEDIT)
2488 compose->references = hentry[H_REFERENCES].body;
2490 compose->references = compose_parse_references
2491 (hentry[H_REFERENCES].body, msginfo->msgid);
2492 g_free(hentry[H_REFERENCES].body);
2494 hentry[H_REFERENCES].body = NULL;
2496 if (hentry[H_BCC].body != NULL) {
2497 if (compose->mode == COMPOSE_REEDIT)
2499 conv_unmime_header(hentry[H_BCC].body, NULL);
2500 g_free(hentry[H_BCC].body);
2501 hentry[H_BCC].body = NULL;
2503 if (hentry[H_NEWSGROUPS].body != NULL) {
2504 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2505 hentry[H_NEWSGROUPS].body = NULL;
2507 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2508 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2509 compose->followup_to =
2510 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2513 g_free(hentry[H_FOLLOWUP_TO].body);
2514 hentry[H_FOLLOWUP_TO].body = NULL;
2516 if (hentry[H_LIST_POST].body != NULL) {
2519 extract_address(hentry[H_LIST_POST].body);
2520 if (hentry[H_LIST_POST].body[0] != '\0') {
2521 scan_mailto_url(hentry[H_LIST_POST].body,
2522 &to, NULL, NULL, NULL, NULL, NULL);
2524 g_free(compose->ml_post);
2525 compose->ml_post = to;
2528 g_free(hentry[H_LIST_POST].body);
2529 hentry[H_LIST_POST].body = NULL;
2532 /* CLAWS - X-Priority */
2533 if (compose->mode == COMPOSE_REEDIT)
2534 if (hentry[H_X_PRIORITY].body != NULL) {
2537 priority = atoi(hentry[H_X_PRIORITY].body);
2538 g_free(hentry[H_X_PRIORITY].body);
2540 hentry[H_X_PRIORITY].body = NULL;
2542 if (priority < PRIORITY_HIGHEST ||
2543 priority > PRIORITY_LOWEST)
2544 priority = PRIORITY_NORMAL;
2546 compose->priority = priority;
2549 if (compose->mode == COMPOSE_REEDIT) {
2550 if (msginfo->inreplyto && *msginfo->inreplyto)
2551 compose->inreplyto = g_strdup(msginfo->inreplyto);
2555 if (msginfo->msgid && *msginfo->msgid)
2556 compose->inreplyto = g_strdup(msginfo->msgid);
2558 if (!compose->references) {
2559 if (msginfo->msgid && *msginfo->msgid) {
2560 if (msginfo->inreplyto && *msginfo->inreplyto)
2561 compose->references =
2562 g_strdup_printf("<%s>\n\t<%s>",
2566 compose->references =
2567 g_strconcat("<", msginfo->msgid, ">",
2569 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
2570 compose->references =
2571 g_strconcat("<", msginfo->inreplyto, ">",
2579 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
2581 GSList *ref_id_list, *cur;
2585 ref_id_list = references_list_append(NULL, ref);
2586 if (!ref_id_list) return NULL;
2587 if (msgid && *msgid)
2588 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
2593 for (cur = ref_id_list; cur != NULL; cur = cur->next)
2594 /* "<" + Message-ID + ">" + CR+LF+TAB */
2595 len += strlen((gchar *)cur->data) + 5;
2597 if (len > MAX_REFERENCES_LEN) {
2598 /* remove second message-ID */
2599 if (ref_id_list && ref_id_list->next &&
2600 ref_id_list->next->next) {
2601 g_free(ref_id_list->next->data);
2602 ref_id_list = g_slist_remove
2603 (ref_id_list, ref_id_list->next->data);
2605 slist_free_strings(ref_id_list);
2606 g_slist_free(ref_id_list);
2613 new_ref = g_string_new("");
2614 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
2615 if (new_ref->len > 0)
2616 g_string_append(new_ref, "\n\t");
2617 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
2620 slist_free_strings(ref_id_list);
2621 g_slist_free(ref_id_list);
2623 new_ref_str = new_ref->str;
2624 g_string_free(new_ref, FALSE);
2629 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
2630 const gchar *fmt, const gchar *qmark,
2631 const gchar *body, gboolean rewrap,
2632 gboolean need_unescape,
2633 const gchar *err_msg)
2635 MsgInfo* dummyinfo = NULL;
2636 gchar *quote_str = NULL;
2638 gboolean prev_autowrap;
2639 const gchar *trimmed_body = body;
2640 gint cursor_pos = -1;
2641 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2642 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2647 SIGNAL_BLOCK(buffer);
2650 dummyinfo = compose_msginfo_new_from_compose(compose);
2651 msginfo = dummyinfo;
2654 if (qmark != NULL) {
2656 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
2657 compose->gtkaspell);
2659 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
2661 quote_fmt_scan_string(qmark);
2664 buf = quote_fmt_get_buffer();
2666 alertpanel_error(_("Quote mark format error."));
2668 Xstrdup_a(quote_str, buf, goto error)
2671 if (fmt && *fmt != '\0') {
2674 while (*trimmed_body == '\n')
2678 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account,
2679 compose->gtkaspell);
2681 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account);
2683 if (need_unescape) {
2686 /* decode \-escape sequences in the internal representation of the quote format */
2687 tmp = malloc(strlen(fmt)+1);
2688 pref_get_unescaped_pref(tmp, fmt);
2689 quote_fmt_scan_string(tmp);
2693 quote_fmt_scan_string(fmt);
2697 buf = quote_fmt_get_buffer();
2699 gint line = quote_fmt_get_line();
2700 alertpanel_error(err_msg, line);
2706 prev_autowrap = compose->autowrap;
2707 compose->autowrap = FALSE;
2709 mark = gtk_text_buffer_get_insert(buffer);
2710 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2711 if (g_utf8_validate(buf, -1, NULL)) {
2712 gtk_text_buffer_insert(buffer, &iter, buf, -1);
2714 gchar *tmpout = NULL;
2715 tmpout = conv_codeset_strdup
2716 (buf, conv_get_locale_charset_str_no_utf8(),
2718 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
2720 tmpout = g_malloc(strlen(buf)*2+1);
2721 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
2723 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
2727 cursor_pos = quote_fmt_get_cursor_pos();
2728 compose->set_cursor_pos = cursor_pos;
2729 if (cursor_pos == -1) {
2732 gtk_text_buffer_get_start_iter(buffer, &iter);
2733 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
2734 gtk_text_buffer_place_cursor(buffer, &iter);
2736 compose->autowrap = prev_autowrap;
2737 if (compose->autowrap && rewrap)
2738 compose_wrap_all(compose);
2745 SIGNAL_UNBLOCK(buffer);
2747 procmsg_msginfo_free( dummyinfo );
2752 /* if ml_post is of type addr@host and from is of type
2753 * addr-anything@host, return TRUE
2755 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
2757 gchar *left_ml = NULL;
2758 gchar *right_ml = NULL;
2759 gchar *left_from = NULL;
2760 gchar *right_from = NULL;
2761 gboolean result = FALSE;
2763 if (!ml_post || !from)
2766 left_ml = g_strdup(ml_post);
2767 if (strstr(left_ml, "@")) {
2768 right_ml = strstr(left_ml, "@")+1;
2769 *(strstr(left_ml, "@")) = '\0';
2772 left_from = g_strdup(from);
2773 if (strstr(left_from, "@")) {
2774 right_from = strstr(left_from, "@")+1;
2775 *(strstr(left_from, "@")) = '\0';
2778 if (left_ml && left_from && right_ml && right_from
2779 && !strncmp(left_from, left_ml, strlen(left_ml))
2780 && !strcmp(right_from, right_ml)) {
2789 static gboolean same_address(const gchar *addr1, const gchar *addr2)
2791 gchar *my_addr1, *my_addr2;
2793 if (!addr1 || !addr2)
2796 Xstrdup_a(my_addr1, addr1, return FALSE);
2797 Xstrdup_a(my_addr2, addr2, return FALSE);
2799 extract_address(my_addr1);
2800 extract_address(my_addr2);
2802 return !strcasecmp(my_addr1, my_addr2);
2805 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
2806 gboolean to_all, gboolean to_ml,
2808 gboolean followup_and_reply_to)
2810 GSList *cc_list = NULL;
2813 gchar *replyto = NULL;
2814 GHashTable *to_table;
2816 gboolean reply_to_ml = FALSE;
2817 gboolean default_reply_to = FALSE;
2819 g_return_if_fail(compose->account != NULL);
2820 g_return_if_fail(msginfo != NULL);
2822 reply_to_ml = to_ml && compose->ml_post;
2824 default_reply_to = msginfo->folder &&
2825 msginfo->folder->prefs->enable_default_reply_to;
2827 if (compose->account->protocol != A_NNTP) {
2828 if (reply_to_ml && !default_reply_to) {
2830 gboolean is_subscr = is_subscription(compose->ml_post,
2833 /* normal answer to ml post with a reply-to */
2834 compose_entry_append(compose,
2837 if (compose->replyto
2838 && !same_address(compose->ml_post, compose->replyto))
2839 compose_entry_append(compose,
2843 /* answer to subscription confirmation */
2844 if (compose->replyto)
2845 compose_entry_append(compose,
2848 else if (msginfo->from)
2849 compose_entry_append(compose,
2854 else if (!(to_all || to_sender) && default_reply_to) {
2855 compose_entry_append(compose,
2856 msginfo->folder->prefs->default_reply_to,
2858 compose_entry_mark_default_to(compose,
2859 msginfo->folder->prefs->default_reply_to);
2864 Xstrdup_a(tmp1, msginfo->from, return);
2865 extract_address(tmp1);
2866 if (to_all || to_sender ||
2867 !account_find_from_address(tmp1))
2868 compose_entry_append(compose,
2869 (compose->replyto && !to_sender)
2870 ? compose->replyto :
2871 msginfo->from ? msginfo->from : "",
2873 else if (!to_all && !to_sender) {
2874 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
2875 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
2876 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
2877 compose_entry_append(compose,
2878 msginfo->from ? msginfo->from : "",
2881 /* replying to own mail, use original recp */
2882 compose_entry_append(compose,
2883 msginfo->to ? msginfo->to : "",
2885 compose_entry_append(compose,
2886 msginfo->cc ? msginfo->cc : "",
2892 if (to_sender || (compose->followup_to &&
2893 !strncmp(compose->followup_to, "poster", 6)))
2894 compose_entry_append
2896 (compose->replyto ? compose->replyto :
2897 msginfo->from ? msginfo->from : ""),
2900 else if (followup_and_reply_to || to_all) {
2901 compose_entry_append
2903 (compose->replyto ? compose->replyto :
2904 msginfo->from ? msginfo->from : ""),
2907 compose_entry_append
2909 compose->followup_to ? compose->followup_to :
2910 compose->newsgroups ? compose->newsgroups : "",
2911 COMPOSE_NEWSGROUPS);
2914 compose_entry_append
2916 compose->followup_to ? compose->followup_to :
2917 compose->newsgroups ? compose->newsgroups : "",
2918 COMPOSE_NEWSGROUPS);
2921 if (msginfo->subject && *msginfo->subject) {
2925 buf = p = g_strdup(msginfo->subject);
2926 p += subject_get_prefix_length(p);
2927 memmove(buf, p, strlen(p) + 1);
2929 buf2 = g_strdup_printf("Re: %s", buf);
2930 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
2935 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
2937 if (to_ml && compose->ml_post) return;
2938 if (!to_all || compose->account->protocol == A_NNTP) return;
2940 if (compose->replyto) {
2941 Xstrdup_a(replyto, compose->replyto, return);
2942 extract_address(replyto);
2944 if (msginfo->from) {
2945 Xstrdup_a(from, msginfo->from, return);
2946 extract_address(from);
2949 if (replyto && from)
2950 cc_list = address_list_append_with_comments(cc_list, from);
2951 if (to_all && msginfo->folder &&
2952 msginfo->folder->prefs->enable_default_reply_to)
2953 cc_list = address_list_append_with_comments(cc_list,
2954 msginfo->folder->prefs->default_reply_to);
2955 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
2956 cc_list = address_list_append_with_comments(cc_list, compose->cc);
2958 to_table = g_hash_table_new(g_str_hash, g_str_equal);
2960 g_hash_table_insert(to_table, g_utf8_strdown(replyto, -1), GINT_TO_POINTER(1));
2961 if (compose->account) {
2962 g_hash_table_insert(to_table, g_utf8_strdown(compose->account->address, -1),
2963 GINT_TO_POINTER(1));
2965 /* remove address on To: and that of current account */
2966 for (cur = cc_list; cur != NULL; ) {
2967 GSList *next = cur->next;
2970 addr = g_utf8_strdown(cur->data, -1);
2971 extract_address(addr);
2973 if (GPOINTER_TO_INT(g_hash_table_lookup(to_table, addr)) == 1)
2974 cc_list = g_slist_remove(cc_list, cur->data);
2976 g_hash_table_insert(to_table, addr, GINT_TO_POINTER(1));
2980 hash_free_strings(to_table);
2981 g_hash_table_destroy(to_table);
2984 for (cur = cc_list; cur != NULL; cur = cur->next)
2985 compose_entry_append(compose, (gchar *)cur->data,
2987 slist_free_strings(cc_list);
2988 g_slist_free(cc_list);
2993 #define SET_ENTRY(entry, str) \
2996 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
2999 #define SET_ADDRESS(type, str) \
3002 compose_entry_append(compose, str, type); \
3005 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3007 g_return_if_fail(msginfo != NULL);
3009 SET_ENTRY(subject_entry, msginfo->subject);
3010 SET_ENTRY(from_name, msginfo->from);
3011 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3012 SET_ADDRESS(COMPOSE_CC, compose->cc);
3013 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3014 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3015 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3016 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3018 compose_update_priority_menu_item(compose);
3019 compose_update_privacy_system_menu_item(compose, FALSE);
3020 compose_show_first_last_header(compose, TRUE);
3026 static void compose_insert_sig(Compose *compose, gboolean replace)
3028 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3029 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3031 GtkTextIter iter, iter_end;
3033 gboolean prev_autowrap;
3034 gboolean found = FALSE;
3035 gboolean exists = FALSE;
3037 g_return_if_fail(compose->account != NULL);
3041 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3042 G_CALLBACK(compose_changed_cb),
3045 mark = gtk_text_buffer_get_insert(buffer);
3046 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3047 cur_pos = gtk_text_iter_get_offset (&iter);
3049 gtk_text_buffer_get_end_iter(buffer, &iter);
3051 exists = (compose->sig_str != NULL);
3054 GtkTextIter first_iter, start_iter, end_iter;
3056 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3058 if (!exists || compose->sig_str[0] == '\0')
3061 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3062 compose->signature_tag);
3065 /* include previous \n\n */
3066 gtk_text_iter_backward_chars(&first_iter, 2);
3067 start_iter = first_iter;
3068 end_iter = first_iter;
3070 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3071 compose->signature_tag);
3072 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3073 compose->signature_tag);
3075 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3081 g_free(compose->sig_str);
3082 compose->sig_str = compose_get_signature_str(compose);
3084 cur_pos = gtk_text_iter_get_offset(&iter);
3086 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3087 g_free(compose->sig_str);
3088 compose->sig_str = NULL;
3090 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3092 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3093 gtk_text_iter_forward_chars(&iter, 2);
3094 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3095 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3097 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3098 cur_pos = gtk_text_buffer_get_char_count (buffer);
3100 /* put the cursor where it should be
3101 * either where the quote_fmt says, either before the signature */
3102 if (compose->set_cursor_pos < 0)
3103 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3105 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3106 compose->set_cursor_pos);
3108 gtk_text_buffer_place_cursor(buffer, &iter);
3109 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3110 G_CALLBACK(compose_changed_cb),
3116 static gchar *compose_get_signature_str(Compose *compose)
3118 gchar *sig_body = NULL;
3119 gchar *sig_str = NULL;
3120 gchar *utf8_sig_str = NULL;
3122 g_return_val_if_fail(compose->account != NULL, NULL);
3124 if (!compose->account->sig_path)
3127 if (compose->account->sig_type == SIG_FILE) {
3128 if (!is_file_or_fifo_exist(compose->account->sig_path)) {
3129 g_warning("can't open signature file: %s\n",
3130 compose->account->sig_path);
3135 if (compose->account->sig_type == SIG_COMMAND)
3136 sig_body = get_command_output(compose->account->sig_path);
3140 tmp = file_read_to_str(compose->account->sig_path);
3143 sig_body = normalize_newlines(tmp);
3147 if (compose->account->sig_sep) {
3148 sig_str = g_strconcat("\n\n", compose->account->sig_sep, "\n", sig_body,
3152 sig_str = g_strconcat("\n\n", sig_body, NULL);
3155 if (g_utf8_validate(sig_str, -1, NULL) == TRUE)
3156 utf8_sig_str = sig_str;
3158 utf8_sig_str = conv_codeset_strdup
3159 (sig_str, conv_get_locale_charset_str_no_utf8(),
3165 return utf8_sig_str;
3168 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3171 GtkTextBuffer *buffer;
3174 const gchar *cur_encoding;
3175 gchar buf[BUFFSIZE];
3178 gboolean prev_autowrap;
3179 gboolean badtxt = FALSE;
3181 g_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3183 if ((fp = g_fopen(file, "rb")) == NULL) {
3184 FILE_OP_ERROR(file, "fopen");
3185 return COMPOSE_INSERT_READ_ERROR;
3188 prev_autowrap = compose->autowrap;
3189 compose->autowrap = FALSE;
3191 text = GTK_TEXT_VIEW(compose->text);
3192 buffer = gtk_text_view_get_buffer(text);
3193 mark = gtk_text_buffer_get_insert(buffer);
3194 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3196 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3197 G_CALLBACK(text_inserted),
3200 cur_encoding = conv_get_locale_charset_str_no_utf8();
3202 while (fgets(buf, sizeof(buf), fp) != NULL) {
3205 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3206 str = g_strdup(buf);
3208 str = conv_codeset_strdup
3209 (buf, cur_encoding, CS_INTERNAL);
3212 /* strip <CR> if DOS/Windows file,
3213 replace <CR> with <LF> if Macintosh file. */
3216 if (len > 0 && str[len - 1] != '\n') {
3218 if (str[len] == '\r') str[len] = '\n';
3221 gtk_text_buffer_insert(buffer, &iter, str, -1);
3225 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3226 G_CALLBACK(text_inserted),
3228 compose->autowrap = prev_autowrap;
3229 if (compose->autowrap)
3230 compose_wrap_all(compose);
3235 return COMPOSE_INSERT_INVALID_CHARACTER;
3237 return COMPOSE_INSERT_SUCCESS;
3240 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3241 const gchar *filename,
3242 const gchar *content_type)
3250 GtkListStore *store;
3252 gboolean has_binary = FALSE;
3254 if (!is_file_exist(file)) {
3255 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3256 gboolean result = FALSE;
3257 if (file_from_uri && is_file_exist(file_from_uri)) {
3258 result = compose_attach_append(
3259 compose, file_from_uri,
3263 g_free(file_from_uri);
3266 alertpanel_error("File %s doesn't exist\n", filename);
3269 if ((size = get_file_size(file)) < 0) {
3270 alertpanel_error("Can't get file size of %s\n", filename);
3274 alertpanel_error(_("File %s is empty."), filename);
3277 if ((fp = g_fopen(file, "rb")) == NULL) {
3278 alertpanel_error(_("Can't read %s."), filename);
3283 ainfo = g_new0(AttachInfo, 1);
3284 auto_ainfo = g_auto_pointer_new_with_free
3285 (ainfo, (GFreeFunc) compose_attach_info_free);
3286 ainfo->file = g_strdup(file);
3289 ainfo->content_type = g_strdup(content_type);
3290 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3292 MsgFlags flags = {0, 0};
3294 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3295 ainfo->encoding = ENC_7BIT;
3297 ainfo->encoding = ENC_8BIT;
3299 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3300 if (msginfo && msginfo->subject)
3301 name = g_strdup(msginfo->subject);
3303 name = g_path_get_basename(filename ? filename : file);
3305 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3307 procmsg_msginfo_free(msginfo);
3309 if (!g_ascii_strncasecmp(content_type, "text", 4))
3310 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3312 ainfo->encoding = ENC_BASE64;
3313 name = g_path_get_basename(filename ? filename : file);
3314 ainfo->name = g_strdup(name);
3318 ainfo->content_type = procmime_get_mime_type(file);
3319 if (!ainfo->content_type) {
3320 ainfo->content_type =
3321 g_strdup("application/octet-stream");
3322 ainfo->encoding = ENC_BASE64;
3323 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text", 4))
3325 procmime_get_encoding_for_text_file(file, &has_binary);
3327 ainfo->encoding = ENC_BASE64;
3328 name = g_path_get_basename(filename ? filename : file);
3329 ainfo->name = g_strdup(name);
3333 if (ainfo->name != NULL
3334 && !strcmp(ainfo->name, ".")) {
3335 g_free(ainfo->name);
3339 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3340 g_free(ainfo->content_type);
3341 ainfo->content_type = g_strdup("application/octet-stream");
3345 size_text = to_human_readable(size);
3347 store = GTK_LIST_STORE(gtk_tree_view_get_model
3348 (GTK_TREE_VIEW(compose->attach_clist)));
3350 gtk_list_store_append(store, &iter);
3351 gtk_list_store_set(store, &iter,
3352 COL_MIMETYPE, ainfo->content_type,
3353 COL_SIZE, size_text,
3354 COL_NAME, ainfo->name,
3356 COL_AUTODATA, auto_ainfo,
3359 g_auto_pointer_free(auto_ainfo);
3363 static void compose_use_signing(Compose *compose, gboolean use_signing)
3365 GtkItemFactory *ifactory;
3366 GtkWidget *menuitem = NULL;
3368 compose->use_signing = use_signing;
3369 ifactory = gtk_item_factory_from_widget(compose->menubar);
3370 menuitem = gtk_item_factory_get_item
3371 (ifactory, "/Options/Sign");
3372 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
3376 static void compose_use_encryption(Compose *compose, gboolean use_encryption)
3378 GtkItemFactory *ifactory;
3379 GtkWidget *menuitem = NULL;
3381 compose->use_encryption = use_encryption;
3382 ifactory = gtk_item_factory_from_widget(compose->menubar);
3383 menuitem = gtk_item_factory_get_item
3384 (ifactory, "/Options/Encrypt");
3386 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
3390 #define NEXT_PART_NOT_CHILD(info) \
3392 node = info->node; \
3393 while (node->children) \
3394 node = g_node_last_child(node); \
3395 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3398 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3402 MimeInfo *firsttext = NULL;
3403 MimeInfo *encrypted = NULL;
3406 const gchar *partname = NULL;
3408 mimeinfo = procmime_scan_message(msginfo);
3409 if (!mimeinfo) return;
3411 if (mimeinfo->node->children == NULL) {
3412 procmime_mimeinfo_free_all(mimeinfo);
3416 /* find first content part */
3417 child = (MimeInfo *) mimeinfo->node->children->data;
3418 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3419 child = (MimeInfo *)child->node->children->data;
3421 if (child->type == MIMETYPE_TEXT) {
3423 debug_print("First text part found\n");
3424 } else if (compose->mode == COMPOSE_REEDIT &&
3425 child->type == MIMETYPE_APPLICATION &&
3426 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3427 encrypted = (MimeInfo *)child->node->parent->data;
3430 child = (MimeInfo *) mimeinfo->node->children->data;
3431 while (child != NULL) {
3434 if (child == encrypted) {
3435 /* skip this part of tree */
3436 NEXT_PART_NOT_CHILD(child);
3440 if (child->type == MIMETYPE_MULTIPART) {
3441 /* get the actual content */
3442 child = procmime_mimeinfo_next(child);
3446 if (child == firsttext) {
3447 child = procmime_mimeinfo_next(child);
3451 outfile = procmime_get_tmp_file_name(child);
3452 if ((err = procmime_get_part(outfile, child)) < 0)
3453 g_warning("Can't get the part of multipart message. (%s)", strerror(-err));
3455 gchar *content_type;
3457 content_type = procmime_get_content_type_str(child->type, child->subtype);
3459 /* if we meet a pgp signature, we don't attach it, but
3460 * we force signing. */
3461 if ((strcmp(content_type, "application/pgp-signature") &&
3462 strcmp(content_type, "application/pkcs7-signature") &&
3463 strcmp(content_type, "application/x-pkcs7-signature"))
3464 || compose->mode == COMPOSE_REDIRECT) {
3465 partname = procmime_mimeinfo_get_parameter(child, "filename");
3466 if (partname == NULL)
3467 partname = procmime_mimeinfo_get_parameter(child, "name");
3468 if (partname == NULL)
3470 compose_attach_append(compose, outfile,
3471 partname, content_type);
3473 compose_force_signing(compose, compose->account);
3475 g_free(content_type);
3478 NEXT_PART_NOT_CHILD(child);
3480 procmime_mimeinfo_free_all(mimeinfo);
3483 #undef NEXT_PART_NOT_CHILD
3488 WAIT_FOR_INDENT_CHAR,
3489 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3492 /* return indent length, we allow:
3493 indent characters followed by indent characters or spaces/tabs,
3494 alphabets and numbers immediately followed by indent characters,
3495 and the repeating sequences of the above
3496 If quote ends with multiple spaces, only the first one is included. */
3497 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3498 const GtkTextIter *start, gint *len)
3500 GtkTextIter iter = *start;
3504 IndentState state = WAIT_FOR_INDENT_CHAR;
3507 gint alnum_count = 0;
3508 gint space_count = 0;
3511 if (prefs_common.quote_chars == NULL) {
3515 while (!gtk_text_iter_ends_line(&iter)) {
3516 wc = gtk_text_iter_get_char(&iter);
3517 if (g_unichar_iswide(wc))
3519 clen = g_unichar_to_utf8(wc, ch);
3523 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
3524 is_space = g_unichar_isspace(wc);
3526 if (state == WAIT_FOR_INDENT_CHAR) {
3527 if (!is_indent && !g_unichar_isalnum(wc))
3530 quote_len += alnum_count + space_count + 1;
3531 alnum_count = space_count = 0;
3532 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
3535 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
3536 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
3540 else if (is_indent) {
3541 quote_len += alnum_count + space_count + 1;
3542 alnum_count = space_count = 0;
3545 state = WAIT_FOR_INDENT_CHAR;
3549 gtk_text_iter_forward_char(&iter);
3552 if (quote_len > 0 && space_count > 0)
3558 if (quote_len > 0) {
3560 gtk_text_iter_forward_chars(&iter, quote_len);
3561 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
3567 /* return TRUE if the line is itemized */
3568 static gboolean compose_is_itemized(GtkTextBuffer *buffer,
3569 const GtkTextIter *start)
3571 GtkTextIter iter = *start;
3576 if (gtk_text_iter_ends_line(&iter))
3580 wc = gtk_text_iter_get_char(&iter);
3581 if (!g_unichar_isspace(wc))
3583 gtk_text_iter_forward_char(&iter);
3584 if (gtk_text_iter_ends_line(&iter))
3588 clen = g_unichar_to_utf8(wc, ch);
3592 if (!strchr("*-+", ch[0]))
3595 gtk_text_iter_forward_char(&iter);
3596 if (gtk_text_iter_ends_line(&iter))
3598 wc = gtk_text_iter_get_char(&iter);
3599 if (g_unichar_isspace(wc))
3605 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
3606 const GtkTextIter *start,
3607 GtkTextIter *break_pos,
3611 GtkTextIter iter = *start, line_end = *start;
3612 PangoLogAttr *attrs;
3619 gboolean can_break = FALSE;
3620 gboolean do_break = FALSE;
3621 gboolean was_white = FALSE;
3622 gboolean prev_dont_break = FALSE;
3624 gtk_text_iter_forward_to_line_end(&line_end);
3625 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
3626 len = g_utf8_strlen(str, -1);
3630 g_warning("compose_get_line_break_pos: len = 0!\n");
3634 /* g_print("breaking line: %d: %s (len = %d)\n",
3635 gtk_text_iter_get_line(&iter), str, len); */
3637 attrs = g_new(PangoLogAttr, len + 1);
3639 pango_default_break(str, -1, NULL, attrs, len + 1);
3643 /* skip quote and leading spaces */
3644 for (i = 0; *p != '\0' && i < len; i++) {
3647 wc = g_utf8_get_char(p);
3648 if (i >= quote_len && !g_unichar_isspace(wc))
3650 if (g_unichar_iswide(wc))
3652 else if (*p == '\t')
3656 p = g_utf8_next_char(p);
3659 for (; *p != '\0' && i < len; i++) {
3660 PangoLogAttr *attr = attrs + i;
3664 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
3667 was_white = attr->is_white;
3669 /* don't wrap URI */
3670 if ((uri_len = get_uri_len(p)) > 0) {
3672 if (pos > 0 && col > max_col) {
3682 wc = g_utf8_get_char(p);
3683 if (g_unichar_iswide(wc)) {
3685 if (prev_dont_break && can_break && attr->is_line_break)
3687 } else if (*p == '\t')
3691 if (pos > 0 && col > max_col) {
3696 if (*p == '-' || *p == '/')
3697 prev_dont_break = TRUE;
3699 prev_dont_break = FALSE;
3701 p = g_utf8_next_char(p);
3705 debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col);
3710 *break_pos = *start;
3711 gtk_text_iter_set_line_offset(break_pos, pos);
3716 static gboolean compose_join_next_line(Compose *compose,
3717 GtkTextBuffer *buffer,
3719 const gchar *quote_str)
3721 GtkTextIter iter_ = *iter, cur, prev, next, end;
3722 PangoLogAttr attrs[3];
3724 gchar *next_quote_str;
3727 gboolean keep_cursor = FALSE;
3729 if (!gtk_text_iter_forward_line(&iter_) ||
3730 gtk_text_iter_ends_line(&iter_))
3733 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
3735 if ((quote_str || next_quote_str) &&
3736 strcmp2(quote_str, next_quote_str) != 0) {
3737 g_free(next_quote_str);
3740 g_free(next_quote_str);
3743 if (quote_len > 0) {
3744 gtk_text_iter_forward_chars(&end, quote_len);
3745 if (gtk_text_iter_ends_line(&end))
3749 /* don't join itemized lines */
3750 if (compose_is_itemized(buffer, &end))
3753 /* don't join signature separator */
3754 if (compose_is_sig_separator(compose, buffer, &iter_))
3757 /* delete quote str */
3759 gtk_text_buffer_delete(buffer, &iter_, &end);
3761 /* don't join line breaks put by the user */
3763 gtk_text_iter_backward_char(&cur);
3764 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
3765 gtk_text_iter_forward_char(&cur);
3769 gtk_text_iter_forward_char(&cur);
3770 /* delete linebreak and extra spaces */
3771 while (gtk_text_iter_backward_char(&cur)) {
3772 wc1 = gtk_text_iter_get_char(&cur);
3773 if (!g_unichar_isspace(wc1))
3778 while (!gtk_text_iter_ends_line(&cur)) {
3779 wc1 = gtk_text_iter_get_char(&cur);
3780 if (!g_unichar_isspace(wc1))
3782 gtk_text_iter_forward_char(&cur);
3785 if (!gtk_text_iter_equal(&prev, &next)) {
3788 mark = gtk_text_buffer_get_insert(buffer);
3789 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
3790 if (gtk_text_iter_equal(&prev, &cur))
3792 gtk_text_buffer_delete(buffer, &prev, &next);
3796 /* insert space if required */
3797 gtk_text_iter_backward_char(&prev);
3798 wc1 = gtk_text_iter_get_char(&prev);
3799 wc2 = gtk_text_iter_get_char(&next);
3800 gtk_text_iter_forward_char(&next);
3801 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
3802 pango_default_break(str, -1, NULL, attrs, 3);
3803 if (!attrs[1].is_line_break ||
3804 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
3805 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
3807 gtk_text_iter_backward_char(&iter_);
3808 gtk_text_buffer_place_cursor(buffer, &iter_);
3817 #define ADD_TXT_POS(bp_, ep_, pti_) \
3818 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
3819 last = last->next; \
3820 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
3821 last->next = NULL; \
3823 g_warning("alloc error scanning URIs\n"); \
3826 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
3828 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3829 GtkTextBuffer *buffer;
3830 GtkTextIter iter, break_pos, end_of_line;
3831 gchar *quote_str = NULL;
3833 gboolean wrap_quote = prefs_common.linewrap_quote;
3834 gboolean prev_autowrap = compose->autowrap;
3835 gint startq_offset = -1, noq_offset = -1;
3836 gint uri_start = -1, uri_stop = -1;
3837 gint nouri_start = -1, nouri_stop = -1;
3838 gint num_blocks = 0;
3839 gint quotelevel = -1;
3840 gboolean modified = force;
3841 gboolean removed = FALSE;
3842 gboolean modified_before_remove = FALSE;
3844 gboolean start = TRUE;
3849 if (compose->draft_timeout_tag == -2) {
3853 compose->autowrap = FALSE;
3855 buffer = gtk_text_view_get_buffer(text);
3856 undo_wrapping(compose->undostruct, TRUE);
3861 mark = gtk_text_buffer_get_insert(buffer);
3862 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3866 if (compose->draft_timeout_tag == -2) {
3867 if (gtk_text_iter_ends_line(&iter)) {
3868 while (gtk_text_iter_ends_line(&iter) &&
3869 gtk_text_iter_forward_line(&iter))
3872 while (gtk_text_iter_backward_line(&iter)) {
3873 if (gtk_text_iter_ends_line(&iter)) {
3874 gtk_text_iter_forward_line(&iter);
3880 /* move to line start */
3881 gtk_text_iter_set_line_offset(&iter, 0);
3883 /* go until paragraph end (empty line) */
3884 while (start || !gtk_text_iter_ends_line(&iter)) {
3885 gchar *scanpos = NULL;
3886 /* parse table - in order of priority */
3888 const gchar *needle; /* token */
3890 /* token search function */
3891 gchar *(*search) (const gchar *haystack,
3892 const gchar *needle);
3893 /* part parsing function */
3894 gboolean (*parse) (const gchar *start,
3895 const gchar *scanpos,
3899 /* part to URI function */
3900 gchar *(*build_uri) (const gchar *bp,
3904 static struct table parser[] = {
3905 {"http://", strcasestr, get_uri_part, make_uri_string},
3906 {"https://", strcasestr, get_uri_part, make_uri_string},
3907 {"ftp://", strcasestr, get_uri_part, make_uri_string},
3908 {"sftp://", strcasestr, get_uri_part, make_uri_string},
3909 {"www.", strcasestr, get_uri_part, make_http_string},
3910 {"mailto:", strcasestr, get_uri_part, make_uri_string},
3911 {"@", strcasestr, get_email_part, make_email_string}
3913 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
3914 gint last_index = PARSE_ELEMS;
3916 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
3920 if (!prev_autowrap && num_blocks == 0) {
3922 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3923 G_CALLBACK(text_inserted),
3926 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
3929 uri_start = uri_stop = -1;
3931 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
3934 debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str);
3935 if (startq_offset == -1)
3936 startq_offset = gtk_text_iter_get_offset(&iter);
3937 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
3938 if (quotelevel > 2) {
3939 /* recycle colors */
3940 if (prefs_common.recycle_quote_colors)
3949 if (startq_offset == -1)
3950 noq_offset = gtk_text_iter_get_offset(&iter);
3954 if (prev_autowrap == FALSE && !force && !wrap_quote) {
3957 if (gtk_text_iter_ends_line(&iter)) {
3959 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
3960 prefs_common.linewrap_len,
3962 GtkTextIter prev, next, cur;
3964 if (prev_autowrap != FALSE || force) {
3965 compose->automatic_break = TRUE;
3967 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
3968 compose->automatic_break = FALSE;
3969 } else if (quote_str && wrap_quote) {
3970 compose->automatic_break = TRUE;
3972 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
3973 compose->automatic_break = FALSE;
3976 /* remove trailing spaces */
3978 gtk_text_iter_backward_char(&cur);
3980 while (!gtk_text_iter_starts_line(&cur)) {
3983 gtk_text_iter_backward_char(&cur);
3984 wc = gtk_text_iter_get_char(&cur);
3985 if (!g_unichar_isspace(wc))
3989 if (!gtk_text_iter_equal(&prev, &next)) {
3990 gtk_text_buffer_delete(buffer, &prev, &next);
3992 gtk_text_iter_forward_char(&break_pos);
3996 gtk_text_buffer_insert(buffer, &break_pos,
4000 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4002 /* move iter to current line start */
4003 gtk_text_iter_set_line_offset(&iter, 0);
4010 /* move iter to next line start */
4016 if (!prev_autowrap && num_blocks > 0) {
4018 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4019 G_CALLBACK(text_inserted),
4023 while (!gtk_text_iter_ends_line(&end_of_line)) {
4024 gtk_text_iter_forward_char(&end_of_line);
4026 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4028 nouri_start = gtk_text_iter_get_offset(&iter);
4029 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4031 walk_pos = gtk_text_iter_get_offset(&iter);
4032 /* FIXME: this looks phony. scanning for anything in the parse table */
4033 for (n = 0; n < PARSE_ELEMS; n++) {
4036 tmp = parser[n].search(walk, parser[n].needle);
4038 if (scanpos == NULL || tmp < scanpos) {
4047 /* check if URI can be parsed */
4048 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4049 (const gchar **)&ep, FALSE)
4050 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4054 strlen(parser[last_index].needle);
4057 uri_start = walk_pos + (bp - o_walk);
4058 uri_stop = walk_pos + (ep - o_walk);
4062 gtk_text_iter_forward_line(&iter);
4065 if (startq_offset != -1) {
4066 GtkTextIter startquote, endquote;
4067 gtk_text_buffer_get_iter_at_offset(
4068 buffer, &startquote, startq_offset);
4071 switch (quotelevel) {
4073 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4074 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4075 gtk_text_buffer_apply_tag_by_name(
4076 buffer, "quote0", &startquote, &endquote);
4077 gtk_text_buffer_remove_tag_by_name(
4078 buffer, "quote1", &startquote, &endquote);
4079 gtk_text_buffer_remove_tag_by_name(
4080 buffer, "quote2", &startquote, &endquote);
4085 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4086 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4087 gtk_text_buffer_apply_tag_by_name(
4088 buffer, "quote1", &startquote, &endquote);
4089 gtk_text_buffer_remove_tag_by_name(
4090 buffer, "quote0", &startquote, &endquote);
4091 gtk_text_buffer_remove_tag_by_name(
4092 buffer, "quote2", &startquote, &endquote);
4097 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4098 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4099 gtk_text_buffer_apply_tag_by_name(
4100 buffer, "quote2", &startquote, &endquote);
4101 gtk_text_buffer_remove_tag_by_name(
4102 buffer, "quote0", &startquote, &endquote);
4103 gtk_text_buffer_remove_tag_by_name(
4104 buffer, "quote1", &startquote, &endquote);
4110 } else if (noq_offset != -1) {
4111 GtkTextIter startnoquote, endnoquote;
4112 gtk_text_buffer_get_iter_at_offset(
4113 buffer, &startnoquote, noq_offset);
4116 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4117 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4118 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4119 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4120 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4121 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4122 gtk_text_buffer_remove_tag_by_name(
4123 buffer, "quote0", &startnoquote, &endnoquote);
4124 gtk_text_buffer_remove_tag_by_name(
4125 buffer, "quote1", &startnoquote, &endnoquote);
4126 gtk_text_buffer_remove_tag_by_name(
4127 buffer, "quote2", &startnoquote, &endnoquote);
4133 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4134 GtkTextIter nouri_start_iter, nouri_end_iter;
4135 gtk_text_buffer_get_iter_at_offset(
4136 buffer, &nouri_start_iter, nouri_start);
4137 gtk_text_buffer_get_iter_at_offset(
4138 buffer, &nouri_end_iter, nouri_stop);
4139 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4140 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4141 gtk_text_buffer_remove_tag_by_name(
4142 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4143 modified_before_remove = modified;
4148 if (uri_start > 0 && uri_stop > 0) {
4149 GtkTextIter uri_start_iter, uri_end_iter, back;
4150 gtk_text_buffer_get_iter_at_offset(
4151 buffer, &uri_start_iter, uri_start);
4152 gtk_text_buffer_get_iter_at_offset(
4153 buffer, &uri_end_iter, uri_stop);
4154 back = uri_end_iter;
4155 gtk_text_iter_backward_char(&back);
4156 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4157 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4158 gtk_text_buffer_apply_tag_by_name(
4159 buffer, "link", &uri_start_iter, &uri_end_iter);
4161 if (removed && !modified_before_remove) {
4167 debug_print("not modified, out after %d lines\n", lines);
4172 debug_print("modified, out after %d lines\n", lines);
4176 undo_wrapping(compose->undostruct, FALSE);
4177 compose->autowrap = prev_autowrap;
4182 void compose_action_cb(void *data)
4184 Compose *compose = (Compose *)data;
4185 compose_wrap_all(compose);
4188 static void compose_wrap_all(Compose *compose)
4190 compose_wrap_all_full(compose, FALSE);
4193 static void compose_wrap_all_full(Compose *compose, gboolean force)
4195 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4196 GtkTextBuffer *buffer;
4198 gboolean modified = TRUE;
4200 buffer = gtk_text_view_get_buffer(text);
4202 gtk_text_buffer_get_start_iter(buffer, &iter);
4203 while (!gtk_text_iter_is_end(&iter) && modified)
4204 modified = compose_beautify_paragraph(compose, &iter, force);
4208 static void compose_set_title(Compose *compose)
4214 edited = compose->modified ? _(" [Edited]") : "";
4216 subject = gtk_editable_get_chars(
4217 GTK_EDITABLE(compose->subject_entry), 0, -1);
4220 if (subject && strlen(subject))
4221 str = g_strdup_printf(_("%s - Compose message%s"),
4224 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4226 str = g_strdup(_("Compose message"));
4229 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4235 * compose_current_mail_account:
4237 * Find a current mail account (the currently selected account, or the
4238 * default account, if a news account is currently selected). If a
4239 * mail account cannot be found, display an error message.
4241 * Return value: Mail account, or NULL if not found.
4243 static PrefsAccount *
4244 compose_current_mail_account(void)
4248 if (cur_account && cur_account->protocol != A_NNTP)
4251 ac = account_get_default();
4252 if (!ac || ac->protocol == A_NNTP) {
4253 alertpanel_error(_("Account for sending mail is not specified.\n"
4254 "Please select a mail account before sending."));
4261 #define QUOTE_IF_REQUIRED(out, str) \
4263 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4267 len = strlen(str) + 3; \
4268 if ((__tmp = alloca(len)) == NULL) { \
4269 g_warning("can't allocate memory\n"); \
4270 g_string_free(header, TRUE); \
4273 g_snprintf(__tmp, len, "\"%s\"", str); \
4278 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4279 g_warning("can't allocate memory\n"); \
4280 g_string_free(header, TRUE); \
4283 strcpy(__tmp, str); \
4289 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4291 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4295 len = strlen(str) + 3; \
4296 if ((__tmp = alloca(len)) == NULL) { \
4297 g_warning("can't allocate memory\n"); \
4300 g_snprintf(__tmp, len, "\"%s\"", str); \
4305 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4306 g_warning("can't allocate memory\n"); \
4309 strcpy(__tmp, str); \
4315 static void compose_select_account(Compose *compose, PrefsAccount *account,
4318 GtkItemFactory *ifactory;
4321 g_return_if_fail(account != NULL);
4323 compose->account = account;
4325 if (account->name && *account->name) {
4327 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4328 from = g_strdup_printf("%s <%s>",
4329 buf, account->address);
4330 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4332 from = g_strdup_printf("<%s>",
4334 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4339 compose_set_title(compose);
4341 ifactory = gtk_item_factory_from_widget(compose->menubar);
4343 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4344 menu_set_active(ifactory, "/Options/Sign", TRUE);
4346 menu_set_active(ifactory, "/Options/Sign", FALSE);
4347 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4348 menu_set_active(ifactory, "/Options/Encrypt", TRUE);
4350 menu_set_active(ifactory, "/Options/Encrypt", FALSE);
4352 activate_privacy_system(compose, account, FALSE);
4354 if (!init && compose->mode != COMPOSE_REDIRECT) {
4355 undo_block(compose->undostruct);
4356 compose_insert_sig(compose, TRUE);
4357 undo_unblock(compose->undostruct);
4361 /* use account's dict info if set */
4362 if (compose->gtkaspell) {
4363 if (account->enable_default_dictionary)
4364 gtkaspell_change_dict(compose->gtkaspell,
4365 account->default_dictionary, FALSE);
4366 if (account->enable_default_alt_dictionary)
4367 gtkaspell_change_alt_dict(compose->gtkaspell,
4368 account->default_alt_dictionary);
4369 if (account->enable_default_dictionary
4370 || account->enable_default_alt_dictionary)
4371 compose_spell_menu_changed(compose);
4376 gboolean compose_check_for_valid_recipient(Compose *compose) {
4377 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
4378 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
4379 gboolean recipient_found = FALSE;
4383 /* free to and newsgroup list */
4384 slist_free_strings(compose->to_list);
4385 g_slist_free(compose->to_list);
4386 compose->to_list = NULL;
4388 slist_free_strings(compose->newsgroup_list);
4389 g_slist_free(compose->newsgroup_list);
4390 compose->newsgroup_list = NULL;
4392 /* search header entries for to and newsgroup entries */
4393 for (list = compose->header_list; list; list = list->next) {
4396 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_COMBO(((ComposeHeaderEntry *)list->data)->combo)->entry), 0, -1);
4397 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4400 if (entry[0] != '\0') {
4401 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
4402 if (!strcmp(header, prefs_common_translated_header_name(*strptr))) {
4403 compose->to_list = address_list_append(compose->to_list, entry);
4404 recipient_found = TRUE;
4407 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
4408 if (!strcmp(header, prefs_common_translated_header_name(*strptr))) {
4409 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
4410 recipient_found = TRUE;
4417 return recipient_found;
4420 static gboolean compose_check_for_set_recipients(Compose *compose)
4422 if (compose->account->set_autocc && compose->account->auto_cc) {
4423 gboolean found_other = FALSE;
4425 /* search header entries for to and newsgroup entries */
4426 for (list = compose->header_list; list; list = list->next) {
4429 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4430 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_COMBO(((ComposeHeaderEntry *)list->data)->combo)->entry), 0, -1);
4432 if (strcmp(entry, compose->account->auto_cc)
4433 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
4443 if (compose->batch) {
4444 gtk_widget_show_all(compose->window);
4446 aval = alertpanel(_("Send"),
4447 _("The only recipient is the default CC address. Send anyway?"),
4448 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4449 if (aval != G_ALERTALTERNATE)
4453 if (compose->account->set_autobcc && compose->account->auto_bcc) {
4454 gboolean found_other = FALSE;
4456 /* search header entries for to and newsgroup entries */
4457 for (list = compose->header_list; list; list = list->next) {
4460 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4461 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_COMBO(((ComposeHeaderEntry *)list->data)->combo)->entry), 0, -1);
4463 if (strcmp(entry, compose->account->auto_bcc)
4464 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
4474 if (compose->batch) {
4475 gtk_widget_show_all(compose->window);
4477 aval = alertpanel(_("Send"),
4478 _("The only recipient is the default BCC address. Send anyway?"),
4479 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4480 if (aval != G_ALERTALTERNATE)
4487 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
4491 if (compose_check_for_valid_recipient(compose) == FALSE) {
4492 if (compose->batch) {
4493 gtk_widget_show_all(compose->window);
4495 alertpanel_error(_("Recipient is not specified."));
4499 if (compose_check_for_set_recipients(compose) == FALSE) {
4503 if (!compose->batch) {
4504 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
4505 if (*str == '\0' && check_everything == TRUE &&
4506 compose->mode != COMPOSE_REDIRECT) {
4508 gchar *button_label;
4511 if (compose->sending)
4512 button_label = _("+_Send");
4514 button_label = _("+_Queue");
4515 message = g_strdup_printf(_("Subject is empty. %s it anyway?"),
4516 compose->sending?_("Send"):_("Queue"));
4518 aval = alertpanel(compose->sending?_("Send"):_("Send later"), message,
4519 GTK_STOCK_CANCEL, button_label, NULL);
4521 if (aval != G_ALERTALTERNATE)
4526 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
4532 gint compose_send(Compose *compose)
4535 FolderItem *folder = NULL;
4537 gchar *msgpath = NULL;
4538 gboolean discard_window = FALSE;
4539 gchar *errstr = NULL;
4540 gchar *tmsgid = NULL;
4541 MainWindow *mainwin = mainwindow_get_mainwindow();
4542 gboolean queued_removed = FALSE;
4544 if (prefs_common.send_dialog_invisible
4545 || compose->batch == TRUE)
4546 discard_window = TRUE;
4548 compose_allow_user_actions (compose, FALSE);
4549 compose->sending = TRUE;
4551 if (compose_check_entries(compose, TRUE) == FALSE) {
4552 if (compose->batch) {
4553 gtk_widget_show_all(compose->window);
4559 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
4562 if (compose->batch) {
4563 gtk_widget_show_all(compose->window);
4566 alertpanel_error(_("Could not queue message for sending:\n\n"
4567 "Charset conversion failed."));
4568 } else if (val == -5) {
4569 alertpanel_error(_("Could not queue message for sending:\n\n"
4570 "Couldn't get recipient encryption key."));
4571 } else if (val == -6) {
4573 } else if (val == -3) {
4574 if (privacy_peek_error())
4575 alertpanel_error(_("Could not queue message for sending:\n\n"
4576 "Signature failed: %s"), privacy_get_error());
4577 } else if (val == -2 && errno != 0) {
4578 alertpanel_error(_("Could not queue message for sending:\n\n%s."), strerror(errno));
4580 alertpanel_error(_("Could not queue message for sending."));
4585 tmsgid = g_strdup(compose->msgid);
4586 if (discard_window) {
4587 compose->sending = FALSE;
4588 compose_close(compose);
4589 /* No more compose access in the normal codepath
4590 * after this point! */
4595 alertpanel_error(_("The message was queued but could not be "
4596 "sent.\nUse \"Send queued messages\" from "
4597 "the main window to retry."));
4598 if (!discard_window) {
4605 if (msgpath == NULL) {
4606 msgpath = folder_item_fetch_msg(folder, msgnum);
4607 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
4610 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
4614 if (!discard_window) {
4616 if (!queued_removed)
4617 folder_item_remove_msg(folder, msgnum);
4618 folder_item_scan(folder);
4620 /* make sure we delete that */
4621 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
4623 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
4624 folder_item_remove_msg(folder, tmp->msgnum);
4625 procmsg_msginfo_free(tmp);
4632 if (!queued_removed)
4633 folder_item_remove_msg(folder, msgnum);
4634 folder_item_scan(folder);
4636 /* make sure we delete that */
4637 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
4639 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
4640 folder_item_remove_msg(folder, tmp->msgnum);
4641 procmsg_msginfo_free(tmp);
4644 if (!discard_window) {
4645 compose->sending = FALSE;
4646 compose_allow_user_actions (compose, TRUE);
4647 compose_close(compose);
4651 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
4652 "the main window to retry."), errstr);
4655 alertpanel_error_log(_("The message was queued but could not be "
4656 "sent.\nUse \"Send queued messages\" from "
4657 "the main window to retry."));
4659 if (!discard_window) {
4668 toolbar_main_set_sensitive(mainwin);
4669 main_window_set_menu_sensitive(mainwin);
4675 compose_allow_user_actions (compose, TRUE);
4676 compose->sending = FALSE;
4677 compose->modified = TRUE;
4678 toolbar_main_set_sensitive(mainwin);
4679 main_window_set_menu_sensitive(mainwin);
4684 static gboolean compose_use_attach(Compose *compose)
4686 GtkTreeModel *model = gtk_tree_view_get_model
4687 (GTK_TREE_VIEW(compose->attach_clist));
4688 return gtk_tree_model_iter_n_children(model, NULL) > 0;
4691 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
4694 gchar buf[BUFFSIZE];
4696 gboolean first_to_address;
4697 gboolean first_cc_address;
4699 ComposeHeaderEntry *headerentry;
4700 const gchar *headerentryname;
4701 const gchar *cc_hdr;
4702 const gchar *to_hdr;
4704 debug_print("Writing redirect header\n");
4706 cc_hdr = prefs_common_translated_header_name("Cc:");
4707 to_hdr = prefs_common_translated_header_name("To:");
4709 first_to_address = TRUE;
4710 for (list = compose->header_list; list; list = list->next) {
4711 headerentry = ((ComposeHeaderEntry *)list->data);
4712 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(headerentry->combo)->entry));
4714 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
4715 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
4716 Xstrdup_a(str, entstr, return -1);
4718 if (str[0] != '\0') {
4719 compose_convert_header
4720 (compose, buf, sizeof(buf), str,
4721 strlen("Resent-To") + 2, TRUE);
4723 if (first_to_address) {
4724 fprintf(fp, "Resent-To: ");
4725 first_to_address = FALSE;
4729 fprintf(fp, "%s", buf);
4733 if (!first_to_address) {
4737 first_cc_address = TRUE;
4738 for (list = compose->header_list; list; list = list->next) {
4739 headerentry = ((ComposeHeaderEntry *)list->data);
4740 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(headerentry->combo)->entry));
4742 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
4743 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
4744 Xstrdup_a(str, strg, return -1);
4746 if (str[0] != '\0') {
4747 compose_convert_header
4748 (compose, buf, sizeof(buf), str,
4749 strlen("Resent-Cc") + 2, TRUE);
4751 if (first_cc_address) {
4752 fprintf(fp, "Resent-Cc: ");
4753 first_cc_address = FALSE;
4757 fprintf(fp, "%s", buf);
4761 if (!first_cc_address) {
4768 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
4770 gchar buf[BUFFSIZE];
4772 const gchar *entstr;
4773 /* struct utsname utsbuf; */
4775 g_return_val_if_fail(fp != NULL, -1);
4776 g_return_val_if_fail(compose->account != NULL, -1);
4777 g_return_val_if_fail(compose->account->address != NULL, -1);
4780 get_rfc822_date(buf, sizeof(buf));
4781 fprintf(fp, "Resent-Date: %s\n", buf);
4784 if (compose->account->name && *compose->account->name) {
4785 compose_convert_header
4786 (compose, buf, sizeof(buf), compose->account->name,
4787 strlen("From: "), TRUE);
4788 fprintf(fp, "Resent-From: %s <%s>\n",
4789 buf, compose->account->address);
4791 fprintf(fp, "Resent-From: %s\n", compose->account->address);
4794 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
4795 if (*entstr != '\0') {
4796 Xstrdup_a(str, entstr, return -1);
4799 compose_convert_header(compose, buf, sizeof(buf), str,
4800 strlen("Subject: "), FALSE);
4801 fprintf(fp, "Subject: %s\n", buf);
4805 /* Resent-Message-ID */
4806 if (compose->account->set_domain && compose->account->domain) {
4807 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
4808 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
4809 g_snprintf(buf, sizeof(buf), "%s",
4810 strchr(compose->account->address, '@') ?
4811 strchr(compose->account->address, '@')+1 :
4812 compose->account->address);
4814 g_snprintf(buf, sizeof(buf), "%s", "");
4816 generate_msgid(buf, sizeof(buf));
4817 fprintf(fp, "Resent-Message-ID: <%s>\n", buf);
4818 compose->msgid = g_strdup(buf);
4820 compose_redirect_write_headers_from_headerlist(compose, fp);
4822 /* separator between header and body */
4828 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
4832 gchar buf[BUFFSIZE];
4834 gboolean skip = FALSE;
4835 gchar *not_included[]={
4836 "Return-Path:", "Delivered-To:", "Received:",
4837 "Subject:", "X-UIDL:", "AF:",
4838 "NF:", "PS:", "SRH:",
4839 "SFN:", "DSR:", "MID:",
4840 "CFG:", "PT:", "S:",
4841 "RQ:", "SSV:", "NSV:",
4842 "SSH:", "R:", "MAID:",
4843 "NAID:", "RMID:", "FMID:",
4844 "SCF:", "RRCPT:", "NG:",
4845 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
4846 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
4847 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
4848 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
4851 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
4852 FILE_OP_ERROR(compose->redirect_filename, "fopen");
4856 while (procheader_get_one_field_asis(buf, sizeof(buf), fp) != -1) {
4858 for (i = 0; not_included[i] != NULL; i++) {
4859 if (g_ascii_strncasecmp(buf, not_included[i],
4860 strlen(not_included[i])) == 0) {
4867 if (fputs(buf, fdest) == -1)
4870 if (!prefs_common.redirect_keep_from) {
4871 if (g_ascii_strncasecmp(buf, "From:",
4872 strlen("From:")) == 0) {
4873 fputs(" (by way of ", fdest);
4874 if (compose->account->name
4875 && *compose->account->name) {
4876 compose_convert_header
4877 (compose, buf, sizeof(buf),
4878 compose->account->name,
4881 fprintf(fdest, "%s <%s>",
4883 compose->account->address);
4885 fprintf(fdest, "%s",
4886 compose->account->address);
4891 if (fputs("\n", fdest) == -1)
4895 compose_redirect_write_headers(compose, fdest);
4897 while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
4898 if (fwrite(buf, sizeof(gchar), len, fdest) != len)
4911 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
4913 GtkTextBuffer *buffer;
4914 GtkTextIter start, end;
4917 const gchar *out_codeset;
4918 EncodingType encoding;
4919 MimeInfo *mimemsg, *mimetext;
4922 if (action == COMPOSE_WRITE_FOR_SEND)
4923 attach_parts = TRUE;
4925 /* create message MimeInfo */
4926 mimemsg = procmime_mimeinfo_new();
4927 mimemsg->type = MIMETYPE_MESSAGE;
4928 mimemsg->subtype = g_strdup("rfc822");
4929 mimemsg->content = MIMECONTENT_MEM;
4930 mimemsg->tmp = TRUE; /* must free content later */
4931 mimemsg->data.mem = compose_get_header(compose);
4933 /* Create text part MimeInfo */
4934 /* get all composed text */
4935 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
4936 gtk_text_buffer_get_start_iter(buffer, &start);
4937 gtk_text_buffer_get_end_iter(buffer, &end);
4938 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
4939 if (is_ascii_str(chars)) {
4942 out_codeset = CS_US_ASCII;
4943 encoding = ENC_7BIT;
4945 const gchar *src_codeset = CS_INTERNAL;
4947 out_codeset = conv_get_charset_str(compose->out_encoding);
4950 gchar *test_conv_global_out = NULL;
4951 gchar *test_conv_reply = NULL;
4953 /* automatic mode. be automatic. */
4954 codeconv_set_strict(TRUE);
4956 out_codeset = conv_get_outgoing_charset_str();
4958 debug_print("trying to convert to %s\n", out_codeset);
4959 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
4962 if (!test_conv_global_out && compose->orig_charset
4963 && strcmp(compose->orig_charset, CS_US_ASCII)) {
4964 out_codeset = compose->orig_charset;
4965 debug_print("failure; trying to convert to %s\n", out_codeset);
4966 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
4969 if (!test_conv_global_out && !test_conv_reply) {
4971 out_codeset = CS_INTERNAL;
4972 debug_print("failure; finally using %s\n", out_codeset);
4974 g_free(test_conv_global_out);
4975 g_free(test_conv_reply);
4976 codeconv_set_strict(FALSE);
4979 if (!g_ascii_strcasecmp(out_codeset, CS_US_ASCII))
4980 out_codeset = CS_ISO_8859_1;
4982 if (prefs_common.encoding_method == CTE_BASE64)
4983 encoding = ENC_BASE64;
4984 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
4985 encoding = ENC_QUOTED_PRINTABLE;
4986 else if (prefs_common.encoding_method == CTE_8BIT)
4987 encoding = ENC_8BIT;
4989 encoding = procmime_get_encoding_for_charset(out_codeset);
4991 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
4992 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
4994 if (action == COMPOSE_WRITE_FOR_SEND) {
4995 codeconv_set_strict(TRUE);
4996 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
4997 codeconv_set_strict(FALSE);
5003 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5004 "to the specified %s charset.\n"
5005 "Send it as %s?"), out_codeset, src_codeset);
5006 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL, _("+_Send"), NULL, FALSE,
5007 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5010 if (aval != G_ALERTALTERNATE) {
5015 out_codeset = src_codeset;
5021 out_codeset = src_codeset;
5027 if (encoding == ENC_8BIT || encoding == ENC_7BIT) {
5028 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5029 strstr(buf, "\nFrom ") != NULL) {
5030 encoding = ENC_QUOTED_PRINTABLE;
5034 mimetext = procmime_mimeinfo_new();
5035 mimetext->content = MIMECONTENT_MEM;
5036 mimetext->tmp = TRUE; /* must free content later */
5037 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5038 * and free the data, which we need later. */
5039 mimetext->data.mem = g_strdup(buf);
5040 mimetext->type = MIMETYPE_TEXT;
5041 mimetext->subtype = g_strdup("plain");
5042 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5043 g_strdup(out_codeset));
5045 /* protect trailing spaces when signing message */
5046 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5047 privacy_system_can_sign(compose->privacy_system)) {
5048 encoding = ENC_QUOTED_PRINTABLE;
5051 debug_print("main text: %zd bytes encoded as %s in %d\n",
5052 strlen(buf), out_codeset, encoding);
5054 /* check for line length limit */
5055 if (action == COMPOSE_WRITE_FOR_SEND &&
5056 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5057 check_line_length(buf, 1000, &line) < 0) {
5061 msg = g_strdup_printf
5062 (_("Line %d exceeds the line length limit (998 bytes).\n"
5063 "The contents of the message might be broken on the way to the delivery.\n"
5065 "Send it anyway?"), line + 1);
5066 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5068 if (aval != G_ALERTALTERNATE) {
5074 if (encoding != ENC_UNKNOWN)
5075 procmime_encode_content(mimetext, encoding);
5077 /* append attachment parts */
5078 if (compose_use_attach(compose) && attach_parts) {
5079 MimeInfo *mimempart;
5080 gchar *boundary = NULL;
5081 mimempart = procmime_mimeinfo_new();
5082 mimempart->content = MIMECONTENT_EMPTY;
5083 mimempart->type = MIMETYPE_MULTIPART;
5084 mimempart->subtype = g_strdup("mixed");
5088 boundary = generate_mime_boundary(NULL);
5089 } while (strstr(buf, boundary) != NULL);
5091 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5094 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5096 g_node_append(mimempart->node, mimetext->node);
5097 g_node_append(mimemsg->node, mimempart->node);
5099 compose_add_attachments(compose, mimempart);
5101 g_node_append(mimemsg->node, mimetext->node);
5105 /* sign message if sending */
5106 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5107 privacy_system_can_sign(compose->privacy_system))
5108 if (!privacy_sign(compose->privacy_system, mimemsg, compose->account))
5111 procmime_write_mimeinfo(mimemsg, fp);
5113 procmime_mimeinfo_free_all(mimemsg);
5118 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5120 GtkTextBuffer *buffer;
5121 GtkTextIter start, end;
5126 if ((fp = g_fopen(file, "wb")) == NULL) {
5127 FILE_OP_ERROR(file, "fopen");
5131 /* chmod for security */
5132 if (change_file_mode_rw(fp, file) < 0) {
5133 FILE_OP_ERROR(file, "chmod");
5134 g_warning("can't change file mode\n");
5137 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5138 gtk_text_buffer_get_start_iter(buffer, &start);
5139 gtk_text_buffer_get_end_iter(buffer, &end);
5140 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5142 chars = conv_codeset_strdup
5143 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5146 if (!chars) return -1;
5149 len = strlen(chars);
5150 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5151 FILE_OP_ERROR(file, "fwrite");
5160 if (fclose(fp) == EOF) {
5161 FILE_OP_ERROR(file, "fclose");
5168 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5171 MsgInfo *msginfo = compose->targetinfo;
5173 g_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5174 if (!msginfo) return -1;
5176 if (!force && MSG_IS_LOCKED(msginfo->flags))
5179 item = msginfo->folder;
5180 g_return_val_if_fail(item != NULL, -1);
5182 if (procmsg_msg_exist(msginfo) &&
5183 (folder_has_parent_of_type(item, F_QUEUE) ||
5184 folder_has_parent_of_type(item, F_DRAFT)
5185 || msginfo == compose->autosaved_draft)) {
5186 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5187 g_warning("can't remove the old message\n");
5190 debug_print("removed reedit target %d\n", msginfo->msgnum);
5197 static void compose_remove_draft(Compose *compose)
5200 MsgInfo *msginfo = compose->targetinfo;
5201 drafts = account_get_special_folder(compose->account, F_DRAFT);
5203 if (procmsg_msg_exist(msginfo)) {
5204 folder_item_remove_msg(drafts, msginfo->msgnum);
5209 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5210 gboolean remove_reedit_target)
5212 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5215 static gboolean compose_warn_encryption(Compose *compose)
5217 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5218 AlertValue val = G_ALERTALTERNATE;
5220 if (warning == NULL)
5223 val = alertpanel_full(_("Encryption warning"), warning,
5224 GTK_STOCK_CANCEL, _("+C_ontinue"), NULL,
5225 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5226 if (val & G_ALERTDISABLE) {
5227 val &= ~G_ALERTDISABLE;
5228 if (val == G_ALERTALTERNATE)
5229 privacy_inhibit_encrypt_warning(compose->privacy_system,
5233 if (val == G_ALERTALTERNATE) {
5240 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
5241 gchar **msgpath, gboolean check_subject,
5242 gboolean remove_reedit_target)
5249 static gboolean lock = FALSE;
5250 PrefsAccount *mailac = NULL, *newsac = NULL;
5252 debug_print("queueing message...\n");
5253 g_return_val_if_fail(compose->account != NULL, -1);
5257 if (compose_check_entries(compose, check_subject) == FALSE) {
5259 if (compose->batch) {
5260 gtk_widget_show_all(compose->window);
5265 if (!compose->to_list && !compose->newsgroup_list) {
5266 g_warning("can't get recipient list.");
5271 if (compose->to_list) {
5272 if (compose->account->protocol != A_NNTP)
5273 mailac = compose->account;
5274 else if (cur_account && cur_account->protocol != A_NNTP)
5275 mailac = cur_account;
5276 else if (!(mailac = compose_current_mail_account())) {
5278 alertpanel_error(_("No account for sending mails available!"));
5283 if (compose->newsgroup_list) {
5284 if (compose->account->protocol == A_NNTP)
5285 newsac = compose->account;
5286 else if (!newsac->protocol != A_NNTP) {
5288 alertpanel_error(_("No account for posting news available!"));
5293 /* write queue header */
5294 tmp = g_strdup_printf("%s%cqueue.%p", get_tmp_dir(),
5295 G_DIR_SEPARATOR, compose);
5296 if ((fp = g_fopen(tmp, "wb")) == NULL) {
5297 FILE_OP_ERROR(tmp, "fopen");
5303 if (change_file_mode_rw(fp, tmp) < 0) {
5304 FILE_OP_ERROR(tmp, "chmod");
5305 g_warning("can't change file mode\n");
5308 /* queueing variables */
5309 fprintf(fp, "AF:\n");
5310 fprintf(fp, "NF:0\n");
5311 fprintf(fp, "PS:10\n");
5312 fprintf(fp, "SRH:1\n");
5313 fprintf(fp, "SFN:\n");
5314 fprintf(fp, "DSR:\n");
5316 fprintf(fp, "MID:<%s>\n", compose->msgid);
5318 fprintf(fp, "MID:\n");
5319 fprintf(fp, "CFG:\n");
5320 fprintf(fp, "PT:0\n");
5321 fprintf(fp, "S:%s\n", compose->account->address);
5322 fprintf(fp, "RQ:\n");
5324 fprintf(fp, "SSV:%s\n", mailac->smtp_server);
5326 fprintf(fp, "SSV:\n");
5328 fprintf(fp, "NSV:%s\n", newsac->nntp_server);
5330 fprintf(fp, "NSV:\n");
5331 fprintf(fp, "SSH:\n");
5332 /* write recepient list */
5333 if (compose->to_list) {
5334 fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data);
5335 for (cur = compose->to_list->next; cur != NULL;
5337 fprintf(fp, ",<%s>", (gchar *)cur->data);
5340 /* write newsgroup list */
5341 if (compose->newsgroup_list) {
5343 fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data);
5344 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
5345 fprintf(fp, ",%s", (gchar *)cur->data);
5348 /* Sylpheed account IDs */
5350 fprintf(fp, "MAID:%d\n", mailac->account_id);
5352 fprintf(fp, "NAID:%d\n", newsac->account_id);
5355 if (compose->privacy_system != NULL) {
5356 fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system);
5357 fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing);
5358 if (compose->use_encryption) {
5360 if (!compose_warn_encryption(compose)) {
5367 if (mailac && mailac->encrypt_to_self) {
5368 GSList *tmp_list = g_slist_copy(compose->to_list);
5369 tmp_list = g_slist_append(tmp_list, compose->account->address);
5370 encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
5371 g_slist_free(tmp_list);
5373 encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
5375 if (encdata != NULL) {
5376 if (strcmp(encdata, "_DONT_ENCRYPT_")) {
5377 fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption);
5378 fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
5380 } /* else we finally dont want to encrypt */
5382 fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption);
5383 /* and if encdata was null, it means there's been a problem in
5395 /* Save copy folder */
5396 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
5397 gchar *savefolderid;
5399 savefolderid = gtk_editable_get_chars(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
5400 fprintf(fp, "SCF:%s\n", savefolderid);
5401 g_free(savefolderid);
5403 /* Save copy folder */
5404 if (compose->return_receipt) {
5405 fprintf(fp, "RRCPT:1\n");
5407 /* Message-ID of message replying to */
5408 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
5411 folderid = folder_item_get_identifier(compose->replyinfo->folder);
5412 fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid);
5415 /* Message-ID of message forwarding to */
5416 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
5419 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
5420 fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid);
5424 /* end of headers */
5425 fprintf(fp, "X-Claws-End-Special-Headers: 1\n");
5427 if (compose->redirect_filename != NULL) {
5428 if (compose_redirect_write_to_file(compose, fp) < 0) {
5437 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
5442 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
5446 if (fclose(fp) == EOF) {
5447 FILE_OP_ERROR(tmp, "fclose");
5454 if (item && *item) {
5457 queue = account_get_special_folder(compose->account, F_QUEUE);
5460 g_warning("can't find queue folder\n");
5466 folder_item_scan(queue);
5467 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
5468 g_warning("can't queue the message\n");
5475 if (msgpath == NULL) {
5481 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
5482 compose_remove_reedit_target(compose, FALSE);
5485 if ((msgnum != NULL) && (item != NULL)) {
5493 static void compose_add_attachments(Compose *compose, MimeInfo *parent)
5496 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
5498 struct stat statbuf;
5499 gchar *type, *subtype;
5500 GtkTreeModel *model;
5503 model = gtk_tree_view_get_model(tree_view);
5505 if (!gtk_tree_model_get_iter_first(model, &iter))
5508 gtk_tree_model_get(model, &iter,
5512 mimepart = procmime_mimeinfo_new();
5513 mimepart->content = MIMECONTENT_FILE;
5514 mimepart->data.filename = g_strdup(ainfo->file);
5515 mimepart->tmp = FALSE; /* or we destroy our attachment */
5516 mimepart->offset = 0;
5518 stat(ainfo->file, &statbuf);
5519 mimepart->length = statbuf.st_size;
5521 type = g_strdup(ainfo->content_type);
5523 if (!strchr(type, '/')) {
5525 type = g_strdup("application/octet-stream");
5528 subtype = strchr(type, '/') + 1;
5529 *(subtype - 1) = '\0';
5530 mimepart->type = procmime_get_media_type(type);
5531 mimepart->subtype = g_strdup(subtype);
5534 if (mimepart->type == MIMETYPE_MESSAGE &&
5535 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
5536 mimepart->disposition = DISPOSITIONTYPE_INLINE;
5539 g_hash_table_insert(mimepart->typeparameters,
5540 g_strdup("name"), g_strdup(ainfo->name));
5541 g_hash_table_insert(mimepart->dispositionparameters,
5542 g_strdup("filename"), g_strdup(ainfo->name));
5543 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
5547 if (compose->use_signing) {
5548 if (ainfo->encoding == ENC_7BIT)
5549 ainfo->encoding = ENC_QUOTED_PRINTABLE;
5550 else if (ainfo->encoding == ENC_8BIT)
5551 ainfo->encoding = ENC_BASE64;
5554 procmime_encode_content(mimepart, ainfo->encoding);
5556 g_node_append(parent->node, mimepart->node);
5557 } while (gtk_tree_model_iter_next(model, &iter));
5560 #define IS_IN_CUSTOM_HEADER(header) \
5561 (compose->account->add_customhdr && \
5562 custom_header_find(compose->account->customhdr_list, header) != NULL)
5564 static void compose_add_headerfield_from_headerlist(Compose *compose,
5566 const gchar *fieldname,
5567 const gchar *seperator)
5569 gchar *str, *fieldname_w_colon;
5570 gboolean add_field = FALSE;
5572 ComposeHeaderEntry *headerentry;
5573 const gchar *headerentryname;
5574 const gchar *trans_fieldname;
5577 if (IS_IN_CUSTOM_HEADER(fieldname))
5580 debug_print("Adding %s-fields\n", fieldname);
5582 fieldstr = g_string_sized_new(64);
5584 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
5585 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
5587 for (list = compose->header_list; list; list = list->next) {
5588 headerentry = ((ComposeHeaderEntry *)list->data);
5589 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(headerentry->combo)->entry));
5591 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
5592 str = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
5594 if (str[0] != '\0') {
5596 g_string_append(fieldstr, seperator);
5597 g_string_append(fieldstr, str);
5606 buf = g_new0(gchar, fieldstr->len * 4 + 256);
5607 compose_convert_header
5608 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
5609 strlen(fieldname) + 2, TRUE);
5610 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
5614 g_free(fieldname_w_colon);
5615 g_string_free(fieldstr, TRUE);
5620 static gchar *compose_get_header(Compose *compose)
5622 gchar buf[BUFFSIZE];
5623 const gchar *entry_str;
5627 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
5629 gchar *from_name = NULL, *from_address = NULL;
5632 g_return_val_if_fail(compose->account != NULL, NULL);
5633 g_return_val_if_fail(compose->account->address != NULL, NULL);
5635 header = g_string_sized_new(64);
5638 get_rfc822_date(buf, sizeof(buf));
5639 g_string_append_printf(header, "Date: %s\n", buf);
5643 if (compose->account->name && *compose->account->name) {
5645 QUOTE_IF_REQUIRED(buf, compose->account->name);
5646 tmp = g_strdup_printf("%s <%s>",
5647 buf, compose->account->address);
5649 tmp = g_strdup_printf("%s",
5650 compose->account->address);
5652 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
5653 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
5655 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
5656 from_address = g_strdup(compose->account->address);
5658 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5659 /* extract name and address */
5660 if (strstr(spec, " <") && strstr(spec, ">")) {
5661 from_address = g_strdup(strrchr(spec, '<')+1);
5662 *(strrchr(from_address, '>')) = '\0';
5663 from_name = g_strdup(spec);
5664 *(strrchr(from_name, '<')) = '\0';
5667 from_address = g_strdup(spec);
5674 if (from_name && *from_name) {
5675 compose_convert_header
5676 (compose, buf, sizeof(buf), from_name,
5677 strlen("From: "), TRUE);
5678 QUOTE_IF_REQUIRED(name, buf);
5680 g_string_append_printf(header, "From: %s <%s>\n",
5681 name, from_address);
5683 g_string_append_printf(header, "From: %s\n", from_address);
5686 g_free(from_address);
5689 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
5692 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
5695 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
5698 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
5701 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
5703 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
5706 compose_convert_header(compose, buf, sizeof(buf), str,
5707 strlen("Subject: "), FALSE);
5708 g_string_append_printf(header, "Subject: %s\n", buf);
5714 if (compose->account->set_domain && compose->account->domain) {
5715 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
5716 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
5717 g_snprintf(buf, sizeof(buf), "%s",
5718 strchr(compose->account->address, '@') ?
5719 strchr(compose->account->address, '@')+1 :
5720 compose->account->address);
5722 g_snprintf(buf, sizeof(buf), "%s", "");
5724 generate_msgid(buf, sizeof(buf));
5725 g_string_append_printf(header, "Message-ID: <%s>\n", buf);
5726 compose->msgid = g_strdup(buf);
5728 if (compose->remove_references == FALSE) {
5730 if (compose->inreplyto && compose->to_list)
5731 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
5734 if (compose->references)
5735 g_string_append_printf(header, "References: %s\n", compose->references);
5739 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
5742 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
5745 if (compose->account->organization &&
5746 strlen(compose->account->organization) &&
5747 !IS_IN_CUSTOM_HEADER("Organization")) {
5748 compose_convert_header(compose, buf, sizeof(buf),
5749 compose->account->organization,
5750 strlen("Organization: "), FALSE);
5751 g_string_append_printf(header, "Organization: %s\n", buf);
5754 /* Program version and system info */
5755 if (g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
5756 !compose->newsgroup_list) {
5757 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
5759 gtk_major_version, gtk_minor_version, gtk_micro_version,
5762 if (g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
5763 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
5765 gtk_major_version, gtk_minor_version, gtk_micro_version,
5769 /* custom headers */
5770 if (compose->account->add_customhdr) {
5773 for (cur = compose->account->customhdr_list; cur != NULL;
5775 CustomHeader *chdr = (CustomHeader *)cur->data;
5777 if (custom_header_is_allowed(chdr->name)) {
5778 compose_convert_header
5779 (compose, buf, sizeof(buf),
5780 chdr->value ? chdr->value : "",
5781 strlen(chdr->name) + 2, FALSE);
5782 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
5788 switch (compose->priority) {
5789 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
5790 "X-Priority: 1 (Highest)\n");
5792 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
5793 "X-Priority: 2 (High)\n");
5795 case PRIORITY_NORMAL: break;
5796 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
5797 "X-Priority: 4 (Low)\n");
5799 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
5800 "X-Priority: 5 (Lowest)\n");
5802 default: debug_print("compose: priority unknown : %d\n",
5806 /* Request Return Receipt */
5807 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To")) {
5808 if (compose->return_receipt) {
5809 if (compose->account->name
5810 && *compose->account->name) {
5811 compose_convert_header(compose, buf, sizeof(buf),
5812 compose->account->name,
5813 strlen("Disposition-Notification-To: "),
5815 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, compose->account->address);
5817 g_string_append_printf(header, "Disposition-Notification-To: %s\n", compose->account->address);
5821 /* get special headers */
5822 for (list = compose->header_list; list; list = list->next) {
5823 ComposeHeaderEntry *headerentry;
5826 gchar *headername_wcolon;
5827 const gchar *headername_trans;
5830 gboolean standard_header = FALSE;
5832 headerentry = ((ComposeHeaderEntry *)list->data);
5834 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(headerentry->combo)->entry)));
5835 if (strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
5840 if (!strstr(tmp, ":")) {
5841 headername_wcolon = g_strconcat(tmp, ":", NULL);
5842 headername = g_strdup(tmp);
5844 headername_wcolon = g_strdup(tmp);
5845 headername = g_strdup(strtok(tmp, ":"));
5849 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5850 Xstrdup_a(headervalue, entry_str, return NULL);
5851 subst_char(headervalue, '\r', ' ');
5852 subst_char(headervalue, '\n', ' ');
5853 string = std_headers;
5854 while (*string != NULL) {
5855 headername_trans = prefs_common_translated_header_name(*string);
5856 if (!strcmp(headername_trans, headername_wcolon))
5857 standard_header = TRUE;
5860 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
5861 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
5864 g_free(headername_wcolon);
5868 g_string_free(header, FALSE);
5873 #undef IS_IN_CUSTOM_HEADER
5875 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
5876 gint header_len, gboolean addr_field)
5878 gchar *tmpstr = NULL;
5879 const gchar *out_codeset = NULL;
5881 g_return_if_fail(src != NULL);
5882 g_return_if_fail(dest != NULL);
5884 if (len < 1) return;
5886 tmpstr = g_strdup(src);
5888 subst_char(tmpstr, '\n', ' ');
5889 subst_char(tmpstr, '\r', ' ');
5892 if (!g_utf8_validate(tmpstr, -1, NULL)) {
5893 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
5894 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
5899 codeconv_set_strict(TRUE);
5900 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
5901 conv_get_charset_str(compose->out_encoding));
5902 codeconv_set_strict(FALSE);
5904 if (!dest || *dest == '\0') {
5905 gchar *test_conv_global_out = NULL;
5906 gchar *test_conv_reply = NULL;
5908 /* automatic mode. be automatic. */
5909 codeconv_set_strict(TRUE);
5911 out_codeset = conv_get_outgoing_charset_str();
5913 debug_print("trying to convert to %s\n", out_codeset);
5914 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
5917 if (!test_conv_global_out && compose->orig_charset
5918 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5919 out_codeset = compose->orig_charset;
5920 debug_print("failure; trying to convert to %s\n", out_codeset);
5921 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
5924 if (!test_conv_global_out && !test_conv_reply) {
5926 out_codeset = CS_INTERNAL;
5927 debug_print("finally using %s\n", out_codeset);
5929 g_free(test_conv_global_out);
5930 g_free(test_conv_reply);
5931 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
5933 codeconv_set_strict(FALSE);
5938 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
5942 g_return_if_fail(user_data != NULL);
5944 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
5945 g_strstrip(address);
5946 if (*address != '\0') {
5947 gchar *name = procheader_get_fromname(address);
5948 extract_address(address);
5949 addressbook_add_contact(name, address, NULL);
5954 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
5956 GtkWidget *menuitem;
5959 g_return_if_fail(menu != NULL);
5960 g_return_if_fail(GTK_IS_MENU_SHELL(menu));
5962 menuitem = gtk_separator_menu_item_new();
5963 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
5964 gtk_widget_show(menuitem);
5966 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
5967 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
5969 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
5970 g_strstrip(address);
5971 if (*address == '\0') {
5972 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
5975 g_signal_connect(G_OBJECT(menuitem), "activate",
5976 G_CALLBACK(compose_add_to_addressbook_cb), entry);
5977 gtk_widget_show(menuitem);
5980 static void compose_create_header_entry(Compose *compose)
5982 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
5986 GList *combo_list = NULL;
5988 const gchar *header = NULL;
5989 ComposeHeaderEntry *headerentry;
5990 gboolean standard_header = FALSE;
5992 headerentry = g_new0(ComposeHeaderEntry, 1);
5995 combo = gtk_combo_new();
5997 while(*string != NULL) {
5998 combo_list = g_list_append(combo_list, (gchar*)prefs_common_translated_header_name(*string));
6001 gtk_combo_set_popdown_strings(GTK_COMBO(combo), combo_list);
6002 g_list_free(combo_list);
6003 gtk_editable_set_editable(GTK_EDITABLE(GTK_COMBO(combo)->entry), TRUE);
6004 g_signal_connect(G_OBJECT(GTK_COMBO(combo)->entry), "grab_focus",
6005 G_CALLBACK(compose_grab_focus_cb), compose);
6006 gtk_widget_show(combo);
6007 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
6008 compose->header_nextrow, compose->header_nextrow+1,
6009 GTK_SHRINK, GTK_FILL, 0, 0);
6010 if (compose->header_last) {
6011 const gchar *last_header_entry = gtk_entry_get_text(
6012 GTK_ENTRY(GTK_COMBO(compose->header_last->combo)->entry));
6014 while (*string != NULL) {
6015 if (!strcmp(*string, last_header_entry))
6016 standard_header = TRUE;
6019 if (standard_header)
6020 header = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(compose->header_last->combo)->entry));
6022 if (!compose->header_last || !standard_header) {
6023 switch(compose->account->protocol) {
6025 header = prefs_common_translated_header_name("Newsgroups:");
6028 header = prefs_common_translated_header_name("To:");
6033 gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(combo)->entry), header);
6035 g_signal_connect_after(G_OBJECT(GTK_COMBO(combo)->entry), "grab_focus",
6036 G_CALLBACK(compose_grab_focus_cb), compose);
6039 entry = gtk_entry_new();
6040 gtk_widget_show(entry);
6041 gtk_tooltips_set_tip(compose->tooltips, entry,
6042 _("Use <tab> to autocomplete from addressbook"), NULL);
6043 gtk_table_attach(GTK_TABLE(compose->header_table), entry, 1, 2,
6044 compose->header_nextrow, compose->header_nextrow+1,
6045 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
6047 g_signal_connect(G_OBJECT(entry), "key-press-event",
6048 G_CALLBACK(compose_headerentry_key_press_event_cb),
6050 g_signal_connect(G_OBJECT(entry), "changed",
6051 G_CALLBACK(compose_headerentry_changed_cb),
6053 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
6054 G_CALLBACK(compose_grab_focus_cb), compose);
6057 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6058 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6059 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6060 g_signal_connect(G_OBJECT(entry), "drag_data_received",
6061 G_CALLBACK(compose_header_drag_received_cb),
6063 g_signal_connect(G_OBJECT(entry), "drag-drop",
6064 G_CALLBACK(compose_drag_drop),
6066 g_signal_connect(G_OBJECT(entry), "populate-popup",
6067 G_CALLBACK(compose_entry_popup_extend),
6070 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
6072 headerentry->compose = compose;
6073 headerentry->combo = combo;
6074 headerentry->entry = entry;
6075 headerentry->headernum = compose->header_nextrow;
6077 compose->header_nextrow++;
6078 compose->header_last = headerentry;
6079 compose->header_list =
6080 g_slist_append(compose->header_list,
6084 static void compose_add_header_entry(Compose *compose, const gchar *header, gchar *text)
6086 ComposeHeaderEntry *last_header;
6088 last_header = compose->header_last;
6090 gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(last_header->combo)->entry), header);
6091 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
6094 static void compose_remove_header_entries(Compose *compose)
6097 for (list = compose->header_list; list; list = list->next) {
6098 ComposeHeaderEntry *headerentry =
6099 (ComposeHeaderEntry *)list->data;
6100 gtk_widget_destroy(headerentry->combo);
6101 gtk_widget_destroy(headerentry->entry);
6102 g_free(headerentry);
6104 compose->header_last = NULL;
6105 g_slist_free(compose->header_list);
6106 compose->header_list = NULL;
6107 compose->header_nextrow = 1;
6108 compose_create_header_entry(compose);
6111 static GtkWidget *compose_create_header(Compose *compose)
6113 GtkWidget *from_optmenu_hbox;
6114 GtkWidget *header_scrolledwin;
6115 GtkWidget *header_table;
6119 /* header labels and entries */
6120 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
6121 gtk_widget_show(header_scrolledwin);
6122 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
6124 header_table = gtk_table_new(2, 2, FALSE);
6125 gtk_widget_show(header_table);
6126 gtk_container_set_border_width(GTK_CONTAINER(header_table), BORDER_WIDTH);
6127 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
6128 gtk_viewport_set_shadow_type(GTK_VIEWPORT(GTK_BIN(header_scrolledwin)->child), GTK_SHADOW_ETCHED_IN);
6131 /* option menu for selecting accounts */
6132 from_optmenu_hbox = compose_account_option_menu_create(compose);
6133 gtk_table_attach(GTK_TABLE(header_table), from_optmenu_hbox,
6134 0, 2, count, count + 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
6137 compose->header_table = header_table;
6138 compose->header_list = NULL;
6139 compose->header_nextrow = count;
6141 compose_create_header_entry(compose);
6143 compose->table = NULL;
6145 return header_scrolledwin ;
6148 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
6150 Compose *compose = (Compose *)data;
6151 GdkEventButton event;
6154 event.time = gtk_get_current_event_time();
6156 return attach_button_pressed(compose->attach_clist, &event, compose);
6159 static GtkWidget *compose_create_attach(Compose *compose)
6161 GtkWidget *attach_scrwin;
6162 GtkWidget *attach_clist;
6164 GtkListStore *store;
6165 GtkCellRenderer *renderer;
6166 GtkTreeViewColumn *column;
6167 GtkTreeSelection *selection;
6169 /* attachment list */
6170 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
6171 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
6172 GTK_POLICY_AUTOMATIC,
6173 GTK_POLICY_AUTOMATIC);
6174 gtk_widget_set_size_request(attach_scrwin, -1, 80);
6176 store = gtk_list_store_new(N_ATTACH_COLS,
6181 G_TYPE_AUTO_POINTER,
6183 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
6184 (GTK_TREE_MODEL(store)));
6185 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
6186 g_object_unref(store);
6188 renderer = gtk_cell_renderer_text_new();
6189 column = gtk_tree_view_column_new_with_attributes
6190 (_("Mime type"), renderer, "text",
6191 COL_MIMETYPE, NULL);
6192 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6194 renderer = gtk_cell_renderer_text_new();
6195 column = gtk_tree_view_column_new_with_attributes
6196 (_("Size"), renderer, "text",
6198 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6200 renderer = gtk_cell_renderer_text_new();
6201 column = gtk_tree_view_column_new_with_attributes
6202 (_("Name"), renderer, "text",
6204 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6206 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
6207 prefs_common.use_stripes_everywhere);
6208 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
6209 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
6211 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
6212 G_CALLBACK(attach_selected), compose);
6213 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
6214 G_CALLBACK(attach_button_pressed), compose);
6216 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
6217 G_CALLBACK(popup_attach_button_pressed), compose);
6219 gtk_widget_tap_and_hold_setup(GTK_WIDGET(attach_clist), NULL, NULL,
6220 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
6221 g_signal_connect(G_OBJECT(attach_clist), "tap-and-hold",
6222 G_CALLBACK(popup_attach_button_pressed), compose);
6224 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
6225 G_CALLBACK(attach_key_pressed), compose);
6228 gtk_drag_dest_set(attach_clist,
6229 GTK_DEST_DEFAULT_ALL, compose_mime_types,
6230 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6231 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6232 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
6233 G_CALLBACK(compose_attach_drag_received_cb),
6235 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
6236 G_CALLBACK(compose_drag_drop),
6239 compose->attach_scrwin = attach_scrwin;
6240 compose->attach_clist = attach_clist;
6242 return attach_scrwin;
6245 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
6246 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
6248 static GtkWidget *compose_create_others(Compose *compose)
6251 GtkWidget *savemsg_checkbtn;
6252 GtkWidget *savemsg_entry;
6253 GtkWidget *savemsg_select;
6256 gchar *folderidentifier;
6258 /* Table for settings */
6259 table = gtk_table_new(3, 1, FALSE);
6260 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
6261 gtk_widget_show(table);
6262 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
6265 /* Save Message to folder */
6266 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
6267 gtk_widget_show(savemsg_checkbtn);
6268 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6269 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6270 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
6272 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
6273 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
6275 savemsg_entry = gtk_entry_new();
6276 gtk_widget_show(savemsg_entry);
6277 gtk_table_attach_defaults(GTK_TABLE(table), savemsg_entry, 1, 2, rowcount, rowcount + 1);
6278 gtk_editable_set_editable(GTK_EDITABLE(savemsg_entry), prefs_common.savemsg);
6279 g_signal_connect_after(G_OBJECT(savemsg_entry), "grab_focus",
6280 G_CALLBACK(compose_grab_focus_cb), compose);
6281 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6282 folderidentifier = folder_item_get_identifier(account_get_special_folder
6283 (compose->account, F_OUTBOX));
6284 gtk_entry_set_text(GTK_ENTRY(savemsg_entry), folderidentifier);
6285 g_free(folderidentifier);
6288 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
6289 gtk_widget_show(savemsg_select);
6290 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6291 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
6292 G_CALLBACK(compose_savemsg_select_cb),
6297 compose->savemsg_checkbtn = savemsg_checkbtn;
6298 compose->savemsg_entry = savemsg_entry;
6303 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
6305 gtk_editable_set_editable(GTK_EDITABLE(compose->savemsg_entry),
6306 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
6309 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
6314 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL);
6317 path = folder_item_get_identifier(dest);
6319 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), path);
6323 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
6324 GdkAtom clip, GtkTextIter *insert_place);
6327 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
6331 GtkTextBuffer *buffer;
6333 if (event->button == 3) {
6335 GtkTextIter sel_start, sel_end;
6336 gboolean stuff_selected;
6338 /* move the cursor to allow GtkAspell to check the word
6339 * under the mouse */
6340 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6341 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6343 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6346 stuff_selected = gtk_text_buffer_get_selection_bounds(
6347 GTK_TEXT_VIEW(text)->buffer,
6348 &sel_start, &sel_end);
6350 gtk_text_buffer_place_cursor (GTK_TEXT_VIEW(text)->buffer, &iter);
6351 /* reselect stuff */
6353 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
6354 gtk_text_buffer_select_range(GTK_TEXT_VIEW(text)->buffer,
6355 &sel_start, &sel_end);
6357 return FALSE; /* pass the event so that the right-click goes through */
6360 if (event->button == 2) {
6365 /* get the middle-click position to paste at the correct place */
6366 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6367 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6369 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6372 entry_paste_clipboard(compose, text,
6373 prefs_common.linewrap_pastes,
6374 GDK_SELECTION_PRIMARY, &iter);
6382 static void compose_spell_menu_changed(void *data)
6384 Compose *compose = (Compose *)data;
6386 GtkWidget *menuitem;
6387 GtkWidget *parent_item;
6388 GtkMenu *menu = GTK_MENU(gtk_menu_new());
6389 GtkItemFactory *ifactory = gtk_item_factory_from_widget(compose->menubar);
6392 if (compose->gtkaspell == NULL)
6395 parent_item = gtk_item_factory_get_item(ifactory,
6396 "/Spelling/Options");
6398 /* setting the submenu removes /Spelling/Options from the factory
6399 * so we need to save it */
6401 if (parent_item == NULL) {
6402 parent_item = compose->aspell_options_menu;
6403 gtk_menu_item_remove_submenu(GTK_MENU_ITEM(parent_item));
6405 compose->aspell_options_menu = parent_item;
6407 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
6409 spell_menu = g_slist_reverse(spell_menu);
6410 for (items = spell_menu;
6411 items; items = items->next) {
6412 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
6413 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
6414 gtk_widget_show(GTK_WIDGET(menuitem));
6416 g_slist_free(spell_menu);
6418 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
6423 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
6425 Compose *compose = (Compose *)data;
6426 GdkEventButton event;
6429 event.time = gtk_get_current_event_time();
6431 return text_clicked(compose->text, &event, compose);
6434 static gboolean compose_force_window_origin = TRUE;
6435 static Compose *compose_create(PrefsAccount *account,
6444 GtkWidget *handlebox;
6446 GtkWidget *notebook;
6451 GtkWidget *subject_hbox;
6452 GtkWidget *subject_frame;
6453 GtkWidget *subject_entry;
6457 GtkWidget *edit_vbox;
6458 GtkWidget *ruler_hbox;
6460 GtkWidget *scrolledwin;
6462 GtkTextBuffer *buffer;
6463 GtkClipboard *clipboard;
6465 UndoMain *undostruct;
6467 gchar *titles[N_ATTACH_COLS];
6468 guint n_menu_entries;
6469 GtkWidget *popupmenu;
6470 GtkItemFactory *popupfactory;
6471 GtkItemFactory *ifactory;
6472 GtkWidget *tmpl_menu;
6474 GtkWidget *menuitem;
6477 GtkAspell * gtkaspell = NULL;
6480 static GdkGeometry geometry;
6482 g_return_val_if_fail(account != NULL, NULL);
6484 debug_print("Creating compose window...\n");
6485 compose = g_new0(Compose, 1);
6487 titles[COL_MIMETYPE] = _("MIME type");
6488 titles[COL_SIZE] = _("Size");
6489 titles[COL_NAME] = _("Name");
6491 compose->batch = batch;
6492 compose->account = account;
6493 compose->folder = folder;
6495 compose->mutex = g_mutex_new();
6496 compose->set_cursor_pos = -1;
6498 compose->tooltips = gtk_tooltips_new();
6500 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
6502 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
6503 gtk_widget_set_size_request(window, -1, prefs_common.compose_height);
6505 if (!geometry.max_width) {
6506 geometry.max_width = gdk_screen_width();
6507 geometry.max_height = gdk_screen_height();
6510 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
6511 &geometry, GDK_HINT_MAX_SIZE);
6512 if (!geometry.min_width) {
6513 geometry.min_width = 600;
6514 geometry.min_height = 480;
6516 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
6517 &geometry, GDK_HINT_MIN_SIZE);
6520 if (compose_force_window_origin)
6521 gtk_widget_set_uposition(window, prefs_common.compose_x,
6522 prefs_common.compose_y);
6524 g_signal_connect(G_OBJECT(window), "delete_event",
6525 G_CALLBACK(compose_delete_cb), compose);
6526 MANAGE_WINDOW_SIGNALS_CONNECT(window);
6527 gtk_widget_realize(window);
6529 gtkut_widget_set_composer_icon(window);
6531 vbox = gtk_vbox_new(FALSE, 0);
6532 gtk_container_add(GTK_CONTAINER(window), vbox);
6534 n_menu_entries = sizeof(compose_entries) / sizeof(compose_entries[0]);
6535 menubar = menubar_create(window, compose_entries,
6536 n_menu_entries, "<Compose>", compose);
6537 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
6539 if (prefs_common.toolbar_detachable) {
6540 handlebox = gtk_handle_box_new();
6542 handlebox = gtk_hbox_new(FALSE, 0);
6544 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
6546 gtk_widget_realize(handlebox);
6548 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, window,
6551 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
6555 vbox2 = gtk_vbox_new(FALSE, 2);
6556 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
6557 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
6560 notebook = gtk_notebook_new();
6561 gtk_widget_set_size_request(notebook, -1, 130);
6562 gtk_widget_show(notebook);
6564 /* header labels and entries */
6565 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6566 compose_create_header(compose),
6567 gtk_label_new_with_mnemonic(_("Hea_der")));
6568 /* attachment list */
6569 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6570 compose_create_attach(compose),
6571 gtk_label_new_with_mnemonic(_("_Attachments")));
6573 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6574 compose_create_others(compose),
6575 gtk_label_new_with_mnemonic(_("Othe_rs")));
6578 subject_hbox = gtk_hbox_new(FALSE, 0);
6579 gtk_widget_show(subject_hbox);
6581 subject_frame = gtk_frame_new(NULL);
6582 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
6583 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
6584 gtk_widget_show(subject_frame);
6586 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
6587 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
6588 gtk_widget_show(subject);
6590 label = gtk_label_new(_("Subject:"));
6591 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
6592 gtk_widget_show(label);
6594 subject_entry = gtk_entry_new();
6595 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
6596 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
6597 G_CALLBACK(compose_grab_focus_cb), compose);
6598 gtk_widget_show(subject_entry);
6599 compose->subject_entry = subject_entry;
6600 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
6602 edit_vbox = gtk_vbox_new(FALSE, 0);
6604 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
6607 ruler_hbox = gtk_hbox_new(FALSE, 0);
6608 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
6610 ruler = gtk_shruler_new();
6611 gtk_ruler_set_range(GTK_RULER(ruler), 0.0, 100.0, 1.0, 100.0);
6612 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
6616 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
6617 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
6618 GTK_POLICY_AUTOMATIC,
6619 GTK_POLICY_AUTOMATIC);
6620 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
6622 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
6623 gtk_widget_set_size_request(scrolledwin, prefs_common.compose_width, -1);
6625 text = gtk_text_view_new();
6626 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
6627 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
6628 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
6629 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
6630 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
6632 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
6634 g_signal_connect_after(G_OBJECT(text), "size_allocate",
6635 G_CALLBACK(compose_edit_size_alloc),
6637 g_signal_connect(G_OBJECT(buffer), "changed",
6638 G_CALLBACK(compose_changed_cb), compose);
6639 g_signal_connect(G_OBJECT(text), "grab_focus",
6640 G_CALLBACK(compose_grab_focus_cb), compose);
6641 g_signal_connect(G_OBJECT(buffer), "insert_text",
6642 G_CALLBACK(text_inserted), compose);
6643 g_signal_connect(G_OBJECT(text), "button_press_event",
6644 G_CALLBACK(text_clicked), compose);
6646 g_signal_connect(G_OBJECT(text), "popup-menu",
6647 G_CALLBACK(compose_popup_menu), compose);
6649 gtk_widget_tap_and_hold_setup(GTK_WIDGET(text), NULL, NULL,
6650 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
6651 g_signal_connect(G_OBJECT(text), "tap-and-hold",
6652 G_CALLBACK(compose_popup_menu), compose);
6654 g_signal_connect(G_OBJECT(subject_entry), "changed",
6655 G_CALLBACK(compose_changed_cb), compose);
6658 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6659 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6660 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6661 g_signal_connect(G_OBJECT(text), "drag_data_received",
6662 G_CALLBACK(compose_insert_drag_received_cb),
6664 g_signal_connect(G_OBJECT(text), "drag-drop",
6665 G_CALLBACK(compose_drag_drop),
6667 gtk_widget_show_all(vbox);
6669 /* pane between attach clist and text */
6670 paned = gtk_vpaned_new();
6671 gtk_paned_set_gutter_size(GTK_PANED(paned), 12);
6672 gtk_container_add(GTK_CONTAINER(vbox2), paned);
6674 if( maemo_mainwindow_is_fullscreen(mainwindow_get_mainwindow()->window) )
6675 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 300 : 280);
6677 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 250 : 230);
6679 gtk_paned_add1(GTK_PANED(paned), notebook);
6680 gtk_paned_add2(GTK_PANED(paned), edit_vbox);
6681 gtk_widget_show_all(paned);
6684 if (prefs_common.textfont) {
6685 PangoFontDescription *font_desc;
6687 font_desc = pango_font_description_from_string
6688 (prefs_common.textfont);
6690 gtk_widget_modify_font(text, font_desc);
6691 pango_font_description_free(font_desc);
6695 n_entries = sizeof(compose_popup_entries) /
6696 sizeof(compose_popup_entries[0]);
6697 popupmenu = menu_create_items(compose_popup_entries, n_entries,
6698 "<Compose>", &popupfactory,
6701 ifactory = gtk_item_factory_from_widget(menubar);
6702 menu_set_sensitive(ifactory, "/Edit/Undo", FALSE);
6703 menu_set_sensitive(ifactory, "/Edit/Redo", FALSE);
6704 menu_set_sensitive(ifactory, "/Options/Remove references", FALSE);
6706 tmpl_menu = gtk_item_factory_get_item(ifactory, "/Tools/Template");
6708 undostruct = undo_init(text);
6709 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
6712 address_completion_start(window);
6714 compose->window = window;
6715 compose->vbox = vbox;
6716 compose->menubar = menubar;
6717 compose->handlebox = handlebox;
6719 compose->vbox2 = vbox2;
6721 compose->paned = paned;
6723 compose->notebook = notebook;
6724 compose->edit_vbox = edit_vbox;
6725 compose->ruler_hbox = ruler_hbox;
6726 compose->ruler = ruler;
6727 compose->scrolledwin = scrolledwin;
6728 compose->text = text;
6730 compose->focused_editable = NULL;
6732 compose->popupmenu = popupmenu;
6733 compose->popupfactory = popupfactory;
6735 compose->tmpl_menu = tmpl_menu;
6737 compose->mode = mode;
6738 compose->rmode = mode;
6740 compose->targetinfo = NULL;
6741 compose->replyinfo = NULL;
6742 compose->fwdinfo = NULL;
6744 compose->replyto = NULL;
6746 compose->bcc = NULL;
6747 compose->followup_to = NULL;
6749 compose->ml_post = NULL;
6751 compose->inreplyto = NULL;
6752 compose->references = NULL;
6753 compose->msgid = NULL;
6754 compose->boundary = NULL;
6756 compose->autowrap = prefs_common.autowrap;
6758 compose->use_signing = FALSE;
6759 compose->use_encryption = FALSE;
6760 compose->privacy_system = NULL;
6762 compose->modified = FALSE;
6764 compose->return_receipt = FALSE;
6766 compose->to_list = NULL;
6767 compose->newsgroup_list = NULL;
6769 compose->undostruct = undostruct;
6771 compose->sig_str = NULL;
6773 compose->exteditor_file = NULL;
6774 compose->exteditor_pid = -1;
6775 compose->exteditor_tag = -1;
6776 compose->draft_timeout_tag = -2; /* inhibit auto-drafting while loading */
6779 menu_set_sensitive(ifactory, "/Spelling", FALSE);
6780 if (mode != COMPOSE_REDIRECT) {
6781 if (prefs_common.enable_aspell && prefs_common.dictionary &&
6782 strcmp(prefs_common.dictionary, "")) {
6783 gtkaspell = gtkaspell_new(prefs_common.aspell_path,
6784 prefs_common.dictionary,
6785 prefs_common.alt_dictionary,
6786 conv_get_locale_charset_str(),
6787 prefs_common.misspelled_col,
6788 prefs_common.check_while_typing,
6789 prefs_common.recheck_when_changing_dict,
6790 prefs_common.use_alternate,
6791 prefs_common.use_both_dicts,
6792 GTK_TEXT_VIEW(text),
6793 GTK_WINDOW(compose->window),
6794 compose_spell_menu_changed,
6797 alertpanel_error(_("Spell checker could not "
6799 gtkaspell_checkers_strerror());
6800 gtkaspell_checkers_reset_error();
6802 if (!gtkaspell_set_sug_mode(gtkaspell,
6803 prefs_common.aspell_sugmode)) {
6804 debug_print("Aspell: could not set "
6805 "suggestion mode %s\n",
6806 gtkaspell_checkers_strerror());
6807 gtkaspell_checkers_reset_error();
6810 menu_set_sensitive(ifactory, "/Spelling", TRUE);
6814 compose->gtkaspell = gtkaspell;
6815 compose_spell_menu_changed(compose);
6818 compose_select_account(compose, account, TRUE);
6820 menu_set_active(ifactory, "/Edit/Auto wrapping", prefs_common.autowrap);
6821 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
6822 compose_entry_append(compose, account->auto_cc, COMPOSE_CC);
6824 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
6825 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC);
6827 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
6828 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO);
6830 menu_set_sensitive(ifactory, "/Options/Reply mode", compose->mode == COMPOSE_REPLY);
6832 if (account->protocol != A_NNTP)
6833 gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(compose->header_last->combo)->entry),
6834 prefs_common_translated_header_name("To:"));
6836 gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(compose->header_last->combo)->entry),
6837 prefs_common_translated_header_name("Newsgroups:"));
6839 addressbook_set_target_compose(compose);
6841 if (mode != COMPOSE_REDIRECT)
6842 compose_set_template_menu(compose);
6844 menuitem = gtk_item_factory_get_item(ifactory, "/Tools/Template");
6845 menu_set_sensitive(ifactory, "/Tools/Template", FALSE);
6848 compose_list = g_list_append(compose_list, compose);
6850 if (!prefs_common.show_ruler)
6851 gtk_widget_hide(ruler_hbox);
6853 menuitem = gtk_item_factory_get_item(ifactory, "/Tools/Show ruler");
6854 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
6855 prefs_common.show_ruler);
6858 compose->priority = PRIORITY_NORMAL;
6859 compose_update_priority_menu_item(compose);
6861 compose_set_out_encoding(compose);
6864 compose_update_actions_menu(compose);
6866 /* Privacy Systems menu */
6867 compose_update_privacy_systems_menu(compose);
6869 activate_privacy_system(compose, account, TRUE);
6870 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
6872 gtk_widget_realize(window);
6874 gtk_widget_show(window);
6876 maemo_window_full_screen_if_needed(GTK_WINDOW(window));
6877 maemo_connect_key_press_to_mainwindow(GTK_WINDOW(window));
6884 static GtkWidget *compose_account_option_menu_create(Compose *compose)
6889 GtkWidget *optmenubox;
6892 GtkWidget *from_name = NULL;
6894 gint num = 0, def_menu = 0;
6896 accounts = account_get_list();
6897 g_return_val_if_fail(accounts != NULL, NULL);
6899 optmenubox = gtk_event_box_new();
6900 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
6901 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
6903 hbox = gtk_hbox_new(FALSE, 6);
6904 from_name = gtk_entry_new();
6906 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
6907 G_CALLBACK(compose_grab_focus_cb), compose);
6909 for (; accounts != NULL; accounts = accounts->next, num++) {
6910 PrefsAccount *ac = (PrefsAccount *)accounts->data;
6911 gchar *name, *from = NULL;
6913 if (ac == compose->account) def_menu = num;
6915 name = g_markup_printf_escaped(_("From: <i>%s</i>"),
6918 if (ac == compose->account) {
6919 if (ac->name && *ac->name) {
6921 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
6922 from = g_strdup_printf("%s <%s>",
6924 gtk_entry_set_text(GTK_ENTRY(from_name), from);
6926 from = g_strdup_printf("%s",
6928 gtk_entry_set_text(GTK_ENTRY(from_name), from);
6931 COMBOBOX_ADD(menu, name, ac->account_id);
6936 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
6938 g_signal_connect(G_OBJECT(optmenu), "changed",
6939 G_CALLBACK(account_activated),
6941 g_signal_connect(G_OBJECT(from_name), "populate-popup",
6942 G_CALLBACK(compose_entry_popup_extend),
6945 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
6946 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
6948 gtk_tooltips_set_tip(compose->tooltips, optmenubox,
6949 _("Account to use for this email"), NULL);
6950 gtk_tooltips_set_tip(compose->tooltips, from_name,
6951 _("Sender address to be used"), NULL);
6953 compose->from_name = from_name;
6958 static void compose_set_priority_cb(gpointer data,
6962 Compose *compose = (Compose *) data;
6963 compose->priority = action;
6966 static void compose_reply_change_mode(gpointer data,
6970 Compose *compose = (Compose *) data;
6971 gboolean was_modified = compose->modified;
6973 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
6975 g_return_if_fail(compose->replyinfo != NULL);
6977 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
6979 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
6981 if (action == COMPOSE_REPLY_TO_ALL)
6983 if (action == COMPOSE_REPLY_TO_SENDER)
6985 if (action == COMPOSE_REPLY_TO_LIST)
6988 compose_remove_header_entries(compose);
6989 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
6990 if (compose->account->set_autocc && compose->account->auto_cc)
6991 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC);
6993 if (compose->account->set_autobcc && compose->account->auto_bcc)
6994 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC);
6996 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
6997 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO);
6998 compose_show_first_last_header(compose, TRUE);
6999 compose->modified = was_modified;
7000 compose_set_title(compose);
7003 static void compose_update_priority_menu_item(Compose * compose)
7005 GtkItemFactory *ifactory;
7006 GtkWidget *menuitem = NULL;
7008 ifactory = gtk_item_factory_from_widget(compose->menubar);
7010 switch (compose->priority) {
7011 case PRIORITY_HIGHEST:
7012 menuitem = gtk_item_factory_get_item
7013 (ifactory, "/Options/Priority/Highest");
7016 menuitem = gtk_item_factory_get_item
7017 (ifactory, "/Options/Priority/High");
7019 case PRIORITY_NORMAL:
7020 menuitem = gtk_item_factory_get_item
7021 (ifactory, "/Options/Priority/Normal");
7024 menuitem = gtk_item_factory_get_item
7025 (ifactory, "/Options/Priority/Low");
7027 case PRIORITY_LOWEST:
7028 menuitem = gtk_item_factory_get_item
7029 (ifactory, "/Options/Priority/Lowest");
7032 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
7035 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
7037 Compose *compose = (Compose *) data;
7039 GtkItemFactory *ifactory;
7040 gboolean can_sign = FALSE, can_encrypt = FALSE;
7042 g_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
7044 if (!GTK_CHECK_MENU_ITEM(widget)->active)
7047 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
7048 g_free(compose->privacy_system);
7049 compose->privacy_system = NULL;
7050 if (systemid != NULL) {
7051 compose->privacy_system = g_strdup(systemid);
7053 can_sign = privacy_system_can_sign(systemid);
7054 can_encrypt = privacy_system_can_encrypt(systemid);
7057 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
7059 ifactory = gtk_item_factory_from_widget(compose->menubar);
7060 menu_set_sensitive(ifactory, "/Options/Sign", can_sign);
7061 menu_set_sensitive(ifactory, "/Options/Encrypt", can_encrypt);
7064 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
7066 static gchar *branch_path = "/Options/Privacy System";
7067 GtkItemFactory *ifactory;
7068 GtkWidget *menuitem = NULL;
7070 gboolean can_sign = FALSE, can_encrypt = FALSE;
7071 gboolean found = FALSE;
7073 ifactory = gtk_item_factory_from_widget(compose->menubar);
7075 if (compose->privacy_system != NULL) {
7078 menuitem = gtk_item_factory_get_widget(ifactory, branch_path);
7079 g_return_if_fail(menuitem != NULL);
7081 amenu = GTK_MENU_SHELL(menuitem)->children;
7083 while (amenu != NULL) {
7084 GList *alist = amenu->next;
7086 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
7087 if (systemid != NULL) {
7088 if (strcmp(systemid, compose->privacy_system) == 0) {
7089 menuitem = GTK_WIDGET(amenu->data);
7091 can_sign = privacy_system_can_sign(systemid);
7092 can_encrypt = privacy_system_can_encrypt(systemid);
7096 } else if (strlen(compose->privacy_system) == 0) {
7097 menuitem = GTK_WIDGET(amenu->data);
7100 can_encrypt = FALSE;
7107 if (menuitem != NULL)
7108 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
7110 if (warn && !found && strlen(compose->privacy_system)) {
7111 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
7112 "will not be able to sign or encrypt this message."),
7113 compose->privacy_system);
7117 menu_set_sensitive(ifactory, "/Options/Sign", can_sign);
7118 menu_set_sensitive(ifactory, "/Options/Encrypt", can_encrypt);
7121 static void compose_set_out_encoding(Compose *compose)
7123 GtkItemFactoryEntry *entry;
7124 GtkItemFactory *ifactory;
7125 CharSet out_encoding;
7126 gchar *path, *p, *q;
7129 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
7130 ifactory = gtk_item_factory_from_widget(compose->menubar);
7132 for (entry = compose_entries; entry->callback != compose_address_cb;
7134 if (entry->callback == compose_set_encoding_cb &&
7135 (CharSet)entry->callback_action == out_encoding) {
7136 p = q = path = g_strdup(entry->path);
7148 item = gtk_item_factory_get_item(ifactory, path);
7149 gtk_widget_activate(item);
7156 static void compose_set_template_menu(Compose *compose)
7158 GSList *tmpl_list, *cur;
7162 tmpl_list = template_get_config();
7164 menu = gtk_menu_new();
7166 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
7167 Template *tmpl = (Template *)cur->data;
7169 item = gtk_menu_item_new_with_label(tmpl->name);
7170 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
7171 g_signal_connect(G_OBJECT(item), "activate",
7172 G_CALLBACK(compose_template_activate_cb),
7174 g_object_set_data(G_OBJECT(item), "template", tmpl);
7175 gtk_widget_show(item);
7178 gtk_widget_show(menu);
7179 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
7182 void compose_update_actions_menu(Compose *compose)
7184 GtkItemFactory *ifactory;
7186 ifactory = gtk_item_factory_from_widget(compose->menubar);
7187 action_update_compose_menu(ifactory, "/Tools/Actions", compose);
7190 static void compose_update_privacy_systems_menu(Compose *compose)
7192 static gchar *branch_path = "/Options/Privacy System";
7193 GtkItemFactory *ifactory;
7194 GtkWidget *menuitem;
7195 GSList *systems, *cur;
7198 GtkWidget *system_none;
7201 ifactory = gtk_item_factory_from_widget(compose->menubar);
7203 /* remove old entries */
7204 menuitem = gtk_item_factory_get_widget(ifactory, branch_path);
7205 g_return_if_fail(menuitem != NULL);
7207 amenu = GTK_MENU_SHELL(menuitem)->children->next;
7208 while (amenu != NULL) {
7209 GList *alist = amenu->next;
7210 gtk_widget_destroy(GTK_WIDGET(amenu->data));
7214 system_none = gtk_item_factory_get_widget(ifactory,
7215 "/Options/Privacy System/None");
7217 g_signal_connect(G_OBJECT(system_none), "activate",
7218 G_CALLBACK(compose_set_privacy_system_cb), compose);
7220 systems = privacy_get_system_ids();
7221 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
7222 gchar *systemid = cur->data;
7224 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
7225 widget = gtk_radio_menu_item_new_with_label(group,
7226 privacy_system_get_name(systemid));
7227 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
7228 g_strdup(systemid), g_free);
7229 g_signal_connect(G_OBJECT(widget), "activate",
7230 G_CALLBACK(compose_set_privacy_system_cb), compose);
7232 gtk_menu_append(GTK_MENU(system_none->parent), widget);
7233 gtk_widget_show(widget);
7236 g_slist_free(systems);
7239 void compose_reflect_prefs_all(void)
7244 for (cur = compose_list; cur != NULL; cur = cur->next) {
7245 compose = (Compose *)cur->data;
7246 compose_set_template_menu(compose);
7250 void compose_reflect_prefs_pixmap_theme(void)
7255 for (cur = compose_list; cur != NULL; cur = cur->next) {
7256 compose = (Compose *)cur->data;
7257 toolbar_update(TOOLBAR_COMPOSE, compose);
7261 static const gchar *compose_quote_char_from_context(Compose *compose)
7263 const gchar *qmark = NULL;
7265 g_return_val_if_fail(compose != NULL, NULL);
7267 switch (compose->mode) {
7268 /* use forward-specific quote char */
7269 case COMPOSE_FORWARD:
7270 case COMPOSE_FORWARD_AS_ATTACH:
7271 case COMPOSE_FORWARD_INLINE:
7272 if (compose->folder && compose->folder->prefs &&
7273 compose->folder->prefs->forward_with_format)
7274 qmark = compose->folder->prefs->forward_quotemark;
7275 else if (compose->account->forward_with_format)
7276 qmark = compose->account->forward_quotemark;
7278 qmark = prefs_common.fw_quotemark;
7281 /* use reply-specific quote char in all other modes */
7283 if (compose->folder && compose->folder->prefs &&
7284 compose->folder->prefs->reply_with_format)
7285 qmark = compose->folder->prefs->reply_quotemark;
7286 else if (compose->account->reply_with_format)
7287 qmark = compose->account->reply_quotemark;
7289 qmark = prefs_common.quotemark;
7293 if (qmark == NULL || *qmark == '\0')
7299 static void compose_template_apply(Compose *compose, Template *tmpl,
7303 GtkTextBuffer *buffer;
7307 gchar *parsed_str = NULL;
7308 gint cursor_pos = 0;
7309 const gchar *err_msg = _("Template body format error at line %d.");
7312 /* process the body */
7314 text = GTK_TEXT_VIEW(compose->text);
7315 buffer = gtk_text_view_get_buffer(text);
7318 qmark = compose_quote_char_from_context(compose);
7320 if (compose->replyinfo != NULL) {
7323 gtk_text_buffer_set_text(buffer, "", -1);
7324 mark = gtk_text_buffer_get_insert(buffer);
7325 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7327 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
7328 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
7330 } else if (compose->fwdinfo != NULL) {
7333 gtk_text_buffer_set_text(buffer, "", -1);
7334 mark = gtk_text_buffer_get_insert(buffer);
7335 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7337 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
7338 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
7341 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
7343 GtkTextIter start, end;
7346 gtk_text_buffer_get_start_iter(buffer, &start);
7347 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
7348 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
7350 /* clear the buffer now */
7352 gtk_text_buffer_set_text(buffer, "", -1);
7354 parsed_str = compose_quote_fmt(compose, dummyinfo,
7355 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
7356 procmsg_msginfo_free( dummyinfo );
7362 gtk_text_buffer_set_text(buffer, "", -1);
7363 mark = gtk_text_buffer_get_insert(buffer);
7364 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7367 if (replace && parsed_str && compose->account->auto_sig)
7368 compose_insert_sig(compose, FALSE);
7370 if (replace && parsed_str) {
7371 gtk_text_buffer_get_start_iter(buffer, &iter);
7372 gtk_text_buffer_place_cursor(buffer, &iter);
7376 cursor_pos = quote_fmt_get_cursor_pos();
7377 compose->set_cursor_pos = cursor_pos;
7378 if (cursor_pos == -1)
7380 gtk_text_buffer_get_start_iter(buffer, &iter);
7381 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
7382 gtk_text_buffer_place_cursor(buffer, &iter);
7385 /* process the other fields */
7387 compose_template_apply_fields(compose, tmpl);
7388 quote_fmt_reset_vartable();
7389 compose_changed_cb(NULL, compose);
7392 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
7394 MsgInfo* dummyinfo = NULL;
7395 MsgInfo *msginfo = NULL;
7398 if (compose->replyinfo != NULL)
7399 msginfo = compose->replyinfo;
7400 else if (compose->fwdinfo != NULL)
7401 msginfo = compose->fwdinfo;
7403 dummyinfo = compose_msginfo_new_from_compose(compose);
7404 msginfo = dummyinfo;
7407 if (tmpl->to && *tmpl->to != '\0') {
7409 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7410 compose->gtkaspell);
7412 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7414 quote_fmt_scan_string(tmpl->to);
7417 buf = quote_fmt_get_buffer();
7419 alertpanel_error(_("Template To format error."));
7421 compose_entry_append(compose, buf, COMPOSE_TO);
7425 if (tmpl->cc && *tmpl->cc != '\0') {
7427 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7428 compose->gtkaspell);
7430 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7432 quote_fmt_scan_string(tmpl->cc);
7435 buf = quote_fmt_get_buffer();
7437 alertpanel_error(_("Template Cc format error."));
7439 compose_entry_append(compose, buf, COMPOSE_CC);
7443 if (tmpl->bcc && *tmpl->bcc != '\0') {
7445 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7446 compose->gtkaspell);
7448 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7450 quote_fmt_scan_string(tmpl->bcc);
7453 buf = quote_fmt_get_buffer();
7455 alertpanel_error(_("Template Bcc format error."));
7457 compose_entry_append(compose, buf, COMPOSE_BCC);
7461 /* process the subject */
7462 if (tmpl->subject && *tmpl->subject != '\0') {
7464 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7465 compose->gtkaspell);
7467 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7469 quote_fmt_scan_string(tmpl->subject);
7472 buf = quote_fmt_get_buffer();
7474 alertpanel_error(_("Template subject format error."));
7476 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
7480 procmsg_msginfo_free( dummyinfo );
7483 static void compose_destroy(Compose *compose)
7485 GtkTextBuffer *buffer;
7486 GtkClipboard *clipboard;
7488 compose_list = g_list_remove(compose_list, compose);
7490 if (compose->updating) {
7491 debug_print("danger, not destroying anything now\n");
7492 compose->deferred_destroy = TRUE;
7495 /* NOTE: address_completion_end() does nothing with the window
7496 * however this may change. */
7497 address_completion_end(compose->window);
7499 slist_free_strings(compose->to_list);
7500 g_slist_free(compose->to_list);
7501 slist_free_strings(compose->newsgroup_list);
7502 g_slist_free(compose->newsgroup_list);
7503 slist_free_strings(compose->header_list);
7504 g_slist_free(compose->header_list);
7506 procmsg_msginfo_free(compose->targetinfo);
7507 procmsg_msginfo_free(compose->replyinfo);
7508 procmsg_msginfo_free(compose->fwdinfo);
7510 g_free(compose->replyto);
7511 g_free(compose->cc);
7512 g_free(compose->bcc);
7513 g_free(compose->newsgroups);
7514 g_free(compose->followup_to);
7516 g_free(compose->ml_post);
7518 g_free(compose->inreplyto);
7519 g_free(compose->references);
7520 g_free(compose->msgid);
7521 g_free(compose->boundary);
7523 g_free(compose->redirect_filename);
7524 if (compose->undostruct)
7525 undo_destroy(compose->undostruct);
7527 g_free(compose->sig_str);
7529 g_free(compose->exteditor_file);
7531 g_free(compose->orig_charset);
7533 g_free(compose->privacy_system);
7535 if (addressbook_get_target_compose() == compose)
7536 addressbook_set_target_compose(NULL);
7539 if (compose->gtkaspell) {
7540 gtkaspell_delete(compose->gtkaspell);
7541 compose->gtkaspell = NULL;
7545 prefs_common.compose_width = compose->scrolledwin->allocation.width;
7546 prefs_common.compose_height = compose->window->allocation.height;
7548 if (!gtk_widget_get_parent(compose->paned))
7549 gtk_widget_destroy(compose->paned);
7550 gtk_widget_destroy(compose->popupmenu);
7552 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
7553 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7554 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
7556 gtk_widget_destroy(compose->window);
7557 toolbar_destroy(compose->toolbar);
7558 g_free(compose->toolbar);
7559 g_mutex_free(compose->mutex);
7563 static void compose_attach_info_free(AttachInfo *ainfo)
7565 g_free(ainfo->file);
7566 g_free(ainfo->content_type);
7567 g_free(ainfo->name);
7571 static void compose_attach_remove_selected(Compose *compose)
7573 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
7574 GtkTreeSelection *selection;
7576 GtkTreeModel *model;
7578 selection = gtk_tree_view_get_selection(tree_view);
7579 sel = gtk_tree_selection_get_selected_rows(selection, &model);
7584 for (cur = sel; cur != NULL; cur = cur->next) {
7585 GtkTreePath *path = cur->data;
7586 GtkTreeRowReference *ref = gtk_tree_row_reference_new
7589 gtk_tree_path_free(path);
7592 for (cur = sel; cur != NULL; cur = cur->next) {
7593 GtkTreeRowReference *ref = cur->data;
7594 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
7597 if (gtk_tree_model_get_iter(model, &iter, path))
7598 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
7600 gtk_tree_path_free(path);
7601 gtk_tree_row_reference_free(ref);
7607 static struct _AttachProperty
7610 GtkWidget *mimetype_entry;
7611 GtkWidget *encoding_optmenu;
7612 GtkWidget *path_entry;
7613 GtkWidget *filename_entry;
7615 GtkWidget *cancel_btn;
7618 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
7620 gtk_tree_path_free((GtkTreePath *)ptr);
7623 static void compose_attach_property(Compose *compose)
7625 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
7627 GtkComboBox *optmenu;
7628 GtkTreeSelection *selection;
7630 GtkTreeModel *model;
7633 static gboolean cancelled;
7635 /* only if one selected */
7636 selection = gtk_tree_view_get_selection(tree_view);
7637 if (gtk_tree_selection_count_selected_rows(selection) != 1)
7640 sel = gtk_tree_selection_get_selected_rows(selection, &model);
7644 path = (GtkTreePath *) sel->data;
7645 gtk_tree_model_get_iter(model, &iter, path);
7646 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
7649 g_list_foreach(sel, gtk_tree_path_free_, NULL);
7655 if (!attach_prop.window)
7656 compose_attach_property_create(&cancelled);
7657 gtk_widget_grab_focus(attach_prop.ok_btn);
7658 gtk_widget_show(attach_prop.window);
7659 manage_window_set_transient(GTK_WINDOW(attach_prop.window));
7661 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
7662 if (ainfo->encoding == ENC_UNKNOWN)
7663 combobox_select_by_data(optmenu, ENC_BASE64);
7665 combobox_select_by_data(optmenu, ainfo->encoding);
7667 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
7668 ainfo->content_type ? ainfo->content_type : "");
7669 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
7670 ainfo->file ? ainfo->file : "");
7671 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
7672 ainfo->name ? ainfo->name : "");
7675 const gchar *entry_text;
7677 gchar *cnttype = NULL;
7684 gtk_widget_hide(attach_prop.window);
7689 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
7690 if (*entry_text != '\0') {
7693 text = g_strstrip(g_strdup(entry_text));
7694 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
7695 cnttype = g_strdup(text);
7698 alertpanel_error(_("Invalid MIME type."));
7704 ainfo->encoding = combobox_get_active_data(optmenu);
7706 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
7707 if (*entry_text != '\0') {
7708 if (is_file_exist(entry_text) &&
7709 (size = get_file_size(entry_text)) > 0)
7710 file = g_strdup(entry_text);
7713 (_("File doesn't exist or is empty."));
7719 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
7720 if (*entry_text != '\0') {
7721 g_free(ainfo->name);
7722 ainfo->name = g_strdup(entry_text);
7726 g_free(ainfo->content_type);
7727 ainfo->content_type = cnttype;
7730 g_free(ainfo->file);
7736 /* update tree store */
7737 text = to_human_readable(ainfo->size);
7738 gtk_tree_model_get_iter(model, &iter, path);
7739 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
7740 COL_MIMETYPE, ainfo->content_type,
7742 COL_NAME, ainfo->name,
7748 gtk_tree_path_free(path);
7751 #define SET_LABEL_AND_ENTRY(str, entry, top) \
7753 label = gtk_label_new(str); \
7754 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
7755 GTK_FILL, 0, 0, 0); \
7756 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
7758 entry = gtk_entry_new(); \
7759 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
7760 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
7763 static void compose_attach_property_create(gboolean *cancelled)
7769 GtkWidget *mimetype_entry;
7772 GtkListStore *optmenu_menu;
7773 GtkWidget *path_entry;
7774 GtkWidget *filename_entry;
7777 GtkWidget *cancel_btn;
7778 GList *mime_type_list, *strlist;
7781 debug_print("Creating attach_property window...\n");
7783 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
7784 gtk_widget_set_size_request(window, 480, -1);
7785 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
7786 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
7787 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
7788 gtk_window_set_modal(GTK_WINDOW(window), TRUE);
7789 g_signal_connect(G_OBJECT(window), "delete_event",
7790 G_CALLBACK(attach_property_delete_event),
7792 g_signal_connect(G_OBJECT(window), "key_press_event",
7793 G_CALLBACK(attach_property_key_pressed),
7796 vbox = gtk_vbox_new(FALSE, 8);
7797 gtk_container_add(GTK_CONTAINER(window), vbox);
7799 table = gtk_table_new(4, 2, FALSE);
7800 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
7801 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
7802 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
7804 label = gtk_label_new(_("MIME type"));
7805 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
7807 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
7808 mimetype_entry = gtk_combo_new();
7809 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
7810 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
7812 /* stuff with list */
7813 mime_type_list = procmime_get_mime_type_list();
7815 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
7816 MimeType *type = (MimeType *) mime_type_list->data;
7819 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
7821 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
7824 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
7825 (GCompareFunc)strcmp2);
7828 gtk_combo_set_popdown_strings(GTK_COMBO(mimetype_entry), strlist);
7830 for (mime_type_list = strlist; mime_type_list != NULL;
7831 mime_type_list = mime_type_list->next)
7832 g_free(mime_type_list->data);
7833 g_list_free(strlist);
7835 mimetype_entry = GTK_COMBO(mimetype_entry)->entry;
7837 label = gtk_label_new(_("Encoding"));
7838 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
7840 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
7842 hbox = gtk_hbox_new(FALSE, 0);
7843 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
7844 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
7846 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
7847 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
7849 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
7850 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
7851 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
7852 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
7853 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
7855 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
7857 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
7858 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
7860 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
7861 &ok_btn, GTK_STOCK_OK,
7863 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
7864 gtk_widget_grab_default(ok_btn);
7866 g_signal_connect(G_OBJECT(ok_btn), "clicked",
7867 G_CALLBACK(attach_property_ok),
7869 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
7870 G_CALLBACK(attach_property_cancel),
7873 gtk_widget_show_all(vbox);
7875 attach_prop.window = window;
7876 attach_prop.mimetype_entry = mimetype_entry;
7877 attach_prop.encoding_optmenu = optmenu;
7878 attach_prop.path_entry = path_entry;
7879 attach_prop.filename_entry = filename_entry;
7880 attach_prop.ok_btn = ok_btn;
7881 attach_prop.cancel_btn = cancel_btn;
7884 #undef SET_LABEL_AND_ENTRY
7886 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
7892 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
7898 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
7899 gboolean *cancelled)
7907 static gboolean attach_property_key_pressed(GtkWidget *widget,
7909 gboolean *cancelled)
7911 if (event && event->keyval == GDK_Escape) {
7918 static void compose_exec_ext_editor(Compose *compose)
7925 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
7926 G_DIR_SEPARATOR, compose);
7928 if (pipe(pipe_fds) < 0) {
7934 if ((pid = fork()) < 0) {
7941 /* close the write side of the pipe */
7944 compose->exteditor_file = g_strdup(tmp);
7945 compose->exteditor_pid = pid;
7947 compose_set_ext_editor_sensitive(compose, FALSE);
7949 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
7950 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
7954 } else { /* process-monitoring process */
7960 /* close the read side of the pipe */
7963 if (compose_write_body_to_file(compose, tmp) < 0) {
7964 fd_write_all(pipe_fds[1], "2\n", 2);
7968 pid_ed = compose_exec_ext_editor_real(tmp);
7970 fd_write_all(pipe_fds[1], "1\n", 2);
7974 /* wait until editor is terminated */
7975 waitpid(pid_ed, NULL, 0);
7977 fd_write_all(pipe_fds[1], "0\n", 2);
7984 #endif /* G_OS_UNIX */
7988 static gint compose_exec_ext_editor_real(const gchar *file)
7995 g_return_val_if_fail(file != NULL, -1);
7997 if ((pid = fork()) < 0) {
8002 if (pid != 0) return pid;
8004 /* grandchild process */
8006 if (setpgid(0, getppid()))
8009 if (prefs_common.ext_editor_cmd &&
8010 (p = strchr(prefs_common.ext_editor_cmd, '%')) &&
8011 *(p + 1) == 's' && !strchr(p + 2, '%')) {
8012 g_snprintf(buf, sizeof(buf), prefs_common.ext_editor_cmd, file);
8014 if (prefs_common.ext_editor_cmd)
8015 g_warning("External editor command line is invalid: '%s'\n",
8016 prefs_common.ext_editor_cmd);
8017 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
8020 cmdline = strsplit_with_quote(buf, " ", 1024);
8021 execvp(cmdline[0], cmdline);
8024 g_strfreev(cmdline);
8029 static gboolean compose_ext_editor_kill(Compose *compose)
8031 pid_t pgid = compose->exteditor_pid * -1;
8034 ret = kill(pgid, 0);
8036 if (ret == 0 || (ret == -1 && EPERM == errno)) {
8040 msg = g_strdup_printf
8041 (_("The external editor is still working.\n"
8042 "Force terminating the process?\n"
8043 "process group id: %d"), -pgid);
8044 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
8045 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
8049 if (val == G_ALERTALTERNATE) {
8050 g_source_remove(compose->exteditor_tag);
8051 g_io_channel_shutdown(compose->exteditor_ch,
8053 g_io_channel_unref(compose->exteditor_ch);
8055 if (kill(pgid, SIGTERM) < 0) perror("kill");
8056 waitpid(compose->exteditor_pid, NULL, 0);
8058 g_warning("Terminated process group id: %d", -pgid);
8059 g_warning("Temporary file: %s",
8060 compose->exteditor_file);
8062 compose_set_ext_editor_sensitive(compose, TRUE);
8064 g_free(compose->exteditor_file);
8065 compose->exteditor_file = NULL;
8066 compose->exteditor_pid = -1;
8067 compose->exteditor_ch = NULL;
8068 compose->exteditor_tag = -1;
8076 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
8080 Compose *compose = (Compose *)data;
8083 debug_print(_("Compose: input from monitoring process\n"));
8085 g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL);
8087 g_io_channel_shutdown(source, FALSE, NULL);
8088 g_io_channel_unref(source);
8090 waitpid(compose->exteditor_pid, NULL, 0);
8092 if (buf[0] == '0') { /* success */
8093 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
8094 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
8096 gtk_text_buffer_set_text(buffer, "", -1);
8097 compose_insert_file(compose, compose->exteditor_file);
8098 compose_changed_cb(NULL, compose);
8100 if (g_unlink(compose->exteditor_file) < 0)
8101 FILE_OP_ERROR(compose->exteditor_file, "unlink");
8102 } else if (buf[0] == '1') { /* failed */
8103 g_warning("Couldn't exec external editor\n");
8104 if (g_unlink(compose->exteditor_file) < 0)
8105 FILE_OP_ERROR(compose->exteditor_file, "unlink");
8106 } else if (buf[0] == '2') {
8107 g_warning("Couldn't write to file\n");
8108 } else if (buf[0] == '3') {
8109 g_warning("Pipe read failed\n");
8112 compose_set_ext_editor_sensitive(compose, TRUE);
8114 g_free(compose->exteditor_file);
8115 compose->exteditor_file = NULL;
8116 compose->exteditor_pid = -1;
8117 compose->exteditor_ch = NULL;
8118 compose->exteditor_tag = -1;
8123 static void compose_set_ext_editor_sensitive(Compose *compose,
8126 GtkItemFactory *ifactory;
8128 ifactory = gtk_item_factory_from_widget(compose->menubar);
8130 menu_set_sensitive(ifactory, "/Message/Send", sensitive);
8131 menu_set_sensitive(ifactory, "/Message/Send later", sensitive);
8132 menu_set_sensitive(ifactory, "/Message/Insert file", sensitive);
8133 menu_set_sensitive(ifactory, "/Message/Insert signature", sensitive);
8134 menu_set_sensitive(ifactory, "/Edit/Wrap current paragraph", sensitive);
8135 menu_set_sensitive(ifactory, "/Edit/Wrap all long lines", sensitive);
8136 menu_set_sensitive(ifactory, "/Edit/Edit with external editor",
8139 gtk_widget_set_sensitive(compose->text, sensitive);
8140 if (compose->toolbar->send_btn)
8141 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
8142 if (compose->toolbar->sendl_btn)
8143 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
8144 if (compose->toolbar->draft_btn)
8145 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
8146 if (compose->toolbar->insert_btn)
8147 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
8148 if (compose->toolbar->sig_btn)
8149 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
8150 if (compose->toolbar->exteditor_btn)
8151 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
8152 if (compose->toolbar->linewrap_current_btn)
8153 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
8154 if (compose->toolbar->linewrap_all_btn)
8155 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
8157 #endif /* G_OS_UNIX */
8160 * compose_undo_state_changed:
8162 * Change the sensivity of the menuentries undo and redo
8164 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
8165 gint redo_state, gpointer data)
8167 GtkWidget *widget = GTK_WIDGET(data);
8168 GtkItemFactory *ifactory;
8170 g_return_if_fail(widget != NULL);
8172 ifactory = gtk_item_factory_from_widget(widget);
8174 switch (undo_state) {
8175 case UNDO_STATE_TRUE:
8176 if (!undostruct->undo_state) {
8177 undostruct->undo_state = TRUE;
8178 menu_set_sensitive(ifactory, "/Edit/Undo", TRUE);
8181 case UNDO_STATE_FALSE:
8182 if (undostruct->undo_state) {
8183 undostruct->undo_state = FALSE;
8184 menu_set_sensitive(ifactory, "/Edit/Undo", FALSE);
8187 case UNDO_STATE_UNCHANGED:
8189 case UNDO_STATE_REFRESH:
8190 menu_set_sensitive(ifactory, "/Edit/Undo",
8191 undostruct->undo_state);
8194 g_warning("Undo state not recognized");
8198 switch (redo_state) {
8199 case UNDO_STATE_TRUE:
8200 if (!undostruct->redo_state) {
8201 undostruct->redo_state = TRUE;
8202 menu_set_sensitive(ifactory, "/Edit/Redo", TRUE);
8205 case UNDO_STATE_FALSE:
8206 if (undostruct->redo_state) {
8207 undostruct->redo_state = FALSE;
8208 menu_set_sensitive(ifactory, "/Edit/Redo", FALSE);
8211 case UNDO_STATE_UNCHANGED:
8213 case UNDO_STATE_REFRESH:
8214 menu_set_sensitive(ifactory, "/Edit/Redo",
8215 undostruct->redo_state);
8218 g_warning("Redo state not recognized");
8223 /* callback functions */
8225 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
8226 * includes "non-client" (windows-izm) in calculation, so this calculation
8227 * may not be accurate.
8229 static gboolean compose_edit_size_alloc(GtkEditable *widget,
8230 GtkAllocation *allocation,
8231 GtkSHRuler *shruler)
8233 if (prefs_common.show_ruler) {
8234 gint char_width = 0, char_height = 0;
8235 gint line_width_in_chars;
8237 gtkut_get_font_size(GTK_WIDGET(widget),
8238 &char_width, &char_height);
8239 line_width_in_chars =
8240 (allocation->width - allocation->x) / char_width;
8242 /* got the maximum */
8243 gtk_ruler_set_range(GTK_RULER(shruler),
8244 0.0, line_width_in_chars, 0,
8245 /*line_width_in_chars*/ char_width);
8251 static void account_activated(GtkComboBox *optmenu, gpointer data)
8253 Compose *compose = (Compose *)data;
8256 gchar *folderidentifier;
8257 gint account_id = 0;
8261 /* Get ID of active account in the combo box */
8262 menu = gtk_combo_box_get_model(optmenu);
8263 gtk_combo_box_get_active_iter(optmenu, &iter);
8264 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
8266 ac = account_find_from_id(account_id);
8267 g_return_if_fail(ac != NULL);
8269 if (ac != compose->account)
8270 compose_select_account(compose, ac, FALSE);
8272 /* Set message save folder */
8273 if (account_get_special_folder(compose->account, F_OUTBOX)) {
8274 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
8276 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
8277 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
8279 gtk_editable_delete_text(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
8280 if (account_get_special_folder(compose->account, F_OUTBOX)) {
8281 folderidentifier = folder_item_get_identifier(account_get_special_folder
8282 (compose->account, F_OUTBOX));
8283 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
8284 g_free(folderidentifier);
8288 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
8289 GtkTreeViewColumn *column, Compose *compose)
8291 compose_attach_property(compose);
8294 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
8297 Compose *compose = (Compose *)data;
8298 GtkTreeSelection *attach_selection;
8299 gint attach_nr_selected;
8300 GtkItemFactory *ifactory;
8302 if (!event) return FALSE;
8304 if (event->button == 3) {
8305 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
8306 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
8307 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
8309 if (attach_nr_selected > 0)
8311 menu_set_sensitive(ifactory, "/Remove", TRUE);
8312 menu_set_sensitive(ifactory, "/Properties...", TRUE);
8314 menu_set_sensitive(ifactory, "/Remove", FALSE);
8315 menu_set_sensitive(ifactory, "/Properties...", FALSE);
8318 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
8319 NULL, NULL, event->button, event->time);
8326 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
8329 Compose *compose = (Compose *)data;
8331 if (!event) return FALSE;
8333 switch (event->keyval) {
8335 compose_attach_remove_selected(compose);
8341 static void compose_allow_user_actions (Compose *compose, gboolean allow)
8343 GtkItemFactory *ifactory = gtk_item_factory_from_widget(compose->menubar);
8344 toolbar_comp_set_sensitive(compose, allow);
8345 menu_set_sensitive(ifactory, "/Message", allow);
8346 menu_set_sensitive(ifactory, "/Edit", allow);
8348 menu_set_sensitive(ifactory, "/Spelling", allow);
8350 menu_set_sensitive(ifactory, "/Options", allow);
8351 menu_set_sensitive(ifactory, "/Tools", allow);
8352 menu_set_sensitive(ifactory, "/Help", allow);
8354 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
8358 static void compose_send_cb(gpointer data, guint action, GtkWidget *widget)
8360 Compose *compose = (Compose *)data;
8362 if (prefs_common.work_offline &&
8363 !inc_offline_should_override(TRUE,
8364 _("Claws Mail needs network access in order "
8365 "to send this email.")))
8368 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
8369 g_source_remove(compose->draft_timeout_tag);
8370 compose->draft_timeout_tag = -1;
8373 compose_send(compose);
8376 static void compose_send_later_cb(gpointer data, guint action,
8379 Compose *compose = (Compose *)data;
8383 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
8387 compose_close(compose);
8388 } else if (val == -1) {
8389 alertpanel_error(_("Could not queue message."));
8390 } else if (val == -2) {
8391 alertpanel_error(_("Could not queue message:\n\n%s."), strerror(errno));
8392 } else if (val == -3) {
8393 if (privacy_peek_error())
8394 alertpanel_error(_("Could not queue message for sending:\n\n"
8395 "Signature failed: %s"), privacy_get_error());
8396 } else if (val == -4) {
8397 alertpanel_error(_("Could not queue message for sending:\n\n"
8398 "Charset conversion failed."));
8399 } else if (val == -5) {
8400 alertpanel_error(_("Could not queue message for sending:\n\n"
8401 "Couldn't get recipient encryption key."));
8402 } else if (val == -6) {
8405 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
8408 #define DRAFTED_AT_EXIT "drafted_at_exit"
8409 static void compose_register_draft(MsgInfo *info)
8411 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8412 DRAFTED_AT_EXIT, NULL);
8413 FILE *fp = fopen(filepath, "ab");
8416 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
8424 gboolean compose_draft (gpointer data, guint action)
8426 Compose *compose = (Compose *)data;
8430 MsgFlags flag = {0, 0};
8431 static gboolean lock = FALSE;
8432 MsgInfo *newmsginfo;
8434 gboolean target_locked = FALSE;
8436 if (lock) return FALSE;
8438 if (compose->sending)
8441 draft = account_get_special_folder(compose->account, F_DRAFT);
8442 g_return_val_if_fail(draft != NULL, FALSE);
8444 if (!g_mutex_trylock(compose->mutex)) {
8445 /* we don't want to lock the mutex once it's available,
8446 * because as the only other part of compose.c locking
8447 * it is compose_close - which means once unlocked,
8448 * the compose struct will be freed */
8449 debug_print("couldn't lock mutex, probably sending\n");
8455 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
8456 G_DIR_SEPARATOR, compose);
8457 if ((fp = g_fopen(tmp, "wb")) == NULL) {
8458 FILE_OP_ERROR(tmp, "fopen");
8462 /* chmod for security */
8463 if (change_file_mode_rw(fp, tmp) < 0) {
8464 FILE_OP_ERROR(tmp, "chmod");
8465 g_warning("can't change file mode\n");
8468 /* Save draft infos */
8469 fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id);
8470 fprintf(fp, "S:%s\n", compose->account->address);
8472 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
8473 gchar *savefolderid;
8475 savefolderid = gtk_editable_get_chars(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
8476 fprintf(fp, "SCF:%s\n", savefolderid);
8477 g_free(savefolderid);
8479 if (compose->return_receipt) {
8480 fprintf(fp, "RRCPT:1\n");
8482 if (compose->privacy_system) {
8483 fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing);
8484 fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption);
8485 fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system);
8488 /* Message-ID of message replying to */
8489 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
8492 folderid = folder_item_get_identifier(compose->replyinfo->folder);
8493 fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid);
8496 /* Message-ID of message forwarding to */
8497 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
8500 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
8501 fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid);
8505 /* end of headers */
8506 fprintf(fp, "X-Claws-End-Special-Headers: 1\n");
8508 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
8516 if (compose->targetinfo) {
8517 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
8518 flag.perm_flags = target_locked?MSG_LOCKED:0;
8520 flag.tmp_flags = MSG_DRAFT;
8522 folder_item_scan(draft);
8523 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
8524 MsgInfo *tmpinfo = NULL;
8525 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
8526 if (compose->msgid) {
8527 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
8530 msgnum = tmpinfo->msgnum;
8531 procmsg_msginfo_free(tmpinfo);
8532 debug_print("got draft msgnum %d from scanning\n", msgnum);
8534 debug_print("didn't get draft msgnum after scanning\n");
8537 debug_print("got draft msgnum %d from adding\n", msgnum);
8542 if (action != COMPOSE_AUTO_SAVE) {
8543 if (action != COMPOSE_DRAFT_FOR_EXIT)
8544 alertpanel_error(_("Could not save draft."));
8547 gtkut_window_popup(compose->window);
8548 val = alertpanel_full(_("Could not save draft"),
8549 _("Could not save draft.\n"
8550 "Do you want to cancel exit or discard this email?"),
8551 _("_Cancel exit"), _("_Discard email"), NULL,
8552 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
8553 if (val == G_ALERTALTERNATE) {
8555 g_mutex_unlock(compose->mutex); /* must be done before closing */
8556 compose_close(compose);
8560 g_mutex_unlock(compose->mutex); /* must be done before closing */
8569 if (compose->mode == COMPOSE_REEDIT) {
8570 compose_remove_reedit_target(compose, TRUE);
8573 newmsginfo = folder_item_get_msginfo(draft, msgnum);
8576 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
8578 procmsg_msginfo_set_flags(newmsginfo, MSG_LOCKED, MSG_DRAFT);
8580 procmsg_msginfo_set_flags(newmsginfo, 0, MSG_DRAFT);
8581 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
8582 procmsg_msginfo_set_flags(newmsginfo, 0,
8583 MSG_HAS_ATTACHMENT);
8585 if (action == COMPOSE_DRAFT_FOR_EXIT) {
8586 compose_register_draft(newmsginfo);
8588 procmsg_msginfo_free(newmsginfo);
8591 folder_item_scan(draft);
8593 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
8595 g_mutex_unlock(compose->mutex); /* must be done before closing */
8596 compose_close(compose);
8602 path = folder_item_fetch_msg(draft, msgnum);
8604 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
8607 if (g_stat(path, &s) < 0) {
8608 FILE_OP_ERROR(path, "stat");
8614 procmsg_msginfo_free(compose->targetinfo);
8615 compose->targetinfo = procmsg_msginfo_new();
8616 compose->targetinfo->msgnum = msgnum;
8617 compose->targetinfo->size = s.st_size;
8618 compose->targetinfo->mtime = s.st_mtime;
8619 compose->targetinfo->folder = draft;
8621 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
8622 compose->mode = COMPOSE_REEDIT;
8624 if (action == COMPOSE_AUTO_SAVE) {
8625 compose->autosaved_draft = compose->targetinfo;
8627 compose->modified = FALSE;
8628 compose_set_title(compose);
8632 g_mutex_unlock(compose->mutex);
8636 void compose_clear_exit_drafts(void)
8638 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8639 DRAFTED_AT_EXIT, NULL);
8640 if (is_file_exist(filepath))
8646 void compose_reopen_exit_drafts(void)
8648 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8649 DRAFTED_AT_EXIT, NULL);
8650 FILE *fp = fopen(filepath, "rb");
8654 while (fgets(buf, sizeof(buf), fp)) {
8655 gchar **parts = g_strsplit(buf, "\t", 2);
8656 const gchar *folder = parts[0];
8657 int msgnum = parts[1] ? atoi(parts[1]):-1;
8659 if (folder && *folder && msgnum > -1) {
8660 FolderItem *item = folder_find_item_from_identifier(folder);
8661 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
8663 compose_reedit(info, FALSE);
8670 compose_clear_exit_drafts();
8673 static void compose_draft_cb(gpointer data, guint action, GtkWidget *widget)
8675 compose_draft(data, action);
8678 static void compose_attach_cb(gpointer data, guint action, GtkWidget *widget)
8680 Compose *compose = (Compose *)data;
8683 if (compose->redirect_filename != NULL)
8686 file_list = filesel_select_multiple_files_open(_("Select file"));
8691 for ( tmp = file_list; tmp; tmp = tmp->next) {
8692 gchar *file = (gchar *) tmp->data;
8693 gchar *utf8_filename = conv_filename_to_utf8(file);
8694 compose_attach_append(compose, file, utf8_filename, NULL);
8695 compose_changed_cb(NULL, compose);
8697 g_free(utf8_filename);
8699 g_list_free(file_list);
8703 static void compose_insert_file_cb(gpointer data, guint action,
8706 Compose *compose = (Compose *)data;
8709 file_list = filesel_select_multiple_files_open(_("Select file"));
8714 for ( tmp = file_list; tmp; tmp = tmp->next) {
8715 gchar *file = (gchar *) tmp->data;
8716 gchar *filedup = g_strdup(file);
8717 gchar *shortfile = g_path_get_basename(filedup);
8718 ComposeInsertResult res;
8720 res = compose_insert_file(compose, file);
8721 if (res == COMPOSE_INSERT_READ_ERROR) {
8722 alertpanel_error(_("File '%s' could not be read."), shortfile);
8723 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
8724 alertpanel_error(_("File '%s' contained invalid characters\n"
8725 "for the current encoding, insertion may be incorrect."), shortfile);
8731 g_list_free(file_list);
8735 static void compose_insert_sig_cb(gpointer data, guint action,
8738 Compose *compose = (Compose *)data;
8740 compose_insert_sig(compose, FALSE);
8743 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
8747 Compose *compose = (Compose *)data;
8749 gtkut_widget_get_uposition(widget, &x, &y);
8750 prefs_common.compose_x = x;
8751 prefs_common.compose_y = y;
8753 if (compose->sending || compose->updating)
8755 compose_close_cb(compose, 0, NULL);
8759 void compose_close_toolbar(Compose *compose)
8761 compose_close_cb(compose, 0, NULL);
8764 static void compose_close_cb(gpointer data, guint action, GtkWidget *widget)
8766 Compose *compose = (Compose *)data;
8770 if (compose->exteditor_tag != -1) {
8771 if (!compose_ext_editor_kill(compose))
8776 if (compose->modified) {
8777 val = alertpanel(_("Discard message"),
8778 _("This message has been modified. Discard it?"),
8779 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
8782 case G_ALERTDEFAULT:
8783 if (prefs_common.autosave)
8784 compose_remove_draft(compose);
8786 case G_ALERTALTERNATE:
8787 compose_draft_cb(data, COMPOSE_QUIT_EDITING, NULL);
8794 compose_close(compose);
8797 static void compose_set_encoding_cb(gpointer data, guint action,
8800 Compose *compose = (Compose *)data;
8802 if (GTK_CHECK_MENU_ITEM(widget)->active)
8803 compose->out_encoding = (CharSet)action;
8806 static void compose_address_cb(gpointer data, guint action, GtkWidget *widget)
8808 Compose *compose = (Compose *)data;
8810 addressbook_open(compose);
8813 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
8815 Compose *compose = (Compose *)data;
8820 tmpl = g_object_get_data(G_OBJECT(widget), "template");
8821 g_return_if_fail(tmpl != NULL);
8823 msg = g_strdup_printf(_("Do you want to apply the template '%s' ?"),
8825 val = alertpanel(_("Apply template"), msg,
8826 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
8829 if (val == G_ALERTDEFAULT)
8830 compose_template_apply(compose, tmpl, TRUE);
8831 else if (val == G_ALERTALTERNATE)
8832 compose_template_apply(compose, tmpl, FALSE);
8835 static void compose_ext_editor_cb(gpointer data, guint action,
8838 Compose *compose = (Compose *)data;
8840 compose_exec_ext_editor(compose);
8843 static void compose_undo_cb(Compose *compose)
8845 gboolean prev_autowrap = compose->autowrap;
8847 compose->autowrap = FALSE;
8848 undo_undo(compose->undostruct);
8849 compose->autowrap = prev_autowrap;
8852 static void compose_redo_cb(Compose *compose)
8854 gboolean prev_autowrap = compose->autowrap;
8856 compose->autowrap = FALSE;
8857 undo_redo(compose->undostruct);
8858 compose->autowrap = prev_autowrap;
8861 static void entry_cut_clipboard(GtkWidget *entry)
8863 if (GTK_IS_EDITABLE(entry))
8864 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
8865 else if (GTK_IS_TEXT_VIEW(entry))
8866 gtk_text_buffer_cut_clipboard(
8867 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
8868 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
8872 static void entry_copy_clipboard(GtkWidget *entry)
8874 if (GTK_IS_EDITABLE(entry))
8875 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
8876 else if (GTK_IS_TEXT_VIEW(entry))
8877 gtk_text_buffer_copy_clipboard(
8878 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
8879 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
8882 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
8883 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
8885 if (GTK_IS_TEXT_VIEW(entry)) {
8886 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
8887 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
8888 GtkTextIter start_iter, end_iter;
8890 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
8892 if (contents == NULL)
8895 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
8897 /* we shouldn't delete the selection when middle-click-pasting, or we
8898 * can't mid-click-paste our own selection */
8899 if (clip != GDK_SELECTION_PRIMARY) {
8900 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
8903 if (insert_place == NULL) {
8904 /* if insert_place isn't specified, insert at the cursor.
8905 * used for Ctrl-V pasting */
8906 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
8907 start = gtk_text_iter_get_offset(&start_iter);
8908 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
8910 /* if insert_place is specified, paste here.
8911 * used for mid-click-pasting */
8912 start = gtk_text_iter_get_offset(insert_place);
8913 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
8917 /* paste unwrapped: mark the paste so it's not wrapped later */
8918 end = start + strlen(contents);
8919 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
8920 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
8921 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
8922 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
8923 /* rewrap paragraph now (after a mid-click-paste) */
8924 mark_start = gtk_text_buffer_get_insert(buffer);
8925 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
8926 gtk_text_iter_backward_char(&start_iter);
8927 compose_beautify_paragraph(compose, &start_iter, TRUE);
8929 } else if (GTK_IS_EDITABLE(entry))
8930 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
8934 static void entry_allsel(GtkWidget *entry)
8936 if (GTK_IS_EDITABLE(entry))
8937 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
8938 else if (GTK_IS_TEXT_VIEW(entry)) {
8939 GtkTextIter startiter, enditer;
8940 GtkTextBuffer *textbuf;
8942 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
8943 gtk_text_buffer_get_start_iter(textbuf, &startiter);
8944 gtk_text_buffer_get_end_iter(textbuf, &enditer);
8946 gtk_text_buffer_move_mark_by_name(textbuf,
8947 "selection_bound", &startiter);
8948 gtk_text_buffer_move_mark_by_name(textbuf,
8949 "insert", &enditer);
8953 static void compose_cut_cb(Compose *compose)
8955 if (compose->focused_editable
8957 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
8960 entry_cut_clipboard(compose->focused_editable);
8963 static void compose_copy_cb(Compose *compose)
8965 if (compose->focused_editable
8967 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
8970 entry_copy_clipboard(compose->focused_editable);
8973 static void compose_paste_cb(Compose *compose)
8976 GtkTextBuffer *buffer;
8978 if (compose->focused_editable &&
8979 GTK_WIDGET_HAS_FOCUS(compose->focused_editable))
8980 entry_paste_clipboard(compose, compose->focused_editable,
8981 prefs_common.linewrap_pastes,
8982 GDK_SELECTION_CLIPBOARD, NULL);
8986 static void compose_paste_as_quote_cb(Compose *compose)
8988 gint wrap_quote = prefs_common.linewrap_quote;
8989 if (compose->focused_editable
8991 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
8994 /* let text_insert() (called directly or at a later time
8995 * after the gtk_editable_paste_clipboard) know that
8996 * text is to be inserted as a quotation. implemented
8997 * by using a simple refcount... */
8998 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
8999 G_OBJECT(compose->focused_editable),
9000 "paste_as_quotation"));
9001 g_object_set_data(G_OBJECT(compose->focused_editable),
9002 "paste_as_quotation",
9003 GINT_TO_POINTER(paste_as_quotation + 1));
9004 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
9005 entry_paste_clipboard(compose, compose->focused_editable,
9006 prefs_common.linewrap_pastes,
9007 GDK_SELECTION_CLIPBOARD, NULL);
9008 prefs_common.linewrap_quote = wrap_quote;
9012 static void compose_paste_no_wrap_cb(Compose *compose)
9015 GtkTextBuffer *buffer;
9017 if (compose->focused_editable
9019 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9022 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
9023 GDK_SELECTION_CLIPBOARD, NULL);
9027 static void compose_paste_wrap_cb(Compose *compose)
9030 GtkTextBuffer *buffer;
9032 if (compose->focused_editable
9034 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9037 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
9038 GDK_SELECTION_CLIPBOARD, NULL);
9042 static void compose_allsel_cb(Compose *compose)
9044 if (compose->focused_editable
9046 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9049 entry_allsel(compose->focused_editable);
9052 static void textview_move_beginning_of_line (GtkTextView *text)
9054 GtkTextBuffer *buffer;
9058 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9060 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9061 mark = gtk_text_buffer_get_insert(buffer);
9062 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9063 gtk_text_iter_set_line_offset(&ins, 0);
9064 gtk_text_buffer_place_cursor(buffer, &ins);
9067 static void textview_move_forward_character (GtkTextView *text)
9069 GtkTextBuffer *buffer;
9073 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9075 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9076 mark = gtk_text_buffer_get_insert(buffer);
9077 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9078 if (gtk_text_iter_forward_cursor_position(&ins))
9079 gtk_text_buffer_place_cursor(buffer, &ins);
9082 static void textview_move_backward_character (GtkTextView *text)
9084 GtkTextBuffer *buffer;
9088 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9090 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9091 mark = gtk_text_buffer_get_insert(buffer);
9092 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9093 if (gtk_text_iter_backward_cursor_position(&ins))
9094 gtk_text_buffer_place_cursor(buffer, &ins);
9097 static void textview_move_forward_word (GtkTextView *text)
9099 GtkTextBuffer *buffer;
9104 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9106 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9107 mark = gtk_text_buffer_get_insert(buffer);
9108 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9109 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
9110 if (gtk_text_iter_forward_word_ends(&ins, count)) {
9111 gtk_text_iter_backward_word_start(&ins);
9112 gtk_text_buffer_place_cursor(buffer, &ins);
9116 static void textview_move_backward_word (GtkTextView *text)
9118 GtkTextBuffer *buffer;
9123 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9125 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9126 mark = gtk_text_buffer_get_insert(buffer);
9127 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9128 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
9129 if (gtk_text_iter_backward_word_starts(&ins, 1))
9130 gtk_text_buffer_place_cursor(buffer, &ins);
9133 static void textview_move_end_of_line (GtkTextView *text)
9135 GtkTextBuffer *buffer;
9139 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9141 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9142 mark = gtk_text_buffer_get_insert(buffer);
9143 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9144 if (gtk_text_iter_forward_to_line_end(&ins))
9145 gtk_text_buffer_place_cursor(buffer, &ins);
9148 static void textview_move_next_line (GtkTextView *text)
9150 GtkTextBuffer *buffer;
9155 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9157 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9158 mark = gtk_text_buffer_get_insert(buffer);
9159 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9160 offset = gtk_text_iter_get_line_offset(&ins);
9161 if (gtk_text_iter_forward_line(&ins)) {
9162 gtk_text_iter_set_line_offset(&ins, offset);
9163 gtk_text_buffer_place_cursor(buffer, &ins);
9167 static void textview_move_previous_line (GtkTextView *text)
9169 GtkTextBuffer *buffer;
9174 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9176 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9177 mark = gtk_text_buffer_get_insert(buffer);
9178 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9179 offset = gtk_text_iter_get_line_offset(&ins);
9180 if (gtk_text_iter_backward_line(&ins)) {
9181 gtk_text_iter_set_line_offset(&ins, offset);
9182 gtk_text_buffer_place_cursor(buffer, &ins);
9186 static void textview_delete_forward_character (GtkTextView *text)
9188 GtkTextBuffer *buffer;
9190 GtkTextIter ins, end_iter;
9192 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9194 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9195 mark = gtk_text_buffer_get_insert(buffer);
9196 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9198 if (gtk_text_iter_forward_char(&end_iter)) {
9199 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9203 static void textview_delete_backward_character (GtkTextView *text)
9205 GtkTextBuffer *buffer;
9207 GtkTextIter ins, end_iter;
9209 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9211 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9212 mark = gtk_text_buffer_get_insert(buffer);
9213 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9215 if (gtk_text_iter_backward_char(&end_iter)) {
9216 gtk_text_buffer_delete(buffer, &end_iter, &ins);
9220 static void textview_delete_forward_word (GtkTextView *text)
9222 GtkTextBuffer *buffer;
9224 GtkTextIter ins, end_iter;
9226 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9228 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9229 mark = gtk_text_buffer_get_insert(buffer);
9230 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9232 if (gtk_text_iter_forward_word_end(&end_iter)) {
9233 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9237 static void textview_delete_backward_word (GtkTextView *text)
9239 GtkTextBuffer *buffer;
9241 GtkTextIter ins, end_iter;
9243 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9245 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9246 mark = gtk_text_buffer_get_insert(buffer);
9247 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9249 if (gtk_text_iter_backward_word_start(&end_iter)) {
9250 gtk_text_buffer_delete(buffer, &end_iter, &ins);
9254 static void textview_delete_line (GtkTextView *text)
9256 GtkTextBuffer *buffer;
9258 GtkTextIter ins, start_iter, end_iter;
9261 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9263 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9264 mark = gtk_text_buffer_get_insert(buffer);
9265 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9268 gtk_text_iter_set_line_offset(&start_iter, 0);
9271 if (gtk_text_iter_ends_line(&end_iter))
9272 found = gtk_text_iter_forward_char(&end_iter);
9274 found = gtk_text_iter_forward_to_line_end(&end_iter);
9277 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
9280 static void textview_delete_to_line_end (GtkTextView *text)
9282 GtkTextBuffer *buffer;
9284 GtkTextIter ins, end_iter;
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);
9293 if (gtk_text_iter_ends_line(&end_iter))
9294 found = gtk_text_iter_forward_char(&end_iter);
9296 found = gtk_text_iter_forward_to_line_end(&end_iter);
9298 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9301 static void compose_advanced_action_cb(Compose *compose,
9302 ComposeCallAdvancedAction action)
9304 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9306 void (*do_action) (GtkTextView *text);
9307 } action_table[] = {
9308 {textview_move_beginning_of_line},
9309 {textview_move_forward_character},
9310 {textview_move_backward_character},
9311 {textview_move_forward_word},
9312 {textview_move_backward_word},
9313 {textview_move_end_of_line},
9314 {textview_move_next_line},
9315 {textview_move_previous_line},
9316 {textview_delete_forward_character},
9317 {textview_delete_backward_character},
9318 {textview_delete_forward_word},
9319 {textview_delete_backward_word},
9320 {textview_delete_line},
9321 {NULL}, /* gtk_stext_delete_line_n */
9322 {textview_delete_to_line_end}
9325 if (!GTK_WIDGET_HAS_FOCUS(text)) return;
9327 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
9328 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
9329 if (action_table[action].do_action)
9330 action_table[action].do_action(text);
9332 g_warning("Not implemented yet.");
9336 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
9340 if (GTK_IS_EDITABLE(widget)) {
9341 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
9342 gtk_editable_set_position(GTK_EDITABLE(widget),
9345 if (widget->parent && widget->parent->parent
9346 && widget->parent->parent->parent) {
9347 if (GTK_IS_SCROLLED_WINDOW(widget->parent->parent->parent)) {
9348 gint y = widget->allocation.y;
9349 gint height = widget->allocation.height;
9350 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
9351 (GTK_SCROLLED_WINDOW(widget->parent->parent->parent));
9353 if (y < (int)shown->value) {
9354 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown), y - 1);
9356 if (y + height > (int)shown->value + (int)shown->page_size) {
9357 if (y - height - 1 < (int)shown->upper - (int)shown->page_size) {
9358 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
9359 y + height - (int)shown->page_size - 1);
9361 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
9362 (int)shown->upper - (int)shown->page_size - 1);
9369 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
9370 compose->focused_editable = widget;
9373 if (GTK_IS_TEXT_VIEW(widget)
9374 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
9375 gtk_widget_ref(compose->notebook);
9376 gtk_widget_ref(compose->edit_vbox);
9377 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
9378 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
9379 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
9380 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
9381 gtk_widget_unref(compose->notebook);
9382 gtk_widget_unref(compose->edit_vbox);
9383 g_signal_handlers_block_by_func(G_OBJECT(widget),
9384 G_CALLBACK(compose_grab_focus_cb),
9386 gtk_widget_grab_focus(widget);
9387 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
9388 G_CALLBACK(compose_grab_focus_cb),
9390 } else if (!GTK_IS_TEXT_VIEW(widget)
9391 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
9392 gtk_widget_ref(compose->notebook);
9393 gtk_widget_ref(compose->edit_vbox);
9394 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
9395 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
9396 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
9397 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
9398 gtk_widget_unref(compose->notebook);
9399 gtk_widget_unref(compose->edit_vbox);
9400 g_signal_handlers_block_by_func(G_OBJECT(widget),
9401 G_CALLBACK(compose_grab_focus_cb),
9403 gtk_widget_grab_focus(widget);
9404 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
9405 G_CALLBACK(compose_grab_focus_cb),
9411 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
9413 compose->modified = TRUE;
9415 compose_set_title(compose);
9419 static void compose_wrap_cb(gpointer data, guint action, GtkWidget *widget)
9421 Compose *compose = (Compose *)data;
9424 compose_wrap_all_full(compose, TRUE);
9426 compose_beautify_paragraph(compose, NULL, TRUE);
9429 static void compose_find_cb(gpointer data, guint action, GtkWidget *widget)
9431 Compose *compose = (Compose *)data;
9433 message_search_compose(compose);
9436 static void compose_toggle_autowrap_cb(gpointer data, guint action,
9439 Compose *compose = (Compose *)data;
9440 compose->autowrap = GTK_CHECK_MENU_ITEM(widget)->active;
9441 if (compose->autowrap)
9442 compose_wrap_all_full(compose, TRUE);
9443 compose->autowrap = GTK_CHECK_MENU_ITEM(widget)->active;
9446 static void compose_toggle_sign_cb(gpointer data, guint action,
9449 Compose *compose = (Compose *)data;
9451 if (GTK_CHECK_MENU_ITEM(widget)->active)
9452 compose->use_signing = TRUE;
9454 compose->use_signing = FALSE;
9457 static void compose_toggle_encrypt_cb(gpointer data, guint action,
9460 Compose *compose = (Compose *)data;
9462 if (GTK_CHECK_MENU_ITEM(widget)->active)
9463 compose->use_encryption = TRUE;
9465 compose->use_encryption = FALSE;
9468 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
9470 g_free(compose->privacy_system);
9472 compose->privacy_system = g_strdup(account->default_privacy_system);
9473 compose_update_privacy_system_menu_item(compose, warn);
9476 static void compose_toggle_ruler_cb(gpointer data, guint action,
9479 Compose *compose = (Compose *)data;
9481 if (GTK_CHECK_MENU_ITEM(widget)->active) {
9482 gtk_widget_show(compose->ruler_hbox);
9483 prefs_common.show_ruler = TRUE;
9485 gtk_widget_hide(compose->ruler_hbox);
9486 gtk_widget_queue_resize(compose->edit_vbox);
9487 prefs_common.show_ruler = FALSE;
9491 static void compose_attach_drag_received_cb (GtkWidget *widget,
9492 GdkDragContext *context,
9495 GtkSelectionData *data,
9500 Compose *compose = (Compose *)user_data;
9503 if (gdk_atom_name(data->type) &&
9504 !strcmp(gdk_atom_name(data->type), "text/uri-list")
9505 && gtk_drag_get_source_widget(context) !=
9506 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
9507 list = uri_list_extract_filenames((const gchar *)data->data);
9508 for (tmp = list; tmp != NULL; tmp = tmp->next) {
9509 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
9510 compose_attach_append
9511 (compose, (const gchar *)tmp->data,
9512 utf8_filename, NULL);
9513 g_free(utf8_filename);
9515 if (list) compose_changed_cb(NULL, compose);
9516 list_free_strings(list);
9518 } else if (gtk_drag_get_source_widget(context)
9519 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
9520 /* comes from our summaryview */
9521 SummaryView * summaryview = NULL;
9522 GSList * list = NULL, *cur = NULL;
9524 if (mainwindow_get_mainwindow())
9525 summaryview = mainwindow_get_mainwindow()->summaryview;
9528 list = summary_get_selected_msg_list(summaryview);
9530 for (cur = list; cur; cur = cur->next) {
9531 MsgInfo *msginfo = (MsgInfo *)cur->data;
9534 file = procmsg_get_message_file_full(msginfo,
9537 compose_attach_append(compose, (const gchar *)file,
9538 (const gchar *)file, "message/rfc822");
9546 static gboolean compose_drag_drop(GtkWidget *widget,
9547 GdkDragContext *drag_context,
9549 guint time, gpointer user_data)
9551 /* not handling this signal makes compose_insert_drag_received_cb
9556 static void compose_insert_drag_received_cb (GtkWidget *widget,
9557 GdkDragContext *drag_context,
9560 GtkSelectionData *data,
9565 Compose *compose = (Compose *)user_data;
9568 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
9570 if (gdk_atom_name(data->type) && !strcmp(gdk_atom_name(data->type), "text/uri-list")) {
9571 AlertValue val = G_ALERTDEFAULT;
9573 switch (prefs_common.compose_dnd_mode) {
9574 case COMPOSE_DND_ASK:
9575 val = alertpanel_full(_("Insert or attach?"),
9576 _("Do you want to insert the contents of the file(s) "
9577 "into the message body, or attach it to the email?"),
9578 GTK_STOCK_CANCEL, _("+_Insert"), _("_Attach"),
9579 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
9581 case COMPOSE_DND_INSERT:
9582 val = G_ALERTALTERNATE;
9584 case COMPOSE_DND_ATTACH:
9588 /* unexpected case */
9589 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
9592 if (val & G_ALERTDISABLE) {
9593 val &= ~G_ALERTDISABLE;
9594 /* remember what action to perform by default, only if we don't click Cancel */
9595 if (val == G_ALERTALTERNATE)
9596 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
9597 else if (val == G_ALERTOTHER)
9598 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
9601 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
9602 gtk_drag_finish(drag_context, FALSE, FALSE, time);
9604 } else if (val == G_ALERTOTHER) {
9605 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
9608 list = uri_list_extract_filenames((const gchar *)data->data);
9609 for (tmp = list; tmp != NULL; tmp = tmp->next) {
9610 compose_insert_file(compose, (const gchar *)tmp->data);
9612 list_free_strings(list);
9614 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9617 #if GTK_CHECK_VERSION(2, 8, 0)
9618 /* do nothing, handled by GTK */
9620 gchar *tmpfile = get_tmp_file();
9621 str_write_to_file((const gchar *)data->data, tmpfile);
9622 compose_insert_file(compose, tmpfile);
9625 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9629 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9632 static void compose_header_drag_received_cb (GtkWidget *widget,
9633 GdkDragContext *drag_context,
9636 GtkSelectionData *data,
9641 GtkEditable *entry = (GtkEditable *)user_data;
9642 gchar *email = (gchar *)data->data;
9644 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
9647 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
9648 gchar *decoded=g_new(gchar, strlen(email));
9651 email += strlen("mailto:");
9652 decode_uri(decoded, email); /* will fit */
9653 gtk_editable_delete_text(entry, 0, -1);
9654 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
9655 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9659 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9662 static void compose_toggle_return_receipt_cb(gpointer data, guint action,
9665 Compose *compose = (Compose *)data;
9667 if (GTK_CHECK_MENU_ITEM(widget)->active)
9668 compose->return_receipt = TRUE;
9670 compose->return_receipt = FALSE;
9673 static void compose_toggle_remove_refs_cb(gpointer data, guint action,
9676 Compose *compose = (Compose *)data;
9678 if (GTK_CHECK_MENU_ITEM(widget)->active)
9679 compose->remove_references = TRUE;
9681 compose->remove_references = FALSE;
9684 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
9686 ComposeHeaderEntry *headerentry)
9688 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
9689 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
9690 !(event->state & GDK_MODIFIER_MASK) &&
9691 (event->keyval == GDK_BackSpace) &&
9692 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
9693 gtk_container_remove
9694 (GTK_CONTAINER(headerentry->compose->header_table),
9695 headerentry->combo);
9696 gtk_container_remove
9697 (GTK_CONTAINER(headerentry->compose->header_table),
9698 headerentry->entry);
9699 headerentry->compose->header_list =
9700 g_slist_remove(headerentry->compose->header_list,
9702 g_free(headerentry);
9703 } else if (event->keyval == GDK_Tab) {
9704 if (headerentry->compose->header_last == headerentry) {
9705 /* Override default next focus, and give it to subject_entry
9706 * instead of notebook tabs
9708 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
9709 gtk_widget_grab_focus(headerentry->compose->subject_entry);
9716 static gboolean compose_headerentry_changed_cb(GtkWidget *entry,
9717 ComposeHeaderEntry *headerentry)
9719 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
9720 compose_create_header_entry(headerentry->compose);
9721 g_signal_handlers_disconnect_matched
9722 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
9723 0, 0, NULL, NULL, headerentry);
9725 /* Automatically scroll down */
9726 compose_show_first_last_header(headerentry->compose, FALSE);
9732 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
9734 GtkAdjustment *vadj;
9736 g_return_if_fail(compose);
9737 g_return_if_fail(GTK_IS_WIDGET(compose->header_table));
9738 g_return_if_fail(GTK_IS_VIEWPORT(compose->header_table->parent));
9740 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(compose->header_table->parent));
9741 gtk_adjustment_set_value(vadj, (show_first ? vadj->lower : vadj->upper));
9742 gtk_adjustment_changed(vadj);
9745 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
9746 const gchar *text, gint len, Compose *compose)
9748 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
9749 (G_OBJECT(compose->text), "paste_as_quotation"));
9752 g_return_if_fail(text != NULL);
9754 g_signal_handlers_block_by_func(G_OBJECT(buffer),
9755 G_CALLBACK(text_inserted),
9757 if (paste_as_quotation) {
9761 GtkTextIter start_iter;
9766 new_text = g_strndup(text, len);
9768 qmark = compose_quote_char_from_context(compose);
9770 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
9771 gtk_text_buffer_place_cursor(buffer, iter);
9773 pos = gtk_text_iter_get_offset(iter);
9775 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
9776 _("Quote format error at line %d."));
9777 quote_fmt_reset_vartable();
9779 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
9780 GINT_TO_POINTER(paste_as_quotation - 1));
9782 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
9783 gtk_text_buffer_place_cursor(buffer, iter);
9784 gtk_text_buffer_delete_mark(buffer, mark);
9786 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
9787 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
9788 compose_beautify_paragraph(compose, &start_iter, FALSE);
9789 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
9790 gtk_text_buffer_delete_mark(buffer, mark);
9792 if (strcmp(text, "\n") || compose->automatic_break
9793 || gtk_text_iter_starts_line(iter))
9794 gtk_text_buffer_insert(buffer, iter, text, len);
9796 debug_print("insert nowrap \\n\n");
9797 gtk_text_buffer_insert_with_tags_by_name(buffer,
9798 iter, text, len, "no_join", NULL);
9802 if (!paste_as_quotation) {
9803 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
9804 compose_beautify_paragraph(compose, iter, FALSE);
9805 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
9806 gtk_text_buffer_delete_mark(buffer, mark);
9809 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
9810 G_CALLBACK(text_inserted),
9812 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
9814 if (prefs_common.autosave &&
9815 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
9816 compose->draft_timeout_tag != -2 /* disabled while loading */)
9817 compose->draft_timeout_tag = g_timeout_add
9818 (500, (GtkFunction) compose_defer_auto_save_draft, compose);
9820 static gint compose_defer_auto_save_draft(Compose *compose)
9822 compose->draft_timeout_tag = -1;
9823 compose_draft_cb((gpointer)compose, COMPOSE_AUTO_SAVE, NULL);
9828 static void compose_check_all(Compose *compose)
9830 if (compose->gtkaspell)
9831 gtkaspell_check_all(compose->gtkaspell);
9834 static void compose_highlight_all(Compose *compose)
9836 if (compose->gtkaspell)
9837 gtkaspell_highlight_all(compose->gtkaspell);
9840 static void compose_check_backwards(Compose *compose)
9842 if (compose->gtkaspell)
9843 gtkaspell_check_backwards(compose->gtkaspell);
9845 GtkItemFactory *ifactory;
9846 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
9847 menu_set_sensitive(ifactory, "/Edit/Check backwards misspelled word", FALSE);
9848 menu_set_sensitive(ifactory, "/Edit/Forward to next misspelled word", FALSE);
9852 static void compose_check_forwards_go(Compose *compose)
9854 if (compose->gtkaspell)
9855 gtkaspell_check_forwards_go(compose->gtkaspell);
9857 GtkItemFactory *ifactory;
9858 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
9859 menu_set_sensitive(ifactory, "/Edit/Check backwards misspelled word", FALSE);
9860 menu_set_sensitive(ifactory, "/Edit/Forward to next misspelled word", FALSE);
9866 *\brief Guess originating forward account from MsgInfo and several
9867 * "common preference" settings. Return NULL if no guess.
9869 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
9871 PrefsAccount *account = NULL;
9873 g_return_val_if_fail(msginfo, NULL);
9874 g_return_val_if_fail(msginfo->folder, NULL);
9875 g_return_val_if_fail(msginfo->folder->prefs, NULL);
9877 if (msginfo->folder->prefs->enable_default_account)
9878 account = account_find_from_id(msginfo->folder->prefs->default_account);
9881 account = msginfo->folder->folder->account;
9883 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
9885 Xstrdup_a(to, msginfo->to, return NULL);
9886 extract_address(to);
9887 account = account_find_from_address(to);
9890 if (!account && prefs_common.forward_account_autosel) {
9892 if (!procheader_get_header_from_msginfo
9893 (msginfo, cc,sizeof cc , "Cc:")) {
9894 gchar *buf = cc + strlen("Cc:");
9895 extract_address(buf);
9896 account = account_find_from_address(buf);
9900 if (!account && prefs_common.forward_account_autosel) {
9901 gchar deliveredto[BUFFSIZE];
9902 if (!procheader_get_header_from_msginfo
9903 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
9904 gchar *buf = deliveredto + strlen("Delivered-To:");
9905 extract_address(buf);
9906 account = account_find_from_address(buf);
9913 gboolean compose_close(Compose *compose)
9917 if (!g_mutex_trylock(compose->mutex)) {
9918 /* we have to wait for the (possibly deferred by auto-save)
9919 * drafting to be done, before destroying the compose under
9921 debug_print("waiting for drafting to finish...\n");
9922 compose_allow_user_actions(compose, FALSE);
9923 g_timeout_add (500, (GSourceFunc) compose_close, compose);
9926 g_return_val_if_fail(compose, FALSE);
9927 gtkut_widget_get_uposition(compose->window, &x, &y);
9928 prefs_common.compose_x = x;
9929 prefs_common.compose_y = y;
9930 g_mutex_unlock(compose->mutex);
9931 compose_destroy(compose);
9936 * Add entry field for each address in list.
9937 * \param compose E-Mail composition object.
9938 * \param listAddress List of (formatted) E-Mail addresses.
9940 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
9945 addr = ( gchar * ) node->data;
9946 compose_entry_append( compose, addr, COMPOSE_TO );
9947 node = g_list_next( node );
9951 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
9952 guint action, gboolean opening_multiple)
9955 GSList *new_msglist = NULL;
9956 MsgInfo *tmp_msginfo = NULL;
9957 gboolean originally_enc = FALSE;
9958 Compose *compose = NULL;
9960 g_return_if_fail(msgview != NULL);
9962 g_return_if_fail(msginfo_list != NULL);
9964 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
9965 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
9966 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
9968 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
9969 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
9970 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
9971 orig_msginfo, mimeinfo);
9972 if (tmp_msginfo != NULL) {
9973 new_msglist = g_slist_append(NULL, tmp_msginfo);
9975 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
9976 tmp_msginfo->folder = orig_msginfo->folder;
9977 tmp_msginfo->msgnum = orig_msginfo->msgnum;
9978 if (orig_msginfo->tags)
9979 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
9984 if (!opening_multiple)
9985 body = messageview_get_selection(msgview);
9988 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
9989 procmsg_msginfo_free(tmp_msginfo);
9990 g_slist_free(new_msglist);
9992 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
9994 if (compose && originally_enc) {
9995 compose_force_encryption(compose, compose->account, FALSE);
10001 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
10004 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
10005 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
10006 GSList *cur = msginfo_list;
10007 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
10008 "messages. Opening the windows "
10009 "could take some time. Do you "
10010 "want to continue?"),
10011 g_slist_length(msginfo_list));
10012 if (g_slist_length(msginfo_list) > 9
10013 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
10014 != G_ALERTALTERNATE) {
10019 /* We'll open multiple compose windows */
10020 /* let the WM place the next windows */
10021 compose_force_window_origin = FALSE;
10022 for (; cur; cur = cur->next) {
10024 tmplist.data = cur->data;
10025 tmplist.next = NULL;
10026 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
10028 compose_force_window_origin = TRUE;
10030 /* forwarding multiple mails as attachments is done via a
10031 * single compose window */
10032 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
10036 void compose_set_position(Compose *compose, gint pos)
10038 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10040 gtkut_text_view_set_position(text, pos);
10043 gboolean compose_search_string(Compose *compose,
10044 const gchar *str, gboolean case_sens)
10046 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10048 return gtkut_text_view_search_string(text, str, case_sens);
10051 gboolean compose_search_string_backward(Compose *compose,
10052 const gchar *str, gboolean case_sens)
10054 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10056 return gtkut_text_view_search_string_backward(text, str, case_sens);
10059 /* allocate a msginfo structure and populate its data from a compose data structure */
10060 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
10062 MsgInfo *newmsginfo;
10064 gchar buf[BUFFSIZE];
10066 g_return_val_if_fail( compose != NULL, NULL );
10068 newmsginfo = procmsg_msginfo_new();
10071 get_rfc822_date(buf, sizeof(buf));
10072 newmsginfo->date = g_strdup(buf);
10075 if (compose->from_name) {
10076 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
10077 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
10081 if (compose->subject_entry)
10082 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
10084 /* to, cc, reply-to, newsgroups */
10085 for (list = compose->header_list; list; list = list->next) {
10086 gchar *header = gtk_editable_get_chars(
10088 GTK_COMBO(((ComposeHeaderEntry *)list->data)->combo)->entry), 0, -1);
10089 gchar *entry = gtk_editable_get_chars(
10090 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
10092 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
10093 if ( newmsginfo->to == NULL ) {
10094 newmsginfo->to = g_strdup(entry);
10095 } else if (entry && *entry) {
10096 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
10097 g_free(newmsginfo->to);
10098 newmsginfo->to = tmp;
10101 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
10102 if ( newmsginfo->cc == NULL ) {
10103 newmsginfo->cc = g_strdup(entry);
10104 } else if (entry && *entry) {
10105 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
10106 g_free(newmsginfo->cc);
10107 newmsginfo->cc = tmp;
10110 if ( strcasecmp(header,
10111 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
10112 if ( newmsginfo->newsgroups == NULL ) {
10113 newmsginfo->newsgroups = g_strdup(entry);
10114 } else if (entry && *entry) {
10115 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
10116 g_free(newmsginfo->newsgroups);
10117 newmsginfo->newsgroups = tmp;
10125 /* other data is unset */
10131 /* update compose's dictionaries from folder dict settings */
10132 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
10133 FolderItem *folder_item)
10135 g_return_if_fail(compose != NULL);
10137 if (compose->gtkaspell && folder_item && folder_item->prefs) {
10138 FolderItemPrefs *prefs = folder_item->prefs;
10140 if (prefs->enable_default_dictionary)
10141 gtkaspell_change_dict(compose->gtkaspell,
10142 prefs->default_dictionary, FALSE);
10143 if (folder_item->prefs->enable_default_alt_dictionary)
10144 gtkaspell_change_alt_dict(compose->gtkaspell,
10145 prefs->default_alt_dictionary);
10146 if (prefs->enable_default_dictionary
10147 || prefs->enable_default_alt_dictionary)
10148 compose_spell_menu_changed(compose);