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