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