Logo Search packages:      
Sourcecode: pan version File versions  Download package

message-window.c

/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
 * Pan - A Newsreader for Gtk+
 * Copyright (C) 2002  Charles Kerr <charles@rebelbase.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include <config.h>

#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <locale.h>

#include <glib.h>
#include <gtk/gtk.h>
#include <gmime/gmime.h>

#ifdef HAVE_LIBGTKSPELL
#include <gtkspell/gtkspell.h>
#endif

#include <gmime/gmime-charset.h>

#include <pan/base/argset.h>
#include <pan/base/debug.h>
#include <pan/base/gnksa.h>
#include <pan/base/log.h>
#include <pan/base/pan-i18n.h>
#include <pan/base/pan-glib-extensions.h>
#include <pan/base/serverlist.h>
#include <pan/base/util-file.h>
#include <pan/base/util-mime.h>
#include <pan/base/pan-callback.h>
#include <pan/base/pan-config.h>
#include <pan/base/text-massager.h>

#include <pan/identities/identity-manager.h>

#include <pan/pan-charset-picker.h>
#include <pan/articlelist.h>
#include <pan/globals.h>
#include <pan/grouplist.h>
#include <pan/gui-headers.h>
#include <pan/message-check-ui.h>
#include <pan/message-window.h>
#include <pan/prefs.h>
#include <pan/print.h>
#include <pan/queue.h>
#include <pan/task-post.h>
#include <pan/text.h> /* for colors of fg, bg, and quoted text */
#include <pan/util.h>

#include <pan/xpm/pan-pixbufs.h>

typedef enum
{
      NNTP_POST,
      NNTP_REPLY,
      EMAIL_REPLY,
      EMAIL_FORWARD,
      EDIT_ORIGINAL
}
ComposeType;

/* #define ATTACHMENTS */
#define PAN_REPLY_PORTION "PAN_REPLY_PORTION"

typedef struct
{
      GtkWidget * window;

      GtkWidget * read_info_pane;

      GtkWidget * from_om;
      GtkWidget * custom_headers_text;
      GtkWidget * post_user_agent_tb;
      GtkWidget * post_message_id_tb;
      GtkWidget * organization;
      GtkWidget * charset_om;
      GtkWidget * newsgroups;
      GtkWidget * users;
      GtkWidget * subject;
        GtkWidget * followup_to;
        GtkWidget * reply_to;

        GtkWidget * do_wrap_togglebutton;

      GtkTextBuffer * body_buffer;
      GtkWidget * body_view;

      /* Attachment */
      GtkWidget * file_clist;
      GtkWidget * file_add_button;
      GtkWidget * file_remove_button;
      GtkWidget * attachment_lines_per_part_sb;
      GtkWidget * single_part_rb;
      GtkWidget * multi_part_rb;
      GtkWidget * encoding_mime_tb;
      GtkWidget * encoding_uuenc_tb;
      GtkWidget * encoding_yenc_tb;

      GMimeMessage * message;
      TextMassager * text_massager;
      ComposeType type;
      Server * server;

      gulong cursor_position_signal_handler_id;
      gulong text_inserted_signal_handler_id;

      char * identity_name;
      char * last_attribution_text;
}
Compose;

extern GtkTooltips *ttips;

static void rot13_cb                       (gpointer, int, GtkWidget*);
static void wrap_cb                        (gpointer, int, GtkWidget*);
static void save_cb                        (gpointer, int, GtkWidget*);
static void send_now_cb                    (gpointer, int, GtkWidget*);
static void send_later_cb                  (gpointer, int, GtkWidget*);
static void message_window_save_cb         (gpointer, int, GtkWidget*);
static void message_window_cut_cb          (gpointer, int, GtkWidget*);
static void message_window_copy_cb         (gpointer, int, GtkWidget*);
static void message_window_paste_cb        (gpointer, int, GtkWidget*);
static void message_window_close           (gpointer, int, GtkWidget*);
static void compose_ext_editor_cb          (gpointer, int, GtkWidget*);
static void identities_changed_cb          (gpointer, gpointer, gpointer);

static void compose_ext_editor_cb2          (GtkWidget*, gpointer);
static void rot13_cb2                       (GtkWidget*, gpointer);
static void wrap_cb2                        (GtkWidget*, gpointer);
static void send_now_cb2                    (GtkWidget*, gpointer);
static void send_later_cb2                  (GtkWidget*, gpointer);

static GtkWidget * extra_headers_page      (Compose*);
static GtkWidget * create_body_pane_nolock (Compose*);

static void message_window_new (GMimeMessage * message, ComposeType type);

/***
****  SHUTDOWN
***/

static int
window_delete_event_cb (GtkWidget * w, GdkEvent * e, gpointer data)
{
      Compose * compose = (Compose*) data;
      gui_save_window_size (compose->window, "compose");
      return FALSE;
}

static void
window_destroy_cb (GtkWidget * window, Compose * compose)
{
      pan_callback_remove (identity_manager_get_identities_changed_callback(),
                           identities_changed_cb, compose);

      g_signal_handler_disconnect (compose->body_buffer,
                                   compose->cursor_position_signal_handler_id);
      g_signal_handler_disconnect (compose->body_buffer,
                                   compose->text_inserted_signal_handler_id);

      pan_config_set_bool_if_different (KEY_POST_USER_AGENT_HEADER,
                                        gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(compose->post_user_agent_tb)),
                                        DEFAULT_VALUE_POST_USER_AGENT_HEADER);
      pan_config_set_bool_if_different (KEY_POST_MESSAGE_ID_HEADER,
                                        gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(compose->post_message_id_tb)),
                                        DEFAULT_VALUE_POST_MESSAGE_ID_HEADER);
      pan_config_sync ();

      if (compose->message != NULL)
            g_object_unref (compose->message);
      text_massager_free (compose->text_massager);
      g_free (compose->identity_name);
      g_free (compose->last_attribution_text);
      g_free (compose);
}

/***
****  EXPERIMENTAL WRAP CODE
***/

static gboolean wrap_is_enabled = TRUE;

static void
wrap_tb_toggled_cb (GtkToggleButton * togglebutton, gpointer user_data)
{
      wrap_is_enabled = gtk_toggle_button_get_active (togglebutton);
}

static void
text_was_inserted_cb (GtkTextBuffer   * text_buffer,
                      GtkTextIter     * insert_pos,
                  char            * text,
                  int               text_len,
                  gpointer          unused)
{
      /* dampen out a recursive event storm that would otherwise
         happen from us changing the text buffer by rewrapping the
         text here */
      static gboolean dampen_feedback = FALSE;

      if (!dampen_feedback && wrap_is_enabled)
      {
            int line_length;
            GtkTextIter line_begin_iter;
            const TextMassager * text_massager = text_pane_get_text_massager ();
            const int line_number = gtk_text_iter_get_line (insert_pos);

            /* check the line length -- if it's too wide, we need to wrap the text */
            gtk_text_buffer_get_iter_at_line (text_buffer, &line_begin_iter, line_number);
            line_length = gtk_text_iter_get_chars_in_line(&line_begin_iter);
            if (line_length >= text_massager_get_wrap_column(text_massager))
            {
                  char * before;
                  char * after;
                  GtkTextIter end_iter;

                  /* we'll need to wrap all the way to the end of the paragraph to ensure
                     the wrapping works right -- so look for an empty line.. */
                  end_iter = line_begin_iter;
                  for (;;) {
                        if (!gtk_text_iter_forward_line (&end_iter))
                              break;
                        if (gtk_text_iter_get_chars_in_line (&end_iter) < 2)
                              break;
                  }
                  /* move backward to the end of the previous word */
                  gtk_text_iter_backward_word_start (&end_iter);
                  gtk_text_iter_forward_word_end (&end_iter);

                  /* if the wrapped text is any different from the text already in
                     the buffer, then replace the buffer's text with the wrapped version. */
                  before = gtk_text_buffer_get_text (text_buffer, &line_begin_iter, &end_iter, FALSE);
                  after = text_massager_fill (text_massager, before);
                  if (pan_strcmp (before, after))
                  {
                        int insert_iterator_pos;
                        int char_offset;
                        GtkTextMark * mark;
                        GtkTextIter insert_iter;

                        dampen_feedback = TRUE;

                        /* remember where the insert pos was so that we can revalidate
                           the iterator that was passed in */
                        insert_iterator_pos = gtk_text_iter_get_offset (insert_pos);

                        /* get char_offset so that we can update insert and selection_bound
                           marks after we make the change */
                        mark = gtk_text_buffer_get_mark (text_buffer, "insert");
                        gtk_text_buffer_get_iter_at_mark (text_buffer, &insert_iter, mark);
                        char_offset = gtk_text_iter_get_offset (&insert_iter);

                        /* swap the non-wrapped for the wrapped text */
                        gtk_text_buffer_delete (text_buffer, &line_begin_iter, &end_iter);
                        gtk_text_buffer_insert (text_buffer, &line_begin_iter, after, -1);

                        /* update the insert and selection_bound marks to where they were
                           before the swap */
                        gtk_text_buffer_get_iter_at_offset (text_buffer, &insert_iter, char_offset);
                        mark = gtk_text_buffer_get_mark (text_buffer, "insert");
                        gtk_text_buffer_move_mark (text_buffer, mark, &insert_iter);
                        mark = gtk_text_buffer_get_mark (text_buffer, "selection_bound");
                        gtk_text_buffer_move_mark (text_buffer, mark, &insert_iter);

                        /* revalidate the insert_pos iterator */
                        gtk_text_buffer_get_iter_at_offset (text_buffer, insert_pos, insert_iterator_pos);

                        dampen_feedback = FALSE;
                  }

                  /* cleanup */
                  g_free (after);
                  g_free (before);
            }
      }
}

static void
experimental_wrap_handler (Compose * compose, GtkTextBuffer * buffer, GtkToggleButton * tb)
{
      pan_lock ();

      gtk_toggle_button_set_active (tb, wrap_is_enabled);
      compose->text_inserted_signal_handler_id = g_signal_connect_after (buffer, "insert-text", G_CALLBACK(text_was_inserted_cb), NULL);
      g_signal_connect (tb, "toggled", G_CALLBACK(wrap_tb_toggled_cb), NULL);

      pan_unlock ();
}

/***
****
***/

