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);
549 static void compose_attach_update_label(Compose *compose);
551 static void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data);
553 static GtkItemFactoryEntry compose_popup_entries[] =
555 {N_("/_Add..."), NULL, compose_attach_cb, 0, NULL},
556 {N_("/_Remove"), NULL, compose_attach_remove_selected, 0, NULL},
557 {"/---", NULL, NULL, 0, "<Separator>"},
558 {N_("/_Properties..."), NULL, compose_attach_property, 0, NULL}
561 static GtkItemFactoryEntry compose_entries[] =
563 {N_("/_Message"), NULL, NULL, 0, "<Branch>"},
564 {N_("/_Message/S_end"), "<control>Return",
565 compose_send_cb, 0, NULL},
566 {N_("/_Message/Send _later"), "<shift><control>S",
567 compose_send_later_cb, 0, NULL},
568 {N_("/_Message/---"), NULL, NULL, 0, "<Separator>"},
569 {N_("/_Message/_Attach file"), "<control>M", compose_attach_cb, 0, NULL},
570 {N_("/_Message/_Insert file"), "<control>I", compose_insert_file_cb, 0, NULL},
571 {N_("/_Message/Insert si_gnature"), "<control>G", compose_insert_sig_cb, 0, NULL},
572 {N_("/_Message/---"), NULL, NULL, 0, "<Separator>"},
573 {N_("/_Message/_Save"),
574 "<control>S", compose_draft_cb, COMPOSE_KEEP_EDITING, NULL},
575 {N_("/_Message/---"), NULL, NULL, 0, "<Separator>"},
576 {N_("/_Message/_Close"), "<control>W", compose_close_cb, 0, NULL},
578 {N_("/_Edit"), NULL, NULL, 0, "<Branch>"},
579 {N_("/_Edit/_Undo"), "<control>Z", compose_undo_cb, 0, NULL},
580 {N_("/_Edit/_Redo"), "<control>Y", compose_redo_cb, 0, NULL},
581 {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
582 {N_("/_Edit/Cu_t"), "<control>X", compose_cut_cb, 0, NULL},
583 {N_("/_Edit/_Copy"), "<control>C", compose_copy_cb, 0, NULL},
584 {N_("/_Edit/_Paste"), "<control>V", compose_paste_cb, 0, NULL},
585 {N_("/_Edit/Special paste"), NULL, NULL, 0, "<Branch>"},
586 {N_("/_Edit/Special paste/as _quotation"),
587 NULL, compose_paste_as_quote_cb, 0, NULL},
588 {N_("/_Edit/Special paste/_wrapped"),
589 NULL, compose_paste_wrap_cb, 0, NULL},
590 {N_("/_Edit/Special paste/_unwrapped"),
591 NULL, compose_paste_no_wrap_cb, 0, NULL},
592 {N_("/_Edit/Select _all"), "<control>A", compose_allsel_cb, 0, NULL},
593 {N_("/_Edit/A_dvanced"), NULL, NULL, 0, "<Branch>"},
594 {N_("/_Edit/A_dvanced/Move a character backward"),
596 compose_advanced_action_cb,
597 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER,
599 {N_("/_Edit/A_dvanced/Move a character forward"),
601 compose_advanced_action_cb,
602 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER,
604 {N_("/_Edit/A_dvanced/Move a word backward"),
606 compose_advanced_action_cb,
607 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD,
609 {N_("/_Edit/A_dvanced/Move a word forward"),
611 compose_advanced_action_cb,
612 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD,
614 {N_("/_Edit/A_dvanced/Move to beginning of line"),
615 NULL, /* "<control>A" */
616 compose_advanced_action_cb,
617 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE,
619 {N_("/_Edit/A_dvanced/Move to end of line"),
621 compose_advanced_action_cb,
622 COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE,
624 {N_("/_Edit/A_dvanced/Move to previous line"),
626 compose_advanced_action_cb,
627 COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE,
629 {N_("/_Edit/A_dvanced/Move to next line"),
631 compose_advanced_action_cb,
632 COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE,
634 {N_("/_Edit/A_dvanced/Delete a character backward"),
636 compose_advanced_action_cb,
637 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER,
639 {N_("/_Edit/A_dvanced/Delete a character forward"),
641 compose_advanced_action_cb,
642 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER,
644 {N_("/_Edit/A_dvanced/Delete a word backward"),
645 NULL, /* "<control>W" */
646 compose_advanced_action_cb,
647 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD,
649 {N_("/_Edit/A_dvanced/Delete a word forward"),
650 NULL, /* "<alt>D", */
651 compose_advanced_action_cb,
652 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD,
654 {N_("/_Edit/A_dvanced/Delete line"),
656 compose_advanced_action_cb,
657 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE,
659 {N_("/_Edit/A_dvanced/Delete entire line"),
661 compose_advanced_action_cb,
662 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE_N,
664 {N_("/_Edit/A_dvanced/Delete to end of line"),
666 compose_advanced_action_cb,
667 COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END,
669 {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
671 "<control>F", compose_find_cb, 0, NULL},
672 {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
673 {N_("/_Edit/_Wrap current paragraph"),
674 "<control>L", compose_wrap_cb, 0, NULL},
675 {N_("/_Edit/Wrap all long _lines"),
676 "<control><alt>L", compose_wrap_cb, 1, NULL},
677 {N_("/_Edit/Aut_o wrapping"), "<shift><control>L", compose_toggle_autowrap_cb, 0, "<ToggleItem>"},
678 {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
679 {N_("/_Edit/Edit with e_xternal editor"),
680 "<shift><control>X", compose_ext_editor_cb, 0, NULL},
682 {N_("/_Spelling"), NULL, NULL, 0, "<Branch>"},
683 {N_("/_Spelling/_Check all or check selection"),
684 NULL, compose_check_all, 0, NULL},
685 {N_("/_Spelling/_Highlight all misspelled words"),
686 NULL, compose_highlight_all, 0, NULL},
687 {N_("/_Spelling/Check _backwards misspelled word"),
688 NULL, compose_check_backwards , 0, NULL},
689 {N_("/_Spelling/_Forward to next misspelled word"),
690 NULL, compose_check_forwards_go, 0, NULL},
691 {N_("/_Spelling/---"), NULL, NULL, 0, "<Separator>"},
692 {N_("/_Spelling/Options"),
693 NULL, NULL, 0, "<Branch>"},
695 {N_("/_Options"), NULL, NULL, 0, "<Branch>"},
696 {N_("/_Options/Reply _mode"), NULL, NULL, 0, "<Branch>"},
697 {N_("/_Options/Reply _mode/_Normal"), NULL, compose_reply_change_mode, COMPOSE_REPLY, "<RadioItem>"},
698 {N_("/_Options/Reply _mode/_All"), NULL, compose_reply_change_mode, COMPOSE_REPLY_TO_ALL, "/Options/Reply mode/Normal"},
699 {N_("/_Options/Reply _mode/_Sender"), NULL, compose_reply_change_mode, COMPOSE_REPLY_TO_SENDER, "/Options/Reply mode/Normal"},
700 {N_("/_Options/Reply _mode/_Mailing-list"), NULL, compose_reply_change_mode, COMPOSE_REPLY_TO_LIST, "/Options/Reply mode/Normal"},
701 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
702 {N_("/_Options/Privacy _System"), NULL, NULL, 0, "<Branch>"},
703 {N_("/_Options/Privacy _System/None"), NULL, NULL, 0, "<RadioItem>"},
704 {N_("/_Options/Si_gn"), NULL, compose_toggle_sign_cb , 0, "<ToggleItem>"},
705 {N_("/_Options/_Encrypt"), NULL, compose_toggle_encrypt_cb, 0, "<ToggleItem>"},
706 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
707 {N_("/_Options/_Priority"), NULL, NULL, 0, "<Branch>"},
708 {N_("/_Options/Priority/_Highest"), NULL, compose_set_priority_cb, PRIORITY_HIGHEST, "<RadioItem>"},
709 {N_("/_Options/Priority/Hi_gh"), NULL, compose_set_priority_cb, PRIORITY_HIGH, "/Options/Priority/Highest"},
710 {N_("/_Options/Priority/_Normal"), NULL, compose_set_priority_cb, PRIORITY_NORMAL, "/Options/Priority/Highest"},
711 {N_("/_Options/Priority/Lo_w"), NULL, compose_set_priority_cb, PRIORITY_LOW, "/Options/Priority/Highest"},
712 {N_("/_Options/Priority/_Lowest"), NULL, compose_set_priority_cb, PRIORITY_LOWEST, "/Options/Priority/Highest"},
713 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
714 {N_("/_Options/_Request Return Receipt"), NULL, compose_toggle_return_receipt_cb, 0, "<ToggleItem>"},
715 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
716 {N_("/_Options/Remo_ve references"), NULL, compose_toggle_remove_refs_cb, 0, "<ToggleItem>"},
717 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
719 #define ENC_ACTION(action) \
720 NULL, compose_set_encoding_cb, action, \
721 "/Options/Character encoding/Automatic"
723 {N_("/_Options/Character _encoding"), NULL, NULL, 0, "<Branch>"},
724 {N_("/_Options/Character _encoding/_Automatic"),
725 NULL, compose_set_encoding_cb, C_AUTO, "<RadioItem>"},
726 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
728 {N_("/_Options/Character _encoding/7bit ASCII (US-ASC_II)"),
729 ENC_ACTION(C_US_ASCII)},
730 {N_("/_Options/Character _encoding/Unicode (_UTF-8)"),
731 ENC_ACTION(C_UTF_8)},
732 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
734 {N_("/_Options/Character _encoding/Western European"), NULL, NULL, 0, "<Branch>"},
735 {N_("/_Options/Character _encoding/Western European/ISO-8859-_1"),
736 ENC_ACTION(C_ISO_8859_1)},
737 {N_("/_Options/Character _encoding/Western European/ISO-8859-15"),
738 ENC_ACTION(C_ISO_8859_15)},
739 {N_("/_Options/Character _encoding/Western European/Windows-1252"),
740 ENC_ACTION(C_WINDOWS_1252)},
742 {N_("/_Options/Character _encoding/Central European (ISO-8859-_2)"),
743 ENC_ACTION(C_ISO_8859_2)},
745 {N_("/_Options/Character _encoding/Baltic"), NULL, NULL, 0, "<Branch>"},
746 {N_("/_Options/Character _encoding/Baltic/ISO-8859-13"),
747 ENC_ACTION(C_ISO_8859_13)},
748 {N_("/_Options/Character _encoding/Baltic/ISO-8859-_4"),
749 ENC_ACTION(C_ISO_8859_4)},
751 {N_("/_Options/Character _encoding/Greek (ISO-8859-_7)"),
752 ENC_ACTION(C_ISO_8859_7)},
754 {N_("/_Options/Character _encoding/Hebrew"), NULL, NULL, 0, "<Branch>"},
755 {N_("/_Options/Character _encoding/Hebrew/ISO-8859-_8"),
756 ENC_ACTION(C_ISO_8859_8)},
757 {N_("/_Options/Character _encoding/Hebrew/Windows-1255"),
758 ENC_ACTION(C_WINDOWS_1255)},
760 {N_("/_Options/Character _encoding/Arabic"), NULL, NULL, 0, "<Branch>"},
761 {N_("/_Options/Character _encoding/Arabic/ISO-8859-_6"),
762 ENC_ACTION(C_ISO_8859_6)},
763 {N_("/_Options/Character _encoding/Arabic/Windows-1256"),
764 ENC_ACTION(C_CP1256)},
766 {N_("/_Options/Character _encoding/Turkish (ISO-8859-_9)"),
767 ENC_ACTION(C_ISO_8859_9)},
769 {N_("/_Options/Character _encoding/Cyrillic"), NULL, NULL, 0, "<Branch>"},
770 {N_("/_Options/Character _encoding/Cyrillic/ISO-8859-_5"),
771 ENC_ACTION(C_ISO_8859_5)},
772 {N_("/_Options/Character _encoding/Cyrillic/KOI8-_R"),
773 ENC_ACTION(C_KOI8_R)},
774 {N_("/_Options/Character _encoding/Cyrillic/KOI8-U"),
775 ENC_ACTION(C_KOI8_U)},
776 {N_("/_Options/Character _encoding/Cyrillic/Windows-1251"),
777 ENC_ACTION(C_WINDOWS_1251)},
779 {N_("/_Options/Character _encoding/Japanese"), NULL, NULL, 0, "<Branch>"},
780 {N_("/_Options/Character _encoding/Japanese/ISO-2022-_JP"),
781 ENC_ACTION(C_ISO_2022_JP)},
782 {N_("/_Options/Character _encoding/Japanese/ISO-2022-JP-2"),
783 ENC_ACTION(C_ISO_2022_JP_2)},
784 {N_("/_Options/Character _encoding/Japanese/_EUC-JP"),
785 ENC_ACTION(C_EUC_JP)},
786 {N_("/_Options/Character _encoding/Japanese/_Shift__JIS"),
787 ENC_ACTION(C_SHIFT_JIS)},
789 {N_("/_Options/Character _encoding/Chinese"), NULL, NULL, 0, "<Branch>"},
790 {N_("/_Options/Character _encoding/Chinese/Simplified (_GB2312)"),
791 ENC_ACTION(C_GB2312)},
792 {N_("/_Options/Character _encoding/Chinese/Simplified (GBK)"),
794 {N_("/_Options/Character _encoding/Chinese/Traditional (_Big5)"),
796 {N_("/_Options/Character _encoding/Chinese/Traditional (EUC-_TW)"),
797 ENC_ACTION(C_EUC_TW)},
799 {N_("/_Options/Character _encoding/Korean"), NULL, NULL, 0, "<Branch>"},
800 {N_("/_Options/Character _encoding/Korean/EUC-_KR"),
801 ENC_ACTION(C_EUC_KR)},
802 {N_("/_Options/Character _encoding/Korean/ISO-2022-KR"),
803 ENC_ACTION(C_ISO_2022_KR)},
805 {N_("/_Options/Character _encoding/Thai"), NULL, NULL, 0, "<Branch>"},
806 {N_("/_Options/Character _encoding/Thai/TIS-620"),
807 ENC_ACTION(C_TIS_620)},
808 {N_("/_Options/Character _encoding/Thai/Windows-874"),
809 ENC_ACTION(C_WINDOWS_874)},
811 {N_("/_Tools"), NULL, NULL, 0, "<Branch>"},
812 {N_("/_Tools/Show _ruler"), NULL, compose_toggle_ruler_cb, 0, "<ToggleItem>"},
813 {N_("/_Tools/_Address book"), "<shift><control>A", compose_address_cb , 0, NULL},
814 {N_("/_Tools/_Template"), NULL, NULL, 0, "<Branch>"},
815 {N_("/_Tools/Actio_ns"), NULL, NULL, 0, "<Branch>"},
816 {N_("/_Help"), NULL, NULL, 0, "<Branch>"},
817 {N_("/_Help/_About"), NULL, about_show, 0, NULL}
820 static GtkTargetEntry compose_mime_types[] =
822 {"text/uri-list", 0, 0},
823 {"UTF8_STRING", 0, 0},
827 static gboolean compose_put_existing_to_front(MsgInfo *info)
829 GList *compose_list = compose_get_compose_list();
833 for (elem = compose_list; elem != NULL && elem->data != NULL;
835 Compose *c = (Compose*)elem->data;
837 if (!c->targetinfo || !c->targetinfo->msgid ||
841 if (!strcmp(c->targetinfo->msgid, info->msgid)) {
842 gtkut_window_popup(c->window);
850 static GdkColor quote_color1 =
851 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
852 static GdkColor quote_color2 =
853 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
854 static GdkColor quote_color3 =
855 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
857 static GdkColor quote_bgcolor1 =
858 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
859 static GdkColor quote_bgcolor2 =
860 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
861 static GdkColor quote_bgcolor3 =
862 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
864 static GdkColor signature_color = {
871 static GdkColor uri_color = {
878 static void compose_create_tags(GtkTextView *text, Compose *compose)
880 GtkTextBuffer *buffer;
881 GdkColor black = {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
887 buffer = gtk_text_view_get_buffer(text);
889 if (prefs_common.enable_color) {
890 /* grab the quote colors, converting from an int to a GdkColor */
891 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
893 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
895 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
897 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_bgcol,
899 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_bgcol,
901 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_bgcol,
903 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
905 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
908 signature_color = quote_color1 = quote_color2 = quote_color3 =
909 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
912 if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
913 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
914 "foreground-gdk", "e_color1,
915 "paragraph-background-gdk", "e_bgcolor1,
917 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
918 "foreground-gdk", "e_color2,
919 "paragraph-background-gdk", "e_bgcolor2,
921 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
922 "foreground-gdk", "e_color3,
923 "paragraph-background-gdk", "e_bgcolor3,
926 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
927 "foreground-gdk", "e_color1,
929 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
930 "foreground-gdk", "e_color2,
932 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
933 "foreground-gdk", "e_color3,
937 compose->signature_tag = gtk_text_buffer_create_tag(buffer, "signature",
938 "foreground-gdk", &signature_color,
941 compose->uri_tag = gtk_text_buffer_create_tag(buffer, "link",
942 "foreground-gdk", &uri_color,
944 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
945 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
947 color[0] = quote_color1;
948 color[1] = quote_color2;
949 color[2] = quote_color3;
950 color[3] = quote_bgcolor1;
951 color[4] = quote_bgcolor2;
952 color[5] = quote_bgcolor3;
953 color[6] = signature_color;
954 color[7] = uri_color;
955 cmap = gdk_drawable_get_colormap(compose->window->window);
956 gdk_colormap_alloc_colors(cmap, color, 8, FALSE, TRUE, success);
958 for (i = 0; i < 8; i++) {
959 if (success[i] == FALSE) {
962 g_warning("Compose: color allocation failed.\n");
963 style = gtk_widget_get_style(GTK_WIDGET(text));
964 quote_color1 = quote_color2 = quote_color3 =
965 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 =
966 signature_color = uri_color = black;
971 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
972 GPtrArray *attach_files)
974 return compose_generic_new(account, mailto, NULL, attach_files, NULL);
977 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
979 return compose_generic_new(account, mailto, item, NULL, NULL);
982 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
984 return compose_generic_new( account, NULL, NULL, NULL, listAddress );
987 #define SCROLL_TO_CURSOR(compose) { \
988 GtkTextMark *cmark = gtk_text_buffer_get_insert( \
989 gtk_text_view_get_buffer( \
990 GTK_TEXT_VIEW(compose->text))); \
991 gtk_text_view_scroll_mark_onscreen( \
992 GTK_TEXT_VIEW(compose->text), \
996 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
997 GPtrArray *attach_files, GList *listAddress )
1000 GtkTextView *textview;
1001 GtkTextBuffer *textbuf;
1003 GtkItemFactory *ifactory;
1004 const gchar *subject_format = NULL;
1005 const gchar *body_format = NULL;
1007 if (item && item->prefs && item->prefs->enable_default_account)
1008 account = account_find_from_id(item->prefs->default_account);
1010 if (!account) account = cur_account;
1011 g_return_val_if_fail(account != NULL, NULL);
1013 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
1015 ifactory = gtk_item_factory_from_widget(compose->menubar);
1017 compose->replyinfo = NULL;
1018 compose->fwdinfo = NULL;
1020 textview = GTK_TEXT_VIEW(compose->text);
1021 textbuf = gtk_text_view_get_buffer(textview);
1022 compose_create_tags(textview, compose);
1024 undo_block(compose->undostruct);
1026 compose_set_dictionaries_from_folder_prefs(compose, item);
1029 if (account->auto_sig)
1030 compose_insert_sig(compose, FALSE);
1031 gtk_text_buffer_get_start_iter(textbuf, &iter);
1032 gtk_text_buffer_place_cursor(textbuf, &iter);
1034 if (account->protocol != A_NNTP) {
1035 if (mailto && *mailto != '\0') {
1036 compose_entries_set(compose, mailto);
1038 } else if (item && item->prefs->enable_default_to) {
1039 compose_entry_append(compose, item->prefs->default_to, COMPOSE_TO);
1040 compose_entry_mark_default_to(compose, item->prefs->default_to);
1042 if (item && item->ret_rcpt) {
1043 menu_set_active(ifactory, "/Options/Request Return Receipt", TRUE);
1047 compose_entry_append(compose, mailto, COMPOSE_NEWSGROUPS);
1048 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1049 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS);
1052 * CLAWS: just don't allow return receipt request, even if the user
1053 * may want to send an email. simple but foolproof.
1055 menu_set_sensitive(ifactory, "/Options/Request Return Receipt", FALSE);
1057 compose_add_field_list( compose, listAddress );
1059 if (item && item->prefs && item->prefs->compose_with_format) {
1060 subject_format = item->prefs->compose_subject_format;
1061 body_format = item->prefs->compose_body_format;
1062 } else if (account->compose_with_format) {
1063 subject_format = account->compose_subject_format;
1064 body_format = account->compose_body_format;
1065 } else if (prefs_common.compose_with_format) {
1066 subject_format = prefs_common.compose_subject_format;
1067 body_format = prefs_common.compose_body_format;
1070 if (subject_format || body_format) {
1071 MsgInfo* dummyinfo = NULL;
1074 && *subject_format != '\0' )
1076 gchar *subject = NULL;
1080 dummyinfo = compose_msginfo_new_from_compose(compose);
1082 /* decode \-escape sequences in the internal representation of the quote format */
1083 tmp = malloc(strlen(subject_format)+1);
1084 pref_get_unescaped_pref(tmp, subject_format);
1086 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1088 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account,
1089 compose->gtkaspell);
1091 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account);
1093 quote_fmt_scan_string(tmp);
1096 buf = quote_fmt_get_buffer();
1098 alertpanel_error(_("New message subject format error."));
1100 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1101 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1102 quote_fmt_reset_vartable();
1109 && *body_format != '\0' )
1112 GtkTextBuffer *buffer;
1113 GtkTextIter start, end;
1116 if ( dummyinfo == NULL )
1117 dummyinfo = compose_msginfo_new_from_compose(compose);
1119 text = GTK_TEXT_VIEW(compose->text);
1120 buffer = gtk_text_view_get_buffer(text);
1121 gtk_text_buffer_get_start_iter(buffer, &start);
1122 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1123 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1125 compose_quote_fmt(compose, dummyinfo,
1127 NULL, tmp, FALSE, TRUE,
1128 _("New message body format error at line %d."));
1129 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1130 quote_fmt_reset_vartable();
1135 procmsg_msginfo_free( dummyinfo );
1142 for (i = 0; i < attach_files->len; i++) {
1143 file = g_ptr_array_index(attach_files, i);
1144 compose_attach_append(compose, file, file, NULL);
1148 compose_show_first_last_header(compose, TRUE);
1150 /* Set save folder */
1151 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1152 gchar *folderidentifier;
1154 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
1155 folderidentifier = folder_item_get_identifier(item);
1156 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
1157 g_free(folderidentifier);
1160 gtk_widget_grab_focus(compose->header_last->entry);
1162 undo_unblock(compose->undostruct);
1164 if (prefs_common.auto_exteditor)
1165 compose_exec_ext_editor(compose);
1167 compose->draft_timeout_tag = -1;
1168 SCROLL_TO_CURSOR(compose);
1170 compose->modified = FALSE;
1171 compose_set_title(compose);
1175 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1176 gboolean override_pref)
1178 gchar *privacy = NULL;
1180 g_return_if_fail(compose != NULL);
1181 g_return_if_fail(account != NULL);
1183 if (override_pref == FALSE && account->default_encrypt_reply == FALSE)
1186 if (account->default_privacy_system
1187 && strlen(account->default_privacy_system)) {
1188 privacy = account->default_privacy_system;
1190 GSList *privacy_avail = privacy_get_system_ids();
1191 if (privacy_avail && g_slist_length(privacy_avail)) {
1192 privacy = (gchar *)(privacy_avail->data);
1195 if (privacy != NULL) {
1196 if (compose->privacy_system == NULL)
1197 compose->privacy_system = g_strdup(privacy);
1198 compose_update_privacy_system_menu_item(compose, FALSE);
1199 compose_use_encryption(compose, TRUE);
1203 static void compose_force_signing(Compose *compose, PrefsAccount *account)
1205 gchar *privacy = NULL;
1207 if (account->default_privacy_system
1208 && strlen(account->default_privacy_system)) {
1209 privacy = account->default_privacy_system;
1211 GSList *privacy_avail = privacy_get_system_ids();
1212 if (privacy_avail && g_slist_length(privacy_avail)) {
1213 privacy = (gchar *)(privacy_avail->data);
1216 if (privacy != NULL) {
1217 if (compose->privacy_system == NULL)
1218 compose->privacy_system = g_strdup(privacy);
1219 compose_update_privacy_system_menu_item(compose, FALSE);
1220 compose_use_signing(compose, TRUE);
1224 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1228 Compose *compose = NULL;
1229 GtkItemFactory *ifactory = NULL;
1231 g_return_val_if_fail(msginfo_list != NULL, NULL);
1233 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1234 g_return_val_if_fail(msginfo != NULL, NULL);
1236 list_len = g_slist_length(msginfo_list);
1240 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1241 FALSE, prefs_common.default_reply_list, FALSE, body);
1243 case COMPOSE_REPLY_WITH_QUOTE:
1244 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1245 FALSE, prefs_common.default_reply_list, FALSE, body);
1247 case COMPOSE_REPLY_WITHOUT_QUOTE:
1248 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1249 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1251 case COMPOSE_REPLY_TO_SENDER:
1252 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1253 FALSE, FALSE, TRUE, body);
1255 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1256 compose = compose_followup_and_reply_to(msginfo,
1257 COMPOSE_QUOTE_CHECK,
1258 FALSE, FALSE, body);
1260 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1261 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1262 FALSE, FALSE, TRUE, body);
1264 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1265 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1266 FALSE, FALSE, TRUE, NULL);
1268 case COMPOSE_REPLY_TO_ALL:
1269 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1270 TRUE, FALSE, FALSE, body);
1272 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1273 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1274 TRUE, FALSE, FALSE, body);
1276 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1277 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1278 TRUE, FALSE, FALSE, NULL);
1280 case COMPOSE_REPLY_TO_LIST:
1281 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1282 FALSE, TRUE, FALSE, body);
1284 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1285 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1286 FALSE, TRUE, FALSE, body);
1288 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1289 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1290 FALSE, TRUE, FALSE, NULL);
1292 case COMPOSE_FORWARD:
1293 if (prefs_common.forward_as_attachment) {
1294 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1297 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1301 case COMPOSE_FORWARD_INLINE:
1302 /* check if we reply to more than one Message */
1303 if (list_len == 1) {
1304 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1307 /* more messages FALL THROUGH */
1308 case COMPOSE_FORWARD_AS_ATTACH:
1309 compose = compose_forward_multiple(NULL, msginfo_list);
1311 case COMPOSE_REDIRECT:
1312 compose = compose_redirect(NULL, msginfo, FALSE);
1315 g_warning("compose_reply_mode(): invalid Compose Mode: %d\n", mode);
1318 if (compose == NULL) {
1319 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1322 ifactory = gtk_item_factory_from_widget(compose->menubar);
1324 compose->rmode = mode;
1325 switch (compose->rmode) {
1327 case COMPOSE_REPLY_WITH_QUOTE:
1328 case COMPOSE_REPLY_WITHOUT_QUOTE:
1329 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1330 debug_print("reply mode Normal\n");
1331 menu_set_active(ifactory, "/Options/Reply mode/Normal", TRUE);
1332 compose_reply_change_mode(compose, COMPOSE_REPLY, NULL); /* force update */
1334 case COMPOSE_REPLY_TO_SENDER:
1335 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1336 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1337 debug_print("reply mode Sender\n");
1338 menu_set_active(ifactory, "/Options/Reply mode/Sender", TRUE);
1340 case COMPOSE_REPLY_TO_ALL:
1341 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1342 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1343 debug_print("reply mode All\n");
1344 menu_set_active(ifactory, "/Options/Reply mode/All", TRUE);
1346 case COMPOSE_REPLY_TO_LIST:
1347 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1348 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1349 debug_print("reply mode List\n");
1350 menu_set_active(ifactory, "/Options/Reply mode/Mailing-list", TRUE);
1358 static Compose *compose_reply(MsgInfo *msginfo,
1359 ComposeQuoteMode quote_mode,
1365 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1366 to_sender, FALSE, body);
1369 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1370 ComposeQuoteMode quote_mode,
1375 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1376 to_sender, TRUE, body);
1379 static void compose_extract_original_charset(Compose *compose)
1381 MsgInfo *info = NULL;
1382 if (compose->replyinfo) {
1383 info = compose->replyinfo;
1384 } else if (compose->fwdinfo) {
1385 info = compose->fwdinfo;
1386 } else if (compose->targetinfo) {
1387 info = compose->targetinfo;
1390 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1391 MimeInfo *partinfo = mimeinfo;
1392 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1393 partinfo = procmime_mimeinfo_next(partinfo);
1395 compose->orig_charset =
1396 g_strdup(procmime_mimeinfo_get_parameter(
1397 partinfo, "charset"));
1399 procmime_mimeinfo_free_all(mimeinfo);
1403 #define SIGNAL_BLOCK(buffer) { \
1404 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1405 G_CALLBACK(compose_changed_cb), \
1407 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1408 G_CALLBACK(text_inserted), \
1412 #define SIGNAL_UNBLOCK(buffer) { \
1413 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1414 G_CALLBACK(compose_changed_cb), \
1416 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1417 G_CALLBACK(text_inserted), \
1421 static Compose *compose_generic_reply(MsgInfo *msginfo,
1422 ComposeQuoteMode quote_mode,
1423 gboolean to_all, gboolean to_ml,
1425 gboolean followup_and_reply_to,
1428 GtkItemFactory *ifactory;
1430 PrefsAccount *account = NULL;
1431 GtkTextView *textview;
1432 GtkTextBuffer *textbuf;
1433 gboolean quote = FALSE;
1434 const gchar *qmark = NULL;
1435 const gchar *body_fmt = NULL;
1437 g_return_val_if_fail(msginfo != NULL, NULL);
1438 g_return_val_if_fail(msginfo->folder != NULL, NULL);
1440 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1442 g_return_val_if_fail(account != NULL, NULL);
1444 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1446 compose->updating = TRUE;
1448 ifactory = gtk_item_factory_from_widget(compose->menubar);
1450 menu_set_active(ifactory, "/Options/Remove references", FALSE);
1451 menu_set_sensitive(ifactory, "/Options/Remove references", TRUE);
1453 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1454 if (!compose->replyinfo)
1455 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1457 compose_extract_original_charset(compose);
1459 if (msginfo->folder && msginfo->folder->ret_rcpt)
1460 menu_set_active(ifactory, "/Options/Request Return Receipt", TRUE);
1462 /* Set save folder */
1463 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1464 gchar *folderidentifier;
1466 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1467 folderidentifier = folder_item_get_identifier(msginfo->folder);
1468 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
1469 g_free(folderidentifier);
1472 if (compose_parse_header(compose, msginfo) < 0) return NULL;
1474 textview = (GTK_TEXT_VIEW(compose->text));
1475 textbuf = gtk_text_view_get_buffer(textview);
1476 compose_create_tags(textview, compose);
1478 undo_block(compose->undostruct);
1480 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1483 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1484 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1485 /* use the reply format of folder (if enabled), or the account's one
1486 (if enabled) or fallback to the global reply format, which is always
1487 enabled (even if empty), and use the relevant quotemark */
1489 if (msginfo->folder && msginfo->folder->prefs &&
1490 msginfo->folder->prefs->reply_with_format) {
1491 qmark = msginfo->folder->prefs->reply_quotemark;
1492 body_fmt = msginfo->folder->prefs->reply_body_format;
1494 } else if (account->reply_with_format) {
1495 qmark = account->reply_quotemark;
1496 body_fmt = account->reply_body_format;
1499 qmark = prefs_common.quotemark;
1500 body_fmt = prefs_common.quotefmt;
1505 /* empty quotemark is not allowed */
1506 if (qmark == NULL || *qmark == '\0')
1508 compose_quote_fmt(compose, compose->replyinfo,
1509 body_fmt, qmark, body, FALSE, TRUE,
1510 _("Message reply format error at line %d."));
1511 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1512 quote_fmt_reset_vartable();
1515 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1516 compose_force_encryption(compose, account, FALSE);
1519 SIGNAL_BLOCK(textbuf);
1521 if (account->auto_sig)
1522 compose_insert_sig(compose, FALSE);
1524 compose_wrap_all(compose);
1526 SIGNAL_UNBLOCK(textbuf);
1528 gtk_widget_grab_focus(compose->text);
1530 undo_unblock(compose->undostruct);
1532 if (prefs_common.auto_exteditor)
1533 compose_exec_ext_editor(compose);
1535 compose->modified = FALSE;
1536 compose_set_title(compose);
1538 compose->updating = FALSE;
1539 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1540 SCROLL_TO_CURSOR(compose);
1542 if (compose->deferred_destroy) {
1543 compose_destroy(compose);
1550 #define INSERT_FW_HEADER(var, hdr) \
1551 if (msginfo->var && *msginfo->var) { \
1552 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1553 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1554 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1557 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1558 gboolean as_attach, const gchar *body,
1559 gboolean no_extedit,
1563 GtkTextView *textview;
1564 GtkTextBuffer *textbuf;
1567 g_return_val_if_fail(msginfo != NULL, NULL);
1568 g_return_val_if_fail(msginfo->folder != NULL, NULL);
1571 !(account = compose_guess_forward_account_from_msginfo
1573 account = cur_account;
1575 compose = compose_create(account, msginfo->folder, COMPOSE_FORWARD, batch);
1577 compose->updating = TRUE;
1578 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1579 if (!compose->fwdinfo)
1580 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1582 compose_extract_original_charset(compose);
1584 if (msginfo->subject && *msginfo->subject) {
1585 gchar *buf, *buf2, *p;
1587 buf = p = g_strdup(msginfo->subject);
1588 p += subject_get_prefix_length(p);
1589 memmove(buf, p, strlen(p) + 1);
1591 buf2 = g_strdup_printf("Fw: %s", buf);
1592 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1598 textview = GTK_TEXT_VIEW(compose->text);
1599 textbuf = gtk_text_view_get_buffer(textview);
1600 compose_create_tags(textview, compose);
1602 undo_block(compose->undostruct);
1606 msgfile = procmsg_get_message_file(msginfo);
1607 if (!is_file_exist(msgfile))
1608 g_warning("%s: file not exist\n", msgfile);
1610 compose_attach_append(compose, msgfile, msgfile,
1615 const gchar *qmark = NULL;
1616 const gchar *body_fmt = prefs_common.fw_quotefmt;
1617 MsgInfo *full_msginfo;
1619 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1621 full_msginfo = procmsg_msginfo_copy(msginfo);
1623 /* use the forward format of folder (if enabled), or the account's one
1624 (if enabled) or fallback to the global forward format, which is always
1625 enabled (even if empty), and use the relevant quotemark */
1626 if (msginfo->folder && msginfo->folder->prefs &&
1627 msginfo->folder->prefs->forward_with_format) {
1628 qmark = msginfo->folder->prefs->forward_quotemark;
1629 body_fmt = msginfo->folder->prefs->forward_body_format;
1631 } else if (account->forward_with_format) {
1632 qmark = account->forward_quotemark;
1633 body_fmt = account->forward_body_format;
1636 qmark = prefs_common.fw_quotemark;
1637 body_fmt = prefs_common.fw_quotefmt;
1640 /* empty quotemark is not allowed */
1641 if (qmark == NULL || *qmark == '\0')
1644 compose_quote_fmt(compose, full_msginfo,
1645 body_fmt, qmark, body, FALSE, TRUE,
1646 _("Message forward format error at line %d."));
1647 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1648 quote_fmt_reset_vartable();
1649 compose_attach_parts(compose, msginfo);
1651 procmsg_msginfo_free(full_msginfo);
1654 SIGNAL_BLOCK(textbuf);
1656 if (account->auto_sig)
1657 compose_insert_sig(compose, FALSE);
1659 compose_wrap_all(compose);
1661 SIGNAL_UNBLOCK(textbuf);
1663 gtk_text_buffer_get_start_iter(textbuf, &iter);
1664 gtk_text_buffer_place_cursor(textbuf, &iter);
1666 gtk_widget_grab_focus(compose->header_last->entry);
1668 if (!no_extedit && prefs_common.auto_exteditor)
1669 compose_exec_ext_editor(compose);
1672 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1673 gchar *folderidentifier;
1675 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1676 folderidentifier = folder_item_get_identifier(msginfo->folder);
1677 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
1678 g_free(folderidentifier);
1681 undo_unblock(compose->undostruct);
1683 compose->modified = FALSE;
1684 compose_set_title(compose);
1686 compose->updating = FALSE;
1687 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1688 SCROLL_TO_CURSOR(compose);
1690 if (compose->deferred_destroy) {
1691 compose_destroy(compose);
1698 #undef INSERT_FW_HEADER
1700 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1703 GtkTextView *textview;
1704 GtkTextBuffer *textbuf;
1708 gboolean single_mail = TRUE;
1710 g_return_val_if_fail(msginfo_list != NULL, NULL);
1712 if (g_slist_length(msginfo_list) > 1)
1713 single_mail = FALSE;
1715 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1716 if (((MsgInfo *)msginfo->data)->folder == NULL)
1719 /* guess account from first selected message */
1721 !(account = compose_guess_forward_account_from_msginfo
1722 (msginfo_list->data)))
1723 account = cur_account;
1725 g_return_val_if_fail(account != NULL, NULL);
1727 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1728 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1729 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1732 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1734 compose->updating = TRUE;
1736 textview = GTK_TEXT_VIEW(compose->text);
1737 textbuf = gtk_text_view_get_buffer(textview);
1738 compose_create_tags(textview, compose);
1740 undo_block(compose->undostruct);
1741 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1742 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
1744 if (!is_file_exist(msgfile))
1745 g_warning("%s: file not exist\n", msgfile);
1747 compose_attach_append(compose, msgfile, msgfile,
1753 MsgInfo *info = (MsgInfo *)msginfo_list->data;
1754 if (info->subject && *info->subject) {
1755 gchar *buf, *buf2, *p;
1757 buf = p = g_strdup(info->subject);
1758 p += subject_get_prefix_length(p);
1759 memmove(buf, p, strlen(p) + 1);
1761 buf2 = g_strdup_printf("Fw: %s", buf);
1762 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1768 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
1769 _("Fw: multiple emails"));
1772 SIGNAL_BLOCK(textbuf);
1774 if (account->auto_sig)
1775 compose_insert_sig(compose, FALSE);
1777 compose_wrap_all(compose);
1779 SIGNAL_UNBLOCK(textbuf);
1781 gtk_text_buffer_get_start_iter(textbuf, &iter);
1782 gtk_text_buffer_place_cursor(textbuf, &iter);
1784 gtk_widget_grab_focus(compose->header_last->entry);
1785 undo_unblock(compose->undostruct);
1786 compose->modified = FALSE;
1787 compose_set_title(compose);
1789 compose->updating = FALSE;
1790 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1791 SCROLL_TO_CURSOR(compose);
1793 if (compose->deferred_destroy) {
1794 compose_destroy(compose);
1801 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
1803 GtkTextIter start = *iter;
1804 GtkTextIter end_iter;
1805 int start_pos = gtk_text_iter_get_offset(&start);
1807 if (!compose->account->sig_sep)
1810 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
1811 start_pos+strlen(compose->account->sig_sep));
1813 /* check sig separator */
1814 str = gtk_text_iter_get_text(&start, &end_iter);
1815 if (!strcmp(str, compose->account->sig_sep)) {
1817 /* check end of line (\n) */
1818 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
1819 start_pos+strlen(compose->account->sig_sep));
1820 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
1821 start_pos+strlen(compose->account->sig_sep)+1);
1822 tmp = gtk_text_iter_get_text(&start, &end_iter);
1823 if (!strcmp(tmp,"\n")) {
1835 static void compose_colorize_signature(Compose *compose)
1837 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
1839 GtkTextIter end_iter;
1840 gtk_text_buffer_get_start_iter(buffer, &iter);
1841 while (gtk_text_iter_forward_line(&iter))
1842 if (compose_is_sig_separator(compose, buffer, &iter)) {
1843 gtk_text_buffer_get_end_iter(buffer, &end_iter);
1844 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
1848 #define BLOCK_WRAP() { \
1849 prev_autowrap = compose->autowrap; \
1850 buffer = gtk_text_view_get_buffer( \
1851 GTK_TEXT_VIEW(compose->text)); \
1852 compose->autowrap = FALSE; \
1854 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1855 G_CALLBACK(compose_changed_cb), \
1857 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1858 G_CALLBACK(text_inserted), \
1861 #define UNBLOCK_WRAP() { \
1862 compose->autowrap = prev_autowrap; \
1863 if (compose->autowrap) { \
1864 gint old = compose->draft_timeout_tag; \
1865 compose->draft_timeout_tag = -2; \
1866 compose_wrap_all(compose); \
1867 compose->draft_timeout_tag = old; \
1870 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1871 G_CALLBACK(compose_changed_cb), \
1873 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1874 G_CALLBACK(text_inserted), \
1878 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
1880 Compose *compose = NULL;
1881 PrefsAccount *account = NULL;
1882 GtkTextView *textview;
1883 GtkTextBuffer *textbuf;
1887 gchar buf[BUFFSIZE];
1888 gboolean use_signing = FALSE;
1889 gboolean use_encryption = FALSE;
1890 gchar *privacy_system = NULL;
1891 int priority = PRIORITY_NORMAL;
1892 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
1894 g_return_val_if_fail(msginfo != NULL, NULL);
1895 g_return_val_if_fail(msginfo->folder != NULL, NULL);
1897 if (compose_put_existing_to_front(msginfo)) {
1901 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
1902 folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
1903 gchar queueheader_buf[BUFFSIZE];
1906 /* Select Account from queue headers */
1907 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1908 sizeof(queueheader_buf), "X-Claws-Account-Id:")) {
1909 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
1910 account = account_find_from_id(id);
1912 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1913 sizeof(queueheader_buf), "X-Sylpheed-Account-Id:")) {
1914 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
1915 account = account_find_from_id(id);
1917 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1918 sizeof(queueheader_buf), "NAID:")) {
1919 id = atoi(&queueheader_buf[strlen("NAID:")]);
1920 account = account_find_from_id(id);
1922 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1923 sizeof(queueheader_buf), "MAID:")) {
1924 id = atoi(&queueheader_buf[strlen("MAID:")]);
1925 account = account_find_from_id(id);
1927 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1928 sizeof(queueheader_buf), "S:")) {
1929 account = account_find_from_address(queueheader_buf);
1931 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1932 sizeof(queueheader_buf), "X-Claws-Sign:")) {
1933 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
1934 use_signing = param;
1937 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1938 sizeof(queueheader_buf), "X-Sylpheed-Sign:")) {
1939 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
1940 use_signing = param;
1943 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1944 sizeof(queueheader_buf), "X-Claws-Encrypt:")) {
1945 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
1946 use_encryption = param;
1948 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1949 sizeof(queueheader_buf), "X-Sylpheed-Encrypt:")) {
1950 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
1951 use_encryption = param;
1953 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1954 sizeof(queueheader_buf), "X-Claws-Privacy-System:")) {
1955 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
1957 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1958 sizeof(queueheader_buf), "X-Sylpheed-Privacy-System:")) {
1959 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
1961 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1962 sizeof(queueheader_buf), "X-Priority: ")) {
1963 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
1966 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1967 sizeof(queueheader_buf), "RMID:")) {
1968 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
1969 if (tokens[0] && tokens[1] && tokens[2]) {
1970 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
1971 if (orig_item != NULL) {
1972 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
1977 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1978 sizeof(queueheader_buf), "FMID:")) {
1979 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
1980 if (tokens[0] && tokens[1] && tokens[2]) {
1981 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
1982 if (orig_item != NULL) {
1983 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
1989 account = msginfo->folder->folder->account;
1992 if (!account && prefs_common.reedit_account_autosel) {
1993 gchar from[BUFFSIZE];
1994 if (!procheader_get_header_from_msginfo(msginfo, from, sizeof(from), "FROM:")) {
1995 extract_address(from);
1996 account = account_find_from_address(from);
2000 account = cur_account;
2002 g_return_val_if_fail(account != NULL, NULL);
2004 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2006 compose->replyinfo = replyinfo;
2007 compose->fwdinfo = fwdinfo;
2009 compose->updating = TRUE;
2010 compose->priority = priority;
2012 if (privacy_system != NULL) {
2013 compose->privacy_system = privacy_system;
2014 compose_use_signing(compose, use_signing);
2015 compose_use_encryption(compose, use_encryption);
2016 compose_update_privacy_system_menu_item(compose, FALSE);
2018 activate_privacy_system(compose, account, FALSE);
2021 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2023 compose_extract_original_charset(compose);
2025 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2026 folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
2027 gchar queueheader_buf[BUFFSIZE];
2029 /* Set message save folder */
2030 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "SCF:")) {
2033 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2034 gtk_editable_delete_text(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
2035 gtk_editable_insert_text(GTK_EDITABLE(compose->savemsg_entry), &queueheader_buf[4], strlen(&queueheader_buf[4]), &startpos);
2037 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "RRCPT:")) {
2038 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2040 GtkItemFactory *ifactory;
2041 ifactory = gtk_item_factory_from_widget(compose->menubar);
2042 menu_set_active(ifactory, "/Options/Request Return Receipt", TRUE);
2047 if (compose_parse_header(compose, msginfo) < 0) {
2048 compose->updating = FALSE;
2049 compose_destroy(compose);
2052 compose_reedit_set_entry(compose, msginfo);
2054 textview = GTK_TEXT_VIEW(compose->text);
2055 textbuf = gtk_text_view_get_buffer(textview);
2056 compose_create_tags(textview, compose);
2058 mark = gtk_text_buffer_get_insert(textbuf);
2059 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2061 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2062 G_CALLBACK(compose_changed_cb),
2065 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2066 fp = procmime_get_first_encrypted_text_content(msginfo);
2068 compose_force_encryption(compose, account, TRUE);
2071 fp = procmime_get_first_text_content(msginfo);
2074 g_warning("Can't get text part\n");
2078 gboolean prev_autowrap = compose->autowrap;
2079 GtkTextBuffer *buffer = textbuf;
2081 while (fgets(buf, sizeof(buf), fp) != NULL) {
2083 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2089 compose_attach_parts(compose, msginfo);
2091 compose_colorize_signature(compose);
2093 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2094 G_CALLBACK(compose_changed_cb),
2097 gtk_widget_grab_focus(compose->text);
2099 if (prefs_common.auto_exteditor) {
2100 compose_exec_ext_editor(compose);
2102 compose->modified = FALSE;
2103 compose_set_title(compose);
2105 compose->updating = FALSE;
2106 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2107 SCROLL_TO_CURSOR(compose);
2109 if (compose->deferred_destroy) {
2110 compose_destroy(compose);
2114 compose->sig_str = compose_get_signature_str(compose);
2119 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2124 GtkItemFactory *ifactory;
2127 g_return_val_if_fail(msginfo != NULL, NULL);
2130 account = account_get_reply_account(msginfo,
2131 prefs_common.reply_account_autosel);
2132 g_return_val_if_fail(account != NULL, NULL);
2134 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2136 compose->updating = TRUE;
2138 ifactory = gtk_item_factory_from_widget(compose->menubar);
2139 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2140 compose->replyinfo = NULL;
2141 compose->fwdinfo = NULL;
2143 compose_show_first_last_header(compose, TRUE);
2145 gtk_widget_grab_focus(compose->header_last->entry);
2147 filename = procmsg_get_message_file(msginfo);
2149 if (filename == NULL) {
2150 compose->updating = FALSE;
2151 compose_destroy(compose);
2156 compose->redirect_filename = filename;
2158 /* Set save folder */
2159 item = msginfo->folder;
2160 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2161 gchar *folderidentifier;
2163 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
2164 folderidentifier = folder_item_get_identifier(item);
2165 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
2166 g_free(folderidentifier);
2169 compose_attach_parts(compose, msginfo);
2171 if (msginfo->subject)
2172 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2174 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2176 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2177 _("Message redirect format error at line %d."));
2178 quote_fmt_reset_vartable();
2179 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2181 compose_colorize_signature(compose);
2183 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
2184 menu_set_sensitive(ifactory, "/Add...", FALSE);
2185 menu_set_sensitive(ifactory, "/Remove", FALSE);
2186 menu_set_sensitive(ifactory, "/Properties...", FALSE);
2188 ifactory = gtk_item_factory_from_widget(compose->menubar);
2189 menu_set_sensitive(ifactory, "/Message/Save", FALSE);
2190 menu_set_sensitive(ifactory, "/Message/Insert file", FALSE);
2191 menu_set_sensitive(ifactory, "/Message/Attach file", FALSE);
2192 menu_set_sensitive(ifactory, "/Message/Insert signature", FALSE);
2193 menu_set_sensitive(ifactory, "/Edit", FALSE);
2194 menu_set_sensitive(ifactory, "/Options", FALSE);
2195 menu_set_sensitive(ifactory, "/Tools/Show ruler", FALSE);
2196 menu_set_sensitive(ifactory, "/Tools/Actions", FALSE);
2198 if (compose->toolbar->draft_btn)
2199 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2200 if (compose->toolbar->insert_btn)
2201 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2202 if (compose->toolbar->attach_btn)
2203 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2204 if (compose->toolbar->sig_btn)
2205 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2206 if (compose->toolbar->exteditor_btn)
2207 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2208 if (compose->toolbar->linewrap_current_btn)
2209 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2210 if (compose->toolbar->linewrap_all_btn)
2211 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2213 compose->modified = FALSE;
2214 compose_set_title(compose);
2215 compose->updating = FALSE;
2216 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2217 SCROLL_TO_CURSOR(compose);
2219 if (compose->deferred_destroy) {
2220 compose_destroy(compose);
2227 GList *compose_get_compose_list(void)
2229 return compose_list;
2232 void compose_entry_append(Compose *compose, const gchar *address,
2233 ComposeEntryType type)
2235 const gchar *header;
2237 gboolean in_quote = FALSE;
2238 if (!address || *address == '\0') return;
2245 header = N_("Bcc:");
2247 case COMPOSE_REPLYTO:
2248 header = N_("Reply-To:");
2250 case COMPOSE_NEWSGROUPS:
2251 header = N_("Newsgroups:");
2253 case COMPOSE_FOLLOWUPTO:
2254 header = N_( "Followup-To:");
2261 header = prefs_common_translated_header_name(header);
2263 cur = begin = (gchar *)address;
2265 /* we separate the line by commas, but not if we're inside a quoted
2267 while (*cur != '\0') {
2269 in_quote = !in_quote;
2270 if (*cur == ',' && !in_quote) {
2271 gchar *tmp = g_strdup(begin);
2273 tmp[cur-begin]='\0';
2276 while (*tmp == ' ' || *tmp == '\t')
2278 compose_add_header_entry(compose, header, tmp);
2285 gchar *tmp = g_strdup(begin);
2287 tmp[cur-begin]='\0';
2290 while (*tmp == ' ' || *tmp == '\t')
2292 compose_add_header_entry(compose, header, tmp);
2297 static void compose_entry_mark_default_to(Compose *compose, const gchar *mailto)
2299 static GdkColor yellow;
2300 static GdkColor black;
2301 static gboolean yellow_initialised = FALSE;
2305 if (!yellow_initialised) {
2306 gdk_color_parse("#f5f6be", &yellow);
2307 gdk_color_parse("#000000", &black);
2308 yellow_initialised = gdk_colormap_alloc_color(
2309 gdk_colormap_get_system(), &yellow, FALSE, TRUE);
2310 yellow_initialised &= gdk_colormap_alloc_color(
2311 gdk_colormap_get_system(), &black, FALSE, TRUE);
2314 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2315 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2316 if (gtk_entry_get_text(entry) &&
2317 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2318 if (yellow_initialised) {
2319 gtk_widget_modify_base(
2320 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2321 GTK_STATE_NORMAL, &yellow);
2322 gtk_widget_modify_text(
2323 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2324 GTK_STATE_NORMAL, &black);
2330 void compose_toolbar_cb(gint action, gpointer data)
2332 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2333 Compose *compose = (Compose*)toolbar_item->parent;
2335 g_return_if_fail(compose != NULL);
2339 compose_send_cb(compose, 0, NULL);
2342 compose_send_later_cb(compose, 0, NULL);
2345 compose_draft_cb(compose, COMPOSE_QUIT_EDITING, NULL);
2348 compose_insert_file_cb(compose, 0, NULL);
2351 compose_attach_cb(compose, 0, NULL);
2354 compose_insert_sig(compose, FALSE);
2357 compose_ext_editor_cb(compose, 0, NULL);
2359 case A_LINEWRAP_CURRENT:
2360 compose_beautify_paragraph(compose, NULL, TRUE);
2362 case A_LINEWRAP_ALL:
2363 compose_wrap_all_full(compose, TRUE);
2366 compose_address_cb(compose, 0, NULL);
2369 case A_CHECK_SPELLING:
2370 compose_check_all(compose);
2378 static void compose_entries_set(Compose *compose, const gchar *mailto)
2383 gchar *subject = NULL;
2387 gchar **attach = NULL;
2389 scan_mailto_url(mailto, &to, &cc, &bcc, &subject, &body, &attach);
2392 compose_entry_append(compose, to, COMPOSE_TO);
2394 compose_entry_append(compose, cc, COMPOSE_CC);
2396 compose_entry_append(compose, bcc, COMPOSE_BCC);
2398 if (!g_utf8_validate (subject, -1, NULL)) {
2399 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2400 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2403 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2407 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2408 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2411 gboolean prev_autowrap = compose->autowrap;
2413 compose->autowrap = FALSE;
2415 mark = gtk_text_buffer_get_insert(buffer);
2416 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2418 if (!g_utf8_validate (body, -1, NULL)) {
2419 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2420 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2423 gtk_text_buffer_insert(buffer, &iter, body, -1);
2425 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2427 compose->autowrap = prev_autowrap;
2428 if (compose->autowrap)
2429 compose_wrap_all(compose);
2434 while (attach[i] != NULL) {
2435 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2436 if (utf8_filename) {
2437 if (compose_attach_append(compose, attach[i], utf8_filename, NULL)) {
2438 alertpanel_notice(_("The file '%s' has been attached."), utf8_filename);
2440 g_free(utf8_filename);
2442 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2455 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2457 static HeaderEntry hentry[] = {{"Reply-To:", NULL, TRUE},
2458 {"Cc:", NULL, TRUE},
2459 {"References:", NULL, FALSE},
2460 {"Bcc:", NULL, TRUE},
2461 {"Newsgroups:", NULL, TRUE},
2462 {"Followup-To:", NULL, TRUE},
2463 {"List-Post:", NULL, FALSE},
2464 {"X-Priority:", NULL, FALSE},
2465 {NULL, NULL, FALSE}};
2481 g_return_val_if_fail(msginfo != NULL, -1);
2483 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2484 procheader_get_header_fields(fp, hentry);
2487 if (hentry[H_REPLY_TO].body != NULL) {
2488 if (hentry[H_REPLY_TO].body[0] != '\0') {
2490 conv_unmime_header(hentry[H_REPLY_TO].body,
2493 g_free(hentry[H_REPLY_TO].body);
2494 hentry[H_REPLY_TO].body = NULL;
2496 if (hentry[H_CC].body != NULL) {
2497 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL);
2498 g_free(hentry[H_CC].body);
2499 hentry[H_CC].body = NULL;
2501 if (hentry[H_REFERENCES].body != NULL) {
2502 if (compose->mode == COMPOSE_REEDIT)
2503 compose->references = hentry[H_REFERENCES].body;
2505 compose->references = compose_parse_references
2506 (hentry[H_REFERENCES].body, msginfo->msgid);
2507 g_free(hentry[H_REFERENCES].body);
2509 hentry[H_REFERENCES].body = NULL;
2511 if (hentry[H_BCC].body != NULL) {
2512 if (compose->mode == COMPOSE_REEDIT)
2514 conv_unmime_header(hentry[H_BCC].body, NULL);
2515 g_free(hentry[H_BCC].body);
2516 hentry[H_BCC].body = NULL;
2518 if (hentry[H_NEWSGROUPS].body != NULL) {
2519 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2520 hentry[H_NEWSGROUPS].body = NULL;
2522 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2523 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2524 compose->followup_to =
2525 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2528 g_free(hentry[H_FOLLOWUP_TO].body);
2529 hentry[H_FOLLOWUP_TO].body = NULL;
2531 if (hentry[H_LIST_POST].body != NULL) {
2534 extract_address(hentry[H_LIST_POST].body);
2535 if (hentry[H_LIST_POST].body[0] != '\0') {
2536 scan_mailto_url(hentry[H_LIST_POST].body,
2537 &to, NULL, NULL, NULL, NULL, NULL);
2539 g_free(compose->ml_post);
2540 compose->ml_post = to;
2543 g_free(hentry[H_LIST_POST].body);
2544 hentry[H_LIST_POST].body = NULL;
2547 /* CLAWS - X-Priority */
2548 if (compose->mode == COMPOSE_REEDIT)
2549 if (hentry[H_X_PRIORITY].body != NULL) {
2552 priority = atoi(hentry[H_X_PRIORITY].body);
2553 g_free(hentry[H_X_PRIORITY].body);
2555 hentry[H_X_PRIORITY].body = NULL;
2557 if (priority < PRIORITY_HIGHEST ||
2558 priority > PRIORITY_LOWEST)
2559 priority = PRIORITY_NORMAL;
2561 compose->priority = priority;
2564 if (compose->mode == COMPOSE_REEDIT) {
2565 if (msginfo->inreplyto && *msginfo->inreplyto)
2566 compose->inreplyto = g_strdup(msginfo->inreplyto);
2570 if (msginfo->msgid && *msginfo->msgid)
2571 compose->inreplyto = g_strdup(msginfo->msgid);
2573 if (!compose->references) {
2574 if (msginfo->msgid && *msginfo->msgid) {
2575 if (msginfo->inreplyto && *msginfo->inreplyto)
2576 compose->references =
2577 g_strdup_printf("<%s>\n\t<%s>",
2581 compose->references =
2582 g_strconcat("<", msginfo->msgid, ">",
2584 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
2585 compose->references =
2586 g_strconcat("<", msginfo->inreplyto, ">",
2594 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
2596 GSList *ref_id_list, *cur;
2600 ref_id_list = references_list_append(NULL, ref);
2601 if (!ref_id_list) return NULL;
2602 if (msgid && *msgid)
2603 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
2608 for (cur = ref_id_list; cur != NULL; cur = cur->next)
2609 /* "<" + Message-ID + ">" + CR+LF+TAB */
2610 len += strlen((gchar *)cur->data) + 5;
2612 if (len > MAX_REFERENCES_LEN) {
2613 /* remove second message-ID */
2614 if (ref_id_list && ref_id_list->next &&
2615 ref_id_list->next->next) {
2616 g_free(ref_id_list->next->data);
2617 ref_id_list = g_slist_remove
2618 (ref_id_list, ref_id_list->next->data);
2620 slist_free_strings(ref_id_list);
2621 g_slist_free(ref_id_list);
2628 new_ref = g_string_new("");
2629 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
2630 if (new_ref->len > 0)
2631 g_string_append(new_ref, "\n\t");
2632 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
2635 slist_free_strings(ref_id_list);
2636 g_slist_free(ref_id_list);
2638 new_ref_str = new_ref->str;
2639 g_string_free(new_ref, FALSE);
2644 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
2645 const gchar *fmt, const gchar *qmark,
2646 const gchar *body, gboolean rewrap,
2647 gboolean need_unescape,
2648 const gchar *err_msg)
2650 MsgInfo* dummyinfo = NULL;
2651 gchar *quote_str = NULL;
2653 gboolean prev_autowrap;
2654 const gchar *trimmed_body = body;
2655 gint cursor_pos = -1;
2656 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2657 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2662 SIGNAL_BLOCK(buffer);
2665 dummyinfo = compose_msginfo_new_from_compose(compose);
2666 msginfo = dummyinfo;
2669 if (qmark != NULL) {
2671 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
2672 compose->gtkaspell);
2674 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
2676 quote_fmt_scan_string(qmark);
2679 buf = quote_fmt_get_buffer();
2681 alertpanel_error(_("Quote mark format error."));
2683 Xstrdup_a(quote_str, buf, goto error)
2686 if (fmt && *fmt != '\0') {
2689 while (*trimmed_body == '\n')
2693 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account,
2694 compose->gtkaspell);
2696 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account);
2698 if (need_unescape) {
2701 /* decode \-escape sequences in the internal representation of the quote format */
2702 tmp = malloc(strlen(fmt)+1);
2703 pref_get_unescaped_pref(tmp, fmt);
2704 quote_fmt_scan_string(tmp);
2708 quote_fmt_scan_string(fmt);
2712 buf = quote_fmt_get_buffer();
2714 gint line = quote_fmt_get_line();
2715 alertpanel_error(err_msg, line);
2721 prev_autowrap = compose->autowrap;
2722 compose->autowrap = FALSE;
2724 mark = gtk_text_buffer_get_insert(buffer);
2725 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2726 if (g_utf8_validate(buf, -1, NULL)) {
2727 gtk_text_buffer_insert(buffer, &iter, buf, -1);
2729 gchar *tmpout = NULL;
2730 tmpout = conv_codeset_strdup
2731 (buf, conv_get_locale_charset_str_no_utf8(),
2733 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
2735 tmpout = g_malloc(strlen(buf)*2+1);
2736 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
2738 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
2742 cursor_pos = quote_fmt_get_cursor_pos();
2743 compose->set_cursor_pos = cursor_pos;
2744 if (cursor_pos == -1) {
2747 gtk_text_buffer_get_start_iter(buffer, &iter);
2748 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
2749 gtk_text_buffer_place_cursor(buffer, &iter);
2751 compose->autowrap = prev_autowrap;
2752 if (compose->autowrap && rewrap)
2753 compose_wrap_all(compose);
2760 SIGNAL_UNBLOCK(buffer);
2762 procmsg_msginfo_free( dummyinfo );
2767 /* if ml_post is of type addr@host and from is of type
2768 * addr-anything@host, return TRUE
2770 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
2772 gchar *left_ml = NULL;
2773 gchar *right_ml = NULL;
2774 gchar *left_from = NULL;
2775 gchar *right_from = NULL;
2776 gboolean result = FALSE;
2778 if (!ml_post || !from)
2781 left_ml = g_strdup(ml_post);
2782 if (strstr(left_ml, "@")) {
2783 right_ml = strstr(left_ml, "@")+1;
2784 *(strstr(left_ml, "@")) = '\0';
2787 left_from = g_strdup(from);
2788 if (strstr(left_from, "@")) {
2789 right_from = strstr(left_from, "@")+1;
2790 *(strstr(left_from, "@")) = '\0';
2793 if (left_ml && left_from && right_ml && right_from
2794 && !strncmp(left_from, left_ml, strlen(left_ml))
2795 && !strcmp(right_from, right_ml)) {
2804 static gboolean same_address(const gchar *addr1, const gchar *addr2)
2806 gchar *my_addr1, *my_addr2;
2808 if (!addr1 || !addr2)
2811 Xstrdup_a(my_addr1, addr1, return FALSE);
2812 Xstrdup_a(my_addr2, addr2, return FALSE);
2814 extract_address(my_addr1);
2815 extract_address(my_addr2);
2817 return !strcasecmp(my_addr1, my_addr2);
2820 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
2821 gboolean to_all, gboolean to_ml,
2823 gboolean followup_and_reply_to)
2825 GSList *cc_list = NULL;
2828 gchar *replyto = NULL;
2829 GHashTable *to_table;
2831 gboolean reply_to_ml = FALSE;
2832 gboolean default_reply_to = FALSE;
2834 g_return_if_fail(compose->account != NULL);
2835 g_return_if_fail(msginfo != NULL);
2837 reply_to_ml = to_ml && compose->ml_post;
2839 default_reply_to = msginfo->folder &&
2840 msginfo->folder->prefs->enable_default_reply_to;
2842 if (compose->account->protocol != A_NNTP) {
2843 if (reply_to_ml && !default_reply_to) {
2845 gboolean is_subscr = is_subscription(compose->ml_post,
2848 /* normal answer to ml post with a reply-to */
2849 compose_entry_append(compose,
2852 if (compose->replyto
2853 && !same_address(compose->ml_post, compose->replyto))
2854 compose_entry_append(compose,
2858 /* answer to subscription confirmation */
2859 if (compose->replyto)
2860 compose_entry_append(compose,
2863 else if (msginfo->from)
2864 compose_entry_append(compose,
2869 else if (!(to_all || to_sender) && default_reply_to) {
2870 compose_entry_append(compose,
2871 msginfo->folder->prefs->default_reply_to,
2873 compose_entry_mark_default_to(compose,
2874 msginfo->folder->prefs->default_reply_to);
2879 Xstrdup_a(tmp1, msginfo->from, return);
2880 extract_address(tmp1);
2881 if (to_all || to_sender ||
2882 !account_find_from_address(tmp1))
2883 compose_entry_append(compose,
2884 (compose->replyto && !to_sender)
2885 ? compose->replyto :
2886 msginfo->from ? msginfo->from : "",
2888 else if (!to_all && !to_sender) {
2889 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
2890 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
2891 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
2892 compose_entry_append(compose,
2893 msginfo->from ? msginfo->from : "",
2896 /* replying to own mail, use original recp */
2897 compose_entry_append(compose,
2898 msginfo->to ? msginfo->to : "",
2900 compose_entry_append(compose,
2901 msginfo->cc ? msginfo->cc : "",
2907 if (to_sender || (compose->followup_to &&
2908 !strncmp(compose->followup_to, "poster", 6)))
2909 compose_entry_append
2911 (compose->replyto ? compose->replyto :
2912 msginfo->from ? msginfo->from : ""),
2915 else if (followup_and_reply_to || to_all) {
2916 compose_entry_append
2918 (compose->replyto ? compose->replyto :
2919 msginfo->from ? msginfo->from : ""),
2922 compose_entry_append
2924 compose->followup_to ? compose->followup_to :
2925 compose->newsgroups ? compose->newsgroups : "",
2926 COMPOSE_NEWSGROUPS);
2929 compose_entry_append
2931 compose->followup_to ? compose->followup_to :
2932 compose->newsgroups ? compose->newsgroups : "",
2933 COMPOSE_NEWSGROUPS);
2936 if (msginfo->subject && *msginfo->subject) {
2940 buf = p = g_strdup(msginfo->subject);
2941 p += subject_get_prefix_length(p);
2942 memmove(buf, p, strlen(p) + 1);
2944 buf2 = g_strdup_printf("Re: %s", buf);
2945 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
2950 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
2952 if (to_ml && compose->ml_post) return;
2953 if (!to_all || compose->account->protocol == A_NNTP) return;
2955 if (compose->replyto) {
2956 Xstrdup_a(replyto, compose->replyto, return);
2957 extract_address(replyto);
2959 if (msginfo->from) {
2960 Xstrdup_a(from, msginfo->from, return);
2961 extract_address(from);
2964 if (replyto && from)
2965 cc_list = address_list_append_with_comments(cc_list, from);
2966 if (to_all && msginfo->folder &&
2967 msginfo->folder->prefs->enable_default_reply_to)
2968 cc_list = address_list_append_with_comments(cc_list,
2969 msginfo->folder->prefs->default_reply_to);
2970 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
2971 cc_list = address_list_append_with_comments(cc_list, compose->cc);
2973 to_table = g_hash_table_new(g_str_hash, g_str_equal);
2975 g_hash_table_insert(to_table, g_utf8_strdown(replyto, -1), GINT_TO_POINTER(1));
2976 if (compose->account) {
2977 g_hash_table_insert(to_table, g_utf8_strdown(compose->account->address, -1),
2978 GINT_TO_POINTER(1));
2980 /* remove address on To: and that of current account */
2981 for (cur = cc_list; cur != NULL; ) {
2982 GSList *next = cur->next;
2985 addr = g_utf8_strdown(cur->data, -1);
2986 extract_address(addr);
2988 if (GPOINTER_TO_INT(g_hash_table_lookup(to_table, addr)) == 1)
2989 cc_list = g_slist_remove(cc_list, cur->data);
2991 g_hash_table_insert(to_table, addr, GINT_TO_POINTER(1));
2995 hash_free_strings(to_table);
2996 g_hash_table_destroy(to_table);
2999 for (cur = cc_list; cur != NULL; cur = cur->next)
3000 compose_entry_append(compose, (gchar *)cur->data,
3002 slist_free_strings(cc_list);
3003 g_slist_free(cc_list);
3008 #define SET_ENTRY(entry, str) \
3011 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3014 #define SET_ADDRESS(type, str) \
3017 compose_entry_append(compose, str, type); \
3020 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3022 g_return_if_fail(msginfo != NULL);
3024 SET_ENTRY(subject_entry, msginfo->subject);
3025 SET_ENTRY(from_name, msginfo->from);
3026 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3027 SET_ADDRESS(COMPOSE_CC, compose->cc);
3028 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3029 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3030 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3031 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3033 compose_update_priority_menu_item(compose);
3034 compose_update_privacy_system_menu_item(compose, FALSE);
3035 compose_show_first_last_header(compose, TRUE);
3041 static void compose_insert_sig(Compose *compose, gboolean replace)
3043 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3044 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3046 GtkTextIter iter, iter_end;
3048 gboolean prev_autowrap;
3049 gboolean found = FALSE;
3050 gboolean exists = FALSE;
3052 g_return_if_fail(compose->account != NULL);
3056 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3057 G_CALLBACK(compose_changed_cb),
3060 mark = gtk_text_buffer_get_insert(buffer);
3061 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3062 cur_pos = gtk_text_iter_get_offset (&iter);
3064 gtk_text_buffer_get_end_iter(buffer, &iter);
3066 exists = (compose->sig_str != NULL);
3069 GtkTextIter first_iter, start_iter, end_iter;
3071 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3073 if (!exists || compose->sig_str[0] == '\0')
3076 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3077 compose->signature_tag);
3080 /* include previous \n\n */
3081 gtk_text_iter_backward_chars(&first_iter, 2);
3082 start_iter = first_iter;
3083 end_iter = first_iter;
3085 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3086 compose->signature_tag);
3087 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3088 compose->signature_tag);
3090 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3096 g_free(compose->sig_str);
3097 compose->sig_str = compose_get_signature_str(compose);
3099 cur_pos = gtk_text_iter_get_offset(&iter);
3101 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3102 g_free(compose->sig_str);
3103 compose->sig_str = NULL;
3105 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3107 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3108 gtk_text_iter_forward_chars(&iter, 2);
3109 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3110 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3112 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3113 cur_pos = gtk_text_buffer_get_char_count (buffer);
3115 /* put the cursor where it should be
3116 * either where the quote_fmt says, either before the signature */
3117 if (compose->set_cursor_pos < 0)
3118 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3120 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3121 compose->set_cursor_pos);
3123 gtk_text_buffer_place_cursor(buffer, &iter);
3124 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3125 G_CALLBACK(compose_changed_cb),
3131 static gchar *compose_get_signature_str(Compose *compose)
3133 gchar *sig_body = NULL;
3134 gchar *sig_str = NULL;
3135 gchar *utf8_sig_str = NULL;
3137 g_return_val_if_fail(compose->account != NULL, NULL);
3139 if (!compose->account->sig_path)
3142 if (compose->account->sig_type == SIG_FILE) {
3143 if (!is_file_or_fifo_exist(compose->account->sig_path)) {
3144 g_warning("can't open signature file: %s\n",
3145 compose->account->sig_path);
3150 if (compose->account->sig_type == SIG_COMMAND)
3151 sig_body = get_command_output(compose->account->sig_path);
3155 tmp = file_read_to_str(compose->account->sig_path);
3158 sig_body = normalize_newlines(tmp);
3162 if (compose->account->sig_sep) {
3163 sig_str = g_strconcat("\n\n", compose->account->sig_sep, "\n", sig_body,
3167 sig_str = g_strconcat("\n\n", sig_body, NULL);
3170 if (g_utf8_validate(sig_str, -1, NULL) == TRUE)
3171 utf8_sig_str = sig_str;
3173 utf8_sig_str = conv_codeset_strdup
3174 (sig_str, conv_get_locale_charset_str_no_utf8(),
3180 return utf8_sig_str;
3183 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3186 GtkTextBuffer *buffer;
3189 const gchar *cur_encoding;
3190 gchar buf[BUFFSIZE];
3193 gboolean prev_autowrap;
3194 gboolean badtxt = FALSE;
3196 g_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3198 if ((fp = g_fopen(file, "rb")) == NULL) {
3199 FILE_OP_ERROR(file, "fopen");
3200 return COMPOSE_INSERT_READ_ERROR;
3203 prev_autowrap = compose->autowrap;
3204 compose->autowrap = FALSE;
3206 text = GTK_TEXT_VIEW(compose->text);
3207 buffer = gtk_text_view_get_buffer(text);
3208 mark = gtk_text_buffer_get_insert(buffer);
3209 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3211 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3212 G_CALLBACK(text_inserted),
3215 cur_encoding = conv_get_locale_charset_str_no_utf8();
3217 while (fgets(buf, sizeof(buf), fp) != NULL) {
3220 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3221 str = g_strdup(buf);
3223 str = conv_codeset_strdup
3224 (buf, cur_encoding, CS_INTERNAL);
3227 /* strip <CR> if DOS/Windows file,
3228 replace <CR> with <LF> if Macintosh file. */
3231 if (len > 0 && str[len - 1] != '\n') {
3233 if (str[len] == '\r') str[len] = '\n';
3236 gtk_text_buffer_insert(buffer, &iter, str, -1);
3240 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3241 G_CALLBACK(text_inserted),
3243 compose->autowrap = prev_autowrap;
3244 if (compose->autowrap)
3245 compose_wrap_all(compose);
3250 return COMPOSE_INSERT_INVALID_CHARACTER;
3252 return COMPOSE_INSERT_SUCCESS;
3255 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3256 const gchar *filename,
3257 const gchar *content_type)
3265 GtkListStore *store;
3267 gboolean has_binary = FALSE;
3269 if (!is_file_exist(file)) {
3270 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3271 gboolean result = FALSE;
3272 if (file_from_uri && is_file_exist(file_from_uri)) {
3273 result = compose_attach_append(
3274 compose, file_from_uri,
3278 g_free(file_from_uri);
3281 alertpanel_error("File %s doesn't exist\n", filename);
3284 if ((size = get_file_size(file)) < 0) {
3285 alertpanel_error("Can't get file size of %s\n", filename);
3289 alertpanel_error(_("File %s is empty."), filename);
3292 if ((fp = g_fopen(file, "rb")) == NULL) {
3293 alertpanel_error(_("Can't read %s."), filename);
3298 ainfo = g_new0(AttachInfo, 1);
3299 auto_ainfo = g_auto_pointer_new_with_free
3300 (ainfo, (GFreeFunc) compose_attach_info_free);
3301 ainfo->file = g_strdup(file);
3304 ainfo->content_type = g_strdup(content_type);
3305 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3307 MsgFlags flags = {0, 0};
3309 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3310 ainfo->encoding = ENC_7BIT;
3312 ainfo->encoding = ENC_8BIT;
3314 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3315 if (msginfo && msginfo->subject)
3316 name = g_strdup(msginfo->subject);
3318 name = g_path_get_basename(filename ? filename : file);
3320 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3322 procmsg_msginfo_free(msginfo);
3324 if (!g_ascii_strncasecmp(content_type, "text", 4))
3325 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3327 ainfo->encoding = ENC_BASE64;
3328 name = g_path_get_basename(filename ? filename : file);
3329 ainfo->name = g_strdup(name);
3333 ainfo->content_type = procmime_get_mime_type(file);
3334 if (!ainfo->content_type) {
3335 ainfo->content_type =
3336 g_strdup("application/octet-stream");
3337 ainfo->encoding = ENC_BASE64;
3338 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text", 4))
3340 procmime_get_encoding_for_text_file(file, &has_binary);
3342 ainfo->encoding = ENC_BASE64;
3343 name = g_path_get_basename(filename ? filename : file);
3344 ainfo->name = g_strdup(name);
3348 if (ainfo->name != NULL
3349 && !strcmp(ainfo->name, ".")) {
3350 g_free(ainfo->name);
3354 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3355 g_free(ainfo->content_type);
3356 ainfo->content_type = g_strdup("application/octet-stream");
3360 size_text = to_human_readable(size);
3362 store = GTK_LIST_STORE(gtk_tree_view_get_model
3363 (GTK_TREE_VIEW(compose->attach_clist)));
3365 gtk_list_store_append(store, &iter);
3366 gtk_list_store_set(store, &iter,
3367 COL_MIMETYPE, ainfo->content_type,
3368 COL_SIZE, size_text,
3369 COL_NAME, ainfo->name,
3371 COL_AUTODATA, auto_ainfo,
3374 g_auto_pointer_free(auto_ainfo);
3375 compose_attach_update_label(compose);
3379 static void compose_use_signing(Compose *compose, gboolean use_signing)
3381 GtkItemFactory *ifactory;
3382 GtkWidget *menuitem = NULL;
3384 compose->use_signing = use_signing;
3385 ifactory = gtk_item_factory_from_widget(compose->menubar);
3386 menuitem = gtk_item_factory_get_item
3387 (ifactory, "/Options/Sign");
3388 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
3392 static void compose_use_encryption(Compose *compose, gboolean use_encryption)
3394 GtkItemFactory *ifactory;
3395 GtkWidget *menuitem = NULL;
3397 compose->use_encryption = use_encryption;
3398 ifactory = gtk_item_factory_from_widget(compose->menubar);
3399 menuitem = gtk_item_factory_get_item
3400 (ifactory, "/Options/Encrypt");
3402 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
3406 #define NEXT_PART_NOT_CHILD(info) \
3408 node = info->node; \
3409 while (node->children) \
3410 node = g_node_last_child(node); \
3411 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3414 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3418 MimeInfo *firsttext = NULL;
3419 MimeInfo *encrypted = NULL;
3422 const gchar *partname = NULL;
3424 mimeinfo = procmime_scan_message(msginfo);
3425 if (!mimeinfo) return;
3427 if (mimeinfo->node->children == NULL) {
3428 procmime_mimeinfo_free_all(mimeinfo);
3432 /* find first content part */
3433 child = (MimeInfo *) mimeinfo->node->children->data;
3434 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3435 child = (MimeInfo *)child->node->children->data;
3437 if (child->type == MIMETYPE_TEXT) {
3439 debug_print("First text part found\n");
3440 } else if (compose->mode == COMPOSE_REEDIT &&
3441 child->type == MIMETYPE_APPLICATION &&
3442 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3443 encrypted = (MimeInfo *)child->node->parent->data;
3446 child = (MimeInfo *) mimeinfo->node->children->data;
3447 while (child != NULL) {
3450 if (child == encrypted) {
3451 /* skip this part of tree */
3452 NEXT_PART_NOT_CHILD(child);
3456 if (child->type == MIMETYPE_MULTIPART) {
3457 /* get the actual content */
3458 child = procmime_mimeinfo_next(child);
3462 if (child == firsttext) {
3463 child = procmime_mimeinfo_next(child);
3467 outfile = procmime_get_tmp_file_name(child);
3468 if ((err = procmime_get_part(outfile, child)) < 0)
3469 g_warning("Can't get the part of multipart message. (%s)", strerror(-err));
3471 gchar *content_type;
3473 content_type = procmime_get_content_type_str(child->type, child->subtype);
3475 /* if we meet a pgp signature, we don't attach it, but
3476 * we force signing. */
3477 if ((strcmp(content_type, "application/pgp-signature") &&
3478 strcmp(content_type, "application/pkcs7-signature") &&
3479 strcmp(content_type, "application/x-pkcs7-signature"))
3480 || compose->mode == COMPOSE_REDIRECT) {
3481 partname = procmime_mimeinfo_get_parameter(child, "filename");
3482 if (partname == NULL)
3483 partname = procmime_mimeinfo_get_parameter(child, "name");
3484 if (partname == NULL)
3486 compose_attach_append(compose, outfile,
3487 partname, content_type);
3489 compose_force_signing(compose, compose->account);
3491 g_free(content_type);
3494 NEXT_PART_NOT_CHILD(child);
3496 procmime_mimeinfo_free_all(mimeinfo);
3499 #undef NEXT_PART_NOT_CHILD
3504 WAIT_FOR_INDENT_CHAR,
3505 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3508 /* return indent length, we allow:
3509 indent characters followed by indent characters or spaces/tabs,
3510 alphabets and numbers immediately followed by indent characters,
3511 and the repeating sequences of the above
3512 If quote ends with multiple spaces, only the first one is included. */
3513 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3514 const GtkTextIter *start, gint *len)
3516 GtkTextIter iter = *start;
3520 IndentState state = WAIT_FOR_INDENT_CHAR;
3523 gint alnum_count = 0;
3524 gint space_count = 0;
3527 if (prefs_common.quote_chars == NULL) {
3531 while (!gtk_text_iter_ends_line(&iter)) {
3532 wc = gtk_text_iter_get_char(&iter);
3533 if (g_unichar_iswide(wc))
3535 clen = g_unichar_to_utf8(wc, ch);
3539 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
3540 is_space = g_unichar_isspace(wc);
3542 if (state == WAIT_FOR_INDENT_CHAR) {
3543 if (!is_indent && !g_unichar_isalnum(wc))
3546 quote_len += alnum_count + space_count + 1;
3547 alnum_count = space_count = 0;
3548 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
3551 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
3552 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
3556 else if (is_indent) {
3557 quote_len += alnum_count + space_count + 1;
3558 alnum_count = space_count = 0;
3561 state = WAIT_FOR_INDENT_CHAR;
3565 gtk_text_iter_forward_char(&iter);
3568 if (quote_len > 0 && space_count > 0)
3574 if (quote_len > 0) {
3576 gtk_text_iter_forward_chars(&iter, quote_len);
3577 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
3583 /* return TRUE if the line is itemized */
3584 static gboolean compose_is_itemized(GtkTextBuffer *buffer,
3585 const GtkTextIter *start)
3587 GtkTextIter iter = *start;
3592 if (gtk_text_iter_ends_line(&iter))
3596 wc = gtk_text_iter_get_char(&iter);
3597 if (!g_unichar_isspace(wc))
3599 gtk_text_iter_forward_char(&iter);
3600 if (gtk_text_iter_ends_line(&iter))
3604 clen = g_unichar_to_utf8(wc, ch);
3608 if (!strchr("*-+", ch[0]))
3611 gtk_text_iter_forward_char(&iter);
3612 if (gtk_text_iter_ends_line(&iter))
3614 wc = gtk_text_iter_get_char(&iter);
3615 if (g_unichar_isspace(wc))
3621 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
3622 const GtkTextIter *start,
3623 GtkTextIter *break_pos,
3627 GtkTextIter iter = *start, line_end = *start;
3628 PangoLogAttr *attrs;
3635 gboolean can_break = FALSE;
3636 gboolean do_break = FALSE;
3637 gboolean was_white = FALSE;
3638 gboolean prev_dont_break = FALSE;
3640 gtk_text_iter_forward_to_line_end(&line_end);
3641 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
3642 len = g_utf8_strlen(str, -1);
3646 g_warning("compose_get_line_break_pos: len = 0!\n");
3650 /* g_print("breaking line: %d: %s (len = %d)\n",
3651 gtk_text_iter_get_line(&iter), str, len); */
3653 attrs = g_new(PangoLogAttr, len + 1);
3655 pango_default_break(str, -1, NULL, attrs, len + 1);
3659 /* skip quote and leading spaces */
3660 for (i = 0; *p != '\0' && i < len; i++) {
3663 wc = g_utf8_get_char(p);
3664 if (i >= quote_len && !g_unichar_isspace(wc))
3666 if (g_unichar_iswide(wc))
3668 else if (*p == '\t')
3672 p = g_utf8_next_char(p);
3675 for (; *p != '\0' && i < len; i++) {
3676 PangoLogAttr *attr = attrs + i;
3680 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
3683 was_white = attr->is_white;
3685 /* don't wrap URI */
3686 if ((uri_len = get_uri_len(p)) > 0) {
3688 if (pos > 0 && col > max_col) {
3698 wc = g_utf8_get_char(p);
3699 if (g_unichar_iswide(wc)) {
3701 if (prev_dont_break && can_break && attr->is_line_break)
3703 } else if (*p == '\t')
3707 if (pos > 0 && col > max_col) {
3712 if (*p == '-' || *p == '/')
3713 prev_dont_break = TRUE;
3715 prev_dont_break = FALSE;
3717 p = g_utf8_next_char(p);
3721 debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col);
3726 *break_pos = *start;
3727 gtk_text_iter_set_line_offset(break_pos, pos);
3732 static gboolean compose_join_next_line(Compose *compose,
3733 GtkTextBuffer *buffer,
3735 const gchar *quote_str)
3737 GtkTextIter iter_ = *iter, cur, prev, next, end;
3738 PangoLogAttr attrs[3];
3740 gchar *next_quote_str;
3743 gboolean keep_cursor = FALSE;
3745 if (!gtk_text_iter_forward_line(&iter_) ||
3746 gtk_text_iter_ends_line(&iter_))
3749 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
3751 if ((quote_str || next_quote_str) &&
3752 strcmp2(quote_str, next_quote_str) != 0) {
3753 g_free(next_quote_str);
3756 g_free(next_quote_str);
3759 if (quote_len > 0) {
3760 gtk_text_iter_forward_chars(&end, quote_len);
3761 if (gtk_text_iter_ends_line(&end))
3765 /* don't join itemized lines */
3766 if (compose_is_itemized(buffer, &end))
3769 /* don't join signature separator */
3770 if (compose_is_sig_separator(compose, buffer, &iter_))
3773 /* delete quote str */
3775 gtk_text_buffer_delete(buffer, &iter_, &end);
3777 /* don't join line breaks put by the user */
3779 gtk_text_iter_backward_char(&cur);
3780 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
3781 gtk_text_iter_forward_char(&cur);
3785 gtk_text_iter_forward_char(&cur);
3786 /* delete linebreak and extra spaces */
3787 while (gtk_text_iter_backward_char(&cur)) {
3788 wc1 = gtk_text_iter_get_char(&cur);
3789 if (!g_unichar_isspace(wc1))
3794 while (!gtk_text_iter_ends_line(&cur)) {
3795 wc1 = gtk_text_iter_get_char(&cur);
3796 if (!g_unichar_isspace(wc1))
3798 gtk_text_iter_forward_char(&cur);
3801 if (!gtk_text_iter_equal(&prev, &next)) {
3804 mark = gtk_text_buffer_get_insert(buffer);
3805 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
3806 if (gtk_text_iter_equal(&prev, &cur))
3808 gtk_text_buffer_delete(buffer, &prev, &next);
3812 /* insert space if required */
3813 gtk_text_iter_backward_char(&prev);
3814 wc1 = gtk_text_iter_get_char(&prev);
3815 wc2 = gtk_text_iter_get_char(&next);
3816 gtk_text_iter_forward_char(&next);
3817 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
3818 pango_default_break(str, -1, NULL, attrs, 3);
3819 if (!attrs[1].is_line_break ||
3820 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
3821 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
3823 gtk_text_iter_backward_char(&iter_);
3824 gtk_text_buffer_place_cursor(buffer, &iter_);
3833 #define ADD_TXT_POS(bp_, ep_, pti_) \
3834 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
3835 last = last->next; \
3836 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
3837 last->next = NULL; \
3839 g_warning("alloc error scanning URIs\n"); \
3842 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
3844 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3845 GtkTextBuffer *buffer;
3846 GtkTextIter iter, break_pos, end_of_line;
3847 gchar *quote_str = NULL;
3849 gboolean wrap_quote = prefs_common.linewrap_quote;
3850 gboolean prev_autowrap = compose->autowrap;
3851 gint startq_offset = -1, noq_offset = -1;
3852 gint uri_start = -1, uri_stop = -1;
3853 gint nouri_start = -1, nouri_stop = -1;
3854 gint num_blocks = 0;
3855 gint quotelevel = -1;
3856 gboolean modified = force;
3857 gboolean removed = FALSE;
3858 gboolean modified_before_remove = FALSE;
3860 gboolean start = TRUE;
3865 if (compose->draft_timeout_tag == -2) {
3869 compose->autowrap = FALSE;
3871 buffer = gtk_text_view_get_buffer(text);
3872 undo_wrapping(compose->undostruct, TRUE);
3877 mark = gtk_text_buffer_get_insert(buffer);
3878 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3882 if (compose->draft_timeout_tag == -2) {
3883 if (gtk_text_iter_ends_line(&iter)) {
3884 while (gtk_text_iter_ends_line(&iter) &&
3885 gtk_text_iter_forward_line(&iter))
3888 while (gtk_text_iter_backward_line(&iter)) {
3889 if (gtk_text_iter_ends_line(&iter)) {
3890 gtk_text_iter_forward_line(&iter);
3896 /* move to line start */
3897 gtk_text_iter_set_line_offset(&iter, 0);
3899 /* go until paragraph end (empty line) */
3900 while (start || !gtk_text_iter_ends_line(&iter)) {
3901 gchar *scanpos = NULL;
3902 /* parse table - in order of priority */
3904 const gchar *needle; /* token */
3906 /* token search function */
3907 gchar *(*search) (const gchar *haystack,
3908 const gchar *needle);
3909 /* part parsing function */
3910 gboolean (*parse) (const gchar *start,
3911 const gchar *scanpos,
3915 /* part to URI function */
3916 gchar *(*build_uri) (const gchar *bp,
3920 static struct table parser[] = {
3921 {"http://", strcasestr, get_uri_part, make_uri_string},
3922 {"https://", strcasestr, get_uri_part, make_uri_string},
3923 {"ftp://", strcasestr, get_uri_part, make_uri_string},
3924 {"sftp://", strcasestr, get_uri_part, make_uri_string},
3925 {"www.", strcasestr, get_uri_part, make_http_string},
3926 {"mailto:", strcasestr, get_uri_part, make_uri_string},
3927 {"@", strcasestr, get_email_part, make_email_string}
3929 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
3930 gint last_index = PARSE_ELEMS;
3932 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
3936 if (!prev_autowrap && num_blocks == 0) {
3938 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3939 G_CALLBACK(text_inserted),
3942 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
3945 uri_start = uri_stop = -1;
3947 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
3950 debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str);
3951 if (startq_offset == -1)
3952 startq_offset = gtk_text_iter_get_offset(&iter);
3953 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
3954 if (quotelevel > 2) {
3955 /* recycle colors */
3956 if (prefs_common.recycle_quote_colors)
3965 if (startq_offset == -1)
3966 noq_offset = gtk_text_iter_get_offset(&iter);
3970 if (prev_autowrap == FALSE && !force && !wrap_quote) {
3973 if (gtk_text_iter_ends_line(&iter)) {
3975 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
3976 prefs_common.linewrap_len,
3978 GtkTextIter prev, next, cur;
3980 if (prev_autowrap != FALSE || force) {
3981 compose->automatic_break = TRUE;
3983 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
3984 compose->automatic_break = FALSE;
3985 } else if (quote_str && wrap_quote) {
3986 compose->automatic_break = TRUE;
3988 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
3989 compose->automatic_break = FALSE;
3992 /* remove trailing spaces */
3994 gtk_text_iter_backward_char(&cur);
3996 while (!gtk_text_iter_starts_line(&cur)) {
3999 gtk_text_iter_backward_char(&cur);
4000 wc = gtk_text_iter_get_char(&cur);
4001 if (!g_unichar_isspace(wc))
4005 if (!gtk_text_iter_equal(&prev, &next)) {
4006 gtk_text_buffer_delete(buffer, &prev, &next);
4008 gtk_text_iter_forward_char(&break_pos);
4012 gtk_text_buffer_insert(buffer, &break_pos,
4016 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4018 /* move iter to current line start */
4019 gtk_text_iter_set_line_offset(&iter, 0);
4026 /* move iter to next line start */
4032 if (!prev_autowrap && num_blocks > 0) {
4034 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4035 G_CALLBACK(text_inserted),
4039 while (!gtk_text_iter_ends_line(&end_of_line)) {
4040 gtk_text_iter_forward_char(&end_of_line);
4042 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4044 nouri_start = gtk_text_iter_get_offset(&iter);
4045 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4047 walk_pos = gtk_text_iter_get_offset(&iter);
4048 /* FIXME: this looks phony. scanning for anything in the parse table */
4049 for (n = 0; n < PARSE_ELEMS; n++) {
4052 tmp = parser[n].search(walk, parser[n].needle);
4054 if (scanpos == NULL || tmp < scanpos) {
4063 /* check if URI can be parsed */
4064 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4065 (const gchar **)&ep, FALSE)
4066 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4070 strlen(parser[last_index].needle);
4073 uri_start = walk_pos + (bp - o_walk);
4074 uri_stop = walk_pos + (ep - o_walk);
4078 gtk_text_iter_forward_line(&iter);
4081 if (startq_offset != -1) {
4082 GtkTextIter startquote, endquote;
4083 gtk_text_buffer_get_iter_at_offset(
4084 buffer, &startquote, startq_offset);
4087 switch (quotelevel) {
4089 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4090 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4091 gtk_text_buffer_apply_tag_by_name(
4092 buffer, "quote0", &startquote, &endquote);
4093 gtk_text_buffer_remove_tag_by_name(
4094 buffer, "quote1", &startquote, &endquote);
4095 gtk_text_buffer_remove_tag_by_name(
4096 buffer, "quote2", &startquote, &endquote);
4101 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4102 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4103 gtk_text_buffer_apply_tag_by_name(
4104 buffer, "quote1", &startquote, &endquote);
4105 gtk_text_buffer_remove_tag_by_name(
4106 buffer, "quote0", &startquote, &endquote);
4107 gtk_text_buffer_remove_tag_by_name(
4108 buffer, "quote2", &startquote, &endquote);
4113 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4114 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4115 gtk_text_buffer_apply_tag_by_name(
4116 buffer, "quote2", &startquote, &endquote);
4117 gtk_text_buffer_remove_tag_by_name(
4118 buffer, "quote0", &startquote, &endquote);
4119 gtk_text_buffer_remove_tag_by_name(
4120 buffer, "quote1", &startquote, &endquote);
4126 } else if (noq_offset != -1) {
4127 GtkTextIter startnoquote, endnoquote;
4128 gtk_text_buffer_get_iter_at_offset(
4129 buffer, &startnoquote, noq_offset);
4132 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4133 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4134 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4135 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4136 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4137 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4138 gtk_text_buffer_remove_tag_by_name(
4139 buffer, "quote0", &startnoquote, &endnoquote);
4140 gtk_text_buffer_remove_tag_by_name(
4141 buffer, "quote1", &startnoquote, &endnoquote);
4142 gtk_text_buffer_remove_tag_by_name(
4143 buffer, "quote2", &startnoquote, &endnoquote);
4149 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4150 GtkTextIter nouri_start_iter, nouri_end_iter;
4151 gtk_text_buffer_get_iter_at_offset(
4152 buffer, &nouri_start_iter, nouri_start);
4153 gtk_text_buffer_get_iter_at_offset(
4154 buffer, &nouri_end_iter, nouri_stop);
4155 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4156 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4157 gtk_text_buffer_remove_tag_by_name(
4158 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4159 modified_before_remove = modified;
4164 if (uri_start > 0 && uri_stop > 0) {
4165 GtkTextIter uri_start_iter, uri_end_iter, back;
4166 gtk_text_buffer_get_iter_at_offset(
4167 buffer, &uri_start_iter, uri_start);
4168 gtk_text_buffer_get_iter_at_offset(
4169 buffer, &uri_end_iter, uri_stop);
4170 back = uri_end_iter;
4171 gtk_text_iter_backward_char(&back);
4172 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4173 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4174 gtk_text_buffer_apply_tag_by_name(
4175 buffer, "link", &uri_start_iter, &uri_end_iter);
4177 if (removed && !modified_before_remove) {
4183 debug_print("not modified, out after %d lines\n", lines);
4188 debug_print("modified, out after %d lines\n", lines);
4192 undo_wrapping(compose->undostruct, FALSE);
4193 compose->autowrap = prev_autowrap;
4198 void compose_action_cb(void *data)
4200 Compose *compose = (Compose *)data;
4201 compose_wrap_all(compose);
4204 static void compose_wrap_all(Compose *compose)
4206 compose_wrap_all_full(compose, FALSE);
4209 static void compose_wrap_all_full(Compose *compose, gboolean force)
4211 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4212 GtkTextBuffer *buffer;
4214 gboolean modified = TRUE;
4216 buffer = gtk_text_view_get_buffer(text);
4218 gtk_text_buffer_get_start_iter(buffer, &iter);
4219 while (!gtk_text_iter_is_end(&iter) && modified)
4220 modified = compose_beautify_paragraph(compose, &iter, force);
4224 static void compose_set_title(Compose *compose)
4230 edited = compose->modified ? _(" [Edited]") : "";
4232 subject = gtk_editable_get_chars(
4233 GTK_EDITABLE(compose->subject_entry), 0, -1);
4236 if (subject && strlen(subject))
4237 str = g_strdup_printf(_("%s - Compose message%s"),
4240 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4242 str = g_strdup(_("Compose message"));
4245 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4251 * compose_current_mail_account:
4253 * Find a current mail account (the currently selected account, or the
4254 * default account, if a news account is currently selected). If a
4255 * mail account cannot be found, display an error message.
4257 * Return value: Mail account, or NULL if not found.
4259 static PrefsAccount *
4260 compose_current_mail_account(void)
4264 if (cur_account && cur_account->protocol != A_NNTP)
4267 ac = account_get_default();
4268 if (!ac || ac->protocol == A_NNTP) {
4269 alertpanel_error(_("Account for sending mail is not specified.\n"
4270 "Please select a mail account before sending."));
4277 #define QUOTE_IF_REQUIRED(out, str) \
4279 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4283 len = strlen(str) + 3; \
4284 if ((__tmp = alloca(len)) == NULL) { \
4285 g_warning("can't allocate memory\n"); \
4286 g_string_free(header, TRUE); \
4289 g_snprintf(__tmp, len, "\"%s\"", str); \
4294 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4295 g_warning("can't allocate memory\n"); \
4296 g_string_free(header, TRUE); \
4299 strcpy(__tmp, str); \
4305 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4307 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4311 len = strlen(str) + 3; \
4312 if ((__tmp = alloca(len)) == NULL) { \
4313 g_warning("can't allocate memory\n"); \
4316 g_snprintf(__tmp, len, "\"%s\"", str); \
4321 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4322 g_warning("can't allocate memory\n"); \
4325 strcpy(__tmp, str); \
4331 static void compose_select_account(Compose *compose, PrefsAccount *account,
4334 GtkItemFactory *ifactory;
4337 g_return_if_fail(account != NULL);
4339 compose->account = account;
4341 if (account->name && *account->name) {
4343 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4344 from = g_strdup_printf("%s <%s>",
4345 buf, account->address);
4346 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4348 from = g_strdup_printf("<%s>",
4350 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4355 compose_set_title(compose);
4357 ifactory = gtk_item_factory_from_widget(compose->menubar);
4359 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4360 menu_set_active(ifactory, "/Options/Sign", TRUE);
4362 menu_set_active(ifactory, "/Options/Sign", FALSE);
4363 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4364 menu_set_active(ifactory, "/Options/Encrypt", TRUE);
4366 menu_set_active(ifactory, "/Options/Encrypt", FALSE);
4368 activate_privacy_system(compose, account, FALSE);
4370 if (!init && compose->mode != COMPOSE_REDIRECT) {
4371 undo_block(compose->undostruct);
4372 compose_insert_sig(compose, TRUE);
4373 undo_unblock(compose->undostruct);
4377 /* use account's dict info if set */
4378 if (compose->gtkaspell) {
4379 if (account->enable_default_dictionary)
4380 gtkaspell_change_dict(compose->gtkaspell,
4381 account->default_dictionary, FALSE);
4382 if (account->enable_default_alt_dictionary)
4383 gtkaspell_change_alt_dict(compose->gtkaspell,
4384 account->default_alt_dictionary);
4385 if (account->enable_default_dictionary
4386 || account->enable_default_alt_dictionary)
4387 compose_spell_menu_changed(compose);
4392 gboolean compose_check_for_valid_recipient(Compose *compose) {
4393 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
4394 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
4395 gboolean recipient_found = FALSE;
4399 /* free to and newsgroup list */
4400 slist_free_strings(compose->to_list);
4401 g_slist_free(compose->to_list);
4402 compose->to_list = NULL;
4404 slist_free_strings(compose->newsgroup_list);
4405 g_slist_free(compose->newsgroup_list);
4406 compose->newsgroup_list = NULL;
4408 /* search header entries for to and newsgroup entries */
4409 for (list = compose->header_list; list; list = list->next) {
4412 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_BIN(((ComposeHeaderEntry *)list->data)->combo)->child), 0, -1);
4413 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4416 if (entry[0] != '\0') {
4417 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
4418 if (!strcmp(header, prefs_common_translated_header_name(*strptr))) {
4419 compose->to_list = address_list_append(compose->to_list, entry);
4420 recipient_found = TRUE;
4423 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
4424 if (!strcmp(header, prefs_common_translated_header_name(*strptr))) {
4425 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
4426 recipient_found = TRUE;
4433 return recipient_found;
4436 static gboolean compose_check_for_set_recipients(Compose *compose)
4438 if (compose->account->set_autocc && compose->account->auto_cc) {
4439 gboolean found_other = FALSE;
4441 /* search header entries for to and newsgroup entries */
4442 for (list = compose->header_list; list; list = list->next) {
4445 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4446 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_BIN(((ComposeHeaderEntry *)list->data)->combo)->child), 0, -1);
4448 if (strcmp(entry, compose->account->auto_cc)
4449 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
4459 if (compose->batch) {
4460 gtk_widget_show_all(compose->window);
4462 aval = alertpanel(_("Send"),
4463 _("The only recipient is the default CC address. Send anyway?"),
4464 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4465 if (aval != G_ALERTALTERNATE)
4469 if (compose->account->set_autobcc && compose->account->auto_bcc) {
4470 gboolean found_other = FALSE;
4472 /* search header entries for to and newsgroup entries */
4473 for (list = compose->header_list; list; list = list->next) {
4476 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4477 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_BIN(((ComposeHeaderEntry *)list->data)->combo)->child), 0, -1);
4479 if (strcmp(entry, compose->account->auto_bcc)
4480 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
4490 if (compose->batch) {
4491 gtk_widget_show_all(compose->window);
4493 aval = alertpanel(_("Send"),
4494 _("The only recipient is the default BCC address. Send anyway?"),
4495 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4496 if (aval != G_ALERTALTERNATE)
4503 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
4507 if (compose_check_for_valid_recipient(compose) == FALSE) {
4508 if (compose->batch) {
4509 gtk_widget_show_all(compose->window);
4511 alertpanel_error(_("Recipient is not specified."));
4515 if (compose_check_for_set_recipients(compose) == FALSE) {
4519 if (!compose->batch) {
4520 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
4521 if (*str == '\0' && check_everything == TRUE &&
4522 compose->mode != COMPOSE_REDIRECT) {
4524 gchar *button_label;
4527 if (compose->sending)
4528 button_label = _("+_Send");
4530 button_label = _("+_Queue");
4531 message = g_strdup_printf(_("Subject is empty. %s it anyway?"),
4532 compose->sending?_("Send"):_("Queue"));
4534 aval = alertpanel(compose->sending?_("Send"):_("Send later"), message,
4535 GTK_STOCK_CANCEL, button_label, NULL);
4537 if (aval != G_ALERTALTERNATE)
4542 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
4548 gint compose_send(Compose *compose)
4551 FolderItem *folder = NULL;
4553 gchar *msgpath = NULL;
4554 gboolean discard_window = FALSE;
4555 gchar *errstr = NULL;
4556 gchar *tmsgid = NULL;
4557 MainWindow *mainwin = mainwindow_get_mainwindow();
4558 gboolean queued_removed = FALSE;
4560 if (prefs_common.send_dialog_invisible
4561 || compose->batch == TRUE)
4562 discard_window = TRUE;
4564 compose_allow_user_actions (compose, FALSE);
4565 compose->sending = TRUE;
4567 if (compose_check_entries(compose, TRUE) == FALSE) {
4568 if (compose->batch) {
4569 gtk_widget_show_all(compose->window);
4575 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
4578 if (compose->batch) {
4579 gtk_widget_show_all(compose->window);
4582 alertpanel_error(_("Could not queue message for sending:\n\n"
4583 "Charset conversion failed."));
4584 } else if (val == -5) {
4585 alertpanel_error(_("Could not queue message for sending:\n\n"
4586 "Couldn't get recipient encryption key."));
4587 } else if (val == -6) {
4589 } else if (val == -3) {
4590 if (privacy_peek_error())
4591 alertpanel_error(_("Could not queue message for sending:\n\n"
4592 "Signature failed: %s"), privacy_get_error());
4593 } else if (val == -2 && errno != 0) {
4594 alertpanel_error(_("Could not queue message for sending:\n\n%s."), strerror(errno));
4596 alertpanel_error(_("Could not queue message for sending."));
4601 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
4602 if (discard_window) {
4603 compose->sending = FALSE;
4604 compose_close(compose);
4605 /* No more compose access in the normal codepath
4606 * after this point! */
4611 alertpanel_error(_("The message was queued but could not be "
4612 "sent.\nUse \"Send queued messages\" from "
4613 "the main window to retry."));
4614 if (!discard_window) {
4621 if (msgpath == NULL) {
4622 msgpath = folder_item_fetch_msg(folder, msgnum);
4623 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
4626 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
4630 if (!discard_window) {
4632 if (!queued_removed)
4633 folder_item_remove_msg(folder, msgnum);
4634 folder_item_scan(folder);
4636 /* make sure we delete that */
4637 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
4639 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
4640 folder_item_remove_msg(folder, tmp->msgnum);
4641 procmsg_msginfo_free(tmp);
4648 if (!queued_removed)
4649 folder_item_remove_msg(folder, msgnum);
4650 folder_item_scan(folder);
4652 /* make sure we delete that */
4653 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
4655 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
4656 folder_item_remove_msg(folder, tmp->msgnum);
4657 procmsg_msginfo_free(tmp);
4660 if (!discard_window) {
4661 compose->sending = FALSE;
4662 compose_allow_user_actions (compose, TRUE);
4663 compose_close(compose);
4667 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
4668 "the main window to retry."), errstr);
4671 alertpanel_error_log(_("The message was queued but could not be "
4672 "sent.\nUse \"Send queued messages\" from "
4673 "the main window to retry."));
4675 if (!discard_window) {
4684 toolbar_main_set_sensitive(mainwin);
4685 main_window_set_menu_sensitive(mainwin);
4691 compose_allow_user_actions (compose, TRUE);
4692 compose->sending = FALSE;
4693 compose->modified = TRUE;
4694 toolbar_main_set_sensitive(mainwin);
4695 main_window_set_menu_sensitive(mainwin);
4700 static gboolean compose_use_attach(Compose *compose)
4702 GtkTreeModel *model = gtk_tree_view_get_model
4703 (GTK_TREE_VIEW(compose->attach_clist));
4704 return gtk_tree_model_iter_n_children(model, NULL) > 0;
4707 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
4710 gchar buf[BUFFSIZE];
4712 gboolean first_to_address;
4713 gboolean first_cc_address;
4715 ComposeHeaderEntry *headerentry;
4716 const gchar *headerentryname;
4717 const gchar *cc_hdr;
4718 const gchar *to_hdr;
4719 gboolean err = FALSE;
4721 debug_print("Writing redirect header\n");
4723 cc_hdr = prefs_common_translated_header_name("Cc:");
4724 to_hdr = prefs_common_translated_header_name("To:");
4726 first_to_address = TRUE;
4727 for (list = compose->header_list; list; list = list->next) {
4728 headerentry = ((ComposeHeaderEntry *)list->data);
4729 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_BIN(headerentry->combo)->child));
4731 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
4732 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
4733 Xstrdup_a(str, entstr, return -1);
4735 if (str[0] != '\0') {
4736 compose_convert_header
4737 (compose, buf, sizeof(buf), str,
4738 strlen("Resent-To") + 2, TRUE);
4740 if (first_to_address) {
4741 err |= (fprintf(fp, "Resent-To: ") < 0);
4742 first_to_address = FALSE;
4744 err |= (fprintf(fp, ",") < 0);
4746 err |= (fprintf(fp, "%s", buf) < 0);
4750 if (!first_to_address) {
4751 err |= (fprintf(fp, "\n") < 0);
4754 first_cc_address = TRUE;
4755 for (list = compose->header_list; list; list = list->next) {
4756 headerentry = ((ComposeHeaderEntry *)list->data);
4757 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_BIN(headerentry->combo)->child));
4759 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
4760 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
4761 Xstrdup_a(str, strg, return -1);
4763 if (str[0] != '\0') {
4764 compose_convert_header
4765 (compose, buf, sizeof(buf), str,
4766 strlen("Resent-Cc") + 2, TRUE);
4768 if (first_cc_address) {
4769 err |= (fprintf(fp, "Resent-Cc: ") < 0);
4770 first_cc_address = FALSE;
4772 err |= (fprintf(fp, ",") < 0);
4774 err |= (fprintf(fp, "%s", buf) < 0);
4778 if (!first_cc_address) {
4779 err |= (fprintf(fp, "\n") < 0);
4782 return (err ? -1:0);
4785 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
4787 gchar buf[BUFFSIZE];
4789 const gchar *entstr;
4790 /* struct utsname utsbuf; */
4791 gboolean err = FALSE;
4793 g_return_val_if_fail(fp != NULL, -1);
4794 g_return_val_if_fail(compose->account != NULL, -1);
4795 g_return_val_if_fail(compose->account->address != NULL, -1);
4798 get_rfc822_date(buf, sizeof(buf));
4799 err |= (fprintf(fp, "Resent-Date: %s\n", buf) < 0);
4802 if (compose->account->name && *compose->account->name) {
4803 compose_convert_header
4804 (compose, buf, sizeof(buf), compose->account->name,
4805 strlen("From: "), TRUE);
4806 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
4807 buf, compose->account->address) < 0);
4809 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
4812 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
4813 if (*entstr != '\0') {
4814 Xstrdup_a(str, entstr, return -1);
4817 compose_convert_header(compose, buf, sizeof(buf), str,
4818 strlen("Subject: "), FALSE);
4819 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
4823 /* Resent-Message-ID */
4824 if (compose->account->set_domain && compose->account->domain) {
4825 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
4826 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
4827 g_snprintf(buf, sizeof(buf), "%s",
4828 strchr(compose->account->address, '@') ?
4829 strchr(compose->account->address, '@')+1 :
4830 compose->account->address);
4832 g_snprintf(buf, sizeof(buf), "%s", "");
4835 if (compose->account->gen_msgid) {
4836 generate_msgid(buf, sizeof(buf));
4837 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", buf) < 0);
4838 compose->msgid = g_strdup(buf);
4840 compose->msgid = NULL;
4843 if (compose_redirect_write_headers_from_headerlist(compose, fp))
4846 /* separator between header and body */
4847 err |= (fputs("\n", fp) == EOF);
4849 return (err ? -1:0);
4852 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
4856 gchar buf[BUFFSIZE];
4858 gboolean skip = FALSE;
4859 gboolean err = FALSE;
4860 gchar *not_included[]={
4861 "Return-Path:", "Delivered-To:", "Received:",
4862 "Subject:", "X-UIDL:", "AF:",
4863 "NF:", "PS:", "SRH:",
4864 "SFN:", "DSR:", "MID:",
4865 "CFG:", "PT:", "S:",
4866 "RQ:", "SSV:", "NSV:",
4867 "SSH:", "R:", "MAID:",
4868 "NAID:", "RMID:", "FMID:",
4869 "SCF:", "RRCPT:", "NG:",
4870 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
4871 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
4872 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
4873 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
4876 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
4877 FILE_OP_ERROR(compose->redirect_filename, "fopen");
4881 while (procheader_get_one_field_asis(buf, sizeof(buf), fp) != -1) {
4883 for (i = 0; not_included[i] != NULL; i++) {
4884 if (g_ascii_strncasecmp(buf, not_included[i],
4885 strlen(not_included[i])) == 0) {
4892 if (fputs(buf, fdest) == -1)
4895 if (!prefs_common.redirect_keep_from) {
4896 if (g_ascii_strncasecmp(buf, "From:",
4897 strlen("From:")) == 0) {
4898 err |= (fputs(" (by way of ", fdest) == EOF);
4899 if (compose->account->name
4900 && *compose->account->name) {
4901 compose_convert_header
4902 (compose, buf, sizeof(buf),
4903 compose->account->name,
4906 err |= (fprintf(fdest, "%s <%s>",
4908 compose->account->address) < 0);
4910 err |= (fprintf(fdest, "%s",
4911 compose->account->address) < 0);
4912 err |= (fputs(")", fdest) == EOF);
4916 if (fputs("\n", fdest) == -1)
4923 if (compose_redirect_write_headers(compose, fdest))
4926 while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
4927 if (fwrite(buf, sizeof(gchar), len, fdest) != len)
4940 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
4942 GtkTextBuffer *buffer;
4943 GtkTextIter start, end;
4946 const gchar *out_codeset;
4947 EncodingType encoding;
4948 MimeInfo *mimemsg, *mimetext;
4951 if (action == COMPOSE_WRITE_FOR_SEND)
4952 attach_parts = TRUE;
4954 /* create message MimeInfo */
4955 mimemsg = procmime_mimeinfo_new();
4956 mimemsg->type = MIMETYPE_MESSAGE;
4957 mimemsg->subtype = g_strdup("rfc822");
4958 mimemsg->content = MIMECONTENT_MEM;
4959 mimemsg->tmp = TRUE; /* must free content later */
4960 mimemsg->data.mem = compose_get_header(compose);
4962 /* Create text part MimeInfo */
4963 /* get all composed text */
4964 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
4965 gtk_text_buffer_get_start_iter(buffer, &start);
4966 gtk_text_buffer_get_end_iter(buffer, &end);
4967 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
4968 if (is_ascii_str(chars)) {
4971 out_codeset = CS_US_ASCII;
4972 encoding = ENC_7BIT;
4974 const gchar *src_codeset = CS_INTERNAL;
4976 out_codeset = conv_get_charset_str(compose->out_encoding);
4979 gchar *test_conv_global_out = NULL;
4980 gchar *test_conv_reply = NULL;
4982 /* automatic mode. be automatic. */
4983 codeconv_set_strict(TRUE);
4985 out_codeset = conv_get_outgoing_charset_str();
4987 debug_print("trying to convert to %s\n", out_codeset);
4988 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
4991 if (!test_conv_global_out && compose->orig_charset
4992 && strcmp(compose->orig_charset, CS_US_ASCII)) {
4993 out_codeset = compose->orig_charset;
4994 debug_print("failure; trying to convert to %s\n", out_codeset);
4995 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
4998 if (!test_conv_global_out && !test_conv_reply) {
5000 out_codeset = CS_INTERNAL;
5001 debug_print("failure; finally using %s\n", out_codeset);
5003 g_free(test_conv_global_out);
5004 g_free(test_conv_reply);
5005 codeconv_set_strict(FALSE);
5008 if (!g_ascii_strcasecmp(out_codeset, CS_US_ASCII))
5009 out_codeset = CS_ISO_8859_1;
5011 if (prefs_common.encoding_method == CTE_BASE64)
5012 encoding = ENC_BASE64;
5013 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5014 encoding = ENC_QUOTED_PRINTABLE;
5015 else if (prefs_common.encoding_method == CTE_8BIT)
5016 encoding = ENC_8BIT;
5018 encoding = procmime_get_encoding_for_charset(out_codeset);
5020 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5021 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5023 if (action == COMPOSE_WRITE_FOR_SEND) {
5024 codeconv_set_strict(TRUE);
5025 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5026 codeconv_set_strict(FALSE);
5032 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5033 "to the specified %s charset.\n"
5034 "Send it as %s?"), out_codeset, src_codeset);
5035 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL, _("+_Send"), NULL, FALSE,
5036 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5039 if (aval != G_ALERTALTERNATE) {
5044 out_codeset = src_codeset;
5050 out_codeset = src_codeset;
5056 if (encoding == ENC_8BIT || encoding == ENC_7BIT) {
5057 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5058 strstr(buf, "\nFrom ") != NULL) {
5059 encoding = ENC_QUOTED_PRINTABLE;
5063 mimetext = procmime_mimeinfo_new();
5064 mimetext->content = MIMECONTENT_MEM;
5065 mimetext->tmp = TRUE; /* must free content later */
5066 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5067 * and free the data, which we need later. */
5068 mimetext->data.mem = g_strdup(buf);
5069 mimetext->type = MIMETYPE_TEXT;
5070 mimetext->subtype = g_strdup("plain");
5071 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5072 g_strdup(out_codeset));
5074 /* protect trailing spaces when signing message */
5075 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5076 privacy_system_can_sign(compose->privacy_system)) {
5077 encoding = ENC_QUOTED_PRINTABLE;
5080 debug_print("main text: %zd bytes encoded as %s in %d\n",
5081 strlen(buf), out_codeset, encoding);
5083 /* check for line length limit */
5084 if (action == COMPOSE_WRITE_FOR_SEND &&
5085 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5086 check_line_length(buf, 1000, &line) < 0) {
5090 msg = g_strdup_printf
5091 (_("Line %d exceeds the line length limit (998 bytes).\n"
5092 "The contents of the message might be broken on the way to the delivery.\n"
5094 "Send it anyway?"), line + 1);
5095 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5097 if (aval != G_ALERTALTERNATE) {
5103 if (encoding != ENC_UNKNOWN)
5104 procmime_encode_content(mimetext, encoding);
5106 /* append attachment parts */
5107 if (compose_use_attach(compose) && attach_parts) {
5108 MimeInfo *mimempart;
5109 gchar *boundary = NULL;
5110 mimempart = procmime_mimeinfo_new();
5111 mimempart->content = MIMECONTENT_EMPTY;
5112 mimempart->type = MIMETYPE_MULTIPART;
5113 mimempart->subtype = g_strdup("mixed");
5117 boundary = generate_mime_boundary(NULL);
5118 } while (strstr(buf, boundary) != NULL);
5120 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5123 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5125 g_node_append(mimempart->node, mimetext->node);
5126 g_node_append(mimemsg->node, mimempart->node);
5128 compose_add_attachments(compose, mimempart);
5130 g_node_append(mimemsg->node, mimetext->node);
5134 /* sign message if sending */
5135 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5136 privacy_system_can_sign(compose->privacy_system))
5137 if (!privacy_sign(compose->privacy_system, mimemsg, compose->account))
5140 procmime_write_mimeinfo(mimemsg, fp);
5142 procmime_mimeinfo_free_all(mimemsg);
5147 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5149 GtkTextBuffer *buffer;
5150 GtkTextIter start, end;
5155 if ((fp = g_fopen(file, "wb")) == NULL) {
5156 FILE_OP_ERROR(file, "fopen");
5160 /* chmod for security */
5161 if (change_file_mode_rw(fp, file) < 0) {
5162 FILE_OP_ERROR(file, "chmod");
5163 g_warning("can't change file mode\n");
5166 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5167 gtk_text_buffer_get_start_iter(buffer, &start);
5168 gtk_text_buffer_get_end_iter(buffer, &end);
5169 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5171 chars = conv_codeset_strdup
5172 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5175 if (!chars) return -1;
5178 len = strlen(chars);
5179 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5180 FILE_OP_ERROR(file, "fwrite");
5189 if (fclose(fp) == EOF) {
5190 FILE_OP_ERROR(file, "fclose");
5197 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5200 MsgInfo *msginfo = compose->targetinfo;
5202 g_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5203 if (!msginfo) return -1;
5205 if (!force && MSG_IS_LOCKED(msginfo->flags))
5208 item = msginfo->folder;
5209 g_return_val_if_fail(item != NULL, -1);
5211 if (procmsg_msg_exist(msginfo) &&
5212 (folder_has_parent_of_type(item, F_QUEUE) ||
5213 folder_has_parent_of_type(item, F_DRAFT)
5214 || msginfo == compose->autosaved_draft)) {
5215 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5216 g_warning("can't remove the old message\n");
5219 debug_print("removed reedit target %d\n", msginfo->msgnum);
5226 static void compose_remove_draft(Compose *compose)
5229 MsgInfo *msginfo = compose->targetinfo;
5230 drafts = account_get_special_folder(compose->account, F_DRAFT);
5232 if (procmsg_msg_exist(msginfo)) {
5233 folder_item_remove_msg(drafts, msginfo->msgnum);
5238 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5239 gboolean remove_reedit_target)
5241 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5244 static gboolean compose_warn_encryption(Compose *compose)
5246 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5247 AlertValue val = G_ALERTALTERNATE;
5249 if (warning == NULL)
5252 val = alertpanel_full(_("Encryption warning"), warning,
5253 GTK_STOCK_CANCEL, _("+C_ontinue"), NULL,
5254 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5255 if (val & G_ALERTDISABLE) {
5256 val &= ~G_ALERTDISABLE;
5257 if (val == G_ALERTALTERNATE)
5258 privacy_inhibit_encrypt_warning(compose->privacy_system,
5262 if (val == G_ALERTALTERNATE) {
5269 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
5270 gchar **msgpath, gboolean check_subject,
5271 gboolean remove_reedit_target)
5278 static gboolean lock = FALSE;
5279 PrefsAccount *mailac = NULL, *newsac = NULL;
5280 gboolean err = FALSE;
5282 debug_print("queueing message...\n");
5283 g_return_val_if_fail(compose->account != NULL, -1);
5287 if (compose_check_entries(compose, check_subject) == FALSE) {
5289 if (compose->batch) {
5290 gtk_widget_show_all(compose->window);
5295 if (!compose->to_list && !compose->newsgroup_list) {
5296 g_warning("can't get recipient list.");
5301 if (compose->to_list) {
5302 if (compose->account->protocol != A_NNTP)
5303 mailac = compose->account;
5304 else if (cur_account && cur_account->protocol != A_NNTP)
5305 mailac = cur_account;
5306 else if (!(mailac = compose_current_mail_account())) {
5308 alertpanel_error(_("No account for sending mails available!"));
5313 if (compose->newsgroup_list) {
5314 if (compose->account->protocol == A_NNTP)
5315 newsac = compose->account;
5316 else if (!newsac->protocol != A_NNTP) {
5318 alertpanel_error(_("No account for posting news available!"));
5323 /* write queue header */
5324 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
5325 G_DIR_SEPARATOR, compose, (guint) rand());
5326 debug_print("queuing to %s\n", tmp);
5327 if ((fp = g_fopen(tmp, "wb")) == NULL) {
5328 FILE_OP_ERROR(tmp, "fopen");
5334 if (change_file_mode_rw(fp, tmp) < 0) {
5335 FILE_OP_ERROR(tmp, "chmod");
5336 g_warning("can't change file mode\n");
5339 /* queueing variables */
5340 err |= (fprintf(fp, "AF:\n") < 0);
5341 err |= (fprintf(fp, "NF:0\n") < 0);
5342 err |= (fprintf(fp, "PS:10\n") < 0);
5343 err |= (fprintf(fp, "SRH:1\n") < 0);
5344 err |= (fprintf(fp, "SFN:\n") < 0);
5345 err |= (fprintf(fp, "DSR:\n") < 0);
5347 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
5349 err |= (fprintf(fp, "MID:\n") < 0);
5350 err |= (fprintf(fp, "CFG:\n") < 0);
5351 err |= (fprintf(fp, "PT:0\n") < 0);
5352 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
5353 err |= (fprintf(fp, "RQ:\n") < 0);
5355 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
5357 err |= (fprintf(fp, "SSV:\n") < 0);
5359 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
5361 err |= (fprintf(fp, "NSV:\n") < 0);
5362 err |= (fprintf(fp, "SSH:\n") < 0);
5363 /* write recepient list */
5364 if (compose->to_list) {
5365 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
5366 for (cur = compose->to_list->next; cur != NULL;
5368 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
5369 err |= (fprintf(fp, "\n") < 0);
5371 /* write newsgroup list */
5372 if (compose->newsgroup_list) {
5373 err |= (fprintf(fp, "NG:") < 0);
5374 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
5375 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
5376 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
5377 err |= (fprintf(fp, "\n") < 0);
5379 /* Sylpheed account IDs */
5381 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
5383 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
5386 if (compose->privacy_system != NULL) {
5387 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
5388 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
5389 if (compose->use_encryption) {
5391 if (!compose_warn_encryption(compose)) {
5398 if (mailac && mailac->encrypt_to_self) {
5399 GSList *tmp_list = g_slist_copy(compose->to_list);
5400 tmp_list = g_slist_append(tmp_list, compose->account->address);
5401 encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
5402 g_slist_free(tmp_list);
5404 encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
5406 if (encdata != NULL) {
5407 if (strcmp(encdata, "_DONT_ENCRYPT_")) {
5408 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5409 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
5411 } /* else we finally dont want to encrypt */
5413 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5414 /* and if encdata was null, it means there's been a problem in
5426 /* Save copy folder */
5427 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
5428 gchar *savefolderid;
5430 savefolderid = gtk_editable_get_chars(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
5431 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
5432 g_free(savefolderid);
5434 /* Save copy folder */
5435 if (compose->return_receipt) {
5436 err |= (fprintf(fp, "RRCPT:1\n") < 0);
5438 /* Message-ID of message replying to */
5439 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
5442 folderid = folder_item_get_identifier(compose->replyinfo->folder);
5443 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
5446 /* Message-ID of message forwarding to */
5447 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
5450 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
5451 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
5455 /* end of headers */
5456 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
5458 if (compose->redirect_filename != NULL) {
5459 if (compose_redirect_write_to_file(compose, fp) < 0) {
5468 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
5473 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
5477 g_warning("failed to write queue message\n");
5484 if (fclose(fp) == EOF) {
5485 FILE_OP_ERROR(tmp, "fclose");
5492 if (item && *item) {
5495 queue = account_get_special_folder(compose->account, F_QUEUE);
5498 g_warning("can't find queue folder\n");
5504 folder_item_scan(queue);
5505 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
5506 g_warning("can't queue the message\n");
5513 if (msgpath == NULL) {
5519 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
5520 compose_remove_reedit_target(compose, FALSE);
5523 if ((msgnum != NULL) && (item != NULL)) {
5531 static void compose_add_attachments(Compose *compose, MimeInfo *parent)
5534 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
5536 struct stat statbuf;
5537 gchar *type, *subtype;
5538 GtkTreeModel *model;
5541 model = gtk_tree_view_get_model(tree_view);
5543 if (!gtk_tree_model_get_iter_first(model, &iter))
5546 gtk_tree_model_get(model, &iter,
5550 mimepart = procmime_mimeinfo_new();
5551 mimepart->content = MIMECONTENT_FILE;
5552 mimepart->data.filename = g_strdup(ainfo->file);
5553 mimepart->tmp = FALSE; /* or we destroy our attachment */
5554 mimepart->offset = 0;
5556 stat(ainfo->file, &statbuf);
5557 mimepart->length = statbuf.st_size;
5559 type = g_strdup(ainfo->content_type);
5561 if (!strchr(type, '/')) {
5563 type = g_strdup("application/octet-stream");
5566 subtype = strchr(type, '/') + 1;
5567 *(subtype - 1) = '\0';
5568 mimepart->type = procmime_get_media_type(type);
5569 mimepart->subtype = g_strdup(subtype);
5572 if (mimepart->type == MIMETYPE_MESSAGE &&
5573 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
5574 mimepart->disposition = DISPOSITIONTYPE_INLINE;
5577 g_hash_table_insert(mimepart->typeparameters,
5578 g_strdup("name"), g_strdup(ainfo->name));
5579 g_hash_table_insert(mimepart->dispositionparameters,
5580 g_strdup("filename"), g_strdup(ainfo->name));
5581 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
5585 if (compose->use_signing) {
5586 if (ainfo->encoding == ENC_7BIT)
5587 ainfo->encoding = ENC_QUOTED_PRINTABLE;
5588 else if (ainfo->encoding == ENC_8BIT)
5589 ainfo->encoding = ENC_BASE64;
5592 procmime_encode_content(mimepart, ainfo->encoding);
5594 g_node_append(parent->node, mimepart->node);
5595 } while (gtk_tree_model_iter_next(model, &iter));
5598 #define IS_IN_CUSTOM_HEADER(header) \
5599 (compose->account->add_customhdr && \
5600 custom_header_find(compose->account->customhdr_list, header) != NULL)
5602 static void compose_add_headerfield_from_headerlist(Compose *compose,
5604 const gchar *fieldname,
5605 const gchar *seperator)
5607 gchar *str, *fieldname_w_colon;
5608 gboolean add_field = FALSE;
5610 ComposeHeaderEntry *headerentry;
5611 const gchar *headerentryname;
5612 const gchar *trans_fieldname;
5615 if (IS_IN_CUSTOM_HEADER(fieldname))
5618 debug_print("Adding %s-fields\n", fieldname);
5620 fieldstr = g_string_sized_new(64);
5622 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
5623 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
5625 for (list = compose->header_list; list; list = list->next) {
5626 headerentry = ((ComposeHeaderEntry *)list->data);
5627 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_BIN(headerentry->combo)->child));
5629 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
5630 str = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
5632 if (str[0] != '\0') {
5634 g_string_append(fieldstr, seperator);
5635 g_string_append(fieldstr, str);
5644 buf = g_new0(gchar, fieldstr->len * 4 + 256);
5645 compose_convert_header
5646 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
5647 strlen(fieldname) + 2, TRUE);
5648 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
5652 g_free(fieldname_w_colon);
5653 g_string_free(fieldstr, TRUE);
5658 static gchar *compose_get_header(Compose *compose)
5660 gchar buf[BUFFSIZE];
5661 const gchar *entry_str;
5665 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
5667 gchar *from_name = NULL, *from_address = NULL;
5670 g_return_val_if_fail(compose->account != NULL, NULL);
5671 g_return_val_if_fail(compose->account->address != NULL, NULL);
5673 header = g_string_sized_new(64);
5676 get_rfc822_date(buf, sizeof(buf));
5677 g_string_append_printf(header, "Date: %s\n", buf);
5681 if (compose->account->name && *compose->account->name) {
5683 QUOTE_IF_REQUIRED(buf, compose->account->name);
5684 tmp = g_strdup_printf("%s <%s>",
5685 buf, compose->account->address);
5687 tmp = g_strdup_printf("%s",
5688 compose->account->address);
5690 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
5691 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
5693 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
5694 from_address = g_strdup(compose->account->address);
5696 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5697 /* extract name and address */
5698 if (strstr(spec, " <") && strstr(spec, ">")) {
5699 from_address = g_strdup(strrchr(spec, '<')+1);
5700 *(strrchr(from_address, '>')) = '\0';
5701 from_name = g_strdup(spec);
5702 *(strrchr(from_name, '<')) = '\0';
5705 from_address = g_strdup(spec);
5712 if (from_name && *from_name) {
5713 compose_convert_header
5714 (compose, buf, sizeof(buf), from_name,
5715 strlen("From: "), TRUE);
5716 QUOTE_IF_REQUIRED(name, buf);
5718 g_string_append_printf(header, "From: %s <%s>\n",
5719 name, from_address);
5721 g_string_append_printf(header, "From: %s\n", from_address);
5724 g_free(from_address);
5727 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
5730 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
5733 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
5736 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
5739 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
5741 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
5744 compose_convert_header(compose, buf, sizeof(buf), str,
5745 strlen("Subject: "), FALSE);
5746 g_string_append_printf(header, "Subject: %s\n", buf);
5752 if (compose->account->set_domain && compose->account->domain) {
5753 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
5754 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
5755 g_snprintf(buf, sizeof(buf), "%s",
5756 strchr(compose->account->address, '@') ?
5757 strchr(compose->account->address, '@')+1 :
5758 compose->account->address);
5760 g_snprintf(buf, sizeof(buf), "%s", "");
5763 if (compose->account->gen_msgid) {
5764 generate_msgid(buf, sizeof(buf));
5765 g_string_append_printf(header, "Message-ID: <%s>\n", buf);
5766 compose->msgid = g_strdup(buf);
5768 compose->msgid = NULL;
5771 if (compose->remove_references == FALSE) {
5773 if (compose->inreplyto && compose->to_list)
5774 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
5777 if (compose->references)
5778 g_string_append_printf(header, "References: %s\n", compose->references);
5782 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
5785 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
5788 if (compose->account->organization &&
5789 strlen(compose->account->organization) &&
5790 !IS_IN_CUSTOM_HEADER("Organization")) {
5791 compose_convert_header(compose, buf, sizeof(buf),
5792 compose->account->organization,
5793 strlen("Organization: "), FALSE);
5794 g_string_append_printf(header, "Organization: %s\n", buf);
5797 /* Program version and system info */
5798 if (g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
5799 !compose->newsgroup_list) {
5800 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
5802 gtk_major_version, gtk_minor_version, gtk_micro_version,
5805 if (g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
5806 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
5808 gtk_major_version, gtk_minor_version, gtk_micro_version,
5812 /* custom headers */
5813 if (compose->account->add_customhdr) {
5816 for (cur = compose->account->customhdr_list; cur != NULL;
5818 CustomHeader *chdr = (CustomHeader *)cur->data;
5820 if (custom_header_is_allowed(chdr->name)) {
5821 compose_convert_header
5822 (compose, buf, sizeof(buf),
5823 chdr->value ? chdr->value : "",
5824 strlen(chdr->name) + 2, FALSE);
5825 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
5831 switch (compose->priority) {
5832 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
5833 "X-Priority: 1 (Highest)\n");
5835 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
5836 "X-Priority: 2 (High)\n");
5838 case PRIORITY_NORMAL: break;
5839 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
5840 "X-Priority: 4 (Low)\n");
5842 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
5843 "X-Priority: 5 (Lowest)\n");
5845 default: debug_print("compose: priority unknown : %d\n",
5849 /* Request Return Receipt */
5850 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To")) {
5851 if (compose->return_receipt) {
5852 if (compose->account->name
5853 && *compose->account->name) {
5854 compose_convert_header(compose, buf, sizeof(buf),
5855 compose->account->name,
5856 strlen("Disposition-Notification-To: "),
5858 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, compose->account->address);
5860 g_string_append_printf(header, "Disposition-Notification-To: %s\n", compose->account->address);
5864 /* get special headers */
5865 for (list = compose->header_list; list; list = list->next) {
5866 ComposeHeaderEntry *headerentry;
5869 gchar *headername_wcolon;
5870 const gchar *headername_trans;
5873 gboolean standard_header = FALSE;
5875 headerentry = ((ComposeHeaderEntry *)list->data);
5877 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(GTK_BIN(headerentry->combo)->child)));
5878 if (strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
5883 if (!strstr(tmp, ":")) {
5884 headername_wcolon = g_strconcat(tmp, ":", NULL);
5885 headername = g_strdup(tmp);
5887 headername_wcolon = g_strdup(tmp);
5888 headername = g_strdup(strtok(tmp, ":"));
5892 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5893 Xstrdup_a(headervalue, entry_str, return NULL);
5894 subst_char(headervalue, '\r', ' ');
5895 subst_char(headervalue, '\n', ' ');
5896 string = std_headers;
5897 while (*string != NULL) {
5898 headername_trans = prefs_common_translated_header_name(*string);
5899 if (!strcmp(headername_trans, headername_wcolon))
5900 standard_header = TRUE;
5903 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
5904 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
5907 g_free(headername_wcolon);
5911 g_string_free(header, FALSE);
5916 #undef IS_IN_CUSTOM_HEADER
5918 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
5919 gint header_len, gboolean addr_field)
5921 gchar *tmpstr = NULL;
5922 const gchar *out_codeset = NULL;
5924 g_return_if_fail(src != NULL);
5925 g_return_if_fail(dest != NULL);
5927 if (len < 1) return;
5929 tmpstr = g_strdup(src);
5931 subst_char(tmpstr, '\n', ' ');
5932 subst_char(tmpstr, '\r', ' ');
5935 if (!g_utf8_validate(tmpstr, -1, NULL)) {
5936 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
5937 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
5942 codeconv_set_strict(TRUE);
5943 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
5944 conv_get_charset_str(compose->out_encoding));
5945 codeconv_set_strict(FALSE);
5947 if (!dest || *dest == '\0') {
5948 gchar *test_conv_global_out = NULL;
5949 gchar *test_conv_reply = NULL;
5951 /* automatic mode. be automatic. */
5952 codeconv_set_strict(TRUE);
5954 out_codeset = conv_get_outgoing_charset_str();
5956 debug_print("trying to convert to %s\n", out_codeset);
5957 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
5960 if (!test_conv_global_out && compose->orig_charset
5961 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5962 out_codeset = compose->orig_charset;
5963 debug_print("failure; trying to convert to %s\n", out_codeset);
5964 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
5967 if (!test_conv_global_out && !test_conv_reply) {
5969 out_codeset = CS_INTERNAL;
5970 debug_print("finally using %s\n", out_codeset);
5972 g_free(test_conv_global_out);
5973 g_free(test_conv_reply);
5974 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
5976 codeconv_set_strict(FALSE);
5981 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
5985 g_return_if_fail(user_data != NULL);
5987 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
5988 g_strstrip(address);
5989 if (*address != '\0') {
5990 gchar *name = procheader_get_fromname(address);
5991 extract_address(address);
5992 addressbook_add_contact(name, address, NULL, NULL);
5997 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
5999 GtkWidget *menuitem;
6002 g_return_if_fail(menu != NULL);
6003 g_return_if_fail(GTK_IS_MENU_SHELL(menu));
6005 menuitem = gtk_separator_menu_item_new();
6006 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6007 gtk_widget_show(menuitem);
6009 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
6010 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6012 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
6013 g_strstrip(address);
6014 if (*address == '\0') {
6015 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
6018 g_signal_connect(G_OBJECT(menuitem), "activate",
6019 G_CALLBACK(compose_add_to_addressbook_cb), entry);
6020 gtk_widget_show(menuitem);
6023 static void compose_create_header_entry(Compose *compose)
6025 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6030 const gchar *header = NULL;
6031 ComposeHeaderEntry *headerentry;
6032 gboolean standard_header = FALSE;
6034 headerentry = g_new0(ComposeHeaderEntry, 1);
6037 combo = gtk_combo_box_entry_new_text();
6039 while(*string != NULL) {
6040 gtk_combo_box_append_text(GTK_COMBO_BOX(combo),
6041 (gchar*)prefs_common_translated_header_name(*string));
6044 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
6045 g_signal_connect(G_OBJECT(GTK_BIN(combo)->child), "grab_focus",
6046 G_CALLBACK(compose_grab_focus_cb), compose);
6047 gtk_widget_show(combo);
6048 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
6049 compose->header_nextrow, compose->header_nextrow+1,
6050 GTK_SHRINK, GTK_FILL, 0, 0);
6051 if (compose->header_last) {
6052 const gchar *last_header_entry = gtk_entry_get_text(
6053 GTK_ENTRY(GTK_BIN(compose->header_last->combo)->child));
6055 while (*string != NULL) {
6056 if (!strcmp(*string, last_header_entry))
6057 standard_header = TRUE;
6060 if (standard_header)
6061 header = gtk_entry_get_text(GTK_ENTRY(GTK_BIN(compose->header_last->combo)->child));
6063 if (!compose->header_last || !standard_header) {
6064 switch(compose->account->protocol) {
6066 header = prefs_common_translated_header_name("Newsgroups:");
6069 header = prefs_common_translated_header_name("To:");
6074 gtk_entry_set_text(GTK_ENTRY(GTK_BIN(combo)->child), header);
6076 g_signal_connect_after(G_OBJECT(GTK_BIN(combo)->child), "grab_focus",
6077 G_CALLBACK(compose_grab_focus_cb), compose);
6080 entry = gtk_entry_new();
6081 gtk_widget_show(entry);
6082 gtk_tooltips_set_tip(compose->tooltips, entry,
6083 _("Use <tab> to autocomplete from addressbook"), NULL);
6084 gtk_table_attach(GTK_TABLE(compose->header_table), entry, 1, 2,
6085 compose->header_nextrow, compose->header_nextrow+1,
6086 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
6088 g_signal_connect(G_OBJECT(entry), "key-press-event",
6089 G_CALLBACK(compose_headerentry_key_press_event_cb),
6091 g_signal_connect(G_OBJECT(entry), "changed",
6092 G_CALLBACK(compose_headerentry_changed_cb),
6094 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
6095 G_CALLBACK(compose_grab_focus_cb), compose);
6098 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6099 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6100 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6101 g_signal_connect(G_OBJECT(entry), "drag_data_received",
6102 G_CALLBACK(compose_header_drag_received_cb),
6104 g_signal_connect(G_OBJECT(entry), "drag-drop",
6105 G_CALLBACK(compose_drag_drop),
6107 g_signal_connect(G_OBJECT(entry), "populate-popup",
6108 G_CALLBACK(compose_entry_popup_extend),
6111 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
6113 headerentry->compose = compose;
6114 headerentry->combo = combo;
6115 headerentry->entry = entry;
6116 headerentry->headernum = compose->header_nextrow;
6118 compose->header_nextrow++;
6119 compose->header_last = headerentry;
6120 compose->header_list =
6121 g_slist_append(compose->header_list,
6125 static void compose_add_header_entry(Compose *compose, const gchar *header, gchar *text)
6127 ComposeHeaderEntry *last_header;
6129 last_header = compose->header_last;
6131 gtk_entry_set_text(GTK_ENTRY(GTK_BIN(last_header->combo)->child), header);
6132 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
6135 static void compose_remove_header_entries(Compose *compose)
6138 for (list = compose->header_list; list; list = list->next) {
6139 ComposeHeaderEntry *headerentry =
6140 (ComposeHeaderEntry *)list->data;
6141 gtk_widget_destroy(headerentry->combo);
6142 gtk_widget_destroy(headerentry->entry);
6143 g_free(headerentry);
6145 compose->header_last = NULL;
6146 g_slist_free(compose->header_list);
6147 compose->header_list = NULL;
6148 compose->header_nextrow = 1;
6149 compose_create_header_entry(compose);
6152 static GtkWidget *compose_create_header(Compose *compose)
6154 GtkWidget *from_optmenu_hbox;
6155 GtkWidget *header_scrolledwin;
6156 GtkWidget *header_table;
6160 /* header labels and entries */
6161 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
6162 gtk_widget_show(header_scrolledwin);
6163 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
6165 header_table = gtk_table_new(2, 2, FALSE);
6166 gtk_widget_show(header_table);
6167 gtk_container_set_border_width(GTK_CONTAINER(header_table), BORDER_WIDTH);
6168 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
6169 gtk_viewport_set_shadow_type(GTK_VIEWPORT(GTK_BIN(header_scrolledwin)->child), GTK_SHADOW_NONE);
6172 /* option menu for selecting accounts */
6173 from_optmenu_hbox = compose_account_option_menu_create(compose);
6174 gtk_table_attach(GTK_TABLE(header_table), from_optmenu_hbox,
6175 0, 2, count, count + 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
6178 compose->header_table = header_table;
6179 compose->header_list = NULL;
6180 compose->header_nextrow = count;
6182 compose_create_header_entry(compose);
6184 compose->table = NULL;
6186 return header_scrolledwin ;
6189 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
6191 Compose *compose = (Compose *)data;
6192 GdkEventButton event;
6195 event.time = gtk_get_current_event_time();
6197 return attach_button_pressed(compose->attach_clist, &event, compose);
6200 static GtkWidget *compose_create_attach(Compose *compose)
6202 GtkWidget *attach_scrwin;
6203 GtkWidget *attach_clist;
6205 GtkListStore *store;
6206 GtkCellRenderer *renderer;
6207 GtkTreeViewColumn *column;
6208 GtkTreeSelection *selection;
6210 /* attachment list */
6211 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
6212 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
6213 GTK_POLICY_AUTOMATIC,
6214 GTK_POLICY_AUTOMATIC);
6215 gtk_widget_set_size_request(attach_scrwin, -1, 80);
6217 store = gtk_list_store_new(N_ATTACH_COLS,
6222 G_TYPE_AUTO_POINTER,
6224 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
6225 (GTK_TREE_MODEL(store)));
6226 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
6227 g_object_unref(store);
6229 renderer = gtk_cell_renderer_text_new();
6230 column = gtk_tree_view_column_new_with_attributes
6231 (_("Mime type"), renderer, "text",
6232 COL_MIMETYPE, NULL);
6233 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6235 renderer = gtk_cell_renderer_text_new();
6236 column = gtk_tree_view_column_new_with_attributes
6237 (_("Size"), renderer, "text",
6239 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6241 renderer = gtk_cell_renderer_text_new();
6242 column = gtk_tree_view_column_new_with_attributes
6243 (_("Name"), renderer, "text",
6245 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6247 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
6248 prefs_common.use_stripes_everywhere);
6249 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
6250 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
6252 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
6253 G_CALLBACK(attach_selected), compose);
6254 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
6255 G_CALLBACK(attach_button_pressed), compose);
6257 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
6258 G_CALLBACK(popup_attach_button_pressed), compose);
6260 gtk_widget_tap_and_hold_setup(GTK_WIDGET(attach_clist), NULL, NULL,
6261 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
6262 g_signal_connect(G_OBJECT(attach_clist), "tap-and-hold",
6263 G_CALLBACK(popup_attach_button_pressed), compose);
6265 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
6266 G_CALLBACK(attach_key_pressed), compose);
6269 gtk_drag_dest_set(attach_clist,
6270 GTK_DEST_DEFAULT_ALL, compose_mime_types,
6271 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6272 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6273 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
6274 G_CALLBACK(compose_attach_drag_received_cb),
6276 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
6277 G_CALLBACK(compose_drag_drop),
6280 compose->attach_scrwin = attach_scrwin;
6281 compose->attach_clist = attach_clist;
6283 return attach_scrwin;
6286 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
6287 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
6289 static GtkWidget *compose_create_others(Compose *compose)
6292 GtkWidget *savemsg_checkbtn;
6293 GtkWidget *savemsg_entry;
6294 GtkWidget *savemsg_select;
6297 gchar *folderidentifier;
6299 /* Table for settings */
6300 table = gtk_table_new(3, 1, FALSE);
6301 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
6302 gtk_widget_show(table);
6303 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
6306 /* Save Message to folder */
6307 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
6308 gtk_widget_show(savemsg_checkbtn);
6309 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6310 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6311 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
6313 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
6314 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
6316 savemsg_entry = gtk_entry_new();
6317 gtk_widget_show(savemsg_entry);
6318 gtk_table_attach_defaults(GTK_TABLE(table), savemsg_entry, 1, 2, rowcount, rowcount + 1);
6319 gtk_editable_set_editable(GTK_EDITABLE(savemsg_entry), prefs_common.savemsg);
6320 g_signal_connect_after(G_OBJECT(savemsg_entry), "grab_focus",
6321 G_CALLBACK(compose_grab_focus_cb), compose);
6322 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6323 folderidentifier = folder_item_get_identifier(account_get_special_folder
6324 (compose->account, F_OUTBOX));
6325 gtk_entry_set_text(GTK_ENTRY(savemsg_entry), folderidentifier);
6326 g_free(folderidentifier);
6329 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
6330 gtk_widget_show(savemsg_select);
6331 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6332 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
6333 G_CALLBACK(compose_savemsg_select_cb),
6338 compose->savemsg_checkbtn = savemsg_checkbtn;
6339 compose->savemsg_entry = savemsg_entry;
6344 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
6346 gtk_editable_set_editable(GTK_EDITABLE(compose->savemsg_entry),
6347 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
6350 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
6355 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL);
6358 path = folder_item_get_identifier(dest);
6360 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), path);
6364 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
6365 GdkAtom clip, GtkTextIter *insert_place);
6368 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
6372 GtkTextBuffer *buffer;
6374 if (event->button == 3) {
6376 GtkTextIter sel_start, sel_end;
6377 gboolean stuff_selected;
6379 /* move the cursor to allow GtkAspell to check the word
6380 * under the mouse */
6381 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6382 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6384 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6387 stuff_selected = gtk_text_buffer_get_selection_bounds(
6388 GTK_TEXT_VIEW(text)->buffer,
6389 &sel_start, &sel_end);
6391 gtk_text_buffer_place_cursor (GTK_TEXT_VIEW(text)->buffer, &iter);
6392 /* reselect stuff */
6394 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
6395 gtk_text_buffer_select_range(GTK_TEXT_VIEW(text)->buffer,
6396 &sel_start, &sel_end);
6398 return FALSE; /* pass the event so that the right-click goes through */
6401 if (event->button == 2) {
6406 /* get the middle-click position to paste at the correct place */
6407 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6408 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6410 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6413 entry_paste_clipboard(compose, text,
6414 prefs_common.linewrap_pastes,
6415 GDK_SELECTION_PRIMARY, &iter);
6423 static void compose_spell_menu_changed(void *data)
6425 Compose *compose = (Compose *)data;
6427 GtkWidget *menuitem;
6428 GtkWidget *parent_item;
6429 GtkMenu *menu = GTK_MENU(gtk_menu_new());
6430 GtkItemFactory *ifactory = gtk_item_factory_from_widget(compose->menubar);
6433 if (compose->gtkaspell == NULL)
6436 parent_item = gtk_item_factory_get_item(ifactory,
6437 "/Spelling/Options");
6439 /* setting the submenu removes /Spelling/Options from the factory
6440 * so we need to save it */
6442 if (parent_item == NULL) {
6443 parent_item = compose->aspell_options_menu;
6444 gtk_menu_item_remove_submenu(GTK_MENU_ITEM(parent_item));
6446 compose->aspell_options_menu = parent_item;
6448 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
6450 spell_menu = g_slist_reverse(spell_menu);
6451 for (items = spell_menu;
6452 items; items = items->next) {
6453 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
6454 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
6455 gtk_widget_show(GTK_WIDGET(menuitem));
6457 g_slist_free(spell_menu);
6459 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
6464 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
6466 Compose *compose = (Compose *)data;
6467 GdkEventButton event;
6470 event.time = gtk_get_current_event_time();
6472 return text_clicked(compose->text, &event, compose);
6475 static gboolean compose_force_window_origin = TRUE;
6476 static Compose *compose_create(PrefsAccount *account,
6485 GtkWidget *handlebox;
6487 GtkWidget *notebook;
6489 GtkWidget *attach_hbox;
6490 GtkWidget *attach_lab1;
6491 GtkWidget *attach_lab2;
6496 GtkWidget *subject_hbox;
6497 GtkWidget *subject_frame;
6498 GtkWidget *subject_entry;
6502 GtkWidget *edit_vbox;
6503 GtkWidget *ruler_hbox;
6505 GtkWidget *scrolledwin;
6507 GtkTextBuffer *buffer;
6508 GtkClipboard *clipboard;
6510 UndoMain *undostruct;
6512 gchar *titles[N_ATTACH_COLS];
6513 guint n_menu_entries;
6514 GtkWidget *popupmenu;
6515 GtkItemFactory *popupfactory;
6516 GtkItemFactory *ifactory;
6517 GtkWidget *tmpl_menu;
6519 GtkWidget *menuitem;
6522 GtkAspell * gtkaspell = NULL;
6525 static GdkGeometry geometry;
6527 g_return_val_if_fail(account != NULL, NULL);
6529 debug_print("Creating compose window...\n");
6530 compose = g_new0(Compose, 1);
6532 titles[COL_MIMETYPE] = _("MIME type");
6533 titles[COL_SIZE] = _("Size");
6534 titles[COL_NAME] = _("Name");
6536 compose->batch = batch;
6537 compose->account = account;
6538 compose->folder = folder;
6540 compose->mutex = g_mutex_new();
6541 compose->set_cursor_pos = -1;
6543 compose->tooltips = gtk_tooltips_new();
6545 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
6547 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
6548 gtk_widget_set_size_request(window, -1, prefs_common.compose_height);
6550 if (!geometry.max_width) {
6551 geometry.max_width = gdk_screen_width();
6552 geometry.max_height = gdk_screen_height();
6555 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
6556 &geometry, GDK_HINT_MAX_SIZE);
6557 if (!geometry.min_width) {
6558 geometry.min_width = 600;
6559 geometry.min_height = 480;
6561 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
6562 &geometry, GDK_HINT_MIN_SIZE);
6565 if (compose_force_window_origin)
6566 gtk_widget_set_uposition(window, prefs_common.compose_x,
6567 prefs_common.compose_y);
6569 g_signal_connect(G_OBJECT(window), "delete_event",
6570 G_CALLBACK(compose_delete_cb), compose);
6571 MANAGE_WINDOW_SIGNALS_CONNECT(window);
6572 gtk_widget_realize(window);
6574 gtkut_widget_set_composer_icon(window);
6576 vbox = gtk_vbox_new(FALSE, 0);
6577 gtk_container_add(GTK_CONTAINER(window), vbox);
6579 n_menu_entries = sizeof(compose_entries) / sizeof(compose_entries[0]);
6580 menubar = menubar_create(window, compose_entries,
6581 n_menu_entries, "<Compose>", compose);
6582 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
6584 if (prefs_common.toolbar_detachable) {
6585 handlebox = gtk_handle_box_new();
6587 handlebox = gtk_hbox_new(FALSE, 0);
6589 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
6591 gtk_widget_realize(handlebox);
6593 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, window,
6596 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
6600 vbox2 = gtk_vbox_new(FALSE, 2);
6601 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
6602 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
6605 notebook = gtk_notebook_new();
6606 gtk_widget_set_size_request(notebook, -1, 130);
6607 gtk_widget_show(notebook);
6609 /* header labels and entries */
6610 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6611 compose_create_header(compose),
6612 gtk_label_new_with_mnemonic(_("Hea_der")));
6613 /* attachment list */
6614 attach_hbox = gtk_hbox_new(FALSE, 0);
6615 gtk_widget_show(attach_hbox);
6617 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
6618 gtk_widget_show(attach_lab1);
6619 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
6621 attach_lab2 = gtk_label_new("");
6622 gtk_widget_show(attach_lab2);
6623 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
6625 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6626 compose_create_attach(compose),
6629 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6630 compose_create_others(compose),
6631 gtk_label_new_with_mnemonic(_("Othe_rs")));
6634 subject_hbox = gtk_hbox_new(FALSE, 0);
6635 gtk_widget_show(subject_hbox);
6637 subject_frame = gtk_frame_new(NULL);
6638 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
6639 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
6640 gtk_widget_show(subject_frame);
6642 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
6643 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
6644 gtk_widget_show(subject);
6646 label = gtk_label_new(_("Subject:"));
6647 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
6648 gtk_widget_show(label);
6650 subject_entry = gtk_entry_new();
6651 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
6652 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
6653 G_CALLBACK(compose_grab_focus_cb), compose);
6654 gtk_widget_show(subject_entry);
6655 compose->subject_entry = subject_entry;
6656 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
6658 edit_vbox = gtk_vbox_new(FALSE, 0);
6660 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
6663 ruler_hbox = gtk_hbox_new(FALSE, 0);
6664 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
6666 ruler = gtk_shruler_new();
6667 gtk_ruler_set_range(GTK_RULER(ruler), 0.0, 100.0, 1.0, 100.0);
6668 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
6672 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
6673 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
6674 GTK_POLICY_AUTOMATIC,
6675 GTK_POLICY_AUTOMATIC);
6676 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
6678 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
6679 gtk_widget_set_size_request(scrolledwin, prefs_common.compose_width, -1);
6681 text = gtk_text_view_new();
6682 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
6683 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
6684 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
6685 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
6686 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
6688 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
6690 g_signal_connect_after(G_OBJECT(text), "size_allocate",
6691 G_CALLBACK(compose_edit_size_alloc),
6693 g_signal_connect(G_OBJECT(buffer), "changed",
6694 G_CALLBACK(compose_changed_cb), compose);
6695 g_signal_connect(G_OBJECT(text), "grab_focus",
6696 G_CALLBACK(compose_grab_focus_cb), compose);
6697 g_signal_connect(G_OBJECT(buffer), "insert_text",
6698 G_CALLBACK(text_inserted), compose);
6699 g_signal_connect(G_OBJECT(text), "button_press_event",
6700 G_CALLBACK(text_clicked), compose);
6702 g_signal_connect(G_OBJECT(text), "popup-menu",
6703 G_CALLBACK(compose_popup_menu), compose);
6705 gtk_widget_tap_and_hold_setup(GTK_WIDGET(text), NULL, NULL,
6706 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
6707 g_signal_connect(G_OBJECT(text), "tap-and-hold",
6708 G_CALLBACK(compose_popup_menu), compose);
6710 g_signal_connect(G_OBJECT(subject_entry), "changed",
6711 G_CALLBACK(compose_changed_cb), compose);
6714 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6715 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6716 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6717 g_signal_connect(G_OBJECT(text), "drag_data_received",
6718 G_CALLBACK(compose_insert_drag_received_cb),
6720 g_signal_connect(G_OBJECT(text), "drag-drop",
6721 G_CALLBACK(compose_drag_drop),
6723 gtk_widget_show_all(vbox);
6725 /* pane between attach clist and text */
6726 paned = gtk_vpaned_new();
6727 gtk_paned_set_gutter_size(GTK_PANED(paned), 12);
6728 gtk_container_add(GTK_CONTAINER(vbox2), paned);
6730 if( maemo_mainwindow_is_fullscreen(mainwindow_get_mainwindow()->window) )
6731 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 300 : 280);
6733 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 250 : 230);
6735 gtk_paned_add1(GTK_PANED(paned), notebook);
6736 gtk_paned_add2(GTK_PANED(paned), edit_vbox);
6737 gtk_widget_show_all(paned);
6740 if (prefs_common.textfont) {
6741 PangoFontDescription *font_desc;
6743 font_desc = pango_font_description_from_string
6744 (prefs_common.textfont);
6746 gtk_widget_modify_font(text, font_desc);
6747 pango_font_description_free(font_desc);
6751 n_entries = sizeof(compose_popup_entries) /
6752 sizeof(compose_popup_entries[0]);
6753 popupmenu = menu_create_items(compose_popup_entries, n_entries,
6754 "<Compose>", &popupfactory,
6757 ifactory = gtk_item_factory_from_widget(menubar);
6758 menu_set_sensitive(ifactory, "/Edit/Undo", FALSE);
6759 menu_set_sensitive(ifactory, "/Edit/Redo", FALSE);
6760 menu_set_sensitive(ifactory, "/Options/Remove references", FALSE);
6762 tmpl_menu = gtk_item_factory_get_item(ifactory, "/Tools/Template");
6764 undostruct = undo_init(text);
6765 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
6768 address_completion_start(window);
6770 compose->window = window;
6771 compose->vbox = vbox;
6772 compose->menubar = menubar;
6773 compose->handlebox = handlebox;
6775 compose->vbox2 = vbox2;
6777 compose->paned = paned;
6779 compose->attach_label = attach_lab2;
6781 compose->notebook = notebook;
6782 compose->edit_vbox = edit_vbox;
6783 compose->ruler_hbox = ruler_hbox;
6784 compose->ruler = ruler;
6785 compose->scrolledwin = scrolledwin;
6786 compose->text = text;
6788 compose->focused_editable = NULL;
6790 compose->popupmenu = popupmenu;
6791 compose->popupfactory = popupfactory;
6793 compose->tmpl_menu = tmpl_menu;
6795 compose->mode = mode;
6796 compose->rmode = mode;
6798 compose->targetinfo = NULL;
6799 compose->replyinfo = NULL;
6800 compose->fwdinfo = NULL;
6802 compose->replyto = NULL;
6804 compose->bcc = NULL;
6805 compose->followup_to = NULL;
6807 compose->ml_post = NULL;
6809 compose->inreplyto = NULL;
6810 compose->references = NULL;
6811 compose->msgid = NULL;
6812 compose->boundary = NULL;
6814 compose->autowrap = prefs_common.autowrap;
6816 compose->use_signing = FALSE;
6817 compose->use_encryption = FALSE;
6818 compose->privacy_system = NULL;
6820 compose->modified = FALSE;
6822 compose->return_receipt = FALSE;
6824 compose->to_list = NULL;
6825 compose->newsgroup_list = NULL;
6827 compose->undostruct = undostruct;
6829 compose->sig_str = NULL;
6831 compose->exteditor_file = NULL;
6832 compose->exteditor_pid = -1;
6833 compose->exteditor_tag = -1;
6834 compose->draft_timeout_tag = -2; /* inhibit auto-drafting while loading */
6837 menu_set_sensitive(ifactory, "/Spelling", FALSE);
6838 if (mode != COMPOSE_REDIRECT) {
6839 if (prefs_common.enable_aspell && prefs_common.dictionary &&
6840 strcmp(prefs_common.dictionary, "")) {
6841 gtkaspell = gtkaspell_new(prefs_common.aspell_path,
6842 prefs_common.dictionary,
6843 prefs_common.alt_dictionary,
6844 conv_get_locale_charset_str(),
6845 prefs_common.misspelled_col,
6846 prefs_common.check_while_typing,
6847 prefs_common.recheck_when_changing_dict,
6848 prefs_common.use_alternate,
6849 prefs_common.use_both_dicts,
6850 GTK_TEXT_VIEW(text),
6851 GTK_WINDOW(compose->window),
6852 compose_spell_menu_changed,
6855 alertpanel_error(_("Spell checker could not "
6857 gtkaspell_checkers_strerror());
6858 gtkaspell_checkers_reset_error();
6860 if (!gtkaspell_set_sug_mode(gtkaspell,
6861 prefs_common.aspell_sugmode)) {
6862 debug_print("Aspell: could not set "
6863 "suggestion mode %s\n",
6864 gtkaspell_checkers_strerror());
6865 gtkaspell_checkers_reset_error();
6868 menu_set_sensitive(ifactory, "/Spelling", TRUE);
6872 compose->gtkaspell = gtkaspell;
6873 compose_spell_menu_changed(compose);
6876 compose_select_account(compose, account, TRUE);
6878 menu_set_active(ifactory, "/Edit/Auto wrapping", prefs_common.autowrap);
6879 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
6880 compose_entry_append(compose, account->auto_cc, COMPOSE_CC);
6882 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
6883 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC);
6885 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
6886 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO);
6888 menu_set_sensitive(ifactory, "/Options/Reply mode", compose->mode == COMPOSE_REPLY);
6890 if (account->protocol != A_NNTP)
6891 gtk_entry_set_text(GTK_ENTRY(GTK_BIN(compose->header_last->combo)->child),
6892 prefs_common_translated_header_name("To:"));
6894 gtk_entry_set_text(GTK_ENTRY(GTK_BIN(compose->header_last->combo)->child),
6895 prefs_common_translated_header_name("Newsgroups:"));
6897 addressbook_set_target_compose(compose);
6899 if (mode != COMPOSE_REDIRECT)
6900 compose_set_template_menu(compose);
6902 menuitem = gtk_item_factory_get_item(ifactory, "/Tools/Template");
6903 menu_set_sensitive(ifactory, "/Tools/Template", FALSE);
6906 compose_list = g_list_append(compose_list, compose);
6908 if (!prefs_common.show_ruler)
6909 gtk_widget_hide(ruler_hbox);
6911 menuitem = gtk_item_factory_get_item(ifactory, "/Tools/Show ruler");
6912 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
6913 prefs_common.show_ruler);
6916 compose->priority = PRIORITY_NORMAL;
6917 compose_update_priority_menu_item(compose);
6919 compose_set_out_encoding(compose);
6922 compose_update_actions_menu(compose);
6924 /* Privacy Systems menu */
6925 compose_update_privacy_systems_menu(compose);
6927 activate_privacy_system(compose, account, TRUE);
6928 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
6930 gtk_widget_realize(window);
6932 gtk_widget_show(window);
6934 maemo_window_full_screen_if_needed(GTK_WINDOW(window));
6935 maemo_connect_key_press_to_mainwindow(GTK_WINDOW(window));
6942 static GtkWidget *compose_account_option_menu_create(Compose *compose)
6947 GtkWidget *optmenubox;
6950 GtkWidget *from_name = NULL;
6952 gint num = 0, def_menu = 0;
6954 accounts = account_get_list();
6955 g_return_val_if_fail(accounts != NULL, NULL);
6957 optmenubox = gtk_event_box_new();
6958 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
6959 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
6961 hbox = gtk_hbox_new(FALSE, 6);
6962 from_name = gtk_entry_new();
6964 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
6965 G_CALLBACK(compose_grab_focus_cb), compose);
6967 for (; accounts != NULL; accounts = accounts->next, num++) {
6968 PrefsAccount *ac = (PrefsAccount *)accounts->data;
6969 gchar *name, *from = NULL;
6971 if (ac == compose->account) def_menu = num;
6973 name = g_markup_printf_escaped(_("From: <i>%s</i>"),
6976 if (ac == compose->account) {
6977 if (ac->name && *ac->name) {
6979 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
6980 from = g_strdup_printf("%s <%s>",
6982 gtk_entry_set_text(GTK_ENTRY(from_name), from);
6984 from = g_strdup_printf("%s",
6986 gtk_entry_set_text(GTK_ENTRY(from_name), from);
6989 COMBOBOX_ADD(menu, name, ac->account_id);
6994 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
6996 g_signal_connect(G_OBJECT(optmenu), "changed",
6997 G_CALLBACK(account_activated),
6999 g_signal_connect(G_OBJECT(from_name), "populate-popup",
7000 G_CALLBACK(compose_entry_popup_extend),
7003 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
7004 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
7006 gtk_tooltips_set_tip(compose->tooltips, optmenubox,
7007 _("Account to use for this email"), NULL);
7008 gtk_tooltips_set_tip(compose->tooltips, from_name,
7009 _("Sender address to be used"), NULL);
7011 compose->from_name = from_name;
7016 static void compose_set_priority_cb(gpointer data,
7020 Compose *compose = (Compose *) data;
7021 compose->priority = action;
7024 static void compose_reply_change_mode(gpointer data,
7028 Compose *compose = (Compose *) data;
7029 gboolean was_modified = compose->modified;
7031 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
7033 g_return_if_fail(compose->replyinfo != NULL);
7035 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
7037 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
7039 if (action == COMPOSE_REPLY_TO_ALL)
7041 if (action == COMPOSE_REPLY_TO_SENDER)
7043 if (action == COMPOSE_REPLY_TO_LIST)
7046 compose_remove_header_entries(compose);
7047 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
7048 if (compose->account->set_autocc && compose->account->auto_cc)
7049 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC);
7051 if (compose->account->set_autobcc && compose->account->auto_bcc)
7052 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC);
7054 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
7055 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO);
7056 compose_show_first_last_header(compose, TRUE);
7057 compose->modified = was_modified;
7058 compose_set_title(compose);
7061 static void compose_update_priority_menu_item(Compose * compose)
7063 GtkItemFactory *ifactory;
7064 GtkWidget *menuitem = NULL;
7066 ifactory = gtk_item_factory_from_widget(compose->menubar);
7068 switch (compose->priority) {
7069 case PRIORITY_HIGHEST:
7070 menuitem = gtk_item_factory_get_item
7071 (ifactory, "/Options/Priority/Highest");
7074 menuitem = gtk_item_factory_get_item
7075 (ifactory, "/Options/Priority/High");
7077 case PRIORITY_NORMAL:
7078 menuitem = gtk_item_factory_get_item
7079 (ifactory, "/Options/Priority/Normal");
7082 menuitem = gtk_item_factory_get_item
7083 (ifactory, "/Options/Priority/Low");
7085 case PRIORITY_LOWEST:
7086 menuitem = gtk_item_factory_get_item
7087 (ifactory, "/Options/Priority/Lowest");
7090 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
7093 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
7095 Compose *compose = (Compose *) data;
7097 GtkItemFactory *ifactory;
7098 gboolean can_sign = FALSE, can_encrypt = FALSE;
7100 g_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
7102 if (!GTK_CHECK_MENU_ITEM(widget)->active)
7105 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
7106 g_free(compose->privacy_system);
7107 compose->privacy_system = NULL;
7108 if (systemid != NULL) {
7109 compose->privacy_system = g_strdup(systemid);
7111 can_sign = privacy_system_can_sign(systemid);
7112 can_encrypt = privacy_system_can_encrypt(systemid);
7115 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
7117 ifactory = gtk_item_factory_from_widget(compose->menubar);
7118 menu_set_sensitive(ifactory, "/Options/Sign", can_sign);
7119 menu_set_sensitive(ifactory, "/Options/Encrypt", can_encrypt);
7122 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
7124 static gchar *branch_path = "/Options/Privacy System";
7125 GtkItemFactory *ifactory;
7126 GtkWidget *menuitem = NULL;
7128 gboolean can_sign = FALSE, can_encrypt = FALSE;
7129 gboolean found = FALSE;
7131 ifactory = gtk_item_factory_from_widget(compose->menubar);
7133 if (compose->privacy_system != NULL) {
7136 menuitem = gtk_item_factory_get_widget(ifactory, branch_path);
7137 g_return_if_fail(menuitem != NULL);
7139 amenu = GTK_MENU_SHELL(menuitem)->children;
7141 while (amenu != NULL) {
7142 GList *alist = amenu->next;
7144 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
7145 if (systemid != NULL) {
7146 if (strcmp(systemid, compose->privacy_system) == 0) {
7147 menuitem = GTK_WIDGET(amenu->data);
7149 can_sign = privacy_system_can_sign(systemid);
7150 can_encrypt = privacy_system_can_encrypt(systemid);
7154 } else if (strlen(compose->privacy_system) == 0) {
7155 menuitem = GTK_WIDGET(amenu->data);
7158 can_encrypt = FALSE;
7165 if (menuitem != NULL)
7166 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
7168 if (warn && !found && strlen(compose->privacy_system)) {
7169 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
7170 "will not be able to sign or encrypt this message."),
7171 compose->privacy_system);
7175 menu_set_sensitive(ifactory, "/Options/Sign", can_sign);
7176 menu_set_sensitive(ifactory, "/Options/Encrypt", can_encrypt);
7179 static void compose_set_out_encoding(Compose *compose)
7181 GtkItemFactoryEntry *entry;
7182 GtkItemFactory *ifactory;
7183 CharSet out_encoding;
7184 gchar *path, *p, *q;
7187 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
7188 ifactory = gtk_item_factory_from_widget(compose->menubar);
7190 for (entry = compose_entries; entry->callback != compose_address_cb;
7192 if (entry->callback == compose_set_encoding_cb &&
7193 (CharSet)entry->callback_action == out_encoding) {
7194 p = q = path = g_strdup(entry->path);
7206 item = gtk_item_factory_get_item(ifactory, path);
7207 gtk_widget_activate(item);
7214 static void compose_set_template_menu(Compose *compose)
7216 GSList *tmpl_list, *cur;
7220 tmpl_list = template_get_config();
7222 menu = gtk_menu_new();
7224 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
7225 Template *tmpl = (Template *)cur->data;
7227 item = gtk_menu_item_new_with_label(tmpl->name);
7228 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
7229 g_signal_connect(G_OBJECT(item), "activate",
7230 G_CALLBACK(compose_template_activate_cb),
7232 g_object_set_data(G_OBJECT(item), "template", tmpl);
7233 gtk_widget_show(item);
7236 gtk_widget_show(menu);
7237 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
7240 void compose_update_actions_menu(Compose *compose)
7242 GtkItemFactory *ifactory;
7244 ifactory = gtk_item_factory_from_widget(compose->menubar);
7245 action_update_compose_menu(ifactory, "/Tools/Actions", compose);
7248 static void compose_update_privacy_systems_menu(Compose *compose)
7250 static gchar *branch_path = "/Options/Privacy System";
7251 GtkItemFactory *ifactory;
7252 GtkWidget *menuitem;
7253 GSList *systems, *cur;
7256 GtkWidget *system_none;
7259 ifactory = gtk_item_factory_from_widget(compose->menubar);
7261 /* remove old entries */
7262 menuitem = gtk_item_factory_get_widget(ifactory, branch_path);
7263 g_return_if_fail(menuitem != NULL);
7265 amenu = GTK_MENU_SHELL(menuitem)->children->next;
7266 while (amenu != NULL) {
7267 GList *alist = amenu->next;
7268 gtk_widget_destroy(GTK_WIDGET(amenu->data));
7272 system_none = gtk_item_factory_get_widget(ifactory,
7273 "/Options/Privacy System/None");
7275 g_signal_connect(G_OBJECT(system_none), "activate",
7276 G_CALLBACK(compose_set_privacy_system_cb), compose);
7278 systems = privacy_get_system_ids();
7279 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
7280 gchar *systemid = cur->data;
7282 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
7283 widget = gtk_radio_menu_item_new_with_label(group,
7284 privacy_system_get_name(systemid));
7285 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
7286 g_strdup(systemid), g_free);
7287 g_signal_connect(G_OBJECT(widget), "activate",
7288 G_CALLBACK(compose_set_privacy_system_cb), compose);
7290 gtk_menu_append(GTK_MENU(system_none->parent), widget);
7291 gtk_widget_show(widget);
7294 g_slist_free(systems);
7297 void compose_reflect_prefs_all(void)
7302 for (cur = compose_list; cur != NULL; cur = cur->next) {
7303 compose = (Compose *)cur->data;
7304 compose_set_template_menu(compose);
7308 void compose_reflect_prefs_pixmap_theme(void)
7313 for (cur = compose_list; cur != NULL; cur = cur->next) {
7314 compose = (Compose *)cur->data;
7315 toolbar_update(TOOLBAR_COMPOSE, compose);
7319 static const gchar *compose_quote_char_from_context(Compose *compose)
7321 const gchar *qmark = NULL;
7323 g_return_val_if_fail(compose != NULL, NULL);
7325 switch (compose->mode) {
7326 /* use forward-specific quote char */
7327 case COMPOSE_FORWARD:
7328 case COMPOSE_FORWARD_AS_ATTACH:
7329 case COMPOSE_FORWARD_INLINE:
7330 if (compose->folder && compose->folder->prefs &&
7331 compose->folder->prefs->forward_with_format)
7332 qmark = compose->folder->prefs->forward_quotemark;
7333 else if (compose->account->forward_with_format)
7334 qmark = compose->account->forward_quotemark;
7336 qmark = prefs_common.fw_quotemark;
7339 /* use reply-specific quote char in all other modes */
7341 if (compose->folder && compose->folder->prefs &&
7342 compose->folder->prefs->reply_with_format)
7343 qmark = compose->folder->prefs->reply_quotemark;
7344 else if (compose->account->reply_with_format)
7345 qmark = compose->account->reply_quotemark;
7347 qmark = prefs_common.quotemark;
7351 if (qmark == NULL || *qmark == '\0')
7357 static void compose_template_apply(Compose *compose, Template *tmpl,
7361 GtkTextBuffer *buffer;
7365 gchar *parsed_str = NULL;
7366 gint cursor_pos = 0;
7367 const gchar *err_msg = _("Template body format error at line %d.");
7370 /* process the body */
7372 text = GTK_TEXT_VIEW(compose->text);
7373 buffer = gtk_text_view_get_buffer(text);
7376 qmark = compose_quote_char_from_context(compose);
7378 if (compose->replyinfo != NULL) {
7381 gtk_text_buffer_set_text(buffer, "", -1);
7382 mark = gtk_text_buffer_get_insert(buffer);
7383 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7385 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
7386 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
7388 } else if (compose->fwdinfo != NULL) {
7391 gtk_text_buffer_set_text(buffer, "", -1);
7392 mark = gtk_text_buffer_get_insert(buffer);
7393 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7395 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
7396 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
7399 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
7401 GtkTextIter start, end;
7404 gtk_text_buffer_get_start_iter(buffer, &start);
7405 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
7406 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
7408 /* clear the buffer now */
7410 gtk_text_buffer_set_text(buffer, "", -1);
7412 parsed_str = compose_quote_fmt(compose, dummyinfo,
7413 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
7414 procmsg_msginfo_free( dummyinfo );
7420 gtk_text_buffer_set_text(buffer, "", -1);
7421 mark = gtk_text_buffer_get_insert(buffer);
7422 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7425 if (replace && parsed_str && compose->account->auto_sig)
7426 compose_insert_sig(compose, FALSE);
7428 if (replace && parsed_str) {
7429 gtk_text_buffer_get_start_iter(buffer, &iter);
7430 gtk_text_buffer_place_cursor(buffer, &iter);
7434 cursor_pos = quote_fmt_get_cursor_pos();
7435 compose->set_cursor_pos = cursor_pos;
7436 if (cursor_pos == -1)
7438 gtk_text_buffer_get_start_iter(buffer, &iter);
7439 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
7440 gtk_text_buffer_place_cursor(buffer, &iter);
7443 /* process the other fields */
7445 compose_template_apply_fields(compose, tmpl);
7446 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
7447 quote_fmt_reset_vartable();
7448 compose_changed_cb(NULL, compose);
7451 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
7453 MsgInfo* dummyinfo = NULL;
7454 MsgInfo *msginfo = NULL;
7457 if (compose->replyinfo != NULL)
7458 msginfo = compose->replyinfo;
7459 else if (compose->fwdinfo != NULL)
7460 msginfo = compose->fwdinfo;
7462 dummyinfo = compose_msginfo_new_from_compose(compose);
7463 msginfo = dummyinfo;
7466 if (tmpl->to && *tmpl->to != '\0') {
7468 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7469 compose->gtkaspell);
7471 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7473 quote_fmt_scan_string(tmpl->to);
7476 buf = quote_fmt_get_buffer();
7478 alertpanel_error(_("Template To format error."));
7480 compose_entry_append(compose, buf, COMPOSE_TO);
7484 if (tmpl->cc && *tmpl->cc != '\0') {
7486 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7487 compose->gtkaspell);
7489 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7491 quote_fmt_scan_string(tmpl->cc);
7494 buf = quote_fmt_get_buffer();
7496 alertpanel_error(_("Template Cc format error."));
7498 compose_entry_append(compose, buf, COMPOSE_CC);
7502 if (tmpl->bcc && *tmpl->bcc != '\0') {
7504 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7505 compose->gtkaspell);
7507 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7509 quote_fmt_scan_string(tmpl->bcc);
7512 buf = quote_fmt_get_buffer();
7514 alertpanel_error(_("Template Bcc format error."));
7516 compose_entry_append(compose, buf, COMPOSE_BCC);
7520 /* process the subject */
7521 if (tmpl->subject && *tmpl->subject != '\0') {
7523 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7524 compose->gtkaspell);
7526 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7528 quote_fmt_scan_string(tmpl->subject);
7531 buf = quote_fmt_get_buffer();
7533 alertpanel_error(_("Template subject format error."));
7535 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
7539 procmsg_msginfo_free( dummyinfo );
7542 static void compose_destroy(Compose *compose)
7544 GtkTextBuffer *buffer;
7545 GtkClipboard *clipboard;
7547 compose_list = g_list_remove(compose_list, compose);
7549 if (compose->updating) {
7550 debug_print("danger, not destroying anything now\n");
7551 compose->deferred_destroy = TRUE;
7554 /* NOTE: address_completion_end() does nothing with the window
7555 * however this may change. */
7556 address_completion_end(compose->window);
7558 slist_free_strings(compose->to_list);
7559 g_slist_free(compose->to_list);
7560 slist_free_strings(compose->newsgroup_list);
7561 g_slist_free(compose->newsgroup_list);
7562 slist_free_strings(compose->header_list);
7563 g_slist_free(compose->header_list);
7565 procmsg_msginfo_free(compose->targetinfo);
7566 procmsg_msginfo_free(compose->replyinfo);
7567 procmsg_msginfo_free(compose->fwdinfo);
7569 g_free(compose->replyto);
7570 g_free(compose->cc);
7571 g_free(compose->bcc);
7572 g_free(compose->newsgroups);
7573 g_free(compose->followup_to);
7575 g_free(compose->ml_post);
7577 g_free(compose->inreplyto);
7578 g_free(compose->references);
7579 g_free(compose->msgid);
7580 g_free(compose->boundary);
7582 g_free(compose->redirect_filename);
7583 if (compose->undostruct)
7584 undo_destroy(compose->undostruct);
7586 g_free(compose->sig_str);
7588 g_free(compose->exteditor_file);
7590 g_free(compose->orig_charset);
7592 g_free(compose->privacy_system);
7594 if (addressbook_get_target_compose() == compose)
7595 addressbook_set_target_compose(NULL);
7598 if (compose->gtkaspell) {
7599 gtkaspell_delete(compose->gtkaspell);
7600 compose->gtkaspell = NULL;
7604 prefs_common.compose_width = compose->scrolledwin->allocation.width;
7605 prefs_common.compose_height = compose->window->allocation.height;
7607 if (!gtk_widget_get_parent(compose->paned))
7608 gtk_widget_destroy(compose->paned);
7609 gtk_widget_destroy(compose->popupmenu);
7611 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
7612 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7613 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
7615 gtk_widget_destroy(compose->window);
7616 toolbar_destroy(compose->toolbar);
7617 g_free(compose->toolbar);
7618 g_mutex_free(compose->mutex);
7622 static void compose_attach_info_free(AttachInfo *ainfo)
7624 g_free(ainfo->file);
7625 g_free(ainfo->content_type);
7626 g_free(ainfo->name);
7630 static void compose_attach_update_label(Compose *compose)
7635 GtkTreeModel *model;
7640 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
7641 if(!gtk_tree_model_get_iter_first(model, &iter)) {
7642 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
7646 while(gtk_tree_model_iter_next(model, &iter))
7649 text = g_strdup_printf("(%d)", i);
7650 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
7654 static void compose_attach_remove_selected(Compose *compose)
7656 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
7657 GtkTreeSelection *selection;
7659 GtkTreeModel *model;
7661 selection = gtk_tree_view_get_selection(tree_view);
7662 sel = gtk_tree_selection_get_selected_rows(selection, &model);
7667 for (cur = sel; cur != NULL; cur = cur->next) {
7668 GtkTreePath *path = cur->data;
7669 GtkTreeRowReference *ref = gtk_tree_row_reference_new
7672 gtk_tree_path_free(path);
7675 for (cur = sel; cur != NULL; cur = cur->next) {
7676 GtkTreeRowReference *ref = cur->data;
7677 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
7680 if (gtk_tree_model_get_iter(model, &iter, path))
7681 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
7683 gtk_tree_path_free(path);
7684 gtk_tree_row_reference_free(ref);
7688 compose_attach_update_label(compose);
7691 static struct _AttachProperty
7694 GtkWidget *mimetype_entry;
7695 GtkWidget *encoding_optmenu;
7696 GtkWidget *path_entry;
7697 GtkWidget *filename_entry;
7699 GtkWidget *cancel_btn;
7702 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
7704 gtk_tree_path_free((GtkTreePath *)ptr);
7707 static void compose_attach_property(Compose *compose)
7709 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
7711 GtkComboBox *optmenu;
7712 GtkTreeSelection *selection;
7714 GtkTreeModel *model;
7717 static gboolean cancelled;
7719 /* only if one selected */
7720 selection = gtk_tree_view_get_selection(tree_view);
7721 if (gtk_tree_selection_count_selected_rows(selection) != 1)
7724 sel = gtk_tree_selection_get_selected_rows(selection, &model);
7728 path = (GtkTreePath *) sel->data;
7729 gtk_tree_model_get_iter(model, &iter, path);
7730 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
7733 g_list_foreach(sel, gtk_tree_path_free_, NULL);
7739 if (!attach_prop.window)
7740 compose_attach_property_create(&cancelled);
7741 gtk_widget_grab_focus(attach_prop.ok_btn);
7742 gtk_widget_show(attach_prop.window);
7743 manage_window_set_transient(GTK_WINDOW(attach_prop.window));
7745 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
7746 if (ainfo->encoding == ENC_UNKNOWN)
7747 combobox_select_by_data(optmenu, ENC_BASE64);
7749 combobox_select_by_data(optmenu, ainfo->encoding);
7751 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
7752 ainfo->content_type ? ainfo->content_type : "");
7753 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
7754 ainfo->file ? ainfo->file : "");
7755 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
7756 ainfo->name ? ainfo->name : "");
7759 const gchar *entry_text;
7761 gchar *cnttype = NULL;
7768 gtk_widget_hide(attach_prop.window);
7773 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
7774 if (*entry_text != '\0') {
7777 text = g_strstrip(g_strdup(entry_text));
7778 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
7779 cnttype = g_strdup(text);
7782 alertpanel_error(_("Invalid MIME type."));
7788 ainfo->encoding = combobox_get_active_data(optmenu);
7790 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
7791 if (*entry_text != '\0') {
7792 if (is_file_exist(entry_text) &&
7793 (size = get_file_size(entry_text)) > 0)
7794 file = g_strdup(entry_text);
7797 (_("File doesn't exist or is empty."));
7803 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
7804 if (*entry_text != '\0') {
7805 g_free(ainfo->name);
7806 ainfo->name = g_strdup(entry_text);
7810 g_free(ainfo->content_type);
7811 ainfo->content_type = cnttype;
7814 g_free(ainfo->file);
7820 /* update tree store */
7821 text = to_human_readable(ainfo->size);
7822 gtk_tree_model_get_iter(model, &iter, path);
7823 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
7824 COL_MIMETYPE, ainfo->content_type,
7826 COL_NAME, ainfo->name,
7832 gtk_tree_path_free(path);
7835 #define SET_LABEL_AND_ENTRY(str, entry, top) \
7837 label = gtk_label_new(str); \
7838 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
7839 GTK_FILL, 0, 0, 0); \
7840 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
7842 entry = gtk_entry_new(); \
7843 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
7844 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
7847 static void compose_attach_property_create(gboolean *cancelled)
7853 GtkWidget *mimetype_entry;
7856 GtkListStore *optmenu_menu;
7857 GtkWidget *path_entry;
7858 GtkWidget *filename_entry;
7861 GtkWidget *cancel_btn;
7862 GList *mime_type_list, *strlist;
7865 debug_print("Creating attach_property window...\n");
7867 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
7868 gtk_widget_set_size_request(window, 480, -1);
7869 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
7870 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
7871 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
7872 gtk_window_set_modal(GTK_WINDOW(window), TRUE);
7873 g_signal_connect(G_OBJECT(window), "delete_event",
7874 G_CALLBACK(attach_property_delete_event),
7876 g_signal_connect(G_OBJECT(window), "key_press_event",
7877 G_CALLBACK(attach_property_key_pressed),
7880 vbox = gtk_vbox_new(FALSE, 8);
7881 gtk_container_add(GTK_CONTAINER(window), vbox);
7883 table = gtk_table_new(4, 2, FALSE);
7884 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
7885 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
7886 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
7888 label = gtk_label_new(_("MIME type"));
7889 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
7891 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
7892 mimetype_entry = gtk_combo_box_entry_new_text();
7893 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
7894 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
7896 /* stuff with list */
7897 mime_type_list = procmime_get_mime_type_list();
7899 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
7900 MimeType *type = (MimeType *) mime_type_list->data;
7903 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
7905 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
7908 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
7909 (GCompareFunc)strcmp2);
7912 for (mime_type_list = strlist; mime_type_list != NULL;
7913 mime_type_list = mime_type_list->next) {
7914 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
7915 g_free(mime_type_list->data);
7917 g_list_free(strlist);
7918 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
7919 mimetype_entry = GTK_BIN(mimetype_entry)->child;
7921 label = gtk_label_new(_("Encoding"));
7922 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
7924 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
7926 hbox = gtk_hbox_new(FALSE, 0);
7927 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
7928 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
7930 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
7931 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
7933 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
7934 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
7935 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
7936 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
7937 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
7939 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
7941 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
7942 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
7944 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
7945 &ok_btn, GTK_STOCK_OK,
7947 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
7948 gtk_widget_grab_default(ok_btn);
7950 g_signal_connect(G_OBJECT(ok_btn), "clicked",
7951 G_CALLBACK(attach_property_ok),
7953 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
7954 G_CALLBACK(attach_property_cancel),
7957 gtk_widget_show_all(vbox);
7959 attach_prop.window = window;
7960 attach_prop.mimetype_entry = mimetype_entry;
7961 attach_prop.encoding_optmenu = optmenu;
7962 attach_prop.path_entry = path_entry;
7963 attach_prop.filename_entry = filename_entry;
7964 attach_prop.ok_btn = ok_btn;
7965 attach_prop.cancel_btn = cancel_btn;
7968 #undef SET_LABEL_AND_ENTRY
7970 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
7976 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
7982 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
7983 gboolean *cancelled)
7991 static gboolean attach_property_key_pressed(GtkWidget *widget,
7993 gboolean *cancelled)
7995 if (event && event->keyval == GDK_Escape) {
8002 static void compose_exec_ext_editor(Compose *compose)
8009 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
8010 G_DIR_SEPARATOR, compose);
8012 if (pipe(pipe_fds) < 0) {
8018 if ((pid = fork()) < 0) {
8025 /* close the write side of the pipe */
8028 compose->exteditor_file = g_strdup(tmp);
8029 compose->exteditor_pid = pid;
8031 compose_set_ext_editor_sensitive(compose, FALSE);
8033 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
8034 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
8038 } else { /* process-monitoring process */
8044 /* close the read side of the pipe */
8047 if (compose_write_body_to_file(compose, tmp) < 0) {
8048 fd_write_all(pipe_fds[1], "2\n", 2);
8052 pid_ed = compose_exec_ext_editor_real(tmp);
8054 fd_write_all(pipe_fds[1], "1\n", 2);
8058 /* wait until editor is terminated */
8059 waitpid(pid_ed, NULL, 0);
8061 fd_write_all(pipe_fds[1], "0\n", 2);
8068 #endif /* G_OS_UNIX */
8072 static gint compose_exec_ext_editor_real(const gchar *file)
8079 g_return_val_if_fail(file != NULL, -1);
8081 if ((pid = fork()) < 0) {
8086 if (pid != 0) return pid;
8088 /* grandchild process */
8090 if (setpgid(0, getppid()))
8093 if (prefs_common.ext_editor_cmd &&
8094 (p = strchr(prefs_common.ext_editor_cmd, '%')) &&
8095 *(p + 1) == 's' && !strchr(p + 2, '%')) {
8096 g_snprintf(buf, sizeof(buf), prefs_common.ext_editor_cmd, file);
8098 if (prefs_common.ext_editor_cmd)
8099 g_warning("External editor command line is invalid: '%s'\n",
8100 prefs_common.ext_editor_cmd);
8101 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
8104 cmdline = strsplit_with_quote(buf, " ", 1024);
8105 execvp(cmdline[0], cmdline);
8108 g_strfreev(cmdline);
8113 static gboolean compose_ext_editor_kill(Compose *compose)
8115 pid_t pgid = compose->exteditor_pid * -1;
8118 ret = kill(pgid, 0);
8120 if (ret == 0 || (ret == -1 && EPERM == errno)) {
8124 msg = g_strdup_printf
8125 (_("The external editor is still working.\n"
8126 "Force terminating the process?\n"
8127 "process group id: %d"), -pgid);
8128 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
8129 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
8133 if (val == G_ALERTALTERNATE) {
8134 g_source_remove(compose->exteditor_tag);
8135 g_io_channel_shutdown(compose->exteditor_ch,
8137 g_io_channel_unref(compose->exteditor_ch);
8139 if (kill(pgid, SIGTERM) < 0) perror("kill");
8140 waitpid(compose->exteditor_pid, NULL, 0);
8142 g_warning("Terminated process group id: %d", -pgid);
8143 g_warning("Temporary file: %s",
8144 compose->exteditor_file);
8146 compose_set_ext_editor_sensitive(compose, TRUE);
8148 g_free(compose->exteditor_file);
8149 compose->exteditor_file = NULL;
8150 compose->exteditor_pid = -1;
8151 compose->exteditor_ch = NULL;
8152 compose->exteditor_tag = -1;
8160 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
8164 Compose *compose = (Compose *)data;
8167 debug_print(_("Compose: input from monitoring process\n"));
8169 g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL);
8171 g_io_channel_shutdown(source, FALSE, NULL);
8172 g_io_channel_unref(source);
8174 waitpid(compose->exteditor_pid, NULL, 0);
8176 if (buf[0] == '0') { /* success */
8177 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
8178 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
8180 gtk_text_buffer_set_text(buffer, "", -1);
8181 compose_insert_file(compose, compose->exteditor_file);
8182 compose_changed_cb(NULL, compose);
8184 if (g_unlink(compose->exteditor_file) < 0)
8185 FILE_OP_ERROR(compose->exteditor_file, "unlink");
8186 } else if (buf[0] == '1') { /* failed */
8187 g_warning("Couldn't exec external editor\n");
8188 if (g_unlink(compose->exteditor_file) < 0)
8189 FILE_OP_ERROR(compose->exteditor_file, "unlink");
8190 } else if (buf[0] == '2') {
8191 g_warning("Couldn't write to file\n");
8192 } else if (buf[0] == '3') {
8193 g_warning("Pipe read failed\n");
8196 compose_set_ext_editor_sensitive(compose, TRUE);
8198 g_free(compose->exteditor_file);
8199 compose->exteditor_file = NULL;
8200 compose->exteditor_pid = -1;
8201 compose->exteditor_ch = NULL;
8202 compose->exteditor_tag = -1;
8207 static void compose_set_ext_editor_sensitive(Compose *compose,
8210 GtkItemFactory *ifactory;
8212 ifactory = gtk_item_factory_from_widget(compose->menubar);
8214 menu_set_sensitive(ifactory, "/Message/Send", sensitive);
8215 menu_set_sensitive(ifactory, "/Message/Send later", sensitive);
8216 menu_set_sensitive(ifactory, "/Message/Insert file", sensitive);
8217 menu_set_sensitive(ifactory, "/Message/Insert signature", sensitive);
8218 menu_set_sensitive(ifactory, "/Edit/Wrap current paragraph", sensitive);
8219 menu_set_sensitive(ifactory, "/Edit/Wrap all long lines", sensitive);
8220 menu_set_sensitive(ifactory, "/Edit/Edit with external editor",
8223 gtk_widget_set_sensitive(compose->text, sensitive);
8224 if (compose->toolbar->send_btn)
8225 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
8226 if (compose->toolbar->sendl_btn)
8227 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
8228 if (compose->toolbar->draft_btn)
8229 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
8230 if (compose->toolbar->insert_btn)
8231 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
8232 if (compose->toolbar->sig_btn)
8233 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
8234 if (compose->toolbar->exteditor_btn)
8235 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
8236 if (compose->toolbar->linewrap_current_btn)
8237 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
8238 if (compose->toolbar->linewrap_all_btn)
8239 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
8241 #endif /* G_OS_UNIX */
8244 * compose_undo_state_changed:
8246 * Change the sensivity of the menuentries undo and redo
8248 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
8249 gint redo_state, gpointer data)
8251 GtkWidget *widget = GTK_WIDGET(data);
8252 GtkItemFactory *ifactory;
8254 g_return_if_fail(widget != NULL);
8256 ifactory = gtk_item_factory_from_widget(widget);
8258 switch (undo_state) {
8259 case UNDO_STATE_TRUE:
8260 if (!undostruct->undo_state) {
8261 undostruct->undo_state = TRUE;
8262 menu_set_sensitive(ifactory, "/Edit/Undo", TRUE);
8265 case UNDO_STATE_FALSE:
8266 if (undostruct->undo_state) {
8267 undostruct->undo_state = FALSE;
8268 menu_set_sensitive(ifactory, "/Edit/Undo", FALSE);
8271 case UNDO_STATE_UNCHANGED:
8273 case UNDO_STATE_REFRESH:
8274 menu_set_sensitive(ifactory, "/Edit/Undo",
8275 undostruct->undo_state);
8278 g_warning("Undo state not recognized");
8282 switch (redo_state) {
8283 case UNDO_STATE_TRUE:
8284 if (!undostruct->redo_state) {
8285 undostruct->redo_state = TRUE;
8286 menu_set_sensitive(ifactory, "/Edit/Redo", TRUE);
8289 case UNDO_STATE_FALSE:
8290 if (undostruct->redo_state) {
8291 undostruct->redo_state = FALSE;
8292 menu_set_sensitive(ifactory, "/Edit/Redo", FALSE);
8295 case UNDO_STATE_UNCHANGED:
8297 case UNDO_STATE_REFRESH:
8298 menu_set_sensitive(ifactory, "/Edit/Redo",
8299 undostruct->redo_state);
8302 g_warning("Redo state not recognized");
8307 /* callback functions */
8309 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
8310 * includes "non-client" (windows-izm) in calculation, so this calculation
8311 * may not be accurate.
8313 static gboolean compose_edit_size_alloc(GtkEditable *widget,
8314 GtkAllocation *allocation,
8315 GtkSHRuler *shruler)
8317 if (prefs_common.show_ruler) {
8318 gint char_width = 0, char_height = 0;
8319 gint line_width_in_chars;
8321 gtkut_get_font_size(GTK_WIDGET(widget),
8322 &char_width, &char_height);
8323 line_width_in_chars =
8324 (allocation->width - allocation->x) / char_width;
8326 /* got the maximum */
8327 gtk_ruler_set_range(GTK_RULER(shruler),
8328 0.0, line_width_in_chars, 0,
8329 /*line_width_in_chars*/ char_width);
8335 static void account_activated(GtkComboBox *optmenu, gpointer data)
8337 Compose *compose = (Compose *)data;
8340 gchar *folderidentifier;
8341 gint account_id = 0;
8345 /* Get ID of active account in the combo box */
8346 menu = gtk_combo_box_get_model(optmenu);
8347 gtk_combo_box_get_active_iter(optmenu, &iter);
8348 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
8350 ac = account_find_from_id(account_id);
8351 g_return_if_fail(ac != NULL);
8353 if (ac != compose->account)
8354 compose_select_account(compose, ac, FALSE);
8356 /* Set message save folder */
8357 if (account_get_special_folder(compose->account, F_OUTBOX)) {
8358 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
8360 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
8361 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
8363 gtk_editable_delete_text(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
8364 if (account_get_special_folder(compose->account, F_OUTBOX)) {
8365 folderidentifier = folder_item_get_identifier(account_get_special_folder
8366 (compose->account, F_OUTBOX));
8367 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
8368 g_free(folderidentifier);
8372 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
8373 GtkTreeViewColumn *column, Compose *compose)
8375 compose_attach_property(compose);
8378 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
8381 Compose *compose = (Compose *)data;
8382 GtkTreeSelection *attach_selection;
8383 gint attach_nr_selected;
8384 GtkItemFactory *ifactory;
8386 if (!event) return FALSE;
8388 if (event->button == 3) {
8389 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
8390 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
8391 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
8393 if (attach_nr_selected > 0)
8395 menu_set_sensitive(ifactory, "/Remove", TRUE);
8396 menu_set_sensitive(ifactory, "/Properties...", TRUE);
8398 menu_set_sensitive(ifactory, "/Remove", FALSE);
8399 menu_set_sensitive(ifactory, "/Properties...", FALSE);
8402 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
8403 NULL, NULL, event->button, event->time);
8410 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
8413 Compose *compose = (Compose *)data;
8415 if (!event) return FALSE;
8417 switch (event->keyval) {
8419 compose_attach_remove_selected(compose);
8425 static void compose_allow_user_actions (Compose *compose, gboolean allow)
8427 GtkItemFactory *ifactory = gtk_item_factory_from_widget(compose->menubar);
8428 toolbar_comp_set_sensitive(compose, allow);
8429 menu_set_sensitive(ifactory, "/Message", allow);
8430 menu_set_sensitive(ifactory, "/Edit", allow);
8432 menu_set_sensitive(ifactory, "/Spelling", allow);
8434 menu_set_sensitive(ifactory, "/Options", allow);
8435 menu_set_sensitive(ifactory, "/Tools", allow);
8436 menu_set_sensitive(ifactory, "/Help", allow);
8438 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
8442 static void compose_send_cb(gpointer data, guint action, GtkWidget *widget)
8444 Compose *compose = (Compose *)data;
8446 if (prefs_common.work_offline &&
8447 !inc_offline_should_override(TRUE,
8448 _("Claws Mail needs network access in order "
8449 "to send this email.")))
8452 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
8453 g_source_remove(compose->draft_timeout_tag);
8454 compose->draft_timeout_tag = -1;
8457 compose_send(compose);
8460 static void compose_send_later_cb(gpointer data, guint action,
8463 Compose *compose = (Compose *)data;
8467 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
8471 compose_close(compose);
8472 } else if (val == -1) {
8473 alertpanel_error(_("Could not queue message."));
8474 } else if (val == -2) {
8475 alertpanel_error(_("Could not queue message:\n\n%s."), strerror(errno));
8476 } else if (val == -3) {
8477 if (privacy_peek_error())
8478 alertpanel_error(_("Could not queue message for sending:\n\n"
8479 "Signature failed: %s"), privacy_get_error());
8480 } else if (val == -4) {
8481 alertpanel_error(_("Could not queue message for sending:\n\n"
8482 "Charset conversion failed."));
8483 } else if (val == -5) {
8484 alertpanel_error(_("Could not queue message for sending:\n\n"
8485 "Couldn't get recipient encryption key."));
8486 } else if (val == -6) {
8489 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
8492 #define DRAFTED_AT_EXIT "drafted_at_exit"
8493 static void compose_register_draft(MsgInfo *info)
8495 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8496 DRAFTED_AT_EXIT, NULL);
8497 FILE *fp = fopen(filepath, "ab");
8500 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
8508 gboolean compose_draft (gpointer data, guint action)
8510 Compose *compose = (Compose *)data;
8514 MsgFlags flag = {0, 0};
8515 static gboolean lock = FALSE;
8516 MsgInfo *newmsginfo;
8518 gboolean target_locked = FALSE;
8519 gboolean err = FALSE;
8521 if (lock) return FALSE;
8523 if (compose->sending)
8526 draft = account_get_special_folder(compose->account, F_DRAFT);
8527 g_return_val_if_fail(draft != NULL, FALSE);
8529 if (!g_mutex_trylock(compose->mutex)) {
8530 /* we don't want to lock the mutex once it's available,
8531 * because as the only other part of compose.c locking
8532 * it is compose_close - which means once unlocked,
8533 * the compose struct will be freed */
8534 debug_print("couldn't lock mutex, probably sending\n");
8540 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
8541 G_DIR_SEPARATOR, compose);
8542 if ((fp = g_fopen(tmp, "wb")) == NULL) {
8543 FILE_OP_ERROR(tmp, "fopen");
8547 /* chmod for security */
8548 if (change_file_mode_rw(fp, tmp) < 0) {
8549 FILE_OP_ERROR(tmp, "chmod");
8550 g_warning("can't change file mode\n");
8553 /* Save draft infos */
8554 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
8555 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
8557 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
8558 gchar *savefolderid;
8560 savefolderid = gtk_editable_get_chars(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
8561 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
8562 g_free(savefolderid);
8564 if (compose->return_receipt) {
8565 err |= (fprintf(fp, "RRCPT:1\n") < 0);
8567 if (compose->privacy_system) {
8568 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
8569 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
8570 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
8573 /* Message-ID of message replying to */
8574 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
8577 folderid = folder_item_get_identifier(compose->replyinfo->folder);
8578 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
8581 /* Message-ID of message forwarding to */
8582 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
8585 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
8586 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
8590 /* end of headers */
8591 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
8598 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
8602 if (fclose(fp) == EOF) {
8606 if (compose->targetinfo) {
8607 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
8608 flag.perm_flags = target_locked?MSG_LOCKED:0;
8610 flag.tmp_flags = MSG_DRAFT;
8612 folder_item_scan(draft);
8613 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
8614 MsgInfo *tmpinfo = NULL;
8615 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
8616 if (compose->msgid) {
8617 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
8620 msgnum = tmpinfo->msgnum;
8621 procmsg_msginfo_free(tmpinfo);
8622 debug_print("got draft msgnum %d from scanning\n", msgnum);
8624 debug_print("didn't get draft msgnum after scanning\n");
8627 debug_print("got draft msgnum %d from adding\n", msgnum);
8633 if (action != COMPOSE_AUTO_SAVE) {
8634 if (action != COMPOSE_DRAFT_FOR_EXIT)
8635 alertpanel_error(_("Could not save draft."));
8638 gtkut_window_popup(compose->window);
8639 val = alertpanel_full(_("Could not save draft"),
8640 _("Could not save draft.\n"
8641 "Do you want to cancel exit or discard this email?"),
8642 _("_Cancel exit"), _("_Discard email"), NULL,
8643 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
8644 if (val == G_ALERTALTERNATE) {
8646 g_mutex_unlock(compose->mutex); /* must be done before closing */
8647 compose_close(compose);
8651 g_mutex_unlock(compose->mutex); /* must be done before closing */
8660 if (compose->mode == COMPOSE_REEDIT) {
8661 compose_remove_reedit_target(compose, TRUE);
8664 newmsginfo = folder_item_get_msginfo(draft, msgnum);
8667 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
8669 procmsg_msginfo_set_flags(newmsginfo, MSG_LOCKED, MSG_DRAFT);
8671 procmsg_msginfo_set_flags(newmsginfo, 0, MSG_DRAFT);
8672 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
8673 procmsg_msginfo_set_flags(newmsginfo, 0,
8674 MSG_HAS_ATTACHMENT);
8676 if (action == COMPOSE_DRAFT_FOR_EXIT) {
8677 compose_register_draft(newmsginfo);
8679 procmsg_msginfo_free(newmsginfo);
8682 folder_item_scan(draft);
8684 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
8686 g_mutex_unlock(compose->mutex); /* must be done before closing */
8687 compose_close(compose);
8693 path = folder_item_fetch_msg(draft, msgnum);
8695 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
8698 if (g_stat(path, &s) < 0) {
8699 FILE_OP_ERROR(path, "stat");
8705 procmsg_msginfo_free(compose->targetinfo);
8706 compose->targetinfo = procmsg_msginfo_new();
8707 compose->targetinfo->msgnum = msgnum;
8708 compose->targetinfo->size = s.st_size;
8709 compose->targetinfo->mtime = s.st_mtime;
8710 compose->targetinfo->folder = draft;
8712 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
8713 compose->mode = COMPOSE_REEDIT;
8715 if (action == COMPOSE_AUTO_SAVE) {
8716 compose->autosaved_draft = compose->targetinfo;
8718 compose->modified = FALSE;
8719 compose_set_title(compose);
8723 g_mutex_unlock(compose->mutex);
8727 void compose_clear_exit_drafts(void)
8729 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8730 DRAFTED_AT_EXIT, NULL);
8731 if (is_file_exist(filepath))
8737 void compose_reopen_exit_drafts(void)
8739 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8740 DRAFTED_AT_EXIT, NULL);
8741 FILE *fp = fopen(filepath, "rb");
8745 while (fgets(buf, sizeof(buf), fp)) {
8746 gchar **parts = g_strsplit(buf, "\t", 2);
8747 const gchar *folder = parts[0];
8748 int msgnum = parts[1] ? atoi(parts[1]):-1;
8750 if (folder && *folder && msgnum > -1) {
8751 FolderItem *item = folder_find_item_from_identifier(folder);
8752 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
8754 compose_reedit(info, FALSE);
8761 compose_clear_exit_drafts();
8764 static void compose_draft_cb(gpointer data, guint action, GtkWidget *widget)
8766 compose_draft(data, action);
8769 static void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
8771 if (compose && file_list) {
8774 for ( tmp = file_list; tmp; tmp = tmp->next) {
8775 gchar *file = (gchar *) tmp->data;
8776 gchar *utf8_filename = conv_filename_to_utf8(file);
8777 compose_attach_append(compose, file, utf8_filename, NULL);
8778 compose_changed_cb(NULL, compose);
8783 g_free(utf8_filename);
8788 static void compose_attach_cb(gpointer data, guint action, GtkWidget *widget)
8790 Compose *compose = (Compose *)data;
8793 if (compose->redirect_filename != NULL)
8796 file_list = filesel_select_multiple_files_open(_("Select file"));
8799 compose_attach_from_list(compose, file_list, TRUE);
8800 g_list_free(file_list);
8804 static void compose_insert_file_cb(gpointer data, guint action,
8807 Compose *compose = (Compose *)data;
8810 file_list = filesel_select_multiple_files_open(_("Select file"));
8815 for ( tmp = file_list; tmp; tmp = tmp->next) {
8816 gchar *file = (gchar *) tmp->data;
8817 gchar *filedup = g_strdup(file);
8818 gchar *shortfile = g_path_get_basename(filedup);
8819 ComposeInsertResult res;
8821 res = compose_insert_file(compose, file);
8822 if (res == COMPOSE_INSERT_READ_ERROR) {
8823 alertpanel_error(_("File '%s' could not be read."), shortfile);
8824 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
8825 alertpanel_error(_("File '%s' contained invalid characters\n"
8826 "for the current encoding, insertion may be incorrect."), shortfile);
8832 g_list_free(file_list);
8836 static void compose_insert_sig_cb(gpointer data, guint action,
8839 Compose *compose = (Compose *)data;
8841 compose_insert_sig(compose, FALSE);
8844 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
8848 Compose *compose = (Compose *)data;
8850 gtkut_widget_get_uposition(widget, &x, &y);
8851 prefs_common.compose_x = x;
8852 prefs_common.compose_y = y;
8854 if (compose->sending || compose->updating)
8856 compose_close_cb(compose, 0, NULL);
8860 void compose_close_toolbar(Compose *compose)
8862 compose_close_cb(compose, 0, NULL);
8865 static void compose_close_cb(gpointer data, guint action, GtkWidget *widget)
8867 Compose *compose = (Compose *)data;
8871 if (compose->exteditor_tag != -1) {
8872 if (!compose_ext_editor_kill(compose))
8877 if (compose->modified) {
8878 val = alertpanel(_("Discard message"),
8879 _("This message has been modified. Discard it?"),
8880 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
8883 case G_ALERTDEFAULT:
8884 if (prefs_common.autosave)
8885 compose_remove_draft(compose);
8887 case G_ALERTALTERNATE:
8888 compose_draft_cb(data, COMPOSE_QUIT_EDITING, NULL);
8895 compose_close(compose);
8898 static void compose_set_encoding_cb(gpointer data, guint action,
8901 Compose *compose = (Compose *)data;
8903 if (GTK_CHECK_MENU_ITEM(widget)->active)
8904 compose->out_encoding = (CharSet)action;
8907 static void compose_address_cb(gpointer data, guint action, GtkWidget *widget)
8909 Compose *compose = (Compose *)data;
8911 addressbook_open(compose);
8914 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
8916 Compose *compose = (Compose *)data;
8921 tmpl = g_object_get_data(G_OBJECT(widget), "template");
8922 g_return_if_fail(tmpl != NULL);
8924 msg = g_strdup_printf(_("Do you want to apply the template '%s' ?"),
8926 val = alertpanel(_("Apply template"), msg,
8927 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
8930 if (val == G_ALERTDEFAULT)
8931 compose_template_apply(compose, tmpl, TRUE);
8932 else if (val == G_ALERTALTERNATE)
8933 compose_template_apply(compose, tmpl, FALSE);
8936 static void compose_ext_editor_cb(gpointer data, guint action,
8939 Compose *compose = (Compose *)data;
8941 compose_exec_ext_editor(compose);
8944 static void compose_undo_cb(Compose *compose)
8946 gboolean prev_autowrap = compose->autowrap;
8948 compose->autowrap = FALSE;
8949 undo_undo(compose->undostruct);
8950 compose->autowrap = prev_autowrap;
8953 static void compose_redo_cb(Compose *compose)
8955 gboolean prev_autowrap = compose->autowrap;
8957 compose->autowrap = FALSE;
8958 undo_redo(compose->undostruct);
8959 compose->autowrap = prev_autowrap;
8962 static void entry_cut_clipboard(GtkWidget *entry)
8964 if (GTK_IS_EDITABLE(entry))
8965 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
8966 else if (GTK_IS_TEXT_VIEW(entry))
8967 gtk_text_buffer_cut_clipboard(
8968 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
8969 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
8973 static void entry_copy_clipboard(GtkWidget *entry)
8975 if (GTK_IS_EDITABLE(entry))
8976 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
8977 else if (GTK_IS_TEXT_VIEW(entry))
8978 gtk_text_buffer_copy_clipboard(
8979 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
8980 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
8983 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
8984 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
8986 if (GTK_IS_TEXT_VIEW(entry)) {
8987 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
8988 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
8989 GtkTextIter start_iter, end_iter;
8991 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
8993 if (contents == NULL)
8996 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
8998 /* we shouldn't delete the selection when middle-click-pasting, or we
8999 * can't mid-click-paste our own selection */
9000 if (clip != GDK_SELECTION_PRIMARY) {
9001 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
9004 if (insert_place == NULL) {
9005 /* if insert_place isn't specified, insert at the cursor.
9006 * used for Ctrl-V pasting */
9007 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
9008 start = gtk_text_iter_get_offset(&start_iter);
9009 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
9011 /* if insert_place is specified, paste here.
9012 * used for mid-click-pasting */
9013 start = gtk_text_iter_get_offset(insert_place);
9014 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
9018 /* paste unwrapped: mark the paste so it's not wrapped later */
9019 end = start + strlen(contents);
9020 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
9021 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
9022 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
9023 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
9024 /* rewrap paragraph now (after a mid-click-paste) */
9025 mark_start = gtk_text_buffer_get_insert(buffer);
9026 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
9027 gtk_text_iter_backward_char(&start_iter);
9028 compose_beautify_paragraph(compose, &start_iter, TRUE);
9030 } else if (GTK_IS_EDITABLE(entry))
9031 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
9035 static void entry_allsel(GtkWidget *entry)
9037 if (GTK_IS_EDITABLE(entry))
9038 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
9039 else if (GTK_IS_TEXT_VIEW(entry)) {
9040 GtkTextIter startiter, enditer;
9041 GtkTextBuffer *textbuf;
9043 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
9044 gtk_text_buffer_get_start_iter(textbuf, &startiter);
9045 gtk_text_buffer_get_end_iter(textbuf, &enditer);
9047 gtk_text_buffer_move_mark_by_name(textbuf,
9048 "selection_bound", &startiter);
9049 gtk_text_buffer_move_mark_by_name(textbuf,
9050 "insert", &enditer);
9054 static void compose_cut_cb(Compose *compose)
9056 if (compose->focused_editable
9058 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9061 entry_cut_clipboard(compose->focused_editable);
9064 static void compose_copy_cb(Compose *compose)
9066 if (compose->focused_editable
9068 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9071 entry_copy_clipboard(compose->focused_editable);
9074 static void compose_paste_cb(Compose *compose)
9077 GtkTextBuffer *buffer;
9079 if (compose->focused_editable &&
9080 GTK_WIDGET_HAS_FOCUS(compose->focused_editable))
9081 entry_paste_clipboard(compose, compose->focused_editable,
9082 prefs_common.linewrap_pastes,
9083 GDK_SELECTION_CLIPBOARD, NULL);
9087 static void compose_paste_as_quote_cb(Compose *compose)
9089 gint wrap_quote = prefs_common.linewrap_quote;
9090 if (compose->focused_editable
9092 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9095 /* let text_insert() (called directly or at a later time
9096 * after the gtk_editable_paste_clipboard) know that
9097 * text is to be inserted as a quotation. implemented
9098 * by using a simple refcount... */
9099 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
9100 G_OBJECT(compose->focused_editable),
9101 "paste_as_quotation"));
9102 g_object_set_data(G_OBJECT(compose->focused_editable),
9103 "paste_as_quotation",
9104 GINT_TO_POINTER(paste_as_quotation + 1));
9105 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
9106 entry_paste_clipboard(compose, compose->focused_editable,
9107 prefs_common.linewrap_pastes,
9108 GDK_SELECTION_CLIPBOARD, NULL);
9109 prefs_common.linewrap_quote = wrap_quote;
9113 static void compose_paste_no_wrap_cb(Compose *compose)
9116 GtkTextBuffer *buffer;
9118 if (compose->focused_editable
9120 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9123 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
9124 GDK_SELECTION_CLIPBOARD, NULL);
9128 static void compose_paste_wrap_cb(Compose *compose)
9131 GtkTextBuffer *buffer;
9133 if (compose->focused_editable
9135 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9138 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
9139 GDK_SELECTION_CLIPBOARD, NULL);
9143 static void compose_allsel_cb(Compose *compose)
9145 if (compose->focused_editable
9147 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9150 entry_allsel(compose->focused_editable);
9153 static void textview_move_beginning_of_line (GtkTextView *text)
9155 GtkTextBuffer *buffer;
9159 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9161 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9162 mark = gtk_text_buffer_get_insert(buffer);
9163 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9164 gtk_text_iter_set_line_offset(&ins, 0);
9165 gtk_text_buffer_place_cursor(buffer, &ins);
9168 static void textview_move_forward_character (GtkTextView *text)
9170 GtkTextBuffer *buffer;
9174 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9176 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9177 mark = gtk_text_buffer_get_insert(buffer);
9178 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9179 if (gtk_text_iter_forward_cursor_position(&ins))
9180 gtk_text_buffer_place_cursor(buffer, &ins);
9183 static void textview_move_backward_character (GtkTextView *text)
9185 GtkTextBuffer *buffer;
9189 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9191 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9192 mark = gtk_text_buffer_get_insert(buffer);
9193 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9194 if (gtk_text_iter_backward_cursor_position(&ins))
9195 gtk_text_buffer_place_cursor(buffer, &ins);
9198 static void textview_move_forward_word (GtkTextView *text)
9200 GtkTextBuffer *buffer;
9205 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9207 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9208 mark = gtk_text_buffer_get_insert(buffer);
9209 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9210 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
9211 if (gtk_text_iter_forward_word_ends(&ins, count)) {
9212 gtk_text_iter_backward_word_start(&ins);
9213 gtk_text_buffer_place_cursor(buffer, &ins);
9217 static void textview_move_backward_word (GtkTextView *text)
9219 GtkTextBuffer *buffer;
9224 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9226 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9227 mark = gtk_text_buffer_get_insert(buffer);
9228 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9229 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
9230 if (gtk_text_iter_backward_word_starts(&ins, 1))
9231 gtk_text_buffer_place_cursor(buffer, &ins);
9234 static void textview_move_end_of_line (GtkTextView *text)
9236 GtkTextBuffer *buffer;
9240 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9242 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9243 mark = gtk_text_buffer_get_insert(buffer);
9244 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9245 if (gtk_text_iter_forward_to_line_end(&ins))
9246 gtk_text_buffer_place_cursor(buffer, &ins);
9249 static void textview_move_next_line (GtkTextView *text)
9251 GtkTextBuffer *buffer;
9256 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9258 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9259 mark = gtk_text_buffer_get_insert(buffer);
9260 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9261 offset = gtk_text_iter_get_line_offset(&ins);
9262 if (gtk_text_iter_forward_line(&ins)) {
9263 gtk_text_iter_set_line_offset(&ins, offset);
9264 gtk_text_buffer_place_cursor(buffer, &ins);
9268 static void textview_move_previous_line (GtkTextView *text)
9270 GtkTextBuffer *buffer;
9275 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9277 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9278 mark = gtk_text_buffer_get_insert(buffer);
9279 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9280 offset = gtk_text_iter_get_line_offset(&ins);
9281 if (gtk_text_iter_backward_line(&ins)) {
9282 gtk_text_iter_set_line_offset(&ins, offset);
9283 gtk_text_buffer_place_cursor(buffer, &ins);
9287 static void textview_delete_forward_character (GtkTextView *text)
9289 GtkTextBuffer *buffer;
9291 GtkTextIter ins, end_iter;
9293 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9295 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9296 mark = gtk_text_buffer_get_insert(buffer);
9297 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9299 if (gtk_text_iter_forward_char(&end_iter)) {
9300 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9304 static void textview_delete_backward_character (GtkTextView *text)
9306 GtkTextBuffer *buffer;
9308 GtkTextIter ins, end_iter;
9310 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9312 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9313 mark = gtk_text_buffer_get_insert(buffer);
9314 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9316 if (gtk_text_iter_backward_char(&end_iter)) {
9317 gtk_text_buffer_delete(buffer, &end_iter, &ins);
9321 static void textview_delete_forward_word (GtkTextView *text)
9323 GtkTextBuffer *buffer;
9325 GtkTextIter ins, end_iter;
9327 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9329 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9330 mark = gtk_text_buffer_get_insert(buffer);
9331 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9333 if (gtk_text_iter_forward_word_end(&end_iter)) {
9334 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9338 static void textview_delete_backward_word (GtkTextView *text)
9340 GtkTextBuffer *buffer;
9342 GtkTextIter ins, end_iter;
9344 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9346 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9347 mark = gtk_text_buffer_get_insert(buffer);
9348 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9350 if (gtk_text_iter_backward_word_start(&end_iter)) {
9351 gtk_text_buffer_delete(buffer, &end_iter, &ins);
9355 static void textview_delete_line (GtkTextView *text)
9357 GtkTextBuffer *buffer;
9359 GtkTextIter ins, start_iter, end_iter;
9362 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9364 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9365 mark = gtk_text_buffer_get_insert(buffer);
9366 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9369 gtk_text_iter_set_line_offset(&start_iter, 0);
9372 if (gtk_text_iter_ends_line(&end_iter))
9373 found = gtk_text_iter_forward_char(&end_iter);
9375 found = gtk_text_iter_forward_to_line_end(&end_iter);
9378 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
9381 static void textview_delete_to_line_end (GtkTextView *text)
9383 GtkTextBuffer *buffer;
9385 GtkTextIter ins, end_iter;
9388 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9390 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9391 mark = gtk_text_buffer_get_insert(buffer);
9392 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9394 if (gtk_text_iter_ends_line(&end_iter))
9395 found = gtk_text_iter_forward_char(&end_iter);
9397 found = gtk_text_iter_forward_to_line_end(&end_iter);
9399 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9402 static void compose_advanced_action_cb(Compose *compose,
9403 ComposeCallAdvancedAction action)
9405 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9407 void (*do_action) (GtkTextView *text);
9408 } action_table[] = {
9409 {textview_move_beginning_of_line},
9410 {textview_move_forward_character},
9411 {textview_move_backward_character},
9412 {textview_move_forward_word},
9413 {textview_move_backward_word},
9414 {textview_move_end_of_line},
9415 {textview_move_next_line},
9416 {textview_move_previous_line},
9417 {textview_delete_forward_character},
9418 {textview_delete_backward_character},
9419 {textview_delete_forward_word},
9420 {textview_delete_backward_word},
9421 {textview_delete_line},
9422 {NULL}, /* gtk_stext_delete_line_n */
9423 {textview_delete_to_line_end}
9426 if (!GTK_WIDGET_HAS_FOCUS(text)) return;
9428 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
9429 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
9430 if (action_table[action].do_action)
9431 action_table[action].do_action(text);
9433 g_warning("Not implemented yet.");
9437 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
9441 if (GTK_IS_EDITABLE(widget)) {
9442 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
9443 gtk_editable_set_position(GTK_EDITABLE(widget),
9446 if (widget->parent && widget->parent->parent
9447 && widget->parent->parent->parent) {
9448 if (GTK_IS_SCROLLED_WINDOW(widget->parent->parent->parent)) {
9449 gint y = widget->allocation.y;
9450 gint height = widget->allocation.height;
9451 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
9452 (GTK_SCROLLED_WINDOW(widget->parent->parent->parent));
9454 if (y < (int)shown->value) {
9455 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown), y - 1);
9457 if (y + height > (int)shown->value + (int)shown->page_size) {
9458 if (y - height - 1 < (int)shown->upper - (int)shown->page_size) {
9459 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
9460 y + height - (int)shown->page_size - 1);
9462 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
9463 (int)shown->upper - (int)shown->page_size - 1);
9470 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
9471 compose->focused_editable = widget;
9474 if (GTK_IS_TEXT_VIEW(widget)
9475 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
9476 gtk_widget_ref(compose->notebook);
9477 gtk_widget_ref(compose->edit_vbox);
9478 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
9479 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
9480 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
9481 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
9482 gtk_widget_unref(compose->notebook);
9483 gtk_widget_unref(compose->edit_vbox);
9484 g_signal_handlers_block_by_func(G_OBJECT(widget),
9485 G_CALLBACK(compose_grab_focus_cb),
9487 gtk_widget_grab_focus(widget);
9488 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
9489 G_CALLBACK(compose_grab_focus_cb),
9491 } else if (!GTK_IS_TEXT_VIEW(widget)
9492 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
9493 gtk_widget_ref(compose->notebook);
9494 gtk_widget_ref(compose->edit_vbox);
9495 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
9496 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
9497 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
9498 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
9499 gtk_widget_unref(compose->notebook);
9500 gtk_widget_unref(compose->edit_vbox);
9501 g_signal_handlers_block_by_func(G_OBJECT(widget),
9502 G_CALLBACK(compose_grab_focus_cb),
9504 gtk_widget_grab_focus(widget);
9505 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
9506 G_CALLBACK(compose_grab_focus_cb),
9512 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
9514 compose->modified = TRUE;
9516 compose_set_title(compose);
9520 static void compose_wrap_cb(gpointer data, guint action, GtkWidget *widget)
9522 Compose *compose = (Compose *)data;
9525 compose_wrap_all_full(compose, TRUE);
9527 compose_beautify_paragraph(compose, NULL, TRUE);
9530 static void compose_find_cb(gpointer data, guint action, GtkWidget *widget)
9532 Compose *compose = (Compose *)data;
9534 message_search_compose(compose);
9537 static void compose_toggle_autowrap_cb(gpointer data, guint action,
9540 Compose *compose = (Compose *)data;
9541 compose->autowrap = GTK_CHECK_MENU_ITEM(widget)->active;
9542 if (compose->autowrap)
9543 compose_wrap_all_full(compose, TRUE);
9544 compose->autowrap = GTK_CHECK_MENU_ITEM(widget)->active;
9547 static void compose_toggle_sign_cb(gpointer data, guint action,
9550 Compose *compose = (Compose *)data;
9552 if (GTK_CHECK_MENU_ITEM(widget)->active)
9553 compose->use_signing = TRUE;
9555 compose->use_signing = FALSE;
9558 static void compose_toggle_encrypt_cb(gpointer data, guint action,
9561 Compose *compose = (Compose *)data;
9563 if (GTK_CHECK_MENU_ITEM(widget)->active)
9564 compose->use_encryption = TRUE;
9566 compose->use_encryption = FALSE;
9569 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
9571 g_free(compose->privacy_system);
9573 compose->privacy_system = g_strdup(account->default_privacy_system);
9574 compose_update_privacy_system_menu_item(compose, warn);
9577 static void compose_toggle_ruler_cb(gpointer data, guint action,
9580 Compose *compose = (Compose *)data;
9582 if (GTK_CHECK_MENU_ITEM(widget)->active) {
9583 gtk_widget_show(compose->ruler_hbox);
9584 prefs_common.show_ruler = TRUE;
9586 gtk_widget_hide(compose->ruler_hbox);
9587 gtk_widget_queue_resize(compose->edit_vbox);
9588 prefs_common.show_ruler = FALSE;
9592 static void compose_attach_drag_received_cb (GtkWidget *widget,
9593 GdkDragContext *context,
9596 GtkSelectionData *data,
9601 Compose *compose = (Compose *)user_data;
9604 if (gdk_atom_name(data->type) &&
9605 !strcmp(gdk_atom_name(data->type), "text/uri-list")
9606 && gtk_drag_get_source_widget(context) !=
9607 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
9608 list = uri_list_extract_filenames((const gchar *)data->data);
9609 for (tmp = list; tmp != NULL; tmp = tmp->next) {
9610 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
9611 compose_attach_append
9612 (compose, (const gchar *)tmp->data,
9613 utf8_filename, NULL);
9614 g_free(utf8_filename);
9616 if (list) compose_changed_cb(NULL, compose);
9617 list_free_strings(list);
9619 } else if (gtk_drag_get_source_widget(context)
9620 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
9621 /* comes from our summaryview */
9622 SummaryView * summaryview = NULL;
9623 GSList * list = NULL, *cur = NULL;
9625 if (mainwindow_get_mainwindow())
9626 summaryview = mainwindow_get_mainwindow()->summaryview;
9629 list = summary_get_selected_msg_list(summaryview);
9631 for (cur = list; cur; cur = cur->next) {
9632 MsgInfo *msginfo = (MsgInfo *)cur->data;
9635 file = procmsg_get_message_file_full(msginfo,
9638 compose_attach_append(compose, (const gchar *)file,
9639 (const gchar *)file, "message/rfc822");
9647 static gboolean compose_drag_drop(GtkWidget *widget,
9648 GdkDragContext *drag_context,
9650 guint time, gpointer user_data)
9652 /* not handling this signal makes compose_insert_drag_received_cb
9657 static void compose_insert_drag_received_cb (GtkWidget *widget,
9658 GdkDragContext *drag_context,
9661 GtkSelectionData *data,
9666 Compose *compose = (Compose *)user_data;
9669 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
9671 if (gdk_atom_name(data->type) && !strcmp(gdk_atom_name(data->type), "text/uri-list")) {
9672 AlertValue val = G_ALERTDEFAULT;
9674 switch (prefs_common.compose_dnd_mode) {
9675 case COMPOSE_DND_ASK:
9676 val = alertpanel_full(_("Insert or attach?"),
9677 _("Do you want to insert the contents of the file(s) "
9678 "into the message body, or attach it to the email?"),
9679 GTK_STOCK_CANCEL, _("+_Insert"), _("_Attach"),
9680 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
9682 case COMPOSE_DND_INSERT:
9683 val = G_ALERTALTERNATE;
9685 case COMPOSE_DND_ATTACH:
9689 /* unexpected case */
9690 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
9693 if (val & G_ALERTDISABLE) {
9694 val &= ~G_ALERTDISABLE;
9695 /* remember what action to perform by default, only if we don't click Cancel */
9696 if (val == G_ALERTALTERNATE)
9697 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
9698 else if (val == G_ALERTOTHER)
9699 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
9702 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
9703 gtk_drag_finish(drag_context, FALSE, FALSE, time);
9705 } else if (val == G_ALERTOTHER) {
9706 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
9709 list = uri_list_extract_filenames((const gchar *)data->data);
9710 for (tmp = list; tmp != NULL; tmp = tmp->next) {
9711 compose_insert_file(compose, (const gchar *)tmp->data);
9713 list_free_strings(list);
9715 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9718 #if GTK_CHECK_VERSION(2, 8, 0)
9719 /* do nothing, handled by GTK */
9721 gchar *tmpfile = get_tmp_file();
9722 str_write_to_file((const gchar *)data->data, tmpfile);
9723 compose_insert_file(compose, tmpfile);
9726 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9730 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9733 static void compose_header_drag_received_cb (GtkWidget *widget,
9734 GdkDragContext *drag_context,
9737 GtkSelectionData *data,
9742 GtkEditable *entry = (GtkEditable *)user_data;
9743 gchar *email = (gchar *)data->data;
9745 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
9748 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
9749 gchar *decoded=g_new(gchar, strlen(email));
9752 email += strlen("mailto:");
9753 decode_uri(decoded, email); /* will fit */
9754 gtk_editable_delete_text(entry, 0, -1);
9755 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
9756 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9760 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9763 static void compose_toggle_return_receipt_cb(gpointer data, guint action,
9766 Compose *compose = (Compose *)data;
9768 if (GTK_CHECK_MENU_ITEM(widget)->active)
9769 compose->return_receipt = TRUE;
9771 compose->return_receipt = FALSE;
9774 static void compose_toggle_remove_refs_cb(gpointer data, guint action,
9777 Compose *compose = (Compose *)data;
9779 if (GTK_CHECK_MENU_ITEM(widget)->active)
9780 compose->remove_references = TRUE;
9782 compose->remove_references = FALSE;
9785 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
9787 ComposeHeaderEntry *headerentry)
9789 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
9790 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
9791 !(event->state & GDK_MODIFIER_MASK) &&
9792 (event->keyval == GDK_BackSpace) &&
9793 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
9794 gtk_container_remove
9795 (GTK_CONTAINER(headerentry->compose->header_table),
9796 headerentry->combo);
9797 gtk_container_remove
9798 (GTK_CONTAINER(headerentry->compose->header_table),
9799 headerentry->entry);
9800 headerentry->compose->header_list =
9801 g_slist_remove(headerentry->compose->header_list,
9803 g_free(headerentry);
9804 } else if (event->keyval == GDK_Tab) {
9805 if (headerentry->compose->header_last == headerentry) {
9806 /* Override default next focus, and give it to subject_entry
9807 * instead of notebook tabs
9809 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
9810 gtk_widget_grab_focus(headerentry->compose->subject_entry);
9817 static gboolean compose_headerentry_changed_cb(GtkWidget *entry,
9818 ComposeHeaderEntry *headerentry)
9820 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
9821 compose_create_header_entry(headerentry->compose);
9822 g_signal_handlers_disconnect_matched
9823 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
9824 0, 0, NULL, NULL, headerentry);
9826 /* Automatically scroll down */
9827 compose_show_first_last_header(headerentry->compose, FALSE);
9833 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
9835 GtkAdjustment *vadj;
9837 g_return_if_fail(compose);
9838 g_return_if_fail(GTK_IS_WIDGET(compose->header_table));
9839 g_return_if_fail(GTK_IS_VIEWPORT(compose->header_table->parent));
9841 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(compose->header_table->parent));
9842 gtk_adjustment_set_value(vadj, (show_first ? vadj->lower : vadj->upper));
9843 gtk_adjustment_changed(vadj);
9846 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
9847 const gchar *text, gint len, Compose *compose)
9849 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
9850 (G_OBJECT(compose->text), "paste_as_quotation"));
9853 g_return_if_fail(text != NULL);
9855 g_signal_handlers_block_by_func(G_OBJECT(buffer),
9856 G_CALLBACK(text_inserted),
9858 if (paste_as_quotation) {
9862 GtkTextIter start_iter;
9867 new_text = g_strndup(text, len);
9869 qmark = compose_quote_char_from_context(compose);
9871 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
9872 gtk_text_buffer_place_cursor(buffer, iter);
9874 pos = gtk_text_iter_get_offset(iter);
9876 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
9877 _("Quote format error at line %d."));
9878 quote_fmt_reset_vartable();
9880 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
9881 GINT_TO_POINTER(paste_as_quotation - 1));
9883 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
9884 gtk_text_buffer_place_cursor(buffer, iter);
9885 gtk_text_buffer_delete_mark(buffer, mark);
9887 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
9888 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
9889 compose_beautify_paragraph(compose, &start_iter, FALSE);
9890 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
9891 gtk_text_buffer_delete_mark(buffer, mark);
9893 if (strcmp(text, "\n") || compose->automatic_break
9894 || gtk_text_iter_starts_line(iter))
9895 gtk_text_buffer_insert(buffer, iter, text, len);
9897 debug_print("insert nowrap \\n\n");
9898 gtk_text_buffer_insert_with_tags_by_name(buffer,
9899 iter, text, len, "no_join", NULL);
9903 if (!paste_as_quotation) {
9904 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
9905 compose_beautify_paragraph(compose, iter, FALSE);
9906 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
9907 gtk_text_buffer_delete_mark(buffer, mark);
9910 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
9911 G_CALLBACK(text_inserted),
9913 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
9915 if (prefs_common.autosave &&
9916 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
9917 compose->draft_timeout_tag != -2 /* disabled while loading */)
9918 compose->draft_timeout_tag = g_timeout_add
9919 (500, (GtkFunction) compose_defer_auto_save_draft, compose);
9921 static gint compose_defer_auto_save_draft(Compose *compose)
9923 compose->draft_timeout_tag = -1;
9924 compose_draft_cb((gpointer)compose, COMPOSE_AUTO_SAVE, NULL);
9929 static void compose_check_all(Compose *compose)
9931 if (compose->gtkaspell)
9932 gtkaspell_check_all(compose->gtkaspell);
9935 static void compose_highlight_all(Compose *compose)
9937 if (compose->gtkaspell)
9938 gtkaspell_highlight_all(compose->gtkaspell);
9941 static void compose_check_backwards(Compose *compose)
9943 if (compose->gtkaspell)
9944 gtkaspell_check_backwards(compose->gtkaspell);
9946 GtkItemFactory *ifactory;
9947 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
9948 menu_set_sensitive(ifactory, "/Edit/Check backwards misspelled word", FALSE);
9949 menu_set_sensitive(ifactory, "/Edit/Forward to next misspelled word", FALSE);
9953 static void compose_check_forwards_go(Compose *compose)
9955 if (compose->gtkaspell)
9956 gtkaspell_check_forwards_go(compose->gtkaspell);
9958 GtkItemFactory *ifactory;
9959 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
9960 menu_set_sensitive(ifactory, "/Edit/Check backwards misspelled word", FALSE);
9961 menu_set_sensitive(ifactory, "/Edit/Forward to next misspelled word", FALSE);
9967 *\brief Guess originating forward account from MsgInfo and several
9968 * "common preference" settings. Return NULL if no guess.
9970 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
9972 PrefsAccount *account = NULL;
9974 g_return_val_if_fail(msginfo, NULL);
9975 g_return_val_if_fail(msginfo->folder, NULL);
9976 g_return_val_if_fail(msginfo->folder->prefs, NULL);
9978 if (msginfo->folder->prefs->enable_default_account)
9979 account = account_find_from_id(msginfo->folder->prefs->default_account);
9982 account = msginfo->folder->folder->account;
9984 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
9986 Xstrdup_a(to, msginfo->to, return NULL);
9987 extract_address(to);
9988 account = account_find_from_address(to);
9991 if (!account && prefs_common.forward_account_autosel) {
9993 if (!procheader_get_header_from_msginfo
9994 (msginfo, cc,sizeof cc , "Cc:")) {
9995 gchar *buf = cc + strlen("Cc:");
9996 extract_address(buf);
9997 account = account_find_from_address(buf);
10001 if (!account && prefs_common.forward_account_autosel) {
10002 gchar deliveredto[BUFFSIZE];
10003 if (!procheader_get_header_from_msginfo
10004 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
10005 gchar *buf = deliveredto + strlen("Delivered-To:");
10006 extract_address(buf);
10007 account = account_find_from_address(buf);
10014 gboolean compose_close(Compose *compose)
10018 if (!g_mutex_trylock(compose->mutex)) {
10019 /* we have to wait for the (possibly deferred by auto-save)
10020 * drafting to be done, before destroying the compose under
10022 debug_print("waiting for drafting to finish...\n");
10023 compose_allow_user_actions(compose, FALSE);
10024 g_timeout_add (500, (GSourceFunc) compose_close, compose);
10027 g_return_val_if_fail(compose, FALSE);
10028 gtkut_widget_get_uposition(compose->window, &x, &y);
10029 prefs_common.compose_x = x;
10030 prefs_common.compose_y = y;
10031 g_mutex_unlock(compose->mutex);
10032 compose_destroy(compose);
10037 * Add entry field for each address in list.
10038 * \param compose E-Mail composition object.
10039 * \param listAddress List of (formatted) E-Mail addresses.
10041 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
10044 node = listAddress;
10046 addr = ( gchar * ) node->data;
10047 compose_entry_append( compose, addr, COMPOSE_TO );
10048 node = g_list_next( node );
10052 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
10053 guint action, gboolean opening_multiple)
10055 gchar *body = NULL;
10056 GSList *new_msglist = NULL;
10057 MsgInfo *tmp_msginfo = NULL;
10058 gboolean originally_enc = FALSE;
10059 Compose *compose = NULL;
10061 g_return_if_fail(msgview != NULL);
10063 g_return_if_fail(msginfo_list != NULL);
10065 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
10066 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
10067 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
10069 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
10070 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
10071 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
10072 orig_msginfo, mimeinfo);
10073 if (tmp_msginfo != NULL) {
10074 new_msglist = g_slist_append(NULL, tmp_msginfo);
10076 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
10077 tmp_msginfo->folder = orig_msginfo->folder;
10078 tmp_msginfo->msgnum = orig_msginfo->msgnum;
10079 if (orig_msginfo->tags)
10080 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
10085 if (!opening_multiple)
10086 body = messageview_get_selection(msgview);
10089 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
10090 procmsg_msginfo_free(tmp_msginfo);
10091 g_slist_free(new_msglist);
10093 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
10095 if (compose && originally_enc) {
10096 compose_force_encryption(compose, compose->account, FALSE);
10102 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
10105 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
10106 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
10107 GSList *cur = msginfo_list;
10108 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
10109 "messages. Opening the windows "
10110 "could take some time. Do you "
10111 "want to continue?"),
10112 g_slist_length(msginfo_list));
10113 if (g_slist_length(msginfo_list) > 9
10114 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
10115 != G_ALERTALTERNATE) {
10120 /* We'll open multiple compose windows */
10121 /* let the WM place the next windows */
10122 compose_force_window_origin = FALSE;
10123 for (; cur; cur = cur->next) {
10125 tmplist.data = cur->data;
10126 tmplist.next = NULL;
10127 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
10129 compose_force_window_origin = TRUE;
10131 /* forwarding multiple mails as attachments is done via a
10132 * single compose window */
10133 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
10137 void compose_set_position(Compose *compose, gint pos)
10139 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10141 gtkut_text_view_set_position(text, pos);
10144 gboolean compose_search_string(Compose *compose,
10145 const gchar *str, gboolean case_sens)
10147 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10149 return gtkut_text_view_search_string(text, str, case_sens);
10152 gboolean compose_search_string_backward(Compose *compose,
10153 const gchar *str, gboolean case_sens)
10155 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10157 return gtkut_text_view_search_string_backward(text, str, case_sens);
10160 /* allocate a msginfo structure and populate its data from a compose data structure */
10161 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
10163 MsgInfo *newmsginfo;
10165 gchar buf[BUFFSIZE];
10167 g_return_val_if_fail( compose != NULL, NULL );
10169 newmsginfo = procmsg_msginfo_new();
10172 get_rfc822_date(buf, sizeof(buf));
10173 newmsginfo->date = g_strdup(buf);
10176 if (compose->from_name) {
10177 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
10178 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
10182 if (compose->subject_entry)
10183 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
10185 /* to, cc, reply-to, newsgroups */
10186 for (list = compose->header_list; list; list = list->next) {
10187 gchar *header = gtk_editable_get_chars(
10189 GTK_BIN(((ComposeHeaderEntry *)list->data)->combo)->child), 0, -1);
10190 gchar *entry = gtk_editable_get_chars(
10191 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
10193 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
10194 if ( newmsginfo->to == NULL ) {
10195 newmsginfo->to = g_strdup(entry);
10196 } else if (entry && *entry) {
10197 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
10198 g_free(newmsginfo->to);
10199 newmsginfo->to = tmp;
10202 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
10203 if ( newmsginfo->cc == NULL ) {
10204 newmsginfo->cc = g_strdup(entry);
10205 } else if (entry && *entry) {
10206 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
10207 g_free(newmsginfo->cc);
10208 newmsginfo->cc = tmp;
10211 if ( strcasecmp(header,
10212 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
10213 if ( newmsginfo->newsgroups == NULL ) {
10214 newmsginfo->newsgroups = g_strdup(entry);
10215 } else if (entry && *entry) {
10216 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
10217 g_free(newmsginfo->newsgroups);
10218 newmsginfo->newsgroups = tmp;
10226 /* other data is unset */
10232 /* update compose's dictionaries from folder dict settings */
10233 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
10234 FolderItem *folder_item)
10236 g_return_if_fail(compose != NULL);
10238 if (compose->gtkaspell && folder_item && folder_item->prefs) {
10239 FolderItemPrefs *prefs = folder_item->prefs;
10241 if (prefs->enable_default_dictionary)
10242 gtkaspell_change_dict(compose->gtkaspell,
10243 prefs->default_dictionary, FALSE);
10244 if (folder_item->prefs->enable_default_alt_dictionary)
10245 gtkaspell_change_alt_dict(compose->gtkaspell,
10246 prefs->default_alt_dictionary);
10247 if (prefs->enable_default_dictionary
10248 || prefs->enable_default_alt_dictionary)
10249 compose_spell_menu_changed(compose);