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