static GtkItemFactoryEntry edit_menu_entries [] =
{
      {N_("/_File"), NULL, NULL, 0, "<Branch>"},
      {N_("/_File/_Save Changes"), "<control><shift>S", save_cb, 0, "<StockItem>", GTK_STOCK_SAVE},
      {N_("/_File/Save _As..."), "<control>S", message_window_save_cb, 0, "<StockItem>", GTK_STOCK_SAVE_AS},
      {N_("/_File/---"), NULL, NULL, 0, "<Separator>"},
      {N_("/_File/_Close"), "<control>W", message_window_close, 0, "<StockItem>", GTK_STOCK_CLOSE},
      {N_("/_Edit"), NULL, NULL, 0, "<Branch>"},
      {N_("/_Edit/Cu_t"), NULL, message_window_cut_cb, 0, "<StockItem>", GTK_STOCK_CUT},
      {N_("/_Edit/_Copy"), NULL, message_window_copy_cb, 0, "<StockItem>", GTK_STOCK_COPY},
      {N_("/_Edit/_Paste"), NULL, message_window_paste_cb, 0, "<StockItem>", GTK_STOCK_PASTE},
      {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
      {N_("/_Edit/_Rot13 Selected Text"), "<shift><control>R", rot13_cb, 0, "<StockItem>", GTK_STOCK_REFRESH},
      {N_("/_Edit/Edit with E_xternal Editor"), "<control>E", compose_ext_editor_cb, 0, "<StockItem>", GTK_STOCK_JUMP_TO}
};

static GtkItemFactoryEntry post_menu_entries [] =
{
      {N_("/_File/Send Now"), "<control>Return", send_now_cb, 0, "<ImageItem>", icon_compose_send},
      {N_("/_File/Send Later"), NULL, send_later_cb, 0, "<ImageItem>", icon_stock_timer},
      {N_("/_File/---"), NULL, NULL, 0, "<Separator>"},
      {N_("/_File/Save _As..."), "<control>S", message_window_save_cb, 0, "<StockItem>", GTK_STOCK_SAVE_AS},
      {N_("/_File/---"), NULL, NULL, 0, "<Separator>"},
      {N_("/_File/_Close"), NULL, message_window_close, 0, "<StockItem>", GTK_STOCK_CLOSE},
      {N_("/_Edit"), NULL, NULL, 0, "<Branch>"},
      {N_("/_Edit/Cu_t"), NULL, message_window_cut_cb, 0, "<StockItem>", GTK_STOCK_CUT},
      {N_("/_Edit/_Copy"), NULL, message_window_copy_cb, 0, "<StockItem>", GTK_STOCK_COPY},
      {N_("/_Edit/_Paste"), NULL, message_window_paste_cb, 0, "<StockItem>", GTK_STOCK_PASTE},
      {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
      {N_("/_Edit/_Rot13 Selected Text"), "<shift><control>R", rot13_cb, 0, "<StockItem>", GTK_STOCK_REFRESH},
      {N_("/_Edit/Edit with E_xternal Editor"), "<control>E", compose_ext_editor_cb, 0, "<StockItem>", GTK_STOCK_JUMP_TO}
};

static char*
menu_translate (const char* path, gpointer data)
{
      return (char*) gettext (path);
}

static GtkWidget*
create_main_menu (Compose * compose)
{
      int entries_qty;
      GtkItemFactory * factory;
      GtkItemFactoryEntry * entries;
      GtkAccelGroup * accel_group;
      GtkWidget * menubar;

      /* sanity clause */
      g_return_val_if_fail (compose!=NULL, NULL);

      /* figure out which menu we're building */
      if (compose->type==EDIT_ORIGINAL) {
            entries = edit_menu_entries;
            entries_qty = G_N_ELEMENTS (edit_menu_entries);
      }
      else {
            entries = post_menu_entries;
            entries_qty = G_N_ELEMENTS (post_menu_entries);
      }

        /* build the menu */
        accel_group = gtk_accel_group_new ();
      gtk_window_add_accel_group (GTK_WINDOW(compose->window), accel_group);
      g_object_unref (accel_group);
      factory = gtk_item_factory_new (GTK_TYPE_MENU_BAR, "<compose>", accel_group);
      gtk_item_factory_set_translate_func (factory, menu_translate, NULL, NULL);
      gtk_item_factory_create_items (factory, entries_qty, entries, compose);
      menubar = gtk_item_factory_get_widget (factory, "<compose>");

      g_assert (GTK_IS_WIDGET(menubar));
      return menubar;
}

static GtkWidget*
create_toolbar (Compose * compose)
{
      GtkWidget * image;
      GtkWidget * toolbar;

      /* sanity clause */
      g_return_val_if_fail (compose!=NULL, NULL);

      toolbar = gtk_toolbar_new ();

      /* send now */
      image = pan_gtk_image_new_from_inline_text (icon_compose_send, GTK_ICON_SIZE_SMALL_TOOLBAR);
      gtk_toolbar_append_element (GTK_TOOLBAR(toolbar),
                                  GTK_TOOLBAR_CHILD_BUTTON, NULL,
                                  _("Send Now"), NULL, NULL,
                                  image, G_CALLBACK(send_now_cb2), compose);

      /* send later */
      image = pan_gtk_image_new_from_inline_text (icon_stock_timer, GTK_ICON_SIZE_SMALL_TOOLBAR);
      gtk_toolbar_append_element (GTK_TOOLBAR(toolbar),
                                  GTK_TOOLBAR_CHILD_BUTTON, NULL,
                                  _("Send Later"), NULL, NULL,
                                  image, G_CALLBACK(send_later_cb2), compose);

      gtk_toolbar_insert_space (GTK_TOOLBAR(toolbar), -1);

      /* rewrap */
      image = gtk_image_new_from_stock (GTK_STOCK_JUSTIFY_FILL, GTK_ICON_SIZE_SMALL_TOOLBAR);
      gtk_toolbar_append_element (GTK_TOOLBAR(toolbar),
                                  GTK_TOOLBAR_CHILD_BUTTON, NULL,
                                  _("Rewrap"), NULL, NULL,
                                  image, G_CALLBACK(wrap_cb2), compose);

      /* line wrap togglebutton */
      image = gtk_image_new_from_stock (GTK_STOCK_JUSTIFY_FILL, GTK_ICON_SIZE_SMALL_TOOLBAR);
        compose->do_wrap_togglebutton =
      gtk_toolbar_append_element (GTK_TOOLBAR(toolbar),
                                  GTK_TOOLBAR_CHILD_TOGGLEBUTTON, NULL,
                                  _("Wrap Text"), _("Turn line wrap on/off"), NULL,
                                  image, NULL, NULL);

      gtk_toolbar_insert_space (GTK_TOOLBAR(toolbar), -1);

      /* rot13 */
      image = gtk_image_new_from_stock (GTK_STOCK_REFRESH, GTK_ICON_SIZE_SMALL_TOOLBAR);
      gtk_toolbar_append_element (GTK_TOOLBAR(toolbar),
                                  GTK_TOOLBAR_CHILD_BUTTON, NULL,
                                  _("Rot13"), _("Rot13 Selected Text"), NULL,
                                  image, G_CALLBACK(rot13_cb2), compose);

      /* external editor */
      image = gtk_image_new_from_stock (GTK_STOCK_JUMP_TO, GTK_ICON_SIZE_SMALL_TOOLBAR);
      gtk_toolbar_append_element (GTK_TOOLBAR(toolbar),
                                  GTK_TOOLBAR_CHILD_BUTTON, NULL,
                                  _("Editor"), _("Edit with an External Editor"), NULL,
                                  image, G_CALLBACK(compose_ext_editor_cb2), compose);

      return toolbar;
}


/**
 *  1. If the user selected an area, we use that.
 *  2. If the user didn't select an area, use the whole body.
 *  3. Quote fresh text with "> "
 *  4. Requote older quoted text with ">"
 *  5. Strip the signature if user didn't select it.
 *  6. Wrap everything.
 */
static char*
create_reply_body (GMimeMessage * message)
{
      gboolean marked;
      gboolean is_html = FALSE;
      GString * out = g_string_new (NULL);
      char * reply_portion;
      const char * march;
      PString line = PSTRING_INIT;

      /* get reply portion */
      reply_portion = g_strdup ((const char*) g_object_get_data (G_OBJECT(message), PAN_REPLY_PORTION));
      marked = reply_portion != NULL;
      if (reply_portion == NULL)
            reply_portion = g_mime_message_get_body (message, TRUE, &is_html);
      g_strchomp (reply_portion);

      /* build each quoted line */
      march = reply_portion;
      while (get_next_token_pstring (march, '\n', &march, &line)) {
            g_string_append (out, *line.str=='>' ? ">" : "> ");
            g_string_append_len (out, line.str, line.len);
            g_string_append_c (out, '\n');
      }

      /* return the result */
      g_free (reply_portion);
      return g_string_free (out, FALSE);
}

/***
****
***/

static gboolean
is_posting (ComposeType type)
{
      return type==NNTP_POST || type==NNTP_REPLY;
}

static gboolean
is_original (ComposeType type)
{
      return type==NNTP_POST || type==EDIT_ORIGINAL;
}

static char*
make_reply_string (const char* string)
{
      char * retval = NULL;

      if (string != NULL)
      {
            if (g_strncasecmp ("Re: ", string, 4))
                  retval = g_strconcat ("Re: ", string, NULL);
            else
                  retval = g_strdup (string);
      }

      return retval;
}

static void
rot13_cb (gpointer user_data, int action, GtkWidget * w)
{
      Compose * compose = (Compose*) user_data;
      GtkTextBuffer * buffer = compose->body_buffer;
      GtkTextIter start;
      GtkTextIter end;

      if (gtk_text_buffer_get_selection_bounds (buffer, &start, &end))
      {
            char * str = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
            text_massager_rot13_inplace (compose->text_massager, str);
            gtk_text_buffer_delete (buffer, &start, &end);
            gtk_text_buffer_insert (buffer, &start, str, strlen(str));
            g_free (str);
      }
}
static void
rot13_cb2 (GtkWidget * w, gpointer user_data)
{
      rot13_cb (user_data, 0, w);
}

static void
wrap_cb (gpointer user_data, int action, GtkWidget * w)
{
      Compose * compose = (Compose*) user_data;
      GtkTextBuffer * buffer;
      char * body;
      char * new_body;
      gboolean b;
      GtkTextIter start;
      GtkTextIter end;
      debug_enter ("wrap_cb");

      /* get the current body */
      buffer = compose->body_buffer;
      gtk_text_buffer_get_bounds (buffer, &start, &end);
      body = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
      new_body = text_massager_fill (compose->text_massager, body);

      /* turn off our own wrapping while we fill the body pane */
      b = wrap_is_enabled;
      wrap_is_enabled = FALSE;
      update_body_pane (compose->text_massager, compose->body_buffer, new_body, FALSE);
      wrap_is_enabled = b;

      /* cleanup */
      g_free (body);
      g_free (new_body);
      debug_exit ("wrap_cb");
}

static void
wrap_cb2 (GtkWidget * w, gpointer user_data)
{
      wrap_cb (user_data, 0, w);
}

/**
***
**/

static void
message_window_cut_cb (gpointer user_data, int action, GtkWidget * w)
{
      Compose * compose = (Compose*) user_data;
      GtkWidget * focus = gtk_window_get_focus (GTK_WINDOW(compose->window));
      g_signal_emit_by_name (focus, "cut_clipboard");
}

static void
message_window_copy_cb (gpointer user_data, int action, GtkWidget * w)
{
      Compose * compose = (Compose*) user_data;
      GtkWidget * focus = gtk_window_get_focus (GTK_WINDOW(compose->window));
      g_signal_emit_by_name (focus, "copy_clipboard");
}

static void
message_window_paste_cb (gpointer user_data, int action, GtkWidget * w)
{
      Compose * compose = (Compose*) user_data;
      GtkWidget * focus = gtk_window_get_focus (GTK_WINDOW(compose->window));
      g_signal_emit_by_name (focus, "paste_clipboard");
}

static void
message_window_destroy (Compose * compose)
{
      pan_lock();
      gtk_widget_destroy (compose->window);
      pan_unlock();
}

static void
message_window_close (gpointer user_data, int action, GtkWidget * widget)
{
      Compose * compose = (Compose*) user_data;
      message_window_destroy (compose);
}


static void
update_title_with_subject (GtkWidget * widget, Compose * compose)
{
      gtk_window_set_title (GTK_WINDOW(compose->window),
                            gtk_entry_get_text (GTK_ENTRY(widget)));
}


/***
****
****   ATTACHMENTS
****
***/

#if 0
typedef enum
{
      ENCODING_BASE64,
      ENCODING_UUENC,
      ENCODING_YENC
}
PanEncoding;

static PanEncoding
get_encoding_type (Compose * compose)
{
      PanEncoding retval;

      if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(compose->encoding_mime_tb)))
            retval = ENCODING_BASE64;
      else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(compose->encoding_uuenc_tb)))
            retval = ENCODING_UUENC;
      else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(compose->encoding_yenc_tb)))
            retval = ENCODING_YENC;
      else
            pan_warn_if_reached ();

      return retval;
}

static gulong
get_estimated_encoded_size (gulong raw_size, PanEncoding encoding)
{
      switch (encoding) {
            case ENCODING_BASE64: raw_size *= 1.33; break;
            case ENCODING_UUENC: raw_size *= 1.35; break;
            case ENCODING_YENC: raw_size *= 1.03; break;
      }
                            
      return raw_size;
}

static void
refresh_attachment_page (Compose * mw)
{
      size_t raw_size = 0;
      size_t lines_per_part = 0;
      GString * gstr = g_string_new (NULL);

      /* get lines-per-part */
      lines_per_part = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON(mw->attachment_lines_per_part_sb));

      /* get the unencoded size */
      if (1) {
            int i;
            GtkCList * clist = GTK_CLIST(mw->file_clist);
            for (i=0; i<clist->rows; ++i) {
                  const char * filename = gtk_clist_get_row_data (clist, i);
                  raw_size += pan_file_get_size (filename);
            }
      }

      /* update mime label */
      if (1) {
            gulong encoded_size = get_estimated_encoded_size (raw_size, ENCODING_BASE64);
            gulong lines = encoded_size / 45;
            gulong parts = (lines/lines_per_part) + 1;

            g_string_assign (gstr, _("Mime (single-part posts only)"));
            if (lines!=0 && parts==1)
                  g_string_append_printf (gstr, _(" (%lu lines in 1 article)"), lines);
            else if (lines!=0)
                  g_string_append_printf (gstr, _(" (%lu lines in %lu articles)"), lines, parts);
            gtk_label_set_text (GTK_LABEL(GTK_BIN(mw->encoding_mime_tb)->child), gstr->str);

            if (parts>1 && gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(mw->encoding_mime_tb)))
                  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(mw->encoding_uuenc_tb), TRUE);
            gtk_widget_set_sensitive (mw->encoding_mime_tb, parts<2);
      }

      /* update uuenc label */
      if (1) {
            gulong encoded_size = get_estimated_encoded_size (raw_size, ENCODING_UUENC);
            gulong lines = encoded_size / 45;
            gulong parts = (lines/lines_per_part) + 1;

            g_string_assign (gstr, _("UUEncoded (universally accepted)"));
            if (lines!=0 && parts==1)
                  g_string_append_printf (gstr, _(" (%lu lines in 1 article)"), lines);
            else if (lines!=0)
                  g_string_append_printf (gstr, _(" (%lu lines in %lu articles)"), lines, parts);

            gtk_label_set_text (GTK_LABEL(GTK_BIN(mw->encoding_uuenc_tb)->child), gstr->str);
      }

      /* update yenc label */
      if (1) {
            gulong encoded_size = get_estimated_encoded_size (raw_size, ENCODING_YENC);
            gulong lines = encoded_size / 45;
            gulong parts = (lines/lines_per_part) + 1;

            g_string_assign (gstr, _("yEnc (30 percent smaller than UUEnc but less universal)"));
            if (lines!=0 && parts==1)
                  g_string_append_printf (gstr, _(" (%lu lines in 1 article)"), lines);
            else if (lines!=0)
                  g_string_append_printf (gstr, _(" (%lu lines in %lu articles)"), lines, parts);
            gtk_label_set_text (GTK_LABEL(GTK_BIN(mw->encoding_yenc_tb)->child), gstr->str);
      }

      g_string_free (gstr, TRUE);
}

