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"
138 #define N_ATTACH_COLS (N_COL_COLUMNS)
142 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE,
143 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER,
144 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER,
145 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD,
146 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD,
147 COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE,
148 COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE,
149 COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE,
150 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER,
151 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER,
152 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD,
153 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD,
154 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE,
155 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE_N,
156 COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END
157 } ComposeCallAdvancedAction;
161 PRIORITY_HIGHEST = 1,
170 COMPOSE_INSERT_SUCCESS,
171 COMPOSE_INSERT_READ_ERROR,
172 COMPOSE_INSERT_INVALID_CHARACTER,
173 COMPOSE_INSERT_NO_FILE
174 } ComposeInsertResult;
178 COMPOSE_WRITE_FOR_SEND,
179 COMPOSE_WRITE_FOR_STORE
184 COMPOSE_QUOTE_FORCED,
189 #define B64_LINE_SIZE 57
190 #define B64_BUFFSIZE 77
192 #define MAX_REFERENCES_LEN 999
194 static GList *compose_list = NULL;
196 static Compose *compose_generic_new (PrefsAccount *account,
199 GPtrArray *attach_files,
200 GList *listAddress );
202 static Compose *compose_create (PrefsAccount *account,
207 static void compose_entry_mark_default_to (Compose *compose,
208 const gchar *address);
209 static Compose *compose_followup_and_reply_to (MsgInfo *msginfo,
210 ComposeQuoteMode quote_mode,
214 static Compose *compose_forward_multiple (PrefsAccount *account,
215 GSList *msginfo_list);
216 static Compose *compose_reply (MsgInfo *msginfo,
217 ComposeQuoteMode quote_mode,
222 static Compose *compose_reply_mode (ComposeMode mode,
223 GSList *msginfo_list,
225 static void compose_template_apply_fields(Compose *compose, Template *tmpl);
226 static void compose_update_privacy_systems_menu(Compose *compose);
228 static GtkWidget *compose_account_option_menu_create
230 static void compose_set_out_encoding (Compose *compose);
231 static void compose_set_template_menu (Compose *compose);
232 static void compose_template_apply (Compose *compose,
235 static void compose_destroy (Compose *compose);
237 static void compose_entries_set (Compose *compose,
238 const gchar *mailto);
239 static gint compose_parse_header (Compose *compose,
241 static gchar *compose_parse_references (const gchar *ref,
244 static gchar *compose_quote_fmt (Compose *compose,
250 gboolean need_unescape,
251 const gchar *err_msg);
253 static void compose_reply_set_entry (Compose *compose,
259 followup_and_reply_to);
260 static void compose_reedit_set_entry (Compose *compose,
263 static void compose_insert_sig (Compose *compose,
265 static gchar *compose_get_signature_str (Compose *compose);
266 static ComposeInsertResult compose_insert_file (Compose *compose,
269 static gboolean compose_attach_append (Compose *compose,
272 const gchar *content_type);
273 static void compose_attach_parts (Compose *compose,
276 static void compose_beautify_paragraph (Compose *compose,
277 GtkTextIter *par_iter,
279 static void compose_wrap_all (Compose *compose);
280 static void compose_wrap_all_full (Compose *compose,
283 static void compose_set_title (Compose *compose);
284 static void compose_select_account (Compose *compose,
285 PrefsAccount *account,
288 static PrefsAccount *compose_current_mail_account(void);
289 /* static gint compose_send (Compose *compose); */
290 static gboolean compose_check_for_valid_recipient
292 static gboolean compose_check_entries (Compose *compose,
293 gboolean check_everything);
294 static gint compose_write_to_file (Compose *compose,
297 gboolean attach_parts);
298 static gint compose_write_body_to_file (Compose *compose,
300 static gint compose_remove_reedit_target (Compose *compose,
302 static void compose_remove_draft (Compose *compose);
303 static gint compose_queue_sub (Compose *compose,
307 gboolean check_subject,
308 gboolean remove_reedit_target);
309 static void compose_add_attachments (Compose *compose,
311 static gchar *compose_get_header (Compose *compose);
313 static void compose_convert_header (Compose *compose,
318 gboolean addr_field);
320 static void compose_attach_info_free (AttachInfo *ainfo);
321 static void compose_attach_remove_selected (Compose *compose);
323 static void compose_attach_property (Compose *compose);
324 static void compose_attach_property_create (gboolean *cancelled);
325 static void attach_property_ok (GtkWidget *widget,
326 gboolean *cancelled);
327 static void attach_property_cancel (GtkWidget *widget,
328 gboolean *cancelled);
329 static gint attach_property_delete_event (GtkWidget *widget,
331 gboolean *cancelled);
332 static gboolean attach_property_key_pressed (GtkWidget *widget,
334 gboolean *cancelled);
336 static void compose_exec_ext_editor (Compose *compose);
338 static gint compose_exec_ext_editor_real (const gchar *file);
339 static gboolean compose_ext_editor_kill (Compose *compose);
340 static gboolean compose_input_cb (GIOChannel *source,
341 GIOCondition condition,
343 static void compose_set_ext_editor_sensitive (Compose *compose,
345 #endif /* G_OS_UNIX */
347 static void compose_undo_state_changed (UndoMain *undostruct,
352 static void compose_create_header_entry (Compose *compose);
353 static void compose_add_header_entry (Compose *compose, const gchar *header, gchar *text);
354 static void compose_remove_header_entries(Compose *compose);
356 static void compose_update_priority_menu_item(Compose * compose);
358 static void compose_spell_menu_changed (void *data);
360 static void compose_add_field_list ( Compose *compose,
361 GList *listAddress );
363 /* callback functions */
365 static gboolean compose_edit_size_alloc (GtkEditable *widget,
366 GtkAllocation *allocation,
367 GtkSHRuler *shruler);
368 static void account_activated (GtkComboBox *optmenu,
370 static void attach_selected (GtkTreeView *tree_view,
371 GtkTreePath *tree_path,
372 GtkTreeViewColumn *column,
374 static gboolean attach_button_pressed (GtkWidget *widget,
375 GdkEventButton *event,
377 static gboolean attach_key_pressed (GtkWidget *widget,
380 static void compose_send_cb (gpointer data,
383 static void compose_send_later_cb (gpointer data,
387 static void compose_draft_cb (gpointer data,
391 static void compose_attach_cb (gpointer data,
394 static void compose_insert_file_cb (gpointer data,
397 static void compose_insert_sig_cb (gpointer data,
401 static void compose_close_cb (gpointer data,
405 static void compose_set_encoding_cb (gpointer data,
409 static void compose_address_cb (gpointer data,
412 static void compose_template_activate_cb(GtkWidget *widget,
415 static void compose_ext_editor_cb (gpointer data,
419 static gint compose_delete_cb (GtkWidget *widget,
423 static void compose_undo_cb (Compose *compose);
424 static void compose_redo_cb (Compose *compose);
425 static void compose_cut_cb (Compose *compose);
426 static void compose_copy_cb (Compose *compose);
427 static void compose_paste_cb (Compose *compose);
428 static void compose_paste_as_quote_cb (Compose *compose);
429 static void compose_paste_no_wrap_cb (Compose *compose);
430 static void compose_paste_wrap_cb (Compose *compose);
431 static void compose_allsel_cb (Compose *compose);
433 static void compose_advanced_action_cb (Compose *compose,
434 ComposeCallAdvancedAction action);
436 static void compose_grab_focus_cb (GtkWidget *widget,
439 static void compose_changed_cb (GtkTextBuffer *textbuf,
442 static void compose_wrap_cb (gpointer data,
445 static void compose_find_cb (gpointer data,
448 static void compose_toggle_autowrap_cb (gpointer data,
452 static void compose_toggle_ruler_cb (gpointer data,
455 static void compose_toggle_sign_cb (gpointer data,
458 static void compose_toggle_encrypt_cb (gpointer data,
461 static void compose_set_privacy_system_cb(GtkWidget *widget,
463 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn);
464 static void activate_privacy_system (Compose *compose,
465 PrefsAccount *account,
467 static void compose_use_signing(Compose *compose, gboolean use_signing);
468 static void compose_use_encryption(Compose *compose, gboolean use_encryption);
469 static void compose_toggle_return_receipt_cb(gpointer data, guint action,
471 static void compose_toggle_remove_refs_cb(gpointer data, guint action,
473 static void compose_set_priority_cb (gpointer data,
476 static void compose_reply_change_mode (gpointer data,
480 static void compose_attach_drag_received_cb (GtkWidget *widget,
481 GdkDragContext *drag_context,
484 GtkSelectionData *data,
488 static void compose_insert_drag_received_cb (GtkWidget *widget,
489 GdkDragContext *drag_context,
492 GtkSelectionData *data,
496 static void compose_header_drag_received_cb (GtkWidget *widget,
497 GdkDragContext *drag_context,
500 GtkSelectionData *data,
505 static gboolean compose_drag_drop (GtkWidget *widget,
506 GdkDragContext *drag_context,
508 guint time, gpointer user_data);
510 static void text_inserted (GtkTextBuffer *buffer,
515 static Compose *compose_generic_reply(MsgInfo *msginfo,
516 ComposeQuoteMode quote_mode,
520 gboolean followup_and_reply_to,
523 static gboolean compose_headerentry_changed_cb (GtkWidget *entry,
524 ComposeHeaderEntry *headerentry);
525 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
527 ComposeHeaderEntry *headerentry);
529 static void compose_show_first_last_header (Compose *compose, gboolean show_first);
531 static void compose_allow_user_actions (Compose *compose, gboolean allow);
534 static void compose_check_all (Compose *compose);
535 static void compose_highlight_all (Compose *compose);
536 static void compose_check_backwards (Compose *compose);
537 static void compose_check_forwards_go (Compose *compose);
540 static gint compose_defer_auto_save_draft (Compose *compose);
541 static PrefsAccount *compose_guess_forward_account_from_msginfo (MsgInfo *msginfo);
543 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose);
546 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
547 FolderItem *folder_item);
550 static GtkItemFactoryEntry compose_popup_entries[] =
552 {N_("/_Add..."), NULL, compose_attach_cb, 0, NULL},
553 {N_("/_Remove"), NULL, compose_attach_remove_selected, 0, NULL},
554 {"/---", NULL, NULL, 0, "<Separator>"},
555 {N_("/_Properties..."), NULL, compose_attach_property, 0, NULL}
558 static GtkItemFactoryEntry compose_entries[] =
560 {N_("/_Message"), NULL, NULL, 0, "<Branch>"},
561 {N_("/_Message/S_end"), "<control>Return",
562 compose_send_cb, 0, NULL},
563 {N_("/_Message/Send _later"), "<shift><control>S",
564 compose_send_later_cb, 0, NULL},
565 {N_("/_Message/---"), NULL, NULL, 0, "<Separator>"},
566 {N_("/_Message/_Attach file"), "<control>M", compose_attach_cb, 0, NULL},
567 {N_("/_Message/_Insert file"), "<control>I", compose_insert_file_cb, 0, NULL},
568 {N_("/_Message/Insert si_gnature"), "<control>G", compose_insert_sig_cb, 0, NULL},
569 {N_("/_Message/---"), NULL, NULL, 0, "<Separator>"},
570 {N_("/_Message/_Save"),
571 "<control>S", compose_draft_cb, COMPOSE_KEEP_EDITING, NULL},
572 {N_("/_Message/---"), NULL, NULL, 0, "<Separator>"},
573 {N_("/_Message/_Close"), "<control>W", compose_close_cb, 0, NULL},
575 {N_("/_Edit"), NULL, NULL, 0, "<Branch>"},
576 {N_("/_Edit/_Undo"), "<control>Z", compose_undo_cb, 0, NULL},
577 {N_("/_Edit/_Redo"), "<control>Y", compose_redo_cb, 0, NULL},
578 {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
579 {N_("/_Edit/Cu_t"), "<control>X", compose_cut_cb, 0, NULL},
580 {N_("/_Edit/_Copy"), "<control>C", compose_copy_cb, 0, NULL},
581 {N_("/_Edit/_Paste"), "<control>V", compose_paste_cb, 0, NULL},
582 {N_("/_Edit/Special paste"), NULL, NULL, 0, "<Branch>"},
583 {N_("/_Edit/Special paste/as _quotation"),
584 NULL, compose_paste_as_quote_cb, 0, NULL},
585 {N_("/_Edit/Special paste/_wrapped"),
586 NULL, compose_paste_wrap_cb, 0, NULL},
587 {N_("/_Edit/Special paste/_unwrapped"),
588 NULL, compose_paste_no_wrap_cb, 0, NULL},
589 {N_("/_Edit/Select _all"), "<control>A", compose_allsel_cb, 0, NULL},
590 {N_("/_Edit/A_dvanced"), NULL, NULL, 0, "<Branch>"},
591 {N_("/_Edit/A_dvanced/Move a character backward"),
593 compose_advanced_action_cb,
594 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER,
596 {N_("/_Edit/A_dvanced/Move a character forward"),
598 compose_advanced_action_cb,
599 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER,
601 {N_("/_Edit/A_dvanced/Move a word backward"),
603 compose_advanced_action_cb,
604 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD,
606 {N_("/_Edit/A_dvanced/Move a word forward"),
608 compose_advanced_action_cb,
609 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD,
611 {N_("/_Edit/A_dvanced/Move to beginning of line"),
612 NULL, /* "<control>A" */
613 compose_advanced_action_cb,
614 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE,
616 {N_("/_Edit/A_dvanced/Move to end of line"),
618 compose_advanced_action_cb,
619 COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE,
621 {N_("/_Edit/A_dvanced/Move to previous line"),
623 compose_advanced_action_cb,
624 COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE,
626 {N_("/_Edit/A_dvanced/Move to next line"),
628 compose_advanced_action_cb,
629 COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE,
631 {N_("/_Edit/A_dvanced/Delete a character backward"),
633 compose_advanced_action_cb,
634 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER,
636 {N_("/_Edit/A_dvanced/Delete a character forward"),
638 compose_advanced_action_cb,
639 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER,
641 {N_("/_Edit/A_dvanced/Delete a word backward"),
642 NULL, /* "<control>W" */
643 compose_advanced_action_cb,
644 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD,
646 {N_("/_Edit/A_dvanced/Delete a word forward"),
647 NULL, /* "<alt>D", */
648 compose_advanced_action_cb,
649 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD,
651 {N_("/_Edit/A_dvanced/Delete line"),
653 compose_advanced_action_cb,
654 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE,
656 {N_("/_Edit/A_dvanced/Delete entire line"),
658 compose_advanced_action_cb,
659 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE_N,
661 {N_("/_Edit/A_dvanced/Delete to end of line"),
663 compose_advanced_action_cb,
664 COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END,
666 {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
668 "<control>F", compose_find_cb, 0, NULL},
669 {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
670 {N_("/_Edit/_Wrap current paragraph"),
671 "<control>L", compose_wrap_cb, 0, NULL},
672 {N_("/_Edit/Wrap all long _lines"),
673 "<control><alt>L", compose_wrap_cb, 1, NULL},
674 {N_("/_Edit/Aut_o wrapping"), "<shift><control>L", compose_toggle_autowrap_cb, 0, "<ToggleItem>"},
675 {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
676 {N_("/_Edit/Edit with e_xternal editor"),
677 "<shift><control>X", compose_ext_editor_cb, 0, NULL},
679 {N_("/_Spelling"), NULL, NULL, 0, "<Branch>"},
680 {N_("/_Spelling/_Check all or check selection"),
681 NULL, compose_check_all, 0, NULL},
682 {N_("/_Spelling/_Highlight all misspelled words"),
683 NULL, compose_highlight_all, 0, NULL},
684 {N_("/_Spelling/Check _backwards misspelled word"),
685 NULL, compose_check_backwards , 0, NULL},
686 {N_("/_Spelling/_Forward to next misspelled word"),
687 NULL, compose_check_forwards_go, 0, NULL},
688 {N_("/_Spelling/---"), NULL, NULL, 0, "<Separator>"},
689 {N_("/_Spelling/Options"),
690 NULL, NULL, 0, "<Branch>"},
692 {N_("/_Options"), NULL, NULL, 0, "<Branch>"},
693 {N_("/_Options/Reply _mode"), NULL, NULL, 0, "<Branch>"},
694 {N_("/_Options/Reply _mode/_Normal"), NULL, compose_reply_change_mode, COMPOSE_REPLY, "<RadioItem>"},
695 {N_("/_Options/Reply _mode/_All"), NULL, compose_reply_change_mode, COMPOSE_REPLY_TO_ALL, "/Options/Reply mode/Normal"},
696 {N_("/_Options/Reply _mode/_Sender"), NULL, compose_reply_change_mode, COMPOSE_REPLY_TO_SENDER, "/Options/Reply mode/Normal"},
697 {N_("/_Options/Reply _mode/_Mailing-list"), NULL, compose_reply_change_mode, COMPOSE_REPLY_TO_LIST, "/Options/Reply mode/Normal"},
698 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
699 {N_("/_Options/Privacy _System"), NULL, NULL, 0, "<Branch>"},
700 {N_("/_Options/Privacy _System/None"), NULL, NULL, 0, "<RadioItem>"},
701 {N_("/_Options/Si_gn"), NULL, compose_toggle_sign_cb , 0, "<ToggleItem>"},
702 {N_("/_Options/_Encrypt"), NULL, compose_toggle_encrypt_cb, 0, "<ToggleItem>"},
703 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
704 {N_("/_Options/_Priority"), NULL, NULL, 0, "<Branch>"},
705 {N_("/_Options/Priority/_Highest"), NULL, compose_set_priority_cb, PRIORITY_HIGHEST, "<RadioItem>"},
706 {N_("/_Options/Priority/Hi_gh"), NULL, compose_set_priority_cb, PRIORITY_HIGH, "/Options/Priority/Highest"},
707 {N_("/_Options/Priority/_Normal"), NULL, compose_set_priority_cb, PRIORITY_NORMAL, "/Options/Priority/Highest"},
708 {N_("/_Options/Priority/Lo_w"), NULL, compose_set_priority_cb, PRIORITY_LOW, "/Options/Priority/Highest"},
709 {N_("/_Options/Priority/_Lowest"), NULL, compose_set_priority_cb, PRIORITY_LOWEST, "/Options/Priority/Highest"},
710 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
711 {N_("/_Options/_Request Return Receipt"), NULL, compose_toggle_return_receipt_cb, 0, "<ToggleItem>"},
712 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
713 {N_("/_Options/Remo_ve references"), NULL, compose_toggle_remove_refs_cb, 0, "<ToggleItem>"},
714 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
716 #define ENC_ACTION(action) \
717 NULL, compose_set_encoding_cb, action, \
718 "/Options/Character encoding/Automatic"
720 {N_("/_Options/Character _encoding"), NULL, NULL, 0, "<Branch>"},
721 {N_("/_Options/Character _encoding/_Automatic"),
722 NULL, compose_set_encoding_cb, C_AUTO, "<RadioItem>"},
723 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
725 {N_("/_Options/Character _encoding/7bit ascii (US-ASC_II)"),
726 ENC_ACTION(C_US_ASCII)},
727 {N_("/_Options/Character _encoding/Unicode (_UTF-8)"),
728 ENC_ACTION(C_UTF_8)},
729 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
731 {N_("/_Options/Character _encoding/Western European (ISO-8859-_1)"),
732 ENC_ACTION(C_ISO_8859_1)},
733 {N_("/_Options/Character _encoding/Western European (ISO-8859-15)"),
734 ENC_ACTION(C_ISO_8859_15)},
735 {N_("/_Options/Character _encoding/Western European (Windows-1252)"),
736 ENC_ACTION(C_WINDOWS_1252)},
737 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
739 {N_("/_Options/Character _encoding/Central European (ISO-8859-_2)"),
740 ENC_ACTION(C_ISO_8859_2)},
741 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
743 {N_("/_Options/Character _encoding/_Baltic (ISO-8859-13)"),
744 ENC_ACTION(C_ISO_8859_13)},
745 {N_("/_Options/Character _encoding/Baltic (ISO-8859-_4)"),
746 ENC_ACTION(C_ISO_8859_4)},
747 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
749 {N_("/_Options/Character _encoding/Greek (ISO-8859-_7)"),
750 ENC_ACTION(C_ISO_8859_7)},
751 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
753 {N_("/_Options/Character _encoding/Hebrew (ISO-8859-_8)"),
754 ENC_ACTION(C_ISO_8859_8)},
755 {N_("/_Options/Character _encoding/Hebrew (Windows-1255)"),
756 ENC_ACTION(C_WINDOWS_1255)},
757 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
759 {N_("/_Options/Character _encoding/Arabic (ISO-8859-_6)"),
760 ENC_ACTION(C_ISO_8859_6)},
761 {N_("/_Options/Character _encoding/Arabic (Windows-1256)"),
762 ENC_ACTION(C_CP1256)},
763 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
765 {N_("/_Options/Character _encoding/Turkish (ISO-8859-_9)"),
766 ENC_ACTION(C_ISO_8859_9)},
767 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
769 {N_("/_Options/Character _encoding/Cyrillic (ISO-8859-_5)"),
770 ENC_ACTION(C_ISO_8859_5)},
771 {N_("/_Options/Character _encoding/Cyrillic (KOI8-_R)"),
772 ENC_ACTION(C_KOI8_R)},
773 {N_("/_Options/Character _encoding/Cyrillic (KOI8-U)"),
774 ENC_ACTION(C_KOI8_U)},
775 {N_("/_Options/Character _encoding/Cyrillic (Windows-1251)"),
776 ENC_ACTION(C_WINDOWS_1251)},
777 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
779 {N_("/_Options/Character _encoding/Japanese (ISO-2022-_JP)"),
780 ENC_ACTION(C_ISO_2022_JP)},
781 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
783 {N_("/_Options/Character _encoding/Simplified Chinese (_GB2312)"),
784 ENC_ACTION(C_GB2312)},
785 {N_("/_Options/Character _encoding/Simplified Chinese (GBK)"),
787 {N_("/_Options/Character _encoding/Traditional Chinese (_Big5)"),
789 {N_("/_Options/Character _encoding/Traditional Chinese (EUC-_TW)"),
790 ENC_ACTION(C_EUC_TW)},
791 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
793 {N_("/_Options/Character _encoding/Korean (EUC-_KR)"),
794 ENC_ACTION(C_EUC_KR)},
795 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
797 {N_("/_Options/Character _encoding/Thai (TIS-620)"),
798 ENC_ACTION(C_TIS_620)},
799 {N_("/_Options/Character _encoding/Thai (Windows-874)"),
800 ENC_ACTION(C_WINDOWS_874)},
802 {N_("/_Tools"), NULL, NULL, 0, "<Branch>"},
803 {N_("/_Tools/Show _ruler"), NULL, compose_toggle_ruler_cb, 0, "<ToggleItem>"},
804 {N_("/_Tools/_Address book"), "<shift><control>A", compose_address_cb , 0, NULL},
805 {N_("/_Tools/_Template"), NULL, NULL, 0, "<Branch>"},
806 {N_("/_Tools/Actio_ns"), NULL, NULL, 0, "<Branch>"},
807 {N_("/_Help"), NULL, NULL, 0, "<Branch>"},
808 {N_("/_Help/_About"), NULL, about_show, 0, NULL}
811 static GtkTargetEntry compose_mime_types[] =
813 {"text/uri-list", 0, 0},
814 {"UTF8_STRING", 0, 0},
818 static gboolean compose_put_existing_to_front(MsgInfo *info)
820 GList *compose_list = compose_get_compose_list();
824 for (elem = compose_list; elem != NULL && elem->data != NULL;
826 Compose *c = (Compose*)elem->data;
828 if (!c->targetinfo || !c->targetinfo->msgid ||
832 if (!strcmp(c->targetinfo->msgid, info->msgid)) {
833 gtkut_window_popup(c->window);
841 static GdkColor quote_color1 =
842 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
843 static GdkColor quote_color2 =
844 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
845 static GdkColor quote_color3 =
846 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
848 static GdkColor quote_bgcolor1 =
849 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
850 static GdkColor quote_bgcolor2 =
851 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
852 static GdkColor quote_bgcolor3 =
853 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
855 static GdkColor signature_color = {
862 static GdkColor uri_color = {
869 static void compose_create_tags(GtkTextView *text, Compose *compose)
871 GtkTextBuffer *buffer;
872 GdkColor black = {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
878 buffer = gtk_text_view_get_buffer(text);
880 if (prefs_common.enable_color) {
881 /* grab the quote colors, converting from an int to a GdkColor */
882 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
884 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
886 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
888 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_bgcol,
890 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_bgcol,
892 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_bgcol,
894 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
896 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
899 signature_color = quote_color1 = quote_color2 = quote_color3 =
900 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
903 if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
904 gtk_text_buffer_create_tag(buffer, "quote0",
905 "foreground-gdk", "e_color1,
906 "paragraph-background-gdk", "e_bgcolor1,
908 gtk_text_buffer_create_tag(buffer, "quote1",
909 "foreground-gdk", "e_color2,
910 "paragraph-background-gdk", "e_bgcolor2,
912 gtk_text_buffer_create_tag(buffer, "quote2",
913 "foreground-gdk", "e_color3,
914 "paragraph-background-gdk", "e_bgcolor3,
917 gtk_text_buffer_create_tag(buffer, "quote0",
918 "foreground-gdk", "e_color1,
920 gtk_text_buffer_create_tag(buffer, "quote1",
921 "foreground-gdk", "e_color2,
923 gtk_text_buffer_create_tag(buffer, "quote2",
924 "foreground-gdk", "e_color3,
928 gtk_text_buffer_create_tag(buffer, "signature",
929 "foreground-gdk", &signature_color,
931 gtk_text_buffer_create_tag(buffer, "link",
932 "foreground-gdk", &uri_color,
934 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
935 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
937 color[0] = quote_color1;
938 color[1] = quote_color2;
939 color[2] = quote_color3;
940 color[3] = quote_bgcolor1;
941 color[4] = quote_bgcolor2;
942 color[5] = quote_bgcolor3;
943 color[6] = signature_color;
944 color[7] = uri_color;
945 cmap = gdk_drawable_get_colormap(compose->window->window);
946 gdk_colormap_alloc_colors(cmap, color, 8, FALSE, TRUE, success);
948 for (i = 0; i < 8; i++) {
949 if (success[i] == FALSE) {
952 g_warning("Compose: color allocation failed.\n");
953 style = gtk_widget_get_style(GTK_WIDGET(text));
954 quote_color1 = quote_color2 = quote_color3 =
955 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 =
956 signature_color = uri_color = black;
961 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
962 GPtrArray *attach_files)
964 return compose_generic_new(account, mailto, NULL, attach_files, NULL);
967 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
969 return compose_generic_new(account, mailto, item, NULL, NULL);
972 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
974 return compose_generic_new( account, NULL, NULL, NULL, listAddress );
977 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
978 GPtrArray *attach_files, GList *listAddress )
981 GtkTextView *textview;
982 GtkTextBuffer *textbuf;
984 GtkItemFactory *ifactory;
985 const gchar *subject_format = NULL;
986 const gchar *body_format = NULL;
988 if (item && item->prefs && item->prefs->enable_default_account)
989 account = account_find_from_id(item->prefs->default_account);
991 if (!account) account = cur_account;
992 g_return_val_if_fail(account != NULL, NULL);
994 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
996 ifactory = gtk_item_factory_from_widget(compose->menubar);
998 compose->replyinfo = NULL;
999 compose->fwdinfo = NULL;
1001 textview = GTK_TEXT_VIEW(compose->text);
1002 textbuf = gtk_text_view_get_buffer(textview);
1003 compose_create_tags(textview, compose);
1005 undo_block(compose->undostruct);
1007 compose_set_dictionaries_from_folder_prefs(compose, item);
1010 if (account->auto_sig)
1011 compose_insert_sig(compose, FALSE);
1012 gtk_text_buffer_get_start_iter(textbuf, &iter);
1013 gtk_text_buffer_place_cursor(textbuf, &iter);
1015 if (account->protocol != A_NNTP) {
1016 if (mailto && *mailto != '\0') {
1017 compose_entries_set(compose, mailto);
1019 } else if (item && item->prefs->enable_default_to) {
1020 compose_entry_append(compose, item->prefs->default_to, COMPOSE_TO);
1021 compose_entry_mark_default_to(compose, item->prefs->default_to);
1023 if (item && item->ret_rcpt) {
1024 menu_set_active(ifactory, "/Options/Request Return Receipt", TRUE);
1028 compose_entry_append(compose, mailto, COMPOSE_NEWSGROUPS);
1029 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1030 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS);
1033 * CLAWS: just don't allow return receipt request, even if the user
1034 * may want to send an email. simple but foolproof.
1036 menu_set_sensitive(ifactory, "/Options/Request Return Receipt", FALSE);
1038 compose_add_field_list( compose, listAddress );
1040 if (item && item->prefs && item->prefs->compose_with_format) {
1041 subject_format = item->prefs->compose_subject_format;
1042 body_format = item->prefs->compose_body_format;
1043 } else if (account->compose_with_format) {
1044 subject_format = account->compose_subject_format;
1045 body_format = account->compose_body_format;
1046 } else if (prefs_common.compose_with_format) {
1047 subject_format = prefs_common.compose_subject_format;
1048 body_format = prefs_common.compose_body_format;
1051 if (subject_format || body_format) {
1052 MsgInfo* dummyinfo = NULL;
1055 && *subject_format != '\0' )
1057 gchar *subject = NULL;
1061 dummyinfo = compose_msginfo_new_from_compose(compose);
1063 /* decode \-escape sequences in the internal representation of the quote format */
1064 tmp = malloc(strlen(subject_format)+1);
1065 pref_get_unescaped_pref(tmp, subject_format);
1067 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1069 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account,
1070 compose->gtkaspell);
1072 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account);
1074 quote_fmt_scan_string(tmp);
1077 buf = quote_fmt_get_buffer();
1079 alertpanel_error(_("New message subject format error."));
1081 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1082 quote_fmt_reset_vartable();
1089 && *body_format != '\0' )
1092 GtkTextBuffer *buffer;
1093 GtkTextIter start, end;
1096 if ( dummyinfo == NULL )
1097 dummyinfo = compose_msginfo_new_from_compose(compose);
1099 text = GTK_TEXT_VIEW(compose->text);
1100 buffer = gtk_text_view_get_buffer(text);
1101 gtk_text_buffer_get_start_iter(buffer, &start);
1102 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1103 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1105 compose_quote_fmt(compose, dummyinfo,
1107 NULL, tmp, FALSE, TRUE,
1108 _("New message body format error at line %d."));
1109 quote_fmt_reset_vartable();
1114 procmsg_msginfo_free( dummyinfo );
1121 for (i = 0; i < attach_files->len; i++) {
1122 file = g_ptr_array_index(attach_files, i);
1123 compose_attach_append(compose, file, file, NULL);
1127 compose_show_first_last_header(compose, TRUE);
1129 /* Set save folder */
1130 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1131 gchar *folderidentifier;
1133 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
1134 folderidentifier = folder_item_get_identifier(item);
1135 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
1136 g_free(folderidentifier);
1139 gtk_widget_grab_focus(compose->header_last->entry);
1141 undo_unblock(compose->undostruct);
1143 if (prefs_common.auto_exteditor)
1144 compose_exec_ext_editor(compose);
1146 compose->draft_timeout_tag = -1;
1147 compose->modified = FALSE;
1148 compose_set_title(compose);
1152 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1153 gboolean override_pref)
1155 gchar *privacy = NULL;
1157 g_return_if_fail(compose != NULL);
1158 g_return_if_fail(account != NULL);
1160 if (override_pref == FALSE && account->default_encrypt_reply == FALSE)
1163 if (account->default_privacy_system
1164 && strlen(account->default_privacy_system)) {
1165 privacy = account->default_privacy_system;
1167 GSList *privacy_avail = privacy_get_system_ids();
1168 if (privacy_avail && g_slist_length(privacy_avail)) {
1169 privacy = (gchar *)(privacy_avail->data);
1172 if (privacy != NULL) {
1173 if (compose->privacy_system == NULL)
1174 compose->privacy_system = g_strdup(privacy);
1175 compose_update_privacy_system_menu_item(compose, FALSE);
1176 compose_use_encryption(compose, TRUE);
1180 static void compose_force_signing(Compose *compose, PrefsAccount *account)
1182 gchar *privacy = NULL;
1184 if (account->default_privacy_system
1185 && strlen(account->default_privacy_system)) {
1186 privacy = account->default_privacy_system;
1188 GSList *privacy_avail = privacy_get_system_ids();
1189 if (privacy_avail && g_slist_length(privacy_avail)) {
1190 privacy = (gchar *)(privacy_avail->data);
1193 if (privacy != NULL) {
1194 if (compose->privacy_system == NULL)
1195 compose->privacy_system = g_strdup(privacy);
1196 compose_update_privacy_system_menu_item(compose, FALSE);
1197 compose_use_signing(compose, TRUE);
1201 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1205 Compose *compose = NULL;
1206 GtkItemFactory *ifactory = NULL;
1208 g_return_val_if_fail(msginfo_list != NULL, NULL);
1210 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1211 g_return_val_if_fail(msginfo != NULL, NULL);
1213 list_len = g_slist_length(msginfo_list);
1217 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1218 FALSE, prefs_common.default_reply_list, FALSE, body);
1220 case COMPOSE_REPLY_WITH_QUOTE:
1221 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1222 FALSE, prefs_common.default_reply_list, FALSE, body);
1224 case COMPOSE_REPLY_WITHOUT_QUOTE:
1225 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1226 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1228 case COMPOSE_REPLY_TO_SENDER:
1229 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1230 FALSE, FALSE, TRUE, body);
1232 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1233 compose = compose_followup_and_reply_to(msginfo,
1234 COMPOSE_QUOTE_CHECK,
1235 FALSE, FALSE, body);
1237 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1238 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1239 FALSE, FALSE, TRUE, body);
1241 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1242 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1243 FALSE, FALSE, TRUE, NULL);
1245 case COMPOSE_REPLY_TO_ALL:
1246 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1247 TRUE, FALSE, FALSE, body);
1249 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1250 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1251 TRUE, FALSE, FALSE, body);
1253 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1254 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1255 TRUE, FALSE, FALSE, NULL);
1257 case COMPOSE_REPLY_TO_LIST:
1258 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1259 FALSE, TRUE, FALSE, body);
1261 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1262 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1263 FALSE, TRUE, FALSE, body);
1265 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1266 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1267 FALSE, TRUE, FALSE, NULL);
1269 case COMPOSE_FORWARD:
1270 if (prefs_common.forward_as_attachment) {
1271 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1274 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1278 case COMPOSE_FORWARD_INLINE:
1279 /* check if we reply to more than one Message */
1280 if (list_len == 1) {
1281 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1284 /* more messages FALL THROUGH */
1285 case COMPOSE_FORWARD_AS_ATTACH:
1286 compose = compose_forward_multiple(NULL, msginfo_list);
1288 case COMPOSE_REDIRECT:
1289 compose = compose_redirect(NULL, msginfo, FALSE);
1292 g_warning("compose_reply_mode(): invalid Compose Mode: %d\n", mode);
1295 ifactory = gtk_item_factory_from_widget(compose->menubar);
1297 compose->rmode = mode;
1298 switch (compose->rmode) {
1300 case COMPOSE_REPLY_WITH_QUOTE:
1301 case COMPOSE_REPLY_WITHOUT_QUOTE:
1302 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1303 debug_print("reply mode Normal\n");
1304 menu_set_active(ifactory, "/Options/Reply mode/Normal", TRUE);
1305 compose_reply_change_mode(compose, COMPOSE_REPLY, NULL); /* force update */
1307 case COMPOSE_REPLY_TO_SENDER:
1308 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1309 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1310 debug_print("reply mode Sender\n");
1311 menu_set_active(ifactory, "/Options/Reply mode/Sender", TRUE);
1313 case COMPOSE_REPLY_TO_ALL:
1314 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1315 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1316 debug_print("reply mode All\n");
1317 menu_set_active(ifactory, "/Options/Reply mode/All", TRUE);
1319 case COMPOSE_REPLY_TO_LIST:
1320 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1321 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1322 debug_print("reply mode List\n");
1323 menu_set_active(ifactory, "/Options/Reply mode/Mailing-list", TRUE);
1331 static Compose *compose_reply(MsgInfo *msginfo,
1332 ComposeQuoteMode quote_mode,
1338 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1339 to_sender, FALSE, body);
1342 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1343 ComposeQuoteMode quote_mode,
1348 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1349 to_sender, TRUE, body);
1352 static void compose_extract_original_charset(Compose *compose)
1354 MsgInfo *info = NULL;
1355 if (compose->replyinfo) {
1356 info = compose->replyinfo;
1357 } else if (compose->fwdinfo) {
1358 info = compose->fwdinfo;
1359 } else if (compose->targetinfo) {
1360 info = compose->targetinfo;
1363 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1364 MimeInfo *partinfo = mimeinfo;
1365 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1366 partinfo = procmime_mimeinfo_next(partinfo);
1368 compose->orig_charset =
1369 g_strdup(procmime_mimeinfo_get_parameter(
1370 partinfo, "charset"));
1372 procmime_mimeinfo_free_all(mimeinfo);
1376 #define SIGNAL_BLOCK(buffer) { \
1377 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1378 G_CALLBACK(compose_changed_cb), \
1380 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1381 G_CALLBACK(text_inserted), \
1385 #define SIGNAL_UNBLOCK(buffer) { \
1386 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1387 G_CALLBACK(compose_changed_cb), \
1389 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1390 G_CALLBACK(text_inserted), \
1394 static Compose *compose_generic_reply(MsgInfo *msginfo,
1395 ComposeQuoteMode quote_mode,
1396 gboolean to_all, gboolean to_ml,
1398 gboolean followup_and_reply_to,
1401 GtkItemFactory *ifactory;
1403 PrefsAccount *account = NULL;
1404 GtkTextView *textview;
1405 GtkTextBuffer *textbuf;
1406 gboolean quote = FALSE;
1407 const gchar *qmark = NULL;
1408 const gchar *body_fmt = NULL;
1410 g_return_val_if_fail(msginfo != NULL, NULL);
1411 g_return_val_if_fail(msginfo->folder != NULL, NULL);
1413 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1415 g_return_val_if_fail(account != NULL, NULL);
1417 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1419 compose->updating = TRUE;
1421 ifactory = gtk_item_factory_from_widget(compose->menubar);
1423 menu_set_active(ifactory, "/Options/Remove references", FALSE);
1424 menu_set_sensitive(ifactory, "/Options/Remove references", TRUE);
1426 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1427 if (!compose->replyinfo)
1428 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1430 compose_extract_original_charset(compose);
1432 if (msginfo->folder && msginfo->folder->ret_rcpt)
1433 menu_set_active(ifactory, "/Options/Request Return Receipt", TRUE);
1435 /* Set save folder */
1436 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1437 gchar *folderidentifier;
1439 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1440 folderidentifier = folder_item_get_identifier(msginfo->folder);
1441 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
1442 g_free(folderidentifier);
1445 if (compose_parse_header(compose, msginfo) < 0) return NULL;
1447 textview = (GTK_TEXT_VIEW(compose->text));
1448 textbuf = gtk_text_view_get_buffer(textview);
1449 compose_create_tags(textview, compose);
1451 undo_block(compose->undostruct);
1453 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1456 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1457 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1458 /* use the reply format of folder (if enabled), or the account's one
1459 (if enabled) or fallback to the global reply format, which is always
1460 enabled (even if empty), and use the relevant quotemark */
1462 if (msginfo->folder && msginfo->folder->prefs &&
1463 msginfo->folder->prefs->reply_with_format) {
1464 qmark = msginfo->folder->prefs->reply_quotemark;
1465 body_fmt = msginfo->folder->prefs->reply_body_format;
1467 } else if (account->reply_with_format) {
1468 qmark = account->reply_quotemark;
1469 body_fmt = account->reply_body_format;
1472 qmark = prefs_common.quotemark;
1473 body_fmt = prefs_common.quotefmt;
1478 /* empty quotemark is not allowed */
1479 if (qmark == NULL || *qmark == '\0')
1481 compose_quote_fmt(compose, compose->replyinfo,
1482 body_fmt, qmark, body, FALSE, TRUE,
1483 _("Message reply format error at line %d."));
1484 quote_fmt_reset_vartable();
1487 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1488 compose_force_encryption(compose, account, FALSE);
1491 SIGNAL_BLOCK(textbuf);
1493 if (account->auto_sig)
1494 compose_insert_sig(compose, FALSE);
1496 compose_wrap_all(compose);
1498 SIGNAL_UNBLOCK(textbuf);
1500 gtk_widget_grab_focus(compose->text);
1502 undo_unblock(compose->undostruct);
1504 if (prefs_common.auto_exteditor)
1505 compose_exec_ext_editor(compose);
1507 compose->modified = FALSE;
1508 compose_set_title(compose);
1510 compose->updating = FALSE;
1511 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1513 if (compose->deferred_destroy) {
1514 compose_destroy(compose);
1521 #define INSERT_FW_HEADER(var, hdr) \
1522 if (msginfo->var && *msginfo->var) { \
1523 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1524 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1525 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1528 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1529 gboolean as_attach, const gchar *body,
1530 gboolean no_extedit,
1534 GtkTextView *textview;
1535 GtkTextBuffer *textbuf;
1538 g_return_val_if_fail(msginfo != NULL, NULL);
1539 g_return_val_if_fail(msginfo->folder != NULL, NULL);
1542 !(account = compose_guess_forward_account_from_msginfo
1544 account = cur_account;
1546 compose = compose_create(account, msginfo->folder, COMPOSE_FORWARD, batch);
1548 compose->updating = TRUE;
1549 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1550 if (!compose->fwdinfo)
1551 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1553 compose_extract_original_charset(compose);
1555 if (msginfo->subject && *msginfo->subject) {
1556 gchar *buf, *buf2, *p;
1558 buf = p = g_strdup(msginfo->subject);
1559 p += subject_get_prefix_length(p);
1560 memmove(buf, p, strlen(p) + 1);
1562 buf2 = g_strdup_printf("Fw: %s", buf);
1563 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1569 textview = GTK_TEXT_VIEW(compose->text);
1570 textbuf = gtk_text_view_get_buffer(textview);
1571 compose_create_tags(textview, compose);
1573 undo_block(compose->undostruct);
1577 msgfile = procmsg_get_message_file(msginfo);
1578 if (!is_file_exist(msgfile))
1579 g_warning("%s: file not exist\n", msgfile);
1581 compose_attach_append(compose, msgfile, msgfile,
1586 const gchar *qmark = NULL;
1587 const gchar *body_fmt = prefs_common.fw_quotefmt;
1588 MsgInfo *full_msginfo;
1590 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1592 full_msginfo = procmsg_msginfo_copy(msginfo);
1594 /* use the forward format of folder (if enabled), or the account's one
1595 (if enabled) or fallback to the global forward format, which is always
1596 enabled (even if empty), and use the relevant quotemark */
1597 if (msginfo->folder && msginfo->folder->prefs &&
1598 msginfo->folder->prefs->forward_with_format) {
1599 qmark = msginfo->folder->prefs->forward_quotemark;
1600 body_fmt = msginfo->folder->prefs->forward_body_format;
1602 } else if (account->forward_with_format) {
1603 qmark = account->forward_quotemark;
1604 body_fmt = account->forward_body_format;
1607 qmark = prefs_common.fw_quotemark;
1608 body_fmt = prefs_common.fw_quotefmt;
1611 /* empty quotemark is not allowed */
1612 if (qmark == NULL || *qmark == '\0')
1615 compose_quote_fmt(compose, full_msginfo,
1616 body_fmt, qmark, body, FALSE, TRUE,
1617 _("Message forward format error at line %d."));
1618 quote_fmt_reset_vartable();
1619 compose_attach_parts(compose, msginfo);
1621 procmsg_msginfo_free(full_msginfo);
1624 SIGNAL_BLOCK(textbuf);
1626 if (account->auto_sig)
1627 compose_insert_sig(compose, FALSE);
1629 compose_wrap_all(compose);
1631 SIGNAL_UNBLOCK(textbuf);
1633 gtk_text_buffer_get_start_iter(textbuf, &iter);
1634 gtk_text_buffer_place_cursor(textbuf, &iter);
1636 gtk_widget_grab_focus(compose->header_last->entry);
1638 if (!no_extedit && prefs_common.auto_exteditor)
1639 compose_exec_ext_editor(compose);
1642 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1643 gchar *folderidentifier;
1645 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1646 folderidentifier = folder_item_get_identifier(msginfo->folder);
1647 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
1648 g_free(folderidentifier);
1651 undo_unblock(compose->undostruct);
1653 compose->modified = FALSE;
1654 compose_set_title(compose);
1656 compose->updating = FALSE;
1657 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1659 if (compose->deferred_destroy) {
1660 compose_destroy(compose);
1667 #undef INSERT_FW_HEADER
1669 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1672 GtkTextView *textview;
1673 GtkTextBuffer *textbuf;
1677 gboolean single_mail = TRUE;
1679 g_return_val_if_fail(msginfo_list != NULL, NULL);
1681 if (g_slist_length(msginfo_list) > 1)
1682 single_mail = FALSE;
1684 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1685 if (((MsgInfo *)msginfo->data)->folder == NULL)
1688 /* guess account from first selected message */
1690 !(account = compose_guess_forward_account_from_msginfo
1691 (msginfo_list->data)))
1692 account = cur_account;
1694 g_return_val_if_fail(account != NULL, NULL);
1696 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1697 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1698 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1701 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1703 compose->updating = TRUE;
1705 textview = GTK_TEXT_VIEW(compose->text);
1706 textbuf = gtk_text_view_get_buffer(textview);
1707 compose_create_tags(textview, compose);
1709 undo_block(compose->undostruct);
1710 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1711 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
1713 if (!is_file_exist(msgfile))
1714 g_warning("%s: file not exist\n", msgfile);
1716 compose_attach_append(compose, msgfile, msgfile,
1722 MsgInfo *info = (MsgInfo *)msginfo_list->data;
1723 if (info->subject && *info->subject) {
1724 gchar *buf, *buf2, *p;
1726 buf = p = g_strdup(info->subject);
1727 p += subject_get_prefix_length(p);
1728 memmove(buf, p, strlen(p) + 1);
1730 buf2 = g_strdup_printf("Fw: %s", buf);
1731 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1737 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
1738 _("Fw: multiple emails"));
1741 SIGNAL_BLOCK(textbuf);
1743 if (account->auto_sig)
1744 compose_insert_sig(compose, FALSE);
1746 compose_wrap_all(compose);
1748 SIGNAL_UNBLOCK(textbuf);
1750 gtk_text_buffer_get_start_iter(textbuf, &iter);
1751 gtk_text_buffer_place_cursor(textbuf, &iter);
1753 gtk_widget_grab_focus(compose->header_last->entry);
1754 undo_unblock(compose->undostruct);
1755 compose->modified = FALSE;
1756 compose_set_title(compose);
1758 compose->updating = FALSE;
1759 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1761 if (compose->deferred_destroy) {
1762 compose_destroy(compose);
1769 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
1771 GtkTextIter start = *iter;
1772 GtkTextIter end_iter;
1773 int start_pos = gtk_text_iter_get_offset(&start);
1775 if (!compose->account->sig_sep)
1778 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
1779 start_pos+strlen(compose->account->sig_sep));
1781 /* check sig separator */
1782 str = gtk_text_iter_get_text(&start, &end_iter);
1783 if (!strcmp(str, compose->account->sig_sep)) {
1785 /* check end of line (\n) */
1786 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
1787 start_pos+strlen(compose->account->sig_sep));
1788 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
1789 start_pos+strlen(compose->account->sig_sep)+1);
1790 tmp = gtk_text_iter_get_text(&start, &end_iter);
1791 if (!strcmp(tmp,"\n")) {
1803 static void compose_colorize_signature(Compose *compose)
1805 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
1807 GtkTextIter end_iter;
1808 gtk_text_buffer_get_start_iter(buffer, &iter);
1809 while (gtk_text_iter_forward_line(&iter))
1810 if (compose_is_sig_separator(compose, buffer, &iter)) {
1811 gtk_text_buffer_get_end_iter(buffer, &end_iter);
1812 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
1816 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
1818 Compose *compose = NULL;
1819 PrefsAccount *account = NULL;
1820 GtkTextView *textview;
1821 GtkTextBuffer *textbuf;
1825 gchar buf[BUFFSIZE];
1826 gboolean use_signing = FALSE;
1827 gboolean use_encryption = FALSE;
1828 gchar *privacy_system = NULL;
1829 int priority = PRIORITY_NORMAL;
1830 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
1832 g_return_val_if_fail(msginfo != NULL, NULL);
1833 g_return_val_if_fail(msginfo->folder != NULL, NULL);
1835 if (compose_put_existing_to_front(msginfo)) {
1839 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
1840 folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
1841 gchar queueheader_buf[BUFFSIZE];
1844 /* Select Account from queue headers */
1845 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1846 sizeof(queueheader_buf), "X-Claws-Account-Id:")) {
1847 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
1848 account = account_find_from_id(id);
1850 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1851 sizeof(queueheader_buf), "X-Sylpheed-Account-Id:")) {
1852 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
1853 account = account_find_from_id(id);
1855 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1856 sizeof(queueheader_buf), "NAID:")) {
1857 id = atoi(&queueheader_buf[strlen("NAID:")]);
1858 account = account_find_from_id(id);
1860 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1861 sizeof(queueheader_buf), "MAID:")) {
1862 id = atoi(&queueheader_buf[strlen("MAID:")]);
1863 account = account_find_from_id(id);
1865 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1866 sizeof(queueheader_buf), "S:")) {
1867 account = account_find_from_address(queueheader_buf);
1869 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1870 sizeof(queueheader_buf), "X-Claws-Sign:")) {
1871 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
1872 use_signing = param;
1875 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1876 sizeof(queueheader_buf), "X-Sylpheed-Sign:")) {
1877 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
1878 use_signing = param;
1881 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1882 sizeof(queueheader_buf), "X-Claws-Encrypt:")) {
1883 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
1884 use_encryption = param;
1886 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1887 sizeof(queueheader_buf), "X-Sylpheed-Encrypt:")) {
1888 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
1889 use_encryption = param;
1891 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1892 sizeof(queueheader_buf), "X-Claws-Privacy-System:")) {
1893 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
1895 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1896 sizeof(queueheader_buf), "X-Sylpheed-Privacy-System:")) {
1897 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
1899 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1900 sizeof(queueheader_buf), "X-Priority: ")) {
1901 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
1904 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1905 sizeof(queueheader_buf), "RMID:")) {
1906 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
1907 if (tokens[0] && tokens[1] && tokens[2]) {
1908 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
1909 if (orig_item != NULL) {
1910 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
1915 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1916 sizeof(queueheader_buf), "FMID:")) {
1917 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
1918 if (tokens[0] && tokens[1] && tokens[2]) {
1919 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
1920 if (orig_item != NULL) {
1921 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
1927 account = msginfo->folder->folder->account;
1930 if (!account && prefs_common.reedit_account_autosel) {
1931 gchar from[BUFFSIZE];
1932 if (!procheader_get_header_from_msginfo(msginfo, from, sizeof(from), "FROM:")) {
1933 extract_address(from);
1934 account = account_find_from_address(from);
1938 account = cur_account;
1940 g_return_val_if_fail(account != NULL, NULL);
1942 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
1944 compose->replyinfo = replyinfo;
1945 compose->fwdinfo = fwdinfo;
1947 compose->updating = TRUE;
1948 compose->priority = priority;
1950 if (privacy_system != NULL) {
1951 compose->privacy_system = privacy_system;
1952 compose_use_signing(compose, use_signing);
1953 compose_use_encryption(compose, use_encryption);
1954 compose_update_privacy_system_menu_item(compose, FALSE);
1956 activate_privacy_system(compose, account, FALSE);
1959 compose->targetinfo = procmsg_msginfo_copy(msginfo);
1961 compose_extract_original_charset(compose);
1963 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
1964 folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
1965 gchar queueheader_buf[BUFFSIZE];
1967 /* Set message save folder */
1968 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "SCF:")) {
1971 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1972 gtk_editable_delete_text(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
1973 gtk_editable_insert_text(GTK_EDITABLE(compose->savemsg_entry), &queueheader_buf[4], strlen(&queueheader_buf[4]), &startpos);
1975 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "RRCPT:")) {
1976 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
1978 GtkItemFactory *ifactory;
1979 ifactory = gtk_item_factory_from_widget(compose->menubar);
1980 menu_set_active(ifactory, "/Options/Request Return Receipt", TRUE);
1985 if (compose_parse_header(compose, msginfo) < 0) {
1986 compose->updating = FALSE;
1987 compose_destroy(compose);
1990 compose_reedit_set_entry(compose, msginfo);
1992 textview = GTK_TEXT_VIEW(compose->text);
1993 textbuf = gtk_text_view_get_buffer(textview);
1994 compose_create_tags(textview, compose);
1996 mark = gtk_text_buffer_get_insert(textbuf);
1997 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
1999 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2000 G_CALLBACK(compose_changed_cb),
2003 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2004 fp = procmime_get_first_encrypted_text_content(msginfo);
2006 compose_force_encryption(compose, account, TRUE);
2009 fp = procmime_get_first_text_content(msginfo);
2012 g_warning("Can't get text part\n");
2016 gboolean prev_autowrap = compose->autowrap;
2018 compose->autowrap = FALSE;
2019 while (fgets(buf, sizeof(buf), fp) != NULL) {
2021 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2023 compose_wrap_all_full(compose, FALSE);
2024 compose->autowrap = prev_autowrap;
2028 compose_attach_parts(compose, msginfo);
2030 compose_colorize_signature(compose);
2032 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2033 G_CALLBACK(compose_changed_cb),
2036 gtk_widget_grab_focus(compose->text);
2038 if (prefs_common.auto_exteditor) {
2039 compose_exec_ext_editor(compose);
2041 compose->modified = FALSE;
2042 compose_set_title(compose);
2044 compose->updating = FALSE;
2045 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2047 if (compose->deferred_destroy) {
2048 compose_destroy(compose);
2052 compose->sig_str = compose_get_signature_str(compose);
2057 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2062 GtkItemFactory *ifactory;
2065 g_return_val_if_fail(msginfo != NULL, NULL);
2068 account = account_get_reply_account(msginfo,
2069 prefs_common.reply_account_autosel);
2070 g_return_val_if_fail(account != NULL, NULL);
2072 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2074 compose->updating = TRUE;
2076 ifactory = gtk_item_factory_from_widget(compose->menubar);
2077 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2078 compose->replyinfo = NULL;
2079 compose->fwdinfo = NULL;
2081 compose_show_first_last_header(compose, TRUE);
2083 gtk_widget_grab_focus(compose->header_last->entry);
2085 filename = procmsg_get_message_file(msginfo);
2087 if (filename == NULL) {
2088 compose->updating = FALSE;
2089 compose_destroy(compose);
2094 compose->redirect_filename = filename;
2096 /* Set save folder */
2097 item = msginfo->folder;
2098 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2099 gchar *folderidentifier;
2101 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
2102 folderidentifier = folder_item_get_identifier(item);
2103 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
2104 g_free(folderidentifier);
2107 compose_attach_parts(compose, msginfo);
2109 if (msginfo->subject)
2110 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2112 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2114 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2115 _("Message redirect format error at line %d."));
2116 quote_fmt_reset_vartable();
2117 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2119 compose_colorize_signature(compose);
2121 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
2122 menu_set_sensitive(ifactory, "/Add...", FALSE);
2123 menu_set_sensitive(ifactory, "/Remove", FALSE);
2124 menu_set_sensitive(ifactory, "/Properties...", FALSE);
2126 ifactory = gtk_item_factory_from_widget(compose->menubar);
2127 menu_set_sensitive(ifactory, "/Message/Save", FALSE);
2128 menu_set_sensitive(ifactory, "/Message/Insert file", FALSE);
2129 menu_set_sensitive(ifactory, "/Message/Attach file", FALSE);
2130 menu_set_sensitive(ifactory, "/Message/Insert signature", FALSE);
2131 menu_set_sensitive(ifactory, "/Edit", FALSE);
2132 menu_set_sensitive(ifactory, "/Options", FALSE);
2133 menu_set_sensitive(ifactory, "/Tools/Show ruler", FALSE);
2134 menu_set_sensitive(ifactory, "/Tools/Actions", FALSE);
2136 if (compose->toolbar->draft_btn)
2137 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2138 if (compose->toolbar->insert_btn)
2139 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2140 if (compose->toolbar->attach_btn)
2141 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2142 if (compose->toolbar->sig_btn)
2143 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2144 if (compose->toolbar->exteditor_btn)
2145 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2146 if (compose->toolbar->linewrap_current_btn)
2147 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2148 if (compose->toolbar->linewrap_all_btn)
2149 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2151 compose->modified = FALSE;
2152 compose_set_title(compose);
2153 compose->updating = FALSE;
2154 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2156 if (compose->deferred_destroy) {
2157 compose_destroy(compose);
2164 GList *compose_get_compose_list(void)
2166 return compose_list;
2169 void compose_entry_append(Compose *compose, const gchar *address,
2170 ComposeEntryType type)
2172 const gchar *header;
2174 gboolean in_quote = FALSE;
2175 if (!address || *address == '\0') return;
2182 header = N_("Bcc:");
2184 case COMPOSE_REPLYTO:
2185 header = N_("Reply-To:");
2187 case COMPOSE_NEWSGROUPS:
2188 header = N_("Newsgroups:");
2190 case COMPOSE_FOLLOWUPTO:
2191 header = N_( "Followup-To:");
2198 header = prefs_common_translated_header_name(header);
2200 cur = begin = (gchar *)address;
2202 /* we separate the line by commas, but not if we're inside a quoted
2204 while (*cur != '\0') {
2206 in_quote = !in_quote;
2207 if (*cur == ',' && !in_quote) {
2208 gchar *tmp = g_strdup(begin);
2210 tmp[cur-begin]='\0';
2213 while (*tmp == ' ' || *tmp == '\t')
2215 compose_add_header_entry(compose, header, tmp);
2222 gchar *tmp = g_strdup(begin);
2224 tmp[cur-begin]='\0';
2227 while (*tmp == ' ' || *tmp == '\t')
2229 compose_add_header_entry(compose, header, tmp);
2234 static void compose_entry_mark_default_to(Compose *compose, const gchar *mailto)
2236 static GdkColor yellow;
2237 static GdkColor black;
2238 static gboolean yellow_initialised = FALSE;
2242 if (!yellow_initialised) {
2243 gdk_color_parse("#f5f6be", &yellow);
2244 gdk_color_parse("#000000", &black);
2245 yellow_initialised = gdk_colormap_alloc_color(
2246 gdk_colormap_get_system(), &yellow, FALSE, TRUE);
2247 yellow_initialised &= gdk_colormap_alloc_color(
2248 gdk_colormap_get_system(), &black, FALSE, TRUE);
2251 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2252 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2253 if (gtk_entry_get_text(entry) &&
2254 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2255 if (yellow_initialised) {
2256 gtk_widget_modify_base(
2257 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2258 GTK_STATE_NORMAL, &yellow);
2259 gtk_widget_modify_text(
2260 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2261 GTK_STATE_NORMAL, &black);
2267 void compose_toolbar_cb(gint action, gpointer data)
2269 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2270 Compose *compose = (Compose*)toolbar_item->parent;
2272 g_return_if_fail(compose != NULL);
2276 compose_send_cb(compose, 0, NULL);
2279 compose_send_later_cb(compose, 0, NULL);
2282 compose_draft_cb(compose, COMPOSE_QUIT_EDITING, NULL);
2285 compose_insert_file_cb(compose, 0, NULL);
2288 compose_attach_cb(compose, 0, NULL);
2291 compose_insert_sig(compose, FALSE);
2294 compose_ext_editor_cb(compose, 0, NULL);
2296 case A_LINEWRAP_CURRENT:
2297 compose_beautify_paragraph(compose, NULL, TRUE);
2299 case A_LINEWRAP_ALL:
2300 compose_wrap_all_full(compose, TRUE);
2303 compose_address_cb(compose, 0, NULL);
2306 case A_CHECK_SPELLING:
2307 compose_check_all(compose);
2315 static void compose_entries_set(Compose *compose, const gchar *mailto)
2319 gchar *subject = NULL;
2323 gchar *attach = NULL;
2325 scan_mailto_url(mailto, &to, &cc, NULL, &subject, &body, &attach);
2328 compose_entry_append(compose, to, COMPOSE_TO);
2330 compose_entry_append(compose, cc, COMPOSE_CC);
2332 if (!g_utf8_validate (subject, -1, NULL)) {
2333 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2334 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2337 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2341 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2342 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2345 gboolean prev_autowrap = compose->autowrap;
2347 compose->autowrap = FALSE;
2349 mark = gtk_text_buffer_get_insert(buffer);
2350 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2352 if (!g_utf8_validate (body, -1, NULL)) {
2353 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2354 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2357 gtk_text_buffer_insert(buffer, &iter, body, -1);
2359 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2361 compose->autowrap = prev_autowrap;
2362 if (compose->autowrap)
2363 compose_wrap_all(compose);
2367 gchar *utf8_filename = conv_filename_to_utf8(attach);
2368 if (utf8_filename) {
2369 if (compose_attach_append(compose, attach, utf8_filename, NULL)) {
2370 alertpanel_notice(_("The file '%s' has been attached."), attach);
2372 g_free(utf8_filename);
2374 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2384 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2386 static HeaderEntry hentry[] = {{"Reply-To:", NULL, TRUE},
2387 {"Cc:", NULL, TRUE},
2388 {"References:", NULL, FALSE},
2389 {"Bcc:", NULL, TRUE},
2390 {"Newsgroups:", NULL, TRUE},
2391 {"Followup-To:", NULL, TRUE},
2392 {"List-Post:", NULL, FALSE},
2393 {"X-Priority:", NULL, FALSE},
2394 {NULL, NULL, FALSE}};
2410 g_return_val_if_fail(msginfo != NULL, -1);
2412 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2413 procheader_get_header_fields(fp, hentry);
2416 if (hentry[H_REPLY_TO].body != NULL) {
2417 if (hentry[H_REPLY_TO].body[0] != '\0') {
2419 conv_unmime_header(hentry[H_REPLY_TO].body,
2422 g_free(hentry[H_REPLY_TO].body);
2423 hentry[H_REPLY_TO].body = NULL;
2425 if (hentry[H_CC].body != NULL) {
2426 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL);
2427 g_free(hentry[H_CC].body);
2428 hentry[H_CC].body = NULL;
2430 if (hentry[H_REFERENCES].body != NULL) {
2431 if (compose->mode == COMPOSE_REEDIT)
2432 compose->references = hentry[H_REFERENCES].body;
2434 compose->references = compose_parse_references
2435 (hentry[H_REFERENCES].body, msginfo->msgid);
2436 g_free(hentry[H_REFERENCES].body);
2438 hentry[H_REFERENCES].body = NULL;
2440 if (hentry[H_BCC].body != NULL) {
2441 if (compose->mode == COMPOSE_REEDIT)
2443 conv_unmime_header(hentry[H_BCC].body, NULL);
2444 g_free(hentry[H_BCC].body);
2445 hentry[H_BCC].body = NULL;
2447 if (hentry[H_NEWSGROUPS].body != NULL) {
2448 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2449 hentry[H_NEWSGROUPS].body = NULL;
2451 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2452 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2453 compose->followup_to =
2454 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2457 g_free(hentry[H_FOLLOWUP_TO].body);
2458 hentry[H_FOLLOWUP_TO].body = NULL;
2460 if (hentry[H_LIST_POST].body != NULL) {
2463 extract_address(hentry[H_LIST_POST].body);
2464 if (hentry[H_LIST_POST].body[0] != '\0') {
2465 scan_mailto_url(hentry[H_LIST_POST].body,
2466 &to, NULL, NULL, NULL, NULL, NULL);
2468 g_free(compose->ml_post);
2469 compose->ml_post = to;
2472 g_free(hentry[H_LIST_POST].body);
2473 hentry[H_LIST_POST].body = NULL;
2476 /* CLAWS - X-Priority */
2477 if (compose->mode == COMPOSE_REEDIT)
2478 if (hentry[H_X_PRIORITY].body != NULL) {
2481 priority = atoi(hentry[H_X_PRIORITY].body);
2482 g_free(hentry[H_X_PRIORITY].body);
2484 hentry[H_X_PRIORITY].body = NULL;
2486 if (priority < PRIORITY_HIGHEST ||
2487 priority > PRIORITY_LOWEST)
2488 priority = PRIORITY_NORMAL;
2490 compose->priority = priority;
2493 if (compose->mode == COMPOSE_REEDIT) {
2494 if (msginfo->inreplyto && *msginfo->inreplyto)
2495 compose->inreplyto = g_strdup(msginfo->inreplyto);
2499 if (msginfo->msgid && *msginfo->msgid)
2500 compose->inreplyto = g_strdup(msginfo->msgid);
2502 if (!compose->references) {
2503 if (msginfo->msgid && *msginfo->msgid) {
2504 if (msginfo->inreplyto && *msginfo->inreplyto)
2505 compose->references =
2506 g_strdup_printf("<%s>\n\t<%s>",
2510 compose->references =
2511 g_strconcat("<", msginfo->msgid, ">",
2513 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
2514 compose->references =
2515 g_strconcat("<", msginfo->inreplyto, ">",
2523 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
2525 GSList *ref_id_list, *cur;
2529 ref_id_list = references_list_append(NULL, ref);
2530 if (!ref_id_list) return NULL;
2531 if (msgid && *msgid)
2532 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
2537 for (cur = ref_id_list; cur != NULL; cur = cur->next)
2538 /* "<" + Message-ID + ">" + CR+LF+TAB */
2539 len += strlen((gchar *)cur->data) + 5;
2541 if (len > MAX_REFERENCES_LEN) {
2542 /* remove second message-ID */
2543 if (ref_id_list && ref_id_list->next &&
2544 ref_id_list->next->next) {
2545 g_free(ref_id_list->next->data);
2546 ref_id_list = g_slist_remove
2547 (ref_id_list, ref_id_list->next->data);
2549 slist_free_strings(ref_id_list);
2550 g_slist_free(ref_id_list);
2557 new_ref = g_string_new("");
2558 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
2559 if (new_ref->len > 0)
2560 g_string_append(new_ref, "\n\t");
2561 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
2564 slist_free_strings(ref_id_list);
2565 g_slist_free(ref_id_list);
2567 new_ref_str = new_ref->str;
2568 g_string_free(new_ref, FALSE);
2573 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
2574 const gchar *fmt, const gchar *qmark,
2575 const gchar *body, gboolean rewrap,
2576 gboolean need_unescape,
2577 const gchar *err_msg)
2579 MsgInfo* dummyinfo = NULL;
2580 gchar *quote_str = NULL;
2582 gboolean prev_autowrap;
2583 const gchar *trimmed_body = body;
2584 gint cursor_pos = -1;
2585 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2586 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2591 SIGNAL_BLOCK(buffer);
2594 dummyinfo = compose_msginfo_new_from_compose(compose);
2595 msginfo = dummyinfo;
2598 if (qmark != NULL) {
2600 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
2601 compose->gtkaspell);
2603 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
2605 quote_fmt_scan_string(qmark);
2608 buf = quote_fmt_get_buffer();
2610 alertpanel_error(_("Quote mark format error."));
2612 Xstrdup_a(quote_str, buf, goto error)
2615 if (fmt && *fmt != '\0') {
2618 while (*trimmed_body == '\n')
2622 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account,
2623 compose->gtkaspell);
2625 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account);
2627 if (need_unescape) {
2630 /* decode \-escape sequences in the internal representation of the quote format */
2631 tmp = malloc(strlen(fmt)+1);
2632 pref_get_unescaped_pref(tmp, fmt);
2633 quote_fmt_scan_string(tmp);
2637 quote_fmt_scan_string(fmt);
2641 buf = quote_fmt_get_buffer();
2643 gint line = quote_fmt_get_line();
2644 gchar *msg = g_strdup_printf(err_msg, line);
2645 alertpanel_error(msg);
2652 prev_autowrap = compose->autowrap;
2653 compose->autowrap = FALSE;
2655 mark = gtk_text_buffer_get_insert(buffer);
2656 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2657 if (g_utf8_validate(buf, -1, NULL)) {
2658 gtk_text_buffer_insert(buffer, &iter, buf, -1);
2660 gchar *tmpout = NULL;
2661 tmpout = conv_codeset_strdup
2662 (buf, conv_get_locale_charset_str_no_utf8(),
2664 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
2666 tmpout = g_malloc(strlen(buf)*2+1);
2667 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
2669 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
2673 cursor_pos = quote_fmt_get_cursor_pos();
2674 compose->set_cursor_pos = cursor_pos;
2675 if (cursor_pos == -1) {
2678 gtk_text_buffer_get_start_iter(buffer, &iter);
2679 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
2680 gtk_text_buffer_place_cursor(buffer, &iter);
2682 compose->autowrap = prev_autowrap;
2683 if (compose->autowrap && rewrap)
2684 compose_wrap_all(compose);
2691 SIGNAL_UNBLOCK(buffer);
2693 procmsg_msginfo_free( dummyinfo );
2698 /* if ml_post is of type addr@host and from is of type
2699 * addr-anything@host, return TRUE
2701 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
2703 gchar *left_ml = NULL;
2704 gchar *right_ml = NULL;
2705 gchar *left_from = NULL;
2706 gchar *right_from = NULL;
2707 gboolean result = FALSE;
2709 if (!ml_post || !from)
2712 left_ml = g_strdup(ml_post);
2713 if (strstr(left_ml, "@")) {
2714 right_ml = strstr(left_ml, "@")+1;
2715 *(strstr(left_ml, "@")) = '\0';
2718 left_from = g_strdup(from);
2719 if (strstr(left_from, "@")) {
2720 right_from = strstr(left_from, "@")+1;
2721 *(strstr(left_from, "@")) = '\0';
2724 if (left_ml && left_from && right_ml && right_from
2725 && !strncmp(left_from, left_ml, strlen(left_ml))
2726 && !strcmp(right_from, right_ml)) {
2735 static gboolean same_address(const gchar *addr1, const gchar *addr2)
2737 gchar *my_addr1, *my_addr2;
2739 if (!addr1 || !addr2)
2742 Xstrdup_a(my_addr1, addr1, return FALSE);
2743 Xstrdup_a(my_addr2, addr2, return FALSE);
2745 extract_address(my_addr1);
2746 extract_address(my_addr2);
2748 return !strcasecmp(my_addr1, my_addr2);
2751 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
2752 gboolean to_all, gboolean to_ml,
2754 gboolean followup_and_reply_to)
2756 GSList *cc_list = NULL;
2759 gchar *replyto = NULL;
2760 GHashTable *to_table;
2762 gboolean reply_to_ml = FALSE;
2763 gboolean default_reply_to = FALSE;
2765 g_return_if_fail(compose->account != NULL);
2766 g_return_if_fail(msginfo != NULL);
2768 reply_to_ml = to_ml && compose->ml_post;
2770 default_reply_to = msginfo->folder &&
2771 msginfo->folder->prefs->enable_default_reply_to;
2773 if (compose->account->protocol != A_NNTP) {
2774 if (reply_to_ml && !default_reply_to) {
2776 gboolean is_subscr = is_subscription(compose->ml_post,
2779 /* normal answer to ml post with a reply-to */
2780 compose_entry_append(compose,
2783 if (compose->replyto
2784 && !same_address(compose->ml_post, compose->replyto))
2785 compose_entry_append(compose,
2789 /* answer to subscription confirmation */
2790 if (compose->replyto)
2791 compose_entry_append(compose,
2794 else if (msginfo->from)
2795 compose_entry_append(compose,
2800 else if (!(to_all || to_sender) && default_reply_to) {
2801 compose_entry_append(compose,
2802 msginfo->folder->prefs->default_reply_to,
2804 compose_entry_mark_default_to(compose,
2805 msginfo->folder->prefs->default_reply_to);
2810 Xstrdup_a(tmp1, msginfo->from, return);
2811 extract_address(tmp1);
2812 if (to_all || to_sender ||
2813 !account_find_from_address(tmp1))
2814 compose_entry_append(compose,
2815 (compose->replyto && !to_sender)
2816 ? compose->replyto :
2817 msginfo->from ? msginfo->from : "",
2819 else if (!to_all && !to_sender) {
2820 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
2821 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
2822 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
2823 compose_entry_append(compose,
2824 msginfo->from ? msginfo->from : "",
2827 /* replying to own mail, use original recp */
2828 compose_entry_append(compose,
2829 msginfo->to ? msginfo->to : "",
2831 compose_entry_append(compose,
2832 msginfo->cc ? msginfo->cc : "",
2838 if (to_sender || (compose->followup_to &&
2839 !strncmp(compose->followup_to, "poster", 6)))
2840 compose_entry_append
2842 (compose->replyto ? compose->replyto :
2843 msginfo->from ? msginfo->from : ""),
2846 else if (followup_and_reply_to || to_all) {
2847 compose_entry_append
2849 (compose->replyto ? compose->replyto :
2850 msginfo->from ? msginfo->from : ""),
2853 compose_entry_append
2855 compose->followup_to ? compose->followup_to :
2856 compose->newsgroups ? compose->newsgroups : "",
2857 COMPOSE_NEWSGROUPS);
2860 compose_entry_append
2862 compose->followup_to ? compose->followup_to :
2863 compose->newsgroups ? compose->newsgroups : "",
2864 COMPOSE_NEWSGROUPS);
2867 if (msginfo->subject && *msginfo->subject) {
2871 buf = p = g_strdup(msginfo->subject);
2872 p += subject_get_prefix_length(p);
2873 memmove(buf, p, strlen(p) + 1);
2875 buf2 = g_strdup_printf("Re: %s", buf);
2876 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
2881 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
2883 if (to_ml && compose->ml_post) return;
2884 if (!to_all || compose->account->protocol == A_NNTP) return;
2886 if (compose->replyto) {
2887 Xstrdup_a(replyto, compose->replyto, return);
2888 extract_address(replyto);
2890 if (msginfo->from) {
2891 Xstrdup_a(from, msginfo->from, return);
2892 extract_address(from);
2895 if (replyto && from)
2896 cc_list = address_list_append_with_comments(cc_list, from);
2897 if (to_all && msginfo->folder &&
2898 msginfo->folder->prefs->enable_default_reply_to)
2899 cc_list = address_list_append_with_comments(cc_list,
2900 msginfo->folder->prefs->default_reply_to);
2901 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
2902 cc_list = address_list_append_with_comments(cc_list, compose->cc);
2904 to_table = g_hash_table_new(g_str_hash, g_str_equal);
2906 g_hash_table_insert(to_table, g_utf8_strdown(replyto, -1), GINT_TO_POINTER(1));
2907 if (compose->account) {
2908 g_hash_table_insert(to_table, g_utf8_strdown(compose->account->address, -1),
2909 GINT_TO_POINTER(1));
2911 /* remove address on To: and that of current account */
2912 for (cur = cc_list; cur != NULL; ) {
2913 GSList *next = cur->next;
2916 addr = g_utf8_strdown(cur->data, -1);
2917 extract_address(addr);
2919 if (GPOINTER_TO_INT(g_hash_table_lookup(to_table, addr)) == 1)
2920 cc_list = g_slist_remove(cc_list, cur->data);
2922 g_hash_table_insert(to_table, addr, GINT_TO_POINTER(1));
2926 hash_free_strings(to_table);
2927 g_hash_table_destroy(to_table);
2930 for (cur = cc_list; cur != NULL; cur = cur->next)
2931 compose_entry_append(compose, (gchar *)cur->data,
2933 slist_free_strings(cc_list);
2934 g_slist_free(cc_list);
2939 #define SET_ENTRY(entry, str) \
2942 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
2945 #define SET_ADDRESS(type, str) \
2948 compose_entry_append(compose, str, type); \
2951 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
2953 g_return_if_fail(msginfo != NULL);
2955 SET_ENTRY(subject_entry, msginfo->subject);
2956 SET_ENTRY(from_name, msginfo->from);
2957 SET_ADDRESS(COMPOSE_TO, msginfo->to);
2958 SET_ADDRESS(COMPOSE_CC, compose->cc);
2959 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
2960 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
2961 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
2962 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
2964 compose_update_priority_menu_item(compose);
2965 compose_update_privacy_system_menu_item(compose, FALSE);
2966 compose_show_first_last_header(compose, TRUE);
2972 static void compose_insert_sig(Compose *compose, gboolean replace)
2974 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2975 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2977 GtkTextIter iter, iter_end;
2979 gchar *search = NULL;
2980 gboolean prev_autowrap;
2981 gboolean found = FALSE, shift = FALSE;
2984 g_return_if_fail(compose->account != NULL);
2986 prev_autowrap = compose->autowrap;
2987 compose->autowrap = FALSE;
2989 g_signal_handlers_block_by_func(G_OBJECT(buffer),
2990 G_CALLBACK(compose_changed_cb),
2993 mark = gtk_text_buffer_get_insert(buffer);
2994 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2995 cur_pos = gtk_text_iter_get_offset (&iter);
2997 gtk_text_buffer_get_end_iter(buffer, &iter);
2999 search = compose->sig_str;
3001 if (replace && search) {
3002 GtkTextIter first_iter, start_iter, end_iter;
3004 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3006 if (compose->sig_str[0] == '\0')
3009 found = gtk_text_iter_forward_search(&first_iter,
3011 GTK_TEXT_SEARCH_TEXT_ONLY,
3012 &start_iter, &end_iter,
3016 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3020 if (replace && !found && search && strlen(search) > 2
3021 && search[0] == '\n' && search[1] == '\n') {
3027 g_free(compose->sig_str);
3028 compose->sig_str = compose_get_signature_str(compose);
3029 if (!compose->sig_str || (replace && !compose->account->auto_sig))
3030 compose->sig_str = g_strdup("");
3032 cur_pos = gtk_text_iter_get_offset(&iter);
3034 gtk_text_buffer_insert(buffer, &iter, compose->sig_str + 1, -1);
3036 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3038 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3039 gtk_text_iter_forward_char(&iter);
3040 gtk_text_iter_forward_char(&iter);
3041 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3042 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3044 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3045 cur_pos = gtk_text_buffer_get_char_count (buffer);
3047 /* put the cursor where it should be
3048 * either where the quote_fmt says, either before the signature */
3049 if (compose->set_cursor_pos < 0)
3050 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3052 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3053 compose->set_cursor_pos);
3055 gtk_text_buffer_place_cursor(buffer, &iter);
3056 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3057 G_CALLBACK(compose_changed_cb),
3060 compose->autowrap = prev_autowrap;
3061 if (compose->autowrap)
3062 compose_wrap_all(compose);
3065 static gchar *compose_get_signature_str(Compose *compose)
3067 gchar *sig_body = NULL;
3068 gchar *sig_str = NULL;
3069 gchar *utf8_sig_str = NULL;
3071 g_return_val_if_fail(compose->account != NULL, NULL);
3073 if (!compose->account->sig_path)
3076 if (compose->account->sig_type == SIG_FILE) {
3077 if (!is_file_or_fifo_exist(compose->account->sig_path)) {
3078 g_warning("can't open signature file: %s\n",
3079 compose->account->sig_path);
3084 if (compose->account->sig_type == SIG_COMMAND)
3085 sig_body = get_command_output(compose->account->sig_path);
3089 tmp = file_read_to_str(compose->account->sig_path);
3092 sig_body = normalize_newlines(tmp);
3096 if (compose->account->sig_sep) {
3097 sig_str = g_strconcat("\n\n", compose->account->sig_sep, "\n", sig_body,
3101 sig_str = g_strconcat("\n\n", sig_body, NULL);
3104 if (g_utf8_validate(sig_str, -1, NULL) == TRUE)
3105 utf8_sig_str = sig_str;
3107 utf8_sig_str = conv_codeset_strdup
3108 (sig_str, conv_get_locale_charset_str_no_utf8(),
3114 return utf8_sig_str;
3117 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3120 GtkTextBuffer *buffer;
3123 const gchar *cur_encoding;
3124 gchar buf[BUFFSIZE];
3127 gboolean prev_autowrap;
3128 gboolean badtxt = FALSE;
3130 g_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3132 if ((fp = g_fopen(file, "rb")) == NULL) {
3133 FILE_OP_ERROR(file, "fopen");
3134 return COMPOSE_INSERT_READ_ERROR;
3137 prev_autowrap = compose->autowrap;
3138 compose->autowrap = FALSE;
3140 text = GTK_TEXT_VIEW(compose->text);
3141 buffer = gtk_text_view_get_buffer(text);
3142 mark = gtk_text_buffer_get_insert(buffer);
3143 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3145 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3146 G_CALLBACK(text_inserted),
3149 cur_encoding = conv_get_locale_charset_str_no_utf8();
3151 while (fgets(buf, sizeof(buf), fp) != NULL) {
3154 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3155 str = g_strdup(buf);
3157 str = conv_codeset_strdup
3158 (buf, cur_encoding, CS_INTERNAL);
3161 /* strip <CR> if DOS/Windows file,
3162 replace <CR> with <LF> if Macintosh file. */
3165 if (len > 0 && str[len - 1] != '\n') {
3167 if (str[len] == '\r') str[len] = '\n';
3170 gtk_text_buffer_insert(buffer, &iter, str, -1);
3174 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3175 G_CALLBACK(text_inserted),
3177 compose->autowrap = prev_autowrap;
3178 if (compose->autowrap)
3179 compose_wrap_all(compose);
3184 return COMPOSE_INSERT_INVALID_CHARACTER;
3186 return COMPOSE_INSERT_SUCCESS;
3189 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3190 const gchar *filename,
3191 const gchar *content_type)
3199 GtkListStore *store;
3201 gboolean has_binary = FALSE;
3203 if (!is_file_exist(file)) {
3204 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3205 gboolean result = FALSE;
3206 if (file_from_uri && is_file_exist(file_from_uri)) {
3207 result = compose_attach_append(
3208 compose, file_from_uri,
3212 g_free(file_from_uri);
3215 alertpanel_error("File %s doesn't exist\n", filename);
3218 if ((size = get_file_size(file)) < 0) {
3219 alertpanel_error("Can't get file size of %s\n", filename);
3223 alertpanel_error(_("File %s is empty."), filename);
3226 if ((fp = g_fopen(file, "rb")) == NULL) {
3227 alertpanel_error(_("Can't read %s."), filename);
3232 ainfo = g_new0(AttachInfo, 1);
3233 auto_ainfo = g_auto_pointer_new_with_free
3234 (ainfo, (GFreeFunc) compose_attach_info_free);
3235 ainfo->file = g_strdup(file);
3238 ainfo->content_type = g_strdup(content_type);
3239 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3241 MsgFlags flags = {0, 0};
3243 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3244 ainfo->encoding = ENC_7BIT;
3246 ainfo->encoding = ENC_8BIT;
3248 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3249 if (msginfo && msginfo->subject)
3250 name = g_strdup(msginfo->subject);
3252 name = g_path_get_basename(filename ? filename : file);
3254 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3256 procmsg_msginfo_free(msginfo);
3258 if (!g_ascii_strncasecmp(content_type, "text", 4))
3259 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3261 ainfo->encoding = ENC_BASE64;
3262 name = g_path_get_basename(filename ? filename : file);
3263 ainfo->name = g_strdup(name);
3267 ainfo->content_type = procmime_get_mime_type(file);
3268 if (!ainfo->content_type) {
3269 ainfo->content_type =
3270 g_strdup("application/octet-stream");
3271 ainfo->encoding = ENC_BASE64;
3272 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text", 4))
3274 procmime_get_encoding_for_text_file(file, &has_binary);
3276 ainfo->encoding = ENC_BASE64;
3277 name = g_path_get_basename(filename ? filename : file);
3278 ainfo->name = g_strdup(name);
3282 if (ainfo->name != NULL
3283 && !strcmp(ainfo->name, ".")) {
3284 g_free(ainfo->name);
3288 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3289 g_free(ainfo->content_type);
3290 ainfo->content_type = g_strdup("application/octet-stream");
3294 size_text = to_human_readable(size);
3296 store = GTK_LIST_STORE(gtk_tree_view_get_model
3297 (GTK_TREE_VIEW(compose->attach_clist)));
3299 gtk_list_store_append(store, &iter);
3300 gtk_list_store_set(store, &iter,
3301 COL_MIMETYPE, ainfo->content_type,
3302 COL_SIZE, size_text,
3303 COL_NAME, ainfo->name,
3305 COL_AUTODATA, auto_ainfo,
3308 g_auto_pointer_free(auto_ainfo);
3312 static void compose_use_signing(Compose *compose, gboolean use_signing)
3314 GtkItemFactory *ifactory;
3315 GtkWidget *menuitem = NULL;
3317 compose->use_signing = use_signing;
3318 ifactory = gtk_item_factory_from_widget(compose->menubar);
3319 menuitem = gtk_item_factory_get_item
3320 (ifactory, "/Options/Sign");
3321 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
3325 static void compose_use_encryption(Compose *compose, gboolean use_encryption)
3327 GtkItemFactory *ifactory;
3328 GtkWidget *menuitem = NULL;
3330 compose->use_encryption = use_encryption;
3331 ifactory = gtk_item_factory_from_widget(compose->menubar);
3332 menuitem = gtk_item_factory_get_item
3333 (ifactory, "/Options/Encrypt");
3335 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
3339 #define NEXT_PART_NOT_CHILD(info) \
3341 node = info->node; \
3342 while (node->children) \
3343 node = g_node_last_child(node); \
3344 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3347 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3351 MimeInfo *firsttext = NULL;
3352 MimeInfo *encrypted = NULL;
3355 const gchar *partname = NULL;
3357 mimeinfo = procmime_scan_message(msginfo);
3358 if (!mimeinfo) return;
3360 if (mimeinfo->node->children == NULL) {
3361 procmime_mimeinfo_free_all(mimeinfo);
3365 /* find first content part */
3366 child = (MimeInfo *) mimeinfo->node->children->data;
3367 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3368 child = (MimeInfo *)child->node->children->data;
3370 if (child->type == MIMETYPE_TEXT) {
3372 debug_print("First text part found\n");
3373 } else if (compose->mode == COMPOSE_REEDIT &&
3374 child->type == MIMETYPE_APPLICATION &&
3375 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3376 encrypted = (MimeInfo *)child->node->parent->data;
3379 child = (MimeInfo *) mimeinfo->node->children->data;
3380 while (child != NULL) {
3383 if (child == encrypted) {
3384 /* skip this part of tree */
3385 NEXT_PART_NOT_CHILD(child);
3389 if (child->type == MIMETYPE_MULTIPART) {
3390 /* get the actual content */
3391 child = procmime_mimeinfo_next(child);
3395 if (child == firsttext) {
3396 child = procmime_mimeinfo_next(child);
3400 outfile = procmime_get_tmp_file_name(child);
3401 if ((err = procmime_get_part(outfile, child)) < 0)
3402 g_warning("Can't get the part of multipart message. (%s)", strerror(-err));
3404 gchar *content_type;
3406 content_type = procmime_get_content_type_str(child->type, child->subtype);
3408 /* if we meet a pgp signature, we don't attach it, but
3409 * we force signing. */
3410 if ((strcmp(content_type, "application/pgp-signature") &&
3411 strcmp(content_type, "application/pkcs7-signature") &&
3412 strcmp(content_type, "application/x-pkcs7-signature"))
3413 || compose->mode == COMPOSE_REDIRECT) {
3414 partname = procmime_mimeinfo_get_parameter(child, "filename");
3415 if (partname == NULL)
3416 partname = procmime_mimeinfo_get_parameter(child, "name");
3417 if (partname == NULL)
3419 compose_attach_append(compose, outfile,
3420 partname, content_type);
3422 compose_force_signing(compose, compose->account);
3424 g_free(content_type);
3427 NEXT_PART_NOT_CHILD(child);
3429 procmime_mimeinfo_free_all(mimeinfo);
3432 #undef NEXT_PART_NOT_CHILD
3437 WAIT_FOR_INDENT_CHAR,
3438 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3441 /* return indent length, we allow:
3442 indent characters followed by indent characters or spaces/tabs,
3443 alphabets and numbers immediately followed by indent characters,
3444 and the repeating sequences of the above
3445 If quote ends with multiple spaces, only the first one is included. */
3446 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3447 const GtkTextIter *start, gint *len)
3449 GtkTextIter iter = *start;
3453 IndentState state = WAIT_FOR_INDENT_CHAR;
3456 gint alnum_count = 0;
3457 gint space_count = 0;
3460 if (prefs_common.quote_chars == NULL) {
3464 while (!gtk_text_iter_ends_line(&iter)) {
3465 wc = gtk_text_iter_get_char(&iter);
3466 if (g_unichar_iswide(wc))
3468 clen = g_unichar_to_utf8(wc, ch);
3472 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
3473 is_space = g_unichar_isspace(wc);
3475 if (state == WAIT_FOR_INDENT_CHAR) {
3476 if (!is_indent && !g_unichar_isalnum(wc))
3479 quote_len += alnum_count + space_count + 1;
3480 alnum_count = space_count = 0;
3481 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
3484 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
3485 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
3489 else if (is_indent) {
3490 quote_len += alnum_count + space_count + 1;
3491 alnum_count = space_count = 0;
3494 state = WAIT_FOR_INDENT_CHAR;
3498 gtk_text_iter_forward_char(&iter);
3501 if (quote_len > 0 && space_count > 0)
3507 if (quote_len > 0) {
3509 gtk_text_iter_forward_chars(&iter, quote_len);
3510 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
3516 /* return TRUE if the line is itemized */
3517 static gboolean compose_is_itemized(GtkTextBuffer *buffer,
3518 const GtkTextIter *start)
3520 GtkTextIter iter = *start;
3525 if (gtk_text_iter_ends_line(&iter))
3529 wc = gtk_text_iter_get_char(&iter);
3530 if (!g_unichar_isspace(wc))
3532 gtk_text_iter_forward_char(&iter);
3533 if (gtk_text_iter_ends_line(&iter))
3537 clen = g_unichar_to_utf8(wc, ch);
3541 if (!strchr("*-+", ch[0]))
3544 gtk_text_iter_forward_char(&iter);
3545 if (gtk_text_iter_ends_line(&iter))
3547 wc = gtk_text_iter_get_char(&iter);
3548 if (g_unichar_isspace(wc))
3554 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
3555 const GtkTextIter *start,
3556 GtkTextIter *break_pos,
3560 GtkTextIter iter = *start, line_end = *start;
3561 PangoLogAttr *attrs;
3568 gboolean can_break = FALSE;
3569 gboolean do_break = FALSE;
3570 gboolean was_white = FALSE;
3571 gboolean prev_dont_break = FALSE;
3573 gtk_text_iter_forward_to_line_end(&line_end);
3574 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
3575 len = g_utf8_strlen(str, -1);
3576 /* g_print("breaking line: %d: %s (len = %d)\n",
3577 gtk_text_iter_get_line(&iter), str, len); */
3578 attrs = g_new(PangoLogAttr, len + 1);
3580 pango_default_break(str, -1, NULL, attrs, len + 1);
3584 /* skip quote and leading spaces */
3585 for (i = 0; *p != '\0' && i < len; i++) {
3588 wc = g_utf8_get_char(p);
3589 if (i >= quote_len && !g_unichar_isspace(wc))
3591 if (g_unichar_iswide(wc))
3593 else if (*p == '\t')
3597 p = g_utf8_next_char(p);
3600 for (; *p != '\0' && i < len; i++) {
3601 PangoLogAttr *attr = attrs + i;
3605 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
3608 was_white = attr->is_white;
3610 /* don't wrap URI */
3611 if ((uri_len = get_uri_len(p)) > 0) {
3613 if (pos > 0 && col > max_col) {
3623 wc = g_utf8_get_char(p);
3624 if (g_unichar_iswide(wc)) {
3626 if (prev_dont_break && can_break && attr->is_line_break)
3628 } else if (*p == '\t')
3632 if (pos > 0 && col > max_col) {
3637 if (*p == '-' || *p == '/')
3638 prev_dont_break = TRUE;
3640 prev_dont_break = FALSE;
3642 p = g_utf8_next_char(p);
3646 debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col);
3651 *break_pos = *start;
3652 gtk_text_iter_set_line_offset(break_pos, pos);
3657 static gboolean compose_join_next_line(Compose *compose,
3658 GtkTextBuffer *buffer,
3660 const gchar *quote_str)
3662 GtkTextIter iter_ = *iter, cur, prev, next, end;
3663 PangoLogAttr attrs[3];
3665 gchar *next_quote_str;
3668 gboolean keep_cursor = FALSE;
3670 if (!gtk_text_iter_forward_line(&iter_) ||
3671 gtk_text_iter_ends_line(&iter_))
3674 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
3676 if ((quote_str || next_quote_str) &&
3677 strcmp2(quote_str, next_quote_str) != 0) {
3678 g_free(next_quote_str);
3681 g_free(next_quote_str);
3684 if (quote_len > 0) {
3685 gtk_text_iter_forward_chars(&end, quote_len);
3686 if (gtk_text_iter_ends_line(&end))
3690 /* don't join itemized lines */
3691 if (compose_is_itemized(buffer, &end))
3694 /* don't join signature separator */
3695 if (compose_is_sig_separator(compose, buffer, &iter_))
3698 /* delete quote str */
3700 gtk_text_buffer_delete(buffer, &iter_, &end);
3702 /* don't join line breaks put by the user */
3704 gtk_text_iter_backward_char(&cur);
3705 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
3706 gtk_text_iter_forward_char(&cur);
3710 gtk_text_iter_forward_char(&cur);
3711 /* delete linebreak and extra spaces */
3712 while (gtk_text_iter_backward_char(&cur)) {
3713 wc1 = gtk_text_iter_get_char(&cur);
3714 if (!g_unichar_isspace(wc1))
3719 while (!gtk_text_iter_ends_line(&cur)) {
3720 wc1 = gtk_text_iter_get_char(&cur);
3721 if (!g_unichar_isspace(wc1))
3723 gtk_text_iter_forward_char(&cur);
3726 if (!gtk_text_iter_equal(&prev, &next)) {
3729 mark = gtk_text_buffer_get_insert(buffer);
3730 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
3731 if (gtk_text_iter_equal(&prev, &cur))
3733 gtk_text_buffer_delete(buffer, &prev, &next);
3737 /* insert space if required */
3738 gtk_text_iter_backward_char(&prev);
3739 wc1 = gtk_text_iter_get_char(&prev);
3740 wc2 = gtk_text_iter_get_char(&next);
3741 gtk_text_iter_forward_char(&next);
3742 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
3743 pango_default_break(str, -1, NULL, attrs, 3);
3744 if (!attrs[1].is_line_break ||
3745 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
3746 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
3748 gtk_text_iter_backward_char(&iter_);
3749 gtk_text_buffer_place_cursor(buffer, &iter_);
3758 #define ADD_TXT_POS(bp_, ep_, pti_) \
3759 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
3760 last = last->next; \
3761 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
3762 last->next = NULL; \
3764 g_warning("alloc error scanning URIs\n"); \
3767 static gboolean automatic_break = FALSE;
3768 static void compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
3770 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3771 GtkTextBuffer *buffer;
3772 GtkTextIter iter, break_pos, end_of_line;
3773 gchar *quote_str = NULL;
3775 gboolean wrap_quote = prefs_common.linewrap_quote;
3776 gboolean prev_autowrap = compose->autowrap;
3777 gint startq_offset = -1, noq_offset = -1;
3778 gint uri_start = -1, uri_stop = -1;
3779 gint nouri_start = -1, nouri_stop = -1;
3780 gint num_blocks = 0;
3781 gint quotelevel = -1;
3783 compose->autowrap = FALSE;
3785 buffer = gtk_text_view_get_buffer(text);
3786 undo_wrapping(compose->undostruct, TRUE);
3791 mark = gtk_text_buffer_get_insert(buffer);
3792 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3795 /* move to paragraph start */
3796 gtk_text_iter_set_line_offset(&iter, 0);
3797 if (gtk_text_iter_ends_line(&iter)) {
3798 while (gtk_text_iter_ends_line(&iter) &&
3799 gtk_text_iter_forward_line(&iter))
3802 while (gtk_text_iter_backward_line(&iter)) {
3803 if (gtk_text_iter_ends_line(&iter)) {
3804 gtk_text_iter_forward_line(&iter);
3810 /* go until paragraph end (empty line) */
3812 while (!gtk_text_iter_ends_line(&iter)) {
3813 gchar *scanpos = NULL;
3814 /* parse table - in order of priority */
3816 const gchar *needle; /* token */
3818 /* token search function */
3819 gchar *(*search) (const gchar *haystack,
3820 const gchar *needle);
3821 /* part parsing function */
3822 gboolean (*parse) (const gchar *start,
3823 const gchar *scanpos,
3827 /* part to URI function */
3828 gchar *(*build_uri) (const gchar *bp,
3832 static struct table parser[] = {
3833 {"http://", strcasestr, get_uri_part, make_uri_string},
3834 {"https://", strcasestr, get_uri_part, make_uri_string},
3835 {"ftp://", strcasestr, get_uri_part, make_uri_string},
3836 {"sftp://", strcasestr, get_uri_part, make_uri_string},
3837 {"www.", strcasestr, get_uri_part, make_http_string},
3838 {"mailto:", strcasestr, get_uri_part, make_uri_string},
3839 {"@", strcasestr, get_email_part, make_email_string}
3841 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
3842 gint last_index = PARSE_ELEMS;
3844 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
3847 if (!prev_autowrap && num_blocks == 0) {
3849 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3850 G_CALLBACK(text_inserted),
3853 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
3856 uri_start = uri_stop = -1;
3858 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
3861 debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str);
3862 if (startq_offset == -1)
3863 startq_offset = gtk_text_iter_get_offset(&iter);
3864 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
3865 if (quotelevel > 2) {
3866 /* recycle colors */
3867 if (prefs_common.recycle_quote_colors)
3876 if (startq_offset == -1)
3877 noq_offset = gtk_text_iter_get_offset(&iter);
3881 if (prev_autowrap == FALSE && !force && !wrap_quote) {
3884 if (compose_get_line_break_pos(buffer, &iter, &break_pos,
3885 prefs_common.linewrap_len,
3887 GtkTextIter prev, next, cur;
3889 if (prev_autowrap != FALSE || force) {
3890 automatic_break = TRUE;
3891 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
3892 automatic_break = FALSE;
3893 } else if (quote_str && wrap_quote) {
3894 automatic_break = TRUE;
3895 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
3896 automatic_break = FALSE;
3899 /* remove trailing spaces */
3901 gtk_text_iter_backward_char(&cur);
3903 while (!gtk_text_iter_starts_line(&cur)) {
3906 gtk_text_iter_backward_char(&cur);
3907 wc = gtk_text_iter_get_char(&cur);
3908 if (!g_unichar_isspace(wc))
3912 if (!gtk_text_iter_equal(&prev, &next)) {
3913 gtk_text_buffer_delete(buffer, &prev, &next);
3915 gtk_text_iter_forward_char(&break_pos);
3919 gtk_text_buffer_insert(buffer, &break_pos,
3923 compose_join_next_line(compose, buffer, &iter, quote_str);
3925 /* move iter to current line start */
3926 gtk_text_iter_set_line_offset(&iter, 0);
3933 /* move iter to next line start */
3938 if (!prev_autowrap && num_blocks > 0) {
3940 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3941 G_CALLBACK(text_inserted),
3945 while (!gtk_text_iter_ends_line(&end_of_line)) {
3946 gtk_text_iter_forward_char(&end_of_line);
3948 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
3950 nouri_start = gtk_text_iter_get_offset(&iter);
3951 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
3953 walk_pos = gtk_text_iter_get_offset(&iter);
3954 /* FIXME: this looks phony. scanning for anything in the parse table */
3955 for (n = 0; n < PARSE_ELEMS; n++) {
3958 tmp = parser[n].search(walk, parser[n].needle);
3960 if (scanpos == NULL || tmp < scanpos) {
3969 /* check if URI can be parsed */
3970 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
3971 (const gchar **)&ep, FALSE)
3972 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
3976 strlen(parser[last_index].needle);
3979 uri_start = walk_pos + (bp - o_walk);
3980 uri_stop = walk_pos + (ep - o_walk);
3984 gtk_text_iter_forward_line(&iter);
3987 if (startq_offset != -1) {
3988 GtkTextIter startquote, endquote;
3989 gtk_text_buffer_get_iter_at_offset(
3990 buffer, &startquote, startq_offset);
3993 switch (quotelevel) {
3994 case 0: gtk_text_buffer_apply_tag_by_name(
3995 buffer, "quote0", &startquote, &endquote);
3997 case 1: gtk_text_buffer_apply_tag_by_name(
3998 buffer, "quote1", &startquote, &endquote);
4000 case 2: gtk_text_buffer_apply_tag_by_name(
4001 buffer, "quote2", &startquote, &endquote);
4005 } else if (noq_offset != -1) {
4006 GtkTextIter startnoquote, endnoquote;
4007 gtk_text_buffer_get_iter_at_offset(
4008 buffer, &startnoquote, noq_offset);
4010 gtk_text_buffer_remove_tag_by_name(
4011 buffer, "quote0", &startnoquote, &endnoquote);
4012 gtk_text_buffer_remove_tag_by_name(
4013 buffer, "quote1", &startnoquote, &endnoquote);
4014 gtk_text_buffer_remove_tag_by_name(
4015 buffer, "quote2", &startnoquote, &endnoquote);
4020 GtkTextIter nouri_start_iter, nouri_end_iter;
4021 gtk_text_buffer_get_iter_at_offset(
4022 buffer, &nouri_start_iter, nouri_start);
4023 gtk_text_buffer_get_iter_at_offset(
4024 buffer, &nouri_end_iter, nouri_stop);
4025 gtk_text_buffer_remove_tag_by_name(
4026 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4028 if (uri_start > 0 && uri_stop > 0) {
4029 GtkTextIter uri_start_iter, uri_end_iter;
4030 gtk_text_buffer_get_iter_at_offset(
4031 buffer, &uri_start_iter, uri_start);
4032 gtk_text_buffer_get_iter_at_offset(
4033 buffer, &uri_end_iter, uri_stop);
4034 gtk_text_buffer_apply_tag_by_name(
4035 buffer, "link", &uri_start_iter, &uri_end_iter);
4041 undo_wrapping(compose->undostruct, FALSE);
4042 compose->autowrap = prev_autowrap;
4045 void compose_action_cb(void *data)
4047 Compose *compose = (Compose *)data;
4048 compose_wrap_all(compose);
4051 static void compose_wrap_all(Compose *compose)
4053 compose_wrap_all_full(compose, FALSE);
4056 static void compose_wrap_all_full(Compose *compose, gboolean force)
4058 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4059 GtkTextBuffer *buffer;
4062 buffer = gtk_text_view_get_buffer(text);
4064 gtk_text_buffer_get_start_iter(buffer, &iter);
4065 while (!gtk_text_iter_is_end(&iter))
4066 compose_beautify_paragraph(compose, &iter, force);
4070 static void compose_set_title(Compose *compose)
4076 edited = compose->modified ? _(" [Edited]") : "";
4078 subject = gtk_editable_get_chars(
4079 GTK_EDITABLE(compose->subject_entry), 0, -1);
4082 if (subject && strlen(subject))
4083 str = g_strdup_printf(_("%s - Compose message%s"),
4086 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4088 str = g_strdup(_("Compose message"));
4091 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4097 * compose_current_mail_account:
4099 * Find a current mail account (the currently selected account, or the
4100 * default account, if a news account is currently selected). If a
4101 * mail account cannot be found, display an error message.
4103 * Return value: Mail account, or NULL if not found.
4105 static PrefsAccount *
4106 compose_current_mail_account(void)
4110 if (cur_account && cur_account->protocol != A_NNTP)
4113 ac = account_get_default();
4114 if (!ac || ac->protocol == A_NNTP) {
4115 alertpanel_error(_("Account for sending mail is not specified.\n"
4116 "Please select a mail account before sending."));
4123 #define QUOTE_IF_REQUIRED(out, str) \
4125 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4129 len = strlen(str) + 3; \
4130 if ((__tmp = alloca(len)) == NULL) { \
4131 g_warning("can't allocate memory\n"); \
4132 g_string_free(header, TRUE); \
4135 g_snprintf(__tmp, len, "\"%s\"", str); \
4140 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4141 g_warning("can't allocate memory\n"); \
4142 g_string_free(header, TRUE); \
4145 strcpy(__tmp, str); \
4151 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4153 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4157 len = strlen(str) + 3; \
4158 if ((__tmp = alloca(len)) == NULL) { \
4159 g_warning("can't allocate memory\n"); \
4162 g_snprintf(__tmp, len, "\"%s\"", str); \
4167 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4168 g_warning("can't allocate memory\n"); \
4171 strcpy(__tmp, str); \
4177 static void compose_select_account(Compose *compose, PrefsAccount *account,
4180 GtkItemFactory *ifactory;
4183 g_return_if_fail(account != NULL);
4185 compose->account = account;
4187 if (account->name && *account->name) {
4189 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4190 from = g_strdup_printf("%s <%s>",
4191 buf, account->address);
4192 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4194 from = g_strdup_printf("<%s>",
4196 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4201 compose_set_title(compose);
4203 ifactory = gtk_item_factory_from_widget(compose->menubar);
4205 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4206 menu_set_active(ifactory, "/Options/Sign", TRUE);
4208 menu_set_active(ifactory, "/Options/Sign", FALSE);
4209 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4210 menu_set_active(ifactory, "/Options/Encrypt", TRUE);
4212 menu_set_active(ifactory, "/Options/Encrypt", FALSE);
4214 activate_privacy_system(compose, account, FALSE);
4216 if (!init && compose->mode != COMPOSE_REDIRECT) {
4217 undo_block(compose->undostruct);
4218 compose_insert_sig(compose, TRUE);
4219 undo_unblock(compose->undostruct);
4223 /* use account's dict info if set */
4224 if (compose->gtkaspell) {
4225 if (account->enable_default_dictionary)
4226 gtkaspell_change_dict(compose->gtkaspell,
4227 account->default_dictionary, FALSE);
4228 if (account->enable_default_alt_dictionary)
4229 gtkaspell_change_alt_dict(compose->gtkaspell,
4230 account->default_alt_dictionary);
4231 if (account->enable_default_dictionary
4232 || account->enable_default_alt_dictionary)
4233 compose_spell_menu_changed(compose);
4238 gboolean compose_check_for_valid_recipient(Compose *compose) {
4239 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
4240 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
4241 gboolean recipient_found = FALSE;
4245 /* free to and newsgroup list */
4246 slist_free_strings(compose->to_list);
4247 g_slist_free(compose->to_list);
4248 compose->to_list = NULL;
4250 slist_free_strings(compose->newsgroup_list);
4251 g_slist_free(compose->newsgroup_list);
4252 compose->newsgroup_list = NULL;
4254 /* search header entries for to and newsgroup entries */
4255 for (list = compose->header_list; list; list = list->next) {
4258 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_COMBO(((ComposeHeaderEntry *)list->data)->combo)->entry), 0, -1);
4259 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4262 if (entry[0] != '\0') {
4263 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
4264 if (!strcmp(header, prefs_common_translated_header_name(*strptr))) {
4265 compose->to_list = address_list_append(compose->to_list, entry);
4266 recipient_found = TRUE;
4269 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
4270 if (!strcmp(header, prefs_common_translated_header_name(*strptr))) {
4271 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
4272 recipient_found = TRUE;
4279 return recipient_found;
4282 static gboolean compose_check_for_set_recipients(Compose *compose)
4284 if (compose->account->set_autocc && compose->account->auto_cc) {
4285 gboolean found_other = FALSE;
4287 /* search header entries for to and newsgroup entries */
4288 for (list = compose->header_list; list; list = list->next) {
4291 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4292 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_COMBO(((ComposeHeaderEntry *)list->data)->combo)->entry), 0, -1);
4294 if (strcmp(entry, compose->account->auto_cc)
4295 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
4305 if (compose->batch) {
4306 gtk_widget_show_all(compose->window);
4308 aval = alertpanel(_("Send"),
4309 _("The only recipient is the default CC address. Send anyway?"),
4310 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4311 if (aval != G_ALERTALTERNATE)
4315 if (compose->account->set_autobcc && compose->account->auto_bcc) {
4316 gboolean found_other = FALSE;
4318 /* search header entries for to and newsgroup entries */
4319 for (list = compose->header_list; list; list = list->next) {
4322 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4323 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_COMBO(((ComposeHeaderEntry *)list->data)->combo)->entry), 0, -1);
4325 if (strcmp(entry, compose->account->auto_bcc)
4326 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
4336 if (compose->batch) {
4337 gtk_widget_show_all(compose->window);
4339 aval = alertpanel(_("Send"),
4340 _("The only recipient is the default BCC address. Send anyway?"),
4341 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4342 if (aval != G_ALERTALTERNATE)
4349 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
4353 if (compose_check_for_valid_recipient(compose) == FALSE) {
4354 if (compose->batch) {
4355 gtk_widget_show_all(compose->window);
4357 alertpanel_error(_("Recipient is not specified."));
4361 if (compose_check_for_set_recipients(compose) == FALSE) {
4365 if (!compose->batch) {
4366 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
4367 if (*str == '\0' && check_everything == TRUE &&
4368 compose->mode != COMPOSE_REDIRECT) {
4371 aval = alertpanel(_("Send"),
4372 _("Subject is empty. Send it anyway?"),
4373 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4374 if (aval != G_ALERTALTERNATE)
4379 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
4385 gint compose_send(Compose *compose)
4388 FolderItem *folder = NULL;
4390 gchar *msgpath = NULL;
4391 gboolean discard_window = FALSE;
4392 gchar *errstr = NULL;
4393 gchar *tmsgid = NULL;
4394 MainWindow *mainwin = mainwindow_get_mainwindow();
4395 gboolean queued_removed = FALSE;
4397 if (prefs_common.send_dialog_mode != SEND_DIALOG_ALWAYS
4398 || compose->batch == TRUE)
4399 discard_window = TRUE;
4401 compose_allow_user_actions (compose, FALSE);
4402 compose->sending = TRUE;
4404 if (compose_check_entries(compose, TRUE) == FALSE) {
4405 if (compose->batch) {
4406 gtk_widget_show_all(compose->window);
4412 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
4415 if (compose->batch) {
4416 gtk_widget_show_all(compose->window);
4419 alertpanel_error(_("Could not queue message for sending:\n\n"
4420 "Charset conversion failed."));
4421 } else if (val == -5) {
4422 alertpanel_error(_("Could not queue message for sending:\n\n"
4423 "Couldn't get recipient encryption key."));
4424 } else if (val == -6) {
4426 } else if (val == -3) {
4427 if (privacy_peek_error())
4428 alertpanel_error(_("Could not queue message for sending:\n\n"
4429 "Signature failed: %s"), privacy_get_error());
4430 } else if (val == -2 && errno != 0) {
4431 alertpanel_error(_("Could not queue message for sending:\n\n%s."), strerror(errno));
4433 alertpanel_error(_("Could not queue message for sending."));
4438 tmsgid = g_strdup(compose->msgid);
4439 if (discard_window) {
4440 compose->sending = FALSE;
4441 compose_close(compose);
4442 /* No more compose access in the normal codepath
4443 * after this point! */
4448 alertpanel_error(_("The message was queued but could not be "
4449 "sent.\nUse \"Send queued messages\" from "
4450 "the main window to retry."));
4451 if (!discard_window) {
4458 if (msgpath == NULL) {
4459 msgpath = folder_item_fetch_msg(folder, msgnum);
4460 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
4463 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
4467 if (!discard_window) {
4469 if (!queued_removed)
4470 folder_item_remove_msg(folder, msgnum);
4471 folder_item_scan(folder);
4473 /* make sure we delete that */
4474 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
4476 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
4477 folder_item_remove_msg(folder, tmp->msgnum);
4478 procmsg_msginfo_free(tmp);
4485 if (!queued_removed)
4486 folder_item_remove_msg(folder, msgnum);
4487 folder_item_scan(folder);
4489 /* make sure we delete that */
4490 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
4492 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
4493 folder_item_remove_msg(folder, tmp->msgnum);
4494 procmsg_msginfo_free(tmp);
4497 if (!discard_window) {
4498 compose->sending = FALSE;
4499 compose_allow_user_actions (compose, TRUE);
4500 compose_close(compose);
4504 gchar *tmp = g_strdup_printf(_("%s\nUse \"Send queued messages\" from "
4505 "the main window to retry."), errstr);
4507 alertpanel_error_log(tmp);
4510 alertpanel_error_log(_("The message was queued but could not be "
4511 "sent.\nUse \"Send queued messages\" from "
4512 "the main window to retry."));
4514 if (!discard_window) {
4523 toolbar_main_set_sensitive(mainwin);
4524 main_window_set_menu_sensitive(mainwin);
4530 compose_allow_user_actions (compose, TRUE);
4531 compose->sending = FALSE;
4532 compose->modified = TRUE;
4533 toolbar_main_set_sensitive(mainwin);
4534 main_window_set_menu_sensitive(mainwin);
4539 static gboolean compose_use_attach(Compose *compose)
4541 GtkTreeModel *model = gtk_tree_view_get_model
4542 (GTK_TREE_VIEW(compose->attach_clist));
4543 return gtk_tree_model_iter_n_children(model, NULL) > 0;
4546 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
4549 gchar buf[BUFFSIZE];
4551 gboolean first_to_address;
4552 gboolean first_cc_address;
4554 ComposeHeaderEntry *headerentry;
4555 const gchar *headerentryname;
4556 const gchar *cc_hdr;
4557 const gchar *to_hdr;
4559 debug_print("Writing redirect header\n");
4561 cc_hdr = prefs_common_translated_header_name("Cc:");
4562 to_hdr = prefs_common_translated_header_name("To:");
4564 first_to_address = TRUE;
4565 for (list = compose->header_list; list; list = list->next) {
4566 headerentry = ((ComposeHeaderEntry *)list->data);
4567 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(headerentry->combo)->entry));
4569 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
4570 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
4571 Xstrdup_a(str, entstr, return -1);
4573 if (str[0] != '\0') {
4574 compose_convert_header
4575 (compose, buf, sizeof(buf), str,
4576 strlen("Resent-To") + 2, TRUE);
4578 if (first_to_address) {
4579 fprintf(fp, "Resent-To: ");
4580 first_to_address = FALSE;
4584 fprintf(fp, "%s", buf);
4588 if (!first_to_address) {
4592 first_cc_address = TRUE;
4593 for (list = compose->header_list; list; list = list->next) {
4594 headerentry = ((ComposeHeaderEntry *)list->data);
4595 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(headerentry->combo)->entry));
4597 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
4598 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
4599 Xstrdup_a(str, strg, return -1);
4601 if (str[0] != '\0') {
4602 compose_convert_header
4603 (compose, buf, sizeof(buf), str,
4604 strlen("Resent-Cc") + 2, TRUE);
4606 if (first_cc_address) {
4607 fprintf(fp, "Resent-Cc: ");
4608 first_cc_address = FALSE;
4612 fprintf(fp, "%s", buf);
4616 if (!first_cc_address) {
4623 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
4625 gchar buf[BUFFSIZE];
4627 const gchar *entstr;
4628 /* struct utsname utsbuf; */
4630 g_return_val_if_fail(fp != NULL, -1);
4631 g_return_val_if_fail(compose->account != NULL, -1);
4632 g_return_val_if_fail(compose->account->address != NULL, -1);
4635 get_rfc822_date(buf, sizeof(buf));
4636 fprintf(fp, "Resent-Date: %s\n", buf);
4639 if (compose->account->name && *compose->account->name) {
4640 compose_convert_header
4641 (compose, buf, sizeof(buf), compose->account->name,
4642 strlen("From: "), TRUE);
4643 fprintf(fp, "Resent-From: %s <%s>\n",
4644 buf, compose->account->address);
4646 fprintf(fp, "Resent-From: %s\n", compose->account->address);
4649 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
4650 if (*entstr != '\0') {
4651 Xstrdup_a(str, entstr, return -1);
4654 compose_convert_header(compose, buf, sizeof(buf), str,
4655 strlen("Subject: "), FALSE);
4656 fprintf(fp, "Subject: %s\n", buf);
4660 /* Resent-Message-ID */
4661 if (compose->account->gen_msgid) {
4662 generate_msgid(buf, sizeof(buf));
4663 fprintf(fp, "Resent-Message-ID: <%s>\n", buf);
4664 compose->msgid = g_strdup(buf);
4667 compose_redirect_write_headers_from_headerlist(compose, fp);
4669 /* separator between header and body */
4675 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
4679 gchar buf[BUFFSIZE];
4681 gboolean skip = FALSE;
4682 gchar *not_included[]={
4683 "Return-Path:", "Delivered-To:", "Received:",
4684 "Subject:", "X-UIDL:", "AF:",
4685 "NF:", "PS:", "SRH:",
4686 "SFN:", "DSR:", "MID:",
4687 "CFG:", "PT:", "S:",
4688 "RQ:", "SSV:", "NSV:",
4689 "SSH:", "R:", "MAID:",
4690 "NAID:", "RMID:", "FMID:",
4691 "SCF:", "RRCPT:", "NG:",
4692 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
4693 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
4694 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
4695 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
4698 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
4699 FILE_OP_ERROR(compose->redirect_filename, "fopen");
4703 while (procheader_get_one_field_asis(buf, sizeof(buf), fp) != -1) {
4705 for (i = 0; not_included[i] != NULL; i++) {
4706 if (g_ascii_strncasecmp(buf, not_included[i],
4707 strlen(not_included[i])) == 0) {
4714 if (fputs(buf, fdest) == -1)
4717 if (!prefs_common.redirect_keep_from) {
4718 if (g_ascii_strncasecmp(buf, "From:",
4719 strlen("From:")) == 0) {
4720 fputs(" (by way of ", fdest);
4721 if (compose->account->name
4722 && *compose->account->name) {
4723 compose_convert_header
4724 (compose, buf, sizeof(buf),
4725 compose->account->name,
4728 fprintf(fdest, "%s <%s>",
4730 compose->account->address);
4732 fprintf(fdest, "%s",
4733 compose->account->address);
4738 if (fputs("\n", fdest) == -1)
4742 compose_redirect_write_headers(compose, fdest);
4744 while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
4745 if (fwrite(buf, sizeof(gchar), len, fdest) != len)
4758 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
4760 GtkTextBuffer *buffer;
4761 GtkTextIter start, end;
4764 const gchar *out_codeset;
4765 EncodingType encoding;
4766 MimeInfo *mimemsg, *mimetext;
4769 if (action == COMPOSE_WRITE_FOR_SEND)
4770 attach_parts = TRUE;
4772 /* create message MimeInfo */
4773 mimemsg = procmime_mimeinfo_new();
4774 mimemsg->type = MIMETYPE_MESSAGE;
4775 mimemsg->subtype = g_strdup("rfc822");
4776 mimemsg->content = MIMECONTENT_MEM;
4777 mimemsg->tmp = TRUE; /* must free content later */
4778 mimemsg->data.mem = compose_get_header(compose);
4780 /* Create text part MimeInfo */
4781 /* get all composed text */
4782 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
4783 gtk_text_buffer_get_start_iter(buffer, &start);
4784 gtk_text_buffer_get_end_iter(buffer, &end);
4785 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
4786 if (is_ascii_str(chars)) {
4789 out_codeset = CS_US_ASCII;
4790 encoding = ENC_7BIT;
4792 const gchar *src_codeset = CS_INTERNAL;
4794 out_codeset = conv_get_charset_str(compose->out_encoding);
4797 gchar *test_conv_global_out = NULL;
4798 gchar *test_conv_reply = NULL;
4800 /* automatic mode. be automatic. */
4801 codeconv_set_strict(TRUE);
4803 out_codeset = conv_get_outgoing_charset_str();
4805 debug_print("trying to convert to %s\n", out_codeset);
4806 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
4809 if (!test_conv_global_out && compose->orig_charset
4810 && strcmp(compose->orig_charset, CS_US_ASCII)) {
4811 out_codeset = compose->orig_charset;
4812 debug_print("failure; trying to convert to %s\n", out_codeset);
4813 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
4816 if (!test_conv_global_out && !test_conv_reply) {
4818 out_codeset = CS_INTERNAL;
4819 debug_print("failure; finally using %s\n", out_codeset);
4821 g_free(test_conv_global_out);
4822 g_free(test_conv_reply);
4823 codeconv_set_strict(FALSE);
4826 if (!g_ascii_strcasecmp(out_codeset, CS_US_ASCII))
4827 out_codeset = CS_ISO_8859_1;
4829 if (prefs_common.encoding_method == CTE_BASE64)
4830 encoding = ENC_BASE64;
4831 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
4832 encoding = ENC_QUOTED_PRINTABLE;
4833 else if (prefs_common.encoding_method == CTE_8BIT)
4834 encoding = ENC_8BIT;
4836 encoding = procmime_get_encoding_for_charset(out_codeset);
4838 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
4839 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
4841 if (action == COMPOSE_WRITE_FOR_SEND) {
4842 codeconv_set_strict(TRUE);
4843 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
4844 codeconv_set_strict(FALSE);
4850 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
4851 "to the specified %s charset.\n"
4852 "Send it as %s?"), out_codeset, src_codeset);
4853 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL, _("+_Send"), NULL, FALSE,
4854 NULL, ALERT_ERROR, G_ALERTDEFAULT);
4857 if (aval != G_ALERTALTERNATE) {
4862 out_codeset = src_codeset;
4868 out_codeset = src_codeset;
4874 if (encoding == ENC_8BIT || encoding == ENC_7BIT) {
4875 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
4876 strstr(buf, "\nFrom ") != NULL) {
4877 encoding = ENC_QUOTED_PRINTABLE;
4881 mimetext = procmime_mimeinfo_new();
4882 mimetext->content = MIMECONTENT_MEM;
4883 mimetext->tmp = TRUE; /* must free content later */
4884 /* dup'ed because procmime_encode_content can turn it into a tmpfile
4885 * and free the data, which we need later. */
4886 mimetext->data.mem = g_strdup(buf);
4887 mimetext->type = MIMETYPE_TEXT;
4888 mimetext->subtype = g_strdup("plain");
4889 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
4890 g_strdup(out_codeset));
4892 /* protect trailing spaces when signing message */
4893 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
4894 privacy_system_can_sign(compose->privacy_system)) {
4895 encoding = ENC_QUOTED_PRINTABLE;
4898 debug_print("main text: %d bytes encoded as %s in %d\n",
4899 strlen(buf), out_codeset, encoding);
4901 /* check for line length limit */
4902 if (action == COMPOSE_WRITE_FOR_SEND &&
4903 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
4904 check_line_length(buf, 1000, &line) < 0) {
4908 msg = g_strdup_printf
4909 (_("Line %d exceeds the line length limit (998 bytes).\n"
4910 "The contents of the message might be broken on the way to the delivery.\n"
4912 "Send it anyway?"), line + 1);
4913 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
4915 if (aval != G_ALERTALTERNATE) {
4921 if (encoding != ENC_UNKNOWN)
4922 procmime_encode_content(mimetext, encoding);
4924 /* append attachment parts */
4925 if (compose_use_attach(compose) && attach_parts) {
4926 MimeInfo *mimempart;
4927 gchar *boundary = NULL;
4928 mimempart = procmime_mimeinfo_new();
4929 mimempart->content = MIMECONTENT_EMPTY;
4930 mimempart->type = MIMETYPE_MULTIPART;
4931 mimempart->subtype = g_strdup("mixed");
4935 boundary = generate_mime_boundary(NULL);
4936 } while (strstr(buf, boundary) != NULL);
4938 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
4941 mimetext->disposition = DISPOSITIONTYPE_INLINE;
4943 g_node_append(mimempart->node, mimetext->node);
4944 g_node_append(mimemsg->node, mimempart->node);
4946 compose_add_attachments(compose, mimempart);
4948 g_node_append(mimemsg->node, mimetext->node);
4952 /* sign message if sending */
4953 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
4954 privacy_system_can_sign(compose->privacy_system))
4955 if (!privacy_sign(compose->privacy_system, mimemsg, compose->account))
4958 procmime_write_mimeinfo(mimemsg, fp);
4960 procmime_mimeinfo_free_all(mimemsg);
4965 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
4967 GtkTextBuffer *buffer;
4968 GtkTextIter start, end;
4973 if ((fp = g_fopen(file, "wb")) == NULL) {
4974 FILE_OP_ERROR(file, "fopen");
4978 /* chmod for security */
4979 if (change_file_mode_rw(fp, file) < 0) {
4980 FILE_OP_ERROR(file, "chmod");
4981 g_warning("can't change file mode\n");
4984 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
4985 gtk_text_buffer_get_start_iter(buffer, &start);
4986 gtk_text_buffer_get_end_iter(buffer, &end);
4987 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
4989 chars = conv_codeset_strdup
4990 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
4993 if (!chars) return -1;
4996 len = strlen(chars);
4997 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
4998 FILE_OP_ERROR(file, "fwrite");
5007 if (fclose(fp) == EOF) {
5008 FILE_OP_ERROR(file, "fclose");
5015 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5018 MsgInfo *msginfo = compose->targetinfo;
5020 g_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5021 if (!msginfo) return -1;
5023 if (!force && MSG_IS_LOCKED(msginfo->flags))
5026 item = msginfo->folder;
5027 g_return_val_if_fail(item != NULL, -1);
5029 if (procmsg_msg_exist(msginfo) &&
5030 (folder_has_parent_of_type(item, F_QUEUE) ||
5031 folder_has_parent_of_type(item, F_DRAFT)
5032 || msginfo == compose->autosaved_draft)) {
5033 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5034 g_warning("can't remove the old message\n");
5042 static void compose_remove_draft(Compose *compose)
5045 MsgInfo *msginfo = compose->targetinfo;
5046 drafts = account_get_special_folder(compose->account, F_DRAFT);
5048 if (procmsg_msg_exist(msginfo)) {
5049 folder_item_remove_msg(drafts, msginfo->msgnum);
5054 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5055 gboolean remove_reedit_target)
5057 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5060 static gboolean compose_warn_encryption(Compose *compose)
5062 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5063 AlertValue val = G_ALERTALTERNATE;
5065 if (warning == NULL)
5068 val = alertpanel_full(_("Encryption warning"), warning,
5069 GTK_STOCK_CANCEL, _("+C_ontinue"), NULL,
5070 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5071 if (val & G_ALERTDISABLE) {
5072 val &= ~G_ALERTDISABLE;
5073 if (val == G_ALERTALTERNATE)
5074 privacy_inhibit_encrypt_warning(compose->privacy_system,
5078 if (val == G_ALERTALTERNATE) {
5085 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
5086 gchar **msgpath, gboolean check_subject,
5087 gboolean remove_reedit_target)
5094 static gboolean lock = FALSE;
5095 PrefsAccount *mailac = NULL, *newsac = NULL;
5097 debug_print("queueing message...\n");
5098 g_return_val_if_fail(compose->account != NULL, -1);
5102 if (compose_check_entries(compose, check_subject) == FALSE) {
5104 if (compose->batch) {
5105 gtk_widget_show_all(compose->window);
5110 if (!compose->to_list && !compose->newsgroup_list) {
5111 g_warning("can't get recipient list.");
5116 if (compose->to_list) {
5117 if (compose->account->protocol != A_NNTP)
5118 mailac = compose->account;
5119 else if (cur_account && cur_account->protocol != A_NNTP)
5120 mailac = cur_account;
5121 else if (!(mailac = compose_current_mail_account())) {
5123 alertpanel_error(_("No account for sending mails available!"));
5128 if (compose->newsgroup_list) {
5129 if (compose->account->protocol == A_NNTP)
5130 newsac = compose->account;
5131 else if (!newsac->protocol != A_NNTP) {
5133 alertpanel_error(_("No account for posting news available!"));
5138 /* write queue header */
5139 tmp = g_strdup_printf("%s%cqueue.%p", get_tmp_dir(),
5140 G_DIR_SEPARATOR, compose);
5141 if ((fp = g_fopen(tmp, "wb")) == NULL) {
5142 FILE_OP_ERROR(tmp, "fopen");
5148 if (change_file_mode_rw(fp, tmp) < 0) {
5149 FILE_OP_ERROR(tmp, "chmod");
5150 g_warning("can't change file mode\n");
5153 /* queueing variables */
5154 fprintf(fp, "AF:\n");
5155 fprintf(fp, "NF:0\n");
5156 fprintf(fp, "PS:10\n");
5157 fprintf(fp, "SRH:1\n");
5158 fprintf(fp, "SFN:\n");
5159 fprintf(fp, "DSR:\n");
5161 fprintf(fp, "MID:<%s>\n", compose->msgid);
5163 fprintf(fp, "MID:\n");
5164 fprintf(fp, "CFG:\n");
5165 fprintf(fp, "PT:0\n");
5166 fprintf(fp, "S:%s\n", compose->account->address);
5167 fprintf(fp, "RQ:\n");
5169 fprintf(fp, "SSV:%s\n", mailac->smtp_server);
5171 fprintf(fp, "SSV:\n");
5173 fprintf(fp, "NSV:%s\n", newsac->nntp_server);
5175 fprintf(fp, "NSV:\n");
5176 fprintf(fp, "SSH:\n");
5177 /* write recepient list */
5178 if (compose->to_list) {
5179 fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data);
5180 for (cur = compose->to_list->next; cur != NULL;
5182 fprintf(fp, ",<%s>", (gchar *)cur->data);
5185 /* write newsgroup list */
5186 if (compose->newsgroup_list) {
5188 fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data);
5189 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
5190 fprintf(fp, ",%s", (gchar *)cur->data);
5193 /* Sylpheed account IDs */
5195 fprintf(fp, "MAID:%d\n", mailac->account_id);
5197 fprintf(fp, "NAID:%d\n", newsac->account_id);
5200 if (compose->privacy_system != NULL) {
5201 fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system);
5202 fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing);
5203 if (compose->use_encryption) {
5205 if (!compose_warn_encryption(compose)) {
5212 if (mailac && mailac->encrypt_to_self) {
5213 GSList *tmp_list = g_slist_copy(compose->to_list);
5214 tmp_list = g_slist_append(tmp_list, compose->account->address);
5215 encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
5216 g_slist_free(tmp_list);
5218 encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
5220 if (encdata != NULL) {
5221 if (strcmp(encdata, "_DONT_ENCRYPT_")) {
5222 fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption);
5223 fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
5225 } /* else we finally dont want to encrypt */
5227 fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption);
5228 /* and if encdata was null, it means there's been a problem in
5240 /* Save copy folder */
5241 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
5242 gchar *savefolderid;
5244 savefolderid = gtk_editable_get_chars(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
5245 fprintf(fp, "SCF:%s\n", savefolderid);
5246 g_free(savefolderid);
5248 /* Save copy folder */
5249 if (compose->return_receipt) {
5250 fprintf(fp, "RRCPT:1\n");
5252 /* Message-ID of message replying to */
5253 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
5256 folderid = folder_item_get_identifier(compose->replyinfo->folder);
5257 fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid);
5260 /* Message-ID of message forwarding to */
5261 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
5264 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
5265 fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid);
5269 /* end of headers */
5270 fprintf(fp, "X-Claws-End-Special-Headers: 1\n");
5272 if (compose->redirect_filename != NULL) {
5273 if (compose_redirect_write_to_file(compose, fp) < 0) {
5282 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
5287 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
5291 if (fclose(fp) == EOF) {
5292 FILE_OP_ERROR(tmp, "fclose");
5299 if (item && *item) {
5302 queue = account_get_special_folder(compose->account, F_QUEUE);
5305 g_warning("can't find queue folder\n");
5311 folder_item_scan(queue);
5312 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
5313 g_warning("can't queue the message\n");
5320 if (msgpath == NULL) {
5326 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
5327 compose_remove_reedit_target(compose, FALSE);
5330 if ((msgnum != NULL) && (item != NULL)) {
5338 static void compose_add_attachments(Compose *compose, MimeInfo *parent)
5341 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
5343 struct stat statbuf;
5344 gchar *type, *subtype;
5345 GtkTreeModel *model;
5348 model = gtk_tree_view_get_model(tree_view);
5350 if (!gtk_tree_model_get_iter_first(model, &iter))
5353 gtk_tree_model_get(model, &iter,
5357 mimepart = procmime_mimeinfo_new();
5358 mimepart->content = MIMECONTENT_FILE;
5359 mimepart->data.filename = g_strdup(ainfo->file);
5360 mimepart->tmp = FALSE; /* or we destroy our attachment */
5361 mimepart->offset = 0;
5363 stat(ainfo->file, &statbuf);
5364 mimepart->length = statbuf.st_size;
5366 type = g_strdup(ainfo->content_type);
5368 if (!strchr(type, '/')) {
5370 type = g_strdup("application/octet-stream");
5373 subtype = strchr(type, '/') + 1;
5374 *(subtype - 1) = '\0';
5375 mimepart->type = procmime_get_media_type(type);
5376 mimepart->subtype = g_strdup(subtype);
5379 if (mimepart->type == MIMETYPE_MESSAGE &&
5380 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
5381 mimepart->disposition = DISPOSITIONTYPE_INLINE;
5384 g_hash_table_insert(mimepart->typeparameters,
5385 g_strdup("name"), g_strdup(ainfo->name));
5386 g_hash_table_insert(mimepart->dispositionparameters,
5387 g_strdup("filename"), g_strdup(ainfo->name));
5388 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
5392 if (compose->use_signing) {
5393 if (ainfo->encoding == ENC_7BIT)
5394 ainfo->encoding = ENC_QUOTED_PRINTABLE;
5395 else if (ainfo->encoding == ENC_8BIT)
5396 ainfo->encoding = ENC_BASE64;
5399 procmime_encode_content(mimepart, ainfo->encoding);
5401 g_node_append(parent->node, mimepart->node);
5402 } while (gtk_tree_model_iter_next(model, &iter));
5405 #define IS_IN_CUSTOM_HEADER(header) \
5406 (compose->account->add_customhdr && \
5407 custom_header_find(compose->account->customhdr_list, header) != NULL)
5409 static void compose_add_headerfield_from_headerlist(Compose *compose,
5411 const gchar *fieldname,
5412 const gchar *seperator)
5414 gchar *str, *fieldname_w_colon;
5415 gboolean add_field = FALSE;
5417 ComposeHeaderEntry *headerentry;
5418 const gchar *headerentryname;
5419 const gchar *trans_fieldname;
5422 if (IS_IN_CUSTOM_HEADER(fieldname))
5425 debug_print("Adding %s-fields\n", fieldname);
5427 fieldstr = g_string_sized_new(64);
5429 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
5430 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
5432 for (list = compose->header_list; list; list = list->next) {
5433 headerentry = ((ComposeHeaderEntry *)list->data);
5434 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(headerentry->combo)->entry));
5436 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
5437 str = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
5439 if (str[0] != '\0') {
5441 g_string_append(fieldstr, seperator);
5442 g_string_append(fieldstr, str);
5451 buf = g_new0(gchar, fieldstr->len * 4 + 256);
5452 compose_convert_header
5453 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
5454 strlen(fieldname) + 2, TRUE);
5455 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
5459 g_free(fieldname_w_colon);
5460 g_string_free(fieldstr, TRUE);
5465 static gchar *compose_get_header(Compose *compose)
5467 gchar buf[BUFFSIZE];
5468 const gchar *entry_str;
5472 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
5474 gchar *from_name = NULL, *from_address = NULL;
5477 g_return_val_if_fail(compose->account != NULL, NULL);
5478 g_return_val_if_fail(compose->account->address != NULL, NULL);
5480 header = g_string_sized_new(64);
5483 get_rfc822_date(buf, sizeof(buf));
5484 g_string_append_printf(header, "Date: %s\n", buf);
5488 if (compose->account->name && *compose->account->name) {
5490 QUOTE_IF_REQUIRED(buf, compose->account->name);
5491 tmp = g_strdup_printf("%s <%s>",
5492 buf, compose->account->address);
5494 tmp = g_strdup_printf("%s",
5495 compose->account->address);
5497 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
5498 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
5500 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
5501 from_address = g_strdup(compose->account->address);
5503 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5504 /* extract name and address */
5505 if (strstr(spec, " <") && strstr(spec, ">")) {
5506 from_address = g_strdup(strrchr(spec, '<')+1);
5507 *(strrchr(from_address, '>')) = '\0';
5508 from_name = g_strdup(spec);
5509 *(strrchr(from_name, '<')) = '\0';
5512 from_address = g_strdup(spec);
5519 if (from_name && *from_name) {
5520 compose_convert_header
5521 (compose, buf, sizeof(buf), from_name,
5522 strlen("From: "), TRUE);
5523 QUOTE_IF_REQUIRED(name, buf);
5525 g_string_append_printf(header, "From: %s <%s>\n",
5526 name, from_address);
5528 g_string_append_printf(header, "From: %s\n", from_address);
5531 g_free(from_address);
5534 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
5537 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
5540 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
5543 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
5546 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
5548 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
5551 compose_convert_header(compose, buf, sizeof(buf), str,
5552 strlen("Subject: "), FALSE);
5553 g_string_append_printf(header, "Subject: %s\n", buf);
5559 if (compose->account->gen_msgid) {
5560 generate_msgid(buf, sizeof(buf));
5561 g_string_append_printf(header, "Message-ID: <%s>\n", buf);
5562 compose->msgid = g_strdup(buf);
5565 if (compose->remove_references == FALSE) {
5567 if (compose->inreplyto && compose->to_list)
5568 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
5571 if (compose->references)
5572 g_string_append_printf(header, "References: %s\n", compose->references);
5576 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
5579 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
5582 if (compose->account->organization &&
5583 strlen(compose->account->organization) &&
5584 !IS_IN_CUSTOM_HEADER("Organization")) {
5585 compose_convert_header(compose, buf, sizeof(buf),
5586 compose->account->organization,
5587 strlen("Organization: "), FALSE);
5588 g_string_append_printf(header, "Organization: %s\n", buf);
5591 /* Program version and system info */
5592 if (g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
5593 !compose->newsgroup_list) {
5594 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
5596 gtk_major_version, gtk_minor_version, gtk_micro_version,
5599 if (g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
5600 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
5602 gtk_major_version, gtk_minor_version, gtk_micro_version,
5606 /* custom headers */
5607 if (compose->account->add_customhdr) {
5610 for (cur = compose->account->customhdr_list; cur != NULL;
5612 CustomHeader *chdr = (CustomHeader *)cur->data;
5614 if (custom_header_is_allowed(chdr->name)) {
5615 compose_convert_header
5616 (compose, buf, sizeof(buf),
5617 chdr->value ? chdr->value : "",
5618 strlen(chdr->name) + 2, FALSE);
5619 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
5625 switch (compose->priority) {
5626 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
5627 "X-Priority: 1 (Highest)\n");
5629 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
5630 "X-Priority: 2 (High)\n");
5632 case PRIORITY_NORMAL: break;
5633 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
5634 "X-Priority: 4 (Low)\n");
5636 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
5637 "X-Priority: 5 (Lowest)\n");
5639 default: debug_print("compose: priority unknown : %d\n",
5643 /* Request Return Receipt */
5644 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To")) {
5645 if (compose->return_receipt) {
5646 if (compose->account->name
5647 && *compose->account->name) {
5648 compose_convert_header(compose, buf, sizeof(buf),
5649 compose->account->name,
5650 strlen("Disposition-Notification-To: "),
5652 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, compose->account->address);
5654 g_string_append_printf(header, "Disposition-Notification-To: %s\n", compose->account->address);
5658 /* get special headers */
5659 for (list = compose->header_list; list; list = list->next) {
5660 ComposeHeaderEntry *headerentry;
5663 gchar *headername_wcolon;
5664 const gchar *headername_trans;
5667 gboolean standard_header = FALSE;
5669 headerentry = ((ComposeHeaderEntry *)list->data);
5671 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(headerentry->combo)->entry)));
5672 if (strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
5677 if (!strstr(tmp, ":")) {
5678 headername_wcolon = g_strconcat(tmp, ":", NULL);
5679 headername = g_strdup(tmp);
5681 headername_wcolon = g_strdup(tmp);
5682 headername = g_strdup(strtok(tmp, ":"));
5686 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5687 Xstrdup_a(headervalue, entry_str, return NULL);
5688 subst_char(headervalue, '\r', ' ');
5689 subst_char(headervalue, '\n', ' ');
5690 string = std_headers;
5691 while (*string != NULL) {
5692 headername_trans = prefs_common_translated_header_name(*string);
5693 if (!strcmp(headername_trans, headername_wcolon))
5694 standard_header = TRUE;
5697 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
5698 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
5701 g_free(headername_wcolon);
5705 g_string_free(header, FALSE);
5710 #undef IS_IN_CUSTOM_HEADER
5712 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
5713 gint header_len, gboolean addr_field)
5715 gchar *tmpstr = NULL;
5716 const gchar *out_codeset = NULL;
5718 g_return_if_fail(src != NULL);
5719 g_return_if_fail(dest != NULL);
5721 if (len < 1) return;
5723 tmpstr = g_strdup(src);
5725 subst_char(tmpstr, '\n', ' ');
5726 subst_char(tmpstr, '\r', ' ');
5729 if (!g_utf8_validate(tmpstr, -1, NULL)) {
5730 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
5731 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
5736 codeconv_set_strict(TRUE);
5737 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
5738 conv_get_charset_str(compose->out_encoding));
5739 codeconv_set_strict(FALSE);
5741 if (!dest || *dest == '\0') {
5742 gchar *test_conv_global_out = NULL;
5743 gchar *test_conv_reply = NULL;
5745 /* automatic mode. be automatic. */
5746 codeconv_set_strict(TRUE);
5748 out_codeset = conv_get_outgoing_charset_str();
5750 debug_print("trying to convert to %s\n", out_codeset);
5751 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
5754 if (!test_conv_global_out && compose->orig_charset
5755 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5756 out_codeset = compose->orig_charset;
5757 debug_print("failure; trying to convert to %s\n", out_codeset);
5758 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
5761 if (!test_conv_global_out && !test_conv_reply) {
5763 out_codeset = CS_INTERNAL;
5764 debug_print("finally using %s\n", out_codeset);
5766 g_free(test_conv_global_out);
5767 g_free(test_conv_reply);
5768 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
5770 codeconv_set_strict(FALSE);
5775 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
5779 g_return_if_fail(user_data != NULL);
5781 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
5782 g_strstrip(address);
5783 if (*address != '\0') {
5784 gchar *name = procheader_get_fromname(address);
5785 extract_address(address);
5786 addressbook_add_contact(name, address, NULL);
5791 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
5793 GtkWidget *menuitem;
5796 g_return_if_fail(menu != NULL);
5797 g_return_if_fail(GTK_IS_MENU_SHELL(menu));
5799 menuitem = gtk_separator_menu_item_new();
5800 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
5801 gtk_widget_show(menuitem);
5803 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
5804 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
5806 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
5807 g_strstrip(address);
5808 if (*address == '\0') {
5809 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
5812 g_signal_connect(G_OBJECT(menuitem), "activate",
5813 G_CALLBACK(compose_add_to_addressbook_cb), entry);
5814 gtk_widget_show(menuitem);
5817 static void compose_create_header_entry(Compose *compose)
5819 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
5823 GList *combo_list = NULL;
5825 const gchar *header = NULL;
5826 ComposeHeaderEntry *headerentry;
5827 gboolean standard_header = FALSE;
5829 headerentry = g_new0(ComposeHeaderEntry, 1);
5832 combo = gtk_combo_new();
5834 while(*string != NULL) {
5835 combo_list = g_list_append(combo_list, (gchar*)prefs_common_translated_header_name(*string));
5838 gtk_combo_set_popdown_strings(GTK_COMBO(combo), combo_list);
5839 g_list_free(combo_list);
5840 gtk_editable_set_editable(GTK_EDITABLE(GTK_COMBO(combo)->entry), TRUE);
5841 g_signal_connect(G_OBJECT(GTK_COMBO(combo)->entry), "grab_focus",
5842 G_CALLBACK(compose_grab_focus_cb), compose);
5843 gtk_widget_show(combo);
5844 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
5845 compose->header_nextrow, compose->header_nextrow+1,
5846 GTK_SHRINK, GTK_FILL, 0, 0);
5847 if (compose->header_last) {
5848 const gchar *last_header_entry = gtk_entry_get_text(
5849 GTK_ENTRY(GTK_COMBO(compose->header_last->combo)->entry));
5851 while (*string != NULL) {
5852 if (!strcmp(*string, last_header_entry))
5853 standard_header = TRUE;
5856 if (standard_header)
5857 header = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(compose->header_last->combo)->entry));
5859 if (!compose->header_last || !standard_header) {
5860 switch(compose->account->protocol) {
5862 header = prefs_common_translated_header_name("Newsgroups:");
5865 header = prefs_common_translated_header_name("To:");
5870 gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(combo)->entry), header);
5872 g_signal_connect_after(G_OBJECT(GTK_COMBO(combo)->entry), "grab_focus",
5873 G_CALLBACK(compose_grab_focus_cb), compose);
5876 entry = gtk_entry_new();
5877 gtk_widget_show(entry);
5878 gtk_tooltips_set_tip(compose->tooltips, entry,
5879 _("Use <tab> to autocomplete from addressbook"), NULL);
5880 gtk_table_attach(GTK_TABLE(compose->header_table), entry, 1, 2,
5881 compose->header_nextrow, compose->header_nextrow+1,
5882 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
5884 g_signal_connect(G_OBJECT(entry), "key-press-event",
5885 G_CALLBACK(compose_headerentry_key_press_event_cb),
5887 g_signal_connect(G_OBJECT(entry), "changed",
5888 G_CALLBACK(compose_headerentry_changed_cb),
5890 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
5891 G_CALLBACK(compose_grab_focus_cb), compose);
5894 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
5895 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
5896 GDK_ACTION_COPY | GDK_ACTION_MOVE);
5897 g_signal_connect(G_OBJECT(entry), "drag_data_received",
5898 G_CALLBACK(compose_header_drag_received_cb),
5900 g_signal_connect(G_OBJECT(entry), "drag-drop",
5901 G_CALLBACK(compose_drag_drop),
5903 g_signal_connect(G_OBJECT(entry), "populate-popup",
5904 G_CALLBACK(compose_entry_popup_extend),
5907 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
5909 headerentry->compose = compose;
5910 headerentry->combo = combo;
5911 headerentry->entry = entry;
5912 headerentry->headernum = compose->header_nextrow;
5914 compose->header_nextrow++;
5915 compose->header_last = headerentry;
5916 compose->header_list =
5917 g_slist_append(compose->header_list,
5921 static void compose_add_header_entry(Compose *compose, const gchar *header, gchar *text)
5923 ComposeHeaderEntry *last_header;
5925 last_header = compose->header_last;
5927 gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(last_header->combo)->entry), header);
5928 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
5931 static void compose_remove_header_entries(Compose *compose)
5934 for (list = compose->header_list; list; list = list->next) {
5935 ComposeHeaderEntry *headerentry =
5936 (ComposeHeaderEntry *)list->data;
5937 gtk_widget_destroy(headerentry->combo);
5938 gtk_widget_destroy(headerentry->entry);
5939 g_free(headerentry);
5941 compose->header_last = NULL;
5942 g_slist_free(compose->header_list);
5943 compose->header_list = NULL;
5944 compose->header_nextrow = 1;
5945 compose_create_header_entry(compose);
5948 static GtkWidget *compose_create_header(Compose *compose)
5950 GtkWidget *from_optmenu_hbox;
5951 GtkWidget *header_scrolledwin;
5952 GtkWidget *header_table;
5956 /* header labels and entries */
5957 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
5958 gtk_widget_show(header_scrolledwin);
5959 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
5961 header_table = gtk_table_new(2, 2, FALSE);
5962 gtk_widget_show(header_table);
5963 gtk_container_set_border_width(GTK_CONTAINER(header_table), BORDER_WIDTH);
5964 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
5965 gtk_viewport_set_shadow_type(GTK_VIEWPORT(GTK_BIN(header_scrolledwin)->child), GTK_SHADOW_ETCHED_IN);
5968 /* option menu for selecting accounts */
5969 from_optmenu_hbox = compose_account_option_menu_create(compose);
5970 gtk_table_attach(GTK_TABLE(header_table), from_optmenu_hbox,
5971 0, 2, count, count + 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
5974 compose->header_table = header_table;
5975 compose->header_list = NULL;
5976 compose->header_nextrow = count;
5978 compose_create_header_entry(compose);
5980 compose->table = NULL;
5982 return header_scrolledwin ;
5985 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
5987 Compose *compose = (Compose *)data;
5988 GdkEventButton event;
5991 event.time = gtk_get_current_event_time();
5993 return attach_button_pressed(compose->attach_clist, &event, compose);
5996 static GtkWidget *compose_create_attach(Compose *compose)
5998 GtkWidget *attach_scrwin;
5999 GtkWidget *attach_clist;
6001 GtkListStore *store;
6002 GtkCellRenderer *renderer;
6003 GtkTreeViewColumn *column;
6004 GtkTreeSelection *selection;
6006 /* attachment list */
6007 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
6008 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
6009 GTK_POLICY_AUTOMATIC,
6010 GTK_POLICY_AUTOMATIC);
6011 gtk_widget_set_size_request(attach_scrwin, -1, 80);
6013 store = gtk_list_store_new(N_ATTACH_COLS,
6018 G_TYPE_AUTO_POINTER,
6020 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
6021 (GTK_TREE_MODEL(store)));
6022 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
6023 g_object_unref(store);
6025 renderer = gtk_cell_renderer_text_new();
6026 column = gtk_tree_view_column_new_with_attributes
6027 (_("Mime type"), renderer, "text",
6028 COL_MIMETYPE, NULL);
6029 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6031 renderer = gtk_cell_renderer_text_new();
6032 column = gtk_tree_view_column_new_with_attributes
6033 (_("Size"), renderer, "text",
6035 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6037 renderer = gtk_cell_renderer_text_new();
6038 column = gtk_tree_view_column_new_with_attributes
6039 (_("Name"), renderer, "text",
6041 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6043 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
6044 prefs_common.use_stripes_everywhere);
6045 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
6046 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
6048 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
6049 G_CALLBACK(attach_selected), compose);
6050 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
6051 G_CALLBACK(attach_button_pressed), compose);
6053 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
6054 G_CALLBACK(popup_attach_button_pressed), compose);
6056 gtk_widget_tap_and_hold_setup(GTK_WIDGET(attach_clist), NULL, NULL,
6057 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
6058 g_signal_connect(G_OBJECT(attach_clist), "tap-and-hold",
6059 G_CALLBACK(popup_attach_button_pressed), compose);
6061 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
6062 G_CALLBACK(attach_key_pressed), compose);
6065 gtk_drag_dest_set(attach_clist,
6066 GTK_DEST_DEFAULT_ALL, compose_mime_types,
6067 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6068 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6069 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
6070 G_CALLBACK(compose_attach_drag_received_cb),
6072 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
6073 G_CALLBACK(compose_drag_drop),
6076 compose->attach_scrwin = attach_scrwin;
6077 compose->attach_clist = attach_clist;
6079 return attach_scrwin;
6082 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
6083 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
6085 static GtkWidget *compose_create_others(Compose *compose)
6088 GtkWidget *savemsg_checkbtn;
6089 GtkWidget *savemsg_entry;
6090 GtkWidget *savemsg_select;
6093 gchar *folderidentifier;
6095 /* Table for settings */
6096 table = gtk_table_new(3, 1, FALSE);
6097 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
6098 gtk_widget_show(table);
6099 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
6102 /* Save Message to folder */
6103 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
6104 gtk_widget_show(savemsg_checkbtn);
6105 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6106 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6107 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
6109 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
6110 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
6112 savemsg_entry = gtk_entry_new();
6113 gtk_widget_show(savemsg_entry);
6114 gtk_table_attach_defaults(GTK_TABLE(table), savemsg_entry, 1, 2, rowcount, rowcount + 1);
6115 gtk_editable_set_editable(GTK_EDITABLE(savemsg_entry), prefs_common.savemsg);
6116 g_signal_connect_after(G_OBJECT(savemsg_entry), "grab_focus",
6117 G_CALLBACK(compose_grab_focus_cb), compose);
6118 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6119 folderidentifier = folder_item_get_identifier(account_get_special_folder
6120 (compose->account, F_OUTBOX));
6121 gtk_entry_set_text(GTK_ENTRY(savemsg_entry), folderidentifier);
6122 g_free(folderidentifier);
6125 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
6126 gtk_widget_show(savemsg_select);
6127 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6128 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
6129 G_CALLBACK(compose_savemsg_select_cb),
6134 compose->savemsg_checkbtn = savemsg_checkbtn;
6135 compose->savemsg_entry = savemsg_entry;
6140 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
6142 gtk_editable_set_editable(GTK_EDITABLE(compose->savemsg_entry),
6143 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
6146 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
6151 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL);
6154 path = folder_item_get_identifier(dest);
6156 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), path);
6160 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
6161 GdkAtom clip, GtkTextIter *insert_place);
6163 #define BLOCK_WRAP() { \
6164 prev_autowrap = compose->autowrap; \
6165 buffer = gtk_text_view_get_buffer( \
6166 GTK_TEXT_VIEW(compose->text)); \
6167 compose->autowrap = FALSE; \
6169 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
6170 G_CALLBACK(compose_changed_cb), \
6172 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
6173 G_CALLBACK(text_inserted), \
6176 #define UNBLOCK_WRAP() { \
6177 compose->autowrap = prev_autowrap; \
6178 if (compose->autowrap) \
6179 compose_wrap_all(compose); \
6181 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
6182 G_CALLBACK(compose_changed_cb), \
6184 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
6185 G_CALLBACK(text_inserted), \
6190 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
6194 GtkTextBuffer *buffer;
6196 if (event->button == 3) {
6198 GtkTextIter sel_start, sel_end;
6199 gboolean stuff_selected;
6201 /* move the cursor to allow GtkAspell to check the word
6202 * under the mouse */
6203 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6204 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6206 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6209 stuff_selected = gtk_text_buffer_get_selection_bounds(
6210 GTK_TEXT_VIEW(text)->buffer,
6211 &sel_start, &sel_end);
6213 gtk_text_buffer_place_cursor (GTK_TEXT_VIEW(text)->buffer, &iter);
6214 /* reselect stuff */
6216 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
6217 gtk_text_buffer_select_range(GTK_TEXT_VIEW(text)->buffer,
6218 &sel_start, &sel_end);
6220 return FALSE; /* pass the event so that the right-click goes through */
6223 if (event->button == 2) {
6228 /* get the middle-click position to paste at the correct place */
6229 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6230 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6232 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6235 entry_paste_clipboard(compose, text,
6236 prefs_common.linewrap_pastes,
6237 GDK_SELECTION_PRIMARY, &iter);
6245 static void compose_spell_menu_changed(void *data)
6247 Compose *compose = (Compose *)data;
6249 GtkWidget *menuitem;
6250 GtkWidget *parent_item;
6251 GtkMenu *menu = GTK_MENU(gtk_menu_new());
6252 GtkItemFactory *ifactory = gtk_item_factory_from_widget(compose->menubar);
6255 if (compose->gtkaspell == NULL)
6258 parent_item = gtk_item_factory_get_item(ifactory,
6259 "/Spelling/Options");
6261 /* setting the submenu removes /Spelling/Options from the factory
6262 * so we need to save it */
6264 if (parent_item == NULL) {
6265 parent_item = compose->aspell_options_menu;
6266 gtk_menu_item_remove_submenu(GTK_MENU_ITEM(parent_item));
6268 compose->aspell_options_menu = parent_item;
6270 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
6272 spell_menu = g_slist_reverse(spell_menu);
6273 for (items = spell_menu;
6274 items; items = items->next) {
6275 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
6276 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
6277 gtk_widget_show(GTK_WIDGET(menuitem));
6279 g_slist_free(spell_menu);
6281 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
6286 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
6288 Compose *compose = (Compose *)data;
6289 GdkEventButton event;
6292 event.time = gtk_get_current_event_time();
6294 return text_clicked(compose->text, &event, compose);
6297 static gboolean compose_force_window_origin = TRUE;
6298 static Compose *compose_create(PrefsAccount *account,
6307 GtkWidget *handlebox;
6309 GtkWidget *notebook;
6314 GtkWidget *subject_hbox;
6315 GtkWidget *subject_frame;
6316 GtkWidget *subject_entry;
6320 GtkWidget *edit_vbox;
6321 GtkWidget *ruler_hbox;
6323 GtkWidget *scrolledwin;
6325 GtkTextBuffer *buffer;
6326 GtkClipboard *clipboard;
6328 UndoMain *undostruct;
6330 gchar *titles[N_ATTACH_COLS];
6331 guint n_menu_entries;
6332 GtkWidget *popupmenu;
6333 GtkItemFactory *popupfactory;
6334 GtkItemFactory *ifactory;
6335 GtkWidget *tmpl_menu;
6337 GtkWidget *menuitem;
6340 GtkAspell * gtkaspell = NULL;
6343 static GdkGeometry geometry;
6345 g_return_val_if_fail(account != NULL, NULL);
6347 debug_print("Creating compose window...\n");
6348 compose = g_new0(Compose, 1);
6350 titles[COL_MIMETYPE] = _("MIME type");
6351 titles[COL_SIZE] = _("Size");
6352 titles[COL_NAME] = _("Name");
6354 compose->batch = batch;
6355 compose->account = account;
6356 compose->folder = folder;
6358 compose->mutex = g_mutex_new();
6359 compose->set_cursor_pos = -1;
6361 compose->tooltips = gtk_tooltips_new();
6363 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
6365 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
6366 gtk_widget_set_size_request(window, -1, prefs_common.compose_height);
6368 if (!geometry.max_width) {
6369 geometry.max_width = gdk_screen_width();
6370 geometry.max_height = gdk_screen_height();
6373 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
6374 &geometry, GDK_HINT_MAX_SIZE);
6375 if (!geometry.min_width) {
6376 geometry.min_width = 600;
6377 geometry.min_height = 480;
6379 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
6380 &geometry, GDK_HINT_MIN_SIZE);
6383 if (compose_force_window_origin)
6384 gtk_widget_set_uposition(window, prefs_common.compose_x,
6385 prefs_common.compose_y);
6387 g_signal_connect(G_OBJECT(window), "delete_event",
6388 G_CALLBACK(compose_delete_cb), compose);
6389 MANAGE_WINDOW_SIGNALS_CONNECT(window);
6390 gtk_widget_realize(window);
6392 gtkut_widget_set_composer_icon(window);
6394 vbox = gtk_vbox_new(FALSE, 0);
6395 gtk_container_add(GTK_CONTAINER(window), vbox);
6397 n_menu_entries = sizeof(compose_entries) / sizeof(compose_entries[0]);
6398 menubar = menubar_create(window, compose_entries,
6399 n_menu_entries, "<Compose>", compose);
6400 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
6402 if (prefs_common.toolbar_detachable) {
6403 handlebox = gtk_handle_box_new();
6405 handlebox = gtk_hbox_new(FALSE, 0);
6407 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
6409 gtk_widget_realize(handlebox);
6411 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, window,
6414 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
6418 vbox2 = gtk_vbox_new(FALSE, 2);
6419 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
6420 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
6423 notebook = gtk_notebook_new();
6424 gtk_widget_set_size_request(notebook, -1, 130);
6425 gtk_widget_show(notebook);
6427 /* header labels and entries */
6428 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6429 compose_create_header(compose),
6430 gtk_label_new_with_mnemonic(_("Hea_der")));
6431 /* attachment list */
6432 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6433 compose_create_attach(compose),
6434 gtk_label_new_with_mnemonic(_("_Attachments")));
6436 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6437 compose_create_others(compose),
6438 gtk_label_new_with_mnemonic(_("Othe_rs")));
6441 subject_hbox = gtk_hbox_new(FALSE, 0);
6442 gtk_widget_show(subject_hbox);
6444 subject_frame = gtk_frame_new(NULL);
6445 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
6446 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
6447 gtk_widget_show(subject_frame);
6449 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
6450 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
6451 gtk_widget_show(subject);
6453 label = gtk_label_new(_("Subject:"));
6454 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
6455 gtk_widget_show(label);
6457 subject_entry = gtk_entry_new();
6458 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
6459 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
6460 G_CALLBACK(compose_grab_focus_cb), compose);
6461 gtk_widget_show(subject_entry);
6462 compose->subject_entry = subject_entry;
6463 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
6465 edit_vbox = gtk_vbox_new(FALSE, 0);
6467 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
6470 ruler_hbox = gtk_hbox_new(FALSE, 0);
6471 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
6473 ruler = gtk_shruler_new();
6474 gtk_ruler_set_range(GTK_RULER(ruler), 0.0, 100.0, 1.0, 100.0);
6475 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
6479 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
6480 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
6481 GTK_POLICY_AUTOMATIC,
6482 GTK_POLICY_AUTOMATIC);
6483 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
6485 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
6486 gtk_widget_set_size_request(scrolledwin, prefs_common.compose_width, -1);
6488 text = gtk_text_view_new();
6489 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
6490 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
6491 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
6492 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
6493 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
6495 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
6497 g_signal_connect_after(G_OBJECT(text), "size_allocate",
6498 G_CALLBACK(compose_edit_size_alloc),
6500 g_signal_connect(G_OBJECT(buffer), "changed",
6501 G_CALLBACK(compose_changed_cb), compose);
6502 g_signal_connect(G_OBJECT(text), "grab_focus",
6503 G_CALLBACK(compose_grab_focus_cb), compose);
6504 g_signal_connect(G_OBJECT(buffer), "insert_text",
6505 G_CALLBACK(text_inserted), compose);
6506 g_signal_connect(G_OBJECT(text), "button_press_event",
6507 G_CALLBACK(text_clicked), compose);
6509 g_signal_connect(G_OBJECT(text), "popup-menu",
6510 G_CALLBACK(compose_popup_menu), compose);
6512 gtk_widget_tap_and_hold_setup(GTK_WIDGET(text), NULL, NULL,
6513 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
6514 g_signal_connect(G_OBJECT(text), "tap-and-hold",
6515 G_CALLBACK(compose_popup_menu), compose);
6517 g_signal_connect(G_OBJECT(subject_entry), "changed",
6518 G_CALLBACK(compose_changed_cb), compose);
6521 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6522 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6523 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6524 g_signal_connect(G_OBJECT(text), "drag_data_received",
6525 G_CALLBACK(compose_insert_drag_received_cb),
6527 g_signal_connect(G_OBJECT(text), "drag-drop",
6528 G_CALLBACK(compose_drag_drop),
6530 gtk_widget_show_all(vbox);
6532 /* pane between attach clist and text */
6533 paned = gtk_vpaned_new();
6534 gtk_paned_set_gutter_size(GTK_PANED(paned), 12);
6535 gtk_container_add(GTK_CONTAINER(vbox2), paned);
6537 if( maemo_mainwindow_is_fullscreen(mainwindow_get_mainwindow()->window) )
6538 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 300 : 280);
6540 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 250 : 230);
6542 gtk_paned_add1(GTK_PANED(paned), notebook);
6543 gtk_paned_add2(GTK_PANED(paned), edit_vbox);
6544 gtk_widget_show_all(paned);
6547 if (prefs_common.textfont) {
6548 PangoFontDescription *font_desc;
6550 font_desc = pango_font_description_from_string
6551 (prefs_common.textfont);
6553 gtk_widget_modify_font(text, font_desc);
6554 pango_font_description_free(font_desc);
6558 n_entries = sizeof(compose_popup_entries) /
6559 sizeof(compose_popup_entries[0]);
6560 popupmenu = menu_create_items(compose_popup_entries, n_entries,
6561 "<Compose>", &popupfactory,
6564 ifactory = gtk_item_factory_from_widget(menubar);
6565 menu_set_sensitive(ifactory, "/Edit/Undo", FALSE);
6566 menu_set_sensitive(ifactory, "/Edit/Redo", FALSE);
6567 menu_set_sensitive(ifactory, "/Options/Remove references", FALSE);
6569 tmpl_menu = gtk_item_factory_get_item(ifactory, "/Tools/Template");
6571 undostruct = undo_init(text);
6572 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
6575 address_completion_start(window);
6577 compose->window = window;
6578 compose->vbox = vbox;
6579 compose->menubar = menubar;
6580 compose->handlebox = handlebox;
6582 compose->vbox2 = vbox2;
6584 compose->paned = paned;
6586 compose->notebook = notebook;
6587 compose->edit_vbox = edit_vbox;
6588 compose->ruler_hbox = ruler_hbox;
6589 compose->ruler = ruler;
6590 compose->scrolledwin = scrolledwin;
6591 compose->text = text;
6593 compose->focused_editable = NULL;
6595 compose->popupmenu = popupmenu;
6596 compose->popupfactory = popupfactory;
6598 compose->tmpl_menu = tmpl_menu;
6600 compose->mode = mode;
6601 compose->rmode = mode;
6603 compose->targetinfo = NULL;
6604 compose->replyinfo = NULL;
6605 compose->fwdinfo = NULL;
6607 compose->replyto = NULL;
6609 compose->bcc = NULL;
6610 compose->followup_to = NULL;
6612 compose->ml_post = NULL;
6614 compose->inreplyto = NULL;
6615 compose->references = NULL;
6616 compose->msgid = NULL;
6617 compose->boundary = NULL;
6619 compose->autowrap = prefs_common.autowrap;
6621 compose->use_signing = FALSE;
6622 compose->use_encryption = FALSE;
6623 compose->privacy_system = NULL;
6625 compose->modified = FALSE;
6627 compose->return_receipt = FALSE;
6629 compose->to_list = NULL;
6630 compose->newsgroup_list = NULL;
6632 compose->undostruct = undostruct;
6634 compose->sig_str = NULL;
6636 compose->exteditor_file = NULL;
6637 compose->exteditor_pid = -1;
6638 compose->exteditor_tag = -1;
6639 compose->draft_timeout_tag = -2; /* inhibit auto-drafting while loading */
6642 menu_set_sensitive(ifactory, "/Spelling", FALSE);
6643 if (mode != COMPOSE_REDIRECT) {
6644 if (prefs_common.enable_aspell && prefs_common.dictionary &&
6645 strcmp(prefs_common.dictionary, "")) {
6646 gtkaspell = gtkaspell_new(prefs_common.aspell_path,
6647 prefs_common.dictionary,
6648 prefs_common.alt_dictionary,
6649 conv_get_locale_charset_str(),
6650 prefs_common.misspelled_col,
6651 prefs_common.check_while_typing,
6652 prefs_common.recheck_when_changing_dict,
6653 prefs_common.use_alternate,
6654 prefs_common.use_both_dicts,
6655 GTK_TEXT_VIEW(text),
6656 GTK_WINDOW(compose->window),
6657 compose_spell_menu_changed,
6660 alertpanel_error(_("Spell checker could not "
6662 gtkaspell_checkers_strerror());
6663 gtkaspell_checkers_reset_error();
6665 if (!gtkaspell_set_sug_mode(gtkaspell,
6666 prefs_common.aspell_sugmode)) {
6667 debug_print("Aspell: could not set "
6668 "suggestion mode %s\n",
6669 gtkaspell_checkers_strerror());
6670 gtkaspell_checkers_reset_error();
6673 menu_set_sensitive(ifactory, "/Spelling", TRUE);
6677 compose->gtkaspell = gtkaspell;
6678 compose_spell_menu_changed(compose);
6681 compose_select_account(compose, account, TRUE);
6683 menu_set_active(ifactory, "/Edit/Auto wrapping", prefs_common.autowrap);
6684 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
6685 compose_entry_append(compose, account->auto_cc, COMPOSE_CC);
6687 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
6688 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC);
6690 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
6691 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO);
6693 menu_set_sensitive(ifactory, "/Options/Reply mode", compose->mode == COMPOSE_REPLY);
6695 if (account->protocol != A_NNTP)
6696 gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(compose->header_last->combo)->entry),
6697 prefs_common_translated_header_name("To:"));
6699 gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(compose->header_last->combo)->entry),
6700 prefs_common_translated_header_name("Newsgroups:"));
6702 addressbook_set_target_compose(compose);
6704 if (mode != COMPOSE_REDIRECT)
6705 compose_set_template_menu(compose);
6707 menuitem = gtk_item_factory_get_item(ifactory, "/Tools/Template");
6708 menu_set_sensitive(ifactory, "/Tools/Template", FALSE);
6711 compose_list = g_list_append(compose_list, compose);
6713 if (!prefs_common.show_ruler)
6714 gtk_widget_hide(ruler_hbox);
6716 menuitem = gtk_item_factory_get_item(ifactory, "/Tools/Show ruler");
6717 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
6718 prefs_common.show_ruler);
6721 compose->priority = PRIORITY_NORMAL;
6722 compose_update_priority_menu_item(compose);
6724 compose_set_out_encoding(compose);
6727 compose_update_actions_menu(compose);
6729 /* Privacy Systems menu */
6730 compose_update_privacy_systems_menu(compose);
6732 activate_privacy_system(compose, account, TRUE);
6733 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
6735 gtk_widget_realize(window);
6737 gtk_widget_show(window);
6739 maemo_window_full_screen_if_needed(GTK_WINDOW(window));
6740 maemo_connect_key_press_to_mainwindow(GTK_WINDOW(window));
6747 static GtkWidget *compose_account_option_menu_create(Compose *compose)
6752 GtkWidget *optmenubox;
6755 GtkWidget *from_name = NULL;
6757 gint num = 0, def_menu = 0;
6759 accounts = account_get_list();
6760 g_return_val_if_fail(accounts != NULL, NULL);
6762 optmenubox = gtk_event_box_new();
6763 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
6764 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
6766 hbox = gtk_hbox_new(FALSE, 6);
6767 from_name = gtk_entry_new();
6769 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
6770 G_CALLBACK(compose_grab_focus_cb), compose);
6772 for (; accounts != NULL; accounts = accounts->next, num++) {
6773 PrefsAccount *ac = (PrefsAccount *)accounts->data;
6774 gchar *name, *from = NULL;
6776 if (ac == compose->account) def_menu = num;
6778 name = g_markup_printf_escaped(_("From: <i>%s</i>"),
6781 if (ac == compose->account) {
6782 if (ac->name && *ac->name) {
6784 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
6785 from = g_strdup_printf("%s <%s>",
6787 gtk_entry_set_text(GTK_ENTRY(from_name), from);
6789 from = g_strdup_printf("%s",
6791 gtk_entry_set_text(GTK_ENTRY(from_name), from);
6794 COMBOBOX_ADD(menu, name, ac->account_id);
6799 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
6801 g_signal_connect(G_OBJECT(optmenu), "changed",
6802 G_CALLBACK(account_activated),
6804 g_signal_connect(G_OBJECT(from_name), "populate-popup",
6805 G_CALLBACK(compose_entry_popup_extend),
6808 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
6809 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
6811 gtk_tooltips_set_tip(compose->tooltips, optmenubox,
6812 _("Account to use for this email"), NULL);
6813 gtk_tooltips_set_tip(compose->tooltips, from_name,
6814 _("Sender address to be used"), NULL);
6816 compose->from_name = from_name;
6821 static void compose_set_priority_cb(gpointer data,
6825 Compose *compose = (Compose *) data;
6826 compose->priority = action;
6829 static void compose_reply_change_mode(gpointer data,
6833 Compose *compose = (Compose *) data;
6834 gboolean was_modified = compose->modified;
6836 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
6838 g_return_if_fail(compose->replyinfo != NULL);
6840 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
6842 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
6844 if (action == COMPOSE_REPLY_TO_ALL)
6846 if (action == COMPOSE_REPLY_TO_SENDER)
6848 if (action == COMPOSE_REPLY_TO_LIST)
6851 compose_remove_header_entries(compose);
6852 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
6853 if (compose->account->set_autocc && compose->account->auto_cc)
6854 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC);
6856 if (compose->account->set_autobcc && compose->account->auto_bcc)
6857 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC);
6859 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
6860 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO);
6861 compose_show_first_last_header(compose, TRUE);
6862 compose->modified = was_modified;
6863 compose_set_title(compose);
6866 static void compose_update_priority_menu_item(Compose * compose)
6868 GtkItemFactory *ifactory;
6869 GtkWidget *menuitem = NULL;
6871 ifactory = gtk_item_factory_from_widget(compose->menubar);
6873 switch (compose->priority) {
6874 case PRIORITY_HIGHEST:
6875 menuitem = gtk_item_factory_get_item
6876 (ifactory, "/Options/Priority/Highest");
6879 menuitem = gtk_item_factory_get_item
6880 (ifactory, "/Options/Priority/High");
6882 case PRIORITY_NORMAL:
6883 menuitem = gtk_item_factory_get_item
6884 (ifactory, "/Options/Priority/Normal");
6887 menuitem = gtk_item_factory_get_item
6888 (ifactory, "/Options/Priority/Low");
6890 case PRIORITY_LOWEST:
6891 menuitem = gtk_item_factory_get_item
6892 (ifactory, "/Options/Priority/Lowest");
6895 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
6898 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
6900 Compose *compose = (Compose *) data;
6902 GtkItemFactory *ifactory;
6903 gboolean can_sign = FALSE, can_encrypt = FALSE;
6905 g_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
6907 if (!GTK_CHECK_MENU_ITEM(widget)->active)
6910 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
6911 g_free(compose->privacy_system);
6912 compose->privacy_system = NULL;
6913 if (systemid != NULL) {
6914 compose->privacy_system = g_strdup(systemid);
6916 can_sign = privacy_system_can_sign(systemid);
6917 can_encrypt = privacy_system_can_encrypt(systemid);
6920 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
6922 ifactory = gtk_item_factory_from_widget(compose->menubar);
6923 menu_set_sensitive(ifactory, "/Options/Sign", can_sign);
6924 menu_set_sensitive(ifactory, "/Options/Encrypt", can_encrypt);
6927 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
6929 static gchar *branch_path = "/Options/Privacy System";
6930 GtkItemFactory *ifactory;
6931 GtkWidget *menuitem = NULL;
6933 gboolean can_sign = FALSE, can_encrypt = FALSE;
6934 gboolean found = FALSE;
6936 ifactory = gtk_item_factory_from_widget(compose->menubar);
6938 if (compose->privacy_system != NULL) {
6941 menuitem = gtk_item_factory_get_widget(ifactory, branch_path);
6942 g_return_if_fail(menuitem != NULL);
6944 amenu = GTK_MENU_SHELL(menuitem)->children;
6946 while (amenu != NULL) {
6947 GList *alist = amenu->next;
6949 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
6950 if (systemid != NULL) {
6951 if (strcmp(systemid, compose->privacy_system) == 0) {
6952 menuitem = GTK_WIDGET(amenu->data);
6954 can_sign = privacy_system_can_sign(systemid);
6955 can_encrypt = privacy_system_can_encrypt(systemid);
6959 } else if (strlen(compose->privacy_system) == 0) {
6960 menuitem = GTK_WIDGET(amenu->data);
6963 can_encrypt = FALSE;
6970 if (menuitem != NULL)
6971 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
6973 if (warn && !found && strlen(compose->privacy_system)) {
6974 gchar *tmp = g_strdup_printf(
6975 _("The privacy system '%s' cannot be loaded. You "
6976 "will not be able to sign or encrypt this message."),
6977 compose->privacy_system);
6978 alertpanel_warning(tmp);
6983 menu_set_sensitive(ifactory, "/Options/Sign", can_sign);
6984 menu_set_sensitive(ifactory, "/Options/Encrypt", can_encrypt);
6987 static void compose_set_out_encoding(Compose *compose)
6989 GtkItemFactoryEntry *entry;
6990 GtkItemFactory *ifactory;
6991 CharSet out_encoding;
6992 gchar *path, *p, *q;
6995 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
6996 ifactory = gtk_item_factory_from_widget(compose->menubar);
6998 for (entry = compose_entries; entry->callback != compose_address_cb;
7000 if (entry->callback == compose_set_encoding_cb &&
7001 (CharSet)entry->callback_action == out_encoding) {
7002 p = q = path = g_strdup(entry->path);
7014 item = gtk_item_factory_get_item(ifactory, path);
7015 gtk_widget_activate(item);
7022 static void compose_set_template_menu(Compose *compose)
7024 GSList *tmpl_list, *cur;
7028 tmpl_list = template_get_config();
7030 menu = gtk_menu_new();
7032 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
7033 Template *tmpl = (Template *)cur->data;
7035 item = gtk_menu_item_new_with_label(tmpl->name);
7036 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
7037 g_signal_connect(G_OBJECT(item), "activate",
7038 G_CALLBACK(compose_template_activate_cb),
7040 g_object_set_data(G_OBJECT(item), "template", tmpl);
7041 gtk_widget_show(item);
7044 gtk_widget_show(menu);
7045 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
7048 void compose_update_actions_menu(Compose *compose)
7050 GtkItemFactory *ifactory;
7052 ifactory = gtk_item_factory_from_widget(compose->menubar);
7053 action_update_compose_menu(ifactory, "/Tools/Actions", compose);
7056 static void compose_update_privacy_systems_menu(Compose *compose)
7058 static gchar *branch_path = "/Options/Privacy System";
7059 GtkItemFactory *ifactory;
7060 GtkWidget *menuitem;
7061 GSList *systems, *cur;
7064 GtkWidget *system_none;
7067 ifactory = gtk_item_factory_from_widget(compose->menubar);
7069 /* remove old entries */
7070 menuitem = gtk_item_factory_get_widget(ifactory, branch_path);
7071 g_return_if_fail(menuitem != NULL);
7073 amenu = GTK_MENU_SHELL(menuitem)->children->next;
7074 while (amenu != NULL) {
7075 GList *alist = amenu->next;
7076 gtk_widget_destroy(GTK_WIDGET(amenu->data));
7080 system_none = gtk_item_factory_get_widget(ifactory,
7081 "/Options/Privacy System/None");
7083 g_signal_connect(G_OBJECT(system_none), "activate",
7084 G_CALLBACK(compose_set_privacy_system_cb), compose);
7086 systems = privacy_get_system_ids();
7087 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
7088 gchar *systemid = cur->data;
7090 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
7091 widget = gtk_radio_menu_item_new_with_label(group,
7092 privacy_system_get_name(systemid));
7093 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
7094 g_strdup(systemid), g_free);
7095 g_signal_connect(G_OBJECT(widget), "activate",
7096 G_CALLBACK(compose_set_privacy_system_cb), compose);
7098 gtk_menu_append(GTK_MENU(system_none->parent), widget);
7099 gtk_widget_show(widget);
7102 g_slist_free(systems);
7105 void compose_reflect_prefs_all(void)
7110 for (cur = compose_list; cur != NULL; cur = cur->next) {
7111 compose = (Compose *)cur->data;
7112 compose_set_template_menu(compose);
7116 void compose_reflect_prefs_pixmap_theme(void)
7121 for (cur = compose_list; cur != NULL; cur = cur->next) {
7122 compose = (Compose *)cur->data;
7123 toolbar_update(TOOLBAR_COMPOSE, compose);
7127 static const gchar *compose_quote_char_from_context(Compose *compose)
7129 const gchar *qmark = NULL;
7131 g_return_val_if_fail(compose != NULL, NULL);
7133 switch (compose->mode) {
7134 /* use forward-specific quote char */
7135 case COMPOSE_FORWARD:
7136 case COMPOSE_FORWARD_AS_ATTACH:
7137 case COMPOSE_FORWARD_INLINE:
7138 if (compose->folder && compose->folder->prefs &&
7139 compose->folder->prefs->forward_with_format)
7140 qmark = compose->folder->prefs->forward_quotemark;
7141 else if (compose->account->forward_with_format)
7142 qmark = compose->account->forward_quotemark;
7144 qmark = prefs_common.fw_quotemark;
7147 /* use reply-specific quote char in all other modes */
7149 if (compose->folder && compose->folder->prefs &&
7150 compose->folder->prefs->reply_with_format)
7151 qmark = compose->folder->prefs->reply_quotemark;
7152 else if (compose->account->reply_with_format)
7153 qmark = compose->account->reply_quotemark;
7155 qmark = prefs_common.quotemark;
7159 if (qmark == NULL || *qmark == '\0')
7165 static void compose_template_apply(Compose *compose, Template *tmpl,
7169 GtkTextBuffer *buffer;
7173 gchar *parsed_str = NULL;
7174 gint cursor_pos = 0;
7175 const gchar *err_msg = _("Template body format error at line %d.");
7178 /* process the body */
7180 text = GTK_TEXT_VIEW(compose->text);
7181 buffer = gtk_text_view_get_buffer(text);
7184 qmark = compose_quote_char_from_context(compose);
7186 if (compose->replyinfo != NULL) {
7189 gtk_text_buffer_set_text(buffer, "", -1);
7190 mark = gtk_text_buffer_get_insert(buffer);
7191 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7193 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
7194 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
7196 } else if (compose->fwdinfo != NULL) {
7199 gtk_text_buffer_set_text(buffer, "", -1);
7200 mark = gtk_text_buffer_get_insert(buffer);
7201 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7203 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
7204 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
7207 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
7209 GtkTextIter start, end;
7212 gtk_text_buffer_get_start_iter(buffer, &start);
7213 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
7214 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
7216 /* clear the buffer now */
7218 gtk_text_buffer_set_text(buffer, "", -1);
7220 parsed_str = compose_quote_fmt(compose, dummyinfo,
7221 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
7222 procmsg_msginfo_free( dummyinfo );
7228 gtk_text_buffer_set_text(buffer, "", -1);
7229 mark = gtk_text_buffer_get_insert(buffer);
7230 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7233 if (replace && parsed_str && compose->account->auto_sig)
7234 compose_insert_sig(compose, FALSE);
7236 if (replace && parsed_str) {
7237 gtk_text_buffer_get_start_iter(buffer, &iter);
7238 gtk_text_buffer_place_cursor(buffer, &iter);
7242 cursor_pos = quote_fmt_get_cursor_pos();
7243 compose->set_cursor_pos = cursor_pos;
7244 if (cursor_pos == -1)
7246 gtk_text_buffer_get_start_iter(buffer, &iter);
7247 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
7248 gtk_text_buffer_place_cursor(buffer, &iter);
7251 /* process the other fields */
7253 compose_template_apply_fields(compose, tmpl);
7254 quote_fmt_reset_vartable();
7255 compose_changed_cb(NULL, compose);
7258 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
7260 MsgInfo* dummyinfo = NULL;
7261 MsgInfo *msginfo = NULL;
7264 if (compose->replyinfo != NULL)
7265 msginfo = compose->replyinfo;
7266 else if (compose->fwdinfo != NULL)
7267 msginfo = compose->fwdinfo;
7269 dummyinfo = compose_msginfo_new_from_compose(compose);
7270 msginfo = dummyinfo;
7273 if (tmpl->to && *tmpl->to != '\0') {
7275 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7276 compose->gtkaspell);
7278 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7280 quote_fmt_scan_string(tmpl->to);
7283 buf = quote_fmt_get_buffer();
7285 alertpanel_error(_("Template To format error."));
7287 compose_entry_append(compose, buf, COMPOSE_TO);
7291 if (tmpl->cc && *tmpl->cc != '\0') {
7293 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7294 compose->gtkaspell);
7296 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7298 quote_fmt_scan_string(tmpl->cc);
7301 buf = quote_fmt_get_buffer();
7303 alertpanel_error(_("Template Cc format error."));
7305 compose_entry_append(compose, buf, COMPOSE_CC);
7309 if (tmpl->bcc && *tmpl->bcc != '\0') {
7311 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7312 compose->gtkaspell);
7314 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7316 quote_fmt_scan_string(tmpl->bcc);
7319 buf = quote_fmt_get_buffer();
7321 alertpanel_error(_("Template Bcc format error."));
7323 compose_entry_append(compose, buf, COMPOSE_BCC);
7327 /* process the subject */
7328 if (tmpl->subject && *tmpl->subject != '\0') {
7330 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7331 compose->gtkaspell);
7333 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7335 quote_fmt_scan_string(tmpl->subject);
7338 buf = quote_fmt_get_buffer();
7340 alertpanel_error(_("Template subject format error."));
7342 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
7346 procmsg_msginfo_free( dummyinfo );
7349 static void compose_destroy(Compose *compose)
7351 GtkTextBuffer *buffer;
7352 GtkClipboard *clipboard;
7354 compose_list = g_list_remove(compose_list, compose);
7356 if (compose->updating) {
7357 debug_print("danger, not destroying anything now\n");
7358 compose->deferred_destroy = TRUE;
7361 /* NOTE: address_completion_end() does nothing with the window
7362 * however this may change. */
7363 address_completion_end(compose->window);
7365 slist_free_strings(compose->to_list);
7366 g_slist_free(compose->to_list);
7367 slist_free_strings(compose->newsgroup_list);
7368 g_slist_free(compose->newsgroup_list);
7369 slist_free_strings(compose->header_list);
7370 g_slist_free(compose->header_list);
7372 procmsg_msginfo_free(compose->targetinfo);
7373 procmsg_msginfo_free(compose->replyinfo);
7374 procmsg_msginfo_free(compose->fwdinfo);
7376 g_free(compose->replyto);
7377 g_free(compose->cc);
7378 g_free(compose->bcc);
7379 g_free(compose->newsgroups);
7380 g_free(compose->followup_to);
7382 g_free(compose->ml_post);
7384 g_free(compose->inreplyto);
7385 g_free(compose->references);
7386 g_free(compose->msgid);
7387 g_free(compose->boundary);
7389 g_free(compose->redirect_filename);
7390 if (compose->undostruct)
7391 undo_destroy(compose->undostruct);
7393 g_free(compose->sig_str);
7395 g_free(compose->exteditor_file);
7397 g_free(compose->orig_charset);
7399 g_free(compose->privacy_system);
7401 if (addressbook_get_target_compose() == compose)
7402 addressbook_set_target_compose(NULL);
7405 if (compose->gtkaspell) {
7406 gtkaspell_delete(compose->gtkaspell);
7407 compose->gtkaspell = NULL;
7411 prefs_common.compose_width = compose->scrolledwin->allocation.width;
7412 prefs_common.compose_height = compose->window->allocation.height;
7414 if (!gtk_widget_get_parent(compose->paned))
7415 gtk_widget_destroy(compose->paned);
7416 gtk_widget_destroy(compose->popupmenu);
7418 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
7419 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7420 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
7422 gtk_widget_destroy(compose->window);
7423 toolbar_destroy(compose->toolbar);
7424 g_free(compose->toolbar);
7425 g_mutex_free(compose->mutex);
7429 static void compose_attach_info_free(AttachInfo *ainfo)
7431 g_free(ainfo->file);
7432 g_free(ainfo->content_type);
7433 g_free(ainfo->name);
7437 static void compose_attach_remove_selected(Compose *compose)
7439 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
7440 GtkTreeSelection *selection;
7442 GtkTreeModel *model;
7444 selection = gtk_tree_view_get_selection(tree_view);
7445 sel = gtk_tree_selection_get_selected_rows(selection, &model);
7450 for (cur = sel; cur != NULL; cur = cur->next) {
7451 GtkTreePath *path = cur->data;
7452 GtkTreeRowReference *ref = gtk_tree_row_reference_new
7455 gtk_tree_path_free(path);
7458 for (cur = sel; cur != NULL; cur = cur->next) {
7459 GtkTreeRowReference *ref = cur->data;
7460 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
7463 if (gtk_tree_model_get_iter(model, &iter, path))
7464 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
7466 gtk_tree_path_free(path);
7467 gtk_tree_row_reference_free(ref);
7473 static struct _AttachProperty
7476 GtkWidget *mimetype_entry;
7477 GtkWidget *encoding_optmenu;
7478 GtkWidget *path_entry;
7479 GtkWidget *filename_entry;
7481 GtkWidget *cancel_btn;
7484 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
7486 gtk_tree_path_free((GtkTreePath *)ptr);
7489 static void compose_attach_property(Compose *compose)
7491 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
7493 GtkComboBox *optmenu;
7494 GtkTreeSelection *selection;
7496 GtkTreeModel *model;
7499 static gboolean cancelled;
7501 /* only if one selected */
7502 selection = gtk_tree_view_get_selection(tree_view);
7503 if (gtk_tree_selection_count_selected_rows(selection) != 1)
7506 sel = gtk_tree_selection_get_selected_rows(selection, &model);
7510 path = (GtkTreePath *) sel->data;
7511 gtk_tree_model_get_iter(model, &iter, path);
7512 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
7515 g_list_foreach(sel, gtk_tree_path_free_, NULL);
7521 if (!attach_prop.window)
7522 compose_attach_property_create(&cancelled);
7523 gtk_widget_grab_focus(attach_prop.ok_btn);
7524 gtk_widget_show(attach_prop.window);
7525 manage_window_set_transient(GTK_WINDOW(attach_prop.window));
7527 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
7528 if (ainfo->encoding == ENC_UNKNOWN)
7529 combobox_select_by_data(optmenu, ENC_BASE64);
7531 combobox_select_by_data(optmenu, ainfo->encoding);
7533 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
7534 ainfo->content_type ? ainfo->content_type : "");
7535 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
7536 ainfo->file ? ainfo->file : "");
7537 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
7538 ainfo->name ? ainfo->name : "");
7541 const gchar *entry_text;
7543 gchar *cnttype = NULL;
7550 gtk_widget_hide(attach_prop.window);
7555 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
7556 if (*entry_text != '\0') {
7559 text = g_strstrip(g_strdup(entry_text));
7560 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
7561 cnttype = g_strdup(text);
7564 alertpanel_error(_("Invalid MIME type."));
7570 ainfo->encoding = combobox_get_active_data(optmenu);
7572 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
7573 if (*entry_text != '\0') {
7574 if (is_file_exist(entry_text) &&
7575 (size = get_file_size(entry_text)) > 0)
7576 file = g_strdup(entry_text);
7579 (_("File doesn't exist or is empty."));
7585 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
7586 if (*entry_text != '\0') {
7587 g_free(ainfo->name);
7588 ainfo->name = g_strdup(entry_text);
7592 g_free(ainfo->content_type);
7593 ainfo->content_type = cnttype;
7596 g_free(ainfo->file);
7602 /* update tree store */
7603 text = to_human_readable(ainfo->size);
7604 gtk_tree_model_get_iter(model, &iter, path);
7605 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
7606 COL_MIMETYPE, ainfo->content_type,
7608 COL_NAME, ainfo->name,
7614 gtk_tree_path_free(path);
7617 #define SET_LABEL_AND_ENTRY(str, entry, top) \
7619 label = gtk_label_new(str); \
7620 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
7621 GTK_FILL, 0, 0, 0); \
7622 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
7624 entry = gtk_entry_new(); \
7625 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
7626 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
7629 static void compose_attach_property_create(gboolean *cancelled)
7635 GtkWidget *mimetype_entry;
7638 GtkListStore *optmenu_menu;
7639 GtkWidget *path_entry;
7640 GtkWidget *filename_entry;
7643 GtkWidget *cancel_btn;
7644 GList *mime_type_list, *strlist;
7647 debug_print("Creating attach_property window...\n");
7649 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
7650 gtk_widget_set_size_request(window, 480, -1);
7651 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
7652 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
7653 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
7654 gtk_window_set_modal(GTK_WINDOW(window), TRUE);
7655 g_signal_connect(G_OBJECT(window), "delete_event",
7656 G_CALLBACK(attach_property_delete_event),
7658 g_signal_connect(G_OBJECT(window), "key_press_event",
7659 G_CALLBACK(attach_property_key_pressed),
7662 vbox = gtk_vbox_new(FALSE, 8);
7663 gtk_container_add(GTK_CONTAINER(window), vbox);
7665 table = gtk_table_new(4, 2, FALSE);
7666 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
7667 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
7668 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
7670 label = gtk_label_new(_("MIME type"));
7671 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
7673 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
7674 mimetype_entry = gtk_combo_new();
7675 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
7676 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
7678 /* stuff with list */
7679 mime_type_list = procmime_get_mime_type_list();
7681 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
7682 MimeType *type = (MimeType *) mime_type_list->data;
7685 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
7687 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
7690 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
7691 (GCompareFunc)strcmp2);
7694 gtk_combo_set_popdown_strings(GTK_COMBO(mimetype_entry), strlist);
7696 for (mime_type_list = strlist; mime_type_list != NULL;
7697 mime_type_list = mime_type_list->next)
7698 g_free(mime_type_list->data);
7699 g_list_free(strlist);
7701 mimetype_entry = GTK_COMBO(mimetype_entry)->entry;
7703 label = gtk_label_new(_("Encoding"));
7704 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
7706 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
7708 hbox = gtk_hbox_new(FALSE, 0);
7709 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
7710 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
7712 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
7713 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
7715 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
7716 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
7717 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
7718 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
7719 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
7721 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
7723 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
7724 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
7726 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
7727 &ok_btn, GTK_STOCK_OK,
7729 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
7730 gtk_widget_grab_default(ok_btn);
7732 g_signal_connect(G_OBJECT(ok_btn), "clicked",
7733 G_CALLBACK(attach_property_ok),
7735 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
7736 G_CALLBACK(attach_property_cancel),
7739 gtk_widget_show_all(vbox);
7741 attach_prop.window = window;
7742 attach_prop.mimetype_entry = mimetype_entry;
7743 attach_prop.encoding_optmenu = optmenu;
7744 attach_prop.path_entry = path_entry;
7745 attach_prop.filename_entry = filename_entry;
7746 attach_prop.ok_btn = ok_btn;
7747 attach_prop.cancel_btn = cancel_btn;
7750 #undef SET_LABEL_AND_ENTRY
7752 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
7758 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
7764 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
7765 gboolean *cancelled)
7773 static gboolean attach_property_key_pressed(GtkWidget *widget,
7775 gboolean *cancelled)
7777 if (event && event->keyval == GDK_Escape) {
7784 static void compose_exec_ext_editor(Compose *compose)
7791 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
7792 G_DIR_SEPARATOR, compose);
7794 if (pipe(pipe_fds) < 0) {
7800 if ((pid = fork()) < 0) {
7807 /* close the write side of the pipe */
7810 compose->exteditor_file = g_strdup(tmp);
7811 compose->exteditor_pid = pid;
7813 compose_set_ext_editor_sensitive(compose, FALSE);
7815 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
7816 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
7820 } else { /* process-monitoring process */
7826 /* close the read side of the pipe */
7829 if (compose_write_body_to_file(compose, tmp) < 0) {
7830 fd_write_all(pipe_fds[1], "2\n", 2);
7834 pid_ed = compose_exec_ext_editor_real(tmp);
7836 fd_write_all(pipe_fds[1], "1\n", 2);
7840 /* wait until editor is terminated */
7841 waitpid(pid_ed, NULL, 0);
7843 fd_write_all(pipe_fds[1], "0\n", 2);
7850 #endif /* G_OS_UNIX */
7854 static gint compose_exec_ext_editor_real(const gchar *file)
7861 g_return_val_if_fail(file != NULL, -1);
7863 if ((pid = fork()) < 0) {
7868 if (pid != 0) return pid;
7870 /* grandchild process */
7872 if (setpgid(0, getppid()))
7875 if (prefs_common.ext_editor_cmd &&
7876 (p = strchr(prefs_common.ext_editor_cmd, '%')) &&
7877 *(p + 1) == 's' && !strchr(p + 2, '%')) {
7878 g_snprintf(buf, sizeof(buf), prefs_common.ext_editor_cmd, file);
7880 if (prefs_common.ext_editor_cmd)
7881 g_warning("External editor command line is invalid: '%s'\n",
7882 prefs_common.ext_editor_cmd);
7883 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
7886 cmdline = strsplit_with_quote(buf, " ", 1024);
7887 execvp(cmdline[0], cmdline);
7890 g_strfreev(cmdline);
7895 static gboolean compose_ext_editor_kill(Compose *compose)
7897 pid_t pgid = compose->exteditor_pid * -1;
7900 ret = kill(pgid, 0);
7902 if (ret == 0 || (ret == -1 && EPERM == errno)) {
7906 msg = g_strdup_printf
7907 (_("The external editor is still working.\n"
7908 "Force terminating the process?\n"
7909 "process group id: %d"), -pgid);
7910 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
7911 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
7915 if (val == G_ALERTALTERNATE) {
7916 g_source_remove(compose->exteditor_tag);
7917 g_io_channel_shutdown(compose->exteditor_ch,
7919 g_io_channel_unref(compose->exteditor_ch);
7921 if (kill(pgid, SIGTERM) < 0) perror("kill");
7922 waitpid(compose->exteditor_pid, NULL, 0);
7924 g_warning("Terminated process group id: %d", -pgid);
7925 g_warning("Temporary file: %s",
7926 compose->exteditor_file);
7928 compose_set_ext_editor_sensitive(compose, TRUE);
7930 g_free(compose->exteditor_file);
7931 compose->exteditor_file = NULL;
7932 compose->exteditor_pid = -1;
7933 compose->exteditor_ch = NULL;
7934 compose->exteditor_tag = -1;
7942 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
7946 Compose *compose = (Compose *)data;
7949 debug_print(_("Compose: input from monitoring process\n"));
7951 g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL);
7953 g_io_channel_shutdown(source, FALSE, NULL);
7954 g_io_channel_unref(source);
7956 waitpid(compose->exteditor_pid, NULL, 0);
7958 if (buf[0] == '0') { /* success */
7959 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
7960 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
7962 gtk_text_buffer_set_text(buffer, "", -1);
7963 compose_insert_file(compose, compose->exteditor_file);
7964 compose_changed_cb(NULL, compose);
7966 if (g_unlink(compose->exteditor_file) < 0)
7967 FILE_OP_ERROR(compose->exteditor_file, "unlink");
7968 } else if (buf[0] == '1') { /* failed */
7969 g_warning("Couldn't exec external editor\n");
7970 if (g_unlink(compose->exteditor_file) < 0)
7971 FILE_OP_ERROR(compose->exteditor_file, "unlink");
7972 } else if (buf[0] == '2') {
7973 g_warning("Couldn't write to file\n");
7974 } else if (buf[0] == '3') {
7975 g_warning("Pipe read failed\n");
7978 compose_set_ext_editor_sensitive(compose, TRUE);
7980 g_free(compose->exteditor_file);
7981 compose->exteditor_file = NULL;
7982 compose->exteditor_pid = -1;
7983 compose->exteditor_ch = NULL;
7984 compose->exteditor_tag = -1;
7989 static void compose_set_ext_editor_sensitive(Compose *compose,
7992 GtkItemFactory *ifactory;
7994 ifactory = gtk_item_factory_from_widget(compose->menubar);
7996 menu_set_sensitive(ifactory, "/Message/Send", sensitive);
7997 menu_set_sensitive(ifactory, "/Message/Send later", sensitive);
7998 menu_set_sensitive(ifactory, "/Message/Insert file", sensitive);
7999 menu_set_sensitive(ifactory, "/Message/Insert signature", sensitive);
8000 menu_set_sensitive(ifactory, "/Edit/Wrap current paragraph", sensitive);
8001 menu_set_sensitive(ifactory, "/Edit/Wrap all long lines", sensitive);
8002 menu_set_sensitive(ifactory, "/Edit/Edit with external editor",
8005 gtk_widget_set_sensitive(compose->text, sensitive);
8006 if (compose->toolbar->send_btn)
8007 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
8008 if (compose->toolbar->sendl_btn)
8009 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
8010 if (compose->toolbar->draft_btn)
8011 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
8012 if (compose->toolbar->insert_btn)
8013 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
8014 if (compose->toolbar->sig_btn)
8015 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
8016 if (compose->toolbar->exteditor_btn)
8017 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
8018 if (compose->toolbar->linewrap_current_btn)
8019 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
8020 if (compose->toolbar->linewrap_all_btn)
8021 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
8023 #endif /* G_OS_UNIX */
8026 * compose_undo_state_changed:
8028 * Change the sensivity of the menuentries undo and redo
8030 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
8031 gint redo_state, gpointer data)
8033 GtkWidget *widget = GTK_WIDGET(data);
8034 GtkItemFactory *ifactory;
8036 g_return_if_fail(widget != NULL);
8038 ifactory = gtk_item_factory_from_widget(widget);
8040 switch (undo_state) {
8041 case UNDO_STATE_TRUE:
8042 if (!undostruct->undo_state) {
8043 undostruct->undo_state = TRUE;
8044 menu_set_sensitive(ifactory, "/Edit/Undo", TRUE);
8047 case UNDO_STATE_FALSE:
8048 if (undostruct->undo_state) {
8049 undostruct->undo_state = FALSE;
8050 menu_set_sensitive(ifactory, "/Edit/Undo", FALSE);
8053 case UNDO_STATE_UNCHANGED:
8055 case UNDO_STATE_REFRESH:
8056 menu_set_sensitive(ifactory, "/Edit/Undo",
8057 undostruct->undo_state);
8060 g_warning("Undo state not recognized");
8064 switch (redo_state) {
8065 case UNDO_STATE_TRUE:
8066 if (!undostruct->redo_state) {
8067 undostruct->redo_state = TRUE;
8068 menu_set_sensitive(ifactory, "/Edit/Redo", TRUE);
8071 case UNDO_STATE_FALSE:
8072 if (undostruct->redo_state) {
8073 undostruct->redo_state = FALSE;
8074 menu_set_sensitive(ifactory, "/Edit/Redo", FALSE);
8077 case UNDO_STATE_UNCHANGED:
8079 case UNDO_STATE_REFRESH:
8080 menu_set_sensitive(ifactory, "/Edit/Redo",
8081 undostruct->redo_state);
8084 g_warning("Redo state not recognized");
8089 /* callback functions */
8091 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
8092 * includes "non-client" (windows-izm) in calculation, so this calculation
8093 * may not be accurate.
8095 static gboolean compose_edit_size_alloc(GtkEditable *widget,
8096 GtkAllocation *allocation,
8097 GtkSHRuler *shruler)
8099 if (prefs_common.show_ruler) {
8100 gint char_width = 0, char_height = 0;
8101 gint line_width_in_chars;
8103 gtkut_get_font_size(GTK_WIDGET(widget),
8104 &char_width, &char_height);
8105 line_width_in_chars =
8106 (allocation->width - allocation->x) / char_width;
8108 /* got the maximum */
8109 gtk_ruler_set_range(GTK_RULER(shruler),
8110 0.0, line_width_in_chars, 0,
8111 /*line_width_in_chars*/ char_width);
8117 static void account_activated(GtkComboBox *optmenu, gpointer data)
8119 Compose *compose = (Compose *)data;
8122 gchar *folderidentifier;
8123 gint account_id = 0;
8127 /* Get ID of active account in the combo box */
8128 menu = gtk_combo_box_get_model(optmenu);
8129 gtk_combo_box_get_active_iter(optmenu, &iter);
8130 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
8132 ac = account_find_from_id(account_id);
8133 g_return_if_fail(ac != NULL);
8135 if (ac != compose->account)
8136 compose_select_account(compose, ac, FALSE);
8138 /* Set message save folder */
8139 if (account_get_special_folder(compose->account, F_OUTBOX)) {
8140 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
8142 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
8143 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
8145 gtk_editable_delete_text(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
8146 if (account_get_special_folder(compose->account, F_OUTBOX)) {
8147 folderidentifier = folder_item_get_identifier(account_get_special_folder
8148 (compose->account, F_OUTBOX));
8149 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
8150 g_free(folderidentifier);
8154 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
8155 GtkTreeViewColumn *column, Compose *compose)
8157 compose_attach_property(compose);
8160 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
8163 Compose *compose = (Compose *)data;
8164 GtkTreeSelection *attach_selection;
8165 gint attach_nr_selected;
8166 GtkItemFactory *ifactory;
8168 if (!event) return FALSE;
8170 if (event->button == 3) {
8171 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
8172 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
8173 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
8175 if (attach_nr_selected > 0)
8177 menu_set_sensitive(ifactory, "/Remove", TRUE);
8178 menu_set_sensitive(ifactory, "/Properties...", TRUE);
8180 menu_set_sensitive(ifactory, "/Remove", FALSE);
8181 menu_set_sensitive(ifactory, "/Properties...", FALSE);
8184 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
8185 NULL, NULL, event->button, event->time);
8192 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
8195 Compose *compose = (Compose *)data;
8197 if (!event) return FALSE;
8199 switch (event->keyval) {
8201 compose_attach_remove_selected(compose);
8207 static void compose_allow_user_actions (Compose *compose, gboolean allow)
8209 GtkItemFactory *ifactory = gtk_item_factory_from_widget(compose->menubar);
8210 toolbar_comp_set_sensitive(compose, allow);
8211 menu_set_sensitive(ifactory, "/Message", allow);
8212 menu_set_sensitive(ifactory, "/Edit", allow);
8214 menu_set_sensitive(ifactory, "/Spelling", allow);
8216 menu_set_sensitive(ifactory, "/Options", allow);
8217 menu_set_sensitive(ifactory, "/Tools", allow);
8218 menu_set_sensitive(ifactory, "/Help", allow);
8220 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
8224 static void compose_send_cb(gpointer data, guint action, GtkWidget *widget)
8226 Compose *compose = (Compose *)data;
8228 if (prefs_common.work_offline &&
8229 !inc_offline_should_override(TRUE,
8230 _("Claws Mail needs network access in order "
8231 "to send this email.")))
8234 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
8235 g_source_remove(compose->draft_timeout_tag);
8236 compose->draft_timeout_tag = -1;
8239 compose_send(compose);
8242 static void compose_send_later_cb(gpointer data, guint action,
8245 Compose *compose = (Compose *)data;
8249 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
8253 compose_close(compose);
8254 } else if (val == -1) {
8255 alertpanel_error(_("Could not queue message."));
8256 } else if (val == -2) {
8257 alertpanel_error(_("Could not queue message:\n\n%s."), strerror(errno));
8258 } else if (val == -3) {
8259 if (privacy_peek_error())
8260 alertpanel_error(_("Could not queue message for sending:\n\n"
8261 "Signature failed: %s"), privacy_get_error());
8262 } else if (val == -4) {
8263 alertpanel_error(_("Could not queue message for sending:\n\n"
8264 "Charset conversion failed."));
8265 } else if (val == -5) {
8266 alertpanel_error(_("Could not queue message for sending:\n\n"
8267 "Couldn't get recipient encryption key."));
8268 } else if (val == -6) {
8271 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
8274 #define DRAFTED_AT_EXIT "drafted_at_exit"
8275 static void compose_register_draft(MsgInfo *info)
8277 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8278 DRAFTED_AT_EXIT, NULL);
8279 FILE *fp = fopen(filepath, "ab");
8282 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
8290 gboolean compose_draft (gpointer data, guint action)
8292 Compose *compose = (Compose *)data;
8296 MsgFlags flag = {0, 0};
8297 static gboolean lock = FALSE;
8298 MsgInfo *newmsginfo;
8300 gboolean target_locked = FALSE;
8302 if (lock) return FALSE;
8304 if (compose->sending)
8307 draft = account_get_special_folder(compose->account, F_DRAFT);
8308 g_return_val_if_fail(draft != NULL, FALSE);
8310 if (!g_mutex_trylock(compose->mutex)) {
8311 /* we don't want to lock the mutex once it's available,
8312 * because as the only other part of compose.c locking
8313 * it is compose_close - which means once unlocked,
8314 * the compose struct will be freed */
8315 debug_print("couldn't lock mutex, probably sending\n");
8321 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
8322 G_DIR_SEPARATOR, compose);
8323 if ((fp = g_fopen(tmp, "wb")) == NULL) {
8324 FILE_OP_ERROR(tmp, "fopen");
8328 /* chmod for security */
8329 if (change_file_mode_rw(fp, tmp) < 0) {
8330 FILE_OP_ERROR(tmp, "chmod");
8331 g_warning("can't change file mode\n");
8334 /* Save draft infos */
8335 fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id);
8336 fprintf(fp, "S:%s\n", compose->account->address);
8338 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
8339 gchar *savefolderid;
8341 savefolderid = gtk_editable_get_chars(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
8342 fprintf(fp, "SCF:%s\n", savefolderid);
8343 g_free(savefolderid);
8345 if (compose->return_receipt) {
8346 fprintf(fp, "RRCPT:1\n");
8348 if (compose->privacy_system) {
8349 fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing);
8350 fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption);
8351 fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system);
8354 /* Message-ID of message replying to */
8355 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
8358 folderid = folder_item_get_identifier(compose->replyinfo->folder);
8359 fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid);
8362 /* Message-ID of message forwarding to */
8363 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
8366 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
8367 fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid);
8371 /* end of headers */
8372 fprintf(fp, "X-Claws-End-Special-Headers: 1\n");
8374 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
8382 if (compose->targetinfo) {
8383 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
8384 flag.perm_flags = target_locked?MSG_LOCKED:0;
8386 flag.tmp_flags = MSG_DRAFT;
8388 folder_item_scan(draft);
8389 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
8392 if (action != COMPOSE_AUTO_SAVE) {
8393 if (action != COMPOSE_DRAFT_FOR_EXIT)
8394 alertpanel_error(_("Could not save draft."));
8397 gtkut_window_popup(compose->window);
8398 val = alertpanel_full(_("Could not save draft"),
8399 _("Could not save draft.\n"
8400 "Do you want to cancel exit or discard this email?"),
8401 _("_Cancel exit"), _("_Discard email"), NULL,
8402 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
8403 if (val == G_ALERTALTERNATE) {
8405 g_mutex_unlock(compose->mutex); /* must be done before closing */
8406 compose_close(compose);
8410 g_mutex_unlock(compose->mutex); /* must be done before closing */
8419 if (compose->mode == COMPOSE_REEDIT) {
8420 compose_remove_reedit_target(compose, TRUE);
8423 newmsginfo = folder_item_get_msginfo(draft, msgnum);
8424 if (!newmsginfo && compose->msgid) {
8425 newmsginfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
8427 msgnum = newmsginfo->msgnum;
8430 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
8432 procmsg_msginfo_set_flags(newmsginfo, MSG_LOCKED, MSG_DRAFT);
8434 procmsg_msginfo_set_flags(newmsginfo, 0, MSG_DRAFT);
8435 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
8436 procmsg_msginfo_set_flags(newmsginfo, 0,
8437 MSG_HAS_ATTACHMENT);
8439 if (action == COMPOSE_DRAFT_FOR_EXIT) {
8440 compose_register_draft(newmsginfo);
8442 procmsg_msginfo_free(newmsginfo);
8445 folder_item_scan(draft);
8447 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
8449 g_mutex_unlock(compose->mutex); /* must be done before closing */
8450 compose_close(compose);
8456 path = folder_item_fetch_msg(draft, msgnum);
8458 debug_print("can't fetch %s:%d\n",draft->path, msgnum);
8461 if (g_stat(path, &s) < 0) {
8462 FILE_OP_ERROR(path, "stat");
8468 procmsg_msginfo_free(compose->targetinfo);
8469 compose->targetinfo = procmsg_msginfo_new();
8470 compose->targetinfo->msgnum = msgnum;
8471 compose->targetinfo->size = s.st_size;
8472 compose->targetinfo->mtime = s.st_mtime;
8473 compose->targetinfo->folder = draft;
8475 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
8476 compose->mode = COMPOSE_REEDIT;
8478 if (action == COMPOSE_AUTO_SAVE) {
8479 compose->autosaved_draft = compose->targetinfo;
8481 compose->modified = FALSE;
8482 compose_set_title(compose);
8486 g_mutex_unlock(compose->mutex);
8490 void compose_clear_exit_drafts(void)
8492 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8493 DRAFTED_AT_EXIT, NULL);
8494 if (is_file_exist(filepath))
8500 void compose_reopen_exit_drafts(void)
8502 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8503 DRAFTED_AT_EXIT, NULL);
8504 FILE *fp = fopen(filepath, "rb");
8508 while (fgets(buf, sizeof(buf), fp)) {
8509 gchar **parts = g_strsplit(buf, "\t", 2);
8510 const gchar *folder = parts[0];
8511 int msgnum = parts[1] ? atoi(parts[1]):-1;
8513 if (folder && *folder && msgnum > -1) {
8514 FolderItem *item = folder_find_item_from_identifier(folder);
8515 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
8517 compose_reedit(info, FALSE);
8524 compose_clear_exit_drafts();
8527 static void compose_draft_cb(gpointer data, guint action, GtkWidget *widget)
8529 compose_draft(data, action);
8532 static void compose_attach_cb(gpointer data, guint action, GtkWidget *widget)
8534 Compose *compose = (Compose *)data;
8537 if (compose->redirect_filename != NULL)
8540 file_list = filesel_select_multiple_files_open(_("Select file"));
8545 for ( tmp = file_list; tmp; tmp = tmp->next) {
8546 gchar *file = (gchar *) tmp->data;
8547 gchar *utf8_filename = conv_filename_to_utf8(file);
8548 compose_attach_append(compose, file, utf8_filename, NULL);
8549 compose_changed_cb(NULL, compose);
8551 g_free(utf8_filename);
8553 g_list_free(file_list);
8557 static void compose_insert_file_cb(gpointer data, guint action,
8560 Compose *compose = (Compose *)data;
8563 file_list = filesel_select_multiple_files_open(_("Select file"));
8568 for ( tmp = file_list; tmp; tmp = tmp->next) {
8569 gchar *file = (gchar *) tmp->data;
8570 gchar *filedup = g_strdup(file);
8571 gchar *shortfile = g_path_get_basename(filedup);
8572 ComposeInsertResult res;
8574 res = compose_insert_file(compose, file);
8575 if (res == COMPOSE_INSERT_READ_ERROR) {
8576 alertpanel_error(_("File '%s' could not be read."), shortfile);
8577 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
8578 alertpanel_error(_("File '%s' contained invalid characters\n"
8579 "for the current encoding, insertion may be incorrect."), shortfile);
8585 g_list_free(file_list);
8589 static void compose_insert_sig_cb(gpointer data, guint action,
8592 Compose *compose = (Compose *)data;
8594 compose_insert_sig(compose, FALSE);
8597 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
8601 Compose *compose = (Compose *)data;
8603 gtkut_widget_get_uposition(widget, &x, &y);
8604 prefs_common.compose_x = x;
8605 prefs_common.compose_y = y;
8607 if (compose->sending || compose->updating)
8609 compose_close_cb(compose, 0, NULL);
8613 void compose_close_toolbar(Compose *compose)
8615 compose_close_cb(compose, 0, NULL);
8618 static void compose_close_cb(gpointer data, guint action, GtkWidget *widget)
8620 Compose *compose = (Compose *)data;
8624 if (compose->exteditor_tag != -1) {
8625 if (!compose_ext_editor_kill(compose))
8630 if (compose->modified) {
8631 val = alertpanel(_("Discard message"),
8632 _("This message has been modified. Discard it?"),
8633 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
8636 case G_ALERTDEFAULT:
8637 if (prefs_common.autosave)
8638 compose_remove_draft(compose);
8640 case G_ALERTALTERNATE:
8641 compose_draft_cb(data, COMPOSE_QUIT_EDITING, NULL);
8648 compose_close(compose);
8651 static void compose_set_encoding_cb(gpointer data, guint action,
8654 Compose *compose = (Compose *)data;
8656 if (GTK_CHECK_MENU_ITEM(widget)->active)
8657 compose->out_encoding = (CharSet)action;
8660 static void compose_address_cb(gpointer data, guint action, GtkWidget *widget)
8662 Compose *compose = (Compose *)data;
8664 addressbook_open(compose);
8667 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
8669 Compose *compose = (Compose *)data;
8674 tmpl = g_object_get_data(G_OBJECT(widget), "template");
8675 g_return_if_fail(tmpl != NULL);
8677 msg = g_strdup_printf(_("Do you want to apply the template '%s' ?"),
8679 val = alertpanel(_("Apply template"), msg,
8680 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
8683 if (val == G_ALERTDEFAULT)
8684 compose_template_apply(compose, tmpl, TRUE);
8685 else if (val == G_ALERTALTERNATE)
8686 compose_template_apply(compose, tmpl, FALSE);
8689 static void compose_ext_editor_cb(gpointer data, guint action,
8692 Compose *compose = (Compose *)data;
8694 compose_exec_ext_editor(compose);
8697 static void compose_undo_cb(Compose *compose)
8699 gboolean prev_autowrap = compose->autowrap;
8701 compose->autowrap = FALSE;
8702 undo_undo(compose->undostruct);
8703 compose->autowrap = prev_autowrap;
8706 static void compose_redo_cb(Compose *compose)
8708 gboolean prev_autowrap = compose->autowrap;
8710 compose->autowrap = FALSE;
8711 undo_redo(compose->undostruct);
8712 compose->autowrap = prev_autowrap;
8715 static void entry_cut_clipboard(GtkWidget *entry)
8717 if (GTK_IS_EDITABLE(entry))
8718 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
8719 else if (GTK_IS_TEXT_VIEW(entry))
8720 gtk_text_buffer_cut_clipboard(
8721 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
8722 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
8726 static void entry_copy_clipboard(GtkWidget *entry)
8728 if (GTK_IS_EDITABLE(entry))
8729 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
8730 else if (GTK_IS_TEXT_VIEW(entry))
8731 gtk_text_buffer_copy_clipboard(
8732 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
8733 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
8736 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
8737 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
8739 if (GTK_IS_TEXT_VIEW(entry)) {
8740 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
8741 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
8742 GtkTextIter start_iter, end_iter;
8744 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
8746 if (contents == NULL)
8749 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
8751 /* we shouldn't delete the selection when middle-click-pasting, or we
8752 * can't mid-click-paste our own selection */
8753 if (clip != GDK_SELECTION_PRIMARY) {
8754 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
8757 if (insert_place == NULL) {
8758 /* if insert_place isn't specified, insert at the cursor.
8759 * used for Ctrl-V pasting */
8760 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
8761 start = gtk_text_iter_get_offset(&start_iter);
8762 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
8764 /* if insert_place is specified, paste here.
8765 * used for mid-click-pasting */
8766 start = gtk_text_iter_get_offset(insert_place);
8767 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
8771 /* paste unwrapped: mark the paste so it's not wrapped later */
8772 end = start + strlen(contents);
8773 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
8774 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
8775 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
8776 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
8777 /* rewrap paragraph now (after a mid-click-paste) */
8778 mark_start = gtk_text_buffer_get_insert(buffer);
8779 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
8780 gtk_text_iter_backward_char(&start_iter);
8781 compose_beautify_paragraph(compose, &start_iter, TRUE);
8783 } else if (GTK_IS_EDITABLE(entry))
8784 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
8788 static void entry_allsel(GtkWidget *entry)
8790 if (GTK_IS_EDITABLE(entry))
8791 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
8792 else if (GTK_IS_TEXT_VIEW(entry)) {
8793 GtkTextIter startiter, enditer;
8794 GtkTextBuffer *textbuf;
8796 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
8797 gtk_text_buffer_get_start_iter(textbuf, &startiter);
8798 gtk_text_buffer_get_end_iter(textbuf, &enditer);
8800 gtk_text_buffer_move_mark_by_name(textbuf,
8801 "selection_bound", &startiter);
8802 gtk_text_buffer_move_mark_by_name(textbuf,
8803 "insert", &enditer);
8807 static void compose_cut_cb(Compose *compose)
8809 if (compose->focused_editable
8811 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
8814 entry_cut_clipboard(compose->focused_editable);
8817 static void compose_copy_cb(Compose *compose)
8819 if (compose->focused_editable
8821 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
8824 entry_copy_clipboard(compose->focused_editable);
8827 static void compose_paste_cb(Compose *compose)
8830 GtkTextBuffer *buffer;
8832 if (compose->focused_editable &&
8833 GTK_WIDGET_HAS_FOCUS(compose->focused_editable))
8834 entry_paste_clipboard(compose, compose->focused_editable,
8835 prefs_common.linewrap_pastes,
8836 GDK_SELECTION_CLIPBOARD, NULL);
8840 static void compose_paste_as_quote_cb(Compose *compose)
8842 gint wrap_quote = prefs_common.linewrap_quote;
8843 if (compose->focused_editable
8845 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
8848 /* let text_insert() (called directly or at a later time
8849 * after the gtk_editable_paste_clipboard) know that
8850 * text is to be inserted as a quotation. implemented
8851 * by using a simple refcount... */
8852 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
8853 G_OBJECT(compose->focused_editable),
8854 "paste_as_quotation"));
8855 g_object_set_data(G_OBJECT(compose->focused_editable),
8856 "paste_as_quotation",
8857 GINT_TO_POINTER(paste_as_quotation + 1));
8858 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
8859 entry_paste_clipboard(compose, compose->focused_editable,
8860 prefs_common.linewrap_pastes,
8861 GDK_SELECTION_CLIPBOARD, NULL);
8862 prefs_common.linewrap_quote = wrap_quote;
8866 static void compose_paste_no_wrap_cb(Compose *compose)
8869 GtkTextBuffer *buffer;
8871 if (compose->focused_editable
8873 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
8876 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
8877 GDK_SELECTION_CLIPBOARD, NULL);
8881 static void compose_paste_wrap_cb(Compose *compose)
8884 GtkTextBuffer *buffer;
8886 if (compose->focused_editable
8888 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
8891 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
8892 GDK_SELECTION_CLIPBOARD, NULL);
8896 static void compose_allsel_cb(Compose *compose)
8898 if (compose->focused_editable
8900 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
8903 entry_allsel(compose->focused_editable);
8906 static void textview_move_beginning_of_line (GtkTextView *text)
8908 GtkTextBuffer *buffer;
8912 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
8914 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8915 mark = gtk_text_buffer_get_insert(buffer);
8916 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
8917 gtk_text_iter_set_line_offset(&ins, 0);
8918 gtk_text_buffer_place_cursor(buffer, &ins);
8921 static void textview_move_forward_character (GtkTextView *text)
8923 GtkTextBuffer *buffer;
8927 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
8929 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8930 mark = gtk_text_buffer_get_insert(buffer);
8931 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
8932 if (gtk_text_iter_forward_cursor_position(&ins))
8933 gtk_text_buffer_place_cursor(buffer, &ins);
8936 static void textview_move_backward_character (GtkTextView *text)
8938 GtkTextBuffer *buffer;
8942 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
8944 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8945 mark = gtk_text_buffer_get_insert(buffer);
8946 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
8947 if (gtk_text_iter_backward_cursor_position(&ins))
8948 gtk_text_buffer_place_cursor(buffer, &ins);
8951 static void textview_move_forward_word (GtkTextView *text)
8953 GtkTextBuffer *buffer;
8958 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
8960 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8961 mark = gtk_text_buffer_get_insert(buffer);
8962 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
8963 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
8964 if (gtk_text_iter_forward_word_ends(&ins, count)) {
8965 gtk_text_iter_backward_word_start(&ins);
8966 gtk_text_buffer_place_cursor(buffer, &ins);
8970 static void textview_move_backward_word (GtkTextView *text)
8972 GtkTextBuffer *buffer;
8977 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
8979 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8980 mark = gtk_text_buffer_get_insert(buffer);
8981 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
8982 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
8983 if (gtk_text_iter_backward_word_starts(&ins, 1))
8984 gtk_text_buffer_place_cursor(buffer, &ins);
8987 static void textview_move_end_of_line (GtkTextView *text)
8989 GtkTextBuffer *buffer;
8993 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
8995 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8996 mark = gtk_text_buffer_get_insert(buffer);
8997 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
8998 if (gtk_text_iter_forward_to_line_end(&ins))
8999 gtk_text_buffer_place_cursor(buffer, &ins);
9002 static void textview_move_next_line (GtkTextView *text)
9004 GtkTextBuffer *buffer;
9009 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9011 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9012 mark = gtk_text_buffer_get_insert(buffer);
9013 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9014 offset = gtk_text_iter_get_line_offset(&ins);
9015 if (gtk_text_iter_forward_line(&ins)) {
9016 gtk_text_iter_set_line_offset(&ins, offset);
9017 gtk_text_buffer_place_cursor(buffer, &ins);
9021 static void textview_move_previous_line (GtkTextView *text)
9023 GtkTextBuffer *buffer;
9028 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9030 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9031 mark = gtk_text_buffer_get_insert(buffer);
9032 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9033 offset = gtk_text_iter_get_line_offset(&ins);
9034 if (gtk_text_iter_backward_line(&ins)) {
9035 gtk_text_iter_set_line_offset(&ins, offset);
9036 gtk_text_buffer_place_cursor(buffer, &ins);
9040 static void textview_delete_forward_character (GtkTextView *text)
9042 GtkTextBuffer *buffer;
9044 GtkTextIter ins, end_iter;
9046 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9048 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9049 mark = gtk_text_buffer_get_insert(buffer);
9050 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9052 if (gtk_text_iter_forward_char(&end_iter)) {
9053 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9057 static void textview_delete_backward_character (GtkTextView *text)
9059 GtkTextBuffer *buffer;
9061 GtkTextIter ins, end_iter;
9063 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9065 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9066 mark = gtk_text_buffer_get_insert(buffer);
9067 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9069 if (gtk_text_iter_backward_char(&end_iter)) {
9070 gtk_text_buffer_delete(buffer, &end_iter, &ins);
9074 static void textview_delete_forward_word (GtkTextView *text)
9076 GtkTextBuffer *buffer;
9078 GtkTextIter ins, end_iter;
9080 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9082 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9083 mark = gtk_text_buffer_get_insert(buffer);
9084 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9086 if (gtk_text_iter_forward_word_end(&end_iter)) {
9087 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9091 static void textview_delete_backward_word (GtkTextView *text)
9093 GtkTextBuffer *buffer;
9095 GtkTextIter ins, end_iter;
9097 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9099 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9100 mark = gtk_text_buffer_get_insert(buffer);
9101 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9103 if (gtk_text_iter_backward_word_start(&end_iter)) {
9104 gtk_text_buffer_delete(buffer, &end_iter, &ins);
9108 static void textview_delete_line (GtkTextView *text)
9110 GtkTextBuffer *buffer;
9112 GtkTextIter ins, start_iter, end_iter;
9115 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9117 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9118 mark = gtk_text_buffer_get_insert(buffer);
9119 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9122 gtk_text_iter_set_line_offset(&start_iter, 0);
9125 if (gtk_text_iter_ends_line(&end_iter))
9126 found = gtk_text_iter_forward_char(&end_iter);
9128 found = gtk_text_iter_forward_to_line_end(&end_iter);
9131 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
9134 static void textview_delete_to_line_end (GtkTextView *text)
9136 GtkTextBuffer *buffer;
9138 GtkTextIter ins, end_iter;
9141 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9143 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9144 mark = gtk_text_buffer_get_insert(buffer);
9145 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9147 if (gtk_text_iter_ends_line(&end_iter))
9148 found = gtk_text_iter_forward_char(&end_iter);
9150 found = gtk_text_iter_forward_to_line_end(&end_iter);
9152 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9155 static void compose_advanced_action_cb(Compose *compose,
9156 ComposeCallAdvancedAction action)
9158 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9160 void (*do_action) (GtkTextView *text);
9161 } action_table[] = {
9162 {textview_move_beginning_of_line},
9163 {textview_move_forward_character},
9164 {textview_move_backward_character},
9165 {textview_move_forward_word},
9166 {textview_move_backward_word},
9167 {textview_move_end_of_line},
9168 {textview_move_next_line},
9169 {textview_move_previous_line},
9170 {textview_delete_forward_character},
9171 {textview_delete_backward_character},
9172 {textview_delete_forward_word},
9173 {textview_delete_backward_word},
9174 {textview_delete_line},
9175 {NULL}, /* gtk_stext_delete_line_n */
9176 {textview_delete_to_line_end}
9179 if (!GTK_WIDGET_HAS_FOCUS(text)) return;
9181 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
9182 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
9183 if (action_table[action].do_action)
9184 action_table[action].do_action(text);
9186 g_warning("Not implemented yet.");
9190 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
9194 if (GTK_IS_EDITABLE(widget)) {
9195 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
9196 gtk_editable_set_position(GTK_EDITABLE(widget),
9199 if (widget->parent && widget->parent->parent
9200 && widget->parent->parent->parent) {
9201 if (GTK_IS_SCROLLED_WINDOW(widget->parent->parent->parent)) {
9202 gint y = widget->allocation.y;
9203 gint height = widget->allocation.height;
9204 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
9205 (GTK_SCROLLED_WINDOW(widget->parent->parent->parent));
9207 if (y < (int)shown->value) {
9208 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown), y - 1);
9210 if (y + height > (int)shown->value + (int)shown->page_size) {
9211 if (y - height - 1 < (int)shown->upper - (int)shown->page_size) {
9212 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
9213 y + height - (int)shown->page_size - 1);
9215 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
9216 (int)shown->upper - (int)shown->page_size - 1);
9223 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
9224 compose->focused_editable = widget;
9227 if (GTK_IS_TEXT_VIEW(widget)
9228 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
9229 gtk_widget_ref(compose->notebook);
9230 gtk_widget_ref(compose->edit_vbox);
9231 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
9232 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
9233 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
9234 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
9235 gtk_widget_unref(compose->notebook);
9236 gtk_widget_unref(compose->edit_vbox);
9237 g_signal_handlers_block_by_func(G_OBJECT(widget),
9238 G_CALLBACK(compose_grab_focus_cb),
9240 gtk_widget_grab_focus(widget);
9241 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
9242 G_CALLBACK(compose_grab_focus_cb),
9244 } else if (!GTK_IS_TEXT_VIEW(widget)
9245 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
9246 gtk_widget_ref(compose->notebook);
9247 gtk_widget_ref(compose->edit_vbox);
9248 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
9249 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
9250 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
9251 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
9252 gtk_widget_unref(compose->notebook);
9253 gtk_widget_unref(compose->edit_vbox);
9254 g_signal_handlers_block_by_func(G_OBJECT(widget),
9255 G_CALLBACK(compose_grab_focus_cb),
9257 gtk_widget_grab_focus(widget);
9258 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
9259 G_CALLBACK(compose_grab_focus_cb),
9265 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
9267 compose->modified = TRUE;
9269 compose_set_title(compose);
9273 static void compose_wrap_cb(gpointer data, guint action, GtkWidget *widget)
9275 Compose *compose = (Compose *)data;
9278 compose_wrap_all_full(compose, TRUE);
9280 compose_beautify_paragraph(compose, NULL, TRUE);
9283 static void compose_find_cb(gpointer data, guint action, GtkWidget *widget)
9285 Compose *compose = (Compose *)data;
9287 message_search_compose(compose);
9290 static void compose_toggle_autowrap_cb(gpointer data, guint action,
9293 Compose *compose = (Compose *)data;
9294 compose->autowrap = GTK_CHECK_MENU_ITEM(widget)->active;
9295 if (compose->autowrap)
9296 compose_wrap_all_full(compose, TRUE);
9297 compose->autowrap = GTK_CHECK_MENU_ITEM(widget)->active;
9300 static void compose_toggle_sign_cb(gpointer data, guint action,
9303 Compose *compose = (Compose *)data;
9305 if (GTK_CHECK_MENU_ITEM(widget)->active)
9306 compose->use_signing = TRUE;
9308 compose->use_signing = FALSE;
9311 static void compose_toggle_encrypt_cb(gpointer data, guint action,
9314 Compose *compose = (Compose *)data;
9316 if (GTK_CHECK_MENU_ITEM(widget)->active)
9317 compose->use_encryption = TRUE;
9319 compose->use_encryption = FALSE;
9322 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
9324 g_free(compose->privacy_system);
9326 compose->privacy_system = g_strdup(account->default_privacy_system);
9327 compose_update_privacy_system_menu_item(compose, warn);
9330 static void compose_toggle_ruler_cb(gpointer data, guint action,
9333 Compose *compose = (Compose *)data;
9335 if (GTK_CHECK_MENU_ITEM(widget)->active) {
9336 gtk_widget_show(compose->ruler_hbox);
9337 prefs_common.show_ruler = TRUE;
9339 gtk_widget_hide(compose->ruler_hbox);
9340 gtk_widget_queue_resize(compose->edit_vbox);
9341 prefs_common.show_ruler = FALSE;
9345 static void compose_attach_drag_received_cb (GtkWidget *widget,
9346 GdkDragContext *context,
9349 GtkSelectionData *data,
9354 Compose *compose = (Compose *)user_data;
9357 if (gdk_atom_name(data->type) &&
9358 !strcmp(gdk_atom_name(data->type), "text/uri-list")
9359 && gtk_drag_get_source_widget(context) !=
9360 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
9361 list = uri_list_extract_filenames((const gchar *)data->data);
9362 for (tmp = list; tmp != NULL; tmp = tmp->next) {
9363 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
9364 compose_attach_append
9365 (compose, (const gchar *)tmp->data,
9366 utf8_filename, NULL);
9367 g_free(utf8_filename);
9369 if (list) compose_changed_cb(NULL, compose);
9370 list_free_strings(list);
9372 } else if (gtk_drag_get_source_widget(context)
9373 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
9374 /* comes from our summaryview */
9375 SummaryView * summaryview = NULL;
9376 GSList * list = NULL, *cur = NULL;
9378 if (mainwindow_get_mainwindow())
9379 summaryview = mainwindow_get_mainwindow()->summaryview;
9382 list = summary_get_selected_msg_list(summaryview);
9384 for (cur = list; cur; cur = cur->next) {
9385 MsgInfo *msginfo = (MsgInfo *)cur->data;
9388 file = procmsg_get_message_file_full(msginfo,
9391 compose_attach_append(compose, (const gchar *)file,
9392 (const gchar *)file, "message/rfc822");
9400 static gboolean compose_drag_drop(GtkWidget *widget,
9401 GdkDragContext *drag_context,
9403 guint time, gpointer user_data)
9405 /* not handling this signal makes compose_insert_drag_received_cb
9410 static void compose_insert_drag_received_cb (GtkWidget *widget,
9411 GdkDragContext *drag_context,
9414 GtkSelectionData *data,
9419 Compose *compose = (Compose *)user_data;
9422 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
9424 if (gdk_atom_name(data->type) && !strcmp(gdk_atom_name(data->type), "text/uri-list")) {
9425 AlertValue val = G_ALERTDEFAULT;
9427 switch (prefs_common.compose_dnd_mode) {
9428 case COMPOSE_DND_ASK:
9429 val = alertpanel_full(_("Insert or attach?"),
9430 _("Do you want to insert the contents of the file(s) "
9431 "into the message body, or attach it to the email?"),
9432 GTK_STOCK_CANCEL, _("+_Insert"), _("_Attach"),
9433 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
9435 case COMPOSE_DND_INSERT:
9436 val = G_ALERTALTERNATE;
9438 case COMPOSE_DND_ATTACH:
9442 /* unexpected case */
9443 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
9446 if (val & G_ALERTDISABLE) {
9447 val &= ~G_ALERTDISABLE;
9448 /* remember what action to perform by default, only if we don't click Cancel */
9449 if (val == G_ALERTALTERNATE)
9450 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
9451 else if (val == G_ALERTOTHER)
9452 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
9455 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
9456 gtk_drag_finish(drag_context, FALSE, FALSE, time);
9458 } else if (val == G_ALERTOTHER) {
9459 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
9462 list = uri_list_extract_filenames((const gchar *)data->data);
9463 for (tmp = list; tmp != NULL; tmp = tmp->next) {
9464 compose_insert_file(compose, (const gchar *)tmp->data);
9466 list_free_strings(list);
9468 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9471 #if GTK_CHECK_VERSION(2, 8, 0)
9472 /* do nothing, handled by GTK */
9474 gchar *tmpfile = get_tmp_file();
9475 str_write_to_file((const gchar *)data->data, tmpfile);
9476 compose_insert_file(compose, tmpfile);
9479 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9483 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9486 static void compose_header_drag_received_cb (GtkWidget *widget,
9487 GdkDragContext *drag_context,
9490 GtkSelectionData *data,
9495 GtkEditable *entry = (GtkEditable *)user_data;
9496 gchar *email = (gchar *)data->data;
9498 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
9501 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
9502 gchar *decoded=g_new(gchar, strlen(email));
9505 email += strlen("mailto:");
9506 decode_uri(decoded, email); /* will fit */
9507 gtk_editable_delete_text(entry, 0, -1);
9508 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
9509 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9513 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9516 static void compose_toggle_return_receipt_cb(gpointer data, guint action,
9519 Compose *compose = (Compose *)data;
9521 if (GTK_CHECK_MENU_ITEM(widget)->active)
9522 compose->return_receipt = TRUE;
9524 compose->return_receipt = FALSE;
9527 static void compose_toggle_remove_refs_cb(gpointer data, guint action,
9530 Compose *compose = (Compose *)data;
9532 if (GTK_CHECK_MENU_ITEM(widget)->active)
9533 compose->remove_references = TRUE;
9535 compose->remove_references = FALSE;
9538 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
9540 ComposeHeaderEntry *headerentry)
9542 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
9543 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
9544 !(event->state & GDK_MODIFIER_MASK) &&
9545 (event->keyval == GDK_BackSpace) &&
9546 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
9547 gtk_container_remove
9548 (GTK_CONTAINER(headerentry->compose->header_table),
9549 headerentry->combo);
9550 gtk_container_remove
9551 (GTK_CONTAINER(headerentry->compose->header_table),
9552 headerentry->entry);
9553 headerentry->compose->header_list =
9554 g_slist_remove(headerentry->compose->header_list,
9556 g_free(headerentry);
9557 } else if (event->keyval == GDK_Tab) {
9558 if (headerentry->compose->header_last == headerentry) {
9559 /* Override default next focus, and give it to subject_entry
9560 * instead of notebook tabs
9562 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
9563 gtk_widget_grab_focus(headerentry->compose->subject_entry);
9570 static gboolean compose_headerentry_changed_cb(GtkWidget *entry,
9571 ComposeHeaderEntry *headerentry)
9573 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
9574 compose_create_header_entry(headerentry->compose);
9575 g_signal_handlers_disconnect_matched
9576 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
9577 0, 0, NULL, NULL, headerentry);
9579 /* Automatically scroll down */
9580 compose_show_first_last_header(headerentry->compose, FALSE);
9586 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
9588 GtkAdjustment *vadj;
9590 g_return_if_fail(compose);
9591 g_return_if_fail(GTK_IS_WIDGET(compose->header_table));
9592 g_return_if_fail(GTK_IS_VIEWPORT(compose->header_table->parent));
9594 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(compose->header_table->parent));
9595 gtk_adjustment_set_value(vadj, (show_first ? vadj->lower : vadj->upper));
9598 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
9599 const gchar *text, gint len, Compose *compose)
9601 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
9602 (G_OBJECT(compose->text), "paste_as_quotation"));
9605 g_return_if_fail(text != NULL);
9607 g_signal_handlers_block_by_func(G_OBJECT(buffer),
9608 G_CALLBACK(text_inserted),
9610 if (paste_as_quotation) {
9617 new_text = g_strndup(text, len);
9619 qmark = compose_quote_char_from_context(compose);
9621 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
9622 gtk_text_buffer_place_cursor(buffer, iter);
9624 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
9625 _("Quote format error at line %d."));
9626 quote_fmt_reset_vartable();
9628 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
9629 GINT_TO_POINTER(paste_as_quotation - 1));
9631 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
9632 gtk_text_buffer_place_cursor(buffer, iter);
9634 if (strcmp(text, "\n") || automatic_break
9635 || gtk_text_iter_starts_line(iter))
9636 gtk_text_buffer_insert(buffer, iter, text, len);
9638 debug_print("insert nowrap \\n\n");
9639 gtk_text_buffer_insert_with_tags_by_name(buffer,
9640 iter, text, len, "no_join", NULL);
9644 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
9646 compose_beautify_paragraph(compose, iter, FALSE);
9648 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
9649 gtk_text_buffer_delete_mark(buffer, mark);
9651 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
9652 G_CALLBACK(text_inserted),
9654 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
9656 if (prefs_common.autosave &&
9657 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
9658 compose->draft_timeout_tag != -2 /* disabled while loading */)
9659 compose->draft_timeout_tag = g_timeout_add
9660 (500, (GtkFunction) compose_defer_auto_save_draft, compose);
9662 static gint compose_defer_auto_save_draft(Compose *compose)
9664 compose->draft_timeout_tag = -1;
9665 compose_draft_cb((gpointer)compose, COMPOSE_AUTO_SAVE, NULL);
9670 static void compose_check_all(Compose *compose)
9672 if (compose->gtkaspell)
9673 gtkaspell_check_all(compose->gtkaspell);
9676 static void compose_highlight_all(Compose *compose)
9678 if (compose->gtkaspell)
9679 gtkaspell_highlight_all(compose->gtkaspell);
9682 static void compose_check_backwards(Compose *compose)
9684 if (compose->gtkaspell)
9685 gtkaspell_check_backwards(compose->gtkaspell);
9687 GtkItemFactory *ifactory;
9688 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
9689 menu_set_sensitive(ifactory, "/Edit/Check backwards misspelled word", FALSE);
9690 menu_set_sensitive(ifactory, "/Edit/Forward to next misspelled word", FALSE);
9694 static void compose_check_forwards_go(Compose *compose)
9696 if (compose->gtkaspell)
9697 gtkaspell_check_forwards_go(compose->gtkaspell);
9699 GtkItemFactory *ifactory;
9700 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
9701 menu_set_sensitive(ifactory, "/Edit/Check backwards misspelled word", FALSE);
9702 menu_set_sensitive(ifactory, "/Edit/Forward to next misspelled word", FALSE);
9708 *\brief Guess originating forward account from MsgInfo and several
9709 * "common preference" settings. Return NULL if no guess.
9711 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
9713 PrefsAccount *account = NULL;
9715 g_return_val_if_fail(msginfo, NULL);
9716 g_return_val_if_fail(msginfo->folder, NULL);
9717 g_return_val_if_fail(msginfo->folder->prefs, NULL);
9719 if (msginfo->folder->prefs->enable_default_account)
9720 account = account_find_from_id(msginfo->folder->prefs->default_account);
9723 account = msginfo->folder->folder->account;
9725 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
9727 Xstrdup_a(to, msginfo->to, return NULL);
9728 extract_address(to);
9729 account = account_find_from_address(to);
9732 if (!account && prefs_common.forward_account_autosel) {
9734 if (!procheader_get_header_from_msginfo
9735 (msginfo, cc,sizeof cc , "Cc:")) {
9736 gchar *buf = cc + strlen("Cc:");
9737 extract_address(buf);
9738 account = account_find_from_address(buf);
9742 if (!account && prefs_common.forward_account_autosel) {
9743 gchar deliveredto[BUFFSIZE];
9744 if (!procheader_get_header_from_msginfo
9745 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
9746 gchar *buf = deliveredto + strlen("Delivered-To:");
9747 extract_address(buf);
9748 account = account_find_from_address(buf);
9755 gboolean compose_close(Compose *compose)
9759 if (!g_mutex_trylock(compose->mutex)) {
9760 /* we have to wait for the (possibly deferred by auto-save)
9761 * drafting to be done, before destroying the compose under
9763 debug_print("waiting for drafting to finish...\n");
9764 compose_allow_user_actions(compose, FALSE);
9765 g_timeout_add (500, (GSourceFunc) compose_close, compose);
9768 g_return_val_if_fail(compose, FALSE);
9769 gtkut_widget_get_uposition(compose->window, &x, &y);
9770 prefs_common.compose_x = x;
9771 prefs_common.compose_y = y;
9772 g_mutex_unlock(compose->mutex);
9773 compose_destroy(compose);
9778 * Add entry field for each address in list.
9779 * \param compose E-Mail composition object.
9780 * \param listAddress List of (formatted) E-Mail addresses.
9782 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
9787 addr = ( gchar * ) node->data;
9788 compose_entry_append( compose, addr, COMPOSE_TO );
9789 node = g_list_next( node );
9793 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
9794 guint action, gboolean opening_multiple)
9797 GSList *new_msglist = NULL;
9798 MsgInfo *tmp_msginfo = NULL;
9799 gboolean originally_enc = FALSE;
9800 Compose *compose = NULL;
9802 g_return_if_fail(msgview != NULL);
9804 g_return_if_fail(msginfo_list != NULL);
9806 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
9807 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
9808 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
9810 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
9811 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
9812 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
9813 orig_msginfo, mimeinfo);
9814 if (tmp_msginfo != NULL) {
9815 new_msglist = g_slist_append(NULL, tmp_msginfo);
9817 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
9818 tmp_msginfo->folder = orig_msginfo->folder;
9819 tmp_msginfo->msgnum = orig_msginfo->msgnum;
9820 if (orig_msginfo->tags)
9821 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
9826 if (!opening_multiple)
9827 body = messageview_get_selection(msgview);
9830 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
9831 procmsg_msginfo_free(tmp_msginfo);
9832 g_slist_free(new_msglist);
9834 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
9836 if (originally_enc) {
9837 compose_force_encryption(compose, compose->account, FALSE);
9843 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
9846 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
9847 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
9848 GSList *cur = msginfo_list;
9849 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
9850 "messages. Opening the windows "
9851 "could take some time. Do you "
9852 "want to continue?"),
9853 g_slist_length(msginfo_list));
9854 if (g_slist_length(msginfo_list) > 9
9855 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
9856 != G_ALERTALTERNATE) {
9861 /* We'll open multiple compose windows */
9862 /* let the WM place the next windows */
9863 compose_force_window_origin = FALSE;
9864 for (; cur; cur = cur->next) {
9866 tmplist.data = cur->data;
9867 tmplist.next = NULL;
9868 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
9870 compose_force_window_origin = TRUE;
9872 /* forwarding multiple mails as attachments is done via a
9873 * single compose window */
9874 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
9878 void compose_set_position(Compose *compose, gint pos)
9880 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9882 gtkut_text_view_set_position(text, pos);
9885 gboolean compose_search_string(Compose *compose,
9886 const gchar *str, gboolean case_sens)
9888 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9890 return gtkut_text_view_search_string(text, str, case_sens);
9893 gboolean compose_search_string_backward(Compose *compose,
9894 const gchar *str, gboolean case_sens)
9896 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9898 return gtkut_text_view_search_string_backward(text, str, case_sens);
9901 /* allocate a msginfo structure and populate its data from a compose data structure */
9902 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
9904 MsgInfo *newmsginfo;
9906 gchar buf[BUFFSIZE];
9908 g_return_val_if_fail( compose != NULL, NULL );
9910 newmsginfo = procmsg_msginfo_new();
9913 get_rfc822_date(buf, sizeof(buf));
9914 newmsginfo->date = g_strdup(buf);
9917 if (compose->from_name) {
9918 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
9919 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
9923 if (compose->subject_entry)
9924 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
9926 /* to, cc, reply-to, newsgroups */
9927 for (list = compose->header_list; list; list = list->next) {
9928 gchar *header = gtk_editable_get_chars(
9930 GTK_COMBO(((ComposeHeaderEntry *)list->data)->combo)->entry), 0, -1);
9931 gchar *entry = gtk_editable_get_chars(
9932 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
9934 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
9935 if ( newmsginfo->to == NULL ) {
9936 newmsginfo->to = g_strdup(entry);
9937 } else if (entry && *entry) {
9938 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
9939 g_free(newmsginfo->to);
9940 newmsginfo->to = tmp;
9943 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
9944 if ( newmsginfo->cc == NULL ) {
9945 newmsginfo->cc = g_strdup(entry);
9946 } else if (entry && *entry) {
9947 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
9948 g_free(newmsginfo->cc);
9949 newmsginfo->cc = tmp;
9952 if ( strcasecmp(header,
9953 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
9954 if ( newmsginfo->newsgroups == NULL ) {
9955 newmsginfo->newsgroups = g_strdup(entry);
9956 } else if (entry && *entry) {
9957 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
9958 g_free(newmsginfo->newsgroups);
9959 newmsginfo->newsgroups = tmp;
9967 /* other data is unset */
9973 /* update compose's dictionaries from folder dict settings */
9974 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
9975 FolderItem *folder_item)
9977 g_return_if_fail(compose != NULL);
9979 if (compose->gtkaspell && folder_item && folder_item->prefs) {
9980 FolderItemPrefs *prefs = folder_item->prefs;
9982 if (prefs->enable_default_dictionary)
9983 gtkaspell_change_dict(compose->gtkaspell,
9984 prefs->default_dictionary, FALSE);
9985 if (folder_item->prefs->enable_default_alt_dictionary)
9986 gtkaspell_change_alt_dict(compose->gtkaspell,
9987 prefs->default_alt_dictionary);
9988 if (prefs->enable_default_dictionary
9989 || prefs->enable_default_alt_dictionary)
9990 compose_spell_menu_changed(compose);