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