static void
attachments_lines_per_part_changed (GtkWidget * w, Compose * mw)
{
      refresh_attachment_page (mw);
}

static void
attachments_filesel_response_cb (GtkDialog * dialog, int response, gpointer user_data)
{
      if (response == GTK_RESPONSE_OK)
      {
            Compose * mw = (Compose*) user_data;
            int row;
            char * text[2];
            char line_buf[32];
            const char * selected_filename = gtk_file_selection_get_filename (GTK_FILE_SELECTION(dialog));
            GtkCList * clist = GTK_CLIST(mw->file_clist);

            /* insert the file */
            g_snprintf (line_buf, sizeof(line_buf), "%lu", (gulong)(pan_file_get_size(selected_filename)/1024));
            text[0] = (char*) selected_filename;
            text[1] = line_buf;
            row = gtk_clist_insert (clist, -1, text);
            gtk_clist_set_row_data (clist, row, g_strdup(selected_filename));

            /* update the line counts */
            refresh_attachment_page (mw);
      }

      gtk_widget_destroy (GTK_WIDGET(dialog));
}

static void
attachment_add_button_clicked_cb (GtkButton * button, gpointer user_data)
{
      GtkWidget * w = gtk_file_selection_new(_("Select the file to attach."));
      g_signal_connect (w, "response", G_CALLBACK(attachments_filesel_response_cb), user_data);
      gtk_widget_show (w);
}

static void
attachment_remove_button_clicked_cb (GtkButton * button,
                                     gpointer user_data)
{
      GList * l;
      Compose * mw = (Compose*) user_data;
      GtkCList * clist = GTK_CLIST (mw->file_clist);

      l = clist->selection;
      if (l != NULL)
      {
            /* remove the file */
            int row = GPOINTER_TO_INT (l->data);
            char * filename = gtk_clist_get_row_data (clist, row);
            g_free (filename);
            gtk_clist_remove (clist, row);

            /* update the line counts */
            refresh_attachment_page (mw);
      }
}

static void
attachment_clist_selection_changed_cb (GtkCList        * clist,
                                       int               row,
                                       int               column,
                                       GdkEventButton  * event,
                                       gpointer          user_data)
{
      Compose * mw = (Compose*) user_data;
      gboolean has_selection = clist->selection != NULL;
      gtk_widget_set_sensitive (mw->file_remove_button, has_selection);
}

static GtkWidget *
attachment_page (Compose *mw)
{
      char * titles[2];
      GtkAdjustment * a;
      GtkWidget * w;
      GtkWidget * h;
      GtkWidget * v;
      GtkWidget * frame;
      GtkWidget * top;

      top = gtk_vbox_new (FALSE, GUI_PAD);


      /**
      ***  Files Frame
      **/

      frame = gtk_frame_new (_("Files to Attach"));
      gtk_container_set_border_width (GTK_CONTAINER(frame), GUI_PAD_SMALL);

      v = gtk_vbox_new (FALSE, 0);
      gtk_container_set_border_width (GTK_CONTAINER(v), GUI_PAD);
      gtk_container_add (GTK_CONTAINER(frame), v);
      gtk_box_pack_start (GTK_BOX(top), frame, TRUE, TRUE, 0);

      /* Files clist */
      titles[0] = _("Filename");
      titles[1] = _("Kilobytes");
      mw->file_clist = w = gtk_clist_new_with_titles (2, titles);
      gtk_widget_set_usize (GTK_WIDGET(w), -1, 160);
      gtk_clist_set_column_width (GTK_CLIST(w), 0, 400);
      gtk_clist_set_column_width (GTK_CLIST(w), 1, 80);
      g_signal_connect (w, "select_row",
                        G_CALLBACK(attachment_clist_selection_changed_cb), mw);
      g_signal_connect (w, "unselect_row",
                        G_CALLBACK(attachment_clist_selection_changed_cb), mw);
        w = gtk_scrolled_window_new (NULL, NULL);
        gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW(w),
                GTK_POLICY_AUTOMATIC,
                GTK_POLICY_AUTOMATIC);
        gtk_container_add (GTK_CONTAINER(w), mw->file_clist);
      gtk_box_pack_start (GTK_BOX(v), w, TRUE, TRUE, GUI_PAD_SMALL);

      h = gtk_hbox_new (FALSE, 0);
      gtk_box_pack_start (GTK_BOX(v), h, FALSE, FALSE, 0);

      /* "Add" button */
      mw->file_add_button = w = gtk_button_new_from_stock (GTK_STOCK_ADD);
      g_signal_connect (w, "clicked", G_CALLBACK(attachment_add_button_clicked_cb), mw);
      gtk_tooltips_set_tip (GTK_TOOLTIPS(ttips), w,
            _("Add a File to the Attachment List"), NULL);
      gtk_box_pack_start (GTK_BOX(h), w, FALSE, FALSE, 0);

      /* "Remove" button */
      mw->file_remove_button = w = gtk_button_new_from_stock (GTK_STOCK_CUT);
      gtk_widget_set_sensitive (w, FALSE);
      g_signal_connect (w, "clicked", G_CALLBACK(attachment_remove_button_clicked_cb), mw);
      gtk_tooltips_set_tip (GTK_TOOLTIPS(ttips), w,
            _("Remove a File from the Attachment List"), NULL);
      gtk_box_pack_end (GTK_BOX(h), w, FALSE, FALSE, 0);

      /**
      ***  Parts Frame
      **/

      frame = gtk_frame_new (_("Parts"));
      gtk_container_set_border_width (GTK_CONTAINER(frame), GUI_PAD_SMALL);
      gtk_box_pack_start (GTK_BOX(top), frame, FALSE, FALSE, 0);

      h = gtk_hbox_new (FALSE, GUI_PAD);
      gtk_container_set_border_width (GTK_CONTAINER(h), GUI_PAD_BIG);
      gtk_container_add (GTK_CONTAINER(frame), h);

      w = gtk_label_new (_("Lines per article:"));
      gtk_box_pack_start (GTK_BOX(h), w, FALSE, FALSE, 0);
      a = GTK_ADJUSTMENT(gtk_adjustment_new (5000, 100, 15000, 100, 10, 10));
      mw->attachment_lines_per_part_sb = w = gtk_spin_button_new (a, 0, 0);
      g_signal_connect (a, "value_changed",
                        G_CALLBACK(attachments_lines_per_part_changed), mw);
      gtk_widget_set_usize (GTK_WIDGET(w), 70, 0);
      gtk_box_pack_start (GTK_BOX(h), w, FALSE, FALSE, 0);


      /**
      ***  Encoding Frame
      **/

      frame = gtk_frame_new (_("Estimated Article Size"));
      gtk_container_set_border_width (GTK_CONTAINER(frame), GUI_PAD_SMALL);
      gtk_box_pack_start (GTK_BOX(top), frame, FALSE, FALSE, 0);

      v = gtk_vbox_new (FALSE, 0);
      gtk_container_set_border_width (GTK_CONTAINER(v), GUI_PAD_BIG);
      gtk_container_add (GTK_CONTAINER(frame), v);

      w = gtk_radio_button_new_with_label (NULL, _("UUEncoded (universally accepted)"));
      mw->encoding_uuenc_tb = w;
      gtk_box_pack_start (GTK_BOX(v), w, FALSE, FALSE, 0);
      w = gtk_radio_button_new_with_label_from_widget (GTK_RADIO_BUTTON(w), _("yEnc (30 percent smaller than UUEnc but less universal)"));
      mw->encoding_yenc_tb = w;
      gtk_box_pack_start (GTK_BOX(v), w, FALSE, FALSE, 0);
      w = gtk_radio_button_new_with_label_from_widget (GTK_RADIO_BUTTON(w), _("Mime (single-part posts only)"));
      mw->encoding_mime_tb = w;
      gtk_box_pack_start (GTK_BOX(v), w, FALSE, FALSE, 0);


      refresh_attachment_page (mw);
      return top;
}
#endif

/***
****
****   EXTRA HEADERS
****
***/

static const char *
determine_charset_for_message (const Compose *mw)
{
      const char * charset = NULL; 
  
      /* does the article specify a charset ? */
      if (mw && mw->message)
            charset = pan_g_mime_message_get_charset (mw->message);

      /* is it one we support ? */
      if (!pan_charset_picker_has (charset)) {
            charset = NULL;
      }

      /* otherwise, use the profile's charset */
      if (charset == NULL) {
            Identity * id = is_nonempty_string (mw->identity_name)
                  ? identity_manager_get_identity (mw->identity_name)
                  : NULL;

            if (id != NULL)
            {
                  charset = id->posting_charset;
                  pan_object_unref (PAN_OBJECT(id));
            }
      }

      /* fallback #1: use group's charset */
      /* fallback #2: use user's locale to guess the charset */
      if (charset == NULL) {
            const Group * group = articlelist_get_group ();

            charset = group ? group_get_default_charset (group) :
                            get_charset_from_locale ();
      }

      return charset;
}

static GtkWidget *
extra_headers_page (Compose * mw)
{
      GtkWidget * scroll;
      GtkWidget * w;
      GtkWidget * t; 
      int row = 0;
      gboolean b;
      char buf[512];

      /**
      ***  The top table: common headers
      **/

      t = pan_hig_workarea_create ();

      pan_hig_workarea_add_section_title (t, &row, _("More Headers"));
      pan_hig_workarea_add_section_spacer (t, row, 4);

            g_snprintf (buf, sizeof(buf), "<b>%s:</b>", _("Followup-To"));
            w = pan_hig_workarea_add_label (t, row, buf);
            mw->followup_to = gtk_entry_new ();
            gtk_tooltips_set_tip (GTK_TOOLTIPS(ttips), mw->followup_to,
                  _("The newsgroups where replies to "
                    "your message should go.  This is only "
                    "needed if it differs from the \"Post To Groups\" "
                    "header. "
                    "\nTo direct all replies to your email address, "
                    "use \"Followup-To: poster\""), NULL);
            gtk_table_attach (GTK_TABLE(t), mw->followup_to, 3, 4, row, row+1, GTK_FILL|GTK_EXPAND|GTK_SHRINK, 0, 0, 0);
            ++row;

            g_snprintf (buf, sizeof(buf), "<b>%s:</b>", _("Reply-To"));
            w = pan_hig_workarea_add_label (t, row, buf);
            mw->reply_to = gtk_entry_new ();
            gtk_tooltips_set_tip (GTK_TOOLTIPS(ttips), mw->reply_to,
                  _("The email account where mail replies to "
                    "your posted message should go.  This is only "
                    "needed if it differs from the \"From\" "
                    "header."), NULL);
            gtk_table_attach (GTK_TABLE(t), mw->reply_to, 3, 4, row, row+1, GTK_FILL|GTK_EXPAND|GTK_SHRINK, 0, 0, 0);
            ++row;

            g_snprintf (buf, sizeof(buf), "<b>%s:</b>", _("Organization"));
            w = pan_hig_workarea_add_label (t, row, buf);
            mw->organization = gtk_entry_new ();
            gtk_tooltips_set_tip (GTK_TOOLTIPS(ttips), mw->organization, _("The organization you're associated with."), NULL);
            gtk_table_attach (GTK_TABLE(t), mw->organization, 3, 4, row, row+1, GTK_FILL|GTK_EXPAND|GTK_SHRINK, 0, 0, 0);
            ++row;

            g_snprintf (buf, sizeof(buf), "<b>%s:</b>", _("Charset"));
            w = pan_hig_workarea_add_label (t, row, buf);
            mw->charset_om = pan_charset_picker_new (determine_charset_for_message (mw));
            gtk_table_attach (GTK_TABLE(t), mw->charset_om, 3, 4, row, row+1, GTK_FILL|GTK_EXPAND|GTK_SHRINK, 0, 0, 0);
            ++row;

      pan_hig_workarea_add_section_divider (t, &row);

      pan_hig_workarea_add_section_title (t, &row, _("Custom Headers"));

            w = gtk_text_view_new ();
            gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW(w), GTK_WRAP_NONE);
            gtk_text_view_set_editable (GTK_TEXT_VIEW(w), TRUE);
            mw->custom_headers_text = w;
            scroll = gtk_scrolled_window_new (NULL, NULL);
            gtk_container_set_border_width (GTK_CONTAINER(scroll), GUI_PAD_SMALL);
            gtk_container_add (GTK_CONTAINER(scroll), w);
            gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
            gtk_table_attach (GTK_TABLE(t), scroll, 1, 4, row, row+1, GTK_FILL|GTK_EXPAND|GTK_SHRINK, GTK_FILL|GTK_EXPAND|GTK_SHRINK, 0, 0);
            ++row;

            w = gtk_check_button_new_with_label (_("Add \"User-Agent\" header"));
            b = pan_config_get_bool (KEY_POST_USER_AGENT_HEADER, DEFAULT_VALUE_POST_USER_AGENT_HEADER);
            gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(w), b);
            mw->post_user_agent_tb = w;
            gtk_table_attach (GTK_TABLE(t), w, 1, 4, row, row+1, GTK_FILL|GTK_EXPAND|GTK_SHRINK, 0, 0, 0);
            ++row;

            w = gtk_check_button_new_with_label (_("Add \"Message-Id\" header"));
            b = pan_config_get_bool (KEY_POST_MESSAGE_ID_HEADER, DEFAULT_VALUE_POST_MESSAGE_ID_HEADER);
            gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(w), b);
            mw->post_message_id_tb = w;
            gtk_table_attach (GTK_TABLE(t), w, 1, 4, row, row+1, GTK_FILL|GTK_EXPAND|GTK_SHRINK, 0, 0, 0);
            ++row;

      return t;
}

