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 && !*qmark);
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_path(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 && !*qmark)
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_path((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_path(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 partname = procmime_mimeinfo_get_parameter(child, "filename");
3405 if (partname == NULL)
3406 partname = procmime_mimeinfo_get_parameter(child, "name");
3407 if (partname == NULL)
3409 compose_attach_append(compose, outfile,
3410 partname, content_type);
3412 compose_force_signing(compose, compose->account);
3414 g_free(content_type);
3417 NEXT_PART_NOT_CHILD(child);
3419 procmime_mimeinfo_free_all(mimeinfo);
3422 #undef NEXT_PART_NOT_CHILD
3427 WAIT_FOR_INDENT_CHAR,
3428 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3431 /* return indent length, we allow:
3432 indent characters followed by indent characters or spaces/tabs,
3433 alphabets and numbers immediately followed by indent characters,
3434 and the repeating sequences of the above
3435 If quote ends with multiple spaces, only the first one is included. */
3436 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3437 const GtkTextIter *start, gint *len)
3439 GtkTextIter iter = *start;
3443 IndentState state = WAIT_FOR_INDENT_CHAR;
3446 gint alnum_count = 0;
3447 gint space_count = 0;
3450 if (prefs_common.quote_chars == NULL) {
3454 while (!gtk_text_iter_ends_line(&iter)) {
3455 wc = gtk_text_iter_get_char(&iter);
3456 if (g_unichar_iswide(wc))
3458 clen = g_unichar_to_utf8(wc, ch);
3462 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
3463 is_space = g_unichar_isspace(wc);
3465 if (state == WAIT_FOR_INDENT_CHAR) {
3466 if (!is_indent && !g_unichar_isalnum(wc))
3469 quote_len += alnum_count + space_count + 1;
3470 alnum_count = space_count = 0;
3471 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
3474 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
3475 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
3479 else if (is_indent) {
3480 quote_len += alnum_count + space_count + 1;
3481 alnum_count = space_count = 0;
3484 state = WAIT_FOR_INDENT_CHAR;
3488 gtk_text_iter_forward_char(&iter);
3491 if (quote_len > 0 && space_count > 0)
3497 if (quote_len > 0) {
3499 gtk_text_iter_forward_chars(&iter, quote_len);
3500 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
3506 /* return TRUE if the line is itemized */
3507 static gboolean compose_is_itemized(GtkTextBuffer *buffer,
3508 const GtkTextIter *start)
3510 GtkTextIter iter = *start;
3515 if (gtk_text_iter_ends_line(&iter))
3519 wc = gtk_text_iter_get_char(&iter);
3520 if (!g_unichar_isspace(wc))
3522 gtk_text_iter_forward_char(&iter);
3523 if (gtk_text_iter_ends_line(&iter))
3527 clen = g_unichar_to_utf8(wc, ch);
3531 if (!strchr("*-+", ch[0]))
3534 gtk_text_iter_forward_char(&iter);
3535 if (gtk_text_iter_ends_line(&iter))
3537 wc = gtk_text_iter_get_char(&iter);
3538 if (g_unichar_isspace(wc))
3544 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
3545 const GtkTextIter *start,
3546 GtkTextIter *break_pos,
3550 GtkTextIter iter = *start, line_end = *start;
3551 PangoLogAttr *attrs;
3558 gboolean can_break = FALSE;
3559 gboolean do_break = FALSE;
3560 gboolean was_white = FALSE;
3561 gboolean prev_dont_break = FALSE;
3563 gtk_text_iter_forward_to_line_end(&line_end);
3564 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
3565 len = g_utf8_strlen(str, -1);
3566 /* g_print("breaking line: %d: %s (len = %d)\n",
3567 gtk_text_iter_get_line(&iter), str, len); */
3568 attrs = g_new(PangoLogAttr, len + 1);
3570 pango_default_break(str, -1, NULL, attrs, len + 1);
3574 /* skip quote and leading spaces */
3575 for (i = 0; *p != '\0' && i < len; i++) {
3578 wc = g_utf8_get_char(p);
3579 if (i >= quote_len && !g_unichar_isspace(wc))
3581 if (g_unichar_iswide(wc))
3583 else if (*p == '\t')
3587 p = g_utf8_next_char(p);
3590 for (; *p != '\0' && i < len; i++) {
3591 PangoLogAttr *attr = attrs + i;
3595 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
3598 was_white = attr->is_white;
3600 /* don't wrap URI */
3601 if ((uri_len = get_uri_len(p)) > 0) {
3603 if (pos > 0 && col > max_col) {
3613 wc = g_utf8_get_char(p);
3614 if (g_unichar_iswide(wc)) {
3616 if (prev_dont_break && can_break && attr->is_line_break)
3618 } else if (*p == '\t')
3622 if (pos > 0 && col > max_col) {
3627 if (*p == '-' || *p == '/')
3628 prev_dont_break = TRUE;
3630 prev_dont_break = FALSE;
3632 p = g_utf8_next_char(p);
3636 debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col);
3641 *break_pos = *start;
3642 gtk_text_iter_set_line_offset(break_pos, pos);
3647 static gboolean compose_join_next_line(Compose *compose,
3648 GtkTextBuffer *buffer,
3650 const gchar *quote_str)
3652 GtkTextIter iter_ = *iter, cur, prev, next, end;
3653 PangoLogAttr attrs[3];
3655 gchar *next_quote_str;
3658 gboolean keep_cursor = FALSE;
3660 if (!gtk_text_iter_forward_line(&iter_) ||
3661 gtk_text_iter_ends_line(&iter_))
3664 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
3666 if ((quote_str || next_quote_str) &&
3667 strcmp2(quote_str, next_quote_str) != 0) {
3668 g_free(next_quote_str);
3671 g_free(next_quote_str);
3674 if (quote_len > 0) {
3675 gtk_text_iter_forward_chars(&end, quote_len);
3676 if (gtk_text_iter_ends_line(&end))
3680 /* don't join itemized lines */
3681 if (compose_is_itemized(buffer, &end))
3684 /* don't join signature separator */
3685 if (compose_is_sig_separator(compose, buffer, &iter_))
3688 /* delete quote str */
3690 gtk_text_buffer_delete(buffer, &iter_, &end);
3692 /* don't join line breaks put by the user */
3694 gtk_text_iter_backward_char(&cur);
3695 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
3696 gtk_text_iter_forward_char(&cur);
3700 gtk_text_iter_forward_char(&cur);
3701 /* delete linebreak and extra spaces */
3702 while (gtk_text_iter_backward_char(&cur)) {
3703 wc1 = gtk_text_iter_get_char(&cur);
3704 if (!g_unichar_isspace(wc1))
3709 while (!gtk_text_iter_ends_line(&cur)) {
3710 wc1 = gtk_text_iter_get_char(&cur);
3711 if (!g_unichar_isspace(wc1))
3713 gtk_text_iter_forward_char(&cur);
3716 if (!gtk_text_iter_equal(&prev, &next)) {
3719 mark = gtk_text_buffer_get_insert(buffer);
3720 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
3721 if (gtk_text_iter_equal(&prev, &cur))
3723 gtk_text_buffer_delete(buffer, &prev, &next);
3727 /* insert space if required */
3728 gtk_text_iter_backward_char(&prev);
3729 wc1 = gtk_text_iter_get_char(&prev);
3730 wc2 = gtk_text_iter_get_char(&next);
3731 gtk_text_iter_forward_char(&next);
3732 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
3733 pango_default_break(str, -1, NULL, attrs, 3);
3734 if (!attrs[1].is_line_break ||
3735 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
3736 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
3738 gtk_text_iter_backward_char(&iter_);
3739 gtk_text_buffer_place_cursor(buffer, &iter_);
3748 #define ADD_TXT_POS(bp_, ep_, pti_) \
3749 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
3750 last = last->next; \
3751 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
3752 last->next = NULL; \
3754 g_warning("alloc error scanning URIs\n"); \
3757 static gboolean automatic_break = FALSE;
3758 static void compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
3760 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3761 GtkTextBuffer *buffer;
3762 GtkTextIter iter, break_pos, end_of_line;
3763 gchar *quote_str = NULL;
3765 gboolean wrap_quote = prefs_common.linewrap_quote;
3766 gboolean prev_autowrap = compose->autowrap;
3767 gint startq_offset = -1, noq_offset = -1;
3768 gint uri_start = -1, uri_stop = -1;
3769 gint nouri_start = -1, nouri_stop = -1;
3770 gint num_blocks = 0;
3771 gint quotelevel = -1;
3773 compose->autowrap = FALSE;
3775 buffer = gtk_text_view_get_buffer(text);
3776 undo_wrapping(compose->undostruct, TRUE);
3781 mark = gtk_text_buffer_get_insert(buffer);
3782 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3785 /* move to paragraph start */
3786 gtk_text_iter_set_line_offset(&iter, 0);
3787 if (gtk_text_iter_ends_line(&iter)) {
3788 while (gtk_text_iter_ends_line(&iter) &&
3789 gtk_text_iter_forward_line(&iter))
3792 while (gtk_text_iter_backward_line(&iter)) {
3793 if (gtk_text_iter_ends_line(&iter)) {
3794 gtk_text_iter_forward_line(&iter);
3800 /* go until paragraph end (empty line) */
3802 while (!gtk_text_iter_ends_line(&iter)) {
3803 gchar *scanpos = NULL;
3804 /* parse table - in order of priority */
3806 const gchar *needle; /* token */
3808 /* token search function */
3809 gchar *(*search) (const gchar *haystack,
3810 const gchar *needle);
3811 /* part parsing function */
3812 gboolean (*parse) (const gchar *start,
3813 const gchar *scanpos,
3817 /* part to URI function */
3818 gchar *(*build_uri) (const gchar *bp,
3822 static struct table parser[] = {
3823 {"http://", strcasestr, get_uri_part, make_uri_string},
3824 {"https://", strcasestr, get_uri_part, make_uri_string},
3825 {"ftp://", strcasestr, get_uri_part, make_uri_string},
3826 {"sftp://", strcasestr, get_uri_part, make_uri_string},
3827 {"www.", strcasestr, get_uri_part, make_http_string},
3828 {"mailto:", strcasestr, get_uri_part, make_uri_string},
3829 {"@", strcasestr, get_email_part, make_email_string}
3831 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
3832 gint last_index = PARSE_ELEMS;
3834 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
3837 if (!prev_autowrap && num_blocks == 0) {
3839 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3840 G_CALLBACK(text_inserted),
3843 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
3846 uri_start = uri_stop = -1;
3848 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
3851 debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str);
3852 if (startq_offset == -1)
3853 startq_offset = gtk_text_iter_get_offset(&iter);
3854 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
3855 if (quotelevel > 2) {
3856 /* recycle colors */
3857 if (prefs_common.recycle_quote_colors)
3866 if (startq_offset == -1)
3867 noq_offset = gtk_text_iter_get_offset(&iter);
3871 if (prev_autowrap == FALSE && !force && !wrap_quote) {
3874 if (compose_get_line_break_pos(buffer, &iter, &break_pos,
3875 prefs_common.linewrap_len,
3877 GtkTextIter prev, next, cur;
3879 if (prev_autowrap != FALSE || force) {
3880 automatic_break = TRUE;
3881 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
3882 automatic_break = FALSE;
3883 } else if (quote_str && wrap_quote) {
3884 automatic_break = TRUE;
3885 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
3886 automatic_break = FALSE;
3889 /* remove trailing spaces */
3891 gtk_text_iter_backward_char(&cur);
3893 while (!gtk_text_iter_starts_line(&cur)) {
3896 gtk_text_iter_backward_char(&cur);
3897 wc = gtk_text_iter_get_char(&cur);
3898 if (!g_unichar_isspace(wc))
3902 if (!gtk_text_iter_equal(&prev, &next)) {
3903 gtk_text_buffer_delete(buffer, &prev, &next);
3905 gtk_text_iter_forward_char(&break_pos);
3909 gtk_text_buffer_insert(buffer, &break_pos,
3913 compose_join_next_line(compose, buffer, &iter, quote_str);
3915 /* move iter to current line start */
3916 gtk_text_iter_set_line_offset(&iter, 0);
3923 /* move iter to next line start */
3928 if (!prev_autowrap && num_blocks > 0) {
3930 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3931 G_CALLBACK(text_inserted),
3935 while (!gtk_text_iter_ends_line(&end_of_line)) {
3936 gtk_text_iter_forward_char(&end_of_line);
3938 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
3940 nouri_start = gtk_text_iter_get_offset(&iter);
3941 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
3943 walk_pos = gtk_text_iter_get_offset(&iter);
3944 /* FIXME: this looks phony. scanning for anything in the parse table */
3945 for (n = 0; n < PARSE_ELEMS; n++) {
3948 tmp = parser[n].search(walk, parser[n].needle);
3950 if (scanpos == NULL || tmp < scanpos) {
3959 /* check if URI can be parsed */
3960 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
3961 (const gchar **)&ep, FALSE)
3962 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
3966 strlen(parser[last_index].needle);
3969 uri_start = walk_pos + (bp - o_walk);
3970 uri_stop = walk_pos + (ep - o_walk);
3974 gtk_text_iter_forward_line(&iter);
3977 if (startq_offset != -1) {
3978 GtkTextIter startquote, endquote;
3979 gtk_text_buffer_get_iter_at_offset(
3980 buffer, &startquote, startq_offset);
3983 switch (quotelevel) {
3984 case 0: gtk_text_buffer_apply_tag_by_name(
3985 buffer, "quote0", &startquote, &endquote);
3987 case 1: gtk_text_buffer_apply_tag_by_name(
3988 buffer, "quote1", &startquote, &endquote);
3990 case 2: gtk_text_buffer_apply_tag_by_name(
3991 buffer, "quote2", &startquote, &endquote);
3995 } else if (noq_offset != -1) {
3996 GtkTextIter startnoquote, endnoquote;
3997 gtk_text_buffer_get_iter_at_offset(
3998 buffer, &startnoquote, noq_offset);
4000 gtk_text_buffer_remove_tag_by_name(
4001 buffer, "quote0", &startnoquote, &endnoquote);
4002 gtk_text_buffer_remove_tag_by_name(
4003 buffer, "quote1", &startnoquote, &endnoquote);
4004 gtk_text_buffer_remove_tag_by_name(
4005 buffer, "quote2", &startnoquote, &endnoquote);
4010 GtkTextIter nouri_start_iter, nouri_end_iter;
4011 gtk_text_buffer_get_iter_at_offset(
4012 buffer, &nouri_start_iter, nouri_start);
4013 gtk_text_buffer_get_iter_at_offset(
4014 buffer, &nouri_end_iter, nouri_stop);
4015 gtk_text_buffer_remove_tag_by_name(
4016 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4018 if (uri_start > 0 && uri_stop > 0) {
4019 GtkTextIter uri_start_iter, uri_end_iter;
4020 gtk_text_buffer_get_iter_at_offset(
4021 buffer, &uri_start_iter, uri_start);
4022 gtk_text_buffer_get_iter_at_offset(
4023 buffer, &uri_end_iter, uri_stop);
4024 gtk_text_buffer_apply_tag_by_name(
4025 buffer, "link", &uri_start_iter, &uri_end_iter);
4031 undo_wrapping(compose->undostruct, FALSE);
4032 compose->autowrap = prev_autowrap;
4035 void compose_action_cb(void *data)
4037 Compose *compose = (Compose *)data;
4038 compose_wrap_all(compose);
4041 static void compose_wrap_all(Compose *compose)
4043 compose_wrap_all_full(compose, FALSE);
4046 static void compose_wrap_all_full(Compose *compose, gboolean force)
4048 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4049 GtkTextBuffer *buffer;
4052 buffer = gtk_text_view_get_buffer(text);
4054 gtk_text_buffer_get_start_iter(buffer, &iter);
4055 while (!gtk_text_iter_is_end(&iter))
4056 compose_beautify_paragraph(compose, &iter, force);
4060 static void compose_set_title(Compose *compose)
4066 edited = compose->modified ? _(" [Edited]") : "";
4068 subject = gtk_editable_get_chars(
4069 GTK_EDITABLE(compose->subject_entry), 0, -1);
4072 if (subject && strlen(subject))
4073 str = g_strdup_printf(_("%s - Compose message%s"),
4076 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4078 str = g_strdup(_("Compose message"));
4081 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4087 * compose_current_mail_account:
4089 * Find a current mail account (the currently selected account, or the
4090 * default account, if a news account is currently selected). If a
4091 * mail account cannot be found, display an error message.
4093 * Return value: Mail account, or NULL if not found.
4095 static PrefsAccount *
4096 compose_current_mail_account(void)
4100 if (cur_account && cur_account->protocol != A_NNTP)
4103 ac = account_get_default();
4104 if (!ac || ac->protocol == A_NNTP) {
4105 alertpanel_error(_("Account for sending mail is not specified.\n"
4106 "Please select a mail account before sending."));
4113 #define QUOTE_IF_REQUIRED(out, str) \
4115 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4119 len = strlen(str) + 3; \
4120 if ((__tmp = alloca(len)) == NULL) { \
4121 g_warning("can't allocate memory\n"); \
4122 g_string_free(header, TRUE); \
4125 g_snprintf(__tmp, len, "\"%s\"", str); \
4130 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4131 g_warning("can't allocate memory\n"); \
4132 g_string_free(header, TRUE); \
4135 strcpy(__tmp, str); \
4141 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4143 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4147 len = strlen(str) + 3; \
4148 if ((__tmp = alloca(len)) == NULL) { \
4149 g_warning("can't allocate memory\n"); \
4152 g_snprintf(__tmp, len, "\"%s\"", str); \
4157 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4158 g_warning("can't allocate memory\n"); \
4161 strcpy(__tmp, str); \
4167 static void compose_select_account(Compose *compose, PrefsAccount *account,
4170 GtkItemFactory *ifactory;
4173 g_return_if_fail(account != NULL);
4175 compose->account = account;
4177 if (account->name && *account->name) {
4179 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4180 from = g_strdup_printf("%s <%s>",
4181 buf, account->address);
4182 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4184 from = g_strdup_printf("<%s>",
4186 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4191 compose_set_title(compose);
4193 ifactory = gtk_item_factory_from_widget(compose->menubar);
4195 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4196 menu_set_active(ifactory, "/Options/Sign", TRUE);
4198 menu_set_active(ifactory, "/Options/Sign", FALSE);
4199 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4200 menu_set_active(ifactory, "/Options/Encrypt", TRUE);
4202 menu_set_active(ifactory, "/Options/Encrypt", FALSE);
4204 activate_privacy_system(compose, account, FALSE);
4206 if (!init && compose->mode != COMPOSE_REDIRECT) {
4207 undo_block(compose->undostruct);
4208 compose_insert_sig(compose, TRUE);
4209 undo_unblock(compose->undostruct);
4213 /* use account's dict info if set */
4214 if (compose->gtkaspell) {
4215 if (account->enable_default_dictionary)
4216 gtkaspell_change_dict(compose->gtkaspell,
4217 account->default_dictionary, FALSE);
4218 if (account->enable_default_alt_dictionary)
4219 gtkaspell_change_alt_dict(compose->gtkaspell,
4220 account->default_alt_dictionary);
4221 if (account->enable_default_dictionary
4222 || account->enable_default_alt_dictionary)
4223 compose_spell_menu_changed(compose);
4228 gboolean compose_check_for_valid_recipient(Compose *compose) {
4229 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
4230 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
4231 gboolean recipient_found = FALSE;
4235 /* free to and newsgroup list */
4236 slist_free_strings(compose->to_list);
4237 g_slist_free(compose->to_list);
4238 compose->to_list = NULL;
4240 slist_free_strings(compose->newsgroup_list);
4241 g_slist_free(compose->newsgroup_list);
4242 compose->newsgroup_list = NULL;
4244 /* search header entries for to and newsgroup entries */
4245 for (list = compose->header_list; list; list = list->next) {
4248 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_COMBO(((ComposeHeaderEntry *)list->data)->combo)->entry), 0, -1);
4249 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4252 if (entry[0] != '\0') {
4253 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
4254 if (!strcmp(header, prefs_common_translated_header_name(*strptr))) {
4255 compose->to_list = address_list_append(compose->to_list, entry);
4256 recipient_found = TRUE;
4259 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
4260 if (!strcmp(header, prefs_common_translated_header_name(*strptr))) {
4261 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
4262 recipient_found = TRUE;
4269 return recipient_found;
4272 static gboolean compose_check_for_set_recipients(Compose *compose)
4274 if (compose->account->set_autocc && compose->account->auto_cc) {
4275 gboolean found_other = FALSE;
4277 /* search header entries for to and newsgroup entries */
4278 for (list = compose->header_list; list; list = list->next) {
4281 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4282 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_COMBO(((ComposeHeaderEntry *)list->data)->combo)->entry), 0, -1);
4284 if (strcmp(entry, compose->account->auto_cc)
4285 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
4295 if (compose->batch) {
4296 gtk_widget_show_all(compose->window);
4298 aval = alertpanel(_("Send"),
4299 _("The only recipient is the default CC address. Send anyway?"),
4300 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4301 if (aval != G_ALERTALTERNATE)
4305 if (compose->account->set_autobcc && compose->account->auto_bcc) {
4306 gboolean found_other = FALSE;
4308 /* search header entries for to and newsgroup entries */
4309 for (list = compose->header_list; list; list = list->next) {
4312 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4313 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_COMBO(((ComposeHeaderEntry *)list->data)->combo)->entry), 0, -1);
4315 if (strcmp(entry, compose->account->auto_bcc)
4316 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
4326 if (compose->batch) {
4327 gtk_widget_show_all(compose->window);
4329 aval = alertpanel(_("Send"),
4330 _("The only recipient is the default BCC address. Send anyway?"),
4331 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4332 if (aval != G_ALERTALTERNATE)
4339 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
4343 if (compose_check_for_valid_recipient(compose) == FALSE) {
4344 if (compose->batch) {
4345 gtk_widget_show_all(compose->window);
4347 alertpanel_error(_("Recipient is not specified."));
4351 if (compose_check_for_set_recipients(compose) == FALSE) {
4355 if (!compose->batch) {
4356 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
4357 if (*str == '\0' && check_everything == TRUE &&
4358 compose->mode != COMPOSE_REDIRECT) {
4361 aval = alertpanel(_("Send"),
4362 _("Subject is empty. Send it anyway?"),
4363 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4364 if (aval != G_ALERTALTERNATE)
4369 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
4375 gint compose_send(Compose *compose)
4378 FolderItem *folder = NULL;
4380 gchar *msgpath = NULL;
4381 gboolean discard_window = FALSE;
4382 gchar *errstr = NULL;
4383 gchar *tmsgid = NULL;
4384 MainWindow *mainwin = mainwindow_get_mainwindow();
4385 gboolean queued_removed = FALSE;
4387 if (prefs_common.send_dialog_mode != SEND_DIALOG_ALWAYS
4388 || compose->batch == TRUE)
4389 discard_window = TRUE;
4391 compose_allow_user_actions (compose, FALSE);
4392 compose->sending = TRUE;
4394 if (compose_check_entries(compose, TRUE) == FALSE) {
4395 if (compose->batch) {
4396 gtk_widget_show_all(compose->window);
4402 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
4405 if (compose->batch) {
4406 gtk_widget_show_all(compose->window);
4409 alertpanel_error(_("Could not queue message for sending:\n\n"
4410 "Charset conversion failed."));
4411 } else if (val == -5) {
4412 alertpanel_error(_("Could not queue message for sending:\n\n"
4413 "Couldn't get recipient encryption key."));
4414 } else if (val == -6) {
4416 } else if (val == -3) {
4417 if (privacy_peek_error())
4418 alertpanel_error(_("Could not queue message for sending:\n\n"
4419 "Signature failed: %s"), privacy_get_error());
4420 } else if (val == -2 && errno != 0) {
4421 alertpanel_error(_("Could not queue message for sending:\n\n%s."), strerror(errno));
4423 alertpanel_error(_("Could not queue message for sending."));
4428 tmsgid = g_strdup(compose->msgid);
4429 if (discard_window) {
4430 compose->sending = FALSE;
4431 compose_close(compose);
4432 /* No more compose access in the normal codepath
4433 * after this point! */
4438 alertpanel_error(_("The message was queued but could not be "
4439 "sent.\nUse \"Send queued messages\" from "
4440 "the main window to retry."));
4441 if (!discard_window) {
4448 if (msgpath == NULL) {
4449 msgpath = folder_item_fetch_msg(folder, msgnum);
4450 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
4453 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
4457 if (!discard_window) {
4459 if (!queued_removed)
4460 folder_item_remove_msg(folder, msgnum);
4461 folder_item_scan(folder);
4463 /* make sure we delete that */
4464 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
4466 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
4467 folder_item_remove_msg(folder, tmp->msgnum);
4468 procmsg_msginfo_free(tmp);
4475 if (!queued_removed)
4476 folder_item_remove_msg(folder, msgnum);
4477 folder_item_scan(folder);
4479 /* make sure we delete that */
4480 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
4482 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
4483 folder_item_remove_msg(folder, tmp->msgnum);
4484 procmsg_msginfo_free(tmp);
4487 if (!discard_window) {
4488 compose->sending = FALSE;
4489 compose_allow_user_actions (compose, TRUE);
4490 compose_close(compose);
4494 gchar *tmp = g_strdup_printf(_("%s\nUse \"Send queued messages\" from "
4495 "the main window to retry."), errstr);
4497 alertpanel_error_log(tmp);
4500 alertpanel_error_log(_("The message was queued but could not be "
4501 "sent.\nUse \"Send queued messages\" from "
4502 "the main window to retry."));
4504 if (!discard_window) {
4513 toolbar_main_set_sensitive(mainwin);
4514 main_window_set_menu_sensitive(mainwin);
4520 compose_allow_user_actions (compose, TRUE);
4521 compose->sending = FALSE;
4522 compose->modified = TRUE;
4523 toolbar_main_set_sensitive(mainwin);
4524 main_window_set_menu_sensitive(mainwin);
4529 static gboolean compose_use_attach(Compose *compose)
4531 GtkTreeModel *model = gtk_tree_view_get_model
4532 (GTK_TREE_VIEW(compose->attach_clist));
4533 return gtk_tree_model_iter_n_children(model, NULL) > 0;
4536 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
4539 gchar buf[BUFFSIZE];
4541 gboolean first_to_address;
4542 gboolean first_cc_address;
4544 ComposeHeaderEntry *headerentry;
4545 const gchar *headerentryname;
4546 const gchar *cc_hdr;
4547 const gchar *to_hdr;
4549 debug_print("Writing redirect header\n");
4551 cc_hdr = prefs_common_translated_header_name("Cc:");
4552 to_hdr = prefs_common_translated_header_name("To:");
4554 first_to_address = TRUE;
4555 for (list = compose->header_list; list; list = list->next) {
4556 headerentry = ((ComposeHeaderEntry *)list->data);
4557 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(headerentry->combo)->entry));
4559 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
4560 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
4561 Xstrdup_a(str, entstr, return -1);
4563 if (str[0] != '\0') {
4564 compose_convert_header
4565 (compose, buf, sizeof(buf), str,
4566 strlen("Resent-To") + 2, TRUE);
4568 if (first_to_address) {
4569 fprintf(fp, "Resent-To: ");
4570 first_to_address = FALSE;
4574 fprintf(fp, "%s", buf);
4578 if (!first_to_address) {
4582 first_cc_address = TRUE;
4583 for (list = compose->header_list; list; list = list->next) {
4584 headerentry = ((ComposeHeaderEntry *)list->data);
4585 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(headerentry->combo)->entry));
4587 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
4588 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
4589 Xstrdup_a(str, strg, return -1);
4591 if (str[0] != '\0') {
4592 compose_convert_header
4593 (compose, buf, sizeof(buf), str,
4594 strlen("Resent-Cc") + 2, TRUE);
4596 if (first_cc_address) {
4597 fprintf(fp, "Resent-Cc: ");
4598 first_cc_address = FALSE;
4602 fprintf(fp, "%s", buf);
4606 if (!first_cc_address) {
4613 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
4615 gchar buf[BUFFSIZE];
4617 const gchar *entstr;
4618 /* struct utsname utsbuf; */
4620 g_return_val_if_fail(fp != NULL, -1);
4621 g_return_val_if_fail(compose->account != NULL, -1);
4622 g_return_val_if_fail(compose->account->address != NULL, -1);
4625 get_rfc822_date(buf, sizeof(buf));
4626 fprintf(fp, "Resent-Date: %s\n", buf);
4629 if (compose->account->name && *compose->account->name) {
4630 compose_convert_header
4631 (compose, buf, sizeof(buf), compose->account->name,
4632 strlen("From: "), TRUE);
4633 fprintf(fp, "Resent-From: %s <%s>\n",
4634 buf, compose->account->address);
4636 fprintf(fp, "Resent-From: %s\n", compose->account->address);
4639 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
4640 if (*entstr != '\0') {
4641 Xstrdup_a(str, entstr, return -1);
4644 compose_convert_header(compose, buf, sizeof(buf), str,
4645 strlen("Subject: "), FALSE);
4646 fprintf(fp, "Subject: %s\n", buf);
4650 /* Resent-Message-ID */
4651 if (compose->account->gen_msgid) {
4652 generate_msgid(buf, sizeof(buf));
4653 fprintf(fp, "Resent-Message-ID: <%s>\n", buf);
4654 compose->msgid = g_strdup(buf);
4657 compose_redirect_write_headers_from_headerlist(compose, fp);
4659 /* separator between header and body */
4665 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
4669 gchar buf[BUFFSIZE];
4671 gboolean skip = FALSE;
4672 gchar *not_included[]={
4673 "Return-Path:", "Delivered-To:", "Received:",
4674 "Subject:", "X-UIDL:", "AF:",
4675 "NF:", "PS:", "SRH:",
4676 "SFN:", "DSR:", "MID:",
4677 "CFG:", "PT:", "S:",
4678 "RQ:", "SSV:", "NSV:",
4679 "SSH:", "R:", "MAID:",
4680 "NAID:", "RMID:", "FMID:",
4681 "SCF:", "RRCPT:", "NG:",
4682 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
4683 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
4684 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
4685 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
4688 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
4689 FILE_OP_ERROR(compose->redirect_filename, "fopen");
4693 while (procheader_get_one_field_asis(buf, sizeof(buf), fp) != -1) {
4695 for (i = 0; not_included[i] != NULL; i++) {
4696 if (g_ascii_strncasecmp(buf, not_included[i],
4697 strlen(not_included[i])) == 0) {
4704 if (fputs(buf, fdest) == -1)
4707 if (!prefs_common.redirect_keep_from) {
4708 if (g_ascii_strncasecmp(buf, "From:",
4709 strlen("From:")) == 0) {
4710 fputs(" (by way of ", fdest);
4711 if (compose->account->name
4712 && *compose->account->name) {
4713 compose_convert_header
4714 (compose, buf, sizeof(buf),
4715 compose->account->name,
4718 fprintf(fdest, "%s <%s>",
4720 compose->account->address);
4722 fprintf(fdest, "%s",
4723 compose->account->address);
4728 if (fputs("\n", fdest) == -1)
4732 compose_redirect_write_headers(compose, fdest);
4734 while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
4735 if (fwrite(buf, sizeof(gchar), len, fdest) != len)
4748 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
4750 GtkTextBuffer *buffer;
4751 GtkTextIter start, end;
4754 const gchar *out_codeset;
4755 EncodingType encoding;
4756 MimeInfo *mimemsg, *mimetext;
4759 if (action == COMPOSE_WRITE_FOR_SEND)
4760 attach_parts = TRUE;
4762 /* create message MimeInfo */
4763 mimemsg = procmime_mimeinfo_new();
4764 mimemsg->type = MIMETYPE_MESSAGE;
4765 mimemsg->subtype = g_strdup("rfc822");
4766 mimemsg->content = MIMECONTENT_MEM;
4767 mimemsg->tmp = TRUE; /* must free content later */
4768 mimemsg->data.mem = compose_get_header(compose);
4770 /* Create text part MimeInfo */
4771 /* get all composed text */
4772 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
4773 gtk_text_buffer_get_start_iter(buffer, &start);
4774 gtk_text_buffer_get_end_iter(buffer, &end);
4775 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
4776 if (is_ascii_str(chars)) {
4779 out_codeset = CS_US_ASCII;
4780 encoding = ENC_7BIT;
4782 const gchar *src_codeset = CS_INTERNAL;
4784 out_codeset = conv_get_charset_str(compose->out_encoding);
4787 gchar *test_conv_global_out = NULL;
4788 gchar *test_conv_reply = NULL;
4790 /* automatic mode. be automatic. */
4791 codeconv_set_strict(TRUE);
4793 out_codeset = conv_get_outgoing_charset_str();
4795 debug_print("trying to convert to %s\n", out_codeset);
4796 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
4799 if (!test_conv_global_out && compose->orig_charset
4800 && strcmp(compose->orig_charset, CS_US_ASCII)) {
4801 out_codeset = compose->orig_charset;
4802 debug_print("failure; trying to convert to %s\n", out_codeset);
4803 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
4806 if (!test_conv_global_out && !test_conv_reply) {
4808 out_codeset = CS_INTERNAL;
4809 debug_print("failure; finally using %s\n", out_codeset);
4811 g_free(test_conv_global_out);
4812 g_free(test_conv_reply);
4813 codeconv_set_strict(FALSE);
4816 if (!g_ascii_strcasecmp(out_codeset, CS_US_ASCII))
4817 out_codeset = CS_ISO_8859_1;
4819 if (prefs_common.encoding_method == CTE_BASE64)
4820 encoding = ENC_BASE64;
4821 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
4822 encoding = ENC_QUOTED_PRINTABLE;
4823 else if (prefs_common.encoding_method == CTE_8BIT)
4824 encoding = ENC_8BIT;
4826 encoding = procmime_get_encoding_for_charset(out_codeset);
4828 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
4829 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
4831 if (action == COMPOSE_WRITE_FOR_SEND) {
4832 codeconv_set_strict(TRUE);
4833 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
4834 codeconv_set_strict(FALSE);
4840 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
4841 "to the specified %s charset.\n"
4842 "Send it as %s?"), out_codeset, src_codeset);
4843 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL, _("+_Send"), NULL, FALSE,
4844 NULL, ALERT_ERROR, G_ALERTDEFAULT);
4847 if (aval != G_ALERTALTERNATE) {
4852 out_codeset = src_codeset;
4858 out_codeset = src_codeset;
4864 if (encoding == ENC_8BIT || encoding == ENC_7BIT) {
4865 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
4866 strstr(buf, "\nFrom ") != NULL) {
4867 encoding = ENC_QUOTED_PRINTABLE;
4871 mimetext = procmime_mimeinfo_new();
4872 mimetext->content = MIMECONTENT_MEM;
4873 mimetext->tmp = TRUE; /* must free content later */
4874 /* dup'ed because procmime_encode_content can turn it into a tmpfile
4875 * and free the data, which we need later. */
4876 mimetext->data.mem = g_strdup(buf);
4877 mimetext->type = MIMETYPE_TEXT;
4878 mimetext->subtype = g_strdup("plain");
4879 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
4880 g_strdup(out_codeset));
4882 /* protect trailing spaces when signing message */
4883 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
4884 privacy_system_can_sign(compose->privacy_system)) {
4885 encoding = ENC_QUOTED_PRINTABLE;
4888 debug_print("main text: %d bytes encoded as %s in %d\n",
4889 strlen(buf), out_codeset, encoding);
4891 /* check for line length limit */
4892 if (action == COMPOSE_WRITE_FOR_SEND &&
4893 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
4894 check_line_length(buf, 1000, &line) < 0) {
4898 msg = g_strdup_printf
4899 (_("Line %d exceeds the line length limit (998 bytes).\n"
4900 "The contents of the message might be broken on the way to the delivery.\n"
4902 "Send it anyway?"), line + 1);
4903 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
4905 if (aval != G_ALERTALTERNATE) {
4911 if (encoding != ENC_UNKNOWN)
4912 procmime_encode_content(mimetext, encoding);
4914 /* append attachment parts */
4915 if (compose_use_attach(compose) && attach_parts) {
4916 MimeInfo *mimempart;
4917 gchar *boundary = NULL;
4918 mimempart = procmime_mimeinfo_new();
4919 mimempart->content = MIMECONTENT_EMPTY;
4920 mimempart->type = MIMETYPE_MULTIPART;
4921 mimempart->subtype = g_strdup("mixed");
4925 boundary = generate_mime_boundary(NULL);
4926 } while (strstr(buf, boundary) != NULL);
4928 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
4931 mimetext->disposition = DISPOSITIONTYPE_INLINE;
4933 g_node_append(mimempart->node, mimetext->node);
4934 g_node_append(mimemsg->node, mimempart->node);
4936 compose_add_attachments(compose, mimempart);
4938 g_node_append(mimemsg->node, mimetext->node);
4942 /* sign message if sending */
4943 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
4944 privacy_system_can_sign(compose->privacy_system))
4945 if (!privacy_sign(compose->privacy_system, mimemsg, compose->account))
4948 procmime_write_mimeinfo(mimemsg, fp);
4950 procmime_mimeinfo_free_all(mimemsg);
4955 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
4957 GtkTextBuffer *buffer;
4958 GtkTextIter start, end;
4963 if ((fp = g_fopen(file, "wb")) == NULL) {
4964 FILE_OP_ERROR(file, "fopen");
4968 /* chmod for security */
4969 if (change_file_mode_rw(fp, file) < 0) {
4970 FILE_OP_ERROR(file, "chmod");
4971 g_warning("can't change file mode\n");
4974 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
4975 gtk_text_buffer_get_start_iter(buffer, &start);
4976 gtk_text_buffer_get_end_iter(buffer, &end);
4977 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
4979 chars = conv_codeset_strdup
4980 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
4983 if (!chars) return -1;
4986 len = strlen(chars);
4987 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
4988 FILE_OP_ERROR(file, "fwrite");
4997 if (fclose(fp) == EOF) {
4998 FILE_OP_ERROR(file, "fclose");
5005 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5008 MsgInfo *msginfo = compose->targetinfo;
5010 g_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5011 if (!msginfo) return -1;
5013 if (!force && MSG_IS_LOCKED(msginfo->flags))
5016 item = msginfo->folder;
5017 g_return_val_if_fail(item != NULL, -1);
5019 if (procmsg_msg_exist(msginfo) &&
5020 (folder_has_parent_of_type(item, F_QUEUE) ||
5021 folder_has_parent_of_type(item, F_DRAFT)
5022 || msginfo == compose->autosaved_draft)) {
5023 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5024 g_warning("can't remove the old message\n");
5032 static void compose_remove_draft(Compose *compose)
5035 MsgInfo *msginfo = compose->targetinfo;
5036 drafts = account_get_special_folder(compose->account, F_DRAFT);
5038 if (procmsg_msg_exist(msginfo)) {
5039 folder_item_remove_msg(drafts, msginfo->msgnum);
5044 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5045 gboolean remove_reedit_target)
5047 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5050 static gboolean compose_warn_encryption(Compose *compose)
5052 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5053 AlertValue val = G_ALERTALTERNATE;
5055 if (warning == NULL)
5058 val = alertpanel_full(_("Encryption warning"), warning,
5059 GTK_STOCK_CANCEL, _("+C_ontinue"), NULL,
5060 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5061 if (val & G_ALERTDISABLE) {
5062 val &= ~G_ALERTDISABLE;
5063 if (val == G_ALERTALTERNATE)
5064 privacy_inhibit_encrypt_warning(compose->privacy_system,
5068 if (val == G_ALERTALTERNATE) {
5075 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
5076 gchar **msgpath, gboolean check_subject,
5077 gboolean remove_reedit_target)
5084 static gboolean lock = FALSE;
5085 PrefsAccount *mailac = NULL, *newsac = NULL;
5087 debug_print("queueing message...\n");
5088 g_return_val_if_fail(compose->account != NULL, -1);
5092 if (compose_check_entries(compose, check_subject) == FALSE) {
5094 if (compose->batch) {
5095 gtk_widget_show_all(compose->window);
5100 if (!compose->to_list && !compose->newsgroup_list) {
5101 g_warning("can't get recipient list.");
5106 if (compose->to_list) {
5107 if (compose->account->protocol != A_NNTP)
5108 mailac = compose->account;
5109 else if (cur_account && cur_account->protocol != A_NNTP)
5110 mailac = cur_account;
5111 else if (!(mailac = compose_current_mail_account())) {
5113 alertpanel_error(_("No account for sending mails available!"));
5118 if (compose->newsgroup_list) {
5119 if (compose->account->protocol == A_NNTP)
5120 newsac = compose->account;
5121 else if (!newsac->protocol != A_NNTP) {
5123 alertpanel_error(_("No account for posting news available!"));
5128 /* write queue header */
5129 tmp = g_strdup_printf("%s%cqueue.%p", get_tmp_dir(),
5130 G_DIR_SEPARATOR, compose);
5131 if ((fp = g_fopen(tmp, "wb")) == NULL) {
5132 FILE_OP_ERROR(tmp, "fopen");
5138 if (change_file_mode_rw(fp, tmp) < 0) {
5139 FILE_OP_ERROR(tmp, "chmod");
5140 g_warning("can't change file mode\n");
5143 /* queueing variables */
5144 fprintf(fp, "AF:\n");
5145 fprintf(fp, "NF:0\n");
5146 fprintf(fp, "PS:10\n");
5147 fprintf(fp, "SRH:1\n");
5148 fprintf(fp, "SFN:\n");
5149 fprintf(fp, "DSR:\n");
5151 fprintf(fp, "MID:<%s>\n", compose->msgid);
5153 fprintf(fp, "MID:\n");
5154 fprintf(fp, "CFG:\n");
5155 fprintf(fp, "PT:0\n");
5156 fprintf(fp, "S:%s\n", compose->account->address);
5157 fprintf(fp, "RQ:\n");
5159 fprintf(fp, "SSV:%s\n", mailac->smtp_server);
5161 fprintf(fp, "SSV:\n");
5163 fprintf(fp, "NSV:%s\n", newsac->nntp_server);
5165 fprintf(fp, "NSV:\n");
5166 fprintf(fp, "SSH:\n");
5167 /* write recepient list */
5168 if (compose->to_list) {
5169 fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data);
5170 for (cur = compose->to_list->next; cur != NULL;
5172 fprintf(fp, ",<%s>", (gchar *)cur->data);
5175 /* write newsgroup list */
5176 if (compose->newsgroup_list) {
5178 fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data);
5179 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
5180 fprintf(fp, ",%s", (gchar *)cur->data);
5183 /* Sylpheed account IDs */
5185 fprintf(fp, "MAID:%d\n", mailac->account_id);
5187 fprintf(fp, "NAID:%d\n", newsac->account_id);
5190 if (compose->privacy_system != NULL) {
5191 fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system);
5192 fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing);
5193 if (compose->use_encryption) {
5195 if (!compose_warn_encryption(compose)) {
5202 if (mailac && mailac->encrypt_to_self) {
5203 GSList *tmp_list = g_slist_copy(compose->to_list);
5204 tmp_list = g_slist_append(tmp_list, compose->account->address);
5205 encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
5206 g_slist_free(tmp_list);
5208 encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
5210 if (encdata != NULL) {
5211 if (strcmp(encdata, "_DONT_ENCRYPT_")) {
5212 fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption);
5213 fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
5215 } /* else we finally dont want to encrypt */
5217 fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption);
5218 /* and if encdata was null, it means there's been a problem in
5230 /* Save copy folder */
5231 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
5232 gchar *savefolderid;
5234 savefolderid = gtk_editable_get_chars(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
5235 fprintf(fp, "SCF:%s\n", savefolderid);
5236 g_free(savefolderid);
5238 /* Save copy folder */
5239 if (compose->return_receipt) {
5240 fprintf(fp, "RRCPT:1\n");
5242 /* Message-ID of message replying to */
5243 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
5246 folderid = folder_item_get_identifier(compose->replyinfo->folder);
5247 fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid);
5250 /* Message-ID of message forwarding to */
5251 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
5254 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
5255 fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid);
5259 /* end of headers */
5260 fprintf(fp, "X-Claws-End-Special-Headers: 1\n");
5262 if (compose->redirect_filename != NULL) {
5263 if (compose_redirect_write_to_file(compose, fp) < 0) {
5272 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
5277 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
5281 if (fclose(fp) == EOF) {
5282 FILE_OP_ERROR(tmp, "fclose");
5289 if (item && *item) {
5292 queue = account_get_special_folder(compose->account, F_QUEUE);
5295 g_warning("can't find queue folder\n");
5301 folder_item_scan(queue);
5302 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
5303 g_warning("can't queue the message\n");
5310 if (msgpath == NULL) {
5316 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
5317 compose_remove_reedit_target(compose, FALSE);
5320 if ((msgnum != NULL) && (item != NULL)) {
5328 static void compose_add_attachments(Compose *compose, MimeInfo *parent)
5331 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
5333 struct stat statbuf;
5334 gchar *type, *subtype;
5335 GtkTreeModel *model;
5338 model = gtk_tree_view_get_model(tree_view);
5340 if (!gtk_tree_model_get_iter_first(model, &iter))
5343 gtk_tree_model_get(model, &iter,
5347 mimepart = procmime_mimeinfo_new();
5348 mimepart->content = MIMECONTENT_FILE;
5349 mimepart->data.filename = g_strdup(ainfo->file);
5350 mimepart->tmp = FALSE; /* or we destroy our attachment */
5351 mimepart->offset = 0;
5353 stat(ainfo->file, &statbuf);
5354 mimepart->length = statbuf.st_size;
5356 type = g_strdup(ainfo->content_type);
5358 if (!strchr(type, '/')) {
5360 type = g_strdup("application/octet-stream");
5363 subtype = strchr(type, '/') + 1;
5364 *(subtype - 1) = '\0';
5365 mimepart->type = procmime_get_media_type(type);
5366 mimepart->subtype = g_strdup(subtype);
5369 if (mimepart->type == MIMETYPE_MESSAGE &&
5370 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
5371 mimepart->disposition = DISPOSITIONTYPE_INLINE;
5374 g_hash_table_insert(mimepart->typeparameters,
5375 g_strdup("name"), g_strdup(ainfo->name));
5376 g_hash_table_insert(mimepart->dispositionparameters,
5377 g_strdup("filename"), g_strdup(ainfo->name));
5378 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
5382 if (compose->use_signing) {
5383 if (ainfo->encoding == ENC_7BIT)
5384 ainfo->encoding = ENC_QUOTED_PRINTABLE;
5385 else if (ainfo->encoding == ENC_8BIT)
5386 ainfo->encoding = ENC_BASE64;
5389 procmime_encode_content(mimepart, ainfo->encoding);
5391 g_node_append(parent->node, mimepart->node);
5392 } while (gtk_tree_model_iter_next(model, &iter));
5395 #define IS_IN_CUSTOM_HEADER(header) \
5396 (compose->account->add_customhdr && \
5397 custom_header_find(compose->account->customhdr_list, header) != NULL)
5399 static void compose_add_headerfield_from_headerlist(Compose *compose,
5401 const gchar *fieldname,
5402 const gchar *seperator)
5404 gchar *str, *fieldname_w_colon;
5405 gboolean add_field = FALSE;
5407 ComposeHeaderEntry *headerentry;
5408 const gchar *headerentryname;
5409 const gchar *trans_fieldname;
5412 if (IS_IN_CUSTOM_HEADER(fieldname))
5415 debug_print("Adding %s-fields\n", fieldname);
5417 fieldstr = g_string_sized_new(64);
5419 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
5420 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
5422 for (list = compose->header_list; list; list = list->next) {
5423 headerentry = ((ComposeHeaderEntry *)list->data);
5424 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(headerentry->combo)->entry));
5426 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
5427 str = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
5429 if (str[0] != '\0') {
5431 g_string_append(fieldstr, seperator);
5432 g_string_append(fieldstr, str);
5441 buf = g_new0(gchar, fieldstr->len * 4 + 256);
5442 compose_convert_header
5443 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
5444 strlen(fieldname) + 2, TRUE);
5445 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
5449 g_free(fieldname_w_colon);
5450 g_string_free(fieldstr, TRUE);
5455 static gchar *compose_get_header(Compose *compose)
5457 gchar buf[BUFFSIZE];
5458 const gchar *entry_str;
5462 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
5464 gchar *from_name = NULL, *from_address = NULL;
5467 g_return_val_if_fail(compose->account != NULL, NULL);
5468 g_return_val_if_fail(compose->account->address != NULL, NULL);
5470 header = g_string_sized_new(64);
5473 get_rfc822_date(buf, sizeof(buf));
5474 g_string_append_printf(header, "Date: %s\n", buf);
5478 if (compose->account->name && *compose->account->name) {
5480 QUOTE_IF_REQUIRED(buf, compose->account->name);
5481 tmp = g_strdup_printf("%s <%s>",
5482 buf, compose->account->address);
5484 tmp = g_strdup_printf("%s",
5485 compose->account->address);
5487 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
5488 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
5490 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
5491 from_address = g_strdup(compose->account->address);
5493 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5494 /* extract name and address */
5495 if (strstr(spec, " <") && strstr(spec, ">")) {
5496 from_address = g_strdup(strrchr(spec, '<')+1);
5497 *(strrchr(from_address, '>')) = '\0';
5498 from_name = g_strdup(spec);
5499 *(strrchr(from_name, '<')) = '\0';
5502 from_address = g_strdup(spec);
5509 if (from_name && *from_name) {
5510 compose_convert_header
5511 (compose, buf, sizeof(buf), from_name,
5512 strlen("From: "), TRUE);
5513 QUOTE_IF_REQUIRED(name, buf);
5515 g_string_append_printf(header, "From: %s <%s>\n",
5516 name, from_address);
5518 g_string_append_printf(header, "From: %s\n", from_address);
5521 g_free(from_address);
5524 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
5527 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
5530 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
5533 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
5536 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
5538 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
5541 compose_convert_header(compose, buf, sizeof(buf), str,
5542 strlen("Subject: "), FALSE);
5543 g_string_append_printf(header, "Subject: %s\n", buf);
5549 if (compose->account->gen_msgid) {
5550 generate_msgid(buf, sizeof(buf));
5551 g_string_append_printf(header, "Message-ID: <%s>\n", buf);
5552 compose->msgid = g_strdup(buf);
5555 if (compose->remove_references == FALSE) {
5557 if (compose->inreplyto && compose->to_list)
5558 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
5561 if (compose->references)
5562 g_string_append_printf(header, "References: %s\n", compose->references);
5566 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
5569 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
5572 if (compose->account->organization &&
5573 strlen(compose->account->organization) &&
5574 !IS_IN_CUSTOM_HEADER("Organization")) {
5575 compose_convert_header(compose, buf, sizeof(buf),
5576 compose->account->organization,
5577 strlen("Organization: "), FALSE);
5578 g_string_append_printf(header, "Organization: %s\n", buf);
5581 /* Program version and system info */
5582 if (g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
5583 !compose->newsgroup_list) {
5584 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
5586 gtk_major_version, gtk_minor_version, gtk_micro_version,
5589 if (g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
5590 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
5592 gtk_major_version, gtk_minor_version, gtk_micro_version,
5596 /* custom headers */
5597 if (compose->account->add_customhdr) {
5600 for (cur = compose->account->customhdr_list; cur != NULL;
5602 CustomHeader *chdr = (CustomHeader *)cur->data;
5604 if (custom_header_is_allowed(chdr->name)) {
5605 compose_convert_header
5606 (compose, buf, sizeof(buf),
5607 chdr->value ? chdr->value : "",
5608 strlen(chdr->name) + 2, FALSE);
5609 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
5615 switch (compose->priority) {
5616 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
5617 "X-Priority: 1 (Highest)\n");
5619 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
5620 "X-Priority: 2 (High)\n");
5622 case PRIORITY_NORMAL: break;
5623 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
5624 "X-Priority: 4 (Low)\n");
5626 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
5627 "X-Priority: 5 (Lowest)\n");
5629 default: debug_print("compose: priority unknown : %d\n",
5633 /* Request Return Receipt */
5634 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To")) {
5635 if (compose->return_receipt) {
5636 if (compose->account->name
5637 && *compose->account->name) {
5638 compose_convert_header(compose, buf, sizeof(buf),
5639 compose->account->name,
5640 strlen("Disposition-Notification-To: "),
5642 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, compose->account->address);
5644 g_string_append_printf(header, "Disposition-Notification-To: %s\n", compose->account->address);
5648 /* get special headers */
5649 for (list = compose->header_list; list; list = list->next) {
5650 ComposeHeaderEntry *headerentry;
5653 gchar *headername_wcolon;
5654 const gchar *headername_trans;
5657 gboolean standard_header = FALSE;
5659 headerentry = ((ComposeHeaderEntry *)list->data);
5661 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(headerentry->combo)->entry)));
5662 if (strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
5667 if (!strstr(tmp, ":")) {
5668 headername_wcolon = g_strconcat(tmp, ":", NULL);
5669 headername = g_strdup(tmp);
5671 headername_wcolon = g_strdup(tmp);
5672 headername = g_strdup(strtok(tmp, ":"));
5676 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5677 Xstrdup_a(headervalue, entry_str, return NULL);
5678 subst_char(headervalue, '\r', ' ');
5679 subst_char(headervalue, '\n', ' ');
5680 string = std_headers;
5681 while (*string != NULL) {
5682 headername_trans = prefs_common_translated_header_name(*string);
5683 if (!strcmp(headername_trans, headername_wcolon))
5684 standard_header = TRUE;
5687 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
5688 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
5691 g_free(headername_wcolon);
5695 g_string_free(header, FALSE);
5700 #undef IS_IN_CUSTOM_HEADER
5702 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
5703 gint header_len, gboolean addr_field)
5705 gchar *tmpstr = NULL;
5706 const gchar *out_codeset = NULL;
5708 g_return_if_fail(src != NULL);
5709 g_return_if_fail(dest != NULL);
5711 if (len < 1) return;
5713 tmpstr = g_strdup(src);
5715 subst_char(tmpstr, '\n', ' ');
5716 subst_char(tmpstr, '\r', ' ');
5719 if (!g_utf8_validate(tmpstr, -1, NULL)) {
5720 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
5721 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
5726 codeconv_set_strict(TRUE);
5727 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
5728 conv_get_charset_str(compose->out_encoding));
5729 codeconv_set_strict(FALSE);
5731 if (!dest || *dest == '\0') {
5732 gchar *test_conv_global_out = NULL;
5733 gchar *test_conv_reply = NULL;
5735 /* automatic mode. be automatic. */
5736 codeconv_set_strict(TRUE);
5738 out_codeset = conv_get_outgoing_charset_str();
5740 debug_print("trying to convert to %s\n", out_codeset);
5741 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
5744 if (!test_conv_global_out && compose->orig_charset
5745 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5746 out_codeset = compose->orig_charset;
5747 debug_print("failure; trying to convert to %s\n", out_codeset);
5748 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
5751 if (!test_conv_global_out && !test_conv_reply) {
5753 out_codeset = CS_INTERNAL;
5754 debug_print("finally using %s\n", out_codeset);
5756 g_free(test_conv_global_out);
5757 g_free(test_conv_reply);
5758 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
5760 codeconv_set_strict(FALSE);
5765 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
5769 g_return_if_fail(user_data != NULL);
5771 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
5772 g_strstrip(address);
5773 if (*address != '\0') {
5774 gchar *name = procheader_get_fromname(address);
5775 extract_address(address);
5776 addressbook_add_contact(name, address, NULL);
5781 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
5783 GtkWidget *menuitem;
5786 g_return_if_fail(menu != NULL);
5787 g_return_if_fail(GTK_IS_MENU_SHELL(menu));
5789 menuitem = gtk_separator_menu_item_new();
5790 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
5791 gtk_widget_show(menuitem);
5793 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
5794 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
5796 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
5797 g_strstrip(address);
5798 if (*address == '\0') {
5799 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
5802 g_signal_connect(G_OBJECT(menuitem), "activate",
5803 G_CALLBACK(compose_add_to_addressbook_cb), entry);
5804 gtk_widget_show(menuitem);
5807 static void compose_create_header_entry(Compose *compose)
5809 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
5813 GList *combo_list = NULL;
5815 const gchar *header = NULL;
5816 ComposeHeaderEntry *headerentry;
5817 gboolean standard_header = FALSE;
5819 headerentry = g_new0(ComposeHeaderEntry, 1);
5822 combo = gtk_combo_new();
5824 while(*string != NULL) {
5825 combo_list = g_list_append(combo_list, (gchar*)prefs_common_translated_header_name(*string));
5828 gtk_combo_set_popdown_strings(GTK_COMBO(combo), combo_list);
5829 g_list_free(combo_list);
5830 gtk_editable_set_editable(GTK_EDITABLE(GTK_COMBO(combo)->entry), TRUE);
5831 g_signal_connect(G_OBJECT(GTK_COMBO(combo)->entry), "grab_focus",
5832 G_CALLBACK(compose_grab_focus_cb), compose);
5833 gtk_widget_show(combo);
5834 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
5835 compose->header_nextrow, compose->header_nextrow+1,
5836 GTK_SHRINK, GTK_FILL, 0, 0);
5837 if (compose->header_last) {
5838 const gchar *last_header_entry = gtk_entry_get_text(
5839 GTK_ENTRY(GTK_COMBO(compose->header_last->combo)->entry));
5841 while (*string != NULL) {
5842 if (!strcmp(*string, last_header_entry))
5843 standard_header = TRUE;
5846 if (standard_header)
5847 header = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(compose->header_last->combo)->entry));
5849 if (!compose->header_last || !standard_header) {
5850 switch(compose->account->protocol) {
5852 header = prefs_common_translated_header_name("Newsgroups:");
5855 header = prefs_common_translated_header_name("To:");
5860 gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(combo)->entry), header);
5862 g_signal_connect_after(G_OBJECT(GTK_COMBO(combo)->entry), "grab_focus",
5863 G_CALLBACK(compose_grab_focus_cb), compose);
5866 entry = gtk_entry_new();
5867 gtk_widget_show(entry);
5868 gtk_tooltips_set_tip(compose->tooltips, entry,
5869 _("Use <tab> to autocomplete from addressbook"), NULL);
5870 gtk_table_attach(GTK_TABLE(compose->header_table), entry, 1, 2,
5871 compose->header_nextrow, compose->header_nextrow+1,
5872 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
5874 g_signal_connect(G_OBJECT(entry), "key-press-event",
5875 G_CALLBACK(compose_headerentry_key_press_event_cb),
5877 g_signal_connect(G_OBJECT(entry), "changed",
5878 G_CALLBACK(compose_headerentry_changed_cb),
5880 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
5881 G_CALLBACK(compose_grab_focus_cb), compose);
5884 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
5885 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
5886 GDK_ACTION_COPY | GDK_ACTION_MOVE);
5887 g_signal_connect(G_OBJECT(entry), "drag_data_received",
5888 G_CALLBACK(compose_header_drag_received_cb),
5890 g_signal_connect(G_OBJECT(entry), "drag-drop",
5891 G_CALLBACK(compose_drag_drop),
5893 g_signal_connect(G_OBJECT(entry), "populate-popup",
5894 G_CALLBACK(compose_entry_popup_extend),
5897 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
5899 headerentry->compose = compose;
5900 headerentry->combo = combo;
5901 headerentry->entry = entry;
5902 headerentry->headernum = compose->header_nextrow;
5904 compose->header_nextrow++;
5905 compose->header_last = headerentry;
5906 compose->header_list =
5907 g_slist_append(compose->header_list,
5911 static void compose_add_header_entry(Compose *compose, const gchar *header, gchar *text)
5913 ComposeHeaderEntry *last_header;
5915 last_header = compose->header_last;
5917 gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(last_header->combo)->entry), header);
5918 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
5921 static void compose_remove_header_entries(Compose *compose)
5924 for (list = compose->header_list; list; list = list->next) {
5925 ComposeHeaderEntry *headerentry =
5926 (ComposeHeaderEntry *)list->data;
5927 gtk_widget_destroy(headerentry->combo);
5928 gtk_widget_destroy(headerentry->entry);
5929 g_free(headerentry);
5931 compose->header_last = NULL;
5932 g_slist_free(compose->header_list);
5933 compose->header_list = NULL;
5934 compose->header_nextrow = 1;
5935 compose_create_header_entry(compose);
5938 static GtkWidget *compose_create_header(Compose *compose)
5940 GtkWidget *from_optmenu_hbox;
5941 GtkWidget *header_scrolledwin;
5942 GtkWidget *header_table;
5946 /* header labels and entries */
5947 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
5948 gtk_widget_show(header_scrolledwin);
5949 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
5951 header_table = gtk_table_new(2, 2, FALSE);
5952 gtk_widget_show(header_table);
5953 gtk_container_set_border_width(GTK_CONTAINER(header_table), BORDER_WIDTH);
5954 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
5955 gtk_viewport_set_shadow_type(GTK_VIEWPORT(GTK_BIN(header_scrolledwin)->child), GTK_SHADOW_ETCHED_IN);
5958 /* option menu for selecting accounts */
5959 from_optmenu_hbox = compose_account_option_menu_create(compose);
5960 gtk_table_attach(GTK_TABLE(header_table), from_optmenu_hbox,
5961 0, 2, count, count + 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
5964 compose->header_table = header_table;
5965 compose->header_list = NULL;
5966 compose->header_nextrow = count;
5968 compose_create_header_entry(compose);
5970 compose->table = NULL;
5972 return header_scrolledwin ;
5975 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
5977 Compose *compose = (Compose *)data;
5978 GdkEventButton event;
5981 event.time = gtk_get_current_event_time();
5983 return attach_button_pressed(compose->attach_clist, &event, compose);
5986 static GtkWidget *compose_create_attach(Compose *compose)
5988 GtkWidget *attach_scrwin;
5989 GtkWidget *attach_clist;
5991 GtkListStore *store;
5992 GtkCellRenderer *renderer;
5993 GtkTreeViewColumn *column;
5994 GtkTreeSelection *selection;
5996 /* attachment list */
5997 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
5998 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
5999 GTK_POLICY_AUTOMATIC,
6000 GTK_POLICY_AUTOMATIC);
6001 gtk_widget_set_size_request(attach_scrwin, -1, 80);
6003 store = gtk_list_store_new(N_ATTACH_COLS,
6008 G_TYPE_AUTO_POINTER,
6010 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
6011 (GTK_TREE_MODEL(store)));
6012 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
6013 g_object_unref(store);
6015 renderer = gtk_cell_renderer_text_new();
6016 column = gtk_tree_view_column_new_with_attributes
6017 (_("Mime type"), renderer, "text",
6018 COL_MIMETYPE, NULL);
6019 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6021 renderer = gtk_cell_renderer_text_new();
6022 column = gtk_tree_view_column_new_with_attributes
6023 (_("Size"), renderer, "text",
6025 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6027 renderer = gtk_cell_renderer_text_new();
6028 column = gtk_tree_view_column_new_with_attributes
6029 (_("Name"), renderer, "text",
6031 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6033 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
6034 prefs_common.use_stripes_everywhere);
6035 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
6036 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
6038 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
6039 G_CALLBACK(attach_selected), compose);
6040 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
6041 G_CALLBACK(attach_button_pressed), compose);
6043 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
6044 G_CALLBACK(popup_attach_button_pressed), compose);
6046 gtk_widget_tap_and_hold_setup(GTK_WIDGET(attach_clist), NULL, NULL,
6047 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
6048 g_signal_connect(G_OBJECT(attach_clist), "tap-and-hold",
6049 G_CALLBACK(popup_attach_button_pressed), compose);
6051 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
6052 G_CALLBACK(attach_key_pressed), compose);
6055 gtk_drag_dest_set(attach_clist,
6056 GTK_DEST_DEFAULT_ALL, compose_mime_types,
6057 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6058 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6059 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
6060 G_CALLBACK(compose_attach_drag_received_cb),
6062 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
6063 G_CALLBACK(compose_drag_drop),
6066 compose->attach_scrwin = attach_scrwin;
6067 compose->attach_clist = attach_clist;
6069 return attach_scrwin;
6072 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
6073 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
6075 static GtkWidget *compose_create_others(Compose *compose)
6078 GtkWidget *savemsg_checkbtn;
6079 GtkWidget *savemsg_entry;
6080 GtkWidget *savemsg_select;
6083 gchar *folderidentifier;
6085 /* Table for settings */
6086 table = gtk_table_new(3, 1, FALSE);
6087 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
6088 gtk_widget_show(table);
6089 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
6092 /* Save Message to folder */
6093 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
6094 gtk_widget_show(savemsg_checkbtn);
6095 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6096 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6097 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
6099 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
6100 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
6102 savemsg_entry = gtk_entry_new();
6103 gtk_widget_show(savemsg_entry);
6104 gtk_table_attach_defaults(GTK_TABLE(table), savemsg_entry, 1, 2, rowcount, rowcount + 1);
6105 gtk_editable_set_editable(GTK_EDITABLE(savemsg_entry), prefs_common.savemsg);
6106 g_signal_connect_after(G_OBJECT(savemsg_entry), "grab_focus",
6107 G_CALLBACK(compose_grab_focus_cb), compose);
6108 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6109 folderidentifier = folder_item_get_identifier(account_get_special_folder
6110 (compose->account, F_OUTBOX));
6111 gtk_entry_set_text(GTK_ENTRY(savemsg_entry), folderidentifier);
6112 g_free(folderidentifier);
6115 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
6116 gtk_widget_show(savemsg_select);
6117 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6118 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
6119 G_CALLBACK(compose_savemsg_select_cb),
6124 compose->savemsg_checkbtn = savemsg_checkbtn;
6125 compose->savemsg_entry = savemsg_entry;
6130 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
6132 gtk_editable_set_editable(GTK_EDITABLE(compose->savemsg_entry),
6133 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
6136 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
6141 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL);
6144 path = folder_item_get_identifier(dest);
6146 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), path);
6150 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
6151 GdkAtom clip, GtkTextIter *insert_place);
6153 #define BLOCK_WRAP() { \
6154 prev_autowrap = compose->autowrap; \
6155 buffer = gtk_text_view_get_buffer( \
6156 GTK_TEXT_VIEW(compose->text)); \
6157 compose->autowrap = FALSE; \
6159 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
6160 G_CALLBACK(compose_changed_cb), \
6162 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
6163 G_CALLBACK(text_inserted), \
6166 #define UNBLOCK_WRAP() { \
6167 compose->autowrap = prev_autowrap; \
6168 if (compose->autowrap) \
6169 compose_wrap_all(compose); \
6171 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
6172 G_CALLBACK(compose_changed_cb), \
6174 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
6175 G_CALLBACK(text_inserted), \
6180 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
6184 GtkTextBuffer *buffer;
6186 if (event->button == 3) {
6188 GtkTextIter sel_start, sel_end;
6189 gboolean stuff_selected;
6191 /* move the cursor to allow GtkAspell to check the word
6192 * under the mouse */
6193 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6194 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6196 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6199 stuff_selected = gtk_text_buffer_get_selection_bounds(
6200 GTK_TEXT_VIEW(text)->buffer,
6201 &sel_start, &sel_end);
6203 gtk_text_buffer_place_cursor (GTK_TEXT_VIEW(text)->buffer, &iter);
6204 /* reselect stuff */
6206 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
6207 gtk_text_buffer_select_range(GTK_TEXT_VIEW(text)->buffer,
6208 &sel_start, &sel_end);
6210 return FALSE; /* pass the event so that the right-click goes through */
6213 if (event->button == 2) {
6218 /* get the middle-click position to paste at the correct place */
6219 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6220 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6222 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6225 entry_paste_clipboard(compose, text,
6226 prefs_common.linewrap_pastes,
6227 GDK_SELECTION_PRIMARY, &iter);
6235 static void compose_spell_menu_changed(void *data)
6237 Compose *compose = (Compose *)data;
6239 GtkWidget *menuitem;
6240 GtkWidget *parent_item;
6241 GtkMenu *menu = GTK_MENU(gtk_menu_new());
6242 GtkItemFactory *ifactory = gtk_item_factory_from_widget(compose->menubar);
6245 if (compose->gtkaspell == NULL)
6248 parent_item = gtk_item_factory_get_item(ifactory,
6249 "/Spelling/Options");
6251 /* setting the submenu removes /Spelling/Options from the factory
6252 * so we need to save it */
6254 if (parent_item == NULL) {
6255 parent_item = compose->aspell_options_menu;
6256 gtk_menu_item_remove_submenu(GTK_MENU_ITEM(parent_item));
6258 compose->aspell_options_menu = parent_item;
6260 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
6262 spell_menu = g_slist_reverse(spell_menu);
6263 for (items = spell_menu;
6264 items; items = items->next) {
6265 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
6266 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
6267 gtk_widget_show(GTK_WIDGET(menuitem));
6269 g_slist_free(spell_menu);
6271 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
6276 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
6278 Compose *compose = (Compose *)data;
6279 GdkEventButton event;
6282 event.time = gtk_get_current_event_time();
6284 return text_clicked(compose->text, &event, compose);
6287 static gboolean compose_force_window_origin = TRUE;
6288 static Compose *compose_create(PrefsAccount *account, ComposeMode mode,
6295 GtkWidget *handlebox;
6297 GtkWidget *notebook;
6302 GtkWidget *subject_hbox;
6303 GtkWidget *subject_frame;
6304 GtkWidget *subject_entry;
6308 GtkWidget *edit_vbox;
6309 GtkWidget *ruler_hbox;
6311 GtkWidget *scrolledwin;
6313 GtkTextBuffer *buffer;
6314 GtkClipboard *clipboard;
6316 UndoMain *undostruct;
6318 gchar *titles[N_ATTACH_COLS];
6319 guint n_menu_entries;
6320 GtkWidget *popupmenu;
6321 GtkItemFactory *popupfactory;
6322 GtkItemFactory *ifactory;
6323 GtkWidget *tmpl_menu;
6325 GtkWidget *menuitem;
6328 GtkAspell * gtkaspell = NULL;
6331 static GdkGeometry geometry;
6333 g_return_val_if_fail(account != NULL, NULL);
6335 debug_print("Creating compose window...\n");
6336 compose = g_new0(Compose, 1);
6338 titles[COL_MIMETYPE] = _("MIME type");
6339 titles[COL_SIZE] = _("Size");
6340 titles[COL_NAME] = _("Name");
6342 compose->batch = batch;
6343 compose->account = account;
6345 compose->mutex = g_mutex_new();
6346 compose->set_cursor_pos = -1;
6348 compose->tooltips = gtk_tooltips_new();
6350 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
6352 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
6353 gtk_widget_set_size_request(window, -1, prefs_common.compose_height);
6355 if (!geometry.max_width) {
6356 geometry.max_width = gdk_screen_width();
6357 geometry.max_height = gdk_screen_height();
6360 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
6361 &geometry, GDK_HINT_MAX_SIZE);
6362 if (!geometry.min_width) {
6363 geometry.min_width = 600;
6364 geometry.min_height = 480;
6366 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
6367 &geometry, GDK_HINT_MIN_SIZE);
6370 if (compose_force_window_origin)
6371 gtk_widget_set_uposition(window, prefs_common.compose_x,
6372 prefs_common.compose_y);
6374 g_signal_connect(G_OBJECT(window), "delete_event",
6375 G_CALLBACK(compose_delete_cb), compose);
6376 MANAGE_WINDOW_SIGNALS_CONNECT(window);
6377 gtk_widget_realize(window);
6379 gtkut_widget_set_composer_icon(window);
6381 vbox = gtk_vbox_new(FALSE, 0);
6382 gtk_container_add(GTK_CONTAINER(window), vbox);
6384 n_menu_entries = sizeof(compose_entries) / sizeof(compose_entries[0]);
6385 menubar = menubar_create(window, compose_entries,
6386 n_menu_entries, "<Compose>", compose);
6387 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
6389 if (prefs_common.toolbar_detachable) {
6390 handlebox = gtk_handle_box_new();
6392 handlebox = gtk_hbox_new(FALSE, 0);
6394 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
6396 gtk_widget_realize(handlebox);
6397 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
6400 vbox2 = gtk_vbox_new(FALSE, 2);
6401 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
6402 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
6405 notebook = gtk_notebook_new();
6406 gtk_widget_set_size_request(notebook, -1, 130);
6407 gtk_widget_show(notebook);
6409 /* header labels and entries */
6410 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6411 compose_create_header(compose),
6412 gtk_label_new_with_mnemonic(_("Hea_der")));
6413 /* attachment list */
6414 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6415 compose_create_attach(compose),
6416 gtk_label_new_with_mnemonic(_("_Attachments")));
6418 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6419 compose_create_others(compose),
6420 gtk_label_new_with_mnemonic(_("Othe_rs")));
6423 subject_hbox = gtk_hbox_new(FALSE, 0);
6424 gtk_widget_show(subject_hbox);
6426 subject_frame = gtk_frame_new(NULL);
6427 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
6428 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
6429 gtk_widget_show(subject_frame);
6431 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
6432 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
6433 gtk_widget_show(subject);
6435 label = gtk_label_new(_("Subject:"));
6436 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
6437 gtk_widget_show(label);
6439 subject_entry = gtk_entry_new();
6440 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
6441 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
6442 G_CALLBACK(compose_grab_focus_cb), compose);
6443 gtk_widget_show(subject_entry);
6444 compose->subject_entry = subject_entry;
6445 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
6447 edit_vbox = gtk_vbox_new(FALSE, 0);
6449 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
6452 ruler_hbox = gtk_hbox_new(FALSE, 0);
6453 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
6455 ruler = gtk_shruler_new();
6456 gtk_ruler_set_range(GTK_RULER(ruler), 0.0, 100.0, 1.0, 100.0);
6457 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
6461 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
6462 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
6463 GTK_POLICY_AUTOMATIC,
6464 GTK_POLICY_AUTOMATIC);
6465 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
6467 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
6468 gtk_widget_set_size_request(scrolledwin, prefs_common.compose_width, -1);
6470 text = gtk_text_view_new();
6471 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
6472 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
6473 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
6474 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
6475 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
6477 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
6479 g_signal_connect_after(G_OBJECT(text), "size_allocate",
6480 G_CALLBACK(compose_edit_size_alloc),
6482 g_signal_connect(G_OBJECT(buffer), "changed",
6483 G_CALLBACK(compose_changed_cb), compose);
6484 g_signal_connect(G_OBJECT(text), "grab_focus",
6485 G_CALLBACK(compose_grab_focus_cb), compose);
6486 g_signal_connect(G_OBJECT(buffer), "insert_text",
6487 G_CALLBACK(text_inserted), compose);
6488 g_signal_connect(G_OBJECT(text), "button_press_event",
6489 G_CALLBACK(text_clicked), compose);
6491 g_signal_connect(G_OBJECT(text), "popup-menu",
6492 G_CALLBACK(compose_popup_menu), compose);
6494 gtk_widget_tap_and_hold_setup(GTK_WIDGET(text), NULL, NULL,
6495 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
6496 g_signal_connect(G_OBJECT(text), "tap-and-hold",
6497 G_CALLBACK(compose_popup_menu), compose);
6499 g_signal_connect(G_OBJECT(subject_entry), "changed",
6500 G_CALLBACK(compose_changed_cb), compose);
6503 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6504 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6505 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6506 g_signal_connect(G_OBJECT(text), "drag_data_received",
6507 G_CALLBACK(compose_insert_drag_received_cb),
6509 g_signal_connect(G_OBJECT(text), "drag-drop",
6510 G_CALLBACK(compose_drag_drop),
6512 gtk_widget_show_all(vbox);
6514 /* pane between attach clist and text */
6515 paned = gtk_vpaned_new();
6516 gtk_paned_set_gutter_size(GTK_PANED(paned), 12);
6517 gtk_container_add(GTK_CONTAINER(vbox2), paned);
6519 if( maemo_mainwindow_is_fullscreen(mainwindow_get_mainwindow()->window) )
6520 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 300 : 280);
6522 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 250 : 230);
6524 gtk_paned_add1(GTK_PANED(paned), notebook);
6525 gtk_paned_add2(GTK_PANED(paned), edit_vbox);
6526 gtk_widget_show_all(paned);
6529 if (prefs_common.textfont) {
6530 PangoFontDescription *font_desc;
6532 font_desc = pango_font_description_from_string
6533 (prefs_common.textfont);
6535 gtk_widget_modify_font(text, font_desc);
6536 pango_font_description_free(font_desc);
6540 n_entries = sizeof(compose_popup_entries) /
6541 sizeof(compose_popup_entries[0]);
6542 popupmenu = menu_create_items(compose_popup_entries, n_entries,
6543 "<Compose>", &popupfactory,
6546 ifactory = gtk_item_factory_from_widget(menubar);
6547 menu_set_sensitive(ifactory, "/Edit/Undo", FALSE);
6548 menu_set_sensitive(ifactory, "/Edit/Redo", FALSE);
6549 menu_set_sensitive(ifactory, "/Options/Remove references", FALSE);
6551 tmpl_menu = gtk_item_factory_get_item(ifactory, "/Tools/Template");
6553 undostruct = undo_init(text);
6554 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
6557 address_completion_start(window);
6559 compose->window = window;
6560 compose->vbox = vbox;
6561 compose->menubar = menubar;
6562 compose->handlebox = handlebox;
6564 compose->vbox2 = vbox2;
6566 compose->paned = paned;
6568 compose->notebook = notebook;
6569 compose->edit_vbox = edit_vbox;
6570 compose->ruler_hbox = ruler_hbox;
6571 compose->ruler = ruler;
6572 compose->scrolledwin = scrolledwin;
6573 compose->text = text;
6575 compose->focused_editable = NULL;
6577 compose->popupmenu = popupmenu;
6578 compose->popupfactory = popupfactory;
6580 compose->tmpl_menu = tmpl_menu;
6582 compose->mode = mode;
6583 compose->rmode = mode;
6585 compose->targetinfo = NULL;
6586 compose->replyinfo = NULL;
6587 compose->fwdinfo = NULL;
6589 compose->replyto = NULL;
6591 compose->bcc = NULL;
6592 compose->followup_to = NULL;
6594 compose->ml_post = NULL;
6596 compose->inreplyto = NULL;
6597 compose->references = NULL;
6598 compose->msgid = NULL;
6599 compose->boundary = NULL;
6601 compose->autowrap = prefs_common.autowrap;
6603 compose->use_signing = FALSE;
6604 compose->use_encryption = FALSE;
6605 compose->privacy_system = NULL;
6607 compose->modified = FALSE;
6609 compose->return_receipt = FALSE;
6611 compose->to_list = NULL;
6612 compose->newsgroup_list = NULL;
6614 compose->undostruct = undostruct;
6616 compose->sig_str = NULL;
6618 compose->exteditor_file = NULL;
6619 compose->exteditor_pid = -1;
6620 compose->exteditor_tag = -1;
6621 compose->draft_timeout_tag = -1;
6624 menu_set_sensitive(ifactory, "/Spelling", FALSE);
6625 if (mode != COMPOSE_REDIRECT) {
6626 if (prefs_common.enable_aspell && prefs_common.dictionary &&
6627 strcmp(prefs_common.dictionary, "")) {
6628 gtkaspell = gtkaspell_new(prefs_common.aspell_path,
6629 prefs_common.dictionary,
6630 prefs_common.alt_dictionary,
6631 conv_get_locale_charset_str(),
6632 prefs_common.misspelled_col,
6633 prefs_common.check_while_typing,
6634 prefs_common.recheck_when_changing_dict,
6635 prefs_common.use_alternate,
6636 prefs_common.use_both_dicts,
6637 GTK_TEXT_VIEW(text),
6638 GTK_WINDOW(compose->window),
6639 compose_spell_menu_changed,
6642 alertpanel_error(_("Spell checker could not "
6644 gtkaspell_checkers_strerror());
6645 gtkaspell_checkers_reset_error();
6647 if (!gtkaspell_set_sug_mode(gtkaspell,
6648 prefs_common.aspell_sugmode)) {
6649 debug_print("Aspell: could not set "
6650 "suggestion mode %s\n",
6651 gtkaspell_checkers_strerror());
6652 gtkaspell_checkers_reset_error();
6655 menu_set_sensitive(ifactory, "/Spelling", TRUE);
6659 compose->gtkaspell = gtkaspell;
6660 compose_spell_menu_changed(compose);
6663 compose_select_account(compose, account, TRUE);
6665 menu_set_active(ifactory, "/Edit/Auto wrapping", prefs_common.autowrap);
6666 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
6667 compose_entry_append(compose, account->auto_cc, COMPOSE_CC);
6669 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
6670 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC);
6672 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
6673 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO);
6675 menu_set_sensitive(ifactory, "/Options/Reply mode", compose->mode == COMPOSE_REPLY);
6677 if (account->protocol != A_NNTP)
6678 gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(compose->header_last->combo)->entry),
6679 prefs_common_translated_header_name("To:"));
6681 gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(compose->header_last->combo)->entry),
6682 prefs_common_translated_header_name("Newsgroups:"));
6684 addressbook_set_target_compose(compose);
6686 if (mode != COMPOSE_REDIRECT)
6687 compose_set_template_menu(compose);
6689 menuitem = gtk_item_factory_get_item(ifactory, "/Tools/Template");
6690 menu_set_sensitive(ifactory, "/Tools/Template", FALSE);
6693 compose_list = g_list_append(compose_list, compose);
6695 if (!prefs_common.show_ruler)
6696 gtk_widget_hide(ruler_hbox);
6698 menuitem = gtk_item_factory_get_item(ifactory, "/Tools/Show ruler");
6699 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
6700 prefs_common.show_ruler);
6703 compose->priority = PRIORITY_NORMAL;
6704 compose_update_priority_menu_item(compose);
6706 compose_set_out_encoding(compose);
6709 compose_update_actions_menu(compose);
6711 /* Privacy Systems menu */
6712 compose_update_privacy_systems_menu(compose);
6714 activate_privacy_system(compose, account, TRUE);
6715 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
6717 gtk_widget_realize(window);
6719 gtk_widget_show(window);
6721 maemo_window_full_screen_if_needed(GTK_WINDOW(window));
6722 maemo_connect_key_press_to_mainwindow(GTK_WINDOW(window));
6729 static GtkWidget *compose_account_option_menu_create(Compose *compose)
6734 GtkWidget *optmenubox;
6737 GtkWidget *from_name = NULL;
6739 gint num = 0, def_menu = 0;
6741 accounts = account_get_list();
6742 g_return_val_if_fail(accounts != NULL, NULL);
6744 optmenubox = gtk_event_box_new();
6745 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
6746 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
6748 hbox = gtk_hbox_new(FALSE, 6);
6749 from_name = gtk_entry_new();
6751 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
6752 G_CALLBACK(compose_grab_focus_cb), compose);
6754 for (; accounts != NULL; accounts = accounts->next, num++) {
6755 PrefsAccount *ac = (PrefsAccount *)accounts->data;
6756 gchar *name, *from = NULL;
6758 if (ac == compose->account) def_menu = num;
6760 name = g_markup_printf_escaped(_("From: <i>%s</i>"),
6763 if (ac == compose->account) {
6764 if (ac->name && *ac->name) {
6766 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
6767 from = g_strdup_printf("%s <%s>",
6769 gtk_entry_set_text(GTK_ENTRY(from_name), from);
6771 from = g_strdup_printf("%s",
6773 gtk_entry_set_text(GTK_ENTRY(from_name), from);
6776 COMBOBOX_ADD(menu, name, ac->account_id);
6781 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
6783 g_signal_connect(G_OBJECT(optmenu), "changed",
6784 G_CALLBACK(account_activated),
6786 g_signal_connect(G_OBJECT(from_name), "populate-popup",
6787 G_CALLBACK(compose_entry_popup_extend),
6790 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
6791 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
6793 gtk_tooltips_set_tip(compose->tooltips, optmenubox,
6794 _("Account to use for this email"), NULL);
6795 gtk_tooltips_set_tip(compose->tooltips, from_name,
6796 _("Sender address to be used"), NULL);
6798 compose->from_name = from_name;
6803 static void compose_set_priority_cb(gpointer data,
6807 Compose *compose = (Compose *) data;
6808 compose->priority = action;
6811 static void compose_reply_change_mode(gpointer data,
6815 Compose *compose = (Compose *) data;
6816 gboolean was_modified = compose->modified;
6818 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
6820 g_return_if_fail(compose->replyinfo != NULL);
6822 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
6824 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
6826 if (action == COMPOSE_REPLY_TO_ALL)
6828 if (action == COMPOSE_REPLY_TO_SENDER)
6830 if (action == COMPOSE_REPLY_TO_LIST)
6833 compose_remove_header_entries(compose);
6834 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
6835 if (compose->account->set_autocc && compose->account->auto_cc)
6836 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC);
6838 if (compose->account->set_autobcc && compose->account->auto_bcc)
6839 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC);
6841 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
6842 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO);
6843 compose_show_first_last_header(compose, TRUE);
6844 compose->modified = was_modified;
6845 compose_set_title(compose);
6848 static void compose_update_priority_menu_item(Compose * compose)
6850 GtkItemFactory *ifactory;
6851 GtkWidget *menuitem = NULL;
6853 ifactory = gtk_item_factory_from_widget(compose->menubar);
6855 switch (compose->priority) {
6856 case PRIORITY_HIGHEST:
6857 menuitem = gtk_item_factory_get_item
6858 (ifactory, "/Options/Priority/Highest");
6861 menuitem = gtk_item_factory_get_item
6862 (ifactory, "/Options/Priority/High");
6864 case PRIORITY_NORMAL:
6865 menuitem = gtk_item_factory_get_item
6866 (ifactory, "/Options/Priority/Normal");
6869 menuitem = gtk_item_factory_get_item
6870 (ifactory, "/Options/Priority/Low");
6872 case PRIORITY_LOWEST:
6873 menuitem = gtk_item_factory_get_item
6874 (ifactory, "/Options/Priority/Lowest");
6877 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
6880 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
6882 Compose *compose = (Compose *) data;
6884 GtkItemFactory *ifactory;
6885 gboolean can_sign = FALSE, can_encrypt = FALSE;
6887 g_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
6889 if (!GTK_CHECK_MENU_ITEM(widget)->active)
6892 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
6893 g_free(compose->privacy_system);
6894 compose->privacy_system = NULL;
6895 if (systemid != NULL) {
6896 compose->privacy_system = g_strdup(systemid);
6898 can_sign = privacy_system_can_sign(systemid);
6899 can_encrypt = privacy_system_can_encrypt(systemid);
6902 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
6904 ifactory = gtk_item_factory_from_widget(compose->menubar);
6905 menu_set_sensitive(ifactory, "/Options/Sign", can_sign);
6906 menu_set_sensitive(ifactory, "/Options/Encrypt", can_encrypt);
6909 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
6911 static gchar *branch_path = "/Options/Privacy System";
6912 GtkItemFactory *ifactory;
6913 GtkWidget *menuitem = NULL;
6915 gboolean can_sign = FALSE, can_encrypt = FALSE;
6916 gboolean found = FALSE;
6918 ifactory = gtk_item_factory_from_widget(compose->menubar);
6920 if (compose->privacy_system != NULL) {
6923 menuitem = gtk_item_factory_get_widget(ifactory, branch_path);
6924 g_return_if_fail(menuitem != NULL);
6926 amenu = GTK_MENU_SHELL(menuitem)->children;
6928 while (amenu != NULL) {
6929 GList *alist = amenu->next;
6931 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
6932 if (systemid != NULL) {
6933 if (strcmp(systemid, compose->privacy_system) == 0) {
6934 menuitem = GTK_WIDGET(amenu->data);
6936 can_sign = privacy_system_can_sign(systemid);
6937 can_encrypt = privacy_system_can_encrypt(systemid);
6941 } else if (strlen(compose->privacy_system) == 0) {
6942 menuitem = GTK_WIDGET(amenu->data);
6945 can_encrypt = FALSE;
6952 if (menuitem != NULL)
6953 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
6955 if (warn && !found && strlen(compose->privacy_system)) {
6956 gchar *tmp = g_strdup_printf(
6957 _("The privacy system '%s' cannot be loaded. You "
6958 "will not be able to sign or encrypt this message."),
6959 compose->privacy_system);
6960 alertpanel_warning(tmp);
6965 menu_set_sensitive(ifactory, "/Options/Sign", can_sign);
6966 menu_set_sensitive(ifactory, "/Options/Encrypt", can_encrypt);
6969 static void compose_set_out_encoding(Compose *compose)
6971 GtkItemFactoryEntry *entry;
6972 GtkItemFactory *ifactory;
6973 CharSet out_encoding;
6974 gchar *path, *p, *q;
6977 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
6978 ifactory = gtk_item_factory_from_widget(compose->menubar);
6980 for (entry = compose_entries; entry->callback != compose_address_cb;
6982 if (entry->callback == compose_set_encoding_cb &&
6983 (CharSet)entry->callback_action == out_encoding) {
6984 p = q = path = g_strdup(entry->path);
6996 item = gtk_item_factory_get_item(ifactory, path);
6997 gtk_widget_activate(item);
7004 static void compose_set_template_menu(Compose *compose)
7006 GSList *tmpl_list, *cur;
7010 tmpl_list = template_get_config();
7012 menu = gtk_menu_new();
7014 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
7015 Template *tmpl = (Template *)cur->data;
7017 item = gtk_menu_item_new_with_label(tmpl->name);
7018 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
7019 g_signal_connect(G_OBJECT(item), "activate",
7020 G_CALLBACK(compose_template_activate_cb),
7022 g_object_set_data(G_OBJECT(item), "template", tmpl);
7023 gtk_widget_show(item);
7026 gtk_widget_show(menu);
7027 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
7030 void compose_update_actions_menu(Compose *compose)
7032 GtkItemFactory *ifactory;
7034 ifactory = gtk_item_factory_from_widget(compose->menubar);
7035 action_update_compose_menu(ifactory, "/Tools/Actions", compose);
7038 static void compose_update_privacy_systems_menu(Compose *compose)
7040 static gchar *branch_path = "/Options/Privacy System";
7041 GtkItemFactory *ifactory;
7042 GtkWidget *menuitem;
7043 GSList *systems, *cur;
7046 GtkWidget *system_none;
7049 ifactory = gtk_item_factory_from_widget(compose->menubar);
7051 /* remove old entries */
7052 menuitem = gtk_item_factory_get_widget(ifactory, branch_path);
7053 g_return_if_fail(menuitem != NULL);
7055 amenu = GTK_MENU_SHELL(menuitem)->children->next;
7056 while (amenu != NULL) {
7057 GList *alist = amenu->next;
7058 gtk_widget_destroy(GTK_WIDGET(amenu->data));
7062 system_none = gtk_item_factory_get_widget(ifactory,
7063 "/Options/Privacy System/None");
7065 g_signal_connect(G_OBJECT(system_none), "activate",
7066 G_CALLBACK(compose_set_privacy_system_cb), compose);
7068 systems = privacy_get_system_ids();
7069 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
7070 gchar *systemid = cur->data;
7072 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
7073 widget = gtk_radio_menu_item_new_with_label(group,
7074 privacy_system_get_name(systemid));
7075 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
7076 g_strdup(systemid), g_free);
7077 g_signal_connect(G_OBJECT(widget), "activate",
7078 G_CALLBACK(compose_set_privacy_system_cb), compose);
7080 gtk_menu_append(GTK_MENU(system_none->parent), widget);
7081 gtk_widget_show(widget);
7084 g_slist_free(systems);
7087 void compose_reflect_prefs_all(void)
7092 for (cur = compose_list; cur != NULL; cur = cur->next) {
7093 compose = (Compose *)cur->data;
7094 compose_set_template_menu(compose);
7098 void compose_reflect_prefs_pixmap_theme(void)
7103 for (cur = compose_list; cur != NULL; cur = cur->next) {
7104 compose = (Compose *)cur->data;
7105 toolbar_update(TOOLBAR_COMPOSE, compose);
7109 static void compose_template_apply(Compose *compose, Template *tmpl,
7113 GtkTextBuffer *buffer;
7117 gchar *parsed_str = NULL;
7118 gint cursor_pos = 0;
7119 const gchar *err_msg = _("Template body format error at line %d.");
7122 /* process the body */
7124 text = GTK_TEXT_VIEW(compose->text);
7125 buffer = gtk_text_view_get_buffer(text);
7128 if (prefs_common.quotemark && *prefs_common.quotemark)
7129 qmark = prefs_common.quotemark;
7133 if (compose->replyinfo != NULL) {
7136 gtk_text_buffer_set_text(buffer, "", -1);
7137 mark = gtk_text_buffer_get_insert(buffer);
7138 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7140 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
7141 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
7143 } else if (compose->fwdinfo != NULL) {
7146 gtk_text_buffer_set_text(buffer, "", -1);
7147 mark = gtk_text_buffer_get_insert(buffer);
7148 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7150 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
7151 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
7154 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
7156 GtkTextIter start, end;
7159 gtk_text_buffer_get_start_iter(buffer, &start);
7160 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
7161 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
7163 /* clear the buffer now */
7165 gtk_text_buffer_set_text(buffer, "", -1);
7167 parsed_str = compose_quote_fmt(compose, dummyinfo,
7168 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
7169 procmsg_msginfo_free( dummyinfo );
7175 gtk_text_buffer_set_text(buffer, "", -1);
7176 mark = gtk_text_buffer_get_insert(buffer);
7177 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7180 if (replace && parsed_str && compose->account->auto_sig)
7181 compose_insert_sig(compose, FALSE);
7183 if (replace && parsed_str) {
7184 gtk_text_buffer_get_start_iter(buffer, &iter);
7185 gtk_text_buffer_place_cursor(buffer, &iter);
7189 cursor_pos = quote_fmt_get_cursor_pos();
7190 compose->set_cursor_pos = cursor_pos;
7191 if (cursor_pos == -1)
7193 gtk_text_buffer_get_start_iter(buffer, &iter);
7194 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
7195 gtk_text_buffer_place_cursor(buffer, &iter);
7198 /* process the other fields */
7200 compose_template_apply_fields(compose, tmpl);
7201 quote_fmt_reset_vartable();
7202 compose_changed_cb(NULL, compose);
7205 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
7207 MsgInfo* dummyinfo = NULL;
7208 MsgInfo *msginfo = NULL;
7211 if (compose->replyinfo != NULL)
7212 msginfo = compose->replyinfo;
7213 else if (compose->fwdinfo != NULL)
7214 msginfo = compose->fwdinfo;
7216 dummyinfo = compose_msginfo_new_from_compose(compose);
7217 msginfo = dummyinfo;
7220 if (tmpl->to && *tmpl->to != '\0') {
7222 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7223 compose->gtkaspell);
7225 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7227 quote_fmt_scan_string(tmpl->to);
7230 buf = quote_fmt_get_buffer();
7232 alertpanel_error(_("Template To format error."));
7234 compose_entry_append(compose, buf, COMPOSE_TO);
7238 if (tmpl->cc && *tmpl->cc != '\0') {
7240 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7241 compose->gtkaspell);
7243 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7245 quote_fmt_scan_string(tmpl->cc);
7248 buf = quote_fmt_get_buffer();
7250 alertpanel_error(_("Template Cc format error."));
7252 compose_entry_append(compose, buf, COMPOSE_CC);
7256 if (tmpl->bcc && *tmpl->bcc != '\0') {
7258 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7259 compose->gtkaspell);
7261 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7263 quote_fmt_scan_string(tmpl->bcc);
7266 buf = quote_fmt_get_buffer();
7268 alertpanel_error(_("Template Bcc format error."));
7270 compose_entry_append(compose, buf, COMPOSE_BCC);
7274 /* process the subject */
7275 if (tmpl->subject && *tmpl->subject != '\0') {
7277 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7278 compose->gtkaspell);
7280 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7282 quote_fmt_scan_string(tmpl->subject);
7285 buf = quote_fmt_get_buffer();
7287 alertpanel_error(_("Template subject format error."));
7289 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
7293 procmsg_msginfo_free( dummyinfo );
7296 static void compose_destroy(Compose *compose)
7298 GtkTextBuffer *buffer;
7299 GtkClipboard *clipboard;
7301 compose_list = g_list_remove(compose_list, compose);
7303 if (compose->updating) {
7304 debug_print("danger, not destroying anything now\n");
7305 compose->deferred_destroy = TRUE;
7308 /* NOTE: address_completion_end() does nothing with the window
7309 * however this may change. */
7310 address_completion_end(compose->window);
7312 slist_free_strings(compose->to_list);
7313 g_slist_free(compose->to_list);
7314 slist_free_strings(compose->newsgroup_list);
7315 g_slist_free(compose->newsgroup_list);
7316 slist_free_strings(compose->header_list);
7317 g_slist_free(compose->header_list);
7319 procmsg_msginfo_free(compose->targetinfo);
7320 procmsg_msginfo_free(compose->replyinfo);
7321 procmsg_msginfo_free(compose->fwdinfo);
7323 g_free(compose->replyto);
7324 g_free(compose->cc);
7325 g_free(compose->bcc);
7326 g_free(compose->newsgroups);
7327 g_free(compose->followup_to);
7329 g_free(compose->ml_post);
7331 g_free(compose->inreplyto);
7332 g_free(compose->references);
7333 g_free(compose->msgid);
7334 g_free(compose->boundary);
7336 g_free(compose->redirect_filename);
7337 if (compose->undostruct)
7338 undo_destroy(compose->undostruct);
7340 g_free(compose->sig_str);
7342 g_free(compose->exteditor_file);
7344 g_free(compose->orig_charset);
7346 g_free(compose->privacy_system);
7348 if (addressbook_get_target_compose() == compose)
7349 addressbook_set_target_compose(NULL);
7352 if (compose->gtkaspell) {
7353 gtkaspell_delete(compose->gtkaspell);
7354 compose->gtkaspell = NULL;
7358 prefs_common.compose_width = compose->scrolledwin->allocation.width;
7359 prefs_common.compose_height = compose->window->allocation.height;
7361 if (!gtk_widget_get_parent(compose->paned))
7362 gtk_widget_destroy(compose->paned);
7363 gtk_widget_destroy(compose->popupmenu);
7365 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
7366 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7367 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
7369 gtk_widget_destroy(compose->window);
7370 toolbar_destroy(compose->toolbar);
7371 g_free(compose->toolbar);
7372 g_mutex_free(compose->mutex);
7376 static void compose_attach_info_free(AttachInfo *ainfo)
7378 g_free(ainfo->file);
7379 g_free(ainfo->content_type);
7380 g_free(ainfo->name);
7384 static void compose_attach_remove_selected(Compose *compose)
7386 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
7387 GtkTreeSelection *selection;
7389 GtkTreeModel *model;
7391 selection = gtk_tree_view_get_selection(tree_view);
7392 sel = gtk_tree_selection_get_selected_rows(selection, &model);
7397 for (cur = sel; cur != NULL; cur = cur->next) {
7398 GtkTreePath *path = cur->data;
7399 GtkTreeRowReference *ref = gtk_tree_row_reference_new
7402 gtk_tree_path_free(path);
7405 for (cur = sel; cur != NULL; cur = cur->next) {
7406 GtkTreeRowReference *ref = cur->data;
7407 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
7410 if (gtk_tree_model_get_iter(model, &iter, path))
7411 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
7413 gtk_tree_path_free(path);
7414 gtk_tree_row_reference_free(ref);
7420 static struct _AttachProperty
7423 GtkWidget *mimetype_entry;
7424 GtkWidget *encoding_optmenu;
7425 GtkWidget *path_entry;
7426 GtkWidget *filename_entry;
7428 GtkWidget *cancel_btn;
7431 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
7433 gtk_tree_path_free((GtkTreePath *)ptr);
7436 static void compose_attach_property(Compose *compose)
7438 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
7440 GtkComboBox *optmenu;
7441 GtkTreeSelection *selection;
7443 GtkTreeModel *model;
7446 static gboolean cancelled;
7448 /* only if one selected */
7449 selection = gtk_tree_view_get_selection(tree_view);
7450 if (gtk_tree_selection_count_selected_rows(selection) != 1)
7453 sel = gtk_tree_selection_get_selected_rows(selection, &model);
7457 path = (GtkTreePath *) sel->data;
7458 gtk_tree_model_get_iter(model, &iter, path);
7459 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
7462 g_list_foreach(sel, gtk_tree_path_free_, NULL);
7468 if (!attach_prop.window)
7469 compose_attach_property_create(&cancelled);
7470 gtk_widget_grab_focus(attach_prop.ok_btn);
7471 gtk_widget_show(attach_prop.window);
7472 manage_window_set_transient(GTK_WINDOW(attach_prop.window));
7474 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
7475 if (ainfo->encoding == ENC_UNKNOWN)
7476 combobox_select_by_data(optmenu, ENC_BASE64);
7478 combobox_select_by_data(optmenu, ainfo->encoding);
7480 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
7481 ainfo->content_type ? ainfo->content_type : "");
7482 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
7483 ainfo->file ? ainfo->file : "");
7484 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
7485 ainfo->name ? ainfo->name : "");
7488 const gchar *entry_text;
7490 gchar *cnttype = NULL;
7497 gtk_widget_hide(attach_prop.window);
7502 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
7503 if (*entry_text != '\0') {
7506 text = g_strstrip(g_strdup(entry_text));
7507 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
7508 cnttype = g_strdup(text);
7511 alertpanel_error(_("Invalid MIME type."));
7517 ainfo->encoding = combobox_get_active_data(optmenu);
7519 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
7520 if (*entry_text != '\0') {
7521 if (is_file_exist(entry_text) &&
7522 (size = get_file_size(entry_text)) > 0)
7523 file = g_strdup(entry_text);
7526 (_("File doesn't exist or is empty."));
7532 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
7533 if (*entry_text != '\0') {
7534 g_free(ainfo->name);
7535 ainfo->name = g_strdup(entry_text);
7539 g_free(ainfo->content_type);
7540 ainfo->content_type = cnttype;
7543 g_free(ainfo->file);
7549 /* update tree store */
7550 text = to_human_readable(ainfo->size);
7551 gtk_tree_model_get_iter(model, &iter, path);
7552 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
7553 COL_MIMETYPE, ainfo->content_type,
7555 COL_NAME, ainfo->name,
7561 gtk_tree_path_free(path);
7564 #define SET_LABEL_AND_ENTRY(str, entry, top) \
7566 label = gtk_label_new(str); \
7567 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
7568 GTK_FILL, 0, 0, 0); \
7569 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
7571 entry = gtk_entry_new(); \
7572 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
7573 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
7576 static void compose_attach_property_create(gboolean *cancelled)
7582 GtkWidget *mimetype_entry;
7585 GtkListStore *optmenu_menu;
7586 GtkWidget *path_entry;
7587 GtkWidget *filename_entry;
7590 GtkWidget *cancel_btn;
7591 GList *mime_type_list, *strlist;
7594 debug_print("Creating attach_property window...\n");
7596 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
7597 gtk_widget_set_size_request(window, 480, -1);
7598 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
7599 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
7600 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
7601 gtk_window_set_modal(GTK_WINDOW(window), TRUE);
7602 g_signal_connect(G_OBJECT(window), "delete_event",
7603 G_CALLBACK(attach_property_delete_event),
7605 g_signal_connect(G_OBJECT(window), "key_press_event",
7606 G_CALLBACK(attach_property_key_pressed),
7609 vbox = gtk_vbox_new(FALSE, 8);
7610 gtk_container_add(GTK_CONTAINER(window), vbox);
7612 table = gtk_table_new(4, 2, FALSE);
7613 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
7614 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
7615 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
7617 label = gtk_label_new(_("MIME type"));
7618 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
7620 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
7621 mimetype_entry = gtk_combo_new();
7622 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
7623 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
7625 /* stuff with list */
7626 mime_type_list = procmime_get_mime_type_list();
7628 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
7629 MimeType *type = (MimeType *) mime_type_list->data;
7632 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
7634 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
7637 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
7638 (GCompareFunc)strcmp2);
7641 gtk_combo_set_popdown_strings(GTK_COMBO(mimetype_entry), strlist);
7643 for (mime_type_list = strlist; mime_type_list != NULL;
7644 mime_type_list = mime_type_list->next)
7645 g_free(mime_type_list->data);
7646 g_list_free(strlist);
7648 mimetype_entry = GTK_COMBO(mimetype_entry)->entry;
7650 label = gtk_label_new(_("Encoding"));
7651 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
7653 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
7655 hbox = gtk_hbox_new(FALSE, 0);
7656 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
7657 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
7659 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
7660 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
7662 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
7663 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
7664 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
7665 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
7666 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
7668 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
7670 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
7671 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
7673 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
7674 &ok_btn, GTK_STOCK_OK,
7676 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
7677 gtk_widget_grab_default(ok_btn);
7679 g_signal_connect(G_OBJECT(ok_btn), "clicked",
7680 G_CALLBACK(attach_property_ok),
7682 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
7683 G_CALLBACK(attach_property_cancel),
7686 gtk_widget_show_all(vbox);
7688 attach_prop.window = window;
7689 attach_prop.mimetype_entry = mimetype_entry;
7690 attach_prop.encoding_optmenu = optmenu;
7691 attach_prop.path_entry = path_entry;
7692 attach_prop.filename_entry = filename_entry;
7693 attach_prop.ok_btn = ok_btn;
7694 attach_prop.cancel_btn = cancel_btn;
7697 #undef SET_LABEL_AND_ENTRY
7699 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
7705 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
7711 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
7712 gboolean *cancelled)
7720 static gboolean attach_property_key_pressed(GtkWidget *widget,
7722 gboolean *cancelled)
7724 if (event && event->keyval == GDK_Escape) {
7731 static void compose_exec_ext_editor(Compose *compose)
7738 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
7739 G_DIR_SEPARATOR, compose);
7741 if (pipe(pipe_fds) < 0) {
7747 if ((pid = fork()) < 0) {
7754 /* close the write side of the pipe */
7757 compose->exteditor_file = g_strdup(tmp);
7758 compose->exteditor_pid = pid;
7760 compose_set_ext_editor_sensitive(compose, FALSE);
7762 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
7763 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
7767 } else { /* process-monitoring process */
7773 /* close the read side of the pipe */
7776 if (compose_write_body_to_file(compose, tmp) < 0) {
7777 fd_write_all(pipe_fds[1], "2\n", 2);
7781 pid_ed = compose_exec_ext_editor_real(tmp);
7783 fd_write_all(pipe_fds[1], "1\n", 2);
7787 /* wait until editor is terminated */
7788 waitpid(pid_ed, NULL, 0);
7790 fd_write_all(pipe_fds[1], "0\n", 2);
7797 #endif /* G_OS_UNIX */
7801 static gint compose_exec_ext_editor_real(const gchar *file)
7808 g_return_val_if_fail(file != NULL, -1);
7810 if ((pid = fork()) < 0) {
7815 if (pid != 0) return pid;
7817 /* grandchild process */
7819 if (setpgid(0, getppid()))
7822 if (prefs_common.ext_editor_cmd &&
7823 (p = strchr(prefs_common.ext_editor_cmd, '%')) &&
7824 *(p + 1) == 's' && !strchr(p + 2, '%')) {
7825 g_snprintf(buf, sizeof(buf), prefs_common.ext_editor_cmd, file);
7827 if (prefs_common.ext_editor_cmd)
7828 g_warning("External editor command line is invalid: '%s'\n",
7829 prefs_common.ext_editor_cmd);
7830 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
7833 cmdline = strsplit_with_quote(buf, " ", 1024);
7834 execvp(cmdline[0], cmdline);
7837 g_strfreev(cmdline);
7842 static gboolean compose_ext_editor_kill(Compose *compose)
7844 pid_t pgid = compose->exteditor_pid * -1;
7847 ret = kill(pgid, 0);
7849 if (ret == 0 || (ret == -1 && EPERM == errno)) {
7853 msg = g_strdup_printf
7854 (_("The external editor is still working.\n"
7855 "Force terminating the process?\n"
7856 "process group id: %d"), -pgid);
7857 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
7858 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
7862 if (val == G_ALERTALTERNATE) {
7863 g_source_remove(compose->exteditor_tag);
7864 g_io_channel_shutdown(compose->exteditor_ch,
7866 g_io_channel_unref(compose->exteditor_ch);
7868 if (kill(pgid, SIGTERM) < 0) perror("kill");
7869 waitpid(compose->exteditor_pid, NULL, 0);
7871 g_warning("Terminated process group id: %d", -pgid);
7872 g_warning("Temporary file: %s",
7873 compose->exteditor_file);
7875 compose_set_ext_editor_sensitive(compose, TRUE);
7877 g_free(compose->exteditor_file);
7878 compose->exteditor_file = NULL;
7879 compose->exteditor_pid = -1;
7880 compose->exteditor_ch = NULL;
7881 compose->exteditor_tag = -1;
7889 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
7893 Compose *compose = (Compose *)data;
7896 debug_print(_("Compose: input from monitoring process\n"));
7898 g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL);
7900 g_io_channel_shutdown(source, FALSE, NULL);
7901 g_io_channel_unref(source);
7903 waitpid(compose->exteditor_pid, NULL, 0);
7905 if (buf[0] == '0') { /* success */
7906 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
7907 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
7909 gtk_text_buffer_set_text(buffer, "", -1);
7910 compose_insert_file(compose, compose->exteditor_file);
7911 compose_changed_cb(NULL, compose);
7913 if (g_unlink(compose->exteditor_file) < 0)
7914 FILE_OP_ERROR(compose->exteditor_file, "unlink");
7915 } else if (buf[0] == '1') { /* failed */
7916 g_warning("Couldn't exec external editor\n");
7917 if (g_unlink(compose->exteditor_file) < 0)
7918 FILE_OP_ERROR(compose->exteditor_file, "unlink");
7919 } else if (buf[0] == '2') {
7920 g_warning("Couldn't write to file\n");
7921 } else if (buf[0] == '3') {
7922 g_warning("Pipe read failed\n");
7925 compose_set_ext_editor_sensitive(compose, TRUE);
7927 g_free(compose->exteditor_file);
7928 compose->exteditor_file = NULL;
7929 compose->exteditor_pid = -1;
7930 compose->exteditor_ch = NULL;
7931 compose->exteditor_tag = -1;
7936 static void compose_set_ext_editor_sensitive(Compose *compose,
7939 GtkItemFactory *ifactory;
7941 ifactory = gtk_item_factory_from_widget(compose->menubar);
7943 menu_set_sensitive(ifactory, "/Message/Send", sensitive);
7944 menu_set_sensitive(ifactory, "/Message/Send later", sensitive);
7945 menu_set_sensitive(ifactory, "/Message/Insert file", sensitive);
7946 menu_set_sensitive(ifactory, "/Message/Insert signature", sensitive);
7947 menu_set_sensitive(ifactory, "/Edit/Wrap current paragraph", sensitive);
7948 menu_set_sensitive(ifactory, "/Edit/Wrap all long lines", sensitive);
7949 menu_set_sensitive(ifactory, "/Edit/Edit with external editor",
7952 gtk_widget_set_sensitive(compose->text, sensitive);
7953 if (compose->toolbar->send_btn)
7954 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
7955 if (compose->toolbar->sendl_btn)
7956 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
7957 if (compose->toolbar->draft_btn)
7958 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
7959 if (compose->toolbar->insert_btn)
7960 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
7961 if (compose->toolbar->sig_btn)
7962 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
7963 if (compose->toolbar->exteditor_btn)
7964 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
7965 if (compose->toolbar->linewrap_current_btn)
7966 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
7967 if (compose->toolbar->linewrap_all_btn)
7968 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
7970 #endif /* G_OS_UNIX */
7973 * compose_undo_state_changed:
7975 * Change the sensivity of the menuentries undo and redo
7977 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
7978 gint redo_state, gpointer data)
7980 GtkWidget *widget = GTK_WIDGET(data);
7981 GtkItemFactory *ifactory;
7983 g_return_if_fail(widget != NULL);
7985 ifactory = gtk_item_factory_from_widget(widget);
7987 switch (undo_state) {
7988 case UNDO_STATE_TRUE:
7989 if (!undostruct->undo_state) {
7990 undostruct->undo_state = TRUE;
7991 menu_set_sensitive(ifactory, "/Edit/Undo", TRUE);
7994 case UNDO_STATE_FALSE:
7995 if (undostruct->undo_state) {
7996 undostruct->undo_state = FALSE;
7997 menu_set_sensitive(ifactory, "/Edit/Undo", FALSE);
8000 case UNDO_STATE_UNCHANGED:
8002 case UNDO_STATE_REFRESH:
8003 menu_set_sensitive(ifactory, "/Edit/Undo",
8004 undostruct->undo_state);
8007 g_warning("Undo state not recognized");
8011 switch (redo_state) {
8012 case UNDO_STATE_TRUE:
8013 if (!undostruct->redo_state) {
8014 undostruct->redo_state = TRUE;
8015 menu_set_sensitive(ifactory, "/Edit/Redo", TRUE);
8018 case UNDO_STATE_FALSE:
8019 if (undostruct->redo_state) {
8020 undostruct->redo_state = FALSE;
8021 menu_set_sensitive(ifactory, "/Edit/Redo", FALSE);
8024 case UNDO_STATE_UNCHANGED:
8026 case UNDO_STATE_REFRESH:
8027 menu_set_sensitive(ifactory, "/Edit/Redo",
8028 undostruct->redo_state);
8031 g_warning("Redo state not recognized");
8036 /* callback functions */
8038 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
8039 * includes "non-client" (windows-izm) in calculation, so this calculation
8040 * may not be accurate.
8042 static gboolean compose_edit_size_alloc(GtkEditable *widget,
8043 GtkAllocation *allocation,
8044 GtkSHRuler *shruler)
8046 if (prefs_common.show_ruler) {
8047 gint char_width = 0, char_height = 0;
8048 gint line_width_in_chars;
8050 gtkut_get_font_size(GTK_WIDGET(widget),
8051 &char_width, &char_height);
8052 line_width_in_chars =
8053 (allocation->width - allocation->x) / char_width;
8055 /* got the maximum */
8056 gtk_ruler_set_range(GTK_RULER(shruler),
8057 0.0, line_width_in_chars, 0,
8058 /*line_width_in_chars*/ char_width);
8064 static void account_activated(GtkComboBox *optmenu, gpointer data)
8066 Compose *compose = (Compose *)data;
8069 gchar *folderidentifier;
8070 gint account_id = 0;
8074 /* Get ID of active account in the combo box */
8075 menu = gtk_combo_box_get_model(optmenu);
8076 gtk_combo_box_get_active_iter(optmenu, &iter);
8077 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
8079 ac = account_find_from_id(account_id);
8080 g_return_if_fail(ac != NULL);
8082 if (ac != compose->account)
8083 compose_select_account(compose, ac, FALSE);
8085 /* Set message save folder */
8086 if (account_get_special_folder(compose->account, F_OUTBOX)) {
8087 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
8089 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
8090 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
8092 gtk_editable_delete_text(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
8093 if (account_get_special_folder(compose->account, F_OUTBOX)) {
8094 folderidentifier = folder_item_get_identifier(account_get_special_folder
8095 (compose->account, F_OUTBOX));
8096 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
8097 g_free(folderidentifier);
8101 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
8102 GtkTreeViewColumn *column, Compose *compose)
8104 compose_attach_property(compose);
8107 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
8110 Compose *compose = (Compose *)data;
8111 GtkTreeSelection *attach_selection;
8112 gint attach_nr_selected;
8113 GtkItemFactory *ifactory;
8115 if (!event) return FALSE;
8117 if (event->button == 3) {
8118 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
8119 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
8120 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
8122 if (attach_nr_selected > 0)
8124 menu_set_sensitive(ifactory, "/Remove", TRUE);
8125 menu_set_sensitive(ifactory, "/Properties...", TRUE);
8127 menu_set_sensitive(ifactory, "/Remove", FALSE);
8128 menu_set_sensitive(ifactory, "/Properties...", FALSE);
8131 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
8132 NULL, NULL, event->button, event->time);
8139 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
8142 Compose *compose = (Compose *)data;
8144 if (!event) return FALSE;
8146 switch (event->keyval) {
8148 compose_attach_remove_selected(compose);
8154 static void compose_allow_user_actions (Compose *compose, gboolean allow)
8156 GtkItemFactory *ifactory = gtk_item_factory_from_widget(compose->menubar);
8157 toolbar_comp_set_sensitive(compose, allow);
8158 menu_set_sensitive(ifactory, "/Message", allow);
8159 menu_set_sensitive(ifactory, "/Edit", allow);
8161 menu_set_sensitive(ifactory, "/Spelling", allow);
8163 menu_set_sensitive(ifactory, "/Options", allow);
8164 menu_set_sensitive(ifactory, "/Tools", allow);
8165 menu_set_sensitive(ifactory, "/Help", allow);
8167 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
8171 static void compose_send_cb(gpointer data, guint action, GtkWidget *widget)
8173 Compose *compose = (Compose *)data;
8175 if (prefs_common.work_offline &&
8176 !inc_offline_should_override(TRUE,
8177 _("Claws Mail needs network access in order "
8178 "to send this email.")))
8181 if (compose->draft_timeout_tag != -1) { /* CLAWS: disable draft timeout */
8182 g_source_remove(compose->draft_timeout_tag);
8183 compose->draft_timeout_tag = -1;
8186 compose_send(compose);
8189 static void compose_send_later_cb(gpointer data, guint action,
8192 Compose *compose = (Compose *)data;
8196 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
8200 compose_close(compose);
8201 } else if (val == -1) {
8202 alertpanel_error(_("Could not queue message."));
8203 } else if (val == -2) {
8204 alertpanel_error(_("Could not queue message:\n\n%s."), strerror(errno));
8205 } else if (val == -3) {
8206 if (privacy_peek_error())
8207 alertpanel_error(_("Could not queue message for sending:\n\n"
8208 "Signature failed: %s"), privacy_get_error());
8209 } else if (val == -4) {
8210 alertpanel_error(_("Could not queue message for sending:\n\n"
8211 "Charset conversion failed."));
8212 } else if (val == -5) {
8213 alertpanel_error(_("Could not queue message for sending:\n\n"
8214 "Couldn't get recipient encryption key."));
8215 } else if (val == -6) {
8218 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
8221 void compose_draft (gpointer data, guint action)
8223 compose_draft_cb(data, action, NULL);
8226 #define DRAFTED_AT_EXIT "drafted_at_exit"
8227 void compose_clear_exit_drafts(void)
8229 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8230 DRAFTED_AT_EXIT, NULL);
8231 if (is_file_exist(filepath))
8237 static void compose_register_draft(MsgInfo *info)
8239 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8240 DRAFTED_AT_EXIT, NULL);
8241 FILE *fp = fopen(filepath, "ab");
8244 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
8252 void compose_reopen_exit_drafts(void)
8254 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8255 DRAFTED_AT_EXIT, NULL);
8256 FILE *fp = fopen(filepath, "rb");
8260 while (fgets(buf, sizeof(buf), fp)) {
8261 gchar **parts = g_strsplit(buf, "\t", 2);
8262 const gchar *folder = parts[0];
8263 int msgnum = parts[1] ? atoi(parts[1]):-1;
8265 if (folder && *folder && msgnum > -1) {
8266 FolderItem *item = folder_find_item_from_identifier(folder);
8267 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
8269 compose_reedit(info, FALSE);
8276 compose_clear_exit_drafts();
8279 static void compose_draft_cb(gpointer data, guint action, GtkWidget *widget)
8281 Compose *compose = (Compose *)data;
8285 MsgFlags flag = {0, 0};
8286 static gboolean lock = FALSE;
8287 MsgInfo *newmsginfo;
8289 gboolean target_locked = FALSE;
8293 draft = account_get_special_folder(compose->account, F_DRAFT);
8294 g_return_if_fail(draft != NULL);
8296 if (!g_mutex_trylock(compose->mutex)) {
8297 /* we don't want to lock the mutex once it's available,
8298 * because as the only other part of compose.c locking
8299 * it is compose_close - which means once unlocked,
8300 * the compose struct will be freed */
8301 debug_print("couldn't lock mutex, probably sending\n");
8307 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
8308 G_DIR_SEPARATOR, compose);
8309 if ((fp = g_fopen(tmp, "wb")) == NULL) {
8310 FILE_OP_ERROR(tmp, "fopen");
8314 /* chmod for security */
8315 if (change_file_mode_rw(fp, tmp) < 0) {
8316 FILE_OP_ERROR(tmp, "chmod");
8317 g_warning("can't change file mode\n");
8320 /* Save draft infos */
8321 fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id);
8322 fprintf(fp, "S:%s\n", compose->account->address);
8324 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
8325 gchar *savefolderid;
8327 savefolderid = gtk_editable_get_chars(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
8328 fprintf(fp, "SCF:%s\n", savefolderid);
8329 g_free(savefolderid);
8331 if (compose->return_receipt) {
8332 fprintf(fp, "RRCPT:1\n");
8334 if (compose->privacy_system) {
8335 fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing);
8336 fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption);
8337 fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system);
8340 /* Message-ID of message replying to */
8341 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
8344 folderid = folder_item_get_identifier(compose->replyinfo->folder);
8345 fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid);
8348 /* Message-ID of message forwarding to */
8349 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
8352 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
8353 fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid);
8357 /* end of headers */
8358 fprintf(fp, "X-Claws-End-Special-Headers: 1\n");
8360 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
8368 if (compose->targetinfo) {
8369 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
8370 flag.perm_flags = target_locked?MSG_LOCKED:0;
8372 flag.tmp_flags = MSG_DRAFT;
8374 folder_item_scan(draft);
8375 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
8378 if (action != COMPOSE_AUTO_SAVE)
8379 alertpanel_error(_("Could not save draft."));
8384 if (compose->mode == COMPOSE_REEDIT) {
8385 compose_remove_reedit_target(compose, TRUE);
8388 newmsginfo = folder_item_get_msginfo(draft, msgnum);
8390 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
8392 procmsg_msginfo_set_flags(newmsginfo, MSG_LOCKED, MSG_DRAFT);
8394 procmsg_msginfo_set_flags(newmsginfo, 0, MSG_DRAFT);
8395 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
8396 procmsg_msginfo_set_flags(newmsginfo, 0,
8397 MSG_HAS_ATTACHMENT);
8399 if (action == COMPOSE_DRAFT_FOR_EXIT) {
8400 compose_register_draft(newmsginfo);
8402 procmsg_msginfo_free(newmsginfo);
8405 folder_item_scan(draft);
8407 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
8409 g_mutex_unlock(compose->mutex); /* must be done before closing */
8410 compose_close(compose);
8416 path = folder_item_fetch_msg(draft, msgnum);
8418 debug_print("can't fetch %s:%d\n",draft->path, msgnum);
8421 if (g_stat(path, &s) < 0) {
8422 FILE_OP_ERROR(path, "stat");
8428 procmsg_msginfo_free(compose->targetinfo);
8429 compose->targetinfo = procmsg_msginfo_new();
8430 compose->targetinfo->msgnum = msgnum;
8431 compose->targetinfo->size = s.st_size;
8432 compose->targetinfo->mtime = s.st_mtime;
8433 compose->targetinfo->folder = draft;
8435 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
8436 compose->mode = COMPOSE_REEDIT;
8438 if (action == COMPOSE_AUTO_SAVE) {
8439 compose->autosaved_draft = compose->targetinfo;
8441 compose->modified = FALSE;
8442 compose_set_title(compose);
8446 g_mutex_unlock(compose->mutex);
8449 static void compose_attach_cb(gpointer data, guint action, GtkWidget *widget)
8451 Compose *compose = (Compose *)data;
8454 if (compose->redirect_filename != NULL)
8457 file_list = filesel_select_multiple_files_open(_("Select file"));
8462 for ( tmp = file_list; tmp; tmp = tmp->next) {
8463 gchar *file = (gchar *) tmp->data;
8464 gchar *utf8_filename = conv_filename_to_utf8(file);
8465 compose_attach_append(compose, file, utf8_filename, NULL);
8466 compose_changed_cb(NULL, compose);
8468 g_free(utf8_filename);
8470 g_list_free(file_list);
8474 static void compose_insert_file_cb(gpointer data, guint action,
8477 Compose *compose = (Compose *)data;
8480 file_list = filesel_select_multiple_files_open(_("Select file"));
8485 for ( tmp = file_list; tmp; tmp = tmp->next) {
8486 gchar *file = (gchar *) tmp->data;
8487 gchar *filedup = g_strdup(file);
8488 gchar *shortfile = g_path_get_basename(filedup);
8489 ComposeInsertResult res;
8491 res = compose_insert_file(compose, file);
8492 if (res == COMPOSE_INSERT_READ_ERROR) {
8493 alertpanel_error(_("File '%s' could not be read."), shortfile);
8494 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
8495 alertpanel_error(_("File '%s' contained invalid characters\n"
8496 "for the current encoding, insertion may be incorrect."), shortfile);
8502 g_list_free(file_list);
8506 static void compose_insert_sig_cb(gpointer data, guint action,
8509 Compose *compose = (Compose *)data;
8511 compose_insert_sig(compose, FALSE);
8514 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
8518 Compose *compose = (Compose *)data;
8520 gtkut_widget_get_uposition(widget, &x, &y);
8521 prefs_common.compose_x = x;
8522 prefs_common.compose_y = y;
8524 if (compose->sending || compose->updating)
8526 compose_close_cb(compose, 0, NULL);
8530 void compose_close_toolbar(Compose *compose)
8532 compose_close_cb(compose, 0, NULL);
8535 static void compose_close_cb(gpointer data, guint action, GtkWidget *widget)
8537 Compose *compose = (Compose *)data;
8541 if (compose->exteditor_tag != -1) {
8542 if (!compose_ext_editor_kill(compose))
8547 if (compose->modified) {
8548 val = alertpanel(_("Discard message"),
8549 _("This message has been modified. Discard it?"),
8550 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
8553 case G_ALERTDEFAULT:
8554 if (prefs_common.autosave)
8555 compose_remove_draft(compose);
8557 case G_ALERTALTERNATE:
8558 compose_draft_cb(data, COMPOSE_QUIT_EDITING, NULL);
8565 compose_close(compose);
8568 static void compose_set_encoding_cb(gpointer data, guint action,
8571 Compose *compose = (Compose *)data;
8573 if (GTK_CHECK_MENU_ITEM(widget)->active)
8574 compose->out_encoding = (CharSet)action;
8577 static void compose_address_cb(gpointer data, guint action, GtkWidget *widget)
8579 Compose *compose = (Compose *)data;
8581 addressbook_open(compose);
8584 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
8586 Compose *compose = (Compose *)data;
8591 tmpl = g_object_get_data(G_OBJECT(widget), "template");
8592 g_return_if_fail(tmpl != NULL);
8594 msg = g_strdup_printf(_("Do you want to apply the template '%s' ?"),
8596 val = alertpanel(_("Apply template"), msg,
8597 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
8600 if (val == G_ALERTDEFAULT)
8601 compose_template_apply(compose, tmpl, TRUE);
8602 else if (val == G_ALERTALTERNATE)
8603 compose_template_apply(compose, tmpl, FALSE);
8606 static void compose_ext_editor_cb(gpointer data, guint action,
8609 Compose *compose = (Compose *)data;
8611 compose_exec_ext_editor(compose);
8614 static void compose_undo_cb(Compose *compose)
8616 gboolean prev_autowrap = compose->autowrap;
8618 compose->autowrap = FALSE;
8619 undo_undo(compose->undostruct);
8620 compose->autowrap = prev_autowrap;
8623 static void compose_redo_cb(Compose *compose)
8625 gboolean prev_autowrap = compose->autowrap;
8627 compose->autowrap = FALSE;
8628 undo_redo(compose->undostruct);
8629 compose->autowrap = prev_autowrap;
8632 static void entry_cut_clipboard(GtkWidget *entry)
8634 if (GTK_IS_EDITABLE(entry))
8635 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
8636 else if (GTK_IS_TEXT_VIEW(entry))
8637 gtk_text_buffer_cut_clipboard(
8638 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
8639 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
8643 static void entry_copy_clipboard(GtkWidget *entry)
8645 if (GTK_IS_EDITABLE(entry))
8646 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
8647 else if (GTK_IS_TEXT_VIEW(entry))
8648 gtk_text_buffer_copy_clipboard(
8649 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
8650 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
8653 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
8654 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
8656 if (GTK_IS_TEXT_VIEW(entry)) {
8657 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
8658 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
8659 GtkTextIter start_iter, end_iter;
8661 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
8663 if (contents == NULL)
8666 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
8668 /* we shouldn't delete the selection when middle-click-pasting, or we
8669 * can't mid-click-paste our own selection */
8670 if (clip != GDK_SELECTION_PRIMARY) {
8671 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
8674 if (insert_place == NULL) {
8675 /* if insert_place isn't specified, insert at the cursor.
8676 * used for Ctrl-V pasting */
8677 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
8678 start = gtk_text_iter_get_offset(&start_iter);
8679 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
8681 /* if insert_place is specified, paste here.
8682 * used for mid-click-pasting */
8683 start = gtk_text_iter_get_offset(insert_place);
8684 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
8688 /* paste unwrapped: mark the paste so it's not wrapped later */
8689 end = start + strlen(contents);
8690 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
8691 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
8692 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
8693 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
8694 /* rewrap paragraph now (after a mid-click-paste) */
8695 mark_start = gtk_text_buffer_get_insert(buffer);
8696 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
8697 gtk_text_iter_backward_char(&start_iter);
8698 compose_beautify_paragraph(compose, &start_iter, TRUE);
8700 } else if (GTK_IS_EDITABLE(entry))
8701 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
8705 static void entry_allsel(GtkWidget *entry)
8707 if (GTK_IS_EDITABLE(entry))
8708 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
8709 else if (GTK_IS_TEXT_VIEW(entry)) {
8710 GtkTextIter startiter, enditer;
8711 GtkTextBuffer *textbuf;
8713 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
8714 gtk_text_buffer_get_start_iter(textbuf, &startiter);
8715 gtk_text_buffer_get_end_iter(textbuf, &enditer);
8717 gtk_text_buffer_move_mark_by_name(textbuf,
8718 "selection_bound", &startiter);
8719 gtk_text_buffer_move_mark_by_name(textbuf,
8720 "insert", &enditer);
8724 static void compose_cut_cb(Compose *compose)
8726 if (compose->focused_editable
8728 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
8731 entry_cut_clipboard(compose->focused_editable);
8734 static void compose_copy_cb(Compose *compose)
8736 if (compose->focused_editable
8738 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
8741 entry_copy_clipboard(compose->focused_editable);
8744 static void compose_paste_cb(Compose *compose)
8747 GtkTextBuffer *buffer;
8749 if (compose->focused_editable &&
8750 GTK_WIDGET_HAS_FOCUS(compose->focused_editable))
8751 entry_paste_clipboard(compose, compose->focused_editable,
8752 prefs_common.linewrap_pastes,
8753 GDK_SELECTION_CLIPBOARD, NULL);
8757 static void compose_paste_as_quote_cb(Compose *compose)
8759 gint wrap_quote = prefs_common.linewrap_quote;
8760 if (compose->focused_editable
8762 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
8765 /* let text_insert() (called directly or at a later time
8766 * after the gtk_editable_paste_clipboard) know that
8767 * text is to be inserted as a quotation. implemented
8768 * by using a simple refcount... */
8769 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
8770 G_OBJECT(compose->focused_editable),
8771 "paste_as_quotation"));
8772 g_object_set_data(G_OBJECT(compose->focused_editable),
8773 "paste_as_quotation",
8774 GINT_TO_POINTER(paste_as_quotation + 1));
8775 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
8776 entry_paste_clipboard(compose, compose->focused_editable,
8777 prefs_common.linewrap_pastes,
8778 GDK_SELECTION_CLIPBOARD, NULL);
8779 prefs_common.linewrap_quote = wrap_quote;
8783 static void compose_paste_no_wrap_cb(Compose *compose)
8786 GtkTextBuffer *buffer;
8788 if (compose->focused_editable
8790 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
8793 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
8794 GDK_SELECTION_CLIPBOARD, NULL);
8798 static void compose_paste_wrap_cb(Compose *compose)
8801 GtkTextBuffer *buffer;
8803 if (compose->focused_editable
8805 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
8808 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
8809 GDK_SELECTION_CLIPBOARD, NULL);
8813 static void compose_allsel_cb(Compose *compose)
8815 if (compose->focused_editable
8817 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
8820 entry_allsel(compose->focused_editable);
8823 static void textview_move_beginning_of_line (GtkTextView *text)
8825 GtkTextBuffer *buffer;
8829 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
8831 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8832 mark = gtk_text_buffer_get_insert(buffer);
8833 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
8834 gtk_text_iter_set_line_offset(&ins, 0);
8835 gtk_text_buffer_place_cursor(buffer, &ins);
8838 static void textview_move_forward_character (GtkTextView *text)
8840 GtkTextBuffer *buffer;
8844 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
8846 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8847 mark = gtk_text_buffer_get_insert(buffer);
8848 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
8849 if (gtk_text_iter_forward_cursor_position(&ins))
8850 gtk_text_buffer_place_cursor(buffer, &ins);
8853 static void textview_move_backward_character (GtkTextView *text)
8855 GtkTextBuffer *buffer;
8859 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
8861 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8862 mark = gtk_text_buffer_get_insert(buffer);
8863 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
8864 if (gtk_text_iter_backward_cursor_position(&ins))
8865 gtk_text_buffer_place_cursor(buffer, &ins);
8868 static void textview_move_forward_word (GtkTextView *text)
8870 GtkTextBuffer *buffer;
8875 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
8877 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8878 mark = gtk_text_buffer_get_insert(buffer);
8879 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
8880 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
8881 if (gtk_text_iter_forward_word_ends(&ins, count)) {
8882 gtk_text_iter_backward_word_start(&ins);
8883 gtk_text_buffer_place_cursor(buffer, &ins);
8887 static void textview_move_backward_word (GtkTextView *text)
8889 GtkTextBuffer *buffer;
8894 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
8896 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8897 mark = gtk_text_buffer_get_insert(buffer);
8898 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
8899 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
8900 if (gtk_text_iter_backward_word_starts(&ins, 1))
8901 gtk_text_buffer_place_cursor(buffer, &ins);
8904 static void textview_move_end_of_line (GtkTextView *text)
8906 GtkTextBuffer *buffer;
8910 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
8912 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8913 mark = gtk_text_buffer_get_insert(buffer);
8914 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
8915 if (gtk_text_iter_forward_to_line_end(&ins))
8916 gtk_text_buffer_place_cursor(buffer, &ins);
8919 static void textview_move_next_line (GtkTextView *text)
8921 GtkTextBuffer *buffer;
8926 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
8928 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8929 mark = gtk_text_buffer_get_insert(buffer);
8930 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
8931 offset = gtk_text_iter_get_line_offset(&ins);
8932 if (gtk_text_iter_forward_line(&ins)) {
8933 gtk_text_iter_set_line_offset(&ins, offset);
8934 gtk_text_buffer_place_cursor(buffer, &ins);
8938 static void textview_move_previous_line (GtkTextView *text)
8940 GtkTextBuffer *buffer;
8945 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
8947 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8948 mark = gtk_text_buffer_get_insert(buffer);
8949 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
8950 offset = gtk_text_iter_get_line_offset(&ins);
8951 if (gtk_text_iter_backward_line(&ins)) {
8952 gtk_text_iter_set_line_offset(&ins, offset);
8953 gtk_text_buffer_place_cursor(buffer, &ins);
8957 static void textview_delete_forward_character (GtkTextView *text)
8959 GtkTextBuffer *buffer;
8961 GtkTextIter ins, end_iter;
8963 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
8965 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8966 mark = gtk_text_buffer_get_insert(buffer);
8967 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
8969 if (gtk_text_iter_forward_char(&end_iter)) {
8970 gtk_text_buffer_delete(buffer, &ins, &end_iter);
8974 static void textview_delete_backward_character (GtkTextView *text)
8976 GtkTextBuffer *buffer;
8978 GtkTextIter ins, end_iter;
8980 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
8982 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8983 mark = gtk_text_buffer_get_insert(buffer);
8984 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
8986 if (gtk_text_iter_backward_char(&end_iter)) {
8987 gtk_text_buffer_delete(buffer, &end_iter, &ins);
8991 static void textview_delete_forward_word (GtkTextView *text)
8993 GtkTextBuffer *buffer;
8995 GtkTextIter ins, end_iter;
8997 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
8999 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9000 mark = gtk_text_buffer_get_insert(buffer);
9001 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9003 if (gtk_text_iter_forward_word_end(&end_iter)) {
9004 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9008 static void textview_delete_backward_word (GtkTextView *text)
9010 GtkTextBuffer *buffer;
9012 GtkTextIter ins, end_iter;
9014 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9016 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9017 mark = gtk_text_buffer_get_insert(buffer);
9018 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9020 if (gtk_text_iter_backward_word_start(&end_iter)) {
9021 gtk_text_buffer_delete(buffer, &end_iter, &ins);
9025 static void textview_delete_line (GtkTextView *text)
9027 GtkTextBuffer *buffer;
9029 GtkTextIter ins, start_iter, end_iter;
9032 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9034 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9035 mark = gtk_text_buffer_get_insert(buffer);
9036 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9039 gtk_text_iter_set_line_offset(&start_iter, 0);
9042 if (gtk_text_iter_ends_line(&end_iter))
9043 found = gtk_text_iter_forward_char(&end_iter);
9045 found = gtk_text_iter_forward_to_line_end(&end_iter);
9048 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
9051 static void textview_delete_to_line_end (GtkTextView *text)
9053 GtkTextBuffer *buffer;
9055 GtkTextIter ins, end_iter;
9058 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9060 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9061 mark = gtk_text_buffer_get_insert(buffer);
9062 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9064 if (gtk_text_iter_ends_line(&end_iter))
9065 found = gtk_text_iter_forward_char(&end_iter);
9067 found = gtk_text_iter_forward_to_line_end(&end_iter);
9069 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9072 static void compose_advanced_action_cb(Compose *compose,
9073 ComposeCallAdvancedAction action)
9075 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9077 void (*do_action) (GtkTextView *text);
9078 } action_table[] = {
9079 {textview_move_beginning_of_line},
9080 {textview_move_forward_character},
9081 {textview_move_backward_character},
9082 {textview_move_forward_word},
9083 {textview_move_backward_word},
9084 {textview_move_end_of_line},
9085 {textview_move_next_line},
9086 {textview_move_previous_line},
9087 {textview_delete_forward_character},
9088 {textview_delete_backward_character},
9089 {textview_delete_forward_word},
9090 {textview_delete_backward_word},
9091 {textview_delete_line},
9092 {NULL}, /* gtk_stext_delete_line_n */
9093 {textview_delete_to_line_end}
9096 if (!GTK_WIDGET_HAS_FOCUS(text)) return;
9098 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
9099 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
9100 if (action_table[action].do_action)
9101 action_table[action].do_action(text);
9103 g_warning("Not implemented yet.");
9107 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
9111 if (GTK_IS_EDITABLE(widget)) {
9112 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
9113 gtk_editable_set_position(GTK_EDITABLE(widget),
9116 if (widget->parent && widget->parent->parent
9117 && widget->parent->parent->parent) {
9118 if (GTK_IS_SCROLLED_WINDOW(widget->parent->parent->parent)) {
9119 gint y = widget->allocation.y;
9120 gint height = widget->allocation.height;
9121 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
9122 (GTK_SCROLLED_WINDOW(widget->parent->parent->parent));
9124 if (y < (int)shown->value) {
9125 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown), y - 1);
9127 if (y + height > (int)shown->value + (int)shown->page_size) {
9128 if (y - height - 1 < (int)shown->upper - (int)shown->page_size) {
9129 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
9130 y + height - (int)shown->page_size - 1);
9132 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
9133 (int)shown->upper - (int)shown->page_size - 1);
9140 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
9141 compose->focused_editable = widget;
9144 if (GTK_IS_TEXT_VIEW(widget)
9145 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
9146 gtk_widget_ref(compose->notebook);
9147 gtk_widget_ref(compose->edit_vbox);
9148 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
9149 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
9150 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
9151 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
9152 gtk_widget_unref(compose->notebook);
9153 gtk_widget_unref(compose->edit_vbox);
9154 g_signal_handlers_block_by_func(G_OBJECT(widget),
9155 G_CALLBACK(compose_grab_focus_cb),
9157 gtk_widget_grab_focus(widget);
9158 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
9159 G_CALLBACK(compose_grab_focus_cb),
9161 } else if (!GTK_IS_TEXT_VIEW(widget)
9162 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
9163 gtk_widget_ref(compose->notebook);
9164 gtk_widget_ref(compose->edit_vbox);
9165 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
9166 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
9167 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
9168 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
9169 gtk_widget_unref(compose->notebook);
9170 gtk_widget_unref(compose->edit_vbox);
9171 g_signal_handlers_block_by_func(G_OBJECT(widget),
9172 G_CALLBACK(compose_grab_focus_cb),
9174 gtk_widget_grab_focus(widget);
9175 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
9176 G_CALLBACK(compose_grab_focus_cb),
9182 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
9184 compose->modified = TRUE;
9186 compose_set_title(compose);
9190 static void compose_wrap_cb(gpointer data, guint action, GtkWidget *widget)
9192 Compose *compose = (Compose *)data;
9195 compose_wrap_all_full(compose, TRUE);
9197 compose_beautify_paragraph(compose, NULL, TRUE);
9200 static void compose_find_cb(gpointer data, guint action, GtkWidget *widget)
9202 Compose *compose = (Compose *)data;
9204 message_search_compose(compose);
9207 static void compose_toggle_autowrap_cb(gpointer data, guint action,
9210 Compose *compose = (Compose *)data;
9211 compose->autowrap = GTK_CHECK_MENU_ITEM(widget)->active;
9212 if (compose->autowrap)
9213 compose_wrap_all_full(compose, TRUE);
9214 compose->autowrap = GTK_CHECK_MENU_ITEM(widget)->active;
9217 static void compose_toggle_sign_cb(gpointer data, guint action,
9220 Compose *compose = (Compose *)data;
9222 if (GTK_CHECK_MENU_ITEM(widget)->active)
9223 compose->use_signing = TRUE;
9225 compose->use_signing = FALSE;
9228 static void compose_toggle_encrypt_cb(gpointer data, guint action,
9231 Compose *compose = (Compose *)data;
9233 if (GTK_CHECK_MENU_ITEM(widget)->active)
9234 compose->use_encryption = TRUE;
9236 compose->use_encryption = FALSE;
9239 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
9241 g_free(compose->privacy_system);
9243 compose->privacy_system = g_strdup(account->default_privacy_system);
9244 compose_update_privacy_system_menu_item(compose, warn);
9247 static void compose_toggle_ruler_cb(gpointer data, guint action,
9250 Compose *compose = (Compose *)data;
9252 if (GTK_CHECK_MENU_ITEM(widget)->active) {
9253 gtk_widget_show(compose->ruler_hbox);
9254 prefs_common.show_ruler = TRUE;
9256 gtk_widget_hide(compose->ruler_hbox);
9257 gtk_widget_queue_resize(compose->edit_vbox);
9258 prefs_common.show_ruler = FALSE;
9262 static void compose_attach_drag_received_cb (GtkWidget *widget,
9263 GdkDragContext *context,
9266 GtkSelectionData *data,
9271 Compose *compose = (Compose *)user_data;
9274 if (gdk_atom_name(data->type) &&
9275 !strcmp(gdk_atom_name(data->type), "text/uri-list")
9276 && gtk_drag_get_source_widget(context) !=
9277 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
9278 list = uri_list_extract_filenames((const gchar *)data->data);
9279 for (tmp = list; tmp != NULL; tmp = tmp->next) {
9280 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
9281 compose_attach_append
9282 (compose, (const gchar *)tmp->data,
9283 utf8_filename, NULL);
9284 g_free(utf8_filename);
9286 if (list) compose_changed_cb(NULL, compose);
9287 list_free_strings(list);
9289 } else if (gtk_drag_get_source_widget(context)
9290 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
9291 /* comes from our summaryview */
9292 SummaryView * summaryview = NULL;
9293 GSList * list = NULL, *cur = NULL;
9295 if (mainwindow_get_mainwindow())
9296 summaryview = mainwindow_get_mainwindow()->summaryview;
9299 list = summary_get_selected_msg_list(summaryview);
9301 for (cur = list; cur; cur = cur->next) {
9302 MsgInfo *msginfo = (MsgInfo *)cur->data;
9305 file = procmsg_get_message_file_full(msginfo,
9308 compose_attach_append(compose, (const gchar *)file,
9309 (const gchar *)file, "message/rfc822");
9317 static gboolean compose_drag_drop(GtkWidget *widget,
9318 GdkDragContext *drag_context,
9320 guint time, gpointer user_data)
9322 /* not handling this signal makes compose_insert_drag_received_cb
9327 static void compose_insert_drag_received_cb (GtkWidget *widget,
9328 GdkDragContext *drag_context,
9331 GtkSelectionData *data,
9336 Compose *compose = (Compose *)user_data;
9339 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
9341 if (gdk_atom_name(data->type) && !strcmp(gdk_atom_name(data->type), "text/uri-list")) {
9342 AlertValue val = G_ALERTDEFAULT;
9344 switch (prefs_common.compose_dnd_mode) {
9345 case COMPOSE_DND_ASK:
9346 val = alertpanel_full(_("Insert or attach?"),
9347 _("Do you want to insert the contents of the file(s) "
9348 "into the message body, or attach it to the email?"),
9349 GTK_STOCK_CANCEL, _("+_Insert"), _("_Attach"),
9350 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
9352 case COMPOSE_DND_INSERT:
9353 val = G_ALERTALTERNATE;
9355 case COMPOSE_DND_ATTACH:
9359 /* unexpected case */
9360 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
9363 if (val & G_ALERTDISABLE) {
9364 val &= ~G_ALERTDISABLE;
9365 /* remember what action to perform by default, only if we don't click Cancel */
9366 if (val == G_ALERTALTERNATE)
9367 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
9368 else if (val == G_ALERTOTHER)
9369 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
9372 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
9373 gtk_drag_finish(drag_context, FALSE, FALSE, time);
9375 } else if (val == G_ALERTOTHER) {
9376 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
9379 list = uri_list_extract_filenames((const gchar *)data->data);
9380 for (tmp = list; tmp != NULL; tmp = tmp->next) {
9381 compose_insert_file(compose, (const gchar *)tmp->data);
9383 list_free_strings(list);
9385 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9388 #if GTK_CHECK_VERSION(2, 8, 0)
9389 /* do nothing, handled by GTK */
9391 gchar *tmpfile = get_tmp_file();
9392 str_write_to_file((const gchar *)data->data, tmpfile);
9393 compose_insert_file(compose, tmpfile);
9396 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9400 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9403 static void compose_header_drag_received_cb (GtkWidget *widget,
9404 GdkDragContext *drag_context,
9407 GtkSelectionData *data,
9412 GtkEditable *entry = (GtkEditable *)user_data;
9413 gchar *email = (gchar *)data->data;
9415 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
9418 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
9419 gchar *decoded=g_new(gchar, strlen(email));
9422 email += strlen("mailto:");
9423 decode_uri(decoded, email); /* will fit */
9424 gtk_editable_delete_text(entry, 0, -1);
9425 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
9426 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9430 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9433 static void compose_toggle_return_receipt_cb(gpointer data, guint action,
9436 Compose *compose = (Compose *)data;
9438 if (GTK_CHECK_MENU_ITEM(widget)->active)
9439 compose->return_receipt = TRUE;
9441 compose->return_receipt = FALSE;
9444 static void compose_toggle_remove_refs_cb(gpointer data, guint action,
9447 Compose *compose = (Compose *)data;
9449 if (GTK_CHECK_MENU_ITEM(widget)->active)
9450 compose->remove_references = TRUE;
9452 compose->remove_references = FALSE;
9455 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
9457 ComposeHeaderEntry *headerentry)
9459 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
9460 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
9461 !(event->state & GDK_MODIFIER_MASK) &&
9462 (event->keyval == GDK_BackSpace) &&
9463 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
9464 gtk_container_remove
9465 (GTK_CONTAINER(headerentry->compose->header_table),
9466 headerentry->combo);
9467 gtk_container_remove
9468 (GTK_CONTAINER(headerentry->compose->header_table),
9469 headerentry->entry);
9470 headerentry->compose->header_list =
9471 g_slist_remove(headerentry->compose->header_list,
9473 g_free(headerentry);
9474 } else if (event->keyval == GDK_Tab) {
9475 if (headerentry->compose->header_last == headerentry) {
9476 /* Override default next focus, and give it to subject_entry
9477 * instead of notebook tabs
9479 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
9480 gtk_widget_grab_focus(headerentry->compose->subject_entry);
9487 static gboolean compose_headerentry_changed_cb(GtkWidget *entry,
9488 ComposeHeaderEntry *headerentry)
9490 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
9491 compose_create_header_entry(headerentry->compose);
9492 g_signal_handlers_disconnect_matched
9493 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
9494 0, 0, NULL, NULL, headerentry);
9496 /* Automatically scroll down */
9497 compose_show_first_last_header(headerentry->compose, FALSE);
9503 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
9505 GtkAdjustment *vadj;
9507 g_return_if_fail(compose);
9508 g_return_if_fail(GTK_IS_WIDGET(compose->header_table));
9509 g_return_if_fail(GTK_IS_VIEWPORT(compose->header_table->parent));
9511 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(compose->header_table->parent));
9512 gtk_adjustment_set_value(vadj, (show_first ? vadj->lower : vadj->upper));
9515 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
9516 const gchar *text, gint len, Compose *compose)
9518 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
9519 (G_OBJECT(compose->text), "paste_as_quotation"));
9522 g_return_if_fail(text != NULL);
9524 g_signal_handlers_block_by_func(G_OBJECT(buffer),
9525 G_CALLBACK(text_inserted),
9527 if (paste_as_quotation) {
9534 new_text = g_strndup(text, len);
9535 if (prefs_common.quotemark && *prefs_common.quotemark)
9536 qmark = prefs_common.quotemark;
9540 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
9541 gtk_text_buffer_place_cursor(buffer, iter);
9543 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
9544 _("Quote format error at line %d."));
9545 quote_fmt_reset_vartable();
9547 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
9548 GINT_TO_POINTER(paste_as_quotation - 1));
9550 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
9551 gtk_text_buffer_place_cursor(buffer, iter);
9553 if (strcmp(text, "\n") || automatic_break
9554 || gtk_text_iter_starts_line(iter))
9555 gtk_text_buffer_insert(buffer, iter, text, len);
9557 debug_print("insert nowrap \\n\n");
9558 gtk_text_buffer_insert_with_tags_by_name(buffer,
9559 iter, text, len, "no_join", NULL);
9563 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
9565 compose_beautify_paragraph(compose, iter, FALSE);
9567 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
9568 gtk_text_buffer_delete_mark(buffer, mark);
9570 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
9571 G_CALLBACK(text_inserted),
9573 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
9575 if (prefs_common.autosave &&
9576 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0)
9577 compose->draft_timeout_tag = g_timeout_add
9578 (500, (GtkFunction) compose_defer_auto_save_draft, compose);
9580 static gint compose_defer_auto_save_draft(Compose *compose)
9582 compose->draft_timeout_tag = -1;
9583 compose_draft_cb((gpointer)compose, COMPOSE_AUTO_SAVE, NULL);
9588 static void compose_check_all(Compose *compose)
9590 if (compose->gtkaspell)
9591 gtkaspell_check_all(compose->gtkaspell);
9594 static void compose_highlight_all(Compose *compose)
9596 if (compose->gtkaspell)
9597 gtkaspell_highlight_all(compose->gtkaspell);
9600 static void compose_check_backwards(Compose *compose)
9602 if (compose->gtkaspell)
9603 gtkaspell_check_backwards(compose->gtkaspell);
9605 GtkItemFactory *ifactory;
9606 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
9607 menu_set_sensitive(ifactory, "/Edit/Check backwards misspelled word", FALSE);
9608 menu_set_sensitive(ifactory, "/Edit/Forward to next misspelled word", FALSE);
9612 static void compose_check_forwards_go(Compose *compose)
9614 if (compose->gtkaspell)
9615 gtkaspell_check_forwards_go(compose->gtkaspell);
9617 GtkItemFactory *ifactory;
9618 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
9619 menu_set_sensitive(ifactory, "/Edit/Check backwards misspelled word", FALSE);
9620 menu_set_sensitive(ifactory, "/Edit/Forward to next misspelled word", FALSE);
9626 *\brief Guess originating forward account from MsgInfo and several
9627 * "common preference" settings. Return NULL if no guess.
9629 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
9631 PrefsAccount *account = NULL;
9633 g_return_val_if_fail(msginfo, NULL);
9634 g_return_val_if_fail(msginfo->folder, NULL);
9635 g_return_val_if_fail(msginfo->folder->prefs, NULL);
9637 if (msginfo->folder->prefs->enable_default_account)
9638 account = account_find_from_id(msginfo->folder->prefs->default_account);
9641 account = msginfo->folder->folder->account;
9643 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
9645 Xstrdup_a(to, msginfo->to, return NULL);
9646 extract_address(to);
9647 account = account_find_from_address(to);
9650 if (!account && prefs_common.forward_account_autosel) {
9652 if (!procheader_get_header_from_msginfo
9653 (msginfo, cc,sizeof cc , "Cc:")) {
9654 gchar *buf = cc + strlen("Cc:");
9655 extract_address(buf);
9656 account = account_find_from_address(buf);
9660 if (!account && prefs_common.forward_account_autosel) {
9661 gchar deliveredto[BUFFSIZE];
9662 if (!procheader_get_header_from_msginfo
9663 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
9664 gchar *buf = deliveredto + strlen("Delivered-To:");
9665 extract_address(buf);
9666 account = account_find_from_address(buf);
9673 gboolean compose_close(Compose *compose)
9677 if (!g_mutex_trylock(compose->mutex)) {
9678 /* we have to wait for the (possibly deferred by auto-save)
9679 * drafting to be done, before destroying the compose under
9681 debug_print("waiting for drafting to finish...\n");
9682 g_timeout_add (500, (GSourceFunc) compose_close, compose);
9685 g_return_val_if_fail(compose, FALSE);
9686 gtkut_widget_get_uposition(compose->window, &x, &y);
9687 prefs_common.compose_x = x;
9688 prefs_common.compose_y = y;
9689 g_mutex_unlock(compose->mutex);
9690 compose_destroy(compose);
9695 * Add entry field for each address in list.
9696 * \param compose E-Mail composition object.
9697 * \param listAddress List of (formatted) E-Mail addresses.
9699 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
9704 addr = ( gchar * ) node->data;
9705 compose_entry_append( compose, addr, COMPOSE_TO );
9706 node = g_list_next( node );
9710 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
9711 guint action, gboolean opening_multiple)
9714 GSList *new_msglist = NULL;
9715 MsgInfo *tmp_msginfo = NULL;
9716 gboolean originally_enc = FALSE;
9717 Compose *compose = NULL;
9719 g_return_if_fail(msgview != NULL);
9721 g_return_if_fail(msginfo_list != NULL);
9723 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
9724 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
9725 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
9727 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
9728 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
9729 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
9730 orig_msginfo, mimeinfo);
9731 if (tmp_msginfo != NULL) {
9732 new_msglist = g_slist_append(NULL, tmp_msginfo);
9733 if (procmime_msginfo_is_encrypted(orig_msginfo)) {
9734 originally_enc = TRUE;
9740 if (!opening_multiple)
9741 body = messageview_get_selection(msgview);
9744 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
9745 procmsg_msginfo_free(tmp_msginfo);
9746 g_slist_free(new_msglist);
9748 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
9750 if (originally_enc) {
9751 compose_force_encryption(compose, compose->account, FALSE);
9757 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
9760 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
9761 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
9762 GSList *cur = msginfo_list;
9763 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
9764 "messages. Opening the windows "
9765 "could take some time. Do you "
9766 "want to continue?"),
9767 g_slist_length(msginfo_list));
9768 if (g_slist_length(msginfo_list) > 9
9769 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
9770 != G_ALERTALTERNATE) {
9775 /* We'll open multiple compose windows */
9776 /* let the WM place the next windows */
9777 compose_force_window_origin = FALSE;
9778 for (; cur; cur = cur->next) {
9780 tmplist.data = cur->data;
9781 tmplist.next = NULL;
9782 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
9784 compose_force_window_origin = TRUE;
9786 /* forwarding multiple mails as attachments is done via a
9787 * single compose window */
9788 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
9792 void compose_set_position(Compose *compose, gint pos)
9794 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9796 gtkut_text_view_set_position(text, pos);
9799 gboolean compose_search_string(Compose *compose,
9800 const gchar *str, gboolean case_sens)
9802 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9804 return gtkut_text_view_search_string(text, str, case_sens);
9807 gboolean compose_search_string_backward(Compose *compose,
9808 const gchar *str, gboolean case_sens)
9810 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9812 return gtkut_text_view_search_string_backward(text, str, case_sens);
9815 /* allocate a msginfo structure and populate its data from a compose data structure */
9816 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
9818 MsgInfo *newmsginfo;
9820 gchar buf[BUFFSIZE];
9822 g_return_val_if_fail( compose != NULL, NULL );
9824 newmsginfo = procmsg_msginfo_new();
9827 get_rfc822_date(buf, sizeof(buf));
9828 newmsginfo->date = g_strdup(buf);
9831 if (compose->from_name) {
9832 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
9833 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
9837 if (compose->subject_entry)
9838 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
9840 /* to, cc, reply-to, newsgroups */
9841 for (list = compose->header_list; list; list = list->next) {
9842 gchar *header = gtk_editable_get_chars(
9844 GTK_COMBO(((ComposeHeaderEntry *)list->data)->combo)->entry), 0, -1);
9845 gchar *entry = gtk_editable_get_chars(
9846 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
9848 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
9849 if ( newmsginfo->to == NULL ) {
9850 newmsginfo->to = g_strdup(entry);
9851 } else if (entry && *entry) {
9852 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
9853 g_free(newmsginfo->to);
9854 newmsginfo->to = tmp;
9857 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
9858 if ( newmsginfo->cc == NULL ) {
9859 newmsginfo->cc = g_strdup(entry);
9860 } else if (entry && *entry) {
9861 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
9862 g_free(newmsginfo->cc);
9863 newmsginfo->cc = tmp;
9866 if ( strcasecmp(header,
9867 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
9868 if ( newmsginfo->newsgroups == NULL ) {
9869 newmsginfo->newsgroups = g_strdup(entry);
9870 } else if (entry && *entry) {
9871 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
9872 g_free(newmsginfo->newsgroups);
9873 newmsginfo->newsgroups = tmp;
9881 /* other data is unset */
9887 /* update compose's dictionaries from folder dict settings */
9888 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
9889 FolderItem *folder_item)
9891 g_return_if_fail(compose != NULL);
9893 if (compose->gtkaspell && folder_item && folder_item->prefs) {
9894 FolderItemPrefs *prefs = folder_item->prefs;
9896 if (prefs->enable_default_dictionary)
9897 gtkaspell_change_dict(compose->gtkaspell,
9898 prefs->default_dictionary, FALSE);
9899 if (folder_item->prefs->enable_default_alt_dictionary)
9900 gtkaspell_change_alt_dict(compose->gtkaspell,
9901 prefs->default_alt_dictionary);
9902 if (prefs->enable_default_dictionary
9903 || prefs->enable_default_alt_dictionary)
9904 compose_spell_menu_changed(compose);