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_TO_LINE_END
156 } ComposeCallAdvancedAction;
160 PRIORITY_HIGHEST = 1,
169 COMPOSE_INSERT_SUCCESS,
170 COMPOSE_INSERT_READ_ERROR,
171 COMPOSE_INSERT_INVALID_CHARACTER,
172 COMPOSE_INSERT_NO_FILE
173 } ComposeInsertResult;
177 COMPOSE_WRITE_FOR_SEND,
178 COMPOSE_WRITE_FOR_STORE
183 COMPOSE_QUOTE_FORCED,
188 #define B64_LINE_SIZE 57
189 #define B64_BUFFSIZE 77
191 #define MAX_REFERENCES_LEN 999
193 static GList *compose_list = NULL;
195 static Compose *compose_generic_new (PrefsAccount *account,
198 GPtrArray *attach_files,
199 GList *listAddress );
201 static Compose *compose_create (PrefsAccount *account,
206 static void compose_entry_mark_default_to (Compose *compose,
207 const gchar *address);
208 static Compose *compose_followup_and_reply_to (MsgInfo *msginfo,
209 ComposeQuoteMode quote_mode,
213 static Compose *compose_forward_multiple (PrefsAccount *account,
214 GSList *msginfo_list);
215 static Compose *compose_reply (MsgInfo *msginfo,
216 ComposeQuoteMode quote_mode,
221 static Compose *compose_reply_mode (ComposeMode mode,
222 GSList *msginfo_list,
224 static void compose_template_apply_fields(Compose *compose, Template *tmpl);
225 static void compose_update_privacy_systems_menu(Compose *compose);
227 static GtkWidget *compose_account_option_menu_create
229 static void compose_set_out_encoding (Compose *compose);
230 static void compose_set_template_menu (Compose *compose);
231 static void compose_template_apply (Compose *compose,
234 static void compose_destroy (Compose *compose);
236 static void compose_entries_set (Compose *compose,
238 ComposeEntryType to_type);
239 static gint compose_parse_header (Compose *compose,
241 static gchar *compose_parse_references (const gchar *ref,
244 static gchar *compose_quote_fmt (Compose *compose,
250 gboolean need_unescape,
251 const gchar *err_msg);
253 static void compose_reply_set_entry (Compose *compose,
259 followup_and_reply_to);
260 static void compose_reedit_set_entry (Compose *compose,
263 static void compose_insert_sig (Compose *compose,
265 static gchar *compose_get_signature_str (Compose *compose);
266 static ComposeInsertResult compose_insert_file (Compose *compose,
269 static gboolean compose_attach_append (Compose *compose,
272 const gchar *content_type);
273 static void compose_attach_parts (Compose *compose,
276 static gboolean compose_beautify_paragraph (Compose *compose,
277 GtkTextIter *par_iter,
279 static void compose_wrap_all (Compose *compose);
280 static void compose_wrap_all_full (Compose *compose,
283 static void compose_set_title (Compose *compose);
284 static void compose_select_account (Compose *compose,
285 PrefsAccount *account,
288 static PrefsAccount *compose_current_mail_account(void);
289 /* static gint compose_send (Compose *compose); */
290 static gboolean compose_check_for_valid_recipient
292 static gboolean compose_check_entries (Compose *compose,
293 gboolean check_everything);
294 static gint compose_write_to_file (Compose *compose,
297 gboolean attach_parts);
298 static gint compose_write_body_to_file (Compose *compose,
300 static gint compose_remove_reedit_target (Compose *compose,
302 static void compose_remove_draft (Compose *compose);
303 static gint compose_queue_sub (Compose *compose,
307 gboolean check_subject,
308 gboolean remove_reedit_target);
309 static void compose_add_attachments (Compose *compose,
311 static gchar *compose_get_header (Compose *compose);
313 static void compose_convert_header (Compose *compose,
318 gboolean addr_field);
320 static void compose_attach_info_free (AttachInfo *ainfo);
321 static void compose_attach_remove_selected (Compose *compose);
323 static void compose_attach_property (Compose *compose);
324 static void compose_attach_property_create (gboolean *cancelled);
325 static void attach_property_ok (GtkWidget *widget,
326 gboolean *cancelled);
327 static void attach_property_cancel (GtkWidget *widget,
328 gboolean *cancelled);
329 static gint attach_property_delete_event (GtkWidget *widget,
331 gboolean *cancelled);
332 static gboolean attach_property_key_pressed (GtkWidget *widget,
334 gboolean *cancelled);
336 static void compose_exec_ext_editor (Compose *compose);
338 static gint compose_exec_ext_editor_real (const gchar *file);
339 static gboolean compose_ext_editor_kill (Compose *compose);
340 static gboolean compose_input_cb (GIOChannel *source,
341 GIOCondition condition,
343 static void compose_set_ext_editor_sensitive (Compose *compose,
345 #endif /* G_OS_UNIX */
347 static void compose_undo_state_changed (UndoMain *undostruct,
352 static void compose_create_header_entry (Compose *compose);
353 static void compose_add_header_entry (Compose *compose, const gchar *header, gchar *text);
354 static void compose_remove_header_entries(Compose *compose);
356 static void compose_update_priority_menu_item(Compose * compose);
358 static void compose_spell_menu_changed (void *data);
360 static void compose_add_field_list ( Compose *compose,
361 GList *listAddress );
363 /* callback functions */
365 static gboolean compose_edit_size_alloc (GtkEditable *widget,
366 GtkAllocation *allocation,
367 GtkSHRuler *shruler);
368 static void account_activated (GtkComboBox *optmenu,
370 static void attach_selected (GtkTreeView *tree_view,
371 GtkTreePath *tree_path,
372 GtkTreeViewColumn *column,
374 static gboolean attach_button_pressed (GtkWidget *widget,
375 GdkEventButton *event,
377 static gboolean attach_key_pressed (GtkWidget *widget,
380 static void compose_send_cb (gpointer data,
383 static void compose_send_later_cb (gpointer data,
387 static void compose_draft_cb (gpointer data,
391 static void compose_attach_cb (gpointer data,
394 static void compose_insert_file_cb (gpointer data,
397 static void compose_insert_sig_cb (gpointer data,
401 static void compose_close_cb (gpointer data,
405 static void compose_set_encoding_cb (gpointer data,
409 static void compose_address_cb (gpointer data,
412 static void compose_template_activate_cb(GtkWidget *widget,
415 static void compose_ext_editor_cb (gpointer data,
419 static gint compose_delete_cb (GtkWidget *widget,
423 static void compose_undo_cb (Compose *compose);
424 static void compose_redo_cb (Compose *compose);
425 static void compose_cut_cb (Compose *compose);
426 static void compose_copy_cb (Compose *compose);
427 static void compose_paste_cb (Compose *compose);
428 static void compose_paste_as_quote_cb (Compose *compose);
429 static void compose_paste_no_wrap_cb (Compose *compose);
430 static void compose_paste_wrap_cb (Compose *compose);
431 static void compose_allsel_cb (Compose *compose);
433 static void compose_advanced_action_cb (Compose *compose,
434 ComposeCallAdvancedAction action);
436 static void compose_grab_focus_cb (GtkWidget *widget,
439 static void compose_changed_cb (GtkTextBuffer *textbuf,
442 static void compose_wrap_cb (gpointer data,
445 static void compose_find_cb (gpointer data,
448 static void compose_toggle_autowrap_cb (gpointer data,
452 static void compose_toggle_ruler_cb (gpointer data,
455 static void compose_toggle_sign_cb (gpointer data,
458 static void compose_toggle_encrypt_cb (gpointer data,
461 static void compose_set_privacy_system_cb(GtkWidget *widget,
463 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn);
464 static void activate_privacy_system (Compose *compose,
465 PrefsAccount *account,
467 static void compose_use_signing(Compose *compose, gboolean use_signing);
468 static void compose_use_encryption(Compose *compose, gboolean use_encryption);
469 static void compose_toggle_return_receipt_cb(gpointer data, guint action,
471 static void compose_toggle_remove_refs_cb(gpointer data, guint action,
473 static void compose_set_priority_cb (gpointer data,
476 static void compose_reply_change_mode (gpointer data,
480 static void compose_attach_drag_received_cb (GtkWidget *widget,
481 GdkDragContext *drag_context,
484 GtkSelectionData *data,
488 static void compose_insert_drag_received_cb (GtkWidget *widget,
489 GdkDragContext *drag_context,
492 GtkSelectionData *data,
496 static void compose_header_drag_received_cb (GtkWidget *widget,
497 GdkDragContext *drag_context,
500 GtkSelectionData *data,
505 static gboolean compose_drag_drop (GtkWidget *widget,
506 GdkDragContext *drag_context,
508 guint time, gpointer user_data);
510 static void text_inserted (GtkTextBuffer *buffer,
515 static Compose *compose_generic_reply(MsgInfo *msginfo,
516 ComposeQuoteMode quote_mode,
520 gboolean followup_and_reply_to,
523 static gboolean compose_headerentry_changed_cb (GtkWidget *entry,
524 ComposeHeaderEntry *headerentry);
525 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
527 ComposeHeaderEntry *headerentry);
529 static void compose_show_first_last_header (Compose *compose, gboolean show_first);
531 static void compose_allow_user_actions (Compose *compose, gboolean allow);
534 static void compose_check_all (Compose *compose);
535 static void compose_highlight_all (Compose *compose);
536 static void compose_check_backwards (Compose *compose);
537 static void compose_check_forwards_go (Compose *compose);
540 static gint compose_defer_auto_save_draft (Compose *compose);
541 static PrefsAccount *compose_guess_forward_account_from_msginfo (MsgInfo *msginfo);
543 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose);
546 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
547 FolderItem *folder_item);
549 static void compose_attach_update_label(Compose *compose);
551 static void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data);
553 static GtkItemFactoryEntry compose_popup_entries[] =
555 {N_("/_Add..."), NULL, compose_attach_cb, 0, NULL},
556 {N_("/_Remove"), NULL, compose_attach_remove_selected, 0, NULL},
557 {"/---", NULL, NULL, 0, "<Separator>"},
558 {N_("/_Properties..."), NULL, compose_attach_property, 0, NULL}
561 static GtkItemFactoryEntry compose_entries[] =
563 {N_("/_Message"), NULL, NULL, 0, "<Branch>"},
564 {N_("/_Message/S_end"), "<control>Return",
565 compose_send_cb, 0, NULL},
566 {N_("/_Message/Send _later"), "<shift><control>S",
567 compose_send_later_cb, 0, NULL},
568 {N_("/_Message/---"), NULL, NULL, 0, "<Separator>"},
569 {N_("/_Message/_Attach file"), "<control>M", compose_attach_cb, 0, NULL},
570 {N_("/_Message/_Insert file"), "<control>I", compose_insert_file_cb, 0, NULL},
571 {N_("/_Message/Insert si_gnature"), "<control>G", compose_insert_sig_cb, 0, NULL},
572 {N_("/_Message/---"), NULL, NULL, 0, "<Separator>"},
573 {N_("/_Message/_Save"),
574 "<control>S", compose_draft_cb, COMPOSE_KEEP_EDITING, NULL},
575 {N_("/_Message/---"), NULL, NULL, 0, "<Separator>"},
576 {N_("/_Message/_Close"), "<control>W", compose_close_cb, 0, NULL},
578 {N_("/_Edit"), NULL, NULL, 0, "<Branch>"},
579 {N_("/_Edit/_Undo"), "<control>Z", compose_undo_cb, 0, NULL},
580 {N_("/_Edit/_Redo"), "<control>Y", compose_redo_cb, 0, NULL},
581 {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
582 {N_("/_Edit/Cu_t"), "<control>X", compose_cut_cb, 0, NULL},
583 {N_("/_Edit/_Copy"), "<control>C", compose_copy_cb, 0, NULL},
584 {N_("/_Edit/_Paste"), "<control>V", compose_paste_cb, 0, NULL},
585 {N_("/_Edit/Special paste"), NULL, NULL, 0, "<Branch>"},
586 {N_("/_Edit/Special paste/as _quotation"),
587 NULL, compose_paste_as_quote_cb, 0, NULL},
588 {N_("/_Edit/Special paste/_wrapped"),
589 NULL, compose_paste_wrap_cb, 0, NULL},
590 {N_("/_Edit/Special paste/_unwrapped"),
591 NULL, compose_paste_no_wrap_cb, 0, NULL},
592 {N_("/_Edit/Select _all"), "<control>A", compose_allsel_cb, 0, NULL},
593 {N_("/_Edit/A_dvanced"), NULL, NULL, 0, "<Branch>"},
594 {N_("/_Edit/A_dvanced/Move a character backward"),
596 compose_advanced_action_cb,
597 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER,
599 {N_("/_Edit/A_dvanced/Move a character forward"),
601 compose_advanced_action_cb,
602 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER,
604 {N_("/_Edit/A_dvanced/Move a word backward"),
606 compose_advanced_action_cb,
607 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD,
609 {N_("/_Edit/A_dvanced/Move a word forward"),
611 compose_advanced_action_cb,
612 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD,
614 {N_("/_Edit/A_dvanced/Move to beginning of line"),
615 NULL, /* "<control>A" */
616 compose_advanced_action_cb,
617 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE,
619 {N_("/_Edit/A_dvanced/Move to end of line"),
621 compose_advanced_action_cb,
622 COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE,
624 {N_("/_Edit/A_dvanced/Move to previous line"),
626 compose_advanced_action_cb,
627 COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE,
629 {N_("/_Edit/A_dvanced/Move to next line"),
631 compose_advanced_action_cb,
632 COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE,
634 {N_("/_Edit/A_dvanced/Delete a character backward"),
636 compose_advanced_action_cb,
637 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER,
639 {N_("/_Edit/A_dvanced/Delete a character forward"),
641 compose_advanced_action_cb,
642 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER,
644 {N_("/_Edit/A_dvanced/Delete a word backward"),
645 NULL, /* "<control>W" */
646 compose_advanced_action_cb,
647 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD,
649 {N_("/_Edit/A_dvanced/Delete a word forward"),
650 NULL, /* "<alt>D", */
651 compose_advanced_action_cb,
652 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD,
654 {N_("/_Edit/A_dvanced/Delete line"),
656 compose_advanced_action_cb,
657 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE,
659 {N_("/_Edit/A_dvanced/Delete to end of line"),
661 compose_advanced_action_cb,
662 COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END,
664 {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
666 "<control>F", compose_find_cb, 0, NULL},
667 {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
668 {N_("/_Edit/_Wrap current paragraph"),
669 "<control>L", compose_wrap_cb, 0, NULL},
670 {N_("/_Edit/Wrap all long _lines"),
671 "<control><alt>L", compose_wrap_cb, 1, NULL},
672 {N_("/_Edit/Aut_o wrapping"), "<shift><control>L", compose_toggle_autowrap_cb, 0, "<ToggleItem>"},
673 {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
674 {N_("/_Edit/Edit with e_xternal editor"),
675 "<shift><control>X", compose_ext_editor_cb, 0, NULL},
677 {N_("/_Spelling"), NULL, NULL, 0, "<Branch>"},
678 {N_("/_Spelling/_Check all or check selection"),
679 NULL, compose_check_all, 0, NULL},
680 {N_("/_Spelling/_Highlight all misspelled words"),
681 NULL, compose_highlight_all, 0, NULL},
682 {N_("/_Spelling/Check _backwards misspelled word"),
683 NULL, compose_check_backwards , 0, NULL},
684 {N_("/_Spelling/_Forward to next misspelled word"),
685 NULL, compose_check_forwards_go, 0, NULL},
686 {N_("/_Spelling/---"), NULL, NULL, 0, "<Separator>"},
687 {N_("/_Spelling/Options"),
688 NULL, NULL, 0, "<Branch>"},
690 {N_("/_Options"), NULL, NULL, 0, "<Branch>"},
691 {N_("/_Options/Reply _mode"), NULL, NULL, 0, "<Branch>"},
692 {N_("/_Options/Reply _mode/_Normal"), NULL, compose_reply_change_mode, COMPOSE_REPLY, "<RadioItem>"},
693 {N_("/_Options/Reply _mode/_All"), NULL, compose_reply_change_mode, COMPOSE_REPLY_TO_ALL, "/Options/Reply mode/Normal"},
694 {N_("/_Options/Reply _mode/_Sender"), NULL, compose_reply_change_mode, COMPOSE_REPLY_TO_SENDER, "/Options/Reply mode/Normal"},
695 {N_("/_Options/Reply _mode/_Mailing-list"), NULL, compose_reply_change_mode, COMPOSE_REPLY_TO_LIST, "/Options/Reply mode/Normal"},
696 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
697 {N_("/_Options/Privacy _System"), NULL, NULL, 0, "<Branch>"},
698 {N_("/_Options/Privacy _System/None"), NULL, NULL, 0, "<RadioItem>"},
699 {N_("/_Options/Si_gn"), NULL, compose_toggle_sign_cb , 0, "<ToggleItem>"},
700 {N_("/_Options/_Encrypt"), NULL, compose_toggle_encrypt_cb, 0, "<ToggleItem>"},
701 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
702 {N_("/_Options/_Priority"), NULL, NULL, 0, "<Branch>"},
703 {N_("/_Options/Priority/_Highest"), NULL, compose_set_priority_cb, PRIORITY_HIGHEST, "<RadioItem>"},
704 {N_("/_Options/Priority/Hi_gh"), NULL, compose_set_priority_cb, PRIORITY_HIGH, "/Options/Priority/Highest"},
705 {N_("/_Options/Priority/_Normal"), NULL, compose_set_priority_cb, PRIORITY_NORMAL, "/Options/Priority/Highest"},
706 {N_("/_Options/Priority/Lo_w"), NULL, compose_set_priority_cb, PRIORITY_LOW, "/Options/Priority/Highest"},
707 {N_("/_Options/Priority/_Lowest"), NULL, compose_set_priority_cb, PRIORITY_LOWEST, "/Options/Priority/Highest"},
708 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
709 {N_("/_Options/_Request Return Receipt"), NULL, compose_toggle_return_receipt_cb, 0, "<ToggleItem>"},
710 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
711 {N_("/_Options/Remo_ve references"), NULL, compose_toggle_remove_refs_cb, 0, "<ToggleItem>"},
712 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
714 #define ENC_ACTION(action) \
715 NULL, compose_set_encoding_cb, action, \
716 "/Options/Character encoding/Automatic"
718 {N_("/_Options/Character _encoding"), NULL, NULL, 0, "<Branch>"},
719 {N_("/_Options/Character _encoding/_Automatic"),
720 NULL, compose_set_encoding_cb, C_AUTO, "<RadioItem>"},
721 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
723 {N_("/_Options/Character _encoding/7bit ASCII (US-ASC_II)"),
724 ENC_ACTION(C_US_ASCII)},
725 {N_("/_Options/Character _encoding/Unicode (_UTF-8)"),
726 ENC_ACTION(C_UTF_8)},
727 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
729 {N_("/_Options/Character _encoding/Western European"), NULL, NULL, 0, "<Branch>"},
730 {N_("/_Options/Character _encoding/Western European/ISO-8859-_1"),
731 ENC_ACTION(C_ISO_8859_1)},
732 {N_("/_Options/Character _encoding/Western European/ISO-8859-15"),
733 ENC_ACTION(C_ISO_8859_15)},
734 {N_("/_Options/Character _encoding/Western European/Windows-1252"),
735 ENC_ACTION(C_WINDOWS_1252)},
737 {N_("/_Options/Character _encoding/Central European (ISO-8859-_2)"),
738 ENC_ACTION(C_ISO_8859_2)},
740 {N_("/_Options/Character _encoding/Baltic"), NULL, NULL, 0, "<Branch>"},
741 {N_("/_Options/Character _encoding/Baltic/ISO-8859-13"),
742 ENC_ACTION(C_ISO_8859_13)},
743 {N_("/_Options/Character _encoding/Baltic/ISO-8859-_4"),
744 ENC_ACTION(C_ISO_8859_4)},
746 {N_("/_Options/Character _encoding/Greek (ISO-8859-_7)"),
747 ENC_ACTION(C_ISO_8859_7)},
749 {N_("/_Options/Character _encoding/Hebrew"), NULL, NULL, 0, "<Branch>"},
750 {N_("/_Options/Character _encoding/Hebrew/ISO-8859-_8"),
751 ENC_ACTION(C_ISO_8859_8)},
752 {N_("/_Options/Character _encoding/Hebrew/Windows-1255"),
753 ENC_ACTION(C_WINDOWS_1255)},
755 {N_("/_Options/Character _encoding/Arabic"), NULL, NULL, 0, "<Branch>"},
756 {N_("/_Options/Character _encoding/Arabic/ISO-8859-_6"),
757 ENC_ACTION(C_ISO_8859_6)},
758 {N_("/_Options/Character _encoding/Arabic/Windows-1256"),
759 ENC_ACTION(C_CP1256)},
761 {N_("/_Options/Character _encoding/Turkish (ISO-8859-_9)"),
762 ENC_ACTION(C_ISO_8859_9)},
764 {N_("/_Options/Character _encoding/Cyrillic"), NULL, NULL, 0, "<Branch>"},
765 {N_("/_Options/Character _encoding/Cyrillic/ISO-8859-_5"),
766 ENC_ACTION(C_ISO_8859_5)},
767 {N_("/_Options/Character _encoding/Cyrillic/KOI8-_R"),
768 ENC_ACTION(C_KOI8_R)},
769 {N_("/_Options/Character _encoding/Cyrillic/KOI8-U"),
770 ENC_ACTION(C_KOI8_U)},
771 {N_("/_Options/Character _encoding/Cyrillic/Windows-1251"),
772 ENC_ACTION(C_WINDOWS_1251)},
774 {N_("/_Options/Character _encoding/Japanese"), NULL, NULL, 0, "<Branch>"},
775 {N_("/_Options/Character _encoding/Japanese/ISO-2022-_JP"),
776 ENC_ACTION(C_ISO_2022_JP)},
777 {N_("/_Options/Character _encoding/Japanese/ISO-2022-JP-2"),
778 ENC_ACTION(C_ISO_2022_JP_2)},
779 {N_("/_Options/Character _encoding/Japanese/_EUC-JP"),
780 ENC_ACTION(C_EUC_JP)},
781 {N_("/_Options/Character _encoding/Japanese/_Shift__JIS"),
782 ENC_ACTION(C_SHIFT_JIS)},
784 {N_("/_Options/Character _encoding/Chinese"), NULL, NULL, 0, "<Branch>"},
785 {N_("/_Options/Character _encoding/Chinese/Simplified (_GB2312)"),
786 ENC_ACTION(C_GB2312)},
787 {N_("/_Options/Character _encoding/Chinese/Simplified (GBK)"),
789 {N_("/_Options/Character _encoding/Chinese/Traditional (_Big5)"),
791 {N_("/_Options/Character _encoding/Chinese/Traditional (EUC-_TW)"),
792 ENC_ACTION(C_EUC_TW)},
794 {N_("/_Options/Character _encoding/Korean"), NULL, NULL, 0, "<Branch>"},
795 {N_("/_Options/Character _encoding/Korean/EUC-_KR"),
796 ENC_ACTION(C_EUC_KR)},
797 {N_("/_Options/Character _encoding/Korean/ISO-2022-KR"),
798 ENC_ACTION(C_ISO_2022_KR)},
800 {N_("/_Options/Character _encoding/Thai"), NULL, NULL, 0, "<Branch>"},
801 {N_("/_Options/Character _encoding/Thai/TIS-620"),
802 ENC_ACTION(C_TIS_620)},
803 {N_("/_Options/Character _encoding/Thai/Windows-874"),
804 ENC_ACTION(C_WINDOWS_874)},
806 {N_("/_Tools"), NULL, NULL, 0, "<Branch>"},
807 {N_("/_Tools/Show _ruler"), NULL, compose_toggle_ruler_cb, 0, "<ToggleItem>"},
808 {N_("/_Tools/_Address book"), "<shift><control>A", compose_address_cb , 0, NULL},
809 {N_("/_Tools/_Template"), NULL, NULL, 0, "<Branch>"},
810 {N_("/_Tools/Actio_ns"), NULL, NULL, 0, "<Branch>"},
811 {N_("/_Help"), NULL, NULL, 0, "<Branch>"},
812 {N_("/_Help/_About"), NULL, about_show, 0, NULL}
815 static GtkTargetEntry compose_mime_types[] =
817 {"text/uri-list", 0, 0},
818 {"UTF8_STRING", 0, 0},
822 static gboolean compose_put_existing_to_front(MsgInfo *info)
824 GList *compose_list = compose_get_compose_list();
828 for (elem = compose_list; elem != NULL && elem->data != NULL;
830 Compose *c = (Compose*)elem->data;
832 if (!c->targetinfo || !c->targetinfo->msgid ||
836 if (!strcmp(c->targetinfo->msgid, info->msgid)) {
837 gtkut_window_popup(c->window);
845 static GdkColor quote_color1 =
846 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
847 static GdkColor quote_color2 =
848 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
849 static GdkColor quote_color3 =
850 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
852 static GdkColor quote_bgcolor1 =
853 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
854 static GdkColor quote_bgcolor2 =
855 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
856 static GdkColor quote_bgcolor3 =
857 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
859 static GdkColor signature_color = {
866 static GdkColor uri_color = {
873 static void compose_create_tags(GtkTextView *text, Compose *compose)
875 GtkTextBuffer *buffer;
876 GdkColor black = {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
882 buffer = gtk_text_view_get_buffer(text);
884 if (prefs_common.enable_color) {
885 /* grab the quote colors, converting from an int to a GdkColor */
886 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
888 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
890 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
892 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_bgcol,
894 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_bgcol,
896 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_bgcol,
898 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
900 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
903 signature_color = quote_color1 = quote_color2 = quote_color3 =
904 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
907 if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
908 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
909 "foreground-gdk", "e_color1,
910 "paragraph-background-gdk", "e_bgcolor1,
912 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
913 "foreground-gdk", "e_color2,
914 "paragraph-background-gdk", "e_bgcolor2,
916 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
917 "foreground-gdk", "e_color3,
918 "paragraph-background-gdk", "e_bgcolor3,
921 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
922 "foreground-gdk", "e_color1,
924 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
925 "foreground-gdk", "e_color2,
927 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
928 "foreground-gdk", "e_color3,
932 compose->signature_tag = gtk_text_buffer_create_tag(buffer, "signature",
933 "foreground-gdk", &signature_color,
936 compose->uri_tag = gtk_text_buffer_create_tag(buffer, "link",
937 "foreground-gdk", &uri_color,
939 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
940 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
942 color[0] = quote_color1;
943 color[1] = quote_color2;
944 color[2] = quote_color3;
945 color[3] = quote_bgcolor1;
946 color[4] = quote_bgcolor2;
947 color[5] = quote_bgcolor3;
948 color[6] = signature_color;
949 color[7] = uri_color;
950 cmap = gdk_drawable_get_colormap(compose->window->window);
951 gdk_colormap_alloc_colors(cmap, color, 8, FALSE, TRUE, success);
953 for (i = 0; i < 8; i++) {
954 if (success[i] == FALSE) {
957 g_warning("Compose: color allocation failed.\n");
958 style = gtk_widget_get_style(GTK_WIDGET(text));
959 quote_color1 = quote_color2 = quote_color3 =
960 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 =
961 signature_color = uri_color = black;
966 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
967 GPtrArray *attach_files)
969 return compose_generic_new(account, mailto, NULL, attach_files, NULL);
972 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
974 return compose_generic_new(account, mailto, item, NULL, NULL);
977 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
979 return compose_generic_new( account, NULL, NULL, NULL, listAddress );
982 #define SCROLL_TO_CURSOR(compose) { \
983 GtkTextMark *cmark = gtk_text_buffer_get_insert( \
984 gtk_text_view_get_buffer( \
985 GTK_TEXT_VIEW(compose->text))); \
986 gtk_text_view_scroll_mark_onscreen( \
987 GTK_TEXT_VIEW(compose->text), \
991 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
992 GPtrArray *attach_files, GList *listAddress )
995 GtkTextView *textview;
996 GtkTextBuffer *textbuf;
998 GtkItemFactory *ifactory;
999 const gchar *subject_format = NULL;
1000 const gchar *body_format = NULL;
1001 gchar *mailto_from = NULL;
1002 PrefsAccount *mailto_account = NULL;
1003 MsgInfo* dummyinfo = NULL;
1005 /* check if mailto defines a from */
1006 if (mailto && *mailto != '\0') {
1007 scan_mailto_url(mailto, &mailto_from, NULL, NULL, NULL, NULL, NULL, NULL);
1008 /* mailto defines a from, check if we can get account prefs from it,
1009 if not, the account prefs will be guessed using other ways, but we'll keep
1012 mailto_account = account_find_from_address(mailto_from, TRUE);
1014 account = mailto_account;
1017 /* if no account prefs set from mailto, set if from folder prefs (if any) */
1018 if (!mailto_account && item && item->prefs && item->prefs->enable_default_account)
1019 account = account_find_from_id(item->prefs->default_account);
1021 /* if no account prefs set, fallback to the current one */
1022 if (!account) account = cur_account;
1023 g_return_val_if_fail(account != NULL, NULL);
1025 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
1027 /* override from name if mailto asked for it */
1029 gtk_entry_set_text(GTK_ENTRY(compose->from_name), mailto_from);
1030 g_free(mailto_from);
1032 /* override from name according to folder properties */
1033 if (item && item->prefs &&
1034 item->prefs->compose_with_format &&
1035 item->prefs->compose_override_from_format &&
1036 *item->prefs->compose_override_from_format != '\0') {
1041 dummyinfo = compose_msginfo_new_from_compose(compose);
1043 /* decode \-escape sequences in the internal representation of the quote format */
1044 tmp = malloc(strlen(item->prefs->compose_override_from_format)+1);
1045 pref_get_unescaped_pref(tmp, item->prefs->compose_override_from_format);
1048 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1049 compose->gtkaspell);
1051 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1053 quote_fmt_scan_string(tmp);
1056 buf = quote_fmt_get_buffer();
1058 alertpanel_error(_("New message From format error."));
1060 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1061 quote_fmt_reset_vartable();
1066 ifactory = gtk_item_factory_from_widget(compose->menubar);
1068 compose->replyinfo = NULL;
1069 compose->fwdinfo = NULL;
1071 textview = GTK_TEXT_VIEW(compose->text);
1072 textbuf = gtk_text_view_get_buffer(textview);
1073 compose_create_tags(textview, compose);
1075 undo_block(compose->undostruct);
1077 compose_set_dictionaries_from_folder_prefs(compose, item);
1080 if (account->auto_sig)
1081 compose_insert_sig(compose, FALSE);
1082 gtk_text_buffer_get_start_iter(textbuf, &iter);
1083 gtk_text_buffer_place_cursor(textbuf, &iter);
1085 if (account->protocol != A_NNTP) {
1086 if (mailto && *mailto != '\0') {
1087 compose_entries_set(compose, mailto, COMPOSE_TO);
1089 } else if (item && item->prefs->enable_default_to) {
1090 compose_entry_append(compose, item->prefs->default_to, COMPOSE_TO);
1091 compose_entry_mark_default_to(compose, item->prefs->default_to);
1093 if (item && item->ret_rcpt) {
1094 menu_set_active(ifactory, "/Options/Request Return Receipt", TRUE);
1097 if (mailto && *mailto != '\0') {
1098 if (!strchr(mailto, '@'))
1099 compose_entries_set(compose, mailto, COMPOSE_NEWSGROUPS);
1101 compose_entries_set(compose, mailto, COMPOSE_TO);
1102 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1103 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS);
1106 * CLAWS: just don't allow return receipt request, even if the user
1107 * may want to send an email. simple but foolproof.
1109 menu_set_sensitive(ifactory, "/Options/Request Return Receipt", FALSE);
1111 compose_add_field_list( compose, listAddress );
1113 if (item && item->prefs && item->prefs->compose_with_format) {
1114 subject_format = item->prefs->compose_subject_format;
1115 body_format = item->prefs->compose_body_format;
1116 } else if (account->compose_with_format) {
1117 subject_format = account->compose_subject_format;
1118 body_format = account->compose_body_format;
1119 } else if (prefs_common.compose_with_format) {
1120 subject_format = prefs_common.compose_subject_format;
1121 body_format = prefs_common.compose_body_format;
1124 if (subject_format || body_format) {
1127 && *subject_format != '\0' )
1129 gchar *subject = NULL;
1134 dummyinfo = compose_msginfo_new_from_compose(compose);
1136 /* decode \-escape sequences in the internal representation of the quote format */
1137 tmp = malloc(strlen(subject_format)+1);
1138 pref_get_unescaped_pref(tmp, subject_format);
1140 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1142 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE,
1143 compose->gtkaspell);
1145 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE);
1147 quote_fmt_scan_string(tmp);
1150 buf = quote_fmt_get_buffer();
1152 alertpanel_error(_("New message subject format error."));
1154 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1155 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1156 quote_fmt_reset_vartable();
1163 && *body_format != '\0' )
1166 GtkTextBuffer *buffer;
1167 GtkTextIter start, end;
1171 dummyinfo = compose_msginfo_new_from_compose(compose);
1173 text = GTK_TEXT_VIEW(compose->text);
1174 buffer = gtk_text_view_get_buffer(text);
1175 gtk_text_buffer_get_start_iter(buffer, &start);
1176 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1177 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1179 compose_quote_fmt(compose, dummyinfo,
1181 NULL, tmp, FALSE, TRUE,
1182 _("New message body format error at line %d."));
1183 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1184 quote_fmt_reset_vartable();
1190 procmsg_msginfo_free( dummyinfo );
1196 for (i = 0; i < attach_files->len; i++) {
1197 file = g_ptr_array_index(attach_files, i);
1198 compose_attach_append(compose, file, file, NULL);
1202 compose_show_first_last_header(compose, TRUE);
1204 /* Set save folder */
1205 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1206 gchar *folderidentifier;
1208 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
1209 folderidentifier = folder_item_get_identifier(item);
1210 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
1211 g_free(folderidentifier);
1214 gtk_widget_grab_focus(compose->header_last->entry);
1216 undo_unblock(compose->undostruct);
1218 if (prefs_common.auto_exteditor)
1219 compose_exec_ext_editor(compose);
1221 compose->draft_timeout_tag = -1;
1222 SCROLL_TO_CURSOR(compose);
1224 compose->modified = FALSE;
1225 compose_set_title(compose);
1229 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1230 gboolean override_pref)
1232 gchar *privacy = NULL;
1234 g_return_if_fail(compose != NULL);
1235 g_return_if_fail(account != NULL);
1237 if (override_pref == FALSE && account->default_encrypt_reply == FALSE)
1240 if (account->default_privacy_system
1241 && strlen(account->default_privacy_system)) {
1242 privacy = account->default_privacy_system;
1244 GSList *privacy_avail = privacy_get_system_ids();
1245 if (privacy_avail && g_slist_length(privacy_avail)) {
1246 privacy = (gchar *)(privacy_avail->data);
1249 if (privacy != NULL) {
1250 if (compose->privacy_system == NULL)
1251 compose->privacy_system = g_strdup(privacy);
1252 else if (*(compose->privacy_system) == '\0') {
1253 g_free(compose->privacy_system);
1254 compose->privacy_system = g_strdup(privacy);
1256 compose_update_privacy_system_menu_item(compose, FALSE);
1257 compose_use_encryption(compose, TRUE);
1261 static void compose_force_signing(Compose *compose, PrefsAccount *account)
1263 gchar *privacy = NULL;
1265 if (account->default_privacy_system
1266 && strlen(account->default_privacy_system)) {
1267 privacy = account->default_privacy_system;
1269 GSList *privacy_avail = privacy_get_system_ids();
1270 if (privacy_avail && g_slist_length(privacy_avail)) {
1271 privacy = (gchar *)(privacy_avail->data);
1274 if (privacy != NULL) {
1275 if (compose->privacy_system == NULL)
1276 compose->privacy_system = g_strdup(privacy);
1277 compose_update_privacy_system_menu_item(compose, FALSE);
1278 compose_use_signing(compose, TRUE);
1282 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1286 Compose *compose = NULL;
1287 GtkItemFactory *ifactory = NULL;
1289 g_return_val_if_fail(msginfo_list != NULL, NULL);
1291 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1292 g_return_val_if_fail(msginfo != NULL, NULL);
1294 list_len = g_slist_length(msginfo_list);
1298 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1299 FALSE, prefs_common.default_reply_list, FALSE, body);
1301 case COMPOSE_REPLY_WITH_QUOTE:
1302 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1303 FALSE, prefs_common.default_reply_list, FALSE, body);
1305 case COMPOSE_REPLY_WITHOUT_QUOTE:
1306 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1307 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1309 case COMPOSE_REPLY_TO_SENDER:
1310 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1311 FALSE, FALSE, TRUE, body);
1313 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1314 compose = compose_followup_and_reply_to(msginfo,
1315 COMPOSE_QUOTE_CHECK,
1316 FALSE, FALSE, body);
1318 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1319 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1320 FALSE, FALSE, TRUE, body);
1322 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1323 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1324 FALSE, FALSE, TRUE, NULL);
1326 case COMPOSE_REPLY_TO_ALL:
1327 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1328 TRUE, FALSE, FALSE, body);
1330 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1331 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1332 TRUE, FALSE, FALSE, body);
1334 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1335 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1336 TRUE, FALSE, FALSE, NULL);
1338 case COMPOSE_REPLY_TO_LIST:
1339 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1340 FALSE, TRUE, FALSE, body);
1342 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1343 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1344 FALSE, TRUE, FALSE, body);
1346 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1347 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1348 FALSE, TRUE, FALSE, NULL);
1350 case COMPOSE_FORWARD:
1351 if (prefs_common.forward_as_attachment) {
1352 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1355 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1359 case COMPOSE_FORWARD_INLINE:
1360 /* check if we reply to more than one Message */
1361 if (list_len == 1) {
1362 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1365 /* more messages FALL THROUGH */
1366 case COMPOSE_FORWARD_AS_ATTACH:
1367 compose = compose_forward_multiple(NULL, msginfo_list);
1369 case COMPOSE_REDIRECT:
1370 compose = compose_redirect(NULL, msginfo, FALSE);
1373 g_warning("compose_reply_mode(): invalid Compose Mode: %d\n", mode);
1376 if (compose == NULL) {
1377 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1380 ifactory = gtk_item_factory_from_widget(compose->menubar);
1382 compose->rmode = mode;
1383 switch (compose->rmode) {
1385 case COMPOSE_REPLY_WITH_QUOTE:
1386 case COMPOSE_REPLY_WITHOUT_QUOTE:
1387 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1388 debug_print("reply mode Normal\n");
1389 menu_set_active(ifactory, "/Options/Reply mode/Normal", TRUE);
1390 compose_reply_change_mode(compose, COMPOSE_REPLY, NULL); /* force update */
1392 case COMPOSE_REPLY_TO_SENDER:
1393 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1394 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1395 debug_print("reply mode Sender\n");
1396 menu_set_active(ifactory, "/Options/Reply mode/Sender", TRUE);
1398 case COMPOSE_REPLY_TO_ALL:
1399 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1400 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1401 debug_print("reply mode All\n");
1402 menu_set_active(ifactory, "/Options/Reply mode/All", TRUE);
1404 case COMPOSE_REPLY_TO_LIST:
1405 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1406 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1407 debug_print("reply mode List\n");
1408 menu_set_active(ifactory, "/Options/Reply mode/Mailing-list", TRUE);
1416 static Compose *compose_reply(MsgInfo *msginfo,
1417 ComposeQuoteMode quote_mode,
1423 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1424 to_sender, FALSE, body);
1427 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1428 ComposeQuoteMode quote_mode,
1433 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1434 to_sender, TRUE, body);
1437 static void compose_extract_original_charset(Compose *compose)
1439 MsgInfo *info = NULL;
1440 if (compose->replyinfo) {
1441 info = compose->replyinfo;
1442 } else if (compose->fwdinfo) {
1443 info = compose->fwdinfo;
1444 } else if (compose->targetinfo) {
1445 info = compose->targetinfo;
1448 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1449 MimeInfo *partinfo = mimeinfo;
1450 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1451 partinfo = procmime_mimeinfo_next(partinfo);
1453 compose->orig_charset =
1454 g_strdup(procmime_mimeinfo_get_parameter(
1455 partinfo, "charset"));
1457 procmime_mimeinfo_free_all(mimeinfo);
1461 #define SIGNAL_BLOCK(buffer) { \
1462 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1463 G_CALLBACK(compose_changed_cb), \
1465 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1466 G_CALLBACK(text_inserted), \
1470 #define SIGNAL_UNBLOCK(buffer) { \
1471 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1472 G_CALLBACK(compose_changed_cb), \
1474 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1475 G_CALLBACK(text_inserted), \
1479 static Compose *compose_generic_reply(MsgInfo *msginfo,
1480 ComposeQuoteMode quote_mode,
1481 gboolean to_all, gboolean to_ml,
1483 gboolean followup_and_reply_to,
1486 GtkItemFactory *ifactory;
1488 PrefsAccount *account = NULL;
1489 GtkTextView *textview;
1490 GtkTextBuffer *textbuf;
1491 gboolean quote = FALSE;
1492 const gchar *qmark = NULL;
1493 const gchar *body_fmt = NULL;
1495 g_return_val_if_fail(msginfo != NULL, NULL);
1496 g_return_val_if_fail(msginfo->folder != NULL, NULL);
1498 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1500 g_return_val_if_fail(account != NULL, NULL);
1502 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1504 compose->updating = TRUE;
1506 ifactory = gtk_item_factory_from_widget(compose->menubar);
1508 menu_set_active(ifactory, "/Options/Remove references", FALSE);
1509 menu_set_sensitive(ifactory, "/Options/Remove references", TRUE);
1511 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1512 if (!compose->replyinfo)
1513 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1515 compose_extract_original_charset(compose);
1517 if (msginfo->folder && msginfo->folder->ret_rcpt)
1518 menu_set_active(ifactory, "/Options/Request Return Receipt", TRUE);
1520 /* Set save folder */
1521 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1522 gchar *folderidentifier;
1524 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1525 folderidentifier = folder_item_get_identifier(msginfo->folder);
1526 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
1527 g_free(folderidentifier);
1530 if (compose_parse_header(compose, msginfo) < 0) {
1531 compose->updating = FALSE;
1532 compose_destroy(compose);
1536 /* override from name according to folder properties */
1537 if (msginfo->folder && msginfo->folder->prefs &&
1538 msginfo->folder->prefs->reply_with_format &&
1539 msginfo->folder->prefs->reply_override_from_format &&
1540 *msginfo->folder->prefs->reply_override_from_format != '\0') {
1545 /* decode \-escape sequences in the internal representation of the quote format */
1546 tmp = malloc(strlen(msginfo->folder->prefs->reply_override_from_format)+1);
1547 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->reply_override_from_format);
1550 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1551 compose->gtkaspell);
1553 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1555 quote_fmt_scan_string(tmp);
1558 buf = quote_fmt_get_buffer();
1560 alertpanel_error(_("Message reply From format error."));
1562 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1563 quote_fmt_reset_vartable();
1568 textview = (GTK_TEXT_VIEW(compose->text));
1569 textbuf = gtk_text_view_get_buffer(textview);
1570 compose_create_tags(textview, compose);
1572 undo_block(compose->undostruct);
1574 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1577 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1578 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1579 /* use the reply format of folder (if enabled), or the account's one
1580 (if enabled) or fallback to the global reply format, which is always
1581 enabled (even if empty), and use the relevant quotemark */
1583 if (msginfo->folder && msginfo->folder->prefs &&
1584 msginfo->folder->prefs->reply_with_format) {
1585 qmark = msginfo->folder->prefs->reply_quotemark;
1586 body_fmt = msginfo->folder->prefs->reply_body_format;
1588 } else if (account->reply_with_format) {
1589 qmark = account->reply_quotemark;
1590 body_fmt = account->reply_body_format;
1593 qmark = prefs_common.quotemark;
1594 body_fmt = gettext(prefs_common.quotefmt);
1599 /* empty quotemark is not allowed */
1600 if (qmark == NULL || *qmark == '\0')
1602 compose_quote_fmt(compose, compose->replyinfo,
1603 body_fmt, qmark, body, FALSE, TRUE,
1604 _("Message reply format error at line %d."));
1605 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1606 quote_fmt_reset_vartable();
1609 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1610 compose_force_encryption(compose, account, FALSE);
1613 SIGNAL_BLOCK(textbuf);
1615 if (account->auto_sig)
1616 compose_insert_sig(compose, FALSE);
1618 compose_wrap_all(compose);
1620 SIGNAL_UNBLOCK(textbuf);
1622 gtk_widget_grab_focus(compose->text);
1624 undo_unblock(compose->undostruct);
1626 if (prefs_common.auto_exteditor)
1627 compose_exec_ext_editor(compose);
1629 compose->modified = FALSE;
1630 compose_set_title(compose);
1632 compose->updating = FALSE;
1633 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1634 SCROLL_TO_CURSOR(compose);
1636 if (compose->deferred_destroy) {
1637 compose_destroy(compose);
1644 #define INSERT_FW_HEADER(var, hdr) \
1645 if (msginfo->var && *msginfo->var) { \
1646 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1647 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1648 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1651 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1652 gboolean as_attach, const gchar *body,
1653 gboolean no_extedit,
1657 GtkTextView *textview;
1658 GtkTextBuffer *textbuf;
1661 g_return_val_if_fail(msginfo != NULL, NULL);
1662 g_return_val_if_fail(msginfo->folder != NULL, NULL);
1665 !(account = compose_guess_forward_account_from_msginfo
1667 account = cur_account;
1669 compose = compose_create(account, msginfo->folder, COMPOSE_FORWARD, batch);
1671 compose->updating = TRUE;
1672 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1673 if (!compose->fwdinfo)
1674 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1676 compose_extract_original_charset(compose);
1678 if (msginfo->subject && *msginfo->subject) {
1679 gchar *buf, *buf2, *p;
1681 buf = p = g_strdup(msginfo->subject);
1682 p += subject_get_prefix_length(p);
1683 memmove(buf, p, strlen(p) + 1);
1685 buf2 = g_strdup_printf("Fw: %s", buf);
1686 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1692 /* override from name according to folder properties */
1693 if (msginfo->folder && msginfo->folder->prefs &&
1694 msginfo->folder->prefs->forward_with_format &&
1695 msginfo->folder->prefs->forward_override_from_format &&
1696 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1700 MsgInfo *full_msginfo = NULL;
1703 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1705 full_msginfo = procmsg_msginfo_copy(msginfo);
1707 /* decode \-escape sequences in the internal representation of the quote format */
1708 tmp = malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1709 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1712 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1713 compose->gtkaspell);
1715 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1717 quote_fmt_scan_string(tmp);
1720 buf = quote_fmt_get_buffer();
1722 alertpanel_error(_("Message forward From format error."));
1724 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1725 quote_fmt_reset_vartable();
1728 procmsg_msginfo_free(full_msginfo);
1731 textview = GTK_TEXT_VIEW(compose->text);
1732 textbuf = gtk_text_view_get_buffer(textview);
1733 compose_create_tags(textview, compose);
1735 undo_block(compose->undostruct);
1739 msgfile = procmsg_get_message_file(msginfo);
1740 if (!is_file_exist(msgfile))
1741 g_warning("%s: file not exist\n", msgfile);
1743 compose_attach_append(compose, msgfile, msgfile,
1748 const gchar *qmark = NULL;
1749 const gchar *body_fmt = gettext(prefs_common.fw_quotefmt);
1750 MsgInfo *full_msginfo;
1752 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1754 full_msginfo = procmsg_msginfo_copy(msginfo);
1756 /* use the forward format of folder (if enabled), or the account's one
1757 (if enabled) or fallback to the global forward format, which is always
1758 enabled (even if empty), and use the relevant quotemark */
1759 if (msginfo->folder && msginfo->folder->prefs &&
1760 msginfo->folder->prefs->forward_with_format) {
1761 qmark = msginfo->folder->prefs->forward_quotemark;
1762 body_fmt = msginfo->folder->prefs->forward_body_format;
1764 } else if (account->forward_with_format) {
1765 qmark = account->forward_quotemark;
1766 body_fmt = account->forward_body_format;
1769 qmark = prefs_common.fw_quotemark;
1770 body_fmt = gettext(prefs_common.fw_quotefmt);
1773 /* empty quotemark is not allowed */
1774 if (qmark == NULL || *qmark == '\0')
1777 compose_quote_fmt(compose, full_msginfo,
1778 body_fmt, qmark, body, FALSE, TRUE,
1779 _("Message forward format error at line %d."));
1780 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1781 quote_fmt_reset_vartable();
1782 compose_attach_parts(compose, msginfo);
1784 procmsg_msginfo_free(full_msginfo);
1787 SIGNAL_BLOCK(textbuf);
1789 if (account->auto_sig)
1790 compose_insert_sig(compose, FALSE);
1792 compose_wrap_all(compose);
1794 SIGNAL_UNBLOCK(textbuf);
1796 gtk_text_buffer_get_start_iter(textbuf, &iter);
1797 gtk_text_buffer_place_cursor(textbuf, &iter);
1799 gtk_widget_grab_focus(compose->header_last->entry);
1801 if (!no_extedit && prefs_common.auto_exteditor)
1802 compose_exec_ext_editor(compose);
1805 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1806 gchar *folderidentifier;
1808 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1809 folderidentifier = folder_item_get_identifier(msginfo->folder);
1810 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
1811 g_free(folderidentifier);
1814 undo_unblock(compose->undostruct);
1816 compose->modified = FALSE;
1817 compose_set_title(compose);
1819 compose->updating = FALSE;
1820 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1821 SCROLL_TO_CURSOR(compose);
1823 if (compose->deferred_destroy) {
1824 compose_destroy(compose);
1831 #undef INSERT_FW_HEADER
1833 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1836 GtkTextView *textview;
1837 GtkTextBuffer *textbuf;
1841 gboolean single_mail = TRUE;
1843 g_return_val_if_fail(msginfo_list != NULL, NULL);
1845 if (g_slist_length(msginfo_list) > 1)
1846 single_mail = FALSE;
1848 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1849 if (((MsgInfo *)msginfo->data)->folder == NULL)
1852 /* guess account from first selected message */
1854 !(account = compose_guess_forward_account_from_msginfo
1855 (msginfo_list->data)))
1856 account = cur_account;
1858 g_return_val_if_fail(account != NULL, NULL);
1860 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1861 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1862 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1865 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1867 compose->updating = TRUE;
1869 /* override from name according to folder properties */
1870 if (msginfo_list->data) {
1871 MsgInfo *msginfo = msginfo_list->data;
1873 if (msginfo->folder && msginfo->folder->prefs &&
1874 msginfo->folder->prefs->forward_with_format &&
1875 msginfo->folder->prefs->forward_override_from_format &&
1876 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1881 /* decode \-escape sequences in the internal representation of the quote format */
1882 tmp = malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1883 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1886 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1887 compose->gtkaspell);
1889 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1891 quote_fmt_scan_string(tmp);
1894 buf = quote_fmt_get_buffer();
1896 alertpanel_error(_("Message forward From format error."));
1898 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1899 quote_fmt_reset_vartable();
1905 textview = GTK_TEXT_VIEW(compose->text);
1906 textbuf = gtk_text_view_get_buffer(textview);
1907 compose_create_tags(textview, compose);
1909 undo_block(compose->undostruct);
1910 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1911 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
1913 if (!is_file_exist(msgfile))
1914 g_warning("%s: file not exist\n", msgfile);
1916 compose_attach_append(compose, msgfile, msgfile,
1922 MsgInfo *info = (MsgInfo *)msginfo_list->data;
1923 if (info->subject && *info->subject) {
1924 gchar *buf, *buf2, *p;
1926 buf = p = g_strdup(info->subject);
1927 p += subject_get_prefix_length(p);
1928 memmove(buf, p, strlen(p) + 1);
1930 buf2 = g_strdup_printf("Fw: %s", buf);
1931 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1937 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
1938 _("Fw: multiple emails"));
1941 SIGNAL_BLOCK(textbuf);
1943 if (account->auto_sig)
1944 compose_insert_sig(compose, FALSE);
1946 compose_wrap_all(compose);
1948 SIGNAL_UNBLOCK(textbuf);
1950 gtk_text_buffer_get_start_iter(textbuf, &iter);
1951 gtk_text_buffer_place_cursor(textbuf, &iter);
1953 gtk_widget_grab_focus(compose->header_last->entry);
1954 undo_unblock(compose->undostruct);
1955 compose->modified = FALSE;
1956 compose_set_title(compose);
1958 compose->updating = FALSE;
1959 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1960 SCROLL_TO_CURSOR(compose);
1962 if (compose->deferred_destroy) {
1963 compose_destroy(compose);
1970 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
1972 GtkTextIter start = *iter;
1973 GtkTextIter end_iter;
1974 int start_pos = gtk_text_iter_get_offset(&start);
1976 if (!compose->account->sig_sep)
1979 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
1980 start_pos+strlen(compose->account->sig_sep));
1982 /* check sig separator */
1983 str = gtk_text_iter_get_text(&start, &end_iter);
1984 if (!strcmp(str, compose->account->sig_sep)) {
1986 /* check end of line (\n) */
1987 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
1988 start_pos+strlen(compose->account->sig_sep));
1989 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
1990 start_pos+strlen(compose->account->sig_sep)+1);
1991 tmp = gtk_text_iter_get_text(&start, &end_iter);
1992 if (!strcmp(tmp,"\n")) {
2004 static void compose_colorize_signature(Compose *compose)
2006 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
2008 GtkTextIter end_iter;
2009 gtk_text_buffer_get_start_iter(buffer, &iter);
2010 while (gtk_text_iter_forward_line(&iter))
2011 if (compose_is_sig_separator(compose, buffer, &iter)) {
2012 gtk_text_buffer_get_end_iter(buffer, &end_iter);
2013 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
2017 #define BLOCK_WRAP() { \
2018 prev_autowrap = compose->autowrap; \
2019 buffer = gtk_text_view_get_buffer( \
2020 GTK_TEXT_VIEW(compose->text)); \
2021 compose->autowrap = FALSE; \
2023 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2024 G_CALLBACK(compose_changed_cb), \
2026 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2027 G_CALLBACK(text_inserted), \
2030 #define UNBLOCK_WRAP() { \
2031 compose->autowrap = prev_autowrap; \
2032 if (compose->autowrap) { \
2033 gint old = compose->draft_timeout_tag; \
2034 compose->draft_timeout_tag = -2; \
2035 compose_wrap_all(compose); \
2036 compose->draft_timeout_tag = old; \
2039 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2040 G_CALLBACK(compose_changed_cb), \
2042 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2043 G_CALLBACK(text_inserted), \
2047 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
2049 Compose *compose = NULL;
2050 PrefsAccount *account = NULL;
2051 GtkTextView *textview;
2052 GtkTextBuffer *textbuf;
2056 gchar buf[BUFFSIZE];
2057 gboolean use_signing = FALSE;
2058 gboolean use_encryption = FALSE;
2059 gchar *privacy_system = NULL;
2060 int priority = PRIORITY_NORMAL;
2061 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
2063 g_return_val_if_fail(msginfo != NULL, NULL);
2064 g_return_val_if_fail(msginfo->folder != NULL, NULL);
2066 if (compose_put_existing_to_front(msginfo)) {
2070 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2071 folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
2072 gchar queueheader_buf[BUFFSIZE];
2075 /* Select Account from queue headers */
2076 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2077 sizeof(queueheader_buf), "X-Claws-Account-Id:")) {
2078 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
2079 account = account_find_from_id(id);
2081 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2082 sizeof(queueheader_buf), "X-Sylpheed-Account-Id:")) {
2083 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
2084 account = account_find_from_id(id);
2086 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2087 sizeof(queueheader_buf), "NAID:")) {
2088 id = atoi(&queueheader_buf[strlen("NAID:")]);
2089 account = account_find_from_id(id);
2091 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2092 sizeof(queueheader_buf), "MAID:")) {
2093 id = atoi(&queueheader_buf[strlen("MAID:")]);
2094 account = account_find_from_id(id);
2096 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2097 sizeof(queueheader_buf), "S:")) {
2098 account = account_find_from_address(queueheader_buf, FALSE);
2100 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2101 sizeof(queueheader_buf), "X-Claws-Sign:")) {
2102 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
2103 use_signing = param;
2106 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2107 sizeof(queueheader_buf), "X-Sylpheed-Sign:")) {
2108 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
2109 use_signing = param;
2112 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2113 sizeof(queueheader_buf), "X-Claws-Encrypt:")) {
2114 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
2115 use_encryption = param;
2117 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2118 sizeof(queueheader_buf), "X-Sylpheed-Encrypt:")) {
2119 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
2120 use_encryption = param;
2122 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2123 sizeof(queueheader_buf), "X-Claws-Privacy-System:")) {
2124 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
2126 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2127 sizeof(queueheader_buf), "X-Sylpheed-Privacy-System:")) {
2128 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
2130 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2131 sizeof(queueheader_buf), "X-Priority: ")) {
2132 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
2135 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2136 sizeof(queueheader_buf), "RMID:")) {
2137 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
2138 if (tokens[0] && tokens[1] && tokens[2]) {
2139 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2140 if (orig_item != NULL) {
2141 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2146 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2147 sizeof(queueheader_buf), "FMID:")) {
2148 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
2149 if (tokens[0] && tokens[1] && tokens[2]) {
2150 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2151 if (orig_item != NULL) {
2152 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2158 account = msginfo->folder->folder->account;
2161 if (!account && prefs_common.reedit_account_autosel) {
2162 gchar from[BUFFSIZE];
2163 if (!procheader_get_header_from_msginfo(msginfo, from, sizeof(from), "FROM:")) {
2164 extract_address(from);
2165 account = account_find_from_address(from, FALSE);
2169 account = cur_account;
2171 g_return_val_if_fail(account != NULL, NULL);
2173 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2175 compose->replyinfo = replyinfo;
2176 compose->fwdinfo = fwdinfo;
2178 compose->updating = TRUE;
2179 compose->priority = priority;
2181 if (privacy_system != NULL) {
2182 compose->privacy_system = privacy_system;
2183 compose_use_signing(compose, use_signing);
2184 compose_use_encryption(compose, use_encryption);
2185 compose_update_privacy_system_menu_item(compose, FALSE);
2187 activate_privacy_system(compose, account, FALSE);
2190 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2192 compose_extract_original_charset(compose);
2194 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2195 folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
2196 gchar queueheader_buf[BUFFSIZE];
2198 /* Set message save folder */
2199 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "SCF:")) {
2202 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2203 gtk_editable_delete_text(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
2204 gtk_editable_insert_text(GTK_EDITABLE(compose->savemsg_entry), &queueheader_buf[4], strlen(&queueheader_buf[4]), &startpos);
2206 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "RRCPT:")) {
2207 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2209 GtkItemFactory *ifactory;
2210 ifactory = gtk_item_factory_from_widget(compose->menubar);
2211 menu_set_active(ifactory, "/Options/Request Return Receipt", TRUE);
2216 if (compose_parse_header(compose, msginfo) < 0) {
2217 compose->updating = FALSE;
2218 compose_destroy(compose);
2221 compose_reedit_set_entry(compose, msginfo);
2223 textview = GTK_TEXT_VIEW(compose->text);
2224 textbuf = gtk_text_view_get_buffer(textview);
2225 compose_create_tags(textview, compose);
2227 mark = gtk_text_buffer_get_insert(textbuf);
2228 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2230 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2231 G_CALLBACK(compose_changed_cb),
2234 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2235 fp = procmime_get_first_encrypted_text_content(msginfo);
2237 compose_force_encryption(compose, account, TRUE);
2240 fp = procmime_get_first_text_content(msginfo);
2243 g_warning("Can't get text part\n");
2247 gboolean prev_autowrap = compose->autowrap;
2248 GtkTextBuffer *buffer = textbuf;
2250 while (fgets(buf, sizeof(buf), fp) != NULL) {
2252 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2258 compose_attach_parts(compose, msginfo);
2260 compose_colorize_signature(compose);
2262 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2263 G_CALLBACK(compose_changed_cb),
2266 gtk_widget_grab_focus(compose->text);
2268 if (prefs_common.auto_exteditor) {
2269 compose_exec_ext_editor(compose);
2271 compose->modified = FALSE;
2272 compose_set_title(compose);
2274 compose->updating = FALSE;
2275 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2276 SCROLL_TO_CURSOR(compose);
2278 if (compose->deferred_destroy) {
2279 compose_destroy(compose);
2283 compose->sig_str = compose_get_signature_str(compose);
2288 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2293 GtkItemFactory *ifactory;
2296 g_return_val_if_fail(msginfo != NULL, NULL);
2299 account = account_get_reply_account(msginfo,
2300 prefs_common.reply_account_autosel);
2301 g_return_val_if_fail(account != NULL, NULL);
2303 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2305 compose->updating = TRUE;
2307 ifactory = gtk_item_factory_from_widget(compose->menubar);
2308 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2309 compose->replyinfo = NULL;
2310 compose->fwdinfo = NULL;
2312 compose_show_first_last_header(compose, TRUE);
2314 gtk_widget_grab_focus(compose->header_last->entry);
2316 filename = procmsg_get_message_file(msginfo);
2318 if (filename == NULL) {
2319 compose->updating = FALSE;
2320 compose_destroy(compose);
2325 compose->redirect_filename = filename;
2327 /* Set save folder */
2328 item = msginfo->folder;
2329 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2330 gchar *folderidentifier;
2332 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
2333 folderidentifier = folder_item_get_identifier(item);
2334 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
2335 g_free(folderidentifier);
2338 compose_attach_parts(compose, msginfo);
2340 if (msginfo->subject)
2341 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2343 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2345 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2346 _("Message redirect format error at line %d."));
2347 quote_fmt_reset_vartable();
2348 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2350 compose_colorize_signature(compose);
2352 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
2353 menu_set_sensitive(ifactory, "/Add...", FALSE);
2354 menu_set_sensitive(ifactory, "/Remove", FALSE);
2355 menu_set_sensitive(ifactory, "/Properties...", FALSE);
2357 ifactory = gtk_item_factory_from_widget(compose->menubar);
2358 menu_set_sensitive(ifactory, "/Message/Save", FALSE);
2359 menu_set_sensitive(ifactory, "/Message/Insert file", FALSE);
2360 menu_set_sensitive(ifactory, "/Message/Attach file", FALSE);
2361 menu_set_sensitive(ifactory, "/Message/Insert signature", FALSE);
2362 menu_set_sensitive(ifactory, "/Edit", FALSE);
2363 menu_set_sensitive(ifactory, "/Options", FALSE);
2364 menu_set_sensitive(ifactory, "/Tools/Show ruler", FALSE);
2365 menu_set_sensitive(ifactory, "/Tools/Actions", FALSE);
2367 if (compose->toolbar->draft_btn)
2368 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2369 if (compose->toolbar->insert_btn)
2370 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2371 if (compose->toolbar->attach_btn)
2372 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2373 if (compose->toolbar->sig_btn)
2374 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2375 if (compose->toolbar->exteditor_btn)
2376 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2377 if (compose->toolbar->linewrap_current_btn)
2378 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2379 if (compose->toolbar->linewrap_all_btn)
2380 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2382 compose->modified = FALSE;
2383 compose_set_title(compose);
2384 compose->updating = FALSE;
2385 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2386 SCROLL_TO_CURSOR(compose);
2388 if (compose->deferred_destroy) {
2389 compose_destroy(compose);
2396 GList *compose_get_compose_list(void)
2398 return compose_list;
2401 void compose_entry_append(Compose *compose, const gchar *address,
2402 ComposeEntryType type)
2404 const gchar *header;
2406 gboolean in_quote = FALSE;
2407 if (!address || *address == '\0') return;
2414 header = N_("Bcc:");
2416 case COMPOSE_REPLYTO:
2417 header = N_("Reply-To:");
2419 case COMPOSE_NEWSGROUPS:
2420 header = N_("Newsgroups:");
2422 case COMPOSE_FOLLOWUPTO:
2423 header = N_( "Followup-To:");
2430 header = prefs_common_translated_header_name(header);
2432 cur = begin = (gchar *)address;
2434 /* we separate the line by commas, but not if we're inside a quoted
2436 while (*cur != '\0') {
2438 in_quote = !in_quote;
2439 if (*cur == ',' && !in_quote) {
2440 gchar *tmp = g_strdup(begin);
2442 tmp[cur-begin]='\0';
2445 while (*tmp == ' ' || *tmp == '\t')
2447 compose_add_header_entry(compose, header, tmp);
2454 gchar *tmp = g_strdup(begin);
2456 tmp[cur-begin]='\0';
2459 while (*tmp == ' ' || *tmp == '\t')
2461 compose_add_header_entry(compose, header, tmp);
2466 static void compose_entry_mark_default_to(Compose *compose, const gchar *mailto)
2468 static GdkColor yellow;
2469 static GdkColor black;
2470 static gboolean yellow_initialised = FALSE;
2474 if (!yellow_initialised) {
2475 gdk_color_parse("#f5f6be", &yellow);
2476 gdk_color_parse("#000000", &black);
2477 yellow_initialised = gdk_colormap_alloc_color(
2478 gdk_colormap_get_system(), &yellow, FALSE, TRUE);
2479 yellow_initialised &= gdk_colormap_alloc_color(
2480 gdk_colormap_get_system(), &black, FALSE, TRUE);
2483 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2484 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2485 if (gtk_entry_get_text(entry) &&
2486 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2487 if (yellow_initialised) {
2488 gtk_widget_modify_base(
2489 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2490 GTK_STATE_NORMAL, &yellow);
2491 gtk_widget_modify_text(
2492 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2493 GTK_STATE_NORMAL, &black);
2499 void compose_toolbar_cb(gint action, gpointer data)
2501 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2502 Compose *compose = (Compose*)toolbar_item->parent;
2504 g_return_if_fail(compose != NULL);
2508 compose_send_cb(compose, 0, NULL);
2511 compose_send_later_cb(compose, 0, NULL);
2514 compose_draft_cb(compose, COMPOSE_QUIT_EDITING, NULL);
2517 compose_insert_file_cb(compose, 0, NULL);
2520 compose_attach_cb(compose, 0, NULL);
2523 compose_insert_sig(compose, FALSE);
2526 compose_ext_editor_cb(compose, 0, NULL);
2528 case A_LINEWRAP_CURRENT:
2529 compose_beautify_paragraph(compose, NULL, TRUE);
2531 case A_LINEWRAP_ALL:
2532 compose_wrap_all_full(compose, TRUE);
2535 compose_address_cb(compose, 0, NULL);
2538 case A_CHECK_SPELLING:
2539 compose_check_all(compose);
2547 static void compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
2552 gchar *subject = NULL;
2556 gchar **attach = NULL;
2558 /* get mailto parts but skip from */
2559 scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach);
2562 compose_entry_append(compose, to, to_type);
2564 compose_entry_append(compose, cc, COMPOSE_CC);
2566 compose_entry_append(compose, bcc, COMPOSE_BCC);
2568 if (!g_utf8_validate (subject, -1, NULL)) {
2569 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2570 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2573 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2577 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2578 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2581 gboolean prev_autowrap = compose->autowrap;
2583 compose->autowrap = FALSE;
2585 mark = gtk_text_buffer_get_insert(buffer);
2586 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2588 if (!g_utf8_validate (body, -1, NULL)) {
2589 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2590 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2593 gtk_text_buffer_insert(buffer, &iter, body, -1);
2595 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2597 compose->autowrap = prev_autowrap;
2598 if (compose->autowrap)
2599 compose_wrap_all(compose);
2603 gint i = 0, att = 0;
2604 gchar *warn_files = NULL;
2605 while (attach[i] != NULL) {
2606 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2607 if (utf8_filename) {
2608 if (compose_attach_append(compose, attach[i], utf8_filename, NULL)) {
2609 gchar *tmp = g_strdup_printf("%s%s\n",
2610 warn_files?warn_files:"",
2616 g_free(utf8_filename);
2618 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2623 alertpanel_notice(ngettext(
2624 "The following file has been attached: \n%s",
2625 "The following files have been attached: \n%s", att), warn_files);
2637 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2639 static HeaderEntry hentry[] = {{"Reply-To:", NULL, TRUE},
2640 {"Cc:", NULL, TRUE},
2641 {"References:", NULL, FALSE},
2642 {"Bcc:", NULL, TRUE},
2643 {"Newsgroups:", NULL, TRUE},
2644 {"Followup-To:", NULL, TRUE},
2645 {"List-Post:", NULL, FALSE},
2646 {"X-Priority:", NULL, FALSE},
2647 {NULL, NULL, FALSE}};
2663 g_return_val_if_fail(msginfo != NULL, -1);
2665 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2666 procheader_get_header_fields(fp, hentry);
2669 if (hentry[H_REPLY_TO].body != NULL) {
2670 if (hentry[H_REPLY_TO].body[0] != '\0') {
2672 conv_unmime_header(hentry[H_REPLY_TO].body,
2675 g_free(hentry[H_REPLY_TO].body);
2676 hentry[H_REPLY_TO].body = NULL;
2678 if (hentry[H_CC].body != NULL) {
2679 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL);
2680 g_free(hentry[H_CC].body);
2681 hentry[H_CC].body = NULL;
2683 if (hentry[H_REFERENCES].body != NULL) {
2684 if (compose->mode == COMPOSE_REEDIT)
2685 compose->references = hentry[H_REFERENCES].body;
2687 compose->references = compose_parse_references
2688 (hentry[H_REFERENCES].body, msginfo->msgid);
2689 g_free(hentry[H_REFERENCES].body);
2691 hentry[H_REFERENCES].body = NULL;
2693 if (hentry[H_BCC].body != NULL) {
2694 if (compose->mode == COMPOSE_REEDIT)
2696 conv_unmime_header(hentry[H_BCC].body, NULL);
2697 g_free(hentry[H_BCC].body);
2698 hentry[H_BCC].body = NULL;
2700 if (hentry[H_NEWSGROUPS].body != NULL) {
2701 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2702 hentry[H_NEWSGROUPS].body = NULL;
2704 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2705 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2706 compose->followup_to =
2707 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2710 g_free(hentry[H_FOLLOWUP_TO].body);
2711 hentry[H_FOLLOWUP_TO].body = NULL;
2713 if (hentry[H_LIST_POST].body != NULL) {
2716 extract_address(hentry[H_LIST_POST].body);
2717 if (hentry[H_LIST_POST].body[0] != '\0') {
2718 scan_mailto_url(hentry[H_LIST_POST].body,
2719 NULL, &to, NULL, NULL, NULL, NULL, NULL);
2721 g_free(compose->ml_post);
2722 compose->ml_post = to;
2725 g_free(hentry[H_LIST_POST].body);
2726 hentry[H_LIST_POST].body = NULL;
2729 /* CLAWS - X-Priority */
2730 if (compose->mode == COMPOSE_REEDIT)
2731 if (hentry[H_X_PRIORITY].body != NULL) {
2734 priority = atoi(hentry[H_X_PRIORITY].body);
2735 g_free(hentry[H_X_PRIORITY].body);
2737 hentry[H_X_PRIORITY].body = NULL;
2739 if (priority < PRIORITY_HIGHEST ||
2740 priority > PRIORITY_LOWEST)
2741 priority = PRIORITY_NORMAL;
2743 compose->priority = priority;
2746 if (compose->mode == COMPOSE_REEDIT) {
2747 if (msginfo->inreplyto && *msginfo->inreplyto)
2748 compose->inreplyto = g_strdup(msginfo->inreplyto);
2752 if (msginfo->msgid && *msginfo->msgid)
2753 compose->inreplyto = g_strdup(msginfo->msgid);
2755 if (!compose->references) {
2756 if (msginfo->msgid && *msginfo->msgid) {
2757 if (msginfo->inreplyto && *msginfo->inreplyto)
2758 compose->references =
2759 g_strdup_printf("<%s>\n\t<%s>",
2763 compose->references =
2764 g_strconcat("<", msginfo->msgid, ">",
2766 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
2767 compose->references =
2768 g_strconcat("<", msginfo->inreplyto, ">",
2776 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
2778 GSList *ref_id_list, *cur;
2782 ref_id_list = references_list_append(NULL, ref);
2783 if (!ref_id_list) return NULL;
2784 if (msgid && *msgid)
2785 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
2790 for (cur = ref_id_list; cur != NULL; cur = cur->next)
2791 /* "<" + Message-ID + ">" + CR+LF+TAB */
2792 len += strlen((gchar *)cur->data) + 5;
2794 if (len > MAX_REFERENCES_LEN) {
2795 /* remove second message-ID */
2796 if (ref_id_list && ref_id_list->next &&
2797 ref_id_list->next->next) {
2798 g_free(ref_id_list->next->data);
2799 ref_id_list = g_slist_remove
2800 (ref_id_list, ref_id_list->next->data);
2802 slist_free_strings(ref_id_list);
2803 g_slist_free(ref_id_list);
2810 new_ref = g_string_new("");
2811 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
2812 if (new_ref->len > 0)
2813 g_string_append(new_ref, "\n\t");
2814 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
2817 slist_free_strings(ref_id_list);
2818 g_slist_free(ref_id_list);
2820 new_ref_str = new_ref->str;
2821 g_string_free(new_ref, FALSE);
2826 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
2827 const gchar *fmt, const gchar *qmark,
2828 const gchar *body, gboolean rewrap,
2829 gboolean need_unescape,
2830 const gchar *err_msg)
2832 MsgInfo* dummyinfo = NULL;
2833 gchar *quote_str = NULL;
2835 gboolean prev_autowrap;
2836 const gchar *trimmed_body = body;
2837 gint cursor_pos = -1;
2838 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2839 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2844 SIGNAL_BLOCK(buffer);
2847 dummyinfo = compose_msginfo_new_from_compose(compose);
2848 msginfo = dummyinfo;
2851 if (qmark != NULL) {
2853 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
2854 compose->gtkaspell);
2856 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
2858 quote_fmt_scan_string(qmark);
2861 buf = quote_fmt_get_buffer();
2863 alertpanel_error(_("Quote mark format error."));
2865 Xstrdup_a(quote_str, buf, goto error)
2868 if (fmt && *fmt != '\0') {
2871 while (*trimmed_body == '\n')
2875 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
2876 compose->gtkaspell);
2878 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
2880 if (need_unescape) {
2883 /* decode \-escape sequences in the internal representation of the quote format */
2884 tmp = malloc(strlen(fmt)+1);
2885 pref_get_unescaped_pref(tmp, fmt);
2886 quote_fmt_scan_string(tmp);
2890 quote_fmt_scan_string(fmt);
2894 buf = quote_fmt_get_buffer();
2896 gint line = quote_fmt_get_line();
2897 alertpanel_error(err_msg, line);
2903 prev_autowrap = compose->autowrap;
2904 compose->autowrap = FALSE;
2906 mark = gtk_text_buffer_get_insert(buffer);
2907 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2908 if (g_utf8_validate(buf, -1, NULL)) {
2909 gtk_text_buffer_insert(buffer, &iter, buf, -1);
2911 gchar *tmpout = NULL;
2912 tmpout = conv_codeset_strdup
2913 (buf, conv_get_locale_charset_str_no_utf8(),
2915 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
2917 tmpout = g_malloc(strlen(buf)*2+1);
2918 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
2920 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
2924 cursor_pos = quote_fmt_get_cursor_pos();
2925 compose->set_cursor_pos = cursor_pos;
2926 if (cursor_pos == -1) {
2929 gtk_text_buffer_get_start_iter(buffer, &iter);
2930 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
2931 gtk_text_buffer_place_cursor(buffer, &iter);
2933 compose->autowrap = prev_autowrap;
2934 if (compose->autowrap && rewrap)
2935 compose_wrap_all(compose);
2942 SIGNAL_UNBLOCK(buffer);
2944 procmsg_msginfo_free( dummyinfo );
2949 /* if ml_post is of type addr@host and from is of type
2950 * addr-anything@host, return TRUE
2952 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
2954 gchar *left_ml = NULL;
2955 gchar *right_ml = NULL;
2956 gchar *left_from = NULL;
2957 gchar *right_from = NULL;
2958 gboolean result = FALSE;
2960 if (!ml_post || !from)
2963 left_ml = g_strdup(ml_post);
2964 if (strstr(left_ml, "@")) {
2965 right_ml = strstr(left_ml, "@")+1;
2966 *(strstr(left_ml, "@")) = '\0';
2969 left_from = g_strdup(from);
2970 if (strstr(left_from, "@")) {
2971 right_from = strstr(left_from, "@")+1;
2972 *(strstr(left_from, "@")) = '\0';
2975 if (left_ml && left_from && right_ml && right_from
2976 && !strncmp(left_from, left_ml, strlen(left_ml))
2977 && !strcmp(right_from, right_ml)) {
2986 static gboolean same_address(const gchar *addr1, const gchar *addr2)
2988 gchar *my_addr1, *my_addr2;
2990 if (!addr1 || !addr2)
2993 Xstrdup_a(my_addr1, addr1, return FALSE);
2994 Xstrdup_a(my_addr2, addr2, return FALSE);
2996 extract_address(my_addr1);
2997 extract_address(my_addr2);
2999 return !strcasecmp(my_addr1, my_addr2);
3002 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3003 gboolean to_all, gboolean to_ml,
3005 gboolean followup_and_reply_to)
3007 GSList *cc_list = NULL;
3010 gchar *replyto = NULL;
3011 GHashTable *to_table;
3013 gboolean reply_to_ml = FALSE;
3014 gboolean default_reply_to = FALSE;
3016 g_return_if_fail(compose->account != NULL);
3017 g_return_if_fail(msginfo != NULL);
3019 reply_to_ml = to_ml && compose->ml_post;
3021 default_reply_to = msginfo->folder &&
3022 msginfo->folder->prefs->enable_default_reply_to;
3024 if (compose->account->protocol != A_NNTP) {
3025 if (reply_to_ml && !default_reply_to) {
3027 gboolean is_subscr = is_subscription(compose->ml_post,
3030 /* normal answer to ml post with a reply-to */
3031 compose_entry_append(compose,
3034 if (compose->replyto
3035 && !same_address(compose->ml_post, compose->replyto))
3036 compose_entry_append(compose,
3040 /* answer to subscription confirmation */
3041 if (compose->replyto)
3042 compose_entry_append(compose,
3045 else if (msginfo->from)
3046 compose_entry_append(compose,
3051 else if (!(to_all || to_sender) && default_reply_to) {
3052 compose_entry_append(compose,
3053 msginfo->folder->prefs->default_reply_to,
3055 compose_entry_mark_default_to(compose,
3056 msginfo->folder->prefs->default_reply_to);
3061 Xstrdup_a(tmp1, msginfo->from, return);
3062 extract_address(tmp1);
3063 if (to_all || to_sender ||
3064 !account_find_from_address(tmp1, FALSE))
3065 compose_entry_append(compose,
3066 (compose->replyto && !to_sender)
3067 ? compose->replyto :
3068 msginfo->from ? msginfo->from : "",
3070 else if (!to_all && !to_sender) {
3071 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3072 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3073 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3074 if (compose->replyto) {
3075 compose_entry_append(compose,
3079 compose_entry_append(compose,
3080 msginfo->from ? msginfo->from : "",
3084 /* replying to own mail, use original recp */
3085 compose_entry_append(compose,
3086 msginfo->to ? msginfo->to : "",
3088 compose_entry_append(compose,
3089 msginfo->cc ? msginfo->cc : "",
3095 if (to_sender || (compose->followup_to &&
3096 !strncmp(compose->followup_to, "poster", 6)))
3097 compose_entry_append
3099 (compose->replyto ? compose->replyto :
3100 msginfo->from ? msginfo->from : ""),
3103 else if (followup_and_reply_to || to_all) {
3104 compose_entry_append
3106 (compose->replyto ? compose->replyto :
3107 msginfo->from ? msginfo->from : ""),
3110 compose_entry_append
3112 compose->followup_to ? compose->followup_to :
3113 compose->newsgroups ? compose->newsgroups : "",
3114 COMPOSE_NEWSGROUPS);
3117 compose_entry_append
3119 compose->followup_to ? compose->followup_to :
3120 compose->newsgroups ? compose->newsgroups : "",
3121 COMPOSE_NEWSGROUPS);
3124 if (msginfo->subject && *msginfo->subject) {
3128 buf = p = g_strdup(msginfo->subject);
3129 p += subject_get_prefix_length(p);
3130 memmove(buf, p, strlen(p) + 1);
3132 buf2 = g_strdup_printf("Re: %s", buf);
3133 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3138 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3140 if (to_ml && compose->ml_post) return;
3141 if (!to_all || compose->account->protocol == A_NNTP) return;
3143 if (compose->replyto) {
3144 Xstrdup_a(replyto, compose->replyto, return);
3145 extract_address(replyto);
3147 if (msginfo->from) {
3148 Xstrdup_a(from, msginfo->from, return);
3149 extract_address(from);
3152 if (replyto && from)
3153 cc_list = address_list_append_with_comments(cc_list, from);
3154 if (to_all && msginfo->folder &&
3155 msginfo->folder->prefs->enable_default_reply_to)
3156 cc_list = address_list_append_with_comments(cc_list,
3157 msginfo->folder->prefs->default_reply_to);
3158 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3159 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3161 to_table = g_hash_table_new(g_str_hash, g_str_equal);
3163 g_hash_table_insert(to_table, g_utf8_strdown(replyto, -1), GINT_TO_POINTER(1));
3164 if (compose->account) {
3165 g_hash_table_insert(to_table, g_utf8_strdown(compose->account->address, -1),
3166 GINT_TO_POINTER(1));
3168 /* remove address on To: and that of current account */
3169 for (cur = cc_list; cur != NULL; ) {
3170 GSList *next = cur->next;
3173 addr = g_utf8_strdown(cur->data, -1);
3174 extract_address(addr);
3176 if (GPOINTER_TO_INT(g_hash_table_lookup(to_table, addr)) == 1)
3177 cc_list = g_slist_remove(cc_list, cur->data);
3179 g_hash_table_insert(to_table, addr, GINT_TO_POINTER(1));
3183 hash_free_strings(to_table);
3184 g_hash_table_destroy(to_table);
3187 for (cur = cc_list; cur != NULL; cur = cur->next)
3188 compose_entry_append(compose, (gchar *)cur->data,
3190 slist_free_strings(cc_list);
3191 g_slist_free(cc_list);
3196 #define SET_ENTRY(entry, str) \
3199 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3202 #define SET_ADDRESS(type, str) \
3205 compose_entry_append(compose, str, type); \
3208 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3210 g_return_if_fail(msginfo != NULL);
3212 SET_ENTRY(subject_entry, msginfo->subject);
3213 SET_ENTRY(from_name, msginfo->from);
3214 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3215 SET_ADDRESS(COMPOSE_CC, compose->cc);
3216 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3217 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3218 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3219 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3221 compose_update_priority_menu_item(compose);
3222 compose_update_privacy_system_menu_item(compose, FALSE);
3223 compose_show_first_last_header(compose, TRUE);
3229 static void compose_insert_sig(Compose *compose, gboolean replace)
3231 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3232 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3234 GtkTextIter iter, iter_end;
3236 gboolean prev_autowrap;
3237 gboolean found = FALSE;
3238 gboolean exists = FALSE;
3240 g_return_if_fail(compose->account != NULL);
3244 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3245 G_CALLBACK(compose_changed_cb),
3248 mark = gtk_text_buffer_get_insert(buffer);
3249 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3250 cur_pos = gtk_text_iter_get_offset (&iter);
3252 gtk_text_buffer_get_end_iter(buffer, &iter);
3254 exists = (compose->sig_str != NULL);
3257 GtkTextIter first_iter, start_iter, end_iter;
3259 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3261 if (!exists || compose->sig_str[0] == '\0')
3264 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3265 compose->signature_tag);
3268 /* include previous \n\n */
3269 gtk_text_iter_backward_chars(&first_iter, 2);
3270 start_iter = first_iter;
3271 end_iter = first_iter;
3273 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3274 compose->signature_tag);
3275 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3276 compose->signature_tag);
3278 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3284 g_free(compose->sig_str);
3285 compose->sig_str = compose_get_signature_str(compose);
3287 cur_pos = gtk_text_iter_get_offset(&iter);
3289 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3290 g_free(compose->sig_str);
3291 compose->sig_str = NULL;
3293 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3295 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3296 gtk_text_iter_forward_chars(&iter, 2);
3297 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3298 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3300 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3301 cur_pos = gtk_text_buffer_get_char_count (buffer);
3303 /* put the cursor where it should be
3304 * either where the quote_fmt says, either before the signature */
3305 if (compose->set_cursor_pos < 0)
3306 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3308 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3309 compose->set_cursor_pos);
3311 gtk_text_buffer_place_cursor(buffer, &iter);
3312 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3313 G_CALLBACK(compose_changed_cb),
3319 static gchar *compose_get_signature_str(Compose *compose)
3321 gchar *sig_body = NULL;
3322 gchar *sig_str = NULL;
3323 gchar *utf8_sig_str = NULL;
3325 g_return_val_if_fail(compose->account != NULL, NULL);
3327 if (!compose->account->sig_path)
3330 if (compose->account->sig_type == SIG_FILE) {
3331 if (!is_file_or_fifo_exist(compose->account->sig_path)) {
3332 g_warning("can't open signature file: %s\n",
3333 compose->account->sig_path);
3338 if (compose->account->sig_type == SIG_COMMAND)
3339 sig_body = get_command_output(compose->account->sig_path);
3343 tmp = file_read_to_str(compose->account->sig_path);
3346 sig_body = normalize_newlines(tmp);
3350 if (compose->account->sig_sep) {
3351 sig_str = g_strconcat("\n\n", compose->account->sig_sep, "\n", sig_body,
3355 sig_str = g_strconcat("\n\n", sig_body, NULL);
3358 if (g_utf8_validate(sig_str, -1, NULL) == TRUE)
3359 utf8_sig_str = sig_str;
3361 utf8_sig_str = conv_codeset_strdup
3362 (sig_str, conv_get_locale_charset_str_no_utf8(),
3368 return utf8_sig_str;
3371 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3374 GtkTextBuffer *buffer;
3377 const gchar *cur_encoding;
3378 gchar buf[BUFFSIZE];
3381 gboolean prev_autowrap;
3382 gboolean badtxt = FALSE;
3384 g_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3386 if ((fp = g_fopen(file, "rb")) == NULL) {
3387 FILE_OP_ERROR(file, "fopen");
3388 return COMPOSE_INSERT_READ_ERROR;
3391 prev_autowrap = compose->autowrap;
3392 compose->autowrap = FALSE;
3394 text = GTK_TEXT_VIEW(compose->text);
3395 buffer = gtk_text_view_get_buffer(text);
3396 mark = gtk_text_buffer_get_insert(buffer);
3397 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3399 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3400 G_CALLBACK(text_inserted),
3403 cur_encoding = conv_get_locale_charset_str_no_utf8();
3405 while (fgets(buf, sizeof(buf), fp) != NULL) {
3408 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3409 str = g_strdup(buf);
3411 str = conv_codeset_strdup
3412 (buf, cur_encoding, CS_INTERNAL);
3415 /* strip <CR> if DOS/Windows file,
3416 replace <CR> with <LF> if Macintosh file. */
3419 if (len > 0 && str[len - 1] != '\n') {
3421 if (str[len] == '\r') str[len] = '\n';
3424 gtk_text_buffer_insert(buffer, &iter, str, -1);
3428 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3429 G_CALLBACK(text_inserted),
3431 compose->autowrap = prev_autowrap;
3432 if (compose->autowrap)
3433 compose_wrap_all(compose);
3438 return COMPOSE_INSERT_INVALID_CHARACTER;
3440 return COMPOSE_INSERT_SUCCESS;
3443 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3444 const gchar *filename,
3445 const gchar *content_type)
3453 GtkListStore *store;
3455 gboolean has_binary = FALSE;
3457 if (!is_file_exist(file)) {
3458 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3459 gboolean result = FALSE;
3460 if (file_from_uri && is_file_exist(file_from_uri)) {
3461 result = compose_attach_append(
3462 compose, file_from_uri,
3466 g_free(file_from_uri);
3469 alertpanel_error("File %s doesn't exist\n", filename);
3472 if ((size = get_file_size(file)) < 0) {
3473 alertpanel_error("Can't get file size of %s\n", filename);
3477 alertpanel_error(_("File %s is empty."), filename);
3480 if ((fp = g_fopen(file, "rb")) == NULL) {
3481 alertpanel_error(_("Can't read %s."), filename);
3486 ainfo = g_new0(AttachInfo, 1);
3487 auto_ainfo = g_auto_pointer_new_with_free
3488 (ainfo, (GFreeFunc) compose_attach_info_free);
3489 ainfo->file = g_strdup(file);
3492 ainfo->content_type = g_strdup(content_type);
3493 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3495 MsgFlags flags = {0, 0};
3497 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3498 ainfo->encoding = ENC_7BIT;
3500 ainfo->encoding = ENC_8BIT;
3502 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3503 if (msginfo && msginfo->subject)
3504 name = g_strdup(msginfo->subject);
3506 name = g_path_get_basename(filename ? filename : file);
3508 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3510 procmsg_msginfo_free(msginfo);
3512 if (!g_ascii_strncasecmp(content_type, "text", 4))
3513 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3515 ainfo->encoding = ENC_BASE64;
3516 name = g_path_get_basename(filename ? filename : file);
3517 ainfo->name = g_strdup(name);
3521 ainfo->content_type = procmime_get_mime_type(file);
3522 if (!ainfo->content_type) {
3523 ainfo->content_type =
3524 g_strdup("application/octet-stream");
3525 ainfo->encoding = ENC_BASE64;
3526 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text", 4))
3528 procmime_get_encoding_for_text_file(file, &has_binary);
3530 ainfo->encoding = ENC_BASE64;
3531 name = g_path_get_basename(filename ? filename : file);
3532 ainfo->name = g_strdup(name);
3536 if (ainfo->name != NULL
3537 && !strcmp(ainfo->name, ".")) {
3538 g_free(ainfo->name);
3542 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3543 g_free(ainfo->content_type);
3544 ainfo->content_type = g_strdup("application/octet-stream");
3548 size_text = to_human_readable(size);
3550 store = GTK_LIST_STORE(gtk_tree_view_get_model
3551 (GTK_TREE_VIEW(compose->attach_clist)));
3553 gtk_list_store_append(store, &iter);
3554 gtk_list_store_set(store, &iter,
3555 COL_MIMETYPE, ainfo->content_type,
3556 COL_SIZE, size_text,
3557 COL_NAME, ainfo->name,
3559 COL_AUTODATA, auto_ainfo,
3562 g_auto_pointer_free(auto_ainfo);
3563 compose_attach_update_label(compose);
3567 static void compose_use_signing(Compose *compose, gboolean use_signing)
3569 GtkItemFactory *ifactory;
3570 GtkWidget *menuitem = NULL;
3572 compose->use_signing = use_signing;
3573 ifactory = gtk_item_factory_from_widget(compose->menubar);
3574 menuitem = gtk_item_factory_get_item
3575 (ifactory, "/Options/Sign");
3576 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
3580 static void compose_use_encryption(Compose *compose, gboolean use_encryption)
3582 GtkItemFactory *ifactory;
3583 GtkWidget *menuitem = NULL;
3585 compose->use_encryption = use_encryption;
3586 ifactory = gtk_item_factory_from_widget(compose->menubar);
3587 menuitem = gtk_item_factory_get_item
3588 (ifactory, "/Options/Encrypt");
3590 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
3594 #define NEXT_PART_NOT_CHILD(info) \
3596 node = info->node; \
3597 while (node->children) \
3598 node = g_node_last_child(node); \
3599 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3602 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3606 MimeInfo *firsttext = NULL;
3607 MimeInfo *encrypted = NULL;
3610 const gchar *partname = NULL;
3612 mimeinfo = procmime_scan_message(msginfo);
3613 if (!mimeinfo) return;
3615 if (mimeinfo->node->children == NULL) {
3616 procmime_mimeinfo_free_all(mimeinfo);
3620 /* find first content part */
3621 child = (MimeInfo *) mimeinfo->node->children->data;
3622 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3623 child = (MimeInfo *)child->node->children->data;
3625 if (child->type == MIMETYPE_TEXT) {
3627 debug_print("First text part found\n");
3628 } else if (compose->mode == COMPOSE_REEDIT &&
3629 child->type == MIMETYPE_APPLICATION &&
3630 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3631 encrypted = (MimeInfo *)child->node->parent->data;
3634 child = (MimeInfo *) mimeinfo->node->children->data;
3635 while (child != NULL) {
3638 if (child == encrypted) {
3639 /* skip this part of tree */
3640 NEXT_PART_NOT_CHILD(child);
3644 if (child->type == MIMETYPE_MULTIPART) {
3645 /* get the actual content */
3646 child = procmime_mimeinfo_next(child);
3650 if (child == firsttext) {
3651 child = procmime_mimeinfo_next(child);
3655 outfile = procmime_get_tmp_file_name(child);
3656 if ((err = procmime_get_part(outfile, child)) < 0)
3657 g_warning("Can't get the part of multipart message. (%s)", strerror(-err));
3659 gchar *content_type;
3661 content_type = procmime_get_content_type_str(child->type, child->subtype);
3663 /* if we meet a pgp signature, we don't attach it, but
3664 * we force signing. */
3665 if ((strcmp(content_type, "application/pgp-signature") &&
3666 strcmp(content_type, "application/pkcs7-signature") &&
3667 strcmp(content_type, "application/x-pkcs7-signature"))
3668 || compose->mode == COMPOSE_REDIRECT) {
3669 partname = procmime_mimeinfo_get_parameter(child, "filename");
3670 if (partname == NULL)
3671 partname = procmime_mimeinfo_get_parameter(child, "name");
3672 if (partname == NULL)
3674 compose_attach_append(compose, outfile,
3675 partname, content_type);
3677 compose_force_signing(compose, compose->account);
3679 g_free(content_type);
3682 NEXT_PART_NOT_CHILD(child);
3684 procmime_mimeinfo_free_all(mimeinfo);
3687 #undef NEXT_PART_NOT_CHILD
3692 WAIT_FOR_INDENT_CHAR,
3693 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3696 /* return indent length, we allow:
3697 indent characters followed by indent characters or spaces/tabs,
3698 alphabets and numbers immediately followed by indent characters,
3699 and the repeating sequences of the above
3700 If quote ends with multiple spaces, only the first one is included. */
3701 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3702 const GtkTextIter *start, gint *len)
3704 GtkTextIter iter = *start;
3708 IndentState state = WAIT_FOR_INDENT_CHAR;
3711 gint alnum_count = 0;
3712 gint space_count = 0;
3715 if (prefs_common.quote_chars == NULL) {
3719 while (!gtk_text_iter_ends_line(&iter)) {
3720 wc = gtk_text_iter_get_char(&iter);
3721 if (g_unichar_iswide(wc))
3723 clen = g_unichar_to_utf8(wc, ch);
3727 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
3728 is_space = g_unichar_isspace(wc);
3730 if (state == WAIT_FOR_INDENT_CHAR) {
3731 if (!is_indent && !g_unichar_isalnum(wc))
3734 quote_len += alnum_count + space_count + 1;
3735 alnum_count = space_count = 0;
3736 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
3739 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
3740 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
3744 else if (is_indent) {
3745 quote_len += alnum_count + space_count + 1;
3746 alnum_count = space_count = 0;
3749 state = WAIT_FOR_INDENT_CHAR;
3753 gtk_text_iter_forward_char(&iter);
3756 if (quote_len > 0 && space_count > 0)
3762 if (quote_len > 0) {
3764 gtk_text_iter_forward_chars(&iter, quote_len);
3765 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
3771 /* return TRUE if the line is itemized */
3772 static gboolean compose_is_itemized(GtkTextBuffer *buffer,
3773 const GtkTextIter *start)
3775 GtkTextIter iter = *start;
3780 if (gtk_text_iter_ends_line(&iter))
3784 wc = gtk_text_iter_get_char(&iter);
3785 if (!g_unichar_isspace(wc))
3787 gtk_text_iter_forward_char(&iter);
3788 if (gtk_text_iter_ends_line(&iter))
3792 clen = g_unichar_to_utf8(wc, ch);
3796 if (!strchr("*-+", ch[0]))
3799 gtk_text_iter_forward_char(&iter);
3800 if (gtk_text_iter_ends_line(&iter))
3802 wc = gtk_text_iter_get_char(&iter);
3803 if (g_unichar_isspace(wc))
3809 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
3810 const GtkTextIter *start,
3811 GtkTextIter *break_pos,
3815 GtkTextIter iter = *start, line_end = *start;
3816 PangoLogAttr *attrs;
3823 gboolean can_break = FALSE;
3824 gboolean do_break = FALSE;
3825 gboolean was_white = FALSE;
3826 gboolean prev_dont_break = FALSE;
3828 gtk_text_iter_forward_to_line_end(&line_end);
3829 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
3830 len = g_utf8_strlen(str, -1);
3834 g_warning("compose_get_line_break_pos: len = 0!\n");
3838 /* g_print("breaking line: %d: %s (len = %d)\n",
3839 gtk_text_iter_get_line(&iter), str, len); */
3841 attrs = g_new(PangoLogAttr, len + 1);
3843 pango_default_break(str, -1, NULL, attrs, len + 1);
3847 /* skip quote and leading spaces */
3848 for (i = 0; *p != '\0' && i < len; i++) {
3851 wc = g_utf8_get_char(p);
3852 if (i >= quote_len && !g_unichar_isspace(wc))
3854 if (g_unichar_iswide(wc))
3856 else if (*p == '\t')
3860 p = g_utf8_next_char(p);
3863 for (; *p != '\0' && i < len; i++) {
3864 PangoLogAttr *attr = attrs + i;
3868 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
3871 was_white = attr->is_white;
3873 /* don't wrap URI */
3874 if ((uri_len = get_uri_len(p)) > 0) {
3876 if (pos > 0 && col > max_col) {
3886 wc = g_utf8_get_char(p);
3887 if (g_unichar_iswide(wc)) {
3889 if (prev_dont_break && can_break && attr->is_line_break)
3891 } else if (*p == '\t')
3895 if (pos > 0 && col > max_col) {
3900 if (*p == '-' || *p == '/')
3901 prev_dont_break = TRUE;
3903 prev_dont_break = FALSE;
3905 p = g_utf8_next_char(p);
3909 debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col);
3914 *break_pos = *start;
3915 gtk_text_iter_set_line_offset(break_pos, pos);
3920 static gboolean compose_join_next_line(Compose *compose,
3921 GtkTextBuffer *buffer,
3923 const gchar *quote_str)
3925 GtkTextIter iter_ = *iter, cur, prev, next, end;
3926 PangoLogAttr attrs[3];
3928 gchar *next_quote_str;
3931 gboolean keep_cursor = FALSE;
3933 if (!gtk_text_iter_forward_line(&iter_) ||
3934 gtk_text_iter_ends_line(&iter_)) {
3937 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
3939 if ((quote_str || next_quote_str) &&
3940 strcmp2(quote_str, next_quote_str) != 0) {
3941 g_free(next_quote_str);
3944 g_free(next_quote_str);
3947 if (quote_len > 0) {
3948 gtk_text_iter_forward_chars(&end, quote_len);
3949 if (gtk_text_iter_ends_line(&end)) {
3954 /* don't join itemized lines */
3955 if (compose_is_itemized(buffer, &end)) {
3959 /* don't join signature separator */
3960 if (compose_is_sig_separator(compose, buffer, &iter_)) {
3963 /* delete quote str */
3965 gtk_text_buffer_delete(buffer, &iter_, &end);
3967 /* don't join line breaks put by the user */
3969 gtk_text_iter_backward_char(&cur);
3970 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
3971 gtk_text_iter_forward_char(&cur);
3975 gtk_text_iter_forward_char(&cur);
3976 /* delete linebreak and extra spaces */
3977 while (gtk_text_iter_backward_char(&cur)) {
3978 wc1 = gtk_text_iter_get_char(&cur);
3979 if (!g_unichar_isspace(wc1))
3984 while (!gtk_text_iter_ends_line(&cur)) {
3985 wc1 = gtk_text_iter_get_char(&cur);
3986 if (!g_unichar_isspace(wc1))
3988 gtk_text_iter_forward_char(&cur);
3991 if (!gtk_text_iter_equal(&prev, &next)) {
3994 mark = gtk_text_buffer_get_insert(buffer);
3995 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
3996 if (gtk_text_iter_equal(&prev, &cur))
3998 gtk_text_buffer_delete(buffer, &prev, &next);
4002 /* insert space if required */
4003 gtk_text_iter_backward_char(&prev);
4004 wc1 = gtk_text_iter_get_char(&prev);
4005 wc2 = gtk_text_iter_get_char(&next);
4006 gtk_text_iter_forward_char(&next);
4007 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4008 pango_default_break(str, -1, NULL, attrs, 3);
4009 if (!attrs[1].is_line_break ||
4010 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4011 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4013 gtk_text_iter_backward_char(&iter_);
4014 gtk_text_buffer_place_cursor(buffer, &iter_);
4023 #define ADD_TXT_POS(bp_, ep_, pti_) \
4024 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4025 last = last->next; \
4026 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4027 last->next = NULL; \
4029 g_warning("alloc error scanning URIs\n"); \
4032 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4034 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4035 GtkTextBuffer *buffer;
4036 GtkTextIter iter, break_pos, end_of_line;
4037 gchar *quote_str = NULL;
4039 gboolean wrap_quote = prefs_common.linewrap_quote;
4040 gboolean prev_autowrap = compose->autowrap;
4041 gint startq_offset = -1, noq_offset = -1;
4042 gint uri_start = -1, uri_stop = -1;
4043 gint nouri_start = -1, nouri_stop = -1;
4044 gint num_blocks = 0;
4045 gint quotelevel = -1;
4046 gboolean modified = force;
4047 gboolean removed = FALSE;
4048 gboolean modified_before_remove = FALSE;
4050 gboolean start = TRUE;
4055 if (compose->draft_timeout_tag == -2) {
4059 compose->autowrap = FALSE;
4061 buffer = gtk_text_view_get_buffer(text);
4062 undo_wrapping(compose->undostruct, TRUE);
4067 mark = gtk_text_buffer_get_insert(buffer);
4068 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4072 if (compose->draft_timeout_tag == -2) {
4073 if (gtk_text_iter_ends_line(&iter)) {
4074 while (gtk_text_iter_ends_line(&iter) &&
4075 gtk_text_iter_forward_line(&iter))
4078 while (gtk_text_iter_backward_line(&iter)) {
4079 if (gtk_text_iter_ends_line(&iter)) {
4080 gtk_text_iter_forward_line(&iter);
4086 /* move to line start */
4087 gtk_text_iter_set_line_offset(&iter, 0);
4089 /* go until paragraph end (empty line) */
4090 while (start || !gtk_text_iter_ends_line(&iter)) {
4091 gchar *scanpos = NULL;
4092 /* parse table - in order of priority */
4094 const gchar *needle; /* token */
4096 /* token search function */
4097 gchar *(*search) (const gchar *haystack,
4098 const gchar *needle);
4099 /* part parsing function */
4100 gboolean (*parse) (const gchar *start,
4101 const gchar *scanpos,
4105 /* part to URI function */
4106 gchar *(*build_uri) (const gchar *bp,
4110 static struct table parser[] = {
4111 {"http://", strcasestr, get_uri_part, make_uri_string},
4112 {"https://", strcasestr, get_uri_part, make_uri_string},
4113 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4114 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4115 {"www.", strcasestr, get_uri_part, make_http_string},
4116 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4117 {"@", strcasestr, get_email_part, make_email_string}
4119 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4120 gint last_index = PARSE_ELEMS;
4122 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4126 if (!prev_autowrap && num_blocks == 0) {
4128 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4129 G_CALLBACK(text_inserted),
4132 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4135 uri_start = uri_stop = -1;
4137 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
4140 debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str);
4141 if (startq_offset == -1)
4142 startq_offset = gtk_text_iter_get_offset(&iter);
4143 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4144 if (quotelevel > 2) {
4145 /* recycle colors */
4146 if (prefs_common.recycle_quote_colors)
4155 if (startq_offset == -1)
4156 noq_offset = gtk_text_iter_get_offset(&iter);
4160 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4163 if (gtk_text_iter_ends_line(&iter)) {
4165 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4166 prefs_common.linewrap_len,
4168 GtkTextIter prev, next, cur;
4170 if (prev_autowrap != FALSE || force) {
4171 compose->automatic_break = TRUE;
4173 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4174 compose->automatic_break = FALSE;
4175 } else if (quote_str && wrap_quote) {
4176 compose->automatic_break = TRUE;
4178 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4179 compose->automatic_break = FALSE;
4182 /* remove trailing spaces */
4184 gtk_text_iter_backward_char(&cur);
4186 while (!gtk_text_iter_starts_line(&cur)) {
4189 gtk_text_iter_backward_char(&cur);
4190 wc = gtk_text_iter_get_char(&cur);
4191 if (!g_unichar_isspace(wc))
4195 if (!gtk_text_iter_equal(&prev, &next)) {
4196 gtk_text_buffer_delete(buffer, &prev, &next);
4198 gtk_text_iter_forward_char(&break_pos);
4202 gtk_text_buffer_insert(buffer, &break_pos,
4206 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4208 /* move iter to current line start */
4209 gtk_text_iter_set_line_offset(&iter, 0);
4216 /* move iter to next line start */
4222 if (!prev_autowrap && num_blocks > 0) {
4224 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4225 G_CALLBACK(text_inserted),
4229 while (!gtk_text_iter_ends_line(&end_of_line)) {
4230 gtk_text_iter_forward_char(&end_of_line);
4232 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4234 nouri_start = gtk_text_iter_get_offset(&iter);
4235 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4237 walk_pos = gtk_text_iter_get_offset(&iter);
4238 /* FIXME: this looks phony. scanning for anything in the parse table */
4239 for (n = 0; n < PARSE_ELEMS; n++) {
4242 tmp = parser[n].search(walk, parser[n].needle);
4244 if (scanpos == NULL || tmp < scanpos) {
4253 /* check if URI can be parsed */
4254 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4255 (const gchar **)&ep, FALSE)
4256 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4260 strlen(parser[last_index].needle);
4263 uri_start = walk_pos + (bp - o_walk);
4264 uri_stop = walk_pos + (ep - o_walk);
4268 gtk_text_iter_forward_line(&iter);
4271 if (startq_offset != -1) {
4272 GtkTextIter startquote, endquote;
4273 gtk_text_buffer_get_iter_at_offset(
4274 buffer, &startquote, startq_offset);
4277 switch (quotelevel) {
4279 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4280 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4281 gtk_text_buffer_apply_tag_by_name(
4282 buffer, "quote0", &startquote, &endquote);
4283 gtk_text_buffer_remove_tag_by_name(
4284 buffer, "quote1", &startquote, &endquote);
4285 gtk_text_buffer_remove_tag_by_name(
4286 buffer, "quote2", &startquote, &endquote);
4291 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4292 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4293 gtk_text_buffer_apply_tag_by_name(
4294 buffer, "quote1", &startquote, &endquote);
4295 gtk_text_buffer_remove_tag_by_name(
4296 buffer, "quote0", &startquote, &endquote);
4297 gtk_text_buffer_remove_tag_by_name(
4298 buffer, "quote2", &startquote, &endquote);
4303 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4304 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4305 gtk_text_buffer_apply_tag_by_name(
4306 buffer, "quote2", &startquote, &endquote);
4307 gtk_text_buffer_remove_tag_by_name(
4308 buffer, "quote0", &startquote, &endquote);
4309 gtk_text_buffer_remove_tag_by_name(
4310 buffer, "quote1", &startquote, &endquote);
4316 } else if (noq_offset != -1) {
4317 GtkTextIter startnoquote, endnoquote;
4318 gtk_text_buffer_get_iter_at_offset(
4319 buffer, &startnoquote, noq_offset);
4322 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4323 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4324 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4325 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4326 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4327 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4328 gtk_text_buffer_remove_tag_by_name(
4329 buffer, "quote0", &startnoquote, &endnoquote);
4330 gtk_text_buffer_remove_tag_by_name(
4331 buffer, "quote1", &startnoquote, &endnoquote);
4332 gtk_text_buffer_remove_tag_by_name(
4333 buffer, "quote2", &startnoquote, &endnoquote);
4339 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4340 GtkTextIter nouri_start_iter, nouri_end_iter;
4341 gtk_text_buffer_get_iter_at_offset(
4342 buffer, &nouri_start_iter, nouri_start);
4343 gtk_text_buffer_get_iter_at_offset(
4344 buffer, &nouri_end_iter, nouri_stop);
4345 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4346 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4347 gtk_text_buffer_remove_tag_by_name(
4348 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4349 modified_before_remove = modified;
4354 if (uri_start >= 0 && uri_stop > 0) {
4355 GtkTextIter uri_start_iter, uri_end_iter, back;
4356 gtk_text_buffer_get_iter_at_offset(
4357 buffer, &uri_start_iter, uri_start);
4358 gtk_text_buffer_get_iter_at_offset(
4359 buffer, &uri_end_iter, uri_stop);
4360 back = uri_end_iter;
4361 gtk_text_iter_backward_char(&back);
4362 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4363 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4364 gtk_text_buffer_apply_tag_by_name(
4365 buffer, "link", &uri_start_iter, &uri_end_iter);
4367 if (removed && !modified_before_remove) {
4373 debug_print("not modified, out after %d lines\n", lines);
4378 debug_print("modified, out after %d lines\n", lines);
4382 undo_wrapping(compose->undostruct, FALSE);
4383 compose->autowrap = prev_autowrap;
4388 void compose_action_cb(void *data)
4390 Compose *compose = (Compose *)data;
4391 compose_wrap_all(compose);
4394 static void compose_wrap_all(Compose *compose)
4396 compose_wrap_all_full(compose, FALSE);
4399 static void compose_wrap_all_full(Compose *compose, gboolean force)
4401 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4402 GtkTextBuffer *buffer;
4404 gboolean modified = TRUE;
4406 buffer = gtk_text_view_get_buffer(text);
4408 gtk_text_buffer_get_start_iter(buffer, &iter);
4409 while (!gtk_text_iter_is_end(&iter) && modified)
4410 modified = compose_beautify_paragraph(compose, &iter, force);
4414 static void compose_set_title(Compose *compose)
4420 edited = compose->modified ? _(" [Edited]") : "";
4422 subject = gtk_editable_get_chars(
4423 GTK_EDITABLE(compose->subject_entry), 0, -1);
4426 if (subject && strlen(subject))
4427 str = g_strdup_printf(_("%s - Compose message%s"),
4430 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4432 str = g_strdup(_("Compose message"));
4435 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4441 * compose_current_mail_account:
4443 * Find a current mail account (the currently selected account, or the
4444 * default account, if a news account is currently selected). If a
4445 * mail account cannot be found, display an error message.
4447 * Return value: Mail account, or NULL if not found.
4449 static PrefsAccount *
4450 compose_current_mail_account(void)
4454 if (cur_account && cur_account->protocol != A_NNTP)
4457 ac = account_get_default();
4458 if (!ac || ac->protocol == A_NNTP) {
4459 alertpanel_error(_("Account for sending mail is not specified.\n"
4460 "Please select a mail account before sending."));
4467 #define QUOTE_IF_REQUIRED(out, str) \
4469 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4473 len = strlen(str) + 3; \
4474 if ((__tmp = alloca(len)) == NULL) { \
4475 g_warning("can't allocate memory\n"); \
4476 g_string_free(header, TRUE); \
4479 g_snprintf(__tmp, len, "\"%s\"", str); \
4484 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4485 g_warning("can't allocate memory\n"); \
4486 g_string_free(header, TRUE); \
4489 strcpy(__tmp, str); \
4495 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4497 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4501 len = strlen(str) + 3; \
4502 if ((__tmp = alloca(len)) == NULL) { \
4503 g_warning("can't allocate memory\n"); \
4506 g_snprintf(__tmp, len, "\"%s\"", str); \
4511 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4512 g_warning("can't allocate memory\n"); \
4515 strcpy(__tmp, str); \
4521 static void compose_select_account(Compose *compose, PrefsAccount *account,
4524 GtkItemFactory *ifactory;
4527 g_return_if_fail(account != NULL);
4529 compose->account = account;
4531 if (account->name && *account->name) {
4533 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4534 from = g_strdup_printf("%s <%s>",
4535 buf, account->address);
4536 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4538 from = g_strdup_printf("<%s>",
4540 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4545 compose_set_title(compose);
4547 ifactory = gtk_item_factory_from_widget(compose->menubar);
4549 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4550 menu_set_active(ifactory, "/Options/Sign", TRUE);
4552 menu_set_active(ifactory, "/Options/Sign", FALSE);
4553 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4554 menu_set_active(ifactory, "/Options/Encrypt", TRUE);
4556 menu_set_active(ifactory, "/Options/Encrypt", FALSE);
4558 activate_privacy_system(compose, account, FALSE);
4560 if (!init && compose->mode != COMPOSE_REDIRECT) {
4561 undo_block(compose->undostruct);
4562 compose_insert_sig(compose, TRUE);
4563 undo_unblock(compose->undostruct);
4567 /* use account's dict info if set */
4568 if (compose->gtkaspell) {
4569 if (account->enable_default_dictionary)
4570 gtkaspell_change_dict(compose->gtkaspell,
4571 account->default_dictionary, FALSE);
4572 if (account->enable_default_alt_dictionary)
4573 gtkaspell_change_alt_dict(compose->gtkaspell,
4574 account->default_alt_dictionary);
4575 if (account->enable_default_dictionary
4576 || account->enable_default_alt_dictionary)
4577 compose_spell_menu_changed(compose);
4582 gboolean compose_check_for_valid_recipient(Compose *compose) {
4583 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
4584 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
4585 gboolean recipient_found = FALSE;
4589 /* free to and newsgroup list */
4590 slist_free_strings(compose->to_list);
4591 g_slist_free(compose->to_list);
4592 compose->to_list = NULL;
4594 slist_free_strings(compose->newsgroup_list);
4595 g_slist_free(compose->newsgroup_list);
4596 compose->newsgroup_list = NULL;
4598 /* search header entries for to and newsgroup entries */
4599 for (list = compose->header_list; list; list = list->next) {
4602 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_BIN(((ComposeHeaderEntry *)list->data)->combo)->child), 0, -1);
4603 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4606 if (entry[0] != '\0') {
4607 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
4608 if (!strcmp(header, prefs_common_translated_header_name(*strptr))) {
4609 compose->to_list = address_list_append(compose->to_list, entry);
4610 recipient_found = TRUE;
4613 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
4614 if (!strcmp(header, prefs_common_translated_header_name(*strptr))) {
4615 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
4616 recipient_found = TRUE;
4623 return recipient_found;
4626 static gboolean compose_check_for_set_recipients(Compose *compose)
4628 if (compose->account->set_autocc && compose->account->auto_cc) {
4629 gboolean found_other = FALSE;
4631 /* search header entries for to and newsgroup entries */
4632 for (list = compose->header_list; list; list = list->next) {
4635 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4636 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_BIN(((ComposeHeaderEntry *)list->data)->combo)->child), 0, -1);
4639 if (strcmp(entry, compose->account->auto_cc)
4640 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
4650 if (compose->batch) {
4651 gtk_widget_show_all(compose->window);
4653 aval = alertpanel(_("Send"),
4654 _("The only recipient is the default CC address. Send anyway?"),
4655 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4656 if (aval != G_ALERTALTERNATE)
4660 if (compose->account->set_autobcc && compose->account->auto_bcc) {
4661 gboolean found_other = FALSE;
4663 /* search header entries for to and newsgroup entries */
4664 for (list = compose->header_list; list; list = list->next) {
4667 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4668 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_BIN(((ComposeHeaderEntry *)list->data)->combo)->child), 0, -1);
4671 if (strcmp(entry, compose->account->auto_bcc)
4672 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
4682 if (compose->batch) {
4683 gtk_widget_show_all(compose->window);
4685 aval = alertpanel(_("Send"),
4686 _("The only recipient is the default BCC address. Send anyway?"),
4687 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4688 if (aval != G_ALERTALTERNATE)
4695 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
4699 if (compose_check_for_valid_recipient(compose) == FALSE) {
4700 if (compose->batch) {
4701 gtk_widget_show_all(compose->window);
4703 alertpanel_error(_("Recipient is not specified."));
4707 if (compose_check_for_set_recipients(compose) == FALSE) {
4711 if (!compose->batch) {
4712 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
4713 if (*str == '\0' && check_everything == TRUE &&
4714 compose->mode != COMPOSE_REDIRECT) {
4716 gchar *button_label;
4719 if (compose->sending)
4720 button_label = _("+_Send");
4722 button_label = _("+_Queue");
4723 message = g_strdup_printf(_("Subject is empty. %s"),
4724 compose->sending?_("Send it anyway?"):
4725 _("Queue it anyway?"));
4727 aval = alertpanel(compose->sending?_("Send"):_("Send later"), message,
4728 GTK_STOCK_CANCEL, button_label, NULL);
4730 if (aval != G_ALERTALTERNATE)
4735 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
4741 gint compose_send(Compose *compose)
4744 FolderItem *folder = NULL;
4746 gchar *msgpath = NULL;
4747 gboolean discard_window = FALSE;
4748 gchar *errstr = NULL;
4749 gchar *tmsgid = NULL;
4750 MainWindow *mainwin = mainwindow_get_mainwindow();
4751 gboolean queued_removed = FALSE;
4753 if (prefs_common.send_dialog_invisible
4754 || compose->batch == TRUE)
4755 discard_window = TRUE;
4757 compose_allow_user_actions (compose, FALSE);
4758 compose->sending = TRUE;
4760 if (compose_check_entries(compose, TRUE) == FALSE) {
4761 if (compose->batch) {
4762 gtk_widget_show_all(compose->window);
4768 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
4771 if (compose->batch) {
4772 gtk_widget_show_all(compose->window);
4775 alertpanel_error(_("Could not queue message for sending:\n\n"
4776 "Charset conversion failed."));
4777 } else if (val == -5) {
4778 alertpanel_error(_("Could not queue message for sending:\n\n"
4779 "Couldn't get recipient encryption key."));
4780 } else if (val == -6) {
4782 } else if (val == -3) {
4783 if (privacy_peek_error())
4784 alertpanel_error(_("Could not queue message for sending:\n\n"
4785 "Signature failed: %s"), privacy_get_error());
4786 } else if (val == -2 && errno != 0) {
4787 alertpanel_error(_("Could not queue message for sending:\n\n%s."), strerror(errno));
4789 alertpanel_error(_("Could not queue message for sending."));
4794 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
4795 if (discard_window) {
4796 compose->sending = FALSE;
4797 compose_close(compose);
4798 /* No more compose access in the normal codepath
4799 * after this point! */
4804 alertpanel_error(_("The message was queued but could not be "
4805 "sent.\nUse \"Send queued messages\" from "
4806 "the main window to retry."));
4807 if (!discard_window) {
4814 if (msgpath == NULL) {
4815 msgpath = folder_item_fetch_msg(folder, msgnum);
4816 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
4819 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
4820 claws_unlink(msgpath);
4823 if (!discard_window) {
4825 if (!queued_removed)
4826 folder_item_remove_msg(folder, msgnum);
4827 folder_item_scan(folder);
4829 /* make sure we delete that */
4830 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
4832 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
4833 folder_item_remove_msg(folder, tmp->msgnum);
4834 procmsg_msginfo_free(tmp);
4841 if (!queued_removed)
4842 folder_item_remove_msg(folder, msgnum);
4843 folder_item_scan(folder);
4845 /* make sure we delete that */
4846 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
4848 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
4849 folder_item_remove_msg(folder, tmp->msgnum);
4850 procmsg_msginfo_free(tmp);
4853 if (!discard_window) {
4854 compose->sending = FALSE;
4855 compose_allow_user_actions (compose, TRUE);
4856 compose_close(compose);
4860 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
4861 "the main window to retry."), errstr);
4864 alertpanel_error_log(_("The message was queued but could not be "
4865 "sent.\nUse \"Send queued messages\" from "
4866 "the main window to retry."));
4868 if (!discard_window) {
4877 toolbar_main_set_sensitive(mainwin);
4878 main_window_set_menu_sensitive(mainwin);
4884 compose_allow_user_actions (compose, TRUE);
4885 compose->sending = FALSE;
4886 compose->modified = TRUE;
4887 toolbar_main_set_sensitive(mainwin);
4888 main_window_set_menu_sensitive(mainwin);
4893 static gboolean compose_use_attach(Compose *compose)
4895 GtkTreeModel *model = gtk_tree_view_get_model
4896 (GTK_TREE_VIEW(compose->attach_clist));
4897 return gtk_tree_model_iter_n_children(model, NULL) > 0;
4900 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
4903 gchar buf[BUFFSIZE];
4905 gboolean first_to_address;
4906 gboolean first_cc_address;
4908 ComposeHeaderEntry *headerentry;
4909 const gchar *headerentryname;
4910 const gchar *cc_hdr;
4911 const gchar *to_hdr;
4912 gboolean err = FALSE;
4914 debug_print("Writing redirect header\n");
4916 cc_hdr = prefs_common_translated_header_name("Cc:");
4917 to_hdr = prefs_common_translated_header_name("To:");
4919 first_to_address = TRUE;
4920 for (list = compose->header_list; list; list = list->next) {
4921 headerentry = ((ComposeHeaderEntry *)list->data);
4922 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_BIN(headerentry->combo)->child));
4924 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
4925 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
4926 Xstrdup_a(str, entstr, return -1);
4928 if (str[0] != '\0') {
4929 compose_convert_header
4930 (compose, buf, sizeof(buf), str,
4931 strlen("Resent-To") + 2, TRUE);
4933 if (first_to_address) {
4934 err |= (fprintf(fp, "Resent-To: ") < 0);
4935 first_to_address = FALSE;
4937 err |= (fprintf(fp, ",") < 0);
4939 err |= (fprintf(fp, "%s", buf) < 0);
4943 if (!first_to_address) {
4944 err |= (fprintf(fp, "\n") < 0);
4947 first_cc_address = TRUE;
4948 for (list = compose->header_list; list; list = list->next) {
4949 headerentry = ((ComposeHeaderEntry *)list->data);
4950 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_BIN(headerentry->combo)->child));
4952 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
4953 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
4954 Xstrdup_a(str, strg, return -1);
4956 if (str[0] != '\0') {
4957 compose_convert_header
4958 (compose, buf, sizeof(buf), str,
4959 strlen("Resent-Cc") + 2, TRUE);
4961 if (first_cc_address) {
4962 err |= (fprintf(fp, "Resent-Cc: ") < 0);
4963 first_cc_address = FALSE;
4965 err |= (fprintf(fp, ",") < 0);
4967 err |= (fprintf(fp, "%s", buf) < 0);
4971 if (!first_cc_address) {
4972 err |= (fprintf(fp, "\n") < 0);
4975 return (err ? -1:0);
4978 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
4980 gchar buf[BUFFSIZE];
4982 const gchar *entstr;
4983 /* struct utsname utsbuf; */
4984 gboolean err = FALSE;
4986 g_return_val_if_fail(fp != NULL, -1);
4987 g_return_val_if_fail(compose->account != NULL, -1);
4988 g_return_val_if_fail(compose->account->address != NULL, -1);
4991 get_rfc822_date(buf, sizeof(buf));
4992 err |= (fprintf(fp, "Resent-Date: %s\n", buf) < 0);
4995 if (compose->account->name && *compose->account->name) {
4996 compose_convert_header
4997 (compose, buf, sizeof(buf), compose->account->name,
4998 strlen("From: "), TRUE);
4999 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5000 buf, compose->account->address) < 0);
5002 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5005 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5006 if (*entstr != '\0') {
5007 Xstrdup_a(str, entstr, return -1);
5010 compose_convert_header(compose, buf, sizeof(buf), str,
5011 strlen("Subject: "), FALSE);
5012 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5016 /* Resent-Message-ID */
5017 if (compose->account->set_domain && compose->account->domain) {
5018 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
5019 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
5020 g_snprintf(buf, sizeof(buf), "%s",
5021 strchr(compose->account->address, '@') ?
5022 strchr(compose->account->address, '@')+1 :
5023 compose->account->address);
5025 g_snprintf(buf, sizeof(buf), "%s", "");
5028 if (compose->account->gen_msgid) {
5029 generate_msgid(buf, sizeof(buf));
5030 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", buf) < 0);
5031 compose->msgid = g_strdup(buf);
5033 compose->msgid = NULL;
5036 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5039 /* separator between header and body */
5040 err |= (fputs("\n", fp) == EOF);
5042 return (err ? -1:0);
5045 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5049 gchar buf[BUFFSIZE];
5051 gboolean skip = FALSE;
5052 gboolean err = FALSE;
5053 gchar *not_included[]={
5054 "Return-Path:", "Delivered-To:", "Received:",
5055 "Subject:", "X-UIDL:", "AF:",
5056 "NF:", "PS:", "SRH:",
5057 "SFN:", "DSR:", "MID:",
5058 "CFG:", "PT:", "S:",
5059 "RQ:", "SSV:", "NSV:",
5060 "SSH:", "R:", "MAID:",
5061 "NAID:", "RMID:", "FMID:",
5062 "SCF:", "RRCPT:", "NG:",
5063 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5064 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5065 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5066 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5069 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
5070 FILE_OP_ERROR(compose->redirect_filename, "fopen");
5074 while (procheader_get_one_field_asis(buf, sizeof(buf), fp) != -1) {
5076 for (i = 0; not_included[i] != NULL; i++) {
5077 if (g_ascii_strncasecmp(buf, not_included[i],
5078 strlen(not_included[i])) == 0) {
5085 if (fputs(buf, fdest) == -1)
5088 if (!prefs_common.redirect_keep_from) {
5089 if (g_ascii_strncasecmp(buf, "From:",
5090 strlen("From:")) == 0) {
5091 err |= (fputs(" (by way of ", fdest) == EOF);
5092 if (compose->account->name
5093 && *compose->account->name) {
5094 compose_convert_header
5095 (compose, buf, sizeof(buf),
5096 compose->account->name,
5099 err |= (fprintf(fdest, "%s <%s>",
5101 compose->account->address) < 0);
5103 err |= (fprintf(fdest, "%s",
5104 compose->account->address) < 0);
5105 err |= (fputs(")", fdest) == EOF);
5109 if (fputs("\n", fdest) == -1)
5116 if (compose_redirect_write_headers(compose, fdest))
5119 while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
5120 if (fwrite(buf, sizeof(gchar), len, fdest) != len)
5133 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5135 GtkTextBuffer *buffer;
5136 GtkTextIter start, end;
5139 const gchar *out_codeset;
5140 EncodingType encoding;
5141 MimeInfo *mimemsg, *mimetext;
5144 if (action == COMPOSE_WRITE_FOR_SEND)
5145 attach_parts = TRUE;
5147 /* create message MimeInfo */
5148 mimemsg = procmime_mimeinfo_new();
5149 mimemsg->type = MIMETYPE_MESSAGE;
5150 mimemsg->subtype = g_strdup("rfc822");
5151 mimemsg->content = MIMECONTENT_MEM;
5152 mimemsg->tmp = TRUE; /* must free content later */
5153 mimemsg->data.mem = compose_get_header(compose);
5155 /* Create text part MimeInfo */
5156 /* get all composed text */
5157 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5158 gtk_text_buffer_get_start_iter(buffer, &start);
5159 gtk_text_buffer_get_end_iter(buffer, &end);
5160 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5161 if (is_ascii_str(chars)) {
5164 out_codeset = CS_US_ASCII;
5165 encoding = ENC_7BIT;
5167 const gchar *src_codeset = CS_INTERNAL;
5169 out_codeset = conv_get_charset_str(compose->out_encoding);
5172 gchar *test_conv_global_out = NULL;
5173 gchar *test_conv_reply = NULL;
5175 /* automatic mode. be automatic. */
5176 codeconv_set_strict(TRUE);
5178 out_codeset = conv_get_outgoing_charset_str();
5180 debug_print("trying to convert to %s\n", out_codeset);
5181 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5184 if (!test_conv_global_out && compose->orig_charset
5185 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5186 out_codeset = compose->orig_charset;
5187 debug_print("failure; trying to convert to %s\n", out_codeset);
5188 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5191 if (!test_conv_global_out && !test_conv_reply) {
5193 out_codeset = CS_INTERNAL;
5194 debug_print("failure; finally using %s\n", out_codeset);
5196 g_free(test_conv_global_out);
5197 g_free(test_conv_reply);
5198 codeconv_set_strict(FALSE);
5201 if (!g_ascii_strcasecmp(out_codeset, CS_US_ASCII))
5202 out_codeset = CS_ISO_8859_1;
5204 if (prefs_common.encoding_method == CTE_BASE64)
5205 encoding = ENC_BASE64;
5206 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5207 encoding = ENC_QUOTED_PRINTABLE;
5208 else if (prefs_common.encoding_method == CTE_8BIT)
5209 encoding = ENC_8BIT;
5211 encoding = procmime_get_encoding_for_charset(out_codeset);
5213 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5214 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5216 if (action == COMPOSE_WRITE_FOR_SEND) {
5217 codeconv_set_strict(TRUE);
5218 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5219 codeconv_set_strict(FALSE);
5225 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5226 "to the specified %s charset.\n"
5227 "Send it as %s?"), out_codeset, src_codeset);
5228 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL, _("+_Send"), NULL, FALSE,
5229 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5232 if (aval != G_ALERTALTERNATE) {
5237 out_codeset = src_codeset;
5243 out_codeset = src_codeset;
5249 if (encoding == ENC_8BIT || encoding == ENC_7BIT) {
5250 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5251 strstr(buf, "\nFrom ") != NULL) {
5252 encoding = ENC_QUOTED_PRINTABLE;
5256 mimetext = procmime_mimeinfo_new();
5257 mimetext->content = MIMECONTENT_MEM;
5258 mimetext->tmp = TRUE; /* must free content later */
5259 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5260 * and free the data, which we need later. */
5261 mimetext->data.mem = g_strdup(buf);
5262 mimetext->type = MIMETYPE_TEXT;
5263 mimetext->subtype = g_strdup("plain");
5264 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5265 g_strdup(out_codeset));
5267 /* protect trailing spaces when signing message */
5268 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5269 privacy_system_can_sign(compose->privacy_system)) {
5270 encoding = ENC_QUOTED_PRINTABLE;
5273 debug_print("main text: %zd bytes encoded as %s in %d\n",
5274 strlen(buf), out_codeset, encoding);
5276 /* check for line length limit */
5277 if (action == COMPOSE_WRITE_FOR_SEND &&
5278 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5279 check_line_length(buf, 1000, &line) < 0) {
5283 msg = g_strdup_printf
5284 (_("Line %d exceeds the line length limit (998 bytes).\n"
5285 "The contents of the message might be broken on the way to the delivery.\n"
5287 "Send it anyway?"), line + 1);
5288 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5290 if (aval != G_ALERTALTERNATE) {
5296 if (encoding != ENC_UNKNOWN)
5297 procmime_encode_content(mimetext, encoding);
5299 /* append attachment parts */
5300 if (compose_use_attach(compose) && attach_parts) {
5301 MimeInfo *mimempart;
5302 gchar *boundary = NULL;
5303 mimempart = procmime_mimeinfo_new();
5304 mimempart->content = MIMECONTENT_EMPTY;
5305 mimempart->type = MIMETYPE_MULTIPART;
5306 mimempart->subtype = g_strdup("mixed");
5310 boundary = generate_mime_boundary(NULL);
5311 } while (strstr(buf, boundary) != NULL);
5313 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5316 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5318 g_node_append(mimempart->node, mimetext->node);
5319 g_node_append(mimemsg->node, mimempart->node);
5321 compose_add_attachments(compose, mimempart);
5323 g_node_append(mimemsg->node, mimetext->node);
5327 /* sign message if sending */
5328 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5329 privacy_system_can_sign(compose->privacy_system))
5330 if (!privacy_sign(compose->privacy_system, mimemsg, compose->account))
5333 procmime_write_mimeinfo(mimemsg, fp);
5335 procmime_mimeinfo_free_all(mimemsg);
5340 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5342 GtkTextBuffer *buffer;
5343 GtkTextIter start, end;
5348 if ((fp = g_fopen(file, "wb")) == NULL) {
5349 FILE_OP_ERROR(file, "fopen");
5353 /* chmod for security */
5354 if (change_file_mode_rw(fp, file) < 0) {
5355 FILE_OP_ERROR(file, "chmod");
5356 g_warning("can't change file mode\n");
5359 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5360 gtk_text_buffer_get_start_iter(buffer, &start);
5361 gtk_text_buffer_get_end_iter(buffer, &end);
5362 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5364 chars = conv_codeset_strdup
5365 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5368 if (!chars) return -1;
5371 len = strlen(chars);
5372 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5373 FILE_OP_ERROR(file, "fwrite");
5382 if (fclose(fp) == EOF) {
5383 FILE_OP_ERROR(file, "fclose");
5390 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5393 MsgInfo *msginfo = compose->targetinfo;
5395 g_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5396 if (!msginfo) return -1;
5398 if (!force && MSG_IS_LOCKED(msginfo->flags))
5401 item = msginfo->folder;
5402 g_return_val_if_fail(item != NULL, -1);
5404 if (procmsg_msg_exist(msginfo) &&
5405 (folder_has_parent_of_type(item, F_QUEUE) ||
5406 folder_has_parent_of_type(item, F_DRAFT)
5407 || msginfo == compose->autosaved_draft)) {
5408 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5409 g_warning("can't remove the old message\n");
5412 debug_print("removed reedit target %d\n", msginfo->msgnum);
5419 static void compose_remove_draft(Compose *compose)
5422 MsgInfo *msginfo = compose->targetinfo;
5423 drafts = account_get_special_folder(compose->account, F_DRAFT);
5425 if (procmsg_msg_exist(msginfo)) {
5426 folder_item_remove_msg(drafts, msginfo->msgnum);
5431 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5432 gboolean remove_reedit_target)
5434 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5437 static gboolean compose_warn_encryption(Compose *compose)
5439 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5440 AlertValue val = G_ALERTALTERNATE;
5442 if (warning == NULL)
5445 val = alertpanel_full(_("Encryption warning"), warning,
5446 GTK_STOCK_CANCEL, _("+C_ontinue"), NULL,
5447 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5448 if (val & G_ALERTDISABLE) {
5449 val &= ~G_ALERTDISABLE;
5450 if (val == G_ALERTALTERNATE)
5451 privacy_inhibit_encrypt_warning(compose->privacy_system,
5455 if (val == G_ALERTALTERNATE) {
5462 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
5463 gchar **msgpath, gboolean check_subject,
5464 gboolean remove_reedit_target)
5471 static gboolean lock = FALSE;
5472 PrefsAccount *mailac = NULL, *newsac = NULL;
5473 gboolean err = FALSE;
5475 debug_print("queueing message...\n");
5476 g_return_val_if_fail(compose->account != NULL, -1);
5480 if (compose_check_entries(compose, check_subject) == FALSE) {
5482 if (compose->batch) {
5483 gtk_widget_show_all(compose->window);
5488 if (!compose->to_list && !compose->newsgroup_list) {
5489 g_warning("can't get recipient list.");
5494 if (compose->to_list) {
5495 if (compose->account->protocol != A_NNTP)
5496 mailac = compose->account;
5497 else if (cur_account && cur_account->protocol != A_NNTP)
5498 mailac = cur_account;
5499 else if (!(mailac = compose_current_mail_account())) {
5501 alertpanel_error(_("No account for sending mails available!"));
5506 if (compose->newsgroup_list) {
5507 if (compose->account->protocol == A_NNTP)
5508 newsac = compose->account;
5509 else if (!newsac->protocol != A_NNTP) {
5511 alertpanel_error(_("No account for posting news available!"));
5516 /* write queue header */
5517 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
5518 G_DIR_SEPARATOR, compose, (guint) rand());
5519 debug_print("queuing to %s\n", tmp);
5520 if ((fp = g_fopen(tmp, "wb")) == NULL) {
5521 FILE_OP_ERROR(tmp, "fopen");
5527 if (change_file_mode_rw(fp, tmp) < 0) {
5528 FILE_OP_ERROR(tmp, "chmod");
5529 g_warning("can't change file mode\n");
5532 /* queueing variables */
5533 err |= (fprintf(fp, "AF:\n") < 0);
5534 err |= (fprintf(fp, "NF:0\n") < 0);
5535 err |= (fprintf(fp, "PS:10\n") < 0);
5536 err |= (fprintf(fp, "SRH:1\n") < 0);
5537 err |= (fprintf(fp, "SFN:\n") < 0);
5538 err |= (fprintf(fp, "DSR:\n") < 0);
5540 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
5542 err |= (fprintf(fp, "MID:\n") < 0);
5543 err |= (fprintf(fp, "CFG:\n") < 0);
5544 err |= (fprintf(fp, "PT:0\n") < 0);
5545 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
5546 err |= (fprintf(fp, "RQ:\n") < 0);
5548 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
5550 err |= (fprintf(fp, "SSV:\n") < 0);
5552 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
5554 err |= (fprintf(fp, "NSV:\n") < 0);
5555 err |= (fprintf(fp, "SSH:\n") < 0);
5556 /* write recepient list */
5557 if (compose->to_list) {
5558 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
5559 for (cur = compose->to_list->next; cur != NULL;
5561 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
5562 err |= (fprintf(fp, "\n") < 0);
5564 /* write newsgroup list */
5565 if (compose->newsgroup_list) {
5566 err |= (fprintf(fp, "NG:") < 0);
5567 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
5568 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
5569 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
5570 err |= (fprintf(fp, "\n") < 0);
5572 /* Sylpheed account IDs */
5574 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
5576 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
5579 if (compose->privacy_system != NULL) {
5580 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
5581 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
5582 if (compose->use_encryption) {
5584 if (!compose_warn_encryption(compose)) {
5591 if (mailac && mailac->encrypt_to_self) {
5592 GSList *tmp_list = g_slist_copy(compose->to_list);
5593 tmp_list = g_slist_append(tmp_list, compose->account->address);
5594 encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
5595 g_slist_free(tmp_list);
5597 encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
5599 if (encdata != NULL) {
5600 if (strcmp(encdata, "_DONT_ENCRYPT_")) {
5601 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5602 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
5604 } /* else we finally dont want to encrypt */
5606 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5607 /* and if encdata was null, it means there's been a problem in
5619 /* Save copy folder */
5620 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
5621 gchar *savefolderid;
5623 savefolderid = gtk_editable_get_chars(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
5624 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
5625 g_free(savefolderid);
5627 /* Save copy folder */
5628 if (compose->return_receipt) {
5629 err |= (fprintf(fp, "RRCPT:1\n") < 0);
5631 /* Message-ID of message replying to */
5632 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
5635 folderid = folder_item_get_identifier(compose->replyinfo->folder);
5636 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
5639 /* Message-ID of message forwarding to */
5640 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
5643 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
5644 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
5648 /* end of headers */
5649 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
5651 if (compose->redirect_filename != NULL) {
5652 if (compose_redirect_write_to_file(compose, fp) < 0) {
5661 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
5666 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
5670 g_warning("failed to write queue message\n");
5677 if (fclose(fp) == EOF) {
5678 FILE_OP_ERROR(tmp, "fclose");
5685 if (item && *item) {
5688 queue = account_get_special_folder(compose->account, F_QUEUE);
5691 g_warning("can't find queue folder\n");
5697 folder_item_scan(queue);
5698 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
5699 g_warning("can't queue the message\n");
5706 if (msgpath == NULL) {
5712 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
5713 compose_remove_reedit_target(compose, FALSE);
5716 if ((msgnum != NULL) && (item != NULL)) {
5724 static void compose_add_attachments(Compose *compose, MimeInfo *parent)
5727 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
5729 struct stat statbuf;
5730 gchar *type, *subtype;
5731 GtkTreeModel *model;
5734 model = gtk_tree_view_get_model(tree_view);
5736 if (!gtk_tree_model_get_iter_first(model, &iter))
5739 gtk_tree_model_get(model, &iter,
5743 mimepart = procmime_mimeinfo_new();
5744 mimepart->content = MIMECONTENT_FILE;
5745 mimepart->data.filename = g_strdup(ainfo->file);
5746 mimepart->tmp = FALSE; /* or we destroy our attachment */
5747 mimepart->offset = 0;
5749 stat(ainfo->file, &statbuf);
5750 mimepart->length = statbuf.st_size;
5752 type = g_strdup(ainfo->content_type);
5754 if (!strchr(type, '/')) {
5756 type = g_strdup("application/octet-stream");
5759 subtype = strchr(type, '/') + 1;
5760 *(subtype - 1) = '\0';
5761 mimepart->type = procmime_get_media_type(type);
5762 mimepart->subtype = g_strdup(subtype);
5765 if (mimepart->type == MIMETYPE_MESSAGE &&
5766 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
5767 mimepart->disposition = DISPOSITIONTYPE_INLINE;
5770 g_hash_table_insert(mimepart->typeparameters,
5771 g_strdup("name"), g_strdup(ainfo->name));
5772 g_hash_table_insert(mimepart->dispositionparameters,
5773 g_strdup("filename"), g_strdup(ainfo->name));
5774 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
5778 if (compose->use_signing) {
5779 if (ainfo->encoding == ENC_7BIT)
5780 ainfo->encoding = ENC_QUOTED_PRINTABLE;
5781 else if (ainfo->encoding == ENC_8BIT)
5782 ainfo->encoding = ENC_BASE64;
5785 procmime_encode_content(mimepart, ainfo->encoding);
5787 g_node_append(parent->node, mimepart->node);
5788 } while (gtk_tree_model_iter_next(model, &iter));
5791 #define IS_IN_CUSTOM_HEADER(header) \
5792 (compose->account->add_customhdr && \
5793 custom_header_find(compose->account->customhdr_list, header) != NULL)
5795 static void compose_add_headerfield_from_headerlist(Compose *compose,
5797 const gchar *fieldname,
5798 const gchar *seperator)
5800 gchar *str, *fieldname_w_colon;
5801 gboolean add_field = FALSE;
5803 ComposeHeaderEntry *headerentry;
5804 const gchar *headerentryname;
5805 const gchar *trans_fieldname;
5808 if (IS_IN_CUSTOM_HEADER(fieldname))
5811 debug_print("Adding %s-fields\n", fieldname);
5813 fieldstr = g_string_sized_new(64);
5815 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
5816 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
5818 for (list = compose->header_list; list; list = list->next) {
5819 headerentry = ((ComposeHeaderEntry *)list->data);
5820 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_BIN(headerentry->combo)->child));
5822 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
5823 str = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
5825 if (str[0] != '\0') {
5827 g_string_append(fieldstr, seperator);
5828 g_string_append(fieldstr, str);
5837 buf = g_new0(gchar, fieldstr->len * 4 + 256);
5838 compose_convert_header
5839 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
5840 strlen(fieldname) + 2, TRUE);
5841 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
5845 g_free(fieldname_w_colon);
5846 g_string_free(fieldstr, TRUE);
5851 static gchar *compose_get_header(Compose *compose)
5853 gchar buf[BUFFSIZE];
5854 const gchar *entry_str;
5858 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
5860 gchar *from_name = NULL, *from_address = NULL;
5863 g_return_val_if_fail(compose->account != NULL, NULL);
5864 g_return_val_if_fail(compose->account->address != NULL, NULL);
5866 header = g_string_sized_new(64);
5869 get_rfc822_date(buf, sizeof(buf));
5870 g_string_append_printf(header, "Date: %s\n", buf);
5874 if (compose->account->name && *compose->account->name) {
5876 QUOTE_IF_REQUIRED(buf, compose->account->name);
5877 tmp = g_strdup_printf("%s <%s>",
5878 buf, compose->account->address);
5880 tmp = g_strdup_printf("%s",
5881 compose->account->address);
5883 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
5884 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
5886 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
5887 from_address = g_strdup(compose->account->address);
5889 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5890 /* extract name and address */
5891 if (strstr(spec, " <") && strstr(spec, ">")) {
5892 from_address = g_strdup(strrchr(spec, '<')+1);
5893 *(strrchr(from_address, '>')) = '\0';
5894 from_name = g_strdup(spec);
5895 *(strrchr(from_name, '<')) = '\0';
5898 from_address = g_strdup(spec);
5905 if (from_name && *from_name) {
5906 compose_convert_header
5907 (compose, buf, sizeof(buf), from_name,
5908 strlen("From: "), TRUE);
5909 QUOTE_IF_REQUIRED(name, buf);
5911 g_string_append_printf(header, "From: %s <%s>\n",
5912 name, from_address);
5914 g_string_append_printf(header, "From: %s\n", from_address);
5917 g_free(from_address);
5920 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
5923 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
5926 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
5929 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
5932 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
5934 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
5937 compose_convert_header(compose, buf, sizeof(buf), str,
5938 strlen("Subject: "), FALSE);
5939 g_string_append_printf(header, "Subject: %s\n", buf);
5945 if (compose->account->set_domain && compose->account->domain) {
5946 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
5947 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
5948 g_snprintf(buf, sizeof(buf), "%s",
5949 strchr(compose->account->address, '@') ?
5950 strchr(compose->account->address, '@')+1 :
5951 compose->account->address);
5953 g_snprintf(buf, sizeof(buf), "%s", "");
5956 if (compose->account->gen_msgid) {
5957 generate_msgid(buf, sizeof(buf));
5958 g_string_append_printf(header, "Message-ID: <%s>\n", buf);
5959 compose->msgid = g_strdup(buf);
5961 compose->msgid = NULL;
5964 if (compose->remove_references == FALSE) {
5966 if (compose->inreplyto && compose->to_list)
5967 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
5970 if (compose->references)
5971 g_string_append_printf(header, "References: %s\n", compose->references);
5975 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
5978 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
5981 if (compose->account->organization &&
5982 strlen(compose->account->organization) &&
5983 !IS_IN_CUSTOM_HEADER("Organization")) {
5984 compose_convert_header(compose, buf, sizeof(buf),
5985 compose->account->organization,
5986 strlen("Organization: "), FALSE);
5987 g_string_append_printf(header, "Organization: %s\n", buf);
5990 /* Program version and system info */
5991 if (g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
5992 !compose->newsgroup_list) {
5993 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
5995 gtk_major_version, gtk_minor_version, gtk_micro_version,
5998 if (g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
5999 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6001 gtk_major_version, gtk_minor_version, gtk_micro_version,
6005 /* custom headers */
6006 if (compose->account->add_customhdr) {
6009 for (cur = compose->account->customhdr_list; cur != NULL;
6011 CustomHeader *chdr = (CustomHeader *)cur->data;
6013 if (custom_header_is_allowed(chdr->name)) {
6014 compose_convert_header
6015 (compose, buf, sizeof(buf),
6016 chdr->value ? chdr->value : "",
6017 strlen(chdr->name) + 2, FALSE);
6018 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6024 switch (compose->priority) {
6025 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6026 "X-Priority: 1 (Highest)\n");
6028 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6029 "X-Priority: 2 (High)\n");
6031 case PRIORITY_NORMAL: break;
6032 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6033 "X-Priority: 4 (Low)\n");
6035 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6036 "X-Priority: 5 (Lowest)\n");
6038 default: debug_print("compose: priority unknown : %d\n",
6042 /* Request Return Receipt */
6043 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To")) {
6044 if (compose->return_receipt) {
6045 if (compose->account->name
6046 && *compose->account->name) {
6047 compose_convert_header(compose, buf, sizeof(buf),
6048 compose->account->name,
6049 strlen("Disposition-Notification-To: "),
6051 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, compose->account->address);
6053 g_string_append_printf(header, "Disposition-Notification-To: %s\n", compose->account->address);
6057 /* get special headers */
6058 for (list = compose->header_list; list; list = list->next) {
6059 ComposeHeaderEntry *headerentry;
6062 gchar *headername_wcolon;
6063 const gchar *headername_trans;
6066 gboolean standard_header = FALSE;
6068 headerentry = ((ComposeHeaderEntry *)list->data);
6070 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(GTK_BIN(headerentry->combo)->child)));
6072 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6077 if (!strstr(tmp, ":")) {
6078 headername_wcolon = g_strconcat(tmp, ":", NULL);
6079 headername = g_strdup(tmp);
6081 headername_wcolon = g_strdup(tmp);
6082 headername = g_strdup(strtok(tmp, ":"));
6086 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6087 Xstrdup_a(headervalue, entry_str, return NULL);
6088 subst_char(headervalue, '\r', ' ');
6089 subst_char(headervalue, '\n', ' ');
6090 string = std_headers;
6091 while (*string != NULL) {
6092 headername_trans = prefs_common_translated_header_name(*string);
6093 if (!strcmp(headername_trans, headername_wcolon))
6094 standard_header = TRUE;
6097 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6098 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
6101 g_free(headername_wcolon);
6105 g_string_free(header, FALSE);
6110 #undef IS_IN_CUSTOM_HEADER
6112 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6113 gint header_len, gboolean addr_field)
6115 gchar *tmpstr = NULL;
6116 const gchar *out_codeset = NULL;
6118 g_return_if_fail(src != NULL);
6119 g_return_if_fail(dest != NULL);
6121 if (len < 1) return;
6123 tmpstr = g_strdup(src);
6125 subst_char(tmpstr, '\n', ' ');
6126 subst_char(tmpstr, '\r', ' ');
6129 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6130 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6131 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6136 codeconv_set_strict(TRUE);
6137 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6138 conv_get_charset_str(compose->out_encoding));
6139 codeconv_set_strict(FALSE);
6141 if (!dest || *dest == '\0') {
6142 gchar *test_conv_global_out = NULL;
6143 gchar *test_conv_reply = NULL;
6145 /* automatic mode. be automatic. */
6146 codeconv_set_strict(TRUE);
6148 out_codeset = conv_get_outgoing_charset_str();
6150 debug_print("trying to convert to %s\n", out_codeset);
6151 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6154 if (!test_conv_global_out && compose->orig_charset
6155 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6156 out_codeset = compose->orig_charset;
6157 debug_print("failure; trying to convert to %s\n", out_codeset);
6158 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6161 if (!test_conv_global_out && !test_conv_reply) {
6163 out_codeset = CS_INTERNAL;
6164 debug_print("finally using %s\n", out_codeset);
6166 g_free(test_conv_global_out);
6167 g_free(test_conv_reply);
6168 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6170 codeconv_set_strict(FALSE);
6175 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
6179 g_return_if_fail(user_data != NULL);
6181 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6182 g_strstrip(address);
6183 if (*address != '\0') {
6184 gchar *name = procheader_get_fromname(address);
6185 extract_address(address);
6186 addressbook_add_contact(name, address, NULL, NULL);
6191 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
6193 GtkWidget *menuitem;
6196 g_return_if_fail(menu != NULL);
6197 g_return_if_fail(GTK_IS_MENU_SHELL(menu));
6199 menuitem = gtk_separator_menu_item_new();
6200 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6201 gtk_widget_show(menuitem);
6203 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
6204 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6206 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
6207 g_strstrip(address);
6208 if (*address == '\0') {
6209 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
6212 g_signal_connect(G_OBJECT(menuitem), "activate",
6213 G_CALLBACK(compose_add_to_addressbook_cb), entry);
6214 gtk_widget_show(menuitem);
6217 static void compose_create_header_entry(Compose *compose)
6219 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6224 const gchar *header = NULL;
6225 ComposeHeaderEntry *headerentry;
6226 gboolean standard_header = FALSE;
6228 headerentry = g_new0(ComposeHeaderEntry, 1);
6231 combo = gtk_combo_box_entry_new_text();
6233 while(*string != NULL) {
6234 gtk_combo_box_append_text(GTK_COMBO_BOX(combo),
6235 (gchar*)prefs_common_translated_header_name(*string));
6238 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
6239 g_signal_connect(G_OBJECT(GTK_BIN(combo)->child), "grab_focus",
6240 G_CALLBACK(compose_grab_focus_cb), compose);
6241 gtk_widget_show(combo);
6242 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
6243 compose->header_nextrow, compose->header_nextrow+1,
6244 GTK_SHRINK, GTK_FILL, 0, 0);
6245 if (compose->header_last) {
6246 const gchar *last_header_entry = gtk_entry_get_text(
6247 GTK_ENTRY(GTK_BIN(compose->header_last->combo)->child));
6249 while (*string != NULL) {
6250 if (!strcmp(*string, last_header_entry))
6251 standard_header = TRUE;
6254 if (standard_header)
6255 header = gtk_entry_get_text(GTK_ENTRY(GTK_BIN(compose->header_last->combo)->child));
6257 if (!compose->header_last || !standard_header) {
6258 switch(compose->account->protocol) {
6260 header = prefs_common_translated_header_name("Newsgroups:");
6263 header = prefs_common_translated_header_name("To:");
6268 gtk_entry_set_text(GTK_ENTRY(GTK_BIN(combo)->child), header);
6270 g_signal_connect_after(G_OBJECT(GTK_BIN(combo)->child), "grab_focus",
6271 G_CALLBACK(compose_grab_focus_cb), compose);
6274 entry = gtk_entry_new();
6275 gtk_widget_show(entry);
6276 gtk_tooltips_set_tip(compose->tooltips, entry,
6277 _("Use <tab> to autocomplete from addressbook"), NULL);
6278 gtk_table_attach(GTK_TABLE(compose->header_table), entry, 1, 2,
6279 compose->header_nextrow, compose->header_nextrow+1,
6280 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
6282 g_signal_connect(G_OBJECT(entry), "key-press-event",
6283 G_CALLBACK(compose_headerentry_key_press_event_cb),
6285 g_signal_connect(G_OBJECT(entry), "changed",
6286 G_CALLBACK(compose_headerentry_changed_cb),
6288 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
6289 G_CALLBACK(compose_grab_focus_cb), compose);
6292 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6293 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6294 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6295 g_signal_connect(G_OBJECT(entry), "drag_data_received",
6296 G_CALLBACK(compose_header_drag_received_cb),
6298 g_signal_connect(G_OBJECT(entry), "drag-drop",
6299 G_CALLBACK(compose_drag_drop),
6301 g_signal_connect(G_OBJECT(entry), "populate-popup",
6302 G_CALLBACK(compose_entry_popup_extend),
6305 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
6307 headerentry->compose = compose;
6308 headerentry->combo = combo;
6309 headerentry->entry = entry;
6310 headerentry->headernum = compose->header_nextrow;
6312 compose->header_nextrow++;
6313 compose->header_last = headerentry;
6314 compose->header_list =
6315 g_slist_append(compose->header_list,
6319 static void compose_add_header_entry(Compose *compose, const gchar *header, gchar *text)
6321 ComposeHeaderEntry *last_header;
6323 last_header = compose->header_last;
6325 gtk_entry_set_text(GTK_ENTRY(GTK_BIN(last_header->combo)->child), header);
6326 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
6329 static void compose_remove_header_entries(Compose *compose)
6332 for (list = compose->header_list; list; list = list->next) {
6333 ComposeHeaderEntry *headerentry =
6334 (ComposeHeaderEntry *)list->data;
6335 gtk_widget_destroy(headerentry->combo);
6336 gtk_widget_destroy(headerentry->entry);
6337 g_free(headerentry);
6339 compose->header_last = NULL;
6340 g_slist_free(compose->header_list);
6341 compose->header_list = NULL;
6342 compose->header_nextrow = 1;
6343 compose_create_header_entry(compose);
6346 static GtkWidget *compose_create_header(Compose *compose)
6348 GtkWidget *from_optmenu_hbox;
6349 GtkWidget *header_scrolledwin;
6350 GtkWidget *header_table;
6354 /* header labels and entries */
6355 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
6356 gtk_widget_show(header_scrolledwin);
6357 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
6359 header_table = gtk_table_new(2, 2, FALSE);
6360 gtk_widget_show(header_table);
6361 gtk_container_set_border_width(GTK_CONTAINER(header_table), BORDER_WIDTH);
6362 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
6363 gtk_viewport_set_shadow_type(GTK_VIEWPORT(GTK_BIN(header_scrolledwin)->child), GTK_SHADOW_NONE);
6366 /* option menu for selecting accounts */
6367 from_optmenu_hbox = compose_account_option_menu_create(compose);
6368 gtk_table_attach(GTK_TABLE(header_table), from_optmenu_hbox,
6369 0, 2, count, count + 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
6372 compose->header_table = header_table;
6373 compose->header_list = NULL;
6374 compose->header_nextrow = count;
6376 compose_create_header_entry(compose);
6378 compose->table = NULL;
6380 return header_scrolledwin ;
6383 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
6385 Compose *compose = (Compose *)data;
6386 GdkEventButton event;
6389 event.time = gtk_get_current_event_time();
6391 return attach_button_pressed(compose->attach_clist, &event, compose);
6394 static GtkWidget *compose_create_attach(Compose *compose)
6396 GtkWidget *attach_scrwin;
6397 GtkWidget *attach_clist;
6399 GtkListStore *store;
6400 GtkCellRenderer *renderer;
6401 GtkTreeViewColumn *column;
6402 GtkTreeSelection *selection;
6404 /* attachment list */
6405 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
6406 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
6407 GTK_POLICY_AUTOMATIC,
6408 GTK_POLICY_AUTOMATIC);
6409 gtk_widget_set_size_request(attach_scrwin, -1, 80);
6411 store = gtk_list_store_new(N_ATTACH_COLS,
6416 G_TYPE_AUTO_POINTER,
6418 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
6419 (GTK_TREE_MODEL(store)));
6420 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
6421 g_object_unref(store);
6423 renderer = gtk_cell_renderer_text_new();
6424 column = gtk_tree_view_column_new_with_attributes
6425 (_("Mime type"), renderer, "text",
6426 COL_MIMETYPE, NULL);
6427 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6429 renderer = gtk_cell_renderer_text_new();
6430 column = gtk_tree_view_column_new_with_attributes
6431 (_("Size"), renderer, "text",
6433 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6435 renderer = gtk_cell_renderer_text_new();
6436 column = gtk_tree_view_column_new_with_attributes
6437 (_("Name"), renderer, "text",
6439 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6441 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
6442 prefs_common.use_stripes_everywhere);
6443 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
6444 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
6446 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
6447 G_CALLBACK(attach_selected), compose);
6448 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
6449 G_CALLBACK(attach_button_pressed), compose);
6451 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
6452 G_CALLBACK(popup_attach_button_pressed), compose);
6454 gtk_widget_tap_and_hold_setup(GTK_WIDGET(attach_clist), NULL, NULL,
6455 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
6456 g_signal_connect(G_OBJECT(attach_clist), "tap-and-hold",
6457 G_CALLBACK(popup_attach_button_pressed), compose);
6459 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
6460 G_CALLBACK(attach_key_pressed), compose);
6463 gtk_drag_dest_set(attach_clist,
6464 GTK_DEST_DEFAULT_ALL, compose_mime_types,
6465 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6466 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6467 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
6468 G_CALLBACK(compose_attach_drag_received_cb),
6470 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
6471 G_CALLBACK(compose_drag_drop),
6474 compose->attach_scrwin = attach_scrwin;
6475 compose->attach_clist = attach_clist;
6477 return attach_scrwin;
6480 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
6481 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
6483 static GtkWidget *compose_create_others(Compose *compose)
6486 GtkWidget *savemsg_checkbtn;
6487 GtkWidget *savemsg_entry;
6488 GtkWidget *savemsg_select;
6491 gchar *folderidentifier;
6493 /* Table for settings */
6494 table = gtk_table_new(3, 1, FALSE);
6495 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
6496 gtk_widget_show(table);
6497 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
6500 /* Save Message to folder */
6501 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
6502 gtk_widget_show(savemsg_checkbtn);
6503 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6504 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6505 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
6507 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
6508 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
6510 savemsg_entry = gtk_entry_new();
6511 gtk_widget_show(savemsg_entry);
6512 gtk_table_attach_defaults(GTK_TABLE(table), savemsg_entry, 1, 2, rowcount, rowcount + 1);
6513 gtk_editable_set_editable(GTK_EDITABLE(savemsg_entry), prefs_common.savemsg);
6514 g_signal_connect_after(G_OBJECT(savemsg_entry), "grab_focus",
6515 G_CALLBACK(compose_grab_focus_cb), compose);
6516 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6517 folderidentifier = folder_item_get_identifier(account_get_special_folder
6518 (compose->account, F_OUTBOX));
6519 gtk_entry_set_text(GTK_ENTRY(savemsg_entry), folderidentifier);
6520 g_free(folderidentifier);
6523 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
6524 gtk_widget_show(savemsg_select);
6525 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6526 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
6527 G_CALLBACK(compose_savemsg_select_cb),
6532 compose->savemsg_checkbtn = savemsg_checkbtn;
6533 compose->savemsg_entry = savemsg_entry;
6538 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
6540 gtk_editable_set_editable(GTK_EDITABLE(compose->savemsg_entry),
6541 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
6544 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
6549 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE);
6552 path = folder_item_get_identifier(dest);
6554 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), path);
6558 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
6559 GdkAtom clip, GtkTextIter *insert_place);
6562 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
6566 GtkTextBuffer *buffer = GTK_TEXT_VIEW(text)->buffer;
6568 if (event->button == 3) {
6570 GtkTextIter sel_start, sel_end;
6571 gboolean stuff_selected;
6573 /* move the cursor to allow GtkAspell to check the word
6574 * under the mouse */
6575 if (event->x && event->y) {
6576 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6577 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6579 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6582 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
6583 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
6586 stuff_selected = gtk_text_buffer_get_selection_bounds(
6588 &sel_start, &sel_end);
6590 gtk_text_buffer_place_cursor (buffer, &iter);
6591 /* reselect stuff */
6593 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
6594 gtk_text_buffer_select_range(buffer,
6595 &sel_start, &sel_end);
6597 return FALSE; /* pass the event so that the right-click goes through */
6600 if (event->button == 2) {
6605 /* get the middle-click position to paste at the correct place */
6606 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6607 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6609 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6612 entry_paste_clipboard(compose, text,
6613 prefs_common.linewrap_pastes,
6614 GDK_SELECTION_PRIMARY, &iter);
6622 static void compose_spell_menu_changed(void *data)
6624 Compose *compose = (Compose *)data;
6626 GtkWidget *menuitem;
6627 GtkWidget *parent_item;
6628 GtkMenu *menu = GTK_MENU(gtk_menu_new());
6629 GtkItemFactory *ifactory = gtk_item_factory_from_widget(compose->menubar);
6632 if (compose->gtkaspell == NULL)
6635 parent_item = gtk_item_factory_get_item(ifactory,
6636 "/Spelling/Options");
6638 /* setting the submenu removes /Spelling/Options from the factory
6639 * so we need to save it */
6641 if (parent_item == NULL) {
6642 parent_item = compose->aspell_options_menu;
6643 gtk_menu_item_remove_submenu(GTK_MENU_ITEM(parent_item));
6645 compose->aspell_options_menu = parent_item;
6647 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
6649 spell_menu = g_slist_reverse(spell_menu);
6650 for (items = spell_menu;
6651 items; items = items->next) {
6652 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
6653 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
6654 gtk_widget_show(GTK_WIDGET(menuitem));
6656 g_slist_free(spell_menu);
6658 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
6663 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
6665 Compose *compose = (Compose *)data;
6666 GdkEventButton event;
6669 event.time = gtk_get_current_event_time();
6673 return text_clicked(compose->text, &event, compose);
6676 static gboolean compose_force_window_origin = TRUE;
6677 static Compose *compose_create(PrefsAccount *account,
6686 GtkWidget *handlebox;
6688 GtkWidget *notebook;
6690 GtkWidget *attach_hbox;
6691 GtkWidget *attach_lab1;
6692 GtkWidget *attach_lab2;
6697 GtkWidget *subject_hbox;
6698 GtkWidget *subject_frame;
6699 GtkWidget *subject_entry;
6703 GtkWidget *edit_vbox;
6704 GtkWidget *ruler_hbox;
6706 GtkWidget *scrolledwin;
6708 GtkTextBuffer *buffer;
6709 GtkClipboard *clipboard;
6711 UndoMain *undostruct;
6713 gchar *titles[N_ATTACH_COLS];
6714 guint n_menu_entries;
6715 GtkWidget *popupmenu;
6716 GtkItemFactory *popupfactory;
6717 GtkItemFactory *ifactory;
6718 GtkWidget *tmpl_menu;
6720 GtkWidget *menuitem;
6723 GtkAspell * gtkaspell = NULL;
6726 static GdkGeometry geometry;
6728 g_return_val_if_fail(account != NULL, NULL);
6730 debug_print("Creating compose window...\n");
6731 compose = g_new0(Compose, 1);
6733 titles[COL_MIMETYPE] = _("MIME type");
6734 titles[COL_SIZE] = _("Size");
6735 titles[COL_NAME] = _("Name");
6737 compose->batch = batch;
6738 compose->account = account;
6739 compose->folder = folder;
6741 compose->mutex = g_mutex_new();
6742 compose->set_cursor_pos = -1;
6744 compose->tooltips = gtk_tooltips_new();
6746 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
6748 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
6749 gtk_widget_set_size_request(window, -1, prefs_common.compose_height);
6751 if (!geometry.max_width) {
6752 geometry.max_width = gdk_screen_width();
6753 geometry.max_height = gdk_screen_height();
6756 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
6757 &geometry, GDK_HINT_MAX_SIZE);
6758 if (!geometry.min_width) {
6759 geometry.min_width = 600;
6760 geometry.min_height = 480;
6762 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
6763 &geometry, GDK_HINT_MIN_SIZE);
6766 if (compose_force_window_origin)
6767 gtk_widget_set_uposition(window, prefs_common.compose_x,
6768 prefs_common.compose_y);
6770 g_signal_connect(G_OBJECT(window), "delete_event",
6771 G_CALLBACK(compose_delete_cb), compose);
6772 MANAGE_WINDOW_SIGNALS_CONNECT(window);
6773 gtk_widget_realize(window);
6775 gtkut_widget_set_composer_icon(window);
6777 vbox = gtk_vbox_new(FALSE, 0);
6778 gtk_container_add(GTK_CONTAINER(window), vbox);
6780 n_menu_entries = sizeof(compose_entries) / sizeof(compose_entries[0]);
6781 menubar = menubar_create(window, compose_entries,
6782 n_menu_entries, "<Compose>", compose);
6783 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
6785 if (prefs_common.toolbar_detachable) {
6786 handlebox = gtk_handle_box_new();
6788 handlebox = gtk_hbox_new(FALSE, 0);
6790 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
6792 gtk_widget_realize(handlebox);
6794 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, window,
6797 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
6801 vbox2 = gtk_vbox_new(FALSE, 2);
6802 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
6803 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
6806 notebook = gtk_notebook_new();
6807 gtk_widget_set_size_request(notebook, -1, 130);
6808 gtk_widget_show(notebook);
6810 /* header labels and entries */
6811 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6812 compose_create_header(compose),
6813 gtk_label_new_with_mnemonic(_("Hea_der")));
6814 /* attachment list */
6815 attach_hbox = gtk_hbox_new(FALSE, 0);
6816 gtk_widget_show(attach_hbox);
6818 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
6819 gtk_widget_show(attach_lab1);
6820 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
6822 attach_lab2 = gtk_label_new("");
6823 gtk_widget_show(attach_lab2);
6824 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
6826 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6827 compose_create_attach(compose),
6830 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6831 compose_create_others(compose),
6832 gtk_label_new_with_mnemonic(_("Othe_rs")));
6835 subject_hbox = gtk_hbox_new(FALSE, 0);
6836 gtk_widget_show(subject_hbox);
6838 subject_frame = gtk_frame_new(NULL);
6839 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
6840 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
6841 gtk_widget_show(subject_frame);
6843 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
6844 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
6845 gtk_widget_show(subject);
6847 label = gtk_label_new(_("Subject:"));
6848 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
6849 gtk_widget_show(label);
6851 subject_entry = gtk_entry_new();
6852 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
6853 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
6854 G_CALLBACK(compose_grab_focus_cb), compose);
6855 gtk_widget_show(subject_entry);
6856 compose->subject_entry = subject_entry;
6857 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
6859 edit_vbox = gtk_vbox_new(FALSE, 0);
6861 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
6864 ruler_hbox = gtk_hbox_new(FALSE, 0);
6865 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
6867 ruler = gtk_shruler_new();
6868 gtk_ruler_set_range(GTK_RULER(ruler), 0.0, 100.0, 1.0, 100.0);
6869 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
6873 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
6874 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
6875 GTK_POLICY_AUTOMATIC,
6876 GTK_POLICY_AUTOMATIC);
6877 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
6879 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
6880 gtk_widget_set_size_request(scrolledwin, prefs_common.compose_width, -1);
6882 text = gtk_text_view_new();
6883 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
6884 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
6885 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
6886 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
6887 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
6889 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
6891 g_signal_connect_after(G_OBJECT(text), "size_allocate",
6892 G_CALLBACK(compose_edit_size_alloc),
6894 g_signal_connect(G_OBJECT(buffer), "changed",
6895 G_CALLBACK(compose_changed_cb), compose);
6896 g_signal_connect(G_OBJECT(text), "grab_focus",
6897 G_CALLBACK(compose_grab_focus_cb), compose);
6898 g_signal_connect(G_OBJECT(buffer), "insert_text",
6899 G_CALLBACK(text_inserted), compose);
6900 g_signal_connect(G_OBJECT(text), "button_press_event",
6901 G_CALLBACK(text_clicked), compose);
6903 g_signal_connect(G_OBJECT(text), "popup-menu",
6904 G_CALLBACK(compose_popup_menu), compose);
6906 gtk_widget_tap_and_hold_setup(GTK_WIDGET(text), NULL, NULL,
6907 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
6908 g_signal_connect(G_OBJECT(text), "tap-and-hold",
6909 G_CALLBACK(compose_popup_menu), compose);
6911 g_signal_connect(G_OBJECT(subject_entry), "changed",
6912 G_CALLBACK(compose_changed_cb), compose);
6915 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6916 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6917 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6918 g_signal_connect(G_OBJECT(text), "drag_data_received",
6919 G_CALLBACK(compose_insert_drag_received_cb),
6921 g_signal_connect(G_OBJECT(text), "drag-drop",
6922 G_CALLBACK(compose_drag_drop),
6924 gtk_widget_show_all(vbox);
6926 /* pane between attach clist and text */
6927 paned = gtk_vpaned_new();
6928 gtk_paned_set_gutter_size(GTK_PANED(paned), 12);
6929 gtk_container_add(GTK_CONTAINER(vbox2), paned);
6931 if( maemo_mainwindow_is_fullscreen(mainwindow_get_mainwindow()->window) )
6932 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 300 : 280);
6934 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 250 : 230);
6936 gtk_paned_add1(GTK_PANED(paned), notebook);
6937 gtk_paned_add2(GTK_PANED(paned), edit_vbox);
6938 gtk_widget_show_all(paned);
6941 if (prefs_common.textfont) {
6942 PangoFontDescription *font_desc;
6944 font_desc = pango_font_description_from_string
6945 (prefs_common.textfont);
6947 gtk_widget_modify_font(text, font_desc);
6948 pango_font_description_free(font_desc);
6952 n_entries = sizeof(compose_popup_entries) /
6953 sizeof(compose_popup_entries[0]);
6954 popupmenu = menu_create_items(compose_popup_entries, n_entries,
6955 "<Compose>", &popupfactory,
6958 ifactory = gtk_item_factory_from_widget(menubar);
6959 menu_set_sensitive(ifactory, "/Edit/Undo", FALSE);
6960 menu_set_sensitive(ifactory, "/Edit/Redo", FALSE);
6961 menu_set_sensitive(ifactory, "/Options/Remove references", FALSE);
6963 tmpl_menu = gtk_item_factory_get_item(ifactory, "/Tools/Template");
6965 undostruct = undo_init(text);
6966 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
6969 address_completion_start(window);
6971 compose->window = window;
6972 compose->vbox = vbox;
6973 compose->menubar = menubar;
6974 compose->handlebox = handlebox;
6976 compose->vbox2 = vbox2;
6978 compose->paned = paned;
6980 compose->attach_label = attach_lab2;
6982 compose->notebook = notebook;
6983 compose->edit_vbox = edit_vbox;
6984 compose->ruler_hbox = ruler_hbox;
6985 compose->ruler = ruler;
6986 compose->scrolledwin = scrolledwin;
6987 compose->text = text;
6989 compose->focused_editable = NULL;
6991 compose->popupmenu = popupmenu;
6992 compose->popupfactory = popupfactory;
6994 compose->tmpl_menu = tmpl_menu;
6996 compose->mode = mode;
6997 compose->rmode = mode;
6999 compose->targetinfo = NULL;
7000 compose->replyinfo = NULL;
7001 compose->fwdinfo = NULL;
7003 compose->replyto = NULL;
7005 compose->bcc = NULL;
7006 compose->followup_to = NULL;
7008 compose->ml_post = NULL;
7010 compose->inreplyto = NULL;
7011 compose->references = NULL;
7012 compose->msgid = NULL;
7013 compose->boundary = NULL;
7015 compose->autowrap = prefs_common.autowrap;
7017 compose->use_signing = FALSE;
7018 compose->use_encryption = FALSE;
7019 compose->privacy_system = NULL;
7021 compose->modified = FALSE;
7023 compose->return_receipt = FALSE;
7025 compose->to_list = NULL;
7026 compose->newsgroup_list = NULL;
7028 compose->undostruct = undostruct;
7030 compose->sig_str = NULL;
7032 compose->exteditor_file = NULL;
7033 compose->exteditor_pid = -1;
7034 compose->exteditor_tag = -1;
7035 compose->draft_timeout_tag = -2; /* inhibit auto-drafting while loading */
7038 menu_set_sensitive(ifactory, "/Spelling", FALSE);
7039 if (mode != COMPOSE_REDIRECT) {
7040 if (prefs_common.enable_aspell && prefs_common.dictionary &&
7041 strcmp(prefs_common.dictionary, "")) {
7042 gtkaspell = gtkaspell_new(prefs_common.aspell_path,
7043 prefs_common.dictionary,
7044 prefs_common.alt_dictionary,
7045 conv_get_locale_charset_str(),
7046 prefs_common.misspelled_col,
7047 prefs_common.check_while_typing,
7048 prefs_common.recheck_when_changing_dict,
7049 prefs_common.use_alternate,
7050 prefs_common.use_both_dicts,
7051 GTK_TEXT_VIEW(text),
7052 GTK_WINDOW(compose->window),
7053 compose_spell_menu_changed,
7056 alertpanel_error(_("Spell checker could not "
7058 gtkaspell_checkers_strerror());
7059 gtkaspell_checkers_reset_error();
7061 if (!gtkaspell_set_sug_mode(gtkaspell,
7062 prefs_common.aspell_sugmode)) {
7063 debug_print("Aspell: could not set "
7064 "suggestion mode %s\n",
7065 gtkaspell_checkers_strerror());
7066 gtkaspell_checkers_reset_error();
7069 menu_set_sensitive(ifactory, "/Spelling", TRUE);
7073 compose->gtkaspell = gtkaspell;
7074 compose_spell_menu_changed(compose);
7077 compose_select_account(compose, account, TRUE);
7079 menu_set_active(ifactory, "/Edit/Auto wrapping", prefs_common.autowrap);
7080 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
7081 compose_entry_append(compose, account->auto_cc, COMPOSE_CC);
7083 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
7084 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC);
7086 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
7087 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO);
7089 menu_set_sensitive(ifactory, "/Options/Reply mode", compose->mode == COMPOSE_REPLY);
7091 if (account->protocol != A_NNTP)
7092 gtk_entry_set_text(GTK_ENTRY(GTK_BIN(compose->header_last->combo)->child),
7093 prefs_common_translated_header_name("To:"));
7095 gtk_entry_set_text(GTK_ENTRY(GTK_BIN(compose->header_last->combo)->child),
7096 prefs_common_translated_header_name("Newsgroups:"));
7098 addressbook_set_target_compose(compose);
7100 if (mode != COMPOSE_REDIRECT)
7101 compose_set_template_menu(compose);
7103 menuitem = gtk_item_factory_get_item(ifactory, "/Tools/Template");
7104 menu_set_sensitive(ifactory, "/Tools/Template", FALSE);
7107 compose_list = g_list_append(compose_list, compose);
7109 if (!prefs_common.show_ruler)
7110 gtk_widget_hide(ruler_hbox);
7112 menuitem = gtk_item_factory_get_item(ifactory, "/Tools/Show ruler");
7113 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
7114 prefs_common.show_ruler);
7117 compose->priority = PRIORITY_NORMAL;
7118 compose_update_priority_menu_item(compose);
7120 compose_set_out_encoding(compose);
7123 compose_update_actions_menu(compose);
7125 /* Privacy Systems menu */
7126 compose_update_privacy_systems_menu(compose);
7128 activate_privacy_system(compose, account, TRUE);
7129 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
7131 gtk_widget_realize(window);
7133 gtk_widget_show(window);
7135 maemo_window_full_screen_if_needed(GTK_WINDOW(window));
7136 maemo_connect_key_press_to_mainwindow(GTK_WINDOW(window));
7143 static GtkWidget *compose_account_option_menu_create(Compose *compose)
7148 GtkWidget *optmenubox;
7151 GtkWidget *from_name = NULL;
7153 gint num = 0, def_menu = 0;
7155 accounts = account_get_list();
7156 g_return_val_if_fail(accounts != NULL, NULL);
7158 optmenubox = gtk_event_box_new();
7159 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
7160 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
7162 hbox = gtk_hbox_new(FALSE, 6);
7163 from_name = gtk_entry_new();
7165 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
7166 G_CALLBACK(compose_grab_focus_cb), compose);
7168 for (; accounts != NULL; accounts = accounts->next, num++) {
7169 PrefsAccount *ac = (PrefsAccount *)accounts->data;
7170 gchar *name, *from = NULL;
7172 if (ac == compose->account) def_menu = num;
7174 name = g_markup_printf_escaped(_("From: <i>%s</i>"),
7177 if (ac == compose->account) {
7178 if (ac->name && *ac->name) {
7180 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
7181 from = g_strdup_printf("%s <%s>",
7183 gtk_entry_set_text(GTK_ENTRY(from_name), from);
7185 from = g_strdup_printf("%s",
7187 gtk_entry_set_text(GTK_ENTRY(from_name), from);
7190 COMBOBOX_ADD(menu, name, ac->account_id);
7195 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
7197 g_signal_connect(G_OBJECT(optmenu), "changed",
7198 G_CALLBACK(account_activated),
7200 g_signal_connect(G_OBJECT(from_name), "populate-popup",
7201 G_CALLBACK(compose_entry_popup_extend),
7204 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
7205 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
7207 gtk_tooltips_set_tip(compose->tooltips, optmenubox,
7208 _("Account to use for this email"), NULL);
7209 gtk_tooltips_set_tip(compose->tooltips, from_name,
7210 _("Sender address to be used"), NULL);
7212 compose->from_name = from_name;
7217 static void compose_set_priority_cb(gpointer data,
7221 Compose *compose = (Compose *) data;
7222 compose->priority = action;
7225 static void compose_reply_change_mode(gpointer data,
7229 Compose *compose = (Compose *) data;
7230 gboolean was_modified = compose->modified;
7232 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
7234 g_return_if_fail(compose->replyinfo != NULL);
7236 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
7238 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
7240 if (action == COMPOSE_REPLY_TO_ALL)
7242 if (action == COMPOSE_REPLY_TO_SENDER)
7244 if (action == COMPOSE_REPLY_TO_LIST)
7247 compose_remove_header_entries(compose);
7248 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
7249 if (compose->account->set_autocc && compose->account->auto_cc)
7250 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC);
7252 if (compose->account->set_autobcc && compose->account->auto_bcc)
7253 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC);
7255 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
7256 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO);
7257 compose_show_first_last_header(compose, TRUE);
7258 compose->modified = was_modified;
7259 compose_set_title(compose);
7262 static void compose_update_priority_menu_item(Compose * compose)
7264 GtkItemFactory *ifactory;
7265 GtkWidget *menuitem = NULL;
7267 ifactory = gtk_item_factory_from_widget(compose->menubar);
7269 switch (compose->priority) {
7270 case PRIORITY_HIGHEST:
7271 menuitem = gtk_item_factory_get_item
7272 (ifactory, "/Options/Priority/Highest");
7275 menuitem = gtk_item_factory_get_item
7276 (ifactory, "/Options/Priority/High");
7278 case PRIORITY_NORMAL:
7279 menuitem = gtk_item_factory_get_item
7280 (ifactory, "/Options/Priority/Normal");
7283 menuitem = gtk_item_factory_get_item
7284 (ifactory, "/Options/Priority/Low");
7286 case PRIORITY_LOWEST:
7287 menuitem = gtk_item_factory_get_item
7288 (ifactory, "/Options/Priority/Lowest");
7291 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
7294 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
7296 Compose *compose = (Compose *) data;
7298 GtkItemFactory *ifactory;
7299 gboolean can_sign = FALSE, can_encrypt = FALSE;
7301 g_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
7303 if (!GTK_CHECK_MENU_ITEM(widget)->active)
7306 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
7307 g_free(compose->privacy_system);
7308 compose->privacy_system = NULL;
7309 if (systemid != NULL) {
7310 compose->privacy_system = g_strdup(systemid);
7312 can_sign = privacy_system_can_sign(systemid);
7313 can_encrypt = privacy_system_can_encrypt(systemid);
7316 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
7318 ifactory = gtk_item_factory_from_widget(compose->menubar);
7319 menu_set_sensitive(ifactory, "/Options/Sign", can_sign);
7320 menu_set_sensitive(ifactory, "/Options/Encrypt", can_encrypt);
7323 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
7325 static gchar *branch_path = "/Options/Privacy System";
7326 GtkItemFactory *ifactory;
7327 GtkWidget *menuitem = NULL;
7329 gboolean can_sign = FALSE, can_encrypt = FALSE;
7330 gboolean found = FALSE;
7332 ifactory = gtk_item_factory_from_widget(compose->menubar);
7334 if (compose->privacy_system != NULL) {
7336 menuitem = gtk_item_factory_get_widget(ifactory, branch_path);
7337 g_return_if_fail(menuitem != NULL);
7339 amenu = GTK_MENU_SHELL(menuitem)->children;
7341 while (amenu != NULL) {
7342 GList *alist = amenu->next;
7344 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
7345 if (systemid != NULL) {
7346 if (strcmp(systemid, compose->privacy_system) == 0) {
7347 menuitem = GTK_WIDGET(amenu->data);
7349 can_sign = privacy_system_can_sign(systemid);
7350 can_encrypt = privacy_system_can_encrypt(systemid);
7354 } else if (strlen(compose->privacy_system) == 0) {
7355 menuitem = GTK_WIDGET(amenu->data);
7358 can_encrypt = FALSE;
7365 if (menuitem != NULL)
7366 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
7368 if (warn && !found && strlen(compose->privacy_system)) {
7369 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
7370 "will not be able to sign or encrypt this message."),
7371 compose->privacy_system);
7375 menu_set_sensitive(ifactory, "/Options/Sign", can_sign);
7376 menu_set_sensitive(ifactory, "/Options/Encrypt", can_encrypt);
7379 static void compose_set_out_encoding(Compose *compose)
7381 GtkItemFactoryEntry *entry;
7382 GtkItemFactory *ifactory;
7383 CharSet out_encoding;
7384 gchar *path, *p, *q;
7387 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
7388 ifactory = gtk_item_factory_from_widget(compose->menubar);
7390 for (entry = compose_entries; entry->callback != compose_address_cb;
7392 if (entry->callback == compose_set_encoding_cb &&
7393 (CharSet)entry->callback_action == out_encoding) {
7394 p = q = path = g_strdup(entry->path);
7406 item = gtk_item_factory_get_item(ifactory, path);
7407 gtk_widget_activate(item);
7414 static void compose_set_template_menu(Compose *compose)
7416 GSList *tmpl_list, *cur;
7420 tmpl_list = template_get_config();
7422 menu = gtk_menu_new();
7424 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
7425 Template *tmpl = (Template *)cur->data;
7427 item = gtk_menu_item_new_with_label(tmpl->name);
7428 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
7429 g_signal_connect(G_OBJECT(item), "activate",
7430 G_CALLBACK(compose_template_activate_cb),
7432 g_object_set_data(G_OBJECT(item), "template", tmpl);
7433 gtk_widget_show(item);
7436 gtk_widget_show(menu);
7437 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
7440 void compose_update_actions_menu(Compose *compose)
7442 GtkItemFactory *ifactory;
7444 ifactory = gtk_item_factory_from_widget(compose->menubar);
7445 action_update_compose_menu(ifactory, "/Tools/Actions", compose);
7448 static void compose_update_privacy_systems_menu(Compose *compose)
7450 static gchar *branch_path = "/Options/Privacy System";
7451 GtkItemFactory *ifactory;
7452 GtkWidget *menuitem;
7453 GSList *systems, *cur;
7456 GtkWidget *system_none;
7459 ifactory = gtk_item_factory_from_widget(compose->menubar);
7461 /* remove old entries */
7462 menuitem = gtk_item_factory_get_widget(ifactory, branch_path);
7463 g_return_if_fail(menuitem != NULL);
7465 amenu = GTK_MENU_SHELL(menuitem)->children->next;
7466 while (amenu != NULL) {
7467 GList *alist = amenu->next;
7468 gtk_widget_destroy(GTK_WIDGET(amenu->data));
7472 system_none = gtk_item_factory_get_widget(ifactory,
7473 "/Options/Privacy System/None");
7475 g_signal_connect(G_OBJECT(system_none), "activate",
7476 G_CALLBACK(compose_set_privacy_system_cb), compose);
7478 systems = privacy_get_system_ids();
7479 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
7480 gchar *systemid = cur->data;
7482 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
7483 widget = gtk_radio_menu_item_new_with_label(group,
7484 privacy_system_get_name(systemid));
7485 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
7486 g_strdup(systemid), g_free);
7487 g_signal_connect(G_OBJECT(widget), "activate",
7488 G_CALLBACK(compose_set_privacy_system_cb), compose);
7490 gtk_menu_append(GTK_MENU(system_none->parent), widget);
7491 gtk_widget_show(widget);
7494 g_slist_free(systems);
7497 void compose_reflect_prefs_all(void)
7502 for (cur = compose_list; cur != NULL; cur = cur->next) {
7503 compose = (Compose *)cur->data;
7504 compose_set_template_menu(compose);
7508 void compose_reflect_prefs_pixmap_theme(void)
7513 for (cur = compose_list; cur != NULL; cur = cur->next) {
7514 compose = (Compose *)cur->data;
7515 toolbar_update(TOOLBAR_COMPOSE, compose);
7519 static const gchar *compose_quote_char_from_context(Compose *compose)
7521 const gchar *qmark = NULL;
7523 g_return_val_if_fail(compose != NULL, NULL);
7525 switch (compose->mode) {
7526 /* use forward-specific quote char */
7527 case COMPOSE_FORWARD:
7528 case COMPOSE_FORWARD_AS_ATTACH:
7529 case COMPOSE_FORWARD_INLINE:
7530 if (compose->folder && compose->folder->prefs &&
7531 compose->folder->prefs->forward_with_format)
7532 qmark = compose->folder->prefs->forward_quotemark;
7533 else if (compose->account->forward_with_format)
7534 qmark = compose->account->forward_quotemark;
7536 qmark = prefs_common.fw_quotemark;
7539 /* use reply-specific quote char in all other modes */
7541 if (compose->folder && compose->folder->prefs &&
7542 compose->folder->prefs->reply_with_format)
7543 qmark = compose->folder->prefs->reply_quotemark;
7544 else if (compose->account->reply_with_format)
7545 qmark = compose->account->reply_quotemark;
7547 qmark = prefs_common.quotemark;
7551 if (qmark == NULL || *qmark == '\0')
7557 static void compose_template_apply(Compose *compose, Template *tmpl,
7561 GtkTextBuffer *buffer;
7565 gchar *parsed_str = NULL;
7566 gint cursor_pos = 0;
7567 const gchar *err_msg = _("Template body format error at line %d.");
7570 /* process the body */
7572 text = GTK_TEXT_VIEW(compose->text);
7573 buffer = gtk_text_view_get_buffer(text);
7576 qmark = compose_quote_char_from_context(compose);
7578 if (compose->replyinfo != NULL) {
7581 gtk_text_buffer_set_text(buffer, "", -1);
7582 mark = gtk_text_buffer_get_insert(buffer);
7583 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7585 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
7586 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
7588 } else if (compose->fwdinfo != NULL) {
7591 gtk_text_buffer_set_text(buffer, "", -1);
7592 mark = gtk_text_buffer_get_insert(buffer);
7593 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7595 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
7596 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
7599 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
7601 GtkTextIter start, end;
7604 gtk_text_buffer_get_start_iter(buffer, &start);
7605 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
7606 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
7608 /* clear the buffer now */
7610 gtk_text_buffer_set_text(buffer, "", -1);
7612 parsed_str = compose_quote_fmt(compose, dummyinfo,
7613 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
7614 procmsg_msginfo_free( dummyinfo );
7620 gtk_text_buffer_set_text(buffer, "", -1);
7621 mark = gtk_text_buffer_get_insert(buffer);
7622 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7625 if (replace && parsed_str && compose->account->auto_sig)
7626 compose_insert_sig(compose, FALSE);
7628 if (replace && parsed_str) {
7629 gtk_text_buffer_get_start_iter(buffer, &iter);
7630 gtk_text_buffer_place_cursor(buffer, &iter);
7634 cursor_pos = quote_fmt_get_cursor_pos();
7635 compose->set_cursor_pos = cursor_pos;
7636 if (cursor_pos == -1)
7638 gtk_text_buffer_get_start_iter(buffer, &iter);
7639 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
7640 gtk_text_buffer_place_cursor(buffer, &iter);
7643 /* process the other fields */
7645 compose_template_apply_fields(compose, tmpl);
7646 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
7647 quote_fmt_reset_vartable();
7648 compose_changed_cb(NULL, compose);
7651 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
7653 MsgInfo* dummyinfo = NULL;
7654 MsgInfo *msginfo = NULL;
7657 if (compose->replyinfo != NULL)
7658 msginfo = compose->replyinfo;
7659 else if (compose->fwdinfo != NULL)
7660 msginfo = compose->fwdinfo;
7662 dummyinfo = compose_msginfo_new_from_compose(compose);
7663 msginfo = dummyinfo;
7666 if (tmpl->from && *tmpl->from != '\0') {
7668 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
7669 compose->gtkaspell);
7671 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
7673 quote_fmt_scan_string(tmpl->from);
7676 buf = quote_fmt_get_buffer();
7678 alertpanel_error(_("Template From format error."));
7680 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
7684 if (tmpl->to && *tmpl->to != '\0') {
7686 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
7687 compose->gtkaspell);
7689 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
7691 quote_fmt_scan_string(tmpl->to);
7694 buf = quote_fmt_get_buffer();
7696 alertpanel_error(_("Template To format error."));
7698 compose_entry_append(compose, buf, COMPOSE_TO);
7702 if (tmpl->cc && *tmpl->cc != '\0') {
7704 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
7705 compose->gtkaspell);
7707 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
7709 quote_fmt_scan_string(tmpl->cc);
7712 buf = quote_fmt_get_buffer();
7714 alertpanel_error(_("Template Cc format error."));
7716 compose_entry_append(compose, buf, COMPOSE_CC);
7720 if (tmpl->bcc && *tmpl->bcc != '\0') {
7722 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
7723 compose->gtkaspell);
7725 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
7727 quote_fmt_scan_string(tmpl->bcc);
7730 buf = quote_fmt_get_buffer();
7732 alertpanel_error(_("Template Bcc format error."));
7734 compose_entry_append(compose, buf, COMPOSE_BCC);
7738 /* process the subject */
7739 if (tmpl->subject && *tmpl->subject != '\0') {
7741 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
7742 compose->gtkaspell);
7744 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
7746 quote_fmt_scan_string(tmpl->subject);
7749 buf = quote_fmt_get_buffer();
7751 alertpanel_error(_("Template subject format error."));
7753 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
7757 procmsg_msginfo_free( dummyinfo );
7760 static void compose_destroy(Compose *compose)
7762 GtkTextBuffer *buffer;
7763 GtkClipboard *clipboard;
7765 compose_list = g_list_remove(compose_list, compose);
7767 if (compose->updating) {
7768 debug_print("danger, not destroying anything now\n");
7769 compose->deferred_destroy = TRUE;
7772 /* NOTE: address_completion_end() does nothing with the window
7773 * however this may change. */
7774 address_completion_end(compose->window);
7776 slist_free_strings(compose->to_list);
7777 g_slist_free(compose->to_list);
7778 slist_free_strings(compose->newsgroup_list);
7779 g_slist_free(compose->newsgroup_list);
7780 slist_free_strings(compose->header_list);
7781 g_slist_free(compose->header_list);
7783 procmsg_msginfo_free(compose->targetinfo);
7784 procmsg_msginfo_free(compose->replyinfo);
7785 procmsg_msginfo_free(compose->fwdinfo);
7787 g_free(compose->replyto);
7788 g_free(compose->cc);
7789 g_free(compose->bcc);
7790 g_free(compose->newsgroups);
7791 g_free(compose->followup_to);
7793 g_free(compose->ml_post);
7795 g_free(compose->inreplyto);
7796 g_free(compose->references);
7797 g_free(compose->msgid);
7798 g_free(compose->boundary);
7800 g_free(compose->redirect_filename);
7801 if (compose->undostruct)
7802 undo_destroy(compose->undostruct);
7804 g_free(compose->sig_str);
7806 g_free(compose->exteditor_file);
7808 g_free(compose->orig_charset);
7810 g_free(compose->privacy_system);
7812 if (addressbook_get_target_compose() == compose)
7813 addressbook_set_target_compose(NULL);
7816 if (compose->gtkaspell) {
7817 gtkaspell_delete(compose->gtkaspell);
7818 compose->gtkaspell = NULL;
7822 prefs_common.compose_width = compose->scrolledwin->allocation.width;
7823 prefs_common.compose_height = compose->window->allocation.height;
7825 if (!gtk_widget_get_parent(compose->paned))
7826 gtk_widget_destroy(compose->paned);
7827 gtk_widget_destroy(compose->popupmenu);
7829 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
7830 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7831 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
7833 gtk_widget_destroy(compose->window);
7834 toolbar_destroy(compose->toolbar);
7835 g_free(compose->toolbar);
7836 g_mutex_free(compose->mutex);
7840 static void compose_attach_info_free(AttachInfo *ainfo)
7842 g_free(ainfo->file);
7843 g_free(ainfo->content_type);
7844 g_free(ainfo->name);
7848 static void compose_attach_update_label(Compose *compose)
7853 GtkTreeModel *model;
7858 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
7859 if(!gtk_tree_model_get_iter_first(model, &iter)) {
7860 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
7864 while(gtk_tree_model_iter_next(model, &iter))
7867 text = g_strdup_printf("(%d)", i);
7868 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
7872 static void compose_attach_remove_selected(Compose *compose)
7874 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
7875 GtkTreeSelection *selection;
7877 GtkTreeModel *model;
7879 selection = gtk_tree_view_get_selection(tree_view);
7880 sel = gtk_tree_selection_get_selected_rows(selection, &model);
7885 for (cur = sel; cur != NULL; cur = cur->next) {
7886 GtkTreePath *path = cur->data;
7887 GtkTreeRowReference *ref = gtk_tree_row_reference_new
7890 gtk_tree_path_free(path);
7893 for (cur = sel; cur != NULL; cur = cur->next) {
7894 GtkTreeRowReference *ref = cur->data;
7895 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
7898 if (gtk_tree_model_get_iter(model, &iter, path))
7899 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
7901 gtk_tree_path_free(path);
7902 gtk_tree_row_reference_free(ref);
7906 compose_attach_update_label(compose);
7909 static struct _AttachProperty
7912 GtkWidget *mimetype_entry;
7913 GtkWidget *encoding_optmenu;
7914 GtkWidget *path_entry;
7915 GtkWidget *filename_entry;
7917 GtkWidget *cancel_btn;
7920 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
7922 gtk_tree_path_free((GtkTreePath *)ptr);
7925 static void compose_attach_property(Compose *compose)
7927 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
7929 GtkComboBox *optmenu;
7930 GtkTreeSelection *selection;
7932 GtkTreeModel *model;
7935 static gboolean cancelled;
7937 /* only if one selected */
7938 selection = gtk_tree_view_get_selection(tree_view);
7939 if (gtk_tree_selection_count_selected_rows(selection) != 1)
7942 sel = gtk_tree_selection_get_selected_rows(selection, &model);
7946 path = (GtkTreePath *) sel->data;
7947 gtk_tree_model_get_iter(model, &iter, path);
7948 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
7951 g_list_foreach(sel, gtk_tree_path_free_, NULL);
7957 if (!attach_prop.window)
7958 compose_attach_property_create(&cancelled);
7959 gtk_widget_grab_focus(attach_prop.ok_btn);
7960 gtk_widget_show(attach_prop.window);
7961 manage_window_set_transient(GTK_WINDOW(attach_prop.window));
7963 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
7964 if (ainfo->encoding == ENC_UNKNOWN)
7965 combobox_select_by_data(optmenu, ENC_BASE64);
7967 combobox_select_by_data(optmenu, ainfo->encoding);
7969 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
7970 ainfo->content_type ? ainfo->content_type : "");
7971 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
7972 ainfo->file ? ainfo->file : "");
7973 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
7974 ainfo->name ? ainfo->name : "");
7977 const gchar *entry_text;
7979 gchar *cnttype = NULL;
7986 gtk_widget_hide(attach_prop.window);
7991 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
7992 if (*entry_text != '\0') {
7995 text = g_strstrip(g_strdup(entry_text));
7996 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
7997 cnttype = g_strdup(text);
8000 alertpanel_error(_("Invalid MIME type."));
8006 ainfo->encoding = combobox_get_active_data(optmenu);
8008 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
8009 if (*entry_text != '\0') {
8010 if (is_file_exist(entry_text) &&
8011 (size = get_file_size(entry_text)) > 0)
8012 file = g_strdup(entry_text);
8015 (_("File doesn't exist or is empty."));
8021 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
8022 if (*entry_text != '\0') {
8023 g_free(ainfo->name);
8024 ainfo->name = g_strdup(entry_text);
8028 g_free(ainfo->content_type);
8029 ainfo->content_type = cnttype;
8032 g_free(ainfo->file);
8038 /* update tree store */
8039 text = to_human_readable(ainfo->size);
8040 gtk_tree_model_get_iter(model, &iter, path);
8041 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
8042 COL_MIMETYPE, ainfo->content_type,
8044 COL_NAME, ainfo->name,
8050 gtk_tree_path_free(path);
8053 #define SET_LABEL_AND_ENTRY(str, entry, top) \
8055 label = gtk_label_new(str); \
8056 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
8057 GTK_FILL, 0, 0, 0); \
8058 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
8060 entry = gtk_entry_new(); \
8061 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
8062 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
8065 static void compose_attach_property_create(gboolean *cancelled)
8071 GtkWidget *mimetype_entry;
8074 GtkListStore *optmenu_menu;
8075 GtkWidget *path_entry;
8076 GtkWidget *filename_entry;
8079 GtkWidget *cancel_btn;
8080 GList *mime_type_list, *strlist;
8083 debug_print("Creating attach_property window...\n");
8085 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
8086 gtk_widget_set_size_request(window, 480, -1);
8087 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
8088 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
8089 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
8090 gtk_window_set_modal(GTK_WINDOW(window), TRUE);
8091 g_signal_connect(G_OBJECT(window), "delete_event",
8092 G_CALLBACK(attach_property_delete_event),
8094 g_signal_connect(G_OBJECT(window), "key_press_event",
8095 G_CALLBACK(attach_property_key_pressed),
8098 vbox = gtk_vbox_new(FALSE, 8);
8099 gtk_container_add(GTK_CONTAINER(window), vbox);
8101 table = gtk_table_new(4, 2, FALSE);
8102 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
8103 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
8104 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
8106 label = gtk_label_new(_("MIME type"));
8107 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
8109 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
8110 mimetype_entry = gtk_combo_box_entry_new_text();
8111 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
8112 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
8114 /* stuff with list */
8115 mime_type_list = procmime_get_mime_type_list();
8117 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
8118 MimeType *type = (MimeType *) mime_type_list->data;
8121 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
8123 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
8126 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
8127 (GCompareFunc)strcmp2);
8130 for (mime_type_list = strlist; mime_type_list != NULL;
8131 mime_type_list = mime_type_list->next) {
8132 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
8133 g_free(mime_type_list->data);
8135 g_list_free(strlist);
8136 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
8137 mimetype_entry = GTK_BIN(mimetype_entry)->child;
8139 label = gtk_label_new(_("Encoding"));
8140 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
8142 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
8144 hbox = gtk_hbox_new(FALSE, 0);
8145 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
8146 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
8148 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
8149 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8151 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
8152 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
8153 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
8154 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
8155 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
8157 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
8159 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
8160 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
8162 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
8163 &ok_btn, GTK_STOCK_OK,
8165 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
8166 gtk_widget_grab_default(ok_btn);
8168 g_signal_connect(G_OBJECT(ok_btn), "clicked",
8169 G_CALLBACK(attach_property_ok),
8171 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
8172 G_CALLBACK(attach_property_cancel),
8175 gtk_widget_show_all(vbox);
8177 attach_prop.window = window;
8178 attach_prop.mimetype_entry = mimetype_entry;
8179 attach_prop.encoding_optmenu = optmenu;
8180 attach_prop.path_entry = path_entry;
8181 attach_prop.filename_entry = filename_entry;
8182 attach_prop.ok_btn = ok_btn;
8183 attach_prop.cancel_btn = cancel_btn;
8186 #undef SET_LABEL_AND_ENTRY
8188 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
8194 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
8200 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
8201 gboolean *cancelled)
8209 static gboolean attach_property_key_pressed(GtkWidget *widget,
8211 gboolean *cancelled)
8213 if (event && event->keyval == GDK_Escape) {
8220 static void compose_exec_ext_editor(Compose *compose)
8227 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
8228 G_DIR_SEPARATOR, compose);
8230 if (pipe(pipe_fds) < 0) {
8236 if ((pid = fork()) < 0) {
8243 /* close the write side of the pipe */
8246 compose->exteditor_file = g_strdup(tmp);
8247 compose->exteditor_pid = pid;
8249 compose_set_ext_editor_sensitive(compose, FALSE);
8251 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
8252 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
8256 } else { /* process-monitoring process */
8262 /* close the read side of the pipe */
8265 if (compose_write_body_to_file(compose, tmp) < 0) {
8266 fd_write_all(pipe_fds[1], "2\n", 2);
8270 pid_ed = compose_exec_ext_editor_real(tmp);
8272 fd_write_all(pipe_fds[1], "1\n", 2);
8276 /* wait until editor is terminated */
8277 waitpid(pid_ed, NULL, 0);
8279 fd_write_all(pipe_fds[1], "0\n", 2);
8286 #endif /* G_OS_UNIX */
8290 static gint compose_exec_ext_editor_real(const gchar *file)
8297 g_return_val_if_fail(file != NULL, -1);
8299 if ((pid = fork()) < 0) {
8304 if (pid != 0) return pid;
8306 /* grandchild process */
8308 if (setpgid(0, getppid()))
8311 if (prefs_common_get_ext_editor_cmd() &&
8312 (p = strchr(prefs_common_get_ext_editor_cmd(), '%')) &&
8313 *(p + 1) == 's' && !strchr(p + 2, '%')) {
8314 g_snprintf(buf, sizeof(buf), prefs_common_get_ext_editor_cmd(), file);
8316 if (prefs_common_get_ext_editor_cmd())
8317 g_warning("External editor command line is invalid: '%s'\n",
8318 prefs_common_get_ext_editor_cmd());
8319 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
8322 cmdline = strsplit_with_quote(buf, " ", 1024);
8323 execvp(cmdline[0], cmdline);
8326 g_strfreev(cmdline);
8331 static gboolean compose_ext_editor_kill(Compose *compose)
8333 pid_t pgid = compose->exteditor_pid * -1;
8336 ret = kill(pgid, 0);
8338 if (ret == 0 || (ret == -1 && EPERM == errno)) {
8342 msg = g_strdup_printf
8343 (_("The external editor is still working.\n"
8344 "Force terminating the process?\n"
8345 "process group id: %d"), -pgid);
8346 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
8347 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
8351 if (val == G_ALERTALTERNATE) {
8352 g_source_remove(compose->exteditor_tag);
8353 g_io_channel_shutdown(compose->exteditor_ch,
8355 g_io_channel_unref(compose->exteditor_ch);
8357 if (kill(pgid, SIGTERM) < 0) perror("kill");
8358 waitpid(compose->exteditor_pid, NULL, 0);
8360 g_warning("Terminated process group id: %d", -pgid);
8361 g_warning("Temporary file: %s",
8362 compose->exteditor_file);
8364 compose_set_ext_editor_sensitive(compose, TRUE);
8366 g_free(compose->exteditor_file);
8367 compose->exteditor_file = NULL;
8368 compose->exteditor_pid = -1;
8369 compose->exteditor_ch = NULL;
8370 compose->exteditor_tag = -1;
8378 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
8382 Compose *compose = (Compose *)data;
8385 debug_print(_("Compose: input from monitoring process\n"));
8387 g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL);
8389 g_io_channel_shutdown(source, FALSE, NULL);
8390 g_io_channel_unref(source);
8392 waitpid(compose->exteditor_pid, NULL, 0);
8394 if (buf[0] == '0') { /* success */
8395 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
8396 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
8398 gtk_text_buffer_set_text(buffer, "", -1);
8399 compose_insert_file(compose, compose->exteditor_file);
8400 compose_changed_cb(NULL, compose);
8402 if (claws_unlink(compose->exteditor_file) < 0)
8403 FILE_OP_ERROR(compose->exteditor_file, "unlink");
8404 } else if (buf[0] == '1') { /* failed */
8405 g_warning("Couldn't exec external editor\n");
8406 if (claws_unlink(compose->exteditor_file) < 0)
8407 FILE_OP_ERROR(compose->exteditor_file, "unlink");
8408 } else if (buf[0] == '2') {
8409 g_warning("Couldn't write to file\n");
8410 } else if (buf[0] == '3') {
8411 g_warning("Pipe read failed\n");
8414 compose_set_ext_editor_sensitive(compose, TRUE);
8416 g_free(compose->exteditor_file);
8417 compose->exteditor_file = NULL;
8418 compose->exteditor_pid = -1;
8419 compose->exteditor_ch = NULL;
8420 compose->exteditor_tag = -1;
8425 static void compose_set_ext_editor_sensitive(Compose *compose,
8428 GtkItemFactory *ifactory;
8430 ifactory = gtk_item_factory_from_widget(compose->menubar);
8432 menu_set_sensitive(ifactory, "/Message/Send", sensitive);
8433 menu_set_sensitive(ifactory, "/Message/Send later", sensitive);
8434 menu_set_sensitive(ifactory, "/Message/Insert file", sensitive);
8435 menu_set_sensitive(ifactory, "/Message/Insert signature", sensitive);
8436 menu_set_sensitive(ifactory, "/Edit/Wrap current paragraph", sensitive);
8437 menu_set_sensitive(ifactory, "/Edit/Wrap all long lines", sensitive);
8438 menu_set_sensitive(ifactory, "/Edit/Edit with external editor",
8441 gtk_widget_set_sensitive(compose->text, sensitive);
8442 if (compose->toolbar->send_btn)
8443 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
8444 if (compose->toolbar->sendl_btn)
8445 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
8446 if (compose->toolbar->draft_btn)
8447 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
8448 if (compose->toolbar->insert_btn)
8449 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
8450 if (compose->toolbar->sig_btn)
8451 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
8452 if (compose->toolbar->exteditor_btn)
8453 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
8454 if (compose->toolbar->linewrap_current_btn)
8455 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
8456 if (compose->toolbar->linewrap_all_btn)
8457 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
8459 #endif /* G_OS_UNIX */
8462 * compose_undo_state_changed:
8464 * Change the sensivity of the menuentries undo and redo
8466 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
8467 gint redo_state, gpointer data)
8469 GtkWidget *widget = GTK_WIDGET(data);
8470 GtkItemFactory *ifactory;
8472 g_return_if_fail(widget != NULL);
8474 ifactory = gtk_item_factory_from_widget(widget);
8476 switch (undo_state) {
8477 case UNDO_STATE_TRUE:
8478 if (!undostruct->undo_state) {
8479 undostruct->undo_state = TRUE;
8480 menu_set_sensitive(ifactory, "/Edit/Undo", TRUE);
8483 case UNDO_STATE_FALSE:
8484 if (undostruct->undo_state) {
8485 undostruct->undo_state = FALSE;
8486 menu_set_sensitive(ifactory, "/Edit/Undo", FALSE);
8489 case UNDO_STATE_UNCHANGED:
8491 case UNDO_STATE_REFRESH:
8492 menu_set_sensitive(ifactory, "/Edit/Undo",
8493 undostruct->undo_state);
8496 g_warning("Undo state not recognized");
8500 switch (redo_state) {
8501 case UNDO_STATE_TRUE:
8502 if (!undostruct->redo_state) {
8503 undostruct->redo_state = TRUE;
8504 menu_set_sensitive(ifactory, "/Edit/Redo", TRUE);
8507 case UNDO_STATE_FALSE:
8508 if (undostruct->redo_state) {
8509 undostruct->redo_state = FALSE;
8510 menu_set_sensitive(ifactory, "/Edit/Redo", FALSE);
8513 case UNDO_STATE_UNCHANGED:
8515 case UNDO_STATE_REFRESH:
8516 menu_set_sensitive(ifactory, "/Edit/Redo",
8517 undostruct->redo_state);
8520 g_warning("Redo state not recognized");
8525 /* callback functions */
8527 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
8528 * includes "non-client" (windows-izm) in calculation, so this calculation
8529 * may not be accurate.
8531 static gboolean compose_edit_size_alloc(GtkEditable *widget,
8532 GtkAllocation *allocation,
8533 GtkSHRuler *shruler)
8535 if (prefs_common.show_ruler) {
8536 gint char_width = 0, char_height = 0;
8537 gint line_width_in_chars;
8539 gtkut_get_font_size(GTK_WIDGET(widget),
8540 &char_width, &char_height);
8541 line_width_in_chars =
8542 (allocation->width - allocation->x) / char_width;
8544 /* got the maximum */
8545 gtk_ruler_set_range(GTK_RULER(shruler),
8546 0.0, line_width_in_chars, 0,
8547 /*line_width_in_chars*/ char_width);
8553 static void account_activated(GtkComboBox *optmenu, gpointer data)
8555 Compose *compose = (Compose *)data;
8558 gchar *folderidentifier;
8559 gint account_id = 0;
8563 /* Get ID of active account in the combo box */
8564 menu = gtk_combo_box_get_model(optmenu);
8565 gtk_combo_box_get_active_iter(optmenu, &iter);
8566 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
8568 ac = account_find_from_id(account_id);
8569 g_return_if_fail(ac != NULL);
8571 if (ac != compose->account)
8572 compose_select_account(compose, ac, FALSE);
8574 /* Set message save folder */
8575 if (account_get_special_folder(compose->account, F_OUTBOX)) {
8576 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
8578 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
8579 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
8581 gtk_editable_delete_text(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
8582 if (account_get_special_folder(compose->account, F_OUTBOX)) {
8583 folderidentifier = folder_item_get_identifier(account_get_special_folder
8584 (compose->account, F_OUTBOX));
8585 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
8586 g_free(folderidentifier);
8590 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
8591 GtkTreeViewColumn *column, Compose *compose)
8593 compose_attach_property(compose);
8596 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
8599 Compose *compose = (Compose *)data;
8600 GtkTreeSelection *attach_selection;
8601 gint attach_nr_selected;
8602 GtkItemFactory *ifactory;
8604 if (!event) return FALSE;
8606 if (event->button == 3) {
8607 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
8608 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
8609 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
8611 if (attach_nr_selected > 0)
8613 menu_set_sensitive(ifactory, "/Remove", TRUE);
8614 menu_set_sensitive(ifactory, "/Properties...", TRUE);
8616 menu_set_sensitive(ifactory, "/Remove", FALSE);
8617 menu_set_sensitive(ifactory, "/Properties...", FALSE);
8620 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
8621 NULL, NULL, event->button, event->time);
8628 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
8631 Compose *compose = (Compose *)data;
8633 if (!event) return FALSE;
8635 switch (event->keyval) {
8637 compose_attach_remove_selected(compose);
8643 static void compose_allow_user_actions (Compose *compose, gboolean allow)
8645 GtkItemFactory *ifactory = gtk_item_factory_from_widget(compose->menubar);
8646 toolbar_comp_set_sensitive(compose, allow);
8647 menu_set_sensitive(ifactory, "/Message", allow);
8648 menu_set_sensitive(ifactory, "/Edit", allow);
8650 menu_set_sensitive(ifactory, "/Spelling", allow);
8652 menu_set_sensitive(ifactory, "/Options", allow);
8653 menu_set_sensitive(ifactory, "/Tools", allow);
8654 menu_set_sensitive(ifactory, "/Help", allow);
8656 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
8660 static void compose_send_cb(gpointer data, guint action, GtkWidget *widget)
8662 Compose *compose = (Compose *)data;
8664 if (prefs_common.work_offline &&
8665 !inc_offline_should_override(TRUE,
8666 _("Claws Mail needs network access in order "
8667 "to send this email.")))
8670 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
8671 g_source_remove(compose->draft_timeout_tag);
8672 compose->draft_timeout_tag = -1;
8675 compose_send(compose);
8678 static void compose_send_later_cb(gpointer data, guint action,
8681 Compose *compose = (Compose *)data;
8685 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
8689 compose_close(compose);
8690 } else if (val == -1) {
8691 alertpanel_error(_("Could not queue message."));
8692 } else if (val == -2) {
8693 alertpanel_error(_("Could not queue message:\n\n%s."), strerror(errno));
8694 } else if (val == -3) {
8695 if (privacy_peek_error())
8696 alertpanel_error(_("Could not queue message for sending:\n\n"
8697 "Signature failed: %s"), privacy_get_error());
8698 } else if (val == -4) {
8699 alertpanel_error(_("Could not queue message for sending:\n\n"
8700 "Charset conversion failed."));
8701 } else if (val == -5) {
8702 alertpanel_error(_("Could not queue message for sending:\n\n"
8703 "Couldn't get recipient encryption key."));
8704 } else if (val == -6) {
8707 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
8710 #define DRAFTED_AT_EXIT "drafted_at_exit"
8711 static void compose_register_draft(MsgInfo *info)
8713 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8714 DRAFTED_AT_EXIT, NULL);
8715 FILE *fp = fopen(filepath, "ab");
8718 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
8726 gboolean compose_draft (gpointer data, guint action)
8728 Compose *compose = (Compose *)data;
8732 MsgFlags flag = {0, 0};
8733 static gboolean lock = FALSE;
8734 MsgInfo *newmsginfo;
8736 gboolean target_locked = FALSE;
8737 gboolean err = FALSE;
8739 if (lock) return FALSE;
8741 if (compose->sending)
8744 draft = account_get_special_folder(compose->account, F_DRAFT);
8745 g_return_val_if_fail(draft != NULL, FALSE);
8747 if (!g_mutex_trylock(compose->mutex)) {
8748 /* we don't want to lock the mutex once it's available,
8749 * because as the only other part of compose.c locking
8750 * it is compose_close - which means once unlocked,
8751 * the compose struct will be freed */
8752 debug_print("couldn't lock mutex, probably sending\n");
8758 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
8759 G_DIR_SEPARATOR, compose);
8760 if ((fp = g_fopen(tmp, "wb")) == NULL) {
8761 FILE_OP_ERROR(tmp, "fopen");
8765 /* chmod for security */
8766 if (change_file_mode_rw(fp, tmp) < 0) {
8767 FILE_OP_ERROR(tmp, "chmod");
8768 g_warning("can't change file mode\n");
8771 /* Save draft infos */
8772 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
8773 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
8775 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
8776 gchar *savefolderid;
8778 savefolderid = gtk_editable_get_chars(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
8779 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
8780 g_free(savefolderid);
8782 if (compose->return_receipt) {
8783 err |= (fprintf(fp, "RRCPT:1\n") < 0);
8785 if (compose->privacy_system) {
8786 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
8787 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
8788 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
8791 /* Message-ID of message replying to */
8792 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
8795 folderid = folder_item_get_identifier(compose->replyinfo->folder);
8796 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
8799 /* Message-ID of message forwarding to */
8800 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
8803 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
8804 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
8808 /* end of headers */
8809 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
8816 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
8820 if (fclose(fp) == EOF) {
8824 if (compose->targetinfo) {
8825 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
8826 flag.perm_flags = target_locked?MSG_LOCKED:0;
8828 flag.tmp_flags = MSG_DRAFT;
8830 folder_item_scan(draft);
8831 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
8832 MsgInfo *tmpinfo = NULL;
8833 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
8834 if (compose->msgid) {
8835 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
8838 msgnum = tmpinfo->msgnum;
8839 procmsg_msginfo_free(tmpinfo);
8840 debug_print("got draft msgnum %d from scanning\n", msgnum);
8842 debug_print("didn't get draft msgnum after scanning\n");
8845 debug_print("got draft msgnum %d from adding\n", msgnum);
8851 if (action != COMPOSE_AUTO_SAVE) {
8852 if (action != COMPOSE_DRAFT_FOR_EXIT)
8853 alertpanel_error(_("Could not save draft."));
8856 gtkut_window_popup(compose->window);
8857 val = alertpanel_full(_("Could not save draft"),
8858 _("Could not save draft.\n"
8859 "Do you want to cancel exit or discard this email?"),
8860 _("_Cancel exit"), _("_Discard email"), NULL,
8861 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
8862 if (val == G_ALERTALTERNATE) {
8864 g_mutex_unlock(compose->mutex); /* must be done before closing */
8865 compose_close(compose);
8869 g_mutex_unlock(compose->mutex); /* must be done before closing */
8878 if (compose->mode == COMPOSE_REEDIT) {
8879 compose_remove_reedit_target(compose, TRUE);
8882 newmsginfo = folder_item_get_msginfo(draft, msgnum);
8885 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
8887 procmsg_msginfo_set_flags(newmsginfo, MSG_LOCKED, MSG_DRAFT);
8889 procmsg_msginfo_set_flags(newmsginfo, 0, MSG_DRAFT);
8890 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
8891 procmsg_msginfo_set_flags(newmsginfo, 0,
8892 MSG_HAS_ATTACHMENT);
8894 if (action == COMPOSE_DRAFT_FOR_EXIT) {
8895 compose_register_draft(newmsginfo);
8897 procmsg_msginfo_free(newmsginfo);
8900 folder_item_scan(draft);
8902 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
8904 g_mutex_unlock(compose->mutex); /* must be done before closing */
8905 compose_close(compose);
8911 path = folder_item_fetch_msg(draft, msgnum);
8913 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
8916 if (g_stat(path, &s) < 0) {
8917 FILE_OP_ERROR(path, "stat");
8923 procmsg_msginfo_free(compose->targetinfo);
8924 compose->targetinfo = procmsg_msginfo_new();
8925 compose->targetinfo->msgnum = msgnum;
8926 compose->targetinfo->size = s.st_size;
8927 compose->targetinfo->mtime = s.st_mtime;
8928 compose->targetinfo->folder = draft;
8930 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
8931 compose->mode = COMPOSE_REEDIT;
8933 if (action == COMPOSE_AUTO_SAVE) {
8934 compose->autosaved_draft = compose->targetinfo;
8936 compose->modified = FALSE;
8937 compose_set_title(compose);
8941 g_mutex_unlock(compose->mutex);
8945 void compose_clear_exit_drafts(void)
8947 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8948 DRAFTED_AT_EXIT, NULL);
8949 if (is_file_exist(filepath))
8950 claws_unlink(filepath);
8955 void compose_reopen_exit_drafts(void)
8957 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8958 DRAFTED_AT_EXIT, NULL);
8959 FILE *fp = fopen(filepath, "rb");
8963 while (fgets(buf, sizeof(buf), fp)) {
8964 gchar **parts = g_strsplit(buf, "\t", 2);
8965 const gchar *folder = parts[0];
8966 int msgnum = parts[1] ? atoi(parts[1]):-1;
8968 if (folder && *folder && msgnum > -1) {
8969 FolderItem *item = folder_find_item_from_identifier(folder);
8970 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
8972 compose_reedit(info, FALSE);
8979 compose_clear_exit_drafts();
8982 static void compose_draft_cb(gpointer data, guint action, GtkWidget *widget)
8984 compose_draft(data, action);
8987 static void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
8989 if (compose && file_list) {
8992 for ( tmp = file_list; tmp; tmp = tmp->next) {
8993 gchar *file = (gchar *) tmp->data;
8994 gchar *utf8_filename = conv_filename_to_utf8(file);
8995 compose_attach_append(compose, file, utf8_filename, NULL);
8996 compose_changed_cb(NULL, compose);
9001 g_free(utf8_filename);
9006 static void compose_attach_cb(gpointer data, guint action, GtkWidget *widget)
9008 Compose *compose = (Compose *)data;
9011 if (compose->redirect_filename != NULL)
9014 file_list = filesel_select_multiple_files_open(_("Select file"));
9017 compose_attach_from_list(compose, file_list, TRUE);
9018 g_list_free(file_list);
9022 static void compose_insert_file_cb(gpointer data, guint action,
9025 Compose *compose = (Compose *)data;
9028 file_list = filesel_select_multiple_files_open(_("Select file"));
9033 for ( tmp = file_list; tmp; tmp = tmp->next) {
9034 gchar *file = (gchar *) tmp->data;
9035 gchar *filedup = g_strdup(file);
9036 gchar *shortfile = g_path_get_basename(filedup);
9037 ComposeInsertResult res;
9039 res = compose_insert_file(compose, file);
9040 if (res == COMPOSE_INSERT_READ_ERROR) {
9041 alertpanel_error(_("File '%s' could not be read."), shortfile);
9042 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
9043 alertpanel_error(_("File '%s' contained invalid characters\n"
9044 "for the current encoding, insertion may be incorrect."), shortfile);
9050 g_list_free(file_list);
9054 static void compose_insert_sig_cb(gpointer data, guint action,
9057 Compose *compose = (Compose *)data;
9059 compose_insert_sig(compose, FALSE);
9062 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
9066 Compose *compose = (Compose *)data;
9068 gtkut_widget_get_uposition(widget, &x, &y);
9069 prefs_common.compose_x = x;
9070 prefs_common.compose_y = y;
9072 if (compose->sending || compose->updating)
9074 compose_close_cb(compose, 0, NULL);
9078 void compose_close_toolbar(Compose *compose)
9080 compose_close_cb(compose, 0, NULL);
9083 static void compose_close_cb(gpointer data, guint action, GtkWidget *widget)
9085 Compose *compose = (Compose *)data;
9089 if (compose->exteditor_tag != -1) {
9090 if (!compose_ext_editor_kill(compose))
9095 if (compose->modified) {
9096 if (!g_mutex_trylock(compose->mutex)) {
9097 /* we don't want to lock the mutex once it's available,
9098 * because as the only other part of compose.c locking
9099 * it is compose_close - which means once unlocked,
9100 * the compose struct will be freed */
9101 debug_print("couldn't lock mutex, probably sending\n");
9104 val = alertpanel(_("Discard message"),
9105 _("This message has been modified. Discard it?"),
9106 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
9107 g_mutex_unlock(compose->mutex);
9109 case G_ALERTDEFAULT:
9110 if (prefs_common.autosave)
9111 compose_remove_draft(compose);
9113 case G_ALERTALTERNATE:
9114 compose_draft_cb(data, COMPOSE_QUIT_EDITING, NULL);
9121 compose_close(compose);
9124 static void compose_set_encoding_cb(gpointer data, guint action,
9127 Compose *compose = (Compose *)data;
9129 if (GTK_CHECK_MENU_ITEM(widget)->active)
9130 compose->out_encoding = (CharSet)action;
9133 static void compose_address_cb(gpointer data, guint action, GtkWidget *widget)
9135 Compose *compose = (Compose *)data;
9137 addressbook_open(compose);
9140 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
9142 Compose *compose = (Compose *)data;
9147 tmpl = g_object_get_data(G_OBJECT(widget), "template");
9148 g_return_if_fail(tmpl != NULL);
9150 msg = g_strdup_printf(_("Do you want to apply the template '%s' ?"),
9152 val = alertpanel(_("Apply template"), msg,
9153 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
9156 if (val == G_ALERTDEFAULT)
9157 compose_template_apply(compose, tmpl, TRUE);
9158 else if (val == G_ALERTALTERNATE)
9159 compose_template_apply(compose, tmpl, FALSE);
9162 static void compose_ext_editor_cb(gpointer data, guint action,
9165 Compose *compose = (Compose *)data;
9167 compose_exec_ext_editor(compose);
9170 static void compose_undo_cb(Compose *compose)
9172 gboolean prev_autowrap = compose->autowrap;
9174 compose->autowrap = FALSE;
9175 undo_undo(compose->undostruct);
9176 compose->autowrap = prev_autowrap;
9179 static void compose_redo_cb(Compose *compose)
9181 gboolean prev_autowrap = compose->autowrap;
9183 compose->autowrap = FALSE;
9184 undo_redo(compose->undostruct);
9185 compose->autowrap = prev_autowrap;
9188 static void entry_cut_clipboard(GtkWidget *entry)
9190 if (GTK_IS_EDITABLE(entry))
9191 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
9192 else if (GTK_IS_TEXT_VIEW(entry))
9193 gtk_text_buffer_cut_clipboard(
9194 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
9195 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
9199 static void entry_copy_clipboard(GtkWidget *entry)
9201 if (GTK_IS_EDITABLE(entry))
9202 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
9203 else if (GTK_IS_TEXT_VIEW(entry))
9204 gtk_text_buffer_copy_clipboard(
9205 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
9206 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
9209 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
9210 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
9212 if (GTK_IS_TEXT_VIEW(entry)) {
9213 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
9214 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
9215 GtkTextIter start_iter, end_iter;
9217 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
9219 if (contents == NULL)
9222 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
9224 /* we shouldn't delete the selection when middle-click-pasting, or we
9225 * can't mid-click-paste our own selection */
9226 if (clip != GDK_SELECTION_PRIMARY) {
9227 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
9230 if (insert_place == NULL) {
9231 /* if insert_place isn't specified, insert at the cursor.
9232 * used for Ctrl-V pasting */
9233 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
9234 start = gtk_text_iter_get_offset(&start_iter);
9235 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
9237 /* if insert_place is specified, paste here.
9238 * used for mid-click-pasting */
9239 start = gtk_text_iter_get_offset(insert_place);
9240 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
9244 /* paste unwrapped: mark the paste so it's not wrapped later */
9245 end = start + strlen(contents);
9246 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
9247 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
9248 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
9249 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
9250 /* rewrap paragraph now (after a mid-click-paste) */
9251 mark_start = gtk_text_buffer_get_insert(buffer);
9252 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
9253 gtk_text_iter_backward_char(&start_iter);
9254 compose_beautify_paragraph(compose, &start_iter, TRUE);
9256 } else if (GTK_IS_EDITABLE(entry))
9257 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
9259 compose->modified = TRUE;
9262 static void entry_allsel(GtkWidget *entry)
9264 if (GTK_IS_EDITABLE(entry))
9265 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
9266 else if (GTK_IS_TEXT_VIEW(entry)) {
9267 GtkTextIter startiter, enditer;
9268 GtkTextBuffer *textbuf;
9270 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
9271 gtk_text_buffer_get_start_iter(textbuf, &startiter);
9272 gtk_text_buffer_get_end_iter(textbuf, &enditer);
9274 gtk_text_buffer_move_mark_by_name(textbuf,
9275 "selection_bound", &startiter);
9276 gtk_text_buffer_move_mark_by_name(textbuf,
9277 "insert", &enditer);
9281 static void compose_cut_cb(Compose *compose)
9283 if (compose->focused_editable
9285 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9288 entry_cut_clipboard(compose->focused_editable);
9291 static void compose_copy_cb(Compose *compose)
9293 if (compose->focused_editable
9295 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9298 entry_copy_clipboard(compose->focused_editable);
9301 static void compose_paste_cb(Compose *compose)
9304 GtkTextBuffer *buffer;
9306 if (compose->focused_editable &&
9307 GTK_WIDGET_HAS_FOCUS(compose->focused_editable))
9308 entry_paste_clipboard(compose, compose->focused_editable,
9309 prefs_common.linewrap_pastes,
9310 GDK_SELECTION_CLIPBOARD, NULL);
9314 static void compose_paste_as_quote_cb(Compose *compose)
9316 gint wrap_quote = prefs_common.linewrap_quote;
9317 if (compose->focused_editable
9319 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9322 /* let text_insert() (called directly or at a later time
9323 * after the gtk_editable_paste_clipboard) know that
9324 * text is to be inserted as a quotation. implemented
9325 * by using a simple refcount... */
9326 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
9327 G_OBJECT(compose->focused_editable),
9328 "paste_as_quotation"));
9329 g_object_set_data(G_OBJECT(compose->focused_editable),
9330 "paste_as_quotation",
9331 GINT_TO_POINTER(paste_as_quotation + 1));
9332 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
9333 entry_paste_clipboard(compose, compose->focused_editable,
9334 prefs_common.linewrap_pastes,
9335 GDK_SELECTION_CLIPBOARD, NULL);
9336 prefs_common.linewrap_quote = wrap_quote;
9340 static void compose_paste_no_wrap_cb(Compose *compose)
9343 GtkTextBuffer *buffer;
9345 if (compose->focused_editable
9347 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9350 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
9351 GDK_SELECTION_CLIPBOARD, NULL);
9355 static void compose_paste_wrap_cb(Compose *compose)
9358 GtkTextBuffer *buffer;
9360 if (compose->focused_editable
9362 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9365 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
9366 GDK_SELECTION_CLIPBOARD, NULL);
9370 static void compose_allsel_cb(Compose *compose)
9372 if (compose->focused_editable
9374 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9377 entry_allsel(compose->focused_editable);
9380 static void textview_move_beginning_of_line (GtkTextView *text)
9382 GtkTextBuffer *buffer;
9386 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9388 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9389 mark = gtk_text_buffer_get_insert(buffer);
9390 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9391 gtk_text_iter_set_line_offset(&ins, 0);
9392 gtk_text_buffer_place_cursor(buffer, &ins);
9395 static void textview_move_forward_character (GtkTextView *text)
9397 GtkTextBuffer *buffer;
9401 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9403 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9404 mark = gtk_text_buffer_get_insert(buffer);
9405 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9406 if (gtk_text_iter_forward_cursor_position(&ins))
9407 gtk_text_buffer_place_cursor(buffer, &ins);
9410 static void textview_move_backward_character (GtkTextView *text)
9412 GtkTextBuffer *buffer;
9416 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9418 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9419 mark = gtk_text_buffer_get_insert(buffer);
9420 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9421 if (gtk_text_iter_backward_cursor_position(&ins))
9422 gtk_text_buffer_place_cursor(buffer, &ins);
9425 static void textview_move_forward_word (GtkTextView *text)
9427 GtkTextBuffer *buffer;
9432 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9434 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9435 mark = gtk_text_buffer_get_insert(buffer);
9436 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9437 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
9438 if (gtk_text_iter_forward_word_ends(&ins, count)) {
9439 gtk_text_iter_backward_word_start(&ins);
9440 gtk_text_buffer_place_cursor(buffer, &ins);
9444 static void textview_move_backward_word (GtkTextView *text)
9446 GtkTextBuffer *buffer;
9451 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9453 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9454 mark = gtk_text_buffer_get_insert(buffer);
9455 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9456 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
9457 if (gtk_text_iter_backward_word_starts(&ins, 1))
9458 gtk_text_buffer_place_cursor(buffer, &ins);
9461 static void textview_move_end_of_line (GtkTextView *text)
9463 GtkTextBuffer *buffer;
9467 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9469 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9470 mark = gtk_text_buffer_get_insert(buffer);
9471 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9472 if (gtk_text_iter_forward_to_line_end(&ins))
9473 gtk_text_buffer_place_cursor(buffer, &ins);
9476 static void textview_move_next_line (GtkTextView *text)
9478 GtkTextBuffer *buffer;
9483 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9485 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9486 mark = gtk_text_buffer_get_insert(buffer);
9487 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9488 offset = gtk_text_iter_get_line_offset(&ins);
9489 if (gtk_text_iter_forward_line(&ins)) {
9490 gtk_text_iter_set_line_offset(&ins, offset);
9491 gtk_text_buffer_place_cursor(buffer, &ins);
9495 static void textview_move_previous_line (GtkTextView *text)
9497 GtkTextBuffer *buffer;
9502 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9504 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9505 mark = gtk_text_buffer_get_insert(buffer);
9506 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9507 offset = gtk_text_iter_get_line_offset(&ins);
9508 if (gtk_text_iter_backward_line(&ins)) {
9509 gtk_text_iter_set_line_offset(&ins, offset);
9510 gtk_text_buffer_place_cursor(buffer, &ins);
9514 static void textview_delete_forward_character (GtkTextView *text)
9516 GtkTextBuffer *buffer;
9518 GtkTextIter ins, end_iter;
9520 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9522 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9523 mark = gtk_text_buffer_get_insert(buffer);
9524 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9526 if (gtk_text_iter_forward_char(&end_iter)) {
9527 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9531 static void textview_delete_backward_character (GtkTextView *text)
9533 GtkTextBuffer *buffer;
9535 GtkTextIter ins, end_iter;
9537 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9539 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9540 mark = gtk_text_buffer_get_insert(buffer);
9541 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9543 if (gtk_text_iter_backward_char(&end_iter)) {
9544 gtk_text_buffer_delete(buffer, &end_iter, &ins);
9548 static void textview_delete_forward_word (GtkTextView *text)
9550 GtkTextBuffer *buffer;
9552 GtkTextIter ins, end_iter;
9554 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9556 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9557 mark = gtk_text_buffer_get_insert(buffer);
9558 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9560 if (gtk_text_iter_forward_word_end(&end_iter)) {
9561 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9565 static void textview_delete_backward_word (GtkTextView *text)
9567 GtkTextBuffer *buffer;
9569 GtkTextIter ins, end_iter;
9571 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9573 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9574 mark = gtk_text_buffer_get_insert(buffer);
9575 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9577 if (gtk_text_iter_backward_word_start(&end_iter)) {
9578 gtk_text_buffer_delete(buffer, &end_iter, &ins);
9582 static void textview_delete_line (GtkTextView *text)
9584 GtkTextBuffer *buffer;
9586 GtkTextIter ins, start_iter, end_iter;
9588 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9590 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9591 mark = gtk_text_buffer_get_insert(buffer);
9592 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9595 gtk_text_iter_set_line_offset(&start_iter, 0);
9598 if (gtk_text_iter_ends_line(&end_iter)){
9599 if (!gtk_text_iter_forward_char(&end_iter))
9600 gtk_text_iter_backward_char(&start_iter);
9603 gtk_text_iter_forward_to_line_end(&end_iter);
9604 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
9607 static void textview_delete_to_line_end (GtkTextView *text)
9609 GtkTextBuffer *buffer;
9611 GtkTextIter ins, end_iter;
9613 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9615 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9616 mark = gtk_text_buffer_get_insert(buffer);
9617 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9619 if (gtk_text_iter_ends_line(&end_iter))
9620 gtk_text_iter_forward_char(&end_iter);
9622 gtk_text_iter_forward_to_line_end(&end_iter);
9623 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9626 static void compose_advanced_action_cb(Compose *compose,
9627 ComposeCallAdvancedAction action)
9629 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9631 void (*do_action) (GtkTextView *text);
9632 } action_table[] = {
9633 {textview_move_beginning_of_line},
9634 {textview_move_forward_character},
9635 {textview_move_backward_character},
9636 {textview_move_forward_word},
9637 {textview_move_backward_word},
9638 {textview_move_end_of_line},
9639 {textview_move_next_line},
9640 {textview_move_previous_line},
9641 {textview_delete_forward_character},
9642 {textview_delete_backward_character},
9643 {textview_delete_forward_word},
9644 {textview_delete_backward_word},
9645 {textview_delete_line},
9646 {textview_delete_to_line_end}
9649 if (!GTK_WIDGET_HAS_FOCUS(text)) return;
9651 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
9652 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
9653 if (action_table[action].do_action)
9654 action_table[action].do_action(text);
9656 g_warning("Not implemented yet.");
9660 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
9664 if (GTK_IS_EDITABLE(widget)) {
9665 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
9666 gtk_editable_set_position(GTK_EDITABLE(widget),
9669 if (widget->parent && widget->parent->parent
9670 && widget->parent->parent->parent) {
9671 if (GTK_IS_SCROLLED_WINDOW(widget->parent->parent->parent)) {
9672 gint y = widget->allocation.y;
9673 gint height = widget->allocation.height;
9674 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
9675 (GTK_SCROLLED_WINDOW(widget->parent->parent->parent));
9677 if (y < (int)shown->value) {
9678 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown), y - 1);
9680 if (y + height > (int)shown->value + (int)shown->page_size) {
9681 if (y - height - 1 < (int)shown->upper - (int)shown->page_size) {
9682 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
9683 y + height - (int)shown->page_size - 1);
9685 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
9686 (int)shown->upper - (int)shown->page_size - 1);
9693 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
9694 compose->focused_editable = widget;
9697 if (GTK_IS_TEXT_VIEW(widget)
9698 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
9699 gtk_widget_ref(compose->notebook);
9700 gtk_widget_ref(compose->edit_vbox);
9701 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
9702 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
9703 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
9704 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
9705 gtk_widget_unref(compose->notebook);
9706 gtk_widget_unref(compose->edit_vbox);
9707 g_signal_handlers_block_by_func(G_OBJECT(widget),
9708 G_CALLBACK(compose_grab_focus_cb),
9710 gtk_widget_grab_focus(widget);
9711 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
9712 G_CALLBACK(compose_grab_focus_cb),
9714 } else if (!GTK_IS_TEXT_VIEW(widget)
9715 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
9716 gtk_widget_ref(compose->notebook);
9717 gtk_widget_ref(compose->edit_vbox);
9718 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
9719 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
9720 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
9721 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
9722 gtk_widget_unref(compose->notebook);
9723 gtk_widget_unref(compose->edit_vbox);
9724 g_signal_handlers_block_by_func(G_OBJECT(widget),
9725 G_CALLBACK(compose_grab_focus_cb),
9727 gtk_widget_grab_focus(widget);
9728 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
9729 G_CALLBACK(compose_grab_focus_cb),
9735 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
9737 compose->modified = TRUE;
9739 compose_set_title(compose);
9743 static void compose_wrap_cb(gpointer data, guint action, GtkWidget *widget)
9745 Compose *compose = (Compose *)data;
9748 compose_wrap_all_full(compose, TRUE);
9750 compose_beautify_paragraph(compose, NULL, TRUE);
9753 static void compose_find_cb(gpointer data, guint action, GtkWidget *widget)
9755 Compose *compose = (Compose *)data;
9757 message_search_compose(compose);
9760 static void compose_toggle_autowrap_cb(gpointer data, guint action,
9763 Compose *compose = (Compose *)data;
9764 compose->autowrap = GTK_CHECK_MENU_ITEM(widget)->active;
9765 if (compose->autowrap)
9766 compose_wrap_all_full(compose, TRUE);
9767 compose->autowrap = GTK_CHECK_MENU_ITEM(widget)->active;
9770 static void compose_toggle_sign_cb(gpointer data, guint action,
9773 Compose *compose = (Compose *)data;
9775 if (GTK_CHECK_MENU_ITEM(widget)->active)
9776 compose->use_signing = TRUE;
9778 compose->use_signing = FALSE;
9781 static void compose_toggle_encrypt_cb(gpointer data, guint action,
9784 Compose *compose = (Compose *)data;
9786 if (GTK_CHECK_MENU_ITEM(widget)->active)
9787 compose->use_encryption = TRUE;
9789 compose->use_encryption = FALSE;
9792 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
9794 g_free(compose->privacy_system);
9796 compose->privacy_system = g_strdup(account->default_privacy_system);
9797 compose_update_privacy_system_menu_item(compose, warn);
9800 static void compose_toggle_ruler_cb(gpointer data, guint action,
9803 Compose *compose = (Compose *)data;
9805 if (GTK_CHECK_MENU_ITEM(widget)->active) {
9806 gtk_widget_show(compose->ruler_hbox);
9807 prefs_common.show_ruler = TRUE;
9809 gtk_widget_hide(compose->ruler_hbox);
9810 gtk_widget_queue_resize(compose->edit_vbox);
9811 prefs_common.show_ruler = FALSE;
9815 static void compose_attach_drag_received_cb (GtkWidget *widget,
9816 GdkDragContext *context,
9819 GtkSelectionData *data,
9824 Compose *compose = (Compose *)user_data;
9827 if (gdk_atom_name(data->type) &&
9828 !strcmp(gdk_atom_name(data->type), "text/uri-list")
9829 && gtk_drag_get_source_widget(context) !=
9830 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
9831 list = uri_list_extract_filenames((const gchar *)data->data);
9832 for (tmp = list; tmp != NULL; tmp = tmp->next) {
9833 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
9834 compose_attach_append
9835 (compose, (const gchar *)tmp->data,
9836 utf8_filename, NULL);
9837 g_free(utf8_filename);
9839 if (list) compose_changed_cb(NULL, compose);
9840 list_free_strings(list);
9842 } else if (gtk_drag_get_source_widget(context)
9843 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
9844 /* comes from our summaryview */
9845 SummaryView * summaryview = NULL;
9846 GSList * list = NULL, *cur = NULL;
9848 if (mainwindow_get_mainwindow())
9849 summaryview = mainwindow_get_mainwindow()->summaryview;
9852 list = summary_get_selected_msg_list(summaryview);
9854 for (cur = list; cur; cur = cur->next) {
9855 MsgInfo *msginfo = (MsgInfo *)cur->data;
9858 file = procmsg_get_message_file_full(msginfo,
9861 compose_attach_append(compose, (const gchar *)file,
9862 (const gchar *)file, "message/rfc822");
9870 static gboolean compose_drag_drop(GtkWidget *widget,
9871 GdkDragContext *drag_context,
9873 guint time, gpointer user_data)
9875 /* not handling this signal makes compose_insert_drag_received_cb
9880 static void compose_insert_drag_received_cb (GtkWidget *widget,
9881 GdkDragContext *drag_context,
9884 GtkSelectionData *data,
9889 Compose *compose = (Compose *)user_data;
9892 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
9894 if (gdk_atom_name(data->type) && !strcmp(gdk_atom_name(data->type), "text/uri-list")) {
9895 AlertValue val = G_ALERTDEFAULT;
9897 list = uri_list_extract_filenames((const gchar *)data->data);
9899 if (list == NULL && strstr((gchar *)(data->data), "://")) {
9900 /* Assume a list of no files, and data has ://, is a remote link */
9901 gchar *tmpdata = g_strstrip(g_strdup((const gchar *)data->data));
9902 gchar *tmpfile = get_tmp_file();
9903 str_write_to_file(tmpdata, tmpfile);
9905 compose_insert_file(compose, tmpfile);
9906 claws_unlink(tmpfile);
9908 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9909 compose_beautify_paragraph(compose, NULL, TRUE);
9912 switch (prefs_common.compose_dnd_mode) {
9913 case COMPOSE_DND_ASK:
9914 val = alertpanel_full(_("Insert or attach?"),
9915 _("Do you want to insert the contents of the file(s) "
9916 "into the message body, or attach it to the email?"),
9917 GTK_STOCK_CANCEL, _("+_Insert"), _("_Attach"),
9918 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
9920 case COMPOSE_DND_INSERT:
9921 val = G_ALERTALTERNATE;
9923 case COMPOSE_DND_ATTACH:
9927 /* unexpected case */
9928 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
9931 if (val & G_ALERTDISABLE) {
9932 val &= ~G_ALERTDISABLE;
9933 /* remember what action to perform by default, only if we don't click Cancel */
9934 if (val == G_ALERTALTERNATE)
9935 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
9936 else if (val == G_ALERTOTHER)
9937 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
9940 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
9941 gtk_drag_finish(drag_context, FALSE, FALSE, time);
9942 list_free_strings(list);
9945 } else if (val == G_ALERTOTHER) {
9946 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
9947 list_free_strings(list);
9952 for (tmp = list; tmp != NULL; tmp = tmp->next) {
9953 compose_insert_file(compose, (const gchar *)tmp->data);
9955 list_free_strings(list);
9957 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9960 #if GTK_CHECK_VERSION(2, 8, 0)
9961 /* do nothing, handled by GTK */
9963 gchar *tmpfile = get_tmp_file();
9964 str_write_to_file((const gchar *)data->data, tmpfile);
9965 compose_insert_file(compose, tmpfile);
9966 claws_unlink(tmpfile);
9968 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9972 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9975 static void compose_header_drag_received_cb (GtkWidget *widget,
9976 GdkDragContext *drag_context,
9979 GtkSelectionData *data,
9984 GtkEditable *entry = (GtkEditable *)user_data;
9985 gchar *email = (gchar *)data->data;
9987 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
9990 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
9991 gchar *decoded=g_new(gchar, strlen(email));
9994 email += strlen("mailto:");
9995 decode_uri(decoded, email); /* will fit */
9996 gtk_editable_delete_text(entry, 0, -1);
9997 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
9998 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10002 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10005 static void compose_toggle_return_receipt_cb(gpointer data, guint action,
10008 Compose *compose = (Compose *)data;
10010 if (GTK_CHECK_MENU_ITEM(widget)->active)
10011 compose->return_receipt = TRUE;
10013 compose->return_receipt = FALSE;
10016 static void compose_toggle_remove_refs_cb(gpointer data, guint action,
10019 Compose *compose = (Compose *)data;
10021 if (GTK_CHECK_MENU_ITEM(widget)->active)
10022 compose->remove_references = TRUE;
10024 compose->remove_references = FALSE;
10027 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
10028 GdkEventKey *event,
10029 ComposeHeaderEntry *headerentry)
10031 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
10032 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
10033 !(event->state & GDK_MODIFIER_MASK) &&
10034 (event->keyval == GDK_BackSpace) &&
10035 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
10036 gtk_container_remove
10037 (GTK_CONTAINER(headerentry->compose->header_table),
10038 headerentry->combo);
10039 gtk_container_remove
10040 (GTK_CONTAINER(headerentry->compose->header_table),
10041 headerentry->entry);
10042 headerentry->compose->header_list =
10043 g_slist_remove(headerentry->compose->header_list,
10045 g_free(headerentry);
10046 } else if (event->keyval == GDK_Tab) {
10047 if (headerentry->compose->header_last == headerentry) {
10048 /* Override default next focus, and give it to subject_entry
10049 * instead of notebook tabs
10051 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
10052 gtk_widget_grab_focus(headerentry->compose->subject_entry);
10059 static gboolean compose_headerentry_changed_cb(GtkWidget *entry,
10060 ComposeHeaderEntry *headerentry)
10062 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
10063 compose_create_header_entry(headerentry->compose);
10064 g_signal_handlers_disconnect_matched
10065 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
10066 0, 0, NULL, NULL, headerentry);
10068 /* Automatically scroll down */
10069 compose_show_first_last_header(headerentry->compose, FALSE);
10075 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
10077 GtkAdjustment *vadj;
10079 g_return_if_fail(compose);
10080 g_return_if_fail(GTK_IS_WIDGET(compose->header_table));
10081 g_return_if_fail(GTK_IS_VIEWPORT(compose->header_table->parent));
10083 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(compose->header_table->parent));
10084 gtk_adjustment_set_value(vadj, (show_first ? vadj->lower : vadj->upper));
10085 gtk_adjustment_changed(vadj);
10088 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
10089 const gchar *text, gint len, Compose *compose)
10091 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
10092 (G_OBJECT(compose->text), "paste_as_quotation"));
10095 g_return_if_fail(text != NULL);
10097 g_signal_handlers_block_by_func(G_OBJECT(buffer),
10098 G_CALLBACK(text_inserted),
10100 if (paste_as_quotation) {
10102 const gchar *qmark;
10104 GtkTextIter start_iter;
10107 len = strlen(text);
10109 new_text = g_strndup(text, len);
10111 qmark = compose_quote_char_from_context(compose);
10113 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
10114 gtk_text_buffer_place_cursor(buffer, iter);
10116 pos = gtk_text_iter_get_offset(iter);
10118 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
10119 _("Quote format error at line %d."));
10120 quote_fmt_reset_vartable();
10122 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
10123 GINT_TO_POINTER(paste_as_quotation - 1));
10125 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
10126 gtk_text_buffer_place_cursor(buffer, iter);
10127 gtk_text_buffer_delete_mark(buffer, mark);
10129 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
10130 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
10131 compose_beautify_paragraph(compose, &start_iter, FALSE);
10132 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
10133 gtk_text_buffer_delete_mark(buffer, mark);
10135 if (strcmp(text, "\n") || compose->automatic_break
10136 || gtk_text_iter_starts_line(iter))
10137 gtk_text_buffer_insert(buffer, iter, text, len);
10139 /* check if the preceding is just whitespace or quote */
10140 GtkTextIter start_line;
10141 gchar *tmp = NULL, *quote = NULL;
10142 gint quote_len = 0, is_normal = 0;
10143 start_line = *iter;
10144 gtk_text_iter_set_line_offset(&start_line, 0);
10145 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
10147 if (*tmp == '\0') {
10150 quote = compose_get_quote_str(buffer, &start_line, "e_len);
10158 gtk_text_buffer_insert(buffer, iter, text, len);
10160 gtk_text_buffer_insert_with_tags_by_name(buffer,
10161 iter, text, len, "no_join", NULL);
10166 if (!paste_as_quotation) {
10167 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
10168 compose_beautify_paragraph(compose, iter, FALSE);
10169 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
10170 gtk_text_buffer_delete_mark(buffer, mark);
10173 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
10174 G_CALLBACK(text_inserted),
10176 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
10178 if (prefs_common.autosave &&
10179 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
10180 compose->draft_timeout_tag != -2 /* disabled while loading */)
10181 compose->draft_timeout_tag = g_timeout_add
10182 (500, (GtkFunction) compose_defer_auto_save_draft, compose);
10184 static gint compose_defer_auto_save_draft(Compose *compose)
10186 compose->draft_timeout_tag = -1;
10187 compose_draft_cb((gpointer)compose, COMPOSE_AUTO_SAVE, NULL);
10192 static void compose_check_all(Compose *compose)
10194 if (compose->gtkaspell)
10195 gtkaspell_check_all(compose->gtkaspell);
10198 static void compose_highlight_all(Compose *compose)
10200 if (compose->gtkaspell)
10201 gtkaspell_highlight_all(compose->gtkaspell);
10204 static void compose_check_backwards(Compose *compose)
10206 if (compose->gtkaspell)
10207 gtkaspell_check_backwards(compose->gtkaspell);
10209 GtkItemFactory *ifactory;
10210 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
10211 menu_set_sensitive(ifactory, "/Edit/Check backwards misspelled word", FALSE);
10212 menu_set_sensitive(ifactory, "/Edit/Forward to next misspelled word", FALSE);
10216 static void compose_check_forwards_go(Compose *compose)
10218 if (compose->gtkaspell)
10219 gtkaspell_check_forwards_go(compose->gtkaspell);
10221 GtkItemFactory *ifactory;
10222 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
10223 menu_set_sensitive(ifactory, "/Edit/Check backwards misspelled word", FALSE);
10224 menu_set_sensitive(ifactory, "/Edit/Forward to next misspelled word", FALSE);
10230 *\brief Guess originating forward account from MsgInfo and several
10231 * "common preference" settings. Return NULL if no guess.
10233 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
10235 PrefsAccount *account = NULL;
10237 g_return_val_if_fail(msginfo, NULL);
10238 g_return_val_if_fail(msginfo->folder, NULL);
10239 g_return_val_if_fail(msginfo->folder->prefs, NULL);
10241 if (msginfo->folder->prefs->enable_default_account)
10242 account = account_find_from_id(msginfo->folder->prefs->default_account);
10245 account = msginfo->folder->folder->account;
10247 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
10249 Xstrdup_a(to, msginfo->to, return NULL);
10250 extract_address(to);
10251 account = account_find_from_address(to, FALSE);
10254 if (!account && prefs_common.forward_account_autosel) {
10255 gchar cc[BUFFSIZE];
10256 if (!procheader_get_header_from_msginfo
10257 (msginfo, cc,sizeof cc , "Cc:")) {
10258 gchar *buf = cc + strlen("Cc:");
10259 extract_address(buf);
10260 account = account_find_from_address(buf, FALSE);
10264 if (!account && prefs_common.forward_account_autosel) {
10265 gchar deliveredto[BUFFSIZE];
10266 if (!procheader_get_header_from_msginfo
10267 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
10268 gchar *buf = deliveredto + strlen("Delivered-To:");
10269 extract_address(buf);
10270 account = account_find_from_address(buf, FALSE);
10277 gboolean compose_close(Compose *compose)
10281 if (!g_mutex_trylock(compose->mutex)) {
10282 /* we have to wait for the (possibly deferred by auto-save)
10283 * drafting to be done, before destroying the compose under
10285 debug_print("waiting for drafting to finish...\n");
10286 compose_allow_user_actions(compose, FALSE);
10287 g_timeout_add (500, (GSourceFunc) compose_close, compose);
10290 g_return_val_if_fail(compose, FALSE);
10291 gtkut_widget_get_uposition(compose->window, &x, &y);
10292 prefs_common.compose_x = x;
10293 prefs_common.compose_y = y;
10294 g_mutex_unlock(compose->mutex);
10295 compose_destroy(compose);
10300 * Add entry field for each address in list.
10301 * \param compose E-Mail composition object.
10302 * \param listAddress List of (formatted) E-Mail addresses.
10304 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
10307 node = listAddress;
10309 addr = ( gchar * ) node->data;
10310 compose_entry_append( compose, addr, COMPOSE_TO );
10311 node = g_list_next( node );
10315 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
10316 guint action, gboolean opening_multiple)
10318 gchar *body = NULL;
10319 GSList *new_msglist = NULL;
10320 MsgInfo *tmp_msginfo = NULL;
10321 gboolean originally_enc = FALSE;
10322 Compose *compose = NULL;
10324 g_return_if_fail(msgview != NULL);
10326 g_return_if_fail(msginfo_list != NULL);
10328 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
10329 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
10330 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
10332 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
10333 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
10334 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
10335 orig_msginfo, mimeinfo);
10336 if (tmp_msginfo != NULL) {
10337 new_msglist = g_slist_append(NULL, tmp_msginfo);
10339 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
10340 tmp_msginfo->folder = orig_msginfo->folder;
10341 tmp_msginfo->msgnum = orig_msginfo->msgnum;
10342 if (orig_msginfo->tags)
10343 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
10348 if (!opening_multiple)
10349 body = messageview_get_selection(msgview);
10352 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
10353 procmsg_msginfo_free(tmp_msginfo);
10354 g_slist_free(new_msglist);
10356 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
10358 if (compose && originally_enc) {
10359 compose_force_encryption(compose, compose->account, FALSE);
10365 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
10368 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
10369 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
10370 GSList *cur = msginfo_list;
10371 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
10372 "messages. Opening the windows "
10373 "could take some time. Do you "
10374 "want to continue?"),
10375 g_slist_length(msginfo_list));
10376 if (g_slist_length(msginfo_list) > 9
10377 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
10378 != G_ALERTALTERNATE) {
10383 /* We'll open multiple compose windows */
10384 /* let the WM place the next windows */
10385 compose_force_window_origin = FALSE;
10386 for (; cur; cur = cur->next) {
10388 tmplist.data = cur->data;
10389 tmplist.next = NULL;
10390 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
10392 compose_force_window_origin = TRUE;
10394 /* forwarding multiple mails as attachments is done via a
10395 * single compose window */
10396 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
10400 void compose_set_position(Compose *compose, gint pos)
10402 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10404 gtkut_text_view_set_position(text, pos);
10407 gboolean compose_search_string(Compose *compose,
10408 const gchar *str, gboolean case_sens)
10410 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10412 return gtkut_text_view_search_string(text, str, case_sens);
10415 gboolean compose_search_string_backward(Compose *compose,
10416 const gchar *str, gboolean case_sens)
10418 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10420 return gtkut_text_view_search_string_backward(text, str, case_sens);
10423 /* allocate a msginfo structure and populate its data from a compose data structure */
10424 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
10426 MsgInfo *newmsginfo;
10428 gchar buf[BUFFSIZE];
10430 g_return_val_if_fail( compose != NULL, NULL );
10432 newmsginfo = procmsg_msginfo_new();
10435 get_rfc822_date(buf, sizeof(buf));
10436 newmsginfo->date = g_strdup(buf);
10439 if (compose->from_name) {
10440 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
10441 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
10445 if (compose->subject_entry)
10446 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
10448 /* to, cc, reply-to, newsgroups */
10449 for (list = compose->header_list; list; list = list->next) {
10450 gchar *header = gtk_editable_get_chars(
10452 GTK_BIN(((ComposeHeaderEntry *)list->data)->combo)->child), 0, -1);
10453 gchar *entry = gtk_editable_get_chars(
10454 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
10456 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
10457 if ( newmsginfo->to == NULL ) {
10458 newmsginfo->to = g_strdup(entry);
10459 } else if (entry && *entry) {
10460 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
10461 g_free(newmsginfo->to);
10462 newmsginfo->to = tmp;
10465 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
10466 if ( newmsginfo->cc == NULL ) {
10467 newmsginfo->cc = g_strdup(entry);
10468 } else if (entry && *entry) {
10469 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
10470 g_free(newmsginfo->cc);
10471 newmsginfo->cc = tmp;
10474 if ( strcasecmp(header,
10475 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
10476 if ( newmsginfo->newsgroups == NULL ) {
10477 newmsginfo->newsgroups = g_strdup(entry);
10478 } else if (entry && *entry) {
10479 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
10480 g_free(newmsginfo->newsgroups);
10481 newmsginfo->newsgroups = tmp;
10489 /* other data is unset */
10495 /* update compose's dictionaries from folder dict settings */
10496 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
10497 FolderItem *folder_item)
10499 g_return_if_fail(compose != NULL);
10501 if (compose->gtkaspell && folder_item && folder_item->prefs) {
10502 FolderItemPrefs *prefs = folder_item->prefs;
10504 if (prefs->enable_default_dictionary)
10505 gtkaspell_change_dict(compose->gtkaspell,
10506 prefs->default_dictionary, FALSE);
10507 if (folder_item->prefs->enable_default_alt_dictionary)
10508 gtkaspell_change_alt_dict(compose->gtkaspell,
10509 prefs->default_alt_dictionary);
10510 if (prefs->enable_default_dictionary
10511 || prefs->enable_default_alt_dictionary)
10512 compose_spell_menu_changed(compose);