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 2 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, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
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"
137 #define N_ATTACH_COLS (N_COL_COLUMNS)
141 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE,
142 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER,
143 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER,
144 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD,
145 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD,
146 COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE,
147 COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE,
148 COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE,
149 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER,
150 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER,
151 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD,
152 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD,
153 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE,
154 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE_N,
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,
205 static void compose_entry_mark_default_to (Compose *compose,
206 const gchar *address);
207 static Compose *compose_followup_and_reply_to (MsgInfo *msginfo,
208 ComposeQuoteMode quote_mode,
212 static Compose *compose_forward_multiple (PrefsAccount *account,
213 GSList *msginfo_list);
214 static Compose *compose_reply (MsgInfo *msginfo,
215 ComposeQuoteMode quote_mode,
220 static Compose *compose_reply_mode (ComposeMode mode,
221 GSList *msginfo_list,
223 static void compose_template_apply_fields(Compose *compose, Template *tmpl);
224 static void compose_update_privacy_systems_menu(Compose *compose);
226 static GtkWidget *compose_account_option_menu_create
228 static void compose_set_out_encoding (Compose *compose);
229 static void compose_set_template_menu (Compose *compose);
230 static void compose_template_apply (Compose *compose,
233 static void compose_destroy (Compose *compose);
235 static void compose_entries_set (Compose *compose,
236 const gchar *mailto);
237 static gint compose_parse_header (Compose *compose,
239 static gchar *compose_parse_references (const gchar *ref,
242 static gchar *compose_quote_fmt (Compose *compose,
248 gboolean need_unescape,
249 const gchar *err_msg);
251 static void compose_reply_set_entry (Compose *compose,
257 followup_and_reply_to);
258 static void compose_reedit_set_entry (Compose *compose,
261 static void compose_insert_sig (Compose *compose,
263 static gchar *compose_get_signature_str (Compose *compose);
264 static ComposeInsertResult compose_insert_file (Compose *compose,
267 static gboolean compose_attach_append (Compose *compose,
270 const gchar *content_type);
271 static void compose_attach_parts (Compose *compose,
274 static void compose_beautify_paragraph (Compose *compose,
275 GtkTextIter *par_iter,
277 static void compose_wrap_all (Compose *compose);
278 static void compose_wrap_all_full (Compose *compose,
281 static void compose_set_title (Compose *compose);
282 static void compose_select_account (Compose *compose,
283 PrefsAccount *account,
286 static PrefsAccount *compose_current_mail_account(void);
287 /* static gint compose_send (Compose *compose); */
288 static gboolean compose_check_for_valid_recipient
290 static gboolean compose_check_entries (Compose *compose,
291 gboolean check_everything);
292 static gint compose_write_to_file (Compose *compose,
295 gboolean attach_parts);
296 static gint compose_write_body_to_file (Compose *compose,
298 static gint compose_remove_reedit_target (Compose *compose,
300 static void compose_remove_draft (Compose *compose);
301 static gint compose_queue_sub (Compose *compose,
305 gboolean check_subject,
306 gboolean remove_reedit_target);
307 static void compose_add_attachments (Compose *compose,
309 static gchar *compose_get_header (Compose *compose);
311 static void compose_convert_header (Compose *compose,
316 gboolean addr_field);
318 static void compose_attach_info_free (AttachInfo *ainfo);
319 static void compose_attach_remove_selected (Compose *compose);
321 static void compose_attach_property (Compose *compose);
322 static void compose_attach_property_create (gboolean *cancelled);
323 static void attach_property_ok (GtkWidget *widget,
324 gboolean *cancelled);
325 static void attach_property_cancel (GtkWidget *widget,
326 gboolean *cancelled);
327 static gint attach_property_delete_event (GtkWidget *widget,
329 gboolean *cancelled);
330 static gboolean attach_property_key_pressed (GtkWidget *widget,
332 gboolean *cancelled);
334 static void compose_exec_ext_editor (Compose *compose);
336 static gint compose_exec_ext_editor_real (const gchar *file);
337 static gboolean compose_ext_editor_kill (Compose *compose);
338 static gboolean compose_input_cb (GIOChannel *source,
339 GIOCondition condition,
341 static void compose_set_ext_editor_sensitive (Compose *compose,
343 #endif /* G_OS_UNIX */
345 static void compose_undo_state_changed (UndoMain *undostruct,
350 static void compose_create_header_entry (Compose *compose);
351 static void compose_add_header_entry (Compose *compose, const gchar *header, gchar *text);
352 static void compose_remove_header_entries(Compose *compose);
354 static void compose_update_priority_menu_item(Compose * compose);
356 static void compose_spell_menu_changed (void *data);
358 static void compose_add_field_list ( Compose *compose,
359 GList *listAddress );
361 /* callback functions */
363 static gboolean compose_edit_size_alloc (GtkEditable *widget,
364 GtkAllocation *allocation,
365 GtkSHRuler *shruler);
366 static void account_activated (GtkComboBox *optmenu,
368 static void attach_selected (GtkTreeView *tree_view,
369 GtkTreePath *tree_path,
370 GtkTreeViewColumn *column,
372 static gboolean attach_button_pressed (GtkWidget *widget,
373 GdkEventButton *event,
375 static gboolean attach_key_pressed (GtkWidget *widget,
378 static void compose_send_cb (gpointer data,
381 static void compose_send_later_cb (gpointer data,
385 static void compose_draft_cb (gpointer data,
389 static void compose_attach_cb (gpointer data,
392 static void compose_insert_file_cb (gpointer data,
395 static void compose_insert_sig_cb (gpointer data,
399 static void compose_close_cb (gpointer data,
403 static void compose_set_encoding_cb (gpointer data,
407 static void compose_address_cb (gpointer data,
410 static void compose_template_activate_cb(GtkWidget *widget,
413 static void compose_ext_editor_cb (gpointer data,
417 static gint compose_delete_cb (GtkWidget *widget,
421 static void compose_undo_cb (Compose *compose);
422 static void compose_redo_cb (Compose *compose);
423 static void compose_cut_cb (Compose *compose);
424 static void compose_copy_cb (Compose *compose);
425 static void compose_paste_cb (Compose *compose);
426 static void compose_paste_as_quote_cb (Compose *compose);
427 static void compose_paste_no_wrap_cb (Compose *compose);
428 static void compose_paste_wrap_cb (Compose *compose);
429 static void compose_allsel_cb (Compose *compose);
431 static void compose_advanced_action_cb (Compose *compose,
432 ComposeCallAdvancedAction action);
434 static void compose_grab_focus_cb (GtkWidget *widget,
437 static void compose_changed_cb (GtkTextBuffer *textbuf,
440 static void compose_wrap_cb (gpointer data,
443 static void compose_find_cb (gpointer data,
446 static void compose_toggle_autowrap_cb (gpointer data,
450 static void compose_toggle_ruler_cb (gpointer data,
453 static void compose_toggle_sign_cb (gpointer data,
456 static void compose_toggle_encrypt_cb (gpointer data,
459 static void compose_set_privacy_system_cb(GtkWidget *widget,
461 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn);
462 static void activate_privacy_system (Compose *compose,
463 PrefsAccount *account,
465 static void compose_use_signing(Compose *compose, gboolean use_signing);
466 static void compose_use_encryption(Compose *compose, gboolean use_encryption);
467 static void compose_toggle_return_receipt_cb(gpointer data, guint action,
469 static void compose_toggle_remove_refs_cb(gpointer data, guint action,
471 static void compose_set_priority_cb (gpointer data,
474 static void compose_reply_change_mode (gpointer data,
478 static void compose_attach_drag_received_cb (GtkWidget *widget,
479 GdkDragContext *drag_context,
482 GtkSelectionData *data,
486 static void compose_insert_drag_received_cb (GtkWidget *widget,
487 GdkDragContext *drag_context,
490 GtkSelectionData *data,
494 static void compose_header_drag_received_cb (GtkWidget *widget,
495 GdkDragContext *drag_context,
498 GtkSelectionData *data,
503 static gboolean compose_drag_drop (GtkWidget *widget,
504 GdkDragContext *drag_context,
506 guint time, gpointer user_data);
508 static void text_inserted (GtkTextBuffer *buffer,
513 static Compose *compose_generic_reply(MsgInfo *msginfo,
514 ComposeQuoteMode quote_mode,
518 gboolean followup_and_reply_to,
521 static gboolean compose_headerentry_changed_cb (GtkWidget *entry,
522 ComposeHeaderEntry *headerentry);
523 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
525 ComposeHeaderEntry *headerentry);
527 static void compose_show_first_last_header (Compose *compose, gboolean show_first);
529 static void compose_allow_user_actions (Compose *compose, gboolean allow);
532 static void compose_check_all (Compose *compose);
533 static void compose_highlight_all (Compose *compose);
534 static void compose_check_backwards (Compose *compose);
535 static void compose_check_forwards_go (Compose *compose);
538 static gint compose_defer_auto_save_draft (Compose *compose);
539 static PrefsAccount *compose_guess_forward_account_from_msginfo (MsgInfo *msginfo);
541 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose);
544 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
545 FolderItem *folder_item);
548 static GtkItemFactoryEntry compose_popup_entries[] =
550 {N_("/_Add..."), NULL, compose_attach_cb, 0, NULL},
551 {N_("/_Remove"), NULL, compose_attach_remove_selected, 0, NULL},
552 {"/---", NULL, NULL, 0, "<Separator>"},
553 {N_("/_Properties..."), NULL, compose_attach_property, 0, NULL}
556 static GtkItemFactoryEntry compose_entries[] =
558 {N_("/_Message"), NULL, NULL, 0, "<Branch>"},
559 {N_("/_Message/S_end"), "<control>Return",
560 compose_send_cb, 0, NULL},
561 {N_("/_Message/Send _later"), "<shift><control>S",
562 compose_send_later_cb, 0, NULL},
563 {N_("/_Message/---"), NULL, NULL, 0, "<Separator>"},
564 {N_("/_Message/_Attach file"), "<control>M", compose_attach_cb, 0, NULL},
565 {N_("/_Message/_Insert file"), "<control>I", compose_insert_file_cb, 0, NULL},
566 {N_("/_Message/Insert si_gnature"), "<control>G", compose_insert_sig_cb, 0, NULL},
567 {N_("/_Message/---"), NULL, NULL, 0, "<Separator>"},
568 {N_("/_Message/_Save"),
569 "<control>S", compose_draft_cb, COMPOSE_KEEP_EDITING, NULL},
570 {N_("/_Message/---"), NULL, NULL, 0, "<Separator>"},
571 {N_("/_Message/_Close"), "<control>W", compose_close_cb, 0, NULL},
573 {N_("/_Edit"), NULL, NULL, 0, "<Branch>"},
574 {N_("/_Edit/_Undo"), "<control>Z", compose_undo_cb, 0, NULL},
575 {N_("/_Edit/_Redo"), "<control>Y", compose_redo_cb, 0, NULL},
576 {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
577 {N_("/_Edit/Cu_t"), "<control>X", compose_cut_cb, 0, NULL},
578 {N_("/_Edit/_Copy"), "<control>C", compose_copy_cb, 0, NULL},
579 {N_("/_Edit/_Paste"), "<control>V", compose_paste_cb, 0, NULL},
580 {N_("/_Edit/Special paste"), NULL, NULL, 0, "<Branch>"},
581 {N_("/_Edit/Special paste/as _quotation"),
582 NULL, compose_paste_as_quote_cb, 0, NULL},
583 {N_("/_Edit/Special paste/_wrapped"),
584 NULL, compose_paste_wrap_cb, 0, NULL},
585 {N_("/_Edit/Special paste/_unwrapped"),
586 NULL, compose_paste_no_wrap_cb, 0, NULL},
587 {N_("/_Edit/Select _all"), "<control>A", compose_allsel_cb, 0, NULL},
588 {N_("/_Edit/A_dvanced"), NULL, NULL, 0, "<Branch>"},
589 {N_("/_Edit/A_dvanced/Move a character backward"),
591 compose_advanced_action_cb,
592 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER,
594 {N_("/_Edit/A_dvanced/Move a character forward"),
596 compose_advanced_action_cb,
597 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER,
599 {N_("/_Edit/A_dvanced/Move a word backward"),
601 compose_advanced_action_cb,
602 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD,
604 {N_("/_Edit/A_dvanced/Move a word forward"),
606 compose_advanced_action_cb,
607 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD,
609 {N_("/_Edit/A_dvanced/Move to beginning of line"),
610 NULL, /* "<control>A" */
611 compose_advanced_action_cb,
612 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE,
614 {N_("/_Edit/A_dvanced/Move to end of line"),
616 compose_advanced_action_cb,
617 COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE,
619 {N_("/_Edit/A_dvanced/Move to previous line"),
621 compose_advanced_action_cb,
622 COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE,
624 {N_("/_Edit/A_dvanced/Move to next line"),
626 compose_advanced_action_cb,
627 COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE,
629 {N_("/_Edit/A_dvanced/Delete a character backward"),
631 compose_advanced_action_cb,
632 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER,
634 {N_("/_Edit/A_dvanced/Delete a character forward"),
636 compose_advanced_action_cb,
637 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER,
639 {N_("/_Edit/A_dvanced/Delete a word backward"),
640 NULL, /* "<control>W" */
641 compose_advanced_action_cb,
642 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD,
644 {N_("/_Edit/A_dvanced/Delete a word forward"),
645 NULL, /* "<alt>D", */
646 compose_advanced_action_cb,
647 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD,
649 {N_("/_Edit/A_dvanced/Delete line"),
651 compose_advanced_action_cb,
652 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE,
654 {N_("/_Edit/A_dvanced/Delete entire line"),
656 compose_advanced_action_cb,
657 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE_N,
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 (ISO-8859-_1)"),
730 ENC_ACTION(C_ISO_8859_1)},
731 {N_("/_Options/Character _encoding/Western European (ISO-8859-15)"),
732 ENC_ACTION(C_ISO_8859_15)},
733 {N_("/_Options/Character _encoding/Western European (Windows-1252)"),
734 ENC_ACTION(C_WINDOWS_1252)},
735 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
737 {N_("/_Options/Character _encoding/Central European (ISO-8859-_2)"),
738 ENC_ACTION(C_ISO_8859_2)},
739 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
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)},
745 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
747 {N_("/_Options/Character _encoding/Greek (ISO-8859-_7)"),
748 ENC_ACTION(C_ISO_8859_7)},
749 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
751 {N_("/_Options/Character _encoding/Hebrew (ISO-8859-_8)"),
752 ENC_ACTION(C_ISO_8859_8)},
753 {N_("/_Options/Character _encoding/Hebrew (Windows-1255)"),
754 ENC_ACTION(C_WINDOWS_1255)},
755 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
757 {N_("/_Options/Character _encoding/Arabic (ISO-8859-_6)"),
758 ENC_ACTION(C_ISO_8859_6)},
759 {N_("/_Options/Character _encoding/Arabic (Windows-1256)"),
760 ENC_ACTION(C_CP1256)},
761 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
763 {N_("/_Options/Character _encoding/Turkish (ISO-8859-_9)"),
764 ENC_ACTION(C_ISO_8859_9)},
765 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
767 {N_("/_Options/Character _encoding/Cyrillic (ISO-8859-_5)"),
768 ENC_ACTION(C_ISO_8859_5)},
769 {N_("/_Options/Character _encoding/Cyrillic (KOI8-_R)"),
770 ENC_ACTION(C_KOI8_R)},
771 {N_("/_Options/Character _encoding/Cyrillic (KOI8-U)"),
772 ENC_ACTION(C_KOI8_U)},
773 {N_("/_Options/Character _encoding/Cyrillic (Windows-1251)"),
774 ENC_ACTION(C_WINDOWS_1251)},
775 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
777 {N_("/_Options/Character _encoding/Japanese (ISO-2022-_JP)"),
778 ENC_ACTION(C_ISO_2022_JP)},
779 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
781 {N_("/_Options/Character _encoding/Simplified Chinese (_GB2312)"),
782 ENC_ACTION(C_GB2312)},
783 {N_("/_Options/Character _encoding/Simplified Chinese (GBK)"),
785 {N_("/_Options/Character _encoding/Traditional Chinese (_Big5)"),
787 {N_("/_Options/Character _encoding/Traditional Chinese (EUC-_TW)"),
788 ENC_ACTION(C_EUC_TW)},
789 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
791 {N_("/_Options/Character _encoding/Korean (EUC-_KR)"),
792 ENC_ACTION(C_EUC_KR)},
793 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
795 {N_("/_Options/Character _encoding/Thai (TIS-620)"),
796 ENC_ACTION(C_TIS_620)},
797 {N_("/_Options/Character _encoding/Thai (Windows-874)"),
798 ENC_ACTION(C_WINDOWS_874)},
800 {N_("/_Tools"), NULL, NULL, 0, "<Branch>"},
801 {N_("/_Tools/Show _ruler"), NULL, compose_toggle_ruler_cb, 0, "<ToggleItem>"},
802 {N_("/_Tools/_Address book"), "<shift><control>A", compose_address_cb , 0, NULL},
803 {N_("/_Tools/_Template"), NULL, NULL, 0, "<Branch>"},
804 {N_("/_Tools/Actio_ns"), NULL, NULL, 0, "<Branch>"},
805 {N_("/_Help"), NULL, NULL, 0, "<Branch>"},
806 {N_("/_Help/_About"), NULL, about_show, 0, NULL}
809 static GtkTargetEntry compose_mime_types[] =
811 {"text/uri-list", 0, 0},
812 {"UTF8_STRING", 0, 0},
816 static gboolean compose_put_existing_to_front(MsgInfo *info)
818 GList *compose_list = compose_get_compose_list();
822 for (elem = compose_list; elem != NULL && elem->data != NULL;
824 Compose *c = (Compose*)elem->data;
826 if (!c->targetinfo || !c->targetinfo->msgid ||
830 if (!strcmp(c->targetinfo->msgid, info->msgid)) {
831 gtkut_window_popup(c->window);
839 static GdkColor quote_color1 =
840 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
841 static GdkColor quote_color2 =
842 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
843 static GdkColor quote_color3 =
844 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
846 static GdkColor quote_bgcolor1 =
847 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
848 static GdkColor quote_bgcolor2 =
849 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
850 static GdkColor quote_bgcolor3 =
851 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
853 static GdkColor signature_color = {
860 static GdkColor uri_color = {
867 static void compose_create_tags(GtkTextView *text, Compose *compose)
869 GtkTextBuffer *buffer;
870 GdkColor black = {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
876 buffer = gtk_text_view_get_buffer(text);
878 if (prefs_common.enable_color) {
879 /* grab the quote colors, converting from an int to a GdkColor */
880 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
882 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
884 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
886 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_bgcol,
888 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_bgcol,
890 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_bgcol,
892 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
894 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
897 signature_color = quote_color1 = quote_color2 = quote_color3 =
898 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
901 if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
902 gtk_text_buffer_create_tag(buffer, "quote0",
903 "foreground-gdk", "e_color1,
904 "paragraph-background-gdk", "e_bgcolor1,
906 gtk_text_buffer_create_tag(buffer, "quote1",
907 "foreground-gdk", "e_color2,
908 "paragraph-background-gdk", "e_bgcolor2,
910 gtk_text_buffer_create_tag(buffer, "quote2",
911 "foreground-gdk", "e_color3,
912 "paragraph-background-gdk", "e_bgcolor3,
915 gtk_text_buffer_create_tag(buffer, "quote0",
916 "foreground-gdk", "e_color1,
918 gtk_text_buffer_create_tag(buffer, "quote1",
919 "foreground-gdk", "e_color2,
921 gtk_text_buffer_create_tag(buffer, "quote2",
922 "foreground-gdk", "e_color3,
926 gtk_text_buffer_create_tag(buffer, "signature",
927 "foreground-gdk", &signature_color,
929 gtk_text_buffer_create_tag(buffer, "link",
930 "foreground-gdk", &uri_color,
932 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
933 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
935 color[0] = quote_color1;
936 color[1] = quote_color2;
937 color[2] = quote_color3;
938 color[3] = quote_bgcolor1;
939 color[4] = quote_bgcolor2;
940 color[5] = quote_bgcolor3;
941 color[6] = signature_color;
942 color[7] = uri_color;
943 cmap = gdk_drawable_get_colormap(compose->window->window);
944 gdk_colormap_alloc_colors(cmap, color, 8, FALSE, TRUE, success);
946 for (i = 0; i < 8; i++) {
947 if (success[i] == FALSE) {
950 g_warning("Compose: color allocation failed.\n");
951 style = gtk_widget_get_style(GTK_WIDGET(text));
952 quote_color1 = quote_color2 = quote_color3 =
953 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 =
954 signature_color = uri_color = black;
959 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
960 GPtrArray *attach_files)
962 return compose_generic_new(account, mailto, NULL, attach_files, NULL);
965 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
967 return compose_generic_new(account, mailto, item, NULL, NULL);
970 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
972 return compose_generic_new( account, NULL, NULL, NULL, listAddress );
975 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
976 GPtrArray *attach_files, GList *listAddress )
979 GtkTextView *textview;
980 GtkTextBuffer *textbuf;
982 GtkItemFactory *ifactory;
983 gchar *subject_format = NULL;
984 gchar *body_format = NULL;
986 if (item && item->prefs && item->prefs->enable_default_account)
987 account = account_find_from_id(item->prefs->default_account);
989 if (!account) account = cur_account;
990 g_return_val_if_fail(account != NULL, NULL);
992 compose = compose_create(account, COMPOSE_NEW, FALSE);
994 ifactory = gtk_item_factory_from_widget(compose->menubar);
996 compose->replyinfo = NULL;
997 compose->fwdinfo = NULL;
999 textview = GTK_TEXT_VIEW(compose->text);
1000 textbuf = gtk_text_view_get_buffer(textview);
1001 compose_create_tags(textview, compose);
1003 undo_block(compose->undostruct);
1005 compose_set_dictionaries_from_folder_prefs(compose, item);
1008 if (account->auto_sig)
1009 compose_insert_sig(compose, FALSE);
1010 gtk_text_buffer_get_start_iter(textbuf, &iter);
1011 gtk_text_buffer_place_cursor(textbuf, &iter);
1013 if (account->protocol != A_NNTP) {
1014 if (mailto && *mailto != '\0') {
1015 compose_entries_set(compose, mailto);
1017 } else if (item && item->prefs->enable_default_to) {
1018 compose_entry_append(compose, item->prefs->default_to, COMPOSE_TO);
1019 compose_entry_mark_default_to(compose, item->prefs->default_to);
1021 if (item && item->ret_rcpt) {
1022 menu_set_active(ifactory, "/Options/Request Return Receipt", TRUE);
1026 compose_entry_append(compose, mailto, COMPOSE_NEWSGROUPS);
1027 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1028 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS);
1031 * CLAWS: just don't allow return receipt request, even if the user
1032 * may want to send an email. simple but foolproof.
1034 menu_set_sensitive(ifactory, "/Options/Request Return Receipt", FALSE);
1036 compose_add_field_list( compose, listAddress );
1038 if (item && item->prefs && item->prefs->compose_with_format) {
1039 subject_format = item->prefs->compose_subject_format;
1040 body_format = item->prefs->compose_body_format;
1041 } else if (account->compose_with_format) {
1042 subject_format = account->compose_subject_format;
1043 body_format = account->compose_body_format;
1044 } else if (prefs_common.compose_with_format) {
1045 subject_format = prefs_common.compose_subject_format;
1046 body_format = prefs_common.compose_body_format;
1049 if (subject_format || body_format) {
1050 MsgInfo* dummyinfo = NULL;
1053 && *subject_format != '\0' )
1055 gchar *subject = NULL;
1059 dummyinfo = compose_msginfo_new_from_compose(compose);
1061 /* decode \-escape sequences in the internal representation of the quote format */
1062 tmp = malloc(strlen(subject_format)+1);
1063 pref_get_unescaped_pref(tmp, subject_format);
1065 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1067 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account,
1068 compose->gtkaspell);
1070 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account);
1072 quote_fmt_scan_string(tmp);
1075 buf = quote_fmt_get_buffer();
1077 alertpanel_error(_("New message subject format error."));
1079 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1080 quote_fmt_reset_vartable();
1087 && *body_format != '\0' )
1090 GtkTextBuffer *buffer;
1091 GtkTextIter start, end;
1094 if ( dummyinfo == NULL )
1095 dummyinfo = compose_msginfo_new_from_compose(compose);
1097 text = GTK_TEXT_VIEW(compose->text);
1098 buffer = gtk_text_view_get_buffer(text);
1099 gtk_text_buffer_get_start_iter(buffer, &start);
1100 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1101 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1103 compose_quote_fmt(compose, dummyinfo,
1105 NULL, tmp, FALSE, TRUE,
1106 _("New message body format error at line %d."));
1107 quote_fmt_reset_vartable();
1112 procmsg_msginfo_free( dummyinfo );
1119 for (i = 0; i < attach_files->len; i++) {
1120 file = g_ptr_array_index(attach_files, i);
1121 compose_attach_append(compose, file, file, NULL);
1125 compose_show_first_last_header(compose, TRUE);
1127 /* Set save folder */
1128 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1129 gchar *folderidentifier;
1131 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
1132 folderidentifier = folder_item_get_identifier(item);
1133 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
1134 g_free(folderidentifier);
1137 gtk_widget_grab_focus(compose->header_last->entry);
1139 undo_unblock(compose->undostruct);
1141 if (prefs_common.auto_exteditor)
1142 compose_exec_ext_editor(compose);
1144 compose->modified = FALSE;
1145 compose_set_title(compose);
1149 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1150 gboolean override_pref)
1152 gchar *privacy = NULL;
1154 g_return_if_fail(compose != NULL);
1155 g_return_if_fail(account != NULL);
1157 if (override_pref == FALSE && account->default_encrypt_reply == FALSE)
1160 if (account->default_privacy_system
1161 && strlen(account->default_privacy_system)) {
1162 privacy = account->default_privacy_system;
1164 GSList *privacy_avail = privacy_get_system_ids();
1165 if (privacy_avail && g_slist_length(privacy_avail)) {
1166 privacy = (gchar *)(privacy_avail->data);
1169 if (privacy != NULL) {
1170 if (compose->privacy_system == NULL)
1171 compose->privacy_system = g_strdup(privacy);
1172 compose_update_privacy_system_menu_item(compose, FALSE);
1173 compose_use_encryption(compose, TRUE);
1177 static void compose_force_signing(Compose *compose, PrefsAccount *account)
1179 gchar *privacy = NULL;
1181 if (account->default_privacy_system
1182 && strlen(account->default_privacy_system)) {
1183 privacy = account->default_privacy_system;
1185 GSList *privacy_avail = privacy_get_system_ids();
1186 if (privacy_avail && g_slist_length(privacy_avail)) {
1187 privacy = (gchar *)(privacy_avail->data);
1190 if (privacy != NULL) {
1191 if (compose->privacy_system == NULL)
1192 compose->privacy_system = g_strdup(privacy);
1193 compose_update_privacy_system_menu_item(compose, FALSE);
1194 compose_use_signing(compose, TRUE);
1198 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1202 Compose *compose = NULL;
1203 GtkItemFactory *ifactory = NULL;
1205 g_return_val_if_fail(msginfo_list != NULL, NULL);
1207 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1208 g_return_val_if_fail(msginfo != NULL, NULL);
1210 list_len = g_slist_length(msginfo_list);
1214 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1215 FALSE, prefs_common.default_reply_list, FALSE, body);
1217 case COMPOSE_REPLY_WITH_QUOTE:
1218 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1219 FALSE, prefs_common.default_reply_list, FALSE, body);
1221 case COMPOSE_REPLY_WITHOUT_QUOTE:
1222 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1223 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1225 case COMPOSE_REPLY_TO_SENDER:
1226 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1227 FALSE, FALSE, TRUE, body);
1229 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1230 compose = compose_followup_and_reply_to(msginfo,
1231 COMPOSE_QUOTE_CHECK,
1232 FALSE, FALSE, body);
1234 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1235 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1236 FALSE, FALSE, TRUE, body);
1238 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1239 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1240 FALSE, FALSE, TRUE, NULL);
1242 case COMPOSE_REPLY_TO_ALL:
1243 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1244 TRUE, FALSE, FALSE, body);
1246 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1247 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1248 TRUE, FALSE, FALSE, body);
1250 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1251 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1252 TRUE, FALSE, FALSE, NULL);
1254 case COMPOSE_REPLY_TO_LIST:
1255 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1256 FALSE, TRUE, FALSE, body);
1258 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1259 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1260 FALSE, TRUE, FALSE, body);
1262 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1263 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1264 FALSE, TRUE, FALSE, NULL);
1266 case COMPOSE_FORWARD:
1267 if (prefs_common.forward_as_attachment) {
1268 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1271 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1275 case COMPOSE_FORWARD_INLINE:
1276 /* check if we reply to more than one Message */
1277 if (list_len == 1) {
1278 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1281 /* more messages FALL THROUGH */
1282 case COMPOSE_FORWARD_AS_ATTACH:
1283 compose = compose_forward_multiple(NULL, msginfo_list);
1285 case COMPOSE_REDIRECT:
1286 compose = compose_redirect(NULL, msginfo, FALSE);
1289 g_warning("compose_reply_mode(): invalid Compose Mode: %d\n", mode);
1292 ifactory = gtk_item_factory_from_widget(compose->menubar);
1294 compose->rmode = mode;
1295 switch (compose->rmode) {
1297 case COMPOSE_REPLY_WITH_QUOTE:
1298 case COMPOSE_REPLY_WITHOUT_QUOTE:
1299 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1300 debug_print("reply mode Normal\n");
1301 menu_set_active(ifactory, "/Options/Reply mode/Normal", TRUE);
1302 compose_reply_change_mode(compose, COMPOSE_REPLY, NULL); /* force update */
1304 case COMPOSE_REPLY_TO_SENDER:
1305 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1306 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1307 debug_print("reply mode Sender\n");
1308 menu_set_active(ifactory, "/Options/Reply mode/Sender", TRUE);
1310 case COMPOSE_REPLY_TO_ALL:
1311 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1312 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1313 debug_print("reply mode All\n");
1314 menu_set_active(ifactory, "/Options/Reply mode/All", TRUE);
1316 case COMPOSE_REPLY_TO_LIST:
1317 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1318 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1319 debug_print("reply mode List\n");
1320 menu_set_active(ifactory, "/Options/Reply mode/Mailing-list", TRUE);
1328 static Compose *compose_reply(MsgInfo *msginfo,
1329 ComposeQuoteMode quote_mode,
1335 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1336 to_sender, FALSE, body);
1339 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1340 ComposeQuoteMode quote_mode,
1345 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1346 to_sender, TRUE, body);
1349 static void compose_extract_original_charset(Compose *compose)
1351 MsgInfo *info = NULL;
1352 if (compose->replyinfo) {
1353 info = compose->replyinfo;
1354 } else if (compose->fwdinfo) {
1355 info = compose->fwdinfo;
1356 } else if (compose->targetinfo) {
1357 info = compose->targetinfo;
1360 MimeInfo *mimeinfo = procmime_scan_message(info);
1361 MimeInfo *partinfo = mimeinfo;
1362 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1363 partinfo = procmime_mimeinfo_next(partinfo);
1365 compose->orig_charset =
1366 g_strdup(procmime_mimeinfo_get_parameter(
1367 partinfo, "charset"));
1369 procmime_mimeinfo_free_all(mimeinfo);
1373 #define SIGNAL_BLOCK(buffer) { \
1374 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1375 G_CALLBACK(compose_changed_cb), \
1377 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1378 G_CALLBACK(text_inserted), \
1382 #define SIGNAL_UNBLOCK(buffer) { \
1383 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1384 G_CALLBACK(compose_changed_cb), \
1386 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1387 G_CALLBACK(text_inserted), \
1391 static Compose *compose_generic_reply(MsgInfo *msginfo,
1392 ComposeQuoteMode quote_mode,
1393 gboolean to_all, gboolean to_ml,
1395 gboolean followup_and_reply_to,
1398 GtkItemFactory *ifactory;
1400 PrefsAccount *account = NULL;
1401 GtkTextView *textview;
1402 GtkTextBuffer *textbuf;
1403 gboolean quote = FALSE;
1404 gchar *qmark = NULL;
1405 gchar *body_fmt = NULL;
1407 g_return_val_if_fail(msginfo != NULL, NULL);
1408 g_return_val_if_fail(msginfo->folder != NULL, NULL);
1410 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1412 g_return_val_if_fail(account != NULL, NULL);
1414 compose = compose_create(account, COMPOSE_REPLY, FALSE);
1416 compose->updating = TRUE;
1418 ifactory = gtk_item_factory_from_widget(compose->menubar);
1420 menu_set_active(ifactory, "/Options/Remove references", FALSE);
1421 menu_set_sensitive(ifactory, "/Options/Remove references", TRUE);
1423 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1424 if (!compose->replyinfo)
1425 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1427 compose_extract_original_charset(compose);
1429 if (msginfo->folder && msginfo->folder->ret_rcpt)
1430 menu_set_active(ifactory, "/Options/Request Return Receipt", TRUE);
1432 /* Set save folder */
1433 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1434 gchar *folderidentifier;
1436 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1437 folderidentifier = folder_item_get_identifier(msginfo->folder);
1438 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
1439 g_free(folderidentifier);
1442 if (compose_parse_header(compose, msginfo) < 0) return NULL;
1444 textview = (GTK_TEXT_VIEW(compose->text));
1445 textbuf = gtk_text_view_get_buffer(textview);
1446 compose_create_tags(textview, compose);
1448 undo_block(compose->undostruct);
1450 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1453 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1454 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1455 /* use the reply format of folder (if enabled), or the account's one
1456 (if enabled) or fallback to the global reply format, which is always
1457 enabled (even if empty), and use the relevant quotemark */
1459 if (msginfo->folder && msginfo->folder->prefs &&
1460 msginfo->folder->prefs->reply_with_format) {
1461 qmark = msginfo->folder->prefs->reply_quotemark;
1462 body_fmt = msginfo->folder->prefs->reply_body_format;
1464 } else if (account->reply_with_format) {
1465 qmark = account->reply_quotemark;
1466 body_fmt = account->reply_body_format;
1469 qmark = prefs_common.quotemark;
1470 body_fmt = prefs_common.quotefmt;
1475 /* empty quotemark is not allowed */
1476 if (qmark == NULL || *qmark == '\0')
1478 compose_quote_fmt(compose, compose->replyinfo,
1479 body_fmt, qmark, body, FALSE, TRUE,
1480 _("Message reply format error at line %d."));
1481 quote_fmt_reset_vartable();
1483 if (procmime_msginfo_is_encrypted(compose->replyinfo)) {
1484 compose_force_encryption(compose, account, FALSE);
1487 SIGNAL_BLOCK(textbuf);
1489 if (account->auto_sig)
1490 compose_insert_sig(compose, FALSE);
1492 compose_wrap_all(compose);
1494 SIGNAL_UNBLOCK(textbuf);
1496 gtk_widget_grab_focus(compose->text);
1498 undo_unblock(compose->undostruct);
1500 if (prefs_common.auto_exteditor)
1501 compose_exec_ext_editor(compose);
1503 compose->modified = FALSE;
1504 compose_set_title(compose);
1506 compose->updating = FALSE;
1508 if (compose->deferred_destroy) {
1509 compose_destroy(compose);
1516 #define INSERT_FW_HEADER(var, hdr) \
1517 if (msginfo->var && *msginfo->var) { \
1518 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1519 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1520 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1523 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1524 gboolean as_attach, const gchar *body,
1525 gboolean no_extedit,
1529 GtkTextView *textview;
1530 GtkTextBuffer *textbuf;
1533 g_return_val_if_fail(msginfo != NULL, NULL);
1534 g_return_val_if_fail(msginfo->folder != NULL, NULL);
1537 !(account = compose_guess_forward_account_from_msginfo
1539 account = cur_account;
1541 compose = compose_create(account, COMPOSE_FORWARD, batch);
1543 compose->updating = TRUE;
1544 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1545 if (!compose->fwdinfo)
1546 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1548 compose_extract_original_charset(compose);
1550 if (msginfo->subject && *msginfo->subject) {
1551 gchar *buf, *buf2, *p;
1553 buf = p = g_strdup(msginfo->subject);
1554 p += subject_get_prefix_length(p);
1555 memmove(buf, p, strlen(p) + 1);
1557 buf2 = g_strdup_printf("Fw: %s", buf);
1558 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1564 textview = GTK_TEXT_VIEW(compose->text);
1565 textbuf = gtk_text_view_get_buffer(textview);
1566 compose_create_tags(textview, compose);
1568 undo_block(compose->undostruct);
1572 msgfile = procmsg_get_message_file(msginfo);
1573 if (!is_file_exist(msgfile))
1574 g_warning("%s: file not exist\n", msgfile);
1576 compose_attach_append(compose, msgfile, msgfile,
1581 gchar *qmark = NULL;
1582 gchar *body_fmt = prefs_common.fw_quotefmt;
1583 MsgInfo *full_msginfo;
1585 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1587 full_msginfo = procmsg_msginfo_copy(msginfo);
1589 /* use the forward format of folder (if enabled), or the account's one
1590 (if enabled) or fallback to the global forward format, which is always
1591 enabled (even if empty), and use the relevant quotemark */
1592 if (msginfo->folder && msginfo->folder->prefs &&
1593 msginfo->folder->prefs->forward_with_format) {
1594 qmark = msginfo->folder->prefs->forward_quotemark;
1595 body_fmt = msginfo->folder->prefs->forward_body_format;
1597 } else if (account->forward_with_format) {
1598 qmark = account->forward_quotemark;
1599 body_fmt = account->forward_body_format;
1602 qmark = prefs_common.fw_quotemark;
1603 body_fmt = prefs_common.fw_quotefmt;
1606 /* empty quotemark is not allowed */
1607 if (qmark == NULL || *qmark == '\0')
1610 compose_quote_fmt(compose, full_msginfo,
1611 body_fmt, qmark, body, FALSE, TRUE,
1612 _("Message forward format error at line %d."));
1613 quote_fmt_reset_vartable();
1614 compose_attach_parts(compose, msginfo);
1616 procmsg_msginfo_free(full_msginfo);
1619 SIGNAL_BLOCK(textbuf);
1621 if (account->auto_sig)
1622 compose_insert_sig(compose, FALSE);
1624 compose_wrap_all(compose);
1626 SIGNAL_UNBLOCK(textbuf);
1628 gtk_text_buffer_get_start_iter(textbuf, &iter);
1629 gtk_text_buffer_place_cursor(textbuf, &iter);
1631 gtk_widget_grab_focus(compose->header_last->entry);
1633 if (!no_extedit && prefs_common.auto_exteditor)
1634 compose_exec_ext_editor(compose);
1637 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1638 gchar *folderidentifier;
1640 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1641 folderidentifier = folder_item_get_identifier(msginfo->folder);
1642 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
1643 g_free(folderidentifier);
1646 undo_unblock(compose->undostruct);
1648 compose->modified = FALSE;
1649 compose_set_title(compose);
1651 compose->updating = FALSE;
1653 if (compose->deferred_destroy) {
1654 compose_destroy(compose);
1661 #undef INSERT_FW_HEADER
1663 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1666 GtkTextView *textview;
1667 GtkTextBuffer *textbuf;
1671 gboolean single_mail = TRUE;
1673 g_return_val_if_fail(msginfo_list != NULL, NULL);
1675 if (g_slist_length(msginfo_list) > 1)
1676 single_mail = FALSE;
1678 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1679 if (((MsgInfo *)msginfo->data)->folder == NULL)
1682 /* guess account from first selected message */
1684 !(account = compose_guess_forward_account_from_msginfo
1685 (msginfo_list->data)))
1686 account = cur_account;
1688 g_return_val_if_fail(account != NULL, NULL);
1690 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1691 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1692 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1695 compose = compose_create(account, COMPOSE_FORWARD, FALSE);
1697 compose->updating = TRUE;
1699 textview = GTK_TEXT_VIEW(compose->text);
1700 textbuf = gtk_text_view_get_buffer(textview);
1701 compose_create_tags(textview, compose);
1703 undo_block(compose->undostruct);
1704 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1705 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
1707 if (!is_file_exist(msgfile))
1708 g_warning("%s: file not exist\n", msgfile);
1710 compose_attach_append(compose, msgfile, msgfile,
1716 MsgInfo *info = (MsgInfo *)msginfo_list->data;
1717 if (info->subject && *info->subject) {
1718 gchar *buf, *buf2, *p;
1720 buf = p = g_strdup(info->subject);
1721 p += subject_get_prefix_length(p);
1722 memmove(buf, p, strlen(p) + 1);
1724 buf2 = g_strdup_printf("Fw: %s", buf);
1725 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1731 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
1732 _("Fw: multiple emails"));
1735 SIGNAL_BLOCK(textbuf);
1737 if (account->auto_sig)
1738 compose_insert_sig(compose, FALSE);
1740 compose_wrap_all(compose);
1742 SIGNAL_UNBLOCK(textbuf);
1744 gtk_text_buffer_get_start_iter(textbuf, &iter);
1745 gtk_text_buffer_place_cursor(textbuf, &iter);
1747 gtk_widget_grab_focus(compose->header_last->entry);
1748 undo_unblock(compose->undostruct);
1749 compose->modified = FALSE;
1750 compose_set_title(compose);
1752 compose->updating = FALSE;
1754 if (compose->deferred_destroy) {
1755 compose_destroy(compose);
1762 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
1764 GtkTextIter start = *iter;
1765 GtkTextIter end_iter;
1766 int start_pos = gtk_text_iter_get_offset(&start);
1768 if (!compose->account->sig_sep)
1771 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
1772 start_pos+strlen(compose->account->sig_sep));
1774 /* check sig separator */
1775 str = gtk_text_iter_get_text(&start, &end_iter);
1776 if (!strcmp(str, compose->account->sig_sep)) {
1778 /* check end of line (\n) */
1779 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
1780 start_pos+strlen(compose->account->sig_sep));
1781 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
1782 start_pos+strlen(compose->account->sig_sep)+1);
1783 tmp = gtk_text_iter_get_text(&start, &end_iter);
1784 if (!strcmp(tmp,"\n")) {
1796 static void compose_colorize_signature(Compose *compose)
1798 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
1800 GtkTextIter end_iter;
1801 gtk_text_buffer_get_start_iter(buffer, &iter);
1802 while (gtk_text_iter_forward_line(&iter))
1803 if (compose_is_sig_separator(compose, buffer, &iter)) {
1804 gtk_text_buffer_get_end_iter(buffer, &end_iter);
1805 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
1809 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
1811 Compose *compose = NULL;
1812 PrefsAccount *account = NULL;
1813 GtkTextView *textview;
1814 GtkTextBuffer *textbuf;
1818 gchar buf[BUFFSIZE];
1819 gboolean use_signing = FALSE;
1820 gboolean use_encryption = FALSE;
1821 gchar *privacy_system = NULL;
1822 int priority = PRIORITY_NORMAL;
1823 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
1825 g_return_val_if_fail(msginfo != NULL, NULL);
1826 g_return_val_if_fail(msginfo->folder != NULL, NULL);
1828 if (compose_put_existing_to_front(msginfo)) {
1832 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
1833 folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
1834 gchar queueheader_buf[BUFFSIZE];
1837 /* Select Account from queue headers */
1838 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1839 sizeof(queueheader_buf), "X-Claws-Account-Id:")) {
1840 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
1841 account = account_find_from_id(id);
1843 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1844 sizeof(queueheader_buf), "X-Sylpheed-Account-Id:")) {
1845 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
1846 account = account_find_from_id(id);
1848 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1849 sizeof(queueheader_buf), "NAID:")) {
1850 id = atoi(&queueheader_buf[strlen("NAID:")]);
1851 account = account_find_from_id(id);
1853 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1854 sizeof(queueheader_buf), "MAID:")) {
1855 id = atoi(&queueheader_buf[strlen("MAID:")]);
1856 account = account_find_from_id(id);
1858 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1859 sizeof(queueheader_buf), "S:")) {
1860 account = account_find_from_address(queueheader_buf);
1862 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1863 sizeof(queueheader_buf), "X-Claws-Sign:")) {
1864 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
1865 use_signing = param;
1868 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1869 sizeof(queueheader_buf), "X-Sylpheed-Sign:")) {
1870 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
1871 use_signing = param;
1874 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1875 sizeof(queueheader_buf), "X-Claws-Encrypt:")) {
1876 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
1877 use_encryption = param;
1879 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1880 sizeof(queueheader_buf), "X-Sylpheed-Encrypt:")) {
1881 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
1882 use_encryption = param;
1884 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1885 sizeof(queueheader_buf), "X-Claws-Privacy-System:")) {
1886 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
1888 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1889 sizeof(queueheader_buf), "X-Sylpheed-Privacy-System:")) {
1890 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
1892 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1893 sizeof(queueheader_buf), "X-Priority: ")) {
1894 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
1897 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1898 sizeof(queueheader_buf), "RMID:")) {
1899 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
1900 if (tokens[0] && tokens[1] && tokens[2]) {
1901 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
1902 if (orig_item != NULL) {
1903 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
1908 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1909 sizeof(queueheader_buf), "FMID:")) {
1910 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
1911 if (tokens[0] && tokens[1] && tokens[2]) {
1912 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
1913 if (orig_item != NULL) {
1914 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
1920 account = msginfo->folder->folder->account;
1923 if (!account && prefs_common.reedit_account_autosel) {
1924 gchar from[BUFFSIZE];
1925 if (!procheader_get_header_from_msginfo(msginfo, from, sizeof(from), "FROM:")) {
1926 extract_address(from);
1927 account = account_find_from_address(from);
1931 account = cur_account;
1933 g_return_val_if_fail(account != NULL, NULL);
1935 compose = compose_create(account, COMPOSE_REEDIT, batch);
1937 compose->replyinfo = replyinfo;
1938 compose->fwdinfo = fwdinfo;
1940 compose->updating = TRUE;
1941 compose->priority = priority;
1943 if (privacy_system != NULL) {
1944 compose->privacy_system = privacy_system;
1945 compose_use_signing(compose, use_signing);
1946 compose_use_encryption(compose, use_encryption);
1947 compose_update_privacy_system_menu_item(compose, FALSE);
1949 activate_privacy_system(compose, account, FALSE);
1952 compose->targetinfo = procmsg_msginfo_copy(msginfo);
1954 compose_extract_original_charset(compose);
1956 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
1957 folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
1958 gchar queueheader_buf[BUFFSIZE];
1960 /* Set message save folder */
1961 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "SCF:")) {
1964 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1965 gtk_editable_delete_text(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
1966 gtk_editable_insert_text(GTK_EDITABLE(compose->savemsg_entry), &queueheader_buf[4], strlen(&queueheader_buf[4]), &startpos);
1968 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "RRCPT:")) {
1969 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
1971 GtkItemFactory *ifactory;
1972 ifactory = gtk_item_factory_from_widget(compose->menubar);
1973 menu_set_active(ifactory, "/Options/Request Return Receipt", TRUE);
1978 if (compose_parse_header(compose, msginfo) < 0) {
1979 compose->updating = FALSE;
1980 compose_destroy(compose);
1983 compose_reedit_set_entry(compose, msginfo);
1985 textview = GTK_TEXT_VIEW(compose->text);
1986 textbuf = gtk_text_view_get_buffer(textview);
1987 compose_create_tags(textview, compose);
1989 mark = gtk_text_buffer_get_insert(textbuf);
1990 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
1992 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
1993 G_CALLBACK(compose_changed_cb),
1996 if (procmime_msginfo_is_encrypted(msginfo)) {
1997 fp = procmime_get_first_encrypted_text_content(msginfo);
1999 compose_force_encryption(compose, account, TRUE);
2002 fp = procmime_get_first_text_content(msginfo);
2005 g_warning("Can't get text part\n");
2009 gboolean prev_autowrap = compose->autowrap;
2011 compose->autowrap = FALSE;
2012 while (fgets(buf, sizeof(buf), fp) != NULL) {
2014 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2016 compose_wrap_all_full(compose, FALSE);
2017 compose->autowrap = prev_autowrap;
2021 compose_attach_parts(compose, msginfo);
2023 compose_colorize_signature(compose);
2025 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2026 G_CALLBACK(compose_changed_cb),
2029 gtk_widget_grab_focus(compose->text);
2031 if (prefs_common.auto_exteditor) {
2032 compose_exec_ext_editor(compose);
2034 compose->modified = FALSE;
2035 compose_set_title(compose);
2037 compose->updating = FALSE;
2039 if (compose->deferred_destroy) {
2040 compose_destroy(compose);
2044 compose->sig_str = compose_get_signature_str(compose);
2049 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2054 GtkItemFactory *ifactory;
2057 g_return_val_if_fail(msginfo != NULL, NULL);
2060 account = account_get_reply_account(msginfo,
2061 prefs_common.reply_account_autosel);
2062 g_return_val_if_fail(account != NULL, NULL);
2064 compose = compose_create(account, COMPOSE_REDIRECT, batch);
2066 compose->updating = TRUE;
2068 ifactory = gtk_item_factory_from_widget(compose->menubar);
2069 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2070 compose->replyinfo = NULL;
2071 compose->fwdinfo = NULL;
2073 compose_show_first_last_header(compose, TRUE);
2075 gtk_widget_grab_focus(compose->header_last->entry);
2077 filename = procmsg_get_message_file(msginfo);
2079 if (filename == NULL) {
2080 compose->updating = FALSE;
2081 compose_destroy(compose);
2086 compose->redirect_filename = filename;
2088 /* Set save folder */
2089 item = msginfo->folder;
2090 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2091 gchar *folderidentifier;
2093 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
2094 folderidentifier = folder_item_get_identifier(item);
2095 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
2096 g_free(folderidentifier);
2099 compose_attach_parts(compose, msginfo);
2101 if (msginfo->subject)
2102 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2104 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2106 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2107 _("Message redirect format error at line %d."));
2108 quote_fmt_reset_vartable();
2109 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2111 compose_colorize_signature(compose);
2113 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
2114 menu_set_sensitive(ifactory, "/Add...", FALSE);
2115 menu_set_sensitive(ifactory, "/Remove", FALSE);
2116 menu_set_sensitive(ifactory, "/Properties...", FALSE);
2118 ifactory = gtk_item_factory_from_widget(compose->menubar);
2119 menu_set_sensitive(ifactory, "/Message/Save", FALSE);
2120 menu_set_sensitive(ifactory, "/Message/Insert file", FALSE);
2121 menu_set_sensitive(ifactory, "/Message/Attach file", FALSE);
2122 menu_set_sensitive(ifactory, "/Message/Insert signature", FALSE);
2123 menu_set_sensitive(ifactory, "/Edit", FALSE);
2124 menu_set_sensitive(ifactory, "/Options", FALSE);
2125 menu_set_sensitive(ifactory, "/Tools/Show ruler", FALSE);
2126 menu_set_sensitive(ifactory, "/Tools/Actions", FALSE);
2128 if (compose->toolbar->draft_btn)
2129 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2130 if (compose->toolbar->insert_btn)
2131 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2132 if (compose->toolbar->attach_btn)
2133 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2134 if (compose->toolbar->sig_btn)
2135 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2136 if (compose->toolbar->exteditor_btn)
2137 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2138 if (compose->toolbar->linewrap_current_btn)
2139 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2140 if (compose->toolbar->linewrap_all_btn)
2141 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2143 compose->modified = FALSE;
2144 compose_set_title(compose);
2145 compose->updating = FALSE;
2147 if (compose->deferred_destroy) {
2148 compose_destroy(compose);
2155 GList *compose_get_compose_list(void)
2157 return compose_list;
2160 void compose_entry_append(Compose *compose, const gchar *address,
2161 ComposeEntryType type)
2163 const gchar *header;
2165 gboolean in_quote = FALSE;
2166 if (!address || *address == '\0') return;
2173 header = N_("Bcc:");
2175 case COMPOSE_REPLYTO:
2176 header = N_("Reply-To:");
2178 case COMPOSE_NEWSGROUPS:
2179 header = N_("Newsgroups:");
2181 case COMPOSE_FOLLOWUPTO:
2182 header = N_( "Followup-To:");
2189 header = prefs_common_translated_header_name(header);
2191 cur = begin = (gchar *)address;
2193 /* we separate the line by commas, but not if we're inside a quoted
2195 while (*cur != '\0') {
2197 in_quote = !in_quote;
2198 if (*cur == ',' && !in_quote) {
2199 gchar *tmp = g_strdup(begin);
2201 tmp[cur-begin]='\0';
2204 while (*tmp == ' ' || *tmp == '\t')
2206 compose_add_header_entry(compose, header, tmp);
2213 gchar *tmp = g_strdup(begin);
2215 tmp[cur-begin]='\0';
2218 while (*tmp == ' ' || *tmp == '\t')
2220 compose_add_header_entry(compose, header, tmp);
2225 static void compose_entry_mark_default_to(Compose *compose, const gchar *mailto)
2227 static GdkColor yellow;
2228 static GdkColor black;
2229 static gboolean yellow_initialised = FALSE;
2233 if (!yellow_initialised) {
2234 gdk_color_parse("#f5f6be", &yellow);
2235 gdk_color_parse("#000000", &black);
2236 yellow_initialised = gdk_colormap_alloc_color(
2237 gdk_colormap_get_system(), &yellow, FALSE, TRUE);
2238 yellow_initialised &= gdk_colormap_alloc_color(
2239 gdk_colormap_get_system(), &black, FALSE, TRUE);
2242 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2243 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2244 if (gtk_entry_get_text(entry) &&
2245 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2246 if (yellow_initialised) {
2247 gtk_widget_modify_base(
2248 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2249 GTK_STATE_NORMAL, &yellow);
2250 gtk_widget_modify_text(
2251 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2252 GTK_STATE_NORMAL, &black);
2258 void compose_toolbar_cb(gint action, gpointer data)
2260 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2261 Compose *compose = (Compose*)toolbar_item->parent;
2263 g_return_if_fail(compose != NULL);
2267 compose_send_cb(compose, 0, NULL);
2270 compose_send_later_cb(compose, 0, NULL);
2273 compose_draft_cb(compose, COMPOSE_QUIT_EDITING, NULL);
2276 compose_insert_file_cb(compose, 0, NULL);
2279 compose_attach_cb(compose, 0, NULL);
2282 compose_insert_sig(compose, FALSE);
2285 compose_ext_editor_cb(compose, 0, NULL);
2287 case A_LINEWRAP_CURRENT:
2288 compose_beautify_paragraph(compose, NULL, TRUE);
2290 case A_LINEWRAP_ALL:
2291 compose_wrap_all_full(compose, TRUE);
2294 compose_address_cb(compose, 0, NULL);
2297 case A_CHECK_SPELLING:
2298 compose_check_all(compose);
2306 static void compose_entries_set(Compose *compose, const gchar *mailto)
2310 gchar *subject = NULL;
2314 gchar *attach = NULL;
2316 scan_mailto_url(mailto, &to, &cc, NULL, &subject, &body, &attach);
2319 compose_entry_append(compose, to, COMPOSE_TO);
2321 compose_entry_append(compose, cc, COMPOSE_CC);
2323 if (!g_utf8_validate (subject, -1, NULL)) {
2324 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2325 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2328 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2332 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2333 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2336 gboolean prev_autowrap = compose->autowrap;
2338 compose->autowrap = FALSE;
2340 mark = gtk_text_buffer_get_insert(buffer);
2341 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2343 if (!g_utf8_validate (body, -1, NULL)) {
2344 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2345 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2348 gtk_text_buffer_insert(buffer, &iter, body, -1);
2350 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2352 compose->autowrap = prev_autowrap;
2353 if (compose->autowrap)
2354 compose_wrap_all(compose);
2358 gchar *utf8_filename = conv_filename_to_utf8(attach);
2359 if (utf8_filename) {
2360 if (compose_attach_append(compose, attach, utf8_filename, NULL)) {
2361 alertpanel_notice(_("The file '%s' has been attached."), attach);
2363 g_free(utf8_filename);
2365 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2375 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2377 static HeaderEntry hentry[] = {{"Reply-To:", NULL, TRUE},
2378 {"Cc:", NULL, TRUE},
2379 {"References:", NULL, FALSE},
2380 {"Bcc:", NULL, TRUE},
2381 {"Newsgroups:", NULL, TRUE},
2382 {"Followup-To:", NULL, TRUE},
2383 {"List-Post:", NULL, FALSE},
2384 {"X-Priority:", NULL, FALSE},
2385 {NULL, NULL, FALSE}};
2401 g_return_val_if_fail(msginfo != NULL, -1);
2403 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2404 procheader_get_header_fields(fp, hentry);
2407 if (hentry[H_REPLY_TO].body != NULL) {
2408 if (hentry[H_REPLY_TO].body[0] != '\0') {
2410 conv_unmime_header(hentry[H_REPLY_TO].body,
2413 g_free(hentry[H_REPLY_TO].body);
2414 hentry[H_REPLY_TO].body = NULL;
2416 if (hentry[H_CC].body != NULL) {
2417 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL);
2418 g_free(hentry[H_CC].body);
2419 hentry[H_CC].body = NULL;
2421 if (hentry[H_REFERENCES].body != NULL) {
2422 if (compose->mode == COMPOSE_REEDIT)
2423 compose->references = hentry[H_REFERENCES].body;
2425 compose->references = compose_parse_references
2426 (hentry[H_REFERENCES].body, msginfo->msgid);
2427 g_free(hentry[H_REFERENCES].body);
2429 hentry[H_REFERENCES].body = NULL;
2431 if (hentry[H_BCC].body != NULL) {
2432 if (compose->mode == COMPOSE_REEDIT)
2434 conv_unmime_header(hentry[H_BCC].body, NULL);
2435 g_free(hentry[H_BCC].body);
2436 hentry[H_BCC].body = NULL;
2438 if (hentry[H_NEWSGROUPS].body != NULL) {
2439 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2440 hentry[H_NEWSGROUPS].body = NULL;
2442 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2443 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2444 compose->followup_to =
2445 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2448 g_free(hentry[H_FOLLOWUP_TO].body);
2449 hentry[H_FOLLOWUP_TO].body = NULL;
2451 if (hentry[H_LIST_POST].body != NULL) {
2454 extract_address(hentry[H_LIST_POST].body);
2455 if (hentry[H_LIST_POST].body[0] != '\0') {
2456 scan_mailto_url(hentry[H_LIST_POST].body,
2457 &to, NULL, NULL, NULL, NULL, NULL);
2459 g_free(compose->ml_post);
2460 compose->ml_post = to;
2463 g_free(hentry[H_LIST_POST].body);
2464 hentry[H_LIST_POST].body = NULL;
2467 /* CLAWS - X-Priority */
2468 if (compose->mode == COMPOSE_REEDIT)
2469 if (hentry[H_X_PRIORITY].body != NULL) {
2472 priority = atoi(hentry[H_X_PRIORITY].body);
2473 g_free(hentry[H_X_PRIORITY].body);
2475 hentry[H_X_PRIORITY].body = NULL;
2477 if (priority < PRIORITY_HIGHEST ||
2478 priority > PRIORITY_LOWEST)
2479 priority = PRIORITY_NORMAL;
2481 compose->priority = priority;
2484 if (compose->mode == COMPOSE_REEDIT) {
2485 if (msginfo->inreplyto && *msginfo->inreplyto)
2486 compose->inreplyto = g_strdup(msginfo->inreplyto);
2490 if (msginfo->msgid && *msginfo->msgid)
2491 compose->inreplyto = g_strdup(msginfo->msgid);
2493 if (!compose->references) {
2494 if (msginfo->msgid && *msginfo->msgid) {
2495 if (msginfo->inreplyto && *msginfo->inreplyto)
2496 compose->references =
2497 g_strdup_printf("<%s>\n\t<%s>",
2501 compose->references =
2502 g_strconcat("<", msginfo->msgid, ">",
2504 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
2505 compose->references =
2506 g_strconcat("<", msginfo->inreplyto, ">",
2514 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
2516 GSList *ref_id_list, *cur;
2520 ref_id_list = references_list_append(NULL, ref);
2521 if (!ref_id_list) return NULL;
2522 if (msgid && *msgid)
2523 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
2528 for (cur = ref_id_list; cur != NULL; cur = cur->next)
2529 /* "<" + Message-ID + ">" + CR+LF+TAB */
2530 len += strlen((gchar *)cur->data) + 5;
2532 if (len > MAX_REFERENCES_LEN) {
2533 /* remove second message-ID */
2534 if (ref_id_list && ref_id_list->next &&
2535 ref_id_list->next->next) {
2536 g_free(ref_id_list->next->data);
2537 ref_id_list = g_slist_remove
2538 (ref_id_list, ref_id_list->next->data);
2540 slist_free_strings(ref_id_list);
2541 g_slist_free(ref_id_list);
2548 new_ref = g_string_new("");
2549 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
2550 if (new_ref->len > 0)
2551 g_string_append(new_ref, "\n\t");
2552 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
2555 slist_free_strings(ref_id_list);
2556 g_slist_free(ref_id_list);
2558 new_ref_str = new_ref->str;
2559 g_string_free(new_ref, FALSE);
2564 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
2565 const gchar *fmt, const gchar *qmark,
2566 const gchar *body, gboolean rewrap,
2567 gboolean need_unescape,
2568 const gchar *err_msg)
2570 MsgInfo* dummyinfo = NULL;
2571 gchar *quote_str = NULL;
2573 gboolean prev_autowrap;
2574 const gchar *trimmed_body = body;
2575 gint cursor_pos = -1;
2576 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2577 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2582 SIGNAL_BLOCK(buffer);
2585 dummyinfo = compose_msginfo_new_from_compose(compose);
2586 msginfo = dummyinfo;
2589 if (qmark != NULL) {
2591 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
2592 compose->gtkaspell);
2594 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
2596 quote_fmt_scan_string(qmark);
2599 buf = quote_fmt_get_buffer();
2601 alertpanel_error(_("Quote mark format error."));
2603 Xstrdup_a(quote_str, buf, goto error)
2606 if (fmt && *fmt != '\0') {
2609 while (*trimmed_body == '\n')
2613 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account,
2614 compose->gtkaspell);
2616 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account);
2618 if (need_unescape) {
2621 /* decode \-escape sequences in the internal representation of the quote format */
2622 tmp = malloc(strlen(fmt)+1);
2623 pref_get_unescaped_pref(tmp, fmt);
2624 quote_fmt_scan_string(tmp);
2628 quote_fmt_scan_string(fmt);
2632 buf = quote_fmt_get_buffer();
2634 gint line = quote_fmt_get_line();
2635 gchar *msg = g_strdup_printf(err_msg, line);
2636 alertpanel_error(msg);
2643 prev_autowrap = compose->autowrap;
2644 compose->autowrap = FALSE;
2646 mark = gtk_text_buffer_get_insert(buffer);
2647 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2648 if (g_utf8_validate(buf, -1, NULL)) {
2649 gtk_text_buffer_insert(buffer, &iter, buf, -1);
2651 gchar *tmpout = NULL;
2652 tmpout = conv_codeset_strdup
2653 (buf, conv_get_locale_charset_str_no_utf8(),
2655 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
2657 tmpout = g_malloc(strlen(buf)*2+1);
2658 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
2660 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
2664 cursor_pos = quote_fmt_get_cursor_pos();
2665 compose->set_cursor_pos = cursor_pos;
2666 if (cursor_pos == -1) {
2669 gtk_text_buffer_get_start_iter(buffer, &iter);
2670 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
2671 gtk_text_buffer_place_cursor(buffer, &iter);
2673 compose->autowrap = prev_autowrap;
2674 if (compose->autowrap && rewrap)
2675 compose_wrap_all(compose);
2682 SIGNAL_UNBLOCK(buffer);
2684 procmsg_msginfo_free( dummyinfo );
2689 /* if ml_post is of type addr@host and from is of type
2690 * addr-anything@host, return TRUE
2692 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
2694 gchar *left_ml = NULL;
2695 gchar *right_ml = NULL;
2696 gchar *left_from = NULL;
2697 gchar *right_from = NULL;
2698 gboolean result = FALSE;
2700 if (!ml_post || !from)
2703 left_ml = g_strdup(ml_post);
2704 if (strstr(left_ml, "@")) {
2705 right_ml = strstr(left_ml, "@")+1;
2706 *(strstr(left_ml, "@")) = '\0';
2709 left_from = g_strdup(from);
2710 if (strstr(left_from, "@")) {
2711 right_from = strstr(left_from, "@")+1;
2712 *(strstr(left_from, "@")) = '\0';
2715 if (left_ml && left_from && right_ml && right_from
2716 && !strncmp(left_from, left_ml, strlen(left_ml))
2717 && !strcmp(right_from, right_ml)) {
2726 static gboolean same_address(const gchar *addr1, const gchar *addr2)
2728 gchar *my_addr1, *my_addr2;
2730 if (!addr1 || !addr2)
2733 Xstrdup_a(my_addr1, addr1, return FALSE);
2734 Xstrdup_a(my_addr2, addr2, return FALSE);
2736 extract_address(my_addr1);
2737 extract_address(my_addr2);
2739 return !strcasecmp(my_addr1, my_addr2);
2742 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
2743 gboolean to_all, gboolean to_ml,
2745 gboolean followup_and_reply_to)
2747 GSList *cc_list = NULL;
2750 gchar *replyto = NULL;
2751 GHashTable *to_table;
2753 gboolean reply_to_ml = FALSE;
2754 gboolean default_reply_to = FALSE;
2756 g_return_if_fail(compose->account != NULL);
2757 g_return_if_fail(msginfo != NULL);
2759 reply_to_ml = to_ml && compose->ml_post;
2761 default_reply_to = msginfo->folder &&
2762 msginfo->folder->prefs->enable_default_reply_to;
2764 if (compose->account->protocol != A_NNTP) {
2765 if (reply_to_ml && !default_reply_to) {
2767 gboolean is_subscr = is_subscription(compose->ml_post,
2770 /* normal answer to ml post with a reply-to */
2771 compose_entry_append(compose,
2774 if (compose->replyto
2775 && !same_address(compose->ml_post, compose->replyto))
2776 compose_entry_append(compose,
2780 /* answer to subscription confirmation */
2781 if (compose->replyto)
2782 compose_entry_append(compose,
2785 else if (msginfo->from)
2786 compose_entry_append(compose,
2791 else if (!(to_all || to_sender) && default_reply_to) {
2792 compose_entry_append(compose,
2793 msginfo->folder->prefs->default_reply_to,
2795 compose_entry_mark_default_to(compose,
2796 msginfo->folder->prefs->default_reply_to);
2801 Xstrdup_a(tmp1, msginfo->from, return);
2802 extract_address(tmp1);
2803 if (to_all || to_sender ||
2804 !account_find_from_address(tmp1))
2805 compose_entry_append(compose,
2806 (compose->replyto && !to_sender)
2807 ? compose->replyto :
2808 msginfo->from ? msginfo->from : "",
2810 else if (!to_all && !to_sender) {
2811 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
2812 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
2813 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
2814 compose_entry_append(compose,
2815 msginfo->from ? msginfo->from : "",
2818 /* replying to own mail, use original recp */
2819 compose_entry_append(compose,
2820 msginfo->to ? msginfo->to : "",
2822 compose_entry_append(compose,
2823 msginfo->cc ? msginfo->cc : "",
2829 if (to_sender || (compose->followup_to &&
2830 !strncmp(compose->followup_to, "poster", 6)))
2831 compose_entry_append
2833 (compose->replyto ? compose->replyto :
2834 msginfo->from ? msginfo->from : ""),
2837 else if (followup_and_reply_to || to_all) {
2838 compose_entry_append
2840 (compose->replyto ? compose->replyto :
2841 msginfo->from ? msginfo->from : ""),
2844 compose_entry_append
2846 compose->followup_to ? compose->followup_to :
2847 compose->newsgroups ? compose->newsgroups : "",
2848 COMPOSE_NEWSGROUPS);
2851 compose_entry_append
2853 compose->followup_to ? compose->followup_to :
2854 compose->newsgroups ? compose->newsgroups : "",
2855 COMPOSE_NEWSGROUPS);
2858 if (msginfo->subject && *msginfo->subject) {
2862 buf = p = g_strdup(msginfo->subject);
2863 p += subject_get_prefix_length(p);
2864 memmove(buf, p, strlen(p) + 1);
2866 buf2 = g_strdup_printf("Re: %s", buf);
2867 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
2872 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
2874 if (to_ml && compose->ml_post) return;
2875 if (!to_all || compose->account->protocol == A_NNTP) return;
2877 if (compose->replyto) {
2878 Xstrdup_a(replyto, compose->replyto, return);
2879 extract_address(replyto);
2881 if (msginfo->from) {
2882 Xstrdup_a(from, msginfo->from, return);
2883 extract_address(from);
2886 if (replyto && from)
2887 cc_list = address_list_append_with_comments(cc_list, from);
2888 if (to_all && msginfo->folder &&
2889 msginfo->folder->prefs->enable_default_reply_to)
2890 cc_list = address_list_append_with_comments(cc_list,
2891 msginfo->folder->prefs->default_reply_to);
2892 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
2893 cc_list = address_list_append_with_comments(cc_list, compose->cc);
2895 to_table = g_hash_table_new(g_str_hash, g_str_equal);
2897 g_hash_table_insert(to_table, g_utf8_strdown(replyto, -1), GINT_TO_POINTER(1));
2898 if (compose->account) {
2899 g_hash_table_insert(to_table, g_utf8_strdown(compose->account->address, -1),
2900 GINT_TO_POINTER(1));
2902 /* remove address on To: and that of current account */
2903 for (cur = cc_list; cur != NULL; ) {
2904 GSList *next = cur->next;
2907 addr = g_utf8_strdown(cur->data, -1);
2908 extract_address(addr);
2910 if (GPOINTER_TO_INT(g_hash_table_lookup(to_table, addr)) == 1)
2911 cc_list = g_slist_remove(cc_list, cur->data);
2913 g_hash_table_insert(to_table, addr, GINT_TO_POINTER(1));
2917 hash_free_strings(to_table);
2918 g_hash_table_destroy(to_table);
2921 for (cur = cc_list; cur != NULL; cur = cur->next)
2922 compose_entry_append(compose, (gchar *)cur->data,
2924 slist_free_strings(cc_list);
2925 g_slist_free(cc_list);
2930 #define SET_ENTRY(entry, str) \
2933 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
2936 #define SET_ADDRESS(type, str) \
2939 compose_entry_append(compose, str, type); \
2942 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
2944 g_return_if_fail(msginfo != NULL);
2946 SET_ENTRY(subject_entry, msginfo->subject);
2947 SET_ENTRY(from_name, msginfo->from);
2948 SET_ADDRESS(COMPOSE_TO, msginfo->to);
2949 SET_ADDRESS(COMPOSE_CC, compose->cc);
2950 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
2951 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
2952 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
2953 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
2955 compose_update_priority_menu_item(compose);
2956 compose_update_privacy_system_menu_item(compose, FALSE);
2957 compose_show_first_last_header(compose, TRUE);
2963 static void compose_insert_sig(Compose *compose, gboolean replace)
2965 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2966 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2968 GtkTextIter iter, iter_end;
2970 gchar *search = NULL;
2971 gboolean prev_autowrap;
2972 gboolean found = FALSE, shift = FALSE;
2975 g_return_if_fail(compose->account != NULL);
2977 prev_autowrap = compose->autowrap;
2978 compose->autowrap = FALSE;
2980 g_signal_handlers_block_by_func(G_OBJECT(buffer),
2981 G_CALLBACK(compose_changed_cb),
2984 mark = gtk_text_buffer_get_insert(buffer);
2985 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2986 cur_pos = gtk_text_iter_get_offset (&iter);
2988 gtk_text_buffer_get_end_iter(buffer, &iter);
2990 search = compose->sig_str;
2992 if (replace && search) {
2993 GtkTextIter first_iter, start_iter, end_iter;
2995 gtk_text_buffer_get_start_iter(buffer, &first_iter);
2997 if (compose->sig_str[0] == '\0')
3000 found = gtk_text_iter_forward_search(&first_iter,
3002 GTK_TEXT_SEARCH_TEXT_ONLY,
3003 &start_iter, &end_iter,
3007 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3011 if (replace && !found && search && strlen(search) > 2
3012 && search[0] == '\n' && search[1] == '\n') {
3018 g_free(compose->sig_str);
3019 compose->sig_str = compose_get_signature_str(compose);
3020 if (!compose->sig_str || (replace && !compose->account->auto_sig))
3021 compose->sig_str = g_strdup("");
3023 cur_pos = gtk_text_iter_get_offset(&iter);
3025 gtk_text_buffer_insert(buffer, &iter, compose->sig_str + 1, -1);
3027 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3029 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3030 gtk_text_iter_forward_char(&iter);
3031 gtk_text_iter_forward_char(&iter);
3032 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3033 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3035 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3036 cur_pos = gtk_text_buffer_get_char_count (buffer);
3038 /* put the cursor where it should be
3039 * either where the quote_fmt says, either before the signature */
3040 if (compose->set_cursor_pos < 0)
3041 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3043 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3044 compose->set_cursor_pos);
3046 gtk_text_buffer_place_cursor(buffer, &iter);
3047 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3048 G_CALLBACK(compose_changed_cb),
3051 compose->autowrap = prev_autowrap;
3052 if (compose->autowrap)
3053 compose_wrap_all(compose);
3056 static gchar *compose_get_signature_str(Compose *compose)
3058 gchar *sig_body = NULL;
3059 gchar *sig_str = NULL;
3060 gchar *utf8_sig_str = NULL;
3062 g_return_val_if_fail(compose->account != NULL, NULL);
3064 if (!compose->account->sig_path)
3067 if (compose->account->sig_type == SIG_FILE) {
3068 if (!is_file_or_fifo_exist(compose->account->sig_path)) {
3069 g_warning("can't open signature file: %s\n",
3070 compose->account->sig_path);
3075 if (compose->account->sig_type == SIG_COMMAND)
3076 sig_body = get_command_output(compose->account->sig_path);
3080 tmp = file_read_to_str(compose->account->sig_path);
3083 sig_body = normalize_newlines(tmp);
3087 if (compose->account->sig_sep) {
3088 sig_str = g_strconcat("\n\n", compose->account->sig_sep, "\n", sig_body,
3092 sig_str = g_strconcat("\n\n", sig_body, NULL);
3095 if (g_utf8_validate(sig_str, -1, NULL) == TRUE)
3096 utf8_sig_str = sig_str;
3098 utf8_sig_str = conv_codeset_strdup
3099 (sig_str, conv_get_locale_charset_str_no_utf8(),
3105 return utf8_sig_str;
3108 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3111 GtkTextBuffer *buffer;
3114 const gchar *cur_encoding;
3115 gchar buf[BUFFSIZE];
3118 gboolean prev_autowrap;
3119 gboolean badtxt = FALSE;
3121 g_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3123 if ((fp = g_fopen(file, "rb")) == NULL) {
3124 FILE_OP_ERROR(file, "fopen");
3125 return COMPOSE_INSERT_READ_ERROR;
3128 prev_autowrap = compose->autowrap;
3129 compose->autowrap = FALSE;
3131 text = GTK_TEXT_VIEW(compose->text);
3132 buffer = gtk_text_view_get_buffer(text);
3133 mark = gtk_text_buffer_get_insert(buffer);
3134 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3136 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3137 G_CALLBACK(text_inserted),
3140 cur_encoding = conv_get_locale_charset_str_no_utf8();
3142 while (fgets(buf, sizeof(buf), fp) != NULL) {
3145 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3146 str = g_strdup(buf);
3148 str = conv_codeset_strdup
3149 (buf, cur_encoding, CS_INTERNAL);
3152 /* strip <CR> if DOS/Windows file,
3153 replace <CR> with <LF> if Macintosh file. */
3156 if (len > 0 && str[len - 1] != '\n') {
3158 if (str[len] == '\r') str[len] = '\n';
3161 gtk_text_buffer_insert(buffer, &iter, str, -1);
3165 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3166 G_CALLBACK(text_inserted),
3168 compose->autowrap = prev_autowrap;
3169 if (compose->autowrap)
3170 compose_wrap_all(compose);
3175 return COMPOSE_INSERT_INVALID_CHARACTER;
3177 return COMPOSE_INSERT_SUCCESS;
3180 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3181 const gchar *filename,
3182 const gchar *content_type)
3190 GtkListStore *store;
3192 gboolean has_binary = FALSE;
3194 if (!is_file_exist(file)) {
3195 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3196 gboolean result = FALSE;
3197 if (file_from_uri && is_file_exist(file_from_uri)) {
3198 result = compose_attach_append(
3199 compose, file_from_uri,
3203 g_free(file_from_uri);
3206 alertpanel_error("File %s doesn't exist\n", filename);
3209 if ((size = get_file_size(file)) < 0) {
3210 alertpanel_error("Can't get file size of %s\n", filename);
3214 alertpanel_error(_("File %s is empty."), filename);
3217 if ((fp = g_fopen(file, "rb")) == NULL) {
3218 alertpanel_error(_("Can't read %s."), filename);
3223 ainfo = g_new0(AttachInfo, 1);
3224 auto_ainfo = g_auto_pointer_new_with_free
3225 (ainfo, (GFreeFunc) compose_attach_info_free);
3226 ainfo->file = g_strdup(file);
3229 ainfo->content_type = g_strdup(content_type);
3230 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3232 MsgFlags flags = {0, 0};
3234 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3235 ainfo->encoding = ENC_7BIT;
3237 ainfo->encoding = ENC_8BIT;
3239 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3240 if (msginfo && msginfo->subject)
3241 name = g_strdup(msginfo->subject);
3243 name = g_path_get_basename(filename ? filename : file);
3245 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3247 procmsg_msginfo_free(msginfo);
3249 if (!g_ascii_strncasecmp(content_type, "text", 4))
3250 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3252 ainfo->encoding = ENC_BASE64;
3253 name = g_path_get_basename(filename ? filename : file);
3254 ainfo->name = g_strdup(name);
3258 ainfo->content_type = procmime_get_mime_type(file);
3259 if (!ainfo->content_type) {
3260 ainfo->content_type =
3261 g_strdup("application/octet-stream");
3262 ainfo->encoding = ENC_BASE64;
3263 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text", 4))
3265 procmime_get_encoding_for_text_file(file, &has_binary);
3267 ainfo->encoding = ENC_BASE64;
3268 name = g_path_get_basename(filename ? filename : file);
3269 ainfo->name = g_strdup(name);
3273 if (ainfo->name != NULL
3274 && !strcmp(ainfo->name, ".")) {
3275 g_free(ainfo->name);
3279 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3280 g_free(ainfo->content_type);
3281 ainfo->content_type = g_strdup("application/octet-stream");
3285 size_text = to_human_readable(size);
3287 store = GTK_LIST_STORE(gtk_tree_view_get_model
3288 (GTK_TREE_VIEW(compose->attach_clist)));
3290 gtk_list_store_append(store, &iter);
3291 gtk_list_store_set(store, &iter,
3292 COL_MIMETYPE, ainfo->content_type,
3293 COL_SIZE, size_text,
3294 COL_NAME, ainfo->name,
3296 COL_AUTODATA, auto_ainfo,
3299 g_auto_pointer_free(auto_ainfo);
3303 static void compose_use_signing(Compose *compose, gboolean use_signing)
3305 GtkItemFactory *ifactory;
3306 GtkWidget *menuitem = NULL;
3308 compose->use_signing = use_signing;
3309 ifactory = gtk_item_factory_from_widget(compose->menubar);
3310 menuitem = gtk_item_factory_get_item
3311 (ifactory, "/Options/Sign");
3312 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
3316 static void compose_use_encryption(Compose *compose, gboolean use_encryption)
3318 GtkItemFactory *ifactory;
3319 GtkWidget *menuitem = NULL;
3321 compose->use_encryption = use_encryption;
3322 ifactory = gtk_item_factory_from_widget(compose->menubar);
3323 menuitem = gtk_item_factory_get_item
3324 (ifactory, "/Options/Encrypt");
3326 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
3330 #define NEXT_PART_NOT_CHILD(info) \
3332 node = info->node; \
3333 while (node->children) \
3334 node = g_node_last_child(node); \
3335 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3338 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3342 MimeInfo *firsttext = NULL;
3343 MimeInfo *encrypted = NULL;
3346 const gchar *partname = NULL;
3348 mimeinfo = procmime_scan_message(msginfo);
3349 if (!mimeinfo) return;
3351 if (mimeinfo->node->children == NULL) {
3352 procmime_mimeinfo_free_all(mimeinfo);
3356 /* find first content part */
3357 child = (MimeInfo *) mimeinfo->node->children->data;
3358 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3359 child = (MimeInfo *)child->node->children->data;
3361 if (child->type == MIMETYPE_TEXT) {
3363 debug_print("First text part found\n");
3364 } else if (compose->mode == COMPOSE_REEDIT &&
3365 child->type == MIMETYPE_APPLICATION &&
3366 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3367 encrypted = (MimeInfo *)child->node->parent->data;
3370 child = (MimeInfo *) mimeinfo->node->children->data;
3371 while (child != NULL) {
3374 if (child == encrypted) {
3375 /* skip this part of tree */
3376 NEXT_PART_NOT_CHILD(child);
3380 if (child->type == MIMETYPE_MULTIPART) {
3381 /* get the actual content */
3382 child = procmime_mimeinfo_next(child);
3386 if (child == firsttext) {
3387 child = procmime_mimeinfo_next(child);
3391 outfile = procmime_get_tmp_file_name(child);
3392 if ((err = procmime_get_part(outfile, child)) < 0)
3393 g_warning("Can't get the part of multipart message. (%s)", strerror(-err));
3395 gchar *content_type;
3397 content_type = procmime_get_content_type_str(child->type, child->subtype);
3399 /* if we meet a pgp signature, we don't attach it, but
3400 * we force signing. */
3401 if ((strcmp(content_type, "application/pgp-signature") &&
3402 strcmp(content_type, "application/pkcs7-signature") &&
3403 strcmp(content_type, "application/x-pkcs7-signature"))
3404 || compose->mode == COMPOSE_REDIRECT) {
3405 partname = procmime_mimeinfo_get_parameter(child, "filename");
3406 if (partname == NULL)
3407 partname = procmime_mimeinfo_get_parameter(child, "name");
3408 if (partname == NULL)
3410 compose_attach_append(compose, outfile,
3411 partname, content_type);
3413 compose_force_signing(compose, compose->account);
3415 g_free(content_type);
3418 NEXT_PART_NOT_CHILD(child);
3420 procmime_mimeinfo_free_all(mimeinfo);
3423 #undef NEXT_PART_NOT_CHILD
3428 WAIT_FOR_INDENT_CHAR,
3429 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3432 /* return indent length, we allow:
3433 indent characters followed by indent characters or spaces/tabs,
3434 alphabets and numbers immediately followed by indent characters,
3435 and the repeating sequences of the above
3436 If quote ends with multiple spaces, only the first one is included. */
3437 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3438 const GtkTextIter *start, gint *len)
3440 GtkTextIter iter = *start;
3444 IndentState state = WAIT_FOR_INDENT_CHAR;
3447 gint alnum_count = 0;
3448 gint space_count = 0;
3451 if (prefs_common.quote_chars == NULL) {
3455 while (!gtk_text_iter_ends_line(&iter)) {
3456 wc = gtk_text_iter_get_char(&iter);
3457 if (g_unichar_iswide(wc))
3459 clen = g_unichar_to_utf8(wc, ch);
3463 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
3464 is_space = g_unichar_isspace(wc);
3466 if (state == WAIT_FOR_INDENT_CHAR) {
3467 if (!is_indent && !g_unichar_isalnum(wc))
3470 quote_len += alnum_count + space_count + 1;
3471 alnum_count = space_count = 0;
3472 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
3475 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
3476 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
3480 else if (is_indent) {
3481 quote_len += alnum_count + space_count + 1;
3482 alnum_count = space_count = 0;
3485 state = WAIT_FOR_INDENT_CHAR;
3489 gtk_text_iter_forward_char(&iter);
3492 if (quote_len > 0 && space_count > 0)
3498 if (quote_len > 0) {
3500 gtk_text_iter_forward_chars(&iter, quote_len);
3501 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
3507 /* return TRUE if the line is itemized */
3508 static gboolean compose_is_itemized(GtkTextBuffer *buffer,
3509 const GtkTextIter *start)
3511 GtkTextIter iter = *start;
3516 if (gtk_text_iter_ends_line(&iter))
3520 wc = gtk_text_iter_get_char(&iter);
3521 if (!g_unichar_isspace(wc))
3523 gtk_text_iter_forward_char(&iter);
3524 if (gtk_text_iter_ends_line(&iter))
3528 clen = g_unichar_to_utf8(wc, ch);
3532 if (!strchr("*-+", ch[0]))
3535 gtk_text_iter_forward_char(&iter);
3536 if (gtk_text_iter_ends_line(&iter))
3538 wc = gtk_text_iter_get_char(&iter);
3539 if (g_unichar_isspace(wc))
3545 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
3546 const GtkTextIter *start,
3547 GtkTextIter *break_pos,
3551 GtkTextIter iter = *start, line_end = *start;
3552 PangoLogAttr *attrs;
3559 gboolean can_break = FALSE;
3560 gboolean do_break = FALSE;
3561 gboolean was_white = FALSE;
3562 gboolean prev_dont_break = FALSE;
3564 gtk_text_iter_forward_to_line_end(&line_end);
3565 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
3566 len = g_utf8_strlen(str, -1);
3567 /* g_print("breaking line: %d: %s (len = %d)\n",
3568 gtk_text_iter_get_line(&iter), str, len); */
3569 attrs = g_new(PangoLogAttr, len + 1);
3571 pango_default_break(str, -1, NULL, attrs, len + 1);
3575 /* skip quote and leading spaces */
3576 for (i = 0; *p != '\0' && i < len; i++) {
3579 wc = g_utf8_get_char(p);
3580 if (i >= quote_len && !g_unichar_isspace(wc))
3582 if (g_unichar_iswide(wc))
3584 else if (*p == '\t')
3588 p = g_utf8_next_char(p);
3591 for (; *p != '\0' && i < len; i++) {
3592 PangoLogAttr *attr = attrs + i;
3596 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
3599 was_white = attr->is_white;
3601 /* don't wrap URI */
3602 if ((uri_len = get_uri_len(p)) > 0) {
3604 if (pos > 0 && col > max_col) {
3614 wc = g_utf8_get_char(p);
3615 if (g_unichar_iswide(wc)) {
3617 if (prev_dont_break && can_break && attr->is_line_break)
3619 } else if (*p == '\t')
3623 if (pos > 0 && col > max_col) {
3628 if (*p == '-' || *p == '/')
3629 prev_dont_break = TRUE;
3631 prev_dont_break = FALSE;
3633 p = g_utf8_next_char(p);
3637 debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col);
3642 *break_pos = *start;
3643 gtk_text_iter_set_line_offset(break_pos, pos);
3648 static gboolean compose_join_next_line(Compose *compose,
3649 GtkTextBuffer *buffer,
3651 const gchar *quote_str)
3653 GtkTextIter iter_ = *iter, cur, prev, next, end;
3654 PangoLogAttr attrs[3];
3656 gchar *next_quote_str;
3659 gboolean keep_cursor = FALSE;
3661 if (!gtk_text_iter_forward_line(&iter_) ||
3662 gtk_text_iter_ends_line(&iter_))
3665 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
3667 if ((quote_str || next_quote_str) &&
3668 strcmp2(quote_str, next_quote_str) != 0) {
3669 g_free(next_quote_str);
3672 g_free(next_quote_str);
3675 if (quote_len > 0) {
3676 gtk_text_iter_forward_chars(&end, quote_len);
3677 if (gtk_text_iter_ends_line(&end))
3681 /* don't join itemized lines */
3682 if (compose_is_itemized(buffer, &end))
3685 /* don't join signature separator */
3686 if (compose_is_sig_separator(compose, buffer, &iter_))
3689 /* delete quote str */
3691 gtk_text_buffer_delete(buffer, &iter_, &end);
3693 /* don't join line breaks put by the user */
3695 gtk_text_iter_backward_char(&cur);
3696 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
3697 gtk_text_iter_forward_char(&cur);
3701 gtk_text_iter_forward_char(&cur);
3702 /* delete linebreak and extra spaces */
3703 while (gtk_text_iter_backward_char(&cur)) {
3704 wc1 = gtk_text_iter_get_char(&cur);
3705 if (!g_unichar_isspace(wc1))
3710 while (!gtk_text_iter_ends_line(&cur)) {
3711 wc1 = gtk_text_iter_get_char(&cur);
3712 if (!g_unichar_isspace(wc1))
3714 gtk_text_iter_forward_char(&cur);
3717 if (!gtk_text_iter_equal(&prev, &next)) {
3720 mark = gtk_text_buffer_get_insert(buffer);
3721 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
3722 if (gtk_text_iter_equal(&prev, &cur))
3724 gtk_text_buffer_delete(buffer, &prev, &next);
3728 /* insert space if required */
3729 gtk_text_iter_backward_char(&prev);
3730 wc1 = gtk_text_iter_get_char(&prev);
3731 wc2 = gtk_text_iter_get_char(&next);
3732 gtk_text_iter_forward_char(&next);
3733 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
3734 pango_default_break(str, -1, NULL, attrs, 3);
3735 if (!attrs[1].is_line_break ||
3736 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
3737 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
3739 gtk_text_iter_backward_char(&iter_);
3740 gtk_text_buffer_place_cursor(buffer, &iter_);
3749 #define ADD_TXT_POS(bp_, ep_, pti_) \
3750 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
3751 last = last->next; \
3752 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
3753 last->next = NULL; \
3755 g_warning("alloc error scanning URIs\n"); \
3758 static gboolean automatic_break = FALSE;
3759 static void compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
3761 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3762 GtkTextBuffer *buffer;
3763 GtkTextIter iter, break_pos, end_of_line;
3764 gchar *quote_str = NULL;
3766 gboolean wrap_quote = prefs_common.linewrap_quote;
3767 gboolean prev_autowrap = compose->autowrap;
3768 gint startq_offset = -1, noq_offset = -1;
3769 gint uri_start = -1, uri_stop = -1;
3770 gint nouri_start = -1, nouri_stop = -1;
3771 gint num_blocks = 0;
3772 gint quotelevel = -1;
3774 compose->autowrap = FALSE;
3776 buffer = gtk_text_view_get_buffer(text);
3777 undo_wrapping(compose->undostruct, TRUE);
3782 mark = gtk_text_buffer_get_insert(buffer);
3783 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3786 /* move to paragraph start */
3787 gtk_text_iter_set_line_offset(&iter, 0);
3788 if (gtk_text_iter_ends_line(&iter)) {
3789 while (gtk_text_iter_ends_line(&iter) &&
3790 gtk_text_iter_forward_line(&iter))
3793 while (gtk_text_iter_backward_line(&iter)) {
3794 if (gtk_text_iter_ends_line(&iter)) {
3795 gtk_text_iter_forward_line(&iter);
3801 /* go until paragraph end (empty line) */
3803 while (!gtk_text_iter_ends_line(&iter)) {
3804 gchar *scanpos = NULL;
3805 /* parse table - in order of priority */
3807 const gchar *needle; /* token */
3809 /* token search function */
3810 gchar *(*search) (const gchar *haystack,
3811 const gchar *needle);
3812 /* part parsing function */
3813 gboolean (*parse) (const gchar *start,
3814 const gchar *scanpos,
3818 /* part to URI function */
3819 gchar *(*build_uri) (const gchar *bp,
3823 static struct table parser[] = {
3824 {"http://", strcasestr, get_uri_part, make_uri_string},
3825 {"https://", strcasestr, get_uri_part, make_uri_string},
3826 {"ftp://", strcasestr, get_uri_part, make_uri_string},
3827 {"sftp://", strcasestr, get_uri_part, make_uri_string},
3828 {"www.", strcasestr, get_uri_part, make_http_string},
3829 {"mailto:", strcasestr, get_uri_part, make_uri_string},
3830 {"@", strcasestr, get_email_part, make_email_string}
3832 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
3833 gint last_index = PARSE_ELEMS;
3835 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
3838 if (!prev_autowrap && num_blocks == 0) {
3840 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3841 G_CALLBACK(text_inserted),
3844 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
3847 uri_start = uri_stop = -1;
3849 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
3852 debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str);
3853 if (startq_offset == -1)
3854 startq_offset = gtk_text_iter_get_offset(&iter);
3855 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
3856 if (quotelevel > 2) {
3857 /* recycle colors */
3858 if (prefs_common.recycle_quote_colors)
3867 if (startq_offset == -1)
3868 noq_offset = gtk_text_iter_get_offset(&iter);
3872 if (prev_autowrap == FALSE && !force && !wrap_quote) {
3875 if (compose_get_line_break_pos(buffer, &iter, &break_pos,
3876 prefs_common.linewrap_len,
3878 GtkTextIter prev, next, cur;
3880 if (prev_autowrap != FALSE || force) {
3881 automatic_break = TRUE;
3882 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
3883 automatic_break = FALSE;
3884 } else if (quote_str && wrap_quote) {
3885 automatic_break = TRUE;
3886 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
3887 automatic_break = FALSE;
3890 /* remove trailing spaces */
3892 gtk_text_iter_backward_char(&cur);
3894 while (!gtk_text_iter_starts_line(&cur)) {
3897 gtk_text_iter_backward_char(&cur);
3898 wc = gtk_text_iter_get_char(&cur);
3899 if (!g_unichar_isspace(wc))
3903 if (!gtk_text_iter_equal(&prev, &next)) {
3904 gtk_text_buffer_delete(buffer, &prev, &next);
3906 gtk_text_iter_forward_char(&break_pos);
3910 gtk_text_buffer_insert(buffer, &break_pos,
3914 compose_join_next_line(compose, buffer, &iter, quote_str);
3916 /* move iter to current line start */
3917 gtk_text_iter_set_line_offset(&iter, 0);
3924 /* move iter to next line start */
3929 if (!prev_autowrap && num_blocks > 0) {
3931 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3932 G_CALLBACK(text_inserted),
3936 while (!gtk_text_iter_ends_line(&end_of_line)) {
3937 gtk_text_iter_forward_char(&end_of_line);
3939 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
3941 nouri_start = gtk_text_iter_get_offset(&iter);
3942 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
3944 walk_pos = gtk_text_iter_get_offset(&iter);
3945 /* FIXME: this looks phony. scanning for anything in the parse table */
3946 for (n = 0; n < PARSE_ELEMS; n++) {
3949 tmp = parser[n].search(walk, parser[n].needle);
3951 if (scanpos == NULL || tmp < scanpos) {
3960 /* check if URI can be parsed */
3961 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
3962 (const gchar **)&ep, FALSE)
3963 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
3967 strlen(parser[last_index].needle);
3970 uri_start = walk_pos + (bp - o_walk);
3971 uri_stop = walk_pos + (ep - o_walk);
3975 gtk_text_iter_forward_line(&iter);
3978 if (startq_offset != -1) {
3979 GtkTextIter startquote, endquote;
3980 gtk_text_buffer_get_iter_at_offset(
3981 buffer, &startquote, startq_offset);
3984 switch (quotelevel) {
3985 case 0: gtk_text_buffer_apply_tag_by_name(
3986 buffer, "quote0", &startquote, &endquote);
3988 case 1: gtk_text_buffer_apply_tag_by_name(
3989 buffer, "quote1", &startquote, &endquote);
3991 case 2: gtk_text_buffer_apply_tag_by_name(
3992 buffer, "quote2", &startquote, &endquote);
3996 } else if (noq_offset != -1) {
3997 GtkTextIter startnoquote, endnoquote;
3998 gtk_text_buffer_get_iter_at_offset(
3999 buffer, &startnoquote, noq_offset);
4001 gtk_text_buffer_remove_tag_by_name(
4002 buffer, "quote0", &startnoquote, &endnoquote);
4003 gtk_text_buffer_remove_tag_by_name(
4004 buffer, "quote1", &startnoquote, &endnoquote);
4005 gtk_text_buffer_remove_tag_by_name(
4006 buffer, "quote2", &startnoquote, &endnoquote);
4011 GtkTextIter nouri_start_iter, nouri_end_iter;
4012 gtk_text_buffer_get_iter_at_offset(
4013 buffer, &nouri_start_iter, nouri_start);
4014 gtk_text_buffer_get_iter_at_offset(
4015 buffer, &nouri_end_iter, nouri_stop);
4016 gtk_text_buffer_remove_tag_by_name(
4017 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4019 if (uri_start > 0 && uri_stop > 0) {
4020 GtkTextIter uri_start_iter, uri_end_iter;
4021 gtk_text_buffer_get_iter_at_offset(
4022 buffer, &uri_start_iter, uri_start);
4023 gtk_text_buffer_get_iter_at_offset(
4024 buffer, &uri_end_iter, uri_stop);
4025 gtk_text_buffer_apply_tag_by_name(
4026 buffer, "link", &uri_start_iter, &uri_end_iter);
4032 undo_wrapping(compose->undostruct, FALSE);
4033 compose->autowrap = prev_autowrap;
4036 void compose_action_cb(void *data)
4038 Compose *compose = (Compose *)data;
4039 compose_wrap_all(compose);
4042 static void compose_wrap_all(Compose *compose)
4044 compose_wrap_all_full(compose, FALSE);
4047 static void compose_wrap_all_full(Compose *compose, gboolean force)
4049 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4050 GtkTextBuffer *buffer;
4053 buffer = gtk_text_view_get_buffer(text);
4055 gtk_text_buffer_get_start_iter(buffer, &iter);
4056 while (!gtk_text_iter_is_end(&iter))
4057 compose_beautify_paragraph(compose, &iter, force);
4061 static void compose_set_title(Compose *compose)
4067 edited = compose->modified ? _(" [Edited]") : "";
4069 subject = gtk_editable_get_chars(
4070 GTK_EDITABLE(compose->subject_entry), 0, -1);
4073 if (subject && strlen(subject))
4074 str = g_strdup_printf(_("%s - Compose message%s"),
4077 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4079 str = g_strdup(_("Compose message"));
4082 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4088 * compose_current_mail_account:
4090 * Find a current mail account (the currently selected account, or the
4091 * default account, if a news account is currently selected). If a
4092 * mail account cannot be found, display an error message.
4094 * Return value: Mail account, or NULL if not found.
4096 static PrefsAccount *
4097 compose_current_mail_account(void)
4101 if (cur_account && cur_account->protocol != A_NNTP)
4104 ac = account_get_default();
4105 if (!ac || ac->protocol == A_NNTP) {
4106 alertpanel_error(_("Account for sending mail is not specified.\n"
4107 "Please select a mail account before sending."));
4114 #define QUOTE_IF_REQUIRED(out, str) \
4116 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4120 len = strlen(str) + 3; \
4121 if ((__tmp = alloca(len)) == NULL) { \
4122 g_warning("can't allocate memory\n"); \
4123 g_string_free(header, TRUE); \
4126 g_snprintf(__tmp, len, "\"%s\"", str); \
4131 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4132 g_warning("can't allocate memory\n"); \
4133 g_string_free(header, TRUE); \
4136 strcpy(__tmp, str); \
4142 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4144 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4148 len = strlen(str) + 3; \
4149 if ((__tmp = alloca(len)) == NULL) { \
4150 g_warning("can't allocate memory\n"); \
4153 g_snprintf(__tmp, len, "\"%s\"", str); \
4158 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4159 g_warning("can't allocate memory\n"); \
4162 strcpy(__tmp, str); \
4168 static void compose_select_account(Compose *compose, PrefsAccount *account,
4171 GtkItemFactory *ifactory;
4174 g_return_if_fail(account != NULL);
4176 compose->account = account;
4178 if (account->name && *account->name) {
4180 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4181 from = g_strdup_printf("%s <%s>",
4182 buf, account->address);
4183 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4185 from = g_strdup_printf("<%s>",
4187 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4192 compose_set_title(compose);
4194 ifactory = gtk_item_factory_from_widget(compose->menubar);
4196 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4197 menu_set_active(ifactory, "/Options/Sign", TRUE);
4199 menu_set_active(ifactory, "/Options/Sign", FALSE);
4200 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4201 menu_set_active(ifactory, "/Options/Encrypt", TRUE);
4203 menu_set_active(ifactory, "/Options/Encrypt", FALSE);
4205 activate_privacy_system(compose, account, FALSE);
4207 if (!init && compose->mode != COMPOSE_REDIRECT) {
4208 undo_block(compose->undostruct);
4209 compose_insert_sig(compose, TRUE);
4210 undo_unblock(compose->undostruct);
4214 /* use account's dict info if set */
4215 if (compose->gtkaspell) {
4216 if (account->enable_default_dictionary)
4217 gtkaspell_change_dict(compose->gtkaspell,
4218 account->default_dictionary, FALSE);
4219 if (account->enable_default_alt_dictionary)
4220 gtkaspell_change_alt_dict(compose->gtkaspell,
4221 account->default_alt_dictionary);
4222 if (account->enable_default_dictionary
4223 || account->enable_default_alt_dictionary)
4224 compose_spell_menu_changed(compose);
4229 gboolean compose_check_for_valid_recipient(Compose *compose) {
4230 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
4231 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
4232 gboolean recipient_found = FALSE;
4236 /* free to and newsgroup list */
4237 slist_free_strings(compose->to_list);
4238 g_slist_free(compose->to_list);
4239 compose->to_list = NULL;
4241 slist_free_strings(compose->newsgroup_list);
4242 g_slist_free(compose->newsgroup_list);
4243 compose->newsgroup_list = NULL;
4245 /* search header entries for to and newsgroup entries */
4246 for (list = compose->header_list; list; list = list->next) {
4249 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_COMBO(((ComposeHeaderEntry *)list->data)->combo)->entry), 0, -1);
4250 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4253 if (entry[0] != '\0') {
4254 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
4255 if (!strcmp(header, prefs_common_translated_header_name(*strptr))) {
4256 compose->to_list = address_list_append(compose->to_list, entry);
4257 recipient_found = TRUE;
4260 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
4261 if (!strcmp(header, prefs_common_translated_header_name(*strptr))) {
4262 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
4263 recipient_found = TRUE;
4270 return recipient_found;
4273 static gboolean compose_check_for_set_recipients(Compose *compose)
4275 if (compose->account->set_autocc && compose->account->auto_cc) {
4276 gboolean found_other = FALSE;
4278 /* search header entries for to and newsgroup entries */
4279 for (list = compose->header_list; list; list = list->next) {
4282 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4283 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_COMBO(((ComposeHeaderEntry *)list->data)->combo)->entry), 0, -1);
4285 if (strcmp(entry, compose->account->auto_cc)
4286 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
4296 if (compose->batch) {
4297 gtk_widget_show_all(compose->window);
4299 aval = alertpanel(_("Send"),
4300 _("The only recipient is the default CC address. Send anyway?"),
4301 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4302 if (aval != G_ALERTALTERNATE)
4306 if (compose->account->set_autobcc && compose->account->auto_bcc) {
4307 gboolean found_other = FALSE;
4309 /* search header entries for to and newsgroup entries */
4310 for (list = compose->header_list; list; list = list->next) {
4313 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4314 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_COMBO(((ComposeHeaderEntry *)list->data)->combo)->entry), 0, -1);
4316 if (strcmp(entry, compose->account->auto_bcc)
4317 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
4327 if (compose->batch) {
4328 gtk_widget_show_all(compose->window);
4330 aval = alertpanel(_("Send"),
4331 _("The only recipient is the default BCC address. Send anyway?"),
4332 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4333 if (aval != G_ALERTALTERNATE)
4340 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
4344 if (compose_check_for_valid_recipient(compose) == FALSE) {
4345 if (compose->batch) {
4346 gtk_widget_show_all(compose->window);
4348 alertpanel_error(_("Recipient is not specified."));
4352 if (compose_check_for_set_recipients(compose) == FALSE) {
4356 if (!compose->batch) {
4357 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
4358 if (*str == '\0' && check_everything == TRUE &&
4359 compose->mode != COMPOSE_REDIRECT) {
4362 aval = alertpanel(_("Send"),
4363 _("Subject is empty. Send it anyway?"),
4364 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4365 if (aval != G_ALERTALTERNATE)
4370 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
4376 gint compose_send(Compose *compose)
4379 FolderItem *folder = NULL;
4381 gchar *msgpath = NULL;
4382 gboolean discard_window = FALSE;
4383 gchar *errstr = NULL;
4384 gchar *tmsgid = NULL;
4385 MainWindow *mainwin = mainwindow_get_mainwindow();
4386 gboolean queued_removed = FALSE;
4388 if (prefs_common.send_dialog_mode != SEND_DIALOG_ALWAYS
4389 || compose->batch == TRUE)
4390 discard_window = TRUE;
4392 compose_allow_user_actions (compose, FALSE);
4393 compose->sending = TRUE;
4395 if (compose_check_entries(compose, TRUE) == FALSE) {
4396 if (compose->batch) {
4397 gtk_widget_show_all(compose->window);
4403 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
4406 if (compose->batch) {
4407 gtk_widget_show_all(compose->window);
4410 alertpanel_error(_("Could not queue message for sending:\n\n"
4411 "Charset conversion failed."));
4412 } else if (val == -5) {
4413 alertpanel_error(_("Could not queue message for sending:\n\n"
4414 "Couldn't get recipient encryption key."));
4415 } else if (val == -6) {
4417 } else if (val == -3) {
4418 if (privacy_peek_error())
4419 alertpanel_error(_("Could not queue message for sending:\n\n"
4420 "Signature failed: %s"), privacy_get_error());
4421 } else if (val == -2 && errno != 0) {
4422 alertpanel_error(_("Could not queue message for sending:\n\n%s."), strerror(errno));
4424 alertpanel_error(_("Could not queue message for sending."));
4429 tmsgid = g_strdup(compose->msgid);
4430 if (discard_window) {
4431 compose->sending = FALSE;
4432 compose_close(compose);
4433 /* No more compose access in the normal codepath
4434 * after this point! */
4439 alertpanel_error(_("The message was queued but could not be "
4440 "sent.\nUse \"Send queued messages\" from "
4441 "the main window to retry."));
4442 if (!discard_window) {
4449 if (msgpath == NULL) {
4450 msgpath = folder_item_fetch_msg(folder, msgnum);
4451 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
4454 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
4458 if (!discard_window) {
4460 if (!queued_removed)
4461 folder_item_remove_msg(folder, msgnum);
4462 folder_item_scan(folder);
4464 /* make sure we delete that */
4465 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
4467 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
4468 folder_item_remove_msg(folder, tmp->msgnum);
4469 procmsg_msginfo_free(tmp);
4476 if (!queued_removed)
4477 folder_item_remove_msg(folder, msgnum);
4478 folder_item_scan(folder);
4480 /* make sure we delete that */
4481 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
4483 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
4484 folder_item_remove_msg(folder, tmp->msgnum);
4485 procmsg_msginfo_free(tmp);
4488 if (!discard_window) {
4489 compose->sending = FALSE;
4490 compose_allow_user_actions (compose, TRUE);
4491 compose_close(compose);
4495 gchar *tmp = g_strdup_printf(_("%s\nUse \"Send queued messages\" from "
4496 "the main window to retry."), errstr);
4498 alertpanel_error_log(tmp);
4501 alertpanel_error_log(_("The message was queued but could not be "
4502 "sent.\nUse \"Send queued messages\" from "
4503 "the main window to retry."));
4505 if (!discard_window) {
4514 toolbar_main_set_sensitive(mainwin);
4515 main_window_set_menu_sensitive(mainwin);
4521 compose_allow_user_actions (compose, TRUE);
4522 compose->sending = FALSE;
4523 compose->modified = TRUE;
4524 toolbar_main_set_sensitive(mainwin);
4525 main_window_set_menu_sensitive(mainwin);
4530 static gboolean compose_use_attach(Compose *compose)
4532 GtkTreeModel *model = gtk_tree_view_get_model
4533 (GTK_TREE_VIEW(compose->attach_clist));
4534 return gtk_tree_model_iter_n_children(model, NULL) > 0;
4537 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
4540 gchar buf[BUFFSIZE];
4542 gboolean first_to_address;
4543 gboolean first_cc_address;
4545 ComposeHeaderEntry *headerentry;
4546 const gchar *headerentryname;
4547 const gchar *cc_hdr;
4548 const gchar *to_hdr;
4550 debug_print("Writing redirect header\n");
4552 cc_hdr = prefs_common_translated_header_name("Cc:");
4553 to_hdr = prefs_common_translated_header_name("To:");
4555 first_to_address = TRUE;
4556 for (list = compose->header_list; list; list = list->next) {
4557 headerentry = ((ComposeHeaderEntry *)list->data);
4558 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(headerentry->combo)->entry));
4560 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
4561 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
4562 Xstrdup_a(str, entstr, return -1);
4564 if (str[0] != '\0') {
4565 compose_convert_header
4566 (compose, buf, sizeof(buf), str,
4567 strlen("Resent-To") + 2, TRUE);
4569 if (first_to_address) {
4570 fprintf(fp, "Resent-To: ");
4571 first_to_address = FALSE;
4575 fprintf(fp, "%s", buf);
4579 if (!first_to_address) {
4583 first_cc_address = TRUE;
4584 for (list = compose->header_list; list; list = list->next) {
4585 headerentry = ((ComposeHeaderEntry *)list->data);
4586 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(headerentry->combo)->entry));
4588 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
4589 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
4590 Xstrdup_a(str, strg, return -1);
4592 if (str[0] != '\0') {
4593 compose_convert_header
4594 (compose, buf, sizeof(buf), str,
4595 strlen("Resent-Cc") + 2, TRUE);
4597 if (first_cc_address) {
4598 fprintf(fp, "Resent-Cc: ");
4599 first_cc_address = FALSE;
4603 fprintf(fp, "%s", buf);
4607 if (!first_cc_address) {
4614 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
4616 gchar buf[BUFFSIZE];
4618 const gchar *entstr;
4619 /* struct utsname utsbuf; */
4621 g_return_val_if_fail(fp != NULL, -1);
4622 g_return_val_if_fail(compose->account != NULL, -1);
4623 g_return_val_if_fail(compose->account->address != NULL, -1);
4626 get_rfc822_date(buf, sizeof(buf));
4627 fprintf(fp, "Resent-Date: %s\n", buf);
4630 if (compose->account->name && *compose->account->name) {
4631 compose_convert_header
4632 (compose, buf, sizeof(buf), compose->account->name,
4633 strlen("From: "), TRUE);
4634 fprintf(fp, "Resent-From: %s <%s>\n",
4635 buf, compose->account->address);
4637 fprintf(fp, "Resent-From: %s\n", compose->account->address);
4640 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
4641 if (*entstr != '\0') {
4642 Xstrdup_a(str, entstr, return -1);
4645 compose_convert_header(compose, buf, sizeof(buf), str,
4646 strlen("Subject: "), FALSE);
4647 fprintf(fp, "Subject: %s\n", buf);
4651 /* Resent-Message-ID */
4652 if (compose->account->gen_msgid) {
4653 generate_msgid(buf, sizeof(buf));
4654 fprintf(fp, "Resent-Message-ID: <%s>\n", buf);
4655 compose->msgid = g_strdup(buf);
4658 compose_redirect_write_headers_from_headerlist(compose, fp);
4660 /* separator between header and body */
4666 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
4670 gchar buf[BUFFSIZE];
4672 gboolean skip = FALSE;
4673 gchar *not_included[]={
4674 "Return-Path:", "Delivered-To:", "Received:",
4675 "Subject:", "X-UIDL:", "AF:",
4676 "NF:", "PS:", "SRH:",
4677 "SFN:", "DSR:", "MID:",
4678 "CFG:", "PT:", "S:",
4679 "RQ:", "SSV:", "NSV:",
4680 "SSH:", "R:", "MAID:",
4681 "NAID:", "RMID:", "FMID:",
4682 "SCF:", "RRCPT:", "NG:",
4683 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
4684 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
4685 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
4686 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
4689 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
4690 FILE_OP_ERROR(compose->redirect_filename, "fopen");
4694 while (procheader_get_one_field_asis(buf, sizeof(buf), fp) != -1) {
4696 for (i = 0; not_included[i] != NULL; i++) {
4697 if (g_ascii_strncasecmp(buf, not_included[i],
4698 strlen(not_included[i])) == 0) {
4705 if (fputs(buf, fdest) == -1)
4708 if (!prefs_common.redirect_keep_from) {
4709 if (g_ascii_strncasecmp(buf, "From:",
4710 strlen("From:")) == 0) {
4711 fputs(" (by way of ", fdest);
4712 if (compose->account->name
4713 && *compose->account->name) {
4714 compose_convert_header
4715 (compose, buf, sizeof(buf),
4716 compose->account->name,
4719 fprintf(fdest, "%s <%s>",
4721 compose->account->address);
4723 fprintf(fdest, "%s",
4724 compose->account->address);
4729 if (fputs("\n", fdest) == -1)
4733 compose_redirect_write_headers(compose, fdest);
4735 while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
4736 if (fwrite(buf, sizeof(gchar), len, fdest) != len)
4749 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
4751 GtkTextBuffer *buffer;
4752 GtkTextIter start, end;
4755 const gchar *out_codeset;
4756 EncodingType encoding;
4757 MimeInfo *mimemsg, *mimetext;
4760 if (action == COMPOSE_WRITE_FOR_SEND)
4761 attach_parts = TRUE;
4763 /* create message MimeInfo */
4764 mimemsg = procmime_mimeinfo_new();
4765 mimemsg->type = MIMETYPE_MESSAGE;
4766 mimemsg->subtype = g_strdup("rfc822");
4767 mimemsg->content = MIMECONTENT_MEM;
4768 mimemsg->tmp = TRUE; /* must free content later */
4769 mimemsg->data.mem = compose_get_header(compose);
4771 /* Create text part MimeInfo */
4772 /* get all composed text */
4773 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
4774 gtk_text_buffer_get_start_iter(buffer, &start);
4775 gtk_text_buffer_get_end_iter(buffer, &end);
4776 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
4777 if (is_ascii_str(chars)) {
4780 out_codeset = CS_US_ASCII;
4781 encoding = ENC_7BIT;
4783 const gchar *src_codeset = CS_INTERNAL;
4785 out_codeset = conv_get_charset_str(compose->out_encoding);
4788 gchar *test_conv_global_out = NULL;
4789 gchar *test_conv_reply = NULL;
4791 /* automatic mode. be automatic. */
4792 codeconv_set_strict(TRUE);
4794 out_codeset = conv_get_outgoing_charset_str();
4796 debug_print("trying to convert to %s\n", out_codeset);
4797 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
4800 if (!test_conv_global_out && compose->orig_charset
4801 && strcmp(compose->orig_charset, CS_US_ASCII)) {
4802 out_codeset = compose->orig_charset;
4803 debug_print("failure; trying to convert to %s\n", out_codeset);
4804 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
4807 if (!test_conv_global_out && !test_conv_reply) {
4809 out_codeset = CS_INTERNAL;
4810 debug_print("failure; finally using %s\n", out_codeset);
4812 g_free(test_conv_global_out);
4813 g_free(test_conv_reply);
4814 codeconv_set_strict(FALSE);
4817 if (!g_ascii_strcasecmp(out_codeset, CS_US_ASCII))
4818 out_codeset = CS_ISO_8859_1;
4820 if (prefs_common.encoding_method == CTE_BASE64)
4821 encoding = ENC_BASE64;
4822 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
4823 encoding = ENC_QUOTED_PRINTABLE;
4824 else if (prefs_common.encoding_method == CTE_8BIT)
4825 encoding = ENC_8BIT;
4827 encoding = procmime_get_encoding_for_charset(out_codeset);
4829 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
4830 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
4832 if (action == COMPOSE_WRITE_FOR_SEND) {
4833 codeconv_set_strict(TRUE);
4834 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
4835 codeconv_set_strict(FALSE);
4841 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
4842 "to the specified %s charset.\n"
4843 "Send it as %s?"), out_codeset, src_codeset);
4844 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL, _("+_Send"), NULL, FALSE,
4845 NULL, ALERT_ERROR, G_ALERTDEFAULT);
4848 if (aval != G_ALERTALTERNATE) {
4853 out_codeset = src_codeset;
4859 out_codeset = src_codeset;
4865 if (encoding == ENC_8BIT || encoding == ENC_7BIT) {
4866 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
4867 strstr(buf, "\nFrom ") != NULL) {
4868 encoding = ENC_QUOTED_PRINTABLE;
4872 mimetext = procmime_mimeinfo_new();
4873 mimetext->content = MIMECONTENT_MEM;
4874 mimetext->tmp = TRUE; /* must free content later */
4875 /* dup'ed because procmime_encode_content can turn it into a tmpfile
4876 * and free the data, which we need later. */
4877 mimetext->data.mem = g_strdup(buf);
4878 mimetext->type = MIMETYPE_TEXT;
4879 mimetext->subtype = g_strdup("plain");
4880 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
4881 g_strdup(out_codeset));
4883 /* protect trailing spaces when signing message */
4884 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
4885 privacy_system_can_sign(compose->privacy_system)) {
4886 encoding = ENC_QUOTED_PRINTABLE;
4889 debug_print("main text: %d bytes encoded as %s in %d\n",
4890 strlen(buf), out_codeset, encoding);
4892 /* check for line length limit */
4893 if (action == COMPOSE_WRITE_FOR_SEND &&
4894 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
4895 check_line_length(buf, 1000, &line) < 0) {
4899 msg = g_strdup_printf
4900 (_("Line %d exceeds the line length limit (998 bytes).\n"
4901 "The contents of the message might be broken on the way to the delivery.\n"
4903 "Send it anyway?"), line + 1);
4904 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
4906 if (aval != G_ALERTALTERNATE) {
4912 if (encoding != ENC_UNKNOWN)
4913 procmime_encode_content(mimetext, encoding);
4915 /* append attachment parts */
4916 if (compose_use_attach(compose) && attach_parts) {
4917 MimeInfo *mimempart;
4918 gchar *boundary = NULL;
4919 mimempart = procmime_mimeinfo_new();
4920 mimempart->content = MIMECONTENT_EMPTY;
4921 mimempart->type = MIMETYPE_MULTIPART;
4922 mimempart->subtype = g_strdup("mixed");
4926 boundary = generate_mime_boundary(NULL);
4927 } while (strstr(buf, boundary) != NULL);
4929 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
4932 mimetext->disposition = DISPOSITIONTYPE_INLINE;
4934 g_node_append(mimempart->node, mimetext->node);
4935 g_node_append(mimemsg->node, mimempart->node);
4937 compose_add_attachments(compose, mimempart);
4939 g_node_append(mimemsg->node, mimetext->node);
4943 /* sign message if sending */
4944 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
4945 privacy_system_can_sign(compose->privacy_system))
4946 if (!privacy_sign(compose->privacy_system, mimemsg, compose->account))
4949 procmime_write_mimeinfo(mimemsg, fp);
4951 procmime_mimeinfo_free_all(mimemsg);
4956 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
4958 GtkTextBuffer *buffer;
4959 GtkTextIter start, end;
4964 if ((fp = g_fopen(file, "wb")) == NULL) {
4965 FILE_OP_ERROR(file, "fopen");
4969 /* chmod for security */
4970 if (change_file_mode_rw(fp, file) < 0) {
4971 FILE_OP_ERROR(file, "chmod");
4972 g_warning("can't change file mode\n");
4975 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
4976 gtk_text_buffer_get_start_iter(buffer, &start);
4977 gtk_text_buffer_get_end_iter(buffer, &end);
4978 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
4980 chars = conv_codeset_strdup
4981 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
4984 if (!chars) return -1;
4987 len = strlen(chars);
4988 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
4989 FILE_OP_ERROR(file, "fwrite");
4998 if (fclose(fp) == EOF) {
4999 FILE_OP_ERROR(file, "fclose");
5006 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5009 MsgInfo *msginfo = compose->targetinfo;
5011 g_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5012 if (!msginfo) return -1;
5014 if (!force && MSG_IS_LOCKED(msginfo->flags))
5017 item = msginfo->folder;
5018 g_return_val_if_fail(item != NULL, -1);
5020 if (procmsg_msg_exist(msginfo) &&
5021 (folder_has_parent_of_type(item, F_QUEUE) ||
5022 folder_has_parent_of_type(item, F_DRAFT)
5023 || msginfo == compose->autosaved_draft)) {
5024 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5025 g_warning("can't remove the old message\n");
5033 static void compose_remove_draft(Compose *compose)
5036 MsgInfo *msginfo = compose->targetinfo;
5037 drafts = account_get_special_folder(compose->account, F_DRAFT);
5039 if (procmsg_msg_exist(msginfo)) {
5040 folder_item_remove_msg(drafts, msginfo->msgnum);
5045 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5046 gboolean remove_reedit_target)
5048 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5051 static gboolean compose_warn_encryption(Compose *compose)
5053 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5054 AlertValue val = G_ALERTALTERNATE;
5056 if (warning == NULL)
5059 val = alertpanel_full(_("Encryption warning"), warning,
5060 GTK_STOCK_CANCEL, _("+C_ontinue"), NULL,
5061 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5062 if (val & G_ALERTDISABLE) {
5063 val &= ~G_ALERTDISABLE;
5064 if (val == G_ALERTALTERNATE)
5065 privacy_inhibit_encrypt_warning(compose->privacy_system,
5069 if (val == G_ALERTALTERNATE) {
5076 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
5077 gchar **msgpath, gboolean check_subject,
5078 gboolean remove_reedit_target)
5085 static gboolean lock = FALSE;
5086 PrefsAccount *mailac = NULL, *newsac = NULL;
5088 debug_print("queueing message...\n");
5089 g_return_val_if_fail(compose->account != NULL, -1);
5093 if (compose_check_entries(compose, check_subject) == FALSE) {
5095 if (compose->batch) {
5096 gtk_widget_show_all(compose->window);
5101 if (!compose->to_list && !compose->newsgroup_list) {
5102 g_warning("can't get recipient list.");
5107 if (compose->to_list) {
5108 if (compose->account->protocol != A_NNTP)
5109 mailac = compose->account;
5110 else if (cur_account && cur_account->protocol != A_NNTP)
5111 mailac = cur_account;
5112 else if (!(mailac = compose_current_mail_account())) {
5114 alertpanel_error(_("No account for sending mails available!"));
5119 if (compose->newsgroup_list) {
5120 if (compose->account->protocol == A_NNTP)
5121 newsac = compose->account;
5122 else if (!newsac->protocol != A_NNTP) {
5124 alertpanel_error(_("No account for posting news available!"));
5129 /* write queue header */
5130 tmp = g_strdup_printf("%s%cqueue.%p", get_tmp_dir(),
5131 G_DIR_SEPARATOR, compose);
5132 if ((fp = g_fopen(tmp, "wb")) == NULL) {
5133 FILE_OP_ERROR(tmp, "fopen");
5139 if (change_file_mode_rw(fp, tmp) < 0) {
5140 FILE_OP_ERROR(tmp, "chmod");
5141 g_warning("can't change file mode\n");
5144 /* queueing variables */
5145 fprintf(fp, "AF:\n");
5146 fprintf(fp, "NF:0\n");
5147 fprintf(fp, "PS:10\n");
5148 fprintf(fp, "SRH:1\n");
5149 fprintf(fp, "SFN:\n");
5150 fprintf(fp, "DSR:\n");
5152 fprintf(fp, "MID:<%s>\n", compose->msgid);
5154 fprintf(fp, "MID:\n");
5155 fprintf(fp, "CFG:\n");
5156 fprintf(fp, "PT:0\n");
5157 fprintf(fp, "S:%s\n", compose->account->address);
5158 fprintf(fp, "RQ:\n");
5160 fprintf(fp, "SSV:%s\n", mailac->smtp_server);
5162 fprintf(fp, "SSV:\n");
5164 fprintf(fp, "NSV:%s\n", newsac->nntp_server);
5166 fprintf(fp, "NSV:\n");
5167 fprintf(fp, "SSH:\n");
5168 /* write recepient list */
5169 if (compose->to_list) {
5170 fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data);
5171 for (cur = compose->to_list->next; cur != NULL;
5173 fprintf(fp, ",<%s>", (gchar *)cur->data);
5176 /* write newsgroup list */
5177 if (compose->newsgroup_list) {
5179 fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data);
5180 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
5181 fprintf(fp, ",%s", (gchar *)cur->data);
5184 /* Sylpheed account IDs */
5186 fprintf(fp, "MAID:%d\n", mailac->account_id);
5188 fprintf(fp, "NAID:%d\n", newsac->account_id);
5191 if (compose->privacy_system != NULL) {
5192 fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system);
5193 fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing);
5194 if (compose->use_encryption) {
5196 if (!compose_warn_encryption(compose)) {
5203 if (mailac && mailac->encrypt_to_self) {
5204 GSList *tmp_list = g_slist_copy(compose->to_list);
5205 tmp_list = g_slist_append(tmp_list, compose->account->address);
5206 encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
5207 g_slist_free(tmp_list);
5209 encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
5211 if (encdata != NULL) {
5212 if (strcmp(encdata, "_DONT_ENCRYPT_")) {
5213 fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption);
5214 fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
5216 } /* else we finally dont want to encrypt */
5218 fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption);
5219 /* and if encdata was null, it means there's been a problem in
5231 /* Save copy folder */
5232 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
5233 gchar *savefolderid;
5235 savefolderid = gtk_editable_get_chars(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
5236 fprintf(fp, "SCF:%s\n", savefolderid);
5237 g_free(savefolderid);
5239 /* Save copy folder */
5240 if (compose->return_receipt) {
5241 fprintf(fp, "RRCPT:1\n");
5243 /* Message-ID of message replying to */
5244 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
5247 folderid = folder_item_get_identifier(compose->replyinfo->folder);
5248 fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid);
5251 /* Message-ID of message forwarding to */
5252 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
5255 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
5256 fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid);
5260 /* end of headers */
5261 fprintf(fp, "X-Claws-End-Special-Headers: 1\n");
5263 if (compose->redirect_filename != NULL) {
5264 if (compose_redirect_write_to_file(compose, fp) < 0) {
5273 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
5278 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
5282 if (fclose(fp) == EOF) {
5283 FILE_OP_ERROR(tmp, "fclose");
5290 if (item && *item) {
5293 queue = account_get_special_folder(compose->account, F_QUEUE);
5296 g_warning("can't find queue folder\n");
5302 folder_item_scan(queue);
5303 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
5304 g_warning("can't queue the message\n");
5311 if (msgpath == NULL) {
5317 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
5318 compose_remove_reedit_target(compose, FALSE);
5321 if ((msgnum != NULL) && (item != NULL)) {
5329 static void compose_add_attachments(Compose *compose, MimeInfo *parent)
5332 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
5334 struct stat statbuf;
5335 gchar *type, *subtype;
5336 GtkTreeModel *model;
5339 model = gtk_tree_view_get_model(tree_view);
5341 if (!gtk_tree_model_get_iter_first(model, &iter))
5344 gtk_tree_model_get(model, &iter,
5348 mimepart = procmime_mimeinfo_new();
5349 mimepart->content = MIMECONTENT_FILE;
5350 mimepart->data.filename = g_strdup(ainfo->file);
5351 mimepart->tmp = FALSE; /* or we destroy our attachment */
5352 mimepart->offset = 0;
5354 stat(ainfo->file, &statbuf);
5355 mimepart->length = statbuf.st_size;
5357 type = g_strdup(ainfo->content_type);
5359 if (!strchr(type, '/')) {
5361 type = g_strdup("application/octet-stream");
5364 subtype = strchr(type, '/') + 1;
5365 *(subtype - 1) = '\0';
5366 mimepart->type = procmime_get_media_type(type);
5367 mimepart->subtype = g_strdup(subtype);
5370 if (mimepart->type == MIMETYPE_MESSAGE &&
5371 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
5372 mimepart->disposition = DISPOSITIONTYPE_INLINE;
5375 g_hash_table_insert(mimepart->typeparameters,
5376 g_strdup("name"), g_strdup(ainfo->name));
5377 g_hash_table_insert(mimepart->dispositionparameters,
5378 g_strdup("filename"), g_strdup(ainfo->name));
5379 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
5383 if (compose->use_signing) {
5384 if (ainfo->encoding == ENC_7BIT)
5385 ainfo->encoding = ENC_QUOTED_PRINTABLE;
5386 else if (ainfo->encoding == ENC_8BIT)
5387 ainfo->encoding = ENC_BASE64;
5390 procmime_encode_content(mimepart, ainfo->encoding);
5392 g_node_append(parent->node, mimepart->node);
5393 } while (gtk_tree_model_iter_next(model, &iter));
5396 #define IS_IN_CUSTOM_HEADER(header) \
5397 (compose->account->add_customhdr && \
5398 custom_header_find(compose->account->customhdr_list, header) != NULL)
5400 static void compose_add_headerfield_from_headerlist(Compose *compose,
5402 const gchar *fieldname,
5403 const gchar *seperator)
5405 gchar *str, *fieldname_w_colon;
5406 gboolean add_field = FALSE;
5408 ComposeHeaderEntry *headerentry;
5409 const gchar *headerentryname;
5410 const gchar *trans_fieldname;
5413 if (IS_IN_CUSTOM_HEADER(fieldname))
5416 debug_print("Adding %s-fields\n", fieldname);
5418 fieldstr = g_string_sized_new(64);
5420 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
5421 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
5423 for (list = compose->header_list; list; list = list->next) {
5424 headerentry = ((ComposeHeaderEntry *)list->data);
5425 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(headerentry->combo)->entry));
5427 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
5428 str = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
5430 if (str[0] != '\0') {
5432 g_string_append(fieldstr, seperator);
5433 g_string_append(fieldstr, str);
5442 buf = g_new0(gchar, fieldstr->len * 4 + 256);
5443 compose_convert_header
5444 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
5445 strlen(fieldname) + 2, TRUE);
5446 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
5450 g_free(fieldname_w_colon);
5451 g_string_free(fieldstr, TRUE);
5456 static gchar *compose_get_header(Compose *compose)
5458 gchar buf[BUFFSIZE];
5459 const gchar *entry_str;
5463 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
5465 gchar *from_name = NULL, *from_address = NULL;
5468 g_return_val_if_fail(compose->account != NULL, NULL);
5469 g_return_val_if_fail(compose->account->address != NULL, NULL);
5471 header = g_string_sized_new(64);
5474 get_rfc822_date(buf, sizeof(buf));
5475 g_string_append_printf(header, "Date: %s\n", buf);
5479 if (compose->account->name && *compose->account->name) {
5481 QUOTE_IF_REQUIRED(buf, compose->account->name);
5482 tmp = g_strdup_printf("%s <%s>",
5483 buf, compose->account->address);
5485 tmp = g_strdup_printf("%s",
5486 compose->account->address);
5488 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
5489 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
5491 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
5492 from_address = g_strdup(compose->account->address);
5494 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5495 /* extract name and address */
5496 if (strstr(spec, " <") && strstr(spec, ">")) {
5497 from_address = g_strdup(strrchr(spec, '<')+1);
5498 *(strrchr(from_address, '>')) = '\0';
5499 from_name = g_strdup(spec);
5500 *(strrchr(from_name, '<')) = '\0';
5503 from_address = g_strdup(spec);
5510 if (from_name && *from_name) {
5511 compose_convert_header
5512 (compose, buf, sizeof(buf), from_name,
5513 strlen("From: "), TRUE);
5514 QUOTE_IF_REQUIRED(name, buf);
5516 g_string_append_printf(header, "From: %s <%s>\n",
5517 name, from_address);
5519 g_string_append_printf(header, "From: %s\n", from_address);
5522 g_free(from_address);
5525 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
5528 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
5531 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
5534 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
5537 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
5539 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
5542 compose_convert_header(compose, buf, sizeof(buf), str,
5543 strlen("Subject: "), FALSE);
5544 g_string_append_printf(header, "Subject: %s\n", buf);
5550 if (compose->account->gen_msgid) {
5551 generate_msgid(buf, sizeof(buf));
5552 g_string_append_printf(header, "Message-ID: <%s>\n", buf);
5553 compose->msgid = g_strdup(buf);
5556 if (compose->remove_references == FALSE) {
5558 if (compose->inreplyto && compose->to_list)
5559 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
5562 if (compose->references)
5563 g_string_append_printf(header, "References: %s\n", compose->references);
5567 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
5570 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
5573 if (compose->account->organization &&
5574 strlen(compose->account->organization) &&
5575 !IS_IN_CUSTOM_HEADER("Organization")) {
5576 compose_convert_header(compose, buf, sizeof(buf),
5577 compose->account->organization,
5578 strlen("Organization: "), FALSE);
5579 g_string_append_printf(header, "Organization: %s\n", buf);
5582 /* Program version and system info */
5583 if (g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
5584 !compose->newsgroup_list) {
5585 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
5587 gtk_major_version, gtk_minor_version, gtk_micro_version,
5590 if (g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
5591 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
5593 gtk_major_version, gtk_minor_version, gtk_micro_version,
5597 /* custom headers */
5598 if (compose->account->add_customhdr) {
5601 for (cur = compose->account->customhdr_list; cur != NULL;
5603 CustomHeader *chdr = (CustomHeader *)cur->data;
5605 if (custom_header_is_allowed(chdr->name)) {
5606 compose_convert_header
5607 (compose, buf, sizeof(buf),
5608 chdr->value ? chdr->value : "",
5609 strlen(chdr->name) + 2, FALSE);
5610 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
5616 switch (compose->priority) {
5617 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
5618 "X-Priority: 1 (Highest)\n");
5620 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
5621 "X-Priority: 2 (High)\n");
5623 case PRIORITY_NORMAL: break;
5624 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
5625 "X-Priority: 4 (Low)\n");
5627 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
5628 "X-Priority: 5 (Lowest)\n");
5630 default: debug_print("compose: priority unknown : %d\n",
5634 /* Request Return Receipt */
5635 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To")) {
5636 if (compose->return_receipt) {
5637 if (compose->account->name
5638 && *compose->account->name) {
5639 compose_convert_header(compose, buf, sizeof(buf),
5640 compose->account->name,
5641 strlen("Disposition-Notification-To: "),
5643 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, compose->account->address);
5645 g_string_append_printf(header, "Disposition-Notification-To: %s\n", compose->account->address);
5649 /* get special headers */
5650 for (list = compose->header_list; list; list = list->next) {
5651 ComposeHeaderEntry *headerentry;
5654 gchar *headername_wcolon;
5655 const gchar *headername_trans;
5658 gboolean standard_header = FALSE;
5660 headerentry = ((ComposeHeaderEntry *)list->data);
5662 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(headerentry->combo)->entry)));
5663 if (strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
5668 if (!strstr(tmp, ":")) {
5669 headername_wcolon = g_strconcat(tmp, ":", NULL);
5670 headername = g_strdup(tmp);
5672 headername_wcolon = g_strdup(tmp);
5673 headername = g_strdup(strtok(tmp, ":"));
5677 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5678 Xstrdup_a(headervalue, entry_str, return NULL);
5679 subst_char(headervalue, '\r', ' ');
5680 subst_char(headervalue, '\n', ' ');
5681 string = std_headers;
5682 while (*string != NULL) {
5683 headername_trans = prefs_common_translated_header_name(*string);
5684 if (!strcmp(headername_trans, headername_wcolon))
5685 standard_header = TRUE;
5688 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
5689 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
5692 g_free(headername_wcolon);
5696 g_string_free(header, FALSE);
5701 #undef IS_IN_CUSTOM_HEADER
5703 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
5704 gint header_len, gboolean addr_field)
5706 gchar *tmpstr = NULL;
5707 const gchar *out_codeset = NULL;
5709 g_return_if_fail(src != NULL);
5710 g_return_if_fail(dest != NULL);
5712 if (len < 1) return;
5714 tmpstr = g_strdup(src);
5716 subst_char(tmpstr, '\n', ' ');
5717 subst_char(tmpstr, '\r', ' ');
5720 if (!g_utf8_validate(tmpstr, -1, NULL)) {
5721 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
5722 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
5727 codeconv_set_strict(TRUE);
5728 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
5729 conv_get_charset_str(compose->out_encoding));
5730 codeconv_set_strict(FALSE);
5732 if (!dest || *dest == '\0') {
5733 gchar *test_conv_global_out = NULL;
5734 gchar *test_conv_reply = NULL;
5736 /* automatic mode. be automatic. */
5737 codeconv_set_strict(TRUE);
5739 out_codeset = conv_get_outgoing_charset_str();
5741 debug_print("trying to convert to %s\n", out_codeset);
5742 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
5745 if (!test_conv_global_out && compose->orig_charset
5746 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5747 out_codeset = compose->orig_charset;
5748 debug_print("failure; trying to convert to %s\n", out_codeset);
5749 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
5752 if (!test_conv_global_out && !test_conv_reply) {
5754 out_codeset = CS_INTERNAL;
5755 debug_print("finally using %s\n", out_codeset);
5757 g_free(test_conv_global_out);
5758 g_free(test_conv_reply);
5759 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
5761 codeconv_set_strict(FALSE);
5766 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
5770 g_return_if_fail(user_data != NULL);
5772 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
5773 g_strstrip(address);
5774 if (*address != '\0') {
5775 gchar *name = procheader_get_fromname(address);
5776 extract_address(address);
5777 addressbook_add_contact(name, address, NULL);
5782 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
5784 GtkWidget *menuitem;
5787 g_return_if_fail(menu != NULL);
5788 g_return_if_fail(GTK_IS_MENU_SHELL(menu));
5790 menuitem = gtk_separator_menu_item_new();
5791 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
5792 gtk_widget_show(menuitem);
5794 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
5795 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
5797 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
5798 g_strstrip(address);
5799 if (*address == '\0') {
5800 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
5803 g_signal_connect(G_OBJECT(menuitem), "activate",
5804 G_CALLBACK(compose_add_to_addressbook_cb), entry);
5805 gtk_widget_show(menuitem);
5808 static void compose_create_header_entry(Compose *compose)
5810 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
5814 GList *combo_list = NULL;
5816 const gchar *header = NULL;
5817 ComposeHeaderEntry *headerentry;
5818 gboolean standard_header = FALSE;
5820 headerentry = g_new0(ComposeHeaderEntry, 1);
5823 combo = gtk_combo_new();
5825 while(*string != NULL) {
5826 combo_list = g_list_append(combo_list, (gchar*)prefs_common_translated_header_name(*string));
5829 gtk_combo_set_popdown_strings(GTK_COMBO(combo), combo_list);
5830 g_list_free(combo_list);
5831 gtk_editable_set_editable(GTK_EDITABLE(GTK_COMBO(combo)->entry), TRUE);
5832 g_signal_connect(G_OBJECT(GTK_COMBO(combo)->entry), "grab_focus",
5833 G_CALLBACK(compose_grab_focus_cb), compose);
5834 gtk_widget_show(combo);
5835 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
5836 compose->header_nextrow, compose->header_nextrow+1,
5837 GTK_SHRINK, GTK_FILL, 0, 0);
5838 if (compose->header_last) {
5839 const gchar *last_header_entry = gtk_entry_get_text(
5840 GTK_ENTRY(GTK_COMBO(compose->header_last->combo)->entry));
5842 while (*string != NULL) {
5843 if (!strcmp(*string, last_header_entry))
5844 standard_header = TRUE;
5847 if (standard_header)
5848 header = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(compose->header_last->combo)->entry));
5850 if (!compose->header_last || !standard_header) {
5851 switch(compose->account->protocol) {
5853 header = prefs_common_translated_header_name("Newsgroups:");
5856 header = prefs_common_translated_header_name("To:");
5861 gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(combo)->entry), header);
5863 g_signal_connect_after(G_OBJECT(GTK_COMBO(combo)->entry), "grab_focus",
5864 G_CALLBACK(compose_grab_focus_cb), compose);
5867 entry = gtk_entry_new();
5868 gtk_widget_show(entry);
5869 gtk_tooltips_set_tip(compose->tooltips, entry,
5870 _("Use <tab> to autocomplete from addressbook"), NULL);
5871 gtk_table_attach(GTK_TABLE(compose->header_table), entry, 1, 2,
5872 compose->header_nextrow, compose->header_nextrow+1,
5873 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
5875 g_signal_connect(G_OBJECT(entry), "key-press-event",
5876 G_CALLBACK(compose_headerentry_key_press_event_cb),
5878 g_signal_connect(G_OBJECT(entry), "changed",
5879 G_CALLBACK(compose_headerentry_changed_cb),
5881 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
5882 G_CALLBACK(compose_grab_focus_cb), compose);
5885 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
5886 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
5887 GDK_ACTION_COPY | GDK_ACTION_MOVE);
5888 g_signal_connect(G_OBJECT(entry), "drag_data_received",
5889 G_CALLBACK(compose_header_drag_received_cb),
5891 g_signal_connect(G_OBJECT(entry), "drag-drop",
5892 G_CALLBACK(compose_drag_drop),
5894 g_signal_connect(G_OBJECT(entry), "populate-popup",
5895 G_CALLBACK(compose_entry_popup_extend),
5898 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
5900 headerentry->compose = compose;
5901 headerentry->combo = combo;
5902 headerentry->entry = entry;
5903 headerentry->headernum = compose->header_nextrow;
5905 compose->header_nextrow++;
5906 compose->header_last = headerentry;
5907 compose->header_list =
5908 g_slist_append(compose->header_list,
5912 static void compose_add_header_entry(Compose *compose, const gchar *header, gchar *text)
5914 ComposeHeaderEntry *last_header;
5916 last_header = compose->header_last;
5918 gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(last_header->combo)->entry), header);
5919 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
5922 static void compose_remove_header_entries(Compose *compose)
5925 for (list = compose->header_list; list; list = list->next) {
5926 ComposeHeaderEntry *headerentry =
5927 (ComposeHeaderEntry *)list->data;
5928 gtk_widget_destroy(headerentry->combo);
5929 gtk_widget_destroy(headerentry->entry);
5930 g_free(headerentry);
5932 compose->header_last = NULL;
5933 g_slist_free(compose->header_list);
5934 compose->header_list = NULL;
5935 compose->header_nextrow = 1;
5936 compose_create_header_entry(compose);
5939 static GtkWidget *compose_create_header(Compose *compose)
5941 GtkWidget *from_optmenu_hbox;
5942 GtkWidget *header_scrolledwin;
5943 GtkWidget *header_table;
5947 /* header labels and entries */
5948 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
5949 gtk_widget_show(header_scrolledwin);
5950 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
5952 header_table = gtk_table_new(2, 2, FALSE);
5953 gtk_widget_show(header_table);
5954 gtk_container_set_border_width(GTK_CONTAINER(header_table), BORDER_WIDTH);
5955 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
5956 gtk_viewport_set_shadow_type(GTK_VIEWPORT(GTK_BIN(header_scrolledwin)->child), GTK_SHADOW_ETCHED_IN);
5959 /* option menu for selecting accounts */
5960 from_optmenu_hbox = compose_account_option_menu_create(compose);
5961 gtk_table_attach(GTK_TABLE(header_table), from_optmenu_hbox,
5962 0, 2, count, count + 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
5965 compose->header_table = header_table;
5966 compose->header_list = NULL;
5967 compose->header_nextrow = count;
5969 compose_create_header_entry(compose);
5971 compose->table = NULL;
5973 return header_scrolledwin ;
5976 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
5978 Compose *compose = (Compose *)data;
5979 GdkEventButton event;
5982 event.time = gtk_get_current_event_time();
5984 return attach_button_pressed(compose->attach_clist, &event, compose);
5987 static GtkWidget *compose_create_attach(Compose *compose)
5989 GtkWidget *attach_scrwin;
5990 GtkWidget *attach_clist;
5992 GtkListStore *store;
5993 GtkCellRenderer *renderer;
5994 GtkTreeViewColumn *column;
5995 GtkTreeSelection *selection;
5997 /* attachment list */
5998 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
5999 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
6000 GTK_POLICY_AUTOMATIC,
6001 GTK_POLICY_AUTOMATIC);
6002 gtk_widget_set_size_request(attach_scrwin, -1, 80);
6004 store = gtk_list_store_new(N_ATTACH_COLS,
6009 G_TYPE_AUTO_POINTER,
6011 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
6012 (GTK_TREE_MODEL(store)));
6013 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
6014 g_object_unref(store);
6016 renderer = gtk_cell_renderer_text_new();
6017 column = gtk_tree_view_column_new_with_attributes
6018 (_("Mime type"), renderer, "text",
6019 COL_MIMETYPE, NULL);
6020 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6022 renderer = gtk_cell_renderer_text_new();
6023 column = gtk_tree_view_column_new_with_attributes
6024 (_("Size"), renderer, "text",
6026 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6028 renderer = gtk_cell_renderer_text_new();
6029 column = gtk_tree_view_column_new_with_attributes
6030 (_("Name"), renderer, "text",
6032 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6034 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
6035 prefs_common.use_stripes_everywhere);
6036 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
6037 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
6039 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
6040 G_CALLBACK(attach_selected), compose);
6041 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
6042 G_CALLBACK(attach_button_pressed), compose);
6044 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
6045 G_CALLBACK(popup_attach_button_pressed), compose);
6047 gtk_widget_tap_and_hold_setup(GTK_WIDGET(attach_clist), NULL, NULL,
6048 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
6049 g_signal_connect(G_OBJECT(attach_clist), "tap-and-hold",
6050 G_CALLBACK(popup_attach_button_pressed), compose);
6052 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
6053 G_CALLBACK(attach_key_pressed), compose);
6056 gtk_drag_dest_set(attach_clist,
6057 GTK_DEST_DEFAULT_ALL, compose_mime_types,
6058 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6059 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6060 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
6061 G_CALLBACK(compose_attach_drag_received_cb),
6063 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
6064 G_CALLBACK(compose_drag_drop),
6067 compose->attach_scrwin = attach_scrwin;
6068 compose->attach_clist = attach_clist;
6070 return attach_scrwin;
6073 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
6074 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
6076 static GtkWidget *compose_create_others(Compose *compose)
6079 GtkWidget *savemsg_checkbtn;
6080 GtkWidget *savemsg_entry;
6081 GtkWidget *savemsg_select;
6084 gchar *folderidentifier;
6086 /* Table for settings */
6087 table = gtk_table_new(3, 1, FALSE);
6088 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
6089 gtk_widget_show(table);
6090 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
6093 /* Save Message to folder */
6094 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
6095 gtk_widget_show(savemsg_checkbtn);
6096 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6097 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6098 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
6100 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
6101 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
6103 savemsg_entry = gtk_entry_new();
6104 gtk_widget_show(savemsg_entry);
6105 gtk_table_attach_defaults(GTK_TABLE(table), savemsg_entry, 1, 2, rowcount, rowcount + 1);
6106 gtk_editable_set_editable(GTK_EDITABLE(savemsg_entry), prefs_common.savemsg);
6107 g_signal_connect_after(G_OBJECT(savemsg_entry), "grab_focus",
6108 G_CALLBACK(compose_grab_focus_cb), compose);
6109 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6110 folderidentifier = folder_item_get_identifier(account_get_special_folder
6111 (compose->account, F_OUTBOX));
6112 gtk_entry_set_text(GTK_ENTRY(savemsg_entry), folderidentifier);
6113 g_free(folderidentifier);
6116 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
6117 gtk_widget_show(savemsg_select);
6118 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6119 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
6120 G_CALLBACK(compose_savemsg_select_cb),
6125 compose->savemsg_checkbtn = savemsg_checkbtn;
6126 compose->savemsg_entry = savemsg_entry;
6131 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
6133 gtk_editable_set_editable(GTK_EDITABLE(compose->savemsg_entry),
6134 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
6137 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
6142 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL);
6145 path = folder_item_get_identifier(dest);
6147 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), path);
6151 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
6152 GdkAtom clip, GtkTextIter *insert_place);
6154 #define BLOCK_WRAP() { \
6155 prev_autowrap = compose->autowrap; \
6156 buffer = gtk_text_view_get_buffer( \
6157 GTK_TEXT_VIEW(compose->text)); \
6158 compose->autowrap = FALSE; \
6160 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
6161 G_CALLBACK(compose_changed_cb), \
6163 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
6164 G_CALLBACK(text_inserted), \
6167 #define UNBLOCK_WRAP() { \
6168 compose->autowrap = prev_autowrap; \
6169 if (compose->autowrap) \
6170 compose_wrap_all(compose); \
6172 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
6173 G_CALLBACK(compose_changed_cb), \
6175 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
6176 G_CALLBACK(text_inserted), \
6181 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
6185 GtkTextBuffer *buffer;
6187 if (event->button == 3) {
6189 GtkTextIter sel_start, sel_end;
6190 gboolean stuff_selected;
6192 /* move the cursor to allow GtkAspell to check the word
6193 * under the mouse */
6194 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6195 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6197 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6200 stuff_selected = gtk_text_buffer_get_selection_bounds(
6201 GTK_TEXT_VIEW(text)->buffer,
6202 &sel_start, &sel_end);
6204 gtk_text_buffer_place_cursor (GTK_TEXT_VIEW(text)->buffer, &iter);
6205 /* reselect stuff */
6207 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
6208 gtk_text_buffer_select_range(GTK_TEXT_VIEW(text)->buffer,
6209 &sel_start, &sel_end);
6211 return FALSE; /* pass the event so that the right-click goes through */
6214 if (event->button == 2) {
6219 /* get the middle-click position to paste at the correct place */
6220 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6221 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6223 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6226 entry_paste_clipboard(compose, text,
6227 prefs_common.linewrap_pastes,
6228 GDK_SELECTION_PRIMARY, &iter);
6236 static void compose_spell_menu_changed(void *data)
6238 Compose *compose = (Compose *)data;
6240 GtkWidget *menuitem;
6241 GtkWidget *parent_item;
6242 GtkMenu *menu = GTK_MENU(gtk_menu_new());
6243 GtkItemFactory *ifactory = gtk_item_factory_from_widget(compose->menubar);
6246 if (compose->gtkaspell == NULL)
6249 parent_item = gtk_item_factory_get_item(ifactory,
6250 "/Spelling/Options");
6252 /* setting the submenu removes /Spelling/Options from the factory
6253 * so we need to save it */
6255 if (parent_item == NULL) {
6256 parent_item = compose->aspell_options_menu;
6257 gtk_menu_item_remove_submenu(GTK_MENU_ITEM(parent_item));
6259 compose->aspell_options_menu = parent_item;
6261 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
6263 spell_menu = g_slist_reverse(spell_menu);
6264 for (items = spell_menu;
6265 items; items = items->next) {
6266 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
6267 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
6268 gtk_widget_show(GTK_WIDGET(menuitem));
6270 g_slist_free(spell_menu);
6272 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
6277 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
6279 Compose *compose = (Compose *)data;
6280 GdkEventButton event;
6283 event.time = gtk_get_current_event_time();
6285 return text_clicked(compose->text, &event, compose);
6288 static gboolean compose_force_window_origin = TRUE;
6289 static Compose *compose_create(PrefsAccount *account, ComposeMode mode,
6296 GtkWidget *handlebox;
6298 GtkWidget *notebook;
6303 GtkWidget *subject_hbox;
6304 GtkWidget *subject_frame;
6305 GtkWidget *subject_entry;
6309 GtkWidget *edit_vbox;
6310 GtkWidget *ruler_hbox;
6312 GtkWidget *scrolledwin;
6314 GtkTextBuffer *buffer;
6315 GtkClipboard *clipboard;
6317 UndoMain *undostruct;
6319 gchar *titles[N_ATTACH_COLS];
6320 guint n_menu_entries;
6321 GtkWidget *popupmenu;
6322 GtkItemFactory *popupfactory;
6323 GtkItemFactory *ifactory;
6324 GtkWidget *tmpl_menu;
6326 GtkWidget *menuitem;
6329 GtkAspell * gtkaspell = NULL;
6332 static GdkGeometry geometry;
6334 g_return_val_if_fail(account != NULL, NULL);
6336 debug_print("Creating compose window...\n");
6337 compose = g_new0(Compose, 1);
6339 titles[COL_MIMETYPE] = _("MIME type");
6340 titles[COL_SIZE] = _("Size");
6341 titles[COL_NAME] = _("Name");
6343 compose->batch = batch;
6344 compose->account = account;
6346 compose->mutex = g_mutex_new();
6347 compose->set_cursor_pos = -1;
6349 compose->tooltips = gtk_tooltips_new();
6351 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
6353 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
6354 gtk_widget_set_size_request(window, -1, prefs_common.compose_height);
6356 if (!geometry.max_width) {
6357 geometry.max_width = gdk_screen_width();
6358 geometry.max_height = gdk_screen_height();
6361 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
6362 &geometry, GDK_HINT_MAX_SIZE);
6363 if (!geometry.min_width) {
6364 geometry.min_width = 600;
6365 geometry.min_height = 480;
6367 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
6368 &geometry, GDK_HINT_MIN_SIZE);
6371 if (compose_force_window_origin)
6372 gtk_widget_set_uposition(window, prefs_common.compose_x,
6373 prefs_common.compose_y);
6375 g_signal_connect(G_OBJECT(window), "delete_event",
6376 G_CALLBACK(compose_delete_cb), compose);
6377 MANAGE_WINDOW_SIGNALS_CONNECT(window);
6378 gtk_widget_realize(window);
6380 gtkut_widget_set_composer_icon(window);
6382 vbox = gtk_vbox_new(FALSE, 0);
6383 gtk_container_add(GTK_CONTAINER(window), vbox);
6385 n_menu_entries = sizeof(compose_entries) / sizeof(compose_entries[0]);
6386 menubar = menubar_create(window, compose_entries,
6387 n_menu_entries, "<Compose>", compose);
6388 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
6390 if (prefs_common.toolbar_detachable) {
6391 handlebox = gtk_handle_box_new();
6393 handlebox = gtk_hbox_new(FALSE, 0);
6395 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
6397 gtk_widget_realize(handlebox);
6398 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
6401 vbox2 = gtk_vbox_new(FALSE, 2);
6402 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
6403 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
6406 notebook = gtk_notebook_new();
6407 gtk_widget_set_size_request(notebook, -1, 130);
6408 gtk_widget_show(notebook);
6410 /* header labels and entries */
6411 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6412 compose_create_header(compose),
6413 gtk_label_new_with_mnemonic(_("Hea_der")));
6414 /* attachment list */
6415 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6416 compose_create_attach(compose),
6417 gtk_label_new_with_mnemonic(_("_Attachments")));
6419 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6420 compose_create_others(compose),
6421 gtk_label_new_with_mnemonic(_("Othe_rs")));
6424 subject_hbox = gtk_hbox_new(FALSE, 0);
6425 gtk_widget_show(subject_hbox);
6427 subject_frame = gtk_frame_new(NULL);
6428 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
6429 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
6430 gtk_widget_show(subject_frame);
6432 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
6433 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
6434 gtk_widget_show(subject);
6436 label = gtk_label_new(_("Subject:"));
6437 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
6438 gtk_widget_show(label);
6440 subject_entry = gtk_entry_new();
6441 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
6442 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
6443 G_CALLBACK(compose_grab_focus_cb), compose);
6444 gtk_widget_show(subject_entry);
6445 compose->subject_entry = subject_entry;
6446 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
6448 edit_vbox = gtk_vbox_new(FALSE, 0);
6450 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
6453 ruler_hbox = gtk_hbox_new(FALSE, 0);
6454 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
6456 ruler = gtk_shruler_new();
6457 gtk_ruler_set_range(GTK_RULER(ruler), 0.0, 100.0, 1.0, 100.0);
6458 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
6462 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
6463 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
6464 GTK_POLICY_AUTOMATIC,
6465 GTK_POLICY_AUTOMATIC);
6466 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
6468 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
6469 gtk_widget_set_size_request(scrolledwin, prefs_common.compose_width, -1);
6471 text = gtk_text_view_new();
6472 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
6473 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
6474 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
6475 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
6476 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
6478 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
6480 g_signal_connect_after(G_OBJECT(text), "size_allocate",
6481 G_CALLBACK(compose_edit_size_alloc),
6483 g_signal_connect(G_OBJECT(buffer), "changed",
6484 G_CALLBACK(compose_changed_cb), compose);
6485 g_signal_connect(G_OBJECT(text), "grab_focus",
6486 G_CALLBACK(compose_grab_focus_cb), compose);
6487 g_signal_connect(G_OBJECT(buffer), "insert_text",
6488 G_CALLBACK(text_inserted), compose);
6489 g_signal_connect(G_OBJECT(text), "button_press_event",
6490 G_CALLBACK(text_clicked), compose);
6492 g_signal_connect(G_OBJECT(text), "popup-menu",
6493 G_CALLBACK(compose_popup_menu), compose);
6495 gtk_widget_tap_and_hold_setup(GTK_WIDGET(text), NULL, NULL,
6496 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
6497 g_signal_connect(G_OBJECT(text), "tap-and-hold",
6498 G_CALLBACK(compose_popup_menu), compose);
6500 g_signal_connect(G_OBJECT(subject_entry), "changed",
6501 G_CALLBACK(compose_changed_cb), compose);
6504 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6505 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6506 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6507 g_signal_connect(G_OBJECT(text), "drag_data_received",
6508 G_CALLBACK(compose_insert_drag_received_cb),
6510 g_signal_connect(G_OBJECT(text), "drag-drop",
6511 G_CALLBACK(compose_drag_drop),
6513 gtk_widget_show_all(vbox);
6515 /* pane between attach clist and text */
6516 paned = gtk_vpaned_new();
6517 gtk_paned_set_gutter_size(GTK_PANED(paned), 12);
6518 gtk_container_add(GTK_CONTAINER(vbox2), paned);
6520 if( maemo_mainwindow_is_fullscreen(mainwindow_get_mainwindow()->window) )
6521 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 300 : 280);
6523 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 250 : 230);
6525 gtk_paned_add1(GTK_PANED(paned), notebook);
6526 gtk_paned_add2(GTK_PANED(paned), edit_vbox);
6527 gtk_widget_show_all(paned);
6530 if (prefs_common.textfont) {
6531 PangoFontDescription *font_desc;
6533 font_desc = pango_font_description_from_string
6534 (prefs_common.textfont);
6536 gtk_widget_modify_font(text, font_desc);
6537 pango_font_description_free(font_desc);
6541 n_entries = sizeof(compose_popup_entries) /
6542 sizeof(compose_popup_entries[0]);
6543 popupmenu = menu_create_items(compose_popup_entries, n_entries,
6544 "<Compose>", &popupfactory,
6547 ifactory = gtk_item_factory_from_widget(menubar);
6548 menu_set_sensitive(ifactory, "/Edit/Undo", FALSE);
6549 menu_set_sensitive(ifactory, "/Edit/Redo", FALSE);
6550 menu_set_sensitive(ifactory, "/Options/Remove references", FALSE);
6552 tmpl_menu = gtk_item_factory_get_item(ifactory, "/Tools/Template");
6554 undostruct = undo_init(text);
6555 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
6558 address_completion_start(window);
6560 compose->window = window;
6561 compose->vbox = vbox;
6562 compose->menubar = menubar;
6563 compose->handlebox = handlebox;
6565 compose->vbox2 = vbox2;
6567 compose->paned = paned;
6569 compose->notebook = notebook;
6570 compose->edit_vbox = edit_vbox;
6571 compose->ruler_hbox = ruler_hbox;
6572 compose->ruler = ruler;
6573 compose->scrolledwin = scrolledwin;
6574 compose->text = text;
6576 compose->focused_editable = NULL;
6578 compose->popupmenu = popupmenu;
6579 compose->popupfactory = popupfactory;
6581 compose->tmpl_menu = tmpl_menu;
6583 compose->mode = mode;
6584 compose->rmode = mode;
6586 compose->targetinfo = NULL;
6587 compose->replyinfo = NULL;
6588 compose->fwdinfo = NULL;
6590 compose->replyto = NULL;
6592 compose->bcc = NULL;
6593 compose->followup_to = NULL;
6595 compose->ml_post = NULL;
6597 compose->inreplyto = NULL;
6598 compose->references = NULL;
6599 compose->msgid = NULL;
6600 compose->boundary = NULL;
6602 compose->autowrap = prefs_common.autowrap;
6604 compose->use_signing = FALSE;
6605 compose->use_encryption = FALSE;
6606 compose->privacy_system = NULL;
6608 compose->modified = FALSE;
6610 compose->return_receipt = FALSE;
6612 compose->to_list = NULL;
6613 compose->newsgroup_list = NULL;
6615 compose->undostruct = undostruct;
6617 compose->sig_str = NULL;
6619 compose->exteditor_file = NULL;
6620 compose->exteditor_pid = -1;
6621 compose->exteditor_tag = -1;
6622 compose->draft_timeout_tag = -1;
6625 menu_set_sensitive(ifactory, "/Spelling", FALSE);
6626 if (mode != COMPOSE_REDIRECT) {
6627 if (prefs_common.enable_aspell && prefs_common.dictionary &&
6628 strcmp(prefs_common.dictionary, "")) {
6629 gtkaspell = gtkaspell_new(prefs_common.aspell_path,
6630 prefs_common.dictionary,
6631 prefs_common.alt_dictionary,
6632 conv_get_locale_charset_str(),
6633 prefs_common.misspelled_col,
6634 prefs_common.check_while_typing,
6635 prefs_common.recheck_when_changing_dict,
6636 prefs_common.use_alternate,
6637 prefs_common.use_both_dicts,
6638 GTK_TEXT_VIEW(text),
6639 GTK_WINDOW(compose->window),
6640 compose_spell_menu_changed,
6643 alertpanel_error(_("Spell checker could not "
6645 gtkaspell_checkers_strerror());
6646 gtkaspell_checkers_reset_error();
6648 if (!gtkaspell_set_sug_mode(gtkaspell,
6649 prefs_common.aspell_sugmode)) {
6650 debug_print("Aspell: could not set "
6651 "suggestion mode %s\n",
6652 gtkaspell_checkers_strerror());
6653 gtkaspell_checkers_reset_error();
6656 menu_set_sensitive(ifactory, "/Spelling", TRUE);
6660 compose->gtkaspell = gtkaspell;
6661 compose_spell_menu_changed(compose);
6664 compose_select_account(compose, account, TRUE);
6666 menu_set_active(ifactory, "/Edit/Auto wrapping", prefs_common.autowrap);
6667 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
6668 compose_entry_append(compose, account->auto_cc, COMPOSE_CC);
6670 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
6671 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC);
6673 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
6674 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO);
6676 menu_set_sensitive(ifactory, "/Options/Reply mode", compose->mode == COMPOSE_REPLY);
6678 if (account->protocol != A_NNTP)
6679 gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(compose->header_last->combo)->entry),
6680 prefs_common_translated_header_name("To:"));
6682 gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(compose->header_last->combo)->entry),
6683 prefs_common_translated_header_name("Newsgroups:"));
6685 addressbook_set_target_compose(compose);
6687 if (mode != COMPOSE_REDIRECT)
6688 compose_set_template_menu(compose);
6690 menuitem = gtk_item_factory_get_item(ifactory, "/Tools/Template");
6691 menu_set_sensitive(ifactory, "/Tools/Template", FALSE);
6694 compose_list = g_list_append(compose_list, compose);
6696 if (!prefs_common.show_ruler)
6697 gtk_widget_hide(ruler_hbox);
6699 menuitem = gtk_item_factory_get_item(ifactory, "/Tools/Show ruler");
6700 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
6701 prefs_common.show_ruler);
6704 compose->priority = PRIORITY_NORMAL;
6705 compose_update_priority_menu_item(compose);
6707 compose_set_out_encoding(compose);
6710 compose_update_actions_menu(compose);
6712 /* Privacy Systems menu */
6713 compose_update_privacy_systems_menu(compose);
6715 activate_privacy_system(compose, account, TRUE);
6716 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
6718 gtk_widget_realize(window);
6720 gtk_widget_show(window);
6722 maemo_window_full_screen_if_needed(GTK_WINDOW(window));
6723 maemo_connect_key_press_to_mainwindow(GTK_WINDOW(window));
6730 static GtkWidget *compose_account_option_menu_create(Compose *compose)
6735 GtkWidget *optmenubox;
6738 GtkWidget *from_name = NULL;
6740 gint num = 0, def_menu = 0;
6742 accounts = account_get_list();
6743 g_return_val_if_fail(accounts != NULL, NULL);
6745 optmenubox = gtk_event_box_new();
6746 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
6747 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
6749 hbox = gtk_hbox_new(FALSE, 6);
6750 from_name = gtk_entry_new();
6752 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
6753 G_CALLBACK(compose_grab_focus_cb), compose);
6755 for (; accounts != NULL; accounts = accounts->next, num++) {
6756 PrefsAccount *ac = (PrefsAccount *)accounts->data;
6757 gchar *name, *from = NULL;
6759 if (ac == compose->account) def_menu = num;
6761 name = g_markup_printf_escaped(_("From: <i>%s</i>"),
6764 if (ac == compose->account) {
6765 if (ac->name && *ac->name) {
6767 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
6768 from = g_strdup_printf("%s <%s>",
6770 gtk_entry_set_text(GTK_ENTRY(from_name), from);
6772 from = g_strdup_printf("%s",
6774 gtk_entry_set_text(GTK_ENTRY(from_name), from);
6777 COMBOBOX_ADD(menu, name, ac->account_id);
6782 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
6784 g_signal_connect(G_OBJECT(optmenu), "changed",
6785 G_CALLBACK(account_activated),
6787 g_signal_connect(G_OBJECT(from_name), "populate-popup",
6788 G_CALLBACK(compose_entry_popup_extend),
6791 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
6792 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
6794 gtk_tooltips_set_tip(compose->tooltips, optmenubox,
6795 _("Account to use for this email"), NULL);
6796 gtk_tooltips_set_tip(compose->tooltips, from_name,
6797 _("Sender address to be used"), NULL);
6799 compose->from_name = from_name;
6804 static void compose_set_priority_cb(gpointer data,
6808 Compose *compose = (Compose *) data;
6809 compose->priority = action;
6812 static void compose_reply_change_mode(gpointer data,
6816 Compose *compose = (Compose *) data;
6817 gboolean was_modified = compose->modified;
6819 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
6821 g_return_if_fail(compose->replyinfo != NULL);
6823 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
6825 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
6827 if (action == COMPOSE_REPLY_TO_ALL)
6829 if (action == COMPOSE_REPLY_TO_SENDER)
6831 if (action == COMPOSE_REPLY_TO_LIST)
6834 compose_remove_header_entries(compose);
6835 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
6836 if (compose->account->set_autocc && compose->account->auto_cc)
6837 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC);
6839 if (compose->account->set_autobcc && compose->account->auto_bcc)
6840 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC);
6842 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
6843 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO);
6844 compose_show_first_last_header(compose, TRUE);
6845 compose->modified = was_modified;
6846 compose_set_title(compose);
6849 static void compose_update_priority_menu_item(Compose * compose)
6851 GtkItemFactory *ifactory;
6852 GtkWidget *menuitem = NULL;
6854 ifactory = gtk_item_factory_from_widget(compose->menubar);
6856 switch (compose->priority) {
6857 case PRIORITY_HIGHEST:
6858 menuitem = gtk_item_factory_get_item
6859 (ifactory, "/Options/Priority/Highest");
6862 menuitem = gtk_item_factory_get_item
6863 (ifactory, "/Options/Priority/High");
6865 case PRIORITY_NORMAL:
6866 menuitem = gtk_item_factory_get_item
6867 (ifactory, "/Options/Priority/Normal");
6870 menuitem = gtk_item_factory_get_item
6871 (ifactory, "/Options/Priority/Low");
6873 case PRIORITY_LOWEST:
6874 menuitem = gtk_item_factory_get_item
6875 (ifactory, "/Options/Priority/Lowest");
6878 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
6881 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
6883 Compose *compose = (Compose *) data;
6885 GtkItemFactory *ifactory;
6886 gboolean can_sign = FALSE, can_encrypt = FALSE;
6888 g_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
6890 if (!GTK_CHECK_MENU_ITEM(widget)->active)
6893 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
6894 g_free(compose->privacy_system);
6895 compose->privacy_system = NULL;
6896 if (systemid != NULL) {
6897 compose->privacy_system = g_strdup(systemid);
6899 can_sign = privacy_system_can_sign(systemid);
6900 can_encrypt = privacy_system_can_encrypt(systemid);
6903 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
6905 ifactory = gtk_item_factory_from_widget(compose->menubar);
6906 menu_set_sensitive(ifactory, "/Options/Sign", can_sign);
6907 menu_set_sensitive(ifactory, "/Options/Encrypt", can_encrypt);
6910 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
6912 static gchar *branch_path = "/Options/Privacy System";
6913 GtkItemFactory *ifactory;
6914 GtkWidget *menuitem = NULL;
6916 gboolean can_sign = FALSE, can_encrypt = FALSE;
6917 gboolean found = FALSE;
6919 ifactory = gtk_item_factory_from_widget(compose->menubar);
6921 if (compose->privacy_system != NULL) {
6924 menuitem = gtk_item_factory_get_widget(ifactory, branch_path);
6925 g_return_if_fail(menuitem != NULL);
6927 amenu = GTK_MENU_SHELL(menuitem)->children;
6929 while (amenu != NULL) {
6930 GList *alist = amenu->next;
6932 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
6933 if (systemid != NULL) {
6934 if (strcmp(systemid, compose->privacy_system) == 0) {
6935 menuitem = GTK_WIDGET(amenu->data);
6937 can_sign = privacy_system_can_sign(systemid);
6938 can_encrypt = privacy_system_can_encrypt(systemid);
6942 } else if (strlen(compose->privacy_system) == 0) {
6943 menuitem = GTK_WIDGET(amenu->data);
6946 can_encrypt = FALSE;
6953 if (menuitem != NULL)
6954 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
6956 if (warn && !found && strlen(compose->privacy_system)) {
6957 gchar *tmp = g_strdup_printf(
6958 _("The privacy system '%s' cannot be loaded. You "
6959 "will not be able to sign or encrypt this message."),
6960 compose->privacy_system);
6961 alertpanel_warning(tmp);
6966 menu_set_sensitive(ifactory, "/Options/Sign", can_sign);
6967 menu_set_sensitive(ifactory, "/Options/Encrypt", can_encrypt);
6970 static void compose_set_out_encoding(Compose *compose)
6972 GtkItemFactoryEntry *entry;
6973 GtkItemFactory *ifactory;
6974 CharSet out_encoding;
6975 gchar *path, *p, *q;
6978 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
6979 ifactory = gtk_item_factory_from_widget(compose->menubar);
6981 for (entry = compose_entries; entry->callback != compose_address_cb;
6983 if (entry->callback == compose_set_encoding_cb &&
6984 (CharSet)entry->callback_action == out_encoding) {
6985 p = q = path = g_strdup(entry->path);
6997 item = gtk_item_factory_get_item(ifactory, path);
6998 gtk_widget_activate(item);
7005 static void compose_set_template_menu(Compose *compose)
7007 GSList *tmpl_list, *cur;
7011 tmpl_list = template_get_config();
7013 menu = gtk_menu_new();
7015 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
7016 Template *tmpl = (Template *)cur->data;
7018 item = gtk_menu_item_new_with_label(tmpl->name);
7019 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
7020 g_signal_connect(G_OBJECT(item), "activate",
7021 G_CALLBACK(compose_template_activate_cb),
7023 g_object_set_data(G_OBJECT(item), "template", tmpl);
7024 gtk_widget_show(item);
7027 gtk_widget_show(menu);
7028 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
7031 void compose_update_actions_menu(Compose *compose)
7033 GtkItemFactory *ifactory;
7035 ifactory = gtk_item_factory_from_widget(compose->menubar);
7036 action_update_compose_menu(ifactory, "/Tools/Actions", compose);
7039 static void compose_update_privacy_systems_menu(Compose *compose)
7041 static gchar *branch_path = "/Options/Privacy System";
7042 GtkItemFactory *ifactory;
7043 GtkWidget *menuitem;
7044 GSList *systems, *cur;
7047 GtkWidget *system_none;
7050 ifactory = gtk_item_factory_from_widget(compose->menubar);
7052 /* remove old entries */
7053 menuitem = gtk_item_factory_get_widget(ifactory, branch_path);
7054 g_return_if_fail(menuitem != NULL);
7056 amenu = GTK_MENU_SHELL(menuitem)->children->next;
7057 while (amenu != NULL) {
7058 GList *alist = amenu->next;
7059 gtk_widget_destroy(GTK_WIDGET(amenu->data));
7063 system_none = gtk_item_factory_get_widget(ifactory,
7064 "/Options/Privacy System/None");
7066 g_signal_connect(G_OBJECT(system_none), "activate",
7067 G_CALLBACK(compose_set_privacy_system_cb), compose);
7069 systems = privacy_get_system_ids();
7070 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
7071 gchar *systemid = cur->data;
7073 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
7074 widget = gtk_radio_menu_item_new_with_label(group,
7075 privacy_system_get_name(systemid));
7076 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
7077 g_strdup(systemid), g_free);
7078 g_signal_connect(G_OBJECT(widget), "activate",
7079 G_CALLBACK(compose_set_privacy_system_cb), compose);
7081 gtk_menu_append(GTK_MENU(system_none->parent), widget);
7082 gtk_widget_show(widget);
7085 g_slist_free(systems);
7088 void compose_reflect_prefs_all(void)
7093 for (cur = compose_list; cur != NULL; cur = cur->next) {
7094 compose = (Compose *)cur->data;
7095 compose_set_template_menu(compose);
7099 void compose_reflect_prefs_pixmap_theme(void)
7104 for (cur = compose_list; cur != NULL; cur = cur->next) {
7105 compose = (Compose *)cur->data;
7106 toolbar_update(TOOLBAR_COMPOSE, compose);
7110 static void compose_template_apply(Compose *compose, Template *tmpl,
7114 GtkTextBuffer *buffer;
7118 gchar *parsed_str = NULL;
7119 gint cursor_pos = 0;
7120 const gchar *err_msg = _("Template body format error at line %d.");
7123 /* process the body */
7125 text = GTK_TEXT_VIEW(compose->text);
7126 buffer = gtk_text_view_get_buffer(text);
7129 if (prefs_common.quotemark && *prefs_common.quotemark)
7130 qmark = prefs_common.quotemark;
7134 if (compose->replyinfo != NULL) {
7137 gtk_text_buffer_set_text(buffer, "", -1);
7138 mark = gtk_text_buffer_get_insert(buffer);
7139 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7141 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
7142 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
7144 } else if (compose->fwdinfo != NULL) {
7147 gtk_text_buffer_set_text(buffer, "", -1);
7148 mark = gtk_text_buffer_get_insert(buffer);
7149 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7151 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
7152 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
7155 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
7157 GtkTextIter start, end;
7160 gtk_text_buffer_get_start_iter(buffer, &start);
7161 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
7162 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
7164 /* clear the buffer now */
7166 gtk_text_buffer_set_text(buffer, "", -1);
7168 parsed_str = compose_quote_fmt(compose, dummyinfo,
7169 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
7170 procmsg_msginfo_free( dummyinfo );
7176 gtk_text_buffer_set_text(buffer, "", -1);
7177 mark = gtk_text_buffer_get_insert(buffer);
7178 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7181 if (replace && parsed_str && compose->account->auto_sig)
7182 compose_insert_sig(compose, FALSE);
7184 if (replace && parsed_str) {
7185 gtk_text_buffer_get_start_iter(buffer, &iter);
7186 gtk_text_buffer_place_cursor(buffer, &iter);
7190 cursor_pos = quote_fmt_get_cursor_pos();
7191 compose->set_cursor_pos = cursor_pos;
7192 if (cursor_pos == -1)
7194 gtk_text_buffer_get_start_iter(buffer, &iter);
7195 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
7196 gtk_text_buffer_place_cursor(buffer, &iter);
7199 /* process the other fields */
7201 compose_template_apply_fields(compose, tmpl);
7202 quote_fmt_reset_vartable();
7203 compose_changed_cb(NULL, compose);
7206 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
7208 MsgInfo* dummyinfo = NULL;
7209 MsgInfo *msginfo = NULL;
7212 if (compose->replyinfo != NULL)
7213 msginfo = compose->replyinfo;
7214 else if (compose->fwdinfo != NULL)
7215 msginfo = compose->fwdinfo;
7217 dummyinfo = compose_msginfo_new_from_compose(compose);
7218 msginfo = dummyinfo;
7221 if (tmpl->to && *tmpl->to != '\0') {
7223 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7224 compose->gtkaspell);
7226 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7228 quote_fmt_scan_string(tmpl->to);
7231 buf = quote_fmt_get_buffer();
7233 alertpanel_error(_("Template To format error."));
7235 compose_entry_append(compose, buf, COMPOSE_TO);
7239 if (tmpl->cc && *tmpl->cc != '\0') {
7241 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7242 compose->gtkaspell);
7244 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7246 quote_fmt_scan_string(tmpl->cc);
7249 buf = quote_fmt_get_buffer();
7251 alertpanel_error(_("Template Cc format error."));
7253 compose_entry_append(compose, buf, COMPOSE_CC);
7257 if (tmpl->bcc && *tmpl->bcc != '\0') {
7259 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7260 compose->gtkaspell);
7262 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7264 quote_fmt_scan_string(tmpl->bcc);
7267 buf = quote_fmt_get_buffer();
7269 alertpanel_error(_("Template Bcc format error."));
7271 compose_entry_append(compose, buf, COMPOSE_BCC);
7275 /* process the subject */
7276 if (tmpl->subject && *tmpl->subject != '\0') {
7278 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7279 compose->gtkaspell);
7281 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7283 quote_fmt_scan_string(tmpl->subject);
7286 buf = quote_fmt_get_buffer();
7288 alertpanel_error(_("Template subject format error."));
7290 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
7294 procmsg_msginfo_free( dummyinfo );
7297 static void compose_destroy(Compose *compose)
7299 GtkTextBuffer *buffer;
7300 GtkClipboard *clipboard;
7302 compose_list = g_list_remove(compose_list, compose);
7304 if (compose->updating) {
7305 debug_print("danger, not destroying anything now\n");
7306 compose->deferred_destroy = TRUE;
7309 /* NOTE: address_completion_end() does nothing with the window
7310 * however this may change. */
7311 address_completion_end(compose->window);
7313 slist_free_strings(compose->to_list);
7314 g_slist_free(compose->to_list);
7315 slist_free_strings(compose->newsgroup_list);
7316 g_slist_free(compose->newsgroup_list);
7317 slist_free_strings(compose->header_list);
7318 g_slist_free(compose->header_list);
7320 procmsg_msginfo_free(compose->targetinfo);
7321 procmsg_msginfo_free(compose->replyinfo);
7322 procmsg_msginfo_free(compose->fwdinfo);
7324 g_free(compose->replyto);
7325 g_free(compose->cc);
7326 g_free(compose->bcc);
7327 g_free(compose->newsgroups);
7328 g_free(compose->followup_to);
7330 g_free(compose->ml_post);
7332 g_free(compose->inreplyto);
7333 g_free(compose->references);
7334 g_free(compose->msgid);
7335 g_free(compose->boundary);
7337 g_free(compose->redirect_filename);
7338 if (compose->undostruct)
7339 undo_destroy(compose->undostruct);
7341 g_free(compose->sig_str);
7343 g_free(compose->exteditor_file);
7345 g_free(compose->orig_charset);
7347 g_free(compose->privacy_system);
7349 if (addressbook_get_target_compose() == compose)
7350 addressbook_set_target_compose(NULL);
7353 if (compose->gtkaspell) {
7354 gtkaspell_delete(compose->gtkaspell);
7355 compose->gtkaspell = NULL;
7359 prefs_common.compose_width = compose->scrolledwin->allocation.width;
7360 prefs_common.compose_height = compose->window->allocation.height;
7362 if (!gtk_widget_get_parent(compose->paned))
7363 gtk_widget_destroy(compose->paned);
7364 gtk_widget_destroy(compose->popupmenu);
7366 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
7367 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7368 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
7370 gtk_widget_destroy(compose->window);
7371 toolbar_destroy(compose->toolbar);
7372 g_free(compose->toolbar);
7373 g_mutex_free(compose->mutex);
7377 static void compose_attach_info_free(AttachInfo *ainfo)
7379 g_free(ainfo->file);
7380 g_free(ainfo->content_type);
7381 g_free(ainfo->name);
7385 static void compose_attach_remove_selected(Compose *compose)
7387 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
7388 GtkTreeSelection *selection;
7390 GtkTreeModel *model;
7392 selection = gtk_tree_view_get_selection(tree_view);
7393 sel = gtk_tree_selection_get_selected_rows(selection, &model);
7398 for (cur = sel; cur != NULL; cur = cur->next) {
7399 GtkTreePath *path = cur->data;
7400 GtkTreeRowReference *ref = gtk_tree_row_reference_new
7403 gtk_tree_path_free(path);
7406 for (cur = sel; cur != NULL; cur = cur->next) {
7407 GtkTreeRowReference *ref = cur->data;
7408 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
7411 if (gtk_tree_model_get_iter(model, &iter, path))
7412 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
7414 gtk_tree_path_free(path);
7415 gtk_tree_row_reference_free(ref);
7421 static struct _AttachProperty
7424 GtkWidget *mimetype_entry;
7425 GtkWidget *encoding_optmenu;
7426 GtkWidget *path_entry;
7427 GtkWidget *filename_entry;
7429 GtkWidget *cancel_btn;
7432 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
7434 gtk_tree_path_free((GtkTreePath *)ptr);
7437 static void compose_attach_property(Compose *compose)
7439 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
7441 GtkComboBox *optmenu;
7442 GtkTreeSelection *selection;
7444 GtkTreeModel *model;
7447 static gboolean cancelled;
7449 /* only if one selected */
7450 selection = gtk_tree_view_get_selection(tree_view);
7451 if (gtk_tree_selection_count_selected_rows(selection) != 1)
7454 sel = gtk_tree_selection_get_selected_rows(selection, &model);
7458 path = (GtkTreePath *) sel->data;
7459 gtk_tree_model_get_iter(model, &iter, path);
7460 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
7463 g_list_foreach(sel, gtk_tree_path_free_, NULL);
7469 if (!attach_prop.window)
7470 compose_attach_property_create(&cancelled);
7471 gtk_widget_grab_focus(attach_prop.ok_btn);
7472 gtk_widget_show(attach_prop.window);
7473 manage_window_set_transient(GTK_WINDOW(attach_prop.window));
7475 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
7476 if (ainfo->encoding == ENC_UNKNOWN)
7477 combobox_select_by_data(optmenu, ENC_BASE64);
7479 combobox_select_by_data(optmenu, ainfo->encoding);
7481 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
7482 ainfo->content_type ? ainfo->content_type : "");
7483 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
7484 ainfo->file ? ainfo->file : "");
7485 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
7486 ainfo->name ? ainfo->name : "");
7489 const gchar *entry_text;
7491 gchar *cnttype = NULL;
7498 gtk_widget_hide(attach_prop.window);
7503 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
7504 if (*entry_text != '\0') {
7507 text = g_strstrip(g_strdup(entry_text));
7508 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
7509 cnttype = g_strdup(text);
7512 alertpanel_error(_("Invalid MIME type."));
7518 ainfo->encoding = combobox_get_active_data(optmenu);
7520 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
7521 if (*entry_text != '\0') {
7522 if (is_file_exist(entry_text) &&
7523 (size = get_file_size(entry_text)) > 0)
7524 file = g_strdup(entry_text);
7527 (_("File doesn't exist or is empty."));
7533 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
7534 if (*entry_text != '\0') {
7535 g_free(ainfo->name);
7536 ainfo->name = g_strdup(entry_text);
7540 g_free(ainfo->content_type);
7541 ainfo->content_type = cnttype;
7544 g_free(ainfo->file);
7550 /* update tree store */
7551 text = to_human_readable(ainfo->size);
7552 gtk_tree_model_get_iter(model, &iter, path);
7553 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
7554 COL_MIMETYPE, ainfo->content_type,
7556 COL_NAME, ainfo->name,
7562 gtk_tree_path_free(path);
7565 #define SET_LABEL_AND_ENTRY(str, entry, top) \
7567 label = gtk_label_new(str); \
7568 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
7569 GTK_FILL, 0, 0, 0); \
7570 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
7572 entry = gtk_entry_new(); \
7573 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
7574 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
7577 static void compose_attach_property_create(gboolean *cancelled)
7583 GtkWidget *mimetype_entry;
7586 GtkListStore *optmenu_menu;
7587 GtkWidget *path_entry;
7588 GtkWidget *filename_entry;
7591 GtkWidget *cancel_btn;
7592 GList *mime_type_list, *strlist;
7595 debug_print("Creating attach_property window...\n");
7597 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
7598 gtk_widget_set_size_request(window, 480, -1);
7599 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
7600 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
7601 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
7602 gtk_window_set_modal(GTK_WINDOW(window), TRUE);
7603 g_signal_connect(G_OBJECT(window), "delete_event",
7604 G_CALLBACK(attach_property_delete_event),
7606 g_signal_connect(G_OBJECT(window), "key_press_event",
7607 G_CALLBACK(attach_property_key_pressed),
7610 vbox = gtk_vbox_new(FALSE, 8);
7611 gtk_container_add(GTK_CONTAINER(window), vbox);
7613 table = gtk_table_new(4, 2, FALSE);
7614 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
7615 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
7616 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
7618 label = gtk_label_new(_("MIME type"));
7619 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
7621 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
7622 mimetype_entry = gtk_combo_new();
7623 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
7624 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
7626 /* stuff with list */
7627 mime_type_list = procmime_get_mime_type_list();
7629 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
7630 MimeType *type = (MimeType *) mime_type_list->data;
7633 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
7635 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
7638 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
7639 (GCompareFunc)strcmp2);
7642 gtk_combo_set_popdown_strings(GTK_COMBO(mimetype_entry), strlist);
7644 for (mime_type_list = strlist; mime_type_list != NULL;
7645 mime_type_list = mime_type_list->next)
7646 g_free(mime_type_list->data);
7647 g_list_free(strlist);
7649 mimetype_entry = GTK_COMBO(mimetype_entry)->entry;
7651 label = gtk_label_new(_("Encoding"));
7652 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
7654 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
7656 hbox = gtk_hbox_new(FALSE, 0);
7657 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
7658 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
7660 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
7661 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
7663 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
7664 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
7665 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
7666 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
7667 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
7669 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
7671 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
7672 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
7674 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
7675 &ok_btn, GTK_STOCK_OK,
7677 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
7678 gtk_widget_grab_default(ok_btn);
7680 g_signal_connect(G_OBJECT(ok_btn), "clicked",
7681 G_CALLBACK(attach_property_ok),
7683 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
7684 G_CALLBACK(attach_property_cancel),
7687 gtk_widget_show_all(vbox);
7689 attach_prop.window = window;
7690 attach_prop.mimetype_entry = mimetype_entry;
7691 attach_prop.encoding_optmenu = optmenu;
7692 attach_prop.path_entry = path_entry;
7693 attach_prop.filename_entry = filename_entry;
7694 attach_prop.ok_btn = ok_btn;
7695 attach_prop.cancel_btn = cancel_btn;
7698 #undef SET_LABEL_AND_ENTRY
7700 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
7706 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
7712 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
7713 gboolean *cancelled)
7721 static gboolean attach_property_key_pressed(GtkWidget *widget,
7723 gboolean *cancelled)
7725 if (event && event->keyval == GDK_Escape) {
7732 static void compose_exec_ext_editor(Compose *compose)
7739 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
7740 G_DIR_SEPARATOR, compose);
7742 if (pipe(pipe_fds) < 0) {
7748 if ((pid = fork()) < 0) {
7755 /* close the write side of the pipe */
7758 compose->exteditor_file = g_strdup(tmp);
7759 compose->exteditor_pid = pid;
7761 compose_set_ext_editor_sensitive(compose, FALSE);
7763 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
7764 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
7768 } else { /* process-monitoring process */
7774 /* close the read side of the pipe */
7777 if (compose_write_body_to_file(compose, tmp) < 0) {
7778 fd_write_all(pipe_fds[1], "2\n", 2);
7782 pid_ed = compose_exec_ext_editor_real(tmp);
7784 fd_write_all(pipe_fds[1], "1\n", 2);
7788 /* wait until editor is terminated */
7789 waitpid(pid_ed, NULL, 0);
7791 fd_write_all(pipe_fds[1], "0\n", 2);
7798 #endif /* G_OS_UNIX */
7802 static gint compose_exec_ext_editor_real(const gchar *file)
7809 g_return_val_if_fail(file != NULL, -1);
7811 if ((pid = fork()) < 0) {
7816 if (pid != 0) return pid;
7818 /* grandchild process */
7820 if (setpgid(0, getppid()))
7823 if (prefs_common.ext_editor_cmd &&
7824 (p = strchr(prefs_common.ext_editor_cmd, '%')) &&
7825 *(p + 1) == 's' && !strchr(p + 2, '%')) {
7826 g_snprintf(buf, sizeof(buf), prefs_common.ext_editor_cmd, file);
7828 if (prefs_common.ext_editor_cmd)
7829 g_warning("External editor command line is invalid: '%s'\n",
7830 prefs_common.ext_editor_cmd);
7831 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
7834 cmdline = strsplit_with_quote(buf, " ", 1024);
7835 execvp(cmdline[0], cmdline);
7838 g_strfreev(cmdline);
7843 static gboolean compose_ext_editor_kill(Compose *compose)
7845 pid_t pgid = compose->exteditor_pid * -1;
7848 ret = kill(pgid, 0);
7850 if (ret == 0 || (ret == -1 && EPERM == errno)) {
7854 msg = g_strdup_printf
7855 (_("The external editor is still working.\n"
7856 "Force terminating the process?\n"
7857 "process group id: %d"), -pgid);
7858 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
7859 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
7863 if (val == G_ALERTALTERNATE) {
7864 g_source_remove(compose->exteditor_tag);
7865 g_io_channel_shutdown(compose->exteditor_ch,
7867 g_io_channel_unref(compose->exteditor_ch);
7869 if (kill(pgid, SIGTERM) < 0) perror("kill");
7870 waitpid(compose->exteditor_pid, NULL, 0);
7872 g_warning("Terminated process group id: %d", -pgid);
7873 g_warning("Temporary file: %s",
7874 compose->exteditor_file);
7876 compose_set_ext_editor_sensitive(compose, TRUE);
7878 g_free(compose->exteditor_file);
7879 compose->exteditor_file = NULL;
7880 compose->exteditor_pid = -1;
7881 compose->exteditor_ch = NULL;
7882 compose->exteditor_tag = -1;
7890 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
7894 Compose *compose = (Compose *)data;
7897 debug_print(_("Compose: input from monitoring process\n"));
7899 g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL);
7901 g_io_channel_shutdown(source, FALSE, NULL);
7902 g_io_channel_unref(source);
7904 waitpid(compose->exteditor_pid, NULL, 0);
7906 if (buf[0] == '0') { /* success */
7907 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
7908 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
7910 gtk_text_buffer_set_text(buffer, "", -1);
7911 compose_insert_file(compose, compose->exteditor_file);
7912 compose_changed_cb(NULL, compose);
7914 if (g_unlink(compose->exteditor_file) < 0)
7915 FILE_OP_ERROR(compose->exteditor_file, "unlink");
7916 } else if (buf[0] == '1') { /* failed */
7917 g_warning("Couldn't exec external editor\n");
7918 if (g_unlink(compose->exteditor_file) < 0)
7919 FILE_OP_ERROR(compose->exteditor_file, "unlink");
7920 } else if (buf[0] == '2') {
7921 g_warning("Couldn't write to file\n");
7922 } else if (buf[0] == '3') {
7923 g_warning("Pipe read failed\n");
7926 compose_set_ext_editor_sensitive(compose, TRUE);
7928 g_free(compose->exteditor_file);
7929 compose->exteditor_file = NULL;
7930 compose->exteditor_pid = -1;
7931 compose->exteditor_ch = NULL;
7932 compose->exteditor_tag = -1;
7937 static void compose_set_ext_editor_sensitive(Compose *compose,
7940 GtkItemFactory *ifactory;
7942 ifactory = gtk_item_factory_from_widget(compose->menubar);
7944 menu_set_sensitive(ifactory, "/Message/Send", sensitive);
7945 menu_set_sensitive(ifactory, "/Message/Send later", sensitive);
7946 menu_set_sensitive(ifactory, "/Message/Insert file", sensitive);
7947 menu_set_sensitive(ifactory, "/Message/Insert signature", sensitive);
7948 menu_set_sensitive(ifactory, "/Edit/Wrap current paragraph", sensitive);
7949 menu_set_sensitive(ifactory, "/Edit/Wrap all long lines", sensitive);
7950 menu_set_sensitive(ifactory, "/Edit/Edit with external editor",
7953 gtk_widget_set_sensitive(compose->text, sensitive);
7954 if (compose->toolbar->send_btn)
7955 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
7956 if (compose->toolbar->sendl_btn)
7957 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
7958 if (compose->toolbar->draft_btn)
7959 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
7960 if (compose->toolbar->insert_btn)
7961 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
7962 if (compose->toolbar->sig_btn)
7963 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
7964 if (compose->toolbar->exteditor_btn)
7965 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
7966 if (compose->toolbar->linewrap_current_btn)
7967 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
7968 if (compose->toolbar->linewrap_all_btn)
7969 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
7971 #endif /* G_OS_UNIX */
7974 * compose_undo_state_changed:
7976 * Change the sensivity of the menuentries undo and redo
7978 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
7979 gint redo_state, gpointer data)
7981 GtkWidget *widget = GTK_WIDGET(data);
7982 GtkItemFactory *ifactory;
7984 g_return_if_fail(widget != NULL);
7986 ifactory = gtk_item_factory_from_widget(widget);
7988 switch (undo_state) {
7989 case UNDO_STATE_TRUE:
7990 if (!undostruct->undo_state) {
7991 undostruct->undo_state = TRUE;
7992 menu_set_sensitive(ifactory, "/Edit/Undo", TRUE);
7995 case UNDO_STATE_FALSE:
7996 if (undostruct->undo_state) {
7997 undostruct->undo_state = FALSE;
7998 menu_set_sensitive(ifactory, "/Edit/Undo", FALSE);
8001 case UNDO_STATE_UNCHANGED:
8003 case UNDO_STATE_REFRESH:
8004 menu_set_sensitive(ifactory, "/Edit/Undo",
8005 undostruct->undo_state);
8008 g_warning("Undo state not recognized");
8012 switch (redo_state) {
8013 case UNDO_STATE_TRUE:
8014 if (!undostruct->redo_state) {
8015 undostruct->redo_state = TRUE;
8016 menu_set_sensitive(ifactory, "/Edit/Redo", TRUE);
8019 case UNDO_STATE_FALSE:
8020 if (undostruct->redo_state) {
8021 undostruct->redo_state = FALSE;
8022 menu_set_sensitive(ifactory, "/Edit/Redo", FALSE);
8025 case UNDO_STATE_UNCHANGED:
8027 case UNDO_STATE_REFRESH:
8028 menu_set_sensitive(ifactory, "/Edit/Redo",
8029 undostruct->redo_state);
8032 g_warning("Redo state not recognized");
8037 /* callback functions */
8039 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
8040 * includes "non-client" (windows-izm) in calculation, so this calculation
8041 * may not be accurate.
8043 static gboolean compose_edit_size_alloc(GtkEditable *widget,
8044 GtkAllocation *allocation,
8045 GtkSHRuler *shruler)
8047 if (prefs_common.show_ruler) {
8048 gint char_width = 0, char_height = 0;
8049 gint line_width_in_chars;
8051 gtkut_get_font_size(GTK_WIDGET(widget),
8052 &char_width, &char_height);
8053 line_width_in_chars =
8054 (allocation->width - allocation->x) / char_width;
8056 /* got the maximum */
8057 gtk_ruler_set_range(GTK_RULER(shruler),
8058 0.0, line_width_in_chars, 0,
8059 /*line_width_in_chars*/ char_width);
8065 static void account_activated(GtkComboBox *optmenu, gpointer data)
8067 Compose *compose = (Compose *)data;
8070 gchar *folderidentifier;
8071 gint account_id = 0;
8075 /* Get ID of active account in the combo box */
8076 menu = gtk_combo_box_get_model(optmenu);
8077 gtk_combo_box_get_active_iter(optmenu, &iter);
8078 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
8080 ac = account_find_from_id(account_id);
8081 g_return_if_fail(ac != NULL);
8083 if (ac != compose->account)
8084 compose_select_account(compose, ac, FALSE);
8086 /* Set message save folder */
8087 if (account_get_special_folder(compose->account, F_OUTBOX)) {
8088 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
8090 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
8091 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
8093 gtk_editable_delete_text(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
8094 if (account_get_special_folder(compose->account, F_OUTBOX)) {
8095 folderidentifier = folder_item_get_identifier(account_get_special_folder
8096 (compose->account, F_OUTBOX));
8097 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
8098 g_free(folderidentifier);
8102 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
8103 GtkTreeViewColumn *column, Compose *compose)
8105 compose_attach_property(compose);
8108 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
8111 Compose *compose = (Compose *)data;
8112 GtkTreeSelection *attach_selection;
8113 gint attach_nr_selected;
8114 GtkItemFactory *ifactory;
8116 if (!event) return FALSE;
8118 if (event->button == 3) {
8119 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
8120 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
8121 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
8123 if (attach_nr_selected > 0)
8125 menu_set_sensitive(ifactory, "/Remove", TRUE);
8126 menu_set_sensitive(ifactory, "/Properties...", TRUE);
8128 menu_set_sensitive(ifactory, "/Remove", FALSE);
8129 menu_set_sensitive(ifactory, "/Properties...", FALSE);
8132 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
8133 NULL, NULL, event->button, event->time);
8140 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
8143 Compose *compose = (Compose *)data;
8145 if (!event) return FALSE;
8147 switch (event->keyval) {
8149 compose_attach_remove_selected(compose);
8155 static void compose_allow_user_actions (Compose *compose, gboolean allow)
8157 GtkItemFactory *ifactory = gtk_item_factory_from_widget(compose->menubar);
8158 toolbar_comp_set_sensitive(compose, allow);
8159 menu_set_sensitive(ifactory, "/Message", allow);
8160 menu_set_sensitive(ifactory, "/Edit", allow);
8162 menu_set_sensitive(ifactory, "/Spelling", allow);
8164 menu_set_sensitive(ifactory, "/Options", allow);
8165 menu_set_sensitive(ifactory, "/Tools", allow);
8166 menu_set_sensitive(ifactory, "/Help", allow);
8168 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
8172 static void compose_send_cb(gpointer data, guint action, GtkWidget *widget)
8174 Compose *compose = (Compose *)data;
8176 if (prefs_common.work_offline &&
8177 !inc_offline_should_override(TRUE,
8178 _("Claws Mail needs network access in order "
8179 "to send this email.")))
8182 if (compose->draft_timeout_tag != -1) { /* CLAWS: disable draft timeout */
8183 g_source_remove(compose->draft_timeout_tag);
8184 compose->draft_timeout_tag = -1;
8187 compose_send(compose);
8190 static void compose_send_later_cb(gpointer data, guint action,
8193 Compose *compose = (Compose *)data;
8197 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
8201 compose_close(compose);
8202 } else if (val == -1) {
8203 alertpanel_error(_("Could not queue message."));
8204 } else if (val == -2) {
8205 alertpanel_error(_("Could not queue message:\n\n%s."), strerror(errno));
8206 } else if (val == -3) {
8207 if (privacy_peek_error())
8208 alertpanel_error(_("Could not queue message for sending:\n\n"
8209 "Signature failed: %s"), privacy_get_error());
8210 } else if (val == -4) {
8211 alertpanel_error(_("Could not queue message for sending:\n\n"
8212 "Charset conversion failed."));
8213 } else if (val == -5) {
8214 alertpanel_error(_("Could not queue message for sending:\n\n"
8215 "Couldn't get recipient encryption key."));
8216 } else if (val == -6) {
8219 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
8222 void compose_draft (gpointer data, guint action)
8224 compose_draft_cb(data, action, NULL);
8227 #define DRAFTED_AT_EXIT "drafted_at_exit"
8228 void compose_clear_exit_drafts(void)
8230 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8231 DRAFTED_AT_EXIT, NULL);
8232 if (is_file_exist(filepath))
8238 static void compose_register_draft(MsgInfo *info)
8240 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8241 DRAFTED_AT_EXIT, NULL);
8242 FILE *fp = fopen(filepath, "ab");
8245 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
8253 void compose_reopen_exit_drafts(void)
8255 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8256 DRAFTED_AT_EXIT, NULL);
8257 FILE *fp = fopen(filepath, "rb");
8261 while (fgets(buf, sizeof(buf), fp)) {
8262 gchar **parts = g_strsplit(buf, "\t", 2);
8263 const gchar *folder = parts[0];
8264 int msgnum = parts[1] ? atoi(parts[1]):-1;
8266 if (folder && *folder && msgnum > -1) {
8267 FolderItem *item = folder_find_item_from_identifier(folder);
8268 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
8270 compose_reedit(info, FALSE);
8277 compose_clear_exit_drafts();
8280 static void compose_draft_cb(gpointer data, guint action, GtkWidget *widget)
8282 Compose *compose = (Compose *)data;
8286 MsgFlags flag = {0, 0};
8287 static gboolean lock = FALSE;
8288 MsgInfo *newmsginfo;
8290 gboolean target_locked = FALSE;
8294 draft = account_get_special_folder(compose->account, F_DRAFT);
8295 g_return_if_fail(draft != NULL);
8297 if (!g_mutex_trylock(compose->mutex)) {
8298 /* we don't want to lock the mutex once it's available,
8299 * because as the only other part of compose.c locking
8300 * it is compose_close - which means once unlocked,
8301 * the compose struct will be freed */
8302 debug_print("couldn't lock mutex, probably sending\n");
8308 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
8309 G_DIR_SEPARATOR, compose);
8310 if ((fp = g_fopen(tmp, "wb")) == NULL) {
8311 FILE_OP_ERROR(tmp, "fopen");
8315 /* chmod for security */
8316 if (change_file_mode_rw(fp, tmp) < 0) {
8317 FILE_OP_ERROR(tmp, "chmod");
8318 g_warning("can't change file mode\n");
8321 /* Save draft infos */
8322 fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id);
8323 fprintf(fp, "S:%s\n", compose->account->address);
8325 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
8326 gchar *savefolderid;
8328 savefolderid = gtk_editable_get_chars(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
8329 fprintf(fp, "SCF:%s\n", savefolderid);
8330 g_free(savefolderid);
8332 if (compose->return_receipt) {
8333 fprintf(fp, "RRCPT:1\n");
8335 if (compose->privacy_system) {
8336 fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing);
8337 fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption);
8338 fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system);
8341 /* Message-ID of message replying to */
8342 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
8345 folderid = folder_item_get_identifier(compose->replyinfo->folder);
8346 fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid);
8349 /* Message-ID of message forwarding to */
8350 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
8353 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
8354 fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid);
8358 /* end of headers */
8359 fprintf(fp, "X-Claws-End-Special-Headers: 1\n");
8361 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
8369 if (compose->targetinfo) {
8370 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
8371 flag.perm_flags = target_locked?MSG_LOCKED:0;
8373 flag.tmp_flags = MSG_DRAFT;
8375 folder_item_scan(draft);
8376 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
8379 if (action != COMPOSE_AUTO_SAVE)
8380 alertpanel_error(_("Could not save draft."));
8385 if (compose->mode == COMPOSE_REEDIT) {
8386 compose_remove_reedit_target(compose, TRUE);
8389 newmsginfo = folder_item_get_msginfo(draft, msgnum);
8391 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
8393 procmsg_msginfo_set_flags(newmsginfo, MSG_LOCKED, MSG_DRAFT);
8395 procmsg_msginfo_set_flags(newmsginfo, 0, MSG_DRAFT);
8396 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
8397 procmsg_msginfo_set_flags(newmsginfo, 0,
8398 MSG_HAS_ATTACHMENT);
8400 if (action == COMPOSE_DRAFT_FOR_EXIT) {
8401 compose_register_draft(newmsginfo);
8403 procmsg_msginfo_free(newmsginfo);
8406 folder_item_scan(draft);
8408 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
8410 g_mutex_unlock(compose->mutex); /* must be done before closing */
8411 compose_close(compose);
8417 path = folder_item_fetch_msg(draft, msgnum);
8419 debug_print("can't fetch %s:%d\n",draft->path, msgnum);
8422 if (g_stat(path, &s) < 0) {
8423 FILE_OP_ERROR(path, "stat");
8429 procmsg_msginfo_free(compose->targetinfo);
8430 compose->targetinfo = procmsg_msginfo_new();
8431 compose->targetinfo->msgnum = msgnum;
8432 compose->targetinfo->size = s.st_size;
8433 compose->targetinfo->mtime = s.st_mtime;
8434 compose->targetinfo->folder = draft;
8436 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
8437 compose->mode = COMPOSE_REEDIT;
8439 if (action == COMPOSE_AUTO_SAVE) {
8440 compose->autosaved_draft = compose->targetinfo;
8442 compose->modified = FALSE;
8443 compose_set_title(compose);
8447 g_mutex_unlock(compose->mutex);
8450 static void compose_attach_cb(gpointer data, guint action, GtkWidget *widget)
8452 Compose *compose = (Compose *)data;
8455 if (compose->redirect_filename != NULL)
8458 file_list = filesel_select_multiple_files_open(_("Select file"));
8463 for ( tmp = file_list; tmp; tmp = tmp->next) {
8464 gchar *file = (gchar *) tmp->data;
8465 gchar *utf8_filename = conv_filename_to_utf8(file);
8466 compose_attach_append(compose, file, utf8_filename, NULL);
8467 compose_changed_cb(NULL, compose);
8469 g_free(utf8_filename);
8471 g_list_free(file_list);
8475 static void compose_insert_file_cb(gpointer data, guint action,
8478 Compose *compose = (Compose *)data;
8481 file_list = filesel_select_multiple_files_open(_("Select file"));
8486 for ( tmp = file_list; tmp; tmp = tmp->next) {
8487 gchar *file = (gchar *) tmp->data;
8488 gchar *filedup = g_strdup(file);
8489 gchar *shortfile = g_path_get_basename(filedup);
8490 ComposeInsertResult res;
8492 res = compose_insert_file(compose, file);
8493 if (res == COMPOSE_INSERT_READ_ERROR) {
8494 alertpanel_error(_("File '%s' could not be read."), shortfile);
8495 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
8496 alertpanel_error(_("File '%s' contained invalid characters\n"
8497 "for the current encoding, insertion may be incorrect."), shortfile);
8503 g_list_free(file_list);
8507 static void compose_insert_sig_cb(gpointer data, guint action,
8510 Compose *compose = (Compose *)data;
8512 compose_insert_sig(compose, FALSE);
8515 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
8519 Compose *compose = (Compose *)data;
8521 gtkut_widget_get_uposition(widget, &x, &y);
8522 prefs_common.compose_x = x;
8523 prefs_common.compose_y = y;
8525 if (compose->sending || compose->updating)
8527 compose_close_cb(compose, 0, NULL);
8531 void compose_close_toolbar(Compose *compose)
8533 compose_close_cb(compose, 0, NULL);
8536 static void compose_close_cb(gpointer data, guint action, GtkWidget *widget)
8538 Compose *compose = (Compose *)data;
8542 if (compose->exteditor_tag != -1) {
8543 if (!compose_ext_editor_kill(compose))
8548 if (compose->modified) {
8549 val = alertpanel(_("Discard message"),
8550 _("This message has been modified. Discard it?"),
8551 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
8554 case G_ALERTDEFAULT:
8555 if (prefs_common.autosave)
8556 compose_remove_draft(compose);
8558 case G_ALERTALTERNATE:
8559 compose_draft_cb(data, COMPOSE_QUIT_EDITING, NULL);
8566 compose_close(compose);
8569 static void compose_set_encoding_cb(gpointer data, guint action,
8572 Compose *compose = (Compose *)data;
8574 if (GTK_CHECK_MENU_ITEM(widget)->active)
8575 compose->out_encoding = (CharSet)action;
8578 static void compose_address_cb(gpointer data, guint action, GtkWidget *widget)
8580 Compose *compose = (Compose *)data;
8582 addressbook_open(compose);
8585 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
8587 Compose *compose = (Compose *)data;
8592 tmpl = g_object_get_data(G_OBJECT(widget), "template");
8593 g_return_if_fail(tmpl != NULL);
8595 msg = g_strdup_printf(_("Do you want to apply the template '%s' ?"),
8597 val = alertpanel(_("Apply template"), msg,
8598 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
8601 if (val == G_ALERTDEFAULT)
8602 compose_template_apply(compose, tmpl, TRUE);
8603 else if (val == G_ALERTALTERNATE)
8604 compose_template_apply(compose, tmpl, FALSE);
8607 static void compose_ext_editor_cb(gpointer data, guint action,
8610 Compose *compose = (Compose *)data;
8612 compose_exec_ext_editor(compose);
8615 static void compose_undo_cb(Compose *compose)
8617 gboolean prev_autowrap = compose->autowrap;
8619 compose->autowrap = FALSE;
8620 undo_undo(compose->undostruct);
8621 compose->autowrap = prev_autowrap;
8624 static void compose_redo_cb(Compose *compose)
8626 gboolean prev_autowrap = compose->autowrap;
8628 compose->autowrap = FALSE;
8629 undo_redo(compose->undostruct);
8630 compose->autowrap = prev_autowrap;
8633 static void entry_cut_clipboard(GtkWidget *entry)
8635 if (GTK_IS_EDITABLE(entry))
8636 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
8637 else if (GTK_IS_TEXT_VIEW(entry))
8638 gtk_text_buffer_cut_clipboard(
8639 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
8640 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
8644 static void entry_copy_clipboard(GtkWidget *entry)
8646 if (GTK_IS_EDITABLE(entry))
8647 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
8648 else if (GTK_IS_TEXT_VIEW(entry))
8649 gtk_text_buffer_copy_clipboard(
8650 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
8651 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
8654 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
8655 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
8657 if (GTK_IS_TEXT_VIEW(entry)) {
8658 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
8659 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
8660 GtkTextIter start_iter, end_iter;
8662 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
8664 if (contents == NULL)
8667 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
8669 /* we shouldn't delete the selection when middle-click-pasting, or we
8670 * can't mid-click-paste our own selection */
8671 if (clip != GDK_SELECTION_PRIMARY) {
8672 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
8675 if (insert_place == NULL) {
8676 /* if insert_place isn't specified, insert at the cursor.
8677 * used for Ctrl-V pasting */
8678 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
8679 start = gtk_text_iter_get_offset(&start_iter);
8680 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
8682 /* if insert_place is specified, paste here.
8683 * used for mid-click-pasting */
8684 start = gtk_text_iter_get_offset(insert_place);
8685 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
8689 /* paste unwrapped: mark the paste so it's not wrapped later */
8690 end = start + strlen(contents);
8691 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
8692 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
8693 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
8694 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
8695 /* rewrap paragraph now (after a mid-click-paste) */
8696 mark_start = gtk_text_buffer_get_insert(buffer);
8697 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
8698 gtk_text_iter_backward_char(&start_iter);
8699 compose_beautify_paragraph(compose, &start_iter, TRUE);
8701 } else if (GTK_IS_EDITABLE(entry))
8702 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
8706 static void entry_allsel(GtkWidget *entry)
8708 if (GTK_IS_EDITABLE(entry))
8709 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
8710 else if (GTK_IS_TEXT_VIEW(entry)) {
8711 GtkTextIter startiter, enditer;
8712 GtkTextBuffer *textbuf;
8714 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
8715 gtk_text_buffer_get_start_iter(textbuf, &startiter);
8716 gtk_text_buffer_get_end_iter(textbuf, &enditer);
8718 gtk_text_buffer_move_mark_by_name(textbuf,
8719 "selection_bound", &startiter);
8720 gtk_text_buffer_move_mark_by_name(textbuf,
8721 "insert", &enditer);
8725 static void compose_cut_cb(Compose *compose)
8727 if (compose->focused_editable
8729 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
8732 entry_cut_clipboard(compose->focused_editable);
8735 static void compose_copy_cb(Compose *compose)
8737 if (compose->focused_editable
8739 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
8742 entry_copy_clipboard(compose->focused_editable);
8745 static void compose_paste_cb(Compose *compose)
8748 GtkTextBuffer *buffer;
8750 if (compose->focused_editable &&
8751 GTK_WIDGET_HAS_FOCUS(compose->focused_editable))
8752 entry_paste_clipboard(compose, compose->focused_editable,
8753 prefs_common.linewrap_pastes,
8754 GDK_SELECTION_CLIPBOARD, NULL);
8758 static void compose_paste_as_quote_cb(Compose *compose)
8760 gint wrap_quote = prefs_common.linewrap_quote;
8761 if (compose->focused_editable
8763 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
8766 /* let text_insert() (called directly or at a later time
8767 * after the gtk_editable_paste_clipboard) know that
8768 * text is to be inserted as a quotation. implemented
8769 * by using a simple refcount... */
8770 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
8771 G_OBJECT(compose->focused_editable),
8772 "paste_as_quotation"));
8773 g_object_set_data(G_OBJECT(compose->focused_editable),
8774 "paste_as_quotation",
8775 GINT_TO_POINTER(paste_as_quotation + 1));
8776 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
8777 entry_paste_clipboard(compose, compose->focused_editable,
8778 prefs_common.linewrap_pastes,
8779 GDK_SELECTION_CLIPBOARD, NULL);
8780 prefs_common.linewrap_quote = wrap_quote;
8784 static void compose_paste_no_wrap_cb(Compose *compose)
8787 GtkTextBuffer *buffer;
8789 if (compose->focused_editable
8791 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
8794 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
8795 GDK_SELECTION_CLIPBOARD, NULL);
8799 static void compose_paste_wrap_cb(Compose *compose)
8802 GtkTextBuffer *buffer;
8804 if (compose->focused_editable
8806 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
8809 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
8810 GDK_SELECTION_CLIPBOARD, NULL);
8814 static void compose_allsel_cb(Compose *compose)
8816 if (compose->focused_editable
8818 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
8821 entry_allsel(compose->focused_editable);
8824 static void textview_move_beginning_of_line (GtkTextView *text)
8826 GtkTextBuffer *buffer;
8830 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
8832 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8833 mark = gtk_text_buffer_get_insert(buffer);
8834 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
8835 gtk_text_iter_set_line_offset(&ins, 0);
8836 gtk_text_buffer_place_cursor(buffer, &ins);
8839 static void textview_move_forward_character (GtkTextView *text)
8841 GtkTextBuffer *buffer;
8845 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
8847 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8848 mark = gtk_text_buffer_get_insert(buffer);
8849 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
8850 if (gtk_text_iter_forward_cursor_position(&ins))
8851 gtk_text_buffer_place_cursor(buffer, &ins);
8854 static void textview_move_backward_character (GtkTextView *text)
8856 GtkTextBuffer *buffer;
8860 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
8862 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8863 mark = gtk_text_buffer_get_insert(buffer);
8864 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
8865 if (gtk_text_iter_backward_cursor_position(&ins))
8866 gtk_text_buffer_place_cursor(buffer, &ins);
8869 static void textview_move_forward_word (GtkTextView *text)
8871 GtkTextBuffer *buffer;
8876 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
8878 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8879 mark = gtk_text_buffer_get_insert(buffer);
8880 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
8881 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
8882 if (gtk_text_iter_forward_word_ends(&ins, count)) {
8883 gtk_text_iter_backward_word_start(&ins);
8884 gtk_text_buffer_place_cursor(buffer, &ins);
8888 static void textview_move_backward_word (GtkTextView *text)
8890 GtkTextBuffer *buffer;
8895 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
8897 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8898 mark = gtk_text_buffer_get_insert(buffer);
8899 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
8900 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
8901 if (gtk_text_iter_backward_word_starts(&ins, 1))
8902 gtk_text_buffer_place_cursor(buffer, &ins);
8905 static void textview_move_end_of_line (GtkTextView *text)
8907 GtkTextBuffer *buffer;
8911 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
8913 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8914 mark = gtk_text_buffer_get_insert(buffer);
8915 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
8916 if (gtk_text_iter_forward_to_line_end(&ins))
8917 gtk_text_buffer_place_cursor(buffer, &ins);
8920 static void textview_move_next_line (GtkTextView *text)
8922 GtkTextBuffer *buffer;
8927 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
8929 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8930 mark = gtk_text_buffer_get_insert(buffer);
8931 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
8932 offset = gtk_text_iter_get_line_offset(&ins);
8933 if (gtk_text_iter_forward_line(&ins)) {
8934 gtk_text_iter_set_line_offset(&ins, offset);
8935 gtk_text_buffer_place_cursor(buffer, &ins);
8939 static void textview_move_previous_line (GtkTextView *text)
8941 GtkTextBuffer *buffer;
8946 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
8948 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8949 mark = gtk_text_buffer_get_insert(buffer);
8950 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
8951 offset = gtk_text_iter_get_line_offset(&ins);
8952 if (gtk_text_iter_backward_line(&ins)) {
8953 gtk_text_iter_set_line_offset(&ins, offset);
8954 gtk_text_buffer_place_cursor(buffer, &ins);
8958 static void textview_delete_forward_character (GtkTextView *text)
8960 GtkTextBuffer *buffer;
8962 GtkTextIter ins, end_iter;
8964 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
8966 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8967 mark = gtk_text_buffer_get_insert(buffer);
8968 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
8970 if (gtk_text_iter_forward_char(&end_iter)) {
8971 gtk_text_buffer_delete(buffer, &ins, &end_iter);
8975 static void textview_delete_backward_character (GtkTextView *text)
8977 GtkTextBuffer *buffer;
8979 GtkTextIter ins, end_iter;
8981 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
8983 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8984 mark = gtk_text_buffer_get_insert(buffer);
8985 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
8987 if (gtk_text_iter_backward_char(&end_iter)) {
8988 gtk_text_buffer_delete(buffer, &end_iter, &ins);
8992 static void textview_delete_forward_word (GtkTextView *text)
8994 GtkTextBuffer *buffer;
8996 GtkTextIter ins, end_iter;
8998 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9000 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9001 mark = gtk_text_buffer_get_insert(buffer);
9002 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9004 if (gtk_text_iter_forward_word_end(&end_iter)) {
9005 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9009 static void textview_delete_backward_word (GtkTextView *text)
9011 GtkTextBuffer *buffer;
9013 GtkTextIter ins, end_iter;
9015 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9017 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9018 mark = gtk_text_buffer_get_insert(buffer);
9019 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9021 if (gtk_text_iter_backward_word_start(&end_iter)) {
9022 gtk_text_buffer_delete(buffer, &end_iter, &ins);
9026 static void textview_delete_line (GtkTextView *text)
9028 GtkTextBuffer *buffer;
9030 GtkTextIter ins, start_iter, end_iter;
9033 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9035 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9036 mark = gtk_text_buffer_get_insert(buffer);
9037 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9040 gtk_text_iter_set_line_offset(&start_iter, 0);
9043 if (gtk_text_iter_ends_line(&end_iter))
9044 found = gtk_text_iter_forward_char(&end_iter);
9046 found = gtk_text_iter_forward_to_line_end(&end_iter);
9049 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
9052 static void textview_delete_to_line_end (GtkTextView *text)
9054 GtkTextBuffer *buffer;
9056 GtkTextIter ins, end_iter;
9059 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9061 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9062 mark = gtk_text_buffer_get_insert(buffer);
9063 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9065 if (gtk_text_iter_ends_line(&end_iter))
9066 found = gtk_text_iter_forward_char(&end_iter);
9068 found = gtk_text_iter_forward_to_line_end(&end_iter);
9070 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9073 static void compose_advanced_action_cb(Compose *compose,
9074 ComposeCallAdvancedAction action)
9076 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9078 void (*do_action) (GtkTextView *text);
9079 } action_table[] = {
9080 {textview_move_beginning_of_line},
9081 {textview_move_forward_character},
9082 {textview_move_backward_character},
9083 {textview_move_forward_word},
9084 {textview_move_backward_word},
9085 {textview_move_end_of_line},
9086 {textview_move_next_line},
9087 {textview_move_previous_line},
9088 {textview_delete_forward_character},
9089 {textview_delete_backward_character},
9090 {textview_delete_forward_word},
9091 {textview_delete_backward_word},
9092 {textview_delete_line},
9093 {NULL}, /* gtk_stext_delete_line_n */
9094 {textview_delete_to_line_end}
9097 if (!GTK_WIDGET_HAS_FOCUS(text)) return;
9099 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
9100 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
9101 if (action_table[action].do_action)
9102 action_table[action].do_action(text);
9104 g_warning("Not implemented yet.");
9108 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
9112 if (GTK_IS_EDITABLE(widget)) {
9113 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
9114 gtk_editable_set_position(GTK_EDITABLE(widget),
9117 if (widget->parent && widget->parent->parent
9118 && widget->parent->parent->parent) {
9119 if (GTK_IS_SCROLLED_WINDOW(widget->parent->parent->parent)) {
9120 gint y = widget->allocation.y;
9121 gint height = widget->allocation.height;
9122 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
9123 (GTK_SCROLLED_WINDOW(widget->parent->parent->parent));
9125 if (y < (int)shown->value) {
9126 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown), y - 1);
9128 if (y + height > (int)shown->value + (int)shown->page_size) {
9129 if (y - height - 1 < (int)shown->upper - (int)shown->page_size) {
9130 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
9131 y + height - (int)shown->page_size - 1);
9133 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
9134 (int)shown->upper - (int)shown->page_size - 1);
9141 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
9142 compose->focused_editable = widget;
9145 if (GTK_IS_TEXT_VIEW(widget)
9146 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
9147 gtk_widget_ref(compose->notebook);
9148 gtk_widget_ref(compose->edit_vbox);
9149 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
9150 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
9151 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
9152 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
9153 gtk_widget_unref(compose->notebook);
9154 gtk_widget_unref(compose->edit_vbox);
9155 g_signal_handlers_block_by_func(G_OBJECT(widget),
9156 G_CALLBACK(compose_grab_focus_cb),
9158 gtk_widget_grab_focus(widget);
9159 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
9160 G_CALLBACK(compose_grab_focus_cb),
9162 } else if (!GTK_IS_TEXT_VIEW(widget)
9163 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
9164 gtk_widget_ref(compose->notebook);
9165 gtk_widget_ref(compose->edit_vbox);
9166 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
9167 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
9168 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
9169 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
9170 gtk_widget_unref(compose->notebook);
9171 gtk_widget_unref(compose->edit_vbox);
9172 g_signal_handlers_block_by_func(G_OBJECT(widget),
9173 G_CALLBACK(compose_grab_focus_cb),
9175 gtk_widget_grab_focus(widget);
9176 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
9177 G_CALLBACK(compose_grab_focus_cb),
9183 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
9185 compose->modified = TRUE;
9187 compose_set_title(compose);
9191 static void compose_wrap_cb(gpointer data, guint action, GtkWidget *widget)
9193 Compose *compose = (Compose *)data;
9196 compose_wrap_all_full(compose, TRUE);
9198 compose_beautify_paragraph(compose, NULL, TRUE);
9201 static void compose_find_cb(gpointer data, guint action, GtkWidget *widget)
9203 Compose *compose = (Compose *)data;
9205 message_search_compose(compose);
9208 static void compose_toggle_autowrap_cb(gpointer data, guint action,
9211 Compose *compose = (Compose *)data;
9212 compose->autowrap = GTK_CHECK_MENU_ITEM(widget)->active;
9213 if (compose->autowrap)
9214 compose_wrap_all_full(compose, TRUE);
9215 compose->autowrap = GTK_CHECK_MENU_ITEM(widget)->active;
9218 static void compose_toggle_sign_cb(gpointer data, guint action,
9221 Compose *compose = (Compose *)data;
9223 if (GTK_CHECK_MENU_ITEM(widget)->active)
9224 compose->use_signing = TRUE;
9226 compose->use_signing = FALSE;
9229 static void compose_toggle_encrypt_cb(gpointer data, guint action,
9232 Compose *compose = (Compose *)data;
9234 if (GTK_CHECK_MENU_ITEM(widget)->active)
9235 compose->use_encryption = TRUE;
9237 compose->use_encryption = FALSE;
9240 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
9242 g_free(compose->privacy_system);
9244 compose->privacy_system = g_strdup(account->default_privacy_system);
9245 compose_update_privacy_system_menu_item(compose, warn);
9248 static void compose_toggle_ruler_cb(gpointer data, guint action,
9251 Compose *compose = (Compose *)data;
9253 if (GTK_CHECK_MENU_ITEM(widget)->active) {
9254 gtk_widget_show(compose->ruler_hbox);
9255 prefs_common.show_ruler = TRUE;
9257 gtk_widget_hide(compose->ruler_hbox);
9258 gtk_widget_queue_resize(compose->edit_vbox);
9259 prefs_common.show_ruler = FALSE;
9263 static void compose_attach_drag_received_cb (GtkWidget *widget,
9264 GdkDragContext *context,
9267 GtkSelectionData *data,
9272 Compose *compose = (Compose *)user_data;
9275 if (gdk_atom_name(data->type) &&
9276 !strcmp(gdk_atom_name(data->type), "text/uri-list")
9277 && gtk_drag_get_source_widget(context) !=
9278 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
9279 list = uri_list_extract_filenames((const gchar *)data->data);
9280 for (tmp = list; tmp != NULL; tmp = tmp->next) {
9281 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
9282 compose_attach_append
9283 (compose, (const gchar *)tmp->data,
9284 utf8_filename, NULL);
9285 g_free(utf8_filename);
9287 if (list) compose_changed_cb(NULL, compose);
9288 list_free_strings(list);
9290 } else if (gtk_drag_get_source_widget(context)
9291 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
9292 /* comes from our summaryview */
9293 SummaryView * summaryview = NULL;
9294 GSList * list = NULL, *cur = NULL;
9296 if (mainwindow_get_mainwindow())
9297 summaryview = mainwindow_get_mainwindow()->summaryview;
9300 list = summary_get_selected_msg_list(summaryview);
9302 for (cur = list; cur; cur = cur->next) {
9303 MsgInfo *msginfo = (MsgInfo *)cur->data;
9306 file = procmsg_get_message_file_full(msginfo,
9309 compose_attach_append(compose, (const gchar *)file,
9310 (const gchar *)file, "message/rfc822");
9318 static gboolean compose_drag_drop(GtkWidget *widget,
9319 GdkDragContext *drag_context,
9321 guint time, gpointer user_data)
9323 /* not handling this signal makes compose_insert_drag_received_cb
9328 static void compose_insert_drag_received_cb (GtkWidget *widget,
9329 GdkDragContext *drag_context,
9332 GtkSelectionData *data,
9337 Compose *compose = (Compose *)user_data;
9340 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
9342 if (gdk_atom_name(data->type) && !strcmp(gdk_atom_name(data->type), "text/uri-list")) {
9343 AlertValue val = G_ALERTDEFAULT;
9345 switch (prefs_common.compose_dnd_mode) {
9346 case COMPOSE_DND_ASK:
9347 val = alertpanel_full(_("Insert or attach?"),
9348 _("Do you want to insert the contents of the file(s) "
9349 "into the message body, or attach it to the email?"),
9350 GTK_STOCK_CANCEL, _("+_Insert"), _("_Attach"),
9351 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
9353 case COMPOSE_DND_INSERT:
9354 val = G_ALERTALTERNATE;
9356 case COMPOSE_DND_ATTACH:
9360 /* unexpected case */
9361 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
9364 if (val & G_ALERTDISABLE) {
9365 val &= ~G_ALERTDISABLE;
9366 /* remember what action to perform by default, only if we don't click Cancel */
9367 if (val == G_ALERTALTERNATE)
9368 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
9369 else if (val == G_ALERTOTHER)
9370 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
9373 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
9374 gtk_drag_finish(drag_context, FALSE, FALSE, time);
9376 } else if (val == G_ALERTOTHER) {
9377 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
9380 list = uri_list_extract_filenames((const gchar *)data->data);
9381 for (tmp = list; tmp != NULL; tmp = tmp->next) {
9382 compose_insert_file(compose, (const gchar *)tmp->data);
9384 list_free_strings(list);
9386 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9389 #if GTK_CHECK_VERSION(2, 8, 0)
9390 /* do nothing, handled by GTK */
9392 gchar *tmpfile = get_tmp_file();
9393 str_write_to_file((const gchar *)data->data, tmpfile);
9394 compose_insert_file(compose, tmpfile);
9397 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9401 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9404 static void compose_header_drag_received_cb (GtkWidget *widget,
9405 GdkDragContext *drag_context,
9408 GtkSelectionData *data,
9413 GtkEditable *entry = (GtkEditable *)user_data;
9414 gchar *email = (gchar *)data->data;
9416 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
9419 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
9420 gchar *decoded=g_new(gchar, strlen(email));
9423 email += strlen("mailto:");
9424 decode_uri(decoded, email); /* will fit */
9425 gtk_editable_delete_text(entry, 0, -1);
9426 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
9427 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9431 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9434 static void compose_toggle_return_receipt_cb(gpointer data, guint action,
9437 Compose *compose = (Compose *)data;
9439 if (GTK_CHECK_MENU_ITEM(widget)->active)
9440 compose->return_receipt = TRUE;
9442 compose->return_receipt = FALSE;
9445 static void compose_toggle_remove_refs_cb(gpointer data, guint action,
9448 Compose *compose = (Compose *)data;
9450 if (GTK_CHECK_MENU_ITEM(widget)->active)
9451 compose->remove_references = TRUE;
9453 compose->remove_references = FALSE;
9456 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
9458 ComposeHeaderEntry *headerentry)
9460 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
9461 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
9462 !(event->state & GDK_MODIFIER_MASK) &&
9463 (event->keyval == GDK_BackSpace) &&
9464 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
9465 gtk_container_remove
9466 (GTK_CONTAINER(headerentry->compose->header_table),
9467 headerentry->combo);
9468 gtk_container_remove
9469 (GTK_CONTAINER(headerentry->compose->header_table),
9470 headerentry->entry);
9471 headerentry->compose->header_list =
9472 g_slist_remove(headerentry->compose->header_list,
9474 g_free(headerentry);
9475 } else if (event->keyval == GDK_Tab) {
9476 if (headerentry->compose->header_last == headerentry) {
9477 /* Override default next focus, and give it to subject_entry
9478 * instead of notebook tabs
9480 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
9481 gtk_widget_grab_focus(headerentry->compose->subject_entry);
9488 static gboolean compose_headerentry_changed_cb(GtkWidget *entry,
9489 ComposeHeaderEntry *headerentry)
9491 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
9492 compose_create_header_entry(headerentry->compose);
9493 g_signal_handlers_disconnect_matched
9494 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
9495 0, 0, NULL, NULL, headerentry);
9497 /* Automatically scroll down */
9498 compose_show_first_last_header(headerentry->compose, FALSE);
9504 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
9506 GtkAdjustment *vadj;
9508 g_return_if_fail(compose);
9509 g_return_if_fail(GTK_IS_WIDGET(compose->header_table));
9510 g_return_if_fail(GTK_IS_VIEWPORT(compose->header_table->parent));
9512 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(compose->header_table->parent));
9513 gtk_adjustment_set_value(vadj, (show_first ? vadj->lower : vadj->upper));
9516 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
9517 const gchar *text, gint len, Compose *compose)
9519 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
9520 (G_OBJECT(compose->text), "paste_as_quotation"));
9523 g_return_if_fail(text != NULL);
9525 g_signal_handlers_block_by_func(G_OBJECT(buffer),
9526 G_CALLBACK(text_inserted),
9528 if (paste_as_quotation) {
9535 new_text = g_strndup(text, len);
9536 if (prefs_common.quotemark && *prefs_common.quotemark)
9537 qmark = prefs_common.quotemark;
9541 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
9542 gtk_text_buffer_place_cursor(buffer, iter);
9544 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
9545 _("Quote format error at line %d."));
9546 quote_fmt_reset_vartable();
9548 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
9549 GINT_TO_POINTER(paste_as_quotation - 1));
9551 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
9552 gtk_text_buffer_place_cursor(buffer, iter);
9554 if (strcmp(text, "\n") || automatic_break
9555 || gtk_text_iter_starts_line(iter))
9556 gtk_text_buffer_insert(buffer, iter, text, len);
9558 debug_print("insert nowrap \\n\n");
9559 gtk_text_buffer_insert_with_tags_by_name(buffer,
9560 iter, text, len, "no_join", NULL);
9564 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
9566 compose_beautify_paragraph(compose, iter, FALSE);
9568 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
9569 gtk_text_buffer_delete_mark(buffer, mark);
9571 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
9572 G_CALLBACK(text_inserted),
9574 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
9576 if (prefs_common.autosave &&
9577 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0)
9578 compose->draft_timeout_tag = g_timeout_add
9579 (500, (GtkFunction) compose_defer_auto_save_draft, compose);
9581 static gint compose_defer_auto_save_draft(Compose *compose)
9583 compose->draft_timeout_tag = -1;
9584 compose_draft_cb((gpointer)compose, COMPOSE_AUTO_SAVE, NULL);
9589 static void compose_check_all(Compose *compose)
9591 if (compose->gtkaspell)
9592 gtkaspell_check_all(compose->gtkaspell);
9595 static void compose_highlight_all(Compose *compose)
9597 if (compose->gtkaspell)
9598 gtkaspell_highlight_all(compose->gtkaspell);
9601 static void compose_check_backwards(Compose *compose)
9603 if (compose->gtkaspell)
9604 gtkaspell_check_backwards(compose->gtkaspell);
9606 GtkItemFactory *ifactory;
9607 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
9608 menu_set_sensitive(ifactory, "/Edit/Check backwards misspelled word", FALSE);
9609 menu_set_sensitive(ifactory, "/Edit/Forward to next misspelled word", FALSE);
9613 static void compose_check_forwards_go(Compose *compose)
9615 if (compose->gtkaspell)
9616 gtkaspell_check_forwards_go(compose->gtkaspell);
9618 GtkItemFactory *ifactory;
9619 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
9620 menu_set_sensitive(ifactory, "/Edit/Check backwards misspelled word", FALSE);
9621 menu_set_sensitive(ifactory, "/Edit/Forward to next misspelled word", FALSE);
9627 *\brief Guess originating forward account from MsgInfo and several
9628 * "common preference" settings. Return NULL if no guess.
9630 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
9632 PrefsAccount *account = NULL;
9634 g_return_val_if_fail(msginfo, NULL);
9635 g_return_val_if_fail(msginfo->folder, NULL);
9636 g_return_val_if_fail(msginfo->folder->prefs, NULL);
9638 if (msginfo->folder->prefs->enable_default_account)
9639 account = account_find_from_id(msginfo->folder->prefs->default_account);
9642 account = msginfo->folder->folder->account;
9644 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
9646 Xstrdup_a(to, msginfo->to, return NULL);
9647 extract_address(to);
9648 account = account_find_from_address(to);
9651 if (!account && prefs_common.forward_account_autosel) {
9653 if (!procheader_get_header_from_msginfo
9654 (msginfo, cc,sizeof cc , "Cc:")) {
9655 gchar *buf = cc + strlen("Cc:");
9656 extract_address(buf);
9657 account = account_find_from_address(buf);
9661 if (!account && prefs_common.forward_account_autosel) {
9662 gchar deliveredto[BUFFSIZE];
9663 if (!procheader_get_header_from_msginfo
9664 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
9665 gchar *buf = deliveredto + strlen("Delivered-To:");
9666 extract_address(buf);
9667 account = account_find_from_address(buf);
9674 gboolean compose_close(Compose *compose)
9678 if (!g_mutex_trylock(compose->mutex)) {
9679 /* we have to wait for the (possibly deferred by auto-save)
9680 * drafting to be done, before destroying the compose under
9682 debug_print("waiting for drafting to finish...\n");
9683 g_timeout_add (500, (GSourceFunc) compose_close, compose);
9686 g_return_val_if_fail(compose, FALSE);
9687 gtkut_widget_get_uposition(compose->window, &x, &y);
9688 prefs_common.compose_x = x;
9689 prefs_common.compose_y = y;
9690 g_mutex_unlock(compose->mutex);
9691 compose_destroy(compose);
9696 * Add entry field for each address in list.
9697 * \param compose E-Mail composition object.
9698 * \param listAddress List of (formatted) E-Mail addresses.
9700 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
9705 addr = ( gchar * ) node->data;
9706 compose_entry_append( compose, addr, COMPOSE_TO );
9707 node = g_list_next( node );
9711 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
9712 guint action, gboolean opening_multiple)
9715 GSList *new_msglist = NULL;
9716 MsgInfo *tmp_msginfo = NULL;
9717 gboolean originally_enc = FALSE;
9718 Compose *compose = NULL;
9720 g_return_if_fail(msgview != NULL);
9722 g_return_if_fail(msginfo_list != NULL);
9724 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
9725 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
9726 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
9728 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
9729 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
9730 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
9731 orig_msginfo, mimeinfo);
9732 if (tmp_msginfo != NULL) {
9733 new_msglist = g_slist_append(NULL, tmp_msginfo);
9734 if (procmime_msginfo_is_encrypted(orig_msginfo)) {
9735 originally_enc = TRUE;
9737 tmp_msginfo->folder = orig_msginfo->folder;
9738 tmp_msginfo->msgnum = orig_msginfo->msgnum;
9743 if (!opening_multiple)
9744 body = messageview_get_selection(msgview);
9747 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
9748 procmsg_msginfo_free(tmp_msginfo);
9749 g_slist_free(new_msglist);
9751 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
9753 if (originally_enc) {
9754 compose_force_encryption(compose, compose->account, FALSE);
9760 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
9763 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
9764 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
9765 GSList *cur = msginfo_list;
9766 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
9767 "messages. Opening the windows "
9768 "could take some time. Do you "
9769 "want to continue?"),
9770 g_slist_length(msginfo_list));
9771 if (g_slist_length(msginfo_list) > 9
9772 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
9773 != G_ALERTALTERNATE) {
9778 /* We'll open multiple compose windows */
9779 /* let the WM place the next windows */
9780 compose_force_window_origin = FALSE;
9781 for (; cur; cur = cur->next) {
9783 tmplist.data = cur->data;
9784 tmplist.next = NULL;
9785 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
9787 compose_force_window_origin = TRUE;
9789 /* forwarding multiple mails as attachments is done via a
9790 * single compose window */
9791 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
9795 void compose_set_position(Compose *compose, gint pos)
9797 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9799 gtkut_text_view_set_position(text, pos);
9802 gboolean compose_search_string(Compose *compose,
9803 const gchar *str, gboolean case_sens)
9805 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9807 return gtkut_text_view_search_string(text, str, case_sens);
9810 gboolean compose_search_string_backward(Compose *compose,
9811 const gchar *str, gboolean case_sens)
9813 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9815 return gtkut_text_view_search_string_backward(text, str, case_sens);
9818 /* allocate a msginfo structure and populate its data from a compose data structure */
9819 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
9821 MsgInfo *newmsginfo;
9823 gchar buf[BUFFSIZE];
9825 g_return_val_if_fail( compose != NULL, NULL );
9827 newmsginfo = procmsg_msginfo_new();
9830 get_rfc822_date(buf, sizeof(buf));
9831 newmsginfo->date = g_strdup(buf);
9834 if (compose->from_name) {
9835 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
9836 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
9840 if (compose->subject_entry)
9841 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
9843 /* to, cc, reply-to, newsgroups */
9844 for (list = compose->header_list; list; list = list->next) {
9845 gchar *header = gtk_editable_get_chars(
9847 GTK_COMBO(((ComposeHeaderEntry *)list->data)->combo)->entry), 0, -1);
9848 gchar *entry = gtk_editable_get_chars(
9849 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
9851 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
9852 if ( newmsginfo->to == NULL ) {
9853 newmsginfo->to = g_strdup(entry);
9854 } else if (entry && *entry) {
9855 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
9856 g_free(newmsginfo->to);
9857 newmsginfo->to = tmp;
9860 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
9861 if ( newmsginfo->cc == NULL ) {
9862 newmsginfo->cc = g_strdup(entry);
9863 } else if (entry && *entry) {
9864 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
9865 g_free(newmsginfo->cc);
9866 newmsginfo->cc = tmp;
9869 if ( strcasecmp(header,
9870 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
9871 if ( newmsginfo->newsgroups == NULL ) {
9872 newmsginfo->newsgroups = g_strdup(entry);
9873 } else if (entry && *entry) {
9874 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
9875 g_free(newmsginfo->newsgroups);
9876 newmsginfo->newsgroups = tmp;
9884 /* other data is unset */
9890 /* update compose's dictionaries from folder dict settings */
9891 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
9892 FolderItem *folder_item)
9894 g_return_if_fail(compose != NULL);
9896 if (compose->gtkaspell && folder_item && folder_item->prefs) {
9897 FolderItemPrefs *prefs = folder_item->prefs;
9899 if (prefs->enable_default_dictionary)
9900 gtkaspell_change_dict(compose->gtkaspell,
9901 prefs->default_dictionary, FALSE);
9902 if (folder_item->prefs->enable_default_alt_dictionary)
9903 gtkaspell_change_alt_dict(compose->gtkaspell,
9904 prefs->default_alt_dictionary);
9905 if (prefs->enable_default_dictionary
9906 || prefs->enable_default_alt_dictionary)
9907 compose_spell_menu_changed(compose);