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