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,
206 static void compose_entry_mark_default_to (Compose *compose,
207 const gchar *address);
208 static Compose *compose_followup_and_reply_to (MsgInfo *msginfo,
209 ComposeQuoteMode quote_mode,
213 static Compose *compose_forward_multiple (PrefsAccount *account,
214 GSList *msginfo_list);
215 static Compose *compose_reply (MsgInfo *msginfo,
216 ComposeQuoteMode quote_mode,
221 static Compose *compose_reply_mode (ComposeMode mode,
222 GSList *msginfo_list,
224 static void compose_template_apply_fields(Compose *compose, Template *tmpl);
225 static void compose_update_privacy_systems_menu(Compose *compose);
227 static GtkWidget *compose_account_option_menu_create
229 static void compose_set_out_encoding (Compose *compose);
230 static void compose_set_template_menu (Compose *compose);
231 static void compose_template_apply (Compose *compose,
234 static void compose_destroy (Compose *compose);
236 static void compose_entries_set (Compose *compose,
237 const gchar *mailto);
238 static gint compose_parse_header (Compose *compose,
240 static gchar *compose_parse_references (const gchar *ref,
243 static gchar *compose_quote_fmt (Compose *compose,
249 gboolean need_unescape,
250 const gchar *err_msg);
252 static void compose_reply_set_entry (Compose *compose,
258 followup_and_reply_to);
259 static void compose_reedit_set_entry (Compose *compose,
262 static void compose_insert_sig (Compose *compose,
264 static gchar *compose_get_signature_str (Compose *compose);
265 static ComposeInsertResult compose_insert_file (Compose *compose,
268 static gboolean compose_attach_append (Compose *compose,
271 const gchar *content_type);
272 static void compose_attach_parts (Compose *compose,
275 static void compose_beautify_paragraph (Compose *compose,
276 GtkTextIter *par_iter,
278 static void compose_wrap_all (Compose *compose);
279 static void compose_wrap_all_full (Compose *compose,
282 static void compose_set_title (Compose *compose);
283 static void compose_select_account (Compose *compose,
284 PrefsAccount *account,
287 static PrefsAccount *compose_current_mail_account(void);
288 /* static gint compose_send (Compose *compose); */
289 static gboolean compose_check_for_valid_recipient
291 static gboolean compose_check_entries (Compose *compose,
292 gboolean check_everything);
293 static gint compose_write_to_file (Compose *compose,
296 gboolean attach_parts);
297 static gint compose_write_body_to_file (Compose *compose,
299 static gint compose_remove_reedit_target (Compose *compose,
301 static void compose_remove_draft (Compose *compose);
302 static gint compose_queue_sub (Compose *compose,
306 gboolean check_subject,
307 gboolean remove_reedit_target);
308 static void compose_add_attachments (Compose *compose,
310 static gchar *compose_get_header (Compose *compose);
312 static void compose_convert_header (Compose *compose,
317 gboolean addr_field);
319 static void compose_attach_info_free (AttachInfo *ainfo);
320 static void compose_attach_remove_selected (Compose *compose);
322 static void compose_attach_property (Compose *compose);
323 static void compose_attach_property_create (gboolean *cancelled);
324 static void attach_property_ok (GtkWidget *widget,
325 gboolean *cancelled);
326 static void attach_property_cancel (GtkWidget *widget,
327 gboolean *cancelled);
328 static gint attach_property_delete_event (GtkWidget *widget,
330 gboolean *cancelled);
331 static gboolean attach_property_key_pressed (GtkWidget *widget,
333 gboolean *cancelled);
335 static void compose_exec_ext_editor (Compose *compose);
337 static gint compose_exec_ext_editor_real (const gchar *file);
338 static gboolean compose_ext_editor_kill (Compose *compose);
339 static gboolean compose_input_cb (GIOChannel *source,
340 GIOCondition condition,
342 static void compose_set_ext_editor_sensitive (Compose *compose,
344 #endif /* G_OS_UNIX */
346 static void compose_undo_state_changed (UndoMain *undostruct,
351 static void compose_create_header_entry (Compose *compose);
352 static void compose_add_header_entry (Compose *compose, const gchar *header, gchar *text);
353 static void compose_remove_header_entries(Compose *compose);
355 static void compose_update_priority_menu_item(Compose * compose);
357 static void compose_spell_menu_changed (void *data);
359 static void compose_add_field_list ( Compose *compose,
360 GList *listAddress );
362 /* callback functions */
364 static gboolean compose_edit_size_alloc (GtkEditable *widget,
365 GtkAllocation *allocation,
366 GtkSHRuler *shruler);
367 static void account_activated (GtkComboBox *optmenu,
369 static void attach_selected (GtkTreeView *tree_view,
370 GtkTreePath *tree_path,
371 GtkTreeViewColumn *column,
373 static gboolean attach_button_pressed (GtkWidget *widget,
374 GdkEventButton *event,
376 static gboolean attach_key_pressed (GtkWidget *widget,
379 static void compose_send_cb (gpointer data,
382 static void compose_send_later_cb (gpointer data,
386 static void compose_draft_cb (gpointer data,
390 static void compose_attach_cb (gpointer data,
393 static void compose_insert_file_cb (gpointer data,
396 static void compose_insert_sig_cb (gpointer data,
400 static void compose_close_cb (gpointer data,
404 static void compose_set_encoding_cb (gpointer data,
408 static void compose_address_cb (gpointer data,
411 static void compose_template_activate_cb(GtkWidget *widget,
414 static void compose_ext_editor_cb (gpointer data,
418 static gint compose_delete_cb (GtkWidget *widget,
422 static void compose_undo_cb (Compose *compose);
423 static void compose_redo_cb (Compose *compose);
424 static void compose_cut_cb (Compose *compose);
425 static void compose_copy_cb (Compose *compose);
426 static void compose_paste_cb (Compose *compose);
427 static void compose_paste_as_quote_cb (Compose *compose);
428 static void compose_paste_no_wrap_cb (Compose *compose);
429 static void compose_paste_wrap_cb (Compose *compose);
430 static void compose_allsel_cb (Compose *compose);
432 static void compose_advanced_action_cb (Compose *compose,
433 ComposeCallAdvancedAction action);
435 static void compose_grab_focus_cb (GtkWidget *widget,
438 static void compose_changed_cb (GtkTextBuffer *textbuf,
441 static void compose_wrap_cb (gpointer data,
444 static void compose_find_cb (gpointer data,
447 static void compose_toggle_autowrap_cb (gpointer data,
451 static void compose_toggle_ruler_cb (gpointer data,
454 static void compose_toggle_sign_cb (gpointer data,
457 static void compose_toggle_encrypt_cb (gpointer data,
460 static void compose_set_privacy_system_cb(GtkWidget *widget,
462 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn);
463 static void activate_privacy_system (Compose *compose,
464 PrefsAccount *account,
466 static void compose_use_signing(Compose *compose, gboolean use_signing);
467 static void compose_use_encryption(Compose *compose, gboolean use_encryption);
468 static void compose_toggle_return_receipt_cb(gpointer data, guint action,
470 static void compose_toggle_remove_refs_cb(gpointer data, guint action,
472 static void compose_set_priority_cb (gpointer data,
475 static void compose_reply_change_mode (gpointer data,
479 static void compose_attach_drag_received_cb (GtkWidget *widget,
480 GdkDragContext *drag_context,
483 GtkSelectionData *data,
487 static void compose_insert_drag_received_cb (GtkWidget *widget,
488 GdkDragContext *drag_context,
491 GtkSelectionData *data,
495 static void compose_header_drag_received_cb (GtkWidget *widget,
496 GdkDragContext *drag_context,
499 GtkSelectionData *data,
504 static gboolean compose_drag_drop (GtkWidget *widget,
505 GdkDragContext *drag_context,
507 guint time, gpointer user_data);
509 static void text_inserted (GtkTextBuffer *buffer,
514 static Compose *compose_generic_reply(MsgInfo *msginfo,
515 ComposeQuoteMode quote_mode,
519 gboolean followup_and_reply_to,
522 static gboolean compose_headerentry_changed_cb (GtkWidget *entry,
523 ComposeHeaderEntry *headerentry);
524 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
526 ComposeHeaderEntry *headerentry);
528 static void compose_show_first_last_header (Compose *compose, gboolean show_first);
530 static void compose_allow_user_actions (Compose *compose, gboolean allow);
533 static void compose_check_all (Compose *compose);
534 static void compose_highlight_all (Compose *compose);
535 static void compose_check_backwards (Compose *compose);
536 static void compose_check_forwards_go (Compose *compose);
539 static gint compose_defer_auto_save_draft (Compose *compose);
540 static PrefsAccount *compose_guess_forward_account_from_msginfo (MsgInfo *msginfo);
542 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose);
545 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
546 FolderItem *folder_item);
549 static GtkItemFactoryEntry compose_popup_entries[] =
551 {N_("/_Add..."), NULL, compose_attach_cb, 0, NULL},
552 {N_("/_Remove"), NULL, compose_attach_remove_selected, 0, NULL},
553 {"/---", NULL, NULL, 0, "<Separator>"},
554 {N_("/_Properties..."), NULL, compose_attach_property, 0, NULL}
557 static GtkItemFactoryEntry compose_entries[] =
559 {N_("/_Message"), NULL, NULL, 0, "<Branch>"},
560 {N_("/_Message/S_end"), "<control>Return",
561 compose_send_cb, 0, NULL},
562 {N_("/_Message/Send _later"), "<shift><control>S",
563 compose_send_later_cb, 0, NULL},
564 {N_("/_Message/---"), NULL, NULL, 0, "<Separator>"},
565 {N_("/_Message/_Attach file"), "<control>M", compose_attach_cb, 0, NULL},
566 {N_("/_Message/_Insert file"), "<control>I", compose_insert_file_cb, 0, NULL},
567 {N_("/_Message/Insert si_gnature"), "<control>G", compose_insert_sig_cb, 0, NULL},
568 {N_("/_Message/---"), NULL, NULL, 0, "<Separator>"},
569 {N_("/_Message/_Save"),
570 "<control>S", compose_draft_cb, COMPOSE_KEEP_EDITING, NULL},
571 {N_("/_Message/---"), NULL, NULL, 0, "<Separator>"},
572 {N_("/_Message/_Close"), "<control>W", compose_close_cb, 0, NULL},
574 {N_("/_Edit"), NULL, NULL, 0, "<Branch>"},
575 {N_("/_Edit/_Undo"), "<control>Z", compose_undo_cb, 0, NULL},
576 {N_("/_Edit/_Redo"), "<control>Y", compose_redo_cb, 0, NULL},
577 {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
578 {N_("/_Edit/Cu_t"), "<control>X", compose_cut_cb, 0, NULL},
579 {N_("/_Edit/_Copy"), "<control>C", compose_copy_cb, 0, NULL},
580 {N_("/_Edit/_Paste"), "<control>V", compose_paste_cb, 0, NULL},
581 {N_("/_Edit/Special paste"), NULL, NULL, 0, "<Branch>"},
582 {N_("/_Edit/Special paste/as _quotation"),
583 NULL, compose_paste_as_quote_cb, 0, NULL},
584 {N_("/_Edit/Special paste/_wrapped"),
585 NULL, compose_paste_wrap_cb, 0, NULL},
586 {N_("/_Edit/Special paste/_unwrapped"),
587 NULL, compose_paste_no_wrap_cb, 0, NULL},
588 {N_("/_Edit/Select _all"), "<control>A", compose_allsel_cb, 0, NULL},
589 {N_("/_Edit/A_dvanced"), NULL, NULL, 0, "<Branch>"},
590 {N_("/_Edit/A_dvanced/Move a character backward"),
592 compose_advanced_action_cb,
593 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER,
595 {N_("/_Edit/A_dvanced/Move a character forward"),
597 compose_advanced_action_cb,
598 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER,
600 {N_("/_Edit/A_dvanced/Move a word backward"),
602 compose_advanced_action_cb,
603 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD,
605 {N_("/_Edit/A_dvanced/Move a word forward"),
607 compose_advanced_action_cb,
608 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD,
610 {N_("/_Edit/A_dvanced/Move to beginning of line"),
611 NULL, /* "<control>A" */
612 compose_advanced_action_cb,
613 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE,
615 {N_("/_Edit/A_dvanced/Move to end of line"),
617 compose_advanced_action_cb,
618 COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE,
620 {N_("/_Edit/A_dvanced/Move to previous line"),
622 compose_advanced_action_cb,
623 COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE,
625 {N_("/_Edit/A_dvanced/Move to next line"),
627 compose_advanced_action_cb,
628 COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE,
630 {N_("/_Edit/A_dvanced/Delete a character backward"),
632 compose_advanced_action_cb,
633 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER,
635 {N_("/_Edit/A_dvanced/Delete a character forward"),
637 compose_advanced_action_cb,
638 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER,
640 {N_("/_Edit/A_dvanced/Delete a word backward"),
641 NULL, /* "<control>W" */
642 compose_advanced_action_cb,
643 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD,
645 {N_("/_Edit/A_dvanced/Delete a word forward"),
646 NULL, /* "<alt>D", */
647 compose_advanced_action_cb,
648 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD,
650 {N_("/_Edit/A_dvanced/Delete line"),
652 compose_advanced_action_cb,
653 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE,
655 {N_("/_Edit/A_dvanced/Delete entire line"),
657 compose_advanced_action_cb,
658 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE_N,
660 {N_("/_Edit/A_dvanced/Delete to end of line"),
662 compose_advanced_action_cb,
663 COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END,
665 {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
667 "<control>F", compose_find_cb, 0, NULL},
668 {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
669 {N_("/_Edit/_Wrap current paragraph"),
670 "<control>L", compose_wrap_cb, 0, NULL},
671 {N_("/_Edit/Wrap all long _lines"),
672 "<control><alt>L", compose_wrap_cb, 1, NULL},
673 {N_("/_Edit/Aut_o wrapping"), "<shift><control>L", compose_toggle_autowrap_cb, 0, "<ToggleItem>"},
674 {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
675 {N_("/_Edit/Edit with e_xternal editor"),
676 "<shift><control>X", compose_ext_editor_cb, 0, NULL},
678 {N_("/_Spelling"), NULL, NULL, 0, "<Branch>"},
679 {N_("/_Spelling/_Check all or check selection"),
680 NULL, compose_check_all, 0, NULL},
681 {N_("/_Spelling/_Highlight all misspelled words"),
682 NULL, compose_highlight_all, 0, NULL},
683 {N_("/_Spelling/Check _backwards misspelled word"),
684 NULL, compose_check_backwards , 0, NULL},
685 {N_("/_Spelling/_Forward to next misspelled word"),
686 NULL, compose_check_forwards_go, 0, NULL},
687 {N_("/_Spelling/---"), NULL, NULL, 0, "<Separator>"},
688 {N_("/_Spelling/Options"),
689 NULL, NULL, 0, "<Branch>"},
691 {N_("/_Options"), NULL, NULL, 0, "<Branch>"},
692 {N_("/_Options/Reply _mode"), NULL, NULL, 0, "<Branch>"},
693 {N_("/_Options/Reply _mode/_Normal"), NULL, compose_reply_change_mode, COMPOSE_REPLY, "<RadioItem>"},
694 {N_("/_Options/Reply _mode/_All"), NULL, compose_reply_change_mode, COMPOSE_REPLY_TO_ALL, "/Options/Reply mode/Normal"},
695 {N_("/_Options/Reply _mode/_Sender"), NULL, compose_reply_change_mode, COMPOSE_REPLY_TO_SENDER, "/Options/Reply mode/Normal"},
696 {N_("/_Options/Reply _mode/_Mailing-list"), NULL, compose_reply_change_mode, COMPOSE_REPLY_TO_LIST, "/Options/Reply mode/Normal"},
697 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
698 {N_("/_Options/Privacy _System"), NULL, NULL, 0, "<Branch>"},
699 {N_("/_Options/Privacy _System/None"), NULL, NULL, 0, "<RadioItem>"},
700 {N_("/_Options/Si_gn"), NULL, compose_toggle_sign_cb , 0, "<ToggleItem>"},
701 {N_("/_Options/_Encrypt"), NULL, compose_toggle_encrypt_cb, 0, "<ToggleItem>"},
702 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
703 {N_("/_Options/_Priority"), NULL, NULL, 0, "<Branch>"},
704 {N_("/_Options/Priority/_Highest"), NULL, compose_set_priority_cb, PRIORITY_HIGHEST, "<RadioItem>"},
705 {N_("/_Options/Priority/Hi_gh"), NULL, compose_set_priority_cb, PRIORITY_HIGH, "/Options/Priority/Highest"},
706 {N_("/_Options/Priority/_Normal"), NULL, compose_set_priority_cb, PRIORITY_NORMAL, "/Options/Priority/Highest"},
707 {N_("/_Options/Priority/Lo_w"), NULL, compose_set_priority_cb, PRIORITY_LOW, "/Options/Priority/Highest"},
708 {N_("/_Options/Priority/_Lowest"), NULL, compose_set_priority_cb, PRIORITY_LOWEST, "/Options/Priority/Highest"},
709 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
710 {N_("/_Options/_Request Return Receipt"), NULL, compose_toggle_return_receipt_cb, 0, "<ToggleItem>"},
711 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
712 {N_("/_Options/Remo_ve references"), NULL, compose_toggle_remove_refs_cb, 0, "<ToggleItem>"},
713 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
715 #define ENC_ACTION(action) \
716 NULL, compose_set_encoding_cb, action, \
717 "/Options/Character encoding/Automatic"
719 {N_("/_Options/Character _encoding"), NULL, NULL, 0, "<Branch>"},
720 {N_("/_Options/Character _encoding/_Automatic"),
721 NULL, compose_set_encoding_cb, C_AUTO, "<RadioItem>"},
722 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
724 {N_("/_Options/Character _encoding/7bit ascii (US-ASC_II)"),
725 ENC_ACTION(C_US_ASCII)},
726 {N_("/_Options/Character _encoding/Unicode (_UTF-8)"),
727 ENC_ACTION(C_UTF_8)},
728 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
730 {N_("/_Options/Character _encoding/Western European (ISO-8859-_1)"),
731 ENC_ACTION(C_ISO_8859_1)},
732 {N_("/_Options/Character _encoding/Western European (ISO-8859-15)"),
733 ENC_ACTION(C_ISO_8859_15)},
734 {N_("/_Options/Character _encoding/Western European (Windows-1252)"),
735 ENC_ACTION(C_WINDOWS_1252)},
736 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
738 {N_("/_Options/Character _encoding/Central European (ISO-8859-_2)"),
739 ENC_ACTION(C_ISO_8859_2)},
740 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
742 {N_("/_Options/Character _encoding/_Baltic (ISO-8859-13)"),
743 ENC_ACTION(C_ISO_8859_13)},
744 {N_("/_Options/Character _encoding/Baltic (ISO-8859-_4)"),
745 ENC_ACTION(C_ISO_8859_4)},
746 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
748 {N_("/_Options/Character _encoding/Greek (ISO-8859-_7)"),
749 ENC_ACTION(C_ISO_8859_7)},
750 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
752 {N_("/_Options/Character _encoding/Hebrew (ISO-8859-_8)"),
753 ENC_ACTION(C_ISO_8859_8)},
754 {N_("/_Options/Character _encoding/Hebrew (Windows-1255)"),
755 ENC_ACTION(C_WINDOWS_1255)},
756 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
758 {N_("/_Options/Character _encoding/Arabic (ISO-8859-_6)"),
759 ENC_ACTION(C_ISO_8859_6)},
760 {N_("/_Options/Character _encoding/Arabic (Windows-1256)"),
761 ENC_ACTION(C_CP1256)},
762 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
764 {N_("/_Options/Character _encoding/Turkish (ISO-8859-_9)"),
765 ENC_ACTION(C_ISO_8859_9)},
766 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
768 {N_("/_Options/Character _encoding/Cyrillic (ISO-8859-_5)"),
769 ENC_ACTION(C_ISO_8859_5)},
770 {N_("/_Options/Character _encoding/Cyrillic (KOI8-_R)"),
771 ENC_ACTION(C_KOI8_R)},
772 {N_("/_Options/Character _encoding/Cyrillic (KOI8-U)"),
773 ENC_ACTION(C_KOI8_U)},
774 {N_("/_Options/Character _encoding/Cyrillic (Windows-1251)"),
775 ENC_ACTION(C_WINDOWS_1251)},
776 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
778 {N_("/_Options/Character _encoding/Japanese (ISO-2022-_JP)"),
779 ENC_ACTION(C_ISO_2022_JP)},
780 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
782 {N_("/_Options/Character _encoding/Simplified Chinese (_GB2312)"),
783 ENC_ACTION(C_GB2312)},
784 {N_("/_Options/Character _encoding/Simplified Chinese (GBK)"),
786 {N_("/_Options/Character _encoding/Traditional Chinese (_Big5)"),
788 {N_("/_Options/Character _encoding/Traditional Chinese (EUC-_TW)"),
789 ENC_ACTION(C_EUC_TW)},
790 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
792 {N_("/_Options/Character _encoding/Korean (EUC-_KR)"),
793 ENC_ACTION(C_EUC_KR)},
794 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
796 {N_("/_Options/Character _encoding/Thai (TIS-620)"),
797 ENC_ACTION(C_TIS_620)},
798 {N_("/_Options/Character _encoding/Thai (Windows-874)"),
799 ENC_ACTION(C_WINDOWS_874)},
801 {N_("/_Tools"), NULL, NULL, 0, "<Branch>"},
802 {N_("/_Tools/Show _ruler"), NULL, compose_toggle_ruler_cb, 0, "<ToggleItem>"},
803 {N_("/_Tools/_Address book"), "<shift><control>A", compose_address_cb , 0, NULL},
804 {N_("/_Tools/_Template"), NULL, NULL, 0, "<Branch>"},
805 {N_("/_Tools/Actio_ns"), NULL, NULL, 0, "<Branch>"},
806 {N_("/_Help"), NULL, NULL, 0, "<Branch>"},
807 {N_("/_Help/_About"), NULL, about_show, 0, NULL}
810 static GtkTargetEntry compose_mime_types[] =
812 {"text/uri-list", 0, 0},
813 {"UTF8_STRING", 0, 0},
817 static gboolean compose_put_existing_to_front(MsgInfo *info)
819 GList *compose_list = compose_get_compose_list();
823 for (elem = compose_list; elem != NULL && elem->data != NULL;
825 Compose *c = (Compose*)elem->data;
827 if (!c->targetinfo || !c->targetinfo->msgid ||
831 if (!strcmp(c->targetinfo->msgid, info->msgid)) {
832 gtkut_window_popup(c->window);
840 static GdkColor quote_color1 =
841 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
842 static GdkColor quote_color2 =
843 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
844 static GdkColor quote_color3 =
845 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
847 static GdkColor quote_bgcolor1 =
848 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
849 static GdkColor quote_bgcolor2 =
850 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
851 static GdkColor quote_bgcolor3 =
852 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
854 static GdkColor signature_color = {
861 static GdkColor uri_color = {
868 static void compose_create_tags(GtkTextView *text, Compose *compose)
870 GtkTextBuffer *buffer;
871 GdkColor black = {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
877 buffer = gtk_text_view_get_buffer(text);
879 if (prefs_common.enable_color) {
880 /* grab the quote colors, converting from an int to a GdkColor */
881 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
883 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
885 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
887 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_bgcol,
889 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_bgcol,
891 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_bgcol,
893 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
895 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
898 signature_color = quote_color1 = quote_color2 = quote_color3 =
899 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
902 if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
903 gtk_text_buffer_create_tag(buffer, "quote0",
904 "foreground-gdk", "e_color1,
905 "paragraph-background-gdk", "e_bgcolor1,
907 gtk_text_buffer_create_tag(buffer, "quote1",
908 "foreground-gdk", "e_color2,
909 "paragraph-background-gdk", "e_bgcolor2,
911 gtk_text_buffer_create_tag(buffer, "quote2",
912 "foreground-gdk", "e_color3,
913 "paragraph-background-gdk", "e_bgcolor3,
916 gtk_text_buffer_create_tag(buffer, "quote0",
917 "foreground-gdk", "e_color1,
919 gtk_text_buffer_create_tag(buffer, "quote1",
920 "foreground-gdk", "e_color2,
922 gtk_text_buffer_create_tag(buffer, "quote2",
923 "foreground-gdk", "e_color3,
927 gtk_text_buffer_create_tag(buffer, "signature",
928 "foreground-gdk", &signature_color,
930 gtk_text_buffer_create_tag(buffer, "link",
931 "foreground-gdk", &uri_color,
933 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
934 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
936 color[0] = quote_color1;
937 color[1] = quote_color2;
938 color[2] = quote_color3;
939 color[3] = quote_bgcolor1;
940 color[4] = quote_bgcolor2;
941 color[5] = quote_bgcolor3;
942 color[6] = signature_color;
943 color[7] = uri_color;
944 cmap = gdk_drawable_get_colormap(compose->window->window);
945 gdk_colormap_alloc_colors(cmap, color, 8, FALSE, TRUE, success);
947 for (i = 0; i < 8; i++) {
948 if (success[i] == FALSE) {
951 g_warning("Compose: color allocation failed.\n");
952 style = gtk_widget_get_style(GTK_WIDGET(text));
953 quote_color1 = quote_color2 = quote_color3 =
954 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 =
955 signature_color = uri_color = black;
960 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
961 GPtrArray *attach_files)
963 return compose_generic_new(account, mailto, NULL, attach_files, NULL);
966 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
968 return compose_generic_new(account, mailto, item, NULL, NULL);
971 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
973 return compose_generic_new( account, NULL, NULL, NULL, listAddress );
976 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
977 GPtrArray *attach_files, GList *listAddress )
980 GtkTextView *textview;
981 GtkTextBuffer *textbuf;
983 GtkItemFactory *ifactory;
984 const gchar *subject_format = NULL;
985 const gchar *body_format = NULL;
987 if (item && item->prefs && item->prefs->enable_default_account)
988 account = account_find_from_id(item->prefs->default_account);
990 if (!account) account = cur_account;
991 g_return_val_if_fail(account != NULL, NULL);
993 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
995 ifactory = gtk_item_factory_from_widget(compose->menubar);
997 compose->replyinfo = NULL;
998 compose->fwdinfo = NULL;
1000 textview = GTK_TEXT_VIEW(compose->text);
1001 textbuf = gtk_text_view_get_buffer(textview);
1002 compose_create_tags(textview, compose);
1004 undo_block(compose->undostruct);
1006 compose_set_dictionaries_from_folder_prefs(compose, item);
1009 if (account->auto_sig)
1010 compose_insert_sig(compose, FALSE);
1011 gtk_text_buffer_get_start_iter(textbuf, &iter);
1012 gtk_text_buffer_place_cursor(textbuf, &iter);
1014 if (account->protocol != A_NNTP) {
1015 if (mailto && *mailto != '\0') {
1016 compose_entries_set(compose, mailto);
1018 } else if (item && item->prefs->enable_default_to) {
1019 compose_entry_append(compose, item->prefs->default_to, COMPOSE_TO);
1020 compose_entry_mark_default_to(compose, item->prefs->default_to);
1022 if (item && item->ret_rcpt) {
1023 menu_set_active(ifactory, "/Options/Request Return Receipt", TRUE);
1027 compose_entry_append(compose, mailto, COMPOSE_NEWSGROUPS);
1028 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1029 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS);
1032 * CLAWS: just don't allow return receipt request, even if the user
1033 * may want to send an email. simple but foolproof.
1035 menu_set_sensitive(ifactory, "/Options/Request Return Receipt", FALSE);
1037 compose_add_field_list( compose, listAddress );
1039 if (item && item->prefs && item->prefs->compose_with_format) {
1040 subject_format = item->prefs->compose_subject_format;
1041 body_format = item->prefs->compose_body_format;
1042 } else if (account->compose_with_format) {
1043 subject_format = account->compose_subject_format;
1044 body_format = account->compose_body_format;
1045 } else if (prefs_common.compose_with_format) {
1046 subject_format = prefs_common.compose_subject_format;
1047 body_format = prefs_common.compose_body_format;
1050 if (subject_format || body_format) {
1051 MsgInfo* dummyinfo = NULL;
1054 && *subject_format != '\0' )
1056 gchar *subject = NULL;
1060 dummyinfo = compose_msginfo_new_from_compose(compose);
1062 /* decode \-escape sequences in the internal representation of the quote format */
1063 tmp = malloc(strlen(subject_format)+1);
1064 pref_get_unescaped_pref(tmp, subject_format);
1066 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1068 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account,
1069 compose->gtkaspell);
1071 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account);
1073 quote_fmt_scan_string(tmp);
1076 buf = quote_fmt_get_buffer();
1078 alertpanel_error(_("New message subject format error."));
1080 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1081 quote_fmt_reset_vartable();
1088 && *body_format != '\0' )
1091 GtkTextBuffer *buffer;
1092 GtkTextIter start, end;
1095 if ( dummyinfo == NULL )
1096 dummyinfo = compose_msginfo_new_from_compose(compose);
1098 text = GTK_TEXT_VIEW(compose->text);
1099 buffer = gtk_text_view_get_buffer(text);
1100 gtk_text_buffer_get_start_iter(buffer, &start);
1101 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1102 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1104 compose_quote_fmt(compose, dummyinfo,
1106 NULL, tmp, FALSE, TRUE,
1107 _("New message body format error at line %d."));
1108 quote_fmt_reset_vartable();
1113 procmsg_msginfo_free( dummyinfo );
1120 for (i = 0; i < attach_files->len; i++) {
1121 file = g_ptr_array_index(attach_files, i);
1122 compose_attach_append(compose, file, file, NULL);
1126 compose_show_first_last_header(compose, TRUE);
1128 /* Set save folder */
1129 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1130 gchar *folderidentifier;
1132 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
1133 folderidentifier = folder_item_get_identifier(item);
1134 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
1135 g_free(folderidentifier);
1138 gtk_widget_grab_focus(compose->header_last->entry);
1140 undo_unblock(compose->undostruct);
1142 if (prefs_common.auto_exteditor)
1143 compose_exec_ext_editor(compose);
1145 compose->modified = FALSE;
1146 compose_set_title(compose);
1150 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1151 gboolean override_pref)
1153 gchar *privacy = NULL;
1155 g_return_if_fail(compose != NULL);
1156 g_return_if_fail(account != NULL);
1158 if (override_pref == FALSE && account->default_encrypt_reply == FALSE)
1161 if (account->default_privacy_system
1162 && strlen(account->default_privacy_system)) {
1163 privacy = account->default_privacy_system;
1165 GSList *privacy_avail = privacy_get_system_ids();
1166 if (privacy_avail && g_slist_length(privacy_avail)) {
1167 privacy = (gchar *)(privacy_avail->data);
1170 if (privacy != NULL) {
1171 if (compose->privacy_system == NULL)
1172 compose->privacy_system = g_strdup(privacy);
1173 compose_update_privacy_system_menu_item(compose, FALSE);
1174 compose_use_encryption(compose, TRUE);
1178 static void compose_force_signing(Compose *compose, PrefsAccount *account)
1180 gchar *privacy = NULL;
1182 if (account->default_privacy_system
1183 && strlen(account->default_privacy_system)) {
1184 privacy = account->default_privacy_system;
1186 GSList *privacy_avail = privacy_get_system_ids();
1187 if (privacy_avail && g_slist_length(privacy_avail)) {
1188 privacy = (gchar *)(privacy_avail->data);
1191 if (privacy != NULL) {
1192 if (compose->privacy_system == NULL)
1193 compose->privacy_system = g_strdup(privacy);
1194 compose_update_privacy_system_menu_item(compose, FALSE);
1195 compose_use_signing(compose, TRUE);
1199 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1203 Compose *compose = NULL;
1204 GtkItemFactory *ifactory = NULL;
1206 g_return_val_if_fail(msginfo_list != NULL, NULL);
1208 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1209 g_return_val_if_fail(msginfo != NULL, NULL);
1211 list_len = g_slist_length(msginfo_list);
1215 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1216 FALSE, prefs_common.default_reply_list, FALSE, body);
1218 case COMPOSE_REPLY_WITH_QUOTE:
1219 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1220 FALSE, prefs_common.default_reply_list, FALSE, body);
1222 case COMPOSE_REPLY_WITHOUT_QUOTE:
1223 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1224 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1226 case COMPOSE_REPLY_TO_SENDER:
1227 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1228 FALSE, FALSE, TRUE, body);
1230 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1231 compose = compose_followup_and_reply_to(msginfo,
1232 COMPOSE_QUOTE_CHECK,
1233 FALSE, FALSE, body);
1235 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1236 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1237 FALSE, FALSE, TRUE, body);
1239 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1240 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1241 FALSE, FALSE, TRUE, NULL);
1243 case COMPOSE_REPLY_TO_ALL:
1244 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1245 TRUE, FALSE, FALSE, body);
1247 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1248 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1249 TRUE, FALSE, FALSE, body);
1251 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1252 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1253 TRUE, FALSE, FALSE, NULL);
1255 case COMPOSE_REPLY_TO_LIST:
1256 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1257 FALSE, TRUE, FALSE, body);
1259 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1260 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1261 FALSE, TRUE, FALSE, body);
1263 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1264 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1265 FALSE, TRUE, FALSE, NULL);
1267 case COMPOSE_FORWARD:
1268 if (prefs_common.forward_as_attachment) {
1269 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1272 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1276 case COMPOSE_FORWARD_INLINE:
1277 /* check if we reply to more than one Message */
1278 if (list_len == 1) {
1279 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1282 /* more messages FALL THROUGH */
1283 case COMPOSE_FORWARD_AS_ATTACH:
1284 compose = compose_forward_multiple(NULL, msginfo_list);
1286 case COMPOSE_REDIRECT:
1287 compose = compose_redirect(NULL, msginfo, FALSE);
1290 g_warning("compose_reply_mode(): invalid Compose Mode: %d\n", mode);
1293 ifactory = gtk_item_factory_from_widget(compose->menubar);
1295 compose->rmode = mode;
1296 switch (compose->rmode) {
1298 case COMPOSE_REPLY_WITH_QUOTE:
1299 case COMPOSE_REPLY_WITHOUT_QUOTE:
1300 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1301 debug_print("reply mode Normal\n");
1302 menu_set_active(ifactory, "/Options/Reply mode/Normal", TRUE);
1303 compose_reply_change_mode(compose, COMPOSE_REPLY, NULL); /* force update */
1305 case COMPOSE_REPLY_TO_SENDER:
1306 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1307 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1308 debug_print("reply mode Sender\n");
1309 menu_set_active(ifactory, "/Options/Reply mode/Sender", TRUE);
1311 case COMPOSE_REPLY_TO_ALL:
1312 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1313 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1314 debug_print("reply mode All\n");
1315 menu_set_active(ifactory, "/Options/Reply mode/All", TRUE);
1317 case COMPOSE_REPLY_TO_LIST:
1318 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1319 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1320 debug_print("reply mode List\n");
1321 menu_set_active(ifactory, "/Options/Reply mode/Mailing-list", TRUE);
1329 static Compose *compose_reply(MsgInfo *msginfo,
1330 ComposeQuoteMode quote_mode,
1336 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1337 to_sender, FALSE, body);
1340 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1341 ComposeQuoteMode quote_mode,
1346 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1347 to_sender, TRUE, body);
1350 static void compose_extract_original_charset(Compose *compose)
1352 MsgInfo *info = NULL;
1353 if (compose->replyinfo) {
1354 info = compose->replyinfo;
1355 } else if (compose->fwdinfo) {
1356 info = compose->fwdinfo;
1357 } else if (compose->targetinfo) {
1358 info = compose->targetinfo;
1361 MimeInfo *mimeinfo = procmime_scan_message(info);
1362 MimeInfo *partinfo = mimeinfo;
1363 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1364 partinfo = procmime_mimeinfo_next(partinfo);
1366 compose->orig_charset =
1367 g_strdup(procmime_mimeinfo_get_parameter(
1368 partinfo, "charset"));
1370 procmime_mimeinfo_free_all(mimeinfo);
1374 #define SIGNAL_BLOCK(buffer) { \
1375 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1376 G_CALLBACK(compose_changed_cb), \
1378 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1379 G_CALLBACK(text_inserted), \
1383 #define SIGNAL_UNBLOCK(buffer) { \
1384 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1385 G_CALLBACK(compose_changed_cb), \
1387 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1388 G_CALLBACK(text_inserted), \
1392 static Compose *compose_generic_reply(MsgInfo *msginfo,
1393 ComposeQuoteMode quote_mode,
1394 gboolean to_all, gboolean to_ml,
1396 gboolean followup_and_reply_to,
1399 GtkItemFactory *ifactory;
1401 PrefsAccount *account = NULL;
1402 GtkTextView *textview;
1403 GtkTextBuffer *textbuf;
1404 gboolean quote = FALSE;
1405 const gchar *qmark = NULL;
1406 const gchar *body_fmt = NULL;
1408 g_return_val_if_fail(msginfo != NULL, NULL);
1409 g_return_val_if_fail(msginfo->folder != NULL, NULL);
1411 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1413 g_return_val_if_fail(account != NULL, NULL);
1415 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1417 compose->updating = TRUE;
1419 ifactory = gtk_item_factory_from_widget(compose->menubar);
1421 menu_set_active(ifactory, "/Options/Remove references", FALSE);
1422 menu_set_sensitive(ifactory, "/Options/Remove references", TRUE);
1424 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1425 if (!compose->replyinfo)
1426 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1428 compose_extract_original_charset(compose);
1430 if (msginfo->folder && msginfo->folder->ret_rcpt)
1431 menu_set_active(ifactory, "/Options/Request Return Receipt", TRUE);
1433 /* Set save folder */
1434 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1435 gchar *folderidentifier;
1437 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1438 folderidentifier = folder_item_get_identifier(msginfo->folder);
1439 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
1440 g_free(folderidentifier);
1443 if (compose_parse_header(compose, msginfo) < 0) return NULL;
1445 textview = (GTK_TEXT_VIEW(compose->text));
1446 textbuf = gtk_text_view_get_buffer(textview);
1447 compose_create_tags(textview, compose);
1449 undo_block(compose->undostruct);
1451 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1454 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1455 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1456 /* use the reply format of folder (if enabled), or the account's one
1457 (if enabled) or fallback to the global reply format, which is always
1458 enabled (even if empty), and use the relevant quotemark */
1460 if (msginfo->folder && msginfo->folder->prefs &&
1461 msginfo->folder->prefs->reply_with_format) {
1462 qmark = msginfo->folder->prefs->reply_quotemark;
1463 body_fmt = msginfo->folder->prefs->reply_body_format;
1465 } else if (account->reply_with_format) {
1466 qmark = account->reply_quotemark;
1467 body_fmt = account->reply_body_format;
1470 qmark = prefs_common.quotemark;
1471 body_fmt = prefs_common.quotefmt;
1476 /* empty quotemark is not allowed */
1477 if (qmark == NULL || *qmark == '\0')
1479 compose_quote_fmt(compose, compose->replyinfo,
1480 body_fmt, qmark, body, FALSE, TRUE,
1481 _("Message reply format error at line %d."));
1482 quote_fmt_reset_vartable();
1484 if (procmime_msginfo_is_encrypted(compose->replyinfo)) {
1485 compose_force_encryption(compose, account, FALSE);
1488 SIGNAL_BLOCK(textbuf);
1490 if (account->auto_sig)
1491 compose_insert_sig(compose, FALSE);
1493 compose_wrap_all(compose);
1495 SIGNAL_UNBLOCK(textbuf);
1497 gtk_widget_grab_focus(compose->text);
1499 undo_unblock(compose->undostruct);
1501 if (prefs_common.auto_exteditor)
1502 compose_exec_ext_editor(compose);
1504 compose->modified = FALSE;
1505 compose_set_title(compose);
1507 compose->updating = FALSE;
1509 if (compose->deferred_destroy) {
1510 compose_destroy(compose);
1517 #define INSERT_FW_HEADER(var, hdr) \
1518 if (msginfo->var && *msginfo->var) { \
1519 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1520 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1521 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1524 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1525 gboolean as_attach, const gchar *body,
1526 gboolean no_extedit,
1530 GtkTextView *textview;
1531 GtkTextBuffer *textbuf;
1534 g_return_val_if_fail(msginfo != NULL, NULL);
1535 g_return_val_if_fail(msginfo->folder != NULL, NULL);
1538 !(account = compose_guess_forward_account_from_msginfo
1540 account = cur_account;
1542 compose = compose_create(account, msginfo->folder, COMPOSE_FORWARD, batch);
1544 compose->updating = TRUE;
1545 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1546 if (!compose->fwdinfo)
1547 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1549 compose_extract_original_charset(compose);
1551 if (msginfo->subject && *msginfo->subject) {
1552 gchar *buf, *buf2, *p;
1554 buf = p = g_strdup(msginfo->subject);
1555 p += subject_get_prefix_length(p);
1556 memmove(buf, p, strlen(p) + 1);
1558 buf2 = g_strdup_printf("Fw: %s", buf);
1559 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1565 textview = GTK_TEXT_VIEW(compose->text);
1566 textbuf = gtk_text_view_get_buffer(textview);
1567 compose_create_tags(textview, compose);
1569 undo_block(compose->undostruct);
1573 msgfile = procmsg_get_message_file(msginfo);
1574 if (!is_file_exist(msgfile))
1575 g_warning("%s: file not exist\n", msgfile);
1577 compose_attach_append(compose, msgfile, msgfile,
1582 const gchar *qmark = NULL;
1583 const gchar *body_fmt = prefs_common.fw_quotefmt;
1584 MsgInfo *full_msginfo;
1586 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1588 full_msginfo = procmsg_msginfo_copy(msginfo);
1590 /* use the forward format of folder (if enabled), or the account's one
1591 (if enabled) or fallback to the global forward format, which is always
1592 enabled (even if empty), and use the relevant quotemark */
1593 if (msginfo->folder && msginfo->folder->prefs &&
1594 msginfo->folder->prefs->forward_with_format) {
1595 qmark = msginfo->folder->prefs->forward_quotemark;
1596 body_fmt = msginfo->folder->prefs->forward_body_format;
1598 } else if (account->forward_with_format) {
1599 qmark = account->forward_quotemark;
1600 body_fmt = account->forward_body_format;
1603 qmark = prefs_common.fw_quotemark;
1604 body_fmt = prefs_common.fw_quotefmt;
1607 /* empty quotemark is not allowed */
1608 if (qmark == NULL || *qmark == '\0')
1611 compose_quote_fmt(compose, full_msginfo,
1612 body_fmt, qmark, body, FALSE, TRUE,
1613 _("Message forward format error at line %d."));
1614 quote_fmt_reset_vartable();
1615 compose_attach_parts(compose, msginfo);
1617 procmsg_msginfo_free(full_msginfo);
1620 SIGNAL_BLOCK(textbuf);
1622 if (account->auto_sig)
1623 compose_insert_sig(compose, FALSE);
1625 compose_wrap_all(compose);
1627 SIGNAL_UNBLOCK(textbuf);
1629 gtk_text_buffer_get_start_iter(textbuf, &iter);
1630 gtk_text_buffer_place_cursor(textbuf, &iter);
1632 gtk_widget_grab_focus(compose->header_last->entry);
1634 if (!no_extedit && prefs_common.auto_exteditor)
1635 compose_exec_ext_editor(compose);
1638 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1639 gchar *folderidentifier;
1641 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1642 folderidentifier = folder_item_get_identifier(msginfo->folder);
1643 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
1644 g_free(folderidentifier);
1647 undo_unblock(compose->undostruct);
1649 compose->modified = FALSE;
1650 compose_set_title(compose);
1652 compose->updating = FALSE;
1654 if (compose->deferred_destroy) {
1655 compose_destroy(compose);
1662 #undef INSERT_FW_HEADER
1664 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1667 GtkTextView *textview;
1668 GtkTextBuffer *textbuf;
1672 gboolean single_mail = TRUE;
1674 g_return_val_if_fail(msginfo_list != NULL, NULL);
1676 if (g_slist_length(msginfo_list) > 1)
1677 single_mail = FALSE;
1679 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1680 if (((MsgInfo *)msginfo->data)->folder == NULL)
1683 /* guess account from first selected message */
1685 !(account = compose_guess_forward_account_from_msginfo
1686 (msginfo_list->data)))
1687 account = cur_account;
1689 g_return_val_if_fail(account != NULL, NULL);
1691 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1692 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1693 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1696 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1698 compose->updating = TRUE;
1700 textview = GTK_TEXT_VIEW(compose->text);
1701 textbuf = gtk_text_view_get_buffer(textview);
1702 compose_create_tags(textview, compose);
1704 undo_block(compose->undostruct);
1705 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1706 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
1708 if (!is_file_exist(msgfile))
1709 g_warning("%s: file not exist\n", msgfile);
1711 compose_attach_append(compose, msgfile, msgfile,
1717 MsgInfo *info = (MsgInfo *)msginfo_list->data;
1718 if (info->subject && *info->subject) {
1719 gchar *buf, *buf2, *p;
1721 buf = p = g_strdup(info->subject);
1722 p += subject_get_prefix_length(p);
1723 memmove(buf, p, strlen(p) + 1);
1725 buf2 = g_strdup_printf("Fw: %s", buf);
1726 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1732 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
1733 _("Fw: multiple emails"));
1736 SIGNAL_BLOCK(textbuf);
1738 if (account->auto_sig)
1739 compose_insert_sig(compose, FALSE);
1741 compose_wrap_all(compose);
1743 SIGNAL_UNBLOCK(textbuf);
1745 gtk_text_buffer_get_start_iter(textbuf, &iter);
1746 gtk_text_buffer_place_cursor(textbuf, &iter);
1748 gtk_widget_grab_focus(compose->header_last->entry);
1749 undo_unblock(compose->undostruct);
1750 compose->modified = FALSE;
1751 compose_set_title(compose);
1753 compose->updating = FALSE;
1755 if (compose->deferred_destroy) {
1756 compose_destroy(compose);
1763 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
1765 GtkTextIter start = *iter;
1766 GtkTextIter end_iter;
1767 int start_pos = gtk_text_iter_get_offset(&start);
1769 if (!compose->account->sig_sep)
1772 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
1773 start_pos+strlen(compose->account->sig_sep));
1775 /* check sig separator */
1776 str = gtk_text_iter_get_text(&start, &end_iter);
1777 if (!strcmp(str, compose->account->sig_sep)) {
1779 /* check end of line (\n) */
1780 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
1781 start_pos+strlen(compose->account->sig_sep));
1782 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
1783 start_pos+strlen(compose->account->sig_sep)+1);
1784 tmp = gtk_text_iter_get_text(&start, &end_iter);
1785 if (!strcmp(tmp,"\n")) {
1797 static void compose_colorize_signature(Compose *compose)
1799 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
1801 GtkTextIter end_iter;
1802 gtk_text_buffer_get_start_iter(buffer, &iter);
1803 while (gtk_text_iter_forward_line(&iter))
1804 if (compose_is_sig_separator(compose, buffer, &iter)) {
1805 gtk_text_buffer_get_end_iter(buffer, &end_iter);
1806 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
1810 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
1812 Compose *compose = NULL;
1813 PrefsAccount *account = NULL;
1814 GtkTextView *textview;
1815 GtkTextBuffer *textbuf;
1819 gchar buf[BUFFSIZE];
1820 gboolean use_signing = FALSE;
1821 gboolean use_encryption = FALSE;
1822 gchar *privacy_system = NULL;
1823 int priority = PRIORITY_NORMAL;
1824 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
1826 g_return_val_if_fail(msginfo != NULL, NULL);
1827 g_return_val_if_fail(msginfo->folder != NULL, NULL);
1829 if (compose_put_existing_to_front(msginfo)) {
1833 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
1834 folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
1835 gchar queueheader_buf[BUFFSIZE];
1838 /* Select Account from queue headers */
1839 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1840 sizeof(queueheader_buf), "X-Claws-Account-Id:")) {
1841 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
1842 account = account_find_from_id(id);
1844 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1845 sizeof(queueheader_buf), "X-Sylpheed-Account-Id:")) {
1846 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
1847 account = account_find_from_id(id);
1849 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1850 sizeof(queueheader_buf), "NAID:")) {
1851 id = atoi(&queueheader_buf[strlen("NAID:")]);
1852 account = account_find_from_id(id);
1854 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1855 sizeof(queueheader_buf), "MAID:")) {
1856 id = atoi(&queueheader_buf[strlen("MAID:")]);
1857 account = account_find_from_id(id);
1859 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1860 sizeof(queueheader_buf), "S:")) {
1861 account = account_find_from_address(queueheader_buf);
1863 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1864 sizeof(queueheader_buf), "X-Claws-Sign:")) {
1865 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
1866 use_signing = param;
1869 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1870 sizeof(queueheader_buf), "X-Sylpheed-Sign:")) {
1871 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
1872 use_signing = param;
1875 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1876 sizeof(queueheader_buf), "X-Claws-Encrypt:")) {
1877 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
1878 use_encryption = param;
1880 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1881 sizeof(queueheader_buf), "X-Sylpheed-Encrypt:")) {
1882 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
1883 use_encryption = param;
1885 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1886 sizeof(queueheader_buf), "X-Claws-Privacy-System:")) {
1887 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
1889 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1890 sizeof(queueheader_buf), "X-Sylpheed-Privacy-System:")) {
1891 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
1893 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1894 sizeof(queueheader_buf), "X-Priority: ")) {
1895 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
1898 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1899 sizeof(queueheader_buf), "RMID:")) {
1900 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
1901 if (tokens[0] && tokens[1] && tokens[2]) {
1902 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
1903 if (orig_item != NULL) {
1904 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
1909 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1910 sizeof(queueheader_buf), "FMID:")) {
1911 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
1912 if (tokens[0] && tokens[1] && tokens[2]) {
1913 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
1914 if (orig_item != NULL) {
1915 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
1921 account = msginfo->folder->folder->account;
1924 if (!account && prefs_common.reedit_account_autosel) {
1925 gchar from[BUFFSIZE];
1926 if (!procheader_get_header_from_msginfo(msginfo, from, sizeof(from), "FROM:")) {
1927 extract_address(from);
1928 account = account_find_from_address(from);
1932 account = cur_account;
1934 g_return_val_if_fail(account != NULL, NULL);
1936 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
1938 compose->replyinfo = replyinfo;
1939 compose->fwdinfo = fwdinfo;
1941 compose->updating = TRUE;
1942 compose->priority = priority;
1944 if (privacy_system != NULL) {
1945 compose->privacy_system = privacy_system;
1946 compose_use_signing(compose, use_signing);
1947 compose_use_encryption(compose, use_encryption);
1948 compose_update_privacy_system_menu_item(compose, FALSE);
1950 activate_privacy_system(compose, account, FALSE);
1953 compose->targetinfo = procmsg_msginfo_copy(msginfo);
1955 compose_extract_original_charset(compose);
1957 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
1958 folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
1959 gchar queueheader_buf[BUFFSIZE];
1961 /* Set message save folder */
1962 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "SCF:")) {
1965 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1966 gtk_editable_delete_text(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
1967 gtk_editable_insert_text(GTK_EDITABLE(compose->savemsg_entry), &queueheader_buf[4], strlen(&queueheader_buf[4]), &startpos);
1969 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "RRCPT:")) {
1970 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
1972 GtkItemFactory *ifactory;
1973 ifactory = gtk_item_factory_from_widget(compose->menubar);
1974 menu_set_active(ifactory, "/Options/Request Return Receipt", TRUE);
1979 if (compose_parse_header(compose, msginfo) < 0) {
1980 compose->updating = FALSE;
1981 compose_destroy(compose);
1984 compose_reedit_set_entry(compose, msginfo);
1986 textview = GTK_TEXT_VIEW(compose->text);
1987 textbuf = gtk_text_view_get_buffer(textview);
1988 compose_create_tags(textview, compose);
1990 mark = gtk_text_buffer_get_insert(textbuf);
1991 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
1993 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
1994 G_CALLBACK(compose_changed_cb),
1997 if (procmime_msginfo_is_encrypted(msginfo)) {
1998 fp = procmime_get_first_encrypted_text_content(msginfo);
2000 compose_force_encryption(compose, account, TRUE);
2003 fp = procmime_get_first_text_content(msginfo);
2006 g_warning("Can't get text part\n");
2010 gboolean prev_autowrap = compose->autowrap;
2012 compose->autowrap = FALSE;
2013 while (fgets(buf, sizeof(buf), fp) != NULL) {
2015 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2017 compose_wrap_all_full(compose, FALSE);
2018 compose->autowrap = prev_autowrap;
2022 compose_attach_parts(compose, msginfo);
2024 compose_colorize_signature(compose);
2026 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2027 G_CALLBACK(compose_changed_cb),
2030 gtk_widget_grab_focus(compose->text);
2032 if (prefs_common.auto_exteditor) {
2033 compose_exec_ext_editor(compose);
2035 compose->modified = FALSE;
2036 compose_set_title(compose);
2038 compose->updating = FALSE;
2040 if (compose->deferred_destroy) {
2041 compose_destroy(compose);
2045 compose->sig_str = compose_get_signature_str(compose);
2050 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2055 GtkItemFactory *ifactory;
2058 g_return_val_if_fail(msginfo != NULL, NULL);
2061 account = account_get_reply_account(msginfo,
2062 prefs_common.reply_account_autosel);
2063 g_return_val_if_fail(account != NULL, NULL);
2065 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2067 compose->updating = TRUE;
2069 ifactory = gtk_item_factory_from_widget(compose->menubar);
2070 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2071 compose->replyinfo = NULL;
2072 compose->fwdinfo = NULL;
2074 compose_show_first_last_header(compose, TRUE);
2076 gtk_widget_grab_focus(compose->header_last->entry);
2078 filename = procmsg_get_message_file(msginfo);
2080 if (filename == NULL) {
2081 compose->updating = FALSE;
2082 compose_destroy(compose);
2087 compose->redirect_filename = filename;
2089 /* Set save folder */
2090 item = msginfo->folder;
2091 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2092 gchar *folderidentifier;
2094 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
2095 folderidentifier = folder_item_get_identifier(item);
2096 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
2097 g_free(folderidentifier);
2100 compose_attach_parts(compose, msginfo);
2102 if (msginfo->subject)
2103 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2105 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2107 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2108 _("Message redirect format error at line %d."));
2109 quote_fmt_reset_vartable();
2110 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2112 compose_colorize_signature(compose);
2114 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
2115 menu_set_sensitive(ifactory, "/Add...", FALSE);
2116 menu_set_sensitive(ifactory, "/Remove", FALSE);
2117 menu_set_sensitive(ifactory, "/Properties...", FALSE);
2119 ifactory = gtk_item_factory_from_widget(compose->menubar);
2120 menu_set_sensitive(ifactory, "/Message/Save", FALSE);
2121 menu_set_sensitive(ifactory, "/Message/Insert file", FALSE);
2122 menu_set_sensitive(ifactory, "/Message/Attach file", FALSE);
2123 menu_set_sensitive(ifactory, "/Message/Insert signature", FALSE);
2124 menu_set_sensitive(ifactory, "/Edit", FALSE);
2125 menu_set_sensitive(ifactory, "/Options", FALSE);
2126 menu_set_sensitive(ifactory, "/Tools/Show ruler", FALSE);
2127 menu_set_sensitive(ifactory, "/Tools/Actions", FALSE);
2129 if (compose->toolbar->draft_btn)
2130 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2131 if (compose->toolbar->insert_btn)
2132 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2133 if (compose->toolbar->attach_btn)
2134 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2135 if (compose->toolbar->sig_btn)
2136 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2137 if (compose->toolbar->exteditor_btn)
2138 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2139 if (compose->toolbar->linewrap_current_btn)
2140 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2141 if (compose->toolbar->linewrap_all_btn)
2142 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2144 compose->modified = FALSE;
2145 compose_set_title(compose);
2146 compose->updating = FALSE;
2148 if (compose->deferred_destroy) {
2149 compose_destroy(compose);
2156 GList *compose_get_compose_list(void)
2158 return compose_list;
2161 void compose_entry_append(Compose *compose, const gchar *address,
2162 ComposeEntryType type)
2164 const gchar *header;
2166 gboolean in_quote = FALSE;
2167 if (!address || *address == '\0') return;
2174 header = N_("Bcc:");
2176 case COMPOSE_REPLYTO:
2177 header = N_("Reply-To:");
2179 case COMPOSE_NEWSGROUPS:
2180 header = N_("Newsgroups:");
2182 case COMPOSE_FOLLOWUPTO:
2183 header = N_( "Followup-To:");
2190 header = prefs_common_translated_header_name(header);
2192 cur = begin = (gchar *)address;
2194 /* we separate the line by commas, but not if we're inside a quoted
2196 while (*cur != '\0') {
2198 in_quote = !in_quote;
2199 if (*cur == ',' && !in_quote) {
2200 gchar *tmp = g_strdup(begin);
2202 tmp[cur-begin]='\0';
2205 while (*tmp == ' ' || *tmp == '\t')
2207 compose_add_header_entry(compose, header, tmp);
2214 gchar *tmp = g_strdup(begin);
2216 tmp[cur-begin]='\0';
2219 while (*tmp == ' ' || *tmp == '\t')
2221 compose_add_header_entry(compose, header, tmp);
2226 static void compose_entry_mark_default_to(Compose *compose, const gchar *mailto)
2228 static GdkColor yellow;
2229 static GdkColor black;
2230 static gboolean yellow_initialised = FALSE;
2234 if (!yellow_initialised) {
2235 gdk_color_parse("#f5f6be", &yellow);
2236 gdk_color_parse("#000000", &black);
2237 yellow_initialised = gdk_colormap_alloc_color(
2238 gdk_colormap_get_system(), &yellow, FALSE, TRUE);
2239 yellow_initialised &= gdk_colormap_alloc_color(
2240 gdk_colormap_get_system(), &black, FALSE, TRUE);
2243 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2244 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2245 if (gtk_entry_get_text(entry) &&
2246 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2247 if (yellow_initialised) {
2248 gtk_widget_modify_base(
2249 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2250 GTK_STATE_NORMAL, &yellow);
2251 gtk_widget_modify_text(
2252 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2253 GTK_STATE_NORMAL, &black);
2259 void compose_toolbar_cb(gint action, gpointer data)
2261 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2262 Compose *compose = (Compose*)toolbar_item->parent;
2264 g_return_if_fail(compose != NULL);
2268 compose_send_cb(compose, 0, NULL);
2271 compose_send_later_cb(compose, 0, NULL);
2274 compose_draft_cb(compose, COMPOSE_QUIT_EDITING, NULL);
2277 compose_insert_file_cb(compose, 0, NULL);
2280 compose_attach_cb(compose, 0, NULL);
2283 compose_insert_sig(compose, FALSE);
2286 compose_ext_editor_cb(compose, 0, NULL);
2288 case A_LINEWRAP_CURRENT:
2289 compose_beautify_paragraph(compose, NULL, TRUE);
2291 case A_LINEWRAP_ALL:
2292 compose_wrap_all_full(compose, TRUE);
2295 compose_address_cb(compose, 0, NULL);
2298 case A_CHECK_SPELLING:
2299 compose_check_all(compose);
2307 static void compose_entries_set(Compose *compose, const gchar *mailto)
2311 gchar *subject = NULL;
2315 gchar *attach = NULL;
2317 scan_mailto_url(mailto, &to, &cc, NULL, &subject, &body, &attach);
2320 compose_entry_append(compose, to, COMPOSE_TO);
2322 compose_entry_append(compose, cc, COMPOSE_CC);
2324 if (!g_utf8_validate (subject, -1, NULL)) {
2325 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2326 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2329 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2333 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2334 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2337 gboolean prev_autowrap = compose->autowrap;
2339 compose->autowrap = FALSE;
2341 mark = gtk_text_buffer_get_insert(buffer);
2342 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2344 if (!g_utf8_validate (body, -1, NULL)) {
2345 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2346 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2349 gtk_text_buffer_insert(buffer, &iter, body, -1);
2351 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2353 compose->autowrap = prev_autowrap;
2354 if (compose->autowrap)
2355 compose_wrap_all(compose);
2359 gchar *utf8_filename = conv_filename_to_utf8(attach);
2360 if (utf8_filename) {
2361 if (compose_attach_append(compose, attach, utf8_filename, NULL)) {
2362 alertpanel_notice(_("The file '%s' has been attached."), attach);
2364 g_free(utf8_filename);
2366 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2376 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2378 static HeaderEntry hentry[] = {{"Reply-To:", NULL, TRUE},
2379 {"Cc:", NULL, TRUE},
2380 {"References:", NULL, FALSE},
2381 {"Bcc:", NULL, TRUE},
2382 {"Newsgroups:", NULL, TRUE},
2383 {"Followup-To:", NULL, TRUE},
2384 {"List-Post:", NULL, FALSE},
2385 {"X-Priority:", NULL, FALSE},
2386 {NULL, NULL, FALSE}};
2402 g_return_val_if_fail(msginfo != NULL, -1);
2404 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2405 procheader_get_header_fields(fp, hentry);
2408 if (hentry[H_REPLY_TO].body != NULL) {
2409 if (hentry[H_REPLY_TO].body[0] != '\0') {
2411 conv_unmime_header(hentry[H_REPLY_TO].body,
2414 g_free(hentry[H_REPLY_TO].body);
2415 hentry[H_REPLY_TO].body = NULL;
2417 if (hentry[H_CC].body != NULL) {
2418 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL);
2419 g_free(hentry[H_CC].body);
2420 hentry[H_CC].body = NULL;
2422 if (hentry[H_REFERENCES].body != NULL) {
2423 if (compose->mode == COMPOSE_REEDIT)
2424 compose->references = hentry[H_REFERENCES].body;
2426 compose->references = compose_parse_references
2427 (hentry[H_REFERENCES].body, msginfo->msgid);
2428 g_free(hentry[H_REFERENCES].body);
2430 hentry[H_REFERENCES].body = NULL;
2432 if (hentry[H_BCC].body != NULL) {
2433 if (compose->mode == COMPOSE_REEDIT)
2435 conv_unmime_header(hentry[H_BCC].body, NULL);
2436 g_free(hentry[H_BCC].body);
2437 hentry[H_BCC].body = NULL;
2439 if (hentry[H_NEWSGROUPS].body != NULL) {
2440 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2441 hentry[H_NEWSGROUPS].body = NULL;
2443 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2444 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2445 compose->followup_to =
2446 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2449 g_free(hentry[H_FOLLOWUP_TO].body);
2450 hentry[H_FOLLOWUP_TO].body = NULL;
2452 if (hentry[H_LIST_POST].body != NULL) {
2455 extract_address(hentry[H_LIST_POST].body);
2456 if (hentry[H_LIST_POST].body[0] != '\0') {
2457 scan_mailto_url(hentry[H_LIST_POST].body,
2458 &to, NULL, NULL, NULL, NULL, NULL);
2460 g_free(compose->ml_post);
2461 compose->ml_post = to;
2464 g_free(hentry[H_LIST_POST].body);
2465 hentry[H_LIST_POST].body = NULL;
2468 /* CLAWS - X-Priority */
2469 if (compose->mode == COMPOSE_REEDIT)
2470 if (hentry[H_X_PRIORITY].body != NULL) {
2473 priority = atoi(hentry[H_X_PRIORITY].body);
2474 g_free(hentry[H_X_PRIORITY].body);
2476 hentry[H_X_PRIORITY].body = NULL;
2478 if (priority < PRIORITY_HIGHEST ||
2479 priority > PRIORITY_LOWEST)
2480 priority = PRIORITY_NORMAL;
2482 compose->priority = priority;
2485 if (compose->mode == COMPOSE_REEDIT) {
2486 if (msginfo->inreplyto && *msginfo->inreplyto)
2487 compose->inreplyto = g_strdup(msginfo->inreplyto);
2491 if (msginfo->msgid && *msginfo->msgid)
2492 compose->inreplyto = g_strdup(msginfo->msgid);
2494 if (!compose->references) {
2495 if (msginfo->msgid && *msginfo->msgid) {
2496 if (msginfo->inreplyto && *msginfo->inreplyto)
2497 compose->references =
2498 g_strdup_printf("<%s>\n\t<%s>",
2502 compose->references =
2503 g_strconcat("<", msginfo->msgid, ">",
2505 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
2506 compose->references =
2507 g_strconcat("<", msginfo->inreplyto, ">",
2515 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
2517 GSList *ref_id_list, *cur;
2521 ref_id_list = references_list_append(NULL, ref);
2522 if (!ref_id_list) return NULL;
2523 if (msgid && *msgid)
2524 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
2529 for (cur = ref_id_list; cur != NULL; cur = cur->next)
2530 /* "<" + Message-ID + ">" + CR+LF+TAB */
2531 len += strlen((gchar *)cur->data) + 5;
2533 if (len > MAX_REFERENCES_LEN) {
2534 /* remove second message-ID */
2535 if (ref_id_list && ref_id_list->next &&
2536 ref_id_list->next->next) {
2537 g_free(ref_id_list->next->data);
2538 ref_id_list = g_slist_remove
2539 (ref_id_list, ref_id_list->next->data);
2541 slist_free_strings(ref_id_list);
2542 g_slist_free(ref_id_list);
2549 new_ref = g_string_new("");
2550 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
2551 if (new_ref->len > 0)
2552 g_string_append(new_ref, "\n\t");
2553 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
2556 slist_free_strings(ref_id_list);
2557 g_slist_free(ref_id_list);
2559 new_ref_str = new_ref->str;
2560 g_string_free(new_ref, FALSE);
2565 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
2566 const gchar *fmt, const gchar *qmark,
2567 const gchar *body, gboolean rewrap,
2568 gboolean need_unescape,
2569 const gchar *err_msg)
2571 MsgInfo* dummyinfo = NULL;
2572 gchar *quote_str = NULL;
2574 gboolean prev_autowrap;
2575 const gchar *trimmed_body = body;
2576 gint cursor_pos = -1;
2577 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2578 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2583 SIGNAL_BLOCK(buffer);
2586 dummyinfo = compose_msginfo_new_from_compose(compose);
2587 msginfo = dummyinfo;
2590 if (qmark != NULL) {
2592 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
2593 compose->gtkaspell);
2595 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
2597 quote_fmt_scan_string(qmark);
2600 buf = quote_fmt_get_buffer();
2602 alertpanel_error(_("Quote mark format error."));
2604 Xstrdup_a(quote_str, buf, goto error)
2607 if (fmt && *fmt != '\0') {
2610 while (*trimmed_body == '\n')
2614 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account,
2615 compose->gtkaspell);
2617 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account);
2619 if (need_unescape) {
2622 /* decode \-escape sequences in the internal representation of the quote format */
2623 tmp = malloc(strlen(fmt)+1);
2624 pref_get_unescaped_pref(tmp, fmt);
2625 quote_fmt_scan_string(tmp);
2629 quote_fmt_scan_string(fmt);
2633 buf = quote_fmt_get_buffer();
2635 gint line = quote_fmt_get_line();
2636 gchar *msg = g_strdup_printf(err_msg, line);
2637 alertpanel_error(msg);
2644 prev_autowrap = compose->autowrap;
2645 compose->autowrap = FALSE;
2647 mark = gtk_text_buffer_get_insert(buffer);
2648 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2649 if (g_utf8_validate(buf, -1, NULL)) {
2650 gtk_text_buffer_insert(buffer, &iter, buf, -1);
2652 gchar *tmpout = NULL;
2653 tmpout = conv_codeset_strdup
2654 (buf, conv_get_locale_charset_str_no_utf8(),
2656 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
2658 tmpout = g_malloc(strlen(buf)*2+1);
2659 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
2661 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
2665 cursor_pos = quote_fmt_get_cursor_pos();
2666 compose->set_cursor_pos = cursor_pos;
2667 if (cursor_pos == -1) {
2670 gtk_text_buffer_get_start_iter(buffer, &iter);
2671 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
2672 gtk_text_buffer_place_cursor(buffer, &iter);
2674 compose->autowrap = prev_autowrap;
2675 if (compose->autowrap && rewrap)
2676 compose_wrap_all(compose);
2683 SIGNAL_UNBLOCK(buffer);
2685 procmsg_msginfo_free( dummyinfo );
2690 /* if ml_post is of type addr@host and from is of type
2691 * addr-anything@host, return TRUE
2693 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
2695 gchar *left_ml = NULL;
2696 gchar *right_ml = NULL;
2697 gchar *left_from = NULL;
2698 gchar *right_from = NULL;
2699 gboolean result = FALSE;
2701 if (!ml_post || !from)
2704 left_ml = g_strdup(ml_post);
2705 if (strstr(left_ml, "@")) {
2706 right_ml = strstr(left_ml, "@")+1;
2707 *(strstr(left_ml, "@")) = '\0';
2710 left_from = g_strdup(from);
2711 if (strstr(left_from, "@")) {
2712 right_from = strstr(left_from, "@")+1;
2713 *(strstr(left_from, "@")) = '\0';
2716 if (left_ml && left_from && right_ml && right_from
2717 && !strncmp(left_from, left_ml, strlen(left_ml))
2718 && !strcmp(right_from, right_ml)) {
2727 static gboolean same_address(const gchar *addr1, const gchar *addr2)
2729 gchar *my_addr1, *my_addr2;
2731 if (!addr1 || !addr2)
2734 Xstrdup_a(my_addr1, addr1, return FALSE);
2735 Xstrdup_a(my_addr2, addr2, return FALSE);
2737 extract_address(my_addr1);
2738 extract_address(my_addr2);
2740 return !strcasecmp(my_addr1, my_addr2);
2743 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
2744 gboolean to_all, gboolean to_ml,
2746 gboolean followup_and_reply_to)
2748 GSList *cc_list = NULL;
2751 gchar *replyto = NULL;
2752 GHashTable *to_table;
2754 gboolean reply_to_ml = FALSE;
2755 gboolean default_reply_to = FALSE;
2757 g_return_if_fail(compose->account != NULL);
2758 g_return_if_fail(msginfo != NULL);
2760 reply_to_ml = to_ml && compose->ml_post;
2762 default_reply_to = msginfo->folder &&
2763 msginfo->folder->prefs->enable_default_reply_to;
2765 if (compose->account->protocol != A_NNTP) {
2766 if (reply_to_ml && !default_reply_to) {
2768 gboolean is_subscr = is_subscription(compose->ml_post,
2771 /* normal answer to ml post with a reply-to */
2772 compose_entry_append(compose,
2775 if (compose->replyto
2776 && !same_address(compose->ml_post, compose->replyto))
2777 compose_entry_append(compose,
2781 /* answer to subscription confirmation */
2782 if (compose->replyto)
2783 compose_entry_append(compose,
2786 else if (msginfo->from)
2787 compose_entry_append(compose,
2792 else if (!(to_all || to_sender) && default_reply_to) {
2793 compose_entry_append(compose,
2794 msginfo->folder->prefs->default_reply_to,
2796 compose_entry_mark_default_to(compose,
2797 msginfo->folder->prefs->default_reply_to);
2802 Xstrdup_a(tmp1, msginfo->from, return);
2803 extract_address(tmp1);
2804 if (to_all || to_sender ||
2805 !account_find_from_address(tmp1))
2806 compose_entry_append(compose,
2807 (compose->replyto && !to_sender)
2808 ? compose->replyto :
2809 msginfo->from ? msginfo->from : "",
2811 else if (!to_all && !to_sender) {
2812 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
2813 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
2814 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
2815 compose_entry_append(compose,
2816 msginfo->from ? msginfo->from : "",
2819 /* replying to own mail, use original recp */
2820 compose_entry_append(compose,
2821 msginfo->to ? msginfo->to : "",
2823 compose_entry_append(compose,
2824 msginfo->cc ? msginfo->cc : "",
2830 if (to_sender || (compose->followup_to &&
2831 !strncmp(compose->followup_to, "poster", 6)))
2832 compose_entry_append
2834 (compose->replyto ? compose->replyto :
2835 msginfo->from ? msginfo->from : ""),
2838 else if (followup_and_reply_to || to_all) {
2839 compose_entry_append
2841 (compose->replyto ? compose->replyto :
2842 msginfo->from ? msginfo->from : ""),
2845 compose_entry_append
2847 compose->followup_to ? compose->followup_to :
2848 compose->newsgroups ? compose->newsgroups : "",
2849 COMPOSE_NEWSGROUPS);
2852 compose_entry_append
2854 compose->followup_to ? compose->followup_to :
2855 compose->newsgroups ? compose->newsgroups : "",
2856 COMPOSE_NEWSGROUPS);
2859 if (msginfo->subject && *msginfo->subject) {
2863 buf = p = g_strdup(msginfo->subject);
2864 p += subject_get_prefix_length(p);
2865 memmove(buf, p, strlen(p) + 1);
2867 buf2 = g_strdup_printf("Re: %s", buf);
2868 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
2873 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
2875 if (to_ml && compose->ml_post) return;
2876 if (!to_all || compose->account->protocol == A_NNTP) return;
2878 if (compose->replyto) {
2879 Xstrdup_a(replyto, compose->replyto, return);
2880 extract_address(replyto);
2882 if (msginfo->from) {
2883 Xstrdup_a(from, msginfo->from, return);
2884 extract_address(from);
2887 if (replyto && from)
2888 cc_list = address_list_append_with_comments(cc_list, from);
2889 if (to_all && msginfo->folder &&
2890 msginfo->folder->prefs->enable_default_reply_to)
2891 cc_list = address_list_append_with_comments(cc_list,
2892 msginfo->folder->prefs->default_reply_to);
2893 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
2894 cc_list = address_list_append_with_comments(cc_list, compose->cc);
2896 to_table = g_hash_table_new(g_str_hash, g_str_equal);
2898 g_hash_table_insert(to_table, g_utf8_strdown(replyto, -1), GINT_TO_POINTER(1));
2899 if (compose->account) {
2900 g_hash_table_insert(to_table, g_utf8_strdown(compose->account->address, -1),
2901 GINT_TO_POINTER(1));
2903 /* remove address on To: and that of current account */
2904 for (cur = cc_list; cur != NULL; ) {
2905 GSList *next = cur->next;
2908 addr = g_utf8_strdown(cur->data, -1);
2909 extract_address(addr);
2911 if (GPOINTER_TO_INT(g_hash_table_lookup(to_table, addr)) == 1)
2912 cc_list = g_slist_remove(cc_list, cur->data);
2914 g_hash_table_insert(to_table, addr, GINT_TO_POINTER(1));
2918 hash_free_strings(to_table);
2919 g_hash_table_destroy(to_table);
2922 for (cur = cc_list; cur != NULL; cur = cur->next)
2923 compose_entry_append(compose, (gchar *)cur->data,
2925 slist_free_strings(cc_list);
2926 g_slist_free(cc_list);
2931 #define SET_ENTRY(entry, str) \
2934 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
2937 #define SET_ADDRESS(type, str) \
2940 compose_entry_append(compose, str, type); \
2943 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
2945 g_return_if_fail(msginfo != NULL);
2947 SET_ENTRY(subject_entry, msginfo->subject);
2948 SET_ENTRY(from_name, msginfo->from);
2949 SET_ADDRESS(COMPOSE_TO, msginfo->to);
2950 SET_ADDRESS(COMPOSE_CC, compose->cc);
2951 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
2952 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
2953 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
2954 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
2956 compose_update_priority_menu_item(compose);
2957 compose_update_privacy_system_menu_item(compose, FALSE);
2958 compose_show_first_last_header(compose, TRUE);
2964 static void compose_insert_sig(Compose *compose, gboolean replace)
2966 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2967 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2969 GtkTextIter iter, iter_end;
2971 gchar *search = NULL;
2972 gboolean prev_autowrap;
2973 gboolean found = FALSE, shift = FALSE;
2976 g_return_if_fail(compose->account != NULL);
2978 prev_autowrap = compose->autowrap;
2979 compose->autowrap = FALSE;
2981 g_signal_handlers_block_by_func(G_OBJECT(buffer),
2982 G_CALLBACK(compose_changed_cb),
2985 mark = gtk_text_buffer_get_insert(buffer);
2986 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2987 cur_pos = gtk_text_iter_get_offset (&iter);
2989 gtk_text_buffer_get_end_iter(buffer, &iter);
2991 search = compose->sig_str;
2993 if (replace && search) {
2994 GtkTextIter first_iter, start_iter, end_iter;
2996 gtk_text_buffer_get_start_iter(buffer, &first_iter);
2998 if (compose->sig_str[0] == '\0')
3001 found = gtk_text_iter_forward_search(&first_iter,
3003 GTK_TEXT_SEARCH_TEXT_ONLY,
3004 &start_iter, &end_iter,
3008 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3012 if (replace && !found && search && strlen(search) > 2
3013 && search[0] == '\n' && search[1] == '\n') {
3019 g_free(compose->sig_str);
3020 compose->sig_str = compose_get_signature_str(compose);
3021 if (!compose->sig_str || (replace && !compose->account->auto_sig))
3022 compose->sig_str = g_strdup("");
3024 cur_pos = gtk_text_iter_get_offset(&iter);
3026 gtk_text_buffer_insert(buffer, &iter, compose->sig_str + 1, -1);
3028 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3030 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3031 gtk_text_iter_forward_char(&iter);
3032 gtk_text_iter_forward_char(&iter);
3033 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3034 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3036 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3037 cur_pos = gtk_text_buffer_get_char_count (buffer);
3039 /* put the cursor where it should be
3040 * either where the quote_fmt says, either before the signature */
3041 if (compose->set_cursor_pos < 0)
3042 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3044 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3045 compose->set_cursor_pos);
3047 gtk_text_buffer_place_cursor(buffer, &iter);
3048 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3049 G_CALLBACK(compose_changed_cb),
3052 compose->autowrap = prev_autowrap;
3053 if (compose->autowrap)
3054 compose_wrap_all(compose);
3057 static gchar *compose_get_signature_str(Compose *compose)
3059 gchar *sig_body = NULL;
3060 gchar *sig_str = NULL;
3061 gchar *utf8_sig_str = NULL;
3063 g_return_val_if_fail(compose->account != NULL, NULL);
3065 if (!compose->account->sig_path)
3068 if (compose->account->sig_type == SIG_FILE) {
3069 if (!is_file_or_fifo_exist(compose->account->sig_path)) {
3070 g_warning("can't open signature file: %s\n",
3071 compose->account->sig_path);
3076 if (compose->account->sig_type == SIG_COMMAND)
3077 sig_body = get_command_output(compose->account->sig_path);
3081 tmp = file_read_to_str(compose->account->sig_path);
3084 sig_body = normalize_newlines(tmp);
3088 if (compose->account->sig_sep) {
3089 sig_str = g_strconcat("\n\n", compose->account->sig_sep, "\n", sig_body,
3093 sig_str = g_strconcat("\n\n", sig_body, NULL);
3096 if (g_utf8_validate(sig_str, -1, NULL) == TRUE)
3097 utf8_sig_str = sig_str;
3099 utf8_sig_str = conv_codeset_strdup
3100 (sig_str, conv_get_locale_charset_str_no_utf8(),
3106 return utf8_sig_str;
3109 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3112 GtkTextBuffer *buffer;
3115 const gchar *cur_encoding;
3116 gchar buf[BUFFSIZE];
3119 gboolean prev_autowrap;
3120 gboolean badtxt = FALSE;
3122 g_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3124 if ((fp = g_fopen(file, "rb")) == NULL) {
3125 FILE_OP_ERROR(file, "fopen");
3126 return COMPOSE_INSERT_READ_ERROR;
3129 prev_autowrap = compose->autowrap;
3130 compose->autowrap = FALSE;
3132 text = GTK_TEXT_VIEW(compose->text);
3133 buffer = gtk_text_view_get_buffer(text);
3134 mark = gtk_text_buffer_get_insert(buffer);
3135 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3137 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3138 G_CALLBACK(text_inserted),
3141 cur_encoding = conv_get_locale_charset_str_no_utf8();
3143 while (fgets(buf, sizeof(buf), fp) != NULL) {
3146 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3147 str = g_strdup(buf);
3149 str = conv_codeset_strdup
3150 (buf, cur_encoding, CS_INTERNAL);
3153 /* strip <CR> if DOS/Windows file,
3154 replace <CR> with <LF> if Macintosh file. */
3157 if (len > 0 && str[len - 1] != '\n') {
3159 if (str[len] == '\r') str[len] = '\n';
3162 gtk_text_buffer_insert(buffer, &iter, str, -1);
3166 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3167 G_CALLBACK(text_inserted),
3169 compose->autowrap = prev_autowrap;
3170 if (compose->autowrap)
3171 compose_wrap_all(compose);
3176 return COMPOSE_INSERT_INVALID_CHARACTER;
3178 return COMPOSE_INSERT_SUCCESS;
3181 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3182 const gchar *filename,
3183 const gchar *content_type)
3191 GtkListStore *store;
3193 gboolean has_binary = FALSE;
3195 if (!is_file_exist(file)) {
3196 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3197 gboolean result = FALSE;
3198 if (file_from_uri && is_file_exist(file_from_uri)) {
3199 result = compose_attach_append(
3200 compose, file_from_uri,
3204 g_free(file_from_uri);
3207 alertpanel_error("File %s doesn't exist\n", filename);
3210 if ((size = get_file_size(file)) < 0) {
3211 alertpanel_error("Can't get file size of %s\n", filename);
3215 alertpanel_error(_("File %s is empty."), filename);
3218 if ((fp = g_fopen(file, "rb")) == NULL) {
3219 alertpanel_error(_("Can't read %s."), filename);
3224 ainfo = g_new0(AttachInfo, 1);
3225 auto_ainfo = g_auto_pointer_new_with_free
3226 (ainfo, (GFreeFunc) compose_attach_info_free);
3227 ainfo->file = g_strdup(file);
3230 ainfo->content_type = g_strdup(content_type);
3231 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3233 MsgFlags flags = {0, 0};
3235 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3236 ainfo->encoding = ENC_7BIT;
3238 ainfo->encoding = ENC_8BIT;
3240 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3241 if (msginfo && msginfo->subject)
3242 name = g_strdup(msginfo->subject);
3244 name = g_path_get_basename(filename ? filename : file);
3246 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3248 procmsg_msginfo_free(msginfo);
3250 if (!g_ascii_strncasecmp(content_type, "text", 4))
3251 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3253 ainfo->encoding = ENC_BASE64;
3254 name = g_path_get_basename(filename ? filename : file);
3255 ainfo->name = g_strdup(name);
3259 ainfo->content_type = procmime_get_mime_type(file);
3260 if (!ainfo->content_type) {
3261 ainfo->content_type =
3262 g_strdup("application/octet-stream");
3263 ainfo->encoding = ENC_BASE64;
3264 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text", 4))
3266 procmime_get_encoding_for_text_file(file, &has_binary);
3268 ainfo->encoding = ENC_BASE64;
3269 name = g_path_get_basename(filename ? filename : file);
3270 ainfo->name = g_strdup(name);
3274 if (ainfo->name != NULL
3275 && !strcmp(ainfo->name, ".")) {
3276 g_free(ainfo->name);
3280 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3281 g_free(ainfo->content_type);
3282 ainfo->content_type = g_strdup("application/octet-stream");
3286 size_text = to_human_readable(size);
3288 store = GTK_LIST_STORE(gtk_tree_view_get_model
3289 (GTK_TREE_VIEW(compose->attach_clist)));
3291 gtk_list_store_append(store, &iter);
3292 gtk_list_store_set(store, &iter,
3293 COL_MIMETYPE, ainfo->content_type,
3294 COL_SIZE, size_text,
3295 COL_NAME, ainfo->name,
3297 COL_AUTODATA, auto_ainfo,
3300 g_auto_pointer_free(auto_ainfo);
3304 static void compose_use_signing(Compose *compose, gboolean use_signing)
3306 GtkItemFactory *ifactory;
3307 GtkWidget *menuitem = NULL;
3309 compose->use_signing = use_signing;
3310 ifactory = gtk_item_factory_from_widget(compose->menubar);
3311 menuitem = gtk_item_factory_get_item
3312 (ifactory, "/Options/Sign");
3313 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
3317 static void compose_use_encryption(Compose *compose, gboolean use_encryption)
3319 GtkItemFactory *ifactory;
3320 GtkWidget *menuitem = NULL;
3322 compose->use_encryption = use_encryption;
3323 ifactory = gtk_item_factory_from_widget(compose->menubar);
3324 menuitem = gtk_item_factory_get_item
3325 (ifactory, "/Options/Encrypt");
3327 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
3331 #define NEXT_PART_NOT_CHILD(info) \
3333 node = info->node; \
3334 while (node->children) \
3335 node = g_node_last_child(node); \
3336 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3339 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3343 MimeInfo *firsttext = NULL;
3344 MimeInfo *encrypted = NULL;
3347 const gchar *partname = NULL;
3349 mimeinfo = procmime_scan_message(msginfo);
3350 if (!mimeinfo) return;
3352 if (mimeinfo->node->children == NULL) {
3353 procmime_mimeinfo_free_all(mimeinfo);
3357 /* find first content part */
3358 child = (MimeInfo *) mimeinfo->node->children->data;
3359 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3360 child = (MimeInfo *)child->node->children->data;
3362 if (child->type == MIMETYPE_TEXT) {
3364 debug_print("First text part found\n");
3365 } else if (compose->mode == COMPOSE_REEDIT &&
3366 child->type == MIMETYPE_APPLICATION &&
3367 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3368 encrypted = (MimeInfo *)child->node->parent->data;
3371 child = (MimeInfo *) mimeinfo->node->children->data;
3372 while (child != NULL) {
3375 if (child == encrypted) {
3376 /* skip this part of tree */
3377 NEXT_PART_NOT_CHILD(child);
3381 if (child->type == MIMETYPE_MULTIPART) {
3382 /* get the actual content */
3383 child = procmime_mimeinfo_next(child);
3387 if (child == firsttext) {
3388 child = procmime_mimeinfo_next(child);
3392 outfile = procmime_get_tmp_file_name(child);
3393 if ((err = procmime_get_part(outfile, child)) < 0)
3394 g_warning("Can't get the part of multipart message. (%s)", strerror(-err));
3396 gchar *content_type;
3398 content_type = procmime_get_content_type_str(child->type, child->subtype);
3400 /* if we meet a pgp signature, we don't attach it, but
3401 * we force signing. */
3402 if ((strcmp(content_type, "application/pgp-signature") &&
3403 strcmp(content_type, "application/pkcs7-signature") &&
3404 strcmp(content_type, "application/x-pkcs7-signature"))
3405 || compose->mode == COMPOSE_REDIRECT) {
3406 partname = procmime_mimeinfo_get_parameter(child, "filename");
3407 if (partname == NULL)
3408 partname = procmime_mimeinfo_get_parameter(child, "name");
3409 if (partname == NULL)
3411 compose_attach_append(compose, outfile,
3412 partname, content_type);
3414 compose_force_signing(compose, compose->account);
3416 g_free(content_type);
3419 NEXT_PART_NOT_CHILD(child);
3421 procmime_mimeinfo_free_all(mimeinfo);
3424 #undef NEXT_PART_NOT_CHILD
3429 WAIT_FOR_INDENT_CHAR,
3430 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3433 /* return indent length, we allow:
3434 indent characters followed by indent characters or spaces/tabs,
3435 alphabets and numbers immediately followed by indent characters,
3436 and the repeating sequences of the above
3437 If quote ends with multiple spaces, only the first one is included. */
3438 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3439 const GtkTextIter *start, gint *len)
3441 GtkTextIter iter = *start;
3445 IndentState state = WAIT_FOR_INDENT_CHAR;
3448 gint alnum_count = 0;
3449 gint space_count = 0;
3452 if (prefs_common.quote_chars == NULL) {
3456 while (!gtk_text_iter_ends_line(&iter)) {
3457 wc = gtk_text_iter_get_char(&iter);
3458 if (g_unichar_iswide(wc))
3460 clen = g_unichar_to_utf8(wc, ch);
3464 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
3465 is_space = g_unichar_isspace(wc);
3467 if (state == WAIT_FOR_INDENT_CHAR) {
3468 if (!is_indent && !g_unichar_isalnum(wc))
3471 quote_len += alnum_count + space_count + 1;
3472 alnum_count = space_count = 0;
3473 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
3476 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
3477 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
3481 else if (is_indent) {
3482 quote_len += alnum_count + space_count + 1;
3483 alnum_count = space_count = 0;
3486 state = WAIT_FOR_INDENT_CHAR;
3490 gtk_text_iter_forward_char(&iter);
3493 if (quote_len > 0 && space_count > 0)
3499 if (quote_len > 0) {
3501 gtk_text_iter_forward_chars(&iter, quote_len);
3502 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
3508 /* return TRUE if the line is itemized */
3509 static gboolean compose_is_itemized(GtkTextBuffer *buffer,
3510 const GtkTextIter *start)
3512 GtkTextIter iter = *start;
3517 if (gtk_text_iter_ends_line(&iter))
3521 wc = gtk_text_iter_get_char(&iter);
3522 if (!g_unichar_isspace(wc))
3524 gtk_text_iter_forward_char(&iter);
3525 if (gtk_text_iter_ends_line(&iter))
3529 clen = g_unichar_to_utf8(wc, ch);
3533 if (!strchr("*-+", ch[0]))
3536 gtk_text_iter_forward_char(&iter);
3537 if (gtk_text_iter_ends_line(&iter))
3539 wc = gtk_text_iter_get_char(&iter);
3540 if (g_unichar_isspace(wc))
3546 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
3547 const GtkTextIter *start,
3548 GtkTextIter *break_pos,
3552 GtkTextIter iter = *start, line_end = *start;
3553 PangoLogAttr *attrs;
3560 gboolean can_break = FALSE;
3561 gboolean do_break = FALSE;
3562 gboolean was_white = FALSE;
3563 gboolean prev_dont_break = FALSE;
3565 gtk_text_iter_forward_to_line_end(&line_end);
3566 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
3567 len = g_utf8_strlen(str, -1);
3568 /* g_print("breaking line: %d: %s (len = %d)\n",
3569 gtk_text_iter_get_line(&iter), str, len); */
3570 attrs = g_new(PangoLogAttr, len + 1);
3572 pango_default_break(str, -1, NULL, attrs, len + 1);
3576 /* skip quote and leading spaces */
3577 for (i = 0; *p != '\0' && i < len; i++) {
3580 wc = g_utf8_get_char(p);
3581 if (i >= quote_len && !g_unichar_isspace(wc))
3583 if (g_unichar_iswide(wc))
3585 else if (*p == '\t')
3589 p = g_utf8_next_char(p);
3592 for (; *p != '\0' && i < len; i++) {
3593 PangoLogAttr *attr = attrs + i;
3597 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
3600 was_white = attr->is_white;
3602 /* don't wrap URI */
3603 if ((uri_len = get_uri_len(p)) > 0) {
3605 if (pos > 0 && col > max_col) {
3615 wc = g_utf8_get_char(p);
3616 if (g_unichar_iswide(wc)) {
3618 if (prev_dont_break && can_break && attr->is_line_break)
3620 } else if (*p == '\t')
3624 if (pos > 0 && col > max_col) {
3629 if (*p == '-' || *p == '/')
3630 prev_dont_break = TRUE;
3632 prev_dont_break = FALSE;
3634 p = g_utf8_next_char(p);
3638 debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col);
3643 *break_pos = *start;
3644 gtk_text_iter_set_line_offset(break_pos, pos);
3649 static gboolean compose_join_next_line(Compose *compose,
3650 GtkTextBuffer *buffer,
3652 const gchar *quote_str)
3654 GtkTextIter iter_ = *iter, cur, prev, next, end;
3655 PangoLogAttr attrs[3];
3657 gchar *next_quote_str;
3660 gboolean keep_cursor = FALSE;
3662 if (!gtk_text_iter_forward_line(&iter_) ||
3663 gtk_text_iter_ends_line(&iter_))
3666 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
3668 if ((quote_str || next_quote_str) &&
3669 strcmp2(quote_str, next_quote_str) != 0) {
3670 g_free(next_quote_str);
3673 g_free(next_quote_str);
3676 if (quote_len > 0) {
3677 gtk_text_iter_forward_chars(&end, quote_len);
3678 if (gtk_text_iter_ends_line(&end))
3682 /* don't join itemized lines */
3683 if (compose_is_itemized(buffer, &end))
3686 /* don't join signature separator */
3687 if (compose_is_sig_separator(compose, buffer, &iter_))
3690 /* delete quote str */
3692 gtk_text_buffer_delete(buffer, &iter_, &end);
3694 /* don't join line breaks put by the user */
3696 gtk_text_iter_backward_char(&cur);
3697 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
3698 gtk_text_iter_forward_char(&cur);
3702 gtk_text_iter_forward_char(&cur);
3703 /* delete linebreak and extra spaces */
3704 while (gtk_text_iter_backward_char(&cur)) {
3705 wc1 = gtk_text_iter_get_char(&cur);
3706 if (!g_unichar_isspace(wc1))
3711 while (!gtk_text_iter_ends_line(&cur)) {
3712 wc1 = gtk_text_iter_get_char(&cur);
3713 if (!g_unichar_isspace(wc1))
3715 gtk_text_iter_forward_char(&cur);
3718 if (!gtk_text_iter_equal(&prev, &next)) {
3721 mark = gtk_text_buffer_get_insert(buffer);
3722 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
3723 if (gtk_text_iter_equal(&prev, &cur))
3725 gtk_text_buffer_delete(buffer, &prev, &next);
3729 /* insert space if required */
3730 gtk_text_iter_backward_char(&prev);
3731 wc1 = gtk_text_iter_get_char(&prev);
3732 wc2 = gtk_text_iter_get_char(&next);
3733 gtk_text_iter_forward_char(&next);
3734 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
3735 pango_default_break(str, -1, NULL, attrs, 3);
3736 if (!attrs[1].is_line_break ||
3737 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
3738 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
3740 gtk_text_iter_backward_char(&iter_);
3741 gtk_text_buffer_place_cursor(buffer, &iter_);
3750 #define ADD_TXT_POS(bp_, ep_, pti_) \
3751 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
3752 last = last->next; \
3753 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
3754 last->next = NULL; \
3756 g_warning("alloc error scanning URIs\n"); \
3759 static gboolean automatic_break = FALSE;
3760 static void compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
3762 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3763 GtkTextBuffer *buffer;
3764 GtkTextIter iter, break_pos, end_of_line;
3765 gchar *quote_str = NULL;
3767 gboolean wrap_quote = prefs_common.linewrap_quote;
3768 gboolean prev_autowrap = compose->autowrap;
3769 gint startq_offset = -1, noq_offset = -1;
3770 gint uri_start = -1, uri_stop = -1;
3771 gint nouri_start = -1, nouri_stop = -1;
3772 gint num_blocks = 0;
3773 gint quotelevel = -1;
3775 compose->autowrap = FALSE;
3777 buffer = gtk_text_view_get_buffer(text);
3778 undo_wrapping(compose->undostruct, TRUE);
3783 mark = gtk_text_buffer_get_insert(buffer);
3784 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3787 /* move to paragraph start */
3788 gtk_text_iter_set_line_offset(&iter, 0);
3789 if (gtk_text_iter_ends_line(&iter)) {
3790 while (gtk_text_iter_ends_line(&iter) &&
3791 gtk_text_iter_forward_line(&iter))
3794 while (gtk_text_iter_backward_line(&iter)) {
3795 if (gtk_text_iter_ends_line(&iter)) {
3796 gtk_text_iter_forward_line(&iter);
3802 /* go until paragraph end (empty line) */
3804 while (!gtk_text_iter_ends_line(&iter)) {
3805 gchar *scanpos = NULL;
3806 /* parse table - in order of priority */
3808 const gchar *needle; /* token */
3810 /* token search function */
3811 gchar *(*search) (const gchar *haystack,
3812 const gchar *needle);
3813 /* part parsing function */
3814 gboolean (*parse) (const gchar *start,
3815 const gchar *scanpos,
3819 /* part to URI function */
3820 gchar *(*build_uri) (const gchar *bp,
3824 static struct table parser[] = {
3825 {"http://", strcasestr, get_uri_part, make_uri_string},
3826 {"https://", strcasestr, get_uri_part, make_uri_string},
3827 {"ftp://", strcasestr, get_uri_part, make_uri_string},
3828 {"sftp://", strcasestr, get_uri_part, make_uri_string},
3829 {"www.", strcasestr, get_uri_part, make_http_string},
3830 {"mailto:", strcasestr, get_uri_part, make_uri_string},
3831 {"@", strcasestr, get_email_part, make_email_string}
3833 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
3834 gint last_index = PARSE_ELEMS;
3836 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
3839 if (!prev_autowrap && num_blocks == 0) {
3841 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3842 G_CALLBACK(text_inserted),
3845 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
3848 uri_start = uri_stop = -1;
3850 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
3853 debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str);
3854 if (startq_offset == -1)
3855 startq_offset = gtk_text_iter_get_offset(&iter);
3856 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
3857 if (quotelevel > 2) {
3858 /* recycle colors */
3859 if (prefs_common.recycle_quote_colors)
3868 if (startq_offset == -1)
3869 noq_offset = gtk_text_iter_get_offset(&iter);
3873 if (prev_autowrap == FALSE && !force && !wrap_quote) {
3876 if (compose_get_line_break_pos(buffer, &iter, &break_pos,
3877 prefs_common.linewrap_len,
3879 GtkTextIter prev, next, cur;
3881 if (prev_autowrap != FALSE || force) {
3882 automatic_break = TRUE;
3883 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
3884 automatic_break = FALSE;
3885 } else if (quote_str && wrap_quote) {
3886 automatic_break = TRUE;
3887 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
3888 automatic_break = FALSE;
3891 /* remove trailing spaces */
3893 gtk_text_iter_backward_char(&cur);
3895 while (!gtk_text_iter_starts_line(&cur)) {
3898 gtk_text_iter_backward_char(&cur);
3899 wc = gtk_text_iter_get_char(&cur);
3900 if (!g_unichar_isspace(wc))
3904 if (!gtk_text_iter_equal(&prev, &next)) {
3905 gtk_text_buffer_delete(buffer, &prev, &next);
3907 gtk_text_iter_forward_char(&break_pos);
3911 gtk_text_buffer_insert(buffer, &break_pos,
3915 compose_join_next_line(compose, buffer, &iter, quote_str);
3917 /* move iter to current line start */
3918 gtk_text_iter_set_line_offset(&iter, 0);
3925 /* move iter to next line start */
3930 if (!prev_autowrap && num_blocks > 0) {
3932 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3933 G_CALLBACK(text_inserted),
3937 while (!gtk_text_iter_ends_line(&end_of_line)) {
3938 gtk_text_iter_forward_char(&end_of_line);
3940 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
3942 nouri_start = gtk_text_iter_get_offset(&iter);
3943 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
3945 walk_pos = gtk_text_iter_get_offset(&iter);
3946 /* FIXME: this looks phony. scanning for anything in the parse table */
3947 for (n = 0; n < PARSE_ELEMS; n++) {
3950 tmp = parser[n].search(walk, parser[n].needle);
3952 if (scanpos == NULL || tmp < scanpos) {
3961 /* check if URI can be parsed */
3962 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
3963 (const gchar **)&ep, FALSE)
3964 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
3968 strlen(parser[last_index].needle);
3971 uri_start = walk_pos + (bp - o_walk);
3972 uri_stop = walk_pos + (ep - o_walk);
3976 gtk_text_iter_forward_line(&iter);
3979 if (startq_offset != -1) {
3980 GtkTextIter startquote, endquote;
3981 gtk_text_buffer_get_iter_at_offset(
3982 buffer, &startquote, startq_offset);
3985 switch (quotelevel) {
3986 case 0: gtk_text_buffer_apply_tag_by_name(
3987 buffer, "quote0", &startquote, &endquote);
3989 case 1: gtk_text_buffer_apply_tag_by_name(
3990 buffer, "quote1", &startquote, &endquote);
3992 case 2: gtk_text_buffer_apply_tag_by_name(
3993 buffer, "quote2", &startquote, &endquote);
3997 } else if (noq_offset != -1) {
3998 GtkTextIter startnoquote, endnoquote;
3999 gtk_text_buffer_get_iter_at_offset(
4000 buffer, &startnoquote, noq_offset);
4002 gtk_text_buffer_remove_tag_by_name(
4003 buffer, "quote0", &startnoquote, &endnoquote);
4004 gtk_text_buffer_remove_tag_by_name(
4005 buffer, "quote1", &startnoquote, &endnoquote);
4006 gtk_text_buffer_remove_tag_by_name(
4007 buffer, "quote2", &startnoquote, &endnoquote);
4012 GtkTextIter nouri_start_iter, nouri_end_iter;
4013 gtk_text_buffer_get_iter_at_offset(
4014 buffer, &nouri_start_iter, nouri_start);
4015 gtk_text_buffer_get_iter_at_offset(
4016 buffer, &nouri_end_iter, nouri_stop);
4017 gtk_text_buffer_remove_tag_by_name(
4018 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4020 if (uri_start > 0 && uri_stop > 0) {
4021 GtkTextIter uri_start_iter, uri_end_iter;
4022 gtk_text_buffer_get_iter_at_offset(
4023 buffer, &uri_start_iter, uri_start);
4024 gtk_text_buffer_get_iter_at_offset(
4025 buffer, &uri_end_iter, uri_stop);
4026 gtk_text_buffer_apply_tag_by_name(
4027 buffer, "link", &uri_start_iter, &uri_end_iter);
4033 undo_wrapping(compose->undostruct, FALSE);
4034 compose->autowrap = prev_autowrap;
4037 void compose_action_cb(void *data)
4039 Compose *compose = (Compose *)data;
4040 compose_wrap_all(compose);
4043 static void compose_wrap_all(Compose *compose)
4045 compose_wrap_all_full(compose, FALSE);
4048 static void compose_wrap_all_full(Compose *compose, gboolean force)
4050 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4051 GtkTextBuffer *buffer;
4054 buffer = gtk_text_view_get_buffer(text);
4056 gtk_text_buffer_get_start_iter(buffer, &iter);
4057 while (!gtk_text_iter_is_end(&iter))
4058 compose_beautify_paragraph(compose, &iter, force);
4062 static void compose_set_title(Compose *compose)
4068 edited = compose->modified ? _(" [Edited]") : "";
4070 subject = gtk_editable_get_chars(
4071 GTK_EDITABLE(compose->subject_entry), 0, -1);
4074 if (subject && strlen(subject))
4075 str = g_strdup_printf(_("%s - Compose message%s"),
4078 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4080 str = g_strdup(_("Compose message"));
4083 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4089 * compose_current_mail_account:
4091 * Find a current mail account (the currently selected account, or the
4092 * default account, if a news account is currently selected). If a
4093 * mail account cannot be found, display an error message.
4095 * Return value: Mail account, or NULL if not found.
4097 static PrefsAccount *
4098 compose_current_mail_account(void)
4102 if (cur_account && cur_account->protocol != A_NNTP)
4105 ac = account_get_default();
4106 if (!ac || ac->protocol == A_NNTP) {
4107 alertpanel_error(_("Account for sending mail is not specified.\n"
4108 "Please select a mail account before sending."));
4115 #define QUOTE_IF_REQUIRED(out, str) \
4117 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4121 len = strlen(str) + 3; \
4122 if ((__tmp = alloca(len)) == NULL) { \
4123 g_warning("can't allocate memory\n"); \
4124 g_string_free(header, TRUE); \
4127 g_snprintf(__tmp, len, "\"%s\"", str); \
4132 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4133 g_warning("can't allocate memory\n"); \
4134 g_string_free(header, TRUE); \
4137 strcpy(__tmp, str); \
4143 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4145 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4149 len = strlen(str) + 3; \
4150 if ((__tmp = alloca(len)) == NULL) { \
4151 g_warning("can't allocate memory\n"); \
4154 g_snprintf(__tmp, len, "\"%s\"", str); \
4159 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4160 g_warning("can't allocate memory\n"); \
4163 strcpy(__tmp, str); \
4169 static void compose_select_account(Compose *compose, PrefsAccount *account,
4172 GtkItemFactory *ifactory;
4175 g_return_if_fail(account != NULL);
4177 compose->account = account;
4179 if (account->name && *account->name) {
4181 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4182 from = g_strdup_printf("%s <%s>",
4183 buf, account->address);
4184 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4186 from = g_strdup_printf("<%s>",
4188 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4193 compose_set_title(compose);
4195 ifactory = gtk_item_factory_from_widget(compose->menubar);
4197 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4198 menu_set_active(ifactory, "/Options/Sign", TRUE);
4200 menu_set_active(ifactory, "/Options/Sign", FALSE);
4201 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4202 menu_set_active(ifactory, "/Options/Encrypt", TRUE);
4204 menu_set_active(ifactory, "/Options/Encrypt", FALSE);
4206 activate_privacy_system(compose, account, FALSE);
4208 if (!init && compose->mode != COMPOSE_REDIRECT) {
4209 undo_block(compose->undostruct);
4210 compose_insert_sig(compose, TRUE);
4211 undo_unblock(compose->undostruct);
4215 /* use account's dict info if set */
4216 if (compose->gtkaspell) {
4217 if (account->enable_default_dictionary)
4218 gtkaspell_change_dict(compose->gtkaspell,
4219 account->default_dictionary, FALSE);
4220 if (account->enable_default_alt_dictionary)
4221 gtkaspell_change_alt_dict(compose->gtkaspell,
4222 account->default_alt_dictionary);
4223 if (account->enable_default_dictionary
4224 || account->enable_default_alt_dictionary)
4225 compose_spell_menu_changed(compose);
4230 gboolean compose_check_for_valid_recipient(Compose *compose) {
4231 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
4232 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
4233 gboolean recipient_found = FALSE;
4237 /* free to and newsgroup list */
4238 slist_free_strings(compose->to_list);
4239 g_slist_free(compose->to_list);
4240 compose->to_list = NULL;
4242 slist_free_strings(compose->newsgroup_list);
4243 g_slist_free(compose->newsgroup_list);
4244 compose->newsgroup_list = NULL;
4246 /* search header entries for to and newsgroup entries */
4247 for (list = compose->header_list; list; list = list->next) {
4250 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_COMBO(((ComposeHeaderEntry *)list->data)->combo)->entry), 0, -1);
4251 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4254 if (entry[0] != '\0') {
4255 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
4256 if (!strcmp(header, prefs_common_translated_header_name(*strptr))) {
4257 compose->to_list = address_list_append(compose->to_list, entry);
4258 recipient_found = TRUE;
4261 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
4262 if (!strcmp(header, prefs_common_translated_header_name(*strptr))) {
4263 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
4264 recipient_found = TRUE;
4271 return recipient_found;
4274 static gboolean compose_check_for_set_recipients(Compose *compose)
4276 if (compose->account->set_autocc && compose->account->auto_cc) {
4277 gboolean found_other = FALSE;
4279 /* search header entries for to and newsgroup entries */
4280 for (list = compose->header_list; list; list = list->next) {
4283 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4284 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_COMBO(((ComposeHeaderEntry *)list->data)->combo)->entry), 0, -1);
4286 if (strcmp(entry, compose->account->auto_cc)
4287 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
4297 if (compose->batch) {
4298 gtk_widget_show_all(compose->window);
4300 aval = alertpanel(_("Send"),
4301 _("The only recipient is the default CC address. Send anyway?"),
4302 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4303 if (aval != G_ALERTALTERNATE)
4307 if (compose->account->set_autobcc && compose->account->auto_bcc) {
4308 gboolean found_other = FALSE;
4310 /* search header entries for to and newsgroup entries */
4311 for (list = compose->header_list; list; list = list->next) {
4314 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4315 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_COMBO(((ComposeHeaderEntry *)list->data)->combo)->entry), 0, -1);
4317 if (strcmp(entry, compose->account->auto_bcc)
4318 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
4328 if (compose->batch) {
4329 gtk_widget_show_all(compose->window);
4331 aval = alertpanel(_("Send"),
4332 _("The only recipient is the default BCC address. Send anyway?"),
4333 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4334 if (aval != G_ALERTALTERNATE)
4341 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
4345 if (compose_check_for_valid_recipient(compose) == FALSE) {
4346 if (compose->batch) {
4347 gtk_widget_show_all(compose->window);
4349 alertpanel_error(_("Recipient is not specified."));
4353 if (compose_check_for_set_recipients(compose) == FALSE) {
4357 if (!compose->batch) {
4358 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
4359 if (*str == '\0' && check_everything == TRUE &&
4360 compose->mode != COMPOSE_REDIRECT) {
4363 aval = alertpanel(_("Send"),
4364 _("Subject is empty. Send it anyway?"),
4365 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4366 if (aval != G_ALERTALTERNATE)
4371 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
4377 gint compose_send(Compose *compose)
4380 FolderItem *folder = NULL;
4382 gchar *msgpath = NULL;
4383 gboolean discard_window = FALSE;
4384 gchar *errstr = NULL;
4385 gchar *tmsgid = NULL;
4386 MainWindow *mainwin = mainwindow_get_mainwindow();
4387 gboolean queued_removed = FALSE;
4389 if (prefs_common.send_dialog_mode != SEND_DIALOG_ALWAYS
4390 || compose->batch == TRUE)
4391 discard_window = TRUE;
4393 compose_allow_user_actions (compose, FALSE);
4394 compose->sending = TRUE;
4396 if (compose_check_entries(compose, TRUE) == FALSE) {
4397 if (compose->batch) {
4398 gtk_widget_show_all(compose->window);
4404 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
4407 if (compose->batch) {
4408 gtk_widget_show_all(compose->window);
4411 alertpanel_error(_("Could not queue message for sending:\n\n"
4412 "Charset conversion failed."));
4413 } else if (val == -5) {
4414 alertpanel_error(_("Could not queue message for sending:\n\n"
4415 "Couldn't get recipient encryption key."));
4416 } else if (val == -6) {
4418 } else if (val == -3) {
4419 if (privacy_peek_error())
4420 alertpanel_error(_("Could not queue message for sending:\n\n"
4421 "Signature failed: %s"), privacy_get_error());
4422 } else if (val == -2 && errno != 0) {
4423 alertpanel_error(_("Could not queue message for sending:\n\n%s."), strerror(errno));
4425 alertpanel_error(_("Could not queue message for sending."));
4430 tmsgid = g_strdup(compose->msgid);
4431 if (discard_window) {
4432 compose->sending = FALSE;
4433 compose_close(compose);
4434 /* No more compose access in the normal codepath
4435 * after this point! */
4440 alertpanel_error(_("The message was queued but could not be "
4441 "sent.\nUse \"Send queued messages\" from "
4442 "the main window to retry."));
4443 if (!discard_window) {
4450 if (msgpath == NULL) {
4451 msgpath = folder_item_fetch_msg(folder, msgnum);
4452 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
4455 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
4459 if (!discard_window) {
4461 if (!queued_removed)
4462 folder_item_remove_msg(folder, msgnum);
4463 folder_item_scan(folder);
4465 /* make sure we delete that */
4466 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
4468 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
4469 folder_item_remove_msg(folder, tmp->msgnum);
4470 procmsg_msginfo_free(tmp);
4477 if (!queued_removed)
4478 folder_item_remove_msg(folder, msgnum);
4479 folder_item_scan(folder);
4481 /* make sure we delete that */
4482 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
4484 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
4485 folder_item_remove_msg(folder, tmp->msgnum);
4486 procmsg_msginfo_free(tmp);
4489 if (!discard_window) {
4490 compose->sending = FALSE;
4491 compose_allow_user_actions (compose, TRUE);
4492 compose_close(compose);
4496 gchar *tmp = g_strdup_printf(_("%s\nUse \"Send queued messages\" from "
4497 "the main window to retry."), errstr);
4499 alertpanel_error_log(tmp);
4502 alertpanel_error_log(_("The message was queued but could not be "
4503 "sent.\nUse \"Send queued messages\" from "
4504 "the main window to retry."));
4506 if (!discard_window) {
4515 toolbar_main_set_sensitive(mainwin);
4516 main_window_set_menu_sensitive(mainwin);
4522 compose_allow_user_actions (compose, TRUE);
4523 compose->sending = FALSE;
4524 compose->modified = TRUE;
4525 toolbar_main_set_sensitive(mainwin);
4526 main_window_set_menu_sensitive(mainwin);
4531 static gboolean compose_use_attach(Compose *compose)
4533 GtkTreeModel *model = gtk_tree_view_get_model
4534 (GTK_TREE_VIEW(compose->attach_clist));
4535 return gtk_tree_model_iter_n_children(model, NULL) > 0;
4538 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
4541 gchar buf[BUFFSIZE];
4543 gboolean first_to_address;
4544 gboolean first_cc_address;
4546 ComposeHeaderEntry *headerentry;
4547 const gchar *headerentryname;
4548 const gchar *cc_hdr;
4549 const gchar *to_hdr;
4551 debug_print("Writing redirect header\n");
4553 cc_hdr = prefs_common_translated_header_name("Cc:");
4554 to_hdr = prefs_common_translated_header_name("To:");
4556 first_to_address = TRUE;
4557 for (list = compose->header_list; list; list = list->next) {
4558 headerentry = ((ComposeHeaderEntry *)list->data);
4559 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(headerentry->combo)->entry));
4561 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
4562 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
4563 Xstrdup_a(str, entstr, return -1);
4565 if (str[0] != '\0') {
4566 compose_convert_header
4567 (compose, buf, sizeof(buf), str,
4568 strlen("Resent-To") + 2, TRUE);
4570 if (first_to_address) {
4571 fprintf(fp, "Resent-To: ");
4572 first_to_address = FALSE;
4576 fprintf(fp, "%s", buf);
4580 if (!first_to_address) {
4584 first_cc_address = TRUE;
4585 for (list = compose->header_list; list; list = list->next) {
4586 headerentry = ((ComposeHeaderEntry *)list->data);
4587 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(headerentry->combo)->entry));
4589 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
4590 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
4591 Xstrdup_a(str, strg, return -1);
4593 if (str[0] != '\0') {
4594 compose_convert_header
4595 (compose, buf, sizeof(buf), str,
4596 strlen("Resent-Cc") + 2, TRUE);
4598 if (first_cc_address) {
4599 fprintf(fp, "Resent-Cc: ");
4600 first_cc_address = FALSE;
4604 fprintf(fp, "%s", buf);
4608 if (!first_cc_address) {
4615 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
4617 gchar buf[BUFFSIZE];
4619 const gchar *entstr;
4620 /* struct utsname utsbuf; */
4622 g_return_val_if_fail(fp != NULL, -1);
4623 g_return_val_if_fail(compose->account != NULL, -1);
4624 g_return_val_if_fail(compose->account->address != NULL, -1);
4627 get_rfc822_date(buf, sizeof(buf));
4628 fprintf(fp, "Resent-Date: %s\n", buf);
4631 if (compose->account->name && *compose->account->name) {
4632 compose_convert_header
4633 (compose, buf, sizeof(buf), compose->account->name,
4634 strlen("From: "), TRUE);
4635 fprintf(fp, "Resent-From: %s <%s>\n",
4636 buf, compose->account->address);
4638 fprintf(fp, "Resent-From: %s\n", compose->account->address);
4641 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
4642 if (*entstr != '\0') {
4643 Xstrdup_a(str, entstr, return -1);
4646 compose_convert_header(compose, buf, sizeof(buf), str,
4647 strlen("Subject: "), FALSE);
4648 fprintf(fp, "Subject: %s\n", buf);
4652 /* Resent-Message-ID */
4653 if (compose->account->gen_msgid) {
4654 generate_msgid(buf, sizeof(buf));
4655 fprintf(fp, "Resent-Message-ID: <%s>\n", buf);
4656 compose->msgid = g_strdup(buf);
4659 compose_redirect_write_headers_from_headerlist(compose, fp);
4661 /* separator between header and body */
4667 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
4671 gchar buf[BUFFSIZE];
4673 gboolean skip = FALSE;
4674 gchar *not_included[]={
4675 "Return-Path:", "Delivered-To:", "Received:",
4676 "Subject:", "X-UIDL:", "AF:",
4677 "NF:", "PS:", "SRH:",
4678 "SFN:", "DSR:", "MID:",
4679 "CFG:", "PT:", "S:",
4680 "RQ:", "SSV:", "NSV:",
4681 "SSH:", "R:", "MAID:",
4682 "NAID:", "RMID:", "FMID:",
4683 "SCF:", "RRCPT:", "NG:",
4684 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
4685 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
4686 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
4687 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
4690 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
4691 FILE_OP_ERROR(compose->redirect_filename, "fopen");
4695 while (procheader_get_one_field_asis(buf, sizeof(buf), fp) != -1) {
4697 for (i = 0; not_included[i] != NULL; i++) {
4698 if (g_ascii_strncasecmp(buf, not_included[i],
4699 strlen(not_included[i])) == 0) {
4706 if (fputs(buf, fdest) == -1)
4709 if (!prefs_common.redirect_keep_from) {
4710 if (g_ascii_strncasecmp(buf, "From:",
4711 strlen("From:")) == 0) {
4712 fputs(" (by way of ", fdest);
4713 if (compose->account->name
4714 && *compose->account->name) {
4715 compose_convert_header
4716 (compose, buf, sizeof(buf),
4717 compose->account->name,
4720 fprintf(fdest, "%s <%s>",
4722 compose->account->address);
4724 fprintf(fdest, "%s",
4725 compose->account->address);
4730 if (fputs("\n", fdest) == -1)
4734 compose_redirect_write_headers(compose, fdest);
4736 while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
4737 if (fwrite(buf, sizeof(gchar), len, fdest) != len)
4750 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
4752 GtkTextBuffer *buffer;
4753 GtkTextIter start, end;
4756 const gchar *out_codeset;
4757 EncodingType encoding;
4758 MimeInfo *mimemsg, *mimetext;
4761 if (action == COMPOSE_WRITE_FOR_SEND)
4762 attach_parts = TRUE;
4764 /* create message MimeInfo */
4765 mimemsg = procmime_mimeinfo_new();
4766 mimemsg->type = MIMETYPE_MESSAGE;
4767 mimemsg->subtype = g_strdup("rfc822");
4768 mimemsg->content = MIMECONTENT_MEM;
4769 mimemsg->tmp = TRUE; /* must free content later */
4770 mimemsg->data.mem = compose_get_header(compose);
4772 /* Create text part MimeInfo */
4773 /* get all composed text */
4774 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
4775 gtk_text_buffer_get_start_iter(buffer, &start);
4776 gtk_text_buffer_get_end_iter(buffer, &end);
4777 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
4778 if (is_ascii_str(chars)) {
4781 out_codeset = CS_US_ASCII;
4782 encoding = ENC_7BIT;
4784 const gchar *src_codeset = CS_INTERNAL;
4786 out_codeset = conv_get_charset_str(compose->out_encoding);
4789 gchar *test_conv_global_out = NULL;
4790 gchar *test_conv_reply = NULL;
4792 /* automatic mode. be automatic. */
4793 codeconv_set_strict(TRUE);
4795 out_codeset = conv_get_outgoing_charset_str();
4797 debug_print("trying to convert to %s\n", out_codeset);
4798 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
4801 if (!test_conv_global_out && compose->orig_charset
4802 && strcmp(compose->orig_charset, CS_US_ASCII)) {
4803 out_codeset = compose->orig_charset;
4804 debug_print("failure; trying to convert to %s\n", out_codeset);
4805 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
4808 if (!test_conv_global_out && !test_conv_reply) {
4810 out_codeset = CS_INTERNAL;
4811 debug_print("failure; finally using %s\n", out_codeset);
4813 g_free(test_conv_global_out);
4814 g_free(test_conv_reply);
4815 codeconv_set_strict(FALSE);
4818 if (!g_ascii_strcasecmp(out_codeset, CS_US_ASCII))
4819 out_codeset = CS_ISO_8859_1;
4821 if (prefs_common.encoding_method == CTE_BASE64)
4822 encoding = ENC_BASE64;
4823 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
4824 encoding = ENC_QUOTED_PRINTABLE;
4825 else if (prefs_common.encoding_method == CTE_8BIT)
4826 encoding = ENC_8BIT;
4828 encoding = procmime_get_encoding_for_charset(out_codeset);
4830 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
4831 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
4833 if (action == COMPOSE_WRITE_FOR_SEND) {
4834 codeconv_set_strict(TRUE);
4835 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
4836 codeconv_set_strict(FALSE);
4842 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
4843 "to the specified %s charset.\n"
4844 "Send it as %s?"), out_codeset, src_codeset);
4845 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL, _("+_Send"), NULL, FALSE,
4846 NULL, ALERT_ERROR, G_ALERTDEFAULT);
4849 if (aval != G_ALERTALTERNATE) {
4854 out_codeset = src_codeset;
4860 out_codeset = src_codeset;
4866 if (encoding == ENC_8BIT || encoding == ENC_7BIT) {
4867 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
4868 strstr(buf, "\nFrom ") != NULL) {
4869 encoding = ENC_QUOTED_PRINTABLE;
4873 mimetext = procmime_mimeinfo_new();
4874 mimetext->content = MIMECONTENT_MEM;
4875 mimetext->tmp = TRUE; /* must free content later */
4876 /* dup'ed because procmime_encode_content can turn it into a tmpfile
4877 * and free the data, which we need later. */
4878 mimetext->data.mem = g_strdup(buf);
4879 mimetext->type = MIMETYPE_TEXT;
4880 mimetext->subtype = g_strdup("plain");
4881 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
4882 g_strdup(out_codeset));
4884 /* protect trailing spaces when signing message */
4885 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
4886 privacy_system_can_sign(compose->privacy_system)) {
4887 encoding = ENC_QUOTED_PRINTABLE;
4890 debug_print("main text: %d bytes encoded as %s in %d\n",
4891 strlen(buf), out_codeset, encoding);
4893 /* check for line length limit */
4894 if (action == COMPOSE_WRITE_FOR_SEND &&
4895 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
4896 check_line_length(buf, 1000, &line) < 0) {
4900 msg = g_strdup_printf
4901 (_("Line %d exceeds the line length limit (998 bytes).\n"
4902 "The contents of the message might be broken on the way to the delivery.\n"
4904 "Send it anyway?"), line + 1);
4905 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
4907 if (aval != G_ALERTALTERNATE) {
4913 if (encoding != ENC_UNKNOWN)
4914 procmime_encode_content(mimetext, encoding);
4916 /* append attachment parts */
4917 if (compose_use_attach(compose) && attach_parts) {
4918 MimeInfo *mimempart;
4919 gchar *boundary = NULL;
4920 mimempart = procmime_mimeinfo_new();
4921 mimempart->content = MIMECONTENT_EMPTY;
4922 mimempart->type = MIMETYPE_MULTIPART;
4923 mimempart->subtype = g_strdup("mixed");
4927 boundary = generate_mime_boundary(NULL);
4928 } while (strstr(buf, boundary) != NULL);
4930 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
4933 mimetext->disposition = DISPOSITIONTYPE_INLINE;
4935 g_node_append(mimempart->node, mimetext->node);
4936 g_node_append(mimemsg->node, mimempart->node);
4938 compose_add_attachments(compose, mimempart);
4940 g_node_append(mimemsg->node, mimetext->node);
4944 /* sign message if sending */
4945 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
4946 privacy_system_can_sign(compose->privacy_system))
4947 if (!privacy_sign(compose->privacy_system, mimemsg, compose->account))
4950 procmime_write_mimeinfo(mimemsg, fp);
4952 procmime_mimeinfo_free_all(mimemsg);
4957 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
4959 GtkTextBuffer *buffer;
4960 GtkTextIter start, end;
4965 if ((fp = g_fopen(file, "wb")) == NULL) {
4966 FILE_OP_ERROR(file, "fopen");
4970 /* chmod for security */
4971 if (change_file_mode_rw(fp, file) < 0) {
4972 FILE_OP_ERROR(file, "chmod");
4973 g_warning("can't change file mode\n");
4976 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
4977 gtk_text_buffer_get_start_iter(buffer, &start);
4978 gtk_text_buffer_get_end_iter(buffer, &end);
4979 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
4981 chars = conv_codeset_strdup
4982 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
4985 if (!chars) return -1;
4988 len = strlen(chars);
4989 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
4990 FILE_OP_ERROR(file, "fwrite");
4999 if (fclose(fp) == EOF) {
5000 FILE_OP_ERROR(file, "fclose");
5007 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5010 MsgInfo *msginfo = compose->targetinfo;
5012 g_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5013 if (!msginfo) return -1;
5015 if (!force && MSG_IS_LOCKED(msginfo->flags))
5018 item = msginfo->folder;
5019 g_return_val_if_fail(item != NULL, -1);
5021 if (procmsg_msg_exist(msginfo) &&
5022 (folder_has_parent_of_type(item, F_QUEUE) ||
5023 folder_has_parent_of_type(item, F_DRAFT)
5024 || msginfo == compose->autosaved_draft)) {
5025 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5026 g_warning("can't remove the old message\n");
5034 static void compose_remove_draft(Compose *compose)
5037 MsgInfo *msginfo = compose->targetinfo;
5038 drafts = account_get_special_folder(compose->account, F_DRAFT);
5040 if (procmsg_msg_exist(msginfo)) {
5041 folder_item_remove_msg(drafts, msginfo->msgnum);
5046 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5047 gboolean remove_reedit_target)
5049 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5052 static gboolean compose_warn_encryption(Compose *compose)
5054 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5055 AlertValue val = G_ALERTALTERNATE;
5057 if (warning == NULL)
5060 val = alertpanel_full(_("Encryption warning"), warning,
5061 GTK_STOCK_CANCEL, _("+C_ontinue"), NULL,
5062 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5063 if (val & G_ALERTDISABLE) {
5064 val &= ~G_ALERTDISABLE;
5065 if (val == G_ALERTALTERNATE)
5066 privacy_inhibit_encrypt_warning(compose->privacy_system,
5070 if (val == G_ALERTALTERNATE) {
5077 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
5078 gchar **msgpath, gboolean check_subject,
5079 gboolean remove_reedit_target)
5086 static gboolean lock = FALSE;
5087 PrefsAccount *mailac = NULL, *newsac = NULL;
5089 debug_print("queueing message...\n");
5090 g_return_val_if_fail(compose->account != NULL, -1);
5094 if (compose_check_entries(compose, check_subject) == FALSE) {
5096 if (compose->batch) {
5097 gtk_widget_show_all(compose->window);
5102 if (!compose->to_list && !compose->newsgroup_list) {
5103 g_warning("can't get recipient list.");
5108 if (compose->to_list) {
5109 if (compose->account->protocol != A_NNTP)
5110 mailac = compose->account;
5111 else if (cur_account && cur_account->protocol != A_NNTP)
5112 mailac = cur_account;
5113 else if (!(mailac = compose_current_mail_account())) {
5115 alertpanel_error(_("No account for sending mails available!"));
5120 if (compose->newsgroup_list) {
5121 if (compose->account->protocol == A_NNTP)
5122 newsac = compose->account;
5123 else if (!newsac->protocol != A_NNTP) {
5125 alertpanel_error(_("No account for posting news available!"));
5130 /* write queue header */
5131 tmp = g_strdup_printf("%s%cqueue.%p", get_tmp_dir(),
5132 G_DIR_SEPARATOR, compose);
5133 if ((fp = g_fopen(tmp, "wb")) == NULL) {
5134 FILE_OP_ERROR(tmp, "fopen");
5140 if (change_file_mode_rw(fp, tmp) < 0) {
5141 FILE_OP_ERROR(tmp, "chmod");
5142 g_warning("can't change file mode\n");
5145 /* queueing variables */
5146 fprintf(fp, "AF:\n");
5147 fprintf(fp, "NF:0\n");
5148 fprintf(fp, "PS:10\n");
5149 fprintf(fp, "SRH:1\n");
5150 fprintf(fp, "SFN:\n");
5151 fprintf(fp, "DSR:\n");
5153 fprintf(fp, "MID:<%s>\n", compose->msgid);
5155 fprintf(fp, "MID:\n");
5156 fprintf(fp, "CFG:\n");
5157 fprintf(fp, "PT:0\n");
5158 fprintf(fp, "S:%s\n", compose->account->address);
5159 fprintf(fp, "RQ:\n");
5161 fprintf(fp, "SSV:%s\n", mailac->smtp_server);
5163 fprintf(fp, "SSV:\n");
5165 fprintf(fp, "NSV:%s\n", newsac->nntp_server);
5167 fprintf(fp, "NSV:\n");
5168 fprintf(fp, "SSH:\n");
5169 /* write recepient list */
5170 if (compose->to_list) {
5171 fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data);
5172 for (cur = compose->to_list->next; cur != NULL;
5174 fprintf(fp, ",<%s>", (gchar *)cur->data);
5177 /* write newsgroup list */
5178 if (compose->newsgroup_list) {
5180 fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data);
5181 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
5182 fprintf(fp, ",%s", (gchar *)cur->data);
5185 /* Sylpheed account IDs */
5187 fprintf(fp, "MAID:%d\n", mailac->account_id);
5189 fprintf(fp, "NAID:%d\n", newsac->account_id);
5192 if (compose->privacy_system != NULL) {
5193 fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system);
5194 fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing);
5195 if (compose->use_encryption) {
5197 if (!compose_warn_encryption(compose)) {
5204 if (mailac && mailac->encrypt_to_self) {
5205 GSList *tmp_list = g_slist_copy(compose->to_list);
5206 tmp_list = g_slist_append(tmp_list, compose->account->address);
5207 encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
5208 g_slist_free(tmp_list);
5210 encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
5212 if (encdata != NULL) {
5213 if (strcmp(encdata, "_DONT_ENCRYPT_")) {
5214 fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption);
5215 fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
5217 } /* else we finally dont want to encrypt */
5219 fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption);
5220 /* and if encdata was null, it means there's been a problem in
5232 /* Save copy folder */
5233 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
5234 gchar *savefolderid;
5236 savefolderid = gtk_editable_get_chars(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
5237 fprintf(fp, "SCF:%s\n", savefolderid);
5238 g_free(savefolderid);
5240 /* Save copy folder */
5241 if (compose->return_receipt) {
5242 fprintf(fp, "RRCPT:1\n");
5244 /* Message-ID of message replying to */
5245 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
5248 folderid = folder_item_get_identifier(compose->replyinfo->folder);
5249 fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid);
5252 /* Message-ID of message forwarding to */
5253 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
5256 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
5257 fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid);
5261 /* end of headers */
5262 fprintf(fp, "X-Claws-End-Special-Headers: 1\n");
5264 if (compose->redirect_filename != NULL) {
5265 if (compose_redirect_write_to_file(compose, fp) < 0) {
5274 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
5279 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
5283 if (fclose(fp) == EOF) {
5284 FILE_OP_ERROR(tmp, "fclose");
5291 if (item && *item) {
5294 queue = account_get_special_folder(compose->account, F_QUEUE);
5297 g_warning("can't find queue folder\n");
5303 folder_item_scan(queue);
5304 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
5305 g_warning("can't queue the message\n");
5312 if (msgpath == NULL) {
5318 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
5319 compose_remove_reedit_target(compose, FALSE);
5322 if ((msgnum != NULL) && (item != NULL)) {
5330 static void compose_add_attachments(Compose *compose, MimeInfo *parent)
5333 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
5335 struct stat statbuf;
5336 gchar *type, *subtype;
5337 GtkTreeModel *model;
5340 model = gtk_tree_view_get_model(tree_view);
5342 if (!gtk_tree_model_get_iter_first(model, &iter))
5345 gtk_tree_model_get(model, &iter,
5349 mimepart = procmime_mimeinfo_new();
5350 mimepart->content = MIMECONTENT_FILE;
5351 mimepart->data.filename = g_strdup(ainfo->file);
5352 mimepart->tmp = FALSE; /* or we destroy our attachment */
5353 mimepart->offset = 0;
5355 stat(ainfo->file, &statbuf);
5356 mimepart->length = statbuf.st_size;
5358 type = g_strdup(ainfo->content_type);
5360 if (!strchr(type, '/')) {
5362 type = g_strdup("application/octet-stream");
5365 subtype = strchr(type, '/') + 1;
5366 *(subtype - 1) = '\0';
5367 mimepart->type = procmime_get_media_type(type);
5368 mimepart->subtype = g_strdup(subtype);
5371 if (mimepart->type == MIMETYPE_MESSAGE &&
5372 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
5373 mimepart->disposition = DISPOSITIONTYPE_INLINE;
5376 g_hash_table_insert(mimepart->typeparameters,
5377 g_strdup("name"), g_strdup(ainfo->name));
5378 g_hash_table_insert(mimepart->dispositionparameters,
5379 g_strdup("filename"), g_strdup(ainfo->name));
5380 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
5384 if (compose->use_signing) {
5385 if (ainfo->encoding == ENC_7BIT)
5386 ainfo->encoding = ENC_QUOTED_PRINTABLE;
5387 else if (ainfo->encoding == ENC_8BIT)
5388 ainfo->encoding = ENC_BASE64;
5391 procmime_encode_content(mimepart, ainfo->encoding);
5393 g_node_append(parent->node, mimepart->node);
5394 } while (gtk_tree_model_iter_next(model, &iter));
5397 #define IS_IN_CUSTOM_HEADER(header) \
5398 (compose->account->add_customhdr && \
5399 custom_header_find(compose->account->customhdr_list, header) != NULL)
5401 static void compose_add_headerfield_from_headerlist(Compose *compose,
5403 const gchar *fieldname,
5404 const gchar *seperator)
5406 gchar *str, *fieldname_w_colon;
5407 gboolean add_field = FALSE;
5409 ComposeHeaderEntry *headerentry;
5410 const gchar *headerentryname;
5411 const gchar *trans_fieldname;
5414 if (IS_IN_CUSTOM_HEADER(fieldname))
5417 debug_print("Adding %s-fields\n", fieldname);
5419 fieldstr = g_string_sized_new(64);
5421 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
5422 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
5424 for (list = compose->header_list; list; list = list->next) {
5425 headerentry = ((ComposeHeaderEntry *)list->data);
5426 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(headerentry->combo)->entry));
5428 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
5429 str = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
5431 if (str[0] != '\0') {
5433 g_string_append(fieldstr, seperator);
5434 g_string_append(fieldstr, str);
5443 buf = g_new0(gchar, fieldstr->len * 4 + 256);
5444 compose_convert_header
5445 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
5446 strlen(fieldname) + 2, TRUE);
5447 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
5451 g_free(fieldname_w_colon);
5452 g_string_free(fieldstr, TRUE);
5457 static gchar *compose_get_header(Compose *compose)
5459 gchar buf[BUFFSIZE];
5460 const gchar *entry_str;
5464 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
5466 gchar *from_name = NULL, *from_address = NULL;
5469 g_return_val_if_fail(compose->account != NULL, NULL);
5470 g_return_val_if_fail(compose->account->address != NULL, NULL);
5472 header = g_string_sized_new(64);
5475 get_rfc822_date(buf, sizeof(buf));
5476 g_string_append_printf(header, "Date: %s\n", buf);
5480 if (compose->account->name && *compose->account->name) {
5482 QUOTE_IF_REQUIRED(buf, compose->account->name);
5483 tmp = g_strdup_printf("%s <%s>",
5484 buf, compose->account->address);
5486 tmp = g_strdup_printf("%s",
5487 compose->account->address);
5489 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
5490 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
5492 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
5493 from_address = g_strdup(compose->account->address);
5495 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5496 /* extract name and address */
5497 if (strstr(spec, " <") && strstr(spec, ">")) {
5498 from_address = g_strdup(strrchr(spec, '<')+1);
5499 *(strrchr(from_address, '>')) = '\0';
5500 from_name = g_strdup(spec);
5501 *(strrchr(from_name, '<')) = '\0';
5504 from_address = g_strdup(spec);
5511 if (from_name && *from_name) {
5512 compose_convert_header
5513 (compose, buf, sizeof(buf), from_name,
5514 strlen("From: "), TRUE);
5515 QUOTE_IF_REQUIRED(name, buf);
5517 g_string_append_printf(header, "From: %s <%s>\n",
5518 name, from_address);
5520 g_string_append_printf(header, "From: %s\n", from_address);
5523 g_free(from_address);
5526 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
5529 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
5532 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
5535 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
5538 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
5540 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
5543 compose_convert_header(compose, buf, sizeof(buf), str,
5544 strlen("Subject: "), FALSE);
5545 g_string_append_printf(header, "Subject: %s\n", buf);
5551 if (compose->account->gen_msgid) {
5552 generate_msgid(buf, sizeof(buf));
5553 g_string_append_printf(header, "Message-ID: <%s>\n", buf);
5554 compose->msgid = g_strdup(buf);
5557 if (compose->remove_references == FALSE) {
5559 if (compose->inreplyto && compose->to_list)
5560 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
5563 if (compose->references)
5564 g_string_append_printf(header, "References: %s\n", compose->references);
5568 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
5571 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
5574 if (compose->account->organization &&
5575 strlen(compose->account->organization) &&
5576 !IS_IN_CUSTOM_HEADER("Organization")) {
5577 compose_convert_header(compose, buf, sizeof(buf),
5578 compose->account->organization,
5579 strlen("Organization: "), FALSE);
5580 g_string_append_printf(header, "Organization: %s\n", buf);
5583 /* Program version and system info */
5584 if (g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
5585 !compose->newsgroup_list) {
5586 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
5588 gtk_major_version, gtk_minor_version, gtk_micro_version,
5591 if (g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
5592 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
5594 gtk_major_version, gtk_minor_version, gtk_micro_version,
5598 /* custom headers */
5599 if (compose->account->add_customhdr) {
5602 for (cur = compose->account->customhdr_list; cur != NULL;
5604 CustomHeader *chdr = (CustomHeader *)cur->data;
5606 if (custom_header_is_allowed(chdr->name)) {
5607 compose_convert_header
5608 (compose, buf, sizeof(buf),
5609 chdr->value ? chdr->value : "",
5610 strlen(chdr->name) + 2, FALSE);
5611 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
5617 switch (compose->priority) {
5618 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
5619 "X-Priority: 1 (Highest)\n");
5621 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
5622 "X-Priority: 2 (High)\n");
5624 case PRIORITY_NORMAL: break;
5625 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
5626 "X-Priority: 4 (Low)\n");
5628 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
5629 "X-Priority: 5 (Lowest)\n");
5631 default: debug_print("compose: priority unknown : %d\n",
5635 /* Request Return Receipt */
5636 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To")) {
5637 if (compose->return_receipt) {
5638 if (compose->account->name
5639 && *compose->account->name) {
5640 compose_convert_header(compose, buf, sizeof(buf),
5641 compose->account->name,
5642 strlen("Disposition-Notification-To: "),
5644 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, compose->account->address);
5646 g_string_append_printf(header, "Disposition-Notification-To: %s\n", compose->account->address);
5650 /* get special headers */
5651 for (list = compose->header_list; list; list = list->next) {
5652 ComposeHeaderEntry *headerentry;
5655 gchar *headername_wcolon;
5656 const gchar *headername_trans;
5659 gboolean standard_header = FALSE;
5661 headerentry = ((ComposeHeaderEntry *)list->data);
5663 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(headerentry->combo)->entry)));
5664 if (strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
5669 if (!strstr(tmp, ":")) {
5670 headername_wcolon = g_strconcat(tmp, ":", NULL);
5671 headername = g_strdup(tmp);
5673 headername_wcolon = g_strdup(tmp);
5674 headername = g_strdup(strtok(tmp, ":"));
5678 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5679 Xstrdup_a(headervalue, entry_str, return NULL);
5680 subst_char(headervalue, '\r', ' ');
5681 subst_char(headervalue, '\n', ' ');
5682 string = std_headers;
5683 while (*string != NULL) {
5684 headername_trans = prefs_common_translated_header_name(*string);
5685 if (!strcmp(headername_trans, headername_wcolon))
5686 standard_header = TRUE;
5689 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
5690 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
5693 g_free(headername_wcolon);
5697 g_string_free(header, FALSE);
5702 #undef IS_IN_CUSTOM_HEADER
5704 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
5705 gint header_len, gboolean addr_field)
5707 gchar *tmpstr = NULL;
5708 const gchar *out_codeset = NULL;
5710 g_return_if_fail(src != NULL);
5711 g_return_if_fail(dest != NULL);
5713 if (len < 1) return;
5715 tmpstr = g_strdup(src);
5717 subst_char(tmpstr, '\n', ' ');
5718 subst_char(tmpstr, '\r', ' ');
5721 if (!g_utf8_validate(tmpstr, -1, NULL)) {
5722 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
5723 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
5728 codeconv_set_strict(TRUE);
5729 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
5730 conv_get_charset_str(compose->out_encoding));
5731 codeconv_set_strict(FALSE);
5733 if (!dest || *dest == '\0') {
5734 gchar *test_conv_global_out = NULL;
5735 gchar *test_conv_reply = NULL;
5737 /* automatic mode. be automatic. */
5738 codeconv_set_strict(TRUE);
5740 out_codeset = conv_get_outgoing_charset_str();
5742 debug_print("trying to convert to %s\n", out_codeset);
5743 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
5746 if (!test_conv_global_out && compose->orig_charset
5747 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5748 out_codeset = compose->orig_charset;
5749 debug_print("failure; trying to convert to %s\n", out_codeset);
5750 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
5753 if (!test_conv_global_out && !test_conv_reply) {
5755 out_codeset = CS_INTERNAL;
5756 debug_print("finally using %s\n", out_codeset);
5758 g_free(test_conv_global_out);
5759 g_free(test_conv_reply);
5760 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
5762 codeconv_set_strict(FALSE);
5767 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
5771 g_return_if_fail(user_data != NULL);
5773 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
5774 g_strstrip(address);
5775 if (*address != '\0') {
5776 gchar *name = procheader_get_fromname(address);
5777 extract_address(address);
5778 addressbook_add_contact(name, address, NULL);
5783 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
5785 GtkWidget *menuitem;
5788 g_return_if_fail(menu != NULL);
5789 g_return_if_fail(GTK_IS_MENU_SHELL(menu));
5791 menuitem = gtk_separator_menu_item_new();
5792 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
5793 gtk_widget_show(menuitem);
5795 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
5796 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
5798 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
5799 g_strstrip(address);
5800 if (*address == '\0') {
5801 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
5804 g_signal_connect(G_OBJECT(menuitem), "activate",
5805 G_CALLBACK(compose_add_to_addressbook_cb), entry);
5806 gtk_widget_show(menuitem);
5809 static void compose_create_header_entry(Compose *compose)
5811 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
5815 GList *combo_list = NULL;
5817 const gchar *header = NULL;
5818 ComposeHeaderEntry *headerentry;
5819 gboolean standard_header = FALSE;
5821 headerentry = g_new0(ComposeHeaderEntry, 1);
5824 combo = gtk_combo_new();
5826 while(*string != NULL) {
5827 combo_list = g_list_append(combo_list, (gchar*)prefs_common_translated_header_name(*string));
5830 gtk_combo_set_popdown_strings(GTK_COMBO(combo), combo_list);
5831 g_list_free(combo_list);
5832 gtk_editable_set_editable(GTK_EDITABLE(GTK_COMBO(combo)->entry), TRUE);
5833 g_signal_connect(G_OBJECT(GTK_COMBO(combo)->entry), "grab_focus",
5834 G_CALLBACK(compose_grab_focus_cb), compose);
5835 gtk_widget_show(combo);
5836 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
5837 compose->header_nextrow, compose->header_nextrow+1,
5838 GTK_SHRINK, GTK_FILL, 0, 0);
5839 if (compose->header_last) {
5840 const gchar *last_header_entry = gtk_entry_get_text(
5841 GTK_ENTRY(GTK_COMBO(compose->header_last->combo)->entry));
5843 while (*string != NULL) {
5844 if (!strcmp(*string, last_header_entry))
5845 standard_header = TRUE;
5848 if (standard_header)
5849 header = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(compose->header_last->combo)->entry));
5851 if (!compose->header_last || !standard_header) {
5852 switch(compose->account->protocol) {
5854 header = prefs_common_translated_header_name("Newsgroups:");
5857 header = prefs_common_translated_header_name("To:");
5862 gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(combo)->entry), header);
5864 g_signal_connect_after(G_OBJECT(GTK_COMBO(combo)->entry), "grab_focus",
5865 G_CALLBACK(compose_grab_focus_cb), compose);
5868 entry = gtk_entry_new();
5869 gtk_widget_show(entry);
5870 gtk_tooltips_set_tip(compose->tooltips, entry,
5871 _("Use <tab> to autocomplete from addressbook"), NULL);
5872 gtk_table_attach(GTK_TABLE(compose->header_table), entry, 1, 2,
5873 compose->header_nextrow, compose->header_nextrow+1,
5874 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
5876 g_signal_connect(G_OBJECT(entry), "key-press-event",
5877 G_CALLBACK(compose_headerentry_key_press_event_cb),
5879 g_signal_connect(G_OBJECT(entry), "changed",
5880 G_CALLBACK(compose_headerentry_changed_cb),
5882 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
5883 G_CALLBACK(compose_grab_focus_cb), compose);
5886 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
5887 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
5888 GDK_ACTION_COPY | GDK_ACTION_MOVE);
5889 g_signal_connect(G_OBJECT(entry), "drag_data_received",
5890 G_CALLBACK(compose_header_drag_received_cb),
5892 g_signal_connect(G_OBJECT(entry), "drag-drop",
5893 G_CALLBACK(compose_drag_drop),
5895 g_signal_connect(G_OBJECT(entry), "populate-popup",
5896 G_CALLBACK(compose_entry_popup_extend),
5899 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
5901 headerentry->compose = compose;
5902 headerentry->combo = combo;
5903 headerentry->entry = entry;
5904 headerentry->headernum = compose->header_nextrow;
5906 compose->header_nextrow++;
5907 compose->header_last = headerentry;
5908 compose->header_list =
5909 g_slist_append(compose->header_list,
5913 static void compose_add_header_entry(Compose *compose, const gchar *header, gchar *text)
5915 ComposeHeaderEntry *last_header;
5917 last_header = compose->header_last;
5919 gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(last_header->combo)->entry), header);
5920 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
5923 static void compose_remove_header_entries(Compose *compose)
5926 for (list = compose->header_list; list; list = list->next) {
5927 ComposeHeaderEntry *headerentry =
5928 (ComposeHeaderEntry *)list->data;
5929 gtk_widget_destroy(headerentry->combo);
5930 gtk_widget_destroy(headerentry->entry);
5931 g_free(headerentry);
5933 compose->header_last = NULL;
5934 g_slist_free(compose->header_list);
5935 compose->header_list = NULL;
5936 compose->header_nextrow = 1;
5937 compose_create_header_entry(compose);
5940 static GtkWidget *compose_create_header(Compose *compose)
5942 GtkWidget *from_optmenu_hbox;
5943 GtkWidget *header_scrolledwin;
5944 GtkWidget *header_table;
5948 /* header labels and entries */
5949 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
5950 gtk_widget_show(header_scrolledwin);
5951 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
5953 header_table = gtk_table_new(2, 2, FALSE);
5954 gtk_widget_show(header_table);
5955 gtk_container_set_border_width(GTK_CONTAINER(header_table), BORDER_WIDTH);
5956 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
5957 gtk_viewport_set_shadow_type(GTK_VIEWPORT(GTK_BIN(header_scrolledwin)->child), GTK_SHADOW_ETCHED_IN);
5960 /* option menu for selecting accounts */
5961 from_optmenu_hbox = compose_account_option_menu_create(compose);
5962 gtk_table_attach(GTK_TABLE(header_table), from_optmenu_hbox,
5963 0, 2, count, count + 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
5966 compose->header_table = header_table;
5967 compose->header_list = NULL;
5968 compose->header_nextrow = count;
5970 compose_create_header_entry(compose);
5972 compose->table = NULL;
5974 return header_scrolledwin ;
5977 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
5979 Compose *compose = (Compose *)data;
5980 GdkEventButton event;
5983 event.time = gtk_get_current_event_time();
5985 return attach_button_pressed(compose->attach_clist, &event, compose);
5988 static GtkWidget *compose_create_attach(Compose *compose)
5990 GtkWidget *attach_scrwin;
5991 GtkWidget *attach_clist;
5993 GtkListStore *store;
5994 GtkCellRenderer *renderer;
5995 GtkTreeViewColumn *column;
5996 GtkTreeSelection *selection;
5998 /* attachment list */
5999 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
6000 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
6001 GTK_POLICY_AUTOMATIC,
6002 GTK_POLICY_AUTOMATIC);
6003 gtk_widget_set_size_request(attach_scrwin, -1, 80);
6005 store = gtk_list_store_new(N_ATTACH_COLS,
6010 G_TYPE_AUTO_POINTER,
6012 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
6013 (GTK_TREE_MODEL(store)));
6014 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
6015 g_object_unref(store);
6017 renderer = gtk_cell_renderer_text_new();
6018 column = gtk_tree_view_column_new_with_attributes
6019 (_("Mime type"), renderer, "text",
6020 COL_MIMETYPE, NULL);
6021 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6023 renderer = gtk_cell_renderer_text_new();
6024 column = gtk_tree_view_column_new_with_attributes
6025 (_("Size"), renderer, "text",
6027 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6029 renderer = gtk_cell_renderer_text_new();
6030 column = gtk_tree_view_column_new_with_attributes
6031 (_("Name"), renderer, "text",
6033 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6035 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
6036 prefs_common.use_stripes_everywhere);
6037 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
6038 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
6040 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
6041 G_CALLBACK(attach_selected), compose);
6042 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
6043 G_CALLBACK(attach_button_pressed), compose);
6045 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
6046 G_CALLBACK(popup_attach_button_pressed), compose);
6048 gtk_widget_tap_and_hold_setup(GTK_WIDGET(attach_clist), NULL, NULL,
6049 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
6050 g_signal_connect(G_OBJECT(attach_clist), "tap-and-hold",
6051 G_CALLBACK(popup_attach_button_pressed), compose);
6053 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
6054 G_CALLBACK(attach_key_pressed), compose);
6057 gtk_drag_dest_set(attach_clist,
6058 GTK_DEST_DEFAULT_ALL, compose_mime_types,
6059 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6060 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6061 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
6062 G_CALLBACK(compose_attach_drag_received_cb),
6064 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
6065 G_CALLBACK(compose_drag_drop),
6068 compose->attach_scrwin = attach_scrwin;
6069 compose->attach_clist = attach_clist;
6071 return attach_scrwin;
6074 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
6075 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
6077 static GtkWidget *compose_create_others(Compose *compose)
6080 GtkWidget *savemsg_checkbtn;
6081 GtkWidget *savemsg_entry;
6082 GtkWidget *savemsg_select;
6085 gchar *folderidentifier;
6087 /* Table for settings */
6088 table = gtk_table_new(3, 1, FALSE);
6089 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
6090 gtk_widget_show(table);
6091 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
6094 /* Save Message to folder */
6095 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
6096 gtk_widget_show(savemsg_checkbtn);
6097 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6098 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6099 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
6101 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
6102 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
6104 savemsg_entry = gtk_entry_new();
6105 gtk_widget_show(savemsg_entry);
6106 gtk_table_attach_defaults(GTK_TABLE(table), savemsg_entry, 1, 2, rowcount, rowcount + 1);
6107 gtk_editable_set_editable(GTK_EDITABLE(savemsg_entry), prefs_common.savemsg);
6108 g_signal_connect_after(G_OBJECT(savemsg_entry), "grab_focus",
6109 G_CALLBACK(compose_grab_focus_cb), compose);
6110 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6111 folderidentifier = folder_item_get_identifier(account_get_special_folder
6112 (compose->account, F_OUTBOX));
6113 gtk_entry_set_text(GTK_ENTRY(savemsg_entry), folderidentifier);
6114 g_free(folderidentifier);
6117 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
6118 gtk_widget_show(savemsg_select);
6119 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6120 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
6121 G_CALLBACK(compose_savemsg_select_cb),
6126 compose->savemsg_checkbtn = savemsg_checkbtn;
6127 compose->savemsg_entry = savemsg_entry;
6132 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
6134 gtk_editable_set_editable(GTK_EDITABLE(compose->savemsg_entry),
6135 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
6138 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
6143 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL);
6146 path = folder_item_get_identifier(dest);
6148 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), path);
6152 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
6153 GdkAtom clip, GtkTextIter *insert_place);
6155 #define BLOCK_WRAP() { \
6156 prev_autowrap = compose->autowrap; \
6157 buffer = gtk_text_view_get_buffer( \
6158 GTK_TEXT_VIEW(compose->text)); \
6159 compose->autowrap = FALSE; \
6161 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
6162 G_CALLBACK(compose_changed_cb), \
6164 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
6165 G_CALLBACK(text_inserted), \
6168 #define UNBLOCK_WRAP() { \
6169 compose->autowrap = prev_autowrap; \
6170 if (compose->autowrap) \
6171 compose_wrap_all(compose); \
6173 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
6174 G_CALLBACK(compose_changed_cb), \
6176 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
6177 G_CALLBACK(text_inserted), \
6182 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
6186 GtkTextBuffer *buffer;
6188 if (event->button == 3) {
6190 GtkTextIter sel_start, sel_end;
6191 gboolean stuff_selected;
6193 /* move the cursor to allow GtkAspell to check the word
6194 * under the mouse */
6195 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6196 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6198 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6201 stuff_selected = gtk_text_buffer_get_selection_bounds(
6202 GTK_TEXT_VIEW(text)->buffer,
6203 &sel_start, &sel_end);
6205 gtk_text_buffer_place_cursor (GTK_TEXT_VIEW(text)->buffer, &iter);
6206 /* reselect stuff */
6208 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
6209 gtk_text_buffer_select_range(GTK_TEXT_VIEW(text)->buffer,
6210 &sel_start, &sel_end);
6212 return FALSE; /* pass the event so that the right-click goes through */
6215 if (event->button == 2) {
6220 /* get the middle-click position to paste at the correct place */
6221 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6222 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6224 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6227 entry_paste_clipboard(compose, text,
6228 prefs_common.linewrap_pastes,
6229 GDK_SELECTION_PRIMARY, &iter);
6237 static void compose_spell_menu_changed(void *data)
6239 Compose *compose = (Compose *)data;
6241 GtkWidget *menuitem;
6242 GtkWidget *parent_item;
6243 GtkMenu *menu = GTK_MENU(gtk_menu_new());
6244 GtkItemFactory *ifactory = gtk_item_factory_from_widget(compose->menubar);
6247 if (compose->gtkaspell == NULL)
6250 parent_item = gtk_item_factory_get_item(ifactory,
6251 "/Spelling/Options");
6253 /* setting the submenu removes /Spelling/Options from the factory
6254 * so we need to save it */
6256 if (parent_item == NULL) {
6257 parent_item = compose->aspell_options_menu;
6258 gtk_menu_item_remove_submenu(GTK_MENU_ITEM(parent_item));
6260 compose->aspell_options_menu = parent_item;
6262 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
6264 spell_menu = g_slist_reverse(spell_menu);
6265 for (items = spell_menu;
6266 items; items = items->next) {
6267 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
6268 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
6269 gtk_widget_show(GTK_WIDGET(menuitem));
6271 g_slist_free(spell_menu);
6273 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
6278 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
6280 Compose *compose = (Compose *)data;
6281 GdkEventButton event;
6284 event.time = gtk_get_current_event_time();
6286 return text_clicked(compose->text, &event, compose);
6289 static gboolean compose_force_window_origin = TRUE;
6290 static Compose *compose_create(PrefsAccount *account,
6299 GtkWidget *handlebox;
6301 GtkWidget *notebook;
6306 GtkWidget *subject_hbox;
6307 GtkWidget *subject_frame;
6308 GtkWidget *subject_entry;
6312 GtkWidget *edit_vbox;
6313 GtkWidget *ruler_hbox;
6315 GtkWidget *scrolledwin;
6317 GtkTextBuffer *buffer;
6318 GtkClipboard *clipboard;
6320 UndoMain *undostruct;
6322 gchar *titles[N_ATTACH_COLS];
6323 guint n_menu_entries;
6324 GtkWidget *popupmenu;
6325 GtkItemFactory *popupfactory;
6326 GtkItemFactory *ifactory;
6327 GtkWidget *tmpl_menu;
6329 GtkWidget *menuitem;
6332 GtkAspell * gtkaspell = NULL;
6335 static GdkGeometry geometry;
6337 g_return_val_if_fail(account != NULL, NULL);
6339 debug_print("Creating compose window...\n");
6340 compose = g_new0(Compose, 1);
6342 titles[COL_MIMETYPE] = _("MIME type");
6343 titles[COL_SIZE] = _("Size");
6344 titles[COL_NAME] = _("Name");
6346 compose->batch = batch;
6347 compose->account = account;
6348 compose->folder = folder;
6350 compose->mutex = g_mutex_new();
6351 compose->set_cursor_pos = -1;
6353 compose->tooltips = gtk_tooltips_new();
6355 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
6357 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
6358 gtk_widget_set_size_request(window, -1, prefs_common.compose_height);
6360 if (!geometry.max_width) {
6361 geometry.max_width = gdk_screen_width();
6362 geometry.max_height = gdk_screen_height();
6365 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
6366 &geometry, GDK_HINT_MAX_SIZE);
6367 if (!geometry.min_width) {
6368 geometry.min_width = 600;
6369 geometry.min_height = 480;
6371 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
6372 &geometry, GDK_HINT_MIN_SIZE);
6375 if (compose_force_window_origin)
6376 gtk_widget_set_uposition(window, prefs_common.compose_x,
6377 prefs_common.compose_y);
6379 g_signal_connect(G_OBJECT(window), "delete_event",
6380 G_CALLBACK(compose_delete_cb), compose);
6381 MANAGE_WINDOW_SIGNALS_CONNECT(window);
6382 gtk_widget_realize(window);
6384 gtkut_widget_set_composer_icon(window);
6386 vbox = gtk_vbox_new(FALSE, 0);
6387 gtk_container_add(GTK_CONTAINER(window), vbox);
6389 n_menu_entries = sizeof(compose_entries) / sizeof(compose_entries[0]);
6390 menubar = menubar_create(window, compose_entries,
6391 n_menu_entries, "<Compose>", compose);
6392 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
6394 if (prefs_common.toolbar_detachable) {
6395 handlebox = gtk_handle_box_new();
6397 handlebox = gtk_hbox_new(FALSE, 0);
6399 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
6401 gtk_widget_realize(handlebox);
6402 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
6405 vbox2 = gtk_vbox_new(FALSE, 2);
6406 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
6407 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
6410 notebook = gtk_notebook_new();
6411 gtk_widget_set_size_request(notebook, -1, 130);
6412 gtk_widget_show(notebook);
6414 /* header labels and entries */
6415 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6416 compose_create_header(compose),
6417 gtk_label_new_with_mnemonic(_("Hea_der")));
6418 /* attachment list */
6419 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6420 compose_create_attach(compose),
6421 gtk_label_new_with_mnemonic(_("_Attachments")));
6423 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6424 compose_create_others(compose),
6425 gtk_label_new_with_mnemonic(_("Othe_rs")));
6428 subject_hbox = gtk_hbox_new(FALSE, 0);
6429 gtk_widget_show(subject_hbox);
6431 subject_frame = gtk_frame_new(NULL);
6432 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
6433 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
6434 gtk_widget_show(subject_frame);
6436 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
6437 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
6438 gtk_widget_show(subject);
6440 label = gtk_label_new(_("Subject:"));
6441 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
6442 gtk_widget_show(label);
6444 subject_entry = gtk_entry_new();
6445 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
6446 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
6447 G_CALLBACK(compose_grab_focus_cb), compose);
6448 gtk_widget_show(subject_entry);
6449 compose->subject_entry = subject_entry;
6450 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
6452 edit_vbox = gtk_vbox_new(FALSE, 0);
6454 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
6457 ruler_hbox = gtk_hbox_new(FALSE, 0);
6458 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
6460 ruler = gtk_shruler_new();
6461 gtk_ruler_set_range(GTK_RULER(ruler), 0.0, 100.0, 1.0, 100.0);
6462 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
6466 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
6467 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
6468 GTK_POLICY_AUTOMATIC,
6469 GTK_POLICY_AUTOMATIC);
6470 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
6472 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
6473 gtk_widget_set_size_request(scrolledwin, prefs_common.compose_width, -1);
6475 text = gtk_text_view_new();
6476 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
6477 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
6478 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
6479 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
6480 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
6482 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
6484 g_signal_connect_after(G_OBJECT(text), "size_allocate",
6485 G_CALLBACK(compose_edit_size_alloc),
6487 g_signal_connect(G_OBJECT(buffer), "changed",
6488 G_CALLBACK(compose_changed_cb), compose);
6489 g_signal_connect(G_OBJECT(text), "grab_focus",
6490 G_CALLBACK(compose_grab_focus_cb), compose);
6491 g_signal_connect(G_OBJECT(buffer), "insert_text",
6492 G_CALLBACK(text_inserted), compose);
6493 g_signal_connect(G_OBJECT(text), "button_press_event",
6494 G_CALLBACK(text_clicked), compose);
6496 g_signal_connect(G_OBJECT(text), "popup-menu",
6497 G_CALLBACK(compose_popup_menu), compose);
6499 gtk_widget_tap_and_hold_setup(GTK_WIDGET(text), NULL, NULL,
6500 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
6501 g_signal_connect(G_OBJECT(text), "tap-and-hold",
6502 G_CALLBACK(compose_popup_menu), compose);
6504 g_signal_connect(G_OBJECT(subject_entry), "changed",
6505 G_CALLBACK(compose_changed_cb), compose);
6508 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6509 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6510 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6511 g_signal_connect(G_OBJECT(text), "drag_data_received",
6512 G_CALLBACK(compose_insert_drag_received_cb),
6514 g_signal_connect(G_OBJECT(text), "drag-drop",
6515 G_CALLBACK(compose_drag_drop),
6517 gtk_widget_show_all(vbox);
6519 /* pane between attach clist and text */
6520 paned = gtk_vpaned_new();
6521 gtk_paned_set_gutter_size(GTK_PANED(paned), 12);
6522 gtk_container_add(GTK_CONTAINER(vbox2), paned);
6524 if( maemo_mainwindow_is_fullscreen(mainwindow_get_mainwindow()->window) )
6525 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 300 : 280);
6527 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 250 : 230);
6529 gtk_paned_add1(GTK_PANED(paned), notebook);
6530 gtk_paned_add2(GTK_PANED(paned), edit_vbox);
6531 gtk_widget_show_all(paned);
6534 if (prefs_common.textfont) {
6535 PangoFontDescription *font_desc;
6537 font_desc = pango_font_description_from_string
6538 (prefs_common.textfont);
6540 gtk_widget_modify_font(text, font_desc);
6541 pango_font_description_free(font_desc);
6545 n_entries = sizeof(compose_popup_entries) /
6546 sizeof(compose_popup_entries[0]);
6547 popupmenu = menu_create_items(compose_popup_entries, n_entries,
6548 "<Compose>", &popupfactory,
6551 ifactory = gtk_item_factory_from_widget(menubar);
6552 menu_set_sensitive(ifactory, "/Edit/Undo", FALSE);
6553 menu_set_sensitive(ifactory, "/Edit/Redo", FALSE);
6554 menu_set_sensitive(ifactory, "/Options/Remove references", FALSE);
6556 tmpl_menu = gtk_item_factory_get_item(ifactory, "/Tools/Template");
6558 undostruct = undo_init(text);
6559 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
6562 address_completion_start(window);
6564 compose->window = window;
6565 compose->vbox = vbox;
6566 compose->menubar = menubar;
6567 compose->handlebox = handlebox;
6569 compose->vbox2 = vbox2;
6571 compose->paned = paned;
6573 compose->notebook = notebook;
6574 compose->edit_vbox = edit_vbox;
6575 compose->ruler_hbox = ruler_hbox;
6576 compose->ruler = ruler;
6577 compose->scrolledwin = scrolledwin;
6578 compose->text = text;
6580 compose->focused_editable = NULL;
6582 compose->popupmenu = popupmenu;
6583 compose->popupfactory = popupfactory;
6585 compose->tmpl_menu = tmpl_menu;
6587 compose->mode = mode;
6588 compose->rmode = mode;
6590 compose->targetinfo = NULL;
6591 compose->replyinfo = NULL;
6592 compose->fwdinfo = NULL;
6594 compose->replyto = NULL;
6596 compose->bcc = NULL;
6597 compose->followup_to = NULL;
6599 compose->ml_post = NULL;
6601 compose->inreplyto = NULL;
6602 compose->references = NULL;
6603 compose->msgid = NULL;
6604 compose->boundary = NULL;
6606 compose->autowrap = prefs_common.autowrap;
6608 compose->use_signing = FALSE;
6609 compose->use_encryption = FALSE;
6610 compose->privacy_system = NULL;
6612 compose->modified = FALSE;
6614 compose->return_receipt = FALSE;
6616 compose->to_list = NULL;
6617 compose->newsgroup_list = NULL;
6619 compose->undostruct = undostruct;
6621 compose->sig_str = NULL;
6623 compose->exteditor_file = NULL;
6624 compose->exteditor_pid = -1;
6625 compose->exteditor_tag = -1;
6626 compose->draft_timeout_tag = -1;
6629 menu_set_sensitive(ifactory, "/Spelling", FALSE);
6630 if (mode != COMPOSE_REDIRECT) {
6631 if (prefs_common.enable_aspell && prefs_common.dictionary &&
6632 strcmp(prefs_common.dictionary, "")) {
6633 gtkaspell = gtkaspell_new(prefs_common.aspell_path,
6634 prefs_common.dictionary,
6635 prefs_common.alt_dictionary,
6636 conv_get_locale_charset_str(),
6637 prefs_common.misspelled_col,
6638 prefs_common.check_while_typing,
6639 prefs_common.recheck_when_changing_dict,
6640 prefs_common.use_alternate,
6641 prefs_common.use_both_dicts,
6642 GTK_TEXT_VIEW(text),
6643 GTK_WINDOW(compose->window),
6644 compose_spell_menu_changed,
6647 alertpanel_error(_("Spell checker could not "
6649 gtkaspell_checkers_strerror());
6650 gtkaspell_checkers_reset_error();
6652 if (!gtkaspell_set_sug_mode(gtkaspell,
6653 prefs_common.aspell_sugmode)) {
6654 debug_print("Aspell: could not set "
6655 "suggestion mode %s\n",
6656 gtkaspell_checkers_strerror());
6657 gtkaspell_checkers_reset_error();
6660 menu_set_sensitive(ifactory, "/Spelling", TRUE);
6664 compose->gtkaspell = gtkaspell;
6665 compose_spell_menu_changed(compose);
6668 compose_select_account(compose, account, TRUE);
6670 menu_set_active(ifactory, "/Edit/Auto wrapping", prefs_common.autowrap);
6671 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
6672 compose_entry_append(compose, account->auto_cc, COMPOSE_CC);
6674 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
6675 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC);
6677 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
6678 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO);
6680 menu_set_sensitive(ifactory, "/Options/Reply mode", compose->mode == COMPOSE_REPLY);
6682 if (account->protocol != A_NNTP)
6683 gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(compose->header_last->combo)->entry),
6684 prefs_common_translated_header_name("To:"));
6686 gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(compose->header_last->combo)->entry),
6687 prefs_common_translated_header_name("Newsgroups:"));
6689 addressbook_set_target_compose(compose);
6691 if (mode != COMPOSE_REDIRECT)
6692 compose_set_template_menu(compose);
6694 menuitem = gtk_item_factory_get_item(ifactory, "/Tools/Template");
6695 menu_set_sensitive(ifactory, "/Tools/Template", FALSE);
6698 compose_list = g_list_append(compose_list, compose);
6700 if (!prefs_common.show_ruler)
6701 gtk_widget_hide(ruler_hbox);
6703 menuitem = gtk_item_factory_get_item(ifactory, "/Tools/Show ruler");
6704 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
6705 prefs_common.show_ruler);
6708 compose->priority = PRIORITY_NORMAL;
6709 compose_update_priority_menu_item(compose);
6711 compose_set_out_encoding(compose);
6714 compose_update_actions_menu(compose);
6716 /* Privacy Systems menu */
6717 compose_update_privacy_systems_menu(compose);
6719 activate_privacy_system(compose, account, TRUE);
6720 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
6722 gtk_widget_realize(window);
6724 gtk_widget_show(window);
6726 maemo_window_full_screen_if_needed(GTK_WINDOW(window));
6727 maemo_connect_key_press_to_mainwindow(GTK_WINDOW(window));
6734 static GtkWidget *compose_account_option_menu_create(Compose *compose)
6739 GtkWidget *optmenubox;
6742 GtkWidget *from_name = NULL;
6744 gint num = 0, def_menu = 0;
6746 accounts = account_get_list();
6747 g_return_val_if_fail(accounts != NULL, NULL);
6749 optmenubox = gtk_event_box_new();
6750 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
6751 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
6753 hbox = gtk_hbox_new(FALSE, 6);
6754 from_name = gtk_entry_new();
6756 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
6757 G_CALLBACK(compose_grab_focus_cb), compose);
6759 for (; accounts != NULL; accounts = accounts->next, num++) {
6760 PrefsAccount *ac = (PrefsAccount *)accounts->data;
6761 gchar *name, *from = NULL;
6763 if (ac == compose->account) def_menu = num;
6765 name = g_markup_printf_escaped(_("From: <i>%s</i>"),
6768 if (ac == compose->account) {
6769 if (ac->name && *ac->name) {
6771 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
6772 from = g_strdup_printf("%s <%s>",
6774 gtk_entry_set_text(GTK_ENTRY(from_name), from);
6776 from = g_strdup_printf("%s",
6778 gtk_entry_set_text(GTK_ENTRY(from_name), from);
6781 COMBOBOX_ADD(menu, name, ac->account_id);
6786 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
6788 g_signal_connect(G_OBJECT(optmenu), "changed",
6789 G_CALLBACK(account_activated),
6791 g_signal_connect(G_OBJECT(from_name), "populate-popup",
6792 G_CALLBACK(compose_entry_popup_extend),
6795 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
6796 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
6798 gtk_tooltips_set_tip(compose->tooltips, optmenubox,
6799 _("Account to use for this email"), NULL);
6800 gtk_tooltips_set_tip(compose->tooltips, from_name,
6801 _("Sender address to be used"), NULL);
6803 compose->from_name = from_name;
6808 static void compose_set_priority_cb(gpointer data,
6812 Compose *compose = (Compose *) data;
6813 compose->priority = action;
6816 static void compose_reply_change_mode(gpointer data,
6820 Compose *compose = (Compose *) data;
6821 gboolean was_modified = compose->modified;
6823 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
6825 g_return_if_fail(compose->replyinfo != NULL);
6827 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
6829 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
6831 if (action == COMPOSE_REPLY_TO_ALL)
6833 if (action == COMPOSE_REPLY_TO_SENDER)
6835 if (action == COMPOSE_REPLY_TO_LIST)
6838 compose_remove_header_entries(compose);
6839 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
6840 if (compose->account->set_autocc && compose->account->auto_cc)
6841 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC);
6843 if (compose->account->set_autobcc && compose->account->auto_bcc)
6844 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC);
6846 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
6847 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO);
6848 compose_show_first_last_header(compose, TRUE);
6849 compose->modified = was_modified;
6850 compose_set_title(compose);
6853 static void compose_update_priority_menu_item(Compose * compose)
6855 GtkItemFactory *ifactory;
6856 GtkWidget *menuitem = NULL;
6858 ifactory = gtk_item_factory_from_widget(compose->menubar);
6860 switch (compose->priority) {
6861 case PRIORITY_HIGHEST:
6862 menuitem = gtk_item_factory_get_item
6863 (ifactory, "/Options/Priority/Highest");
6866 menuitem = gtk_item_factory_get_item
6867 (ifactory, "/Options/Priority/High");
6869 case PRIORITY_NORMAL:
6870 menuitem = gtk_item_factory_get_item
6871 (ifactory, "/Options/Priority/Normal");
6874 menuitem = gtk_item_factory_get_item
6875 (ifactory, "/Options/Priority/Low");
6877 case PRIORITY_LOWEST:
6878 menuitem = gtk_item_factory_get_item
6879 (ifactory, "/Options/Priority/Lowest");
6882 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
6885 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
6887 Compose *compose = (Compose *) data;
6889 GtkItemFactory *ifactory;
6890 gboolean can_sign = FALSE, can_encrypt = FALSE;
6892 g_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
6894 if (!GTK_CHECK_MENU_ITEM(widget)->active)
6897 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
6898 g_free(compose->privacy_system);
6899 compose->privacy_system = NULL;
6900 if (systemid != NULL) {
6901 compose->privacy_system = g_strdup(systemid);
6903 can_sign = privacy_system_can_sign(systemid);
6904 can_encrypt = privacy_system_can_encrypt(systemid);
6907 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
6909 ifactory = gtk_item_factory_from_widget(compose->menubar);
6910 menu_set_sensitive(ifactory, "/Options/Sign", can_sign);
6911 menu_set_sensitive(ifactory, "/Options/Encrypt", can_encrypt);
6914 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
6916 static gchar *branch_path = "/Options/Privacy System";
6917 GtkItemFactory *ifactory;
6918 GtkWidget *menuitem = NULL;
6920 gboolean can_sign = FALSE, can_encrypt = FALSE;
6921 gboolean found = FALSE;
6923 ifactory = gtk_item_factory_from_widget(compose->menubar);
6925 if (compose->privacy_system != NULL) {
6928 menuitem = gtk_item_factory_get_widget(ifactory, branch_path);
6929 g_return_if_fail(menuitem != NULL);
6931 amenu = GTK_MENU_SHELL(menuitem)->children;
6933 while (amenu != NULL) {
6934 GList *alist = amenu->next;
6936 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
6937 if (systemid != NULL) {
6938 if (strcmp(systemid, compose->privacy_system) == 0) {
6939 menuitem = GTK_WIDGET(amenu->data);
6941 can_sign = privacy_system_can_sign(systemid);
6942 can_encrypt = privacy_system_can_encrypt(systemid);
6946 } else if (strlen(compose->privacy_system) == 0) {
6947 menuitem = GTK_WIDGET(amenu->data);
6950 can_encrypt = FALSE;
6957 if (menuitem != NULL)
6958 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
6960 if (warn && !found && strlen(compose->privacy_system)) {
6961 gchar *tmp = g_strdup_printf(
6962 _("The privacy system '%s' cannot be loaded. You "
6963 "will not be able to sign or encrypt this message."),
6964 compose->privacy_system);
6965 alertpanel_warning(tmp);
6970 menu_set_sensitive(ifactory, "/Options/Sign", can_sign);
6971 menu_set_sensitive(ifactory, "/Options/Encrypt", can_encrypt);
6974 static void compose_set_out_encoding(Compose *compose)
6976 GtkItemFactoryEntry *entry;
6977 GtkItemFactory *ifactory;
6978 CharSet out_encoding;
6979 gchar *path, *p, *q;
6982 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
6983 ifactory = gtk_item_factory_from_widget(compose->menubar);
6985 for (entry = compose_entries; entry->callback != compose_address_cb;
6987 if (entry->callback == compose_set_encoding_cb &&
6988 (CharSet)entry->callback_action == out_encoding) {
6989 p = q = path = g_strdup(entry->path);
7001 item = gtk_item_factory_get_item(ifactory, path);
7002 gtk_widget_activate(item);
7009 static void compose_set_template_menu(Compose *compose)
7011 GSList *tmpl_list, *cur;
7015 tmpl_list = template_get_config();
7017 menu = gtk_menu_new();
7019 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
7020 Template *tmpl = (Template *)cur->data;
7022 item = gtk_menu_item_new_with_label(tmpl->name);
7023 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
7024 g_signal_connect(G_OBJECT(item), "activate",
7025 G_CALLBACK(compose_template_activate_cb),
7027 g_object_set_data(G_OBJECT(item), "template", tmpl);
7028 gtk_widget_show(item);
7031 gtk_widget_show(menu);
7032 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
7035 void compose_update_actions_menu(Compose *compose)
7037 GtkItemFactory *ifactory;
7039 ifactory = gtk_item_factory_from_widget(compose->menubar);
7040 action_update_compose_menu(ifactory, "/Tools/Actions", compose);
7043 static void compose_update_privacy_systems_menu(Compose *compose)
7045 static gchar *branch_path = "/Options/Privacy System";
7046 GtkItemFactory *ifactory;
7047 GtkWidget *menuitem;
7048 GSList *systems, *cur;
7051 GtkWidget *system_none;
7054 ifactory = gtk_item_factory_from_widget(compose->menubar);
7056 /* remove old entries */
7057 menuitem = gtk_item_factory_get_widget(ifactory, branch_path);
7058 g_return_if_fail(menuitem != NULL);
7060 amenu = GTK_MENU_SHELL(menuitem)->children->next;
7061 while (amenu != NULL) {
7062 GList *alist = amenu->next;
7063 gtk_widget_destroy(GTK_WIDGET(amenu->data));
7067 system_none = gtk_item_factory_get_widget(ifactory,
7068 "/Options/Privacy System/None");
7070 g_signal_connect(G_OBJECT(system_none), "activate",
7071 G_CALLBACK(compose_set_privacy_system_cb), compose);
7073 systems = privacy_get_system_ids();
7074 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
7075 gchar *systemid = cur->data;
7077 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
7078 widget = gtk_radio_menu_item_new_with_label(group,
7079 privacy_system_get_name(systemid));
7080 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
7081 g_strdup(systemid), g_free);
7082 g_signal_connect(G_OBJECT(widget), "activate",
7083 G_CALLBACK(compose_set_privacy_system_cb), compose);
7085 gtk_menu_append(GTK_MENU(system_none->parent), widget);
7086 gtk_widget_show(widget);
7089 g_slist_free(systems);
7092 void compose_reflect_prefs_all(void)
7097 for (cur = compose_list; cur != NULL; cur = cur->next) {
7098 compose = (Compose *)cur->data;
7099 compose_set_template_menu(compose);
7103 void compose_reflect_prefs_pixmap_theme(void)
7108 for (cur = compose_list; cur != NULL; cur = cur->next) {
7109 compose = (Compose *)cur->data;
7110 toolbar_update(TOOLBAR_COMPOSE, compose);
7114 static const gchar *compose_quote_char_from_context(Compose *compose)
7116 const gchar *qmark = NULL;
7118 g_return_val_if_fail(compose != NULL, NULL);
7120 switch (compose->mode) {
7121 /* use forward-specific quote char */
7122 case COMPOSE_FORWARD:
7123 case COMPOSE_FORWARD_AS_ATTACH:
7124 case COMPOSE_FORWARD_INLINE:
7125 if (compose->folder && compose->folder->prefs &&
7126 compose->folder->prefs->forward_with_format)
7127 qmark = compose->folder->prefs->forward_quotemark;
7128 else if (compose->account->forward_with_format)
7129 qmark = compose->account->forward_quotemark;
7131 qmark = prefs_common.fw_quotemark;
7134 /* use reply-specific quote char in all other modes */
7136 if (compose->folder && compose->folder->prefs &&
7137 compose->folder->prefs->reply_with_format)
7138 qmark = compose->folder->prefs->reply_quotemark;
7139 else if (compose->account->reply_with_format)
7140 qmark = compose->account->reply_quotemark;
7142 qmark = prefs_common.quotemark;
7146 if (qmark == NULL || *qmark == '\0')
7152 static void compose_template_apply(Compose *compose, Template *tmpl,
7156 GtkTextBuffer *buffer;
7160 gchar *parsed_str = NULL;
7161 gint cursor_pos = 0;
7162 const gchar *err_msg = _("Template body format error at line %d.");
7165 /* process the body */
7167 text = GTK_TEXT_VIEW(compose->text);
7168 buffer = gtk_text_view_get_buffer(text);
7171 qmark = compose_quote_char_from_context(compose);
7173 if (compose->replyinfo != NULL) {
7176 gtk_text_buffer_set_text(buffer, "", -1);
7177 mark = gtk_text_buffer_get_insert(buffer);
7178 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7180 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
7181 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
7183 } else if (compose->fwdinfo != NULL) {
7186 gtk_text_buffer_set_text(buffer, "", -1);
7187 mark = gtk_text_buffer_get_insert(buffer);
7188 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7190 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
7191 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
7194 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
7196 GtkTextIter start, end;
7199 gtk_text_buffer_get_start_iter(buffer, &start);
7200 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
7201 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
7203 /* clear the buffer now */
7205 gtk_text_buffer_set_text(buffer, "", -1);
7207 parsed_str = compose_quote_fmt(compose, dummyinfo,
7208 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
7209 procmsg_msginfo_free( dummyinfo );
7215 gtk_text_buffer_set_text(buffer, "", -1);
7216 mark = gtk_text_buffer_get_insert(buffer);
7217 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7220 if (replace && parsed_str && compose->account->auto_sig)
7221 compose_insert_sig(compose, FALSE);
7223 if (replace && parsed_str) {
7224 gtk_text_buffer_get_start_iter(buffer, &iter);
7225 gtk_text_buffer_place_cursor(buffer, &iter);
7229 cursor_pos = quote_fmt_get_cursor_pos();
7230 compose->set_cursor_pos = cursor_pos;
7231 if (cursor_pos == -1)
7233 gtk_text_buffer_get_start_iter(buffer, &iter);
7234 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
7235 gtk_text_buffer_place_cursor(buffer, &iter);
7238 /* process the other fields */
7240 compose_template_apply_fields(compose, tmpl);
7241 quote_fmt_reset_vartable();
7242 compose_changed_cb(NULL, compose);
7245 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
7247 MsgInfo* dummyinfo = NULL;
7248 MsgInfo *msginfo = NULL;
7251 if (compose->replyinfo != NULL)
7252 msginfo = compose->replyinfo;
7253 else if (compose->fwdinfo != NULL)
7254 msginfo = compose->fwdinfo;
7256 dummyinfo = compose_msginfo_new_from_compose(compose);
7257 msginfo = dummyinfo;
7260 if (tmpl->to && *tmpl->to != '\0') {
7262 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7263 compose->gtkaspell);
7265 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7267 quote_fmt_scan_string(tmpl->to);
7270 buf = quote_fmt_get_buffer();
7272 alertpanel_error(_("Template To format error."));
7274 compose_entry_append(compose, buf, COMPOSE_TO);
7278 if (tmpl->cc && *tmpl->cc != '\0') {
7280 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7281 compose->gtkaspell);
7283 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7285 quote_fmt_scan_string(tmpl->cc);
7288 buf = quote_fmt_get_buffer();
7290 alertpanel_error(_("Template Cc format error."));
7292 compose_entry_append(compose, buf, COMPOSE_CC);
7296 if (tmpl->bcc && *tmpl->bcc != '\0') {
7298 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7299 compose->gtkaspell);
7301 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7303 quote_fmt_scan_string(tmpl->bcc);
7306 buf = quote_fmt_get_buffer();
7308 alertpanel_error(_("Template Bcc format error."));
7310 compose_entry_append(compose, buf, COMPOSE_BCC);
7314 /* process the subject */
7315 if (tmpl->subject && *tmpl->subject != '\0') {
7317 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7318 compose->gtkaspell);
7320 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7322 quote_fmt_scan_string(tmpl->subject);
7325 buf = quote_fmt_get_buffer();
7327 alertpanel_error(_("Template subject format error."));
7329 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
7333 procmsg_msginfo_free( dummyinfo );
7336 static void compose_destroy(Compose *compose)
7338 GtkTextBuffer *buffer;
7339 GtkClipboard *clipboard;
7341 compose_list = g_list_remove(compose_list, compose);
7343 if (compose->updating) {
7344 debug_print("danger, not destroying anything now\n");
7345 compose->deferred_destroy = TRUE;
7348 /* NOTE: address_completion_end() does nothing with the window
7349 * however this may change. */
7350 address_completion_end(compose->window);
7352 slist_free_strings(compose->to_list);
7353 g_slist_free(compose->to_list);
7354 slist_free_strings(compose->newsgroup_list);
7355 g_slist_free(compose->newsgroup_list);
7356 slist_free_strings(compose->header_list);
7357 g_slist_free(compose->header_list);
7359 procmsg_msginfo_free(compose->targetinfo);
7360 procmsg_msginfo_free(compose->replyinfo);
7361 procmsg_msginfo_free(compose->fwdinfo);
7363 g_free(compose->replyto);
7364 g_free(compose->cc);
7365 g_free(compose->bcc);
7366 g_free(compose->newsgroups);
7367 g_free(compose->followup_to);
7369 g_free(compose->ml_post);
7371 g_free(compose->inreplyto);
7372 g_free(compose->references);
7373 g_free(compose->msgid);
7374 g_free(compose->boundary);
7376 g_free(compose->redirect_filename);
7377 if (compose->undostruct)
7378 undo_destroy(compose->undostruct);
7380 g_free(compose->sig_str);
7382 g_free(compose->exteditor_file);
7384 g_free(compose->orig_charset);
7386 g_free(compose->privacy_system);
7388 if (addressbook_get_target_compose() == compose)
7389 addressbook_set_target_compose(NULL);
7392 if (compose->gtkaspell) {
7393 gtkaspell_delete(compose->gtkaspell);
7394 compose->gtkaspell = NULL;
7398 prefs_common.compose_width = compose->scrolledwin->allocation.width;
7399 prefs_common.compose_height = compose->window->allocation.height;
7401 if (!gtk_widget_get_parent(compose->paned))
7402 gtk_widget_destroy(compose->paned);
7403 gtk_widget_destroy(compose->popupmenu);
7405 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
7406 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7407 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
7409 gtk_widget_destroy(compose->window);
7410 toolbar_destroy(compose->toolbar);
7411 g_free(compose->toolbar);
7412 g_mutex_free(compose->mutex);
7416 static void compose_attach_info_free(AttachInfo *ainfo)
7418 g_free(ainfo->file);
7419 g_free(ainfo->content_type);
7420 g_free(ainfo->name);
7424 static void compose_attach_remove_selected(Compose *compose)
7426 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
7427 GtkTreeSelection *selection;
7429 GtkTreeModel *model;
7431 selection = gtk_tree_view_get_selection(tree_view);
7432 sel = gtk_tree_selection_get_selected_rows(selection, &model);
7437 for (cur = sel; cur != NULL; cur = cur->next) {
7438 GtkTreePath *path = cur->data;
7439 GtkTreeRowReference *ref = gtk_tree_row_reference_new
7442 gtk_tree_path_free(path);
7445 for (cur = sel; cur != NULL; cur = cur->next) {
7446 GtkTreeRowReference *ref = cur->data;
7447 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
7450 if (gtk_tree_model_get_iter(model, &iter, path))
7451 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
7453 gtk_tree_path_free(path);
7454 gtk_tree_row_reference_free(ref);
7460 static struct _AttachProperty
7463 GtkWidget *mimetype_entry;
7464 GtkWidget *encoding_optmenu;
7465 GtkWidget *path_entry;
7466 GtkWidget *filename_entry;
7468 GtkWidget *cancel_btn;
7471 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
7473 gtk_tree_path_free((GtkTreePath *)ptr);
7476 static void compose_attach_property(Compose *compose)
7478 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
7480 GtkComboBox *optmenu;
7481 GtkTreeSelection *selection;
7483 GtkTreeModel *model;
7486 static gboolean cancelled;
7488 /* only if one selected */
7489 selection = gtk_tree_view_get_selection(tree_view);
7490 if (gtk_tree_selection_count_selected_rows(selection) != 1)
7493 sel = gtk_tree_selection_get_selected_rows(selection, &model);
7497 path = (GtkTreePath *) sel->data;
7498 gtk_tree_model_get_iter(model, &iter, path);
7499 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
7502 g_list_foreach(sel, gtk_tree_path_free_, NULL);
7508 if (!attach_prop.window)
7509 compose_attach_property_create(&cancelled);
7510 gtk_widget_grab_focus(attach_prop.ok_btn);
7511 gtk_widget_show(attach_prop.window);
7512 manage_window_set_transient(GTK_WINDOW(attach_prop.window));
7514 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
7515 if (ainfo->encoding == ENC_UNKNOWN)
7516 combobox_select_by_data(optmenu, ENC_BASE64);
7518 combobox_select_by_data(optmenu, ainfo->encoding);
7520 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
7521 ainfo->content_type ? ainfo->content_type : "");
7522 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
7523 ainfo->file ? ainfo->file : "");
7524 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
7525 ainfo->name ? ainfo->name : "");
7528 const gchar *entry_text;
7530 gchar *cnttype = NULL;
7537 gtk_widget_hide(attach_prop.window);
7542 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
7543 if (*entry_text != '\0') {
7546 text = g_strstrip(g_strdup(entry_text));
7547 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
7548 cnttype = g_strdup(text);
7551 alertpanel_error(_("Invalid MIME type."));
7557 ainfo->encoding = combobox_get_active_data(optmenu);
7559 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
7560 if (*entry_text != '\0') {
7561 if (is_file_exist(entry_text) &&
7562 (size = get_file_size(entry_text)) > 0)
7563 file = g_strdup(entry_text);
7566 (_("File doesn't exist or is empty."));
7572 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
7573 if (*entry_text != '\0') {
7574 g_free(ainfo->name);
7575 ainfo->name = g_strdup(entry_text);
7579 g_free(ainfo->content_type);
7580 ainfo->content_type = cnttype;
7583 g_free(ainfo->file);
7589 /* update tree store */
7590 text = to_human_readable(ainfo->size);
7591 gtk_tree_model_get_iter(model, &iter, path);
7592 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
7593 COL_MIMETYPE, ainfo->content_type,
7595 COL_NAME, ainfo->name,
7601 gtk_tree_path_free(path);
7604 #define SET_LABEL_AND_ENTRY(str, entry, top) \
7606 label = gtk_label_new(str); \
7607 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
7608 GTK_FILL, 0, 0, 0); \
7609 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
7611 entry = gtk_entry_new(); \
7612 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
7613 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
7616 static void compose_attach_property_create(gboolean *cancelled)
7622 GtkWidget *mimetype_entry;
7625 GtkListStore *optmenu_menu;
7626 GtkWidget *path_entry;
7627 GtkWidget *filename_entry;
7630 GtkWidget *cancel_btn;
7631 GList *mime_type_list, *strlist;
7634 debug_print("Creating attach_property window...\n");
7636 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
7637 gtk_widget_set_size_request(window, 480, -1);
7638 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
7639 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
7640 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
7641 gtk_window_set_modal(GTK_WINDOW(window), TRUE);
7642 g_signal_connect(G_OBJECT(window), "delete_event",
7643 G_CALLBACK(attach_property_delete_event),
7645 g_signal_connect(G_OBJECT(window), "key_press_event",
7646 G_CALLBACK(attach_property_key_pressed),
7649 vbox = gtk_vbox_new(FALSE, 8);
7650 gtk_container_add(GTK_CONTAINER(window), vbox);
7652 table = gtk_table_new(4, 2, FALSE);
7653 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
7654 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
7655 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
7657 label = gtk_label_new(_("MIME type"));
7658 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
7660 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
7661 mimetype_entry = gtk_combo_new();
7662 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
7663 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
7665 /* stuff with list */
7666 mime_type_list = procmime_get_mime_type_list();
7668 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
7669 MimeType *type = (MimeType *) mime_type_list->data;
7672 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
7674 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
7677 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
7678 (GCompareFunc)strcmp2);
7681 gtk_combo_set_popdown_strings(GTK_COMBO(mimetype_entry), strlist);
7683 for (mime_type_list = strlist; mime_type_list != NULL;
7684 mime_type_list = mime_type_list->next)
7685 g_free(mime_type_list->data);
7686 g_list_free(strlist);
7688 mimetype_entry = GTK_COMBO(mimetype_entry)->entry;
7690 label = gtk_label_new(_("Encoding"));
7691 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
7693 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
7695 hbox = gtk_hbox_new(FALSE, 0);
7696 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
7697 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
7699 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
7700 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
7702 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
7703 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
7704 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
7705 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
7706 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
7708 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
7710 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
7711 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
7713 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
7714 &ok_btn, GTK_STOCK_OK,
7716 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
7717 gtk_widget_grab_default(ok_btn);
7719 g_signal_connect(G_OBJECT(ok_btn), "clicked",
7720 G_CALLBACK(attach_property_ok),
7722 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
7723 G_CALLBACK(attach_property_cancel),
7726 gtk_widget_show_all(vbox);
7728 attach_prop.window = window;
7729 attach_prop.mimetype_entry = mimetype_entry;
7730 attach_prop.encoding_optmenu = optmenu;
7731 attach_prop.path_entry = path_entry;
7732 attach_prop.filename_entry = filename_entry;
7733 attach_prop.ok_btn = ok_btn;
7734 attach_prop.cancel_btn = cancel_btn;
7737 #undef SET_LABEL_AND_ENTRY
7739 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
7745 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
7751 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
7752 gboolean *cancelled)
7760 static gboolean attach_property_key_pressed(GtkWidget *widget,
7762 gboolean *cancelled)
7764 if (event && event->keyval == GDK_Escape) {
7771 static void compose_exec_ext_editor(Compose *compose)
7778 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
7779 G_DIR_SEPARATOR, compose);
7781 if (pipe(pipe_fds) < 0) {
7787 if ((pid = fork()) < 0) {
7794 /* close the write side of the pipe */
7797 compose->exteditor_file = g_strdup(tmp);
7798 compose->exteditor_pid = pid;
7800 compose_set_ext_editor_sensitive(compose, FALSE);
7802 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
7803 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
7807 } else { /* process-monitoring process */
7813 /* close the read side of the pipe */
7816 if (compose_write_body_to_file(compose, tmp) < 0) {
7817 fd_write_all(pipe_fds[1], "2\n", 2);
7821 pid_ed = compose_exec_ext_editor_real(tmp);
7823 fd_write_all(pipe_fds[1], "1\n", 2);
7827 /* wait until editor is terminated */
7828 waitpid(pid_ed, NULL, 0);
7830 fd_write_all(pipe_fds[1], "0\n", 2);
7837 #endif /* G_OS_UNIX */
7841 static gint compose_exec_ext_editor_real(const gchar *file)
7848 g_return_val_if_fail(file != NULL, -1);
7850 if ((pid = fork()) < 0) {
7855 if (pid != 0) return pid;
7857 /* grandchild process */
7859 if (setpgid(0, getppid()))
7862 if (prefs_common.ext_editor_cmd &&
7863 (p = strchr(prefs_common.ext_editor_cmd, '%')) &&
7864 *(p + 1) == 's' && !strchr(p + 2, '%')) {
7865 g_snprintf(buf, sizeof(buf), prefs_common.ext_editor_cmd, file);
7867 if (prefs_common.ext_editor_cmd)
7868 g_warning("External editor command line is invalid: '%s'\n",
7869 prefs_common.ext_editor_cmd);
7870 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
7873 cmdline = strsplit_with_quote(buf, " ", 1024);
7874 execvp(cmdline[0], cmdline);
7877 g_strfreev(cmdline);
7882 static gboolean compose_ext_editor_kill(Compose *compose)
7884 pid_t pgid = compose->exteditor_pid * -1;
7887 ret = kill(pgid, 0);
7889 if (ret == 0 || (ret == -1 && EPERM == errno)) {
7893 msg = g_strdup_printf
7894 (_("The external editor is still working.\n"
7895 "Force terminating the process?\n"
7896 "process group id: %d"), -pgid);
7897 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
7898 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
7902 if (val == G_ALERTALTERNATE) {
7903 g_source_remove(compose->exteditor_tag);
7904 g_io_channel_shutdown(compose->exteditor_ch,
7906 g_io_channel_unref(compose->exteditor_ch);
7908 if (kill(pgid, SIGTERM) < 0) perror("kill");
7909 waitpid(compose->exteditor_pid, NULL, 0);
7911 g_warning("Terminated process group id: %d", -pgid);
7912 g_warning("Temporary file: %s",
7913 compose->exteditor_file);
7915 compose_set_ext_editor_sensitive(compose, TRUE);
7917 g_free(compose->exteditor_file);
7918 compose->exteditor_file = NULL;
7919 compose->exteditor_pid = -1;
7920 compose->exteditor_ch = NULL;
7921 compose->exteditor_tag = -1;
7929 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
7933 Compose *compose = (Compose *)data;
7936 debug_print(_("Compose: input from monitoring process\n"));
7938 g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL);
7940 g_io_channel_shutdown(source, FALSE, NULL);
7941 g_io_channel_unref(source);
7943 waitpid(compose->exteditor_pid, NULL, 0);
7945 if (buf[0] == '0') { /* success */
7946 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
7947 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
7949 gtk_text_buffer_set_text(buffer, "", -1);
7950 compose_insert_file(compose, compose->exteditor_file);
7951 compose_changed_cb(NULL, compose);
7953 if (g_unlink(compose->exteditor_file) < 0)
7954 FILE_OP_ERROR(compose->exteditor_file, "unlink");
7955 } else if (buf[0] == '1') { /* failed */
7956 g_warning("Couldn't exec external editor\n");
7957 if (g_unlink(compose->exteditor_file) < 0)
7958 FILE_OP_ERROR(compose->exteditor_file, "unlink");
7959 } else if (buf[0] == '2') {
7960 g_warning("Couldn't write to file\n");
7961 } else if (buf[0] == '3') {
7962 g_warning("Pipe read failed\n");
7965 compose_set_ext_editor_sensitive(compose, TRUE);
7967 g_free(compose->exteditor_file);
7968 compose->exteditor_file = NULL;
7969 compose->exteditor_pid = -1;
7970 compose->exteditor_ch = NULL;
7971 compose->exteditor_tag = -1;
7976 static void compose_set_ext_editor_sensitive(Compose *compose,
7979 GtkItemFactory *ifactory;
7981 ifactory = gtk_item_factory_from_widget(compose->menubar);
7983 menu_set_sensitive(ifactory, "/Message/Send", sensitive);
7984 menu_set_sensitive(ifactory, "/Message/Send later", sensitive);
7985 menu_set_sensitive(ifactory, "/Message/Insert file", sensitive);
7986 menu_set_sensitive(ifactory, "/Message/Insert signature", sensitive);
7987 menu_set_sensitive(ifactory, "/Edit/Wrap current paragraph", sensitive);
7988 menu_set_sensitive(ifactory, "/Edit/Wrap all long lines", sensitive);
7989 menu_set_sensitive(ifactory, "/Edit/Edit with external editor",
7992 gtk_widget_set_sensitive(compose->text, sensitive);
7993 if (compose->toolbar->send_btn)
7994 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
7995 if (compose->toolbar->sendl_btn)
7996 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
7997 if (compose->toolbar->draft_btn)
7998 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
7999 if (compose->toolbar->insert_btn)
8000 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
8001 if (compose->toolbar->sig_btn)
8002 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
8003 if (compose->toolbar->exteditor_btn)
8004 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
8005 if (compose->toolbar->linewrap_current_btn)
8006 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
8007 if (compose->toolbar->linewrap_all_btn)
8008 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
8010 #endif /* G_OS_UNIX */
8013 * compose_undo_state_changed:
8015 * Change the sensivity of the menuentries undo and redo
8017 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
8018 gint redo_state, gpointer data)
8020 GtkWidget *widget = GTK_WIDGET(data);
8021 GtkItemFactory *ifactory;
8023 g_return_if_fail(widget != NULL);
8025 ifactory = gtk_item_factory_from_widget(widget);
8027 switch (undo_state) {
8028 case UNDO_STATE_TRUE:
8029 if (!undostruct->undo_state) {
8030 undostruct->undo_state = TRUE;
8031 menu_set_sensitive(ifactory, "/Edit/Undo", TRUE);
8034 case UNDO_STATE_FALSE:
8035 if (undostruct->undo_state) {
8036 undostruct->undo_state = FALSE;
8037 menu_set_sensitive(ifactory, "/Edit/Undo", FALSE);
8040 case UNDO_STATE_UNCHANGED:
8042 case UNDO_STATE_REFRESH:
8043 menu_set_sensitive(ifactory, "/Edit/Undo",
8044 undostruct->undo_state);
8047 g_warning("Undo state not recognized");
8051 switch (redo_state) {
8052 case UNDO_STATE_TRUE:
8053 if (!undostruct->redo_state) {
8054 undostruct->redo_state = TRUE;
8055 menu_set_sensitive(ifactory, "/Edit/Redo", TRUE);
8058 case UNDO_STATE_FALSE:
8059 if (undostruct->redo_state) {
8060 undostruct->redo_state = FALSE;
8061 menu_set_sensitive(ifactory, "/Edit/Redo", FALSE);
8064 case UNDO_STATE_UNCHANGED:
8066 case UNDO_STATE_REFRESH:
8067 menu_set_sensitive(ifactory, "/Edit/Redo",
8068 undostruct->redo_state);
8071 g_warning("Redo state not recognized");
8076 /* callback functions */
8078 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
8079 * includes "non-client" (windows-izm) in calculation, so this calculation
8080 * may not be accurate.
8082 static gboolean compose_edit_size_alloc(GtkEditable *widget,
8083 GtkAllocation *allocation,
8084 GtkSHRuler *shruler)
8086 if (prefs_common.show_ruler) {
8087 gint char_width = 0, char_height = 0;
8088 gint line_width_in_chars;
8090 gtkut_get_font_size(GTK_WIDGET(widget),
8091 &char_width, &char_height);
8092 line_width_in_chars =
8093 (allocation->width - allocation->x) / char_width;
8095 /* got the maximum */
8096 gtk_ruler_set_range(GTK_RULER(shruler),
8097 0.0, line_width_in_chars, 0,
8098 /*line_width_in_chars*/ char_width);
8104 static void account_activated(GtkComboBox *optmenu, gpointer data)
8106 Compose *compose = (Compose *)data;
8109 gchar *folderidentifier;
8110 gint account_id = 0;
8114 /* Get ID of active account in the combo box */
8115 menu = gtk_combo_box_get_model(optmenu);
8116 gtk_combo_box_get_active_iter(optmenu, &iter);
8117 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
8119 ac = account_find_from_id(account_id);
8120 g_return_if_fail(ac != NULL);
8122 if (ac != compose->account)
8123 compose_select_account(compose, ac, FALSE);
8125 /* Set message save folder */
8126 if (account_get_special_folder(compose->account, F_OUTBOX)) {
8127 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
8129 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
8130 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
8132 gtk_editable_delete_text(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
8133 if (account_get_special_folder(compose->account, F_OUTBOX)) {
8134 folderidentifier = folder_item_get_identifier(account_get_special_folder
8135 (compose->account, F_OUTBOX));
8136 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
8137 g_free(folderidentifier);
8141 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
8142 GtkTreeViewColumn *column, Compose *compose)
8144 compose_attach_property(compose);
8147 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
8150 Compose *compose = (Compose *)data;
8151 GtkTreeSelection *attach_selection;
8152 gint attach_nr_selected;
8153 GtkItemFactory *ifactory;
8155 if (!event) return FALSE;
8157 if (event->button == 3) {
8158 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
8159 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
8160 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
8162 if (attach_nr_selected > 0)
8164 menu_set_sensitive(ifactory, "/Remove", TRUE);
8165 menu_set_sensitive(ifactory, "/Properties...", TRUE);
8167 menu_set_sensitive(ifactory, "/Remove", FALSE);
8168 menu_set_sensitive(ifactory, "/Properties...", FALSE);
8171 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
8172 NULL, NULL, event->button, event->time);
8179 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
8182 Compose *compose = (Compose *)data;
8184 if (!event) return FALSE;
8186 switch (event->keyval) {
8188 compose_attach_remove_selected(compose);
8194 static void compose_allow_user_actions (Compose *compose, gboolean allow)
8196 GtkItemFactory *ifactory = gtk_item_factory_from_widget(compose->menubar);
8197 toolbar_comp_set_sensitive(compose, allow);
8198 menu_set_sensitive(ifactory, "/Message", allow);
8199 menu_set_sensitive(ifactory, "/Edit", allow);
8201 menu_set_sensitive(ifactory, "/Spelling", allow);
8203 menu_set_sensitive(ifactory, "/Options", allow);
8204 menu_set_sensitive(ifactory, "/Tools", allow);
8205 menu_set_sensitive(ifactory, "/Help", allow);
8207 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
8211 static void compose_send_cb(gpointer data, guint action, GtkWidget *widget)
8213 Compose *compose = (Compose *)data;
8215 if (prefs_common.work_offline &&
8216 !inc_offline_should_override(TRUE,
8217 _("Claws Mail needs network access in order "
8218 "to send this email.")))
8221 if (compose->draft_timeout_tag != -1) { /* CLAWS: disable draft timeout */
8222 g_source_remove(compose->draft_timeout_tag);
8223 compose->draft_timeout_tag = -1;
8226 compose_send(compose);
8229 static void compose_send_later_cb(gpointer data, guint action,
8232 Compose *compose = (Compose *)data;
8236 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
8240 compose_close(compose);
8241 } else if (val == -1) {
8242 alertpanel_error(_("Could not queue message."));
8243 } else if (val == -2) {
8244 alertpanel_error(_("Could not queue message:\n\n%s."), strerror(errno));
8245 } else if (val == -3) {
8246 if (privacy_peek_error())
8247 alertpanel_error(_("Could not queue message for sending:\n\n"
8248 "Signature failed: %s"), privacy_get_error());
8249 } else if (val == -4) {
8250 alertpanel_error(_("Could not queue message for sending:\n\n"
8251 "Charset conversion failed."));
8252 } else if (val == -5) {
8253 alertpanel_error(_("Could not queue message for sending:\n\n"
8254 "Couldn't get recipient encryption key."));
8255 } else if (val == -6) {
8258 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
8261 void compose_draft (gpointer data, guint action)
8263 compose_draft_cb(data, action, NULL);
8266 #define DRAFTED_AT_EXIT "drafted_at_exit"
8267 void compose_clear_exit_drafts(void)
8269 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8270 DRAFTED_AT_EXIT, NULL);
8271 if (is_file_exist(filepath))
8277 static void compose_register_draft(MsgInfo *info)
8279 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8280 DRAFTED_AT_EXIT, NULL);
8281 FILE *fp = fopen(filepath, "ab");
8284 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
8292 void compose_reopen_exit_drafts(void)
8294 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8295 DRAFTED_AT_EXIT, NULL);
8296 FILE *fp = fopen(filepath, "rb");
8300 while (fgets(buf, sizeof(buf), fp)) {
8301 gchar **parts = g_strsplit(buf, "\t", 2);
8302 const gchar *folder = parts[0];
8303 int msgnum = parts[1] ? atoi(parts[1]):-1;
8305 if (folder && *folder && msgnum > -1) {
8306 FolderItem *item = folder_find_item_from_identifier(folder);
8307 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
8309 compose_reedit(info, FALSE);
8316 compose_clear_exit_drafts();
8319 static void compose_draft_cb(gpointer data, guint action, GtkWidget *widget)
8321 Compose *compose = (Compose *)data;
8325 MsgFlags flag = {0, 0};
8326 static gboolean lock = FALSE;
8327 MsgInfo *newmsginfo;
8329 gboolean target_locked = FALSE;
8333 draft = account_get_special_folder(compose->account, F_DRAFT);
8334 g_return_if_fail(draft != NULL);
8336 if (!g_mutex_trylock(compose->mutex)) {
8337 /* we don't want to lock the mutex once it's available,
8338 * because as the only other part of compose.c locking
8339 * it is compose_close - which means once unlocked,
8340 * the compose struct will be freed */
8341 debug_print("couldn't lock mutex, probably sending\n");
8347 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
8348 G_DIR_SEPARATOR, compose);
8349 if ((fp = g_fopen(tmp, "wb")) == NULL) {
8350 FILE_OP_ERROR(tmp, "fopen");
8354 /* chmod for security */
8355 if (change_file_mode_rw(fp, tmp) < 0) {
8356 FILE_OP_ERROR(tmp, "chmod");
8357 g_warning("can't change file mode\n");
8360 /* Save draft infos */
8361 fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id);
8362 fprintf(fp, "S:%s\n", compose->account->address);
8364 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
8365 gchar *savefolderid;
8367 savefolderid = gtk_editable_get_chars(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
8368 fprintf(fp, "SCF:%s\n", savefolderid);
8369 g_free(savefolderid);
8371 if (compose->return_receipt) {
8372 fprintf(fp, "RRCPT:1\n");
8374 if (compose->privacy_system) {
8375 fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing);
8376 fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption);
8377 fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system);
8380 /* Message-ID of message replying to */
8381 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
8384 folderid = folder_item_get_identifier(compose->replyinfo->folder);
8385 fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid);
8388 /* Message-ID of message forwarding to */
8389 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
8392 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
8393 fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid);
8397 /* end of headers */
8398 fprintf(fp, "X-Claws-End-Special-Headers: 1\n");
8400 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
8408 if (compose->targetinfo) {
8409 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
8410 flag.perm_flags = target_locked?MSG_LOCKED:0;
8412 flag.tmp_flags = MSG_DRAFT;
8414 folder_item_scan(draft);
8415 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
8418 if (action != COMPOSE_AUTO_SAVE)
8419 alertpanel_error(_("Could not save draft."));
8424 if (compose->mode == COMPOSE_REEDIT) {
8425 compose_remove_reedit_target(compose, TRUE);
8428 newmsginfo = folder_item_get_msginfo(draft, msgnum);
8430 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
8432 procmsg_msginfo_set_flags(newmsginfo, MSG_LOCKED, MSG_DRAFT);
8434 procmsg_msginfo_set_flags(newmsginfo, 0, MSG_DRAFT);
8435 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
8436 procmsg_msginfo_set_flags(newmsginfo, 0,
8437 MSG_HAS_ATTACHMENT);
8439 if (action == COMPOSE_DRAFT_FOR_EXIT) {
8440 compose_register_draft(newmsginfo);
8442 procmsg_msginfo_free(newmsginfo);
8445 folder_item_scan(draft);
8447 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
8449 g_mutex_unlock(compose->mutex); /* must be done before closing */
8450 compose_close(compose);
8456 path = folder_item_fetch_msg(draft, msgnum);
8458 debug_print("can't fetch %s:%d\n",draft->path, msgnum);
8461 if (g_stat(path, &s) < 0) {
8462 FILE_OP_ERROR(path, "stat");
8468 procmsg_msginfo_free(compose->targetinfo);
8469 compose->targetinfo = procmsg_msginfo_new();
8470 compose->targetinfo->msgnum = msgnum;
8471 compose->targetinfo->size = s.st_size;
8472 compose->targetinfo->mtime = s.st_mtime;
8473 compose->targetinfo->folder = draft;
8475 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
8476 compose->mode = COMPOSE_REEDIT;
8478 if (action == COMPOSE_AUTO_SAVE) {
8479 compose->autosaved_draft = compose->targetinfo;
8481 compose->modified = FALSE;
8482 compose_set_title(compose);
8486 g_mutex_unlock(compose->mutex);
8489 static void compose_attach_cb(gpointer data, guint action, GtkWidget *widget)
8491 Compose *compose = (Compose *)data;
8494 if (compose->redirect_filename != NULL)
8497 file_list = filesel_select_multiple_files_open(_("Select file"));
8502 for ( tmp = file_list; tmp; tmp = tmp->next) {
8503 gchar *file = (gchar *) tmp->data;
8504 gchar *utf8_filename = conv_filename_to_utf8(file);
8505 compose_attach_append(compose, file, utf8_filename, NULL);
8506 compose_changed_cb(NULL, compose);
8508 g_free(utf8_filename);
8510 g_list_free(file_list);
8514 static void compose_insert_file_cb(gpointer data, guint action,
8517 Compose *compose = (Compose *)data;
8520 file_list = filesel_select_multiple_files_open(_("Select file"));
8525 for ( tmp = file_list; tmp; tmp = tmp->next) {
8526 gchar *file = (gchar *) tmp->data;
8527 gchar *filedup = g_strdup(file);
8528 gchar *shortfile = g_path_get_basename(filedup);
8529 ComposeInsertResult res;
8531 res = compose_insert_file(compose, file);
8532 if (res == COMPOSE_INSERT_READ_ERROR) {
8533 alertpanel_error(_("File '%s' could not be read."), shortfile);
8534 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
8535 alertpanel_error(_("File '%s' contained invalid characters\n"
8536 "for the current encoding, insertion may be incorrect."), shortfile);
8542 g_list_free(file_list);
8546 static void compose_insert_sig_cb(gpointer data, guint action,
8549 Compose *compose = (Compose *)data;
8551 compose_insert_sig(compose, FALSE);
8554 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
8558 Compose *compose = (Compose *)data;
8560 gtkut_widget_get_uposition(widget, &x, &y);
8561 prefs_common.compose_x = x;
8562 prefs_common.compose_y = y;
8564 if (compose->sending || compose->updating)
8566 compose_close_cb(compose, 0, NULL);
8570 void compose_close_toolbar(Compose *compose)
8572 compose_close_cb(compose, 0, NULL);
8575 static void compose_close_cb(gpointer data, guint action, GtkWidget *widget)
8577 Compose *compose = (Compose *)data;
8581 if (compose->exteditor_tag != -1) {
8582 if (!compose_ext_editor_kill(compose))
8587 if (compose->modified) {
8588 val = alertpanel(_("Discard message"),
8589 _("This message has been modified. Discard it?"),
8590 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
8593 case G_ALERTDEFAULT:
8594 if (prefs_common.autosave)
8595 compose_remove_draft(compose);
8597 case G_ALERTALTERNATE:
8598 compose_draft_cb(data, COMPOSE_QUIT_EDITING, NULL);
8605 compose_close(compose);
8608 static void compose_set_encoding_cb(gpointer data, guint action,
8611 Compose *compose = (Compose *)data;
8613 if (GTK_CHECK_MENU_ITEM(widget)->active)
8614 compose->out_encoding = (CharSet)action;
8617 static void compose_address_cb(gpointer data, guint action, GtkWidget *widget)
8619 Compose *compose = (Compose *)data;
8621 addressbook_open(compose);
8624 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
8626 Compose *compose = (Compose *)data;
8631 tmpl = g_object_get_data(G_OBJECT(widget), "template");
8632 g_return_if_fail(tmpl != NULL);
8634 msg = g_strdup_printf(_("Do you want to apply the template '%s' ?"),
8636 val = alertpanel(_("Apply template"), msg,
8637 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
8640 if (val == G_ALERTDEFAULT)
8641 compose_template_apply(compose, tmpl, TRUE);
8642 else if (val == G_ALERTALTERNATE)
8643 compose_template_apply(compose, tmpl, FALSE);
8646 static void compose_ext_editor_cb(gpointer data, guint action,
8649 Compose *compose = (Compose *)data;
8651 compose_exec_ext_editor(compose);
8654 static void compose_undo_cb(Compose *compose)
8656 gboolean prev_autowrap = compose->autowrap;
8658 compose->autowrap = FALSE;
8659 undo_undo(compose->undostruct);
8660 compose->autowrap = prev_autowrap;
8663 static void compose_redo_cb(Compose *compose)
8665 gboolean prev_autowrap = compose->autowrap;
8667 compose->autowrap = FALSE;
8668 undo_redo(compose->undostruct);
8669 compose->autowrap = prev_autowrap;
8672 static void entry_cut_clipboard(GtkWidget *entry)
8674 if (GTK_IS_EDITABLE(entry))
8675 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
8676 else if (GTK_IS_TEXT_VIEW(entry))
8677 gtk_text_buffer_cut_clipboard(
8678 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
8679 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
8683 static void entry_copy_clipboard(GtkWidget *entry)
8685 if (GTK_IS_EDITABLE(entry))
8686 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
8687 else if (GTK_IS_TEXT_VIEW(entry))
8688 gtk_text_buffer_copy_clipboard(
8689 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
8690 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
8693 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
8694 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
8696 if (GTK_IS_TEXT_VIEW(entry)) {
8697 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
8698 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
8699 GtkTextIter start_iter, end_iter;
8701 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
8703 if (contents == NULL)
8706 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
8708 /* we shouldn't delete the selection when middle-click-pasting, or we
8709 * can't mid-click-paste our own selection */
8710 if (clip != GDK_SELECTION_PRIMARY) {
8711 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
8714 if (insert_place == NULL) {
8715 /* if insert_place isn't specified, insert at the cursor.
8716 * used for Ctrl-V pasting */
8717 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
8718 start = gtk_text_iter_get_offset(&start_iter);
8719 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
8721 /* if insert_place is specified, paste here.
8722 * used for mid-click-pasting */
8723 start = gtk_text_iter_get_offset(insert_place);
8724 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
8728 /* paste unwrapped: mark the paste so it's not wrapped later */
8729 end = start + strlen(contents);
8730 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
8731 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
8732 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
8733 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
8734 /* rewrap paragraph now (after a mid-click-paste) */
8735 mark_start = gtk_text_buffer_get_insert(buffer);
8736 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
8737 gtk_text_iter_backward_char(&start_iter);
8738 compose_beautify_paragraph(compose, &start_iter, TRUE);
8740 } else if (GTK_IS_EDITABLE(entry))
8741 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
8745 static void entry_allsel(GtkWidget *entry)
8747 if (GTK_IS_EDITABLE(entry))
8748 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
8749 else if (GTK_IS_TEXT_VIEW(entry)) {
8750 GtkTextIter startiter, enditer;
8751 GtkTextBuffer *textbuf;
8753 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
8754 gtk_text_buffer_get_start_iter(textbuf, &startiter);
8755 gtk_text_buffer_get_end_iter(textbuf, &enditer);
8757 gtk_text_buffer_move_mark_by_name(textbuf,
8758 "selection_bound", &startiter);
8759 gtk_text_buffer_move_mark_by_name(textbuf,
8760 "insert", &enditer);
8764 static void compose_cut_cb(Compose *compose)
8766 if (compose->focused_editable
8768 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
8771 entry_cut_clipboard(compose->focused_editable);
8774 static void compose_copy_cb(Compose *compose)
8776 if (compose->focused_editable
8778 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
8781 entry_copy_clipboard(compose->focused_editable);
8784 static void compose_paste_cb(Compose *compose)
8787 GtkTextBuffer *buffer;
8789 if (compose->focused_editable &&
8790 GTK_WIDGET_HAS_FOCUS(compose->focused_editable))
8791 entry_paste_clipboard(compose, compose->focused_editable,
8792 prefs_common.linewrap_pastes,
8793 GDK_SELECTION_CLIPBOARD, NULL);
8797 static void compose_paste_as_quote_cb(Compose *compose)
8799 gint wrap_quote = prefs_common.linewrap_quote;
8800 if (compose->focused_editable
8802 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
8805 /* let text_insert() (called directly or at a later time
8806 * after the gtk_editable_paste_clipboard) know that
8807 * text is to be inserted as a quotation. implemented
8808 * by using a simple refcount... */
8809 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
8810 G_OBJECT(compose->focused_editable),
8811 "paste_as_quotation"));
8812 g_object_set_data(G_OBJECT(compose->focused_editable),
8813 "paste_as_quotation",
8814 GINT_TO_POINTER(paste_as_quotation + 1));
8815 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
8816 entry_paste_clipboard(compose, compose->focused_editable,
8817 prefs_common.linewrap_pastes,
8818 GDK_SELECTION_CLIPBOARD, NULL);
8819 prefs_common.linewrap_quote = wrap_quote;
8823 static void compose_paste_no_wrap_cb(Compose *compose)
8826 GtkTextBuffer *buffer;
8828 if (compose->focused_editable
8830 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
8833 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
8834 GDK_SELECTION_CLIPBOARD, NULL);
8838 static void compose_paste_wrap_cb(Compose *compose)
8841 GtkTextBuffer *buffer;
8843 if (compose->focused_editable
8845 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
8848 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
8849 GDK_SELECTION_CLIPBOARD, NULL);
8853 static void compose_allsel_cb(Compose *compose)
8855 if (compose->focused_editable
8857 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
8860 entry_allsel(compose->focused_editable);
8863 static void textview_move_beginning_of_line (GtkTextView *text)
8865 GtkTextBuffer *buffer;
8869 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
8871 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8872 mark = gtk_text_buffer_get_insert(buffer);
8873 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
8874 gtk_text_iter_set_line_offset(&ins, 0);
8875 gtk_text_buffer_place_cursor(buffer, &ins);
8878 static void textview_move_forward_character (GtkTextView *text)
8880 GtkTextBuffer *buffer;
8884 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
8886 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8887 mark = gtk_text_buffer_get_insert(buffer);
8888 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
8889 if (gtk_text_iter_forward_cursor_position(&ins))
8890 gtk_text_buffer_place_cursor(buffer, &ins);
8893 static void textview_move_backward_character (GtkTextView *text)
8895 GtkTextBuffer *buffer;
8899 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
8901 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8902 mark = gtk_text_buffer_get_insert(buffer);
8903 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
8904 if (gtk_text_iter_backward_cursor_position(&ins))
8905 gtk_text_buffer_place_cursor(buffer, &ins);
8908 static void textview_move_forward_word (GtkTextView *text)
8910 GtkTextBuffer *buffer;
8915 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
8917 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8918 mark = gtk_text_buffer_get_insert(buffer);
8919 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
8920 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
8921 if (gtk_text_iter_forward_word_ends(&ins, count)) {
8922 gtk_text_iter_backward_word_start(&ins);
8923 gtk_text_buffer_place_cursor(buffer, &ins);
8927 static void textview_move_backward_word (GtkTextView *text)
8929 GtkTextBuffer *buffer;
8934 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
8936 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8937 mark = gtk_text_buffer_get_insert(buffer);
8938 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
8939 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
8940 if (gtk_text_iter_backward_word_starts(&ins, 1))
8941 gtk_text_buffer_place_cursor(buffer, &ins);
8944 static void textview_move_end_of_line (GtkTextView *text)
8946 GtkTextBuffer *buffer;
8950 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
8952 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8953 mark = gtk_text_buffer_get_insert(buffer);
8954 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
8955 if (gtk_text_iter_forward_to_line_end(&ins))
8956 gtk_text_buffer_place_cursor(buffer, &ins);
8959 static void textview_move_next_line (GtkTextView *text)
8961 GtkTextBuffer *buffer;
8966 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
8968 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8969 mark = gtk_text_buffer_get_insert(buffer);
8970 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
8971 offset = gtk_text_iter_get_line_offset(&ins);
8972 if (gtk_text_iter_forward_line(&ins)) {
8973 gtk_text_iter_set_line_offset(&ins, offset);
8974 gtk_text_buffer_place_cursor(buffer, &ins);
8978 static void textview_move_previous_line (GtkTextView *text)
8980 GtkTextBuffer *buffer;
8985 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
8987 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8988 mark = gtk_text_buffer_get_insert(buffer);
8989 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
8990 offset = gtk_text_iter_get_line_offset(&ins);
8991 if (gtk_text_iter_backward_line(&ins)) {
8992 gtk_text_iter_set_line_offset(&ins, offset);
8993 gtk_text_buffer_place_cursor(buffer, &ins);
8997 static void textview_delete_forward_character (GtkTextView *text)
8999 GtkTextBuffer *buffer;
9001 GtkTextIter ins, end_iter;
9003 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9005 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9006 mark = gtk_text_buffer_get_insert(buffer);
9007 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9009 if (gtk_text_iter_forward_char(&end_iter)) {
9010 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9014 static void textview_delete_backward_character (GtkTextView *text)
9016 GtkTextBuffer *buffer;
9018 GtkTextIter ins, end_iter;
9020 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9022 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9023 mark = gtk_text_buffer_get_insert(buffer);
9024 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9026 if (gtk_text_iter_backward_char(&end_iter)) {
9027 gtk_text_buffer_delete(buffer, &end_iter, &ins);
9031 static void textview_delete_forward_word (GtkTextView *text)
9033 GtkTextBuffer *buffer;
9035 GtkTextIter ins, end_iter;
9037 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9039 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9040 mark = gtk_text_buffer_get_insert(buffer);
9041 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9043 if (gtk_text_iter_forward_word_end(&end_iter)) {
9044 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9048 static void textview_delete_backward_word (GtkTextView *text)
9050 GtkTextBuffer *buffer;
9052 GtkTextIter ins, end_iter;
9054 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9056 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9057 mark = gtk_text_buffer_get_insert(buffer);
9058 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9060 if (gtk_text_iter_backward_word_start(&end_iter)) {
9061 gtk_text_buffer_delete(buffer, &end_iter, &ins);
9065 static void textview_delete_line (GtkTextView *text)
9067 GtkTextBuffer *buffer;
9069 GtkTextIter ins, start_iter, end_iter;
9072 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9074 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9075 mark = gtk_text_buffer_get_insert(buffer);
9076 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9079 gtk_text_iter_set_line_offset(&start_iter, 0);
9082 if (gtk_text_iter_ends_line(&end_iter))
9083 found = gtk_text_iter_forward_char(&end_iter);
9085 found = gtk_text_iter_forward_to_line_end(&end_iter);
9088 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
9091 static void textview_delete_to_line_end (GtkTextView *text)
9093 GtkTextBuffer *buffer;
9095 GtkTextIter ins, end_iter;
9098 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9100 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9101 mark = gtk_text_buffer_get_insert(buffer);
9102 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9104 if (gtk_text_iter_ends_line(&end_iter))
9105 found = gtk_text_iter_forward_char(&end_iter);
9107 found = gtk_text_iter_forward_to_line_end(&end_iter);
9109 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9112 static void compose_advanced_action_cb(Compose *compose,
9113 ComposeCallAdvancedAction action)
9115 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9117 void (*do_action) (GtkTextView *text);
9118 } action_table[] = {
9119 {textview_move_beginning_of_line},
9120 {textview_move_forward_character},
9121 {textview_move_backward_character},
9122 {textview_move_forward_word},
9123 {textview_move_backward_word},
9124 {textview_move_end_of_line},
9125 {textview_move_next_line},
9126 {textview_move_previous_line},
9127 {textview_delete_forward_character},
9128 {textview_delete_backward_character},
9129 {textview_delete_forward_word},
9130 {textview_delete_backward_word},
9131 {textview_delete_line},
9132 {NULL}, /* gtk_stext_delete_line_n */
9133 {textview_delete_to_line_end}
9136 if (!GTK_WIDGET_HAS_FOCUS(text)) return;
9138 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
9139 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
9140 if (action_table[action].do_action)
9141 action_table[action].do_action(text);
9143 g_warning("Not implemented yet.");
9147 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
9151 if (GTK_IS_EDITABLE(widget)) {
9152 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
9153 gtk_editable_set_position(GTK_EDITABLE(widget),
9156 if (widget->parent && widget->parent->parent
9157 && widget->parent->parent->parent) {
9158 if (GTK_IS_SCROLLED_WINDOW(widget->parent->parent->parent)) {
9159 gint y = widget->allocation.y;
9160 gint height = widget->allocation.height;
9161 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
9162 (GTK_SCROLLED_WINDOW(widget->parent->parent->parent));
9164 if (y < (int)shown->value) {
9165 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown), y - 1);
9167 if (y + height > (int)shown->value + (int)shown->page_size) {
9168 if (y - height - 1 < (int)shown->upper - (int)shown->page_size) {
9169 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
9170 y + height - (int)shown->page_size - 1);
9172 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
9173 (int)shown->upper - (int)shown->page_size - 1);
9180 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
9181 compose->focused_editable = widget;
9184 if (GTK_IS_TEXT_VIEW(widget)
9185 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
9186 gtk_widget_ref(compose->notebook);
9187 gtk_widget_ref(compose->edit_vbox);
9188 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
9189 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
9190 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
9191 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
9192 gtk_widget_unref(compose->notebook);
9193 gtk_widget_unref(compose->edit_vbox);
9194 g_signal_handlers_block_by_func(G_OBJECT(widget),
9195 G_CALLBACK(compose_grab_focus_cb),
9197 gtk_widget_grab_focus(widget);
9198 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
9199 G_CALLBACK(compose_grab_focus_cb),
9201 } else if (!GTK_IS_TEXT_VIEW(widget)
9202 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
9203 gtk_widget_ref(compose->notebook);
9204 gtk_widget_ref(compose->edit_vbox);
9205 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
9206 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
9207 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
9208 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
9209 gtk_widget_unref(compose->notebook);
9210 gtk_widget_unref(compose->edit_vbox);
9211 g_signal_handlers_block_by_func(G_OBJECT(widget),
9212 G_CALLBACK(compose_grab_focus_cb),
9214 gtk_widget_grab_focus(widget);
9215 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
9216 G_CALLBACK(compose_grab_focus_cb),
9222 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
9224 compose->modified = TRUE;
9226 compose_set_title(compose);
9230 static void compose_wrap_cb(gpointer data, guint action, GtkWidget *widget)
9232 Compose *compose = (Compose *)data;
9235 compose_wrap_all_full(compose, TRUE);
9237 compose_beautify_paragraph(compose, NULL, TRUE);
9240 static void compose_find_cb(gpointer data, guint action, GtkWidget *widget)
9242 Compose *compose = (Compose *)data;
9244 message_search_compose(compose);
9247 static void compose_toggle_autowrap_cb(gpointer data, guint action,
9250 Compose *compose = (Compose *)data;
9251 compose->autowrap = GTK_CHECK_MENU_ITEM(widget)->active;
9252 if (compose->autowrap)
9253 compose_wrap_all_full(compose, TRUE);
9254 compose->autowrap = GTK_CHECK_MENU_ITEM(widget)->active;
9257 static void compose_toggle_sign_cb(gpointer data, guint action,
9260 Compose *compose = (Compose *)data;
9262 if (GTK_CHECK_MENU_ITEM(widget)->active)
9263 compose->use_signing = TRUE;
9265 compose->use_signing = FALSE;
9268 static void compose_toggle_encrypt_cb(gpointer data, guint action,
9271 Compose *compose = (Compose *)data;
9273 if (GTK_CHECK_MENU_ITEM(widget)->active)
9274 compose->use_encryption = TRUE;
9276 compose->use_encryption = FALSE;
9279 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
9281 g_free(compose->privacy_system);
9283 compose->privacy_system = g_strdup(account->default_privacy_system);
9284 compose_update_privacy_system_menu_item(compose, warn);
9287 static void compose_toggle_ruler_cb(gpointer data, guint action,
9290 Compose *compose = (Compose *)data;
9292 if (GTK_CHECK_MENU_ITEM(widget)->active) {
9293 gtk_widget_show(compose->ruler_hbox);
9294 prefs_common.show_ruler = TRUE;
9296 gtk_widget_hide(compose->ruler_hbox);
9297 gtk_widget_queue_resize(compose->edit_vbox);
9298 prefs_common.show_ruler = FALSE;
9302 static void compose_attach_drag_received_cb (GtkWidget *widget,
9303 GdkDragContext *context,
9306 GtkSelectionData *data,
9311 Compose *compose = (Compose *)user_data;
9314 if (gdk_atom_name(data->type) &&
9315 !strcmp(gdk_atom_name(data->type), "text/uri-list")
9316 && gtk_drag_get_source_widget(context) !=
9317 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
9318 list = uri_list_extract_filenames((const gchar *)data->data);
9319 for (tmp = list; tmp != NULL; tmp = tmp->next) {
9320 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
9321 compose_attach_append
9322 (compose, (const gchar *)tmp->data,
9323 utf8_filename, NULL);
9324 g_free(utf8_filename);
9326 if (list) compose_changed_cb(NULL, compose);
9327 list_free_strings(list);
9329 } else if (gtk_drag_get_source_widget(context)
9330 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
9331 /* comes from our summaryview */
9332 SummaryView * summaryview = NULL;
9333 GSList * list = NULL, *cur = NULL;
9335 if (mainwindow_get_mainwindow())
9336 summaryview = mainwindow_get_mainwindow()->summaryview;
9339 list = summary_get_selected_msg_list(summaryview);
9341 for (cur = list; cur; cur = cur->next) {
9342 MsgInfo *msginfo = (MsgInfo *)cur->data;
9345 file = procmsg_get_message_file_full(msginfo,
9348 compose_attach_append(compose, (const gchar *)file,
9349 (const gchar *)file, "message/rfc822");
9357 static gboolean compose_drag_drop(GtkWidget *widget,
9358 GdkDragContext *drag_context,
9360 guint time, gpointer user_data)
9362 /* not handling this signal makes compose_insert_drag_received_cb
9367 static void compose_insert_drag_received_cb (GtkWidget *widget,
9368 GdkDragContext *drag_context,
9371 GtkSelectionData *data,
9376 Compose *compose = (Compose *)user_data;
9379 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
9381 if (gdk_atom_name(data->type) && !strcmp(gdk_atom_name(data->type), "text/uri-list")) {
9382 AlertValue val = G_ALERTDEFAULT;
9384 switch (prefs_common.compose_dnd_mode) {
9385 case COMPOSE_DND_ASK:
9386 val = alertpanel_full(_("Insert or attach?"),
9387 _("Do you want to insert the contents of the file(s) "
9388 "into the message body, or attach it to the email?"),
9389 GTK_STOCK_CANCEL, _("+_Insert"), _("_Attach"),
9390 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
9392 case COMPOSE_DND_INSERT:
9393 val = G_ALERTALTERNATE;
9395 case COMPOSE_DND_ATTACH:
9399 /* unexpected case */
9400 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
9403 if (val & G_ALERTDISABLE) {
9404 val &= ~G_ALERTDISABLE;
9405 /* remember what action to perform by default, only if we don't click Cancel */
9406 if (val == G_ALERTALTERNATE)
9407 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
9408 else if (val == G_ALERTOTHER)
9409 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
9412 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
9413 gtk_drag_finish(drag_context, FALSE, FALSE, time);
9415 } else if (val == G_ALERTOTHER) {
9416 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
9419 list = uri_list_extract_filenames((const gchar *)data->data);
9420 for (tmp = list; tmp != NULL; tmp = tmp->next) {
9421 compose_insert_file(compose, (const gchar *)tmp->data);
9423 list_free_strings(list);
9425 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9428 #if GTK_CHECK_VERSION(2, 8, 0)
9429 /* do nothing, handled by GTK */
9431 gchar *tmpfile = get_tmp_file();
9432 str_write_to_file((const gchar *)data->data, tmpfile);
9433 compose_insert_file(compose, tmpfile);
9436 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9440 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9443 static void compose_header_drag_received_cb (GtkWidget *widget,
9444 GdkDragContext *drag_context,
9447 GtkSelectionData *data,
9452 GtkEditable *entry = (GtkEditable *)user_data;
9453 gchar *email = (gchar *)data->data;
9455 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
9458 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
9459 gchar *decoded=g_new(gchar, strlen(email));
9462 email += strlen("mailto:");
9463 decode_uri(decoded, email); /* will fit */
9464 gtk_editable_delete_text(entry, 0, -1);
9465 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
9466 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9470 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9473 static void compose_toggle_return_receipt_cb(gpointer data, guint action,
9476 Compose *compose = (Compose *)data;
9478 if (GTK_CHECK_MENU_ITEM(widget)->active)
9479 compose->return_receipt = TRUE;
9481 compose->return_receipt = FALSE;
9484 static void compose_toggle_remove_refs_cb(gpointer data, guint action,
9487 Compose *compose = (Compose *)data;
9489 if (GTK_CHECK_MENU_ITEM(widget)->active)
9490 compose->remove_references = TRUE;
9492 compose->remove_references = FALSE;
9495 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
9497 ComposeHeaderEntry *headerentry)
9499 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
9500 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
9501 !(event->state & GDK_MODIFIER_MASK) &&
9502 (event->keyval == GDK_BackSpace) &&
9503 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
9504 gtk_container_remove
9505 (GTK_CONTAINER(headerentry->compose->header_table),
9506 headerentry->combo);
9507 gtk_container_remove
9508 (GTK_CONTAINER(headerentry->compose->header_table),
9509 headerentry->entry);
9510 headerentry->compose->header_list =
9511 g_slist_remove(headerentry->compose->header_list,
9513 g_free(headerentry);
9514 } else if (event->keyval == GDK_Tab) {
9515 if (headerentry->compose->header_last == headerentry) {
9516 /* Override default next focus, and give it to subject_entry
9517 * instead of notebook tabs
9519 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
9520 gtk_widget_grab_focus(headerentry->compose->subject_entry);
9527 static gboolean compose_headerentry_changed_cb(GtkWidget *entry,
9528 ComposeHeaderEntry *headerentry)
9530 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
9531 compose_create_header_entry(headerentry->compose);
9532 g_signal_handlers_disconnect_matched
9533 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
9534 0, 0, NULL, NULL, headerentry);
9536 /* Automatically scroll down */
9537 compose_show_first_last_header(headerentry->compose, FALSE);
9543 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
9545 GtkAdjustment *vadj;
9547 g_return_if_fail(compose);
9548 g_return_if_fail(GTK_IS_WIDGET(compose->header_table));
9549 g_return_if_fail(GTK_IS_VIEWPORT(compose->header_table->parent));
9551 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(compose->header_table->parent));
9552 gtk_adjustment_set_value(vadj, (show_first ? vadj->lower : vadj->upper));
9555 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
9556 const gchar *text, gint len, Compose *compose)
9558 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
9559 (G_OBJECT(compose->text), "paste_as_quotation"));
9562 g_return_if_fail(text != NULL);
9564 g_signal_handlers_block_by_func(G_OBJECT(buffer),
9565 G_CALLBACK(text_inserted),
9567 if (paste_as_quotation) {
9574 new_text = g_strndup(text, len);
9576 qmark = compose_quote_char_from_context(compose);
9578 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
9579 gtk_text_buffer_place_cursor(buffer, iter);
9581 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
9582 _("Quote format error at line %d."));
9583 quote_fmt_reset_vartable();
9585 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
9586 GINT_TO_POINTER(paste_as_quotation - 1));
9588 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
9589 gtk_text_buffer_place_cursor(buffer, iter);
9591 if (strcmp(text, "\n") || automatic_break
9592 || gtk_text_iter_starts_line(iter))
9593 gtk_text_buffer_insert(buffer, iter, text, len);
9595 debug_print("insert nowrap \\n\n");
9596 gtk_text_buffer_insert_with_tags_by_name(buffer,
9597 iter, text, len, "no_join", NULL);
9601 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
9603 compose_beautify_paragraph(compose, iter, FALSE);
9605 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
9606 gtk_text_buffer_delete_mark(buffer, mark);
9608 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
9609 G_CALLBACK(text_inserted),
9611 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
9613 if (prefs_common.autosave &&
9614 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0)
9615 compose->draft_timeout_tag = g_timeout_add
9616 (500, (GtkFunction) compose_defer_auto_save_draft, compose);
9618 static gint compose_defer_auto_save_draft(Compose *compose)
9620 compose->draft_timeout_tag = -1;
9621 compose_draft_cb((gpointer)compose, COMPOSE_AUTO_SAVE, NULL);
9626 static void compose_check_all(Compose *compose)
9628 if (compose->gtkaspell)
9629 gtkaspell_check_all(compose->gtkaspell);
9632 static void compose_highlight_all(Compose *compose)
9634 if (compose->gtkaspell)
9635 gtkaspell_highlight_all(compose->gtkaspell);
9638 static void compose_check_backwards(Compose *compose)
9640 if (compose->gtkaspell)
9641 gtkaspell_check_backwards(compose->gtkaspell);
9643 GtkItemFactory *ifactory;
9644 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
9645 menu_set_sensitive(ifactory, "/Edit/Check backwards misspelled word", FALSE);
9646 menu_set_sensitive(ifactory, "/Edit/Forward to next misspelled word", FALSE);
9650 static void compose_check_forwards_go(Compose *compose)
9652 if (compose->gtkaspell)
9653 gtkaspell_check_forwards_go(compose->gtkaspell);
9655 GtkItemFactory *ifactory;
9656 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
9657 menu_set_sensitive(ifactory, "/Edit/Check backwards misspelled word", FALSE);
9658 menu_set_sensitive(ifactory, "/Edit/Forward to next misspelled word", FALSE);
9664 *\brief Guess originating forward account from MsgInfo and several
9665 * "common preference" settings. Return NULL if no guess.
9667 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
9669 PrefsAccount *account = NULL;
9671 g_return_val_if_fail(msginfo, NULL);
9672 g_return_val_if_fail(msginfo->folder, NULL);
9673 g_return_val_if_fail(msginfo->folder->prefs, NULL);
9675 if (msginfo->folder->prefs->enable_default_account)
9676 account = account_find_from_id(msginfo->folder->prefs->default_account);
9679 account = msginfo->folder->folder->account;
9681 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
9683 Xstrdup_a(to, msginfo->to, return NULL);
9684 extract_address(to);
9685 account = account_find_from_address(to);
9688 if (!account && prefs_common.forward_account_autosel) {
9690 if (!procheader_get_header_from_msginfo
9691 (msginfo, cc,sizeof cc , "Cc:")) {
9692 gchar *buf = cc + strlen("Cc:");
9693 extract_address(buf);
9694 account = account_find_from_address(buf);
9698 if (!account && prefs_common.forward_account_autosel) {
9699 gchar deliveredto[BUFFSIZE];
9700 if (!procheader_get_header_from_msginfo
9701 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
9702 gchar *buf = deliveredto + strlen("Delivered-To:");
9703 extract_address(buf);
9704 account = account_find_from_address(buf);
9711 gboolean compose_close(Compose *compose)
9715 if (!g_mutex_trylock(compose->mutex)) {
9716 /* we have to wait for the (possibly deferred by auto-save)
9717 * drafting to be done, before destroying the compose under
9719 debug_print("waiting for drafting to finish...\n");
9720 g_timeout_add (500, (GSourceFunc) compose_close, compose);
9723 g_return_val_if_fail(compose, FALSE);
9724 gtkut_widget_get_uposition(compose->window, &x, &y);
9725 prefs_common.compose_x = x;
9726 prefs_common.compose_y = y;
9727 g_mutex_unlock(compose->mutex);
9728 compose_destroy(compose);
9733 * Add entry field for each address in list.
9734 * \param compose E-Mail composition object.
9735 * \param listAddress List of (formatted) E-Mail addresses.
9737 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
9742 addr = ( gchar * ) node->data;
9743 compose_entry_append( compose, addr, COMPOSE_TO );
9744 node = g_list_next( node );
9748 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
9749 guint action, gboolean opening_multiple)
9752 GSList *new_msglist = NULL;
9753 MsgInfo *tmp_msginfo = NULL;
9754 gboolean originally_enc = FALSE;
9755 Compose *compose = NULL;
9757 g_return_if_fail(msgview != NULL);
9759 g_return_if_fail(msginfo_list != NULL);
9761 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
9762 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
9763 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
9765 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
9766 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
9767 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
9768 orig_msginfo, mimeinfo);
9769 if (tmp_msginfo != NULL) {
9770 new_msglist = g_slist_append(NULL, tmp_msginfo);
9771 if (procmime_msginfo_is_encrypted(orig_msginfo)) {
9772 originally_enc = TRUE;
9774 tmp_msginfo->folder = orig_msginfo->folder;
9775 tmp_msginfo->msgnum = orig_msginfo->msgnum;
9780 if (!opening_multiple)
9781 body = messageview_get_selection(msgview);
9784 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
9785 procmsg_msginfo_free(tmp_msginfo);
9786 g_slist_free(new_msglist);
9788 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
9790 if (originally_enc) {
9791 compose_force_encryption(compose, compose->account, FALSE);
9797 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
9800 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
9801 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
9802 GSList *cur = msginfo_list;
9803 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
9804 "messages. Opening the windows "
9805 "could take some time. Do you "
9806 "want to continue?"),
9807 g_slist_length(msginfo_list));
9808 if (g_slist_length(msginfo_list) > 9
9809 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
9810 != G_ALERTALTERNATE) {
9815 /* We'll open multiple compose windows */
9816 /* let the WM place the next windows */
9817 compose_force_window_origin = FALSE;
9818 for (; cur; cur = cur->next) {
9820 tmplist.data = cur->data;
9821 tmplist.next = NULL;
9822 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
9824 compose_force_window_origin = TRUE;
9826 /* forwarding multiple mails as attachments is done via a
9827 * single compose window */
9828 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
9832 void compose_set_position(Compose *compose, gint pos)
9834 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9836 gtkut_text_view_set_position(text, pos);
9839 gboolean compose_search_string(Compose *compose,
9840 const gchar *str, gboolean case_sens)
9842 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9844 return gtkut_text_view_search_string(text, str, case_sens);
9847 gboolean compose_search_string_backward(Compose *compose,
9848 const gchar *str, gboolean case_sens)
9850 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9852 return gtkut_text_view_search_string_backward(text, str, case_sens);
9855 /* allocate a msginfo structure and populate its data from a compose data structure */
9856 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
9858 MsgInfo *newmsginfo;
9860 gchar buf[BUFFSIZE];
9862 g_return_val_if_fail( compose != NULL, NULL );
9864 newmsginfo = procmsg_msginfo_new();
9867 get_rfc822_date(buf, sizeof(buf));
9868 newmsginfo->date = g_strdup(buf);
9871 if (compose->from_name) {
9872 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
9873 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
9877 if (compose->subject_entry)
9878 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
9880 /* to, cc, reply-to, newsgroups */
9881 for (list = compose->header_list; list; list = list->next) {
9882 gchar *header = gtk_editable_get_chars(
9884 GTK_COMBO(((ComposeHeaderEntry *)list->data)->combo)->entry), 0, -1);
9885 gchar *entry = gtk_editable_get_chars(
9886 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
9888 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
9889 if ( newmsginfo->to == NULL ) {
9890 newmsginfo->to = g_strdup(entry);
9891 } else if (entry && *entry) {
9892 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
9893 g_free(newmsginfo->to);
9894 newmsginfo->to = tmp;
9897 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
9898 if ( newmsginfo->cc == NULL ) {
9899 newmsginfo->cc = g_strdup(entry);
9900 } else if (entry && *entry) {
9901 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
9902 g_free(newmsginfo->cc);
9903 newmsginfo->cc = tmp;
9906 if ( strcasecmp(header,
9907 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
9908 if ( newmsginfo->newsgroups == NULL ) {
9909 newmsginfo->newsgroups = g_strdup(entry);
9910 } else if (entry && *entry) {
9911 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
9912 g_free(newmsginfo->newsgroups);
9913 newmsginfo->newsgroups = tmp;
9921 /* other data is unset */
9927 /* update compose's dictionaries from folder dict settings */
9928 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
9929 FolderItem *folder_item)
9931 g_return_if_fail(compose != NULL);
9933 if (compose->gtkaspell && folder_item && folder_item->prefs) {
9934 FolderItemPrefs *prefs = folder_item->prefs;
9936 if (prefs->enable_default_dictionary)
9937 gtkaspell_change_dict(compose->gtkaspell,
9938 prefs->default_dictionary, FALSE);
9939 if (folder_item->prefs->enable_default_alt_dictionary)
9940 gtkaspell_change_alt_dict(compose->gtkaspell,
9941 prefs->default_alt_dictionary);
9942 if (prefs->enable_default_dictionary
9943 || prefs->enable_default_alt_dictionary)
9944 compose_spell_menu_changed(compose);