static void
mark_set_cb (GtkTextBuffer * text_buffer, GtkTextIter * arg1, GtkTextIter * arg2, gpointer user_data)
{
      int row, col;
      GtkTextIter iter;
      GtkTextMark * mark;
      char buf[512];

      mark = gtk_text_buffer_get_insert (text_buffer);
      gtk_text_buffer_get_iter_at_mark (text_buffer, &iter, mark);

      row = gtk_text_iter_get_line (&iter) + 1;
      col = gtk_text_iter_get_line_offset (&iter) + 1;
      g_snprintf (buf, sizeof(buf), _("Line %d, Column %d"), row, col);

      gtk_label_set_text (GTK_LABEL(user_data), buf);
}

static GtkWidget*
create_post_info_pane (Compose *mw)
{
      GtkWidget * w;
      GtkWidget * l;
      GtkWidget * t;
      GtkWidget * notebook;
      GtkWidget * frame;
      char buf[512];
      int row = 0;

      pan_lock();
      
      t = pan_hig_workarea_create ();

        w = gtk_alignment_new (0.0f, 0.0f, 0.0f, 0.0f);
        gtk_widget_set_usize (w, 12u, 0u);
        gtk_table_attach (GTK_TABLE(t), w, 2, 3, row, row+4, 0, 0, 0, 0);

      g_snprintf (buf, sizeof(buf), "<b>%s:</b>", _("From"));
      l = pan_hig_workarea_add_label (t, row, buf);
      w = mw->from_om = gtk_option_menu_new ();
      gtk_table_attach (GTK_TABLE(t), w, 3, 4, row, row+1, GTK_FILL|GTK_EXPAND|GTK_SHRINK, 0, 0, 0);
      ++row;

      g_snprintf (buf, sizeof(buf), "<b>%s:</b>", _("Subject"));
      l = pan_hig_workarea_add_label (t, row, buf);
      w = mw->subject = gtk_entry_new ();
      gtk_table_attach (GTK_TABLE(t), w, 3, 4, row, row+1, GTK_FILL|GTK_EXPAND|GTK_SHRINK, 0, 0, 0);
      g_signal_connect (mw->subject, "changed", G_CALLBACK(update_title_with_subject), mw);
      ++row;

      g_snprintf (buf, sizeof(buf), "<b>%s:</b>", _("Newsgroups"));
      l = pan_hig_workarea_add_label (t, row, buf);
      w = mw->newsgroups = gtk_entry_new ();
      gtk_table_attach (GTK_TABLE(t), w, 3, 4, row, row+1, GTK_FILL|GTK_EXPAND|GTK_SHRINK, 0, 0, 0);
      ++row;

      g_snprintf (buf, sizeof(buf), "<b>%s:</b>", _("Mail To"));
      l = pan_hig_workarea_add_label (t, row, buf);
      w = mw->users = gtk_entry_new();
      gtk_table_attach (GTK_TABLE(t), w, 3, 4, row, row+1, GTK_FILL|GTK_EXPAND|GTK_SHRINK, 0, 0, 0);
      ++row;

      w = create_body_pane_nolock (mw);
      gtk_table_attach (GTK_TABLE(t), w, 1, 4, row, row+1, GTK_FILL|GTK_EXPAND|GTK_SHRINK, GTK_FILL|GTK_EXPAND|GTK_SHRINK, 0, 0);
      ++row;

      w = gtk_hbox_new (FALSE, GUI_PAD);
      gtk_table_attach (GTK_TABLE(t), w, 1, 4, row, row+1, GTK_FILL|GTK_EXPAND|GTK_SHRINK, 0, 0, 0);
      ++row;
            l = gtk_alignment_new (0.0f, 0.0f, 0.0f, 0.0f);
            gtk_box_pack_start_defaults (GTK_BOX(w), l);

            frame = gtk_frame_new (NULL);
            gtk_frame_set_shadow_type (GTK_FRAME(frame), GTK_SHADOW_IN);
            gtk_box_pack_end (GTK_BOX(w), frame, FALSE, FALSE, 0);

                  l = gtk_label_new ("");
                  gtk_container_add (GTK_CONTAINER(frame), l);
                  mw->cursor_position_signal_handler_id = g_signal_connect (mw->body_buffer, "mark-set", G_CALLBACK(mark_set_cb), l);

      /*-----
       * Fill the notebook
       * ---- */
      
      notebook = gtk_notebook_new ();

        gtk_notebook_append_page (GTK_NOTEBOOK (notebook), t,
                                gtk_label_new (_("Message")));
        gtk_notebook_append_page (GTK_NOTEBOOK (notebook), extra_headers_page(mw),
                                gtk_label_new (_("More Headers")));

      pan_unlock();

      /* listen for changes to the identity manager */
      pan_callback_add (identity_manager_get_identities_changed_callback(),
            identities_changed_cb, mw);

      return notebook;
}

/***
**** ATTRIBUTION LINE
***/

/*
 * Generates an attribution line
 */

static char * 
get_author_utf8 (GMimeMessage * message)
{
      const char * charset = NULL;
      const char * author = NULL;
      char * author_utf8 = NULL;

      if (message == NULL)
            return g_strdup ("");

      charset = pan_g_mime_message_get_charset (message);

      if (charset == NULL)
      {
            Group * group = articlelist_get_group ();
            charset = group ? group_get_default_charset (group) : get_charset_from_locale ();
      }

      author = g_mime_message_get_sender (message);
      author_utf8 = pan_header_to_utf8 (author, -1, charset);

      if (author_utf8 == NULL)
      {
            author_utf8 = g_strdup (author);
            log_add_va (LOG_ERROR, _("Could not convert \"%s\" to UTF-8. Article may not display correctly."), author);
      }

      return author_utf8;
}

static char*
message_gen_attribution_string (GMimeMessage    * message,
                                const char      * attribution_line)
{
      GString * gstr = NULL;

      /* get the unsubstituted string */
      gstr = g_string_new (attribution_line);

      /* substitutions: message-id */
      if (strstr (attribution_line, "%i")) {
            const char * cpch = g_mime_message_get_message_id (message);
            pan_g_string_replace (gstr, "%i", cpch);
      }

      /* substitutions: date */ 
      if (strstr (attribution_line, "%d"))
      {
            char * pch = g_mime_message_get_date_string (message);
            pan_g_string_replace (gstr, "%d", pch);
            g_free (pch);
      }

      /* substitutions: full author */
      if (strstr (attribution_line, "%a")) {
            char * author_utf8 = get_author_utf8 (message);
            pan_g_string_replace (gstr, "%a", author_utf8);
            g_free (author_utf8);
      }

      /* substitutions: author name */
      if (strstr (attribution_line, "%n") != NULL) {
            char buf[512] = { '\0' };
            char * author_utf8 = get_author_utf8 (message);
            InternetAddressList * list = internet_address_parse_string (author_utf8);
            if (list != NULL)
            {
                  InternetAddress * ia = list->address;
                  if (ia!=NULL && ia->type==INTERNET_ADDRESS_NAME) {
                        const PString addr = pstring_shallow (ia->value.addr, -1);
                        const PString name = pstring_shallow (ia->name, -1);
                        article_format_short_author_str (&addr, &name, buf, sizeof(buf));
                  }
                  internet_address_list_destroy (list);
            }
            pan_g_string_replace (gstr, "%n", buf);
            g_free (author_utf8);
      }

      return g_string_free (gstr, FALSE);
}

/***
**** SIGNATURE
***/

static char *
load_signature (const char * sig_file)
{
      char * pch;
      char * sig = NULL;
      GError * err = NULL;
      int argc = 0;
      char ** argv = NULL;

      /* process file name */
      pch = pan_file_normalize (sig_file, NULL);
      g_return_val_if_fail (pch!=NULL, NULL);

      /* some care must be given to the order of the steps we use to
       * to test & poke the sig file.  Older versions of Pan parsed to the shell
       * and used argv[0] as the filename, which fails on Windows if there are
       * spaces in the filename.  Some test cases:
       * 1. "fortune -s -o" (to keep the sig length polite and content rude)
       * 2. "c:\program files\pan\signature.txt" */

      if (g_file_test (pch, G_FILE_TEST_EXISTS))
      {
            if (g_file_test (pch, G_FILE_TEST_IS_EXECUTABLE))
            {
                  /* file is executable */
                  argc = 1;
                  argv = g_new (char*, 2);
                  argv[0] = g_strdup (pch);
                  argv[1] = NULL; /* this is for g_strfreev() */
            }
      }
      else if (!g_shell_parse_argv (pch, &argc, &argv, &err))
      {
            log_add_va (LOG_ERROR, _("Couldn't parse signature command \"%s\""), pch);
            log_add (LOG_ERROR, err->message);
            g_error_free (err);
            err = NULL;
      }

      /* try to execute the file... */
      if (argc>0 && argv!=NULL && argv[0]!=NULL && g_file_test (argv[0], G_FILE_TEST_IS_EXECUTABLE))
      {
            char * spawn_stdout = NULL;
            char * spawn_stderr = NULL;
            int exit_status = 0;

            if (g_spawn_sync (NULL, argv, NULL, 0, NULL, NULL, &spawn_stdout, &spawn_stderr, &exit_status, NULL))
                  sig = g_strdup (spawn_stdout);
            if (is_nonempty_string (spawn_stderr))
                  log_add_va (LOG_ERROR, "%s", spawn_stderr);

            g_free (spawn_stderr);
            g_free (spawn_stdout);
      }

      /* if the file wasn't executable, or we couldn't execute it, load the file. */
      if (sig == NULL)
      {
            if (!g_file_get_contents (pch, &sig, NULL, &err))
            {
                  log_add_va (LOG_ERROR, _("Couldn't read signature file \"%s\": %s"), pch, err->message);
                  g_error_free (err);
                  err = NULL;
            }
      }

      /* Convert signature to UTF-8. Since the signature is a local file,
       * we assume the contents is in the user's locale's charset.
       * If we can't convert, clear the signature. Otherwise, we'd add an
       * charset-encoded sig (say 'iso-8859-1') to the body (in UTF-8),
       * which could result in a blank message in the composer window. */
      if (sig!=NULL)
      {
            const char * sig_utf8 = NULL;
            char * freeme = NULL;
            sig_utf8 = pan_utf8ize (sig, -1, &freeme);
            if (sig_utf8 != NULL) {
                  replace_gstr (&sig, g_strdup (sig_utf8));
                  g_free (freeme);
            }
            else {
                  log_add_va (LOG_ERROR, _("Could not convert signature file '%s' to UTF-8. Signature must be in '%s'"), pch, g_mime_charset_locale_name ());
                  g_free (sig);
                  sig = NULL;
            }
      }

      /* Cleanup */
      g_strfreev (argv);
      g_free (pch);

      return sig;
}


