2007-06-04 [colin] 2.9.2cvs39
[claws.git] / src / compose.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2007 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 2 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, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
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/gtkmain.h>
34 #include <gtk/gtkmenu.h>
35 #include <gtk/gtkmenuitem.h>
36 #include <gtk/gtkitemfactory.h>
37 #include <gtk/gtkcheckmenuitem.h>
38 #include <gtk/gtkoptionmenu.h>
39 #include <gtk/gtkwidget.h>
40 #include <gtk/gtkvpaned.h>
41 #include <gtk/gtkentry.h>
42 #include <gtk/gtkeditable.h>
43 #include <gtk/gtkwindow.h>
44 #include <gtk/gtksignal.h>
45 #include <gtk/gtkvbox.h>
46 #include <gtk/gtkcontainer.h>
47 #include <gtk/gtkhandlebox.h>
48 #include <gtk/gtktoolbar.h>
49 #include <gtk/gtktable.h>
50 #include <gtk/gtkhbox.h>
51 #include <gtk/gtklabel.h>
52 #include <gtk/gtkscrolledwindow.h>
53 #include <gtk/gtktreeview.h>
54 #include <gtk/gtkliststore.h>
55 #include <gtk/gtktreeselection.h>
56 #include <gtk/gtktreemodel.h>
57
58 #include <gtk/gtkdnd.h>
59 #include <gtk/gtkclipboard.h>
60 #include <pango/pango-break.h>
61 #include <stdio.h>
62 #include <stdlib.h>
63 #include <string.h>
64 #include <ctype.h>
65 #include <sys/types.h>
66 #include <sys/stat.h>
67 #include <unistd.h>
68 #include <time.h>
69 #include <stdlib.h>
70 #if HAVE_SYS_WAIT_H
71 #  include <sys/wait.h>
72 #endif
73 #include <signal.h>
74 #include <errno.h>
75 #ifndef G_OS_WIN32  /* fixme we should have a configure test. */
76 #include <libgen.h>
77 #endif
78
79 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
80 #  include <wchar.h>
81 #  include <wctype.h>
82 #endif
83
84 #include "claws.h"
85 #include "main.h"
86 #include "mainwindow.h"
87 #include "compose.h"
88 #include "addressbook.h"
89 #include "folderview.h"
90 #include "procmsg.h"
91 #include "menu.h"
92 #include "stock_pixmap.h"
93 #include "send_message.h"
94 #include "imap.h"
95 #include "news.h"
96 #include "customheader.h"
97 #include "prefs_common.h"
98 #include "prefs_account.h"
99 #include "action.h"
100 #include "account.h"
101 #include "filesel.h"
102 #include "procheader.h"
103 #include "procmime.h"
104 #include "statusbar.h"
105 #include "about.h"
106 #include "base64.h"
107 #include "quoted-printable.h"
108 #include "codeconv.h"
109 #include "utils.h"
110 #include "gtkutils.h"
111 #include "socket.h"
112 #include "alertpanel.h"
113 #include "manage_window.h"
114 #include "gtkshruler.h"
115 #include "folder.h"
116 #include "addr_compl.h"
117 #include "quote_fmt.h"
118 #include "undo.h"
119 #include "foldersel.h"
120 #include "toolbar.h"
121 #include "inc.h"
122 #include "message_search.h"
123 #include "combobox.h"
124 #include "hooks.h"
125 #include "privacy.h"
126
127 enum
128 {
129         COL_MIMETYPE = 0,
130         COL_SIZE     = 1,
131         COL_NAME     = 2,
132         COL_DATA     = 3,
133         COL_AUTODATA = 4,
134         N_COL_COLUMNS
135 };
136
137 #define N_ATTACH_COLS   (N_COL_COLUMNS)
138
139 typedef enum
140 {
141         COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE,
142         COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER,
143         COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER,
144         COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD,
145         COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD,
146         COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE,
147         COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE,
148         COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE,
149         COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER,
150         COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER,
151         COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD,
152         COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD,
153         COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE,
154         COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE_N,
155         COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END
156 } ComposeCallAdvancedAction;
157
158 typedef enum
159 {
160         PRIORITY_HIGHEST = 1,
161         PRIORITY_HIGH,
162         PRIORITY_NORMAL,
163         PRIORITY_LOW,
164         PRIORITY_LOWEST
165 } PriorityLevel;
166
167 typedef enum
168 {
169         COMPOSE_INSERT_SUCCESS,
170         COMPOSE_INSERT_READ_ERROR,
171         COMPOSE_INSERT_INVALID_CHARACTER,
172         COMPOSE_INSERT_NO_FILE
173 } ComposeInsertResult;
174
175 typedef enum
176 {
177         COMPOSE_WRITE_FOR_SEND,
178         COMPOSE_WRITE_FOR_STORE
179 } ComposeWriteType;
180
181 typedef enum
182 {
183         COMPOSE_QUOTE_FORCED,
184         COMPOSE_QUOTE_CHECK,
185         COMPOSE_QUOTE_SKIP
186 } ComposeQuoteMode;
187
188 #define B64_LINE_SIZE           57
189 #define B64_BUFFSIZE            77
190
191 #define MAX_REFERENCES_LEN      999
192
193 static GList *compose_list = NULL;
194
195 static Compose *compose_generic_new                     (PrefsAccount   *account,
196                                                  const gchar    *to,
197                                                  FolderItem     *item,
198                                                  GPtrArray      *attach_files,
199                                                  GList          *listAddress );
200
201 static Compose *compose_create                  (PrefsAccount   *account,
202                                                  ComposeMode     mode,
203                                                  gboolean batch);
204
205 static void compose_entry_mark_default_to       (Compose          *compose,
206                                          const gchar      *address);
207 static Compose *compose_followup_and_reply_to   (MsgInfo        *msginfo,
208                                          ComposeQuoteMode        quote_mode,
209                                          gboolean        to_all,
210                                          gboolean        to_sender,
211                                          const gchar    *body);
212 static Compose *compose_forward_multiple        (PrefsAccount   *account, 
213                                          GSList         *msginfo_list);
214 static Compose *compose_reply                   (MsgInfo        *msginfo,
215                                          ComposeQuoteMode        quote_mode,
216                                          gboolean        to_all,
217                                          gboolean        to_ml,
218                                          gboolean        to_sender,
219                                          const gchar    *body);
220 static Compose *compose_reply_mode              (ComposeMode     mode, 
221                                          GSList         *msginfo_list, 
222                                          gchar          *body);
223 static void compose_template_apply_fields(Compose *compose, Template *tmpl);
224 static void compose_update_privacy_systems_menu(Compose *compose);
225
226 static GtkWidget *compose_account_option_menu_create
227                                                 (Compose        *compose);
228 static void compose_set_out_encoding            (Compose        *compose);
229 static void compose_set_template_menu           (Compose        *compose);
230 static void compose_template_apply              (Compose        *compose,
231                                                  Template       *tmpl,
232                                                  gboolean        replace);
233 static void compose_destroy                     (Compose        *compose);
234
235 static void compose_entries_set                 (Compose        *compose,
236                                                  const gchar    *mailto);
237 static gint compose_parse_header                (Compose        *compose,
238                                                  MsgInfo        *msginfo);
239 static gchar *compose_parse_references          (const gchar    *ref,
240                                                  const gchar    *msgid);
241
242 static gchar *compose_quote_fmt                 (Compose        *compose,
243                                                  MsgInfo        *msginfo,
244                                                  const gchar    *fmt,
245                                                  const gchar    *qmark,
246                                                  const gchar    *body,
247                                                  gboolean        rewrap,
248                                                  gboolean        need_unescape,
249                                                  const gchar *err_msg);
250
251 static void compose_reply_set_entry             (Compose        *compose,
252                                                  MsgInfo        *msginfo,
253                                                  gboolean        to_all,
254                                                  gboolean        to_ml,
255                                                  gboolean        to_sender,
256                                                  gboolean
257                                                  followup_and_reply_to);
258 static void compose_reedit_set_entry            (Compose        *compose,
259                                                  MsgInfo        *msginfo);
260
261 static void compose_insert_sig                  (Compose        *compose,
262                                                  gboolean        replace);
263 static gchar *compose_get_signature_str         (Compose        *compose);
264 static ComposeInsertResult compose_insert_file  (Compose        *compose,
265                                                  const gchar    *file);
266
267 static gboolean compose_attach_append           (Compose        *compose,
268                                                  const gchar    *file,
269                                                  const gchar    *type,
270                                                  const gchar    *content_type);
271 static void compose_attach_parts                (Compose        *compose,
272                                                  MsgInfo        *msginfo);
273
274 static void compose_beautify_paragraph          (Compose        *compose,
275                                                  GtkTextIter    *par_iter,
276                                                  gboolean        force);
277 static void compose_wrap_all                    (Compose        *compose);
278 static void compose_wrap_all_full               (Compose        *compose,
279                                                  gboolean        autowrap);
280
281 static void compose_set_title                   (Compose        *compose);
282 static void compose_select_account              (Compose        *compose,
283                                                  PrefsAccount   *account,
284                                                  gboolean        init);
285
286 static PrefsAccount *compose_current_mail_account(void);
287 /* static gint compose_send                     (Compose        *compose); */
288 static gboolean compose_check_for_valid_recipient
289                                                 (Compose        *compose);
290 static gboolean compose_check_entries           (Compose        *compose,
291                                                  gboolean       check_everything);
292 static gint compose_write_to_file               (Compose        *compose,
293                                                  FILE           *fp,
294                                                  gint            action,
295                                                  gboolean        attach_parts);
296 static gint compose_write_body_to_file          (Compose        *compose,
297                                                  const gchar    *file);
298 static gint compose_remove_reedit_target        (Compose        *compose,
299                                                  gboolean        force);
300 static void compose_remove_draft                        (Compose        *compose);
301 static gint compose_queue_sub                   (Compose        *compose,
302                                                  gint           *msgnum,
303                                                  FolderItem     **item,
304                                                  gchar          **msgpath,
305                                                  gboolean       check_subject,
306                                                  gboolean       remove_reedit_target);
307 static void compose_add_attachments             (Compose        *compose,
308                                                  MimeInfo       *parent);
309 static gchar *compose_get_header                (Compose        *compose);
310
311 static void compose_convert_header              (Compose        *compose,
312                                                  gchar          *dest,
313                                                  gint            len,
314                                                  gchar          *src,
315                                                  gint            header_len,
316                                                  gboolean        addr_field);
317
318 static void compose_attach_info_free            (AttachInfo     *ainfo);
319 static void compose_attach_remove_selected      (Compose        *compose);
320
321 static void compose_attach_property             (Compose        *compose);
322 static void compose_attach_property_create      (gboolean       *cancelled);
323 static void attach_property_ok                  (GtkWidget      *widget,
324                                                  gboolean       *cancelled);
325 static void attach_property_cancel              (GtkWidget      *widget,
326                                                  gboolean       *cancelled);
327 static gint attach_property_delete_event        (GtkWidget      *widget,
328                                                  GdkEventAny    *event,
329                                                  gboolean       *cancelled);
330 static gboolean attach_property_key_pressed     (GtkWidget      *widget,
331                                                  GdkEventKey    *event,
332                                                  gboolean       *cancelled);
333
334 static void compose_exec_ext_editor             (Compose        *compose);
335 #ifdef G_OS_UNIX
336 static gint compose_exec_ext_editor_real        (const gchar    *file);
337 static gboolean compose_ext_editor_kill         (Compose        *compose);
338 static gboolean compose_input_cb                (GIOChannel     *source,
339                                                  GIOCondition    condition,
340                                                  gpointer        data);
341 static void compose_set_ext_editor_sensitive    (Compose        *compose,
342                                                  gboolean        sensitive);
343 #endif /* G_OS_UNIX */
344
345 static void compose_undo_state_changed          (UndoMain       *undostruct,
346                                                  gint            undo_state,
347                                                  gint            redo_state,
348                                                  gpointer        data);
349
350 static void compose_create_header_entry (Compose *compose);
351 static void compose_add_header_entry    (Compose *compose, const gchar *header, gchar *text);
352 static void compose_remove_header_entries(Compose *compose);
353
354 static void compose_update_priority_menu_item(Compose * compose);
355 #if USE_ASPELL
356 static void compose_spell_menu_changed  (void *data);
357 #endif
358 static void compose_add_field_list      ( Compose *compose,
359                                           GList *listAddress );
360
361 /* callback functions */
362
363 static gboolean compose_edit_size_alloc (GtkEditable    *widget,
364                                          GtkAllocation  *allocation,
365                                          GtkSHRuler     *shruler);
366 static void account_activated           (GtkComboBox *optmenu,
367                                          gpointer        data);
368 static void attach_selected             (GtkTreeView    *tree_view, 
369                                          GtkTreePath    *tree_path,
370                                          GtkTreeViewColumn *column, 
371                                          Compose *compose);
372 static gboolean attach_button_pressed   (GtkWidget      *widget,
373                                          GdkEventButton *event,
374                                          gpointer        data);
375 static gboolean attach_key_pressed      (GtkWidget      *widget,
376                                          GdkEventKey    *event,
377                                          gpointer        data);
378 static void compose_send_cb             (gpointer        data,
379                                          guint           action,
380                                          GtkWidget      *widget);
381 static void compose_send_later_cb       (gpointer        data,
382                                          guint           action,
383                                          GtkWidget      *widget);
384
385 static void compose_draft_cb            (gpointer        data,
386                                          guint           action,
387                                          GtkWidget      *widget);
388
389 static void compose_attach_cb           (gpointer        data,
390                                          guint           action,
391                                          GtkWidget      *widget);
392 static void compose_insert_file_cb      (gpointer        data,
393                                          guint           action,
394                                          GtkWidget      *widget);
395 static void compose_insert_sig_cb       (gpointer        data,
396                                          guint           action,
397                                          GtkWidget      *widget);
398
399 static void compose_close_cb            (gpointer        data,
400                                          guint           action,
401                                          GtkWidget      *widget);
402
403 static void compose_set_encoding_cb     (gpointer        data,
404                                          guint           action,
405                                          GtkWidget      *widget);
406
407 static void compose_address_cb          (gpointer        data,
408                                          guint           action,
409                                          GtkWidget      *widget);
410 static void compose_template_activate_cb(GtkWidget      *widget,
411                                          gpointer        data);
412
413 static void compose_ext_editor_cb       (gpointer        data,
414                                          guint           action,
415                                          GtkWidget      *widget);
416
417 static gint compose_delete_cb           (GtkWidget      *widget,
418                                          GdkEventAny    *event,
419                                          gpointer        data);
420
421 static void compose_undo_cb             (Compose        *compose);
422 static void compose_redo_cb             (Compose        *compose);
423 static void compose_cut_cb              (Compose        *compose);
424 static void compose_copy_cb             (Compose        *compose);
425 static void compose_paste_cb            (Compose        *compose);
426 static void compose_paste_as_quote_cb   (Compose        *compose);
427 static void compose_paste_no_wrap_cb    (Compose        *compose);
428 static void compose_paste_wrap_cb       (Compose        *compose);
429 static void compose_allsel_cb           (Compose        *compose);
430
431 static void compose_advanced_action_cb  (Compose                   *compose,
432                                          ComposeCallAdvancedAction  action);
433
434 static void compose_grab_focus_cb       (GtkWidget      *widget,
435                                          Compose        *compose);
436
437 static void compose_changed_cb          (GtkTextBuffer  *textbuf,
438                                          Compose        *compose);
439
440 static void compose_wrap_cb             (gpointer        data,
441                                          guint           action,
442                                          GtkWidget      *widget);
443 static void compose_find_cb             (gpointer        data,
444                                          guint           action,
445                                          GtkWidget      *widget);
446 static void compose_toggle_autowrap_cb  (gpointer        data,
447                                          guint           action,
448                                          GtkWidget      *widget);
449
450 static void compose_toggle_ruler_cb     (gpointer        data,
451                                          guint           action,
452                                          GtkWidget      *widget);
453 static void compose_toggle_sign_cb      (gpointer        data,
454                                          guint           action,
455                                          GtkWidget      *widget);
456 static void compose_toggle_encrypt_cb   (gpointer        data,
457                                          guint           action,
458                                          GtkWidget      *widget);
459 static void compose_set_privacy_system_cb(GtkWidget      *widget,
460                                           gpointer        data);
461 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn);
462 static void activate_privacy_system     (Compose *compose, 
463                                          PrefsAccount *account,
464                                          gboolean warn);
465 static void compose_use_signing(Compose *compose, gboolean use_signing);
466 static void compose_use_encryption(Compose *compose, gboolean use_encryption);
467 static void compose_toggle_return_receipt_cb(gpointer data, guint action,
468                                              GtkWidget *widget);
469 static void compose_toggle_remove_refs_cb(gpointer data, guint action,
470                                              GtkWidget *widget);
471 static void compose_set_priority_cb     (gpointer        data,
472                                          guint           action,
473                                          GtkWidget      *widget);
474 static void compose_reply_change_mode   (gpointer        data,
475                                          ComposeMode    action,
476                                          GtkWidget      *widget);
477
478 static void compose_attach_drag_received_cb (GtkWidget          *widget,
479                                              GdkDragContext     *drag_context,
480                                              gint                x,
481                                              gint                y,
482                                              GtkSelectionData   *data,
483                                              guint               info,
484                                              guint               time,
485                                              gpointer            user_data);
486 static void compose_insert_drag_received_cb (GtkWidget          *widget,
487                                              GdkDragContext     *drag_context,
488                                              gint                x,
489                                              gint                y,
490                                              GtkSelectionData   *data,
491                                              guint               info,
492                                              guint               time,
493                                              gpointer            user_data);
494 static void compose_header_drag_received_cb (GtkWidget          *widget,
495                                              GdkDragContext     *drag_context,
496                                              gint                x,
497                                              gint                y,
498                                              GtkSelectionData   *data,
499                                              guint               info,
500                                              guint               time,
501                                              gpointer            user_data);
502
503 static gboolean compose_drag_drop           (GtkWidget *widget,
504                                              GdkDragContext *drag_context,
505                                              gint x, gint y,
506                                              guint time, gpointer user_data);
507
508 static void text_inserted               (GtkTextBuffer  *buffer,
509                                          GtkTextIter    *iter,
510                                          const gchar    *text,
511                                          gint            len,
512                                          Compose        *compose);
513 static Compose *compose_generic_reply(MsgInfo *msginfo,
514                                   ComposeQuoteMode quote_mode,
515                                   gboolean to_all,
516                                   gboolean to_ml,
517                                   gboolean to_sender,
518                                   gboolean followup_and_reply_to,
519                                   const gchar *body);
520
521 static gboolean compose_headerentry_changed_cb     (GtkWidget          *entry,
522                                             ComposeHeaderEntry *headerentry);
523 static gboolean compose_headerentry_key_press_event_cb(GtkWidget               *entry,
524                                             GdkEventKey        *event,
525                                             ComposeHeaderEntry *headerentry);
526
527 static void compose_show_first_last_header (Compose *compose, gboolean show_first);
528
529 static void compose_allow_user_actions (Compose *compose, gboolean allow);
530
531 #if USE_ASPELL
532 static void compose_check_all              (Compose *compose);
533 static void compose_highlight_all          (Compose *compose);
534 static void compose_check_backwards        (Compose *compose);
535 static void compose_check_forwards_go      (Compose *compose);
536 #endif
537
538 static gint compose_defer_auto_save_draft       (Compose        *compose);
539 static PrefsAccount *compose_guess_forward_account_from_msginfo (MsgInfo *msginfo);
540
541 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose);
542
543 #ifdef USE_ASPELL
544 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
545                                                 FolderItem *folder_item);
546 #endif
547
548 static GtkItemFactoryEntry compose_popup_entries[] =
549 {
550         {N_("/_Add..."),        NULL, compose_attach_cb, 0, NULL},
551         {N_("/_Remove"),        NULL, compose_attach_remove_selected, 0, NULL},
552         {"/---",                NULL, NULL, 0, "<Separator>"},
553         {N_("/_Properties..."), NULL, compose_attach_property, 0, NULL}
554 };
555
556 static GtkItemFactoryEntry compose_entries[] =
557 {
558         {N_("/_Message"),                               NULL, NULL, 0, "<Branch>"},
559         {N_("/_Message/S_end"),         "<control>Return",
560                                         compose_send_cb, 0, NULL},
561         {N_("/_Message/Send _later"),   "<shift><control>S",
562                                         compose_send_later_cb,  0, NULL},
563         {N_("/_Message/---"),                   NULL, NULL, 0, "<Separator>"},
564         {N_("/_Message/_Attach file"),          "<control>M", compose_attach_cb,      0, NULL},
565         {N_("/_Message/_Insert file"),          "<control>I", compose_insert_file_cb, 0, NULL},
566         {N_("/_Message/Insert si_gnature"),     "<control>G", compose_insert_sig_cb,  0, NULL},
567         {N_("/_Message/---"),                   NULL, NULL, 0, "<Separator>"},
568         {N_("/_Message/_Save"),
569                                                 "<control>S", compose_draft_cb, COMPOSE_KEEP_EDITING, NULL},
570         {N_("/_Message/---"),                   NULL, NULL, 0, "<Separator>"},
571         {N_("/_Message/_Close"),                        "<control>W", compose_close_cb, 0, NULL},
572
573         {N_("/_Edit"),                  NULL, NULL, 0, "<Branch>"},
574         {N_("/_Edit/_Undo"),            "<control>Z", compose_undo_cb, 0, NULL},
575         {N_("/_Edit/_Redo"),            "<control>Y", compose_redo_cb, 0, NULL},
576         {N_("/_Edit/---"),              NULL, NULL, 0, "<Separator>"},
577         {N_("/_Edit/Cu_t"),             "<control>X", compose_cut_cb,    0, NULL},
578         {N_("/_Edit/_Copy"),            "<control>C", compose_copy_cb,   0, NULL},
579         {N_("/_Edit/_Paste"),           "<control>V", compose_paste_cb,  0, NULL},
580         {N_("/_Edit/Special paste"),    NULL, NULL, 0, "<Branch>"},
581         {N_("/_Edit/Special paste/as _quotation"),
582                                         NULL, compose_paste_as_quote_cb, 0, NULL},
583         {N_("/_Edit/Special paste/_wrapped"),
584                                         NULL, compose_paste_wrap_cb, 0, NULL},
585         {N_("/_Edit/Special paste/_unwrapped"),
586                                         NULL, compose_paste_no_wrap_cb, 0, NULL},
587         {N_("/_Edit/Select _all"),      "<control>A", compose_allsel_cb, 0, NULL},
588         {N_("/_Edit/A_dvanced"),        NULL, NULL, 0, "<Branch>"},
589         {N_("/_Edit/A_dvanced/Move a character backward"),
590                                         "<shift><control>B",
591                                         compose_advanced_action_cb,
592                                         COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER,
593                                         NULL},
594         {N_("/_Edit/A_dvanced/Move a character forward"),
595                                         "<shift><control>F",
596                                         compose_advanced_action_cb,
597                                         COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER,
598                                         NULL},
599         {N_("/_Edit/A_dvanced/Move a word backward"),
600                                         NULL, /* "<alt>B" */
601                                         compose_advanced_action_cb,
602                                         COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD,
603                                         NULL},
604         {N_("/_Edit/A_dvanced/Move a word forward"),
605                                         NULL, /* "<alt>F" */
606                                         compose_advanced_action_cb,
607                                         COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD,
608                                         NULL},
609         {N_("/_Edit/A_dvanced/Move to beginning of line"),
610                                         NULL, /* "<control>A" */
611                                         compose_advanced_action_cb,
612                                         COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE,
613                                         NULL},
614         {N_("/_Edit/A_dvanced/Move to end of line"),
615                                         "<control>E",
616                                         compose_advanced_action_cb,
617                                         COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE,
618                                         NULL},
619         {N_("/_Edit/A_dvanced/Move to previous line"),
620                                         "<control>P",
621                                         compose_advanced_action_cb,
622                                         COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE,
623                                         NULL},
624         {N_("/_Edit/A_dvanced/Move to next line"),
625                                         "<control>N",
626                                         compose_advanced_action_cb,
627                                         COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE,
628                                         NULL},
629         {N_("/_Edit/A_dvanced/Delete a character backward"),
630                                         "<control>H",
631                                         compose_advanced_action_cb,
632                                         COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER,
633                                         NULL},
634         {N_("/_Edit/A_dvanced/Delete a character forward"),
635                                         "<control>D",
636                                         compose_advanced_action_cb,
637                                         COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER,
638                                         NULL},
639         {N_("/_Edit/A_dvanced/Delete a word backward"),
640                                         NULL, /* "<control>W" */
641                                         compose_advanced_action_cb,
642                                         COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD,
643                                         NULL},
644         {N_("/_Edit/A_dvanced/Delete a word forward"),
645                                         NULL, /* "<alt>D", */
646                                         compose_advanced_action_cb,
647                                         COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD,
648                                         NULL},
649         {N_("/_Edit/A_dvanced/Delete line"),
650                                         "<control>U",
651                                         compose_advanced_action_cb,
652                                         COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE,
653                                         NULL},
654         {N_("/_Edit/A_dvanced/Delete entire line"),
655                                         NULL,
656                                         compose_advanced_action_cb,
657                                         COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE_N,
658                                         NULL},
659         {N_("/_Edit/A_dvanced/Delete to end of line"),
660                                         "<control>K",
661                                         compose_advanced_action_cb,
662                                         COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END,
663                                         NULL},
664         {N_("/_Edit/---"),              NULL, NULL, 0, "<Separator>"},
665         {N_("/_Edit/_Find"),
666                                         "<control>F", compose_find_cb, 0, NULL},
667         {N_("/_Edit/---"),                      NULL, NULL, 0, "<Separator>"},
668         {N_("/_Edit/_Wrap current paragraph"),
669                                         "<control>L", compose_wrap_cb, 0, NULL},
670         {N_("/_Edit/Wrap all long _lines"),
671                                         "<control><alt>L", compose_wrap_cb, 1, NULL},
672         {N_("/_Edit/Aut_o wrapping"),   "<shift><control>L", compose_toggle_autowrap_cb, 0, "<ToggleItem>"},
673         {N_("/_Edit/---"),              NULL, NULL, 0, "<Separator>"},
674         {N_("/_Edit/Edit with e_xternal editor"),
675                                         "<shift><control>X", compose_ext_editor_cb, 0, NULL},
676 #if USE_ASPELL
677         {N_("/_Spelling"),              NULL, NULL, 0, "<Branch>"},
678         {N_("/_Spelling/_Check all or check selection"),
679                                         NULL, compose_check_all, 0, NULL},
680         {N_("/_Spelling/_Highlight all misspelled words"),
681                                         NULL, compose_highlight_all, 0, NULL},
682         {N_("/_Spelling/Check _backwards misspelled word"),
683                                         NULL, compose_check_backwards , 0, NULL},
684         {N_("/_Spelling/_Forward to next misspelled word"),
685                                         NULL, compose_check_forwards_go, 0, NULL},
686         {N_("/_Spelling/---"),          NULL, NULL, 0, "<Separator>"},
687         {N_("/_Spelling/Options"),
688                                         NULL, NULL, 0, "<Branch>"},
689 #endif
690         {N_("/_Options"),               NULL, NULL, 0, "<Branch>"},
691         {N_("/_Options/Reply _mode"),           NULL, NULL,   0, "<Branch>"},
692         {N_("/_Options/Reply _mode/_Normal"),   NULL, compose_reply_change_mode,   COMPOSE_REPLY, "<RadioItem>"},
693         {N_("/_Options/Reply _mode/_All"),              NULL, compose_reply_change_mode,   COMPOSE_REPLY_TO_ALL, "/Options/Reply mode/Normal"},
694         {N_("/_Options/Reply _mode/_Sender"),           NULL, compose_reply_change_mode,   COMPOSE_REPLY_TO_SENDER, "/Options/Reply mode/Normal"},
695         {N_("/_Options/Reply _mode/_Mailing-list"),     NULL, compose_reply_change_mode,   COMPOSE_REPLY_TO_LIST, "/Options/Reply mode/Normal"},
696         {N_("/_Options/---"),           NULL,           NULL,   0, "<Separator>"},
697         {N_("/_Options/Privacy _System"),               NULL, NULL,   0, "<Branch>"},
698         {N_("/_Options/Privacy _System/None"),  NULL, NULL,   0, "<RadioItem>"},
699         {N_("/_Options/Si_gn"),         NULL, compose_toggle_sign_cb   , 0, "<ToggleItem>"},
700         {N_("/_Options/_Encrypt"),      NULL, compose_toggle_encrypt_cb, 0, "<ToggleItem>"},
701         {N_("/_Options/---"),           NULL,           NULL,   0, "<Separator>"},
702         {N_("/_Options/_Priority"),     NULL,           NULL,   0, "<Branch>"},
703         {N_("/_Options/Priority/_Highest"), NULL, compose_set_priority_cb, PRIORITY_HIGHEST, "<RadioItem>"},
704         {N_("/_Options/Priority/Hi_gh"),    NULL, compose_set_priority_cb, PRIORITY_HIGH, "/Options/Priority/Highest"},
705         {N_("/_Options/Priority/_Normal"),  NULL, compose_set_priority_cb, PRIORITY_NORMAL, "/Options/Priority/Highest"},
706         {N_("/_Options/Priority/Lo_w"),    NULL, compose_set_priority_cb, PRIORITY_LOW, "/Options/Priority/Highest"},
707         {N_("/_Options/Priority/_Lowest"),  NULL, compose_set_priority_cb, PRIORITY_LOWEST, "/Options/Priority/Highest"},
708         {N_("/_Options/---"),           NULL,           NULL,   0, "<Separator>"},
709         {N_("/_Options/_Request Return Receipt"),       NULL, compose_toggle_return_receipt_cb, 0, "<ToggleItem>"},
710         {N_("/_Options/---"),           NULL,           NULL,   0, "<Separator>"},
711         {N_("/_Options/Remo_ve references"),    NULL, compose_toggle_remove_refs_cb, 0, "<ToggleItem>"},
712         {N_("/_Options/---"),           NULL,           NULL,   0, "<Separator>"},
713
714 #define ENC_ACTION(action) \
715         NULL, compose_set_encoding_cb, action, \
716         "/Options/Character encoding/Automatic"
717
718         {N_("/_Options/Character _encoding"), NULL, NULL, 0, "<Branch>"},
719         {N_("/_Options/Character _encoding/_Automatic"),
720                         NULL, compose_set_encoding_cb, C_AUTO, "<RadioItem>"},
721         {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
722
723         {N_("/_Options/Character _encoding/7bit ascii (US-ASC_II)"),
724          ENC_ACTION(C_US_ASCII)},
725         {N_("/_Options/Character _encoding/Unicode (_UTF-8)"),
726          ENC_ACTION(C_UTF_8)},
727         {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
728
729         {N_("/_Options/Character _encoding/Western European (ISO-8859-_1)"),
730          ENC_ACTION(C_ISO_8859_1)},
731         {N_("/_Options/Character _encoding/Western European (ISO-8859-15)"),
732          ENC_ACTION(C_ISO_8859_15)},
733         {N_("/_Options/Character _encoding/Western European (Windows-1252)"),
734          ENC_ACTION(C_WINDOWS_1252)},
735         {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
736
737         {N_("/_Options/Character _encoding/Central European (ISO-8859-_2)"),
738          ENC_ACTION(C_ISO_8859_2)},
739         {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
740
741         {N_("/_Options/Character _encoding/_Baltic (ISO-8859-13)"),
742          ENC_ACTION(C_ISO_8859_13)},
743         {N_("/_Options/Character _encoding/Baltic (ISO-8859-_4)"),
744          ENC_ACTION(C_ISO_8859_4)},
745         {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
746
747         {N_("/_Options/Character _encoding/Greek (ISO-8859-_7)"),
748          ENC_ACTION(C_ISO_8859_7)},
749         {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
750
751         {N_("/_Options/Character _encoding/Hebrew (ISO-8859-_8)"),
752          ENC_ACTION(C_ISO_8859_8)},
753         {N_("/_Options/Character _encoding/Hebrew (Windows-1255)"),
754          ENC_ACTION(C_WINDOWS_1255)},
755         {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
756
757         {N_("/_Options/Character _encoding/Arabic (ISO-8859-_6)"),
758          ENC_ACTION(C_ISO_8859_6)},
759         {N_("/_Options/Character _encoding/Arabic (Windows-1256)"),
760          ENC_ACTION(C_CP1256)},
761         {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
762
763         {N_("/_Options/Character _encoding/Turkish (ISO-8859-_9)"),
764          ENC_ACTION(C_ISO_8859_9)},
765         {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
766
767         {N_("/_Options/Character _encoding/Cyrillic (ISO-8859-_5)"),
768          ENC_ACTION(C_ISO_8859_5)},
769         {N_("/_Options/Character _encoding/Cyrillic (KOI8-_R)"),
770          ENC_ACTION(C_KOI8_R)},
771         {N_("/_Options/Character _encoding/Cyrillic (KOI8-U)"),
772          ENC_ACTION(C_KOI8_U)},
773         {N_("/_Options/Character _encoding/Cyrillic (Windows-1251)"),
774          ENC_ACTION(C_WINDOWS_1251)},
775         {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
776
777         {N_("/_Options/Character _encoding/Japanese (ISO-2022-_JP)"),
778          ENC_ACTION(C_ISO_2022_JP)},
779         {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
780
781         {N_("/_Options/Character _encoding/Simplified Chinese (_GB2312)"),
782          ENC_ACTION(C_GB2312)},
783         {N_("/_Options/Character _encoding/Simplified Chinese (GBK)"),
784          ENC_ACTION(C_GBK)},
785         {N_("/_Options/Character _encoding/Traditional Chinese (_Big5)"),
786          ENC_ACTION(C_BIG5)},
787         {N_("/_Options/Character _encoding/Traditional Chinese (EUC-_TW)"),
788          ENC_ACTION(C_EUC_TW)},
789         {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
790
791         {N_("/_Options/Character _encoding/Korean (EUC-_KR)"),
792          ENC_ACTION(C_EUC_KR)},
793         {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
794
795         {N_("/_Options/Character _encoding/Thai (TIS-620)"),
796          ENC_ACTION(C_TIS_620)},
797         {N_("/_Options/Character _encoding/Thai (Windows-874)"),
798          ENC_ACTION(C_WINDOWS_874)},
799
800         {N_("/_Tools"),                 NULL, NULL, 0, "<Branch>"},
801         {N_("/_Tools/Show _ruler"),     NULL, compose_toggle_ruler_cb, 0, "<ToggleItem>"},
802         {N_("/_Tools/_Address book"),   "<shift><control>A", compose_address_cb , 0, NULL},
803         {N_("/_Tools/_Template"),       NULL, NULL, 0, "<Branch>"},
804         {N_("/_Tools/Actio_ns"),        NULL, NULL, 0, "<Branch>"},
805         {N_("/_Help"),                  NULL, NULL, 0, "<Branch>"},
806         {N_("/_Help/_About"),           NULL, about_show, 0, NULL}
807 };
808
809 static GtkTargetEntry compose_mime_types[] =
810 {
811         {"text/uri-list", 0, 0},
812         {"UTF8_STRING", 0, 0},
813         {"text/plain", 0, 0}
814 };
815
816 static gboolean compose_put_existing_to_front(MsgInfo *info)
817 {
818         GList *compose_list = compose_get_compose_list();
819         GList *elem = NULL;
820         
821         if (compose_list) {
822                 for (elem = compose_list; elem != NULL && elem->data != NULL; 
823                      elem = elem->next) {
824                         Compose *c = (Compose*)elem->data;
825
826                         if (!c->targetinfo || !c->targetinfo->msgid ||
827                             !info->msgid)
828                                 continue;
829
830                         if (!strcmp(c->targetinfo->msgid, info->msgid)) {
831                                 gtkut_window_popup(c->window);
832                                 return TRUE;
833                         }
834                 }
835         }
836         return FALSE;
837 }
838
839 static GdkColor quote_color1 = 
840         {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
841 static GdkColor quote_color2 = 
842         {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
843 static GdkColor quote_color3 = 
844         {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
845
846 static GdkColor quote_bgcolor1 = 
847         {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
848 static GdkColor quote_bgcolor2 = 
849         {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
850 static GdkColor quote_bgcolor3 = 
851         {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
852
853 static GdkColor signature_color = {
854         (gulong)0,
855         (gushort)0x7fff,
856         (gushort)0x7fff,
857         (gushort)0x7fff
858 };
859
860 static GdkColor uri_color = {
861         (gulong)0,
862         (gushort)0,
863         (gushort)0,
864         (gushort)0
865 };
866
867 static void compose_create_tags(GtkTextView *text, Compose *compose)
868 {
869         GtkTextBuffer *buffer;
870         GdkColor black = {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
871         GdkColormap *cmap;
872         GdkColor color[8];
873         gboolean success[8];
874         int i;
875
876         buffer = gtk_text_view_get_buffer(text);
877
878         if (prefs_common.enable_color) {
879                 /* grab the quote colors, converting from an int to a GdkColor */
880                 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
881                                                &quote_color1);
882                 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
883                                                &quote_color2);
884                 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
885                                                &quote_color3);
886                 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_bgcol,
887                                                &quote_bgcolor1);
888                 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_bgcol,
889                                                &quote_bgcolor2);
890                 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_bgcol,
891                                                &quote_bgcolor3);
892                 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
893                                                &signature_color);
894                 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
895                                                &uri_color);
896         } else {
897                 signature_color = quote_color1 = quote_color2 = quote_color3 = 
898                         quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
899         }
900
901         if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
902                 gtk_text_buffer_create_tag(buffer, "quote0",
903                                            "foreground-gdk", &quote_color1,
904                                            "paragraph-background-gdk", &quote_bgcolor1,
905                                            NULL);
906                 gtk_text_buffer_create_tag(buffer, "quote1",
907                                            "foreground-gdk", &quote_color2,
908                                            "paragraph-background-gdk", &quote_bgcolor2,
909                                            NULL);
910                 gtk_text_buffer_create_tag(buffer, "quote2",
911                                            "foreground-gdk", &quote_color3,
912                                            "paragraph-background-gdk", &quote_bgcolor3,
913                                            NULL);
914         } else {
915                 gtk_text_buffer_create_tag(buffer, "quote0",
916                                            "foreground-gdk", &quote_color1,
917                                            NULL);
918                 gtk_text_buffer_create_tag(buffer, "quote1",
919                                            "foreground-gdk", &quote_color2,
920                                            NULL);
921                 gtk_text_buffer_create_tag(buffer, "quote2",
922                                            "foreground-gdk", &quote_color3,
923                                            NULL);
924         }
925         
926         gtk_text_buffer_create_tag(buffer, "signature",
927                                    "foreground-gdk", &signature_color,
928                                    NULL);
929         gtk_text_buffer_create_tag(buffer, "link",
930                                         "foreground-gdk", &uri_color,
931                                          NULL);
932         compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
933         compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
934
935         color[0] = quote_color1;
936         color[1] = quote_color2;
937         color[2] = quote_color3;
938         color[3] = quote_bgcolor1;
939         color[4] = quote_bgcolor2;
940         color[5] = quote_bgcolor3;
941         color[6] = signature_color;
942         color[7] = uri_color;
943         cmap = gdk_drawable_get_colormap(compose->window->window);
944         gdk_colormap_alloc_colors(cmap, color, 8, FALSE, TRUE, success);
945
946         for (i = 0; i < 8; i++) {
947                 if (success[i] == FALSE) {
948                         GtkStyle *style;
949
950                         g_warning("Compose: color allocation failed.\n");
951                         style = gtk_widget_get_style(GTK_WIDGET(text));
952                         quote_color1 = quote_color2 = quote_color3 = 
953                                 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = 
954                                 signature_color = uri_color = black;
955                 }
956         }
957 }
958
959 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
960                      GPtrArray *attach_files)
961 {
962         return compose_generic_new(account, mailto, NULL, attach_files, NULL);
963 }
964
965 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
966 {
967         return compose_generic_new(account, mailto, item, NULL, NULL);
968 }
969
970 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
971 {
972         return compose_generic_new( account, NULL, NULL, NULL, listAddress );
973 }
974
975 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
976                              GPtrArray *attach_files, GList *listAddress )
977 {
978         Compose *compose;
979         GtkTextView *textview;
980         GtkTextBuffer *textbuf;
981         GtkTextIter iter;
982         GtkItemFactory *ifactory;
983         gchar *subject_format = NULL;
984         gchar *body_format = NULL;
985
986         if (item && item->prefs && item->prefs->enable_default_account)
987                 account = account_find_from_id(item->prefs->default_account);
988
989         if (!account) account = cur_account;
990         g_return_val_if_fail(account != NULL, NULL);
991
992         compose = compose_create(account, COMPOSE_NEW, FALSE);
993
994         ifactory = gtk_item_factory_from_widget(compose->menubar);
995
996         compose->replyinfo = NULL;
997         compose->fwdinfo   = NULL;
998
999         textview = GTK_TEXT_VIEW(compose->text);
1000         textbuf = gtk_text_view_get_buffer(textview);
1001         compose_create_tags(textview, compose);
1002
1003         undo_block(compose->undostruct);
1004 #ifdef USE_ASPELL
1005         compose_set_dictionaries_from_folder_prefs(compose, item);
1006 #endif
1007
1008         if (account->auto_sig)
1009                 compose_insert_sig(compose, FALSE);
1010         gtk_text_buffer_get_start_iter(textbuf, &iter);
1011         gtk_text_buffer_place_cursor(textbuf, &iter);
1012
1013         if (account->protocol != A_NNTP) {
1014                 if (mailto && *mailto != '\0') {
1015                         compose_entries_set(compose, mailto);
1016
1017                 } else if (item && item->prefs->enable_default_to) {
1018                         compose_entry_append(compose, item->prefs->default_to, COMPOSE_TO);
1019                         compose_entry_mark_default_to(compose, item->prefs->default_to);
1020                 }
1021                 if (item && item->ret_rcpt) {
1022                         menu_set_active(ifactory, "/Options/Request Return Receipt", TRUE);
1023                 }
1024         } else {
1025                 if (mailto) {
1026                         compose_entry_append(compose, mailto, COMPOSE_NEWSGROUPS);
1027                 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1028                         compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS);
1029                 }
1030                 /*
1031                  * CLAWS: just don't allow return receipt request, even if the user
1032                  * may want to send an email. simple but foolproof.
1033                  */
1034                 menu_set_sensitive(ifactory, "/Options/Request Return Receipt", FALSE); 
1035         }
1036         compose_add_field_list( compose, listAddress );
1037
1038         if (item && item->prefs && item->prefs->compose_with_format) {
1039                 subject_format = item->prefs->compose_subject_format;
1040                 body_format = item->prefs->compose_body_format;
1041         } else if (account->compose_with_format) {
1042                 subject_format = account->compose_subject_format;
1043                 body_format = account->compose_body_format;
1044         } else if (prefs_common.compose_with_format) {
1045                 subject_format = prefs_common.compose_subject_format;
1046                 body_format = prefs_common.compose_body_format;
1047         }
1048
1049         if (subject_format || body_format) {
1050                 MsgInfo* dummyinfo = NULL;
1051
1052                 if ( subject_format
1053                          && *subject_format != '\0' )
1054                 {
1055                         gchar *subject = NULL;
1056                         gchar *tmp = NULL;
1057                         gchar *buf = NULL;
1058
1059                         dummyinfo = compose_msginfo_new_from_compose(compose);
1060
1061                         /* decode \-escape sequences in the internal representation of the quote format */
1062                         tmp = malloc(strlen(subject_format)+1);
1063                         pref_get_unescaped_pref(tmp, subject_format);
1064
1065                         subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1066 #ifdef USE_ASPELL
1067                         quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account,
1068                                         compose->gtkaspell);
1069 #else
1070                         quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account);
1071 #endif
1072                         quote_fmt_scan_string(tmp);
1073                         quote_fmt_parse();
1074
1075                         buf = quote_fmt_get_buffer();
1076                         if (buf == NULL)
1077                                 alertpanel_error(_("New message subject format error."));
1078                         else
1079                                 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1080                         quote_fmt_reset_vartable();
1081
1082                         g_free(subject);
1083                         g_free(tmp);
1084                 }
1085
1086                 if ( body_format
1087                          && *body_format != '\0' )
1088                 {
1089                         GtkTextView *text;
1090                         GtkTextBuffer *buffer;
1091                         GtkTextIter start, end;
1092                         gchar *tmp = NULL;
1093
1094                         if ( dummyinfo == NULL )
1095                                 dummyinfo = compose_msginfo_new_from_compose(compose);
1096
1097                         text = GTK_TEXT_VIEW(compose->text);
1098                         buffer = gtk_text_view_get_buffer(text);
1099                         gtk_text_buffer_get_start_iter(buffer, &start);
1100                         gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1101                         tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1102
1103                         compose_quote_fmt(compose, dummyinfo,
1104                                           body_format,
1105                                           NULL, tmp, FALSE, TRUE,
1106                                                   _("New message body format error at line %d."));
1107                         quote_fmt_reset_vartable();
1108
1109                         g_free(tmp);
1110                 }
1111
1112                 procmsg_msginfo_free( dummyinfo );
1113         }
1114
1115         if (attach_files) {
1116                 gint i;
1117                 gchar *file;
1118
1119                 for (i = 0; i < attach_files->len; i++) {
1120                         file = g_ptr_array_index(attach_files, i);
1121                         compose_attach_append(compose, file, file, NULL);
1122                 }
1123         }
1124
1125         compose_show_first_last_header(compose, TRUE);
1126
1127         /* Set save folder */
1128         if (item && item->prefs && item->prefs->save_copy_to_folder) {
1129                 gchar *folderidentifier;
1130
1131                 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
1132                 folderidentifier = folder_item_get_identifier(item);
1133                 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
1134                 g_free(folderidentifier);
1135         }
1136         
1137         gtk_widget_grab_focus(compose->header_last->entry);
1138
1139         undo_unblock(compose->undostruct);
1140
1141         if (prefs_common.auto_exteditor)
1142                 compose_exec_ext_editor(compose);
1143
1144         compose->modified = FALSE;
1145         compose_set_title(compose);
1146         return compose;
1147 }
1148
1149 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1150                 gboolean override_pref)
1151 {
1152         gchar *privacy = NULL;
1153
1154         g_return_if_fail(compose != NULL);
1155         g_return_if_fail(account != NULL);
1156
1157         if (override_pref == FALSE && account->default_encrypt_reply == FALSE)
1158                 return;
1159
1160         if (account->default_privacy_system
1161         &&  strlen(account->default_privacy_system)) {
1162                 privacy = account->default_privacy_system;
1163         } else {
1164                 GSList *privacy_avail = privacy_get_system_ids();
1165                 if (privacy_avail && g_slist_length(privacy_avail)) {
1166                         privacy = (gchar *)(privacy_avail->data);
1167                 }
1168         }
1169         if (privacy != NULL) {
1170                 if (compose->privacy_system == NULL)
1171                         compose->privacy_system = g_strdup(privacy);
1172                 compose_update_privacy_system_menu_item(compose, FALSE);
1173                 compose_use_encryption(compose, TRUE);
1174         }
1175 }       
1176
1177 static void compose_force_signing(Compose *compose, PrefsAccount *account)
1178 {
1179         gchar *privacy = NULL;
1180
1181         if (account->default_privacy_system
1182         &&  strlen(account->default_privacy_system)) {
1183                 privacy = account->default_privacy_system;
1184         } else {
1185                 GSList *privacy_avail = privacy_get_system_ids();
1186                 if (privacy_avail && g_slist_length(privacy_avail)) {
1187                         privacy = (gchar *)(privacy_avail->data);
1188                 }
1189         }
1190         if (privacy != NULL) {
1191                 if (compose->privacy_system == NULL)
1192                         compose->privacy_system = g_strdup(privacy);
1193                 compose_update_privacy_system_menu_item(compose, FALSE);
1194                 compose_use_signing(compose, TRUE);
1195         }
1196 }       
1197
1198 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1199 {
1200         MsgInfo *msginfo;
1201         guint list_len;
1202         Compose *compose = NULL;
1203         GtkItemFactory *ifactory = NULL;
1204         
1205         g_return_val_if_fail(msginfo_list != NULL, NULL);
1206
1207         msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1208         g_return_val_if_fail(msginfo != NULL, NULL);
1209
1210         list_len = g_slist_length(msginfo_list);
1211
1212         switch (mode) {
1213         case COMPOSE_REPLY:
1214                 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1215                               FALSE, prefs_common.default_reply_list, FALSE, body);
1216                 break;
1217         case COMPOSE_REPLY_WITH_QUOTE:
1218                 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED, 
1219                         FALSE, prefs_common.default_reply_list, FALSE, body);
1220                 break;
1221         case COMPOSE_REPLY_WITHOUT_QUOTE:
1222                 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP, 
1223                         FALSE, prefs_common.default_reply_list, FALSE, NULL);
1224                 break;
1225         case COMPOSE_REPLY_TO_SENDER:
1226                 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1227                               FALSE, FALSE, TRUE, body);
1228                 break;
1229         case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1230                 compose = compose_followup_and_reply_to(msginfo,
1231                                               COMPOSE_QUOTE_CHECK,
1232                                               FALSE, FALSE, body);
1233                 break;
1234         case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1235                 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED, 
1236                         FALSE, FALSE, TRUE, body);
1237                 break;
1238         case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1239                 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP, 
1240                         FALSE, FALSE, TRUE, NULL);
1241                 break;
1242         case COMPOSE_REPLY_TO_ALL:
1243                 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1244                         TRUE, FALSE, FALSE, body);
1245                 break;
1246         case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1247                 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED, 
1248                         TRUE, FALSE, FALSE, body);
1249                 break;
1250         case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1251                 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP, 
1252                         TRUE, FALSE, FALSE, NULL);
1253                 break;
1254         case COMPOSE_REPLY_TO_LIST:
1255                 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1256                         FALSE, TRUE, FALSE, body);
1257                 break;
1258         case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1259                 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED, 
1260                         FALSE, TRUE, FALSE, body);
1261                 break;
1262         case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1263                 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP, 
1264                         FALSE, TRUE, FALSE, NULL);
1265                 break;
1266         case COMPOSE_FORWARD:
1267                 if (prefs_common.forward_as_attachment) {
1268                         compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1269                         return compose;
1270                 } else {
1271                         compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1272                         return compose;
1273                 }
1274                 break;
1275         case COMPOSE_FORWARD_INLINE:
1276                 /* check if we reply to more than one Message */
1277                 if (list_len == 1) {
1278                         compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1279                         break;
1280                 } 
1281                 /* more messages FALL THROUGH */
1282         case COMPOSE_FORWARD_AS_ATTACH:
1283                 compose = compose_forward_multiple(NULL, msginfo_list);
1284                 break;
1285         case COMPOSE_REDIRECT:
1286                 compose = compose_redirect(NULL, msginfo, FALSE);
1287                 break;
1288         default:
1289                 g_warning("compose_reply_mode(): invalid Compose Mode: %d\n", mode);
1290         }
1291         
1292         ifactory = gtk_item_factory_from_widget(compose->menubar);
1293
1294         compose->rmode = mode;
1295         switch (compose->rmode) {
1296         case COMPOSE_REPLY:
1297         case COMPOSE_REPLY_WITH_QUOTE:
1298         case COMPOSE_REPLY_WITHOUT_QUOTE:
1299         case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1300                 debug_print("reply mode Normal\n");
1301                 menu_set_active(ifactory, "/Options/Reply mode/Normal", TRUE);
1302                 compose_reply_change_mode(compose, COMPOSE_REPLY, NULL); /* force update */
1303                 break;
1304         case COMPOSE_REPLY_TO_SENDER:
1305         case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1306         case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1307                 debug_print("reply mode Sender\n");
1308                 menu_set_active(ifactory, "/Options/Reply mode/Sender", TRUE);
1309                 break;
1310         case COMPOSE_REPLY_TO_ALL:
1311         case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1312         case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1313                 debug_print("reply mode All\n");
1314                 menu_set_active(ifactory, "/Options/Reply mode/All", TRUE);
1315                 break;
1316         case COMPOSE_REPLY_TO_LIST:
1317         case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1318         case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1319                 debug_print("reply mode List\n");
1320                 menu_set_active(ifactory, "/Options/Reply mode/Mailing-list", TRUE);
1321                 break;
1322         default:
1323                 break;
1324         }
1325         return compose;
1326 }
1327
1328 static Compose *compose_reply(MsgInfo *msginfo,
1329                                    ComposeQuoteMode quote_mode,
1330                                    gboolean to_all,
1331                                    gboolean to_ml,
1332                                    gboolean to_sender, 
1333                    const gchar *body)
1334 {
1335         return compose_generic_reply(msginfo, quote_mode, to_all, to_ml, 
1336                               to_sender, FALSE, body);
1337 }
1338
1339 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1340                                    ComposeQuoteMode quote_mode,
1341                                    gboolean to_all,
1342                                    gboolean to_sender,
1343                                    const gchar *body)
1344 {
1345         return compose_generic_reply(msginfo, quote_mode, to_all, FALSE, 
1346                               to_sender, TRUE, body);
1347 }
1348
1349 static void compose_extract_original_charset(Compose *compose)
1350 {
1351         MsgInfo *info = NULL;
1352         if (compose->replyinfo) {
1353                 info = compose->replyinfo;
1354         } else if (compose->fwdinfo) {
1355                 info = compose->fwdinfo;
1356         } else if (compose->targetinfo) {
1357                 info = compose->targetinfo;
1358         }
1359         if (info) {
1360                 MimeInfo *mimeinfo = procmime_scan_message(info);
1361                 MimeInfo *partinfo = mimeinfo;
1362                 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1363                         partinfo = procmime_mimeinfo_next(partinfo);
1364                 if (partinfo) {
1365                         compose->orig_charset = 
1366                                 g_strdup(procmime_mimeinfo_get_parameter(
1367                                                 partinfo, "charset"));
1368                 }
1369                 procmime_mimeinfo_free_all(mimeinfo);
1370         }
1371 }
1372
1373 #define SIGNAL_BLOCK(buffer) {                                  \
1374         g_signal_handlers_block_by_func(G_OBJECT(buffer),       \
1375                                 G_CALLBACK(compose_changed_cb), \
1376                                 compose);                       \
1377         g_signal_handlers_block_by_func(G_OBJECT(buffer),       \
1378                                 G_CALLBACK(text_inserted),      \
1379                                 compose);                       \
1380 }
1381
1382 #define SIGNAL_UNBLOCK(buffer) {                                \
1383         g_signal_handlers_unblock_by_func(G_OBJECT(buffer),     \
1384                                 G_CALLBACK(compose_changed_cb), \
1385                                 compose);                       \
1386         g_signal_handlers_unblock_by_func(G_OBJECT(buffer),     \
1387                                 G_CALLBACK(text_inserted),      \
1388                                 compose);                       \
1389 }
1390
1391 static Compose *compose_generic_reply(MsgInfo *msginfo,
1392                                   ComposeQuoteMode quote_mode,
1393                                   gboolean to_all, gboolean to_ml,
1394                                   gboolean to_sender,
1395                                   gboolean followup_and_reply_to,
1396                                   const gchar *body)
1397 {
1398         GtkItemFactory *ifactory;
1399         Compose *compose;
1400         PrefsAccount *account = NULL;
1401         GtkTextView *textview;
1402         GtkTextBuffer *textbuf;
1403         gboolean quote = FALSE;
1404         gchar *qmark = NULL;
1405         gchar *body_fmt = NULL;
1406
1407         g_return_val_if_fail(msginfo != NULL, NULL);
1408         g_return_val_if_fail(msginfo->folder != NULL, NULL);
1409
1410         account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1411
1412         g_return_val_if_fail(account != NULL, NULL);
1413
1414         compose = compose_create(account, COMPOSE_REPLY, FALSE);
1415
1416         compose->updating = TRUE;
1417
1418         ifactory = gtk_item_factory_from_widget(compose->menubar);
1419
1420         menu_set_active(ifactory, "/Options/Remove references", FALSE);
1421         menu_set_sensitive(ifactory, "/Options/Remove references", TRUE);
1422
1423         compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1424         if (!compose->replyinfo)
1425                 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1426
1427         compose_extract_original_charset(compose);
1428         
1429         if (msginfo->folder && msginfo->folder->ret_rcpt)
1430                 menu_set_active(ifactory, "/Options/Request Return Receipt", TRUE);
1431
1432         /* Set save folder */
1433         if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1434                 gchar *folderidentifier;
1435
1436                 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1437                 folderidentifier = folder_item_get_identifier(msginfo->folder);
1438                 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
1439                 g_free(folderidentifier);
1440         }
1441
1442         if (compose_parse_header(compose, msginfo) < 0) return NULL;
1443
1444         textview = (GTK_TEXT_VIEW(compose->text));
1445         textbuf = gtk_text_view_get_buffer(textview);
1446         compose_create_tags(textview, compose);
1447
1448         undo_block(compose->undostruct);
1449 #ifdef USE_ASPELL
1450                 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1451 #endif
1452
1453         if (quote_mode == COMPOSE_QUOTE_FORCED ||
1454                         (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1455                 /* use the reply format of folder (if enabled), or the account's one
1456                    (if enabled) or fallback to the global reply format, which is always
1457                    enabled (even if empty), and use the relevant quotemark */
1458                 quote = TRUE;
1459                 if (msginfo->folder && msginfo->folder->prefs &&
1460                                 msginfo->folder->prefs->reply_with_format) {
1461                         qmark = msginfo->folder->prefs->reply_quotemark;
1462                         body_fmt = msginfo->folder->prefs->reply_body_format;
1463
1464                 } else if (account->reply_with_format) {
1465                         qmark = account->reply_quotemark;
1466                         body_fmt = account->reply_body_format;
1467
1468                 } else {
1469                         qmark = prefs_common.quotemark;
1470                         body_fmt = prefs_common.quotefmt;
1471                 }
1472         }
1473
1474         if (quote) {
1475                 /* empty quotemark is not allowed */
1476                 if (qmark == NULL || *qmark == '\0')
1477                         qmark = "> ";
1478                 compose_quote_fmt(compose, compose->replyinfo,
1479                                   body_fmt, qmark, body, FALSE, TRUE,
1480                                           _("Message reply format error at line %d."));
1481                 quote_fmt_reset_vartable();
1482         }
1483         if (procmime_msginfo_is_encrypted(compose->replyinfo)) {
1484                 compose_force_encryption(compose, account, FALSE);
1485         }
1486
1487         SIGNAL_BLOCK(textbuf);
1488         
1489         if (account->auto_sig)
1490                 compose_insert_sig(compose, FALSE);
1491
1492         compose_wrap_all(compose);
1493
1494         SIGNAL_UNBLOCK(textbuf);
1495         
1496         gtk_widget_grab_focus(compose->text);
1497
1498         undo_unblock(compose->undostruct);
1499
1500         if (prefs_common.auto_exteditor)
1501                 compose_exec_ext_editor(compose);
1502                 
1503         compose->modified = FALSE;
1504         compose_set_title(compose);
1505
1506         compose->updating = FALSE;
1507
1508         if (compose->deferred_destroy) {
1509                 compose_destroy(compose);
1510                 return NULL;
1511         }
1512
1513         return compose;
1514 }
1515
1516 #define INSERT_FW_HEADER(var, hdr) \
1517 if (msginfo->var && *msginfo->var) { \
1518         gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1519         gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1520         gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1521 }
1522
1523 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1524                          gboolean as_attach, const gchar *body,
1525                          gboolean no_extedit,
1526                          gboolean batch)
1527 {
1528         Compose *compose;
1529         GtkTextView *textview;
1530         GtkTextBuffer *textbuf;
1531         GtkTextIter iter;
1532
1533         g_return_val_if_fail(msginfo != NULL, NULL);
1534         g_return_val_if_fail(msginfo->folder != NULL, NULL);
1535
1536         if (!account && 
1537             !(account = compose_guess_forward_account_from_msginfo
1538                                 (msginfo)))
1539                 account = cur_account;
1540
1541         compose = compose_create(account, COMPOSE_FORWARD, batch);
1542
1543         compose->updating = TRUE;
1544         compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1545         if (!compose->fwdinfo)
1546                 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1547
1548         compose_extract_original_charset(compose);
1549
1550         if (msginfo->subject && *msginfo->subject) {
1551                 gchar *buf, *buf2, *p;
1552
1553                 buf = p = g_strdup(msginfo->subject);
1554                 p += subject_get_prefix_length(p);
1555                 memmove(buf, p, strlen(p) + 1);
1556
1557                 buf2 = g_strdup_printf("Fw: %s", buf);
1558                 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1559                 
1560                 g_free(buf);
1561                 g_free(buf2);
1562         }
1563
1564         textview = GTK_TEXT_VIEW(compose->text);
1565         textbuf = gtk_text_view_get_buffer(textview);
1566         compose_create_tags(textview, compose);
1567         
1568         undo_block(compose->undostruct);
1569         if (as_attach) {
1570                 gchar *msgfile;
1571
1572                 msgfile = procmsg_get_message_file(msginfo);
1573                 if (!is_file_exist(msgfile))
1574                         g_warning("%s: file not exist\n", msgfile);
1575                 else
1576                         compose_attach_append(compose, msgfile, msgfile,
1577                                               "message/rfc822");
1578
1579                 g_free(msgfile);
1580         } else {
1581                 gchar *qmark = NULL;
1582                 gchar *body_fmt = prefs_common.fw_quotefmt;
1583                 MsgInfo *full_msginfo;
1584
1585                 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1586                 if (!full_msginfo)
1587                         full_msginfo = procmsg_msginfo_copy(msginfo);
1588
1589                 /* use the forward format of folder (if enabled), or the account's one
1590                    (if enabled) or fallback to the global forward format, which is always
1591                    enabled (even if empty), and use the relevant quotemark */
1592                 if (msginfo->folder && msginfo->folder->prefs &&
1593                                 msginfo->folder->prefs->forward_with_format) {
1594                         qmark = msginfo->folder->prefs->forward_quotemark;
1595                         body_fmt = msginfo->folder->prefs->forward_body_format;
1596
1597                 } else if (account->forward_with_format) {
1598                         qmark = account->forward_quotemark;
1599                         body_fmt = account->forward_body_format;
1600
1601                 } else {
1602                         qmark = prefs_common.fw_quotemark;
1603                         body_fmt = prefs_common.fw_quotefmt;
1604                 }
1605
1606                 /* empty quotemark is not allowed */
1607                 if (qmark == NULL || *qmark == '\0')
1608                         qmark = "> ";
1609
1610                 compose_quote_fmt(compose, full_msginfo,
1611                                   body_fmt, qmark, body, FALSE, TRUE,
1612                                           _("Message forward format error at line %d."));
1613                 quote_fmt_reset_vartable();
1614                 compose_attach_parts(compose, msginfo);
1615
1616                 procmsg_msginfo_free(full_msginfo);
1617         }
1618
1619         SIGNAL_BLOCK(textbuf);
1620
1621         if (account->auto_sig)
1622                 compose_insert_sig(compose, FALSE);
1623
1624         compose_wrap_all(compose);
1625
1626         SIGNAL_UNBLOCK(textbuf);
1627         
1628         gtk_text_buffer_get_start_iter(textbuf, &iter);
1629         gtk_text_buffer_place_cursor(textbuf, &iter);
1630
1631         gtk_widget_grab_focus(compose->header_last->entry);
1632
1633         if (!no_extedit && prefs_common.auto_exteditor)
1634                 compose_exec_ext_editor(compose);
1635         
1636         /*save folder*/
1637         if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1638                 gchar *folderidentifier;
1639
1640                 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1641                 folderidentifier = folder_item_get_identifier(msginfo->folder);
1642                 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
1643                 g_free(folderidentifier);
1644         }
1645
1646         undo_unblock(compose->undostruct);
1647         
1648         compose->modified = FALSE;
1649         compose_set_title(compose);
1650
1651         compose->updating = FALSE;
1652
1653         if (compose->deferred_destroy) {
1654                 compose_destroy(compose);
1655                 return NULL;
1656         }
1657
1658         return compose;
1659 }
1660
1661 #undef INSERT_FW_HEADER
1662
1663 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1664 {
1665         Compose *compose;
1666         GtkTextView *textview;
1667         GtkTextBuffer *textbuf;
1668         GtkTextIter iter;
1669         GSList *msginfo;
1670         gchar *msgfile;
1671         gboolean single_mail = TRUE;
1672         
1673         g_return_val_if_fail(msginfo_list != NULL, NULL);
1674
1675         if (g_slist_length(msginfo_list) > 1)
1676                 single_mail = FALSE;
1677
1678         for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1679                 if (((MsgInfo *)msginfo->data)->folder == NULL)
1680                         return NULL;
1681
1682         /* guess account from first selected message */
1683         if (!account && 
1684             !(account = compose_guess_forward_account_from_msginfo
1685                                 (msginfo_list->data)))
1686                 account = cur_account;
1687
1688         g_return_val_if_fail(account != NULL, NULL);
1689
1690         for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1691                 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1692                 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1693         }
1694
1695         compose = compose_create(account, COMPOSE_FORWARD, FALSE);
1696
1697         compose->updating = TRUE;
1698
1699         textview = GTK_TEXT_VIEW(compose->text);
1700         textbuf = gtk_text_view_get_buffer(textview);
1701         compose_create_tags(textview, compose);
1702         
1703         undo_block(compose->undostruct);
1704         for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1705                 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
1706
1707                 if (!is_file_exist(msgfile))
1708                         g_warning("%s: file not exist\n", msgfile);
1709                 else
1710                         compose_attach_append(compose, msgfile, msgfile,
1711                                 "message/rfc822");
1712                 g_free(msgfile);
1713         }
1714         
1715         if (single_mail) {
1716                 MsgInfo *info = (MsgInfo *)msginfo_list->data;
1717                 if (info->subject && *info->subject) {
1718                         gchar *buf, *buf2, *p;
1719
1720                         buf = p = g_strdup(info->subject);
1721                         p += subject_get_prefix_length(p);
1722                         memmove(buf, p, strlen(p) + 1);
1723
1724                         buf2 = g_strdup_printf("Fw: %s", buf);
1725                         gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1726
1727                         g_free(buf);
1728                         g_free(buf2);
1729                 }
1730         } else {
1731                 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
1732                         _("Fw: multiple emails"));
1733         }
1734
1735         SIGNAL_BLOCK(textbuf);
1736         
1737         if (account->auto_sig)
1738                 compose_insert_sig(compose, FALSE);
1739
1740         compose_wrap_all(compose);
1741
1742         SIGNAL_UNBLOCK(textbuf);
1743         
1744         gtk_text_buffer_get_start_iter(textbuf, &iter);
1745         gtk_text_buffer_place_cursor(textbuf, &iter);
1746
1747         gtk_widget_grab_focus(compose->header_last->entry);
1748         undo_unblock(compose->undostruct);
1749         compose->modified = FALSE;
1750         compose_set_title(compose);
1751
1752         compose->updating = FALSE;
1753
1754         if (compose->deferred_destroy) {
1755                 compose_destroy(compose);
1756                 return NULL;
1757         }
1758
1759         return compose;
1760 }
1761
1762 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter) 
1763 {
1764         GtkTextIter start = *iter;
1765         GtkTextIter end_iter;
1766         int start_pos = gtk_text_iter_get_offset(&start);
1767         gchar *str = NULL;
1768         if (!compose->account->sig_sep)
1769                 return FALSE;
1770         
1771         gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
1772                 start_pos+strlen(compose->account->sig_sep));
1773
1774         /* check sig separator */
1775         str = gtk_text_iter_get_text(&start, &end_iter);
1776         if (!strcmp(str, compose->account->sig_sep)) {
1777                 gchar *tmp = NULL;
1778                 /* check end of line (\n) */
1779                 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
1780                         start_pos+strlen(compose->account->sig_sep));
1781                 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
1782                         start_pos+strlen(compose->account->sig_sep)+1);
1783                 tmp = gtk_text_iter_get_text(&start, &end_iter);
1784                 if (!strcmp(tmp,"\n")) {
1785                         g_free(str);
1786                         g_free(tmp);
1787                         return TRUE;
1788                 }
1789                 g_free(tmp);    
1790         }
1791         g_free(str);
1792
1793         return FALSE;
1794 }
1795
1796 static void compose_colorize_signature(Compose *compose)
1797 {
1798         GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
1799         GtkTextIter iter;
1800         GtkTextIter end_iter;
1801         gtk_text_buffer_get_start_iter(buffer, &iter);
1802         while (gtk_text_iter_forward_line(&iter))
1803                 if (compose_is_sig_separator(compose, buffer, &iter)) {
1804                         gtk_text_buffer_get_end_iter(buffer, &end_iter);
1805                         gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
1806                 }
1807 }
1808
1809 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
1810 {
1811         Compose *compose = NULL;
1812         PrefsAccount *account = NULL;
1813         GtkTextView *textview;
1814         GtkTextBuffer *textbuf;
1815         GtkTextMark *mark;
1816         GtkTextIter iter;
1817         FILE *fp;
1818         gchar buf[BUFFSIZE];
1819         gboolean use_signing = FALSE;
1820         gboolean use_encryption = FALSE;
1821         gchar *privacy_system = NULL;
1822         int priority = PRIORITY_NORMAL;
1823         MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
1824
1825         g_return_val_if_fail(msginfo != NULL, NULL);
1826         g_return_val_if_fail(msginfo->folder != NULL, NULL);
1827
1828         if (compose_put_existing_to_front(msginfo)) {
1829                 return NULL;
1830         }
1831
1832         if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
1833             folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
1834                 gchar queueheader_buf[BUFFSIZE];
1835                 gint id, param;
1836
1837                 /* Select Account from queue headers */
1838                 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, 
1839                                              sizeof(queueheader_buf), "X-Claws-Account-Id:")) {
1840                         id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
1841                         account = account_find_from_id(id);
1842                 }
1843                 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, 
1844                                              sizeof(queueheader_buf), "X-Sylpheed-Account-Id:")) {
1845                         id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
1846                         account = account_find_from_id(id);
1847                 }
1848                 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf, 
1849                                              sizeof(queueheader_buf), "NAID:")) {
1850                         id = atoi(&queueheader_buf[strlen("NAID:")]);
1851                         account = account_find_from_id(id);
1852                 }
1853                 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf, 
1854                                                     sizeof(queueheader_buf), "MAID:")) {
1855                         id = atoi(&queueheader_buf[strlen("MAID:")]);
1856                         account = account_find_from_id(id);
1857                 }
1858                 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf, 
1859                                                                 sizeof(queueheader_buf), "S:")) {
1860                         account = account_find_from_address(queueheader_buf);
1861                 }
1862                 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, 
1863                                              sizeof(queueheader_buf), "X-Claws-Sign:")) {
1864                         param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
1865                         use_signing = param;
1866                         
1867                 }
1868                 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, 
1869                                              sizeof(queueheader_buf), "X-Sylpheed-Sign:")) {
1870                         param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
1871                         use_signing = param;
1872                         
1873                 }
1874                 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, 
1875                                              sizeof(queueheader_buf), "X-Claws-Encrypt:")) {
1876                         param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
1877                         use_encryption = param;
1878                 }
1879                 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, 
1880                                              sizeof(queueheader_buf), "X-Sylpheed-Encrypt:")) {
1881                         param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
1882                         use_encryption = param;
1883                 }
1884                 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, 
1885                                             sizeof(queueheader_buf), "X-Claws-Privacy-System:")) {
1886                         privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
1887                 }
1888                 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, 
1889                                             sizeof(queueheader_buf), "X-Sylpheed-Privacy-System:")) {
1890                         privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
1891                 }
1892                 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, 
1893                                              sizeof(queueheader_buf), "X-Priority: ")) {
1894                         param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
1895                         priority = param;
1896                 }
1897                 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, 
1898                                              sizeof(queueheader_buf), "RMID:")) {
1899                         gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
1900                         if (tokens[0] && tokens[1] && tokens[2]) {
1901                                 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
1902                                 if (orig_item != NULL) {
1903                                         replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
1904                                 }
1905                         }
1906                         g_strfreev(tokens);
1907                 }
1908                 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, 
1909                                              sizeof(queueheader_buf), "FMID:")) {
1910                         gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
1911                         if (tokens[0] && tokens[1] && tokens[2]) {
1912                                 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
1913                                 if (orig_item != NULL) {
1914                                         fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
1915                                 }
1916                         }
1917                         g_strfreev(tokens);
1918                 }
1919         } else {
1920                 account = msginfo->folder->folder->account;
1921         }
1922
1923         if (!account && prefs_common.reedit_account_autosel) {
1924                 gchar from[BUFFSIZE];
1925                 if (!procheader_get_header_from_msginfo(msginfo, from, sizeof(from), "FROM:")) {
1926                         extract_address(from);
1927                         account = account_find_from_address(from);
1928                 }
1929         }
1930         if (!account) {
1931                 account = cur_account;
1932         }
1933         g_return_val_if_fail(account != NULL, NULL);
1934
1935         compose = compose_create(account, COMPOSE_REEDIT, batch);
1936         
1937         compose->replyinfo = replyinfo;
1938         compose->fwdinfo = fwdinfo;
1939
1940         compose->updating = TRUE;
1941         compose->priority = priority;
1942
1943         if (privacy_system != NULL) {
1944                 compose->privacy_system = privacy_system;
1945                 compose_use_signing(compose, use_signing);
1946                 compose_use_encryption(compose, use_encryption);
1947                 compose_update_privacy_system_menu_item(compose, FALSE);
1948         } else {
1949                 activate_privacy_system(compose, account, FALSE);
1950         }
1951
1952         compose->targetinfo = procmsg_msginfo_copy(msginfo);
1953
1954         compose_extract_original_charset(compose);
1955
1956         if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
1957             folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
1958                 gchar queueheader_buf[BUFFSIZE];
1959
1960                 /* Set message save folder */
1961                 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "SCF:")) {
1962                         gint startpos = 0;
1963
1964                         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1965                         gtk_editable_delete_text(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
1966                         gtk_editable_insert_text(GTK_EDITABLE(compose->savemsg_entry), &queueheader_buf[4], strlen(&queueheader_buf[4]), &startpos);
1967                 }
1968                 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "RRCPT:")) {
1969                         gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
1970                         if (active) {
1971                                 GtkItemFactory *ifactory;
1972                                 ifactory = gtk_item_factory_from_widget(compose->menubar);
1973                                 menu_set_active(ifactory, "/Options/Request Return Receipt", TRUE);
1974                         }
1975                 }
1976         }
1977         
1978         if (compose_parse_header(compose, msginfo) < 0) {
1979                 compose->updating = FALSE;
1980                 compose_destroy(compose);
1981                 return NULL;
1982         }
1983         compose_reedit_set_entry(compose, msginfo);
1984
1985         textview = GTK_TEXT_VIEW(compose->text);
1986         textbuf = gtk_text_view_get_buffer(textview);
1987         compose_create_tags(textview, compose);
1988
1989         mark = gtk_text_buffer_get_insert(textbuf);
1990         gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
1991
1992         g_signal_handlers_block_by_func(G_OBJECT(textbuf),
1993                                         G_CALLBACK(compose_changed_cb),
1994                                         compose);
1995         
1996         if (procmime_msginfo_is_encrypted(msginfo)) {
1997                 fp = procmime_get_first_encrypted_text_content(msginfo);
1998                 if (fp) {
1999                         compose_force_encryption(compose, account, TRUE);
2000                 }
2001         } else {
2002                 fp = procmime_get_first_text_content(msginfo);
2003         }
2004         if (fp == NULL) {
2005                 g_warning("Can't get text part\n");
2006         }
2007
2008         if (fp != NULL) {
2009                 gboolean prev_autowrap = compose->autowrap;
2010
2011                 compose->autowrap = FALSE;
2012                 while (fgets(buf, sizeof(buf), fp) != NULL) {
2013                         strcrchomp(buf);
2014                         gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2015                 }
2016                 compose_wrap_all_full(compose, FALSE);
2017                 compose->autowrap = prev_autowrap;
2018                 fclose(fp);
2019         }
2020         
2021         compose_attach_parts(compose, msginfo);
2022
2023         compose_colorize_signature(compose);
2024
2025         g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2026                                         G_CALLBACK(compose_changed_cb),
2027                                         compose);
2028
2029         gtk_widget_grab_focus(compose->text);
2030
2031         if (prefs_common.auto_exteditor) {
2032                 compose_exec_ext_editor(compose);
2033         }
2034         compose->modified = FALSE;
2035         compose_set_title(compose);
2036
2037         compose->updating = FALSE;
2038
2039         if (compose->deferred_destroy) {
2040                 compose_destroy(compose);
2041                 return NULL;
2042         }
2043         
2044         compose->sig_str = compose_get_signature_str(compose);
2045         
2046         return compose;
2047 }
2048
2049 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2050                                                  gboolean batch)
2051 {
2052         Compose *compose;
2053         gchar *filename;
2054         GtkItemFactory *ifactory;
2055         FolderItem *item;
2056
2057         g_return_val_if_fail(msginfo != NULL, NULL);
2058
2059         if (!account)
2060                 account = account_get_reply_account(msginfo,
2061                                         prefs_common.reply_account_autosel);
2062         g_return_val_if_fail(account != NULL, NULL);
2063
2064         compose = compose_create(account, COMPOSE_REDIRECT, batch);
2065
2066         compose->updating = TRUE;
2067
2068         ifactory = gtk_item_factory_from_widget(compose->menubar);
2069         compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2070         compose->replyinfo = NULL;
2071         compose->fwdinfo = NULL;
2072
2073         compose_show_first_last_header(compose, TRUE);
2074
2075         gtk_widget_grab_focus(compose->header_last->entry);
2076
2077         filename = procmsg_get_message_file(msginfo);
2078
2079         if (filename == NULL) {
2080                 compose->updating = FALSE;
2081                 compose_destroy(compose);
2082
2083                 return NULL;
2084         }
2085
2086         compose->redirect_filename = filename;
2087         
2088         /* Set save folder */
2089         item = msginfo->folder;
2090         if (item && item->prefs && item->prefs->save_copy_to_folder) {
2091                 gchar *folderidentifier;
2092
2093                 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
2094                 folderidentifier = folder_item_get_identifier(item);
2095                 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
2096                 g_free(folderidentifier);
2097         }
2098
2099         compose_attach_parts(compose, msginfo);
2100
2101         if (msginfo->subject)
2102                 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2103                                    msginfo->subject);
2104         gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2105
2106         compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2107                                           _("Message redirect format error at line %d."));
2108         quote_fmt_reset_vartable();
2109         gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2110
2111         compose_colorize_signature(compose);
2112
2113         ifactory = gtk_item_factory_from_widget(compose->popupmenu);
2114         menu_set_sensitive(ifactory, "/Add...", FALSE);
2115         menu_set_sensitive(ifactory, "/Remove", FALSE);
2116         menu_set_sensitive(ifactory, "/Properties...", FALSE);
2117
2118         ifactory = gtk_item_factory_from_widget(compose->menubar);
2119         menu_set_sensitive(ifactory, "/Message/Save", FALSE);
2120         menu_set_sensitive(ifactory, "/Message/Insert file", FALSE);
2121         menu_set_sensitive(ifactory, "/Message/Attach file", FALSE);
2122         menu_set_sensitive(ifactory, "/Message/Insert signature", FALSE);
2123         menu_set_sensitive(ifactory, "/Edit", FALSE);
2124         menu_set_sensitive(ifactory, "/Options", FALSE);
2125         menu_set_sensitive(ifactory, "/Tools/Show ruler", FALSE);
2126         menu_set_sensitive(ifactory, "/Tools/Actions", FALSE);
2127         
2128         if (compose->toolbar->draft_btn)
2129                 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2130         if (compose->toolbar->insert_btn)
2131                 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2132         if (compose->toolbar->attach_btn)
2133                 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2134         if (compose->toolbar->sig_btn)
2135                 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2136         if (compose->toolbar->exteditor_btn)
2137                 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2138         if (compose->toolbar->linewrap_current_btn)
2139                 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2140         if (compose->toolbar->linewrap_all_btn)
2141                 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2142
2143         compose->modified = FALSE;
2144         compose_set_title(compose);
2145         compose->updating = FALSE;
2146
2147         if (compose->deferred_destroy) {
2148                 compose_destroy(compose);
2149                 return NULL;
2150         }
2151         
2152         return compose;
2153 }
2154
2155 GList *compose_get_compose_list(void)
2156 {
2157         return compose_list;
2158 }
2159
2160 void compose_entry_append(Compose *compose, const gchar *address,
2161                           ComposeEntryType type)
2162 {
2163         const gchar *header;
2164         gchar *cur, *begin;
2165         gboolean in_quote = FALSE;
2166         if (!address || *address == '\0') return;
2167
2168         switch (type) {
2169         case COMPOSE_CC:
2170                 header = N_("Cc:");
2171                 break;
2172         case COMPOSE_BCC:
2173                 header = N_("Bcc:");
2174                 break;
2175         case COMPOSE_REPLYTO:
2176                 header = N_("Reply-To:");
2177                 break;
2178         case COMPOSE_NEWSGROUPS:
2179                 header = N_("Newsgroups:");
2180                 break;
2181         case COMPOSE_FOLLOWUPTO:
2182                 header = N_( "Followup-To:");
2183                 break;
2184         case COMPOSE_TO:
2185         default:
2186                 header = N_("To:");
2187                 break;
2188         }
2189         header = prefs_common_translated_header_name(header);
2190         
2191         cur = begin = (gchar *)address;
2192         
2193         /* we separate the line by commas, but not if we're inside a quoted
2194          * string */
2195         while (*cur != '\0') {
2196                 if (*cur == '"') 
2197                         in_quote = !in_quote;
2198                 if (*cur == ',' && !in_quote) {
2199                         gchar *tmp = g_strdup(begin);
2200                         gchar *o_tmp = tmp;
2201                         tmp[cur-begin]='\0';
2202                         cur++;
2203                         begin = cur;
2204                         while (*tmp == ' ' || *tmp == '\t')
2205                                 tmp++;
2206                         compose_add_header_entry(compose, header, tmp);
2207                         g_free(o_tmp);
2208                         continue;
2209                 }
2210                 cur++;
2211         }
2212         if (begin < cur) {
2213                 gchar *tmp = g_strdup(begin);
2214                 gchar *o_tmp = tmp;
2215                 tmp[cur-begin]='\0';
2216                 cur++;
2217                 begin = cur;
2218                 while (*tmp == ' ' || *tmp == '\t')
2219                         tmp++;
2220                 compose_add_header_entry(compose, header, tmp);
2221                 g_free(o_tmp);          
2222         }
2223 }
2224
2225 static void compose_entry_mark_default_to(Compose *compose, const gchar *mailto)
2226 {
2227         static GdkColor yellow;
2228         static GdkColor black;
2229         static gboolean yellow_initialised = FALSE;
2230         GSList *h_list;
2231         GtkEntry *entry;
2232                 
2233         if (!yellow_initialised) {
2234                 gdk_color_parse("#f5f6be", &yellow);
2235                 gdk_color_parse("#000000", &black);
2236                 yellow_initialised = gdk_colormap_alloc_color(
2237                         gdk_colormap_get_system(), &yellow, FALSE, TRUE);
2238                 yellow_initialised &= gdk_colormap_alloc_color(
2239                         gdk_colormap_get_system(), &black, FALSE, TRUE);
2240         }
2241
2242         for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2243                 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2244                 if (gtk_entry_get_text(entry) && 
2245                     !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2246                         if (yellow_initialised) {
2247                                 gtk_widget_modify_base(
2248                                         GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2249                                         GTK_STATE_NORMAL, &yellow);
2250                                 gtk_widget_modify_text(
2251                                         GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2252                                         GTK_STATE_NORMAL, &black);
2253                         }
2254                 }
2255         }
2256 }
2257
2258 void compose_toolbar_cb(gint action, gpointer data)
2259 {
2260         ToolbarItem *toolbar_item = (ToolbarItem*)data;
2261         Compose *compose = (Compose*)toolbar_item->parent;
2262         
2263         g_return_if_fail(compose != NULL);
2264
2265         switch(action) {
2266         case A_SEND:
2267                 compose_send_cb(compose, 0, NULL);
2268                 break;
2269         case A_SENDL:
2270                 compose_send_later_cb(compose, 0, NULL);
2271                 break;
2272         case A_DRAFT:
2273                 compose_draft_cb(compose, COMPOSE_QUIT_EDITING, NULL);
2274                 break;
2275         case A_INSERT:
2276                 compose_insert_file_cb(compose, 0, NULL);
2277                 break;
2278         case A_ATTACH:
2279                 compose_attach_cb(compose, 0, NULL);
2280                 break;
2281         case A_SIG:
2282                 compose_insert_sig(compose, FALSE);
2283                 break;
2284         case A_EXTEDITOR:
2285                 compose_ext_editor_cb(compose, 0, NULL);
2286                 break;
2287         case A_LINEWRAP_CURRENT:
2288                 compose_beautify_paragraph(compose, NULL, TRUE);
2289                 break;
2290         case A_LINEWRAP_ALL:
2291                 compose_wrap_all_full(compose, TRUE);
2292                 break;
2293         case A_ADDRBOOK:
2294                 compose_address_cb(compose, 0, NULL);
2295                 break;
2296 #ifdef USE_ASPELL
2297         case A_CHECK_SPELLING:
2298                 compose_check_all(compose);
2299                 break;
2300 #endif
2301         default:
2302                 break;
2303         }
2304 }
2305
2306 static void compose_entries_set(Compose *compose, const gchar *mailto)
2307 {
2308         gchar *to = NULL;
2309         gchar *cc = NULL;
2310         gchar *subject = NULL;
2311         gchar *body = NULL;
2312         gchar *temp = NULL;
2313         gsize  len = 0;
2314         gchar *attach = NULL;
2315         
2316         scan_mailto_url(mailto, &to, &cc, NULL, &subject, &body, &attach);
2317
2318         if (to)
2319                 compose_entry_append(compose, to, COMPOSE_TO);
2320         if (cc)
2321                 compose_entry_append(compose, cc, COMPOSE_CC);
2322         if (subject) {
2323                 if (!g_utf8_validate (subject, -1, NULL)) {
2324                         temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2325                         gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2326                         g_free(temp);
2327                 } else {
2328                         gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2329                 }
2330         }
2331         if (body) {
2332                 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2333                 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2334                 GtkTextMark *mark;
2335                 GtkTextIter iter;
2336                 gboolean prev_autowrap = compose->autowrap;
2337
2338                 compose->autowrap = FALSE;
2339
2340                 mark = gtk_text_buffer_get_insert(buffer);
2341                 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2342
2343                 if (!g_utf8_validate (body, -1, NULL)) {
2344                         temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2345                         gtk_text_buffer_insert(buffer, &iter, temp, -1);
2346                         g_free(temp);
2347                 } else {
2348                         gtk_text_buffer_insert(buffer, &iter, body, -1);
2349                 }
2350                 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2351
2352                 compose->autowrap = prev_autowrap;
2353                 if (compose->autowrap)
2354                         compose_wrap_all(compose);
2355         }
2356
2357         if (attach) {
2358                 gchar *utf8_filename = conv_filename_to_utf8(attach);
2359                 if (utf8_filename) {
2360                         if (compose_attach_append(compose, attach, utf8_filename, NULL)) {
2361                                 alertpanel_notice(_("The file '%s' has been attached."), attach);
2362                         } 
2363                         g_free(utf8_filename);
2364                 } else {
2365                         alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2366                 }
2367         }
2368         g_free(to);
2369         g_free(cc);
2370         g_free(subject);
2371         g_free(body);
2372         g_free(attach);
2373 }
2374
2375 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2376 {
2377         static HeaderEntry hentry[] = {{"Reply-To:",    NULL, TRUE},
2378                                        {"Cc:",          NULL, TRUE},
2379                                        {"References:",  NULL, FALSE},
2380                                        {"Bcc:",         NULL, TRUE},
2381                                        {"Newsgroups:",  NULL, TRUE},
2382                                        {"Followup-To:", NULL, TRUE},
2383                                        {"List-Post:",   NULL, FALSE},
2384                                        {"X-Priority:",  NULL, FALSE},
2385                                        {NULL,           NULL, FALSE}};
2386
2387         enum
2388         {
2389                 H_REPLY_TO      = 0,
2390                 H_CC            = 1,
2391                 H_REFERENCES    = 2,
2392                 H_BCC           = 3,
2393                 H_NEWSGROUPS    = 4,
2394                 H_FOLLOWUP_TO   = 5,
2395                 H_LIST_POST     = 6,
2396                 H_X_PRIORITY    = 7
2397         };
2398
2399         FILE *fp;
2400
2401         g_return_val_if_fail(msginfo != NULL, -1);
2402
2403         if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2404         procheader_get_header_fields(fp, hentry);
2405         fclose(fp);
2406
2407         if (hentry[H_REPLY_TO].body != NULL) {
2408                 if (hentry[H_REPLY_TO].body[0] != '\0') {
2409                         compose->replyto =
2410                                 conv_unmime_header(hentry[H_REPLY_TO].body,
2411                                                    NULL);
2412                 }
2413                 g_free(hentry[H_REPLY_TO].body);
2414                 hentry[H_REPLY_TO].body = NULL;
2415         }
2416         if (hentry[H_CC].body != NULL) {
2417                 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL);
2418                 g_free(hentry[H_CC].body);
2419                 hentry[H_CC].body = NULL;
2420         }
2421         if (hentry[H_REFERENCES].body != NULL) {
2422                 if (compose->mode == COMPOSE_REEDIT)
2423                         compose->references = hentry[H_REFERENCES].body;
2424                 else {
2425                         compose->references = compose_parse_references
2426                                 (hentry[H_REFERENCES].body, msginfo->msgid);
2427                         g_free(hentry[H_REFERENCES].body);
2428                 }
2429                 hentry[H_REFERENCES].body = NULL;
2430         }
2431         if (hentry[H_BCC].body != NULL) {
2432                 if (compose->mode == COMPOSE_REEDIT)
2433                         compose->bcc =
2434                                 conv_unmime_header(hentry[H_BCC].body, NULL);
2435                 g_free(hentry[H_BCC].body);
2436                 hentry[H_BCC].body = NULL;
2437         }
2438         if (hentry[H_NEWSGROUPS].body != NULL) {
2439                 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2440                 hentry[H_NEWSGROUPS].body = NULL;
2441         }
2442         if (hentry[H_FOLLOWUP_TO].body != NULL) {
2443                 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2444                         compose->followup_to =
2445                                 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2446                                                    NULL);
2447                 }
2448                 g_free(hentry[H_FOLLOWUP_TO].body);
2449                 hentry[H_FOLLOWUP_TO].body = NULL;
2450         }
2451         if (hentry[H_LIST_POST].body != NULL) {
2452                 gchar *to = NULL;
2453
2454                 extract_address(hentry[H_LIST_POST].body);
2455                 if (hentry[H_LIST_POST].body[0] != '\0') {
2456                         scan_mailto_url(hentry[H_LIST_POST].body,
2457                                         &to, NULL, NULL, NULL, NULL, NULL);
2458                         if (to) {
2459                                 g_free(compose->ml_post);
2460                                 compose->ml_post = to;
2461                         }
2462                 }
2463                 g_free(hentry[H_LIST_POST].body);
2464                 hentry[H_LIST_POST].body = NULL;
2465         }
2466
2467         /* CLAWS - X-Priority */
2468         if (compose->mode == COMPOSE_REEDIT)
2469                 if (hentry[H_X_PRIORITY].body != NULL) {
2470                         gint priority;
2471                         
2472                         priority = atoi(hentry[H_X_PRIORITY].body);
2473                         g_free(hentry[H_X_PRIORITY].body);
2474                         
2475                         hentry[H_X_PRIORITY].body = NULL;
2476                         
2477                         if (priority < PRIORITY_HIGHEST || 
2478                             priority > PRIORITY_LOWEST)
2479                                 priority = PRIORITY_NORMAL;
2480                         
2481                         compose->priority =  priority;
2482                 }
2483  
2484         if (compose->mode == COMPOSE_REEDIT) {
2485                 if (msginfo->inreplyto && *msginfo->inreplyto)
2486                         compose->inreplyto = g_strdup(msginfo->inreplyto);
2487                 return 0;
2488         }
2489
2490         if (msginfo->msgid && *msginfo->msgid)
2491                 compose->inreplyto = g_strdup(msginfo->msgid);
2492
2493         if (!compose->references) {
2494                 if (msginfo->msgid && *msginfo->msgid) {
2495                         if (msginfo->inreplyto && *msginfo->inreplyto)
2496                                 compose->references =
2497                                         g_strdup_printf("<%s>\n\t<%s>",
2498                                                         msginfo->inreplyto,
2499                                                         msginfo->msgid);
2500                         else
2501                                 compose->references =
2502                                         g_strconcat("<", msginfo->msgid, ">",
2503                                                     NULL);
2504                 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
2505                         compose->references =
2506                                 g_strconcat("<", msginfo->inreplyto, ">",
2507                                             NULL);
2508                 }
2509         }
2510
2511         return 0;
2512 }
2513
2514 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
2515 {
2516         GSList *ref_id_list, *cur;
2517         GString *new_ref;
2518         gchar *new_ref_str;
2519
2520         ref_id_list = references_list_append(NULL, ref);
2521         if (!ref_id_list) return NULL;
2522         if (msgid && *msgid)
2523                 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
2524
2525         for (;;) {
2526                 gint len = 0;
2527
2528                 for (cur = ref_id_list; cur != NULL; cur = cur->next)
2529                         /* "<" + Message-ID + ">" + CR+LF+TAB */
2530                         len += strlen((gchar *)cur->data) + 5;
2531
2532                 if (len > MAX_REFERENCES_LEN) {
2533                         /* remove second message-ID */
2534                         if (ref_id_list && ref_id_list->next &&
2535                             ref_id_list->next->next) {
2536                                 g_free(ref_id_list->next->data);
2537                                 ref_id_list = g_slist_remove
2538                                         (ref_id_list, ref_id_list->next->data);
2539                         } else {
2540                                 slist_free_strings(ref_id_list);
2541                                 g_slist_free(ref_id_list);
2542                                 return NULL;
2543                         }
2544                 } else
2545                         break;
2546         }
2547
2548         new_ref = g_string_new("");
2549         for (cur = ref_id_list; cur != NULL; cur = cur->next) {
2550                 if (new_ref->len > 0)
2551                         g_string_append(new_ref, "\n\t");
2552                 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
2553         }
2554
2555         slist_free_strings(ref_id_list);
2556         g_slist_free(ref_id_list);
2557
2558         new_ref_str = new_ref->str;
2559         g_string_free(new_ref, FALSE);
2560
2561         return new_ref_str;
2562 }
2563
2564 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
2565                                 const gchar *fmt, const gchar *qmark,
2566                                 const gchar *body, gboolean rewrap,
2567                                 gboolean need_unescape,
2568                                 const gchar *err_msg)
2569 {
2570         MsgInfo* dummyinfo = NULL;
2571         gchar *quote_str = NULL;
2572         gchar *buf;
2573         gboolean prev_autowrap;
2574         const gchar *trimmed_body = body;
2575         gint cursor_pos = -1;
2576         GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2577         GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2578         GtkTextIter iter;
2579         GtkTextMark *mark;
2580         
2581
2582         SIGNAL_BLOCK(buffer);
2583
2584         if (!msginfo) {
2585                 dummyinfo = compose_msginfo_new_from_compose(compose);
2586                 msginfo = dummyinfo;
2587         }
2588
2589         if (qmark != NULL) {
2590 #ifdef USE_ASPELL
2591                 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
2592                                 compose->gtkaspell);
2593 #else
2594                 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
2595 #endif
2596                 quote_fmt_scan_string(qmark);
2597                 quote_fmt_parse();
2598
2599                 buf = quote_fmt_get_buffer();
2600                 if (buf == NULL)
2601                         alertpanel_error(_("Quote mark format error."));
2602                 else
2603                         Xstrdup_a(quote_str, buf, goto error)
2604         }
2605
2606         if (fmt && *fmt != '\0') {
2607
2608                 if (trimmed_body)
2609                         while (*trimmed_body == '\n')
2610                                 trimmed_body++;
2611
2612 #ifdef USE_ASPELL
2613                 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account,
2614                                 compose->gtkaspell);
2615 #else
2616                 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account);
2617 #endif
2618                 if (need_unescape) {
2619                         gchar *tmp = NULL;
2620
2621                         /* decode \-escape sequences in the internal representation of the quote format */
2622                         tmp = malloc(strlen(fmt)+1);
2623                         pref_get_unescaped_pref(tmp, fmt);
2624                         quote_fmt_scan_string(tmp);
2625                         quote_fmt_parse();
2626                         g_free(tmp);
2627                 } else {
2628                         quote_fmt_scan_string(fmt);
2629                         quote_fmt_parse();
2630                 }
2631
2632                 buf = quote_fmt_get_buffer();
2633                 if (buf == NULL) {
2634                         gint line = quote_fmt_get_line();
2635                         gchar *msg = g_strdup_printf(err_msg, line);
2636                         alertpanel_error(msg);
2637                         g_free(msg);
2638                         goto error;
2639                 }
2640         } else
2641                 buf = "";
2642
2643         prev_autowrap = compose->autowrap;
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         if (g_utf8_validate(buf, -1, NULL)) { 
2649                 gtk_text_buffer_insert(buffer, &iter, buf, -1);
2650         } else {
2651                 gchar *tmpout = NULL;
2652                 tmpout = conv_codeset_strdup
2653                         (buf, conv_get_locale_charset_str_no_utf8(),
2654                          CS_INTERNAL);
2655                 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
2656                         g_free(tmpout);
2657                         tmpout = g_malloc(strlen(buf)*2+1);
2658                         conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
2659                 }
2660                 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
2661                 g_free(tmpout);
2662         }
2663
2664         cursor_pos = quote_fmt_get_cursor_pos();
2665         compose->set_cursor_pos = cursor_pos;
2666         if (cursor_pos == -1) {
2667                 cursor_pos = 0;
2668         }
2669         gtk_text_buffer_get_start_iter(buffer, &iter);
2670         gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
2671         gtk_text_buffer_place_cursor(buffer, &iter);
2672
2673         compose->autowrap = prev_autowrap;
2674         if (compose->autowrap && rewrap)
2675                 compose_wrap_all(compose);
2676
2677         goto ok;
2678
2679 error:
2680         buf = NULL;
2681 ok:
2682         SIGNAL_UNBLOCK(buffer);
2683
2684         procmsg_msginfo_free( dummyinfo );
2685
2686         return buf;
2687 }
2688
2689 /* if ml_post is of type addr@host and from is of type
2690  * addr-anything@host, return TRUE
2691  */
2692 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
2693 {
2694         gchar *left_ml = NULL;
2695         gchar *right_ml = NULL;
2696         gchar *left_from = NULL;
2697         gchar *right_from = NULL;
2698         gboolean result = FALSE;
2699         
2700         if (!ml_post || !from)
2701                 return FALSE;
2702         
2703         left_ml = g_strdup(ml_post);
2704         if (strstr(left_ml, "@")) {
2705                 right_ml = strstr(left_ml, "@")+1;
2706                 *(strstr(left_ml, "@")) = '\0';
2707         }
2708         
2709         left_from = g_strdup(from);
2710         if (strstr(left_from, "@")) {
2711                 right_from = strstr(left_from, "@")+1;
2712                 *(strstr(left_from, "@")) = '\0';
2713         }
2714         
2715         if (left_ml && left_from && right_ml && right_from
2716         &&  !strncmp(left_from, left_ml, strlen(left_ml))
2717         &&  !strcmp(right_from, right_ml)) {
2718                 result = TRUE;
2719         }
2720         g_free(left_ml);
2721         g_free(left_from);
2722         
2723         return result;
2724 }
2725
2726 static gboolean same_address(const gchar *addr1, const gchar *addr2)
2727 {
2728         gchar *my_addr1, *my_addr2;
2729         
2730         if (!addr1 || !addr2)
2731                 return FALSE;
2732
2733         Xstrdup_a(my_addr1, addr1, return FALSE);
2734         Xstrdup_a(my_addr2, addr2, return FALSE);
2735         
2736         extract_address(my_addr1);
2737         extract_address(my_addr2);
2738         
2739         return !strcasecmp(my_addr1, my_addr2);
2740 }
2741
2742 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
2743                                     gboolean to_all, gboolean to_ml,
2744                                     gboolean to_sender,
2745                                     gboolean followup_and_reply_to)
2746 {
2747         GSList *cc_list = NULL;
2748         GSList *cur;
2749         gchar *from = NULL;
2750         gchar *replyto = NULL;
2751         GHashTable *to_table;
2752
2753         gboolean reply_to_ml = FALSE;
2754         gboolean default_reply_to = FALSE;
2755
2756         g_return_if_fail(compose->account != NULL);
2757         g_return_if_fail(msginfo != NULL);
2758
2759         reply_to_ml = to_ml && compose->ml_post;
2760
2761         default_reply_to = msginfo->folder && 
2762                 msginfo->folder->prefs->enable_default_reply_to;
2763
2764         if (compose->account->protocol != A_NNTP) {
2765                 if (reply_to_ml && !default_reply_to) {
2766                         
2767                         gboolean is_subscr = is_subscription(compose->ml_post,
2768                                                              msginfo->from);
2769                         if (!is_subscr) {
2770                                 /* normal answer to ml post with a reply-to */
2771                                 compose_entry_append(compose,
2772                                            compose->ml_post,
2773                                            COMPOSE_TO);
2774                                 if (compose->replyto
2775                                 &&  !same_address(compose->ml_post, compose->replyto))
2776                                         compose_entry_append(compose,
2777                                                 compose->replyto,
2778                                                 COMPOSE_CC);
2779                         } else {
2780                                 /* answer to subscription confirmation */
2781                                 if (compose->replyto)
2782                                         compose_entry_append(compose,
2783                                                 compose->replyto,
2784                                                 COMPOSE_TO);
2785                                 else if (msginfo->from)
2786                                         compose_entry_append(compose,
2787                                                 msginfo->from,
2788                                                 COMPOSE_TO);
2789                         }
2790                 }
2791                 else if (!(to_all || to_sender) && default_reply_to) {
2792                         compose_entry_append(compose,
2793                             msginfo->folder->prefs->default_reply_to,
2794                             COMPOSE_TO);
2795                         compose_entry_mark_default_to(compose,
2796                                 msginfo->folder->prefs->default_reply_to);
2797                 } else {
2798                         gchar *tmp1 = NULL;
2799                         if (!msginfo->from)
2800                                 return;
2801                         Xstrdup_a(tmp1, msginfo->from, return);
2802                         extract_address(tmp1);
2803                         if (to_all || to_sender ||
2804                             !account_find_from_address(tmp1))
2805                                 compose_entry_append(compose,
2806                                  (compose->replyto && !to_sender)
2807                                           ? compose->replyto :
2808                                           msginfo->from ? msginfo->from : "",
2809                                           COMPOSE_TO);
2810                         else if (!to_all && !to_sender) {
2811                                 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
2812                                     !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
2813                                     !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
2814                                         compose_entry_append(compose,
2815                                                   msginfo->from ? msginfo->from : "",
2816                                                   COMPOSE_TO);
2817                                 } else {
2818                                         /* replying to own mail, use original recp */
2819                                         compose_entry_append(compose,
2820                                                   msginfo->to ? msginfo->to : "",
2821                                                   COMPOSE_TO);
2822                                         compose_entry_append(compose,
2823                                                   msginfo->cc ? msginfo->cc : "",
2824                                                   COMPOSE_CC);
2825                                 }
2826                         }
2827                 }
2828         } else {
2829                 if (to_sender || (compose->followup_to && 
2830                         !strncmp(compose->followup_to, "poster", 6)))
2831                         compose_entry_append
2832                                 (compose, 
2833                                  (compose->replyto ? compose->replyto :
2834                                         msginfo->from ? msginfo->from : ""),
2835                                  COMPOSE_TO);
2836                                  
2837                 else if (followup_and_reply_to || to_all) {
2838                         compose_entry_append
2839                                 (compose,
2840                                  (compose->replyto ? compose->replyto :
2841                                  msginfo->from ? msginfo->from : ""),
2842                                  COMPOSE_TO);                           
2843                 
2844                         compose_entry_append
2845                                 (compose,
2846                                  compose->followup_to ? compose->followup_to :
2847                                  compose->newsgroups ? compose->newsgroups : "",
2848                                  COMPOSE_NEWSGROUPS);
2849                 } 
2850                 else 
2851                         compose_entry_append
2852                                 (compose,
2853                                  compose->followup_to ? compose->followup_to :
2854                                  compose->newsgroups ? compose->newsgroups : "",
2855                                  COMPOSE_NEWSGROUPS);
2856         }
2857
2858         if (msginfo->subject && *msginfo->subject) {
2859                 gchar *buf, *buf2;
2860                 gchar *p;
2861
2862                 buf = p = g_strdup(msginfo->subject);
2863                 p += subject_get_prefix_length(p);
2864                 memmove(buf, p, strlen(p) + 1);
2865
2866                 buf2 = g_strdup_printf("Re: %s", buf);
2867                 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
2868
2869                 g_free(buf2);
2870                 g_free(buf);
2871         } else
2872                 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
2873
2874         if (to_ml && compose->ml_post) return;
2875         if (!to_all || compose->account->protocol == A_NNTP) return;
2876
2877         if (compose->replyto) {
2878                 Xstrdup_a(replyto, compose->replyto, return);
2879                 extract_address(replyto);
2880         }
2881         if (msginfo->from) {
2882                 Xstrdup_a(from, msginfo->from, return);
2883                 extract_address(from);
2884         }
2885
2886         if (replyto && from)
2887                 cc_list = address_list_append_with_comments(cc_list, from);
2888         if (to_all && msginfo->folder && 
2889             msginfo->folder->prefs->enable_default_reply_to)
2890                 cc_list = address_list_append_with_comments(cc_list,
2891                                 msginfo->folder->prefs->default_reply_to);
2892         cc_list = address_list_append_with_comments(cc_list, msginfo->to);
2893         cc_list = address_list_append_with_comments(cc_list, compose->cc);
2894
2895         to_table = g_hash_table_new(g_str_hash, g_str_equal);
2896         if (replyto)
2897                 g_hash_table_insert(to_table, g_utf8_strdown(replyto, -1), GINT_TO_POINTER(1));
2898         if (compose->account) {
2899                 g_hash_table_insert(to_table, g_utf8_strdown(compose->account->address, -1),
2900                                     GINT_TO_POINTER(1));
2901         }
2902         /* remove address on To: and that of current account */
2903         for (cur = cc_list; cur != NULL; ) {
2904                 GSList *next = cur->next;
2905                 gchar *addr;
2906
2907                 addr = g_utf8_strdown(cur->data, -1);
2908                 extract_address(addr);
2909
2910                 if (GPOINTER_TO_INT(g_hash_table_lookup(to_table, addr)) == 1)
2911                         cc_list = g_slist_remove(cc_list, cur->data);
2912                 else
2913                         g_hash_table_insert(to_table, addr, GINT_TO_POINTER(1));
2914
2915                 cur = next;
2916         }
2917         hash_free_strings(to_table);
2918         g_hash_table_destroy(to_table);
2919
2920         if (cc_list) {
2921                 for (cur = cc_list; cur != NULL; cur = cur->next)
2922                         compose_entry_append(compose, (gchar *)cur->data,
2923                                              COMPOSE_CC);
2924                 slist_free_strings(cc_list);
2925                 g_slist_free(cc_list);
2926         }
2927
2928 }
2929
2930 #define SET_ENTRY(entry, str) \
2931 { \
2932         if (str && *str) \
2933                 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
2934 }
2935
2936 #define SET_ADDRESS(type, str) \
2937 { \
2938         if (str && *str) \
2939                 compose_entry_append(compose, str, type); \
2940 }
2941
2942 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
2943 {
2944         g_return_if_fail(msginfo != NULL);
2945
2946         SET_ENTRY(subject_entry, msginfo->subject);
2947         SET_ENTRY(from_name, msginfo->from);
2948         SET_ADDRESS(COMPOSE_TO, msginfo->to);
2949         SET_ADDRESS(COMPOSE_CC, compose->cc);
2950         SET_ADDRESS(COMPOSE_BCC, compose->bcc);
2951         SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
2952         SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
2953         SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
2954
2955         compose_update_priority_menu_item(compose);
2956         compose_update_privacy_system_menu_item(compose, FALSE);
2957         compose_show_first_last_header(compose, TRUE);
2958 }
2959
2960 #undef SET_ENTRY
2961 #undef SET_ADDRESS
2962
2963 static void compose_insert_sig(Compose *compose, gboolean replace)
2964 {
2965         GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2966         GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2967         GtkTextMark *mark;
2968         GtkTextIter iter, iter_end;
2969         gint cur_pos;
2970         gchar *search = NULL;
2971         gboolean prev_autowrap;
2972         gboolean found = FALSE, shift = FALSE;
2973
2974         
2975         g_return_if_fail(compose->account != NULL);
2976
2977         prev_autowrap = compose->autowrap;
2978         compose->autowrap = FALSE;
2979
2980         g_signal_handlers_block_by_func(G_OBJECT(buffer),
2981                                         G_CALLBACK(compose_changed_cb),
2982                                         compose);
2983         
2984         mark = gtk_text_buffer_get_insert(buffer);
2985         gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2986         cur_pos = gtk_text_iter_get_offset (&iter);
2987
2988         gtk_text_buffer_get_end_iter(buffer, &iter);
2989
2990         search = compose->sig_str;
2991 again:
2992         if (replace && search) {
2993                 GtkTextIter first_iter, start_iter, end_iter;
2994
2995                 gtk_text_buffer_get_start_iter(buffer, &first_iter);
2996
2997                 if (compose->sig_str[0] == '\0')
2998                         found = FALSE;
2999                 else
3000                         found = gtk_text_iter_forward_search(&first_iter,
3001                                                              search,
3002                                                              GTK_TEXT_SEARCH_TEXT_ONLY,
3003                                                              &start_iter, &end_iter,
3004                                                              NULL);
3005
3006                 if (found) {
3007                         gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3008                         iter = start_iter;
3009                 }
3010         } 
3011         if (replace && !found && search && strlen(search) > 2
3012         &&  search[0] == '\n' && search[1] == '\n') {
3013                 search ++;
3014                 shift = TRUE;
3015                 goto again;
3016         }
3017
3018         g_free(compose->sig_str);
3019         compose->sig_str = compose_get_signature_str(compose);
3020         if (!compose->sig_str || (replace && !compose->account->auto_sig))
3021                 compose->sig_str = g_strdup("");
3022
3023         cur_pos = gtk_text_iter_get_offset(&iter);
3024         if (shift && found)
3025                 gtk_text_buffer_insert(buffer, &iter, compose->sig_str + 1, -1);
3026         else
3027                 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3028         /* skip \n\n */
3029         gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3030         gtk_text_iter_forward_char(&iter);
3031         gtk_text_iter_forward_char(&iter);
3032         gtk_text_buffer_get_end_iter(buffer, &iter_end);
3033         gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3034
3035         if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3036                 cur_pos = gtk_text_buffer_get_char_count (buffer);
3037
3038         /* put the cursor where it should be 
3039          * either where the quote_fmt says, either before the signature */
3040         if (compose->set_cursor_pos < 0)
3041                 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3042         else
3043                 gtk_text_buffer_get_iter_at_offset(buffer, &iter, 
3044                         compose->set_cursor_pos);
3045                 
3046         gtk_text_buffer_place_cursor(buffer, &iter);
3047         g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3048                                         G_CALLBACK(compose_changed_cb),
3049                                         compose);
3050                 
3051         compose->autowrap = prev_autowrap;
3052         if (compose->autowrap)
3053                 compose_wrap_all(compose);
3054 }
3055
3056 static gchar *compose_get_signature_str(Compose *compose)
3057 {
3058         gchar *sig_body = NULL;
3059         gchar *sig_str = NULL;
3060         gchar *utf8_sig_str = NULL;
3061
3062         g_return_val_if_fail(compose->account != NULL, NULL);
3063
3064         if (!compose->account->sig_path)
3065                 return NULL;
3066
3067         if (compose->account->sig_type == SIG_FILE) {
3068                 if (!is_file_or_fifo_exist(compose->account->sig_path)) {
3069                         g_warning("can't open signature file: %s\n",
3070                                   compose->account->sig_path);
3071                         return NULL;
3072                 }
3073         }
3074
3075         if (compose->account->sig_type == SIG_COMMAND)
3076                 sig_body = get_command_output(compose->account->sig_path);
3077         else {
3078                 gchar *tmp;
3079
3080                 tmp = file_read_to_str(compose->account->sig_path);
3081                 if (!tmp)
3082                         return NULL;
3083                 sig_body = normalize_newlines(tmp);
3084                 g_free(tmp);
3085         }
3086
3087         if (compose->account->sig_sep) {
3088                 sig_str = g_strconcat("\n\n", compose->account->sig_sep, "\n", sig_body,
3089                                       NULL);
3090                 g_free(sig_body);
3091         } else
3092                 sig_str = g_strconcat("\n\n", sig_body, NULL);
3093
3094         if (sig_str) {
3095                 if (g_utf8_validate(sig_str, -1, NULL) == TRUE)
3096                         utf8_sig_str = sig_str;
3097                 else {
3098                         utf8_sig_str = conv_codeset_strdup
3099                                 (sig_str, conv_get_locale_charset_str_no_utf8(),
3100                                  CS_INTERNAL);
3101                         g_free(sig_str);
3102                 }
3103         }
3104
3105         return utf8_sig_str;
3106 }
3107
3108 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3109 {
3110         GtkTextView *text;
3111         GtkTextBuffer *buffer;
3112         GtkTextMark *mark;
3113         GtkTextIter iter;
3114         const gchar *cur_encoding;
3115         gchar buf[BUFFSIZE];
3116         gint len;
3117         FILE *fp;
3118         gboolean prev_autowrap;
3119         gboolean badtxt = FALSE;
3120
3121         g_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3122
3123         if ((fp = g_fopen(file, "rb")) == NULL) {
3124                 FILE_OP_ERROR(file, "fopen");
3125                 return COMPOSE_INSERT_READ_ERROR;
3126         }
3127
3128         prev_autowrap = compose->autowrap;
3129         compose->autowrap = FALSE;
3130
3131         text = GTK_TEXT_VIEW(compose->text);
3132         buffer = gtk_text_view_get_buffer(text);
3133         mark = gtk_text_buffer_get_insert(buffer);
3134         gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3135
3136         g_signal_handlers_block_by_func(G_OBJECT(buffer),
3137                                         G_CALLBACK(text_inserted),
3138                                         compose);
3139
3140         cur_encoding = conv_get_locale_charset_str_no_utf8();
3141
3142         while (fgets(buf, sizeof(buf), fp) != NULL) {
3143                 gchar *str;
3144
3145                 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3146                         str = g_strdup(buf);
3147                 else
3148                         str = conv_codeset_strdup
3149                                 (buf, cur_encoding, CS_INTERNAL);
3150                 if (!str) continue;
3151
3152                 /* strip <CR> if DOS/Windows file,
3153                    replace <CR> with <LF> if Macintosh file. */
3154                 strcrchomp(str);
3155                 len = strlen(str);
3156                 if (len > 0 && str[len - 1] != '\n') {
3157                         while (--len >= 0)
3158                                 if (str[len] == '\r') str[len] = '\n';
3159                 }
3160
3161                 gtk_text_buffer_insert(buffer, &iter, str, -1);
3162                 g_free(str);
3163         }
3164
3165         g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3166                                           G_CALLBACK(text_inserted),
3167                                           compose);
3168         compose->autowrap = prev_autowrap;
3169         if (compose->autowrap)
3170                 compose_wrap_all(compose);
3171
3172         fclose(fp);
3173
3174         if (badtxt)
3175                 return COMPOSE_INSERT_INVALID_CHARACTER;
3176         else 
3177                 return COMPOSE_INSERT_SUCCESS;
3178 }
3179
3180 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3181                                   const gchar *filename,
3182                                   const gchar *content_type)
3183 {
3184         AttachInfo *ainfo;
3185         GtkTreeIter iter;
3186         FILE *fp;
3187         off_t size;
3188         GAuto *auto_ainfo;
3189         gchar *size_text;
3190         GtkListStore *store;
3191         gchar *name;
3192         gboolean has_binary = FALSE;
3193
3194         if (!is_file_exist(file)) {
3195                 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3196                 gboolean result = FALSE;
3197                 if (file_from_uri && is_file_exist(file_from_uri)) {
3198                         result = compose_attach_append(
3199                                                 compose, file_from_uri,
3200                                                 filename,
3201                                                 content_type);
3202                 }
3203                 g_free(file_from_uri);
3204                 if (result)
3205                         return TRUE;
3206                 alertpanel_error("File %s doesn't exist\n", filename);
3207                 return FALSE;
3208         }
3209         if ((size = get_file_size(file)) < 0) {
3210                 alertpanel_error("Can't get file size of %s\n", filename);
3211                 return FALSE;
3212         }
3213         if (size == 0) {
3214                 alertpanel_error(_("File %s is empty."), filename);
3215                 return FALSE;
3216         }
3217         if ((fp = g_fopen(file, "rb")) == NULL) {
3218                 alertpanel_error(_("Can't read %s."), filename);
3219                 return FALSE;
3220         }
3221         fclose(fp);
3222
3223         ainfo = g_new0(AttachInfo, 1);
3224         auto_ainfo = g_auto_pointer_new_with_free
3225                         (ainfo, (GFreeFunc) compose_attach_info_free); 
3226         ainfo->file = g_strdup(file);
3227
3228         if (content_type) {
3229                 ainfo->content_type = g_strdup(content_type);
3230                 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3231                         MsgInfo *msginfo;
3232                         MsgFlags flags = {0, 0};
3233
3234                         if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3235                                 ainfo->encoding = ENC_7BIT;
3236                         else
3237                                 ainfo->encoding = ENC_8BIT;
3238
3239                         msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3240                         if (msginfo && msginfo->subject)
3241                                 name = g_strdup(msginfo->subject);
3242                         else
3243                                 name = g_path_get_basename(filename ? filename : file);
3244
3245                         ainfo->name = g_strdup_printf(_("Message: %s"), name);
3246
3247                         procmsg_msginfo_free(msginfo);
3248                 } else {
3249                         if (!g_ascii_strncasecmp(content_type, "text", 4))
3250                                 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3251                         else
3252                                 ainfo->encoding = ENC_BASE64;
3253                         name = g_path_get_basename(filename ? filename : file);
3254                         ainfo->name = g_strdup(name);
3255                 }
3256                 g_free(name);
3257         } else {
3258                 ainfo->content_type = procmime_get_mime_type(file);
3259                 if (!ainfo->content_type) {
3260                         ainfo->content_type =
3261                                 g_strdup("application/octet-stream");
3262                         ainfo->encoding = ENC_BASE64;
3263                 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text", 4))
3264                         ainfo->encoding =
3265                                 procmime_get_encoding_for_text_file(file, &has_binary);
3266                 else
3267                         ainfo->encoding = ENC_BASE64;
3268                 name = g_path_get_basename(filename ? filename : file);
3269                 ainfo->name = g_strdup(name);   
3270                 g_free(name);
3271         }
3272
3273         if (ainfo->name != NULL
3274         &&  !strcmp(ainfo->name, ".")) {
3275                 g_free(ainfo->name);
3276                 ainfo->name = NULL;
3277         }
3278
3279         if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3280                 g_free(ainfo->content_type);
3281                 ainfo->content_type = g_strdup("application/octet-stream");
3282         }
3283
3284         ainfo->size = size;
3285         size_text = to_human_readable(size);
3286
3287         store = GTK_LIST_STORE(gtk_tree_view_get_model
3288                         (GTK_TREE_VIEW(compose->attach_clist)));
3289                 
3290         gtk_list_store_append(store, &iter);
3291         gtk_list_store_set(store, &iter, 
3292                            COL_MIMETYPE, ainfo->content_type,
3293                            COL_SIZE, size_text,
3294                            COL_NAME, ainfo->name,
3295                            COL_DATA, ainfo,
3296                            COL_AUTODATA, auto_ainfo,
3297                            -1);
3298         
3299         g_auto_pointer_free(auto_ainfo);
3300         return TRUE;
3301 }
3302
3303 static void compose_use_signing(Compose *compose, gboolean use_signing)
3304 {
3305         GtkItemFactory *ifactory;
3306         GtkWidget *menuitem = NULL;
3307
3308         compose->use_signing = use_signing;
3309         ifactory = gtk_item_factory_from_widget(compose->menubar);
3310         menuitem = gtk_item_factory_get_item
3311                 (ifactory, "/Options/Sign");
3312         gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), 
3313                                        use_signing);
3314 }
3315
3316 static void compose_use_encryption(Compose *compose, gboolean use_encryption)
3317 {
3318         GtkItemFactory *ifactory;
3319         GtkWidget *menuitem = NULL;
3320
3321         compose->use_encryption = use_encryption;
3322         ifactory = gtk_item_factory_from_widget(compose->menubar);
3323         menuitem = gtk_item_factory_get_item
3324                 (ifactory, "/Options/Encrypt");
3325
3326         gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), 
3327                                        use_encryption);
3328 }
3329
3330 #define NEXT_PART_NOT_CHILD(info)  \
3331 {  \
3332         node = info->node;  \
3333         while (node->children)  \
3334                 node = g_node_last_child(node);  \
3335         info = procmime_mimeinfo_next((MimeInfo *)node->data);  \
3336 }
3337
3338 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3339 {
3340         MimeInfo *mimeinfo;
3341         MimeInfo *child;
3342         MimeInfo *firsttext = NULL;
3343         MimeInfo *encrypted = NULL;
3344         GNode    *node;
3345         gchar *outfile;
3346         const gchar *partname = NULL;
3347
3348         mimeinfo = procmime_scan_message(msginfo);
3349         if (!mimeinfo) return;
3350
3351         if (mimeinfo->node->children == NULL) {
3352                 procmime_mimeinfo_free_all(mimeinfo);
3353                 return;
3354         }
3355
3356         /* find first content part */
3357         child = (MimeInfo *) mimeinfo->node->children->data;
3358         while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3359                 child = (MimeInfo *)child->node->children->data;
3360
3361         if (child->type == MIMETYPE_TEXT) {
3362                 firsttext = child;
3363                 debug_print("First text part found\n");
3364         } else if (compose->mode == COMPOSE_REEDIT &&
3365                  child->type == MIMETYPE_APPLICATION &&
3366                  !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3367                 encrypted = (MimeInfo *)child->node->parent->data;
3368         }
3369      
3370         child = (MimeInfo *) mimeinfo->node->children->data;
3371         while (child != NULL) {
3372                 gint err;
3373
3374                 if (child == encrypted) {
3375                         /* skip this part of tree */
3376                         NEXT_PART_NOT_CHILD(child);
3377                         continue;
3378                 }
3379
3380                 if (child->type == MIMETYPE_MULTIPART) {
3381                         /* get the actual content */
3382                         child = procmime_mimeinfo_next(child);
3383                         continue;
3384                 }
3385                     
3386                 if (child == firsttext) {
3387                         child = procmime_mimeinfo_next(child);
3388                         continue;
3389                 }
3390
3391                 outfile = procmime_get_tmp_file_name(child);
3392                 if ((err = procmime_get_part(outfile, child)) < 0)
3393                         g_warning("Can't get the part of multipart message. (%s)", strerror(-err));
3394                 else {
3395                         gchar *content_type;
3396
3397                         content_type = procmime_get_content_type_str(child->type, child->subtype);
3398
3399                         /* if we meet a pgp signature, we don't attach it, but
3400                          * we force signing. */
3401                         if ((strcmp(content_type, "application/pgp-signature") &&
3402                             strcmp(content_type, "application/pkcs7-signature") &&
3403                             strcmp(content_type, "application/x-pkcs7-signature"))
3404                             || compose->mode == COMPOSE_REDIRECT) {
3405                                 partname = procmime_mimeinfo_get_parameter(child, "filename");
3406                                 if (partname == NULL)
3407                                         partname = procmime_mimeinfo_get_parameter(child, "name");
3408                                 if (partname == NULL)
3409                                         partname = "";
3410                                 compose_attach_append(compose, outfile, 
3411                                                       partname, content_type);
3412                         } else {
3413                                 compose_force_signing(compose, compose->account);
3414                         }
3415                         g_free(content_type);
3416                 }
3417                 g_free(outfile);
3418                 NEXT_PART_NOT_CHILD(child);
3419         }
3420         procmime_mimeinfo_free_all(mimeinfo);
3421 }
3422
3423 #undef NEXT_PART_NOT_CHILD
3424
3425
3426
3427 typedef enum {
3428         WAIT_FOR_INDENT_CHAR,
3429         WAIT_FOR_INDENT_CHAR_OR_SPACE,
3430 } IndentState;
3431
3432 /* return indent length, we allow:
3433    indent characters followed by indent characters or spaces/tabs,
3434    alphabets and numbers immediately followed by indent characters,
3435    and the repeating sequences of the above
3436    If quote ends with multiple spaces, only the first one is included. */
3437 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3438                                     const GtkTextIter *start, gint *len)
3439 {
3440         GtkTextIter iter = *start;
3441         gunichar wc;
3442         gchar ch[6];
3443         gint clen;
3444         IndentState state = WAIT_FOR_INDENT_CHAR;
3445         gboolean is_space;
3446         gboolean is_indent;
3447         gint alnum_count = 0;
3448         gint space_count = 0;
3449         gint quote_len = 0;
3450
3451         if (prefs_common.quote_chars == NULL) {
3452                 return 0 ;
3453         }
3454
3455         while (!gtk_text_iter_ends_line(&iter)) {
3456                 wc = gtk_text_iter_get_char(&iter);
3457                 if (g_unichar_iswide(wc))
3458                         break;
3459                 clen = g_unichar_to_utf8(wc, ch);
3460                 if (clen != 1)
3461                         break;
3462
3463                 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
3464                 is_space = g_unichar_isspace(wc);
3465
3466                 if (state == WAIT_FOR_INDENT_CHAR) {
3467                         if (!is_indent && !g_unichar_isalnum(wc))
3468                                 break;
3469                         if (is_indent) {
3470                                 quote_len += alnum_count + space_count + 1;
3471                                 alnum_count = space_count = 0;
3472                                 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
3473                         } else
3474                                 alnum_count++;
3475                 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
3476                         if (!is_indent && !is_space && !g_unichar_isalnum(wc))
3477                                 break;
3478                         if (is_space)
3479                                 space_count++;
3480                         else if (is_indent) {
3481                                 quote_len += alnum_count + space_count + 1;
3482                                 alnum_count = space_count = 0;
3483                         } else {
3484                                 alnum_count++;
3485                                 state = WAIT_FOR_INDENT_CHAR;
3486                         }
3487                 }
3488
3489                 gtk_text_iter_forward_char(&iter);
3490         }
3491
3492         if (quote_len > 0 && space_count > 0)
3493                 quote_len++;
3494
3495         if (len)
3496                 *len = quote_len;
3497
3498         if (quote_len > 0) {
3499                 iter = *start;
3500                 gtk_text_iter_forward_chars(&iter, quote_len);
3501                 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
3502         }
3503
3504         return NULL;
3505 }
3506
3507 /* return TRUE if the line is itemized */
3508 static gboolean compose_is_itemized(GtkTextBuffer *buffer,
3509                                     const GtkTextIter *start)
3510 {
3511         GtkTextIter iter = *start;
3512         gunichar wc;
3513         gchar ch[6];
3514         gint clen;
3515
3516         if (gtk_text_iter_ends_line(&iter))
3517                 return FALSE;
3518
3519         while (1) {
3520                 wc = gtk_text_iter_get_char(&iter);
3521                 if (!g_unichar_isspace(wc))
3522                         break;
3523                 gtk_text_iter_forward_char(&iter);
3524                 if (gtk_text_iter_ends_line(&iter))
3525                         return FALSE;
3526         }
3527
3528         clen = g_unichar_to_utf8(wc, ch);
3529         if (clen != 1)
3530                 return FALSE;
3531
3532         if (!strchr("*-+", ch[0]))
3533                 return FALSE;
3534
3535         gtk_text_iter_forward_char(&iter);
3536         if (gtk_text_iter_ends_line(&iter))
3537                 return FALSE;
3538         wc = gtk_text_iter_get_char(&iter);
3539         if (g_unichar_isspace(wc))
3540                 return TRUE;
3541
3542         return FALSE;
3543 }
3544
3545 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
3546                                            const GtkTextIter *start,
3547                                            GtkTextIter *break_pos,
3548                                            gint max_col,
3549                                            gint quote_len)
3550 {
3551         GtkTextIter iter = *start, line_end = *start;
3552         PangoLogAttr *attrs;
3553         gchar *str;
3554         gchar *p;
3555         gint len;
3556         gint i;
3557         gint col = 0;
3558         gint pos = 0;
3559         gboolean can_break = FALSE;
3560         gboolean do_break = FALSE;
3561         gboolean was_white = FALSE;
3562         gboolean prev_dont_break = FALSE;
3563
3564         gtk_text_iter_forward_to_line_end(&line_end);
3565         str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
3566         len = g_utf8_strlen(str, -1);
3567         /* g_print("breaking line: %d: %s (len = %d)\n",
3568                 gtk_text_iter_get_line(&iter), str, len); */
3569         attrs = g_new(PangoLogAttr, len + 1);
3570
3571         pango_default_break(str, -1, NULL, attrs, len + 1);
3572
3573         p = str;
3574
3575         /* skip quote and leading spaces */
3576         for (i = 0; *p != '\0' && i < len; i++) {
3577                 gunichar wc;
3578
3579                 wc = g_utf8_get_char(p);
3580                 if (i >= quote_len && !g_unichar_isspace(wc))
3581                         break;
3582                 if (g_unichar_iswide(wc))
3583                         col += 2;
3584                 else if (*p == '\t')
3585                         col += 8;
3586                 else
3587                         col++;
3588                 p = g_utf8_next_char(p);
3589         }
3590
3591         for (; *p != '\0' && i < len; i++) {
3592                 PangoLogAttr *attr = attrs + i;
3593                 gunichar wc;
3594                 gint uri_len;
3595
3596                 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
3597                         pos = i;
3598                 
3599                 was_white = attr->is_white;
3600
3601                 /* don't wrap URI */
3602                 if ((uri_len = get_uri_len(p)) > 0) {
3603                         col += uri_len;
3604                         if (pos > 0 && col > max_col) {
3605                                 do_break = TRUE;
3606                                 break;
3607                         }
3608                         i += uri_len - 1;
3609                         p += uri_len;
3610                         can_break = TRUE;
3611                         continue;
3612                 }
3613
3614                 wc = g_utf8_get_char(p);
3615                 if (g_unichar_iswide(wc)) {
3616                         col += 2;
3617                         if (prev_dont_break && can_break && attr->is_line_break)
3618                                 pos = i;
3619                 } else if (*p == '\t')
3620                         col += 8;
3621                 else
3622                         col++;
3623                 if (pos > 0 && col > max_col) {
3624                         do_break = TRUE;
3625                         break;
3626                 }
3627
3628                 if (*p == '-' || *p == '/')
3629                         prev_dont_break = TRUE;
3630                 else
3631                         prev_dont_break = FALSE;
3632
3633                 p = g_utf8_next_char(p);
3634                 can_break = TRUE;
3635         }
3636
3637         debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col);
3638
3639         g_free(attrs);
3640         g_free(str);
3641
3642         *break_pos = *start;
3643         gtk_text_iter_set_line_offset(break_pos, pos);
3644
3645         return do_break;
3646 }
3647
3648 static gboolean compose_join_next_line(Compose *compose,
3649                                        GtkTextBuffer *buffer,
3650                                        GtkTextIter *iter,
3651                                        const gchar *quote_str)
3652 {
3653         GtkTextIter iter_ = *iter, cur, prev, next, end;
3654         PangoLogAttr attrs[3];
3655         gchar *str;
3656         gchar *next_quote_str;
3657         gunichar wc1, wc2;
3658         gint quote_len;
3659         gboolean keep_cursor = FALSE;
3660
3661         if (!gtk_text_iter_forward_line(&iter_) ||
3662             gtk_text_iter_ends_line(&iter_))
3663                 return FALSE;
3664
3665         next_quote_str = compose_get_quote_str(buffer, &iter_, &quote_len);
3666
3667         if ((quote_str || next_quote_str) &&
3668             strcmp2(quote_str, next_quote_str) != 0) {
3669                 g_free(next_quote_str);
3670                 return FALSE;
3671         }
3672         g_free(next_quote_str);
3673
3674         end = iter_;
3675         if (quote_len > 0) {
3676                 gtk_text_iter_forward_chars(&end, quote_len);
3677                 if (gtk_text_iter_ends_line(&end))
3678                         return FALSE;
3679         }
3680
3681         /* don't join itemized lines */
3682         if (compose_is_itemized(buffer, &end))
3683                 return FALSE;
3684
3685         /* don't join signature separator */
3686         if (compose_is_sig_separator(compose, buffer, &iter_))
3687                 return FALSE;
3688
3689         /* delete quote str */
3690         if (quote_len > 0)
3691                 gtk_text_buffer_delete(buffer, &iter_, &end);
3692
3693         /* don't join line breaks put by the user */
3694         prev = cur = iter_;
3695         gtk_text_iter_backward_char(&cur);
3696         if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
3697                 gtk_text_iter_forward_char(&cur);
3698                 *iter = cur;
3699                 return FALSE;
3700         }
3701         gtk_text_iter_forward_char(&cur);
3702         /* delete linebreak and extra spaces */
3703         while (gtk_text_iter_backward_char(&cur)) {
3704                 wc1 = gtk_text_iter_get_char(&cur);
3705                 if (!g_unichar_isspace(wc1))
3706                         break;
3707                 prev = cur;
3708         }
3709         next = cur = iter_;
3710         while (!gtk_text_iter_ends_line(&cur)) {
3711                 wc1 = gtk_text_iter_get_char(&cur);
3712                 if (!g_unichar_isspace(wc1))
3713                         break;
3714                 gtk_text_iter_forward_char(&cur);
3715                 next = cur;
3716         }
3717         if (!gtk_text_iter_equal(&prev, &next)) {
3718                 GtkTextMark *mark;
3719
3720                 mark = gtk_text_buffer_get_insert(buffer);
3721                 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
3722                 if (gtk_text_iter_equal(&prev, &cur))
3723                         keep_cursor = TRUE;
3724                 gtk_text_buffer_delete(buffer, &prev, &next);
3725         }
3726         iter_ = prev;
3727
3728         /* insert space if required */
3729         gtk_text_iter_backward_char(&prev);
3730         wc1 = gtk_text_iter_get_char(&prev);
3731         wc2 = gtk_text_iter_get_char(&next);
3732         gtk_text_iter_forward_char(&next);
3733         str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
3734         pango_default_break(str, -1, NULL, attrs, 3);
3735         if (!attrs[1].is_line_break ||
3736             (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
3737                 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
3738                 if (keep_cursor) {
3739                         gtk_text_iter_backward_char(&iter_);
3740                         gtk_text_buffer_place_cursor(buffer, &iter_);
3741                 }
3742         }
3743         g_free(str);
3744
3745         *iter = iter_;
3746         return TRUE;
3747 }
3748
3749 #define ADD_TXT_POS(bp_, ep_, pti_) \
3750         if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
3751                 last = last->next; \
3752                 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
3753                 last->next = NULL; \
3754         } else { \
3755                 g_warning("alloc error scanning URIs\n"); \
3756         }
3757
3758 static gboolean automatic_break = FALSE;
3759 static void compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
3760 {
3761         GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3762         GtkTextBuffer *buffer;
3763         GtkTextIter iter, break_pos, end_of_line;
3764         gchar *quote_str = NULL;
3765         gint quote_len;
3766         gboolean wrap_quote = prefs_common.linewrap_quote;
3767         gboolean prev_autowrap = compose->autowrap;
3768         gint startq_offset = -1, noq_offset = -1;
3769         gint uri_start = -1, uri_stop = -1;
3770         gint nouri_start = -1, nouri_stop = -1;
3771         gint num_blocks = 0;
3772         gint quotelevel = -1;
3773
3774         compose->autowrap = FALSE;
3775
3776         buffer = gtk_text_view_get_buffer(text);
3777         undo_wrapping(compose->undostruct, TRUE);
3778         if (par_iter) {
3779                 iter = *par_iter;
3780         } else {
3781                 GtkTextMark *mark;
3782                 mark = gtk_text_buffer_get_insert(buffer);
3783                 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3784         }
3785
3786         /* move to paragraph start */
3787         gtk_text_iter_set_line_offset(&iter, 0);
3788         if (gtk_text_iter_ends_line(&iter)) {
3789                 while (gtk_text_iter_ends_line(&iter) &&
3790                        gtk_text_iter_forward_line(&iter))
3791                         ;
3792         } else {
3793                 while (gtk_text_iter_backward_line(&iter)) {
3794                         if (gtk_text_iter_ends_line(&iter)) {
3795                                 gtk_text_iter_forward_line(&iter);
3796                                 break;
3797                         }
3798                 }
3799         }
3800
3801         /* go until paragraph end (empty line) */
3802         
3803         while (!gtk_text_iter_ends_line(&iter)) {
3804                 gchar *scanpos = NULL;
3805                 /* parse table - in order of priority */
3806                 struct table {
3807                         const gchar *needle; /* token */
3808
3809                         /* token search function */
3810                         gchar    *(*search)     (const gchar *haystack,
3811                                                  const gchar *needle);
3812                         /* part parsing function */
3813                         gboolean  (*parse)      (const gchar *start,
3814                                                  const gchar *scanpos,
3815                                                  const gchar **bp_,
3816                                                  const gchar **ep_,
3817                                                  gboolean hdr);
3818                         /* part to URI function */
3819                         gchar    *(*build_uri)  (const gchar *bp,
3820                                                  const gchar *ep);
3821                 };
3822
3823                 static struct table parser[] = {
3824                         {"http://",  strcasestr, get_uri_part,   make_uri_string},
3825                         {"https://", strcasestr, get_uri_part,   make_uri_string},
3826                         {"ftp://",   strcasestr, get_uri_part,   make_uri_string},
3827                         {"sftp://",  strcasestr, get_uri_part,   make_uri_string},
3828                         {"www.",     strcasestr, get_uri_part,   make_http_string},
3829                         {"mailto:",  strcasestr, get_uri_part,   make_uri_string},
3830                         {"@",        strcasestr, get_email_part, make_email_string}
3831                 };
3832                 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
3833                 gint last_index = PARSE_ELEMS;
3834                 gint  n;
3835                 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
3836                 gint walk_pos;
3837                 
3838                 if (!prev_autowrap && num_blocks == 0) {
3839                         num_blocks++;
3840                         g_signal_handlers_block_by_func(G_OBJECT(buffer),
3841                                         G_CALLBACK(text_inserted),
3842                                         compose);
3843                 }
3844                 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
3845                         goto colorize;
3846
3847                 uri_start = uri_stop = -1;
3848                 quote_len = 0;
3849                 quote_str = compose_get_quote_str(buffer, &iter, &quote_len);
3850
3851                 if (quote_str) {
3852                         debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str);
3853                         if (startq_offset == -1) 
3854                                 startq_offset = gtk_text_iter_get_offset(&iter);
3855                         quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
3856                         if (quotelevel > 2) {
3857                                 /* recycle colors */
3858                                 if (prefs_common.recycle_quote_colors)
3859                                         quotelevel %= 3;
3860                                 else
3861                                         quotelevel = 2;
3862                         }
3863                         if (!wrap_quote) {
3864                                 goto colorize;
3865                         }
3866                 } else {
3867                         if (startq_offset == -1)
3868                                 noq_offset = gtk_text_iter_get_offset(&iter);
3869                         quotelevel = -1;
3870                 }
3871
3872                 if (prev_autowrap == FALSE && !force && !wrap_quote) {
3873                         goto colorize;
3874                 }
3875                 if (compose_get_line_break_pos(buffer, &iter, &break_pos,
3876                                                prefs_common.linewrap_len,
3877                                                quote_len)) {
3878                         GtkTextIter prev, next, cur;
3879                         
3880                         if (prev_autowrap != FALSE || force) {
3881                                 automatic_break = TRUE;
3882                                 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
3883                                 automatic_break = FALSE;
3884                         } else if (quote_str && wrap_quote) {
3885                                 automatic_break = TRUE;
3886                                 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
3887                                 automatic_break = FALSE;
3888                         } else 
3889                                 goto colorize;
3890                         /* remove trailing spaces */
3891                         cur = break_pos;
3892                         gtk_text_iter_backward_char(&cur);
3893                         prev = next = cur;
3894                         while (!gtk_text_iter_starts_line(&cur)) {
3895                                 gunichar wc;
3896
3897                                 gtk_text_iter_backward_char(&cur);
3898                                 wc = gtk_text_iter_get_char(&cur);
3899                                 if (!g_unichar_isspace(wc))
3900                                         break;
3901                                 prev = cur;
3902                         }
3903                         if (!gtk_text_iter_equal(&prev, &next)) {
3904                                 gtk_text_buffer_delete(buffer, &prev, &next);
3905                                 break_pos = next;
3906                                 gtk_text_iter_forward_char(&break_pos);
3907                         }
3908
3909                         if (quote_str)
3910                                 gtk_text_buffer_insert(buffer, &break_pos,
3911                                                        quote_str, -1);
3912
3913                         iter = break_pos;
3914                         compose_join_next_line(compose, buffer, &iter, quote_str);
3915
3916                         /* move iter to current line start */
3917                         gtk_text_iter_set_line_offset(&iter, 0);
3918                         if (quote_str) {
3919                                 g_free(quote_str);
3920                                 quote_str = NULL;
3921                         }
3922                         continue;
3923                 } else {
3924                         /* move iter to next line start */
3925                         iter = break_pos;
3926                 }
3927
3928 colorize:
3929                 if (!prev_autowrap && num_blocks > 0) {
3930                         num_blocks--;
3931                         g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3932                                         G_CALLBACK(text_inserted),
3933                                         compose);
3934                 }
3935                 end_of_line = iter;
3936                 while (!gtk_text_iter_ends_line(&end_of_line)) {
3937                         gtk_text_iter_forward_char(&end_of_line);
3938                 }
3939                 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
3940
3941                 nouri_start = gtk_text_iter_get_offset(&iter);
3942                 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
3943
3944                 walk_pos = gtk_text_iter_get_offset(&iter);
3945                 /* FIXME: this looks phony. scanning for anything in the parse table */
3946                 for (n = 0; n < PARSE_ELEMS; n++) {
3947                         gchar *tmp;
3948
3949                         tmp = parser[n].search(walk, parser[n].needle);
3950                         if (tmp) {
3951                                 if (scanpos == NULL || tmp < scanpos) {
3952                                         scanpos = tmp;
3953                                         last_index = n;
3954                                 }
3955                         }                                       
3956                 }
3957
3958                 bp = ep = 0;
3959                 if (scanpos) {
3960                         /* check if URI can be parsed */
3961                         if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
3962                                         (const gchar **)&ep, FALSE)
3963                             && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
3964                                         walk = ep;
3965                         } else
3966                                 walk = scanpos +
3967                                         strlen(parser[last_index].needle);
3968                 } 
3969                 if (bp && ep) {
3970                         uri_start = walk_pos + (bp - o_walk);
3971                         uri_stop  = walk_pos + (ep - o_walk);
3972                 }
3973                 g_free(o_walk);
3974                 o_walk = NULL;
3975                 gtk_text_iter_forward_line(&iter);
3976                 g_free(quote_str);
3977                 quote_str = NULL;
3978                 if (startq_offset != -1) {
3979                         GtkTextIter startquote, endquote;
3980                         gtk_text_buffer_get_iter_at_offset(
3981                                 buffer, &startquote, startq_offset);
3982                         endquote = iter;
3983
3984                         switch (quotelevel) {
3985                         case 0: gtk_text_buffer_apply_tag_by_name(
3986                                         buffer, "quote0", &startquote, &endquote);
3987                                 break;
3988                         case 1: gtk_text_buffer_apply_tag_by_name(
3989                                         buffer, "quote1", &startquote, &endquote);
3990                                 break;
3991                         case 2: gtk_text_buffer_apply_tag_by_name(
3992                                         buffer, "quote2", &startquote, &endquote);
3993                                 break;
3994                         }
3995                         startq_offset = -1;
3996                 } else if (noq_offset != -1) {
3997                         GtkTextIter startnoquote, endnoquote;
3998                         gtk_text_buffer_get_iter_at_offset(
3999                                 buffer, &startnoquote, noq_offset);
4000                         endnoquote = iter;
4001                         gtk_text_buffer_remove_tag_by_name(
4002                                 buffer, "quote0", &startnoquote, &endnoquote);
4003                         gtk_text_buffer_remove_tag_by_name(
4004                                 buffer, "quote1", &startnoquote, &endnoquote);
4005                         gtk_text_buffer_remove_tag_by_name(
4006                                 buffer, "quote2", &startnoquote, &endnoquote);
4007                         noq_offset = -1;
4008                 }
4009                 
4010                 /* always */ {
4011                         GtkTextIter nouri_start_iter, nouri_end_iter;
4012                         gtk_text_buffer_get_iter_at_offset(
4013                                 buffer, &nouri_start_iter, nouri_start);
4014                         gtk_text_buffer_get_iter_at_offset(
4015                                 buffer, &nouri_end_iter, nouri_stop);
4016                         gtk_text_buffer_remove_tag_by_name(
4017                                 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4018                 }
4019                 if (uri_start > 0 && uri_stop > 0) {
4020                         GtkTextIter uri_start_iter, uri_end_iter;
4021                         gtk_text_buffer_get_iter_at_offset(
4022                                 buffer, &uri_start_iter, uri_start);
4023                         gtk_text_buffer_get_iter_at_offset(
4024                                 buffer, &uri_end_iter, uri_stop);
4025                         gtk_text_buffer_apply_tag_by_name(
4026                                 buffer, "link", &uri_start_iter, &uri_end_iter);
4027                 }
4028         }
4029
4030         if (par_iter)
4031                 *par_iter = iter;
4032         undo_wrapping(compose->undostruct, FALSE);
4033         compose->autowrap = prev_autowrap;
4034 }
4035
4036 void compose_action_cb(void *data)
4037 {
4038         Compose *compose = (Compose *)data;
4039         compose_wrap_all(compose);
4040 }
4041
4042 static void compose_wrap_all(Compose *compose)
4043 {
4044         compose_wrap_all_full(compose, FALSE);
4045 }
4046
4047 static void compose_wrap_all_full(Compose *compose, gboolean force)
4048 {
4049         GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4050         GtkTextBuffer *buffer;
4051         GtkTextIter iter;
4052
4053         buffer = gtk_text_view_get_buffer(text);
4054
4055         gtk_text_buffer_get_start_iter(buffer, &iter);
4056         while (!gtk_text_iter_is_end(&iter))
4057                 compose_beautify_paragraph(compose, &iter, force);
4058
4059 }
4060
4061 static void compose_set_title(Compose *compose)
4062 {
4063         gchar *str;
4064         gchar *edited;
4065         gchar *subject;
4066         
4067         edited = compose->modified ? _(" [Edited]") : "";
4068         
4069         subject = gtk_editable_get_chars(
4070                         GTK_EDITABLE(compose->subject_entry), 0, -1);
4071
4072 #ifndef MAEMO
4073         if (subject && strlen(subject))
4074                 str = g_strdup_printf(_("%s - Compose message%s"),
4075                                       subject, edited); 
4076         else
4077                 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4078 #else
4079         str = g_strdup(_("Compose message"));
4080 #endif
4081
4082         gtk_window_set_title(GTK_WINDOW(compose->window), str);
4083         g_free(str);
4084         g_free(subject);
4085 }
4086
4087 /**
4088  * compose_current_mail_account:
4089  * 
4090  * Find a current mail account (the currently selected account, or the
4091  * default account, if a news account is currently selected).  If a
4092  * mail account cannot be found, display an error message.
4093  * 
4094  * Return value: Mail account, or NULL if not found.
4095  **/
4096 static PrefsAccount *
4097 compose_current_mail_account(void)
4098 {
4099         PrefsAccount *ac;
4100
4101         if (cur_account && cur_account->protocol != A_NNTP)
4102                 ac = cur_account;
4103         else {
4104                 ac = account_get_default();
4105                 if (!ac || ac->protocol == A_NNTP) {
4106                         alertpanel_error(_("Account for sending mail is not specified.\n"
4107                                            "Please select a mail account before sending."));
4108                         return NULL;
4109                 }
4110         }
4111         return ac;
4112 }
4113
4114 #define QUOTE_IF_REQUIRED(out, str)                                     \
4115 {                                                                       \
4116         if (*str != '"' && strpbrk(str, ",.[]<>")) {                    \
4117                 gchar *__tmp;                                           \
4118                 gint len;                                               \
4119                                                                         \
4120                 len = strlen(str) + 3;                                  \
4121                 if ((__tmp = alloca(len)) == NULL) {                    \
4122                         g_warning("can't allocate memory\n");           \
4123                         g_string_free(header, TRUE);                    \
4124                         return NULL;                                    \
4125                 }                                                       \
4126                 g_snprintf(__tmp, len, "\"%s\"", str);                  \
4127                 out = __tmp;                                            \
4128         } else {                                                        \
4129                 gchar *__tmp;                                           \
4130                                                                         \
4131                 if ((__tmp = alloca(strlen(str) + 1)) == NULL) {        \
4132                         g_warning("can't allocate memory\n");           \
4133                         g_string_free(header, TRUE);                    \
4134                         return NULL;                                    \
4135                 } else                                                  \
4136                         strcpy(__tmp, str);                             \
4137                                                                         \
4138                 out = __tmp;                                            \
4139         }                                                               \
4140 }
4141
4142 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret)                      \
4143 {                                                                       \
4144         if (*str != '"' && strpbrk(str, ",.[]<>")) {                    \
4145                 gchar *__tmp;                                           \
4146                 gint len;                                               \
4147                                                                         \
4148                 len = strlen(str) + 3;                                  \
4149                 if ((__tmp = alloca(len)) == NULL) {                    \
4150                         g_warning("can't allocate memory\n");           \
4151                         errret;                                         \
4152                 }                                                       \
4153                 g_snprintf(__tmp, len, "\"%s\"", str);                  \
4154                 out = __tmp;                                            \
4155         } else {                                                        \
4156                 gchar *__tmp;                                           \
4157                                                                         \
4158                 if ((__tmp = alloca(strlen(str) + 1)) == NULL) {        \
4159                         g_warning("can't allocate memory\n");           \
4160                         errret;                                         \
4161                 } else                                                  \
4162                         strcpy(__tmp, str);                             \
4163                                                                         \
4164                 out = __tmp;                                            \
4165         }                                                               \
4166 }
4167
4168 static void compose_select_account(Compose *compose, PrefsAccount *account,
4169                                    gboolean init)
4170 {
4171         GtkItemFactory *ifactory;
4172         gchar *from = NULL;
4173
4174         g_return_if_fail(account != NULL);
4175
4176         compose->account = account;
4177
4178         if (account->name && *account->name) {
4179                 gchar *buf;
4180                 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4181                 from = g_strdup_printf("%s <%s>",
4182                                        buf, account->address);
4183                 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4184         } else {
4185                 from = g_strdup_printf("<%s>",
4186                                        account->address);
4187                 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4188         }
4189
4190         g_free(from);
4191
4192         compose_set_title(compose);
4193
4194         ifactory = gtk_item_factory_from_widget(compose->menubar);
4195
4196         if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4197                 menu_set_active(ifactory, "/Options/Sign", TRUE);
4198         else
4199                 menu_set_active(ifactory, "/Options/Sign", FALSE);
4200         if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4201                 menu_set_active(ifactory, "/Options/Encrypt", TRUE);
4202         else
4203                 menu_set_active(ifactory, "/Options/Encrypt", FALSE);
4204                                        
4205         activate_privacy_system(compose, account, FALSE);
4206
4207         if (!init && compose->mode != COMPOSE_REDIRECT) {
4208                 undo_block(compose->undostruct);
4209                 compose_insert_sig(compose, TRUE);
4210                 undo_unblock(compose->undostruct);
4211         }
4212
4213 #ifdef USE_ASPELL
4214         /* use account's dict info if set */
4215         if (compose->gtkaspell) {
4216                 if (account->enable_default_dictionary)
4217                         gtkaspell_change_dict(compose->gtkaspell,
4218                                         account->default_dictionary, FALSE);
4219                 if (account->enable_default_alt_dictionary)
4220                         gtkaspell_change_alt_dict(compose->gtkaspell,
4221                                         account->default_alt_dictionary);
4222                 if (account->enable_default_dictionary
4223                         || account->enable_default_alt_dictionary)
4224                         compose_spell_menu_changed(compose);
4225         }
4226 #endif
4227 }
4228
4229 gboolean compose_check_for_valid_recipient(Compose *compose) {
4230         gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
4231         gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
4232         gboolean recipient_found = FALSE;
4233         GSList *list;
4234         gchar **strptr;
4235
4236         /* free to and newsgroup list */
4237         slist_free_strings(compose->to_list);
4238         g_slist_free(compose->to_list);
4239         compose->to_list = NULL;
4240                         
4241         slist_free_strings(compose->newsgroup_list);
4242         g_slist_free(compose->newsgroup_list);
4243         compose->newsgroup_list = NULL;
4244
4245         /* search header entries for to and newsgroup entries */
4246         for (list = compose->header_list; list; list = list->next) {
4247                 gchar *header;
4248                 gchar *entry;
4249                 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_COMBO(((ComposeHeaderEntry *)list->data)->combo)->entry), 0, -1);
4250                 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4251
4252                 g_strstrip(entry);
4253                 if (entry[0] != '\0') {
4254                         for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
4255                                 if (!strcmp(header, prefs_common_translated_header_name(*strptr))) {
4256                                         compose->to_list = address_list_append(compose->to_list, entry);
4257                                         recipient_found = TRUE;
4258                                 }
4259                         }
4260                         for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
4261                                 if (!strcmp(header, prefs_common_translated_header_name(*strptr))) {
4262                                         compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
4263                                         recipient_found = TRUE;
4264                                 }
4265                         }
4266                 }
4267                 g_free(header);
4268                 g_free(entry);
4269         }
4270         return recipient_found;
4271 }
4272
4273 static gboolean compose_check_for_set_recipients(Compose *compose)
4274 {
4275         if (compose->account->set_autocc && compose->account->auto_cc) {
4276                 gboolean found_other = FALSE;
4277                 GSList *list;
4278                 /* search header entries for to and newsgroup entries */
4279                 for (list = compose->header_list; list; list = list->next) {
4280                         gchar *entry;
4281                         gchar *header;
4282                         entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4283                         header = gtk_editable_get_chars(GTK_EDITABLE(GTK_COMBO(((ComposeHeaderEntry *)list->data)->combo)->entry), 0, -1);
4284                         g_strstrip(entry);
4285                         if (strcmp(entry, compose->account->auto_cc)
4286                         ||  strcmp(header, prefs_common_translated_header_name("Cc:"))) {
4287                                 found_other = TRUE;
4288                                 g_free(entry);
4289                                 break;
4290                         }
4291                         g_free(entry);
4292                         g_free(header);
4293                 }
4294                 if (!found_other) {
4295                         AlertValue aval;
4296                         if (compose->batch) {
4297                                 gtk_widget_show_all(compose->window);
4298                         }
4299                         aval = alertpanel(_("Send"),
4300                                           _("The only recipient is the default CC address. Send anyway?"),
4301                                           GTK_STOCK_CANCEL, _("+_Send"), NULL);
4302                         if (aval != G_ALERTALTERNATE)
4303                                 return FALSE;
4304                 }
4305         }
4306         if (compose->account->set_autobcc && compose->account->auto_bcc) {
4307                 gboolean found_other = FALSE;
4308                 GSList *list;
4309                 /* search header entries for to and newsgroup entries */
4310                 for (list = compose->header_list; list; list = list->next) {
4311                         gchar *entry;
4312                         gchar *header;
4313                         entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4314                         header = gtk_editable_get_chars(GTK_EDITABLE(GTK_COMBO(((ComposeHeaderEntry *)list->data)->combo)->entry), 0, -1);
4315                         g_strstrip(entry);
4316                         if (strcmp(entry, compose->account->auto_bcc)
4317                         ||  strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
4318                                 found_other = TRUE;
4319                                 g_free(entry);
4320                                 break;
4321                         }
4322                         g_free(entry);
4323                         g_free(header);
4324                 }
4325                 if (!found_other) {
4326                         AlertValue aval;
4327                         if (compose->batch) {
4328                                 gtk_widget_show_all(compose->window);
4329                         }
4330                         aval = alertpanel(_("Send"),
4331                                           _("The only recipient is the default BCC address. Send anyway?"),
4332                                           GTK_STOCK_CANCEL, _("+_Send"), NULL);
4333                         if (aval != G_ALERTALTERNATE)
4334                                 return FALSE;
4335                 }
4336         }
4337         return TRUE;
4338 }
4339
4340 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
4341 {
4342         const gchar *str;
4343
4344         if (compose_check_for_valid_recipient(compose) == FALSE) {
4345                 if (compose->batch) {
4346                         gtk_widget_show_all(compose->window);
4347                 }
4348                 alertpanel_error(_("Recipient is not specified."));
4349                 return FALSE;
4350         }
4351
4352         if (compose_check_for_set_recipients(compose) == FALSE) {
4353                 return FALSE;
4354         }
4355
4356         if (!compose->batch) {
4357                 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
4358                 if (*str == '\0' && check_everything == TRUE && 
4359                     compose->mode != COMPOSE_REDIRECT) {
4360                         AlertValue aval;
4361
4362                         aval = alertpanel(_("Send"),
4363                                           _("Subject is empty. Send it anyway?"),
4364                                           GTK_STOCK_CANCEL, _("+_Send"), NULL);
4365                         if (aval != G_ALERTALTERNATE)
4366                                 return FALSE;
4367                 }
4368         }
4369
4370         if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
4371                 return FALSE;
4372
4373         return TRUE;
4374 }
4375
4376 gint compose_send(Compose *compose)
4377 {
4378         gint msgnum;
4379         FolderItem *folder = NULL;
4380         gint val = -1;
4381         gchar *msgpath = NULL;
4382         gboolean discard_window = FALSE;
4383         gchar *errstr = NULL;
4384         gchar *tmsgid = NULL;
4385         MainWindow *mainwin = mainwindow_get_mainwindow();
4386         gboolean queued_removed = FALSE;
4387
4388         if (prefs_common.send_dialog_mode != SEND_DIALOG_ALWAYS
4389                         || compose->batch == TRUE)
4390                 discard_window = TRUE;
4391
4392         compose_allow_user_actions (compose, FALSE);
4393         compose->sending = TRUE;
4394
4395         if (compose_check_entries(compose, TRUE) == FALSE) {
4396                 if (compose->batch) {
4397                         gtk_widget_show_all(compose->window);
4398                 }
4399                 goto bail;
4400         }
4401
4402         inc_lock();
4403         val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
4404
4405         if (val) {
4406                 if (compose->batch) {
4407                         gtk_widget_show_all(compose->window);
4408                 }
4409                 if (val == -4) {
4410                         alertpanel_error(_("Could not queue message for sending:\n\n"
4411                                            "Charset conversion failed."));
4412                 } else if (val == -5) {
4413                         alertpanel_error(_("Could not queue message for sending:\n\n"
4414                                            "Couldn't get recipient encryption key."));
4415                 } else if (val == -6) {
4416                         /* silent error */
4417                 } else if (val == -3) {
4418                         if (privacy_peek_error())
4419                         alertpanel_error(_("Could not queue message for sending:\n\n"
4420                                            "Signature failed: %s"), privacy_get_error());
4421                 } else if (val == -2 && errno != 0) {
4422                         alertpanel_error(_("Could not queue message for sending:\n\n%s."), strerror(errno));
4423                 } else {
4424                         alertpanel_error(_("Could not queue message for sending."));
4425                 }
4426                 goto bail;
4427         }
4428
4429         tmsgid = g_strdup(compose->msgid);
4430         if (discard_window) {
4431                 compose->sending = FALSE;
4432                 compose_close(compose);
4433                 /* No more compose access in the normal codepath 
4434                  * after this point! */
4435                 compose = NULL;
4436         }
4437
4438         if (msgnum == 0) {
4439                 alertpanel_error(_("The message was queued but could not be "
4440                                    "sent.\nUse \"Send queued messages\" from "
4441                                    "the main window to retry."));
4442                 if (!discard_window) {
4443                         goto bail;
4444                 }
4445                 inc_unlock();
4446                 g_free(tmsgid);
4447                 return -1;
4448         }
4449         if (msgpath == NULL) {
4450                 msgpath = folder_item_fetch_msg(folder, msgnum);
4451                 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
4452                 g_free(msgpath);
4453         } else {
4454                 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
4455                 g_unlink(msgpath);
4456                 g_free(msgpath);
4457         }
4458         if (!discard_window) {
4459                 if (val != 0) {
4460                         if (!queued_removed)
4461                                 folder_item_remove_msg(folder, msgnum);
4462                         folder_item_scan(folder);
4463                         if (tmsgid) {
4464                                 /* make sure we delete that */
4465                                 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
4466                                 if (tmp) {
4467                                         debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
4468                                         folder_item_remove_msg(folder, tmp->msgnum);
4469                                         procmsg_msginfo_free(tmp);
4470                                 } 
4471                         }
4472                 }
4473         }
4474
4475         if (val == 0) {
4476                 if (!queued_removed)
4477                         folder_item_remove_msg(folder, msgnum);
4478                 folder_item_scan(folder);
4479                 if (tmsgid) {
4480                         /* make sure we delete that */
4481                         MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
4482                         if (tmp) {
4483                                 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
4484                                 folder_item_remove_msg(folder, tmp->msgnum);
4485                                 procmsg_msginfo_free(tmp);
4486                         }
4487                 }
4488                 if (!discard_window) {
4489                         compose->sending = FALSE;
4490                         compose_allow_user_actions (compose, TRUE);
4491                         compose_close(compose);
4492                 }
4493         } else {
4494                 if (errstr) {
4495                         gchar *tmp = g_strdup_printf(_("%s\nUse \"Send queued messages\" from "
4496                                    "the main window to retry."), errstr);
4497                         g_free(errstr);
4498                         alertpanel_error_log(tmp);
4499                         g_free(tmp);
4500                 } else {
4501                         alertpanel_error_log(_("The message was queued but could not be "
4502                                    "sent.\nUse \"Send queued messages\" from "
4503                                    "the main window to retry."));
4504                 }
4505                 if (!discard_window) {
4506                         goto bail;              
4507                 }
4508                 inc_unlock();
4509                 g_free(tmsgid);
4510                 return -1;
4511         }
4512         g_free(tmsgid);
4513         inc_unlock();
4514         toolbar_main_set_sensitive(mainwin);
4515         main_window_set_menu_sensitive(mainwin);
4516         return 0;
4517
4518 bail:
4519         inc_unlock();
4520         g_free(tmsgid);
4521         compose_allow_user_actions (compose, TRUE);
4522         compose->sending = FALSE;
4523         compose->modified = TRUE; 
4524         toolbar_main_set_sensitive(mainwin);
4525         main_window_set_menu_sensitive(mainwin);
4526
4527         return -1;
4528 }
4529
4530 static gboolean compose_use_attach(Compose *compose) 
4531 {
4532         GtkTreeModel *model = gtk_tree_view_get_model
4533                                 (GTK_TREE_VIEW(compose->attach_clist));
4534         return gtk_tree_model_iter_n_children(model, NULL) > 0;
4535 }
4536
4537 static gint compose_redirect_write_headers_from_headerlist(Compose *compose, 
4538                                                            FILE *fp)
4539 {
4540         gchar buf[BUFFSIZE];
4541         gchar *str;
4542         gboolean first_to_address;
4543         gboolean first_cc_address;
4544         GSList *list;
4545         ComposeHeaderEntry *headerentry;
4546         const gchar *headerentryname;
4547         const gchar *cc_hdr;
4548         const gchar *to_hdr;
4549
4550         debug_print("Writing redirect header\n");
4551
4552         cc_hdr = prefs_common_translated_header_name("Cc:");
4553         to_hdr = prefs_common_translated_header_name("To:");
4554
4555         first_to_address = TRUE;
4556         for (list = compose->header_list; list; list = list->next) {
4557                 headerentry = ((ComposeHeaderEntry *)list->data);
4558                 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(headerentry->combo)->entry));
4559
4560                 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
4561                         const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
4562                         Xstrdup_a(str, entstr, return -1);
4563                         g_strstrip(str);
4564                         if (str[0] != '\0') {
4565                                 compose_convert_header
4566                                         (compose, buf, sizeof(buf), str,
4567                                         strlen("Resent-To") + 2, TRUE);
4568
4569                                 if (first_to_address) {
4570                                         fprintf(fp, "Resent-To: ");
4571                                         first_to_address = FALSE;
4572                                 } else {
4573                                         fprintf(fp, ",");
4574                                 }
4575                                 fprintf(fp, "%s", buf);
4576                         }
4577                 }
4578         }
4579         if (!first_to_address) {
4580                 fprintf(fp, "\n");
4581         }
4582
4583         first_cc_address = TRUE;
4584         for (list = compose->header_list; list; list = list->next) {
4585                 headerentry = ((ComposeHeaderEntry *)list->data);
4586                 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(headerentry->combo)->entry));
4587
4588                 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
4589                         const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
4590                         Xstrdup_a(str, strg, return -1);
4591                         g_strstrip(str);
4592                         if (str[0] != '\0') {
4593                                 compose_convert_header
4594                                         (compose, buf, sizeof(buf), str,
4595                                         strlen("Resent-Cc") + 2, TRUE);
4596
4597                                 if (first_cc_address) {
4598                                         fprintf(fp, "Resent-Cc: ");
4599                                         first_cc_address = FALSE;
4600                                 } else {
4601                                         fprintf(fp, ",");
4602                                 }
4603                                 fprintf(fp, "%s", buf);
4604                         }
4605                 }
4606         }
4607         if (!first_cc_address) {
4608                 fprintf(fp, "\n");
4609         }
4610         
4611         return(0);
4612 }
4613
4614 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
4615 {
4616         gchar buf[BUFFSIZE];
4617         gchar *str;
4618         const gchar *entstr;
4619         /* struct utsname utsbuf; */
4620
4621         g_return_val_if_fail(fp != NULL, -1);
4622         g_return_val_if_fail(compose->account != NULL, -1);
4623         g_return_val_if_fail(compose->account->address != NULL, -1);
4624
4625         /* Resent-Date */
4626         get_rfc822_date(buf, sizeof(buf));
4627         fprintf(fp, "Resent-Date: %s\n", buf);
4628
4629         /* Resent-From */
4630         if (compose->account->name && *compose->account->name) {
4631                 compose_convert_header
4632                         (compose, buf, sizeof(buf), compose->account->name,
4633                          strlen("From: "), TRUE);
4634                 fprintf(fp, "Resent-From: %s <%s>\n",
4635                         buf, compose->account->address);
4636         } else
4637                 fprintf(fp, "Resent-From: %s\n", compose->account->address);
4638
4639         /* Subject */
4640         entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
4641         if (*entstr != '\0') {
4642                 Xstrdup_a(str, entstr, return -1);
4643                 g_strstrip(str);
4644                 if (*str != '\0') {
4645                         compose_convert_header(compose, buf, sizeof(buf), str,
4646                                                strlen("Subject: "), FALSE);
4647                         fprintf(fp, "Subject: %s\n", buf);
4648                 }
4649         }
4650
4651         /* Resent-Message-ID */
4652         if (compose->account->gen_msgid) {
4653                 generate_msgid(buf, sizeof(buf));
4654                 fprintf(fp, "Resent-Message-ID: <%s>\n", buf);
4655                 compose->msgid = g_strdup(buf);
4656         }
4657
4658         compose_redirect_write_headers_from_headerlist(compose, fp);
4659
4660         /* separator between header and body */
4661         fputs("\n", fp);
4662
4663         return 0;
4664 }
4665
4666 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
4667 {
4668         FILE *fp;
4669         size_t len;
4670         gchar buf[BUFFSIZE];
4671         int i = 0;
4672         gboolean skip = FALSE;
4673         gchar *not_included[]={
4674                 "Return-Path:",         "Delivered-To:",        "Received:",
4675                 "Subject:",             "X-UIDL:",              "AF:",
4676                 "NF:",                  "PS:",                  "SRH:",
4677                 "SFN:",                 "DSR:",                 "MID:",
4678                 "CFG:",                 "PT:",                  "S:",
4679                 "RQ:",                  "SSV:",                 "NSV:",
4680                 "SSH:",                 "R:",                   "MAID:",
4681                 "NAID:",                "RMID:",                "FMID:",
4682                 "SCF:",                 "RRCPT:",               "NG:",
4683                 "X-Claws-Privacy",      "X-Claws-Sign:",        "X-Claws-Encrypt",
4684                 "X-Claws-End-Special-Headers:",                 "X-Claws-Account-Id:",
4685                 "X-Sylpheed-Privacy",   "X-Sylpheed-Sign:",     "X-Sylpheed-Encrypt",
4686                 "X-Sylpheed-End-Special-Headers:",              "X-Sylpheed-Account-Id:",
4687                 NULL
4688                 };
4689         if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
4690                 FILE_OP_ERROR(compose->redirect_filename, "fopen");
4691                 return -1;
4692         }
4693
4694         while (procheader_get_one_field_asis(buf, sizeof(buf), fp) != -1) {
4695                 skip = FALSE;
4696                 for (i = 0; not_included[i] != NULL; i++) {
4697                         if (g_ascii_strncasecmp(buf, not_included[i],
4698                                                 strlen(not_included[i])) == 0) {
4699                                 skip = TRUE;
4700                                 break;
4701                         }
4702                 }
4703                 if (skip)
4704                         continue;
4705                 if (fputs(buf, fdest) == -1)
4706                         goto error;
4707
4708                 if (!prefs_common.redirect_keep_from) {
4709                         if (g_ascii_strncasecmp(buf, "From:",
4710                                           strlen("From:")) == 0) {
4711                                 fputs(" (by way of ", fdest);
4712                                 if (compose->account->name
4713                                     && *compose->account->name) {
4714                                         compose_convert_header
4715                                                 (compose, buf, sizeof(buf),
4716                                                  compose->account->name,
4717                                                  strlen("From: "),
4718                                                  FALSE);
4719                                         fprintf(fdest, "%s <%s>",
4720                                                 buf,
4721                                                 compose->account->address);
4722                                 } else
4723                                         fprintf(fdest, "%s",
4724                                                 compose->account->address);
4725                                 fputs(")", fdest);
4726                         }
4727                 }
4728
4729                 if (fputs("\n", fdest) == -1)
4730                         goto error;
4731         }
4732
4733         compose_redirect_write_headers(compose, fdest);
4734
4735         while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
4736                 if (fwrite(buf, sizeof(gchar), len, fdest) != len)
4737                         goto error;
4738         }
4739
4740         fclose(fp);
4741
4742         return 0;
4743 error:
4744         fclose(fp);
4745
4746         return -1;
4747 }
4748
4749 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
4750 {
4751         GtkTextBuffer *buffer;
4752         GtkTextIter start, end;
4753         gchar *chars;
4754         gchar *buf;
4755         const gchar *out_codeset;
4756         EncodingType encoding;
4757         MimeInfo *mimemsg, *mimetext;
4758         gint line;
4759
4760         if (action == COMPOSE_WRITE_FOR_SEND)
4761                 attach_parts = TRUE;
4762
4763         /* create message MimeInfo */
4764         mimemsg = procmime_mimeinfo_new();
4765         mimemsg->type = MIMETYPE_MESSAGE;
4766         mimemsg->subtype = g_strdup("rfc822");
4767         mimemsg->content = MIMECONTENT_MEM;
4768         mimemsg->tmp = TRUE; /* must free content later */
4769         mimemsg->data.mem = compose_get_header(compose);
4770
4771         /* Create text part MimeInfo */
4772         /* get all composed text */
4773         buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
4774         gtk_text_buffer_get_start_iter(buffer, &start);
4775         gtk_text_buffer_get_end_iter(buffer, &end);
4776         chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
4777         if (is_ascii_str(chars)) {
4778                 buf = chars;
4779                 chars = NULL;
4780                 out_codeset = CS_US_ASCII;
4781                 encoding = ENC_7BIT;
4782         } else {
4783                 const gchar *src_codeset = CS_INTERNAL;
4784
4785                 out_codeset = conv_get_charset_str(compose->out_encoding);
4786
4787                 if (!out_codeset) {
4788                         gchar *test_conv_global_out = NULL;
4789                         gchar *test_conv_reply = NULL;
4790
4791                         /* automatic mode. be automatic. */
4792                         codeconv_set_strict(TRUE);
4793                         
4794                         out_codeset = conv_get_outgoing_charset_str();
4795                         if (out_codeset) {
4796                                 debug_print("trying to convert to %s\n", out_codeset);
4797                                 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
4798                         }
4799                         
4800                         if (!test_conv_global_out && compose->orig_charset
4801                         &&  strcmp(compose->orig_charset, CS_US_ASCII)) {
4802                                 out_codeset = compose->orig_charset;
4803                                 debug_print("failure; trying to convert to %s\n", out_codeset);
4804                                 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
4805                         }
4806                         
4807                         if (!test_conv_global_out && !test_conv_reply) {
4808                                 /* we're lost */
4809                                 out_codeset = CS_INTERNAL;
4810                                 debug_print("failure; finally using %s\n", out_codeset);
4811                         }
4812                         g_free(test_conv_global_out);
4813                         g_free(test_conv_reply);
4814                         codeconv_set_strict(FALSE);
4815                 }
4816
4817                 if (!g_ascii_strcasecmp(out_codeset, CS_US_ASCII))
4818                         out_codeset = CS_ISO_8859_1;
4819
4820                 if (prefs_common.encoding_method == CTE_BASE64)
4821                         encoding = ENC_BASE64;
4822                 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
4823                         encoding = ENC_QUOTED_PRINTABLE;
4824                 else if (prefs_common.encoding_method == CTE_8BIT)
4825                         encoding = ENC_8BIT;
4826                 else
4827                         encoding = procmime_get_encoding_for_charset(out_codeset);
4828
4829                 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
4830                             src_codeset, out_codeset, procmime_get_encoding_str(encoding));
4831
4832                 if (action == COMPOSE_WRITE_FOR_SEND) {
4833                         codeconv_set_strict(TRUE);
4834                         buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
4835                         codeconv_set_strict(FALSE);
4836
4837                         if (!buf) {
4838                                 AlertValue aval;
4839                                 gchar *msg;
4840
4841                                 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
4842                                                         "to the specified %s charset.\n"
4843                                                         "Send it as %s?"), out_codeset, src_codeset);
4844                                 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL, _("+_Send"), NULL, FALSE,
4845                                                       NULL, ALERT_ERROR, G_ALERTDEFAULT);
4846                                 g_free(msg);
4847
4848                                 if (aval != G_ALERTALTERNATE) {
4849                                         g_free(chars);
4850                                         return -3;
4851                                 } else {
4852                                         buf = chars;
4853                                         out_codeset = src_codeset;
4854                                         chars = NULL;
4855                                 }
4856                         }
4857                 } else {
4858                         buf = chars;
4859                         out_codeset = src_codeset;
4860                         chars = NULL;
4861                 }
4862         }
4863         g_free(chars);
4864
4865         if (encoding == ENC_8BIT || encoding == ENC_7BIT) {
4866                 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
4867                     strstr(buf, "\nFrom ") != NULL) {
4868                         encoding = ENC_QUOTED_PRINTABLE;
4869                 }
4870         }
4871
4872         mimetext = procmime_mimeinfo_new();
4873         mimetext->content = MIMECONTENT_MEM;
4874         mimetext->tmp = TRUE; /* must free content later */
4875         /* dup'ed because procmime_encode_content can turn it into a tmpfile
4876          * and free the data, which we need later. */
4877         mimetext->data.mem = g_strdup(buf); 
4878         mimetext->type = MIMETYPE_TEXT;
4879         mimetext->subtype = g_strdup("plain");
4880         g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
4881                             g_strdup(out_codeset));
4882                             
4883         /* protect trailing spaces when signing message */
4884         if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing && 
4885             privacy_system_can_sign(compose->privacy_system)) {
4886                 encoding = ENC_QUOTED_PRINTABLE;
4887         }
4888         
4889         debug_print("main text: %d bytes encoded as %s in %d\n",
4890                 strlen(buf), out_codeset, encoding);
4891
4892         /* check for line length limit */
4893         if (action == COMPOSE_WRITE_FOR_SEND &&
4894             encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
4895             check_line_length(buf, 1000, &line) < 0) {
4896                 AlertValue aval;
4897                 gchar *msg;
4898
4899                 msg = g_strdup_printf
4900                         (_("Line %d exceeds the line length limit (998 bytes).\n"
4901                            "The contents of the message might be broken on the way to the delivery.\n"
4902                            "\n"
4903                            "Send it anyway?"), line + 1);
4904                 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
4905                 g_free(msg);
4906                 if (aval != G_ALERTALTERNATE) {
4907                         g_free(buf);
4908                         return -1;
4909                 }
4910         }
4911         
4912         if (encoding != ENC_UNKNOWN)
4913                 procmime_encode_content(mimetext, encoding);
4914
4915         /* append attachment parts */
4916         if (compose_use_attach(compose) && attach_parts) {
4917                 MimeInfo *mimempart;
4918                 gchar *boundary = NULL;
4919                 mimempart = procmime_mimeinfo_new();
4920                 mimempart->content = MIMECONTENT_EMPTY;
4921                 mimempart->type = MIMETYPE_MULTIPART;
4922                 mimempart->subtype = g_strdup("mixed");
4923
4924                 do {
4925                         g_free(boundary);
4926                         boundary = generate_mime_boundary(NULL);
4927                 } while (strstr(buf, boundary) != NULL);
4928
4929                 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
4930                                     boundary);
4931
4932                 mimetext->disposition = DISPOSITIONTYPE_INLINE;
4933
4934                 g_node_append(mimempart->node, mimetext->node);
4935                 g_node_append(mimemsg->node, mimempart->node);
4936
4937                 compose_add_attachments(compose, mimempart);
4938         } else
4939                 g_node_append(mimemsg->node, mimetext->node);
4940
4941         g_free(buf);
4942
4943         /* sign message if sending */
4944         if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing && 
4945             privacy_system_can_sign(compose->privacy_system))
4946                 if (!privacy_sign(compose->privacy_system, mimemsg, compose->account))
4947                         return -2;
4948
4949         procmime_write_mimeinfo(mimemsg, fp);
4950         
4951         procmime_mimeinfo_free_all(mimemsg);
4952
4953         return 0;
4954 }
4955
4956 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
4957 {
4958         GtkTextBuffer *buffer;
4959         GtkTextIter start, end;
4960         FILE *fp;
4961         size_t len;
4962         gchar *chars, *tmp;
4963
4964         if ((fp = g_fopen(file, "wb")) == NULL) {
4965                 FILE_OP_ERROR(file, "fopen");
4966                 return -1;
4967         }
4968
4969         /* chmod for security */
4970         if (change_file_mode_rw(fp, file) < 0) {
4971                 FILE_OP_ERROR(file, "chmod");
4972                 g_warning("can't change file mode\n");
4973         }
4974
4975         buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
4976         gtk_text_buffer_get_start_iter(buffer, &start);
4977         gtk_text_buffer_get_end_iter(buffer, &end);
4978         tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
4979
4980         chars = conv_codeset_strdup
4981                 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
4982
4983         g_free(tmp);
4984         if (!chars) return -1;
4985
4986         /* write body */
4987         len = strlen(chars);
4988         if (fwrite(chars, sizeof(gchar), len, fp) != len) {
4989                 FILE_OP_ERROR(file, "fwrite");
4990                 g_free(chars);
4991                 fclose(fp);
4992                 g_unlink(file);
4993                 return -1;
4994         }
4995
4996         g_free(chars);
4997
4998         if (fclose(fp) == EOF) {
4999                 FILE_OP_ERROR(file, "fclose");
5000                 g_unlink(file);
5001                 return -1;
5002         }
5003         return 0;
5004 }
5005
5006 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5007 {
5008         FolderItem *item;
5009         MsgInfo *msginfo = compose->targetinfo;
5010
5011         g_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5012         if (!msginfo) return -1;
5013
5014         if (!force && MSG_IS_LOCKED(msginfo->flags))
5015                 return 0;
5016
5017         item = msginfo->folder;
5018         g_return_val_if_fail(item != NULL, -1);
5019
5020         if (procmsg_msg_exist(msginfo) &&
5021             (folder_has_parent_of_type(item, F_QUEUE) ||
5022              folder_has_parent_of_type(item, F_DRAFT) 
5023              || msginfo == compose->autosaved_draft)) {
5024                 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5025                         g_warning("can't remove the old message\n");
5026                         return -1;
5027                 }
5028         }
5029
5030         return 0;
5031 }
5032
5033 static void compose_remove_draft(Compose *compose)
5034 {
5035         FolderItem *drafts;
5036         MsgInfo *msginfo = compose->targetinfo;
5037         drafts = account_get_special_folder(compose->account, F_DRAFT);
5038
5039         if (procmsg_msg_exist(msginfo)) {
5040                 folder_item_remove_msg(drafts, msginfo->msgnum);
5041         }
5042
5043 }
5044
5045 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5046                    gboolean remove_reedit_target)
5047 {
5048         return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5049 }
5050
5051 static gboolean compose_warn_encryption(Compose *compose)
5052 {
5053         const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5054         AlertValue val = G_ALERTALTERNATE;
5055         
5056         if (warning == NULL)
5057                 return TRUE;
5058
5059         val = alertpanel_full(_("Encryption warning"), warning,
5060                   GTK_STOCK_CANCEL, _("+C_ontinue"), NULL,
5061                   TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5062         if (val & G_ALERTDISABLE) {
5063                 val &= ~G_ALERTDISABLE;
5064                 if (val == G_ALERTALTERNATE)
5065                         privacy_inhibit_encrypt_warning(compose->privacy_system,
5066                                 TRUE);
5067         }
5068
5069         if (val == G_ALERTALTERNATE) {
5070                 return TRUE;
5071         } else {
5072                 return FALSE;
5073         } 
5074 }
5075
5076 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item, 
5077                               gchar **msgpath, gboolean check_subject,
5078                               gboolean remove_reedit_target)
5079 {
5080         FolderItem *queue;
5081         gchar *tmp;
5082         FILE *fp;
5083         GSList *cur;
5084         gint num;
5085         static gboolean lock = FALSE;
5086         PrefsAccount *mailac = NULL, *newsac = NULL;
5087         
5088         debug_print("queueing message...\n");
5089         g_return_val_if_fail(compose->account != NULL, -1);
5090
5091         lock = TRUE;
5092         
5093         if (compose_check_entries(compose, check_subject) == FALSE) {
5094                 lock = FALSE;
5095                 if (compose->batch) {
5096                         gtk_widget_show_all(compose->window);
5097                 }
5098                 return -1;
5099         }
5100
5101         if (!compose->to_list && !compose->newsgroup_list) {
5102                 g_warning("can't get recipient list.");
5103                 lock = FALSE;
5104                 return -1;
5105         }
5106
5107         if (compose->to_list) {
5108                 if (compose->account->protocol != A_NNTP)
5109                         mailac = compose->account;
5110                 else if (cur_account && cur_account->protocol != A_NNTP)
5111                         mailac = cur_account;
5112                 else if (!(mailac = compose_current_mail_account())) {
5113                         lock = FALSE;
5114                         alertpanel_error(_("No account for sending mails available!"));
5115                         return -1;
5116                 }
5117         }
5118
5119         if (compose->newsgroup_list) {
5120                 if (compose->account->protocol == A_NNTP)
5121                         newsac = compose->account;
5122                 else if (!newsac->protocol != A_NNTP) {
5123                         lock = FALSE;
5124                         alertpanel_error(_("No account for posting news available!"));
5125                         return -1;
5126                 }                       
5127         }
5128
5129         /* write queue header */
5130         tmp = g_strdup_printf("%s%cqueue.%p", get_tmp_dir(),
5131                               G_DIR_SEPARATOR, compose);
5132         if ((fp = g_fopen(tmp, "wb")) == NULL) {
5133                 FILE_OP_ERROR(tmp, "fopen");
5134                 g_free(tmp);
5135                 lock = FALSE;
5136                 return -2;
5137         }
5138
5139         if (change_file_mode_rw(fp, tmp) < 0) {
5140                 FILE_OP_ERROR(tmp, "chmod");
5141                 g_warning("can't change file mode\n");
5142         }
5143
5144         /* queueing variables */
5145         fprintf(fp, "AF:\n");
5146         fprintf(fp, "NF:0\n");
5147         fprintf(fp, "PS:10\n");
5148         fprintf(fp, "SRH:1\n");
5149         fprintf(fp, "SFN:\n");
5150         fprintf(fp, "DSR:\n");
5151         if (compose->msgid)
5152                 fprintf(fp, "MID:<%s>\n", compose->msgid);
5153         else
5154                 fprintf(fp, "MID:\n");
5155         fprintf(fp, "CFG:\n");
5156         fprintf(fp, "PT:0\n");
5157         fprintf(fp, "S:%s\n", compose->account->address);
5158         fprintf(fp, "RQ:\n");
5159         if (mailac)
5160                 fprintf(fp, "SSV:%s\n", mailac->smtp_server);
5161         else
5162                 fprintf(fp, "SSV:\n");
5163         if (newsac)
5164                 fprintf(fp, "NSV:%s\n", newsac->nntp_server);
5165         else
5166                 fprintf(fp, "NSV:\n");
5167         fprintf(fp, "SSH:\n");
5168         /* write recepient list */
5169         if (compose->to_list) {
5170                 fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data);
5171                 for (cur = compose->to_list->next; cur != NULL;
5172                      cur = cur->next)
5173                         fprintf(fp, ",<%s>", (gchar *)cur->data);
5174                 fprintf(fp, "\n");
5175         }
5176         /* write newsgroup list */
5177         if (compose->newsgroup_list) {
5178                 fprintf(fp, "NG:");
5179                 fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data);
5180                 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
5181                         fprintf(fp, ",%s", (gchar *)cur->data);
5182                 fprintf(fp, "\n");
5183         }
5184         /* Sylpheed account IDs */
5185         if (mailac)
5186                 fprintf(fp, "MAID:%d\n", mailac->account_id);
5187         if (newsac)
5188                 fprintf(fp, "NAID:%d\n", newsac->account_id);
5189
5190         
5191         if (compose->privacy_system != NULL) {
5192                 fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system);
5193                 fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing);
5194                 if (compose->use_encryption) {
5195                         gchar *encdata;
5196                         if (!compose_warn_encryption(compose)) {
5197                                 lock = FALSE;
5198                                 fclose(fp);
5199                                 g_unlink(tmp);
5200                                 g_free(tmp);
5201                                 return -6;
5202                         }
5203                         if (mailac && mailac->encrypt_to_self) {
5204                                 GSList *tmp_list = g_slist_copy(compose->to_list);
5205                                 tmp_list = g_slist_append(tmp_list, compose->account->address);
5206                                 encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
5207                                 g_slist_free(tmp_list);
5208                         } else {
5209                                 encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
5210                         }
5211                         if (encdata != NULL) {
5212                                 if (strcmp(encdata, "_DONT_ENCRYPT_")) {
5213                                         fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption);
5214                                         fprintf(fp, "X-Claws-Encrypt-Data:%s\n", 
5215                                                 encdata);
5216                                 } /* else we finally dont want to encrypt */
5217                         } else {
5218                                 fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption);
5219                                 /* and if encdata was null, it means there's been a problem in 
5220                                  * key selection */
5221                                 lock = FALSE;
5222                                 fclose(fp);
5223                                 g_unlink(tmp);
5224                                 g_free(tmp);
5225                                 return -5;
5226                         }
5227                         g_free(encdata);
5228                 }
5229         }
5230
5231         /* Save copy folder */
5232         if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
5233                 gchar *savefolderid;
5234                 
5235                 savefolderid = gtk_editable_get_chars(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
5236                 fprintf(fp, "SCF:%s\n", savefolderid);
5237                 g_free(savefolderid);
5238         }
5239         /* Save copy folder */
5240         if (compose->return_receipt) {
5241                 fprintf(fp, "RRCPT:1\n");
5242         }
5243         /* Message-ID of message replying to */
5244         if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
5245                 gchar *folderid;
5246                 
5247                 folderid = folder_item_get_identifier(compose->replyinfo->folder);
5248                 fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid);
5249                 g_free(folderid);
5250         }
5251         /* Message-ID of message forwarding to */
5252         if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
5253                 gchar *folderid;
5254                 
5255                 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
5256                 fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid);
5257                 g_free(folderid);
5258         }
5259
5260         /* end of headers */
5261         fprintf(fp, "X-Claws-End-Special-Headers: 1\n");
5262
5263         if (compose->redirect_filename != NULL) {
5264                 if (compose_redirect_write_to_file(compose, fp) < 0) {
5265                         lock = FALSE;
5266                         fclose(fp);
5267                         g_unlink(tmp);
5268                         g_free(tmp);
5269                         return -2;
5270                 }
5271         } else {
5272                 gint result = 0;
5273                 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
5274                         lock = FALSE;
5275                         fclose(fp);
5276                         g_unlink(tmp);
5277                         g_free(tmp);
5278                         return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
5279                 }
5280         }
5281
5282         if (fclose(fp) == EOF) {
5283                 FILE_OP_ERROR(tmp, "fclose");
5284                 g_unlink(tmp);
5285                 g_free(tmp);
5286                 lock = FALSE;
5287                 return -2;
5288         }
5289
5290         if (item && *item) {
5291                 queue = *item;
5292         } else {
5293                 queue = account_get_special_folder(compose->account, F_QUEUE);
5294         }
5295         if (!queue) {
5296                 g_warning("can't find queue folder\n");
5297                 g_unlink(tmp);
5298                 g_free(tmp);
5299                 lock = FALSE;
5300                 return -1;
5301         }
5302         folder_item_scan(queue);
5303         if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
5304                 g_warning("can't queue the message\n");
5305                 g_unlink(tmp);
5306                 g_free(tmp);
5307                 lock = FALSE;
5308                 return -1;
5309         }
5310         
5311         if (msgpath == NULL) {
5312                 g_unlink(tmp);
5313                 g_free(tmp);
5314         } else
5315                 *msgpath = tmp;
5316
5317         if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
5318                 compose_remove_reedit_target(compose, FALSE);
5319         }
5320
5321         if ((msgnum != NULL) && (item != NULL)) {
5322                 *msgnum = num;
5323                 *item = queue;
5324         }
5325
5326         return 0;
5327 }
5328
5329 static void compose_add_attachments(Compose *compose, MimeInfo *parent)
5330 {
5331         AttachInfo *ainfo;
5332         GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
5333         MimeInfo *mimepart;
5334         struct stat statbuf;
5335         gchar *type, *subtype;
5336         GtkTreeModel *model;
5337         GtkTreeIter iter;
5338
5339         model = gtk_tree_view_get_model(tree_view);
5340         
5341         if (!gtk_tree_model_get_iter_first(model, &iter))
5342                 return;
5343         do {
5344                 gtk_tree_model_get(model, &iter,
5345                                    COL_DATA, &ainfo,
5346                                    -1);
5347                                                            
5348                 mimepart = procmime_mimeinfo_new();
5349                 mimepart->content = MIMECONTENT_FILE;
5350                 mimepart->data.filename = g_strdup(ainfo->file);
5351                 mimepart->tmp = FALSE; /* or we destroy our attachment */
5352                 mimepart->offset = 0;
5353
5354                 stat(ainfo->file, &statbuf);
5355                 mimepart->length = statbuf.st_size;
5356
5357                 type = g_strdup(ainfo->content_type);
5358
5359                 if (!strchr(type, '/')) {
5360                         g_free(type);
5361                         type = g_strdup("application/octet-stream");
5362                 }
5363
5364                 subtype = strchr(type, '/') + 1;
5365                 *(subtype - 1) = '\0';
5366                 mimepart->type = procmime_get_media_type(type);
5367                 mimepart->subtype = g_strdup(subtype);
5368                 g_free(type);
5369
5370                 if (mimepart->type == MIMETYPE_MESSAGE && 
5371                     !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
5372                         mimepart->disposition = DISPOSITIONTYPE_INLINE;
5373                 } else {
5374                         if (ainfo->name) {
5375                                 g_hash_table_insert(mimepart->typeparameters,
5376                                             g_strdup("name"), g_strdup(ainfo->name));
5377                                 g_hash_table_insert(mimepart->dispositionparameters,
5378                                             g_strdup("filename"), g_strdup(ainfo->name));
5379                                 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
5380                         }
5381                 }
5382
5383                 if (compose->use_signing) {
5384                         if (ainfo->encoding == ENC_7BIT)
5385                                 ainfo->encoding = ENC_QUOTED_PRINTABLE;
5386                         else if (ainfo->encoding == ENC_8BIT)
5387                                 ainfo->encoding = ENC_BASE64;
5388                 }
5389                 
5390                 procmime_encode_content(mimepart, ainfo->encoding);
5391
5392                 g_node_append(parent->node, mimepart->node);
5393         } while (gtk_tree_model_iter_next(model, &iter));
5394 }
5395
5396 #define IS_IN_CUSTOM_HEADER(header) \
5397         (compose->account->add_customhdr && \
5398          custom_header_find(compose->account->customhdr_list, header) != NULL)
5399
5400 static void compose_add_headerfield_from_headerlist(Compose *compose, 
5401                                                     GString *header, 
5402                                                     const gchar *fieldname,
5403                                                     const gchar *seperator)
5404 {
5405         gchar *str, *fieldname_w_colon;
5406         gboolean add_field = FALSE;
5407         GSList *list;
5408         ComposeHeaderEntry *headerentry;
5409         const gchar *headerentryname;
5410         const gchar *trans_fieldname;
5411         GString *fieldstr;
5412
5413         if (IS_IN_CUSTOM_HEADER(fieldname))
5414                 return;
5415
5416         debug_print("Adding %s-fields\n", fieldname);
5417
5418         fieldstr = g_string_sized_new(64);
5419
5420         fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
5421         trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
5422
5423         for (list = compose->header_list; list; list = list->next) {
5424                 headerentry = ((ComposeHeaderEntry *)list->data);
5425                 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(headerentry->combo)->entry));
5426
5427                 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
5428                         str = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
5429                         g_strstrip(str);
5430                         if (str[0] != '\0') {
5431                                 if (add_field)
5432                                         g_string_append(fieldstr, seperator);
5433                                 g_string_append(fieldstr, str);
5434                                 add_field = TRUE;
5435                         }
5436                         g_free(str);
5437                 }
5438         }
5439         if (add_field) {
5440                 gchar *buf;
5441
5442                 buf = g_new0(gchar, fieldstr->len * 4 + 256);
5443                 compose_convert_header
5444                         (compose, buf, fieldstr->len * 4  + 256, fieldstr->str,
5445                         strlen(fieldname) + 2, TRUE);
5446                 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
5447                 g_free(buf);
5448         }
5449
5450         g_free(fieldname_w_colon);
5451         g_string_free(fieldstr, TRUE);
5452
5453         return;
5454 }
5455
5456 static gchar *compose_get_header(Compose *compose)
5457 {
5458         gchar buf[BUFFSIZE];
5459         const gchar *entry_str;
5460         gchar *str;
5461         gchar *name;
5462         GSList *list;
5463         gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
5464         GString *header;
5465         gchar *from_name = NULL, *from_address = NULL;
5466         gchar *tmp;
5467
5468         g_return_val_if_fail(compose->account != NULL, NULL);
5469         g_return_val_if_fail(compose->account->address != NULL, NULL);
5470
5471         header = g_string_sized_new(64);
5472
5473         /* Date */
5474         get_rfc822_date(buf, sizeof(buf));
5475         g_string_append_printf(header, "Date: %s\n", buf);
5476
5477         /* From */
5478         
5479         if (compose->account->name && *compose->account->name) {
5480                 gchar *buf;
5481                 QUOTE_IF_REQUIRED(buf, compose->account->name);
5482                 tmp = g_strdup_printf("%s <%s>",
5483                         buf, compose->account->address);
5484         } else {
5485                 tmp = g_strdup_printf("%s",
5486                         compose->account->address);
5487         }
5488         if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
5489         ||  strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
5490                 /* use default */
5491                 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
5492                 from_address = g_strdup(compose->account->address);
5493         } else {
5494                 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5495                 /* extract name and address */
5496                 if (strstr(spec, " <") && strstr(spec, ">")) {
5497                         from_address = g_strdup(strrchr(spec, '<')+1);
5498                         *(strrchr(from_address, '>')) = '\0';
5499                         from_name = g_strdup(spec);
5500                         *(strrchr(from_name, '<')) = '\0';
5501                 } else {
5502                         from_name = NULL;
5503                         from_address = g_strdup(spec);
5504                 }
5505                 g_free(spec);
5506         }
5507         g_free(tmp);
5508         
5509         
5510         if (from_name && *from_name) {
5511                 compose_convert_header
5512                         (compose, buf, sizeof(buf), from_name,
5513                          strlen("From: "), TRUE);
5514                 QUOTE_IF_REQUIRED(name, buf);
5515                 
5516                 g_string_append_printf(header, "From: %s <%s>\n",
5517                         name, from_address);
5518         } else
5519                 g_string_append_printf(header, "From: %s\n", from_address);
5520         
5521         g_free(from_name);
5522         g_free(from_address);
5523
5524         /* To */
5525         compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
5526
5527         /* Newsgroups */
5528         compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
5529
5530         /* Cc */
5531         compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
5532
5533         /* Bcc */
5534         compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
5535
5536         /* Subject */
5537         str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
5538
5539         if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
5540                 g_strstrip(str);
5541                 if (*str != '\0') {
5542                         compose_convert_header(compose, buf, sizeof(buf), str,
5543                                                strlen("Subject: "), FALSE);
5544                         g_string_append_printf(header, "Subject: %s\n", buf);
5545                 }
5546         }
5547         g_free(str);
5548
5549         /* Message-ID */
5550         if (compose->account->gen_msgid) {
5551                 generate_msgid(buf, sizeof(buf));
5552                 g_string_append_printf(header, "Message-ID: <%s>\n", buf);
5553                 compose->msgid = g_strdup(buf);
5554         }
5555
5556         if (compose->remove_references == FALSE) {
5557                 /* In-Reply-To */
5558                 if (compose->inreplyto && compose->to_list)
5559                         g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
5560         
5561                 /* References */
5562                 if (compose->references)
5563                         g_string_append_printf(header, "References: %s\n", compose->references);
5564         }
5565
5566         /* Followup-To */
5567         compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
5568
5569         /* Reply-To */
5570         compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
5571
5572         /* Organization */
5573         if (compose->account->organization &&
5574             strlen(compose->account->organization) &&
5575             !IS_IN_CUSTOM_HEADER("Organization")) {
5576                 compose_convert_header(compose, buf, sizeof(buf),
5577                                        compose->account->organization,
5578                                        strlen("Organization: "), FALSE);
5579                 g_string_append_printf(header, "Organization: %s\n", buf);
5580         }
5581
5582         /* Program version and system info */
5583         if (g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
5584             !compose->newsgroup_list) {
5585                 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
5586                         prog_version,
5587                         gtk_major_version, gtk_minor_version, gtk_micro_version,
5588                         TARGET_ALIAS);
5589         }
5590         if (g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
5591                 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
5592                         prog_version,
5593                         gtk_major_version, gtk_minor_version, gtk_micro_version,
5594                         TARGET_ALIAS);
5595         }
5596
5597         /* custom headers */
5598         if (compose->account->add_customhdr) {
5599                 GSList *cur;
5600
5601                 for (cur = compose->account->customhdr_list; cur != NULL;
5602                      cur = cur->next) {
5603                         CustomHeader *chdr = (CustomHeader *)cur->data;
5604
5605                         if (custom_header_is_allowed(chdr->name)) {
5606                                 compose_convert_header
5607                                         (compose, buf, sizeof(buf),
5608                                          chdr->value ? chdr->value : "",
5609                                          strlen(chdr->name) + 2, FALSE);
5610                                 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
5611                         }
5612                 }
5613         }
5614
5615         /* PRIORITY */
5616         switch (compose->priority) {
5617                 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
5618                                                    "X-Priority: 1 (Highest)\n");
5619                         break;
5620                 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
5621                                                 "X-Priority: 2 (High)\n");
5622                         break;
5623                 case PRIORITY_NORMAL: break;
5624                 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
5625                                                "X-Priority: 4 (Low)\n");
5626                         break;
5627                 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
5628                                                   "X-Priority: 5 (Lowest)\n");
5629                         break;
5630                 default: debug_print("compose: priority unknown : %d\n",
5631                                      compose->priority);
5632         }
5633
5634         /* Request Return Receipt */
5635         if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To")) {
5636                 if (compose->return_receipt) {
5637                         if (compose->account->name
5638                             && *compose->account->name) {
5639                                 compose_convert_header(compose, buf, sizeof(buf), 
5640                                                        compose->account->name, 
5641                                                        strlen("Disposition-Notification-To: "),
5642                                                        TRUE);
5643                                 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, compose->account->address);
5644                         } else
5645                                 g_string_append_printf(header, "Disposition-Notification-To: %s\n", compose->account->address);
5646                 }
5647         }
5648
5649         /* get special headers */
5650         for (list = compose->header_list; list; list = list->next) {
5651                 ComposeHeaderEntry *headerentry;
5652                 gchar *tmp;
5653                 gchar *headername;
5654                 gchar *headername_wcolon;
5655                 const gchar *headername_trans;
5656                 gchar *headervalue;
5657                 gchar **string;
5658                 gboolean standard_header = FALSE;
5659
5660                 headerentry = ((ComposeHeaderEntry *)list->data);
5661                 
5662                 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(headerentry->combo)->entry)));
5663                 if (strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
5664                         g_free(tmp);
5665                         continue;
5666                 }
5667
5668                 if (!strstr(tmp, ":")) {
5669                         headername_wcolon = g_strconcat(tmp, ":", NULL);
5670                         headername = g_strdup(tmp);
5671                 } else {
5672                         headername_wcolon = g_strdup(tmp);
5673                         headername = g_strdup(strtok(tmp, ":"));
5674                 }
5675                 g_free(tmp);
5676                 
5677                 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5678                 Xstrdup_a(headervalue, entry_str, return NULL);
5679                 subst_char(headervalue, '\r', ' ');
5680                 subst_char(headervalue, '\n', ' ');
5681                 string = std_headers;
5682                 while (*string != NULL) {
5683                         headername_trans = prefs_common_translated_header_name(*string);
5684                         if (!strcmp(headername_trans, headername_wcolon))
5685                                 standard_header = TRUE;
5686                         string++;
5687                 }
5688                 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
5689                         g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
5690                                 
5691                 g_free(headername);
5692                 g_free(headername_wcolon);              
5693         }
5694
5695         str = header->str;
5696         g_string_free(header, FALSE);
5697
5698         return str;
5699 }
5700
5701 #undef IS_IN_CUSTOM_HEADER
5702
5703 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
5704                                    gint header_len, gboolean addr_field)
5705 {
5706         gchar *tmpstr = NULL;
5707         const gchar *out_codeset = NULL;
5708
5709         g_return_if_fail(src != NULL);
5710         g_return_if_fail(dest != NULL);
5711
5712         if (len < 1) return;
5713
5714         tmpstr = g_strdup(src);
5715
5716         subst_char(tmpstr, '\n', ' ');
5717         subst_char(tmpstr, '\r', ' ');
5718         g_strchomp(tmpstr);
5719
5720         if (!g_utf8_validate(tmpstr, -1, NULL)) {
5721                 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
5722                 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
5723                 g_free(tmpstr);
5724                 tmpstr = mybuf;
5725         }
5726
5727         codeconv_set_strict(TRUE);
5728         conv_encode_header_full(dest, len, tmpstr, header_len, addr_field, 
5729                 conv_get_charset_str(compose->out_encoding));
5730         codeconv_set_strict(FALSE);
5731         
5732         if (!dest || *dest == '\0') {
5733                 gchar *test_conv_global_out = NULL;
5734                 gchar *test_conv_reply = NULL;
5735
5736                 /* automatic mode. be automatic. */
5737                 codeconv_set_strict(TRUE);
5738
5739                 out_codeset = conv_get_outgoing_charset_str();
5740                 if (out_codeset) {
5741                         debug_print("trying to convert to %s\n", out_codeset);
5742                         test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
5743                 }
5744
5745                 if (!test_conv_global_out && compose->orig_charset
5746                 &&  strcmp(compose->orig_charset, CS_US_ASCII)) {
5747                         out_codeset = compose->orig_charset;
5748                         debug_print("failure; trying to convert to %s\n", out_codeset);
5749                         test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
5750                 }
5751
5752                 if (!test_conv_global_out && !test_conv_reply) {
5753                         /* we're lost */
5754                         out_codeset = CS_INTERNAL;
5755                         debug_print("finally using %s\n", out_codeset);
5756                 }
5757                 g_free(test_conv_global_out);
5758                 g_free(test_conv_reply);
5759                 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field, 
5760                                         out_codeset);
5761                 codeconv_set_strict(FALSE);
5762         }
5763         g_free(tmpstr);
5764 }
5765
5766 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
5767 {
5768         gchar *address;
5769
5770         g_return_if_fail(user_data != NULL);
5771
5772         address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
5773         g_strstrip(address);
5774         if (*address != '\0') {
5775                 gchar *name = procheader_get_fromname(address);
5776                 extract_address(address);
5777                 addressbook_add_contact(name, address, NULL);
5778         }
5779         g_free(address);
5780 }
5781
5782 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
5783 {
5784         GtkWidget *menuitem;
5785         gchar *address;
5786
5787         g_return_if_fail(menu != NULL);
5788         g_return_if_fail(GTK_IS_MENU_SHELL(menu));
5789
5790         menuitem = gtk_separator_menu_item_new();
5791         gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
5792         gtk_widget_show(menuitem);
5793
5794         menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
5795         gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
5796
5797         address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
5798         g_strstrip(address);
5799         if (*address == '\0') {
5800                 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
5801         }
5802
5803         g_signal_connect(G_OBJECT(menuitem), "activate",
5804                          G_CALLBACK(compose_add_to_addressbook_cb), entry);
5805         gtk_widget_show(menuitem);
5806 }
5807
5808 static void compose_create_header_entry(Compose *compose) 
5809 {
5810         gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
5811
5812         GtkWidget *combo;
5813         GtkWidget *entry;
5814         GList *combo_list = NULL;
5815         gchar **string;
5816         const gchar *header = NULL;
5817         ComposeHeaderEntry *headerentry;
5818         gboolean standard_header = FALSE;
5819
5820         headerentry = g_new0(ComposeHeaderEntry, 1);
5821
5822         /* Combo box */
5823         combo = gtk_combo_new();
5824         string = headers; 
5825         while(*string != NULL) {
5826                 combo_list = g_list_append(combo_list, (gchar*)prefs_common_translated_header_name(*string));
5827                 string++;
5828         }
5829         gtk_combo_set_popdown_strings(GTK_COMBO(combo), combo_list);
5830         g_list_free(combo_list);
5831         gtk_editable_set_editable(GTK_EDITABLE(GTK_COMBO(combo)->entry), TRUE);
5832         g_signal_connect(G_OBJECT(GTK_COMBO(combo)->entry), "grab_focus",
5833                          G_CALLBACK(compose_grab_focus_cb), compose);
5834         gtk_widget_show(combo);
5835         gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
5836                         compose->header_nextrow, compose->header_nextrow+1,
5837                         GTK_SHRINK, GTK_FILL, 0, 0);
5838         if (compose->header_last) {     
5839                 const gchar *last_header_entry = gtk_entry_get_text(
5840                                 GTK_ENTRY(GTK_COMBO(compose->header_last->combo)->entry));
5841                 string = headers;
5842                 while (*string != NULL) {
5843                         if (!strcmp(*string, last_header_entry))
5844                                 standard_header = TRUE;
5845                         string++;
5846                 }
5847                 if (standard_header)
5848                         header = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(compose->header_last->combo)->entry));
5849         }
5850         if (!compose->header_last || !standard_header) {
5851                 switch(compose->account->protocol) {
5852                         case A_NNTP:
5853                                 header = prefs_common_translated_header_name("Newsgroups:");
5854                                 break;
5855                         default:
5856                                 header = prefs_common_translated_header_name("To:");
5857                                 break;
5858                 }                                                                   
5859         }
5860         if (header)
5861                 gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(combo)->entry), header);
5862
5863         g_signal_connect_after(G_OBJECT(GTK_COMBO(combo)->entry), "grab_focus",
5864                          G_CALLBACK(compose_grab_focus_cb), compose);
5865
5866         /* Entry field */
5867         entry = gtk_entry_new(); 
5868         gtk_widget_show(entry);
5869         gtk_tooltips_set_tip(compose->tooltips, entry,
5870                 _("Use <tab> to autocomplete from addressbook"), NULL);
5871         gtk_table_attach(GTK_TABLE(compose->header_table), entry, 1, 2,
5872                         compose->header_nextrow, compose->header_nextrow+1,
5873                         GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
5874
5875         g_signal_connect(G_OBJECT(entry), "key-press-event", 
5876                          G_CALLBACK(compose_headerentry_key_press_event_cb), 
5877                          headerentry);
5878         g_signal_connect(G_OBJECT(entry), "changed", 
5879                          G_CALLBACK(compose_headerentry_changed_cb), 
5880                          headerentry);
5881         g_signal_connect_after(G_OBJECT(entry), "grab_focus",
5882                          G_CALLBACK(compose_grab_focus_cb), compose);
5883                          
5884         /* email dnd */
5885         gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types, 
5886                           sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
5887                           GDK_ACTION_COPY | GDK_ACTION_MOVE);
5888         g_signal_connect(G_OBJECT(entry), "drag_data_received",
5889                          G_CALLBACK(compose_header_drag_received_cb),
5890                          entry);
5891         g_signal_connect(G_OBJECT(entry), "drag-drop",
5892                          G_CALLBACK(compose_drag_drop),
5893                          compose);
5894         g_signal_connect(G_OBJECT(entry), "populate-popup",
5895                          G_CALLBACK(compose_entry_popup_extend),
5896                          NULL);
5897         
5898         address_completion_register_entry(GTK_ENTRY(entry), TRUE);
5899
5900         headerentry->compose = compose;
5901         headerentry->combo = combo;
5902         headerentry->entry = entry;
5903         headerentry->headernum = compose->header_nextrow;
5904
5905         compose->header_nextrow++;
5906         compose->header_last = headerentry;             
5907         compose->header_list =
5908                 g_slist_append(compose->header_list,
5909                                headerentry);
5910 }
5911
5912 static void compose_add_header_entry(Compose *compose, const gchar *header, gchar *text) 
5913 {
5914         ComposeHeaderEntry *last_header;
5915         
5916         last_header = compose->header_last;
5917
5918         gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(last_header->combo)->entry), header);
5919         gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
5920 }
5921
5922 static void compose_remove_header_entries(Compose *compose) 
5923 {
5924         GSList *list;
5925         for (list = compose->header_list; list; list = list->next) {
5926                 ComposeHeaderEntry *headerentry = 
5927                         (ComposeHeaderEntry *)list->data;
5928                 gtk_widget_destroy(headerentry->combo);
5929                 gtk_widget_destroy(headerentry->entry);
5930                 g_free(headerentry);
5931         }
5932         compose->header_last = NULL;
5933         g_slist_free(compose->header_list);
5934         compose->header_list = NULL;
5935         compose->header_nextrow = 1;
5936         compose_create_header_entry(compose);
5937 }
5938
5939 static GtkWidget *compose_create_header(Compose *compose) 
5940 {
5941         GtkWidget *from_optmenu_hbox;
5942         GtkWidget *header_scrolledwin;
5943         GtkWidget *header_table;
5944
5945         gint count = 0;
5946
5947         /* header labels and entries */
5948         header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
5949         gtk_widget_show(header_scrolledwin);
5950         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
5951
5952         header_table = gtk_table_new(2, 2, FALSE);
5953         gtk_widget_show(header_table);
5954         gtk_container_set_border_width(GTK_CONTAINER(header_table), BORDER_WIDTH);
5955         gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
5956         gtk_viewport_set_shadow_type(GTK_VIEWPORT(GTK_BIN(header_scrolledwin)->child), GTK_SHADOW_ETCHED_IN);
5957         count = 0;
5958
5959         /* option menu for selecting accounts */
5960         from_optmenu_hbox = compose_account_option_menu_create(compose);
5961         gtk_table_attach(GTK_TABLE(header_table), from_optmenu_hbox,
5962                                   0, 2, count, count + 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
5963         count++;
5964
5965         compose->header_table = header_table;
5966         compose->header_list = NULL;
5967         compose->header_nextrow = count;
5968
5969         compose_create_header_entry(compose);
5970
5971         compose->table            = NULL;
5972
5973         return header_scrolledwin ;
5974 }
5975
5976 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
5977 {
5978         Compose *compose = (Compose *)data;
5979         GdkEventButton event;
5980         
5981         event.button = 3;
5982         event.time = gtk_get_current_event_time();
5983
5984         return attach_button_pressed(compose->attach_clist, &event, compose);
5985 }
5986
5987 static GtkWidget *compose_create_attach(Compose *compose)
5988 {
5989         GtkWidget *attach_scrwin;
5990         GtkWidget *attach_clist;
5991
5992         GtkListStore *store;
5993         GtkCellRenderer *renderer;
5994         GtkTreeViewColumn *column;
5995         GtkTreeSelection *selection;
5996
5997         /* attachment list */
5998         attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
5999         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
6000                                        GTK_POLICY_AUTOMATIC,
6001                                        GTK_POLICY_AUTOMATIC);
6002         gtk_widget_set_size_request(attach_scrwin, -1, 80);
6003
6004         store = gtk_list_store_new(N_ATTACH_COLS, 
6005                                    G_TYPE_STRING,
6006                                    G_TYPE_STRING,
6007                                    G_TYPE_STRING,
6008                                    G_TYPE_POINTER,
6009                                    G_TYPE_AUTO_POINTER,
6010                                    -1);
6011         attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
6012                                         (GTK_TREE_MODEL(store)));
6013         gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
6014         g_object_unref(store);
6015         
6016         renderer = gtk_cell_renderer_text_new();
6017         column = gtk_tree_view_column_new_with_attributes
6018                         (_("Mime type"), renderer, "text", 
6019                          COL_MIMETYPE, NULL);
6020         gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);                        
6021         
6022         renderer = gtk_cell_renderer_text_new();
6023         column = gtk_tree_view_column_new_with_attributes
6024                         (_("Size"), renderer, "text", 
6025                          COL_SIZE, NULL);
6026         gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);                        
6027         
6028         renderer = gtk_cell_renderer_text_new();
6029         column = gtk_tree_view_column_new_with_attributes
6030                         (_("Name"), renderer, "text", 
6031                          COL_NAME, NULL);
6032         gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6033
6034         gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
6035                                      prefs_common.use_stripes_everywhere);
6036         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
6037         gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
6038
6039         g_signal_connect(G_OBJECT(attach_clist), "row_activated",
6040                          G_CALLBACK(attach_selected), compose);
6041         g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
6042                          G_CALLBACK(attach_button_pressed), compose);
6043 #ifndef MAEMO
6044         g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
6045                          G_CALLBACK(popup_attach_button_pressed), compose);
6046 #else
6047         gtk_widget_tap_and_hold_setup(GTK_WIDGET(attach_clist), NULL, NULL,
6048                         GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
6049         g_signal_connect(G_OBJECT(attach_clist), "tap-and-hold",
6050                          G_CALLBACK(popup_attach_button_pressed), compose);
6051 #endif
6052         g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
6053                          G_CALLBACK(attach_key_pressed), compose);
6054
6055         /* drag and drop */
6056         gtk_drag_dest_set(attach_clist,
6057                           GTK_DEST_DEFAULT_ALL, compose_mime_types, 
6058                           sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6059                           GDK_ACTION_COPY | GDK_ACTION_MOVE);
6060         g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
6061                          G_CALLBACK(compose_attach_drag_received_cb),
6062                          compose);
6063         g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
6064                          G_CALLBACK(compose_drag_drop),
6065                          compose);
6066
6067         compose->attach_scrwin = attach_scrwin;
6068         compose->attach_clist  = attach_clist;
6069
6070         return attach_scrwin;
6071 }
6072
6073 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
6074 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
6075
6076 static GtkWidget *compose_create_others(Compose *compose)
6077 {
6078         GtkWidget *table;
6079         GtkWidget *savemsg_checkbtn;
6080         GtkWidget *savemsg_entry;
6081         GtkWidget *savemsg_select;
6082         
6083         guint rowcount = 0;
6084         gchar *folderidentifier;
6085
6086         /* Table for settings */
6087         table = gtk_table_new(3, 1, FALSE);
6088         gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
6089         gtk_widget_show(table);
6090         gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
6091         rowcount = 0;
6092
6093         /* Save Message to folder */
6094         savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
6095         gtk_widget_show(savemsg_checkbtn);
6096         gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6097         if (account_get_special_folder(compose->account, F_OUTBOX)) {
6098                 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
6099         }
6100         g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
6101                          G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
6102
6103         savemsg_entry = gtk_entry_new();
6104         gtk_widget_show(savemsg_entry);
6105         gtk_table_attach_defaults(GTK_TABLE(table), savemsg_entry, 1, 2, rowcount, rowcount + 1);
6106         gtk_editable_set_editable(GTK_EDITABLE(savemsg_entry), prefs_common.savemsg);
6107         g_signal_connect_after(G_OBJECT(savemsg_entry), "grab_focus",
6108                          G_CALLBACK(compose_grab_focus_cb), compose);
6109         if (account_get_special_folder(compose->account, F_OUTBOX)) {
6110                 folderidentifier = folder_item_get_identifier(account_get_special_folder
6111                                   (compose->account, F_OUTBOX));
6112                 gtk_entry_set_text(GTK_ENTRY(savemsg_entry), folderidentifier);
6113                 g_free(folderidentifier);
6114         }
6115
6116         savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
6117         gtk_widget_show(savemsg_select);
6118         gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6119         g_signal_connect(G_OBJECT(savemsg_select), "clicked",
6120                          G_CALLBACK(compose_savemsg_select_cb),
6121                          compose);
6122
6123         rowcount++;
6124
6125         compose->savemsg_checkbtn = savemsg_checkbtn;
6126         compose->savemsg_entry = savemsg_entry;
6127
6128         return table;   
6129 }
6130
6131 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose) 
6132 {
6133         gtk_editable_set_editable(GTK_EDITABLE(compose->savemsg_entry),
6134                 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
6135 }
6136
6137 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
6138 {
6139         FolderItem *dest;
6140         gchar * path;
6141
6142         dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL);
6143         if (!dest) return;
6144
6145         path = folder_item_get_identifier(dest);
6146
6147         gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), path);
6148         g_free(path);
6149 }
6150
6151 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
6152                                   GdkAtom clip, GtkTextIter *insert_place);
6153
6154 #define BLOCK_WRAP() {                                                  \
6155         prev_autowrap = compose->autowrap;                              \
6156         buffer = gtk_text_view_get_buffer(                              \
6157                                         GTK_TEXT_VIEW(compose->text));  \
6158         compose->autowrap = FALSE;                                      \
6159                                                                         \
6160         g_signal_handlers_block_by_func(G_OBJECT(buffer),               \
6161                                 G_CALLBACK(compose_changed_cb),         \
6162                                 compose);                               \
6163         g_signal_handlers_block_by_func(G_OBJECT(buffer),               \
6164                                 G_CALLBACK(text_inserted),              \
6165                                 compose);                               \
6166 }
6167 #define UNBLOCK_WRAP() {                                                \
6168         compose->autowrap = prev_autowrap;                              \
6169         if (compose->autowrap)                                          \
6170                 compose_wrap_all(compose);                              \
6171                                                                         \
6172         g_signal_handlers_unblock_by_func(G_OBJECT(buffer),             \
6173                                 G_CALLBACK(compose_changed_cb),         \
6174                                 compose);                               \
6175         g_signal_handlers_unblock_by_func(G_OBJECT(buffer),             \
6176                                 G_CALLBACK(text_inserted),              \
6177                                 compose);                               \
6178 }
6179
6180
6181 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
6182                                        Compose *compose)
6183 {
6184         gint prev_autowrap;
6185         GtkTextBuffer *buffer;
6186 #if USE_ASPELL
6187         if (event->button == 3) {
6188                 GtkTextIter iter;
6189                 GtkTextIter sel_start, sel_end;
6190                 gboolean stuff_selected;
6191                 gint x, y;
6192                 /* move the cursor to allow GtkAspell to check the word
6193                  * under the mouse */
6194                 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6195                         GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6196                         &x, &y);
6197                 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6198                         &iter, x, y);
6199                 /* get selection */
6200                 stuff_selected = gtk_text_buffer_get_selection_bounds(
6201                                 GTK_TEXT_VIEW(text)->buffer,
6202                                 &sel_start, &sel_end);
6203
6204                 gtk_text_buffer_place_cursor (GTK_TEXT_VIEW(text)->buffer, &iter);
6205                 /* reselect stuff */
6206                 if (stuff_selected 
6207                 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
6208                         gtk_text_buffer_select_range(GTK_TEXT_VIEW(text)->buffer,
6209                                 &sel_start, &sel_end);
6210                 }
6211                 return FALSE; /* pass the event so that the right-click goes through */
6212         }
6213 #endif
6214         if (event->button == 2) {
6215                 GtkTextIter iter;
6216                 gint x, y;
6217                 BLOCK_WRAP();
6218                 
6219                 /* get the middle-click position to paste at the correct place */
6220                 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6221                         GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6222                         &x, &y);
6223                 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6224                         &iter, x, y);
6225                 
6226                 entry_paste_clipboard(compose, text, 
6227                                 prefs_common.linewrap_pastes,
6228                                 GDK_SELECTION_PRIMARY, &iter);
6229                 UNBLOCK_WRAP();
6230                 return TRUE;
6231         }
6232         return FALSE;
6233 }
6234
6235 #if USE_ASPELL
6236 static void compose_spell_menu_changed(void *data)
6237 {
6238         Compose *compose = (Compose *)data;
6239         GSList *items;
6240         GtkWidget *menuitem;
6241         GtkWidget *parent_item;
6242         GtkMenu *menu = GTK_MENU(gtk_menu_new());
6243         GtkItemFactory *ifactory = gtk_item_factory_from_widget(compose->menubar);
6244         GSList *spell_menu;
6245
6246         if (compose->gtkaspell == NULL)
6247                 return;
6248
6249         parent_item = gtk_item_factory_get_item(ifactory, 
6250                         "/Spelling/Options");
6251
6252         /* setting the submenu removes /Spelling/Options from the factory 
6253          * so we need to save it */
6254
6255         if (parent_item == NULL) {
6256                 parent_item = compose->aspell_options_menu;
6257                 gtk_menu_item_remove_submenu(GTK_MENU_ITEM(parent_item));
6258         } else
6259                 compose->aspell_options_menu = parent_item;
6260
6261         spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
6262
6263         spell_menu = g_slist_reverse(spell_menu);
6264         for (items = spell_menu;
6265              items; items = items->next) {
6266                 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
6267                 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
6268                 gtk_widget_show(GTK_WIDGET(menuitem));
6269         }
6270         g_slist_free(spell_menu);
6271
6272         gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
6273         
6274 }
6275 #endif
6276
6277 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
6278 {
6279         Compose *compose = (Compose *)data;
6280         GdkEventButton event;
6281         
6282         event.button = 3;
6283         event.time = gtk_get_current_event_time();
6284
6285         return text_clicked(compose->text, &event, compose);
6286 }
6287
6288 static gboolean compose_force_window_origin = TRUE;
6289 static Compose *compose_create(PrefsAccount *account, ComposeMode mode,
6290                                                  gboolean batch)
6291 {
6292         Compose   *compose;
6293         GtkWidget *window;
6294         GtkWidget *vbox;
6295         GtkWidget *menubar;
6296         GtkWidget *handlebox;
6297
6298         GtkWidget *notebook;
6299
6300         GtkWidget *vbox2;
6301
6302         GtkWidget *label;
6303         GtkWidget *subject_hbox;
6304         GtkWidget *subject_frame;
6305         GtkWidget *subject_entry;
6306         GtkWidget *subject;
6307         GtkWidget *paned;
6308
6309         GtkWidget *edit_vbox;
6310         GtkWidget *ruler_hbox;
6311         GtkWidget *ruler;
6312         GtkWidget *scrolledwin;
6313         GtkWidget *text;
6314         GtkTextBuffer *buffer;
6315         GtkClipboard *clipboard;
6316
6317         UndoMain *undostruct;
6318
6319         gchar *titles[N_ATTACH_COLS];
6320         guint n_menu_entries;
6321         GtkWidget *popupmenu;
6322         GtkItemFactory *popupfactory;
6323         GtkItemFactory *ifactory;
6324         GtkWidget *tmpl_menu;
6325         gint n_entries;
6326         GtkWidget *menuitem;
6327
6328 #if USE_ASPELL
6329         GtkAspell * gtkaspell = NULL;
6330 #endif
6331
6332         static GdkGeometry geometry;
6333
6334         g_return_val_if_fail(account != NULL, NULL);
6335
6336         debug_print("Creating compose window...\n");
6337         compose = g_new0(Compose, 1);
6338
6339         titles[COL_MIMETYPE] = _("MIME type");
6340         titles[COL_SIZE]     = _("Size");
6341         titles[COL_NAME]     = _("Name");
6342
6343         compose->batch = batch;
6344         compose->account = account;
6345         
6346         compose->mutex = g_mutex_new();
6347         compose->set_cursor_pos = -1;
6348
6349         compose->tooltips = gtk_tooltips_new();
6350
6351         window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
6352
6353         gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
6354         gtk_widget_set_size_request(window, -1, prefs_common.compose_height);
6355
6356         if (!geometry.max_width) {
6357                 geometry.max_width = gdk_screen_width();
6358                 geometry.max_height = gdk_screen_height();
6359         }
6360
6361         gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
6362                                       &geometry, GDK_HINT_MAX_SIZE);
6363         if (!geometry.min_width) {
6364                 geometry.min_width = 600;
6365                 geometry.min_height = 480;
6366         }
6367         gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
6368                                       &geometry, GDK_HINT_MIN_SIZE);
6369
6370 #ifndef MAEMO   
6371         if (compose_force_window_origin)
6372                 gtk_widget_set_uposition(window, prefs_common.compose_x, 
6373                                  prefs_common.compose_y);
6374 #endif
6375         g_signal_connect(G_OBJECT(window), "delete_event",
6376                          G_CALLBACK(compose_delete_cb), compose);
6377         MANAGE_WINDOW_SIGNALS_CONNECT(window);
6378         gtk_widget_realize(window);
6379
6380         gtkut_widget_set_composer_icon(window);
6381
6382         vbox = gtk_vbox_new(FALSE, 0);
6383         gtk_container_add(GTK_CONTAINER(window), vbox);
6384
6385         n_menu_entries = sizeof(compose_entries) / sizeof(compose_entries[0]);
6386         menubar = menubar_create(window, compose_entries,
6387                                  n_menu_entries, "<Compose>", compose);
6388         gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
6389
6390         if (prefs_common.toolbar_detachable) {
6391                 handlebox = gtk_handle_box_new();
6392         } else {
6393                 handlebox = gtk_hbox_new(FALSE, 0);
6394         }
6395         gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
6396
6397         gtk_widget_realize(handlebox);
6398         compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
6399                                           (gpointer)compose);
6400
6401         vbox2 = gtk_vbox_new(FALSE, 2);
6402         gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
6403         gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
6404         
6405         /* Notebook */
6406         notebook = gtk_notebook_new();
6407         gtk_widget_set_size_request(notebook, -1, 130);
6408         gtk_widget_show(notebook);
6409
6410         /* header labels and entries */
6411         gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6412                         compose_create_header(compose),
6413                         gtk_label_new_with_mnemonic(_("Hea_der")));
6414         /* attachment list */
6415         gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6416                         compose_create_attach(compose),
6417                         gtk_label_new_with_mnemonic(_("_Attachments")));
6418         /* Others Tab */
6419         gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6420                         compose_create_others(compose),
6421                         gtk_label_new_with_mnemonic(_("Othe_rs")));
6422
6423         /* Subject */
6424         subject_hbox = gtk_hbox_new(FALSE, 0);
6425         gtk_widget_show(subject_hbox);
6426
6427         subject_frame = gtk_frame_new(NULL);
6428         gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
6429         gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
6430         gtk_widget_show(subject_frame);
6431
6432         subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
6433         gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
6434         gtk_widget_show(subject);
6435
6436         label = gtk_label_new(_("Subject:"));
6437         gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
6438         gtk_widget_show(label);
6439
6440         subject_entry = gtk_entry_new();
6441         gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
6442         g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
6443                          G_CALLBACK(compose_grab_focus_cb), compose);
6444         gtk_widget_show(subject_entry);
6445         compose->subject_entry = subject_entry;
6446         gtk_container_add(GTK_CONTAINER(subject_frame), subject);
6447         
6448         edit_vbox = gtk_vbox_new(FALSE, 0);
6449
6450         gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
6451
6452         /* ruler */
6453         ruler_hbox = gtk_hbox_new(FALSE, 0);
6454         gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
6455
6456         ruler = gtk_shruler_new();
6457         gtk_ruler_set_range(GTK_RULER(ruler), 0.0, 100.0, 1.0, 100.0);
6458         gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
6459                            BORDER_WIDTH);
6460
6461         /* text widget */
6462         scrolledwin = gtk_scrolled_window_new(NULL, NULL);
6463         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
6464                                        GTK_POLICY_AUTOMATIC,
6465                                        GTK_POLICY_AUTOMATIC);
6466         gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
6467                                             GTK_SHADOW_IN);
6468         gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
6469         gtk_widget_set_size_request(scrolledwin, prefs_common.compose_width, -1);
6470
6471         text = gtk_text_view_new();
6472         buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
6473         gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
6474         gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
6475         clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
6476         gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
6477         
6478         gtk_container_add(GTK_CONTAINER(scrolledwin), text);
6479
6480         g_signal_connect_after(G_OBJECT(text), "size_allocate",
6481                                G_CALLBACK(compose_edit_size_alloc),
6482                                ruler);
6483         g_signal_connect(G_OBJECT(buffer), "changed",
6484                          G_CALLBACK(compose_changed_cb), compose);
6485         g_signal_connect(G_OBJECT(text), "grab_focus",
6486                          G_CALLBACK(compose_grab_focus_cb), compose);
6487         g_signal_connect(G_OBJECT(buffer), "insert_text",
6488                          G_CALLBACK(text_inserted), compose);
6489         g_signal_connect(G_OBJECT(text), "button_press_event",
6490                          G_CALLBACK(text_clicked), compose);
6491 #ifndef MAEMO
6492         g_signal_connect(G_OBJECT(text), "popup-menu",
6493                          G_CALLBACK(compose_popup_menu), compose);
6494 #else
6495         gtk_widget_tap_and_hold_setup(GTK_WIDGET(text), NULL, NULL,
6496                         GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
6497         g_signal_connect(G_OBJECT(text), "tap-and-hold",
6498                          G_CALLBACK(compose_popup_menu), compose);
6499 #endif
6500         g_signal_connect(G_OBJECT(subject_entry), "changed",
6501                          G_CALLBACK(compose_changed_cb), compose);
6502
6503         /* drag and drop */
6504         gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types, 
6505                           sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6506                           GDK_ACTION_COPY | GDK_ACTION_MOVE);
6507         g_signal_connect(G_OBJECT(text), "drag_data_received",
6508                          G_CALLBACK(compose_insert_drag_received_cb),
6509                          compose);
6510         g_signal_connect(G_OBJECT(text), "drag-drop",
6511                          G_CALLBACK(compose_drag_drop),
6512                          compose);
6513         gtk_widget_show_all(vbox);
6514
6515         /* pane between attach clist and text */
6516         paned = gtk_vpaned_new();
6517         gtk_paned_set_gutter_size(GTK_PANED(paned), 12);
6518         gtk_container_add(GTK_CONTAINER(vbox2), paned);
6519 #ifdef MAEMO
6520         if( maemo_mainwindow_is_fullscreen(mainwindow_get_mainwindow()->window) )
6521                 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 300 : 280);
6522         else
6523                 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 250 : 230);
6524 #endif
6525         gtk_paned_add1(GTK_PANED(paned), notebook);
6526         gtk_paned_add2(GTK_PANED(paned), edit_vbox);
6527         gtk_widget_show_all(paned);
6528
6529
6530         if (prefs_common.textfont) {
6531                 PangoFontDescription *font_desc;
6532
6533                 font_desc = pango_font_description_from_string
6534                         (prefs_common.textfont);
6535                 if (font_desc) {
6536                         gtk_widget_modify_font(text, font_desc);
6537                         pango_font_description_free(font_desc);
6538                 }
6539         }
6540
6541         n_entries = sizeof(compose_popup_entries) /
6542                 sizeof(compose_popup_entries[0]);
6543         popupmenu = menu_create_items(compose_popup_entries, n_entries,
6544                                       "<Compose>", &popupfactory,
6545                                       compose);
6546
6547         ifactory = gtk_item_factory_from_widget(menubar);
6548         menu_set_sensitive(ifactory, "/Edit/Undo", FALSE);
6549         menu_set_sensitive(ifactory, "/Edit/Redo", FALSE);
6550         menu_set_sensitive(ifactory, "/Options/Remove references", FALSE);
6551
6552         tmpl_menu = gtk_item_factory_get_item(ifactory, "/Tools/Template");
6553
6554         undostruct = undo_init(text);
6555         undo_set_change_state_func(undostruct, &compose_undo_state_changed,
6556                                    menubar);
6557
6558         address_completion_start(window);
6559
6560         compose->window        = window;
6561         compose->vbox          = vbox;
6562         compose->menubar       = menubar;
6563         compose->handlebox     = handlebox;
6564
6565         compose->vbox2         = vbox2;
6566
6567         compose->paned = paned;
6568
6569         compose->notebook      = notebook;
6570         compose->edit_vbox     = edit_vbox;
6571         compose->ruler_hbox    = ruler_hbox;
6572         compose->ruler         = ruler;
6573         compose->scrolledwin   = scrolledwin;
6574         compose->text          = text;
6575
6576         compose->focused_editable = NULL;
6577
6578         compose->popupmenu    = popupmenu;
6579         compose->popupfactory = popupfactory;
6580
6581         compose->tmpl_menu = tmpl_menu;
6582
6583         compose->mode = mode;
6584         compose->rmode = mode;
6585
6586         compose->targetinfo = NULL;
6587         compose->replyinfo  = NULL;
6588         compose->fwdinfo    = NULL;
6589
6590         compose->replyto     = NULL;
6591         compose->cc          = NULL;
6592         compose->bcc         = NULL;
6593         compose->followup_to = NULL;
6594
6595         compose->ml_post     = NULL;
6596
6597         compose->inreplyto   = NULL;
6598         compose->references  = NULL;
6599         compose->msgid       = NULL;
6600         compose->boundary    = NULL;
6601
6602         compose->autowrap       = prefs_common.autowrap;
6603
6604         compose->use_signing    = FALSE;
6605         compose->use_encryption = FALSE;
6606         compose->privacy_system = NULL;
6607
6608         compose->modified = FALSE;
6609
6610         compose->return_receipt = FALSE;
6611
6612         compose->to_list        = NULL;
6613         compose->newsgroup_list = NULL;
6614
6615         compose->undostruct = undostruct;
6616
6617         compose->sig_str = NULL;
6618
6619         compose->exteditor_file    = NULL;
6620         compose->exteditor_pid     = -1;
6621         compose->exteditor_tag     = -1;
6622         compose->draft_timeout_tag = -1;
6623
6624 #if USE_ASPELL
6625         menu_set_sensitive(ifactory, "/Spelling", FALSE);
6626         if (mode != COMPOSE_REDIRECT) {
6627                 if (prefs_common.enable_aspell && prefs_common.dictionary &&
6628                     strcmp(prefs_common.dictionary, "")) {
6629                         gtkaspell = gtkaspell_new(prefs_common.aspell_path,
6630                                                   prefs_common.dictionary,
6631                                                   prefs_common.alt_dictionary,
6632                                                   conv_get_locale_charset_str(),
6633                                                   prefs_common.misspelled_col,
6634                                                   prefs_common.check_while_typing,
6635                                                   prefs_common.recheck_when_changing_dict,
6636                                                   prefs_common.use_alternate,
6637                                                   prefs_common.use_both_dicts,
6638                                                   GTK_TEXT_VIEW(text),
6639                                                   GTK_WINDOW(compose->window),
6640                                                   compose_spell_menu_changed,
6641                                                   compose);
6642                         if (!gtkaspell) {
6643                                 alertpanel_error(_("Spell checker could not "
6644                                                 "be started.\n%s"),
6645                                                 gtkaspell_checkers_strerror());
6646                                 gtkaspell_checkers_reset_error();
6647                         } else {
6648                                 if (!gtkaspell_set_sug_mode(gtkaspell,
6649                                                 prefs_common.aspell_sugmode)) {
6650                                         debug_print("Aspell: could not set "
6651                                                     "suggestion mode %s\n",
6652                                                     gtkaspell_checkers_strerror());
6653                                         gtkaspell_checkers_reset_error();
6654                                 }
6655
6656                                 menu_set_sensitive(ifactory, "/Spelling", TRUE);
6657                         }
6658                 }
6659         }
6660         compose->gtkaspell = gtkaspell;
6661         compose_spell_menu_changed(compose);
6662 #endif
6663
6664         compose_select_account(compose, account, TRUE);
6665
6666         menu_set_active(ifactory, "/Edit/Auto wrapping", prefs_common.autowrap);
6667         if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
6668                 compose_entry_append(compose, account->auto_cc, COMPOSE_CC);
6669
6670         if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT) 
6671                 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC);
6672         
6673         if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
6674                 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO);
6675
6676         menu_set_sensitive(ifactory, "/Options/Reply mode", compose->mode == COMPOSE_REPLY);
6677
6678         if (account->protocol != A_NNTP)
6679                 gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(compose->header_last->combo)->entry),
6680                                 prefs_common_translated_header_name("To:"));
6681         else
6682                 gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(compose->header_last->combo)->entry),
6683                                 prefs_common_translated_header_name("Newsgroups:"));
6684
6685         addressbook_set_target_compose(compose);
6686         
6687         if (mode != COMPOSE_REDIRECT)
6688                 compose_set_template_menu(compose);
6689         else {
6690                 menuitem = gtk_item_factory_get_item(ifactory, "/Tools/Template");
6691                 menu_set_sensitive(ifactory, "/Tools/Template", FALSE);
6692         }
6693
6694         compose_list = g_list_append(compose_list, compose);
6695
6696         if (!prefs_common.show_ruler)
6697                 gtk_widget_hide(ruler_hbox);
6698                 
6699         menuitem = gtk_item_factory_get_item(ifactory, "/Tools/Show ruler");
6700         gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
6701                                        prefs_common.show_ruler);
6702
6703         /* Priority */
6704         compose->priority = PRIORITY_NORMAL;
6705         compose_update_priority_menu_item(compose);
6706
6707         compose_set_out_encoding(compose);
6708         
6709         /* Actions menu */
6710         compose_update_actions_menu(compose);
6711
6712         /* Privacy Systems menu */
6713         compose_update_privacy_systems_menu(compose);
6714
6715         activate_privacy_system(compose, account, TRUE);
6716         toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
6717         if (batch) {
6718                 gtk_widget_realize(window);
6719         } else {
6720                 gtk_widget_show(window);
6721 #ifdef MAEMO
6722                 maemo_window_full_screen_if_needed(GTK_WINDOW(window));
6723                 maemo_connect_key_press_to_mainwindow(GTK_WINDOW(window));
6724 #endif
6725         }
6726         
6727         return compose;
6728 }
6729
6730 static GtkWidget *compose_account_option_menu_create(Compose *compose)
6731 {
6732         GList *accounts;
6733         GtkWidget *hbox;
6734         GtkWidget *optmenu;
6735         GtkWidget *optmenubox;
6736         GtkListStore *menu;
6737         GtkTreeIter iter;
6738         GtkWidget *from_name = NULL;
6739
6740         gint num = 0, def_menu = 0;
6741         
6742         accounts = account_get_list();
6743         g_return_val_if_fail(accounts != NULL, NULL);
6744
6745         optmenubox = gtk_event_box_new();
6746         optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
6747         menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
6748
6749         hbox = gtk_hbox_new(FALSE, 6);
6750         from_name = gtk_entry_new();
6751         
6752         g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
6753                          G_CALLBACK(compose_grab_focus_cb), compose);
6754
6755         for (; accounts != NULL; accounts = accounts->next, num++) {
6756                 PrefsAccount *ac = (PrefsAccount *)accounts->data;
6757                 gchar *name, *from = NULL;
6758
6759                 if (ac == compose->account) def_menu = num;
6760
6761                 name = g_markup_printf_escaped(_("From: <i>%s</i>"),
6762                                        ac->account_name);
6763                 
6764                 if (ac == compose->account) {
6765                         if (ac->name && *ac->name) {
6766                                 gchar *buf;
6767                                 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
6768                                 from = g_strdup_printf("%s <%s>",
6769                                                        buf, ac->address);
6770                                 gtk_entry_set_text(GTK_ENTRY(from_name), from);
6771                         } else {
6772                                 from = g_strdup_printf("%s",
6773                                                        ac->address);
6774                                 gtk_entry_set_text(GTK_ENTRY(from_name), from);
6775                         }
6776                 }
6777                 COMBOBOX_ADD(menu, name, ac->account_id);
6778                 g_free(name);
6779                 g_free(from);
6780         }
6781
6782         gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
6783
6784         g_signal_connect(G_OBJECT(optmenu), "changed",
6785                         G_CALLBACK(account_activated),
6786                         compose);
6787         g_signal_connect(G_OBJECT(from_name), "populate-popup",
6788                          G_CALLBACK(compose_entry_popup_extend),
6789                          NULL);
6790
6791         gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
6792         gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
6793         
6794         gtk_tooltips_set_tip(compose->tooltips, optmenubox,
6795                 _("Account to use for this email"), NULL);
6796         gtk_tooltips_set_tip(compose->tooltips, from_name,
6797                 _("Sender address to be used"), NULL);
6798
6799         compose->from_name = from_name;
6800         
6801         return hbox;
6802 }
6803
6804 static void compose_set_priority_cb(gpointer data,
6805                                     guint action,
6806                                     GtkWidget *widget)
6807 {
6808         Compose *compose = (Compose *) data;
6809         compose->priority = action;
6810 }
6811
6812 static void compose_reply_change_mode(gpointer data,
6813                                     ComposeMode action,
6814                                     GtkWidget *widget)
6815 {
6816         Compose *compose = (Compose *) data;
6817         gboolean was_modified = compose->modified;
6818
6819         gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
6820         
6821         g_return_if_fail(compose->replyinfo != NULL);
6822         
6823         if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
6824                 ml = TRUE;
6825         if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
6826                 followup = TRUE;
6827         if (action == COMPOSE_REPLY_TO_ALL)
6828                 all = TRUE;
6829         if (action == COMPOSE_REPLY_TO_SENDER)
6830                 sender = TRUE;
6831         if (action == COMPOSE_REPLY_TO_LIST)
6832                 ml = TRUE;
6833
6834         compose_remove_header_entries(compose);
6835         compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
6836         if (compose->account->set_autocc && compose->account->auto_cc)
6837                 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC);
6838
6839         if (compose->account->set_autobcc && compose->account->auto_bcc) 
6840                 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC);
6841         
6842         if (compose->account->set_autoreplyto && compose->account->auto_replyto)
6843                 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO);
6844         compose_show_first_last_header(compose, TRUE);
6845         compose->modified = was_modified;
6846         compose_set_title(compose);
6847 }
6848
6849 static void compose_update_priority_menu_item(Compose * compose)
6850 {
6851         GtkItemFactory *ifactory;
6852         GtkWidget *menuitem = NULL;
6853
6854         ifactory = gtk_item_factory_from_widget(compose->menubar);
6855         
6856         switch (compose->priority) {
6857                 case PRIORITY_HIGHEST:
6858                         menuitem = gtk_item_factory_get_item
6859                                 (ifactory, "/Options/Priority/Highest");
6860                         break;
6861                 case PRIORITY_HIGH:
6862                         menuitem = gtk_item_factory_get_item
6863                                 (ifactory, "/Options/Priority/High");
6864                         break;
6865                 case PRIORITY_NORMAL:
6866                         menuitem = gtk_item_factory_get_item
6867                                 (ifactory, "/Options/Priority/Normal");
6868                         break;
6869                 case PRIORITY_LOW:
6870                         menuitem = gtk_item_factory_get_item
6871                                 (ifactory, "/Options/Priority/Low");
6872                         break;
6873                 case PRIORITY_LOWEST:
6874                         menuitem = gtk_item_factory_get_item
6875                                 (ifactory, "/Options/Priority/Lowest");
6876                         break;
6877         }
6878         gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
6879 }       
6880
6881 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
6882 {
6883         Compose *compose = (Compose *) data;
6884         gchar *systemid;
6885         GtkItemFactory *ifactory;
6886         gboolean can_sign = FALSE, can_encrypt = FALSE;
6887
6888         g_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
6889
6890         if (!GTK_CHECK_MENU_ITEM(widget)->active)
6891                 return;
6892
6893         systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
6894         g_free(compose->privacy_system);
6895         compose->privacy_system = NULL;
6896         if (systemid != NULL) {
6897                 compose->privacy_system = g_strdup(systemid);
6898
6899                 can_sign = privacy_system_can_sign(systemid);
6900                 can_encrypt = privacy_system_can_encrypt(systemid);
6901         }
6902
6903         debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
6904
6905         ifactory = gtk_item_factory_from_widget(compose->menubar);
6906         menu_set_sensitive(ifactory, "/Options/Sign", can_sign);
6907         menu_set_sensitive(ifactory, "/Options/Encrypt", can_encrypt);
6908 }
6909
6910 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
6911 {
6912         static gchar *branch_path = "/Options/Privacy System";
6913         GtkItemFactory *ifactory;
6914         GtkWidget *menuitem = NULL;
6915         GList *amenu;
6916         gboolean can_sign = FALSE, can_encrypt = FALSE;
6917         gboolean found = FALSE;
6918
6919         ifactory = gtk_item_factory_from_widget(compose->menubar);
6920
6921         if (compose->privacy_system != NULL) {
6922                 gchar *systemid;
6923
6924                 menuitem = gtk_item_factory_get_widget(ifactory, branch_path);
6925                 g_return_if_fail(menuitem != NULL);
6926
6927                 amenu = GTK_MENU_SHELL(menuitem)->children;
6928                 menuitem = NULL;
6929                 while (amenu != NULL) {
6930                         GList *alist = amenu->next;
6931
6932                         systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
6933                         if (systemid != NULL) {
6934                                 if (strcmp(systemid, compose->privacy_system) == 0) {
6935                                         menuitem = GTK_WIDGET(amenu->data);
6936
6937                                         can_sign = privacy_system_can_sign(systemid);
6938                                         can_encrypt = privacy_system_can_encrypt(systemid);
6939                                         found = TRUE;
6940                                         break;
6941                                 } 
6942                         } else if (strlen(compose->privacy_system) == 0) {
6943                                         menuitem = GTK_WIDGET(amenu->data);
6944
6945                                         can_sign = FALSE;
6946                                         can_encrypt = FALSE;
6947                                         found = TRUE;
6948                                         break;
6949                         }
6950
6951                         amenu = alist;
6952                 }
6953                 if (menuitem != NULL)
6954                         gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
6955                 
6956                 if (warn && !found && strlen(compose->privacy_system)) {
6957                         gchar *tmp = g_strdup_printf(
6958                                 _("The privacy system '%s' cannot be loaded. You "
6959                                   "will not be able to sign or encrypt this message."),
6960                                   compose->privacy_system);
6961                         alertpanel_warning(tmp);
6962                         g_free(tmp);
6963                 }
6964         } 
6965
6966         menu_set_sensitive(ifactory, "/Options/Sign", can_sign);
6967         menu_set_sensitive(ifactory, "/Options/Encrypt", can_encrypt);
6968 }       
6969  
6970 static void compose_set_out_encoding(Compose *compose)
6971 {
6972         GtkItemFactoryEntry *entry;
6973         GtkItemFactory *ifactory;
6974         CharSet out_encoding;
6975         gchar *path, *p, *q;
6976         GtkWidget *item;
6977
6978         out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
6979         ifactory = gtk_item_factory_from_widget(compose->menubar);
6980
6981         for (entry = compose_entries; entry->callback != compose_address_cb;
6982              entry++) {
6983                 if (entry->callback == compose_set_encoding_cb &&
6984                     (CharSet)entry->callback_action == out_encoding) {
6985                         p = q = path = g_strdup(entry->path);
6986                         while (*p) {
6987                                 if (*p == '_') {
6988                                         if (p[1] == '_') {
6989                                                 p++;
6990                                                 *q++ = '_';
6991                                         }
6992                                 } else
6993                                         *q++ = *p;
6994                                 p++;
6995                         }
6996                         *q = '\0';
6997                         item = gtk_item_factory_get_item(ifactory, path);
6998                         gtk_widget_activate(item);
6999                         g_free(path);
7000                         break;
7001                 }
7002         }
7003 }
7004
7005 static void compose_set_template_menu(Compose *compose)
7006 {
7007         GSList *tmpl_list, *cur;
7008         GtkWidget *menu;
7009         GtkWidget *item;
7010
7011         tmpl_list = template_get_config();
7012
7013         menu = gtk_menu_new();
7014
7015         for (cur = tmpl_list; cur != NULL; cur = cur->next) {
7016                 Template *tmpl = (Template *)cur->data;
7017
7018                 item = gtk_menu_item_new_with_label(tmpl->name);
7019                 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
7020                 g_signal_connect(G_OBJECT(item), "activate",
7021                                  G_CALLBACK(compose_template_activate_cb),
7022                                  compose);
7023                 g_object_set_data(G_OBJECT(item), "template", tmpl);
7024                 gtk_widget_show(item);
7025         }
7026
7027         gtk_widget_show(menu);
7028         gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
7029 }
7030
7031 void compose_update_actions_menu(Compose *compose)
7032 {
7033         GtkItemFactory *ifactory;
7034
7035         ifactory = gtk_item_factory_from_widget(compose->menubar);
7036         action_update_compose_menu(ifactory, "/Tools/Actions", compose);
7037 }
7038
7039 static void compose_update_privacy_systems_menu(Compose *compose)
7040 {
7041         static gchar *branch_path = "/Options/Privacy System";
7042         GtkItemFactory *ifactory;
7043         GtkWidget *menuitem;
7044         GSList *systems, *cur;
7045         GList *amenu;
7046         GtkWidget *widget;
7047         GtkWidget *system_none;
7048         GSList *group;
7049
7050         ifactory = gtk_item_factory_from_widget(compose->menubar);
7051
7052         /* remove old entries */
7053         menuitem = gtk_item_factory_get_widget(ifactory, branch_path);
7054         g_return_if_fail(menuitem != NULL);
7055
7056         amenu = GTK_MENU_SHELL(menuitem)->children->next;
7057         while (amenu != NULL) {
7058                 GList *alist = amenu->next;
7059                 gtk_widget_destroy(GTK_WIDGET(amenu->data));
7060                 amenu = alist;
7061         }
7062
7063         system_none = gtk_item_factory_get_widget(ifactory,
7064                 "/Options/Privacy System/None");
7065
7066         g_signal_connect(G_OBJECT(system_none), "activate",
7067                 G_CALLBACK(compose_set_privacy_system_cb), compose);
7068
7069         systems = privacy_get_system_ids();
7070         for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
7071                 gchar *systemid = cur->data;
7072
7073                 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
7074                 widget = gtk_radio_menu_item_new_with_label(group,
7075                         privacy_system_get_name(systemid));
7076                 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
7077                                        g_strdup(systemid), g_free);
7078                 g_signal_connect(G_OBJECT(widget), "activate",
7079                         G_CALLBACK(compose_set_privacy_system_cb), compose);
7080
7081                 gtk_menu_append(GTK_MENU(system_none->parent), widget);
7082                 gtk_widget_show(widget);
7083                 g_free(systemid);
7084         }
7085         g_slist_free(systems);
7086 }
7087
7088 void compose_reflect_prefs_all(void)
7089 {
7090         GList *cur;
7091         Compose *compose;
7092
7093         for (cur = compose_list; cur != NULL; cur = cur->next) {
7094                 compose = (Compose *)cur->data;
7095                 compose_set_template_menu(compose);
7096         }
7097 }
7098
7099 void compose_reflect_prefs_pixmap_theme(void)
7100 {
7101         GList *cur;
7102         Compose *compose;
7103
7104         for (cur = compose_list; cur != NULL; cur = cur->next) {
7105                 compose = (Compose *)cur->data;
7106                 toolbar_update(TOOLBAR_COMPOSE, compose);
7107         }
7108 }
7109
7110 static void compose_template_apply(Compose *compose, Template *tmpl,
7111                                    gboolean replace)
7112 {
7113         GtkTextView *text;
7114         GtkTextBuffer *buffer;
7115         GtkTextMark *mark;
7116         GtkTextIter iter;
7117         gchar *qmark;
7118         gchar *parsed_str = NULL;
7119         gint cursor_pos = 0;
7120         const gchar *err_msg = _("Template body format error at line %d.");
7121         if (!tmpl) return;
7122
7123         /* process the body */
7124
7125         text = GTK_TEXT_VIEW(compose->text);
7126         buffer = gtk_text_view_get_buffer(text);
7127
7128         if (tmpl->value) {
7129                 /* FIXME - use per-folder/account quotemark */
7130                 if (prefs_common.quotemark && *prefs_common.quotemark)
7131                         qmark = prefs_common.quotemark;
7132                 else
7133                         qmark = "> ";
7134
7135                 if (compose->replyinfo != NULL) {
7136
7137                         if (replace)
7138                                 gtk_text_buffer_set_text(buffer, "", -1);
7139                         mark = gtk_text_buffer_get_insert(buffer);
7140                         gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7141
7142                         parsed_str = compose_quote_fmt(compose, compose->replyinfo,
7143                                                    tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
7144
7145                 } else if (compose->fwdinfo != NULL) {
7146
7147                         if (replace)
7148                                 gtk_text_buffer_set_text(buffer, "", -1);
7149                         mark = gtk_text_buffer_get_insert(buffer);
7150                         gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7151
7152                         parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
7153                                                    tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
7154
7155                 } else {
7156                         MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
7157
7158                         GtkTextIter start, end;
7159                         gchar *tmp = NULL;
7160
7161                         gtk_text_buffer_get_start_iter(buffer, &start);
7162                         gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
7163                         tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
7164
7165                         /* clear the buffer now */
7166                         if (replace)
7167                                 gtk_text_buffer_set_text(buffer, "", -1);
7168
7169                         parsed_str = compose_quote_fmt(compose, dummyinfo,
7170                                                            tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
7171                         procmsg_msginfo_free( dummyinfo );
7172
7173                         g_free( tmp );
7174                 } 
7175         } else {
7176                 if (replace)
7177                         gtk_text_buffer_set_text(buffer, "", -1);
7178                 mark = gtk_text_buffer_get_insert(buffer);
7179                 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7180         }       
7181
7182         if (replace && parsed_str && compose->account->auto_sig)
7183                 compose_insert_sig(compose, FALSE);
7184
7185         if (replace && parsed_str) {
7186                 gtk_text_buffer_get_start_iter(buffer, &iter);
7187                 gtk_text_buffer_place_cursor(buffer, &iter);
7188         }
7189         
7190         if (parsed_str) {
7191                 cursor_pos = quote_fmt_get_cursor_pos();
7192                 compose->set_cursor_pos = cursor_pos;
7193                 if (cursor_pos == -1)
7194                         cursor_pos = 0;
7195                 gtk_text_buffer_get_start_iter(buffer, &iter);
7196                 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
7197                 gtk_text_buffer_place_cursor(buffer, &iter);
7198         }
7199
7200         /* process the other fields */
7201
7202         compose_template_apply_fields(compose, tmpl);
7203         quote_fmt_reset_vartable();
7204         compose_changed_cb(NULL, compose);
7205 }
7206
7207 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
7208 {
7209         MsgInfo* dummyinfo = NULL;
7210         MsgInfo *msginfo = NULL;
7211         gchar *buf = NULL;
7212
7213         if (compose->replyinfo != NULL)
7214                 msginfo = compose->replyinfo;
7215         else if (compose->fwdinfo != NULL)
7216                 msginfo = compose->fwdinfo;
7217         else {
7218                 dummyinfo = compose_msginfo_new_from_compose(compose);
7219                 msginfo = dummyinfo;
7220         }
7221
7222         if (tmpl->to && *tmpl->to != '\0') {
7223 #ifdef USE_ASPELL
7224                 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7225                                 compose->gtkaspell);
7226 #else
7227                 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7228 #endif
7229                 quote_fmt_scan_string(tmpl->to);
7230                 quote_fmt_parse();
7231
7232                 buf = quote_fmt_get_buffer();
7233                 if (buf == NULL) {
7234                         alertpanel_error(_("Template To format error."));
7235                 } else {
7236                         compose_entry_append(compose, buf, COMPOSE_TO);
7237                 }
7238         }
7239
7240         if (tmpl->cc && *tmpl->cc != '\0') {
7241 #ifdef USE_ASPELL
7242                 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7243                                 compose->gtkaspell);
7244 #else
7245                 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7246 #endif
7247                 quote_fmt_scan_string(tmpl->cc);
7248                 quote_fmt_parse();
7249
7250                 buf = quote_fmt_get_buffer();
7251                 if (buf == NULL) {
7252                         alertpanel_error(_("Template Cc format error."));
7253                 } else {
7254                         compose_entry_append(compose, buf, COMPOSE_CC);
7255                 }
7256         }
7257
7258         if (tmpl->bcc && *tmpl->bcc != '\0') {
7259 #ifdef USE_ASPELL
7260                 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7261                                 compose->gtkaspell);
7262 #else
7263                 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7264 #endif
7265                 quote_fmt_scan_string(tmpl->bcc);
7266                 quote_fmt_parse();
7267
7268                 buf = quote_fmt_get_buffer();
7269                 if (buf == NULL) {
7270                         alertpanel_error(_("Template Bcc format error."));
7271                 } else {
7272                         compose_entry_append(compose, buf, COMPOSE_BCC);
7273                 }
7274         }
7275
7276         /* process the subject */
7277         if (tmpl->subject && *tmpl->subject != '\0') {
7278 #ifdef USE_ASPELL
7279                 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7280                                 compose->gtkaspell);
7281 #else
7282                 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7283 #endif
7284                 quote_fmt_scan_string(tmpl->subject);
7285                 quote_fmt_parse();
7286
7287                 buf = quote_fmt_get_buffer();
7288                 if (buf == NULL) {
7289                         alertpanel_error(_("Template subject format error."));
7290                 } else {
7291                         gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
7292                 }
7293         }
7294
7295         procmsg_msginfo_free( dummyinfo );
7296 }
7297
7298 static void compose_destroy(Compose *compose)
7299 {
7300         GtkTextBuffer *buffer;
7301         GtkClipboard *clipboard;
7302
7303         compose_list = g_list_remove(compose_list, compose);
7304
7305         if (compose->updating) {
7306                 debug_print("danger, not destroying anything now\n");
7307                 compose->deferred_destroy = TRUE;
7308                 return;
7309         }
7310         /* NOTE: address_completion_end() does nothing with the window
7311          * however this may change. */
7312         address_completion_end(compose->window);
7313
7314         slist_free_strings(compose->to_list);
7315         g_slist_free(compose->to_list);
7316         slist_free_strings(compose->newsgroup_list);
7317         g_slist_free(compose->newsgroup_list);
7318         slist_free_strings(compose->header_list);
7319         g_slist_free(compose->header_list);
7320
7321         procmsg_msginfo_free(compose->targetinfo);
7322         procmsg_msginfo_free(compose->replyinfo);
7323         procmsg_msginfo_free(compose->fwdinfo);
7324
7325         g_free(compose->replyto);
7326         g_free(compose->cc);
7327         g_free(compose->bcc);
7328         g_free(compose->newsgroups);
7329         g_free(compose->followup_to);
7330
7331         g_free(compose->ml_post);
7332
7333         g_free(compose->inreplyto);
7334         g_free(compose->references);
7335         g_free(compose->msgid);
7336         g_free(compose->boundary);
7337
7338         g_free(compose->redirect_filename);
7339         if (compose->undostruct)
7340                 undo_destroy(compose->undostruct);
7341
7342         g_free(compose->sig_str);
7343
7344         g_free(compose->exteditor_file);
7345
7346         g_free(compose->orig_charset);
7347
7348         g_free(compose->privacy_system);
7349
7350         if (addressbook_get_target_compose() == compose)
7351                 addressbook_set_target_compose(NULL);
7352
7353 #if USE_ASPELL
7354         if (compose->gtkaspell) {
7355                 gtkaspell_delete(compose->gtkaspell);
7356                 compose->gtkaspell = NULL;
7357         }
7358 #endif
7359
7360         prefs_common.compose_width = compose->scrolledwin->allocation.width;
7361         prefs_common.compose_height = compose->window->allocation.height;
7362
7363         if (!gtk_widget_get_parent(compose->paned))
7364                 gtk_widget_destroy(compose->paned);
7365         gtk_widget_destroy(compose->popupmenu);
7366
7367         buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
7368         clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7369         gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
7370
7371         gtk_widget_destroy(compose->window);
7372         toolbar_destroy(compose->toolbar);
7373         g_free(compose->toolbar);
7374         g_mutex_free(compose->mutex);
7375         g_free(compose);
7376 }
7377
7378 static void compose_attach_info_free(AttachInfo *ainfo)
7379 {
7380         g_free(ainfo->file);
7381         g_free(ainfo->content_type);
7382         g_free(ainfo->name);
7383         g_free(ainfo);
7384 }
7385
7386 static void compose_attach_remove_selected(Compose *compose)
7387 {
7388         GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
7389         GtkTreeSelection *selection;
7390         GList *sel, *cur;
7391         GtkTreeModel *model;
7392
7393         selection = gtk_tree_view_get_selection(tree_view);
7394         sel = gtk_tree_selection_get_selected_rows(selection, &model);
7395
7396         if (!sel) 
7397                 return;
7398
7399         for (cur = sel; cur != NULL; cur = cur->next) {
7400                 GtkTreePath *path = cur->data;
7401                 GtkTreeRowReference *ref = gtk_tree_row_reference_new
7402                                                 (model, cur->data);
7403                 cur->data = ref;
7404                 gtk_tree_path_free(path);
7405         }
7406
7407         for (cur = sel; cur != NULL; cur = cur->next) {
7408                 GtkTreeRowReference *ref = cur->data;
7409                 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
7410                 GtkTreeIter iter;
7411
7412                 if (gtk_tree_model_get_iter(model, &iter, path))
7413                         gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
7414                 
7415                 gtk_tree_path_free(path);
7416                 gtk_tree_row_reference_free(ref);
7417         }
7418
7419         g_list_free(sel);
7420 }
7421
7422 static struct _AttachProperty
7423 {
7424         GtkWidget *window;
7425         GtkWidget *mimetype_entry;
7426         GtkWidget *encoding_optmenu;
7427         GtkWidget *path_entry;
7428         GtkWidget *filename_entry;
7429         GtkWidget *ok_btn;
7430         GtkWidget *cancel_btn;
7431 } attach_prop;
7432
7433 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
7434 {       
7435         gtk_tree_path_free((GtkTreePath *)ptr);
7436 }
7437
7438 static void compose_attach_property(Compose *compose)
7439 {
7440         GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
7441         AttachInfo *ainfo;
7442         GtkComboBox *optmenu;
7443         GtkTreeSelection *selection;
7444         GList *sel;
7445         GtkTreeModel *model;
7446         GtkTreeIter iter;
7447         GtkTreePath *path;
7448         static gboolean cancelled;
7449
7450         /* only if one selected */
7451         selection = gtk_tree_view_get_selection(tree_view);
7452         if (gtk_tree_selection_count_selected_rows(selection) != 1) 
7453                 return;
7454
7455         sel = gtk_tree_selection_get_selected_rows(selection, &model);
7456         if (!sel)
7457                 return;
7458
7459         path = (GtkTreePath *) sel->data;
7460         gtk_tree_model_get_iter(model, &iter, path);
7461         gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1); 
7462         
7463         if (!ainfo) {
7464                 g_list_foreach(sel, gtk_tree_path_free_, NULL);
7465                 g_list_free(sel);
7466                 return;
7467         }               
7468         g_list_free(sel);
7469
7470         if (!attach_prop.window)
7471                 compose_attach_property_create(&cancelled);
7472         gtk_widget_grab_focus(attach_prop.ok_btn);
7473         gtk_widget_show(attach_prop.window);
7474         manage_window_set_transient(GTK_WINDOW(attach_prop.window));
7475
7476         optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
7477         if (ainfo->encoding == ENC_UNKNOWN)
7478                 combobox_select_by_data(optmenu, ENC_BASE64);
7479         else
7480                 combobox_select_by_data(optmenu, ainfo->encoding);
7481
7482         gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
7483                            ainfo->content_type ? ainfo->content_type : "");
7484         gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
7485                            ainfo->file ? ainfo->file : "");
7486         gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
7487                            ainfo->name ? ainfo->name : "");
7488
7489         for (;;) {
7490                 const gchar *entry_text;
7491                 gchar *text;
7492                 gchar *cnttype = NULL;
7493                 gchar *file = NULL;
7494                 off_t size = 0;
7495
7496                 cancelled = FALSE;
7497                 gtk_main();
7498
7499                 gtk_widget_hide(attach_prop.window);
7500                 
7501                 if (cancelled) 
7502                         break;
7503
7504                 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
7505                 if (*entry_text != '\0') {
7506                         gchar *p;
7507
7508                         text = g_strstrip(g_strdup(entry_text));
7509                         if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
7510                                 cnttype = g_strdup(text);
7511                                 g_free(text);
7512                         } else {
7513                                 alertpanel_error(_("Invalid MIME type."));
7514                                 g_free(text);
7515                                 continue;
7516                         }
7517                 }
7518
7519                 ainfo->encoding = combobox_get_active_data(optmenu);
7520
7521                 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
7522                 if (*entry_text != '\0') {
7523                         if (is_file_exist(entry_text) &&
7524                             (size = get_file_size(entry_text)) > 0)
7525                                 file = g_strdup(entry_text);
7526                         else {
7527                                 alertpanel_error
7528                                         (_("File doesn't exist or is empty."));
7529                                 g_free(cnttype);
7530                                 continue;
7531                         }
7532                 }
7533
7534                 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
7535                 if (*entry_text != '\0') {
7536                         g_free(ainfo->name);
7537                         ainfo->name = g_strdup(entry_text);
7538                 }
7539
7540                 if (cnttype) {
7541                         g_free(ainfo->content_type);
7542                         ainfo->content_type = cnttype;
7543                 }
7544                 if (file) {
7545                         g_free(ainfo->file);
7546                         ainfo->file = file;
7547                 }
7548                 if (size)
7549                         ainfo->size = size;
7550
7551                 /* update tree store */
7552                 text = to_human_readable(ainfo->size);
7553                 gtk_tree_model_get_iter(model, &iter, path);
7554                 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
7555                                    COL_MIMETYPE, ainfo->content_type,
7556                                    COL_SIZE, text,
7557                                    COL_NAME, ainfo->name,
7558                                    -1);
7559                 
7560                 break;
7561         }
7562
7563         gtk_tree_path_free(path);
7564 }
7565
7566 #define SET_LABEL_AND_ENTRY(str, entry, top) \
7567 { \
7568         label = gtk_label_new(str); \
7569         gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
7570                          GTK_FILL, 0, 0, 0); \
7571         gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
7572  \
7573         entry = gtk_entry_new(); \
7574         gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
7575                          GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
7576 }
7577
7578 static void compose_attach_property_create(gboolean *cancelled)
7579 {
7580         GtkWidget *window;
7581         GtkWidget *vbox;
7582         GtkWidget *table;
7583         GtkWidget *label;
7584         GtkWidget *mimetype_entry;
7585         GtkWidget *hbox;
7586         GtkWidget *optmenu;
7587         GtkListStore *optmenu_menu;
7588         GtkWidget *path_entry;
7589         GtkWidget *filename_entry;
7590         GtkWidget *hbbox;
7591         GtkWidget *ok_btn;
7592         GtkWidget *cancel_btn;
7593         GList     *mime_type_list, *strlist;
7594         GtkTreeIter iter;
7595
7596         debug_print("Creating attach_property window...\n");
7597
7598         window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
7599         gtk_widget_set_size_request(window, 480, -1);
7600         gtk_container_set_border_width(GTK_CONTAINER(window), 8);
7601         gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
7602         gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
7603         gtk_window_set_modal(GTK_WINDOW(window), TRUE);
7604         g_signal_connect(G_OBJECT(window), "delete_event",
7605                          G_CALLBACK(attach_property_delete_event),
7606                          cancelled);
7607         g_signal_connect(G_OBJECT(window), "key_press_event",
7608                          G_CALLBACK(attach_property_key_pressed),
7609                          cancelled);
7610
7611         vbox = gtk_vbox_new(FALSE, 8);
7612         gtk_container_add(GTK_CONTAINER(window), vbox);
7613
7614         table = gtk_table_new(4, 2, FALSE);
7615         gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
7616         gtk_table_set_row_spacings(GTK_TABLE(table), 8);
7617         gtk_table_set_col_spacings(GTK_TABLE(table), 8);
7618
7619         label = gtk_label_new(_("MIME type")); 
7620         gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1), 
7621                          GTK_FILL, 0, 0, 0); 
7622         gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); 
7623         mimetype_entry = gtk_combo_new(); 
7624         gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1), 
7625                          GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
7626                          
7627         /* stuff with list */
7628         mime_type_list = procmime_get_mime_type_list();
7629         strlist = NULL;
7630         for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
7631                 MimeType *type = (MimeType *) mime_type_list->data;
7632                 gchar *tmp;
7633
7634                 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
7635
7636                 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
7637                         g_free(tmp);
7638                 else
7639                         strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
7640                                         (GCompareFunc)strcmp2);
7641         }
7642
7643         gtk_combo_set_popdown_strings(GTK_COMBO(mimetype_entry), strlist);
7644
7645         for (mime_type_list = strlist; mime_type_list != NULL; 
7646                 mime_type_list = mime_type_list->next)
7647                 g_free(mime_type_list->data);
7648         g_list_free(strlist);
7649                          
7650         mimetype_entry = GTK_COMBO(mimetype_entry)->entry;                       
7651
7652         label = gtk_label_new(_("Encoding"));
7653         gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
7654                          GTK_FILL, 0, 0, 0);
7655         gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
7656
7657         hbox = gtk_hbox_new(FALSE, 0);
7658         gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
7659                          GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
7660
7661         optmenu = gtkut_sc_combobox_create(NULL, TRUE);
7662         optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
7663
7664         COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
7665         COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
7666         COMBOBOX_ADD(optmenu_menu, "quoted-printable",  ENC_QUOTED_PRINTABLE);
7667         COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
7668         gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
7669
7670         gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
7671
7672         SET_LABEL_AND_ENTRY(_("Path"),      path_entry,     2);
7673         SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
7674
7675         gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
7676                                       &ok_btn, GTK_STOCK_OK,
7677                                       NULL, NULL);
7678         gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
7679         gtk_widget_grab_default(ok_btn);
7680
7681         g_signal_connect(G_OBJECT(ok_btn), "clicked",
7682                          G_CALLBACK(attach_property_ok),
7683                          cancelled);
7684         g_signal_connect(G_OBJECT(cancel_btn), "clicked",
7685                          G_CALLBACK(attach_property_cancel),
7686                          cancelled);
7687
7688         gtk_widget_show_all(vbox);
7689
7690         attach_prop.window           = window;
7691         attach_prop.mimetype_entry   = mimetype_entry;
7692         attach_prop.encoding_optmenu = optmenu;
7693         attach_prop.path_entry       = path_entry;
7694         attach_prop.filename_entry   = filename_entry;
7695         attach_prop.ok_btn           = ok_btn;
7696         attach_prop.cancel_btn       = cancel_btn;
7697 }
7698
7699 #undef SET_LABEL_AND_ENTRY
7700
7701 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
7702 {
7703         *cancelled = FALSE;
7704         gtk_main_quit();
7705 }
7706
7707 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
7708 {
7709         *cancelled = TRUE;
7710         gtk_main_quit();
7711 }
7712
7713 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
7714                                          gboolean *cancelled)
7715 {
7716         *cancelled = TRUE;
7717         gtk_main_quit();
7718
7719         return TRUE;
7720 }
7721
7722 static gboolean attach_property_key_pressed(GtkWidget *widget,
7723                                             GdkEventKey *event,
7724                                             gboolean *cancelled)
7725 {
7726         if (event && event->keyval == GDK_Escape) {
7727                 *cancelled = TRUE;
7728                 gtk_main_quit();
7729         }
7730         return FALSE;
7731 }
7732
7733 static void compose_exec_ext_editor(Compose *compose)
7734 {
7735 #ifdef G_OS_UNIX
7736         gchar *tmp;
7737         pid_t pid;
7738         gint pipe_fds[2];
7739
7740         tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
7741                               G_DIR_SEPARATOR, compose);
7742
7743         if (pipe(pipe_fds) < 0) {
7744                 perror("pipe");
7745                 g_free(tmp);
7746                 return;
7747         }
7748
7749         if ((pid = fork()) < 0) {
7750                 perror("fork");
7751                 g_free(tmp);
7752                 return;
7753         }
7754
7755         if (pid != 0) {
7756                 /* close the write side of the pipe */
7757                 close(pipe_fds[1]);
7758
7759                 compose->exteditor_file    = g_strdup(tmp);
7760                 compose->exteditor_pid     = pid;
7761
7762                 compose_set_ext_editor_sensitive(compose, FALSE);
7763
7764                 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
7765                 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
7766                                                         G_IO_IN,
7767                                                         compose_input_cb,
7768                                                         compose);
7769         } else {        /* process-monitoring process */
7770                 pid_t pid_ed;
7771
7772                 if (setpgid(0, 0))
7773                         perror("setpgid");
7774
7775                 /* close the read side of the pipe */
7776                 close(pipe_fds[0]);
7777
7778                 if (compose_write_body_to_file(compose, tmp) < 0) {
7779                         fd_write_all(pipe_fds[1], "2\n", 2);
7780                         _exit(1);
7781                 }
7782
7783                 pid_ed = compose_exec_ext_editor_real(tmp);
7784                 if (pid_ed < 0) {
7785                         fd_write_all(pipe_fds[1], "1\n", 2);
7786                         _exit(1);
7787                 }
7788
7789                 /* wait until editor is terminated */
7790                 waitpid(pid_ed, NULL, 0);
7791
7792                 fd_write_all(pipe_fds[1], "0\n", 2);
7793
7794                 close(pipe_fds[1]);
7795                 _exit(0);
7796         }
7797
7798         g_free(tmp);
7799 #endif /* G_OS_UNIX */
7800 }
7801
7802 #ifdef G_OS_UNIX
7803 static gint compose_exec_ext_editor_real(const gchar *file)
7804 {
7805         gchar buf[1024];
7806         gchar *p;
7807         gchar **cmdline;
7808         pid_t pid;
7809
7810         g_return_val_if_fail(file != NULL, -1);
7811
7812         if ((pid = fork()) < 0) {
7813                 perror("fork");
7814                 return -1;
7815         }
7816
7817         if (pid != 0) return pid;
7818
7819         /* grandchild process */
7820
7821         if (setpgid(0, getppid()))
7822                 perror("setpgid");
7823
7824         if (prefs_common.ext_editor_cmd &&
7825             (p = strchr(prefs_common.ext_editor_cmd, '%')) &&
7826             *(p + 1) == 's' && !strchr(p + 2, '%')) {
7827                 g_snprintf(buf, sizeof(buf), prefs_common.ext_editor_cmd, file);
7828         } else {
7829                 if (prefs_common.ext_editor_cmd)
7830                         g_warning("External editor command line is invalid: '%s'\n",
7831                                   prefs_common.ext_editor_cmd);
7832                 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
7833         }
7834
7835         cmdline = strsplit_with_quote(buf, " ", 1024);
7836         execvp(cmdline[0], cmdline);
7837
7838         perror("execvp");
7839         g_strfreev(cmdline);
7840
7841         _exit(1);
7842 }
7843
7844 static gboolean compose_ext_editor_kill(Compose *compose)
7845 {
7846         pid_t pgid = compose->exteditor_pid * -1;
7847         gint ret;
7848
7849         ret = kill(pgid, 0);
7850
7851         if (ret == 0 || (ret == -1 && EPERM == errno)) {
7852                 AlertValue val;
7853                 gchar *msg;
7854
7855                 msg = g_strdup_printf
7856                         (_("The external editor is still working.\n"
7857                            "Force terminating the process?\n"
7858                            "process group id: %d"), -pgid);
7859                 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
7860                                       NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
7861                         
7862                 g_free(msg);
7863
7864                 if (val == G_ALERTALTERNATE) {
7865                         g_source_remove(compose->exteditor_tag);
7866                         g_io_channel_shutdown(compose->exteditor_ch,
7867                                               FALSE, NULL);
7868                         g_io_channel_unref(compose->exteditor_ch);
7869
7870                         if (kill(pgid, SIGTERM) < 0) perror("kill");
7871                         waitpid(compose->exteditor_pid, NULL, 0);
7872
7873                         g_warning("Terminated process group id: %d", -pgid);
7874                         g_warning("Temporary file: %s",
7875                                   compose->exteditor_file);
7876
7877                         compose_set_ext_editor_sensitive(compose, TRUE);
7878
7879                         g_free(compose->exteditor_file);
7880                         compose->exteditor_file    = NULL;
7881                         compose->exteditor_pid     = -1;
7882                         compose->exteditor_ch      = NULL;
7883                         compose->exteditor_tag     = -1;
7884                 } else
7885                         return FALSE;
7886         }
7887
7888         return TRUE;
7889 }
7890
7891 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
7892                                  gpointer data)
7893 {
7894         gchar buf[3] = "3";
7895         Compose *compose = (Compose *)data;
7896         gsize bytes_read;
7897
7898         debug_print(_("Compose: input from monitoring process\n"));
7899
7900         g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL);
7901
7902         g_io_channel_shutdown(source, FALSE, NULL);
7903         g_io_channel_unref(source);
7904
7905         waitpid(compose->exteditor_pid, NULL, 0);
7906
7907         if (buf[0] == '0') {            /* success */
7908                 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
7909                 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
7910
7911                 gtk_text_buffer_set_text(buffer, "", -1);
7912                 compose_insert_file(compose, compose->exteditor_file);
7913                 compose_changed_cb(NULL, compose);
7914
7915                 if (g_unlink(compose->exteditor_file) < 0)
7916                         FILE_OP_ERROR(compose->exteditor_file, "unlink");
7917         } else if (buf[0] == '1') {     /* failed */
7918                 g_warning("Couldn't exec external editor\n");
7919                 if (g_unlink(compose->exteditor_file) < 0)
7920                         FILE_OP_ERROR(compose->exteditor_file, "unlink");
7921         } else if (buf[0] == '2') {
7922                 g_warning("Couldn't write to file\n");
7923         } else if (buf[0] == '3') {
7924                 g_warning("Pipe read failed\n");
7925         }
7926
7927         compose_set_ext_editor_sensitive(compose, TRUE);
7928
7929         g_free(compose->exteditor_file);
7930         compose->exteditor_file    = NULL;
7931         compose->exteditor_pid     = -1;
7932         compose->exteditor_ch      = NULL;
7933         compose->exteditor_tag     = -1;
7934
7935         return FALSE;
7936 }
7937
7938 static void compose_set_ext_editor_sensitive(Compose *compose,
7939                                              gboolean sensitive)
7940 {
7941         GtkItemFactory *ifactory;
7942
7943         ifactory = gtk_item_factory_from_widget(compose->menubar);
7944
7945         menu_set_sensitive(ifactory, "/Message/Send", sensitive);
7946         menu_set_sensitive(ifactory, "/Message/Send later", sensitive);
7947         menu_set_sensitive(ifactory, "/Message/Insert file", sensitive);
7948         menu_set_sensitive(ifactory, "/Message/Insert signature", sensitive);
7949         menu_set_sensitive(ifactory, "/Edit/Wrap current paragraph", sensitive);
7950         menu_set_sensitive(ifactory, "/Edit/Wrap all long lines", sensitive);
7951         menu_set_sensitive(ifactory, "/Edit/Edit with external editor",
7952                            sensitive);
7953
7954         gtk_widget_set_sensitive(compose->text,                       sensitive);
7955         if (compose->toolbar->send_btn)
7956                 gtk_widget_set_sensitive(compose->toolbar->send_btn,      sensitive);
7957         if (compose->toolbar->sendl_btn)
7958                 gtk_widget_set_sensitive(compose->toolbar->sendl_btn,     sensitive);
7959         if (compose->toolbar->draft_btn)
7960                 gtk_widget_set_sensitive(compose->toolbar->draft_btn,     sensitive);
7961         if (compose->toolbar->insert_btn)
7962                 gtk_widget_set_sensitive(compose->toolbar->insert_btn,    sensitive);
7963         if (compose->toolbar->sig_btn)
7964                 gtk_widget_set_sensitive(compose->toolbar->sig_btn,       sensitive);
7965         if (compose->toolbar->exteditor_btn)
7966                 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
7967         if (compose->toolbar->linewrap_current_btn)
7968                 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
7969         if (compose->toolbar->linewrap_all_btn)
7970                 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
7971 }
7972 #endif /* G_OS_UNIX */
7973
7974 /**
7975  * compose_undo_state_changed:
7976  *
7977  * Change the sensivity of the menuentries undo and redo
7978  **/
7979 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
7980                                        gint redo_state, gpointer data)
7981 {
7982         GtkWidget *widget = GTK_WIDGET(data);
7983         GtkItemFactory *ifactory;
7984
7985         g_return_if_fail(widget != NULL);
7986
7987         ifactory = gtk_item_factory_from_widget(widget);
7988
7989         switch (undo_state) {
7990         case UNDO_STATE_TRUE:
7991                 if (!undostruct->undo_state) {
7992                         undostruct->undo_state = TRUE;
7993                         menu_set_sensitive(ifactory, "/Edit/Undo", TRUE);
7994                 }
7995                 break;
7996         case UNDO_STATE_FALSE:
7997                 if (undostruct->undo_state) {
7998                         undostruct->undo_state = FALSE;
7999                         menu_set_sensitive(ifactory, "/Edit/Undo", FALSE);
8000                 }
8001                 break;
8002         case UNDO_STATE_UNCHANGED:
8003                 break;
8004         case UNDO_STATE_REFRESH:
8005                 menu_set_sensitive(ifactory, "/Edit/Undo",
8006                                    undostruct->undo_state);
8007                 break;
8008         default:
8009                 g_warning("Undo state not recognized");
8010                 break;
8011         }
8012
8013         switch (redo_state) {
8014         case UNDO_STATE_TRUE:
8015                 if (!undostruct->redo_state) {
8016                         undostruct->redo_state = TRUE;
8017                         menu_set_sensitive(ifactory, "/Edit/Redo", TRUE);
8018                 }
8019                 break;
8020         case UNDO_STATE_FALSE:
8021                 if (undostruct->redo_state) {
8022                         undostruct->redo_state = FALSE;
8023                         menu_set_sensitive(ifactory, "/Edit/Redo", FALSE);
8024                 }
8025                 break;
8026         case UNDO_STATE_UNCHANGED:
8027                 break;
8028         case UNDO_STATE_REFRESH:
8029                 menu_set_sensitive(ifactory, "/Edit/Redo",
8030                                    undostruct->redo_state);
8031                 break;
8032         default:
8033                 g_warning("Redo state not recognized");
8034                 break;
8035         }
8036 }
8037
8038 /* callback functions */
8039
8040 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
8041  * includes "non-client" (windows-izm) in calculation, so this calculation
8042  * may not be accurate.
8043  */
8044 static gboolean compose_edit_size_alloc(GtkEditable *widget,
8045                                         GtkAllocation *allocation,
8046                                         GtkSHRuler *shruler)
8047 {
8048         if (prefs_common.show_ruler) {
8049                 gint char_width = 0, char_height = 0;
8050                 gint line_width_in_chars;
8051
8052                 gtkut_get_font_size(GTK_WIDGET(widget),
8053                                     &char_width, &char_height);
8054                 line_width_in_chars =
8055                         (allocation->width - allocation->x) / char_width;
8056
8057                 /* got the maximum */
8058                 gtk_ruler_set_range(GTK_RULER(shruler),
8059                                     0.0, line_width_in_chars, 0,
8060                                     /*line_width_in_chars*/ char_width);
8061         }
8062
8063         return TRUE;
8064 }
8065
8066 static void account_activated(GtkComboBox *optmenu, gpointer data)
8067 {
8068         Compose *compose = (Compose *)data;
8069
8070         PrefsAccount *ac;
8071         gchar *folderidentifier;
8072         gint account_id = 0;
8073         GtkTreeModel *menu;
8074         GtkTreeIter iter;
8075
8076         /* Get ID of active account in the combo box */
8077         menu = gtk_combo_box_get_model(optmenu);
8078         gtk_combo_box_get_active_iter(optmenu, &iter);
8079         gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
8080
8081         ac = account_find_from_id(account_id);
8082         g_return_if_fail(ac != NULL);
8083
8084         if (ac != compose->account)
8085                 compose_select_account(compose, ac, FALSE);
8086
8087         /* Set message save folder */
8088         if (account_get_special_folder(compose->account, F_OUTBOX)) {
8089                 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
8090         }
8091         g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
8092                          G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
8093                            
8094         gtk_editable_delete_text(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
8095         if (account_get_special_folder(compose->account, F_OUTBOX)) {
8096                 folderidentifier = folder_item_get_identifier(account_get_special_folder
8097                                   (compose->account, F_OUTBOX));
8098                 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
8099                 g_free(folderidentifier);
8100         }
8101 }
8102
8103 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
8104                             GtkTreeViewColumn *column, Compose *compose)
8105 {
8106         compose_attach_property(compose);
8107 }
8108
8109 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
8110                                       gpointer data)
8111 {
8112         Compose *compose = (Compose *)data;
8113         GtkTreeSelection *attach_selection;
8114         gint attach_nr_selected;
8115         GtkItemFactory *ifactory;
8116         
8117         if (!event) return FALSE;
8118
8119         if (event->button == 3) {
8120                 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
8121                 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
8122                 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
8123                         
8124                 if (attach_nr_selected > 0)
8125                 {
8126                         menu_set_sensitive(ifactory, "/Remove", TRUE);
8127                         menu_set_sensitive(ifactory, "/Properties...", TRUE);
8128                 } else {
8129                         menu_set_sensitive(ifactory, "/Remove", FALSE);
8130                         menu_set_sensitive(ifactory, "/Properties...", FALSE);
8131                 }
8132                         
8133                 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
8134                                NULL, NULL, event->button, event->time);
8135                 return TRUE;                           
8136         }
8137
8138         return FALSE;
8139 }
8140
8141 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
8142                                    gpointer data)
8143 {
8144         Compose *compose = (Compose *)data;
8145
8146         if (!event) return FALSE;
8147
8148         switch (event->keyval) {
8149         case GDK_Delete:
8150                 compose_attach_remove_selected(compose);
8151                 break;
8152         }
8153         return FALSE;
8154 }
8155
8156 static void compose_allow_user_actions (Compose *compose, gboolean allow)
8157 {
8158         GtkItemFactory *ifactory = gtk_item_factory_from_widget(compose->menubar);
8159         toolbar_comp_set_sensitive(compose, allow);
8160         menu_set_sensitive(ifactory, "/Message", allow);
8161         menu_set_sensitive(ifactory, "/Edit", allow);
8162 #if USE_ASPELL
8163         menu_set_sensitive(ifactory, "/Spelling", allow);
8164 #endif  
8165         menu_set_sensitive(ifactory, "/Options", allow);
8166         menu_set_sensitive(ifactory, "/Tools", allow);
8167         menu_set_sensitive(ifactory, "/Help", allow);
8168         
8169         gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
8170
8171 }
8172
8173 static void compose_send_cb(gpointer data, guint action, GtkWidget *widget)
8174 {
8175         Compose *compose = (Compose *)data;
8176         
8177         if (prefs_common.work_offline && 
8178             !inc_offline_should_override(TRUE,
8179                 _("Claws Mail needs network access in order "
8180                   "to send this email.")))
8181                 return;
8182         
8183         if (compose->draft_timeout_tag != -1) { /* CLAWS: disable draft timeout */
8184                 g_source_remove(compose->draft_timeout_tag);
8185                 compose->draft_timeout_tag = -1;
8186         }
8187
8188         compose_send(compose);
8189 }
8190
8191 static void compose_send_later_cb(gpointer data, guint action,
8192                                   GtkWidget *widget)
8193 {
8194         Compose *compose = (Compose *)data;
8195         gint val;
8196
8197         inc_lock();
8198         val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
8199         inc_unlock();
8200
8201         if (!val) {
8202                 compose_close(compose);
8203         } else if (val == -1) {
8204                 alertpanel_error(_("Could not queue message."));
8205         } else if (val == -2) {
8206                 alertpanel_error(_("Could not queue message:\n\n%s."), strerror(errno));
8207         } else if (val == -3) {
8208                 if (privacy_peek_error())
8209                 alertpanel_error(_("Could not queue message for sending:\n\n"
8210                                    "Signature failed: %s"), privacy_get_error());
8211         } else if (val == -4) {
8212                 alertpanel_error(_("Could not queue message for sending:\n\n"
8213                                    "Charset conversion failed."));
8214         } else if (val == -5) {
8215                 alertpanel_error(_("Could not queue message for sending:\n\n"
8216                                    "Couldn't get recipient encryption key."));
8217         } else if (val == -6) {
8218                 /* silent error */
8219         }
8220         toolbar_main_set_sensitive(mainwindow_get_mainwindow());
8221 }
8222
8223 void compose_draft (gpointer data, guint action) 
8224 {
8225         compose_draft_cb(data, action, NULL);   
8226 }
8227
8228 #define DRAFTED_AT_EXIT "drafted_at_exit"
8229 void compose_clear_exit_drafts(void)
8230 {
8231         gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8232                                       DRAFTED_AT_EXIT, NULL);
8233         if (is_file_exist(filepath))
8234                 g_unlink(filepath);
8235         
8236         g_free(filepath);
8237 }
8238
8239 static void compose_register_draft(MsgInfo *info)
8240 {
8241         gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8242                                       DRAFTED_AT_EXIT, NULL);
8243         FILE *fp = fopen(filepath, "ab");
8244         
8245         if (fp) {
8246                 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder), 
8247                                 info->msgnum);
8248                 fclose(fp);
8249         }
8250                 
8251         g_free(filepath);       
8252 }
8253
8254 void compose_reopen_exit_drafts(void)
8255 {
8256         gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8257                                       DRAFTED_AT_EXIT, NULL);
8258         FILE *fp = fopen(filepath, "rb");
8259         gchar buf[1024];
8260         
8261         if (fp) {
8262                 while (fgets(buf, sizeof(buf), fp)) {
8263                         gchar **parts = g_strsplit(buf, "\t", 2);
8264                         const gchar *folder = parts[0];
8265                         int msgnum = parts[1] ? atoi(parts[1]):-1;
8266                         
8267                         if (folder && *folder && msgnum > -1) {
8268                                 FolderItem *item = folder_find_item_from_identifier(folder);
8269                                 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
8270                                 if (info)
8271                                         compose_reedit(info, FALSE);
8272                         }
8273                         g_strfreev(parts);
8274                 }       
8275                 fclose(fp);
8276         }       
8277         g_free(filepath);
8278         compose_clear_exit_drafts();
8279 }
8280
8281 static void compose_draft_cb(gpointer data, guint action, GtkWidget *widget)
8282 {
8283         Compose *compose = (Compose *)data;
8284         FolderItem *draft;
8285         gchar *tmp;
8286         gint msgnum;
8287         MsgFlags flag = {0, 0};
8288         static gboolean lock = FALSE;
8289         MsgInfo *newmsginfo;
8290         FILE *fp;
8291         gboolean target_locked = FALSE;
8292         
8293         if (lock) return;
8294
8295         draft = account_get_special_folder(compose->account, F_DRAFT);
8296         g_return_if_fail(draft != NULL);
8297         
8298         if (!g_mutex_trylock(compose->mutex)) {
8299                 /* we don't want to lock the mutex once it's available,
8300                  * because as the only other part of compose.c locking
8301                  * it is compose_close - which means once unlocked,
8302                  * the compose struct will be freed */
8303                 debug_print("couldn't lock mutex, probably sending\n");
8304                 return;
8305         }
8306         
8307         lock = TRUE;
8308
8309         tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
8310                               G_DIR_SEPARATOR, compose);
8311         if ((fp = g_fopen(tmp, "wb")) == NULL) {
8312                 FILE_OP_ERROR(tmp, "fopen");
8313                 goto unlock;
8314         }
8315
8316         /* chmod for security */
8317         if (change_file_mode_rw(fp, tmp) < 0) {
8318                 FILE_OP_ERROR(tmp, "chmod");
8319                 g_warning("can't change file mode\n");
8320         }
8321
8322         /* Save draft infos */
8323         fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id);
8324         fprintf(fp, "S:%s\n", compose->account->address);
8325
8326         if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
8327                 gchar *savefolderid;
8328
8329                 savefolderid = gtk_editable_get_chars(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
8330                 fprintf(fp, "SCF:%s\n", savefolderid);
8331                 g_free(savefolderid);
8332         }
8333         if (compose->return_receipt) {
8334                 fprintf(fp, "RRCPT:1\n");
8335         }
8336         if (compose->privacy_system) {
8337                 fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing);
8338                 fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption);
8339                 fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system);
8340         }
8341
8342         /* Message-ID of message replying to */
8343         if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
8344                 gchar *folderid;
8345                 
8346                 folderid = folder_item_get_identifier(compose->replyinfo->folder);
8347                 fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid);
8348                 g_free(folderid);
8349         }
8350         /* Message-ID of message forwarding to */
8351         if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
8352                 gchar *folderid;
8353                 
8354                 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
8355                 fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid);
8356                 g_free(folderid);
8357         }
8358
8359         /* end of headers */
8360         fprintf(fp, "X-Claws-End-Special-Headers: 1\n");
8361
8362         if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
8363                 fclose(fp);
8364                 g_unlink(tmp);
8365                 g_free(tmp);
8366                 goto unlock;
8367         }
8368         fclose(fp);
8369         
8370         if (compose->targetinfo) {
8371                 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
8372                 flag.perm_flags = target_locked?MSG_LOCKED:0;
8373         }
8374         flag.tmp_flags = MSG_DRAFT;
8375
8376         folder_item_scan(draft);
8377         if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
8378                 g_unlink(tmp);
8379                 g_free(tmp);
8380                 if (action != COMPOSE_AUTO_SAVE)
8381                         alertpanel_error(_("Could not save draft."));
8382                 goto unlock;
8383         }
8384         g_free(tmp);
8385
8386         if (compose->mode == COMPOSE_REEDIT) {
8387                 compose_remove_reedit_target(compose, TRUE);
8388         }
8389
8390         newmsginfo = folder_item_get_msginfo(draft, msgnum);
8391         if (newmsginfo) {
8392                 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
8393                 if (target_locked)
8394                         procmsg_msginfo_set_flags(newmsginfo, MSG_LOCKED, MSG_DRAFT);
8395                 else
8396                         procmsg_msginfo_set_flags(newmsginfo, 0, MSG_DRAFT);
8397                 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
8398                         procmsg_msginfo_set_flags(newmsginfo, 0,
8399                                                   MSG_HAS_ATTACHMENT);
8400
8401                 if (action == COMPOSE_DRAFT_FOR_EXIT) {
8402                         compose_register_draft(newmsginfo);
8403                 }
8404                 procmsg_msginfo_free(newmsginfo);
8405         }
8406         
8407         folder_item_scan(draft);
8408         
8409         if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
8410                 lock = FALSE;
8411                 g_mutex_unlock(compose->mutex); /* must be done before closing */
8412                 compose_close(compose);
8413                 return;
8414         } else {
8415                 struct stat s;
8416                 gchar *path;
8417
8418                 path = folder_item_fetch_msg(draft, msgnum);
8419                 if (path == NULL) {
8420                         debug_print("can't fetch %s:%d\n",draft->path, msgnum);
8421                         goto unlock;
8422                 }
8423                 if (g_stat(path, &s) < 0) {
8424                         FILE_OP_ERROR(path, "stat");
8425                         g_free(path);
8426                         goto unlock;
8427                 }
8428                 g_free(path);
8429
8430                 procmsg_msginfo_free(compose->targetinfo);
8431                 compose->targetinfo = procmsg_msginfo_new();
8432                 compose->targetinfo->msgnum = msgnum;
8433                 compose->targetinfo->size = s.st_size;
8434                 compose->targetinfo->mtime = s.st_mtime;
8435                 compose->targetinfo->folder = draft;
8436                 if (target_locked)
8437                         procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
8438                 compose->mode = COMPOSE_REEDIT;
8439                 
8440                 if (action == COMPOSE_AUTO_SAVE) {
8441                         compose->autosaved_draft = compose->targetinfo;
8442                 }
8443                 compose->modified = FALSE;
8444                 compose_set_title(compose);
8445         }
8446 unlock:
8447         lock = FALSE;
8448         g_mutex_unlock(compose->mutex);
8449 }
8450
8451 static void compose_attach_cb(gpointer data, guint action, GtkWidget *widget)
8452 {
8453         Compose *compose = (Compose *)data;
8454         GList *file_list;
8455
8456         if (compose->redirect_filename != NULL)
8457                 return;
8458
8459         file_list = filesel_select_multiple_files_open(_("Select file"));
8460
8461         if (file_list) {
8462                 GList *tmp;
8463
8464                 for ( tmp = file_list; tmp; tmp = tmp->next) {
8465                         gchar *file = (gchar *) tmp->data;
8466                         gchar *utf8_filename = conv_filename_to_utf8(file);
8467                         compose_attach_append(compose, file, utf8_filename, NULL);
8468                         compose_changed_cb(NULL, compose);
8469                         g_free(file);
8470                         g_free(utf8_filename);
8471                 }
8472                 g_list_free(file_list);
8473         }               
8474 }
8475
8476 static void compose_insert_file_cb(gpointer data, guint action,
8477                                    GtkWidget *widget)
8478 {
8479         Compose *compose = (Compose *)data;
8480         GList *file_list;
8481
8482         file_list = filesel_select_multiple_files_open(_("Select file"));
8483
8484         if (file_list) {
8485                 GList *tmp;
8486
8487                 for ( tmp = file_list; tmp; tmp = tmp->next) {
8488                         gchar *file = (gchar *) tmp->data;
8489                         gchar *filedup = g_strdup(file);
8490                         gchar *shortfile = g_path_get_basename(filedup);
8491                         ComposeInsertResult res;
8492
8493                         res = compose_insert_file(compose, file);
8494                         if (res == COMPOSE_INSERT_READ_ERROR) {
8495                                 alertpanel_error(_("File '%s' could not be read."), shortfile);
8496                         } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
8497                                 alertpanel_error(_("File '%s' contained invalid characters\n"
8498                                                    "for the current encoding, insertion may be incorrect."), shortfile);
8499                         }
8500                         g_free(shortfile);
8501                         g_free(filedup);
8502                         g_free(file);
8503                 }
8504                 g_list_free(file_list);
8505         }
8506 }
8507
8508 static void compose_insert_sig_cb(gpointer data, guint action,
8509                                   GtkWidget *widget)
8510 {
8511         Compose *compose = (Compose *)data;
8512
8513         compose_insert_sig(compose, FALSE);
8514 }
8515
8516 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
8517                               gpointer data)
8518 {
8519         gint x, y;
8520         Compose *compose = (Compose *)data;
8521
8522         gtkut_widget_get_uposition(widget, &x, &y);
8523         prefs_common.compose_x = x;
8524         prefs_common.compose_y = y;
8525
8526         if (compose->sending || compose->updating)
8527                 return TRUE;
8528         compose_close_cb(compose, 0, NULL);
8529         return TRUE;
8530 }
8531
8532 void compose_close_toolbar(Compose *compose)
8533 {
8534         compose_close_cb(compose, 0, NULL);
8535 }
8536
8537 static void compose_close_cb(gpointer data, guint action, GtkWidget *widget)
8538 {
8539         Compose *compose = (Compose *)data;
8540         AlertValue val;
8541
8542 #ifdef G_OS_UNIX
8543         if (compose->exteditor_tag != -1) {
8544                 if (!compose_ext_editor_kill(compose))
8545                         return;
8546         }
8547 #endif
8548
8549         if (compose->modified) {
8550                 val = alertpanel(_("Discard message"),
8551                                  _("This message has been modified. Discard it?"),
8552                                  _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
8553
8554                 switch (val) {
8555                 case G_ALERTDEFAULT:
8556                         if (prefs_common.autosave)
8557                                 compose_remove_draft(compose);                  
8558                         break;
8559                 case G_ALERTALTERNATE:
8560                         compose_draft_cb(data, COMPOSE_QUIT_EDITING, NULL);
8561                         return;
8562                 default:
8563                         return;
8564                 }
8565         }
8566
8567         compose_close(compose);
8568 }
8569
8570 static void compose_set_encoding_cb(gpointer data, guint action,
8571                                     GtkWidget *widget)
8572 {
8573         Compose *compose = (Compose *)data;
8574
8575         if (GTK_CHECK_MENU_ITEM(widget)->active)
8576                 compose->out_encoding = (CharSet)action;
8577 }
8578
8579 static void compose_address_cb(gpointer data, guint action, GtkWidget *widget)
8580 {
8581         Compose *compose = (Compose *)data;
8582
8583         addressbook_open(compose);
8584 }
8585
8586 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
8587 {
8588         Compose *compose = (Compose *)data;
8589         Template *tmpl;
8590         gchar *msg;
8591         AlertValue val;
8592
8593         tmpl = g_object_get_data(G_OBJECT(widget), "template");
8594         g_return_if_fail(tmpl != NULL);
8595
8596         msg = g_strdup_printf(_("Do you want to apply the template '%s' ?"),
8597                               tmpl->name);
8598         val = alertpanel(_("Apply template"), msg,
8599                          _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
8600         g_free(msg);
8601
8602         if (val == G_ALERTDEFAULT)
8603                 compose_template_apply(compose, tmpl, TRUE);
8604         else if (val == G_ALERTALTERNATE)
8605                 compose_template_apply(compose, tmpl, FALSE);
8606 }
8607
8608 static void compose_ext_editor_cb(gpointer data, guint action,
8609                                   GtkWidget *widget)
8610 {
8611         Compose *compose = (Compose *)data;
8612
8613         compose_exec_ext_editor(compose);
8614 }
8615
8616 static void compose_undo_cb(Compose *compose)
8617 {
8618         gboolean prev_autowrap = compose->autowrap;
8619
8620         compose->autowrap = FALSE;
8621         undo_undo(compose->undostruct);
8622         compose->autowrap = prev_autowrap;
8623 }
8624
8625 static void compose_redo_cb(Compose *compose)
8626 {
8627         gboolean prev_autowrap = compose->autowrap;
8628         
8629         compose->autowrap = FALSE;
8630         undo_redo(compose->undostruct);
8631         compose->autowrap = prev_autowrap;
8632 }
8633
8634 static void entry_cut_clipboard(GtkWidget *entry)
8635 {
8636         if (GTK_IS_EDITABLE(entry))
8637                 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
8638         else if (GTK_IS_TEXT_VIEW(entry))
8639                 gtk_text_buffer_cut_clipboard(
8640                         gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
8641                         gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
8642                         TRUE);
8643 }
8644
8645 static void entry_copy_clipboard(GtkWidget *entry)
8646 {
8647         if (GTK_IS_EDITABLE(entry))
8648                 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
8649         else if (GTK_IS_TEXT_VIEW(entry))
8650                 gtk_text_buffer_copy_clipboard(
8651                         gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
8652                         gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
8653 }
8654
8655 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, 
8656                                   gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
8657 {
8658         if (GTK_IS_TEXT_VIEW(entry)) {
8659                 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
8660                 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
8661                 GtkTextIter start_iter, end_iter;
8662                 gint start, end;
8663                 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
8664
8665                 if (contents == NULL)
8666                         return;
8667
8668                 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
8669
8670                 /* we shouldn't delete the selection when middle-click-pasting, or we
8671                  * can't mid-click-paste our own selection */
8672                 if (clip != GDK_SELECTION_PRIMARY) {
8673                         gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
8674                 }
8675                 
8676                 if (insert_place == NULL) {
8677                         /* if insert_place isn't specified, insert at the cursor.
8678                          * used for Ctrl-V pasting */
8679                         gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
8680                         start = gtk_text_iter_get_offset(&start_iter);
8681                         gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
8682                 } else {
8683                         /* if insert_place is specified, paste here.
8684                          * used for mid-click-pasting */
8685                         start = gtk_text_iter_get_offset(insert_place);
8686                         gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
8687                 }
8688                 
8689                 if (!wrap) {
8690                         /* paste unwrapped: mark the paste so it's not wrapped later */
8691                         end = start + strlen(contents);
8692                         gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
8693                         gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
8694                         gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
8695                 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
8696                         /* rewrap paragraph now (after a mid-click-paste) */
8697                         mark_start = gtk_text_buffer_get_insert(buffer);
8698                         gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
8699                         gtk_text_iter_backward_char(&start_iter);
8700                         compose_beautify_paragraph(compose, &start_iter, TRUE);
8701                 }
8702         } else if (GTK_IS_EDITABLE(entry))
8703                 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
8704         
8705 }
8706
8707 static void entry_allsel(GtkWidget *entry)
8708 {
8709         if (GTK_IS_EDITABLE(entry))
8710                 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
8711         else if (GTK_IS_TEXT_VIEW(entry)) {
8712                 GtkTextIter startiter, enditer;
8713                 GtkTextBuffer *textbuf;
8714
8715                 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
8716                 gtk_text_buffer_get_start_iter(textbuf, &startiter);
8717                 gtk_text_buffer_get_end_iter(textbuf, &enditer);
8718
8719                 gtk_text_buffer_move_mark_by_name(textbuf, 
8720                         "selection_bound", &startiter);
8721                 gtk_text_buffer_move_mark_by_name(textbuf, 
8722                         "insert", &enditer);
8723         }
8724 }
8725
8726 static void compose_cut_cb(Compose *compose)
8727 {
8728         if (compose->focused_editable 
8729 #ifndef MAEMO
8730             && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
8731 #endif
8732             )
8733                 entry_cut_clipboard(compose->focused_editable);
8734 }
8735
8736 static void compose_copy_cb(Compose *compose)
8737 {
8738         if (compose->focused_editable 
8739 #ifndef MAEMO
8740             && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
8741 #endif
8742             )
8743                 entry_copy_clipboard(compose->focused_editable);
8744 }
8745
8746 static void compose_paste_cb(Compose *compose)
8747 {
8748         gint prev_autowrap;
8749         GtkTextBuffer *buffer;
8750         BLOCK_WRAP();
8751         if (compose->focused_editable &&
8752             GTK_WIDGET_HAS_FOCUS(compose->focused_editable))
8753                 entry_paste_clipboard(compose, compose->focused_editable, 
8754                                 prefs_common.linewrap_pastes,
8755                                 GDK_SELECTION_CLIPBOARD, NULL);
8756         UNBLOCK_WRAP();
8757 }
8758
8759 static void compose_paste_as_quote_cb(Compose *compose)
8760 {
8761         gint wrap_quote = prefs_common.linewrap_quote;
8762         if (compose->focused_editable 
8763 #ifndef MAEMO
8764             && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
8765 #endif
8766             ) {
8767                 /* let text_insert() (called directly or at a later time
8768                  * after the gtk_editable_paste_clipboard) know that 
8769                  * text is to be inserted as a quotation. implemented
8770                  * by using a simple refcount... */
8771                 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
8772                                                 G_OBJECT(compose->focused_editable),
8773                                                 "paste_as_quotation"));
8774                 g_object_set_data(G_OBJECT(compose->focused_editable),
8775                                     "paste_as_quotation",
8776                                     GINT_TO_POINTER(paste_as_quotation + 1));
8777                 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
8778                 entry_paste_clipboard(compose, compose->focused_editable, 
8779                                 prefs_common.linewrap_pastes,
8780                                 GDK_SELECTION_CLIPBOARD, NULL);
8781                 prefs_common.linewrap_quote = wrap_quote;
8782         }
8783 }
8784
8785 static void compose_paste_no_wrap_cb(Compose *compose)
8786 {
8787         gint prev_autowrap;
8788         GtkTextBuffer *buffer;
8789         BLOCK_WRAP();
8790         if (compose->focused_editable 
8791 #ifndef MAEMO
8792             && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
8793 #endif
8794             )
8795                 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
8796                         GDK_SELECTION_CLIPBOARD, NULL);
8797         UNBLOCK_WRAP();
8798 }
8799
8800 static void compose_paste_wrap_cb(Compose *compose)
8801 {
8802         gint prev_autowrap;
8803         GtkTextBuffer *buffer;
8804         BLOCK_WRAP();
8805         if (compose->focused_editable 
8806 #ifndef MAEMO
8807             && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
8808 #endif
8809             )
8810                 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
8811                         GDK_SELECTION_CLIPBOARD, NULL);
8812         UNBLOCK_WRAP();
8813 }
8814
8815 static void compose_allsel_cb(Compose *compose)
8816 {
8817         if (compose->focused_editable 
8818 #ifndef MAEMO
8819             && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
8820 #endif
8821             )
8822                 entry_allsel(compose->focused_editable);
8823 }
8824
8825 static void textview_move_beginning_of_line (GtkTextView *text)
8826 {
8827         GtkTextBuffer *buffer;
8828         GtkTextMark *mark;
8829         GtkTextIter ins;
8830
8831         g_return_if_fail(GTK_IS_TEXT_VIEW(text));
8832
8833         buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8834         mark = gtk_text_buffer_get_insert(buffer);
8835         gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
8836         gtk_text_iter_set_line_offset(&ins, 0);
8837         gtk_text_buffer_place_cursor(buffer, &ins);
8838 }
8839
8840 static void textview_move_forward_character (GtkTextView *text)
8841 {
8842         GtkTextBuffer *buffer;
8843         GtkTextMark *mark;
8844         GtkTextIter ins;
8845
8846         g_return_if_fail(GTK_IS_TEXT_VIEW(text));
8847
8848         buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8849         mark = gtk_text_buffer_get_insert(buffer);
8850         gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
8851         if (gtk_text_iter_forward_cursor_position(&ins))
8852                 gtk_text_buffer_place_cursor(buffer, &ins);
8853 }
8854
8855 static void textview_move_backward_character (GtkTextView *text)
8856 {
8857         GtkTextBuffer *buffer;
8858         GtkTextMark *mark;
8859         GtkTextIter ins;
8860
8861         g_return_if_fail(GTK_IS_TEXT_VIEW(text));
8862
8863         buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8864         mark = gtk_text_buffer_get_insert(buffer);
8865         gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
8866         if (gtk_text_iter_backward_cursor_position(&ins))
8867                 gtk_text_buffer_place_cursor(buffer, &ins);
8868 }
8869
8870 static void textview_move_forward_word (GtkTextView *text)
8871 {
8872         GtkTextBuffer *buffer;
8873         GtkTextMark *mark;
8874         GtkTextIter ins;
8875         gint count;
8876
8877         g_return_if_fail(GTK_IS_TEXT_VIEW(text));
8878
8879         buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8880         mark = gtk_text_buffer_get_insert(buffer);
8881         gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
8882         count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
8883         if (gtk_text_iter_forward_word_ends(&ins, count)) {
8884                 gtk_text_iter_backward_word_start(&ins);
8885                 gtk_text_buffer_place_cursor(buffer, &ins);
8886         }
8887 }
8888
8889 static void textview_move_backward_word (GtkTextView *text)
8890 {
8891         GtkTextBuffer *buffer;
8892         GtkTextMark *mark;
8893         GtkTextIter ins;
8894         gint count;
8895
8896         g_return_if_fail(GTK_IS_TEXT_VIEW(text));
8897
8898         buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8899         mark = gtk_text_buffer_get_insert(buffer);
8900         gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
8901         count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
8902         if (gtk_text_iter_backward_word_starts(&ins, 1))
8903                 gtk_text_buffer_place_cursor(buffer, &ins);
8904 }
8905
8906 static void textview_move_end_of_line (GtkTextView *text)
8907 {
8908         GtkTextBuffer *buffer;
8909         GtkTextMark *mark;
8910         GtkTextIter ins;
8911
8912         g_return_if_fail(GTK_IS_TEXT_VIEW(text));
8913
8914         buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8915         mark = gtk_text_buffer_get_insert(buffer);
8916         gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
8917         if (gtk_text_iter_forward_to_line_end(&ins))
8918                 gtk_text_buffer_place_cursor(buffer, &ins);
8919 }
8920
8921 static void textview_move_next_line (GtkTextView *text)
8922 {
8923         GtkTextBuffer *buffer;
8924         GtkTextMark *mark;
8925         GtkTextIter ins;
8926         gint offset;
8927
8928         g_return_if_fail(GTK_IS_TEXT_VIEW(text));
8929
8930         buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8931         mark = gtk_text_buffer_get_insert(buffer);
8932         gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
8933         offset = gtk_text_iter_get_line_offset(&ins);
8934         if (gtk_text_iter_forward_line(&ins)) {
8935                 gtk_text_iter_set_line_offset(&ins, offset);
8936                 gtk_text_buffer_place_cursor(buffer, &ins);
8937         }
8938 }
8939
8940 static void textview_move_previous_line (GtkTextView *text)
8941 {
8942         GtkTextBuffer *buffer;
8943         GtkTextMark *mark;
8944         GtkTextIter ins;
8945         gint offset;
8946
8947         g_return_if_fail(GTK_IS_TEXT_VIEW(text));
8948
8949         buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8950         mark = gtk_text_buffer_get_insert(buffer);
8951         gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
8952         offset = gtk_text_iter_get_line_offset(&ins);
8953         if (gtk_text_iter_backward_line(&ins)) {
8954                 gtk_text_iter_set_line_offset(&ins, offset);
8955                 gtk_text_buffer_place_cursor(buffer, &ins);
8956         }
8957 }
8958
8959 static void textview_delete_forward_character (GtkTextView *text)
8960 {
8961         GtkTextBuffer *buffer;
8962         GtkTextMark *mark;
8963         GtkTextIter ins, end_iter;
8964
8965         g_return_if_fail(GTK_IS_TEXT_VIEW(text));
8966
8967         buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8968         mark = gtk_text_buffer_get_insert(buffer);
8969         gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
8970         end_iter = ins;
8971         if (gtk_text_iter_forward_char(&end_iter)) {
8972                 gtk_text_buffer_delete(buffer, &ins, &end_iter);
8973         }
8974 }
8975
8976 static void textview_delete_backward_character (GtkTextView *text)
8977 {
8978         GtkTextBuffer *buffer;
8979         GtkTextMark *mark;
8980         GtkTextIter ins, end_iter;
8981
8982         g_return_if_fail(GTK_IS_TEXT_VIEW(text));
8983
8984         buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8985         mark = gtk_text_buffer_get_insert(buffer);
8986         gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
8987         end_iter = ins;
8988         if (gtk_text_iter_backward_char(&end_iter)) {
8989                 gtk_text_buffer_delete(buffer, &end_iter, &ins);
8990         }
8991 }
8992
8993 static void textview_delete_forward_word (GtkTextView *text)
8994 {
8995         GtkTextBuffer *buffer;
8996         GtkTextMark *mark;
8997         GtkTextIter ins, end_iter;
8998
8999         g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9000
9001         buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9002         mark = gtk_text_buffer_get_insert(buffer);
9003         gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9004         end_iter = ins;
9005         if (gtk_text_iter_forward_word_end(&end_iter)) {
9006                 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9007         }
9008 }
9009
9010 static void textview_delete_backward_word (GtkTextView *text)
9011 {
9012         GtkTextBuffer *buffer;
9013         GtkTextMark *mark;
9014         GtkTextIter ins, end_iter;
9015
9016         g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9017
9018         buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9019         mark = gtk_text_buffer_get_insert(buffer);
9020         gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9021         end_iter = ins;
9022         if (gtk_text_iter_backward_word_start(&end_iter)) {
9023                 gtk_text_buffer_delete(buffer, &end_iter, &ins);
9024         }
9025 }
9026
9027 static void textview_delete_line (GtkTextView *text)
9028 {
9029         GtkTextBuffer *buffer;
9030         GtkTextMark *mark;
9031         GtkTextIter ins, start_iter, end_iter;
9032         gboolean found;
9033
9034         g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9035
9036         buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9037         mark = gtk_text_buffer_get_insert(buffer);
9038         gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9039
9040         start_iter = ins;
9041         gtk_text_iter_set_line_offset(&start_iter, 0);
9042
9043         end_iter = ins;
9044         if (gtk_text_iter_ends_line(&end_iter))
9045                 found = gtk_text_iter_forward_char(&end_iter);
9046         else
9047                 found = gtk_text_iter_forward_to_line_end(&end_iter);
9048
9049         if (found)
9050                 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
9051 }
9052
9053 static void textview_delete_to_line_end (GtkTextView *text)
9054 {
9055         GtkTextBuffer *buffer;
9056         GtkTextMark *mark;
9057         GtkTextIter ins, end_iter;
9058         gboolean found;
9059
9060         g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9061
9062         buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9063         mark = gtk_text_buffer_get_insert(buffer);
9064         gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9065         end_iter = ins;
9066         if (gtk_text_iter_ends_line(&end_iter))
9067                 found = gtk_text_iter_forward_char(&end_iter);
9068         else
9069                 found = gtk_text_iter_forward_to_line_end(&end_iter);
9070         if (found)
9071                 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9072 }
9073
9074 static void compose_advanced_action_cb(Compose *compose,
9075                                         ComposeCallAdvancedAction action)
9076 {
9077         GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9078         static struct {
9079                 void (*do_action) (GtkTextView *text);
9080         } action_table[] = {
9081                 {textview_move_beginning_of_line},
9082                 {textview_move_forward_character},
9083                 {textview_move_backward_character},
9084                 {textview_move_forward_word},
9085                 {textview_move_backward_word},
9086                 {textview_move_end_of_line},
9087                 {textview_move_next_line},
9088                 {textview_move_previous_line},
9089                 {textview_delete_forward_character},
9090                 {textview_delete_backward_character},
9091                 {textview_delete_forward_word},
9092                 {textview_delete_backward_word},
9093                 {textview_delete_line},
9094                 {NULL}, /* gtk_stext_delete_line_n */
9095                 {textview_delete_to_line_end}
9096         };
9097
9098         if (!GTK_WIDGET_HAS_FOCUS(text)) return;
9099
9100         if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
9101             action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
9102                 if (action_table[action].do_action)
9103                         action_table[action].do_action(text);
9104                 else
9105                         g_warning("Not implemented yet.");
9106         }
9107 }
9108
9109 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
9110 {
9111         gchar *str = NULL;
9112         
9113         if (GTK_IS_EDITABLE(widget)) {
9114                 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
9115                 gtk_editable_set_position(GTK_EDITABLE(widget), 
9116                         strlen(str));
9117                 g_free(str);
9118                 if (widget->parent && widget->parent->parent
9119                  && widget->parent->parent->parent) {
9120                         if (GTK_IS_SCROLLED_WINDOW(widget->parent->parent->parent)) {
9121                                 gint y = widget->allocation.y;
9122                                 gint height = widget->allocation.height;
9123                                 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
9124                                         (GTK_SCROLLED_WINDOW(widget->parent->parent->parent));
9125
9126                                 if (y < (int)shown->value) {
9127                                         gtk_adjustment_set_value(GTK_ADJUSTMENT(shown), y - 1);
9128                                 }
9129                                 if (y + height > (int)shown->value + (int)shown->page_size) {
9130                                         if (y - height - 1 < (int)shown->upper - (int)shown->page_size) {
9131                                                 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown), 
9132                                                         y + height - (int)shown->page_size - 1);
9133                                         } else {
9134                                                 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown), 
9135                                                         (int)shown->upper - (int)shown->page_size - 1);
9136                                         }
9137                                 }
9138                         }
9139                 }
9140         }
9141
9142         if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
9143                 compose->focused_editable = widget;
9144         
9145 #ifdef MAEMO
9146         if (GTK_IS_TEXT_VIEW(widget) 
9147             && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
9148                 gtk_widget_ref(compose->notebook);
9149                 gtk_widget_ref(compose->edit_vbox);
9150                 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
9151                 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
9152                 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
9153                 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
9154                 gtk_widget_unref(compose->notebook);
9155                 gtk_widget_unref(compose->edit_vbox);
9156                 g_signal_handlers_block_by_func(G_OBJECT(widget),
9157                                         G_CALLBACK(compose_grab_focus_cb),
9158                                         compose);
9159                 gtk_widget_grab_focus(widget);
9160                 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
9161                                         G_CALLBACK(compose_grab_focus_cb),
9162                                         compose);
9163         } else if (!GTK_IS_TEXT_VIEW(widget) 
9164                    && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
9165                 gtk_widget_ref(compose->notebook);
9166                 gtk_widget_ref(compose->edit_vbox);
9167                 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
9168                 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
9169                 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
9170                 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
9171                 gtk_widget_unref(compose->notebook);
9172                 gtk_widget_unref(compose->edit_vbox);
9173                 g_signal_handlers_block_by_func(G_OBJECT(widget),
9174                                         G_CALLBACK(compose_grab_focus_cb),
9175                                         compose);
9176                 gtk_widget_grab_focus(widget);
9177                 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
9178                                         G_CALLBACK(compose_grab_focus_cb),
9179                                         compose);
9180         }
9181 #endif
9182 }
9183
9184 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
9185 {
9186         compose->modified = TRUE;
9187 #ifndef MAEMO
9188         compose_set_title(compose);
9189 #endif
9190 }
9191
9192 static void compose_wrap_cb(gpointer data, guint action, GtkWidget *widget)
9193 {
9194         Compose *compose = (Compose *)data;
9195
9196         if (action == 1)
9197                 compose_wrap_all_full(compose, TRUE);
9198         else
9199                 compose_beautify_paragraph(compose, NULL, TRUE);
9200 }
9201
9202 static void compose_find_cb(gpointer data, guint action, GtkWidget *widget)
9203 {
9204         Compose *compose = (Compose *)data;
9205
9206         message_search_compose(compose);
9207 }
9208
9209 static void compose_toggle_autowrap_cb(gpointer data, guint action,
9210                                        GtkWidget *widget)
9211 {
9212         Compose *compose = (Compose *)data;
9213         compose->autowrap = GTK_CHECK_MENU_ITEM(widget)->active;
9214         if (compose->autowrap)
9215                 compose_wrap_all_full(compose, TRUE);
9216         compose->autowrap = GTK_CHECK_MENU_ITEM(widget)->active;
9217 }
9218
9219 static void compose_toggle_sign_cb(gpointer data, guint action,
9220                                    GtkWidget *widget)
9221 {
9222         Compose *compose = (Compose *)data;
9223
9224         if (GTK_CHECK_MENU_ITEM(widget)->active)
9225                 compose->use_signing = TRUE;
9226         else
9227                 compose->use_signing = FALSE;
9228 }
9229
9230 static void compose_toggle_encrypt_cb(gpointer data, guint action,
9231                                       GtkWidget *widget)
9232 {
9233         Compose *compose = (Compose *)data;
9234
9235         if (GTK_CHECK_MENU_ITEM(widget)->active)
9236                 compose->use_encryption = TRUE;
9237         else
9238                 compose->use_encryption = FALSE;
9239 }
9240
9241 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn) 
9242 {
9243         g_free(compose->privacy_system);
9244
9245         compose->privacy_system = g_strdup(account->default_privacy_system);
9246         compose_update_privacy_system_menu_item(compose, warn);
9247 }
9248
9249 static void compose_toggle_ruler_cb(gpointer data, guint action,
9250                                     GtkWidget *widget)
9251 {
9252         Compose *compose = (Compose *)data;
9253
9254         if (GTK_CHECK_MENU_ITEM(widget)->active) {
9255                 gtk_widget_show(compose->ruler_hbox);
9256                 prefs_common.show_ruler = TRUE;
9257         } else {
9258                 gtk_widget_hide(compose->ruler_hbox);
9259                 gtk_widget_queue_resize(compose->edit_vbox);
9260                 prefs_common.show_ruler = FALSE;
9261         }
9262 }
9263
9264 static void compose_attach_drag_received_cb (GtkWidget          *widget,
9265                                              GdkDragContext     *context,
9266                                              gint                x,
9267                                              gint                y,
9268                                              GtkSelectionData   *data,
9269                                              guint               info,
9270                                              guint               time,
9271                                              gpointer            user_data)
9272 {
9273         Compose *compose = (Compose *)user_data;
9274         GList *list, *tmp;
9275
9276         if (gdk_atom_name(data->type) && 
9277             !strcmp(gdk_atom_name(data->type), "text/uri-list")
9278             && gtk_drag_get_source_widget(context) != 
9279                 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
9280                 list = uri_list_extract_filenames((const gchar *)data->data);
9281                 for (tmp = list; tmp != NULL; tmp = tmp->next) {
9282                         gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
9283                         compose_attach_append
9284                                 (compose, (const gchar *)tmp->data,
9285                                  utf8_filename, NULL);
9286                         g_free(utf8_filename);
9287                 }
9288                 if (list) compose_changed_cb(NULL, compose);
9289                 list_free_strings(list);
9290                 g_list_free(list);
9291         } else if (gtk_drag_get_source_widget(context) 
9292                    == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
9293                 /* comes from our summaryview */
9294                 SummaryView * summaryview = NULL;
9295                 GSList * list = NULL, *cur = NULL;
9296                 
9297                 if (mainwindow_get_mainwindow())
9298                         summaryview = mainwindow_get_mainwindow()->summaryview;
9299                 
9300                 if (summaryview)
9301                         list = summary_get_selected_msg_list(summaryview);
9302                 
9303                 for (cur = list; cur; cur = cur->next) {
9304                         MsgInfo *msginfo = (MsgInfo *)cur->data;
9305                         gchar *file = NULL;
9306                         if (msginfo)
9307                                 file = procmsg_get_message_file_full(msginfo, 
9308                                         TRUE, TRUE);
9309                         if (file) {
9310                                 compose_attach_append(compose, (const gchar *)file, 
9311                                         (const gchar *)file, "message/rfc822");
9312                                 g_free(file);
9313                         }
9314                 }
9315                 g_slist_free(list);
9316         }
9317 }
9318
9319 static gboolean compose_drag_drop(GtkWidget *widget,
9320                                   GdkDragContext *drag_context,
9321                                   gint x, gint y,
9322                                   guint time, gpointer user_data)
9323 {
9324         /* not handling this signal makes compose_insert_drag_received_cb
9325          * called twice */
9326         return TRUE;                                     
9327 }
9328
9329 static void compose_insert_drag_received_cb (GtkWidget          *widget,
9330                                              GdkDragContext     *drag_context,
9331                                              gint                x,
9332                                              gint                y,
9333                                              GtkSelectionData   *data,
9334                                              guint               info,
9335                                              guint               time,
9336                                              gpointer            user_data)
9337 {
9338         Compose *compose = (Compose *)user_data;
9339         GList *list, *tmp;
9340
9341         /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
9342          * does not work */
9343         if (gdk_atom_name(data->type) && !strcmp(gdk_atom_name(data->type), "text/uri-list")) {
9344                 AlertValue val = G_ALERTDEFAULT;
9345
9346                 switch (prefs_common.compose_dnd_mode) {
9347                         case COMPOSE_DND_ASK:
9348                                 val = alertpanel_full(_("Insert or attach?"),
9349                                          _("Do you want to insert the contents of the file(s) "
9350                                            "into the message body, or attach it to the email?"),
9351                                           GTK_STOCK_CANCEL, _("+_Insert"), _("_Attach"),
9352                                           TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
9353                                 break;
9354                         case COMPOSE_DND_INSERT:
9355                                 val = G_ALERTALTERNATE;
9356                                 break;
9357                         case COMPOSE_DND_ATTACH:
9358                                 val = G_ALERTOTHER;
9359                                 break;
9360                         default:
9361                                 /* unexpected case */
9362                                 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
9363                 }
9364
9365                 if (val & G_ALERTDISABLE) {
9366                         val &= ~G_ALERTDISABLE;
9367                         /* remember what action to perform by default, only if we don't click Cancel */
9368                         if (val == G_ALERTALTERNATE)
9369                                 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
9370                         else if (val == G_ALERTOTHER)
9371                                         prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
9372                 }
9373
9374                 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
9375                         gtk_drag_finish(drag_context, FALSE, FALSE, time);
9376                         return;
9377                 } else if (val == G_ALERTOTHER) {
9378                         compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
9379                         return;
9380                 } 
9381                 list = uri_list_extract_filenames((const gchar *)data->data);
9382                 for (tmp = list; tmp != NULL; tmp = tmp->next) {
9383                         compose_insert_file(compose, (const gchar *)tmp->data);
9384                 }
9385                 list_free_strings(list);
9386                 g_list_free(list);
9387                 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9388                 return;
9389         } else {
9390 #if GTK_CHECK_VERSION(2, 8, 0)
9391                 /* do nothing, handled by GTK */
9392 #else
9393                 gchar *tmpfile = get_tmp_file();
9394                 str_write_to_file((const gchar *)data->data, tmpfile);
9395                 compose_insert_file(compose, tmpfile);
9396                 g_unlink(tmpfile);
9397                 g_free(tmpfile);
9398                 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9399 #endif
9400                 return;
9401         }
9402         gtk_drag_finish(drag_context, TRUE, FALSE, time);
9403 }
9404
9405 static void compose_header_drag_received_cb (GtkWidget          *widget,
9406                                              GdkDragContext     *drag_context,
9407                                              gint                x,
9408                                              gint                y,
9409                                              GtkSelectionData   *data,
9410                                              guint               info,
9411                                              guint               time,
9412                                              gpointer            user_data)
9413 {
9414         GtkEditable *entry = (GtkEditable *)user_data;
9415         gchar *email = (gchar *)data->data;
9416
9417         /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
9418          * does not work */
9419
9420         if (!strncmp(email, "mailto:", strlen("mailto:"))) {
9421                 gchar *decoded=g_new(gchar, strlen(email));
9422                 int start = 0;
9423
9424                 email += strlen("mailto:");
9425                 decode_uri(decoded, email); /* will fit */
9426                 gtk_editable_delete_text(entry, 0, -1);
9427                 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
9428                 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9429                 g_free(decoded);
9430                 return;
9431         }
9432         gtk_drag_finish(drag_context, TRUE, FALSE, time);
9433 }
9434
9435 static void compose_toggle_return_receipt_cb(gpointer data, guint action,
9436                                              GtkWidget *widget)
9437 {
9438         Compose *compose = (Compose *)data;
9439
9440         if (GTK_CHECK_MENU_ITEM(widget)->active)
9441                 compose->return_receipt = TRUE;
9442         else
9443                 compose->return_receipt = FALSE;
9444 }
9445
9446 static void compose_toggle_remove_refs_cb(gpointer data, guint action,
9447                                              GtkWidget *widget)
9448 {
9449         Compose *compose = (Compose *)data;
9450
9451         if (GTK_CHECK_MENU_ITEM(widget)->active)
9452                 compose->remove_references = TRUE;
9453         else
9454                 compose->remove_references = FALSE;
9455 }
9456
9457 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
9458                                             GdkEventKey *event,
9459                                             ComposeHeaderEntry *headerentry)
9460 {
9461         if ((g_slist_length(headerentry->compose->header_list) > 0) &&
9462             ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
9463             !(event->state & GDK_MODIFIER_MASK) &&
9464             (event->keyval == GDK_BackSpace) &&
9465             (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
9466                 gtk_container_remove
9467                         (GTK_CONTAINER(headerentry->compose->header_table),
9468                          headerentry->combo);
9469                 gtk_container_remove
9470                         (GTK_CONTAINER(headerentry->compose->header_table),
9471                          headerentry->entry);
9472                 headerentry->compose->header_list =
9473                         g_slist_remove(headerentry->compose->header_list,
9474                                        headerentry);
9475                 g_free(headerentry);
9476         } else  if (event->keyval == GDK_Tab) {
9477                 if (headerentry->compose->header_last == headerentry) {
9478                         /* Override default next focus, and give it to subject_entry
9479                          * instead of notebook tabs
9480                          */
9481                         g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event"); 
9482                         gtk_widget_grab_focus(headerentry->compose->subject_entry);
9483                         return TRUE;
9484                 }
9485         }
9486         return FALSE;
9487 }
9488
9489 static gboolean compose_headerentry_changed_cb(GtkWidget *entry,
9490                                     ComposeHeaderEntry *headerentry)
9491 {
9492         if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
9493                 compose_create_header_entry(headerentry->compose);
9494                 g_signal_handlers_disconnect_matched
9495                         (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
9496                          0, 0, NULL, NULL, headerentry);
9497                 
9498                 /* Automatically scroll down */
9499                 compose_show_first_last_header(headerentry->compose, FALSE);
9500                 
9501         }
9502         return FALSE;
9503 }
9504
9505 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
9506 {
9507         GtkAdjustment *vadj;
9508
9509         g_return_if_fail(compose);
9510         g_return_if_fail(GTK_IS_WIDGET(compose->header_table));
9511         g_return_if_fail(GTK_IS_VIEWPORT(compose->header_table->parent));
9512
9513         vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(compose->header_table->parent));
9514         gtk_adjustment_set_value(vadj, (show_first ? vadj->lower : vadj->upper));
9515 }
9516
9517 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
9518                           const gchar *text, gint len, Compose *compose)
9519 {
9520         gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
9521                                 (G_OBJECT(compose->text), "paste_as_quotation"));
9522         GtkTextMark *mark;
9523
9524         g_return_if_fail(text != NULL);
9525
9526         g_signal_handlers_block_by_func(G_OBJECT(buffer),
9527                                         G_CALLBACK(text_inserted),
9528                                         compose);
9529         if (paste_as_quotation) {
9530                 gchar *new_text;
9531                 gchar *qmark;
9532
9533                 if (len < 0)
9534                         len = strlen(text);
9535
9536                 new_text = g_strndup(text, len);
9537
9538                 /* FIXME - use per-folder/account quotemark */
9539                 if (prefs_common.quotemark && *prefs_common.quotemark)
9540                         qmark = prefs_common.quotemark;
9541                 else
9542                         qmark = "> ";
9543
9544                 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
9545                 gtk_text_buffer_place_cursor(buffer, iter);
9546
9547                 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
9548                                                   _("Quote format error at line %d."));
9549                 quote_fmt_reset_vartable();
9550                 g_free(new_text);
9551                 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
9552                                   GINT_TO_POINTER(paste_as_quotation - 1));
9553                                   
9554                 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
9555                 gtk_text_buffer_place_cursor(buffer, iter);
9556         } else {
9557                 if (strcmp(text, "\n") || automatic_break
9558                 || gtk_text_iter_starts_line(iter))
9559                         gtk_text_buffer_insert(buffer, iter, text, len);
9560                 else {
9561                         debug_print("insert nowrap \\n\n");
9562                         gtk_text_buffer_insert_with_tags_by_name(buffer, 
9563                                 iter, text, len, "no_join", NULL);
9564                 }
9565         }
9566         
9567         mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
9568         
9569         compose_beautify_paragraph(compose, iter, FALSE);
9570
9571         gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
9572         gtk_text_buffer_delete_mark(buffer, mark);
9573
9574         g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
9575                                           G_CALLBACK(text_inserted),
9576                                           compose);
9577         g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
9578
9579         if (prefs_common.autosave && 
9580             gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0)
9581                 compose->draft_timeout_tag = g_timeout_add
9582                         (500, (GtkFunction) compose_defer_auto_save_draft, compose);
9583 }
9584 static gint compose_defer_auto_save_draft(Compose *compose)
9585 {
9586         compose->draft_timeout_tag = -1;
9587         compose_draft_cb((gpointer)compose, COMPOSE_AUTO_SAVE, NULL);
9588         return FALSE;
9589 }
9590
9591 #if USE_ASPELL
9592 static void compose_check_all(Compose *compose)
9593 {
9594         if (compose->gtkaspell)
9595                 gtkaspell_check_all(compose->gtkaspell);
9596 }
9597
9598 static void compose_highlight_all(Compose *compose)
9599 {
9600         if (compose->gtkaspell)
9601                 gtkaspell_highlight_all(compose->gtkaspell);
9602 }
9603
9604 static void compose_check_backwards(Compose *compose)
9605 {
9606         if (compose->gtkaspell) 
9607                 gtkaspell_check_backwards(compose->gtkaspell);
9608         else {
9609                 GtkItemFactory *ifactory;
9610                 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
9611                 menu_set_sensitive(ifactory, "/Edit/Check backwards misspelled word", FALSE);
9612                 menu_set_sensitive(ifactory, "/Edit/Forward to next misspelled word", FALSE);
9613         }
9614 }
9615
9616 static void compose_check_forwards_go(Compose *compose)
9617 {
9618         if (compose->gtkaspell) 
9619                 gtkaspell_check_forwards_go(compose->gtkaspell);
9620         else {
9621                 GtkItemFactory *ifactory;
9622                 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
9623                 menu_set_sensitive(ifactory, "/Edit/Check backwards misspelled word", FALSE);
9624                 menu_set_sensitive(ifactory, "/Edit/Forward to next misspelled word", FALSE);
9625         }
9626 }
9627 #endif
9628
9629 /*!
9630  *\brief        Guess originating forward account from MsgInfo and several 
9631  *              "common preference" settings. Return NULL if no guess. 
9632  */
9633 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
9634 {
9635         PrefsAccount *account = NULL;
9636         
9637         g_return_val_if_fail(msginfo, NULL);
9638         g_return_val_if_fail(msginfo->folder, NULL);
9639         g_return_val_if_fail(msginfo->folder->prefs, NULL);
9640
9641         if (msginfo->folder->prefs->enable_default_account)
9642                 account = account_find_from_id(msginfo->folder->prefs->default_account);
9643                 
9644         if (!account) 
9645                 account = msginfo->folder->folder->account;
9646                 
9647         if (!account && msginfo->to && prefs_common.forward_account_autosel) {
9648                 gchar *to;
9649                 Xstrdup_a(to, msginfo->to, return NULL);
9650                 extract_address(to);
9651                 account = account_find_from_address(to);
9652         }
9653
9654         if (!account && prefs_common.forward_account_autosel) {
9655                 gchar cc[BUFFSIZE];
9656                 if (!procheader_get_header_from_msginfo
9657                         (msginfo, cc,sizeof cc , "Cc:")) { 
9658                         gchar *buf = cc + strlen("Cc:");
9659                         extract_address(buf);
9660                         account = account_find_from_address(buf);
9661                 }
9662         }
9663         
9664         if (!account && prefs_common.forward_account_autosel) {
9665                 gchar deliveredto[BUFFSIZE];
9666                 if (!procheader_get_header_from_msginfo
9667                         (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) { 
9668                         gchar *buf = deliveredto + strlen("Delivered-To:");
9669                         extract_address(buf);
9670                         account = account_find_from_address(buf);
9671                 }
9672         }
9673         
9674         return account;
9675 }
9676
9677 gboolean compose_close(Compose *compose)
9678 {
9679         gint x, y;
9680
9681         if (!g_mutex_trylock(compose->mutex)) {
9682                 /* we have to wait for the (possibly deferred by auto-save)
9683                  * drafting to be done, before destroying the compose under
9684                  * it. */
9685                 debug_print("waiting for drafting to finish...\n");
9686                 g_timeout_add (500, (GSourceFunc) compose_close, compose);
9687                 return FALSE;
9688         }
9689         g_return_val_if_fail(compose, FALSE);
9690         gtkut_widget_get_uposition(compose->window, &x, &y);
9691         prefs_common.compose_x = x;
9692         prefs_common.compose_y = y;
9693         g_mutex_unlock(compose->mutex);
9694         compose_destroy(compose);
9695         return FALSE;
9696 }
9697
9698 /**
9699  * Add entry field for each address in list.
9700  * \param compose     E-Mail composition object.
9701  * \param listAddress List of (formatted) E-Mail addresses.
9702  */
9703 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
9704         GList *node;
9705         gchar *addr;
9706         node = listAddress;
9707         while( node ) {
9708                 addr = ( gchar * ) node->data;
9709                 compose_entry_append( compose, addr, COMPOSE_TO );
9710                 node = g_list_next( node );
9711         }
9712 }
9713
9714 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list, 
9715                                     guint action, gboolean opening_multiple)
9716 {
9717         gchar *body = NULL;
9718         GSList *new_msglist = NULL;
9719         MsgInfo *tmp_msginfo = NULL;
9720         gboolean originally_enc = FALSE;
9721         Compose *compose = NULL;
9722
9723         g_return_if_fail(msgview != NULL);
9724
9725         g_return_if_fail(msginfo_list != NULL);
9726
9727         if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
9728                 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
9729                 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
9730
9731                 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE && 
9732                     !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
9733                         tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
9734                                                 orig_msginfo, mimeinfo);
9735                         if (tmp_msginfo != NULL) {
9736                                 new_msglist = g_slist_append(NULL, tmp_msginfo);
9737                                 if (procmime_msginfo_is_encrypted(orig_msginfo)) {
9738                                         originally_enc = TRUE;
9739                                 }
9740                                 tmp_msginfo->folder = orig_msginfo->folder;
9741                                 tmp_msginfo->msgnum = orig_msginfo->msgnum; 
9742                         }
9743                 }
9744         }
9745
9746         if (!opening_multiple)
9747                 body = messageview_get_selection(msgview);
9748
9749         if (new_msglist) {
9750                 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
9751                 procmsg_msginfo_free(tmp_msginfo);
9752                 g_slist_free(new_msglist);
9753         } else
9754                 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
9755
9756         if (originally_enc) {
9757                 compose_force_encryption(compose, compose->account, FALSE);
9758         }
9759
9760         g_free(body);
9761 }
9762
9763 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list, 
9764                                     guint action)
9765 {
9766         if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD) 
9767         &&  action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
9768                 GSList *cur = msginfo_list;
9769                 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
9770                                                "messages. Opening the windows "
9771                                                "could take some time. Do you "
9772                                                "want to continue?"), 
9773                                                g_slist_length(msginfo_list));
9774                 if (g_slist_length(msginfo_list) > 9
9775                 &&  alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
9776                     != G_ALERTALTERNATE) {
9777                         g_free(msg);
9778                         return;
9779                 }
9780                 g_free(msg);
9781                 /* We'll open multiple compose windows */
9782                 /* let the WM place the next windows */
9783                 compose_force_window_origin = FALSE;
9784                 for (; cur; cur = cur->next) {
9785                         GSList tmplist;
9786                         tmplist.data = cur->data;
9787                         tmplist.next = NULL;
9788                         compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
9789                 }
9790                 compose_force_window_origin = TRUE;
9791         } else {
9792                 /* forwarding multiple mails as attachments is done via a
9793                  * single compose window */
9794                 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
9795         }
9796 }
9797
9798 void compose_set_position(Compose *compose, gint pos)
9799 {
9800         GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9801
9802         gtkut_text_view_set_position(text, pos);
9803 }
9804
9805 gboolean compose_search_string(Compose *compose,
9806                                 const gchar *str, gboolean case_sens)
9807 {
9808         GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9809
9810         return gtkut_text_view_search_string(text, str, case_sens);
9811 }
9812
9813 gboolean compose_search_string_backward(Compose *compose,
9814                                 const gchar *str, gboolean case_sens)
9815 {
9816         GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9817
9818         return gtkut_text_view_search_string_backward(text, str, case_sens);
9819 }
9820
9821 /* allocate a msginfo structure and populate its data from a compose data structure */
9822 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
9823 {
9824         MsgInfo *newmsginfo;
9825         GSList *list;
9826         gchar buf[BUFFSIZE];
9827
9828         g_return_val_if_fail( compose != NULL, NULL );
9829
9830         newmsginfo = procmsg_msginfo_new();
9831
9832         /* date is now */
9833         get_rfc822_date(buf, sizeof(buf));
9834         newmsginfo->date = g_strdup(buf);
9835
9836         /* from */
9837         if (compose->from_name) {
9838                 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
9839                 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
9840         }
9841
9842         /* subject */
9843         if (compose->subject_entry)
9844                 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
9845
9846         /* to, cc, reply-to, newsgroups */
9847         for (list = compose->header_list; list; list = list->next) {
9848                 gchar *header = gtk_editable_get_chars(
9849                                                                 GTK_EDITABLE(
9850                                                                 GTK_COMBO(((ComposeHeaderEntry *)list->data)->combo)->entry), 0, -1);
9851                 gchar *entry = gtk_editable_get_chars(
9852                                                                 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
9853
9854                 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
9855                         if ( newmsginfo->to == NULL ) {
9856                                 newmsginfo->to = g_strdup(entry);
9857                         } else if (entry && *entry) {
9858                                 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
9859                                 g_free(newmsginfo->to);
9860                                 newmsginfo->to = tmp;
9861                         }
9862                 } else
9863                 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
9864                         if ( newmsginfo->cc == NULL ) {
9865                                 newmsginfo->cc = g_strdup(entry);
9866                         } else if (entry && *entry) {
9867                                 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
9868                                 g_free(newmsginfo->cc);
9869                                 newmsginfo->cc = tmp;
9870                         }
9871                 } else
9872                 if ( strcasecmp(header,
9873                                                 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
9874                         if ( newmsginfo->newsgroups == NULL ) {
9875                                 newmsginfo->newsgroups = g_strdup(entry);
9876                         } else if (entry && *entry) {
9877                                 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
9878                                 g_free(newmsginfo->newsgroups);
9879                                 newmsginfo->newsgroups = tmp;
9880                         }
9881                 }
9882
9883                 g_free(header);
9884                 g_free(entry);  
9885         }
9886
9887         /* other data is unset */
9888
9889         return newmsginfo;
9890 }
9891
9892 #ifdef USE_ASPELL
9893 /* update compose's dictionaries from folder dict settings */
9894 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
9895                                                 FolderItem *folder_item)
9896 {
9897         g_return_if_fail(compose != NULL);
9898
9899         if (compose->gtkaspell && folder_item && folder_item->prefs) {
9900                 FolderItemPrefs *prefs = folder_item->prefs;
9901
9902                 if (prefs->enable_default_dictionary)
9903                         gtkaspell_change_dict(compose->gtkaspell,
9904                                         prefs->default_dictionary, FALSE);
9905                 if (folder_item->prefs->enable_default_alt_dictionary)
9906                         gtkaspell_change_alt_dict(compose->gtkaspell,
9907                                         prefs->default_alt_dictionary);
9908                 if (prefs->enable_default_dictionary
9909                         || prefs->enable_default_alt_dictionary)
9910                         compose_spell_menu_changed(compose);
9911         }
9912 }
9913 #endif
9914
9915 /*
9916  * End of Source.
9917  */