2011-11-13 [colin] 3.7.10cvs85
[claws.git] / src / compose.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2011 Hiroyuki Yamamoto and the Claws Mail team
4  *
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.
9  *
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.
14  *
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/>.
17  * 
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #  include "config.h"
22 #endif
23
24 #include "defs.h"
25
26 #ifndef PANGO_ENABLE_ENGINE
27 #  define PANGO_ENABLE_ENGINE
28 #endif
29
30 #include <glib.h>
31 #include <glib/gi18n.h>
32 #include <gdk/gdkkeysyms.h>
33 #include <gtk/gtk.h>
34
35 #include <pango/pango-break.h>
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <ctype.h>
40 #include <sys/types.h>
41 #include <sys/stat.h>
42 #include <unistd.h>
43 #include <time.h>
44 #include <stdlib.h>
45 #if HAVE_SYS_WAIT_H
46 #  include <sys/wait.h>
47 #endif
48 #include <signal.h>
49 #include <errno.h>
50 #ifndef G_OS_WIN32  /* fixme we should have a configure test. */
51 #include <libgen.h>
52 #endif
53
54 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
55 #  include <wchar.h>
56 #  include <wctype.h>
57 #endif
58
59 #include "claws.h"
60 #include "main.h"
61 #include "mainwindow.h"
62 #include "compose.h"
63 #include "addressbook.h"
64 #include "folderview.h"
65 #include "procmsg.h"
66 #include "menu.h"
67 #include "stock_pixmap.h"
68 #include "send_message.h"
69 #include "imap.h"
70 #include "news.h"
71 #include "customheader.h"
72 #include "prefs_common.h"
73 #include "prefs_account.h"
74 #include "action.h"
75 #include "account.h"
76 #include "filesel.h"
77 #include "procheader.h"
78 #include "procmime.h"
79 #include "statusbar.h"
80 #include "about.h"
81 #include "base64.h"
82 #include "quoted-printable.h"
83 #include "codeconv.h"
84 #include "utils.h"
85 #include "gtkutils.h"
86 #include "gtkshruler.h"
87 #include "socket.h"
88 #include "alertpanel.h"
89 #include "manage_window.h"
90 #include "folder.h"
91 #include "addr_compl.h"
92 #include "quote_fmt.h"
93 #include "undo.h"
94 #include "foldersel.h"
95 #include "toolbar.h"
96 #include "inc.h"
97 #include "message_search.h"
98 #include "combobox.h"
99 #include "hooks.h"
100 #include "privacy.h"
101 #include "timing.h"
102 #include "autofaces.h"
103 #include "spell_entry.h"
104
105 enum
106 {
107         COL_MIMETYPE = 0,
108         COL_SIZE     = 1,
109         COL_NAME     = 2,
110         COL_CHARSET  = 3,
111         COL_DATA     = 4,
112         COL_AUTODATA = 5,
113         N_COL_COLUMNS
114 };
115
116 #define N_ATTACH_COLS   (N_COL_COLUMNS)
117
118 typedef enum
119 {
120         COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE,
121         COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER,
122         COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER,
123         COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD,
124         COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD,
125         COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE,
126         COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE,
127         COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE,
128         COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER,
129         COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER,
130         COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD,
131         COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD,
132         COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE,
133         COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END
134 } ComposeCallAdvancedAction;
135
136 typedef enum
137 {
138         PRIORITY_HIGHEST = 1,
139         PRIORITY_HIGH,
140         PRIORITY_NORMAL,
141         PRIORITY_LOW,
142         PRIORITY_LOWEST
143 } PriorityLevel;
144
145 typedef enum
146 {
147         COMPOSE_INSERT_SUCCESS,
148         COMPOSE_INSERT_READ_ERROR,
149         COMPOSE_INSERT_INVALID_CHARACTER,
150         COMPOSE_INSERT_NO_FILE
151 } ComposeInsertResult;
152
153 typedef enum
154 {
155         COMPOSE_WRITE_FOR_SEND,
156         COMPOSE_WRITE_FOR_STORE
157 } ComposeWriteType;
158
159 typedef enum
160 {
161         COMPOSE_QUOTE_FORCED,
162         COMPOSE_QUOTE_CHECK,
163         COMPOSE_QUOTE_SKIP
164 } ComposeQuoteMode;
165
166 typedef enum {
167     TO_FIELD_PRESENT,
168     SUBJECT_FIELD_PRESENT,
169     BODY_FIELD_PRESENT,
170     NO_FIELD_PRESENT
171 } MailField;
172
173 #define B64_LINE_SIZE           57
174 #define B64_BUFFSIZE            77
175
176 #define MAX_REFERENCES_LEN      999
177
178 static GList *compose_list = NULL;
179
180 static Compose *compose_generic_new                     (PrefsAccount   *account,
181                                                  const gchar    *to,
182                                                  FolderItem     *item,
183                                                  GPtrArray      *attach_files,
184                                                  GList          *listAddress );
185
186 static Compose *compose_create                  (PrefsAccount   *account,
187                                                  FolderItem              *item,
188                                                  ComposeMode     mode,
189                                                  gboolean batch);
190
191 static void compose_entry_mark_default_to       (Compose          *compose,
192                                          const gchar      *address);
193 static Compose *compose_followup_and_reply_to   (MsgInfo        *msginfo,
194                                          ComposeQuoteMode        quote_mode,
195                                          gboolean        to_all,
196                                          gboolean        to_sender,
197                                          const gchar    *body);
198 static Compose *compose_forward_multiple        (PrefsAccount   *account, 
199                                          GSList         *msginfo_list);
200 static Compose *compose_reply                   (MsgInfo        *msginfo,
201                                          ComposeQuoteMode        quote_mode,
202                                          gboolean        to_all,
203                                          gboolean        to_ml,
204                                          gboolean        to_sender,
205                                          const gchar    *body);
206 static Compose *compose_reply_mode              (ComposeMode     mode, 
207                                          GSList         *msginfo_list, 
208                                          gchar          *body);
209 static void compose_template_apply_fields(Compose *compose, Template *tmpl);
210 static void compose_update_privacy_systems_menu(Compose *compose);
211
212 static GtkWidget *compose_account_option_menu_create
213                                                 (Compose        *compose);
214 static void compose_set_out_encoding            (Compose        *compose);
215 static void compose_set_template_menu           (Compose        *compose);
216 static void compose_destroy                     (Compose        *compose);
217
218 static MailField compose_entries_set            (Compose        *compose,
219                                                  const gchar    *mailto,
220                                                  ComposeEntryType to_type);
221 static gint compose_parse_header                (Compose        *compose,
222                                                  MsgInfo        *msginfo);
223 static gchar *compose_parse_references          (const gchar    *ref,
224                                                  const gchar    *msgid);
225
226 static gchar *compose_quote_fmt                 (Compose        *compose,
227                                                  MsgInfo        *msginfo,
228                                                  const gchar    *fmt,
229                                                  const gchar    *qmark,
230                                                  const gchar    *body,
231                                                  gboolean        rewrap,
232                                                  gboolean        need_unescape,
233                                                  const gchar *err_msg);
234
235 static void compose_reply_set_entry             (Compose        *compose,
236                                                  MsgInfo        *msginfo,
237                                                  gboolean        to_all,
238                                                  gboolean        to_ml,
239                                                  gboolean        to_sender,
240                                                  gboolean
241                                                  followup_and_reply_to);
242 static void compose_reedit_set_entry            (Compose        *compose,
243                                                  MsgInfo        *msginfo);
244
245 static void compose_insert_sig                  (Compose        *compose,
246                                                  gboolean        replace);
247 static ComposeInsertResult compose_insert_file  (Compose        *compose,
248                                                  const gchar    *file);
249
250 static gboolean compose_attach_append           (Compose        *compose,
251                                                  const gchar    *file,
252                                                  const gchar    *type,
253                                                  const gchar    *content_type,
254                                                  const gchar    *charset);
255 static void compose_attach_parts                (Compose        *compose,
256                                                  MsgInfo        *msginfo);
257
258 static gboolean compose_beautify_paragraph      (Compose        *compose,
259                                                  GtkTextIter    *par_iter,
260                                                  gboolean        force);
261 static void compose_wrap_all                    (Compose        *compose);
262 static void compose_wrap_all_full               (Compose        *compose,
263                                                  gboolean        autowrap);
264
265 static void compose_set_title                   (Compose        *compose);
266 static void compose_select_account              (Compose        *compose,
267                                                  PrefsAccount   *account,
268                                                  gboolean        init);
269
270 static PrefsAccount *compose_current_mail_account(void);
271 /* static gint compose_send                     (Compose        *compose); */
272 static gboolean compose_check_for_valid_recipient
273                                                 (Compose        *compose);
274 static gboolean compose_check_entries           (Compose        *compose,
275                                                  gboolean       check_everything);
276 static gint compose_write_to_file               (Compose        *compose,
277                                                  FILE           *fp,
278                                                  gint            action,
279                                                  gboolean        attach_parts);
280 static gint compose_write_body_to_file          (Compose        *compose,
281                                                  const gchar    *file);
282 static gint compose_remove_reedit_target        (Compose        *compose,
283                                                  gboolean        force);
284 static void compose_remove_draft                        (Compose        *compose);
285 static gint compose_queue_sub                   (Compose        *compose,
286                                                  gint           *msgnum,
287                                                  FolderItem     **item,
288                                                  gchar          **msgpath,
289                                                  gboolean       check_subject,
290                                                  gboolean       remove_reedit_target);
291 static int compose_add_attachments              (Compose        *compose,
292                                                  MimeInfo       *parent);
293 static gchar *compose_get_header                (Compose        *compose);
294
295 static void compose_convert_header              (Compose        *compose,
296                                                  gchar          *dest,
297                                                  gint            len,
298                                                  gchar          *src,
299                                                  gint            header_len,
300                                                  gboolean        addr_field);
301
302 static void compose_attach_info_free            (AttachInfo     *ainfo);
303 static void compose_attach_remove_selected      (GtkAction      *action,
304                                                  gpointer        data);
305
306 static void compose_template_apply              (Compose        *compose,
307                                                  Template       *tmpl,
308                                                  gboolean        replace);
309 static void compose_attach_property             (GtkAction      *action,
310                                                  gpointer        data);
311 static void compose_attach_property_create      (gboolean       *cancelled);
312 static void attach_property_ok                  (GtkWidget      *widget,
313                                                  gboolean       *cancelled);
314 static void attach_property_cancel              (GtkWidget      *widget,
315                                                  gboolean       *cancelled);
316 static gint attach_property_delete_event        (GtkWidget      *widget,
317                                                  GdkEventAny    *event,
318                                                  gboolean       *cancelled);
319 static gboolean attach_property_key_pressed     (GtkWidget      *widget,
320                                                  GdkEventKey    *event,
321                                                  gboolean       *cancelled);
322
323 static void compose_exec_ext_editor             (Compose        *compose);
324 #ifdef G_OS_UNIX
325 static gint compose_exec_ext_editor_real        (const gchar    *file);
326 static gboolean compose_ext_editor_kill         (Compose        *compose);
327 static gboolean compose_input_cb                (GIOChannel     *source,
328                                                  GIOCondition    condition,
329                                                  gpointer        data);
330 static void compose_set_ext_editor_sensitive    (Compose        *compose,
331                                                  gboolean        sensitive);
332 #endif /* G_OS_UNIX */
333
334 static void compose_undo_state_changed          (UndoMain       *undostruct,
335                                                  gint            undo_state,
336                                                  gint            redo_state,
337                                                  gpointer        data);
338
339 static void compose_create_header_entry (Compose *compose);
340 static void compose_add_header_entry    (Compose *compose, const gchar *header,
341                                          gchar *text, ComposePrefType pref_type);
342 static void compose_remove_header_entries(Compose *compose);
343
344 static void compose_update_priority_menu_item(Compose * compose);
345 #if USE_ENCHANT
346 static void compose_spell_menu_changed  (void *data);
347 static void compose_dict_changed        (void *data);
348 #endif
349 static void compose_add_field_list      ( Compose *compose,
350                                           GList *listAddress );
351
352 /* callback functions */
353
354 static gboolean compose_edit_size_alloc (GtkEditable    *widget,
355                                          GtkAllocation  *allocation,
356                                          GtkSHRuler     *shruler);
357 static void account_activated           (GtkComboBox *optmenu,
358                                          gpointer        data);
359 static void attach_selected             (GtkTreeView    *tree_view, 
360                                          GtkTreePath    *tree_path,
361                                          GtkTreeViewColumn *column, 
362                                          Compose *compose);
363 static gboolean attach_button_pressed   (GtkWidget      *widget,
364                                          GdkEventButton *event,
365                                          gpointer        data);
366 static gboolean attach_key_pressed      (GtkWidget      *widget,
367                                          GdkEventKey    *event,
368                                          gpointer        data);
369 static void compose_send_cb             (GtkAction      *action, gpointer data);
370 static void compose_send_later_cb       (GtkAction      *action, gpointer data);
371
372 static void compose_save_cb             (GtkAction      *action,
373                                          gpointer        data);
374
375 static void compose_attach_cb           (GtkAction      *action,
376                                          gpointer        data);
377 static void compose_insert_file_cb      (GtkAction      *action,
378                                          gpointer        data);
379 static void compose_insert_sig_cb       (GtkAction      *action,
380                                          gpointer        data);
381
382 static void compose_close_cb            (GtkAction      *action,
383                                          gpointer        data);
384 static void compose_print_cb            (GtkAction      *action,
385                                          gpointer        data);
386
387 static void compose_set_encoding_cb     (GtkAction      *action, GtkRadioAction *current, gpointer data);
388
389 static void compose_address_cb          (GtkAction      *action,
390                                          gpointer        data);
391 static void about_show_cb               (GtkAction      *action,
392                                          gpointer        data);
393 static void compose_template_activate_cb(GtkWidget      *widget,
394                                          gpointer        data);
395
396 static void compose_ext_editor_cb       (GtkAction      *action,
397                                          gpointer        data);
398
399 static gint compose_delete_cb           (GtkWidget      *widget,
400                                          GdkEventAny    *event,
401                                          gpointer        data);
402
403 static void compose_undo_cb             (GtkAction      *action,
404                                          gpointer        data);
405 static void compose_redo_cb             (GtkAction      *action,
406                                          gpointer        data);
407 static void compose_cut_cb              (GtkAction      *action,
408                                          gpointer        data);
409 static void compose_copy_cb             (GtkAction      *action,
410                                          gpointer        data);
411 static void compose_paste_cb            (GtkAction      *action,
412                                          gpointer        data);
413 static void compose_paste_as_quote_cb   (GtkAction      *action,
414                                          gpointer        data);
415 static void compose_paste_no_wrap_cb    (GtkAction      *action,
416                                          gpointer        data);
417 static void compose_paste_wrap_cb       (GtkAction      *action,
418                                          gpointer        data);
419 static void compose_allsel_cb           (GtkAction      *action,
420                                          gpointer        data);
421
422 static void compose_advanced_action_cb  (GtkAction      *action,
423                                          gpointer        data);
424
425 static void compose_grab_focus_cb       (GtkWidget      *widget,
426                                          Compose        *compose);
427
428 static void compose_changed_cb          (GtkTextBuffer  *textbuf,
429                                          Compose        *compose);
430
431 static void compose_wrap_cb             (GtkAction      *action,
432                                          gpointer        data);
433 static void compose_wrap_all_cb         (GtkAction      *action,
434                                          gpointer        data);
435 static void compose_find_cb             (GtkAction      *action,
436                                          gpointer        data);
437 static void compose_toggle_autowrap_cb  (GtkToggleAction *action,
438                                          gpointer        data);
439 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
440                                          gpointer        data);
441
442 static void compose_toggle_ruler_cb     (GtkToggleAction *action,
443                                          gpointer        data);
444 static void compose_toggle_sign_cb      (GtkToggleAction *action,
445                                          gpointer        data);
446 static void compose_toggle_encrypt_cb   (GtkToggleAction *action,
447                                          gpointer        data);
448 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data);
449 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn);
450 static void activate_privacy_system     (Compose *compose, 
451                                          PrefsAccount *account,
452                                          gboolean warn);
453 static void compose_use_signing(Compose *compose, gboolean use_signing);
454 static void compose_use_encryption(Compose *compose, gboolean use_encryption);
455 static void compose_toggle_return_receipt_cb(GtkToggleAction *action,
456                                          gpointer        data);
457 static void compose_toggle_remove_refs_cb(GtkToggleAction *action,
458                                          gpointer        data);
459 static void compose_set_priority_cb     (GtkAction *action, GtkRadioAction *current, gpointer data);
460 static void compose_reply_change_mode   (Compose *compose, ComposeMode action);
461 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data);
462
463 static void compose_attach_drag_received_cb (GtkWidget          *widget,
464                                              GdkDragContext     *drag_context,
465                                              gint                x,
466                                              gint                y,
467                                              GtkSelectionData   *data,
468                                              guint               info,
469                                              guint               time,
470                                              gpointer            user_data);
471 static void compose_insert_drag_received_cb (GtkWidget          *widget,
472                                              GdkDragContext     *drag_context,
473                                              gint                x,
474                                              gint                y,
475                                              GtkSelectionData   *data,
476                                              guint               info,
477                                              guint               time,
478                                              gpointer            user_data);
479 static void compose_header_drag_received_cb (GtkWidget          *widget,
480                                              GdkDragContext     *drag_context,
481                                              gint                x,
482                                              gint                y,
483                                              GtkSelectionData   *data,
484                                              guint               info,
485                                              guint               time,
486                                              gpointer            user_data);
487
488 static gboolean compose_drag_drop           (GtkWidget *widget,
489                                              GdkDragContext *drag_context,
490                                              gint x, gint y,
491                                              guint time, gpointer user_data);
492
493 static void text_inserted               (GtkTextBuffer  *buffer,
494                                          GtkTextIter    *iter,
495                                          const gchar    *text,
496                                          gint            len,
497                                          Compose        *compose);
498 static Compose *compose_generic_reply(MsgInfo *msginfo,
499                                   ComposeQuoteMode quote_mode,
500                                   gboolean to_all,
501                                   gboolean to_ml,
502                                   gboolean to_sender,
503                                   gboolean followup_and_reply_to,
504                                   const gchar *body);
505
506 static void compose_headerentry_changed_cb         (GtkWidget          *entry,
507                                             ComposeHeaderEntry *headerentry);
508 static gboolean compose_headerentry_key_press_event_cb(GtkWidget               *entry,
509                                             GdkEventKey        *event,
510                                             ComposeHeaderEntry *headerentry);
511 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
512                                         ComposeHeaderEntry *headerentry);
513
514 static void compose_show_first_last_header (Compose *compose, gboolean show_first);
515
516 static void compose_allow_user_actions (Compose *compose, gboolean allow);
517
518 static void compose_nothing_cb             (GtkAction *action, gpointer data)
519 {
520
521 }
522
523 #if USE_ENCHANT
524 static void compose_check_all              (GtkAction *action, gpointer data);
525 static void compose_highlight_all          (GtkAction *action, gpointer data);
526 static void compose_check_backwards        (GtkAction *action, gpointer data);
527 static void compose_check_forwards_go      (GtkAction *action, gpointer data);
528 #endif
529
530 static PrefsAccount *compose_guess_forward_account_from_msginfo (MsgInfo *msginfo);
531
532 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose);
533
534 #ifdef USE_ENCHANT
535 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
536                                                 FolderItem *folder_item);
537 #endif
538 static void compose_attach_update_label(Compose *compose);
539 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
540                                      gboolean respect_default_to);
541
542 static GtkActionEntry compose_popup_entries[] =
543 {
544         {"Compose",                     NULL, "Compose" },
545         {"Compose/Add",                 NULL, N_("_Add..."), NULL, NULL, G_CALLBACK(compose_attach_cb) },
546         {"Compose/Remove",                      NULL, N_("_Remove"), NULL, NULL, G_CALLBACK(compose_attach_remove_selected) },
547         {"Compose/---",                 NULL, "---", NULL, NULL, NULL },
548         {"Compose/Properties",          NULL, N_("_Properties..."), NULL, NULL, G_CALLBACK(compose_attach_property) },
549 };
550
551 static GtkActionEntry compose_entries[] =
552 {
553         {"Menu",                                NULL, "Menu" },
554 /* menus */
555         {"Message",                     NULL, N_("_Message") },
556         {"Edit",                        NULL, N_("_Edit") },
557 #if USE_ENCHANT
558         {"Spelling",                    NULL, N_("_Spelling") },
559 #endif
560         {"Options",                     NULL, N_("_Options") },
561         {"Tools",                       NULL, N_("_Tools") },
562         {"Help",                        NULL, N_("_Help") },
563 /* Message menu */
564         {"Message/Send",                NULL, N_("S_end"), "<control>Return", NULL, G_CALLBACK(compose_send_cb) },
565         {"Message/SendLater",           NULL, N_("Send _later"), "<shift><control>S", NULL, G_CALLBACK(compose_send_later_cb) },
566         {"Message/---",                 NULL, "---" },
567
568         {"Message/AttachFile",          NULL, N_("_Attach file"), "<control>M", NULL, G_CALLBACK(compose_attach_cb) },
569         {"Message/InsertFile",          NULL, N_("_Insert file"), "<control>I", NULL, G_CALLBACK(compose_insert_file_cb) },
570         {"Message/InsertSig",           NULL, N_("Insert si_gnature"), "<control>G", NULL, G_CALLBACK(compose_insert_sig_cb) },
571         /* {"Message/---",              NULL, "---" }, */
572         {"Message/Save",                NULL, N_("_Save"), "<control>S", NULL, G_CALLBACK(compose_save_cb) }, /*COMPOSE_KEEP_EDITING*/
573         /* {"Message/---",              NULL, "---" }, */
574         {"Message/Print",               NULL, N_("_Print"), NULL, NULL, G_CALLBACK(compose_print_cb) },
575         /* {"Message/---",              NULL, "---" }, */
576         {"Message/Close",               NULL, N_("_Close"), "<control>W", NULL, G_CALLBACK(compose_close_cb) },
577
578 /* Edit menu */
579         {"Edit/Undo",                   NULL, N_("_Undo"), "<control>Z", NULL, G_CALLBACK(compose_undo_cb) },
580         {"Edit/Redo",                   NULL, N_("_Redo"), "<control>Y", NULL, G_CALLBACK(compose_redo_cb) },
581         {"Edit/---",                    NULL, "---" },
582
583         {"Edit/Cut",                    NULL, N_("Cu_t"), "<control>X", NULL, G_CALLBACK(compose_cut_cb) },
584         {"Edit/Copy",                   NULL, N_("_Copy"), "<control>C", NULL, G_CALLBACK(compose_copy_cb) },
585         {"Edit/Paste",                  NULL, N_("_Paste"), "<control>V", NULL, G_CALLBACK(compose_paste_cb) },
586
587         {"Edit/SpecialPaste",           NULL, N_("_Special paste") },
588         {"Edit/SpecialPaste/AsQuotation",       NULL, N_("as _quotation"), NULL, NULL, G_CALLBACK(compose_paste_as_quote_cb) },
589         {"Edit/SpecialPaste/Wrapped",   NULL, N_("_wrapped"), NULL, NULL, G_CALLBACK(compose_paste_wrap_cb) },
590         {"Edit/SpecialPaste/Unwrapped", NULL, N_("_unwrapped"), NULL, NULL, G_CALLBACK(compose_paste_no_wrap_cb) },
591
592         {"Edit/SelectAll",              NULL, N_("Select _all"), "<control>A", NULL, G_CALLBACK(compose_allsel_cb) },
593
594         {"Edit/Advanced",               NULL, N_("A_dvanced") },
595         {"Edit/Advanced/BackChar",      NULL, N_("Move a character backward"), "<shift><control>B", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER*/
596         {"Edit/Advanced/ForwChar",      NULL, N_("Move a character forward"), "<shift><control>F", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER*/
597         {"Edit/Advanced/BackWord",      NULL, N_("Move a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD*/
598         {"Edit/Advanced/ForwWord",      NULL, N_("Move a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD*/
599         {"Edit/Advanced/BegLine",       NULL, N_("Move to beginning of line"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE*/
600         {"Edit/Advanced/EndLine",       NULL, N_("Move to end of line"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE*/
601         {"Edit/Advanced/PrevLine",      NULL, N_("Move to previous line"), "<control>P", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE*/
602         {"Edit/Advanced/NextLine",      NULL, N_("Move to next line"), "<control>N", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE*/
603         {"Edit/Advanced/DelBackChar",   NULL, N_("Delete a character backward"), "<control>H", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER*/
604         {"Edit/Advanced/DelForwChar",   NULL, N_("Delete a character forward"), "<control>D", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER*/
605         {"Edit/Advanced/DelBackWord",   NULL, N_("Delete a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD*/
606         {"Edit/Advanced/DelForwWord",   NULL, N_("Delete a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD*/
607         {"Edit/Advanced/DelLine",       NULL, N_("Delete line"), "<control>U", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE*/
608         {"Edit/Advanced/DelEndLine",    NULL, N_("Delete to end of line"), "<control>K", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END*/
609
610         /* {"Edit/---",                 NULL, "---" }, */
611         {"Edit/Find",           NULL, N_("_Find"), "<control>F", NULL, G_CALLBACK(compose_find_cb) },
612
613         /* {"Edit/---",                 NULL, "---" }, */
614         {"Edit/WrapPara",               NULL, N_("_Wrap current paragraph"), "<control>L", NULL, G_CALLBACK(compose_wrap_cb) }, /* 0 */
615         {"Edit/WrapAllLines",           NULL, N_("Wrap all long _lines"), "<control><alt>L", NULL, G_CALLBACK(compose_wrap_all_cb) }, /* 1 */
616         /* {"Edit/---",                 NULL, "---" }, */
617         {"Edit/ExtEditor",              NULL, N_("Edit with e_xternal editor"), "<shift><control>X", NULL, G_CALLBACK(compose_ext_editor_cb) },
618 #if USE_ENCHANT
619 /* Spelling menu */
620         {"Spelling/CheckAllSel",        NULL, N_("_Check all or check selection"), NULL, NULL, G_CALLBACK(compose_check_all) },
621         {"Spelling/HighlightAll",       NULL, N_("_Highlight all misspelled words"), NULL, NULL, G_CALLBACK(compose_highlight_all) },
622         {"Spelling/CheckBackwards",     NULL, N_("Check _backwards misspelled word"), NULL, NULL, G_CALLBACK(compose_check_backwards) },
623         {"Spelling/ForwardNext",        NULL, N_("_Forward to next misspelled word"), NULL, NULL, G_CALLBACK(compose_check_forwards_go) },
624
625         {"Spelling/---",                NULL, "---" },
626         {"Spelling/Options",            NULL, N_("_Options") },
627 #endif
628
629 /* Options menu */
630
631         {"Options/ReplyMode",           NULL, N_("Reply _mode") },
632         {"Options/---",                 NULL, "---" },
633         {"Options/PrivacySystem",       NULL, N_("Privacy _System") },
634         {"Options/PrivacySystem/PlaceHolder",   NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
635
636         /* {"Options/---",              NULL, "---" }, */
637
638         {"Options/Priority",            NULL, N_("_Priority") },
639
640         {"Options/Encoding",            NULL, N_("Character _encoding") },
641         {"Options/Encoding/---",        NULL, "---" },
642 #define ENC_ACTION(cs_char,c_char,string) \
643         { "Options/Encoding/" cs_char, NULL, N_(string), NULL, NULL, c_char }
644
645         {"Options/Encoding/Western",    NULL, N_("Western European") },
646         {"Options/Encoding/Baltic",     NULL, N_("Baltic") },
647         {"Options/Encoding/Hebrew",     NULL, N_("Hebrew") },
648         {"Options/Encoding/Arabic",     NULL, N_("Arabic") },
649         {"Options/Encoding/Cyrillic",   NULL, N_("Cyrillic") },
650         {"Options/Encoding/Japanese",   NULL, N_("Japanese") },
651         {"Options/Encoding/Chinese",    NULL, N_("Chinese") },
652         {"Options/Encoding/Korean",     NULL, N_("Korean") },
653         {"Options/Encoding/Thai",       NULL, N_("Thai") },
654
655 /* Tools menu */
656         {"Tools/AddressBook",           NULL, N_("_Address book"), NULL, NULL, G_CALLBACK(compose_address_cb) }, 
657
658         {"Tools/Template",      NULL, N_("_Template") },
659         {"Tools/Template/PlaceHolder",  NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
660         {"Tools/Actions",       NULL, N_("Actio_ns") },
661         {"Tools/Actions/PlaceHolder",   NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
662
663 /* Help menu */
664         {"Help/About",          NULL, N_("_About"), NULL, NULL, G_CALLBACK(about_show_cb) }, 
665 };
666
667 static GtkToggleActionEntry compose_toggle_entries[] =
668 {
669         {"Edit/AutoWrap",               NULL, N_("Aut_o wrapping"), "<shift><control>L", NULL, G_CALLBACK(compose_toggle_autowrap_cb) }, /* TOGGLE */
670         {"Edit/AutoIndent",             NULL, N_("Auto _indent"), NULL, NULL, G_CALLBACK(compose_toggle_autoindent_cb) }, /* TOGGLE */
671         {"Options/Sign",                NULL, N_("Si_gn"), NULL, NULL, G_CALLBACK(compose_toggle_sign_cb) }, /* Toggle */
672         {"Options/Encrypt",             NULL, N_("_Encrypt"), NULL, NULL, G_CALLBACK(compose_toggle_encrypt_cb) }, /* Toggle */
673         {"Options/RequestRetRcpt",      NULL, N_("_Request Return Receipt"), NULL, NULL, G_CALLBACK(compose_toggle_return_receipt_cb) }, /* TOGGLE */
674         {"Options/RemoveReferences",    NULL, N_("Remo_ve references"), NULL, NULL, G_CALLBACK(compose_toggle_remove_refs_cb) }, /* TOGGLE */
675         {"Tools/ShowRuler",             NULL, N_("Show _ruler"), NULL, NULL, G_CALLBACK(compose_toggle_ruler_cb) }, /* Toggle */
676 };
677
678 static GtkRadioActionEntry compose_radio_rm_entries[] =
679 {
680         {"Options/ReplyMode/Normal",    NULL, N_("_Normal"), NULL, NULL, COMPOSE_REPLY }, /* RADIO compose_reply_change_mode_cb */
681         {"Options/ReplyMode/All",       NULL, N_("_All"), NULL, NULL, COMPOSE_REPLY_TO_ALL }, /* RADIO compose_reply_change_mode_cb */
682         {"Options/ReplyMode/Sender",    NULL, N_("_Sender"), NULL, NULL, COMPOSE_REPLY_TO_SENDER }, /* RADIO compose_reply_change_mode_cb */
683         {"Options/ReplyMode/List",      NULL, N_("_Mailing-list"), NULL, NULL, COMPOSE_REPLY_TO_LIST }, /* RADIO compose_reply_change_mode_cb */
684 };
685
686 static GtkRadioActionEntry compose_radio_prio_entries[] =
687 {
688         {"Options/Priority/Highest",    NULL, N_("_Highest"), NULL, NULL, PRIORITY_HIGHEST }, /* RADIO compose_set_priority_cb */
689         {"Options/Priority/High",       NULL, N_("Hi_gh"), NULL, NULL, PRIORITY_HIGH }, /* RADIO compose_set_priority_cb */
690         {"Options/Priority/Normal",     NULL, N_("_Normal"), NULL, NULL, PRIORITY_NORMAL }, /* RADIO compose_set_priority_cb */
691         {"Options/Priority/Low",        NULL, N_("Lo_w"), NULL, NULL, PRIORITY_LOW }, /* RADIO compose_set_priority_cb */
692         {"Options/Priority/Lowest",     NULL, N_("_Lowest"), NULL, NULL, PRIORITY_LOWEST }, /* RADIO compose_set_priority_cb */
693 };
694
695 static GtkRadioActionEntry compose_radio_enc_entries[] =
696 {
697         ENC_ACTION(CS_AUTO, C_AUTO, N_("_Automatic")), /* RADIO compose_set_encoding_cb */
698         ENC_ACTION(CS_US_ASCII, C_US_ASCII, N_("7bit ASCII (US-ASC_II)")), /* RADIO compose_set_encoding_cb */
699         ENC_ACTION(CS_UTF_8, C_UTF_8, N_("Unicode (_UTF-8)")), /* RADIO compose_set_encoding_cb */
700         ENC_ACTION("Western/"CS_ISO_8859_1, C_ISO_8859_1, "ISO-8859-_1"), /* RADIO compose_set_encoding_cb */
701         ENC_ACTION("Western/"CS_ISO_8859_15, C_ISO_8859_15, "ISO-8859-15"), /* RADIO compose_set_encoding_cb */
702         ENC_ACTION("Western/"CS_WINDOWS_1252, C_WINDOWS_1252, "Windows-1252"), /* RADIO compose_set_encoding_cb */
703         ENC_ACTION(CS_ISO_8859_2, C_ISO_8859_2, N_("Central European (ISO-8859-_2)")), /* RADIO compose_set_encoding_cb */
704         ENC_ACTION("Baltic/"CS_ISO_8859_13, C_ISO_8859_13, "ISO-8859-13"), /* RADIO compose_set_encoding_cb */
705         ENC_ACTION("Baltic/"CS_ISO_8859_4, C_ISO_8859_14, "ISO-8859-_4"), /* RADIO compose_set_encoding_cb */
706         ENC_ACTION(CS_ISO_8859_7, C_ISO_8859_7, N_("Greek (ISO-8859-_7)")), /* RADIO compose_set_encoding_cb */
707         ENC_ACTION("Hebrew/"CS_ISO_8859_8, C_ISO_8859_8, "ISO-8859-_8"), /* RADIO compose_set_encoding_cb */
708         ENC_ACTION("Hebrew/"CS_WINDOWS_1255, C_WINDOWS_1255, "Windows-1255"), /* RADIO compose_set_encoding_cb */
709         ENC_ACTION("Arabic/"CS_ISO_8859_6, C_ISO_8859_6, "ISO-8859-_6"), /* RADIO compose_set_encoding_cb */
710         ENC_ACTION("Arabic/"CS_WINDOWS_1256, C_WINDOWS_1256, "Windows-1256"), /* RADIO compose_set_encoding_cb */
711         ENC_ACTION(CS_ISO_8859_9, C_ISO_8859_9, N_("Turkish (ISO-8859-_9)")), /* RADIO compose_set_encoding_cb */
712         ENC_ACTION("Cyrillic/"CS_ISO_8859_5, C_ISO_8859_5, "ISO-8859-_5"), /* RADIO compose_set_encoding_cb */
713         ENC_ACTION("Cyrillic/"CS_KOI8_R, C_KOI8_R, "KOI8-_R"), /* RADIO compose_set_encoding_cb */
714         ENC_ACTION("Cyrillic/"CS_KOI8_U, C_KOI8_U, "KOI8-_U"), /* RADIO compose_set_encoding_cb */
715         ENC_ACTION("Cyrillic/"CS_WINDOWS_1251, C_WINDOWS_1251, "Windows-1251"), /* RADIO compose_set_encoding_cb */
716         ENC_ACTION("Japanese/"CS_ISO_2022_JP, C_ISO_2022_JP, "ISO-2022-_JP"), /* RADIO compose_set_encoding_cb */
717         ENC_ACTION("Japanese/"CS_ISO_2022_JP_2, C_ISO_2022_JP_2, "ISO-2022-JP-_2"), /* RADIO compose_set_encoding_cb */
718         ENC_ACTION("Japanese/"CS_EUC_JP, C_EUC_JP, "_EUC-JP"), /* RADIO compose_set_encoding_cb */
719         ENC_ACTION("Japanese/"CS_SHIFT_JIS, C_SHIFT_JIS, "_Shift-JIS"), /* RADIO compose_set_encoding_cb */
720         ENC_ACTION("Chinese/"CS_GB18030, C_GB18030, "_GB18030"), /* RADIO compose_set_encoding_cb */
721         ENC_ACTION("Chinese/"CS_GB2312, C_GB2312, "_GB2312"), /* RADIO compose_set_encoding_cb */
722         ENC_ACTION("Chinese/"CS_GBK, C_GBK, "GB_K"), /* RADIO compose_set_encoding_cb */
723         ENC_ACTION("Chinese/"CS_BIG5, C_BIG5, "_Big5-JP"), /* RADIO compose_set_encoding_cb */
724         ENC_ACTION("Chinese/"CS_EUC_TW, C_EUC_TW, "EUC-_TW"), /* RADIO compose_set_encoding_cb */
725         ENC_ACTION("Korean/"CS_EUC_KR, C_EUC_KR, "_EUC-KR"), /* RADIO compose_set_encoding_cb */
726         ENC_ACTION("Korean/"CS_ISO_2022_KR, C_ISO_2022_KR, "_ISO-2022-KR"), /* RADIO compose_set_encoding_cb */
727         ENC_ACTION("Thai/"CS_TIS_620, C_TIS_620, "_TIS-620-KR"), /* RADIO compose_set_encoding_cb */
728         ENC_ACTION("Thai/"CS_WINDOWS_874, C_WINDOWS_874, "_Windows-874"), /* RADIO compose_set_encoding_cb */
729 };
730
731 static GtkTargetEntry compose_mime_types[] =
732 {
733         {"text/uri-list", 0, 0},
734         {"UTF8_STRING", 0, 0},
735         {"text/plain", 0, 0}
736 };
737
738 static gboolean compose_put_existing_to_front(MsgInfo *info)
739 {
740         GList *compose_list = compose_get_compose_list();
741         GList *elem = NULL;
742         
743         if (compose_list) {
744                 for (elem = compose_list; elem != NULL && elem->data != NULL; 
745                      elem = elem->next) {
746                         Compose *c = (Compose*)elem->data;
747
748                         if (!c->targetinfo || !c->targetinfo->msgid ||
749                             !info->msgid)
750                                 continue;
751
752                         if (!strcmp(c->targetinfo->msgid, info->msgid)) {
753                                 gtkut_window_popup(c->window);
754                                 return TRUE;
755                         }
756                 }
757         }
758         return FALSE;
759 }
760
761 static GdkColor quote_color1 = 
762         {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
763 static GdkColor quote_color2 = 
764         {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
765 static GdkColor quote_color3 = 
766         {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
767
768 static GdkColor quote_bgcolor1 = 
769         {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
770 static GdkColor quote_bgcolor2 = 
771         {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
772 static GdkColor quote_bgcolor3 = 
773         {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
774
775 static GdkColor signature_color = {
776         (gulong)0,
777         (gushort)0x7fff,
778         (gushort)0x7fff,
779         (gushort)0x7fff
780 };
781
782 static GdkColor uri_color = {
783         (gulong)0,
784         (gushort)0,
785         (gushort)0,
786         (gushort)0
787 };
788
789 static void compose_create_tags(GtkTextView *text, Compose *compose)
790 {
791         GtkTextBuffer *buffer;
792         GdkColor black = {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
793 #if !GTK_CHECK_VERSION(2, 24, 0)
794         GdkColormap *cmap;
795         gboolean success[8];
796         int i;
797 #endif
798         GdkColor color[8];
799
800         buffer = gtk_text_view_get_buffer(text);
801
802         if (prefs_common.enable_color) {
803                 /* grab the quote colors, converting from an int to a GdkColor */
804                 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
805                                                &quote_color1);
806                 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
807                                                &quote_color2);
808                 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
809                                                &quote_color3);
810                 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_bgcol,
811                                                &quote_bgcolor1);
812                 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_bgcol,
813                                                &quote_bgcolor2);
814                 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_bgcol,
815                                                &quote_bgcolor3);
816                 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
817                                                &signature_color);
818                 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
819                                                &uri_color);
820         } else {
821                 signature_color = quote_color1 = quote_color2 = quote_color3 = 
822                         quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
823         }
824
825         if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
826                 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
827                                            "foreground-gdk", &quote_color1,
828                                            "paragraph-background-gdk", &quote_bgcolor1,
829                                            NULL);
830                 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
831                                            "foreground-gdk", &quote_color2,
832                                            "paragraph-background-gdk", &quote_bgcolor2,
833                                            NULL);
834                 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
835                                            "foreground-gdk", &quote_color3,
836                                            "paragraph-background-gdk", &quote_bgcolor3,
837                                            NULL);
838         } else {
839                 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
840                                            "foreground-gdk", &quote_color1,
841                                            NULL);
842                 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
843                                            "foreground-gdk", &quote_color2,
844                                            NULL);
845                 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
846                                            "foreground-gdk", &quote_color3,
847                                            NULL);
848         }
849         
850         compose->signature_tag = gtk_text_buffer_create_tag(buffer, "signature",
851                                    "foreground-gdk", &signature_color,
852                                    NULL);
853         
854         compose->uri_tag = gtk_text_buffer_create_tag(buffer, "link",
855                                         "foreground-gdk", &uri_color,
856                                          NULL);
857         compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
858         compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
859
860         color[0] = quote_color1;
861         color[1] = quote_color2;
862         color[2] = quote_color3;
863         color[3] = quote_bgcolor1;
864         color[4] = quote_bgcolor2;
865         color[5] = quote_bgcolor3;
866         color[6] = signature_color;
867         color[7] = uri_color;
868 #if !GTK_CHECK_VERSION(2, 24, 0)
869         cmap = gdk_drawable_get_colormap(gtk_widget_get_window(compose->window));
870         gdk_colormap_alloc_colors(cmap, color, 8, FALSE, TRUE, success);
871
872         for (i = 0; i < 8; i++) {
873                 if (success[i] == FALSE) {
874                         GtkStyle *style;
875
876                         g_warning("Compose: color allocation failed.\n");
877                         style = gtk_widget_get_style(GTK_WIDGET(text));
878                         quote_color1 = quote_color2 = quote_color3 = 
879                                 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = 
880                                 signature_color = uri_color = black;
881                 }
882         }
883 #endif
884 }
885
886 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
887                      GPtrArray *attach_files)
888 {
889         return compose_generic_new(account, mailto, NULL, attach_files, NULL);
890 }
891
892 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
893 {
894         return compose_generic_new(account, mailto, item, NULL, NULL);
895 }
896
897 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
898 {
899         return compose_generic_new( account, NULL, NULL, NULL, listAddress );
900 }
901
902 #define SCROLL_TO_CURSOR(compose) {                             \
903         GtkTextMark *cmark = gtk_text_buffer_get_insert(        \
904                 gtk_text_view_get_buffer(                       \
905                         GTK_TEXT_VIEW(compose->text)));         \
906         gtk_text_view_scroll_mark_onscreen(                     \
907                 GTK_TEXT_VIEW(compose->text),                   \
908                 cmark);                                         \
909 }
910
911 static void compose_set_save_to(Compose *compose, const gchar *folderidentifier)
912 {
913         GtkEditable *entry;
914         if (folderidentifier) {
915                 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
916                 prefs_common.compose_save_to_history = add_history(
917                                 prefs_common.compose_save_to_history, folderidentifier);
918                 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
919                                 prefs_common.compose_save_to_history);
920         }
921
922         entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
923         if (folderidentifier)
924                 gtk_entry_set_text(GTK_ENTRY(entry), folderidentifier);
925         else
926                 gtk_entry_set_text(GTK_ENTRY(entry), "");
927 }
928
929 static gchar *compose_get_save_to(Compose *compose)
930 {
931         GtkEditable *entry;
932         gchar *result = NULL;
933         entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
934         result = gtk_editable_get_chars(entry, 0, -1);
935         
936         if (result) {
937                 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
938                 prefs_common.compose_save_to_history = add_history(
939                                 prefs_common.compose_save_to_history, result);
940                 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
941                                 prefs_common.compose_save_to_history);
942         }
943         return result;
944 }
945
946 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
947                              GPtrArray *attach_files, GList *listAddress )
948 {
949         Compose *compose;
950         GtkTextView *textview;
951         GtkTextBuffer *textbuf;
952         GtkTextIter iter;
953         const gchar *subject_format = NULL;
954         const gchar *body_format = NULL;
955         gchar *mailto_from = NULL;
956         PrefsAccount *mailto_account = NULL;
957         MsgInfo* dummyinfo = NULL;
958         gint cursor_pos = -1;
959         MailField mfield = NO_FIELD_PRESENT;
960         gchar* buf;
961         GtkTextMark *mark;
962
963         /* check if mailto defines a from */
964         if (mailto && *mailto != '\0') {
965                 scan_mailto_url(mailto, &mailto_from, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
966                 /* mailto defines a from, check if we can get account prefs from it,
967                    if not, the account prefs will be guessed using other ways, but we'll keep
968                    the from anyway */
969                 if (mailto_from)
970                         mailto_account = account_find_from_address(mailto_from, TRUE);
971                 if (mailto_account)
972                         account = mailto_account;
973         }
974
975         /* if no account prefs set from mailto, set if from folder prefs (if any) */
976         if (!mailto_account && item && item->prefs && item->prefs->enable_default_account)
977                 account = account_find_from_id(item->prefs->default_account);
978
979         /* if no account prefs set, fallback to the current one */
980         if (!account) account = cur_account;
981         cm_return_val_if_fail(account != NULL, NULL);
982
983         compose = compose_create(account, item, COMPOSE_NEW, FALSE);
984
985         /* override from name if mailto asked for it */
986         if (mailto_from) {
987                 gtk_entry_set_text(GTK_ENTRY(compose->from_name), mailto_from);
988                 g_free(mailto_from);
989         } else
990                 /* override from name according to folder properties */
991                 if (item && item->prefs &&
992                         item->prefs->compose_with_format &&
993                         item->prefs->compose_override_from_format &&
994                         *item->prefs->compose_override_from_format != '\0') {
995
996                         gchar *tmp = NULL;
997                         gchar *buf = NULL;
998
999                         dummyinfo = compose_msginfo_new_from_compose(compose);
1000
1001                         /* decode \-escape sequences in the internal representation of the quote format */
1002                         tmp = g_malloc(strlen(item->prefs->compose_override_from_format)+1);
1003                         pref_get_unescaped_pref(tmp, item->prefs->compose_override_from_format);
1004
1005 #ifdef USE_ENCHANT
1006                         quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1007                                         compose->gtkaspell);
1008 #else
1009                         quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1010 #endif
1011                         quote_fmt_scan_string(tmp);
1012                         quote_fmt_parse();
1013
1014                         buf = quote_fmt_get_buffer();
1015                         if (buf == NULL)
1016                                 alertpanel_error(_("New message From format error."));
1017                         else
1018                                 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1019                         quote_fmt_reset_vartable();
1020
1021                         g_free(tmp);
1022                 }
1023
1024         compose->replyinfo = NULL;
1025         compose->fwdinfo   = NULL;
1026
1027         textview = GTK_TEXT_VIEW(compose->text);
1028         textbuf = gtk_text_view_get_buffer(textview);
1029         compose_create_tags(textview, compose);
1030
1031         undo_block(compose->undostruct);
1032 #ifdef USE_ENCHANT
1033         compose_set_dictionaries_from_folder_prefs(compose, item);
1034 #endif
1035
1036         if (account->auto_sig)
1037                 compose_insert_sig(compose, FALSE);
1038         gtk_text_buffer_get_start_iter(textbuf, &iter);
1039         gtk_text_buffer_place_cursor(textbuf, &iter);
1040
1041         if (account->protocol != A_NNTP) {
1042                 if (mailto && *mailto != '\0') {
1043                         mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1044
1045                 } else {
1046                         compose_set_folder_prefs(compose, item, TRUE);
1047                 }
1048                 if (item && item->ret_rcpt) {
1049                         cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1050                 }
1051         } else {
1052                 if (mailto && *mailto != '\0') {
1053                         if (!strchr(mailto, '@'))
1054                                 mfield = compose_entries_set(compose, mailto, COMPOSE_NEWSGROUPS);
1055                         else
1056                                 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1057                 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1058                         compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS, PREF_FOLDER);
1059                         mfield = TO_FIELD_PRESENT;
1060                 }
1061                 /*
1062                  * CLAWS: just don't allow return receipt request, even if the user
1063                  * may want to send an email. simple but foolproof.
1064                  */
1065                 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", FALSE); 
1066         }
1067         compose_add_field_list( compose, listAddress );
1068
1069         if (item && item->prefs && item->prefs->compose_with_format) {
1070                 subject_format = item->prefs->compose_subject_format;
1071                 body_format = item->prefs->compose_body_format;
1072         } else if (account->compose_with_format) {
1073                 subject_format = account->compose_subject_format;
1074                 body_format = account->compose_body_format;
1075         } else if (prefs_common.compose_with_format) {
1076                 subject_format = prefs_common.compose_subject_format;
1077                 body_format = prefs_common.compose_body_format;
1078         }
1079
1080         if (subject_format || body_format) {
1081
1082                 if ( subject_format
1083                          && *subject_format != '\0' )
1084                 {
1085                         gchar *subject = NULL;
1086                         gchar *tmp = NULL;
1087                         gchar *buf = NULL;
1088
1089                         if (!dummyinfo)
1090                                 dummyinfo = compose_msginfo_new_from_compose(compose);
1091
1092                         /* decode \-escape sequences in the internal representation of the quote format */
1093                         tmp = g_malloc(strlen(subject_format)+1);
1094                         pref_get_unescaped_pref(tmp, subject_format);
1095
1096                         subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1097 #ifdef USE_ENCHANT
1098                         quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE,
1099                                         compose->gtkaspell);
1100 #else
1101                         quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE);
1102 #endif
1103                         quote_fmt_scan_string(tmp);
1104                         quote_fmt_parse();
1105
1106                         buf = quote_fmt_get_buffer();
1107                         if (buf == NULL)
1108                                 alertpanel_error(_("New message subject format error."));
1109                         else
1110                                 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1111                         compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1112                         quote_fmt_reset_vartable();
1113
1114                         g_free(subject);
1115                         g_free(tmp);
1116                         mfield = SUBJECT_FIELD_PRESENT;
1117                 }
1118
1119                 if ( body_format
1120                          && *body_format != '\0' )
1121                 {
1122                         GtkTextView *text;
1123                         GtkTextBuffer *buffer;
1124                         GtkTextIter start, end;
1125                         gchar *tmp = NULL;
1126
1127                         if (!dummyinfo)
1128                                 dummyinfo = compose_msginfo_new_from_compose(compose);
1129
1130                         text = GTK_TEXT_VIEW(compose->text);
1131                         buffer = gtk_text_view_get_buffer(text);
1132                         gtk_text_buffer_get_start_iter(buffer, &start);
1133                         gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1134                         tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1135
1136                         compose_quote_fmt(compose, dummyinfo,
1137                                           body_format,
1138                                           NULL, tmp, FALSE, TRUE,
1139                                                   _("The body of the \"New message\" template has an error at line %d."));
1140                         compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1141                         quote_fmt_reset_vartable();
1142
1143                         g_free(tmp);
1144 #ifdef USE_ENCHANT
1145                         if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1146                                 gtkaspell_highlight_all(compose->gtkaspell);
1147 #endif
1148                         mfield = BODY_FIELD_PRESENT;
1149                 }
1150
1151         }
1152         procmsg_msginfo_free( dummyinfo );
1153
1154         if (attach_files) {
1155                 gint i;
1156                 gchar *file;
1157
1158                 for (i = 0; i < attach_files->len; i++) {
1159                         file = g_ptr_array_index(attach_files, i);
1160                         compose_attach_append(compose, file, file, NULL, NULL);
1161                 }
1162         }
1163
1164         compose_show_first_last_header(compose, TRUE);
1165
1166         /* Set save folder */
1167         if (item && item->prefs && item->prefs->save_copy_to_folder) {
1168                 gchar *folderidentifier;
1169
1170                 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
1171                 folderidentifier = folder_item_get_identifier(item);
1172                 compose_set_save_to(compose, folderidentifier);
1173                 g_free(folderidentifier);
1174         }
1175
1176         /* Place cursor according to provided input (mfield) */
1177         switch (mfield) { 
1178                 case NO_FIELD_PRESENT:
1179                         if (compose->header_last)
1180                                 gtk_widget_grab_focus(compose->header_last->entry);
1181                         break;
1182                 case TO_FIELD_PRESENT:
1183                         buf = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1184                         if (buf) {
1185                                 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1186                                 g_free(buf);
1187                         }
1188                         gtk_widget_grab_focus(compose->subject_entry);
1189                         break;
1190                 case SUBJECT_FIELD_PRESENT:
1191                         textview = GTK_TEXT_VIEW(compose->text);
1192                         if (!textview)
1193                                 break;
1194                         textbuf = gtk_text_view_get_buffer(textview);
1195                         if (!textbuf)
1196                                 break;
1197                         mark = gtk_text_buffer_get_insert(textbuf);
1198                         gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
1199                         gtk_text_buffer_insert(textbuf, &iter, "", -1);
1200                     /* 
1201                      * SUBJECT_FIELD_PRESENT and BODY_FIELD_PRESENT
1202                      * only defers where it comes to the variable body
1203                      * is not null. If no body is present compose->text
1204                      * will be null in which case you cannot place the
1205                      * cursor inside the component so. An empty component
1206                      * is therefore created before placing the cursor
1207                      */
1208                 case BODY_FIELD_PRESENT:
1209                         cursor_pos = quote_fmt_get_cursor_pos();
1210                         if (cursor_pos == -1)
1211                                 gtk_widget_grab_focus(compose->header_last->entry);
1212                         else
1213                                 gtk_widget_grab_focus(compose->text);
1214                         break;
1215         }
1216
1217         undo_unblock(compose->undostruct);
1218
1219         if (prefs_common.auto_exteditor)
1220                 compose_exec_ext_editor(compose);
1221
1222         compose->draft_timeout_tag = -1;
1223         SCROLL_TO_CURSOR(compose);
1224
1225         compose->modified = FALSE;
1226         compose_set_title(compose);
1227
1228         hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1229
1230         return compose;
1231 }
1232
1233 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1234                 gboolean override_pref, const gchar *system)
1235 {
1236         const gchar *privacy = NULL;
1237
1238         cm_return_if_fail(compose != NULL);
1239         cm_return_if_fail(account != NULL);
1240
1241         if (override_pref == FALSE && account->default_encrypt_reply == FALSE)
1242                 return;
1243
1244         if (system)
1245                 privacy = system;
1246         else if (account->default_privacy_system
1247         &&  strlen(account->default_privacy_system)) {
1248                 privacy = account->default_privacy_system;
1249         } else {
1250                 GSList *privacy_avail = privacy_get_system_ids();
1251                 if (privacy_avail && g_slist_length(privacy_avail)) {
1252                         privacy = (gchar *)(privacy_avail->data);
1253                 }
1254         }
1255         if (privacy != NULL) {
1256                 if (system) {
1257                         g_free(compose->privacy_system);
1258                         compose->privacy_system = NULL;
1259                 }
1260                 if (compose->privacy_system == NULL)
1261                         compose->privacy_system = g_strdup(privacy);
1262                 else if (*(compose->privacy_system) == '\0') {
1263                         g_free(compose->privacy_system);
1264                         compose->privacy_system = g_strdup(privacy);
1265                 }
1266                 compose_update_privacy_system_menu_item(compose, FALSE);
1267                 compose_use_encryption(compose, TRUE);
1268         }
1269 }       
1270
1271 static void compose_force_signing(Compose *compose, PrefsAccount *account, const gchar *system)
1272 {
1273         const gchar *privacy = NULL;
1274
1275         if (system)
1276                 privacy = system;
1277         else if (account->default_privacy_system
1278         &&  strlen(account->default_privacy_system)) {
1279                 privacy = account->default_privacy_system;
1280         } else {
1281                 GSList *privacy_avail = privacy_get_system_ids();
1282                 if (privacy_avail && g_slist_length(privacy_avail)) {
1283                         privacy = (gchar *)(privacy_avail->data);
1284                 }
1285         }
1286
1287         if (privacy != NULL) {
1288                 if (system) {
1289                         g_free(compose->privacy_system);
1290                         compose->privacy_system = NULL;
1291                 }
1292                 if (compose->privacy_system == NULL)
1293                         compose->privacy_system = g_strdup(privacy);
1294                 compose_update_privacy_system_menu_item(compose, FALSE);
1295                 compose_use_signing(compose, TRUE);
1296         }
1297 }       
1298
1299 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1300 {
1301         MsgInfo *msginfo;
1302         guint list_len;
1303         Compose *compose = NULL;
1304         
1305         cm_return_val_if_fail(msginfo_list != NULL, NULL);
1306
1307         msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1308         cm_return_val_if_fail(msginfo != NULL, NULL);
1309
1310         list_len = g_slist_length(msginfo_list);
1311
1312         switch (mode) {
1313         case COMPOSE_REPLY:
1314         case COMPOSE_REPLY_TO_ADDRESS:
1315                 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1316                               FALSE, prefs_common.default_reply_list, FALSE, body);
1317                 break;
1318         case COMPOSE_REPLY_WITH_QUOTE:
1319                 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED, 
1320                         FALSE, prefs_common.default_reply_list, FALSE, body);
1321                 break;
1322         case COMPOSE_REPLY_WITHOUT_QUOTE:
1323                 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP, 
1324                         FALSE, prefs_common.default_reply_list, FALSE, NULL);
1325                 break;
1326         case COMPOSE_REPLY_TO_SENDER:
1327                 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1328                               FALSE, FALSE, TRUE, body);
1329                 break;
1330         case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1331                 compose = compose_followup_and_reply_to(msginfo,
1332                                               COMPOSE_QUOTE_CHECK,
1333                                               FALSE, FALSE, body);
1334                 break;
1335         case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1336                 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED, 
1337                         FALSE, FALSE, TRUE, body);
1338                 break;
1339         case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1340                 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP, 
1341                         FALSE, FALSE, TRUE, NULL);
1342                 break;
1343         case COMPOSE_REPLY_TO_ALL:
1344                 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1345                         TRUE, FALSE, FALSE, body);
1346                 break;
1347         case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1348                 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED, 
1349                         TRUE, FALSE, FALSE, body);
1350                 break;
1351         case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1352                 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP, 
1353                         TRUE, FALSE, FALSE, NULL);
1354                 break;
1355         case COMPOSE_REPLY_TO_LIST:
1356                 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1357                         FALSE, TRUE, FALSE, body);
1358                 break;
1359         case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1360                 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED, 
1361                         FALSE, TRUE, FALSE, body);
1362                 break;
1363         case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1364                 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP, 
1365                         FALSE, TRUE, FALSE, NULL);
1366                 break;
1367         case COMPOSE_FORWARD:
1368                 if (prefs_common.forward_as_attachment) {
1369                         compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1370                         return compose;
1371                 } else {
1372                         compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1373                         return compose;
1374                 }
1375                 break;
1376         case COMPOSE_FORWARD_INLINE:
1377                 /* check if we reply to more than one Message */
1378                 if (list_len == 1) {
1379                         compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1380                         break;
1381                 } 
1382                 /* more messages FALL THROUGH */
1383         case COMPOSE_FORWARD_AS_ATTACH:
1384                 compose = compose_forward_multiple(NULL, msginfo_list);
1385                 break;
1386         case COMPOSE_REDIRECT:
1387                 compose = compose_redirect(NULL, msginfo, FALSE);
1388                 break;
1389         default:
1390                 g_warning("compose_reply_mode(): invalid Compose Mode: %d\n", mode);
1391         }
1392         
1393         if (compose == NULL) {
1394                 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1395                 return NULL;
1396         }
1397
1398         compose->rmode = mode;
1399         switch (compose->rmode) {
1400         case COMPOSE_REPLY:
1401         case COMPOSE_REPLY_WITH_QUOTE:
1402         case COMPOSE_REPLY_WITHOUT_QUOTE:
1403         case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1404                 debug_print("reply mode Normal\n");
1405                 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Normal", TRUE);
1406                 compose_reply_change_mode(compose, COMPOSE_REPLY); /* force update */
1407                 break;
1408         case COMPOSE_REPLY_TO_SENDER:
1409         case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1410         case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1411                 debug_print("reply mode Sender\n");
1412                 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Sender", TRUE);
1413                 break;
1414         case COMPOSE_REPLY_TO_ALL:
1415         case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1416         case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1417                 debug_print("reply mode All\n");
1418                 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/All", TRUE);
1419                 break;
1420         case COMPOSE_REPLY_TO_LIST:
1421         case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1422         case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1423                 debug_print("reply mode List\n");
1424                 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/List", TRUE);
1425                 break;
1426         case COMPOSE_REPLY_TO_ADDRESS:
1427                 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", FALSE);
1428                 break;
1429         default:
1430                 break;
1431         }
1432         return compose;
1433 }
1434
1435 static Compose *compose_reply(MsgInfo *msginfo,
1436                                    ComposeQuoteMode quote_mode,
1437                                    gboolean to_all,
1438                                    gboolean to_ml,
1439                                    gboolean to_sender, 
1440                                    const gchar *body)
1441 {
1442         return compose_generic_reply(msginfo, quote_mode, to_all, to_ml, 
1443                               to_sender, FALSE, body);
1444 }
1445
1446 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1447                                    ComposeQuoteMode quote_mode,
1448                                    gboolean to_all,
1449                                    gboolean to_sender,
1450                                    const gchar *body)
1451 {
1452         return compose_generic_reply(msginfo, quote_mode, to_all, FALSE, 
1453                               to_sender, TRUE, body);
1454 }
1455
1456 static void compose_extract_original_charset(Compose *compose)
1457 {
1458         MsgInfo *info = NULL;
1459         if (compose->replyinfo) {
1460                 info = compose->replyinfo;
1461         } else if (compose->fwdinfo) {
1462                 info = compose->fwdinfo;
1463         } else if (compose->targetinfo) {
1464                 info = compose->targetinfo;
1465         }
1466         if (info) {
1467                 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1468                 MimeInfo *partinfo = mimeinfo;
1469                 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1470                         partinfo = procmime_mimeinfo_next(partinfo);
1471                 if (partinfo) {
1472                         compose->orig_charset = 
1473                                 g_strdup(procmime_mimeinfo_get_parameter(
1474                                                 partinfo, "charset"));
1475                 }
1476                 procmime_mimeinfo_free_all(mimeinfo);
1477         }
1478 }
1479
1480 #define SIGNAL_BLOCK(buffer) {                                  \
1481         g_signal_handlers_block_by_func(G_OBJECT(buffer),       \
1482                                 G_CALLBACK(compose_changed_cb), \
1483                                 compose);                       \
1484         g_signal_handlers_block_by_func(G_OBJECT(buffer),       \
1485                                 G_CALLBACK(text_inserted),      \
1486                                 compose);                       \
1487 }
1488
1489 #define SIGNAL_UNBLOCK(buffer) {                                \
1490         g_signal_handlers_unblock_by_func(G_OBJECT(buffer),     \
1491                                 G_CALLBACK(compose_changed_cb), \
1492                                 compose);                       \
1493         g_signal_handlers_unblock_by_func(G_OBJECT(buffer),     \
1494                                 G_CALLBACK(text_inserted),      \
1495                                 compose);                       \
1496 }
1497
1498 static Compose *compose_generic_reply(MsgInfo *msginfo,
1499                                   ComposeQuoteMode quote_mode,
1500                                   gboolean to_all, gboolean to_ml,
1501                                   gboolean to_sender,
1502                                   gboolean followup_and_reply_to,
1503                                   const gchar *body)
1504 {
1505         Compose *compose;
1506         PrefsAccount *account = NULL;
1507         GtkTextView *textview;
1508         GtkTextBuffer *textbuf;
1509         gboolean quote = FALSE;
1510         const gchar *qmark = NULL;
1511         const gchar *body_fmt = NULL;
1512         gchar *s_system = NULL;
1513         START_TIMING("");
1514         cm_return_val_if_fail(msginfo != NULL, NULL);
1515         cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1516
1517         account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1518
1519         cm_return_val_if_fail(account != NULL, NULL);
1520
1521         compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1522
1523         compose->updating = TRUE;
1524
1525         cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
1526         cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE);
1527
1528         compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1529         if (!compose->replyinfo)
1530                 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1531
1532         compose_extract_original_charset(compose);
1533         
1534         if (msginfo->folder && msginfo->folder->ret_rcpt)
1535                 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1536
1537         /* Set save folder */
1538         if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1539                 gchar *folderidentifier;
1540
1541                 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1542                 folderidentifier = folder_item_get_identifier(msginfo->folder);
1543                 compose_set_save_to(compose, folderidentifier);
1544                 g_free(folderidentifier);
1545         }
1546
1547         if (compose_parse_header(compose, msginfo) < 0) {
1548                 compose->updating = FALSE;
1549                 compose_destroy(compose);
1550                 return NULL;
1551         }
1552
1553         /* override from name according to folder properties */
1554         if (msginfo->folder && msginfo->folder->prefs &&
1555                 msginfo->folder->prefs->reply_with_format &&
1556                 msginfo->folder->prefs->reply_override_from_format &&
1557                 *msginfo->folder->prefs->reply_override_from_format != '\0') {
1558
1559                 gchar *tmp = NULL;
1560                 gchar *buf = NULL;
1561
1562                 /* decode \-escape sequences in the internal representation of the quote format */
1563                 tmp = g_malloc(strlen(msginfo->folder->prefs->reply_override_from_format)+1);
1564                 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->reply_override_from_format);
1565
1566 #ifdef USE_ENCHANT
1567                 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1568                                 compose->gtkaspell);
1569 #else
1570                 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1571 #endif
1572                 quote_fmt_scan_string(tmp);
1573                 quote_fmt_parse();
1574
1575                 buf = quote_fmt_get_buffer();
1576                 if (buf == NULL)
1577                         alertpanel_error(_("The \"From\" field of the \"Reply\" template contains an invalid email address."));
1578                 else
1579                         gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1580                 quote_fmt_reset_vartable();
1581
1582                 g_free(tmp);
1583         }
1584
1585         textview = (GTK_TEXT_VIEW(compose->text));
1586         textbuf = gtk_text_view_get_buffer(textview);
1587         compose_create_tags(textview, compose);
1588
1589         undo_block(compose->undostruct);
1590 #ifdef USE_ENCHANT
1591         compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1592         gtkaspell_block_check(compose->gtkaspell);
1593 #endif
1594
1595         if (quote_mode == COMPOSE_QUOTE_FORCED ||
1596                         (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1597                 /* use the reply format of folder (if enabled), or the account's one
1598                    (if enabled) or fallback to the global reply format, which is always
1599                    enabled (even if empty), and use the relevant quotemark */
1600                 quote = TRUE;
1601                 if (msginfo->folder && msginfo->folder->prefs &&
1602                                 msginfo->folder->prefs->reply_with_format) {
1603                         qmark = msginfo->folder->prefs->reply_quotemark;
1604                         body_fmt = msginfo->folder->prefs->reply_body_format;
1605
1606                 } else if (account->reply_with_format) {
1607                         qmark = account->reply_quotemark;
1608                         body_fmt = account->reply_body_format;
1609
1610                 } else {
1611                         qmark = prefs_common.quotemark;
1612                         if (prefs_common.quotefmt && *prefs_common.quotefmt)
1613                                 body_fmt = gettext(prefs_common.quotefmt);
1614                         else
1615                                 body_fmt = "";
1616                 }
1617         }
1618
1619         if (quote) {
1620                 /* empty quotemark is not allowed */
1621                 if (qmark == NULL || *qmark == '\0')
1622                         qmark = "> ";
1623                 compose_quote_fmt(compose, compose->replyinfo,
1624                                   body_fmt, qmark, body, FALSE, TRUE,
1625                                           _("The body of the \"Reply\" template has an error at line %d."));
1626                 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1627                 quote_fmt_reset_vartable();
1628         }
1629
1630         if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1631                 compose_force_encryption(compose, account, FALSE, s_system);
1632         }
1633
1634         privacy_msginfo_get_signed_state(compose->replyinfo, &s_system);
1635         if (MSG_IS_SIGNED(compose->replyinfo->flags) && account->default_sign_reply) {
1636                 compose_force_signing(compose, account, s_system);
1637         }
1638         g_free(s_system);
1639
1640         SIGNAL_BLOCK(textbuf);
1641         
1642         if (account->auto_sig)
1643                 compose_insert_sig(compose, FALSE);
1644
1645         compose_wrap_all(compose);
1646
1647 #ifdef USE_ENCHANT
1648         if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1649                 gtkaspell_highlight_all(compose->gtkaspell);
1650         gtkaspell_unblock_check(compose->gtkaspell);
1651 #endif
1652         SIGNAL_UNBLOCK(textbuf);
1653         
1654         gtk_widget_grab_focus(compose->text);
1655
1656         undo_unblock(compose->undostruct);
1657
1658         if (prefs_common.auto_exteditor)
1659                 compose_exec_ext_editor(compose);
1660                 
1661         compose->modified = FALSE;
1662         compose_set_title(compose);
1663
1664         compose->updating = FALSE;
1665         compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1666         SCROLL_TO_CURSOR(compose);
1667         
1668         if (compose->deferred_destroy) {
1669                 compose_destroy(compose);
1670                 return NULL;
1671         }
1672         END_TIMING();
1673
1674         return compose;
1675 }
1676
1677 #define INSERT_FW_HEADER(var, hdr) \
1678 if (msginfo->var && *msginfo->var) { \
1679         gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1680         gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1681         gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1682 }
1683
1684 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1685                          gboolean as_attach, const gchar *body,
1686                          gboolean no_extedit,
1687                          gboolean batch)
1688 {
1689         Compose *compose;
1690         GtkTextView *textview;
1691         GtkTextBuffer *textbuf;
1692         gint cursor_pos = -1;
1693         ComposeMode mode;
1694
1695         cm_return_val_if_fail(msginfo != NULL, NULL);
1696         cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1697
1698         if (!account && 
1699             !(account = compose_guess_forward_account_from_msginfo
1700                                 (msginfo)))
1701                 account = cur_account;
1702
1703         if (!prefs_common.forward_as_attachment)
1704                 mode = COMPOSE_FORWARD_INLINE;
1705         else
1706                 mode = COMPOSE_FORWARD;
1707         compose = compose_create(account, msginfo->folder, mode, batch);
1708
1709         compose->updating = TRUE;
1710         compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1711         if (!compose->fwdinfo)
1712                 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1713
1714         compose_extract_original_charset(compose);
1715
1716         if (msginfo->subject && *msginfo->subject) {
1717                 gchar *buf, *buf2, *p;
1718
1719                 buf = p = g_strdup(msginfo->subject);
1720                 p += subject_get_prefix_length(p);
1721                 memmove(buf, p, strlen(p) + 1);
1722
1723                 buf2 = g_strdup_printf("Fw: %s", buf);
1724                 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1725                 
1726                 g_free(buf);
1727                 g_free(buf2);
1728         }
1729
1730         /* override from name according to folder properties */
1731         if (msginfo->folder && msginfo->folder->prefs &&
1732                 msginfo->folder->prefs->forward_with_format &&
1733                 msginfo->folder->prefs->forward_override_from_format &&
1734                 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1735
1736                 gchar *tmp = NULL;
1737                 gchar *buf = NULL;
1738                 MsgInfo *full_msginfo = NULL;
1739
1740                 if (!as_attach)
1741                         full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1742                 if (!full_msginfo)
1743                         full_msginfo = procmsg_msginfo_copy(msginfo);
1744
1745                 /* decode \-escape sequences in the internal representation of the quote format */
1746                 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1747                 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1748
1749 #ifdef USE_ENCHANT
1750                 gtkaspell_block_check(compose->gtkaspell);
1751                 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1752                                 compose->gtkaspell);
1753 #else
1754                 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1755 #endif
1756                 quote_fmt_scan_string(tmp);
1757                 quote_fmt_parse();
1758
1759                 buf = quote_fmt_get_buffer();
1760                 if (buf == NULL)
1761                         alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1762                 else
1763                         gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1764                 quote_fmt_reset_vartable();
1765
1766                 g_free(tmp);
1767                 procmsg_msginfo_free(full_msginfo);
1768         }
1769
1770         textview = GTK_TEXT_VIEW(compose->text);
1771         textbuf = gtk_text_view_get_buffer(textview);
1772         compose_create_tags(textview, compose);
1773         
1774         undo_block(compose->undostruct);
1775         if (as_attach) {
1776                 gchar *msgfile;
1777
1778                 msgfile = procmsg_get_message_file(msginfo);
1779                 if (!is_file_exist(msgfile))
1780                         g_warning("%s: file not exist\n", msgfile);
1781                 else
1782                         compose_attach_append(compose, msgfile, msgfile,
1783                                               "message/rfc822", NULL);
1784
1785                 g_free(msgfile);
1786         } else {
1787                 const gchar *qmark = NULL;
1788                 const gchar *body_fmt = NULL;
1789                 MsgInfo *full_msginfo;
1790
1791                 if (prefs_common.fw_quotefmt && *prefs_common.fw_quotefmt)
1792                         body_fmt = gettext(prefs_common.fw_quotefmt);
1793                 else
1794                         body_fmt = "";
1795         
1796                 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1797                 if (!full_msginfo)
1798                         full_msginfo = procmsg_msginfo_copy(msginfo);
1799
1800                 /* use the forward format of folder (if enabled), or the account's one
1801                    (if enabled) or fallback to the global forward format, which is always
1802                    enabled (even if empty), and use the relevant quotemark */
1803                 if (msginfo->folder && msginfo->folder->prefs &&
1804                                 msginfo->folder->prefs->forward_with_format) {
1805                         qmark = msginfo->folder->prefs->forward_quotemark;
1806                         body_fmt = msginfo->folder->prefs->forward_body_format;
1807
1808                 } else if (account->forward_with_format) {
1809                         qmark = account->forward_quotemark;
1810                         body_fmt = account->forward_body_format;
1811
1812                 } else {
1813                         qmark = prefs_common.fw_quotemark;
1814                         if (prefs_common.fw_quotefmt && *prefs_common.fw_quotefmt)
1815                                 body_fmt = gettext(prefs_common.fw_quotefmt);
1816                         else
1817                                 body_fmt = "";
1818                 }
1819
1820                 /* empty quotemark is not allowed */
1821                 if (qmark == NULL || *qmark == '\0')
1822                         qmark = "> ";
1823
1824                 compose_quote_fmt(compose, full_msginfo,
1825                                   body_fmt, qmark, body, FALSE, TRUE,
1826                                           _("The body of the \"Forward\" template has an error at line %d."));
1827                 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1828                 quote_fmt_reset_vartable();
1829                 compose_attach_parts(compose, msginfo);
1830
1831                 procmsg_msginfo_free(full_msginfo);
1832         }
1833
1834         SIGNAL_BLOCK(textbuf);
1835
1836         if (account->auto_sig)
1837                 compose_insert_sig(compose, FALSE);
1838
1839         compose_wrap_all(compose);
1840
1841 #ifdef USE_ENCHANT
1842         if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1843                 gtkaspell_highlight_all(compose->gtkaspell);
1844         gtkaspell_unblock_check(compose->gtkaspell);
1845 #endif
1846         SIGNAL_UNBLOCK(textbuf);
1847         
1848         cursor_pos = quote_fmt_get_cursor_pos();
1849         if (cursor_pos == -1)
1850                 gtk_widget_grab_focus(compose->header_last->entry);
1851         else
1852                 gtk_widget_grab_focus(compose->text);
1853
1854         if (!no_extedit && prefs_common.auto_exteditor)
1855                 compose_exec_ext_editor(compose);
1856         
1857         /*save folder*/
1858         if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1859                 gchar *folderidentifier;
1860
1861                 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1862                 folderidentifier = folder_item_get_identifier(msginfo->folder);
1863                 compose_set_save_to(compose, folderidentifier);
1864                 g_free(folderidentifier);
1865         }
1866
1867         undo_unblock(compose->undostruct);
1868         
1869         compose->modified = FALSE;
1870         compose_set_title(compose);
1871
1872         compose->updating = FALSE;
1873         compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1874         SCROLL_TO_CURSOR(compose);
1875
1876         if (compose->deferred_destroy) {
1877                 compose_destroy(compose);
1878                 return NULL;
1879         }
1880
1881         hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1882
1883         return compose;
1884 }
1885
1886 #undef INSERT_FW_HEADER
1887
1888 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1889 {
1890         Compose *compose;
1891         GtkTextView *textview;
1892         GtkTextBuffer *textbuf;
1893         GtkTextIter iter;
1894         GSList *msginfo;
1895         gchar *msgfile;
1896         gboolean single_mail = TRUE;
1897         
1898         cm_return_val_if_fail(msginfo_list != NULL, NULL);
1899
1900         if (g_slist_length(msginfo_list) > 1)
1901                 single_mail = FALSE;
1902
1903         for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1904                 if (((MsgInfo *)msginfo->data)->folder == NULL)
1905                         return NULL;
1906
1907         /* guess account from first selected message */
1908         if (!account && 
1909             !(account = compose_guess_forward_account_from_msginfo
1910                                 (msginfo_list->data)))
1911                 account = cur_account;
1912
1913         cm_return_val_if_fail(account != NULL, NULL);
1914
1915         for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1916                 if (msginfo->data) {
1917                         MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1918                         MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1919                 }
1920         }
1921
1922         if (msginfo_list == NULL || msginfo_list->data == NULL) {
1923                 g_warning("no msginfo_list");
1924                 return NULL;
1925         }
1926
1927         compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1928
1929         compose->updating = TRUE;
1930
1931         /* override from name according to folder properties */
1932         if (msginfo_list->data) {
1933                 MsgInfo *msginfo = msginfo_list->data;
1934
1935                 if (msginfo->folder && msginfo->folder->prefs &&
1936                         msginfo->folder->prefs->forward_with_format &&
1937                         msginfo->folder->prefs->forward_override_from_format &&
1938                         *msginfo->folder->prefs->forward_override_from_format != '\0') {
1939
1940                         gchar *tmp = NULL;
1941                         gchar *buf = NULL;
1942
1943                         /* decode \-escape sequences in the internal representation of the quote format */
1944                         tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1945                         pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1946
1947 #ifdef USE_ENCHANT
1948                         quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1949                                         compose->gtkaspell);
1950 #else
1951                         quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1952 #endif
1953                         quote_fmt_scan_string(tmp);
1954                         quote_fmt_parse();
1955
1956                         buf = quote_fmt_get_buffer();
1957                         if (buf == NULL)
1958                                 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1959                         else
1960                                 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1961                         quote_fmt_reset_vartable();
1962
1963                         g_free(tmp);
1964                 }
1965         }
1966
1967         textview = GTK_TEXT_VIEW(compose->text);
1968         textbuf = gtk_text_view_get_buffer(textview);
1969         compose_create_tags(textview, compose);
1970         
1971         undo_block(compose->undostruct);
1972         for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1973                 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
1974
1975                 if (!is_file_exist(msgfile))
1976                         g_warning("%s: file not exist\n", msgfile);
1977                 else
1978                         compose_attach_append(compose, msgfile, msgfile,
1979                                 "message/rfc822", NULL);
1980                 g_free(msgfile);
1981         }
1982         
1983         if (single_mail) {
1984                 MsgInfo *info = (MsgInfo *)msginfo_list->data;
1985                 if (info->subject && *info->subject) {
1986                         gchar *buf, *buf2, *p;
1987
1988                         buf = p = g_strdup(info->subject);
1989                         p += subject_get_prefix_length(p);
1990                         memmove(buf, p, strlen(p) + 1);
1991
1992                         buf2 = g_strdup_printf("Fw: %s", buf);
1993                         gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1994
1995                         g_free(buf);
1996                         g_free(buf2);
1997                 }
1998         } else {
1999                 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2000                         _("Fw: multiple emails"));
2001         }
2002
2003         SIGNAL_BLOCK(textbuf);
2004         
2005         if (account->auto_sig)
2006                 compose_insert_sig(compose, FALSE);
2007
2008         compose_wrap_all(compose);
2009
2010         SIGNAL_UNBLOCK(textbuf);
2011         
2012         gtk_text_buffer_get_start_iter(textbuf, &iter);
2013         gtk_text_buffer_place_cursor(textbuf, &iter);
2014
2015         gtk_widget_grab_focus(compose->header_last->entry);
2016         undo_unblock(compose->undostruct);
2017         compose->modified = FALSE;
2018         compose_set_title(compose);
2019
2020         compose->updating = FALSE;
2021         compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2022         SCROLL_TO_CURSOR(compose);
2023
2024         if (compose->deferred_destroy) {
2025                 compose_destroy(compose);
2026                 return NULL;
2027         }
2028
2029         hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2030
2031         return compose;
2032 }
2033
2034 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter) 
2035 {
2036         GtkTextIter start = *iter;
2037         GtkTextIter end_iter;
2038         int start_pos = gtk_text_iter_get_offset(&start);
2039         gchar *str = NULL;
2040         if (!compose->account->sig_sep)
2041                 return FALSE;
2042         
2043         gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2044                 start_pos+strlen(compose->account->sig_sep));
2045
2046         /* check sig separator */
2047         str = gtk_text_iter_get_text(&start, &end_iter);
2048         if (!strcmp(str, compose->account->sig_sep)) {
2049                 gchar *tmp = NULL;
2050                 /* check end of line (\n) */
2051                 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
2052                         start_pos+strlen(compose->account->sig_sep));
2053                 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2054                         start_pos+strlen(compose->account->sig_sep)+1);
2055                 tmp = gtk_text_iter_get_text(&start, &end_iter);
2056                 if (!strcmp(tmp,"\n")) {
2057                         g_free(str);
2058                         g_free(tmp);
2059                         return TRUE;
2060                 }
2061                 g_free(tmp);    
2062         }
2063         g_free(str);
2064
2065         return FALSE;
2066 }
2067
2068 static void compose_colorize_signature(Compose *compose)
2069 {
2070         GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
2071         GtkTextIter iter;
2072         GtkTextIter end_iter;
2073         gtk_text_buffer_get_start_iter(buffer, &iter);
2074         while (gtk_text_iter_forward_line(&iter))
2075                 if (compose_is_sig_separator(compose, buffer, &iter)) {
2076                         gtk_text_buffer_get_end_iter(buffer, &end_iter);
2077                         gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
2078                 }
2079 }
2080
2081 #define BLOCK_WRAP() {                                                  \
2082         prev_autowrap = compose->autowrap;                              \
2083         buffer = gtk_text_view_get_buffer(                              \
2084                                         GTK_TEXT_VIEW(compose->text));  \
2085         compose->autowrap = FALSE;                                      \
2086                                                                         \
2087         g_signal_handlers_block_by_func(G_OBJECT(buffer),               \
2088                                 G_CALLBACK(compose_changed_cb),         \
2089                                 compose);                               \
2090         g_signal_handlers_block_by_func(G_OBJECT(buffer),               \
2091                                 G_CALLBACK(text_inserted),              \
2092                                 compose);                               \
2093 }
2094 #define UNBLOCK_WRAP() {                                                \
2095         compose->autowrap = prev_autowrap;                              \
2096         if (compose->autowrap) {                                        \
2097                 gint old = compose->draft_timeout_tag;                  \
2098                 compose->draft_timeout_tag = -2;                        \
2099                 compose_wrap_all(compose);                              \
2100                 compose->draft_timeout_tag = old;                       \
2101         }                                                               \
2102                                                                         \
2103         g_signal_handlers_unblock_by_func(G_OBJECT(buffer),             \
2104                                 G_CALLBACK(compose_changed_cb),         \
2105                                 compose);                               \
2106         g_signal_handlers_unblock_by_func(G_OBJECT(buffer),             \
2107                                 G_CALLBACK(text_inserted),              \
2108                                 compose);                               \
2109 }
2110
2111 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
2112 {
2113         Compose *compose = NULL;
2114         PrefsAccount *account = NULL;
2115         GtkTextView *textview;
2116         GtkTextBuffer *textbuf;
2117         GtkTextMark *mark;
2118         GtkTextIter iter;
2119         FILE *fp;
2120         gchar buf[BUFFSIZE];
2121         gboolean use_signing = FALSE;
2122         gboolean use_encryption = FALSE;
2123         gchar *privacy_system = NULL;
2124         int priority = PRIORITY_NORMAL;
2125         MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
2126         gboolean autowrap = prefs_common.autowrap;
2127         gboolean autoindent = prefs_common.auto_indent;
2128
2129         cm_return_val_if_fail(msginfo != NULL, NULL);
2130         cm_return_val_if_fail(msginfo->folder != NULL, NULL);
2131
2132         if (compose_put_existing_to_front(msginfo)) {
2133                 return NULL;
2134         }
2135
2136         if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2137             folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2138             folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2139                 gchar queueheader_buf[BUFFSIZE];
2140                 gint id, param;
2141
2142                 /* Select Account from queue headers */
2143                 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, 
2144                                              sizeof(queueheader_buf), "X-Claws-Account-Id:")) {
2145                         id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
2146                         account = account_find_from_id(id);
2147                 }
2148                 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, 
2149                                              sizeof(queueheader_buf), "X-Sylpheed-Account-Id:")) {
2150                         id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
2151                         account = account_find_from_id(id);
2152                 }
2153                 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf, 
2154                                              sizeof(queueheader_buf), "NAID:")) {
2155                         id = atoi(&queueheader_buf[strlen("NAID:")]);
2156                         account = account_find_from_id(id);
2157                 }
2158                 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf, 
2159                                                     sizeof(queueheader_buf), "MAID:")) {
2160                         id = atoi(&queueheader_buf[strlen("MAID:")]);
2161                         account = account_find_from_id(id);
2162                 }
2163                 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf, 
2164                                                                 sizeof(queueheader_buf), "S:")) {
2165                         account = account_find_from_address(queueheader_buf, FALSE);
2166                 }
2167                 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, 
2168                                              sizeof(queueheader_buf), "X-Claws-Sign:")) {
2169                         param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
2170                         use_signing = param;
2171                         
2172                 }
2173                 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, 
2174                                              sizeof(queueheader_buf), "X-Sylpheed-Sign:")) {
2175                         param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
2176                         use_signing = param;
2177                         
2178                 }
2179                 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, 
2180                                              sizeof(queueheader_buf), "X-Claws-Encrypt:")) {
2181                         param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
2182                         use_encryption = param;
2183                 }
2184                 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, 
2185                                              sizeof(queueheader_buf), "X-Sylpheed-Encrypt:")) {
2186                         param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
2187                         use_encryption = param;
2188                 }
2189                 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, 
2190                                              sizeof(queueheader_buf), "X-Claws-Auto-Wrapping:")) {
2191                         param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Wrapping:")]);
2192                         autowrap = param;
2193                 }
2194                 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, 
2195                                              sizeof(queueheader_buf), "X-Claws-Auto-Indent:")) {
2196                         param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Indent:")]);
2197                         autoindent = param;
2198                 }
2199                 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, 
2200                                             sizeof(queueheader_buf), "X-Claws-Privacy-System:")) {
2201                         privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
2202                 }
2203                 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, 
2204                                             sizeof(queueheader_buf), "X-Sylpheed-Privacy-System:")) {
2205                         privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
2206                 }
2207                 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, 
2208                                              sizeof(queueheader_buf), "X-Priority: ")) {
2209                         param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
2210                         priority = param;
2211                 }
2212                 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, 
2213                                              sizeof(queueheader_buf), "RMID:")) {
2214                         gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
2215                         if (tokens[0] && tokens[1] && tokens[2]) {
2216                                 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2217                                 if (orig_item != NULL) {
2218                                         replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2219                                 }
2220                         }
2221                         g_strfreev(tokens);
2222                 }
2223                 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, 
2224                                              sizeof(queueheader_buf), "FMID:")) {
2225                         gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
2226                         if (tokens[0] && tokens[1] && tokens[2]) {
2227                                 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2228                                 if (orig_item != NULL) {
2229                                         fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2230                                 }
2231                         }
2232                         g_strfreev(tokens);
2233                 }
2234         } else {
2235                 account = msginfo->folder->folder->account;
2236         }
2237
2238         if (!account && prefs_common.reedit_account_autosel) {
2239                 gchar from[BUFFSIZE];
2240                 if (!procheader_get_header_from_msginfo(msginfo, from, sizeof(from), "FROM:")) {
2241                         extract_address(from);
2242                         account = account_find_from_address(from, FALSE);
2243                 }
2244         }
2245         if (!account) {
2246                 account = cur_account;
2247         }
2248         cm_return_val_if_fail(account != NULL, NULL);
2249
2250         compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2251
2252         cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", autowrap);
2253         cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", autoindent);
2254         compose->autowrap = autowrap;
2255         compose->replyinfo = replyinfo;
2256         compose->fwdinfo = fwdinfo;
2257
2258         compose->updating = TRUE;
2259         compose->priority = priority;
2260
2261         if (privacy_system != NULL) {
2262                 compose->privacy_system = privacy_system;
2263                 compose_use_signing(compose, use_signing);
2264                 compose_use_encryption(compose, use_encryption);
2265                 compose_update_privacy_system_menu_item(compose, FALSE);
2266         } else {
2267                 activate_privacy_system(compose, account, FALSE);
2268         }
2269
2270         compose->targetinfo = procmsg_msginfo_copy(msginfo);
2271
2272         compose_extract_original_charset(compose);
2273
2274         if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2275             folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2276             folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2277                 gchar queueheader_buf[BUFFSIZE];
2278
2279                 /* Set message save folder */
2280                 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "SCF:")) {
2281                         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2282                         compose_set_save_to(compose, &queueheader_buf[4]);
2283                 }
2284                 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "RRCPT:")) {
2285                         gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2286                         if (active) {
2287                                 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
2288                         }
2289                 }
2290         }
2291         
2292         if (compose_parse_header(compose, msginfo) < 0) {
2293                 compose->updating = FALSE;
2294                 compose_destroy(compose);
2295                 return NULL;
2296         }
2297         compose_reedit_set_entry(compose, msginfo);
2298
2299         textview = GTK_TEXT_VIEW(compose->text);
2300         textbuf = gtk_text_view_get_buffer(textview);
2301         compose_create_tags(textview, compose);
2302
2303         mark = gtk_text_buffer_get_insert(textbuf);
2304         gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2305
2306         g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2307                                         G_CALLBACK(compose_changed_cb),
2308                                         compose);
2309         
2310         if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2311                 fp = procmime_get_first_encrypted_text_content(msginfo);
2312                 if (fp) {
2313                         compose_force_encryption(compose, account, TRUE, NULL);
2314                 }
2315         } else {
2316                 fp = procmime_get_first_text_content(msginfo);
2317         }
2318         if (fp == NULL) {
2319                 g_warning("Can't get text part\n");
2320         }
2321
2322         if (fp != NULL) {
2323                 gboolean prev_autowrap = compose->autowrap;
2324                 GtkTextBuffer *buffer = textbuf;
2325                 BLOCK_WRAP();
2326                 while (fgets(buf, sizeof(buf), fp) != NULL) {
2327                         strcrchomp(buf);
2328                         gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2329                 }
2330                 UNBLOCK_WRAP();
2331                 fclose(fp);
2332         }
2333         
2334         compose_attach_parts(compose, msginfo);
2335
2336         compose_colorize_signature(compose);
2337
2338         g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2339                                         G_CALLBACK(compose_changed_cb),
2340                                         compose);
2341
2342         gtk_widget_grab_focus(compose->text);
2343
2344         if (prefs_common.auto_exteditor) {
2345                 compose_exec_ext_editor(compose);
2346         }
2347         compose->modified = FALSE;
2348         compose_set_title(compose);
2349
2350         compose->updating = FALSE;
2351         compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2352         SCROLL_TO_CURSOR(compose);
2353
2354         if (compose->deferred_destroy) {
2355                 compose_destroy(compose);
2356                 return NULL;
2357         }
2358         
2359         compose->sig_str = account_get_signature_str(compose->account);
2360         
2361         hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2362
2363         return compose;
2364 }
2365
2366 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2367                                                  gboolean batch)
2368 {
2369         Compose *compose;
2370         gchar *filename;
2371         FolderItem *item;
2372
2373         cm_return_val_if_fail(msginfo != NULL, NULL);
2374
2375         if (!account)
2376                 account = account_get_reply_account(msginfo,
2377                                         prefs_common.reply_account_autosel);
2378         cm_return_val_if_fail(account != NULL, NULL);
2379
2380         compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2381
2382         compose->updating = TRUE;
2383
2384         compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2385         compose->replyinfo = NULL;
2386         compose->fwdinfo = NULL;
2387
2388         compose_show_first_last_header(compose, TRUE);
2389
2390         gtk_widget_grab_focus(compose->header_last->entry);
2391
2392         filename = procmsg_get_message_file(msginfo);
2393
2394         if (filename == NULL) {
2395                 compose->updating = FALSE;
2396                 compose_destroy(compose);
2397
2398                 return NULL;
2399         }
2400
2401         compose->redirect_filename = filename;
2402         
2403         /* Set save folder */
2404         item = msginfo->folder;
2405         if (item && item->prefs && item->prefs->save_copy_to_folder) {
2406                 gchar *folderidentifier;
2407
2408                 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
2409                 folderidentifier = folder_item_get_identifier(item);
2410                 compose_set_save_to(compose, folderidentifier);
2411                 g_free(folderidentifier);
2412         }
2413
2414         compose_attach_parts(compose, msginfo);
2415
2416         if (msginfo->subject)
2417                 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2418                                    msginfo->subject);
2419         gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2420
2421         compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2422                                           _("The body of the \"Redirect\" template has an error at line %d."));
2423         quote_fmt_reset_vartable();
2424         gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2425
2426         compose_colorize_signature(compose);
2427
2428         
2429         cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Add", FALSE);
2430         cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
2431         cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
2432
2433         cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Save", FALSE);
2434         cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", FALSE);
2435         cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/AttachFile", FALSE);
2436         cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", FALSE);
2437         cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", FALSE);
2438         cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", FALSE);
2439         cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/ShowRuler", FALSE);
2440         cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Actions", FALSE);
2441         
2442         if (compose->toolbar->draft_btn)
2443                 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2444         if (compose->toolbar->insert_btn)
2445                 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2446         if (compose->toolbar->attach_btn)
2447                 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2448         if (compose->toolbar->sig_btn)
2449                 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2450         if (compose->toolbar->exteditor_btn)
2451                 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2452         if (compose->toolbar->linewrap_current_btn)
2453                 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2454         if (compose->toolbar->linewrap_all_btn)
2455                 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2456
2457         compose->modified = FALSE;
2458         compose_set_title(compose);
2459         compose->updating = FALSE;
2460         compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2461         SCROLL_TO_CURSOR(compose);
2462
2463         if (compose->deferred_destroy) {
2464                 compose_destroy(compose);
2465                 return NULL;
2466         }
2467         
2468         hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2469
2470         return compose;
2471 }
2472
2473 GList *compose_get_compose_list(void)
2474 {
2475         return compose_list;
2476 }
2477
2478 void compose_entry_append(Compose *compose, const gchar *address,
2479                           ComposeEntryType type, ComposePrefType pref_type)
2480 {
2481         const gchar *header;
2482         gchar *cur, *begin;
2483         gboolean in_quote = FALSE;
2484         if (!address || *address == '\0') return;
2485
2486         switch (type) {
2487         case COMPOSE_CC:
2488                 header = N_("Cc:");
2489                 break;
2490         case COMPOSE_BCC:
2491                 header = N_("Bcc:");
2492                 break;
2493         case COMPOSE_REPLYTO:
2494                 header = N_("Reply-To:");
2495                 break;
2496         case COMPOSE_NEWSGROUPS:
2497                 header = N_("Newsgroups:");
2498                 break;
2499         case COMPOSE_FOLLOWUPTO:
2500                 header = N_( "Followup-To:");
2501                 break;
2502         case COMPOSE_INREPLYTO:
2503                 header = N_( "In-Reply-To:");
2504                 break;
2505         case COMPOSE_TO:
2506         default:
2507                 header = N_("To:");
2508                 break;
2509         }
2510         header = prefs_common_translated_header_name(header);
2511         
2512         cur = begin = (gchar *)address;
2513         
2514         /* we separate the line by commas, but not if we're inside a quoted
2515          * string */
2516         while (*cur != '\0') {
2517                 if (*cur == '"') 
2518                         in_quote = !in_quote;
2519                 if (*cur == ',' && !in_quote) {
2520                         gchar *tmp = g_strdup(begin);
2521                         gchar *o_tmp = tmp;
2522                         tmp[cur-begin]='\0';
2523                         cur++;
2524                         begin = cur;
2525                         while (*tmp == ' ' || *tmp == '\t')
2526                                 tmp++;
2527                         compose_add_header_entry(compose, header, tmp, pref_type);
2528                         g_free(o_tmp);
2529                         continue;
2530                 }
2531                 cur++;
2532         }
2533         if (begin < cur) {
2534                 gchar *tmp = g_strdup(begin);
2535                 gchar *o_tmp = tmp;
2536                 tmp[cur-begin]='\0';
2537                 cur++;
2538                 begin = cur;
2539                 while (*tmp == ' ' || *tmp == '\t')
2540                         tmp++;
2541                 compose_add_header_entry(compose, header, tmp, pref_type);
2542                 g_free(o_tmp);          
2543         }
2544 }
2545
2546 static void compose_entry_mark_default_to(Compose *compose, const gchar *mailto)
2547 {
2548 #if !GTK_CHECK_VERSION(3, 0, 0)
2549         static GdkColor yellow;
2550         static GdkColor black;
2551         static gboolean yellow_initialised = FALSE;
2552 #else
2553         static GdkColor yellow = { (guint32)0, (guint16)0xf5, (guint16)0xf6, (guint16)0xbe };
2554         static GdkColor black = { (guint32)0, (guint16)0x0, (guint16)0x0, (guint16)0x0 };
2555 #endif
2556         GSList *h_list;
2557         GtkEntry *entry;
2558                 
2559 #if !GTK_CHECK_VERSION(3, 0, 0)
2560         if (!yellow_initialised) {
2561                 gdk_color_parse("#f5f6be", &yellow);
2562                 gdk_color_parse("#000000", &black);
2563                 yellow_initialised = gdk_colormap_alloc_color(
2564                         gdk_colormap_get_system(), &yellow, FALSE, TRUE);
2565                 yellow_initialised &= gdk_colormap_alloc_color(
2566                         gdk_colormap_get_system(), &black, FALSE, TRUE);
2567         }
2568 #endif
2569
2570         for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2571                 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2572                 if (gtk_entry_get_text(entry) && 
2573                     !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2574 #if !GTK_CHECK_VERSION(3, 0, 0)
2575                         if (yellow_initialised) {
2576 #endif
2577                                 gtk_widget_modify_base(
2578                                         GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2579                                         GTK_STATE_NORMAL, &yellow);
2580                                 gtk_widget_modify_text(
2581                                         GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2582                                         GTK_STATE_NORMAL, &black);
2583 #if !GTK_CHECK_VERSION(3, 0, 0)
2584                         }
2585 #endif
2586                 }
2587         }
2588 }
2589
2590 void compose_toolbar_cb(gint action, gpointer data)
2591 {
2592         ToolbarItem *toolbar_item = (ToolbarItem*)data;
2593         Compose *compose = (Compose*)toolbar_item->parent;
2594         
2595         cm_return_if_fail(compose != NULL);
2596
2597         switch(action) {
2598         case A_SEND:
2599                 compose_send_cb(NULL, compose);
2600                 break;
2601         case A_SENDL:
2602                 compose_send_later_cb(NULL, compose);
2603                 break;
2604         case A_DRAFT:
2605                 compose_draft(compose, COMPOSE_QUIT_EDITING);
2606                 break;
2607         case A_INSERT:
2608                 compose_insert_file_cb(NULL, compose);
2609                 break;
2610         case A_ATTACH:
2611                 compose_attach_cb(NULL, compose);
2612                 break;
2613         case A_SIG:
2614                 compose_insert_sig(compose, FALSE);
2615                 break;
2616         case A_EXTEDITOR:
2617                 compose_ext_editor_cb(NULL, compose);
2618                 break;
2619         case A_LINEWRAP_CURRENT:
2620                 compose_beautify_paragraph(compose, NULL, TRUE);
2621                 break;
2622         case A_LINEWRAP_ALL:
2623                 compose_wrap_all_full(compose, TRUE);
2624                 break;
2625         case A_ADDRBOOK:
2626                 compose_address_cb(NULL, compose);
2627                 break;
2628 #ifdef USE_ENCHANT
2629         case A_CHECK_SPELLING:
2630                 compose_check_all(NULL, compose);
2631                 break;
2632 #endif
2633         default:
2634                 break;
2635         }
2636 }
2637
2638 static MailField compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
2639 {
2640         gchar *to = NULL;
2641         gchar *cc = NULL;
2642         gchar *bcc = NULL;
2643         gchar *subject = NULL;
2644         gchar *body = NULL;
2645         gchar *temp = NULL;
2646         gsize  len = 0;
2647         gchar **attach = NULL;
2648         gchar *inreplyto = NULL;
2649         MailField mfield = NO_FIELD_PRESENT;
2650
2651         /* get mailto parts but skip from */
2652         scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach, &inreplyto);
2653
2654         if (to) {
2655                 compose_entry_append(compose, to, to_type, PREF_MAILTO);
2656                 mfield = TO_FIELD_PRESENT;
2657         }
2658         if (cc)
2659                 compose_entry_append(compose, cc, COMPOSE_CC, PREF_MAILTO);
2660         if (bcc)
2661                 compose_entry_append(compose, bcc, COMPOSE_BCC, PREF_MAILTO);
2662         if (subject) {
2663                 if (!g_utf8_validate (subject, -1, NULL)) {
2664                         temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2665                         gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2666                         g_free(temp);
2667                 } else {
2668                         gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2669                 }
2670                 mfield = SUBJECT_FIELD_PRESENT;
2671         }
2672         if (body) {
2673                 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2674                 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2675                 GtkTextMark *mark;
2676                 GtkTextIter iter;
2677                 gboolean prev_autowrap = compose->autowrap;
2678
2679                 compose->autowrap = FALSE;
2680
2681                 mark = gtk_text_buffer_get_insert(buffer);
2682                 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2683
2684                 if (!g_utf8_validate (body, -1, NULL)) {
2685                         temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2686                         gtk_text_buffer_insert(buffer, &iter, temp, -1);
2687                         g_free(temp);
2688                 } else {
2689                         gtk_text_buffer_insert(buffer, &iter, body, -1);
2690                 }
2691                 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2692
2693                 compose->autowrap = prev_autowrap;
2694                 if (compose->autowrap)
2695                         compose_wrap_all(compose);
2696                 mfield = BODY_FIELD_PRESENT;
2697         }
2698
2699         if (attach) {
2700                 gint i = 0, att = 0;
2701                 gchar *warn_files = NULL;
2702                 while (attach[i] != NULL) {
2703                         gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2704                         if (utf8_filename) {
2705                                 if (compose_attach_append(compose, attach[i], utf8_filename, NULL, NULL)) {
2706                                         gchar *tmp = g_strdup_printf("%s%s\n",
2707                                                         warn_files?warn_files:"",
2708                                                         utf8_filename);
2709                                         g_free(warn_files);
2710                                         warn_files = tmp;
2711                                         att++;
2712                                 }
2713                                 g_free(utf8_filename);
2714                         } else {
2715                                 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2716                         }
2717                         i++;
2718                 }
2719                 if (warn_files) {
2720                         alertpanel_notice(ngettext(
2721                         "The following file has been attached: \n%s",
2722                         "The following files have been attached: \n%s", att), warn_files);
2723                         g_free(warn_files);
2724                 }
2725         }
2726         if (inreplyto)
2727                 compose_entry_append(compose, inreplyto, COMPOSE_INREPLYTO, PREF_MAILTO);
2728
2729         g_free(to);
2730         g_free(cc);
2731         g_free(bcc);
2732         g_free(subject);
2733         g_free(body);
2734         g_strfreev(attach);
2735         g_free(inreplyto);
2736         
2737         return mfield;
2738 }
2739
2740 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2741 {
2742         static HeaderEntry hentry[] = {{"Reply-To:",    NULL, TRUE},
2743                                        {"Cc:",          NULL, TRUE},
2744                                        {"References:",  NULL, FALSE},
2745                                        {"Bcc:",         NULL, TRUE},
2746                                        {"Newsgroups:",  NULL, TRUE},
2747                                        {"Followup-To:", NULL, TRUE},
2748                                        {"List-Post:",   NULL, FALSE},
2749                                        {"X-Priority:",  NULL, FALSE},
2750                                        {NULL,           NULL, FALSE}};
2751
2752         enum
2753         {
2754                 H_REPLY_TO      = 0,
2755                 H_CC            = 1,
2756                 H_REFERENCES    = 2,
2757                 H_BCC           = 3,
2758                 H_NEWSGROUPS    = 4,
2759                 H_FOLLOWUP_TO   = 5,
2760                 H_LIST_POST     = 6,
2761                 H_X_PRIORITY    = 7
2762         };
2763
2764         FILE *fp;
2765
2766         cm_return_val_if_fail(msginfo != NULL, -1);
2767
2768         if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2769         procheader_get_header_fields(fp, hentry);
2770         fclose(fp);
2771
2772         if (hentry[H_REPLY_TO].body != NULL) {
2773                 if (hentry[H_REPLY_TO].body[0] != '\0') {
2774                         compose->replyto =
2775                                 conv_unmime_header(hentry[H_REPLY_TO].body,
2776                                                    NULL, TRUE);
2777                 }
2778                 g_free(hentry[H_REPLY_TO].body);
2779                 hentry[H_REPLY_TO].body = NULL;
2780         }
2781         if (hentry[H_CC].body != NULL) {
2782                 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL, TRUE);
2783                 g_free(hentry[H_CC].body);
2784                 hentry[H_CC].body = NULL;
2785         }
2786         if (hentry[H_REFERENCES].body != NULL) {
2787                 if (compose->mode == COMPOSE_REEDIT)
2788                         compose->references = hentry[H_REFERENCES].body;
2789                 else {
2790                         compose->references = compose_parse_references
2791                                 (hentry[H_REFERENCES].body, msginfo->msgid);
2792                         g_free(hentry[H_REFERENCES].body);
2793                 }
2794                 hentry[H_REFERENCES].body = NULL;
2795         }
2796         if (hentry[H_BCC].body != NULL) {
2797                 if (compose->mode == COMPOSE_REEDIT)
2798                         compose->bcc =
2799                                 conv_unmime_header(hentry[H_BCC].body, NULL, TRUE);
2800                 g_free(hentry[H_BCC].body);
2801                 hentry[H_BCC].body = NULL;
2802         }
2803         if (hentry[H_NEWSGROUPS].body != NULL) {
2804                 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2805                 hentry[H_NEWSGROUPS].body = NULL;
2806         }
2807         if (hentry[H_FOLLOWUP_TO].body != NULL) {
2808                 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2809                         compose->followup_to =
2810                                 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2811                                                    NULL, TRUE);
2812                 }
2813                 g_free(hentry[H_FOLLOWUP_TO].body);
2814                 hentry[H_FOLLOWUP_TO].body = NULL;
2815         }
2816         if (hentry[H_LIST_POST].body != NULL) {
2817                 gchar *to = NULL, *start = NULL;
2818
2819                 extract_address(hentry[H_LIST_POST].body);
2820                 if (hentry[H_LIST_POST].body[0] != '\0') {
2821                         start = strstr(hentry[H_LIST_POST].body, "mailto:");
2822                         
2823                         scan_mailto_url(start ? start : hentry[H_LIST_POST].body,
2824                                         NULL, &to, NULL, NULL, NULL, NULL, NULL, NULL);
2825
2826                         if (to) {
2827                                 g_free(compose->ml_post);
2828                                 compose->ml_post = to;
2829                         }
2830                 }
2831                 g_free(hentry[H_LIST_POST].body);
2832                 hentry[H_LIST_POST].body = NULL;
2833         }
2834
2835         /* CLAWS - X-Priority */
2836         if (compose->mode == COMPOSE_REEDIT)
2837                 if (hentry[H_X_PRIORITY].body != NULL) {
2838                         gint priority;
2839                         
2840                         priority = atoi(hentry[H_X_PRIORITY].body);
2841                         g_free(hentry[H_X_PRIORITY].body);
2842                         
2843                         hentry[H_X_PRIORITY].body = NULL;
2844                         
2845                         if (priority < PRIORITY_HIGHEST || 
2846                             priority > PRIORITY_LOWEST)
2847                                 priority = PRIORITY_NORMAL;
2848                         
2849                         compose->priority =  priority;
2850                 }
2851  
2852         if (compose->mode == COMPOSE_REEDIT) {
2853                 if (msginfo->inreplyto && *msginfo->inreplyto)
2854                         compose->inreplyto = g_strdup(msginfo->inreplyto);
2855                 return 0;
2856         }
2857
2858         if (msginfo->msgid && *msginfo->msgid)
2859                 compose->inreplyto = g_strdup(msginfo->msgid);
2860
2861         if (!compose->references) {
2862                 if (msginfo->msgid && *msginfo->msgid) {
2863                         if (msginfo->inreplyto && *msginfo->inreplyto)
2864                                 compose->references =
2865                                         g_strdup_printf("<%s>\n\t<%s>",
2866                                                         msginfo->inreplyto,
2867                                                         msginfo->msgid);
2868                         else
2869                                 compose->references =
2870                                         g_strconcat("<", msginfo->msgid, ">",
2871                                                     NULL);
2872                 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
2873                         compose->references =
2874                                 g_strconcat("<", msginfo->inreplyto, ">",
2875                                             NULL);
2876                 }
2877         }
2878
2879         return 0;
2880 }
2881
2882 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
2883 {
2884         GSList *ref_id_list, *cur;
2885         GString *new_ref;
2886         gchar *new_ref_str;
2887
2888         ref_id_list = references_list_append(NULL, ref);
2889         if (!ref_id_list) return NULL;
2890         if (msgid && *msgid)
2891                 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
2892
2893         for (;;) {
2894                 gint len = 0;
2895
2896                 for (cur = ref_id_list; cur != NULL; cur = cur->next)
2897                         /* "<" + Message-ID + ">" + CR+LF+TAB */
2898                         len += strlen((gchar *)cur->data) + 5;
2899
2900                 if (len > MAX_REFERENCES_LEN) {
2901                         /* remove second message-ID */
2902                         if (ref_id_list && ref_id_list->next &&
2903                             ref_id_list->next->next) {
2904                                 g_free(ref_id_list->next->data);
2905                                 ref_id_list = g_slist_remove
2906                                         (ref_id_list, ref_id_list->next->data);
2907                         } else {
2908                                 slist_free_strings(ref_id_list);
2909                                 g_slist_free(ref_id_list);
2910                                 return NULL;
2911                         }
2912                 } else
2913                         break;
2914         }
2915
2916         new_ref = g_string_new("");
2917         for (cur = ref_id_list; cur != NULL; cur = cur->next) {
2918                 if (new_ref->len > 0)
2919                         g_string_append(new_ref, "\n\t");
2920                 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
2921         }
2922
2923         slist_free_strings(ref_id_list);
2924         g_slist_free(ref_id_list);
2925
2926         new_ref_str = new_ref->str;
2927         g_string_free(new_ref, FALSE);
2928
2929         return new_ref_str;
2930 }
2931
2932 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
2933                                 const gchar *fmt, const gchar *qmark,
2934                                 const gchar *body, gboolean rewrap,
2935                                 gboolean need_unescape,
2936                                 const gchar *err_msg)
2937 {
2938         MsgInfo* dummyinfo = NULL;
2939         gchar *quote_str = NULL;
2940         gchar *buf;
2941         gboolean prev_autowrap;
2942         const gchar *trimmed_body = body;
2943         gint cursor_pos = -1;
2944         GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2945         GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2946         GtkTextIter iter;
2947         GtkTextMark *mark;
2948         
2949
2950         SIGNAL_BLOCK(buffer);
2951
2952         if (!msginfo) {
2953                 dummyinfo = compose_msginfo_new_from_compose(compose);
2954                 msginfo = dummyinfo;
2955         }
2956
2957         if (qmark != NULL) {
2958 #ifdef USE_ENCHANT
2959                 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
2960                                 compose->gtkaspell);
2961 #else
2962                 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
2963 #endif
2964                 quote_fmt_scan_string(qmark);
2965                 quote_fmt_parse();
2966
2967                 buf = quote_fmt_get_buffer();
2968                 if (buf == NULL)
2969                         alertpanel_error(_("The \"Quotation mark\" of the template is invalid."));
2970                 else
2971                         Xstrdup_a(quote_str, buf, goto error)
2972         }
2973
2974         if (fmt && *fmt != '\0') {
2975
2976                 if (trimmed_body)
2977                         while (*trimmed_body == '\n')
2978                                 trimmed_body++;
2979
2980 #ifdef USE_ENCHANT
2981                 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
2982                                 compose->gtkaspell);
2983 #else
2984                 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
2985 #endif
2986                 if (need_unescape) {
2987                         gchar *tmp = NULL;
2988
2989                         /* decode \-escape sequences in the internal representation of the quote format */
2990                         tmp = g_malloc(strlen(fmt)+1);
2991                         pref_get_unescaped_pref(tmp, fmt);
2992                         quote_fmt_scan_string(tmp);
2993                         quote_fmt_parse();
2994                         g_free(tmp);
2995                 } else {
2996                         quote_fmt_scan_string(fmt);
2997                         quote_fmt_parse();
2998                 }
2999
3000                 buf = quote_fmt_get_buffer();
3001                 if (buf == NULL) {
3002                         gint line = quote_fmt_get_line();
3003                         alertpanel_error(err_msg, line);
3004                         goto error;
3005                 }
3006         } else
3007                 buf = "";
3008
3009         prev_autowrap = compose->autowrap;
3010         compose->autowrap = FALSE;
3011
3012         mark = gtk_text_buffer_get_insert(buffer);
3013         gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3014         if (g_utf8_validate(buf, -1, NULL)) { 
3015                 gtk_text_buffer_insert(buffer, &iter, buf, -1);
3016         } else {
3017                 gchar *tmpout = NULL;
3018                 tmpout = conv_codeset_strdup
3019                         (buf, conv_get_locale_charset_str_no_utf8(),
3020                          CS_INTERNAL);
3021                 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
3022                         g_free(tmpout);
3023                         tmpout = g_malloc(strlen(buf)*2+1);
3024                         conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
3025                 }
3026                 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
3027                 g_free(tmpout);
3028         }
3029
3030         cursor_pos = quote_fmt_get_cursor_pos();
3031         if (cursor_pos == -1)
3032                 cursor_pos = gtk_text_iter_get_offset(&iter);
3033         compose->set_cursor_pos = cursor_pos;
3034
3035         gtk_text_buffer_get_start_iter(buffer, &iter);
3036         gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
3037         gtk_text_buffer_place_cursor(buffer, &iter);
3038
3039         compose->autowrap = prev_autowrap;
3040         if (compose->autowrap && rewrap)
3041                 compose_wrap_all(compose);
3042
3043         goto ok;
3044
3045 error:
3046         buf = NULL;
3047 ok:
3048         SIGNAL_UNBLOCK(buffer);
3049
3050         procmsg_msginfo_free( dummyinfo );
3051
3052         return buf;
3053 }
3054
3055 /* if ml_post is of type addr@host and from is of type
3056  * addr-anything@host, return TRUE
3057  */
3058 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
3059 {
3060         gchar *left_ml = NULL;
3061         gchar *right_ml = NULL;
3062         gchar *left_from = NULL;
3063         gchar *right_from = NULL;
3064         gboolean result = FALSE;
3065         
3066         if (!ml_post || !from)
3067                 return FALSE;
3068         
3069         left_ml = g_strdup(ml_post);
3070         if (strstr(left_ml, "@")) {
3071                 right_ml = strstr(left_ml, "@")+1;
3072                 *(strstr(left_ml, "@")) = '\0';
3073         }
3074         
3075         left_from = g_strdup(from);
3076         if (strstr(left_from, "@")) {
3077                 right_from = strstr(left_from, "@")+1;
3078                 *(strstr(left_from, "@")) = '\0';
3079         }
3080         
3081         if (left_ml && left_from && right_ml && right_from
3082         &&  !strncmp(left_from, left_ml, strlen(left_ml))
3083         &&  !strcmp(right_from, right_ml)) {
3084                 result = TRUE;
3085         }
3086         g_free(left_ml);
3087         g_free(left_from);
3088         
3089         return result;
3090 }
3091
3092 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
3093                                      gboolean respect_default_to)
3094 {
3095         if (!compose)
3096                 return;
3097         if (!folder || !folder->prefs)
3098                 return;
3099
3100         if (respect_default_to && folder->prefs->enable_default_to) {
3101                 compose_entry_append(compose, folder->prefs->default_to,
3102                                         COMPOSE_TO, PREF_FOLDER);
3103                 compose_entry_mark_default_to(compose, folder->prefs->default_to);
3104         }
3105         if (folder->prefs->enable_default_cc)
3106                 compose_entry_append(compose, folder->prefs->default_cc,
3107                                         COMPOSE_CC, PREF_FOLDER);
3108         if (folder->prefs->enable_default_bcc)
3109                 compose_entry_append(compose, folder->prefs->default_bcc,
3110                                         COMPOSE_BCC, PREF_FOLDER);
3111         if (folder->prefs->enable_default_replyto)
3112                 compose_entry_append(compose, folder->prefs->default_replyto,
3113                                         COMPOSE_REPLYTO, PREF_FOLDER);
3114 }
3115
3116 static void compose_reply_set_subject(Compose *compose, MsgInfo *msginfo)
3117 {
3118         gchar *buf, *buf2;
3119         gchar *p;
3120         
3121         if (!compose || !msginfo)
3122                 return;
3123
3124         if (msginfo->subject && *msginfo->subject) {
3125                 buf = p = g_strdup(msginfo->subject);
3126                 p += subject_get_prefix_length(p);
3127                 memmove(buf, p, strlen(p) + 1);
3128
3129                 buf2 = g_strdup_printf("Re: %s", buf);
3130                 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3131
3132                 g_free(buf2);
3133                 g_free(buf);
3134         } else
3135                 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3136 }
3137
3138 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3139                                     gboolean to_all, gboolean to_ml,
3140                                     gboolean to_sender,
3141                                     gboolean followup_and_reply_to)
3142 {
3143         GSList *cc_list = NULL;
3144         GSList *cur;
3145         gchar *from = NULL;
3146         gchar *replyto = NULL;
3147         gchar *ac_email = NULL;
3148
3149         gboolean reply_to_ml = FALSE;
3150         gboolean default_reply_to = FALSE;
3151
3152         cm_return_if_fail(compose->account != NULL);
3153         cm_return_if_fail(msginfo != NULL);
3154
3155         reply_to_ml = to_ml && compose->ml_post;
3156
3157         default_reply_to = msginfo->folder && 
3158                 msginfo->folder->prefs->enable_default_reply_to;
3159
3160         if (compose->account->protocol != A_NNTP) {
3161                 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
3162
3163                 if (reply_to_ml && !default_reply_to) {
3164                         
3165                         gboolean is_subscr = is_subscription(compose->ml_post,
3166                                                              msginfo->from);
3167                         if (!is_subscr) {
3168                                 /* normal answer to ml post with a reply-to */
3169                                 compose_entry_append(compose,
3170                                            compose->ml_post,
3171                                            COMPOSE_TO, PREF_ML);
3172                                 if (compose->replyto)
3173                                         compose_entry_append(compose,
3174                                                 compose->replyto,
3175                                                 COMPOSE_CC, PREF_ML);
3176                         } else {
3177                                 /* answer to subscription confirmation */
3178                                 if (compose->replyto)
3179                                         compose_entry_append(compose,
3180                                                 compose->replyto,
3181                                                 COMPOSE_TO, PREF_ML);
3182                                 else if (msginfo->from)
3183                      &nbs