/***
**** CUSTOM HEADERS
****
****/

static void
remove_custom_headers (GtkWidget * header_view, const Identity * id)
{
      int             i;
      char          * text;
      char         ** lines;
      GString       * new_body;
      GtkTextIter     start;
      GtkTextIter     end;
      GtkTextBuffer * buffer;

      g_return_if_fail (GTK_IS_TEXT_VIEW(header_view));
      g_return_if_fail (id!=NULL);
      g_return_if_fail (id->custom_headers!=NULL);

      buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW(header_view));
      gtk_text_buffer_get_bounds (buffer, &start, &end);
      text = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
      lines = g_strsplit (text, "\n", -1);
            new_body = g_string_new (NULL);
      for (i=0; lines != NULL && lines[i] != NULL; i++)
      {
            char * delimit;

            g_strstrip (lines[i]);
            delimit = strchr (lines[i], ':');

            if (delimit != NULL)
            {
                  int       h;
                  gboolean  match;
                  char    * n =  g_strndup (lines[i], delimit - lines[i]);
                  char    * v = g_strdup (delimit + 1);

                  g_strstrip (n);
                  g_strstrip (v);

                  /*
                   * If it's a custom header from this ID, 
                   * don't add it again.
                   */

                  match = FALSE;
                  for (h = 0; h < id->custom_headers->len; h++)
                  {
                        Header * header = g_ptr_array_index (id->custom_headers, h);

                        if (!pan_strcmp (n, header->name))
                        {
                              match = TRUE;
                              break;
                        }
                  }
                  if (!match)
                        g_string_append_printf (new_body, "%s: %s\n", 
                              n, v);

                  g_free (v);
                  g_free (n);
            }
      }

      /* rebuild text buffer */
      gtk_text_buffer_delete (buffer, &start, &end);
      gtk_text_buffer_insert (buffer, &start, new_body->str, new_body->len);

      /* cleanup */
      g_free (text);
      g_strfreev (lines);
      g_string_free (new_body, TRUE);
}

static void
add_custom_headers (GtkWidget * header_view, const Identity * id)
{
      int             i;
      char          * text;
      GString       * new_body;
      GtkTextIter     start;
      GtkTextIter     end;
      GtkTextBuffer * buffer;

      /* sanity clause */
      g_return_if_fail (GTK_IS_TEXT_VIEW(header_view));
      g_return_if_fail (id!=NULL);
      g_return_if_fail (id->custom_headers!=NULL);

      /* To avoid duplicates, delete them first */
      remove_custom_headers (header_view, id);

      /* get the text into a GString */
      buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW(header_view));
      gtk_text_buffer_get_bounds (buffer, &start, &end);
      text = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
      new_body = g_string_new (text);

      /* append the custom headers */
      for (i=0; i<id->custom_headers->len; i++) {
            Header * h = g_ptr_array_index (id->custom_headers, i);
            g_string_append_printf (new_body, "%s: %s\n", h->name, h->value);
      }

      /* rebuild text buffer */
      gtk_text_buffer_delete (buffer, &start, &end);
      gtk_text_buffer_insert (buffer, &start, new_body->str, new_body->len);

      /* cleanup */
      g_free (text);
      g_string_free (new_body, TRUE);
}

/***
**** SETTING IDENTITIES 
***/

static void
set_entry_to_next_if_empty_or_prev (GtkEntry * entry, const char * prev, const char * next)
{
      const char * cur = gtk_entry_get_text (entry);

      if (!is_nonempty_string(cur) || !pan_strcmp(cur,prev))
            gtk_entry_set_text (entry, next ? next : "");
}

static void
apply_identity (Compose * compose, const char * new_id_name)
{
      Identity * new_id = identity_manager_get_identity (new_id_name);
      Identity * old_id = identity_manager_get_identity (compose->identity_name);

      /* custom headers */
      if (old_id != NULL)
            remove_custom_headers (compose->custom_headers_text, old_id);
      if (new_id != NULL)
            add_custom_headers (compose->custom_headers_text, new_id);

      /* replace the organization */
      set_entry_to_next_if_empty_or_prev (GTK_ENTRY(compose->organization),
                                          old_id ? old_id->organization : NULL,
                                          new_id ? new_id->organization : NULL);

      /* replace the reply-to */
      set_entry_to_next_if_empty_or_prev (GTK_ENTRY(compose->reply_to),
                                          old_id ? old_id->reply_to : NULL,
                                          new_id ? new_id->reply_to : NULL);

      /* body manipulation: attribution and signature */
      {
            char         * body;
            GtkTextIter    start;
            GtkTextIter    end;
            gboolean       sig_removed;
            int            insert_pos = 0;

            /* get the body */
            gtk_text_buffer_get_bounds (compose->body_buffer, &start, &end);
            body = gtk_text_buffer_get_text (compose->body_buffer, &start, &end, FALSE);

            /* replace the attribution */
            if (!is_original (compose->type))
            {
                  const char * old_attribution = compose->last_attribution_text;
                  char * new_attribution = new_id ? message_gen_attribution_string (compose->message, new_id->attribution) : g_strdup("");

                  if (is_nonempty_string (old_attribution) && strstr (body, old_attribution))
                  {
                        const char * old_point = strstr (body, old_attribution);
                        GString * str = g_string_new_len (body, old_point - body);
                        g_string_append (str, new_attribution);
                        g_string_append (str, old_point + strlen(old_attribution));
                        replace_gstr (&body, g_string_free (str, FALSE));
                  }
                  else if (is_nonempty_string (new_attribution))
                  {
                        replace_gstr (&new_attribution, text_massager_fill (compose->text_massager, new_attribution));
                        replace_gstr (&body, g_strdup_printf ("%s\n\n%s", new_attribution, body));
                  }

                  replace_gstr (&compose->last_attribution_text, new_attribution);
            }

            /* replace the signature */
            sig_removed = pan_remove_signature (body);

            if (new_id==NULL || !is_nonempty_string(new_id->signature))
                  insert_pos = strlen(body) - 1;
            else
            {
                  char * sig = load_signature (new_id->signature);

                  if (is_nonempty_string (sig))
                  {
                        const char * divider;

                        if (pan_find_signature_delimiter (sig, NULL) != SIG_NONE)
                              divider = "";
                        else if (sig_removed)
                              divider = "-- \n";
                        else
                              divider = "\n-- \n";

                        if (is_nonempty_string (body))
                              insert_pos = strlen (body) - 1;
                        else
                              insert_pos = 0;

                        replace_gstr (&body, g_strdup_printf ("%s%s%s", body, divider, sig));
                  }

                  g_free (sig);
            }

            update_body_pane (compose->text_massager, compose->body_buffer, body, FALSE);

            {
                  GtkTextIter iter;
                  gtk_text_buffer_get_iter_at_offset (compose->body_buffer, &iter, insert_pos);
                  gtk_text_buffer_move_mark_by_name (compose->body_buffer, "insert", &iter);
                  gtk_text_buffer_move_mark_by_name (compose->body_buffer, "selection_bound", &iter);
                  gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW(compose->body_view), 
                                                gtk_text_buffer_get_mark(compose->body_buffer, "insert"),
                                                0.0, FALSE, 0.0, 0.0);
            }

            g_free (body);
      }

      /* charset */
      if (new_id!=NULL && is_nonempty_string (new_id->posting_charset))
            pan_charset_picker_set_charset (compose->charset_om, new_id->posting_charset);

      if (old_id!=NULL)
            pan_object_unref (PAN_OBJECT(old_id));

      if (new_id!=NULL) {
            replace_gstr (&compose->identity_name, g_strdup(new_id_name));
            pan_object_unref (PAN_OBJECT(new_id));
      }
}


static void
set_identity_cb (GtkMenuItem * item, gpointer compose_gpointer)
{
      const char * new_id_name = (const char *) gtk_object_get_data (GTK_OBJECT(item), "identity_name");
      Compose * compose = (Compose *) compose_gpointer;

      apply_identity (compose, new_id_name);
}

static char *
determine_default_identity (Compose *mw)
{
      char       * name = NULL;
      Identity   * id = NULL;

      g_return_val_if_fail (mw!=NULL, NULL);

      /* If we're replying/forwarding via email, use that identity */
      if (mw->type == EMAIL_REPLY ||
          mw->type == EMAIL_FORWARD)
      {
            id = identity_manager_get_default (ID_MAIL_DEFAULT);
      }

      if (id == NULL)
      {
            /* Check we're already posted with an identity */
            Group * g = articlelist_get_group ();

            if (g != NULL && is_nonempty_string(g->identity_name))
            {
                  id = identity_manager_get_identity (g->identity_name);
            }
      }

      /*
       * If it's a new message, see if there's already an author specified.
       * If so, use the identity with the same name/address.
       *
       * This is mainly here to find the right identity for feedback email.
       *
       * Note: this isn't bullet proof: several ids could have a matching
       * author and email addresses. Would be better if we could 
       * distinguish whether we have are writing an email or an article.
       */
      if (id == NULL && mw->type == EDIT_ORIGINAL && mw->message != NULL)
      {
            const char * sender = g_mime_message_get_sender (mw->message);
            InternetAddressList * list = internet_address_parse_string (sender);
            if (list != NULL) {
                  InternetAddress * ia = list->address;
                  if (ia!=NULL && ia->type==INTERNET_ADDRESS_NAME)
                        id = identity_manager_get_identity_by_author (ia->name, ia->value.addr);
                  internet_address_list_destroy (list);
            }
      }

      /* Still nothing. Just use the default identity for news */
      if (id == NULL)
      {
            id = identity_manager_get_default (ID_NEWS_DEFAULT);
      }

      if (id != NULL)
      {
            name = g_strdup (id->name);
            pan_object_unref (PAN_OBJECT(id));
      }

      return name;
}

static void
populate_identity_menu (Compose * mw, const char * id_name)
{
      int         i;
      int         index = 0;
      GtkWidget * menu = gtk_menu_new ();
      GPtrArray * identities = g_ptr_array_new ();
      const Identity  * id = NULL;

      /* get all identities */
      identity_manager_get_identities (identities);

      for (i=0; i<identities->len; ++i)
      {
            GtkWidget * item;
            char buf[512];
            gboolean have_addr;
            gboolean have_name;

            id = IDENTITY(g_ptr_array_index(identities,i));

            /* get default id */
            if (!pan_strcmp (id->name, id_name) || i == 0)
                  index = i;

            have_addr = is_nonempty_string (id->author_addr);
            have_name = is_nonempty_string (id->author_real);

              if (have_addr && have_name)
                  g_snprintf (buf, sizeof(buf), "%s: \"%s\" <%s>", id->name, id->author_real, id->author_addr);
            else if (have_addr)
                  g_snprintf (buf, sizeof(buf), "%s: %s", id->name, id->author_addr);
            else if (have_name)
                  g_snprintf (buf, sizeof(buf), "%s: %s", id->name, id->author_real);


            item = gtk_menu_item_new_with_label (buf);
            gtk_object_set_data_full (GTK_OBJECT(item), "identity_name", g_strdup(id->name), g_free);
            g_signal_connect (item, "activate", G_CALLBACK(set_identity_cb), mw);
            gtk_widget_show (item);
            gtk_menu_append (GTK_MENU(menu), item);
      }

      gtk_option_menu_set_menu (GTK_OPTION_MENU(mw->from_om), menu);
      gtk_option_menu_set_history (GTK_OPTION_MENU(mw->from_om), index);
      gtk_widget_show_all (GTK_WIDGET(mw->from_om));

      /* remember the id name */
      id = IDENTITY(g_ptr_array_index(identities, index));
      replace_gstr (&mw->identity_name, g_strdup(id->name));

      pan_g_ptr_array_foreach (identities, (GFunc)pan_object_unref, NULL);
      g_ptr_array_free (identities, TRUE);
}


static void 
identities_changed_cb (gpointer a, gpointer b, gpointer c)
{
      Compose * mw = (Compose *) c;

      populate_identity_menu (mw, mw->identity_name);

}

static void
extra_header_func (const char * name, const char * value, gpointer data)
{
      if (article_header_is_extra (name))
            g_string_append_printf ((GString*)data, "%s: %s\n", name, value);
}

