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 GtkItemFactoryEntry compose_popup_entries[] =
553 {N_("/_Add..."), NULL, compose_attach_cb, 0, NULL},
554 {N_("/_Remove"), NULL, compose_attach_remove_selected, 0, NULL},
555 {"/---", NULL, NULL, 0, "<Separator>"},
556 {N_("/_Properties..."), NULL, compose_attach_property, 0, NULL}
559 static GtkItemFactoryEntry compose_entries[] =
561 {N_("/_Message"), NULL, NULL, 0, "<Branch>"},
562 {N_("/_Message/S_end"), "<control>Return",
563 compose_send_cb, 0, NULL},
564 {N_("/_Message/Send _later"), "<shift><control>S",
565 compose_send_later_cb, 0, NULL},
566 {N_("/_Message/---"), NULL, NULL, 0, "<Separator>"},
567 {N_("/_Message/_Attach file"), "<control>M", compose_attach_cb, 0, NULL},
568 {N_("/_Message/_Insert file"), "<control>I", compose_insert_file_cb, 0, NULL},
569 {N_("/_Message/Insert si_gnature"), "<control>G", compose_insert_sig_cb, 0, NULL},
570 {N_("/_Message/---"), NULL, NULL, 0, "<Separator>"},
571 {N_("/_Message/_Save"),
572 "<control>S", compose_draft_cb, COMPOSE_KEEP_EDITING, NULL},
573 {N_("/_Message/---"), NULL, NULL, 0, "<Separator>"},
574 {N_("/_Message/_Close"), "<control>W", compose_close_cb, 0, NULL},
576 {N_("/_Edit"), NULL, NULL, 0, "<Branch>"},
577 {N_("/_Edit/_Undo"), "<control>Z", compose_undo_cb, 0, NULL},
578 {N_("/_Edit/_Redo"), "<control>Y", compose_redo_cb, 0, NULL},
579 {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
580 {N_("/_Edit/Cu_t"), "<control>X", compose_cut_cb, 0, NULL},
581 {N_("/_Edit/_Copy"), "<control>C", compose_copy_cb, 0, NULL},
582 {N_("/_Edit/_Paste"), "<control>V", compose_paste_cb, 0, NULL},
583 {N_("/_Edit/Special paste"), NULL, NULL, 0, "<Branch>"},
584 {N_("/_Edit/Special paste/as _quotation"),
585 NULL, compose_paste_as_quote_cb, 0, NULL},
586 {N_("/_Edit/Special paste/_wrapped"),
587 NULL, compose_paste_wrap_cb, 0, NULL},
588 {N_("/_Edit/Special paste/_unwrapped"),
589 NULL, compose_paste_no_wrap_cb, 0, NULL},
590 {N_("/_Edit/Select _all"), "<control>A", compose_allsel_cb, 0, NULL},
591 {N_("/_Edit/A_dvanced"), NULL, NULL, 0, "<Branch>"},
592 {N_("/_Edit/A_dvanced/Move a character backward"),
594 compose_advanced_action_cb,
595 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER,
597 {N_("/_Edit/A_dvanced/Move a character forward"),
599 compose_advanced_action_cb,
600 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER,
602 {N_("/_Edit/A_dvanced/Move a word backward"),
604 compose_advanced_action_cb,
605 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD,
607 {N_("/_Edit/A_dvanced/Move a word forward"),
609 compose_advanced_action_cb,
610 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD,
612 {N_("/_Edit/A_dvanced/Move to beginning of line"),
613 NULL, /* "<control>A" */
614 compose_advanced_action_cb,
615 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE,
617 {N_("/_Edit/A_dvanced/Move to end of line"),
619 compose_advanced_action_cb,
620 COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE,
622 {N_("/_Edit/A_dvanced/Move to previous line"),
624 compose_advanced_action_cb,
625 COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE,
627 {N_("/_Edit/A_dvanced/Move to next line"),
629 compose_advanced_action_cb,
630 COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE,
632 {N_("/_Edit/A_dvanced/Delete a character backward"),
634 compose_advanced_action_cb,
635 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER,
637 {N_("/_Edit/A_dvanced/Delete a character forward"),
639 compose_advanced_action_cb,
640 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER,
642 {N_("/_Edit/A_dvanced/Delete a word backward"),
643 NULL, /* "<control>W" */
644 compose_advanced_action_cb,
645 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD,
647 {N_("/_Edit/A_dvanced/Delete a word forward"),
648 NULL, /* "<alt>D", */
649 compose_advanced_action_cb,
650 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD,
652 {N_("/_Edit/A_dvanced/Delete line"),
654 compose_advanced_action_cb,
655 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE,
657 {N_("/_Edit/A_dvanced/Delete entire line"),
659 compose_advanced_action_cb,
660 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE_N,
662 {N_("/_Edit/A_dvanced/Delete to end of line"),
664 compose_advanced_action_cb,
665 COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END,
667 {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
669 "<control>F", compose_find_cb, 0, NULL},
670 {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
671 {N_("/_Edit/_Wrap current paragraph"),
672 "<control>L", compose_wrap_cb, 0, NULL},
673 {N_("/_Edit/Wrap all long _lines"),
674 "<control><alt>L", compose_wrap_cb, 1, NULL},
675 {N_("/_Edit/Aut_o wrapping"), "<shift><control>L", compose_toggle_autowrap_cb, 0, "<ToggleItem>"},
676 {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
677 {N_("/_Edit/Edit with e_xternal editor"),
678 "<shift><control>X", compose_ext_editor_cb, 0, NULL},
680 {N_("/_Spelling"), NULL, NULL, 0, "<Branch>"},
681 {N_("/_Spelling/_Check all or check selection"),
682 NULL, compose_check_all, 0, NULL},
683 {N_("/_Spelling/_Highlight all misspelled words"),
684 NULL, compose_highlight_all, 0, NULL},
685 {N_("/_Spelling/Check _backwards misspelled word"),
686 NULL, compose_check_backwards , 0, NULL},
687 {N_("/_Spelling/_Forward to next misspelled word"),
688 NULL, compose_check_forwards_go, 0, NULL},
689 {N_("/_Spelling/---"), NULL, NULL, 0, "<Separator>"},
690 {N_("/_Spelling/Options"),
691 NULL, NULL, 0, "<Branch>"},
693 {N_("/_Options"), NULL, NULL, 0, "<Branch>"},
694 {N_("/_Options/Reply _mode"), NULL, NULL, 0, "<Branch>"},
695 {N_("/_Options/Reply _mode/_Normal"), NULL, compose_reply_change_mode, COMPOSE_REPLY, "<RadioItem>"},
696 {N_("/_Options/Reply _mode/_All"), NULL, compose_reply_change_mode, COMPOSE_REPLY_TO_ALL, "/Options/Reply mode/Normal"},
697 {N_("/_Options/Reply _mode/_Sender"), NULL, compose_reply_change_mode, COMPOSE_REPLY_TO_SENDER, "/Options/Reply mode/Normal"},
698 {N_("/_Options/Reply _mode/_Mailing-list"), NULL, compose_reply_change_mode, COMPOSE_REPLY_TO_LIST, "/Options/Reply mode/Normal"},
699 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
700 {N_("/_Options/Privacy _System"), NULL, NULL, 0, "<Branch>"},
701 {N_("/_Options/Privacy _System/None"), NULL, NULL, 0, "<RadioItem>"},
702 {N_("/_Options/Si_gn"), NULL, compose_toggle_sign_cb , 0, "<ToggleItem>"},
703 {N_("/_Options/_Encrypt"), NULL, compose_toggle_encrypt_cb, 0, "<ToggleItem>"},
704 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
705 {N_("/_Options/_Priority"), NULL, NULL, 0, "<Branch>"},
706 {N_("/_Options/Priority/_Highest"), NULL, compose_set_priority_cb, PRIORITY_HIGHEST, "<RadioItem>"},
707 {N_("/_Options/Priority/Hi_gh"), NULL, compose_set_priority_cb, PRIORITY_HIGH, "/Options/Priority/Highest"},
708 {N_("/_Options/Priority/_Normal"), NULL, compose_set_priority_cb, PRIORITY_NORMAL, "/Options/Priority/Highest"},
709 {N_("/_Options/Priority/Lo_w"), NULL, compose_set_priority_cb, PRIORITY_LOW, "/Options/Priority/Highest"},
710 {N_("/_Options/Priority/_Lowest"), NULL, compose_set_priority_cb, PRIORITY_LOWEST, "/Options/Priority/Highest"},
711 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
712 {N_("/_Options/_Request Return Receipt"), NULL, compose_toggle_return_receipt_cb, 0, "<ToggleItem>"},
713 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
714 {N_("/_Options/Remo_ve references"), NULL, compose_toggle_remove_refs_cb, 0, "<ToggleItem>"},
715 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
717 #define ENC_ACTION(action) \
718 NULL, compose_set_encoding_cb, action, \
719 "/Options/Character encoding/Automatic"
721 {N_("/_Options/Character _encoding"), NULL, NULL, 0, "<Branch>"},
722 {N_("/_Options/Character _encoding/_Automatic"),
723 NULL, compose_set_encoding_cb, C_AUTO, "<RadioItem>"},
724 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
726 {N_("/_Options/Character _encoding/7bit ASCII (US-ASC_II)"),
727 ENC_ACTION(C_US_ASCII)},
728 {N_("/_Options/Character _encoding/Unicode (_UTF-8)"),
729 ENC_ACTION(C_UTF_8)},
730 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
732 {N_("/_Options/Character _encoding/Western European"), NULL, NULL, 0, "<Branch>"},
733 {N_("/_Options/Character _encoding/Western European/ISO-8859-_1"),
734 ENC_ACTION(C_ISO_8859_1)},
735 {N_("/_Options/Character _encoding/Western European/ISO-8859-15"),
736 ENC_ACTION(C_ISO_8859_15)},
737 {N_("/_Options/Character _encoding/Western European/Windows-1252"),
738 ENC_ACTION(C_WINDOWS_1252)},
740 {N_("/_Options/Character _encoding/Central European (ISO-8859-_2)"),
741 ENC_ACTION(C_ISO_8859_2)},
743 {N_("/_Options/Character _encoding/Baltic"), NULL, NULL, 0, "<Branch>"},
744 {N_("/_Options/Character _encoding/Baltic/ISO-8859-13"),
745 ENC_ACTION(C_ISO_8859_13)},
746 {N_("/_Options/Character _encoding/Baltic/ISO-8859-_4"),
747 ENC_ACTION(C_ISO_8859_4)},
749 {N_("/_Options/Character _encoding/Greek (ISO-8859-_7)"),
750 ENC_ACTION(C_ISO_8859_7)},
752 {N_("/_Options/Character _encoding/Hebrew"), NULL, NULL, 0, "<Branch>"},
753 {N_("/_Options/Character _encoding/Hebrew/ISO-8859-_8"),
754 ENC_ACTION(C_ISO_8859_8)},
755 {N_("/_Options/Character _encoding/Hebrew/Windows-1255"),
756 ENC_ACTION(C_WINDOWS_1255)},
758 {N_("/_Options/Character _encoding/Arabic"), NULL, NULL, 0, "<Branch>"},
759 {N_("/_Options/Character _encoding/Arabic/ISO-8859-_6"),
760 ENC_ACTION(C_ISO_8859_6)},
761 {N_("/_Options/Character _encoding/Arabic/Windows-1256"),
762 ENC_ACTION(C_CP1256)},
764 {N_("/_Options/Character _encoding/Turkish (ISO-8859-_9)"),
765 ENC_ACTION(C_ISO_8859_9)},
767 {N_("/_Options/Character _encoding/Cyrillic"), NULL, NULL, 0, "<Branch>"},
768 {N_("/_Options/Character _encoding/Cyrillic/ISO-8859-_5"),
769 ENC_ACTION(C_ISO_8859_5)},
770 {N_("/_Options/Character _encoding/Cyrillic/KOI8-_R"),
771 ENC_ACTION(C_KOI8_R)},
772 {N_("/_Options/Character _encoding/Cyrillic/KOI8-U"),
773 ENC_ACTION(C_KOI8_U)},
774 {N_("/_Options/Character _encoding/Cyrillic/Windows-1251"),
775 ENC_ACTION(C_WINDOWS_1251)},
777 {N_("/_Options/Character _encoding/Japanese"), NULL, NULL, 0, "<Branch>"},
778 {N_("/_Options/Character _encoding/Japanese/ISO-2022-_JP"),
779 ENC_ACTION(C_ISO_2022_JP)},
780 {N_("/_Options/Character _encoding/Japanese/ISO-2022-JP-2"),
781 ENC_ACTION(C_ISO_2022_JP_2)},
782 {N_("/_Options/Character _encoding/Japanese/_EUC-JP"),
783 ENC_ACTION(C_EUC_JP)},
784 {N_("/_Options/Character _encoding/Japanese/_Shift__JIS"),
785 ENC_ACTION(C_SHIFT_JIS)},
787 {N_("/_Options/Character _encoding/Chinese"), NULL, NULL, 0, "<Branch>"},
788 {N_("/_Options/Character _encoding/Chinese/Simplified (_GB2312)"),
789 ENC_ACTION(C_GB2312)},
790 {N_("/_Options/Character _encoding/Chinese/Simplified (GBK)"),
792 {N_("/_Options/Character _encoding/Chinese/Traditional (_Big5)"),
794 {N_("/_Options/Character _encoding/Chinese/Traditional (EUC-_TW)"),
795 ENC_ACTION(C_EUC_TW)},
797 {N_("/_Options/Character _encoding/Korean"), NULL, NULL, 0, "<Branch>"},
798 {N_("/_Options/Character _encoding/Korean/EUC-_KR"),
799 ENC_ACTION(C_EUC_KR)},
800 {N_("/_Options/Character _encoding/Korean/ISO-2022-KR"),
801 ENC_ACTION(C_ISO_2022_KR)},
803 {N_("/_Options/Character _encoding/Thai"), NULL, NULL, 0, "<Branch>"},
804 {N_("/_Options/Character _encoding/Thai/TIS-620"),
805 ENC_ACTION(C_TIS_620)},
806 {N_("/_Options/Character _encoding/Thai/Windows-874"),
807 ENC_ACTION(C_WINDOWS_874)},
809 {N_("/_Tools"), NULL, NULL, 0, "<Branch>"},
810 {N_("/_Tools/Show _ruler"), NULL, compose_toggle_ruler_cb, 0, "<ToggleItem>"},
811 {N_("/_Tools/_Address book"), "<shift><control>A", compose_address_cb , 0, NULL},
812 {N_("/_Tools/_Template"), NULL, NULL, 0, "<Branch>"},
813 {N_("/_Tools/Actio_ns"), NULL, NULL, 0, "<Branch>"},
814 {N_("/_Help"), NULL, NULL, 0, "<Branch>"},
815 {N_("/_Help/_About"), NULL, about_show, 0, NULL}
818 static GtkTargetEntry compose_mime_types[] =
820 {"text/uri-list", 0, 0},
821 {"UTF8_STRING", 0, 0},
825 static gboolean compose_put_existing_to_front(MsgInfo *info)
827 GList *compose_list = compose_get_compose_list();
831 for (elem = compose_list; elem != NULL && elem->data != NULL;
833 Compose *c = (Compose*)elem->data;
835 if (!c->targetinfo || !c->targetinfo->msgid ||
839 if (!strcmp(c->targetinfo->msgid, info->msgid)) {
840 gtkut_window_popup(c->window);
848 static GdkColor quote_color1 =
849 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
850 static GdkColor quote_color2 =
851 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
852 static GdkColor quote_color3 =
853 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
855 static GdkColor quote_bgcolor1 =
856 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
857 static GdkColor quote_bgcolor2 =
858 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
859 static GdkColor quote_bgcolor3 =
860 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
862 static GdkColor signature_color = {
869 static GdkColor uri_color = {
876 static void compose_create_tags(GtkTextView *text, Compose *compose)
878 GtkTextBuffer *buffer;
879 GdkColor black = {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
885 buffer = gtk_text_view_get_buffer(text);
887 if (prefs_common.enable_color) {
888 /* grab the quote colors, converting from an int to a GdkColor */
889 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
891 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
893 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
895 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_bgcol,
897 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_bgcol,
899 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_bgcol,
901 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
903 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
906 signature_color = quote_color1 = quote_color2 = quote_color3 =
907 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
910 if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
911 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
912 "foreground-gdk", "e_color1,
913 "paragraph-background-gdk", "e_bgcolor1,
915 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
916 "foreground-gdk", "e_color2,
917 "paragraph-background-gdk", "e_bgcolor2,
919 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
920 "foreground-gdk", "e_color3,
921 "paragraph-background-gdk", "e_bgcolor3,
924 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
925 "foreground-gdk", "e_color1,
927 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
928 "foreground-gdk", "e_color2,
930 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
931 "foreground-gdk", "e_color3,
935 compose->signature_tag = gtk_text_buffer_create_tag(buffer, "signature",
936 "foreground-gdk", &signature_color,
939 compose->uri_tag = gtk_text_buffer_create_tag(buffer, "link",
940 "foreground-gdk", &uri_color,
942 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
943 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
945 color[0] = quote_color1;
946 color[1] = quote_color2;
947 color[2] = quote_color3;
948 color[3] = quote_bgcolor1;
949 color[4] = quote_bgcolor2;
950 color[5] = quote_bgcolor3;
951 color[6] = signature_color;
952 color[7] = uri_color;
953 cmap = gdk_drawable_get_colormap(compose->window->window);
954 gdk_colormap_alloc_colors(cmap, color, 8, FALSE, TRUE, success);
956 for (i = 0; i < 8; i++) {
957 if (success[i] == FALSE) {
960 g_warning("Compose: color allocation failed.\n");
961 style = gtk_widget_get_style(GTK_WIDGET(text));
962 quote_color1 = quote_color2 = quote_color3 =
963 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 =
964 signature_color = uri_color = black;
969 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
970 GPtrArray *attach_files)
972 return compose_generic_new(account, mailto, NULL, attach_files, NULL);
975 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
977 return compose_generic_new(account, mailto, item, NULL, NULL);
980 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
982 return compose_generic_new( account, NULL, NULL, NULL, listAddress );
985 #define SCROLL_TO_CURSOR(compose) { \
986 GtkTextMark *cmark = gtk_text_buffer_get_insert( \
987 gtk_text_view_get_buffer( \
988 GTK_TEXT_VIEW(compose->text))); \
989 gtk_text_view_scroll_mark_onscreen( \
990 GTK_TEXT_VIEW(compose->text), \
994 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
995 GPtrArray *attach_files, GList *listAddress )
998 GtkTextView *textview;
999 GtkTextBuffer *textbuf;
1001 GtkItemFactory *ifactory;
1002 const gchar *subject_format = NULL;
1003 const gchar *body_format = NULL;
1005 if (item && item->prefs && item->prefs->enable_default_account)
1006 account = account_find_from_id(item->prefs->default_account);
1008 if (!account) account = cur_account;
1009 g_return_val_if_fail(account != NULL, NULL);
1011 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
1013 ifactory = gtk_item_factory_from_widget(compose->menubar);
1015 compose->replyinfo = NULL;
1016 compose->fwdinfo = NULL;
1018 textview = GTK_TEXT_VIEW(compose->text);
1019 textbuf = gtk_text_view_get_buffer(textview);
1020 compose_create_tags(textview, compose);
1022 undo_block(compose->undostruct);
1024 compose_set_dictionaries_from_folder_prefs(compose, item);
1027 if (account->auto_sig)
1028 compose_insert_sig(compose, FALSE);
1029 gtk_text_buffer_get_start_iter(textbuf, &iter);
1030 gtk_text_buffer_place_cursor(textbuf, &iter);
1032 if (account->protocol != A_NNTP) {
1033 if (mailto && *mailto != '\0') {
1034 compose_entries_set(compose, mailto);
1036 } else if (item && item->prefs->enable_default_to) {
1037 compose_entry_append(compose, item->prefs->default_to, COMPOSE_TO);
1038 compose_entry_mark_default_to(compose, item->prefs->default_to);
1040 if (item && item->ret_rcpt) {
1041 menu_set_active(ifactory, "/Options/Request Return Receipt", TRUE);
1045 compose_entry_append(compose, mailto, COMPOSE_NEWSGROUPS);
1046 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1047 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS);
1050 * CLAWS: just don't allow return receipt request, even if the user
1051 * may want to send an email. simple but foolproof.
1053 menu_set_sensitive(ifactory, "/Options/Request Return Receipt", FALSE);
1055 compose_add_field_list( compose, listAddress );
1057 if (item && item->prefs && item->prefs->compose_with_format) {
1058 subject_format = item->prefs->compose_subject_format;
1059 body_format = item->prefs->compose_body_format;
1060 } else if (account->compose_with_format) {
1061 subject_format = account->compose_subject_format;
1062 body_format = account->compose_body_format;
1063 } else if (prefs_common.compose_with_format) {
1064 subject_format = prefs_common.compose_subject_format;
1065 body_format = prefs_common.compose_body_format;
1068 if (subject_format || body_format) {
1069 MsgInfo* dummyinfo = NULL;
1072 && *subject_format != '\0' )
1074 gchar *subject = NULL;
1078 dummyinfo = compose_msginfo_new_from_compose(compose);
1080 /* decode \-escape sequences in the internal representation of the quote format */
1081 tmp = malloc(strlen(subject_format)+1);
1082 pref_get_unescaped_pref(tmp, subject_format);
1084 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1086 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account,
1087 compose->gtkaspell);
1089 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account);
1091 quote_fmt_scan_string(tmp);
1094 buf = quote_fmt_get_buffer();
1096 alertpanel_error(_("New message subject format error."));
1098 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1099 quote_fmt_reset_vartable();
1106 && *body_format != '\0' )
1109 GtkTextBuffer *buffer;
1110 GtkTextIter start, end;
1113 if ( dummyinfo == NULL )
1114 dummyinfo = compose_msginfo_new_from_compose(compose);
1116 text = GTK_TEXT_VIEW(compose->text);
1117 buffer = gtk_text_view_get_buffer(text);
1118 gtk_text_buffer_get_start_iter(buffer, &start);
1119 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1120 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1122 compose_quote_fmt(compose, dummyinfo,
1124 NULL, tmp, FALSE, TRUE,
1125 _("New message body format error at line %d."));
1126 quote_fmt_reset_vartable();
1131 procmsg_msginfo_free( dummyinfo );
1138 for (i = 0; i < attach_files->len; i++) {
1139 file = g_ptr_array_index(attach_files, i);
1140 compose_attach_append(compose, file, file, NULL);
1144 compose_show_first_last_header(compose, TRUE);
1146 /* Set save folder */
1147 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1148 gchar *folderidentifier;
1150 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
1151 folderidentifier = folder_item_get_identifier(item);
1152 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
1153 g_free(folderidentifier);
1156 gtk_widget_grab_focus(compose->header_last->entry);
1158 undo_unblock(compose->undostruct);
1160 if (prefs_common.auto_exteditor)
1161 compose_exec_ext_editor(compose);
1163 compose->draft_timeout_tag = -1;
1164 SCROLL_TO_CURSOR(compose);
1166 compose->modified = FALSE;
1167 compose_set_title(compose);
1171 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1172 gboolean override_pref)
1174 gchar *privacy = NULL;
1176 g_return_if_fail(compose != NULL);
1177 g_return_if_fail(account != NULL);
1179 if (override_pref == FALSE && account->default_encrypt_reply == FALSE)
1182 if (account->default_privacy_system
1183 && strlen(account->default_privacy_system)) {
1184 privacy = account->default_privacy_system;
1186 GSList *privacy_avail = privacy_get_system_ids();
1187 if (privacy_avail && g_slist_length(privacy_avail)) {
1188 privacy = (gchar *)(privacy_avail->data);
1191 if (privacy != NULL) {
1192 if (compose->privacy_system == NULL)
1193 compose->privacy_system = g_strdup(privacy);
1194 compose_update_privacy_system_menu_item(compose, FALSE);
1195 compose_use_encryption(compose, TRUE);
1199 static void compose_force_signing(Compose *compose, PrefsAccount *account)
1201 gchar *privacy = NULL;
1203 if (account->default_privacy_system
1204 && strlen(account->default_privacy_system)) {
1205 privacy = account->default_privacy_system;
1207 GSList *privacy_avail = privacy_get_system_ids();
1208 if (privacy_avail && g_slist_length(privacy_avail)) {
1209 privacy = (gchar *)(privacy_avail->data);
1212 if (privacy != NULL) {
1213 if (compose->privacy_system == NULL)
1214 compose->privacy_system = g_strdup(privacy);
1215 compose_update_privacy_system_menu_item(compose, FALSE);
1216 compose_use_signing(compose, TRUE);
1220 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1224 Compose *compose = NULL;
1225 GtkItemFactory *ifactory = NULL;
1227 g_return_val_if_fail(msginfo_list != NULL, NULL);
1229 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1230 g_return_val_if_fail(msginfo != NULL, NULL);
1232 list_len = g_slist_length(msginfo_list);
1236 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1237 FALSE, prefs_common.default_reply_list, FALSE, body);
1239 case COMPOSE_REPLY_WITH_QUOTE:
1240 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1241 FALSE, prefs_common.default_reply_list, FALSE, body);
1243 case COMPOSE_REPLY_WITHOUT_QUOTE:
1244 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1245 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1247 case COMPOSE_REPLY_TO_SENDER:
1248 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1249 FALSE, FALSE, TRUE, body);
1251 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1252 compose = compose_followup_and_reply_to(msginfo,
1253 COMPOSE_QUOTE_CHECK,
1254 FALSE, FALSE, body);
1256 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1257 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1258 FALSE, FALSE, TRUE, body);
1260 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1261 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1262 FALSE, FALSE, TRUE, NULL);
1264 case COMPOSE_REPLY_TO_ALL:
1265 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1266 TRUE, FALSE, FALSE, body);
1268 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1269 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1270 TRUE, FALSE, FALSE, body);
1272 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1273 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1274 TRUE, FALSE, FALSE, NULL);
1276 case COMPOSE_REPLY_TO_LIST:
1277 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1278 FALSE, TRUE, FALSE, body);
1280 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1281 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1282 FALSE, TRUE, FALSE, body);
1284 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1285 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1286 FALSE, TRUE, FALSE, NULL);
1288 case COMPOSE_FORWARD:
1289 if (prefs_common.forward_as_attachment) {
1290 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1293 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1297 case COMPOSE_FORWARD_INLINE:
1298 /* check if we reply to more than one Message */
1299 if (list_len == 1) {
1300 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1303 /* more messages FALL THROUGH */
1304 case COMPOSE_FORWARD_AS_ATTACH:
1305 compose = compose_forward_multiple(NULL, msginfo_list);
1307 case COMPOSE_REDIRECT:
1308 compose = compose_redirect(NULL, msginfo, FALSE);
1311 g_warning("compose_reply_mode(): invalid Compose Mode: %d\n", mode);
1314 if (compose == NULL) {
1315 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1318 ifactory = gtk_item_factory_from_widget(compose->menubar);
1320 compose->rmode = mode;
1321 switch (compose->rmode) {
1323 case COMPOSE_REPLY_WITH_QUOTE:
1324 case COMPOSE_REPLY_WITHOUT_QUOTE:
1325 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1326 debug_print("reply mode Normal\n");
1327 menu_set_active(ifactory, "/Options/Reply mode/Normal", TRUE);
1328 compose_reply_change_mode(compose, COMPOSE_REPLY, NULL); /* force update */
1330 case COMPOSE_REPLY_TO_SENDER:
1331 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1332 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1333 debug_print("reply mode Sender\n");
1334 menu_set_active(ifactory, "/Options/Reply mode/Sender", TRUE);
1336 case COMPOSE_REPLY_TO_ALL:
1337 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1338 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1339 debug_print("reply mode All\n");
1340 menu_set_active(ifactory, "/Options/Reply mode/All", TRUE);
1342 case COMPOSE_REPLY_TO_LIST:
1343 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1344 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1345 debug_print("reply mode List\n");
1346 menu_set_active(ifactory, "/Options/Reply mode/Mailing-list", TRUE);
1354 static Compose *compose_reply(MsgInfo *msginfo,
1355 ComposeQuoteMode quote_mode,
1361 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1362 to_sender, FALSE, body);
1365 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1366 ComposeQuoteMode quote_mode,
1371 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1372 to_sender, TRUE, body);
1375 static void compose_extract_original_charset(Compose *compose)
1377 MsgInfo *info = NULL;
1378 if (compose->replyinfo) {
1379 info = compose->replyinfo;
1380 } else if (compose->fwdinfo) {
1381 info = compose->fwdinfo;
1382 } else if (compose->targetinfo) {
1383 info = compose->targetinfo;
1386 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1387 MimeInfo *partinfo = mimeinfo;
1388 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1389 partinfo = procmime_mimeinfo_next(partinfo);
1391 compose->orig_charset =
1392 g_strdup(procmime_mimeinfo_get_parameter(
1393 partinfo, "charset"));
1395 procmime_mimeinfo_free_all(mimeinfo);
1399 #define SIGNAL_BLOCK(buffer) { \
1400 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1401 G_CALLBACK(compose_changed_cb), \
1403 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1404 G_CALLBACK(text_inserted), \
1408 #define SIGNAL_UNBLOCK(buffer) { \
1409 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1410 G_CALLBACK(compose_changed_cb), \
1412 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1413 G_CALLBACK(text_inserted), \
1417 static Compose *compose_generic_reply(MsgInfo *msginfo,
1418 ComposeQuoteMode quote_mode,
1419 gboolean to_all, gboolean to_ml,
1421 gboolean followup_and_reply_to,
1424 GtkItemFactory *ifactory;
1426 PrefsAccount *account = NULL;
1427 GtkTextView *textview;
1428 GtkTextBuffer *textbuf;
1429 gboolean quote = FALSE;
1430 const gchar *qmark = NULL;
1431 const gchar *body_fmt = NULL;
1433 g_return_val_if_fail(msginfo != NULL, NULL);
1434 g_return_val_if_fail(msginfo->folder != NULL, NULL);
1436 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1438 g_return_val_if_fail(account != NULL, NULL);
1440 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1442 compose->updating = TRUE;
1444 ifactory = gtk_item_factory_from_widget(compose->menubar);
1446 menu_set_active(ifactory, "/Options/Remove references", FALSE);
1447 menu_set_sensitive(ifactory, "/Options/Remove references", TRUE);
1449 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1450 if (!compose->replyinfo)
1451 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1453 compose_extract_original_charset(compose);
1455 if (msginfo->folder && msginfo->folder->ret_rcpt)
1456 menu_set_active(ifactory, "/Options/Request Return Receipt", TRUE);
1458 /* Set save folder */
1459 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1460 gchar *folderidentifier;
1462 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1463 folderidentifier = folder_item_get_identifier(msginfo->folder);
1464 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
1465 g_free(folderidentifier);
1468 if (compose_parse_header(compose, msginfo) < 0) return NULL;
1470 textview = (GTK_TEXT_VIEW(compose->text));
1471 textbuf = gtk_text_view_get_buffer(textview);
1472 compose_create_tags(textview, compose);
1474 undo_block(compose->undostruct);
1476 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1479 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1480 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1481 /* use the reply format of folder (if enabled), or the account's one
1482 (if enabled) or fallback to the global reply format, which is always
1483 enabled (even if empty), and use the relevant quotemark */
1485 if (msginfo->folder && msginfo->folder->prefs &&
1486 msginfo->folder->prefs->reply_with_format) {
1487 qmark = msginfo->folder->prefs->reply_quotemark;
1488 body_fmt = msginfo->folder->prefs->reply_body_format;
1490 } else if (account->reply_with_format) {
1491 qmark = account->reply_quotemark;
1492 body_fmt = account->reply_body_format;
1495 qmark = prefs_common.quotemark;
1496 body_fmt = prefs_common.quotefmt;
1501 /* empty quotemark is not allowed */
1502 if (qmark == NULL || *qmark == '\0')
1504 compose_quote_fmt(compose, compose->replyinfo,
1505 body_fmt, qmark, body, FALSE, TRUE,
1506 _("Message reply format error at line %d."));
1507 quote_fmt_reset_vartable();
1510 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1511 compose_force_encryption(compose, account, FALSE);
1514 SIGNAL_BLOCK(textbuf);
1516 if (account->auto_sig)
1517 compose_insert_sig(compose, FALSE);
1519 compose_wrap_all(compose);
1521 SIGNAL_UNBLOCK(textbuf);
1523 gtk_widget_grab_focus(compose->text);
1525 undo_unblock(compose->undostruct);
1527 if (prefs_common.auto_exteditor)
1528 compose_exec_ext_editor(compose);
1530 compose->modified = FALSE;
1531 compose_set_title(compose);
1533 compose->updating = FALSE;
1534 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1535 SCROLL_TO_CURSOR(compose);
1537 if (compose->deferred_destroy) {
1538 compose_destroy(compose);
1545 #define INSERT_FW_HEADER(var, hdr) \
1546 if (msginfo->var && *msginfo->var) { \
1547 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1548 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1549 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1552 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1553 gboolean as_attach, const gchar *body,
1554 gboolean no_extedit,
1558 GtkTextView *textview;
1559 GtkTextBuffer *textbuf;
1562 g_return_val_if_fail(msginfo != NULL, NULL);
1563 g_return_val_if_fail(msginfo->folder != NULL, NULL);
1566 !(account = compose_guess_forward_account_from_msginfo
1568 account = cur_account;
1570 compose = compose_create(account, msginfo->folder, COMPOSE_FORWARD, batch);
1572 compose->updating = TRUE;
1573 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1574 if (!compose->fwdinfo)
1575 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1577 compose_extract_original_charset(compose);
1579 if (msginfo->subject && *msginfo->subject) {
1580 gchar *buf, *buf2, *p;
1582 buf = p = g_strdup(msginfo->subject);
1583 p += subject_get_prefix_length(p);
1584 memmove(buf, p, strlen(p) + 1);
1586 buf2 = g_strdup_printf("Fw: %s", buf);
1587 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1593 textview = GTK_TEXT_VIEW(compose->text);
1594 textbuf = gtk_text_view_get_buffer(textview);
1595 compose_create_tags(textview, compose);
1597 undo_block(compose->undostruct);
1601 msgfile = procmsg_get_message_file(msginfo);
1602 if (!is_file_exist(msgfile))
1603 g_warning("%s: file not exist\n", msgfile);
1605 compose_attach_append(compose, msgfile, msgfile,
1610 const gchar *qmark = NULL;
1611 const gchar *body_fmt = prefs_common.fw_quotefmt;
1612 MsgInfo *full_msginfo;
1614 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1616 full_msginfo = procmsg_msginfo_copy(msginfo);
1618 /* use the forward format of folder (if enabled), or the account's one
1619 (if enabled) or fallback to the global forward format, which is always
1620 enabled (even if empty), and use the relevant quotemark */
1621 if (msginfo->folder && msginfo->folder->prefs &&
1622 msginfo->folder->prefs->forward_with_format) {
1623 qmark = msginfo->folder->prefs->forward_quotemark;
1624 body_fmt = msginfo->folder->prefs->forward_body_format;
1626 } else if (account->forward_with_format) {
1627 qmark = account->forward_quotemark;
1628 body_fmt = account->forward_body_format;
1631 qmark = prefs_common.fw_quotemark;
1632 body_fmt = prefs_common.fw_quotefmt;
1635 /* empty quotemark is not allowed */
1636 if (qmark == NULL || *qmark == '\0')
1639 compose_quote_fmt(compose, full_msginfo,
1640 body_fmt, qmark, body, FALSE, TRUE,
1641 _("Message forward format error at line %d."));
1642 quote_fmt_reset_vartable();
1643 compose_attach_parts(compose, msginfo);
1645 procmsg_msginfo_free(full_msginfo);
1648 SIGNAL_BLOCK(textbuf);
1650 if (account->auto_sig)
1651 compose_insert_sig(compose, FALSE);
1653 compose_wrap_all(compose);
1655 SIGNAL_UNBLOCK(textbuf);
1657 gtk_text_buffer_get_start_iter(textbuf, &iter);
1658 gtk_text_buffer_place_cursor(textbuf, &iter);
1660 gtk_widget_grab_focus(compose->header_last->entry);
1662 if (!no_extedit && prefs_common.auto_exteditor)
1663 compose_exec_ext_editor(compose);
1666 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1667 gchar *folderidentifier;
1669 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1670 folderidentifier = folder_item_get_identifier(msginfo->folder);
1671 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
1672 g_free(folderidentifier);
1675 undo_unblock(compose->undostruct);
1677 compose->modified = FALSE;
1678 compose_set_title(compose);
1680 compose->updating = FALSE;
1681 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1682 SCROLL_TO_CURSOR(compose);
1684 if (compose->deferred_destroy) {
1685 compose_destroy(compose);
1692 #undef INSERT_FW_HEADER
1694 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1697 GtkTextView *textview;
1698 GtkTextBuffer *textbuf;
1702 gboolean single_mail = TRUE;
1704 g_return_val_if_fail(msginfo_list != NULL, NULL);
1706 if (g_slist_length(msginfo_list) > 1)
1707 single_mail = FALSE;
1709 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1710 if (((MsgInfo *)msginfo->data)->folder == NULL)
1713 /* guess account from first selected message */
1715 !(account = compose_guess_forward_account_from_msginfo
1716 (msginfo_list->data)))
1717 account = cur_account;
1719 g_return_val_if_fail(account != NULL, NULL);
1721 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1722 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1723 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1726 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1728 compose->updating = TRUE;
1730 textview = GTK_TEXT_VIEW(compose->text);
1731 textbuf = gtk_text_view_get_buffer(textview);
1732 compose_create_tags(textview, compose);
1734 undo_block(compose->undostruct);
1735 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1736 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
1738 if (!is_file_exist(msgfile))
1739 g_warning("%s: file not exist\n", msgfile);
1741 compose_attach_append(compose, msgfile, msgfile,
1747 MsgInfo *info = (MsgInfo *)msginfo_list->data;
1748 if (info->subject && *info->subject) {
1749 gchar *buf, *buf2, *p;
1751 buf = p = g_strdup(info->subject);
1752 p += subject_get_prefix_length(p);
1753 memmove(buf, p, strlen(p) + 1);
1755 buf2 = g_strdup_printf("Fw: %s", buf);
1756 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1762 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
1763 _("Fw: multiple emails"));
1766 SIGNAL_BLOCK(textbuf);
1768 if (account->auto_sig)
1769 compose_insert_sig(compose, FALSE);
1771 compose_wrap_all(compose);
1773 SIGNAL_UNBLOCK(textbuf);
1775 gtk_text_buffer_get_start_iter(textbuf, &iter);
1776 gtk_text_buffer_place_cursor(textbuf, &iter);
1778 gtk_widget_grab_focus(compose->header_last->entry);
1779 undo_unblock(compose->undostruct);
1780 compose->modified = FALSE;
1781 compose_set_title(compose);
1783 compose->updating = FALSE;
1784 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1785 SCROLL_TO_CURSOR(compose);
1787 if (compose->deferred_destroy) {
1788 compose_destroy(compose);
1795 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
1797 GtkTextIter start = *iter;
1798 GtkTextIter end_iter;
1799 int start_pos = gtk_text_iter_get_offset(&start);
1801 if (!compose->account->sig_sep)
1804 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
1805 start_pos+strlen(compose->account->sig_sep));
1807 /* check sig separator */
1808 str = gtk_text_iter_get_text(&start, &end_iter);
1809 if (!strcmp(str, compose->account->sig_sep)) {
1811 /* check end of line (\n) */
1812 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
1813 start_pos+strlen(compose->account->sig_sep));
1814 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
1815 start_pos+strlen(compose->account->sig_sep)+1);
1816 tmp = gtk_text_iter_get_text(&start, &end_iter);
1817 if (!strcmp(tmp,"\n")) {
1829 static void compose_colorize_signature(Compose *compose)
1831 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
1833 GtkTextIter end_iter;
1834 gtk_text_buffer_get_start_iter(buffer, &iter);
1835 while (gtk_text_iter_forward_line(&iter))
1836 if (compose_is_sig_separator(compose, buffer, &iter)) {
1837 gtk_text_buffer_get_end_iter(buffer, &end_iter);
1838 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
1842 #define BLOCK_WRAP() { \
1843 prev_autowrap = compose->autowrap; \
1844 buffer = gtk_text_view_get_buffer( \
1845 GTK_TEXT_VIEW(compose->text)); \
1846 compose->autowrap = FALSE; \
1848 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1849 G_CALLBACK(compose_changed_cb), \
1851 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1852 G_CALLBACK(text_inserted), \
1855 #define UNBLOCK_WRAP() { \
1856 compose->autowrap = prev_autowrap; \
1857 if (compose->autowrap) { \
1858 gint old = compose->draft_timeout_tag; \
1859 compose->draft_timeout_tag = -2; \
1860 compose_wrap_all(compose); \
1861 compose->draft_timeout_tag = old; \
1864 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1865 G_CALLBACK(compose_changed_cb), \
1867 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1868 G_CALLBACK(text_inserted), \
1872 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
1874 Compose *compose = NULL;
1875 PrefsAccount *account = NULL;
1876 GtkTextView *textview;
1877 GtkTextBuffer *textbuf;
1881 gchar buf[BUFFSIZE];
1882 gboolean use_signing = FALSE;
1883 gboolean use_encryption = FALSE;
1884 gchar *privacy_system = NULL;
1885 int priority = PRIORITY_NORMAL;
1886 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
1888 g_return_val_if_fail(msginfo != NULL, NULL);
1889 g_return_val_if_fail(msginfo->folder != NULL, NULL);
1891 if (compose_put_existing_to_front(msginfo)) {
1895 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
1896 folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
1897 gchar queueheader_buf[BUFFSIZE];
1900 /* Select Account from queue headers */
1901 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1902 sizeof(queueheader_buf), "X-Claws-Account-Id:")) {
1903 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
1904 account = account_find_from_id(id);
1906 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1907 sizeof(queueheader_buf), "X-Sylpheed-Account-Id:")) {
1908 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
1909 account = account_find_from_id(id);
1911 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1912 sizeof(queueheader_buf), "NAID:")) {
1913 id = atoi(&queueheader_buf[strlen("NAID:")]);
1914 account = account_find_from_id(id);
1916 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1917 sizeof(queueheader_buf), "MAID:")) {
1918 id = atoi(&queueheader_buf[strlen("MAID:")]);
1919 account = account_find_from_id(id);
1921 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1922 sizeof(queueheader_buf), "S:")) {
1923 account = account_find_from_address(queueheader_buf);
1925 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1926 sizeof(queueheader_buf), "X-Claws-Sign:")) {
1927 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
1928 use_signing = param;
1931 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1932 sizeof(queueheader_buf), "X-Sylpheed-Sign:")) {
1933 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
1934 use_signing = param;
1937 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1938 sizeof(queueheader_buf), "X-Claws-Encrypt:")) {
1939 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
1940 use_encryption = param;
1942 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1943 sizeof(queueheader_buf), "X-Sylpheed-Encrypt:")) {
1944 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
1945 use_encryption = param;
1947 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1948 sizeof(queueheader_buf), "X-Claws-Privacy-System:")) {
1949 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
1951 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1952 sizeof(queueheader_buf), "X-Sylpheed-Privacy-System:")) {
1953 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
1955 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1956 sizeof(queueheader_buf), "X-Priority: ")) {
1957 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
1960 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1961 sizeof(queueheader_buf), "RMID:")) {
1962 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
1963 if (tokens[0] && tokens[1] && tokens[2]) {
1964 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
1965 if (orig_item != NULL) {
1966 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
1971 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1972 sizeof(queueheader_buf), "FMID:")) {
1973 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
1974 if (tokens[0] && tokens[1] && tokens[2]) {
1975 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
1976 if (orig_item != NULL) {
1977 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
1983 account = msginfo->folder->folder->account;
1986 if (!account && prefs_common.reedit_account_autosel) {
1987 gchar from[BUFFSIZE];
1988 if (!procheader_get_header_from_msginfo(msginfo, from, sizeof(from), "FROM:")) {
1989 extract_address(from);
1990 account = account_find_from_address(from);
1994 account = cur_account;
1996 g_return_val_if_fail(account != NULL, NULL);
1998 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2000 compose->replyinfo = replyinfo;
2001 compose->fwdinfo = fwdinfo;
2003 compose->updating = TRUE;
2004 compose->priority = priority;
2006 if (privacy_system != NULL) {
2007 compose->privacy_system = privacy_system;
2008 compose_use_signing(compose, use_signing);
2009 compose_use_encryption(compose, use_encryption);
2010 compose_update_privacy_system_menu_item(compose, FALSE);
2012 activate_privacy_system(compose, account, FALSE);
2015 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2017 compose_extract_original_charset(compose);
2019 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2020 folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
2021 gchar queueheader_buf[BUFFSIZE];
2023 /* Set message save folder */
2024 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "SCF:")) {
2027 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2028 gtk_editable_delete_text(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
2029 gtk_editable_insert_text(GTK_EDITABLE(compose->savemsg_entry), &queueheader_buf[4], strlen(&queueheader_buf[4]), &startpos);
2031 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "RRCPT:")) {
2032 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2034 GtkItemFactory *ifactory;
2035 ifactory = gtk_item_factory_from_widget(compose->menubar);
2036 menu_set_active(ifactory, "/Options/Request Return Receipt", TRUE);
2041 if (compose_parse_header(compose, msginfo) < 0) {
2042 compose->updating = FALSE;
2043 compose_destroy(compose);
2046 compose_reedit_set_entry(compose, msginfo);
2048 textview = GTK_TEXT_VIEW(compose->text);
2049 textbuf = gtk_text_view_get_buffer(textview);
2050 compose_create_tags(textview, compose);
2052 mark = gtk_text_buffer_get_insert(textbuf);
2053 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2055 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2056 G_CALLBACK(compose_changed_cb),
2059 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2060 fp = procmime_get_first_encrypted_text_content(msginfo);
2062 compose_force_encryption(compose, account, TRUE);
2065 fp = procmime_get_first_text_content(msginfo);
2068 g_warning("Can't get text part\n");
2072 gboolean prev_autowrap = compose->autowrap;
2073 GtkTextBuffer *buffer = textbuf;
2075 while (fgets(buf, sizeof(buf), fp) != NULL) {
2077 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2083 compose_attach_parts(compose, msginfo);
2085 compose_colorize_signature(compose);
2087 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2088 G_CALLBACK(compose_changed_cb),
2091 gtk_widget_grab_focus(compose->text);
2093 if (prefs_common.auto_exteditor) {
2094 compose_exec_ext_editor(compose);
2096 compose->modified = FALSE;
2097 compose_set_title(compose);
2099 compose->updating = FALSE;
2100 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2101 SCROLL_TO_CURSOR(compose);
2103 if (compose->deferred_destroy) {
2104 compose_destroy(compose);
2108 compose->sig_str = compose_get_signature_str(compose);
2113 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2118 GtkItemFactory *ifactory;
2121 g_return_val_if_fail(msginfo != NULL, NULL);
2124 account = account_get_reply_account(msginfo,
2125 prefs_common.reply_account_autosel);
2126 g_return_val_if_fail(account != NULL, NULL);
2128 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2130 compose->updating = TRUE;
2132 ifactory = gtk_item_factory_from_widget(compose->menubar);
2133 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2134 compose->replyinfo = NULL;
2135 compose->fwdinfo = NULL;
2137 compose_show_first_last_header(compose, TRUE);
2139 gtk_widget_grab_focus(compose->header_last->entry);
2141 filename = procmsg_get_message_file(msginfo);
2143 if (filename == NULL) {
2144 compose->updating = FALSE;
2145 compose_destroy(compose);
2150 compose->redirect_filename = filename;
2152 /* Set save folder */
2153 item = msginfo->folder;
2154 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2155 gchar *folderidentifier;
2157 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
2158 folderidentifier = folder_item_get_identifier(item);
2159 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
2160 g_free(folderidentifier);
2163 compose_attach_parts(compose, msginfo);
2165 if (msginfo->subject)
2166 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2168 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2170 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2171 _("Message redirect format error at line %d."));
2172 quote_fmt_reset_vartable();
2173 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2175 compose_colorize_signature(compose);
2177 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
2178 menu_set_sensitive(ifactory, "/Add...", FALSE);
2179 menu_set_sensitive(ifactory, "/Remove", FALSE);
2180 menu_set_sensitive(ifactory, "/Properties...", FALSE);
2182 ifactory = gtk_item_factory_from_widget(compose->menubar);
2183 menu_set_sensitive(ifactory, "/Message/Save", FALSE);
2184 menu_set_sensitive(ifactory, "/Message/Insert file", FALSE);
2185 menu_set_sensitive(ifactory, "/Message/Attach file", FALSE);
2186 menu_set_sensitive(ifactory, "/Message/Insert signature", FALSE);
2187 menu_set_sensitive(ifactory, "/Edit", FALSE);
2188 menu_set_sensitive(ifactory, "/Options", FALSE);
2189 menu_set_sensitive(ifactory, "/Tools/Show ruler", FALSE);
2190 menu_set_sensitive(ifactory, "/Tools/Actions", FALSE);
2192 if (compose->toolbar->draft_btn)
2193 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2194 if (compose->toolbar->insert_btn)
2195 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2196 if (compose->toolbar->attach_btn)
2197 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2198 if (compose->toolbar->sig_btn)
2199 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2200 if (compose->toolbar->exteditor_btn)
2201 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2202 if (compose->toolbar->linewrap_current_btn)
2203 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2204 if (compose->toolbar->linewrap_all_btn)
2205 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2207 compose->modified = FALSE;
2208 compose_set_title(compose);
2209 compose->updating = FALSE;
2210 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2211 SCROLL_TO_CURSOR(compose);
2213 if (compose->deferred_destroy) {
2214 compose_destroy(compose);
2221 GList *compose_get_compose_list(void)
2223 return compose_list;
2226 void compose_entry_append(Compose *compose, const gchar *address,
2227 ComposeEntryType type)
2229 const gchar *header;
2231 gboolean in_quote = FALSE;
2232 if (!address || *address == '\0') return;
2239 header = N_("Bcc:");
2241 case COMPOSE_REPLYTO:
2242 header = N_("Reply-To:");
2244 case COMPOSE_NEWSGROUPS:
2245 header = N_("Newsgroups:");
2247 case COMPOSE_FOLLOWUPTO:
2248 header = N_( "Followup-To:");
2255 header = prefs_common_translated_header_name(header);
2257 cur = begin = (gchar *)address;
2259 /* we separate the line by commas, but not if we're inside a quoted
2261 while (*cur != '\0') {
2263 in_quote = !in_quote;
2264 if (*cur == ',' && !in_quote) {
2265 gchar *tmp = g_strdup(begin);
2267 tmp[cur-begin]='\0';
2270 while (*tmp == ' ' || *tmp == '\t')
2272 compose_add_header_entry(compose, header, tmp);
2279 gchar *tmp = g_strdup(begin);
2281 tmp[cur-begin]='\0';
2284 while (*tmp == ' ' || *tmp == '\t')
2286 compose_add_header_entry(compose, header, tmp);
2291 static void compose_entry_mark_default_to(Compose *compose, const gchar *mailto)
2293 static GdkColor yellow;
2294 static GdkColor black;
2295 static gboolean yellow_initialised = FALSE;
2299 if (!yellow_initialised) {
2300 gdk_color_parse("#f5f6be", &yellow);
2301 gdk_color_parse("#000000", &black);
2302 yellow_initialised = gdk_colormap_alloc_color(
2303 gdk_colormap_get_system(), &yellow, FALSE, TRUE);
2304 yellow_initialised &= gdk_colormap_alloc_color(
2305 gdk_colormap_get_system(), &black, FALSE, TRUE);
2308 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2309 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2310 if (gtk_entry_get_text(entry) &&
2311 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2312 if (yellow_initialised) {
2313 gtk_widget_modify_base(
2314 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2315 GTK_STATE_NORMAL, &yellow);
2316 gtk_widget_modify_text(
2317 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2318 GTK_STATE_NORMAL, &black);
2324 void compose_toolbar_cb(gint action, gpointer data)
2326 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2327 Compose *compose = (Compose*)toolbar_item->parent;
2329 g_return_if_fail(compose != NULL);
2333 compose_send_cb(compose, 0, NULL);
2336 compose_send_later_cb(compose, 0, NULL);
2339 compose_draft_cb(compose, COMPOSE_QUIT_EDITING, NULL);
2342 compose_insert_file_cb(compose, 0, NULL);
2345 compose_attach_cb(compose, 0, NULL);
2348 compose_insert_sig(compose, FALSE);
2351 compose_ext_editor_cb(compose, 0, NULL);
2353 case A_LINEWRAP_CURRENT:
2354 compose_beautify_paragraph(compose, NULL, TRUE);
2356 case A_LINEWRAP_ALL:
2357 compose_wrap_all_full(compose, TRUE);
2360 compose_address_cb(compose, 0, NULL);
2363 case A_CHECK_SPELLING:
2364 compose_check_all(compose);
2372 static void compose_entries_set(Compose *compose, const gchar *mailto)
2376 gchar *subject = NULL;
2380 gchar *attach = NULL;
2382 scan_mailto_url(mailto, &to, &cc, NULL, &subject, &body, &attach);
2385 compose_entry_append(compose, to, COMPOSE_TO);
2387 compose_entry_append(compose, cc, COMPOSE_CC);
2389 if (!g_utf8_validate (subject, -1, NULL)) {
2390 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2391 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2394 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2398 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2399 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2402 gboolean prev_autowrap = compose->autowrap;
2404 compose->autowrap = FALSE;
2406 mark = gtk_text_buffer_get_insert(buffer);
2407 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2409 if (!g_utf8_validate (body, -1, NULL)) {
2410 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2411 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2414 gtk_text_buffer_insert(buffer, &iter, body, -1);
2416 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2418 compose->autowrap = prev_autowrap;
2419 if (compose->autowrap)
2420 compose_wrap_all(compose);
2424 gchar *utf8_filename = conv_filename_to_utf8(attach);
2425 if (utf8_filename) {
2426 if (compose_attach_append(compose, attach, utf8_filename, NULL)) {
2427 alertpanel_notice(_("The file '%s' has been attached."), attach);
2429 g_free(utf8_filename);
2431 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2441 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2443 static HeaderEntry hentry[] = {{"Reply-To:", NULL, TRUE},
2444 {"Cc:", NULL, TRUE},
2445 {"References:", NULL, FALSE},
2446 {"Bcc:", NULL, TRUE},
2447 {"Newsgroups:", NULL, TRUE},
2448 {"Followup-To:", NULL, TRUE},
2449 {"List-Post:", NULL, FALSE},
2450 {"X-Priority:", NULL, FALSE},
2451 {NULL, NULL, FALSE}};
2467 g_return_val_if_fail(msginfo != NULL, -1);
2469 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2470 procheader_get_header_fields(fp, hentry);
2473 if (hentry[H_REPLY_TO].body != NULL) {
2474 if (hentry[H_REPLY_TO].body[0] != '\0') {
2476 conv_unmime_header(hentry[H_REPLY_TO].body,
2479 g_free(hentry[H_REPLY_TO].body);
2480 hentry[H_REPLY_TO].body = NULL;
2482 if (hentry[H_CC].body != NULL) {
2483 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL);
2484 g_free(hentry[H_CC].body);
2485 hentry[H_CC].body = NULL;
2487 if (hentry[H_REFERENCES].body != NULL) {
2488 if (compose->mode == COMPOSE_REEDIT)
2489 compose->references = hentry[H_REFERENCES].body;
2491 compose->references = compose_parse_references
2492 (hentry[H_REFERENCES].body, msginfo->msgid);
2493 g_free(hentry[H_REFERENCES].body);
2495 hentry[H_REFERENCES].body = NULL;
2497 if (hentry[H_BCC].body != NULL) {
2498 if (compose->mode == COMPOSE_REEDIT)
2500 conv_unmime_header(hentry[H_BCC].body, NULL);
2501 g_free(hentry[H_BCC].body);
2502 hentry[H_BCC].body = NULL;
2504 if (hentry[H_NEWSGROUPS].body != NULL) {
2505 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2506 hentry[H_NEWSGROUPS].body = NULL;
2508 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2509 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2510 compose->followup_to =
2511 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2514 g_free(hentry[H_FOLLOWUP_TO].body);
2515 hentry[H_FOLLOWUP_TO].body = NULL;
2517 if (hentry[H_LIST_POST].body != NULL) {
2520 extract_address(hentry[H_LIST_POST].body);
2521 if (hentry[H_LIST_POST].body[0] != '\0') {
2522 scan_mailto_url(hentry[H_LIST_POST].body,
2523 &to, NULL, NULL, NULL, NULL, NULL);
2525 g_free(compose->ml_post);
2526 compose->ml_post = to;
2529 g_free(hentry[H_LIST_POST].body);
2530 hentry[H_LIST_POST].body = NULL;
2533 /* CLAWS - X-Priority */
2534 if (compose->mode == COMPOSE_REEDIT)
2535 if (hentry[H_X_PRIORITY].body != NULL) {
2538 priority = atoi(hentry[H_X_PRIORITY].body);
2539 g_free(hentry[H_X_PRIORITY].body);
2541 hentry[H_X_PRIORITY].body = NULL;
2543 if (priority < PRIORITY_HIGHEST ||
2544 priority > PRIORITY_LOWEST)
2545 priority = PRIORITY_NORMAL;
2547 compose->priority = priority;
2550 if (compose->mode == COMPOSE_REEDIT) {
2551 if (msginfo->inreplyto && *msginfo->inreplyto)
2552 compose->inreplyto = g_strdup(msginfo->inreplyto);
2556 if (msginfo->msgid && *msginfo->msgid)
2557 compose->inreplyto = g_strdup(msginfo->msgid);
2559 if (!compose->references) {
2560 if (msginfo->msgid && *msginfo->msgid) {
2561 if (msginfo->inreplyto && *msginfo->inreplyto)
2562 compose->references =
2563 g_strdup_printf("<%s>\n\t<%s>",
2567 compose->references =
2568 g_strconcat("<", msginfo->msgid, ">",
2570 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
2571 compose->references =
2572 g_strconcat("<", msginfo->inreplyto, ">",
2580 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
2582 GSList *ref_id_list, *cur;
2586 ref_id_list = references_list_append(NULL, ref);
2587 if (!ref_id_list) return NULL;
2588 if (msgid && *msgid)
2589 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
2594 for (cur = ref_id_list; cur != NULL; cur = cur->next)
2595 /* "<" + Message-ID + ">" + CR+LF+TAB */
2596 len += strlen((gchar *)cur->data) + 5;
2598 if (len > MAX_REFERENCES_LEN) {
2599 /* remove second message-ID */
2600 if (ref_id_list && ref_id_list->next &&
2601 ref_id_list->next->next) {
2602 g_free(ref_id_list->next->data);
2603 ref_id_list = g_slist_remove
2604 (ref_id_list, ref_id_list->next->data);
2606 slist_free_strings(ref_id_list);
2607 g_slist_free(ref_id_list);
2614 new_ref = g_string_new("");
2615 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
2616 if (new_ref->len > 0)
2617 g_string_append(new_ref, "\n\t");
2618 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
2621 slist_free_strings(ref_id_list);
2622 g_slist_free(ref_id_list);
2624 new_ref_str = new_ref->str;
2625 g_string_free(new_ref, FALSE);
2630 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
2631 const gchar *fmt, const gchar *qmark,
2632 const gchar *body, gboolean rewrap,
2633 gboolean need_unescape,
2634 const gchar *err_msg)
2636 MsgInfo* dummyinfo = NULL;
2637 gchar *quote_str = NULL;
2639 gboolean prev_autowrap;
2640 const gchar *trimmed_body = body;
2641 gint cursor_pos = -1;
2642 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2643 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2648 SIGNAL_BLOCK(buffer);
2651 dummyinfo = compose_msginfo_new_from_compose(compose);
2652 msginfo = dummyinfo;
2655 if (qmark != NULL) {
2657 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
2658 compose->gtkaspell);
2660 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
2662 quote_fmt_scan_string(qmark);
2665 buf = quote_fmt_get_buffer();
2667 alertpanel_error(_("Quote mark format error."));
2669 Xstrdup_a(quote_str, buf, goto error)
2672 if (fmt && *fmt != '\0') {
2675 while (*trimmed_body == '\n')
2679 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account,
2680 compose->gtkaspell);
2682 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account);
2684 if (need_unescape) {
2687 /* decode \-escape sequences in the internal representation of the quote format */
2688 tmp = malloc(strlen(fmt)+1);
2689 pref_get_unescaped_pref(tmp, fmt);
2690 quote_fmt_scan_string(tmp);
2694 quote_fmt_scan_string(fmt);
2698 buf = quote_fmt_get_buffer();
2700 gint line = quote_fmt_get_line();
2701 alertpanel_error(err_msg, line);
2707 prev_autowrap = compose->autowrap;
2708 compose->autowrap = FALSE;
2710 mark = gtk_text_buffer_get_insert(buffer);
2711 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2712 if (g_utf8_validate(buf, -1, NULL)) {
2713 gtk_text_buffer_insert(buffer, &iter, buf, -1);
2715 gchar *tmpout = NULL;
2716 tmpout = conv_codeset_strdup
2717 (buf, conv_get_locale_charset_str_no_utf8(),
2719 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
2721 tmpout = g_malloc(strlen(buf)*2+1);
2722 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
2724 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
2728 cursor_pos = quote_fmt_get_cursor_pos();
2729 compose->set_cursor_pos = cursor_pos;
2730 if (cursor_pos == -1) {
2733 gtk_text_buffer_get_start_iter(buffer, &iter);
2734 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
2735 gtk_text_buffer_place_cursor(buffer, &iter);
2737 compose->autowrap = prev_autowrap;
2738 if (compose->autowrap && rewrap)
2739 compose_wrap_all(compose);
2746 SIGNAL_UNBLOCK(buffer);
2748 procmsg_msginfo_free( dummyinfo );
2753 /* if ml_post is of type addr@host and from is of type
2754 * addr-anything@host, return TRUE
2756 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
2758 gchar *left_ml = NULL;
2759 gchar *right_ml = NULL;
2760 gchar *left_from = NULL;
2761 gchar *right_from = NULL;
2762 gboolean result = FALSE;
2764 if (!ml_post || !from)
2767 left_ml = g_strdup(ml_post);
2768 if (strstr(left_ml, "@")) {
2769 right_ml = strstr(left_ml, "@")+1;
2770 *(strstr(left_ml, "@")) = '\0';
2773 left_from = g_strdup(from);
2774 if (strstr(left_from, "@")) {
2775 right_from = strstr(left_from, "@")+1;
2776 *(strstr(left_from, "@")) = '\0';
2779 if (left_ml && left_from && right_ml && right_from
2780 && !strncmp(left_from, left_ml, strlen(left_ml))
2781 && !strcmp(right_from, right_ml)) {
2790 static gboolean same_address(const gchar *addr1, const gchar *addr2)
2792 gchar *my_addr1, *my_addr2;
2794 if (!addr1 || !addr2)
2797 Xstrdup_a(my_addr1, addr1, return FALSE);
2798 Xstrdup_a(my_addr2, addr2, return FALSE);
2800 extract_address(my_addr1);
2801 extract_address(my_addr2);
2803 return !strcasecmp(my_addr1, my_addr2);
2806 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
2807 gboolean to_all, gboolean to_ml,
2809 gboolean followup_and_reply_to)
2811 GSList *cc_list = NULL;
2814 gchar *replyto = NULL;
2815 GHashTable *to_table;
2817 gboolean reply_to_ml = FALSE;
2818 gboolean default_reply_to = FALSE;
2820 g_return_if_fail(compose->account != NULL);
2821 g_return_if_fail(msginfo != NULL);
2823 reply_to_ml = to_ml && compose->ml_post;
2825 default_reply_to = msginfo->folder &&
2826 msginfo->folder->prefs->enable_default_reply_to;
2828 if (compose->account->protocol != A_NNTP) {
2829 if (reply_to_ml && !default_reply_to) {
2831 gboolean is_subscr = is_subscription(compose->ml_post,
2834 /* normal answer to ml post with a reply-to */
2835 compose_entry_append(compose,
2838 if (compose->replyto
2839 && !same_address(compose->ml_post, compose->replyto))
2840 compose_entry_append(compose,
2844 /* answer to subscription confirmation */
2845 if (compose->replyto)
2846 compose_entry_append(compose,
2849 else if (msginfo->from)
2850 compose_entry_append(compose,
2855 else if (!(to_all || to_sender) && default_reply_to) {
2856 compose_entry_append(compose,
2857 msginfo->folder->prefs->default_reply_to,
2859 compose_entry_mark_default_to(compose,
2860 msginfo->folder->prefs->default_reply_to);
2865 Xstrdup_a(tmp1, msginfo->from, return);
2866 extract_address(tmp1);
2867 if (to_all || to_sender ||
2868 !account_find_from_address(tmp1))
2869 compose_entry_append(compose,
2870 (compose->replyto && !to_sender)
2871 ? compose->replyto :
2872 msginfo->from ? msginfo->from : "",
2874 else if (!to_all && !to_sender) {
2875 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
2876 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
2877 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
2878 compose_entry_append(compose,
2879 msginfo->from ? msginfo->from : "",
2882 /* replying to own mail, use original recp */
2883 compose_entry_append(compose,
2884 msginfo->to ? msginfo->to : "",
2886 compose_entry_append(compose,
2887 msginfo->cc ? msginfo->cc : "",
2893 if (to_sender || (compose->followup_to &&
2894 !strncmp(compose->followup_to, "poster", 6)))
2895 compose_entry_append
2897 (compose->replyto ? compose->replyto :
2898 msginfo->from ? msginfo->from : ""),
2901 else if (followup_and_reply_to || to_all) {
2902 compose_entry_append
2904 (compose->replyto ? compose->replyto :
2905 msginfo->from ? msginfo->from : ""),
2908 compose_entry_append
2910 compose->followup_to ? compose->followup_to :
2911 compose->newsgroups ? compose->newsgroups : "",
2912 COMPOSE_NEWSGROUPS);
2915 compose_entry_append
2917 compose->followup_to ? compose->followup_to :
2918 compose->newsgroups ? compose->newsgroups : "",
2919 COMPOSE_NEWSGROUPS);
2922 if (msginfo->subject && *msginfo->subject) {
2926 buf = p = g_strdup(msginfo->subject);
2927 p += subject_get_prefix_length(p);
2928 memmove(buf, p, strlen(p) + 1);
2930 buf2 = g_strdup_printf("Re: %s", buf);
2931 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
2936 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
2938 if (to_ml && compose->ml_post) return;
2939 if (!to_all || compose->account->protocol == A_NNTP) return;
2941 if (compose->replyto) {
2942 Xstrdup_a(replyto, compose->replyto, return);
2943 extract_address(replyto);
2945 if (msginfo->from) {
2946 Xstrdup_a(from, msginfo->from, return);
2947 extract_address(from);
2950 if (replyto && from)
2951 cc_list = address_list_append_with_comments(cc_list, from);
2952 if (to_all && msginfo->folder &&
2953 msginfo->folder->prefs->enable_default_reply_to)
2954 cc_list = address_list_append_with_comments(cc_list,
2955 msginfo->folder->prefs->default_reply_to);
2956 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
2957 cc_list = address_list_append_with_comments(cc_list, compose->cc);
2959 to_table = g_hash_table_new(g_str_hash, g_str_equal);
2961 g_hash_table_insert(to_table, g_utf8_strdown(replyto, -1), GINT_TO_POINTER(1));
2962 if (compose->account) {
2963 g_hash_table_insert(to_table, g_utf8_strdown(compose->account->address, -1),
2964 GINT_TO_POINTER(1));
2966 /* remove address on To: and that of current account */
2967 for (cur = cc_list; cur != NULL; ) {
2968 GSList *next = cur->next;
2971 addr = g_utf8_strdown(cur->data, -1);
2972 extract_address(addr);
2974 if (GPOINTER_TO_INT(g_hash_table_lookup(to_table, addr)) == 1)
2975 cc_list = g_slist_remove(cc_list, cur->data);
2977 g_hash_table_insert(to_table, addr, GINT_TO_POINTER(1));
2981 hash_free_strings(to_table);
2982 g_hash_table_destroy(to_table);
2985 for (cur = cc_list; cur != NULL; cur = cur->next)
2986 compose_entry_append(compose, (gchar *)cur->data,
2988 slist_free_strings(cc_list);
2989 g_slist_free(cc_list);
2994 #define SET_ENTRY(entry, str) \
2997 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3000 #define SET_ADDRESS(type, str) \
3003 compose_entry_append(compose, str, type); \
3006 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3008 g_return_if_fail(msginfo != NULL);
3010 SET_ENTRY(subject_entry, msginfo->subject);
3011 SET_ENTRY(from_name, msginfo->from);
3012 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3013 SET_ADDRESS(COMPOSE_CC, compose->cc);
3014 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3015 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3016 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3017 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3019 compose_update_priority_menu_item(compose);
3020 compose_update_privacy_system_menu_item(compose, FALSE);
3021 compose_show_first_last_header(compose, TRUE);
3027 static void compose_insert_sig(Compose *compose, gboolean replace)
3029 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3030 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3032 GtkTextIter iter, iter_end;
3034 gboolean prev_autowrap;
3035 gboolean found = FALSE;
3036 gboolean exists = FALSE;
3038 g_return_if_fail(compose->account != NULL);
3042 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3043 G_CALLBACK(compose_changed_cb),
3046 mark = gtk_text_buffer_get_insert(buffer);
3047 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3048 cur_pos = gtk_text_iter_get_offset (&iter);
3050 gtk_text_buffer_get_end_iter(buffer, &iter);
3052 exists = (compose->sig_str != NULL);
3055 GtkTextIter first_iter, start_iter, end_iter;
3057 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3059 if (!exists || compose->sig_str[0] == '\0')
3062 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3063 compose->signature_tag);
3066 /* include previous \n\n */
3067 gtk_text_iter_backward_chars(&first_iter, 2);
3068 start_iter = first_iter;
3069 end_iter = first_iter;
3071 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3072 compose->signature_tag);
3073 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3074 compose->signature_tag);
3076 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3082 g_free(compose->sig_str);
3083 compose->sig_str = compose_get_signature_str(compose);
3085 cur_pos = gtk_text_iter_get_offset(&iter);
3087 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3088 g_free(compose->sig_str);
3089 compose->sig_str = NULL;
3091 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3093 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3094 gtk_text_iter_forward_chars(&iter, 2);
3095 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3096 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3098 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3099 cur_pos = gtk_text_buffer_get_char_count (buffer);
3101 /* put the cursor where it should be
3102 * either where the quote_fmt says, either before the signature */
3103 if (compose->set_cursor_pos < 0)
3104 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3106 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3107 compose->set_cursor_pos);
3109 gtk_text_buffer_place_cursor(buffer, &iter);
3110 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3111 G_CALLBACK(compose_changed_cb),
3117 static gchar *compose_get_signature_str(Compose *compose)
3119 gchar *sig_body = NULL;
3120 gchar *sig_str = NULL;
3121 gchar *utf8_sig_str = NULL;
3123 g_return_val_if_fail(compose->account != NULL, NULL);
3125 if (!compose->account->sig_path)
3128 if (compose->account->sig_type == SIG_FILE) {
3129 if (!is_file_or_fifo_exist(compose->account->sig_path)) {
3130 g_warning("can't open signature file: %s\n",
3131 compose->account->sig_path);
3136 if (compose->account->sig_type == SIG_COMMAND)
3137 sig_body = get_command_output(compose->account->sig_path);
3141 tmp = file_read_to_str(compose->account->sig_path);
3144 sig_body = normalize_newlines(tmp);
3148 if (compose->account->sig_sep) {
3149 sig_str = g_strconcat("\n\n", compose->account->sig_sep, "\n", sig_body,
3153 sig_str = g_strconcat("\n\n", sig_body, NULL);
3156 if (g_utf8_validate(sig_str, -1, NULL) == TRUE)
3157 utf8_sig_str = sig_str;
3159 utf8_sig_str = conv_codeset_strdup
3160 (sig_str, conv_get_locale_charset_str_no_utf8(),
3166 return utf8_sig_str;
3169 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3172 GtkTextBuffer *buffer;
3175 const gchar *cur_encoding;
3176 gchar buf[BUFFSIZE];
3179 gboolean prev_autowrap;
3180 gboolean badtxt = FALSE;
3182 g_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3184 if ((fp = g_fopen(file, "rb")) == NULL) {
3185 FILE_OP_ERROR(file, "fopen");
3186 return COMPOSE_INSERT_READ_ERROR;
3189 prev_autowrap = compose->autowrap;
3190 compose->autowrap = FALSE;
3192 text = GTK_TEXT_VIEW(compose->text);
3193 buffer = gtk_text_view_get_buffer(text);
3194 mark = gtk_text_buffer_get_insert(buffer);
3195 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3197 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3198 G_CALLBACK(text_inserted),
3201 cur_encoding = conv_get_locale_charset_str_no_utf8();
3203 while (fgets(buf, sizeof(buf), fp) != NULL) {
3206 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3207 str = g_strdup(buf);
3209 str = conv_codeset_strdup
3210 (buf, cur_encoding, CS_INTERNAL);
3213 /* strip <CR> if DOS/Windows file,
3214 replace <CR> with <LF> if Macintosh file. */
3217 if (len > 0 && str[len - 1] != '\n') {
3219 if (str[len] == '\r') str[len] = '\n';
3222 gtk_text_buffer_insert(buffer, &iter, str, -1);
3226 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3227 G_CALLBACK(text_inserted),
3229 compose->autowrap = prev_autowrap;
3230 if (compose->autowrap)
3231 compose_wrap_all(compose);
3236 return COMPOSE_INSERT_INVALID_CHARACTER;
3238 return COMPOSE_INSERT_SUCCESS;
3241 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3242 const gchar *filename,
3243 const gchar *content_type)
3251 GtkListStore *store;
3253 gboolean has_binary = FALSE;
3255 if (!is_file_exist(file)) {
3256 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3257 gboolean result = FALSE;
3258 if (file_from_uri && is_file_exist(file_from_uri)) {
3259 result = compose_attach_append(
3260 compose, file_from_uri,
3264 g_free(file_from_uri);
3267 alertpanel_error("File %s doesn't exist\n", filename);
3270 if ((size = get_file_size(file)) < 0) {
3271 alertpanel_error("Can't get file size of %s\n", filename);
3275 alertpanel_error(_("File %s is empty."), filename);
3278 if ((fp = g_fopen(file, "rb")) == NULL) {
3279 alertpanel_error(_("Can't read %s."), filename);
3284 ainfo = g_new0(AttachInfo, 1);
3285 auto_ainfo = g_auto_pointer_new_with_free
3286 (ainfo, (GFreeFunc) compose_attach_info_free);
3287 ainfo->file = g_strdup(file);
3290 ainfo->content_type = g_strdup(content_type);
3291 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3293 MsgFlags flags = {0, 0};
3295 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3296 ainfo->encoding = ENC_7BIT;
3298 ainfo->encoding = ENC_8BIT;
3300 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3301 if (msginfo && msginfo->subject)
3302 name = g_strdup(msginfo->subject);
3304 name = g_path_get_basename(filename ? filename : file);
3306 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3308 procmsg_msginfo_free(msginfo);
3310 if (!g_ascii_strncasecmp(content_type, "text", 4))
3311 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3313 ainfo->encoding = ENC_BASE64;
3314 name = g_path_get_basename(filename ? filename : file);
3315 ainfo->name = g_strdup(name);
3319 ainfo->content_type = procmime_get_mime_type(file);
3320 if (!ainfo->content_type) {
3321 ainfo->content_type =
3322 g_strdup("application/octet-stream");
3323 ainfo->encoding = ENC_BASE64;
3324 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text", 4))
3326 procmime_get_encoding_for_text_file(file, &has_binary);
3328 ainfo->encoding = ENC_BASE64;
3329 name = g_path_get_basename(filename ? filename : file);
3330 ainfo->name = g_strdup(name);
3334 if (ainfo->name != NULL
3335 && !strcmp(ainfo->name, ".")) {
3336 g_free(ainfo->name);
3340 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3341 g_free(ainfo->content_type);
3342 ainfo->content_type = g_strdup("application/octet-stream");
3346 size_text = to_human_readable(size);
3348 store = GTK_LIST_STORE(gtk_tree_view_get_model
3349 (GTK_TREE_VIEW(compose->attach_clist)));
3351 gtk_list_store_append(store, &iter);
3352 gtk_list_store_set(store, &iter,
3353 COL_MIMETYPE, ainfo->content_type,
3354 COL_SIZE, size_text,
3355 COL_NAME, ainfo->name,
3357 COL_AUTODATA, auto_ainfo,
3360 g_auto_pointer_free(auto_ainfo);
3361 compose_attach_update_label(compose);
3365 static void compose_use_signing(Compose *compose, gboolean use_signing)
3367 GtkItemFactory *ifactory;
3368 GtkWidget *menuitem = NULL;
3370 compose->use_signing = use_signing;
3371 ifactory = gtk_item_factory_from_widget(compose->menubar);
3372 menuitem = gtk_item_factory_get_item
3373 (ifactory, "/Options/Sign");
3374 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
3378 static void compose_use_encryption(Compose *compose, gboolean use_encryption)
3380 GtkItemFactory *ifactory;
3381 GtkWidget *menuitem = NULL;
3383 compose->use_encryption = use_encryption;
3384 ifactory = gtk_item_factory_from_widget(compose->menubar);
3385 menuitem = gtk_item_factory_get_item
3386 (ifactory, "/Options/Encrypt");
3388 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
3392 #define NEXT_PART_NOT_CHILD(info) \
3394 node = info->node; \
3395 while (node->children) \
3396 node = g_node_last_child(node); \
3397 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3400 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3404 MimeInfo *firsttext = NULL;
3405 MimeInfo *encrypted = NULL;
3408 const gchar *partname = NULL;
3410 mimeinfo = procmime_scan_message(msginfo);
3411 if (!mimeinfo) return;
3413 if (mimeinfo->node->children == NULL) {
3414 procmime_mimeinfo_free_all(mimeinfo);
3418 /* find first content part */
3419 child = (MimeInfo *) mimeinfo->node->children->data;
3420 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3421 child = (MimeInfo *)child->node->children->data;
3423 if (child->type == MIMETYPE_TEXT) {
3425 debug_print("First text part found\n");
3426 } else if (compose->mode == COMPOSE_REEDIT &&
3427 child->type == MIMETYPE_APPLICATION &&
3428 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3429 encrypted = (MimeInfo *)child->node->parent->data;
3432 child = (MimeInfo *) mimeinfo->node->children->data;
3433 while (child != NULL) {
3436 if (child == encrypted) {
3437 /* skip this part of tree */
3438 NEXT_PART_NOT_CHILD(child);
3442 if (child->type == MIMETYPE_MULTIPART) {
3443 /* get the actual content */
3444 child = procmime_mimeinfo_next(child);
3448 if (child == firsttext) {
3449 child = procmime_mimeinfo_next(child);
3453 outfile = procmime_get_tmp_file_name(child);
3454 if ((err = procmime_get_part(outfile, child)) < 0)
3455 g_warning("Can't get the part of multipart message. (%s)", strerror(-err));
3457 gchar *content_type;
3459 content_type = procmime_get_content_type_str(child->type, child->subtype);
3461 /* if we meet a pgp signature, we don't attach it, but
3462 * we force signing. */
3463 if ((strcmp(content_type, "application/pgp-signature") &&
3464 strcmp(content_type, "application/pkcs7-signature") &&
3465 strcmp(content_type, "application/x-pkcs7-signature"))
3466 || compose->mode == COMPOSE_REDIRECT) {
3467 partname = procmime_mimeinfo_get_parameter(child, "filename");
3468 if (partname == NULL)
3469 partname = procmime_mimeinfo_get_parameter(child, "name");
3470 if (partname == NULL)
3472 compose_attach_append(compose, outfile,
3473 partname, content_type);
3475 compose_force_signing(compose, compose->account);
3477 g_free(content_type);
3480 NEXT_PART_NOT_CHILD(child);
3482 procmime_mimeinfo_free_all(mimeinfo);
3485 #undef NEXT_PART_NOT_CHILD
3490 WAIT_FOR_INDENT_CHAR,
3491 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3494 /* return indent length, we allow:
3495 indent characters followed by indent characters or spaces/tabs,
3496 alphabets and numbers immediately followed by indent characters,
3497 and the repeating sequences of the above
3498 If quote ends with multiple spaces, only the first one is included. */
3499 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3500 const GtkTextIter *start, gint *len)
3502 GtkTextIter iter = *start;
3506 IndentState state = WAIT_FOR_INDENT_CHAR;
3509 gint alnum_count = 0;
3510 gint space_count = 0;
3513 if (prefs_common.quote_chars == NULL) {
3517 while (!gtk_text_iter_ends_line(&iter)) {
3518 wc = gtk_text_iter_get_char(&iter);
3519 if (g_unichar_iswide(wc))
3521 clen = g_unichar_to_utf8(wc, ch);
3525 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
3526 is_space = g_unichar_isspace(wc);
3528 if (state == WAIT_FOR_INDENT_CHAR) {
3529 if (!is_indent && !g_unichar_isalnum(wc))
3532 quote_len += alnum_count + space_count + 1;
3533 alnum_count = space_count = 0;
3534 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
3537 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
3538 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
3542 else if (is_indent) {
3543 quote_len += alnum_count + space_count + 1;
3544 alnum_count = space_count = 0;
3547 state = WAIT_FOR_INDENT_CHAR;
3551 gtk_text_iter_forward_char(&iter);
3554 if (quote_len > 0 && space_count > 0)
3560 if (quote_len > 0) {
3562 gtk_text_iter_forward_chars(&iter, quote_len);
3563 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
3569 /* return TRUE if the line is itemized */
3570 static gboolean compose_is_itemized(GtkTextBuffer *buffer,
3571 const GtkTextIter *start)
3573 GtkTextIter iter = *start;
3578 if (gtk_text_iter_ends_line(&iter))
3582 wc = gtk_text_iter_get_char(&iter);
3583 if (!g_unichar_isspace(wc))
3585 gtk_text_iter_forward_char(&iter);
3586 if (gtk_text_iter_ends_line(&iter))
3590 clen = g_unichar_to_utf8(wc, ch);
3594 if (!strchr("*-+", ch[0]))
3597 gtk_text_iter_forward_char(&iter);
3598 if (gtk_text_iter_ends_line(&iter))
3600 wc = gtk_text_iter_get_char(&iter);
3601 if (g_unichar_isspace(wc))
3607 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
3608 const GtkTextIter *start,
3609 GtkTextIter *break_pos,
3613 GtkTextIter iter = *start, line_end = *start;
3614 PangoLogAttr *attrs;
3621 gboolean can_break = FALSE;
3622 gboolean do_break = FALSE;
3623 gboolean was_white = FALSE;
3624 gboolean prev_dont_break = FALSE;
3626 gtk_text_iter_forward_to_line_end(&line_end);
3627 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
3628 len = g_utf8_strlen(str, -1);
3632 g_warning("compose_get_line_break_pos: len = 0!\n");
3636 /* g_print("breaking line: %d: %s (len = %d)\n",
3637 gtk_text_iter_get_line(&iter), str, len); */
3639 attrs = g_new(PangoLogAttr, len + 1);
3641 pango_default_break(str, -1, NULL, attrs, len + 1);
3645 /* skip quote and leading spaces */
3646 for (i = 0; *p != '\0' && i < len; i++) {
3649 wc = g_utf8_get_char(p);
3650 if (i >= quote_len && !g_unichar_isspace(wc))
3652 if (g_unichar_iswide(wc))
3654 else if (*p == '\t')
3658 p = g_utf8_next_char(p);
3661 for (; *p != '\0' && i < len; i++) {
3662 PangoLogAttr *attr = attrs + i;
3666 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
3669 was_white = attr->is_white;
3671 /* don't wrap URI */
3672 if ((uri_len = get_uri_len(p)) > 0) {
3674 if (pos > 0 && col > max_col) {
3684 wc = g_utf8_get_char(p);
3685 if (g_unichar_iswide(wc)) {
3687 if (prev_dont_break && can_break && attr->is_line_break)
3689 } else if (*p == '\t')
3693 if (pos > 0 && col > max_col) {
3698 if (*p == '-' || *p == '/')
3699 prev_dont_break = TRUE;
3701 prev_dont_break = FALSE;
3703 p = g_utf8_next_char(p);
3707 debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col);
3712 *break_pos = *start;
3713 gtk_text_iter_set_line_offset(break_pos, pos);
3718 static gboolean compose_join_next_line(Compose *compose,
3719 GtkTextBuffer *buffer,
3721 const gchar *quote_str)
3723 GtkTextIter iter_ = *iter, cur, prev, next, end;
3724 PangoLogAttr attrs[3];
3726 gchar *next_quote_str;
3729 gboolean keep_cursor = FALSE;
3731 if (!gtk_text_iter_forward_line(&iter_) ||
3732 gtk_text_iter_ends_line(&iter_))
3735 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
3737 if ((quote_str || next_quote_str) &&
3738 strcmp2(quote_str, next_quote_str) != 0) {
3739 g_free(next_quote_str);
3742 g_free(next_quote_str);
3745 if (quote_len > 0) {
3746 gtk_text_iter_forward_chars(&end, quote_len);
3747 if (gtk_text_iter_ends_line(&end))
3751 /* don't join itemized lines */
3752 if (compose_is_itemized(buffer, &end))
3755 /* don't join signature separator */
3756 if (compose_is_sig_separator(compose, buffer, &iter_))
3759 /* delete quote str */
3761 gtk_text_buffer_delete(buffer, &iter_, &end);
3763 /* don't join line breaks put by the user */
3765 gtk_text_iter_backward_char(&cur);
3766 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
3767 gtk_text_iter_forward_char(&cur);
3771 gtk_text_iter_forward_char(&cur);
3772 /* delete linebreak and extra spaces */
3773 while (gtk_text_iter_backward_char(&cur)) {
3774 wc1 = gtk_text_iter_get_char(&cur);
3775 if (!g_unichar_isspace(wc1))
3780 while (!gtk_text_iter_ends_line(&cur)) {
3781 wc1 = gtk_text_iter_get_char(&cur);
3782 if (!g_unichar_isspace(wc1))
3784 gtk_text_iter_forward_char(&cur);
3787 if (!gtk_text_iter_equal(&prev, &next)) {
3790 mark = gtk_text_buffer_get_insert(buffer);
3791 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
3792 if (gtk_text_iter_equal(&prev, &cur))
3794 gtk_text_buffer_delete(buffer, &prev, &next);
3798 /* insert space if required */
3799 gtk_text_iter_backward_char(&prev);
3800 wc1 = gtk_text_iter_get_char(&prev);
3801 wc2 = gtk_text_iter_get_char(&next);
3802 gtk_text_iter_forward_char(&next);
3803 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
3804 pango_default_break(str, -1, NULL, attrs, 3);
3805 if (!attrs[1].is_line_break ||
3806 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
3807 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
3809 gtk_text_iter_backward_char(&iter_);
3810 gtk_text_buffer_place_cursor(buffer, &iter_);
3819 #define ADD_TXT_POS(bp_, ep_, pti_) \
3820 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
3821 last = last->next; \
3822 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
3823 last->next = NULL; \
3825 g_warning("alloc error scanning URIs\n"); \
3828 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
3830 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3831 GtkTextBuffer *buffer;
3832 GtkTextIter iter, break_pos, end_of_line;
3833 gchar *quote_str = NULL;
3835 gboolean wrap_quote = prefs_common.linewrap_quote;
3836 gboolean prev_autowrap = compose->autowrap;
3837 gint startq_offset = -1, noq_offset = -1;
3838 gint uri_start = -1, uri_stop = -1;
3839 gint nouri_start = -1, nouri_stop = -1;
3840 gint num_blocks = 0;
3841 gint quotelevel = -1;
3842 gboolean modified = force;
3843 gboolean removed = FALSE;
3844 gboolean modified_before_remove = FALSE;
3846 gboolean start = TRUE;
3851 if (compose->draft_timeout_tag == -2) {
3855 compose->autowrap = FALSE;
3857 buffer = gtk_text_view_get_buffer(text);
3858 undo_wrapping(compose->undostruct, TRUE);
3863 mark = gtk_text_buffer_get_insert(buffer);
3864 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3868 if (compose->draft_timeout_tag == -2) {
3869 if (gtk_text_iter_ends_line(&iter)) {
3870 while (gtk_text_iter_ends_line(&iter) &&
3871 gtk_text_iter_forward_line(&iter))
3874 while (gtk_text_iter_backward_line(&iter)) {
3875 if (gtk_text_iter_ends_line(&iter)) {
3876 gtk_text_iter_forward_line(&iter);
3882 /* move to line start */
3883 gtk_text_iter_set_line_offset(&iter, 0);
3885 /* go until paragraph end (empty line) */
3886 while (start || !gtk_text_iter_ends_line(&iter)) {
3887 gchar *scanpos = NULL;
3888 /* parse table - in order of priority */
3890 const gchar *needle; /* token */
3892 /* token search function */
3893 gchar *(*search) (const gchar *haystack,
3894 const gchar *needle);
3895 /* part parsing function */
3896 gboolean (*parse) (const gchar *start,
3897 const gchar *scanpos,
3901 /* part to URI function */
3902 gchar *(*build_uri) (const gchar *bp,
3906 static struct table parser[] = {
3907 {"http://", strcasestr, get_uri_part, make_uri_string},
3908 {"https://", strcasestr, get_uri_part, make_uri_string},
3909 {"ftp://", strcasestr, get_uri_part, make_uri_string},
3910 {"sftp://", strcasestr, get_uri_part, make_uri_string},
3911 {"www.", strcasestr, get_uri_part, make_http_string},
3912 {"mailto:", strcasestr, get_uri_part, make_uri_string},
3913 {"@", strcasestr, get_email_part, make_email_string}
3915 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
3916 gint last_index = PARSE_ELEMS;
3918 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
3922 if (!prev_autowrap && num_blocks == 0) {
3924 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3925 G_CALLBACK(text_inserted),
3928 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
3931 uri_start = uri_stop = -1;
3933 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
3936 debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str);
3937 if (startq_offset == -1)
3938 startq_offset = gtk_text_iter_get_offset(&iter);
3939 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
3940 if (quotelevel > 2) {
3941 /* recycle colors */
3942 if (prefs_common.recycle_quote_colors)
3951 if (startq_offset == -1)
3952 noq_offset = gtk_text_iter_get_offset(&iter);
3956 if (prev_autowrap == FALSE && !force && !wrap_quote) {
3959 if (gtk_text_iter_ends_line(&iter)) {
3961 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
3962 prefs_common.linewrap_len,
3964 GtkTextIter prev, next, cur;
3966 if (prev_autowrap != FALSE || force) {
3967 compose->automatic_break = TRUE;
3969 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
3970 compose->automatic_break = FALSE;
3971 } else if (quote_str && wrap_quote) {
3972 compose->automatic_break = TRUE;
3974 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
3975 compose->automatic_break = FALSE;
3978 /* remove trailing spaces */
3980 gtk_text_iter_backward_char(&cur);
3982 while (!gtk_text_iter_starts_line(&cur)) {
3985 gtk_text_iter_backward_char(&cur);
3986 wc = gtk_text_iter_get_char(&cur);
3987 if (!g_unichar_isspace(wc))
3991 if (!gtk_text_iter_equal(&prev, &next)) {
3992 gtk_text_buffer_delete(buffer, &prev, &next);
3994 gtk_text_iter_forward_char(&break_pos);
3998 gtk_text_buffer_insert(buffer, &break_pos,
4002 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4004 /* move iter to current line start */
4005 gtk_text_iter_set_line_offset(&iter, 0);
4012 /* move iter to next line start */
4018 if (!prev_autowrap && num_blocks > 0) {
4020 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4021 G_CALLBACK(text_inserted),
4025 while (!gtk_text_iter_ends_line(&end_of_line)) {
4026 gtk_text_iter_forward_char(&end_of_line);
4028 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4030 nouri_start = gtk_text_iter_get_offset(&iter);
4031 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4033 walk_pos = gtk_text_iter_get_offset(&iter);
4034 /* FIXME: this looks phony. scanning for anything in the parse table */
4035 for (n = 0; n < PARSE_ELEMS; n++) {
4038 tmp = parser[n].search(walk, parser[n].needle);
4040 if (scanpos == NULL || tmp < scanpos) {
4049 /* check if URI can be parsed */
4050 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4051 (const gchar **)&ep, FALSE)
4052 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4056 strlen(parser[last_index].needle);
4059 uri_start = walk_pos + (bp - o_walk);
4060 uri_stop = walk_pos + (ep - o_walk);
4064 gtk_text_iter_forward_line(&iter);
4067 if (startq_offset != -1) {
4068 GtkTextIter startquote, endquote;
4069 gtk_text_buffer_get_iter_at_offset(
4070 buffer, &startquote, startq_offset);
4073 switch (quotelevel) {
4075 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4076 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4077 gtk_text_buffer_apply_tag_by_name(
4078 buffer, "quote0", &startquote, &endquote);
4079 gtk_text_buffer_remove_tag_by_name(
4080 buffer, "quote1", &startquote, &endquote);
4081 gtk_text_buffer_remove_tag_by_name(
4082 buffer, "quote2", &startquote, &endquote);
4087 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4088 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4089 gtk_text_buffer_apply_tag_by_name(
4090 buffer, "quote1", &startquote, &endquote);
4091 gtk_text_buffer_remove_tag_by_name(
4092 buffer, "quote0", &startquote, &endquote);
4093 gtk_text_buffer_remove_tag_by_name(
4094 buffer, "quote2", &startquote, &endquote);
4099 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4100 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4101 gtk_text_buffer_apply_tag_by_name(
4102 buffer, "quote2", &startquote, &endquote);
4103 gtk_text_buffer_remove_tag_by_name(
4104 buffer, "quote0", &startquote, &endquote);
4105 gtk_text_buffer_remove_tag_by_name(
4106 buffer, "quote1", &startquote, &endquote);
4112 } else if (noq_offset != -1) {
4113 GtkTextIter startnoquote, endnoquote;
4114 gtk_text_buffer_get_iter_at_offset(
4115 buffer, &startnoquote, noq_offset);
4118 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4119 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4120 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4121 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4122 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4123 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4124 gtk_text_buffer_remove_tag_by_name(
4125 buffer, "quote0", &startnoquote, &endnoquote);
4126 gtk_text_buffer_remove_tag_by_name(
4127 buffer, "quote1", &startnoquote, &endnoquote);
4128 gtk_text_buffer_remove_tag_by_name(
4129 buffer, "quote2", &startnoquote, &endnoquote);
4135 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4136 GtkTextIter nouri_start_iter, nouri_end_iter;
4137 gtk_text_buffer_get_iter_at_offset(
4138 buffer, &nouri_start_iter, nouri_start);
4139 gtk_text_buffer_get_iter_at_offset(
4140 buffer, &nouri_end_iter, nouri_stop);
4141 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4142 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4143 gtk_text_buffer_remove_tag_by_name(
4144 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4145 modified_before_remove = modified;
4150 if (uri_start > 0 && uri_stop > 0) {
4151 GtkTextIter uri_start_iter, uri_end_iter, back;
4152 gtk_text_buffer_get_iter_at_offset(
4153 buffer, &uri_start_iter, uri_start);
4154 gtk_text_buffer_get_iter_at_offset(
4155 buffer, &uri_end_iter, uri_stop);
4156 back = uri_end_iter;
4157 gtk_text_iter_backward_char(&back);
4158 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4159 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4160 gtk_text_buffer_apply_tag_by_name(
4161 buffer, "link", &uri_start_iter, &uri_end_iter);
4163 if (removed && !modified_before_remove) {
4169 debug_print("not modified, out after %d lines\n", lines);
4174 debug_print("modified, out after %d lines\n", lines);
4178 undo_wrapping(compose->undostruct, FALSE);
4179 compose->autowrap = prev_autowrap;
4184 void compose_action_cb(void *data)
4186 Compose *compose = (Compose *)data;
4187 compose_wrap_all(compose);
4190 static void compose_wrap_all(Compose *compose)
4192 compose_wrap_all_full(compose, FALSE);
4195 static void compose_wrap_all_full(Compose *compose, gboolean force)
4197 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4198 GtkTextBuffer *buffer;
4200 gboolean modified = TRUE;
4202 buffer = gtk_text_view_get_buffer(text);
4204 gtk_text_buffer_get_start_iter(buffer, &iter);
4205 while (!gtk_text_iter_is_end(&iter) && modified)
4206 modified = compose_beautify_paragraph(compose, &iter, force);
4210 static void compose_set_title(Compose *compose)
4216 edited = compose->modified ? _(" [Edited]") : "";
4218 subject = gtk_editable_get_chars(
4219 GTK_EDITABLE(compose->subject_entry), 0, -1);
4222 if (subject && strlen(subject))
4223 str = g_strdup_printf(_("%s - Compose message%s"),
4226 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4228 str = g_strdup(_("Compose message"));
4231 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4237 * compose_current_mail_account:
4239 * Find a current mail account (the currently selected account, or the
4240 * default account, if a news account is currently selected). If a
4241 * mail account cannot be found, display an error message.
4243 * Return value: Mail account, or NULL if not found.
4245 static PrefsAccount *
4246 compose_current_mail_account(void)
4250 if (cur_account && cur_account->protocol != A_NNTP)
4253 ac = account_get_default();
4254 if (!ac || ac->protocol == A_NNTP) {
4255 alertpanel_error(_("Account for sending mail is not specified.\n"
4256 "Please select a mail account before sending."));
4263 #define QUOTE_IF_REQUIRED(out, str) \
4265 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4269 len = strlen(str) + 3; \
4270 if ((__tmp = alloca(len)) == NULL) { \
4271 g_warning("can't allocate memory\n"); \
4272 g_string_free(header, TRUE); \
4275 g_snprintf(__tmp, len, "\"%s\"", str); \
4280 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4281 g_warning("can't allocate memory\n"); \
4282 g_string_free(header, TRUE); \
4285 strcpy(__tmp, str); \
4291 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4293 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4297 len = strlen(str) + 3; \
4298 if ((__tmp = alloca(len)) == NULL) { \
4299 g_warning("can't allocate memory\n"); \
4302 g_snprintf(__tmp, len, "\"%s\"", str); \
4307 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4308 g_warning("can't allocate memory\n"); \
4311 strcpy(__tmp, str); \
4317 static void compose_select_account(Compose *compose, PrefsAccount *account,
4320 GtkItemFactory *ifactory;
4323 g_return_if_fail(account != NULL);
4325 compose->account = account;
4327 if (account->name && *account->name) {
4329 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4330 from = g_strdup_printf("%s <%s>",
4331 buf, account->address);
4332 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4334 from = g_strdup_printf("<%s>",
4336 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4341 compose_set_title(compose);
4343 ifactory = gtk_item_factory_from_widget(compose->menubar);
4345 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4346 menu_set_active(ifactory, "/Options/Sign", TRUE);
4348 menu_set_active(ifactory, "/Options/Sign", FALSE);
4349 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4350 menu_set_active(ifactory, "/Options/Encrypt", TRUE);
4352 menu_set_active(ifactory, "/Options/Encrypt", FALSE);
4354 activate_privacy_system(compose, account, FALSE);
4356 if (!init && compose->mode != COMPOSE_REDIRECT) {
4357 undo_block(compose->undostruct);
4358 compose_insert_sig(compose, TRUE);
4359 undo_unblock(compose->undostruct);
4363 /* use account's dict info if set */
4364 if (compose->gtkaspell) {
4365 if (account->enable_default_dictionary)
4366 gtkaspell_change_dict(compose->gtkaspell,
4367 account->default_dictionary, FALSE);
4368 if (account->enable_default_alt_dictionary)
4369 gtkaspell_change_alt_dict(compose->gtkaspell,
4370 account->default_alt_dictionary);
4371 if (account->enable_default_dictionary
4372 || account->enable_default_alt_dictionary)
4373 compose_spell_menu_changed(compose);
4378 gboolean compose_check_for_valid_recipient(Compose *compose) {
4379 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
4380 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
4381 gboolean recipient_found = FALSE;
4385 /* free to and newsgroup list */
4386 slist_free_strings(compose->to_list);
4387 g_slist_free(compose->to_list);
4388 compose->to_list = NULL;
4390 slist_free_strings(compose->newsgroup_list);
4391 g_slist_free(compose->newsgroup_list);
4392 compose->newsgroup_list = NULL;
4394 /* search header entries for to and newsgroup entries */
4395 for (list = compose->header_list; list; list = list->next) {
4398 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_BIN(((ComposeHeaderEntry *)list->data)->combo)->child), 0, -1);
4399 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4402 if (entry[0] != '\0') {
4403 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
4404 if (!strcmp(header, prefs_common_translated_header_name(*strptr))) {
4405 compose->to_list = address_list_append(compose->to_list, entry);
4406 recipient_found = TRUE;
4409 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
4410 if (!strcmp(header, prefs_common_translated_header_name(*strptr))) {
4411 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
4412 recipient_found = TRUE;
4419 return recipient_found;
4422 static gboolean compose_check_for_set_recipients(Compose *compose)
4424 if (compose->account->set_autocc && compose->account->auto_cc) {
4425 gboolean found_other = FALSE;
4427 /* search header entries for to and newsgroup entries */
4428 for (list = compose->header_list; list; list = list->next) {
4431 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4432 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_BIN(((ComposeHeaderEntry *)list->data)->combo)->child), 0, -1);
4434 if (strcmp(entry, compose->account->auto_cc)
4435 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
4445 if (compose->batch) {
4446 gtk_widget_show_all(compose->window);
4448 aval = alertpanel(_("Send"),
4449 _("The only recipient is the default CC address. Send anyway?"),
4450 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4451 if (aval != G_ALERTALTERNATE)
4455 if (compose->account->set_autobcc && compose->account->auto_bcc) {
4456 gboolean found_other = FALSE;
4458 /* search header entries for to and newsgroup entries */
4459 for (list = compose->header_list; list; list = list->next) {
4462 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4463 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_BIN(((ComposeHeaderEntry *)list->data)->combo)->child), 0, -1);
4465 if (strcmp(entry, compose->account->auto_bcc)
4466 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
4476 if (compose->batch) {
4477 gtk_widget_show_all(compose->window);
4479 aval = alertpanel(_("Send"),
4480 _("The only recipient is the default BCC address. Send anyway?"),
4481 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4482 if (aval != G_ALERTALTERNATE)
4489 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
4493 if (compose_check_for_valid_recipient(compose) == FALSE) {
4494 if (compose->batch) {
4495 gtk_widget_show_all(compose->window);
4497 alertpanel_error(_("Recipient is not specified."));
4501 if (compose_check_for_set_recipients(compose) == FALSE) {
4505 if (!compose->batch) {
4506 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
4507 if (*str == '\0' && check_everything == TRUE &&
4508 compose->mode != COMPOSE_REDIRECT) {
4510 gchar *button_label;
4513 if (compose->sending)
4514 button_label = _("+_Send");
4516 button_label = _("+_Queue");
4517 message = g_strdup_printf(_("Subject is empty. %s it anyway?"),
4518 compose->sending?_("Send"):_("Queue"));
4520 aval = alertpanel(compose->sending?_("Send"):_("Send later"), message,
4521 GTK_STOCK_CANCEL, button_label, NULL);
4523 if (aval != G_ALERTALTERNATE)
4528 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
4534 gint compose_send(Compose *compose)
4537 FolderItem *folder = NULL;
4539 gchar *msgpath = NULL;
4540 gboolean discard_window = FALSE;
4541 gchar *errstr = NULL;
4542 gchar *tmsgid = NULL;
4543 MainWindow *mainwin = mainwindow_get_mainwindow();
4544 gboolean queued_removed = FALSE;
4546 if (prefs_common.send_dialog_invisible
4547 || compose->batch == TRUE)
4548 discard_window = TRUE;
4550 compose_allow_user_actions (compose, FALSE);
4551 compose->sending = TRUE;
4553 if (compose_check_entries(compose, TRUE) == FALSE) {
4554 if (compose->batch) {
4555 gtk_widget_show_all(compose->window);
4561 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
4564 if (compose->batch) {
4565 gtk_widget_show_all(compose->window);
4568 alertpanel_error(_("Could not queue message for sending:\n\n"
4569 "Charset conversion failed."));
4570 } else if (val == -5) {
4571 alertpanel_error(_("Could not queue message for sending:\n\n"
4572 "Couldn't get recipient encryption key."));
4573 } else if (val == -6) {
4575 } else if (val == -3) {
4576 if (privacy_peek_error())
4577 alertpanel_error(_("Could not queue message for sending:\n\n"
4578 "Signature failed: %s"), privacy_get_error());
4579 } else if (val == -2 && errno != 0) {
4580 alertpanel_error(_("Could not queue message for sending:\n\n%s."), strerror(errno));
4582 alertpanel_error(_("Could not queue message for sending."));
4587 tmsgid = g_strdup(compose->msgid);
4588 if (discard_window) {
4589 compose->sending = FALSE;
4590 compose_close(compose);
4591 /* No more compose access in the normal codepath
4592 * after this point! */
4597 alertpanel_error(_("The message was queued but could not be "
4598 "sent.\nUse \"Send queued messages\" from "
4599 "the main window to retry."));
4600 if (!discard_window) {
4607 if (msgpath == NULL) {
4608 msgpath = folder_item_fetch_msg(folder, msgnum);
4609 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
4612 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
4616 if (!discard_window) {
4618 if (!queued_removed)
4619 folder_item_remove_msg(folder, msgnum);
4620 folder_item_scan(folder);
4622 /* make sure we delete that */
4623 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
4625 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
4626 folder_item_remove_msg(folder, tmp->msgnum);
4627 procmsg_msginfo_free(tmp);
4634 if (!queued_removed)
4635 folder_item_remove_msg(folder, msgnum);
4636 folder_item_scan(folder);
4638 /* make sure we delete that */
4639 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
4641 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
4642 folder_item_remove_msg(folder, tmp->msgnum);
4643 procmsg_msginfo_free(tmp);
4646 if (!discard_window) {
4647 compose->sending = FALSE;
4648 compose_allow_user_actions (compose, TRUE);
4649 compose_close(compose);
4653 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
4654 "the main window to retry."), errstr);
4657 alertpanel_error_log(_("The message was queued but could not be "
4658 "sent.\nUse \"Send queued messages\" from "
4659 "the main window to retry."));
4661 if (!discard_window) {
4670 toolbar_main_set_sensitive(mainwin);
4671 main_window_set_menu_sensitive(mainwin);
4677 compose_allow_user_actions (compose, TRUE);
4678 compose->sending = FALSE;
4679 compose->modified = TRUE;
4680 toolbar_main_set_sensitive(mainwin);
4681 main_window_set_menu_sensitive(mainwin);
4686 static gboolean compose_use_attach(Compose *compose)
4688 GtkTreeModel *model = gtk_tree_view_get_model
4689 (GTK_TREE_VIEW(compose->attach_clist));
4690 return gtk_tree_model_iter_n_children(model, NULL) > 0;
4693 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
4696 gchar buf[BUFFSIZE];
4698 gboolean first_to_address;
4699 gboolean first_cc_address;
4701 ComposeHeaderEntry *headerentry;
4702 const gchar *headerentryname;
4703 const gchar *cc_hdr;
4704 const gchar *to_hdr;
4705 gboolean err = FALSE;
4707 debug_print("Writing redirect header\n");
4709 cc_hdr = prefs_common_translated_header_name("Cc:");
4710 to_hdr = prefs_common_translated_header_name("To:");
4712 first_to_address = TRUE;
4713 for (list = compose->header_list; list; list = list->next) {
4714 headerentry = ((ComposeHeaderEntry *)list->data);
4715 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_BIN(headerentry->combo)->child));
4717 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
4718 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
4719 Xstrdup_a(str, entstr, return -1);
4721 if (str[0] != '\0') {
4722 compose_convert_header
4723 (compose, buf, sizeof(buf), str,
4724 strlen("Resent-To") + 2, TRUE);
4726 if (first_to_address) {
4727 err |= (fprintf(fp, "Resent-To: ") < 0);
4728 first_to_address = FALSE;
4730 err |= (fprintf(fp, ",") < 0);
4732 err |= (fprintf(fp, "%s", buf) < 0);
4736 if (!first_to_address) {
4737 err |= (fprintf(fp, "\n") < 0);
4740 first_cc_address = TRUE;
4741 for (list = compose->header_list; list; list = list->next) {
4742 headerentry = ((ComposeHeaderEntry *)list->data);
4743 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_BIN(headerentry->combo)->child));
4745 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
4746 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
4747 Xstrdup_a(str, strg, return -1);
4749 if (str[0] != '\0') {
4750 compose_convert_header
4751 (compose, buf, sizeof(buf), str,
4752 strlen("Resent-Cc") + 2, TRUE);
4754 if (first_cc_address) {
4755 err |= (fprintf(fp, "Resent-Cc: ") < 0);
4756 first_cc_address = FALSE;
4758 err |= (fprintf(fp, ",") < 0);
4760 err |= (fprintf(fp, "%s", buf) < 0);
4764 if (!first_cc_address) {
4765 err |= (fprintf(fp, "\n") < 0);
4768 return (err ? -1:0);
4771 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
4773 gchar buf[BUFFSIZE];
4775 const gchar *entstr;
4776 /* struct utsname utsbuf; */
4777 gboolean err = FALSE;
4779 g_return_val_if_fail(fp != NULL, -1);
4780 g_return_val_if_fail(compose->account != NULL, -1);
4781 g_return_val_if_fail(compose->account->address != NULL, -1);
4784 get_rfc822_date(buf, sizeof(buf));
4785 err |= (fprintf(fp, "Resent-Date: %s\n", buf) < 0);
4788 if (compose->account->name && *compose->account->name) {
4789 compose_convert_header
4790 (compose, buf, sizeof(buf), compose->account->name,
4791 strlen("From: "), TRUE);
4792 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
4793 buf, compose->account->address) < 0);
4795 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
4798 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
4799 if (*entstr != '\0') {
4800 Xstrdup_a(str, entstr, return -1);
4803 compose_convert_header(compose, buf, sizeof(buf), str,
4804 strlen("Subject: "), FALSE);
4805 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
4809 /* Resent-Message-ID */
4810 if (compose->account->set_domain && compose->account->domain) {
4811 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
4812 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
4813 g_snprintf(buf, sizeof(buf), "%s",
4814 strchr(compose->account->address, '@') ?
4815 strchr(compose->account->address, '@')+1 :
4816 compose->account->address);
4818 g_snprintf(buf, sizeof(buf), "%s", "");
4820 generate_msgid(buf, sizeof(buf));
4821 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", buf) < 0);
4822 compose->msgid = g_strdup(buf);
4824 if (compose_redirect_write_headers_from_headerlist(compose, fp))
4827 /* separator between header and body */
4828 err |= (fputs("\n", fp) == EOF);
4830 return (err ? -1:0);
4833 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
4837 gchar buf[BUFFSIZE];
4839 gboolean skip = FALSE;
4840 gboolean err = FALSE;
4841 gchar *not_included[]={
4842 "Return-Path:", "Delivered-To:", "Received:",
4843 "Subject:", "X-UIDL:", "AF:",
4844 "NF:", "PS:", "SRH:",
4845 "SFN:", "DSR:", "MID:",
4846 "CFG:", "PT:", "S:",
4847 "RQ:", "SSV:", "NSV:",
4848 "SSH:", "R:", "MAID:",
4849 "NAID:", "RMID:", "FMID:",
4850 "SCF:", "RRCPT:", "NG:",
4851 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
4852 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
4853 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
4854 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
4857 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
4858 FILE_OP_ERROR(compose->redirect_filename, "fopen");
4862 while (procheader_get_one_field_asis(buf, sizeof(buf), fp) != -1) {
4864 for (i = 0; not_included[i] != NULL; i++) {
4865 if (g_ascii_strncasecmp(buf, not_included[i],
4866 strlen(not_included[i])) == 0) {
4873 if (fputs(buf, fdest) == -1)
4876 if (!prefs_common.redirect_keep_from) {
4877 if (g_ascii_strncasecmp(buf, "From:",
4878 strlen("From:")) == 0) {
4879 err |= (fputs(" (by way of ", fdest) == EOF);
4880 if (compose->account->name
4881 && *compose->account->name) {
4882 compose_convert_header
4883 (compose, buf, sizeof(buf),
4884 compose->account->name,
4887 err |= (fprintf(fdest, "%s <%s>",
4889 compose->account->address) < 0);
4891 err |= (fprintf(fdest, "%s",
4892 compose->account->address) < 0);
4893 err |= (fputs(")", fdest) == EOF);
4897 if (fputs("\n", fdest) == -1)
4904 if (compose_redirect_write_headers(compose, fdest))
4907 while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
4908 if (fwrite(buf, sizeof(gchar), len, fdest) != len)
4921 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
4923 GtkTextBuffer *buffer;
4924 GtkTextIter start, end;
4927 const gchar *out_codeset;
4928 EncodingType encoding;
4929 MimeInfo *mimemsg, *mimetext;
4932 if (action == COMPOSE_WRITE_FOR_SEND)
4933 attach_parts = TRUE;
4935 /* create message MimeInfo */
4936 mimemsg = procmime_mimeinfo_new();
4937 mimemsg->type = MIMETYPE_MESSAGE;
4938 mimemsg->subtype = g_strdup("rfc822");
4939 mimemsg->content = MIMECONTENT_MEM;
4940 mimemsg->tmp = TRUE; /* must free content later */
4941 mimemsg->data.mem = compose_get_header(compose);
4943 /* Create text part MimeInfo */
4944 /* get all composed text */
4945 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
4946 gtk_text_buffer_get_start_iter(buffer, &start);
4947 gtk_text_buffer_get_end_iter(buffer, &end);
4948 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
4949 if (is_ascii_str(chars)) {
4952 out_codeset = CS_US_ASCII;
4953 encoding = ENC_7BIT;
4955 const gchar *src_codeset = CS_INTERNAL;
4957 out_codeset = conv_get_charset_str(compose->out_encoding);
4960 gchar *test_conv_global_out = NULL;
4961 gchar *test_conv_reply = NULL;
4963 /* automatic mode. be automatic. */
4964 codeconv_set_strict(TRUE);
4966 out_codeset = conv_get_outgoing_charset_str();
4968 debug_print("trying to convert to %s\n", out_codeset);
4969 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
4972 if (!test_conv_global_out && compose->orig_charset
4973 && strcmp(compose->orig_charset, CS_US_ASCII)) {
4974 out_codeset = compose->orig_charset;
4975 debug_print("failure; trying to convert to %s\n", out_codeset);
4976 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
4979 if (!test_conv_global_out && !test_conv_reply) {
4981 out_codeset = CS_INTERNAL;
4982 debug_print("failure; finally using %s\n", out_codeset);
4984 g_free(test_conv_global_out);
4985 g_free(test_conv_reply);
4986 codeconv_set_strict(FALSE);
4989 if (!g_ascii_strcasecmp(out_codeset, CS_US_ASCII))
4990 out_codeset = CS_ISO_8859_1;
4992 if (prefs_common.encoding_method == CTE_BASE64)
4993 encoding = ENC_BASE64;
4994 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
4995 encoding = ENC_QUOTED_PRINTABLE;
4996 else if (prefs_common.encoding_method == CTE_8BIT)
4997 encoding = ENC_8BIT;
4999 encoding = procmime_get_encoding_for_charset(out_codeset);
5001 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5002 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5004 if (action == COMPOSE_WRITE_FOR_SEND) {
5005 codeconv_set_strict(TRUE);
5006 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5007 codeconv_set_strict(FALSE);
5013 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5014 "to the specified %s charset.\n"
5015 "Send it as %s?"), out_codeset, src_codeset);
5016 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL, _("+_Send"), NULL, FALSE,
5017 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5020 if (aval != G_ALERTALTERNATE) {
5025 out_codeset = src_codeset;
5031 out_codeset = src_codeset;
5037 if (encoding == ENC_8BIT || encoding == ENC_7BIT) {
5038 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5039 strstr(buf, "\nFrom ") != NULL) {
5040 encoding = ENC_QUOTED_PRINTABLE;
5044 mimetext = procmime_mimeinfo_new();
5045 mimetext->content = MIMECONTENT_MEM;
5046 mimetext->tmp = TRUE; /* must free content later */
5047 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5048 * and free the data, which we need later. */
5049 mimetext->data.mem = g_strdup(buf);
5050 mimetext->type = MIMETYPE_TEXT;
5051 mimetext->subtype = g_strdup("plain");
5052 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5053 g_strdup(out_codeset));
5055 /* protect trailing spaces when signing message */
5056 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5057 privacy_system_can_sign(compose->privacy_system)) {
5058 encoding = ENC_QUOTED_PRINTABLE;
5061 debug_print("main text: %zd bytes encoded as %s in %d\n",
5062 strlen(buf), out_codeset, encoding);
5064 /* check for line length limit */
5065 if (action == COMPOSE_WRITE_FOR_SEND &&
5066 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5067 check_line_length(buf, 1000, &line) < 0) {
5071 msg = g_strdup_printf
5072 (_("Line %d exceeds the line length limit (998 bytes).\n"
5073 "The contents of the message might be broken on the way to the delivery.\n"
5075 "Send it anyway?"), line + 1);
5076 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5078 if (aval != G_ALERTALTERNATE) {
5084 if (encoding != ENC_UNKNOWN)
5085 procmime_encode_content(mimetext, encoding);
5087 /* append attachment parts */
5088 if (compose_use_attach(compose) && attach_parts) {
5089 MimeInfo *mimempart;
5090 gchar *boundary = NULL;
5091 mimempart = procmime_mimeinfo_new();
5092 mimempart->content = MIMECONTENT_EMPTY;
5093 mimempart->type = MIMETYPE_MULTIPART;
5094 mimempart->subtype = g_strdup("mixed");
5098 boundary = generate_mime_boundary(NULL);
5099 } while (strstr(buf, boundary) != NULL);
5101 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5104 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5106 g_node_append(mimempart->node, mimetext->node);
5107 g_node_append(mimemsg->node, mimempart->node);
5109 compose_add_attachments(compose, mimempart);
5111 g_node_append(mimemsg->node, mimetext->node);
5115 /* sign message if sending */
5116 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5117 privacy_system_can_sign(compose->privacy_system))
5118 if (!privacy_sign(compose->privacy_system, mimemsg, compose->account))
5121 procmime_write_mimeinfo(mimemsg, fp);
5123 procmime_mimeinfo_free_all(mimemsg);
5128 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5130 GtkTextBuffer *buffer;
5131 GtkTextIter start, end;
5136 if ((fp = g_fopen(file, "wb")) == NULL) {
5137 FILE_OP_ERROR(file, "fopen");
5141 /* chmod for security */
5142 if (change_file_mode_rw(fp, file) < 0) {
5143 FILE_OP_ERROR(file, "chmod");
5144 g_warning("can't change file mode\n");
5147 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5148 gtk_text_buffer_get_start_iter(buffer, &start);
5149 gtk_text_buffer_get_end_iter(buffer, &end);
5150 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5152 chars = conv_codeset_strdup
5153 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5156 if (!chars) return -1;
5159 len = strlen(chars);
5160 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5161 FILE_OP_ERROR(file, "fwrite");
5170 if (fclose(fp) == EOF) {
5171 FILE_OP_ERROR(file, "fclose");
5178 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5181 MsgInfo *msginfo = compose->targetinfo;
5183 g_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5184 if (!msginfo) return -1;
5186 if (!force && MSG_IS_LOCKED(msginfo->flags))
5189 item = msginfo->folder;
5190 g_return_val_if_fail(item != NULL, -1);
5192 if (procmsg_msg_exist(msginfo) &&
5193 (folder_has_parent_of_type(item, F_QUEUE) ||
5194 folder_has_parent_of_type(item, F_DRAFT)
5195 || msginfo == compose->autosaved_draft)) {
5196 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5197 g_warning("can't remove the old message\n");
5200 debug_print("removed reedit target %d\n", msginfo->msgnum);
5207 static void compose_remove_draft(Compose *compose)
5210 MsgInfo *msginfo = compose->targetinfo;
5211 drafts = account_get_special_folder(compose->account, F_DRAFT);
5213 if (procmsg_msg_exist(msginfo)) {
5214 folder_item_remove_msg(drafts, msginfo->msgnum);
5219 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5220 gboolean remove_reedit_target)
5222 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5225 static gboolean compose_warn_encryption(Compose *compose)
5227 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5228 AlertValue val = G_ALERTALTERNATE;
5230 if (warning == NULL)
5233 val = alertpanel_full(_("Encryption warning"), warning,
5234 GTK_STOCK_CANCEL, _("+C_ontinue"), NULL,
5235 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5236 if (val & G_ALERTDISABLE) {
5237 val &= ~G_ALERTDISABLE;
5238 if (val == G_ALERTALTERNATE)
5239 privacy_inhibit_encrypt_warning(compose->privacy_system,
5243 if (val == G_ALERTALTERNATE) {
5250 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
5251 gchar **msgpath, gboolean check_subject,
5252 gboolean remove_reedit_target)
5259 static gboolean lock = FALSE;
5260 PrefsAccount *mailac = NULL, *newsac = NULL;
5261 gboolean err = FALSE;
5263 debug_print("queueing message...\n");
5264 g_return_val_if_fail(compose->account != NULL, -1);
5268 if (compose_check_entries(compose, check_subject) == FALSE) {
5270 if (compose->batch) {
5271 gtk_widget_show_all(compose->window);
5276 if (!compose->to_list && !compose->newsgroup_list) {
5277 g_warning("can't get recipient list.");
5282 if (compose->to_list) {
5283 if (compose->account->protocol != A_NNTP)
5284 mailac = compose->account;
5285 else if (cur_account && cur_account->protocol != A_NNTP)
5286 mailac = cur_account;
5287 else if (!(mailac = compose_current_mail_account())) {
5289 alertpanel_error(_("No account for sending mails available!"));
5294 if (compose->newsgroup_list) {
5295 if (compose->account->protocol == A_NNTP)
5296 newsac = compose->account;
5297 else if (!newsac->protocol != A_NNTP) {
5299 alertpanel_error(_("No account for posting news available!"));
5304 /* write queue header */
5305 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
5306 G_DIR_SEPARATOR, compose, (guint) rand());
5307 debug_print("queuing to %s\n", tmp);
5308 if ((fp = g_fopen(tmp, "wb")) == NULL) {
5309 FILE_OP_ERROR(tmp, "fopen");
5315 if (change_file_mode_rw(fp, tmp) < 0) {
5316 FILE_OP_ERROR(tmp, "chmod");
5317 g_warning("can't change file mode\n");
5320 /* queueing variables */
5321 err |= (fprintf(fp, "AF:\n") < 0);
5322 err |= (fprintf(fp, "NF:0\n") < 0);
5323 err |= (fprintf(fp, "PS:10\n") < 0);
5324 err |= (fprintf(fp, "SRH:1\n") < 0);
5325 err |= (fprintf(fp, "SFN:\n") < 0);
5326 err |= (fprintf(fp, "DSR:\n") < 0);
5328 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
5330 err |= (fprintf(fp, "MID:\n") < 0);
5331 err |= (fprintf(fp, "CFG:\n") < 0);
5332 err |= (fprintf(fp, "PT:0\n") < 0);
5333 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
5334 err |= (fprintf(fp, "RQ:\n") < 0);
5336 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
5338 err |= (fprintf(fp, "SSV:\n") < 0);
5340 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
5342 err |= (fprintf(fp, "NSV:\n") < 0);
5343 err |= (fprintf(fp, "SSH:\n") < 0);
5344 /* write recepient list */
5345 if (compose->to_list) {
5346 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
5347 for (cur = compose->to_list->next; cur != NULL;
5349 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
5350 err |= (fprintf(fp, "\n") < 0);
5352 /* write newsgroup list */
5353 if (compose->newsgroup_list) {
5354 err |= (fprintf(fp, "NG:") < 0);
5355 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
5356 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
5357 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
5358 err |= (fprintf(fp, "\n") < 0);
5360 /* Sylpheed account IDs */
5362 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
5364 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
5367 if (compose->privacy_system != NULL) {
5368 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
5369 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
5370 if (compose->use_encryption) {
5372 if (!compose_warn_encryption(compose)) {
5379 if (mailac && mailac->encrypt_to_self) {
5380 GSList *tmp_list = g_slist_copy(compose->to_list);
5381 tmp_list = g_slist_append(tmp_list, compose->account->address);
5382 encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
5383 g_slist_free(tmp_list);
5385 encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
5387 if (encdata != NULL) {
5388 if (strcmp(encdata, "_DONT_ENCRYPT_")) {
5389 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5390 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
5392 } /* else we finally dont want to encrypt */
5394 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5395 /* and if encdata was null, it means there's been a problem in
5407 /* Save copy folder */
5408 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
5409 gchar *savefolderid;
5411 savefolderid = gtk_editable_get_chars(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
5412 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
5413 g_free(savefolderid);
5415 /* Save copy folder */
5416 if (compose->return_receipt) {
5417 err |= (fprintf(fp, "RRCPT:1\n") < 0);
5419 /* Message-ID of message replying to */
5420 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
5423 folderid = folder_item_get_identifier(compose->replyinfo->folder);
5424 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
5427 /* Message-ID of message forwarding to */
5428 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
5431 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
5432 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
5436 /* end of headers */
5437 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
5439 if (compose->redirect_filename != NULL) {
5440 if (compose_redirect_write_to_file(compose, fp) < 0) {
5449 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
5454 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
5458 g_warning("failed to write queue message\n");
5465 if (fclose(fp) == EOF) {
5466 FILE_OP_ERROR(tmp, "fclose");
5473 if (item && *item) {
5476 queue = account_get_special_folder(compose->account, F_QUEUE);
5479 g_warning("can't find queue folder\n");
5485 folder_item_scan(queue);
5486 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
5487 g_warning("can't queue the message\n");
5494 if (msgpath == NULL) {
5500 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
5501 compose_remove_reedit_target(compose, FALSE);
5504 if ((msgnum != NULL) && (item != NULL)) {
5512 static void compose_add_attachments(Compose *compose, MimeInfo *parent)
5515 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
5517 struct stat statbuf;
5518 gchar *type, *subtype;
5519 GtkTreeModel *model;
5522 model = gtk_tree_view_get_model(tree_view);
5524 if (!gtk_tree_model_get_iter_first(model, &iter))
5527 gtk_tree_model_get(model, &iter,
5531 mimepart = procmime_mimeinfo_new();
5532 mimepart->content = MIMECONTENT_FILE;
5533 mimepart->data.filename = g_strdup(ainfo->file);
5534 mimepart->tmp = FALSE; /* or we destroy our attachment */
5535 mimepart->offset = 0;
5537 stat(ainfo->file, &statbuf);
5538 mimepart->length = statbuf.st_size;
5540 type = g_strdup(ainfo->content_type);
5542 if (!strchr(type, '/')) {
5544 type = g_strdup("application/octet-stream");
5547 subtype = strchr(type, '/') + 1;
5548 *(subtype - 1) = '\0';
5549 mimepart->type = procmime_get_media_type(type);
5550 mimepart->subtype = g_strdup(subtype);
5553 if (mimepart->type == MIMETYPE_MESSAGE &&
5554 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
5555 mimepart->disposition = DISPOSITIONTYPE_INLINE;
5558 g_hash_table_insert(mimepart->typeparameters,
5559 g_strdup("name"), g_strdup(ainfo->name));
5560 g_hash_table_insert(mimepart->dispositionparameters,
5561 g_strdup("filename"), g_strdup(ainfo->name));
5562 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
5566 if (compose->use_signing) {
5567 if (ainfo->encoding == ENC_7BIT)
5568 ainfo->encoding = ENC_QUOTED_PRINTABLE;
5569 else if (ainfo->encoding == ENC_8BIT)
5570 ainfo->encoding = ENC_BASE64;
5573 procmime_encode_content(mimepart, ainfo->encoding);
5575 g_node_append(parent->node, mimepart->node);
5576 } while (gtk_tree_model_iter_next(model, &iter));
5579 #define IS_IN_CUSTOM_HEADER(header) \
5580 (compose->account->add_customhdr && \
5581 custom_header_find(compose->account->customhdr_list, header) != NULL)
5583 static void compose_add_headerfield_from_headerlist(Compose *compose,
5585 const gchar *fieldname,
5586 const gchar *seperator)
5588 gchar *str, *fieldname_w_colon;
5589 gboolean add_field = FALSE;
5591 ComposeHeaderEntry *headerentry;
5592 const gchar *headerentryname;
5593 const gchar *trans_fieldname;
5596 if (IS_IN_CUSTOM_HEADER(fieldname))
5599 debug_print("Adding %s-fields\n", fieldname);
5601 fieldstr = g_string_sized_new(64);
5603 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
5604 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
5606 for (list = compose->header_list; list; list = list->next) {
5607 headerentry = ((ComposeHeaderEntry *)list->data);
5608 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_BIN(headerentry->combo)->child));
5610 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
5611 str = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
5613 if (str[0] != '\0') {
5615 g_string_append(fieldstr, seperator);
5616 g_string_append(fieldstr, str);
5625 buf = g_new0(gchar, fieldstr->len * 4 + 256);
5626 compose_convert_header
5627 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
5628 strlen(fieldname) + 2, TRUE);
5629 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
5633 g_free(fieldname_w_colon);
5634 g_string_free(fieldstr, TRUE);
5639 static gchar *compose_get_header(Compose *compose)
5641 gchar buf[BUFFSIZE];
5642 const gchar *entry_str;
5646 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
5648 gchar *from_name = NULL, *from_address = NULL;
5651 g_return_val_if_fail(compose->account != NULL, NULL);
5652 g_return_val_if_fail(compose->account->address != NULL, NULL);
5654 header = g_string_sized_new(64);
5657 get_rfc822_date(buf, sizeof(buf));
5658 g_string_append_printf(header, "Date: %s\n", buf);
5662 if (compose->account->name && *compose->account->name) {
5664 QUOTE_IF_REQUIRED(buf, compose->account->name);
5665 tmp = g_strdup_printf("%s <%s>",
5666 buf, compose->account->address);
5668 tmp = g_strdup_printf("%s",
5669 compose->account->address);
5671 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
5672 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
5674 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
5675 from_address = g_strdup(compose->account->address);
5677 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5678 /* extract name and address */
5679 if (strstr(spec, " <") && strstr(spec, ">")) {
5680 from_address = g_strdup(strrchr(spec, '<')+1);
5681 *(strrchr(from_address, '>')) = '\0';
5682 from_name = g_strdup(spec);
5683 *(strrchr(from_name, '<')) = '\0';
5686 from_address = g_strdup(spec);
5693 if (from_name && *from_name) {
5694 compose_convert_header
5695 (compose, buf, sizeof(buf), from_name,
5696 strlen("From: "), TRUE);
5697 QUOTE_IF_REQUIRED(name, buf);
5699 g_string_append_printf(header, "From: %s <%s>\n",
5700 name, from_address);
5702 g_string_append_printf(header, "From: %s\n", from_address);
5705 g_free(from_address);
5708 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
5711 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
5714 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
5717 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
5720 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
5722 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
5725 compose_convert_header(compose, buf, sizeof(buf), str,
5726 strlen("Subject: "), FALSE);
5727 g_string_append_printf(header, "Subject: %s\n", buf);
5733 if (compose->account->set_domain && compose->account->domain) {
5734 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
5735 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
5736 g_snprintf(buf, sizeof(buf), "%s",
5737 strchr(compose->account->address, '@') ?
5738 strchr(compose->account->address, '@')+1 :
5739 compose->account->address);
5741 g_snprintf(buf, sizeof(buf), "%s", "");
5743 generate_msgid(buf, sizeof(buf));
5744 g_string_append_printf(header, "Message-ID: <%s>\n", buf);
5745 compose->msgid = g_strdup(buf);
5747 if (compose->remove_references == FALSE) {
5749 if (compose->inreplyto && compose->to_list)
5750 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
5753 if (compose->references)
5754 g_string_append_printf(header, "References: %s\n", compose->references);
5758 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
5761 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
5764 if (compose->account->organization &&
5765 strlen(compose->account->organization) &&
5766 !IS_IN_CUSTOM_HEADER("Organization")) {
5767 compose_convert_header(compose, buf, sizeof(buf),
5768 compose->account->organization,
5769 strlen("Organization: "), FALSE);
5770 g_string_append_printf(header, "Organization: %s\n", buf);
5773 /* Program version and system info */
5774 if (g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
5775 !compose->newsgroup_list) {
5776 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
5778 gtk_major_version, gtk_minor_version, gtk_micro_version,
5781 if (g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
5782 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
5784 gtk_major_version, gtk_minor_version, gtk_micro_version,
5788 /* custom headers */
5789 if (compose->account->add_customhdr) {
5792 for (cur = compose->account->customhdr_list; cur != NULL;
5794 CustomHeader *chdr = (CustomHeader *)cur->data;
5796 if (custom_header_is_allowed(chdr->name)) {
5797 compose_convert_header
5798 (compose, buf, sizeof(buf),
5799 chdr->value ? chdr->value : "",
5800 strlen(chdr->name) + 2, FALSE);
5801 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
5807 switch (compose->priority) {
5808 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
5809 "X-Priority: 1 (Highest)\n");
5811 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
5812 "X-Priority: 2 (High)\n");
5814 case PRIORITY_NORMAL: break;
5815 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
5816 "X-Priority: 4 (Low)\n");
5818 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
5819 "X-Priority: 5 (Lowest)\n");
5821 default: debug_print("compose: priority unknown : %d\n",
5825 /* Request Return Receipt */
5826 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To")) {
5827 if (compose->return_receipt) {
5828 if (compose->account->name
5829 && *compose->account->name) {
5830 compose_convert_header(compose, buf, sizeof(buf),
5831 compose->account->name,
5832 strlen("Disposition-Notification-To: "),
5834 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, compose->account->address);
5836 g_string_append_printf(header, "Disposition-Notification-To: %s\n", compose->account->address);
5840 /* get special headers */
5841 for (list = compose->header_list; list; list = list->next) {
5842 ComposeHeaderEntry *headerentry;
5845 gchar *headername_wcolon;
5846 const gchar *headername_trans;
5849 gboolean standard_header = FALSE;
5851 headerentry = ((ComposeHeaderEntry *)list->data);
5853 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(GTK_BIN(headerentry->combo)->child)));
5854 if (strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
5859 if (!strstr(tmp, ":")) {
5860 headername_wcolon = g_strconcat(tmp, ":", NULL);
5861 headername = g_strdup(tmp);
5863 headername_wcolon = g_strdup(tmp);
5864 headername = g_strdup(strtok(tmp, ":"));
5868 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5869 Xstrdup_a(headervalue, entry_str, return NULL);
5870 subst_char(headervalue, '\r', ' ');
5871 subst_char(headervalue, '\n', ' ');
5872 string = std_headers;
5873 while (*string != NULL) {
5874 headername_trans = prefs_common_translated_header_name(*string);
5875 if (!strcmp(headername_trans, headername_wcolon))
5876 standard_header = TRUE;
5879 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
5880 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
5883 g_free(headername_wcolon);
5887 g_string_free(header, FALSE);
5892 #undef IS_IN_CUSTOM_HEADER
5894 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
5895 gint header_len, gboolean addr_field)
5897 gchar *tmpstr = NULL;
5898 const gchar *out_codeset = NULL;
5900 g_return_if_fail(src != NULL);
5901 g_return_if_fail(dest != NULL);
5903 if (len < 1) return;
5905 tmpstr = g_strdup(src);
5907 subst_char(tmpstr, '\n', ' ');
5908 subst_char(tmpstr, '\r', ' ');
5911 if (!g_utf8_validate(tmpstr, -1, NULL)) {
5912 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
5913 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
5918 codeconv_set_strict(TRUE);
5919 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
5920 conv_get_charset_str(compose->out_encoding));
5921 codeconv_set_strict(FALSE);
5923 if (!dest || *dest == '\0') {
5924 gchar *test_conv_global_out = NULL;
5925 gchar *test_conv_reply = NULL;
5927 /* automatic mode. be automatic. */
5928 codeconv_set_strict(TRUE);
5930 out_codeset = conv_get_outgoing_charset_str();
5932 debug_print("trying to convert to %s\n", out_codeset);
5933 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
5936 if (!test_conv_global_out && compose->orig_charset
5937 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5938 out_codeset = compose->orig_charset;
5939 debug_print("failure; trying to convert to %s\n", out_codeset);
5940 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
5943 if (!test_conv_global_out && !test_conv_reply) {
5945 out_codeset = CS_INTERNAL;
5946 debug_print("finally using %s\n", out_codeset);
5948 g_free(test_conv_global_out);
5949 g_free(test_conv_reply);
5950 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
5952 codeconv_set_strict(FALSE);
5957 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
5961 g_return_if_fail(user_data != NULL);
5963 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
5964 g_strstrip(address);
5965 if (*address != '\0') {
5966 gchar *name = procheader_get_fromname(address);
5967 extract_address(address);
5968 addressbook_add_contact(name, address, NULL, NULL);
5973 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
5975 GtkWidget *menuitem;
5978 g_return_if_fail(menu != NULL);
5979 g_return_if_fail(GTK_IS_MENU_SHELL(menu));
5981 menuitem = gtk_separator_menu_item_new();
5982 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
5983 gtk_widget_show(menuitem);
5985 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
5986 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
5988 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
5989 g_strstrip(address);
5990 if (*address == '\0') {
5991 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
5994 g_signal_connect(G_OBJECT(menuitem), "activate",
5995 G_CALLBACK(compose_add_to_addressbook_cb), entry);
5996 gtk_widget_show(menuitem);
5999 static void compose_create_header_entry(Compose *compose)
6001 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6006 const gchar *header = NULL;
6007 ComposeHeaderEntry *headerentry;
6008 gboolean standard_header = FALSE;
6010 headerentry = g_new0(ComposeHeaderEntry, 1);
6013 combo = gtk_combo_box_entry_new_text();
6015 while(*string != NULL) {
6016 gtk_combo_box_append_text(GTK_COMBO_BOX(combo),
6017 (gchar*)prefs_common_translated_header_name(*string));
6020 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
6021 g_signal_connect(G_OBJECT(GTK_BIN(combo)->child), "grab_focus",
6022 G_CALLBACK(compose_grab_focus_cb), compose);
6023 gtk_widget_show(combo);
6024 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
6025 compose->header_nextrow, compose->header_nextrow+1,
6026 GTK_SHRINK, GTK_FILL, 0, 0);
6027 if (compose->header_last) {
6028 const gchar *last_header_entry = gtk_entry_get_text(
6029 GTK_ENTRY(GTK_BIN(compose->header_last->combo)->child));
6031 while (*string != NULL) {
6032 if (!strcmp(*string, last_header_entry))
6033 standard_header = TRUE;
6036 if (standard_header)
6037 header = gtk_entry_get_text(GTK_ENTRY(GTK_BIN(compose->header_last->combo)->child));
6039 if (!compose->header_last || !standard_header) {
6040 switch(compose->account->protocol) {
6042 header = prefs_common_translated_header_name("Newsgroups:");
6045 header = prefs_common_translated_header_name("To:");
6050 gtk_entry_set_text(GTK_ENTRY(GTK_BIN(combo)->child), header);
6052 g_signal_connect_after(G_OBJECT(GTK_BIN(combo)->child), "grab_focus",
6053 G_CALLBACK(compose_grab_focus_cb), compose);
6056 entry = gtk_entry_new();
6057 gtk_widget_show(entry);
6058 gtk_tooltips_set_tip(compose->tooltips, entry,
6059 _("Use <tab> to autocomplete from addressbook"), NULL);
6060 gtk_table_attach(GTK_TABLE(compose->header_table), entry, 1, 2,
6061 compose->header_nextrow, compose->header_nextrow+1,
6062 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
6064 g_signal_connect(G_OBJECT(entry), "key-press-event",
6065 G_CALLBACK(compose_headerentry_key_press_event_cb),
6067 g_signal_connect(G_OBJECT(entry), "changed",
6068 G_CALLBACK(compose_headerentry_changed_cb),
6070 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
6071 G_CALLBACK(compose_grab_focus_cb), compose);
6074 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6075 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6076 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6077 g_signal_connect(G_OBJECT(entry), "drag_data_received",
6078 G_CALLBACK(compose_header_drag_received_cb),
6080 g_signal_connect(G_OBJECT(entry), "drag-drop",
6081 G_CALLBACK(compose_drag_drop),
6083 g_signal_connect(G_OBJECT(entry), "populate-popup",
6084 G_CALLBACK(compose_entry_popup_extend),
6087 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
6089 headerentry->compose = compose;
6090 headerentry->combo = combo;
6091 headerentry->entry = entry;
6092 headerentry->headernum = compose->header_nextrow;
6094 compose->header_nextrow++;
6095 compose->header_last = headerentry;
6096 compose->header_list =
6097 g_slist_append(compose->header_list,
6101 static void compose_add_header_entry(Compose *compose, const gchar *header, gchar *text)
6103 ComposeHeaderEntry *last_header;
6105 last_header = compose->header_last;
6107 gtk_entry_set_text(GTK_ENTRY(GTK_BIN(last_header->combo)->child), header);
6108 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
6111 static void compose_remove_header_entries(Compose *compose)
6114 for (list = compose->header_list; list; list = list->next) {
6115 ComposeHeaderEntry *headerentry =
6116 (ComposeHeaderEntry *)list->data;
6117 gtk_widget_destroy(headerentry->combo);
6118 gtk_widget_destroy(headerentry->entry);
6119 g_free(headerentry);
6121 compose->header_last = NULL;
6122 g_slist_free(compose->header_list);
6123 compose->header_list = NULL;
6124 compose->header_nextrow = 1;
6125 compose_create_header_entry(compose);
6128 static GtkWidget *compose_create_header(Compose *compose)
6130 GtkWidget *from_optmenu_hbox;
6131 GtkWidget *header_scrolledwin;
6132 GtkWidget *header_table;
6136 /* header labels and entries */
6137 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
6138 gtk_widget_show(header_scrolledwin);
6139 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
6141 header_table = gtk_table_new(2, 2, FALSE);
6142 gtk_widget_show(header_table);
6143 gtk_container_set_border_width(GTK_CONTAINER(header_table), BORDER_WIDTH);
6144 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
6145 gtk_viewport_set_shadow_type(GTK_VIEWPORT(GTK_BIN(header_scrolledwin)->child), GTK_SHADOW_NONE);
6148 /* option menu for selecting accounts */
6149 from_optmenu_hbox = compose_account_option_menu_create(compose);
6150 gtk_table_attach(GTK_TABLE(header_table), from_optmenu_hbox,
6151 0, 2, count, count + 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
6154 compose->header_table = header_table;
6155 compose->header_list = NULL;
6156 compose->header_nextrow = count;
6158 compose_create_header_entry(compose);
6160 compose->table = NULL;
6162 return header_scrolledwin ;
6165 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
6167 Compose *compose = (Compose *)data;
6168 GdkEventButton event;
6171 event.time = gtk_get_current_event_time();
6173 return attach_button_pressed(compose->attach_clist, &event, compose);
6176 static GtkWidget *compose_create_attach(Compose *compose)
6178 GtkWidget *attach_scrwin;
6179 GtkWidget *attach_clist;
6181 GtkListStore *store;
6182 GtkCellRenderer *renderer;
6183 GtkTreeViewColumn *column;
6184 GtkTreeSelection *selection;
6186 /* attachment list */
6187 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
6188 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
6189 GTK_POLICY_AUTOMATIC,
6190 GTK_POLICY_AUTOMATIC);
6191 gtk_widget_set_size_request(attach_scrwin, -1, 80);
6193 store = gtk_list_store_new(N_ATTACH_COLS,
6198 G_TYPE_AUTO_POINTER,
6200 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
6201 (GTK_TREE_MODEL(store)));
6202 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
6203 g_object_unref(store);
6205 renderer = gtk_cell_renderer_text_new();
6206 column = gtk_tree_view_column_new_with_attributes
6207 (_("Mime type"), renderer, "text",
6208 COL_MIMETYPE, NULL);
6209 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6211 renderer = gtk_cell_renderer_text_new();
6212 column = gtk_tree_view_column_new_with_attributes
6213 (_("Size"), renderer, "text",
6215 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6217 renderer = gtk_cell_renderer_text_new();
6218 column = gtk_tree_view_column_new_with_attributes
6219 (_("Name"), renderer, "text",
6221 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6223 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
6224 prefs_common.use_stripes_everywhere);
6225 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
6226 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
6228 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
6229 G_CALLBACK(attach_selected), compose);
6230 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
6231 G_CALLBACK(attach_button_pressed), compose);
6233 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
6234 G_CALLBACK(popup_attach_button_pressed), compose);
6236 gtk_widget_tap_and_hold_setup(GTK_WIDGET(attach_clist), NULL, NULL,
6237 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
6238 g_signal_connect(G_OBJECT(attach_clist), "tap-and-hold",
6239 G_CALLBACK(popup_attach_button_pressed), compose);
6241 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
6242 G_CALLBACK(attach_key_pressed), compose);
6245 gtk_drag_dest_set(attach_clist,
6246 GTK_DEST_DEFAULT_ALL, compose_mime_types,
6247 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6248 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6249 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
6250 G_CALLBACK(compose_attach_drag_received_cb),
6252 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
6253 G_CALLBACK(compose_drag_drop),
6256 compose->attach_scrwin = attach_scrwin;
6257 compose->attach_clist = attach_clist;
6259 return attach_scrwin;
6262 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
6263 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
6265 static GtkWidget *compose_create_others(Compose *compose)
6268 GtkWidget *savemsg_checkbtn;
6269 GtkWidget *savemsg_entry;
6270 GtkWidget *savemsg_select;
6273 gchar *folderidentifier;
6275 /* Table for settings */
6276 table = gtk_table_new(3, 1, FALSE);
6277 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
6278 gtk_widget_show(table);
6279 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
6282 /* Save Message to folder */
6283 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
6284 gtk_widget_show(savemsg_checkbtn);
6285 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6286 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6287 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
6289 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
6290 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
6292 savemsg_entry = gtk_entry_new();
6293 gtk_widget_show(savemsg_entry);
6294 gtk_table_attach_defaults(GTK_TABLE(table), savemsg_entry, 1, 2, rowcount, rowcount + 1);
6295 gtk_editable_set_editable(GTK_EDITABLE(savemsg_entry), prefs_common.savemsg);
6296 g_signal_connect_after(G_OBJECT(savemsg_entry), "grab_focus",
6297 G_CALLBACK(compose_grab_focus_cb), compose);
6298 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6299 folderidentifier = folder_item_get_identifier(account_get_special_folder
6300 (compose->account, F_OUTBOX));
6301 gtk_entry_set_text(GTK_ENTRY(savemsg_entry), folderidentifier);
6302 g_free(folderidentifier);
6305 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
6306 gtk_widget_show(savemsg_select);
6307 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6308 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
6309 G_CALLBACK(compose_savemsg_select_cb),
6314 compose->savemsg_checkbtn = savemsg_checkbtn;
6315 compose->savemsg_entry = savemsg_entry;
6320 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
6322 gtk_editable_set_editable(GTK_EDITABLE(compose->savemsg_entry),
6323 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
6326 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
6331 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL);
6334 path = folder_item_get_identifier(dest);
6336 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), path);
6340 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
6341 GdkAtom clip, GtkTextIter *insert_place);
6344 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
6348 GtkTextBuffer *buffer;
6350 if (event->button == 3) {
6352 GtkTextIter sel_start, sel_end;
6353 gboolean stuff_selected;
6355 /* move the cursor to allow GtkAspell to check the word
6356 * under the mouse */
6357 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6358 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6360 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6363 stuff_selected = gtk_text_buffer_get_selection_bounds(
6364 GTK_TEXT_VIEW(text)->buffer,
6365 &sel_start, &sel_end);
6367 gtk_text_buffer_place_cursor (GTK_TEXT_VIEW(text)->buffer, &iter);
6368 /* reselect stuff */
6370 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
6371 gtk_text_buffer_select_range(GTK_TEXT_VIEW(text)->buffer,
6372 &sel_start, &sel_end);
6374 return FALSE; /* pass the event so that the right-click goes through */
6377 if (event->button == 2) {
6382 /* get the middle-click position to paste at the correct place */
6383 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6384 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6386 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6389 entry_paste_clipboard(compose, text,
6390 prefs_common.linewrap_pastes,
6391 GDK_SELECTION_PRIMARY, &iter);
6399 static void compose_spell_menu_changed(void *data)
6401 Compose *compose = (Compose *)data;
6403 GtkWidget *menuitem;
6404 GtkWidget *parent_item;
6405 GtkMenu *menu = GTK_MENU(gtk_menu_new());
6406 GtkItemFactory *ifactory = gtk_item_factory_from_widget(compose->menubar);
6409 if (compose->gtkaspell == NULL)
6412 parent_item = gtk_item_factory_get_item(ifactory,
6413 "/Spelling/Options");
6415 /* setting the submenu removes /Spelling/Options from the factory
6416 * so we need to save it */
6418 if (parent_item == NULL) {
6419 parent_item = compose->aspell_options_menu;
6420 gtk_menu_item_remove_submenu(GTK_MENU_ITEM(parent_item));
6422 compose->aspell_options_menu = parent_item;
6424 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
6426 spell_menu = g_slist_reverse(spell_menu);
6427 for (items = spell_menu;
6428 items; items = items->next) {
6429 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
6430 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
6431 gtk_widget_show(GTK_WIDGET(menuitem));
6433 g_slist_free(spell_menu);
6435 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
6440 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
6442 Compose *compose = (Compose *)data;
6443 GdkEventButton event;
6446 event.time = gtk_get_current_event_time();
6448 return text_clicked(compose->text, &event, compose);
6451 static gboolean compose_force_window_origin = TRUE;
6452 static Compose *compose_create(PrefsAccount *account,
6461 GtkWidget *handlebox;
6463 GtkWidget *notebook;
6465 GtkWidget *attach_hbox;
6466 GtkWidget *attach_lab1;
6467 GtkWidget *attach_lab2;
6472 GtkWidget *subject_hbox;
6473 GtkWidget *subject_frame;
6474 GtkWidget *subject_entry;
6478 GtkWidget *edit_vbox;
6479 GtkWidget *ruler_hbox;
6481 GtkWidget *scrolledwin;
6483 GtkTextBuffer *buffer;
6484 GtkClipboard *clipboard;
6486 UndoMain *undostruct;
6488 gchar *titles[N_ATTACH_COLS];
6489 guint n_menu_entries;
6490 GtkWidget *popupmenu;
6491 GtkItemFactory *popupfactory;
6492 GtkItemFactory *ifactory;
6493 GtkWidget *tmpl_menu;
6495 GtkWidget *menuitem;
6498 GtkAspell * gtkaspell = NULL;
6501 static GdkGeometry geometry;
6503 g_return_val_if_fail(account != NULL, NULL);
6505 debug_print("Creating compose window...\n");
6506 compose = g_new0(Compose, 1);
6508 titles[COL_MIMETYPE] = _("MIME type");
6509 titles[COL_SIZE] = _("Size");
6510 titles[COL_NAME] = _("Name");
6512 compose->batch = batch;
6513 compose->account = account;
6514 compose->folder = folder;
6516 compose->mutex = g_mutex_new();
6517 compose->set_cursor_pos = -1;
6519 compose->tooltips = gtk_tooltips_new();
6521 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
6523 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
6524 gtk_widget_set_size_request(window, -1, prefs_common.compose_height);
6526 if (!geometry.max_width) {
6527 geometry.max_width = gdk_screen_width();
6528 geometry.max_height = gdk_screen_height();
6531 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
6532 &geometry, GDK_HINT_MAX_SIZE);
6533 if (!geometry.min_width) {
6534 geometry.min_width = 600;
6535 geometry.min_height = 480;
6537 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
6538 &geometry, GDK_HINT_MIN_SIZE);
6541 if (compose_force_window_origin)
6542 gtk_widget_set_uposition(window, prefs_common.compose_x,
6543 prefs_common.compose_y);
6545 g_signal_connect(G_OBJECT(window), "delete_event",
6546 G_CALLBACK(compose_delete_cb), compose);
6547 MANAGE_WINDOW_SIGNALS_CONNECT(window);
6548 gtk_widget_realize(window);
6550 gtkut_widget_set_composer_icon(window);
6552 vbox = gtk_vbox_new(FALSE, 0);
6553 gtk_container_add(GTK_CONTAINER(window), vbox);
6555 n_menu_entries = sizeof(compose_entries) / sizeof(compose_entries[0]);
6556 menubar = menubar_create(window, compose_entries,
6557 n_menu_entries, "<Compose>", compose);
6558 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
6560 if (prefs_common.toolbar_detachable) {
6561 handlebox = gtk_handle_box_new();
6563 handlebox = gtk_hbox_new(FALSE, 0);
6565 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
6567 gtk_widget_realize(handlebox);
6569 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, window,
6572 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
6576 vbox2 = gtk_vbox_new(FALSE, 2);
6577 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
6578 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
6581 notebook = gtk_notebook_new();
6582 gtk_widget_set_size_request(notebook, -1, 130);
6583 gtk_widget_show(notebook);
6585 /* header labels and entries */
6586 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6587 compose_create_header(compose),
6588 gtk_label_new_with_mnemonic(_("Hea_der")));
6589 /* attachment list */
6590 attach_hbox = gtk_hbox_new(FALSE, 0);
6591 gtk_widget_show(attach_hbox);
6593 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
6594 gtk_widget_show(attach_lab1);
6595 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
6597 attach_lab2 = gtk_label_new("");
6598 gtk_widget_show(attach_lab2);
6599 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
6601 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6602 compose_create_attach(compose),
6605 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6606 compose_create_others(compose),
6607 gtk_label_new_with_mnemonic(_("Othe_rs")));
6610 subject_hbox = gtk_hbox_new(FALSE, 0);
6611 gtk_widget_show(subject_hbox);
6613 subject_frame = gtk_frame_new(NULL);
6614 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
6615 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
6616 gtk_widget_show(subject_frame);
6618 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
6619 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
6620 gtk_widget_show(subject);
6622 label = gtk_label_new(_("Subject:"));
6623 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
6624 gtk_widget_show(label);
6626 subject_entry = gtk_entry_new();
6627 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
6628 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
6629 G_CALLBACK(compose_grab_focus_cb), compose);
6630 gtk_widget_show(subject_entry);
6631 compose->subject_entry = subject_entry;
6632 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
6634 edit_vbox = gtk_vbox_new(FALSE, 0);
6636 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
6639 ruler_hbox = gtk_hbox_new(FALSE, 0);
6640 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
6642 ruler = gtk_shruler_new();
6643 gtk_ruler_set_range(GTK_RULER(ruler), 0.0, 100.0, 1.0, 100.0);
6644 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
6648 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
6649 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
6650 GTK_POLICY_AUTOMATIC,
6651 GTK_POLICY_AUTOMATIC);
6652 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
6654 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
6655 gtk_widget_set_size_request(scrolledwin, prefs_common.compose_width, -1);
6657 text = gtk_text_view_new();
6658 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
6659 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
6660 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
6661 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
6662 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
6664 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
6666 g_signal_connect_after(G_OBJECT(text), "size_allocate",
6667 G_CALLBACK(compose_edit_size_alloc),
6669 g_signal_connect(G_OBJECT(buffer), "changed",
6670 G_CALLBACK(compose_changed_cb), compose);
6671 g_signal_connect(G_OBJECT(text), "grab_focus",
6672 G_CALLBACK(compose_grab_focus_cb), compose);
6673 g_signal_connect(G_OBJECT(buffer), "insert_text",
6674 G_CALLBACK(text_inserted), compose);
6675 g_signal_connect(G_OBJECT(text), "button_press_event",
6676 G_CALLBACK(text_clicked), compose);
6678 g_signal_connect(G_OBJECT(text), "popup-menu",
6679 G_CALLBACK(compose_popup_menu), compose);
6681 gtk_widget_tap_and_hold_setup(GTK_WIDGET(text), NULL, NULL,
6682 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
6683 g_signal_connect(G_OBJECT(text), "tap-and-hold",
6684 G_CALLBACK(compose_popup_menu), compose);
6686 g_signal_connect(G_OBJECT(subject_entry), "changed",
6687 G_CALLBACK(compose_changed_cb), compose);
6690 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6691 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6692 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6693 g_signal_connect(G_OBJECT(text), "drag_data_received",
6694 G_CALLBACK(compose_insert_drag_received_cb),
6696 g_signal_connect(G_OBJECT(text), "drag-drop",
6697 G_CALLBACK(compose_drag_drop),
6699 gtk_widget_show_all(vbox);
6701 /* pane between attach clist and text */
6702 paned = gtk_vpaned_new();
6703 gtk_paned_set_gutter_size(GTK_PANED(paned), 12);
6704 gtk_container_add(GTK_CONTAINER(vbox2), paned);
6706 if( maemo_mainwindow_is_fullscreen(mainwindow_get_mainwindow()->window) )
6707 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 300 : 280);
6709 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 250 : 230);
6711 gtk_paned_add1(GTK_PANED(paned), notebook);
6712 gtk_paned_add2(GTK_PANED(paned), edit_vbox);
6713 gtk_widget_show_all(paned);
6716 if (prefs_common.textfont) {
6717 PangoFontDescription *font_desc;
6719 font_desc = pango_font_description_from_string
6720 (prefs_common.textfont);
6722 gtk_widget_modify_font(text, font_desc);
6723 pango_font_description_free(font_desc);
6727 n_entries = sizeof(compose_popup_entries) /
6728 sizeof(compose_popup_entries[0]);
6729 popupmenu = menu_create_items(compose_popup_entries, n_entries,
6730 "<Compose>", &popupfactory,
6733 ifactory = gtk_item_factory_from_widget(menubar);
6734 menu_set_sensitive(ifactory, "/Edit/Undo", FALSE);
6735 menu_set_sensitive(ifactory, "/Edit/Redo", FALSE);
6736 menu_set_sensitive(ifactory, "/Options/Remove references", FALSE);
6738 tmpl_menu = gtk_item_factory_get_item(ifactory, "/Tools/Template");
6740 undostruct = undo_init(text);
6741 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
6744 address_completion_start(window);
6746 compose->window = window;
6747 compose->vbox = vbox;
6748 compose->menubar = menubar;
6749 compose->handlebox = handlebox;
6751 compose->vbox2 = vbox2;
6753 compose->paned = paned;
6755 compose->attach_label = attach_lab2;
6757 compose->notebook = notebook;
6758 compose->edit_vbox = edit_vbox;
6759 compose->ruler_hbox = ruler_hbox;
6760 compose->ruler = ruler;
6761 compose->scrolledwin = scrolledwin;
6762 compose->text = text;
6764 compose->focused_editable = NULL;
6766 compose->popupmenu = popupmenu;
6767 compose->popupfactory = popupfactory;
6769 compose->tmpl_menu = tmpl_menu;
6771 compose->mode = mode;
6772 compose->rmode = mode;
6774 compose->targetinfo = NULL;
6775 compose->replyinfo = NULL;
6776 compose->fwdinfo = NULL;
6778 compose->replyto = NULL;
6780 compose->bcc = NULL;
6781 compose->followup_to = NULL;
6783 compose->ml_post = NULL;
6785 compose->inreplyto = NULL;
6786 compose->references = NULL;
6787 compose->msgid = NULL;
6788 compose->boundary = NULL;
6790 compose->autowrap = prefs_common.autowrap;
6792 compose->use_signing = FALSE;
6793 compose->use_encryption = FALSE;
6794 compose->privacy_system = NULL;
6796 compose->modified = FALSE;
6798 compose->return_receipt = FALSE;
6800 compose->to_list = NULL;
6801 compose->newsgroup_list = NULL;
6803 compose->undostruct = undostruct;
6805 compose->sig_str = NULL;
6807 compose->exteditor_file = NULL;
6808 compose->exteditor_pid = -1;
6809 compose->exteditor_tag = -1;
6810 compose->draft_timeout_tag = -2; /* inhibit auto-drafting while loading */
6813 menu_set_sensitive(ifactory, "/Spelling", FALSE);
6814 if (mode != COMPOSE_REDIRECT) {
6815 if (prefs_common.enable_aspell && prefs_common.dictionary &&
6816 strcmp(prefs_common.dictionary, "")) {
6817 gtkaspell = gtkaspell_new(prefs_common.aspell_path,
6818 prefs_common.dictionary,
6819 prefs_common.alt_dictionary,
6820 conv_get_locale_charset_str(),
6821 prefs_common.misspelled_col,
6822 prefs_common.check_while_typing,
6823 prefs_common.recheck_when_changing_dict,
6824 prefs_common.use_alternate,
6825 prefs_common.use_both_dicts,
6826 GTK_TEXT_VIEW(text),
6827 GTK_WINDOW(compose->window),
6828 compose_spell_menu_changed,
6831 alertpanel_error(_("Spell checker could not "
6833 gtkaspell_checkers_strerror());
6834 gtkaspell_checkers_reset_error();
6836 if (!gtkaspell_set_sug_mode(gtkaspell,
6837 prefs_common.aspell_sugmode)) {
6838 debug_print("Aspell: could not set "
6839 "suggestion mode %s\n",
6840 gtkaspell_checkers_strerror());
6841 gtkaspell_checkers_reset_error();
6844 menu_set_sensitive(ifactory, "/Spelling", TRUE);
6848 compose->gtkaspell = gtkaspell;
6849 compose_spell_menu_changed(compose);
6852 compose_select_account(compose, account, TRUE);
6854 menu_set_active(ifactory, "/Edit/Auto wrapping", prefs_common.autowrap);
6855 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
6856 compose_entry_append(compose, account->auto_cc, COMPOSE_CC);
6858 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
6859 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC);
6861 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
6862 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO);
6864 menu_set_sensitive(ifactory, "/Options/Reply mode", compose->mode == COMPOSE_REPLY);
6866 if (account->protocol != A_NNTP)
6867 gtk_entry_set_text(GTK_ENTRY(GTK_BIN(compose->header_last->combo)->child),
6868 prefs_common_translated_header_name("To:"));
6870 gtk_entry_set_text(GTK_ENTRY(GTK_BIN(compose->header_last->combo)->child),
6871 prefs_common_translated_header_name("Newsgroups:"));
6873 addressbook_set_target_compose(compose);
6875 if (mode != COMPOSE_REDIRECT)
6876 compose_set_template_menu(compose);
6878 menuitem = gtk_item_factory_get_item(ifactory, "/Tools/Template");
6879 menu_set_sensitive(ifactory, "/Tools/Template", FALSE);
6882 compose_list = g_list_append(compose_list, compose);
6884 if (!prefs_common.show_ruler)
6885 gtk_widget_hide(ruler_hbox);
6887 menuitem = gtk_item_factory_get_item(ifactory, "/Tools/Show ruler");
6888 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
6889 prefs_common.show_ruler);
6892 compose->priority = PRIORITY_NORMAL;
6893 compose_update_priority_menu_item(compose);
6895 compose_set_out_encoding(compose);
6898 compose_update_actions_menu(compose);
6900 /* Privacy Systems menu */
6901 compose_update_privacy_systems_menu(compose);
6903 activate_privacy_system(compose, account, TRUE);
6904 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
6906 gtk_widget_realize(window);
6908 gtk_widget_show(window);
6910 maemo_window_full_screen_if_needed(GTK_WINDOW(window));
6911 maemo_connect_key_press_to_mainwindow(GTK_WINDOW(window));
6918 static GtkWidget *compose_account_option_menu_create(Compose *compose)
6923 GtkWidget *optmenubox;
6926 GtkWidget *from_name = NULL;
6928 gint num = 0, def_menu = 0;
6930 accounts = account_get_list();
6931 g_return_val_if_fail(accounts != NULL, NULL);
6933 optmenubox = gtk_event_box_new();
6934 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
6935 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
6937 hbox = gtk_hbox_new(FALSE, 6);
6938 from_name = gtk_entry_new();
6940 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
6941 G_CALLBACK(compose_grab_focus_cb), compose);
6943 for (; accounts != NULL; accounts = accounts->next, num++) {
6944 PrefsAccount *ac = (PrefsAccount *)accounts->data;
6945 gchar *name, *from = NULL;
6947 if (ac == compose->account) def_menu = num;
6949 name = g_markup_printf_escaped(_("From: <i>%s</i>"),
6952 if (ac == compose->account) {
6953 if (ac->name && *ac->name) {
6955 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
6956 from = g_strdup_printf("%s <%s>",
6958 gtk_entry_set_text(GTK_ENTRY(from_name), from);
6960 from = g_strdup_printf("%s",
6962 gtk_entry_set_text(GTK_ENTRY(from_name), from);
6965 COMBOBOX_ADD(menu, name, ac->account_id);
6970 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
6972 g_signal_connect(G_OBJECT(optmenu), "changed",
6973 G_CALLBACK(account_activated),
6975 g_signal_connect(G_OBJECT(from_name), "populate-popup",
6976 G_CALLBACK(compose_entry_popup_extend),
6979 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
6980 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
6982 gtk_tooltips_set_tip(compose->tooltips, optmenubox,
6983 _("Account to use for this email"), NULL);
6984 gtk_tooltips_set_tip(compose->tooltips, from_name,
6985 _("Sender address to be used"), NULL);
6987 compose->from_name = from_name;
6992 static void compose_set_priority_cb(gpointer data,
6996 Compose *compose = (Compose *) data;
6997 compose->priority = action;
7000 static void compose_reply_change_mode(gpointer data,
7004 Compose *compose = (Compose *) data;
7005 gboolean was_modified = compose->modified;
7007 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
7009 g_return_if_fail(compose->replyinfo != NULL);
7011 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
7013 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
7015 if (action == COMPOSE_REPLY_TO_ALL)
7017 if (action == COMPOSE_REPLY_TO_SENDER)
7019 if (action == COMPOSE_REPLY_TO_LIST)
7022 compose_remove_header_entries(compose);
7023 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
7024 if (compose->account->set_autocc && compose->account->auto_cc)
7025 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC);
7027 if (compose->account->set_autobcc && compose->account->auto_bcc)
7028 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC);
7030 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
7031 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO);
7032 compose_show_first_last_header(compose, TRUE);
7033 compose->modified = was_modified;
7034 compose_set_title(compose);
7037 static void compose_update_priority_menu_item(Compose * compose)
7039 GtkItemFactory *ifactory;
7040 GtkWidget *menuitem = NULL;
7042 ifactory = gtk_item_factory_from_widget(compose->menubar);
7044 switch (compose->priority) {
7045 case PRIORITY_HIGHEST:
7046 menuitem = gtk_item_factory_get_item
7047 (ifactory, "/Options/Priority/Highest");
7050 menuitem = gtk_item_factory_get_item
7051 (ifactory, "/Options/Priority/High");
7053 case PRIORITY_NORMAL:
7054 menuitem = gtk_item_factory_get_item
7055 (ifactory, "/Options/Priority/Normal");
7058 menuitem = gtk_item_factory_get_item
7059 (ifactory, "/Options/Priority/Low");
7061 case PRIORITY_LOWEST:
7062 menuitem = gtk_item_factory_get_item
7063 (ifactory, "/Options/Priority/Lowest");
7066 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
7069 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
7071 Compose *compose = (Compose *) data;
7073 GtkItemFactory *ifactory;
7074 gboolean can_sign = FALSE, can_encrypt = FALSE;
7076 g_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
7078 if (!GTK_CHECK_MENU_ITEM(widget)->active)
7081 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
7082 g_free(compose->privacy_system);
7083 compose->privacy_system = NULL;
7084 if (systemid != NULL) {
7085 compose->privacy_system = g_strdup(systemid);
7087 can_sign = privacy_system_can_sign(systemid);
7088 can_encrypt = privacy_system_can_encrypt(systemid);
7091 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
7093 ifactory = gtk_item_factory_from_widget(compose->menubar);
7094 menu_set_sensitive(ifactory, "/Options/Sign", can_sign);
7095 menu_set_sensitive(ifactory, "/Options/Encrypt", can_encrypt);
7098 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
7100 static gchar *branch_path = "/Options/Privacy System";
7101 GtkItemFactory *ifactory;
7102 GtkWidget *menuitem = NULL;
7104 gboolean can_sign = FALSE, can_encrypt = FALSE;
7105 gboolean found = FALSE;
7107 ifactory = gtk_item_factory_from_widget(compose->menubar);
7109 if (compose->privacy_system != NULL) {
7112 menuitem = gtk_item_factory_get_widget(ifactory, branch_path);
7113 g_return_if_fail(menuitem != NULL);
7115 amenu = GTK_MENU_SHELL(menuitem)->children;
7117 while (amenu != NULL) {
7118 GList *alist = amenu->next;
7120 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
7121 if (systemid != NULL) {
7122 if (strcmp(systemid, compose->privacy_system) == 0) {
7123 menuitem = GTK_WIDGET(amenu->data);
7125 can_sign = privacy_system_can_sign(systemid);
7126 can_encrypt = privacy_system_can_encrypt(systemid);
7130 } else if (strlen(compose->privacy_system) == 0) {
7131 menuitem = GTK_WIDGET(amenu->data);
7134 can_encrypt = FALSE;
7141 if (menuitem != NULL)
7142 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
7144 if (warn && !found && strlen(compose->privacy_system)) {
7145 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
7146 "will not be able to sign or encrypt this message."),
7147 compose->privacy_system);
7151 menu_set_sensitive(ifactory, "/Options/Sign", can_sign);
7152 menu_set_sensitive(ifactory, "/Options/Encrypt", can_encrypt);
7155 static void compose_set_out_encoding(Compose *compose)
7157 GtkItemFactoryEntry *entry;
7158 GtkItemFactory *ifactory;
7159 CharSet out_encoding;
7160 gchar *path, *p, *q;
7163 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
7164 ifactory = gtk_item_factory_from_widget(compose->menubar);
7166 for (entry = compose_entries; entry->callback != compose_address_cb;
7168 if (entry->callback == compose_set_encoding_cb &&
7169 (CharSet)entry->callback_action == out_encoding) {
7170 p = q = path = g_strdup(entry->path);
7182 item = gtk_item_factory_get_item(ifactory, path);
7183 gtk_widget_activate(item);
7190 static void compose_set_template_menu(Compose *compose)
7192 GSList *tmpl_list, *cur;
7196 tmpl_list = template_get_config();
7198 menu = gtk_menu_new();
7200 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
7201 Template *tmpl = (Template *)cur->data;
7203 item = gtk_menu_item_new_with_label(tmpl->name);
7204 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
7205 g_signal_connect(G_OBJECT(item), "activate",
7206 G_CALLBACK(compose_template_activate_cb),
7208 g_object_set_data(G_OBJECT(item), "template", tmpl);
7209 gtk_widget_show(item);
7212 gtk_widget_show(menu);
7213 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
7216 void compose_update_actions_menu(Compose *compose)
7218 GtkItemFactory *ifactory;
7220 ifactory = gtk_item_factory_from_widget(compose->menubar);
7221 action_update_compose_menu(ifactory, "/Tools/Actions", compose);
7224 static void compose_update_privacy_systems_menu(Compose *compose)
7226 static gchar *branch_path = "/Options/Privacy System";
7227 GtkItemFactory *ifactory;
7228 GtkWidget *menuitem;
7229 GSList *systems, *cur;
7232 GtkWidget *system_none;
7235 ifactory = gtk_item_factory_from_widget(compose->menubar);
7237 /* remove old entries */
7238 menuitem = gtk_item_factory_get_widget(ifactory, branch_path);
7239 g_return_if_fail(menuitem != NULL);
7241 amenu = GTK_MENU_SHELL(menuitem)->children->next;
7242 while (amenu != NULL) {
7243 GList *alist = amenu->next;
7244 gtk_widget_destroy(GTK_WIDGET(amenu->data));
7248 system_none = gtk_item_factory_get_widget(ifactory,
7249 "/Options/Privacy System/None");
7251 g_signal_connect(G_OBJECT(system_none), "activate",
7252 G_CALLBACK(compose_set_privacy_system_cb), compose);
7254 systems = privacy_get_system_ids();
7255 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
7256 gchar *systemid = cur->data;
7258 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
7259 widget = gtk_radio_menu_item_new_with_label(group,
7260 privacy_system_get_name(systemid));
7261 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
7262 g_strdup(systemid), g_free);
7263 g_signal_connect(G_OBJECT(widget), "activate",
7264 G_CALLBACK(compose_set_privacy_system_cb), compose);
7266 gtk_menu_append(GTK_MENU(system_none->parent), widget);
7267 gtk_widget_show(widget);
7270 g_slist_free(systems);
7273 void compose_reflect_prefs_all(void)
7278 for (cur = compose_list; cur != NULL; cur = cur->next) {
7279 compose = (Compose *)cur->data;
7280 compose_set_template_menu(compose);
7284 void compose_reflect_prefs_pixmap_theme(void)
7289 for (cur = compose_list; cur != NULL; cur = cur->next) {
7290 compose = (Compose *)cur->data;
7291 toolbar_update(TOOLBAR_COMPOSE, compose);
7295 static const gchar *compose_quote_char_from_context(Compose *compose)
7297 const gchar *qmark = NULL;
7299 g_return_val_if_fail(compose != NULL, NULL);
7301 switch (compose->mode) {
7302 /* use forward-specific quote char */
7303 case COMPOSE_FORWARD:
7304 case COMPOSE_FORWARD_AS_ATTACH:
7305 case COMPOSE_FORWARD_INLINE:
7306 if (compose->folder && compose->folder->prefs &&
7307 compose->folder->prefs->forward_with_format)
7308 qmark = compose->folder->prefs->forward_quotemark;
7309 else if (compose->account->forward_with_format)
7310 qmark = compose->account->forward_quotemark;
7312 qmark = prefs_common.fw_quotemark;
7315 /* use reply-specific quote char in all other modes */
7317 if (compose->folder && compose->folder->prefs &&
7318 compose->folder->prefs->reply_with_format)
7319 qmark = compose->folder->prefs->reply_quotemark;
7320 else if (compose->account->reply_with_format)
7321 qmark = compose->account->reply_quotemark;
7323 qmark = prefs_common.quotemark;
7327 if (qmark == NULL || *qmark == '\0')
7333 static void compose_template_apply(Compose *compose, Template *tmpl,
7337 GtkTextBuffer *buffer;
7341 gchar *parsed_str = NULL;
7342 gint cursor_pos = 0;
7343 const gchar *err_msg = _("Template body format error at line %d.");
7346 /* process the body */
7348 text = GTK_TEXT_VIEW(compose->text);
7349 buffer = gtk_text_view_get_buffer(text);
7352 qmark = compose_quote_char_from_context(compose);
7354 if (compose->replyinfo != NULL) {
7357 gtk_text_buffer_set_text(buffer, "", -1);
7358 mark = gtk_text_buffer_get_insert(buffer);
7359 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7361 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
7362 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
7364 } else if (compose->fwdinfo != NULL) {
7367 gtk_text_buffer_set_text(buffer, "", -1);
7368 mark = gtk_text_buffer_get_insert(buffer);
7369 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7371 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
7372 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
7375 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
7377 GtkTextIter start, end;
7380 gtk_text_buffer_get_start_iter(buffer, &start);
7381 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
7382 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
7384 /* clear the buffer now */
7386 gtk_text_buffer_set_text(buffer, "", -1);
7388 parsed_str = compose_quote_fmt(compose, dummyinfo,
7389 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
7390 procmsg_msginfo_free( dummyinfo );
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);
7401 if (replace && parsed_str && compose->account->auto_sig)
7402 compose_insert_sig(compose, FALSE);
7404 if (replace && parsed_str) {
7405 gtk_text_buffer_get_start_iter(buffer, &iter);
7406 gtk_text_buffer_place_cursor(buffer, &iter);
7410 cursor_pos = quote_fmt_get_cursor_pos();
7411 compose->set_cursor_pos = cursor_pos;
7412 if (cursor_pos == -1)
7414 gtk_text_buffer_get_start_iter(buffer, &iter);
7415 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
7416 gtk_text_buffer_place_cursor(buffer, &iter);
7419 /* process the other fields */
7421 compose_template_apply_fields(compose, tmpl);
7422 quote_fmt_reset_vartable();
7423 compose_changed_cb(NULL, compose);
7426 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
7428 MsgInfo* dummyinfo = NULL;
7429 MsgInfo *msginfo = NULL;
7432 if (compose->replyinfo != NULL)
7433 msginfo = compose->replyinfo;
7434 else if (compose->fwdinfo != NULL)
7435 msginfo = compose->fwdinfo;
7437 dummyinfo = compose_msginfo_new_from_compose(compose);
7438 msginfo = dummyinfo;
7441 if (tmpl->to && *tmpl->to != '\0') {
7443 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7444 compose->gtkaspell);
7446 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7448 quote_fmt_scan_string(tmpl->to);
7451 buf = quote_fmt_get_buffer();
7453 alertpanel_error(_("Template To format error."));
7455 compose_entry_append(compose, buf, COMPOSE_TO);
7459 if (tmpl->cc && *tmpl->cc != '\0') {
7461 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7462 compose->gtkaspell);
7464 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7466 quote_fmt_scan_string(tmpl->cc);
7469 buf = quote_fmt_get_buffer();
7471 alertpanel_error(_("Template Cc format error."));
7473 compose_entry_append(compose, buf, COMPOSE_CC);
7477 if (tmpl->bcc && *tmpl->bcc != '\0') {
7479 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7480 compose->gtkaspell);
7482 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7484 quote_fmt_scan_string(tmpl->bcc);
7487 buf = quote_fmt_get_buffer();
7489 alertpanel_error(_("Template Bcc format error."));
7491 compose_entry_append(compose, buf, COMPOSE_BCC);
7495 /* process the subject */
7496 if (tmpl->subject && *tmpl->subject != '\0') {
7498 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7499 compose->gtkaspell);
7501 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7503 quote_fmt_scan_string(tmpl->subject);
7506 buf = quote_fmt_get_buffer();
7508 alertpanel_error(_("Template subject format error."));
7510 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
7514 procmsg_msginfo_free( dummyinfo );
7517 static void compose_destroy(Compose *compose)
7519 GtkTextBuffer *buffer;
7520 GtkClipboard *clipboard;
7522 compose_list = g_list_remove(compose_list, compose);
7524 if (compose->updating) {
7525 debug_print("danger, not destroying anything now\n");
7526 compose->deferred_destroy = TRUE;
7529 /* NOTE: address_completion_end() does nothing with the window
7530 * however this may change. */
7531 address_completion_end(compose->window);
7533 slist_free_strings(compose->to_list);
7534 g_slist_free(compose->to_list);
7535 slist_free_strings(compose->newsgroup_list);
7536 g_slist_free(compose->newsgroup_list);
7537 slist_free_strings(compose->header_list);
7538 g_slist_free(compose->header_list);
7540 procmsg_msginfo_free(compose->targetinfo);
7541 procmsg_msginfo_free(compose->replyinfo);
7542 procmsg_msginfo_free(compose->fwdinfo);
7544 g_free(compose->replyto);
7545 g_free(compose->cc);
7546 g_free(compose->bcc);
7547 g_free(compose->newsgroups);
7548 g_free(compose->followup_to);
7550 g_free(compose->ml_post);
7552 g_free(compose->inreplyto);
7553 g_free(compose->references);
7554 g_free(compose->msgid);
7555 g_free(compose->boundary);
7557 g_free(compose->redirect_filename);
7558 if (compose->undostruct)
7559 undo_destroy(compose->undostruct);
7561 g_free(compose->sig_str);
7563 g_free(compose->exteditor_file);
7565 g_free(compose->orig_charset);
7567 g_free(compose->privacy_system);
7569 if (addressbook_get_target_compose() == compose)
7570 addressbook_set_target_compose(NULL);
7573 if (compose->gtkaspell) {
7574 gtkaspell_delete(compose->gtkaspell);
7575 compose->gtkaspell = NULL;
7579 prefs_common.compose_width = compose->scrolledwin->allocation.width;
7580 prefs_common.compose_height = compose->window->allocation.height;
7582 if (!gtk_widget_get_parent(compose->paned))
7583 gtk_widget_destroy(compose->paned);
7584 gtk_widget_destroy(compose->popupmenu);
7586 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
7587 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7588 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
7590 gtk_widget_destroy(compose->window);
7591 toolbar_destroy(compose->toolbar);
7592 g_free(compose->toolbar);
7593 g_mutex_free(compose->mutex);
7597 static void compose_attach_info_free(AttachInfo *ainfo)
7599 g_free(ainfo->file);
7600 g_free(ainfo->content_type);
7601 g_free(ainfo->name);
7605 static void compose_attach_update_label(Compose *compose)
7610 GtkTreeModel *model;
7615 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
7616 if(!gtk_tree_model_get_iter_first(model, &iter)) {
7617 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
7621 while(gtk_tree_model_iter_next(model, &iter))
7624 text = g_strdup_printf("(%d)", i);
7625 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
7629 static void compose_attach_remove_selected(Compose *compose)
7631 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
7632 GtkTreeSelection *selection;
7634 GtkTreeModel *model;
7636 selection = gtk_tree_view_get_selection(tree_view);
7637 sel = gtk_tree_selection_get_selected_rows(selection, &model);
7642 for (cur = sel; cur != NULL; cur = cur->next) {
7643 GtkTreePath *path = cur->data;
7644 GtkTreeRowReference *ref = gtk_tree_row_reference_new
7647 gtk_tree_path_free(path);
7650 for (cur = sel; cur != NULL; cur = cur->next) {
7651 GtkTreeRowReference *ref = cur->data;
7652 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
7655 if (gtk_tree_model_get_iter(model, &iter, path))
7656 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
7658 gtk_tree_path_free(path);
7659 gtk_tree_row_reference_free(ref);
7663 compose_attach_update_label(compose);
7666 static struct _AttachProperty
7669 GtkWidget *mimetype_entry;
7670 GtkWidget *encoding_optmenu;
7671 GtkWidget *path_entry;
7672 GtkWidget *filename_entry;
7674 GtkWidget *cancel_btn;
7677 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
7679 gtk_tree_path_free((GtkTreePath *)ptr);
7682 static void compose_attach_property(Compose *compose)
7684 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
7686 GtkComboBox *optmenu;
7687 GtkTreeSelection *selection;
7689 GtkTreeModel *model;
7692 static gboolean cancelled;
7694 /* only if one selected */
7695 selection = gtk_tree_view_get_selection(tree_view);
7696 if (gtk_tree_selection_count_selected_rows(selection) != 1)
7699 sel = gtk_tree_selection_get_selected_rows(selection, &model);
7703 path = (GtkTreePath *) sel->data;
7704 gtk_tree_model_get_iter(model, &iter, path);
7705 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
7708 g_list_foreach(sel, gtk_tree_path_free_, NULL);
7714 if (!attach_prop.window)
7715 compose_attach_property_create(&cancelled);
7716 gtk_widget_grab_focus(attach_prop.ok_btn);
7717 gtk_widget_show(attach_prop.window);
7718 manage_window_set_transient(GTK_WINDOW(attach_prop.window));
7720 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
7721 if (ainfo->encoding == ENC_UNKNOWN)
7722 combobox_select_by_data(optmenu, ENC_BASE64);
7724 combobox_select_by_data(optmenu, ainfo->encoding);
7726 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
7727 ainfo->content_type ? ainfo->content_type : "");
7728 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
7729 ainfo->file ? ainfo->file : "");
7730 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
7731 ainfo->name ? ainfo->name : "");
7734 const gchar *entry_text;
7736 gchar *cnttype = NULL;
7743 gtk_widget_hide(attach_prop.window);
7748 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
7749 if (*entry_text != '\0') {
7752 text = g_strstrip(g_strdup(entry_text));
7753 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
7754 cnttype = g_strdup(text);
7757 alertpanel_error(_("Invalid MIME type."));
7763 ainfo->encoding = combobox_get_active_data(optmenu);
7765 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
7766 if (*entry_text != '\0') {
7767 if (is_file_exist(entry_text) &&
7768 (size = get_file_size(entry_text)) > 0)
7769 file = g_strdup(entry_text);
7772 (_("File doesn't exist or is empty."));
7778 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
7779 if (*entry_text != '\0') {
7780 g_free(ainfo->name);
7781 ainfo->name = g_strdup(entry_text);
7785 g_free(ainfo->content_type);
7786 ainfo->content_type = cnttype;
7789 g_free(ainfo->file);
7795 /* update tree store */
7796 text = to_human_readable(ainfo->size);
7797 gtk_tree_model_get_iter(model, &iter, path);
7798 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
7799 COL_MIMETYPE, ainfo->content_type,
7801 COL_NAME, ainfo->name,
7807 gtk_tree_path_free(path);
7810 #define SET_LABEL_AND_ENTRY(str, entry, top) \
7812 label = gtk_label_new(str); \
7813 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
7814 GTK_FILL, 0, 0, 0); \
7815 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
7817 entry = gtk_entry_new(); \
7818 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
7819 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
7822 static void compose_attach_property_create(gboolean *cancelled)
7828 GtkWidget *mimetype_entry;
7831 GtkListStore *optmenu_menu;
7832 GtkWidget *path_entry;
7833 GtkWidget *filename_entry;
7836 GtkWidget *cancel_btn;
7837 GList *mime_type_list, *strlist;
7840 debug_print("Creating attach_property window...\n");
7842 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
7843 gtk_widget_set_size_request(window, 480, -1);
7844 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
7845 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
7846 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
7847 gtk_window_set_modal(GTK_WINDOW(window), TRUE);
7848 g_signal_connect(G_OBJECT(window), "delete_event",
7849 G_CALLBACK(attach_property_delete_event),
7851 g_signal_connect(G_OBJECT(window), "key_press_event",
7852 G_CALLBACK(attach_property_key_pressed),
7855 vbox = gtk_vbox_new(FALSE, 8);
7856 gtk_container_add(GTK_CONTAINER(window), vbox);
7858 table = gtk_table_new(4, 2, FALSE);
7859 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
7860 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
7861 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
7863 label = gtk_label_new(_("MIME type"));
7864 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
7866 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
7867 mimetype_entry = gtk_combo_box_entry_new_text();
7868 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
7869 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
7871 /* stuff with list */
7872 mime_type_list = procmime_get_mime_type_list();
7874 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
7875 MimeType *type = (MimeType *) mime_type_list->data;
7878 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
7880 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
7883 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
7884 (GCompareFunc)strcmp2);
7887 for (mime_type_list = strlist; mime_type_list != NULL;
7888 mime_type_list = mime_type_list->next) {
7889 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
7890 g_free(mime_type_list->data);
7892 g_list_free(strlist);
7893 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
7894 mimetype_entry = GTK_BIN(mimetype_entry)->child;
7896 label = gtk_label_new(_("Encoding"));
7897 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
7899 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
7901 hbox = gtk_hbox_new(FALSE, 0);
7902 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
7903 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
7905 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
7906 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
7908 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
7909 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
7910 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
7911 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
7912 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
7914 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
7916 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
7917 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
7919 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
7920 &ok_btn, GTK_STOCK_OK,
7922 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
7923 gtk_widget_grab_default(ok_btn);
7925 g_signal_connect(G_OBJECT(ok_btn), "clicked",
7926 G_CALLBACK(attach_property_ok),
7928 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
7929 G_CALLBACK(attach_property_cancel),
7932 gtk_widget_show_all(vbox);
7934 attach_prop.window = window;
7935 attach_prop.mimetype_entry = mimetype_entry;
7936 attach_prop.encoding_optmenu = optmenu;
7937 attach_prop.path_entry = path_entry;
7938 attach_prop.filename_entry = filename_entry;
7939 attach_prop.ok_btn = ok_btn;
7940 attach_prop.cancel_btn = cancel_btn;
7943 #undef SET_LABEL_AND_ENTRY
7945 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
7951 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
7957 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
7958 gboolean *cancelled)
7966 static gboolean attach_property_key_pressed(GtkWidget *widget,
7968 gboolean *cancelled)
7970 if (event && event->keyval == GDK_Escape) {
7977 static void compose_exec_ext_editor(Compose *compose)
7984 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
7985 G_DIR_SEPARATOR, compose);
7987 if (pipe(pipe_fds) < 0) {
7993 if ((pid = fork()) < 0) {
8000 /* close the write side of the pipe */
8003 compose->exteditor_file = g_strdup(tmp);
8004 compose->exteditor_pid = pid;
8006 compose_set_ext_editor_sensitive(compose, FALSE);
8008 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
8009 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
8013 } else { /* process-monitoring process */
8019 /* close the read side of the pipe */
8022 if (compose_write_body_to_file(compose, tmp) < 0) {
8023 fd_write_all(pipe_fds[1], "2\n", 2);
8027 pid_ed = compose_exec_ext_editor_real(tmp);
8029 fd_write_all(pipe_fds[1], "1\n", 2);
8033 /* wait until editor is terminated */
8034 waitpid(pid_ed, NULL, 0);
8036 fd_write_all(pipe_fds[1], "0\n", 2);
8043 #endif /* G_OS_UNIX */
8047 static gint compose_exec_ext_editor_real(const gchar *file)
8054 g_return_val_if_fail(file != NULL, -1);
8056 if ((pid = fork()) < 0) {
8061 if (pid != 0) return pid;
8063 /* grandchild process */
8065 if (setpgid(0, getppid()))
8068 if (prefs_common.ext_editor_cmd &&
8069 (p = strchr(prefs_common.ext_editor_cmd, '%')) &&
8070 *(p + 1) == 's' && !strchr(p + 2, '%')) {
8071 g_snprintf(buf, sizeof(buf), prefs_common.ext_editor_cmd, file);
8073 if (prefs_common.ext_editor_cmd)
8074 g_warning("External editor command line is invalid: '%s'\n",
8075 prefs_common.ext_editor_cmd);
8076 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
8079 cmdline = strsplit_with_quote(buf, " ", 1024);
8080 execvp(cmdline[0], cmdline);
8083 g_strfreev(cmdline);
8088 static gboolean compose_ext_editor_kill(Compose *compose)
8090 pid_t pgid = compose->exteditor_pid * -1;
8093 ret = kill(pgid, 0);
8095 if (ret == 0 || (ret == -1 && EPERM == errno)) {
8099 msg = g_strdup_printf
8100 (_("The external editor is still working.\n"
8101 "Force terminating the process?\n"
8102 "process group id: %d"), -pgid);
8103 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
8104 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
8108 if (val == G_ALERTALTERNATE) {
8109 g_source_remove(compose->exteditor_tag);
8110 g_io_channel_shutdown(compose->exteditor_ch,
8112 g_io_channel_unref(compose->exteditor_ch);
8114 if (kill(pgid, SIGTERM) < 0) perror("kill");
8115 waitpid(compose->exteditor_pid, NULL, 0);
8117 g_warning("Terminated process group id: %d", -pgid);
8118 g_warning("Temporary file: %s",
8119 compose->exteditor_file);
8121 compose_set_ext_editor_sensitive(compose, TRUE);
8123 g_free(compose->exteditor_file);
8124 compose->exteditor_file = NULL;
8125 compose->exteditor_pid = -1;
8126 compose->exteditor_ch = NULL;
8127 compose->exteditor_tag = -1;
8135 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
8139 Compose *compose = (Compose *)data;
8142 debug_print(_("Compose: input from monitoring process\n"));
8144 g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL);
8146 g_io_channel_shutdown(source, FALSE, NULL);
8147 g_io_channel_unref(source);
8149 waitpid(compose->exteditor_pid, NULL, 0);
8151 if (buf[0] == '0') { /* success */
8152 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
8153 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
8155 gtk_text_buffer_set_text(buffer, "", -1);
8156 compose_insert_file(compose, compose->exteditor_file);
8157 compose_changed_cb(NULL, compose);
8159 if (g_unlink(compose->exteditor_file) < 0)
8160 FILE_OP_ERROR(compose->exteditor_file, "unlink");
8161 } else if (buf[0] == '1') { /* failed */
8162 g_warning("Couldn't exec external editor\n");
8163 if (g_unlink(compose->exteditor_file) < 0)
8164 FILE_OP_ERROR(compose->exteditor_file, "unlink");
8165 } else if (buf[0] == '2') {
8166 g_warning("Couldn't write to file\n");
8167 } else if (buf[0] == '3') {
8168 g_warning("Pipe read failed\n");
8171 compose_set_ext_editor_sensitive(compose, TRUE);
8173 g_free(compose->exteditor_file);
8174 compose->exteditor_file = NULL;
8175 compose->exteditor_pid = -1;
8176 compose->exteditor_ch = NULL;
8177 compose->exteditor_tag = -1;
8182 static void compose_set_ext_editor_sensitive(Compose *compose,
8185 GtkItemFactory *ifactory;
8187 ifactory = gtk_item_factory_from_widget(compose->menubar);
8189 menu_set_sensitive(ifactory, "/Message/Send", sensitive);
8190 menu_set_sensitive(ifactory, "/Message/Send later", sensitive);
8191 menu_set_sensitive(ifactory, "/Message/Insert file", sensitive);
8192 menu_set_sensitive(ifactory, "/Message/Insert signature", sensitive);
8193 menu_set_sensitive(ifactory, "/Edit/Wrap current paragraph", sensitive);
8194 menu_set_sensitive(ifactory, "/Edit/Wrap all long lines", sensitive);
8195 menu_set_sensitive(ifactory, "/Edit/Edit with external editor",
8198 gtk_widget_set_sensitive(compose->text, sensitive);
8199 if (compose->toolbar->send_btn)
8200 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
8201 if (compose->toolbar->sendl_btn)
8202 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
8203 if (compose->toolbar->draft_btn)
8204 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
8205 if (compose->toolbar->insert_btn)
8206 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
8207 if (compose->toolbar->sig_btn)
8208 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
8209 if (compose->toolbar->exteditor_btn)
8210 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
8211 if (compose->toolbar->linewrap_current_btn)
8212 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
8213 if (compose->toolbar->linewrap_all_btn)
8214 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
8216 #endif /* G_OS_UNIX */
8219 * compose_undo_state_changed:
8221 * Change the sensivity of the menuentries undo and redo
8223 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
8224 gint redo_state, gpointer data)
8226 GtkWidget *widget = GTK_WIDGET(data);
8227 GtkItemFactory *ifactory;
8229 g_return_if_fail(widget != NULL);
8231 ifactory = gtk_item_factory_from_widget(widget);
8233 switch (undo_state) {
8234 case UNDO_STATE_TRUE:
8235 if (!undostruct->undo_state) {
8236 undostruct->undo_state = TRUE;
8237 menu_set_sensitive(ifactory, "/Edit/Undo", TRUE);
8240 case UNDO_STATE_FALSE:
8241 if (undostruct->undo_state) {
8242 undostruct->undo_state = FALSE;
8243 menu_set_sensitive(ifactory, "/Edit/Undo", FALSE);
8246 case UNDO_STATE_UNCHANGED:
8248 case UNDO_STATE_REFRESH:
8249 menu_set_sensitive(ifactory, "/Edit/Undo",
8250 undostruct->undo_state);
8253 g_warning("Undo state not recognized");
8257 switch (redo_state) {
8258 case UNDO_STATE_TRUE:
8259 if (!undostruct->redo_state) {
8260 undostruct->redo_state = TRUE;
8261 menu_set_sensitive(ifactory, "/Edit/Redo", TRUE);
8264 case UNDO_STATE_FALSE:
8265 if (undostruct->redo_state) {
8266 undostruct->redo_state = FALSE;
8267 menu_set_sensitive(ifactory, "/Edit/Redo", FALSE);
8270 case UNDO_STATE_UNCHANGED:
8272 case UNDO_STATE_REFRESH:
8273 menu_set_sensitive(ifactory, "/Edit/Redo",
8274 undostruct->redo_state);
8277 g_warning("Redo state not recognized");
8282 /* callback functions */
8284 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
8285 * includes "non-client" (windows-izm) in calculation, so this calculation
8286 * may not be accurate.
8288 static gboolean compose_edit_size_alloc(GtkEditable *widget,
8289 GtkAllocation *allocation,
8290 GtkSHRuler *shruler)
8292 if (prefs_common.show_ruler) {
8293 gint char_width = 0, char_height = 0;
8294 gint line_width_in_chars;
8296 gtkut_get_font_size(GTK_WIDGET(widget),
8297 &char_width, &char_height);
8298 line_width_in_chars =
8299 (allocation->width - allocation->x) / char_width;
8301 /* got the maximum */
8302 gtk_ruler_set_range(GTK_RULER(shruler),
8303 0.0, line_width_in_chars, 0,
8304 /*line_width_in_chars*/ char_width);
8310 static void account_activated(GtkComboBox *optmenu, gpointer data)
8312 Compose *compose = (Compose *)data;
8315 gchar *folderidentifier;
8316 gint account_id = 0;
8320 /* Get ID of active account in the combo box */
8321 menu = gtk_combo_box_get_model(optmenu);
8322 gtk_combo_box_get_active_iter(optmenu, &iter);
8323 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
8325 ac = account_find_from_id(account_id);
8326 g_return_if_fail(ac != NULL);
8328 if (ac != compose->account)
8329 compose_select_account(compose, ac, FALSE);
8331 /* Set message save folder */
8332 if (account_get_special_folder(compose->account, F_OUTBOX)) {
8333 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
8335 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
8336 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
8338 gtk_editable_delete_text(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
8339 if (account_get_special_folder(compose->account, F_OUTBOX)) {
8340 folderidentifier = folder_item_get_identifier(account_get_special_folder
8341 (compose->account, F_OUTBOX));
8342 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
8343 g_free(folderidentifier);
8347 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
8348 GtkTreeViewColumn *column, Compose *compose)
8350 compose_attach_property(compose);
8353 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
8356 Compose *compose = (Compose *)data;
8357 GtkTreeSelection *attach_selection;
8358 gint attach_nr_selected;
8359 GtkItemFactory *ifactory;
8361 if (!event) return FALSE;
8363 if (event->button == 3) {
8364 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
8365 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
8366 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
8368 if (attach_nr_selected > 0)
8370 menu_set_sensitive(ifactory, "/Remove", TRUE);
8371 menu_set_sensitive(ifactory, "/Properties...", TRUE);
8373 menu_set_sensitive(ifactory, "/Remove", FALSE);
8374 menu_set_sensitive(ifactory, "/Properties...", FALSE);
8377 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
8378 NULL, NULL, event->button, event->time);
8385 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
8388 Compose *compose = (Compose *)data;
8390 if (!event) return FALSE;
8392 switch (event->keyval) {
8394 compose_attach_remove_selected(compose);
8400 static void compose_allow_user_actions (Compose *compose, gboolean allow)
8402 GtkItemFactory *ifactory = gtk_item_factory_from_widget(compose->menubar);
8403 toolbar_comp_set_sensitive(compose, allow);
8404 menu_set_sensitive(ifactory, "/Message", allow);
8405 menu_set_sensitive(ifactory, "/Edit", allow);
8407 menu_set_sensitive(ifactory, "/Spelling", allow);
8409 menu_set_sensitive(ifactory, "/Options", allow);
8410 menu_set_sensitive(ifactory, "/Tools", allow);
8411 menu_set_sensitive(ifactory, "/Help", allow);
8413 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
8417 static void compose_send_cb(gpointer data, guint action, GtkWidget *widget)
8419 Compose *compose = (Compose *)data;
8421 if (prefs_common.work_offline &&
8422 !inc_offline_should_override(TRUE,
8423 _("Claws Mail needs network access in order "
8424 "to send this email.")))
8427 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
8428 g_source_remove(compose->draft_timeout_tag);
8429 compose->draft_timeout_tag = -1;
8432 compose_send(compose);
8435 static void compose_send_later_cb(gpointer data, guint action,
8438 Compose *compose = (Compose *)data;
8442 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
8446 compose_close(compose);
8447 } else if (val == -1) {
8448 alertpanel_error(_("Could not queue message."));
8449 } else if (val == -2) {
8450 alertpanel_error(_("Could not queue message:\n\n%s."), strerror(errno));
8451 } else if (val == -3) {
8452 if (privacy_peek_error())
8453 alertpanel_error(_("Could not queue message for sending:\n\n"
8454 "Signature failed: %s"), privacy_get_error());
8455 } else if (val == -4) {
8456 alertpanel_error(_("Could not queue message for sending:\n\n"
8457 "Charset conversion failed."));
8458 } else if (val == -5) {
8459 alertpanel_error(_("Could not queue message for sending:\n\n"
8460 "Couldn't get recipient encryption key."));
8461 } else if (val == -6) {
8464 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
8467 #define DRAFTED_AT_EXIT "drafted_at_exit"
8468 static void compose_register_draft(MsgInfo *info)
8470 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8471 DRAFTED_AT_EXIT, NULL);
8472 FILE *fp = fopen(filepath, "ab");
8475 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
8483 gboolean compose_draft (gpointer data, guint action)
8485 Compose *compose = (Compose *)data;
8489 MsgFlags flag = {0, 0};
8490 static gboolean lock = FALSE;
8491 MsgInfo *newmsginfo;
8493 gboolean target_locked = FALSE;
8494 gboolean err = FALSE;
8496 if (lock) return FALSE;
8498 if (compose->sending)
8501 draft = account_get_special_folder(compose->account, F_DRAFT);
8502 g_return_val_if_fail(draft != NULL, FALSE);
8504 if (!g_mutex_trylock(compose->mutex)) {
8505 /* we don't want to lock the mutex once it's available,
8506 * because as the only other part of compose.c locking
8507 * it is compose_close - which means once unlocked,
8508 * the compose struct will be freed */
8509 debug_print("couldn't lock mutex, probably sending\n");
8515 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
8516 G_DIR_SEPARATOR, compose);
8517 if ((fp = g_fopen(tmp, "wb")) == NULL) {
8518 FILE_OP_ERROR(tmp, "fopen");
8522 /* chmod for security */
8523 if (change_file_mode_rw(fp, tmp) < 0) {
8524 FILE_OP_ERROR(tmp, "chmod");
8525 g_warning("can't change file mode\n");
8528 /* Save draft infos */
8529 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
8530 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
8532 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
8533 gchar *savefolderid;
8535 savefolderid = gtk_editable_get_chars(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
8536 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
8537 g_free(savefolderid);
8539 if (compose->return_receipt) {
8540 err |= (fprintf(fp, "RRCPT:1\n") < 0);
8542 if (compose->privacy_system) {
8543 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
8544 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
8545 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
8548 /* Message-ID of message replying to */
8549 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
8552 folderid = folder_item_get_identifier(compose->replyinfo->folder);
8553 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
8556 /* Message-ID of message forwarding to */
8557 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
8560 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
8561 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
8565 /* end of headers */
8566 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
8573 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
8577 if (fclose(fp) == EOF) {
8581 if (compose->targetinfo) {
8582 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
8583 flag.perm_flags = target_locked?MSG_LOCKED:0;
8585 flag.tmp_flags = MSG_DRAFT;
8587 folder_item_scan(draft);
8588 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
8589 MsgInfo *tmpinfo = NULL;
8590 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
8591 if (compose->msgid) {
8592 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
8595 msgnum = tmpinfo->msgnum;
8596 procmsg_msginfo_free(tmpinfo);
8597 debug_print("got draft msgnum %d from scanning\n", msgnum);
8599 debug_print("didn't get draft msgnum after scanning\n");
8602 debug_print("got draft msgnum %d from adding\n", msgnum);
8608 if (action != COMPOSE_AUTO_SAVE) {
8609 if (action != COMPOSE_DRAFT_FOR_EXIT)
8610 alertpanel_error(_("Could not save draft."));
8613 gtkut_window_popup(compose->window);
8614 val = alertpanel_full(_("Could not save draft"),
8615 _("Could not save draft.\n"
8616 "Do you want to cancel exit or discard this email?"),
8617 _("_Cancel exit"), _("_Discard email"), NULL,
8618 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
8619 if (val == G_ALERTALTERNATE) {
8621 g_mutex_unlock(compose->mutex); /* must be done before closing */
8622 compose_close(compose);
8626 g_mutex_unlock(compose->mutex); /* must be done before closing */
8635 if (compose->mode == COMPOSE_REEDIT) {
8636 compose_remove_reedit_target(compose, TRUE);
8639 newmsginfo = folder_item_get_msginfo(draft, msgnum);
8642 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
8644 procmsg_msginfo_set_flags(newmsginfo, MSG_LOCKED, MSG_DRAFT);
8646 procmsg_msginfo_set_flags(newmsginfo, 0, MSG_DRAFT);
8647 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
8648 procmsg_msginfo_set_flags(newmsginfo, 0,
8649 MSG_HAS_ATTACHMENT);
8651 if (action == COMPOSE_DRAFT_FOR_EXIT) {
8652 compose_register_draft(newmsginfo);
8654 procmsg_msginfo_free(newmsginfo);
8657 folder_item_scan(draft);
8659 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
8661 g_mutex_unlock(compose->mutex); /* must be done before closing */
8662 compose_close(compose);
8668 path = folder_item_fetch_msg(draft, msgnum);
8670 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
8673 if (g_stat(path, &s) < 0) {
8674 FILE_OP_ERROR(path, "stat");
8680 procmsg_msginfo_free(compose->targetinfo);
8681 compose->targetinfo = procmsg_msginfo_new();
8682 compose->targetinfo->msgnum = msgnum;
8683 compose->targetinfo->size = s.st_size;
8684 compose->targetinfo->mtime = s.st_mtime;
8685 compose->targetinfo->folder = draft;
8687 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
8688 compose->mode = COMPOSE_REEDIT;
8690 if (action == COMPOSE_AUTO_SAVE) {
8691 compose->autosaved_draft = compose->targetinfo;
8693 compose->modified = FALSE;
8694 compose_set_title(compose);
8698 g_mutex_unlock(compose->mutex);
8702 void compose_clear_exit_drafts(void)
8704 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8705 DRAFTED_AT_EXIT, NULL);
8706 if (is_file_exist(filepath))
8712 void compose_reopen_exit_drafts(void)
8714 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8715 DRAFTED_AT_EXIT, NULL);
8716 FILE *fp = fopen(filepath, "rb");
8720 while (fgets(buf, sizeof(buf), fp)) {
8721 gchar **parts = g_strsplit(buf, "\t", 2);
8722 const gchar *folder = parts[0];
8723 int msgnum = parts[1] ? atoi(parts[1]):-1;
8725 if (folder && *folder && msgnum > -1) {
8726 FolderItem *item = folder_find_item_from_identifier(folder);
8727 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
8729 compose_reedit(info, FALSE);
8736 compose_clear_exit_drafts();
8739 static void compose_draft_cb(gpointer data, guint action, GtkWidget *widget)
8741 compose_draft(data, action);
8744 static void compose_attach_cb(gpointer data, guint action, GtkWidget *widget)
8746 Compose *compose = (Compose *)data;
8749 if (compose->redirect_filename != NULL)
8752 file_list = filesel_select_multiple_files_open(_("Select file"));
8757 for ( tmp = file_list; tmp; tmp = tmp->next) {
8758 gchar *file = (gchar *) tmp->data;
8759 gchar *utf8_filename = conv_filename_to_utf8(file);
8760 compose_attach_append(compose, file, utf8_filename, NULL);
8761 compose_changed_cb(NULL, compose);
8763 g_free(utf8_filename);
8765 g_list_free(file_list);
8769 static void compose_insert_file_cb(gpointer data, guint action,
8772 Compose *compose = (Compose *)data;
8775 file_list = filesel_select_multiple_files_open(_("Select file"));
8780 for ( tmp = file_list; tmp; tmp = tmp->next) {
8781 gchar *file = (gchar *) tmp->data;
8782 gchar *filedup = g_strdup(file);
8783 gchar *shortfile = g_path_get_basename(filedup);
8784 ComposeInsertResult res;
8786 res = compose_insert_file(compose, file);
8787 if (res == COMPOSE_INSERT_READ_ERROR) {
8788 alertpanel_error(_("File '%s' could not be read."), shortfile);
8789 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
8790 alertpanel_error(_("File '%s' contained invalid characters\n"
8791 "for the current encoding, insertion may be incorrect."), shortfile);
8797 g_list_free(file_list);
8801 static void compose_insert_sig_cb(gpointer data, guint action,
8804 Compose *compose = (Compose *)data;
8806 compose_insert_sig(compose, FALSE);
8809 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
8813 Compose *compose = (Compose *)data;
8815 gtkut_widget_get_uposition(widget, &x, &y);
8816 prefs_common.compose_x = x;
8817 prefs_common.compose_y = y;
8819 if (compose->sending || compose->updating)
8821 compose_close_cb(compose, 0, NULL);
8825 void compose_close_toolbar(Compose *compose)
8827 compose_close_cb(compose, 0, NULL);
8830 static void compose_close_cb(gpointer data, guint action, GtkWidget *widget)
8832 Compose *compose = (Compose *)data;
8836 if (compose->exteditor_tag != -1) {
8837 if (!compose_ext_editor_kill(compose))
8842 if (compose->modified) {
8843 val = alertpanel(_("Discard message"),
8844 _("This message has been modified. Discard it?"),
8845 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
8848 case G_ALERTDEFAULT:
8849 if (prefs_common.autosave)
8850 compose_remove_draft(compose);
8852 case G_ALERTALTERNATE:
8853 compose_draft_cb(data, COMPOSE_QUIT_EDITING, NULL);
8860 compose_close(compose);
8863 static void compose_set_encoding_cb(gpointer data, guint action,
8866 Compose *compose = (Compose *)data;
8868 if (GTK_CHECK_MENU_ITEM(widget)->active)
8869 compose->out_encoding = (CharSet)action;
8872 static void compose_address_cb(gpointer data, guint action, GtkWidget *widget)
8874 Compose *compose = (Compose *)data;
8876 addressbook_open(compose);
8879 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
8881 Compose *compose = (Compose *)data;
8886 tmpl = g_object_get_data(G_OBJECT(widget), "template");
8887 g_return_if_fail(tmpl != NULL);
8889 msg = g_strdup_printf(_("Do you want to apply the template '%s' ?"),
8891 val = alertpanel(_("Apply template"), msg,
8892 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
8895 if (val == G_ALERTDEFAULT)
8896 compose_template_apply(compose, tmpl, TRUE);
8897 else if (val == G_ALERTALTERNATE)
8898 compose_template_apply(compose, tmpl, FALSE);
8901 static void compose_ext_editor_cb(gpointer data, guint action,
8904 Compose *compose = (Compose *)data;
8906 compose_exec_ext_editor(compose);
8909 static void compose_undo_cb(Compose *compose)
8911 gboolean prev_autowrap = compose->autowrap;
8913 compose->autowrap = FALSE;
8914 undo_undo(compose->undostruct);
8915 compose->autowrap = prev_autowrap;
8918 static void compose_redo_cb(Compose *compose)
8920 gboolean prev_autowrap = compose->autowrap;
8922 compose->autowrap = FALSE;
8923 undo_redo(compose->undostruct);
8924 compose->autowrap = prev_autowrap;
8927 static void entry_cut_clipboard(GtkWidget *entry)
8929 if (GTK_IS_EDITABLE(entry))
8930 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
8931 else if (GTK_IS_TEXT_VIEW(entry))
8932 gtk_text_buffer_cut_clipboard(
8933 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
8934 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
8938 static void entry_copy_clipboard(GtkWidget *entry)
8940 if (GTK_IS_EDITABLE(entry))
8941 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
8942 else if (GTK_IS_TEXT_VIEW(entry))
8943 gtk_text_buffer_copy_clipboard(
8944 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
8945 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
8948 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
8949 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
8951 if (GTK_IS_TEXT_VIEW(entry)) {
8952 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
8953 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
8954 GtkTextIter start_iter, end_iter;
8956 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
8958 if (contents == NULL)
8961 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
8963 /* we shouldn't delete the selection when middle-click-pasting, or we
8964 * can't mid-click-paste our own selection */
8965 if (clip != GDK_SELECTION_PRIMARY) {
8966 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
8969 if (insert_place == NULL) {
8970 /* if insert_place isn't specified, insert at the cursor.
8971 * used for Ctrl-V pasting */
8972 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
8973 start = gtk_text_iter_get_offset(&start_iter);
8974 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
8976 /* if insert_place is specified, paste here.
8977 * used for mid-click-pasting */
8978 start = gtk_text_iter_get_offset(insert_place);
8979 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
8983 /* paste unwrapped: mark the paste so it's not wrapped later */
8984 end = start + strlen(contents);
8985 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
8986 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
8987 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
8988 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
8989 /* rewrap paragraph now (after a mid-click-paste) */
8990 mark_start = gtk_text_buffer_get_insert(buffer);
8991 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
8992 gtk_text_iter_backward_char(&start_iter);
8993 compose_beautify_paragraph(compose, &start_iter, TRUE);
8995 } else if (GTK_IS_EDITABLE(entry))
8996 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
9000 static void entry_allsel(GtkWidget *entry)
9002 if (GTK_IS_EDITABLE(entry))
9003 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
9004 else if (GTK_IS_TEXT_VIEW(entry)) {
9005 GtkTextIter startiter, enditer;
9006 GtkTextBuffer *textbuf;
9008 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
9009 gtk_text_buffer_get_start_iter(textbuf, &startiter);
9010 gtk_text_buffer_get_end_iter(textbuf, &enditer);
9012 gtk_text_buffer_move_mark_by_name(textbuf,
9013 "selection_bound", &startiter);
9014 gtk_text_buffer_move_mark_by_name(textbuf,
9015 "insert", &enditer);
9019 static void compose_cut_cb(Compose *compose)
9021 if (compose->focused_editable
9023 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9026 entry_cut_clipboard(compose->focused_editable);
9029 static void compose_copy_cb(Compose *compose)
9031 if (compose->focused_editable
9033 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9036 entry_copy_clipboard(compose->focused_editable);
9039 static void compose_paste_cb(Compose *compose)
9042 GtkTextBuffer *buffer;
9044 if (compose->focused_editable &&
9045 GTK_WIDGET_HAS_FOCUS(compose->focused_editable))
9046 entry_paste_clipboard(compose, compose->focused_editable,
9047 prefs_common.linewrap_pastes,
9048 GDK_SELECTION_CLIPBOARD, NULL);
9052 static void compose_paste_as_quote_cb(Compose *compose)
9054 gint wrap_quote = prefs_common.linewrap_quote;
9055 if (compose->focused_editable
9057 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9060 /* let text_insert() (called directly or at a later time
9061 * after the gtk_editable_paste_clipboard) know that
9062 * text is to be inserted as a quotation. implemented
9063 * by using a simple refcount... */
9064 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
9065 G_OBJECT(compose->focused_editable),
9066 "paste_as_quotation"));
9067 g_object_set_data(G_OBJECT(compose->focused_editable),
9068 "paste_as_quotation",
9069 GINT_TO_POINTER(paste_as_quotation + 1));
9070 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
9071 entry_paste_clipboard(compose, compose->focused_editable,
9072 prefs_common.linewrap_pastes,
9073 GDK_SELECTION_CLIPBOARD, NULL);
9074 prefs_common.linewrap_quote = wrap_quote;
9078 static void compose_paste_no_wrap_cb(Compose *compose)
9081 GtkTextBuffer *buffer;
9083 if (compose->focused_editable
9085 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9088 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
9089 GDK_SELECTION_CLIPBOARD, NULL);
9093 static void compose_paste_wrap_cb(Compose *compose)
9096 GtkTextBuffer *buffer;
9098 if (compose->focused_editable
9100 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9103 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
9104 GDK_SELECTION_CLIPBOARD, NULL);
9108 static void compose_allsel_cb(Compose *compose)
9110 if (compose->focused_editable
9112 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9115 entry_allsel(compose->focused_editable);
9118 static void textview_move_beginning_of_line (GtkTextView *text)
9120 GtkTextBuffer *buffer;
9124 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9126 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9127 mark = gtk_text_buffer_get_insert(buffer);
9128 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9129 gtk_text_iter_set_line_offset(&ins, 0);
9130 gtk_text_buffer_place_cursor(buffer, &ins);
9133 static void textview_move_forward_character (GtkTextView *text)
9135 GtkTextBuffer *buffer;
9139 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9141 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9142 mark = gtk_text_buffer_get_insert(buffer);
9143 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9144 if (gtk_text_iter_forward_cursor_position(&ins))
9145 gtk_text_buffer_place_cursor(buffer, &ins);
9148 static void textview_move_backward_character (GtkTextView *text)
9150 GtkTextBuffer *buffer;
9154 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9156 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9157 mark = gtk_text_buffer_get_insert(buffer);
9158 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9159 if (gtk_text_iter_backward_cursor_position(&ins))
9160 gtk_text_buffer_place_cursor(buffer, &ins);
9163 static void textview_move_forward_word (GtkTextView *text)
9165 GtkTextBuffer *buffer;
9170 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9172 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9173 mark = gtk_text_buffer_get_insert(buffer);
9174 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9175 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
9176 if (gtk_text_iter_forward_word_ends(&ins, count)) {
9177 gtk_text_iter_backward_word_start(&ins);
9178 gtk_text_buffer_place_cursor(buffer, &ins);
9182 static void textview_move_backward_word (GtkTextView *text)
9184 GtkTextBuffer *buffer;
9189 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9191 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9192 mark = gtk_text_buffer_get_insert(buffer);
9193 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9194 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
9195 if (gtk_text_iter_backward_word_starts(&ins, 1))
9196 gtk_text_buffer_place_cursor(buffer, &ins);
9199 static void textview_move_end_of_line (GtkTextView *text)
9201 GtkTextBuffer *buffer;
9205 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9207 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9208 mark = gtk_text_buffer_get_insert(buffer);
9209 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9210 if (gtk_text_iter_forward_to_line_end(&ins))
9211 gtk_text_buffer_place_cursor(buffer, &ins);
9214 static void textview_move_next_line (GtkTextView *text)
9216 GtkTextBuffer *buffer;
9221 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9223 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9224 mark = gtk_text_buffer_get_insert(buffer);
9225 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9226 offset = gtk_text_iter_get_line_offset(&ins);
9227 if (gtk_text_iter_forward_line(&ins)) {
9228 gtk_text_iter_set_line_offset(&ins, offset);
9229 gtk_text_buffer_place_cursor(buffer, &ins);
9233 static void textview_move_previous_line (GtkTextView *text)
9235 GtkTextBuffer *buffer;
9240 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9242 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9243 mark = gtk_text_buffer_get_insert(buffer);
9244 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9245 offset = gtk_text_iter_get_line_offset(&ins);
9246 if (gtk_text_iter_backward_line(&ins)) {
9247 gtk_text_iter_set_line_offset(&ins, offset);
9248 gtk_text_buffer_place_cursor(buffer, &ins);
9252 static void textview_delete_forward_character (GtkTextView *text)
9254 GtkTextBuffer *buffer;
9256 GtkTextIter ins, end_iter;
9258 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9260 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9261 mark = gtk_text_buffer_get_insert(buffer);
9262 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9264 if (gtk_text_iter_forward_char(&end_iter)) {
9265 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9269 static void textview_delete_backward_character (GtkTextView *text)
9271 GtkTextBuffer *buffer;
9273 GtkTextIter ins, end_iter;
9275 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9277 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9278 mark = gtk_text_buffer_get_insert(buffer);
9279 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9281 if (gtk_text_iter_backward_char(&end_iter)) {
9282 gtk_text_buffer_delete(buffer, &end_iter, &ins);
9286 static void textview_delete_forward_word (GtkTextView *text)
9288 GtkTextBuffer *buffer;
9290 GtkTextIter ins, end_iter;
9292 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9294 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9295 mark = gtk_text_buffer_get_insert(buffer);
9296 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9298 if (gtk_text_iter_forward_word_end(&end_iter)) {
9299 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9303 static void textview_delete_backward_word (GtkTextView *text)
9305 GtkTextBuffer *buffer;
9307 GtkTextIter ins, end_iter;
9309 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9311 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9312 mark = gtk_text_buffer_get_insert(buffer);
9313 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9315 if (gtk_text_iter_backward_word_start(&end_iter)) {
9316 gtk_text_buffer_delete(buffer, &end_iter, &ins);
9320 static void textview_delete_line (GtkTextView *text)
9322 GtkTextBuffer *buffer;
9324 GtkTextIter ins, start_iter, end_iter;
9327 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9329 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9330 mark = gtk_text_buffer_get_insert(buffer);
9331 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9334 gtk_text_iter_set_line_offset(&start_iter, 0);
9337 if (gtk_text_iter_ends_line(&end_iter))
9338 found = gtk_text_iter_forward_char(&end_iter);
9340 found = gtk_text_iter_forward_to_line_end(&end_iter);
9343 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
9346 static void textview_delete_to_line_end (GtkTextView *text)
9348 GtkTextBuffer *buffer;
9350 GtkTextIter ins, end_iter;
9353 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9355 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9356 mark = gtk_text_buffer_get_insert(buffer);
9357 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9359 if (gtk_text_iter_ends_line(&end_iter))
9360 found = gtk_text_iter_forward_char(&end_iter);
9362 found = gtk_text_iter_forward_to_line_end(&end_iter);
9364 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9367 static void compose_advanced_action_cb(Compose *compose,
9368 ComposeCallAdvancedAction action)
9370 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9372 void (*do_action) (GtkTextView *text);
9373 } action_table[] = {
9374 {textview_move_beginning_of_line},
9375 {textview_move_forward_character},
9376 {textview_move_backward_character},
9377 {textview_move_forward_word},
9378 {textview_move_backward_word},
9379 {textview_move_end_of_line},
9380 {textview_move_next_line},
9381 {textview_move_previous_line},
9382 {textview_delete_forward_character},
9383 {textview_delete_backward_character},
9384 {textview_delete_forward_word},
9385 {textview_delete_backward_word},
9386 {textview_delete_line},
9387 {NULL}, /* gtk_stext_delete_line_n */
9388 {textview_delete_to_line_end}
9391 if (!GTK_WIDGET_HAS_FOCUS(text)) return;
9393 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
9394 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
9395 if (action_table[action].do_action)
9396 action_table[action].do_action(text);
9398 g_warning("Not implemented yet.");
9402 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
9406 if (GTK_IS_EDITABLE(widget)) {
9407 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
9408 gtk_editable_set_position(GTK_EDITABLE(widget),
9411 if (widget->parent && widget->parent->parent
9412 && widget->parent->parent->parent) {
9413 if (GTK_IS_SCROLLED_WINDOW(widget->parent->parent->parent)) {
9414 gint y = widget->allocation.y;
9415 gint height = widget->allocation.height;
9416 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
9417 (GTK_SCROLLED_WINDOW(widget->parent->parent->parent));
9419 if (y < (int)shown->value) {
9420 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown), y - 1);
9422 if (y + height > (int)shown->value + (int)shown->page_size) {
9423 if (y - height - 1 < (int)shown->upper - (int)shown->page_size) {
9424 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
9425 y + height - (int)shown->page_size - 1);
9427 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
9428 (int)shown->upper - (int)shown->page_size - 1);
9435 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
9436 compose->focused_editable = widget;
9439 if (GTK_IS_TEXT_VIEW(widget)
9440 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
9441 gtk_widget_ref(compose->notebook);
9442 gtk_widget_ref(compose->edit_vbox);
9443 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
9444 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
9445 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
9446 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
9447 gtk_widget_unref(compose->notebook);
9448 gtk_widget_unref(compose->edit_vbox);
9449 g_signal_handlers_block_by_func(G_OBJECT(widget),
9450 G_CALLBACK(compose_grab_focus_cb),
9452 gtk_widget_grab_focus(widget);
9453 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
9454 G_CALLBACK(compose_grab_focus_cb),
9456 } else if (!GTK_IS_TEXT_VIEW(widget)
9457 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
9458 gtk_widget_ref(compose->notebook);
9459 gtk_widget_ref(compose->edit_vbox);
9460 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
9461 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
9462 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
9463 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
9464 gtk_widget_unref(compose->notebook);
9465 gtk_widget_unref(compose->edit_vbox);
9466 g_signal_handlers_block_by_func(G_OBJECT(widget),
9467 G_CALLBACK(compose_grab_focus_cb),
9469 gtk_widget_grab_focus(widget);
9470 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
9471 G_CALLBACK(compose_grab_focus_cb),
9477 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
9479 compose->modified = TRUE;
9481 compose_set_title(compose);
9485 static void compose_wrap_cb(gpointer data, guint action, GtkWidget *widget)
9487 Compose *compose = (Compose *)data;
9490 compose_wrap_all_full(compose, TRUE);
9492 compose_beautify_paragraph(compose, NULL, TRUE);
9495 static void compose_find_cb(gpointer data, guint action, GtkWidget *widget)
9497 Compose *compose = (Compose *)data;
9499 message_search_compose(compose);
9502 static void compose_toggle_autowrap_cb(gpointer data, guint action,
9505 Compose *compose = (Compose *)data;
9506 compose->autowrap = GTK_CHECK_MENU_ITEM(widget)->active;
9507 if (compose->autowrap)
9508 compose_wrap_all_full(compose, TRUE);
9509 compose->autowrap = GTK_CHECK_MENU_ITEM(widget)->active;
9512 static void compose_toggle_sign_cb(gpointer data, guint action,
9515 Compose *compose = (Compose *)data;
9517 if (GTK_CHECK_MENU_ITEM(widget)->active)
9518 compose->use_signing = TRUE;
9520 compose->use_signing = FALSE;
9523 static void compose_toggle_encrypt_cb(gpointer data, guint action,
9526 Compose *compose = (Compose *)data;
9528 if (GTK_CHECK_MENU_ITEM(widget)->active)
9529 compose->use_encryption = TRUE;
9531 compose->use_encryption = FALSE;
9534 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
9536 g_free(compose->privacy_system);
9538 compose->privacy_system = g_strdup(account->default_privacy_system);
9539 compose_update_privacy_system_menu_item(compose, warn);
9542 static void compose_toggle_ruler_cb(gpointer data, guint action,
9545 Compose *compose = (Compose *)data;
9547 if (GTK_CHECK_MENU_ITEM(widget)->active) {
9548 gtk_widget_show(compose->ruler_hbox);
9549 prefs_common.show_ruler = TRUE;
9551 gtk_widget_hide(compose->ruler_hbox);
9552 gtk_widget_queue_resize(compose->edit_vbox);
9553 prefs_common.show_ruler = FALSE;
9557 static void compose_attach_drag_received_cb (GtkWidget *widget,
9558 GdkDragContext *context,
9561 GtkSelectionData *data,
9566 Compose *compose = (Compose *)user_data;
9569 if (gdk_atom_name(data->type) &&
9570 !strcmp(gdk_atom_name(data->type), "text/uri-list")
9571 && gtk_drag_get_source_widget(context) !=
9572 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
9573 list = uri_list_extract_filenames((const gchar *)data->data);
9574 for (tmp = list; tmp != NULL; tmp = tmp->next) {
9575 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
9576 compose_attach_append
9577 (compose, (const gchar *)tmp->data,
9578 utf8_filename, NULL);
9579 g_free(utf8_filename);
9581 if (list) compose_changed_cb(NULL, compose);
9582 list_free_strings(list);
9584 } else if (gtk_drag_get_source_widget(context)
9585 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
9586 /* comes from our summaryview */
9587 SummaryView * summaryview = NULL;
9588 GSList * list = NULL, *cur = NULL;
9590 if (mainwindow_get_mainwindow())
9591 summaryview = mainwindow_get_mainwindow()->summaryview;
9594 list = summary_get_selected_msg_list(summaryview);
9596 for (cur = list; cur; cur = cur->next) {
9597 MsgInfo *msginfo = (MsgInfo *)cur->data;
9600 file = procmsg_get_message_file_full(msginfo,
9603 compose_attach_append(compose, (const gchar *)file,
9604 (const gchar *)file, "message/rfc822");
9612 static gboolean compose_drag_drop(GtkWidget *widget,
9613 GdkDragContext *drag_context,
9615 guint time, gpointer user_data)
9617 /* not handling this signal makes compose_insert_drag_received_cb
9622 static void compose_insert_drag_received_cb (GtkWidget *widget,
9623 GdkDragContext *drag_context,
9626 GtkSelectionData *data,
9631 Compose *compose = (Compose *)user_data;
9634 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
9636 if (gdk_atom_name(data->type) && !strcmp(gdk_atom_name(data->type), "text/uri-list")) {
9637 AlertValue val = G_ALERTDEFAULT;
9639 switch (prefs_common.compose_dnd_mode) {
9640 case COMPOSE_DND_ASK:
9641 val = alertpanel_full(_("Insert or attach?"),
9642 _("Do you want to insert the contents of the file(s) "
9643 "into the message body, or attach it to the email?"),
9644 GTK_STOCK_CANCEL, _("+_Insert"), _("_Attach"),
9645 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
9647 case COMPOSE_DND_INSERT:
9648 val = G_ALERTALTERNATE;
9650 case COMPOSE_DND_ATTACH:
9654 /* unexpected case */
9655 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
9658 if (val & G_ALERTDISABLE) {
9659 val &= ~G_ALERTDISABLE;
9660 /* remember what action to perform by default, only if we don't click Cancel */
9661 if (val == G_ALERTALTERNATE)
9662 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
9663 else if (val == G_ALERTOTHER)
9664 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
9667 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
9668 gtk_drag_finish(drag_context, FALSE, FALSE, time);
9670 } else if (val == G_ALERTOTHER) {
9671 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
9674 list = uri_list_extract_filenames((const gchar *)data->data);
9675 for (tmp = list; tmp != NULL; tmp = tmp->next) {
9676 compose_insert_file(compose, (const gchar *)tmp->data);
9678 list_free_strings(list);
9680 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9683 #if GTK_CHECK_VERSION(2, 8, 0)
9684 /* do nothing, handled by GTK */
9686 gchar *tmpfile = get_tmp_file();
9687 str_write_to_file((const gchar *)data->data, tmpfile);
9688 compose_insert_file(compose, tmpfile);
9691 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9695 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9698 static void compose_header_drag_received_cb (GtkWidget *widget,
9699 GdkDragContext *drag_context,
9702 GtkSelectionData *data,
9707 GtkEditable *entry = (GtkEditable *)user_data;
9708 gchar *email = (gchar *)data->data;
9710 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
9713 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
9714 gchar *decoded=g_new(gchar, strlen(email));
9717 email += strlen("mailto:");
9718 decode_uri(decoded, email); /* will fit */
9719 gtk_editable_delete_text(entry, 0, -1);
9720 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
9721 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9725 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9728 static void compose_toggle_return_receipt_cb(gpointer data, guint action,
9731 Compose *compose = (Compose *)data;
9733 if (GTK_CHECK_MENU_ITEM(widget)->active)
9734 compose->return_receipt = TRUE;
9736 compose->return_receipt = FALSE;
9739 static void compose_toggle_remove_refs_cb(gpointer data, guint action,
9742 Compose *compose = (Compose *)data;
9744 if (GTK_CHECK_MENU_ITEM(widget)->active)
9745 compose->remove_references = TRUE;
9747 compose->remove_references = FALSE;
9750 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
9752 ComposeHeaderEntry *headerentry)
9754 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
9755 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
9756 !(event->state & GDK_MODIFIER_MASK) &&
9757 (event->keyval == GDK_BackSpace) &&
9758 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
9759 gtk_container_remove
9760 (GTK_CONTAINER(headerentry->compose->header_table),
9761 headerentry->combo);
9762 gtk_container_remove
9763 (GTK_CONTAINER(headerentry->compose->header_table),
9764 headerentry->entry);
9765 headerentry->compose->header_list =
9766 g_slist_remove(headerentry->compose->header_list,
9768 g_free(headerentry);
9769 } else if (event->keyval == GDK_Tab) {
9770 if (headerentry->compose->header_last == headerentry) {
9771 /* Override default next focus, and give it to subject_entry
9772 * instead of notebook tabs
9774 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
9775 gtk_widget_grab_focus(headerentry->compose->subject_entry);
9782 static gboolean compose_headerentry_changed_cb(GtkWidget *entry,
9783 ComposeHeaderEntry *headerentry)
9785 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
9786 compose_create_header_entry(headerentry->compose);
9787 g_signal_handlers_disconnect_matched
9788 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
9789 0, 0, NULL, NULL, headerentry);
9791 /* Automatically scroll down */
9792 compose_show_first_last_header(headerentry->compose, FALSE);
9798 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
9800 GtkAdjustment *vadj;
9802 g_return_if_fail(compose);
9803 g_return_if_fail(GTK_IS_WIDGET(compose->header_table));
9804 g_return_if_fail(GTK_IS_VIEWPORT(compose->header_table->parent));
9806 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(compose->header_table->parent));
9807 gtk_adjustment_set_value(vadj, (show_first ? vadj->lower : vadj->upper));
9808 gtk_adjustment_changed(vadj);
9811 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
9812 const gchar *text, gint len, Compose *compose)
9814 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
9815 (G_OBJECT(compose->text), "paste_as_quotation"));
9818 g_return_if_fail(text != NULL);
9820 g_signal_handlers_block_by_func(G_OBJECT(buffer),
9821 G_CALLBACK(text_inserted),
9823 if (paste_as_quotation) {
9827 GtkTextIter start_iter;
9832 new_text = g_strndup(text, len);
9834 qmark = compose_quote_char_from_context(compose);
9836 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
9837 gtk_text_buffer_place_cursor(buffer, iter);
9839 pos = gtk_text_iter_get_offset(iter);
9841 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
9842 _("Quote format error at line %d."));
9843 quote_fmt_reset_vartable();
9845 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
9846 GINT_TO_POINTER(paste_as_quotation - 1));
9848 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
9849 gtk_text_buffer_place_cursor(buffer, iter);
9850 gtk_text_buffer_delete_mark(buffer, mark);
9852 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
9853 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
9854 compose_beautify_paragraph(compose, &start_iter, FALSE);
9855 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
9856 gtk_text_buffer_delete_mark(buffer, mark);
9858 if (strcmp(text, "\n") || compose->automatic_break
9859 || gtk_text_iter_starts_line(iter))
9860 gtk_text_buffer_insert(buffer, iter, text, len);
9862 debug_print("insert nowrap \\n\n");
9863 gtk_text_buffer_insert_with_tags_by_name(buffer,
9864 iter, text, len, "no_join", NULL);
9868 if (!paste_as_quotation) {
9869 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
9870 compose_beautify_paragraph(compose, iter, FALSE);
9871 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
9872 gtk_text_buffer_delete_mark(buffer, mark);
9875 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
9876 G_CALLBACK(text_inserted),
9878 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
9880 if (prefs_common.autosave &&
9881 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
9882 compose->draft_timeout_tag != -2 /* disabled while loading */)
9883 compose->draft_timeout_tag = g_timeout_add
9884 (500, (GtkFunction) compose_defer_auto_save_draft, compose);
9886 static gint compose_defer_auto_save_draft(Compose *compose)
9888 compose->draft_timeout_tag = -1;
9889 compose_draft_cb((gpointer)compose, COMPOSE_AUTO_SAVE, NULL);
9894 static void compose_check_all(Compose *compose)
9896 if (compose->gtkaspell)
9897 gtkaspell_check_all(compose->gtkaspell);
9900 static void compose_highlight_all(Compose *compose)
9902 if (compose->gtkaspell)
9903 gtkaspell_highlight_all(compose->gtkaspell);
9906 static void compose_check_backwards(Compose *compose)
9908 if (compose->gtkaspell)
9909 gtkaspell_check_backwards(compose->gtkaspell);
9911 GtkItemFactory *ifactory;
9912 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
9913 menu_set_sensitive(ifactory, "/Edit/Check backwards misspelled word", FALSE);
9914 menu_set_sensitive(ifactory, "/Edit/Forward to next misspelled word", FALSE);
9918 static void compose_check_forwards_go(Compose *compose)
9920 if (compose->gtkaspell)
9921 gtkaspell_check_forwards_go(compose->gtkaspell);
9923 GtkItemFactory *ifactory;
9924 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
9925 menu_set_sensitive(ifactory, "/Edit/Check backwards misspelled word", FALSE);
9926 menu_set_sensitive(ifactory, "/Edit/Forward to next misspelled word", FALSE);
9932 *\brief Guess originating forward account from MsgInfo and several
9933 * "common preference" settings. Return NULL if no guess.
9935 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
9937 PrefsAccount *account = NULL;
9939 g_return_val_if_fail(msginfo, NULL);
9940 g_return_val_if_fail(msginfo->folder, NULL);
9941 g_return_val_if_fail(msginfo->folder->prefs, NULL);
9943 if (msginfo->folder->prefs->enable_default_account)
9944 account = account_find_from_id(msginfo->folder->prefs->default_account);
9947 account = msginfo->folder->folder->account;
9949 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
9951 Xstrdup_a(to, msginfo->to, return NULL);
9952 extract_address(to);
9953 account = account_find_from_address(to);
9956 if (!account && prefs_common.forward_account_autosel) {
9958 if (!procheader_get_header_from_msginfo
9959 (msginfo, cc,sizeof cc , "Cc:")) {
9960 gchar *buf = cc + strlen("Cc:");
9961 extract_address(buf);
9962 account = account_find_from_address(buf);
9966 if (!account && prefs_common.forward_account_autosel) {
9967 gchar deliveredto[BUFFSIZE];
9968 if (!procheader_get_header_from_msginfo
9969 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
9970 gchar *buf = deliveredto + strlen("Delivered-To:");
9971 extract_address(buf);
9972 account = account_find_from_address(buf);
9979 gboolean compose_close(Compose *compose)
9983 if (!g_mutex_trylock(compose->mutex)) {
9984 /* we have to wait for the (possibly deferred by auto-save)
9985 * drafting to be done, before destroying the compose under
9987 debug_print("waiting for drafting to finish...\n");
9988 compose_allow_user_actions(compose, FALSE);
9989 g_timeout_add (500, (GSourceFunc) compose_close, compose);
9992 g_return_val_if_fail(compose, FALSE);
9993 gtkut_widget_get_uposition(compose->window, &x, &y);
9994 prefs_common.compose_x = x;
9995 prefs_common.compose_y = y;
9996 g_mutex_unlock(compose->mutex);
9997 compose_destroy(compose);
10002 * Add entry field for each address in list.
10003 * \param compose E-Mail composition object.
10004 * \param listAddress List of (formatted) E-Mail addresses.
10006 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
10009 node = listAddress;
10011 addr = ( gchar * ) node->data;
10012 compose_entry_append( compose, addr, COMPOSE_TO );
10013 node = g_list_next( node );
10017 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
10018 guint action, gboolean opening_multiple)
10020 gchar *body = NULL;
10021 GSList *new_msglist = NULL;
10022 MsgInfo *tmp_msginfo = NULL;
10023 gboolean originally_enc = FALSE;
10024 Compose *compose = NULL;
10026 g_return_if_fail(msgview != NULL);
10028 g_return_if_fail(msginfo_list != NULL);
10030 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
10031 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
10032 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
10034 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
10035 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
10036 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
10037 orig_msginfo, mimeinfo);
10038 if (tmp_msginfo != NULL) {
10039 new_msglist = g_slist_append(NULL, tmp_msginfo);
10041 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
10042 tmp_msginfo->folder = orig_msginfo->folder;
10043 tmp_msginfo->msgnum = orig_msginfo->msgnum;
10044 if (orig_msginfo->tags)
10045 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
10050 if (!opening_multiple)
10051 body = messageview_get_selection(msgview);
10054 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
10055 procmsg_msginfo_free(tmp_msginfo);
10056 g_slist_free(new_msglist);
10058 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
10060 if (compose && originally_enc) {
10061 compose_force_encryption(compose, compose->account, FALSE);
10067 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
10070 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
10071 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
10072 GSList *cur = msginfo_list;
10073 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
10074 "messages. Opening the windows "
10075 "could take some time. Do you "
10076 "want to continue?"),
10077 g_slist_length(msginfo_list));
10078 if (g_slist_length(msginfo_list) > 9
10079 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
10080 != G_ALERTALTERNATE) {
10085 /* We'll open multiple compose windows */
10086 /* let the WM place the next windows */
10087 compose_force_window_origin = FALSE;
10088 for (; cur; cur = cur->next) {
10090 tmplist.data = cur->data;
10091 tmplist.next = NULL;
10092 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
10094 compose_force_window_origin = TRUE;
10096 /* forwarding multiple mails as attachments is done via a
10097 * single compose window */
10098 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
10102 void compose_set_position(Compose *compose, gint pos)
10104 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10106 gtkut_text_view_set_position(text, pos);
10109 gboolean compose_search_string(Compose *compose,
10110 const gchar *str, gboolean case_sens)
10112 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10114 return gtkut_text_view_search_string(text, str, case_sens);
10117 gboolean compose_search_string_backward(Compose *compose,
10118 const gchar *str, gboolean case_sens)
10120 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10122 return gtkut_text_view_search_string_backward(text, str, case_sens);
10125 /* allocate a msginfo structure and populate its data from a compose data structure */
10126 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
10128 MsgInfo *newmsginfo;
10130 gchar buf[BUFFSIZE];
10132 g_return_val_if_fail( compose != NULL, NULL );
10134 newmsginfo = procmsg_msginfo_new();
10137 get_rfc822_date(buf, sizeof(buf));
10138 newmsginfo->date = g_strdup(buf);
10141 if (compose->from_name) {
10142 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
10143 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
10147 if (compose->subject_entry)
10148 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
10150 /* to, cc, reply-to, newsgroups */
10151 for (list = compose->header_list; list; list = list->next) {
10152 gchar *header = gtk_editable_get_chars(
10154 GTK_BIN(((ComposeHeaderEntry *)list->data)->combo)->child), 0, -1);
10155 gchar *entry = gtk_editable_get_chars(
10156 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
10158 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
10159 if ( newmsginfo->to == NULL ) {
10160 newmsginfo->to = g_strdup(entry);
10161 } else if (entry && *entry) {
10162 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
10163 g_free(newmsginfo->to);
10164 newmsginfo->to = tmp;
10167 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
10168 if ( newmsginfo->cc == NULL ) {
10169 newmsginfo->cc = g_strdup(entry);
10170 } else if (entry && *entry) {
10171 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
10172 g_free(newmsginfo->cc);
10173 newmsginfo->cc = tmp;
10176 if ( strcasecmp(header,
10177 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
10178 if ( newmsginfo->newsgroups == NULL ) {
10179 newmsginfo->newsgroups = g_strdup(entry);
10180 } else if (entry && *entry) {
10181 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
10182 g_free(newmsginfo->newsgroups);
10183 newmsginfo->newsgroups = tmp;
10191 /* other data is unset */
10197 /* update compose's dictionaries from folder dict settings */
10198 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
10199 FolderItem *folder_item)
10201 g_return_if_fail(compose != NULL);
10203 if (compose->gtkaspell && folder_item && folder_item->prefs) {
10204 FolderItemPrefs *prefs = folder_item->prefs;
10206 if (prefs->enable_default_dictionary)
10207 gtkaspell_change_dict(compose->gtkaspell,
10208 prefs->default_dictionary, FALSE);
10209 if (folder_item->prefs->enable_default_alt_dictionary)
10210 gtkaspell_change_alt_dict(compose->gtkaspell,
10211 prefs->default_alt_dictionary);
10212 if (prefs->enable_default_dictionary
10213 || prefs->enable_default_alt_dictionary)
10214 compose_spell_menu_changed(compose);