2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2007 Hiroyuki Yamamoto and the Claws Mail team
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
26 #ifndef PANGO_ENABLE_ENGINE
27 # define PANGO_ENABLE_ENGINE
31 #include <glib/gi18n.h>
32 #include <gdk/gdkkeysyms.h>
33 #include <gtk/gtkmain.h>
34 #include <gtk/gtkmenu.h>
35 #include <gtk/gtkmenuitem.h>
36 #include <gtk/gtkitemfactory.h>
37 #include <gtk/gtkcheckmenuitem.h>
38 #include <gtk/gtkoptionmenu.h>
39 #include <gtk/gtkwidget.h>
40 #include <gtk/gtkvpaned.h>
41 #include <gtk/gtkentry.h>
42 #include <gtk/gtkeditable.h>
43 #include <gtk/gtkwindow.h>
44 #include <gtk/gtksignal.h>
45 #include <gtk/gtkvbox.h>
46 #include <gtk/gtkcontainer.h>
47 #include <gtk/gtkhandlebox.h>
48 #include <gtk/gtktoolbar.h>
49 #include <gtk/gtktable.h>
50 #include <gtk/gtkhbox.h>
51 #include <gtk/gtklabel.h>
52 #include <gtk/gtkscrolledwindow.h>
53 #include <gtk/gtktreeview.h>
54 #include <gtk/gtkliststore.h>
55 #include <gtk/gtktreeselection.h>
56 #include <gtk/gtktreemodel.h>
58 #include <gtk/gtkdnd.h>
59 #include <gtk/gtkclipboard.h>
60 #include <pango/pango-break.h>
65 #include <sys/types.h>
71 # include <sys/wait.h>
75 #ifndef G_OS_WIN32 /* fixme we should have a configure test. */
79 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
86 #include "mainwindow.h"
88 #include "addressbook.h"
89 #include "folderview.h"
92 #include "stock_pixmap.h"
93 #include "send_message.h"
96 #include "customheader.h"
97 #include "prefs_common.h"
98 #include "prefs_account.h"
102 #include "procheader.h"
103 #include "procmime.h"
104 #include "statusbar.h"
107 #include "quoted-printable.h"
108 #include "codeconv.h"
110 #include "gtkutils.h"
112 #include "alertpanel.h"
113 #include "manage_window.h"
114 #include "gtkshruler.h"
116 #include "addr_compl.h"
117 #include "quote_fmt.h"
119 #include "foldersel.h"
122 #include "message_search.h"
123 #include "combobox.h"
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->draft_timeout_tag = -1;
1146 compose->modified = FALSE;
1147 compose_set_title(compose);
1151 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1152 gboolean override_pref)
1154 gchar *privacy = NULL;
1156 g_return_if_fail(compose != NULL);
1157 g_return_if_fail(account != NULL);
1159 if (override_pref == FALSE && account->default_encrypt_reply == FALSE)
1162 if (account->default_privacy_system
1163 && strlen(account->default_privacy_system)) {
1164 privacy = account->default_privacy_system;
1166 GSList *privacy_avail = privacy_get_system_ids();
1167 if (privacy_avail && g_slist_length(privacy_avail)) {
1168 privacy = (gchar *)(privacy_avail->data);
1171 if (privacy != NULL) {
1172 if (compose->privacy_system == NULL)
1173 compose->privacy_system = g_strdup(privacy);
1174 compose_update_privacy_system_menu_item(compose, FALSE);
1175 compose_use_encryption(compose, TRUE);
1179 static void compose_force_signing(Compose *compose, PrefsAccount *account)
1181 gchar *privacy = NULL;
1183 if (account->default_privacy_system
1184 && strlen(account->default_privacy_system)) {
1185 privacy = account->default_privacy_system;
1187 GSList *privacy_avail = privacy_get_system_ids();
1188 if (privacy_avail && g_slist_length(privacy_avail)) {
1189 privacy = (gchar *)(privacy_avail->data);
1192 if (privacy != NULL) {
1193 if (compose->privacy_system == NULL)
1194 compose->privacy_system = g_strdup(privacy);
1195 compose_update_privacy_system_menu_item(compose, FALSE);
1196 compose_use_signing(compose, TRUE);
1200 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1204 Compose *compose = NULL;
1205 GtkItemFactory *ifactory = NULL;
1207 g_return_val_if_fail(msginfo_list != NULL, NULL);
1209 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1210 g_return_val_if_fail(msginfo != NULL, NULL);
1212 list_len = g_slist_length(msginfo_list);
1216 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1217 FALSE, prefs_common.default_reply_list, FALSE, body);
1219 case COMPOSE_REPLY_WITH_QUOTE:
1220 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1221 FALSE, prefs_common.default_reply_list, FALSE, body);
1223 case COMPOSE_REPLY_WITHOUT_QUOTE:
1224 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1225 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1227 case COMPOSE_REPLY_TO_SENDER:
1228 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1229 FALSE, FALSE, TRUE, body);
1231 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1232 compose = compose_followup_and_reply_to(msginfo,
1233 COMPOSE_QUOTE_CHECK,
1234 FALSE, FALSE, body);
1236 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1237 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1238 FALSE, FALSE, TRUE, body);
1240 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1241 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1242 FALSE, FALSE, TRUE, NULL);
1244 case COMPOSE_REPLY_TO_ALL:
1245 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1246 TRUE, FALSE, FALSE, body);
1248 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1249 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1250 TRUE, FALSE, FALSE, body);
1252 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1253 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1254 TRUE, FALSE, FALSE, NULL);
1256 case COMPOSE_REPLY_TO_LIST:
1257 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1258 FALSE, TRUE, FALSE, body);
1260 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1261 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1262 FALSE, TRUE, FALSE, body);
1264 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1265 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1266 FALSE, TRUE, FALSE, NULL);
1268 case COMPOSE_FORWARD:
1269 if (prefs_common.forward_as_attachment) {
1270 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1273 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1277 case COMPOSE_FORWARD_INLINE:
1278 /* check if we reply to more than one Message */
1279 if (list_len == 1) {
1280 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1283 /* more messages FALL THROUGH */
1284 case COMPOSE_FORWARD_AS_ATTACH:
1285 compose = compose_forward_multiple(NULL, msginfo_list);
1287 case COMPOSE_REDIRECT:
1288 compose = compose_redirect(NULL, msginfo, FALSE);
1291 g_warning("compose_reply_mode(): invalid Compose Mode: %d\n", mode);
1294 ifactory = gtk_item_factory_from_widget(compose->menubar);
1296 compose->rmode = mode;
1297 switch (compose->rmode) {
1299 case COMPOSE_REPLY_WITH_QUOTE:
1300 case COMPOSE_REPLY_WITHOUT_QUOTE:
1301 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1302 debug_print("reply mode Normal\n");
1303 menu_set_active(ifactory, "/Options/Reply mode/Normal", TRUE);
1304 compose_reply_change_mode(compose, COMPOSE_REPLY, NULL); /* force update */
1306 case COMPOSE_REPLY_TO_SENDER:
1307 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1308 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1309 debug_print("reply mode Sender\n");
1310 menu_set_active(ifactory, "/Options/Reply mode/Sender", TRUE);
1312 case COMPOSE_REPLY_TO_ALL:
1313 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1314 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1315 debug_print("reply mode All\n");
1316 menu_set_active(ifactory, "/Options/Reply mode/All", TRUE);
1318 case COMPOSE_REPLY_TO_LIST:
1319 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1320 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1321 debug_print("reply mode List\n");
1322 menu_set_active(ifactory, "/Options/Reply mode/Mailing-list", TRUE);
1330 static Compose *compose_reply(MsgInfo *msginfo,
1331 ComposeQuoteMode quote_mode,
1337 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1338 to_sender, FALSE, body);
1341 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1342 ComposeQuoteMode quote_mode,
1347 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1348 to_sender, TRUE, body);
1351 static void compose_extract_original_charset(Compose *compose)
1353 MsgInfo *info = NULL;
1354 if (compose->replyinfo) {
1355 info = compose->replyinfo;
1356 } else if (compose->fwdinfo) {
1357 info = compose->fwdinfo;
1358 } else if (compose->targetinfo) {
1359 info = compose->targetinfo;
1362 MimeInfo *mimeinfo = procmime_scan_message(info);
1363 MimeInfo *partinfo = mimeinfo;
1364 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1365 partinfo = procmime_mimeinfo_next(partinfo);
1367 compose->orig_charset =
1368 g_strdup(procmime_mimeinfo_get_parameter(
1369 partinfo, "charset"));
1371 procmime_mimeinfo_free_all(mimeinfo);
1375 #define SIGNAL_BLOCK(buffer) { \
1376 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1377 G_CALLBACK(compose_changed_cb), \
1379 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1380 G_CALLBACK(text_inserted), \
1384 #define SIGNAL_UNBLOCK(buffer) { \
1385 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1386 G_CALLBACK(compose_changed_cb), \
1388 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1389 G_CALLBACK(text_inserted), \
1393 static Compose *compose_generic_reply(MsgInfo *msginfo,
1394 ComposeQuoteMode quote_mode,
1395 gboolean to_all, gboolean to_ml,
1397 gboolean followup_and_reply_to,
1400 GtkItemFactory *ifactory;
1402 PrefsAccount *account = NULL;
1403 GtkTextView *textview;
1404 GtkTextBuffer *textbuf;
1405 gboolean quote = FALSE;
1406 const gchar *qmark = NULL;
1407 const gchar *body_fmt = NULL;
1409 g_return_val_if_fail(msginfo != NULL, NULL);
1410 g_return_val_if_fail(msginfo->folder != NULL, NULL);
1412 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1414 g_return_val_if_fail(account != NULL, NULL);
1416 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1418 compose->updating = TRUE;
1420 ifactory = gtk_item_factory_from_widget(compose->menubar);
1422 menu_set_active(ifactory, "/Options/Remove references", FALSE);
1423 menu_set_sensitive(ifactory, "/Options/Remove references", TRUE);
1425 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1426 if (!compose->replyinfo)
1427 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1429 compose_extract_original_charset(compose);
1431 if (msginfo->folder && msginfo->folder->ret_rcpt)
1432 menu_set_active(ifactory, "/Options/Request Return Receipt", TRUE);
1434 /* Set save folder */
1435 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1436 gchar *folderidentifier;
1438 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1439 folderidentifier = folder_item_get_identifier(msginfo->folder);
1440 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
1441 g_free(folderidentifier);
1444 if (compose_parse_header(compose, msginfo) < 0) return NULL;
1446 textview = (GTK_TEXT_VIEW(compose->text));
1447 textbuf = gtk_text_view_get_buffer(textview);
1448 compose_create_tags(textview, compose);
1450 undo_block(compose->undostruct);
1452 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1455 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1456 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1457 /* use the reply format of folder (if enabled), or the account's one
1458 (if enabled) or fallback to the global reply format, which is always
1459 enabled (even if empty), and use the relevant quotemark */
1461 if (msginfo->folder && msginfo->folder->prefs &&
1462 msginfo->folder->prefs->reply_with_format) {
1463 qmark = msginfo->folder->prefs->reply_quotemark;
1464 body_fmt = msginfo->folder->prefs->reply_body_format;
1466 } else if (account->reply_with_format) {
1467 qmark = account->reply_quotemark;
1468 body_fmt = account->reply_body_format;
1471 qmark = prefs_common.quotemark;
1472 body_fmt = prefs_common.quotefmt;
1477 /* empty quotemark is not allowed */
1478 if (qmark == NULL || *qmark == '\0')
1480 compose_quote_fmt(compose, compose->replyinfo,
1481 body_fmt, qmark, body, FALSE, TRUE,
1482 _("Message reply format error at line %d."));
1483 quote_fmt_reset_vartable();
1485 if (procmime_msginfo_is_encrypted(compose->replyinfo)) {
1486 compose_force_encryption(compose, account, FALSE);
1489 SIGNAL_BLOCK(textbuf);
1491 if (account->auto_sig)
1492 compose_insert_sig(compose, FALSE);
1494 compose_wrap_all(compose);
1496 SIGNAL_UNBLOCK(textbuf);
1498 gtk_widget_grab_focus(compose->text);
1500 undo_unblock(compose->undostruct);
1502 if (prefs_common.auto_exteditor)
1503 compose_exec_ext_editor(compose);
1505 compose->modified = FALSE;
1506 compose_set_title(compose);
1508 compose->updating = FALSE;
1509 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1511 if (compose->deferred_destroy) {
1512 compose_destroy(compose);
1519 #define INSERT_FW_HEADER(var, hdr) \
1520 if (msginfo->var && *msginfo->var) { \
1521 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1522 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1523 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1526 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1527 gboolean as_attach, const gchar *body,
1528 gboolean no_extedit,
1532 GtkTextView *textview;
1533 GtkTextBuffer *textbuf;
1536 g_return_val_if_fail(msginfo != NULL, NULL);
1537 g_return_val_if_fail(msginfo->folder != NULL, NULL);
1540 !(account = compose_guess_forward_account_from_msginfo
1542 account = cur_account;
1544 compose = compose_create(account, msginfo->folder, COMPOSE_FORWARD, batch);
1546 compose->updating = TRUE;
1547 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1548 if (!compose->fwdinfo)
1549 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1551 compose_extract_original_charset(compose);
1553 if (msginfo->subject && *msginfo->subject) {
1554 gchar *buf, *buf2, *p;
1556 buf = p = g_strdup(msginfo->subject);
1557 p += subject_get_prefix_length(p);
1558 memmove(buf, p, strlen(p) + 1);
1560 buf2 = g_strdup_printf("Fw: %s", buf);
1561 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1567 textview = GTK_TEXT_VIEW(compose->text);
1568 textbuf = gtk_text_view_get_buffer(textview);
1569 compose_create_tags(textview, compose);
1571 undo_block(compose->undostruct);
1575 msgfile = procmsg_get_message_file(msginfo);
1576 if (!is_file_exist(msgfile))
1577 g_warning("%s: file not exist\n", msgfile);
1579 compose_attach_append(compose, msgfile, msgfile,
1584 const gchar *qmark = NULL;
1585 const gchar *body_fmt = prefs_common.fw_quotefmt;
1586 MsgInfo *full_msginfo;
1588 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1590 full_msginfo = procmsg_msginfo_copy(msginfo);
1592 /* use the forward format of folder (if enabled), or the account's one
1593 (if enabled) or fallback to the global forward format, which is always
1594 enabled (even if empty), and use the relevant quotemark */
1595 if (msginfo->folder && msginfo->folder->prefs &&
1596 msginfo->folder->prefs->forward_with_format) {
1597 qmark = msginfo->folder->prefs->forward_quotemark;
1598 body_fmt = msginfo->folder->prefs->forward_body_format;
1600 } else if (account->forward_with_format) {
1601 qmark = account->forward_quotemark;
1602 body_fmt = account->forward_body_format;
1605 qmark = prefs_common.fw_quotemark;
1606 body_fmt = prefs_common.fw_quotefmt;
1609 /* empty quotemark is not allowed */
1610 if (qmark == NULL || *qmark == '\0')
1613 compose_quote_fmt(compose, full_msginfo,
1614 body_fmt, qmark, body, FALSE, TRUE,
1615 _("Message forward format error at line %d."));
1616 quote_fmt_reset_vartable();
1617 compose_attach_parts(compose, msginfo);
1619 procmsg_msginfo_free(full_msginfo);
1622 SIGNAL_BLOCK(textbuf);
1624 if (account->auto_sig)
1625 compose_insert_sig(compose, FALSE);
1627 compose_wrap_all(compose);
1629 SIGNAL_UNBLOCK(textbuf);
1631 gtk_text_buffer_get_start_iter(textbuf, &iter);
1632 gtk_text_buffer_place_cursor(textbuf, &iter);
1634 gtk_widget_grab_focus(compose->header_last->entry);
1636 if (!no_extedit && prefs_common.auto_exteditor)
1637 compose_exec_ext_editor(compose);
1640 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1641 gchar *folderidentifier;
1643 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1644 folderidentifier = folder_item_get_identifier(msginfo->folder);
1645 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
1646 g_free(folderidentifier);
1649 undo_unblock(compose->undostruct);
1651 compose->modified = FALSE;
1652 compose_set_title(compose);
1654 compose->updating = FALSE;
1655 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1657 if (compose->deferred_destroy) {
1658 compose_destroy(compose);
1665 #undef INSERT_FW_HEADER
1667 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1670 GtkTextView *textview;
1671 GtkTextBuffer *textbuf;
1675 gboolean single_mail = TRUE;
1677 g_return_val_if_fail(msginfo_list != NULL, NULL);
1679 if (g_slist_length(msginfo_list) > 1)
1680 single_mail = FALSE;
1682 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1683 if (((MsgInfo *)msginfo->data)->folder == NULL)
1686 /* guess account from first selected message */
1688 !(account = compose_guess_forward_account_from_msginfo
1689 (msginfo_list->data)))
1690 account = cur_account;
1692 g_return_val_if_fail(account != NULL, NULL);
1694 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1695 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1696 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1699 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1701 compose->updating = TRUE;
1703 textview = GTK_TEXT_VIEW(compose->text);
1704 textbuf = gtk_text_view_get_buffer(textview);
1705 compose_create_tags(textview, compose);
1707 undo_block(compose->undostruct);
1708 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1709 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
1711 if (!is_file_exist(msgfile))
1712 g_warning("%s: file not exist\n", msgfile);
1714 compose_attach_append(compose, msgfile, msgfile,
1720 MsgInfo *info = (MsgInfo *)msginfo_list->data;
1721 if (info->subject && *info->subject) {
1722 gchar *buf, *buf2, *p;
1724 buf = p = g_strdup(info->subject);
1725 p += subject_get_prefix_length(p);
1726 memmove(buf, p, strlen(p) + 1);
1728 buf2 = g_strdup_printf("Fw: %s", buf);
1729 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1735 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
1736 _("Fw: multiple emails"));
1739 SIGNAL_BLOCK(textbuf);
1741 if (account->auto_sig)
1742 compose_insert_sig(compose, FALSE);
1744 compose_wrap_all(compose);
1746 SIGNAL_UNBLOCK(textbuf);
1748 gtk_text_buffer_get_start_iter(textbuf, &iter);
1749 gtk_text_buffer_place_cursor(textbuf, &iter);
1751 gtk_widget_grab_focus(compose->header_last->entry);
1752 undo_unblock(compose->undostruct);
1753 compose->modified = FALSE;
1754 compose_set_title(compose);
1756 compose->updating = FALSE;
1757 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1759 if (compose->deferred_destroy) {
1760 compose_destroy(compose);
1767 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
1769 GtkTextIter start = *iter;
1770 GtkTextIter end_iter;
1771 int start_pos = gtk_text_iter_get_offset(&start);
1773 if (!compose->account->sig_sep)
1776 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
1777 start_pos+strlen(compose->account->sig_sep));
1779 /* check sig separator */
1780 str = gtk_text_iter_get_text(&start, &end_iter);
1781 if (!strcmp(str, compose->account->sig_sep)) {
1783 /* check end of line (\n) */
1784 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
1785 start_pos+strlen(compose->account->sig_sep));
1786 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
1787 start_pos+strlen(compose->account->sig_sep)+1);
1788 tmp = gtk_text_iter_get_text(&start, &end_iter);
1789 if (!strcmp(tmp,"\n")) {
1801 static void compose_colorize_signature(Compose *compose)
1803 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
1805 GtkTextIter end_iter;
1806 gtk_text_buffer_get_start_iter(buffer, &iter);
1807 while (gtk_text_iter_forward_line(&iter))
1808 if (compose_is_sig_separator(compose, buffer, &iter)) {
1809 gtk_text_buffer_get_end_iter(buffer, &end_iter);
1810 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
1814 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
1816 Compose *compose = NULL;
1817 PrefsAccount *account = NULL;
1818 GtkTextView *textview;
1819 GtkTextBuffer *textbuf;
1823 gchar buf[BUFFSIZE];
1824 gboolean use_signing = FALSE;
1825 gboolean use_encryption = FALSE;
1826 gchar *privacy_system = NULL;
1827 int priority = PRIORITY_NORMAL;
1828 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
1830 g_return_val_if_fail(msginfo != NULL, NULL);
1831 g_return_val_if_fail(msginfo->folder != NULL, NULL);
1833 if (compose_put_existing_to_front(msginfo)) {
1837 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
1838 folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
1839 gchar queueheader_buf[BUFFSIZE];
1842 /* Select Account from queue headers */
1843 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1844 sizeof(queueheader_buf), "X-Claws-Account-Id:")) {
1845 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
1846 account = account_find_from_id(id);
1848 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1849 sizeof(queueheader_buf), "X-Sylpheed-Account-Id:")) {
1850 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
1851 account = account_find_from_id(id);
1853 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1854 sizeof(queueheader_buf), "NAID:")) {
1855 id = atoi(&queueheader_buf[strlen("NAID:")]);
1856 account = account_find_from_id(id);
1858 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1859 sizeof(queueheader_buf), "MAID:")) {
1860 id = atoi(&queueheader_buf[strlen("MAID:")]);
1861 account = account_find_from_id(id);
1863 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1864 sizeof(queueheader_buf), "S:")) {
1865 account = account_find_from_address(queueheader_buf);
1867 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1868 sizeof(queueheader_buf), "X-Claws-Sign:")) {
1869 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
1870 use_signing = param;
1873 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1874 sizeof(queueheader_buf), "X-Sylpheed-Sign:")) {
1875 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
1876 use_signing = param;
1879 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1880 sizeof(queueheader_buf), "X-Claws-Encrypt:")) {
1881 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
1882 use_encryption = param;
1884 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1885 sizeof(queueheader_buf), "X-Sylpheed-Encrypt:")) {
1886 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
1887 use_encryption = param;
1889 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1890 sizeof(queueheader_buf), "X-Claws-Privacy-System:")) {
1891 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
1893 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1894 sizeof(queueheader_buf), "X-Sylpheed-Privacy-System:")) {
1895 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
1897 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1898 sizeof(queueheader_buf), "X-Priority: ")) {
1899 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
1902 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1903 sizeof(queueheader_buf), "RMID:")) {
1904 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
1905 if (tokens[0] && tokens[1] && tokens[2]) {
1906 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
1907 if (orig_item != NULL) {
1908 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
1913 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1914 sizeof(queueheader_buf), "FMID:")) {
1915 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
1916 if (tokens[0] && tokens[1] && tokens[2]) {
1917 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
1918 if (orig_item != NULL) {
1919 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
1925 account = msginfo->folder->folder->account;
1928 if (!account && prefs_common.reedit_account_autosel) {
1929 gchar from[BUFFSIZE];
1930 if (!procheader_get_header_from_msginfo(msginfo, from, sizeof(from), "FROM:")) {
1931 extract_address(from);
1932 account = account_find_from_address(from);
1936 account = cur_account;
1938 g_return_val_if_fail(account != NULL, NULL);
1940 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
1942 compose->replyinfo = replyinfo;
1943 compose->fwdinfo = fwdinfo;
1945 compose->updating = TRUE;
1946 compose->priority = priority;
1948 if (privacy_system != NULL) {
1949 compose->privacy_system = privacy_system;
1950 compose_use_signing(compose, use_signing);
1951 compose_use_encryption(compose, use_encryption);
1952 compose_update_privacy_system_menu_item(compose, FALSE);
1954 activate_privacy_system(compose, account, FALSE);
1957 compose->targetinfo = procmsg_msginfo_copy(msginfo);
1959 compose_extract_original_charset(compose);
1961 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
1962 folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
1963 gchar queueheader_buf[BUFFSIZE];
1965 /* Set message save folder */
1966 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "SCF:")) {
1969 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1970 gtk_editable_delete_text(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
1971 gtk_editable_insert_text(GTK_EDITABLE(compose->savemsg_entry), &queueheader_buf[4], strlen(&queueheader_buf[4]), &startpos);
1973 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "RRCPT:")) {
1974 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
1976 GtkItemFactory *ifactory;
1977 ifactory = gtk_item_factory_from_widget(compose->menubar);
1978 menu_set_active(ifactory, "/Options/Request Return Receipt", TRUE);
1983 if (compose_parse_header(compose, msginfo) < 0) {
1984 compose->updating = FALSE;
1985 compose_destroy(compose);
1988 compose_reedit_set_entry(compose, msginfo);
1990 textview = GTK_TEXT_VIEW(compose->text);
1991 textbuf = gtk_text_view_get_buffer(textview);
1992 compose_create_tags(textview, compose);
1994 mark = gtk_text_buffer_get_insert(textbuf);
1995 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
1997 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
1998 G_CALLBACK(compose_changed_cb),
2001 if (procmime_msginfo_is_encrypted(msginfo)) {
2002 fp = procmime_get_first_encrypted_text_content(msginfo);
2004 compose_force_encryption(compose, account, TRUE);
2007 fp = procmime_get_first_text_content(msginfo);
2010 g_warning("Can't get text part\n");
2014 gboolean prev_autowrap = compose->autowrap;
2016 compose->autowrap = FALSE;
2017 while (fgets(buf, sizeof(buf), fp) != NULL) {
2019 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2021 compose_wrap_all_full(compose, FALSE);
2022 compose->autowrap = prev_autowrap;
2026 compose_attach_parts(compose, msginfo);
2028 compose_colorize_signature(compose);
2030 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2031 G_CALLBACK(compose_changed_cb),
2034 gtk_widget_grab_focus(compose->text);
2036 if (prefs_common.auto_exteditor) {
2037 compose_exec_ext_editor(compose);
2039 compose->modified = FALSE;
2040 compose_set_title(compose);
2042 compose->updating = FALSE;
2043 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2045 if (compose->deferred_destroy) {
2046 compose_destroy(compose);
2050 compose->sig_str = compose_get_signature_str(compose);
2055 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2060 GtkItemFactory *ifactory;
2063 g_return_val_if_fail(msginfo != NULL, NULL);
2066 account = account_get_reply_account(msginfo,
2067 prefs_common.reply_account_autosel);
2068 g_return_val_if_fail(account != NULL, NULL);
2070 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2072 compose->updating = TRUE;
2074 ifactory = gtk_item_factory_from_widget(compose->menubar);
2075 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2076 compose->replyinfo = NULL;
2077 compose->fwdinfo = NULL;
2079 compose_show_first_last_header(compose, TRUE);
2081 gtk_widget_grab_focus(compose->header_last->entry);
2083 filename = procmsg_get_message_file(msginfo);
2085 if (filename == NULL) {
2086 compose->updating = FALSE;
2087 compose_destroy(compose);
2092 compose->redirect_filename = filename;
2094 /* Set save folder */
2095 item = msginfo->folder;
2096 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2097 gchar *folderidentifier;
2099 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
2100 folderidentifier = folder_item_get_identifier(item);
2101 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
2102 g_free(folderidentifier);
2105 compose_attach_parts(compose, msginfo);
2107 if (msginfo->subject)
2108 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2110 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2112 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2113 _("Message redirect format error at line %d."));
2114 quote_fmt_reset_vartable();
2115 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2117 compose_colorize_signature(compose);
2119 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
2120 menu_set_sensitive(ifactory, "/Add...", FALSE);
2121 menu_set_sensitive(ifactory, "/Remove", FALSE);
2122 menu_set_sensitive(ifactory, "/Properties...", FALSE);
2124 ifactory = gtk_item_factory_from_widget(compose->menubar);
2125 menu_set_sensitive(ifactory, "/Message/Save", FALSE);
2126 menu_set_sensitive(ifactory, "/Message/Insert file", FALSE);
2127 menu_set_sensitive(ifactory, "/Message/Attach file", FALSE);
2128 menu_set_sensitive(ifactory, "/Message/Insert signature", FALSE);
2129 menu_set_sensitive(ifactory, "/Edit", FALSE);
2130 menu_set_sensitive(ifactory, "/Options", FALSE);
2131 menu_set_sensitive(ifactory, "/Tools/Show ruler", FALSE);
2132 menu_set_sensitive(ifactory, "/Tools/Actions", FALSE);
2134 if (compose->toolbar->draft_btn)
2135 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2136 if (compose->toolbar->insert_btn)
2137 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2138 if (compose->toolbar->attach_btn)
2139 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2140 if (compose->toolbar->sig_btn)
2141 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2142 if (compose->toolbar->exteditor_btn)
2143 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2144 if (compose->toolbar->linewrap_current_btn)
2145 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2146 if (compose->toolbar->linewrap_all_btn)
2147 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2149 compose->modified = FALSE;
2150 compose_set_title(compose);
2151 compose->updating = FALSE;
2152 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2154 if (compose->deferred_destroy) {
2155 compose_destroy(compose);
2162 GList *compose_get_compose_list(void)
2164 return compose_list;
2167 void compose_entry_append(Compose *compose, const gchar *address,
2168 ComposeEntryType type)
2170 const gchar *header;
2172 gboolean in_quote = FALSE;
2173 if (!address || *address == '\0') return;
2180 header = N_("Bcc:");
2182 case COMPOSE_REPLYTO:
2183 header = N_("Reply-To:");
2185 case COMPOSE_NEWSGROUPS:
2186 header = N_("Newsgroups:");
2188 case COMPOSE_FOLLOWUPTO:
2189 header = N_( "Followup-To:");
2196 header = prefs_common_translated_header_name(header);
2198 cur = begin = (gchar *)address;
2200 /* we separate the line by commas, but not if we're inside a quoted
2202 while (*cur != '\0') {
2204 in_quote = !in_quote;
2205 if (*cur == ',' && !in_quote) {
2206 gchar *tmp = g_strdup(begin);
2208 tmp[cur-begin]='\0';
2211 while (*tmp == ' ' || *tmp == '\t')
2213 compose_add_header_entry(compose, header, tmp);
2220 gchar *tmp = g_strdup(begin);
2222 tmp[cur-begin]='\0';
2225 while (*tmp == ' ' || *tmp == '\t')
2227 compose_add_header_entry(compose, header, tmp);
2232 static void compose_entry_mark_default_to(Compose *compose, const gchar *mailto)
2234 static GdkColor yellow;
2235 static GdkColor black;
2236 static gboolean yellow_initialised = FALSE;
2240 if (!yellow_initialised) {
2241 gdk_color_parse("#f5f6be", &yellow);
2242 gdk_color_parse("#000000", &black);
2243 yellow_initialised = gdk_colormap_alloc_color(
2244 gdk_colormap_get_system(), &yellow, FALSE, TRUE);
2245 yellow_initialised &= gdk_colormap_alloc_color(
2246 gdk_colormap_get_system(), &black, FALSE, TRUE);
2249 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2250 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2251 if (gtk_entry_get_text(entry) &&
2252 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2253 if (yellow_initialised) {
2254 gtk_widget_modify_base(
2255 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2256 GTK_STATE_NORMAL, &yellow);
2257 gtk_widget_modify_text(
2258 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2259 GTK_STATE_NORMAL, &black);
2265 void compose_toolbar_cb(gint action, gpointer data)
2267 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2268 Compose *compose = (Compose*)toolbar_item->parent;
2270 g_return_if_fail(compose != NULL);
2274 compose_send_cb(compose, 0, NULL);
2277 compose_send_later_cb(compose, 0, NULL);
2280 compose_draft_cb(compose, COMPOSE_QUIT_EDITING, NULL);
2283 compose_insert_file_cb(compose, 0, NULL);
2286 compose_attach_cb(compose, 0, NULL);
2289 compose_insert_sig(compose, FALSE);
2292 compose_ext_editor_cb(compose, 0, NULL);
2294 case A_LINEWRAP_CURRENT:
2295 compose_beautify_paragraph(compose, NULL, TRUE);
2297 case A_LINEWRAP_ALL:
2298 compose_wrap_all_full(compose, TRUE);
2301 compose_address_cb(compose, 0, NULL);
2304 case A_CHECK_SPELLING:
2305 compose_check_all(compose);
2313 static void compose_entries_set(Compose *compose, const gchar *mailto)
2317 gchar *subject = NULL;
2321 gchar *attach = NULL;
2323 scan_mailto_url(mailto, &to, &cc, NULL, &subject, &body, &attach);
2326 compose_entry_append(compose, to, COMPOSE_TO);
2328 compose_entry_append(compose, cc, COMPOSE_CC);
2330 if (!g_utf8_validate (subject, -1, NULL)) {
2331 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2332 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2335 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2339 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2340 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2343 gboolean prev_autowrap = compose->autowrap;
2345 compose->autowrap = FALSE;
2347 mark = gtk_text_buffer_get_insert(buffer);
2348 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2350 if (!g_utf8_validate (body, -1, NULL)) {
2351 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2352 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2355 gtk_text_buffer_insert(buffer, &iter, body, -1);
2357 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2359 compose->autowrap = prev_autowrap;
2360 if (compose->autowrap)
2361 compose_wrap_all(compose);
2365 gchar *utf8_filename = conv_filename_to_utf8(attach);
2366 if (utf8_filename) {
2367 if (compose_attach_append(compose, attach, utf8_filename, NULL)) {
2368 alertpanel_notice(_("The file '%s' has been attached."), attach);
2370 g_free(utf8_filename);
2372 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2382 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2384 static HeaderEntry hentry[] = {{"Reply-To:", NULL, TRUE},
2385 {"Cc:", NULL, TRUE},
2386 {"References:", NULL, FALSE},
2387 {"Bcc:", NULL, TRUE},
2388 {"Newsgroups:", NULL, TRUE},
2389 {"Followup-To:", NULL, TRUE},
2390 {"List-Post:", NULL, FALSE},
2391 {"X-Priority:", NULL, FALSE},
2392 {NULL, NULL, FALSE}};
2408 g_return_val_if_fail(msginfo != NULL, -1);
2410 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2411 procheader_get_header_fields(fp, hentry);
2414 if (hentry[H_REPLY_TO].body != NULL) {
2415 if (hentry[H_REPLY_TO].body[0] != '\0') {
2417 conv_unmime_header(hentry[H_REPLY_TO].body,
2420 g_free(hentry[H_REPLY_TO].body);
2421 hentry[H_REPLY_TO].body = NULL;
2423 if (hentry[H_CC].body != NULL) {
2424 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL);
2425 g_free(hentry[H_CC].body);
2426 hentry[H_CC].body = NULL;
2428 if (hentry[H_REFERENCES].body != NULL) {
2429 if (compose->mode == COMPOSE_REEDIT)
2430 compose->references = hentry[H_REFERENCES].body;
2432 compose->references = compose_parse_references
2433 (hentry[H_REFERENCES].body, msginfo->msgid);
2434 g_free(hentry[H_REFERENCES].body);
2436 hentry[H_REFERENCES].body = NULL;
2438 if (hentry[H_BCC].body != NULL) {
2439 if (compose->mode == COMPOSE_REEDIT)
2441 conv_unmime_header(hentry[H_BCC].body, NULL);
2442 g_free(hentry[H_BCC].body);
2443 hentry[H_BCC].body = NULL;
2445 if (hentry[H_NEWSGROUPS].body != NULL) {
2446 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2447 hentry[H_NEWSGROUPS].body = NULL;
2449 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2450 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2451 compose->followup_to =
2452 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2455 g_free(hentry[H_FOLLOWUP_TO].body);
2456 hentry[H_FOLLOWUP_TO].body = NULL;
2458 if (hentry[H_LIST_POST].body != NULL) {
2461 extract_address(hentry[H_LIST_POST].body);
2462 if (hentry[H_LIST_POST].body[0] != '\0') {
2463 scan_mailto_url(hentry[H_LIST_POST].body,
2464 &to, NULL, NULL, NULL, NULL, NULL);
2466 g_free(compose->ml_post);
2467 compose->ml_post = to;
2470 g_free(hentry[H_LIST_POST].body);
2471 hentry[H_LIST_POST].body = NULL;
2474 /* CLAWS - X-Priority */
2475 if (compose->mode == COMPOSE_REEDIT)
2476 if (hentry[H_X_PRIORITY].body != NULL) {
2479 priority = atoi(hentry[H_X_PRIORITY].body);
2480 g_free(hentry[H_X_PRIORITY].body);
2482 hentry[H_X_PRIORITY].body = NULL;
2484 if (priority < PRIORITY_HIGHEST ||
2485 priority > PRIORITY_LOWEST)
2486 priority = PRIORITY_NORMAL;
2488 compose->priority = priority;
2491 if (compose->mode == COMPOSE_REEDIT) {
2492 if (msginfo->inreplyto && *msginfo->inreplyto)
2493 compose->inreplyto = g_strdup(msginfo->inreplyto);
2497 if (msginfo->msgid && *msginfo->msgid)
2498 compose->inreplyto = g_strdup(msginfo->msgid);
2500 if (!compose->references) {
2501 if (msginfo->msgid && *msginfo->msgid) {
2502 if (msginfo->inreplyto && *msginfo->inreplyto)
2503 compose->references =
2504 g_strdup_printf("<%s>\n\t<%s>",
2508 compose->references =
2509 g_strconcat("<", msginfo->msgid, ">",
2511 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
2512 compose->references =
2513 g_strconcat("<", msginfo->inreplyto, ">",
2521 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
2523 GSList *ref_id_list, *cur;
2527 ref_id_list = references_list_append(NULL, ref);
2528 if (!ref_id_list) return NULL;
2529 if (msgid && *msgid)
2530 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
2535 for (cur = ref_id_list; cur != NULL; cur = cur->next)
2536 /* "<" + Message-ID + ">" + CR+LF+TAB */
2537 len += strlen((gchar *)cur->data) + 5;
2539 if (len > MAX_REFERENCES_LEN) {
2540 /* remove second message-ID */
2541 if (ref_id_list && ref_id_list->next &&
2542 ref_id_list->next->next) {
2543 g_free(ref_id_list->next->data);
2544 ref_id_list = g_slist_remove
2545 (ref_id_list, ref_id_list->next->data);
2547 slist_free_strings(ref_id_list);
2548 g_slist_free(ref_id_list);
2555 new_ref = g_string_new("");
2556 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
2557 if (new_ref->len > 0)
2558 g_string_append(new_ref, "\n\t");
2559 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
2562 slist_free_strings(ref_id_list);
2563 g_slist_free(ref_id_list);
2565 new_ref_str = new_ref->str;
2566 g_string_free(new_ref, FALSE);
2571 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
2572 const gchar *fmt, const gchar *qmark,
2573 const gchar *body, gboolean rewrap,
2574 gboolean need_unescape,
2575 const gchar *err_msg)
2577 MsgInfo* dummyinfo = NULL;
2578 gchar *quote_str = NULL;
2580 gboolean prev_autowrap;
2581 const gchar *trimmed_body = body;
2582 gint cursor_pos = -1;
2583 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2584 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2589 SIGNAL_BLOCK(buffer);
2592 dummyinfo = compose_msginfo_new_from_compose(compose);
2593 msginfo = dummyinfo;
2596 if (qmark != NULL) {
2598 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
2599 compose->gtkaspell);
2601 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
2603 quote_fmt_scan_string(qmark);
2606 buf = quote_fmt_get_buffer();
2608 alertpanel_error(_("Quote mark format error."));
2610 Xstrdup_a(quote_str, buf, goto error)
2613 if (fmt && *fmt != '\0') {
2616 while (*trimmed_body == '\n')
2620 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account,
2621 compose->gtkaspell);
2623 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account);
2625 if (need_unescape) {
2628 /* decode \-escape sequences in the internal representation of the quote format */
2629 tmp = malloc(strlen(fmt)+1);
2630 pref_get_unescaped_pref(tmp, fmt);
2631 quote_fmt_scan_string(tmp);
2635 quote_fmt_scan_string(fmt);
2639 buf = quote_fmt_get_buffer();
2641 gint line = quote_fmt_get_line();
2642 gchar *msg = g_strdup_printf(err_msg, line);
2643 alertpanel_error(msg);
2650 prev_autowrap = compose->autowrap;
2651 compose->autowrap = FALSE;
2653 mark = gtk_text_buffer_get_insert(buffer);
2654 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2655 if (g_utf8_validate(buf, -1, NULL)) {
2656 gtk_text_buffer_insert(buffer, &iter, buf, -1);
2658 gchar *tmpout = NULL;
2659 tmpout = conv_codeset_strdup
2660 (buf, conv_get_locale_charset_str_no_utf8(),
2662 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
2664 tmpout = g_malloc(strlen(buf)*2+1);
2665 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
2667 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
2671 cursor_pos = quote_fmt_get_cursor_pos();
2672 compose->set_cursor_pos = cursor_pos;
2673 if (cursor_pos == -1) {
2676 gtk_text_buffer_get_start_iter(buffer, &iter);
2677 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
2678 gtk_text_buffer_place_cursor(buffer, &iter);
2680 compose->autowrap = prev_autowrap;
2681 if (compose->autowrap && rewrap)
2682 compose_wrap_all(compose);
2689 SIGNAL_UNBLOCK(buffer);
2691 procmsg_msginfo_free( dummyinfo );
2696 /* if ml_post is of type addr@host and from is of type
2697 * addr-anything@host, return TRUE
2699 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
2701 gchar *left_ml = NULL;
2702 gchar *right_ml = NULL;
2703 gchar *left_from = NULL;
2704 gchar *right_from = NULL;
2705 gboolean result = FALSE;
2707 if (!ml_post || !from)
2710 left_ml = g_strdup(ml_post);
2711 if (strstr(left_ml, "@")) {
2712 right_ml = strstr(left_ml, "@")+1;
2713 *(strstr(left_ml, "@")) = '\0';
2716 left_from = g_strdup(from);
2717 if (strstr(left_from, "@")) {
2718 right_from = strstr(left_from, "@")+1;
2719 *(strstr(left_from, "@")) = '\0';
2722 if (left_ml && left_from && right_ml && right_from
2723 && !strncmp(left_from, left_ml, strlen(left_ml))
2724 && !strcmp(right_from, right_ml)) {
2733 static gboolean same_address(const gchar *addr1, const gchar *addr2)
2735 gchar *my_addr1, *my_addr2;
2737 if (!addr1 || !addr2)
2740 Xstrdup_a(my_addr1, addr1, return FALSE);
2741 Xstrdup_a(my_addr2, addr2, return FALSE);
2743 extract_address(my_addr1);
2744 extract_address(my_addr2);
2746 return !strcasecmp(my_addr1, my_addr2);
2749 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
2750 gboolean to_all, gboolean to_ml,
2752 gboolean followup_and_reply_to)
2754 GSList *cc_list = NULL;
2757 gchar *replyto = NULL;
2758 GHashTable *to_table;
2760 gboolean reply_to_ml = FALSE;
2761 gboolean default_reply_to = FALSE;
2763 g_return_if_fail(compose->account != NULL);
2764 g_return_if_fail(msginfo != NULL);
2766 reply_to_ml = to_ml && compose->ml_post;
2768 default_reply_to = msginfo->folder &&
2769 msginfo->folder->prefs->enable_default_reply_to;
2771 if (compose->account->protocol != A_NNTP) {
2772 if (reply_to_ml && !default_reply_to) {
2774 gboolean is_subscr = is_subscription(compose->ml_post,
2777 /* normal answer to ml post with a reply-to */
2778 compose_entry_append(compose,
2781 if (compose->replyto
2782 && !same_address(compose->ml_post, compose->replyto))
2783 compose_entry_append(compose,
2787 /* answer to subscription confirmation */
2788 if (compose->replyto)
2789 compose_entry_append(compose,
2792 else if (msginfo->from)
2793 compose_entry_append(compose,
2798 else if (!(to_all || to_sender) && default_reply_to) {
2799 compose_entry_append(compose,
2800 msginfo->folder->prefs->default_reply_to,
2802 compose_entry_mark_default_to(compose,
2803 msginfo->folder->prefs->default_reply_to);
2808 Xstrdup_a(tmp1, msginfo->from, return);
2809 extract_address(tmp1);
2810 if (to_all || to_sender ||
2811 !account_find_from_address(tmp1))
2812 compose_entry_append(compose,
2813 (compose->replyto && !to_sender)
2814 ? compose->replyto :
2815 msginfo->from ? msginfo->from : "",
2817 else if (!to_all && !to_sender) {
2818 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
2819 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
2820 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
2821 compose_entry_append(compose,
2822 msginfo->from ? msginfo->from : "",
2825 /* replying to own mail, use original recp */
2826 compose_entry_append(compose,
2827 msginfo->to ? msginfo->to : "",
2829 compose_entry_append(compose,
2830 msginfo->cc ? msginfo->cc : "",
2836 if (to_sender || (compose->followup_to &&
2837 !strncmp(compose->followup_to, "poster", 6)))
2838 compose_entry_append
2840 (compose->replyto ? compose->replyto :
2841 msginfo->from ? msginfo->from : ""),
2844 else if (followup_and_reply_to || to_all) {
2845 compose_entry_append
2847 (compose->replyto ? compose->replyto :
2848 msginfo->from ? msginfo->from : ""),
2851 compose_entry_append
2853 compose->followup_to ? compose->followup_to :
2854 compose->newsgroups ? compose->newsgroups : "",
2855 COMPOSE_NEWSGROUPS);
2858 compose_entry_append
2860 compose->followup_to ? compose->followup_to :
2861 compose->newsgroups ? compose->newsgroups : "",
2862 COMPOSE_NEWSGROUPS);
2865 if (msginfo->subject && *msginfo->subject) {
2869 buf = p = g_strdup(msginfo->subject);
2870 p += subject_get_prefix_length(p);
2871 memmove(buf, p, strlen(p) + 1);
2873 buf2 = g_strdup_printf("Re: %s", buf);
2874 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
2879 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
2881 if (to_ml && compose->ml_post) return;
2882 if (!to_all || compose->account->protocol == A_NNTP) return;
2884 if (compose->replyto) {
2885 Xstrdup_a(replyto, compose->replyto, return);
2886 extract_address(replyto);
2888 if (msginfo->from) {
2889 Xstrdup_a(from, msginfo->from, return);
2890 extract_address(from);
2893 if (replyto && from)
2894 cc_list = address_list_append_with_comments(cc_list, from);
2895 if (to_all && msginfo->folder &&
2896 msginfo->folder->prefs->enable_default_reply_to)
2897 cc_list = address_list_append_with_comments(cc_list,
2898 msginfo->folder->prefs->default_reply_to);
2899 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
2900 cc_list = address_list_append_with_comments(cc_list, compose->cc);
2902 to_table = g_hash_table_new(g_str_hash, g_str_equal);
2904 g_hash_table_insert(to_table, g_utf8_strdown(replyto, -1), GINT_TO_POINTER(1));
2905 if (compose->account) {
2906 g_hash_table_insert(to_table, g_utf8_strdown(compose->account->address, -1),
2907 GINT_TO_POINTER(1));
2909 /* remove address on To: and that of current account */
2910 for (cur = cc_list; cur != NULL; ) {
2911 GSList *next = cur->next;
2914 addr = g_utf8_strdown(cur->data, -1);
2915 extract_address(addr);
2917 if (GPOINTER_TO_INT(g_hash_table_lookup(to_table, addr)) == 1)
2918 cc_list = g_slist_remove(cc_list, cur->data);
2920 g_hash_table_insert(to_table, addr, GINT_TO_POINTER(1));
2924 hash_free_strings(to_table);
2925 g_hash_table_destroy(to_table);
2928 for (cur = cc_list; cur != NULL; cur = cur->next)
2929 compose_entry_append(compose, (gchar *)cur->data,
2931 slist_free_strings(cc_list);
2932 g_slist_free(cc_list);
2937 #define SET_ENTRY(entry, str) \
2940 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
2943 #define SET_ADDRESS(type, str) \
2946 compose_entry_append(compose, str, type); \
2949 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
2951 g_return_if_fail(msginfo != NULL);
2953 SET_ENTRY(subject_entry, msginfo->subject);
2954 SET_ENTRY(from_name, msginfo->from);
2955 SET_ADDRESS(COMPOSE_TO, msginfo->to);
2956 SET_ADDRESS(COMPOSE_CC, compose->cc);
2957 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
2958 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
2959 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
2960 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
2962 compose_update_priority_menu_item(compose);
2963 compose_update_privacy_system_menu_item(compose, FALSE);
2964 compose_show_first_last_header(compose, TRUE);
2970 static void compose_insert_sig(Compose *compose, gboolean replace)
2972 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2973 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2975 GtkTextIter iter, iter_end;
2977 gchar *search = NULL;
2978 gboolean prev_autowrap;
2979 gboolean found = FALSE, shift = FALSE;
2982 g_return_if_fail(compose->account != NULL);
2984 prev_autowrap = compose->autowrap;
2985 compose->autowrap = FALSE;
2987 g_signal_handlers_block_by_func(G_OBJECT(buffer),
2988 G_CALLBACK(compose_changed_cb),
2991 mark = gtk_text_buffer_get_insert(buffer);
2992 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2993 cur_pos = gtk_text_iter_get_offset (&iter);
2995 gtk_text_buffer_get_end_iter(buffer, &iter);
2997 search = compose->sig_str;
2999 if (replace && search) {
3000 GtkTextIter first_iter, start_iter, end_iter;
3002 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3004 if (compose->sig_str[0] == '\0')
3007 found = gtk_text_iter_forward_search(&first_iter,
3009 GTK_TEXT_SEARCH_TEXT_ONLY,
3010 &start_iter, &end_iter,
3014 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3018 if (replace && !found && search && strlen(search) > 2
3019 && search[0] == '\n' && search[1] == '\n') {
3025 g_free(compose->sig_str);
3026 compose->sig_str = compose_get_signature_str(compose);
3027 if (!compose->sig_str || (replace && !compose->account->auto_sig))
3028 compose->sig_str = g_strdup("");
3030 cur_pos = gtk_text_iter_get_offset(&iter);
3032 gtk_text_buffer_insert(buffer, &iter, compose->sig_str + 1, -1);
3034 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3036 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3037 gtk_text_iter_forward_char(&iter);
3038 gtk_text_iter_forward_char(&iter);
3039 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3040 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3042 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3043 cur_pos = gtk_text_buffer_get_char_count (buffer);
3045 /* put the cursor where it should be
3046 * either where the quote_fmt says, either before the signature */
3047 if (compose->set_cursor_pos < 0)
3048 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3050 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3051 compose->set_cursor_pos);
3053 gtk_text_buffer_place_cursor(buffer, &iter);
3054 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3055 G_CALLBACK(compose_changed_cb),
3058 compose->autowrap = prev_autowrap;
3059 if (compose->autowrap)
3060 compose_wrap_all(compose);
3063 static gchar *compose_get_signature_str(Compose *compose)
3065 gchar *sig_body = NULL;
3066 gchar *sig_str = NULL;
3067 gchar *utf8_sig_str = NULL;
3069 g_return_val_if_fail(compose->account != NULL, NULL);
3071 if (!compose->account->sig_path)
3074 if (compose->account->sig_type == SIG_FILE) {
3075 if (!is_file_or_fifo_exist(compose->account->sig_path)) {
3076 g_warning("can't open signature file: %s\n",
3077 compose->account->sig_path);
3082 if (compose->account->sig_type == SIG_COMMAND)
3083 sig_body = get_command_output(compose->account->sig_path);
3087 tmp = file_read_to_str(compose->account->sig_path);
3090 sig_body = normalize_newlines(tmp);
3094 if (compose->account->sig_sep) {
3095 sig_str = g_strconcat("\n\n", compose->account->sig_sep, "\n", sig_body,
3099 sig_str = g_strconcat("\n\n", sig_body, NULL);
3102 if (g_utf8_validate(sig_str, -1, NULL) == TRUE)
3103 utf8_sig_str = sig_str;
3105 utf8_sig_str = conv_codeset_strdup
3106 (sig_str, conv_get_locale_charset_str_no_utf8(),
3112 return utf8_sig_str;
3115 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3118 GtkTextBuffer *buffer;
3121 const gchar *cur_encoding;
3122 gchar buf[BUFFSIZE];
3125 gboolean prev_autowrap;
3126 gboolean badtxt = FALSE;
3128 g_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3130 if ((fp = g_fopen(file, "rb")) == NULL) {
3131 FILE_OP_ERROR(file, "fopen");
3132 return COMPOSE_INSERT_READ_ERROR;
3135 prev_autowrap = compose->autowrap;
3136 compose->autowrap = FALSE;
3138 text = GTK_TEXT_VIEW(compose->text);
3139 buffer = gtk_text_view_get_buffer(text);
3140 mark = gtk_text_buffer_get_insert(buffer);
3141 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3143 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3144 G_CALLBACK(text_inserted),
3147 cur_encoding = conv_get_locale_charset_str_no_utf8();
3149 while (fgets(buf, sizeof(buf), fp) != NULL) {
3152 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3153 str = g_strdup(buf);
3155 str = conv_codeset_strdup
3156 (buf, cur_encoding, CS_INTERNAL);
3159 /* strip <CR> if DOS/Windows file,
3160 replace <CR> with <LF> if Macintosh file. */
3163 if (len > 0 && str[len - 1] != '\n') {
3165 if (str[len] == '\r') str[len] = '\n';
3168 gtk_text_buffer_insert(buffer, &iter, str, -1);
3172 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3173 G_CALLBACK(text_inserted),
3175 compose->autowrap = prev_autowrap;
3176 if (compose->autowrap)
3177 compose_wrap_all(compose);
3182 return COMPOSE_INSERT_INVALID_CHARACTER;
3184 return COMPOSE_INSERT_SUCCESS;
3187 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3188 const gchar *filename,
3189 const gchar *content_type)
3197 GtkListStore *store;
3199 gboolean has_binary = FALSE;
3201 if (!is_file_exist(file)) {
3202 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3203 gboolean result = FALSE;
3204 if (file_from_uri && is_file_exist(file_from_uri)) {
3205 result = compose_attach_append(
3206 compose, file_from_uri,
3210 g_free(file_from_uri);
3213 alertpanel_error("File %s doesn't exist\n", filename);
3216 if ((size = get_file_size(file)) < 0) {
3217 alertpanel_error("Can't get file size of %s\n", filename);
3221 alertpanel_error(_("File %s is empty."), filename);
3224 if ((fp = g_fopen(file, "rb")) == NULL) {
3225 alertpanel_error(_("Can't read %s."), filename);
3230 ainfo = g_new0(AttachInfo, 1);
3231 auto_ainfo = g_auto_pointer_new_with_free
3232 (ainfo, (GFreeFunc) compose_attach_info_free);
3233 ainfo->file = g_strdup(file);
3236 ainfo->content_type = g_strdup(content_type);
3237 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3239 MsgFlags flags = {0, 0};
3241 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3242 ainfo->encoding = ENC_7BIT;
3244 ainfo->encoding = ENC_8BIT;
3246 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3247 if (msginfo && msginfo->subject)
3248 name = g_strdup(msginfo->subject);
3250 name = g_path_get_basename(filename ? filename : file);
3252 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3254 procmsg_msginfo_free(msginfo);
3256 if (!g_ascii_strncasecmp(content_type, "text", 4))
3257 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3259 ainfo->encoding = ENC_BASE64;
3260 name = g_path_get_basename(filename ? filename : file);
3261 ainfo->name = g_strdup(name);
3265 ainfo->content_type = procmime_get_mime_type(file);
3266 if (!ainfo->content_type) {
3267 ainfo->content_type =
3268 g_strdup("application/octet-stream");
3269 ainfo->encoding = ENC_BASE64;
3270 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text", 4))
3272 procmime_get_encoding_for_text_file(file, &has_binary);
3274 ainfo->encoding = ENC_BASE64;
3275 name = g_path_get_basename(filename ? filename : file);
3276 ainfo->name = g_strdup(name);
3280 if (ainfo->name != NULL
3281 && !strcmp(ainfo->name, ".")) {
3282 g_free(ainfo->name);
3286 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3287 g_free(ainfo->content_type);
3288 ainfo->content_type = g_strdup("application/octet-stream");
3292 size_text = to_human_readable(size);
3294 store = GTK_LIST_STORE(gtk_tree_view_get_model
3295 (GTK_TREE_VIEW(compose->attach_clist)));
3297 gtk_list_store_append(store, &iter);
3298 gtk_list_store_set(store, &iter,
3299 COL_MIMETYPE, ainfo->content_type,
3300 COL_SIZE, size_text,
3301 COL_NAME, ainfo->name,
3303 COL_AUTODATA, auto_ainfo,
3306 g_auto_pointer_free(auto_ainfo);
3310 static void compose_use_signing(Compose *compose, gboolean use_signing)
3312 GtkItemFactory *ifactory;
3313 GtkWidget *menuitem = NULL;
3315 compose->use_signing = use_signing;
3316 ifactory = gtk_item_factory_from_widget(compose->menubar);
3317 menuitem = gtk_item_factory_get_item
3318 (ifactory, "/Options/Sign");
3319 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
3323 static void compose_use_encryption(Compose *compose, gboolean use_encryption)
3325 GtkItemFactory *ifactory;
3326 GtkWidget *menuitem = NULL;
3328 compose->use_encryption = use_encryption;
3329 ifactory = gtk_item_factory_from_widget(compose->menubar);
3330 menuitem = gtk_item_factory_get_item
3331 (ifactory, "/Options/Encrypt");
3333 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
3337 #define NEXT_PART_NOT_CHILD(info) \
3339 node = info->node; \
3340 while (node->children) \
3341 node = g_node_last_child(node); \
3342 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3345 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3349 MimeInfo *firsttext = NULL;
3350 MimeInfo *encrypted = NULL;
3353 const gchar *partname = NULL;
3355 mimeinfo = procmime_scan_message(msginfo);
3356 if (!mimeinfo) return;
3358 if (mimeinfo->node->children == NULL) {
3359 procmime_mimeinfo_free_all(mimeinfo);
3363 /* find first content part */
3364 child = (MimeInfo *) mimeinfo->node->children->data;
3365 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3366 child = (MimeInfo *)child->node->children->data;
3368 if (child->type == MIMETYPE_TEXT) {
3370 debug_print("First text part found\n");
3371 } else if (compose->mode == COMPOSE_REEDIT &&
3372 child->type == MIMETYPE_APPLICATION &&
3373 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3374 encrypted = (MimeInfo *)child->node->parent->data;
3377 child = (MimeInfo *) mimeinfo->node->children->data;
3378 while (child != NULL) {
3381 if (child == encrypted) {
3382 /* skip this part of tree */
3383 NEXT_PART_NOT_CHILD(child);
3387 if (child->type == MIMETYPE_MULTIPART) {
3388 /* get the actual content */
3389 child = procmime_mimeinfo_next(child);
3393 if (child == firsttext) {
3394 child = procmime_mimeinfo_next(child);
3398 outfile = procmime_get_tmp_file_name(child);
3399 if ((err = procmime_get_part(outfile, child)) < 0)
3400 g_warning("Can't get the part of multipart message. (%s)", strerror(-err));
3402 gchar *content_type;
3404 content_type = procmime_get_content_type_str(child->type, child->subtype);
3406 /* if we meet a pgp signature, we don't attach it, but
3407 * we force signing. */
3408 if ((strcmp(content_type, "application/pgp-signature") &&
3409 strcmp(content_type, "application/pkcs7-signature") &&
3410 strcmp(content_type, "application/x-pkcs7-signature"))
3411 || compose->mode == COMPOSE_REDIRECT) {
3412 partname = procmime_mimeinfo_get_parameter(child, "filename");
3413 if (partname == NULL)
3414 partname = procmime_mimeinfo_get_parameter(child, "name");
3415 if (partname == NULL)
3417 compose_attach_append(compose, outfile,
3418 partname, content_type);
3420 compose_force_signing(compose, compose->account);
3422 g_free(content_type);
3425 NEXT_PART_NOT_CHILD(child);
3427 procmime_mimeinfo_free_all(mimeinfo);
3430 #undef NEXT_PART_NOT_CHILD
3435 WAIT_FOR_INDENT_CHAR,
3436 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3439 /* return indent length, we allow:
3440 indent characters followed by indent characters or spaces/tabs,
3441 alphabets and numbers immediately followed by indent characters,
3442 and the repeating sequences of the above
3443 If quote ends with multiple spaces, only the first one is included. */
3444 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3445 const GtkTextIter *start, gint *len)
3447 GtkTextIter iter = *start;
3451 IndentState state = WAIT_FOR_INDENT_CHAR;
3454 gint alnum_count = 0;
3455 gint space_count = 0;
3458 if (prefs_common.quote_chars == NULL) {
3462 while (!gtk_text_iter_ends_line(&iter)) {
3463 wc = gtk_text_iter_get_char(&iter);
3464 if (g_unichar_iswide(wc))
3466 clen = g_unichar_to_utf8(wc, ch);
3470 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
3471 is_space = g_unichar_isspace(wc);
3473 if (state == WAIT_FOR_INDENT_CHAR) {
3474 if (!is_indent && !g_unichar_isalnum(wc))
3477 quote_len += alnum_count + space_count + 1;
3478 alnum_count = space_count = 0;
3479 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
3482 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
3483 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
3487 else if (is_indent) {
3488 quote_len += alnum_count + space_count + 1;
3489 alnum_count = space_count = 0;
3492 state = WAIT_FOR_INDENT_CHAR;
3496 gtk_text_iter_forward_char(&iter);
3499 if (quote_len > 0 && space_count > 0)
3505 if (quote_len > 0) {
3507 gtk_text_iter_forward_chars(&iter, quote_len);
3508 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
3514 /* return TRUE if the line is itemized */
3515 static gboolean compose_is_itemized(GtkTextBuffer *buffer,
3516 const GtkTextIter *start)
3518 GtkTextIter iter = *start;
3523 if (gtk_text_iter_ends_line(&iter))
3527 wc = gtk_text_iter_get_char(&iter);
3528 if (!g_unichar_isspace(wc))
3530 gtk_text_iter_forward_char(&iter);
3531 if (gtk_text_iter_ends_line(&iter))
3535 clen = g_unichar_to_utf8(wc, ch);
3539 if (!strchr("*-+", ch[0]))
3542 gtk_text_iter_forward_char(&iter);
3543 if (gtk_text_iter_ends_line(&iter))
3545 wc = gtk_text_iter_get_char(&iter);
3546 if (g_unichar_isspace(wc))
3552 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
3553 const GtkTextIter *start,
3554 GtkTextIter *break_pos,
3558 GtkTextIter iter = *start, line_end = *start;
3559 PangoLogAttr *attrs;
3566 gboolean can_break = FALSE;
3567 gboolean do_break = FALSE;
3568 gboolean was_white = FALSE;
3569 gboolean prev_dont_break = FALSE;
3571 gtk_text_iter_forward_to_line_end(&line_end);
3572 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
3573 len = g_utf8_strlen(str, -1);
3574 /* g_print("breaking line: %d: %s (len = %d)\n",
3575 gtk_text_iter_get_line(&iter), str, len); */
3576 attrs = g_new(PangoLogAttr, len + 1);
3578 pango_default_break(str, -1, NULL, attrs, len + 1);
3582 /* skip quote and leading spaces */
3583 for (i = 0; *p != '\0' && i < len; i++) {
3586 wc = g_utf8_get_char(p);
3587 if (i >= quote_len && !g_unichar_isspace(wc))
3589 if (g_unichar_iswide(wc))
3591 else if (*p == '\t')
3595 p = g_utf8_next_char(p);
3598 for (; *p != '\0' && i < len; i++) {
3599 PangoLogAttr *attr = attrs + i;
3603 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
3606 was_white = attr->is_white;
3608 /* don't wrap URI */
3609 if ((uri_len = get_uri_len(p)) > 0) {
3611 if (pos > 0 && col > max_col) {
3621 wc = g_utf8_get_char(p);
3622 if (g_unichar_iswide(wc)) {
3624 if (prev_dont_break && can_break && attr->is_line_break)
3626 } else if (*p == '\t')
3630 if (pos > 0 && col > max_col) {
3635 if (*p == '-' || *p == '/')
3636 prev_dont_break = TRUE;
3638 prev_dont_break = FALSE;
3640 p = g_utf8_next_char(p);
3644 debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col);
3649 *break_pos = *start;
3650 gtk_text_iter_set_line_offset(break_pos, pos);
3655 static gboolean compose_join_next_line(Compose *compose,
3656 GtkTextBuffer *buffer,
3658 const gchar *quote_str)
3660 GtkTextIter iter_ = *iter, cur, prev, next, end;
3661 PangoLogAttr attrs[3];
3663 gchar *next_quote_str;
3666 gboolean keep_cursor = FALSE;
3668 if (!gtk_text_iter_forward_line(&iter_) ||
3669 gtk_text_iter_ends_line(&iter_))
3672 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
3674 if ((quote_str || next_quote_str) &&
3675 strcmp2(quote_str, next_quote_str) != 0) {
3676 g_free(next_quote_str);
3679 g_free(next_quote_str);
3682 if (quote_len > 0) {
3683 gtk_text_iter_forward_chars(&end, quote_len);
3684 if (gtk_text_iter_ends_line(&end))
3688 /* don't join itemized lines */
3689 if (compose_is_itemized(buffer, &end))
3692 /* don't join signature separator */
3693 if (compose_is_sig_separator(compose, buffer, &iter_))
3696 /* delete quote str */
3698 gtk_text_buffer_delete(buffer, &iter_, &end);
3700 /* don't join line breaks put by the user */
3702 gtk_text_iter_backward_char(&cur);
3703 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
3704 gtk_text_iter_forward_char(&cur);
3708 gtk_text_iter_forward_char(&cur);
3709 /* delete linebreak and extra spaces */
3710 while (gtk_text_iter_backward_char(&cur)) {
3711 wc1 = gtk_text_iter_get_char(&cur);
3712 if (!g_unichar_isspace(wc1))
3717 while (!gtk_text_iter_ends_line(&cur)) {
3718 wc1 = gtk_text_iter_get_char(&cur);
3719 if (!g_unichar_isspace(wc1))
3721 gtk_text_iter_forward_char(&cur);
3724 if (!gtk_text_iter_equal(&prev, &next)) {
3727 mark = gtk_text_buffer_get_insert(buffer);
3728 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
3729 if (gtk_text_iter_equal(&prev, &cur))
3731 gtk_text_buffer_delete(buffer, &prev, &next);
3735 /* insert space if required */
3736 gtk_text_iter_backward_char(&prev);
3737 wc1 = gtk_text_iter_get_char(&prev);
3738 wc2 = gtk_text_iter_get_char(&next);
3739 gtk_text_iter_forward_char(&next);
3740 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
3741 pango_default_break(str, -1, NULL, attrs, 3);
3742 if (!attrs[1].is_line_break ||
3743 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
3744 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
3746 gtk_text_iter_backward_char(&iter_);
3747 gtk_text_buffer_place_cursor(buffer, &iter_);
3756 #define ADD_TXT_POS(bp_, ep_, pti_) \
3757 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
3758 last = last->next; \
3759 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
3760 last->next = NULL; \
3762 g_warning("alloc error scanning URIs\n"); \
3765 static gboolean automatic_break = FALSE;
3766 static void compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
3768 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3769 GtkTextBuffer *buffer;
3770 GtkTextIter iter, break_pos, end_of_line;
3771 gchar *quote_str = NULL;
3773 gboolean wrap_quote = prefs_common.linewrap_quote;
3774 gboolean prev_autowrap = compose->autowrap;
3775 gint startq_offset = -1, noq_offset = -1;
3776 gint uri_start = -1, uri_stop = -1;
3777 gint nouri_start = -1, nouri_stop = -1;
3778 gint num_blocks = 0;
3779 gint quotelevel = -1;
3781 compose->autowrap = FALSE;
3783 buffer = gtk_text_view_get_buffer(text);
3784 undo_wrapping(compose->undostruct, TRUE);
3789 mark = gtk_text_buffer_get_insert(buffer);
3790 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3793 /* move to paragraph start */
3794 gtk_text_iter_set_line_offset(&iter, 0);
3795 if (gtk_text_iter_ends_line(&iter)) {
3796 while (gtk_text_iter_ends_line(&iter) &&
3797 gtk_text_iter_forward_line(&iter))
3800 while (gtk_text_iter_backward_line(&iter)) {
3801 if (gtk_text_iter_ends_line(&iter)) {
3802 gtk_text_iter_forward_line(&iter);
3808 /* go until paragraph end (empty line) */
3810 while (!gtk_text_iter_ends_line(&iter)) {
3811 gchar *scanpos = NULL;
3812 /* parse table - in order of priority */
3814 const gchar *needle; /* token */
3816 /* token search function */
3817 gchar *(*search) (const gchar *haystack,
3818 const gchar *needle);
3819 /* part parsing function */
3820 gboolean (*parse) (const gchar *start,
3821 const gchar *scanpos,
3825 /* part to URI function */
3826 gchar *(*build_uri) (const gchar *bp,
3830 static struct table parser[] = {
3831 {"http://", strcasestr, get_uri_part, make_uri_string},
3832 {"https://", strcasestr, get_uri_part, make_uri_string},
3833 {"ftp://", strcasestr, get_uri_part, make_uri_string},
3834 {"sftp://", strcasestr, get_uri_part, make_uri_string},
3835 {"www.", strcasestr, get_uri_part, make_http_string},
3836 {"mailto:", strcasestr, get_uri_part, make_uri_string},
3837 {"@", strcasestr, get_email_part, make_email_string}
3839 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
3840 gint last_index = PARSE_ELEMS;
3842 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
3845 if (!prev_autowrap && num_blocks == 0) {
3847 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3848 G_CALLBACK(text_inserted),
3851 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
3854 uri_start = uri_stop = -1;
3856 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
3859 debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str);
3860 if (startq_offset == -1)
3861 startq_offset = gtk_text_iter_get_offset(&iter);
3862 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
3863 if (quotelevel > 2) {
3864 /* recycle colors */
3865 if (prefs_common.recycle_quote_colors)
3874 if (startq_offset == -1)
3875 noq_offset = gtk_text_iter_get_offset(&iter);
3879 if (prev_autowrap == FALSE && !force && !wrap_quote) {
3882 if (compose_get_line_break_pos(buffer, &iter, &break_pos,
3883 prefs_common.linewrap_len,
3885 GtkTextIter prev, next, cur;
3887 if (prev_autowrap != FALSE || force) {
3888 automatic_break = TRUE;
3889 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
3890 automatic_break = FALSE;
3891 } else if (quote_str && wrap_quote) {
3892 automatic_break = TRUE;
3893 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
3894 automatic_break = FALSE;
3897 /* remove trailing spaces */
3899 gtk_text_iter_backward_char(&cur);
3901 while (!gtk_text_iter_starts_line(&cur)) {
3904 gtk_text_iter_backward_char(&cur);
3905 wc = gtk_text_iter_get_char(&cur);
3906 if (!g_unichar_isspace(wc))
3910 if (!gtk_text_iter_equal(&prev, &next)) {
3911 gtk_text_buffer_delete(buffer, &prev, &next);
3913 gtk_text_iter_forward_char(&break_pos);
3917 gtk_text_buffer_insert(buffer, &break_pos,
3921 compose_join_next_line(compose, buffer, &iter, quote_str);
3923 /* move iter to current line start */
3924 gtk_text_iter_set_line_offset(&iter, 0);
3931 /* move iter to next line start */
3936 if (!prev_autowrap && num_blocks > 0) {
3938 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3939 G_CALLBACK(text_inserted),
3943 while (!gtk_text_iter_ends_line(&end_of_line)) {
3944 gtk_text_iter_forward_char(&end_of_line);
3946 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
3948 nouri_start = gtk_text_iter_get_offset(&iter);
3949 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
3951 walk_pos = gtk_text_iter_get_offset(&iter);
3952 /* FIXME: this looks phony. scanning for anything in the parse table */
3953 for (n = 0; n < PARSE_ELEMS; n++) {
3956 tmp = parser[n].search(walk, parser[n].needle);
3958 if (scanpos == NULL || tmp < scanpos) {
3967 /* check if URI can be parsed */
3968 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
3969 (const gchar **)&ep, FALSE)
3970 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
3974 strlen(parser[last_index].needle);
3977 uri_start = walk_pos + (bp - o_walk);
3978 uri_stop = walk_pos + (ep - o_walk);
3982 gtk_text_iter_forward_line(&iter);
3985 if (startq_offset != -1) {
3986 GtkTextIter startquote, endquote;
3987 gtk_text_buffer_get_iter_at_offset(
3988 buffer, &startquote, startq_offset);
3991 switch (quotelevel) {
3992 case 0: gtk_text_buffer_apply_tag_by_name(
3993 buffer, "quote0", &startquote, &endquote);
3995 case 1: gtk_text_buffer_apply_tag_by_name(
3996 buffer, "quote1", &startquote, &endquote);
3998 case 2: gtk_text_buffer_apply_tag_by_name(
3999 buffer, "quote2", &startquote, &endquote);
4003 } else if (noq_offset != -1) {
4004 GtkTextIter startnoquote, endnoquote;
4005 gtk_text_buffer_get_iter_at_offset(
4006 buffer, &startnoquote, noq_offset);
4008 gtk_text_buffer_remove_tag_by_name(
4009 buffer, "quote0", &startnoquote, &endnoquote);
4010 gtk_text_buffer_remove_tag_by_name(
4011 buffer, "quote1", &startnoquote, &endnoquote);
4012 gtk_text_buffer_remove_tag_by_name(
4013 buffer, "quote2", &startnoquote, &endnoquote);
4018 GtkTextIter nouri_start_iter, nouri_end_iter;
4019 gtk_text_buffer_get_iter_at_offset(
4020 buffer, &nouri_start_iter, nouri_start);
4021 gtk_text_buffer_get_iter_at_offset(
4022 buffer, &nouri_end_iter, nouri_stop);
4023 gtk_text_buffer_remove_tag_by_name(
4024 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4026 if (uri_start > 0 && uri_stop > 0) {
4027 GtkTextIter uri_start_iter, uri_end_iter;
4028 gtk_text_buffer_get_iter_at_offset(
4029 buffer, &uri_start_iter, uri_start);
4030 gtk_text_buffer_get_iter_at_offset(
4031 buffer, &uri_end_iter, uri_stop);
4032 gtk_text_buffer_apply_tag_by_name(
4033 buffer, "link", &uri_start_iter, &uri_end_iter);
4039 undo_wrapping(compose->undostruct, FALSE);
4040 compose->autowrap = prev_autowrap;
4043 void compose_action_cb(void *data)
4045 Compose *compose = (Compose *)data;
4046 compose_wrap_all(compose);
4049 static void compose_wrap_all(Compose *compose)
4051 compose_wrap_all_full(compose, FALSE);
4054 static void compose_wrap_all_full(Compose *compose, gboolean force)
4056 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4057 GtkTextBuffer *buffer;
4060 buffer = gtk_text_view_get_buffer(text);
4062 gtk_text_buffer_get_start_iter(buffer, &iter);
4063 while (!gtk_text_iter_is_end(&iter))
4064 compose_beautify_paragraph(compose, &iter, force);
4068 static void compose_set_title(Compose *compose)
4074 edited = compose->modified ? _(" [Edited]") : "";
4076 subject = gtk_editable_get_chars(
4077 GTK_EDITABLE(compose->subject_entry), 0, -1);
4080 if (subject && strlen(subject))
4081 str = g_strdup_printf(_("%s - Compose message%s"),
4084 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4086 str = g_strdup(_("Compose message"));
4089 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4095 * compose_current_mail_account:
4097 * Find a current mail account (the currently selected account, or the
4098 * default account, if a news account is currently selected). If a
4099 * mail account cannot be found, display an error message.
4101 * Return value: Mail account, or NULL if not found.
4103 static PrefsAccount *
4104 compose_current_mail_account(void)
4108 if (cur_account && cur_account->protocol != A_NNTP)
4111 ac = account_get_default();
4112 if (!ac || ac->protocol == A_NNTP) {
4113 alertpanel_error(_("Account for sending mail is not specified.\n"
4114 "Please select a mail account before sending."));
4121 #define QUOTE_IF_REQUIRED(out, str) \
4123 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4127 len = strlen(str) + 3; \
4128 if ((__tmp = alloca(len)) == NULL) { \
4129 g_warning("can't allocate memory\n"); \
4130 g_string_free(header, TRUE); \
4133 g_snprintf(__tmp, len, "\"%s\"", str); \
4138 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4139 g_warning("can't allocate memory\n"); \
4140 g_string_free(header, TRUE); \
4143 strcpy(__tmp, str); \
4149 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4151 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4155 len = strlen(str) + 3; \
4156 if ((__tmp = alloca(len)) == NULL) { \
4157 g_warning("can't allocate memory\n"); \
4160 g_snprintf(__tmp, len, "\"%s\"", str); \
4165 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4166 g_warning("can't allocate memory\n"); \
4169 strcpy(__tmp, str); \
4175 static void compose_select_account(Compose *compose, PrefsAccount *account,
4178 GtkItemFactory *ifactory;
4181 g_return_if_fail(account != NULL);
4183 compose->account = account;
4185 if (account->name && *account->name) {
4187 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4188 from = g_strdup_printf("%s <%s>",
4189 buf, account->address);
4190 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4192 from = g_strdup_printf("<%s>",
4194 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4199 compose_set_title(compose);
4201 ifactory = gtk_item_factory_from_widget(compose->menubar);
4203 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4204 menu_set_active(ifactory, "/Options/Sign", TRUE);
4206 menu_set_active(ifactory, "/Options/Sign", FALSE);
4207 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4208 menu_set_active(ifactory, "/Options/Encrypt", TRUE);
4210 menu_set_active(ifactory, "/Options/Encrypt", FALSE);
4212 activate_privacy_system(compose, account, FALSE);
4214 if (!init && compose->mode != COMPOSE_REDIRECT) {
4215 undo_block(compose->undostruct);
4216 compose_insert_sig(compose, TRUE);
4217 undo_unblock(compose->undostruct);
4221 /* use account's dict info if set */
4222 if (compose->gtkaspell) {
4223 if (account->enable_default_dictionary)
4224 gtkaspell_change_dict(compose->gtkaspell,
4225 account->default_dictionary, FALSE);
4226 if (account->enable_default_alt_dictionary)
4227 gtkaspell_change_alt_dict(compose->gtkaspell,
4228 account->default_alt_dictionary);
4229 if (account->enable_default_dictionary
4230 || account->enable_default_alt_dictionary)
4231 compose_spell_menu_changed(compose);
4236 gboolean compose_check_for_valid_recipient(Compose *compose) {
4237 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
4238 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
4239 gboolean recipient_found = FALSE;
4243 /* free to and newsgroup list */
4244 slist_free_strings(compose->to_list);
4245 g_slist_free(compose->to_list);
4246 compose->to_list = NULL;
4248 slist_free_strings(compose->newsgroup_list);
4249 g_slist_free(compose->newsgroup_list);
4250 compose->newsgroup_list = NULL;
4252 /* search header entries for to and newsgroup entries */
4253 for (list = compose->header_list; list; list = list->next) {
4256 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_COMBO(((ComposeHeaderEntry *)list->data)->combo)->entry), 0, -1);
4257 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4260 if (entry[0] != '\0') {
4261 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
4262 if (!strcmp(header, prefs_common_translated_header_name(*strptr))) {
4263 compose->to_list = address_list_append(compose->to_list, entry);
4264 recipient_found = TRUE;
4267 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
4268 if (!strcmp(header, prefs_common_translated_header_name(*strptr))) {
4269 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
4270 recipient_found = TRUE;
4277 return recipient_found;
4280 static gboolean compose_check_for_set_recipients(Compose *compose)
4282 if (compose->account->set_autocc && compose->account->auto_cc) {
4283 gboolean found_other = FALSE;
4285 /* search header entries for to and newsgroup entries */
4286 for (list = compose->header_list; list; list = list->next) {
4289 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4290 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_COMBO(((ComposeHeaderEntry *)list->data)->combo)->entry), 0, -1);
4292 if (strcmp(entry, compose->account->auto_cc)
4293 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
4303 if (compose->batch) {
4304 gtk_widget_show_all(compose->window);
4306 aval = alertpanel(_("Send"),
4307 _("The only recipient is the default CC address. Send anyway?"),
4308 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4309 if (aval != G_ALERTALTERNATE)
4313 if (compose->account->set_autobcc && compose->account->auto_bcc) {
4314 gboolean found_other = FALSE;
4316 /* search header entries for to and newsgroup entries */
4317 for (list = compose->header_list; list; list = list->next) {
4320 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4321 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_COMBO(((ComposeHeaderEntry *)list->data)->combo)->entry), 0, -1);
4323 if (strcmp(entry, compose->account->auto_bcc)
4324 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
4334 if (compose->batch) {
4335 gtk_widget_show_all(compose->window);
4337 aval = alertpanel(_("Send"),
4338 _("The only recipient is the default BCC address. Send anyway?"),
4339 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4340 if (aval != G_ALERTALTERNATE)
4347 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
4351 if (compose_check_for_valid_recipient(compose) == FALSE) {
4352 if (compose->batch) {
4353 gtk_widget_show_all(compose->window);
4355 alertpanel_error(_("Recipient is not specified."));
4359 if (compose_check_for_set_recipients(compose) == FALSE) {
4363 if (!compose->batch) {
4364 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
4365 if (*str == '\0' && check_everything == TRUE &&
4366 compose->mode != COMPOSE_REDIRECT) {
4369 aval = alertpanel(_("Send"),
4370 _("Subject is empty. Send it anyway?"),
4371 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4372 if (aval != G_ALERTALTERNATE)
4377 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
4383 gint compose_send(Compose *compose)
4386 FolderItem *folder = NULL;
4388 gchar *msgpath = NULL;
4389 gboolean discard_window = FALSE;
4390 gchar *errstr = NULL;
4391 gchar *tmsgid = NULL;
4392 MainWindow *mainwin = mainwindow_get_mainwindow();
4393 gboolean queued_removed = FALSE;
4395 if (prefs_common.send_dialog_mode != SEND_DIALOG_ALWAYS
4396 || compose->batch == TRUE)
4397 discard_window = TRUE;
4399 compose_allow_user_actions (compose, FALSE);
4400 compose->sending = TRUE;
4402 if (compose_check_entries(compose, TRUE) == FALSE) {
4403 if (compose->batch) {
4404 gtk_widget_show_all(compose->window);
4410 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
4413 if (compose->batch) {
4414 gtk_widget_show_all(compose->window);
4417 alertpanel_error(_("Could not queue message for sending:\n\n"
4418 "Charset conversion failed."));
4419 } else if (val == -5) {
4420 alertpanel_error(_("Could not queue message for sending:\n\n"
4421 "Couldn't get recipient encryption key."));
4422 } else if (val == -6) {
4424 } else if (val == -3) {
4425 if (privacy_peek_error())
4426 alertpanel_error(_("Could not queue message for sending:\n\n"
4427 "Signature failed: %s"), privacy_get_error());
4428 } else if (val == -2 && errno != 0) {
4429 alertpanel_error(_("Could not queue message for sending:\n\n%s."), strerror(errno));
4431 alertpanel_error(_("Could not queue message for sending."));
4436 tmsgid = g_strdup(compose->msgid);
4437 if (discard_window) {
4438 compose->sending = FALSE;
4439 compose_close(compose);
4440 /* No more compose access in the normal codepath
4441 * after this point! */
4446 alertpanel_error(_("The message was queued but could not be "
4447 "sent.\nUse \"Send queued messages\" from "
4448 "the main window to retry."));
4449 if (!discard_window) {
4456 if (msgpath == NULL) {
4457 msgpath = folder_item_fetch_msg(folder, msgnum);
4458 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
4461 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
4465 if (!discard_window) {
4467 if (!queued_removed)
4468 folder_item_remove_msg(folder, msgnum);
4469 folder_item_scan(folder);
4471 /* make sure we delete that */
4472 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
4474 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
4475 folder_item_remove_msg(folder, tmp->msgnum);
4476 procmsg_msginfo_free(tmp);
4483 if (!queued_removed)
4484 folder_item_remove_msg(folder, msgnum);
4485 folder_item_scan(folder);
4487 /* make sure we delete that */
4488 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
4490 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
4491 folder_item_remove_msg(folder, tmp->msgnum);
4492 procmsg_msginfo_free(tmp);
4495 if (!discard_window) {
4496 compose->sending = FALSE;
4497 compose_allow_user_actions (compose, TRUE);
4498 compose_close(compose);
4502 gchar *tmp = g_strdup_printf(_("%s\nUse \"Send queued messages\" from "
4503 "the main window to retry."), errstr);
4505 alertpanel_error_log(tmp);
4508 alertpanel_error_log(_("The message was queued but could not be "
4509 "sent.\nUse \"Send queued messages\" from "
4510 "the main window to retry."));
4512 if (!discard_window) {
4521 toolbar_main_set_sensitive(mainwin);
4522 main_window_set_menu_sensitive(mainwin);
4528 compose_allow_user_actions (compose, TRUE);
4529 compose->sending = FALSE;
4530 compose->modified = TRUE;
4531 toolbar_main_set_sensitive(mainwin);
4532 main_window_set_menu_sensitive(mainwin);
4537 static gboolean compose_use_attach(Compose *compose)
4539 GtkTreeModel *model = gtk_tree_view_get_model
4540 (GTK_TREE_VIEW(compose->attach_clist));
4541 return gtk_tree_model_iter_n_children(model, NULL) > 0;
4544 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
4547 gchar buf[BUFFSIZE];
4549 gboolean first_to_address;
4550 gboolean first_cc_address;
4552 ComposeHeaderEntry *headerentry;
4553 const gchar *headerentryname;
4554 const gchar *cc_hdr;
4555 const gchar *to_hdr;
4557 debug_print("Writing redirect header\n");
4559 cc_hdr = prefs_common_translated_header_name("Cc:");
4560 to_hdr = prefs_common_translated_header_name("To:");
4562 first_to_address = TRUE;
4563 for (list = compose->header_list; list; list = list->next) {
4564 headerentry = ((ComposeHeaderEntry *)list->data);
4565 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(headerentry->combo)->entry));
4567 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
4568 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
4569 Xstrdup_a(str, entstr, return -1);
4571 if (str[0] != '\0') {
4572 compose_convert_header
4573 (compose, buf, sizeof(buf), str,
4574 strlen("Resent-To") + 2, TRUE);
4576 if (first_to_address) {
4577 fprintf(fp, "Resent-To: ");
4578 first_to_address = FALSE;
4582 fprintf(fp, "%s", buf);
4586 if (!first_to_address) {
4590 first_cc_address = TRUE;
4591 for (list = compose->header_list; list; list = list->next) {
4592 headerentry = ((ComposeHeaderEntry *)list->data);
4593 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(headerentry->combo)->entry));
4595 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
4596 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
4597 Xstrdup_a(str, strg, return -1);
4599 if (str[0] != '\0') {
4600 compose_convert_header
4601 (compose, buf, sizeof(buf), str,
4602 strlen("Resent-Cc") + 2, TRUE);
4604 if (first_cc_address) {
4605 fprintf(fp, "Resent-Cc: ");
4606 first_cc_address = FALSE;
4610 fprintf(fp, "%s", buf);
4614 if (!first_cc_address) {
4621 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
4623 gchar buf[BUFFSIZE];
4625 const gchar *entstr;
4626 /* struct utsname utsbuf; */
4628 g_return_val_if_fail(fp != NULL, -1);
4629 g_return_val_if_fail(compose->account != NULL, -1);
4630 g_return_val_if_fail(compose->account->address != NULL, -1);
4633 get_rfc822_date(buf, sizeof(buf));
4634 fprintf(fp, "Resent-Date: %s\n", buf);
4637 if (compose->account->name && *compose->account->name) {
4638 compose_convert_header
4639 (compose, buf, sizeof(buf), compose->account->name,
4640 strlen("From: "), TRUE);
4641 fprintf(fp, "Resent-From: %s <%s>\n",
4642 buf, compose->account->address);
4644 fprintf(fp, "Resent-From: %s\n", compose->account->address);
4647 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
4648 if (*entstr != '\0') {
4649 Xstrdup_a(str, entstr, return -1);
4652 compose_convert_header(compose, buf, sizeof(buf), str,
4653 strlen("Subject: "), FALSE);
4654 fprintf(fp, "Subject: %s\n", buf);
4658 /* Resent-Message-ID */
4659 if (compose->account->gen_msgid) {
4660 generate_msgid(buf, sizeof(buf));
4661 fprintf(fp, "Resent-Message-ID: <%s>\n", buf);
4662 compose->msgid = g_strdup(buf);
4665 compose_redirect_write_headers_from_headerlist(compose, fp);
4667 /* separator between header and body */
4673 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
4677 gchar buf[BUFFSIZE];
4679 gboolean skip = FALSE;
4680 gchar *not_included[]={
4681 "Return-Path:", "Delivered-To:", "Received:",
4682 "Subject:", "X-UIDL:", "AF:",
4683 "NF:", "PS:", "SRH:",
4684 "SFN:", "DSR:", "MID:",
4685 "CFG:", "PT:", "S:",
4686 "RQ:", "SSV:", "NSV:",
4687 "SSH:", "R:", "MAID:",
4688 "NAID:", "RMID:", "FMID:",
4689 "SCF:", "RRCPT:", "NG:",
4690 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
4691 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
4692 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
4693 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
4696 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
4697 FILE_OP_ERROR(compose->redirect_filename, "fopen");
4701 while (procheader_get_one_field_asis(buf, sizeof(buf), fp) != -1) {
4703 for (i = 0; not_included[i] != NULL; i++) {
4704 if (g_ascii_strncasecmp(buf, not_included[i],
4705 strlen(not_included[i])) == 0) {
4712 if (fputs(buf, fdest) == -1)
4715 if (!prefs_common.redirect_keep_from) {
4716 if (g_ascii_strncasecmp(buf, "From:",
4717 strlen("From:")) == 0) {
4718 fputs(" (by way of ", fdest);
4719 if (compose->account->name
4720 && *compose->account->name) {
4721 compose_convert_header
4722 (compose, buf, sizeof(buf),
4723 compose->account->name,
4726 fprintf(fdest, "%s <%s>",
4728 compose->account->address);
4730 fprintf(fdest, "%s",
4731 compose->account->address);
4736 if (fputs("\n", fdest) == -1)
4740 compose_redirect_write_headers(compose, fdest);
4742 while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
4743 if (fwrite(buf, sizeof(gchar), len, fdest) != len)
4756 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
4758 GtkTextBuffer *buffer;
4759 GtkTextIter start, end;
4762 const gchar *out_codeset;
4763 EncodingType encoding;
4764 MimeInfo *mimemsg, *mimetext;
4767 if (action == COMPOSE_WRITE_FOR_SEND)
4768 attach_parts = TRUE;
4770 /* create message MimeInfo */
4771 mimemsg = procmime_mimeinfo_new();
4772 mimemsg->type = MIMETYPE_MESSAGE;
4773 mimemsg->subtype = g_strdup("rfc822");
4774 mimemsg->content = MIMECONTENT_MEM;
4775 mimemsg->tmp = TRUE; /* must free content later */
4776 mimemsg->data.mem = compose_get_header(compose);
4778 /* Create text part MimeInfo */
4779 /* get all composed text */
4780 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
4781 gtk_text_buffer_get_start_iter(buffer, &start);
4782 gtk_text_buffer_get_end_iter(buffer, &end);
4783 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
4784 if (is_ascii_str(chars)) {
4787 out_codeset = CS_US_ASCII;
4788 encoding = ENC_7BIT;
4790 const gchar *src_codeset = CS_INTERNAL;
4792 out_codeset = conv_get_charset_str(compose->out_encoding);
4795 gchar *test_conv_global_out = NULL;
4796 gchar *test_conv_reply = NULL;
4798 /* automatic mode. be automatic. */
4799 codeconv_set_strict(TRUE);
4801 out_codeset = conv_get_outgoing_charset_str();
4803 debug_print("trying to convert to %s\n", out_codeset);
4804 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
4807 if (!test_conv_global_out && compose->orig_charset
4808 && strcmp(compose->orig_charset, CS_US_ASCII)) {
4809 out_codeset = compose->orig_charset;
4810 debug_print("failure; trying to convert to %s\n", out_codeset);
4811 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
4814 if (!test_conv_global_out && !test_conv_reply) {
4816 out_codeset = CS_INTERNAL;
4817 debug_print("failure; finally using %s\n", out_codeset);
4819 g_free(test_conv_global_out);
4820 g_free(test_conv_reply);
4821 codeconv_set_strict(FALSE);
4824 if (!g_ascii_strcasecmp(out_codeset, CS_US_ASCII))
4825 out_codeset = CS_ISO_8859_1;
4827 if (prefs_common.encoding_method == CTE_BASE64)
4828 encoding = ENC_BASE64;
4829 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
4830 encoding = ENC_QUOTED_PRINTABLE;
4831 else if (prefs_common.encoding_method == CTE_8BIT)
4832 encoding = ENC_8BIT;
4834 encoding = procmime_get_encoding_for_charset(out_codeset);
4836 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
4837 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
4839 if (action == COMPOSE_WRITE_FOR_SEND) {
4840 codeconv_set_strict(TRUE);
4841 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
4842 codeconv_set_strict(FALSE);
4848 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
4849 "to the specified %s charset.\n"
4850 "Send it as %s?"), out_codeset, src_codeset);
4851 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL, _("+_Send"), NULL, FALSE,
4852 NULL, ALERT_ERROR, G_ALERTDEFAULT);
4855 if (aval != G_ALERTALTERNATE) {
4860 out_codeset = src_codeset;
4866 out_codeset = src_codeset;
4872 if (encoding == ENC_8BIT || encoding == ENC_7BIT) {
4873 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
4874 strstr(buf, "\nFrom ") != NULL) {
4875 encoding = ENC_QUOTED_PRINTABLE;
4879 mimetext = procmime_mimeinfo_new();
4880 mimetext->content = MIMECONTENT_MEM;
4881 mimetext->tmp = TRUE; /* must free content later */
4882 /* dup'ed because procmime_encode_content can turn it into a tmpfile
4883 * and free the data, which we need later. */
4884 mimetext->data.mem = g_strdup(buf);
4885 mimetext->type = MIMETYPE_TEXT;
4886 mimetext->subtype = g_strdup("plain");
4887 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
4888 g_strdup(out_codeset));
4890 /* protect trailing spaces when signing message */
4891 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
4892 privacy_system_can_sign(compose->privacy_system)) {
4893 encoding = ENC_QUOTED_PRINTABLE;
4896 debug_print("main text: %d bytes encoded as %s in %d\n",
4897 strlen(buf), out_codeset, encoding);
4899 /* check for line length limit */
4900 if (action == COMPOSE_WRITE_FOR_SEND &&
4901 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
4902 check_line_length(buf, 1000, &line) < 0) {
4906 msg = g_strdup_printf
4907 (_("Line %d exceeds the line length limit (998 bytes).\n"
4908 "The contents of the message might be broken on the way to the delivery.\n"
4910 "Send it anyway?"), line + 1);
4911 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
4913 if (aval != G_ALERTALTERNATE) {
4919 if (encoding != ENC_UNKNOWN)
4920 procmime_encode_content(mimetext, encoding);
4922 /* append attachment parts */
4923 if (compose_use_attach(compose) && attach_parts) {
4924 MimeInfo *mimempart;
4925 gchar *boundary = NULL;
4926 mimempart = procmime_mimeinfo_new();
4927 mimempart->content = MIMECONTENT_EMPTY;
4928 mimempart->type = MIMETYPE_MULTIPART;
4929 mimempart->subtype = g_strdup("mixed");
4933 boundary = generate_mime_boundary(NULL);
4934 } while (strstr(buf, boundary) != NULL);
4936 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
4939 mimetext->disposition = DISPOSITIONTYPE_INLINE;
4941 g_node_append(mimempart->node, mimetext->node);
4942 g_node_append(mimemsg->node, mimempart->node);
4944 compose_add_attachments(compose, mimempart);
4946 g_node_append(mimemsg->node, mimetext->node);
4950 /* sign message if sending */
4951 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
4952 privacy_system_can_sign(compose->privacy_system))
4953 if (!privacy_sign(compose->privacy_system, mimemsg, compose->account))
4956 procmime_write_mimeinfo(mimemsg, fp);
4958 procmime_mimeinfo_free_all(mimemsg);
4963 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
4965 GtkTextBuffer *buffer;
4966 GtkTextIter start, end;
4971 if ((fp = g_fopen(file, "wb")) == NULL) {
4972 FILE_OP_ERROR(file, "fopen");
4976 /* chmod for security */
4977 if (change_file_mode_rw(fp, file) < 0) {
4978 FILE_OP_ERROR(file, "chmod");
4979 g_warning("can't change file mode\n");
4982 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
4983 gtk_text_buffer_get_start_iter(buffer, &start);
4984 gtk_text_buffer_get_end_iter(buffer, &end);
4985 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
4987 chars = conv_codeset_strdup
4988 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
4991 if (!chars) return -1;
4994 len = strlen(chars);
4995 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
4996 FILE_OP_ERROR(file, "fwrite");
5005 if (fclose(fp) == EOF) {
5006 FILE_OP_ERROR(file, "fclose");
5013 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5016 MsgInfo *msginfo = compose->targetinfo;
5018 g_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5019 if (!msginfo) return -1;
5021 if (!force && MSG_IS_LOCKED(msginfo->flags))
5024 item = msginfo->folder;
5025 g_return_val_if_fail(item != NULL, -1);
5027 if (procmsg_msg_exist(msginfo) &&
5028 (folder_has_parent_of_type(item, F_QUEUE) ||
5029 folder_has_parent_of_type(item, F_DRAFT)
5030 || msginfo == compose->autosaved_draft)) {
5031 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5032 g_warning("can't remove the old message\n");
5040 static void compose_remove_draft(Compose *compose)
5043 MsgInfo *msginfo = compose->targetinfo;
5044 drafts = account_get_special_folder(compose->account, F_DRAFT);
5046 if (procmsg_msg_exist(msginfo)) {
5047 folder_item_remove_msg(drafts, msginfo->msgnum);
5052 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5053 gboolean remove_reedit_target)
5055 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5058 static gboolean compose_warn_encryption(Compose *compose)
5060 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5061 AlertValue val = G_ALERTALTERNATE;
5063 if (warning == NULL)
5066 val = alertpanel_full(_("Encryption warning"), warning,
5067 GTK_STOCK_CANCEL, _("+C_ontinue"), NULL,
5068 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5069 if (val & G_ALERTDISABLE) {
5070 val &= ~G_ALERTDISABLE;
5071 if (val == G_ALERTALTERNATE)
5072 privacy_inhibit_encrypt_warning(compose->privacy_system,
5076 if (val == G_ALERTALTERNATE) {
5083 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
5084 gchar **msgpath, gboolean check_subject,
5085 gboolean remove_reedit_target)
5092 static gboolean lock = FALSE;
5093 PrefsAccount *mailac = NULL, *newsac = NULL;
5095 debug_print("queueing message...\n");
5096 g_return_val_if_fail(compose->account != NULL, -1);
5100 if (compose_check_entries(compose, check_subject) == FALSE) {
5102 if (compose->batch) {
5103 gtk_widget_show_all(compose->window);
5108 if (!compose->to_list && !compose->newsgroup_list) {
5109 g_warning("can't get recipient list.");
5114 if (compose->to_list) {
5115 if (compose->account->protocol != A_NNTP)
5116 mailac = compose->account;
5117 else if (cur_account && cur_account->protocol != A_NNTP)
5118 mailac = cur_account;
5119 else if (!(mailac = compose_current_mail_account())) {
5121 alertpanel_error(_("No account for sending mails available!"));
5126 if (compose->newsgroup_list) {
5127 if (compose->account->protocol == A_NNTP)
5128 newsac = compose->account;
5129 else if (!newsac->protocol != A_NNTP) {
5131 alertpanel_error(_("No account for posting news available!"));
5136 /* write queue header */
5137 tmp = g_strdup_printf("%s%cqueue.%p", get_tmp_dir(),
5138 G_DIR_SEPARATOR, compose);
5139 if ((fp = g_fopen(tmp, "wb")) == NULL) {
5140 FILE_OP_ERROR(tmp, "fopen");
5146 if (change_file_mode_rw(fp, tmp) < 0) {
5147 FILE_OP_ERROR(tmp, "chmod");
5148 g_warning("can't change file mode\n");
5151 /* queueing variables */
5152 fprintf(fp, "AF:\n");
5153 fprintf(fp, "NF:0\n");
5154 fprintf(fp, "PS:10\n");
5155 fprintf(fp, "SRH:1\n");
5156 fprintf(fp, "SFN:\n");
5157 fprintf(fp, "DSR:\n");
5159 fprintf(fp, "MID:<%s>\n", compose->msgid);
5161 fprintf(fp, "MID:\n");
5162 fprintf(fp, "CFG:\n");
5163 fprintf(fp, "PT:0\n");
5164 fprintf(fp, "S:%s\n", compose->account->address);
5165 fprintf(fp, "RQ:\n");
5167 fprintf(fp, "SSV:%s\n", mailac->smtp_server);
5169 fprintf(fp, "SSV:\n");
5171 fprintf(fp, "NSV:%s\n", newsac->nntp_server);
5173 fprintf(fp, "NSV:\n");
5174 fprintf(fp, "SSH:\n");
5175 /* write recepient list */
5176 if (compose->to_list) {
5177 fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data);
5178 for (cur = compose->to_list->next; cur != NULL;
5180 fprintf(fp, ",<%s>", (gchar *)cur->data);
5183 /* write newsgroup list */
5184 if (compose->newsgroup_list) {
5186 fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data);
5187 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
5188 fprintf(fp, ",%s", (gchar *)cur->data);
5191 /* Sylpheed account IDs */
5193 fprintf(fp, "MAID:%d\n", mailac->account_id);
5195 fprintf(fp, "NAID:%d\n", newsac->account_id);
5198 if (compose->privacy_system != NULL) {
5199 fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system);
5200 fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing);
5201 if (compose->use_encryption) {
5203 if (!compose_warn_encryption(compose)) {
5210 if (mailac && mailac->encrypt_to_self) {
5211 GSList *tmp_list = g_slist_copy(compose->to_list);
5212 tmp_list = g_slist_append(tmp_list, compose->account->address);
5213 encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
5214 g_slist_free(tmp_list);
5216 encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
5218 if (encdata != NULL) {
5219 if (strcmp(encdata, "_DONT_ENCRYPT_")) {
5220 fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption);
5221 fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
5223 } /* else we finally dont want to encrypt */
5225 fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption);
5226 /* and if encdata was null, it means there's been a problem in
5238 /* Save copy folder */
5239 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
5240 gchar *savefolderid;
5242 savefolderid = gtk_editable_get_chars(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
5243 fprintf(fp, "SCF:%s\n", savefolderid);
5244 g_free(savefolderid);
5246 /* Save copy folder */
5247 if (compose->return_receipt) {
5248 fprintf(fp, "RRCPT:1\n");
5250 /* Message-ID of message replying to */
5251 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
5254 folderid = folder_item_get_identifier(compose->replyinfo->folder);
5255 fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid);
5258 /* Message-ID of message forwarding to */
5259 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
5262 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
5263 fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid);
5267 /* end of headers */
5268 fprintf(fp, "X-Claws-End-Special-Headers: 1\n");
5270 if (compose->redirect_filename != NULL) {
5271 if (compose_redirect_write_to_file(compose, fp) < 0) {
5280 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
5285 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
5289 if (fclose(fp) == EOF) {
5290 FILE_OP_ERROR(tmp, "fclose");
5297 if (item && *item) {
5300 queue = account_get_special_folder(compose->account, F_QUEUE);
5303 g_warning("can't find queue folder\n");
5309 folder_item_scan(queue);
5310 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
5311 g_warning("can't queue the message\n");
5318 if (msgpath == NULL) {
5324 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
5325 compose_remove_reedit_target(compose, FALSE);
5328 if ((msgnum != NULL) && (item != NULL)) {
5336 static void compose_add_attachments(Compose *compose, MimeInfo *parent)
5339 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
5341 struct stat statbuf;
5342 gchar *type, *subtype;
5343 GtkTreeModel *model;
5346 model = gtk_tree_view_get_model(tree_view);
5348 if (!gtk_tree_model_get_iter_first(model, &iter))
5351 gtk_tree_model_get(model, &iter,
5355 mimepart = procmime_mimeinfo_new();
5356 mimepart->content = MIMECONTENT_FILE;
5357 mimepart->data.filename = g_strdup(ainfo->file);
5358 mimepart->tmp = FALSE; /* or we destroy our attachment */
5359 mimepart->offset = 0;
5361 stat(ainfo->file, &statbuf);
5362 mimepart->length = statbuf.st_size;
5364 type = g_strdup(ainfo->content_type);
5366 if (!strchr(type, '/')) {
5368 type = g_strdup("application/octet-stream");
5371 subtype = strchr(type, '/') + 1;
5372 *(subtype - 1) = '\0';
5373 mimepart->type = procmime_get_media_type(type);
5374 mimepart->subtype = g_strdup(subtype);
5377 if (mimepart->type == MIMETYPE_MESSAGE &&
5378 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
5379 mimepart->disposition = DISPOSITIONTYPE_INLINE;
5382 g_hash_table_insert(mimepart->typeparameters,
5383 g_strdup("name"), g_strdup(ainfo->name));
5384 g_hash_table_insert(mimepart->dispositionparameters,
5385 g_strdup("filename"), g_strdup(ainfo->name));
5386 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
5390 if (compose->use_signing) {
5391 if (ainfo->encoding == ENC_7BIT)
5392 ainfo->encoding = ENC_QUOTED_PRINTABLE;
5393 else if (ainfo->encoding == ENC_8BIT)
5394 ainfo->encoding = ENC_BASE64;
5397 procmime_encode_content(mimepart, ainfo->encoding);
5399 g_node_append(parent->node, mimepart->node);
5400 } while (gtk_tree_model_iter_next(model, &iter));
5403 #define IS_IN_CUSTOM_HEADER(header) \
5404 (compose->account->add_customhdr && \
5405 custom_header_find(compose->account->customhdr_list, header) != NULL)
5407 static void compose_add_headerfield_from_headerlist(Compose *compose,
5409 const gchar *fieldname,
5410 const gchar *seperator)
5412 gchar *str, *fieldname_w_colon;
5413 gboolean add_field = FALSE;
5415 ComposeHeaderEntry *headerentry;
5416 const gchar *headerentryname;
5417 const gchar *trans_fieldname;
5420 if (IS_IN_CUSTOM_HEADER(fieldname))
5423 debug_print("Adding %s-fields\n", fieldname);
5425 fieldstr = g_string_sized_new(64);
5427 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
5428 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
5430 for (list = compose->header_list; list; list = list->next) {
5431 headerentry = ((ComposeHeaderEntry *)list->data);
5432 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(headerentry->combo)->entry));
5434 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
5435 str = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
5437 if (str[0] != '\0') {
5439 g_string_append(fieldstr, seperator);
5440 g_string_append(fieldstr, str);
5449 buf = g_new0(gchar, fieldstr->len * 4 + 256);
5450 compose_convert_header
5451 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
5452 strlen(fieldname) + 2, TRUE);
5453 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
5457 g_free(fieldname_w_colon);
5458 g_string_free(fieldstr, TRUE);
5463 static gchar *compose_get_header(Compose *compose)
5465 gchar buf[BUFFSIZE];
5466 const gchar *entry_str;
5470 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
5472 gchar *from_name = NULL, *from_address = NULL;
5475 g_return_val_if_fail(compose->account != NULL, NULL);
5476 g_return_val_if_fail(compose->account->address != NULL, NULL);
5478 header = g_string_sized_new(64);
5481 get_rfc822_date(buf, sizeof(buf));
5482 g_string_append_printf(header, "Date: %s\n", buf);
5486 if (compose->account->name && *compose->account->name) {
5488 QUOTE_IF_REQUIRED(buf, compose->account->name);
5489 tmp = g_strdup_printf("%s <%s>",
5490 buf, compose->account->address);
5492 tmp = g_strdup_printf("%s",
5493 compose->account->address);
5495 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
5496 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
5498 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
5499 from_address = g_strdup(compose->account->address);
5501 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5502 /* extract name and address */
5503 if (strstr(spec, " <") && strstr(spec, ">")) {
5504 from_address = g_strdup(strrchr(spec, '<')+1);
5505 *(strrchr(from_address, '>')) = '\0';
5506 from_name = g_strdup(spec);
5507 *(strrchr(from_name, '<')) = '\0';
5510 from_address = g_strdup(spec);
5517 if (from_name && *from_name) {
5518 compose_convert_header
5519 (compose, buf, sizeof(buf), from_name,
5520 strlen("From: "), TRUE);
5521 QUOTE_IF_REQUIRED(name, buf);
5523 g_string_append_printf(header, "From: %s <%s>\n",
5524 name, from_address);
5526 g_string_append_printf(header, "From: %s\n", from_address);
5529 g_free(from_address);
5532 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
5535 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
5538 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
5541 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
5544 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
5546 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
5549 compose_convert_header(compose, buf, sizeof(buf), str,
5550 strlen("Subject: "), FALSE);
5551 g_string_append_printf(header, "Subject: %s\n", buf);
5557 if (compose->account->gen_msgid) {
5558 generate_msgid(buf, sizeof(buf));
5559 g_string_append_printf(header, "Message-ID: <%s>\n", buf);
5560 compose->msgid = g_strdup(buf);
5563 if (compose->remove_references == FALSE) {
5565 if (compose->inreplyto && compose->to_list)
5566 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
5569 if (compose->references)
5570 g_string_append_printf(header, "References: %s\n", compose->references);
5574 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
5577 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
5580 if (compose->account->organization &&
5581 strlen(compose->account->organization) &&
5582 !IS_IN_CUSTOM_HEADER("Organization")) {
5583 compose_convert_header(compose, buf, sizeof(buf),
5584 compose->account->organization,
5585 strlen("Organization: "), FALSE);
5586 g_string_append_printf(header, "Organization: %s\n", buf);
5589 /* Program version and system info */
5590 if (g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
5591 !compose->newsgroup_list) {
5592 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
5594 gtk_major_version, gtk_minor_version, gtk_micro_version,
5597 if (g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
5598 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
5600 gtk_major_version, gtk_minor_version, gtk_micro_version,
5604 /* custom headers */
5605 if (compose->account->add_customhdr) {
5608 for (cur = compose->account->customhdr_list; cur != NULL;
5610 CustomHeader *chdr = (CustomHeader *)cur->data;
5612 if (custom_header_is_allowed(chdr->name)) {
5613 compose_convert_header
5614 (compose, buf, sizeof(buf),
5615 chdr->value ? chdr->value : "",
5616 strlen(chdr->name) + 2, FALSE);
5617 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
5623 switch (compose->priority) {
5624 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
5625 "X-Priority: 1 (Highest)\n");
5627 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
5628 "X-Priority: 2 (High)\n");
5630 case PRIORITY_NORMAL: break;
5631 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
5632 "X-Priority: 4 (Low)\n");
5634 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
5635 "X-Priority: 5 (Lowest)\n");
5637 default: debug_print("compose: priority unknown : %d\n",
5641 /* Request Return Receipt */
5642 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To")) {
5643 if (compose->return_receipt) {
5644 if (compose->account->name
5645 && *compose->account->name) {
5646 compose_convert_header(compose, buf, sizeof(buf),
5647 compose->account->name,
5648 strlen("Disposition-Notification-To: "),
5650 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, compose->account->address);
5652 g_string_append_printf(header, "Disposition-Notification-To: %s\n", compose->account->address);
5656 /* get special headers */
5657 for (list = compose->header_list; list; list = list->next) {
5658 ComposeHeaderEntry *headerentry;
5661 gchar *headername_wcolon;
5662 const gchar *headername_trans;
5665 gboolean standard_header = FALSE;
5667 headerentry = ((ComposeHeaderEntry *)list->data);
5669 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(headerentry->combo)->entry)));
5670 if (strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
5675 if (!strstr(tmp, ":")) {
5676 headername_wcolon = g_strconcat(tmp, ":", NULL);
5677 headername = g_strdup(tmp);
5679 headername_wcolon = g_strdup(tmp);
5680 headername = g_strdup(strtok(tmp, ":"));
5684 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5685 Xstrdup_a(headervalue, entry_str, return NULL);
5686 subst_char(headervalue, '\r', ' ');
5687 subst_char(headervalue, '\n', ' ');
5688 string = std_headers;
5689 while (*string != NULL) {
5690 headername_trans = prefs_common_translated_header_name(*string);
5691 if (!strcmp(headername_trans, headername_wcolon))
5692 standard_header = TRUE;
5695 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
5696 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
5699 g_free(headername_wcolon);
5703 g_string_free(header, FALSE);
5708 #undef IS_IN_CUSTOM_HEADER
5710 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
5711 gint header_len, gboolean addr_field)
5713 gchar *tmpstr = NULL;
5714 const gchar *out_codeset = NULL;
5716 g_return_if_fail(src != NULL);
5717 g_return_if_fail(dest != NULL);
5719 if (len < 1) return;
5721 tmpstr = g_strdup(src);
5723 subst_char(tmpstr, '\n', ' ');
5724 subst_char(tmpstr, '\r', ' ');
5727 if (!g_utf8_validate(tmpstr, -1, NULL)) {
5728 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
5729 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
5734 codeconv_set_strict(TRUE);
5735 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
5736 conv_get_charset_str(compose->out_encoding));
5737 codeconv_set_strict(FALSE);
5739 if (!dest || *dest == '\0') {
5740 gchar *test_conv_global_out = NULL;
5741 gchar *test_conv_reply = NULL;
5743 /* automatic mode. be automatic. */
5744 codeconv_set_strict(TRUE);
5746 out_codeset = conv_get_outgoing_charset_str();
5748 debug_print("trying to convert to %s\n", out_codeset);
5749 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
5752 if (!test_conv_global_out && compose->orig_charset
5753 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5754 out_codeset = compose->orig_charset;
5755 debug_print("failure; trying to convert to %s\n", out_codeset);
5756 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
5759 if (!test_conv_global_out && !test_conv_reply) {
5761 out_codeset = CS_INTERNAL;
5762 debug_print("finally using %s\n", out_codeset);
5764 g_free(test_conv_global_out);
5765 g_free(test_conv_reply);
5766 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
5768 codeconv_set_strict(FALSE);
5773 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
5777 g_return_if_fail(user_data != NULL);
5779 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
5780 g_strstrip(address);
5781 if (*address != '\0') {
5782 gchar *name = procheader_get_fromname(address);
5783 extract_address(address);
5784 addressbook_add_contact(name, address, NULL);
5789 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
5791 GtkWidget *menuitem;
5794 g_return_if_fail(menu != NULL);
5795 g_return_if_fail(GTK_IS_MENU_SHELL(menu));
5797 menuitem = gtk_separator_menu_item_new();
5798 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
5799 gtk_widget_show(menuitem);
5801 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
5802 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
5804 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
5805 g_strstrip(address);
5806 if (*address == '\0') {
5807 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
5810 g_signal_connect(G_OBJECT(menuitem), "activate",
5811 G_CALLBACK(compose_add_to_addressbook_cb), entry);
5812 gtk_widget_show(menuitem);
5815 static void compose_create_header_entry(Compose *compose)
5817 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
5821 GList *combo_list = NULL;
5823 const gchar *header = NULL;
5824 ComposeHeaderEntry *headerentry;
5825 gboolean standard_header = FALSE;
5827 headerentry = g_new0(ComposeHeaderEntry, 1);
5830 combo = gtk_combo_new();
5832 while(*string != NULL) {
5833 combo_list = g_list_append(combo_list, (gchar*)prefs_common_translated_header_name(*string));
5836 gtk_combo_set_popdown_strings(GTK_COMBO(combo), combo_list);
5837 g_list_free(combo_list);
5838 gtk_editable_set_editable(GTK_EDITABLE(GTK_COMBO(combo)->entry), TRUE);
5839 g_signal_connect(G_OBJECT(GTK_COMBO(combo)->entry), "grab_focus",
5840 G_CALLBACK(compose_grab_focus_cb), compose);
5841 gtk_widget_show(combo);
5842 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
5843 compose->header_nextrow, compose->header_nextrow+1,
5844 GTK_SHRINK, GTK_FILL, 0, 0);
5845 if (compose->header_last) {
5846 const gchar *last_header_entry = gtk_entry_get_text(
5847 GTK_ENTRY(GTK_COMBO(compose->header_last->combo)->entry));
5849 while (*string != NULL) {
5850 if (!strcmp(*string, last_header_entry))
5851 standard_header = TRUE;
5854 if (standard_header)
5855 header = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(compose->header_last->combo)->entry));
5857 if (!compose->header_last || !standard_header) {
5858 switch(compose->account->protocol) {
5860 header = prefs_common_translated_header_name("Newsgroups:");
5863 header = prefs_common_translated_header_name("To:");
5868 gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(combo)->entry), header);
5870 g_signal_connect_after(G_OBJECT(GTK_COMBO(combo)->entry), "grab_focus",
5871 G_CALLBACK(compose_grab_focus_cb), compose);
5874 entry = gtk_entry_new();
5875 gtk_widget_show(entry);
5876 gtk_tooltips_set_tip(compose->tooltips, entry,
5877 _("Use <tab> to autocomplete from addressbook"), NULL);
5878 gtk_table_attach(GTK_TABLE(compose->header_table), entry, 1, 2,
5879 compose->header_nextrow, compose->header_nextrow+1,
5880 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
5882 g_signal_connect(G_OBJECT(entry), "key-press-event",
5883 G_CALLBACK(compose_headerentry_key_press_event_cb),
5885 g_signal_connect(G_OBJECT(entry), "changed",
5886 G_CALLBACK(compose_headerentry_changed_cb),
5888 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
5889 G_CALLBACK(compose_grab_focus_cb), compose);
5892 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
5893 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
5894 GDK_ACTION_COPY | GDK_ACTION_MOVE);
5895 g_signal_connect(G_OBJECT(entry), "drag_data_received",
5896 G_CALLBACK(compose_header_drag_received_cb),
5898 g_signal_connect(G_OBJECT(entry), "drag-drop",
5899 G_CALLBACK(compose_drag_drop),
5901 g_signal_connect(G_OBJECT(entry), "populate-popup",
5902 G_CALLBACK(compose_entry_popup_extend),
5905 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
5907 headerentry->compose = compose;
5908 headerentry->combo = combo;
5909 headerentry->entry = entry;
5910 headerentry->headernum = compose->header_nextrow;
5912 compose->header_nextrow++;
5913 compose->header_last = headerentry;
5914 compose->header_list =
5915 g_slist_append(compose->header_list,
5919 static void compose_add_header_entry(Compose *compose, const gchar *header, gchar *text)
5921 ComposeHeaderEntry *last_header;
5923 last_header = compose->header_last;
5925 gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(last_header->combo)->entry), header);
5926 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
5929 static void compose_remove_header_entries(Compose *compose)
5932 for (list = compose->header_list; list; list = list->next) {
5933 ComposeHeaderEntry *headerentry =
5934 (ComposeHeaderEntry *)list->data;
5935 gtk_widget_destroy(headerentry->combo);
5936 gtk_widget_destroy(headerentry->entry);
5937 g_free(headerentry);
5939 compose->header_last = NULL;
5940 g_slist_free(compose->header_list);
5941 compose->header_list = NULL;
5942 compose->header_nextrow = 1;
5943 compose_create_header_entry(compose);
5946 static GtkWidget *compose_create_header(Compose *compose)
5948 GtkWidget *from_optmenu_hbox;
5949 GtkWidget *header_scrolledwin;
5950 GtkWidget *header_table;
5954 /* header labels and entries */
5955 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
5956 gtk_widget_show(header_scrolledwin);
5957 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
5959 header_table = gtk_table_new(2, 2, FALSE);
5960 gtk_widget_show(header_table);
5961 gtk_container_set_border_width(GTK_CONTAINER(header_table), BORDER_WIDTH);
5962 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
5963 gtk_viewport_set_shadow_type(GTK_VIEWPORT(GTK_BIN(header_scrolledwin)->child), GTK_SHADOW_ETCHED_IN);
5966 /* option menu for selecting accounts */
5967 from_optmenu_hbox = compose_account_option_menu_create(compose);
5968 gtk_table_attach(GTK_TABLE(header_table), from_optmenu_hbox,
5969 0, 2, count, count + 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
5972 compose->header_table = header_table;
5973 compose->header_list = NULL;
5974 compose->header_nextrow = count;
5976 compose_create_header_entry(compose);
5978 compose->table = NULL;
5980 return header_scrolledwin ;
5983 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
5985 Compose *compose = (Compose *)data;
5986 GdkEventButton event;
5989 event.time = gtk_get_current_event_time();
5991 return attach_button_pressed(compose->attach_clist, &event, compose);
5994 static GtkWidget *compose_create_attach(Compose *compose)
5996 GtkWidget *attach_scrwin;
5997 GtkWidget *attach_clist;
5999 GtkListStore *store;
6000 GtkCellRenderer *renderer;
6001 GtkTreeViewColumn *column;
6002 GtkTreeSelection *selection;
6004 /* attachment list */
6005 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
6006 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
6007 GTK_POLICY_AUTOMATIC,
6008 GTK_POLICY_AUTOMATIC);
6009 gtk_widget_set_size_request(attach_scrwin, -1, 80);
6011 store = gtk_list_store_new(N_ATTACH_COLS,
6016 G_TYPE_AUTO_POINTER,
6018 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
6019 (GTK_TREE_MODEL(store)));
6020 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
6021 g_object_unref(store);
6023 renderer = gtk_cell_renderer_text_new();
6024 column = gtk_tree_view_column_new_with_attributes
6025 (_("Mime type"), renderer, "text",
6026 COL_MIMETYPE, NULL);
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 (_("Size"), renderer, "text",
6033 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6035 renderer = gtk_cell_renderer_text_new();
6036 column = gtk_tree_view_column_new_with_attributes
6037 (_("Name"), renderer, "text",
6039 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6041 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
6042 prefs_common.use_stripes_everywhere);
6043 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
6044 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
6046 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
6047 G_CALLBACK(attach_selected), compose);
6048 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
6049 G_CALLBACK(attach_button_pressed), compose);
6051 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
6052 G_CALLBACK(popup_attach_button_pressed), compose);
6054 gtk_widget_tap_and_hold_setup(GTK_WIDGET(attach_clist), NULL, NULL,
6055 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
6056 g_signal_connect(G_OBJECT(attach_clist), "tap-and-hold",
6057 G_CALLBACK(popup_attach_button_pressed), compose);
6059 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
6060 G_CALLBACK(attach_key_pressed), compose);
6063 gtk_drag_dest_set(attach_clist,
6064 GTK_DEST_DEFAULT_ALL, compose_mime_types,
6065 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6066 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6067 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
6068 G_CALLBACK(compose_attach_drag_received_cb),
6070 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
6071 G_CALLBACK(compose_drag_drop),
6074 compose->attach_scrwin = attach_scrwin;
6075 compose->attach_clist = attach_clist;
6077 return attach_scrwin;
6080 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
6081 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
6083 static GtkWidget *compose_create_others(Compose *compose)
6086 GtkWidget *savemsg_checkbtn;
6087 GtkWidget *savemsg_entry;
6088 GtkWidget *savemsg_select;
6091 gchar *folderidentifier;
6093 /* Table for settings */
6094 table = gtk_table_new(3, 1, FALSE);
6095 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
6096 gtk_widget_show(table);
6097 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
6100 /* Save Message to folder */
6101 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
6102 gtk_widget_show(savemsg_checkbtn);
6103 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6104 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6105 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
6107 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
6108 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
6110 savemsg_entry = gtk_entry_new();
6111 gtk_widget_show(savemsg_entry);
6112 gtk_table_attach_defaults(GTK_TABLE(table), savemsg_entry, 1, 2, rowcount, rowcount + 1);
6113 gtk_editable_set_editable(GTK_EDITABLE(savemsg_entry), prefs_common.savemsg);
6114 g_signal_connect_after(G_OBJECT(savemsg_entry), "grab_focus",
6115 G_CALLBACK(compose_grab_focus_cb), compose);
6116 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6117 folderidentifier = folder_item_get_identifier(account_get_special_folder
6118 (compose->account, F_OUTBOX));
6119 gtk_entry_set_text(GTK_ENTRY(savemsg_entry), folderidentifier);
6120 g_free(folderidentifier);
6123 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
6124 gtk_widget_show(savemsg_select);
6125 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6126 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
6127 G_CALLBACK(compose_savemsg_select_cb),
6132 compose->savemsg_checkbtn = savemsg_checkbtn;
6133 compose->savemsg_entry = savemsg_entry;
6138 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
6140 gtk_editable_set_editable(GTK_EDITABLE(compose->savemsg_entry),
6141 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
6144 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
6149 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL);
6152 path = folder_item_get_identifier(dest);
6154 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), path);
6158 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
6159 GdkAtom clip, GtkTextIter *insert_place);
6161 #define BLOCK_WRAP() { \
6162 prev_autowrap = compose->autowrap; \
6163 buffer = gtk_text_view_get_buffer( \
6164 GTK_TEXT_VIEW(compose->text)); \
6165 compose->autowrap = FALSE; \
6167 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
6168 G_CALLBACK(compose_changed_cb), \
6170 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
6171 G_CALLBACK(text_inserted), \
6174 #define UNBLOCK_WRAP() { \
6175 compose->autowrap = prev_autowrap; \
6176 if (compose->autowrap) \
6177 compose_wrap_all(compose); \
6179 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
6180 G_CALLBACK(compose_changed_cb), \
6182 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
6183 G_CALLBACK(text_inserted), \
6188 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
6192 GtkTextBuffer *buffer;
6194 if (event->button == 3) {
6196 GtkTextIter sel_start, sel_end;
6197 gboolean stuff_selected;
6199 /* move the cursor to allow GtkAspell to check the word
6200 * under the mouse */
6201 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6202 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6204 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6207 stuff_selected = gtk_text_buffer_get_selection_bounds(
6208 GTK_TEXT_VIEW(text)->buffer,
6209 &sel_start, &sel_end);
6211 gtk_text_buffer_place_cursor (GTK_TEXT_VIEW(text)->buffer, &iter);
6212 /* reselect stuff */
6214 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
6215 gtk_text_buffer_select_range(GTK_TEXT_VIEW(text)->buffer,
6216 &sel_start, &sel_end);
6218 return FALSE; /* pass the event so that the right-click goes through */
6221 if (event->button == 2) {
6226 /* get the middle-click position to paste at the correct place */
6227 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6228 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6230 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6233 entry_paste_clipboard(compose, text,
6234 prefs_common.linewrap_pastes,
6235 GDK_SELECTION_PRIMARY, &iter);
6243 static void compose_spell_menu_changed(void *data)
6245 Compose *compose = (Compose *)data;
6247 GtkWidget *menuitem;
6248 GtkWidget *parent_item;
6249 GtkMenu *menu = GTK_MENU(gtk_menu_new());
6250 GtkItemFactory *ifactory = gtk_item_factory_from_widget(compose->menubar);
6253 if (compose->gtkaspell == NULL)
6256 parent_item = gtk_item_factory_get_item(ifactory,
6257 "/Spelling/Options");
6259 /* setting the submenu removes /Spelling/Options from the factory
6260 * so we need to save it */
6262 if (parent_item == NULL) {
6263 parent_item = compose->aspell_options_menu;
6264 gtk_menu_item_remove_submenu(GTK_MENU_ITEM(parent_item));
6266 compose->aspell_options_menu = parent_item;
6268 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
6270 spell_menu = g_slist_reverse(spell_menu);
6271 for (items = spell_menu;
6272 items; items = items->next) {
6273 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
6274 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
6275 gtk_widget_show(GTK_WIDGET(menuitem));
6277 g_slist_free(spell_menu);
6279 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
6284 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
6286 Compose *compose = (Compose *)data;
6287 GdkEventButton event;
6290 event.time = gtk_get_current_event_time();
6292 return text_clicked(compose->text, &event, compose);
6295 static gboolean compose_force_window_origin = TRUE;
6296 static Compose *compose_create(PrefsAccount *account,
6305 GtkWidget *handlebox;
6307 GtkWidget *notebook;
6312 GtkWidget *subject_hbox;
6313 GtkWidget *subject_frame;
6314 GtkWidget *subject_entry;
6318 GtkWidget *edit_vbox;
6319 GtkWidget *ruler_hbox;
6321 GtkWidget *scrolledwin;
6323 GtkTextBuffer *buffer;
6324 GtkClipboard *clipboard;
6326 UndoMain *undostruct;
6328 gchar *titles[N_ATTACH_COLS];
6329 guint n_menu_entries;
6330 GtkWidget *popupmenu;
6331 GtkItemFactory *popupfactory;
6332 GtkItemFactory *ifactory;
6333 GtkWidget *tmpl_menu;
6335 GtkWidget *menuitem;
6338 GtkAspell * gtkaspell = NULL;
6341 static GdkGeometry geometry;
6343 g_return_val_if_fail(account != NULL, NULL);
6345 debug_print("Creating compose window...\n");
6346 compose = g_new0(Compose, 1);
6348 titles[COL_MIMETYPE] = _("MIME type");
6349 titles[COL_SIZE] = _("Size");
6350 titles[COL_NAME] = _("Name");
6352 compose->batch = batch;
6353 compose->account = account;
6354 compose->folder = folder;
6356 compose->mutex = g_mutex_new();
6357 compose->set_cursor_pos = -1;
6359 compose->tooltips = gtk_tooltips_new();
6361 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
6363 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
6364 gtk_widget_set_size_request(window, -1, prefs_common.compose_height);
6366 if (!geometry.max_width) {
6367 geometry.max_width = gdk_screen_width();
6368 geometry.max_height = gdk_screen_height();
6371 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
6372 &geometry, GDK_HINT_MAX_SIZE);
6373 if (!geometry.min_width) {
6374 geometry.min_width = 600;
6375 geometry.min_height = 480;
6377 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
6378 &geometry, GDK_HINT_MIN_SIZE);
6381 if (compose_force_window_origin)
6382 gtk_widget_set_uposition(window, prefs_common.compose_x,
6383 prefs_common.compose_y);
6385 g_signal_connect(G_OBJECT(window), "delete_event",
6386 G_CALLBACK(compose_delete_cb), compose);
6387 MANAGE_WINDOW_SIGNALS_CONNECT(window);
6388 gtk_widget_realize(window);
6390 gtkut_widget_set_composer_icon(window);
6392 vbox = gtk_vbox_new(FALSE, 0);
6393 gtk_container_add(GTK_CONTAINER(window), vbox);
6395 n_menu_entries = sizeof(compose_entries) / sizeof(compose_entries[0]);
6396 menubar = menubar_create(window, compose_entries,
6397 n_menu_entries, "<Compose>", compose);
6398 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
6400 if (prefs_common.toolbar_detachable) {
6401 handlebox = gtk_handle_box_new();
6403 handlebox = gtk_hbox_new(FALSE, 0);
6405 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
6407 gtk_widget_realize(handlebox);
6409 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, window,
6412 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
6416 vbox2 = gtk_vbox_new(FALSE, 2);
6417 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
6418 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
6421 notebook = gtk_notebook_new();
6422 gtk_widget_set_size_request(notebook, -1, 130);
6423 gtk_widget_show(notebook);
6425 /* header labels and entries */
6426 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6427 compose_create_header(compose),
6428 gtk_label_new_with_mnemonic(_("Hea_der")));
6429 /* attachment list */
6430 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6431 compose_create_attach(compose),
6432 gtk_label_new_with_mnemonic(_("_Attachments")));
6434 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6435 compose_create_others(compose),
6436 gtk_label_new_with_mnemonic(_("Othe_rs")));
6439 subject_hbox = gtk_hbox_new(FALSE, 0);
6440 gtk_widget_show(subject_hbox);
6442 subject_frame = gtk_frame_new(NULL);
6443 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
6444 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
6445 gtk_widget_show(subject_frame);
6447 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
6448 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
6449 gtk_widget_show(subject);
6451 label = gtk_label_new(_("Subject:"));
6452 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
6453 gtk_widget_show(label);
6455 subject_entry = gtk_entry_new();
6456 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
6457 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
6458 G_CALLBACK(compose_grab_focus_cb), compose);
6459 gtk_widget_show(subject_entry);
6460 compose->subject_entry = subject_entry;
6461 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
6463 edit_vbox = gtk_vbox_new(FALSE, 0);
6465 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
6468 ruler_hbox = gtk_hbox_new(FALSE, 0);
6469 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
6471 ruler = gtk_shruler_new();
6472 gtk_ruler_set_range(GTK_RULER(ruler), 0.0, 100.0, 1.0, 100.0);
6473 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
6477 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
6478 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
6479 GTK_POLICY_AUTOMATIC,
6480 GTK_POLICY_AUTOMATIC);
6481 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
6483 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
6484 gtk_widget_set_size_request(scrolledwin, prefs_common.compose_width, -1);
6486 text = gtk_text_view_new();
6487 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
6488 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
6489 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
6490 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
6491 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
6493 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
6495 g_signal_connect_after(G_OBJECT(text), "size_allocate",
6496 G_CALLBACK(compose_edit_size_alloc),
6498 g_signal_connect(G_OBJECT(buffer), "changed",
6499 G_CALLBACK(compose_changed_cb), compose);
6500 g_signal_connect(G_OBJECT(text), "grab_focus",
6501 G_CALLBACK(compose_grab_focus_cb), compose);
6502 g_signal_connect(G_OBJECT(buffer), "insert_text",
6503 G_CALLBACK(text_inserted), compose);
6504 g_signal_connect(G_OBJECT(text), "button_press_event",
6505 G_CALLBACK(text_clicked), compose);
6507 g_signal_connect(G_OBJECT(text), "popup-menu",
6508 G_CALLBACK(compose_popup_menu), compose);
6510 gtk_widget_tap_and_hold_setup(GTK_WIDGET(text), NULL, NULL,
6511 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
6512 g_signal_connect(G_OBJECT(text), "tap-and-hold",
6513 G_CALLBACK(compose_popup_menu), compose);
6515 g_signal_connect(G_OBJECT(subject_entry), "changed",
6516 G_CALLBACK(compose_changed_cb), compose);
6519 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6520 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6521 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6522 g_signal_connect(G_OBJECT(text), "drag_data_received",
6523 G_CALLBACK(compose_insert_drag_received_cb),
6525 g_signal_connect(G_OBJECT(text), "drag-drop",
6526 G_CALLBACK(compose_drag_drop),
6528 gtk_widget_show_all(vbox);
6530 /* pane between attach clist and text */
6531 paned = gtk_vpaned_new();
6532 gtk_paned_set_gutter_size(GTK_PANED(paned), 12);
6533 gtk_container_add(GTK_CONTAINER(vbox2), paned);
6535 if( maemo_mainwindow_is_fullscreen(mainwindow_get_mainwindow()->window) )
6536 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 300 : 280);
6538 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 250 : 230);
6540 gtk_paned_add1(GTK_PANED(paned), notebook);
6541 gtk_paned_add2(GTK_PANED(paned), edit_vbox);
6542 gtk_widget_show_all(paned);
6545 if (prefs_common.textfont) {
6546 PangoFontDescription *font_desc;
6548 font_desc = pango_font_description_from_string
6549 (prefs_common.textfont);
6551 gtk_widget_modify_font(text, font_desc);
6552 pango_font_description_free(font_desc);
6556 n_entries = sizeof(compose_popup_entries) /
6557 sizeof(compose_popup_entries[0]);
6558 popupmenu = menu_create_items(compose_popup_entries, n_entries,
6559 "<Compose>", &popupfactory,
6562 ifactory = gtk_item_factory_from_widget(menubar);
6563 menu_set_sensitive(ifactory, "/Edit/Undo", FALSE);
6564 menu_set_sensitive(ifactory, "/Edit/Redo", FALSE);
6565 menu_set_sensitive(ifactory, "/Options/Remove references", FALSE);
6567 tmpl_menu = gtk_item_factory_get_item(ifactory, "/Tools/Template");
6569 undostruct = undo_init(text);
6570 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
6573 address_completion_start(window);
6575 compose->window = window;
6576 compose->vbox = vbox;
6577 compose->menubar = menubar;
6578 compose->handlebox = handlebox;
6580 compose->vbox2 = vbox2;
6582 compose->paned = paned;
6584 compose->notebook = notebook;
6585 compose->edit_vbox = edit_vbox;
6586 compose->ruler_hbox = ruler_hbox;
6587 compose->ruler = ruler;
6588 compose->scrolledwin = scrolledwin;
6589 compose->text = text;
6591 compose->focused_editable = NULL;
6593 compose->popupmenu = popupmenu;
6594 compose->popupfactory = popupfactory;
6596 compose->tmpl_menu = tmpl_menu;
6598 compose->mode = mode;
6599 compose->rmode = mode;
6601 compose->targetinfo = NULL;
6602 compose->replyinfo = NULL;
6603 compose->fwdinfo = NULL;
6605 compose->replyto = NULL;
6607 compose->bcc = NULL;
6608 compose->followup_to = NULL;
6610 compose->ml_post = NULL;
6612 compose->inreplyto = NULL;
6613 compose->references = NULL;
6614 compose->msgid = NULL;
6615 compose->boundary = NULL;
6617 compose->autowrap = prefs_common.autowrap;
6619 compose->use_signing = FALSE;
6620 compose->use_encryption = FALSE;
6621 compose->privacy_system = NULL;
6623 compose->modified = FALSE;
6625 compose->return_receipt = FALSE;
6627 compose->to_list = NULL;
6628 compose->newsgroup_list = NULL;
6630 compose->undostruct = undostruct;
6632 compose->sig_str = NULL;
6634 compose->exteditor_file = NULL;
6635 compose->exteditor_pid = -1;
6636 compose->exteditor_tag = -1;
6637 compose->draft_timeout_tag = -2; /* inhibit auto-drafting while loading */
6640 menu_set_sensitive(ifactory, "/Spelling", FALSE);
6641 if (mode != COMPOSE_REDIRECT) {
6642 if (prefs_common.enable_aspell && prefs_common.dictionary &&
6643 strcmp(prefs_common.dictionary, "")) {
6644 gtkaspell = gtkaspell_new(prefs_common.aspell_path,
6645 prefs_common.dictionary,
6646 prefs_common.alt_dictionary,
6647 conv_get_locale_charset_str(),
6648 prefs_common.misspelled_col,
6649 prefs_common.check_while_typing,
6650 prefs_common.recheck_when_changing_dict,
6651 prefs_common.use_alternate,
6652 prefs_common.use_both_dicts,
6653 GTK_TEXT_VIEW(text),
6654 GTK_WINDOW(compose->window),
6655 compose_spell_menu_changed,
6658 alertpanel_error(_("Spell checker could not "
6660 gtkaspell_checkers_strerror());
6661 gtkaspell_checkers_reset_error();
6663 if (!gtkaspell_set_sug_mode(gtkaspell,
6664 prefs_common.aspell_sugmode)) {
6665 debug_print("Aspell: could not set "
6666 "suggestion mode %s\n",
6667 gtkaspell_checkers_strerror());
6668 gtkaspell_checkers_reset_error();
6671 menu_set_sensitive(ifactory, "/Spelling", TRUE);
6675 compose->gtkaspell = gtkaspell;
6676 compose_spell_menu_changed(compose);
6679 compose_select_account(compose, account, TRUE);
6681 menu_set_active(ifactory, "/Edit/Auto wrapping", prefs_common.autowrap);
6682 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
6683 compose_entry_append(compose, account->auto_cc, COMPOSE_CC);
6685 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
6686 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC);
6688 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
6689 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO);
6691 menu_set_sensitive(ifactory, "/Options/Reply mode", compose->mode == COMPOSE_REPLY);
6693 if (account->protocol != A_NNTP)
6694 gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(compose->header_last->combo)->entry),
6695 prefs_common_translated_header_name("To:"));
6697 gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(compose->header_last->combo)->entry),
6698 prefs_common_translated_header_name("Newsgroups:"));
6700 addressbook_set_target_compose(compose);
6702 if (mode != COMPOSE_REDIRECT)
6703 compose_set_template_menu(compose);
6705 menuitem = gtk_item_factory_get_item(ifactory, "/Tools/Template");
6706 menu_set_sensitive(ifactory, "/Tools/Template", FALSE);
6709 compose_list = g_list_append(compose_list, compose);
6711 if (!prefs_common.show_ruler)
6712 gtk_widget_hide(ruler_hbox);
6714 menuitem = gtk_item_factory_get_item(ifactory, "/Tools/Show ruler");
6715 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
6716 prefs_common.show_ruler);
6719 compose->priority = PRIORITY_NORMAL;
6720 compose_update_priority_menu_item(compose);
6722 compose_set_out_encoding(compose);
6725 compose_update_actions_menu(compose);
6727 /* Privacy Systems menu */
6728 compose_update_privacy_systems_menu(compose);
6730 activate_privacy_system(compose, account, TRUE);
6731 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
6733 gtk_widget_realize(window);
6735 gtk_widget_show(window);
6737 maemo_window_full_screen_if_needed(GTK_WINDOW(window));
6738 maemo_connect_key_press_to_mainwindow(GTK_WINDOW(window));
6745 static GtkWidget *compose_account_option_menu_create(Compose *compose)
6750 GtkWidget *optmenubox;
6753 GtkWidget *from_name = NULL;
6755 gint num = 0, def_menu = 0;
6757 accounts = account_get_list();
6758 g_return_val_if_fail(accounts != NULL, NULL);
6760 optmenubox = gtk_event_box_new();
6761 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
6762 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
6764 hbox = gtk_hbox_new(FALSE, 6);
6765 from_name = gtk_entry_new();
6767 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
6768 G_CALLBACK(compose_grab_focus_cb), compose);
6770 for (; accounts != NULL; accounts = accounts->next, num++) {
6771 PrefsAccount *ac = (PrefsAccount *)accounts->data;
6772 gchar *name, *from = NULL;
6774 if (ac == compose->account) def_menu = num;
6776 name = g_markup_printf_escaped(_("From: <i>%s</i>"),
6779 if (ac == compose->account) {
6780 if (ac->name && *ac->name) {
6782 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
6783 from = g_strdup_printf("%s <%s>",
6785 gtk_entry_set_text(GTK_ENTRY(from_name), from);
6787 from = g_strdup_printf("%s",
6789 gtk_entry_set_text(GTK_ENTRY(from_name), from);
6792 COMBOBOX_ADD(menu, name, ac->account_id);
6797 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
6799 g_signal_connect(G_OBJECT(optmenu), "changed",
6800 G_CALLBACK(account_activated),
6802 g_signal_connect(G_OBJECT(from_name), "populate-popup",
6803 G_CALLBACK(compose_entry_popup_extend),
6806 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
6807 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
6809 gtk_tooltips_set_tip(compose->tooltips, optmenubox,
6810 _("Account to use for this email"), NULL);
6811 gtk_tooltips_set_tip(compose->tooltips, from_name,
6812 _("Sender address to be used"), NULL);
6814 compose->from_name = from_name;
6819 static void compose_set_priority_cb(gpointer data,
6823 Compose *compose = (Compose *) data;
6824 compose->priority = action;
6827 static void compose_reply_change_mode(gpointer data,
6831 Compose *compose = (Compose *) data;
6832 gboolean was_modified = compose->modified;
6834 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
6836 g_return_if_fail(compose->replyinfo != NULL);
6838 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
6840 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
6842 if (action == COMPOSE_REPLY_TO_ALL)
6844 if (action == COMPOSE_REPLY_TO_SENDER)
6846 if (action == COMPOSE_REPLY_TO_LIST)
6849 compose_remove_header_entries(compose);
6850 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
6851 if (compose->account->set_autocc && compose->account->auto_cc)
6852 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC);
6854 if (compose->account->set_autobcc && compose->account->auto_bcc)
6855 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC);
6857 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
6858 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO);
6859 compose_show_first_last_header(compose, TRUE);
6860 compose->modified = was_modified;
6861 compose_set_title(compose);
6864 static void compose_update_priority_menu_item(Compose * compose)
6866 GtkItemFactory *ifactory;
6867 GtkWidget *menuitem = NULL;
6869 ifactory = gtk_item_factory_from_widget(compose->menubar);
6871 switch (compose->priority) {
6872 case PRIORITY_HIGHEST:
6873 menuitem = gtk_item_factory_get_item
6874 (ifactory, "/Options/Priority/Highest");
6877 menuitem = gtk_item_factory_get_item
6878 (ifactory, "/Options/Priority/High");
6880 case PRIORITY_NORMAL:
6881 menuitem = gtk_item_factory_get_item
6882 (ifactory, "/Options/Priority/Normal");
6885 menuitem = gtk_item_factory_get_item
6886 (ifactory, "/Options/Priority/Low");
6888 case PRIORITY_LOWEST:
6889 menuitem = gtk_item_factory_get_item
6890 (ifactory, "/Options/Priority/Lowest");
6893 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
6896 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
6898 Compose *compose = (Compose *) data;
6900 GtkItemFactory *ifactory;
6901 gboolean can_sign = FALSE, can_encrypt = FALSE;
6903 g_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
6905 if (!GTK_CHECK_MENU_ITEM(widget)->active)
6908 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
6909 g_free(compose->privacy_system);
6910 compose->privacy_system = NULL;
6911 if (systemid != NULL) {
6912 compose->privacy_system = g_strdup(systemid);
6914 can_sign = privacy_system_can_sign(systemid);
6915 can_encrypt = privacy_system_can_encrypt(systemid);
6918 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
6920 ifactory = gtk_item_factory_from_widget(compose->menubar);
6921 menu_set_sensitive(ifactory, "/Options/Sign", can_sign);
6922 menu_set_sensitive(ifactory, "/Options/Encrypt", can_encrypt);
6925 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
6927 static gchar *branch_path = "/Options/Privacy System";
6928 GtkItemFactory *ifactory;
6929 GtkWidget *menuitem = NULL;
6931 gboolean can_sign = FALSE, can_encrypt = FALSE;
6932 gboolean found = FALSE;
6934 ifactory = gtk_item_factory_from_widget(compose->menubar);
6936 if (compose->privacy_system != NULL) {
6939 menuitem = gtk_item_factory_get_widget(ifactory, branch_path);
6940 g_return_if_fail(menuitem != NULL);
6942 amenu = GTK_MENU_SHELL(menuitem)->children;
6944 while (amenu != NULL) {
6945 GList *alist = amenu->next;
6947 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
6948 if (systemid != NULL) {
6949 if (strcmp(systemid, compose->privacy_system) == 0) {
6950 menuitem = GTK_WIDGET(amenu->data);
6952 can_sign = privacy_system_can_sign(systemid);
6953 can_encrypt = privacy_system_can_encrypt(systemid);
6957 } else if (strlen(compose->privacy_system) == 0) {
6958 menuitem = GTK_WIDGET(amenu->data);
6961 can_encrypt = FALSE;
6968 if (menuitem != NULL)
6969 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
6971 if (warn && !found && strlen(compose->privacy_system)) {
6972 gchar *tmp = g_strdup_printf(
6973 _("The privacy system '%s' cannot be loaded. You "
6974 "will not be able to sign or encrypt this message."),
6975 compose->privacy_system);
6976 alertpanel_warning(tmp);
6981 menu_set_sensitive(ifactory, "/Options/Sign", can_sign);
6982 menu_set_sensitive(ifactory, "/Options/Encrypt", can_encrypt);
6985 static void compose_set_out_encoding(Compose *compose)
6987 GtkItemFactoryEntry *entry;
6988 GtkItemFactory *ifactory;
6989 CharSet out_encoding;
6990 gchar *path, *p, *q;
6993 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
6994 ifactory = gtk_item_factory_from_widget(compose->menubar);
6996 for (entry = compose_entries; entry->callback != compose_address_cb;
6998 if (entry->callback == compose_set_encoding_cb &&
6999 (CharSet)entry->callback_action == out_encoding) {
7000 p = q = path = g_strdup(entry->path);
7012 item = gtk_item_factory_get_item(ifactory, path);
7013 gtk_widget_activate(item);
7020 static void compose_set_template_menu(Compose *compose)
7022 GSList *tmpl_list, *cur;
7026 tmpl_list = template_get_config();
7028 menu = gtk_menu_new();
7030 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
7031 Template *tmpl = (Template *)cur->data;
7033 item = gtk_menu_item_new_with_label(tmpl->name);
7034 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
7035 g_signal_connect(G_OBJECT(item), "activate",
7036 G_CALLBACK(compose_template_activate_cb),
7038 g_object_set_data(G_OBJECT(item), "template", tmpl);
7039 gtk_widget_show(item);
7042 gtk_widget_show(menu);
7043 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
7046 void compose_update_actions_menu(Compose *compose)
7048 GtkItemFactory *ifactory;
7050 ifactory = gtk_item_factory_from_widget(compose->menubar);
7051 action_update_compose_menu(ifactory, "/Tools/Actions", compose);
7054 static void compose_update_privacy_systems_menu(Compose *compose)
7056 static gchar *branch_path = "/Options/Privacy System";
7057 GtkItemFactory *ifactory;
7058 GtkWidget *menuitem;
7059 GSList *systems, *cur;
7062 GtkWidget *system_none;
7065 ifactory = gtk_item_factory_from_widget(compose->menubar);
7067 /* remove old entries */
7068 menuitem = gtk_item_factory_get_widget(ifactory, branch_path);
7069 g_return_if_fail(menuitem != NULL);
7071 amenu = GTK_MENU_SHELL(menuitem)->children->next;
7072 while (amenu != NULL) {
7073 GList *alist = amenu->next;
7074 gtk_widget_destroy(GTK_WIDGET(amenu->data));
7078 system_none = gtk_item_factory_get_widget(ifactory,
7079 "/Options/Privacy System/None");
7081 g_signal_connect(G_OBJECT(system_none), "activate",
7082 G_CALLBACK(compose_set_privacy_system_cb), compose);
7084 systems = privacy_get_system_ids();
7085 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
7086 gchar *systemid = cur->data;
7088 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
7089 widget = gtk_radio_menu_item_new_with_label(group,
7090 privacy_system_get_name(systemid));
7091 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
7092 g_strdup(systemid), g_free);
7093 g_signal_connect(G_OBJECT(widget), "activate",
7094 G_CALLBACK(compose_set_privacy_system_cb), compose);
7096 gtk_menu_append(GTK_MENU(system_none->parent), widget);
7097 gtk_widget_show(widget);
7100 g_slist_free(systems);
7103 void compose_reflect_prefs_all(void)
7108 for (cur = compose_list; cur != NULL; cur = cur->next) {
7109 compose = (Compose *)cur->data;
7110 compose_set_template_menu(compose);
7114 void compose_reflect_prefs_pixmap_theme(void)
7119 for (cur = compose_list; cur != NULL; cur = cur->next) {
7120 compose = (Compose *)cur->data;
7121 toolbar_update(TOOLBAR_COMPOSE, compose);
7125 static const gchar *compose_quote_char_from_context(Compose *compose)
7127 const gchar *qmark = NULL;
7129 g_return_val_if_fail(compose != NULL, NULL);
7131 switch (compose->mode) {
7132 /* use forward-specific quote char */
7133 case COMPOSE_FORWARD:
7134 case COMPOSE_FORWARD_AS_ATTACH:
7135 case COMPOSE_FORWARD_INLINE:
7136 if (compose->folder && compose->folder->prefs &&
7137 compose->folder->prefs->forward_with_format)
7138 qmark = compose->folder->prefs->forward_quotemark;
7139 else if (compose->account->forward_with_format)
7140 qmark = compose->account->forward_quotemark;
7142 qmark = prefs_common.fw_quotemark;
7145 /* use reply-specific quote char in all other modes */
7147 if (compose->folder && compose->folder->prefs &&
7148 compose->folder->prefs->reply_with_format)
7149 qmark = compose->folder->prefs->reply_quotemark;
7150 else if (compose->account->reply_with_format)
7151 qmark = compose->account->reply_quotemark;
7153 qmark = prefs_common.quotemark;
7157 if (qmark == NULL || *qmark == '\0')
7163 static void compose_template_apply(Compose *compose, Template *tmpl,
7167 GtkTextBuffer *buffer;
7171 gchar *parsed_str = NULL;
7172 gint cursor_pos = 0;
7173 const gchar *err_msg = _("Template body format error at line %d.");
7176 /* process the body */
7178 text = GTK_TEXT_VIEW(compose->text);
7179 buffer = gtk_text_view_get_buffer(text);
7182 qmark = compose_quote_char_from_context(compose);
7184 if (compose->replyinfo != NULL) {
7187 gtk_text_buffer_set_text(buffer, "", -1);
7188 mark = gtk_text_buffer_get_insert(buffer);
7189 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7191 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
7192 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
7194 } else if (compose->fwdinfo != NULL) {
7197 gtk_text_buffer_set_text(buffer, "", -1);
7198 mark = gtk_text_buffer_get_insert(buffer);
7199 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7201 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
7202 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
7205 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
7207 GtkTextIter start, end;
7210 gtk_text_buffer_get_start_iter(buffer, &start);
7211 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
7212 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
7214 /* clear the buffer now */
7216 gtk_text_buffer_set_text(buffer, "", -1);
7218 parsed_str = compose_quote_fmt(compose, dummyinfo,
7219 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
7220 procmsg_msginfo_free( dummyinfo );
7226 gtk_text_buffer_set_text(buffer, "", -1);
7227 mark = gtk_text_buffer_get_insert(buffer);
7228 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7231 if (replace && parsed_str && compose->account->auto_sig)
7232 compose_insert_sig(compose, FALSE);
7234 if (replace && parsed_str) {
7235 gtk_text_buffer_get_start_iter(buffer, &iter);
7236 gtk_text_buffer_place_cursor(buffer, &iter);
7240 cursor_pos = quote_fmt_get_cursor_pos();
7241 compose->set_cursor_pos = cursor_pos;
7242 if (cursor_pos == -1)
7244 gtk_text_buffer_get_start_iter(buffer, &iter);
7245 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
7246 gtk_text_buffer_place_cursor(buffer, &iter);
7249 /* process the other fields */
7251 compose_template_apply_fields(compose, tmpl);
7252 quote_fmt_reset_vartable();
7253 compose_changed_cb(NULL, compose);
7256 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
7258 MsgInfo* dummyinfo = NULL;
7259 MsgInfo *msginfo = NULL;
7262 if (compose->replyinfo != NULL)
7263 msginfo = compose->replyinfo;
7264 else if (compose->fwdinfo != NULL)
7265 msginfo = compose->fwdinfo;
7267 dummyinfo = compose_msginfo_new_from_compose(compose);
7268 msginfo = dummyinfo;
7271 if (tmpl->to && *tmpl->to != '\0') {
7273 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7274 compose->gtkaspell);
7276 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7278 quote_fmt_scan_string(tmpl->to);
7281 buf = quote_fmt_get_buffer();
7283 alertpanel_error(_("Template To format error."));
7285 compose_entry_append(compose, buf, COMPOSE_TO);
7289 if (tmpl->cc && *tmpl->cc != '\0') {
7291 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7292 compose->gtkaspell);
7294 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7296 quote_fmt_scan_string(tmpl->cc);
7299 buf = quote_fmt_get_buffer();
7301 alertpanel_error(_("Template Cc format error."));
7303 compose_entry_append(compose, buf, COMPOSE_CC);
7307 if (tmpl->bcc && *tmpl->bcc != '\0') {
7309 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7310 compose->gtkaspell);
7312 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7314 quote_fmt_scan_string(tmpl->bcc);
7317 buf = quote_fmt_get_buffer();
7319 alertpanel_error(_("Template Bcc format error."));
7321 compose_entry_append(compose, buf, COMPOSE_BCC);
7325 /* process the subject */
7326 if (tmpl->subject && *tmpl->subject != '\0') {
7328 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7329 compose->gtkaspell);
7331 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7333 quote_fmt_scan_string(tmpl->subject);
7336 buf = quote_fmt_get_buffer();
7338 alertpanel_error(_("Template subject format error."));
7340 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
7344 procmsg_msginfo_free( dummyinfo );
7347 static void compose_destroy(Compose *compose)
7349 GtkTextBuffer *buffer;
7350 GtkClipboard *clipboard;
7352 compose_list = g_list_remove(compose_list, compose);
7354 if (compose->updating) {
7355 debug_print("danger, not destroying anything now\n");
7356 compose->deferred_destroy = TRUE;
7359 /* NOTE: address_completion_end() does nothing with the window
7360 * however this may change. */
7361 address_completion_end(compose->window);
7363 slist_free_strings(compose->to_list);
7364 g_slist_free(compose->to_list);
7365 slist_free_strings(compose->newsgroup_list);
7366 g_slist_free(compose->newsgroup_list);
7367 slist_free_strings(compose->header_list);
7368 g_slist_free(compose->header_list);
7370 procmsg_msginfo_free(compose->targetinfo);
7371 procmsg_msginfo_free(compose->replyinfo);
7372 procmsg_msginfo_free(compose->fwdinfo);
7374 g_free(compose->replyto);
7375 g_free(compose->cc);
7376 g_free(compose->bcc);
7377 g_free(compose->newsgroups);
7378 g_free(compose->followup_to);
7380 g_free(compose->ml_post);
7382 g_free(compose->inreplyto);
7383 g_free(compose->references);
7384 g_free(compose->msgid);
7385 g_free(compose->boundary);
7387 g_free(compose->redirect_filename);
7388 if (compose->undostruct)
7389 undo_destroy(compose->undostruct);
7391 g_free(compose->sig_str);
7393 g_free(compose->exteditor_file);
7395 g_free(compose->orig_charset);
7397 g_free(compose->privacy_system);
7399 if (addressbook_get_target_compose() == compose)
7400 addressbook_set_target_compose(NULL);
7403 if (compose->gtkaspell) {
7404 gtkaspell_delete(compose->gtkaspell);
7405 compose->gtkaspell = NULL;
7409 prefs_common.compose_width = compose->scrolledwin->allocation.width;
7410 prefs_common.compose_height = compose->window->allocation.height;
7412 if (!gtk_widget_get_parent(compose->paned))
7413 gtk_widget_destroy(compose->paned);
7414 gtk_widget_destroy(compose->popupmenu);
7416 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
7417 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7418 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
7420 gtk_widget_destroy(compose->window);
7421 toolbar_destroy(compose->toolbar);
7422 g_free(compose->toolbar);
7423 g_mutex_free(compose->mutex);
7427 static void compose_attach_info_free(AttachInfo *ainfo)
7429 g_free(ainfo->file);
7430 g_free(ainfo->content_type);
7431 g_free(ainfo->name);
7435 static void compose_attach_remove_selected(Compose *compose)
7437 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
7438 GtkTreeSelection *selection;
7440 GtkTreeModel *model;
7442 selection = gtk_tree_view_get_selection(tree_view);
7443 sel = gtk_tree_selection_get_selected_rows(selection, &model);
7448 for (cur = sel; cur != NULL; cur = cur->next) {
7449 GtkTreePath *path = cur->data;
7450 GtkTreeRowReference *ref = gtk_tree_row_reference_new
7453 gtk_tree_path_free(path);
7456 for (cur = sel; cur != NULL; cur = cur->next) {
7457 GtkTreeRowReference *ref = cur->data;
7458 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
7461 if (gtk_tree_model_get_iter(model, &iter, path))
7462 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
7464 gtk_tree_path_free(path);
7465 gtk_tree_row_reference_free(ref);
7471 static struct _AttachProperty
7474 GtkWidget *mimetype_entry;
7475 GtkWidget *encoding_optmenu;
7476 GtkWidget *path_entry;
7477 GtkWidget *filename_entry;
7479 GtkWidget *cancel_btn;
7482 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
7484 gtk_tree_path_free((GtkTreePath *)ptr);
7487 static void compose_attach_property(Compose *compose)
7489 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
7491 GtkComboBox *optmenu;
7492 GtkTreeSelection *selection;
7494 GtkTreeModel *model;
7497 static gboolean cancelled;
7499 /* only if one selected */
7500 selection = gtk_tree_view_get_selection(tree_view);
7501 if (gtk_tree_selection_count_selected_rows(selection) != 1)
7504 sel = gtk_tree_selection_get_selected_rows(selection, &model);
7508 path = (GtkTreePath *) sel->data;
7509 gtk_tree_model_get_iter(model, &iter, path);
7510 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
7513 g_list_foreach(sel, gtk_tree_path_free_, NULL);
7519 if (!attach_prop.window)
7520 compose_attach_property_create(&cancelled);
7521 gtk_widget_grab_focus(attach_prop.ok_btn);
7522 gtk_widget_show(attach_prop.window);
7523 manage_window_set_transient(GTK_WINDOW(attach_prop.window));
7525 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
7526 if (ainfo->encoding == ENC_UNKNOWN)
7527 combobox_select_by_data(optmenu, ENC_BASE64);
7529 combobox_select_by_data(optmenu, ainfo->encoding);
7531 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
7532 ainfo->content_type ? ainfo->content_type : "");
7533 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
7534 ainfo->file ? ainfo->file : "");
7535 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
7536 ainfo->name ? ainfo->name : "");
7539 const gchar *entry_text;
7541 gchar *cnttype = NULL;
7548 gtk_widget_hide(attach_prop.window);
7553 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
7554 if (*entry_text != '\0') {
7557 text = g_strstrip(g_strdup(entry_text));
7558 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
7559 cnttype = g_strdup(text);
7562 alertpanel_error(_("Invalid MIME type."));
7568 ainfo->encoding = combobox_get_active_data(optmenu);
7570 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
7571 if (*entry_text != '\0') {
7572 if (is_file_exist(entry_text) &&
7573 (size = get_file_size(entry_text)) > 0)
7574 file = g_strdup(entry_text);
7577 (_("File doesn't exist or is empty."));
7583 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
7584 if (*entry_text != '\0') {
7585 g_free(ainfo->name);
7586 ainfo->name = g_strdup(entry_text);
7590 g_free(ainfo->content_type);
7591 ainfo->content_type = cnttype;
7594 g_free(ainfo->file);
7600 /* update tree store */
7601 text = to_human_readable(ainfo->size);
7602 gtk_tree_model_get_iter(model, &iter, path);
7603 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
7604 COL_MIMETYPE, ainfo->content_type,
7606 COL_NAME, ainfo->name,
7612 gtk_tree_path_free(path);
7615 #define SET_LABEL_AND_ENTRY(str, entry, top) \
7617 label = gtk_label_new(str); \
7618 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
7619 GTK_FILL, 0, 0, 0); \
7620 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
7622 entry = gtk_entry_new(); \
7623 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
7624 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
7627 static void compose_attach_property_create(gboolean *cancelled)
7633 GtkWidget *mimetype_entry;
7636 GtkListStore *optmenu_menu;
7637 GtkWidget *path_entry;
7638 GtkWidget *filename_entry;
7641 GtkWidget *cancel_btn;
7642 GList *mime_type_list, *strlist;
7645 debug_print("Creating attach_property window...\n");
7647 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
7648 gtk_widget_set_size_request(window, 480, -1);
7649 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
7650 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
7651 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
7652 gtk_window_set_modal(GTK_WINDOW(window), TRUE);
7653 g_signal_connect(G_OBJECT(window), "delete_event",
7654 G_CALLBACK(attach_property_delete_event),
7656 g_signal_connect(G_OBJECT(window), "key_press_event",
7657 G_CALLBACK(attach_property_key_pressed),
7660 vbox = gtk_vbox_new(FALSE, 8);
7661 gtk_container_add(GTK_CONTAINER(window), vbox);
7663 table = gtk_table_new(4, 2, FALSE);
7664 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
7665 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
7666 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
7668 label = gtk_label_new(_("MIME type"));
7669 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
7671 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
7672 mimetype_entry = gtk_combo_new();
7673 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
7674 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
7676 /* stuff with list */
7677 mime_type_list = procmime_get_mime_type_list();
7679 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
7680 MimeType *type = (MimeType *) mime_type_list->data;
7683 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
7685 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
7688 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
7689 (GCompareFunc)strcmp2);
7692 gtk_combo_set_popdown_strings(GTK_COMBO(mimetype_entry), strlist);
7694 for (mime_type_list = strlist; mime_type_list != NULL;
7695 mime_type_list = mime_type_list->next)
7696 g_free(mime_type_list->data);
7697 g_list_free(strlist);
7699 mimetype_entry = GTK_COMBO(mimetype_entry)->entry;
7701 label = gtk_label_new(_("Encoding"));
7702 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
7704 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
7706 hbox = gtk_hbox_new(FALSE, 0);
7707 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
7708 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
7710 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
7711 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
7713 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
7714 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
7715 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
7716 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
7717 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
7719 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
7721 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
7722 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
7724 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
7725 &ok_btn, GTK_STOCK_OK,
7727 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
7728 gtk_widget_grab_default(ok_btn);
7730 g_signal_connect(G_OBJECT(ok_btn), "clicked",
7731 G_CALLBACK(attach_property_ok),
7733 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
7734 G_CALLBACK(attach_property_cancel),
7737 gtk_widget_show_all(vbox);
7739 attach_prop.window = window;
7740 attach_prop.mimetype_entry = mimetype_entry;
7741 attach_prop.encoding_optmenu = optmenu;
7742 attach_prop.path_entry = path_entry;
7743 attach_prop.filename_entry = filename_entry;
7744 attach_prop.ok_btn = ok_btn;
7745 attach_prop.cancel_btn = cancel_btn;
7748 #undef SET_LABEL_AND_ENTRY
7750 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
7756 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
7762 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
7763 gboolean *cancelled)
7771 static gboolean attach_property_key_pressed(GtkWidget *widget,
7773 gboolean *cancelled)
7775 if (event && event->keyval == GDK_Escape) {
7782 static void compose_exec_ext_editor(Compose *compose)
7789 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
7790 G_DIR_SEPARATOR, compose);
7792 if (pipe(pipe_fds) < 0) {
7798 if ((pid = fork()) < 0) {
7805 /* close the write side of the pipe */
7808 compose->exteditor_file = g_strdup(tmp);
7809 compose->exteditor_pid = pid;
7811 compose_set_ext_editor_sensitive(compose, FALSE);
7813 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
7814 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
7818 } else { /* process-monitoring process */
7824 /* close the read side of the pipe */
7827 if (compose_write_body_to_file(compose, tmp) < 0) {
7828 fd_write_all(pipe_fds[1], "2\n", 2);
7832 pid_ed = compose_exec_ext_editor_real(tmp);
7834 fd_write_all(pipe_fds[1], "1\n", 2);
7838 /* wait until editor is terminated */
7839 waitpid(pid_ed, NULL, 0);
7841 fd_write_all(pipe_fds[1], "0\n", 2);
7848 #endif /* G_OS_UNIX */
7852 static gint compose_exec_ext_editor_real(const gchar *file)
7859 g_return_val_if_fail(file != NULL, -1);
7861 if ((pid = fork()) < 0) {
7866 if (pid != 0) return pid;
7868 /* grandchild process */
7870 if (setpgid(0, getppid()))
7873 if (prefs_common.ext_editor_cmd &&
7874 (p = strchr(prefs_common.ext_editor_cmd, '%')) &&
7875 *(p + 1) == 's' && !strchr(p + 2, '%')) {
7876 g_snprintf(buf, sizeof(buf), prefs_common.ext_editor_cmd, file);
7878 if (prefs_common.ext_editor_cmd)
7879 g_warning("External editor command line is invalid: '%s'\n",
7880 prefs_common.ext_editor_cmd);
7881 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
7884 cmdline = strsplit_with_quote(buf, " ", 1024);
7885 execvp(cmdline[0], cmdline);
7888 g_strfreev(cmdline);
7893 static gboolean compose_ext_editor_kill(Compose *compose)
7895 pid_t pgid = compose->exteditor_pid * -1;
7898 ret = kill(pgid, 0);
7900 if (ret == 0 || (ret == -1 && EPERM == errno)) {
7904 msg = g_strdup_printf
7905 (_("The external editor is still working.\n"
7906 "Force terminating the process?\n"
7907 "process group id: %d"), -pgid);
7908 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
7909 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
7913 if (val == G_ALERTALTERNATE) {
7914 g_source_remove(compose->exteditor_tag);
7915 g_io_channel_shutdown(compose->exteditor_ch,
7917 g_io_channel_unref(compose->exteditor_ch);
7919 if (kill(pgid, SIGTERM) < 0) perror("kill");
7920 waitpid(compose->exteditor_pid, NULL, 0);
7922 g_warning("Terminated process group id: %d", -pgid);
7923 g_warning("Temporary file: %s",
7924 compose->exteditor_file);
7926 compose_set_ext_editor_sensitive(compose, TRUE);
7928 g_free(compose->exteditor_file);
7929 compose->exteditor_file = NULL;
7930 compose->exteditor_pid = -1;
7931 compose->exteditor_ch = NULL;
7932 compose->exteditor_tag = -1;
7940 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
7944 Compose *compose = (Compose *)data;
7947 debug_print(_("Compose: input from monitoring process\n"));
7949 g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL);
7951 g_io_channel_shutdown(source, FALSE, NULL);
7952 g_io_channel_unref(source);
7954 waitpid(compose->exteditor_pid, NULL, 0);
7956 if (buf[0] == '0') { /* success */
7957 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
7958 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
7960 gtk_text_buffer_set_text(buffer, "", -1);
7961 compose_insert_file(compose, compose->exteditor_file);
7962 compose_changed_cb(NULL, compose);
7964 if (g_unlink(compose->exteditor_file) < 0)
7965 FILE_OP_ERROR(compose->exteditor_file, "unlink");
7966 } else if (buf[0] == '1') { /* failed */
7967 g_warning("Couldn't exec external editor\n");
7968 if (g_unlink(compose->exteditor_file) < 0)
7969 FILE_OP_ERROR(compose->exteditor_file, "unlink");
7970 } else if (buf[0] == '2') {
7971 g_warning("Couldn't write to file\n");
7972 } else if (buf[0] == '3') {
7973 g_warning("Pipe read failed\n");
7976 compose_set_ext_editor_sensitive(compose, TRUE);
7978 g_free(compose->exteditor_file);
7979 compose->exteditor_file = NULL;
7980 compose->exteditor_pid = -1;
7981 compose->exteditor_ch = NULL;
7982 compose->exteditor_tag = -1;
7987 static void compose_set_ext_editor_sensitive(Compose *compose,
7990 GtkItemFactory *ifactory;
7992 ifactory = gtk_item_factory_from_widget(compose->menubar);
7994 menu_set_sensitive(ifactory, "/Message/Send", sensitive);
7995 menu_set_sensitive(ifactory, "/Message/Send later", sensitive);
7996 menu_set_sensitive(ifactory, "/Message/Insert file", sensitive);
7997 menu_set_sensitive(ifactory, "/Message/Insert signature", sensitive);
7998 menu_set_sensitive(ifactory, "/Edit/Wrap current paragraph", sensitive);
7999 menu_set_sensitive(ifactory, "/Edit/Wrap all long lines", sensitive);
8000 menu_set_sensitive(ifactory, "/Edit/Edit with external editor",
8003 gtk_widget_set_sensitive(compose->text, sensitive);
8004 if (compose->toolbar->send_btn)
8005 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
8006 if (compose->toolbar->sendl_btn)
8007 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
8008 if (compose->toolbar->draft_btn)
8009 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
8010 if (compose->toolbar->insert_btn)
8011 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
8012 if (compose->toolbar->sig_btn)
8013 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
8014 if (compose->toolbar->exteditor_btn)
8015 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
8016 if (compose->toolbar->linewrap_current_btn)
8017 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
8018 if (compose->toolbar->linewrap_all_btn)
8019 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
8021 #endif /* G_OS_UNIX */
8024 * compose_undo_state_changed:
8026 * Change the sensivity of the menuentries undo and redo
8028 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
8029 gint redo_state, gpointer data)
8031 GtkWidget *widget = GTK_WIDGET(data);
8032 GtkItemFactory *ifactory;
8034 g_return_if_fail(widget != NULL);
8036 ifactory = gtk_item_factory_from_widget(widget);
8038 switch (undo_state) {
8039 case UNDO_STATE_TRUE:
8040 if (!undostruct->undo_state) {
8041 undostruct->undo_state = TRUE;
8042 menu_set_sensitive(ifactory, "/Edit/Undo", TRUE);
8045 case UNDO_STATE_FALSE:
8046 if (undostruct->undo_state) {
8047 undostruct->undo_state = FALSE;
8048 menu_set_sensitive(ifactory, "/Edit/Undo", FALSE);
8051 case UNDO_STATE_UNCHANGED:
8053 case UNDO_STATE_REFRESH:
8054 menu_set_sensitive(ifactory, "/Edit/Undo",
8055 undostruct->undo_state);
8058 g_warning("Undo state not recognized");
8062 switch (redo_state) {
8063 case UNDO_STATE_TRUE:
8064 if (!undostruct->redo_state) {
8065 undostruct->redo_state = TRUE;
8066 menu_set_sensitive(ifactory, "/Edit/Redo", TRUE);
8069 case UNDO_STATE_FALSE:
8070 if (undostruct->redo_state) {
8071 undostruct->redo_state = FALSE;
8072 menu_set_sensitive(ifactory, "/Edit/Redo", FALSE);
8075 case UNDO_STATE_UNCHANGED:
8077 case UNDO_STATE_REFRESH:
8078 menu_set_sensitive(ifactory, "/Edit/Redo",
8079 undostruct->redo_state);
8082 g_warning("Redo state not recognized");
8087 /* callback functions */
8089 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
8090 * includes "non-client" (windows-izm) in calculation, so this calculation
8091 * may not be accurate.
8093 static gboolean compose_edit_size_alloc(GtkEditable *widget,
8094 GtkAllocation *allocation,
8095 GtkSHRuler *shruler)
8097 if (prefs_common.show_ruler) {
8098 gint char_width = 0, char_height = 0;
8099 gint line_width_in_chars;
8101 gtkut_get_font_size(GTK_WIDGET(widget),
8102 &char_width, &char_height);
8103 line_width_in_chars =
8104 (allocation->width - allocation->x) / char_width;
8106 /* got the maximum */
8107 gtk_ruler_set_range(GTK_RULER(shruler),
8108 0.0, line_width_in_chars, 0,
8109 /*line_width_in_chars*/ char_width);
8115 static void account_activated(GtkComboBox *optmenu, gpointer data)
8117 Compose *compose = (Compose *)data;
8120 gchar *folderidentifier;
8121 gint account_id = 0;
8125 /* Get ID of active account in the combo box */
8126 menu = gtk_combo_box_get_model(optmenu);
8127 gtk_combo_box_get_active_iter(optmenu, &iter);
8128 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
8130 ac = account_find_from_id(account_id);
8131 g_return_if_fail(ac != NULL);
8133 if (ac != compose->account)
8134 compose_select_account(compose, ac, FALSE);
8136 /* Set message save folder */
8137 if (account_get_special_folder(compose->account, F_OUTBOX)) {
8138 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
8140 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
8141 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
8143 gtk_editable_delete_text(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
8144 if (account_get_special_folder(compose->account, F_OUTBOX)) {
8145 folderidentifier = folder_item_get_identifier(account_get_special_folder
8146 (compose->account, F_OUTBOX));
8147 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
8148 g_free(folderidentifier);
8152 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
8153 GtkTreeViewColumn *column, Compose *compose)
8155 compose_attach_property(compose);
8158 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
8161 Compose *compose = (Compose *)data;
8162 GtkTreeSelection *attach_selection;
8163 gint attach_nr_selected;
8164 GtkItemFactory *ifactory;
8166 if (!event) return FALSE;
8168 if (event->button == 3) {
8169 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
8170 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
8171 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
8173 if (attach_nr_selected > 0)
8175 menu_set_sensitive(ifactory, "/Remove", TRUE);
8176 menu_set_sensitive(ifactory, "/Properties...", TRUE);
8178 menu_set_sensitive(ifactory, "/Remove", FALSE);
8179 menu_set_sensitive(ifactory, "/Properties...", FALSE);
8182 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
8183 NULL, NULL, event->button, event->time);
8190 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
8193 Compose *compose = (Compose *)data;
8195 if (!event) return FALSE;
8197 switch (event->keyval) {
8199 compose_attach_remove_selected(compose);
8205 static void compose_allow_user_actions (Compose *compose, gboolean allow)
8207 GtkItemFactory *ifactory = gtk_item_factory_from_widget(compose->menubar);
8208 toolbar_comp_set_sensitive(compose, allow);
8209 menu_set_sensitive(ifactory, "/Message", allow);
8210 menu_set_sensitive(ifactory, "/Edit", allow);
8212 menu_set_sensitive(ifactory, "/Spelling", allow);
8214 menu_set_sensitive(ifactory, "/Options", allow);
8215 menu_set_sensitive(ifactory, "/Tools", allow);
8216 menu_set_sensitive(ifactory, "/Help", allow);
8218 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
8222 static void compose_send_cb(gpointer data, guint action, GtkWidget *widget)
8224 Compose *compose = (Compose *)data;
8226 if (prefs_common.work_offline &&
8227 !inc_offline_should_override(TRUE,
8228 _("Claws Mail needs network access in order "
8229 "to send this email.")))
8232 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
8233 g_source_remove(compose->draft_timeout_tag);
8234 compose->draft_timeout_tag = -1;
8237 compose_send(compose);
8240 static void compose_send_later_cb(gpointer data, guint action,
8243 Compose *compose = (Compose *)data;
8247 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
8251 compose_close(compose);
8252 } else if (val == -1) {
8253 alertpanel_error(_("Could not queue message."));
8254 } else if (val == -2) {
8255 alertpanel_error(_("Could not queue message:\n\n%s."), strerror(errno));
8256 } else if (val == -3) {
8257 if (privacy_peek_error())
8258 alertpanel_error(_("Could not queue message for sending:\n\n"
8259 "Signature failed: %s"), privacy_get_error());
8260 } else if (val == -4) {
8261 alertpanel_error(_("Could not queue message for sending:\n\n"
8262 "Charset conversion failed."));
8263 } else if (val == -5) {
8264 alertpanel_error(_("Could not queue message for sending:\n\n"
8265 "Couldn't get recipient encryption key."));
8266 } else if (val == -6) {
8269 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
8272 #define DRAFTED_AT_EXIT "drafted_at_exit"
8273 static void compose_register_draft(MsgInfo *info)
8275 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8276 DRAFTED_AT_EXIT, NULL);
8277 FILE *fp = fopen(filepath, "ab");
8280 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
8288 gboolean compose_draft (gpointer data, guint action)
8290 Compose *compose = (Compose *)data;
8294 MsgFlags flag = {0, 0};
8295 static gboolean lock = FALSE;
8296 MsgInfo *newmsginfo;
8298 gboolean target_locked = FALSE;
8300 if (lock) return FALSE;
8302 if (compose->sending)
8305 draft = account_get_special_folder(compose->account, F_DRAFT);
8306 g_return_val_if_fail(draft != NULL, FALSE);
8308 if (!g_mutex_trylock(compose->mutex)) {
8309 /* we don't want to lock the mutex once it's available,
8310 * because as the only other part of compose.c locking
8311 * it is compose_close - which means once unlocked,
8312 * the compose struct will be freed */
8313 debug_print("couldn't lock mutex, probably sending\n");
8319 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
8320 G_DIR_SEPARATOR, compose);
8321 if ((fp = g_fopen(tmp, "wb")) == NULL) {
8322 FILE_OP_ERROR(tmp, "fopen");
8326 /* chmod for security */
8327 if (change_file_mode_rw(fp, tmp) < 0) {
8328 FILE_OP_ERROR(tmp, "chmod");
8329 g_warning("can't change file mode\n");
8332 /* Save draft infos */
8333 fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id);
8334 fprintf(fp, "S:%s\n", compose->account->address);
8336 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
8337 gchar *savefolderid;
8339 savefolderid = gtk_editable_get_chars(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
8340 fprintf(fp, "SCF:%s\n", savefolderid);
8341 g_free(savefolderid);
8343 if (compose->return_receipt) {
8344 fprintf(fp, "RRCPT:1\n");
8346 if (compose->privacy_system) {
8347 fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing);
8348 fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption);
8349 fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system);
8352 /* Message-ID of message replying to */
8353 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
8356 folderid = folder_item_get_identifier(compose->replyinfo->folder);
8357 fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid);
8360 /* Message-ID of message forwarding to */
8361 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
8364 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
8365 fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid);
8369 /* end of headers */
8370 fprintf(fp, "X-Claws-End-Special-Headers: 1\n");
8372 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
8380 if (compose->targetinfo) {
8381 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
8382 flag.perm_flags = target_locked?MSG_LOCKED:0;
8384 flag.tmp_flags = MSG_DRAFT;
8386 folder_item_scan(draft);
8387 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
8390 if (action != COMPOSE_AUTO_SAVE) {
8391 if (action != COMPOSE_DRAFT_FOR_EXIT)
8392 alertpanel_error(_("Could not save draft."));
8395 gtkut_window_popup(compose->window);
8396 val = alertpanel_full(_("Could not save draft"),
8397 _("Could not save draft.\n"
8398 "Do you want to cancel exit or discard this email?"),
8399 _("_Cancel exit"), _("_Discard email"), NULL,
8400 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
8401 if (val == G_ALERTALTERNATE) {
8403 g_mutex_unlock(compose->mutex); /* must be done before closing */
8404 compose_close(compose);
8408 g_mutex_unlock(compose->mutex); /* must be done before closing */
8417 if (compose->mode == COMPOSE_REEDIT) {
8418 compose_remove_reedit_target(compose, TRUE);
8421 newmsginfo = folder_item_get_msginfo(draft, msgnum);
8423 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
8425 procmsg_msginfo_set_flags(newmsginfo, MSG_LOCKED, MSG_DRAFT);
8427 procmsg_msginfo_set_flags(newmsginfo, 0, MSG_DRAFT);
8428 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
8429 procmsg_msginfo_set_flags(newmsginfo, 0,
8430 MSG_HAS_ATTACHMENT);
8432 if (action == COMPOSE_DRAFT_FOR_EXIT) {
8433 compose_register_draft(newmsginfo);
8435 procmsg_msginfo_free(newmsginfo);
8438 folder_item_scan(draft);
8440 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
8442 g_mutex_unlock(compose->mutex); /* must be done before closing */
8443 compose_close(compose);
8449 path = folder_item_fetch_msg(draft, msgnum);
8451 debug_print("can't fetch %s:%d\n",draft->path, msgnum);
8454 if (g_stat(path, &s) < 0) {
8455 FILE_OP_ERROR(path, "stat");
8461 procmsg_msginfo_free(compose->targetinfo);
8462 compose->targetinfo = procmsg_msginfo_new();
8463 compose->targetinfo->msgnum = msgnum;
8464 compose->targetinfo->size = s.st_size;
8465 compose->targetinfo->mtime = s.st_mtime;
8466 compose->targetinfo->folder = draft;
8468 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
8469 compose->mode = COMPOSE_REEDIT;
8471 if (action == COMPOSE_AUTO_SAVE) {
8472 compose->autosaved_draft = compose->targetinfo;
8474 compose->modified = FALSE;
8475 compose_set_title(compose);
8479 g_mutex_unlock(compose->mutex);
8483 void compose_clear_exit_drafts(void)
8485 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8486 DRAFTED_AT_EXIT, NULL);
8487 if (is_file_exist(filepath))
8493 void compose_reopen_exit_drafts(void)
8495 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8496 DRAFTED_AT_EXIT, NULL);
8497 FILE *fp = fopen(filepath, "rb");
8501 while (fgets(buf, sizeof(buf), fp)) {
8502 gchar **parts = g_strsplit(buf, "\t", 2);
8503 const gchar *folder = parts[0];
8504 int msgnum = parts[1] ? atoi(parts[1]):-1;
8506 if (folder && *folder && msgnum > -1) {
8507 FolderItem *item = folder_find_item_from_identifier(folder);
8508 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
8510 compose_reedit(info, FALSE);
8517 compose_clear_exit_drafts();
8520 static void compose_draft_cb(gpointer data, guint action, GtkWidget *widget)
8522 compose_draft(data, action);
8525 static void compose_attach_cb(gpointer data, guint action, GtkWidget *widget)
8527 Compose *compose = (Compose *)data;
8530 if (compose->redirect_filename != NULL)
8533 file_list = filesel_select_multiple_files_open(_("Select file"));
8538 for ( tmp = file_list; tmp; tmp = tmp->next) {
8539 gchar *file = (gchar *) tmp->data;
8540 gchar *utf8_filename = conv_filename_to_utf8(file);
8541 compose_attach_append(compose, file, utf8_filename, NULL);
8542 compose_changed_cb(NULL, compose);
8544 g_free(utf8_filename);
8546 g_list_free(file_list);
8550 static void compose_insert_file_cb(gpointer data, guint action,
8553 Compose *compose = (Compose *)data;
8556 file_list = filesel_select_multiple_files_open(_("Select file"));
8561 for ( tmp = file_list; tmp; tmp = tmp->next) {
8562 gchar *file = (gchar *) tmp->data;
8563 gchar *filedup = g_strdup(file);
8564 gchar *shortfile = g_path_get_basename(filedup);
8565 ComposeInsertResult res;
8567 res = compose_insert_file(compose, file);
8568 if (res == COMPOSE_INSERT_READ_ERROR) {
8569 alertpanel_error(_("File '%s' could not be read."), shortfile);
8570 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
8571 alertpanel_error(_("File '%s' contained invalid characters\n"
8572 "for the current encoding, insertion may be incorrect."), shortfile);
8578 g_list_free(file_list);
8582 static void compose_insert_sig_cb(gpointer data, guint action,
8585 Compose *compose = (Compose *)data;
8587 compose_insert_sig(compose, FALSE);
8590 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
8594 Compose *compose = (Compose *)data;
8596 gtkut_widget_get_uposition(widget, &x, &y);
8597 prefs_common.compose_x = x;
8598 prefs_common.compose_y = y;
8600 if (compose->sending || compose->updating)
8602 compose_close_cb(compose, 0, NULL);
8606 void compose_close_toolbar(Compose *compose)
8608 compose_close_cb(compose, 0, NULL);
8611 static void compose_close_cb(gpointer data, guint action, GtkWidget *widget)
8613 Compose *compose = (Compose *)data;
8617 if (compose->exteditor_tag != -1) {
8618 if (!compose_ext_editor_kill(compose))
8623 if (compose->modified) {
8624 val = alertpanel(_("Discard message"),
8625 _("This message has been modified. Discard it?"),
8626 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
8629 case G_ALERTDEFAULT:
8630 if (prefs_common.autosave)
8631 compose_remove_draft(compose);
8633 case G_ALERTALTERNATE:
8634 compose_draft_cb(data, COMPOSE_QUIT_EDITING, NULL);
8641 compose_close(compose);
8644 static void compose_set_encoding_cb(gpointer data, guint action,
8647 Compose *compose = (Compose *)data;
8649 if (GTK_CHECK_MENU_ITEM(widget)->active)
8650 compose->out_encoding = (CharSet)action;
8653 static void compose_address_cb(gpointer data, guint action, GtkWidget *widget)
8655 Compose *compose = (Compose *)data;
8657 addressbook_open(compose);
8660 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
8662 Compose *compose = (Compose *)data;
8667 tmpl = g_object_get_data(G_OBJECT(widget), "template");
8668 g_return_if_fail(tmpl != NULL);
8670 msg = g_strdup_printf(_("Do you want to apply the template '%s' ?"),
8672 val = alertpanel(_("Apply template"), msg,
8673 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
8676 if (val == G_ALERTDEFAULT)
8677 compose_template_apply(compose, tmpl, TRUE);
8678 else if (val == G_ALERTALTERNATE)
8679 compose_template_apply(compose, tmpl, FALSE);
8682 static void compose_ext_editor_cb(gpointer data, guint action,
8685 Compose *compose = (Compose *)data;
8687 compose_exec_ext_editor(compose);
8690 static void compose_undo_cb(Compose *compose)
8692 gboolean prev_autowrap = compose->autowrap;
8694 compose->autowrap = FALSE;
8695 undo_undo(compose->undostruct);
8696 compose->autowrap = prev_autowrap;
8699 static void compose_redo_cb(Compose *compose)
8701 gboolean prev_autowrap = compose->autowrap;
8703 compose->autowrap = FALSE;
8704 undo_redo(compose->undostruct);
8705 compose->autowrap = prev_autowrap;
8708 static void entry_cut_clipboard(GtkWidget *entry)
8710 if (GTK_IS_EDITABLE(entry))
8711 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
8712 else if (GTK_IS_TEXT_VIEW(entry))
8713 gtk_text_buffer_cut_clipboard(
8714 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
8715 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
8719 static void entry_copy_clipboard(GtkWidget *entry)
8721 if (GTK_IS_EDITABLE(entry))
8722 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
8723 else if (GTK_IS_TEXT_VIEW(entry))
8724 gtk_text_buffer_copy_clipboard(
8725 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
8726 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
8729 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
8730 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
8732 if (GTK_IS_TEXT_VIEW(entry)) {
8733 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
8734 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
8735 GtkTextIter start_iter, end_iter;
8737 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
8739 if (contents == NULL)
8742 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
8744 /* we shouldn't delete the selection when middle-click-pasting, or we
8745 * can't mid-click-paste our own selection */
8746 if (clip != GDK_SELECTION_PRIMARY) {
8747 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
8750 if (insert_place == NULL) {
8751 /* if insert_place isn't specified, insert at the cursor.
8752 * used for Ctrl-V pasting */
8753 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
8754 start = gtk_text_iter_get_offset(&start_iter);
8755 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
8757 /* if insert_place is specified, paste here.
8758 * used for mid-click-pasting */
8759 start = gtk_text_iter_get_offset(insert_place);
8760 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
8764 /* paste unwrapped: mark the paste so it's not wrapped later */
8765 end = start + strlen(contents);
8766 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
8767 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
8768 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
8769 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
8770 /* rewrap paragraph now (after a mid-click-paste) */
8771 mark_start = gtk_text_buffer_get_insert(buffer);
8772 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
8773 gtk_text_iter_backward_char(&start_iter);
8774 compose_beautify_paragraph(compose, &start_iter, TRUE);
8776 } else if (GTK_IS_EDITABLE(entry))
8777 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
8781 static void entry_allsel(GtkWidget *entry)
8783 if (GTK_IS_EDITABLE(entry))
8784 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
8785 else if (GTK_IS_TEXT_VIEW(entry)) {
8786 GtkTextIter startiter, enditer;
8787 GtkTextBuffer *textbuf;
8789 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
8790 gtk_text_buffer_get_start_iter(textbuf, &startiter);
8791 gtk_text_buffer_get_end_iter(textbuf, &enditer);
8793 gtk_text_buffer_move_mark_by_name(textbuf,
8794 "selection_bound", &startiter);
8795 gtk_text_buffer_move_mark_by_name(textbuf,
8796 "insert", &enditer);
8800 static void compose_cut_cb(Compose *compose)
8802 if (compose->focused_editable
8804 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
8807 entry_cut_clipboard(compose->focused_editable);
8810 static void compose_copy_cb(Compose *compose)
8812 if (compose->focused_editable
8814 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
8817 entry_copy_clipboard(compose->focused_editable);
8820 static void compose_paste_cb(Compose *compose)
8823 GtkTextBuffer *buffer;
8825 if (compose->focused_editable &&
8826 GTK_WIDGET_HAS_FOCUS(compose->focused_editable))
8827 entry_paste_clipboard(compose, compose->focused_editable,
8828 prefs_common.linewrap_pastes,
8829 GDK_SELECTION_CLIPBOARD, NULL);
8833 static void compose_paste_as_quote_cb(Compose *compose)
8835 gint wrap_quote = prefs_common.linewrap_quote;
8836 if (compose->focused_editable
8838 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
8841 /* let text_insert() (called directly or at a later time
8842 * after the gtk_editable_paste_clipboard) know that
8843 * text is to be inserted as a quotation. implemented
8844 * by using a simple refcount... */
8845 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
8846 G_OBJECT(compose->focused_editable),
8847 "paste_as_quotation"));
8848 g_object_set_data(G_OBJECT(compose->focused_editable),
8849 "paste_as_quotation",
8850 GINT_TO_POINTER(paste_as_quotation + 1));
8851 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
8852 entry_paste_clipboard(compose, compose->focused_editable,
8853 prefs_common.linewrap_pastes,
8854 GDK_SELECTION_CLIPBOARD, NULL);
8855 prefs_common.linewrap_quote = wrap_quote;
8859 static void compose_paste_no_wrap_cb(Compose *compose)
8862 GtkTextBuffer *buffer;
8864 if (compose->focused_editable
8866 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
8869 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
8870 GDK_SELECTION_CLIPBOARD, NULL);
8874 static void compose_paste_wrap_cb(Compose *compose)
8877 GtkTextBuffer *buffer;
8879 if (compose->focused_editable
8881 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
8884 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
8885 GDK_SELECTION_CLIPBOARD, NULL);
8889 static void compose_allsel_cb(Compose *compose)
8891 if (compose->focused_editable
8893 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
8896 entry_allsel(compose->focused_editable);
8899 static void textview_move_beginning_of_line (GtkTextView *text)
8901 GtkTextBuffer *buffer;
8905 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
8907 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8908 mark = gtk_text_buffer_get_insert(buffer);
8909 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
8910 gtk_text_iter_set_line_offset(&ins, 0);
8911 gtk_text_buffer_place_cursor(buffer, &ins);
8914 static void textview_move_forward_character (GtkTextView *text)
8916 GtkTextBuffer *buffer;
8920 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
8922 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8923 mark = gtk_text_buffer_get_insert(buffer);
8924 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
8925 if (gtk_text_iter_forward_cursor_position(&ins))
8926 gtk_text_buffer_place_cursor(buffer, &ins);
8929 static void textview_move_backward_character (GtkTextView *text)
8931 GtkTextBuffer *buffer;
8935 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
8937 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8938 mark = gtk_text_buffer_get_insert(buffer);
8939 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
8940 if (gtk_text_iter_backward_cursor_position(&ins))
8941 gtk_text_buffer_place_cursor(buffer, &ins);
8944 static void textview_move_forward_word (GtkTextView *text)
8946 GtkTextBuffer *buffer;
8951 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
8953 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8954 mark = gtk_text_buffer_get_insert(buffer);
8955 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
8956 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
8957 if (gtk_text_iter_forward_word_ends(&ins, count)) {
8958 gtk_text_iter_backward_word_start(&ins);
8959 gtk_text_buffer_place_cursor(buffer, &ins);
8963 static void textview_move_backward_word (GtkTextView *text)
8965 GtkTextBuffer *buffer;
8970 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
8972 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8973 mark = gtk_text_buffer_get_insert(buffer);
8974 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
8975 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
8976 if (gtk_text_iter_backward_word_starts(&ins, 1))
8977 gtk_text_buffer_place_cursor(buffer, &ins);
8980 static void textview_move_end_of_line (GtkTextView *text)
8982 GtkTextBuffer *buffer;
8986 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
8988 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8989 mark = gtk_text_buffer_get_insert(buffer);
8990 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
8991 if (gtk_text_iter_forward_to_line_end(&ins))
8992 gtk_text_buffer_place_cursor(buffer, &ins);
8995 static void textview_move_next_line (GtkTextView *text)
8997 GtkTextBuffer *buffer;
9002 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9004 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9005 mark = gtk_text_buffer_get_insert(buffer);
9006 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9007 offset = gtk_text_iter_get_line_offset(&ins);
9008 if (gtk_text_iter_forward_line(&ins)) {
9009 gtk_text_iter_set_line_offset(&ins, offset);
9010 gtk_text_buffer_place_cursor(buffer, &ins);
9014 static void textview_move_previous_line (GtkTextView *text)
9016 GtkTextBuffer *buffer;
9021 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9023 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9024 mark = gtk_text_buffer_get_insert(buffer);
9025 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9026 offset = gtk_text_iter_get_line_offset(&ins);
9027 if (gtk_text_iter_backward_line(&ins)) {
9028 gtk_text_iter_set_line_offset(&ins, offset);
9029 gtk_text_buffer_place_cursor(buffer, &ins);
9033 static void textview_delete_forward_character (GtkTextView *text)
9035 GtkTextBuffer *buffer;
9037 GtkTextIter ins, end_iter;
9039 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9041 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9042 mark = gtk_text_buffer_get_insert(buffer);
9043 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9045 if (gtk_text_iter_forward_char(&end_iter)) {
9046 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9050 static void textview_delete_backward_character (GtkTextView *text)
9052 GtkTextBuffer *buffer;
9054 GtkTextIter ins, end_iter;
9056 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9058 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9059 mark = gtk_text_buffer_get_insert(buffer);
9060 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9062 if (gtk_text_iter_backward_char(&end_iter)) {
9063 gtk_text_buffer_delete(buffer, &end_iter, &ins);
9067 static void textview_delete_forward_word (GtkTextView *text)
9069 GtkTextBuffer *buffer;
9071 GtkTextIter ins, end_iter;
9073 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9075 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9076 mark = gtk_text_buffer_get_insert(buffer);
9077 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9079 if (gtk_text_iter_forward_word_end(&end_iter)) {
9080 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9084 static void textview_delete_backward_word (GtkTextView *text)
9086 GtkTextBuffer *buffer;
9088 GtkTextIter ins, end_iter;
9090 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9092 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9093 mark = gtk_text_buffer_get_insert(buffer);
9094 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9096 if (gtk_text_iter_backward_word_start(&end_iter)) {
9097 gtk_text_buffer_delete(buffer, &end_iter, &ins);
9101 static void textview_delete_line (GtkTextView *text)
9103 GtkTextBuffer *buffer;
9105 GtkTextIter ins, start_iter, end_iter;
9108 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9110 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9111 mark = gtk_text_buffer_get_insert(buffer);
9112 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9115 gtk_text_iter_set_line_offset(&start_iter, 0);
9118 if (gtk_text_iter_ends_line(&end_iter))
9119 found = gtk_text_iter_forward_char(&end_iter);
9121 found = gtk_text_iter_forward_to_line_end(&end_iter);
9124 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
9127 static void textview_delete_to_line_end (GtkTextView *text)
9129 GtkTextBuffer *buffer;
9131 GtkTextIter ins, end_iter;
9134 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9136 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9137 mark = gtk_text_buffer_get_insert(buffer);
9138 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9140 if (gtk_text_iter_ends_line(&end_iter))
9141 found = gtk_text_iter_forward_char(&end_iter);
9143 found = gtk_text_iter_forward_to_line_end(&end_iter);
9145 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9148 static void compose_advanced_action_cb(Compose *compose,
9149 ComposeCallAdvancedAction action)
9151 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9153 void (*do_action) (GtkTextView *text);
9154 } action_table[] = {
9155 {textview_move_beginning_of_line},
9156 {textview_move_forward_character},
9157 {textview_move_backward_character},
9158 {textview_move_forward_word},
9159 {textview_move_backward_word},
9160 {textview_move_end_of_line},
9161 {textview_move_next_line},
9162 {textview_move_previous_line},
9163 {textview_delete_forward_character},
9164 {textview_delete_backward_character},
9165 {textview_delete_forward_word},
9166 {textview_delete_backward_word},
9167 {textview_delete_line},
9168 {NULL}, /* gtk_stext_delete_line_n */
9169 {textview_delete_to_line_end}
9172 if (!GTK_WIDGET_HAS_FOCUS(text)) return;
9174 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
9175 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
9176 if (action_table[action].do_action)
9177 action_table[action].do_action(text);
9179 g_warning("Not implemented yet.");
9183 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
9187 if (GTK_IS_EDITABLE(widget)) {
9188 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
9189 gtk_editable_set_position(GTK_EDITABLE(widget),
9192 if (widget->parent && widget->parent->parent
9193 && widget->parent->parent->parent) {
9194 if (GTK_IS_SCROLLED_WINDOW(widget->parent->parent->parent)) {
9195 gint y = widget->allocation.y;
9196 gint height = widget->allocation.height;
9197 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
9198 (GTK_SCROLLED_WINDOW(widget->parent->parent->parent));
9200 if (y < (int)shown->value) {
9201 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown), y - 1);
9203 if (y + height > (int)shown->value + (int)shown->page_size) {
9204 if (y - height - 1 < (int)shown->upper - (int)shown->page_size) {
9205 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
9206 y + height - (int)shown->page_size - 1);
9208 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
9209 (int)shown->upper - (int)shown->page_size - 1);
9216 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
9217 compose->focused_editable = widget;
9220 if (GTK_IS_TEXT_VIEW(widget)
9221 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
9222 gtk_widget_ref(compose->notebook);
9223 gtk_widget_ref(compose->edit_vbox);
9224 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
9225 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
9226 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
9227 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
9228 gtk_widget_unref(compose->notebook);
9229 gtk_widget_unref(compose->edit_vbox);
9230 g_signal_handlers_block_by_func(G_OBJECT(widget),
9231 G_CALLBACK(compose_grab_focus_cb),
9233 gtk_widget_grab_focus(widget);
9234 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
9235 G_CALLBACK(compose_grab_focus_cb),
9237 } else if (!GTK_IS_TEXT_VIEW(widget)
9238 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
9239 gtk_widget_ref(compose->notebook);
9240 gtk_widget_ref(compose->edit_vbox);
9241 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
9242 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
9243 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
9244 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
9245 gtk_widget_unref(compose->notebook);
9246 gtk_widget_unref(compose->edit_vbox);
9247 g_signal_handlers_block_by_func(G_OBJECT(widget),
9248 G_CALLBACK(compose_grab_focus_cb),
9250 gtk_widget_grab_focus(widget);
9251 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
9252 G_CALLBACK(compose_grab_focus_cb),
9258 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
9260 compose->modified = TRUE;
9262 compose_set_title(compose);
9266 static void compose_wrap_cb(gpointer data, guint action, GtkWidget *widget)
9268 Compose *compose = (Compose *)data;
9271 compose_wrap_all_full(compose, TRUE);
9273 compose_beautify_paragraph(compose, NULL, TRUE);
9276 static void compose_find_cb(gpointer data, guint action, GtkWidget *widget)
9278 Compose *compose = (Compose *)data;
9280 message_search_compose(compose);
9283 static void compose_toggle_autowrap_cb(gpointer data, guint action,
9286 Compose *compose = (Compose *)data;
9287 compose->autowrap = GTK_CHECK_MENU_ITEM(widget)->active;
9288 if (compose->autowrap)
9289 compose_wrap_all_full(compose, TRUE);
9290 compose->autowrap = GTK_CHECK_MENU_ITEM(widget)->active;
9293 static void compose_toggle_sign_cb(gpointer data, guint action,
9296 Compose *compose = (Compose *)data;
9298 if (GTK_CHECK_MENU_ITEM(widget)->active)
9299 compose->use_signing = TRUE;
9301 compose->use_signing = FALSE;
9304 static void compose_toggle_encrypt_cb(gpointer data, guint action,
9307 Compose *compose = (Compose *)data;
9309 if (GTK_CHECK_MENU_ITEM(widget)->active)
9310 compose->use_encryption = TRUE;
9312 compose->use_encryption = FALSE;
9315 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
9317 g_free(compose->privacy_system);
9319 compose->privacy_system = g_strdup(account->default_privacy_system);
9320 compose_update_privacy_system_menu_item(compose, warn);
9323 static void compose_toggle_ruler_cb(gpointer data, guint action,
9326 Compose *compose = (Compose *)data;
9328 if (GTK_CHECK_MENU_ITEM(widget)->active) {
9329 gtk_widget_show(compose->ruler_hbox);
9330 prefs_common.show_ruler = TRUE;
9332 gtk_widget_hide(compose->ruler_hbox);
9333 gtk_widget_queue_resize(compose->edit_vbox);
9334 prefs_common.show_ruler = FALSE;
9338 static void compose_attach_drag_received_cb (GtkWidget *widget,
9339 GdkDragContext *context,
9342 GtkSelectionData *data,
9347 Compose *compose = (Compose *)user_data;
9350 if (gdk_atom_name(data->type) &&
9351 !strcmp(gdk_atom_name(data->type), "text/uri-list")
9352 && gtk_drag_get_source_widget(context) !=
9353 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
9354 list = uri_list_extract_filenames((const gchar *)data->data);
9355 for (tmp = list; tmp != NULL; tmp = tmp->next) {
9356 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
9357 compose_attach_append
9358 (compose, (const gchar *)tmp->data,
9359 utf8_filename, NULL);
9360 g_free(utf8_filename);
9362 if (list) compose_changed_cb(NULL, compose);
9363 list_free_strings(list);
9365 } else if (gtk_drag_get_source_widget(context)
9366 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
9367 /* comes from our summaryview */
9368 SummaryView * summaryview = NULL;
9369 GSList * list = NULL, *cur = NULL;
9371 if (mainwindow_get_mainwindow())
9372 summaryview = mainwindow_get_mainwindow()->summaryview;
9375 list = summary_get_selected_msg_list(summaryview);
9377 for (cur = list; cur; cur = cur->next) {
9378 MsgInfo *msginfo = (MsgInfo *)cur->data;
9381 file = procmsg_get_message_file_full(msginfo,
9384 compose_attach_append(compose, (const gchar *)file,
9385 (const gchar *)file, "message/rfc822");
9393 static gboolean compose_drag_drop(GtkWidget *widget,
9394 GdkDragContext *drag_context,
9396 guint time, gpointer user_data)
9398 /* not handling this signal makes compose_insert_drag_received_cb
9403 static void compose_insert_drag_received_cb (GtkWidget *widget,
9404 GdkDragContext *drag_context,
9407 GtkSelectionData *data,
9412 Compose *compose = (Compose *)user_data;
9415 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
9417 if (gdk_atom_name(data->type) && !strcmp(gdk_atom_name(data->type), "text/uri-list")) {
9418 AlertValue val = G_ALERTDEFAULT;
9420 switch (prefs_common.compose_dnd_mode) {
9421 case COMPOSE_DND_ASK:
9422 val = alertpanel_full(_("Insert or attach?"),
9423 _("Do you want to insert the contents of the file(s) "
9424 "into the message body, or attach it to the email?"),
9425 GTK_STOCK_CANCEL, _("+_Insert"), _("_Attach"),
9426 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
9428 case COMPOSE_DND_INSERT:
9429 val = G_ALERTALTERNATE;
9431 case COMPOSE_DND_ATTACH:
9435 /* unexpected case */
9436 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
9439 if (val & G_ALERTDISABLE) {
9440 val &= ~G_ALERTDISABLE;
9441 /* remember what action to perform by default, only if we don't click Cancel */
9442 if (val == G_ALERTALTERNATE)
9443 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
9444 else if (val == G_ALERTOTHER)
9445 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
9448 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
9449 gtk_drag_finish(drag_context, FALSE, FALSE, time);
9451 } else if (val == G_ALERTOTHER) {
9452 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
9455 list = uri_list_extract_filenames((const gchar *)data->data);
9456 for (tmp = list; tmp != NULL; tmp = tmp->next) {
9457 compose_insert_file(compose, (const gchar *)tmp->data);
9459 list_free_strings(list);
9461 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9464 #if GTK_CHECK_VERSION(2, 8, 0)
9465 /* do nothing, handled by GTK */
9467 gchar *tmpfile = get_tmp_file();
9468 str_write_to_file((const gchar *)data->data, tmpfile);
9469 compose_insert_file(compose, tmpfile);
9472 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9476 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9479 static void compose_header_drag_received_cb (GtkWidget *widget,
9480 GdkDragContext *drag_context,
9483 GtkSelectionData *data,
9488 GtkEditable *entry = (GtkEditable *)user_data;
9489 gchar *email = (gchar *)data->data;
9491 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
9494 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
9495 gchar *decoded=g_new(gchar, strlen(email));
9498 email += strlen("mailto:");
9499 decode_uri(decoded, email); /* will fit */
9500 gtk_editable_delete_text(entry, 0, -1);
9501 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
9502 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9506 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9509 static void compose_toggle_return_receipt_cb(gpointer data, guint action,
9512 Compose *compose = (Compose *)data;
9514 if (GTK_CHECK_MENU_ITEM(widget)->active)
9515 compose->return_receipt = TRUE;
9517 compose->return_receipt = FALSE;
9520 static void compose_toggle_remove_refs_cb(gpointer data, guint action,
9523 Compose *compose = (Compose *)data;
9525 if (GTK_CHECK_MENU_ITEM(widget)->active)
9526 compose->remove_references = TRUE;
9528 compose->remove_references = FALSE;
9531 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
9533 ComposeHeaderEntry *headerentry)
9535 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
9536 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
9537 !(event->state & GDK_MODIFIER_MASK) &&
9538 (event->keyval == GDK_BackSpace) &&
9539 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
9540 gtk_container_remove
9541 (GTK_CONTAINER(headerentry->compose->header_table),
9542 headerentry->combo);
9543 gtk_container_remove
9544 (GTK_CONTAINER(headerentry->compose->header_table),
9545 headerentry->entry);
9546 headerentry->compose->header_list =
9547 g_slist_remove(headerentry->compose->header_list,
9549 g_free(headerentry);
9550 } else if (event->keyval == GDK_Tab) {
9551 if (headerentry->compose->header_last == headerentry) {
9552 /* Override default next focus, and give it to subject_entry
9553 * instead of notebook tabs
9555 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
9556 gtk_widget_grab_focus(headerentry->compose->subject_entry);
9563 static gboolean compose_headerentry_changed_cb(GtkWidget *entry,
9564 ComposeHeaderEntry *headerentry)
9566 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
9567 compose_create_header_entry(headerentry->compose);
9568 g_signal_handlers_disconnect_matched
9569 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
9570 0, 0, NULL, NULL, headerentry);
9572 /* Automatically scroll down */
9573 compose_show_first_last_header(headerentry->compose, FALSE);
9579 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
9581 GtkAdjustment *vadj;
9583 g_return_if_fail(compose);
9584 g_return_if_fail(GTK_IS_WIDGET(compose->header_table));
9585 g_return_if_fail(GTK_IS_VIEWPORT(compose->header_table->parent));
9587 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(compose->header_table->parent));
9588 gtk_adjustment_set_value(vadj, (show_first ? vadj->lower : vadj->upper));
9591 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
9592 const gchar *text, gint len, Compose *compose)
9594 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
9595 (G_OBJECT(compose->text), "paste_as_quotation"));
9598 g_return_if_fail(text != NULL);
9600 g_signal_handlers_block_by_func(G_OBJECT(buffer),
9601 G_CALLBACK(text_inserted),
9603 if (paste_as_quotation) {
9610 new_text = g_strndup(text, len);
9612 qmark = compose_quote_char_from_context(compose);
9614 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
9615 gtk_text_buffer_place_cursor(buffer, iter);
9617 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
9618 _("Quote format error at line %d."));
9619 quote_fmt_reset_vartable();
9621 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
9622 GINT_TO_POINTER(paste_as_quotation - 1));
9624 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
9625 gtk_text_buffer_place_cursor(buffer, iter);
9627 if (strcmp(text, "\n") || automatic_break
9628 || gtk_text_iter_starts_line(iter))
9629 gtk_text_buffer_insert(buffer, iter, text, len);
9631 debug_print("insert nowrap \\n\n");
9632 gtk_text_buffer_insert_with_tags_by_name(buffer,
9633 iter, text, len, "no_join", NULL);
9637 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
9639 compose_beautify_paragraph(compose, iter, FALSE);
9641 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
9642 gtk_text_buffer_delete_mark(buffer, mark);
9644 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
9645 G_CALLBACK(text_inserted),
9647 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
9649 if (prefs_common.autosave &&
9650 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
9651 compose->draft_timeout_tag != -2 /* disabled while loading */)
9652 compose->draft_timeout_tag = g_timeout_add
9653 (500, (GtkFunction) compose_defer_auto_save_draft, compose);
9655 static gint compose_defer_auto_save_draft(Compose *compose)
9657 compose->draft_timeout_tag = -1;
9658 compose_draft_cb((gpointer)compose, COMPOSE_AUTO_SAVE, NULL);
9663 static void compose_check_all(Compose *compose)
9665 if (compose->gtkaspell)
9666 gtkaspell_check_all(compose->gtkaspell);
9669 static void compose_highlight_all(Compose *compose)
9671 if (compose->gtkaspell)
9672 gtkaspell_highlight_all(compose->gtkaspell);
9675 static void compose_check_backwards(Compose *compose)
9677 if (compose->gtkaspell)
9678 gtkaspell_check_backwards(compose->gtkaspell);
9680 GtkItemFactory *ifactory;
9681 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
9682 menu_set_sensitive(ifactory, "/Edit/Check backwards misspelled word", FALSE);
9683 menu_set_sensitive(ifactory, "/Edit/Forward to next misspelled word", FALSE);
9687 static void compose_check_forwards_go(Compose *compose)
9689 if (compose->gtkaspell)
9690 gtkaspell_check_forwards_go(compose->gtkaspell);
9692 GtkItemFactory *ifactory;
9693 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
9694 menu_set_sensitive(ifactory, "/Edit/Check backwards misspelled word", FALSE);
9695 menu_set_sensitive(ifactory, "/Edit/Forward to next misspelled word", FALSE);
9701 *\brief Guess originating forward account from MsgInfo and several
9702 * "common preference" settings. Return NULL if no guess.
9704 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
9706 PrefsAccount *account = NULL;
9708 g_return_val_if_fail(msginfo, NULL);
9709 g_return_val_if_fail(msginfo->folder, NULL);
9710 g_return_val_if_fail(msginfo->folder->prefs, NULL);
9712 if (msginfo->folder->prefs->enable_default_account)
9713 account = account_find_from_id(msginfo->folder->prefs->default_account);
9716 account = msginfo->folder->folder->account;
9718 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
9720 Xstrdup_a(to, msginfo->to, return NULL);
9721 extract_address(to);
9722 account = account_find_from_address(to);
9725 if (!account && prefs_common.forward_account_autosel) {
9727 if (!procheader_get_header_from_msginfo
9728 (msginfo, cc,sizeof cc , "Cc:")) {
9729 gchar *buf = cc + strlen("Cc:");
9730 extract_address(buf);
9731 account = account_find_from_address(buf);
9735 if (!account && prefs_common.forward_account_autosel) {
9736 gchar deliveredto[BUFFSIZE];
9737 if (!procheader_get_header_from_msginfo
9738 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
9739 gchar *buf = deliveredto + strlen("Delivered-To:");
9740 extract_address(buf);
9741 account = account_find_from_address(buf);
9748 gboolean compose_close(Compose *compose)
9752 if (!g_mutex_trylock(compose->mutex)) {
9753 /* we have to wait for the (possibly deferred by auto-save)
9754 * drafting to be done, before destroying the compose under
9756 debug_print("waiting for drafting to finish...\n");
9757 compose_allow_user_actions(compose, FALSE);
9758 g_timeout_add (500, (GSourceFunc) compose_close, compose);
9761 g_return_val_if_fail(compose, FALSE);
9762 gtkut_widget_get_uposition(compose->window, &x, &y);
9763 prefs_common.compose_x = x;
9764 prefs_common.compose_y = y;
9765 g_mutex_unlock(compose->mutex);
9766 compose_destroy(compose);
9771 * Add entry field for each address in list.
9772 * \param compose E-Mail composition object.
9773 * \param listAddress List of (formatted) E-Mail addresses.
9775 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
9780 addr = ( gchar * ) node->data;
9781 compose_entry_append( compose, addr, COMPOSE_TO );
9782 node = g_list_next( node );
9786 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
9787 guint action, gboolean opening_multiple)
9790 GSList *new_msglist = NULL;
9791 MsgInfo *tmp_msginfo = NULL;
9792 gboolean originally_enc = FALSE;
9793 Compose *compose = NULL;
9795 g_return_if_fail(msgview != NULL);
9797 g_return_if_fail(msginfo_list != NULL);
9799 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
9800 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
9801 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
9803 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
9804 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
9805 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
9806 orig_msginfo, mimeinfo);
9807 if (tmp_msginfo != NULL) {
9808 new_msglist = g_slist_append(NULL, tmp_msginfo);
9809 if (procmime_msginfo_is_encrypted(orig_msginfo)) {
9810 originally_enc = TRUE;
9812 tmp_msginfo->folder = orig_msginfo->folder;
9813 tmp_msginfo->msgnum = orig_msginfo->msgnum;
9814 if (orig_msginfo->tags)
9815 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
9820 if (!opening_multiple)
9821 body = messageview_get_selection(msgview);
9824 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
9825 procmsg_msginfo_free(tmp_msginfo);
9826 g_slist_free(new_msglist);
9828 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
9830 if (originally_enc) {
9831 compose_force_encryption(compose, compose->account, FALSE);
9837 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
9840 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
9841 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
9842 GSList *cur = msginfo_list;
9843 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
9844 "messages. Opening the windows "
9845 "could take some time. Do you "
9846 "want to continue?"),
9847 g_slist_length(msginfo_list));
9848 if (g_slist_length(msginfo_list) > 9
9849 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
9850 != G_ALERTALTERNATE) {
9855 /* We'll open multiple compose windows */
9856 /* let the WM place the next windows */
9857 compose_force_window_origin = FALSE;
9858 for (; cur; cur = cur->next) {
9860 tmplist.data = cur->data;
9861 tmplist.next = NULL;
9862 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
9864 compose_force_window_origin = TRUE;
9866 /* forwarding multiple mails as attachments is done via a
9867 * single compose window */
9868 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
9872 void compose_set_position(Compose *compose, gint pos)
9874 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9876 gtkut_text_view_set_position(text, pos);
9879 gboolean compose_search_string(Compose *compose,
9880 const gchar *str, gboolean case_sens)
9882 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9884 return gtkut_text_view_search_string(text, str, case_sens);
9887 gboolean compose_search_string_backward(Compose *compose,
9888 const gchar *str, gboolean case_sens)
9890 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9892 return gtkut_text_view_search_string_backward(text, str, case_sens);
9895 /* allocate a msginfo structure and populate its data from a compose data structure */
9896 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
9898 MsgInfo *newmsginfo;
9900 gchar buf[BUFFSIZE];
9902 g_return_val_if_fail( compose != NULL, NULL );
9904 newmsginfo = procmsg_msginfo_new();
9907 get_rfc822_date(buf, sizeof(buf));
9908 newmsginfo->date = g_strdup(buf);
9911 if (compose->from_name) {
9912 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
9913 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
9917 if (compose->subject_entry)
9918 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
9920 /* to, cc, reply-to, newsgroups */
9921 for (list = compose->header_list; list; list = list->next) {
9922 gchar *header = gtk_editable_get_chars(
9924 GTK_COMBO(((ComposeHeaderEntry *)list->data)->combo)->entry), 0, -1);
9925 gchar *entry = gtk_editable_get_chars(
9926 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
9928 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
9929 if ( newmsginfo->to == NULL ) {
9930 newmsginfo->to = g_strdup(entry);
9931 } else if (entry && *entry) {
9932 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
9933 g_free(newmsginfo->to);
9934 newmsginfo->to = tmp;
9937 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
9938 if ( newmsginfo->cc == NULL ) {
9939 newmsginfo->cc = g_strdup(entry);
9940 } else if (entry && *entry) {
9941 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
9942 g_free(newmsginfo->cc);
9943 newmsginfo->cc = tmp;
9946 if ( strcasecmp(header,
9947 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
9948 if ( newmsginfo->newsgroups == NULL ) {
9949 newmsginfo->newsgroups = g_strdup(entry);
9950 } else if (entry && *entry) {
9951 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
9952 g_free(newmsginfo->newsgroups);
9953 newmsginfo->newsgroups = tmp;
9961 /* other data is unset */
9967 /* update compose's dictionaries from folder dict settings */
9968 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
9969 FolderItem *folder_item)
9971 g_return_if_fail(compose != NULL);
9973 if (compose->gtkaspell && folder_item && folder_item->prefs) {
9974 FolderItemPrefs *prefs = folder_item->prefs;
9976 if (prefs->enable_default_dictionary)
9977 gtkaspell_change_dict(compose->gtkaspell,
9978 prefs->default_dictionary, FALSE);
9979 if (folder_item->prefs->enable_default_alt_dictionary)
9980 gtkaspell_change_alt_dict(compose->gtkaspell,
9981 prefs->default_alt_dictionary);
9982 if (prefs->enable_default_dictionary
9983 || prefs->enable_default_alt_dictionary)
9984 compose_spell_menu_changed(compose);