static void
populate_post_info_pane (Compose *mw)
{
      char * p;
      const char * cp;
      GMimeMessage * m;
      char * default_id_name;

      g_return_if_fail (mw != NULL);

      m = mw->message;

      /**
      ***  Populate the extra headers
      **/

      {
            GString * gstr = g_string_new (NULL);

            if (mw->message!=NULL && is_original (mw->type))
                  g_mime_header_foreach (GMIME_OBJECT(mw->message)->headers, extra_header_func, gstr);

            /* add the headers */
            if (1) {
                  GtkTextView * view = GTK_TEXT_VIEW(mw->custom_headers_text);
                  GtkTextBuffer * buffer = gtk_text_view_get_buffer (view);
                  GtkTextIter start;
                  GtkTextIter end;
                  gtk_text_buffer_get_bounds (buffer, &start, &end);
                  gtk_text_buffer_delete (buffer, &start, &end);

                  if (gstr->len) {
                        char * text = pan_header_to_utf8 (gstr->str, gstr->len, NULL);
                        gtk_text_buffer_insert (buffer, &start, text, -1);
                        g_free (text);
                  }
            }

            /* cleanup */
            g_string_free (gstr, TRUE);
      }

      /**
      ***  Subject
      **/

      cp = m != NULL ? g_mime_message_get_header (m, HEADER_SUBJECT) : NULL;
      p = NULL;
      if (cp != NULL) {
            if (mw->type == EDIT_ORIGINAL)
                  p = g_strdup (cp);
            else if (mw->type == EMAIL_FORWARD)
                  p = g_strdup_printf ("[%s] %s", g_mime_message_get_header (m, HEADER_NEWSGROUPS), cp);
            else
                  p = make_reply_string (cp);
      }
      if (is_nonempty_string(p))
            pan_gtk_entry_set_text (mw->subject, p);
      g_free (p);

      /**
      ***  Populate the other pieces
      **/

      if (mw->type == EDIT_ORIGINAL)
      {
            char * pch;
            const char * header;
            gboolean is_html = FALSE;

            /* newsgroups */
            g_assert (mw->newsgroups!=NULL);
            header = g_mime_message_get_header (m, HEADER_NEWSGROUPS);
            if (is_nonempty_string(header))
                  pan_gtk_entry_set_text (mw->newsgroups, header);

            /* mail-to */
            g_assert (mw->users!=NULL);
            header = g_mime_message_get_header (m, HEADER_TO);
            if (is_nonempty_string(header))
                  pan_gtk_entry_set_text (mw->users, header);

            /* followup-to */
            g_assert (mw->followup_to!=NULL);
            header = g_mime_message_get_header (m, HEADER_FOLLOWUP_TO);
            if (is_nonempty_string(header))
                  pan_gtk_entry_set_text (mw->followup_to, header);

            /* editor */
            g_assert (mw->body_view!=NULL);
            pch = g_mime_message_get_body (m, TRUE, &is_html);
            update_body_pane (mw->text_massager, mw->body_buffer, pch, FALSE);
            g_free (pch);

      }
      else /* a reply */
      {
            char * p;
            gboolean default_newsgroups = TRUE;

            /**
            ***  BODY
            **/

            g_assert (mw->body_view!=NULL);
            if (m != NULL) {
                  p = create_reply_body (m);
                  update_body_pane (mw->text_massager, mw->body_buffer, p, FALSE);
                  g_free (p);
            } 

            /**
            ***   NEWSGROUPS
            ***   MAILTO
            **/

            p = NULL;

            /* are we posting an article? */
            if (m!=NULL && is_posting(mw->type))
            {
                  const char * followup = g_mime_message_get_header (m, HEADER_FOLLOWUP_TO);
                  const char * newsgroups = g_mime_message_get_header (m, HEADER_NEWSGROUPS);

                  /* if user set followup-to, use that;
                   * otherwise, use newsgroups;
                   * otherwise, use the currently-selected group */
                  if (is_nonempty_string(followup))
                  {
                        default_newsgroups = FALSE;

                        /* Followup-To: poster */
                        if (!g_strcasecmp ("poster", followup)) /* explicit reply by mail */
                        {
                              GtkWidget * w;
                              const char * replyto = g_mime_message_get_header (m, HEADER_REPLY_TO);

                              w = gtk_message_dialog_new (GTK_WINDOW(mw->window),
                                                          GTK_DIALOG_DESTROY_WITH_PARENT,
                                                          GTK_MESSAGE_INFO,
                                                          GTK_BUTTONS_CLOSE,
                                                          _("\"Followup-To: poster\": sending email to author."));
                              g_signal_connect_swapped (GTK_OBJECT(w), "response",
                                                        G_CALLBACK (gtk_widget_destroy), GTK_OBJECT(w));
                              gtk_widget_show_all (w);

                              if (is_nonempty_string(replyto))
                                    pan_gtk_entry_set_text (mw->users, replyto);
                              else {
                                    const char * author = g_mime_message_get_header (m, HEADER_FROM);
                                    pan_gtk_entry_set_text (mw->users, author);
                              }
                        }
                        /* Does Followup-To: contain a valid email address?
                           Son-of-1036 says this is invalid, but what the hell. */
                        else if (gnksa_check_from (followup, FALSE) == GNKSA_OK)
                        {
                              GtkWidget * w;

                              w = gtk_message_dialog_new (GTK_WINDOW(mw->window),
                                                          GTK_DIALOG_DESTROY_WITH_PARENT,
                                                          GTK_MESSAGE_INFO,
                                                          GTK_BUTTONS_CLOSE,
                                                          _("\"Followup-To:\" contains an email address: sending email to author."));
                              g_signal_connect_swapped (GTK_OBJECT(w), "response",
                                                        G_CALLBACK (gtk_widget_destroy), GTK_OBJECT(w));
                              gtk_widget_show_all (w);

                              pan_gtk_entry_set_text (mw->users, followup);
                        }
                        else /* explicit followup groups */
                        {
                              pan_gtk_entry_set_text (mw->newsgroups, followup);
                        }
                  }
                  else if (is_nonempty_string(newsgroups)) /* explicit groups */
                  {
                        pan_gtk_entry_set_text (mw->newsgroups, newsgroups);
                        default_newsgroups = FALSE;
                  }
            }

            /* are we sending mail? */
            if (mw->type==EMAIL_REPLY)
            {
                  /* figure out who to send mail to by checking reply-to: and from: */
                  if (m!=NULL)
                  {
                        const char * cpch = g_mime_message_get_header (m, HEADER_REPLY_TO);
                        if (!is_nonempty_string(cpch))
                              cpch = g_mime_message_get_header (m, HEADER_FROM);
                        pan_gtk_entry_set_text (mw->users, cpch);
                  }
            }

            /* no explicit newsgroup specified for this post,
             * so let's guess where the user might want to post */
            if (default_newsgroups && is_posting(mw->type))
            {
                  GString * str = g_string_new (NULL);

                  if (m != NULL)
                  {
                        g_string_append_printf (str, "%s,", g_mime_message_get_header (m, HEADER_NEWSGROUPS));
                  }
                  if (!str->len)
                  {
                        guint i;
                        Group * thread_group = articlelist_get_group ();
                        GPtrArray * ggroups = grouplist_get_selected_groups ();
                        if (ggroups->len<2 && thread_group!=NULL) {
                              g_string_append_len (str, thread_group->name.str, thread_group->name.len);
                              g_string_append_c (str, ',');
                        }
                        else for (i=0; i<ggroups->len; ++i) {
                              Group * g = GROUP(g_ptr_array_index(ggroups,i));
                              if (g!=NULL && !group_is_folder(g)) {
                                    g_string_append_len (str, g->name.str, g->name.len);
                                    g_string_append_c (str, ',');
                              }
                        }
                        g_ptr_array_free (ggroups, TRUE);
                  }

                  if (str->len != 0) {
                        g_string_truncate (str, str->len-1); /* zotz last comma */
                        pan_gtk_entry_set_text (mw->newsgroups, str->str);
                  }

                  g_string_free (str, TRUE);
            }
      }

      /* populate the identity menu, setting a default id */
      default_id_name = determine_default_identity (mw);
      populate_identity_menu (mw, default_id_name);
      g_free (default_id_name);

      apply_identity (mw, mw->identity_name);
}


static GtkWidget*
create_body_pane_nolock (Compose * mw)
{
      GtkTextBuffer * buf;
      GtkWidget * scrolled_window;

      g_return_val_if_fail (mw!=NULL, NULL);

      mw->body_view = gtk_text_view_new ();
#ifdef HAVE_LIBGTKSPELL
      if (do_spellcheck)
            gtkspell_attach (GTK_TEXT_VIEW(mw->body_view));
#endif
      mw->body_buffer = buf = gtk_text_view_get_buffer (GTK_TEXT_VIEW(mw->body_view));
      gtk_text_buffer_create_tag (buf, "url", "underline", PANGO_UNDERLINE_SINGLE, "foreground_gdk", &text_url_color, NULL);
      gtk_text_buffer_create_tag (buf, "quote_0", "foreground_gdk", NULL, NULL);
      gtk_text_buffer_create_tag (buf, "quote_1", "foreground_gdk", NULL, NULL);
      gtk_text_buffer_create_tag (buf, "quote_2", "foreground_gdk", NULL, NULL);
      gtk_text_buffer_create_tag (buf, "quote_3", "foreground_gdk", NULL, NULL);
      gtk_text_buffer_create_tag (buf, "signature", "foreground_gdk", NULL, NULL);

      pan_widget_set_font (mw->body_view, body_pane_monospace_font);
      gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW(mw->body_view), GTK_WRAP_NONE);
      gtk_text_view_set_editable (GTK_TEXT_VIEW(mw->body_view), TRUE);
      scrolled_window = gtk_scrolled_window_new (NULL, NULL);
      gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW(scrolled_window), GTK_SHADOW_IN);
      gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
                              GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
      gtk_container_add (GTK_CONTAINER (scrolled_window), mw->body_view);

      return scrolled_window;
}


void
message_post_window (void)
{
      message_window_new (NULL, NNTP_POST);
}

static void
message_post_window_create (Compose * compose)
{
      GtkWidget * w;
      GtkWidget * v;

      pan_lock();
      compose->window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
      gtk_window_set_title (GTK_WINDOW(compose->window), _("New Message"));
      gtk_window_set_role (GTK_WINDOW(compose->window), "pan-compose-window");
      v = gtk_vbox_new (FALSE, 0);
      gtk_container_add (GTK_CONTAINER(compose->window), v);

      gtk_box_pack_start (GTK_BOX(v), create_main_menu(compose), FALSE, FALSE, 0);
      gtk_box_pack_start (GTK_BOX(v), create_toolbar(compose), FALSE, FALSE, 0);
      pan_unlock();

      w = create_post_info_pane (compose);
      populate_post_info_pane (compose);

      experimental_wrap_handler (compose,
                                 compose->body_buffer,
                                 GTK_TOGGLE_BUTTON(compose->do_wrap_togglebutton));

      pan_lock ();
      gtk_box_pack_start (GTK_BOX(v), w, TRUE, TRUE, 0);
      pan_unlock();
}

static void
make_reply_window (ComposeType type)
{
      GMimeMessage * message;

      /* sanity clause */
      g_return_if_fail (type==EMAIL_REPLY
                     || type==NNTP_REPLY
                     || type==EMAIL_FORWARD);

      /* get current message */
            message = get_current_message ();
      if (message != NULL)
      {
            /* open up a populated reply window. */
            char * body = text_get_message_to_reply_to ();
            g_object_set_data_full (G_OBJECT(message), PAN_REPLY_PORTION, g_strdup(body), g_free);
            message_window_new (message, type);
            g_free (body);
      }
}
void
message_forward_window (void)
{
      make_reply_window (EMAIL_FORWARD);
}
void
message_reply_window (void)
{
      make_reply_window (EMAIL_REPLY);
}
void
message_followup_window (void)
{
      make_reply_window (NNTP_REPLY);
}


void
message_edit_window (GMimeMessage * message)
{
      if (message == NULL)
            message = get_current_message ();
      if (message  != NULL)
            message_window_new (message, EDIT_ORIGINAL);
}

static void
message_reply_window_create_impl (Compose * compose, const char * title)
{
      GtkWidget * w;
      GtkWidget * v;

      pan_lock ();
      compose->window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
      gtk_window_set_title (GTK_WINDOW(compose->window), title);
      gtk_window_set_role (GTK_WINDOW(compose->window), "pan-compose-window");
      v = gtk_vbox_new (FALSE, 0);
      gtk_container_add (GTK_CONTAINER(compose->window), v);

      gtk_box_pack_start (GTK_BOX(v), create_main_menu(compose), FALSE, FALSE, 0);
      gtk_box_pack_start (GTK_BOX(v), create_toolbar(compose), FALSE, FALSE, 0);
      pan_unlock ();

      w = create_post_info_pane (compose);
      populate_post_info_pane (compose);

      experimental_wrap_handler (compose,
                                 compose->body_buffer,
                                 GTK_TOGGLE_BUTTON(compose->do_wrap_togglebutton));

      pan_lock ();
      gtk_box_pack_start (GTK_BOX(v), w, TRUE, TRUE, 0);
      gtk_text_view_set_editable (GTK_TEXT_VIEW(compose->body_view), TRUE);
      pan_unlock();
}
static void
message_edit_window_create (Compose * compose)
{
      message_reply_window_create_impl (compose, compose->message->subject);
}
static void
message_reply_window_create (Compose * compose)
{
      char * title = make_reply_string (g_mime_message_get_header (compose->message, HEADER_SUBJECT));
      message_reply_window_create_impl (compose, title);
      g_free (title);
}

