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