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_COMBO(((ComposeHeaderEntry *)list->data)->combo)->entry), 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_COMBO(((ComposeHeaderEntry *)list->data)->combo)->entry), 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_COMBO(((ComposeHeaderEntry *)list->data)->combo)->entry), 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_COMBO(headerentry->combo)->entry));
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_COMBO(headerentry->combo)->entry));
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", get_tmp_dir(),
5304 G_DIR_SEPARATOR, compose);
5305 if ((fp = g_fopen(tmp, "wb")) == NULL) {
5306 FILE_OP_ERROR(tmp, "fopen");
5312 if (change_file_mode_rw(fp, tmp) < 0) {
5313 FILE_OP_ERROR(tmp, "chmod");
5314 g_warning("can't change file mode\n");
5317 /* queueing variables */
5318 err |= (fprintf(fp, "AF:\n") < 0);
5319 err |= (fprintf(fp, "NF:0\n") < 0);
5320 err |= (fprintf(fp, "PS:10\n") < 0);
5321 err |= (fprintf(fp, "SRH:1\n") < 0);
5322 err |= (fprintf(fp, "SFN:\n") < 0);
5323 err |= (fprintf(fp, "DSR:\n") < 0);
5325 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
5327 err |= (fprintf(fp, "MID:\n") < 0);
5328 err |= (fprintf(fp, "CFG:\n") < 0);
5329 err |= (fprintf(fp, "PT:0\n") < 0);
5330 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
5331 err |= (fprintf(fp, "RQ:\n") < 0);
5333 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
5335 err |= (fprintf(fp, "SSV:\n") < 0);
5337 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
5339 err |= (fprintf(fp, "NSV:\n") < 0);
5340 err |= (fprintf(fp, "SSH:\n") < 0);
5341 /* write recepient list */
5342 if (compose->to_list) {
5343 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
5344 for (cur = compose->to_list->next; cur != NULL;
5346 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
5347 err |= (fprintf(fp, "\n") < 0);
5349 /* write newsgroup list */
5350 if (compose->newsgroup_list) {
5351 err |= (fprintf(fp, "NG:") < 0);
5352 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
5353 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
5354 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
5355 err |= (fprintf(fp, "\n") < 0);
5357 /* Sylpheed account IDs */
5359 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
5361 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
5364 if (compose->privacy_system != NULL) {
5365 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
5366 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
5367 if (compose->use_encryption) {
5369 if (!compose_warn_encryption(compose)) {
5376 if (mailac && mailac->encrypt_to_self) {
5377 GSList *tmp_list = g_slist_copy(compose->to_list);
5378 tmp_list = g_slist_append(tmp_list, compose->account->address);
5379 encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
5380 g_slist_free(tmp_list);
5382 encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
5384 if (encdata != NULL) {
5385 if (strcmp(encdata, "_DONT_ENCRYPT_")) {
5386 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5387 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
5389 } /* else we finally dont want to encrypt */
5391 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5392 /* and if encdata was null, it means there's been a problem in
5404 /* Save copy folder */
5405 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
5406 gchar *savefolderid;
5408 savefolderid = gtk_editable_get_chars(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
5409 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
5410 g_free(savefolderid);
5412 /* Save copy folder */
5413 if (compose->return_receipt) {
5414 err |= (fprintf(fp, "RRCPT:1\n") < 0);
5416 /* Message-ID of message replying to */
5417 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
5420 folderid = folder_item_get_identifier(compose->replyinfo->folder);
5421 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
5424 /* Message-ID of message forwarding to */
5425 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
5428 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
5429 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
5433 /* end of headers */
5434 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
5436 if (compose->redirect_filename != NULL) {
5437 if (compose_redirect_write_to_file(compose, fp) < 0) {
5446 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
5451 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
5455 g_warning("failed to write queue message\n");
5462 if (fclose(fp) == EOF) {
5463 FILE_OP_ERROR(tmp, "fclose");
5470 if (item && *item) {
5473 queue = account_get_special_folder(compose->account, F_QUEUE);
5476 g_warning("can't find queue folder\n");
5482 folder_item_scan(queue);
5483 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
5484 g_warning("can't queue the message\n");
5491 if (msgpath == NULL) {
5497 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
5498 compose_remove_reedit_target(compose, FALSE);
5501 if ((msgnum != NULL) && (item != NULL)) {
5509 static void compose_add_attachments(Compose *compose, MimeInfo *parent)
5512 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
5514 struct stat statbuf;
5515 gchar *type, *subtype;
5516 GtkTreeModel *model;
5519 model = gtk_tree_view_get_model(tree_view);
5521 if (!gtk_tree_model_get_iter_first(model, &iter))
5524 gtk_tree_model_get(model, &iter,
5528 mimepart = procmime_mimeinfo_new();
5529 mimepart->content = MIMECONTENT_FILE;
5530 mimepart->data.filename = g_strdup(ainfo->file);
5531 mimepart->tmp = FALSE; /* or we destroy our attachment */
5532 mimepart->offset = 0;
5534 stat(ainfo->file, &statbuf);
5535 mimepart->length = statbuf.st_size;
5537 type = g_strdup(ainfo->content_type);
5539 if (!strchr(type, '/')) {
5541 type = g_strdup("application/octet-stream");
5544 subtype = strchr(type, '/') + 1;
5545 *(subtype - 1) = '\0';
5546 mimepart->type = procmime_get_media_type(type);
5547 mimepart->subtype = g_strdup(subtype);
5550 if (mimepart->type == MIMETYPE_MESSAGE &&
5551 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
5552 mimepart->disposition = DISPOSITIONTYPE_INLINE;
5555 g_hash_table_insert(mimepart->typeparameters,
5556 g_strdup("name"), g_strdup(ainfo->name));
5557 g_hash_table_insert(mimepart->dispositionparameters,
5558 g_strdup("filename"), g_strdup(ainfo->name));
5559 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
5563 if (compose->use_signing) {
5564 if (ainfo->encoding == ENC_7BIT)
5565 ainfo->encoding = ENC_QUOTED_PRINTABLE;
5566 else if (ainfo->encoding == ENC_8BIT)
5567 ainfo->encoding = ENC_BASE64;
5570 procmime_encode_content(mimepart, ainfo->encoding);
5572 g_node_append(parent->node, mimepart->node);
5573 } while (gtk_tree_model_iter_next(model, &iter));
5576 #define IS_IN_CUSTOM_HEADER(header) \
5577 (compose->account->add_customhdr && \
5578 custom_header_find(compose->account->customhdr_list, header) != NULL)
5580 static void compose_add_headerfield_from_headerlist(Compose *compose,
5582 const gchar *fieldname,
5583 const gchar *seperator)
5585 gchar *str, *fieldname_w_colon;
5586 gboolean add_field = FALSE;
5588 ComposeHeaderEntry *headerentry;
5589 const gchar *headerentryname;
5590 const gchar *trans_fieldname;
5593 if (IS_IN_CUSTOM_HEADER(fieldname))
5596 debug_print("Adding %s-fields\n", fieldname);
5598 fieldstr = g_string_sized_new(64);
5600 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
5601 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
5603 for (list = compose->header_list; list; list = list->next) {
5604 headerentry = ((ComposeHeaderEntry *)list->data);
5605 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(headerentry->combo)->entry));
5607 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
5608 str = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
5610 if (str[0] != '\0') {
5612 g_string_append(fieldstr, seperator);
5613 g_string_append(fieldstr, str);
5622 buf = g_new0(gchar, fieldstr->len * 4 + 256);
5623 compose_convert_header
5624 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
5625 strlen(fieldname) + 2, TRUE);
5626 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
5630 g_free(fieldname_w_colon);
5631 g_string_free(fieldstr, TRUE);
5636 static gchar *compose_get_header(Compose *compose)
5638 gchar buf[BUFFSIZE];
5639 const gchar *entry_str;
5643 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
5645 gchar *from_name = NULL, *from_address = NULL;
5648 g_return_val_if_fail(compose->account != NULL, NULL);
5649 g_return_val_if_fail(compose->account->address != NULL, NULL);
5651 header = g_string_sized_new(64);
5654 get_rfc822_date(buf, sizeof(buf));
5655 g_string_append_printf(header, "Date: %s\n", buf);
5659 if (compose->account->name && *compose->account->name) {
5661 QUOTE_IF_REQUIRED(buf, compose->account->name);
5662 tmp = g_strdup_printf("%s <%s>",
5663 buf, compose->account->address);
5665 tmp = g_strdup_printf("%s",
5666 compose->account->address);
5668 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
5669 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
5671 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
5672 from_address = g_strdup(compose->account->address);
5674 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5675 /* extract name and address */
5676 if (strstr(spec, " <") && strstr(spec, ">")) {
5677 from_address = g_strdup(strrchr(spec, '<')+1);
5678 *(strrchr(from_address, '>')) = '\0';
5679 from_name = g_strdup(spec);
5680 *(strrchr(from_name, '<')) = '\0';
5683 from_address = g_strdup(spec);
5690 if (from_name && *from_name) {
5691 compose_convert_header
5692 (compose, buf, sizeof(buf), from_name,
5693 strlen("From: "), TRUE);
5694 QUOTE_IF_REQUIRED(name, buf);
5696 g_string_append_printf(header, "From: %s <%s>\n",
5697 name, from_address);
5699 g_string_append_printf(header, "From: %s\n", from_address);
5702 g_free(from_address);
5705 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
5708 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
5711 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
5714 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
5717 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
5719 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
5722 compose_convert_header(compose, buf, sizeof(buf), str,
5723 strlen("Subject: "), FALSE);
5724 g_string_append_printf(header, "Subject: %s\n", buf);
5730 if (compose->account->set_domain && compose->account->domain) {
5731 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
5732 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
5733 g_snprintf(buf, sizeof(buf), "%s",
5734 strchr(compose->account->address, '@') ?
5735 strchr(compose->account->address, '@')+1 :
5736 compose->account->address);
5738 g_snprintf(buf, sizeof(buf), "%s", "");
5740 generate_msgid(buf, sizeof(buf));
5741 g_string_append_printf(header, "Message-ID: <%s>\n", buf);
5742 compose->msgid = g_strdup(buf);
5744 if (compose->remove_references == FALSE) {
5746 if (compose->inreplyto && compose->to_list)
5747 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
5750 if (compose->references)
5751 g_string_append_printf(header, "References: %s\n", compose->references);
5755 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
5758 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
5761 if (compose->account->organization &&
5762 strlen(compose->account->organization) &&
5763 !IS_IN_CUSTOM_HEADER("Organization")) {
5764 compose_convert_header(compose, buf, sizeof(buf),
5765 compose->account->organization,
5766 strlen("Organization: "), FALSE);
5767 g_string_append_printf(header, "Organization: %s\n", buf);
5770 /* Program version and system info */
5771 if (g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
5772 !compose->newsgroup_list) {
5773 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
5775 gtk_major_version, gtk_minor_version, gtk_micro_version,
5778 if (g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
5779 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
5781 gtk_major_version, gtk_minor_version, gtk_micro_version,
5785 /* custom headers */
5786 if (compose->account->add_customhdr) {
5789 for (cur = compose->account->customhdr_list; cur != NULL;
5791 CustomHeader *chdr = (CustomHeader *)cur->data;
5793 if (custom_header_is_allowed(chdr->name)) {
5794 compose_convert_header
5795 (compose, buf, sizeof(buf),
5796 chdr->value ? chdr->value : "",
5797 strlen(chdr->name) + 2, FALSE);
5798 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
5804 switch (compose->priority) {
5805 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
5806 "X-Priority: 1 (Highest)\n");
5808 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
5809 "X-Priority: 2 (High)\n");
5811 case PRIORITY_NORMAL: break;
5812 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
5813 "X-Priority: 4 (Low)\n");
5815 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
5816 "X-Priority: 5 (Lowest)\n");
5818 default: debug_print("compose: priority unknown : %d\n",
5822 /* Request Return Receipt */
5823 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To")) {
5824 if (compose->return_receipt) {
5825 if (compose->account->name
5826 && *compose->account->name) {
5827 compose_convert_header(compose, buf, sizeof(buf),
5828 compose->account->name,
5829 strlen("Disposition-Notification-To: "),
5831 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, compose->account->address);
5833 g_string_append_printf(header, "Disposition-Notification-To: %s\n", compose->account->address);
5837 /* get special headers */
5838 for (list = compose->header_list; list; list = list->next) {
5839 ComposeHeaderEntry *headerentry;
5842 gchar *headername_wcolon;
5843 const gchar *headername_trans;
5846 gboolean standard_header = FALSE;
5848 headerentry = ((ComposeHeaderEntry *)list->data);
5850 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(headerentry->combo)->entry)));
5851 if (strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
5856 if (!strstr(tmp, ":")) {
5857 headername_wcolon = g_strconcat(tmp, ":", NULL);
5858 headername = g_strdup(tmp);
5860 headername_wcolon = g_strdup(tmp);
5861 headername = g_strdup(strtok(tmp, ":"));
5865 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5866 Xstrdup_a(headervalue, entry_str, return NULL);
5867 subst_char(headervalue, '\r', ' ');
5868 subst_char(headervalue, '\n', ' ');
5869 string = std_headers;
5870 while (*string != NULL) {
5871 headername_trans = prefs_common_translated_header_name(*string);
5872 if (!strcmp(headername_trans, headername_wcolon))
5873 standard_header = TRUE;
5876 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
5877 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
5880 g_free(headername_wcolon);
5884 g_string_free(header, FALSE);
5889 #undef IS_IN_CUSTOM_HEADER
5891 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
5892 gint header_len, gboolean addr_field)
5894 gchar *tmpstr = NULL;
5895 const gchar *out_codeset = NULL;
5897 g_return_if_fail(src != NULL);
5898 g_return_if_fail(dest != NULL);
5900 if (len < 1) return;
5902 tmpstr = g_strdup(src);
5904 subst_char(tmpstr, '\n', ' ');
5905 subst_char(tmpstr, '\r', ' ');
5908 if (!g_utf8_validate(tmpstr, -1, NULL)) {
5909 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
5910 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
5915 codeconv_set_strict(TRUE);
5916 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
5917 conv_get_charset_str(compose->out_encoding));
5918 codeconv_set_strict(FALSE);
5920 if (!dest || *dest == '\0') {
5921 gchar *test_conv_global_out = NULL;
5922 gchar *test_conv_reply = NULL;
5924 /* automatic mode. be automatic. */
5925 codeconv_set_strict(TRUE);
5927 out_codeset = conv_get_outgoing_charset_str();
5929 debug_print("trying to convert to %s\n", out_codeset);
5930 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
5933 if (!test_conv_global_out && compose->orig_charset
5934 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5935 out_codeset = compose->orig_charset;
5936 debug_print("failure; trying to convert to %s\n", out_codeset);
5937 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
5940 if (!test_conv_global_out && !test_conv_reply) {
5942 out_codeset = CS_INTERNAL;
5943 debug_print("finally using %s\n", out_codeset);
5945 g_free(test_conv_global_out);
5946 g_free(test_conv_reply);
5947 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
5949 codeconv_set_strict(FALSE);
5954 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
5958 g_return_if_fail(user_data != NULL);
5960 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
5961 g_strstrip(address);
5962 if (*address != '\0') {
5963 gchar *name = procheader_get_fromname(address);
5964 extract_address(address);
5965 addressbook_add_contact(name, address, NULL);
5970 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
5972 GtkWidget *menuitem;
5975 g_return_if_fail(menu != NULL);
5976 g_return_if_fail(GTK_IS_MENU_SHELL(menu));
5978 menuitem = gtk_separator_menu_item_new();
5979 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
5980 gtk_widget_show(menuitem);
5982 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
5983 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
5985 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
5986 g_strstrip(address);
5987 if (*address == '\0') {
5988 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
5991 g_signal_connect(G_OBJECT(menuitem), "activate",
5992 G_CALLBACK(compose_add_to_addressbook_cb), entry);
5993 gtk_widget_show(menuitem);
5996 static void compose_create_header_entry(Compose *compose)
5998 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6002 GList *combo_list = NULL;
6004 const gchar *header = NULL;
6005 ComposeHeaderEntry *headerentry;
6006 gboolean standard_header = FALSE;
6008 headerentry = g_new0(ComposeHeaderEntry, 1);
6011 combo = gtk_combo_new();
6013 while(*string != NULL) {
6014 combo_list = g_list_append(combo_list, (gchar*)prefs_common_translated_header_name(*string));
6017 gtk_combo_set_popdown_strings(GTK_COMBO(combo), combo_list);
6018 g_list_free(combo_list);
6019 gtk_editable_set_editable(GTK_EDITABLE(GTK_COMBO(combo)->entry), TRUE);
6020 g_signal_connect(G_OBJECT(GTK_COMBO(combo)->entry), "grab_focus",
6021 G_CALLBACK(compose_grab_focus_cb), compose);
6022 gtk_widget_show(combo);
6023 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
6024 compose->header_nextrow, compose->header_nextrow+1,
6025 GTK_SHRINK, GTK_FILL, 0, 0);
6026 if (compose->header_last) {
6027 const gchar *last_header_entry = gtk_entry_get_text(
6028 GTK_ENTRY(GTK_COMBO(compose->header_last->combo)->entry));
6030 while (*string != NULL) {
6031 if (!strcmp(*string, last_header_entry))
6032 standard_header = TRUE;
6035 if (standard_header)
6036 header = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(compose->header_last->combo)->entry));
6038 if (!compose->header_last || !standard_header) {
6039 switch(compose->account->protocol) {
6041 header = prefs_common_translated_header_name("Newsgroups:");
6044 header = prefs_common_translated_header_name("To:");
6049 gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(combo)->entry), header);
6051 g_signal_connect_after(G_OBJECT(GTK_COMBO(combo)->entry), "grab_focus",
6052 G_CALLBACK(compose_grab_focus_cb), compose);
6055 entry = gtk_entry_new();
6056 gtk_widget_show(entry);
6057 gtk_tooltips_set_tip(compose->tooltips, entry,
6058 _("Use <tab> to autocomplete from addressbook"), NULL);
6059 gtk_table_attach(GTK_TABLE(compose->header_table), entry, 1, 2,
6060 compose->header_nextrow, compose->header_nextrow+1,
6061 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
6063 g_signal_connect(G_OBJECT(entry), "key-press-event",
6064 G_CALLBACK(compose_headerentry_key_press_event_cb),
6066 g_signal_connect(G_OBJECT(entry), "changed",
6067 G_CALLBACK(compose_headerentry_changed_cb),
6069 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
6070 G_CALLBACK(compose_grab_focus_cb), compose);
6073 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6074 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6075 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6076 g_signal_connect(G_OBJECT(entry), "drag_data_received",
6077 G_CALLBACK(compose_header_drag_received_cb),
6079 g_signal_connect(G_OBJECT(entry), "drag-drop",
6080 G_CALLBACK(compose_drag_drop),
6082 g_signal_connect(G_OBJECT(entry), "populate-popup",
6083 G_CALLBACK(compose_entry_popup_extend),
6086 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
6088 headerentry->compose = compose;
6089 headerentry->combo = combo;
6090 headerentry->entry = entry;
6091 headerentry->headernum = compose->header_nextrow;
6093 compose->header_nextrow++;
6094 compose->header_last = headerentry;
6095 compose->header_list =
6096 g_slist_append(compose->header_list,
6100 static void compose_add_header_entry(Compose *compose, const gchar *header, gchar *text)
6102 ComposeHeaderEntry *last_header;
6104 last_header = compose->header_last;
6106 gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(last_header->combo)->entry), header);
6107 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
6110 static void compose_remove_header_entries(Compose *compose)
6113 for (list = compose->header_list; list; list = list->next) {
6114 ComposeHeaderEntry *headerentry =
6115 (ComposeHeaderEntry *)list->data;
6116 gtk_widget_destroy(headerentry->combo);
6117 gtk_widget_destroy(headerentry->entry);
6118 g_free(headerentry);
6120 compose->header_last = NULL;
6121 g_slist_free(compose->header_list);
6122 compose->header_list = NULL;
6123 compose->header_nextrow = 1;
6124 compose_create_header_entry(compose);
6127 static GtkWidget *compose_create_header(Compose *compose)
6129 GtkWidget *from_optmenu_hbox;
6130 GtkWidget *header_scrolledwin;
6131 GtkWidget *header_table;
6135 /* header labels and entries */
6136 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
6137 gtk_widget_show(header_scrolledwin);
6138 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
6140 header_table = gtk_table_new(2, 2, FALSE);
6141 gtk_widget_show(header_table);
6142 gtk_container_set_border_width(GTK_CONTAINER(header_table), BORDER_WIDTH);
6143 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
6144 gtk_viewport_set_shadow_type(GTK_VIEWPORT(GTK_BIN(header_scrolledwin)->child), GTK_SHADOW_ETCHED_IN);
6147 /* option menu for selecting accounts */
6148 from_optmenu_hbox = compose_account_option_menu_create(compose);
6149 gtk_table_attach(GTK_TABLE(header_table), from_optmenu_hbox,
6150 0, 2, count, count + 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
6153 compose->header_table = header_table;
6154 compose->header_list = NULL;
6155 compose->header_nextrow = count;
6157 compose_create_header_entry(compose);
6159 compose->table = NULL;
6161 return header_scrolledwin ;
6164 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
6166 Compose *compose = (Compose *)data;
6167 GdkEventButton event;
6170 event.time = gtk_get_current_event_time();
6172 return attach_button_pressed(compose->attach_clist, &event, compose);
6175 static GtkWidget *compose_create_attach(Compose *compose)
6177 GtkWidget *attach_scrwin;
6178 GtkWidget *attach_clist;
6180 GtkListStore *store;
6181 GtkCellRenderer *renderer;
6182 GtkTreeViewColumn *column;
6183 GtkTreeSelection *selection;
6185 /* attachment list */
6186 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
6187 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
6188 GTK_POLICY_AUTOMATIC,
6189 GTK_POLICY_AUTOMATIC);
6190 gtk_widget_set_size_request(attach_scrwin, -1, 80);
6192 store = gtk_list_store_new(N_ATTACH_COLS,
6197 G_TYPE_AUTO_POINTER,
6199 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
6200 (GTK_TREE_MODEL(store)));
6201 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
6202 g_object_unref(store);
6204 renderer = gtk_cell_renderer_text_new();
6205 column = gtk_tree_view_column_new_with_attributes
6206 (_("Mime type"), renderer, "text",
6207 COL_MIMETYPE, NULL);
6208 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6210 renderer = gtk_cell_renderer_text_new();
6211 column = gtk_tree_view_column_new_with_attributes
6212 (_("Size"), renderer, "text",
6214 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6216 renderer = gtk_cell_renderer_text_new();
6217 column = gtk_tree_view_column_new_with_attributes
6218 (_("Name"), renderer, "text",
6220 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6222 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
6223 prefs_common.use_stripes_everywhere);
6224 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
6225 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
6227 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
6228 G_CALLBACK(attach_selected), compose);
6229 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
6230 G_CALLBACK(attach_button_pressed), compose);
6232 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
6233 G_CALLBACK(popup_attach_button_pressed), compose);
6235 gtk_widget_tap_and_hold_setup(GTK_WIDGET(attach_clist), NULL, NULL,
6236 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
6237 g_signal_connect(G_OBJECT(attach_clist), "tap-and-hold",
6238 G_CALLBACK(popup_attach_button_pressed), compose);
6240 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
6241 G_CALLBACK(attach_key_pressed), compose);
6244 gtk_drag_dest_set(attach_clist,
6245 GTK_DEST_DEFAULT_ALL, compose_mime_types,
6246 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6247 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6248 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
6249 G_CALLBACK(compose_attach_drag_received_cb),
6251 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
6252 G_CALLBACK(compose_drag_drop),
6255 compose->attach_scrwin = attach_scrwin;
6256 compose->attach_clist = attach_clist;
6258 return attach_scrwin;
6261 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
6262 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
6264 static GtkWidget *compose_create_others(Compose *compose)
6267 GtkWidget *savemsg_checkbtn;
6268 GtkWidget *savemsg_entry;
6269 GtkWidget *savemsg_select;
6272 gchar *folderidentifier;
6274 /* Table for settings */
6275 table = gtk_table_new(3, 1, FALSE);
6276 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
6277 gtk_widget_show(table);
6278 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
6281 /* Save Message to folder */
6282 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
6283 gtk_widget_show(savemsg_checkbtn);
6284 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6285 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6286 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
6288 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
6289 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
6291 savemsg_entry = gtk_entry_new();
6292 gtk_widget_show(savemsg_entry);
6293 gtk_table_attach_defaults(GTK_TABLE(table), savemsg_entry, 1, 2, rowcount, rowcount + 1);
6294 gtk_editable_set_editable(GTK_EDITABLE(savemsg_entry), prefs_common.savemsg);
6295 g_signal_connect_after(G_OBJECT(savemsg_entry), "grab_focus",
6296 G_CALLBACK(compose_grab_focus_cb), compose);
6297 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6298 folderidentifier = folder_item_get_identifier(account_get_special_folder
6299 (compose->account, F_OUTBOX));
6300 gtk_entry_set_text(GTK_ENTRY(savemsg_entry), folderidentifier);
6301 g_free(folderidentifier);
6304 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
6305 gtk_widget_show(savemsg_select);
6306 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6307 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
6308 G_CALLBACK(compose_savemsg_select_cb),
6313 compose->savemsg_checkbtn = savemsg_checkbtn;
6314 compose->savemsg_entry = savemsg_entry;
6319 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
6321 gtk_editable_set_editable(GTK_EDITABLE(compose->savemsg_entry),
6322 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
6325 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
6330 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL);
6333 path = folder_item_get_identifier(dest);
6335 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), path);
6339 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
6340 GdkAtom clip, GtkTextIter *insert_place);
6343 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
6347 GtkTextBuffer *buffer;
6349 if (event->button == 3) {
6351 GtkTextIter sel_start, sel_end;
6352 gboolean stuff_selected;
6354 /* move the cursor to allow GtkAspell to check the word
6355 * under the mouse */
6356 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6357 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6359 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6362 stuff_selected = gtk_text_buffer_get_selection_bounds(
6363 GTK_TEXT_VIEW(text)->buffer,
6364 &sel_start, &sel_end);
6366 gtk_text_buffer_place_cursor (GTK_TEXT_VIEW(text)->buffer, &iter);
6367 /* reselect stuff */
6369 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
6370 gtk_text_buffer_select_range(GTK_TEXT_VIEW(text)->buffer,
6371 &sel_start, &sel_end);
6373 return FALSE; /* pass the event so that the right-click goes through */
6376 if (event->button == 2) {
6381 /* get the middle-click position to paste at the correct place */
6382 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6383 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6385 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6388 entry_paste_clipboard(compose, text,
6389 prefs_common.linewrap_pastes,
6390 GDK_SELECTION_PRIMARY, &iter);
6398 static void compose_spell_menu_changed(void *data)
6400 Compose *compose = (Compose *)data;
6402 GtkWidget *menuitem;
6403 GtkWidget *parent_item;
6404 GtkMenu *menu = GTK_MENU(gtk_menu_new());
6405 GtkItemFactory *ifactory = gtk_item_factory_from_widget(compose->menubar);
6408 if (compose->gtkaspell == NULL)
6411 parent_item = gtk_item_factory_get_item(ifactory,
6412 "/Spelling/Options");
6414 /* setting the submenu removes /Spelling/Options from the factory
6415 * so we need to save it */
6417 if (parent_item == NULL) {
6418 parent_item = compose->aspell_options_menu;
6419 gtk_menu_item_remove_submenu(GTK_MENU_ITEM(parent_item));
6421 compose->aspell_options_menu = parent_item;
6423 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
6425 spell_menu = g_slist_reverse(spell_menu);
6426 for (items = spell_menu;
6427 items; items = items->next) {
6428 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
6429 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
6430 gtk_widget_show(GTK_WIDGET(menuitem));
6432 g_slist_free(spell_menu);
6434 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
6439 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
6441 Compose *compose = (Compose *)data;
6442 GdkEventButton event;
6445 event.time = gtk_get_current_event_time();
6447 return text_clicked(compose->text, &event, compose);
6450 static gboolean compose_force_window_origin = TRUE;
6451 static Compose *compose_create(PrefsAccount *account,
6460 GtkWidget *handlebox;
6462 GtkWidget *notebook;
6467 GtkWidget *subject_hbox;
6468 GtkWidget *subject_frame;
6469 GtkWidget *subject_entry;
6473 GtkWidget *edit_vbox;
6474 GtkWidget *ruler_hbox;
6476 GtkWidget *scrolledwin;
6478 GtkTextBuffer *buffer;
6479 GtkClipboard *clipboard;
6481 UndoMain *undostruct;
6483 gchar *titles[N_ATTACH_COLS];
6484 guint n_menu_entries;
6485 GtkWidget *popupmenu;
6486 GtkItemFactory *popupfactory;
6487 GtkItemFactory *ifactory;
6488 GtkWidget *tmpl_menu;
6490 GtkWidget *menuitem;
6493 GtkAspell * gtkaspell = NULL;
6496 static GdkGeometry geometry;
6498 g_return_val_if_fail(account != NULL, NULL);
6500 debug_print("Creating compose window...\n");
6501 compose = g_new0(Compose, 1);
6503 titles[COL_MIMETYPE] = _("MIME type");
6504 titles[COL_SIZE] = _("Size");
6505 titles[COL_NAME] = _("Name");
6507 compose->batch = batch;
6508 compose->account = account;
6509 compose->folder = folder;
6511 compose->mutex = g_mutex_new();
6512 compose->set_cursor_pos = -1;
6514 compose->tooltips = gtk_tooltips_new();
6516 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
6518 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
6519 gtk_widget_set_size_request(window, -1, prefs_common.compose_height);
6521 if (!geometry.max_width) {
6522 geometry.max_width = gdk_screen_width();
6523 geometry.max_height = gdk_screen_height();
6526 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
6527 &geometry, GDK_HINT_MAX_SIZE);
6528 if (!geometry.min_width) {
6529 geometry.min_width = 600;
6530 geometry.min_height = 480;
6532 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
6533 &geometry, GDK_HINT_MIN_SIZE);
6536 if (compose_force_window_origin)
6537 gtk_widget_set_uposition(window, prefs_common.compose_x,
6538 prefs_common.compose_y);
6540 g_signal_connect(G_OBJECT(window), "delete_event",
6541 G_CALLBACK(compose_delete_cb), compose);
6542 MANAGE_WINDOW_SIGNALS_CONNECT(window);
6543 gtk_widget_realize(window);
6545 gtkut_widget_set_composer_icon(window);
6547 vbox = gtk_vbox_new(FALSE, 0);
6548 gtk_container_add(GTK_CONTAINER(window), vbox);
6550 n_menu_entries = sizeof(compose_entries) / sizeof(compose_entries[0]);
6551 menubar = menubar_create(window, compose_entries,
6552 n_menu_entries, "<Compose>", compose);
6553 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
6555 if (prefs_common.toolbar_detachable) {
6556 handlebox = gtk_handle_box_new();
6558 handlebox = gtk_hbox_new(FALSE, 0);
6560 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
6562 gtk_widget_realize(handlebox);
6564 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, window,
6567 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
6571 vbox2 = gtk_vbox_new(FALSE, 2);
6572 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
6573 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
6576 notebook = gtk_notebook_new();
6577 gtk_widget_set_size_request(notebook, -1, 130);
6578 gtk_widget_show(notebook);
6580 /* header labels and entries */
6581 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6582 compose_create_header(compose),
6583 gtk_label_new_with_mnemonic(_("Hea_der")));
6584 /* attachment list */
6585 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6586 compose_create_attach(compose),
6587 gtk_label_new_with_mnemonic(_("_Attachments")));
6589 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6590 compose_create_others(compose),
6591 gtk_label_new_with_mnemonic(_("Othe_rs")));
6594 subject_hbox = gtk_hbox_new(FALSE, 0);
6595 gtk_widget_show(subject_hbox);
6597 subject_frame = gtk_frame_new(NULL);
6598 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
6599 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
6600 gtk_widget_show(subject_frame);
6602 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
6603 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
6604 gtk_widget_show(subject);
6606 label = gtk_label_new(_("Subject:"));
6607 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
6608 gtk_widget_show(label);
6610 subject_entry = gtk_entry_new();
6611 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
6612 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
6613 G_CALLBACK(compose_grab_focus_cb), compose);
6614 gtk_widget_show(subject_entry);
6615 compose->subject_entry = subject_entry;
6616 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
6618 edit_vbox = gtk_vbox_new(FALSE, 0);
6620 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
6623 ruler_hbox = gtk_hbox_new(FALSE, 0);
6624 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
6626 ruler = gtk_shruler_new();
6627 gtk_ruler_set_range(GTK_RULER(ruler), 0.0, 100.0, 1.0, 100.0);
6628 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
6632 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
6633 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
6634 GTK_POLICY_AUTOMATIC,
6635 GTK_POLICY_AUTOMATIC);
6636 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
6638 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
6639 gtk_widget_set_size_request(scrolledwin, prefs_common.compose_width, -1);
6641 text = gtk_text_view_new();
6642 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
6643 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
6644 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
6645 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
6646 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
6648 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
6650 g_signal_connect_after(G_OBJECT(text), "size_allocate",
6651 G_CALLBACK(compose_edit_size_alloc),
6653 g_signal_connect(G_OBJECT(buffer), "changed",
6654 G_CALLBACK(compose_changed_cb), compose);
6655 g_signal_connect(G_OBJECT(text), "grab_focus",
6656 G_CALLBACK(compose_grab_focus_cb), compose);
6657 g_signal_connect(G_OBJECT(buffer), "insert_text",
6658 G_CALLBACK(text_inserted), compose);
6659 g_signal_connect(G_OBJECT(text), "button_press_event",
6660 G_CALLBACK(text_clicked), compose);
6662 g_signal_connect(G_OBJECT(text), "popup-menu",
6663 G_CALLBACK(compose_popup_menu), compose);
6665 gtk_widget_tap_and_hold_setup(GTK_WIDGET(text), NULL, NULL,
6666 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
6667 g_signal_connect(G_OBJECT(text), "tap-and-hold",
6668 G_CALLBACK(compose_popup_menu), compose);
6670 g_signal_connect(G_OBJECT(subject_entry), "changed",
6671 G_CALLBACK(compose_changed_cb), compose);
6674 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6675 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6676 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6677 g_signal_connect(G_OBJECT(text), "drag_data_received",
6678 G_CALLBACK(compose_insert_drag_received_cb),
6680 g_signal_connect(G_OBJECT(text), "drag-drop",
6681 G_CALLBACK(compose_drag_drop),
6683 gtk_widget_show_all(vbox);
6685 /* pane between attach clist and text */
6686 paned = gtk_vpaned_new();
6687 gtk_paned_set_gutter_size(GTK_PANED(paned), 12);
6688 gtk_container_add(GTK_CONTAINER(vbox2), paned);
6690 if( maemo_mainwindow_is_fullscreen(mainwindow_get_mainwindow()->window) )
6691 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 300 : 280);
6693 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 250 : 230);
6695 gtk_paned_add1(GTK_PANED(paned), notebook);
6696 gtk_paned_add2(GTK_PANED(paned), edit_vbox);
6697 gtk_widget_show_all(paned);
6700 if (prefs_common.textfont) {
6701 PangoFontDescription *font_desc;
6703 font_desc = pango_font_description_from_string
6704 (prefs_common.textfont);
6706 gtk_widget_modify_font(text, font_desc);
6707 pango_font_description_free(font_desc);
6711 n_entries = sizeof(compose_popup_entries) /
6712 sizeof(compose_popup_entries[0]);
6713 popupmenu = menu_create_items(compose_popup_entries, n_entries,
6714 "<Compose>", &popupfactory,
6717 ifactory = gtk_item_factory_from_widget(menubar);
6718 menu_set_sensitive(ifactory, "/Edit/Undo", FALSE);
6719 menu_set_sensitive(ifactory, "/Edit/Redo", FALSE);
6720 menu_set_sensitive(ifactory, "/Options/Remove references", FALSE);
6722 tmpl_menu = gtk_item_factory_get_item(ifactory, "/Tools/Template");
6724 undostruct = undo_init(text);
6725 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
6728 address_completion_start(window);
6730 compose->window = window;
6731 compose->vbox = vbox;
6732 compose->menubar = menubar;
6733 compose->handlebox = handlebox;
6735 compose->vbox2 = vbox2;
6737 compose->paned = paned;
6739 compose->notebook = notebook;
6740 compose->edit_vbox = edit_vbox;
6741 compose->ruler_hbox = ruler_hbox;
6742 compose->ruler = ruler;
6743 compose->scrolledwin = scrolledwin;
6744 compose->text = text;
6746 compose->focused_editable = NULL;
6748 compose->popupmenu = popupmenu;
6749 compose->popupfactory = popupfactory;
6751 compose->tmpl_menu = tmpl_menu;
6753 compose->mode = mode;
6754 compose->rmode = mode;
6756 compose->targetinfo = NULL;
6757 compose->replyinfo = NULL;
6758 compose->fwdinfo = NULL;
6760 compose->replyto = NULL;
6762 compose->bcc = NULL;
6763 compose->followup_to = NULL;
6765 compose->ml_post = NULL;
6767 compose->inreplyto = NULL;
6768 compose->references = NULL;
6769 compose->msgid = NULL;
6770 compose->boundary = NULL;
6772 compose->autowrap = prefs_common.autowrap;
6774 compose->use_signing = FALSE;
6775 compose->use_encryption = FALSE;
6776 compose->privacy_system = NULL;
6778 compose->modified = FALSE;
6780 compose->return_receipt = FALSE;
6782 compose->to_list = NULL;
6783 compose->newsgroup_list = NULL;
6785 compose->undostruct = undostruct;
6787 compose->sig_str = NULL;
6789 compose->exteditor_file = NULL;
6790 compose->exteditor_pid = -1;
6791 compose->exteditor_tag = -1;
6792 compose->draft_timeout_tag = -2; /* inhibit auto-drafting while loading */
6795 menu_set_sensitive(ifactory, "/Spelling", FALSE);
6796 if (mode != COMPOSE_REDIRECT) {
6797 if (prefs_common.enable_aspell && prefs_common.dictionary &&
6798 strcmp(prefs_common.dictionary, "")) {
6799 gtkaspell = gtkaspell_new(prefs_common.aspell_path,
6800 prefs_common.dictionary,
6801 prefs_common.alt_dictionary,
6802 conv_get_locale_charset_str(),
6803 prefs_common.misspelled_col,
6804 prefs_common.check_while_typing,
6805 prefs_common.recheck_when_changing_dict,
6806 prefs_common.use_alternate,
6807 prefs_common.use_both_dicts,
6808 GTK_TEXT_VIEW(text),
6809 GTK_WINDOW(compose->window),
6810 compose_spell_menu_changed,
6813 alertpanel_error(_("Spell checker could not "
6815 gtkaspell_checkers_strerror());
6816 gtkaspell_checkers_reset_error();
6818 if (!gtkaspell_set_sug_mode(gtkaspell,
6819 prefs_common.aspell_sugmode)) {
6820 debug_print("Aspell: could not set "
6821 "suggestion mode %s\n",
6822 gtkaspell_checkers_strerror());
6823 gtkaspell_checkers_reset_error();
6826 menu_set_sensitive(ifactory, "/Spelling", TRUE);
6830 compose->gtkaspell = gtkaspell;
6831 compose_spell_menu_changed(compose);
6834 compose_select_account(compose, account, TRUE);
6836 menu_set_active(ifactory, "/Edit/Auto wrapping", prefs_common.autowrap);
6837 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
6838 compose_entry_append(compose, account->auto_cc, COMPOSE_CC);
6840 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
6841 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC);
6843 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
6844 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO);
6846 menu_set_sensitive(ifactory, "/Options/Reply mode", compose->mode == COMPOSE_REPLY);
6848 if (account->protocol != A_NNTP)
6849 gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(compose->header_last->combo)->entry),
6850 prefs_common_translated_header_name("To:"));
6852 gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(compose->header_last->combo)->entry),
6853 prefs_common_translated_header_name("Newsgroups:"));
6855 addressbook_set_target_compose(compose);
6857 if (mode != COMPOSE_REDIRECT)
6858 compose_set_template_menu(compose);
6860 menuitem = gtk_item_factory_get_item(ifactory, "/Tools/Template");
6861 menu_set_sensitive(ifactory, "/Tools/Template", FALSE);
6864 compose_list = g_list_append(compose_list, compose);
6866 if (!prefs_common.show_ruler)
6867 gtk_widget_hide(ruler_hbox);
6869 menuitem = gtk_item_factory_get_item(ifactory, "/Tools/Show ruler");
6870 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
6871 prefs_common.show_ruler);
6874 compose->priority = PRIORITY_NORMAL;
6875 compose_update_priority_menu_item(compose);
6877 compose_set_out_encoding(compose);
6880 compose_update_actions_menu(compose);
6882 /* Privacy Systems menu */
6883 compose_update_privacy_systems_menu(compose);
6885 activate_privacy_system(compose, account, TRUE);
6886 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
6888 gtk_widget_realize(window);
6890 gtk_widget_show(window);
6892 maemo_window_full_screen_if_needed(GTK_WINDOW(window));
6893 maemo_connect_key_press_to_mainwindow(GTK_WINDOW(window));
6900 static GtkWidget *compose_account_option_menu_create(Compose *compose)
6905 GtkWidget *optmenubox;
6908 GtkWidget *from_name = NULL;
6910 gint num = 0, def_menu = 0;
6912 accounts = account_get_list();
6913 g_return_val_if_fail(accounts != NULL, NULL);
6915 optmenubox = gtk_event_box_new();
6916 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
6917 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
6919 hbox = gtk_hbox_new(FALSE, 6);
6920 from_name = gtk_entry_new();
6922 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
6923 G_CALLBACK(compose_grab_focus_cb), compose);
6925 for (; accounts != NULL; accounts = accounts->next, num++) {
6926 PrefsAccount *ac = (PrefsAccount *)accounts->data;
6927 gchar *name, *from = NULL;
6929 if (ac == compose->account) def_menu = num;
6931 name = g_markup_printf_escaped(_("From: <i>%s</i>"),
6934 if (ac == compose->account) {
6935 if (ac->name && *ac->name) {
6937 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
6938 from = g_strdup_printf("%s <%s>",
6940 gtk_entry_set_text(GTK_ENTRY(from_name), from);
6942 from = g_strdup_printf("%s",
6944 gtk_entry_set_text(GTK_ENTRY(from_name), from);
6947 COMBOBOX_ADD(menu, name, ac->account_id);
6952 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
6954 g_signal_connect(G_OBJECT(optmenu), "changed",
6955 G_CALLBACK(account_activated),
6957 g_signal_connect(G_OBJECT(from_name), "populate-popup",
6958 G_CALLBACK(compose_entry_popup_extend),
6961 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
6962 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
6964 gtk_tooltips_set_tip(compose->tooltips, optmenubox,
6965 _("Account to use for this email"), NULL);
6966 gtk_tooltips_set_tip(compose->tooltips, from_name,
6967 _("Sender address to be used"), NULL);
6969 compose->from_name = from_name;
6974 static void compose_set_priority_cb(gpointer data,
6978 Compose *compose = (Compose *) data;
6979 compose->priority = action;
6982 static void compose_reply_change_mode(gpointer data,
6986 Compose *compose = (Compose *) data;
6987 gboolean was_modified = compose->modified;
6989 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
6991 g_return_if_fail(compose->replyinfo != NULL);
6993 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
6995 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
6997 if (action == COMPOSE_REPLY_TO_ALL)
6999 if (action == COMPOSE_REPLY_TO_SENDER)
7001 if (action == COMPOSE_REPLY_TO_LIST)
7004 compose_remove_header_entries(compose);
7005 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
7006 if (compose->account->set_autocc && compose->account->auto_cc)
7007 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC);
7009 if (compose->account->set_autobcc && compose->account->auto_bcc)
7010 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC);
7012 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
7013 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO);
7014 compose_show_first_last_header(compose, TRUE);
7015 compose->modified = was_modified;
7016 compose_set_title(compose);
7019 static void compose_update_priority_menu_item(Compose * compose)
7021 GtkItemFactory *ifactory;
7022 GtkWidget *menuitem = NULL;
7024 ifactory = gtk_item_factory_from_widget(compose->menubar);
7026 switch (compose->priority) {
7027 case PRIORITY_HIGHEST:
7028 menuitem = gtk_item_factory_get_item
7029 (ifactory, "/Options/Priority/Highest");
7032 menuitem = gtk_item_factory_get_item
7033 (ifactory, "/Options/Priority/High");
7035 case PRIORITY_NORMAL:
7036 menuitem = gtk_item_factory_get_item
7037 (ifactory, "/Options/Priority/Normal");
7040 menuitem = gtk_item_factory_get_item
7041 (ifactory, "/Options/Priority/Low");
7043 case PRIORITY_LOWEST:
7044 menuitem = gtk_item_factory_get_item
7045 (ifactory, "/Options/Priority/Lowest");
7048 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
7051 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
7053 Compose *compose = (Compose *) data;
7055 GtkItemFactory *ifactory;
7056 gboolean can_sign = FALSE, can_encrypt = FALSE;
7058 g_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
7060 if (!GTK_CHECK_MENU_ITEM(widget)->active)
7063 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
7064 g_free(compose->privacy_system);
7065 compose->privacy_system = NULL;
7066 if (systemid != NULL) {
7067 compose->privacy_system = g_strdup(systemid);
7069 can_sign = privacy_system_can_sign(systemid);
7070 can_encrypt = privacy_system_can_encrypt(systemid);
7073 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
7075 ifactory = gtk_item_factory_from_widget(compose->menubar);
7076 menu_set_sensitive(ifactory, "/Options/Sign", can_sign);
7077 menu_set_sensitive(ifactory, "/Options/Encrypt", can_encrypt);
7080 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
7082 static gchar *branch_path = "/Options/Privacy System";
7083 GtkItemFactory *ifactory;
7084 GtkWidget *menuitem = NULL;
7086 gboolean can_sign = FALSE, can_encrypt = FALSE;
7087 gboolean found = FALSE;
7089 ifactory = gtk_item_factory_from_widget(compose->menubar);
7091 if (compose->privacy_system != NULL) {
7094 menuitem = gtk_item_factory_get_widget(ifactory, branch_path);
7095 g_return_if_fail(menuitem != NULL);
7097 amenu = GTK_MENU_SHELL(menuitem)->children;
7099 while (amenu != NULL) {
7100 GList *alist = amenu->next;
7102 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
7103 if (systemid != NULL) {
7104 if (strcmp(systemid, compose->privacy_system) == 0) {
7105 menuitem = GTK_WIDGET(amenu->data);
7107 can_sign = privacy_system_can_sign(systemid);
7108 can_encrypt = privacy_system_can_encrypt(systemid);
7112 } else if (strlen(compose->privacy_system) == 0) {
7113 menuitem = GTK_WIDGET(amenu->data);
7116 can_encrypt = FALSE;
7123 if (menuitem != NULL)
7124 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
7126 if (warn && !found && strlen(compose->privacy_system)) {
7127 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
7128 "will not be able to sign or encrypt this message."),
7129 compose->privacy_system);
7133 menu_set_sensitive(ifactory, "/Options/Sign", can_sign);
7134 menu_set_sensitive(ifactory, "/Options/Encrypt", can_encrypt);
7137 static void compose_set_out_encoding(Compose *compose)
7139 GtkItemFactoryEntry *entry;
7140 GtkItemFactory *ifactory;
7141 CharSet out_encoding;
7142 gchar *path, *p, *q;
7145 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
7146 ifactory = gtk_item_factory_from_widget(compose->menubar);
7148 for (entry = compose_entries; entry->callback != compose_address_cb;
7150 if (entry->callback == compose_set_encoding_cb &&
7151 (CharSet)entry->callback_action == out_encoding) {
7152 p = q = path = g_strdup(entry->path);
7164 item = gtk_item_factory_get_item(ifactory, path);
7165 gtk_widget_activate(item);
7172 static void compose_set_template_menu(Compose *compose)
7174 GSList *tmpl_list, *cur;
7178 tmpl_list = template_get_config();
7180 menu = gtk_menu_new();
7182 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
7183 Template *tmpl = (Template *)cur->data;
7185 item = gtk_menu_item_new_with_label(tmpl->name);
7186 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
7187 g_signal_connect(G_OBJECT(item), "activate",
7188 G_CALLBACK(compose_template_activate_cb),
7190 g_object_set_data(G_OBJECT(item), "template", tmpl);
7191 gtk_widget_show(item);
7194 gtk_widget_show(menu);
7195 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
7198 void compose_update_actions_menu(Compose *compose)
7200 GtkItemFactory *ifactory;
7202 ifactory = gtk_item_factory_from_widget(compose->menubar);
7203 action_update_compose_menu(ifactory, "/Tools/Actions", compose);
7206 static void compose_update_privacy_systems_menu(Compose *compose)
7208 static gchar *branch_path = "/Options/Privacy System";
7209 GtkItemFactory *ifactory;
7210 GtkWidget *menuitem;
7211 GSList *systems, *cur;
7214 GtkWidget *system_none;
7217 ifactory = gtk_item_factory_from_widget(compose->menubar);
7219 /* remove old entries */
7220 menuitem = gtk_item_factory_get_widget(ifactory, branch_path);
7221 g_return_if_fail(menuitem != NULL);
7223 amenu = GTK_MENU_SHELL(menuitem)->children->next;
7224 while (amenu != NULL) {
7225 GList *alist = amenu->next;
7226 gtk_widget_destroy(GTK_WIDGET(amenu->data));
7230 system_none = gtk_item_factory_get_widget(ifactory,
7231 "/Options/Privacy System/None");
7233 g_signal_connect(G_OBJECT(system_none), "activate",
7234 G_CALLBACK(compose_set_privacy_system_cb), compose);
7236 systems = privacy_get_system_ids();
7237 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
7238 gchar *systemid = cur->data;
7240 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
7241 widget = gtk_radio_menu_item_new_with_label(group,
7242 privacy_system_get_name(systemid));
7243 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
7244 g_strdup(systemid), g_free);
7245 g_signal_connect(G_OBJECT(widget), "activate",
7246 G_CALLBACK(compose_set_privacy_system_cb), compose);
7248 gtk_menu_append(GTK_MENU(system_none->parent), widget);
7249 gtk_widget_show(widget);
7252 g_slist_free(systems);
7255 void compose_reflect_prefs_all(void)
7260 for (cur = compose_list; cur != NULL; cur = cur->next) {
7261 compose = (Compose *)cur->data;
7262 compose_set_template_menu(compose);
7266 void compose_reflect_prefs_pixmap_theme(void)
7271 for (cur = compose_list; cur != NULL; cur = cur->next) {
7272 compose = (Compose *)cur->data;
7273 toolbar_update(TOOLBAR_COMPOSE, compose);
7277 static const gchar *compose_quote_char_from_context(Compose *compose)
7279 const gchar *qmark = NULL;
7281 g_return_val_if_fail(compose != NULL, NULL);
7283 switch (compose->mode) {
7284 /* use forward-specific quote char */
7285 case COMPOSE_FORWARD:
7286 case COMPOSE_FORWARD_AS_ATTACH:
7287 case COMPOSE_FORWARD_INLINE:
7288 if (compose->folder && compose->folder->prefs &&
7289 compose->folder->prefs->forward_with_format)
7290 qmark = compose->folder->prefs->forward_quotemark;
7291 else if (compose->account->forward_with_format)
7292 qmark = compose->account->forward_quotemark;
7294 qmark = prefs_common.fw_quotemark;
7297 /* use reply-specific quote char in all other modes */
7299 if (compose->folder && compose->folder->prefs &&
7300 compose->folder->prefs->reply_with_format)
7301 qmark = compose->folder->prefs->reply_quotemark;
7302 else if (compose->account->reply_with_format)
7303 qmark = compose->account->reply_quotemark;
7305 qmark = prefs_common.quotemark;
7309 if (qmark == NULL || *qmark == '\0')
7315 static void compose_template_apply(Compose *compose, Template *tmpl,
7319 GtkTextBuffer *buffer;
7323 gchar *parsed_str = NULL;
7324 gint cursor_pos = 0;
7325 const gchar *err_msg = _("Template body format error at line %d.");
7328 /* process the body */
7330 text = GTK_TEXT_VIEW(compose->text);
7331 buffer = gtk_text_view_get_buffer(text);
7334 qmark = compose_quote_char_from_context(compose);
7336 if (compose->replyinfo != NULL) {
7339 gtk_text_buffer_set_text(buffer, "", -1);
7340 mark = gtk_text_buffer_get_insert(buffer);
7341 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7343 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
7344 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
7346 } else if (compose->fwdinfo != NULL) {
7349 gtk_text_buffer_set_text(buffer, "", -1);
7350 mark = gtk_text_buffer_get_insert(buffer);
7351 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7353 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
7354 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
7357 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
7359 GtkTextIter start, end;
7362 gtk_text_buffer_get_start_iter(buffer, &start);
7363 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
7364 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
7366 /* clear the buffer now */
7368 gtk_text_buffer_set_text(buffer, "", -1);
7370 parsed_str = compose_quote_fmt(compose, dummyinfo,
7371 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
7372 procmsg_msginfo_free( dummyinfo );
7378 gtk_text_buffer_set_text(buffer, "", -1);
7379 mark = gtk_text_buffer_get_insert(buffer);
7380 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7383 if (replace && parsed_str && compose->account->auto_sig)
7384 compose_insert_sig(compose, FALSE);
7386 if (replace && parsed_str) {
7387 gtk_text_buffer_get_start_iter(buffer, &iter);
7388 gtk_text_buffer_place_cursor(buffer, &iter);
7392 cursor_pos = quote_fmt_get_cursor_pos();
7393 compose->set_cursor_pos = cursor_pos;
7394 if (cursor_pos == -1)
7396 gtk_text_buffer_get_start_iter(buffer, &iter);
7397 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
7398 gtk_text_buffer_place_cursor(buffer, &iter);
7401 /* process the other fields */
7403 compose_template_apply_fields(compose, tmpl);
7404 quote_fmt_reset_vartable();
7405 compose_changed_cb(NULL, compose);
7408 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
7410 MsgInfo* dummyinfo = NULL;
7411 MsgInfo *msginfo = NULL;
7414 if (compose->replyinfo != NULL)
7415 msginfo = compose->replyinfo;
7416 else if (compose->fwdinfo != NULL)
7417 msginfo = compose->fwdinfo;
7419 dummyinfo = compose_msginfo_new_from_compose(compose);
7420 msginfo = dummyinfo;
7423 if (tmpl->to && *tmpl->to != '\0') {
7425 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7426 compose->gtkaspell);
7428 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7430 quote_fmt_scan_string(tmpl->to);
7433 buf = quote_fmt_get_buffer();
7435 alertpanel_error(_("Template To format error."));
7437 compose_entry_append(compose, buf, COMPOSE_TO);
7441 if (tmpl->cc && *tmpl->cc != '\0') {
7443 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7444 compose->gtkaspell);
7446 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7448 quote_fmt_scan_string(tmpl->cc);
7451 buf = quote_fmt_get_buffer();
7453 alertpanel_error(_("Template Cc format error."));
7455 compose_entry_append(compose, buf, COMPOSE_CC);
7459 if (tmpl->bcc && *tmpl->bcc != '\0') {
7461 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7462 compose->gtkaspell);
7464 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7466 quote_fmt_scan_string(tmpl->bcc);
7469 buf = quote_fmt_get_buffer();
7471 alertpanel_error(_("Template Bcc format error."));
7473 compose_entry_append(compose, buf, COMPOSE_BCC);
7477 /* process the subject */
7478 if (tmpl->subject && *tmpl->subject != '\0') {
7480 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7481 compose->gtkaspell);
7483 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7485 quote_fmt_scan_string(tmpl->subject);
7488 buf = quote_fmt_get_buffer();
7490 alertpanel_error(_("Template subject format error."));
7492 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
7496 procmsg_msginfo_free( dummyinfo );
7499 static void compose_destroy(Compose *compose)
7501 GtkTextBuffer *buffer;
7502 GtkClipboard *clipboard;
7504 compose_list = g_list_remove(compose_list, compose);
7506 if (compose->updating) {
7507 debug_print("danger, not destroying anything now\n");
7508 compose->deferred_destroy = TRUE;
7511 /* NOTE: address_completion_end() does nothing with the window
7512 * however this may change. */
7513 address_completion_end(compose->window);
7515 slist_free_strings(compose->to_list);
7516 g_slist_free(compose->to_list);
7517 slist_free_strings(compose->newsgroup_list);
7518 g_slist_free(compose->newsgroup_list);
7519 slist_free_strings(compose->header_list);
7520 g_slist_free(compose->header_list);
7522 procmsg_msginfo_free(compose->targetinfo);
7523 procmsg_msginfo_free(compose->replyinfo);
7524 procmsg_msginfo_free(compose->fwdinfo);
7526 g_free(compose->replyto);
7527 g_free(compose->cc);
7528 g_free(compose->bcc);
7529 g_free(compose->newsgroups);
7530 g_free(compose->followup_to);
7532 g_free(compose->ml_post);
7534 g_free(compose->inreplyto);
7535 g_free(compose->references);
7536 g_free(compose->msgid);
7537 g_free(compose->boundary);
7539 g_free(compose->redirect_filename);
7540 if (compose->undostruct)
7541 undo_destroy(compose->undostruct);
7543 g_free(compose->sig_str);
7545 g_free(compose->exteditor_file);
7547 g_free(compose->orig_charset);
7549 g_free(compose->privacy_system);
7551 if (addressbook_get_target_compose() == compose)
7552 addressbook_set_target_compose(NULL);
7555 if (compose->gtkaspell) {
7556 gtkaspell_delete(compose->gtkaspell);
7557 compose->gtkaspell = NULL;
7561 prefs_common.compose_width = compose->scrolledwin->allocation.width;
7562 prefs_common.compose_height = compose->window->allocation.height;
7564 if (!gtk_widget_get_parent(compose->paned))
7565 gtk_widget_destroy(compose->paned);
7566 gtk_widget_destroy(compose->popupmenu);
7568 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
7569 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7570 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
7572 gtk_widget_destroy(compose->window);
7573 toolbar_destroy(compose->toolbar);
7574 g_free(compose->toolbar);
7575 g_mutex_free(compose->mutex);
7579 static void compose_attach_info_free(AttachInfo *ainfo)
7581 g_free(ainfo->file);
7582 g_free(ainfo->content_type);
7583 g_free(ainfo->name);
7587 static void compose_attach_remove_selected(Compose *compose)
7589 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
7590 GtkTreeSelection *selection;
7592 GtkTreeModel *model;
7594 selection = gtk_tree_view_get_selection(tree_view);
7595 sel = gtk_tree_selection_get_selected_rows(selection, &model);
7600 for (cur = sel; cur != NULL; cur = cur->next) {
7601 GtkTreePath *path = cur->data;
7602 GtkTreeRowReference *ref = gtk_tree_row_reference_new
7605 gtk_tree_path_free(path);
7608 for (cur = sel; cur != NULL; cur = cur->next) {
7609 GtkTreeRowReference *ref = cur->data;
7610 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
7613 if (gtk_tree_model_get_iter(model, &iter, path))
7614 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
7616 gtk_tree_path_free(path);
7617 gtk_tree_row_reference_free(ref);
7623 static struct _AttachProperty
7626 GtkWidget *mimetype_entry;
7627 GtkWidget *encoding_optmenu;
7628 GtkWidget *path_entry;
7629 GtkWidget *filename_entry;
7631 GtkWidget *cancel_btn;
7634 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
7636 gtk_tree_path_free((GtkTreePath *)ptr);
7639 static void compose_attach_property(Compose *compose)
7641 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
7643 GtkComboBox *optmenu;
7644 GtkTreeSelection *selection;
7646 GtkTreeModel *model;
7649 static gboolean cancelled;
7651 /* only if one selected */
7652 selection = gtk_tree_view_get_selection(tree_view);
7653 if (gtk_tree_selection_count_selected_rows(selection) != 1)
7656 sel = gtk_tree_selection_get_selected_rows(selection, &model);
7660 path = (GtkTreePath *) sel->data;
7661 gtk_tree_model_get_iter(model, &iter, path);
7662 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
7665 g_list_foreach(sel, gtk_tree_path_free_, NULL);
7671 if (!attach_prop.window)
7672 compose_attach_property_create(&cancelled);
7673 gtk_widget_grab_focus(attach_prop.ok_btn);
7674 gtk_widget_show(attach_prop.window);
7675 manage_window_set_transient(GTK_WINDOW(attach_prop.window));
7677 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
7678 if (ainfo->encoding == ENC_UNKNOWN)
7679 combobox_select_by_data(optmenu, ENC_BASE64);
7681 combobox_select_by_data(optmenu, ainfo->encoding);
7683 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
7684 ainfo->content_type ? ainfo->content_type : "");
7685 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
7686 ainfo->file ? ainfo->file : "");
7687 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
7688 ainfo->name ? ainfo->name : "");
7691 const gchar *entry_text;
7693 gchar *cnttype = NULL;
7700 gtk_widget_hide(attach_prop.window);
7705 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
7706 if (*entry_text != '\0') {
7709 text = g_strstrip(g_strdup(entry_text));
7710 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
7711 cnttype = g_strdup(text);
7714 alertpanel_error(_("Invalid MIME type."));
7720 ainfo->encoding = combobox_get_active_data(optmenu);
7722 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
7723 if (*entry_text != '\0') {
7724 if (is_file_exist(entry_text) &&
7725 (size = get_file_size(entry_text)) > 0)
7726 file = g_strdup(entry_text);
7729 (_("File doesn't exist or is empty."));
7735 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
7736 if (*entry_text != '\0') {
7737 g_free(ainfo->name);
7738 ainfo->name = g_strdup(entry_text);
7742 g_free(ainfo->content_type);
7743 ainfo->content_type = cnttype;
7746 g_free(ainfo->file);
7752 /* update tree store */
7753 text = to_human_readable(ainfo->size);
7754 gtk_tree_model_get_iter(model, &iter, path);
7755 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
7756 COL_MIMETYPE, ainfo->content_type,
7758 COL_NAME, ainfo->name,
7764 gtk_tree_path_free(path);
7767 #define SET_LABEL_AND_ENTRY(str, entry, top) \
7769 label = gtk_label_new(str); \
7770 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
7771 GTK_FILL, 0, 0, 0); \
7772 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
7774 entry = gtk_entry_new(); \
7775 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
7776 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
7779 static void compose_attach_property_create(gboolean *cancelled)
7785 GtkWidget *mimetype_entry;
7788 GtkListStore *optmenu_menu;
7789 GtkWidget *path_entry;
7790 GtkWidget *filename_entry;
7793 GtkWidget *cancel_btn;
7794 GList *mime_type_list, *strlist;
7797 debug_print("Creating attach_property window...\n");
7799 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
7800 gtk_widget_set_size_request(window, 480, -1);
7801 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
7802 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
7803 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
7804 gtk_window_set_modal(GTK_WINDOW(window), TRUE);
7805 g_signal_connect(G_OBJECT(window), "delete_event",
7806 G_CALLBACK(attach_property_delete_event),
7808 g_signal_connect(G_OBJECT(window), "key_press_event",
7809 G_CALLBACK(attach_property_key_pressed),
7812 vbox = gtk_vbox_new(FALSE, 8);
7813 gtk_container_add(GTK_CONTAINER(window), vbox);
7815 table = gtk_table_new(4, 2, FALSE);
7816 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
7817 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
7818 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
7820 label = gtk_label_new(_("MIME type"));
7821 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
7823 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
7824 mimetype_entry = gtk_combo_new();
7825 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
7826 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
7828 /* stuff with list */
7829 mime_type_list = procmime_get_mime_type_list();
7831 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
7832 MimeType *type = (MimeType *) mime_type_list->data;
7835 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
7837 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
7840 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
7841 (GCompareFunc)strcmp2);
7844 gtk_combo_set_popdown_strings(GTK_COMBO(mimetype_entry), strlist);
7846 for (mime_type_list = strlist; mime_type_list != NULL;
7847 mime_type_list = mime_type_list->next)
7848 g_free(mime_type_list->data);
7849 g_list_free(strlist);
7851 mimetype_entry = GTK_COMBO(mimetype_entry)->entry;
7853 label = gtk_label_new(_("Encoding"));
7854 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
7856 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
7858 hbox = gtk_hbox_new(FALSE, 0);
7859 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
7860 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
7862 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
7863 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
7865 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
7866 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
7867 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
7868 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
7869 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
7871 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
7873 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
7874 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
7876 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
7877 &ok_btn, GTK_STOCK_OK,
7879 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
7880 gtk_widget_grab_default(ok_btn);
7882 g_signal_connect(G_OBJECT(ok_btn), "clicked",
7883 G_CALLBACK(attach_property_ok),
7885 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
7886 G_CALLBACK(attach_property_cancel),
7889 gtk_widget_show_all(vbox);
7891 attach_prop.window = window;
7892 attach_prop.mimetype_entry = mimetype_entry;
7893 attach_prop.encoding_optmenu = optmenu;
7894 attach_prop.path_entry = path_entry;
7895 attach_prop.filename_entry = filename_entry;
7896 attach_prop.ok_btn = ok_btn;
7897 attach_prop.cancel_btn = cancel_btn;
7900 #undef SET_LABEL_AND_ENTRY
7902 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
7908 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
7914 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
7915 gboolean *cancelled)
7923 static gboolean attach_property_key_pressed(GtkWidget *widget,
7925 gboolean *cancelled)
7927 if (event && event->keyval == GDK_Escape) {
7934 static void compose_exec_ext_editor(Compose *compose)
7941 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
7942 G_DIR_SEPARATOR, compose);
7944 if (pipe(pipe_fds) < 0) {
7950 if ((pid = fork()) < 0) {
7957 /* close the write side of the pipe */
7960 compose->exteditor_file = g_strdup(tmp);
7961 compose->exteditor_pid = pid;
7963 compose_set_ext_editor_sensitive(compose, FALSE);
7965 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
7966 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
7970 } else { /* process-monitoring process */
7976 /* close the read side of the pipe */
7979 if (compose_write_body_to_file(compose, tmp) < 0) {
7980 fd_write_all(pipe_fds[1], "2\n", 2);
7984 pid_ed = compose_exec_ext_editor_real(tmp);
7986 fd_write_all(pipe_fds[1], "1\n", 2);
7990 /* wait until editor is terminated */
7991 waitpid(pid_ed, NULL, 0);
7993 fd_write_all(pipe_fds[1], "0\n", 2);
8000 #endif /* G_OS_UNIX */
8004 static gint compose_exec_ext_editor_real(const gchar *file)
8011 g_return_val_if_fail(file != NULL, -1);
8013 if ((pid = fork()) < 0) {
8018 if (pid != 0) return pid;
8020 /* grandchild process */
8022 if (setpgid(0, getppid()))
8025 if (prefs_common.ext_editor_cmd &&
8026 (p = strchr(prefs_common.ext_editor_cmd, '%')) &&
8027 *(p + 1) == 's' && !strchr(p + 2, '%')) {
8028 g_snprintf(buf, sizeof(buf), prefs_common.ext_editor_cmd, file);
8030 if (prefs_common.ext_editor_cmd)
8031 g_warning("External editor command line is invalid: '%s'\n",
8032 prefs_common.ext_editor_cmd);
8033 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
8036 cmdline = strsplit_with_quote(buf, " ", 1024);
8037 execvp(cmdline[0], cmdline);
8040 g_strfreev(cmdline);
8045 static gboolean compose_ext_editor_kill(Compose *compose)
8047 pid_t pgid = compose->exteditor_pid * -1;
8050 ret = kill(pgid, 0);
8052 if (ret == 0 || (ret == -1 && EPERM == errno)) {
8056 msg = g_strdup_printf
8057 (_("The external editor is still working.\n"
8058 "Force terminating the process?\n"
8059 "process group id: %d"), -pgid);
8060 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
8061 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
8065 if (val == G_ALERTALTERNATE) {
8066 g_source_remove(compose->exteditor_tag);
8067 g_io_channel_shutdown(compose->exteditor_ch,
8069 g_io_channel_unref(compose->exteditor_ch);
8071 if (kill(pgid, SIGTERM) < 0) perror("kill");
8072 waitpid(compose->exteditor_pid, NULL, 0);
8074 g_warning("Terminated process group id: %d", -pgid);
8075 g_warning("Temporary file: %s",
8076 compose->exteditor_file);
8078 compose_set_ext_editor_sensitive(compose, TRUE);
8080 g_free(compose->exteditor_file);
8081 compose->exteditor_file = NULL;
8082 compose->exteditor_pid = -1;
8083 compose->exteditor_ch = NULL;
8084 compose->exteditor_tag = -1;
8092 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
8096 Compose *compose = (Compose *)data;
8099 debug_print(_("Compose: input from monitoring process\n"));
8101 g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL);
8103 g_io_channel_shutdown(source, FALSE, NULL);
8104 g_io_channel_unref(source);
8106 waitpid(compose->exteditor_pid, NULL, 0);
8108 if (buf[0] == '0') { /* success */
8109 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
8110 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
8112 gtk_text_buffer_set_text(buffer, "", -1);
8113 compose_insert_file(compose, compose->exteditor_file);
8114 compose_changed_cb(NULL, compose);
8116 if (g_unlink(compose->exteditor_file) < 0)
8117 FILE_OP_ERROR(compose->exteditor_file, "unlink");
8118 } else if (buf[0] == '1') { /* failed */
8119 g_warning("Couldn't exec external editor\n");
8120 if (g_unlink(compose->exteditor_file) < 0)
8121 FILE_OP_ERROR(compose->exteditor_file, "unlink");
8122 } else if (buf[0] == '2') {
8123 g_warning("Couldn't write to file\n");
8124 } else if (buf[0] == '3') {
8125 g_warning("Pipe read failed\n");
8128 compose_set_ext_editor_sensitive(compose, TRUE);
8130 g_free(compose->exteditor_file);
8131 compose->exteditor_file = NULL;
8132 compose->exteditor_pid = -1;
8133 compose->exteditor_ch = NULL;
8134 compose->exteditor_tag = -1;
8139 static void compose_set_ext_editor_sensitive(Compose *compose,
8142 GtkItemFactory *ifactory;
8144 ifactory = gtk_item_factory_from_widget(compose->menubar);
8146 menu_set_sensitive(ifactory, "/Message/Send", sensitive);
8147 menu_set_sensitive(ifactory, "/Message/Send later", sensitive);
8148 menu_set_sensitive(ifactory, "/Message/Insert file", sensitive);
8149 menu_set_sensitive(ifactory, "/Message/Insert signature", sensitive);
8150 menu_set_sensitive(ifactory, "/Edit/Wrap current paragraph", sensitive);
8151 menu_set_sensitive(ifactory, "/Edit/Wrap all long lines", sensitive);
8152 menu_set_sensitive(ifactory, "/Edit/Edit with external editor",
8155 gtk_widget_set_sensitive(compose->text, sensitive);
8156 if (compose->toolbar->send_btn)
8157 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
8158 if (compose->toolbar->sendl_btn)
8159 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
8160 if (compose->toolbar->draft_btn)
8161 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
8162 if (compose->toolbar->insert_btn)
8163 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
8164 if (compose->toolbar->sig_btn)
8165 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
8166 if (compose->toolbar->exteditor_btn)
8167 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
8168 if (compose->toolbar->linewrap_current_btn)
8169 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
8170 if (compose->toolbar->linewrap_all_btn)
8171 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
8173 #endif /* G_OS_UNIX */
8176 * compose_undo_state_changed:
8178 * Change the sensivity of the menuentries undo and redo
8180 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
8181 gint redo_state, gpointer data)
8183 GtkWidget *widget = GTK_WIDGET(data);
8184 GtkItemFactory *ifactory;
8186 g_return_if_fail(widget != NULL);
8188 ifactory = gtk_item_factory_from_widget(widget);
8190 switch (undo_state) {
8191 case UNDO_STATE_TRUE:
8192 if (!undostruct->undo_state) {
8193 undostruct->undo_state = TRUE;
8194 menu_set_sensitive(ifactory, "/Edit/Undo", TRUE);
8197 case UNDO_STATE_FALSE:
8198 if (undostruct->undo_state) {
8199 undostruct->undo_state = FALSE;
8200 menu_set_sensitive(ifactory, "/Edit/Undo", FALSE);
8203 case UNDO_STATE_UNCHANGED:
8205 case UNDO_STATE_REFRESH:
8206 menu_set_sensitive(ifactory, "/Edit/Undo",
8207 undostruct->undo_state);
8210 g_warning("Undo state not recognized");
8214 switch (redo_state) {
8215 case UNDO_STATE_TRUE:
8216 if (!undostruct->redo_state) {
8217 undostruct->redo_state = TRUE;
8218 menu_set_sensitive(ifactory, "/Edit/Redo", TRUE);
8221 case UNDO_STATE_FALSE:
8222 if (undostruct->redo_state) {
8223 undostruct->redo_state = FALSE;
8224 menu_set_sensitive(ifactory, "/Edit/Redo", FALSE);
8227 case UNDO_STATE_UNCHANGED:
8229 case UNDO_STATE_REFRESH:
8230 menu_set_sensitive(ifactory, "/Edit/Redo",
8231 undostruct->redo_state);
8234 g_warning("Redo state not recognized");
8239 /* callback functions */
8241 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
8242 * includes "non-client" (windows-izm) in calculation, so this calculation
8243 * may not be accurate.
8245 static gboolean compose_edit_size_alloc(GtkEditable *widget,
8246 GtkAllocation *allocation,
8247 GtkSHRuler *shruler)
8249 if (prefs_common.show_ruler) {
8250 gint char_width = 0, char_height = 0;
8251 gint line_width_in_chars;
8253 gtkut_get_font_size(GTK_WIDGET(widget),
8254 &char_width, &char_height);
8255 line_width_in_chars =
8256 (allocation->width - allocation->x) / char_width;
8258 /* got the maximum */
8259 gtk_ruler_set_range(GTK_RULER(shruler),
8260 0.0, line_width_in_chars, 0,
8261 /*line_width_in_chars*/ char_width);
8267 static void account_activated(GtkComboBox *optmenu, gpointer data)
8269 Compose *compose = (Compose *)data;
8272 gchar *folderidentifier;
8273 gint account_id = 0;
8277 /* Get ID of active account in the combo box */
8278 menu = gtk_combo_box_get_model(optmenu);
8279 gtk_combo_box_get_active_iter(optmenu, &iter);
8280 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
8282 ac = account_find_from_id(account_id);
8283 g_return_if_fail(ac != NULL);
8285 if (ac != compose->account)
8286 compose_select_account(compose, ac, FALSE);
8288 /* Set message save folder */
8289 if (account_get_special_folder(compose->account, F_OUTBOX)) {
8290 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
8292 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
8293 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
8295 gtk_editable_delete_text(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
8296 if (account_get_special_folder(compose->account, F_OUTBOX)) {
8297 folderidentifier = folder_item_get_identifier(account_get_special_folder
8298 (compose->account, F_OUTBOX));
8299 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
8300 g_free(folderidentifier);
8304 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
8305 GtkTreeViewColumn *column, Compose *compose)
8307 compose_attach_property(compose);
8310 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
8313 Compose *compose = (Compose *)data;
8314 GtkTreeSelection *attach_selection;
8315 gint attach_nr_selected;
8316 GtkItemFactory *ifactory;
8318 if (!event) return FALSE;
8320 if (event->button == 3) {
8321 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
8322 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
8323 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
8325 if (attach_nr_selected > 0)
8327 menu_set_sensitive(ifactory, "/Remove", TRUE);
8328 menu_set_sensitive(ifactory, "/Properties...", TRUE);
8330 menu_set_sensitive(ifactory, "/Remove", FALSE);
8331 menu_set_sensitive(ifactory, "/Properties...", FALSE);
8334 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
8335 NULL, NULL, event->button, event->time);
8342 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
8345 Compose *compose = (Compose *)data;
8347 if (!event) return FALSE;
8349 switch (event->keyval) {
8351 compose_attach_remove_selected(compose);
8357 static void compose_allow_user_actions (Compose *compose, gboolean allow)
8359 GtkItemFactory *ifactory = gtk_item_factory_from_widget(compose->menubar);
8360 toolbar_comp_set_sensitive(compose, allow);
8361 menu_set_sensitive(ifactory, "/Message", allow);
8362 menu_set_sensitive(ifactory, "/Edit", allow);
8364 menu_set_sensitive(ifactory, "/Spelling", allow);
8366 menu_set_sensitive(ifactory, "/Options", allow);
8367 menu_set_sensitive(ifactory, "/Tools", allow);
8368 menu_set_sensitive(ifactory, "/Help", allow);
8370 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
8374 static void compose_send_cb(gpointer data, guint action, GtkWidget *widget)
8376 Compose *compose = (Compose *)data;
8378 if (prefs_common.work_offline &&
8379 !inc_offline_should_override(TRUE,
8380 _("Claws Mail needs network access in order "
8381 "to send this email.")))
8384 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
8385 g_source_remove(compose->draft_timeout_tag);
8386 compose->draft_timeout_tag = -1;
8389 compose_send(compose);
8392 static void compose_send_later_cb(gpointer data, guint action,
8395 Compose *compose = (Compose *)data;
8399 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
8403 compose_close(compose);
8404 } else if (val == -1) {
8405 alertpanel_error(_("Could not queue message."));
8406 } else if (val == -2) {
8407 alertpanel_error(_("Could not queue message:\n\n%s."), strerror(errno));
8408 } else if (val == -3) {
8409 if (privacy_peek_error())
8410 alertpanel_error(_("Could not queue message for sending:\n\n"
8411 "Signature failed: %s"), privacy_get_error());
8412 } else if (val == -4) {
8413 alertpanel_error(_("Could not queue message for sending:\n\n"
8414 "Charset conversion failed."));
8415 } else if (val == -5) {
8416 alertpanel_error(_("Could not queue message for sending:\n\n"
8417 "Couldn't get recipient encryption key."));
8418 } else if (val == -6) {
8421 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
8424 #define DRAFTED_AT_EXIT "drafted_at_exit"
8425 static void compose_register_draft(MsgInfo *info)
8427 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8428 DRAFTED_AT_EXIT, NULL);
8429 FILE *fp = fopen(filepath, "ab");
8432 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
8440 gboolean compose_draft (gpointer data, guint action)
8442 Compose *compose = (Compose *)data;
8446 MsgFlags flag = {0, 0};
8447 static gboolean lock = FALSE;
8448 MsgInfo *newmsginfo;
8450 gboolean target_locked = FALSE;
8451 gboolean err = FALSE;
8453 if (lock) return FALSE;
8455 if (compose->sending)
8458 draft = account_get_special_folder(compose->account, F_DRAFT);
8459 g_return_val_if_fail(draft != NULL, FALSE);
8461 if (!g_mutex_trylock(compose->mutex)) {
8462 /* we don't want to lock the mutex once it's available,
8463 * because as the only other part of compose.c locking
8464 * it is compose_close - which means once unlocked,
8465 * the compose struct will be freed */
8466 debug_print("couldn't lock mutex, probably sending\n");
8472 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
8473 G_DIR_SEPARATOR, compose);
8474 if ((fp = g_fopen(tmp, "wb")) == NULL) {
8475 FILE_OP_ERROR(tmp, "fopen");
8479 /* chmod for security */
8480 if (change_file_mode_rw(fp, tmp) < 0) {
8481 FILE_OP_ERROR(tmp, "chmod");
8482 g_warning("can't change file mode\n");
8485 /* Save draft infos */
8486 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
8487 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
8489 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
8490 gchar *savefolderid;
8492 savefolderid = gtk_editable_get_chars(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
8493 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
8494 g_free(savefolderid);
8496 if (compose->return_receipt) {
8497 err |= (fprintf(fp, "RRCPT:1\n") < 0);
8499 if (compose->privacy_system) {
8500 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
8501 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
8502 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
8505 /* Message-ID of message replying to */
8506 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
8509 folderid = folder_item_get_identifier(compose->replyinfo->folder);
8510 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
8513 /* Message-ID of message forwarding to */
8514 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
8517 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
8518 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
8522 /* end of headers */
8523 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
8530 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
8534 if (fclose(fp) == EOF) {
8538 if (compose->targetinfo) {
8539 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
8540 flag.perm_flags = target_locked?MSG_LOCKED:0;
8542 flag.tmp_flags = MSG_DRAFT;
8544 folder_item_scan(draft);
8545 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
8546 MsgInfo *tmpinfo = NULL;
8547 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
8548 if (compose->msgid) {
8549 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
8552 msgnum = tmpinfo->msgnum;
8553 procmsg_msginfo_free(tmpinfo);
8554 debug_print("got draft msgnum %d from scanning\n", msgnum);
8556 debug_print("didn't get draft msgnum after scanning\n");
8559 debug_print("got draft msgnum %d from adding\n", msgnum);
8565 if (action != COMPOSE_AUTO_SAVE) {
8566 if (action != COMPOSE_DRAFT_FOR_EXIT)
8567 alertpanel_error(_("Could not save draft."));
8570 gtkut_window_popup(compose->window);
8571 val = alertpanel_full(_("Could not save draft"),
8572 _("Could not save draft.\n"
8573 "Do you want to cancel exit or discard this email?"),
8574 _("_Cancel exit"), _("_Discard email"), NULL,
8575 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
8576 if (val == G_ALERTALTERNATE) {
8578 g_mutex_unlock(compose->mutex); /* must be done before closing */
8579 compose_close(compose);
8583 g_mutex_unlock(compose->mutex); /* must be done before closing */
8592 if (compose->mode == COMPOSE_REEDIT) {
8593 compose_remove_reedit_target(compose, TRUE);
8596 newmsginfo = folder_item_get_msginfo(draft, msgnum);
8599 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
8601 procmsg_msginfo_set_flags(newmsginfo, MSG_LOCKED, MSG_DRAFT);
8603 procmsg_msginfo_set_flags(newmsginfo, 0, MSG_DRAFT);
8604 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
8605 procmsg_msginfo_set_flags(newmsginfo, 0,
8606 MSG_HAS_ATTACHMENT);
8608 if (action == COMPOSE_DRAFT_FOR_EXIT) {
8609 compose_register_draft(newmsginfo);
8611 procmsg_msginfo_free(newmsginfo);
8614 folder_item_scan(draft);
8616 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
8618 g_mutex_unlock(compose->mutex); /* must be done before closing */
8619 compose_close(compose);
8625 path = folder_item_fetch_msg(draft, msgnum);
8627 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
8630 if (g_stat(path, &s) < 0) {
8631 FILE_OP_ERROR(path, "stat");
8637 procmsg_msginfo_free(compose->targetinfo);
8638 compose->targetinfo = procmsg_msginfo_new();
8639 compose->targetinfo->msgnum = msgnum;
8640 compose->targetinfo->size = s.st_size;
8641 compose->targetinfo->mtime = s.st_mtime;
8642 compose->targetinfo->folder = draft;
8644 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
8645 compose->mode = COMPOSE_REEDIT;
8647 if (action == COMPOSE_AUTO_SAVE) {
8648 compose->autosaved_draft = compose->targetinfo;
8650 compose->modified = FALSE;
8651 compose_set_title(compose);
8655 g_mutex_unlock(compose->mutex);
8659 void compose_clear_exit_drafts(void)
8661 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8662 DRAFTED_AT_EXIT, NULL);
8663 if (is_file_exist(filepath))
8669 void compose_reopen_exit_drafts(void)
8671 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8672 DRAFTED_AT_EXIT, NULL);
8673 FILE *fp = fopen(filepath, "rb");
8677 while (fgets(buf, sizeof(buf), fp)) {
8678 gchar **parts = g_strsplit(buf, "\t", 2);
8679 const gchar *folder = parts[0];
8680 int msgnum = parts[1] ? atoi(parts[1]):-1;
8682 if (folder && *folder && msgnum > -1) {
8683 FolderItem *item = folder_find_item_from_identifier(folder);
8684 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
8686 compose_reedit(info, FALSE);
8693 compose_clear_exit_drafts();
8696 static void compose_draft_cb(gpointer data, guint action, GtkWidget *widget)
8698 compose_draft(data, action);
8701 static void compose_attach_cb(gpointer data, guint action, GtkWidget *widget)
8703 Compose *compose = (Compose *)data;
8706 if (compose->redirect_filename != NULL)
8709 file_list = filesel_select_multiple_files_open(_("Select file"));
8714 for ( tmp = file_list; tmp; tmp = tmp->next) {
8715 gchar *file = (gchar *) tmp->data;
8716 gchar *utf8_filename = conv_filename_to_utf8(file);
8717 compose_attach_append(compose, file, utf8_filename, NULL);
8718 compose_changed_cb(NULL, compose);
8720 g_free(utf8_filename);
8722 g_list_free(file_list);
8726 static void compose_insert_file_cb(gpointer data, guint action,
8729 Compose *compose = (Compose *)data;
8732 file_list = filesel_select_multiple_files_open(_("Select file"));
8737 for ( tmp = file_list; tmp; tmp = tmp->next) {
8738 gchar *file = (gchar *) tmp->data;
8739 gchar *filedup = g_strdup(file);
8740 gchar *shortfile = g_path_get_basename(filedup);
8741 ComposeInsertResult res;
8743 res = compose_insert_file(compose, file);
8744 if (res == COMPOSE_INSERT_READ_ERROR) {
8745 alertpanel_error(_("File '%s' could not be read."), shortfile);
8746 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
8747 alertpanel_error(_("File '%s' contained invalid characters\n"
8748 "for the current encoding, insertion may be incorrect."), shortfile);
8754 g_list_free(file_list);
8758 static void compose_insert_sig_cb(gpointer data, guint action,
8761 Compose *compose = (Compose *)data;
8763 compose_insert_sig(compose, FALSE);
8766 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
8770 Compose *compose = (Compose *)data;
8772 gtkut_widget_get_uposition(widget, &x, &y);
8773 prefs_common.compose_x = x;
8774 prefs_common.compose_y = y;
8776 if (compose->sending || compose->updating)
8778 compose_close_cb(compose, 0, NULL);
8782 void compose_close_toolbar(Compose *compose)
8784 compose_close_cb(compose, 0, NULL);
8787 static void compose_close_cb(gpointer data, guint action, GtkWidget *widget)
8789 Compose *compose = (Compose *)data;
8793 if (compose->exteditor_tag != -1) {
8794 if (!compose_ext_editor_kill(compose))
8799 if (compose->modified) {
8800 val = alertpanel(_("Discard message"),
8801 _("This message has been modified. Discard it?"),
8802 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
8805 case G_ALERTDEFAULT:
8806 if (prefs_common.autosave)
8807 compose_remove_draft(compose);
8809 case G_ALERTALTERNATE:
8810 compose_draft_cb(data, COMPOSE_QUIT_EDITING, NULL);
8817 compose_close(compose);
8820 static void compose_set_encoding_cb(gpointer data, guint action,
8823 Compose *compose = (Compose *)data;
8825 if (GTK_CHECK_MENU_ITEM(widget)->active)
8826 compose->out_encoding = (CharSet)action;
8829 static void compose_address_cb(gpointer data, guint action, GtkWidget *widget)
8831 Compose *compose = (Compose *)data;
8833 addressbook_open(compose);
8836 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
8838 Compose *compose = (Compose *)data;
8843 tmpl = g_object_get_data(G_OBJECT(widget), "template");
8844 g_return_if_fail(tmpl != NULL);
8846 msg = g_strdup_printf(_("Do you want to apply the template '%s' ?"),
8848 val = alertpanel(_("Apply template"), msg,
8849 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
8852 if (val == G_ALERTDEFAULT)
8853 compose_template_apply(compose, tmpl, TRUE);
8854 else if (val == G_ALERTALTERNATE)
8855 compose_template_apply(compose, tmpl, FALSE);
8858 static void compose_ext_editor_cb(gpointer data, guint action,
8861 Compose *compose = (Compose *)data;
8863 compose_exec_ext_editor(compose);
8866 static void compose_undo_cb(Compose *compose)
8868 gboolean prev_autowrap = compose->autowrap;
8870 compose->autowrap = FALSE;
8871 undo_undo(compose->undostruct);
8872 compose->autowrap = prev_autowrap;
8875 static void compose_redo_cb(Compose *compose)
8877 gboolean prev_autowrap = compose->autowrap;
8879 compose->autowrap = FALSE;
8880 undo_redo(compose->undostruct);
8881 compose->autowrap = prev_autowrap;
8884 static void entry_cut_clipboard(GtkWidget *entry)
8886 if (GTK_IS_EDITABLE(entry))
8887 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
8888 else if (GTK_IS_TEXT_VIEW(entry))
8889 gtk_text_buffer_cut_clipboard(
8890 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
8891 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
8895 static void entry_copy_clipboard(GtkWidget *entry)
8897 if (GTK_IS_EDITABLE(entry))
8898 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
8899 else if (GTK_IS_TEXT_VIEW(entry))
8900 gtk_text_buffer_copy_clipboard(
8901 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
8902 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
8905 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
8906 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
8908 if (GTK_IS_TEXT_VIEW(entry)) {
8909 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
8910 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
8911 GtkTextIter start_iter, end_iter;
8913 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
8915 if (contents == NULL)
8918 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
8920 /* we shouldn't delete the selection when middle-click-pasting, or we
8921 * can't mid-click-paste our own selection */
8922 if (clip != GDK_SELECTION_PRIMARY) {
8923 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
8926 if (insert_place == NULL) {
8927 /* if insert_place isn't specified, insert at the cursor.
8928 * used for Ctrl-V pasting */
8929 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
8930 start = gtk_text_iter_get_offset(&start_iter);
8931 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
8933 /* if insert_place is specified, paste here.
8934 * used for mid-click-pasting */
8935 start = gtk_text_iter_get_offset(insert_place);
8936 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
8940 /* paste unwrapped: mark the paste so it's not wrapped later */
8941 end = start + strlen(contents);
8942 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
8943 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
8944 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
8945 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
8946 /* rewrap paragraph now (after a mid-click-paste) */
8947 mark_start = gtk_text_buffer_get_insert(buffer);
8948 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
8949 gtk_text_iter_backward_char(&start_iter);
8950 compose_beautify_paragraph(compose, &start_iter, TRUE);
8952 } else if (GTK_IS_EDITABLE(entry))
8953 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
8957 static void entry_allsel(GtkWidget *entry)
8959 if (GTK_IS_EDITABLE(entry))
8960 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
8961 else if (GTK_IS_TEXT_VIEW(entry)) {
8962 GtkTextIter startiter, enditer;
8963 GtkTextBuffer *textbuf;
8965 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
8966 gtk_text_buffer_get_start_iter(textbuf, &startiter);
8967 gtk_text_buffer_get_end_iter(textbuf, &enditer);
8969 gtk_text_buffer_move_mark_by_name(textbuf,
8970 "selection_bound", &startiter);
8971 gtk_text_buffer_move_mark_by_name(textbuf,
8972 "insert", &enditer);
8976 static void compose_cut_cb(Compose *compose)
8978 if (compose->focused_editable
8980 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
8983 entry_cut_clipboard(compose->focused_editable);
8986 static void compose_copy_cb(Compose *compose)
8988 if (compose->focused_editable
8990 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
8993 entry_copy_clipboard(compose->focused_editable);
8996 static void compose_paste_cb(Compose *compose)
8999 GtkTextBuffer *buffer;
9001 if (compose->focused_editable &&
9002 GTK_WIDGET_HAS_FOCUS(compose->focused_editable))
9003 entry_paste_clipboard(compose, compose->focused_editable,
9004 prefs_common.linewrap_pastes,
9005 GDK_SELECTION_CLIPBOARD, NULL);
9009 static void compose_paste_as_quote_cb(Compose *compose)
9011 gint wrap_quote = prefs_common.linewrap_quote;
9012 if (compose->focused_editable
9014 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9017 /* let text_insert() (called directly or at a later time
9018 * after the gtk_editable_paste_clipboard) know that
9019 * text is to be inserted as a quotation. implemented
9020 * by using a simple refcount... */
9021 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
9022 G_OBJECT(compose->focused_editable),
9023 "paste_as_quotation"));
9024 g_object_set_data(G_OBJECT(compose->focused_editable),
9025 "paste_as_quotation",
9026 GINT_TO_POINTER(paste_as_quotation + 1));
9027 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
9028 entry_paste_clipboard(compose, compose->focused_editable,
9029 prefs_common.linewrap_pastes,
9030 GDK_SELECTION_CLIPBOARD, NULL);
9031 prefs_common.linewrap_quote = wrap_quote;
9035 static void compose_paste_no_wrap_cb(Compose *compose)
9038 GtkTextBuffer *buffer;
9040 if (compose->focused_editable
9042 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9045 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
9046 GDK_SELECTION_CLIPBOARD, NULL);
9050 static void compose_paste_wrap_cb(Compose *compose)
9053 GtkTextBuffer *buffer;
9055 if (compose->focused_editable
9057 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9060 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
9061 GDK_SELECTION_CLIPBOARD, NULL);
9065 static void compose_allsel_cb(Compose *compose)
9067 if (compose->focused_editable
9069 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9072 entry_allsel(compose->focused_editable);
9075 static void textview_move_beginning_of_line (GtkTextView *text)
9077 GtkTextBuffer *buffer;
9081 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9083 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9084 mark = gtk_text_buffer_get_insert(buffer);
9085 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9086 gtk_text_iter_set_line_offset(&ins, 0);
9087 gtk_text_buffer_place_cursor(buffer, &ins);
9090 static void textview_move_forward_character (GtkTextView *text)
9092 GtkTextBuffer *buffer;
9096 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9098 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9099 mark = gtk_text_buffer_get_insert(buffer);
9100 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9101 if (gtk_text_iter_forward_cursor_position(&ins))
9102 gtk_text_buffer_place_cursor(buffer, &ins);
9105 static void textview_move_backward_character (GtkTextView *text)
9107 GtkTextBuffer *buffer;
9111 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9113 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9114 mark = gtk_text_buffer_get_insert(buffer);
9115 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9116 if (gtk_text_iter_backward_cursor_position(&ins))
9117 gtk_text_buffer_place_cursor(buffer, &ins);
9120 static void textview_move_forward_word (GtkTextView *text)
9122 GtkTextBuffer *buffer;
9127 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9129 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9130 mark = gtk_text_buffer_get_insert(buffer);
9131 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9132 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
9133 if (gtk_text_iter_forward_word_ends(&ins, count)) {
9134 gtk_text_iter_backward_word_start(&ins);
9135 gtk_text_buffer_place_cursor(buffer, &ins);
9139 static void textview_move_backward_word (GtkTextView *text)
9141 GtkTextBuffer *buffer;
9146 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9148 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9149 mark = gtk_text_buffer_get_insert(buffer);
9150 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9151 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
9152 if (gtk_text_iter_backward_word_starts(&ins, 1))
9153 gtk_text_buffer_place_cursor(buffer, &ins);
9156 static void textview_move_end_of_line (GtkTextView *text)
9158 GtkTextBuffer *buffer;
9162 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9164 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9165 mark = gtk_text_buffer_get_insert(buffer);
9166 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9167 if (gtk_text_iter_forward_to_line_end(&ins))
9168 gtk_text_buffer_place_cursor(buffer, &ins);
9171 static void textview_move_next_line (GtkTextView *text)
9173 GtkTextBuffer *buffer;
9178 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9180 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9181 mark = gtk_text_buffer_get_insert(buffer);
9182 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9183 offset = gtk_text_iter_get_line_offset(&ins);
9184 if (gtk_text_iter_forward_line(&ins)) {
9185 gtk_text_iter_set_line_offset(&ins, offset);
9186 gtk_text_buffer_place_cursor(buffer, &ins);
9190 static void textview_move_previous_line (GtkTextView *text)
9192 GtkTextBuffer *buffer;
9197 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9199 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9200 mark = gtk_text_buffer_get_insert(buffer);
9201 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9202 offset = gtk_text_iter_get_line_offset(&ins);
9203 if (gtk_text_iter_backward_line(&ins)) {
9204 gtk_text_iter_set_line_offset(&ins, offset);
9205 gtk_text_buffer_place_cursor(buffer, &ins);
9209 static void textview_delete_forward_character (GtkTextView *text)
9211 GtkTextBuffer *buffer;
9213 GtkTextIter ins, end_iter;
9215 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9217 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9218 mark = gtk_text_buffer_get_insert(buffer);
9219 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9221 if (gtk_text_iter_forward_char(&end_iter)) {
9222 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9226 static void textview_delete_backward_character (GtkTextView *text)
9228 GtkTextBuffer *buffer;
9230 GtkTextIter ins, end_iter;
9232 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9234 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9235 mark = gtk_text_buffer_get_insert(buffer);
9236 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9238 if (gtk_text_iter_backward_char(&end_iter)) {
9239 gtk_text_buffer_delete(buffer, &end_iter, &ins);
9243 static void textview_delete_forward_word (GtkTextView *text)
9245 GtkTextBuffer *buffer;
9247 GtkTextIter ins, end_iter;
9249 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9251 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9252 mark = gtk_text_buffer_get_insert(buffer);
9253 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9255 if (gtk_text_iter_forward_word_end(&end_iter)) {
9256 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9260 static void textview_delete_backward_word (GtkTextView *text)
9262 GtkTextBuffer *buffer;
9264 GtkTextIter ins, end_iter;
9266 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9268 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9269 mark = gtk_text_buffer_get_insert(buffer);
9270 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9272 if (gtk_text_iter_backward_word_start(&end_iter)) {
9273 gtk_text_buffer_delete(buffer, &end_iter, &ins);
9277 static void textview_delete_line (GtkTextView *text)
9279 GtkTextBuffer *buffer;
9281 GtkTextIter ins, start_iter, end_iter;
9284 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9286 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9287 mark = gtk_text_buffer_get_insert(buffer);
9288 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9291 gtk_text_iter_set_line_offset(&start_iter, 0);
9294 if (gtk_text_iter_ends_line(&end_iter))
9295 found = gtk_text_iter_forward_char(&end_iter);
9297 found = gtk_text_iter_forward_to_line_end(&end_iter);
9300 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
9303 static void textview_delete_to_line_end (GtkTextView *text)
9305 GtkTextBuffer *buffer;
9307 GtkTextIter ins, end_iter;
9310 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9312 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9313 mark = gtk_text_buffer_get_insert(buffer);
9314 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9316 if (gtk_text_iter_ends_line(&end_iter))
9317 found = gtk_text_iter_forward_char(&end_iter);
9319 found = gtk_text_iter_forward_to_line_end(&end_iter);
9321 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9324 static void compose_advanced_action_cb(Compose *compose,
9325 ComposeCallAdvancedAction action)
9327 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9329 void (*do_action) (GtkTextView *text);
9330 } action_table[] = {
9331 {textview_move_beginning_of_line},
9332 {textview_move_forward_character},
9333 {textview_move_backward_character},
9334 {textview_move_forward_word},
9335 {textview_move_backward_word},
9336 {textview_move_end_of_line},
9337 {textview_move_next_line},
9338 {textview_move_previous_line},
9339 {textview_delete_forward_character},
9340 {textview_delete_backward_character},
9341 {textview_delete_forward_word},
9342 {textview_delete_backward_word},
9343 {textview_delete_line},
9344 {NULL}, /* gtk_stext_delete_line_n */
9345 {textview_delete_to_line_end}
9348 if (!GTK_WIDGET_HAS_FOCUS(text)) return;
9350 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
9351 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
9352 if (action_table[action].do_action)
9353 action_table[action].do_action(text);
9355 g_warning("Not implemented yet.");
9359 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
9363 if (GTK_IS_EDITABLE(widget)) {
9364 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
9365 gtk_editable_set_position(GTK_EDITABLE(widget),
9368 if (widget->parent && widget->parent->parent
9369 && widget->parent->parent->parent) {
9370 if (GTK_IS_SCROLLED_WINDOW(widget->parent->parent->parent)) {
9371 gint y = widget->allocation.y;
9372 gint height = widget->allocation.height;
9373 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
9374 (GTK_SCROLLED_WINDOW(widget->parent->parent->parent));
9376 if (y < (int)shown->value) {
9377 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown), y - 1);
9379 if (y + height > (int)shown->value + (int)shown->page_size) {
9380 if (y - height - 1 < (int)shown->upper - (int)shown->page_size) {
9381 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
9382 y + height - (int)shown->page_size - 1);
9384 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
9385 (int)shown->upper - (int)shown->page_size - 1);
9392 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
9393 compose->focused_editable = widget;
9396 if (GTK_IS_TEXT_VIEW(widget)
9397 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
9398 gtk_widget_ref(compose->notebook);
9399 gtk_widget_ref(compose->edit_vbox);
9400 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
9401 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
9402 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
9403 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
9404 gtk_widget_unref(compose->notebook);
9405 gtk_widget_unref(compose->edit_vbox);
9406 g_signal_handlers_block_by_func(G_OBJECT(widget),
9407 G_CALLBACK(compose_grab_focus_cb),
9409 gtk_widget_grab_focus(widget);
9410 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
9411 G_CALLBACK(compose_grab_focus_cb),
9413 } else if (!GTK_IS_TEXT_VIEW(widget)
9414 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
9415 gtk_widget_ref(compose->notebook);
9416 gtk_widget_ref(compose->edit_vbox);
9417 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
9418 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
9419 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
9420 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
9421 gtk_widget_unref(compose->notebook);
9422 gtk_widget_unref(compose->edit_vbox);
9423 g_signal_handlers_block_by_func(G_OBJECT(widget),
9424 G_CALLBACK(compose_grab_focus_cb),
9426 gtk_widget_grab_focus(widget);
9427 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
9428 G_CALLBACK(compose_grab_focus_cb),
9434 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
9436 compose->modified = TRUE;
9438 compose_set_title(compose);
9442 static void compose_wrap_cb(gpointer data, guint action, GtkWidget *widget)
9444 Compose *compose = (Compose *)data;
9447 compose_wrap_all_full(compose, TRUE);
9449 compose_beautify_paragraph(compose, NULL, TRUE);
9452 static void compose_find_cb(gpointer data, guint action, GtkWidget *widget)
9454 Compose *compose = (Compose *)data;
9456 message_search_compose(compose);
9459 static void compose_toggle_autowrap_cb(gpointer data, guint action,
9462 Compose *compose = (Compose *)data;
9463 compose->autowrap = GTK_CHECK_MENU_ITEM(widget)->active;
9464 if (compose->autowrap)
9465 compose_wrap_all_full(compose, TRUE);
9466 compose->autowrap = GTK_CHECK_MENU_ITEM(widget)->active;
9469 static void compose_toggle_sign_cb(gpointer data, guint action,
9472 Compose *compose = (Compose *)data;
9474 if (GTK_CHECK_MENU_ITEM(widget)->active)
9475 compose->use_signing = TRUE;
9477 compose->use_signing = FALSE;
9480 static void compose_toggle_encrypt_cb(gpointer data, guint action,
9483 Compose *compose = (Compose *)data;
9485 if (GTK_CHECK_MENU_ITEM(widget)->active)
9486 compose->use_encryption = TRUE;
9488 compose->use_encryption = FALSE;
9491 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
9493 g_free(compose->privacy_system);
9495 compose->privacy_system = g_strdup(account->default_privacy_system);
9496 compose_update_privacy_system_menu_item(compose, warn);
9499 static void compose_toggle_ruler_cb(gpointer data, guint action,
9502 Compose *compose = (Compose *)data;
9504 if (GTK_CHECK_MENU_ITEM(widget)->active) {
9505 gtk_widget_show(compose->ruler_hbox);
9506 prefs_common.show_ruler = TRUE;
9508 gtk_widget_hide(compose->ruler_hbox);
9509 gtk_widget_queue_resize(compose->edit_vbox);
9510 prefs_common.show_ruler = FALSE;
9514 static void compose_attach_drag_received_cb (GtkWidget *widget,
9515 GdkDragContext *context,
9518 GtkSelectionData *data,
9523 Compose *compose = (Compose *)user_data;
9526 if (gdk_atom_name(data->type) &&
9527 !strcmp(gdk_atom_name(data->type), "text/uri-list")
9528 && gtk_drag_get_source_widget(context) !=
9529 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
9530 list = uri_list_extract_filenames((const gchar *)data->data);
9531 for (tmp = list; tmp != NULL; tmp = tmp->next) {
9532 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
9533 compose_attach_append
9534 (compose, (const gchar *)tmp->data,
9535 utf8_filename, NULL);
9536 g_free(utf8_filename);
9538 if (list) compose_changed_cb(NULL, compose);
9539 list_free_strings(list);
9541 } else if (gtk_drag_get_source_widget(context)
9542 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
9543 /* comes from our summaryview */
9544 SummaryView * summaryview = NULL;
9545 GSList * list = NULL, *cur = NULL;
9547 if (mainwindow_get_mainwindow())
9548 summaryview = mainwindow_get_mainwindow()->summaryview;
9551 list = summary_get_selected_msg_list(summaryview);
9553 for (cur = list; cur; cur = cur->next) {
9554 MsgInfo *msginfo = (MsgInfo *)cur->data;
9557 file = procmsg_get_message_file_full(msginfo,
9560 compose_attach_append(compose, (const gchar *)file,
9561 (const gchar *)file, "message/rfc822");
9569 static gboolean compose_drag_drop(GtkWidget *widget,
9570 GdkDragContext *drag_context,
9572 guint time, gpointer user_data)
9574 /* not handling this signal makes compose_insert_drag_received_cb
9579 static void compose_insert_drag_received_cb (GtkWidget *widget,
9580 GdkDragContext *drag_context,
9583 GtkSelectionData *data,
9588 Compose *compose = (Compose *)user_data;
9591 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
9593 if (gdk_atom_name(data->type) && !strcmp(gdk_atom_name(data->type), "text/uri-list")) {
9594 AlertValue val = G_ALERTDEFAULT;
9596 switch (prefs_common.compose_dnd_mode) {
9597 case COMPOSE_DND_ASK:
9598 val = alertpanel_full(_("Insert or attach?"),
9599 _("Do you want to insert the contents of the file(s) "
9600 "into the message body, or attach it to the email?"),
9601 GTK_STOCK_CANCEL, _("+_Insert"), _("_Attach"),
9602 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
9604 case COMPOSE_DND_INSERT:
9605 val = G_ALERTALTERNATE;
9607 case COMPOSE_DND_ATTACH:
9611 /* unexpected case */
9612 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
9615 if (val & G_ALERTDISABLE) {
9616 val &= ~G_ALERTDISABLE;
9617 /* remember what action to perform by default, only if we don't click Cancel */
9618 if (val == G_ALERTALTERNATE)
9619 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
9620 else if (val == G_ALERTOTHER)
9621 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
9624 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
9625 gtk_drag_finish(drag_context, FALSE, FALSE, time);
9627 } else if (val == G_ALERTOTHER) {
9628 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
9631 list = uri_list_extract_filenames((const gchar *)data->data);
9632 for (tmp = list; tmp != NULL; tmp = tmp->next) {
9633 compose_insert_file(compose, (const gchar *)tmp->data);
9635 list_free_strings(list);
9637 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9640 #if GTK_CHECK_VERSION(2, 8, 0)
9641 /* do nothing, handled by GTK */
9643 gchar *tmpfile = get_tmp_file();
9644 str_write_to_file((const gchar *)data->data, tmpfile);
9645 compose_insert_file(compose, tmpfile);
9648 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9652 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9655 static void compose_header_drag_received_cb (GtkWidget *widget,
9656 GdkDragContext *drag_context,
9659 GtkSelectionData *data,
9664 GtkEditable *entry = (GtkEditable *)user_data;
9665 gchar *email = (gchar *)data->data;
9667 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
9670 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
9671 gchar *decoded=g_new(gchar, strlen(email));
9674 email += strlen("mailto:");
9675 decode_uri(decoded, email); /* will fit */
9676 gtk_editable_delete_text(entry, 0, -1);
9677 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
9678 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9682 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9685 static void compose_toggle_return_receipt_cb(gpointer data, guint action,
9688 Compose *compose = (Compose *)data;
9690 if (GTK_CHECK_MENU_ITEM(widget)->active)
9691 compose->return_receipt = TRUE;
9693 compose->return_receipt = FALSE;
9696 static void compose_toggle_remove_refs_cb(gpointer data, guint action,
9699 Compose *compose = (Compose *)data;
9701 if (GTK_CHECK_MENU_ITEM(widget)->active)
9702 compose->remove_references = TRUE;
9704 compose->remove_references = FALSE;
9707 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
9709 ComposeHeaderEntry *headerentry)
9711 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
9712 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
9713 !(event->state & GDK_MODIFIER_MASK) &&
9714 (event->keyval == GDK_BackSpace) &&
9715 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
9716 gtk_container_remove
9717 (GTK_CONTAINER(headerentry->compose->header_table),
9718 headerentry->combo);
9719 gtk_container_remove
9720 (GTK_CONTAINER(headerentry->compose->header_table),
9721 headerentry->entry);
9722 headerentry->compose->header_list =
9723 g_slist_remove(headerentry->compose->header_list,
9725 g_free(headerentry);
9726 } else if (event->keyval == GDK_Tab) {
9727 if (headerentry->compose->header_last == headerentry) {
9728 /* Override default next focus, and give it to subject_entry
9729 * instead of notebook tabs
9731 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
9732 gtk_widget_grab_focus(headerentry->compose->subject_entry);
9739 static gboolean compose_headerentry_changed_cb(GtkWidget *entry,
9740 ComposeHeaderEntry *headerentry)
9742 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
9743 compose_create_header_entry(headerentry->compose);
9744 g_signal_handlers_disconnect_matched
9745 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
9746 0, 0, NULL, NULL, headerentry);
9748 /* Automatically scroll down */
9749 compose_show_first_last_header(headerentry->compose, FALSE);
9755 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
9757 GtkAdjustment *vadj;
9759 g_return_if_fail(compose);
9760 g_return_if_fail(GTK_IS_WIDGET(compose->header_table));
9761 g_return_if_fail(GTK_IS_VIEWPORT(compose->header_table->parent));
9763 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(compose->header_table->parent));
9764 gtk_adjustment_set_value(vadj, (show_first ? vadj->lower : vadj->upper));
9765 gtk_adjustment_changed(vadj);
9768 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
9769 const gchar *text, gint len, Compose *compose)
9771 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
9772 (G_OBJECT(compose->text), "paste_as_quotation"));
9775 g_return_if_fail(text != NULL);
9777 g_signal_handlers_block_by_func(G_OBJECT(buffer),
9778 G_CALLBACK(text_inserted),
9780 if (paste_as_quotation) {
9784 GtkTextIter start_iter;
9789 new_text = g_strndup(text, len);
9791 qmark = compose_quote_char_from_context(compose);
9793 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
9794 gtk_text_buffer_place_cursor(buffer, iter);
9796 pos = gtk_text_iter_get_offset(iter);
9798 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
9799 _("Quote format error at line %d."));
9800 quote_fmt_reset_vartable();
9802 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
9803 GINT_TO_POINTER(paste_as_quotation - 1));
9805 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
9806 gtk_text_buffer_place_cursor(buffer, iter);
9807 gtk_text_buffer_delete_mark(buffer, mark);
9809 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
9810 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
9811 compose_beautify_paragraph(compose, &start_iter, FALSE);
9812 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
9813 gtk_text_buffer_delete_mark(buffer, mark);
9815 if (strcmp(text, "\n") || compose->automatic_break
9816 || gtk_text_iter_starts_line(iter))
9817 gtk_text_buffer_insert(buffer, iter, text, len);
9819 debug_print("insert nowrap \\n\n");
9820 gtk_text_buffer_insert_with_tags_by_name(buffer,
9821 iter, text, len, "no_join", NULL);
9825 if (!paste_as_quotation) {
9826 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
9827 compose_beautify_paragraph(compose, iter, FALSE);
9828 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
9829 gtk_text_buffer_delete_mark(buffer, mark);
9832 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
9833 G_CALLBACK(text_inserted),
9835 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
9837 if (prefs_common.autosave &&
9838 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
9839 compose->draft_timeout_tag != -2 /* disabled while loading */)
9840 compose->draft_timeout_tag = g_timeout_add
9841 (500, (GtkFunction) compose_defer_auto_save_draft, compose);
9843 static gint compose_defer_auto_save_draft(Compose *compose)
9845 compose->draft_timeout_tag = -1;
9846 compose_draft_cb((gpointer)compose, COMPOSE_AUTO_SAVE, NULL);
9851 static void compose_check_all(Compose *compose)
9853 if (compose->gtkaspell)
9854 gtkaspell_check_all(compose->gtkaspell);
9857 static void compose_highlight_all(Compose *compose)
9859 if (compose->gtkaspell)
9860 gtkaspell_highlight_all(compose->gtkaspell);
9863 static void compose_check_backwards(Compose *compose)
9865 if (compose->gtkaspell)
9866 gtkaspell_check_backwards(compose->gtkaspell);
9868 GtkItemFactory *ifactory;
9869 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
9870 menu_set_sensitive(ifactory, "/Edit/Check backwards misspelled word", FALSE);
9871 menu_set_sensitive(ifactory, "/Edit/Forward to next misspelled word", FALSE);
9875 static void compose_check_forwards_go(Compose *compose)
9877 if (compose->gtkaspell)
9878 gtkaspell_check_forwards_go(compose->gtkaspell);
9880 GtkItemFactory *ifactory;
9881 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
9882 menu_set_sensitive(ifactory, "/Edit/Check backwards misspelled word", FALSE);
9883 menu_set_sensitive(ifactory, "/Edit/Forward to next misspelled word", FALSE);
9889 *\brief Guess originating forward account from MsgInfo and several
9890 * "common preference" settings. Return NULL if no guess.
9892 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
9894 PrefsAccount *account = NULL;
9896 g_return_val_if_fail(msginfo, NULL);
9897 g_return_val_if_fail(msginfo->folder, NULL);
9898 g_return_val_if_fail(msginfo->folder->prefs, NULL);
9900 if (msginfo->folder->prefs->enable_default_account)
9901 account = account_find_from_id(msginfo->folder->prefs->default_account);
9904 account = msginfo->folder->folder->account;
9906 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
9908 Xstrdup_a(to, msginfo->to, return NULL);
9909 extract_address(to);
9910 account = account_find_from_address(to);
9913 if (!account && prefs_common.forward_account_autosel) {
9915 if (!procheader_get_header_from_msginfo
9916 (msginfo, cc,sizeof cc , "Cc:")) {
9917 gchar *buf = cc + strlen("Cc:");
9918 extract_address(buf);
9919 account = account_find_from_address(buf);
9923 if (!account && prefs_common.forward_account_autosel) {
9924 gchar deliveredto[BUFFSIZE];
9925 if (!procheader_get_header_from_msginfo
9926 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
9927 gchar *buf = deliveredto + strlen("Delivered-To:");
9928 extract_address(buf);
9929 account = account_find_from_address(buf);
9936 gboolean compose_close(Compose *compose)
9940 if (!g_mutex_trylock(compose->mutex)) {
9941 /* we have to wait for the (possibly deferred by auto-save)
9942 * drafting to be done, before destroying the compose under
9944 debug_print("waiting for drafting to finish...\n");
9945 compose_allow_user_actions(compose, FALSE);
9946 g_timeout_add (500, (GSourceFunc) compose_close, compose);
9949 g_return_val_if_fail(compose, FALSE);
9950 gtkut_widget_get_uposition(compose->window, &x, &y);
9951 prefs_common.compose_x = x;
9952 prefs_common.compose_y = y;
9953 g_mutex_unlock(compose->mutex);
9954 compose_destroy(compose);
9959 * Add entry field for each address in list.
9960 * \param compose E-Mail composition object.
9961 * \param listAddress List of (formatted) E-Mail addresses.
9963 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
9968 addr = ( gchar * ) node->data;
9969 compose_entry_append( compose, addr, COMPOSE_TO );
9970 node = g_list_next( node );
9974 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
9975 guint action, gboolean opening_multiple)
9978 GSList *new_msglist = NULL;
9979 MsgInfo *tmp_msginfo = NULL;
9980 gboolean originally_enc = FALSE;
9981 Compose *compose = NULL;
9983 g_return_if_fail(msgview != NULL);
9985 g_return_if_fail(msginfo_list != NULL);
9987 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
9988 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
9989 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
9991 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
9992 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
9993 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
9994 orig_msginfo, mimeinfo);
9995 if (tmp_msginfo != NULL) {
9996 new_msglist = g_slist_append(NULL, tmp_msginfo);
9998 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
9999 tmp_msginfo->folder = orig_msginfo->folder;
10000 tmp_msginfo->msgnum = orig_msginfo->msgnum;
10001 if (orig_msginfo->tags)
10002 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
10007 if (!opening_multiple)
10008 body = messageview_get_selection(msgview);
10011 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
10012 procmsg_msginfo_free(tmp_msginfo);
10013 g_slist_free(new_msglist);
10015 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
10017 if (compose && originally_enc) {
10018 compose_force_encryption(compose, compose->account, FALSE);
10024 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
10027 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
10028 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
10029 GSList *cur = msginfo_list;
10030 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
10031 "messages. Opening the windows "
10032 "could take some time. Do you "
10033 "want to continue?"),
10034 g_slist_length(msginfo_list));
10035 if (g_slist_length(msginfo_list) > 9
10036 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
10037 != G_ALERTALTERNATE) {
10042 /* We'll open multiple compose windows */
10043 /* let the WM place the next windows */
10044 compose_force_window_origin = FALSE;
10045 for (; cur; cur = cur->next) {
10047 tmplist.data = cur->data;
10048 tmplist.next = NULL;
10049 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
10051 compose_force_window_origin = TRUE;
10053 /* forwarding multiple mails as attachments is done via a
10054 * single compose window */
10055 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
10059 void compose_set_position(Compose *compose, gint pos)
10061 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10063 gtkut_text_view_set_position(text, pos);
10066 gboolean compose_search_string(Compose *compose,
10067 const gchar *str, gboolean case_sens)
10069 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10071 return gtkut_text_view_search_string(text, str, case_sens);
10074 gboolean compose_search_string_backward(Compose *compose,
10075 const gchar *str, gboolean case_sens)
10077 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10079 return gtkut_text_view_search_string_backward(text, str, case_sens);
10082 /* allocate a msginfo structure and populate its data from a compose data structure */
10083 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
10085 MsgInfo *newmsginfo;
10087 gchar buf[BUFFSIZE];
10089 g_return_val_if_fail( compose != NULL, NULL );
10091 newmsginfo = procmsg_msginfo_new();
10094 get_rfc822_date(buf, sizeof(buf));
10095 newmsginfo->date = g_strdup(buf);
10098 if (compose->from_name) {
10099 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
10100 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
10104 if (compose->subject_entry)
10105 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
10107 /* to, cc, reply-to, newsgroups */
10108 for (list = compose->header_list; list; list = list->next) {
10109 gchar *header = gtk_editable_get_chars(
10111 GTK_COMBO(((ComposeHeaderEntry *)list->data)->combo)->entry), 0, -1);
10112 gchar *entry = gtk_editable_get_chars(
10113 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
10115 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
10116 if ( newmsginfo->to == NULL ) {
10117 newmsginfo->to = g_strdup(entry);
10118 } else if (entry && *entry) {
10119 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
10120 g_free(newmsginfo->to);
10121 newmsginfo->to = tmp;
10124 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
10125 if ( newmsginfo->cc == NULL ) {
10126 newmsginfo->cc = g_strdup(entry);
10127 } else if (entry && *entry) {
10128 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
10129 g_free(newmsginfo->cc);
10130 newmsginfo->cc = tmp;
10133 if ( strcasecmp(header,
10134 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
10135 if ( newmsginfo->newsgroups == NULL ) {
10136 newmsginfo->newsgroups = g_strdup(entry);
10137 } else if (entry && *entry) {
10138 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
10139 g_free(newmsginfo->newsgroups);
10140 newmsginfo->newsgroups = tmp;
10148 /* other data is unset */
10154 /* update compose's dictionaries from folder dict settings */
10155 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
10156 FolderItem *folder_item)
10158 g_return_if_fail(compose != NULL);
10160 if (compose->gtkaspell && folder_item && folder_item->prefs) {
10161 FolderItemPrefs *prefs = folder_item->prefs;
10163 if (prefs->enable_default_dictionary)
10164 gtkaspell_change_dict(compose->gtkaspell,
10165 prefs->default_dictionary, FALSE);
10166 if (folder_item->prefs->enable_default_alt_dictionary)
10167 gtkaspell_change_alt_dict(compose->gtkaspell,
10168 prefs->default_alt_dictionary);
10169 if (prefs->enable_default_dictionary
10170 || prefs->enable_default_alt_dictionary)
10171 compose_spell_menu_changed(compose);