static void
message_window_new (GMimeMessage * message, ComposeType type)
{
      Compose * compose = g_new0 (Compose, 1);
      compose->message = message;
      compose->type = type;
      compose->server = serverlist_get_active_server ();
      compose->text_massager = text_massager_new ();

      switch (type)
      {
            case NNTP_POST:
                  message_post_window_create (compose);
                  break;

            case NNTP_REPLY:
            case EMAIL_REPLY:
            case EMAIL_FORWARD:
                  message_reply_window_create (compose);
                  break;

            case EDIT_ORIGINAL:
                  message_edit_window_create (compose);
                  break;

            default:
                  pan_warn_if_reached ();
                  break;
      }

      if (compose->window != NULL)
      {
            pan_lock ();
            g_signal_connect (compose->window, "delete_event",
                              G_CALLBACK(window_delete_event_cb), compose);
            g_signal_connect (compose->window, "destroy",
                              G_CALLBACK(window_destroy_cb), compose);
            gtk_window_set_policy (GTK_WINDOW(compose->window), TRUE, TRUE, TRUE);
            pan_unlock ();

            if (!gui_restore_window_size (compose->window, "compose"))
                  gtk_window_set_default_size (GTK_WINDOW(compose->window), 650, 575);

            pan_lock ();
            gtk_widget_show_all (compose->window);

            /* focus the right widget */
            if (!is_nonempty_string (gtk_entry_get_text(GTK_ENTRY(compose->subject))))
                  gtk_widget_grab_focus (compose->subject);
            else
                  gtk_widget_grab_focus (compose->body_view);

            pan_unlock ();
      }
}

/***
****
****  SENDING THE MESSAGE
****
***/

static char*
get_text_from_editable (GtkWidget* w)
{
      char * pch;
      g_return_val_if_fail (GTK_IS_EDITABLE(w), NULL);
      pch = gtk_editable_get_chars (GTK_EDITABLE(w), 0, -1);
      g_strstrip (pch);
      return pch;
}

static char*
get_text_from_maybe_editable (GtkWidget * w)
{
      char * pch = NULL;

      if (w!=NULL && GTK_IS_EDITABLE(w))
      {
            pch = gtk_editable_get_chars (GTK_EDITABLE(w), 0, -1);
            g_strstrip (pch);
      }

      return pch;
}

static char*
message_window_get_reply_to (const Compose* mw)
{
      char * pch;

      g_return_val_if_fail (mw!=NULL, NULL);

      pch = get_text_from_maybe_editable (mw->reply_to);
      if (pch == NULL)
            pch = g_strdup ("");

      return pch;
}

static char*
message_window_get_group_str (GtkWidget * e)
{
      char * pch;

      g_return_val_if_fail (e!=NULL, NULL);

      pch = get_text_from_maybe_editable (e);
      if (pch == NULL)
      {
            pch = g_strdup ("");
      }
      else
      {
            char * tmp;

            /* ensure all the delimiters are commas */
            g_strdelimit (pch, ":; ", ',');

            /* remove leading/trailing commas */
            for (tmp=pch; *tmp==','; ++tmp);
            g_memmove (pch, tmp, strlen(tmp)+1);
            for (tmp=pch+strlen(pch)-1; tmp>pch && *tmp==','; --tmp);
            tmp[1] = '\0';

            /* remove empty entries */
            while ((tmp=pan_strstr(pch,",,")) != NULL)
                  g_memmove (tmp, tmp+1, strlen(tmp+1)+1);
      }

      return pch;
}

static char*
message_window_get_organization (const Compose* mw)
{
      char * pch = NULL;
      g_return_val_if_fail (mw!=NULL, NULL);
      pch = get_text_from_maybe_editable (mw->organization);
      if (pch == NULL)
            pch = g_strdup ("");
      if (pch != NULL)
            g_strstrip (pch);
      return pch;
}

static void
populate_message_from_mw (GMimeMessage* message, const Compose * mw)
{
      int i;
      char ** sd;
      char * pch;
      Identity * id;
      GMimePart * part;
      InternetAddress *ia;

      /***
      ****  HEADERS
      ***/

      /* sanity clause */
      g_return_if_fail (GMIME_IS_MESSAGE(message));
      g_return_if_fail (mw!=NULL);
      g_return_if_fail (is_nonempty_string (mw->identity_name));

      /* author */
      id = identity_manager_get_identity (mw->identity_name);
      ia = internet_address_new ();
      internet_address_set_name (ia, id->author_real);
      internet_address_set_addr (ia, id->author_addr);
      pch = internet_address_to_string (ia, TRUE);
      g_mime_message_set_sender (message, pch);
      g_free (pch);
      internet_address_unref (ia);

      /* organization */
      pch = message_window_get_organization(mw);
      if (is_nonempty_string (pch))
            g_mime_message_set_header (message, HEADER_ORGANIZATION, pch);
      g_free (pch);

      /* subject */
      pch = get_text_from_editable (mw->subject);
      if (is_nonempty_string (pch))
            g_mime_message_set_subject (message, pch);
      g_free (pch);

      /* date */
      if (1) {
            time_t now = time (0);
            const int offset_secs = tzoffset_sec (&now);
            g_mime_message_set_date (message, now, (offset_secs/3600)*100);
      }

      /* user-agent */
      if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(mw->post_user_agent_tb)))
            g_mime_message_set_header (message, "User-Agent", "Pan/" VERSION " (" VERSION_TITLE ")");

      /* always generate a message-id ... */
      if (is_nonempty_string (id->msg_id_fqdn)) 
            pch = gnksa_generate_message_id (id->msg_id_fqdn);
      else
            pch = gnksa_generate_message_id_from_email_addr (id->author_addr);
      g_mime_message_set_message_id (message, pch);
      g_free (pch);

      /* ... but maybe don't post it */
      if (!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(mw->post_message_id_tb)))
            g_mime_message_set_header (message, PAN_NO_MESSAGE_ID, "True");

      /* newsgroups (for posting articles) */
      pch = message_window_get_group_str (mw->newsgroups);
      if (is_nonempty_string (pch))
            g_mime_message_set_header (message, HEADER_NEWSGROUPS, pch);
      g_free (pch);

      /* references (for posting articles) */
      if ((mw->type==NNTP_REPLY || mw->type==EMAIL_REPLY) && (mw->message != NULL))
      {
            const char * refs = g_mime_message_get_header (mw->message, HEADER_REFERENCES);
            const char * msg_id = g_mime_message_get_message_id (mw->message);
            pch = gnksa_generate_references (refs, msg_id);
            if (pch != NULL) {
                  g_mime_message_set_header (message, HEADER_REFERENCES, pch);
                  g_free (pch);
            }
      }

      /* followup-to */
      pch = message_window_get_group_str(mw->followup_to);
      if (is_nonempty_string (pch))
            g_mime_message_set_header (message, HEADER_FOLLOWUP_TO, pch);
      g_free (pch);

      /* reply-to */
      pch = message_window_get_reply_to (mw);
      if (is_nonempty_string (pch))
            g_mime_message_set_header (message, HEADER_REPLY_TO, pch);
      g_free (pch);

      /* sending mail? */
      pch = get_text_from_editable (mw->users);
      if (is_nonempty_string (pch))
            g_mime_message_set_header (message, "To", pch);
      g_free (pch);

      /* attribution header (for message-check) */
      if (mw->message!=NULL && mw->identity_name!=NULL) {
            char * attribution = message_gen_attribution_string (mw->message, id->attribution);
            if (is_nonempty_string(attribution))
                  g_mime_message_set_header (message, PAN_ATTRIBUTION, attribution);
            g_free (attribution);
      }

      /* custom headers */
      if (1) {
            GtkTextView * view = GTK_TEXT_VIEW(mw->custom_headers_text);
            GtkTextBuffer * buffer = gtk_text_view_get_buffer (view);
            GtkTextIter start, end;
            gtk_text_buffer_get_bounds (buffer, &start, &end);
            pch = gtk_text_iter_get_text (&start,  &end);
      }
      sd = g_strsplit (pch, "\n", -1);
      for (i=0; sd!=NULL && sd[i]!=NULL; ++i) {
            char * delimit;
            g_strstrip (sd[i]);
            delimit = strchr (sd[i], ':');
            if (delimit != NULL) {
                  char * key = g_strndup (sd[i], delimit-sd[i]);
                  char * val = g_strdup (delimit + 1);
                  g_strstrip (key);
                  g_strstrip (val);
                  g_mime_message_set_header (message, key, val);
                  g_free (key);
                  g_free (val);
            }
      }
      g_strfreev (sd);
      g_free (pch);

      /***
      ****  BODY
      ***/

      part = g_mime_part_new_with_type ("text", "plain");

      /* body */
      if (1) {
            GtkTextIter start, end;
            gtk_text_buffer_get_bounds (mw->body_buffer, &start, &end);
            pch = gtk_text_buffer_get_text (mw->body_buffer, &start, &end, FALSE);
            g_mime_part_set_content (part, pch, strlen(pch));
            g_mime_message_set_mime_part (message, GMIME_OBJECT(part));
            g_free (pch);
      }

      /* charset */
      if (1) {
            const char * charset = pan_charset_picker_get_charset (mw->charset_om);
            Identity   * id_to_change = identity_manager_get_identity_nocopy (mw->identity_name);

            /* let the part know what its charset is. */
            g_mime_object_set_content_type_parameter (GMIME_OBJECT(part), "charset", charset);
            g_mime_part_set_encoding (part, GMIME_PART_ENCODING_8BIT);

            /* GtkTextView is in UTF-8, so we may need to convert. */
            if (pan_strcmp (charset, "UTF-8"))
            {
                  GMimeDataWrapper * data_wrapper;
                  GMimeStream * original_stream;
                  GMimeStream * charset_stream;
                  GMimeFilter * charset_filter;

                  data_wrapper = g_mime_part_get_content_object (part);
                  original_stream = g_mime_data_wrapper_get_stream (data_wrapper);
                  charset_stream = g_mime_stream_filter_new_with_stream (original_stream);
                  charset_filter = g_mime_filter_charset_new ("UTF-8", charset);
                  g_mime_stream_filter_add (GMIME_STREAM_FILTER(charset_stream), charset_filter);
                  g_mime_data_wrapper_set_stream (data_wrapper, charset_stream);

                  g_mime_stream_unref (charset_stream);
                  g_mime_stream_unref (original_stream);
                  g_object_unref (data_wrapper);
            }

            /* remember the charset */
            if (id_to_change != NULL)
            {
                  identity_set_posting_charset (id_to_change, charset);
                  identity_manager_save ();
            }

      }

      /* cleanup */
      pan_object_unref(PAN_OBJECT(id));
}

static gboolean
check_charset_for_body (Compose * mw)
{
      GtkTextIter    start;
      GtkTextIter    end;
      const char   * charset;
      char         * utf8_body;
      char         * body;
      gboolean       ok;
        int            bytes_read;
        int            bytes_written;

      /* get settings */
      gtk_text_buffer_get_bounds (mw->body_buffer, &start, &end);
      utf8_body = gtk_text_buffer_get_text (mw->body_buffer, &start, &end, FALSE);
      charset = pan_charset_picker_get_charset (mw->charset_om);

      /* attempt to convert */
      bytes_read = bytes_written = 0;
      body = g_convert (utf8_body, -1, charset, "UTF-8", &bytes_read, &bytes_written, NULL);
      ok = body != NULL;

      /* succeeded? */
      if (!ok)
      {
            int i;
            int row;
            int col;
            int line_begin;
            const char * best_charset;

            /* find the row, column where the problem occurred. */
            for (i=row=0; i!=bytes_read; ++i) {
                  if (utf8_body[i] == '\n') {
                        line_begin = i;
                        ++row;
                  }
            }
            col = bytes_read - line_begin;

            /* find the best charset */
            best_charset = g_mime_charset_best (utf8_body, g_utf8_strlen (utf8_body, -1));
            if (best_charset == NULL)
                  best_charset = "UTF-8";

            /* report the error to the user. */
            pan_error_dialog_parented (mw->window,
                  _("ERROR: line %d, column %d uses a character not specified in charset \"%s\" -- possibly change your charset in \"More Headers\" to \"%s\" instead?"),
                  row+1, col, charset, best_charset);
      }

      /* cleanup */
      g_free (body);
      g_free (utf8_body);

      return ok;
}

