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 (ISO-8859-_1)"),
732 ENC_ACTION(C_ISO_8859_1)},
733 {N_("/_Options/Character _encoding/Western European (ISO-8859-15)"),
734 ENC_ACTION(C_ISO_8859_15)},
735 {N_("/_Options/Character _encoding/Western European (Windows-1252)"),
736 ENC_ACTION(C_WINDOWS_1252)},
737 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
739 {N_("/_Options/Character _encoding/Central European (ISO-8859-_2)"),
740 ENC_ACTION(C_ISO_8859_2)},
741 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
743 {N_("/_Options/Character _encoding/_Baltic (ISO-8859-13)"),
744 ENC_ACTION(C_ISO_8859_13)},
745 {N_("/_Options/Character _encoding/Baltic (ISO-8859-_4)"),
746 ENC_ACTION(C_ISO_8859_4)},
747 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
749 {N_("/_Options/Character _encoding/Greek (ISO-8859-_7)"),
750 ENC_ACTION(C_ISO_8859_7)},
751 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
753 {N_("/_Options/Character _encoding/Hebrew (ISO-8859-_8)"),
754 ENC_ACTION(C_ISO_8859_8)},
755 {N_("/_Options/Character _encoding/Hebrew (Windows-1255)"),
756 ENC_ACTION(C_WINDOWS_1255)},
757 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
759 {N_("/_Options/Character _encoding/Arabic (ISO-8859-_6)"),
760 ENC_ACTION(C_ISO_8859_6)},
761 {N_("/_Options/Character _encoding/Arabic (Windows-1256)"),
762 ENC_ACTION(C_CP1256)},
763 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
765 {N_("/_Options/Character _encoding/Turkish (ISO-8859-_9)"),
766 ENC_ACTION(C_ISO_8859_9)},
767 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
769 {N_("/_Options/Character _encoding/Cyrillic (ISO-8859-_5)"),
770 ENC_ACTION(C_ISO_8859_5)},
771 {N_("/_Options/Character _encoding/Cyrillic (KOI8-_R)"),
772 ENC_ACTION(C_KOI8_R)},
773 {N_("/_Options/Character _encoding/Cyrillic (KOI8-U)"),
774 ENC_ACTION(C_KOI8_U)},
775 {N_("/_Options/Character _encoding/Cyrillic (Windows-1251)"),
776 ENC_ACTION(C_WINDOWS_1251)},
777 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
779 {N_("/_Options/Character _encoding/Japanese (ISO-2022-_JP)"),
780 ENC_ACTION(C_ISO_2022_JP)},
781 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
783 {N_("/_Options/Character _encoding/Simplified Chinese (_GB2312)"),
784 ENC_ACTION(C_GB2312)},
785 {N_("/_Options/Character _encoding/Simplified Chinese (GBK)"),
787 {N_("/_Options/Character _encoding/Traditional Chinese (_Big5)"),
789 {N_("/_Options/Character _encoding/Traditional Chinese (EUC-_TW)"),
790 ENC_ACTION(C_EUC_TW)},
791 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
793 {N_("/_Options/Character _encoding/Korean (EUC-_KR)"),
794 ENC_ACTION(C_EUC_KR)},
795 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
797 {N_("/_Options/Character _encoding/Thai (TIS-620)"),
798 ENC_ACTION(C_TIS_620)},
799 {N_("/_Options/Character _encoding/Thai (Windows-874)"),
800 ENC_ACTION(C_WINDOWS_874)},
802 {N_("/_Tools"), NULL, NULL, 0, "<Branch>"},
803 {N_("/_Tools/Show _ruler"), NULL, compose_toggle_ruler_cb, 0, "<ToggleItem>"},
804 {N_("/_Tools/_Address book"), "<shift><control>A", compose_address_cb , 0, NULL},
805 {N_("/_Tools/_Template"), NULL, NULL, 0, "<Branch>"},
806 {N_("/_Tools/Actio_ns"), NULL, NULL, 0, "<Branch>"},
807 {N_("/_Help"), NULL, NULL, 0, "<Branch>"},
808 {N_("/_Help/_About"), NULL, about_show, 0, NULL}
811 static GtkTargetEntry compose_mime_types[] =
813 {"text/uri-list", 0, 0},
814 {"UTF8_STRING", 0, 0},
818 static gboolean compose_put_existing_to_front(MsgInfo *info)
820 GList *compose_list = compose_get_compose_list();
824 for (elem = compose_list; elem != NULL && elem->data != NULL;
826 Compose *c = (Compose*)elem->data;
828 if (!c->targetinfo || !c->targetinfo->msgid ||
832 if (!strcmp(c->targetinfo->msgid, info->msgid)) {
833 gtkut_window_popup(c->window);
841 static GdkColor quote_color1 =
842 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
843 static GdkColor quote_color2 =
844 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
845 static GdkColor quote_color3 =
846 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
848 static GdkColor quote_bgcolor1 =
849 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
850 static GdkColor quote_bgcolor2 =
851 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
852 static GdkColor quote_bgcolor3 =
853 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
855 static GdkColor signature_color = {
862 static GdkColor uri_color = {
869 static void compose_create_tags(GtkTextView *text, Compose *compose)
871 GtkTextBuffer *buffer;
872 GdkColor black = {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
878 buffer = gtk_text_view_get_buffer(text);
880 if (prefs_common.enable_color) {
881 /* grab the quote colors, converting from an int to a GdkColor */
882 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
884 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
886 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
888 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_bgcol,
890 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_bgcol,
892 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_bgcol,
894 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
896 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
899 signature_color = quote_color1 = quote_color2 = quote_color3 =
900 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
903 if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
904 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
905 "foreground-gdk", "e_color1,
906 "paragraph-background-gdk", "e_bgcolor1,
908 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
909 "foreground-gdk", "e_color2,
910 "paragraph-background-gdk", "e_bgcolor2,
912 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
913 "foreground-gdk", "e_color3,
914 "paragraph-background-gdk", "e_bgcolor3,
917 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
918 "foreground-gdk", "e_color1,
920 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
921 "foreground-gdk", "e_color2,
923 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
924 "foreground-gdk", "e_color3,
928 compose->signature_tag = gtk_text_buffer_create_tag(buffer, "signature",
929 "foreground-gdk", &signature_color,
932 compose->uri_tag = gtk_text_buffer_create_tag(buffer, "link",
933 "foreground-gdk", &uri_color,
935 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
936 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
938 color[0] = quote_color1;
939 color[1] = quote_color2;
940 color[2] = quote_color3;
941 color[3] = quote_bgcolor1;
942 color[4] = quote_bgcolor2;
943 color[5] = quote_bgcolor3;
944 color[6] = signature_color;
945 color[7] = uri_color;
946 cmap = gdk_drawable_get_colormap(compose->window->window);
947 gdk_colormap_alloc_colors(cmap, color, 8, FALSE, TRUE, success);
949 for (i = 0; i < 8; i++) {
950 if (success[i] == FALSE) {
953 g_warning("Compose: color allocation failed.\n");
954 style = gtk_widget_get_style(GTK_WIDGET(text));
955 quote_color1 = quote_color2 = quote_color3 =
956 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 =
957 signature_color = uri_color = black;
962 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
963 GPtrArray *attach_files)
965 return compose_generic_new(account, mailto, NULL, attach_files, NULL);
968 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
970 return compose_generic_new(account, mailto, item, NULL, NULL);
973 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
975 return compose_generic_new( account, NULL, NULL, NULL, listAddress );
978 #define SCROLL_TO_CURSOR(compose) { \
979 GtkTextMark *cmark = gtk_text_buffer_get_insert( \
980 gtk_text_view_get_buffer( \
981 GTK_TEXT_VIEW(compose->text))); \
982 gtk_text_view_scroll_mark_onscreen( \
983 GTK_TEXT_VIEW(compose->text), \
987 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
988 GPtrArray *attach_files, GList *listAddress )
991 GtkTextView *textview;
992 GtkTextBuffer *textbuf;
994 GtkItemFactory *ifactory;
995 const gchar *subject_format = NULL;
996 const gchar *body_format = NULL;
998 if (item && item->prefs && item->prefs->enable_default_account)
999 account = account_find_from_id(item->prefs->default_account);
1001 if (!account) account = cur_account;
1002 g_return_val_if_fail(account != NULL, NULL);
1004 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
1006 ifactory = gtk_item_factory_from_widget(compose->menubar);
1008 compose->replyinfo = NULL;
1009 compose->fwdinfo = NULL;
1011 textview = GTK_TEXT_VIEW(compose->text);
1012 textbuf = gtk_text_view_get_buffer(textview);
1013 compose_create_tags(textview, compose);
1015 undo_block(compose->undostruct);
1017 compose_set_dictionaries_from_folder_prefs(compose, item);
1020 if (account->auto_sig)
1021 compose_insert_sig(compose, FALSE);
1022 gtk_text_buffer_get_start_iter(textbuf, &iter);
1023 gtk_text_buffer_place_cursor(textbuf, &iter);
1025 if (account->protocol != A_NNTP) {
1026 if (mailto && *mailto != '\0') {
1027 compose_entries_set(compose, mailto);
1029 } else if (item && item->prefs->enable_default_to) {
1030 compose_entry_append(compose, item->prefs->default_to, COMPOSE_TO);
1031 compose_entry_mark_default_to(compose, item->prefs->default_to);
1033 if (item && item->ret_rcpt) {
1034 menu_set_active(ifactory, "/Options/Request Return Receipt", TRUE);
1038 compose_entry_append(compose, mailto, COMPOSE_NEWSGROUPS);
1039 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1040 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS);
1043 * CLAWS: just don't allow return receipt request, even if the user
1044 * may want to send an email. simple but foolproof.
1046 menu_set_sensitive(ifactory, "/Options/Request Return Receipt", FALSE);
1048 compose_add_field_list( compose, listAddress );
1050 if (item && item->prefs && item->prefs->compose_with_format) {
1051 subject_format = item->prefs->compose_subject_format;
1052 body_format = item->prefs->compose_body_format;
1053 } else if (account->compose_with_format) {
1054 subject_format = account->compose_subject_format;
1055 body_format = account->compose_body_format;
1056 } else if (prefs_common.compose_with_format) {
1057 subject_format = prefs_common.compose_subject_format;
1058 body_format = prefs_common.compose_body_format;
1061 if (subject_format || body_format) {
1062 MsgInfo* dummyinfo = NULL;
1065 && *subject_format != '\0' )
1067 gchar *subject = NULL;
1071 dummyinfo = compose_msginfo_new_from_compose(compose);
1073 /* decode \-escape sequences in the internal representation of the quote format */
1074 tmp = malloc(strlen(subject_format)+1);
1075 pref_get_unescaped_pref(tmp, subject_format);
1077 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1079 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account,
1080 compose->gtkaspell);
1082 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account);
1084 quote_fmt_scan_string(tmp);
1087 buf = quote_fmt_get_buffer();
1089 alertpanel_error(_("New message subject format error."));
1091 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1092 quote_fmt_reset_vartable();
1099 && *body_format != '\0' )
1102 GtkTextBuffer *buffer;
1103 GtkTextIter start, end;
1106 if ( dummyinfo == NULL )
1107 dummyinfo = compose_msginfo_new_from_compose(compose);
1109 text = GTK_TEXT_VIEW(compose->text);
1110 buffer = gtk_text_view_get_buffer(text);
1111 gtk_text_buffer_get_start_iter(buffer, &start);
1112 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1113 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1115 compose_quote_fmt(compose, dummyinfo,
1117 NULL, tmp, FALSE, TRUE,
1118 _("New message body format error at line %d."));
1119 quote_fmt_reset_vartable();
1124 procmsg_msginfo_free( dummyinfo );
1131 for (i = 0; i < attach_files->len; i++) {
1132 file = g_ptr_array_index(attach_files, i);
1133 compose_attach_append(compose, file, file, NULL);
1137 compose_show_first_last_header(compose, TRUE);
1139 /* Set save folder */
1140 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1141 gchar *folderidentifier;
1143 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
1144 folderidentifier = folder_item_get_identifier(item);
1145 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
1146 g_free(folderidentifier);
1149 gtk_widget_grab_focus(compose->header_last->entry);
1151 undo_unblock(compose->undostruct);
1153 if (prefs_common.auto_exteditor)
1154 compose_exec_ext_editor(compose);
1156 compose->draft_timeout_tag = -1;
1157 SCROLL_TO_CURSOR(compose);
1159 compose->modified = FALSE;
1160 compose_set_title(compose);
1164 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1165 gboolean override_pref)
1167 gchar *privacy = NULL;
1169 g_return_if_fail(compose != NULL);
1170 g_return_if_fail(account != NULL);
1172 if (override_pref == FALSE && account->default_encrypt_reply == FALSE)
1175 if (account->default_privacy_system
1176 && strlen(account->default_privacy_system)) {
1177 privacy = account->default_privacy_system;
1179 GSList *privacy_avail = privacy_get_system_ids();
1180 if (privacy_avail && g_slist_length(privacy_avail)) {
1181 privacy = (gchar *)(privacy_avail->data);
1184 if (privacy != NULL) {
1185 if (compose->privacy_system == NULL)
1186 compose->privacy_system = g_strdup(privacy);
1187 compose_update_privacy_system_menu_item(compose, FALSE);
1188 compose_use_encryption(compose, TRUE);
1192 static void compose_force_signing(Compose *compose, PrefsAccount *account)
1194 gchar *privacy = NULL;
1196 if (account->default_privacy_system
1197 && strlen(account->default_privacy_system)) {
1198 privacy = account->default_privacy_system;
1200 GSList *privacy_avail = privacy_get_system_ids();
1201 if (privacy_avail && g_slist_length(privacy_avail)) {
1202 privacy = (gchar *)(privacy_avail->data);
1205 if (privacy != NULL) {
1206 if (compose->privacy_system == NULL)
1207 compose->privacy_system = g_strdup(privacy);
1208 compose_update_privacy_system_menu_item(compose, FALSE);
1209 compose_use_signing(compose, TRUE);
1213 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1217 Compose *compose = NULL;
1218 GtkItemFactory *ifactory = NULL;
1220 g_return_val_if_fail(msginfo_list != NULL, NULL);
1222 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1223 g_return_val_if_fail(msginfo != NULL, NULL);
1225 list_len = g_slist_length(msginfo_list);
1229 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1230 FALSE, prefs_common.default_reply_list, FALSE, body);
1232 case COMPOSE_REPLY_WITH_QUOTE:
1233 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1234 FALSE, prefs_common.default_reply_list, FALSE, body);
1236 case COMPOSE_REPLY_WITHOUT_QUOTE:
1237 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1238 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1240 case COMPOSE_REPLY_TO_SENDER:
1241 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1242 FALSE, FALSE, TRUE, body);
1244 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1245 compose = compose_followup_and_reply_to(msginfo,
1246 COMPOSE_QUOTE_CHECK,
1247 FALSE, FALSE, body);
1249 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1250 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1251 FALSE, FALSE, TRUE, body);
1253 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1254 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1255 FALSE, FALSE, TRUE, NULL);
1257 case COMPOSE_REPLY_TO_ALL:
1258 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1259 TRUE, FALSE, FALSE, body);
1261 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1262 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1263 TRUE, FALSE, FALSE, body);
1265 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1266 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1267 TRUE, FALSE, FALSE, NULL);
1269 case COMPOSE_REPLY_TO_LIST:
1270 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1271 FALSE, TRUE, FALSE, body);
1273 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1274 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1275 FALSE, TRUE, FALSE, body);
1277 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1278 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1279 FALSE, TRUE, FALSE, NULL);
1281 case COMPOSE_FORWARD:
1282 if (prefs_common.forward_as_attachment) {
1283 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1286 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1290 case COMPOSE_FORWARD_INLINE:
1291 /* check if we reply to more than one Message */
1292 if (list_len == 1) {
1293 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1296 /* more messages FALL THROUGH */
1297 case COMPOSE_FORWARD_AS_ATTACH:
1298 compose = compose_forward_multiple(NULL, msginfo_list);
1300 case COMPOSE_REDIRECT:
1301 compose = compose_redirect(NULL, msginfo, FALSE);
1304 g_warning("compose_reply_mode(): invalid Compose Mode: %d\n", mode);
1307 ifactory = gtk_item_factory_from_widget(compose->menubar);
1309 compose->rmode = mode;
1310 switch (compose->rmode) {
1312 case COMPOSE_REPLY_WITH_QUOTE:
1313 case COMPOSE_REPLY_WITHOUT_QUOTE:
1314 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1315 debug_print("reply mode Normal\n");
1316 menu_set_active(ifactory, "/Options/Reply mode/Normal", TRUE);
1317 compose_reply_change_mode(compose, COMPOSE_REPLY, NULL); /* force update */
1319 case COMPOSE_REPLY_TO_SENDER:
1320 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1321 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1322 debug_print("reply mode Sender\n");
1323 menu_set_active(ifactory, "/Options/Reply mode/Sender", TRUE);
1325 case COMPOSE_REPLY_TO_ALL:
1326 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1327 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1328 debug_print("reply mode All\n");
1329 menu_set_active(ifactory, "/Options/Reply mode/All", TRUE);
1331 case COMPOSE_REPLY_TO_LIST:
1332 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1333 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1334 debug_print("reply mode List\n");
1335 menu_set_active(ifactory, "/Options/Reply mode/Mailing-list", TRUE);
1343 static Compose *compose_reply(MsgInfo *msginfo,
1344 ComposeQuoteMode quote_mode,
1350 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1351 to_sender, FALSE, body);
1354 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1355 ComposeQuoteMode quote_mode,
1360 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1361 to_sender, TRUE, body);
1364 static void compose_extract_original_charset(Compose *compose)
1366 MsgInfo *info = NULL;
1367 if (compose->replyinfo) {
1368 info = compose->replyinfo;
1369 } else if (compose->fwdinfo) {
1370 info = compose->fwdinfo;
1371 } else if (compose->targetinfo) {
1372 info = compose->targetinfo;
1375 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1376 MimeInfo *partinfo = mimeinfo;
1377 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1378 partinfo = procmime_mimeinfo_next(partinfo);
1380 compose->orig_charset =
1381 g_strdup(procmime_mimeinfo_get_parameter(
1382 partinfo, "charset"));
1384 procmime_mimeinfo_free_all(mimeinfo);
1388 #define SIGNAL_BLOCK(buffer) { \
1389 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1390 G_CALLBACK(compose_changed_cb), \
1392 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1393 G_CALLBACK(text_inserted), \
1397 #define SIGNAL_UNBLOCK(buffer) { \
1398 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1399 G_CALLBACK(compose_changed_cb), \
1401 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1402 G_CALLBACK(text_inserted), \
1406 static Compose *compose_generic_reply(MsgInfo *msginfo,
1407 ComposeQuoteMode quote_mode,
1408 gboolean to_all, gboolean to_ml,
1410 gboolean followup_and_reply_to,
1413 GtkItemFactory *ifactory;
1415 PrefsAccount *account = NULL;
1416 GtkTextView *textview;
1417 GtkTextBuffer *textbuf;
1418 gboolean quote = FALSE;
1419 const gchar *qmark = NULL;
1420 const gchar *body_fmt = NULL;
1422 g_return_val_if_fail(msginfo != NULL, NULL);
1423 g_return_val_if_fail(msginfo->folder != NULL, NULL);
1425 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1427 g_return_val_if_fail(account != NULL, NULL);
1429 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1431 compose->updating = TRUE;
1433 ifactory = gtk_item_factory_from_widget(compose->menubar);
1435 menu_set_active(ifactory, "/Options/Remove references", FALSE);
1436 menu_set_sensitive(ifactory, "/Options/Remove references", TRUE);
1438 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1439 if (!compose->replyinfo)
1440 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1442 compose_extract_original_charset(compose);
1444 if (msginfo->folder && msginfo->folder->ret_rcpt)
1445 menu_set_active(ifactory, "/Options/Request Return Receipt", TRUE);
1447 /* Set save folder */
1448 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1449 gchar *folderidentifier;
1451 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1452 folderidentifier = folder_item_get_identifier(msginfo->folder);
1453 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
1454 g_free(folderidentifier);
1457 if (compose_parse_header(compose, msginfo) < 0) return NULL;
1459 textview = (GTK_TEXT_VIEW(compose->text));
1460 textbuf = gtk_text_view_get_buffer(textview);
1461 compose_create_tags(textview, compose);
1463 undo_block(compose->undostruct);
1465 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1468 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1469 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1470 /* use the reply format of folder (if enabled), or the account's one
1471 (if enabled) or fallback to the global reply format, which is always
1472 enabled (even if empty), and use the relevant quotemark */
1474 if (msginfo->folder && msginfo->folder->prefs &&
1475 msginfo->folder->prefs->reply_with_format) {
1476 qmark = msginfo->folder->prefs->reply_quotemark;
1477 body_fmt = msginfo->folder->prefs->reply_body_format;
1479 } else if (account->reply_with_format) {
1480 qmark = account->reply_quotemark;
1481 body_fmt = account->reply_body_format;
1484 qmark = prefs_common.quotemark;
1485 body_fmt = prefs_common.quotefmt;
1490 /* empty quotemark is not allowed */
1491 if (qmark == NULL || *qmark == '\0')
1493 compose_quote_fmt(compose, compose->replyinfo,
1494 body_fmt, qmark, body, FALSE, TRUE,
1495 _("Message reply format error at line %d."));
1496 quote_fmt_reset_vartable();
1499 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1500 compose_force_encryption(compose, account, FALSE);
1503 SIGNAL_BLOCK(textbuf);
1505 if (account->auto_sig)
1506 compose_insert_sig(compose, FALSE);
1508 compose_wrap_all(compose);
1510 SIGNAL_UNBLOCK(textbuf);
1512 gtk_widget_grab_focus(compose->text);
1514 undo_unblock(compose->undostruct);
1516 if (prefs_common.auto_exteditor)
1517 compose_exec_ext_editor(compose);
1519 compose->modified = FALSE;
1520 compose_set_title(compose);
1522 compose->updating = FALSE;
1523 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1524 SCROLL_TO_CURSOR(compose);
1526 if (compose->deferred_destroy) {
1527 compose_destroy(compose);
1534 #define INSERT_FW_HEADER(var, hdr) \
1535 if (msginfo->var && *msginfo->var) { \
1536 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1537 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1538 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1541 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1542 gboolean as_attach, const gchar *body,
1543 gboolean no_extedit,
1547 GtkTextView *textview;
1548 GtkTextBuffer *textbuf;
1551 g_return_val_if_fail(msginfo != NULL, NULL);
1552 g_return_val_if_fail(msginfo->folder != NULL, NULL);
1555 !(account = compose_guess_forward_account_from_msginfo
1557 account = cur_account;
1559 compose = compose_create(account, msginfo->folder, COMPOSE_FORWARD, batch);
1561 compose->updating = TRUE;
1562 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1563 if (!compose->fwdinfo)
1564 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1566 compose_extract_original_charset(compose);
1568 if (msginfo->subject && *msginfo->subject) {
1569 gchar *buf, *buf2, *p;
1571 buf = p = g_strdup(msginfo->subject);
1572 p += subject_get_prefix_length(p);
1573 memmove(buf, p, strlen(p) + 1);
1575 buf2 = g_strdup_printf("Fw: %s", buf);
1576 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1582 textview = GTK_TEXT_VIEW(compose->text);
1583 textbuf = gtk_text_view_get_buffer(textview);
1584 compose_create_tags(textview, compose);
1586 undo_block(compose->undostruct);
1590 msgfile = procmsg_get_message_file(msginfo);
1591 if (!is_file_exist(msgfile))
1592 g_warning("%s: file not exist\n", msgfile);
1594 compose_attach_append(compose, msgfile, msgfile,
1599 const gchar *qmark = NULL;
1600 const gchar *body_fmt = prefs_common.fw_quotefmt;
1601 MsgInfo *full_msginfo;
1603 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1605 full_msginfo = procmsg_msginfo_copy(msginfo);
1607 /* use the forward format of folder (if enabled), or the account's one
1608 (if enabled) or fallback to the global forward format, which is always
1609 enabled (even if empty), and use the relevant quotemark */
1610 if (msginfo->folder && msginfo->folder->prefs &&
1611 msginfo->folder->prefs->forward_with_format) {
1612 qmark = msginfo->folder->prefs->forward_quotemark;
1613 body_fmt = msginfo->folder->prefs->forward_body_format;
1615 } else if (account->forward_with_format) {
1616 qmark = account->forward_quotemark;
1617 body_fmt = account->forward_body_format;
1620 qmark = prefs_common.fw_quotemark;
1621 body_fmt = prefs_common.fw_quotefmt;
1624 /* empty quotemark is not allowed */
1625 if (qmark == NULL || *qmark == '\0')
1628 compose_quote_fmt(compose, full_msginfo,
1629 body_fmt, qmark, body, FALSE, TRUE,
1630 _("Message forward format error at line %d."));
1631 quote_fmt_reset_vartable();
1632 compose_attach_parts(compose, msginfo);
1634 procmsg_msginfo_free(full_msginfo);
1637 SIGNAL_BLOCK(textbuf);
1639 if (account->auto_sig)
1640 compose_insert_sig(compose, FALSE);
1642 compose_wrap_all(compose);
1644 SIGNAL_UNBLOCK(textbuf);
1646 gtk_text_buffer_get_start_iter(textbuf, &iter);
1647 gtk_text_buffer_place_cursor(textbuf, &iter);
1649 gtk_widget_grab_focus(compose->header_last->entry);
1651 if (!no_extedit && prefs_common.auto_exteditor)
1652 compose_exec_ext_editor(compose);
1655 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1656 gchar *folderidentifier;
1658 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1659 folderidentifier = folder_item_get_identifier(msginfo->folder);
1660 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
1661 g_free(folderidentifier);
1664 undo_unblock(compose->undostruct);
1666 compose->modified = FALSE;
1667 compose_set_title(compose);
1669 compose->updating = FALSE;
1670 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1671 SCROLL_TO_CURSOR(compose);
1673 if (compose->deferred_destroy) {
1674 compose_destroy(compose);
1681 #undef INSERT_FW_HEADER
1683 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1686 GtkTextView *textview;
1687 GtkTextBuffer *textbuf;
1691 gboolean single_mail = TRUE;
1693 g_return_val_if_fail(msginfo_list != NULL, NULL);
1695 if (g_slist_length(msginfo_list) > 1)
1696 single_mail = FALSE;
1698 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1699 if (((MsgInfo *)msginfo->data)->folder == NULL)
1702 /* guess account from first selected message */
1704 !(account = compose_guess_forward_account_from_msginfo
1705 (msginfo_list->data)))
1706 account = cur_account;
1708 g_return_val_if_fail(account != NULL, NULL);
1710 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1711 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1712 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1715 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1717 compose->updating = TRUE;
1719 textview = GTK_TEXT_VIEW(compose->text);
1720 textbuf = gtk_text_view_get_buffer(textview);
1721 compose_create_tags(textview, compose);
1723 undo_block(compose->undostruct);
1724 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1725 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
1727 if (!is_file_exist(msgfile))
1728 g_warning("%s: file not exist\n", msgfile);
1730 compose_attach_append(compose, msgfile, msgfile,
1736 MsgInfo *info = (MsgInfo *)msginfo_list->data;
1737 if (info->subject && *info->subject) {
1738 gchar *buf, *buf2, *p;
1740 buf = p = g_strdup(info->subject);
1741 p += subject_get_prefix_length(p);
1742 memmove(buf, p, strlen(p) + 1);
1744 buf2 = g_strdup_printf("Fw: %s", buf);
1745 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1751 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
1752 _("Fw: multiple emails"));
1755 SIGNAL_BLOCK(textbuf);
1757 if (account->auto_sig)
1758 compose_insert_sig(compose, FALSE);
1760 compose_wrap_all(compose);
1762 SIGNAL_UNBLOCK(textbuf);
1764 gtk_text_buffer_get_start_iter(textbuf, &iter);
1765 gtk_text_buffer_place_cursor(textbuf, &iter);
1767 gtk_widget_grab_focus(compose->header_last->entry);
1768 undo_unblock(compose->undostruct);
1769 compose->modified = FALSE;
1770 compose_set_title(compose);
1772 compose->updating = FALSE;
1773 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1774 SCROLL_TO_CURSOR(compose);
1776 if (compose->deferred_destroy) {
1777 compose_destroy(compose);
1784 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
1786 GtkTextIter start = *iter;
1787 GtkTextIter end_iter;
1788 int start_pos = gtk_text_iter_get_offset(&start);
1790 if (!compose->account->sig_sep)
1793 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
1794 start_pos+strlen(compose->account->sig_sep));
1796 /* check sig separator */
1797 str = gtk_text_iter_get_text(&start, &end_iter);
1798 if (!strcmp(str, compose->account->sig_sep)) {
1800 /* check end of line (\n) */
1801 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
1802 start_pos+strlen(compose->account->sig_sep));
1803 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
1804 start_pos+strlen(compose->account->sig_sep)+1);
1805 tmp = gtk_text_iter_get_text(&start, &end_iter);
1806 if (!strcmp(tmp,"\n")) {
1818 static void compose_colorize_signature(Compose *compose)
1820 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
1822 GtkTextIter end_iter;
1823 gtk_text_buffer_get_start_iter(buffer, &iter);
1824 while (gtk_text_iter_forward_line(&iter))
1825 if (compose_is_sig_separator(compose, buffer, &iter)) {
1826 gtk_text_buffer_get_end_iter(buffer, &end_iter);
1827 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
1831 #define BLOCK_WRAP() { \
1832 prev_autowrap = compose->autowrap; \
1833 buffer = gtk_text_view_get_buffer( \
1834 GTK_TEXT_VIEW(compose->text)); \
1835 compose->autowrap = FALSE; \
1837 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1838 G_CALLBACK(compose_changed_cb), \
1840 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1841 G_CALLBACK(text_inserted), \
1844 #define UNBLOCK_WRAP() { \
1845 compose->autowrap = prev_autowrap; \
1846 if (compose->autowrap) { \
1847 gint old = compose->draft_timeout_tag; \
1848 compose->draft_timeout_tag = -2; \
1849 compose_wrap_all(compose); \
1850 compose->draft_timeout_tag = old; \
1853 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1854 G_CALLBACK(compose_changed_cb), \
1856 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1857 G_CALLBACK(text_inserted), \
1861 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
1863 Compose *compose = NULL;
1864 PrefsAccount *account = NULL;
1865 GtkTextView *textview;
1866 GtkTextBuffer *textbuf;
1870 gchar buf[BUFFSIZE];
1871 gboolean use_signing = FALSE;
1872 gboolean use_encryption = FALSE;
1873 gchar *privacy_system = NULL;
1874 int priority = PRIORITY_NORMAL;
1875 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
1877 g_return_val_if_fail(msginfo != NULL, NULL);
1878 g_return_val_if_fail(msginfo->folder != NULL, NULL);
1880 if (compose_put_existing_to_front(msginfo)) {
1884 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
1885 folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
1886 gchar queueheader_buf[BUFFSIZE];
1889 /* Select Account from queue headers */
1890 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1891 sizeof(queueheader_buf), "X-Claws-Account-Id:")) {
1892 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
1893 account = account_find_from_id(id);
1895 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1896 sizeof(queueheader_buf), "X-Sylpheed-Account-Id:")) {
1897 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
1898 account = account_find_from_id(id);
1900 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1901 sizeof(queueheader_buf), "NAID:")) {
1902 id = atoi(&queueheader_buf[strlen("NAID:")]);
1903 account = account_find_from_id(id);
1905 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1906 sizeof(queueheader_buf), "MAID:")) {
1907 id = atoi(&queueheader_buf[strlen("MAID:")]);
1908 account = account_find_from_id(id);
1910 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1911 sizeof(queueheader_buf), "S:")) {
1912 account = account_find_from_address(queueheader_buf);
1914 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1915 sizeof(queueheader_buf), "X-Claws-Sign:")) {
1916 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
1917 use_signing = param;
1920 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1921 sizeof(queueheader_buf), "X-Sylpheed-Sign:")) {
1922 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
1923 use_signing = param;
1926 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1927 sizeof(queueheader_buf), "X-Claws-Encrypt:")) {
1928 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
1929 use_encryption = param;
1931 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1932 sizeof(queueheader_buf), "X-Sylpheed-Encrypt:")) {
1933 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
1934 use_encryption = param;
1936 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1937 sizeof(queueheader_buf), "X-Claws-Privacy-System:")) {
1938 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
1940 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1941 sizeof(queueheader_buf), "X-Sylpheed-Privacy-System:")) {
1942 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
1944 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1945 sizeof(queueheader_buf), "X-Priority: ")) {
1946 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
1949 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1950 sizeof(queueheader_buf), "RMID:")) {
1951 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
1952 if (tokens[0] && tokens[1] && tokens[2]) {
1953 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
1954 if (orig_item != NULL) {
1955 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
1960 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1961 sizeof(queueheader_buf), "FMID:")) {
1962 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
1963 if (tokens[0] && tokens[1] && tokens[2]) {
1964 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
1965 if (orig_item != NULL) {
1966 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
1972 account = msginfo->folder->folder->account;
1975 if (!account && prefs_common.reedit_account_autosel) {
1976 gchar from[BUFFSIZE];
1977 if (!procheader_get_header_from_msginfo(msginfo, from, sizeof(from), "FROM:")) {
1978 extract_address(from);
1979 account = account_find_from_address(from);
1983 account = cur_account;
1985 g_return_val_if_fail(account != NULL, NULL);
1987 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
1989 compose->replyinfo = replyinfo;
1990 compose->fwdinfo = fwdinfo;
1992 compose->updating = TRUE;
1993 compose->priority = priority;
1995 if (privacy_system != NULL) {
1996 compose->privacy_system = privacy_system;
1997 compose_use_signing(compose, use_signing);
1998 compose_use_encryption(compose, use_encryption);
1999 compose_update_privacy_system_menu_item(compose, FALSE);
2001 activate_privacy_system(compose, account, FALSE);
2004 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2006 compose_extract_original_charset(compose);
2008 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2009 folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
2010 gchar queueheader_buf[BUFFSIZE];
2012 /* Set message save folder */
2013 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "SCF:")) {
2016 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2017 gtk_editable_delete_text(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
2018 gtk_editable_insert_text(GTK_EDITABLE(compose->savemsg_entry), &queueheader_buf[4], strlen(&queueheader_buf[4]), &startpos);
2020 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "RRCPT:")) {
2021 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2023 GtkItemFactory *ifactory;
2024 ifactory = gtk_item_factory_from_widget(compose->menubar);
2025 menu_set_active(ifactory, "/Options/Request Return Receipt", TRUE);
2030 if (compose_parse_header(compose, msginfo) < 0) {
2031 compose->updating = FALSE;
2032 compose_destroy(compose);
2035 compose_reedit_set_entry(compose, msginfo);
2037 textview = GTK_TEXT_VIEW(compose->text);
2038 textbuf = gtk_text_view_get_buffer(textview);
2039 compose_create_tags(textview, compose);
2041 mark = gtk_text_buffer_get_insert(textbuf);
2042 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2044 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2045 G_CALLBACK(compose_changed_cb),
2048 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2049 fp = procmime_get_first_encrypted_text_content(msginfo);
2051 compose_force_encryption(compose, account, TRUE);
2054 fp = procmime_get_first_text_content(msginfo);
2057 g_warning("Can't get text part\n");
2061 gboolean prev_autowrap = compose->autowrap;
2062 GtkTextBuffer *buffer = textbuf;
2064 while (fgets(buf, sizeof(buf), fp) != NULL) {
2066 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2072 compose_attach_parts(compose, msginfo);
2074 compose_colorize_signature(compose);
2076 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2077 G_CALLBACK(compose_changed_cb),
2080 gtk_widget_grab_focus(compose->text);
2082 if (prefs_common.auto_exteditor) {
2083 compose_exec_ext_editor(compose);
2085 compose->modified = FALSE;
2086 compose_set_title(compose);
2088 compose->updating = FALSE;
2089 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2090 SCROLL_TO_CURSOR(compose);
2092 if (compose->deferred_destroy) {
2093 compose_destroy(compose);
2097 compose->sig_str = compose_get_signature_str(compose);
2102 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2107 GtkItemFactory *ifactory;
2110 g_return_val_if_fail(msginfo != NULL, NULL);
2113 account = account_get_reply_account(msginfo,
2114 prefs_common.reply_account_autosel);
2115 g_return_val_if_fail(account != NULL, NULL);
2117 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2119 compose->updating = TRUE;
2121 ifactory = gtk_item_factory_from_widget(compose->menubar);
2122 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2123 compose->replyinfo = NULL;
2124 compose->fwdinfo = NULL;
2126 compose_show_first_last_header(compose, TRUE);
2128 gtk_widget_grab_focus(compose->header_last->entry);
2130 filename = procmsg_get_message_file(msginfo);
2132 if (filename == NULL) {
2133 compose->updating = FALSE;
2134 compose_destroy(compose);
2139 compose->redirect_filename = filename;
2141 /* Set save folder */
2142 item = msginfo->folder;
2143 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2144 gchar *folderidentifier;
2146 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
2147 folderidentifier = folder_item_get_identifier(item);
2148 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
2149 g_free(folderidentifier);
2152 compose_attach_parts(compose, msginfo);
2154 if (msginfo->subject)
2155 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2157 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2159 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2160 _("Message redirect format error at line %d."));
2161 quote_fmt_reset_vartable();
2162 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2164 compose_colorize_signature(compose);
2166 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
2167 menu_set_sensitive(ifactory, "/Add...", FALSE);
2168 menu_set_sensitive(ifactory, "/Remove", FALSE);
2169 menu_set_sensitive(ifactory, "/Properties...", FALSE);
2171 ifactory = gtk_item_factory_from_widget(compose->menubar);
2172 menu_set_sensitive(ifactory, "/Message/Save", FALSE);
2173 menu_set_sensitive(ifactory, "/Message/Insert file", FALSE);
2174 menu_set_sensitive(ifactory, "/Message/Attach file", FALSE);
2175 menu_set_sensitive(ifactory, "/Message/Insert signature", FALSE);
2176 menu_set_sensitive(ifactory, "/Edit", FALSE);
2177 menu_set_sensitive(ifactory, "/Options", FALSE);
2178 menu_set_sensitive(ifactory, "/Tools/Show ruler", FALSE);
2179 menu_set_sensitive(ifactory, "/Tools/Actions", FALSE);
2181 if (compose->toolbar->draft_btn)
2182 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2183 if (compose->toolbar->insert_btn)
2184 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2185 if (compose->toolbar->attach_btn)
2186 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2187 if (compose->toolbar->sig_btn)
2188 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2189 if (compose->toolbar->exteditor_btn)
2190 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2191 if (compose->toolbar->linewrap_current_btn)
2192 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2193 if (compose->toolbar->linewrap_all_btn)
2194 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2196 compose->modified = FALSE;
2197 compose_set_title(compose);
2198 compose->updating = FALSE;
2199 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2200 SCROLL_TO_CURSOR(compose);
2202 if (compose->deferred_destroy) {
2203 compose_destroy(compose);
2210 GList *compose_get_compose_list(void)
2212 return compose_list;
2215 void compose_entry_append(Compose *compose, const gchar *address,
2216 ComposeEntryType type)
2218 const gchar *header;
2220 gboolean in_quote = FALSE;
2221 if (!address || *address == '\0') return;
2228 header = N_("Bcc:");
2230 case COMPOSE_REPLYTO:
2231 header = N_("Reply-To:");
2233 case COMPOSE_NEWSGROUPS:
2234 header = N_("Newsgroups:");
2236 case COMPOSE_FOLLOWUPTO:
2237 header = N_( "Followup-To:");
2244 header = prefs_common_translated_header_name(header);
2246 cur = begin = (gchar *)address;
2248 /* we separate the line by commas, but not if we're inside a quoted
2250 while (*cur != '\0') {
2252 in_quote = !in_quote;
2253 if (*cur == ',' && !in_quote) {
2254 gchar *tmp = g_strdup(begin);
2256 tmp[cur-begin]='\0';
2259 while (*tmp == ' ' || *tmp == '\t')
2261 compose_add_header_entry(compose, header, tmp);
2268 gchar *tmp = g_strdup(begin);
2270 tmp[cur-begin]='\0';
2273 while (*tmp == ' ' || *tmp == '\t')
2275 compose_add_header_entry(compose, header, tmp);
2280 static void compose_entry_mark_default_to(Compose *compose, const gchar *mailto)
2282 static GdkColor yellow;
2283 static GdkColor black;
2284 static gboolean yellow_initialised = FALSE;
2288 if (!yellow_initialised) {
2289 gdk_color_parse("#f5f6be", &yellow);
2290 gdk_color_parse("#000000", &black);
2291 yellow_initialised = gdk_colormap_alloc_color(
2292 gdk_colormap_get_system(), &yellow, FALSE, TRUE);
2293 yellow_initialised &= gdk_colormap_alloc_color(
2294 gdk_colormap_get_system(), &black, FALSE, TRUE);
2297 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2298 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2299 if (gtk_entry_get_text(entry) &&
2300 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2301 if (yellow_initialised) {
2302 gtk_widget_modify_base(
2303 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2304 GTK_STATE_NORMAL, &yellow);
2305 gtk_widget_modify_text(
2306 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2307 GTK_STATE_NORMAL, &black);
2313 void compose_toolbar_cb(gint action, gpointer data)
2315 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2316 Compose *compose = (Compose*)toolbar_item->parent;
2318 g_return_if_fail(compose != NULL);
2322 compose_send_cb(compose, 0, NULL);
2325 compose_send_later_cb(compose, 0, NULL);
2328 compose_draft_cb(compose, COMPOSE_QUIT_EDITING, NULL);
2331 compose_insert_file_cb(compose, 0, NULL);
2334 compose_attach_cb(compose, 0, NULL);
2337 compose_insert_sig(compose, FALSE);
2340 compose_ext_editor_cb(compose, 0, NULL);
2342 case A_LINEWRAP_CURRENT:
2343 compose_beautify_paragraph(compose, NULL, TRUE);
2345 case A_LINEWRAP_ALL:
2346 compose_wrap_all_full(compose, TRUE);
2349 compose_address_cb(compose, 0, NULL);
2352 case A_CHECK_SPELLING:
2353 compose_check_all(compose);
2361 static void compose_entries_set(Compose *compose, const gchar *mailto)
2365 gchar *subject = NULL;
2369 gchar *attach = NULL;
2371 scan_mailto_url(mailto, &to, &cc, NULL, &subject, &body, &attach);
2374 compose_entry_append(compose, to, COMPOSE_TO);
2376 compose_entry_append(compose, cc, COMPOSE_CC);
2378 if (!g_utf8_validate (subject, -1, NULL)) {
2379 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2380 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2383 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2387 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2388 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2391 gboolean prev_autowrap = compose->autowrap;
2393 compose->autowrap = FALSE;
2395 mark = gtk_text_buffer_get_insert(buffer);
2396 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2398 if (!g_utf8_validate (body, -1, NULL)) {
2399 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2400 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2403 gtk_text_buffer_insert(buffer, &iter, body, -1);
2405 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2407 compose->autowrap = prev_autowrap;
2408 if (compose->autowrap)
2409 compose_wrap_all(compose);
2413 gchar *utf8_filename = conv_filename_to_utf8(attach);
2414 if (utf8_filename) {
2415 if (compose_attach_append(compose, attach, utf8_filename, NULL)) {
2416 alertpanel_notice(_("The file '%s' has been attached."), attach);
2418 g_free(utf8_filename);
2420 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2430 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2432 static HeaderEntry hentry[] = {{"Reply-To:", NULL, TRUE},
2433 {"Cc:", NULL, TRUE},
2434 {"References:", NULL, FALSE},
2435 {"Bcc:", NULL, TRUE},
2436 {"Newsgroups:", NULL, TRUE},
2437 {"Followup-To:", NULL, TRUE},
2438 {"List-Post:", NULL, FALSE},
2439 {"X-Priority:", NULL, FALSE},
2440 {NULL, NULL, FALSE}};
2456 g_return_val_if_fail(msginfo != NULL, -1);
2458 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2459 procheader_get_header_fields(fp, hentry);
2462 if (hentry[H_REPLY_TO].body != NULL) {
2463 if (hentry[H_REPLY_TO].body[0] != '\0') {
2465 conv_unmime_header(hentry[H_REPLY_TO].body,
2468 g_free(hentry[H_REPLY_TO].body);
2469 hentry[H_REPLY_TO].body = NULL;
2471 if (hentry[H_CC].body != NULL) {
2472 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL);
2473 g_free(hentry[H_CC].body);
2474 hentry[H_CC].body = NULL;
2476 if (hentry[H_REFERENCES].body != NULL) {
2477 if (compose->mode == COMPOSE_REEDIT)
2478 compose->references = hentry[H_REFERENCES].body;
2480 compose->references = compose_parse_references
2481 (hentry[H_REFERENCES].body, msginfo->msgid);
2482 g_free(hentry[H_REFERENCES].body);
2484 hentry[H_REFERENCES].body = NULL;
2486 if (hentry[H_BCC].body != NULL) {
2487 if (compose->mode == COMPOSE_REEDIT)
2489 conv_unmime_header(hentry[H_BCC].body, NULL);
2490 g_free(hentry[H_BCC].body);
2491 hentry[H_BCC].body = NULL;
2493 if (hentry[H_NEWSGROUPS].body != NULL) {
2494 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2495 hentry[H_NEWSGROUPS].body = NULL;
2497 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2498 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2499 compose->followup_to =
2500 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2503 g_free(hentry[H_FOLLOWUP_TO].body);
2504 hentry[H_FOLLOWUP_TO].body = NULL;
2506 if (hentry[H_LIST_POST].body != NULL) {
2509 extract_address(hentry[H_LIST_POST].body);
2510 if (hentry[H_LIST_POST].body[0] != '\0') {
2511 scan_mailto_url(hentry[H_LIST_POST].body,
2512 &to, NULL, NULL, NULL, NULL, NULL);
2514 g_free(compose->ml_post);
2515 compose->ml_post = to;
2518 g_free(hentry[H_LIST_POST].body);
2519 hentry[H_LIST_POST].body = NULL;
2522 /* CLAWS - X-Priority */
2523 if (compose->mode == COMPOSE_REEDIT)
2524 if (hentry[H_X_PRIORITY].body != NULL) {
2527 priority = atoi(hentry[H_X_PRIORITY].body);
2528 g_free(hentry[H_X_PRIORITY].body);
2530 hentry[H_X_PRIORITY].body = NULL;
2532 if (priority < PRIORITY_HIGHEST ||
2533 priority > PRIORITY_LOWEST)
2534 priority = PRIORITY_NORMAL;
2536 compose->priority = priority;
2539 if (compose->mode == COMPOSE_REEDIT) {
2540 if (msginfo->inreplyto && *msginfo->inreplyto)
2541 compose->inreplyto = g_strdup(msginfo->inreplyto);
2545 if (msginfo->msgid && *msginfo->msgid)
2546 compose->inreplyto = g_strdup(msginfo->msgid);
2548 if (!compose->references) {
2549 if (msginfo->msgid && *msginfo->msgid) {
2550 if (msginfo->inreplyto && *msginfo->inreplyto)
2551 compose->references =
2552 g_strdup_printf("<%s>\n\t<%s>",
2556 compose->references =
2557 g_strconcat("<", msginfo->msgid, ">",
2559 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
2560 compose->references =
2561 g_strconcat("<", msginfo->inreplyto, ">",
2569 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
2571 GSList *ref_id_list, *cur;
2575 ref_id_list = references_list_append(NULL, ref);
2576 if (!ref_id_list) return NULL;
2577 if (msgid && *msgid)
2578 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
2583 for (cur = ref_id_list; cur != NULL; cur = cur->next)
2584 /* "<" + Message-ID + ">" + CR+LF+TAB */
2585 len += strlen((gchar *)cur->data) + 5;
2587 if (len > MAX_REFERENCES_LEN) {
2588 /* remove second message-ID */
2589 if (ref_id_list && ref_id_list->next &&
2590 ref_id_list->next->next) {
2591 g_free(ref_id_list->next->data);
2592 ref_id_list = g_slist_remove
2593 (ref_id_list, ref_id_list->next->data);
2595 slist_free_strings(ref_id_list);
2596 g_slist_free(ref_id_list);
2603 new_ref = g_string_new("");
2604 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
2605 if (new_ref->len > 0)
2606 g_string_append(new_ref, "\n\t");
2607 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
2610 slist_free_strings(ref_id_list);
2611 g_slist_free(ref_id_list);
2613 new_ref_str = new_ref->str;
2614 g_string_free(new_ref, FALSE);
2619 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
2620 const gchar *fmt, const gchar *qmark,
2621 const gchar *body, gboolean rewrap,
2622 gboolean need_unescape,
2623 const gchar *err_msg)
2625 MsgInfo* dummyinfo = NULL;
2626 gchar *quote_str = NULL;
2628 gboolean prev_autowrap;
2629 const gchar *trimmed_body = body;
2630 gint cursor_pos = -1;
2631 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2632 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2637 SIGNAL_BLOCK(buffer);
2640 dummyinfo = compose_msginfo_new_from_compose(compose);
2641 msginfo = dummyinfo;
2644 if (qmark != NULL) {
2646 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
2647 compose->gtkaspell);
2649 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
2651 quote_fmt_scan_string(qmark);
2654 buf = quote_fmt_get_buffer();
2656 alertpanel_error(_("Quote mark format error."));
2658 Xstrdup_a(quote_str, buf, goto error)
2661 if (fmt && *fmt != '\0') {
2664 while (*trimmed_body == '\n')
2668 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account,
2669 compose->gtkaspell);
2671 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account);
2673 if (need_unescape) {
2676 /* decode \-escape sequences in the internal representation of the quote format */
2677 tmp = malloc(strlen(fmt)+1);
2678 pref_get_unescaped_pref(tmp, fmt);
2679 quote_fmt_scan_string(tmp);
2683 quote_fmt_scan_string(fmt);
2687 buf = quote_fmt_get_buffer();
2689 gint line = quote_fmt_get_line();
2690 alertpanel_error(err_msg, line);
2696 prev_autowrap = compose->autowrap;
2697 compose->autowrap = FALSE;
2699 mark = gtk_text_buffer_get_insert(buffer);
2700 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2701 if (g_utf8_validate(buf, -1, NULL)) {
2702 gtk_text_buffer_insert(buffer, &iter, buf, -1);
2704 gchar *tmpout = NULL;
2705 tmpout = conv_codeset_strdup
2706 (buf, conv_get_locale_charset_str_no_utf8(),
2708 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
2710 tmpout = g_malloc(strlen(buf)*2+1);
2711 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
2713 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
2717 cursor_pos = quote_fmt_get_cursor_pos();
2718 compose->set_cursor_pos = cursor_pos;
2719 if (cursor_pos == -1) {
2722 gtk_text_buffer_get_start_iter(buffer, &iter);
2723 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
2724 gtk_text_buffer_place_cursor(buffer, &iter);
2726 compose->autowrap = prev_autowrap;
2727 if (compose->autowrap && rewrap)
2728 compose_wrap_all(compose);
2735 SIGNAL_UNBLOCK(buffer);
2737 procmsg_msginfo_free( dummyinfo );
2742 /* if ml_post is of type addr@host and from is of type
2743 * addr-anything@host, return TRUE
2745 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
2747 gchar *left_ml = NULL;
2748 gchar *right_ml = NULL;
2749 gchar *left_from = NULL;
2750 gchar *right_from = NULL;
2751 gboolean result = FALSE;
2753 if (!ml_post || !from)
2756 left_ml = g_strdup(ml_post);
2757 if (strstr(left_ml, "@")) {
2758 right_ml = strstr(left_ml, "@")+1;
2759 *(strstr(left_ml, "@")) = '\0';
2762 left_from = g_strdup(from);
2763 if (strstr(left_from, "@")) {
2764 right_from = strstr(left_from, "@")+1;
2765 *(strstr(left_from, "@")) = '\0';
2768 if (left_ml && left_from && right_ml && right_from
2769 && !strncmp(left_from, left_ml, strlen(left_ml))
2770 && !strcmp(right_from, right_ml)) {
2779 static gboolean same_address(const gchar *addr1, const gchar *addr2)
2781 gchar *my_addr1, *my_addr2;
2783 if (!addr1 || !addr2)
2786 Xstrdup_a(my_addr1, addr1, return FALSE);
2787 Xstrdup_a(my_addr2, addr2, return FALSE);
2789 extract_address(my_addr1);
2790 extract_address(my_addr2);
2792 return !strcasecmp(my_addr1, my_addr2);
2795 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
2796 gboolean to_all, gboolean to_ml,
2798 gboolean followup_and_reply_to)
2800 GSList *cc_list = NULL;
2803 gchar *replyto = NULL;
2804 GHashTable *to_table;
2806 gboolean reply_to_ml = FALSE;
2807 gboolean default_reply_to = FALSE;
2809 g_return_if_fail(compose->account != NULL);
2810 g_return_if_fail(msginfo != NULL);
2812 reply_to_ml = to_ml && compose->ml_post;
2814 default_reply_to = msginfo->folder &&
2815 msginfo->folder->prefs->enable_default_reply_to;
2817 if (compose->account->protocol != A_NNTP) {
2818 if (reply_to_ml && !default_reply_to) {
2820 gboolean is_subscr = is_subscription(compose->ml_post,
2823 /* normal answer to ml post with a reply-to */
2824 compose_entry_append(compose,
2827 if (compose->replyto
2828 && !same_address(compose->ml_post, compose->replyto))
2829 compose_entry_append(compose,
2833 /* answer to subscription confirmation */
2834 if (compose->replyto)
2835 compose_entry_append(compose,
2838 else if (msginfo->from)
2839 compose_entry_append(compose,
2844 else if (!(to_all || to_sender) && default_reply_to) {
2845 compose_entry_append(compose,
2846 msginfo->folder->prefs->default_reply_to,
2848 compose_entry_mark_default_to(compose,
2849 msginfo->folder->prefs->default_reply_to);
2854 Xstrdup_a(tmp1, msginfo->from, return);
2855 extract_address(tmp1);
2856 if (to_all || to_sender ||
2857 !account_find_from_address(tmp1))
2858 compose_entry_append(compose,
2859 (compose->replyto && !to_sender)
2860 ? compose->replyto :
2861 msginfo->from ? msginfo->from : "",
2863 else if (!to_all && !to_sender) {
2864 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
2865 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
2866 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
2867 compose_entry_append(compose,
2868 msginfo->from ? msginfo->from : "",
2871 /* replying to own mail, use original recp */
2872 compose_entry_append(compose,
2873 msginfo->to ? msginfo->to : "",
2875 compose_entry_append(compose,
2876 msginfo->cc ? msginfo->cc : "",
2882 if (to_sender || (compose->followup_to &&
2883 !strncmp(compose->followup_to, "poster", 6)))
2884 compose_entry_append
2886 (compose->replyto ? compose->replyto :
2887 msginfo->from ? msginfo->from : ""),
2890 else if (followup_and_reply_to || to_all) {
2891 compose_entry_append
2893 (compose->replyto ? compose->replyto :
2894 msginfo->from ? msginfo->from : ""),
2897 compose_entry_append
2899 compose->followup_to ? compose->followup_to :
2900 compose->newsgroups ? compose->newsgroups : "",
2901 COMPOSE_NEWSGROUPS);
2904 compose_entry_append
2906 compose->followup_to ? compose->followup_to :
2907 compose->newsgroups ? compose->newsgroups : "",
2908 COMPOSE_NEWSGROUPS);
2911 if (msginfo->subject && *msginfo->subject) {
2915 buf = p = g_strdup(msginfo->subject);
2916 p += subject_get_prefix_length(p);
2917 memmove(buf, p, strlen(p) + 1);
2919 buf2 = g_strdup_printf("Re: %s", buf);
2920 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
2925 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
2927 if (to_ml && compose->ml_post) return;
2928 if (!to_all || compose->account->protocol == A_NNTP) return;
2930 if (compose->replyto) {
2931 Xstrdup_a(replyto, compose->replyto, return);
2932 extract_address(replyto);
2934 if (msginfo->from) {
2935 Xstrdup_a(from, msginfo->from, return);
2936 extract_address(from);
2939 if (replyto && from)
2940 cc_list = address_list_append_with_comments(cc_list, from);
2941 if (to_all && msginfo->folder &&
2942 msginfo->folder->prefs->enable_default_reply_to)
2943 cc_list = address_list_append_with_comments(cc_list,
2944 msginfo->folder->prefs->default_reply_to);
2945 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
2946 cc_list = address_list_append_with_comments(cc_list, compose->cc);
2948 to_table = g_hash_table_new(g_str_hash, g_str_equal);
2950 g_hash_table_insert(to_table, g_utf8_strdown(replyto, -1), GINT_TO_POINTER(1));
2951 if (compose->account) {
2952 g_hash_table_insert(to_table, g_utf8_strdown(compose->account->address, -1),
2953 GINT_TO_POINTER(1));
2955 /* remove address on To: and that of current account */
2956 for (cur = cc_list; cur != NULL; ) {
2957 GSList *next = cur->next;
2960 addr = g_utf8_strdown(cur->data, -1);
2961 extract_address(addr);
2963 if (GPOINTER_TO_INT(g_hash_table_lookup(to_table, addr)) == 1)
2964 cc_list = g_slist_remove(cc_list, cur->data);
2966 g_hash_table_insert(to_table, addr, GINT_TO_POINTER(1));
2970 hash_free_strings(to_table);
2971 g_hash_table_destroy(to_table);
2974 for (cur = cc_list; cur != NULL; cur = cur->next)
2975 compose_entry_append(compose, (gchar *)cur->data,
2977 slist_free_strings(cc_list);
2978 g_slist_free(cc_list);
2983 #define SET_ENTRY(entry, str) \
2986 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
2989 #define SET_ADDRESS(type, str) \
2992 compose_entry_append(compose, str, type); \
2995 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
2997 g_return_if_fail(msginfo != NULL);
2999 SET_ENTRY(subject_entry, msginfo->subject);
3000 SET_ENTRY(from_name, msginfo->from);
3001 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3002 SET_ADDRESS(COMPOSE_CC, compose->cc);
3003 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3004 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3005 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3006 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3008 compose_update_priority_menu_item(compose);
3009 compose_update_privacy_system_menu_item(compose, FALSE);
3010 compose_show_first_last_header(compose, TRUE);
3016 static void compose_insert_sig(Compose *compose, gboolean replace)
3018 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3019 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3021 GtkTextIter iter, iter_end;
3023 gboolean prev_autowrap;
3024 gboolean found = FALSE;
3025 gboolean exists = FALSE;
3027 g_return_if_fail(compose->account != NULL);
3031 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3032 G_CALLBACK(compose_changed_cb),
3035 mark = gtk_text_buffer_get_insert(buffer);
3036 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3037 cur_pos = gtk_text_iter_get_offset (&iter);
3039 gtk_text_buffer_get_end_iter(buffer, &iter);
3041 exists = (compose->sig_str != NULL);
3044 GtkTextIter first_iter, start_iter, end_iter;
3046 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3048 if (!exists || compose->sig_str[0] == '\0')
3051 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3052 compose->signature_tag);
3055 /* include previous \n\n */
3056 gtk_text_iter_backward_chars(&first_iter, 2);
3057 start_iter = first_iter;
3058 end_iter = first_iter;
3060 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3061 compose->signature_tag);
3062 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3063 compose->signature_tag);
3065 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3071 g_free(compose->sig_str);
3072 compose->sig_str = compose_get_signature_str(compose);
3074 cur_pos = gtk_text_iter_get_offset(&iter);
3076 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3077 g_free(compose->sig_str);
3078 compose->sig_str = NULL;
3080 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3082 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3083 gtk_text_iter_forward_chars(&iter, 2);
3084 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3085 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3087 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3088 cur_pos = gtk_text_buffer_get_char_count (buffer);
3090 /* put the cursor where it should be
3091 * either where the quote_fmt says, either before the signature */
3092 if (compose->set_cursor_pos < 0)
3093 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3095 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3096 compose->set_cursor_pos);
3098 gtk_text_buffer_place_cursor(buffer, &iter);
3099 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3100 G_CALLBACK(compose_changed_cb),
3106 static gchar *compose_get_signature_str(Compose *compose)
3108 gchar *sig_body = NULL;
3109 gchar *sig_str = NULL;
3110 gchar *utf8_sig_str = NULL;
3112 g_return_val_if_fail(compose->account != NULL, NULL);
3114 if (!compose->account->sig_path)
3117 if (compose->account->sig_type == SIG_FILE) {
3118 if (!is_file_or_fifo_exist(compose->account->sig_path)) {
3119 g_warning("can't open signature file: %s\n",
3120 compose->account->sig_path);
3125 if (compose->account->sig_type == SIG_COMMAND)
3126 sig_body = get_command_output(compose->account->sig_path);
3130 tmp = file_read_to_str(compose->account->sig_path);
3133 sig_body = normalize_newlines(tmp);
3137 if (compose->account->sig_sep) {
3138 sig_str = g_strconcat("\n\n", compose->account->sig_sep, "\n", sig_body,
3142 sig_str = g_strconcat("\n\n", sig_body, NULL);
3145 if (g_utf8_validate(sig_str, -1, NULL) == TRUE)
3146 utf8_sig_str = sig_str;
3148 utf8_sig_str = conv_codeset_strdup
3149 (sig_str, conv_get_locale_charset_str_no_utf8(),
3155 return utf8_sig_str;
3158 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3161 GtkTextBuffer *buffer;
3164 const gchar *cur_encoding;
3165 gchar buf[BUFFSIZE];
3168 gboolean prev_autowrap;
3169 gboolean badtxt = FALSE;
3171 g_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3173 if ((fp = g_fopen(file, "rb")) == NULL) {
3174 FILE_OP_ERROR(file, "fopen");
3175 return COMPOSE_INSERT_READ_ERROR;
3178 prev_autowrap = compose->autowrap;
3179 compose->autowrap = FALSE;
3181 text = GTK_TEXT_VIEW(compose->text);
3182 buffer = gtk_text_view_get_buffer(text);
3183 mark = gtk_text_buffer_get_insert(buffer);
3184 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3186 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3187 G_CALLBACK(text_inserted),
3190 cur_encoding = conv_get_locale_charset_str_no_utf8();
3192 while (fgets(buf, sizeof(buf), fp) != NULL) {
3195 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3196 str = g_strdup(buf);
3198 str = conv_codeset_strdup
3199 (buf, cur_encoding, CS_INTERNAL);
3202 /* strip <CR> if DOS/Windows file,
3203 replace <CR> with <LF> if Macintosh file. */
3206 if (len > 0 && str[len - 1] != '\n') {
3208 if (str[len] == '\r') str[len] = '\n';
3211 gtk_text_buffer_insert(buffer, &iter, str, -1);
3215 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3216 G_CALLBACK(text_inserted),
3218 compose->autowrap = prev_autowrap;
3219 if (compose->autowrap)
3220 compose_wrap_all(compose);
3225 return COMPOSE_INSERT_INVALID_CHARACTER;
3227 return COMPOSE_INSERT_SUCCESS;
3230 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3231 const gchar *filename,
3232 const gchar *content_type)
3240 GtkListStore *store;
3242 gboolean has_binary = FALSE;
3244 if (!is_file_exist(file)) {
3245 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3246 gboolean result = FALSE;
3247 if (file_from_uri && is_file_exist(file_from_uri)) {
3248 result = compose_attach_append(
3249 compose, file_from_uri,
3253 g_free(file_from_uri);
3256 alertpanel_error("File %s doesn't exist\n", filename);
3259 if ((size = get_file_size(file)) < 0) {
3260 alertpanel_error("Can't get file size of %s\n", filename);
3264 alertpanel_error(_("File %s is empty."), filename);
3267 if ((fp = g_fopen(file, "rb")) == NULL) {
3268 alertpanel_error(_("Can't read %s."), filename);
3273 ainfo = g_new0(AttachInfo, 1);
3274 auto_ainfo = g_auto_pointer_new_with_free
3275 (ainfo, (GFreeFunc) compose_attach_info_free);
3276 ainfo->file = g_strdup(file);
3279 ainfo->content_type = g_strdup(content_type);
3280 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3282 MsgFlags flags = {0, 0};
3284 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3285 ainfo->encoding = ENC_7BIT;
3287 ainfo->encoding = ENC_8BIT;
3289 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3290 if (msginfo && msginfo->subject)
3291 name = g_strdup(msginfo->subject);
3293 name = g_path_get_basename(filename ? filename : file);
3295 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3297 procmsg_msginfo_free(msginfo);
3299 if (!g_ascii_strncasecmp(content_type, "text", 4))
3300 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3302 ainfo->encoding = ENC_BASE64;
3303 name = g_path_get_basename(filename ? filename : file);
3304 ainfo->name = g_strdup(name);
3308 ainfo->content_type = procmime_get_mime_type(file);
3309 if (!ainfo->content_type) {
3310 ainfo->content_type =
3311 g_strdup("application/octet-stream");
3312 ainfo->encoding = ENC_BASE64;
3313 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text", 4))
3315 procmime_get_encoding_for_text_file(file, &has_binary);
3317 ainfo->encoding = ENC_BASE64;
3318 name = g_path_get_basename(filename ? filename : file);
3319 ainfo->name = g_strdup(name);
3323 if (ainfo->name != NULL
3324 && !strcmp(ainfo->name, ".")) {
3325 g_free(ainfo->name);
3329 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3330 g_free(ainfo->content_type);
3331 ainfo->content_type = g_strdup("application/octet-stream");
3335 size_text = to_human_readable(size);
3337 store = GTK_LIST_STORE(gtk_tree_view_get_model
3338 (GTK_TREE_VIEW(compose->attach_clist)));
3340 gtk_list_store_append(store, &iter);
3341 gtk_list_store_set(store, &iter,
3342 COL_MIMETYPE, ainfo->content_type,
3343 COL_SIZE, size_text,
3344 COL_NAME, ainfo->name,
3346 COL_AUTODATA, auto_ainfo,
3349 g_auto_pointer_free(auto_ainfo);
3353 static void compose_use_signing(Compose *compose, gboolean use_signing)
3355 GtkItemFactory *ifactory;
3356 GtkWidget *menuitem = NULL;
3358 compose->use_signing = use_signing;
3359 ifactory = gtk_item_factory_from_widget(compose->menubar);
3360 menuitem = gtk_item_factory_get_item
3361 (ifactory, "/Options/Sign");
3362 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
3366 static void compose_use_encryption(Compose *compose, gboolean use_encryption)
3368 GtkItemFactory *ifactory;
3369 GtkWidget *menuitem = NULL;
3371 compose->use_encryption = use_encryption;
3372 ifactory = gtk_item_factory_from_widget(compose->menubar);
3373 menuitem = gtk_item_factory_get_item
3374 (ifactory, "/Options/Encrypt");
3376 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
3380 #define NEXT_PART_NOT_CHILD(info) \
3382 node = info->node; \
3383 while (node->children) \
3384 node = g_node_last_child(node); \
3385 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3388 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3392 MimeInfo *firsttext = NULL;
3393 MimeInfo *encrypted = NULL;
3396 const gchar *partname = NULL;
3398 mimeinfo = procmime_scan_message(msginfo);
3399 if (!mimeinfo) return;
3401 if (mimeinfo->node->children == NULL) {
3402 procmime_mimeinfo_free_all(mimeinfo);
3406 /* find first content part */
3407 child = (MimeInfo *) mimeinfo->node->children->data;
3408 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3409 child = (MimeInfo *)child->node->children->data;
3411 if (child->type == MIMETYPE_TEXT) {
3413 debug_print("First text part found\n");
3414 } else if (compose->mode == COMPOSE_REEDIT &&
3415 child->type == MIMETYPE_APPLICATION &&
3416 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3417 encrypted = (MimeInfo *)child->node->parent->data;
3420 child = (MimeInfo *) mimeinfo->node->children->data;
3421 while (child != NULL) {
3424 if (child == encrypted) {
3425 /* skip this part of tree */
3426 NEXT_PART_NOT_CHILD(child);
3430 if (child->type == MIMETYPE_MULTIPART) {
3431 /* get the actual content */
3432 child = procmime_mimeinfo_next(child);
3436 if (child == firsttext) {
3437 child = procmime_mimeinfo_next(child);
3441 outfile = procmime_get_tmp_file_name(child);
3442 if ((err = procmime_get_part(outfile, child)) < 0)
3443 g_warning("Can't get the part of multipart message. (%s)", strerror(-err));
3445 gchar *content_type;
3447 content_type = procmime_get_content_type_str(child->type, child->subtype);
3449 /* if we meet a pgp signature, we don't attach it, but
3450 * we force signing. */
3451 if ((strcmp(content_type, "application/pgp-signature") &&
3452 strcmp(content_type, "application/pkcs7-signature") &&
3453 strcmp(content_type, "application/x-pkcs7-signature"))
3454 || compose->mode == COMPOSE_REDIRECT) {
3455 partname = procmime_mimeinfo_get_parameter(child, "filename");
3456 if (partname == NULL)
3457 partname = procmime_mimeinfo_get_parameter(child, "name");
3458 if (partname == NULL)
3460 compose_attach_append(compose, outfile,
3461 partname, content_type);
3463 compose_force_signing(compose, compose->account);
3465 g_free(content_type);
3468 NEXT_PART_NOT_CHILD(child);
3470 procmime_mimeinfo_free_all(mimeinfo);
3473 #undef NEXT_PART_NOT_CHILD
3478 WAIT_FOR_INDENT_CHAR,
3479 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3482 /* return indent length, we allow:
3483 indent characters followed by indent characters or spaces/tabs,
3484 alphabets and numbers immediately followed by indent characters,
3485 and the repeating sequences of the above
3486 If quote ends with multiple spaces, only the first one is included. */
3487 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3488 const GtkTextIter *start, gint *len)
3490 GtkTextIter iter = *start;
3494 IndentState state = WAIT_FOR_INDENT_CHAR;
3497 gint alnum_count = 0;
3498 gint space_count = 0;
3501 if (prefs_common.quote_chars == NULL) {
3505 while (!gtk_text_iter_ends_line(&iter)) {
3506 wc = gtk_text_iter_get_char(&iter);
3507 if (g_unichar_iswide(wc))
3509 clen = g_unichar_to_utf8(wc, ch);
3513 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
3514 is_space = g_unichar_isspace(wc);
3516 if (state == WAIT_FOR_INDENT_CHAR) {
3517 if (!is_indent && !g_unichar_isalnum(wc))
3520 quote_len += alnum_count + space_count + 1;
3521 alnum_count = space_count = 0;
3522 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
3525 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
3526 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
3530 else if (is_indent) {
3531 quote_len += alnum_count + space_count + 1;
3532 alnum_count = space_count = 0;
3535 state = WAIT_FOR_INDENT_CHAR;
3539 gtk_text_iter_forward_char(&iter);
3542 if (quote_len > 0 && space_count > 0)
3548 if (quote_len > 0) {
3550 gtk_text_iter_forward_chars(&iter, quote_len);
3551 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
3557 /* return TRUE if the line is itemized */
3558 static gboolean compose_is_itemized(GtkTextBuffer *buffer,
3559 const GtkTextIter *start)
3561 GtkTextIter iter = *start;
3566 if (gtk_text_iter_ends_line(&iter))
3570 wc = gtk_text_iter_get_char(&iter);
3571 if (!g_unichar_isspace(wc))
3573 gtk_text_iter_forward_char(&iter);
3574 if (gtk_text_iter_ends_line(&iter))
3578 clen = g_unichar_to_utf8(wc, ch);
3582 if (!strchr("*-+", ch[0]))
3585 gtk_text_iter_forward_char(&iter);
3586 if (gtk_text_iter_ends_line(&iter))
3588 wc = gtk_text_iter_get_char(&iter);
3589 if (g_unichar_isspace(wc))
3595 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
3596 const GtkTextIter *start,
3597 GtkTextIter *break_pos,
3601 GtkTextIter iter = *start, line_end = *start;
3602 PangoLogAttr *attrs;
3609 gboolean can_break = FALSE;
3610 gboolean do_break = FALSE;
3611 gboolean was_white = FALSE;
3612 gboolean prev_dont_break = FALSE;
3614 gtk_text_iter_forward_to_line_end(&line_end);
3615 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
3616 len = g_utf8_strlen(str, -1);
3620 g_warning("compose_get_line_break_pos: len = 0!\n");
3624 /* g_print("breaking line: %d: %s (len = %d)\n",
3625 gtk_text_iter_get_line(&iter), str, len); */
3627 attrs = g_new(PangoLogAttr, len + 1);
3629 pango_default_break(str, -1, NULL, attrs, len + 1);
3633 /* skip quote and leading spaces */
3634 for (i = 0; *p != '\0' && i < len; i++) {
3637 wc = g_utf8_get_char(p);
3638 if (i >= quote_len && !g_unichar_isspace(wc))
3640 if (g_unichar_iswide(wc))
3642 else if (*p == '\t')
3646 p = g_utf8_next_char(p);
3649 for (; *p != '\0' && i < len; i++) {
3650 PangoLogAttr *attr = attrs + i;
3654 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
3657 was_white = attr->is_white;
3659 /* don't wrap URI */
3660 if ((uri_len = get_uri_len(p)) > 0) {
3662 if (pos > 0 && col > max_col) {
3672 wc = g_utf8_get_char(p);
3673 if (g_unichar_iswide(wc)) {
3675 if (prev_dont_break && can_break && attr->is_line_break)
3677 } else if (*p == '\t')
3681 if (pos > 0 && col > max_col) {
3686 if (*p == '-' || *p == '/')
3687 prev_dont_break = TRUE;
3689 prev_dont_break = FALSE;
3691 p = g_utf8_next_char(p);
3695 debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col);
3700 *break_pos = *start;
3701 gtk_text_iter_set_line_offset(break_pos, pos);
3706 static gboolean compose_join_next_line(Compose *compose,
3707 GtkTextBuffer *buffer,
3709 const gchar *quote_str)
3711 GtkTextIter iter_ = *iter, cur, prev, next, end;
3712 PangoLogAttr attrs[3];
3714 gchar *next_quote_str;
3717 gboolean keep_cursor = FALSE;
3719 if (!gtk_text_iter_forward_line(&iter_) ||
3720 gtk_text_iter_ends_line(&iter_))
3723 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
3725 if ((quote_str || next_quote_str) &&
3726 strcmp2(quote_str, next_quote_str) != 0) {
3727 g_free(next_quote_str);
3730 g_free(next_quote_str);
3733 if (quote_len > 0) {
3734 gtk_text_iter_forward_chars(&end, quote_len);
3735 if (gtk_text_iter_ends_line(&end))
3739 /* don't join itemized lines */
3740 if (compose_is_itemized(buffer, &end))
3743 /* don't join signature separator */
3744 if (compose_is_sig_separator(compose, buffer, &iter_))
3747 /* delete quote str */
3749 gtk_text_buffer_delete(buffer, &iter_, &end);
3751 /* don't join line breaks put by the user */
3753 gtk_text_iter_backward_char(&cur);
3754 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
3755 gtk_text_iter_forward_char(&cur);
3759 gtk_text_iter_forward_char(&cur);
3760 /* delete linebreak and extra spaces */
3761 while (gtk_text_iter_backward_char(&cur)) {
3762 wc1 = gtk_text_iter_get_char(&cur);
3763 if (!g_unichar_isspace(wc1))
3768 while (!gtk_text_iter_ends_line(&cur)) {
3769 wc1 = gtk_text_iter_get_char(&cur);
3770 if (!g_unichar_isspace(wc1))
3772 gtk_text_iter_forward_char(&cur);
3775 if (!gtk_text_iter_equal(&prev, &next)) {
3778 mark = gtk_text_buffer_get_insert(buffer);
3779 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
3780 if (gtk_text_iter_equal(&prev, &cur))
3782 gtk_text_buffer_delete(buffer, &prev, &next);
3786 /* insert space if required */
3787 gtk_text_iter_backward_char(&prev);
3788 wc1 = gtk_text_iter_get_char(&prev);
3789 wc2 = gtk_text_iter_get_char(&next);
3790 gtk_text_iter_forward_char(&next);
3791 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
3792 pango_default_break(str, -1, NULL, attrs, 3);
3793 if (!attrs[1].is_line_break ||
3794 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
3795 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
3797 gtk_text_iter_backward_char(&iter_);
3798 gtk_text_buffer_place_cursor(buffer, &iter_);
3807 #define ADD_TXT_POS(bp_, ep_, pti_) \
3808 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
3809 last = last->next; \
3810 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
3811 last->next = NULL; \
3813 g_warning("alloc error scanning URIs\n"); \
3816 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
3818 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3819 GtkTextBuffer *buffer;
3820 GtkTextIter iter, break_pos, end_of_line;
3821 gchar *quote_str = NULL;
3823 gboolean wrap_quote = prefs_common.linewrap_quote;
3824 gboolean prev_autowrap = compose->autowrap;
3825 gint startq_offset = -1, noq_offset = -1;
3826 gint uri_start = -1, uri_stop = -1;
3827 gint nouri_start = -1, nouri_stop = -1;
3828 gint num_blocks = 0;
3829 gint quotelevel = -1;
3830 gboolean modified = force;
3831 gboolean removed = FALSE;
3832 gboolean modified_before_remove = FALSE;
3834 gboolean start = TRUE;
3839 if (compose->draft_timeout_tag == -2) {
3843 compose->autowrap = FALSE;
3845 buffer = gtk_text_view_get_buffer(text);
3846 undo_wrapping(compose->undostruct, TRUE);
3851 mark = gtk_text_buffer_get_insert(buffer);
3852 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3856 if (compose->draft_timeout_tag == -2) {
3857 if (gtk_text_iter_ends_line(&iter)) {
3858 while (gtk_text_iter_ends_line(&iter) &&
3859 gtk_text_iter_forward_line(&iter))
3862 while (gtk_text_iter_backward_line(&iter)) {
3863 if (gtk_text_iter_ends_line(&iter)) {
3864 gtk_text_iter_forward_line(&iter);
3870 /* move to line start */
3871 gtk_text_iter_set_line_offset(&iter, 0);
3873 /* go until paragraph end (empty line) */
3874 while (start || !gtk_text_iter_ends_line(&iter)) {
3875 gchar *scanpos = NULL;
3876 /* parse table - in order of priority */
3878 const gchar *needle; /* token */
3880 /* token search function */
3881 gchar *(*search) (const gchar *haystack,
3882 const gchar *needle);
3883 /* part parsing function */
3884 gboolean (*parse) (const gchar *start,
3885 const gchar *scanpos,
3889 /* part to URI function */
3890 gchar *(*build_uri) (const gchar *bp,
3894 static struct table parser[] = {
3895 {"http://", strcasestr, get_uri_part, make_uri_string},
3896 {"https://", strcasestr, get_uri_part, make_uri_string},
3897 {"ftp://", strcasestr, get_uri_part, make_uri_string},
3898 {"sftp://", strcasestr, get_uri_part, make_uri_string},
3899 {"www.", strcasestr, get_uri_part, make_http_string},
3900 {"mailto:", strcasestr, get_uri_part, make_uri_string},
3901 {"@", strcasestr, get_email_part, make_email_string}
3903 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
3904 gint last_index = PARSE_ELEMS;
3906 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
3910 if (!prev_autowrap && num_blocks == 0) {
3912 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3913 G_CALLBACK(text_inserted),
3916 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
3919 uri_start = uri_stop = -1;
3921 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
3924 debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str);
3925 if (startq_offset == -1)
3926 startq_offset = gtk_text_iter_get_offset(&iter);
3927 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
3928 if (quotelevel > 2) {
3929 /* recycle colors */
3930 if (prefs_common.recycle_quote_colors)
3939 if (startq_offset == -1)
3940 noq_offset = gtk_text_iter_get_offset(&iter);
3944 if (prev_autowrap == FALSE && !force && !wrap_quote) {
3947 if (gtk_text_iter_ends_line(&iter)) {
3949 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
3950 prefs_common.linewrap_len,
3952 GtkTextIter prev, next, cur;
3954 if (prev_autowrap != FALSE || force) {
3955 compose->automatic_break = TRUE;
3957 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
3958 compose->automatic_break = FALSE;
3959 } else if (quote_str && wrap_quote) {
3960 compose->automatic_break = TRUE;
3962 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
3963 compose->automatic_break = FALSE;
3966 /* remove trailing spaces */
3968 gtk_text_iter_backward_char(&cur);
3970 while (!gtk_text_iter_starts_line(&cur)) {
3973 gtk_text_iter_backward_char(&cur);
3974 wc = gtk_text_iter_get_char(&cur);
3975 if (!g_unichar_isspace(wc))
3979 if (!gtk_text_iter_equal(&prev, &next)) {
3980 gtk_text_buffer_delete(buffer, &prev, &next);
3982 gtk_text_iter_forward_char(&break_pos);
3986 gtk_text_buffer_insert(buffer, &break_pos,
3990 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
3992 /* move iter to current line start */
3993 gtk_text_iter_set_line_offset(&iter, 0);
4000 /* move iter to next line start */
4006 if (!prev_autowrap && num_blocks > 0) {
4008 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4009 G_CALLBACK(text_inserted),
4013 while (!gtk_text_iter_ends_line(&end_of_line)) {
4014 gtk_text_iter_forward_char(&end_of_line);
4016 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4018 nouri_start = gtk_text_iter_get_offset(&iter);
4019 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4021 walk_pos = gtk_text_iter_get_offset(&iter);
4022 /* FIXME: this looks phony. scanning for anything in the parse table */
4023 for (n = 0; n < PARSE_ELEMS; n++) {
4026 tmp = parser[n].search(walk, parser[n].needle);
4028 if (scanpos == NULL || tmp < scanpos) {
4037 /* check if URI can be parsed */
4038 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4039 (const gchar **)&ep, FALSE)
4040 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4044 strlen(parser[last_index].needle);
4047 uri_start = walk_pos + (bp - o_walk);
4048 uri_stop = walk_pos + (ep - o_walk);
4052 gtk_text_iter_forward_line(&iter);
4055 if (startq_offset != -1) {
4056 GtkTextIter startquote, endquote;
4057 gtk_text_buffer_get_iter_at_offset(
4058 buffer, &startquote, startq_offset);
4061 switch (quotelevel) {
4063 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4064 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4065 gtk_text_buffer_apply_tag_by_name(
4066 buffer, "quote0", &startquote, &endquote);
4067 gtk_text_buffer_remove_tag_by_name(
4068 buffer, "quote1", &startquote, &endquote);
4069 gtk_text_buffer_remove_tag_by_name(
4070 buffer, "quote2", &startquote, &endquote);
4075 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4076 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4077 gtk_text_buffer_apply_tag_by_name(
4078 buffer, "quote1", &startquote, &endquote);
4079 gtk_text_buffer_remove_tag_by_name(
4080 buffer, "quote0", &startquote, &endquote);
4081 gtk_text_buffer_remove_tag_by_name(
4082 buffer, "quote2", &startquote, &endquote);
4087 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4088 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4089 gtk_text_buffer_apply_tag_by_name(
4090 buffer, "quote2", &startquote, &endquote);
4091 gtk_text_buffer_remove_tag_by_name(
4092 buffer, "quote0", &startquote, &endquote);
4093 gtk_text_buffer_remove_tag_by_name(
4094 buffer, "quote1", &startquote, &endquote);
4100 } else if (noq_offset != -1) {
4101 GtkTextIter startnoquote, endnoquote;
4102 gtk_text_buffer_get_iter_at_offset(
4103 buffer, &startnoquote, noq_offset);
4106 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4107 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4108 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4109 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4110 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4111 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4112 gtk_text_buffer_remove_tag_by_name(
4113 buffer, "quote0", &startnoquote, &endnoquote);
4114 gtk_text_buffer_remove_tag_by_name(
4115 buffer, "quote1", &startnoquote, &endnoquote);
4116 gtk_text_buffer_remove_tag_by_name(
4117 buffer, "quote2", &startnoquote, &endnoquote);
4123 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4124 GtkTextIter nouri_start_iter, nouri_end_iter;
4125 gtk_text_buffer_get_iter_at_offset(
4126 buffer, &nouri_start_iter, nouri_start);
4127 gtk_text_buffer_get_iter_at_offset(
4128 buffer, &nouri_end_iter, nouri_stop);
4129 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4130 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4131 gtk_text_buffer_remove_tag_by_name(
4132 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4133 modified_before_remove = modified;
4138 if (uri_start > 0 && uri_stop > 0) {
4139 GtkTextIter uri_start_iter, uri_end_iter, back;
4140 gtk_text_buffer_get_iter_at_offset(
4141 buffer, &uri_start_iter, uri_start);
4142 gtk_text_buffer_get_iter_at_offset(
4143 buffer, &uri_end_iter, uri_stop);
4144 back = uri_end_iter;
4145 gtk_text_iter_backward_char(&back);
4146 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4147 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4148 gtk_text_buffer_apply_tag_by_name(
4149 buffer, "link", &uri_start_iter, &uri_end_iter);
4151 if (removed && !modified_before_remove) {
4157 debug_print("not modified, out after %d lines\n", lines);
4162 debug_print("modified, out after %d lines\n", lines);
4166 undo_wrapping(compose->undostruct, FALSE);
4167 compose->autowrap = prev_autowrap;
4172 void compose_action_cb(void *data)
4174 Compose *compose = (Compose *)data;
4175 compose_wrap_all(compose);
4178 static void compose_wrap_all(Compose *compose)
4180 compose_wrap_all_full(compose, FALSE);
4183 static void compose_wrap_all_full(Compose *compose, gboolean force)
4185 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4186 GtkTextBuffer *buffer;
4188 gboolean modified = TRUE;
4190 buffer = gtk_text_view_get_buffer(text);
4192 gtk_text_buffer_get_start_iter(buffer, &iter);
4193 while (!gtk_text_iter_is_end(&iter) && modified)
4194 modified = compose_beautify_paragraph(compose, &iter, force);
4198 static void compose_set_title(Compose *compose)
4204 edited = compose->modified ? _(" [Edited]") : "";
4206 subject = gtk_editable_get_chars(
4207 GTK_EDITABLE(compose->subject_entry), 0, -1);
4210 if (subject && strlen(subject))
4211 str = g_strdup_printf(_("%s - Compose message%s"),
4214 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4216 str = g_strdup(_("Compose message"));
4219 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4225 * compose_current_mail_account:
4227 * Find a current mail account (the currently selected account, or the
4228 * default account, if a news account is currently selected). If a
4229 * mail account cannot be found, display an error message.
4231 * Return value: Mail account, or NULL if not found.
4233 static PrefsAccount *
4234 compose_current_mail_account(void)
4238 if (cur_account && cur_account->protocol != A_NNTP)
4241 ac = account_get_default();
4242 if (!ac || ac->protocol == A_NNTP) {
4243 alertpanel_error(_("Account for sending mail is not specified.\n"
4244 "Please select a mail account before sending."));
4251 #define QUOTE_IF_REQUIRED(out, str) \
4253 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4257 len = strlen(str) + 3; \
4258 if ((__tmp = alloca(len)) == NULL) { \
4259 g_warning("can't allocate memory\n"); \
4260 g_string_free(header, TRUE); \
4263 g_snprintf(__tmp, len, "\"%s\"", str); \
4268 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4269 g_warning("can't allocate memory\n"); \
4270 g_string_free(header, TRUE); \
4273 strcpy(__tmp, str); \
4279 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4281 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4285 len = strlen(str) + 3; \
4286 if ((__tmp = alloca(len)) == NULL) { \
4287 g_warning("can't allocate memory\n"); \
4290 g_snprintf(__tmp, len, "\"%s\"", str); \
4295 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4296 g_warning("can't allocate memory\n"); \
4299 strcpy(__tmp, str); \
4305 static void compose_select_account(Compose *compose, PrefsAccount *account,
4308 GtkItemFactory *ifactory;
4311 g_return_if_fail(account != NULL);
4313 compose->account = account;
4315 if (account->name && *account->name) {
4317 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4318 from = g_strdup_printf("%s <%s>",
4319 buf, account->address);
4320 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4322 from = g_strdup_printf("<%s>",
4324 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4329 compose_set_title(compose);
4331 ifactory = gtk_item_factory_from_widget(compose->menubar);
4333 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4334 menu_set_active(ifactory, "/Options/Sign", TRUE);
4336 menu_set_active(ifactory, "/Options/Sign", FALSE);
4337 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4338 menu_set_active(ifactory, "/Options/Encrypt", TRUE);
4340 menu_set_active(ifactory, "/Options/Encrypt", FALSE);
4342 activate_privacy_system(compose, account, FALSE);
4344 if (!init && compose->mode != COMPOSE_REDIRECT) {
4345 undo_block(compose->undostruct);
4346 compose_insert_sig(compose, TRUE);
4347 undo_unblock(compose->undostruct);
4351 /* use account's dict info if set */
4352 if (compose->gtkaspell) {
4353 if (account->enable_default_dictionary)
4354 gtkaspell_change_dict(compose->gtkaspell,
4355 account->default_dictionary, FALSE);
4356 if (account->enable_default_alt_dictionary)
4357 gtkaspell_change_alt_dict(compose->gtkaspell,
4358 account->default_alt_dictionary);
4359 if (account->enable_default_dictionary
4360 || account->enable_default_alt_dictionary)
4361 compose_spell_menu_changed(compose);
4366 gboolean compose_check_for_valid_recipient(Compose *compose) {
4367 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
4368 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
4369 gboolean recipient_found = FALSE;
4373 /* free to and newsgroup list */
4374 slist_free_strings(compose->to_list);
4375 g_slist_free(compose->to_list);
4376 compose->to_list = NULL;
4378 slist_free_strings(compose->newsgroup_list);
4379 g_slist_free(compose->newsgroup_list);
4380 compose->newsgroup_list = NULL;
4382 /* search header entries for to and newsgroup entries */
4383 for (list = compose->header_list; list; list = list->next) {
4386 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_COMBO(((ComposeHeaderEntry *)list->data)->combo)->entry), 0, -1);
4387 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4390 if (entry[0] != '\0') {
4391 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
4392 if (!strcmp(header, prefs_common_translated_header_name(*strptr))) {
4393 compose->to_list = address_list_append(compose->to_list, entry);
4394 recipient_found = TRUE;
4397 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
4398 if (!strcmp(header, prefs_common_translated_header_name(*strptr))) {
4399 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
4400 recipient_found = TRUE;
4407 return recipient_found;
4410 static gboolean compose_check_for_set_recipients(Compose *compose)
4412 if (compose->account->set_autocc && compose->account->auto_cc) {
4413 gboolean found_other = FALSE;
4415 /* search header entries for to and newsgroup entries */
4416 for (list = compose->header_list; list; list = list->next) {
4419 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4420 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_COMBO(((ComposeHeaderEntry *)list->data)->combo)->entry), 0, -1);
4422 if (strcmp(entry, compose->account->auto_cc)
4423 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
4433 if (compose->batch) {
4434 gtk_widget_show_all(compose->window);
4436 aval = alertpanel(_("Send"),
4437 _("The only recipient is the default CC address. Send anyway?"),
4438 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4439 if (aval != G_ALERTALTERNATE)
4443 if (compose->account->set_autobcc && compose->account->auto_bcc) {
4444 gboolean found_other = FALSE;
4446 /* search header entries for to and newsgroup entries */
4447 for (list = compose->header_list; list; list = list->next) {
4450 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4451 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_COMBO(((ComposeHeaderEntry *)list->data)->combo)->entry), 0, -1);
4453 if (strcmp(entry, compose->account->auto_bcc)
4454 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
4464 if (compose->batch) {
4465 gtk_widget_show_all(compose->window);
4467 aval = alertpanel(_("Send"),
4468 _("The only recipient is the default BCC address. Send anyway?"),
4469 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4470 if (aval != G_ALERTALTERNATE)
4477 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
4481 if (compose_check_for_valid_recipient(compose) == FALSE) {
4482 if (compose->batch) {
4483 gtk_widget_show_all(compose->window);
4485 alertpanel_error(_("Recipient is not specified."));
4489 if (compose_check_for_set_recipients(compose) == FALSE) {
4493 if (!compose->batch) {
4494 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
4495 if (*str == '\0' && check_everything == TRUE &&
4496 compose->mode != COMPOSE_REDIRECT) {
4498 gchar *button_label;
4501 if (compose->sending)
4502 button_label = _("+_Send");
4504 button_label = _("+_Queue");
4505 message = g_strdup_printf(_("Subject is empty. %s it anyway?"),
4506 compose->sending?_("Send"):_("Queue"));
4508 aval = alertpanel(compose->sending?_("Send"):_("Send later"), message,
4509 GTK_STOCK_CANCEL, button_label, NULL);
4511 if (aval != G_ALERTALTERNATE)
4516 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
4522 gint compose_send(Compose *compose)
4525 FolderItem *folder = NULL;
4527 gchar *msgpath = NULL;
4528 gboolean discard_window = FALSE;
4529 gchar *errstr = NULL;
4530 gchar *tmsgid = NULL;
4531 MainWindow *mainwin = mainwindow_get_mainwindow();
4532 gboolean queued_removed = FALSE;
4534 if (prefs_common.send_dialog_invisible
4535 || compose->batch == TRUE)
4536 discard_window = TRUE;
4538 compose_allow_user_actions (compose, FALSE);
4539 compose->sending = TRUE;
4541 if (compose_check_entries(compose, TRUE) == FALSE) {
4542 if (compose->batch) {
4543 gtk_widget_show_all(compose->window);
4549 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
4552 if (compose->batch) {
4553 gtk_widget_show_all(compose->window);
4556 alertpanel_error(_("Could not queue message for sending:\n\n"
4557 "Charset conversion failed."));
4558 } else if (val == -5) {
4559 alertpanel_error(_("Could not queue message for sending:\n\n"
4560 "Couldn't get recipient encryption key."));
4561 } else if (val == -6) {
4563 } else if (val == -3) {
4564 if (privacy_peek_error())
4565 alertpanel_error(_("Could not queue message for sending:\n\n"
4566 "Signature failed: %s"), privacy_get_error());
4567 } else if (val == -2 && errno != 0) {
4568 alertpanel_error(_("Could not queue message for sending:\n\n%s."), strerror(errno));
4570 alertpanel_error(_("Could not queue message for sending."));
4575 tmsgid = g_strdup(compose->msgid);
4576 if (discard_window) {
4577 compose->sending = FALSE;
4578 compose_close(compose);
4579 /* No more compose access in the normal codepath
4580 * after this point! */
4585 alertpanel_error(_("The message was queued but could not be "
4586 "sent.\nUse \"Send queued messages\" from "
4587 "the main window to retry."));
4588 if (!discard_window) {
4595 if (msgpath == NULL) {
4596 msgpath = folder_item_fetch_msg(folder, msgnum);
4597 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
4600 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
4604 if (!discard_window) {
4606 if (!queued_removed)
4607 folder_item_remove_msg(folder, msgnum);
4608 folder_item_scan(folder);
4610 /* make sure we delete that */
4611 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
4613 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
4614 folder_item_remove_msg(folder, tmp->msgnum);
4615 procmsg_msginfo_free(tmp);
4622 if (!queued_removed)
4623 folder_item_remove_msg(folder, msgnum);
4624 folder_item_scan(folder);
4626 /* make sure we delete that */
4627 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
4629 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
4630 folder_item_remove_msg(folder, tmp->msgnum);
4631 procmsg_msginfo_free(tmp);
4634 if (!discard_window) {
4635 compose->sending = FALSE;
4636 compose_allow_user_actions (compose, TRUE);
4637 compose_close(compose);
4641 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
4642 "the main window to retry."), errstr);
4645 alertpanel_error_log(_("The message was queued but could not be "
4646 "sent.\nUse \"Send queued messages\" from "
4647 "the main window to retry."));
4649 if (!discard_window) {
4658 toolbar_main_set_sensitive(mainwin);
4659 main_window_set_menu_sensitive(mainwin);
4665 compose_allow_user_actions (compose, TRUE);
4666 compose->sending = FALSE;
4667 compose->modified = TRUE;
4668 toolbar_main_set_sensitive(mainwin);
4669 main_window_set_menu_sensitive(mainwin);
4674 static gboolean compose_use_attach(Compose *compose)
4676 GtkTreeModel *model = gtk_tree_view_get_model
4677 (GTK_TREE_VIEW(compose->attach_clist));
4678 return gtk_tree_model_iter_n_children(model, NULL) > 0;
4681 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
4684 gchar buf[BUFFSIZE];
4686 gboolean first_to_address;
4687 gboolean first_cc_address;
4689 ComposeHeaderEntry *headerentry;
4690 const gchar *headerentryname;
4691 const gchar *cc_hdr;
4692 const gchar *to_hdr;
4694 debug_print("Writing redirect header\n");
4696 cc_hdr = prefs_common_translated_header_name("Cc:");
4697 to_hdr = prefs_common_translated_header_name("To:");
4699 first_to_address = TRUE;
4700 for (list = compose->header_list; list; list = list->next) {
4701 headerentry = ((ComposeHeaderEntry *)list->data);
4702 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(headerentry->combo)->entry));
4704 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
4705 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
4706 Xstrdup_a(str, entstr, return -1);
4708 if (str[0] != '\0') {
4709 compose_convert_header
4710 (compose, buf, sizeof(buf), str,
4711 strlen("Resent-To") + 2, TRUE);
4713 if (first_to_address) {
4714 fprintf(fp, "Resent-To: ");
4715 first_to_address = FALSE;
4719 fprintf(fp, "%s", buf);
4723 if (!first_to_address) {
4727 first_cc_address = TRUE;
4728 for (list = compose->header_list; list; list = list->next) {
4729 headerentry = ((ComposeHeaderEntry *)list->data);
4730 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(headerentry->combo)->entry));
4732 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
4733 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
4734 Xstrdup_a(str, strg, return -1);
4736 if (str[0] != '\0') {
4737 compose_convert_header
4738 (compose, buf, sizeof(buf), str,
4739 strlen("Resent-Cc") + 2, TRUE);
4741 if (first_cc_address) {
4742 fprintf(fp, "Resent-Cc: ");
4743 first_cc_address = FALSE;
4747 fprintf(fp, "%s", buf);
4751 if (!first_cc_address) {
4758 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
4760 gchar buf[BUFFSIZE];
4762 const gchar *entstr;
4763 /* struct utsname utsbuf; */
4765 g_return_val_if_fail(fp != NULL, -1);
4766 g_return_val_if_fail(compose->account != NULL, -1);
4767 g_return_val_if_fail(compose->account->address != NULL, -1);
4770 get_rfc822_date(buf, sizeof(buf));
4771 fprintf(fp, "Resent-Date: %s\n", buf);
4774 if (compose->account->name && *compose->account->name) {
4775 compose_convert_header
4776 (compose, buf, sizeof(buf), compose->account->name,
4777 strlen("From: "), TRUE);
4778 fprintf(fp, "Resent-From: %s <%s>\n",
4779 buf, compose->account->address);
4781 fprintf(fp, "Resent-From: %s\n", compose->account->address);
4784 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
4785 if (*entstr != '\0') {
4786 Xstrdup_a(str, entstr, return -1);
4789 compose_convert_header(compose, buf, sizeof(buf), str,
4790 strlen("Subject: "), FALSE);
4791 fprintf(fp, "Subject: %s\n", buf);
4795 /* Resent-Message-ID */
4796 if (compose->account->gen_msgid) {
4797 generate_msgid(buf, sizeof(buf));
4798 fprintf(fp, "Resent-Message-ID: <%s>\n", buf);
4799 compose->msgid = g_strdup(buf);
4802 compose_redirect_write_headers_from_headerlist(compose, fp);
4804 /* separator between header and body */
4810 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
4814 gchar buf[BUFFSIZE];
4816 gboolean skip = FALSE;
4817 gchar *not_included[]={
4818 "Return-Path:", "Delivered-To:", "Received:",
4819 "Subject:", "X-UIDL:", "AF:",
4820 "NF:", "PS:", "SRH:",
4821 "SFN:", "DSR:", "MID:",
4822 "CFG:", "PT:", "S:",
4823 "RQ:", "SSV:", "NSV:",
4824 "SSH:", "R:", "MAID:",
4825 "NAID:", "RMID:", "FMID:",
4826 "SCF:", "RRCPT:", "NG:",
4827 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
4828 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
4829 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
4830 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
4833 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
4834 FILE_OP_ERROR(compose->redirect_filename, "fopen");
4838 while (procheader_get_one_field_asis(buf, sizeof(buf), fp) != -1) {
4840 for (i = 0; not_included[i] != NULL; i++) {
4841 if (g_ascii_strncasecmp(buf, not_included[i],
4842 strlen(not_included[i])) == 0) {
4849 if (fputs(buf, fdest) == -1)
4852 if (!prefs_common.redirect_keep_from) {
4853 if (g_ascii_strncasecmp(buf, "From:",
4854 strlen("From:")) == 0) {
4855 fputs(" (by way of ", fdest);
4856 if (compose->account->name
4857 && *compose->account->name) {
4858 compose_convert_header
4859 (compose, buf, sizeof(buf),
4860 compose->account->name,
4863 fprintf(fdest, "%s <%s>",
4865 compose->account->address);
4867 fprintf(fdest, "%s",
4868 compose->account->address);
4873 if (fputs("\n", fdest) == -1)
4877 compose_redirect_write_headers(compose, fdest);
4879 while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
4880 if (fwrite(buf, sizeof(gchar), len, fdest) != len)
4893 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
4895 GtkTextBuffer *buffer;
4896 GtkTextIter start, end;
4899 const gchar *out_codeset;
4900 EncodingType encoding;
4901 MimeInfo *mimemsg, *mimetext;
4904 if (action == COMPOSE_WRITE_FOR_SEND)
4905 attach_parts = TRUE;
4907 /* create message MimeInfo */
4908 mimemsg = procmime_mimeinfo_new();
4909 mimemsg->type = MIMETYPE_MESSAGE;
4910 mimemsg->subtype = g_strdup("rfc822");
4911 mimemsg->content = MIMECONTENT_MEM;
4912 mimemsg->tmp = TRUE; /* must free content later */
4913 mimemsg->data.mem = compose_get_header(compose);
4915 /* Create text part MimeInfo */
4916 /* get all composed text */
4917 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
4918 gtk_text_buffer_get_start_iter(buffer, &start);
4919 gtk_text_buffer_get_end_iter(buffer, &end);
4920 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
4921 if (is_ascii_str(chars)) {
4924 out_codeset = CS_US_ASCII;
4925 encoding = ENC_7BIT;
4927 const gchar *src_codeset = CS_INTERNAL;
4929 out_codeset = conv_get_charset_str(compose->out_encoding);
4932 gchar *test_conv_global_out = NULL;
4933 gchar *test_conv_reply = NULL;
4935 /* automatic mode. be automatic. */
4936 codeconv_set_strict(TRUE);
4938 out_codeset = conv_get_outgoing_charset_str();
4940 debug_print("trying to convert to %s\n", out_codeset);
4941 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
4944 if (!test_conv_global_out && compose->orig_charset
4945 && strcmp(compose->orig_charset, CS_US_ASCII)) {
4946 out_codeset = compose->orig_charset;
4947 debug_print("failure; trying to convert to %s\n", out_codeset);
4948 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
4951 if (!test_conv_global_out && !test_conv_reply) {
4953 out_codeset = CS_INTERNAL;
4954 debug_print("failure; finally using %s\n", out_codeset);
4956 g_free(test_conv_global_out);
4957 g_free(test_conv_reply);
4958 codeconv_set_strict(FALSE);
4961 if (!g_ascii_strcasecmp(out_codeset, CS_US_ASCII))
4962 out_codeset = CS_ISO_8859_1;
4964 if (prefs_common.encoding_method == CTE_BASE64)
4965 encoding = ENC_BASE64;
4966 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
4967 encoding = ENC_QUOTED_PRINTABLE;
4968 else if (prefs_common.encoding_method == CTE_8BIT)
4969 encoding = ENC_8BIT;
4971 encoding = procmime_get_encoding_for_charset(out_codeset);
4973 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
4974 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
4976 if (action == COMPOSE_WRITE_FOR_SEND) {
4977 codeconv_set_strict(TRUE);
4978 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
4979 codeconv_set_strict(FALSE);
4985 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
4986 "to the specified %s charset.\n"
4987 "Send it as %s?"), out_codeset, src_codeset);
4988 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL, _("+_Send"), NULL, FALSE,
4989 NULL, ALERT_ERROR, G_ALERTDEFAULT);
4992 if (aval != G_ALERTALTERNATE) {
4997 out_codeset = src_codeset;
5003 out_codeset = src_codeset;
5009 if (encoding == ENC_8BIT || encoding == ENC_7BIT) {
5010 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5011 strstr(buf, "\nFrom ") != NULL) {
5012 encoding = ENC_QUOTED_PRINTABLE;
5016 mimetext = procmime_mimeinfo_new();
5017 mimetext->content = MIMECONTENT_MEM;
5018 mimetext->tmp = TRUE; /* must free content later */
5019 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5020 * and free the data, which we need later. */
5021 mimetext->data.mem = g_strdup(buf);
5022 mimetext->type = MIMETYPE_TEXT;
5023 mimetext->subtype = g_strdup("plain");
5024 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5025 g_strdup(out_codeset));
5027 /* protect trailing spaces when signing message */
5028 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5029 privacy_system_can_sign(compose->privacy_system)) {
5030 encoding = ENC_QUOTED_PRINTABLE;
5033 debug_print("main text: %d bytes encoded as %s in %d\n",
5034 strlen(buf), out_codeset, encoding);
5036 /* check for line length limit */
5037 if (action == COMPOSE_WRITE_FOR_SEND &&
5038 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5039 check_line_length(buf, 1000, &line) < 0) {
5043 msg = g_strdup_printf
5044 (_("Line %d exceeds the line length limit (998 bytes).\n"
5045 "The contents of the message might be broken on the way to the delivery.\n"
5047 "Send it anyway?"), line + 1);
5048 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5050 if (aval != G_ALERTALTERNATE) {
5056 if (encoding != ENC_UNKNOWN)
5057 procmime_encode_content(mimetext, encoding);
5059 /* append attachment parts */
5060 if (compose_use_attach(compose) && attach_parts) {
5061 MimeInfo *mimempart;
5062 gchar *boundary = NULL;
5063 mimempart = procmime_mimeinfo_new();
5064 mimempart->content = MIMECONTENT_EMPTY;
5065 mimempart->type = MIMETYPE_MULTIPART;
5066 mimempart->subtype = g_strdup("mixed");
5070 boundary = generate_mime_boundary(NULL);
5071 } while (strstr(buf, boundary) != NULL);
5073 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5076 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5078 g_node_append(mimempart->node, mimetext->node);
5079 g_node_append(mimemsg->node, mimempart->node);
5081 compose_add_attachments(compose, mimempart);
5083 g_node_append(mimemsg->node, mimetext->node);
5087 /* sign message if sending */
5088 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5089 privacy_system_can_sign(compose->privacy_system))
5090 if (!privacy_sign(compose->privacy_system, mimemsg, compose->account))
5093 procmime_write_mimeinfo(mimemsg, fp);
5095 procmime_mimeinfo_free_all(mimemsg);
5100 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5102 GtkTextBuffer *buffer;
5103 GtkTextIter start, end;
5108 if ((fp = g_fopen(file, "wb")) == NULL) {
5109 FILE_OP_ERROR(file, "fopen");
5113 /* chmod for security */
5114 if (change_file_mode_rw(fp, file) < 0) {
5115 FILE_OP_ERROR(file, "chmod");
5116 g_warning("can't change file mode\n");
5119 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5120 gtk_text_buffer_get_start_iter(buffer, &start);
5121 gtk_text_buffer_get_end_iter(buffer, &end);
5122 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5124 chars = conv_codeset_strdup
5125 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5128 if (!chars) return -1;
5131 len = strlen(chars);
5132 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5133 FILE_OP_ERROR(file, "fwrite");
5142 if (fclose(fp) == EOF) {
5143 FILE_OP_ERROR(file, "fclose");
5150 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5153 MsgInfo *msginfo = compose->targetinfo;
5155 g_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5156 if (!msginfo) return -1;
5158 if (!force && MSG_IS_LOCKED(msginfo->flags))
5161 item = msginfo->folder;
5162 g_return_val_if_fail(item != NULL, -1);
5164 if (procmsg_msg_exist(msginfo) &&
5165 (folder_has_parent_of_type(item, F_QUEUE) ||
5166 folder_has_parent_of_type(item, F_DRAFT)
5167 || msginfo == compose->autosaved_draft)) {
5168 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5169 g_warning("can't remove the old message\n");
5172 debug_print("removed reedit target %d\n", msginfo->msgnum);
5179 static void compose_remove_draft(Compose *compose)
5182 MsgInfo *msginfo = compose->targetinfo;
5183 drafts = account_get_special_folder(compose->account, F_DRAFT);
5185 if (procmsg_msg_exist(msginfo)) {
5186 folder_item_remove_msg(drafts, msginfo->msgnum);
5191 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5192 gboolean remove_reedit_target)
5194 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5197 static gboolean compose_warn_encryption(Compose *compose)
5199 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5200 AlertValue val = G_ALERTALTERNATE;
5202 if (warning == NULL)
5205 val = alertpanel_full(_("Encryption warning"), warning,
5206 GTK_STOCK_CANCEL, _("+C_ontinue"), NULL,
5207 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5208 if (val & G_ALERTDISABLE) {
5209 val &= ~G_ALERTDISABLE;
5210 if (val == G_ALERTALTERNATE)
5211 privacy_inhibit_encrypt_warning(compose->privacy_system,
5215 if (val == G_ALERTALTERNATE) {
5222 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
5223 gchar **msgpath, gboolean check_subject,
5224 gboolean remove_reedit_target)
5231 static gboolean lock = FALSE;
5232 PrefsAccount *mailac = NULL, *newsac = NULL;
5234 debug_print("queueing message...\n");
5235 g_return_val_if_fail(compose->account != NULL, -1);
5239 if (compose_check_entries(compose, check_subject) == FALSE) {
5241 if (compose->batch) {
5242 gtk_widget_show_all(compose->window);
5247 if (!compose->to_list && !compose->newsgroup_list) {
5248 g_warning("can't get recipient list.");
5253 if (compose->to_list) {
5254 if (compose->account->protocol != A_NNTP)
5255 mailac = compose->account;
5256 else if (cur_account && cur_account->protocol != A_NNTP)
5257 mailac = cur_account;
5258 else if (!(mailac = compose_current_mail_account())) {
5260 alertpanel_error(_("No account for sending mails available!"));
5265 if (compose->newsgroup_list) {
5266 if (compose->account->protocol == A_NNTP)
5267 newsac = compose->account;
5268 else if (!newsac->protocol != A_NNTP) {
5270 alertpanel_error(_("No account for posting news available!"));
5275 /* write queue header */
5276 tmp = g_strdup_printf("%s%cqueue.%p", get_tmp_dir(),
5277 G_DIR_SEPARATOR, compose);
5278 if ((fp = g_fopen(tmp, "wb")) == NULL) {
5279 FILE_OP_ERROR(tmp, "fopen");
5285 if (change_file_mode_rw(fp, tmp) < 0) {
5286 FILE_OP_ERROR(tmp, "chmod");
5287 g_warning("can't change file mode\n");
5290 /* queueing variables */
5291 fprintf(fp, "AF:\n");
5292 fprintf(fp, "NF:0\n");
5293 fprintf(fp, "PS:10\n");
5294 fprintf(fp, "SRH:1\n");
5295 fprintf(fp, "SFN:\n");
5296 fprintf(fp, "DSR:\n");
5298 fprintf(fp, "MID:<%s>\n", compose->msgid);
5300 fprintf(fp, "MID:\n");
5301 fprintf(fp, "CFG:\n");
5302 fprintf(fp, "PT:0\n");
5303 fprintf(fp, "S:%s\n", compose->account->address);
5304 fprintf(fp, "RQ:\n");
5306 fprintf(fp, "SSV:%s\n", mailac->smtp_server);
5308 fprintf(fp, "SSV:\n");
5310 fprintf(fp, "NSV:%s\n", newsac->nntp_server);
5312 fprintf(fp, "NSV:\n");
5313 fprintf(fp, "SSH:\n");
5314 /* write recepient list */
5315 if (compose->to_list) {
5316 fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data);
5317 for (cur = compose->to_list->next; cur != NULL;
5319 fprintf(fp, ",<%s>", (gchar *)cur->data);
5322 /* write newsgroup list */
5323 if (compose->newsgroup_list) {
5325 fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data);
5326 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
5327 fprintf(fp, ",%s", (gchar *)cur->data);
5330 /* Sylpheed account IDs */
5332 fprintf(fp, "MAID:%d\n", mailac->account_id);
5334 fprintf(fp, "NAID:%d\n", newsac->account_id);
5337 if (compose->privacy_system != NULL) {
5338 fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system);
5339 fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing);
5340 if (compose->use_encryption) {
5342 if (!compose_warn_encryption(compose)) {
5349 if (mailac && mailac->encrypt_to_self) {
5350 GSList *tmp_list = g_slist_copy(compose->to_list);
5351 tmp_list = g_slist_append(tmp_list, compose->account->address);
5352 encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
5353 g_slist_free(tmp_list);
5355 encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
5357 if (encdata != NULL) {
5358 if (strcmp(encdata, "_DONT_ENCRYPT_")) {
5359 fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption);
5360 fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
5362 } /* else we finally dont want to encrypt */
5364 fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption);
5365 /* and if encdata was null, it means there's been a problem in
5377 /* Save copy folder */
5378 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
5379 gchar *savefolderid;
5381 savefolderid = gtk_editable_get_chars(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
5382 fprintf(fp, "SCF:%s\n", savefolderid);
5383 g_free(savefolderid);
5385 /* Save copy folder */
5386 if (compose->return_receipt) {
5387 fprintf(fp, "RRCPT:1\n");
5389 /* Message-ID of message replying to */
5390 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
5393 folderid = folder_item_get_identifier(compose->replyinfo->folder);
5394 fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid);
5397 /* Message-ID of message forwarding to */
5398 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
5401 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
5402 fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid);
5406 /* end of headers */
5407 fprintf(fp, "X-Claws-End-Special-Headers: 1\n");
5409 if (compose->redirect_filename != NULL) {
5410 if (compose_redirect_write_to_file(compose, fp) < 0) {
5419 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
5424 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
5428 if (fclose(fp) == EOF) {
5429 FILE_OP_ERROR(tmp, "fclose");
5436 if (item && *item) {
5439 queue = account_get_special_folder(compose->account, F_QUEUE);
5442 g_warning("can't find queue folder\n");
5448 folder_item_scan(queue);
5449 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
5450 g_warning("can't queue the message\n");
5457 if (msgpath == NULL) {
5463 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
5464 compose_remove_reedit_target(compose, FALSE);
5467 if ((msgnum != NULL) && (item != NULL)) {
5475 static void compose_add_attachments(Compose *compose, MimeInfo *parent)
5478 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
5480 struct stat statbuf;
5481 gchar *type, *subtype;
5482 GtkTreeModel *model;
5485 model = gtk_tree_view_get_model(tree_view);
5487 if (!gtk_tree_model_get_iter_first(model, &iter))
5490 gtk_tree_model_get(model, &iter,
5494 mimepart = procmime_mimeinfo_new();
5495 mimepart->content = MIMECONTENT_FILE;
5496 mimepart->data.filename = g_strdup(ainfo->file);
5497 mimepart->tmp = FALSE; /* or we destroy our attachment */
5498 mimepart->offset = 0;
5500 stat(ainfo->file, &statbuf);
5501 mimepart->length = statbuf.st_size;
5503 type = g_strdup(ainfo->content_type);
5505 if (!strchr(type, '/')) {
5507 type = g_strdup("application/octet-stream");
5510 subtype = strchr(type, '/') + 1;
5511 *(subtype - 1) = '\0';
5512 mimepart->type = procmime_get_media_type(type);
5513 mimepart->subtype = g_strdup(subtype);
5516 if (mimepart->type == MIMETYPE_MESSAGE &&
5517 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
5518 mimepart->disposition = DISPOSITIONTYPE_INLINE;
5521 g_hash_table_insert(mimepart->typeparameters,
5522 g_strdup("name"), g_strdup(ainfo->name));
5523 g_hash_table_insert(mimepart->dispositionparameters,
5524 g_strdup("filename"), g_strdup(ainfo->name));
5525 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
5529 if (compose->use_signing) {
5530 if (ainfo->encoding == ENC_7BIT)
5531 ainfo->encoding = ENC_QUOTED_PRINTABLE;
5532 else if (ainfo->encoding == ENC_8BIT)
5533 ainfo->encoding = ENC_BASE64;
5536 procmime_encode_content(mimepart, ainfo->encoding);
5538 g_node_append(parent->node, mimepart->node);
5539 } while (gtk_tree_model_iter_next(model, &iter));
5542 #define IS_IN_CUSTOM_HEADER(header) \
5543 (compose->account->add_customhdr && \
5544 custom_header_find(compose->account->customhdr_list, header) != NULL)
5546 static void compose_add_headerfield_from_headerlist(Compose *compose,
5548 const gchar *fieldname,
5549 const gchar *seperator)
5551 gchar *str, *fieldname_w_colon;
5552 gboolean add_field = FALSE;
5554 ComposeHeaderEntry *headerentry;
5555 const gchar *headerentryname;
5556 const gchar *trans_fieldname;
5559 if (IS_IN_CUSTOM_HEADER(fieldname))
5562 debug_print("Adding %s-fields\n", fieldname);
5564 fieldstr = g_string_sized_new(64);
5566 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
5567 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
5569 for (list = compose->header_list; list; list = list->next) {
5570 headerentry = ((ComposeHeaderEntry *)list->data);
5571 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(headerentry->combo)->entry));
5573 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
5574 str = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
5576 if (str[0] != '\0') {
5578 g_string_append(fieldstr, seperator);
5579 g_string_append(fieldstr, str);
5588 buf = g_new0(gchar, fieldstr->len * 4 + 256);
5589 compose_convert_header
5590 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
5591 strlen(fieldname) + 2, TRUE);
5592 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
5596 g_free(fieldname_w_colon);
5597 g_string_free(fieldstr, TRUE);
5602 static gchar *compose_get_header(Compose *compose)
5604 gchar buf[BUFFSIZE];
5605 const gchar *entry_str;
5609 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
5611 gchar *from_name = NULL, *from_address = NULL;
5614 g_return_val_if_fail(compose->account != NULL, NULL);
5615 g_return_val_if_fail(compose->account->address != NULL, NULL);
5617 header = g_string_sized_new(64);
5620 get_rfc822_date(buf, sizeof(buf));
5621 g_string_append_printf(header, "Date: %s\n", buf);
5625 if (compose->account->name && *compose->account->name) {
5627 QUOTE_IF_REQUIRED(buf, compose->account->name);
5628 tmp = g_strdup_printf("%s <%s>",
5629 buf, compose->account->address);
5631 tmp = g_strdup_printf("%s",
5632 compose->account->address);
5634 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
5635 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
5637 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
5638 from_address = g_strdup(compose->account->address);
5640 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5641 /* extract name and address */
5642 if (strstr(spec, " <") && strstr(spec, ">")) {
5643 from_address = g_strdup(strrchr(spec, '<')+1);
5644 *(strrchr(from_address, '>')) = '\0';
5645 from_name = g_strdup(spec);
5646 *(strrchr(from_name, '<')) = '\0';
5649 from_address = g_strdup(spec);
5656 if (from_name && *from_name) {
5657 compose_convert_header
5658 (compose, buf, sizeof(buf), from_name,
5659 strlen("From: "), TRUE);
5660 QUOTE_IF_REQUIRED(name, buf);
5662 g_string_append_printf(header, "From: %s <%s>\n",
5663 name, from_address);
5665 g_string_append_printf(header, "From: %s\n", from_address);
5668 g_free(from_address);
5671 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
5674 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
5677 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
5680 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
5683 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
5685 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
5688 compose_convert_header(compose, buf, sizeof(buf), str,
5689 strlen("Subject: "), FALSE);
5690 g_string_append_printf(header, "Subject: %s\n", buf);
5696 if (compose->account->gen_msgid) {
5697 generate_msgid(buf, sizeof(buf));
5698 g_string_append_printf(header, "Message-ID: <%s>\n", buf);
5699 compose->msgid = g_strdup(buf);
5702 if (compose->remove_references == FALSE) {
5704 if (compose->inreplyto && compose->to_list)
5705 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
5708 if (compose->references)
5709 g_string_append_printf(header, "References: %s\n", compose->references);
5713 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
5716 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
5719 if (compose->account->organization &&
5720 strlen(compose->account->organization) &&
5721 !IS_IN_CUSTOM_HEADER("Organization")) {
5722 compose_convert_header(compose, buf, sizeof(buf),
5723 compose->account->organization,
5724 strlen("Organization: "), FALSE);
5725 g_string_append_printf(header, "Organization: %s\n", buf);
5728 /* Program version and system info */
5729 if (g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
5730 !compose->newsgroup_list) {
5731 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
5733 gtk_major_version, gtk_minor_version, gtk_micro_version,
5736 if (g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
5737 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
5739 gtk_major_version, gtk_minor_version, gtk_micro_version,
5743 /* custom headers */
5744 if (compose->account->add_customhdr) {
5747 for (cur = compose->account->customhdr_list; cur != NULL;
5749 CustomHeader *chdr = (CustomHeader *)cur->data;
5751 if (custom_header_is_allowed(chdr->name)) {
5752 compose_convert_header
5753 (compose, buf, sizeof(buf),
5754 chdr->value ? chdr->value : "",
5755 strlen(chdr->name) + 2, FALSE);
5756 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
5762 switch (compose->priority) {
5763 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
5764 "X-Priority: 1 (Highest)\n");
5766 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
5767 "X-Priority: 2 (High)\n");
5769 case PRIORITY_NORMAL: break;
5770 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
5771 "X-Priority: 4 (Low)\n");
5773 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
5774 "X-Priority: 5 (Lowest)\n");
5776 default: debug_print("compose: priority unknown : %d\n",
5780 /* Request Return Receipt */
5781 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To")) {
5782 if (compose->return_receipt) {
5783 if (compose->account->name
5784 && *compose->account->name) {
5785 compose_convert_header(compose, buf, sizeof(buf),
5786 compose->account->name,
5787 strlen("Disposition-Notification-To: "),
5789 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, compose->account->address);
5791 g_string_append_printf(header, "Disposition-Notification-To: %s\n", compose->account->address);
5795 /* get special headers */
5796 for (list = compose->header_list; list; list = list->next) {
5797 ComposeHeaderEntry *headerentry;
5800 gchar *headername_wcolon;
5801 const gchar *headername_trans;
5804 gboolean standard_header = FALSE;
5806 headerentry = ((ComposeHeaderEntry *)list->data);
5808 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(headerentry->combo)->entry)));
5809 if (strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
5814 if (!strstr(tmp, ":")) {
5815 headername_wcolon = g_strconcat(tmp, ":", NULL);
5816 headername = g_strdup(tmp);
5818 headername_wcolon = g_strdup(tmp);
5819 headername = g_strdup(strtok(tmp, ":"));
5823 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5824 Xstrdup_a(headervalue, entry_str, return NULL);
5825 subst_char(headervalue, '\r', ' ');
5826 subst_char(headervalue, '\n', ' ');
5827 string = std_headers;
5828 while (*string != NULL) {
5829 headername_trans = prefs_common_translated_header_name(*string);
5830 if (!strcmp(headername_trans, headername_wcolon))
5831 standard_header = TRUE;
5834 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
5835 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
5838 g_free(headername_wcolon);
5842 g_string_free(header, FALSE);
5847 #undef IS_IN_CUSTOM_HEADER
5849 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
5850 gint header_len, gboolean addr_field)
5852 gchar *tmpstr = NULL;
5853 const gchar *out_codeset = NULL;
5855 g_return_if_fail(src != NULL);
5856 g_return_if_fail(dest != NULL);
5858 if (len < 1) return;
5860 tmpstr = g_strdup(src);
5862 subst_char(tmpstr, '\n', ' ');
5863 subst_char(tmpstr, '\r', ' ');
5866 if (!g_utf8_validate(tmpstr, -1, NULL)) {
5867 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
5868 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
5873 codeconv_set_strict(TRUE);
5874 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
5875 conv_get_charset_str(compose->out_encoding));
5876 codeconv_set_strict(FALSE);
5878 if (!dest || *dest == '\0') {
5879 gchar *test_conv_global_out = NULL;
5880 gchar *test_conv_reply = NULL;
5882 /* automatic mode. be automatic. */
5883 codeconv_set_strict(TRUE);
5885 out_codeset = conv_get_outgoing_charset_str();
5887 debug_print("trying to convert to %s\n", out_codeset);
5888 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
5891 if (!test_conv_global_out && compose->orig_charset
5892 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5893 out_codeset = compose->orig_charset;
5894 debug_print("failure; trying to convert to %s\n", out_codeset);
5895 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
5898 if (!test_conv_global_out && !test_conv_reply) {
5900 out_codeset = CS_INTERNAL;
5901 debug_print("finally using %s\n", out_codeset);
5903 g_free(test_conv_global_out);
5904 g_free(test_conv_reply);
5905 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
5907 codeconv_set_strict(FALSE);
5912 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
5916 g_return_if_fail(user_data != NULL);
5918 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
5919 g_strstrip(address);
5920 if (*address != '\0') {
5921 gchar *name = procheader_get_fromname(address);
5922 extract_address(address);
5923 addressbook_add_contact(name, address, NULL);
5928 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
5930 GtkWidget *menuitem;
5933 g_return_if_fail(menu != NULL);
5934 g_return_if_fail(GTK_IS_MENU_SHELL(menu));
5936 menuitem = gtk_separator_menu_item_new();
5937 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
5938 gtk_widget_show(menuitem);
5940 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
5941 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
5943 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
5944 g_strstrip(address);
5945 if (*address == '\0') {
5946 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
5949 g_signal_connect(G_OBJECT(menuitem), "activate",
5950 G_CALLBACK(compose_add_to_addressbook_cb), entry);
5951 gtk_widget_show(menuitem);
5954 static void compose_create_header_entry(Compose *compose)
5956 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
5960 GList *combo_list = NULL;
5962 const gchar *header = NULL;
5963 ComposeHeaderEntry *headerentry;
5964 gboolean standard_header = FALSE;
5966 headerentry = g_new0(ComposeHeaderEntry, 1);
5969 combo = gtk_combo_new();
5971 while(*string != NULL) {
5972 combo_list = g_list_append(combo_list, (gchar*)prefs_common_translated_header_name(*string));
5975 gtk_combo_set_popdown_strings(GTK_COMBO(combo), combo_list);
5976 g_list_free(combo_list);
5977 gtk_editable_set_editable(GTK_EDITABLE(GTK_COMBO(combo)->entry), TRUE);
5978 g_signal_connect(G_OBJECT(GTK_COMBO(combo)->entry), "grab_focus",
5979 G_CALLBACK(compose_grab_focus_cb), compose);
5980 gtk_widget_show(combo);
5981 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
5982 compose->header_nextrow, compose->header_nextrow+1,
5983 GTK_SHRINK, GTK_FILL, 0, 0);
5984 if (compose->header_last) {
5985 const gchar *last_header_entry = gtk_entry_get_text(
5986 GTK_ENTRY(GTK_COMBO(compose->header_last->combo)->entry));
5988 while (*string != NULL) {
5989 if (!strcmp(*string, last_header_entry))
5990 standard_header = TRUE;
5993 if (standard_header)
5994 header = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(compose->header_last->combo)->entry));
5996 if (!compose->header_last || !standard_header) {
5997 switch(compose->account->protocol) {
5999 header = prefs_common_translated_header_name("Newsgroups:");
6002 header = prefs_common_translated_header_name("To:");
6007 gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(combo)->entry), header);
6009 g_signal_connect_after(G_OBJECT(GTK_COMBO(combo)->entry), "grab_focus",
6010 G_CALLBACK(compose_grab_focus_cb), compose);
6013 entry = gtk_entry_new();
6014 gtk_widget_show(entry);
6015 gtk_tooltips_set_tip(compose->tooltips, entry,
6016 _("Use <tab> to autocomplete from addressbook"), NULL);
6017 gtk_table_attach(GTK_TABLE(compose->header_table), entry, 1, 2,
6018 compose->header_nextrow, compose->header_nextrow+1,
6019 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
6021 g_signal_connect(G_OBJECT(entry), "key-press-event",
6022 G_CALLBACK(compose_headerentry_key_press_event_cb),
6024 g_signal_connect(G_OBJECT(entry), "changed",
6025 G_CALLBACK(compose_headerentry_changed_cb),
6027 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
6028 G_CALLBACK(compose_grab_focus_cb), compose);
6031 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6032 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6033 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6034 g_signal_connect(G_OBJECT(entry), "drag_data_received",
6035 G_CALLBACK(compose_header_drag_received_cb),
6037 g_signal_connect(G_OBJECT(entry), "drag-drop",
6038 G_CALLBACK(compose_drag_drop),
6040 g_signal_connect(G_OBJECT(entry), "populate-popup",
6041 G_CALLBACK(compose_entry_popup_extend),
6044 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
6046 headerentry->compose = compose;
6047 headerentry->combo = combo;
6048 headerentry->entry = entry;
6049 headerentry->headernum = compose->header_nextrow;
6051 compose->header_nextrow++;
6052 compose->header_last = headerentry;
6053 compose->header_list =
6054 g_slist_append(compose->header_list,
6058 static void compose_add_header_entry(Compose *compose, const gchar *header, gchar *text)
6060 ComposeHeaderEntry *last_header;
6062 last_header = compose->header_last;
6064 gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(last_header->combo)->entry), header);
6065 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
6068 static void compose_remove_header_entries(Compose *compose)
6071 for (list = compose->header_list; list; list = list->next) {
6072 ComposeHeaderEntry *headerentry =
6073 (ComposeHeaderEntry *)list->data;
6074 gtk_widget_destroy(headerentry->combo);
6075 gtk_widget_destroy(headerentry->entry);
6076 g_free(headerentry);
6078 compose->header_last = NULL;
6079 g_slist_free(compose->header_list);
6080 compose->header_list = NULL;
6081 compose->header_nextrow = 1;
6082 compose_create_header_entry(compose);
6085 static GtkWidget *compose_create_header(Compose *compose)
6087 GtkWidget *from_optmenu_hbox;
6088 GtkWidget *header_scrolledwin;
6089 GtkWidget *header_table;
6093 /* header labels and entries */
6094 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
6095 gtk_widget_show(header_scrolledwin);
6096 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
6098 header_table = gtk_table_new(2, 2, FALSE);
6099 gtk_widget_show(header_table);
6100 gtk_container_set_border_width(GTK_CONTAINER(header_table), BORDER_WIDTH);
6101 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
6102 gtk_viewport_set_shadow_type(GTK_VIEWPORT(GTK_BIN(header_scrolledwin)->child), GTK_SHADOW_ETCHED_IN);
6105 /* option menu for selecting accounts */
6106 from_optmenu_hbox = compose_account_option_menu_create(compose);
6107 gtk_table_attach(GTK_TABLE(header_table), from_optmenu_hbox,
6108 0, 2, count, count + 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
6111 compose->header_table = header_table;
6112 compose->header_list = NULL;
6113 compose->header_nextrow = count;
6115 compose_create_header_entry(compose);
6117 compose->table = NULL;
6119 return header_scrolledwin ;
6122 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
6124 Compose *compose = (Compose *)data;
6125 GdkEventButton event;
6128 event.time = gtk_get_current_event_time();
6130 return attach_button_pressed(compose->attach_clist, &event, compose);
6133 static GtkWidget *compose_create_attach(Compose *compose)
6135 GtkWidget *attach_scrwin;
6136 GtkWidget *attach_clist;
6138 GtkListStore *store;
6139 GtkCellRenderer *renderer;
6140 GtkTreeViewColumn *column;
6141 GtkTreeSelection *selection;
6143 /* attachment list */
6144 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
6145 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
6146 GTK_POLICY_AUTOMATIC,
6147 GTK_POLICY_AUTOMATIC);
6148 gtk_widget_set_size_request(attach_scrwin, -1, 80);
6150 store = gtk_list_store_new(N_ATTACH_COLS,
6155 G_TYPE_AUTO_POINTER,
6157 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
6158 (GTK_TREE_MODEL(store)));
6159 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
6160 g_object_unref(store);
6162 renderer = gtk_cell_renderer_text_new();
6163 column = gtk_tree_view_column_new_with_attributes
6164 (_("Mime type"), renderer, "text",
6165 COL_MIMETYPE, NULL);
6166 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6168 renderer = gtk_cell_renderer_text_new();
6169 column = gtk_tree_view_column_new_with_attributes
6170 (_("Size"), renderer, "text",
6172 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6174 renderer = gtk_cell_renderer_text_new();
6175 column = gtk_tree_view_column_new_with_attributes
6176 (_("Name"), renderer, "text",
6178 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6180 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
6181 prefs_common.use_stripes_everywhere);
6182 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
6183 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
6185 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
6186 G_CALLBACK(attach_selected), compose);
6187 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
6188 G_CALLBACK(attach_button_pressed), compose);
6190 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
6191 G_CALLBACK(popup_attach_button_pressed), compose);
6193 gtk_widget_tap_and_hold_setup(GTK_WIDGET(attach_clist), NULL, NULL,
6194 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
6195 g_signal_connect(G_OBJECT(attach_clist), "tap-and-hold",
6196 G_CALLBACK(popup_attach_button_pressed), compose);
6198 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
6199 G_CALLBACK(attach_key_pressed), compose);
6202 gtk_drag_dest_set(attach_clist,
6203 GTK_DEST_DEFAULT_ALL, compose_mime_types,
6204 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6205 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6206 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
6207 G_CALLBACK(compose_attach_drag_received_cb),
6209 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
6210 G_CALLBACK(compose_drag_drop),
6213 compose->attach_scrwin = attach_scrwin;
6214 compose->attach_clist = attach_clist;
6216 return attach_scrwin;
6219 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
6220 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
6222 static GtkWidget *compose_create_others(Compose *compose)
6225 GtkWidget *savemsg_checkbtn;
6226 GtkWidget *savemsg_entry;
6227 GtkWidget *savemsg_select;
6230 gchar *folderidentifier;
6232 /* Table for settings */
6233 table = gtk_table_new(3, 1, FALSE);
6234 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
6235 gtk_widget_show(table);
6236 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
6239 /* Save Message to folder */
6240 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
6241 gtk_widget_show(savemsg_checkbtn);
6242 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6243 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6244 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
6246 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
6247 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
6249 savemsg_entry = gtk_entry_new();
6250 gtk_widget_show(savemsg_entry);
6251 gtk_table_attach_defaults(GTK_TABLE(table), savemsg_entry, 1, 2, rowcount, rowcount + 1);
6252 gtk_editable_set_editable(GTK_EDITABLE(savemsg_entry), prefs_common.savemsg);
6253 g_signal_connect_after(G_OBJECT(savemsg_entry), "grab_focus",
6254 G_CALLBACK(compose_grab_focus_cb), compose);
6255 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6256 folderidentifier = folder_item_get_identifier(account_get_special_folder
6257 (compose->account, F_OUTBOX));
6258 gtk_entry_set_text(GTK_ENTRY(savemsg_entry), folderidentifier);
6259 g_free(folderidentifier);
6262 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
6263 gtk_widget_show(savemsg_select);
6264 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6265 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
6266 G_CALLBACK(compose_savemsg_select_cb),
6271 compose->savemsg_checkbtn = savemsg_checkbtn;
6272 compose->savemsg_entry = savemsg_entry;
6277 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
6279 gtk_editable_set_editable(GTK_EDITABLE(compose->savemsg_entry),
6280 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
6283 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
6288 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL);
6291 path = folder_item_get_identifier(dest);
6293 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), path);
6297 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
6298 GdkAtom clip, GtkTextIter *insert_place);
6301 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
6305 GtkTextBuffer *buffer;
6307 if (event->button == 3) {
6309 GtkTextIter sel_start, sel_end;
6310 gboolean stuff_selected;
6312 /* move the cursor to allow GtkAspell to check the word
6313 * under the mouse */
6314 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6315 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6317 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6320 stuff_selected = gtk_text_buffer_get_selection_bounds(
6321 GTK_TEXT_VIEW(text)->buffer,
6322 &sel_start, &sel_end);
6324 gtk_text_buffer_place_cursor (GTK_TEXT_VIEW(text)->buffer, &iter);
6325 /* reselect stuff */
6327 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
6328 gtk_text_buffer_select_range(GTK_TEXT_VIEW(text)->buffer,
6329 &sel_start, &sel_end);
6331 return FALSE; /* pass the event so that the right-click goes through */
6334 if (event->button == 2) {
6339 /* get the middle-click position to paste at the correct place */
6340 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6341 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6343 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6346 entry_paste_clipboard(compose, text,
6347 prefs_common.linewrap_pastes,
6348 GDK_SELECTION_PRIMARY, &iter);
6356 static void compose_spell_menu_changed(void *data)
6358 Compose *compose = (Compose *)data;
6360 GtkWidget *menuitem;
6361 GtkWidget *parent_item;
6362 GtkMenu *menu = GTK_MENU(gtk_menu_new());
6363 GtkItemFactory *ifactory = gtk_item_factory_from_widget(compose->menubar);
6366 if (compose->gtkaspell == NULL)
6369 parent_item = gtk_item_factory_get_item(ifactory,
6370 "/Spelling/Options");
6372 /* setting the submenu removes /Spelling/Options from the factory
6373 * so we need to save it */
6375 if (parent_item == NULL) {
6376 parent_item = compose->aspell_options_menu;
6377 gtk_menu_item_remove_submenu(GTK_MENU_ITEM(parent_item));
6379 compose->aspell_options_menu = parent_item;
6381 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
6383 spell_menu = g_slist_reverse(spell_menu);
6384 for (items = spell_menu;
6385 items; items = items->next) {
6386 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
6387 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
6388 gtk_widget_show(GTK_WIDGET(menuitem));
6390 g_slist_free(spell_menu);
6392 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
6397 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
6399 Compose *compose = (Compose *)data;
6400 GdkEventButton event;
6403 event.time = gtk_get_current_event_time();
6405 return text_clicked(compose->text, &event, compose);
6408 static gboolean compose_force_window_origin = TRUE;
6409 static Compose *compose_create(PrefsAccount *account,
6418 GtkWidget *handlebox;
6420 GtkWidget *notebook;
6425 GtkWidget *subject_hbox;
6426 GtkWidget *subject_frame;
6427 GtkWidget *subject_entry;
6431 GtkWidget *edit_vbox;
6432 GtkWidget *ruler_hbox;
6434 GtkWidget *scrolledwin;
6436 GtkTextBuffer *buffer;
6437 GtkClipboard *clipboard;
6439 UndoMain *undostruct;
6441 gchar *titles[N_ATTACH_COLS];
6442 guint n_menu_entries;
6443 GtkWidget *popupmenu;
6444 GtkItemFactory *popupfactory;
6445 GtkItemFactory *ifactory;
6446 GtkWidget *tmpl_menu;
6448 GtkWidget *menuitem;
6451 GtkAspell * gtkaspell = NULL;
6454 static GdkGeometry geometry;
6456 g_return_val_if_fail(account != NULL, NULL);
6458 debug_print("Creating compose window...\n");
6459 compose = g_new0(Compose, 1);
6461 titles[COL_MIMETYPE] = _("MIME type");
6462 titles[COL_SIZE] = _("Size");
6463 titles[COL_NAME] = _("Name");
6465 compose->batch = batch;
6466 compose->account = account;
6467 compose->folder = folder;
6469 compose->mutex = g_mutex_new();
6470 compose->set_cursor_pos = -1;
6472 compose->tooltips = gtk_tooltips_new();
6474 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
6476 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
6477 gtk_widget_set_size_request(window, -1, prefs_common.compose_height);
6479 if (!geometry.max_width) {
6480 geometry.max_width = gdk_screen_width();
6481 geometry.max_height = gdk_screen_height();
6484 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
6485 &geometry, GDK_HINT_MAX_SIZE);
6486 if (!geometry.min_width) {
6487 geometry.min_width = 600;
6488 geometry.min_height = 480;
6490 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
6491 &geometry, GDK_HINT_MIN_SIZE);
6494 if (compose_force_window_origin)
6495 gtk_widget_set_uposition(window, prefs_common.compose_x,
6496 prefs_common.compose_y);
6498 g_signal_connect(G_OBJECT(window), "delete_event",
6499 G_CALLBACK(compose_delete_cb), compose);
6500 MANAGE_WINDOW_SIGNALS_CONNECT(window);
6501 gtk_widget_realize(window);
6503 gtkut_widget_set_composer_icon(window);
6505 vbox = gtk_vbox_new(FALSE, 0);
6506 gtk_container_add(GTK_CONTAINER(window), vbox);
6508 n_menu_entries = sizeof(compose_entries) / sizeof(compose_entries[0]);
6509 menubar = menubar_create(window, compose_entries,
6510 n_menu_entries, "<Compose>", compose);
6511 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
6513 if (prefs_common.toolbar_detachable) {
6514 handlebox = gtk_handle_box_new();
6516 handlebox = gtk_hbox_new(FALSE, 0);
6518 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
6520 gtk_widget_realize(handlebox);
6522 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, window,
6525 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
6529 vbox2 = gtk_vbox_new(FALSE, 2);
6530 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
6531 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
6534 notebook = gtk_notebook_new();
6535 gtk_widget_set_size_request(notebook, -1, 130);
6536 gtk_widget_show(notebook);
6538 /* header labels and entries */
6539 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6540 compose_create_header(compose),
6541 gtk_label_new_with_mnemonic(_("Hea_der")));
6542 /* attachment list */
6543 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6544 compose_create_attach(compose),
6545 gtk_label_new_with_mnemonic(_("_Attachments")));
6547 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6548 compose_create_others(compose),
6549 gtk_label_new_with_mnemonic(_("Othe_rs")));
6552 subject_hbox = gtk_hbox_new(FALSE, 0);
6553 gtk_widget_show(subject_hbox);
6555 subject_frame = gtk_frame_new(NULL);
6556 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
6557 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
6558 gtk_widget_show(subject_frame);
6560 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
6561 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
6562 gtk_widget_show(subject);
6564 label = gtk_label_new(_("Subject:"));
6565 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
6566 gtk_widget_show(label);
6568 subject_entry = gtk_entry_new();
6569 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
6570 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
6571 G_CALLBACK(compose_grab_focus_cb), compose);
6572 gtk_widget_show(subject_entry);
6573 compose->subject_entry = subject_entry;
6574 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
6576 edit_vbox = gtk_vbox_new(FALSE, 0);
6578 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
6581 ruler_hbox = gtk_hbox_new(FALSE, 0);
6582 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
6584 ruler = gtk_shruler_new();
6585 gtk_ruler_set_range(GTK_RULER(ruler), 0.0, 100.0, 1.0, 100.0);
6586 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
6590 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
6591 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
6592 GTK_POLICY_AUTOMATIC,
6593 GTK_POLICY_AUTOMATIC);
6594 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
6596 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
6597 gtk_widget_set_size_request(scrolledwin, prefs_common.compose_width, -1);
6599 text = gtk_text_view_new();
6600 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
6601 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
6602 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
6603 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
6604 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
6606 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
6608 g_signal_connect_after(G_OBJECT(text), "size_allocate",
6609 G_CALLBACK(compose_edit_size_alloc),
6611 g_signal_connect(G_OBJECT(buffer), "changed",
6612 G_CALLBACK(compose_changed_cb), compose);
6613 g_signal_connect(G_OBJECT(text), "grab_focus",
6614 G_CALLBACK(compose_grab_focus_cb), compose);
6615 g_signal_connect(G_OBJECT(buffer), "insert_text",
6616 G_CALLBACK(text_inserted), compose);
6617 g_signal_connect(G_OBJECT(text), "button_press_event",
6618 G_CALLBACK(text_clicked), compose);
6620 g_signal_connect(G_OBJECT(text), "popup-menu",
6621 G_CALLBACK(compose_popup_menu), compose);
6623 gtk_widget_tap_and_hold_setup(GTK_WIDGET(text), NULL, NULL,
6624 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
6625 g_signal_connect(G_OBJECT(text), "tap-and-hold",
6626 G_CALLBACK(compose_popup_menu), compose);
6628 g_signal_connect(G_OBJECT(subject_entry), "changed",
6629 G_CALLBACK(compose_changed_cb), compose);
6632 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6633 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6634 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6635 g_signal_connect(G_OBJECT(text), "drag_data_received",
6636 G_CALLBACK(compose_insert_drag_received_cb),
6638 g_signal_connect(G_OBJECT(text), "drag-drop",
6639 G_CALLBACK(compose_drag_drop),
6641 gtk_widget_show_all(vbox);
6643 /* pane between attach clist and text */
6644 paned = gtk_vpaned_new();
6645 gtk_paned_set_gutter_size(GTK_PANED(paned), 12);
6646 gtk_container_add(GTK_CONTAINER(vbox2), paned);
6648 if( maemo_mainwindow_is_fullscreen(mainwindow_get_mainwindow()->window) )
6649 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 300 : 280);
6651 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 250 : 230);
6653 gtk_paned_add1(GTK_PANED(paned), notebook);
6654 gtk_paned_add2(GTK_PANED(paned), edit_vbox);
6655 gtk_widget_show_all(paned);
6658 if (prefs_common.textfont) {
6659 PangoFontDescription *font_desc;
6661 font_desc = pango_font_description_from_string
6662 (prefs_common.textfont);
6664 gtk_widget_modify_font(text, font_desc);
6665 pango_font_description_free(font_desc);
6669 n_entries = sizeof(compose_popup_entries) /
6670 sizeof(compose_popup_entries[0]);
6671 popupmenu = menu_create_items(compose_popup_entries, n_entries,
6672 "<Compose>", &popupfactory,
6675 ifactory = gtk_item_factory_from_widget(menubar);
6676 menu_set_sensitive(ifactory, "/Edit/Undo", FALSE);
6677 menu_set_sensitive(ifactory, "/Edit/Redo", FALSE);
6678 menu_set_sensitive(ifactory, "/Options/Remove references", FALSE);
6680 tmpl_menu = gtk_item_factory_get_item(ifactory, "/Tools/Template");
6682 undostruct = undo_init(text);
6683 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
6686 address_completion_start(window);
6688 compose->window = window;
6689 compose->vbox = vbox;
6690 compose->menubar = menubar;
6691 compose->handlebox = handlebox;
6693 compose->vbox2 = vbox2;
6695 compose->paned = paned;
6697 compose->notebook = notebook;
6698 compose->edit_vbox = edit_vbox;
6699 compose->ruler_hbox = ruler_hbox;
6700 compose->ruler = ruler;
6701 compose->scrolledwin = scrolledwin;
6702 compose->text = text;
6704 compose->focused_editable = NULL;
6706 compose->popupmenu = popupmenu;
6707 compose->popupfactory = popupfactory;
6709 compose->tmpl_menu = tmpl_menu;
6711 compose->mode = mode;
6712 compose->rmode = mode;
6714 compose->targetinfo = NULL;
6715 compose->replyinfo = NULL;
6716 compose->fwdinfo = NULL;
6718 compose->replyto = NULL;
6720 compose->bcc = NULL;
6721 compose->followup_to = NULL;
6723 compose->ml_post = NULL;
6725 compose->inreplyto = NULL;
6726 compose->references = NULL;
6727 compose->msgid = NULL;
6728 compose->boundary = NULL;
6730 compose->autowrap = prefs_common.autowrap;
6732 compose->use_signing = FALSE;
6733 compose->use_encryption = FALSE;
6734 compose->privacy_system = NULL;
6736 compose->modified = FALSE;
6738 compose->return_receipt = FALSE;
6740 compose->to_list = NULL;
6741 compose->newsgroup_list = NULL;
6743 compose->undostruct = undostruct;
6745 compose->sig_str = NULL;
6747 compose->exteditor_file = NULL;
6748 compose->exteditor_pid = -1;
6749 compose->exteditor_tag = -1;
6750 compose->draft_timeout_tag = -2; /* inhibit auto-drafting while loading */
6753 menu_set_sensitive(ifactory, "/Spelling", FALSE);
6754 if (mode != COMPOSE_REDIRECT) {
6755 if (prefs_common.enable_aspell && prefs_common.dictionary &&
6756 strcmp(prefs_common.dictionary, "")) {
6757 gtkaspell = gtkaspell_new(prefs_common.aspell_path,
6758 prefs_common.dictionary,
6759 prefs_common.alt_dictionary,
6760 conv_get_locale_charset_str(),
6761 prefs_common.misspelled_col,
6762 prefs_common.check_while_typing,
6763 prefs_common.recheck_when_changing_dict,
6764 prefs_common.use_alternate,
6765 prefs_common.use_both_dicts,
6766 GTK_TEXT_VIEW(text),
6767 GTK_WINDOW(compose->window),
6768 compose_spell_menu_changed,
6771 alertpanel_error(_("Spell checker could not "
6773 gtkaspell_checkers_strerror());
6774 gtkaspell_checkers_reset_error();
6776 if (!gtkaspell_set_sug_mode(gtkaspell,
6777 prefs_common.aspell_sugmode)) {
6778 debug_print("Aspell: could not set "
6779 "suggestion mode %s\n",
6780 gtkaspell_checkers_strerror());
6781 gtkaspell_checkers_reset_error();
6784 menu_set_sensitive(ifactory, "/Spelling", TRUE);
6788 compose->gtkaspell = gtkaspell;
6789 compose_spell_menu_changed(compose);
6792 compose_select_account(compose, account, TRUE);
6794 menu_set_active(ifactory, "/Edit/Auto wrapping", prefs_common.autowrap);
6795 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
6796 compose_entry_append(compose, account->auto_cc, COMPOSE_CC);
6798 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
6799 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC);
6801 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
6802 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO);
6804 menu_set_sensitive(ifactory, "/Options/Reply mode", compose->mode == COMPOSE_REPLY);
6806 if (account->protocol != A_NNTP)
6807 gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(compose->header_last->combo)->entry),
6808 prefs_common_translated_header_name("To:"));
6810 gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(compose->header_last->combo)->entry),
6811 prefs_common_translated_header_name("Newsgroups:"));
6813 addressbook_set_target_compose(compose);
6815 if (mode != COMPOSE_REDIRECT)
6816 compose_set_template_menu(compose);
6818 menuitem = gtk_item_factory_get_item(ifactory, "/Tools/Template");
6819 menu_set_sensitive(ifactory, "/Tools/Template", FALSE);
6822 compose_list = g_list_append(compose_list, compose);
6824 if (!prefs_common.show_ruler)
6825 gtk_widget_hide(ruler_hbox);
6827 menuitem = gtk_item_factory_get_item(ifactory, "/Tools/Show ruler");
6828 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
6829 prefs_common.show_ruler);
6832 compose->priority = PRIORITY_NORMAL;
6833 compose_update_priority_menu_item(compose);
6835 compose_set_out_encoding(compose);
6838 compose_update_actions_menu(compose);
6840 /* Privacy Systems menu */
6841 compose_update_privacy_systems_menu(compose);
6843 activate_privacy_system(compose, account, TRUE);
6844 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
6846 gtk_widget_realize(window);
6848 gtk_widget_show(window);
6850 maemo_window_full_screen_if_needed(GTK_WINDOW(window));
6851 maemo_connect_key_press_to_mainwindow(GTK_WINDOW(window));
6858 static GtkWidget *compose_account_option_menu_create(Compose *compose)
6863 GtkWidget *optmenubox;
6866 GtkWidget *from_name = NULL;
6868 gint num = 0, def_menu = 0;
6870 accounts = account_get_list();
6871 g_return_val_if_fail(accounts != NULL, NULL);
6873 optmenubox = gtk_event_box_new();
6874 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
6875 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
6877 hbox = gtk_hbox_new(FALSE, 6);
6878 from_name = gtk_entry_new();
6880 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
6881 G_CALLBACK(compose_grab_focus_cb), compose);
6883 for (; accounts != NULL; accounts = accounts->next, num++) {
6884 PrefsAccount *ac = (PrefsAccount *)accounts->data;
6885 gchar *name, *from = NULL;
6887 if (ac == compose->account) def_menu = num;
6889 name = g_markup_printf_escaped(_("From: <i>%s</i>"),
6892 if (ac == compose->account) {
6893 if (ac->name && *ac->name) {
6895 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
6896 from = g_strdup_printf("%s <%s>",
6898 gtk_entry_set_text(GTK_ENTRY(from_name), from);
6900 from = g_strdup_printf("%s",
6902 gtk_entry_set_text(GTK_ENTRY(from_name), from);
6905 COMBOBOX_ADD(menu, name, ac->account_id);
6910 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
6912 g_signal_connect(G_OBJECT(optmenu), "changed",
6913 G_CALLBACK(account_activated),
6915 g_signal_connect(G_OBJECT(from_name), "populate-popup",
6916 G_CALLBACK(compose_entry_popup_extend),
6919 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
6920 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
6922 gtk_tooltips_set_tip(compose->tooltips, optmenubox,
6923 _("Account to use for this email"), NULL);
6924 gtk_tooltips_set_tip(compose->tooltips, from_name,
6925 _("Sender address to be used"), NULL);
6927 compose->from_name = from_name;
6932 static void compose_set_priority_cb(gpointer data,
6936 Compose *compose = (Compose *) data;
6937 compose->priority = action;
6940 static void compose_reply_change_mode(gpointer data,
6944 Compose *compose = (Compose *) data;
6945 gboolean was_modified = compose->modified;
6947 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
6949 g_return_if_fail(compose->replyinfo != NULL);
6951 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
6953 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
6955 if (action == COMPOSE_REPLY_TO_ALL)
6957 if (action == COMPOSE_REPLY_TO_SENDER)
6959 if (action == COMPOSE_REPLY_TO_LIST)
6962 compose_remove_header_entries(compose);
6963 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
6964 if (compose->account->set_autocc && compose->account->auto_cc)
6965 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC);
6967 if (compose->account->set_autobcc && compose->account->auto_bcc)
6968 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC);
6970 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
6971 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO);
6972 compose_show_first_last_header(compose, TRUE);
6973 compose->modified = was_modified;
6974 compose_set_title(compose);
6977 static void compose_update_priority_menu_item(Compose * compose)
6979 GtkItemFactory *ifactory;
6980 GtkWidget *menuitem = NULL;
6982 ifactory = gtk_item_factory_from_widget(compose->menubar);
6984 switch (compose->priority) {
6985 case PRIORITY_HIGHEST:
6986 menuitem = gtk_item_factory_get_item
6987 (ifactory, "/Options/Priority/Highest");
6990 menuitem = gtk_item_factory_get_item
6991 (ifactory, "/Options/Priority/High");
6993 case PRIORITY_NORMAL:
6994 menuitem = gtk_item_factory_get_item
6995 (ifactory, "/Options/Priority/Normal");
6998 menuitem = gtk_item_factory_get_item
6999 (ifactory, "/Options/Priority/Low");
7001 case PRIORITY_LOWEST:
7002 menuitem = gtk_item_factory_get_item
7003 (ifactory, "/Options/Priority/Lowest");
7006 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
7009 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
7011 Compose *compose = (Compose *) data;
7013 GtkItemFactory *ifactory;
7014 gboolean can_sign = FALSE, can_encrypt = FALSE;
7016 g_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
7018 if (!GTK_CHECK_MENU_ITEM(widget)->active)
7021 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
7022 g_free(compose->privacy_system);
7023 compose->privacy_system = NULL;
7024 if (systemid != NULL) {
7025 compose->privacy_system = g_strdup(systemid);
7027 can_sign = privacy_system_can_sign(systemid);
7028 can_encrypt = privacy_system_can_encrypt(systemid);
7031 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
7033 ifactory = gtk_item_factory_from_widget(compose->menubar);
7034 menu_set_sensitive(ifactory, "/Options/Sign", can_sign);
7035 menu_set_sensitive(ifactory, "/Options/Encrypt", can_encrypt);
7038 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
7040 static gchar *branch_path = "/Options/Privacy System";
7041 GtkItemFactory *ifactory;
7042 GtkWidget *menuitem = NULL;
7044 gboolean can_sign = FALSE, can_encrypt = FALSE;
7045 gboolean found = FALSE;
7047 ifactory = gtk_item_factory_from_widget(compose->menubar);
7049 if (compose->privacy_system != NULL) {
7052 menuitem = gtk_item_factory_get_widget(ifactory, branch_path);
7053 g_return_if_fail(menuitem != NULL);
7055 amenu = GTK_MENU_SHELL(menuitem)->children;
7057 while (amenu != NULL) {
7058 GList *alist = amenu->next;
7060 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
7061 if (systemid != NULL) {
7062 if (strcmp(systemid, compose->privacy_system) == 0) {
7063 menuitem = GTK_WIDGET(amenu->data);
7065 can_sign = privacy_system_can_sign(systemid);
7066 can_encrypt = privacy_system_can_encrypt(systemid);
7070 } else if (strlen(compose->privacy_system) == 0) {
7071 menuitem = GTK_WIDGET(amenu->data);
7074 can_encrypt = FALSE;
7081 if (menuitem != NULL)
7082 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
7084 if (warn && !found && strlen(compose->privacy_system)) {
7085 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
7086 "will not be able to sign or encrypt this message."),
7087 compose->privacy_system);
7091 menu_set_sensitive(ifactory, "/Options/Sign", can_sign);
7092 menu_set_sensitive(ifactory, "/Options/Encrypt", can_encrypt);
7095 static void compose_set_out_encoding(Compose *compose)
7097 GtkItemFactoryEntry *entry;
7098 GtkItemFactory *ifactory;
7099 CharSet out_encoding;
7100 gchar *path, *p, *q;
7103 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
7104 ifactory = gtk_item_factory_from_widget(compose->menubar);
7106 for (entry = compose_entries; entry->callback != compose_address_cb;
7108 if (entry->callback == compose_set_encoding_cb &&
7109 (CharSet)entry->callback_action == out_encoding) {
7110 p = q = path = g_strdup(entry->path);
7122 item = gtk_item_factory_get_item(ifactory, path);
7123 gtk_widget_activate(item);
7130 static void compose_set_template_menu(Compose *compose)
7132 GSList *tmpl_list, *cur;
7136 tmpl_list = template_get_config();
7138 menu = gtk_menu_new();
7140 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
7141 Template *tmpl = (Template *)cur->data;
7143 item = gtk_menu_item_new_with_label(tmpl->name);
7144 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
7145 g_signal_connect(G_OBJECT(item), "activate",
7146 G_CALLBACK(compose_template_activate_cb),
7148 g_object_set_data(G_OBJECT(item), "template", tmpl);
7149 gtk_widget_show(item);
7152 gtk_widget_show(menu);
7153 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
7156 void compose_update_actions_menu(Compose *compose)
7158 GtkItemFactory *ifactory;
7160 ifactory = gtk_item_factory_from_widget(compose->menubar);
7161 action_update_compose_menu(ifactory, "/Tools/Actions", compose);
7164 static void compose_update_privacy_systems_menu(Compose *compose)
7166 static gchar *branch_path = "/Options/Privacy System";
7167 GtkItemFactory *ifactory;
7168 GtkWidget *menuitem;
7169 GSList *systems, *cur;
7172 GtkWidget *system_none;
7175 ifactory = gtk_item_factory_from_widget(compose->menubar);
7177 /* remove old entries */
7178 menuitem = gtk_item_factory_get_widget(ifactory, branch_path);
7179 g_return_if_fail(menuitem != NULL);
7181 amenu = GTK_MENU_SHELL(menuitem)->children->next;
7182 while (amenu != NULL) {
7183 GList *alist = amenu->next;
7184 gtk_widget_destroy(GTK_WIDGET(amenu->data));
7188 system_none = gtk_item_factory_get_widget(ifactory,
7189 "/Options/Privacy System/None");
7191 g_signal_connect(G_OBJECT(system_none), "activate",
7192 G_CALLBACK(compose_set_privacy_system_cb), compose);
7194 systems = privacy_get_system_ids();
7195 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
7196 gchar *systemid = cur->data;
7198 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
7199 widget = gtk_radio_menu_item_new_with_label(group,
7200 privacy_system_get_name(systemid));
7201 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
7202 g_strdup(systemid), g_free);
7203 g_signal_connect(G_OBJECT(widget), "activate",
7204 G_CALLBACK(compose_set_privacy_system_cb), compose);
7206 gtk_menu_append(GTK_MENU(system_none->parent), widget);
7207 gtk_widget_show(widget);
7210 g_slist_free(systems);
7213 void compose_reflect_prefs_all(void)
7218 for (cur = compose_list; cur != NULL; cur = cur->next) {
7219 compose = (Compose *)cur->data;
7220 compose_set_template_menu(compose);
7224 void compose_reflect_prefs_pixmap_theme(void)
7229 for (cur = compose_list; cur != NULL; cur = cur->next) {
7230 compose = (Compose *)cur->data;
7231 toolbar_update(TOOLBAR_COMPOSE, compose);
7235 static const gchar *compose_quote_char_from_context(Compose *compose)
7237 const gchar *qmark = NULL;
7239 g_return_val_if_fail(compose != NULL, NULL);
7241 switch (compose->mode) {
7242 /* use forward-specific quote char */
7243 case COMPOSE_FORWARD:
7244 case COMPOSE_FORWARD_AS_ATTACH:
7245 case COMPOSE_FORWARD_INLINE:
7246 if (compose->folder && compose->folder->prefs &&
7247 compose->folder->prefs->forward_with_format)
7248 qmark = compose->folder->prefs->forward_quotemark;
7249 else if (compose->account->forward_with_format)
7250 qmark = compose->account->forward_quotemark;
7252 qmark = prefs_common.fw_quotemark;
7255 /* use reply-specific quote char in all other modes */
7257 if (compose->folder && compose->folder->prefs &&
7258 compose->folder->prefs->reply_with_format)
7259 qmark = compose->folder->prefs->reply_quotemark;
7260 else if (compose->account->reply_with_format)
7261 qmark = compose->account->reply_quotemark;
7263 qmark = prefs_common.quotemark;
7267 if (qmark == NULL || *qmark == '\0')
7273 static void compose_template_apply(Compose *compose, Template *tmpl,
7277 GtkTextBuffer *buffer;
7281 gchar *parsed_str = NULL;
7282 gint cursor_pos = 0;
7283 const gchar *err_msg = _("Template body format error at line %d.");
7286 /* process the body */
7288 text = GTK_TEXT_VIEW(compose->text);
7289 buffer = gtk_text_view_get_buffer(text);
7292 qmark = compose_quote_char_from_context(compose);
7294 if (compose->replyinfo != NULL) {
7297 gtk_text_buffer_set_text(buffer, "", -1);
7298 mark = gtk_text_buffer_get_insert(buffer);
7299 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7301 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
7302 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
7304 } else if (compose->fwdinfo != NULL) {
7307 gtk_text_buffer_set_text(buffer, "", -1);
7308 mark = gtk_text_buffer_get_insert(buffer);
7309 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7311 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
7312 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
7315 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
7317 GtkTextIter start, end;
7320 gtk_text_buffer_get_start_iter(buffer, &start);
7321 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
7322 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
7324 /* clear the buffer now */
7326 gtk_text_buffer_set_text(buffer, "", -1);
7328 parsed_str = compose_quote_fmt(compose, dummyinfo,
7329 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
7330 procmsg_msginfo_free( dummyinfo );
7336 gtk_text_buffer_set_text(buffer, "", -1);
7337 mark = gtk_text_buffer_get_insert(buffer);
7338 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7341 if (replace && parsed_str && compose->account->auto_sig)
7342 compose_insert_sig(compose, FALSE);
7344 if (replace && parsed_str) {
7345 gtk_text_buffer_get_start_iter(buffer, &iter);
7346 gtk_text_buffer_place_cursor(buffer, &iter);
7350 cursor_pos = quote_fmt_get_cursor_pos();
7351 compose->set_cursor_pos = cursor_pos;
7352 if (cursor_pos == -1)
7354 gtk_text_buffer_get_start_iter(buffer, &iter);
7355 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
7356 gtk_text_buffer_place_cursor(buffer, &iter);
7359 /* process the other fields */
7361 compose_template_apply_fields(compose, tmpl);
7362 quote_fmt_reset_vartable();
7363 compose_changed_cb(NULL, compose);
7366 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
7368 MsgInfo* dummyinfo = NULL;
7369 MsgInfo *msginfo = NULL;
7372 if (compose->replyinfo != NULL)
7373 msginfo = compose->replyinfo;
7374 else if (compose->fwdinfo != NULL)
7375 msginfo = compose->fwdinfo;
7377 dummyinfo = compose_msginfo_new_from_compose(compose);
7378 msginfo = dummyinfo;
7381 if (tmpl->to && *tmpl->to != '\0') {
7383 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7384 compose->gtkaspell);
7386 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7388 quote_fmt_scan_string(tmpl->to);
7391 buf = quote_fmt_get_buffer();
7393 alertpanel_error(_("Template To format error."));
7395 compose_entry_append(compose, buf, COMPOSE_TO);
7399 if (tmpl->cc && *tmpl->cc != '\0') {
7401 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7402 compose->gtkaspell);
7404 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7406 quote_fmt_scan_string(tmpl->cc);
7409 buf = quote_fmt_get_buffer();
7411 alertpanel_error(_("Template Cc format error."));
7413 compose_entry_append(compose, buf, COMPOSE_CC);
7417 if (tmpl->bcc && *tmpl->bcc != '\0') {
7419 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7420 compose->gtkaspell);
7422 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7424 quote_fmt_scan_string(tmpl->bcc);
7427 buf = quote_fmt_get_buffer();
7429 alertpanel_error(_("Template Bcc format error."));
7431 compose_entry_append(compose, buf, COMPOSE_BCC);
7435 /* process the subject */
7436 if (tmpl->subject && *tmpl->subject != '\0') {
7438 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7439 compose->gtkaspell);
7441 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7443 quote_fmt_scan_string(tmpl->subject);
7446 buf = quote_fmt_get_buffer();
7448 alertpanel_error(_("Template subject format error."));
7450 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
7454 procmsg_msginfo_free( dummyinfo );
7457 static void compose_destroy(Compose *compose)
7459 GtkTextBuffer *buffer;
7460 GtkClipboard *clipboard;
7462 compose_list = g_list_remove(compose_list, compose);
7464 if (compose->updating) {
7465 debug_print("danger, not destroying anything now\n");
7466 compose->deferred_destroy = TRUE;
7469 /* NOTE: address_completion_end() does nothing with the window
7470 * however this may change. */
7471 address_completion_end(compose->window);
7473 slist_free_strings(compose->to_list);
7474 g_slist_free(compose->to_list);
7475 slist_free_strings(compose->newsgroup_list);
7476 g_slist_free(compose->newsgroup_list);
7477 slist_free_strings(compose->header_list);
7478 g_slist_free(compose->header_list);
7480 procmsg_msginfo_free(compose->targetinfo);
7481 procmsg_msginfo_free(compose->replyinfo);
7482 procmsg_msginfo_free(compose->fwdinfo);
7484 g_free(compose->replyto);
7485 g_free(compose->cc);
7486 g_free(compose->bcc);
7487 g_free(compose->newsgroups);
7488 g_free(compose->followup_to);
7490 g_free(compose->ml_post);
7492 g_free(compose->inreplyto);
7493 g_free(compose->references);
7494 g_free(compose->msgid);
7495 g_free(compose->boundary);
7497 g_free(compose->redirect_filename);
7498 if (compose->undostruct)
7499 undo_destroy(compose->undostruct);
7501 g_free(compose->sig_str);
7503 g_free(compose->exteditor_file);
7505 g_free(compose->orig_charset);
7507 g_free(compose->privacy_system);
7509 if (addressbook_get_target_compose() == compose)
7510 addressbook_set_target_compose(NULL);
7513 if (compose->gtkaspell) {
7514 gtkaspell_delete(compose->gtkaspell);
7515 compose->gtkaspell = NULL;
7519 prefs_common.compose_width = compose->scrolledwin->allocation.width;
7520 prefs_common.compose_height = compose->window->allocation.height;
7522 if (!gtk_widget_get_parent(compose->paned))
7523 gtk_widget_destroy(compose->paned);
7524 gtk_widget_destroy(compose->popupmenu);
7526 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
7527 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7528 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
7530 gtk_widget_destroy(compose->window);
7531 toolbar_destroy(compose->toolbar);
7532 g_free(compose->toolbar);
7533 g_mutex_free(compose->mutex);
7537 static void compose_attach_info_free(AttachInfo *ainfo)
7539 g_free(ainfo->file);
7540 g_free(ainfo->content_type);
7541 g_free(ainfo->name);
7545 static void compose_attach_remove_selected(Compose *compose)
7547 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
7548 GtkTreeSelection *selection;
7550 GtkTreeModel *model;
7552 selection = gtk_tree_view_get_selection(tree_view);
7553 sel = gtk_tree_selection_get_selected_rows(selection, &model);
7558 for (cur = sel; cur != NULL; cur = cur->next) {
7559 GtkTreePath *path = cur->data;
7560 GtkTreeRowReference *ref = gtk_tree_row_reference_new
7563 gtk_tree_path_free(path);
7566 for (cur = sel; cur != NULL; cur = cur->next) {
7567 GtkTreeRowReference *ref = cur->data;
7568 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
7571 if (gtk_tree_model_get_iter(model, &iter, path))
7572 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
7574 gtk_tree_path_free(path);
7575 gtk_tree_row_reference_free(ref);
7581 static struct _AttachProperty
7584 GtkWidget *mimetype_entry;
7585 GtkWidget *encoding_optmenu;
7586 GtkWidget *path_entry;
7587 GtkWidget *filename_entry;
7589 GtkWidget *cancel_btn;
7592 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
7594 gtk_tree_path_free((GtkTreePath *)ptr);
7597 static void compose_attach_property(Compose *compose)
7599 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
7601 GtkComboBox *optmenu;
7602 GtkTreeSelection *selection;
7604 GtkTreeModel *model;
7607 static gboolean cancelled;
7609 /* only if one selected */
7610 selection = gtk_tree_view_get_selection(tree_view);
7611 if (gtk_tree_selection_count_selected_rows(selection) != 1)
7614 sel = gtk_tree_selection_get_selected_rows(selection, &model);
7618 path = (GtkTreePath *) sel->data;
7619 gtk_tree_model_get_iter(model, &iter, path);
7620 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
7623 g_list_foreach(sel, gtk_tree_path_free_, NULL);
7629 if (!attach_prop.window)
7630 compose_attach_property_create(&cancelled);
7631 gtk_widget_grab_focus(attach_prop.ok_btn);
7632 gtk_widget_show(attach_prop.window);
7633 manage_window_set_transient(GTK_WINDOW(attach_prop.window));
7635 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
7636 if (ainfo->encoding == ENC_UNKNOWN)
7637 combobox_select_by_data(optmenu, ENC_BASE64);
7639 combobox_select_by_data(optmenu, ainfo->encoding);
7641 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
7642 ainfo->content_type ? ainfo->content_type : "");
7643 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
7644 ainfo->file ? ainfo->file : "");
7645 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
7646 ainfo->name ? ainfo->name : "");
7649 const gchar *entry_text;
7651 gchar *cnttype = NULL;
7658 gtk_widget_hide(attach_prop.window);
7663 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
7664 if (*entry_text != '\0') {
7667 text = g_strstrip(g_strdup(entry_text));
7668 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
7669 cnttype = g_strdup(text);
7672 alertpanel_error(_("Invalid MIME type."));
7678 ainfo->encoding = combobox_get_active_data(optmenu);
7680 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
7681 if (*entry_text != '\0') {
7682 if (is_file_exist(entry_text) &&
7683 (size = get_file_size(entry_text)) > 0)
7684 file = g_strdup(entry_text);
7687 (_("File doesn't exist or is empty."));
7693 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
7694 if (*entry_text != '\0') {
7695 g_free(ainfo->name);
7696 ainfo->name = g_strdup(entry_text);
7700 g_free(ainfo->content_type);
7701 ainfo->content_type = cnttype;
7704 g_free(ainfo->file);
7710 /* update tree store */
7711 text = to_human_readable(ainfo->size);
7712 gtk_tree_model_get_iter(model, &iter, path);
7713 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
7714 COL_MIMETYPE, ainfo->content_type,
7716 COL_NAME, ainfo->name,
7722 gtk_tree_path_free(path);
7725 #define SET_LABEL_AND_ENTRY(str, entry, top) \
7727 label = gtk_label_new(str); \
7728 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
7729 GTK_FILL, 0, 0, 0); \
7730 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
7732 entry = gtk_entry_new(); \
7733 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
7734 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
7737 static void compose_attach_property_create(gboolean *cancelled)
7743 GtkWidget *mimetype_entry;
7746 GtkListStore *optmenu_menu;
7747 GtkWidget *path_entry;
7748 GtkWidget *filename_entry;
7751 GtkWidget *cancel_btn;
7752 GList *mime_type_list, *strlist;
7755 debug_print("Creating attach_property window...\n");
7757 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
7758 gtk_widget_set_size_request(window, 480, -1);
7759 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
7760 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
7761 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
7762 gtk_window_set_modal(GTK_WINDOW(window), TRUE);
7763 g_signal_connect(G_OBJECT(window), "delete_event",
7764 G_CALLBACK(attach_property_delete_event),
7766 g_signal_connect(G_OBJECT(window), "key_press_event",
7767 G_CALLBACK(attach_property_key_pressed),
7770 vbox = gtk_vbox_new(FALSE, 8);
7771 gtk_container_add(GTK_CONTAINER(window), vbox);
7773 table = gtk_table_new(4, 2, FALSE);
7774 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
7775 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
7776 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
7778 label = gtk_label_new(_("MIME type"));
7779 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
7781 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
7782 mimetype_entry = gtk_combo_new();
7783 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
7784 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
7786 /* stuff with list */
7787 mime_type_list = procmime_get_mime_type_list();
7789 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
7790 MimeType *type = (MimeType *) mime_type_list->data;
7793 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
7795 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
7798 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
7799 (GCompareFunc)strcmp2);
7802 gtk_combo_set_popdown_strings(GTK_COMBO(mimetype_entry), strlist);
7804 for (mime_type_list = strlist; mime_type_list != NULL;
7805 mime_type_list = mime_type_list->next)
7806 g_free(mime_type_list->data);
7807 g_list_free(strlist);
7809 mimetype_entry = GTK_COMBO(mimetype_entry)->entry;
7811 label = gtk_label_new(_("Encoding"));
7812 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
7814 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
7816 hbox = gtk_hbox_new(FALSE, 0);
7817 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
7818 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
7820 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
7821 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
7823 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
7824 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
7825 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
7826 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
7827 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
7829 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
7831 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
7832 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
7834 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
7835 &ok_btn, GTK_STOCK_OK,
7837 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
7838 gtk_widget_grab_default(ok_btn);
7840 g_signal_connect(G_OBJECT(ok_btn), "clicked",
7841 G_CALLBACK(attach_property_ok),
7843 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
7844 G_CALLBACK(attach_property_cancel),
7847 gtk_widget_show_all(vbox);
7849 attach_prop.window = window;
7850 attach_prop.mimetype_entry = mimetype_entry;
7851 attach_prop.encoding_optmenu = optmenu;
7852 attach_prop.path_entry = path_entry;
7853 attach_prop.filename_entry = filename_entry;
7854 attach_prop.ok_btn = ok_btn;
7855 attach_prop.cancel_btn = cancel_btn;
7858 #undef SET_LABEL_AND_ENTRY
7860 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
7866 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
7872 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
7873 gboolean *cancelled)
7881 static gboolean attach_property_key_pressed(GtkWidget *widget,
7883 gboolean *cancelled)
7885 if (event && event->keyval == GDK_Escape) {
7892 static void compose_exec_ext_editor(Compose *compose)
7899 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
7900 G_DIR_SEPARATOR, compose);
7902 if (pipe(pipe_fds) < 0) {
7908 if ((pid = fork()) < 0) {
7915 /* close the write side of the pipe */
7918 compose->exteditor_file = g_strdup(tmp);
7919 compose->exteditor_pid = pid;
7921 compose_set_ext_editor_sensitive(compose, FALSE);
7923 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
7924 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
7928 } else { /* process-monitoring process */
7934 /* close the read side of the pipe */
7937 if (compose_write_body_to_file(compose, tmp) < 0) {
7938 fd_write_all(pipe_fds[1], "2\n", 2);
7942 pid_ed = compose_exec_ext_editor_real(tmp);
7944 fd_write_all(pipe_fds[1], "1\n", 2);
7948 /* wait until editor is terminated */
7949 waitpid(pid_ed, NULL, 0);
7951 fd_write_all(pipe_fds[1], "0\n", 2);
7958 #endif /* G_OS_UNIX */
7962 static gint compose_exec_ext_editor_real(const gchar *file)
7969 g_return_val_if_fail(file != NULL, -1);
7971 if ((pid = fork()) < 0) {
7976 if (pid != 0) return pid;
7978 /* grandchild process */
7980 if (setpgid(0, getppid()))
7983 if (prefs_common.ext_editor_cmd &&
7984 (p = strchr(prefs_common.ext_editor_cmd, '%')) &&
7985 *(p + 1) == 's' && !strchr(p + 2, '%')) {
7986 g_snprintf(buf, sizeof(buf), prefs_common.ext_editor_cmd, file);
7988 if (prefs_common.ext_editor_cmd)
7989 g_warning("External editor command line is invalid: '%s'\n",
7990 prefs_common.ext_editor_cmd);
7991 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
7994 cmdline = strsplit_with_quote(buf, " ", 1024);
7995 execvp(cmdline[0], cmdline);
7998 g_strfreev(cmdline);
8003 static gboolean compose_ext_editor_kill(Compose *compose)
8005 pid_t pgid = compose->exteditor_pid * -1;
8008 ret = kill(pgid, 0);
8010 if (ret == 0 || (ret == -1 && EPERM == errno)) {
8014 msg = g_strdup_printf
8015 (_("The external editor is still working.\n"
8016 "Force terminating the process?\n"
8017 "process group id: %d"), -pgid);
8018 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
8019 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
8023 if (val == G_ALERTALTERNATE) {
8024 g_source_remove(compose->exteditor_tag);
8025 g_io_channel_shutdown(compose->exteditor_ch,
8027 g_io_channel_unref(compose->exteditor_ch);
8029 if (kill(pgid, SIGTERM) < 0) perror("kill");
8030 waitpid(compose->exteditor_pid, NULL, 0);
8032 g_warning("Terminated process group id: %d", -pgid);
8033 g_warning("Temporary file: %s",
8034 compose->exteditor_file);
8036 compose_set_ext_editor_sensitive(compose, TRUE);
8038 g_free(compose->exteditor_file);
8039 compose->exteditor_file = NULL;
8040 compose->exteditor_pid = -1;
8041 compose->exteditor_ch = NULL;
8042 compose->exteditor_tag = -1;
8050 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
8054 Compose *compose = (Compose *)data;
8057 debug_print(_("Compose: input from monitoring process\n"));
8059 g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL);
8061 g_io_channel_shutdown(source, FALSE, NULL);
8062 g_io_channel_unref(source);
8064 waitpid(compose->exteditor_pid, NULL, 0);
8066 if (buf[0] == '0') { /* success */
8067 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
8068 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
8070 gtk_text_buffer_set_text(buffer, "", -1);
8071 compose_insert_file(compose, compose->exteditor_file);
8072 compose_changed_cb(NULL, compose);
8074 if (g_unlink(compose->exteditor_file) < 0)
8075 FILE_OP_ERROR(compose->exteditor_file, "unlink");
8076 } else if (buf[0] == '1') { /* failed */
8077 g_warning("Couldn't exec external editor\n");
8078 if (g_unlink(compose->exteditor_file) < 0)
8079 FILE_OP_ERROR(compose->exteditor_file, "unlink");
8080 } else if (buf[0] == '2') {
8081 g_warning("Couldn't write to file\n");
8082 } else if (buf[0] == '3') {
8083 g_warning("Pipe read failed\n");
8086 compose_set_ext_editor_sensitive(compose, TRUE);
8088 g_free(compose->exteditor_file);
8089 compose->exteditor_file = NULL;
8090 compose->exteditor_pid = -1;
8091 compose->exteditor_ch = NULL;
8092 compose->exteditor_tag = -1;
8097 static void compose_set_ext_editor_sensitive(Compose *compose,
8100 GtkItemFactory *ifactory;
8102 ifactory = gtk_item_factory_from_widget(compose->menubar);
8104 menu_set_sensitive(ifactory, "/Message/Send", sensitive);
8105 menu_set_sensitive(ifactory, "/Message/Send later", sensitive);
8106 menu_set_sensitive(ifactory, "/Message/Insert file", sensitive);
8107 menu_set_sensitive(ifactory, "/Message/Insert signature", sensitive);
8108 menu_set_sensitive(ifactory, "/Edit/Wrap current paragraph", sensitive);
8109 menu_set_sensitive(ifactory, "/Edit/Wrap all long lines", sensitive);
8110 menu_set_sensitive(ifactory, "/Edit/Edit with external editor",
8113 gtk_widget_set_sensitive(compose->text, sensitive);
8114 if (compose->toolbar->send_btn)
8115 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
8116 if (compose->toolbar->sendl_btn)
8117 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
8118 if (compose->toolbar->draft_btn)
8119 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
8120 if (compose->toolbar->insert_btn)
8121 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
8122 if (compose->toolbar->sig_btn)
8123 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
8124 if (compose->toolbar->exteditor_btn)
8125 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
8126 if (compose->toolbar->linewrap_current_btn)
8127 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
8128 if (compose->toolbar->linewrap_all_btn)
8129 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
8131 #endif /* G_OS_UNIX */
8134 * compose_undo_state_changed:
8136 * Change the sensivity of the menuentries undo and redo
8138 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
8139 gint redo_state, gpointer data)
8141 GtkWidget *widget = GTK_WIDGET(data);
8142 GtkItemFactory *ifactory;
8144 g_return_if_fail(widget != NULL);
8146 ifactory = gtk_item_factory_from_widget(widget);
8148 switch (undo_state) {
8149 case UNDO_STATE_TRUE:
8150 if (!undostruct->undo_state) {
8151 undostruct->undo_state = TRUE;
8152 menu_set_sensitive(ifactory, "/Edit/Undo", TRUE);
8155 case UNDO_STATE_FALSE:
8156 if (undostruct->undo_state) {
8157 undostruct->undo_state = FALSE;
8158 menu_set_sensitive(ifactory, "/Edit/Undo", FALSE);
8161 case UNDO_STATE_UNCHANGED:
8163 case UNDO_STATE_REFRESH:
8164 menu_set_sensitive(ifactory, "/Edit/Undo",
8165 undostruct->undo_state);
8168 g_warning("Undo state not recognized");
8172 switch (redo_state) {
8173 case UNDO_STATE_TRUE:
8174 if (!undostruct->redo_state) {
8175 undostruct->redo_state = TRUE;
8176 menu_set_sensitive(ifactory, "/Edit/Redo", TRUE);
8179 case UNDO_STATE_FALSE:
8180 if (undostruct->redo_state) {
8181 undostruct->redo_state = FALSE;
8182 menu_set_sensitive(ifactory, "/Edit/Redo", FALSE);
8185 case UNDO_STATE_UNCHANGED:
8187 case UNDO_STATE_REFRESH:
8188 menu_set_sensitive(ifactory, "/Edit/Redo",
8189 undostruct->redo_state);
8192 g_warning("Redo state not recognized");
8197 /* callback functions */
8199 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
8200 * includes "non-client" (windows-izm) in calculation, so this calculation
8201 * may not be accurate.
8203 static gboolean compose_edit_size_alloc(GtkEditable *widget,
8204 GtkAllocation *allocation,
8205 GtkSHRuler *shruler)
8207 if (prefs_common.show_ruler) {
8208 gint char_width = 0, char_height = 0;
8209 gint line_width_in_chars;
8211 gtkut_get_font_size(GTK_WIDGET(widget),
8212 &char_width, &char_height);
8213 line_width_in_chars =
8214 (allocation->width - allocation->x) / char_width;
8216 /* got the maximum */
8217 gtk_ruler_set_range(GTK_RULER(shruler),
8218 0.0, line_width_in_chars, 0,
8219 /*line_width_in_chars*/ char_width);
8225 static void account_activated(GtkComboBox *optmenu, gpointer data)
8227 Compose *compose = (Compose *)data;
8230 gchar *folderidentifier;
8231 gint account_id = 0;
8235 /* Get ID of active account in the combo box */
8236 menu = gtk_combo_box_get_model(optmenu);
8237 gtk_combo_box_get_active_iter(optmenu, &iter);
8238 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
8240 ac = account_find_from_id(account_id);
8241 g_return_if_fail(ac != NULL);
8243 if (ac != compose->account)
8244 compose_select_account(compose, ac, FALSE);
8246 /* Set message save folder */
8247 if (account_get_special_folder(compose->account, F_OUTBOX)) {
8248 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
8250 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
8251 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
8253 gtk_editable_delete_text(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
8254 if (account_get_special_folder(compose->account, F_OUTBOX)) {
8255 folderidentifier = folder_item_get_identifier(account_get_special_folder
8256 (compose->account, F_OUTBOX));
8257 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
8258 g_free(folderidentifier);
8262 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
8263 GtkTreeViewColumn *column, Compose *compose)
8265 compose_attach_property(compose);
8268 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
8271 Compose *compose = (Compose *)data;
8272 GtkTreeSelection *attach_selection;
8273 gint attach_nr_selected;
8274 GtkItemFactory *ifactory;
8276 if (!event) return FALSE;
8278 if (event->button == 3) {
8279 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
8280 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
8281 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
8283 if (attach_nr_selected > 0)
8285 menu_set_sensitive(ifactory, "/Remove", TRUE);
8286 menu_set_sensitive(ifactory, "/Properties...", TRUE);
8288 menu_set_sensitive(ifactory, "/Remove", FALSE);
8289 menu_set_sensitive(ifactory, "/Properties...", FALSE);
8292 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
8293 NULL, NULL, event->button, event->time);
8300 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
8303 Compose *compose = (Compose *)data;
8305 if (!event) return FALSE;
8307 switch (event->keyval) {
8309 compose_attach_remove_selected(compose);
8315 static void compose_allow_user_actions (Compose *compose, gboolean allow)
8317 GtkItemFactory *ifactory = gtk_item_factory_from_widget(compose->menubar);
8318 toolbar_comp_set_sensitive(compose, allow);
8319 menu_set_sensitive(ifactory, "/Message", allow);
8320 menu_set_sensitive(ifactory, "/Edit", allow);
8322 menu_set_sensitive(ifactory, "/Spelling", allow);
8324 menu_set_sensitive(ifactory, "/Options", allow);
8325 menu_set_sensitive(ifactory, "/Tools", allow);
8326 menu_set_sensitive(ifactory, "/Help", allow);
8328 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
8332 static void compose_send_cb(gpointer data, guint action, GtkWidget *widget)
8334 Compose *compose = (Compose *)data;
8336 if (prefs_common.work_offline &&
8337 !inc_offline_should_override(TRUE,
8338 _("Claws Mail needs network access in order "
8339 "to send this email.")))
8342 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
8343 g_source_remove(compose->draft_timeout_tag);
8344 compose->draft_timeout_tag = -1;
8347 compose_send(compose);
8350 static void compose_send_later_cb(gpointer data, guint action,
8353 Compose *compose = (Compose *)data;
8357 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
8361 compose_close(compose);
8362 } else if (val == -1) {
8363 alertpanel_error(_("Could not queue message."));
8364 } else if (val == -2) {
8365 alertpanel_error(_("Could not queue message:\n\n%s."), strerror(errno));
8366 } else if (val == -3) {
8367 if (privacy_peek_error())
8368 alertpanel_error(_("Could not queue message for sending:\n\n"
8369 "Signature failed: %s"), privacy_get_error());
8370 } else if (val == -4) {
8371 alertpanel_error(_("Could not queue message for sending:\n\n"
8372 "Charset conversion failed."));
8373 } else if (val == -5) {
8374 alertpanel_error(_("Could not queue message for sending:\n\n"
8375 "Couldn't get recipient encryption key."));
8376 } else if (val == -6) {
8379 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
8382 #define DRAFTED_AT_EXIT "drafted_at_exit"
8383 static void compose_register_draft(MsgInfo *info)
8385 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8386 DRAFTED_AT_EXIT, NULL);
8387 FILE *fp = fopen(filepath, "ab");
8390 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
8398 gboolean compose_draft (gpointer data, guint action)
8400 Compose *compose = (Compose *)data;
8404 MsgFlags flag = {0, 0};
8405 static gboolean lock = FALSE;
8406 MsgInfo *newmsginfo;
8408 gboolean target_locked = FALSE;
8410 if (lock) return FALSE;
8412 if (compose->sending)
8415 draft = account_get_special_folder(compose->account, F_DRAFT);
8416 g_return_val_if_fail(draft != NULL, FALSE);
8418 if (!g_mutex_trylock(compose->mutex)) {
8419 /* we don't want to lock the mutex once it's available,
8420 * because as the only other part of compose.c locking
8421 * it is compose_close - which means once unlocked,
8422 * the compose struct will be freed */
8423 debug_print("couldn't lock mutex, probably sending\n");
8429 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
8430 G_DIR_SEPARATOR, compose);
8431 if ((fp = g_fopen(tmp, "wb")) == NULL) {
8432 FILE_OP_ERROR(tmp, "fopen");
8436 /* chmod for security */
8437 if (change_file_mode_rw(fp, tmp) < 0) {
8438 FILE_OP_ERROR(tmp, "chmod");
8439 g_warning("can't change file mode\n");
8442 /* Save draft infos */
8443 fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id);
8444 fprintf(fp, "S:%s\n", compose->account->address);
8446 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
8447 gchar *savefolderid;
8449 savefolderid = gtk_editable_get_chars(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
8450 fprintf(fp, "SCF:%s\n", savefolderid);
8451 g_free(savefolderid);
8453 if (compose->return_receipt) {
8454 fprintf(fp, "RRCPT:1\n");
8456 if (compose->privacy_system) {
8457 fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing);
8458 fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption);
8459 fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system);
8462 /* Message-ID of message replying to */
8463 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
8466 folderid = folder_item_get_identifier(compose->replyinfo->folder);
8467 fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid);
8470 /* Message-ID of message forwarding to */
8471 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
8474 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
8475 fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid);
8479 /* end of headers */
8480 fprintf(fp, "X-Claws-End-Special-Headers: 1\n");
8482 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
8490 if (compose->targetinfo) {
8491 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
8492 flag.perm_flags = target_locked?MSG_LOCKED:0;
8494 flag.tmp_flags = MSG_DRAFT;
8496 folder_item_scan(draft);
8497 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
8498 MsgInfo *tmpinfo = NULL;
8499 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
8500 if (compose->msgid) {
8501 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
8504 msgnum = tmpinfo->msgnum;
8505 procmsg_msginfo_free(tmpinfo);
8506 debug_print("got draft msgnum %d from scanning\n", msgnum);
8508 debug_print("didn't get draft msgnum after scanning\n");
8511 debug_print("got draft msgnum %d from adding\n", msgnum);
8516 if (action != COMPOSE_AUTO_SAVE) {
8517 if (action != COMPOSE_DRAFT_FOR_EXIT)
8518 alertpanel_error(_("Could not save draft."));
8521 gtkut_window_popup(compose->window);
8522 val = alertpanel_full(_("Could not save draft"),
8523 _("Could not save draft.\n"
8524 "Do you want to cancel exit or discard this email?"),
8525 _("_Cancel exit"), _("_Discard email"), NULL,
8526 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
8527 if (val == G_ALERTALTERNATE) {
8529 g_mutex_unlock(compose->mutex); /* must be done before closing */
8530 compose_close(compose);
8534 g_mutex_unlock(compose->mutex); /* must be done before closing */
8543 if (compose->mode == COMPOSE_REEDIT) {
8544 compose_remove_reedit_target(compose, TRUE);
8547 newmsginfo = folder_item_get_msginfo(draft, msgnum);
8550 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
8552 procmsg_msginfo_set_flags(newmsginfo, MSG_LOCKED, MSG_DRAFT);
8554 procmsg_msginfo_set_flags(newmsginfo, 0, MSG_DRAFT);
8555 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
8556 procmsg_msginfo_set_flags(newmsginfo, 0,
8557 MSG_HAS_ATTACHMENT);
8559 if (action == COMPOSE_DRAFT_FOR_EXIT) {
8560 compose_register_draft(newmsginfo);
8562 procmsg_msginfo_free(newmsginfo);
8565 folder_item_scan(draft);
8567 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
8569 g_mutex_unlock(compose->mutex); /* must be done before closing */
8570 compose_close(compose);
8576 path = folder_item_fetch_msg(draft, msgnum);
8578 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
8581 if (g_stat(path, &s) < 0) {
8582 FILE_OP_ERROR(path, "stat");
8588 procmsg_msginfo_free(compose->targetinfo);
8589 compose->targetinfo = procmsg_msginfo_new();
8590 compose->targetinfo->msgnum = msgnum;
8591 compose->targetinfo->size = s.st_size;
8592 compose->targetinfo->mtime = s.st_mtime;
8593 compose->targetinfo->folder = draft;
8595 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
8596 compose->mode = COMPOSE_REEDIT;
8598 if (action == COMPOSE_AUTO_SAVE) {
8599 compose->autosaved_draft = compose->targetinfo;
8601 compose->modified = FALSE;
8602 compose_set_title(compose);
8606 g_mutex_unlock(compose->mutex);
8610 void compose_clear_exit_drafts(void)
8612 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8613 DRAFTED_AT_EXIT, NULL);
8614 if (is_file_exist(filepath))
8620 void compose_reopen_exit_drafts(void)
8622 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8623 DRAFTED_AT_EXIT, NULL);
8624 FILE *fp = fopen(filepath, "rb");
8628 while (fgets(buf, sizeof(buf), fp)) {
8629 gchar **parts = g_strsplit(buf, "\t", 2);
8630 const gchar *folder = parts[0];
8631 int msgnum = parts[1] ? atoi(parts[1]):-1;
8633 if (folder && *folder && msgnum > -1) {
8634 FolderItem *item = folder_find_item_from_identifier(folder);
8635 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
8637 compose_reedit(info, FALSE);
8644 compose_clear_exit_drafts();
8647 static void compose_draft_cb(gpointer data, guint action, GtkWidget *widget)
8649 compose_draft(data, action);
8652 static void compose_attach_cb(gpointer data, guint action, GtkWidget *widget)
8654 Compose *compose = (Compose *)data;
8657 if (compose->redirect_filename != NULL)
8660 file_list = filesel_select_multiple_files_open(_("Select file"));
8665 for ( tmp = file_list; tmp; tmp = tmp->next) {
8666 gchar *file = (gchar *) tmp->data;
8667 gchar *utf8_filename = conv_filename_to_utf8(file);
8668 compose_attach_append(compose, file, utf8_filename, NULL);
8669 compose_changed_cb(NULL, compose);
8671 g_free(utf8_filename);
8673 g_list_free(file_list);
8677 static void compose_insert_file_cb(gpointer data, guint action,
8680 Compose *compose = (Compose *)data;
8683 file_list = filesel_select_multiple_files_open(_("Select file"));
8688 for ( tmp = file_list; tmp; tmp = tmp->next) {
8689 gchar *file = (gchar *) tmp->data;
8690 gchar *filedup = g_strdup(file);
8691 gchar *shortfile = g_path_get_basename(filedup);
8692 ComposeInsertResult res;
8694 res = compose_insert_file(compose, file);
8695 if (res == COMPOSE_INSERT_READ_ERROR) {
8696 alertpanel_error(_("File '%s' could not be read."), shortfile);
8697 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
8698 alertpanel_error(_("File '%s' contained invalid characters\n"
8699 "for the current encoding, insertion may be incorrect."), shortfile);
8705 g_list_free(file_list);
8709 static void compose_insert_sig_cb(gpointer data, guint action,
8712 Compose *compose = (Compose *)data;
8714 compose_insert_sig(compose, FALSE);
8717 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
8721 Compose *compose = (Compose *)data;
8723 gtkut_widget_get_uposition(widget, &x, &y);
8724 prefs_common.compose_x = x;
8725 prefs_common.compose_y = y;
8727 if (compose->sending || compose->updating)
8729 compose_close_cb(compose, 0, NULL);
8733 void compose_close_toolbar(Compose *compose)
8735 compose_close_cb(compose, 0, NULL);
8738 static void compose_close_cb(gpointer data, guint action, GtkWidget *widget)
8740 Compose *compose = (Compose *)data;
8744 if (compose->exteditor_tag != -1) {
8745 if (!compose_ext_editor_kill(compose))
8750 if (compose->modified) {
8751 val = alertpanel(_("Discard message"),
8752 _("This message has been modified. Discard it?"),
8753 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
8756 case G_ALERTDEFAULT:
8757 if (prefs_common.autosave)
8758 compose_remove_draft(compose);
8760 case G_ALERTALTERNATE:
8761 compose_draft_cb(data, COMPOSE_QUIT_EDITING, NULL);
8768 compose_close(compose);
8771 static void compose_set_encoding_cb(gpointer data, guint action,
8774 Compose *compose = (Compose *)data;
8776 if (GTK_CHECK_MENU_ITEM(widget)->active)
8777 compose->out_encoding = (CharSet)action;
8780 static void compose_address_cb(gpointer data, guint action, GtkWidget *widget)
8782 Compose *compose = (Compose *)data;
8784 addressbook_open(compose);
8787 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
8789 Compose *compose = (Compose *)data;
8794 tmpl = g_object_get_data(G_OBJECT(widget), "template");
8795 g_return_if_fail(tmpl != NULL);
8797 msg = g_strdup_printf(_("Do you want to apply the template '%s' ?"),
8799 val = alertpanel(_("Apply template"), msg,
8800 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
8803 if (val == G_ALERTDEFAULT)
8804 compose_template_apply(compose, tmpl, TRUE);
8805 else if (val == G_ALERTALTERNATE)
8806 compose_template_apply(compose, tmpl, FALSE);
8809 static void compose_ext_editor_cb(gpointer data, guint action,
8812 Compose *compose = (Compose *)data;
8814 compose_exec_ext_editor(compose);
8817 static void compose_undo_cb(Compose *compose)
8819 gboolean prev_autowrap = compose->autowrap;
8821 compose->autowrap = FALSE;
8822 undo_undo(compose->undostruct);
8823 compose->autowrap = prev_autowrap;
8826 static void compose_redo_cb(Compose *compose)
8828 gboolean prev_autowrap = compose->autowrap;
8830 compose->autowrap = FALSE;
8831 undo_redo(compose->undostruct);
8832 compose->autowrap = prev_autowrap;
8835 static void entry_cut_clipboard(GtkWidget *entry)
8837 if (GTK_IS_EDITABLE(entry))
8838 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
8839 else if (GTK_IS_TEXT_VIEW(entry))
8840 gtk_text_buffer_cut_clipboard(
8841 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
8842 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
8846 static void entry_copy_clipboard(GtkWidget *entry)
8848 if (GTK_IS_EDITABLE(entry))
8849 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
8850 else if (GTK_IS_TEXT_VIEW(entry))
8851 gtk_text_buffer_copy_clipboard(
8852 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
8853 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
8856 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
8857 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
8859 if (GTK_IS_TEXT_VIEW(entry)) {
8860 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
8861 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
8862 GtkTextIter start_iter, end_iter;
8864 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
8866 if (contents == NULL)
8869 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
8871 /* we shouldn't delete the selection when middle-click-pasting, or we
8872 * can't mid-click-paste our own selection */
8873 if (clip != GDK_SELECTION_PRIMARY) {
8874 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
8877 if (insert_place == NULL) {
8878 /* if insert_place isn't specified, insert at the cursor.
8879 * used for Ctrl-V pasting */
8880 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
8881 start = gtk_text_iter_get_offset(&start_iter);
8882 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
8884 /* if insert_place is specified, paste here.
8885 * used for mid-click-pasting */
8886 start = gtk_text_iter_get_offset(insert_place);
8887 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
8891 /* paste unwrapped: mark the paste so it's not wrapped later */
8892 end = start + strlen(contents);
8893 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
8894 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
8895 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
8896 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
8897 /* rewrap paragraph now (after a mid-click-paste) */
8898 mark_start = gtk_text_buffer_get_insert(buffer);
8899 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
8900 gtk_text_iter_backward_char(&start_iter);
8901 compose_beautify_paragraph(compose, &start_iter, TRUE);
8903 } else if (GTK_IS_EDITABLE(entry))
8904 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
8908 static void entry_allsel(GtkWidget *entry)
8910 if (GTK_IS_EDITABLE(entry))
8911 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
8912 else if (GTK_IS_TEXT_VIEW(entry)) {
8913 GtkTextIter startiter, enditer;
8914 GtkTextBuffer *textbuf;
8916 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
8917 gtk_text_buffer_get_start_iter(textbuf, &startiter);
8918 gtk_text_buffer_get_end_iter(textbuf, &enditer);
8920 gtk_text_buffer_move_mark_by_name(textbuf,
8921 "selection_bound", &startiter);
8922 gtk_text_buffer_move_mark_by_name(textbuf,
8923 "insert", &enditer);
8927 static void compose_cut_cb(Compose *compose)
8929 if (compose->focused_editable
8931 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
8934 entry_cut_clipboard(compose->focused_editable);
8937 static void compose_copy_cb(Compose *compose)
8939 if (compose->focused_editable
8941 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
8944 entry_copy_clipboard(compose->focused_editable);
8947 static void compose_paste_cb(Compose *compose)
8950 GtkTextBuffer *buffer;
8952 if (compose->focused_editable &&
8953 GTK_WIDGET_HAS_FOCUS(compose->focused_editable))
8954 entry_paste_clipboard(compose, compose->focused_editable,
8955 prefs_common.linewrap_pastes,
8956 GDK_SELECTION_CLIPBOARD, NULL);
8960 static void compose_paste_as_quote_cb(Compose *compose)
8962 gint wrap_quote = prefs_common.linewrap_quote;
8963 if (compose->focused_editable
8965 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
8968 /* let text_insert() (called directly or at a later time
8969 * after the gtk_editable_paste_clipboard) know that
8970 * text is to be inserted as a quotation. implemented
8971 * by using a simple refcount... */
8972 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
8973 G_OBJECT(compose->focused_editable),
8974 "paste_as_quotation"));
8975 g_object_set_data(G_OBJECT(compose->focused_editable),
8976 "paste_as_quotation",
8977 GINT_TO_POINTER(paste_as_quotation + 1));
8978 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
8979 entry_paste_clipboard(compose, compose->focused_editable,
8980 prefs_common.linewrap_pastes,
8981 GDK_SELECTION_CLIPBOARD, NULL);
8982 prefs_common.linewrap_quote = wrap_quote;
8986 static void compose_paste_no_wrap_cb(Compose *compose)
8989 GtkTextBuffer *buffer;
8991 if (compose->focused_editable
8993 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
8996 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
8997 GDK_SELECTION_CLIPBOARD, NULL);
9001 static void compose_paste_wrap_cb(Compose *compose)
9004 GtkTextBuffer *buffer;
9006 if (compose->focused_editable
9008 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9011 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
9012 GDK_SELECTION_CLIPBOARD, NULL);
9016 static void compose_allsel_cb(Compose *compose)
9018 if (compose->focused_editable
9020 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9023 entry_allsel(compose->focused_editable);
9026 static void textview_move_beginning_of_line (GtkTextView *text)
9028 GtkTextBuffer *buffer;
9032 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9034 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9035 mark = gtk_text_buffer_get_insert(buffer);
9036 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9037 gtk_text_iter_set_line_offset(&ins, 0);
9038 gtk_text_buffer_place_cursor(buffer, &ins);
9041 static void textview_move_forward_character (GtkTextView *text)
9043 GtkTextBuffer *buffer;
9047 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9049 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9050 mark = gtk_text_buffer_get_insert(buffer);
9051 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9052 if (gtk_text_iter_forward_cursor_position(&ins))
9053 gtk_text_buffer_place_cursor(buffer, &ins);
9056 static void textview_move_backward_character (GtkTextView *text)
9058 GtkTextBuffer *buffer;
9062 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9064 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9065 mark = gtk_text_buffer_get_insert(buffer);
9066 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9067 if (gtk_text_iter_backward_cursor_position(&ins))
9068 gtk_text_buffer_place_cursor(buffer, &ins);
9071 static void textview_move_forward_word (GtkTextView *text)
9073 GtkTextBuffer *buffer;
9078 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9080 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9081 mark = gtk_text_buffer_get_insert(buffer);
9082 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9083 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
9084 if (gtk_text_iter_forward_word_ends(&ins, count)) {
9085 gtk_text_iter_backward_word_start(&ins);
9086 gtk_text_buffer_place_cursor(buffer, &ins);
9090 static void textview_move_backward_word (GtkTextView *text)
9092 GtkTextBuffer *buffer;
9097 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9099 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9100 mark = gtk_text_buffer_get_insert(buffer);
9101 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9102 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
9103 if (gtk_text_iter_backward_word_starts(&ins, 1))
9104 gtk_text_buffer_place_cursor(buffer, &ins);
9107 static void textview_move_end_of_line (GtkTextView *text)
9109 GtkTextBuffer *buffer;
9113 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9115 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9116 mark = gtk_text_buffer_get_insert(buffer);
9117 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9118 if (gtk_text_iter_forward_to_line_end(&ins))
9119 gtk_text_buffer_place_cursor(buffer, &ins);
9122 static void textview_move_next_line (GtkTextView *text)
9124 GtkTextBuffer *buffer;
9129 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9131 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9132 mark = gtk_text_buffer_get_insert(buffer);
9133 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9134 offset = gtk_text_iter_get_line_offset(&ins);
9135 if (gtk_text_iter_forward_line(&ins)) {
9136 gtk_text_iter_set_line_offset(&ins, offset);
9137 gtk_text_buffer_place_cursor(buffer, &ins);
9141 static void textview_move_previous_line (GtkTextView *text)
9143 GtkTextBuffer *buffer;
9148 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9150 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9151 mark = gtk_text_buffer_get_insert(buffer);
9152 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9153 offset = gtk_text_iter_get_line_offset(&ins);
9154 if (gtk_text_iter_backward_line(&ins)) {
9155 gtk_text_iter_set_line_offset(&ins, offset);
9156 gtk_text_buffer_place_cursor(buffer, &ins);
9160 static void textview_delete_forward_character (GtkTextView *text)
9162 GtkTextBuffer *buffer;
9164 GtkTextIter ins, end_iter;
9166 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9168 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9169 mark = gtk_text_buffer_get_insert(buffer);
9170 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9172 if (gtk_text_iter_forward_char(&end_iter)) {
9173 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9177 static void textview_delete_backward_character (GtkTextView *text)
9179 GtkTextBuffer *buffer;
9181 GtkTextIter ins, end_iter;
9183 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9185 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9186 mark = gtk_text_buffer_get_insert(buffer);
9187 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9189 if (gtk_text_iter_backward_char(&end_iter)) {
9190 gtk_text_buffer_delete(buffer, &end_iter, &ins);
9194 static void textview_delete_forward_word (GtkTextView *text)
9196 GtkTextBuffer *buffer;
9198 GtkTextIter ins, end_iter;
9200 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9202 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9203 mark = gtk_text_buffer_get_insert(buffer);
9204 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9206 if (gtk_text_iter_forward_word_end(&end_iter)) {
9207 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9211 static void textview_delete_backward_word (GtkTextView *text)
9213 GtkTextBuffer *buffer;
9215 GtkTextIter ins, end_iter;
9217 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9219 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9220 mark = gtk_text_buffer_get_insert(buffer);
9221 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9223 if (gtk_text_iter_backward_word_start(&end_iter)) {
9224 gtk_text_buffer_delete(buffer, &end_iter, &ins);
9228 static void textview_delete_line (GtkTextView *text)
9230 GtkTextBuffer *buffer;
9232 GtkTextIter ins, start_iter, end_iter;
9235 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9237 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9238 mark = gtk_text_buffer_get_insert(buffer);
9239 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9242 gtk_text_iter_set_line_offset(&start_iter, 0);
9245 if (gtk_text_iter_ends_line(&end_iter))
9246 found = gtk_text_iter_forward_char(&end_iter);
9248 found = gtk_text_iter_forward_to_line_end(&end_iter);
9251 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
9254 static void textview_delete_to_line_end (GtkTextView *text)
9256 GtkTextBuffer *buffer;
9258 GtkTextIter ins, end_iter;
9261 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9263 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9264 mark = gtk_text_buffer_get_insert(buffer);
9265 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9267 if (gtk_text_iter_ends_line(&end_iter))
9268 found = gtk_text_iter_forward_char(&end_iter);
9270 found = gtk_text_iter_forward_to_line_end(&end_iter);
9272 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9275 static void compose_advanced_action_cb(Compose *compose,
9276 ComposeCallAdvancedAction action)
9278 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9280 void (*do_action) (GtkTextView *text);
9281 } action_table[] = {
9282 {textview_move_beginning_of_line},
9283 {textview_move_forward_character},
9284 {textview_move_backward_character},
9285 {textview_move_forward_word},
9286 {textview_move_backward_word},
9287 {textview_move_end_of_line},
9288 {textview_move_next_line},
9289 {textview_move_previous_line},
9290 {textview_delete_forward_character},
9291 {textview_delete_backward_character},
9292 {textview_delete_forward_word},
9293 {textview_delete_backward_word},
9294 {textview_delete_line},
9295 {NULL}, /* gtk_stext_delete_line_n */
9296 {textview_delete_to_line_end}
9299 if (!GTK_WIDGET_HAS_FOCUS(text)) return;
9301 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
9302 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
9303 if (action_table[action].do_action)
9304 action_table[action].do_action(text);
9306 g_warning("Not implemented yet.");
9310 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
9314 if (GTK_IS_EDITABLE(widget)) {
9315 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
9316 gtk_editable_set_position(GTK_EDITABLE(widget),
9319 if (widget->parent && widget->parent->parent
9320 && widget->parent->parent->parent) {
9321 if (GTK_IS_SCROLLED_WINDOW(widget->parent->parent->parent)) {
9322 gint y = widget->allocation.y;
9323 gint height = widget->allocation.height;
9324 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
9325 (GTK_SCROLLED_WINDOW(widget->parent->parent->parent));
9327 if (y < (int)shown->value) {
9328 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown), y - 1);
9330 if (y + height > (int)shown->value + (int)shown->page_size) {
9331 if (y - height - 1 < (int)shown->upper - (int)shown->page_size) {
9332 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
9333 y + height - (int)shown->page_size - 1);
9335 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
9336 (int)shown->upper - (int)shown->page_size - 1);
9343 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
9344 compose->focused_editable = widget;
9347 if (GTK_IS_TEXT_VIEW(widget)
9348 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
9349 gtk_widget_ref(compose->notebook);
9350 gtk_widget_ref(compose->edit_vbox);
9351 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
9352 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
9353 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
9354 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
9355 gtk_widget_unref(compose->notebook);
9356 gtk_widget_unref(compose->edit_vbox);
9357 g_signal_handlers_block_by_func(G_OBJECT(widget),
9358 G_CALLBACK(compose_grab_focus_cb),
9360 gtk_widget_grab_focus(widget);
9361 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
9362 G_CALLBACK(compose_grab_focus_cb),
9364 } else if (!GTK_IS_TEXT_VIEW(widget)
9365 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
9366 gtk_widget_ref(compose->notebook);
9367 gtk_widget_ref(compose->edit_vbox);
9368 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
9369 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
9370 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
9371 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
9372 gtk_widget_unref(compose->notebook);
9373 gtk_widget_unref(compose->edit_vbox);
9374 g_signal_handlers_block_by_func(G_OBJECT(widget),
9375 G_CALLBACK(compose_grab_focus_cb),
9377 gtk_widget_grab_focus(widget);
9378 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
9379 G_CALLBACK(compose_grab_focus_cb),
9385 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
9387 compose->modified = TRUE;
9389 compose_set_title(compose);
9393 static void compose_wrap_cb(gpointer data, guint action, GtkWidget *widget)
9395 Compose *compose = (Compose *)data;
9398 compose_wrap_all_full(compose, TRUE);
9400 compose_beautify_paragraph(compose, NULL, TRUE);
9403 static void compose_find_cb(gpointer data, guint action, GtkWidget *widget)
9405 Compose *compose = (Compose *)data;
9407 message_search_compose(compose);
9410 static void compose_toggle_autowrap_cb(gpointer data, guint action,
9413 Compose *compose = (Compose *)data;
9414 compose->autowrap = GTK_CHECK_MENU_ITEM(widget)->active;
9415 if (compose->autowrap)
9416 compose_wrap_all_full(compose, TRUE);
9417 compose->autowrap = GTK_CHECK_MENU_ITEM(widget)->active;
9420 static void compose_toggle_sign_cb(gpointer data, guint action,
9423 Compose *compose = (Compose *)data;
9425 if (GTK_CHECK_MENU_ITEM(widget)->active)
9426 compose->use_signing = TRUE;
9428 compose->use_signing = FALSE;
9431 static void compose_toggle_encrypt_cb(gpointer data, guint action,
9434 Compose *compose = (Compose *)data;
9436 if (GTK_CHECK_MENU_ITEM(widget)->active)
9437 compose->use_encryption = TRUE;
9439 compose->use_encryption = FALSE;
9442 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
9444 g_free(compose->privacy_system);
9446 compose->privacy_system = g_strdup(account->default_privacy_system);
9447 compose_update_privacy_system_menu_item(compose, warn);
9450 static void compose_toggle_ruler_cb(gpointer data, guint action,
9453 Compose *compose = (Compose *)data;
9455 if (GTK_CHECK_MENU_ITEM(widget)->active) {
9456 gtk_widget_show(compose->ruler_hbox);
9457 prefs_common.show_ruler = TRUE;
9459 gtk_widget_hide(compose->ruler_hbox);
9460 gtk_widget_queue_resize(compose->edit_vbox);
9461 prefs_common.show_ruler = FALSE;
9465 static void compose_attach_drag_received_cb (GtkWidget *widget,
9466 GdkDragContext *context,
9469 GtkSelectionData *data,
9474 Compose *compose = (Compose *)user_data;
9477 if (gdk_atom_name(data->type) &&
9478 !strcmp(gdk_atom_name(data->type), "text/uri-list")
9479 && gtk_drag_get_source_widget(context) !=
9480 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
9481 list = uri_list_extract_filenames((const gchar *)data->data);
9482 for (tmp = list; tmp != NULL; tmp = tmp->next) {
9483 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
9484 compose_attach_append
9485 (compose, (const gchar *)tmp->data,
9486 utf8_filename, NULL);
9487 g_free(utf8_filename);
9489 if (list) compose_changed_cb(NULL, compose);
9490 list_free_strings(list);
9492 } else if (gtk_drag_get_source_widget(context)
9493 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
9494 /* comes from our summaryview */
9495 SummaryView * summaryview = NULL;
9496 GSList * list = NULL, *cur = NULL;
9498 if (mainwindow_get_mainwindow())
9499 summaryview = mainwindow_get_mainwindow()->summaryview;
9502 list = summary_get_selected_msg_list(summaryview);
9504 for (cur = list; cur; cur = cur->next) {
9505 MsgInfo *msginfo = (MsgInfo *)cur->data;
9508 file = procmsg_get_message_file_full(msginfo,
9511 compose_attach_append(compose, (const gchar *)file,
9512 (const gchar *)file, "message/rfc822");
9520 static gboolean compose_drag_drop(GtkWidget *widget,
9521 GdkDragContext *drag_context,
9523 guint time, gpointer user_data)
9525 /* not handling this signal makes compose_insert_drag_received_cb
9530 static void compose_insert_drag_received_cb (GtkWidget *widget,
9531 GdkDragContext *drag_context,
9534 GtkSelectionData *data,
9539 Compose *compose = (Compose *)user_data;
9542 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
9544 if (gdk_atom_name(data->type) && !strcmp(gdk_atom_name(data->type), "text/uri-list")) {
9545 AlertValue val = G_ALERTDEFAULT;
9547 switch (prefs_common.compose_dnd_mode) {
9548 case COMPOSE_DND_ASK:
9549 val = alertpanel_full(_("Insert or attach?"),
9550 _("Do you want to insert the contents of the file(s) "
9551 "into the message body, or attach it to the email?"),
9552 GTK_STOCK_CANCEL, _("+_Insert"), _("_Attach"),
9553 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
9555 case COMPOSE_DND_INSERT:
9556 val = G_ALERTALTERNATE;
9558 case COMPOSE_DND_ATTACH:
9562 /* unexpected case */
9563 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
9566 if (val & G_ALERTDISABLE) {
9567 val &= ~G_ALERTDISABLE;
9568 /* remember what action to perform by default, only if we don't click Cancel */
9569 if (val == G_ALERTALTERNATE)
9570 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
9571 else if (val == G_ALERTOTHER)
9572 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
9575 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
9576 gtk_drag_finish(drag_context, FALSE, FALSE, time);
9578 } else if (val == G_ALERTOTHER) {
9579 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
9582 list = uri_list_extract_filenames((const gchar *)data->data);
9583 for (tmp = list; tmp != NULL; tmp = tmp->next) {
9584 compose_insert_file(compose, (const gchar *)tmp->data);
9586 list_free_strings(list);
9588 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9591 #if GTK_CHECK_VERSION(2, 8, 0)
9592 /* do nothing, handled by GTK */
9594 gchar *tmpfile = get_tmp_file();
9595 str_write_to_file((const gchar *)data->data, tmpfile);
9596 compose_insert_file(compose, tmpfile);
9599 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9603 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9606 static void compose_header_drag_received_cb (GtkWidget *widget,
9607 GdkDragContext *drag_context,
9610 GtkSelectionData *data,
9615 GtkEditable *entry = (GtkEditable *)user_data;
9616 gchar *email = (gchar *)data->data;
9618 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
9621 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
9622 gchar *decoded=g_new(gchar, strlen(email));
9625 email += strlen("mailto:");
9626 decode_uri(decoded, email); /* will fit */
9627 gtk_editable_delete_text(entry, 0, -1);
9628 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
9629 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9633 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9636 static void compose_toggle_return_receipt_cb(gpointer data, guint action,
9639 Compose *compose = (Compose *)data;
9641 if (GTK_CHECK_MENU_ITEM(widget)->active)
9642 compose->return_receipt = TRUE;
9644 compose->return_receipt = FALSE;
9647 static void compose_toggle_remove_refs_cb(gpointer data, guint action,
9650 Compose *compose = (Compose *)data;
9652 if (GTK_CHECK_MENU_ITEM(widget)->active)
9653 compose->remove_references = TRUE;
9655 compose->remove_references = FALSE;
9658 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
9660 ComposeHeaderEntry *headerentry)
9662 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
9663 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
9664 !(event->state & GDK_MODIFIER_MASK) &&
9665 (event->keyval == GDK_BackSpace) &&
9666 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
9667 gtk_container_remove
9668 (GTK_CONTAINER(headerentry->compose->header_table),
9669 headerentry->combo);
9670 gtk_container_remove
9671 (GTK_CONTAINER(headerentry->compose->header_table),
9672 headerentry->entry);
9673 headerentry->compose->header_list =
9674 g_slist_remove(headerentry->compose->header_list,
9676 g_free(headerentry);
9677 } else if (event->keyval == GDK_Tab) {
9678 if (headerentry->compose->header_last == headerentry) {
9679 /* Override default next focus, and give it to subject_entry
9680 * instead of notebook tabs
9682 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
9683 gtk_widget_grab_focus(headerentry->compose->subject_entry);
9690 static gboolean compose_headerentry_changed_cb(GtkWidget *entry,
9691 ComposeHeaderEntry *headerentry)
9693 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
9694 compose_create_header_entry(headerentry->compose);
9695 g_signal_handlers_disconnect_matched
9696 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
9697 0, 0, NULL, NULL, headerentry);
9699 /* Automatically scroll down */
9700 compose_show_first_last_header(headerentry->compose, FALSE);
9706 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
9708 GtkAdjustment *vadj;
9710 g_return_if_fail(compose);
9711 g_return_if_fail(GTK_IS_WIDGET(compose->header_table));
9712 g_return_if_fail(GTK_IS_VIEWPORT(compose->header_table->parent));
9714 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(compose->header_table->parent));
9715 gtk_adjustment_set_value(vadj, (show_first ? vadj->lower : vadj->upper));
9716 gtk_adjustment_changed(vadj);
9719 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
9720 const gchar *text, gint len, Compose *compose)
9722 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
9723 (G_OBJECT(compose->text), "paste_as_quotation"));
9726 g_return_if_fail(text != NULL);
9728 g_signal_handlers_block_by_func(G_OBJECT(buffer),
9729 G_CALLBACK(text_inserted),
9731 if (paste_as_quotation) {
9735 GtkTextIter start_iter;
9740 new_text = g_strndup(text, len);
9742 qmark = compose_quote_char_from_context(compose);
9744 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
9745 gtk_text_buffer_place_cursor(buffer, iter);
9747 pos = gtk_text_iter_get_offset(iter);
9749 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
9750 _("Quote format error at line %d."));
9751 quote_fmt_reset_vartable();
9753 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
9754 GINT_TO_POINTER(paste_as_quotation - 1));
9756 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
9757 gtk_text_buffer_place_cursor(buffer, iter);
9758 gtk_text_buffer_delete_mark(buffer, mark);
9760 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
9761 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
9762 compose_beautify_paragraph(compose, &start_iter, FALSE);
9763 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
9764 gtk_text_buffer_delete_mark(buffer, mark);
9766 if (strcmp(text, "\n") || compose->automatic_break
9767 || gtk_text_iter_starts_line(iter))
9768 gtk_text_buffer_insert(buffer, iter, text, len);
9770 debug_print("insert nowrap \\n\n");
9771 gtk_text_buffer_insert_with_tags_by_name(buffer,
9772 iter, text, len, "no_join", NULL);
9776 if (!paste_as_quotation) {
9777 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
9778 compose_beautify_paragraph(compose, iter, FALSE);
9779 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
9780 gtk_text_buffer_delete_mark(buffer, mark);
9783 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
9784 G_CALLBACK(text_inserted),
9786 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
9788 if (prefs_common.autosave &&
9789 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
9790 compose->draft_timeout_tag != -2 /* disabled while loading */)
9791 compose->draft_timeout_tag = g_timeout_add
9792 (500, (GtkFunction) compose_defer_auto_save_draft, compose);
9794 static gint compose_defer_auto_save_draft(Compose *compose)
9796 compose->draft_timeout_tag = -1;
9797 compose_draft_cb((gpointer)compose, COMPOSE_AUTO_SAVE, NULL);
9802 static void compose_check_all(Compose *compose)
9804 if (compose->gtkaspell)
9805 gtkaspell_check_all(compose->gtkaspell);
9808 static void compose_highlight_all(Compose *compose)
9810 if (compose->gtkaspell)
9811 gtkaspell_highlight_all(compose->gtkaspell);
9814 static void compose_check_backwards(Compose *compose)
9816 if (compose->gtkaspell)
9817 gtkaspell_check_backwards(compose->gtkaspell);
9819 GtkItemFactory *ifactory;
9820 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
9821 menu_set_sensitive(ifactory, "/Edit/Check backwards misspelled word", FALSE);
9822 menu_set_sensitive(ifactory, "/Edit/Forward to next misspelled word", FALSE);
9826 static void compose_check_forwards_go(Compose *compose)
9828 if (compose->gtkaspell)
9829 gtkaspell_check_forwards_go(compose->gtkaspell);
9831 GtkItemFactory *ifactory;
9832 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
9833 menu_set_sensitive(ifactory, "/Edit/Check backwards misspelled word", FALSE);
9834 menu_set_sensitive(ifactory, "/Edit/Forward to next misspelled word", FALSE);
9840 *\brief Guess originating forward account from MsgInfo and several
9841 * "common preference" settings. Return NULL if no guess.
9843 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
9845 PrefsAccount *account = NULL;
9847 g_return_val_if_fail(msginfo, NULL);
9848 g_return_val_if_fail(msginfo->folder, NULL);
9849 g_return_val_if_fail(msginfo->folder->prefs, NULL);
9851 if (msginfo->folder->prefs->enable_default_account)
9852 account = account_find_from_id(msginfo->folder->prefs->default_account);
9855 account = msginfo->folder->folder->account;
9857 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
9859 Xstrdup_a(to, msginfo->to, return NULL);
9860 extract_address(to);
9861 account = account_find_from_address(to);
9864 if (!account && prefs_common.forward_account_autosel) {
9866 if (!procheader_get_header_from_msginfo
9867 (msginfo, cc,sizeof cc , "Cc:")) {
9868 gchar *buf = cc + strlen("Cc:");
9869 extract_address(buf);
9870 account = account_find_from_address(buf);
9874 if (!account && prefs_common.forward_account_autosel) {
9875 gchar deliveredto[BUFFSIZE];
9876 if (!procheader_get_header_from_msginfo
9877 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
9878 gchar *buf = deliveredto + strlen("Delivered-To:");
9879 extract_address(buf);
9880 account = account_find_from_address(buf);
9887 gboolean compose_close(Compose *compose)
9891 if (!g_mutex_trylock(compose->mutex)) {
9892 /* we have to wait for the (possibly deferred by auto-save)
9893 * drafting to be done, before destroying the compose under
9895 debug_print("waiting for drafting to finish...\n");
9896 compose_allow_user_actions(compose, FALSE);
9897 g_timeout_add (500, (GSourceFunc) compose_close, compose);
9900 g_return_val_if_fail(compose, FALSE);
9901 gtkut_widget_get_uposition(compose->window, &x, &y);
9902 prefs_common.compose_x = x;
9903 prefs_common.compose_y = y;
9904 g_mutex_unlock(compose->mutex);
9905 compose_destroy(compose);
9910 * Add entry field for each address in list.
9911 * \param compose E-Mail composition object.
9912 * \param listAddress List of (formatted) E-Mail addresses.
9914 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
9919 addr = ( gchar * ) node->data;
9920 compose_entry_append( compose, addr, COMPOSE_TO );
9921 node = g_list_next( node );
9925 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
9926 guint action, gboolean opening_multiple)
9929 GSList *new_msglist = NULL;
9930 MsgInfo *tmp_msginfo = NULL;
9931 gboolean originally_enc = FALSE;
9932 Compose *compose = NULL;
9934 g_return_if_fail(msgview != NULL);
9936 g_return_if_fail(msginfo_list != NULL);
9938 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
9939 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
9940 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
9942 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
9943 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
9944 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
9945 orig_msginfo, mimeinfo);
9946 if (tmp_msginfo != NULL) {
9947 new_msglist = g_slist_append(NULL, tmp_msginfo);
9949 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
9950 tmp_msginfo->folder = orig_msginfo->folder;
9951 tmp_msginfo->msgnum = orig_msginfo->msgnum;
9952 if (orig_msginfo->tags)
9953 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
9958 if (!opening_multiple)
9959 body = messageview_get_selection(msgview);
9962 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
9963 procmsg_msginfo_free(tmp_msginfo);
9964 g_slist_free(new_msglist);
9966 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
9968 if (originally_enc) {
9969 compose_force_encryption(compose, compose->account, FALSE);
9975 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
9978 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
9979 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
9980 GSList *cur = msginfo_list;
9981 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
9982 "messages. Opening the windows "
9983 "could take some time. Do you "
9984 "want to continue?"),
9985 g_slist_length(msginfo_list));
9986 if (g_slist_length(msginfo_list) > 9
9987 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
9988 != G_ALERTALTERNATE) {
9993 /* We'll open multiple compose windows */
9994 /* let the WM place the next windows */
9995 compose_force_window_origin = FALSE;
9996 for (; cur; cur = cur->next) {
9998 tmplist.data = cur->data;
9999 tmplist.next = NULL;
10000 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
10002 compose_force_window_origin = TRUE;
10004 /* forwarding multiple mails as attachments is done via a
10005 * single compose window */
10006 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
10010 void compose_set_position(Compose *compose, gint pos)
10012 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10014 gtkut_text_view_set_position(text, pos);
10017 gboolean compose_search_string(Compose *compose,
10018 const gchar *str, gboolean case_sens)
10020 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10022 return gtkut_text_view_search_string(text, str, case_sens);
10025 gboolean compose_search_string_backward(Compose *compose,
10026 const gchar *str, gboolean case_sens)
10028 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10030 return gtkut_text_view_search_string_backward(text, str, case_sens);
10033 /* allocate a msginfo structure and populate its data from a compose data structure */
10034 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
10036 MsgInfo *newmsginfo;
10038 gchar buf[BUFFSIZE];
10040 g_return_val_if_fail( compose != NULL, NULL );
10042 newmsginfo = procmsg_msginfo_new();
10045 get_rfc822_date(buf, sizeof(buf));
10046 newmsginfo->date = g_strdup(buf);
10049 if (compose->from_name) {
10050 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
10051 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
10055 if (compose->subject_entry)
10056 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
10058 /* to, cc, reply-to, newsgroups */
10059 for (list = compose->header_list; list; list = list->next) {
10060 gchar *header = gtk_editable_get_chars(
10062 GTK_COMBO(((ComposeHeaderEntry *)list->data)->combo)->entry), 0, -1);
10063 gchar *entry = gtk_editable_get_chars(
10064 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
10066 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
10067 if ( newmsginfo->to == NULL ) {
10068 newmsginfo->to = g_strdup(entry);
10069 } else if (entry && *entry) {
10070 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
10071 g_free(newmsginfo->to);
10072 newmsginfo->to = tmp;
10075 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
10076 if ( newmsginfo->cc == NULL ) {
10077 newmsginfo->cc = g_strdup(entry);
10078 } else if (entry && *entry) {
10079 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
10080 g_free(newmsginfo->cc);
10081 newmsginfo->cc = tmp;
10084 if ( strcasecmp(header,
10085 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
10086 if ( newmsginfo->newsgroups == NULL ) {
10087 newmsginfo->newsgroups = g_strdup(entry);
10088 } else if (entry && *entry) {
10089 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
10090 g_free(newmsginfo->newsgroups);
10091 newmsginfo->newsgroups = tmp;
10099 /* other data is unset */
10105 /* update compose's dictionaries from folder dict settings */
10106 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
10107 FolderItem *folder_item)
10109 g_return_if_fail(compose != NULL);
10111 if (compose->gtkaspell && folder_item && folder_item->prefs) {
10112 FolderItemPrefs *prefs = folder_item->prefs;
10114 if (prefs->enable_default_dictionary)
10115 gtkaspell_change_dict(compose->gtkaspell,
10116 prefs->default_dictionary, FALSE);
10117 if (folder_item->prefs->enable_default_alt_dictionary)
10118 gtkaspell_change_alt_dict(compose->gtkaspell,
10119 prefs->default_alt_dictionary);
10120 if (prefs->enable_default_dictionary
10121 || prefs->enable_default_alt_dictionary)
10122 compose_spell_menu_changed(compose);