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