static void
save_cb (gpointer user_data, int action, GtkWidget * w)
{
      Compose * mw = (Compose*) user_data;
      GMimeMessage * old_message;
      GMimeMessage * new_message;

      /* sanity clause */
      g_return_if_fail (mw!=NULL);

      /* validate the charset */
      if (!check_charset_for_body (mw))
            return;

      /* update contents of mw->message. */
      old_message = mw->message;
      new_message = g_mime_message_new (FALSE);
      populate_message_from_mw (new_message, mw);
      mw->message = new_message;
      if (old_message != NULL)
            g_object_unref (old_message);

      /* save mesage to pan.sendlater */
      folder_add_message (serverlist_get_named_folder(&PAN_SENDLATER), mw->message);

      /* cleanup */
      message_window_destroy (mw);
}

typedef enum
{
      SEND_NOW,
      SEND_LATER
}
SendMode;

static void
post_checked_cb (Server        * server,
                 GMimeMessage  * message,
                 GoodnessLevel   goodness,
                 gpointer        user_data)
{
      ArgSet * argset = (ArgSet*)user_data;
      Compose * mw = (Compose*) argset_get (argset, 0);
      SendMode mode = GPOINTER_TO_INT(argset_get(argset, 1));

      if (goodness == OKAY)
      {
            const char * pch;
            GString * group_name;
            Group * folder;

            /* set the server to use, if not already set */
            if (!is_nonempty_string(g_mime_message_get_header (message, PAN_SERVER)))
                  g_mime_message_set_header (message, PAN_SERVER, server->name.str);

            /* save mesage to pan.sendlater */
            folder = serverlist_get_named_folder (&PAN_SENDLATER);
            folder_add_message (folder, message);

            /* try to post right now, if desired... */
            if (mode == SEND_NOW)
                  queue_add (TASK (task_post_new (server, message)));

            /* remember which identity we used -- newsgroups */
            pch = g_mime_message_get_header (message, HEADER_NEWSGROUPS);
            group_name = g_string_new (NULL);
            while (get_next_token_g_str (pch, ',', &pch, group_name)) {
                  PString pname;
                  Group * group;
                  pan_g_string_strstrip (group_name);
                  pname = pstring_shallow (group_name->str, group_name->len);
                  group = server_get_named_group (server, &pname);
                  if (group!=NULL && !group_is_folder (group))
                        group_set_identity (group, mw->identity_name);
            }
            g_string_free (group_name, TRUE);

            /* remember which identity we used -- identity manager */
            {
                  const int id_type = is_posting (mw->type) ? ID_NEWS_DEFAULT : ID_MAIL_DEFAULT;
                  const char * id_name = mw->identity_name;
                  identity_manager_set_default (id_name, id_type);
            }

            /* close this window */
            message_window_destroy (mw);
      }

      argset_free (argset);
}

static void
post (Compose * mw, SendMode mode)
{
      GMimeMessage * tmp = NULL;
      GMimeMessage * message;
      const char * cpch;

      debug_enter ("post");

      /* Validate the charset */
      if (!check_charset_for_body (mw))
            return;

      /* Get the article pointer, either through repopulating the
       * current article (if we're editing an existing message) or
       * by creating a new article (if this is a new message).
       */
      if (mw->type == EDIT_ORIGINAL)
            message = mw->message;
      else
            message = tmp = g_mime_message_new (FALSE);

      /* make sure that we have newsgroups & to actions */
      populate_message_from_mw (message, mw);
      if ((cpch = g_mime_message_get_header (message, HEADER_NEWSGROUPS)))
            g_mime_message_set_header (message, PAN_NEWSGROUPS, cpch);
      if ((cpch = g_mime_message_get_header(message, HEADER_TO)))
            g_mime_message_set_header (message, PAN_TO, cpch);

      /* if the article's okay, then send or queue */
      message_check_and_prompt (mw->window,
                                mw->server,
                                message,
                                post_checked_cb,
                                argset_new2 (mw, GINT_TO_POINTER(mode)));

      /* cleanup */
      if (tmp != NULL)
            g_object_unref (tmp);

      debug_exit ("post");
}

static void
send_now_cb (gpointer user_data, int action, GtkWidget * w)
{
      Compose * compose = (Compose*) user_data;
      post (compose, SEND_NOW);
}
static void
send_now_cb2 (GtkWidget * w, gpointer user_data)
{
      send_now_cb (user_data, 0, w);
}
static void
send_later_cb (gpointer user_data, int action, GtkWidget * w)
{
      Compose * compose = (Compose*) user_data;
      post (compose, SEND_LATER);
}
static void
send_later_cb2 (GtkWidget * w, gpointer user_data)
{
      send_later_cb (user_data, 0, w);
}

/**
***  Save as File
**/

static void
message_window_save_ok_clicked (GtkWidget * widget, GtkWidget * file_entry)
{
      const char * filename;
      char * writeme = NULL;
      const Compose * mw;
      FILE * fp;
      GMimeStream * stream;
      GMimeMessage * message;

      /* get information from the widget */
      mw = (const Compose*) gtk_object_get_data (GTK_OBJECT(file_entry), "mw");
      filename = gtk_file_selection_get_filename (GTK_FILE_SELECTION (file_entry));

      /* create output file */
      fp = fopen (filename, "w");
      if (fp == NULL)
      {
            if (errno != EISDIR)
                  pan_error_dialog (_("Can't create file \"%s\" %s"), filename, g_strerror(errno));
            return;
      }

      /* build the message ... */
      message = g_mime_message_new (FALSE);
      populate_message_from_mw (message, mw);

      /* save the message ... */
      stream = g_mime_stream_file_new (fp);
      g_mime_message_write_to_stream (message, stream);

      /* cleanup */
      g_object_unref (message);
      g_object_unref (stream);
      gtk_widget_destroy (file_entry);
      g_free (writeme);
}

static void
message_window_save_cb (gpointer user_data, int action, GtkWidget * w)
{
      Compose * compose = (Compose*) user_data;

      /* validate the charset first */
      if (check_charset_for_body (compose))
      {
            GtkWidget * file_entry = NULL;

            pan_lock();
            
            file_entry = gtk_file_selection_new (_("Save message to file"));

            gtk_file_selection_hide_fileop_buttons (GTK_FILE_SELECTION (file_entry));

            g_signal_connect_swapped (GTK_FILE_SELECTION(file_entry)->cancel_button, "clicked",
                                G_CALLBACK(gtk_widget_destroy), G_OBJECT(file_entry));

            /* message_id is const, but we don't alter it here */
            gtk_object_set_data (GTK_OBJECT (file_entry), "mw", compose);

            g_signal_connect (GTK_FILE_SELECTION(file_entry)->ok_button, "clicked",
                          G_CALLBACK(message_window_save_ok_clicked), file_entry);

            gtk_widget_show_all (file_entry);

            pan_unlock();
      }
}

/***
****
****  External Editor (mostly from Sylpheed)
****
***/

static int
warn_wrong_charset (gpointer p)
{
      ArgSet     * argset = (ArgSet *) p;
      GtkWidget  * parent       = (GtkWidget *)  argset_get (argset, 0);
      const char * user_charset = (const char *) argset_get (argset, 1);
      const char * best_charset = (const char *) argset_get (argset, 2);

      pan_error_dialog_parented (parent,
            _("Message uses characters not specified in charset '%s' - possibly use '%s' instead?"),
            user_charset, best_charset);

      argset_free (argset);

      return 0;
}

static void*
run_external_editor (void * compose_voidp)
{
      int argc;
      int i;
      FILE * fp = NULL;
      char * fname = NULL;
      char ** argv = NULL;
      gboolean ok = TRUE;
      gboolean need_thaw = FALSE;
      Compose * compose = (Compose*) compose_voidp;
      char * chars;
      size_t len;
      const char * charset;

      /* sanity checks */
      g_return_val_if_fail (compose!=NULL, NULL);

      charset = pan_charset_picker_get_charset (compose->charset_om);

      /* open a new tmp file */
      if (ok) {
            GError * err = NULL;
            const int fd = g_file_open_tmp ("pan_edit_XXXXXX", &fname, &err);
            if (err == NULL)
                  fp = fdopen (fd, "w");
            else {
                  ok = FALSE;
                  log_add_va (LOG_ERROR, _("Error opening temporary file: \"%s\""), err->message);
                  g_error_free (err);
            }
      }

      /* write the article contents to the tmp file */
      if (ok) {
            GtkTextIter start, end;

            pan_lock ();
            need_thaw = TRUE;
            gtk_widget_set_sensitive (compose->window, FALSE);
            gtk_text_buffer_get_bounds (compose->body_buffer, &start, &end);
            chars = gtk_text_buffer_get_text (compose->body_buffer, &start, &end, FALSE);
            len = strlen (chars);
            pan_unlock ();

      }

      /* convert from utf-8 to the specified */

      if (ok && is_nonempty_string (chars)) {
            char * pch;

            pch = g_convert (chars, len, charset, "UTF-8", NULL, NULL, NULL);

            if (is_nonempty_string (pch)) {
                  replace_gstr (&chars, pch);
                  len = strlen (chars);
            }
            else {
                  const char * best_charset = g_mime_charset_best (chars, len);
                  ArgSet * argset = argset_new3 (compose->window, (gpointer) charset, (gpointer) best_charset);

                  gui_queue_add (warn_wrong_charset, argset);
                  ok = FALSE;
            }
      }

      if (ok) {
            if (fwrite (chars, sizeof(char), len, fp) != len) {
                  ok = FALSE;
                  log_add_va (LOG_ERROR, _("Error writing article to temporary file: %s"), g_strerror(errno));
            }
      }

      if (fp != NULL) {
            fclose (fp);
            fp = NULL;
      }
      g_free (chars);

      /* parse the command line */
      if (ok) {
            GError * err = NULL;
            char * cmd = g_strdup (external_editor);
            g_shell_parse_argv (cmd, &argc, &argv, &err);
            if (err != NULL) {
                  log_add_va (LOG_ERROR, _("Error parsing \"external editor\" command line: %s"), err->message);
                  log_add_va (LOG_ERROR, _("The command line was: %s"), cmd);
                  g_error_free (err);
                  ok = FALSE;
            }
            g_free (cmd);
      }
      for (i=0; i<argc; ++i)
            if (strstr (argv[i], "%t"))
                  replace_gstr (&argv[i], pan_substitute(argv[i], "%t", fname));

      /* spawn off the external editor */
      if (ok) {
            GError * err = NULL;
            g_spawn_sync (NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, NULL, NULL, &err);
            if (err != NULL) {
                  log_add_va (LOG_ERROR, _("Error starting external editor: %s"), err->message);
                  g_error_free (err);
                  ok = FALSE;
            }
      }


      /* read the file contents back in */
      if (ok) {
            GError * err = NULL;
            char * body = NULL;
            gsize body_len = 0;

            g_file_get_contents (fname, &body, &body_len, &err);
            if (err != NULL) {
                  log_add_va (LOG_ERROR, _("Error reading file \"%s\": %s"), err->message, g_strerror(errno));
                  g_error_free (err);
                  ok = FALSE;
            }
            else {
                  GtkTextIter start, end;
                  GtkTextBuffer * buf = compose->body_buffer;
                   
                  if (is_nonempty_string (body)) {
                        char * pch;

                        pch = g_convert (body , body_len, "UTF-8", charset, NULL, &body_len, NULL);
                        replace_gstr (&body, pch);
                  }

                  pan_lock ();
                  gtk_text_buffer_get_bounds (buf, &start, &end);
                  gtk_text_buffer_delete (buf, &start, &end);
                  gtk_text_buffer_insert (buf, &start, body, body_len);
                  pan_unlock ();
            }

            /* cleanup */
            g_free (body);
      }

      /* cleanup */
      remove (fname);
      g_free (fname);
      g_strfreev (argv);
      if (need_thaw) {
            pan_lock ();
            gtk_widget_set_sensitive (compose->window, TRUE);
            pan_unlock ();
      }
      return NULL;
}

static void
compose_ext_editor_cb2 (GtkWidget * w, gpointer user_data)
{
      run_in_worker_thread (run_external_editor, user_data);
}
static void
compose_ext_editor_cb (gpointer user_data, int action, GtkWidget * w)
{
      run_in_worker_thread (run_external_editor, user_data);
}

Generated by  Doxygen 1.6.0   Back to index