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();
6038 gtk_size_group_add_widget(compose->size_group, combo);
6040 while(*string != NULL) {
6041 gtk_combo_box_append_text(GTK_COMBO_BOX(combo),
6042 (gchar*)prefs_common_translated_header_name(*string));
6045 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
6046 g_signal_connect(G_OBJECT(GTK_BIN(combo)->child), "grab_focus",
6047 G_CALLBACK(compose_grab_focus_cb), compose);
6048 gtk_widget_show(combo);
6049 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
6050 compose->header_nextrow, compose->header_nextrow+1,
6051 GTK_SHRINK, GTK_FILL, 0, 0);
6052 if (compose->header_last) {
6053 const gchar *last_header_entry = gtk_entry_get_text(
6054 GTK_ENTRY(GTK_BIN(compose->header_last->combo)->child));
6056 while (*string != NULL) {
6057 if (!strcmp(*string, last_header_entry))
6058 standard_header = TRUE;
6061 if (standard_header)
6062 header = gtk_entry_get_text(GTK_ENTRY(GTK_BIN(compose->header_last->combo)->child));
6064 if (!compose->header_last || !standard_header) {
6065 switch(compose->account->protocol) {
6067 header = prefs_common_translated_header_name("Newsgroups:");
6070 header = prefs_common_translated_header_name("To:");
6075 gtk_entry_set_text(GTK_ENTRY(GTK_BIN(combo)->child), header);
6077 g_signal_connect_after(G_OBJECT(GTK_BIN(combo)->child), "grab_focus",
6078 G_CALLBACK(compose_grab_focus_cb), compose);
6081 entry = gtk_entry_new();
6082 gtk_widget_show(entry);
6083 gtk_tooltips_set_tip(compose->tooltips, entry,
6084 _("Use <tab> to autocomplete from addressbook"), NULL);
6085 gtk_table_attach(GTK_TABLE(compose->header_table), entry, 1, 2,
6086 compose->header_nextrow, compose->header_nextrow+1,
6087 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
6089 g_signal_connect(G_OBJECT(entry), "key-press-event",
6090 G_CALLBACK(compose_headerentry_key_press_event_cb),
6092 g_signal_connect(G_OBJECT(entry), "changed",
6093 G_CALLBACK(compose_headerentry_changed_cb),
6095 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
6096 G_CALLBACK(compose_grab_focus_cb), compose);
6099 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6100 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6101 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6102 g_signal_connect(G_OBJECT(entry), "drag_data_received",
6103 G_CALLBACK(compose_header_drag_received_cb),
6105 g_signal_connect(G_OBJECT(entry), "drag-drop",
6106 G_CALLBACK(compose_drag_drop),
6108 g_signal_connect(G_OBJECT(entry), "populate-popup",
6109 G_CALLBACK(compose_entry_popup_extend),
6112 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
6114 headerentry->compose = compose;
6115 headerentry->combo = combo;
6116 headerentry->entry = entry;
6117 headerentry->headernum = compose->header_nextrow;
6119 compose->header_nextrow++;
6120 compose->header_last = headerentry;
6121 compose->header_list =
6122 g_slist_append(compose->header_list,
6126 static void compose_add_header_entry(Compose *compose, const gchar *header, gchar *text)
6128 ComposeHeaderEntry *last_header;
6130 last_header = compose->header_last;
6132 gtk_entry_set_text(GTK_ENTRY(GTK_BIN(last_header->combo)->child), header);
6133 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
6136 static void compose_remove_header_entries(Compose *compose)
6139 for (list = compose->header_list; list; list = list->next) {
6140 ComposeHeaderEntry *headerentry =
6141 (ComposeHeaderEntry *)list->data;
6142 gtk_widget_destroy(headerentry->combo);
6143 gtk_widget_destroy(headerentry->entry);
6144 g_free(headerentry);
6146 compose->header_last = NULL;
6147 g_slist_free(compose->header_list);
6148 compose->header_list = NULL;
6149 compose->header_nextrow = 1;
6150 compose_create_header_entry(compose);
6153 static GtkWidget *compose_create_header(Compose *compose)
6155 GtkWidget *from_optmenu_hbox;
6156 GtkWidget *header_scrolledwin;
6157 GtkWidget *header_table;
6161 compose->size_group = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
6163 /* header labels and entries */
6164 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
6165 gtk_widget_show(header_scrolledwin);
6166 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
6168 header_table = gtk_table_new(2, 2, FALSE);
6169 gtk_widget_show(header_table);
6170 gtk_container_set_border_width(GTK_CONTAINER(header_table), BORDER_WIDTH);
6171 gtk_table_set_col_spacings(GTK_TABLE(header_table), 6);
6172 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
6173 gtk_viewport_set_shadow_type(GTK_VIEWPORT(GTK_BIN(header_scrolledwin)->child), GTK_SHADOW_NONE);
6176 /* option menu for selecting accounts */
6177 from_optmenu_hbox = compose_account_option_menu_create(compose);
6178 gtk_table_attach(GTK_TABLE(header_table), from_optmenu_hbox,
6179 0, 2, count, count + 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
6182 compose->header_table = header_table;
6183 compose->header_list = NULL;
6184 compose->header_nextrow = count;
6186 compose_create_header_entry(compose);
6188 compose->table = NULL;
6190 return header_scrolledwin ;
6193 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
6195 Compose *compose = (Compose *)data;
6196 GdkEventButton event;
6199 event.time = gtk_get_current_event_time();
6201 return attach_button_pressed(compose->attach_clist, &event, compose);
6204 static GtkWidget *compose_create_attach(Compose *compose)
6206 GtkWidget *attach_scrwin;
6207 GtkWidget *attach_clist;
6209 GtkListStore *store;
6210 GtkCellRenderer *renderer;
6211 GtkTreeViewColumn *column;
6212 GtkTreeSelection *selection;
6214 /* attachment list */
6215 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
6216 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
6217 GTK_POLICY_AUTOMATIC,
6218 GTK_POLICY_AUTOMATIC);
6219 gtk_widget_set_size_request(attach_scrwin, -1, 80);
6221 store = gtk_list_store_new(N_ATTACH_COLS,
6226 G_TYPE_AUTO_POINTER,
6228 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
6229 (GTK_TREE_MODEL(store)));
6230 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
6231 g_object_unref(store);
6233 renderer = gtk_cell_renderer_text_new();
6234 column = gtk_tree_view_column_new_with_attributes
6235 (_("Mime type"), renderer, "text",
6236 COL_MIMETYPE, NULL);
6237 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6239 renderer = gtk_cell_renderer_text_new();
6240 column = gtk_tree_view_column_new_with_attributes
6241 (_("Size"), renderer, "text",
6243 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6245 renderer = gtk_cell_renderer_text_new();
6246 column = gtk_tree_view_column_new_with_attributes
6247 (_("Name"), renderer, "text",
6249 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6251 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
6252 prefs_common.use_stripes_everywhere);
6253 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
6254 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
6256 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
6257 G_CALLBACK(attach_selected), compose);
6258 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
6259 G_CALLBACK(attach_button_pressed), compose);
6261 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
6262 G_CALLBACK(popup_attach_button_pressed), compose);
6264 gtk_widget_tap_and_hold_setup(GTK_WIDGET(attach_clist), NULL, NULL,
6265 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
6266 g_signal_connect(G_OBJECT(attach_clist), "tap-and-hold",
6267 G_CALLBACK(popup_attach_button_pressed), compose);
6269 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
6270 G_CALLBACK(attach_key_pressed), compose);
6273 gtk_drag_dest_set(attach_clist,
6274 GTK_DEST_DEFAULT_ALL, compose_mime_types,
6275 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6276 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6277 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
6278 G_CALLBACK(compose_attach_drag_received_cb),
6280 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
6281 G_CALLBACK(compose_drag_drop),
6284 compose->attach_scrwin = attach_scrwin;
6285 compose->attach_clist = attach_clist;
6287 return attach_scrwin;
6290 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
6291 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
6293 static GtkWidget *compose_create_others(Compose *compose)
6296 GtkWidget *savemsg_checkbtn;
6297 GtkWidget *savemsg_entry;
6298 GtkWidget *savemsg_select;
6301 gchar *folderidentifier;
6303 /* Table for settings */
6304 table = gtk_table_new(3, 1, FALSE);
6305 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
6306 gtk_widget_show(table);
6307 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
6310 /* Save Message to folder */
6311 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
6312 gtk_widget_show(savemsg_checkbtn);
6313 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6314 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6315 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
6317 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
6318 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
6320 savemsg_entry = gtk_entry_new();
6321 gtk_widget_show(savemsg_entry);
6322 gtk_table_attach_defaults(GTK_TABLE(table), savemsg_entry, 1, 2, rowcount, rowcount + 1);
6323 gtk_editable_set_editable(GTK_EDITABLE(savemsg_entry), prefs_common.savemsg);
6324 g_signal_connect_after(G_OBJECT(savemsg_entry), "grab_focus",
6325 G_CALLBACK(compose_grab_focus_cb), compose);
6326 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6327 folderidentifier = folder_item_get_identifier(account_get_special_folder
6328 (compose->account, F_OUTBOX));
6329 gtk_entry_set_text(GTK_ENTRY(savemsg_entry), folderidentifier);
6330 g_free(folderidentifier);
6333 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
6334 gtk_widget_show(savemsg_select);
6335 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6336 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
6337 G_CALLBACK(compose_savemsg_select_cb),
6342 compose->savemsg_checkbtn = savemsg_checkbtn;
6343 compose->savemsg_entry = savemsg_entry;
6348 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
6350 gtk_editable_set_editable(GTK_EDITABLE(compose->savemsg_entry),
6351 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
6354 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
6359 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL);
6362 path = folder_item_get_identifier(dest);
6364 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), path);
6368 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
6369 GdkAtom clip, GtkTextIter *insert_place);
6372 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
6376 GtkTextBuffer *buffer;
6378 if (event->button == 3) {
6380 GtkTextIter sel_start, sel_end;
6381 gboolean stuff_selected;
6383 /* move the cursor to allow GtkAspell to check the word
6384 * under the mouse */
6385 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6386 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6388 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6391 stuff_selected = gtk_text_buffer_get_selection_bounds(
6392 GTK_TEXT_VIEW(text)->buffer,
6393 &sel_start, &sel_end);
6395 gtk_text_buffer_place_cursor (GTK_TEXT_VIEW(text)->buffer, &iter);
6396 /* reselect stuff */
6398 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
6399 gtk_text_buffer_select_range(GTK_TEXT_VIEW(text)->buffer,
6400 &sel_start, &sel_end);
6402 return FALSE; /* pass the event so that the right-click goes through */
6405 if (event->button == 2) {
6410 /* get the middle-click position to paste at the correct place */
6411 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6412 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6414 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6417 entry_paste_clipboard(compose, text,
6418 prefs_common.linewrap_pastes,
6419 GDK_SELECTION_PRIMARY, &iter);
6427 static void compose_spell_menu_changed(void *data)
6429 Compose *compose = (Compose *)data;
6431 GtkWidget *menuitem;
6432 GtkWidget *parent_item;
6433 GtkMenu *menu = GTK_MENU(gtk_menu_new());
6434 GtkItemFactory *ifactory = gtk_item_factory_from_widget(compose->menubar);
6437 if (compose->gtkaspell == NULL)
6440 parent_item = gtk_item_factory_get_item(ifactory,
6441 "/Spelling/Options");
6443 /* setting the submenu removes /Spelling/Options from the factory
6444 * so we need to save it */
6446 if (parent_item == NULL) {
6447 parent_item = compose->aspell_options_menu;
6448 gtk_menu_item_remove_submenu(GTK_MENU_ITEM(parent_item));
6450 compose->aspell_options_menu = parent_item;
6452 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
6454 spell_menu = g_slist_reverse(spell_menu);
6455 for (items = spell_menu;
6456 items; items = items->next) {
6457 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
6458 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
6459 gtk_widget_show(GTK_WIDGET(menuitem));
6461 g_slist_free(spell_menu);
6463 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
6468 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
6470 Compose *compose = (Compose *)data;
6471 GdkEventButton event;
6474 event.time = gtk_get_current_event_time();
6476 return text_clicked(compose->text, &event, compose);
6479 static gboolean compose_force_window_origin = TRUE;
6480 static Compose *compose_create(PrefsAccount *account,
6489 GtkWidget *handlebox;
6491 GtkWidget *notebook;
6493 GtkWidget *attach_hbox;
6494 GtkWidget *attach_lab1;
6495 GtkWidget *attach_lab2;
6500 GtkWidget *subject_hbox;
6501 GtkWidget *subject_frame;
6502 GtkWidget *subject_entry;
6506 GtkWidget *edit_vbox;
6507 GtkWidget *ruler_hbox;
6509 GtkWidget *scrolledwin;
6511 GtkTextBuffer *buffer;
6512 GtkClipboard *clipboard;
6514 UndoMain *undostruct;
6516 gchar *titles[N_ATTACH_COLS];
6517 guint n_menu_entries;
6518 GtkWidget *popupmenu;
6519 GtkItemFactory *popupfactory;
6520 GtkItemFactory *ifactory;
6521 GtkWidget *tmpl_menu;
6523 GtkWidget *menuitem;
6526 GtkAspell * gtkaspell = NULL;
6529 static GdkGeometry geometry;
6531 g_return_val_if_fail(account != NULL, NULL);
6533 debug_print("Creating compose window...\n");
6534 compose = g_new0(Compose, 1);
6536 titles[COL_MIMETYPE] = _("MIME type");
6537 titles[COL_SIZE] = _("Size");
6538 titles[COL_NAME] = _("Name");
6540 compose->batch = batch;
6541 compose->account = account;
6542 compose->folder = folder;
6544 compose->mutex = g_mutex_new();
6545 compose->set_cursor_pos = -1;
6547 compose->tooltips = gtk_tooltips_new();
6549 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
6551 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
6552 gtk_widget_set_size_request(window, -1, prefs_common.compose_height);
6554 if (!geometry.max_width) {
6555 geometry.max_width = gdk_screen_width();
6556 geometry.max_height = gdk_screen_height();
6559 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
6560 &geometry, GDK_HINT_MAX_SIZE);
6561 if (!geometry.min_width) {
6562 geometry.min_width = 600;
6563 geometry.min_height = 480;
6565 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
6566 &geometry, GDK_HINT_MIN_SIZE);
6569 if (compose_force_window_origin)
6570 gtk_widget_set_uposition(window, prefs_common.compose_x,
6571 prefs_common.compose_y);
6573 g_signal_connect(G_OBJECT(window), "delete_event",
6574 G_CALLBACK(compose_delete_cb), compose);
6575 MANAGE_WINDOW_SIGNALS_CONNECT(window);
6576 gtk_widget_realize(window);
6578 gtkut_widget_set_composer_icon(window);
6580 vbox = gtk_vbox_new(FALSE, 0);
6581 gtk_container_add(GTK_CONTAINER(window), vbox);
6583 n_menu_entries = sizeof(compose_entries) / sizeof(compose_entries[0]);
6584 menubar = menubar_create(window, compose_entries,
6585 n_menu_entries, "<Compose>", compose);
6586 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
6588 if (prefs_common.toolbar_detachable) {
6589 handlebox = gtk_handle_box_new();
6591 handlebox = gtk_hbox_new(FALSE, 0);
6593 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
6595 gtk_widget_realize(handlebox);
6597 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, window,
6600 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
6604 vbox2 = gtk_vbox_new(FALSE, 2);
6605 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
6606 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
6609 notebook = gtk_notebook_new();
6610 gtk_widget_set_size_request(notebook, -1, 130);
6611 gtk_widget_show(notebook);
6613 /* header labels and entries */
6614 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6615 compose_create_header(compose),
6616 gtk_label_new_with_mnemonic(_("Hea_der")));
6617 /* attachment list */
6618 attach_hbox = gtk_hbox_new(FALSE, 0);
6619 gtk_widget_show(attach_hbox);
6621 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
6622 gtk_widget_show(attach_lab1);
6623 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
6625 attach_lab2 = gtk_label_new("");
6626 gtk_widget_show(attach_lab2);
6627 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
6629 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6630 compose_create_attach(compose),
6633 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6634 compose_create_others(compose),
6635 gtk_label_new_with_mnemonic(_("Othe_rs")));
6638 subject_hbox = gtk_hbox_new(FALSE, 0);
6639 gtk_widget_show(subject_hbox);
6641 subject_frame = gtk_frame_new(NULL);
6642 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
6643 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
6644 gtk_widget_show(subject_frame);
6646 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
6647 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
6648 gtk_widget_show(subject);
6650 label = gtk_label_new(_("Subject:"));
6651 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
6652 gtk_widget_show(label);
6654 subject_entry = gtk_entry_new();
6655 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
6656 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
6657 G_CALLBACK(compose_grab_focus_cb), compose);
6658 gtk_widget_show(subject_entry);
6659 compose->subject_entry = subject_entry;
6660 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
6662 edit_vbox = gtk_vbox_new(FALSE, 0);
6664 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
6667 ruler_hbox = gtk_hbox_new(FALSE, 0);
6668 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
6670 ruler = gtk_shruler_new();
6671 gtk_ruler_set_range(GTK_RULER(ruler), 0.0, 100.0, 1.0, 100.0);
6672 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
6676 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
6677 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
6678 GTK_POLICY_AUTOMATIC,
6679 GTK_POLICY_AUTOMATIC);
6680 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
6682 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
6683 gtk_widget_set_size_request(scrolledwin, prefs_common.compose_width, -1);
6685 text = gtk_text_view_new();
6686 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
6687 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
6688 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
6689 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
6690 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
6692 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
6694 g_signal_connect_after(G_OBJECT(text), "size_allocate",
6695 G_CALLBACK(compose_edit_size_alloc),
6697 g_signal_connect(G_OBJECT(buffer), "changed",
6698 G_CALLBACK(compose_changed_cb), compose);
6699 g_signal_connect(G_OBJECT(text), "grab_focus",
6700 G_CALLBACK(compose_grab_focus_cb), compose);
6701 g_signal_connect(G_OBJECT(buffer), "insert_text",
6702 G_CALLBACK(text_inserted), compose);
6703 g_signal_connect(G_OBJECT(text), "button_press_event",
6704 G_CALLBACK(text_clicked), compose);
6706 g_signal_connect(G_OBJECT(text), "popup-menu",
6707 G_CALLBACK(compose_popup_menu), compose);
6709 gtk_widget_tap_and_hold_setup(GTK_WIDGET(text), NULL, NULL,
6710 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
6711 g_signal_connect(G_OBJECT(text), "tap-and-hold",
6712 G_CALLBACK(compose_popup_menu), compose);
6714 g_signal_connect(G_OBJECT(subject_entry), "changed",
6715 G_CALLBACK(compose_changed_cb), compose);
6718 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6719 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6720 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6721 g_signal_connect(G_OBJECT(text), "drag_data_received",
6722 G_CALLBACK(compose_insert_drag_received_cb),
6724 g_signal_connect(G_OBJECT(text), "drag-drop",
6725 G_CALLBACK(compose_drag_drop),
6727 gtk_widget_show_all(vbox);
6729 /* pane between attach clist and text */
6730 paned = gtk_vpaned_new();
6731 gtk_paned_set_gutter_size(GTK_PANED(paned), 12);
6732 gtk_container_add(GTK_CONTAINER(vbox2), paned);
6734 if( maemo_mainwindow_is_fullscreen(mainwindow_get_mainwindow()->window) )
6735 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 300 : 280);
6737 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 250 : 230);
6739 gtk_paned_add1(GTK_PANED(paned), notebook);
6740 gtk_paned_add2(GTK_PANED(paned), edit_vbox);
6741 gtk_widget_show_all(paned);
6744 if (prefs_common.textfont) {
6745 PangoFontDescription *font_desc;
6747 font_desc = pango_font_description_from_string
6748 (prefs_common.textfont);
6750 gtk_widget_modify_font(text, font_desc);
6751 pango_font_description_free(font_desc);
6755 n_entries = sizeof(compose_popup_entries) /
6756 sizeof(compose_popup_entries[0]);
6757 popupmenu = menu_create_items(compose_popup_entries, n_entries,
6758 "<Compose>", &popupfactory,
6761 ifactory = gtk_item_factory_from_widget(menubar);
6762 menu_set_sensitive(ifactory, "/Edit/Undo", FALSE);
6763 menu_set_sensitive(ifactory, "/Edit/Redo", FALSE);
6764 menu_set_sensitive(ifactory, "/Options/Remove references", FALSE);
6766 tmpl_menu = gtk_item_factory_get_item(ifactory, "/Tools/Template");
6768 undostruct = undo_init(text);
6769 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
6772 address_completion_start(window);
6774 compose->window = window;
6775 compose->vbox = vbox;
6776 compose->menubar = menubar;
6777 compose->handlebox = handlebox;
6779 compose->vbox2 = vbox2;
6781 compose->paned = paned;
6783 compose->attach_label = attach_lab2;
6785 compose->notebook = notebook;
6786 compose->edit_vbox = edit_vbox;
6787 compose->ruler_hbox = ruler_hbox;
6788 compose->ruler = ruler;
6789 compose->scrolledwin = scrolledwin;
6790 compose->text = text;
6792 compose->focused_editable = NULL;
6794 compose->popupmenu = popupmenu;
6795 compose->popupfactory = popupfactory;
6797 compose->tmpl_menu = tmpl_menu;
6799 compose->mode = mode;
6800 compose->rmode = mode;
6802 compose->targetinfo = NULL;
6803 compose->replyinfo = NULL;
6804 compose->fwdinfo = NULL;
6806 compose->replyto = NULL;
6808 compose->bcc = NULL;
6809 compose->followup_to = NULL;
6811 compose->ml_post = NULL;
6813 compose->inreplyto = NULL;
6814 compose->references = NULL;
6815 compose->msgid = NULL;
6816 compose->boundary = NULL;
6818 compose->autowrap = prefs_common.autowrap;
6820 compose->use_signing = FALSE;
6821 compose->use_encryption = FALSE;
6822 compose->privacy_system = NULL;
6824 compose->modified = FALSE;
6826 compose->return_receipt = FALSE;
6828 compose->to_list = NULL;
6829 compose->newsgroup_list = NULL;
6831 compose->undostruct = undostruct;
6833 compose->sig_str = NULL;
6835 compose->exteditor_file = NULL;
6836 compose->exteditor_pid = -1;
6837 compose->exteditor_tag = -1;
6838 compose->draft_timeout_tag = -2; /* inhibit auto-drafting while loading */
6841 menu_set_sensitive(ifactory, "/Spelling", FALSE);
6842 if (mode != COMPOSE_REDIRECT) {
6843 if (prefs_common.enable_aspell && prefs_common.dictionary &&
6844 strcmp(prefs_common.dictionary, "")) {
6845 gtkaspell = gtkaspell_new(prefs_common.aspell_path,
6846 prefs_common.dictionary,
6847 prefs_common.alt_dictionary,
6848 conv_get_locale_charset_str(),
6849 prefs_common.misspelled_col,
6850 prefs_common.check_while_typing,
6851 prefs_common.recheck_when_changing_dict,
6852 prefs_common.use_alternate,
6853 prefs_common.use_both_dicts,
6854 GTK_TEXT_VIEW(text),
6855 GTK_WINDOW(compose->window),
6856 compose_spell_menu_changed,
6859 alertpanel_error(_("Spell checker could not "
6861 gtkaspell_checkers_strerror());
6862 gtkaspell_checkers_reset_error();
6864 if (!gtkaspell_set_sug_mode(gtkaspell,
6865 prefs_common.aspell_sugmode)) {
6866 debug_print("Aspell: could not set "
6867 "suggestion mode %s\n",
6868 gtkaspell_checkers_strerror());
6869 gtkaspell_checkers_reset_error();
6872 menu_set_sensitive(ifactory, "/Spelling", TRUE);
6876 compose->gtkaspell = gtkaspell;
6877 compose_spell_menu_changed(compose);
6880 compose_select_account(compose, account, TRUE);
6882 menu_set_active(ifactory, "/Edit/Auto wrapping", prefs_common.autowrap);
6883 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
6884 compose_entry_append(compose, account->auto_cc, COMPOSE_CC);
6886 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
6887 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC);
6889 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
6890 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO);
6892 menu_set_sensitive(ifactory, "/Options/Reply mode", compose->mode == COMPOSE_REPLY);
6894 if (account->protocol != A_NNTP)
6895 gtk_entry_set_text(GTK_ENTRY(GTK_BIN(compose->header_last->combo)->child),
6896 prefs_common_translated_header_name("To:"));
6898 gtk_entry_set_text(GTK_ENTRY(GTK_BIN(compose->header_last->combo)->child),
6899 prefs_common_translated_header_name("Newsgroups:"));
6901 addressbook_set_target_compose(compose);
6903 if (mode != COMPOSE_REDIRECT)
6904 compose_set_template_menu(compose);
6906 menuitem = gtk_item_factory_get_item(ifactory, "/Tools/Template");
6907 menu_set_sensitive(ifactory, "/Tools/Template", FALSE);
6910 compose_list = g_list_append(compose_list, compose);
6912 if (!prefs_common.show_ruler)
6913 gtk_widget_hide(ruler_hbox);
6915 menuitem = gtk_item_factory_get_item(ifactory, "/Tools/Show ruler");
6916 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
6917 prefs_common.show_ruler);
6920 compose->priority = PRIORITY_NORMAL;
6921 compose_update_priority_menu_item(compose);
6923 compose_set_out_encoding(compose);
6926 compose_update_actions_menu(compose);
6928 /* Privacy Systems menu */
6929 compose_update_privacy_systems_menu(compose);
6931 activate_privacy_system(compose, account, TRUE);
6932 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
6934 gtk_widget_realize(window);
6936 gtk_widget_show(window);
6938 maemo_window_full_screen_if_needed(GTK_WINDOW(window));
6939 maemo_connect_key_press_to_mainwindow(GTK_WINDOW(window));
6946 static GtkWidget *compose_account_option_menu_create(Compose *compose)
6951 GtkWidget *optmenubox;
6954 GtkWidget *from_name = NULL;
6956 gint num = 0, def_menu = 0;
6958 accounts = account_get_list();
6959 g_return_val_if_fail(accounts != NULL, NULL);
6961 optmenubox = gtk_event_box_new();
6962 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
6963 gtk_size_group_add_widget(compose->size_group, optmenu);
6964 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
6966 hbox = gtk_hbox_new(FALSE, 6);
6967 from_name = gtk_entry_new();
6969 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
6970 G_CALLBACK(compose_grab_focus_cb), compose);
6972 for (; accounts != NULL; accounts = accounts->next, num++) {
6973 PrefsAccount *ac = (PrefsAccount *)accounts->data;
6974 gchar *name, *from = NULL;
6976 if (ac == compose->account) def_menu = num;
6978 name = g_markup_printf_escaped(_("From: <i>%s</i>"),
6981 if (ac == compose->account) {
6982 if (ac->name && *ac->name) {
6984 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
6985 from = g_strdup_printf("%s <%s>",
6987 gtk_entry_set_text(GTK_ENTRY(from_name), from);
6989 from = g_strdup_printf("%s",
6991 gtk_entry_set_text(GTK_ENTRY(from_name), from);
6994 COMBOBOX_ADD(menu, name, ac->account_id);
6999 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
7001 g_signal_connect(G_OBJECT(optmenu), "changed",
7002 G_CALLBACK(account_activated),
7004 g_signal_connect(G_OBJECT(from_name), "populate-popup",
7005 G_CALLBACK(compose_entry_popup_extend),
7008 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
7009 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
7011 gtk_tooltips_set_tip(compose->tooltips, optmenubox,
7012 _("Account to use for this email"), NULL);
7013 gtk_tooltips_set_tip(compose->tooltips, from_name,
7014 _("Sender address to be used"), NULL);
7016 compose->from_name = from_name;
7021 static void compose_set_priority_cb(gpointer data,
7025 Compose *compose = (Compose *) data;
7026 compose->priority = action;
7029 static void compose_reply_change_mode(gpointer data,
7033 Compose *compose = (Compose *) data;
7034 gboolean was_modified = compose->modified;
7036 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
7038 g_return_if_fail(compose->replyinfo != NULL);
7040 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
7042 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
7044 if (action == COMPOSE_REPLY_TO_ALL)
7046 if (action == COMPOSE_REPLY_TO_SENDER)
7048 if (action == COMPOSE_REPLY_TO_LIST)
7051 compose_remove_header_entries(compose);
7052 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
7053 if (compose->account->set_autocc && compose->account->auto_cc)
7054 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC);
7056 if (compose->account->set_autobcc && compose->account->auto_bcc)
7057 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC);
7059 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
7060 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO);
7061 compose_show_first_last_header(compose, TRUE);
7062 compose->modified = was_modified;
7063 compose_set_title(compose);
7066 static void compose_update_priority_menu_item(Compose * compose)
7068 GtkItemFactory *ifactory;
7069 GtkWidget *menuitem = NULL;
7071 ifactory = gtk_item_factory_from_widget(compose->menubar);
7073 switch (compose->priority) {
7074 case PRIORITY_HIGHEST:
7075 menuitem = gtk_item_factory_get_item
7076 (ifactory, "/Options/Priority/Highest");
7079 menuitem = gtk_item_factory_get_item
7080 (ifactory, "/Options/Priority/High");
7082 case PRIORITY_NORMAL:
7083 menuitem = gtk_item_factory_get_item
7084 (ifactory, "/Options/Priority/Normal");
7087 menuitem = gtk_item_factory_get_item
7088 (ifactory, "/Options/Priority/Low");
7090 case PRIORITY_LOWEST:
7091 menuitem = gtk_item_factory_get_item
7092 (ifactory, "/Options/Priority/Lowest");
7095 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
7098 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
7100 Compose *compose = (Compose *) data;
7102 GtkItemFactory *ifactory;
7103 gboolean can_sign = FALSE, can_encrypt = FALSE;
7105 g_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
7107 if (!GTK_CHECK_MENU_ITEM(widget)->active)
7110 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
7111 g_free(compose->privacy_system);
7112 compose->privacy_system = NULL;
7113 if (systemid != NULL) {
7114 compose->privacy_system = g_strdup(systemid);
7116 can_sign = privacy_system_can_sign(systemid);
7117 can_encrypt = privacy_system_can_encrypt(systemid);
7120 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
7122 ifactory = gtk_item_factory_from_widget(compose->menubar);
7123 menu_set_sensitive(ifactory, "/Options/Sign", can_sign);
7124 menu_set_sensitive(ifactory, "/Options/Encrypt", can_encrypt);
7127 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
7129 static gchar *branch_path = "/Options/Privacy System";
7130 GtkItemFactory *ifactory;
7131 GtkWidget *menuitem = NULL;
7133 gboolean can_sign = FALSE, can_encrypt = FALSE;
7134 gboolean found = FALSE;
7136 ifactory = gtk_item_factory_from_widget(compose->menubar);
7138 if (compose->privacy_system != NULL) {
7141 menuitem = gtk_item_factory_get_widget(ifactory, branch_path);
7142 g_return_if_fail(menuitem != NULL);
7144 amenu = GTK_MENU_SHELL(menuitem)->children;
7146 while (amenu != NULL) {
7147 GList *alist = amenu->next;
7149 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
7150 if (systemid != NULL) {
7151 if (strcmp(systemid, compose->privacy_system) == 0) {
7152 menuitem = GTK_WIDGET(amenu->data);
7154 can_sign = privacy_system_can_sign(systemid);
7155 can_encrypt = privacy_system_can_encrypt(systemid);
7159 } else if (strlen(compose->privacy_system) == 0) {
7160 menuitem = GTK_WIDGET(amenu->data);
7163 can_encrypt = FALSE;
7170 if (menuitem != NULL)
7171 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
7173 if (warn && !found && strlen(compose->privacy_system)) {
7174 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
7175 "will not be able to sign or encrypt this message."),
7176 compose->privacy_system);
7180 menu_set_sensitive(ifactory, "/Options/Sign", can_sign);
7181 menu_set_sensitive(ifactory, "/Options/Encrypt", can_encrypt);
7184 static void compose_set_out_encoding(Compose *compose)
7186 GtkItemFactoryEntry *entry;
7187 GtkItemFactory *ifactory;
7188 CharSet out_encoding;
7189 gchar *path, *p, *q;
7192 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
7193 ifactory = gtk_item_factory_from_widget(compose->menubar);
7195 for (entry = compose_entries; entry->callback != compose_address_cb;
7197 if (entry->callback == compose_set_encoding_cb &&
7198 (CharSet)entry->callback_action == out_encoding) {
7199 p = q = path = g_strdup(entry->path);
7211 item = gtk_item_factory_get_item(ifactory, path);
7212 gtk_widget_activate(item);
7219 static void compose_set_template_menu(Compose *compose)
7221 GSList *tmpl_list, *cur;
7225 tmpl_list = template_get_config();
7227 menu = gtk_menu_new();
7229 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
7230 Template *tmpl = (Template *)cur->data;
7232 item = gtk_menu_item_new_with_label(tmpl->name);
7233 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
7234 g_signal_connect(G_OBJECT(item), "activate",
7235 G_CALLBACK(compose_template_activate_cb),
7237 g_object_set_data(G_OBJECT(item), "template", tmpl);
7238 gtk_widget_show(item);
7241 gtk_widget_show(menu);
7242 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
7245 void compose_update_actions_menu(Compose *compose)
7247 GtkItemFactory *ifactory;
7249 ifactory = gtk_item_factory_from_widget(compose->menubar);
7250 action_update_compose_menu(ifactory, "/Tools/Actions", compose);
7253 static void compose_update_privacy_systems_menu(Compose *compose)
7255 static gchar *branch_path = "/Options/Privacy System";
7256 GtkItemFactory *ifactory;
7257 GtkWidget *menuitem;
7258 GSList *systems, *cur;
7261 GtkWidget *system_none;
7264 ifactory = gtk_item_factory_from_widget(compose->menubar);
7266 /* remove old entries */
7267 menuitem = gtk_item_factory_get_widget(ifactory, branch_path);
7268 g_return_if_fail(menuitem != NULL);
7270 amenu = GTK_MENU_SHELL(menuitem)->children->next;
7271 while (amenu != NULL) {
7272 GList *alist = amenu->next;
7273 gtk_widget_destroy(GTK_WIDGET(amenu->data));
7277 system_none = gtk_item_factory_get_widget(ifactory,
7278 "/Options/Privacy System/None");
7280 g_signal_connect(G_OBJECT(system_none), "activate",
7281 G_CALLBACK(compose_set_privacy_system_cb), compose);
7283 systems = privacy_get_system_ids();
7284 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
7285 gchar *systemid = cur->data;
7287 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
7288 widget = gtk_radio_menu_item_new_with_label(group,
7289 privacy_system_get_name(systemid));
7290 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
7291 g_strdup(systemid), g_free);
7292 g_signal_connect(G_OBJECT(widget), "activate",
7293 G_CALLBACK(compose_set_privacy_system_cb), compose);
7295 gtk_menu_append(GTK_MENU(system_none->parent), widget);
7296 gtk_widget_show(widget);
7299 g_slist_free(systems);
7302 void compose_reflect_prefs_all(void)
7307 for (cur = compose_list; cur != NULL; cur = cur->next) {
7308 compose = (Compose *)cur->data;
7309 compose_set_template_menu(compose);
7313 void compose_reflect_prefs_pixmap_theme(void)
7318 for (cur = compose_list; cur != NULL; cur = cur->next) {
7319 compose = (Compose *)cur->data;
7320 toolbar_update(TOOLBAR_COMPOSE, compose);
7324 static const gchar *compose_quote_char_from_context(Compose *compose)
7326 const gchar *qmark = NULL;
7328 g_return_val_if_fail(compose != NULL, NULL);
7330 switch (compose->mode) {
7331 /* use forward-specific quote char */
7332 case COMPOSE_FORWARD:
7333 case COMPOSE_FORWARD_AS_ATTACH:
7334 case COMPOSE_FORWARD_INLINE:
7335 if (compose->folder && compose->folder->prefs &&
7336 compose->folder->prefs->forward_with_format)
7337 qmark = compose->folder->prefs->forward_quotemark;
7338 else if (compose->account->forward_with_format)
7339 qmark = compose->account->forward_quotemark;
7341 qmark = prefs_common.fw_quotemark;
7344 /* use reply-specific quote char in all other modes */
7346 if (compose->folder && compose->folder->prefs &&
7347 compose->folder->prefs->reply_with_format)
7348 qmark = compose->folder->prefs->reply_quotemark;
7349 else if (compose->account->reply_with_format)
7350 qmark = compose->account->reply_quotemark;
7352 qmark = prefs_common.quotemark;
7356 if (qmark == NULL || *qmark == '\0')
7362 static void compose_template_apply(Compose *compose, Template *tmpl,
7366 GtkTextBuffer *buffer;
7370 gchar *parsed_str = NULL;
7371 gint cursor_pos = 0;
7372 const gchar *err_msg = _("Template body format error at line %d.");
7375 /* process the body */
7377 text = GTK_TEXT_VIEW(compose->text);
7378 buffer = gtk_text_view_get_buffer(text);
7381 qmark = compose_quote_char_from_context(compose);
7383 if (compose->replyinfo != NULL) {
7386 gtk_text_buffer_set_text(buffer, "", -1);
7387 mark = gtk_text_buffer_get_insert(buffer);
7388 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7390 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
7391 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
7393 } else if (compose->fwdinfo != NULL) {
7396 gtk_text_buffer_set_text(buffer, "", -1);
7397 mark = gtk_text_buffer_get_insert(buffer);
7398 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7400 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
7401 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
7404 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
7406 GtkTextIter start, end;
7409 gtk_text_buffer_get_start_iter(buffer, &start);
7410 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
7411 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
7413 /* clear the buffer now */
7415 gtk_text_buffer_set_text(buffer, "", -1);
7417 parsed_str = compose_quote_fmt(compose, dummyinfo,
7418 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
7419 procmsg_msginfo_free( dummyinfo );
7425 gtk_text_buffer_set_text(buffer, "", -1);
7426 mark = gtk_text_buffer_get_insert(buffer);
7427 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7430 if (replace && parsed_str && compose->account->auto_sig)
7431 compose_insert_sig(compose, FALSE);
7433 if (replace && parsed_str) {
7434 gtk_text_buffer_get_start_iter(buffer, &iter);
7435 gtk_text_buffer_place_cursor(buffer, &iter);
7439 cursor_pos = quote_fmt_get_cursor_pos();
7440 compose->set_cursor_pos = cursor_pos;
7441 if (cursor_pos == -1)
7443 gtk_text_buffer_get_start_iter(buffer, &iter);
7444 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
7445 gtk_text_buffer_place_cursor(buffer, &iter);
7448 /* process the other fields */
7450 compose_template_apply_fields(compose, tmpl);
7451 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
7452 quote_fmt_reset_vartable();
7453 compose_changed_cb(NULL, compose);
7456 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
7458 MsgInfo* dummyinfo = NULL;
7459 MsgInfo *msginfo = NULL;
7462 if (compose->replyinfo != NULL)
7463 msginfo = compose->replyinfo;
7464 else if (compose->fwdinfo != NULL)
7465 msginfo = compose->fwdinfo;
7467 dummyinfo = compose_msginfo_new_from_compose(compose);
7468 msginfo = dummyinfo;
7471 if (tmpl->to && *tmpl->to != '\0') {
7473 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7474 compose->gtkaspell);
7476 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7478 quote_fmt_scan_string(tmpl->to);
7481 buf = quote_fmt_get_buffer();
7483 alertpanel_error(_("Template To format error."));
7485 compose_entry_append(compose, buf, COMPOSE_TO);
7489 if (tmpl->cc && *tmpl->cc != '\0') {
7491 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7492 compose->gtkaspell);
7494 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7496 quote_fmt_scan_string(tmpl->cc);
7499 buf = quote_fmt_get_buffer();
7501 alertpanel_error(_("Template Cc format error."));
7503 compose_entry_append(compose, buf, COMPOSE_CC);
7507 if (tmpl->bcc && *tmpl->bcc != '\0') {
7509 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7510 compose->gtkaspell);
7512 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7514 quote_fmt_scan_string(tmpl->bcc);
7517 buf = quote_fmt_get_buffer();
7519 alertpanel_error(_("Template Bcc format error."));
7521 compose_entry_append(compose, buf, COMPOSE_BCC);
7525 /* process the subject */
7526 if (tmpl->subject && *tmpl->subject != '\0') {
7528 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7529 compose->gtkaspell);
7531 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7533 quote_fmt_scan_string(tmpl->subject);
7536 buf = quote_fmt_get_buffer();
7538 alertpanel_error(_("Template subject format error."));
7540 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
7544 procmsg_msginfo_free( dummyinfo );
7547 static void compose_destroy(Compose *compose)
7549 GtkTextBuffer *buffer;
7550 GtkClipboard *clipboard;
7552 compose_list = g_list_remove(compose_list, compose);
7554 if (compose->updating) {
7555 debug_print("danger, not destroying anything now\n");
7556 compose->deferred_destroy = TRUE;
7559 /* NOTE: address_completion_end() does nothing with the window
7560 * however this may change. */
7561 address_completion_end(compose->window);
7563 slist_free_strings(compose->to_list);
7564 g_slist_free(compose->to_list);
7565 slist_free_strings(compose->newsgroup_list);
7566 g_slist_free(compose->newsgroup_list);
7567 slist_free_strings(compose->header_list);
7568 g_slist_free(compose->header_list);
7570 procmsg_msginfo_free(compose->targetinfo);
7571 procmsg_msginfo_free(compose->replyinfo);
7572 procmsg_msginfo_free(compose->fwdinfo);
7574 g_free(compose->replyto);
7575 g_free(compose->cc);
7576 g_free(compose->bcc);
7577 g_free(compose->newsgroups);
7578 g_free(compose->followup_to);
7580 g_free(compose->ml_post);
7582 g_free(compose->inreplyto);
7583 g_free(compose->references);
7584 g_free(compose->msgid);
7585 g_free(compose->boundary);
7587 g_free(compose->redirect_filename);
7588 if (compose->undostruct)
7589 undo_destroy(compose->undostruct);
7591 g_free(compose->sig_str);
7593 g_free(compose->exteditor_file);
7595 g_free(compose->orig_charset);
7597 g_free(compose->privacy_system);
7599 if (addressbook_get_target_compose() == compose)
7600 addressbook_set_target_compose(NULL);
7603 if (compose->gtkaspell) {
7604 gtkaspell_delete(compose->gtkaspell);
7605 compose->gtkaspell = NULL;
7609 prefs_common.compose_width = compose->scrolledwin->allocation.width;
7610 prefs_common.compose_height = compose->window->allocation.height;
7612 if (!gtk_widget_get_parent(compose->paned))
7613 gtk_widget_destroy(compose->paned);
7614 gtk_widget_destroy(compose->popupmenu);
7616 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
7617 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7618 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
7620 gtk_widget_destroy(compose->window);
7621 toolbar_destroy(compose->toolbar);
7622 g_free(compose->toolbar);
7623 g_mutex_free(compose->mutex);
7627 static void compose_attach_info_free(AttachInfo *ainfo)
7629 g_free(ainfo->file);
7630 g_free(ainfo->content_type);
7631 g_free(ainfo->name);
7635 static void compose_attach_update_label(Compose *compose)
7640 GtkTreeModel *model;
7645 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
7646 if(!gtk_tree_model_get_iter_first(model, &iter)) {
7647 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
7651 while(gtk_tree_model_iter_next(model, &iter))
7654 text = g_strdup_printf("(%d)", i);
7655 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
7659 static void compose_attach_remove_selected(Compose *compose)
7661 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
7662 GtkTreeSelection *selection;
7664 GtkTreeModel *model;
7666 selection = gtk_tree_view_get_selection(tree_view);
7667 sel = gtk_tree_selection_get_selected_rows(selection, &model);
7672 for (cur = sel; cur != NULL; cur = cur->next) {
7673 GtkTreePath *path = cur->data;
7674 GtkTreeRowReference *ref = gtk_tree_row_reference_new
7677 gtk_tree_path_free(path);
7680 for (cur = sel; cur != NULL; cur = cur->next) {
7681 GtkTreeRowReference *ref = cur->data;
7682 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
7685 if (gtk_tree_model_get_iter(model, &iter, path))
7686 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
7688 gtk_tree_path_free(path);
7689 gtk_tree_row_reference_free(ref);
7693 compose_attach_update_label(compose);
7696 static struct _AttachProperty
7699 GtkWidget *mimetype_entry;
7700 GtkWidget *encoding_optmenu;
7701 GtkWidget *path_entry;
7702 GtkWidget *filename_entry;
7704 GtkWidget *cancel_btn;
7707 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
7709 gtk_tree_path_free((GtkTreePath *)ptr);
7712 static void compose_attach_property(Compose *compose)
7714 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
7716 GtkComboBox *optmenu;
7717 GtkTreeSelection *selection;
7719 GtkTreeModel *model;
7722 static gboolean cancelled;
7724 /* only if one selected */
7725 selection = gtk_tree_view_get_selection(tree_view);
7726 if (gtk_tree_selection_count_selected_rows(selection) != 1)
7729 sel = gtk_tree_selection_get_selected_rows(selection, &model);
7733 path = (GtkTreePath *) sel->data;
7734 gtk_tree_model_get_iter(model, &iter, path);
7735 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
7738 g_list_foreach(sel, gtk_tree_path_free_, NULL);
7744 if (!attach_prop.window)
7745 compose_attach_property_create(&cancelled);
7746 gtk_widget_grab_focus(attach_prop.ok_btn);
7747 gtk_widget_show(attach_prop.window);
7748 manage_window_set_transient(GTK_WINDOW(attach_prop.window));
7750 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
7751 if (ainfo->encoding == ENC_UNKNOWN)
7752 combobox_select_by_data(optmenu, ENC_BASE64);
7754 combobox_select_by_data(optmenu, ainfo->encoding);
7756 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
7757 ainfo->content_type ? ainfo->content_type : "");
7758 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
7759 ainfo->file ? ainfo->file : "");
7760 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
7761 ainfo->name ? ainfo->name : "");
7764 const gchar *entry_text;
7766 gchar *cnttype = NULL;
7773 gtk_widget_hide(attach_prop.window);
7778 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
7779 if (*entry_text != '\0') {
7782 text = g_strstrip(g_strdup(entry_text));
7783 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
7784 cnttype = g_strdup(text);
7787 alertpanel_error(_("Invalid MIME type."));
7793 ainfo->encoding = combobox_get_active_data(optmenu);
7795 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
7796 if (*entry_text != '\0') {
7797 if (is_file_exist(entry_text) &&
7798 (size = get_file_size(entry_text)) > 0)
7799 file = g_strdup(entry_text);
7802 (_("File doesn't exist or is empty."));
7808 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
7809 if (*entry_text != '\0') {
7810 g_free(ainfo->name);
7811 ainfo->name = g_strdup(entry_text);
7815 g_free(ainfo->content_type);
7816 ainfo->content_type = cnttype;
7819 g_free(ainfo->file);
7825 /* update tree store */
7826 text = to_human_readable(ainfo->size);
7827 gtk_tree_model_get_iter(model, &iter, path);
7828 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
7829 COL_MIMETYPE, ainfo->content_type,
7831 COL_NAME, ainfo->name,
7837 gtk_tree_path_free(path);
7840 #define SET_LABEL_AND_ENTRY(str, entry, top) \
7842 label = gtk_label_new(str); \
7843 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
7844 GTK_FILL, 0, 0, 0); \
7845 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
7847 entry = gtk_entry_new(); \
7848 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
7849 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
7852 static void compose_attach_property_create(gboolean *cancelled)
7858 GtkWidget *mimetype_entry;
7861 GtkListStore *optmenu_menu;
7862 GtkWidget *path_entry;
7863 GtkWidget *filename_entry;
7866 GtkWidget *cancel_btn;
7867 GList *mime_type_list, *strlist;
7870 debug_print("Creating attach_property window...\n");
7872 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
7873 gtk_widget_set_size_request(window, 480, -1);
7874 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
7875 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
7876 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
7877 gtk_window_set_modal(GTK_WINDOW(window), TRUE);
7878 g_signal_connect(G_OBJECT(window), "delete_event",
7879 G_CALLBACK(attach_property_delete_event),
7881 g_signal_connect(G_OBJECT(window), "key_press_event",
7882 G_CALLBACK(attach_property_key_pressed),
7885 vbox = gtk_vbox_new(FALSE, 8);
7886 gtk_container_add(GTK_CONTAINER(window), vbox);
7888 table = gtk_table_new(4, 2, FALSE);
7889 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
7890 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
7891 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
7893 label = gtk_label_new(_("MIME type"));
7894 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
7896 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
7897 mimetype_entry = gtk_combo_box_entry_new_text();
7898 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
7899 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
7901 /* stuff with list */
7902 mime_type_list = procmime_get_mime_type_list();
7904 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
7905 MimeType *type = (MimeType *) mime_type_list->data;
7908 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
7910 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
7913 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
7914 (GCompareFunc)strcmp2);
7917 for (mime_type_list = strlist; mime_type_list != NULL;
7918 mime_type_list = mime_type_list->next) {
7919 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
7920 g_free(mime_type_list->data);
7922 g_list_free(strlist);
7923 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
7924 mimetype_entry = GTK_BIN(mimetype_entry)->child;
7926 label = gtk_label_new(_("Encoding"));
7927 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
7929 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
7931 hbox = gtk_hbox_new(FALSE, 0);
7932 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
7933 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
7935 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
7936 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
7938 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
7939 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
7940 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
7941 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
7942 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
7944 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
7946 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
7947 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
7949 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
7950 &ok_btn, GTK_STOCK_OK,
7952 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
7953 gtk_widget_grab_default(ok_btn);
7955 g_signal_connect(G_OBJECT(ok_btn), "clicked",
7956 G_CALLBACK(attach_property_ok),
7958 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
7959 G_CALLBACK(attach_property_cancel),
7962 gtk_widget_show_all(vbox);
7964 attach_prop.window = window;
7965 attach_prop.mimetype_entry = mimetype_entry;
7966 attach_prop.encoding_optmenu = optmenu;
7967 attach_prop.path_entry = path_entry;
7968 attach_prop.filename_entry = filename_entry;
7969 attach_prop.ok_btn = ok_btn;
7970 attach_prop.cancel_btn = cancel_btn;
7973 #undef SET_LABEL_AND_ENTRY
7975 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
7981 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
7987 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
7988 gboolean *cancelled)
7996 static gboolean attach_property_key_pressed(GtkWidget *widget,
7998 gboolean *cancelled)
8000 if (event && event->keyval == GDK_Escape) {
8007 static void compose_exec_ext_editor(Compose *compose)
8014 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
8015 G_DIR_SEPARATOR, compose);
8017 if (pipe(pipe_fds) < 0) {
8023 if ((pid = fork()) < 0) {
8030 /* close the write side of the pipe */
8033 compose->exteditor_file = g_strdup(tmp);
8034 compose->exteditor_pid = pid;
8036 compose_set_ext_editor_sensitive(compose, FALSE);
8038 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
8039 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
8043 } else { /* process-monitoring process */
8049 /* close the read side of the pipe */
8052 if (compose_write_body_to_file(compose, tmp) < 0) {
8053 fd_write_all(pipe_fds[1], "2\n", 2);
8057 pid_ed = compose_exec_ext_editor_real(tmp);
8059 fd_write_all(pipe_fds[1], "1\n", 2);
8063 /* wait until editor is terminated */
8064 waitpid(pid_ed, NULL, 0);
8066 fd_write_all(pipe_fds[1], "0\n", 2);
8073 #endif /* G_OS_UNIX */
8077 static gint compose_exec_ext_editor_real(const gchar *file)
8084 g_return_val_if_fail(file != NULL, -1);
8086 if ((pid = fork()) < 0) {
8091 if (pid != 0) return pid;
8093 /* grandchild process */
8095 if (setpgid(0, getppid()))
8098 if (prefs_common.ext_editor_cmd &&
8099 (p = strchr(prefs_common.ext_editor_cmd, '%')) &&
8100 *(p + 1) == 's' && !strchr(p + 2, '%')) {
8101 g_snprintf(buf, sizeof(buf), prefs_common.ext_editor_cmd, file);
8103 if (prefs_common.ext_editor_cmd)
8104 g_warning("External editor command line is invalid: '%s'\n",
8105 prefs_common.ext_editor_cmd);
8106 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
8109 cmdline = strsplit_with_quote(buf, " ", 1024);
8110 execvp(cmdline[0], cmdline);
8113 g_strfreev(cmdline);
8118 static gboolean compose_ext_editor_kill(Compose *compose)
8120 pid_t pgid = compose->exteditor_pid * -1;
8123 ret = kill(pgid, 0);
8125 if (ret == 0 || (ret == -1 && EPERM == errno)) {
8129 msg = g_strdup_printf
8130 (_("The external editor is still working.\n"
8131 "Force terminating the process?\n"
8132 "process group id: %d"), -pgid);
8133 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
8134 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
8138 if (val == G_ALERTALTERNATE) {
8139 g_source_remove(compose->exteditor_tag);
8140 g_io_channel_shutdown(compose->exteditor_ch,
8142 g_io_channel_unref(compose->exteditor_ch);
8144 if (kill(pgid, SIGTERM) < 0) perror("kill");
8145 waitpid(compose->exteditor_pid, NULL, 0);
8147 g_warning("Terminated process group id: %d", -pgid);
8148 g_warning("Temporary file: %s",
8149 compose->exteditor_file);
8151 compose_set_ext_editor_sensitive(compose, TRUE);
8153 g_free(compose->exteditor_file);
8154 compose->exteditor_file = NULL;
8155 compose->exteditor_pid = -1;
8156 compose->exteditor_ch = NULL;
8157 compose->exteditor_tag = -1;
8165 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
8169 Compose *compose = (Compose *)data;
8172 debug_print(_("Compose: input from monitoring process\n"));
8174 g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL);
8176 g_io_channel_shutdown(source, FALSE, NULL);
8177 g_io_channel_unref(source);
8179 waitpid(compose->exteditor_pid, NULL, 0);
8181 if (buf[0] == '0') { /* success */
8182 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
8183 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
8185 gtk_text_buffer_set_text(buffer, "", -1);
8186 compose_insert_file(compose, compose->exteditor_file);
8187 compose_changed_cb(NULL, compose);
8189 if (g_unlink(compose->exteditor_file) < 0)
8190 FILE_OP_ERROR(compose->exteditor_file, "unlink");
8191 } else if (buf[0] == '1') { /* failed */
8192 g_warning("Couldn't exec external editor\n");
8193 if (g_unlink(compose->exteditor_file) < 0)
8194 FILE_OP_ERROR(compose->exteditor_file, "unlink");
8195 } else if (buf[0] == '2') {
8196 g_warning("Couldn't write to file\n");
8197 } else if (buf[0] == '3') {
8198 g_warning("Pipe read failed\n");
8201 compose_set_ext_editor_sensitive(compose, TRUE);
8203 g_free(compose->exteditor_file);
8204 compose->exteditor_file = NULL;
8205 compose->exteditor_pid = -1;
8206 compose->exteditor_ch = NULL;
8207 compose->exteditor_tag = -1;
8212 static void compose_set_ext_editor_sensitive(Compose *compose,
8215 GtkItemFactory *ifactory;
8217 ifactory = gtk_item_factory_from_widget(compose->menubar);
8219 menu_set_sensitive(ifactory, "/Message/Send", sensitive);
8220 menu_set_sensitive(ifactory, "/Message/Send later", sensitive);
8221 menu_set_sensitive(ifactory, "/Message/Insert file", sensitive);
8222 menu_set_sensitive(ifactory, "/Message/Insert signature", sensitive);
8223 menu_set_sensitive(ifactory, "/Edit/Wrap current paragraph", sensitive);
8224 menu_set_sensitive(ifactory, "/Edit/Wrap all long lines", sensitive);
8225 menu_set_sensitive(ifactory, "/Edit/Edit with external editor",
8228 gtk_widget_set_sensitive(compose->text, sensitive);
8229 if (compose->toolbar->send_btn)
8230 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
8231 if (compose->toolbar->sendl_btn)
8232 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
8233 if (compose->toolbar->draft_btn)
8234 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
8235 if (compose->toolbar->insert_btn)
8236 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
8237 if (compose->toolbar->sig_btn)
8238 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
8239 if (compose->toolbar->exteditor_btn)
8240 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
8241 if (compose->toolbar->linewrap_current_btn)
8242 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
8243 if (compose->toolbar->linewrap_all_btn)
8244 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
8246 #endif /* G_OS_UNIX */
8249 * compose_undo_state_changed:
8251 * Change the sensivity of the menuentries undo and redo
8253 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
8254 gint redo_state, gpointer data)
8256 GtkWidget *widget = GTK_WIDGET(data);
8257 GtkItemFactory *ifactory;
8259 g_return_if_fail(widget != NULL);
8261 ifactory = gtk_item_factory_from_widget(widget);
8263 switch (undo_state) {
8264 case UNDO_STATE_TRUE:
8265 if (!undostruct->undo_state) {
8266 undostruct->undo_state = TRUE;
8267 menu_set_sensitive(ifactory, "/Edit/Undo", TRUE);
8270 case UNDO_STATE_FALSE:
8271 if (undostruct->undo_state) {
8272 undostruct->undo_state = FALSE;
8273 menu_set_sensitive(ifactory, "/Edit/Undo", FALSE);
8276 case UNDO_STATE_UNCHANGED:
8278 case UNDO_STATE_REFRESH:
8279 menu_set_sensitive(ifactory, "/Edit/Undo",
8280 undostruct->undo_state);
8283 g_warning("Undo state not recognized");
8287 switch (redo_state) {
8288 case UNDO_STATE_TRUE:
8289 if (!undostruct->redo_state) {
8290 undostruct->redo_state = TRUE;
8291 menu_set_sensitive(ifactory, "/Edit/Redo", TRUE);
8294 case UNDO_STATE_FALSE:
8295 if (undostruct->redo_state) {
8296 undostruct->redo_state = FALSE;
8297 menu_set_sensitive(ifactory, "/Edit/Redo", FALSE);
8300 case UNDO_STATE_UNCHANGED:
8302 case UNDO_STATE_REFRESH:
8303 menu_set_sensitive(ifactory, "/Edit/Redo",
8304 undostruct->redo_state);
8307 g_warning("Redo state not recognized");
8312 /* callback functions */
8314 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
8315 * includes "non-client" (windows-izm) in calculation, so this calculation
8316 * may not be accurate.
8318 static gboolean compose_edit_size_alloc(GtkEditable *widget,
8319 GtkAllocation *allocation,
8320 GtkSHRuler *shruler)
8322 if (prefs_common.show_ruler) {
8323 gint char_width = 0, char_height = 0;
8324 gint line_width_in_chars;
8326 gtkut_get_font_size(GTK_WIDGET(widget),
8327 &char_width, &char_height);
8328 line_width_in_chars =
8329 (allocation->width - allocation->x) / char_width;
8331 /* got the maximum */
8332 gtk_ruler_set_range(GTK_RULER(shruler),
8333 0.0, line_width_in_chars, 0,
8334 /*line_width_in_chars*/ char_width);
8340 static void account_activated(GtkComboBox *optmenu, gpointer data)
8342 Compose *compose = (Compose *)data;
8345 gchar *folderidentifier;
8346 gint account_id = 0;
8350 /* Get ID of active account in the combo box */
8351 menu = gtk_combo_box_get_model(optmenu);
8352 gtk_combo_box_get_active_iter(optmenu, &iter);
8353 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
8355 ac = account_find_from_id(account_id);
8356 g_return_if_fail(ac != NULL);
8358 if (ac != compose->account)
8359 compose_select_account(compose, ac, FALSE);
8361 /* Set message save folder */
8362 if (account_get_special_folder(compose->account, F_OUTBOX)) {
8363 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
8365 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
8366 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
8368 gtk_editable_delete_text(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
8369 if (account_get_special_folder(compose->account, F_OUTBOX)) {
8370 folderidentifier = folder_item_get_identifier(account_get_special_folder
8371 (compose->account, F_OUTBOX));
8372 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
8373 g_free(folderidentifier);
8377 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
8378 GtkTreeViewColumn *column, Compose *compose)
8380 compose_attach_property(compose);
8383 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
8386 Compose *compose = (Compose *)data;
8387 GtkTreeSelection *attach_selection;
8388 gint attach_nr_selected;
8389 GtkItemFactory *ifactory;
8391 if (!event) return FALSE;
8393 if (event->button == 3) {
8394 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
8395 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
8396 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
8398 if (attach_nr_selected > 0)
8400 menu_set_sensitive(ifactory, "/Remove", TRUE);
8401 menu_set_sensitive(ifactory, "/Properties...", TRUE);
8403 menu_set_sensitive(ifactory, "/Remove", FALSE);
8404 menu_set_sensitive(ifactory, "/Properties...", FALSE);
8407 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
8408 NULL, NULL, event->button, event->time);
8415 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
8418 Compose *compose = (Compose *)data;
8420 if (!event) return FALSE;
8422 switch (event->keyval) {
8424 compose_attach_remove_selected(compose);
8430 static void compose_allow_user_actions (Compose *compose, gboolean allow)
8432 GtkItemFactory *ifactory = gtk_item_factory_from_widget(compose->menubar);
8433 toolbar_comp_set_sensitive(compose, allow);
8434 menu_set_sensitive(ifactory, "/Message", allow);
8435 menu_set_sensitive(ifactory, "/Edit", allow);
8437 menu_set_sensitive(ifactory, "/Spelling", allow);
8439 menu_set_sensitive(ifactory, "/Options", allow);
8440 menu_set_sensitive(ifactory, "/Tools", allow);
8441 menu_set_sensitive(ifactory, "/Help", allow);
8443 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
8447 static void compose_send_cb(gpointer data, guint action, GtkWidget *widget)
8449 Compose *compose = (Compose *)data;
8451 if (prefs_common.work_offline &&
8452 !inc_offline_should_override(TRUE,
8453 _("Claws Mail needs network access in order "
8454 "to send this email.")))
8457 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
8458 g_source_remove(compose->draft_timeout_tag);
8459 compose->draft_timeout_tag = -1;
8462 compose_send(compose);
8465 static void compose_send_later_cb(gpointer data, guint action,
8468 Compose *compose = (Compose *)data;
8472 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
8476 compose_close(compose);
8477 } else if (val == -1) {
8478 alertpanel_error(_("Could not queue message."));
8479 } else if (val == -2) {
8480 alertpanel_error(_("Could not queue message:\n\n%s."), strerror(errno));
8481 } else if (val == -3) {
8482 if (privacy_peek_error())
8483 alertpanel_error(_("Could not queue message for sending:\n\n"
8484 "Signature failed: %s"), privacy_get_error());
8485 } else if (val == -4) {
8486 alertpanel_error(_("Could not queue message for sending:\n\n"
8487 "Charset conversion failed."));
8488 } else if (val == -5) {
8489 alertpanel_error(_("Could not queue message for sending:\n\n"
8490 "Couldn't get recipient encryption key."));
8491 } else if (val == -6) {
8494 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
8497 #define DRAFTED_AT_EXIT "drafted_at_exit"
8498 static void compose_register_draft(MsgInfo *info)
8500 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8501 DRAFTED_AT_EXIT, NULL);
8502 FILE *fp = fopen(filepath, "ab");
8505 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
8513 gboolean compose_draft (gpointer data, guint action)
8515 Compose *compose = (Compose *)data;
8519 MsgFlags flag = {0, 0};
8520 static gboolean lock = FALSE;
8521 MsgInfo *newmsginfo;
8523 gboolean target_locked = FALSE;
8524 gboolean err = FALSE;
8526 if (lock) return FALSE;
8528 if (compose->sending)
8531 draft = account_get_special_folder(compose->account, F_DRAFT);
8532 g_return_val_if_fail(draft != NULL, FALSE);
8534 if (!g_mutex_trylock(compose->mutex)) {
8535 /* we don't want to lock the mutex once it's available,
8536 * because as the only other part of compose.c locking
8537 * it is compose_close - which means once unlocked,
8538 * the compose struct will be freed */
8539 debug_print("couldn't lock mutex, probably sending\n");
8545 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
8546 G_DIR_SEPARATOR, compose);
8547 if ((fp = g_fopen(tmp, "wb")) == NULL) {
8548 FILE_OP_ERROR(tmp, "fopen");
8552 /* chmod for security */
8553 if (change_file_mode_rw(fp, tmp) < 0) {
8554 FILE_OP_ERROR(tmp, "chmod");
8555 g_warning("can't change file mode\n");
8558 /* Save draft infos */
8559 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
8560 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
8562 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
8563 gchar *savefolderid;
8565 savefolderid = gtk_editable_get_chars(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
8566 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
8567 g_free(savefolderid);
8569 if (compose->return_receipt) {
8570 err |= (fprintf(fp, "RRCPT:1\n") < 0);
8572 if (compose->privacy_system) {
8573 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
8574 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
8575 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
8578 /* Message-ID of message replying to */
8579 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
8582 folderid = folder_item_get_identifier(compose->replyinfo->folder);
8583 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
8586 /* Message-ID of message forwarding to */
8587 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
8590 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
8591 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
8595 /* end of headers */
8596 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
8603 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
8607 if (fclose(fp) == EOF) {
8611 if (compose->targetinfo) {
8612 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
8613 flag.perm_flags = target_locked?MSG_LOCKED:0;
8615 flag.tmp_flags = MSG_DRAFT;
8617 folder_item_scan(draft);
8618 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
8619 MsgInfo *tmpinfo = NULL;
8620 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
8621 if (compose->msgid) {
8622 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
8625 msgnum = tmpinfo->msgnum;
8626 procmsg_msginfo_free(tmpinfo);
8627 debug_print("got draft msgnum %d from scanning\n", msgnum);
8629 debug_print("didn't get draft msgnum after scanning\n");
8632 debug_print("got draft msgnum %d from adding\n", msgnum);
8638 if (action != COMPOSE_AUTO_SAVE) {
8639 if (action != COMPOSE_DRAFT_FOR_EXIT)
8640 alertpanel_error(_("Could not save draft."));
8643 gtkut_window_popup(compose->window);
8644 val = alertpanel_full(_("Could not save draft"),
8645 _("Could not save draft.\n"
8646 "Do you want to cancel exit or discard this email?"),
8647 _("_Cancel exit"), _("_Discard email"), NULL,
8648 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
8649 if (val == G_ALERTALTERNATE) {
8651 g_mutex_unlock(compose->mutex); /* must be done before closing */
8652 compose_close(compose);
8656 g_mutex_unlock(compose->mutex); /* must be done before closing */
8665 if (compose->mode == COMPOSE_REEDIT) {
8666 compose_remove_reedit_target(compose, TRUE);
8669 newmsginfo = folder_item_get_msginfo(draft, msgnum);
8672 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
8674 procmsg_msginfo_set_flags(newmsginfo, MSG_LOCKED, MSG_DRAFT);
8676 procmsg_msginfo_set_flags(newmsginfo, 0, MSG_DRAFT);
8677 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
8678 procmsg_msginfo_set_flags(newmsginfo, 0,
8679 MSG_HAS_ATTACHMENT);
8681 if (action == COMPOSE_DRAFT_FOR_EXIT) {
8682 compose_register_draft(newmsginfo);
8684 procmsg_msginfo_free(newmsginfo);
8687 folder_item_scan(draft);
8689 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
8691 g_mutex_unlock(compose->mutex); /* must be done before closing */
8692 compose_close(compose);
8698 path = folder_item_fetch_msg(draft, msgnum);
8700 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
8703 if (g_stat(path, &s) < 0) {
8704 FILE_OP_ERROR(path, "stat");
8710 procmsg_msginfo_free(compose->targetinfo);
8711 compose->targetinfo = procmsg_msginfo_new();
8712 compose->targetinfo->msgnum = msgnum;
8713 compose->targetinfo->size = s.st_size;
8714 compose->targetinfo->mtime = s.st_mtime;
8715 compose->targetinfo->folder = draft;
8717 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
8718 compose->mode = COMPOSE_REEDIT;
8720 if (action == COMPOSE_AUTO_SAVE) {
8721 compose->autosaved_draft = compose->targetinfo;
8723 compose->modified = FALSE;
8724 compose_set_title(compose);
8728 g_mutex_unlock(compose->mutex);
8732 void compose_clear_exit_drafts(void)
8734 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8735 DRAFTED_AT_EXIT, NULL);
8736 if (is_file_exist(filepath))
8742 void compose_reopen_exit_drafts(void)
8744 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8745 DRAFTED_AT_EXIT, NULL);
8746 FILE *fp = fopen(filepath, "rb");
8750 while (fgets(buf, sizeof(buf), fp)) {
8751 gchar **parts = g_strsplit(buf, "\t", 2);
8752 const gchar *folder = parts[0];
8753 int msgnum = parts[1] ? atoi(parts[1]):-1;
8755 if (folder && *folder && msgnum > -1) {
8756 FolderItem *item = folder_find_item_from_identifier(folder);
8757 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
8759 compose_reedit(info, FALSE);
8766 compose_clear_exit_drafts();
8769 static void compose_draft_cb(gpointer data, guint action, GtkWidget *widget)
8771 compose_draft(data, action);
8774 static void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
8776 if (compose && file_list) {
8779 for ( tmp = file_list; tmp; tmp = tmp->next) {
8780 gchar *file = (gchar *) tmp->data;
8781 gchar *utf8_filename = conv_filename_to_utf8(file);
8782 compose_attach_append(compose, file, utf8_filename, NULL);
8783 compose_changed_cb(NULL, compose);
8788 g_free(utf8_filename);
8793 static void compose_attach_cb(gpointer data, guint action, GtkWidget *widget)
8795 Compose *compose = (Compose *)data;
8798 if (compose->redirect_filename != NULL)
8801 file_list = filesel_select_multiple_files_open(_("Select file"));
8804 compose_attach_from_list(compose, file_list, TRUE);
8805 g_list_free(file_list);
8809 static void compose_insert_file_cb(gpointer data, guint action,
8812 Compose *compose = (Compose *)data;
8815 file_list = filesel_select_multiple_files_open(_("Select file"));
8820 for ( tmp = file_list; tmp; tmp = tmp->next) {
8821 gchar *file = (gchar *) tmp->data;
8822 gchar *filedup = g_strdup(file);
8823 gchar *shortfile = g_path_get_basename(filedup);
8824 ComposeInsertResult res;
8826 res = compose_insert_file(compose, file);
8827 if (res == COMPOSE_INSERT_READ_ERROR) {
8828 alertpanel_error(_("File '%s' could not be read."), shortfile);
8829 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
8830 alertpanel_error(_("File '%s' contained invalid characters\n"
8831 "for the current encoding, insertion may be incorrect."), shortfile);
8837 g_list_free(file_list);
8841 static void compose_insert_sig_cb(gpointer data, guint action,
8844 Compose *compose = (Compose *)data;
8846 compose_insert_sig(compose, FALSE);
8849 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
8853 Compose *compose = (Compose *)data;
8855 gtkut_widget_get_uposition(widget, &x, &y);
8856 prefs_common.compose_x = x;
8857 prefs_common.compose_y = y;
8859 if (compose->sending || compose->updating)
8861 compose_close_cb(compose, 0, NULL);
8865 void compose_close_toolbar(Compose *compose)
8867 compose_close_cb(compose, 0, NULL);
8870 static void compose_close_cb(gpointer data, guint action, GtkWidget *widget)
8872 Compose *compose = (Compose *)data;
8876 if (compose->exteditor_tag != -1) {
8877 if (!compose_ext_editor_kill(compose))
8882 if (compose->modified) {
8883 val = alertpanel(_("Discard message"),
8884 _("This message has been modified. Discard it?"),
8885 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
8888 case G_ALERTDEFAULT:
8889 if (prefs_common.autosave)
8890 compose_remove_draft(compose);
8892 case G_ALERTALTERNATE:
8893 compose_draft_cb(data, COMPOSE_QUIT_EDITING, NULL);
8900 compose_close(compose);
8903 static void compose_set_encoding_cb(gpointer data, guint action,
8906 Compose *compose = (Compose *)data;
8908 if (GTK_CHECK_MENU_ITEM(widget)->active)
8909 compose->out_encoding = (CharSet)action;
8912 static void compose_address_cb(gpointer data, guint action, GtkWidget *widget)
8914 Compose *compose = (Compose *)data;
8916 addressbook_open(compose);
8919 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
8921 Compose *compose = (Compose *)data;
8926 tmpl = g_object_get_data(G_OBJECT(widget), "template");
8927 g_return_if_fail(tmpl != NULL);
8929 msg = g_strdup_printf(_("Do you want to apply the template '%s' ?"),
8931 val = alertpanel(_("Apply template"), msg,
8932 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
8935 if (val == G_ALERTDEFAULT)
8936 compose_template_apply(compose, tmpl, TRUE);
8937 else if (val == G_ALERTALTERNATE)
8938 compose_template_apply(compose, tmpl, FALSE);
8941 static void compose_ext_editor_cb(gpointer data, guint action,
8944 Compose *compose = (Compose *)data;
8946 compose_exec_ext_editor(compose);
8949 static void compose_undo_cb(Compose *compose)
8951 gboolean prev_autowrap = compose->autowrap;
8953 compose->autowrap = FALSE;
8954 undo_undo(compose->undostruct);
8955 compose->autowrap = prev_autowrap;
8958 static void compose_redo_cb(Compose *compose)
8960 gboolean prev_autowrap = compose->autowrap;
8962 compose->autowrap = FALSE;
8963 undo_redo(compose->undostruct);
8964 compose->autowrap = prev_autowrap;
8967 static void entry_cut_clipboard(GtkWidget *entry)
8969 if (GTK_IS_EDITABLE(entry))
8970 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
8971 else if (GTK_IS_TEXT_VIEW(entry))
8972 gtk_text_buffer_cut_clipboard(
8973 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
8974 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
8978 static void entry_copy_clipboard(GtkWidget *entry)
8980 if (GTK_IS_EDITABLE(entry))
8981 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
8982 else if (GTK_IS_TEXT_VIEW(entry))
8983 gtk_text_buffer_copy_clipboard(
8984 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
8985 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
8988 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
8989 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
8991 if (GTK_IS_TEXT_VIEW(entry)) {
8992 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
8993 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
8994 GtkTextIter start_iter, end_iter;
8996 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
8998 if (contents == NULL)
9001 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
9003 /* we shouldn't delete the selection when middle-click-pasting, or we
9004 * can't mid-click-paste our own selection */
9005 if (clip != GDK_SELECTION_PRIMARY) {
9006 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
9009 if (insert_place == NULL) {
9010 /* if insert_place isn't specified, insert at the cursor.
9011 * used for Ctrl-V pasting */
9012 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
9013 start = gtk_text_iter_get_offset(&start_iter);
9014 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
9016 /* if insert_place is specified, paste here.
9017 * used for mid-click-pasting */
9018 start = gtk_text_iter_get_offset(insert_place);
9019 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
9023 /* paste unwrapped: mark the paste so it's not wrapped later */
9024 end = start + strlen(contents);
9025 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
9026 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
9027 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
9028 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
9029 /* rewrap paragraph now (after a mid-click-paste) */
9030 mark_start = gtk_text_buffer_get_insert(buffer);
9031 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
9032 gtk_text_iter_backward_char(&start_iter);
9033 compose_beautify_paragraph(compose, &start_iter, TRUE);
9035 } else if (GTK_IS_EDITABLE(entry))
9036 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
9040 static void entry_allsel(GtkWidget *entry)
9042 if (GTK_IS_EDITABLE(entry))
9043 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
9044 else if (GTK_IS_TEXT_VIEW(entry)) {
9045 GtkTextIter startiter, enditer;
9046 GtkTextBuffer *textbuf;
9048 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
9049 gtk_text_buffer_get_start_iter(textbuf, &startiter);
9050 gtk_text_buffer_get_end_iter(textbuf, &enditer);
9052 gtk_text_buffer_move_mark_by_name(textbuf,
9053 "selection_bound", &startiter);
9054 gtk_text_buffer_move_mark_by_name(textbuf,
9055 "insert", &enditer);
9059 static void compose_cut_cb(Compose *compose)
9061 if (compose->focused_editable
9063 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9066 entry_cut_clipboard(compose->focused_editable);
9069 static void compose_copy_cb(Compose *compose)
9071 if (compose->focused_editable
9073 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9076 entry_copy_clipboard(compose->focused_editable);
9079 static void compose_paste_cb(Compose *compose)
9082 GtkTextBuffer *buffer;
9084 if (compose->focused_editable &&
9085 GTK_WIDGET_HAS_FOCUS(compose->focused_editable))
9086 entry_paste_clipboard(compose, compose->focused_editable,
9087 prefs_common.linewrap_pastes,
9088 GDK_SELECTION_CLIPBOARD, NULL);
9092 static void compose_paste_as_quote_cb(Compose *compose)
9094 gint wrap_quote = prefs_common.linewrap_quote;
9095 if (compose->focused_editable
9097 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9100 /* let text_insert() (called directly or at a later time
9101 * after the gtk_editable_paste_clipboard) know that
9102 * text is to be inserted as a quotation. implemented
9103 * by using a simple refcount... */
9104 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
9105 G_OBJECT(compose->focused_editable),
9106 "paste_as_quotation"));
9107 g_object_set_data(G_OBJECT(compose->focused_editable),
9108 "paste_as_quotation",
9109 GINT_TO_POINTER(paste_as_quotation + 1));
9110 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
9111 entry_paste_clipboard(compose, compose->focused_editable,
9112 prefs_common.linewrap_pastes,
9113 GDK_SELECTION_CLIPBOARD, NULL);
9114 prefs_common.linewrap_quote = wrap_quote;
9118 static void compose_paste_no_wrap_cb(Compose *compose)
9121 GtkTextBuffer *buffer;
9123 if (compose->focused_editable
9125 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9128 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
9129 GDK_SELECTION_CLIPBOARD, NULL);
9133 static void compose_paste_wrap_cb(Compose *compose)
9136 GtkTextBuffer *buffer;
9138 if (compose->focused_editable
9140 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9143 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
9144 GDK_SELECTION_CLIPBOARD, NULL);
9148 static void compose_allsel_cb(Compose *compose)
9150 if (compose->focused_editable
9152 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9155 entry_allsel(compose->focused_editable);
9158 static void textview_move_beginning_of_line (GtkTextView *text)
9160 GtkTextBuffer *buffer;
9164 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9166 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9167 mark = gtk_text_buffer_get_insert(buffer);
9168 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9169 gtk_text_iter_set_line_offset(&ins, 0);
9170 gtk_text_buffer_place_cursor(buffer, &ins);
9173 static void textview_move_forward_character (GtkTextView *text)
9175 GtkTextBuffer *buffer;
9179 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9181 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9182 mark = gtk_text_buffer_get_insert(buffer);
9183 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9184 if (gtk_text_iter_forward_cursor_position(&ins))
9185 gtk_text_buffer_place_cursor(buffer, &ins);
9188 static void textview_move_backward_character (GtkTextView *text)
9190 GtkTextBuffer *buffer;
9194 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9196 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9197 mark = gtk_text_buffer_get_insert(buffer);
9198 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9199 if (gtk_text_iter_backward_cursor_position(&ins))
9200 gtk_text_buffer_place_cursor(buffer, &ins);
9203 static void textview_move_forward_word (GtkTextView *text)
9205 GtkTextBuffer *buffer;
9210 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9212 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9213 mark = gtk_text_buffer_get_insert(buffer);
9214 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9215 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
9216 if (gtk_text_iter_forward_word_ends(&ins, count)) {
9217 gtk_text_iter_backward_word_start(&ins);
9218 gtk_text_buffer_place_cursor(buffer, &ins);
9222 static void textview_move_backward_word (GtkTextView *text)
9224 GtkTextBuffer *buffer;
9229 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9231 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9232 mark = gtk_text_buffer_get_insert(buffer);
9233 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9234 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
9235 if (gtk_text_iter_backward_word_starts(&ins, 1))
9236 gtk_text_buffer_place_cursor(buffer, &ins);
9239 static void textview_move_end_of_line (GtkTextView *text)
9241 GtkTextBuffer *buffer;
9245 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9247 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9248 mark = gtk_text_buffer_get_insert(buffer);
9249 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9250 if (gtk_text_iter_forward_to_line_end(&ins))
9251 gtk_text_buffer_place_cursor(buffer, &ins);
9254 static void textview_move_next_line (GtkTextView *text)
9256 GtkTextBuffer *buffer;
9261 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9263 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9264 mark = gtk_text_buffer_get_insert(buffer);
9265 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9266 offset = gtk_text_iter_get_line_offset(&ins);
9267 if (gtk_text_iter_forward_line(&ins)) {
9268 gtk_text_iter_set_line_offset(&ins, offset);
9269 gtk_text_buffer_place_cursor(buffer, &ins);
9273 static void textview_move_previous_line (GtkTextView *text)
9275 GtkTextBuffer *buffer;
9280 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9282 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9283 mark = gtk_text_buffer_get_insert(buffer);
9284 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9285 offset = gtk_text_iter_get_line_offset(&ins);
9286 if (gtk_text_iter_backward_line(&ins)) {
9287 gtk_text_iter_set_line_offset(&ins, offset);
9288 gtk_text_buffer_place_cursor(buffer, &ins);
9292 static void textview_delete_forward_character (GtkTextView *text)
9294 GtkTextBuffer *buffer;
9296 GtkTextIter ins, end_iter;
9298 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9300 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9301 mark = gtk_text_buffer_get_insert(buffer);
9302 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9304 if (gtk_text_iter_forward_char(&end_iter)) {
9305 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9309 static void textview_delete_backward_character (GtkTextView *text)
9311 GtkTextBuffer *buffer;
9313 GtkTextIter ins, end_iter;
9315 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9317 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9318 mark = gtk_text_buffer_get_insert(buffer);
9319 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9321 if (gtk_text_iter_backward_char(&end_iter)) {
9322 gtk_text_buffer_delete(buffer, &end_iter, &ins);
9326 static void textview_delete_forward_word (GtkTextView *text)
9328 GtkTextBuffer *buffer;
9330 GtkTextIter ins, end_iter;
9332 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9334 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9335 mark = gtk_text_buffer_get_insert(buffer);
9336 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9338 if (gtk_text_iter_forward_word_end(&end_iter)) {
9339 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9343 static void textview_delete_backward_word (GtkTextView *text)
9345 GtkTextBuffer *buffer;
9347 GtkTextIter ins, end_iter;
9349 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9351 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9352 mark = gtk_text_buffer_get_insert(buffer);
9353 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9355 if (gtk_text_iter_backward_word_start(&end_iter)) {
9356 gtk_text_buffer_delete(buffer, &end_iter, &ins);
9360 static void textview_delete_line (GtkTextView *text)
9362 GtkTextBuffer *buffer;
9364 GtkTextIter ins, start_iter, end_iter;
9367 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9369 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9370 mark = gtk_text_buffer_get_insert(buffer);
9371 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9374 gtk_text_iter_set_line_offset(&start_iter, 0);
9377 if (gtk_text_iter_ends_line(&end_iter))
9378 found = gtk_text_iter_forward_char(&end_iter);
9380 found = gtk_text_iter_forward_to_line_end(&end_iter);
9383 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
9386 static void textview_delete_to_line_end (GtkTextView *text)
9388 GtkTextBuffer *buffer;
9390 GtkTextIter ins, end_iter;
9393 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9395 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9396 mark = gtk_text_buffer_get_insert(buffer);
9397 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9399 if (gtk_text_iter_ends_line(&end_iter))
9400 found = gtk_text_iter_forward_char(&end_iter);
9402 found = gtk_text_iter_forward_to_line_end(&end_iter);
9404 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9407 static void compose_advanced_action_cb(Compose *compose,
9408 ComposeCallAdvancedAction action)
9410 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9412 void (*do_action) (GtkTextView *text);
9413 } action_table[] = {
9414 {textview_move_beginning_of_line},
9415 {textview_move_forward_character},
9416 {textview_move_backward_character},
9417 {textview_move_forward_word},
9418 {textview_move_backward_word},
9419 {textview_move_end_of_line},
9420 {textview_move_next_line},
9421 {textview_move_previous_line},
9422 {textview_delete_forward_character},
9423 {textview_delete_backward_character},
9424 {textview_delete_forward_word},
9425 {textview_delete_backward_word},
9426 {textview_delete_line},
9427 {NULL}, /* gtk_stext_delete_line_n */
9428 {textview_delete_to_line_end}
9431 if (!GTK_WIDGET_HAS_FOCUS(text)) return;
9433 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
9434 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
9435 if (action_table[action].do_action)
9436 action_table[action].do_action(text);
9438 g_warning("Not implemented yet.");
9442 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
9446 if (GTK_IS_EDITABLE(widget)) {
9447 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
9448 gtk_editable_set_position(GTK_EDITABLE(widget),
9451 if (widget->parent && widget->parent->parent
9452 && widget->parent->parent->parent) {
9453 if (GTK_IS_SCROLLED_WINDOW(widget->parent->parent->parent)) {
9454 gint y = widget->allocation.y;
9455 gint height = widget->allocation.height;
9456 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
9457 (GTK_SCROLLED_WINDOW(widget->parent->parent->parent));
9459 if (y < (int)shown->value) {
9460 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown), y - 1);
9462 if (y + height > (int)shown->value + (int)shown->page_size) {
9463 if (y - height - 1 < (int)shown->upper - (int)shown->page_size) {
9464 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
9465 y + height - (int)shown->page_size - 1);
9467 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
9468 (int)shown->upper - (int)shown->page_size - 1);
9475 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
9476 compose->focused_editable = widget;
9479 if (GTK_IS_TEXT_VIEW(widget)
9480 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
9481 gtk_widget_ref(compose->notebook);
9482 gtk_widget_ref(compose->edit_vbox);
9483 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
9484 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
9485 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
9486 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
9487 gtk_widget_unref(compose->notebook);
9488 gtk_widget_unref(compose->edit_vbox);
9489 g_signal_handlers_block_by_func(G_OBJECT(widget),
9490 G_CALLBACK(compose_grab_focus_cb),
9492 gtk_widget_grab_focus(widget);
9493 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
9494 G_CALLBACK(compose_grab_focus_cb),
9496 } else if (!GTK_IS_TEXT_VIEW(widget)
9497 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
9498 gtk_widget_ref(compose->notebook);
9499 gtk_widget_ref(compose->edit_vbox);
9500 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
9501 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
9502 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
9503 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
9504 gtk_widget_unref(compose->notebook);
9505 gtk_widget_unref(compose->edit_vbox);
9506 g_signal_handlers_block_by_func(G_OBJECT(widget),
9507 G_CALLBACK(compose_grab_focus_cb),
9509 gtk_widget_grab_focus(widget);
9510 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
9511 G_CALLBACK(compose_grab_focus_cb),
9517 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
9519 compose->modified = TRUE;
9521 compose_set_title(compose);
9525 static void compose_wrap_cb(gpointer data, guint action, GtkWidget *widget)
9527 Compose *compose = (Compose *)data;
9530 compose_wrap_all_full(compose, TRUE);
9532 compose_beautify_paragraph(compose, NULL, TRUE);
9535 static void compose_find_cb(gpointer data, guint action, GtkWidget *widget)
9537 Compose *compose = (Compose *)data;
9539 message_search_compose(compose);
9542 static void compose_toggle_autowrap_cb(gpointer data, guint action,
9545 Compose *compose = (Compose *)data;
9546 compose->autowrap = GTK_CHECK_MENU_ITEM(widget)->active;
9547 if (compose->autowrap)
9548 compose_wrap_all_full(compose, TRUE);
9549 compose->autowrap = GTK_CHECK_MENU_ITEM(widget)->active;
9552 static void compose_toggle_sign_cb(gpointer data, guint action,
9555 Compose *compose = (Compose *)data;
9557 if (GTK_CHECK_MENU_ITEM(widget)->active)
9558 compose->use_signing = TRUE;
9560 compose->use_signing = FALSE;
9563 static void compose_toggle_encrypt_cb(gpointer data, guint action,
9566 Compose *compose = (Compose *)data;
9568 if (GTK_CHECK_MENU_ITEM(widget)->active)
9569 compose->use_encryption = TRUE;
9571 compose->use_encryption = FALSE;
9574 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
9576 g_free(compose->privacy_system);
9578 compose->privacy_system = g_strdup(account->default_privacy_system);
9579 compose_update_privacy_system_menu_item(compose, warn);
9582 static void compose_toggle_ruler_cb(gpointer data, guint action,
9585 Compose *compose = (Compose *)data;
9587 if (GTK_CHECK_MENU_ITEM(widget)->active) {
9588 gtk_widget_show(compose->ruler_hbox);
9589 prefs_common.show_ruler = TRUE;
9591 gtk_widget_hide(compose->ruler_hbox);
9592 gtk_widget_queue_resize(compose->edit_vbox);
9593 prefs_common.show_ruler = FALSE;
9597 static void compose_attach_drag_received_cb (GtkWidget *widget,
9598 GdkDragContext *context,
9601 GtkSelectionData *data,
9606 Compose *compose = (Compose *)user_data;
9609 if (gdk_atom_name(data->type) &&
9610 !strcmp(gdk_atom_name(data->type), "text/uri-list")
9611 && gtk_drag_get_source_widget(context) !=
9612 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
9613 list = uri_list_extract_filenames((const gchar *)data->data);
9614 for (tmp = list; tmp != NULL; tmp = tmp->next) {
9615 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
9616 compose_attach_append
9617 (compose, (const gchar *)tmp->data,
9618 utf8_filename, NULL);
9619 g_free(utf8_filename);
9621 if (list) compose_changed_cb(NULL, compose);
9622 list_free_strings(list);
9624 } else if (gtk_drag_get_source_widget(context)
9625 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
9626 /* comes from our summaryview */
9627 SummaryView * summaryview = NULL;
9628 GSList * list = NULL, *cur = NULL;
9630 if (mainwindow_get_mainwindow())
9631 summaryview = mainwindow_get_mainwindow()->summaryview;
9634 list = summary_get_selected_msg_list(summaryview);
9636 for (cur = list; cur; cur = cur->next) {
9637 MsgInfo *msginfo = (MsgInfo *)cur->data;
9640 file = procmsg_get_message_file_full(msginfo,
9643 compose_attach_append(compose, (const gchar *)file,
9644 (const gchar *)file, "message/rfc822");
9652 static gboolean compose_drag_drop(GtkWidget *widget,
9653 GdkDragContext *drag_context,
9655 guint time, gpointer user_data)
9657 /* not handling this signal makes compose_insert_drag_received_cb
9662 static void compose_insert_drag_received_cb (GtkWidget *widget,
9663 GdkDragContext *drag_context,
9666 GtkSelectionData *data,
9671 Compose *compose = (Compose *)user_data;
9674 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
9676 if (gdk_atom_name(data->type) && !strcmp(gdk_atom_name(data->type), "text/uri-list")) {
9677 AlertValue val = G_ALERTDEFAULT;
9679 switch (prefs_common.compose_dnd_mode) {
9680 case COMPOSE_DND_ASK:
9681 val = alertpanel_full(_("Insert or attach?"),
9682 _("Do you want to insert the contents of the file(s) "
9683 "into the message body, or attach it to the email?"),
9684 GTK_STOCK_CANCEL, _("+_Insert"), _("_Attach"),
9685 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
9687 case COMPOSE_DND_INSERT:
9688 val = G_ALERTALTERNATE;
9690 case COMPOSE_DND_ATTACH:
9694 /* unexpected case */
9695 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
9698 if (val & G_ALERTDISABLE) {
9699 val &= ~G_ALERTDISABLE;
9700 /* remember what action to perform by default, only if we don't click Cancel */
9701 if (val == G_ALERTALTERNATE)
9702 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
9703 else if (val == G_ALERTOTHER)
9704 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
9707 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
9708 gtk_drag_finish(drag_context, FALSE, FALSE, time);
9710 } else if (val == G_ALERTOTHER) {
9711 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
9714 list = uri_list_extract_filenames((const gchar *)data->data);
9715 for (tmp = list; tmp != NULL; tmp = tmp->next) {
9716 compose_insert_file(compose, (const gchar *)tmp->data);
9718 list_free_strings(list);
9720 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9723 #if GTK_CHECK_VERSION(2, 8, 0)
9724 /* do nothing, handled by GTK */
9726 gchar *tmpfile = get_tmp_file();
9727 str_write_to_file((const gchar *)data->data, tmpfile);
9728 compose_insert_file(compose, tmpfile);
9731 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9735 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9738 static void compose_header_drag_received_cb (GtkWidget *widget,
9739 GdkDragContext *drag_context,
9742 GtkSelectionData *data,
9747 GtkEditable *entry = (GtkEditable *)user_data;
9748 gchar *email = (gchar *)data->data;
9750 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
9753 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
9754 gchar *decoded=g_new(gchar, strlen(email));
9757 email += strlen("mailto:");
9758 decode_uri(decoded, email); /* will fit */
9759 gtk_editable_delete_text(entry, 0, -1);
9760 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
9761 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9765 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9768 static void compose_toggle_return_receipt_cb(gpointer data, guint action,
9771 Compose *compose = (Compose *)data;
9773 if (GTK_CHECK_MENU_ITEM(widget)->active)
9774 compose->return_receipt = TRUE;
9776 compose->return_receipt = FALSE;
9779 static void compose_toggle_remove_refs_cb(gpointer data, guint action,
9782 Compose *compose = (Compose *)data;
9784 if (GTK_CHECK_MENU_ITEM(widget)->active)
9785 compose->remove_references = TRUE;
9787 compose->remove_references = FALSE;
9790 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
9792 ComposeHeaderEntry *headerentry)
9794 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
9795 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
9796 !(event->state & GDK_MODIFIER_MASK) &&
9797 (event->keyval == GDK_BackSpace) &&
9798 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
9799 gtk_container_remove
9800 (GTK_CONTAINER(headerentry->compose->header_table),
9801 headerentry->combo);
9802 gtk_container_remove
9803 (GTK_CONTAINER(headerentry->compose->header_table),
9804 headerentry->entry);
9805 headerentry->compose->header_list =
9806 g_slist_remove(headerentry->compose->header_list,
9808 g_free(headerentry);
9809 } else if (event->keyval == GDK_Tab) {
9810 if (headerentry->compose->header_last == headerentry) {
9811 /* Override default next focus, and give it to subject_entry
9812 * instead of notebook tabs
9814 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
9815 gtk_widget_grab_focus(headerentry->compose->subject_entry);
9822 static gboolean compose_headerentry_changed_cb(GtkWidget *entry,
9823 ComposeHeaderEntry *headerentry)
9825 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
9826 compose_create_header_entry(headerentry->compose);
9827 g_signal_handlers_disconnect_matched
9828 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
9829 0, 0, NULL, NULL, headerentry);
9831 /* Automatically scroll down */
9832 compose_show_first_last_header(headerentry->compose, FALSE);
9838 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
9840 GtkAdjustment *vadj;
9842 g_return_if_fail(compose);
9843 g_return_if_fail(GTK_IS_WIDGET(compose->header_table));
9844 g_return_if_fail(GTK_IS_VIEWPORT(compose->header_table->parent));
9846 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(compose->header_table->parent));
9847 gtk_adjustment_set_value(vadj, (show_first ? vadj->lower : vadj->upper));
9848 gtk_adjustment_changed(vadj);
9851 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
9852 const gchar *text, gint len, Compose *compose)
9854 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
9855 (G_OBJECT(compose->text), "paste_as_quotation"));
9858 g_return_if_fail(text != NULL);
9860 g_signal_handlers_block_by_func(G_OBJECT(buffer),
9861 G_CALLBACK(text_inserted),
9863 if (paste_as_quotation) {
9867 GtkTextIter start_iter;
9872 new_text = g_strndup(text, len);
9874 qmark = compose_quote_char_from_context(compose);
9876 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
9877 gtk_text_buffer_place_cursor(buffer, iter);
9879 pos = gtk_text_iter_get_offset(iter);
9881 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
9882 _("Quote format error at line %d."));
9883 quote_fmt_reset_vartable();
9885 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
9886 GINT_TO_POINTER(paste_as_quotation - 1));
9888 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
9889 gtk_text_buffer_place_cursor(buffer, iter);
9890 gtk_text_buffer_delete_mark(buffer, mark);
9892 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
9893 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
9894 compose_beautify_paragraph(compose, &start_iter, FALSE);
9895 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
9896 gtk_text_buffer_delete_mark(buffer, mark);
9898 if (strcmp(text, "\n") || compose->automatic_break
9899 || gtk_text_iter_starts_line(iter))
9900 gtk_text_buffer_insert(buffer, iter, text, len);
9902 debug_print("insert nowrap \\n\n");
9903 gtk_text_buffer_insert_with_tags_by_name(buffer,
9904 iter, text, len, "no_join", NULL);
9908 if (!paste_as_quotation) {
9909 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
9910 compose_beautify_paragraph(compose, iter, FALSE);
9911 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
9912 gtk_text_buffer_delete_mark(buffer, mark);
9915 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
9916 G_CALLBACK(text_inserted),
9918 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
9920 if (prefs_common.autosave &&
9921 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
9922 compose->draft_timeout_tag != -2 /* disabled while loading */)
9923 compose->draft_timeout_tag = g_timeout_add
9924 (500, (GtkFunction) compose_defer_auto_save_draft, compose);
9926 static gint compose_defer_auto_save_draft(Compose *compose)
9928 compose->draft_timeout_tag = -1;
9929 compose_draft_cb((gpointer)compose, COMPOSE_AUTO_SAVE, NULL);
9934 static void compose_check_all(Compose *compose)
9936 if (compose->gtkaspell)
9937 gtkaspell_check_all(compose->gtkaspell);
9940 static void compose_highlight_all(Compose *compose)
9942 if (compose->gtkaspell)
9943 gtkaspell_highlight_all(compose->gtkaspell);
9946 static void compose_check_backwards(Compose *compose)
9948 if (compose->gtkaspell)
9949 gtkaspell_check_backwards(compose->gtkaspell);
9951 GtkItemFactory *ifactory;
9952 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
9953 menu_set_sensitive(ifactory, "/Edit/Check backwards misspelled word", FALSE);
9954 menu_set_sensitive(ifactory, "/Edit/Forward to next misspelled word", FALSE);
9958 static void compose_check_forwards_go(Compose *compose)
9960 if (compose->gtkaspell)
9961 gtkaspell_check_forwards_go(compose->gtkaspell);
9963 GtkItemFactory *ifactory;
9964 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
9965 menu_set_sensitive(ifactory, "/Edit/Check backwards misspelled word", FALSE);
9966 menu_set_sensitive(ifactory, "/Edit/Forward to next misspelled word", FALSE);
9972 *\brief Guess originating forward account from MsgInfo and several
9973 * "common preference" settings. Return NULL if no guess.
9975 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
9977 PrefsAccount *account = NULL;
9979 g_return_val_if_fail(msginfo, NULL);
9980 g_return_val_if_fail(msginfo->folder, NULL);
9981 g_return_val_if_fail(msginfo->folder->prefs, NULL);
9983 if (msginfo->folder->prefs->enable_default_account)
9984 account = account_find_from_id(msginfo->folder->prefs->default_account);
9987 account = msginfo->folder->folder->account;
9989 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
9991 Xstrdup_a(to, msginfo->to, return NULL);
9992 extract_address(to);
9993 account = account_find_from_address(to);
9996 if (!account && prefs_common.forward_account_autosel) {
9998 if (!procheader_get_header_from_msginfo
9999 (msginfo, cc,sizeof cc , "Cc:")) {
10000 gchar *buf = cc + strlen("Cc:");
10001 extract_address(buf);
10002 account = account_find_from_address(buf);
10006 if (!account && prefs_common.forward_account_autosel) {
10007 gchar deliveredto[BUFFSIZE];
10008 if (!procheader_get_header_from_msginfo
10009 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
10010 gchar *buf = deliveredto + strlen("Delivered-To:");
10011 extract_address(buf);
10012 account = account_find_from_address(buf);
10019 gboolean compose_close(Compose *compose)
10023 if (!g_mutex_trylock(compose->mutex)) {
10024 /* we have to wait for the (possibly deferred by auto-save)
10025 * drafting to be done, before destroying the compose under
10027 debug_print("waiting for drafting to finish...\n");
10028 compose_allow_user_actions(compose, FALSE);
10029 g_timeout_add (500, (GSourceFunc) compose_close, compose);
10032 g_return_val_if_fail(compose, FALSE);
10033 gtkut_widget_get_uposition(compose->window, &x, &y);
10034 prefs_common.compose_x = x;
10035 prefs_common.compose_y = y;
10036 g_mutex_unlock(compose->mutex);
10037 compose_destroy(compose);
10042 * Add entry field for each address in list.
10043 * \param compose E-Mail composition object.
10044 * \param listAddress List of (formatted) E-Mail addresses.
10046 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
10049 node = listAddress;
10051 addr = ( gchar * ) node->data;
10052 compose_entry_append( compose, addr, COMPOSE_TO );
10053 node = g_list_next( node );
10057 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
10058 guint action, gboolean opening_multiple)
10060 gchar *body = NULL;
10061 GSList *new_msglist = NULL;
10062 MsgInfo *tmp_msginfo = NULL;
10063 gboolean originally_enc = FALSE;
10064 Compose *compose = NULL;
10066 g_return_if_fail(msgview != NULL);
10068 g_return_if_fail(msginfo_list != NULL);
10070 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
10071 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
10072 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
10074 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
10075 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
10076 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
10077 orig_msginfo, mimeinfo);
10078 if (tmp_msginfo != NULL) {
10079 new_msglist = g_slist_append(NULL, tmp_msginfo);
10081 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
10082 tmp_msginfo->folder = orig_msginfo->folder;
10083 tmp_msginfo->msgnum = orig_msginfo->msgnum;
10084 if (orig_msginfo->tags)
10085 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
10090 if (!opening_multiple)
10091 body = messageview_get_selection(msgview);
10094 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
10095 procmsg_msginfo_free(tmp_msginfo);
10096 g_slist_free(new_msglist);
10098 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
10100 if (compose && originally_enc) {
10101 compose_force_encryption(compose, compose->account, FALSE);
10107 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
10110 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
10111 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
10112 GSList *cur = msginfo_list;
10113 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
10114 "messages. Opening the windows "
10115 "could take some time. Do you "
10116 "want to continue?"),
10117 g_slist_length(msginfo_list));
10118 if (g_slist_length(msginfo_list) > 9
10119 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
10120 != G_ALERTALTERNATE) {
10125 /* We'll open multiple compose windows */
10126 /* let the WM place the next windows */
10127 compose_force_window_origin = FALSE;
10128 for (; cur; cur = cur->next) {
10130 tmplist.data = cur->data;
10131 tmplist.next = NULL;
10132 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
10134 compose_force_window_origin = TRUE;
10136 /* forwarding multiple mails as attachments is done via a
10137 * single compose window */
10138 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
10142 void compose_set_position(Compose *compose, gint pos)
10144 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10146 gtkut_text_view_set_position(text, pos);
10149 gboolean compose_search_string(Compose *compose,
10150 const gchar *str, gboolean case_sens)
10152 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10154 return gtkut_text_view_search_string(text, str, case_sens);
10157 gboolean compose_search_string_backward(Compose *compose,
10158 const gchar *str, gboolean case_sens)
10160 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10162 return gtkut_text_view_search_string_backward(text, str, case_sens);
10165 /* allocate a msginfo structure and populate its data from a compose data structure */
10166 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
10168 MsgInfo *newmsginfo;
10170 gchar buf[BUFFSIZE];
10172 g_return_val_if_fail( compose != NULL, NULL );
10174 newmsginfo = procmsg_msginfo_new();
10177 get_rfc822_date(buf, sizeof(buf));
10178 newmsginfo->date = g_strdup(buf);
10181 if (compose->from_name) {
10182 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
10183 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
10187 if (compose->subject_entry)
10188 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
10190 /* to, cc, reply-to, newsgroups */
10191 for (list = compose->header_list; list; list = list->next) {
10192 gchar *header = gtk_editable_get_chars(
10194 GTK_BIN(((ComposeHeaderEntry *)list->data)->combo)->child), 0, -1);
10195 gchar *entry = gtk_editable_get_chars(
10196 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
10198 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
10199 if ( newmsginfo->to == NULL ) {
10200 newmsginfo->to = g_strdup(entry);
10201 } else if (entry && *entry) {
10202 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
10203 g_free(newmsginfo->to);
10204 newmsginfo->to = tmp;
10207 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
10208 if ( newmsginfo->cc == NULL ) {
10209 newmsginfo->cc = g_strdup(entry);
10210 } else if (entry && *entry) {
10211 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
10212 g_free(newmsginfo->cc);
10213 newmsginfo->cc = tmp;
10216 if ( strcasecmp(header,
10217 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
10218 if ( newmsginfo->newsgroups == NULL ) {
10219 newmsginfo->newsgroups = g_strdup(entry);
10220 } else if (entry && *entry) {
10221 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
10222 g_free(newmsginfo->newsgroups);
10223 newmsginfo->newsgroups = tmp;
10231 /* other data is unset */
10237 /* update compose's dictionaries from folder dict settings */
10238 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
10239 FolderItem *folder_item)
10241 g_return_if_fail(compose != NULL);
10243 if (compose->gtkaspell && folder_item && folder_item->prefs) {
10244 FolderItemPrefs *prefs = folder_item->prefs;
10246 if (prefs->enable_default_dictionary)
10247 gtkaspell_change_dict(compose->gtkaspell,
10248 prefs->default_dictionary, FALSE);
10249 if (folder_item->prefs->enable_default_alt_dictionary)
10250 gtkaspell_change_alt_dict(compose->gtkaspell,
10251 prefs->default_alt_dictionary);
10252 if (prefs->enable_default_dictionary
10253 || prefs->enable_default_alt_dictionary)
10254 compose_spell_menu_changed(compose);