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 gboolean 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"), NULL, NULL, 0, "<Branch>"},
732 {N_("/_Options/Character _encoding/Western European/ISO-8859-_1"),
733 ENC_ACTION(C_ISO_8859_1)},
734 {N_("/_Options/Character _encoding/Western European/ISO-8859-15"),
735 ENC_ACTION(C_ISO_8859_15)},
736 {N_("/_Options/Character _encoding/Western European/Windows-1252"),
737 ENC_ACTION(C_WINDOWS_1252)},
739 {N_("/_Options/Character _encoding/Central European (ISO-8859-_2)"),
740 ENC_ACTION(C_ISO_8859_2)},
742 {N_("/_Options/Character _encoding/Baltic"), NULL, NULL, 0, "<Branch>"},
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)},
748 {N_("/_Options/Character _encoding/Greek (ISO-8859-_7)"),
749 ENC_ACTION(C_ISO_8859_7)},
751 {N_("/_Options/Character _encoding/Hebrew"), NULL, NULL, 0, "<Branch>"},
752 {N_("/_Options/Character _encoding/Hebrew/ISO-8859-_8"),
753 ENC_ACTION(C_ISO_8859_8)},
754 {N_("/_Options/Character _encoding/Hebrew/Windows-1255"),
755 ENC_ACTION(C_WINDOWS_1255)},
757 {N_("/_Options/Character _encoding/Arabic"), NULL, NULL, 0, "<Branch>"},
758 {N_("/_Options/Character _encoding/Arabic/ISO-8859-_6"),
759 ENC_ACTION(C_ISO_8859_6)},
760 {N_("/_Options/Character _encoding/Arabic/Windows-1256"),
761 ENC_ACTION(C_CP1256)},
763 {N_("/_Options/Character _encoding/Turkish (ISO-8859-_9)"),
764 ENC_ACTION(C_ISO_8859_9)},
766 {N_("/_Options/Character _encoding/Cyrillic"), NULL, NULL, 0, "<Branch>"},
767 {N_("/_Options/Character _encoding/Cyrillic/ISO-8859-_5"),
768 ENC_ACTION(C_ISO_8859_5)},
769 {N_("/_Options/Character _encoding/Cyrillic/KOI8-_R"),
770 ENC_ACTION(C_KOI8_R)},
771 {N_("/_Options/Character _encoding/Cyrillic/KOI8-U"),
772 ENC_ACTION(C_KOI8_U)},
773 {N_("/_Options/Character _encoding/Cyrillic/Windows-1251"),
774 ENC_ACTION(C_WINDOWS_1251)},
776 {N_("/_Options/Character _encoding/Japanese"), NULL, NULL, 0, "<Branch>"},
777 {N_("/_Options/Character _encoding/Japanese/ISO-2022-_JP"),
778 ENC_ACTION(C_ISO_2022_JP)},
779 {N_("/_Options/Character _encoding/Japanese/ISO-2022-JP-2"),
780 ENC_ACTION(C_ISO_2022_JP_2)},
781 {N_("/_Options/Character _encoding/Japanese/_EUC-JP"),
782 ENC_ACTION(C_EUC_JP)},
783 {N_("/_Options/Character _encoding/Japanese/_Shift__JIS"),
784 ENC_ACTION(C_SHIFT_JIS)},
786 {N_("/_Options/Character _encoding/Chinese"), NULL, NULL, 0, "<Branch>"},
787 {N_("/_Options/Character _encoding/Chinese/Simplified (_GB2312)"),
788 ENC_ACTION(C_GB2312)},
789 {N_("/_Options/Character _encoding/Chinese/Simplified (GBK)"),
791 {N_("/_Options/Character _encoding/Chinese/Traditional (_Big5)"),
793 {N_("/_Options/Character _encoding/Chinese/Traditional (EUC-_TW)"),
794 ENC_ACTION(C_EUC_TW)},
796 {N_("/_Options/Character _encoding/Korean"), NULL, NULL, 0, "<Branch>"},
797 {N_("/_Options/Character _encoding/Korean/EUC-_KR"),
798 ENC_ACTION(C_EUC_KR)},
799 {N_("/_Options/Character _encoding/Korean/ISO-2022-KR"),
800 ENC_ACTION(C_ISO_2022_KR)},
802 {N_("/_Options/Character _encoding/Thai"), NULL, NULL, 0, "<Branch>"},
803 {N_("/_Options/Character _encoding/Thai/TIS-620"),
804 ENC_ACTION(C_TIS_620)},
805 {N_("/_Options/Character _encoding/Thai/Windows-874"),
806 ENC_ACTION(C_WINDOWS_874)},
808 {N_("/_Tools"), NULL, NULL, 0, "<Branch>"},
809 {N_("/_Tools/Show _ruler"), NULL, compose_toggle_ruler_cb, 0, "<ToggleItem>"},
810 {N_("/_Tools/_Address book"), "<shift><control>A", compose_address_cb , 0, NULL},
811 {N_("/_Tools/_Template"), NULL, NULL, 0, "<Branch>"},
812 {N_("/_Tools/Actio_ns"), NULL, NULL, 0, "<Branch>"},
813 {N_("/_Help"), NULL, NULL, 0, "<Branch>"},
814 {N_("/_Help/_About"), NULL, about_show, 0, NULL}
817 static GtkTargetEntry compose_mime_types[] =
819 {"text/uri-list", 0, 0},
820 {"UTF8_STRING", 0, 0},
824 static gboolean compose_put_existing_to_front(MsgInfo *info)
826 GList *compose_list = compose_get_compose_list();
830 for (elem = compose_list; elem != NULL && elem->data != NULL;
832 Compose *c = (Compose*)elem->data;
834 if (!c->targetinfo || !c->targetinfo->msgid ||
838 if (!strcmp(c->targetinfo->msgid, info->msgid)) {
839 gtkut_window_popup(c->window);
847 static GdkColor quote_color1 =
848 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
849 static GdkColor quote_color2 =
850 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
851 static GdkColor quote_color3 =
852 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
854 static GdkColor quote_bgcolor1 =
855 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
856 static GdkColor quote_bgcolor2 =
857 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
858 static GdkColor quote_bgcolor3 =
859 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
861 static GdkColor signature_color = {
868 static GdkColor uri_color = {
875 static void compose_create_tags(GtkTextView *text, Compose *compose)
877 GtkTextBuffer *buffer;
878 GdkColor black = {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
884 buffer = gtk_text_view_get_buffer(text);
886 if (prefs_common.enable_color) {
887 /* grab the quote colors, converting from an int to a GdkColor */
888 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
890 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
892 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
894 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_bgcol,
896 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_bgcol,
898 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_bgcol,
900 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
902 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
905 signature_color = quote_color1 = quote_color2 = quote_color3 =
906 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
909 if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
910 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
911 "foreground-gdk", "e_color1,
912 "paragraph-background-gdk", "e_bgcolor1,
914 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
915 "foreground-gdk", "e_color2,
916 "paragraph-background-gdk", "e_bgcolor2,
918 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
919 "foreground-gdk", "e_color3,
920 "paragraph-background-gdk", "e_bgcolor3,
923 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
924 "foreground-gdk", "e_color1,
926 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
927 "foreground-gdk", "e_color2,
929 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
930 "foreground-gdk", "e_color3,
934 compose->signature_tag = gtk_text_buffer_create_tag(buffer, "signature",
935 "foreground-gdk", &signature_color,
938 compose->uri_tag = gtk_text_buffer_create_tag(buffer, "link",
939 "foreground-gdk", &uri_color,
941 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
942 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
944 color[0] = quote_color1;
945 color[1] = quote_color2;
946 color[2] = quote_color3;
947 color[3] = quote_bgcolor1;
948 color[4] = quote_bgcolor2;
949 color[5] = quote_bgcolor3;
950 color[6] = signature_color;
951 color[7] = uri_color;
952 cmap = gdk_drawable_get_colormap(compose->window->window);
953 gdk_colormap_alloc_colors(cmap, color, 8, FALSE, TRUE, success);
955 for (i = 0; i < 8; i++) {
956 if (success[i] == FALSE) {
959 g_warning("Compose: color allocation failed.\n");
960 style = gtk_widget_get_style(GTK_WIDGET(text));
961 quote_color1 = quote_color2 = quote_color3 =
962 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 =
963 signature_color = uri_color = black;
968 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
969 GPtrArray *attach_files)
971 return compose_generic_new(account, mailto, NULL, attach_files, NULL);
974 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
976 return compose_generic_new(account, mailto, item, NULL, NULL);
979 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
981 return compose_generic_new( account, NULL, NULL, NULL, listAddress );
984 #define SCROLL_TO_CURSOR(compose) { \
985 GtkTextMark *cmark = gtk_text_buffer_get_insert( \
986 gtk_text_view_get_buffer( \
987 GTK_TEXT_VIEW(compose->text))); \
988 gtk_text_view_scroll_mark_onscreen( \
989 GTK_TEXT_VIEW(compose->text), \
993 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
994 GPtrArray *attach_files, GList *listAddress )
997 GtkTextView *textview;
998 GtkTextBuffer *textbuf;
1000 GtkItemFactory *ifactory;
1001 const gchar *subject_format = NULL;
1002 const gchar *body_format = NULL;
1004 if (item && item->prefs && item->prefs->enable_default_account)
1005 account = account_find_from_id(item->prefs->default_account);
1007 if (!account) account = cur_account;
1008 g_return_val_if_fail(account != NULL, NULL);
1010 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
1012 ifactory = gtk_item_factory_from_widget(compose->menubar);
1014 compose->replyinfo = NULL;
1015 compose->fwdinfo = NULL;
1017 textview = GTK_TEXT_VIEW(compose->text);
1018 textbuf = gtk_text_view_get_buffer(textview);
1019 compose_create_tags(textview, compose);
1021 undo_block(compose->undostruct);
1023 compose_set_dictionaries_from_folder_prefs(compose, item);
1026 if (account->auto_sig)
1027 compose_insert_sig(compose, FALSE);
1028 gtk_text_buffer_get_start_iter(textbuf, &iter);
1029 gtk_text_buffer_place_cursor(textbuf, &iter);
1031 if (account->protocol != A_NNTP) {
1032 if (mailto && *mailto != '\0') {
1033 compose_entries_set(compose, mailto);
1035 } else if (item && item->prefs->enable_default_to) {
1036 compose_entry_append(compose, item->prefs->default_to, COMPOSE_TO);
1037 compose_entry_mark_default_to(compose, item->prefs->default_to);
1039 if (item && item->ret_rcpt) {
1040 menu_set_active(ifactory, "/Options/Request Return Receipt", TRUE);
1044 compose_entry_append(compose, mailto, COMPOSE_NEWSGROUPS);
1045 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1046 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS);
1049 * CLAWS: just don't allow return receipt request, even if the user
1050 * may want to send an email. simple but foolproof.
1052 menu_set_sensitive(ifactory, "/Options/Request Return Receipt", FALSE);
1054 compose_add_field_list( compose, listAddress );
1056 if (item && item->prefs && item->prefs->compose_with_format) {
1057 subject_format = item->prefs->compose_subject_format;
1058 body_format = item->prefs->compose_body_format;
1059 } else if (account->compose_with_format) {
1060 subject_format = account->compose_subject_format;
1061 body_format = account->compose_body_format;
1062 } else if (prefs_common.compose_with_format) {
1063 subject_format = prefs_common.compose_subject_format;
1064 body_format = prefs_common.compose_body_format;
1067 if (subject_format || body_format) {
1068 MsgInfo* dummyinfo = NULL;
1071 && *subject_format != '\0' )
1073 gchar *subject = NULL;
1077 dummyinfo = compose_msginfo_new_from_compose(compose);
1079 /* decode \-escape sequences in the internal representation of the quote format */
1080 tmp = malloc(strlen(subject_format)+1);
1081 pref_get_unescaped_pref(tmp, subject_format);
1083 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1085 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account,
1086 compose->gtkaspell);
1088 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account);
1090 quote_fmt_scan_string(tmp);
1093 buf = quote_fmt_get_buffer();
1095 alertpanel_error(_("New message subject format error."));
1097 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1098 quote_fmt_reset_vartable();
1105 && *body_format != '\0' )
1108 GtkTextBuffer *buffer;
1109 GtkTextIter start, end;
1112 if ( dummyinfo == NULL )
1113 dummyinfo = compose_msginfo_new_from_compose(compose);
1115 text = GTK_TEXT_VIEW(compose->text);
1116 buffer = gtk_text_view_get_buffer(text);
1117 gtk_text_buffer_get_start_iter(buffer, &start);
1118 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1119 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1121 compose_quote_fmt(compose, dummyinfo,
1123 NULL, tmp, FALSE, TRUE,
1124 _("New message body format error at line %d."));
1125 quote_fmt_reset_vartable();
1130 procmsg_msginfo_free( dummyinfo );
1137 for (i = 0; i < attach_files->len; i++) {
1138 file = g_ptr_array_index(attach_files, i);
1139 compose_attach_append(compose, file, file, NULL);
1143 compose_show_first_last_header(compose, TRUE);
1145 /* Set save folder */
1146 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1147 gchar *folderidentifier;
1149 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
1150 folderidentifier = folder_item_get_identifier(item);
1151 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
1152 g_free(folderidentifier);
1155 gtk_widget_grab_focus(compose->header_last->entry);
1157 undo_unblock(compose->undostruct);
1159 if (prefs_common.auto_exteditor)
1160 compose_exec_ext_editor(compose);
1162 compose->draft_timeout_tag = -1;
1163 SCROLL_TO_CURSOR(compose);
1165 compose->modified = FALSE;
1166 compose_set_title(compose);
1170 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1171 gboolean override_pref)
1173 gchar *privacy = NULL;
1175 g_return_if_fail(compose != NULL);
1176 g_return_if_fail(account != NULL);
1178 if (override_pref == FALSE && account->default_encrypt_reply == FALSE)
1181 if (account->default_privacy_system
1182 && strlen(account->default_privacy_system)) {
1183 privacy = account->default_privacy_system;
1185 GSList *privacy_avail = privacy_get_system_ids();
1186 if (privacy_avail && g_slist_length(privacy_avail)) {
1187 privacy = (gchar *)(privacy_avail->data);
1190 if (privacy != NULL) {
1191 if (compose->privacy_system == NULL)
1192 compose->privacy_system = g_strdup(privacy);
1193 compose_update_privacy_system_menu_item(compose, FALSE);
1194 compose_use_encryption(compose, TRUE);
1198 static void compose_force_signing(Compose *compose, PrefsAccount *account)
1200 gchar *privacy = NULL;
1202 if (account->default_privacy_system
1203 && strlen(account->default_privacy_system)) {
1204 privacy = account->default_privacy_system;
1206 GSList *privacy_avail = privacy_get_system_ids();
1207 if (privacy_avail && g_slist_length(privacy_avail)) {
1208 privacy = (gchar *)(privacy_avail->data);
1211 if (privacy != NULL) {
1212 if (compose->privacy_system == NULL)
1213 compose->privacy_system = g_strdup(privacy);
1214 compose_update_privacy_system_menu_item(compose, FALSE);
1215 compose_use_signing(compose, TRUE);
1219 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1223 Compose *compose = NULL;
1224 GtkItemFactory *ifactory = NULL;
1226 g_return_val_if_fail(msginfo_list != NULL, NULL);
1228 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1229 g_return_val_if_fail(msginfo != NULL, NULL);
1231 list_len = g_slist_length(msginfo_list);
1235 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1236 FALSE, prefs_common.default_reply_list, FALSE, body);
1238 case COMPOSE_REPLY_WITH_QUOTE:
1239 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1240 FALSE, prefs_common.default_reply_list, FALSE, body);
1242 case COMPOSE_REPLY_WITHOUT_QUOTE:
1243 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1244 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1246 case COMPOSE_REPLY_TO_SENDER:
1247 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1248 FALSE, FALSE, TRUE, body);
1250 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1251 compose = compose_followup_and_reply_to(msginfo,
1252 COMPOSE_QUOTE_CHECK,
1253 FALSE, FALSE, body);
1255 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1256 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1257 FALSE, FALSE, TRUE, body);
1259 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1260 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1261 FALSE, FALSE, TRUE, NULL);
1263 case COMPOSE_REPLY_TO_ALL:
1264 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1265 TRUE, FALSE, FALSE, body);
1267 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1268 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1269 TRUE, FALSE, FALSE, body);
1271 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1272 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1273 TRUE, FALSE, FALSE, NULL);
1275 case COMPOSE_REPLY_TO_LIST:
1276 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1277 FALSE, TRUE, FALSE, body);
1279 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1280 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1281 FALSE, TRUE, FALSE, body);
1283 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1284 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1285 FALSE, TRUE, FALSE, NULL);
1287 case COMPOSE_FORWARD:
1288 if (prefs_common.forward_as_attachment) {
1289 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1292 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1296 case COMPOSE_FORWARD_INLINE:
1297 /* check if we reply to more than one Message */
1298 if (list_len == 1) {
1299 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1302 /* more messages FALL THROUGH */
1303 case COMPOSE_FORWARD_AS_ATTACH:
1304 compose = compose_forward_multiple(NULL, msginfo_list);
1306 case COMPOSE_REDIRECT:
1307 compose = compose_redirect(NULL, msginfo, FALSE);
1310 g_warning("compose_reply_mode(): invalid Compose Mode: %d\n", mode);
1313 if (compose == NULL) {
1314 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1317 ifactory = gtk_item_factory_from_widget(compose->menubar);
1319 compose->rmode = mode;
1320 switch (compose->rmode) {
1322 case COMPOSE_REPLY_WITH_QUOTE:
1323 case COMPOSE_REPLY_WITHOUT_QUOTE:
1324 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1325 debug_print("reply mode Normal\n");
1326 menu_set_active(ifactory, "/Options/Reply mode/Normal", TRUE);
1327 compose_reply_change_mode(compose, COMPOSE_REPLY, NULL); /* force update */
1329 case COMPOSE_REPLY_TO_SENDER:
1330 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1331 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1332 debug_print("reply mode Sender\n");
1333 menu_set_active(ifactory, "/Options/Reply mode/Sender", TRUE);
1335 case COMPOSE_REPLY_TO_ALL:
1336 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1337 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1338 debug_print("reply mode All\n");
1339 menu_set_active(ifactory, "/Options/Reply mode/All", TRUE);
1341 case COMPOSE_REPLY_TO_LIST:
1342 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1343 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1344 debug_print("reply mode List\n");
1345 menu_set_active(ifactory, "/Options/Reply mode/Mailing-list", TRUE);
1353 static Compose *compose_reply(MsgInfo *msginfo,
1354 ComposeQuoteMode quote_mode,
1360 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1361 to_sender, FALSE, body);
1364 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1365 ComposeQuoteMode quote_mode,
1370 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1371 to_sender, TRUE, body);
1374 static void compose_extract_original_charset(Compose *compose)
1376 MsgInfo *info = NULL;
1377 if (compose->replyinfo) {
1378 info = compose->replyinfo;
1379 } else if (compose->fwdinfo) {
1380 info = compose->fwdinfo;
1381 } else if (compose->targetinfo) {
1382 info = compose->targetinfo;
1385 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1386 MimeInfo *partinfo = mimeinfo;
1387 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1388 partinfo = procmime_mimeinfo_next(partinfo);
1390 compose->orig_charset =
1391 g_strdup(procmime_mimeinfo_get_parameter(
1392 partinfo, "charset"));
1394 procmime_mimeinfo_free_all(mimeinfo);
1398 #define SIGNAL_BLOCK(buffer) { \
1399 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1400 G_CALLBACK(compose_changed_cb), \
1402 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1403 G_CALLBACK(text_inserted), \
1407 #define SIGNAL_UNBLOCK(buffer) { \
1408 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1409 G_CALLBACK(compose_changed_cb), \
1411 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1412 G_CALLBACK(text_inserted), \
1416 static Compose *compose_generic_reply(MsgInfo *msginfo,
1417 ComposeQuoteMode quote_mode,
1418 gboolean to_all, gboolean to_ml,
1420 gboolean followup_and_reply_to,
1423 GtkItemFactory *ifactory;
1425 PrefsAccount *account = NULL;
1426 GtkTextView *textview;
1427 GtkTextBuffer *textbuf;
1428 gboolean quote = FALSE;
1429 const gchar *qmark = NULL;
1430 const gchar *body_fmt = NULL;
1432 g_return_val_if_fail(msginfo != NULL, NULL);
1433 g_return_val_if_fail(msginfo->folder != NULL, NULL);
1435 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1437 g_return_val_if_fail(account != NULL, NULL);
1439 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1441 compose->updating = TRUE;
1443 ifactory = gtk_item_factory_from_widget(compose->menubar);
1445 menu_set_active(ifactory, "/Options/Remove references", FALSE);
1446 menu_set_sensitive(ifactory, "/Options/Remove references", TRUE);
1448 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1449 if (!compose->replyinfo)
1450 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1452 compose_extract_original_charset(compose);
1454 if (msginfo->folder && msginfo->folder->ret_rcpt)
1455 menu_set_active(ifactory, "/Options/Request Return Receipt", TRUE);
1457 /* Set save folder */
1458 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1459 gchar *folderidentifier;
1461 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1462 folderidentifier = folder_item_get_identifier(msginfo->folder);
1463 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
1464 g_free(folderidentifier);
1467 if (compose_parse_header(compose, msginfo) < 0) return NULL;
1469 textview = (GTK_TEXT_VIEW(compose->text));
1470 textbuf = gtk_text_view_get_buffer(textview);
1471 compose_create_tags(textview, compose);
1473 undo_block(compose->undostruct);
1475 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1478 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1479 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1480 /* use the reply format of folder (if enabled), or the account's one
1481 (if enabled) or fallback to the global reply format, which is always
1482 enabled (even if empty), and use the relevant quotemark */
1484 if (msginfo->folder && msginfo->folder->prefs &&
1485 msginfo->folder->prefs->reply_with_format) {
1486 qmark = msginfo->folder->prefs->reply_quotemark;
1487 body_fmt = msginfo->folder->prefs->reply_body_format;
1489 } else if (account->reply_with_format) {
1490 qmark = account->reply_quotemark;
1491 body_fmt = account->reply_body_format;
1494 qmark = prefs_common.quotemark;
1495 body_fmt = prefs_common.quotefmt;
1500 /* empty quotemark is not allowed */
1501 if (qmark == NULL || *qmark == '\0')
1503 compose_quote_fmt(compose, compose->replyinfo,
1504 body_fmt, qmark, body, FALSE, TRUE,
1505 _("Message reply format error at line %d."));
1506 quote_fmt_reset_vartable();
1509 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1510 compose_force_encryption(compose, account, FALSE);
1513 SIGNAL_BLOCK(textbuf);
1515 if (account->auto_sig)
1516 compose_insert_sig(compose, FALSE);
1518 compose_wrap_all(compose);
1520 SIGNAL_UNBLOCK(textbuf);
1522 gtk_widget_grab_focus(compose->text);
1524 undo_unblock(compose->undostruct);
1526 if (prefs_common.auto_exteditor)
1527 compose_exec_ext_editor(compose);
1529 compose->modified = FALSE;
1530 compose_set_title(compose);
1532 compose->updating = FALSE;
1533 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1534 SCROLL_TO_CURSOR(compose);
1536 if (compose->deferred_destroy) {
1537 compose_destroy(compose);
1544 #define INSERT_FW_HEADER(var, hdr) \
1545 if (msginfo->var && *msginfo->var) { \
1546 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1547 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1548 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1551 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1552 gboolean as_attach, const gchar *body,
1553 gboolean no_extedit,
1557 GtkTextView *textview;
1558 GtkTextBuffer *textbuf;
1561 g_return_val_if_fail(msginfo != NULL, NULL);
1562 g_return_val_if_fail(msginfo->folder != NULL, NULL);
1565 !(account = compose_guess_forward_account_from_msginfo
1567 account = cur_account;
1569 compose = compose_create(account, msginfo->folder, COMPOSE_FORWARD, batch);
1571 compose->updating = TRUE;
1572 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1573 if (!compose->fwdinfo)
1574 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1576 compose_extract_original_charset(compose);
1578 if (msginfo->subject && *msginfo->subject) {
1579 gchar *buf, *buf2, *p;
1581 buf = p = g_strdup(msginfo->subject);
1582 p += subject_get_prefix_length(p);
1583 memmove(buf, p, strlen(p) + 1);
1585 buf2 = g_strdup_printf("Fw: %s", buf);
1586 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1592 textview = GTK_TEXT_VIEW(compose->text);
1593 textbuf = gtk_text_view_get_buffer(textview);
1594 compose_create_tags(textview, compose);
1596 undo_block(compose->undostruct);
1600 msgfile = procmsg_get_message_file(msginfo);
1601 if (!is_file_exist(msgfile))
1602 g_warning("%s: file not exist\n", msgfile);
1604 compose_attach_append(compose, msgfile, msgfile,
1609 const gchar *qmark = NULL;
1610 const gchar *body_fmt = prefs_common.fw_quotefmt;
1611 MsgInfo *full_msginfo;
1613 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1615 full_msginfo = procmsg_msginfo_copy(msginfo);
1617 /* use the forward format of folder (if enabled), or the account's one
1618 (if enabled) or fallback to the global forward format, which is always
1619 enabled (even if empty), and use the relevant quotemark */
1620 if (msginfo->folder && msginfo->folder->prefs &&
1621 msginfo->folder->prefs->forward_with_format) {
1622 qmark = msginfo->folder->prefs->forward_quotemark;
1623 body_fmt = msginfo->folder->prefs->forward_body_format;
1625 } else if (account->forward_with_format) {
1626 qmark = account->forward_quotemark;
1627 body_fmt = account->forward_body_format;
1630 qmark = prefs_common.fw_quotemark;
1631 body_fmt = prefs_common.fw_quotefmt;
1634 /* empty quotemark is not allowed */
1635 if (qmark == NULL || *qmark == '\0')
1638 compose_quote_fmt(compose, full_msginfo,
1639 body_fmt, qmark, body, FALSE, TRUE,
1640 _("Message forward format error at line %d."));
1641 quote_fmt_reset_vartable();
1642 compose_attach_parts(compose, msginfo);
1644 procmsg_msginfo_free(full_msginfo);
1647 SIGNAL_BLOCK(textbuf);
1649 if (account->auto_sig)
1650 compose_insert_sig(compose, FALSE);
1652 compose_wrap_all(compose);
1654 SIGNAL_UNBLOCK(textbuf);
1656 gtk_text_buffer_get_start_iter(textbuf, &iter);
1657 gtk_text_buffer_place_cursor(textbuf, &iter);
1659 gtk_widget_grab_focus(compose->header_last->entry);
1661 if (!no_extedit && prefs_common.auto_exteditor)
1662 compose_exec_ext_editor(compose);
1665 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1666 gchar *folderidentifier;
1668 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1669 folderidentifier = folder_item_get_identifier(msginfo->folder);
1670 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
1671 g_free(folderidentifier);
1674 undo_unblock(compose->undostruct);
1676 compose->modified = FALSE;
1677 compose_set_title(compose);
1679 compose->updating = FALSE;
1680 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1681 SCROLL_TO_CURSOR(compose);
1683 if (compose->deferred_destroy) {
1684 compose_destroy(compose);
1691 #undef INSERT_FW_HEADER
1693 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1696 GtkTextView *textview;
1697 GtkTextBuffer *textbuf;
1701 gboolean single_mail = TRUE;
1703 g_return_val_if_fail(msginfo_list != NULL, NULL);
1705 if (g_slist_length(msginfo_list) > 1)
1706 single_mail = FALSE;
1708 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1709 if (((MsgInfo *)msginfo->data)->folder == NULL)
1712 /* guess account from first selected message */
1714 !(account = compose_guess_forward_account_from_msginfo
1715 (msginfo_list->data)))
1716 account = cur_account;
1718 g_return_val_if_fail(account != NULL, NULL);
1720 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1721 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1722 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1725 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1727 compose->updating = TRUE;
1729 textview = GTK_TEXT_VIEW(compose->text);
1730 textbuf = gtk_text_view_get_buffer(textview);
1731 compose_create_tags(textview, compose);
1733 undo_block(compose->undostruct);
1734 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1735 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
1737 if (!is_file_exist(msgfile))
1738 g_warning("%s: file not exist\n", msgfile);
1740 compose_attach_append(compose, msgfile, msgfile,
1746 MsgInfo *info = (MsgInfo *)msginfo_list->data;
1747 if (info->subject && *info->subject) {
1748 gchar *buf, *buf2, *p;
1750 buf = p = g_strdup(info->subject);
1751 p += subject_get_prefix_length(p);
1752 memmove(buf, p, strlen(p) + 1);
1754 buf2 = g_strdup_printf("Fw: %s", buf);
1755 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1761 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
1762 _("Fw: multiple emails"));
1765 SIGNAL_BLOCK(textbuf);
1767 if (account->auto_sig)
1768 compose_insert_sig(compose, FALSE);
1770 compose_wrap_all(compose);
1772 SIGNAL_UNBLOCK(textbuf);
1774 gtk_text_buffer_get_start_iter(textbuf, &iter);
1775 gtk_text_buffer_place_cursor(textbuf, &iter);
1777 gtk_widget_grab_focus(compose->header_last->entry);
1778 undo_unblock(compose->undostruct);
1779 compose->modified = FALSE;
1780 compose_set_title(compose);
1782 compose->updating = FALSE;
1783 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1784 SCROLL_TO_CURSOR(compose);
1786 if (compose->deferred_destroy) {
1787 compose_destroy(compose);
1794 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
1796 GtkTextIter start = *iter;
1797 GtkTextIter end_iter;
1798 int start_pos = gtk_text_iter_get_offset(&start);
1800 if (!compose->account->sig_sep)
1803 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
1804 start_pos+strlen(compose->account->sig_sep));
1806 /* check sig separator */
1807 str = gtk_text_iter_get_text(&start, &end_iter);
1808 if (!strcmp(str, compose->account->sig_sep)) {
1810 /* check end of line (\n) */
1811 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
1812 start_pos+strlen(compose->account->sig_sep));
1813 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
1814 start_pos+strlen(compose->account->sig_sep)+1);
1815 tmp = gtk_text_iter_get_text(&start, &end_iter);
1816 if (!strcmp(tmp,"\n")) {
1828 static void compose_colorize_signature(Compose *compose)
1830 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
1832 GtkTextIter end_iter;
1833 gtk_text_buffer_get_start_iter(buffer, &iter);
1834 while (gtk_text_iter_forward_line(&iter))
1835 if (compose_is_sig_separator(compose, buffer, &iter)) {
1836 gtk_text_buffer_get_end_iter(buffer, &end_iter);
1837 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
1841 #define BLOCK_WRAP() { \
1842 prev_autowrap = compose->autowrap; \
1843 buffer = gtk_text_view_get_buffer( \
1844 GTK_TEXT_VIEW(compose->text)); \
1845 compose->autowrap = FALSE; \
1847 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1848 G_CALLBACK(compose_changed_cb), \
1850 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1851 G_CALLBACK(text_inserted), \
1854 #define UNBLOCK_WRAP() { \
1855 compose->autowrap = prev_autowrap; \
1856 if (compose->autowrap) { \
1857 gint old = compose->draft_timeout_tag; \
1858 compose->draft_timeout_tag = -2; \
1859 compose_wrap_all(compose); \
1860 compose->draft_timeout_tag = old; \
1863 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1864 G_CALLBACK(compose_changed_cb), \
1866 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1867 G_CALLBACK(text_inserted), \
1871 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
1873 Compose *compose = NULL;
1874 PrefsAccount *account = NULL;
1875 GtkTextView *textview;
1876 GtkTextBuffer *textbuf;
1880 gchar buf[BUFFSIZE];
1881 gboolean use_signing = FALSE;
1882 gboolean use_encryption = FALSE;
1883 gchar *privacy_system = NULL;
1884 int priority = PRIORITY_NORMAL;
1885 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
1887 g_return_val_if_fail(msginfo != NULL, NULL);
1888 g_return_val_if_fail(msginfo->folder != NULL, NULL);
1890 if (compose_put_existing_to_front(msginfo)) {
1894 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
1895 folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
1896 gchar queueheader_buf[BUFFSIZE];
1899 /* Select Account from queue headers */
1900 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1901 sizeof(queueheader_buf), "X-Claws-Account-Id:")) {
1902 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
1903 account = account_find_from_id(id);
1905 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1906 sizeof(queueheader_buf), "X-Sylpheed-Account-Id:")) {
1907 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
1908 account = account_find_from_id(id);
1910 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1911 sizeof(queueheader_buf), "NAID:")) {
1912 id = atoi(&queueheader_buf[strlen("NAID:")]);
1913 account = account_find_from_id(id);
1915 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1916 sizeof(queueheader_buf), "MAID:")) {
1917 id = atoi(&queueheader_buf[strlen("MAID:")]);
1918 account = account_find_from_id(id);
1920 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1921 sizeof(queueheader_buf), "S:")) {
1922 account = account_find_from_address(queueheader_buf);
1924 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1925 sizeof(queueheader_buf), "X-Claws-Sign:")) {
1926 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
1927 use_signing = param;
1930 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1931 sizeof(queueheader_buf), "X-Sylpheed-Sign:")) {
1932 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
1933 use_signing = param;
1936 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1937 sizeof(queueheader_buf), "X-Claws-Encrypt:")) {
1938 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
1939 use_encryption = param;
1941 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1942 sizeof(queueheader_buf), "X-Sylpheed-Encrypt:")) {
1943 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
1944 use_encryption = param;
1946 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1947 sizeof(queueheader_buf), "X-Claws-Privacy-System:")) {
1948 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
1950 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1951 sizeof(queueheader_buf), "X-Sylpheed-Privacy-System:")) {
1952 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
1954 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1955 sizeof(queueheader_buf), "X-Priority: ")) {
1956 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
1959 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1960 sizeof(queueheader_buf), "RMID:")) {
1961 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
1962 if (tokens[0] && tokens[1] && tokens[2]) {
1963 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
1964 if (orig_item != NULL) {
1965 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
1970 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1971 sizeof(queueheader_buf), "FMID:")) {
1972 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
1973 if (tokens[0] && tokens[1] && tokens[2]) {
1974 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
1975 if (orig_item != NULL) {
1976 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
1982 account = msginfo->folder->folder->account;
1985 if (!account && prefs_common.reedit_account_autosel) {
1986 gchar from[BUFFSIZE];
1987 if (!procheader_get_header_from_msginfo(msginfo, from, sizeof(from), "FROM:")) {
1988 extract_address(from);
1989 account = account_find_from_address(from);
1993 account = cur_account;
1995 g_return_val_if_fail(account != NULL, NULL);
1997 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
1999 compose->replyinfo = replyinfo;
2000 compose->fwdinfo = fwdinfo;
2002 compose->updating = TRUE;
2003 compose->priority = priority;
2005 if (privacy_system != NULL) {
2006 compose->privacy_system = privacy_system;
2007 compose_use_signing(compose, use_signing);
2008 compose_use_encryption(compose, use_encryption);
2009 compose_update_privacy_system_menu_item(compose, FALSE);
2011 activate_privacy_system(compose, account, FALSE);
2014 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2016 compose_extract_original_charset(compose);
2018 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2019 folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
2020 gchar queueheader_buf[BUFFSIZE];
2022 /* Set message save folder */
2023 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "SCF:")) {
2026 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2027 gtk_editable_delete_text(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
2028 gtk_editable_insert_text(GTK_EDITABLE(compose->savemsg_entry), &queueheader_buf[4], strlen(&queueheader_buf[4]), &startpos);
2030 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "RRCPT:")) {
2031 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2033 GtkItemFactory *ifactory;
2034 ifactory = gtk_item_factory_from_widget(compose->menubar);
2035 menu_set_active(ifactory, "/Options/Request Return Receipt", TRUE);
2040 if (compose_parse_header(compose, msginfo) < 0) {
2041 compose->updating = FALSE;
2042 compose_destroy(compose);
2045 compose_reedit_set_entry(compose, msginfo);
2047 textview = GTK_TEXT_VIEW(compose->text);
2048 textbuf = gtk_text_view_get_buffer(textview);
2049 compose_create_tags(textview, compose);
2051 mark = gtk_text_buffer_get_insert(textbuf);
2052 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2054 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2055 G_CALLBACK(compose_changed_cb),
2058 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2059 fp = procmime_get_first_encrypted_text_content(msginfo);
2061 compose_force_encryption(compose, account, TRUE);
2064 fp = procmime_get_first_text_content(msginfo);
2067 g_warning("Can't get text part\n");
2071 gboolean prev_autowrap = compose->autowrap;
2072 GtkTextBuffer *buffer = textbuf;
2074 while (fgets(buf, sizeof(buf), fp) != NULL) {
2076 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2082 compose_attach_parts(compose, msginfo);
2084 compose_colorize_signature(compose);
2086 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2087 G_CALLBACK(compose_changed_cb),
2090 gtk_widget_grab_focus(compose->text);
2092 if (prefs_common.auto_exteditor) {
2093 compose_exec_ext_editor(compose);
2095 compose->modified = FALSE;
2096 compose_set_title(compose);
2098 compose->updating = FALSE;
2099 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2100 SCROLL_TO_CURSOR(compose);
2102 if (compose->deferred_destroy) {
2103 compose_destroy(compose);
2107 compose->sig_str = compose_get_signature_str(compose);
2112 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2117 GtkItemFactory *ifactory;
2120 g_return_val_if_fail(msginfo != NULL, NULL);
2123 account = account_get_reply_account(msginfo,
2124 prefs_common.reply_account_autosel);
2125 g_return_val_if_fail(account != NULL, NULL);
2127 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2129 compose->updating = TRUE;
2131 ifactory = gtk_item_factory_from_widget(compose->menubar);
2132 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2133 compose->replyinfo = NULL;
2134 compose->fwdinfo = NULL;
2136 compose_show_first_last_header(compose, TRUE);
2138 gtk_widget_grab_focus(compose->header_last->entry);
2140 filename = procmsg_get_message_file(msginfo);
2142 if (filename == NULL) {
2143 compose->updating = FALSE;
2144 compose_destroy(compose);
2149 compose->redirect_filename = filename;
2151 /* Set save folder */
2152 item = msginfo->folder;
2153 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2154 gchar *folderidentifier;
2156 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
2157 folderidentifier = folder_item_get_identifier(item);
2158 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
2159 g_free(folderidentifier);
2162 compose_attach_parts(compose, msginfo);
2164 if (msginfo->subject)
2165 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2167 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2169 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2170 _("Message redirect format error at line %d."));
2171 quote_fmt_reset_vartable();
2172 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2174 compose_colorize_signature(compose);
2176 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
2177 menu_set_sensitive(ifactory, "/Add...", FALSE);
2178 menu_set_sensitive(ifactory, "/Remove", FALSE);
2179 menu_set_sensitive(ifactory, "/Properties...", FALSE);
2181 ifactory = gtk_item_factory_from_widget(compose->menubar);
2182 menu_set_sensitive(ifactory, "/Message/Save", FALSE);
2183 menu_set_sensitive(ifactory, "/Message/Insert file", FALSE);
2184 menu_set_sensitive(ifactory, "/Message/Attach file", FALSE);
2185 menu_set_sensitive(ifactory, "/Message/Insert signature", FALSE);
2186 menu_set_sensitive(ifactory, "/Edit", FALSE);
2187 menu_set_sensitive(ifactory, "/Options", FALSE);
2188 menu_set_sensitive(ifactory, "/Tools/Show ruler", FALSE);
2189 menu_set_sensitive(ifactory, "/Tools/Actions", FALSE);
2191 if (compose->toolbar->draft_btn)
2192 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2193 if (compose->toolbar->insert_btn)
2194 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2195 if (compose->toolbar->attach_btn)
2196 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2197 if (compose->toolbar->sig_btn)
2198 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2199 if (compose->toolbar->exteditor_btn)
2200 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2201 if (compose->toolbar->linewrap_current_btn)
2202 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2203 if (compose->toolbar->linewrap_all_btn)
2204 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2206 compose->modified = FALSE;
2207 compose_set_title(compose);
2208 compose->updating = FALSE;
2209 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2210 SCROLL_TO_CURSOR(compose);
2212 if (compose->deferred_destroy) {
2213 compose_destroy(compose);
2220 GList *compose_get_compose_list(void)
2222 return compose_list;
2225 void compose_entry_append(Compose *compose, const gchar *address,
2226 ComposeEntryType type)
2228 const gchar *header;
2230 gboolean in_quote = FALSE;
2231 if (!address || *address == '\0') return;
2238 header = N_("Bcc:");
2240 case COMPOSE_REPLYTO:
2241 header = N_("Reply-To:");
2243 case COMPOSE_NEWSGROUPS:
2244 header = N_("Newsgroups:");
2246 case COMPOSE_FOLLOWUPTO:
2247 header = N_( "Followup-To:");
2254 header = prefs_common_translated_header_name(header);
2256 cur = begin = (gchar *)address;
2258 /* we separate the line by commas, but not if we're inside a quoted
2260 while (*cur != '\0') {
2262 in_quote = !in_quote;
2263 if (*cur == ',' && !in_quote) {
2264 gchar *tmp = g_strdup(begin);
2266 tmp[cur-begin]='\0';
2269 while (*tmp == ' ' || *tmp == '\t')
2271 compose_add_header_entry(compose, header, tmp);
2278 gchar *tmp = g_strdup(begin);
2280 tmp[cur-begin]='\0';
2283 while (*tmp == ' ' || *tmp == '\t')
2285 compose_add_header_entry(compose, header, tmp);
2290 static void compose_entry_mark_default_to(Compose *compose, const gchar *mailto)
2292 static GdkColor yellow;
2293 static GdkColor black;
2294 static gboolean yellow_initialised = FALSE;
2298 if (!yellow_initialised) {
2299 gdk_color_parse("#f5f6be", &yellow);
2300 gdk_color_parse("#000000", &black);
2301 yellow_initialised = gdk_colormap_alloc_color(
2302 gdk_colormap_get_system(), &yellow, FALSE, TRUE);
2303 yellow_initialised &= gdk_colormap_alloc_color(
2304 gdk_colormap_get_system(), &black, FALSE, TRUE);
2307 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2308 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2309 if (gtk_entry_get_text(entry) &&
2310 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2311 if (yellow_initialised) {
2312 gtk_widget_modify_base(
2313 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2314 GTK_STATE_NORMAL, &yellow);
2315 gtk_widget_modify_text(
2316 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2317 GTK_STATE_NORMAL, &black);
2323 void compose_toolbar_cb(gint action, gpointer data)
2325 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2326 Compose *compose = (Compose*)toolbar_item->parent;
2328 g_return_if_fail(compose != NULL);
2332 compose_send_cb(compose, 0, NULL);
2335 compose_send_later_cb(compose, 0, NULL);
2338 compose_draft_cb(compose, COMPOSE_QUIT_EDITING, NULL);
2341 compose_insert_file_cb(compose, 0, NULL);
2344 compose_attach_cb(compose, 0, NULL);
2347 compose_insert_sig(compose, FALSE);
2350 compose_ext_editor_cb(compose, 0, NULL);
2352 case A_LINEWRAP_CURRENT:
2353 compose_beautify_paragraph(compose, NULL, TRUE);
2355 case A_LINEWRAP_ALL:
2356 compose_wrap_all_full(compose, TRUE);
2359 compose_address_cb(compose, 0, NULL);
2362 case A_CHECK_SPELLING:
2363 compose_check_all(compose);
2371 static void compose_entries_set(Compose *compose, const gchar *mailto)
2375 gchar *subject = NULL;
2379 gchar *attach = NULL;
2381 scan_mailto_url(mailto, &to, &cc, NULL, &subject, &body, &attach);
2384 compose_entry_append(compose, to, COMPOSE_TO);
2386 compose_entry_append(compose, cc, COMPOSE_CC);
2388 if (!g_utf8_validate (subject, -1, NULL)) {
2389 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2390 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2393 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2397 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2398 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2401 gboolean prev_autowrap = compose->autowrap;
2403 compose->autowrap = FALSE;
2405 mark = gtk_text_buffer_get_insert(buffer);
2406 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2408 if (!g_utf8_validate (body, -1, NULL)) {
2409 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2410 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2413 gtk_text_buffer_insert(buffer, &iter, body, -1);
2415 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2417 compose->autowrap = prev_autowrap;
2418 if (compose->autowrap)
2419 compose_wrap_all(compose);
2423 gchar *utf8_filename = conv_filename_to_utf8(attach);
2424 if (utf8_filename) {
2425 if (compose_attach_append(compose, attach, utf8_filename, NULL)) {
2426 alertpanel_notice(_("The file '%s' has been attached."), attach);
2428 g_free(utf8_filename);
2430 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2440 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2442 static HeaderEntry hentry[] = {{"Reply-To:", NULL, TRUE},
2443 {"Cc:", NULL, TRUE},
2444 {"References:", NULL, FALSE},
2445 {"Bcc:", NULL, TRUE},
2446 {"Newsgroups:", NULL, TRUE},
2447 {"Followup-To:", NULL, TRUE},
2448 {"List-Post:", NULL, FALSE},
2449 {"X-Priority:", NULL, FALSE},
2450 {NULL, NULL, FALSE}};
2466 g_return_val_if_fail(msginfo != NULL, -1);
2468 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2469 procheader_get_header_fields(fp, hentry);
2472 if (hentry[H_REPLY_TO].body != NULL) {
2473 if (hentry[H_REPLY_TO].body[0] != '\0') {
2475 conv_unmime_header(hentry[H_REPLY_TO].body,
2478 g_free(hentry[H_REPLY_TO].body);
2479 hentry[H_REPLY_TO].body = NULL;
2481 if (hentry[H_CC].body != NULL) {
2482 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL);
2483 g_free(hentry[H_CC].body);
2484 hentry[H_CC].body = NULL;
2486 if (hentry[H_REFERENCES].body != NULL) {
2487 if (compose->mode == COMPOSE_REEDIT)
2488 compose->references = hentry[H_REFERENCES].body;
2490 compose->references = compose_parse_references
2491 (hentry[H_REFERENCES].body, msginfo->msgid);
2492 g_free(hentry[H_REFERENCES].body);
2494 hentry[H_REFERENCES].body = NULL;
2496 if (hentry[H_BCC].body != NULL) {
2497 if (compose->mode == COMPOSE_REEDIT)
2499 conv_unmime_header(hentry[H_BCC].body, NULL);
2500 g_free(hentry[H_BCC].body);
2501 hentry[H_BCC].body = NULL;
2503 if (hentry[H_NEWSGROUPS].body != NULL) {
2504 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2505 hentry[H_NEWSGROUPS].body = NULL;
2507 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2508 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2509 compose->followup_to =
2510 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2513 g_free(hentry[H_FOLLOWUP_TO].body);
2514 hentry[H_FOLLOWUP_TO].body = NULL;
2516 if (hentry[H_LIST_POST].body != NULL) {
2519 extract_address(hentry[H_LIST_POST].body);
2520 if (hentry[H_LIST_POST].body[0] != '\0') {
2521 scan_mailto_url(hentry[H_LIST_POST].body,
2522 &to, NULL, NULL, NULL, NULL, NULL);
2524 g_free(compose->ml_post);
2525 compose->ml_post = to;
2528 g_free(hentry[H_LIST_POST].body);
2529 hentry[H_LIST_POST].body = NULL;
2532 /* CLAWS - X-Priority */
2533 if (compose->mode == COMPOSE_REEDIT)
2534 if (hentry[H_X_PRIORITY].body != NULL) {
2537 priority = atoi(hentry[H_X_PRIORITY].body);
2538 g_free(hentry[H_X_PRIORITY].body);
2540 hentry[H_X_PRIORITY].body = NULL;
2542 if (priority < PRIORITY_HIGHEST ||
2543 priority > PRIORITY_LOWEST)
2544 priority = PRIORITY_NORMAL;
2546 compose->priority = priority;
2549 if (compose->mode == COMPOSE_REEDIT) {
2550 if (msginfo->inreplyto && *msginfo->inreplyto)
2551 compose->inreplyto = g_strdup(msginfo->inreplyto);
2555 if (msginfo->msgid && *msginfo->msgid)
2556 compose->inreplyto = g_strdup(msginfo->msgid);
2558 if (!compose->references) {
2559 if (msginfo->msgid && *msginfo->msgid) {
2560 if (msginfo->inreplyto && *msginfo->inreplyto)
2561 compose->references =
2562 g_strdup_printf("<%s>\n\t<%s>",
2566 compose->references =
2567 g_strconcat("<", msginfo->msgid, ">",
2569 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
2570 compose->references =
2571 g_strconcat("<", msginfo->inreplyto, ">",
2579 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
2581 GSList *ref_id_list, *cur;
2585 ref_id_list = references_list_append(NULL, ref);
2586 if (!ref_id_list) return NULL;
2587 if (msgid && *msgid)
2588 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
2593 for (cur = ref_id_list; cur != NULL; cur = cur->next)
2594 /* "<" + Message-ID + ">" + CR+LF+TAB */
2595 len += strlen((gchar *)cur->data) + 5;
2597 if (len > MAX_REFERENCES_LEN) {
2598 /* remove second message-ID */
2599 if (ref_id_list && ref_id_list->next &&
2600 ref_id_list->next->next) {
2601 g_free(ref_id_list->next->data);
2602 ref_id_list = g_slist_remove
2603 (ref_id_list, ref_id_list->next->data);
2605 slist_free_strings(ref_id_list);
2606 g_slist_free(ref_id_list);
2613 new_ref = g_string_new("");
2614 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
2615 if (new_ref->len > 0)
2616 g_string_append(new_ref, "\n\t");
2617 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
2620 slist_free_strings(ref_id_list);
2621 g_slist_free(ref_id_list);
2623 new_ref_str = new_ref->str;
2624 g_string_free(new_ref, FALSE);
2629 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
2630 const gchar *fmt, const gchar *qmark,
2631 const gchar *body, gboolean rewrap,
2632 gboolean need_unescape,
2633 const gchar *err_msg)
2635 MsgInfo* dummyinfo = NULL;
2636 gchar *quote_str = NULL;
2638 gboolean prev_autowrap;
2639 const gchar *trimmed_body = body;
2640 gint cursor_pos = -1;
2641 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2642 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2647 SIGNAL_BLOCK(buffer);
2650 dummyinfo = compose_msginfo_new_from_compose(compose);
2651 msginfo = dummyinfo;
2654 if (qmark != NULL) {
2656 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
2657 compose->gtkaspell);
2659 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
2661 quote_fmt_scan_string(qmark);
2664 buf = quote_fmt_get_buffer();
2666 alertpanel_error(_("Quote mark format error."));
2668 Xstrdup_a(quote_str, buf, goto error)
2671 if (fmt && *fmt != '\0') {
2674 while (*trimmed_body == '\n')
2678 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account,
2679 compose->gtkaspell);
2681 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account);
2683 if (need_unescape) {
2686 /* decode \-escape sequences in the internal representation of the quote format */
2687 tmp = malloc(strlen(fmt)+1);
2688 pref_get_unescaped_pref(tmp, fmt);
2689 quote_fmt_scan_string(tmp);
2693 quote_fmt_scan_string(fmt);
2697 buf = quote_fmt_get_buffer();
2699 gint line = quote_fmt_get_line();
2700 alertpanel_error(err_msg, line);
2706 prev_autowrap = compose->autowrap;
2707 compose->autowrap = FALSE;
2709 mark = gtk_text_buffer_get_insert(buffer);
2710 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2711 if (g_utf8_validate(buf, -1, NULL)) {
2712 gtk_text_buffer_insert(buffer, &iter, buf, -1);
2714 gchar *tmpout = NULL;
2715 tmpout = conv_codeset_strdup
2716 (buf, conv_get_locale_charset_str_no_utf8(),
2718 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
2720 tmpout = g_malloc(strlen(buf)*2+1);
2721 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
2723 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
2727 cursor_pos = quote_fmt_get_cursor_pos();
2728 compose->set_cursor_pos = cursor_pos;
2729 if (cursor_pos == -1) {
2732 gtk_text_buffer_get_start_iter(buffer, &iter);
2733 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
2734 gtk_text_buffer_place_cursor(buffer, &iter);
2736 compose->autowrap = prev_autowrap;
2737 if (compose->autowrap && rewrap)
2738 compose_wrap_all(compose);
2745 SIGNAL_UNBLOCK(buffer);
2747 procmsg_msginfo_free( dummyinfo );
2752 /* if ml_post is of type addr@host and from is of type
2753 * addr-anything@host, return TRUE
2755 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
2757 gchar *left_ml = NULL;
2758 gchar *right_ml = NULL;
2759 gchar *left_from = NULL;
2760 gchar *right_from = NULL;
2761 gboolean result = FALSE;
2763 if (!ml_post || !from)
2766 left_ml = g_strdup(ml_post);
2767 if (strstr(left_ml, "@")) {
2768 right_ml = strstr(left_ml, "@")+1;
2769 *(strstr(left_ml, "@")) = '\0';
2772 left_from = g_strdup(from);
2773 if (strstr(left_from, "@")) {
2774 right_from = strstr(left_from, "@")+1;
2775 *(strstr(left_from, "@")) = '\0';
2778 if (left_ml && left_from && right_ml && right_from
2779 && !strncmp(left_from, left_ml, strlen(left_ml))
2780 && !strcmp(right_from, right_ml)) {
2789 static gboolean same_address(const gchar *addr1, const gchar *addr2)
2791 gchar *my_addr1, *my_addr2;
2793 if (!addr1 || !addr2)
2796 Xstrdup_a(my_addr1, addr1, return FALSE);
2797 Xstrdup_a(my_addr2, addr2, return FALSE);
2799 extract_address(my_addr1);
2800 extract_address(my_addr2);
2802 return !strcasecmp(my_addr1, my_addr2);
2805 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
2806 gboolean to_all, gboolean to_ml,
2808 gboolean followup_and_reply_to)
2810 GSList *cc_list = NULL;
2813 gchar *replyto = NULL;
2814 GHashTable *to_table;
2816 gboolean reply_to_ml = FALSE;
2817 gboolean default_reply_to = FALSE;
2819 g_return_if_fail(compose->account != NULL);
2820 g_return_if_fail(msginfo != NULL);
2822 reply_to_ml = to_ml && compose->ml_post;
2824 default_reply_to = msginfo->folder &&
2825 msginfo->folder->prefs->enable_default_reply_to;
2827 if (compose->account->protocol != A_NNTP) {
2828 if (reply_to_ml && !default_reply_to) {
2830 gboolean is_subscr = is_subscription(compose->ml_post,
2833 /* normal answer to ml post with a reply-to */
2834 compose_entry_append(compose,
2837 if (compose->replyto
2838 && !same_address(compose->ml_post, compose->replyto))
2839 compose_entry_append(compose,
2843 /* answer to subscription confirmation */
2844 if (compose->replyto)
2845 compose_entry_append(compose,
2848 else if (msginfo->from)
2849 compose_entry_append(compose,
2854 else if (!(to_all || to_sender) && default_reply_to) {
2855 compose_entry_append(compose,
2856 msginfo->folder->prefs->default_reply_to,
2858 compose_entry_mark_default_to(compose,
2859 msginfo->folder->prefs->default_reply_to);
2864 Xstrdup_a(tmp1, msginfo->from, return);
2865 extract_address(tmp1);
2866 if (to_all || to_sender ||
2867 !account_find_from_address(tmp1))
2868 compose_entry_append(compose,
2869 (compose->replyto && !to_sender)
2870 ? compose->replyto :
2871 msginfo->from ? msginfo->from : "",
2873 else if (!to_all && !to_sender) {
2874 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
2875 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
2876 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
2877 compose_entry_append(compose,
2878 msginfo->from ? msginfo->from : "",
2881 /* replying to own mail, use original recp */
2882 compose_entry_append(compose,
2883 msginfo->to ? msginfo->to : "",
2885 compose_entry_append(compose,
2886 msginfo->cc ? msginfo->cc : "",
2892 if (to_sender || (compose->followup_to &&
2893 !strncmp(compose->followup_to, "poster", 6)))
2894 compose_entry_append
2896 (compose->replyto ? compose->replyto :
2897 msginfo->from ? msginfo->from : ""),
2900 else if (followup_and_reply_to || to_all) {
2901 compose_entry_append
2903 (compose->replyto ? compose->replyto :
2904 msginfo->from ? msginfo->from : ""),
2907 compose_entry_append
2909 compose->followup_to ? compose->followup_to :
2910 compose->newsgroups ? compose->newsgroups : "",
2911 COMPOSE_NEWSGROUPS);
2914 compose_entry_append
2916 compose->followup_to ? compose->followup_to :
2917 compose->newsgroups ? compose->newsgroups : "",
2918 COMPOSE_NEWSGROUPS);
2921 if (msginfo->subject && *msginfo->subject) {
2925 buf = p = g_strdup(msginfo->subject);
2926 p += subject_get_prefix_length(p);
2927 memmove(buf, p, strlen(p) + 1);
2929 buf2 = g_strdup_printf("Re: %s", buf);
2930 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
2935 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
2937 if (to_ml && compose->ml_post) return;
2938 if (!to_all || compose->account->protocol == A_NNTP) return;
2940 if (compose->replyto) {
2941 Xstrdup_a(replyto, compose->replyto, return);
2942 extract_address(replyto);
2944 if (msginfo->from) {
2945 Xstrdup_a(from, msginfo->from, return);
2946 extract_address(from);
2949 if (replyto && from)
2950 cc_list = address_list_append_with_comments(cc_list, from);
2951 if (to_all && msginfo->folder &&
2952 msginfo->folder->prefs->enable_default_reply_to)
2953 cc_list = address_list_append_with_comments(cc_list,
2954 msginfo->folder->prefs->default_reply_to);
2955 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
2956 cc_list = address_list_append_with_comments(cc_list, compose->cc);
2958 to_table = g_hash_table_new(g_str_hash, g_str_equal);
2960 g_hash_table_insert(to_table, g_utf8_strdown(replyto, -1), GINT_TO_POINTER(1));
2961 if (compose->account) {
2962 g_hash_table_insert(to_table, g_utf8_strdown(compose->account->address, -1),
2963 GINT_TO_POINTER(1));
2965 /* remove address on To: and that of current account */
2966 for (cur = cc_list; cur != NULL; ) {
2967 GSList *next = cur->next;
2970 addr = g_utf8_strdown(cur->data, -1);
2971 extract_address(addr);
2973 if (GPOINTER_TO_INT(g_hash_table_lookup(to_table, addr)) == 1)
2974 cc_list = g_slist_remove(cc_list, cur->data);
2976 g_hash_table_insert(to_table, addr, GINT_TO_POINTER(1));
2980 hash_free_strings(to_table);
2981 g_hash_table_destroy(to_table);
2984 for (cur = cc_list; cur != NULL; cur = cur->next)
2985 compose_entry_append(compose, (gchar *)cur->data,
2987 slist_free_strings(cc_list);
2988 g_slist_free(cc_list);
2993 #define SET_ENTRY(entry, str) \
2996 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
2999 #define SET_ADDRESS(type, str) \
3002 compose_entry_append(compose, str, type); \
3005 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3007 g_return_if_fail(msginfo != NULL);
3009 SET_ENTRY(subject_entry, msginfo->subject);
3010 SET_ENTRY(from_name, msginfo->from);
3011 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3012 SET_ADDRESS(COMPOSE_CC, compose->cc);
3013 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3014 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3015 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3016 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3018 compose_update_priority_menu_item(compose);
3019 compose_update_privacy_system_menu_item(compose, FALSE);
3020 compose_show_first_last_header(compose, TRUE);
3026 static void compose_insert_sig(Compose *compose, gboolean replace)
3028 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3029 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3031 GtkTextIter iter, iter_end;
3033 gboolean prev_autowrap;
3034 gboolean found = FALSE;
3035 gboolean exists = FALSE;
3037 g_return_if_fail(compose->account != NULL);
3041 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3042 G_CALLBACK(compose_changed_cb),
3045 mark = gtk_text_buffer_get_insert(buffer);
3046 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3047 cur_pos = gtk_text_iter_get_offset (&iter);
3049 gtk_text_buffer_get_end_iter(buffer, &iter);
3051 exists = (compose->sig_str != NULL);
3054 GtkTextIter first_iter, start_iter, end_iter;
3056 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3058 if (!exists || compose->sig_str[0] == '\0')
3061 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3062 compose->signature_tag);
3065 /* include previous \n\n */
3066 gtk_text_iter_backward_chars(&first_iter, 2);
3067 start_iter = first_iter;
3068 end_iter = first_iter;
3070 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3071 compose->signature_tag);
3072 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3073 compose->signature_tag);
3075 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3081 g_free(compose->sig_str);
3082 compose->sig_str = compose_get_signature_str(compose);
3084 cur_pos = gtk_text_iter_get_offset(&iter);
3086 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3087 g_free(compose->sig_str);
3088 compose->sig_str = NULL;
3090 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3092 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3093 gtk_text_iter_forward_chars(&iter, 2);
3094 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3095 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3097 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3098 cur_pos = gtk_text_buffer_get_char_count (buffer);
3100 /* put the cursor where it should be
3101 * either where the quote_fmt says, either before the signature */
3102 if (compose->set_cursor_pos < 0)
3103 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3105 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3106 compose->set_cursor_pos);
3108 gtk_text_buffer_place_cursor(buffer, &iter);
3109 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3110 G_CALLBACK(compose_changed_cb),
3116 static gchar *compose_get_signature_str(Compose *compose)
3118 gchar *sig_body = NULL;
3119 gchar *sig_str = NULL;
3120 gchar *utf8_sig_str = NULL;
3122 g_return_val_if_fail(compose->account != NULL, NULL);
3124 if (!compose->account->sig_path)
3127 if (compose->account->sig_type == SIG_FILE) {
3128 if (!is_file_or_fifo_exist(compose->account->sig_path)) {
3129 g_warning("can't open signature file: %s\n",
3130 compose->account->sig_path);
3135 if (compose->account->sig_type == SIG_COMMAND)
3136 sig_body = get_command_output(compose->account->sig_path);
3140 tmp = file_read_to_str(compose->account->sig_path);
3143 sig_body = normalize_newlines(tmp);
3147 if (compose->account->sig_sep) {
3148 sig_str = g_strconcat("\n\n", compose->account->sig_sep, "\n", sig_body,
3152 sig_str = g_strconcat("\n\n", sig_body, NULL);
3155 if (g_utf8_validate(sig_str, -1, NULL) == TRUE)
3156 utf8_sig_str = sig_str;
3158 utf8_sig_str = conv_codeset_strdup
3159 (sig_str, conv_get_locale_charset_str_no_utf8(),
3165 return utf8_sig_str;
3168 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3171 GtkTextBuffer *buffer;
3174 const gchar *cur_encoding;
3175 gchar buf[BUFFSIZE];
3178 gboolean prev_autowrap;
3179 gboolean badtxt = FALSE;
3181 g_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3183 if ((fp = g_fopen(file, "rb")) == NULL) {
3184 FILE_OP_ERROR(file, "fopen");
3185 return COMPOSE_INSERT_READ_ERROR;
3188 prev_autowrap = compose->autowrap;
3189 compose->autowrap = FALSE;
3191 text = GTK_TEXT_VIEW(compose->text);
3192 buffer = gtk_text_view_get_buffer(text);
3193 mark = gtk_text_buffer_get_insert(buffer);
3194 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3196 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3197 G_CALLBACK(text_inserted),
3200 cur_encoding = conv_get_locale_charset_str_no_utf8();
3202 while (fgets(buf, sizeof(buf), fp) != NULL) {
3205 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3206 str = g_strdup(buf);
3208 str = conv_codeset_strdup
3209 (buf, cur_encoding, CS_INTERNAL);
3212 /* strip <CR> if DOS/Windows file,
3213 replace <CR> with <LF> if Macintosh file. */
3216 if (len > 0 && str[len - 1] != '\n') {
3218 if (str[len] == '\r') str[len] = '\n';
3221 gtk_text_buffer_insert(buffer, &iter, str, -1);
3225 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3226 G_CALLBACK(text_inserted),
3228 compose->autowrap = prev_autowrap;
3229 if (compose->autowrap)
3230 compose_wrap_all(compose);
3235 return COMPOSE_INSERT_INVALID_CHARACTER;
3237 return COMPOSE_INSERT_SUCCESS;
3240 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3241 const gchar *filename,
3242 const gchar *content_type)
3250 GtkListStore *store;
3252 gboolean has_binary = FALSE;
3254 if (!is_file_exist(file)) {
3255 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3256 gboolean result = FALSE;
3257 if (file_from_uri && is_file_exist(file_from_uri)) {
3258 result = compose_attach_append(
3259 compose, file_from_uri,
3263 g_free(file_from_uri);
3266 alertpanel_error("File %s doesn't exist\n", filename);
3269 if ((size = get_file_size(file)) < 0) {
3270 alertpanel_error("Can't get file size of %s\n", filename);
3274 alertpanel_error(_("File %s is empty."), filename);
3277 if ((fp = g_fopen(file, "rb")) == NULL) {
3278 alertpanel_error(_("Can't read %s."), filename);
3283 ainfo = g_new0(AttachInfo, 1);
3284 auto_ainfo = g_auto_pointer_new_with_free
3285 (ainfo, (GFreeFunc) compose_attach_info_free);
3286 ainfo->file = g_strdup(file);
3289 ainfo->content_type = g_strdup(content_type);
3290 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3292 MsgFlags flags = {0, 0};
3294 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3295 ainfo->encoding = ENC_7BIT;
3297 ainfo->encoding = ENC_8BIT;
3299 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3300 if (msginfo && msginfo->subject)
3301 name = g_strdup(msginfo->subject);
3303 name = g_path_get_basename(filename ? filename : file);
3305 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3307 procmsg_msginfo_free(msginfo);
3309 if (!g_ascii_strncasecmp(content_type, "text", 4))
3310 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3312 ainfo->encoding = ENC_BASE64;
3313 name = g_path_get_basename(filename ? filename : file);
3314 ainfo->name = g_strdup(name);
3318 ainfo->content_type = procmime_get_mime_type(file);
3319 if (!ainfo->content_type) {
3320 ainfo->content_type =
3321 g_strdup("application/octet-stream");
3322 ainfo->encoding = ENC_BASE64;
3323 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text", 4))
3325 procmime_get_encoding_for_text_file(file, &has_binary);
3327 ainfo->encoding = ENC_BASE64;
3328 name = g_path_get_basename(filename ? filename : file);
3329 ainfo->name = g_strdup(name);
3333 if (ainfo->name != NULL
3334 && !strcmp(ainfo->name, ".")) {
3335 g_free(ainfo->name);
3339 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3340 g_free(ainfo->content_type);
3341 ainfo->content_type = g_strdup("application/octet-stream");
3345 size_text = to_human_readable(size);
3347 store = GTK_LIST_STORE(gtk_tree_view_get_model
3348 (GTK_TREE_VIEW(compose->attach_clist)));
3350 gtk_list_store_append(store, &iter);
3351 gtk_list_store_set(store, &iter,
3352 COL_MIMETYPE, ainfo->content_type,
3353 COL_SIZE, size_text,
3354 COL_NAME, ainfo->name,
3356 COL_AUTODATA, auto_ainfo,
3359 g_auto_pointer_free(auto_ainfo);
3363 static void compose_use_signing(Compose *compose, gboolean use_signing)
3365 GtkItemFactory *ifactory;
3366 GtkWidget *menuitem = NULL;
3368 compose->use_signing = use_signing;
3369 ifactory = gtk_item_factory_from_widget(compose->menubar);
3370 menuitem = gtk_item_factory_get_item
3371 (ifactory, "/Options/Sign");
3372 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
3376 static void compose_use_encryption(Compose *compose, gboolean use_encryption)
3378 GtkItemFactory *ifactory;
3379 GtkWidget *menuitem = NULL;
3381 compose->use_encryption = use_encryption;
3382 ifactory = gtk_item_factory_from_widget(compose->menubar);
3383 menuitem = gtk_item_factory_get_item
3384 (ifactory, "/Options/Encrypt");
3386 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
3390 #define NEXT_PART_NOT_CHILD(info) \
3392 node = info->node; \
3393 while (node->children) \
3394 node = g_node_last_child(node); \
3395 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3398 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3402 MimeInfo *firsttext = NULL;
3403 MimeInfo *encrypted = NULL;
3406 const gchar *partname = NULL;
3408 mimeinfo = procmime_scan_message(msginfo);
3409 if (!mimeinfo) return;
3411 if (mimeinfo->node->children == NULL) {
3412 procmime_mimeinfo_free_all(mimeinfo);
3416 /* find first content part */
3417 child = (MimeInfo *) mimeinfo->node->children->data;
3418 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3419 child = (MimeInfo *)child->node->children->data;
3421 if (child->type == MIMETYPE_TEXT) {
3423 debug_print("First text part found\n");
3424 } else if (compose->mode == COMPOSE_REEDIT &&
3425 child->type == MIMETYPE_APPLICATION &&
3426 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3427 encrypted = (MimeInfo *)child->node->parent->data;
3430 child = (MimeInfo *) mimeinfo->node->children->data;
3431 while (child != NULL) {
3434 if (child == encrypted) {
3435 /* skip this part of tree */
3436 NEXT_PART_NOT_CHILD(child);
3440 if (child->type == MIMETYPE_MULTIPART) {
3441 /* get the actual content */
3442 child = procmime_mimeinfo_next(child);
3446 if (child == firsttext) {
3447 child = procmime_mimeinfo_next(child);
3451 outfile = procmime_get_tmp_file_name(child);
3452 if ((err = procmime_get_part(outfile, child)) < 0)
3453 g_warning("Can't get the part of multipart message. (%s)", strerror(-err));
3455 gchar *content_type;
3457 content_type = procmime_get_content_type_str(child->type, child->subtype);
3459 /* if we meet a pgp signature, we don't attach it, but
3460 * we force signing. */
3461 if ((strcmp(content_type, "application/pgp-signature") &&
3462 strcmp(content_type, "application/pkcs7-signature") &&
3463 strcmp(content_type, "application/x-pkcs7-signature"))
3464 || compose->mode == COMPOSE_REDIRECT) {
3465 partname = procmime_mimeinfo_get_parameter(child, "filename");
3466 if (partname == NULL)
3467 partname = procmime_mimeinfo_get_parameter(child, "name");
3468 if (partname == NULL)
3470 compose_attach_append(compose, outfile,
3471 partname, content_type);
3473 compose_force_signing(compose, compose->account);
3475 g_free(content_type);
3478 NEXT_PART_NOT_CHILD(child);
3480 procmime_mimeinfo_free_all(mimeinfo);
3483 #undef NEXT_PART_NOT_CHILD
3488 WAIT_FOR_INDENT_CHAR,
3489 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3492 /* return indent length, we allow:
3493 indent characters followed by indent characters or spaces/tabs,
3494 alphabets and numbers immediately followed by indent characters,
3495 and the repeating sequences of the above
3496 If quote ends with multiple spaces, only the first one is included. */
3497 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3498 const GtkTextIter *start, gint *len)
3500 GtkTextIter iter = *start;
3504 IndentState state = WAIT_FOR_INDENT_CHAR;
3507 gint alnum_count = 0;
3508 gint space_count = 0;
3511 if (prefs_common.quote_chars == NULL) {
3515 while (!gtk_text_iter_ends_line(&iter)) {
3516 wc = gtk_text_iter_get_char(&iter);
3517 if (g_unichar_iswide(wc))
3519 clen = g_unichar_to_utf8(wc, ch);
3523 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
3524 is_space = g_unichar_isspace(wc);
3526 if (state == WAIT_FOR_INDENT_CHAR) {
3527 if (!is_indent && !g_unichar_isalnum(wc))
3530 quote_len += alnum_count + space_count + 1;
3531 alnum_count = space_count = 0;
3532 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
3535 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
3536 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
3540 else if (is_indent) {
3541 quote_len += alnum_count + space_count + 1;
3542 alnum_count = space_count = 0;
3545 state = WAIT_FOR_INDENT_CHAR;
3549 gtk_text_iter_forward_char(&iter);
3552 if (quote_len > 0 && space_count > 0)
3558 if (quote_len > 0) {
3560 gtk_text_iter_forward_chars(&iter, quote_len);
3561 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
3567 /* return TRUE if the line is itemized */
3568 static gboolean compose_is_itemized(GtkTextBuffer *buffer,
3569 const GtkTextIter *start)
3571 GtkTextIter iter = *start;
3576 if (gtk_text_iter_ends_line(&iter))
3580 wc = gtk_text_iter_get_char(&iter);
3581 if (!g_unichar_isspace(wc))
3583 gtk_text_iter_forward_char(&iter);
3584 if (gtk_text_iter_ends_line(&iter))
3588 clen = g_unichar_to_utf8(wc, ch);
3592 if (!strchr("*-+", ch[0]))
3595 gtk_text_iter_forward_char(&iter);
3596 if (gtk_text_iter_ends_line(&iter))
3598 wc = gtk_text_iter_get_char(&iter);
3599 if (g_unichar_isspace(wc))
3605 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
3606 const GtkTextIter *start,
3607 GtkTextIter *break_pos,
3611 GtkTextIter iter = *start, line_end = *start;
3612 PangoLogAttr *attrs;
3619 gboolean can_break = FALSE;
3620 gboolean do_break = FALSE;
3621 gboolean was_white = FALSE;
3622 gboolean prev_dont_break = FALSE;
3624 gtk_text_iter_forward_to_line_end(&line_end);
3625 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
3626 len = g_utf8_strlen(str, -1);
3630 g_warning("compose_get_line_break_pos: len = 0!\n");
3634 /* g_print("breaking line: %d: %s (len = %d)\n",
3635 gtk_text_iter_get_line(&iter), str, len); */
3637 attrs = g_new(PangoLogAttr, len + 1);
3639 pango_default_break(str, -1, NULL, attrs, len + 1);
3643 /* skip quote and leading spaces */
3644 for (i = 0; *p != '\0' && i < len; i++) {
3647 wc = g_utf8_get_char(p);
3648 if (i >= quote_len && !g_unichar_isspace(wc))
3650 if (g_unichar_iswide(wc))
3652 else if (*p == '\t')
3656 p = g_utf8_next_char(p);
3659 for (; *p != '\0' && i < len; i++) {
3660 PangoLogAttr *attr = attrs + i;
3664 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
3667 was_white = attr->is_white;
3669 /* don't wrap URI */
3670 if ((uri_len = get_uri_len(p)) > 0) {
3672 if (pos > 0 && col > max_col) {
3682 wc = g_utf8_get_char(p);
3683 if (g_unichar_iswide(wc)) {
3685 if (prev_dont_break && can_break && attr->is_line_break)
3687 } else if (*p == '\t')
3691 if (pos > 0 && col > max_col) {
3696 if (*p == '-' || *p == '/')
3697 prev_dont_break = TRUE;
3699 prev_dont_break = FALSE;
3701 p = g_utf8_next_char(p);
3705 debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col);
3710 *break_pos = *start;
3711 gtk_text_iter_set_line_offset(break_pos, pos);
3716 static gboolean compose_join_next_line(Compose *compose,
3717 GtkTextBuffer *buffer,
3719 const gchar *quote_str)
3721 GtkTextIter iter_ = *iter, cur, prev, next, end;
3722 PangoLogAttr attrs[3];
3724 gchar *next_quote_str;
3727 gboolean keep_cursor = FALSE;
3729 if (!gtk_text_iter_forward_line(&iter_) ||
3730 gtk_text_iter_ends_line(&iter_))
3733 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
3735 if ((quote_str || next_quote_str) &&
3736 strcmp2(quote_str, next_quote_str) != 0) {
3737 g_free(next_quote_str);
3740 g_free(next_quote_str);
3743 if (quote_len > 0) {
3744 gtk_text_iter_forward_chars(&end, quote_len);
3745 if (gtk_text_iter_ends_line(&end))
3749 /* don't join itemized lines */
3750 if (compose_is_itemized(buffer, &end))
3753 /* don't join signature separator */
3754 if (compose_is_sig_separator(compose, buffer, &iter_))
3757 /* delete quote str */
3759 gtk_text_buffer_delete(buffer, &iter_, &end);
3761 /* don't join line breaks put by the user */
3763 gtk_text_iter_backward_char(&cur);
3764 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
3765 gtk_text_iter_forward_char(&cur);
3769 gtk_text_iter_forward_char(&cur);
3770 /* delete linebreak and extra spaces */
3771 while (gtk_text_iter_backward_char(&cur)) {
3772 wc1 = gtk_text_iter_get_char(&cur);
3773 if (!g_unichar_isspace(wc1))
3778 while (!gtk_text_iter_ends_line(&cur)) {
3779 wc1 = gtk_text_iter_get_char(&cur);
3780 if (!g_unichar_isspace(wc1))
3782 gtk_text_iter_forward_char(&cur);
3785 if (!gtk_text_iter_equal(&prev, &next)) {
3788 mark = gtk_text_buffer_get_insert(buffer);
3789 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
3790 if (gtk_text_iter_equal(&prev, &cur))
3792 gtk_text_buffer_delete(buffer, &prev, &next);
3796 /* insert space if required */
3797 gtk_text_iter_backward_char(&prev);
3798 wc1 = gtk_text_iter_get_char(&prev);
3799 wc2 = gtk_text_iter_get_char(&next);
3800 gtk_text_iter_forward_char(&next);
3801 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
3802 pango_default_break(str, -1, NULL, attrs, 3);
3803 if (!attrs[1].is_line_break ||
3804 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
3805 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
3807 gtk_text_iter_backward_char(&iter_);
3808 gtk_text_buffer_place_cursor(buffer, &iter_);
3817 #define ADD_TXT_POS(bp_, ep_, pti_) \
3818 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
3819 last = last->next; \
3820 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
3821 last->next = NULL; \
3823 g_warning("alloc error scanning URIs\n"); \
3826 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
3828 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3829 GtkTextBuffer *buffer;
3830 GtkTextIter iter, break_pos, end_of_line;
3831 gchar *quote_str = NULL;
3833 gboolean wrap_quote = prefs_common.linewrap_quote;
3834 gboolean prev_autowrap = compose->autowrap;
3835 gint startq_offset = -1, noq_offset = -1;
3836 gint uri_start = -1, uri_stop = -1;
3837 gint nouri_start = -1, nouri_stop = -1;
3838 gint num_blocks = 0;
3839 gint quotelevel = -1;
3840 gboolean modified = force;
3841 gboolean removed = FALSE;
3842 gboolean modified_before_remove = FALSE;
3844 gboolean start = TRUE;
3849 if (compose->draft_timeout_tag == -2) {
3853 compose->autowrap = FALSE;
3855 buffer = gtk_text_view_get_buffer(text);
3856 undo_wrapping(compose->undostruct, TRUE);
3861 mark = gtk_text_buffer_get_insert(buffer);
3862 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3866 if (compose->draft_timeout_tag == -2) {
3867 if (gtk_text_iter_ends_line(&iter)) {
3868 while (gtk_text_iter_ends_line(&iter) &&
3869 gtk_text_iter_forward_line(&iter))
3872 while (gtk_text_iter_backward_line(&iter)) {
3873 if (gtk_text_iter_ends_line(&iter)) {
3874 gtk_text_iter_forward_line(&iter);
3880 /* move to line start */
3881 gtk_text_iter_set_line_offset(&iter, 0);
3883 /* go until paragraph end (empty line) */
3884 while (start || !gtk_text_iter_ends_line(&iter)) {
3885 gchar *scanpos = NULL;
3886 /* parse table - in order of priority */
3888 const gchar *needle; /* token */
3890 /* token search function */
3891 gchar *(*search) (const gchar *haystack,
3892 const gchar *needle);
3893 /* part parsing function */
3894 gboolean (*parse) (const gchar *start,
3895 const gchar *scanpos,
3899 /* part to URI function */
3900 gchar *(*build_uri) (const gchar *bp,
3904 static struct table parser[] = {
3905 {"http://", strcasestr, get_uri_part, make_uri_string},
3906 {"https://", strcasestr, get_uri_part, make_uri_string},
3907 {"ftp://", strcasestr, get_uri_part, make_uri_string},
3908 {"sftp://", strcasestr, get_uri_part, make_uri_string},
3909 {"www.", strcasestr, get_uri_part, make_http_string},
3910 {"mailto:", strcasestr, get_uri_part, make_uri_string},
3911 {"@", strcasestr, get_email_part, make_email_string}
3913 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
3914 gint last_index = PARSE_ELEMS;
3916 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
3920 if (!prev_autowrap && num_blocks == 0) {
3922 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3923 G_CALLBACK(text_inserted),
3926 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
3929 uri_start = uri_stop = -1;
3931 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
3934 debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str);
3935 if (startq_offset == -1)
3936 startq_offset = gtk_text_iter_get_offset(&iter);
3937 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
3938 if (quotelevel > 2) {
3939 /* recycle colors */
3940 if (prefs_common.recycle_quote_colors)
3949 if (startq_offset == -1)
3950 noq_offset = gtk_text_iter_get_offset(&iter);
3954 if (prev_autowrap == FALSE && !force && !wrap_quote) {
3957 if (gtk_text_iter_ends_line(&iter)) {
3959 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
3960 prefs_common.linewrap_len,
3962 GtkTextIter prev, next, cur;
3964 if (prev_autowrap != FALSE || force) {
3965 compose->automatic_break = TRUE;
3967 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
3968 compose->automatic_break = FALSE;
3969 } else if (quote_str && wrap_quote) {
3970 compose->automatic_break = TRUE;
3972 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
3973 compose->automatic_break = FALSE;
3976 /* remove trailing spaces */
3978 gtk_text_iter_backward_char(&cur);
3980 while (!gtk_text_iter_starts_line(&cur)) {
3983 gtk_text_iter_backward_char(&cur);
3984 wc = gtk_text_iter_get_char(&cur);
3985 if (!g_unichar_isspace(wc))
3989 if (!gtk_text_iter_equal(&prev, &next)) {
3990 gtk_text_buffer_delete(buffer, &prev, &next);
3992 gtk_text_iter_forward_char(&break_pos);
3996 gtk_text_buffer_insert(buffer, &break_pos,
4000 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4002 /* move iter to current line start */
4003 gtk_text_iter_set_line_offset(&iter, 0);
4010 /* move iter to next line start */
4016 if (!prev_autowrap && num_blocks > 0) {
4018 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4019 G_CALLBACK(text_inserted),
4023 while (!gtk_text_iter_ends_line(&end_of_line)) {
4024 gtk_text_iter_forward_char(&end_of_line);
4026 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4028 nouri_start = gtk_text_iter_get_offset(&iter);
4029 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4031 walk_pos = gtk_text_iter_get_offset(&iter);
4032 /* FIXME: this looks phony. scanning for anything in the parse table */
4033 for (n = 0; n < PARSE_ELEMS; n++) {
4036 tmp = parser[n].search(walk, parser[n].needle);
4038 if (scanpos == NULL || tmp < scanpos) {
4047 /* check if URI can be parsed */
4048 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4049 (const gchar **)&ep, FALSE)
4050 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4054 strlen(parser[last_index].needle);
4057 uri_start = walk_pos + (bp - o_walk);
4058 uri_stop = walk_pos + (ep - o_walk);
4062 gtk_text_iter_forward_line(&iter);
4065 if (startq_offset != -1) {
4066 GtkTextIter startquote, endquote;
4067 gtk_text_buffer_get_iter_at_offset(
4068 buffer, &startquote, startq_offset);
4071 switch (quotelevel) {
4073 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4074 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4075 gtk_text_buffer_apply_tag_by_name(
4076 buffer, "quote0", &startquote, &endquote);
4077 gtk_text_buffer_remove_tag_by_name(
4078 buffer, "quote1", &startquote, &endquote);
4079 gtk_text_buffer_remove_tag_by_name(
4080 buffer, "quote2", &startquote, &endquote);
4085 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4086 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4087 gtk_text_buffer_apply_tag_by_name(
4088 buffer, "quote1", &startquote, &endquote);
4089 gtk_text_buffer_remove_tag_by_name(
4090 buffer, "quote0", &startquote, &endquote);
4091 gtk_text_buffer_remove_tag_by_name(
4092 buffer, "quote2", &startquote, &endquote);
4097 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4098 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4099 gtk_text_buffer_apply_tag_by_name(
4100 buffer, "quote2", &startquote, &endquote);
4101 gtk_text_buffer_remove_tag_by_name(
4102 buffer, "quote0", &startquote, &endquote);
4103 gtk_text_buffer_remove_tag_by_name(
4104 buffer, "quote1", &startquote, &endquote);
4110 } else if (noq_offset != -1) {
4111 GtkTextIter startnoquote, endnoquote;
4112 gtk_text_buffer_get_iter_at_offset(
4113 buffer, &startnoquote, noq_offset);
4116 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4117 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4118 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4119 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4120 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4121 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4122 gtk_text_buffer_remove_tag_by_name(
4123 buffer, "quote0", &startnoquote, &endnoquote);
4124 gtk_text_buffer_remove_tag_by_name(
4125 buffer, "quote1", &startnoquote, &endnoquote);
4126 gtk_text_buffer_remove_tag_by_name(
4127 buffer, "quote2", &startnoquote, &endnoquote);
4133 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4134 GtkTextIter nouri_start_iter, nouri_end_iter;
4135 gtk_text_buffer_get_iter_at_offset(
4136 buffer, &nouri_start_iter, nouri_start);
4137 gtk_text_buffer_get_iter_at_offset(
4138 buffer, &nouri_end_iter, nouri_stop);
4139 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4140 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4141 gtk_text_buffer_remove_tag_by_name(
4142 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4143 modified_before_remove = modified;
4148 if (uri_start > 0 && uri_stop > 0) {
4149 GtkTextIter uri_start_iter, uri_end_iter, back;
4150 gtk_text_buffer_get_iter_at_offset(
4151 buffer, &uri_start_iter, uri_start);
4152 gtk_text_buffer_get_iter_at_offset(
4153 buffer, &uri_end_iter, uri_stop);
4154 back = uri_end_iter;
4155 gtk_text_iter_backward_char(&back);
4156 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4157 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4158 gtk_text_buffer_apply_tag_by_name(
4159 buffer, "link", &uri_start_iter, &uri_end_iter);
4161 if (removed && !modified_before_remove) {
4167 debug_print("not modified, out after %d lines\n", lines);
4172 debug_print("modified, out after %d lines\n", lines);
4176 undo_wrapping(compose->undostruct, FALSE);
4177 compose->autowrap = prev_autowrap;
4182 void compose_action_cb(void *data)
4184 Compose *compose = (Compose *)data;
4185 compose_wrap_all(compose);
4188 static void compose_wrap_all(Compose *compose)
4190 compose_wrap_all_full(compose, FALSE);
4193 static void compose_wrap_all_full(Compose *compose, gboolean force)
4195 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4196 GtkTextBuffer *buffer;
4198 gboolean modified = TRUE;
4200 buffer = gtk_text_view_get_buffer(text);
4202 gtk_text_buffer_get_start_iter(buffer, &iter);
4203 while (!gtk_text_iter_is_end(&iter) && modified)
4204 modified = compose_beautify_paragraph(compose, &iter, force);
4208 static void compose_set_title(Compose *compose)
4214 edited = compose->modified ? _(" [Edited]") : "";
4216 subject = gtk_editable_get_chars(
4217 GTK_EDITABLE(compose->subject_entry), 0, -1);
4220 if (subject && strlen(subject))
4221 str = g_strdup_printf(_("%s - Compose message%s"),
4224 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4226 str = g_strdup(_("Compose message"));
4229 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4235 * compose_current_mail_account:
4237 * Find a current mail account (the currently selected account, or the
4238 * default account, if a news account is currently selected). If a
4239 * mail account cannot be found, display an error message.
4241 * Return value: Mail account, or NULL if not found.
4243 static PrefsAccount *
4244 compose_current_mail_account(void)
4248 if (cur_account && cur_account->protocol != A_NNTP)
4251 ac = account_get_default();
4252 if (!ac || ac->protocol == A_NNTP) {
4253 alertpanel_error(_("Account for sending mail is not specified.\n"
4254 "Please select a mail account before sending."));
4261 #define QUOTE_IF_REQUIRED(out, str) \
4263 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4267 len = strlen(str) + 3; \
4268 if ((__tmp = alloca(len)) == NULL) { \
4269 g_warning("can't allocate memory\n"); \
4270 g_string_free(header, TRUE); \
4273 g_snprintf(__tmp, len, "\"%s\"", str); \
4278 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4279 g_warning("can't allocate memory\n"); \
4280 g_string_free(header, TRUE); \
4283 strcpy(__tmp, str); \
4289 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4291 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4295 len = strlen(str) + 3; \
4296 if ((__tmp = alloca(len)) == NULL) { \
4297 g_warning("can't allocate memory\n"); \
4300 g_snprintf(__tmp, len, "\"%s\"", str); \
4305 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4306 g_warning("can't allocate memory\n"); \
4309 strcpy(__tmp, str); \
4315 static void compose_select_account(Compose *compose, PrefsAccount *account,
4318 GtkItemFactory *ifactory;
4321 g_return_if_fail(account != NULL);
4323 compose->account = account;
4325 if (account->name && *account->name) {
4327 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4328 from = g_strdup_printf("%s <%s>",
4329 buf, account->address);
4330 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4332 from = g_strdup_printf("<%s>",
4334 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4339 compose_set_title(compose);
4341 ifactory = gtk_item_factory_from_widget(compose->menubar);
4343 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4344 menu_set_active(ifactory, "/Options/Sign", TRUE);
4346 menu_set_active(ifactory, "/Options/Sign", FALSE);
4347 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4348 menu_set_active(ifactory, "/Options/Encrypt", TRUE);
4350 menu_set_active(ifactory, "/Options/Encrypt", FALSE);
4352 activate_privacy_system(compose, account, FALSE);
4354 if (!init && compose->mode != COMPOSE_REDIRECT) {
4355 undo_block(compose->undostruct);
4356 compose_insert_sig(compose, TRUE);
4357 undo_unblock(compose->undostruct);
4361 /* use account's dict info if set */
4362 if (compose->gtkaspell) {
4363 if (account->enable_default_dictionary)
4364 gtkaspell_change_dict(compose->gtkaspell,
4365 account->default_dictionary, FALSE);
4366 if (account->enable_default_alt_dictionary)
4367 gtkaspell_change_alt_dict(compose->gtkaspell,
4368 account->default_alt_dictionary);
4369 if (account->enable_default_dictionary
4370 || account->enable_default_alt_dictionary)
4371 compose_spell_menu_changed(compose);
4376 gboolean compose_check_for_valid_recipient(Compose *compose) {
4377 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
4378 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
4379 gboolean recipient_found = FALSE;
4383 /* free to and newsgroup list */
4384 slist_free_strings(compose->to_list);
4385 g_slist_free(compose->to_list);
4386 compose->to_list = NULL;
4388 slist_free_strings(compose->newsgroup_list);
4389 g_slist_free(compose->newsgroup_list);
4390 compose->newsgroup_list = NULL;
4392 /* search header entries for to and newsgroup entries */
4393 for (list = compose->header_list; list; list = list->next) {
4396 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_BIN(((ComposeHeaderEntry *)list->data)->combo)->child), 0, -1);
4397 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4400 if (entry[0] != '\0') {
4401 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
4402 if (!strcmp(header, prefs_common_translated_header_name(*strptr))) {
4403 compose->to_list = address_list_append(compose->to_list, entry);
4404 recipient_found = TRUE;
4407 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
4408 if (!strcmp(header, prefs_common_translated_header_name(*strptr))) {
4409 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
4410 recipient_found = TRUE;
4417 return recipient_found;
4420 static gboolean compose_check_for_set_recipients(Compose *compose)
4422 if (compose->account->set_autocc && compose->account->auto_cc) {
4423 gboolean found_other = FALSE;
4425 /* search header entries for to and newsgroup entries */
4426 for (list = compose->header_list; list; list = list->next) {
4429 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4430 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_BIN(((ComposeHeaderEntry *)list->data)->combo)->child), 0, -1);
4432 if (strcmp(entry, compose->account->auto_cc)
4433 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
4443 if (compose->batch) {
4444 gtk_widget_show_all(compose->window);
4446 aval = alertpanel(_("Send"),
4447 _("The only recipient is the default CC address. Send anyway?"),
4448 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4449 if (aval != G_ALERTALTERNATE)
4453 if (compose->account->set_autobcc && compose->account->auto_bcc) {
4454 gboolean found_other = FALSE;
4456 /* search header entries for to and newsgroup entries */
4457 for (list = compose->header_list; list; list = list->next) {
4460 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4461 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_BIN(((ComposeHeaderEntry *)list->data)->combo)->child), 0, -1);
4463 if (strcmp(entry, compose->account->auto_bcc)
4464 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
4474 if (compose->batch) {
4475 gtk_widget_show_all(compose->window);
4477 aval = alertpanel(_("Send"),
4478 _("The only recipient is the default BCC address. Send anyway?"),
4479 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4480 if (aval != G_ALERTALTERNATE)
4487 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
4491 if (compose_check_for_valid_recipient(compose) == FALSE) {
4492 if (compose->batch) {
4493 gtk_widget_show_all(compose->window);
4495 alertpanel_error(_("Recipient is not specified."));
4499 if (compose_check_for_set_recipients(compose) == FALSE) {
4503 if (!compose->batch) {
4504 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
4505 if (*str == '\0' && check_everything == TRUE &&
4506 compose->mode != COMPOSE_REDIRECT) {
4508 gchar *button_label;
4511 if (compose->sending)
4512 button_label = _("+_Send");
4514 button_label = _("+_Queue");
4515 message = g_strdup_printf(_("Subject is empty. %s it anyway?"),
4516 compose->sending?_("Send"):_("Queue"));
4518 aval = alertpanel(compose->sending?_("Send"):_("Send later"), message,
4519 GTK_STOCK_CANCEL, button_label, NULL);
4521 if (aval != G_ALERTALTERNATE)
4526 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
4532 gint compose_send(Compose *compose)
4535 FolderItem *folder = NULL;
4537 gchar *msgpath = NULL;
4538 gboolean discard_window = FALSE;
4539 gchar *errstr = NULL;
4540 gchar *tmsgid = NULL;
4541 MainWindow *mainwin = mainwindow_get_mainwindow();
4542 gboolean queued_removed = FALSE;
4544 if (prefs_common.send_dialog_invisible
4545 || compose->batch == TRUE)
4546 discard_window = TRUE;
4548 compose_allow_user_actions (compose, FALSE);
4549 compose->sending = TRUE;
4551 if (compose_check_entries(compose, TRUE) == FALSE) {
4552 if (compose->batch) {
4553 gtk_widget_show_all(compose->window);
4559 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
4562 if (compose->batch) {
4563 gtk_widget_show_all(compose->window);
4566 alertpanel_error(_("Could not queue message for sending:\n\n"
4567 "Charset conversion failed."));
4568 } else if (val == -5) {
4569 alertpanel_error(_("Could not queue message for sending:\n\n"
4570 "Couldn't get recipient encryption key."));
4571 } else if (val == -6) {
4573 } else if (val == -3) {
4574 if (privacy_peek_error())
4575 alertpanel_error(_("Could not queue message for sending:\n\n"
4576 "Signature failed: %s"), privacy_get_error());
4577 } else if (val == -2 && errno != 0) {
4578 alertpanel_error(_("Could not queue message for sending:\n\n%s."), strerror(errno));
4580 alertpanel_error(_("Could not queue message for sending."));
4585 tmsgid = g_strdup(compose->msgid);
4586 if (discard_window) {
4587 compose->sending = FALSE;
4588 compose_close(compose);
4589 /* No more compose access in the normal codepath
4590 * after this point! */
4595 alertpanel_error(_("The message was queued but could not be "
4596 "sent.\nUse \"Send queued messages\" from "
4597 "the main window to retry."));
4598 if (!discard_window) {
4605 if (msgpath == NULL) {
4606 msgpath = folder_item_fetch_msg(folder, msgnum);
4607 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
4610 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
4614 if (!discard_window) {
4616 if (!queued_removed)
4617 folder_item_remove_msg(folder, msgnum);
4618 folder_item_scan(folder);
4620 /* make sure we delete that */
4621 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
4623 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
4624 folder_item_remove_msg(folder, tmp->msgnum);
4625 procmsg_msginfo_free(tmp);
4632 if (!queued_removed)
4633 folder_item_remove_msg(folder, msgnum);
4634 folder_item_scan(folder);
4636 /* make sure we delete that */
4637 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
4639 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
4640 folder_item_remove_msg(folder, tmp->msgnum);
4641 procmsg_msginfo_free(tmp);
4644 if (!discard_window) {
4645 compose->sending = FALSE;
4646 compose_allow_user_actions (compose, TRUE);
4647 compose_close(compose);
4651 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
4652 "the main window to retry."), errstr);
4655 alertpanel_error_log(_("The message was queued but could not be "
4656 "sent.\nUse \"Send queued messages\" from "
4657 "the main window to retry."));
4659 if (!discard_window) {
4668 toolbar_main_set_sensitive(mainwin);
4669 main_window_set_menu_sensitive(mainwin);
4675 compose_allow_user_actions (compose, TRUE);
4676 compose->sending = FALSE;
4677 compose->modified = TRUE;
4678 toolbar_main_set_sensitive(mainwin);
4679 main_window_set_menu_sensitive(mainwin);
4684 static gboolean compose_use_attach(Compose *compose)
4686 GtkTreeModel *model = gtk_tree_view_get_model
4687 (GTK_TREE_VIEW(compose->attach_clist));
4688 return gtk_tree_model_iter_n_children(model, NULL) > 0;
4691 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
4694 gchar buf[BUFFSIZE];
4696 gboolean first_to_address;
4697 gboolean first_cc_address;
4699 ComposeHeaderEntry *headerentry;
4700 const gchar *headerentryname;
4701 const gchar *cc_hdr;
4702 const gchar *to_hdr;
4703 gboolean err = FALSE;
4705 debug_print("Writing redirect header\n");
4707 cc_hdr = prefs_common_translated_header_name("Cc:");
4708 to_hdr = prefs_common_translated_header_name("To:");
4710 first_to_address = TRUE;
4711 for (list = compose->header_list; list; list = list->next) {
4712 headerentry = ((ComposeHeaderEntry *)list->data);
4713 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_BIN(headerentry->combo)->child));
4715 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
4716 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
4717 Xstrdup_a(str, entstr, return -1);
4719 if (str[0] != '\0') {
4720 compose_convert_header
4721 (compose, buf, sizeof(buf), str,
4722 strlen("Resent-To") + 2, TRUE);
4724 if (first_to_address) {
4725 err |= (fprintf(fp, "Resent-To: ") < 0);
4726 first_to_address = FALSE;
4728 err |= (fprintf(fp, ",") < 0);
4730 err |= (fprintf(fp, "%s", buf) < 0);
4734 if (!first_to_address) {
4735 err |= (fprintf(fp, "\n") < 0);
4738 first_cc_address = TRUE;
4739 for (list = compose->header_list; list; list = list->next) {
4740 headerentry = ((ComposeHeaderEntry *)list->data);
4741 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_BIN(headerentry->combo)->child));
4743 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
4744 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
4745 Xstrdup_a(str, strg, return -1);
4747 if (str[0] != '\0') {
4748 compose_convert_header
4749 (compose, buf, sizeof(buf), str,
4750 strlen("Resent-Cc") + 2, TRUE);
4752 if (first_cc_address) {
4753 err |= (fprintf(fp, "Resent-Cc: ") < 0);
4754 first_cc_address = FALSE;
4756 err |= (fprintf(fp, ",") < 0);
4758 err |= (fprintf(fp, "%s", buf) < 0);
4762 if (!first_cc_address) {
4763 err |= (fprintf(fp, "\n") < 0);
4766 return (err ? -1:0);
4769 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
4771 gchar buf[BUFFSIZE];
4773 const gchar *entstr;
4774 /* struct utsname utsbuf; */
4775 gboolean err = FALSE;
4777 g_return_val_if_fail(fp != NULL, -1);
4778 g_return_val_if_fail(compose->account != NULL, -1);
4779 g_return_val_if_fail(compose->account->address != NULL, -1);
4782 get_rfc822_date(buf, sizeof(buf));
4783 err |= (fprintf(fp, "Resent-Date: %s\n", buf) < 0);
4786 if (compose->account->name && *compose->account->name) {
4787 compose_convert_header
4788 (compose, buf, sizeof(buf), compose->account->name,
4789 strlen("From: "), TRUE);
4790 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
4791 buf, compose->account->address) < 0);
4793 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
4796 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
4797 if (*entstr != '\0') {
4798 Xstrdup_a(str, entstr, return -1);
4801 compose_convert_header(compose, buf, sizeof(buf), str,
4802 strlen("Subject: "), FALSE);
4803 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
4807 /* Resent-Message-ID */
4808 if (compose->account->set_domain && compose->account->domain) {
4809 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
4810 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
4811 g_snprintf(buf, sizeof(buf), "%s",
4812 strchr(compose->account->address, '@') ?
4813 strchr(compose->account->address, '@')+1 :
4814 compose->account->address);
4816 g_snprintf(buf, sizeof(buf), "%s", "");
4818 generate_msgid(buf, sizeof(buf));
4819 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", buf) < 0);
4820 compose->msgid = g_strdup(buf);
4822 if (compose_redirect_write_headers_from_headerlist(compose, fp))
4825 /* separator between header and body */
4826 err |= (fputs("\n", fp) == EOF);
4828 return (err ? -1:0);
4831 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
4835 gchar buf[BUFFSIZE];
4837 gboolean skip = FALSE;
4838 gboolean err = FALSE;
4839 gchar *not_included[]={
4840 "Return-Path:", "Delivered-To:", "Received:",
4841 "Subject:", "X-UIDL:", "AF:",
4842 "NF:", "PS:", "SRH:",
4843 "SFN:", "DSR:", "MID:",
4844 "CFG:", "PT:", "S:",
4845 "RQ:", "SSV:", "NSV:",
4846 "SSH:", "R:", "MAID:",
4847 "NAID:", "RMID:", "FMID:",
4848 "SCF:", "RRCPT:", "NG:",
4849 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
4850 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
4851 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
4852 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
4855 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
4856 FILE_OP_ERROR(compose->redirect_filename, "fopen");
4860 while (procheader_get_one_field_asis(buf, sizeof(buf), fp) != -1) {
4862 for (i = 0; not_included[i] != NULL; i++) {
4863 if (g_ascii_strncasecmp(buf, not_included[i],
4864 strlen(not_included[i])) == 0) {
4871 if (fputs(buf, fdest) == -1)
4874 if (!prefs_common.redirect_keep_from) {
4875 if (g_ascii_strncasecmp(buf, "From:",
4876 strlen("From:")) == 0) {
4877 err |= (fputs(" (by way of ", fdest) == EOF);
4878 if (compose->account->name
4879 && *compose->account->name) {
4880 compose_convert_header
4881 (compose, buf, sizeof(buf),
4882 compose->account->name,
4885 err |= (fprintf(fdest, "%s <%s>",
4887 compose->account->address) < 0);
4889 err |= (fprintf(fdest, "%s",
4890 compose->account->address) < 0);
4891 err |= (fputs(")", fdest) == EOF);
4895 if (fputs("\n", fdest) == -1)
4902 if (compose_redirect_write_headers(compose, fdest))
4905 while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
4906 if (fwrite(buf, sizeof(gchar), len, fdest) != len)
4919 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
4921 GtkTextBuffer *buffer;
4922 GtkTextIter start, end;
4925 const gchar *out_codeset;
4926 EncodingType encoding;
4927 MimeInfo *mimemsg, *mimetext;
4930 if (action == COMPOSE_WRITE_FOR_SEND)
4931 attach_parts = TRUE;
4933 /* create message MimeInfo */
4934 mimemsg = procmime_mimeinfo_new();
4935 mimemsg->type = MIMETYPE_MESSAGE;
4936 mimemsg->subtype = g_strdup("rfc822");
4937 mimemsg->content = MIMECONTENT_MEM;
4938 mimemsg->tmp = TRUE; /* must free content later */
4939 mimemsg->data.mem = compose_get_header(compose);
4941 /* Create text part MimeInfo */
4942 /* get all composed text */
4943 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
4944 gtk_text_buffer_get_start_iter(buffer, &start);
4945 gtk_text_buffer_get_end_iter(buffer, &end);
4946 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
4947 if (is_ascii_str(chars)) {
4950 out_codeset = CS_US_ASCII;
4951 encoding = ENC_7BIT;
4953 const gchar *src_codeset = CS_INTERNAL;
4955 out_codeset = conv_get_charset_str(compose->out_encoding);
4958 gchar *test_conv_global_out = NULL;
4959 gchar *test_conv_reply = NULL;
4961 /* automatic mode. be automatic. */
4962 codeconv_set_strict(TRUE);
4964 out_codeset = conv_get_outgoing_charset_str();
4966 debug_print("trying to convert to %s\n", out_codeset);
4967 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
4970 if (!test_conv_global_out && compose->orig_charset
4971 && strcmp(compose->orig_charset, CS_US_ASCII)) {
4972 out_codeset = compose->orig_charset;
4973 debug_print("failure; trying to convert to %s\n", out_codeset);
4974 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
4977 if (!test_conv_global_out && !test_conv_reply) {
4979 out_codeset = CS_INTERNAL;
4980 debug_print("failure; finally using %s\n", out_codeset);
4982 g_free(test_conv_global_out);
4983 g_free(test_conv_reply);
4984 codeconv_set_strict(FALSE);
4987 if (!g_ascii_strcasecmp(out_codeset, CS_US_ASCII))
4988 out_codeset = CS_ISO_8859_1;
4990 if (prefs_common.encoding_method == CTE_BASE64)
4991 encoding = ENC_BASE64;
4992 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
4993 encoding = ENC_QUOTED_PRINTABLE;
4994 else if (prefs_common.encoding_method == CTE_8BIT)
4995 encoding = ENC_8BIT;
4997 encoding = procmime_get_encoding_for_charset(out_codeset);
4999 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5000 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5002 if (action == COMPOSE_WRITE_FOR_SEND) {
5003 codeconv_set_strict(TRUE);
5004 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5005 codeconv_set_strict(FALSE);
5011 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5012 "to the specified %s charset.\n"
5013 "Send it as %s?"), out_codeset, src_codeset);
5014 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL, _("+_Send"), NULL, FALSE,
5015 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5018 if (aval != G_ALERTALTERNATE) {
5023 out_codeset = src_codeset;
5029 out_codeset = src_codeset;
5035 if (encoding == ENC_8BIT || encoding == ENC_7BIT) {
5036 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5037 strstr(buf, "\nFrom ") != NULL) {
5038 encoding = ENC_QUOTED_PRINTABLE;
5042 mimetext = procmime_mimeinfo_new();
5043 mimetext->content = MIMECONTENT_MEM;
5044 mimetext->tmp = TRUE; /* must free content later */
5045 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5046 * and free the data, which we need later. */
5047 mimetext->data.mem = g_strdup(buf);
5048 mimetext->type = MIMETYPE_TEXT;
5049 mimetext->subtype = g_strdup("plain");
5050 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5051 g_strdup(out_codeset));
5053 /* protect trailing spaces when signing message */
5054 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5055 privacy_system_can_sign(compose->privacy_system)) {
5056 encoding = ENC_QUOTED_PRINTABLE;
5059 debug_print("main text: %zd bytes encoded as %s in %d\n",
5060 strlen(buf), out_codeset, encoding);
5062 /* check for line length limit */
5063 if (action == COMPOSE_WRITE_FOR_SEND &&
5064 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5065 check_line_length(buf, 1000, &line) < 0) {
5069 msg = g_strdup_printf
5070 (_("Line %d exceeds the line length limit (998 bytes).\n"
5071 "The contents of the message might be broken on the way to the delivery.\n"
5073 "Send it anyway?"), line + 1);
5074 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5076 if (aval != G_ALERTALTERNATE) {
5082 if (encoding != ENC_UNKNOWN)
5083 procmime_encode_content(mimetext, encoding);
5085 /* append attachment parts */
5086 if (compose_use_attach(compose) && attach_parts) {
5087 MimeInfo *mimempart;
5088 gchar *boundary = NULL;
5089 mimempart = procmime_mimeinfo_new();
5090 mimempart->content = MIMECONTENT_EMPTY;
5091 mimempart->type = MIMETYPE_MULTIPART;
5092 mimempart->subtype = g_strdup("mixed");
5096 boundary = generate_mime_boundary(NULL);
5097 } while (strstr(buf, boundary) != NULL);
5099 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5102 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5104 g_node_append(mimempart->node, mimetext->node);
5105 g_node_append(mimemsg->node, mimempart->node);
5107 compose_add_attachments(compose, mimempart);
5109 g_node_append(mimemsg->node, mimetext->node);
5113 /* sign message if sending */
5114 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5115 privacy_system_can_sign(compose->privacy_system))
5116 if (!privacy_sign(compose->privacy_system, mimemsg, compose->account))
5119 procmime_write_mimeinfo(mimemsg, fp);
5121 procmime_mimeinfo_free_all(mimemsg);
5126 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5128 GtkTextBuffer *buffer;
5129 GtkTextIter start, end;
5134 if ((fp = g_fopen(file, "wb")) == NULL) {
5135 FILE_OP_ERROR(file, "fopen");
5139 /* chmod for security */
5140 if (change_file_mode_rw(fp, file) < 0) {
5141 FILE_OP_ERROR(file, "chmod");
5142 g_warning("can't change file mode\n");
5145 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5146 gtk_text_buffer_get_start_iter(buffer, &start);
5147 gtk_text_buffer_get_end_iter(buffer, &end);
5148 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5150 chars = conv_codeset_strdup
5151 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5154 if (!chars) return -1;
5157 len = strlen(chars);
5158 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5159 FILE_OP_ERROR(file, "fwrite");
5168 if (fclose(fp) == EOF) {
5169 FILE_OP_ERROR(file, "fclose");
5176 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5179 MsgInfo *msginfo = compose->targetinfo;
5181 g_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5182 if (!msginfo) return -1;
5184 if (!force && MSG_IS_LOCKED(msginfo->flags))
5187 item = msginfo->folder;
5188 g_return_val_if_fail(item != NULL, -1);
5190 if (procmsg_msg_exist(msginfo) &&
5191 (folder_has_parent_of_type(item, F_QUEUE) ||
5192 folder_has_parent_of_type(item, F_DRAFT)
5193 || msginfo == compose->autosaved_draft)) {
5194 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5195 g_warning("can't remove the old message\n");
5198 debug_print("removed reedit target %d\n", msginfo->msgnum);
5205 static void compose_remove_draft(Compose *compose)
5208 MsgInfo *msginfo = compose->targetinfo;
5209 drafts = account_get_special_folder(compose->account, F_DRAFT);
5211 if (procmsg_msg_exist(msginfo)) {
5212 folder_item_remove_msg(drafts, msginfo->msgnum);
5217 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5218 gboolean remove_reedit_target)
5220 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5223 static gboolean compose_warn_encryption(Compose *compose)
5225 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5226 AlertValue val = G_ALERTALTERNATE;
5228 if (warning == NULL)
5231 val = alertpanel_full(_("Encryption warning"), warning,
5232 GTK_STOCK_CANCEL, _("+C_ontinue"), NULL,
5233 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5234 if (val & G_ALERTDISABLE) {
5235 val &= ~G_ALERTDISABLE;
5236 if (val == G_ALERTALTERNATE)
5237 privacy_inhibit_encrypt_warning(compose->privacy_system,
5241 if (val == G_ALERTALTERNATE) {
5248 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
5249 gchar **msgpath, gboolean check_subject,
5250 gboolean remove_reedit_target)
5257 static gboolean lock = FALSE;
5258 PrefsAccount *mailac = NULL, *newsac = NULL;
5259 gboolean err = FALSE;
5261 debug_print("queueing message...\n");
5262 g_return_val_if_fail(compose->account != NULL, -1);
5266 if (compose_check_entries(compose, check_subject) == FALSE) {
5268 if (compose->batch) {
5269 gtk_widget_show_all(compose->window);
5274 if (!compose->to_list && !compose->newsgroup_list) {
5275 g_warning("can't get recipient list.");
5280 if (compose->to_list) {
5281 if (compose->account->protocol != A_NNTP)
5282 mailac = compose->account;
5283 else if (cur_account && cur_account->protocol != A_NNTP)
5284 mailac = cur_account;
5285 else if (!(mailac = compose_current_mail_account())) {
5287 alertpanel_error(_("No account for sending mails available!"));
5292 if (compose->newsgroup_list) {
5293 if (compose->account->protocol == A_NNTP)
5294 newsac = compose->account;
5295 else if (!newsac->protocol != A_NNTP) {
5297 alertpanel_error(_("No account for posting news available!"));
5302 /* write queue header */
5303 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
5304 G_DIR_SEPARATOR, compose, (guint) rand());
5305 debug_print("queuing to %s\n", tmp);
5306 if ((fp = g_fopen(tmp, "wb")) == NULL) {
5307 FILE_OP_ERROR(tmp, "fopen");
5313 if (change_file_mode_rw(fp, tmp) < 0) {
5314 FILE_OP_ERROR(tmp, "chmod");
5315 g_warning("can't change file mode\n");
5318 /* queueing variables */
5319 err |= (fprintf(fp, "AF:\n") < 0);
5320 err |= (fprintf(fp, "NF:0\n") < 0);
5321 err |= (fprintf(fp, "PS:10\n") < 0);
5322 err |= (fprintf(fp, "SRH:1\n") < 0);
5323 err |= (fprintf(fp, "SFN:\n") < 0);
5324 err |= (fprintf(fp, "DSR:\n") < 0);
5326 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
5328 err |= (fprintf(fp, "MID:\n") < 0);
5329 err |= (fprintf(fp, "CFG:\n") < 0);
5330 err |= (fprintf(fp, "PT:0\n") < 0);
5331 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
5332 err |= (fprintf(fp, "RQ:\n") < 0);
5334 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
5336 err |= (fprintf(fp, "SSV:\n") < 0);
5338 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
5340 err |= (fprintf(fp, "NSV:\n") < 0);
5341 err |= (fprintf(fp, "SSH:\n") < 0);
5342 /* write recepient list */
5343 if (compose->to_list) {
5344 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
5345 for (cur = compose->to_list->next; cur != NULL;
5347 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
5348 err |= (fprintf(fp, "\n") < 0);
5350 /* write newsgroup list */
5351 if (compose->newsgroup_list) {
5352 err |= (fprintf(fp, "NG:") < 0);
5353 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
5354 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
5355 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
5356 err |= (fprintf(fp, "\n") < 0);
5358 /* Sylpheed account IDs */
5360 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
5362 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
5365 if (compose->privacy_system != NULL) {
5366 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
5367 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
5368 if (compose->use_encryption) {
5370 if (!compose_warn_encryption(compose)) {
5377 if (mailac && mailac->encrypt_to_self) {
5378 GSList *tmp_list = g_slist_copy(compose->to_list);
5379 tmp_list = g_slist_append(tmp_list, compose->account->address);
5380 encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
5381 g_slist_free(tmp_list);
5383 encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
5385 if (encdata != NULL) {
5386 if (strcmp(encdata, "_DONT_ENCRYPT_")) {
5387 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5388 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
5390 } /* else we finally dont want to encrypt */
5392 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5393 /* and if encdata was null, it means there's been a problem in
5405 /* Save copy folder */
5406 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
5407 gchar *savefolderid;
5409 savefolderid = gtk_editable_get_chars(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
5410 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
5411 g_free(savefolderid);
5413 /* Save copy folder */
5414 if (compose->return_receipt) {
5415 err |= (fprintf(fp, "RRCPT:1\n") < 0);
5417 /* Message-ID of message replying to */
5418 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
5421 folderid = folder_item_get_identifier(compose->replyinfo->folder);
5422 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
5425 /* Message-ID of message forwarding to */
5426 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
5429 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
5430 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
5434 /* end of headers */
5435 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
5437 if (compose->redirect_filename != NULL) {
5438 if (compose_redirect_write_to_file(compose, fp) < 0) {
5447 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
5452 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
5456 g_warning("failed to write queue message\n");
5463 if (fclose(fp) == EOF) {
5464 FILE_OP_ERROR(tmp, "fclose");
5471 if (item && *item) {
5474 queue = account_get_special_folder(compose->account, F_QUEUE);
5477 g_warning("can't find queue folder\n");
5483 folder_item_scan(queue);
5484 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
5485 g_warning("can't queue the message\n");
5492 if (msgpath == NULL) {
5498 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
5499 compose_remove_reedit_target(compose, FALSE);
5502 if ((msgnum != NULL) && (item != NULL)) {
5510 static void compose_add_attachments(Compose *compose, MimeInfo *parent)
5513 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
5515 struct stat statbuf;
5516 gchar *type, *subtype;
5517 GtkTreeModel *model;
5520 model = gtk_tree_view_get_model(tree_view);
5522 if (!gtk_tree_model_get_iter_first(model, &iter))
5525 gtk_tree_model_get(model, &iter,
5529 mimepart = procmime_mimeinfo_new();
5530 mimepart->content = MIMECONTENT_FILE;
5531 mimepart->data.filename = g_strdup(ainfo->file);
5532 mimepart->tmp = FALSE; /* or we destroy our attachment */
5533 mimepart->offset = 0;
5535 stat(ainfo->file, &statbuf);
5536 mimepart->length = statbuf.st_size;
5538 type = g_strdup(ainfo->content_type);
5540 if (!strchr(type, '/')) {
5542 type = g_strdup("application/octet-stream");
5545 subtype = strchr(type, '/') + 1;
5546 *(subtype - 1) = '\0';
5547 mimepart->type = procmime_get_media_type(type);
5548 mimepart->subtype = g_strdup(subtype);
5551 if (mimepart->type == MIMETYPE_MESSAGE &&
5552 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
5553 mimepart->disposition = DISPOSITIONTYPE_INLINE;
5556 g_hash_table_insert(mimepart->typeparameters,
5557 g_strdup("name"), g_strdup(ainfo->name));
5558 g_hash_table_insert(mimepart->dispositionparameters,
5559 g_strdup("filename"), g_strdup(ainfo->name));
5560 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
5564 if (compose->use_signing) {
5565 if (ainfo->encoding == ENC_7BIT)
5566 ainfo->encoding = ENC_QUOTED_PRINTABLE;
5567 else if (ainfo->encoding == ENC_8BIT)
5568 ainfo->encoding = ENC_BASE64;
5571 procmime_encode_content(mimepart, ainfo->encoding);
5573 g_node_append(parent->node, mimepart->node);
5574 } while (gtk_tree_model_iter_next(model, &iter));
5577 #define IS_IN_CUSTOM_HEADER(header) \
5578 (compose->account->add_customhdr && \
5579 custom_header_find(compose->account->customhdr_list, header) != NULL)
5581 static void compose_add_headerfield_from_headerlist(Compose *compose,
5583 const gchar *fieldname,
5584 const gchar *seperator)
5586 gchar *str, *fieldname_w_colon;
5587 gboolean add_field = FALSE;
5589 ComposeHeaderEntry *headerentry;
5590 const gchar *headerentryname;
5591 const gchar *trans_fieldname;
5594 if (IS_IN_CUSTOM_HEADER(fieldname))
5597 debug_print("Adding %s-fields\n", fieldname);
5599 fieldstr = g_string_sized_new(64);
5601 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
5602 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
5604 for (list = compose->header_list; list; list = list->next) {
5605 headerentry = ((ComposeHeaderEntry *)list->data);
5606 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_BIN(headerentry->combo)->child));
5608 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
5609 str = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
5611 if (str[0] != '\0') {
5613 g_string_append(fieldstr, seperator);
5614 g_string_append(fieldstr, str);
5623 buf = g_new0(gchar, fieldstr->len * 4 + 256);
5624 compose_convert_header
5625 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
5626 strlen(fieldname) + 2, TRUE);
5627 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
5631 g_free(fieldname_w_colon);
5632 g_string_free(fieldstr, TRUE);
5637 static gchar *compose_get_header(Compose *compose)
5639 gchar buf[BUFFSIZE];
5640 const gchar *entry_str;
5644 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
5646 gchar *from_name = NULL, *from_address = NULL;
5649 g_return_val_if_fail(compose->account != NULL, NULL);
5650 g_return_val_if_fail(compose->account->address != NULL, NULL);
5652 header = g_string_sized_new(64);
5655 get_rfc822_date(buf, sizeof(buf));
5656 g_string_append_printf(header, "Date: %s\n", buf);
5660 if (compose->account->name && *compose->account->name) {
5662 QUOTE_IF_REQUIRED(buf, compose->account->name);
5663 tmp = g_strdup_printf("%s <%s>",
5664 buf, compose->account->address);
5666 tmp = g_strdup_printf("%s",
5667 compose->account->address);
5669 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
5670 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
5672 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
5673 from_address = g_strdup(compose->account->address);
5675 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5676 /* extract name and address */
5677 if (strstr(spec, " <") && strstr(spec, ">")) {
5678 from_address = g_strdup(strrchr(spec, '<')+1);
5679 *(strrchr(from_address, '>')) = '\0';
5680 from_name = g_strdup(spec);
5681 *(strrchr(from_name, '<')) = '\0';
5684 from_address = g_strdup(spec);
5691 if (from_name && *from_name) {
5692 compose_convert_header
5693 (compose, buf, sizeof(buf), from_name,
5694 strlen("From: "), TRUE);
5695 QUOTE_IF_REQUIRED(name, buf);
5697 g_string_append_printf(header, "From: %s <%s>\n",
5698 name, from_address);
5700 g_string_append_printf(header, "From: %s\n", from_address);
5703 g_free(from_address);
5706 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
5709 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
5712 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
5715 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
5718 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
5720 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
5723 compose_convert_header(compose, buf, sizeof(buf), str,
5724 strlen("Subject: "), FALSE);
5725 g_string_append_printf(header, "Subject: %s\n", buf);
5731 if (compose->account->set_domain && compose->account->domain) {
5732 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
5733 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
5734 g_snprintf(buf, sizeof(buf), "%s",
5735 strchr(compose->account->address, '@') ?
5736 strchr(compose->account->address, '@')+1 :
5737 compose->account->address);
5739 g_snprintf(buf, sizeof(buf), "%s", "");
5741 generate_msgid(buf, sizeof(buf));
5742 g_string_append_printf(header, "Message-ID: <%s>\n", buf);
5743 compose->msgid = g_strdup(buf);
5745 if (compose->remove_references == FALSE) {
5747 if (compose->inreplyto && compose->to_list)
5748 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
5751 if (compose->references)
5752 g_string_append_printf(header, "References: %s\n", compose->references);
5756 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
5759 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
5762 if (compose->account->organization &&
5763 strlen(compose->account->organization) &&
5764 !IS_IN_CUSTOM_HEADER("Organization")) {
5765 compose_convert_header(compose, buf, sizeof(buf),
5766 compose->account->organization,
5767 strlen("Organization: "), FALSE);
5768 g_string_append_printf(header, "Organization: %s\n", buf);
5771 /* Program version and system info */
5772 if (g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
5773 !compose->newsgroup_list) {
5774 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
5776 gtk_major_version, gtk_minor_version, gtk_micro_version,
5779 if (g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
5780 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
5782 gtk_major_version, gtk_minor_version, gtk_micro_version,
5786 /* custom headers */
5787 if (compose->account->add_customhdr) {
5790 for (cur = compose->account->customhdr_list; cur != NULL;
5792 CustomHeader *chdr = (CustomHeader *)cur->data;
5794 if (custom_header_is_allowed(chdr->name)) {
5795 compose_convert_header
5796 (compose, buf, sizeof(buf),
5797 chdr->value ? chdr->value : "",
5798 strlen(chdr->name) + 2, FALSE);
5799 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
5805 switch (compose->priority) {
5806 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
5807 "X-Priority: 1 (Highest)\n");
5809 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
5810 "X-Priority: 2 (High)\n");
5812 case PRIORITY_NORMAL: break;
5813 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
5814 "X-Priority: 4 (Low)\n");
5816 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
5817 "X-Priority: 5 (Lowest)\n");
5819 default: debug_print("compose: priority unknown : %d\n",
5823 /* Request Return Receipt */
5824 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To")) {
5825 if (compose->return_receipt) {
5826 if (compose->account->name
5827 && *compose->account->name) {
5828 compose_convert_header(compose, buf, sizeof(buf),
5829 compose->account->name,
5830 strlen("Disposition-Notification-To: "),
5832 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, compose->account->address);
5834 g_string_append_printf(header, "Disposition-Notification-To: %s\n", compose->account->address);
5838 /* get special headers */
5839 for (list = compose->header_list; list; list = list->next) {
5840 ComposeHeaderEntry *headerentry;
5843 gchar *headername_wcolon;
5844 const gchar *headername_trans;
5847 gboolean standard_header = FALSE;
5849 headerentry = ((ComposeHeaderEntry *)list->data);
5851 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(GTK_BIN(headerentry->combo)->child)));
5852 if (strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
5857 if (!strstr(tmp, ":")) {
5858 headername_wcolon = g_strconcat(tmp, ":", NULL);
5859 headername = g_strdup(tmp);
5861 headername_wcolon = g_strdup(tmp);
5862 headername = g_strdup(strtok(tmp, ":"));
5866 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5867 Xstrdup_a(headervalue, entry_str, return NULL);
5868 subst_char(headervalue, '\r', ' ');
5869 subst_char(headervalue, '\n', ' ');
5870 string = std_headers;
5871 while (*string != NULL) {
5872 headername_trans = prefs_common_translated_header_name(*string);
5873 if (!strcmp(headername_trans, headername_wcolon))
5874 standard_header = TRUE;
5877 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
5878 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
5881 g_free(headername_wcolon);
5885 g_string_free(header, FALSE);
5890 #undef IS_IN_CUSTOM_HEADER
5892 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
5893 gint header_len, gboolean addr_field)
5895 gchar *tmpstr = NULL;
5896 const gchar *out_codeset = NULL;
5898 g_return_if_fail(src != NULL);
5899 g_return_if_fail(dest != NULL);
5901 if (len < 1) return;
5903 tmpstr = g_strdup(src);
5905 subst_char(tmpstr, '\n', ' ');
5906 subst_char(tmpstr, '\r', ' ');
5909 if (!g_utf8_validate(tmpstr, -1, NULL)) {
5910 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
5911 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
5916 codeconv_set_strict(TRUE);
5917 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
5918 conv_get_charset_str(compose->out_encoding));
5919 codeconv_set_strict(FALSE);
5921 if (!dest || *dest == '\0') {
5922 gchar *test_conv_global_out = NULL;
5923 gchar *test_conv_reply = NULL;
5925 /* automatic mode. be automatic. */
5926 codeconv_set_strict(TRUE);
5928 out_codeset = conv_get_outgoing_charset_str();
5930 debug_print("trying to convert to %s\n", out_codeset);
5931 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
5934 if (!test_conv_global_out && compose->orig_charset
5935 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5936 out_codeset = compose->orig_charset;
5937 debug_print("failure; trying to convert to %s\n", out_codeset);
5938 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
5941 if (!test_conv_global_out && !test_conv_reply) {
5943 out_codeset = CS_INTERNAL;
5944 debug_print("finally using %s\n", out_codeset);
5946 g_free(test_conv_global_out);
5947 g_free(test_conv_reply);
5948 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
5950 codeconv_set_strict(FALSE);
5955 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
5959 g_return_if_fail(user_data != NULL);
5961 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
5962 g_strstrip(address);
5963 if (*address != '\0') {
5964 gchar *name = procheader_get_fromname(address);
5965 extract_address(address);
5966 addressbook_add_contact(name, address, NULL);
5971 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
5973 GtkWidget *menuitem;
5976 g_return_if_fail(menu != NULL);
5977 g_return_if_fail(GTK_IS_MENU_SHELL(menu));
5979 menuitem = gtk_separator_menu_item_new();
5980 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
5981 gtk_widget_show(menuitem);
5983 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
5984 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
5986 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
5987 g_strstrip(address);
5988 if (*address == '\0') {
5989 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
5992 g_signal_connect(G_OBJECT(menuitem), "activate",
5993 G_CALLBACK(compose_add_to_addressbook_cb), entry);
5994 gtk_widget_show(menuitem);
5997 static void compose_create_header_entry(Compose *compose)
5999 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6004 const gchar *header = NULL;
6005 ComposeHeaderEntry *headerentry;
6006 gboolean standard_header = FALSE;
6008 headerentry = g_new0(ComposeHeaderEntry, 1);
6011 combo = gtk_combo_box_entry_new_text();
6013 while(*string != NULL) {
6014 gtk_combo_box_append_text(GTK_COMBO_BOX(combo),
6015 (gchar*)prefs_common_translated_header_name(*string));
6018 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
6019 g_signal_connect(G_OBJECT(GTK_BIN(combo)->child), "grab_focus",
6020 G_CALLBACK(compose_grab_focus_cb), compose);
6021 gtk_widget_show(combo);
6022 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
6023 compose->header_nextrow, compose->header_nextrow+1,
6024 GTK_SHRINK, GTK_FILL, 0, 0);
6025 if (compose->header_last) {
6026 const gchar *last_header_entry = gtk_entry_get_text(
6027 GTK_ENTRY(GTK_BIN(compose->header_last->combo)->child));
6029 while (*string != NULL) {
6030 if (!strcmp(*string, last_header_entry))
6031 standard_header = TRUE;
6034 if (standard_header)
6035 header = gtk_entry_get_text(GTK_ENTRY(GTK_BIN(compose->header_last->combo)->child));
6037 if (!compose->header_last || !standard_header) {
6038 switch(compose->account->protocol) {
6040 header = prefs_common_translated_header_name("Newsgroups:");
6043 header = prefs_common_translated_header_name("To:");
6048 gtk_entry_set_text(GTK_ENTRY(GTK_BIN(combo)->child), header);
6050 g_signal_connect_after(G_OBJECT(GTK_BIN(combo)->child), "grab_focus",
6051 G_CALLBACK(compose_grab_focus_cb), compose);
6054 entry = gtk_entry_new();
6055 gtk_widget_show(entry);
6056 gtk_tooltips_set_tip(compose->tooltips, entry,
6057 _("Use <tab> to autocomplete from addressbook"), NULL);
6058 gtk_table_attach(GTK_TABLE(compose->header_table), entry, 1, 2,
6059 compose->header_nextrow, compose->header_nextrow+1,
6060 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
6062 g_signal_connect(G_OBJECT(entry), "key-press-event",
6063 G_CALLBACK(compose_headerentry_key_press_event_cb),
6065 g_signal_connect(G_OBJECT(entry), "changed",
6066 G_CALLBACK(compose_headerentry_changed_cb),
6068 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
6069 G_CALLBACK(compose_grab_focus_cb), compose);
6072 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6073 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6074 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6075 g_signal_connect(G_OBJECT(entry), "drag_data_received",
6076 G_CALLBACK(compose_header_drag_received_cb),
6078 g_signal_connect(G_OBJECT(entry), "drag-drop",
6079 G_CALLBACK(compose_drag_drop),
6081 g_signal_connect(G_OBJECT(entry), "populate-popup",
6082 G_CALLBACK(compose_entry_popup_extend),
6085 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
6087 headerentry->compose = compose;
6088 headerentry->combo = combo;
6089 headerentry->entry = entry;
6090 headerentry->headernum = compose->header_nextrow;
6092 compose->header_nextrow++;
6093 compose->header_last = headerentry;
6094 compose->header_list =
6095 g_slist_append(compose->header_list,
6099 static void compose_add_header_entry(Compose *compose, const gchar *header, gchar *text)
6101 ComposeHeaderEntry *last_header;
6103 last_header = compose->header_last;
6105 gtk_entry_set_text(GTK_ENTRY(GTK_BIN(last_header->combo)->child), header);
6106 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
6109 static void compose_remove_header_entries(Compose *compose)
6112 for (list = compose->header_list; list; list = list->next) {
6113 ComposeHeaderEntry *headerentry =
6114 (ComposeHeaderEntry *)list->data;
6115 gtk_widget_destroy(headerentry->combo);
6116 gtk_widget_destroy(headerentry->entry);
6117 g_free(headerentry);
6119 compose->header_last = NULL;
6120 g_slist_free(compose->header_list);
6121 compose->header_list = NULL;
6122 compose->header_nextrow = 1;
6123 compose_create_header_entry(compose);
6126 static GtkWidget *compose_create_header(Compose *compose)
6128 GtkWidget *from_optmenu_hbox;
6129 GtkWidget *header_scrolledwin;
6130 GtkWidget *header_table;
6134 /* header labels and entries */
6135 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
6136 gtk_widget_show(header_scrolledwin);
6137 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
6139 header_table = gtk_table_new(2, 2, FALSE);
6140 gtk_widget_show(header_table);
6141 gtk_container_set_border_width(GTK_CONTAINER(header_table), BORDER_WIDTH);
6142 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
6143 gtk_viewport_set_shadow_type(GTK_VIEWPORT(GTK_BIN(header_scrolledwin)->child), GTK_SHADOW_NONE);
6146 /* option menu for selecting accounts */
6147 from_optmenu_hbox = compose_account_option_menu_create(compose);
6148 gtk_table_attach(GTK_TABLE(header_table), from_optmenu_hbox,
6149 0, 2, count, count + 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
6152 compose->header_table = header_table;
6153 compose->header_list = NULL;
6154 compose->header_nextrow = count;
6156 compose_create_header_entry(compose);
6158 compose->table = NULL;
6160 return header_scrolledwin ;
6163 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
6165 Compose *compose = (Compose *)data;
6166 GdkEventButton event;
6169 event.time = gtk_get_current_event_time();
6171 return attach_button_pressed(compose->attach_clist, &event, compose);
6174 static GtkWidget *compose_create_attach(Compose *compose)
6176 GtkWidget *attach_scrwin;
6177 GtkWidget *attach_clist;
6179 GtkListStore *store;
6180 GtkCellRenderer *renderer;
6181 GtkTreeViewColumn *column;
6182 GtkTreeSelection *selection;
6184 /* attachment list */
6185 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
6186 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
6187 GTK_POLICY_AUTOMATIC,
6188 GTK_POLICY_AUTOMATIC);
6189 gtk_widget_set_size_request(attach_scrwin, -1, 80);
6191 store = gtk_list_store_new(N_ATTACH_COLS,
6196 G_TYPE_AUTO_POINTER,
6198 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
6199 (GTK_TREE_MODEL(store)));
6200 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
6201 g_object_unref(store);
6203 renderer = gtk_cell_renderer_text_new();
6204 column = gtk_tree_view_column_new_with_attributes
6205 (_("Mime type"), renderer, "text",
6206 COL_MIMETYPE, NULL);
6207 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6209 renderer = gtk_cell_renderer_text_new();
6210 column = gtk_tree_view_column_new_with_attributes
6211 (_("Size"), renderer, "text",
6213 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6215 renderer = gtk_cell_renderer_text_new();
6216 column = gtk_tree_view_column_new_with_attributes
6217 (_("Name"), renderer, "text",
6219 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6221 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
6222 prefs_common.use_stripes_everywhere);
6223 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
6224 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
6226 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
6227 G_CALLBACK(attach_selected), compose);
6228 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
6229 G_CALLBACK(attach_button_pressed), compose);
6231 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
6232 G_CALLBACK(popup_attach_button_pressed), compose);
6234 gtk_widget_tap_and_hold_setup(GTK_WIDGET(attach_clist), NULL, NULL,
6235 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
6236 g_signal_connect(G_OBJECT(attach_clist), "tap-and-hold",
6237 G_CALLBACK(popup_attach_button_pressed), compose);
6239 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
6240 G_CALLBACK(attach_key_pressed), compose);
6243 gtk_drag_dest_set(attach_clist,
6244 GTK_DEST_DEFAULT_ALL, compose_mime_types,
6245 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6246 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6247 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
6248 G_CALLBACK(compose_attach_drag_received_cb),
6250 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
6251 G_CALLBACK(compose_drag_drop),
6254 compose->attach_scrwin = attach_scrwin;
6255 compose->attach_clist = attach_clist;
6257 return attach_scrwin;
6260 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
6261 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
6263 static GtkWidget *compose_create_others(Compose *compose)
6266 GtkWidget *savemsg_checkbtn;
6267 GtkWidget *savemsg_entry;
6268 GtkWidget *savemsg_select;
6271 gchar *folderidentifier;
6273 /* Table for settings */
6274 table = gtk_table_new(3, 1, FALSE);
6275 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
6276 gtk_widget_show(table);
6277 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
6280 /* Save Message to folder */
6281 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
6282 gtk_widget_show(savemsg_checkbtn);
6283 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6284 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6285 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
6287 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
6288 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
6290 savemsg_entry = gtk_entry_new();
6291 gtk_widget_show(savemsg_entry);
6292 gtk_table_attach_defaults(GTK_TABLE(table), savemsg_entry, 1, 2, rowcount, rowcount + 1);
6293 gtk_editable_set_editable(GTK_EDITABLE(savemsg_entry), prefs_common.savemsg);
6294 g_signal_connect_after(G_OBJECT(savemsg_entry), "grab_focus",
6295 G_CALLBACK(compose_grab_focus_cb), compose);
6296 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6297 folderidentifier = folder_item_get_identifier(account_get_special_folder
6298 (compose->account, F_OUTBOX));
6299 gtk_entry_set_text(GTK_ENTRY(savemsg_entry), folderidentifier);
6300 g_free(folderidentifier);
6303 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
6304 gtk_widget_show(savemsg_select);
6305 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6306 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
6307 G_CALLBACK(compose_savemsg_select_cb),
6312 compose->savemsg_checkbtn = savemsg_checkbtn;
6313 compose->savemsg_entry = savemsg_entry;
6318 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
6320 gtk_editable_set_editable(GTK_EDITABLE(compose->savemsg_entry),
6321 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
6324 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
6329 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL);
6332 path = folder_item_get_identifier(dest);
6334 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), path);
6338 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
6339 GdkAtom clip, GtkTextIter *insert_place);
6342 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
6346 GtkTextBuffer *buffer;
6348 if (event->button == 3) {
6350 GtkTextIter sel_start, sel_end;
6351 gboolean stuff_selected;
6353 /* move the cursor to allow GtkAspell to check the word
6354 * under the mouse */
6355 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6356 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6358 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6361 stuff_selected = gtk_text_buffer_get_selection_bounds(
6362 GTK_TEXT_VIEW(text)->buffer,
6363 &sel_start, &sel_end);
6365 gtk_text_buffer_place_cursor (GTK_TEXT_VIEW(text)->buffer, &iter);
6366 /* reselect stuff */
6368 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
6369 gtk_text_buffer_select_range(GTK_TEXT_VIEW(text)->buffer,
6370 &sel_start, &sel_end);
6372 return FALSE; /* pass the event so that the right-click goes through */
6375 if (event->button == 2) {
6380 /* get the middle-click position to paste at the correct place */
6381 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6382 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6384 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6387 entry_paste_clipboard(compose, text,
6388 prefs_common.linewrap_pastes,
6389 GDK_SELECTION_PRIMARY, &iter);
6397 static void compose_spell_menu_changed(void *data)
6399 Compose *compose = (Compose *)data;
6401 GtkWidget *menuitem;
6402 GtkWidget *parent_item;
6403 GtkMenu *menu = GTK_MENU(gtk_menu_new());
6404 GtkItemFactory *ifactory = gtk_item_factory_from_widget(compose->menubar);
6407 if (compose->gtkaspell == NULL)
6410 parent_item = gtk_item_factory_get_item(ifactory,
6411 "/Spelling/Options");
6413 /* setting the submenu removes /Spelling/Options from the factory
6414 * so we need to save it */
6416 if (parent_item == NULL) {
6417 parent_item = compose->aspell_options_menu;
6418 gtk_menu_item_remove_submenu(GTK_MENU_ITEM(parent_item));
6420 compose->aspell_options_menu = parent_item;
6422 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
6424 spell_menu = g_slist_reverse(spell_menu);
6425 for (items = spell_menu;
6426 items; items = items->next) {
6427 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
6428 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
6429 gtk_widget_show(GTK_WIDGET(menuitem));
6431 g_slist_free(spell_menu);
6433 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
6438 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
6440 Compose *compose = (Compose *)data;
6441 GdkEventButton event;
6444 event.time = gtk_get_current_event_time();
6446 return text_clicked(compose->text, &event, compose);
6449 static gboolean compose_force_window_origin = TRUE;
6450 static Compose *compose_create(PrefsAccount *account,
6459 GtkWidget *handlebox;
6461 GtkWidget *notebook;
6463 GtkWidget *attach_hbox;
6464 GtkWidget *attach_lab1;
6465 GtkWidget *attach_lab2;
6470 GtkWidget *subject_hbox;
6471 GtkWidget *subject_frame;
6472 GtkWidget *subject_entry;
6476 GtkWidget *edit_vbox;
6477 GtkWidget *ruler_hbox;
6479 GtkWidget *scrolledwin;
6481 GtkTextBuffer *buffer;
6482 GtkClipboard *clipboard;
6484 UndoMain *undostruct;
6486 gchar *titles[N_ATTACH_COLS];
6487 guint n_menu_entries;
6488 GtkWidget *popupmenu;
6489 GtkItemFactory *popupfactory;
6490 GtkItemFactory *ifactory;
6491 GtkWidget *tmpl_menu;
6493 GtkWidget *menuitem;
6496 GtkAspell * gtkaspell = NULL;
6499 static GdkGeometry geometry;
6501 g_return_val_if_fail(account != NULL, NULL);
6503 debug_print("Creating compose window...\n");
6504 compose = g_new0(Compose, 1);
6506 titles[COL_MIMETYPE] = _("MIME type");
6507 titles[COL_SIZE] = _("Size");
6508 titles[COL_NAME] = _("Name");
6510 compose->batch = batch;
6511 compose->account = account;
6512 compose->folder = folder;
6514 compose->mutex = g_mutex_new();
6515 compose->set_cursor_pos = -1;
6517 compose->tooltips = gtk_tooltips_new();
6519 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
6521 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
6522 gtk_widget_set_size_request(window, -1, prefs_common.compose_height);
6524 if (!geometry.max_width) {
6525 geometry.max_width = gdk_screen_width();
6526 geometry.max_height = gdk_screen_height();
6529 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
6530 &geometry, GDK_HINT_MAX_SIZE);
6531 if (!geometry.min_width) {
6532 geometry.min_width = 600;
6533 geometry.min_height = 480;
6535 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
6536 &geometry, GDK_HINT_MIN_SIZE);
6539 if (compose_force_window_origin)
6540 gtk_widget_set_uposition(window, prefs_common.compose_x,
6541 prefs_common.compose_y);
6543 g_signal_connect(G_OBJECT(window), "delete_event",
6544 G_CALLBACK(compose_delete_cb), compose);
6545 MANAGE_WINDOW_SIGNALS_CONNECT(window);
6546 gtk_widget_realize(window);
6548 gtkut_widget_set_composer_icon(window);
6550 vbox = gtk_vbox_new(FALSE, 0);
6551 gtk_container_add(GTK_CONTAINER(window), vbox);
6553 n_menu_entries = sizeof(compose_entries) / sizeof(compose_entries[0]);
6554 menubar = menubar_create(window, compose_entries,
6555 n_menu_entries, "<Compose>", compose);
6556 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
6558 if (prefs_common.toolbar_detachable) {
6559 handlebox = gtk_handle_box_new();
6561 handlebox = gtk_hbox_new(FALSE, 0);
6563 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
6565 gtk_widget_realize(handlebox);
6567 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, window,
6570 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
6574 vbox2 = gtk_vbox_new(FALSE, 2);
6575 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
6576 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
6579 notebook = gtk_notebook_new();
6580 gtk_widget_set_size_request(notebook, -1, 130);
6581 gtk_widget_show(notebook);
6583 /* header labels and entries */
6584 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6585 compose_create_header(compose),
6586 gtk_label_new_with_mnemonic(_("Hea_der")));
6587 /* attachment list */
6588 attach_hbox = gtk_hbox_new(FALSE, 0);
6589 gtk_widget_show(attach_hbox);
6591 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
6592 gtk_widget_show(attach_lab1);
6593 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
6595 attach_lab2 = gtk_label_new("");
6596 gtk_widget_show(attach_lab2);
6597 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
6599 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6600 compose_create_attach(compose),
6603 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6604 compose_create_others(compose),
6605 gtk_label_new_with_mnemonic(_("Othe_rs")));
6608 subject_hbox = gtk_hbox_new(FALSE, 0);
6609 gtk_widget_show(subject_hbox);
6611 subject_frame = gtk_frame_new(NULL);
6612 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
6613 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
6614 gtk_widget_show(subject_frame);
6616 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
6617 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
6618 gtk_widget_show(subject);
6620 label = gtk_label_new(_("Subject:"));
6621 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
6622 gtk_widget_show(label);
6624 subject_entry = gtk_entry_new();
6625 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
6626 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
6627 G_CALLBACK(compose_grab_focus_cb), compose);
6628 gtk_widget_show(subject_entry);
6629 compose->subject_entry = subject_entry;
6630 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
6632 edit_vbox = gtk_vbox_new(FALSE, 0);
6634 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
6637 ruler_hbox = gtk_hbox_new(FALSE, 0);
6638 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
6640 ruler = gtk_shruler_new();
6641 gtk_ruler_set_range(GTK_RULER(ruler), 0.0, 100.0, 1.0, 100.0);
6642 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
6646 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
6647 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
6648 GTK_POLICY_AUTOMATIC,
6649 GTK_POLICY_AUTOMATIC);
6650 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
6652 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
6653 gtk_widget_set_size_request(scrolledwin, prefs_common.compose_width, -1);
6655 text = gtk_text_view_new();
6656 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
6657 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
6658 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
6659 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
6660 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
6662 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
6664 g_signal_connect_after(G_OBJECT(text), "size_allocate",
6665 G_CALLBACK(compose_edit_size_alloc),
6667 g_signal_connect(G_OBJECT(buffer), "changed",
6668 G_CALLBACK(compose_changed_cb), compose);
6669 g_signal_connect(G_OBJECT(text), "grab_focus",
6670 G_CALLBACK(compose_grab_focus_cb), compose);
6671 g_signal_connect(G_OBJECT(buffer), "insert_text",
6672 G_CALLBACK(text_inserted), compose);
6673 g_signal_connect(G_OBJECT(text), "button_press_event",
6674 G_CALLBACK(text_clicked), compose);
6676 g_signal_connect(G_OBJECT(text), "popup-menu",
6677 G_CALLBACK(compose_popup_menu), compose);
6679 gtk_widget_tap_and_hold_setup(GTK_WIDGET(text), NULL, NULL,
6680 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
6681 g_signal_connect(G_OBJECT(text), "tap-and-hold",
6682 G_CALLBACK(compose_popup_menu), compose);
6684 g_signal_connect(G_OBJECT(subject_entry), "changed",
6685 G_CALLBACK(compose_changed_cb), compose);
6688 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6689 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6690 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6691 g_signal_connect(G_OBJECT(text), "drag_data_received",
6692 G_CALLBACK(compose_insert_drag_received_cb),
6694 g_signal_connect(G_OBJECT(text), "drag-drop",
6695 G_CALLBACK(compose_drag_drop),
6697 gtk_widget_show_all(vbox);
6699 /* pane between attach clist and text */
6700 paned = gtk_vpaned_new();
6701 gtk_paned_set_gutter_size(GTK_PANED(paned), 12);
6702 gtk_container_add(GTK_CONTAINER(vbox2), paned);
6704 if( maemo_mainwindow_is_fullscreen(mainwindow_get_mainwindow()->window) )
6705 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 300 : 280);
6707 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 250 : 230);
6709 gtk_paned_add1(GTK_PANED(paned), notebook);
6710 gtk_paned_add2(GTK_PANED(paned), edit_vbox);
6711 gtk_widget_show_all(paned);
6714 if (prefs_common.textfont) {
6715 PangoFontDescription *font_desc;
6717 font_desc = pango_font_description_from_string
6718 (prefs_common.textfont);
6720 gtk_widget_modify_font(text, font_desc);
6721 pango_font_description_free(font_desc);
6725 n_entries = sizeof(compose_popup_entries) /
6726 sizeof(compose_popup_entries[0]);
6727 popupmenu = menu_create_items(compose_popup_entries, n_entries,
6728 "<Compose>", &popupfactory,
6731 ifactory = gtk_item_factory_from_widget(menubar);
6732 menu_set_sensitive(ifactory, "/Edit/Undo", FALSE);
6733 menu_set_sensitive(ifactory, "/Edit/Redo", FALSE);
6734 menu_set_sensitive(ifactory, "/Options/Remove references", FALSE);
6736 tmpl_menu = gtk_item_factory_get_item(ifactory, "/Tools/Template");
6738 undostruct = undo_init(text);
6739 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
6742 address_completion_start(window);
6744 compose->window = window;
6745 compose->vbox = vbox;
6746 compose->menubar = menubar;
6747 compose->handlebox = handlebox;
6749 compose->vbox2 = vbox2;
6751 compose->paned = paned;
6753 compose->attach_label = attach_lab2;
6755 compose->notebook = notebook;
6756 compose->edit_vbox = edit_vbox;
6757 compose->ruler_hbox = ruler_hbox;
6758 compose->ruler = ruler;
6759 compose->scrolledwin = scrolledwin;
6760 compose->text = text;
6762 compose->focused_editable = NULL;
6764 compose->popupmenu = popupmenu;
6765 compose->popupfactory = popupfactory;
6767 compose->tmpl_menu = tmpl_menu;
6769 compose->mode = mode;
6770 compose->rmode = mode;
6772 compose->targetinfo = NULL;
6773 compose->replyinfo = NULL;
6774 compose->fwdinfo = NULL;
6776 compose->replyto = NULL;
6778 compose->bcc = NULL;
6779 compose->followup_to = NULL;
6781 compose->ml_post = NULL;
6783 compose->inreplyto = NULL;
6784 compose->references = NULL;
6785 compose->msgid = NULL;
6786 compose->boundary = NULL;
6788 compose->autowrap = prefs_common.autowrap;
6790 compose->use_signing = FALSE;
6791 compose->use_encryption = FALSE;
6792 compose->privacy_system = NULL;
6794 compose->modified = FALSE;
6796 compose->return_receipt = FALSE;
6798 compose->to_list = NULL;
6799 compose->newsgroup_list = NULL;
6801 compose->undostruct = undostruct;
6803 compose->sig_str = NULL;
6805 compose->exteditor_file = NULL;
6806 compose->exteditor_pid = -1;
6807 compose->exteditor_tag = -1;
6808 compose->draft_timeout_tag = -2; /* inhibit auto-drafting while loading */
6811 menu_set_sensitive(ifactory, "/Spelling", FALSE);
6812 if (mode != COMPOSE_REDIRECT) {
6813 if (prefs_common.enable_aspell && prefs_common.dictionary &&
6814 strcmp(prefs_common.dictionary, "")) {
6815 gtkaspell = gtkaspell_new(prefs_common.aspell_path,
6816 prefs_common.dictionary,
6817 prefs_common.alt_dictionary,
6818 conv_get_locale_charset_str(),
6819 prefs_common.misspelled_col,
6820 prefs_common.check_while_typing,
6821 prefs_common.recheck_when_changing_dict,
6822 prefs_common.use_alternate,
6823 prefs_common.use_both_dicts,
6824 GTK_TEXT_VIEW(text),
6825 GTK_WINDOW(compose->window),
6826 compose_spell_menu_changed,
6829 alertpanel_error(_("Spell checker could not "
6831 gtkaspell_checkers_strerror());
6832 gtkaspell_checkers_reset_error();
6834 if (!gtkaspell_set_sug_mode(gtkaspell,
6835 prefs_common.aspell_sugmode)) {
6836 debug_print("Aspell: could not set "
6837 "suggestion mode %s\n",
6838 gtkaspell_checkers_strerror());
6839 gtkaspell_checkers_reset_error();
6842 menu_set_sensitive(ifactory, "/Spelling", TRUE);
6846 compose->gtkaspell = gtkaspell;
6847 compose_spell_menu_changed(compose);
6850 compose_select_account(compose, account, TRUE);
6852 menu_set_active(ifactory, "/Edit/Auto wrapping", prefs_common.autowrap);
6853 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
6854 compose_entry_append(compose, account->auto_cc, COMPOSE_CC);
6856 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
6857 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC);
6859 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
6860 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO);
6862 menu_set_sensitive(ifactory, "/Options/Reply mode", compose->mode == COMPOSE_REPLY);
6864 if (account->protocol != A_NNTP)
6865 gtk_entry_set_text(GTK_ENTRY(GTK_BIN(compose->header_last->combo)->child),
6866 prefs_common_translated_header_name("To:"));
6868 gtk_entry_set_text(GTK_ENTRY(GTK_BIN(compose->header_last->combo)->child),
6869 prefs_common_translated_header_name("Newsgroups:"));
6871 addressbook_set_target_compose(compose);
6873 if (mode != COMPOSE_REDIRECT)
6874 compose_set_template_menu(compose);
6876 menuitem = gtk_item_factory_get_item(ifactory, "/Tools/Template");
6877 menu_set_sensitive(ifactory, "/Tools/Template", FALSE);
6880 compose_list = g_list_append(compose_list, compose);
6882 if (!prefs_common.show_ruler)
6883 gtk_widget_hide(ruler_hbox);
6885 menuitem = gtk_item_factory_get_item(ifactory, "/Tools/Show ruler");
6886 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
6887 prefs_common.show_ruler);
6890 compose->priority = PRIORITY_NORMAL;
6891 compose_update_priority_menu_item(compose);
6893 compose_set_out_encoding(compose);
6896 compose_update_actions_menu(compose);
6898 /* Privacy Systems menu */
6899 compose_update_privacy_systems_menu(compose);
6901 activate_privacy_system(compose, account, TRUE);
6902 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
6904 gtk_widget_realize(window);
6906 gtk_widget_show(window);
6908 maemo_window_full_screen_if_needed(GTK_WINDOW(window));
6909 maemo_connect_key_press_to_mainwindow(GTK_WINDOW(window));
6916 static GtkWidget *compose_account_option_menu_create(Compose *compose)
6921 GtkWidget *optmenubox;
6924 GtkWidget *from_name = NULL;
6926 gint num = 0, def_menu = 0;
6928 accounts = account_get_list();
6929 g_return_val_if_fail(accounts != NULL, NULL);
6931 optmenubox = gtk_event_box_new();
6932 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
6933 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
6935 hbox = gtk_hbox_new(FALSE, 6);
6936 from_name = gtk_entry_new();
6938 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
6939 G_CALLBACK(compose_grab_focus_cb), compose);
6941 for (; accounts != NULL; accounts = accounts->next, num++) {
6942 PrefsAccount *ac = (PrefsAccount *)accounts->data;
6943 gchar *name, *from = NULL;
6945 if (ac == compose->account) def_menu = num;
6947 name = g_markup_printf_escaped(_("From: <i>%s</i>"),
6950 if (ac == compose->account) {
6951 if (ac->name && *ac->name) {
6953 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
6954 from = g_strdup_printf("%s <%s>",
6956 gtk_entry_set_text(GTK_ENTRY(from_name), from);
6958 from = g_strdup_printf("%s",
6960 gtk_entry_set_text(GTK_ENTRY(from_name), from);
6963 COMBOBOX_ADD(menu, name, ac->account_id);
6968 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
6970 g_signal_connect(G_OBJECT(optmenu), "changed",
6971 G_CALLBACK(account_activated),
6973 g_signal_connect(G_OBJECT(from_name), "populate-popup",
6974 G_CALLBACK(compose_entry_popup_extend),
6977 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
6978 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
6980 gtk_tooltips_set_tip(compose->tooltips, optmenubox,
6981 _("Account to use for this email"), NULL);
6982 gtk_tooltips_set_tip(compose->tooltips, from_name,
6983 _("Sender address to be used"), NULL);
6985 compose->from_name = from_name;
6990 static void compose_set_priority_cb(gpointer data,
6994 Compose *compose = (Compose *) data;
6995 compose->priority = action;
6998 static void compose_reply_change_mode(gpointer data,
7002 Compose *compose = (Compose *) data;
7003 gboolean was_modified = compose->modified;
7005 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
7007 g_return_if_fail(compose->replyinfo != NULL);
7009 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
7011 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
7013 if (action == COMPOSE_REPLY_TO_ALL)
7015 if (action == COMPOSE_REPLY_TO_SENDER)
7017 if (action == COMPOSE_REPLY_TO_LIST)
7020 compose_remove_header_entries(compose);
7021 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
7022 if (compose->account->set_autocc && compose->account->auto_cc)
7023 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC);
7025 if (compose->account->set_autobcc && compose->account->auto_bcc)
7026 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC);
7028 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
7029 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO);
7030 compose_show_first_last_header(compose, TRUE);
7031 compose->modified = was_modified;
7032 compose_set_title(compose);
7035 static void compose_update_priority_menu_item(Compose * compose)
7037 GtkItemFactory *ifactory;
7038 GtkWidget *menuitem = NULL;
7040 ifactory = gtk_item_factory_from_widget(compose->menubar);
7042 switch (compose->priority) {
7043 case PRIORITY_HIGHEST:
7044 menuitem = gtk_item_factory_get_item
7045 (ifactory, "/Options/Priority/Highest");
7048 menuitem = gtk_item_factory_get_item
7049 (ifactory, "/Options/Priority/High");
7051 case PRIORITY_NORMAL:
7052 menuitem = gtk_item_factory_get_item
7053 (ifactory, "/Options/Priority/Normal");
7056 menuitem = gtk_item_factory_get_item
7057 (ifactory, "/Options/Priority/Low");
7059 case PRIORITY_LOWEST:
7060 menuitem = gtk_item_factory_get_item
7061 (ifactory, "/Options/Priority/Lowest");
7064 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
7067 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
7069 Compose *compose = (Compose *) data;
7071 GtkItemFactory *ifactory;
7072 gboolean can_sign = FALSE, can_encrypt = FALSE;
7074 g_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
7076 if (!GTK_CHECK_MENU_ITEM(widget)->active)
7079 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
7080 g_free(compose->privacy_system);
7081 compose->privacy_system = NULL;
7082 if (systemid != NULL) {
7083 compose->privacy_system = g_strdup(systemid);
7085 can_sign = privacy_system_can_sign(systemid);
7086 can_encrypt = privacy_system_can_encrypt(systemid);
7089 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
7091 ifactory = gtk_item_factory_from_widget(compose->menubar);
7092 menu_set_sensitive(ifactory, "/Options/Sign", can_sign);
7093 menu_set_sensitive(ifactory, "/Options/Encrypt", can_encrypt);
7096 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
7098 static gchar *branch_path = "/Options/Privacy System";
7099 GtkItemFactory *ifactory;
7100 GtkWidget *menuitem = NULL;
7102 gboolean can_sign = FALSE, can_encrypt = FALSE;
7103 gboolean found = FALSE;
7105 ifactory = gtk_item_factory_from_widget(compose->menubar);
7107 if (compose->privacy_system != NULL) {
7110 menuitem = gtk_item_factory_get_widget(ifactory, branch_path);
7111 g_return_if_fail(menuitem != NULL);
7113 amenu = GTK_MENU_SHELL(menuitem)->children;
7115 while (amenu != NULL) {
7116 GList *alist = amenu->next;
7118 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
7119 if (systemid != NULL) {
7120 if (strcmp(systemid, compose->privacy_system) == 0) {
7121 menuitem = GTK_WIDGET(amenu->data);
7123 can_sign = privacy_system_can_sign(systemid);
7124 can_encrypt = privacy_system_can_encrypt(systemid);
7128 } else if (strlen(compose->privacy_system) == 0) {
7129 menuitem = GTK_WIDGET(amenu->data);
7132 can_encrypt = FALSE;
7139 if (menuitem != NULL)
7140 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
7142 if (warn && !found && strlen(compose->privacy_system)) {
7143 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
7144 "will not be able to sign or encrypt this message."),
7145 compose->privacy_system);
7149 menu_set_sensitive(ifactory, "/Options/Sign", can_sign);
7150 menu_set_sensitive(ifactory, "/Options/Encrypt", can_encrypt);
7153 static void compose_set_out_encoding(Compose *compose)
7155 GtkItemFactoryEntry *entry;
7156 GtkItemFactory *ifactory;
7157 CharSet out_encoding;
7158 gchar *path, *p, *q;
7161 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
7162 ifactory = gtk_item_factory_from_widget(compose->menubar);
7164 for (entry = compose_entries; entry->callback != compose_address_cb;
7166 if (entry->callback == compose_set_encoding_cb &&
7167 (CharSet)entry->callback_action == out_encoding) {
7168 p = q = path = g_strdup(entry->path);
7180 item = gtk_item_factory_get_item(ifactory, path);
7181 gtk_widget_activate(item);
7188 static void compose_set_template_menu(Compose *compose)
7190 GSList *tmpl_list, *cur;
7194 tmpl_list = template_get_config();
7196 menu = gtk_menu_new();
7198 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
7199 Template *tmpl = (Template *)cur->data;
7201 item = gtk_menu_item_new_with_label(tmpl->name);
7202 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
7203 g_signal_connect(G_OBJECT(item), "activate",
7204 G_CALLBACK(compose_template_activate_cb),
7206 g_object_set_data(G_OBJECT(item), "template", tmpl);
7207 gtk_widget_show(item);
7210 gtk_widget_show(menu);
7211 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
7214 void compose_update_actions_menu(Compose *compose)
7216 GtkItemFactory *ifactory;
7218 ifactory = gtk_item_factory_from_widget(compose->menubar);
7219 action_update_compose_menu(ifactory, "/Tools/Actions", compose);
7222 static void compose_update_privacy_systems_menu(Compose *compose)
7224 static gchar *branch_path = "/Options/Privacy System";
7225 GtkItemFactory *ifactory;
7226 GtkWidget *menuitem;
7227 GSList *systems, *cur;
7230 GtkWidget *system_none;
7233 ifactory = gtk_item_factory_from_widget(compose->menubar);
7235 /* remove old entries */
7236 menuitem = gtk_item_factory_get_widget(ifactory, branch_path);
7237 g_return_if_fail(menuitem != NULL);
7239 amenu = GTK_MENU_SHELL(menuitem)->children->next;
7240 while (amenu != NULL) {
7241 GList *alist = amenu->next;
7242 gtk_widget_destroy(GTK_WIDGET(amenu->data));
7246 system_none = gtk_item_factory_get_widget(ifactory,
7247 "/Options/Privacy System/None");
7249 g_signal_connect(G_OBJECT(system_none), "activate",
7250 G_CALLBACK(compose_set_privacy_system_cb), compose);
7252 systems = privacy_get_system_ids();
7253 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
7254 gchar *systemid = cur->data;
7256 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
7257 widget = gtk_radio_menu_item_new_with_label(group,
7258 privacy_system_get_name(systemid));
7259 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
7260 g_strdup(systemid), g_free);
7261 g_signal_connect(G_OBJECT(widget), "activate",
7262 G_CALLBACK(compose_set_privacy_system_cb), compose);
7264 gtk_menu_append(GTK_MENU(system_none->parent), widget);
7265 gtk_widget_show(widget);
7268 g_slist_free(systems);
7271 void compose_reflect_prefs_all(void)
7276 for (cur = compose_list; cur != NULL; cur = cur->next) {
7277 compose = (Compose *)cur->data;
7278 compose_set_template_menu(compose);
7282 void compose_reflect_prefs_pixmap_theme(void)
7287 for (cur = compose_list; cur != NULL; cur = cur->next) {
7288 compose = (Compose *)cur->data;
7289 toolbar_update(TOOLBAR_COMPOSE, compose);
7293 static const gchar *compose_quote_char_from_context(Compose *compose)
7295 const gchar *qmark = NULL;
7297 g_return_val_if_fail(compose != NULL, NULL);
7299 switch (compose->mode) {
7300 /* use forward-specific quote char */
7301 case COMPOSE_FORWARD:
7302 case COMPOSE_FORWARD_AS_ATTACH:
7303 case COMPOSE_FORWARD_INLINE:
7304 if (compose->folder && compose->folder->prefs &&
7305 compose->folder->prefs->forward_with_format)
7306 qmark = compose->folder->prefs->forward_quotemark;
7307 else if (compose->account->forward_with_format)
7308 qmark = compose->account->forward_quotemark;
7310 qmark = prefs_common.fw_quotemark;
7313 /* use reply-specific quote char in all other modes */
7315 if (compose->folder && compose->folder->prefs &&
7316 compose->folder->prefs->reply_with_format)
7317 qmark = compose->folder->prefs->reply_quotemark;
7318 else if (compose->account->reply_with_format)
7319 qmark = compose->account->reply_quotemark;
7321 qmark = prefs_common.quotemark;
7325 if (qmark == NULL || *qmark == '\0')
7331 static void compose_template_apply(Compose *compose, Template *tmpl,
7335 GtkTextBuffer *buffer;
7339 gchar *parsed_str = NULL;
7340 gint cursor_pos = 0;
7341 const gchar *err_msg = _("Template body format error at line %d.");
7344 /* process the body */
7346 text = GTK_TEXT_VIEW(compose->text);
7347 buffer = gtk_text_view_get_buffer(text);
7350 qmark = compose_quote_char_from_context(compose);
7352 if (compose->replyinfo != NULL) {
7355 gtk_text_buffer_set_text(buffer, "", -1);
7356 mark = gtk_text_buffer_get_insert(buffer);
7357 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7359 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
7360 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
7362 } else if (compose->fwdinfo != NULL) {
7365 gtk_text_buffer_set_text(buffer, "", -1);
7366 mark = gtk_text_buffer_get_insert(buffer);
7367 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7369 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
7370 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
7373 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
7375 GtkTextIter start, end;
7378 gtk_text_buffer_get_start_iter(buffer, &start);
7379 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
7380 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
7382 /* clear the buffer now */
7384 gtk_text_buffer_set_text(buffer, "", -1);
7386 parsed_str = compose_quote_fmt(compose, dummyinfo,
7387 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
7388 procmsg_msginfo_free( dummyinfo );
7394 gtk_text_buffer_set_text(buffer, "", -1);
7395 mark = gtk_text_buffer_get_insert(buffer);
7396 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7399 if (replace && parsed_str && compose->account->auto_sig)
7400 compose_insert_sig(compose, FALSE);
7402 if (replace && parsed_str) {
7403 gtk_text_buffer_get_start_iter(buffer, &iter);
7404 gtk_text_buffer_place_cursor(buffer, &iter);
7408 cursor_pos = quote_fmt_get_cursor_pos();
7409 compose->set_cursor_pos = cursor_pos;
7410 if (cursor_pos == -1)
7412 gtk_text_buffer_get_start_iter(buffer, &iter);
7413 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
7414 gtk_text_buffer_place_cursor(buffer, &iter);
7417 /* process the other fields */
7419 compose_template_apply_fields(compose, tmpl);
7420 quote_fmt_reset_vartable();
7421 compose_changed_cb(NULL, compose);
7424 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
7426 MsgInfo* dummyinfo = NULL;
7427 MsgInfo *msginfo = NULL;
7430 if (compose->replyinfo != NULL)
7431 msginfo = compose->replyinfo;
7432 else if (compose->fwdinfo != NULL)
7433 msginfo = compose->fwdinfo;
7435 dummyinfo = compose_msginfo_new_from_compose(compose);
7436 msginfo = dummyinfo;
7439 if (tmpl->to && *tmpl->to != '\0') {
7441 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7442 compose->gtkaspell);
7444 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7446 quote_fmt_scan_string(tmpl->to);
7449 buf = quote_fmt_get_buffer();
7451 alertpanel_error(_("Template To format error."));
7453 compose_entry_append(compose, buf, COMPOSE_TO);
7457 if (tmpl->cc && *tmpl->cc != '\0') {
7459 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7460 compose->gtkaspell);
7462 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7464 quote_fmt_scan_string(tmpl->cc);
7467 buf = quote_fmt_get_buffer();
7469 alertpanel_error(_("Template Cc format error."));
7471 compose_entry_append(compose, buf, COMPOSE_CC);
7475 if (tmpl->bcc && *tmpl->bcc != '\0') {
7477 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7478 compose->gtkaspell);
7480 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7482 quote_fmt_scan_string(tmpl->bcc);
7485 buf = quote_fmt_get_buffer();
7487 alertpanel_error(_("Template Bcc format error."));
7489 compose_entry_append(compose, buf, COMPOSE_BCC);
7493 /* process the subject */
7494 if (tmpl->subject && *tmpl->subject != '\0') {
7496 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7497 compose->gtkaspell);
7499 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7501 quote_fmt_scan_string(tmpl->subject);
7504 buf = quote_fmt_get_buffer();
7506 alertpanel_error(_("Template subject format error."));
7508 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
7512 procmsg_msginfo_free( dummyinfo );
7515 static void compose_destroy(Compose *compose)
7517 GtkTextBuffer *buffer;
7518 GtkClipboard *clipboard;
7520 compose_list = g_list_remove(compose_list, compose);
7522 if (compose->updating) {
7523 debug_print("danger, not destroying anything now\n");
7524 compose->deferred_destroy = TRUE;
7527 /* NOTE: address_completion_end() does nothing with the window
7528 * however this may change. */
7529 address_completion_end(compose->window);
7531 slist_free_strings(compose->to_list);
7532 g_slist_free(compose->to_list);
7533 slist_free_strings(compose->newsgroup_list);
7534 g_slist_free(compose->newsgroup_list);
7535 slist_free_strings(compose->header_list);
7536 g_slist_free(compose->header_list);
7538 procmsg_msginfo_free(compose->targetinfo);
7539 procmsg_msginfo_free(compose->replyinfo);
7540 procmsg_msginfo_free(compose->fwdinfo);
7542 g_free(compose->replyto);
7543 g_free(compose->cc);
7544 g_free(compose->bcc);
7545 g_free(compose->newsgroups);
7546 g_free(compose->followup_to);
7548 g_free(compose->ml_post);
7550 g_free(compose->inreplyto);
7551 g_free(compose->references);
7552 g_free(compose->msgid);
7553 g_free(compose->boundary);
7555 g_free(compose->redirect_filename);
7556 if (compose->undostruct)
7557 undo_destroy(compose->undostruct);
7559 g_free(compose->sig_str);
7561 g_free(compose->exteditor_file);
7563 g_free(compose->orig_charset);
7565 g_free(compose->privacy_system);
7567 if (addressbook_get_target_compose() == compose)
7568 addressbook_set_target_compose(NULL);
7571 if (compose->gtkaspell) {
7572 gtkaspell_delete(compose->gtkaspell);
7573 compose->gtkaspell = NULL;
7577 prefs_common.compose_width = compose->scrolledwin->allocation.width;
7578 prefs_common.compose_height = compose->window->allocation.height;
7580 if (!gtk_widget_get_parent(compose->paned))
7581 gtk_widget_destroy(compose->paned);
7582 gtk_widget_destroy(compose->popupmenu);
7584 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
7585 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7586 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
7588 gtk_widget_destroy(compose->window);
7589 toolbar_destroy(compose->toolbar);
7590 g_free(compose->toolbar);
7591 g_mutex_free(compose->mutex);
7595 static void compose_attach_info_free(AttachInfo *ainfo)
7597 g_free(ainfo->file);
7598 g_free(ainfo->content_type);
7599 g_free(ainfo->name);
7603 static void compose_attach_update_label(Compose *compose)
7608 GtkTreeModel *model;
7613 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
7614 if(!gtk_tree_model_get_iter_first(model, &iter)) {
7615 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
7619 while(gtk_tree_model_iter_next(model, &iter))
7622 text = g_strdup_printf("(%d)", i);
7623 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
7627 static void compose_attach_remove_selected(Compose *compose)
7629 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
7630 GtkTreeSelection *selection;
7632 GtkTreeModel *model;
7634 selection = gtk_tree_view_get_selection(tree_view);
7635 sel = gtk_tree_selection_get_selected_rows(selection, &model);
7640 for (cur = sel; cur != NULL; cur = cur->next) {
7641 GtkTreePath *path = cur->data;
7642 GtkTreeRowReference *ref = gtk_tree_row_reference_new
7645 gtk_tree_path_free(path);
7648 for (cur = sel; cur != NULL; cur = cur->next) {
7649 GtkTreeRowReference *ref = cur->data;
7650 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
7653 if (gtk_tree_model_get_iter(model, &iter, path))
7654 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
7656 gtk_tree_path_free(path);
7657 gtk_tree_row_reference_free(ref);
7661 compose_attach_update_label(compose);
7664 static struct _AttachProperty
7667 GtkWidget *mimetype_entry;
7668 GtkWidget *encoding_optmenu;
7669 GtkWidget *path_entry;
7670 GtkWidget *filename_entry;
7672 GtkWidget *cancel_btn;
7675 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
7677 gtk_tree_path_free((GtkTreePath *)ptr);
7680 static void compose_attach_property(Compose *compose)
7682 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
7684 GtkComboBox *optmenu;
7685 GtkTreeSelection *selection;
7687 GtkTreeModel *model;
7690 static gboolean cancelled;
7692 /* only if one selected */
7693 selection = gtk_tree_view_get_selection(tree_view);
7694 if (gtk_tree_selection_count_selected_rows(selection) != 1)
7697 sel = gtk_tree_selection_get_selected_rows(selection, &model);
7701 path = (GtkTreePath *) sel->data;
7702 gtk_tree_model_get_iter(model, &iter, path);
7703 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
7706 g_list_foreach(sel, gtk_tree_path_free_, NULL);
7712 if (!attach_prop.window)
7713 compose_attach_property_create(&cancelled);
7714 gtk_widget_grab_focus(attach_prop.ok_btn);
7715 gtk_widget_show(attach_prop.window);
7716 manage_window_set_transient(GTK_WINDOW(attach_prop.window));
7718 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
7719 if (ainfo->encoding == ENC_UNKNOWN)
7720 combobox_select_by_data(optmenu, ENC_BASE64);
7722 combobox_select_by_data(optmenu, ainfo->encoding);
7724 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
7725 ainfo->content_type ? ainfo->content_type : "");
7726 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
7727 ainfo->file ? ainfo->file : "");
7728 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
7729 ainfo->name ? ainfo->name : "");
7732 const gchar *entry_text;
7734 gchar *cnttype = NULL;
7741 gtk_widget_hide(attach_prop.window);
7746 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
7747 if (*entry_text != '\0') {
7750 text = g_strstrip(g_strdup(entry_text));
7751 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
7752 cnttype = g_strdup(text);
7755 alertpanel_error(_("Invalid MIME type."));
7761 ainfo->encoding = combobox_get_active_data(optmenu);
7763 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
7764 if (*entry_text != '\0') {
7765 if (is_file_exist(entry_text) &&
7766 (size = get_file_size(entry_text)) > 0)
7767 file = g_strdup(entry_text);
7770 (_("File doesn't exist or is empty."));
7776 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
7777 if (*entry_text != '\0') {
7778 g_free(ainfo->name);
7779 ainfo->name = g_strdup(entry_text);
7783 g_free(ainfo->content_type);
7784 ainfo->content_type = cnttype;
7787 g_free(ainfo->file);
7793 /* update tree store */
7794 text = to_human_readable(ainfo->size);
7795 gtk_tree_model_get_iter(model, &iter, path);
7796 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
7797 COL_MIMETYPE, ainfo->content_type,
7799 COL_NAME, ainfo->name,
7805 gtk_tree_path_free(path);
7808 #define SET_LABEL_AND_ENTRY(str, entry, top) \
7810 label = gtk_label_new(str); \
7811 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
7812 GTK_FILL, 0, 0, 0); \
7813 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
7815 entry = gtk_entry_new(); \
7816 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
7817 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
7820 static void compose_attach_property_create(gboolean *cancelled)
7826 GtkWidget *mimetype_entry;
7829 GtkListStore *optmenu_menu;
7830 GtkWidget *path_entry;
7831 GtkWidget *filename_entry;
7834 GtkWidget *cancel_btn;
7835 GList *mime_type_list, *strlist;
7838 debug_print("Creating attach_property window...\n");
7840 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
7841 gtk_widget_set_size_request(window, 480, -1);
7842 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
7843 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
7844 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
7845 gtk_window_set_modal(GTK_WINDOW(window), TRUE);
7846 g_signal_connect(G_OBJECT(window), "delete_event",
7847 G_CALLBACK(attach_property_delete_event),
7849 g_signal_connect(G_OBJECT(window), "key_press_event",
7850 G_CALLBACK(attach_property_key_pressed),
7853 vbox = gtk_vbox_new(FALSE, 8);
7854 gtk_container_add(GTK_CONTAINER(window), vbox);
7856 table = gtk_table_new(4, 2, FALSE);
7857 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
7858 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
7859 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
7861 label = gtk_label_new(_("MIME type"));
7862 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
7864 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
7865 mimetype_entry = gtk_combo_box_entry_new_text();
7866 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
7867 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
7869 /* stuff with list */
7870 mime_type_list = procmime_get_mime_type_list();
7872 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
7873 MimeType *type = (MimeType *) mime_type_list->data;
7876 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
7878 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
7881 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
7882 (GCompareFunc)strcmp2);
7885 for (mime_type_list = strlist; mime_type_list != NULL;
7886 mime_type_list = mime_type_list->next) {
7887 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
7888 g_free(mime_type_list->data);
7890 g_list_free(strlist);
7891 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
7892 mimetype_entry = GTK_BIN(mimetype_entry)->child;
7894 label = gtk_label_new(_("Encoding"));
7895 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
7897 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
7899 hbox = gtk_hbox_new(FALSE, 0);
7900 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
7901 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
7903 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
7904 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
7906 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
7907 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
7908 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
7909 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
7910 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
7912 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
7914 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
7915 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
7917 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
7918 &ok_btn, GTK_STOCK_OK,
7920 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
7921 gtk_widget_grab_default(ok_btn);
7923 g_signal_connect(G_OBJECT(ok_btn), "clicked",
7924 G_CALLBACK(attach_property_ok),
7926 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
7927 G_CALLBACK(attach_property_cancel),
7930 gtk_widget_show_all(vbox);
7932 attach_prop.window = window;
7933 attach_prop.mimetype_entry = mimetype_entry;
7934 attach_prop.encoding_optmenu = optmenu;
7935 attach_prop.path_entry = path_entry;
7936 attach_prop.filename_entry = filename_entry;
7937 attach_prop.ok_btn = ok_btn;
7938 attach_prop.cancel_btn = cancel_btn;
7941 #undef SET_LABEL_AND_ENTRY
7943 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
7949 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
7955 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
7956 gboolean *cancelled)
7964 static gboolean attach_property_key_pressed(GtkWidget *widget,
7966 gboolean *cancelled)
7968 if (event && event->keyval == GDK_Escape) {
7975 static void compose_exec_ext_editor(Compose *compose)
7982 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
7983 G_DIR_SEPARATOR, compose);
7985 if (pipe(pipe_fds) < 0) {
7991 if ((pid = fork()) < 0) {
7998 /* close the write side of the pipe */
8001 compose->exteditor_file = g_strdup(tmp);
8002 compose->exteditor_pid = pid;
8004 compose_set_ext_editor_sensitive(compose, FALSE);
8006 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
8007 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
8011 } else { /* process-monitoring process */
8017 /* close the read side of the pipe */
8020 if (compose_write_body_to_file(compose, tmp) < 0) {
8021 fd_write_all(pipe_fds[1], "2\n", 2);
8025 pid_ed = compose_exec_ext_editor_real(tmp);
8027 fd_write_all(pipe_fds[1], "1\n", 2);
8031 /* wait until editor is terminated */
8032 waitpid(pid_ed, NULL, 0);
8034 fd_write_all(pipe_fds[1], "0\n", 2);
8041 #endif /* G_OS_UNIX */
8045 static gint compose_exec_ext_editor_real(const gchar *file)
8052 g_return_val_if_fail(file != NULL, -1);
8054 if ((pid = fork()) < 0) {
8059 if (pid != 0) return pid;
8061 /* grandchild process */
8063 if (setpgid(0, getppid()))
8066 if (prefs_common.ext_editor_cmd &&
8067 (p = strchr(prefs_common.ext_editor_cmd, '%')) &&
8068 *(p + 1) == 's' && !strchr(p + 2, '%')) {
8069 g_snprintf(buf, sizeof(buf), prefs_common.ext_editor_cmd, file);
8071 if (prefs_common.ext_editor_cmd)
8072 g_warning("External editor command line is invalid: '%s'\n",
8073 prefs_common.ext_editor_cmd);
8074 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
8077 cmdline = strsplit_with_quote(buf, " ", 1024);
8078 execvp(cmdline[0], cmdline);
8081 g_strfreev(cmdline);
8086 static gboolean compose_ext_editor_kill(Compose *compose)
8088 pid_t pgid = compose->exteditor_pid * -1;
8091 ret = kill(pgid, 0);
8093 if (ret == 0 || (ret == -1 && EPERM == errno)) {
8097 msg = g_strdup_printf
8098 (_("The external editor is still working.\n"
8099 "Force terminating the process?\n"
8100 "process group id: %d"), -pgid);
8101 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
8102 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
8106 if (val == G_ALERTALTERNATE) {
8107 g_source_remove(compose->exteditor_tag);
8108 g_io_channel_shutdown(compose->exteditor_ch,
8110 g_io_channel_unref(compose->exteditor_ch);
8112 if (kill(pgid, SIGTERM) < 0) perror("kill");
8113 waitpid(compose->exteditor_pid, NULL, 0);
8115 g_warning("Terminated process group id: %d", -pgid);
8116 g_warning("Temporary file: %s",
8117 compose->exteditor_file);
8119 compose_set_ext_editor_sensitive(compose, TRUE);
8121 g_free(compose->exteditor_file);
8122 compose->exteditor_file = NULL;
8123 compose->exteditor_pid = -1;
8124 compose->exteditor_ch = NULL;
8125 compose->exteditor_tag = -1;
8133 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
8137 Compose *compose = (Compose *)data;
8140 debug_print(_("Compose: input from monitoring process\n"));
8142 g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL);
8144 g_io_channel_shutdown(source, FALSE, NULL);
8145 g_io_channel_unref(source);
8147 waitpid(compose->exteditor_pid, NULL, 0);
8149 if (buf[0] == '0') { /* success */
8150 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
8151 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
8153 gtk_text_buffer_set_text(buffer, "", -1);
8154 compose_insert_file(compose, compose->exteditor_file);
8155 compose_changed_cb(NULL, compose);
8157 if (g_unlink(compose->exteditor_file) < 0)
8158 FILE_OP_ERROR(compose->exteditor_file, "unlink");
8159 } else if (buf[0] == '1') { /* failed */
8160 g_warning("Couldn't exec external editor\n");
8161 if (g_unlink(compose->exteditor_file) < 0)
8162 FILE_OP_ERROR(compose->exteditor_file, "unlink");
8163 } else if (buf[0] == '2') {
8164 g_warning("Couldn't write to file\n");
8165 } else if (buf[0] == '3') {
8166 g_warning("Pipe read failed\n");
8169 compose_set_ext_editor_sensitive(compose, TRUE);
8171 g_free(compose->exteditor_file);
8172 compose->exteditor_file = NULL;
8173 compose->exteditor_pid = -1;
8174 compose->exteditor_ch = NULL;
8175 compose->exteditor_tag = -1;
8180 static void compose_set_ext_editor_sensitive(Compose *compose,
8183 GtkItemFactory *ifactory;
8185 ifactory = gtk_item_factory_from_widget(compose->menubar);
8187 menu_set_sensitive(ifactory, "/Message/Send", sensitive);
8188 menu_set_sensitive(ifactory, "/Message/Send later", sensitive);
8189 menu_set_sensitive(ifactory, "/Message/Insert file", sensitive);
8190 menu_set_sensitive(ifactory, "/Message/Insert signature", sensitive);
8191 menu_set_sensitive(ifactory, "/Edit/Wrap current paragraph", sensitive);
8192 menu_set_sensitive(ifactory, "/Edit/Wrap all long lines", sensitive);
8193 menu_set_sensitive(ifactory, "/Edit/Edit with external editor",
8196 gtk_widget_set_sensitive(compose->text, sensitive);
8197 if (compose->toolbar->send_btn)
8198 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
8199 if (compose->toolbar->sendl_btn)
8200 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
8201 if (compose->toolbar->draft_btn)
8202 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
8203 if (compose->toolbar->insert_btn)
8204 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
8205 if (compose->toolbar->sig_btn)
8206 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
8207 if (compose->toolbar->exteditor_btn)
8208 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
8209 if (compose->toolbar->linewrap_current_btn)
8210 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
8211 if (compose->toolbar->linewrap_all_btn)
8212 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
8214 #endif /* G_OS_UNIX */
8217 * compose_undo_state_changed:
8219 * Change the sensivity of the menuentries undo and redo
8221 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
8222 gint redo_state, gpointer data)
8224 GtkWidget *widget = GTK_WIDGET(data);
8225 GtkItemFactory *ifactory;
8227 g_return_if_fail(widget != NULL);
8229 ifactory = gtk_item_factory_from_widget(widget);
8231 switch (undo_state) {
8232 case UNDO_STATE_TRUE:
8233 if (!undostruct->undo_state) {
8234 undostruct->undo_state = TRUE;
8235 menu_set_sensitive(ifactory, "/Edit/Undo", TRUE);
8238 case UNDO_STATE_FALSE:
8239 if (undostruct->undo_state) {
8240 undostruct->undo_state = FALSE;
8241 menu_set_sensitive(ifactory, "/Edit/Undo", FALSE);
8244 case UNDO_STATE_UNCHANGED:
8246 case UNDO_STATE_REFRESH:
8247 menu_set_sensitive(ifactory, "/Edit/Undo",
8248 undostruct->undo_state);
8251 g_warning("Undo state not recognized");
8255 switch (redo_state) {
8256 case UNDO_STATE_TRUE:
8257 if (!undostruct->redo_state) {
8258 undostruct->redo_state = TRUE;
8259 menu_set_sensitive(ifactory, "/Edit/Redo", TRUE);
8262 case UNDO_STATE_FALSE:
8263 if (undostruct->redo_state) {
8264 undostruct->redo_state = FALSE;
8265 menu_set_sensitive(ifactory, "/Edit/Redo", FALSE);
8268 case UNDO_STATE_UNCHANGED:
8270 case UNDO_STATE_REFRESH:
8271 menu_set_sensitive(ifactory, "/Edit/Redo",
8272 undostruct->redo_state);
8275 g_warning("Redo state not recognized");
8280 /* callback functions */
8282 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
8283 * includes "non-client" (windows-izm) in calculation, so this calculation
8284 * may not be accurate.
8286 static gboolean compose_edit_size_alloc(GtkEditable *widget,
8287 GtkAllocation *allocation,
8288 GtkSHRuler *shruler)
8290 if (prefs_common.show_ruler) {
8291 gint char_width = 0, char_height = 0;
8292 gint line_width_in_chars;
8294 gtkut_get_font_size(GTK_WIDGET(widget),
8295 &char_width, &char_height);
8296 line_width_in_chars =
8297 (allocation->width - allocation->x) / char_width;
8299 /* got the maximum */
8300 gtk_ruler_set_range(GTK_RULER(shruler),
8301 0.0, line_width_in_chars, 0,
8302 /*line_width_in_chars*/ char_width);
8308 static void account_activated(GtkComboBox *optmenu, gpointer data)
8310 Compose *compose = (Compose *)data;
8313 gchar *folderidentifier;
8314 gint account_id = 0;
8318 /* Get ID of active account in the combo box */
8319 menu = gtk_combo_box_get_model(optmenu);
8320 gtk_combo_box_get_active_iter(optmenu, &iter);
8321 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
8323 ac = account_find_from_id(account_id);
8324 g_return_if_fail(ac != NULL);
8326 if (ac != compose->account)
8327 compose_select_account(compose, ac, FALSE);
8329 /* Set message save folder */
8330 if (account_get_special_folder(compose->account, F_OUTBOX)) {
8331 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
8333 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
8334 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
8336 gtk_editable_delete_text(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
8337 if (account_get_special_folder(compose->account, F_OUTBOX)) {
8338 folderidentifier = folder_item_get_identifier(account_get_special_folder
8339 (compose->account, F_OUTBOX));
8340 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
8341 g_free(folderidentifier);
8345 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
8346 GtkTreeViewColumn *column, Compose *compose)
8348 compose_attach_property(compose);
8351 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
8354 Compose *compose = (Compose *)data;
8355 GtkTreeSelection *attach_selection;
8356 gint attach_nr_selected;
8357 GtkItemFactory *ifactory;
8359 if (!event) return FALSE;
8361 if (event->button == 3) {
8362 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
8363 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
8364 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
8366 if (attach_nr_selected > 0)
8368 menu_set_sensitive(ifactory, "/Remove", TRUE);
8369 menu_set_sensitive(ifactory, "/Properties...", TRUE);
8371 menu_set_sensitive(ifactory, "/Remove", FALSE);
8372 menu_set_sensitive(ifactory, "/Properties...", FALSE);
8375 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
8376 NULL, NULL, event->button, event->time);
8383 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
8386 Compose *compose = (Compose *)data;
8388 if (!event) return FALSE;
8390 switch (event->keyval) {
8392 compose_attach_remove_selected(compose);
8398 static void compose_allow_user_actions (Compose *compose, gboolean allow)
8400 GtkItemFactory *ifactory = gtk_item_factory_from_widget(compose->menubar);
8401 toolbar_comp_set_sensitive(compose, allow);
8402 menu_set_sensitive(ifactory, "/Message", allow);
8403 menu_set_sensitive(ifactory, "/Edit", allow);
8405 menu_set_sensitive(ifactory, "/Spelling", allow);
8407 menu_set_sensitive(ifactory, "/Options", allow);
8408 menu_set_sensitive(ifactory, "/Tools", allow);
8409 menu_set_sensitive(ifactory, "/Help", allow);
8411 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
8415 static void compose_send_cb(gpointer data, guint action, GtkWidget *widget)
8417 Compose *compose = (Compose *)data;
8419 if (prefs_common.work_offline &&
8420 !inc_offline_should_override(TRUE,
8421 _("Claws Mail needs network access in order "
8422 "to send this email.")))
8425 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
8426 g_source_remove(compose->draft_timeout_tag);
8427 compose->draft_timeout_tag = -1;
8430 compose_send(compose);
8433 static void compose_send_later_cb(gpointer data, guint action,
8436 Compose *compose = (Compose *)data;
8440 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
8444 compose_close(compose);
8445 } else if (val == -1) {
8446 alertpanel_error(_("Could not queue message."));
8447 } else if (val == -2) {
8448 alertpanel_error(_("Could not queue message:\n\n%s."), strerror(errno));
8449 } else if (val == -3) {
8450 if (privacy_peek_error())
8451 alertpanel_error(_("Could not queue message for sending:\n\n"
8452 "Signature failed: %s"), privacy_get_error());
8453 } else if (val == -4) {
8454 alertpanel_error(_("Could not queue message for sending:\n\n"
8455 "Charset conversion failed."));
8456 } else if (val == -5) {
8457 alertpanel_error(_("Could not queue message for sending:\n\n"
8458 "Couldn't get recipient encryption key."));
8459 } else if (val == -6) {
8462 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
8465 #define DRAFTED_AT_EXIT "drafted_at_exit"
8466 static void compose_register_draft(MsgInfo *info)
8468 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8469 DRAFTED_AT_EXIT, NULL);
8470 FILE *fp = fopen(filepath, "ab");
8473 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
8481 gboolean compose_draft (gpointer data, guint action)
8483 Compose *compose = (Compose *)data;
8487 MsgFlags flag = {0, 0};
8488 static gboolean lock = FALSE;
8489 MsgInfo *newmsginfo;
8491 gboolean target_locked = FALSE;
8492 gboolean err = FALSE;
8494 if (lock) return FALSE;
8496 if (compose->sending)
8499 draft = account_get_special_folder(compose->account, F_DRAFT);
8500 g_return_val_if_fail(draft != NULL, FALSE);
8502 if (!g_mutex_trylock(compose->mutex)) {
8503 /* we don't want to lock the mutex once it's available,
8504 * because as the only other part of compose.c locking
8505 * it is compose_close - which means once unlocked,
8506 * the compose struct will be freed */
8507 debug_print("couldn't lock mutex, probably sending\n");
8513 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
8514 G_DIR_SEPARATOR, compose);
8515 if ((fp = g_fopen(tmp, "wb")) == NULL) {
8516 FILE_OP_ERROR(tmp, "fopen");
8520 /* chmod for security */
8521 if (change_file_mode_rw(fp, tmp) < 0) {
8522 FILE_OP_ERROR(tmp, "chmod");
8523 g_warning("can't change file mode\n");
8526 /* Save draft infos */
8527 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
8528 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
8530 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
8531 gchar *savefolderid;
8533 savefolderid = gtk_editable_get_chars(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
8534 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
8535 g_free(savefolderid);
8537 if (compose->return_receipt) {
8538 err |= (fprintf(fp, "RRCPT:1\n") < 0);
8540 if (compose->privacy_system) {
8541 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
8542 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
8543 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
8546 /* Message-ID of message replying to */
8547 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
8550 folderid = folder_item_get_identifier(compose->replyinfo->folder);
8551 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
8554 /* Message-ID of message forwarding to */
8555 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
8558 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
8559 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
8563 /* end of headers */
8564 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
8571 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
8575 if (fclose(fp) == EOF) {
8579 if (compose->targetinfo) {
8580 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
8581 flag.perm_flags = target_locked?MSG_LOCKED:0;
8583 flag.tmp_flags = MSG_DRAFT;
8585 folder_item_scan(draft);
8586 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
8587 MsgInfo *tmpinfo = NULL;
8588 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
8589 if (compose->msgid) {
8590 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
8593 msgnum = tmpinfo->msgnum;
8594 procmsg_msginfo_free(tmpinfo);
8595 debug_print("got draft msgnum %d from scanning\n", msgnum);
8597 debug_print("didn't get draft msgnum after scanning\n");
8600 debug_print("got draft msgnum %d from adding\n", msgnum);
8606 if (action != COMPOSE_AUTO_SAVE) {
8607 if (action != COMPOSE_DRAFT_FOR_EXIT)
8608 alertpanel_error(_("Could not save draft."));
8611 gtkut_window_popup(compose->window);
8612 val = alertpanel_full(_("Could not save draft"),
8613 _("Could not save draft.\n"
8614 "Do you want to cancel exit or discard this email?"),
8615 _("_Cancel exit"), _("_Discard email"), NULL,
8616 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
8617 if (val == G_ALERTALTERNATE) {
8619 g_mutex_unlock(compose->mutex); /* must be done before closing */
8620 compose_close(compose);
8624 g_mutex_unlock(compose->mutex); /* must be done before closing */
8633 if (compose->mode == COMPOSE_REEDIT) {
8634 compose_remove_reedit_target(compose, TRUE);
8637 newmsginfo = folder_item_get_msginfo(draft, msgnum);
8640 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
8642 procmsg_msginfo_set_flags(newmsginfo, MSG_LOCKED, MSG_DRAFT);
8644 procmsg_msginfo_set_flags(newmsginfo, 0, MSG_DRAFT);
8645 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
8646 procmsg_msginfo_set_flags(newmsginfo, 0,
8647 MSG_HAS_ATTACHMENT);
8649 if (action == COMPOSE_DRAFT_FOR_EXIT) {
8650 compose_register_draft(newmsginfo);
8652 procmsg_msginfo_free(newmsginfo);
8655 folder_item_scan(draft);
8657 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
8659 g_mutex_unlock(compose->mutex); /* must be done before closing */
8660 compose_close(compose);
8666 path = folder_item_fetch_msg(draft, msgnum);
8668 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
8671 if (g_stat(path, &s) < 0) {
8672 FILE_OP_ERROR(path, "stat");
8678 procmsg_msginfo_free(compose->targetinfo);
8679 compose->targetinfo = procmsg_msginfo_new();
8680 compose->targetinfo->msgnum = msgnum;
8681 compose->targetinfo->size = s.st_size;
8682 compose->targetinfo->mtime = s.st_mtime;
8683 compose->targetinfo->folder = draft;
8685 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
8686 compose->mode = COMPOSE_REEDIT;
8688 if (action == COMPOSE_AUTO_SAVE) {
8689 compose->autosaved_draft = compose->targetinfo;
8691 compose->modified = FALSE;
8692 compose_set_title(compose);
8696 g_mutex_unlock(compose->mutex);
8700 void compose_clear_exit_drafts(void)
8702 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8703 DRAFTED_AT_EXIT, NULL);
8704 if (is_file_exist(filepath))
8710 void compose_reopen_exit_drafts(void)
8712 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8713 DRAFTED_AT_EXIT, NULL);
8714 FILE *fp = fopen(filepath, "rb");
8718 while (fgets(buf, sizeof(buf), fp)) {
8719 gchar **parts = g_strsplit(buf, "\t", 2);
8720 const gchar *folder = parts[0];
8721 int msgnum = parts[1] ? atoi(parts[1]):-1;
8723 if (folder && *folder && msgnum > -1) {
8724 FolderItem *item = folder_find_item_from_identifier(folder);
8725 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
8727 compose_reedit(info, FALSE);
8734 compose_clear_exit_drafts();
8737 static void compose_draft_cb(gpointer data, guint action, GtkWidget *widget)
8739 compose_draft(data, action);
8742 static void compose_attach_cb(gpointer data, guint action, GtkWidget *widget)
8744 Compose *compose = (Compose *)data;
8747 if (compose->redirect_filename != NULL)
8750 file_list = filesel_select_multiple_files_open(_("Select file"));
8755 for ( tmp = file_list; tmp; tmp = tmp->next) {
8756 gchar *file = (gchar *) tmp->data;
8757 gchar *utf8_filename = conv_filename_to_utf8(file);
8758 compose_attach_append(compose, file, utf8_filename, NULL);
8759 compose_changed_cb(NULL, compose);
8761 g_free(utf8_filename);
8763 g_list_free(file_list);
8766 compose_attach_update_label(compose);
8769 static void compose_insert_file_cb(gpointer data, guint action,
8772 Compose *compose = (Compose *)data;
8775 file_list = filesel_select_multiple_files_open(_("Select file"));
8780 for ( tmp = file_list; tmp; tmp = tmp->next) {
8781 gchar *file = (gchar *) tmp->data;
8782 gchar *filedup = g_strdup(file);
8783 gchar *shortfile = g_path_get_basename(filedup);
8784 ComposeInsertResult res;
8786 res = compose_insert_file(compose, file);
8787 if (res == COMPOSE_INSERT_READ_ERROR) {
8788 alertpanel_error(_("File '%s' could not be read."), shortfile);
8789 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
8790 alertpanel_error(_("File '%s' contained invalid characters\n"
8791 "for the current encoding, insertion may be incorrect."), shortfile);
8797 g_list_free(file_list);
8801 static void compose_insert_sig_cb(gpointer data, guint action,
8804 Compose *compose = (Compose *)data;
8806 compose_insert_sig(compose, FALSE);
8809 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
8813 Compose *compose = (Compose *)data;
8815 gtkut_widget_get_uposition(widget, &x, &y);
8816 prefs_common.compose_x = x;
8817 prefs_common.compose_y = y;
8819 if (compose->sending || compose->updating)
8821 compose_close_cb(compose, 0, NULL);
8825 void compose_close_toolbar(Compose *compose)
8827 compose_close_cb(compose, 0, NULL);
8830 static void compose_close_cb(gpointer data, guint action, GtkWidget *widget)
8832 Compose *compose = (Compose *)data;
8836 if (compose->exteditor_tag != -1) {
8837 if (!compose_ext_editor_kill(compose))
8842 if (compose->modified) {
8843 val = alertpanel(_("Discard message"),
8844 _("This message has been modified. Discard it?"),
8845 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
8848 case G_ALERTDEFAULT:
8849 if (prefs_common.autosave)
8850 compose_remove_draft(compose);
8852 case G_ALERTALTERNATE:
8853 compose_draft_cb(data, COMPOSE_QUIT_EDITING, NULL);
8860 compose_close(compose);
8863 static void compose_set_encoding_cb(gpointer data, guint action,
8866 Compose *compose = (Compose *)data;
8868 if (GTK_CHECK_MENU_ITEM(widget)->active)
8869 compose->out_encoding = (CharSet)action;
8872 static void compose_address_cb(gpointer data, guint action, GtkWidget *widget)
8874 Compose *compose = (Compose *)data;
8876 addressbook_open(compose);
8879 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
8881 Compose *compose = (Compose *)data;
8886 tmpl = g_object_get_data(G_OBJECT(widget), "template");
8887 g_return_if_fail(tmpl != NULL);
8889 msg = g_strdup_printf(_("Do you want to apply the template '%s' ?"),
8891 val = alertpanel(_("Apply template"), msg,
8892 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
8895 if (val == G_ALERTDEFAULT)
8896 compose_template_apply(compose, tmpl, TRUE);
8897 else if (val == G_ALERTALTERNATE)
8898 compose_template_apply(compose, tmpl, FALSE);
8901 static void compose_ext_editor_cb(gpointer data, guint action,
8904 Compose *compose = (Compose *)data;
8906 compose_exec_ext_editor(compose);
8909 static void compose_undo_cb(Compose *compose)
8911 gboolean prev_autowrap = compose->autowrap;
8913 compose->autowrap = FALSE;
8914 undo_undo(compose->undostruct);
8915 compose->autowrap = prev_autowrap;
8918 static void compose_redo_cb(Compose *compose)
8920 gboolean prev_autowrap = compose->autowrap;
8922 compose->autowrap = FALSE;
8923 undo_redo(compose->undostruct);
8924 compose->autowrap = prev_autowrap;
8927 static void entry_cut_clipboard(GtkWidget *entry)
8929 if (GTK_IS_EDITABLE(entry))
8930 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
8931 else if (GTK_IS_TEXT_VIEW(entry))
8932 gtk_text_buffer_cut_clipboard(
8933 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
8934 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
8938 static void entry_copy_clipboard(GtkWidget *entry)
8940 if (GTK_IS_EDITABLE(entry))
8941 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
8942 else if (GTK_IS_TEXT_VIEW(entry))
8943 gtk_text_buffer_copy_clipboard(
8944 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
8945 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
8948 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
8949 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
8951 if (GTK_IS_TEXT_VIEW(entry)) {
8952 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
8953 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
8954 GtkTextIter start_iter, end_iter;
8956 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
8958 if (contents == NULL)
8961 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
8963 /* we shouldn't delete the selection when middle-click-pasting, or we
8964 * can't mid-click-paste our own selection */
8965 if (clip != GDK_SELECTION_PRIMARY) {
8966 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
8969 if (insert_place == NULL) {
8970 /* if insert_place isn't specified, insert at the cursor.
8971 * used for Ctrl-V pasting */
8972 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
8973 start = gtk_text_iter_get_offset(&start_iter);
8974 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
8976 /* if insert_place is specified, paste here.
8977 * used for mid-click-pasting */
8978 start = gtk_text_iter_get_offset(insert_place);
8979 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
8983 /* paste unwrapped: mark the paste so it's not wrapped later */
8984 end = start + strlen(contents);
8985 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
8986 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
8987 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
8988 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
8989 /* rewrap paragraph now (after a mid-click-paste) */
8990 mark_start = gtk_text_buffer_get_insert(buffer);
8991 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
8992 gtk_text_iter_backward_char(&start_iter);
8993 compose_beautify_paragraph(compose, &start_iter, TRUE);
8995 } else if (GTK_IS_EDITABLE(entry))
8996 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
9000 static void entry_allsel(GtkWidget *entry)
9002 if (GTK_IS_EDITABLE(entry))
9003 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
9004 else if (GTK_IS_TEXT_VIEW(entry)) {
9005 GtkTextIter startiter, enditer;
9006 GtkTextBuffer *textbuf;
9008 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
9009 gtk_text_buffer_get_start_iter(textbuf, &startiter);
9010 gtk_text_buffer_get_end_iter(textbuf, &enditer);
9012 gtk_text_buffer_move_mark_by_name(textbuf,
9013 "selection_bound", &startiter);
9014 gtk_text_buffer_move_mark_by_name(textbuf,
9015 "insert", &enditer);
9019 static void compose_cut_cb(Compose *compose)
9021 if (compose->focused_editable
9023 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9026 entry_cut_clipboard(compose->focused_editable);
9029 static void compose_copy_cb(Compose *compose)
9031 if (compose->focused_editable
9033 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9036 entry_copy_clipboard(compose->focused_editable);
9039 static void compose_paste_cb(Compose *compose)
9042 GtkTextBuffer *buffer;
9044 if (compose->focused_editable &&
9045 GTK_WIDGET_HAS_FOCUS(compose->focused_editable))
9046 entry_paste_clipboard(compose, compose->focused_editable,
9047 prefs_common.linewrap_pastes,
9048 GDK_SELECTION_CLIPBOARD, NULL);
9052 static void compose_paste_as_quote_cb(Compose *compose)
9054 gint wrap_quote = prefs_common.linewrap_quote;
9055 if (compose->focused_editable
9057 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9060 /* let text_insert() (called directly or at a later time
9061 * after the gtk_editable_paste_clipboard) know that
9062 * text is to be inserted as a quotation. implemented
9063 * by using a simple refcount... */
9064 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
9065 G_OBJECT(compose->focused_editable),
9066 "paste_as_quotation"));
9067 g_object_set_data(G_OBJECT(compose->focused_editable),
9068 "paste_as_quotation",
9069 GINT_TO_POINTER(paste_as_quotation + 1));
9070 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
9071 entry_paste_clipboard(compose, compose->focused_editable,
9072 prefs_common.linewrap_pastes,
9073 GDK_SELECTION_CLIPBOARD, NULL);
9074 prefs_common.linewrap_quote = wrap_quote;
9078 static void compose_paste_no_wrap_cb(Compose *compose)
9081 GtkTextBuffer *buffer;
9083 if (compose->focused_editable
9085 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9088 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
9089 GDK_SELECTION_CLIPBOARD, NULL);
9093 static void compose_paste_wrap_cb(Compose *compose)
9096 GtkTextBuffer *buffer;
9098 if (compose->focused_editable
9100 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9103 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
9104 GDK_SELECTION_CLIPBOARD, NULL);
9108 static void compose_allsel_cb(Compose *compose)
9110 if (compose->focused_editable
9112 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9115 entry_allsel(compose->focused_editable);
9118 static void textview_move_beginning_of_line (GtkTextView *text)
9120 GtkTextBuffer *buffer;
9124 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9126 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9127 mark = gtk_text_buffer_get_insert(buffer);
9128 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9129 gtk_text_iter_set_line_offset(&ins, 0);
9130 gtk_text_buffer_place_cursor(buffer, &ins);
9133 static void textview_move_forward_character (GtkTextView *text)
9135 GtkTextBuffer *buffer;
9139 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9141 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9142 mark = gtk_text_buffer_get_insert(buffer);
9143 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9144 if (gtk_text_iter_forward_cursor_position(&ins))
9145 gtk_text_buffer_place_cursor(buffer, &ins);
9148 static void textview_move_backward_character (GtkTextView *text)
9150 GtkTextBuffer *buffer;
9154 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9156 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9157 mark = gtk_text_buffer_get_insert(buffer);
9158 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9159 if (gtk_text_iter_backward_cursor_position(&ins))
9160 gtk_text_buffer_place_cursor(buffer, &ins);
9163 static void textview_move_forward_word (GtkTextView *text)
9165 GtkTextBuffer *buffer;
9170 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9172 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9173 mark = gtk_text_buffer_get_insert(buffer);
9174 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9175 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
9176 if (gtk_text_iter_forward_word_ends(&ins, count)) {
9177 gtk_text_iter_backward_word_start(&ins);
9178 gtk_text_buffer_place_cursor(buffer, &ins);
9182 static void textview_move_backward_word (GtkTextView *text)
9184 GtkTextBuffer *buffer;
9189 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9191 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9192 mark = gtk_text_buffer_get_insert(buffer);
9193 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9194 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
9195 if (gtk_text_iter_backward_word_starts(&ins, 1))
9196 gtk_text_buffer_place_cursor(buffer, &ins);
9199 static void textview_move_end_of_line (GtkTextView *text)
9201 GtkTextBuffer *buffer;
9205 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9207 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9208 mark = gtk_text_buffer_get_insert(buffer);
9209 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9210 if (gtk_text_iter_forward_to_line_end(&ins))
9211 gtk_text_buffer_place_cursor(buffer, &ins);
9214 static void textview_move_next_line (GtkTextView *text)
9216 GtkTextBuffer *buffer;
9221 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9223 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9224 mark = gtk_text_buffer_get_insert(buffer);
9225 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9226 offset = gtk_text_iter_get_line_offset(&ins);
9227 if (gtk_text_iter_forward_line(&ins)) {
9228 gtk_text_iter_set_line_offset(&ins, offset);
9229 gtk_text_buffer_place_cursor(buffer, &ins);
9233 static void textview_move_previous_line (GtkTextView *text)
9235 GtkTextBuffer *buffer;
9240 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9242 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9243 mark = gtk_text_buffer_get_insert(buffer);
9244 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9245 offset = gtk_text_iter_get_line_offset(&ins);
9246 if (gtk_text_iter_backward_line(&ins)) {
9247 gtk_text_iter_set_line_offset(&ins, offset);
9248 gtk_text_buffer_place_cursor(buffer, &ins);
9252 static void textview_delete_forward_character (GtkTextView *text)
9254 GtkTextBuffer *buffer;
9256 GtkTextIter ins, end_iter;
9258 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9260 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9261 mark = gtk_text_buffer_get_insert(buffer);
9262 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9264 if (gtk_text_iter_forward_char(&end_iter)) {
9265 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9269 static void textview_delete_backward_character (GtkTextView *text)
9271 GtkTextBuffer *buffer;
9273 GtkTextIter ins, end_iter;
9275 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9277 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9278 mark = gtk_text_buffer_get_insert(buffer);
9279 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9281 if (gtk_text_iter_backward_char(&end_iter)) {
9282 gtk_text_buffer_delete(buffer, &end_iter, &ins);
9286 static void textview_delete_forward_word (GtkTextView *text)
9288 GtkTextBuffer *buffer;
9290 GtkTextIter ins, end_iter;
9292 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9294 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9295 mark = gtk_text_buffer_get_insert(buffer);
9296 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9298 if (gtk_text_iter_forward_word_end(&end_iter)) {
9299 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9303 static void textview_delete_backward_word (GtkTextView *text)
9305 GtkTextBuffer *buffer;
9307 GtkTextIter ins, end_iter;
9309 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9311 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9312 mark = gtk_text_buffer_get_insert(buffer);
9313 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9315 if (gtk_text_iter_backward_word_start(&end_iter)) {
9316 gtk_text_buffer_delete(buffer, &end_iter, &ins);
9320 static void textview_delete_line (GtkTextView *text)
9322 GtkTextBuffer *buffer;
9324 GtkTextIter ins, start_iter, end_iter;
9327 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9329 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9330 mark = gtk_text_buffer_get_insert(buffer);
9331 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9334 gtk_text_iter_set_line_offset(&start_iter, 0);
9337 if (gtk_text_iter_ends_line(&end_iter))
9338 found = gtk_text_iter_forward_char(&end_iter);
9340 found = gtk_text_iter_forward_to_line_end(&end_iter);
9343 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
9346 static void textview_delete_to_line_end (GtkTextView *text)
9348 GtkTextBuffer *buffer;
9350 GtkTextIter ins, end_iter;
9353 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9355 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9356 mark = gtk_text_buffer_get_insert(buffer);
9357 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9359 if (gtk_text_iter_ends_line(&end_iter))
9360 found = gtk_text_iter_forward_char(&end_iter);
9362 found = gtk_text_iter_forward_to_line_end(&end_iter);
9364 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9367 static void compose_advanced_action_cb(Compose *compose,
9368 ComposeCallAdvancedAction action)
9370 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9372 void (*do_action) (GtkTextView *text);
9373 } action_table[] = {
9374 {textview_move_beginning_of_line},
9375 {textview_move_forward_character},
9376 {textview_move_backward_character},
9377 {textview_move_forward_word},
9378 {textview_move_backward_word},
9379 {textview_move_end_of_line},
9380 {textview_move_next_line},
9381 {textview_move_previous_line},
9382 {textview_delete_forward_character},
9383 {textview_delete_backward_character},
9384 {textview_delete_forward_word},
9385 {textview_delete_backward_word},
9386 {textview_delete_line},
9387 {NULL}, /* gtk_stext_delete_line_n */
9388 {textview_delete_to_line_end}
9391 if (!GTK_WIDGET_HAS_FOCUS(text)) return;
9393 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
9394 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
9395 if (action_table[action].do_action)
9396 action_table[action].do_action(text);
9398 g_warning("Not implemented yet.");
9402 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
9406 if (GTK_IS_EDITABLE(widget)) {
9407 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
9408 gtk_editable_set_position(GTK_EDITABLE(widget),
9411 if (widget->parent && widget->parent->parent
9412 && widget->parent->parent->parent) {
9413 if (GTK_IS_SCROLLED_WINDOW(widget->parent->parent->parent)) {
9414 gint y = widget->allocation.y;
9415 gint height = widget->allocation.height;
9416 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
9417 (GTK_SCROLLED_WINDOW(widget->parent->parent->parent));
9419 if (y < (int)shown->value) {
9420 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown), y - 1);
9422 if (y + height > (int)shown->value + (int)shown->page_size) {
9423 if (y - height - 1 < (int)shown->upper - (int)shown->page_size) {
9424 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
9425 y + height - (int)shown->page_size - 1);
9427 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
9428 (int)shown->upper - (int)shown->page_size - 1);
9435 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
9436 compose->focused_editable = widget;
9439 if (GTK_IS_TEXT_VIEW(widget)
9440 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
9441 gtk_widget_ref(compose->notebook);
9442 gtk_widget_ref(compose->edit_vbox);
9443 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
9444 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
9445 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
9446 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
9447 gtk_widget_unref(compose->notebook);
9448 gtk_widget_unref(compose->edit_vbox);
9449 g_signal_handlers_block_by_func(G_OBJECT(widget),
9450 G_CALLBACK(compose_grab_focus_cb),
9452 gtk_widget_grab_focus(widget);
9453 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
9454 G_CALLBACK(compose_grab_focus_cb),
9456 } else if (!GTK_IS_TEXT_VIEW(widget)
9457 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
9458 gtk_widget_ref(compose->notebook);
9459 gtk_widget_ref(compose->edit_vbox);
9460 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
9461 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
9462 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
9463 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
9464 gtk_widget_unref(compose->notebook);
9465 gtk_widget_unref(compose->edit_vbox);
9466 g_signal_handlers_block_by_func(G_OBJECT(widget),
9467 G_CALLBACK(compose_grab_focus_cb),
9469 gtk_widget_grab_focus(widget);
9470 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
9471 G_CALLBACK(compose_grab_focus_cb),
9477 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
9479 compose->modified = TRUE;
9481 compose_set_title(compose);
9485 static void compose_wrap_cb(gpointer data, guint action, GtkWidget *widget)
9487 Compose *compose = (Compose *)data;
9490 compose_wrap_all_full(compose, TRUE);
9492 compose_beautify_paragraph(compose, NULL, TRUE);
9495 static void compose_find_cb(gpointer data, guint action, GtkWidget *widget)
9497 Compose *compose = (Compose *)data;
9499 message_search_compose(compose);
9502 static void compose_toggle_autowrap_cb(gpointer data, guint action,
9505 Compose *compose = (Compose *)data;
9506 compose->autowrap = GTK_CHECK_MENU_ITEM(widget)->active;
9507 if (compose->autowrap)
9508 compose_wrap_all_full(compose, TRUE);
9509 compose->autowrap = GTK_CHECK_MENU_ITEM(widget)->active;
9512 static void compose_toggle_sign_cb(gpointer data, guint action,
9515 Compose *compose = (Compose *)data;
9517 if (GTK_CHECK_MENU_ITEM(widget)->active)
9518 compose->use_signing = TRUE;
9520 compose->use_signing = FALSE;
9523 static void compose_toggle_encrypt_cb(gpointer data, guint action,
9526 Compose *compose = (Compose *)data;
9528 if (GTK_CHECK_MENU_ITEM(widget)->active)
9529 compose->use_encryption = TRUE;
9531 compose->use_encryption = FALSE;
9534 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
9536 g_free(compose->privacy_system);
9538 compose->privacy_system = g_strdup(account->default_privacy_system);
9539 compose_update_privacy_system_menu_item(compose, warn);
9542 static void compose_toggle_ruler_cb(gpointer data, guint action,
9545 Compose *compose = (Compose *)data;
9547 if (GTK_CHECK_MENU_ITEM(widget)->active) {
9548 gtk_widget_show(compose->ruler_hbox);
9549 prefs_common.show_ruler = TRUE;
9551 gtk_widget_hide(compose->ruler_hbox);
9552 gtk_widget_queue_resize(compose->edit_vbox);
9553 prefs_common.show_ruler = FALSE;
9557 static void compose_attach_drag_received_cb (GtkWidget *widget,
9558 GdkDragContext *context,
9561 GtkSelectionData *data,
9566 Compose *compose = (Compose *)user_data;
9569 if (gdk_atom_name(data->type) &&
9570 !strcmp(gdk_atom_name(data->type), "text/uri-list")
9571 && gtk_drag_get_source_widget(context) !=
9572 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
9573 list = uri_list_extract_filenames((const gchar *)data->data);
9574 for (tmp = list; tmp != NULL; tmp = tmp->next) {
9575 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
9576 compose_attach_append
9577 (compose, (const gchar *)tmp->data,
9578 utf8_filename, NULL);
9579 g_free(utf8_filename);
9581 if (list) compose_changed_cb(NULL, compose);
9582 list_free_strings(list);
9584 } else if (gtk_drag_get_source_widget(context)
9585 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
9586 /* comes from our summaryview */
9587 SummaryView * summaryview = NULL;
9588 GSList * list = NULL, *cur = NULL;
9590 if (mainwindow_get_mainwindow())
9591 summaryview = mainwindow_get_mainwindow()->summaryview;
9594 list = summary_get_selected_msg_list(summaryview);
9596 for (cur = list; cur; cur = cur->next) {
9597 MsgInfo *msginfo = (MsgInfo *)cur->data;
9600 file = procmsg_get_message_file_full(msginfo,
9603 compose_attach_append(compose, (const gchar *)file,
9604 (const gchar *)file, "message/rfc822");
9612 static gboolean compose_drag_drop(GtkWidget *widget,
9613 GdkDragContext *drag_context,
9615 guint time, gpointer user_data)
9617 /* not handling this signal makes compose_insert_drag_received_cb
9622 static void compose_insert_drag_received_cb (GtkWidget *widget,
9623 GdkDragContext *drag_context,
9626 GtkSelectionData *data,
9631 Compose *compose = (Compose *)user_data;
9634 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
9636 if (gdk_atom_name(data->type) && !strcmp(gdk_atom_name(data->type), "text/uri-list")) {
9637 AlertValue val = G_ALERTDEFAULT;
9639 switch (prefs_common.compose_dnd_mode) {
9640 case COMPOSE_DND_ASK:
9641 val = alertpanel_full(_("Insert or attach?"),
9642 _("Do you want to insert the contents of the file(s) "
9643 "into the message body, or attach it to the email?"),
9644 GTK_STOCK_CANCEL, _("+_Insert"), _("_Attach"),
9645 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
9647 case COMPOSE_DND_INSERT:
9648 val = G_ALERTALTERNATE;
9650 case COMPOSE_DND_ATTACH:
9654 /* unexpected case */
9655 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
9658 if (val & G_ALERTDISABLE) {
9659 val &= ~G_ALERTDISABLE;
9660 /* remember what action to perform by default, only if we don't click Cancel */
9661 if (val == G_ALERTALTERNATE)
9662 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
9663 else if (val == G_ALERTOTHER)
9664 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
9667 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
9668 gtk_drag_finish(drag_context, FALSE, FALSE, time);
9670 } else if (val == G_ALERTOTHER) {
9671 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
9674 list = uri_list_extract_filenames((const gchar *)data->data);
9675 for (tmp = list; tmp != NULL; tmp = tmp->next) {
9676 compose_insert_file(compose, (const gchar *)tmp->data);
9678 list_free_strings(list);
9680 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9683 #if GTK_CHECK_VERSION(2, 8, 0)
9684 /* do nothing, handled by GTK */
9686 gchar *tmpfile = get_tmp_file();
9687 str_write_to_file((const gchar *)data->data, tmpfile);
9688 compose_insert_file(compose, tmpfile);
9691 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9695 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9698 static void compose_header_drag_received_cb (GtkWidget *widget,
9699 GdkDragContext *drag_context,
9702 GtkSelectionData *data,
9707 GtkEditable *entry = (GtkEditable *)user_data;
9708 gchar *email = (gchar *)data->data;
9710 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
9713 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
9714 gchar *decoded=g_new(gchar, strlen(email));
9717 email += strlen("mailto:");
9718 decode_uri(decoded, email); /* will fit */
9719 gtk_editable_delete_text(entry, 0, -1);
9720 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
9721 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9725 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9728 static void compose_toggle_return_receipt_cb(gpointer data, guint action,
9731 Compose *compose = (Compose *)data;
9733 if (GTK_CHECK_MENU_ITEM(widget)->active)
9734 compose->return_receipt = TRUE;
9736 compose->return_receipt = FALSE;
9739 static void compose_toggle_remove_refs_cb(gpointer data, guint action,
9742 Compose *compose = (Compose *)data;
9744 if (GTK_CHECK_MENU_ITEM(widget)->active)
9745 compose->remove_references = TRUE;
9747 compose->remove_references = FALSE;
9750 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
9752 ComposeHeaderEntry *headerentry)
9754 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
9755 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
9756 !(event->state & GDK_MODIFIER_MASK) &&
9757 (event->keyval == GDK_BackSpace) &&
9758 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
9759 gtk_container_remove
9760 (GTK_CONTAINER(headerentry->compose->header_table),
9761 headerentry->combo);
9762 gtk_container_remove
9763 (GTK_CONTAINER(headerentry->compose->header_table),
9764 headerentry->entry);
9765 headerentry->compose->header_list =
9766 g_slist_remove(headerentry->compose->header_list,
9768 g_free(headerentry);
9769 } else if (event->keyval == GDK_Tab) {
9770 if (headerentry->compose->header_last == headerentry) {
9771 /* Override default next focus, and give it to subject_entry
9772 * instead of notebook tabs
9774 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
9775 gtk_widget_grab_focus(headerentry->compose->subject_entry);
9782 static gboolean compose_headerentry_changed_cb(GtkWidget *entry,
9783 ComposeHeaderEntry *headerentry)
9785 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
9786 compose_create_header_entry(headerentry->compose);
9787 g_signal_handlers_disconnect_matched
9788 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
9789 0, 0, NULL, NULL, headerentry);
9791 /* Automatically scroll down */
9792 compose_show_first_last_header(headerentry->compose, FALSE);
9798 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
9800 GtkAdjustment *vadj;
9802 g_return_if_fail(compose);
9803 g_return_if_fail(GTK_IS_WIDGET(compose->header_table));
9804 g_return_if_fail(GTK_IS_VIEWPORT(compose->header_table->parent));
9806 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(compose->header_table->parent));
9807 gtk_adjustment_set_value(vadj, (show_first ? vadj->lower : vadj->upper));
9808 gtk_adjustment_changed(vadj);
9811 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
9812 const gchar *text, gint len, Compose *compose)
9814 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
9815 (G_OBJECT(compose->text), "paste_as_quotation"));
9818 g_return_if_fail(text != NULL);
9820 g_signal_handlers_block_by_func(G_OBJECT(buffer),
9821 G_CALLBACK(text_inserted),
9823 if (paste_as_quotation) {
9827 GtkTextIter start_iter;
9832 new_text = g_strndup(text, len);
9834 qmark = compose_quote_char_from_context(compose);
9836 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
9837 gtk_text_buffer_place_cursor(buffer, iter);
9839 pos = gtk_text_iter_get_offset(iter);
9841 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
9842 _("Quote format error at line %d."));
9843 quote_fmt_reset_vartable();
9845 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
9846 GINT_TO_POINTER(paste_as_quotation - 1));
9848 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
9849 gtk_text_buffer_place_cursor(buffer, iter);
9850 gtk_text_buffer_delete_mark(buffer, mark);
9852 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
9853 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
9854 compose_beautify_paragraph(compose, &start_iter, FALSE);
9855 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
9856 gtk_text_buffer_delete_mark(buffer, mark);
9858 if (strcmp(text, "\n") || compose->automatic_break
9859 || gtk_text_iter_starts_line(iter))
9860 gtk_text_buffer_insert(buffer, iter, text, len);
9862 debug_print("insert nowrap \\n\n");
9863 gtk_text_buffer_insert_with_tags_by_name(buffer,
9864 iter, text, len, "no_join", NULL);
9868 if (!paste_as_quotation) {
9869 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
9870 compose_beautify_paragraph(compose, iter, FALSE);
9871 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
9872 gtk_text_buffer_delete_mark(buffer, mark);
9875 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
9876 G_CALLBACK(text_inserted),
9878 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
9880 if (prefs_common.autosave &&
9881 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
9882 compose->draft_timeout_tag != -2 /* disabled while loading */)
9883 compose->draft_timeout_tag = g_timeout_add
9884 (500, (GtkFunction) compose_defer_auto_save_draft, compose);
9886 static gint compose_defer_auto_save_draft(Compose *compose)
9888 compose->draft_timeout_tag = -1;
9889 compose_draft_cb((gpointer)compose, COMPOSE_AUTO_SAVE, NULL);
9894 static void compose_check_all(Compose *compose)
9896 if (compose->gtkaspell)
9897 gtkaspell_check_all(compose->gtkaspell);
9900 static void compose_highlight_all(Compose *compose)
9902 if (compose->gtkaspell)
9903 gtkaspell_highlight_all(compose->gtkaspell);
9906 static void compose_check_backwards(Compose *compose)
9908 if (compose->gtkaspell)
9909 gtkaspell_check_backwards(compose->gtkaspell);
9911 GtkItemFactory *ifactory;
9912 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
9913 menu_set_sensitive(ifactory, "/Edit/Check backwards misspelled word", FALSE);
9914 menu_set_sensitive(ifactory, "/Edit/Forward to next misspelled word", FALSE);
9918 static void compose_check_forwards_go(Compose *compose)
9920 if (compose->gtkaspell)
9921 gtkaspell_check_forwards_go(compose->gtkaspell);
9923 GtkItemFactory *ifactory;
9924 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
9925 menu_set_sensitive(ifactory, "/Edit/Check backwards misspelled word", FALSE);
9926 menu_set_sensitive(ifactory, "/Edit/Forward to next misspelled word", FALSE);
9932 *\brief Guess originating forward account from MsgInfo and several
9933 * "common preference" settings. Return NULL if no guess.
9935 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
9937 PrefsAccount *account = NULL;
9939 g_return_val_if_fail(msginfo, NULL);
9940 g_return_val_if_fail(msginfo->folder, NULL);
9941 g_return_val_if_fail(msginfo->folder->prefs, NULL);
9943 if (msginfo->folder->prefs->enable_default_account)
9944 account = account_find_from_id(msginfo->folder->prefs->default_account);
9947 account = msginfo->folder->folder->account;
9949 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
9951 Xstrdup_a(to, msginfo->to, return NULL);
9952 extract_address(to);
9953 account = account_find_from_address(to);
9956 if (!account && prefs_common.forward_account_autosel) {
9958 if (!procheader_get_header_from_msginfo
9959 (msginfo, cc,sizeof cc , "Cc:")) {
9960 gchar *buf = cc + strlen("Cc:");
9961 extract_address(buf);
9962 account = account_find_from_address(buf);
9966 if (!account && prefs_common.forward_account_autosel) {
9967 gchar deliveredto[BUFFSIZE];
9968 if (!procheader_get_header_from_msginfo
9969 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
9970 gchar *buf = deliveredto + strlen("Delivered-To:");
9971 extract_address(buf);
9972 account = account_find_from_address(buf);
9979 gboolean compose_close(Compose *compose)
9983 if (!g_mutex_trylock(compose->mutex)) {
9984 /* we have to wait for the (possibly deferred by auto-save)
9985 * drafting to be done, before destroying the compose under
9987 debug_print("waiting for drafting to finish...\n");
9988 compose_allow_user_actions(compose, FALSE);
9989 g_timeout_add (500, (GSourceFunc) compose_close, compose);
9992 g_return_val_if_fail(compose, FALSE);
9993 gtkut_widget_get_uposition(compose->window, &x, &y);
9994 prefs_common.compose_x = x;
9995 prefs_common.compose_y = y;
9996 g_mutex_unlock(compose->mutex);
9997 compose_destroy(compose);
10002 * Add entry field for each address in list.
10003 * \param compose E-Mail composition object.
10004 * \param listAddress List of (formatted) E-Mail addresses.
10006 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
10009 node = listAddress;
10011 addr = ( gchar * ) node->data;
10012 compose_entry_append( compose, addr, COMPOSE_TO );
10013 node = g_list_next( node );
10017 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
10018 guint action, gboolean opening_multiple)
10020 gchar *body = NULL;
10021 GSList *new_msglist = NULL;
10022 MsgInfo *tmp_msginfo = NULL;
10023 gboolean originally_enc = FALSE;
10024 Compose *compose = NULL;
10026 g_return_if_fail(msgview != NULL);
10028 g_return_if_fail(msginfo_list != NULL);
10030 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
10031 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
10032 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
10034 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
10035 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
10036 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
10037 orig_msginfo, mimeinfo);
10038 if (tmp_msginfo != NULL) {
10039 new_msglist = g_slist_append(NULL, tmp_msginfo);
10041 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
10042 tmp_msginfo->folder = orig_msginfo->folder;
10043 tmp_msginfo->msgnum = orig_msginfo->msgnum;
10044 if (orig_msginfo->tags)
10045 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
10050 if (!opening_multiple)
10051 body = messageview_get_selection(msgview);
10054 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
10055 procmsg_msginfo_free(tmp_msginfo);
10056 g_slist_free(new_msglist);
10058 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
10060 if (compose && originally_enc) {
10061 compose_force_encryption(compose, compose->account, FALSE);
10067 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
10070 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
10071 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
10072 GSList *cur = msginfo_list;
10073 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
10074 "messages. Opening the windows "
10075 "could take some time. Do you "
10076 "want to continue?"),
10077 g_slist_length(msginfo_list));
10078 if (g_slist_length(msginfo_list) > 9
10079 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
10080 != G_ALERTALTERNATE) {
10085 /* We'll open multiple compose windows */
10086 /* let the WM place the next windows */
10087 compose_force_window_origin = FALSE;
10088 for (; cur; cur = cur->next) {
10090 tmplist.data = cur->data;
10091 tmplist.next = NULL;
10092 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
10094 compose_force_window_origin = TRUE;
10096 /* forwarding multiple mails as attachments is done via a
10097 * single compose window */
10098 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
10102 void compose_set_position(Compose *compose, gint pos)
10104 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10106 gtkut_text_view_set_position(text, pos);
10109 gboolean compose_search_string(Compose *compose,
10110 const gchar *str, gboolean case_sens)
10112 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10114 return gtkut_text_view_search_string(text, str, case_sens);
10117 gboolean compose_search_string_backward(Compose *compose,
10118 const gchar *str, gboolean case_sens)
10120 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10122 return gtkut_text_view_search_string_backward(text, str, case_sens);
10125 /* allocate a msginfo structure and populate its data from a compose data structure */
10126 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
10128 MsgInfo *newmsginfo;
10130 gchar buf[BUFFSIZE];
10132 g_return_val_if_fail( compose != NULL, NULL );
10134 newmsginfo = procmsg_msginfo_new();
10137 get_rfc822_date(buf, sizeof(buf));
10138 newmsginfo->date = g_strdup(buf);
10141 if (compose->from_name) {
10142 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
10143 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
10147 if (compose->subject_entry)
10148 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
10150 /* to, cc, reply-to, newsgroups */
10151 for (list = compose->header_list; list; list = list->next) {
10152 gchar *header = gtk_editable_get_chars(
10154 GTK_BIN(((ComposeHeaderEntry *)list->data)->combo)->child), 0, -1);
10155 gchar *entry = gtk_editable_get_chars(
10156 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
10158 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
10159 if ( newmsginfo->to == NULL ) {
10160 newmsginfo->to = g_strdup(entry);
10161 } else if (entry && *entry) {
10162 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
10163 g_free(newmsginfo->to);
10164 newmsginfo->to = tmp;
10167 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
10168 if ( newmsginfo->cc == NULL ) {
10169 newmsginfo->cc = g_strdup(entry);
10170 } else if (entry && *entry) {
10171 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
10172 g_free(newmsginfo->cc);
10173 newmsginfo->cc = tmp;
10176 if ( strcasecmp(header,
10177 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
10178 if ( newmsginfo->newsgroups == NULL ) {
10179 newmsginfo->newsgroups = g_strdup(entry);
10180 } else if (entry && *entry) {
10181 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
10182 g_free(newmsginfo->newsgroups);
10183 newmsginfo->newsgroups = tmp;
10191 /* other data is unset */
10197 /* update compose's dictionaries from folder dict settings */
10198 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
10199 FolderItem *folder_item)
10201 g_return_if_fail(compose != NULL);
10203 if (compose->gtkaspell && folder_item && folder_item->prefs) {
10204 FolderItemPrefs *prefs = folder_item->prefs;
10206 if (prefs->enable_default_dictionary)
10207 gtkaspell_change_dict(compose->gtkaspell,
10208 prefs->default_dictionary, FALSE);
10209 if (folder_item->prefs->enable_default_alt_dictionary)
10210 gtkaspell_change_alt_dict(compose->gtkaspell,
10211 prefs->default_alt_dictionary);
10212 if (prefs->enable_default_dictionary
10213 || prefs->enable_default_alt_dictionary)
10214 compose_spell_menu_changed(compose);