2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2007 Hiroyuki Yamamoto and the Claws Mail team
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
26 #ifndef PANGO_ENABLE_ENGINE
27 # define PANGO_ENABLE_ENGINE
31 #include <glib/gi18n.h>
32 #include <gdk/gdkkeysyms.h>
33 #include <gtk/gtkmain.h>
34 #include <gtk/gtkmenu.h>
35 #include <gtk/gtkmenuitem.h>
36 #include <gtk/gtkitemfactory.h>
37 #include <gtk/gtkcheckmenuitem.h>
38 #include <gtk/gtkoptionmenu.h>
39 #include <gtk/gtkwidget.h>
40 #include <gtk/gtkvpaned.h>
41 #include <gtk/gtkentry.h>
42 #include <gtk/gtkeditable.h>
43 #include <gtk/gtkwindow.h>
44 #include <gtk/gtksignal.h>
45 #include <gtk/gtkvbox.h>
46 #include <gtk/gtkcontainer.h>
47 #include <gtk/gtkhandlebox.h>
48 #include <gtk/gtktoolbar.h>
49 #include <gtk/gtktable.h>
50 #include <gtk/gtkhbox.h>
51 #include <gtk/gtklabel.h>
52 #include <gtk/gtkscrolledwindow.h>
53 #include <gtk/gtktreeview.h>
54 #include <gtk/gtkliststore.h>
55 #include <gtk/gtktreeselection.h>
56 #include <gtk/gtktreemodel.h>
58 #include <gtk/gtkdnd.h>
59 #include <gtk/gtkclipboard.h>
60 #include <pango/pango-break.h>
65 #include <sys/types.h>
71 # include <sys/wait.h>
75 #ifndef G_OS_WIN32 /* fixme we should have a configure test. */
79 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
86 #include "mainwindow.h"
88 #include "addressbook.h"
89 #include "folderview.h"
92 #include "stock_pixmap.h"
93 #include "send_message.h"
96 #include "customheader.h"
97 #include "prefs_common.h"
98 #include "prefs_account.h"
102 #include "procheader.h"
103 #include "procmime.h"
104 #include "statusbar.h"
107 #include "quoted-printable.h"
108 #include "codeconv.h"
110 #include "gtkutils.h"
112 #include "alertpanel.h"
113 #include "manage_window.h"
114 #include "gtkshruler.h"
116 #include "addr_compl.h"
117 #include "quote_fmt.h"
119 #include "foldersel.h"
122 #include "message_search.h"
123 #include "combobox.h"
138 #define N_ATTACH_COLS (N_COL_COLUMNS)
142 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE,
143 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER,
144 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER,
145 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD,
146 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD,
147 COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE,
148 COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE,
149 COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE,
150 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER,
151 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER,
152 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD,
153 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD,
154 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE,
155 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE_N,
156 COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END
157 } ComposeCallAdvancedAction;
161 PRIORITY_HIGHEST = 1,
170 COMPOSE_INSERT_SUCCESS,
171 COMPOSE_INSERT_READ_ERROR,
172 COMPOSE_INSERT_INVALID_CHARACTER,
173 COMPOSE_INSERT_NO_FILE
174 } ComposeInsertResult;
178 COMPOSE_WRITE_FOR_SEND,
179 COMPOSE_WRITE_FOR_STORE
184 COMPOSE_QUOTE_FORCED,
189 #define B64_LINE_SIZE 57
190 #define B64_BUFFSIZE 77
192 #define MAX_REFERENCES_LEN 999
194 static GList *compose_list = NULL;
196 static Compose *compose_generic_new (PrefsAccount *account,
199 GPtrArray *attach_files,
200 GList *listAddress );
202 static Compose *compose_create (PrefsAccount *account,
207 static void compose_entry_mark_default_to (Compose *compose,
208 const gchar *address);
209 static Compose *compose_followup_and_reply_to (MsgInfo *msginfo,
210 ComposeQuoteMode quote_mode,
214 static Compose *compose_forward_multiple (PrefsAccount *account,
215 GSList *msginfo_list);
216 static Compose *compose_reply (MsgInfo *msginfo,
217 ComposeQuoteMode quote_mode,
222 static Compose *compose_reply_mode (ComposeMode mode,
223 GSList *msginfo_list,
225 static void compose_template_apply_fields(Compose *compose, Template *tmpl);
226 static void compose_update_privacy_systems_menu(Compose *compose);
228 static GtkWidget *compose_account_option_menu_create
230 static void compose_set_out_encoding (Compose *compose);
231 static void compose_set_template_menu (Compose *compose);
232 static void compose_template_apply (Compose *compose,
235 static void compose_destroy (Compose *compose);
237 static void compose_entries_set (Compose *compose,
238 const gchar *mailto);
239 static gint compose_parse_header (Compose *compose,
241 static gchar *compose_parse_references (const gchar *ref,
244 static gchar *compose_quote_fmt (Compose *compose,
250 gboolean need_unescape,
251 const gchar *err_msg);
253 static void compose_reply_set_entry (Compose *compose,
259 followup_and_reply_to);
260 static void compose_reedit_set_entry (Compose *compose,
263 static void compose_insert_sig (Compose *compose,
265 static gchar *compose_get_signature_str (Compose *compose);
266 static ComposeInsertResult compose_insert_file (Compose *compose,
269 static gboolean compose_attach_append (Compose *compose,
272 const gchar *content_type);
273 static void compose_attach_parts (Compose *compose,
276 static gboolean compose_beautify_paragraph (Compose *compose,
277 GtkTextIter *par_iter,
279 static void compose_wrap_all (Compose *compose);
280 static void compose_wrap_all_full (Compose *compose,
283 static void compose_set_title (Compose *compose);
284 static void compose_select_account (Compose *compose,
285 PrefsAccount *account,
288 static PrefsAccount *compose_current_mail_account(void);
289 /* static gint compose_send (Compose *compose); */
290 static gboolean compose_check_for_valid_recipient
292 static gboolean compose_check_entries (Compose *compose,
293 gboolean check_everything);
294 static gint compose_write_to_file (Compose *compose,
297 gboolean attach_parts);
298 static gint compose_write_body_to_file (Compose *compose,
300 static gint compose_remove_reedit_target (Compose *compose,
302 static void compose_remove_draft (Compose *compose);
303 static gint compose_queue_sub (Compose *compose,
307 gboolean check_subject,
308 gboolean remove_reedit_target);
309 static void compose_add_attachments (Compose *compose,
311 static gchar *compose_get_header (Compose *compose);
313 static void compose_convert_header (Compose *compose,
318 gboolean addr_field);
320 static void compose_attach_info_free (AttachInfo *ainfo);
321 static void compose_attach_remove_selected (Compose *compose);
323 static void compose_attach_property (Compose *compose);
324 static void compose_attach_property_create (gboolean *cancelled);
325 static void attach_property_ok (GtkWidget *widget,
326 gboolean *cancelled);
327 static void attach_property_cancel (GtkWidget *widget,
328 gboolean *cancelled);
329 static gint attach_property_delete_event (GtkWidget *widget,
331 gboolean *cancelled);
332 static gboolean attach_property_key_pressed (GtkWidget *widget,
334 gboolean *cancelled);
336 static void compose_exec_ext_editor (Compose *compose);
338 static gint compose_exec_ext_editor_real (const gchar *file);
339 static gboolean compose_ext_editor_kill (Compose *compose);
340 static gboolean compose_input_cb (GIOChannel *source,
341 GIOCondition condition,
343 static void compose_set_ext_editor_sensitive (Compose *compose,
345 #endif /* G_OS_UNIX */
347 static void compose_undo_state_changed (UndoMain *undostruct,
352 static void compose_create_header_entry (Compose *compose);
353 static void compose_add_header_entry (Compose *compose, const gchar *header, gchar *text);
354 static void compose_remove_header_entries(Compose *compose);
356 static void compose_update_priority_menu_item(Compose * compose);
358 static void compose_spell_menu_changed (void *data);
360 static void compose_add_field_list ( Compose *compose,
361 GList *listAddress );
363 /* callback functions */
365 static gboolean compose_edit_size_alloc (GtkEditable *widget,
366 GtkAllocation *allocation,
367 GtkSHRuler *shruler);
368 static void account_activated (GtkComboBox *optmenu,
370 static void attach_selected (GtkTreeView *tree_view,
371 GtkTreePath *tree_path,
372 GtkTreeViewColumn *column,
374 static gboolean attach_button_pressed (GtkWidget *widget,
375 GdkEventButton *event,
377 static gboolean attach_key_pressed (GtkWidget *widget,
380 static void compose_send_cb (gpointer data,
383 static void compose_send_later_cb (gpointer data,
387 static void compose_draft_cb (gpointer data,
391 static void compose_attach_cb (gpointer data,
394 static void compose_insert_file_cb (gpointer data,
397 static void compose_insert_sig_cb (gpointer data,
401 static void compose_close_cb (gpointer data,
405 static void compose_set_encoding_cb (gpointer data,
409 static void compose_address_cb (gpointer data,
412 static void compose_template_activate_cb(GtkWidget *widget,
415 static void compose_ext_editor_cb (gpointer data,
419 static gint compose_delete_cb (GtkWidget *widget,
423 static void compose_undo_cb (Compose *compose);
424 static void compose_redo_cb (Compose *compose);
425 static void compose_cut_cb (Compose *compose);
426 static void compose_copy_cb (Compose *compose);
427 static void compose_paste_cb (Compose *compose);
428 static void compose_paste_as_quote_cb (Compose *compose);
429 static void compose_paste_no_wrap_cb (Compose *compose);
430 static void compose_paste_wrap_cb (Compose *compose);
431 static void compose_allsel_cb (Compose *compose);
433 static void compose_advanced_action_cb (Compose *compose,
434 ComposeCallAdvancedAction action);
436 static void compose_grab_focus_cb (GtkWidget *widget,
439 static void compose_changed_cb (GtkTextBuffer *textbuf,
442 static void compose_wrap_cb (gpointer data,
445 static void compose_find_cb (gpointer data,
448 static void compose_toggle_autowrap_cb (gpointer data,
452 static void compose_toggle_ruler_cb (gpointer data,
455 static void compose_toggle_sign_cb (gpointer data,
458 static void compose_toggle_encrypt_cb (gpointer data,
461 static void compose_set_privacy_system_cb(GtkWidget *widget,
463 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn);
464 static void activate_privacy_system (Compose *compose,
465 PrefsAccount *account,
467 static void compose_use_signing(Compose *compose, gboolean use_signing);
468 static void compose_use_encryption(Compose *compose, gboolean use_encryption);
469 static void compose_toggle_return_receipt_cb(gpointer data, guint action,
471 static void compose_toggle_remove_refs_cb(gpointer data, guint action,
473 static void compose_set_priority_cb (gpointer data,
476 static void compose_reply_change_mode (gpointer data,
480 static void compose_attach_drag_received_cb (GtkWidget *widget,
481 GdkDragContext *drag_context,
484 GtkSelectionData *data,
488 static void compose_insert_drag_received_cb (GtkWidget *widget,
489 GdkDragContext *drag_context,
492 GtkSelectionData *data,
496 static void compose_header_drag_received_cb (GtkWidget *widget,
497 GdkDragContext *drag_context,
500 GtkSelectionData *data,
505 static gboolean compose_drag_drop (GtkWidget *widget,
506 GdkDragContext *drag_context,
508 guint time, gpointer user_data);
510 static void text_inserted (GtkTextBuffer *buffer,
515 static Compose *compose_generic_reply(MsgInfo *msginfo,
516 ComposeQuoteMode quote_mode,
520 gboolean followup_and_reply_to,
523 static gboolean compose_headerentry_changed_cb (GtkWidget *entry,
524 ComposeHeaderEntry *headerentry);
525 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
527 ComposeHeaderEntry *headerentry);
529 static void compose_show_first_last_header (Compose *compose, gboolean show_first);
531 static void compose_allow_user_actions (Compose *compose, gboolean allow);
534 static void compose_check_all (Compose *compose);
535 static void compose_highlight_all (Compose *compose);
536 static void compose_check_backwards (Compose *compose);
537 static void compose_check_forwards_go (Compose *compose);
540 static gint compose_defer_auto_save_draft (Compose *compose);
541 static PrefsAccount *compose_guess_forward_account_from_msginfo (MsgInfo *msginfo);
543 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose);
546 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
547 FolderItem *folder_item);
550 static GtkItemFactoryEntry compose_popup_entries[] =
552 {N_("/_Add..."), NULL, compose_attach_cb, 0, NULL},
553 {N_("/_Remove"), NULL, compose_attach_remove_selected, 0, NULL},
554 {"/---", NULL, NULL, 0, "<Separator>"},
555 {N_("/_Properties..."), NULL, compose_attach_property, 0, NULL}
558 static GtkItemFactoryEntry compose_entries[] =
560 {N_("/_Message"), NULL, NULL, 0, "<Branch>"},
561 {N_("/_Message/S_end"), "<control>Return",
562 compose_send_cb, 0, NULL},
563 {N_("/_Message/Send _later"), "<shift><control>S",
564 compose_send_later_cb, 0, NULL},
565 {N_("/_Message/---"), NULL, NULL, 0, "<Separator>"},
566 {N_("/_Message/_Attach file"), "<control>M", compose_attach_cb, 0, NULL},
567 {N_("/_Message/_Insert file"), "<control>I", compose_insert_file_cb, 0, NULL},
568 {N_("/_Message/Insert si_gnature"), "<control>G", compose_insert_sig_cb, 0, NULL},
569 {N_("/_Message/---"), NULL, NULL, 0, "<Separator>"},
570 {N_("/_Message/_Save"),
571 "<control>S", compose_draft_cb, COMPOSE_KEEP_EDITING, NULL},
572 {N_("/_Message/---"), NULL, NULL, 0, "<Separator>"},
573 {N_("/_Message/_Close"), "<control>W", compose_close_cb, 0, NULL},
575 {N_("/_Edit"), NULL, NULL, 0, "<Branch>"},
576 {N_("/_Edit/_Undo"), "<control>Z", compose_undo_cb, 0, NULL},
577 {N_("/_Edit/_Redo"), "<control>Y", compose_redo_cb, 0, NULL},
578 {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
579 {N_("/_Edit/Cu_t"), "<control>X", compose_cut_cb, 0, NULL},
580 {N_("/_Edit/_Copy"), "<control>C", compose_copy_cb, 0, NULL},
581 {N_("/_Edit/_Paste"), "<control>V", compose_paste_cb, 0, NULL},
582 {N_("/_Edit/Special paste"), NULL, NULL, 0, "<Branch>"},
583 {N_("/_Edit/Special paste/as _quotation"),
584 NULL, compose_paste_as_quote_cb, 0, NULL},
585 {N_("/_Edit/Special paste/_wrapped"),
586 NULL, compose_paste_wrap_cb, 0, NULL},
587 {N_("/_Edit/Special paste/_unwrapped"),
588 NULL, compose_paste_no_wrap_cb, 0, NULL},
589 {N_("/_Edit/Select _all"), "<control>A", compose_allsel_cb, 0, NULL},
590 {N_("/_Edit/A_dvanced"), NULL, NULL, 0, "<Branch>"},
591 {N_("/_Edit/A_dvanced/Move a character backward"),
593 compose_advanced_action_cb,
594 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER,
596 {N_("/_Edit/A_dvanced/Move a character forward"),
598 compose_advanced_action_cb,
599 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER,
601 {N_("/_Edit/A_dvanced/Move a word backward"),
603 compose_advanced_action_cb,
604 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD,
606 {N_("/_Edit/A_dvanced/Move a word forward"),
608 compose_advanced_action_cb,
609 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD,
611 {N_("/_Edit/A_dvanced/Move to beginning of line"),
612 NULL, /* "<control>A" */
613 compose_advanced_action_cb,
614 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE,
616 {N_("/_Edit/A_dvanced/Move to end of line"),
618 compose_advanced_action_cb,
619 COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE,
621 {N_("/_Edit/A_dvanced/Move to previous line"),
623 compose_advanced_action_cb,
624 COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE,
626 {N_("/_Edit/A_dvanced/Move to next line"),
628 compose_advanced_action_cb,
629 COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE,
631 {N_("/_Edit/A_dvanced/Delete a character backward"),
633 compose_advanced_action_cb,
634 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER,
636 {N_("/_Edit/A_dvanced/Delete a character forward"),
638 compose_advanced_action_cb,
639 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER,
641 {N_("/_Edit/A_dvanced/Delete a word backward"),
642 NULL, /* "<control>W" */
643 compose_advanced_action_cb,
644 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD,
646 {N_("/_Edit/A_dvanced/Delete a word forward"),
647 NULL, /* "<alt>D", */
648 compose_advanced_action_cb,
649 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD,
651 {N_("/_Edit/A_dvanced/Delete line"),
653 compose_advanced_action_cb,
654 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE,
656 {N_("/_Edit/A_dvanced/Delete entire line"),
658 compose_advanced_action_cb,
659 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE_N,
661 {N_("/_Edit/A_dvanced/Delete to end of line"),
663 compose_advanced_action_cb,
664 COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END,
666 {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
668 "<control>F", compose_find_cb, 0, NULL},
669 {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
670 {N_("/_Edit/_Wrap current paragraph"),
671 "<control>L", compose_wrap_cb, 0, NULL},
672 {N_("/_Edit/Wrap all long _lines"),
673 "<control><alt>L", compose_wrap_cb, 1, NULL},
674 {N_("/_Edit/Aut_o wrapping"), "<shift><control>L", compose_toggle_autowrap_cb, 0, "<ToggleItem>"},
675 {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
676 {N_("/_Edit/Edit with e_xternal editor"),
677 "<shift><control>X", compose_ext_editor_cb, 0, NULL},
679 {N_("/_Spelling"), NULL, NULL, 0, "<Branch>"},
680 {N_("/_Spelling/_Check all or check selection"),
681 NULL, compose_check_all, 0, NULL},
682 {N_("/_Spelling/_Highlight all misspelled words"),
683 NULL, compose_highlight_all, 0, NULL},
684 {N_("/_Spelling/Check _backwards misspelled word"),
685 NULL, compose_check_backwards , 0, NULL},
686 {N_("/_Spelling/_Forward to next misspelled word"),
687 NULL, compose_check_forwards_go, 0, NULL},
688 {N_("/_Spelling/---"), NULL, NULL, 0, "<Separator>"},
689 {N_("/_Spelling/Options"),
690 NULL, NULL, 0, "<Branch>"},
692 {N_("/_Options"), NULL, NULL, 0, "<Branch>"},
693 {N_("/_Options/Reply _mode"), NULL, NULL, 0, "<Branch>"},
694 {N_("/_Options/Reply _mode/_Normal"), NULL, compose_reply_change_mode, COMPOSE_REPLY, "<RadioItem>"},
695 {N_("/_Options/Reply _mode/_All"), NULL, compose_reply_change_mode, COMPOSE_REPLY_TO_ALL, "/Options/Reply mode/Normal"},
696 {N_("/_Options/Reply _mode/_Sender"), NULL, compose_reply_change_mode, COMPOSE_REPLY_TO_SENDER, "/Options/Reply mode/Normal"},
697 {N_("/_Options/Reply _mode/_Mailing-list"), NULL, compose_reply_change_mode, COMPOSE_REPLY_TO_LIST, "/Options/Reply mode/Normal"},
698 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
699 {N_("/_Options/Privacy _System"), NULL, NULL, 0, "<Branch>"},
700 {N_("/_Options/Privacy _System/None"), NULL, NULL, 0, "<RadioItem>"},
701 {N_("/_Options/Si_gn"), NULL, compose_toggle_sign_cb , 0, "<ToggleItem>"},
702 {N_("/_Options/_Encrypt"), NULL, compose_toggle_encrypt_cb, 0, "<ToggleItem>"},
703 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
704 {N_("/_Options/_Priority"), NULL, NULL, 0, "<Branch>"},
705 {N_("/_Options/Priority/_Highest"), NULL, compose_set_priority_cb, PRIORITY_HIGHEST, "<RadioItem>"},
706 {N_("/_Options/Priority/Hi_gh"), NULL, compose_set_priority_cb, PRIORITY_HIGH, "/Options/Priority/Highest"},
707 {N_("/_Options/Priority/_Normal"), NULL, compose_set_priority_cb, PRIORITY_NORMAL, "/Options/Priority/Highest"},
708 {N_("/_Options/Priority/Lo_w"), NULL, compose_set_priority_cb, PRIORITY_LOW, "/Options/Priority/Highest"},
709 {N_("/_Options/Priority/_Lowest"), NULL, compose_set_priority_cb, PRIORITY_LOWEST, "/Options/Priority/Highest"},
710 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
711 {N_("/_Options/_Request Return Receipt"), NULL, compose_toggle_return_receipt_cb, 0, "<ToggleItem>"},
712 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
713 {N_("/_Options/Remo_ve references"), NULL, compose_toggle_remove_refs_cb, 0, "<ToggleItem>"},
714 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
716 #define ENC_ACTION(action) \
717 NULL, compose_set_encoding_cb, action, \
718 "/Options/Character encoding/Automatic"
720 {N_("/_Options/Character _encoding"), NULL, NULL, 0, "<Branch>"},
721 {N_("/_Options/Character _encoding/_Automatic"),
722 NULL, compose_set_encoding_cb, C_AUTO, "<RadioItem>"},
723 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
725 {N_("/_Options/Character _encoding/7bit ascii (US-ASC_II)"),
726 ENC_ACTION(C_US_ASCII)},
727 {N_("/_Options/Character _encoding/Unicode (_UTF-8)"),
728 ENC_ACTION(C_UTF_8)},
729 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
731 {N_("/_Options/Character _encoding/Western European (ISO-8859-_1)"),
732 ENC_ACTION(C_ISO_8859_1)},
733 {N_("/_Options/Character _encoding/Western European (ISO-8859-15)"),
734 ENC_ACTION(C_ISO_8859_15)},
735 {N_("/_Options/Character _encoding/Western European (Windows-1252)"),
736 ENC_ACTION(C_WINDOWS_1252)},
737 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
739 {N_("/_Options/Character _encoding/Central European (ISO-8859-_2)"),
740 ENC_ACTION(C_ISO_8859_2)},
741 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
743 {N_("/_Options/Character _encoding/_Baltic (ISO-8859-13)"),
744 ENC_ACTION(C_ISO_8859_13)},
745 {N_("/_Options/Character _encoding/Baltic (ISO-8859-_4)"),
746 ENC_ACTION(C_ISO_8859_4)},
747 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
749 {N_("/_Options/Character _encoding/Greek (ISO-8859-_7)"),
750 ENC_ACTION(C_ISO_8859_7)},
751 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
753 {N_("/_Options/Character _encoding/Hebrew (ISO-8859-_8)"),
754 ENC_ACTION(C_ISO_8859_8)},
755 {N_("/_Options/Character _encoding/Hebrew (Windows-1255)"),
756 ENC_ACTION(C_WINDOWS_1255)},
757 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
759 {N_("/_Options/Character _encoding/Arabic (ISO-8859-_6)"),
760 ENC_ACTION(C_ISO_8859_6)},
761 {N_("/_Options/Character _encoding/Arabic (Windows-1256)"),
762 ENC_ACTION(C_CP1256)},
763 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
765 {N_("/_Options/Character _encoding/Turkish (ISO-8859-_9)"),
766 ENC_ACTION(C_ISO_8859_9)},
767 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
769 {N_("/_Options/Character _encoding/Cyrillic (ISO-8859-_5)"),
770 ENC_ACTION(C_ISO_8859_5)},
771 {N_("/_Options/Character _encoding/Cyrillic (KOI8-_R)"),
772 ENC_ACTION(C_KOI8_R)},
773 {N_("/_Options/Character _encoding/Cyrillic (KOI8-U)"),
774 ENC_ACTION(C_KOI8_U)},
775 {N_("/_Options/Character _encoding/Cyrillic (Windows-1251)"),
776 ENC_ACTION(C_WINDOWS_1251)},
777 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
779 {N_("/_Options/Character _encoding/Japanese (ISO-2022-_JP)"),
780 ENC_ACTION(C_ISO_2022_JP)},
781 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
783 {N_("/_Options/Character _encoding/Simplified Chinese (_GB2312)"),
784 ENC_ACTION(C_GB2312)},
785 {N_("/_Options/Character _encoding/Simplified Chinese (GBK)"),
787 {N_("/_Options/Character _encoding/Traditional Chinese (_Big5)"),
789 {N_("/_Options/Character _encoding/Traditional Chinese (EUC-_TW)"),
790 ENC_ACTION(C_EUC_TW)},
791 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
793 {N_("/_Options/Character _encoding/Korean (EUC-_KR)"),
794 ENC_ACTION(C_EUC_KR)},
795 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
797 {N_("/_Options/Character _encoding/Thai (TIS-620)"),
798 ENC_ACTION(C_TIS_620)},
799 {N_("/_Options/Character _encoding/Thai (Windows-874)"),
800 ENC_ACTION(C_WINDOWS_874)},
802 {N_("/_Tools"), NULL, NULL, 0, "<Branch>"},
803 {N_("/_Tools/Show _ruler"), NULL, compose_toggle_ruler_cb, 0, "<ToggleItem>"},
804 {N_("/_Tools/_Address book"), "<shift><control>A", compose_address_cb , 0, NULL},
805 {N_("/_Tools/_Template"), NULL, NULL, 0, "<Branch>"},
806 {N_("/_Tools/Actio_ns"), NULL, NULL, 0, "<Branch>"},
807 {N_("/_Help"), NULL, NULL, 0, "<Branch>"},
808 {N_("/_Help/_About"), NULL, about_show, 0, NULL}
811 static GtkTargetEntry compose_mime_types[] =
813 {"text/uri-list", 0, 0},
814 {"UTF8_STRING", 0, 0},
818 static gboolean compose_put_existing_to_front(MsgInfo *info)
820 GList *compose_list = compose_get_compose_list();
824 for (elem = compose_list; elem != NULL && elem->data != NULL;
826 Compose *c = (Compose*)elem->data;
828 if (!c->targetinfo || !c->targetinfo->msgid ||
832 if (!strcmp(c->targetinfo->msgid, info->msgid)) {
833 gtkut_window_popup(c->window);
841 static GdkColor quote_color1 =
842 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
843 static GdkColor quote_color2 =
844 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
845 static GdkColor quote_color3 =
846 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
848 static GdkColor quote_bgcolor1 =
849 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
850 static GdkColor quote_bgcolor2 =
851 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
852 static GdkColor quote_bgcolor3 =
853 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
855 static GdkColor signature_color = {
862 static GdkColor uri_color = {
869 static void compose_create_tags(GtkTextView *text, Compose *compose)
871 GtkTextBuffer *buffer;
872 GdkColor black = {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
878 buffer = gtk_text_view_get_buffer(text);
880 if (prefs_common.enable_color) {
881 /* grab the quote colors, converting from an int to a GdkColor */
882 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
884 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
886 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
888 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_bgcol,
890 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_bgcol,
892 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_bgcol,
894 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
896 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
899 signature_color = quote_color1 = quote_color2 = quote_color3 =
900 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
903 if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
904 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
905 "foreground-gdk", "e_color1,
906 "paragraph-background-gdk", "e_bgcolor1,
908 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
909 "foreground-gdk", "e_color2,
910 "paragraph-background-gdk", "e_bgcolor2,
912 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
913 "foreground-gdk", "e_color3,
914 "paragraph-background-gdk", "e_bgcolor3,
917 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
918 "foreground-gdk", "e_color1,
920 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
921 "foreground-gdk", "e_color2,
923 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
924 "foreground-gdk", "e_color3,
928 gtk_text_buffer_create_tag(buffer, "signature",
929 "foreground-gdk", &signature_color,
932 compose->uri_tag = gtk_text_buffer_create_tag(buffer, "link",
933 "foreground-gdk", &uri_color,
935 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
936 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
938 color[0] = quote_color1;
939 color[1] = quote_color2;
940 color[2] = quote_color3;
941 color[3] = quote_bgcolor1;
942 color[4] = quote_bgcolor2;
943 color[5] = quote_bgcolor3;
944 color[6] = signature_color;
945 color[7] = uri_color;
946 cmap = gdk_drawable_get_colormap(compose->window->window);
947 gdk_colormap_alloc_colors(cmap, color, 8, FALSE, TRUE, success);
949 for (i = 0; i < 8; i++) {
950 if (success[i] == FALSE) {
953 g_warning("Compose: color allocation failed.\n");
954 style = gtk_widget_get_style(GTK_WIDGET(text));
955 quote_color1 = quote_color2 = quote_color3 =
956 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 =
957 signature_color = uri_color = black;
962 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
963 GPtrArray *attach_files)
965 return compose_generic_new(account, mailto, NULL, attach_files, NULL);
968 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
970 return compose_generic_new(account, mailto, item, NULL, NULL);
973 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
975 return compose_generic_new( account, NULL, NULL, NULL, listAddress );
978 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
979 GPtrArray *attach_files, GList *listAddress )
982 GtkTextView *textview;
983 GtkTextBuffer *textbuf;
985 GtkItemFactory *ifactory;
986 const gchar *subject_format = NULL;
987 const gchar *body_format = NULL;
989 if (item && item->prefs && item->prefs->enable_default_account)
990 account = account_find_from_id(item->prefs->default_account);
992 if (!account) account = cur_account;
993 g_return_val_if_fail(account != NULL, NULL);
995 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
997 ifactory = gtk_item_factory_from_widget(compose->menubar);
999 compose->replyinfo = NULL;
1000 compose->fwdinfo = NULL;
1002 textview = GTK_TEXT_VIEW(compose->text);
1003 textbuf = gtk_text_view_get_buffer(textview);
1004 compose_create_tags(textview, compose);
1006 undo_block(compose->undostruct);
1008 compose_set_dictionaries_from_folder_prefs(compose, item);
1011 if (account->auto_sig)
1012 compose_insert_sig(compose, FALSE);
1013 gtk_text_buffer_get_start_iter(textbuf, &iter);
1014 gtk_text_buffer_place_cursor(textbuf, &iter);
1016 if (account->protocol != A_NNTP) {
1017 if (mailto && *mailto != '\0') {
1018 compose_entries_set(compose, mailto);
1020 } else if (item && item->prefs->enable_default_to) {
1021 compose_entry_append(compose, item->prefs->default_to, COMPOSE_TO);
1022 compose_entry_mark_default_to(compose, item->prefs->default_to);
1024 if (item && item->ret_rcpt) {
1025 menu_set_active(ifactory, "/Options/Request Return Receipt", TRUE);
1029 compose_entry_append(compose, mailto, COMPOSE_NEWSGROUPS);
1030 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1031 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS);
1034 * CLAWS: just don't allow return receipt request, even if the user
1035 * may want to send an email. simple but foolproof.
1037 menu_set_sensitive(ifactory, "/Options/Request Return Receipt", FALSE);
1039 compose_add_field_list( compose, listAddress );
1041 if (item && item->prefs && item->prefs->compose_with_format) {
1042 subject_format = item->prefs->compose_subject_format;
1043 body_format = item->prefs->compose_body_format;
1044 } else if (account->compose_with_format) {
1045 subject_format = account->compose_subject_format;
1046 body_format = account->compose_body_format;
1047 } else if (prefs_common.compose_with_format) {
1048 subject_format = prefs_common.compose_subject_format;
1049 body_format = prefs_common.compose_body_format;
1052 if (subject_format || body_format) {
1053 MsgInfo* dummyinfo = NULL;
1056 && *subject_format != '\0' )
1058 gchar *subject = NULL;
1062 dummyinfo = compose_msginfo_new_from_compose(compose);
1064 /* decode \-escape sequences in the internal representation of the quote format */
1065 tmp = malloc(strlen(subject_format)+1);
1066 pref_get_unescaped_pref(tmp, subject_format);
1068 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1070 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account,
1071 compose->gtkaspell);
1073 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account);
1075 quote_fmt_scan_string(tmp);
1078 buf = quote_fmt_get_buffer();
1080 alertpanel_error(_("New message subject format error."));
1082 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1083 quote_fmt_reset_vartable();
1090 && *body_format != '\0' )
1093 GtkTextBuffer *buffer;
1094 GtkTextIter start, end;
1097 if ( dummyinfo == NULL )
1098 dummyinfo = compose_msginfo_new_from_compose(compose);
1100 text = GTK_TEXT_VIEW(compose->text);
1101 buffer = gtk_text_view_get_buffer(text);
1102 gtk_text_buffer_get_start_iter(buffer, &start);
1103 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1104 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1106 compose_quote_fmt(compose, dummyinfo,
1108 NULL, tmp, FALSE, TRUE,
1109 _("New message body format error at line %d."));
1110 quote_fmt_reset_vartable();
1115 procmsg_msginfo_free( dummyinfo );
1122 for (i = 0; i < attach_files->len; i++) {
1123 file = g_ptr_array_index(attach_files, i);
1124 compose_attach_append(compose, file, file, NULL);
1128 compose_show_first_last_header(compose, TRUE);
1130 /* Set save folder */
1131 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1132 gchar *folderidentifier;
1134 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
1135 folderidentifier = folder_item_get_identifier(item);
1136 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
1137 g_free(folderidentifier);
1140 gtk_widget_grab_focus(compose->header_last->entry);
1142 undo_unblock(compose->undostruct);
1144 if (prefs_common.auto_exteditor)
1145 compose_exec_ext_editor(compose);
1147 compose->draft_timeout_tag = -1;
1148 compose->modified = FALSE;
1149 compose_set_title(compose);
1153 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1154 gboolean override_pref)
1156 gchar *privacy = NULL;
1158 g_return_if_fail(compose != NULL);
1159 g_return_if_fail(account != NULL);
1161 if (override_pref == FALSE && account->default_encrypt_reply == FALSE)
1164 if (account->default_privacy_system
1165 && strlen(account->default_privacy_system)) {
1166 privacy = account->default_privacy_system;
1168 GSList *privacy_avail = privacy_get_system_ids();
1169 if (privacy_avail && g_slist_length(privacy_avail)) {
1170 privacy = (gchar *)(privacy_avail->data);
1173 if (privacy != NULL) {
1174 if (compose->privacy_system == NULL)
1175 compose->privacy_system = g_strdup(privacy);
1176 compose_update_privacy_system_menu_item(compose, FALSE);
1177 compose_use_encryption(compose, TRUE);
1181 static void compose_force_signing(Compose *compose, PrefsAccount *account)
1183 gchar *privacy = NULL;
1185 if (account->default_privacy_system
1186 && strlen(account->default_privacy_system)) {
1187 privacy = account->default_privacy_system;
1189 GSList *privacy_avail = privacy_get_system_ids();
1190 if (privacy_avail && g_slist_length(privacy_avail)) {
1191 privacy = (gchar *)(privacy_avail->data);
1194 if (privacy != NULL) {
1195 if (compose->privacy_system == NULL)
1196 compose->privacy_system = g_strdup(privacy);
1197 compose_update_privacy_system_menu_item(compose, FALSE);
1198 compose_use_signing(compose, TRUE);
1202 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1206 Compose *compose = NULL;
1207 GtkItemFactory *ifactory = NULL;
1209 g_return_val_if_fail(msginfo_list != NULL, NULL);
1211 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1212 g_return_val_if_fail(msginfo != NULL, NULL);
1214 list_len = g_slist_length(msginfo_list);
1218 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1219 FALSE, prefs_common.default_reply_list, FALSE, body);
1221 case COMPOSE_REPLY_WITH_QUOTE:
1222 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1223 FALSE, prefs_common.default_reply_list, FALSE, body);
1225 case COMPOSE_REPLY_WITHOUT_QUOTE:
1226 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1227 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1229 case COMPOSE_REPLY_TO_SENDER:
1230 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1231 FALSE, FALSE, TRUE, body);
1233 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1234 compose = compose_followup_and_reply_to(msginfo,
1235 COMPOSE_QUOTE_CHECK,
1236 FALSE, FALSE, body);
1238 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1239 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1240 FALSE, FALSE, TRUE, body);
1242 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1243 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1244 FALSE, FALSE, TRUE, NULL);
1246 case COMPOSE_REPLY_TO_ALL:
1247 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1248 TRUE, FALSE, FALSE, body);
1250 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1251 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1252 TRUE, FALSE, FALSE, body);
1254 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1255 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1256 TRUE, FALSE, FALSE, NULL);
1258 case COMPOSE_REPLY_TO_LIST:
1259 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1260 FALSE, TRUE, FALSE, body);
1262 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1263 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1264 FALSE, TRUE, FALSE, body);
1266 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1267 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1268 FALSE, TRUE, FALSE, NULL);
1270 case COMPOSE_FORWARD:
1271 if (prefs_common.forward_as_attachment) {
1272 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1275 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1279 case COMPOSE_FORWARD_INLINE:
1280 /* check if we reply to more than one Message */
1281 if (list_len == 1) {
1282 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1285 /* more messages FALL THROUGH */
1286 case COMPOSE_FORWARD_AS_ATTACH:
1287 compose = compose_forward_multiple(NULL, msginfo_list);
1289 case COMPOSE_REDIRECT:
1290 compose = compose_redirect(NULL, msginfo, FALSE);
1293 g_warning("compose_reply_mode(): invalid Compose Mode: %d\n", mode);
1296 ifactory = gtk_item_factory_from_widget(compose->menubar);
1298 compose->rmode = mode;
1299 switch (compose->rmode) {
1301 case COMPOSE_REPLY_WITH_QUOTE:
1302 case COMPOSE_REPLY_WITHOUT_QUOTE:
1303 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1304 debug_print("reply mode Normal\n");
1305 menu_set_active(ifactory, "/Options/Reply mode/Normal", TRUE);
1306 compose_reply_change_mode(compose, COMPOSE_REPLY, NULL); /* force update */
1308 case COMPOSE_REPLY_TO_SENDER:
1309 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1310 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1311 debug_print("reply mode Sender\n");
1312 menu_set_active(ifactory, "/Options/Reply mode/Sender", TRUE);
1314 case COMPOSE_REPLY_TO_ALL:
1315 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1316 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1317 debug_print("reply mode All\n");
1318 menu_set_active(ifactory, "/Options/Reply mode/All", TRUE);
1320 case COMPOSE_REPLY_TO_LIST:
1321 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1322 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1323 debug_print("reply mode List\n");
1324 menu_set_active(ifactory, "/Options/Reply mode/Mailing-list", TRUE);
1332 static Compose *compose_reply(MsgInfo *msginfo,
1333 ComposeQuoteMode quote_mode,
1339 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1340 to_sender, FALSE, body);
1343 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1344 ComposeQuoteMode quote_mode,
1349 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1350 to_sender, TRUE, body);
1353 static void compose_extract_original_charset(Compose *compose)
1355 MsgInfo *info = NULL;
1356 if (compose->replyinfo) {
1357 info = compose->replyinfo;
1358 } else if (compose->fwdinfo) {
1359 info = compose->fwdinfo;
1360 } else if (compose->targetinfo) {
1361 info = compose->targetinfo;
1364 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1365 MimeInfo *partinfo = mimeinfo;
1366 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1367 partinfo = procmime_mimeinfo_next(partinfo);
1369 compose->orig_charset =
1370 g_strdup(procmime_mimeinfo_get_parameter(
1371 partinfo, "charset"));
1373 procmime_mimeinfo_free_all(mimeinfo);
1377 #define SIGNAL_BLOCK(buffer) { \
1378 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1379 G_CALLBACK(compose_changed_cb), \
1381 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1382 G_CALLBACK(text_inserted), \
1386 #define SIGNAL_UNBLOCK(buffer) { \
1387 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1388 G_CALLBACK(compose_changed_cb), \
1390 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1391 G_CALLBACK(text_inserted), \
1395 static Compose *compose_generic_reply(MsgInfo *msginfo,
1396 ComposeQuoteMode quote_mode,
1397 gboolean to_all, gboolean to_ml,
1399 gboolean followup_and_reply_to,
1402 GtkItemFactory *ifactory;
1404 PrefsAccount *account = NULL;
1405 GtkTextView *textview;
1406 GtkTextBuffer *textbuf;
1407 gboolean quote = FALSE;
1408 const gchar *qmark = NULL;
1409 const gchar *body_fmt = NULL;
1411 g_return_val_if_fail(msginfo != NULL, NULL);
1412 g_return_val_if_fail(msginfo->folder != NULL, NULL);
1414 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1416 g_return_val_if_fail(account != NULL, NULL);
1418 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1420 compose->updating = TRUE;
1422 ifactory = gtk_item_factory_from_widget(compose->menubar);
1424 menu_set_active(ifactory, "/Options/Remove references", FALSE);
1425 menu_set_sensitive(ifactory, "/Options/Remove references", TRUE);
1427 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1428 if (!compose->replyinfo)
1429 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1431 compose_extract_original_charset(compose);
1433 if (msginfo->folder && msginfo->folder->ret_rcpt)
1434 menu_set_active(ifactory, "/Options/Request Return Receipt", TRUE);
1436 /* Set save folder */
1437 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1438 gchar *folderidentifier;
1440 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1441 folderidentifier = folder_item_get_identifier(msginfo->folder);
1442 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
1443 g_free(folderidentifier);
1446 if (compose_parse_header(compose, msginfo) < 0) return NULL;
1448 textview = (GTK_TEXT_VIEW(compose->text));
1449 textbuf = gtk_text_view_get_buffer(textview);
1450 compose_create_tags(textview, compose);
1452 undo_block(compose->undostruct);
1454 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1457 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1458 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1459 /* use the reply format of folder (if enabled), or the account's one
1460 (if enabled) or fallback to the global reply format, which is always
1461 enabled (even if empty), and use the relevant quotemark */
1463 if (msginfo->folder && msginfo->folder->prefs &&
1464 msginfo->folder->prefs->reply_with_format) {
1465 qmark = msginfo->folder->prefs->reply_quotemark;
1466 body_fmt = msginfo->folder->prefs->reply_body_format;
1468 } else if (account->reply_with_format) {
1469 qmark = account->reply_quotemark;
1470 body_fmt = account->reply_body_format;
1473 qmark = prefs_common.quotemark;
1474 body_fmt = prefs_common.quotefmt;
1479 /* empty quotemark is not allowed */
1480 if (qmark == NULL || *qmark == '\0')
1482 compose_quote_fmt(compose, compose->replyinfo,
1483 body_fmt, qmark, body, FALSE, TRUE,
1484 _("Message reply format error at line %d."));
1485 quote_fmt_reset_vartable();
1488 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1489 compose_force_encryption(compose, account, FALSE);
1492 SIGNAL_BLOCK(textbuf);
1494 if (account->auto_sig)
1495 compose_insert_sig(compose, FALSE);
1497 compose_wrap_all(compose);
1499 SIGNAL_UNBLOCK(textbuf);
1501 gtk_widget_grab_focus(compose->text);
1503 undo_unblock(compose->undostruct);
1505 if (prefs_common.auto_exteditor)
1506 compose_exec_ext_editor(compose);
1508 compose->modified = FALSE;
1509 compose_set_title(compose);
1511 compose->updating = FALSE;
1512 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1514 if (compose->deferred_destroy) {
1515 compose_destroy(compose);
1522 #define INSERT_FW_HEADER(var, hdr) \
1523 if (msginfo->var && *msginfo->var) { \
1524 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1525 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1526 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1529 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1530 gboolean as_attach, const gchar *body,
1531 gboolean no_extedit,
1535 GtkTextView *textview;
1536 GtkTextBuffer *textbuf;
1539 g_return_val_if_fail(msginfo != NULL, NULL);
1540 g_return_val_if_fail(msginfo->folder != NULL, NULL);
1543 !(account = compose_guess_forward_account_from_msginfo
1545 account = cur_account;
1547 compose = compose_create(account, msginfo->folder, COMPOSE_FORWARD, batch);
1549 compose->updating = TRUE;
1550 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1551 if (!compose->fwdinfo)
1552 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1554 compose_extract_original_charset(compose);
1556 if (msginfo->subject && *msginfo->subject) {
1557 gchar *buf, *buf2, *p;
1559 buf = p = g_strdup(msginfo->subject);
1560 p += subject_get_prefix_length(p);
1561 memmove(buf, p, strlen(p) + 1);
1563 buf2 = g_strdup_printf("Fw: %s", buf);
1564 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1570 textview = GTK_TEXT_VIEW(compose->text);
1571 textbuf = gtk_text_view_get_buffer(textview);
1572 compose_create_tags(textview, compose);
1574 undo_block(compose->undostruct);
1578 msgfile = procmsg_get_message_file(msginfo);
1579 if (!is_file_exist(msgfile))
1580 g_warning("%s: file not exist\n", msgfile);
1582 compose_attach_append(compose, msgfile, msgfile,
1587 const gchar *qmark = NULL;
1588 const gchar *body_fmt = prefs_common.fw_quotefmt;
1589 MsgInfo *full_msginfo;
1591 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1593 full_msginfo = procmsg_msginfo_copy(msginfo);
1595 /* use the forward format of folder (if enabled), or the account's one
1596 (if enabled) or fallback to the global forward format, which is always
1597 enabled (even if empty), and use the relevant quotemark */
1598 if (msginfo->folder && msginfo->folder->prefs &&
1599 msginfo->folder->prefs->forward_with_format) {
1600 qmark = msginfo->folder->prefs->forward_quotemark;
1601 body_fmt = msginfo->folder->prefs->forward_body_format;
1603 } else if (account->forward_with_format) {
1604 qmark = account->forward_quotemark;
1605 body_fmt = account->forward_body_format;
1608 qmark = prefs_common.fw_quotemark;
1609 body_fmt = prefs_common.fw_quotefmt;
1612 /* empty quotemark is not allowed */
1613 if (qmark == NULL || *qmark == '\0')
1616 compose_quote_fmt(compose, full_msginfo,
1617 body_fmt, qmark, body, FALSE, TRUE,
1618 _("Message forward format error at line %d."));
1619 quote_fmt_reset_vartable();
1620 compose_attach_parts(compose, msginfo);
1622 procmsg_msginfo_free(full_msginfo);
1625 SIGNAL_BLOCK(textbuf);
1627 if (account->auto_sig)
1628 compose_insert_sig(compose, FALSE);
1630 compose_wrap_all(compose);
1632 SIGNAL_UNBLOCK(textbuf);
1634 gtk_text_buffer_get_start_iter(textbuf, &iter);
1635 gtk_text_buffer_place_cursor(textbuf, &iter);
1637 gtk_widget_grab_focus(compose->header_last->entry);
1639 if (!no_extedit && prefs_common.auto_exteditor)
1640 compose_exec_ext_editor(compose);
1643 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1644 gchar *folderidentifier;
1646 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1647 folderidentifier = folder_item_get_identifier(msginfo->folder);
1648 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
1649 g_free(folderidentifier);
1652 undo_unblock(compose->undostruct);
1654 compose->modified = FALSE;
1655 compose_set_title(compose);
1657 compose->updating = FALSE;
1658 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1660 if (compose->deferred_destroy) {
1661 compose_destroy(compose);
1668 #undef INSERT_FW_HEADER
1670 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1673 GtkTextView *textview;
1674 GtkTextBuffer *textbuf;
1678 gboolean single_mail = TRUE;
1680 g_return_val_if_fail(msginfo_list != NULL, NULL);
1682 if (g_slist_length(msginfo_list) > 1)
1683 single_mail = FALSE;
1685 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1686 if (((MsgInfo *)msginfo->data)->folder == NULL)
1689 /* guess account from first selected message */
1691 !(account = compose_guess_forward_account_from_msginfo
1692 (msginfo_list->data)))
1693 account = cur_account;
1695 g_return_val_if_fail(account != NULL, NULL);
1697 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1698 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1699 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1702 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1704 compose->updating = TRUE;
1706 textview = GTK_TEXT_VIEW(compose->text);
1707 textbuf = gtk_text_view_get_buffer(textview);
1708 compose_create_tags(textview, compose);
1710 undo_block(compose->undostruct);
1711 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1712 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
1714 if (!is_file_exist(msgfile))
1715 g_warning("%s: file not exist\n", msgfile);
1717 compose_attach_append(compose, msgfile, msgfile,
1723 MsgInfo *info = (MsgInfo *)msginfo_list->data;
1724 if (info->subject && *info->subject) {
1725 gchar *buf, *buf2, *p;
1727 buf = p = g_strdup(info->subject);
1728 p += subject_get_prefix_length(p);
1729 memmove(buf, p, strlen(p) + 1);
1731 buf2 = g_strdup_printf("Fw: %s", buf);
1732 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1738 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
1739 _("Fw: multiple emails"));
1742 SIGNAL_BLOCK(textbuf);
1744 if (account->auto_sig)
1745 compose_insert_sig(compose, FALSE);
1747 compose_wrap_all(compose);
1749 SIGNAL_UNBLOCK(textbuf);
1751 gtk_text_buffer_get_start_iter(textbuf, &iter);
1752 gtk_text_buffer_place_cursor(textbuf, &iter);
1754 gtk_widget_grab_focus(compose->header_last->entry);
1755 undo_unblock(compose->undostruct);
1756 compose->modified = FALSE;
1757 compose_set_title(compose);
1759 compose->updating = FALSE;
1760 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1762 if (compose->deferred_destroy) {
1763 compose_destroy(compose);
1770 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
1772 GtkTextIter start = *iter;
1773 GtkTextIter end_iter;
1774 int start_pos = gtk_text_iter_get_offset(&start);
1776 if (!compose->account->sig_sep)
1779 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
1780 start_pos+strlen(compose->account->sig_sep));
1782 /* check sig separator */
1783 str = gtk_text_iter_get_text(&start, &end_iter);
1784 if (!strcmp(str, compose->account->sig_sep)) {
1786 /* check end of line (\n) */
1787 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
1788 start_pos+strlen(compose->account->sig_sep));
1789 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
1790 start_pos+strlen(compose->account->sig_sep)+1);
1791 tmp = gtk_text_iter_get_text(&start, &end_iter);
1792 if (!strcmp(tmp,"\n")) {
1804 static void compose_colorize_signature(Compose *compose)
1806 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
1808 GtkTextIter end_iter;
1809 gtk_text_buffer_get_start_iter(buffer, &iter);
1810 while (gtk_text_iter_forward_line(&iter))
1811 if (compose_is_sig_separator(compose, buffer, &iter)) {
1812 gtk_text_buffer_get_end_iter(buffer, &end_iter);
1813 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
1817 #define BLOCK_WRAP() { \
1818 prev_autowrap = compose->autowrap; \
1819 buffer = gtk_text_view_get_buffer( \
1820 GTK_TEXT_VIEW(compose->text)); \
1821 compose->autowrap = FALSE; \
1823 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1824 G_CALLBACK(compose_changed_cb), \
1826 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1827 G_CALLBACK(text_inserted), \
1830 #define UNBLOCK_WRAP() { \
1831 compose->autowrap = prev_autowrap; \
1832 if (compose->autowrap) { \
1833 gint old = compose->draft_timeout_tag; \
1834 compose->draft_timeout_tag = -2; \
1835 compose_wrap_all(compose); \
1836 compose->draft_timeout_tag = old; \
1839 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1840 G_CALLBACK(compose_changed_cb), \
1842 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1843 G_CALLBACK(text_inserted), \
1847 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
1849 Compose *compose = NULL;
1850 PrefsAccount *account = NULL;
1851 GtkTextView *textview;
1852 GtkTextBuffer *textbuf;
1856 gchar buf[BUFFSIZE];
1857 gboolean use_signing = FALSE;
1858 gboolean use_encryption = FALSE;
1859 gchar *privacy_system = NULL;
1860 int priority = PRIORITY_NORMAL;
1861 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
1863 g_return_val_if_fail(msginfo != NULL, NULL);
1864 g_return_val_if_fail(msginfo->folder != NULL, NULL);
1866 if (compose_put_existing_to_front(msginfo)) {
1870 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
1871 folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
1872 gchar queueheader_buf[BUFFSIZE];
1875 /* Select Account from queue headers */
1876 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1877 sizeof(queueheader_buf), "X-Claws-Account-Id:")) {
1878 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
1879 account = account_find_from_id(id);
1881 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1882 sizeof(queueheader_buf), "X-Sylpheed-Account-Id:")) {
1883 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
1884 account = account_find_from_id(id);
1886 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1887 sizeof(queueheader_buf), "NAID:")) {
1888 id = atoi(&queueheader_buf[strlen("NAID:")]);
1889 account = account_find_from_id(id);
1891 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1892 sizeof(queueheader_buf), "MAID:")) {
1893 id = atoi(&queueheader_buf[strlen("MAID:")]);
1894 account = account_find_from_id(id);
1896 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1897 sizeof(queueheader_buf), "S:")) {
1898 account = account_find_from_address(queueheader_buf);
1900 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1901 sizeof(queueheader_buf), "X-Claws-Sign:")) {
1902 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
1903 use_signing = param;
1906 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1907 sizeof(queueheader_buf), "X-Sylpheed-Sign:")) {
1908 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
1909 use_signing = param;
1912 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1913 sizeof(queueheader_buf), "X-Claws-Encrypt:")) {
1914 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
1915 use_encryption = param;
1917 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1918 sizeof(queueheader_buf), "X-Sylpheed-Encrypt:")) {
1919 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
1920 use_encryption = param;
1922 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1923 sizeof(queueheader_buf), "X-Claws-Privacy-System:")) {
1924 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
1926 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1927 sizeof(queueheader_buf), "X-Sylpheed-Privacy-System:")) {
1928 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
1930 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1931 sizeof(queueheader_buf), "X-Priority: ")) {
1932 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
1935 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1936 sizeof(queueheader_buf), "RMID:")) {
1937 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
1938 if (tokens[0] && tokens[1] && tokens[2]) {
1939 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
1940 if (orig_item != NULL) {
1941 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
1946 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1947 sizeof(queueheader_buf), "FMID:")) {
1948 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
1949 if (tokens[0] && tokens[1] && tokens[2]) {
1950 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
1951 if (orig_item != NULL) {
1952 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
1958 account = msginfo->folder->folder->account;
1961 if (!account && prefs_common.reedit_account_autosel) {
1962 gchar from[BUFFSIZE];
1963 if (!procheader_get_header_from_msginfo(msginfo, from, sizeof(from), "FROM:")) {
1964 extract_address(from);
1965 account = account_find_from_address(from);
1969 account = cur_account;
1971 g_return_val_if_fail(account != NULL, NULL);
1973 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
1975 compose->replyinfo = replyinfo;
1976 compose->fwdinfo = fwdinfo;
1978 compose->updating = TRUE;
1979 compose->priority = priority;
1981 if (privacy_system != NULL) {
1982 compose->privacy_system = privacy_system;
1983 compose_use_signing(compose, use_signing);
1984 compose_use_encryption(compose, use_encryption);
1985 compose_update_privacy_system_menu_item(compose, FALSE);
1987 activate_privacy_system(compose, account, FALSE);
1990 compose->targetinfo = procmsg_msginfo_copy(msginfo);
1992 compose_extract_original_charset(compose);
1994 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
1995 folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
1996 gchar queueheader_buf[BUFFSIZE];
1998 /* Set message save folder */
1999 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "SCF:")) {
2002 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2003 gtk_editable_delete_text(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
2004 gtk_editable_insert_text(GTK_EDITABLE(compose->savemsg_entry), &queueheader_buf[4], strlen(&queueheader_buf[4]), &startpos);
2006 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "RRCPT:")) {
2007 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2009 GtkItemFactory *ifactory;
2010 ifactory = gtk_item_factory_from_widget(compose->menubar);
2011 menu_set_active(ifactory, "/Options/Request Return Receipt", TRUE);
2016 if (compose_parse_header(compose, msginfo) < 0) {
2017 compose->updating = FALSE;
2018 compose_destroy(compose);
2021 compose_reedit_set_entry(compose, msginfo);
2023 textview = GTK_TEXT_VIEW(compose->text);
2024 textbuf = gtk_text_view_get_buffer(textview);
2025 compose_create_tags(textview, compose);
2027 mark = gtk_text_buffer_get_insert(textbuf);
2028 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2030 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2031 G_CALLBACK(compose_changed_cb),
2034 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2035 fp = procmime_get_first_encrypted_text_content(msginfo);
2037 compose_force_encryption(compose, account, TRUE);
2040 fp = procmime_get_first_text_content(msginfo);
2043 g_warning("Can't get text part\n");
2047 gboolean prev_autowrap = compose->autowrap;
2048 GtkTextBuffer *buffer = textbuf;
2050 while (fgets(buf, sizeof(buf), fp) != NULL) {
2052 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2058 compose_attach_parts(compose, msginfo);
2060 compose_colorize_signature(compose);
2062 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2063 G_CALLBACK(compose_changed_cb),
2066 gtk_widget_grab_focus(compose->text);
2068 if (prefs_common.auto_exteditor) {
2069 compose_exec_ext_editor(compose);
2071 compose->modified = FALSE;
2072 compose_set_title(compose);
2074 compose->updating = FALSE;
2075 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2077 if (compose->deferred_destroy) {
2078 compose_destroy(compose);
2082 compose->sig_str = compose_get_signature_str(compose);
2087 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2092 GtkItemFactory *ifactory;
2095 g_return_val_if_fail(msginfo != NULL, NULL);
2098 account = account_get_reply_account(msginfo,
2099 prefs_common.reply_account_autosel);
2100 g_return_val_if_fail(account != NULL, NULL);
2102 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2104 compose->updating = TRUE;
2106 ifactory = gtk_item_factory_from_widget(compose->menubar);
2107 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2108 compose->replyinfo = NULL;
2109 compose->fwdinfo = NULL;
2111 compose_show_first_last_header(compose, TRUE);
2113 gtk_widget_grab_focus(compose->header_last->entry);
2115 filename = procmsg_get_message_file(msginfo);
2117 if (filename == NULL) {
2118 compose->updating = FALSE;
2119 compose_destroy(compose);
2124 compose->redirect_filename = filename;
2126 /* Set save folder */
2127 item = msginfo->folder;
2128 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2129 gchar *folderidentifier;
2131 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
2132 folderidentifier = folder_item_get_identifier(item);
2133 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
2134 g_free(folderidentifier);
2137 compose_attach_parts(compose, msginfo);
2139 if (msginfo->subject)
2140 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2142 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2144 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2145 _("Message redirect format error at line %d."));
2146 quote_fmt_reset_vartable();
2147 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2149 compose_colorize_signature(compose);
2151 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
2152 menu_set_sensitive(ifactory, "/Add...", FALSE);
2153 menu_set_sensitive(ifactory, "/Remove", FALSE);
2154 menu_set_sensitive(ifactory, "/Properties...", FALSE);
2156 ifactory = gtk_item_factory_from_widget(compose->menubar);
2157 menu_set_sensitive(ifactory, "/Message/Save", FALSE);
2158 menu_set_sensitive(ifactory, "/Message/Insert file", FALSE);
2159 menu_set_sensitive(ifactory, "/Message/Attach file", FALSE);
2160 menu_set_sensitive(ifactory, "/Message/Insert signature", FALSE);
2161 menu_set_sensitive(ifactory, "/Edit", FALSE);
2162 menu_set_sensitive(ifactory, "/Options", FALSE);
2163 menu_set_sensitive(ifactory, "/Tools/Show ruler", FALSE);
2164 menu_set_sensitive(ifactory, "/Tools/Actions", FALSE);
2166 if (compose->toolbar->draft_btn)
2167 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2168 if (compose->toolbar->insert_btn)
2169 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2170 if (compose->toolbar->attach_btn)
2171 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2172 if (compose->toolbar->sig_btn)
2173 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2174 if (compose->toolbar->exteditor_btn)
2175 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2176 if (compose->toolbar->linewrap_current_btn)
2177 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2178 if (compose->toolbar->linewrap_all_btn)
2179 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2181 compose->modified = FALSE;
2182 compose_set_title(compose);
2183 compose->updating = FALSE;
2184 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2186 if (compose->deferred_destroy) {
2187 compose_destroy(compose);
2194 GList *compose_get_compose_list(void)
2196 return compose_list;
2199 void compose_entry_append(Compose *compose, const gchar *address,
2200 ComposeEntryType type)
2202 const gchar *header;
2204 gboolean in_quote = FALSE;
2205 if (!address || *address == '\0') return;
2212 header = N_("Bcc:");
2214 case COMPOSE_REPLYTO:
2215 header = N_("Reply-To:");
2217 case COMPOSE_NEWSGROUPS:
2218 header = N_("Newsgroups:");
2220 case COMPOSE_FOLLOWUPTO:
2221 header = N_( "Followup-To:");
2228 header = prefs_common_translated_header_name(header);
2230 cur = begin = (gchar *)address;
2232 /* we separate the line by commas, but not if we're inside a quoted
2234 while (*cur != '\0') {
2236 in_quote = !in_quote;
2237 if (*cur == ',' && !in_quote) {
2238 gchar *tmp = g_strdup(begin);
2240 tmp[cur-begin]='\0';
2243 while (*tmp == ' ' || *tmp == '\t')
2245 compose_add_header_entry(compose, header, tmp);
2252 gchar *tmp = g_strdup(begin);
2254 tmp[cur-begin]='\0';
2257 while (*tmp == ' ' || *tmp == '\t')
2259 compose_add_header_entry(compose, header, tmp);
2264 static void compose_entry_mark_default_to(Compose *compose, const gchar *mailto)
2266 static GdkColor yellow;
2267 static GdkColor black;
2268 static gboolean yellow_initialised = FALSE;
2272 if (!yellow_initialised) {
2273 gdk_color_parse("#f5f6be", &yellow);
2274 gdk_color_parse("#000000", &black);
2275 yellow_initialised = gdk_colormap_alloc_color(
2276 gdk_colormap_get_system(), &yellow, FALSE, TRUE);
2277 yellow_initialised &= gdk_colormap_alloc_color(
2278 gdk_colormap_get_system(), &black, FALSE, TRUE);
2281 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2282 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2283 if (gtk_entry_get_text(entry) &&
2284 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2285 if (yellow_initialised) {
2286 gtk_widget_modify_base(
2287 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2288 GTK_STATE_NORMAL, &yellow);
2289 gtk_widget_modify_text(
2290 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2291 GTK_STATE_NORMAL, &black);
2297 void compose_toolbar_cb(gint action, gpointer data)
2299 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2300 Compose *compose = (Compose*)toolbar_item->parent;
2302 g_return_if_fail(compose != NULL);
2306 compose_send_cb(compose, 0, NULL);
2309 compose_send_later_cb(compose, 0, NULL);
2312 compose_draft_cb(compose, COMPOSE_QUIT_EDITING, NULL);
2315 compose_insert_file_cb(compose, 0, NULL);
2318 compose_attach_cb(compose, 0, NULL);
2321 compose_insert_sig(compose, FALSE);
2324 compose_ext_editor_cb(compose, 0, NULL);
2326 case A_LINEWRAP_CURRENT:
2327 compose_beautify_paragraph(compose, NULL, TRUE);
2329 case A_LINEWRAP_ALL:
2330 compose_wrap_all_full(compose, TRUE);
2333 compose_address_cb(compose, 0, NULL);
2336 case A_CHECK_SPELLING:
2337 compose_check_all(compose);
2345 static void compose_entries_set(Compose *compose, const gchar *mailto)
2349 gchar *subject = NULL;
2353 gchar *attach = NULL;
2355 scan_mailto_url(mailto, &to, &cc, NULL, &subject, &body, &attach);
2358 compose_entry_append(compose, to, COMPOSE_TO);
2360 compose_entry_append(compose, cc, COMPOSE_CC);
2362 if (!g_utf8_validate (subject, -1, NULL)) {
2363 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2364 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2367 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2371 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2372 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2375 gboolean prev_autowrap = compose->autowrap;
2377 compose->autowrap = FALSE;
2379 mark = gtk_text_buffer_get_insert(buffer);
2380 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2382 if (!g_utf8_validate (body, -1, NULL)) {
2383 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2384 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2387 gtk_text_buffer_insert(buffer, &iter, body, -1);
2389 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2391 compose->autowrap = prev_autowrap;
2392 if (compose->autowrap)
2393 compose_wrap_all(compose);
2397 gchar *utf8_filename = conv_filename_to_utf8(attach);
2398 if (utf8_filename) {
2399 if (compose_attach_append(compose, attach, utf8_filename, NULL)) {
2400 alertpanel_notice(_("The file '%s' has been attached."), attach);
2402 g_free(utf8_filename);
2404 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2414 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2416 static HeaderEntry hentry[] = {{"Reply-To:", NULL, TRUE},
2417 {"Cc:", NULL, TRUE},
2418 {"References:", NULL, FALSE},
2419 {"Bcc:", NULL, TRUE},
2420 {"Newsgroups:", NULL, TRUE},
2421 {"Followup-To:", NULL, TRUE},
2422 {"List-Post:", NULL, FALSE},
2423 {"X-Priority:", NULL, FALSE},
2424 {NULL, NULL, FALSE}};
2440 g_return_val_if_fail(msginfo != NULL, -1);
2442 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2443 procheader_get_header_fields(fp, hentry);
2446 if (hentry[H_REPLY_TO].body != NULL) {
2447 if (hentry[H_REPLY_TO].body[0] != '\0') {
2449 conv_unmime_header(hentry[H_REPLY_TO].body,
2452 g_free(hentry[H_REPLY_TO].body);
2453 hentry[H_REPLY_TO].body = NULL;
2455 if (hentry[H_CC].body != NULL) {
2456 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL);
2457 g_free(hentry[H_CC].body);
2458 hentry[H_CC].body = NULL;
2460 if (hentry[H_REFERENCES].body != NULL) {
2461 if (compose->mode == COMPOSE_REEDIT)
2462 compose->references = hentry[H_REFERENCES].body;
2464 compose->references = compose_parse_references
2465 (hentry[H_REFERENCES].body, msginfo->msgid);
2466 g_free(hentry[H_REFERENCES].body);
2468 hentry[H_REFERENCES].body = NULL;
2470 if (hentry[H_BCC].body != NULL) {
2471 if (compose->mode == COMPOSE_REEDIT)
2473 conv_unmime_header(hentry[H_BCC].body, NULL);
2474 g_free(hentry[H_BCC].body);
2475 hentry[H_BCC].body = NULL;
2477 if (hentry[H_NEWSGROUPS].body != NULL) {
2478 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2479 hentry[H_NEWSGROUPS].body = NULL;
2481 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2482 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2483 compose->followup_to =
2484 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2487 g_free(hentry[H_FOLLOWUP_TO].body);
2488 hentry[H_FOLLOWUP_TO].body = NULL;
2490 if (hentry[H_LIST_POST].body != NULL) {
2493 extract_address(hentry[H_LIST_POST].body);
2494 if (hentry[H_LIST_POST].body[0] != '\0') {
2495 scan_mailto_url(hentry[H_LIST_POST].body,
2496 &to, NULL, NULL, NULL, NULL, NULL);
2498 g_free(compose->ml_post);
2499 compose->ml_post = to;
2502 g_free(hentry[H_LIST_POST].body);
2503 hentry[H_LIST_POST].body = NULL;
2506 /* CLAWS - X-Priority */
2507 if (compose->mode == COMPOSE_REEDIT)
2508 if (hentry[H_X_PRIORITY].body != NULL) {
2511 priority = atoi(hentry[H_X_PRIORITY].body);
2512 g_free(hentry[H_X_PRIORITY].body);
2514 hentry[H_X_PRIORITY].body = NULL;
2516 if (priority < PRIORITY_HIGHEST ||
2517 priority > PRIORITY_LOWEST)
2518 priority = PRIORITY_NORMAL;
2520 compose->priority = priority;
2523 if (compose->mode == COMPOSE_REEDIT) {
2524 if (msginfo->inreplyto && *msginfo->inreplyto)
2525 compose->inreplyto = g_strdup(msginfo->inreplyto);
2529 if (msginfo->msgid && *msginfo->msgid)
2530 compose->inreplyto = g_strdup(msginfo->msgid);
2532 if (!compose->references) {
2533 if (msginfo->msgid && *msginfo->msgid) {
2534 if (msginfo->inreplyto && *msginfo->inreplyto)
2535 compose->references =
2536 g_strdup_printf("<%s>\n\t<%s>",
2540 compose->references =
2541 g_strconcat("<", msginfo->msgid, ">",
2543 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
2544 compose->references =
2545 g_strconcat("<", msginfo->inreplyto, ">",
2553 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
2555 GSList *ref_id_list, *cur;
2559 ref_id_list = references_list_append(NULL, ref);
2560 if (!ref_id_list) return NULL;
2561 if (msgid && *msgid)
2562 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
2567 for (cur = ref_id_list; cur != NULL; cur = cur->next)
2568 /* "<" + Message-ID + ">" + CR+LF+TAB */
2569 len += strlen((gchar *)cur->data) + 5;
2571 if (len > MAX_REFERENCES_LEN) {
2572 /* remove second message-ID */
2573 if (ref_id_list && ref_id_list->next &&
2574 ref_id_list->next->next) {
2575 g_free(ref_id_list->next->data);
2576 ref_id_list = g_slist_remove
2577 (ref_id_list, ref_id_list->next->data);
2579 slist_free_strings(ref_id_list);
2580 g_slist_free(ref_id_list);
2587 new_ref = g_string_new("");
2588 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
2589 if (new_ref->len > 0)
2590 g_string_append(new_ref, "\n\t");
2591 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
2594 slist_free_strings(ref_id_list);
2595 g_slist_free(ref_id_list);
2597 new_ref_str = new_ref->str;
2598 g_string_free(new_ref, FALSE);
2603 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
2604 const gchar *fmt, const gchar *qmark,
2605 const gchar *body, gboolean rewrap,
2606 gboolean need_unescape,
2607 const gchar *err_msg)
2609 MsgInfo* dummyinfo = NULL;
2610 gchar *quote_str = NULL;
2612 gboolean prev_autowrap;
2613 const gchar *trimmed_body = body;
2614 gint cursor_pos = -1;
2615 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2616 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2621 SIGNAL_BLOCK(buffer);
2624 dummyinfo = compose_msginfo_new_from_compose(compose);
2625 msginfo = dummyinfo;
2628 if (qmark != NULL) {
2630 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
2631 compose->gtkaspell);
2633 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
2635 quote_fmt_scan_string(qmark);
2638 buf = quote_fmt_get_buffer();
2640 alertpanel_error(_("Quote mark format error."));
2642 Xstrdup_a(quote_str, buf, goto error)
2645 if (fmt && *fmt != '\0') {
2648 while (*trimmed_body == '\n')
2652 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account,
2653 compose->gtkaspell);
2655 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account);
2657 if (need_unescape) {
2660 /* decode \-escape sequences in the internal representation of the quote format */
2661 tmp = malloc(strlen(fmt)+1);
2662 pref_get_unescaped_pref(tmp, fmt);
2663 quote_fmt_scan_string(tmp);
2667 quote_fmt_scan_string(fmt);
2671 buf = quote_fmt_get_buffer();
2673 gint line = quote_fmt_get_line();
2674 gchar *msg = g_strdup_printf(err_msg, line);
2675 alertpanel_error(msg);
2682 prev_autowrap = compose->autowrap;
2683 compose->autowrap = FALSE;
2685 mark = gtk_text_buffer_get_insert(buffer);
2686 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2687 if (g_utf8_validate(buf, -1, NULL)) {
2688 gtk_text_buffer_insert(buffer, &iter, buf, -1);
2690 gchar *tmpout = NULL;
2691 tmpout = conv_codeset_strdup
2692 (buf, conv_get_locale_charset_str_no_utf8(),
2694 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
2696 tmpout = g_malloc(strlen(buf)*2+1);
2697 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
2699 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
2703 cursor_pos = quote_fmt_get_cursor_pos();
2704 compose->set_cursor_pos = cursor_pos;
2705 if (cursor_pos == -1) {
2708 gtk_text_buffer_get_start_iter(buffer, &iter);
2709 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
2710 gtk_text_buffer_place_cursor(buffer, &iter);
2712 compose->autowrap = prev_autowrap;
2713 if (compose->autowrap && rewrap)
2714 compose_wrap_all(compose);
2721 SIGNAL_UNBLOCK(buffer);
2723 procmsg_msginfo_free( dummyinfo );
2728 /* if ml_post is of type addr@host and from is of type
2729 * addr-anything@host, return TRUE
2731 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
2733 gchar *left_ml = NULL;
2734 gchar *right_ml = NULL;
2735 gchar *left_from = NULL;
2736 gchar *right_from = NULL;
2737 gboolean result = FALSE;
2739 if (!ml_post || !from)
2742 left_ml = g_strdup(ml_post);
2743 if (strstr(left_ml, "@")) {
2744 right_ml = strstr(left_ml, "@")+1;
2745 *(strstr(left_ml, "@")) = '\0';
2748 left_from = g_strdup(from);
2749 if (strstr(left_from, "@")) {
2750 right_from = strstr(left_from, "@")+1;
2751 *(strstr(left_from, "@")) = '\0';
2754 if (left_ml && left_from && right_ml && right_from
2755 && !strncmp(left_from, left_ml, strlen(left_ml))
2756 && !strcmp(right_from, right_ml)) {
2765 static gboolean same_address(const gchar *addr1, const gchar *addr2)
2767 gchar *my_addr1, *my_addr2;
2769 if (!addr1 || !addr2)
2772 Xstrdup_a(my_addr1, addr1, return FALSE);
2773 Xstrdup_a(my_addr2, addr2, return FALSE);
2775 extract_address(my_addr1);
2776 extract_address(my_addr2);
2778 return !strcasecmp(my_addr1, my_addr2);
2781 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
2782 gboolean to_all, gboolean to_ml,
2784 gboolean followup_and_reply_to)
2786 GSList *cc_list = NULL;
2789 gchar *replyto = NULL;
2790 GHashTable *to_table;
2792 gboolean reply_to_ml = FALSE;
2793 gboolean default_reply_to = FALSE;
2795 g_return_if_fail(compose->account != NULL);
2796 g_return_if_fail(msginfo != NULL);
2798 reply_to_ml = to_ml && compose->ml_post;
2800 default_reply_to = msginfo->folder &&
2801 msginfo->folder->prefs->enable_default_reply_to;
2803 if (compose->account->protocol != A_NNTP) {
2804 if (reply_to_ml && !default_reply_to) {
2806 gboolean is_subscr = is_subscription(compose->ml_post,
2809 /* normal answer to ml post with a reply-to */
2810 compose_entry_append(compose,
2813 if (compose->replyto
2814 && !same_address(compose->ml_post, compose->replyto))
2815 compose_entry_append(compose,
2819 /* answer to subscription confirmation */
2820 if (compose->replyto)
2821 compose_entry_append(compose,
2824 else if (msginfo->from)
2825 compose_entry_append(compose,
2830 else if (!(to_all || to_sender) && default_reply_to) {
2831 compose_entry_append(compose,
2832 msginfo->folder->prefs->default_reply_to,
2834 compose_entry_mark_default_to(compose,
2835 msginfo->folder->prefs->default_reply_to);
2840 Xstrdup_a(tmp1, msginfo->from, return);
2841 extract_address(tmp1);
2842 if (to_all || to_sender ||
2843 !account_find_from_address(tmp1))
2844 compose_entry_append(compose,
2845 (compose->replyto && !to_sender)
2846 ? compose->replyto :
2847 msginfo->from ? msginfo->from : "",
2849 else if (!to_all && !to_sender) {
2850 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
2851 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
2852 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
2853 compose_entry_append(compose,
2854 msginfo->from ? msginfo->from : "",
2857 /* replying to own mail, use original recp */
2858 compose_entry_append(compose,
2859 msginfo->to ? msginfo->to : "",
2861 compose_entry_append(compose,
2862 msginfo->cc ? msginfo->cc : "",
2868 if (to_sender || (compose->followup_to &&
2869 !strncmp(compose->followup_to, "poster", 6)))
2870 compose_entry_append
2872 (compose->replyto ? compose->replyto :
2873 msginfo->from ? msginfo->from : ""),
2876 else if (followup_and_reply_to || to_all) {
2877 compose_entry_append
2879 (compose->replyto ? compose->replyto :
2880 msginfo->from ? msginfo->from : ""),
2883 compose_entry_append
2885 compose->followup_to ? compose->followup_to :
2886 compose->newsgroups ? compose->newsgroups : "",
2887 COMPOSE_NEWSGROUPS);
2890 compose_entry_append
2892 compose->followup_to ? compose->followup_to :
2893 compose->newsgroups ? compose->newsgroups : "",
2894 COMPOSE_NEWSGROUPS);
2897 if (msginfo->subject && *msginfo->subject) {
2901 buf = p = g_strdup(msginfo->subject);
2902 p += subject_get_prefix_length(p);
2903 memmove(buf, p, strlen(p) + 1);
2905 buf2 = g_strdup_printf("Re: %s", buf);
2906 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
2911 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
2913 if (to_ml && compose->ml_post) return;
2914 if (!to_all || compose->account->protocol == A_NNTP) return;
2916 if (compose->replyto) {
2917 Xstrdup_a(replyto, compose->replyto, return);
2918 extract_address(replyto);
2920 if (msginfo->from) {
2921 Xstrdup_a(from, msginfo->from, return);
2922 extract_address(from);
2925 if (replyto && from)
2926 cc_list = address_list_append_with_comments(cc_list, from);
2927 if (to_all && msginfo->folder &&
2928 msginfo->folder->prefs->enable_default_reply_to)
2929 cc_list = address_list_append_with_comments(cc_list,
2930 msginfo->folder->prefs->default_reply_to);
2931 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
2932 cc_list = address_list_append_with_comments(cc_list, compose->cc);
2934 to_table = g_hash_table_new(g_str_hash, g_str_equal);
2936 g_hash_table_insert(to_table, g_utf8_strdown(replyto, -1), GINT_TO_POINTER(1));
2937 if (compose->account) {
2938 g_hash_table_insert(to_table, g_utf8_strdown(compose->account->address, -1),
2939 GINT_TO_POINTER(1));
2941 /* remove address on To: and that of current account */
2942 for (cur = cc_list; cur != NULL; ) {
2943 GSList *next = cur->next;
2946 addr = g_utf8_strdown(cur->data, -1);
2947 extract_address(addr);
2949 if (GPOINTER_TO_INT(g_hash_table_lookup(to_table, addr)) == 1)
2950 cc_list = g_slist_remove(cc_list, cur->data);
2952 g_hash_table_insert(to_table, addr, GINT_TO_POINTER(1));
2956 hash_free_strings(to_table);
2957 g_hash_table_destroy(to_table);
2960 for (cur = cc_list; cur != NULL; cur = cur->next)
2961 compose_entry_append(compose, (gchar *)cur->data,
2963 slist_free_strings(cc_list);
2964 g_slist_free(cc_list);
2969 #define SET_ENTRY(entry, str) \
2972 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
2975 #define SET_ADDRESS(type, str) \
2978 compose_entry_append(compose, str, type); \
2981 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
2983 g_return_if_fail(msginfo != NULL);
2985 SET_ENTRY(subject_entry, msginfo->subject);
2986 SET_ENTRY(from_name, msginfo->from);
2987 SET_ADDRESS(COMPOSE_TO, msginfo->to);
2988 SET_ADDRESS(COMPOSE_CC, compose->cc);
2989 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
2990 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
2991 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
2992 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
2994 compose_update_priority_menu_item(compose);
2995 compose_update_privacy_system_menu_item(compose, FALSE);
2996 compose_show_first_last_header(compose, TRUE);
3002 static void compose_insert_sig(Compose *compose, gboolean replace)
3004 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3005 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3007 GtkTextIter iter, iter_end;
3009 gchar *search = NULL;
3010 gboolean prev_autowrap;
3011 gboolean found = FALSE, shift = FALSE;
3014 g_return_if_fail(compose->account != NULL);
3018 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3019 G_CALLBACK(compose_changed_cb),
3022 mark = gtk_text_buffer_get_insert(buffer);
3023 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3024 cur_pos = gtk_text_iter_get_offset (&iter);
3026 gtk_text_buffer_get_end_iter(buffer, &iter);
3028 search = compose->sig_str;
3030 if (replace && search) {
3031 GtkTextIter first_iter, start_iter, end_iter;
3033 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3035 if (compose->sig_str[0] == '\0')
3038 found = gtk_text_iter_forward_search(&first_iter,
3040 GTK_TEXT_SEARCH_TEXT_ONLY,
3041 &start_iter, &end_iter,
3045 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3049 if (replace && !found && search && strlen(search) > 2
3050 && search[0] == '\n' && search[1] == '\n') {
3056 g_free(compose->sig_str);
3057 compose->sig_str = compose_get_signature_str(compose);
3058 if (!compose->sig_str || (replace && !compose->account->auto_sig))
3059 compose->sig_str = g_strdup("");
3061 cur_pos = gtk_text_iter_get_offset(&iter);
3063 gtk_text_buffer_insert(buffer, &iter, compose->sig_str + 1, -1);
3065 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3067 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3068 gtk_text_iter_forward_char(&iter);
3069 gtk_text_iter_forward_char(&iter);
3070 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3071 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3073 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3074 cur_pos = gtk_text_buffer_get_char_count (buffer);
3076 /* put the cursor where it should be
3077 * either where the quote_fmt says, either before the signature */
3078 if (compose->set_cursor_pos < 0)
3079 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3081 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3082 compose->set_cursor_pos);
3084 gtk_text_buffer_place_cursor(buffer, &iter);
3085 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3086 G_CALLBACK(compose_changed_cb),
3092 static gchar *compose_get_signature_str(Compose *compose)
3094 gchar *sig_body = NULL;
3095 gchar *sig_str = NULL;
3096 gchar *utf8_sig_str = NULL;
3098 g_return_val_if_fail(compose->account != NULL, NULL);
3100 if (!compose->account->sig_path)
3103 if (compose->account->sig_type == SIG_FILE) {
3104 if (!is_file_or_fifo_exist(compose->account->sig_path)) {
3105 g_warning("can't open signature file: %s\n",
3106 compose->account->sig_path);
3111 if (compose->account->sig_type == SIG_COMMAND)
3112 sig_body = get_command_output(compose->account->sig_path);
3116 tmp = file_read_to_str(compose->account->sig_path);
3119 sig_body = normalize_newlines(tmp);
3123 if (compose->account->sig_sep) {
3124 sig_str = g_strconcat("\n\n", compose->account->sig_sep, "\n", sig_body,
3128 sig_str = g_strconcat("\n\n", sig_body, NULL);
3131 if (g_utf8_validate(sig_str, -1, NULL) == TRUE)
3132 utf8_sig_str = sig_str;
3134 utf8_sig_str = conv_codeset_strdup
3135 (sig_str, conv_get_locale_charset_str_no_utf8(),
3141 return utf8_sig_str;
3144 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3147 GtkTextBuffer *buffer;
3150 const gchar *cur_encoding;
3151 gchar buf[BUFFSIZE];
3154 gboolean prev_autowrap;
3155 gboolean badtxt = FALSE;
3157 g_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3159 if ((fp = g_fopen(file, "rb")) == NULL) {
3160 FILE_OP_ERROR(file, "fopen");
3161 return COMPOSE_INSERT_READ_ERROR;
3164 prev_autowrap = compose->autowrap;
3165 compose->autowrap = FALSE;
3167 text = GTK_TEXT_VIEW(compose->text);
3168 buffer = gtk_text_view_get_buffer(text);
3169 mark = gtk_text_buffer_get_insert(buffer);
3170 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3172 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3173 G_CALLBACK(text_inserted),
3176 cur_encoding = conv_get_locale_charset_str_no_utf8();
3178 while (fgets(buf, sizeof(buf), fp) != NULL) {
3181 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3182 str = g_strdup(buf);
3184 str = conv_codeset_strdup
3185 (buf, cur_encoding, CS_INTERNAL);
3188 /* strip <CR> if DOS/Windows file,
3189 replace <CR> with <LF> if Macintosh file. */
3192 if (len > 0 && str[len - 1] != '\n') {
3194 if (str[len] == '\r') str[len] = '\n';
3197 gtk_text_buffer_insert(buffer, &iter, str, -1);
3201 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3202 G_CALLBACK(text_inserted),
3204 compose->autowrap = prev_autowrap;
3205 if (compose->autowrap)
3206 compose_wrap_all(compose);
3211 return COMPOSE_INSERT_INVALID_CHARACTER;
3213 return COMPOSE_INSERT_SUCCESS;
3216 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3217 const gchar *filename,
3218 const gchar *content_type)
3226 GtkListStore *store;
3228 gboolean has_binary = FALSE;
3230 if (!is_file_exist(file)) {
3231 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3232 gboolean result = FALSE;
3233 if (file_from_uri && is_file_exist(file_from_uri)) {
3234 result = compose_attach_append(
3235 compose, file_from_uri,
3239 g_free(file_from_uri);
3242 alertpanel_error("File %s doesn't exist\n", filename);
3245 if ((size = get_file_size(file)) < 0) {
3246 alertpanel_error("Can't get file size of %s\n", filename);
3250 alertpanel_error(_("File %s is empty."), filename);
3253 if ((fp = g_fopen(file, "rb")) == NULL) {
3254 alertpanel_error(_("Can't read %s."), filename);
3259 ainfo = g_new0(AttachInfo, 1);
3260 auto_ainfo = g_auto_pointer_new_with_free
3261 (ainfo, (GFreeFunc) compose_attach_info_free);
3262 ainfo->file = g_strdup(file);
3265 ainfo->content_type = g_strdup(content_type);
3266 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3268 MsgFlags flags = {0, 0};
3270 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3271 ainfo->encoding = ENC_7BIT;
3273 ainfo->encoding = ENC_8BIT;
3275 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3276 if (msginfo && msginfo->subject)
3277 name = g_strdup(msginfo->subject);
3279 name = g_path_get_basename(filename ? filename : file);
3281 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3283 procmsg_msginfo_free(msginfo);
3285 if (!g_ascii_strncasecmp(content_type, "text", 4))
3286 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3288 ainfo->encoding = ENC_BASE64;
3289 name = g_path_get_basename(filename ? filename : file);
3290 ainfo->name = g_strdup(name);
3294 ainfo->content_type = procmime_get_mime_type(file);
3295 if (!ainfo->content_type) {
3296 ainfo->content_type =
3297 g_strdup("application/octet-stream");
3298 ainfo->encoding = ENC_BASE64;
3299 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text", 4))
3301 procmime_get_encoding_for_text_file(file, &has_binary);
3303 ainfo->encoding = ENC_BASE64;
3304 name = g_path_get_basename(filename ? filename : file);
3305 ainfo->name = g_strdup(name);
3309 if (ainfo->name != NULL
3310 && !strcmp(ainfo->name, ".")) {
3311 g_free(ainfo->name);
3315 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3316 g_free(ainfo->content_type);
3317 ainfo->content_type = g_strdup("application/octet-stream");
3321 size_text = to_human_readable(size);
3323 store = GTK_LIST_STORE(gtk_tree_view_get_model
3324 (GTK_TREE_VIEW(compose->attach_clist)));
3326 gtk_list_store_append(store, &iter);
3327 gtk_list_store_set(store, &iter,
3328 COL_MIMETYPE, ainfo->content_type,
3329 COL_SIZE, size_text,
3330 COL_NAME, ainfo->name,
3332 COL_AUTODATA, auto_ainfo,
3335 g_auto_pointer_free(auto_ainfo);
3339 static void compose_use_signing(Compose *compose, gboolean use_signing)
3341 GtkItemFactory *ifactory;
3342 GtkWidget *menuitem = NULL;
3344 compose->use_signing = use_signing;
3345 ifactory = gtk_item_factory_from_widget(compose->menubar);
3346 menuitem = gtk_item_factory_get_item
3347 (ifactory, "/Options/Sign");
3348 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
3352 static void compose_use_encryption(Compose *compose, gboolean use_encryption)
3354 GtkItemFactory *ifactory;
3355 GtkWidget *menuitem = NULL;
3357 compose->use_encryption = use_encryption;
3358 ifactory = gtk_item_factory_from_widget(compose->menubar);
3359 menuitem = gtk_item_factory_get_item
3360 (ifactory, "/Options/Encrypt");
3362 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
3366 #define NEXT_PART_NOT_CHILD(info) \
3368 node = info->node; \
3369 while (node->children) \
3370 node = g_node_last_child(node); \
3371 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3374 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3378 MimeInfo *firsttext = NULL;
3379 MimeInfo *encrypted = NULL;
3382 const gchar *partname = NULL;
3384 mimeinfo = procmime_scan_message(msginfo);
3385 if (!mimeinfo) return;
3387 if (mimeinfo->node->children == NULL) {
3388 procmime_mimeinfo_free_all(mimeinfo);
3392 /* find first content part */
3393 child = (MimeInfo *) mimeinfo->node->children->data;
3394 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3395 child = (MimeInfo *)child->node->children->data;
3397 if (child->type == MIMETYPE_TEXT) {
3399 debug_print("First text part found\n");
3400 } else if (compose->mode == COMPOSE_REEDIT &&
3401 child->type == MIMETYPE_APPLICATION &&
3402 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3403 encrypted = (MimeInfo *)child->node->parent->data;
3406 child = (MimeInfo *) mimeinfo->node->children->data;
3407 while (child != NULL) {
3410 if (child == encrypted) {
3411 /* skip this part of tree */
3412 NEXT_PART_NOT_CHILD(child);
3416 if (child->type == MIMETYPE_MULTIPART) {
3417 /* get the actual content */
3418 child = procmime_mimeinfo_next(child);
3422 if (child == firsttext) {
3423 child = procmime_mimeinfo_next(child);
3427 outfile = procmime_get_tmp_file_name(child);
3428 if ((err = procmime_get_part(outfile, child)) < 0)
3429 g_warning("Can't get the part of multipart message. (%s)", strerror(-err));
3431 gchar *content_type;
3433 content_type = procmime_get_content_type_str(child->type, child->subtype);
3435 /* if we meet a pgp signature, we don't attach it, but
3436 * we force signing. */
3437 if ((strcmp(content_type, "application/pgp-signature") &&
3438 strcmp(content_type, "application/pkcs7-signature") &&
3439 strcmp(content_type, "application/x-pkcs7-signature"))
3440 || compose->mode == COMPOSE_REDIRECT) {
3441 partname = procmime_mimeinfo_get_parameter(child, "filename");
3442 if (partname == NULL)
3443 partname = procmime_mimeinfo_get_parameter(child, "name");
3444 if (partname == NULL)
3446 compose_attach_append(compose, outfile,
3447 partname, content_type);
3449 compose_force_signing(compose, compose->account);
3451 g_free(content_type);
3454 NEXT_PART_NOT_CHILD(child);
3456 procmime_mimeinfo_free_all(mimeinfo);
3459 #undef NEXT_PART_NOT_CHILD
3464 WAIT_FOR_INDENT_CHAR,
3465 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3468 /* return indent length, we allow:
3469 indent characters followed by indent characters or spaces/tabs,
3470 alphabets and numbers immediately followed by indent characters,
3471 and the repeating sequences of the above
3472 If quote ends with multiple spaces, only the first one is included. */
3473 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3474 const GtkTextIter *start, gint *len)
3476 GtkTextIter iter = *start;
3480 IndentState state = WAIT_FOR_INDENT_CHAR;
3483 gint alnum_count = 0;
3484 gint space_count = 0;
3487 if (prefs_common.quote_chars == NULL) {
3491 while (!gtk_text_iter_ends_line(&iter)) {
3492 wc = gtk_text_iter_get_char(&iter);
3493 if (g_unichar_iswide(wc))
3495 clen = g_unichar_to_utf8(wc, ch);
3499 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
3500 is_space = g_unichar_isspace(wc);
3502 if (state == WAIT_FOR_INDENT_CHAR) {
3503 if (!is_indent && !g_unichar_isalnum(wc))
3506 quote_len += alnum_count + space_count + 1;
3507 alnum_count = space_count = 0;
3508 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
3511 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
3512 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
3516 else if (is_indent) {
3517 quote_len += alnum_count + space_count + 1;
3518 alnum_count = space_count = 0;
3521 state = WAIT_FOR_INDENT_CHAR;
3525 gtk_text_iter_forward_char(&iter);
3528 if (quote_len > 0 && space_count > 0)
3534 if (quote_len > 0) {
3536 gtk_text_iter_forward_chars(&iter, quote_len);
3537 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
3543 /* return TRUE if the line is itemized */
3544 static gboolean compose_is_itemized(GtkTextBuffer *buffer,
3545 const GtkTextIter *start)
3547 GtkTextIter iter = *start;
3552 if (gtk_text_iter_ends_line(&iter))
3556 wc = gtk_text_iter_get_char(&iter);
3557 if (!g_unichar_isspace(wc))
3559 gtk_text_iter_forward_char(&iter);
3560 if (gtk_text_iter_ends_line(&iter))
3564 clen = g_unichar_to_utf8(wc, ch);
3568 if (!strchr("*-+", ch[0]))
3571 gtk_text_iter_forward_char(&iter);
3572 if (gtk_text_iter_ends_line(&iter))
3574 wc = gtk_text_iter_get_char(&iter);
3575 if (g_unichar_isspace(wc))
3581 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
3582 const GtkTextIter *start,
3583 GtkTextIter *break_pos,
3587 GtkTextIter iter = *start, line_end = *start;
3588 PangoLogAttr *attrs;
3595 gboolean can_break = FALSE;
3596 gboolean do_break = FALSE;
3597 gboolean was_white = FALSE;
3598 gboolean prev_dont_break = FALSE;
3600 gtk_text_iter_forward_to_line_end(&line_end);
3601 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
3602 len = g_utf8_strlen(str, -1);
3606 g_warning("compose_get_line_break_pos: len = 0!\n");
3610 /* g_print("breaking line: %d: %s (len = %d)\n",
3611 gtk_text_iter_get_line(&iter), str, len); */
3613 attrs = g_new(PangoLogAttr, len + 1);
3615 pango_default_break(str, -1, NULL, attrs, len + 1);
3619 /* skip quote and leading spaces */
3620 for (i = 0; *p != '\0' && i < len; i++) {
3623 wc = g_utf8_get_char(p);
3624 if (i >= quote_len && !g_unichar_isspace(wc))
3626 if (g_unichar_iswide(wc))
3628 else if (*p == '\t')
3632 p = g_utf8_next_char(p);
3635 for (; *p != '\0' && i < len; i++) {
3636 PangoLogAttr *attr = attrs + i;
3640 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
3643 was_white = attr->is_white;
3645 /* don't wrap URI */
3646 if ((uri_len = get_uri_len(p)) > 0) {
3648 if (pos > 0 && col > max_col) {
3658 wc = g_utf8_get_char(p);
3659 if (g_unichar_iswide(wc)) {
3661 if (prev_dont_break && can_break && attr->is_line_break)
3663 } else if (*p == '\t')
3667 if (pos > 0 && col > max_col) {
3672 if (*p == '-' || *p == '/')
3673 prev_dont_break = TRUE;
3675 prev_dont_break = FALSE;
3677 p = g_utf8_next_char(p);
3681 debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col);
3686 *break_pos = *start;
3687 gtk_text_iter_set_line_offset(break_pos, pos);
3692 static gboolean compose_join_next_line(Compose *compose,
3693 GtkTextBuffer *buffer,
3695 const gchar *quote_str)
3697 GtkTextIter iter_ = *iter, cur, prev, next, end;
3698 PangoLogAttr attrs[3];
3700 gchar *next_quote_str;
3703 gboolean keep_cursor = FALSE;
3705 if (!gtk_text_iter_forward_line(&iter_) ||
3706 gtk_text_iter_ends_line(&iter_))