[balsa/wip/gtk4: 210/351] Clean up LibBalsaIdentity



commit 9855081a1462a38eacc4dd47963267e72bae673f
Author: Peter Bloomfield <PeterBloomfield bellsouth net>
Date:   Thu Mar 8 20:11:03 2018 -0500

    Clean up LibBalsaIdentity
    
    Split the identity dialogs and combo-box into new files, identity-widgets.[ch].
    
    Declare LibBalsaIdentity using G_DECLARE_FINAL_TYPE, and define it with
    G_DEFINE_TYPE.
    
    Use a more complete set of setters to avoid direct setting to the
    members (we still need getters to make the object opaque).
    
    Use GError API in libbalsa_identity_get_signature.

 libbalsa/Makefile.am        |    2 +
 libbalsa/identity-widgets.c | 1813 ++++++++++++++++++++++++++++++++++++++++
 libbalsa/identity-widgets.h |   56 ++
 libbalsa/identity.c         | 1924 +++----------------------------------------
 libbalsa/identity.h         |   73 +--
 libbalsa/meson.build        |    2 +
 src/mailbox-conf.c          |    1 +
 src/main-window.c           |    1 +
 src/sendmsg-window.c        |   20 +-
 9 files changed, 2008 insertions(+), 1884 deletions(-)
---
diff --git a/libbalsa/Makefile.am b/libbalsa/Makefile.am
index fbc92df..b7a2133 100644
--- a/libbalsa/Makefile.am
+++ b/libbalsa/Makefile.am
@@ -92,6 +92,8 @@ libbalsa_a_SOURCES =          \
        html.h                  \
        identity.c              \
        identity.h              \
+       identity-widgets.c      \
+       identity-widgets.h      \
        imap-server.c           \
        imap-server.h           \
        information.c           \
diff --git a/libbalsa/identity-widgets.c b/libbalsa/identity-widgets.c
new file mode 100644
index 0000000..b1f2224
--- /dev/null
+++ b/libbalsa/identity-widgets.c
@@ -0,0 +1,1813 @@
+/* -*-mode:c; c-style:k&r; c-basic-offset:4; -*- */
+/* Balsa E-Mail Client
+ * Copyright (C) 1997-2018 Stuart Parmenter and others,
+ *                         See the file AUTHORS for a list.
+ *
+ * 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; either version 2, or (at your option)
+ * any later version.
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#if defined(HAVE_CONFIG_H) && HAVE_CONFIG_H
+# include "config.h"
+#endif                          /* HAVE_CONFIG_H */
+#include "identity-widgets.h"
+
+#include <glib/gi18n.h>
+#include "libbalsa-conf.h"
+#include "libbalsa-gpgme.h"
+#include "misc.h"
+#include "smtp-server.h"
+
+/* Tree columns: */
+enum {
+    DEFAULT_COLUMN,
+    NAME_COLUMN,
+    IDENT_COLUMN,
+    N_COLUMNS
+};
+
+/* Widget padding: */
+static const guint padding = 6;
+
+/*
+ * Widget helpers
+ */
+
+static LibBalsaIdentity *
+get_selected_identity(GtkTreeView * tree)
+{
+    GtkTreeSelection *select = gtk_tree_view_get_selection(tree);
+    GtkTreeModel *model = gtk_tree_view_get_model(tree);
+    GtkTreeIter iter;
+    LibBalsaIdentity *identity = NULL;
+
+    if (gtk_tree_selection_get_selected(select, &model, &iter))
+        gtk_tree_model_get(model, &iter, IDENT_COLUMN, &identity, -1);
+
+    return identity;
+}
+
+static gint
+compare_identities(LibBalsaIdentity *id1, LibBalsaIdentity *id2)
+{
+    return g_ascii_strcasecmp(id1->identity_name, id2->identity_name);
+}
+
+static gboolean
+select_identity(GtkTreeView * tree, LibBalsaIdentity * identity)
+{
+    GtkTreeModel *model = gtk_tree_view_get_model(tree);
+    GtkTreeIter iter;
+    gboolean valid;
+
+    for (valid = gtk_tree_model_get_iter_first(model, &iter);
+         valid;
+         valid = gtk_tree_model_iter_next(model, &iter)) {
+        LibBalsaIdentity *tmp;
+
+        gtk_tree_model_get(model, &iter, IDENT_COLUMN, &tmp, -1);
+        if (identity == tmp) {
+            GtkTreePath *path = gtk_tree_model_get_path(model, &iter);
+            gtk_tree_view_set_cursor(tree, path, NULL, FALSE);
+            gtk_tree_path_free(path);
+
+            return TRUE;
+        }
+    }
+
+    return FALSE;
+}
+
+static void
+identity_list_update_real(GtkTreeView * tree,
+                          GList * identities,
+                          LibBalsaIdentity * default_id)
+{
+    GtkListStore *store = GTK_LIST_STORE(gtk_tree_view_get_model(tree));
+    GList *sorted, *list;
+    LibBalsaIdentity *current;
+    GtkTreeIter iter;
+
+    current = get_selected_identity(tree);
+
+    gtk_list_store_clear(store);
+
+    sorted = g_list_sort(g_list_copy(identities),
+                         (GCompareFunc) compare_identities);
+    for (list = sorted; list != NULL; list = list->next) {
+        LibBalsaIdentity* ident = LIBBALSA_IDENTITY(list->data);
+        gtk_list_store_append(store, &iter);
+        gtk_list_store_set(store, &iter,
+                           DEFAULT_COLUMN, ident == default_id,
+                           NAME_COLUMN, ident->identity_name,
+                           IDENT_COLUMN, ident,
+                           -1);
+    }
+    g_list_free(sorted);
+
+    if (!select_identity(tree, current))
+        select_identity(tree, default_id);
+}
+
+/*
+ * Common code for making a GtkTreeView list of identities:
+ *
+ * toggled_cb           callback for the "toggled" signal of the boolean
+ *                      column;
+ * toggled_data         user_data for the callback;
+ * toggled_title        title for the boolean column.
+ */
+static GtkWidget *
+libbalsa_identity_tree(GCallback toggled_cb, gpointer toggled_data,
+                       gchar * toggled_title)
+{
+    GtkListStore *store;
+    GtkWidget *tree;
+    GtkCellRenderer *renderer;
+    GtkTreeViewColumn *column;
+
+    store = gtk_list_store_new(N_COLUMNS,
+                               G_TYPE_BOOLEAN,
+                               G_TYPE_STRING,
+                               G_TYPE_POINTER);
+
+    tree = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
+    g_object_unref(store);
+
+    renderer = gtk_cell_renderer_toggle_new();
+    g_signal_connect_swapped(renderer, "toggled",
+                             toggled_cb, toggled_data);
+    column =
+        gtk_tree_view_column_new_with_attributes(toggled_title, renderer,
+                                                 "radio", DEFAULT_COLUMN,
+                                                 NULL);
+    gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
+
+    renderer = gtk_cell_renderer_text_new();
+    column = gtk_tree_view_column_new_with_attributes("Name", renderer,
+                                                      "text", NAME_COLUMN,
+                                                      NULL);
+    gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
+
+    return tree;
+}
+
+/*
+ * The Select Identity dialog; called from compose window.
+ */
+
+/* Info passed to callbacks: */
+struct SelectDialogInfo_ {
+    LibBalsaIdentityCallback update;
+    gpointer data;
+    GtkWidget *tree;
+    GtkWidget *dialog;
+    GtkWindow *parent;
+    guint idle_handler_id;
+};
+typedef struct SelectDialogInfo_ SelectDialogInfo;
+
+/* Forward references: */
+static void sd_destroy_notify(SelectDialogInfo * sdi);
+static void sd_response_cb(GtkWidget * dialog, gint response,
+                           SelectDialogInfo * sdi);
+static void sd_idle_add_response_ok(SelectDialogInfo * sdi);
+static gboolean sd_response_ok(SelectDialogInfo * sdi);
+
+/*
+ * Public method: create and show the dialog.
+ */
+#define LIBBALSA_IDENTITY_SELECT_DIALOG_KEY "libbalsa-identity-select-dialog"
+void
+libbalsa_identity_select_dialog(GtkWindow * parent,
+                                const gchar * prompt,
+                                GList * identities,
+                                LibBalsaIdentity * initial_id,
+                                LibBalsaIdentityCallback update,
+                                gpointer data)
+{
+    GtkWidget *dialog;
+    GtkWidget *tree;
+    SelectDialogInfo *sdi;
+    GtkWidget *frame;
+
+    /* Show only one dialog at a time. */
+    sdi = g_object_get_data(G_OBJECT(parent),
+                            LIBBALSA_IDENTITY_SELECT_DIALOG_KEY);
+    if (sdi) {
+        gtk_window_present(GTK_WINDOW(sdi->dialog));
+        return;
+    }
+
+    sdi = g_new(SelectDialogInfo, 1);
+    sdi->parent = parent;
+    g_object_set_data_full(G_OBJECT(parent),
+                           LIBBALSA_IDENTITY_SELECT_DIALOG_KEY,
+                           sdi, (GDestroyNotify) sd_destroy_notify);
+    sdi->update = update;
+    sdi->data = data;
+    sdi->idle_handler_id = 0;
+    sdi->dialog = dialog =
+        gtk_dialog_new_with_buttons(prompt, parent,
+                                    GTK_DIALOG_DESTROY_WITH_PARENT |
+                                    libbalsa_dialog_flags(),
+                                    _("_Cancel"), GTK_RESPONSE_CANCEL,
+                                    _("_OK"),     GTK_RESPONSE_OK,
+                                    NULL);
+#if HAVE_MACOSX_DESKTOP
+    libbalsa_macosx_menu_for_parent(dialog, parent);
+#endif
+
+    g_signal_connect(dialog, "response",
+                     G_CALLBACK(sd_response_cb), sdi);
+    gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_OK);
+
+    sdi->tree = tree =
+        libbalsa_identity_tree(G_CALLBACK(sd_idle_add_response_ok), sdi,
+                               _("Current"));
+    g_signal_connect_swapped(tree, "row-activated",
+                             G_CALLBACK(sd_idle_add_response_ok), sdi);
+    identity_list_update_real(GTK_TREE_VIEW(tree), identities, initial_id);
+
+    frame = gtk_frame_new(NULL);
+    gtk_widget_set_vexpand(frame, TRUE);
+    gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))),
+                       frame);
+
+    g_object_set(G_OBJECT(tree), "margin", padding, NULL);
+    gtk_container_add(GTK_CONTAINER(frame), tree);
+
+    gtk_widget_show(dialog);
+    gtk_widget_grab_focus(tree);
+}
+
+/* GDestroyNotify for sdi. */
+static void
+sd_destroy_notify(SelectDialogInfo * sdi)
+{
+    libbalsa_clear_source_id(&sdi->idle_handler_id);
+    g_free(sdi);
+}
+
+/* Callback for the dialog's "response" signal. */
+static void
+sd_response_cb(GtkWidget * dialog, gint response, SelectDialogInfo * sdi)
+{
+    if (response == GTK_RESPONSE_OK) {
+        GtkTreeSelection *selection =
+            gtk_tree_view_get_selection(GTK_TREE_VIEW(sdi->tree));
+        GtkTreeModel *model;
+        GtkTreeIter iter;
+
+        if (gtk_tree_selection_get_selected(selection, &model, &iter)) {
+            LibBalsaIdentity *identity;
+
+            gtk_tree_model_get(model, &iter, IDENT_COLUMN, &identity, -1);
+            sdi->update(sdi->data, identity);
+        }
+    }
+
+    /* Clear the data set on the parent window, so we know that the
+     * dialog was destroyed. This will also trigger the GDestroyNotify
+     * function, sd_destroy_notify.
+     */
+    g_object_set_data(G_OBJECT(sdi->parent),
+                      LIBBALSA_IDENTITY_SELECT_DIALOG_KEY,
+                      NULL);
+
+    gtk_widget_destroy(dialog);
+}
+
+/* Helper for adding idles. */
+static void
+sd_idle_add_response_ok(SelectDialogInfo * sdi)
+{
+    if (!sdi->idle_handler_id)
+        sdi->idle_handler_id =
+            g_idle_add((GSourceFunc) sd_response_ok, sdi);
+}
+
+/* Idle handler for sending the OK response to the dialog. */
+static gboolean
+sd_response_ok(SelectDialogInfo * sdi)
+{
+    if (sdi->idle_handler_id) {
+        sdi->idle_handler_id = 0;
+        gtk_dialog_response(GTK_DIALOG(sdi->dialog), GTK_RESPONSE_OK);
+    }
+    return FALSE;
+}
+
+/*
+ * End of the Select Identity dialog
+ */
+
+/*
+ * The Manage Identities dialog; called from main window.
+ */
+
+typedef struct {
+    GtkTreeView *tree;
+    GtkWidget *dialog;
+} IdentityDeleteInfo;
+
+/* button actions */
+static gboolean close_cb(GObject * dialog);
+static void new_ident_cb(GtkTreeView * tree, GObject * dialog);
+static void delete_ident_cb(GtkTreeView * tree, GtkWidget * dialog);
+static void delete_ident_response(GtkWidget * confirm, gint response,
+                                  IdentityDeleteInfo * di);
+static void help_ident_cb(GtkWidget * widget);
+
+static void set_default_ident_cb(GtkTreeView * tree, GtkTreePath * path,
+                                 GtkTreeViewColumn * column,
+                                 gpointer data);
+static void config_frame_button_select_cb(GtkTreeSelection * selection,
+                                          GtkDialog * dialog);
+
+static void ident_dialog_add_checkbutton(GtkWidget *, gint, GtkDialog *,
+                                         const gchar *, const gchar *,
+                                        gboolean sensitive);
+static void ident_dialog_add_check_and_entry(GtkWidget *, gint, GtkDialog *,
+                                             const gchar *, const gchar *);
+static void ident_dialog_add_entry(GtkWidget *, gint, GtkDialog *,
+                                   const gchar *, const gchar *);
+static void ident_dialog_add_keysel_entry(GtkWidget   *grid,
+                                                                                 gint         row,
+                                                                                 GtkDialog   *dialog,
+                                                                                 const gchar *label_name,
+                                                                                 const gchar *entry_key);
+typedef enum LibBalsaIdentityPathType_ {
+    LBI_PATH_TYPE_FACE,
+    LBI_PATH_TYPE_XFACE
+} LibBalsaIdentityPathType;
+static void ident_dialog_add_file_chooser_button(GtkWidget * grid,
+                                                 gint row,
+                                                 GtkDialog * dialog,
+                                                 LibBalsaIdentityPathType
+                                                 type);
+static void ident_dialog_add_boxes(GtkWidget * grid, gint row,
+                                   GtkDialog * dialog, const gchar * key1,
+                                   const gchar * key2);
+static const gchar *ident_dialog_get_text(GObject *, const gchar *);
+static gboolean ident_dialog_get_bool(GObject *, const gchar *);
+static gchar *ident_dialog_get_path(GObject * dialog, const gchar * key);
+static gboolean ident_dialog_update(GObject *);
+static void config_dialog_select(GtkTreeSelection * selection,
+                                 GtkDialog * dialog);
+
+static void display_frame_update(GObject * dialog, LibBalsaIdentity* ident);
+static void display_frame_set_field(GObject * dialog, const gchar* key,
+                                    const gchar* value);
+static void display_frame_set_boolean(GObject * dialog, const gchar* key,
+                                      gboolean value);
+static void display_frame_set_path(GObject * dialog, const gchar * key,
+                                   const gchar * value, gboolean use_chooser);
+
+
+static void identity_list_update(GtkTreeView * tree);
+static void set_identity_name_in_tree(GtkTreeView * tree,
+                                     LibBalsaIdentity * identity,
+                                     const gchar * name);
+static void md_response_cb(GtkWidget * dialog, gint response,
+                           GtkTreeView * tree);
+static void md_name_changed(GtkEntry * name, GtkTreeView * tree);
+
+static void ident_dialog_add_gpg_menu(GtkWidget * grid, gint row,
+                                      GtkDialog * dialog,
+                                      const gchar * label_name,
+                                      const gchar * menu_key);
+static void add_show_menu(const char *label, gpointer data,
+                          GtkWidget * menu);
+static void ident_dialog_free_values(GPtrArray * values);
+
+static void display_frame_set_gpg_mode(GObject * dialog,
+                                       const gchar * key, gint * value);
+
+static void ident_dialog_add_smtp_menu(GtkWidget * grid, gint row,
+                                       GtkDialog * dialog,
+                                       const gchar * label_name,
+                                       const gchar * menu_key,
+                                      GSList * smtp_servers);
+static void display_frame_set_server(GObject * dialog,
+                                     const gchar * key,
+                                     LibBalsaSmtpServer * smtp_server);
+
+static gpointer ident_dialog_get_value(GObject * dialog,
+                                       const gchar * key);
+
+/* Callback for the "toggled" signal of the "Default" column. */
+static void
+toggle_cb(GObject * dialog, gchar * path)
+{
+    GtkTreeView *tree = g_object_get_data(dialog, "tree");
+    GtkTreeModel *model = gtk_tree_view_get_model(tree);
+    GtkTreeIter iter;
+
+    /* Save any changes to current identity; if it's not valid, just
+     * return. */
+    if (!ident_dialog_update(dialog))
+       return;
+
+    if (gtk_tree_model_get_iter_from_string(model, &iter, path)) {
+        LibBalsaIdentity *identity, **default_id;
+
+        gtk_tree_model_get(model, &iter, IDENT_COLUMN, &identity, -1);
+        default_id = g_object_get_data(G_OBJECT(tree), "default-id");
+        *default_id = identity;
+        identity_list_update(tree);
+    }
+}
+
+/*
+ * Create and return a frame containing a list of the identities in
+ * the application and a number of buttons to edit, create, and delete
+ * identities.  Also provides a way to set the default identity.
+ */
+static GtkWidget*
+libbalsa_identity_config_frame(GList** identities,
+                              LibBalsaIdentity** defid, GtkWidget * dialog,
+                               void (*cb)(gpointer), gpointer data)
+{
+    GtkWidget* config_frame = gtk_frame_new(NULL);
+    GtkWidget *tree;
+
+    tree = libbalsa_identity_tree(G_CALLBACK(toggle_cb), dialog,
+                                  _("Default"));
+    g_signal_connect(tree, "row-activated",
+                     G_CALLBACK(set_default_ident_cb), NULL);
+    g_object_set_data(G_OBJECT(tree), "identities", identities);
+    g_object_set_data(G_OBJECT(tree), "default-id", defid);
+    g_object_set_data(G_OBJECT(tree), "callback", cb);
+    g_object_set_data(G_OBJECT(tree), "cb-data",  data);
+
+    g_object_set(G_OBJECT(tree), "margin", 0, NULL); /* Seriously? */
+    gtk_container_add(GTK_CONTAINER(config_frame), tree);
+
+    identity_list_update(GTK_TREE_VIEW(tree));
+
+    return config_frame;
+}
+
+/* identity_list_update:
+ * Update the list of identities in the config frame, displaying the
+ * available identities in the application, and which is default.
+ */
+static void
+identity_list_update(GtkTreeView * tree)
+{
+    GList **identities =
+        g_object_get_data(G_OBJECT(tree), "identities");
+    LibBalsaIdentity **default_id =
+        g_object_get_data(G_OBJECT(tree), "default-id");
+
+    identity_list_update_real(tree, *identities, *default_id);
+}
+
+enum {
+    IDENTITY_RESPONSE_HELP = GTK_RESPONSE_HELP,
+    IDENTITY_RESPONSE_CLOSE = GTK_RESPONSE_CANCEL,
+    IDENTITY_RESPONSE_NEW,
+    IDENTITY_RESPONSE_REMOVE
+};
+
+/* callback for the "changed" signal */
+static void
+config_frame_button_select_cb(GtkTreeSelection * selection,
+                              GtkDialog * dialog)
+{
+    config_dialog_select(selection, dialog);
+}
+
+/*
+ * Callback for the close button.
+ * Call ident_dialog_update to save any changes, and close the dialog if
+ * OK.
+ */
+static gboolean
+close_cb(GObject * dialog)
+{
+    return ident_dialog_update(dialog);
+}
+
+/*
+ * Create a new identity
+ */
+static void
+new_ident_cb(GtkTreeView * tree, GObject * dialog)
+{
+    LibBalsaIdentity *ident;
+    GList **identities;
+    GtkWidget *name_entry;
+    void (*cb)(gpointer) = g_object_get_data(G_OBJECT(tree), "callback");
+    gpointer data        = g_object_get_data(G_OBJECT(tree), "cb-data");
+
+    /* Save any changes to current identity; if it's not valid, just
+     * return. */
+    if (!ident_dialog_update(dialog))
+       return;
+
+    ident = LIBBALSA_IDENTITY(libbalsa_identity_new());
+    identities = g_object_get_data(G_OBJECT(tree), "identities");
+    *identities = g_list_append(*identities, ident);
+    identity_list_update(tree);
+    /* select just added identity */
+    select_identity(tree, ident);
+
+    name_entry = g_object_get_data(dialog, "identity-name");
+    gtk_widget_grab_focus(name_entry);
+    cb(data);
+}
+
+
+/*
+ * Helper: append a notebook page containing a table
+ */
+static GtkWidget*
+append_ident_notebook_page(GtkNotebook * notebook,
+                          const gchar * tab_label,
+                           const gchar * footnote)
+{
+    GtkWidget *vbox;
+    GtkWidget *grid;
+
+    vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
+    grid = libbalsa_create_grid();
+    g_object_set(G_OBJECT(grid), "margin", padding, NULL);
+    gtk_box_pack_start(GTK_BOX(vbox), grid);
+    if (footnote) {
+       GtkWidget *label;
+
+       label = gtk_label_new(footnote);
+       gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
+        gtk_box_pack_start(GTK_BOX(vbox), label);
+    }
+    gtk_notebook_append_page(notebook, vbox, gtk_label_new(tab_label));
+
+    return grid;
+}
+
+
+/*
+ * Put the required GtkEntries, Labels, and Checkbuttons in the dialog
+ * for creating/editing identities.
+ */
+static const struct {
+    const gchar *mnemonic;
+    const gchar *path_key;
+    const gchar *box_key;
+    const gchar *basename;
+    const gchar *info;
+} path_info[] = {
+        /* Translators: please do not translate Face. */
+    {N_("_Face Path"),
+     "identity-facepath",
+     "identity-facebox",
+     ".face",
+     "Face"},
+        /* Translators: please do not translate Face. */
+    {N_("_X-Face Path"),
+     "identity-xfacepath",
+     "identity-xfacebox",
+     ".xface",
+     "X-Face"}
+};
+
+#define LIBBALSA_IDENTITY_CHECK "libbalsa-identity-check"
+static void md_sig_path_changed_cb(GtkToggleButton * sig_button,
+                                   GObject * dialog);
+
+static GtkWidget*
+setup_ident_frame(GtkDialog * dialog, gboolean createp, gpointer tree,
+                  GSList * smtp_servers)
+{
+    GtkNotebook *notebook = GTK_NOTEBOOK(gtk_notebook_new());
+    GtkWidget *grid;
+    gint row;
+    GObject *name;
+    gpointer path;
+    gchar *footnote;
+
+    /* create the "General" tab */
+    grid = append_ident_notebook_page(notebook, _("General"), NULL);
+    row = 0;
+    ident_dialog_add_entry(grid, row++, dialog, _("_Identity name:"),
+                          "identity-name");
+    ident_dialog_add_entry(grid, row++, dialog, _("_Full name:"),
+                           "identity-fullname");
+    ident_dialog_add_entry(grid, row++, dialog, _("_Mailing address:"),
+                           "identity-address");
+    ident_dialog_add_entry(grid, row++, dialog, _("Reply _to:"),
+                           "identity-replyto");
+    ident_dialog_add_entry(grid, row++, dialog, _("_Domain:"),
+                           "identity-domain");
+
+    /* create the "Messages" tab */
+    grid = append_ident_notebook_page(notebook, _("Messages"), NULL);
+    row = 0;
+    ident_dialog_add_entry(grid, row++, dialog, _("_BCC:"),
+                           "identity-bcc");
+    ident_dialog_add_entry(grid, row++, dialog, _("Reply _string:"),
+                           "identity-replystring");
+    ident_dialog_add_entry(grid, row++, dialog, _("F_orward string:"),
+                           "identity-forwardstring");
+    ident_dialog_add_checkbutton(grid, row++, dialog,
+                                 _("send messages in both plain text and _HTML format"),
+                                 "identity-sendmpalternative", TRUE);
+    ident_dialog_add_checkbutton(grid, row++, dialog,
+                                 _("request positive (successful)"
+                                   " _Delivery Status Notification by default"),
+                                 "identity-requestdsn", TRUE);
+    ident_dialog_add_checkbutton(grid, row++, dialog,
+                                 _("request _Message Disposition Notification by default"),
+                                 "identity-requestmdn", TRUE);
+    ident_dialog_add_file_chooser_button(grid, row++, dialog,
+                                         LBI_PATH_TYPE_FACE);
+    ident_dialog_add_file_chooser_button(grid, row++, dialog,
+                                         LBI_PATH_TYPE_XFACE);
+    ident_dialog_add_boxes(grid, row++, dialog,
+                           path_info[LBI_PATH_TYPE_FACE].box_key,
+                           path_info[LBI_PATH_TYPE_XFACE].box_key);
+    ident_dialog_add_smtp_menu(grid, row++, dialog, _("SMT_P server:"),
+                               "identity-smtp-server", smtp_servers);
+
+    /* create the "Signature" tab */
+    grid = append_ident_notebook_page(notebook, _("Signature"), NULL);
+    row = 0;
+    ident_dialog_add_check_and_entry(grid, row++, dialog,
+                                     _("Signature _path"),
+                                     "identity-sigpath");
+    ident_dialog_add_checkbutton(grid, row++, dialog,
+                                _("_Execute signature"),
+                                "identity-sigexecutable", FALSE);
+    ident_dialog_add_checkbutton(grid, row++, dialog,
+                                 _("Incl_ude signature"),
+                                 "identity-sigappend", FALSE);
+    ident_dialog_add_checkbutton(grid, row++, dialog,
+                                 _("Include signature when for_warding"),
+                                 "identity-whenforward", FALSE);
+    ident_dialog_add_checkbutton(grid, row++, dialog,
+                                 _("Include signature when rep_lying"),
+                                 "identity-whenreply", FALSE);
+    ident_dialog_add_checkbutton(grid, row++, dialog,
+                                 _("_Add signature separator"),
+                                 "identity-sigseparator", FALSE);
+    ident_dialog_add_checkbutton(grid, row++, dialog,
+                                 _("Prepend si_gnature"),
+                                 "identity-sigprepend", FALSE);
+
+#ifdef HAVE_GPGME
+    footnote = NULL;
+#else
+    footnote = _("Signing and encrypting messages are possible "
+                 "only if Balsa is built with cryptographic support.");
+#endif
+    /* create the "Security" tab */
+    grid =
+        append_ident_notebook_page(notebook, _("Security"), footnote);
+    row = 0;
+    ident_dialog_add_checkbutton(grid, row++, dialog,
+                                 _("sign messages by default"),
+                                 "identity-gpgsign", TRUE);
+    ident_dialog_add_checkbutton(grid, row++, dialog,
+                                 _("encrypt messages by default"),
+                                 "identity-gpgencrypt", TRUE);
+    ident_dialog_add_gpg_menu(grid, row++, dialog,
+                                _("default protocol"),
+                                "identity-crypt-protocol");
+    ident_dialog_add_checkbutton(grid, row++, dialog,
+                                 _("always trust GnuPG keys to encrypt messages"),
+                                 "identity-trust-always", TRUE);
+    ident_dialog_add_checkbutton(grid, row++, dialog,
+                                 _("remind me if messages can be encrypted"),
+                                 "identity-warn-send-plain", TRUE);
+    ident_dialog_add_keysel_entry(grid, row++, dialog,
+                                         _("use secret key with this id for signing GnuPG messages\n"
+                                               "(leave empty for automatic selection)"),
+                                                                 "identity-keyid");
+    ident_dialog_add_keysel_entry(grid, row++, dialog,
+                                         _("use certificate with this id for signing S/MIME messages\n"
+                                               "(leave empty for automatic selection)"),
+                                                                 "identity-keyid-sm");
+#ifndef HAVE_GPGME
+    gtk_widget_set_sensitive(grid, FALSE);
+#endif
+
+    name = g_object_get_data(G_OBJECT(dialog), "identity-name");
+    g_signal_connect(name, "changed",
+                     G_CALLBACK(md_name_changed), tree);
+
+    path = g_object_get_data(G_OBJECT(dialog), "identity-sigpath");
+    g_signal_connect(g_object_get_data(G_OBJECT(path),
+                                       LIBBALSA_IDENTITY_CHECK),
+                     "toggled",
+                     G_CALLBACK(md_sig_path_changed_cb), dialog);
+
+    gtk_notebook_set_current_page(notebook, 0);
+
+    return GTK_WIDGET(notebook);
+}
+
+/* Callback for the "changed" signal of the name entry; updates the name
+ * in the tree. */
+static void
+md_name_changed(GtkEntry * name, GtkTreeView * tree)
+{
+    set_identity_name_in_tree(tree, get_selected_identity(tree),
+                             gtk_entry_get_text(name));
+}
+
+/*
+ * Create and add a GtkCheckButton to the given dialog with caption
+ * and add a pointer to it stored under the given key.  The check
+ * button is initialized to the given value.
+ */
+static void
+ident_dialog_add_checkbutton(GtkWidget * grid, gint row,
+                             GtkDialog * dialog, const gchar * check_label,
+                             const gchar * check_key, gboolean sensitive)
+{
+    GtkWidget *check;
+
+    check = libbalsa_create_grid_check(check_label, grid, row, FALSE);
+    g_object_set_data(G_OBJECT(dialog), check_key, check);
+    gtk_widget_set_sensitive(check, sensitive);
+}
+
+/*
+ * Create and add a GtkCheckButton to the given dialog with caption
+ * and add a pointer to it stored under the given key, which is
+ * followed by a text entry.  The check button is initialized to the
+ * given value.
+ */
+static void
+ident_dialog_add_check_and_entry(GtkWidget * grid, gint row,
+                                 GtkDialog * dialog, const gchar * check_label,
+                                 const gchar * entry_key)
+{
+    GtkWidget *check, *entry;
+
+    check = gtk_check_button_new_with_mnemonic(check_label);
+    entry = gtk_entry_new();
+
+
+    gtk_grid_attach(GTK_GRID(grid), check, 0, row, 1, 1);
+    gtk_widget_set_hexpand(entry, TRUE);
+    gtk_grid_attach(GTK_GRID(grid), entry, 1, row, 1, 1);
+
+    g_object_set_data(G_OBJECT(dialog), entry_key, entry);
+    g_object_set_data(G_OBJECT(entry), LIBBALSA_IDENTITY_CHECK, check);
+}
+
+
+/*
+ * Add a GtkEntry to the given dialog with a label next to it
+ * explaining the contents.  A reference to the entry is stored as
+ * object data attached to the dialog with the given key.  The entry
+ * is initialized to the init_value given.
+ */
+static void
+ident_dialog_add_entry(GtkWidget * grid, gint row, GtkDialog * dialog,
+                       const gchar * label_name, const gchar * entry_key)
+{
+    GtkWidget *label;
+    GtkWidget *entry;
+
+    label = libbalsa_create_grid_label(label_name, grid, row);
+
+    entry = libbalsa_create_grid_entry(grid, NULL, NULL, row, NULL, label);
+
+    g_object_set_data(G_OBJECT(dialog), entry_key, entry);
+    if (row == 0)
+        gtk_widget_grab_focus(entry);
+}
+
+
+#ifdef HAVE_GPGME
+static void
+choose_key(GtkButton *button, gpointer user_data)
+{
+       const gchar *target;
+       gpgme_protocol_t protocol;
+       const gchar *email;
+       gchar *keyid;
+       GError *error = NULL;
+
+       target = g_object_get_data(G_OBJECT(button), "target");
+       if (strcmp(target, "identity-keyid") == 0) {
+               protocol = GPGME_PROTOCOL_OpenPGP;
+       } else {
+               protocol = GPGME_PROTOCOL_CMS;
+       }
+
+       email = ident_dialog_get_text(G_OBJECT(user_data), "identity-address");
+       keyid = libbalsa_gpgme_get_seckey(protocol, email, GTK_WINDOW(user_data), &error);
+       if (keyid != NULL) {
+               display_frame_set_field(G_OBJECT(user_data), target, keyid);
+               g_free(keyid);
+       }
+       if (error != NULL) {
+        libbalsa_information(LIBBALSA_INFORMATION_WARNING,
+                             _("Error selecting key: %s"), error->message);
+        g_clear_error(&error);
+       }
+}
+#endif
+
+
+/*
+ * Add a GtkEntry to the given dialog with a label next to it
+ * explaining the contents.  A reference to the entry is stored as
+ * object data attached to the dialog with the given key.  A button
+ * is added behind the entry to choose a key.
+ */
+static void
+ident_dialog_add_keysel_entry(GtkWidget   *grid,
+                                                         gint         row,
+                                                         GtkDialog   *dialog,
+                                         const gchar *label_name,
+                                                         const gchar *entry_key)
+{
+       GtkWidget *button;
+
+       ident_dialog_add_entry(grid, row, dialog, label_name, entry_key);
+       button = gtk_button_new_with_label(_("Choose…"));
+#ifdef HAVE_GPGME
+       g_object_set_data_full(G_OBJECT(button), "target", g_strdup(entry_key), (GDestroyNotify) g_free);
+       g_signal_connect(button, "clicked", G_CALLBACK(choose_key), dialog);
+#endif
+       gtk_grid_attach(GTK_GRID(grid), button, 2, row, 1, 1);
+}
+
+
+/*
+ * Add a GtkFileChooserButton to the given dialog with a label next to it
+ * explaining the contents.  A reference to the button is stored as
+ * object data attached to the dialog with the given key.  The entry
+ * is initialized to the init_value given.
+ */
+
+/* Callbacks and helpers. */
+static void
+file_chooser_check_cb(GtkToggleButton * button, GtkWidget * chooser)
+{
+    gtk_widget_set_sensitive(chooser,
+                             gtk_toggle_button_get_active(button));
+    /* Force validation of current path, if any. */
+    g_signal_emit_by_name(chooser, "selection-changed");
+}
+
+static void
+md_face_path_changed(const gchar * filename, gboolean active,
+                     LibBalsaIdentityPathType type, gpointer data)
+{
+    gchar *content;
+    gsize size;
+    GError *err = NULL;
+    GtkWidget *image;
+    GtkWidget *face_box;
+
+    face_box = g_object_get_data(G_OBJECT(data), path_info[type].box_key);
+    if (!active) {
+        gtk_widget_hide(face_box);
+        return;
+    }
+
+    content = libbalsa_get_header_from_path(path_info[type].info,
+                                            filename, &size, &err);
+
+    if (err) {
+        libbalsa_information(LIBBALSA_INFORMATION_WARNING,
+                             _("Error reading file %s: %s"), filename,
+                             err->message);
+        g_error_free(err);
+        gtk_widget_hide(face_box);
+        return;
+    }
+
+    if (size > 998) {
+        libbalsa_information(LIBBALSA_INFORMATION_WARNING,
+                /* Translators: please do not translate Face. */
+                             _("Face header file %s is too long "
+                               "(%lu bytes)."), filename, (unsigned long)size);
+        g_free(content);
+        gtk_widget_hide(face_box);
+        return;
+    }
+
+    if (libbalsa_text_attr_string(content)) {
+        libbalsa_information(LIBBALSA_INFORMATION_WARNING,
+                /* Translators: please do not translate Face. */
+                             _("Face header file %s contains "
+                               "binary data."), filename);
+        g_free(content);
+        return;
+    }
+
+    if (type == LBI_PATH_TYPE_FACE)
+        image = libbalsa_get_image_from_face_header(content, &err);
+#if HAVE_COMPFACE
+    else if (type == LBI_PATH_TYPE_XFACE)
+        image = libbalsa_get_image_from_x_face_header(content, &err);
+#endif                          /* HAVE_COMPFACE */
+    else {
+        gtk_widget_hide(face_box);
+        g_free(content);
+        return;
+    }
+    if (err) {
+        libbalsa_information(LIBBALSA_INFORMATION_WARNING,
+                /* Translators: please do not translate Face. */
+                             _("Error loading Face: %s"), err->message);
+        g_error_free(err);
+        g_free(content);
+        return;
+    }
+
+    gtk_container_foreach(GTK_CONTAINER(face_box),
+                          (GtkCallback) gtk_widget_destroy, NULL);
+    gtk_box_pack_start(GTK_BOX(face_box), image);
+
+    g_free(content);
+}
+
+/* Callback for the "selection-changed" signal of the signature path
+ * file chooser; sets sensitivity of the signature-related buttons. */
+
+static void
+md_sig_path_changed(gboolean active, GObject * dialog)
+{
+    guint i;
+    static gchar *button_key[] = {
+        "identity-sigexecutable",
+        "identity-sigappend",
+        "identity-whenforward",
+        "identity-whenreply",
+        "identity-sigseparator",
+        "identity-sigprepend",
+        "identity-sigpath",
+    };
+
+    for (i = 0; i < G_N_ELEMENTS(button_key); i++) {
+        GtkWidget *button = g_object_get_data(dialog, button_key[i]);
+        gtk_widget_set_sensitive(button, active);
+    }
+}
+
+static void
+md_sig_path_changed_cb(GtkToggleButton *sig_button, GObject *dialog)
+{
+    md_sig_path_changed(gtk_toggle_button_get_active(sig_button), dialog);
+}
+
+#define LIBBALSA_IDENTITY_INFO "libbalsa-identity-info"
+static void
+file_chooser_cb(GtkWidget * chooser, gpointer data)
+{
+    gchar *filename;
+    LibBalsaIdentityPathType type;
+    GtkToggleButton *check;
+    gboolean active;
+
+    filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(chooser));
+    if (!filename || !*filename) {
+        g_free(filename);
+        return;
+    }
+
+    type = GPOINTER_TO_UINT(g_object_get_data
+                            (G_OBJECT(chooser), LIBBALSA_IDENTITY_INFO));
+    check = g_object_get_data(G_OBJECT(chooser), LIBBALSA_IDENTITY_CHECK);
+    active = gtk_toggle_button_get_active(check);
+
+#if 0
+    if (type == LBI_PATH_TYPE_SIG)
+        md_sig_path_changed(filename, active, data);
+    else
+#endif
+        md_face_path_changed(filename, active, type, data);
+    g_free(filename);
+}
+
+static void
+ident_dialog_add_file_chooser_button(GtkWidget * grid, gint row,
+                                     GtkDialog * dialog,
+                                     LibBalsaIdentityPathType type)
+{
+    GtkWidget *check;
+    gchar *filename;
+    gchar *title;
+    GtkWidget *button;
+
+    check =
+        gtk_check_button_new_with_mnemonic(_(path_info[type].mnemonic));
+    gtk_grid_attach(GTK_GRID(grid), check, 0, row, 1, 1);
+
+    filename =
+        g_build_filename(g_get_home_dir(), path_info[type].basename, NULL);
+    title = g_strdup_printf("Choose %s file", _(path_info[type].info));
+    button = gtk_file_chooser_button_new(title,
+                                         GTK_FILE_CHOOSER_ACTION_OPEN);
+    g_free(title);
+    gtk_file_chooser_set_show_hidden(GTK_FILE_CHOOSER(button), TRUE);
+    gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(button), filename);
+    g_free(filename);
+
+    gtk_widget_set_hexpand(button, TRUE);
+    gtk_widget_set_vexpand(button, FALSE);
+    gtk_grid_attach(GTK_GRID(grid), button, 1, row, 1, 1);
+
+    g_object_set_data(G_OBJECT(dialog), path_info[type].path_key, button);
+    g_object_set_data(G_OBJECT(button), LIBBALSA_IDENTITY_CHECK, check);
+    g_object_set_data(G_OBJECT(button), LIBBALSA_IDENTITY_INFO,
+                      GUINT_TO_POINTER(type));
+    g_signal_connect(check, "toggled",
+                     G_CALLBACK(file_chooser_check_cb), button);
+    g_signal_connect(button, "selection-changed",
+                     G_CALLBACK(file_chooser_cb), dialog);
+}
+
+static void
+ident_dialog_add_boxes(GtkWidget * grid, gint row, GtkDialog * dialog,
+                       const gchar * key1, const gchar *key2)
+{
+    GtkWidget *hbox, *vbox;
+
+    hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 12);
+    gtk_grid_attach(GTK_GRID(grid), hbox, 1, row, 1, 1);
+    vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
+    g_object_set_data(G_OBJECT(dialog), key1, vbox);
+    gtk_box_pack_start(GTK_BOX(hbox), vbox);
+    vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
+    g_object_set_data(G_OBJECT(dialog), key2, vbox);
+    gtk_box_pack_start(GTK_BOX(hbox), vbox);
+}
+
+/* set_identity_name_in_tree:
+ * update the tree to reflect the (possibly) new name of the identity
+ */
+static void
+set_identity_name_in_tree(GtkTreeView * tree, LibBalsaIdentity * identity,
+                  const gchar * name)
+{
+    GtkTreeModel *model = gtk_tree_view_get_model(tree);
+    GtkTreeIter iter;
+    gboolean valid;
+
+    for (valid =
+         gtk_tree_model_get_iter_first(model, &iter);
+         valid;
+         valid = gtk_tree_model_iter_next(model, &iter)) {
+        LibBalsaIdentity *tmp;
+
+        gtk_tree_model_get(model, &iter, IDENT_COLUMN, &tmp, -1);
+        if (identity == tmp) {
+            gtk_list_store_set(GTK_LIST_STORE(model), &iter,
+                               NAME_COLUMN, name, -1);
+            break;
+        }
+    }
+}
+
+/*
+ * Update the identity object associated with the edit/new dialog,
+ * validating along the way.  Correct validation results in a true
+ * return value, otherwise it returns false.
+ */
+
+static gboolean
+ident_dialog_update(GObject * dlg)
+{
+    LibBalsaIdentity* id;
+    LibBalsaIdentity* exist_ident;
+    InternetAddress *ia;
+    GtkWidget *tree;
+    GList **identities, *list;
+    const gchar *text;
+    gchar *dup;
+    gchar *path;
+
+    id = g_object_get_data(dlg, "identity");
+    if (!id)
+        return TRUE;
+    tree = g_object_get_data(dlg, "tree");
+    identities = g_object_get_data(G_OBJECT(tree), "identities");
+
+    text = ident_dialog_get_text(dlg, "identity-name");
+    g_return_val_if_fail(text != NULL, FALSE);
+
+    if (text[0] == '\0') {
+        libbalsa_information(LIBBALSA_INFORMATION_ERROR,
+                             _("Error: The identity does not have a name"));
+        return FALSE;
+    }
+
+    for (list = *identities; list != NULL; list = list->next) {
+        exist_ident = list->data;
+
+        if (g_ascii_strcasecmp(exist_ident->identity_name, text) == 0
+            && id != exist_ident) {
+            libbalsa_information(LIBBALSA_INFORMATION_ERROR,
+                                 _("Error: An identity with that"
+                                   " name already exists"));
+            return FALSE;
+        }
+    }
+
+    libbalsa_identity_set_identity_name(id, text);
+    set_identity_name_in_tree(GTK_TREE_VIEW(tree), id, text);
+
+    text = ident_dialog_get_text(dlg, "identity-address");
+    g_return_val_if_fail(text != NULL, FALSE);
+    ia = internet_address_mailbox_new(NULL, text);
+    internet_address_set_name(ia, ident_dialog_get_text(dlg, "identity-fullname"));
+    libbalsa_identity_set_address(id, ia);
+    g_object_unref(ia);
+
+    libbalsa_identity_set_replyto(id,
+            ident_dialog_get_text(dlg, "identity-replyto"));
+    libbalsa_identity_set_domain(id,
+            ident_dialog_get_text(dlg, "identity-domain"));
+    libbalsa_identity_set_bcc(id,
+            ident_dialog_get_text(dlg, "identity-bcc"));
+    libbalsa_identity_set_reply_string(id,
+            ident_dialog_get_text(dlg, "identity-replystring"));
+    libbalsa_identity_set_forward_string(id,
+            ident_dialog_get_text(dlg, "identity-forwardstring"));
+    libbalsa_identity_set_send_mp_alternative(id,
+            ident_dialog_get_bool(dlg, "identity-sendmpalternative"));
+    libbalsa_identity_set_smtp_server(id,
+            ident_dialog_get_value(dlg, "identity-smtp-server"));
+
+    libbalsa_identity_set_signature_path(id,
+            ident_dialog_get_text(dlg, "identity-sigpath"));
+    libbalsa_identity_set_sig_executable(id,
+            ident_dialog_get_bool(dlg, "identity-sigexecutable"));
+    libbalsa_identity_set_sig_sending(id,
+            ident_dialog_get_bool(dlg, "identity-sigappend"));
+    libbalsa_identity_set_sig_whenforward(id,
+            ident_dialog_get_bool(dlg, "identity-sigwhenforward"));
+    libbalsa_identity_set_sig_whenreply(id,
+            ident_dialog_get_bool(dlg, "identity-sigwhenreply"));
+    libbalsa_identity_set_sig_separator(id,
+            ident_dialog_get_bool(dlg, "identity-sigseparator"));
+    libbalsa_identity_set_sig_prepend(id,
+            ident_dialog_get_bool(dlg, "identity-sigprepend"));
+
+    path = ident_dialog_get_path(dlg, "identity-facepath");
+    libbalsa_identity_set_face_path(id, path);
+    g_free(path);
+
+    path = ident_dialog_get_path(dlg, "identity-xfacepath");
+    libbalsa_identity_set_x_face_path(id, path);
+    g_free(path);
+
+    libbalsa_identity_set_request_mdn(id,
+            ident_dialog_get_bool(dlg, "identity-requestmdn"));
+    libbalsa_identity_set_request_dsn(id,
+            ident_dialog_get_bool(dlg, "identity-requestdsn"));
+
+    libbalsa_identity_set_gpg_sign(id,
+            ident_dialog_get_bool(dlg, "identity-gpgsign"));
+    libbalsa_identity_set_gpg_encrypt(id,
+            ident_dialog_get_bool(dlg, "identity-gpgencrypt"));
+    libbalsa_identity_set_always_trust(id,
+            ident_dialog_get_bool(dlg, "identity-trust-always"));
+    libbalsa_identity_set_warn_send_plain(id,
+            ident_dialog_get_bool(dlg, "identity-warn-send-plain"));
+    libbalsa_identity_set_crypt_protocol(id,
+            GPOINTER_TO_INT(ident_dialog_get_value (dlg, "identity-crypt-protocol")));
+
+    dup = g_strdup(ident_dialog_get_text(dlg, "identity-keyid"));
+    libbalsa_identity_set_force_gpg_key_id(id, g_strstrip(dup));
+    g_free(dup);
+
+    dup = g_strdup(ident_dialog_get_text(dlg, "identity-keyid-sm"));
+    libbalsa_identity_set_force_smime_key_id(id, g_strstrip(dup));
+    g_free(dup);
+
+    return TRUE;
+}
+
+
+/*
+ * Get the text from an entry in the editing/creation dialog.  The
+ * given key accesses the entry using object data.
+ */
+static const gchar *
+ident_dialog_get_text(GObject * dialog, const gchar * key)
+{
+    GtkEntry *entry;
+    GtkToggleButton *check;
+
+    entry = g_object_get_data(dialog, key);
+    check = g_object_get_data(G_OBJECT(entry), LIBBALSA_IDENTITY_CHECK);
+    if (check && !gtk_toggle_button_get_active(check))
+        return NULL;
+    return gtk_entry_get_text(entry);
+}
+
+
+/*
+ * Get the value of a check button from the editing dialog.  The key
+ * is used to retreive the reference to the check button using object
+ * data
+ */
+static gboolean
+ident_dialog_get_bool(GObject* dialog, const gchar* key)
+{
+    GtkToggleButton *button;
+
+    button = g_object_get_data(dialog, key);
+    return gtk_toggle_button_get_active(button);
+}
+
+
+/*
+ * Get the path from a file chooser in the editing/creation dialog.  The
+ * given key accesses the file chooser using object data.
+ */
+static gchar *
+ident_dialog_get_path(GObject * dialog, const gchar * key)
+{
+    GtkWidget *chooser;
+
+    chooser = g_object_get_data(dialog, key);
+    if (!gtk_widget_get_sensitive(chooser))
+        return NULL;
+
+    return gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(chooser));
+}
+
+
+/*
+ * Set the default identity to the currently selected.
+ */
+static void
+set_default_ident_cb(GtkTreeView * tree, GtkTreePath * path,
+                     GtkTreeViewColumn * column, gpointer data)
+{
+    LibBalsaIdentity *ident, **default_id;
+
+    default_id = g_object_get_data(G_OBJECT(tree), "default-id");
+    ident = get_selected_identity(tree);
+    g_return_if_fail(ident != NULL);
+    *default_id = ident;
+
+    identity_list_update(tree);
+}
+
+
+/*
+ * Confirm the deletion of an identity, do the actual deletion here,
+ * and close the dialog.
+ */
+static void
+identity_delete_selected(GtkTreeView * tree, GtkWidget * dialog)
+{
+    GtkTreeSelection *selection = gtk_tree_view_get_selection(tree);
+    GtkTreeModel *model;
+    GtkTreeIter iter;
+    GtkTreePath *path;
+    LibBalsaIdentity *ident;
+    GList **identities;
+    void (*cb)(gpointer) = g_object_get_data(G_OBJECT(tree), "callback");
+    gpointer data        = g_object_get_data(G_OBJECT(tree), "cb-data");
+
+    /* Save the path to the current row. */
+    if (!gtk_tree_selection_get_selected(selection, &model, &iter))
+        return;
+    path = gtk_tree_model_get_path(model, &iter);
+
+    ident = get_selected_identity(tree);
+    identities = g_object_get_data(G_OBJECT(tree), "identities");
+    *identities = g_list_remove(*identities, ident);
+    g_object_set_data(G_OBJECT(dialog), "identity", NULL);
+    identity_list_update(tree);
+    g_object_unref(ident);
+
+    /* Select the row at the saved path, or the previous one. */
+    if (gtk_tree_model_get_iter(model, &iter, path)
+        || gtk_tree_path_prev(path)) {
+        gtk_tree_view_set_cursor(tree, path, NULL, FALSE);
+        gtk_tree_view_scroll_to_cell(tree, path, NULL,
+                                     FALSE, 0, 0);
+    }
+    gtk_tree_path_free(path);
+    gtk_widget_grab_focus(GTK_WIDGET(tree));
+    cb(data);
+}
+
+/*
+ * Delete the currently selected identity after confirming.
+ */
+static void
+delete_ident_cb(GtkTreeView * tree, GtkWidget * dialog)
+{
+    LibBalsaIdentity* ident, **default_id;
+    GtkWidget* confirm;
+    IdentityDeleteInfo *di;
+
+    ident = get_selected_identity(tree);
+    default_id = g_object_get_data(G_OBJECT(tree), "default-id");
+    g_return_if_fail(ident != *default_id);
+    confirm = gtk_message_dialog_new(GTK_WINDOW(dialog),
+                                     GTK_DIALOG_DESTROY_WITH_PARENT,
+                                     GTK_MESSAGE_QUESTION,
+                                     GTK_BUTTONS_OK_CANCEL,
+                                     _("Do you really want to delete"
+                                       " the selected identity?"));
+#if HAVE_MACOSX_DESKTOP
+    libbalsa_macosx_menu_for_parent(confirm, GTK_WINDOW(dialog));
+#endif
+    di = g_new(IdentityDeleteInfo, 1);
+    di->tree = tree;
+    di->dialog = dialog;
+    g_signal_connect(confirm, "response",
+                     G_CALLBACK(delete_ident_response), di);
+    g_object_weak_ref(G_OBJECT(confirm), (GWeakNotify) g_free, di);
+    gtk_widget_set_sensitive(dialog, FALSE);
+    gtk_widget_show(confirm);
+}
+
+static void
+delete_ident_response(GtkWidget * confirm, gint response,
+                      IdentityDeleteInfo * di)
+{
+    if(response == GTK_RESPONSE_OK)
+        identity_delete_selected(di->tree, di->dialog);
+    gtk_widget_set_sensitive(di->dialog, TRUE);
+    gtk_widget_destroy(confirm);
+}
+
+/*
+ * Show the help file.
+ */
+static void
+help_ident_cb(GtkWidget * widget)
+{
+    GError *err = NULL;
+
+    gtk_show_uri_on_window(GTK_WINDOW(widget), "help:balsa/identities",
+                           gtk_get_current_event_time(), &err);
+
+    if (err) {
+        g_print(_("Error displaying help for identities: %s\n"),
+                err->message);
+        g_error_free(err);
+    }
+}
+
+/* libbalsa_identity_config_dialog displays an identity management
+   dialog. The dialog has a specified parent, existing list of
+   identites, the default one. Additionally, a callback is passed that
+   will be executed when the identity list is modified: new entries
+   are added or other entries are removed. */
+static void
+lbi_free_smtp_server_list(GSList ** smtp_server_list)
+{
+    g_slist_free_full(*smtp_server_list, g_object_unref);
+    g_free(smtp_server_list);
+}
+
+void
+libbalsa_identity_config_dialog(GtkWindow *parent, GList **identities,
+                               LibBalsaIdentity **default_id, GSList * smtp_servers,
+                               void (*changed_cb)(gpointer))
+{
+    static GtkWidget *dialog = NULL;
+    GtkWidget* frame;
+    GtkWidget* display_frame;
+    GtkWidget* hbox;
+    GtkTreeView* tree;
+    GtkTreeSelection *select;
+    GSList **smtp_server_list;
+
+    /* Show only one dialog at a time. */
+    if (dialog) {
+        gtk_window_present(GTK_WINDOW(dialog));
+        return;
+    }
+
+    dialog =
+        gtk_dialog_new_with_buttons(_("Manage Identities"),
+                                    parent, /* must NOT be modal */
+                                    GTK_DIALOG_DESTROY_WITH_PARENT |
+                                    libbalsa_dialog_flags(),
+                                    _("_Help"),             IDENTITY_RESPONSE_HELP,
+                                    /* Translators: button "New" identity */
+                                    C_("identity", "_New"), IDENTITY_RESPONSE_NEW,
+                                    _("_Remove"),           IDENTITY_RESPONSE_REMOVE,
+                                    _("_Close"),            IDENTITY_RESPONSE_CLOSE,
+                                    NULL);
+#if HAVE_MACOSX_DESKTOP
+    libbalsa_macosx_menu_for_parent(dialog, parent);
+#endif
+
+    frame = libbalsa_identity_config_frame(identities, default_id, dialog,
+                                           changed_cb, parent);
+    tree = GTK_TREE_VIEW(gtk_bin_get_child(GTK_BIN(frame)));
+
+    g_signal_connect(dialog, "response",
+                     G_CALLBACK(md_response_cb), tree);
+    g_object_set_data(G_OBJECT(dialog), "tree", tree);
+    g_object_add_weak_pointer(G_OBJECT(dialog), (gpointer) & dialog);
+    gtk_dialog_set_default_response(GTK_DIALOG(dialog),
+                                    IDENTITY_RESPONSE_CLOSE);
+
+    hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, padding);
+    gtk_widget_set_vexpand(hbox, TRUE);
+    gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), hbox);
+    gtk_box_pack_start(GTK_BOX(hbox), frame);
+
+    smtp_server_list = g_new(GSList *, 1);
+    *smtp_server_list = g_slist_copy(smtp_servers);
+    g_slist_foreach(smtp_servers, (GFunc) g_object_ref, NULL);
+    display_frame = setup_ident_frame(GTK_DIALOG(dialog),
+                                      FALSE, tree, smtp_servers);
+    g_object_weak_ref(G_OBJECT(display_frame),
+                     (GWeakNotify) lbi_free_smtp_server_list,
+                     smtp_server_list);
+
+    gtk_widget_set_hexpand(display_frame, TRUE);
+    gtk_box_pack_start(GTK_BOX(hbox), display_frame);
+
+    select = gtk_tree_view_get_selection(tree);
+    g_signal_connect(select, "changed",
+                     G_CALLBACK(config_frame_button_select_cb), dialog);
+    config_dialog_select(select, GTK_DIALOG(dialog));
+
+    gtk_widget_show(dialog);
+    gtk_widget_grab_focus(GTK_WIDGET(tree));
+}
+
+/* Callback for the "response" signal of the dialog. */
+static void
+md_response_cb(GtkWidget * dialog, gint response, GtkTreeView * tree)
+{
+    switch (response) {
+    case IDENTITY_RESPONSE_CLOSE:
+        if (close_cb(G_OBJECT(dialog)))
+            break;
+        return;
+    case IDENTITY_RESPONSE_NEW:
+        new_ident_cb(tree, G_OBJECT(dialog));
+        return;
+    case IDENTITY_RESPONSE_REMOVE:
+        delete_ident_cb(tree, dialog);
+        return;
+    case IDENTITY_RESPONSE_HELP:
+        help_ident_cb(dialog);
+        return;
+    default:
+        break;
+    }
+
+    gtk_widget_destroy(dialog);
+}
+
+/* config_dialog_select
+ *
+ * called when the tree's selection changes
+ * manage the button sensitivity, and update the display frame
+ */
+static void
+config_dialog_select(GtkTreeSelection * selection, GtkDialog * dialog)
+{
+    LibBalsaIdentity *ident, **default_id;
+    GtkTreeView *tree = gtk_tree_selection_get_tree_view(selection);
+
+    ident = get_selected_identity(tree);
+    default_id = g_object_get_data(G_OBJECT(tree), "default-id");
+    gtk_dialog_set_response_sensitive(dialog, IDENTITY_RESPONSE_REMOVE,
+                                      ident && ident != *default_id);
+    display_frame_update(G_OBJECT(dialog), ident);
+    g_object_set_data(G_OBJECT(dialog), "identity", ident);
+}
+
+static void
+display_frame_update(GObject * dialog, LibBalsaIdentity* ident)
+{
+    GtkWidget *face_box;
+
+    if (!ident)
+        return;
+
+    ident_dialog_update(dialog);
+    display_frame_set_field(dialog, "identity-name", ident->identity_name);
+    display_frame_set_field(dialog, "identity-fullname", ident->ia ? ident->ia->name : NULL);
+    if (ident->ia && INTERNET_ADDRESS_IS_MAILBOX (ident->ia))
+        display_frame_set_field(dialog, "identity-address",
+                                INTERNET_ADDRESS_MAILBOX(ident->ia)->addr);
+    else
+        display_frame_set_field(dialog, "identity-address", NULL);
+
+    display_frame_set_field(dialog, "identity-replyto", ident->replyto);
+    display_frame_set_field(dialog, "identity-domain", ident->domain);
+    display_frame_set_field(dialog, "identity-bcc", ident->bcc);
+    display_frame_set_field(dialog, "identity-replystring",
+                            ident->reply_string);
+    display_frame_set_field(dialog, "identity-forwardstring",
+                            ident->forward_string);
+    display_frame_set_boolean(dialog, "identity-sendmpalternative",
+                              ident->send_mp_alternative);
+    display_frame_set_server(dialog, "identity-smtp-server",
+                             ident->smtp_server);
+
+    display_frame_set_path(dialog, "identity-sigpath",
+                           ident->signature_path, FALSE);
+    display_frame_set_boolean(dialog, "identity-sigexecutable", ident->sig_executable);
+
+    display_frame_set_boolean(dialog, "identity-sigappend", ident->sig_sending);
+    display_frame_set_boolean(dialog, "identity-whenforward",
+                              ident->sig_whenforward);
+    display_frame_set_boolean(dialog, "identity-whenreply",
+                              ident->sig_whenreply);
+    display_frame_set_boolean(dialog, "identity-sigseparator",
+                              ident->sig_separator);
+    display_frame_set_boolean(dialog, "identity-sigprepend",
+                              ident->sig_prepend);
+
+    face_box = g_object_get_data(G_OBJECT(dialog),
+                                 path_info[LBI_PATH_TYPE_FACE].box_key);
+    gtk_widget_hide(face_box);
+    display_frame_set_path(dialog, path_info[LBI_PATH_TYPE_FACE].path_key,
+                           ident->face, TRUE);
+
+    face_box = g_object_get_data(G_OBJECT(dialog),
+                                 path_info[LBI_PATH_TYPE_XFACE].box_key);
+    gtk_widget_hide(face_box);
+    display_frame_set_path(dialog, path_info[LBI_PATH_TYPE_XFACE].path_key,
+                           ident->x_face, TRUE);
+    display_frame_set_boolean(dialog, "identity-requestmdn",
+                              ident->request_mdn);
+    display_frame_set_boolean(dialog, "identity-requestdsn",
+                              ident->request_dsn);
+
+    display_frame_set_boolean(dialog, "identity-gpgsign",
+                              ident->gpg_sign);
+    display_frame_set_boolean(dialog, "identity-gpgencrypt",
+                              ident->gpg_encrypt);
+    display_frame_set_boolean(dialog, "identity-trust-always",
+                              ident->always_trust);
+    display_frame_set_boolean(dialog, "identity-warn-send-plain",
+                              ident->warn_send_plain);
+    display_frame_set_gpg_mode(dialog, "identity-crypt-protocol",
+                          &ident->crypt_protocol);
+    display_frame_set_field(dialog, "identity-keyid", ident->force_gpg_key_id);
+    display_frame_set_field(dialog, "identity-keyid-sm", ident->force_smime_key_id);
+}
+
+
+static void
+display_frame_set_field(GObject * dialog,
+                        const gchar* key,
+                        const gchar* value)
+{
+    GtkEntry *entry = g_object_get_data(dialog, key);
+
+    gtk_entry_set_text(entry, value ? value : "");
+}
+
+static void
+display_frame_set_boolean(GObject * dialog,
+                          const gchar* key,
+                          gboolean value)
+{
+    GtkToggleButton *check = g_object_get_data(dialog, key);
+
+    gtk_toggle_button_set_active(check, value);
+}
+
+static void
+display_frame_set_path(GObject * dialog,
+                       const gchar* key,
+                       const gchar* value, gboolean use_chooser)
+{
+    gboolean set = (value && *value);
+    GtkWidget *chooser = g_object_get_data(dialog, key);
+    GtkToggleButton *check =
+        g_object_get_data(G_OBJECT(chooser), LIBBALSA_IDENTITY_CHECK);
+
+    if (set) {
+        if(use_chooser)
+            gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(chooser), value);
+        else
+            gtk_entry_set_text(GTK_ENTRY(chooser), value);
+    }
+    gtk_widget_set_sensitive(GTK_WIDGET(chooser), set);
+    gtk_toggle_button_set_active(check, set);
+}
+
+
+static void
+display_frame_set_gpg_mode(GObject * dialog, const gchar* key, gint * value)
+{
+    GtkComboBox *opt_menu = g_object_get_data(G_OBJECT(dialog), key);
+
+    switch (*value)
+        {
+        case LIBBALSA_PROTECT_OPENPGP:
+           gtk_combo_box_set_active(opt_menu, 1);
+            break;
+        case LIBBALSA_PROTECT_SMIMEV3:
+           gtk_combo_box_set_active(opt_menu, 2);
+            break;
+        case LIBBALSA_PROTECT_RFC3156:
+        default:
+           gtk_combo_box_set_active(opt_menu, 0);
+            *value = LIBBALSA_PROTECT_RFC3156;
+        }
+}
+
+/*
+ * Add an option menu to the given dialog with a label next to it
+ * explaining the contents.  A reference to the entry is stored as
+ * object data attached to the dialog with the given key.
+ */
+
+static void
+ident_dialog_add_gpg_menu(GtkWidget * grid, gint row, GtkDialog * dialog,
+                          const gchar * label_name, const gchar * menu_key)
+{
+    GtkWidget *label;
+    GtkWidget *opt_menu;
+    GPtrArray *values;
+
+    label = gtk_label_new_with_mnemonic(label_name);
+    gtk_widget_set_halign(label, GTK_ALIGN_START);
+    gtk_grid_attach(GTK_GRID(grid), label, 0, row, 1, 1);
+
+    opt_menu = gtk_combo_box_text_new();
+    values = g_ptr_array_sized_new(3);
+    g_object_set_data_full(G_OBJECT(opt_menu), "identity-value", values,
+                           (GDestroyNotify) ident_dialog_free_values);
+    gtk_grid_attach(GTK_GRID(grid), opt_menu, 1, row, 1, 1);
+    g_object_set_data(G_OBJECT(dialog), menu_key, opt_menu);
+
+    add_show_menu(_("GnuPG MIME mode"),
+                  GINT_TO_POINTER(LIBBALSA_PROTECT_RFC3156), opt_menu);
+    add_show_menu(_("GnuPG OpenPGP mode"),
+                  GINT_TO_POINTER(LIBBALSA_PROTECT_OPENPGP), opt_menu);
+    add_show_menu(_("GpgSM S/MIME mode"),
+                  GINT_TO_POINTER(LIBBALSA_PROTECT_SMIMEV3), opt_menu);
+}
+
+/* add_show_menu: helper function */
+static void
+add_show_menu(const char *label, gpointer data, GtkWidget * menu)
+{
+    GPtrArray *values =
+        g_object_get_data(G_OBJECT(menu), "identity-value");
+
+    gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(menu), label);
+    g_ptr_array_add(values, data);
+}
+
+/* ident_dialog_free_values: helper function */
+static void
+ident_dialog_free_values(GPtrArray * values)
+{
+    g_ptr_array_free(values, TRUE);
+}
+
+static void
+ident_dialog_add_smtp_menu(GtkWidget * grid, gint row, GtkDialog * dialog,
+                           const gchar * label_name,
+                           const gchar * menu_key, GSList * smtp_servers)
+{
+    GtkWidget *label;
+    GtkWidget *combo_box;
+    GSList *list;
+    GPtrArray *values;
+
+    label = gtk_label_new_with_mnemonic(label_name);
+    gtk_widget_set_halign(label, GTK_ALIGN_START);
+    gtk_grid_attach(GTK_GRID(grid), label, 0, row, 1, 1);
+
+    combo_box = gtk_combo_box_text_new();
+    gtk_label_set_mnemonic_widget(GTK_LABEL(label), combo_box);
+    values = g_ptr_array_sized_new(g_slist_length(smtp_servers));
+    g_object_set_data_full(G_OBJECT(combo_box), "identity-value", values,
+                           (GDestroyNotify) ident_dialog_free_values);
+    gtk_grid_attach(GTK_GRID(grid), combo_box, 1, row, 1, 1);
+    g_object_set_data(G_OBJECT(dialog), menu_key, combo_box);
+
+    for (list = smtp_servers; list; list = list->next) {
+        LibBalsaSmtpServer *smtp_server = LIBBALSA_SMTP_SERVER(list->data);
+        add_show_menu(libbalsa_smtp_server_get_name(smtp_server),
+                      smtp_server, combo_box);
+    }
+}
+
+static void
+display_frame_set_server(GObject * dialog, const gchar * key,
+                         LibBalsaSmtpServer * smtp_server)
+{
+    GtkComboBox *combo_box = g_object_get_data(G_OBJECT(dialog), key);
+    GPtrArray *values;
+    guint i;
+
+    values = g_object_get_data(G_OBJECT(combo_box), "identity-value");
+
+    for (i = 0; i < values->len; i++) {
+        if (g_ptr_array_index(values, i) == smtp_server)
+            gtk_combo_box_set_active(combo_box, i);
+    }
+}
+
+/*
+ * Get the value of the active option menu item
+ */
+static gpointer
+ident_dialog_get_value(GObject * dialog, const gchar * key)
+{
+    GtkWidget * menu;
+    gint value;
+    GPtrArray *values;
+
+    menu = g_object_get_data(dialog, key);
+    value = gtk_combo_box_get_active(GTK_COMBO_BOX(menu));
+    values = g_object_get_data(G_OBJECT(menu), "identity-value");
+
+    return g_ptr_array_index(values, value);
+}
+
+/*
+ * End of the Manage Identities dialog
+ */
+
+/*
+ * The Identities combo box
+ */
+
+GtkWidget *
+libbalsa_identity_combo_box(GList       * identities,
+                            const gchar * active_name,
+                            GCallback     changed_cb,
+                            gpointer      changed_data)
+{
+    GList *list;
+    GtkListStore *store;
+    GtkWidget *combo_box;
+    GtkCellLayout *layout;
+    GtkCellRenderer *renderer;
+
+    /* For each identity, store the address, the identity name, and a
+     * ref to the identity in a combo-box.
+     * Note: we can't depend on identities staying in the same
+     * order while the combo-box is open, so we need a ref to the
+     * actual identity. */
+    store = gtk_list_store_new(3,
+                               G_TYPE_STRING,
+                               G_TYPE_STRING,
+                               G_TYPE_OBJECT);
+    combo_box = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store));
+
+    for (list = identities; list != NULL; list = list->next) {
+        LibBalsaIdentity *ident;
+        gchar *from;
+        gchar *name;
+        GtkTreeIter iter;
+
+        ident = list->data;
+        from = internet_address_to_string(ident->ia, FALSE);
+       name = g_strconcat("(", ident->identity_name, ")", NULL);
+
+        gtk_list_store_append(store, &iter);
+        gtk_list_store_set(store, &iter,
+                           0, from,
+                           1, name,
+                           2, ident,
+                           -1);
+
+        g_free(from);
+        g_free(name);
+
+        if (g_strcmp0(active_name, ident->identity_name) == 0)
+            gtk_combo_box_set_active_iter(GTK_COMBO_BOX(combo_box), &iter);
+    }
+    g_object_unref(store);
+
+    layout = GTK_CELL_LAYOUT(combo_box);
+    renderer = gtk_cell_renderer_text_new();
+    gtk_cell_layout_pack_start(layout, renderer, TRUE);
+    gtk_cell_layout_set_attributes(layout, renderer, "text", 0, NULL);
+    renderer = gtk_cell_renderer_text_new();
+    gtk_cell_layout_pack_start(layout, renderer, FALSE);
+    gtk_cell_layout_set_attributes(layout, renderer, "text", 1, NULL);
+
+    g_signal_connect(combo_box, "changed", changed_cb, changed_data);
+
+    return combo_box;
+}
diff --git a/libbalsa/identity-widgets.h b/libbalsa/identity-widgets.h
new file mode 100644
index 0000000..12e7e8c
--- /dev/null
+++ b/libbalsa/identity-widgets.h
@@ -0,0 +1,56 @@
+/* -*-mode:c; c-style:k&r; c-basic-offset:4; -*- */
+/* Balsa E-Mail Client
+ * Copyright (C) 1997-2018 Stuart Parmenter and others,
+ *                         See the file AUTHORS for a list.
+ *
+ * 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; either version 2, or (at your option) 
+ * any later version.
+ *  
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#ifndef __LIBBALSA_IDENTITY_WIDGETS_H__
+#define __LIBBALSA_IDENTITY_WIDGETS_H__
+
+#ifndef BALSA_VERSION
+# error "Include config.h before this file."
+#endif
+
+#include <gtk/gtk.h>
+#include "identity.h"
+
+G_BEGIN_DECLS
+
+void libbalsa_identity_config_dialog(GtkWindow         * parent,
+                                     GList            ** identities,
+                                     LibBalsaIdentity ** current,
+                                     GSList            * smtp_servers,
+                                     void              (*changed_cb)(gpointer));
+
+typedef void (*LibBalsaIdentityCallback) (gpointer           data,
+                                          LibBalsaIdentity * identity);
+
+void libbalsa_identity_select_dialog(GtkWindow              * parent,
+                                     const gchar            * prompt,
+                                     GList                  * identities,
+                                     LibBalsaIdentity       * initial_id,
+                                     LibBalsaIdentityCallback update,
+                                     gpointer                 data);
+
+GtkWidget * libbalsa_identity_combo_box(GList       * identities,
+                                        const gchar * active_name,
+                                        GCallback     changed_cb,
+                                        gpointer      changed_data);
+
+G_END_DECLS
+
+#endif /* __LIBBALSA_IDENTITY_WIDGETS_H__ */
diff --git a/libbalsa/identity.c b/libbalsa/identity.c
index fe4c0fa..aed5356 100644
--- a/libbalsa/identity.c
+++ b/libbalsa/identity.c
@@ -1,6 +1,6 @@
 /* -*-mode:c; c-style:k&r; c-basic-offset:4; -*- */
 /* Balsa E-Mail Client
- * Copyright (C) 1997-2016 Stuart Parmenter and others,
+ * Copyright (C) 1997-2018 Stuart Parmenter and others,
  *                         See the file AUTHORS for a list.
  *
  * This program is free software; you can redistribute it and/or modify
@@ -44,48 +44,17 @@
  * The class.
  */
 
-static GObjectClass* parent_class;
-
 /* Forward references. */
-static void libbalsa_identity_class_init(LibBalsaIdentityClass* klass);
-static void libbalsa_identity_init(LibBalsaIdentity* ident);
 static void libbalsa_identity_dispose(GObject* object);
 static void libbalsa_identity_finalize(GObject* object);
 
-GType
-libbalsa_identity_get_type()
-{
-    static GType libbalsa_identity_type = 0;
-
-    if (!libbalsa_identity_type) {
-        static const GTypeInfo libbalsa_identity_info = {
-            sizeof(LibBalsaIdentityClass),
-            NULL,               /* base_init */
-            NULL,               /* base_finalize */
-            (GClassInitFunc) libbalsa_identity_class_init,
-            NULL,               /* class_finalize */
-            NULL,               /* class_data */
-            sizeof(LibBalsaIdentity),
-            0,                  /* n_preallocs */
-            (GInstanceInitFunc) libbalsa_identity_init,
-        };
-
-        libbalsa_identity_type =
-            g_type_register_static(G_TYPE_OBJECT,
-                                   "LibBalsaIdentity",
-                                   &libbalsa_identity_info, 0);
-    }
-
-    return libbalsa_identity_type;
-}
+G_DEFINE_TYPE(LibBalsaIdentity, libbalsa_identity, G_TYPE_OBJECT)
 
 static void
 libbalsa_identity_class_init(LibBalsaIdentityClass* klass)
 {
     GObjectClass* object_class;
 
-    parent_class = g_type_class_peek_parent(klass);
-
     object_class = G_OBJECT_CLASS(klass);
     object_class->dispose  = libbalsa_identity_dispose;
     object_class->finalize = libbalsa_identity_finalize;
@@ -138,7 +107,7 @@ libbalsa_identity_dispose(GObject * object)
     g_clear_object(&ident->ia);
     g_clear_object(&ident->smtp_server);
 
-    G_OBJECT_CLASS(parent_class)->dispose(object);
+    G_OBJECT_CLASS(libbalsa_identity_parent_class)->dispose(object);
 }
 
 static void
@@ -158,7 +127,7 @@ libbalsa_identity_finalize(GObject * object)
     g_free(ident->force_gpg_key_id);
     g_free(ident->force_smime_key_id);
 
-    G_OBJECT_CLASS(parent_class)->finalize(object);
+    G_OBJECT_CLASS(libbalsa_identity_parent_class)->finalize(object);
 }
 
 /*
@@ -195,7 +164,7 @@ libbalsa_identity_new_with_name(const gchar* ident_name)
 void
 libbalsa_identity_set_identity_name(LibBalsaIdentity* ident, const gchar* name)
 {
-    g_return_if_fail(ident != NULL);
+    g_return_if_fail(LIBBALSA_IS_IDENTITY(ident));
 
     g_free(ident->identity_name);
     ident->identity_name = g_strdup(name);
@@ -206,7 +175,7 @@ void
 libbalsa_identity_set_address(LibBalsaIdentity * ident,
                               InternetAddress * ia)
 {
-    g_return_if_fail(ident != NULL);
+    g_return_if_fail(LIBBALSA_IS_IDENTITY(ident));
 
     g_set_object(&ident->ia, ia);
 }
@@ -215,7 +184,7 @@ libbalsa_identity_set_address(LibBalsaIdentity * ident,
 void
 libbalsa_identity_set_replyto(LibBalsaIdentity* ident, const gchar* address)
 {
-    g_return_if_fail(ident != NULL);
+    g_return_if_fail(LIBBALSA_IS_IDENTITY(ident));
 
     g_free(ident->replyto);
     ident->replyto = g_strdup(address);
@@ -225,7 +194,7 @@ libbalsa_identity_set_replyto(LibBalsaIdentity* ident, const gchar* address)
 void
 libbalsa_identity_set_domain(LibBalsaIdentity* ident, const gchar* dom)
 {
-    g_return_if_fail(ident != NULL);
+    g_return_if_fail(LIBBALSA_IS_IDENTITY(ident));
 
     g_free(ident->domain);
     ident->domain = g_strdup(dom);
@@ -235,7 +204,7 @@ libbalsa_identity_set_domain(LibBalsaIdentity* ident, const gchar* dom)
 void
 libbalsa_identity_set_bcc(LibBalsaIdentity* ident, const gchar* bcc)
 {
-    g_return_if_fail(ident != NULL);
+    g_return_if_fail(LIBBALSA_IS_IDENTITY(ident));
 
     g_free(ident->bcc);
     ident->bcc = g_strdup(bcc);
@@ -245,7 +214,7 @@ libbalsa_identity_set_bcc(LibBalsaIdentity* ident, const gchar* bcc)
 void
 libbalsa_identity_set_reply_string(LibBalsaIdentity* ident, const gchar* reply)
 {
-    g_return_if_fail(ident != NULL);
+    g_return_if_fail(LIBBALSA_IS_IDENTITY(ident));
 
     g_free(ident->reply_string);
     ident->reply_string = g_strdup(reply);
@@ -255,7 +224,7 @@ libbalsa_identity_set_reply_string(LibBalsaIdentity* ident, const gchar* reply)
 void
 libbalsa_identity_set_forward_string(LibBalsaIdentity* ident, const gchar* forward)
 {
-    g_return_if_fail(ident != NULL);
+    g_return_if_fail(LIBBALSA_IS_IDENTITY(ident));
 
     g_free(ident->forward_string);
     ident->forward_string = g_strdup(forward);
@@ -265,7 +234,7 @@ libbalsa_identity_set_forward_string(LibBalsaIdentity* ident, const gchar* forwa
 void
 libbalsa_identity_set_send_mp_alternative(LibBalsaIdentity* ident, gboolean send_mp_alternative)
 {
-    g_return_if_fail(ident != NULL);
+    g_return_if_fail(LIBBALSA_IS_IDENTITY(ident));
     ident->send_mp_alternative = send_mp_alternative;
 }
 
@@ -273,7 +242,7 @@ libbalsa_identity_set_send_mp_alternative(LibBalsaIdentity* ident, gboolean send
 void
 libbalsa_identity_set_signature_path(LibBalsaIdentity* ident, const gchar* path)
 {
-    g_return_if_fail(ident != NULL);
+    g_return_if_fail(LIBBALSA_IS_IDENTITY(ident));
 
     g_free(ident->signature_path);
     ident->signature_path = g_strdup(path);
@@ -283,7 +252,7 @@ libbalsa_identity_set_signature_path(LibBalsaIdentity* ident, const gchar* path)
 void
 libbalsa_identity_set_sig_executable(LibBalsaIdentity* ident, gboolean sig_executable)
 {
-    g_return_if_fail(ident != NULL);
+    g_return_if_fail(LIBBALSA_IS_IDENTITY(ident));
     ident->sig_executable = sig_executable;
 }
 
@@ -291,7 +260,7 @@ libbalsa_identity_set_sig_executable(LibBalsaIdentity* ident, gboolean sig_execu
 void
 libbalsa_identity_set_sig_sending(LibBalsaIdentity* ident, gboolean sig_sending)
 {
-    g_return_if_fail(ident != NULL);
+    g_return_if_fail(LIBBALSA_IS_IDENTITY(ident));
     ident->sig_sending = sig_sending;
 }
 
@@ -299,7 +268,7 @@ libbalsa_identity_set_sig_sending(LibBalsaIdentity* ident, gboolean sig_sending)
 void
 libbalsa_identity_set_sig_whenforward(LibBalsaIdentity* ident, gboolean forward)
 {
-    g_return_if_fail(ident != NULL);
+    g_return_if_fail(LIBBALSA_IS_IDENTITY(ident));
     ident->sig_whenforward = forward;
 }
 
@@ -307,7 +276,7 @@ libbalsa_identity_set_sig_whenforward(LibBalsaIdentity* ident, gboolean forward)
 void
 libbalsa_identity_set_sig_whenreply(LibBalsaIdentity* ident, gboolean reply)
 {
-    g_return_if_fail(ident != NULL);
+    g_return_if_fail(LIBBALSA_IS_IDENTITY(ident));
     ident->sig_whenreply = reply;
 }
 
@@ -315,7 +284,7 @@ libbalsa_identity_set_sig_whenreply(LibBalsaIdentity* ident, gboolean reply)
 void
 libbalsa_identity_set_sig_separator(LibBalsaIdentity* ident, gboolean separator)
 {
-    g_return_if_fail(ident != NULL);
+    g_return_if_fail(LIBBALSA_IS_IDENTITY(ident));
     ident->sig_separator = separator;
 }
 
@@ -323,1643 +292,146 @@ libbalsa_identity_set_sig_separator(LibBalsaIdentity* ident, gboolean separator)
 void
 libbalsa_identity_set_sig_prepend(LibBalsaIdentity* ident, gboolean prepend)
 {
-    g_return_if_fail(ident != NULL);
+    g_return_if_fail(LIBBALSA_IS_IDENTITY(ident));
     ident->sig_prepend = prepend;
 }
 
-/** Returns a signature for given identity, adding a signature prefix
-    if needed. parent can be NULL. */
-gchar*
-libbalsa_identity_get_signature(LibBalsaIdentity* identity, GtkWindow *parent)
-{
-    gchar *ret = NULL, *path;
-    gchar *retval;
-
-    if (identity->signature_path == NULL ||
-        *identity->signature_path == '\0')
-       return NULL;
-
-    path = libbalsa_expand_path(identity->signature_path);
-    if(identity->sig_executable){
-        GError *error = NULL;
-        gchar *argv[] = {"/bin/sh", "-c", path, NULL};
-
-        if (!g_spawn_sync(NULL, argv, NULL, G_SPAWN_DEFAULT, NULL, NULL,
-                          &ret, NULL, NULL, &error)) {
-            libbalsa_information_parented(parent, LIBBALSA_INFORMATION_ERROR,
-                                          _("Error executing signature generator “%s”: %s"),
-                                          identity->signature_path, error->message);
-            g_error_free(error);
-        }
-    } else {
-       GError *error = NULL;
-
-       if (!g_file_get_contents(path, &ret, NULL, &error)) {
-            libbalsa_information_parented(parent, LIBBALSA_INFORMATION_ERROR,
-                                          _("Cannot read signature file “%s”: %s"),
-                                          identity->signature_path, error->message);
-               g_error_free(error);
-       }
-    }
-    g_free(path);
-
-    if(ret == NULL) return NULL;
-
-    if (!libbalsa_utf8_sanitize(&ret, FALSE, NULL)) {
-        libbalsa_information_parented(parent, LIBBALSA_INFORMATION_ERROR,
-                                      _("Signature in %s is not a UTF-8 text."),
-                                      identity->signature_path);
-    }
-
-    /* Prepend the separator if needed... */
-
-    if (identity->sig_separator
-        && !(g_str_has_prefix(ret, "--\n") || g_str_has_prefix(ret, "-- \n"))) {
-        retval = g_strconcat("\n-- \n", ret, NULL);
-    } else {
-        retval = g_strconcat("\n", ret, NULL);
-    }
-    g_free(ret);
-
-    return retval;
-}
 
 void
-libbalsa_identity_set_smtp_server(LibBalsaIdentity * ident,
-                                  LibBalsaSmtpServer * smtp_server)
+libbalsa_identity_set_face_path(LibBalsaIdentity *ident, const gchar *path)
 {
-    g_return_if_fail(ident != NULL);
+    g_return_if_fail(LIBBALSA_IS_IDENTITY(ident));
 
-    g_set_object(&ident->smtp_server, smtp_server);
+    g_free(ident->face);
+    ident->face = g_strdup(path);
 }
 
 
-/* Used by both dialogs: */
-
-/* Widget padding: */
-static const guint padding = 6;
-
-/* Forward references: */
-static void identity_list_update_real(GtkTreeView * tree,
-                                      GList * identities,
-                                      LibBalsaIdentity * default_id);
-static GtkWidget *libbalsa_identity_tree(GCallback toggled_cb,
-                                         gpointer toggled_data,
-                                         gchar * toggled_title);
-
-/*
- * The Select Identity dialog; called from compose window.
- */
-
-/* Info passed to callbacks: */
-struct SelectDialogInfo_ {
-    LibBalsaIdentityCallback update;
-    gpointer data;
-    GtkWidget *tree;
-    GtkWidget *dialog;
-    GtkWindow *parent;
-    guint idle_handler_id;
-};
-typedef struct SelectDialogInfo_ SelectDialogInfo;
-
-/* Forward references: */
-static void sd_destroy_notify(SelectDialogInfo * sdi);
-static void sd_response_cb(GtkWidget * dialog, gint response,
-                           SelectDialogInfo * sdi);
-static void sd_idle_add_response_ok(SelectDialogInfo * sdi);
-static gboolean sd_response_ok(SelectDialogInfo * sdi);
-
-/* Tree columns: */
-enum {
-    DEFAULT_COLUMN,
-    NAME_COLUMN,
-    IDENT_COLUMN,
-    N_COLUMNS
-};
-
-/*
- * Public method: create and show the dialog.
- */
-#define LIBBALSA_IDENTITY_SELECT_DIALOG_KEY "libbalsa-identity-select-dialog"
 void
-libbalsa_identity_select_dialog(GtkWindow * parent,
-                                const gchar * prompt,
-                                GList * identities,
-                                LibBalsaIdentity * initial_id,
-                                LibBalsaIdentityCallback update,
-                                gpointer data)
-{
-    GtkWidget *dialog;
-    GtkWidget *tree;
-    SelectDialogInfo *sdi;
-    GtkWidget *frame;
-
-    /* Show only one dialog at a time. */
-    sdi = g_object_get_data(G_OBJECT(parent),
-                            LIBBALSA_IDENTITY_SELECT_DIALOG_KEY);
-    if (sdi) {
-        gtk_window_present(GTK_WINDOW(sdi->dialog));
-        return;
-    }
-
-    sdi = g_new(SelectDialogInfo, 1);
-    sdi->parent = parent;
-    g_object_set_data_full(G_OBJECT(parent),
-                           LIBBALSA_IDENTITY_SELECT_DIALOG_KEY,
-                           sdi, (GDestroyNotify) sd_destroy_notify);
-    sdi->update = update;
-    sdi->data = data;
-    sdi->idle_handler_id = 0;
-    sdi->dialog = dialog =
-        gtk_dialog_new_with_buttons(prompt, parent,
-                                    GTK_DIALOG_DESTROY_WITH_PARENT |
-                                    libbalsa_dialog_flags(),
-                                    _("_Cancel"), GTK_RESPONSE_CANCEL,
-                                    _("_OK"),     GTK_RESPONSE_OK,
-                                    NULL);
-#if HAVE_MACOSX_DESKTOP
-    libbalsa_macosx_menu_for_parent(dialog, parent);
-#endif
-
-    g_signal_connect(dialog, "response",
-                     G_CALLBACK(sd_response_cb), sdi);
-    gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_OK);
-
-    sdi->tree = tree =
-        libbalsa_identity_tree(G_CALLBACK(sd_idle_add_response_ok), sdi,
-                               _("Current"));
-    g_signal_connect_swapped(tree, "row-activated",
-                             G_CALLBACK(sd_idle_add_response_ok), sdi);
-    identity_list_update_real(GTK_TREE_VIEW(tree), identities, initial_id);
-
-    frame = gtk_frame_new(NULL);
-    gtk_widget_set_vexpand(frame, TRUE);
-    gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))),
-                       frame);
-
-    g_object_set(G_OBJECT(tree), "margin", padding, NULL);
-    gtk_container_add(GTK_CONTAINER(frame), tree);
-
-    gtk_widget_show(dialog);
-    gtk_widget_grab_focus(tree);
-}
-
-/* GDestroyNotify for sdi. */
-static void
-sd_destroy_notify(SelectDialogInfo * sdi)
-{
-    libbalsa_clear_source_id(&sdi->idle_handler_id);
-    g_free(sdi);
-}
-
-/* Callback for the dialog's "response" signal. */
-static void
-sd_response_cb(GtkWidget * dialog, gint response, SelectDialogInfo * sdi)
-{
-    if (response == GTK_RESPONSE_OK) {
-        GtkTreeSelection *selection =
-            gtk_tree_view_get_selection(GTK_TREE_VIEW(sdi->tree));
-        GtkTreeModel *model;
-        GtkTreeIter iter;
-
-        if (gtk_tree_selection_get_selected(selection, &model, &iter)) {
-            LibBalsaIdentity *identity;
-
-            gtk_tree_model_get(model, &iter, IDENT_COLUMN, &identity, -1);
-            sdi->update(sdi->data, identity);
-        }
-    }
-
-    /* Clear the data set on the parent window, so we know that the
-     * dialog was destroyed. This will also trigger the GDestroyNotify
-     * function, sd_destroy_notify.
-     */
-    g_object_set_data(G_OBJECT(sdi->parent),
-                      LIBBALSA_IDENTITY_SELECT_DIALOG_KEY,
-                      NULL);
-
-    gtk_widget_destroy(dialog);
-}
-
-/* Helper for adding idles. */
-static void
-sd_idle_add_response_ok(SelectDialogInfo * sdi)
-{
-    if (!sdi->idle_handler_id)
-        sdi->idle_handler_id =
-            g_idle_add((GSourceFunc) sd_response_ok, sdi);
-}
-
-/* Idle handler for sending the OK response to the dialog. */
-static gboolean
-sd_response_ok(SelectDialogInfo * sdi)
-{
-    if (sdi->idle_handler_id) {
-        sdi->idle_handler_id = 0;
-        gtk_dialog_response(GTK_DIALOG(sdi->dialog), GTK_RESPONSE_OK);
-    }
-    return FALSE;
-}
-
-/*
- * The Manage Identities dialog; called from main window.
- */
-
-typedef struct _IdentityDeleteInfo IdentityDeleteInfo;
-
-/* button actions */
-static gboolean close_cb(GObject * dialog);
-static void new_ident_cb(GtkTreeView * tree, GObject * dialog);
-static void delete_ident_cb(GtkTreeView * tree, GtkWidget * dialog);
-static void delete_ident_response(GtkWidget * confirm, gint response,
-                                  IdentityDeleteInfo * di);
-static void help_ident_cb(GtkWidget * widget);
-
-static void set_default_ident_cb(GtkTreeView * tree, GtkTreePath * path,
-                                 GtkTreeViewColumn * column,
-                                 gpointer data);
-static void config_frame_button_select_cb(GtkTreeSelection * selection,
-                                          GtkDialog * dialog);
-
-static void ident_dialog_add_checkbutton(GtkWidget *, gint, GtkDialog *,
-                                         const gchar *, const gchar *,
-                                        gboolean sensitive);
-static void ident_dialog_add_check_and_entry(GtkWidget *, gint, GtkDialog *,
-                                             const gchar *, const gchar *);
-static void ident_dialog_add_entry(GtkWidget *, gint, GtkDialog *,
-                                   const gchar *, const gchar *);
-static void ident_dialog_add_keysel_entry(GtkWidget   *grid,
-                                                                                 gint         row,
-                                                                                 GtkDialog   *dialog,
-                                                                                 const gchar *label_name,
-                                                                                 const gchar *entry_key);
-typedef enum LibBalsaIdentityPathType_ {
-    LBI_PATH_TYPE_FACE,
-    LBI_PATH_TYPE_XFACE
-} LibBalsaIdentityPathType;
-static void ident_dialog_add_file_chooser_button(GtkWidget * grid,
-                                                 gint row,
-                                                 GtkDialog * dialog,
-                                                 LibBalsaIdentityPathType
-                                                 type);
-static void ident_dialog_add_boxes(GtkWidget * grid, gint row,
-                                   GtkDialog * dialog, const gchar * key1,
-                                   const gchar * key2);
-static gchar *ident_dialog_get_text(GObject *, const gchar *);
-static gboolean ident_dialog_get_bool(GObject *, const gchar *);
-static gchar *ident_dialog_get_path(GObject * dialog, const gchar * key);
-static gboolean ident_dialog_update(GObject *);
-static void config_dialog_select(GtkTreeSelection * selection,
-                                 GtkDialog * dialog);
-
-static void display_frame_update(GObject * dialog, LibBalsaIdentity* ident);
-static void display_frame_set_field(GObject * dialog, const gchar* key,
-                                    const gchar* value);
-static void display_frame_set_boolean(GObject * dialog, const gchar* key,
-                                      gboolean value);
-static void display_frame_set_path(GObject * dialog, const gchar * key,
-                                   const gchar * value, gboolean use_chooser);
-
-
-static void identity_list_update(GtkTreeView * tree);
-static gboolean select_identity(GtkTreeView * tree,
-                                LibBalsaIdentity * identity);
-static LibBalsaIdentity *get_selected_identity(GtkTreeView * tree);
-static void set_identity_name_in_tree(GtkTreeView * tree,
-                                     LibBalsaIdentity * identity,
-                                     const gchar * name);
-static void md_response_cb(GtkWidget * dialog, gint response,
-                           GtkTreeView * tree);
-static void md_name_changed(GtkEntry * name, GtkTreeView * tree);
-
-static void ident_dialog_add_gpg_menu(GtkWidget * grid, gint row,
-                                      GtkDialog * dialog,
-                                      const gchar * label_name,
-                                      const gchar * menu_key);
-static void add_show_menu(const char *label, gpointer data,
-                          GtkWidget * menu);
-static void ident_dialog_free_values(GPtrArray * values);
-
-static void display_frame_set_gpg_mode(GObject * dialog,
-                                       const gchar * key, gint * value);
-
-static void ident_dialog_add_smtp_menu(GtkWidget * grid, gint row,
-                                       GtkDialog * dialog,
-                                       const gchar * label_name,
-                                       const gchar * menu_key,
-                                      GSList * smtp_servers);
-static void display_frame_set_server(GObject * dialog,
-                                     const gchar * key,
-                                     LibBalsaSmtpServer * smtp_server);
-
-static gpointer ident_dialog_get_value(GObject * dialog,
-                                       const gchar * key);
-
-/* Callback for the "toggled" signal of the "Default" column. */
-static void
-toggle_cb(GObject * dialog, gchar * path)
-{
-    GtkTreeView *tree = g_object_get_data(dialog, "tree");
-    GtkTreeModel *model = gtk_tree_view_get_model(tree);
-    GtkTreeIter iter;
-
-    /* Save any changes to current identity; if it's not valid, just
-     * return. */
-    if (!ident_dialog_update(dialog))
-       return;
-
-    if (gtk_tree_model_get_iter_from_string(model, &iter, path)) {
-        LibBalsaIdentity *identity, **default_id;
-
-        gtk_tree_model_get(model, &iter, IDENT_COLUMN, &identity, -1);
-        default_id = g_object_get_data(G_OBJECT(tree), "default-id");
-        *default_id = identity;
-        identity_list_update(tree);
-    }
-}
-
-/*
- * Common code for making a GtkTreeView list of identities:
- *
- * toggled_cb           callback for the "toggled" signal of the boolean
- *                      column;
- * toggled_data         user_data for the callback;
- * toggled_title        title for the boolean column.
- */
-static GtkWidget *
-libbalsa_identity_tree(GCallback toggled_cb, gpointer toggled_data,
-                       gchar * toggled_title)
-{
-    GtkListStore *store;
-    GtkWidget *tree;
-    GtkCellRenderer *renderer;
-    GtkTreeViewColumn *column;
-
-    store = gtk_list_store_new(N_COLUMNS,
-                               G_TYPE_BOOLEAN,
-                               G_TYPE_STRING,
-                               G_TYPE_POINTER);
-
-    tree = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
-    g_object_unref(store);
-
-    renderer = gtk_cell_renderer_toggle_new();
-    g_signal_connect_swapped(renderer, "toggled",
-                             toggled_cb, toggled_data);
-    column =
-        gtk_tree_view_column_new_with_attributes(toggled_title, renderer,
-                                                 "radio", DEFAULT_COLUMN,
-                                                 NULL);
-    gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
-
-    renderer = gtk_cell_renderer_text_new();
-    column = gtk_tree_view_column_new_with_attributes("Name", renderer,
-                                                      "text", NAME_COLUMN,
-                                                      NULL);
-    gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
-
-    return tree;
-}
-
-/*
- * Create and return a frame containing a list of the identities in
- * the application and a number of buttons to edit, create, and delete
- * identities.  Also provides a way to set the default identity.
- */
-static GtkWidget*
-libbalsa_identity_config_frame(GList** identities,
-                              LibBalsaIdentity** defid, GtkWidget * dialog,
-                               void (*cb)(gpointer), gpointer data)
-{
-    GtkWidget* config_frame = gtk_frame_new(NULL);
-    GtkWidget *tree;
-
-    tree = libbalsa_identity_tree(G_CALLBACK(toggle_cb), dialog,
-                                  _("Default"));
-    g_signal_connect(tree, "row-activated",
-                     G_CALLBACK(set_default_ident_cb), NULL);
-    g_object_set_data(G_OBJECT(tree), "identities", identities);
-    g_object_set_data(G_OBJECT(tree), "default-id", defid);
-    g_object_set_data(G_OBJECT(tree), "callback", cb);
-    g_object_set_data(G_OBJECT(tree), "cb-data",  data);
-
-    g_object_set(G_OBJECT(tree), "margin", 0, NULL); /* Seriously? */
-    gtk_container_add(GTK_CONTAINER(config_frame), tree);
-
-    identity_list_update(GTK_TREE_VIEW(tree));
-
-    return config_frame;
-}
-
-static gint
-compare_identities(LibBalsaIdentity *id1, LibBalsaIdentity *id2)
-{
-    return g_ascii_strcasecmp(id1->identity_name, id2->identity_name);
-}
-
-/* identity_list_update:
- * Update the list of identities in the config frame, displaying the
- * available identities in the application, and which is default.
- */
-static void
-identity_list_update(GtkTreeView * tree)
-{
-    GList **identities =
-        g_object_get_data(G_OBJECT(tree), "identities");
-    LibBalsaIdentity **default_id =
-        g_object_get_data(G_OBJECT(tree), "default-id");
-
-    identity_list_update_real(tree, *identities, *default_id);
-}
-
-static void
-identity_list_update_real(GtkTreeView * tree,
-                          GList * identities,
-                          LibBalsaIdentity * default_id)
-{
-    GtkListStore *store = GTK_LIST_STORE(gtk_tree_view_get_model(tree));
-    GList *sorted, *list;
-    LibBalsaIdentity *current;
-    GtkTreeIter iter;
-
-    current = get_selected_identity(tree);
-
-    gtk_list_store_clear(store);
-
-    sorted = g_list_sort(g_list_copy(identities),
-                         (GCompareFunc) compare_identities);
-    for (list = sorted; list != NULL; list = list->next) {
-        LibBalsaIdentity* ident = LIBBALSA_IDENTITY(list->data);
-        gtk_list_store_append(store, &iter);
-        gtk_list_store_set(store, &iter,
-                           DEFAULT_COLUMN, ident == default_id,
-                           NAME_COLUMN, ident->identity_name,
-                           IDENT_COLUMN, ident,
-                           -1);
-    }
-    g_list_free(sorted);
-
-    if (!select_identity(tree, current))
-        select_identity(tree, default_id);
-}
-
-static gboolean
-select_identity(GtkTreeView * tree, LibBalsaIdentity * identity)
-{
-    GtkTreeModel *model = gtk_tree_view_get_model(tree);
-    GtkTreeIter iter;
-    gboolean valid;
-
-    for (valid = gtk_tree_model_get_iter_first(model, &iter);
-         valid;
-         valid = gtk_tree_model_iter_next(model, &iter)) {
-        LibBalsaIdentity *tmp;
-
-        gtk_tree_model_get(model, &iter, IDENT_COLUMN, &tmp, -1);
-        if (identity == tmp) {
-            GtkTreePath *path = gtk_tree_model_get_path(model, &iter);
-            gtk_tree_view_set_cursor(tree, path, NULL, FALSE);
-            gtk_tree_path_free(path);
-
-            return TRUE;
-        }
-    }
-
-    return FALSE;
-}
-
-static LibBalsaIdentity *
-get_selected_identity(GtkTreeView * tree)
-{
-    GtkTreeSelection *select = gtk_tree_view_get_selection(tree);
-    GtkTreeModel *model = gtk_tree_view_get_model(tree);
-    GtkTreeIter iter;
-    LibBalsaIdentity *identity = NULL;
-
-    if (gtk_tree_selection_get_selected(select, &model, &iter))
-        gtk_tree_model_get(model, &iter, IDENT_COLUMN, &identity, -1);
-
-    return identity;
-}
-
-enum {
-    IDENTITY_RESPONSE_HELP = GTK_RESPONSE_HELP,
-    IDENTITY_RESPONSE_CLOSE = GTK_RESPONSE_CANCEL,
-    IDENTITY_RESPONSE_NEW,
-    IDENTITY_RESPONSE_REMOVE
-};
-
-/* callback for the "changed" signal */
-static void
-config_frame_button_select_cb(GtkTreeSelection * selection,
-                              GtkDialog * dialog)
-{
-    config_dialog_select(selection, dialog);
-}
-
-/*
- * Callback for the close button.
- * Call ident_dialog_update to save any changes, and close the dialog if
- * OK.
- */
-static gboolean
-close_cb(GObject * dialog)
-{
-    return ident_dialog_update(dialog);
-}
-
-/*
- * Create a new identity
- */
-static void
-new_ident_cb(GtkTreeView * tree, GObject * dialog)
-{
-    LibBalsaIdentity *ident;
-    GList **identities;
-    GtkWidget *name_entry;
-    void (*cb)(gpointer) = g_object_get_data(G_OBJECT(tree), "callback");
-    gpointer data        = g_object_get_data(G_OBJECT(tree), "cb-data");
-
-    /* Save any changes to current identity; if it's not valid, just
-     * return. */
-    if (!ident_dialog_update(dialog))
-       return;
-
-    ident = LIBBALSA_IDENTITY(libbalsa_identity_new());
-    identities = g_object_get_data(G_OBJECT(tree), "identities");
-    *identities = g_list_append(*identities, ident);
-    identity_list_update(tree);
-    /* select just added identity */
-    select_identity(tree, ident);
-
-    name_entry = g_object_get_data(dialog, "identity-name");
-    gtk_widget_grab_focus(name_entry);
-    cb(data);
-}
-
-
-/*
- * Helper: append a notebook page containing a table
- */
-static GtkWidget*
-append_ident_notebook_page(GtkNotebook * notebook,
-                          const gchar * tab_label,
-                           const gchar * footnote)
-{
-    GtkWidget *vbox;
-    GtkWidget *grid;
-
-    vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
-    grid = libbalsa_create_grid();
-    g_object_set(G_OBJECT(grid), "margin", padding, NULL);
-    gtk_box_pack_start(GTK_BOX(vbox), grid);
-    if (footnote) {
-       GtkWidget *label;
-
-       label = gtk_label_new(footnote);
-       gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
-        gtk_box_pack_start(GTK_BOX(vbox), label);
-    }
-    gtk_notebook_append_page(notebook, vbox, gtk_label_new(tab_label));
-
-    return grid;
-}
-
-
-/*
- * Put the required GtkEntries, Labels, and Checkbuttons in the dialog
- * for creating/editing identities.
- */
-static const struct {
-    const gchar *mnemonic;
-    const gchar *path_key;
-    const gchar *box_key;
-    const gchar *basename;
-    const gchar *info;
-} path_info[] = {
-        /* Translators: please do not translate Face. */
-    {N_("_Face Path"),
-     "identity-facepath",
-     "identity-facebox",
-     ".face",
-     "Face"},
-        /* Translators: please do not translate Face. */
-    {N_("_X-Face Path"),
-     "identity-xfacepath",
-     "identity-xfacebox",
-     ".xface",
-     "X-Face"}
-};
-
-#define LIBBALSA_IDENTITY_CHECK "libbalsa-identity-check"
-static void md_sig_path_changed_cb(GtkToggleButton * sig_button,
-                                   GObject * dialog);
-
-static GtkWidget*
-setup_ident_frame(GtkDialog * dialog, gboolean createp, gpointer tree,
-                  GSList * smtp_servers)
+libbalsa_identity_set_x_face_path(LibBalsaIdentity *ident, const gchar *path)
 {
-    GtkNotebook *notebook = GTK_NOTEBOOK(gtk_notebook_new());
-    GtkWidget *grid;
-    gint row;
-    GObject *name;
-    gpointer path;
-    gchar *footnote;
-
-    /* create the "General" tab */
-    grid = append_ident_notebook_page(notebook, _("General"), NULL);
-    row = 0;
-    ident_dialog_add_entry(grid, row++, dialog, _("_Identity name:"),
-                          "identity-name");
-    ident_dialog_add_entry(grid, row++, dialog, _("_Full name:"),
-                           "identity-fullname");
-    ident_dialog_add_entry(grid, row++, dialog, _("_Mailing address:"),
-                           "identity-address");
-    ident_dialog_add_entry(grid, row++, dialog, _("Reply _to:"),
-                           "identity-replyto");
-    ident_dialog_add_entry(grid, row++, dialog, _("_Domain:"),
-                           "identity-domain");
-
-    /* create the "Messages" tab */
-    grid = append_ident_notebook_page(notebook, _("Messages"), NULL);
-    row = 0;
-    ident_dialog_add_entry(grid, row++, dialog, _("_BCC:"),
-                           "identity-bcc");
-    ident_dialog_add_entry(grid, row++, dialog, _("Reply _string:"),
-                           "identity-replystring");
-    ident_dialog_add_entry(grid, row++, dialog, _("F_orward string:"),
-                           "identity-forwardstring");
-    ident_dialog_add_checkbutton(grid, row++, dialog,
-                                 _("send messages in both plain text and _HTML format"),
-                                 "identity-sendmpalternative", TRUE);
-    ident_dialog_add_checkbutton(grid, row++, dialog,
-                                 _("request positive (successful)"
-                                   " _Delivery Status Notification by default"),
-                                 "identity-requestdsn", TRUE);
-    ident_dialog_add_checkbutton(grid, row++, dialog,
-                                 _("request _Message Disposition Notification by default"),
-                                 "identity-requestmdn", TRUE);
-    ident_dialog_add_file_chooser_button(grid, row++, dialog,
-                                         LBI_PATH_TYPE_FACE);
-    ident_dialog_add_file_chooser_button(grid, row++, dialog,
-                                         LBI_PATH_TYPE_XFACE);
-    ident_dialog_add_boxes(grid, row++, dialog,
-                           path_info[LBI_PATH_TYPE_FACE].box_key,
-                           path_info[LBI_PATH_TYPE_XFACE].box_key);
-    ident_dialog_add_smtp_menu(grid, row++, dialog, _("SMT_P server:"),
-                               "identity-smtp-server", smtp_servers);
-
-    /* create the "Signature" tab */
-    grid = append_ident_notebook_page(notebook, _("Signature"), NULL);
-    row = 0;
-    ident_dialog_add_check_and_entry(grid, row++, dialog,
-                                     _("Signature _path"),
-                                     "identity-sigpath");
-    ident_dialog_add_checkbutton(grid, row++, dialog,
-                                _("_Execute signature"),
-                                "identity-sigexecutable", FALSE);
-    ident_dialog_add_checkbutton(grid, row++, dialog,
-                                 _("Incl_ude signature"),
-                                 "identity-sigappend", FALSE);
-    ident_dialog_add_checkbutton(grid, row++, dialog,
-                                 _("Include signature when for_warding"),
-                                 "identity-whenforward", FALSE);
-    ident_dialog_add_checkbutton(grid, row++, dialog,
-                                 _("Include signature when rep_lying"),
-                                 "identity-whenreply", FALSE);
-    ident_dialog_add_checkbutton(grid, row++, dialog,
-                                 _("_Add signature separator"),
-                                 "identity-sigseparator", FALSE);
-    ident_dialog_add_checkbutton(grid, row++, dialog,
-                                 _("Prepend si_gnature"),
-                                 "identity-sigprepend", FALSE);
-
-#ifdef HAVE_GPGME
-    footnote = NULL;
-#else
-    footnote = _("Signing and encrypting messages are possible "
-                 "only if Balsa is built with cryptographic support.");
-#endif
-    /* create the "Security" tab */
-    grid =
-        append_ident_notebook_page(notebook, _("Security"), footnote);
-    row = 0;
-    ident_dialog_add_checkbutton(grid, row++, dialog,
-                                 _("sign messages by default"),
-                                 "identity-gpgsign", TRUE);
-    ident_dialog_add_checkbutton(grid, row++, dialog,
-                                 _("encrypt messages by default"),
-                                 "identity-gpgencrypt", TRUE);
-    ident_dialog_add_gpg_menu(grid, row++, dialog,
-                                _("default protocol"),
-                                "identity-crypt-protocol");
-    ident_dialog_add_checkbutton(grid, row++, dialog,
-                                 _("always trust GnuPG keys to encrypt messages"),
-                                 "identity-trust-always", TRUE);
-    ident_dialog_add_checkbutton(grid, row++, dialog,
-                                 _("remind me if messages can be encrypted"),
-                                 "identity-warn-send-plain", TRUE);
-    ident_dialog_add_keysel_entry(grid, row++, dialog,
-                                         _("use secret key with this id for signing GnuPG messages\n"
-                                               "(leave empty for automatic selection)"),
-                                                                 "identity-keyid");
-    ident_dialog_add_keysel_entry(grid, row++, dialog,
-                                         _("use certificate with this id for signing S/MIME messages\n"
-                                               "(leave empty for automatic selection)"),
-                                                                 "identity-keyid-sm");
-#ifndef HAVE_GPGME
-    gtk_widget_set_sensitive(grid, FALSE);
-#endif
-
-    name = g_object_get_data(G_OBJECT(dialog), "identity-name");
-    g_signal_connect(name, "changed",
-                     G_CALLBACK(md_name_changed), tree);
+    g_return_if_fail(LIBBALSA_IS_IDENTITY(ident));
 
-    path = g_object_get_data(G_OBJECT(dialog), "identity-sigpath");
-    g_signal_connect(g_object_get_data(G_OBJECT(path),
-                                       LIBBALSA_IDENTITY_CHECK),
-                     "toggled",
-                     G_CALLBACK(md_sig_path_changed_cb), dialog);
-
-    gtk_notebook_set_current_page(notebook, 0);
-
-    return GTK_WIDGET(notebook);
+    g_free(ident->x_face);
+    ident->x_face = g_strdup(path);
 }
 
-/* Callback for the "changed" signal of the name entry; updates the name
- * in the tree. */
-static void
-md_name_changed(GtkEntry * name, GtkTreeView * tree)
-{
-    set_identity_name_in_tree(tree, get_selected_identity(tree),
-                             gtk_entry_get_text(name));
-}
 
-/*
- * Create and add a GtkCheckButton to the given dialog with caption
- * and add a pointer to it stored under the given key.  The check
- * button is initialized to the given value.
- */
-static void
-ident_dialog_add_checkbutton(GtkWidget * grid, gint row,
-                             GtkDialog * dialog, const gchar * check_label,
-                             const gchar * check_key, gboolean sensitive)
-{
-    GtkWidget *check;
-
-    check = libbalsa_create_grid_check(check_label, grid, row, FALSE);
-    g_object_set_data(G_OBJECT(dialog), check_key, check);
-    gtk_widget_set_sensitive(check, sensitive);
-}
-
-/*
- * Create and add a GtkCheckButton to the given dialog with caption
- * and add a pointer to it stored under the given key, which is
- * followed by a text entry.  The check button is initialized to the
- * given value.
- */
-static void
-ident_dialog_add_check_and_entry(GtkWidget * grid, gint row,
-                                 GtkDialog * dialog, const gchar * check_label,
-                                 const gchar * entry_key)
+void
+libbalsa_identity_set_request_mdn(LibBalsaIdentity *ident, gboolean request)
 {
-    GtkWidget *check, *entry;
-
-    check = gtk_check_button_new_with_mnemonic(check_label);
-    entry = gtk_entry_new();
-
-
-    gtk_grid_attach(GTK_GRID(grid), check, 0, row, 1, 1);
-    gtk_widget_set_hexpand(entry, TRUE);
-    gtk_grid_attach(GTK_GRID(grid), entry, 1, row, 1, 1);
-
-    g_object_set_data(G_OBJECT(dialog), entry_key, entry);
-    g_object_set_data(G_OBJECT(entry), LIBBALSA_IDENTITY_CHECK, check);
+    g_return_if_fail(LIBBALSA_IS_IDENTITY(ident));
+    ident->request_mdn = request;
 }
 
 
-/*
- * Add a GtkEntry to the given dialog with a label next to it
- * explaining the contents.  A reference to the entry is stored as
- * object data attached to the dialog with the given key.  The entry
- * is initialized to the init_value given.
- */
-static void
-ident_dialog_add_entry(GtkWidget * grid, gint row, GtkDialog * dialog,
-                       const gchar * label_name, const gchar * entry_key)
+void
+libbalsa_identity_set_request_dsn(LibBalsaIdentity *ident, gboolean request)
 {
-    GtkWidget *label;
-    GtkWidget *entry;
-
-    label = libbalsa_create_grid_label(label_name, grid, row);
-
-    entry = libbalsa_create_grid_entry(grid, NULL, NULL, row, NULL, label);
-
-    g_object_set_data(G_OBJECT(dialog), entry_key, entry);
-    if (row == 0)
-        gtk_widget_grab_focus(entry);
+    g_return_if_fail(LIBBALSA_IS_IDENTITY(ident));
+    ident->request_dsn = request;
 }
 
 
-#ifdef HAVE_GPGME
-static void
-choose_key(GtkButton *button, gpointer user_data)
+void
+libbalsa_identity_set_always_trust(LibBalsaIdentity *ident, gboolean trust)
 {
-       const gchar *target;
-       gpgme_protocol_t protocol;
-       gchar *email;
-       gchar *keyid;
-       GError *error = NULL;
-
-       target = g_object_get_data(G_OBJECT(button), "target");
-       if (strcmp(target, "identity-keyid") == 0) {
-               protocol = GPGME_PROTOCOL_OpenPGP;
-       } else {
-               protocol = GPGME_PROTOCOL_CMS;
-       }
-
-       email = ident_dialog_get_text(G_OBJECT(user_data), "identity-address");
-       keyid = libbalsa_gpgme_get_seckey(protocol, email, GTK_WINDOW(user_data), &error);
-       if (keyid != NULL) {
-               display_frame_set_field(G_OBJECT(user_data), target, keyid);
-               g_free(keyid);
-       }
-       if (error != NULL) {
-        libbalsa_information(LIBBALSA_INFORMATION_WARNING,
-                             _("Error selecting key: %s"), error->message);
-        g_clear_error(&error);
-       }
+    g_return_if_fail(LIBBALSA_IS_IDENTITY(ident));
+    ident->always_trust = trust;
 }
-#endif
 
 
-/*
- * Add a GtkEntry to the given dialog with a label next to it
- * explaining the contents.  A reference to the entry is stored as
- * object data attached to the dialog with the given key.  A button
- * is added behind the entry to choose a key.
- */
-static void
-ident_dialog_add_keysel_entry(GtkWidget   *grid,
-                                                         gint         row,
-                                                         GtkDialog   *dialog,
-                                         const gchar *label_name,
-                                                         const gchar *entry_key)
+void
+libbalsa_identity_set_warn_send_plain(LibBalsaIdentity *ident, gboolean warn)
 {
-       GtkWidget *button;
-
-       ident_dialog_add_entry(grid, row, dialog, label_name, entry_key);
-       button = gtk_button_new_with_label(_("Choose…"));
-#ifdef HAVE_GPGME
-       g_object_set_data_full(G_OBJECT(button), "target", g_strdup(entry_key), (GDestroyNotify) g_free);
-       g_signal_connect(button, "clicked", G_CALLBACK(choose_key), dialog);
-#endif
-       gtk_grid_attach(GTK_GRID(grid), button, 2, row, 1, 1);
+    g_return_if_fail(LIBBALSA_IS_IDENTITY(ident));
+    ident->warn_send_plain = warn;
 }
 
 
-/*
- * Add a GtkFileChooserButton to the given dialog with a label next to it
- * explaining the contents.  A reference to the button is stored as
- * object data attached to the dialog with the given key.  The entry
- * is initialized to the init_value given.
- */
-
-/* Callbacks and helpers. */
-static void
-file_chooser_check_cb(GtkToggleButton * button, GtkWidget * chooser)
-{
-    gtk_widget_set_sensitive(chooser,
-                             gtk_toggle_button_get_active(button));
-    /* Force validation of current path, if any. */
-    g_signal_emit_by_name(chooser, "selection-changed");
-}
-
-static void
-md_face_path_changed(const gchar * filename, gboolean active,
-                     LibBalsaIdentityPathType type, gpointer data)
+void
+libbalsa_identity_set_force_gpg_key_id(LibBalsaIdentity *ident, const gchar *key_id)
 {
-    gchar *content;
-    gsize size;
-    GError *err = NULL;
-    GtkWidget *image;
-    GtkWidget *face_box;
-
-    face_box = g_object_get_data(G_OBJECT(data), path_info[type].box_key);
-    if (!active) {
-        gtk_widget_hide(face_box);
-        return;
-    }
-
-    content = libbalsa_get_header_from_path(path_info[type].info,
-                                            filename, &size, &err);
-
-    if (err) {
-        libbalsa_information(LIBBALSA_INFORMATION_WARNING,
-                             _("Error reading file %s: %s"), filename,
-                             err->message);
-        g_error_free(err);
-        gtk_widget_hide(face_box);
-        return;
-    }
-
-    if (size > 998) {
-        libbalsa_information(LIBBALSA_INFORMATION_WARNING,
-                /* Translators: please do not translate Face. */
-                             _("Face header file %s is too long "
-                               "(%lu bytes)."), filename, (unsigned long)size);
-        g_free(content);
-        gtk_widget_hide(face_box);
-        return;
-    }
+    g_return_if_fail(LIBBALSA_IS_IDENTITY(ident));
 
-    if (libbalsa_text_attr_string(content)) {
-        libbalsa_information(LIBBALSA_INFORMATION_WARNING,
-                /* Translators: please do not translate Face. */
-                             _("Face header file %s contains "
-                               "binary data."), filename);
-        g_free(content);
-        return;
-    }
-
-    if (type == LBI_PATH_TYPE_FACE)
-        image = libbalsa_get_image_from_face_header(content, &err);
-#if HAVE_COMPFACE
-    else if (type == LBI_PATH_TYPE_XFACE)
-        image = libbalsa_get_image_from_x_face_header(content, &err);
-#endif                          /* HAVE_COMPFACE */
-    else {
-        gtk_widget_hide(face_box);
-        g_free(content);
-        return;
-    }
-    if (err) {
-        libbalsa_information(LIBBALSA_INFORMATION_WARNING,
-                /* Translators: please do not translate Face. */
-                             _("Error loading Face: %s"), err->message);
-        g_error_free(err);
-        g_free(content);
-        return;
-    }
-
-    gtk_container_foreach(GTK_CONTAINER(face_box),
-                          (GtkCallback) gtk_widget_destroy, NULL);
-    gtk_box_pack_start(GTK_BOX(face_box), image);
-
-    g_free(content);
-}
-
-/* Callback for the "selection-changed" signal of the signature path
- * file chooser; sets sensitivity of the signature-related buttons. */
-
-static void
-md_sig_path_changed(gboolean active, GObject * dialog)
-{
-    guint i;
-    static gchar *button_key[] = {
-        "identity-sigexecutable",
-        "identity-sigappend",
-        "identity-whenforward",
-        "identity-whenreply",
-        "identity-sigseparator",
-        "identity-sigprepend",
-        "identity-sigpath",
-    };
-
-    for (i = 0; i < G_N_ELEMENTS(button_key); i++) {
-        GtkWidget *button = g_object_get_data(dialog, button_key[i]);
-        gtk_widget_set_sensitive(button, active);
-    }
+    g_free(ident->force_gpg_key_id);
+    ident->force_gpg_key_id = g_strdup(key_id);
 }
 
-static void
-md_sig_path_changed_cb(GtkToggleButton *sig_button, GObject *dialog)
-{
-    md_sig_path_changed(gtk_toggle_button_get_active(sig_button), dialog);
-}
 
-#define LIBBALSA_IDENTITY_INFO "libbalsa-identity-info"
-static void
-file_chooser_cb(GtkWidget * chooser, gpointer data)
+void
+libbalsa_identity_set_force_smime_key_id(LibBalsaIdentity *ident, const gchar *key_id)
 {
-    gchar *filename;
-    LibBalsaIdentityPathType type;
-    GtkToggleButton *check;
-    gboolean active;
-
-    filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(chooser));
-    if (!filename || !*filename) {
-        g_free(filename);
-        return;
-    }
-
-    type = GPOINTER_TO_UINT(g_object_get_data
-                            (G_OBJECT(chooser), LIBBALSA_IDENTITY_INFO));
-    check = g_object_get_data(G_OBJECT(chooser), LIBBALSA_IDENTITY_CHECK);
-    active = gtk_toggle_button_get_active(check);
+    g_return_if_fail(LIBBALSA_IS_IDENTITY(ident));
 
-#if 0
-    if (type == LBI_PATH_TYPE_SIG)
-        md_sig_path_changed(filename, active, data);
-    else
-#endif
-        md_face_path_changed(filename, active, type, data);
-    g_free(filename);
-}
-
-static void
-ident_dialog_add_file_chooser_button(GtkWidget * grid, gint row,
-                                     GtkDialog * dialog,
-                                     LibBalsaIdentityPathType type)
-{
-    GtkWidget *check;
-    gchar *filename;
-    gchar *title;
-    GtkWidget *button;
-
-    check =
-        gtk_check_button_new_with_mnemonic(_(path_info[type].mnemonic));
-    gtk_grid_attach(GTK_GRID(grid), check, 0, row, 1, 1);
-
-    filename =
-        g_build_filename(g_get_home_dir(), path_info[type].basename, NULL);
-    title = g_strdup_printf("Choose %s file", _(path_info[type].info));
-    button = gtk_file_chooser_button_new(title,
-                                         GTK_FILE_CHOOSER_ACTION_OPEN);
-    g_free(title);
-    gtk_file_chooser_set_show_hidden(GTK_FILE_CHOOSER(button), TRUE);
-    gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(button), filename);
-    g_free(filename);
-
-    gtk_widget_set_hexpand(button, TRUE);
-    gtk_widget_set_vexpand(button, FALSE);
-    gtk_grid_attach(GTK_GRID(grid), button, 1, row, 1, 1);
-
-    g_object_set_data(G_OBJECT(dialog), path_info[type].path_key, button);
-    g_object_set_data(G_OBJECT(button), LIBBALSA_IDENTITY_CHECK, check);
-    g_object_set_data(G_OBJECT(button), LIBBALSA_IDENTITY_INFO,
-                      GUINT_TO_POINTER(type));
-    g_signal_connect(check, "toggled",
-                     G_CALLBACK(file_chooser_check_cb), button);
-    g_signal_connect(button, "selection-changed",
-                     G_CALLBACK(file_chooser_cb), dialog);
+    g_free(ident->force_smime_key_id);
+    ident->force_smime_key_id = g_strdup(key_id);
 }
 
-static void
-ident_dialog_add_boxes(GtkWidget * grid, gint row, GtkDialog * dialog,
-                       const gchar * key1, const gchar *key2)
-{
-    GtkWidget *hbox, *vbox;
-
-    hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 12);
-    gtk_grid_attach(GTK_GRID(grid), hbox, 1, row, 1, 1);
-    vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
-    g_object_set_data(G_OBJECT(dialog), key1, vbox);
-    gtk_box_pack_start(GTK_BOX(hbox), vbox);
-    vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
-    g_object_set_data(G_OBJECT(dialog), key2, vbox);
-    gtk_box_pack_start(GTK_BOX(hbox), vbox);
-}
 
-/* set_identity_name_in_tree:
- * update the tree to reflect the (possibly) new name of the identity
- */
-static void
-set_identity_name_in_tree(GtkTreeView * tree, LibBalsaIdentity * identity,
-                  const gchar * name)
+/** Returns a signature for given identity, adding a signature prefix
+    if needed. parent can be NULL. */
+gchar*
+libbalsa_identity_get_signature(LibBalsaIdentity * ident, GError ** error)
 {
-    GtkTreeModel *model = gtk_tree_view_get_model(tree);
-    GtkTreeIter iter;
-    gboolean valid;
-
-    for (valid =
-         gtk_tree_model_get_iter_first(model, &iter);
-         valid;
-         valid = gtk_tree_model_iter_next(model, &iter)) {
-        LibBalsaIdentity *tmp;
-
-        gtk_tree_model_get(model, &iter, IDENT_COLUMN, &tmp, -1);
-        if (identity == tmp) {
-            gtk_list_store_set(GTK_LIST_STORE(model), &iter,
-                               NAME_COLUMN, name, -1);
-            break;
-        }
-    }
-}
-
-/*
- * Update the identity object associated with the edit/new dialog,
- * validating along the way.  Correct validation results in a true
- * return value, otherwise it returns false.
- */
+    gchar *ret = NULL, *path;
+    gchar *retval;
 
-static gboolean
-ident_dialog_update(GObject * dlg)
-{
-    LibBalsaIdentity* id;
-    LibBalsaIdentity* exist_ident;
-    InternetAddress *ia;
-    GtkWidget *tree;
-    GList **identities, *list;
-    gchar* text;
-
-    id = g_object_get_data(dlg, "identity");
-    if (!id)
-        return TRUE;
-    tree = g_object_get_data(dlg, "tree");
-    identities = g_object_get_data(G_OBJECT(tree), "identities");
-
-    text = ident_dialog_get_text(dlg, "identity-name");
-    g_return_val_if_fail(text != NULL, FALSE);
-
-    if (text[0] == '\0') {
-        libbalsa_information(LIBBALSA_INFORMATION_ERROR,
-                             _("Error: The identity does not have a name"));
-        return FALSE;
-    }
+    if (ident->signature_path == NULL || *ident->signature_path == '\0')
+       return NULL;
 
-    for (list = *identities; list != NULL; list = list->next) {
-        exist_ident = list->data;
+    path = libbalsa_expand_path(ident->signature_path);
+    if (ident->sig_executable) {
+        gchar *argv[] = {"/bin/sh", "-c", path, NULL};
+        gchar *standard_error = NULL;
 
-        if (g_ascii_strcasecmp(exist_ident->identity_name, text) == 0
-            && id != exist_ident) {
-            libbalsa_information(LIBBALSA_INFORMATION_ERROR,
-                                 _("Error: An identity with that"
-                                   " name already exists"));
-            return FALSE;
+        if (!g_spawn_sync(NULL, argv, NULL, G_SPAWN_DEFAULT, NULL, NULL,
+                          &ret, &standard_error, NULL, error)) {
+            g_prefix_error(error, _("Error executing signature generator “%s”: "),
+                           ident->signature_path);
+        } else if (standard_error != NULL) {
+            g_set_error(error, LIBBALSA_ERROR_QUARK, -1,
+                        _("Error executing signature generator “%s”: %s"),
+                        ident->signature_path, standard_error);
         }
+        g_free(standard_error);
+    } else {
+       if (!g_file_get_contents(path, &ret, NULL, error)) {
+            g_prefix_error(error, _("Cannot read signature file “%s”: "),
+                           ident->signature_path);
+       }
     }
+    g_free(path);
 
-    g_free(id->identity_name); id->identity_name = text;
-    set_identity_name_in_tree(GTK_TREE_VIEW(tree), id, text);
-
-    text = ident_dialog_get_text(dlg, "identity-address");
-    g_return_val_if_fail(text != NULL, FALSE);
-    ia = internet_address_mailbox_new(NULL, text);
-    g_free(text);
-
-    text = ident_dialog_get_text(dlg, "identity-fullname");
-    internet_address_set_name(ia, text);
-    libbalsa_identity_set_address(id, ia);
-    g_free(text);
-    g_object_unref(ia);
-
-    g_free(id->replyto);
-    id->replyto         = ident_dialog_get_text(dlg, "identity-replyto");
-    g_free(id->domain);
-    id->domain          = ident_dialog_get_text(dlg, "identity-domain");
-    g_free(id->bcc);
-    id->bcc             = ident_dialog_get_text(dlg, "identity-bcc");
-    g_free(id->reply_string);
-    id->reply_string    = ident_dialog_get_text(dlg, "identity-replystring");
-    g_free(id->forward_string);
-    id->forward_string  = ident_dialog_get_text(dlg, "identity-forwardstring");
-    id->send_mp_alternative = ident_dialog_get_bool(dlg, "identity-sendmpalternative");
-    g_set_object(&id->smtp_server, ident_dialog_get_value(dlg, "identity-smtp-server"));
-
-    g_free(id->signature_path);
-    id->signature_path  = ident_dialog_get_text(dlg, "identity-sigpath");
-
-    id->sig_executable  = ident_dialog_get_bool(dlg, "identity-sigexecutable");
-    id->sig_sending     = ident_dialog_get_bool(dlg, "identity-sigappend");
-    id->sig_whenforward = ident_dialog_get_bool(dlg, "identity-whenforward");
-    id->sig_whenreply   = ident_dialog_get_bool(dlg, "identity-whenreply");
-    id->sig_separator   = ident_dialog_get_bool(dlg, "identity-sigseparator");
-    id->sig_prepend     = ident_dialog_get_bool(dlg, "identity-sigprepend");
-
-    g_free(id->face);
-    id->face            = ident_dialog_get_path(dlg, "identity-facepath");
-    g_free(id->x_face);
-    id->x_face          = ident_dialog_get_path(dlg, "identity-xfacepath");
-    id->request_mdn     = ident_dialog_get_bool(dlg, "identity-requestmdn");
-    id->request_dsn     = ident_dialog_get_bool(dlg, "identity-requestdsn");
-
-    id->gpg_sign        = ident_dialog_get_bool(dlg, "identity-gpgsign");
-    id->gpg_encrypt     = ident_dialog_get_bool(dlg, "identity-gpgencrypt");
-    id->always_trust    = ident_dialog_get_bool(dlg, "identity-trust-always");
-    id->warn_send_plain = ident_dialog_get_bool(dlg, "identity-warn-send-plain");
-    id->crypt_protocol  = GPOINTER_TO_INT(ident_dialog_get_value
-                                          (dlg, "identity-crypt-protocol"));
-    g_free(id->force_gpg_key_id);
-    id->force_gpg_key_id = g_strstrip(ident_dialog_get_text(dlg, "identity-keyid"));
-    g_free(id->force_smime_key_id);
-    id->force_smime_key_id = g_strstrip(ident_dialog_get_text(dlg, "identity-keyid-sm"));
-
-    return TRUE;
-}
-
-
-/*
- * Get the text from an entry in the editing/creation dialog.  The
- * given key accesses the entry using object data.
- */
-static gchar*
-ident_dialog_get_text(GObject * dialog, const gchar * key)
-{
-    GtkEditable *entry;
-    GtkToggleButton *check;
-
-    entry = g_object_get_data(dialog, key);
-    check = g_object_get_data(G_OBJECT(entry), LIBBALSA_IDENTITY_CHECK);
-    if (check && !gtk_toggle_button_get_active(check))
-        return NULL;
-    return gtk_editable_get_chars(entry, 0, -1);
-}
-
-
-/*
- * Get the value of a check button from the editing dialog.  The key
- * is used to retreive the reference to the check button using object
- * data
- */
-static gboolean
-ident_dialog_get_bool(GObject* dialog, const gchar* key)
-{
-    GtkToggleButton *button;
-
-    button = g_object_get_data(dialog, key);
-    return gtk_toggle_button_get_active(button);
-}
-
-
-/*
- * Get the path from a file chooser in the editing/creation dialog.  The
- * given key accesses the file chooser using object data.
- */
-static gchar *
-ident_dialog_get_path(GObject * dialog, const gchar * key)
-{
-    GtkWidget *chooser;
-
-    chooser = g_object_get_data(dialog, key);
-    if (!gtk_widget_get_sensitive(chooser))
+    if ((error != NULL && *error != NULL) || ret == NULL)
         return NULL;
 
-    return gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(chooser));
-}
-
-
-/*
- * Set the default identity to the currently selected.
- */
-static void
-set_default_ident_cb(GtkTreeView * tree, GtkTreePath * path,
-                     GtkTreeViewColumn * column, gpointer data)
-{
-    LibBalsaIdentity *ident, **default_id;
-
-    default_id = g_object_get_data(G_OBJECT(tree), "default-id");
-    ident = get_selected_identity(tree);
-    g_return_if_fail(ident != NULL);
-    *default_id = ident;
-
-    identity_list_update(tree);
-}
-
-
-/*
- * Confirm the deletion of an identity, do the actual deletion here,
- * and close the dialog.
- */
-static void
-identity_delete_selected(GtkTreeView * tree, GtkWidget * dialog)
-{
-    GtkTreeSelection *selection = gtk_tree_view_get_selection(tree);
-    GtkTreeModel *model;
-    GtkTreeIter iter;
-    GtkTreePath *path;
-    LibBalsaIdentity *ident;
-    GList **identities;
-    void (*cb)(gpointer) = g_object_get_data(G_OBJECT(tree), "callback");
-    gpointer data        = g_object_get_data(G_OBJECT(tree), "cb-data");
-
-    /* Save the path to the current row. */
-    if (!gtk_tree_selection_get_selected(selection, &model, &iter))
-        return;
-    path = gtk_tree_model_get_path(model, &iter);
-
-    ident = get_selected_identity(tree);
-    identities = g_object_get_data(G_OBJECT(tree), "identities");
-    *identities = g_list_remove(*identities, ident);
-    g_object_set_data(G_OBJECT(dialog), "identity", NULL);
-    identity_list_update(tree);
-    g_object_unref(ident);
-
-    /* Select the row at the saved path, or the previous one. */
-    if (gtk_tree_model_get_iter(model, &iter, path)
-        || gtk_tree_path_prev(path)) {
-        gtk_tree_view_set_cursor(tree, path, NULL, FALSE);
-        gtk_tree_view_scroll_to_cell(tree, path, NULL,
-                                     FALSE, 0, 0);
+    if (!libbalsa_utf8_sanitize(&ret, FALSE, NULL)) {
+        g_set_error(error, LIBBALSA_ERROR_QUARK, -1,
+                    _("Signature in “%s” is not a UTF-8 text."),
+                    ident->signature_path);
     }
-    gtk_tree_path_free(path);
-    gtk_widget_grab_focus(GTK_WIDGET(tree));
-    cb(data);
-}
 
-/*
- * Delete the currently selected identity after confirming.
- */
-struct _IdentityDeleteInfo {
-    GtkTreeView *tree;
-    GtkWidget *dialog;
-};
-static void
-delete_ident_cb(GtkTreeView * tree, GtkWidget * dialog)
-{
-    LibBalsaIdentity* ident, **default_id;
-    GtkWidget* confirm;
-    IdentityDeleteInfo *di;
-
-    ident = get_selected_identity(tree);
-    default_id = g_object_get_data(G_OBJECT(tree), "default-id");
-    g_return_if_fail(ident != *default_id);
-    confirm = gtk_message_dialog_new(GTK_WINDOW(dialog),
-                                     GTK_DIALOG_DESTROY_WITH_PARENT,
-                                     GTK_MESSAGE_QUESTION,
-                                     GTK_BUTTONS_OK_CANCEL,
-                                     _("Do you really want to delete"
-                                       " the selected identity?"));
-#if HAVE_MACOSX_DESKTOP
-    libbalsa_macosx_menu_for_parent(confirm, GTK_WINDOW(dialog));
-#endif
-    di = g_new(IdentityDeleteInfo, 1);
-    di->tree = tree;
-    di->dialog = dialog;
-    g_signal_connect(confirm, "response",
-                     G_CALLBACK(delete_ident_response), di);
-    g_object_weak_ref(G_OBJECT(confirm), (GWeakNotify) g_free, di);
-    gtk_widget_set_sensitive(dialog, FALSE);
-    gtk_widget_show(confirm);
-}
-
-static void
-delete_ident_response(GtkWidget * confirm, gint response,
-                      IdentityDeleteInfo * di)
-{
-    if(response == GTK_RESPONSE_OK)
-        identity_delete_selected(di->tree, di->dialog);
-    gtk_widget_set_sensitive(di->dialog, TRUE);
-    gtk_widget_destroy(confirm);
-}
-
-/*
- * Show the help file.
- */
-static void
-help_ident_cb(GtkWidget * widget)
-{
-    GError *err = NULL;
-
-    gtk_show_uri_on_window(GTK_WINDOW(widget), "help:balsa/identities",
-                           gtk_get_current_event_time(), &err);
+    /* Prepend the separator if needed... */
 
-    if (err) {
-        g_print(_("Error displaying help for identities: %s\n"),
-                err->message);
-        g_error_free(err);
+    if (ident->sig_separator
+        && !(g_str_has_prefix(ret, "--\n") || g_str_has_prefix(ret, "-- \n"))) {
+        retval = g_strconcat("\n-- \n", ret, NULL);
+    } else {
+        retval = g_strconcat("\n", ret, NULL);
     }
-}
+    g_free(ret);
 
-/* libbalsa_identity_config_dialog displays an identity management
-   dialog. The dialog has a specified parent, existing list of
-   identites, the default one. Additionally, a callback is passed that
-   will be executed when the identity list is modified: new entries
-   are added or other entries are removed. */
-static void
-lbi_free_smtp_server_list(GSList ** smtp_server_list)
-{
-    g_slist_free_full(*smtp_server_list, g_object_unref);
-    g_free(smtp_server_list);
+    return retval;
 }
 
 void
-libbalsa_identity_config_dialog(GtkWindow *parent, GList **identities,
-                               LibBalsaIdentity **default_id, GSList * smtp_servers,
-                               void (*changed_cb)(gpointer))
-{
-    static GtkWidget *dialog = NULL;
-    GtkWidget* frame;
-    GtkWidget* display_frame;
-    GtkWidget* hbox;
-    GtkTreeView* tree;
-    GtkTreeSelection *select;
-    GSList **smtp_server_list;
-
-    /* Show only one dialog at a time. */
-    if (dialog) {
-        gtk_window_present(GTK_WINDOW(dialog));
-        return;
-    }
-
-    dialog =
-        gtk_dialog_new_with_buttons(_("Manage Identities"),
-                                    parent, /* must NOT be modal */
-                                    GTK_DIALOG_DESTROY_WITH_PARENT |
-                                    libbalsa_dialog_flags(),
-                                    _("_Help"),             IDENTITY_RESPONSE_HELP,
-                                    /* Translators: button "New" identity */
-                                    C_("identity", "_New"), IDENTITY_RESPONSE_NEW,
-                                    _("_Remove"),           IDENTITY_RESPONSE_REMOVE,
-                                    _("_Close"),            IDENTITY_RESPONSE_CLOSE,
-                                    NULL);
-#if HAVE_MACOSX_DESKTOP
-    libbalsa_macosx_menu_for_parent(dialog, parent);
-#endif
-
-    frame = libbalsa_identity_config_frame(identities, default_id, dialog,
-                                           changed_cb, parent);
-    tree = GTK_TREE_VIEW(gtk_bin_get_child(GTK_BIN(frame)));
-
-    g_signal_connect(dialog, "response",
-                     G_CALLBACK(md_response_cb), tree);
-    g_object_set_data(G_OBJECT(dialog), "tree", tree);
-    g_object_add_weak_pointer(G_OBJECT(dialog), (gpointer) & dialog);
-    gtk_dialog_set_default_response(GTK_DIALOG(dialog),
-                                    IDENTITY_RESPONSE_CLOSE);
-
-    hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, padding);
-    gtk_widget_set_vexpand(hbox, TRUE);
-    gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), hbox);
-    gtk_box_pack_start(GTK_BOX(hbox), frame);
-
-    smtp_server_list = g_new(GSList *, 1);
-    *smtp_server_list = g_slist_copy(smtp_servers);
-    g_slist_foreach(smtp_servers, (GFunc) g_object_ref, NULL);
-    display_frame = setup_ident_frame(GTK_DIALOG(dialog),
-                                      FALSE, tree, smtp_servers);
-    g_object_weak_ref(G_OBJECT(display_frame),
-                     (GWeakNotify) lbi_free_smtp_server_list,
-                     smtp_server_list);
-
-    gtk_widget_set_hexpand(display_frame, TRUE);
-    gtk_box_pack_start(GTK_BOX(hbox), display_frame);
-
-    select = gtk_tree_view_get_selection(tree);
-    g_signal_connect(select, "changed",
-                     G_CALLBACK(config_frame_button_select_cb), dialog);
-    config_dialog_select(select, GTK_DIALOG(dialog));
-
-    gtk_widget_show(dialog);
-    gtk_widget_grab_focus(GTK_WIDGET(tree));
-}
-
-/* Callback for the "response" signal of the dialog. */
-static void
-md_response_cb(GtkWidget * dialog, gint response, GtkTreeView * tree)
-{
-    switch (response) {
-    case IDENTITY_RESPONSE_CLOSE:
-        if (close_cb(G_OBJECT(dialog)))
-            break;
-        return;
-    case IDENTITY_RESPONSE_NEW:
-        new_ident_cb(tree, G_OBJECT(dialog));
-        return;
-    case IDENTITY_RESPONSE_REMOVE:
-        delete_ident_cb(tree, dialog);
-        return;
-    case IDENTITY_RESPONSE_HELP:
-        help_ident_cb(dialog);
-        return;
-    default:
-        break;
-    }
-
-    gtk_widget_destroy(dialog);
-}
-
-/* config_dialog_select
- *
- * called when the tree's selection changes
- * manage the button sensitivity, and update the display frame
- */
-static void
-config_dialog_select(GtkTreeSelection * selection, GtkDialog * dialog)
-{
-    LibBalsaIdentity *ident, **default_id;
-    GtkTreeView *tree = gtk_tree_selection_get_tree_view(selection);
-
-    ident = get_selected_identity(tree);
-    default_id = g_object_get_data(G_OBJECT(tree), "default-id");
-    gtk_dialog_set_response_sensitive(dialog, IDENTITY_RESPONSE_REMOVE,
-                                      ident && ident != *default_id);
-    display_frame_update(G_OBJECT(dialog), ident);
-    g_object_set_data(G_OBJECT(dialog), "identity", ident);
-}
-
-static void
-display_frame_update(GObject * dialog, LibBalsaIdentity* ident)
-{
-    GtkWidget *face_box;
-
-    if (!ident)
-        return;
-
-    ident_dialog_update(dialog);
-    display_frame_set_field(dialog, "identity-name", ident->identity_name);
-    display_frame_set_field(dialog, "identity-fullname", ident->ia ? ident->ia->name : NULL);
-    if (ident->ia && INTERNET_ADDRESS_IS_MAILBOX (ident->ia))
-        display_frame_set_field(dialog, "identity-address",
-                                INTERNET_ADDRESS_MAILBOX(ident->ia)->addr);
-    else
-        display_frame_set_field(dialog, "identity-address", NULL);
-
-    display_frame_set_field(dialog, "identity-replyto", ident->replyto);
-    display_frame_set_field(dialog, "identity-domain", ident->domain);
-    display_frame_set_field(dialog, "identity-bcc", ident->bcc);
-    display_frame_set_field(dialog, "identity-replystring",
-                            ident->reply_string);
-    display_frame_set_field(dialog, "identity-forwardstring",
-                            ident->forward_string);
-    display_frame_set_boolean(dialog, "identity-sendmpalternative",
-                              ident->send_mp_alternative);
-    display_frame_set_server(dialog, "identity-smtp-server",
-                             ident->smtp_server);
-
-    display_frame_set_path(dialog, "identity-sigpath",
-                           ident->signature_path, FALSE);
-    display_frame_set_boolean(dialog, "identity-sigexecutable", ident->sig_executable);
-
-    display_frame_set_boolean(dialog, "identity-sigappend", ident->sig_sending);
-    display_frame_set_boolean(dialog, "identity-whenforward",
-                              ident->sig_whenforward);
-    display_frame_set_boolean(dialog, "identity-whenreply",
-                              ident->sig_whenreply);
-    display_frame_set_boolean(dialog, "identity-sigseparator",
-                              ident->sig_separator);
-    display_frame_set_boolean(dialog, "identity-sigprepend",
-                              ident->sig_prepend);
-
-    face_box = g_object_get_data(G_OBJECT(dialog),
-                                 path_info[LBI_PATH_TYPE_FACE].box_key);
-    gtk_widget_hide(face_box);
-    display_frame_set_path(dialog, path_info[LBI_PATH_TYPE_FACE].path_key,
-                           ident->face, TRUE);
-
-    face_box = g_object_get_data(G_OBJECT(dialog),
-                                 path_info[LBI_PATH_TYPE_XFACE].box_key);
-    gtk_widget_hide(face_box);
-    display_frame_set_path(dialog, path_info[LBI_PATH_TYPE_XFACE].path_key,
-                           ident->x_face, TRUE);
-    display_frame_set_boolean(dialog, "identity-requestmdn",
-                              ident->request_mdn);
-    display_frame_set_boolean(dialog, "identity-requestdsn",
-                              ident->request_dsn);
-
-    display_frame_set_boolean(dialog, "identity-gpgsign",
-                              ident->gpg_sign);
-    display_frame_set_boolean(dialog, "identity-gpgencrypt",
-                              ident->gpg_encrypt);
-    display_frame_set_boolean(dialog, "identity-trust-always",
-                              ident->always_trust);
-    display_frame_set_boolean(dialog, "identity-warn-send-plain",
-                              ident->warn_send_plain);
-    display_frame_set_gpg_mode(dialog, "identity-crypt-protocol",
-                          &ident->crypt_protocol);
-    display_frame_set_field(dialog, "identity-keyid", ident->force_gpg_key_id);
-    display_frame_set_field(dialog, "identity-keyid-sm", ident->force_smime_key_id);
-}
-
-
-static void
-display_frame_set_field(GObject * dialog,
-                        const gchar* key,
-                        const gchar* value)
-{
-    GtkEntry *entry = g_object_get_data(dialog, key);
-
-    gtk_entry_set_text(entry, value ? value : "");
-}
-
-static void
-display_frame_set_boolean(GObject * dialog,
-                          const gchar* key,
-                          gboolean value)
+libbalsa_identity_set_smtp_server(LibBalsaIdentity * ident,
+                                  LibBalsaSmtpServer * smtp_server)
 {
-    GtkToggleButton *check = g_object_get_data(dialog, key);
-
-    gtk_toggle_button_set_active(check, value);
-}
+    g_return_if_fail(LIBBALSA_IS_IDENTITY(ident));
 
-static void
-display_frame_set_path(GObject * dialog,
-                       const gchar* key,
-                       const gchar* value, gboolean use_chooser)
-{
-    gboolean set = (value && *value);
-    GtkWidget *chooser = g_object_get_data(dialog, key);
-    GtkToggleButton *check =
-        g_object_get_data(G_OBJECT(chooser), LIBBALSA_IDENTITY_CHECK);
-
-    if (set) {
-        if(use_chooser)
-            gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(chooser), value);
-        else
-            gtk_entry_set_text(GTK_ENTRY(chooser), value);
-    }
-    gtk_widget_set_sensitive(GTK_WIDGET(chooser), set);
-    gtk_toggle_button_set_active(check, set);
+    g_set_object(&ident->smtp_server, smtp_server);
 }
 
 
@@ -2077,7 +549,7 @@ libbalsa_identity_save(LibBalsaIdentity* ident, const gchar* group)
 void
 libbalsa_identity_set_gpg_sign(LibBalsaIdentity* ident, gboolean sign)
 {
-    g_return_if_fail(ident != NULL);
+    g_return_if_fail(LIBBALSA_IS_IDENTITY(ident));
     ident->gpg_sign = sign;
 }
 
@@ -2085,7 +557,7 @@ libbalsa_identity_set_gpg_sign(LibBalsaIdentity* ident, gboolean sign)
 void
 libbalsa_identity_set_gpg_encrypt(LibBalsaIdentity* ident, gboolean encrypt)
 {
-    g_return_if_fail(ident != NULL);
+    g_return_if_fail(LIBBALSA_IS_IDENTITY(ident));
     ident->gpg_encrypt = encrypt;
 }
 
@@ -2093,202 +565,6 @@ libbalsa_identity_set_gpg_encrypt(LibBalsaIdentity* ident, gboolean encrypt)
 void
 libbalsa_identity_set_crypt_protocol(LibBalsaIdentity* ident, gint protocol)
 {
-    g_return_if_fail(ident != NULL);
+    g_return_if_fail(LIBBALSA_IS_IDENTITY(ident));
     ident->crypt_protocol = protocol;
 }
-
-
-
-static void
-display_frame_set_gpg_mode(GObject * dialog, const gchar* key, gint * value)
-{
-    GtkComboBox *opt_menu = g_object_get_data(G_OBJECT(dialog), key);
-
-    switch (*value)
-        {
-        case LIBBALSA_PROTECT_OPENPGP:
-           gtk_combo_box_set_active(opt_menu, 1);
-            break;
-        case LIBBALSA_PROTECT_SMIMEV3:
-           gtk_combo_box_set_active(opt_menu, 2);
-            break;
-        case LIBBALSA_PROTECT_RFC3156:
-        default:
-           gtk_combo_box_set_active(opt_menu, 0);
-            *value = LIBBALSA_PROTECT_RFC3156;
-        }
-}
-
-/*
- * Add an option menu to the given dialog with a label next to it
- * explaining the contents.  A reference to the entry is stored as
- * object data attached to the dialog with the given key.
- */
-
-static void
-ident_dialog_add_gpg_menu(GtkWidget * grid, gint row, GtkDialog * dialog,
-                          const gchar * label_name, const gchar * menu_key)
-{
-    GtkWidget *label;
-    GtkWidget *opt_menu;
-    GPtrArray *values;
-
-    label = gtk_label_new_with_mnemonic(label_name);
-    gtk_widget_set_halign(label, GTK_ALIGN_START);
-    gtk_grid_attach(GTK_GRID(grid), label, 0, row, 1, 1);
-
-    opt_menu = gtk_combo_box_text_new();
-    values = g_ptr_array_sized_new(3);
-    g_object_set_data_full(G_OBJECT(opt_menu), "identity-value", values,
-                           (GDestroyNotify) ident_dialog_free_values);
-    gtk_grid_attach(GTK_GRID(grid), opt_menu, 1, row, 1, 1);
-    g_object_set_data(G_OBJECT(dialog), menu_key, opt_menu);
-
-    add_show_menu(_("GnuPG MIME mode"),
-                  GINT_TO_POINTER(LIBBALSA_PROTECT_RFC3156), opt_menu);
-    add_show_menu(_("GnuPG OpenPGP mode"),
-                  GINT_TO_POINTER(LIBBALSA_PROTECT_OPENPGP), opt_menu);
-    add_show_menu(_("GpgSM S/MIME mode"),
-                  GINT_TO_POINTER(LIBBALSA_PROTECT_SMIMEV3), opt_menu);
-}
-
-/* add_show_menu: helper function */
-static void
-add_show_menu(const char *label, gpointer data, GtkWidget * menu)
-{
-    GPtrArray *values =
-        g_object_get_data(G_OBJECT(menu), "identity-value");
-
-    gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(menu), label);
-    g_ptr_array_add(values, data);
-}
-
-/* ident_dialog_free_values: helper function */
-static void
-ident_dialog_free_values(GPtrArray * values)
-{
-    g_ptr_array_free(values, TRUE);
-}
-
-static void
-ident_dialog_add_smtp_menu(GtkWidget * grid, gint row, GtkDialog * dialog,
-                           const gchar * label_name,
-                           const gchar * menu_key, GSList * smtp_servers)
-{
-    GtkWidget *label;
-    GtkWidget *combo_box;
-    GSList *list;
-    GPtrArray *values;
-
-    label = gtk_label_new_with_mnemonic(label_name);
-    gtk_widget_set_halign(label, GTK_ALIGN_START);
-    gtk_grid_attach(GTK_GRID(grid), label, 0, row, 1, 1);
-
-    combo_box = gtk_combo_box_text_new();
-    gtk_label_set_mnemonic_widget(GTK_LABEL(label), combo_box);
-    values = g_ptr_array_sized_new(g_slist_length(smtp_servers));
-    g_object_set_data_full(G_OBJECT(combo_box), "identity-value", values,
-                           (GDestroyNotify) ident_dialog_free_values);
-    gtk_grid_attach(GTK_GRID(grid), combo_box, 1, row, 1, 1);
-    g_object_set_data(G_OBJECT(dialog), menu_key, combo_box);
-
-    for (list = smtp_servers; list; list = list->next) {
-        LibBalsaSmtpServer *smtp_server = LIBBALSA_SMTP_SERVER(list->data);
-        add_show_menu(libbalsa_smtp_server_get_name(smtp_server),
-                      smtp_server, combo_box);
-    }
-}
-
-static void
-display_frame_set_server(GObject * dialog, const gchar * key,
-                         LibBalsaSmtpServer * smtp_server)
-{
-    GtkComboBox *combo_box = g_object_get_data(G_OBJECT(dialog), key);
-    GPtrArray *values;
-    guint i;
-
-    values = g_object_get_data(G_OBJECT(combo_box), "identity-value");
-
-    for (i = 0; i < values->len; i++) {
-        if (g_ptr_array_index(values, i) == smtp_server)
-            gtk_combo_box_set_active(combo_box, i);
-    }
-}
-
-/*
- * Get the value of the active option menu item
- */
-static gpointer
-ident_dialog_get_value(GObject * dialog, const gchar * key)
-{
-    GtkWidget * menu;
-    gint value;
-    GPtrArray *values;
-
-    menu = g_object_get_data(dialog, key);
-    value = gtk_combo_box_get_active(GTK_COMBO_BOX(menu));
-    values = g_object_get_data(G_OBJECT(menu), "identity-value");
-
-    return g_ptr_array_index(values, value);
-}
-
-GtkWidget *
-libbalsa_identity_combo_box(GList       * identities,
-                            const gchar * active_name,
-                            GCallback     changed_cb,
-                            gpointer      changed_data)
-{
-    GList *list;
-    GtkListStore *store;
-    GtkWidget *combo_box;
-    GtkCellLayout *layout;
-    GtkCellRenderer *renderer;
-
-    /* For each identity, store the address, the identity name, and a
-     * ref to the identity in a combo-box.
-     * Note: we can't depend on identities staying in the same
-     * order while the combo-box is open, so we need a ref to the
-     * actual identity. */
-    store = gtk_list_store_new(3,
-                               G_TYPE_STRING,
-                               G_TYPE_STRING,
-                               G_TYPE_OBJECT);
-    combo_box = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store));
-
-    for (list = identities; list != NULL; list = list->next) {
-        LibBalsaIdentity *ident;
-        gchar *from;
-        gchar *name;
-        GtkTreeIter iter;
-
-        ident = list->data;
-        from = internet_address_to_string(ident->ia, FALSE);
-       name = g_strconcat("(", ident->identity_name, ")", NULL);
-
-        gtk_list_store_append(store, &iter);
-        gtk_list_store_set(store, &iter,
-                           0, from,
-                           1, name,
-                           2, ident,
-                           -1);
-
-        g_free(from);
-        g_free(name);
-
-        if (g_strcmp0(active_name, ident->identity_name) == 0)
-            gtk_combo_box_set_active_iter(GTK_COMBO_BOX(combo_box), &iter);
-    }
-    g_object_unref(store);
-
-    layout = GTK_CELL_LAYOUT(combo_box);
-    renderer = gtk_cell_renderer_text_new();
-    gtk_cell_layout_pack_start(layout, renderer, TRUE);
-    gtk_cell_layout_set_attributes(layout, renderer, "text", 0, NULL);
-    renderer = gtk_cell_renderer_text_new();
-    gtk_cell_layout_pack_start(layout, renderer, FALSE);
-    gtk_cell_layout_set_attributes(layout, renderer, "text", 1, NULL);
-
-    g_signal_connect(combo_box, "changed", changed_cb, changed_data);
-
-    return combo_box;
-}
diff --git a/libbalsa/identity.h b/libbalsa/identity.h
index d8768d2..69d8092 100644
--- a/libbalsa/identity.h
+++ b/libbalsa/identity.h
@@ -5,14 +5,14 @@
  *
  * 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; either version 2, or (at your option) 
+ * the Free Software Foundation; either version 2, or (at your option)
  * any later version.
- *  
+ *
  * 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  
+ * 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, see <http://www.gnu.org/licenses/>.
  */
@@ -25,7 +25,6 @@
 # error "Include config.h before this file."
 #endif
 
-#include <gtk/gtk.h>
 #include <gmime/internet-address.h>
 
 #include "libbalsa.h"
@@ -33,31 +32,15 @@
 
 G_BEGIN_DECLS
 
+#define LIBBALSA_TYPE_IDENTITY (libbalsa_identity_get_type ())
+G_DECLARE_FINAL_TYPE(LibBalsaIdentity, libbalsa_identity, LIBBALSA, IDENTITY, GObject)
 
-    GType libbalsa_identity_get_type(void);
-
-#define LIBBALSA_TYPE_IDENTITY \
-    (libbalsa_identity_get_type ())
-#define LIBBALSA_IDENTITY(obj) \
-    (G_TYPE_CHECK_INSTANCE_CAST (obj, LIBBALSA_TYPE_IDENTITY, \
-                                 LibBalsaIdentity))
-#define LIBBALSA_IDENTITY_CLASS(klass) \
-    (G_TYPE_CHECK_CLASS_CAST (klass, LIBBALSA_TYPE_IDENTITY, \
-                              LibBalsaIdentityClass))
-#define LIBBALSA_IS_IDENTITY(obj) \
-    (G_TYPE_CHECK_INSTANCE_TYPE (obj, LIBBALSA_TYPE_IDENTITY))
-#define LIBBALSA_IS_IDENTITY_CLASS(klass) \
-    (G_TYPE_CHECK_CLASS_TYPE (klass, LIBBALSA_TYPE_IDENTITY))
-
-    typedef struct _LibBalsaIdentityClass LibBalsaIdentityClass;
-    
-    
-    struct _LibBalsaIdentity 
+    struct _LibBalsaIdentity
     {
         GObject object;
-        
+
         gchar* identity_name;
-        
+
         InternetAddress *ia;
         gchar* replyto;
         gchar* domain;
@@ -88,7 +71,7 @@ G_BEGIN_DECLS
        LibBalsaSmtpServer *smtp_server;
     };
 
-    struct _LibBalsaIdentityClass 
+    struct _LibBalsaIdentityClass
     {
         GObjectClass parent_class;
     };
@@ -97,7 +80,7 @@ G_BEGIN_DECLS
 /* Function prototypes */
     GObject* libbalsa_identity_new(void);
     GObject* libbalsa_identity_new_with_name(const gchar* ident_name);
-    
+
     void libbalsa_identity_set_identity_name(LibBalsaIdentity*, const gchar*);
     void libbalsa_identity_set_address(LibBalsaIdentity*, InternetAddress*);
     void libbalsa_identity_set_replyto(LibBalsaIdentity*, const gchar*);
@@ -113,8 +96,17 @@ G_BEGIN_DECLS
     void libbalsa_identity_set_sig_whenreply(LibBalsaIdentity*, gboolean);
     void libbalsa_identity_set_sig_separator(LibBalsaIdentity*, gboolean);
     void libbalsa_identity_set_sig_prepend(LibBalsaIdentity*, gboolean);
-    gchar* libbalsa_identity_get_signature(LibBalsaIdentity*, 
-                                           GtkWindow *parent);
+    void libbalsa_identity_set_face_path(LibBalsaIdentity*, const gchar*);
+    void libbalsa_identity_set_x_face_path(LibBalsaIdentity*, const gchar*);
+    void libbalsa_identity_set_request_mdn(LibBalsaIdentity*, gboolean);
+    void libbalsa_identity_set_request_dsn(LibBalsaIdentity*, gboolean);
+    void libbalsa_identity_set_always_trust(LibBalsaIdentity*, gboolean);
+    void libbalsa_identity_set_warn_send_plain(LibBalsaIdentity*, gboolean);
+    void libbalsa_identity_set_force_gpg_key_id(LibBalsaIdentity*, const gchar *);
+    void libbalsa_identity_set_force_smime_key_id(LibBalsaIdentity*, const gchar *);
+
+    gchar* libbalsa_identity_get_signature(LibBalsaIdentity *,
+                                           GError **);
     void libbalsa_identity_set_smtp_server(LibBalsaIdentity * ident,
                                            LibBalsaSmtpServer *
                                            smtp_server);
@@ -123,25 +115,6 @@ G_BEGIN_DECLS
     void libbalsa_identity_set_gpg_encrypt(LibBalsaIdentity*, gboolean);
     void libbalsa_identity_set_crypt_protocol(LibBalsaIdentity* ident, gint);
 
-    void libbalsa_identity_config_dialog(GtkWindow * parent,
-                                         GList ** identities,
-                                         LibBalsaIdentity ** current,
-                                                                                GSList * smtp_servers,
-                                         void (*changed_cb)(gpointer));
-
-    typedef void (*LibBalsaIdentityCallback) (gpointer data,
-                                              LibBalsaIdentity * identity);
-    void libbalsa_identity_select_dialog(GtkWindow * parent,
-                                         const gchar * prompt,
-                                         GList * identities,
-                                         LibBalsaIdentity * initial_id,
-                                         LibBalsaIdentityCallback update,
-                                         gpointer data);
-    GtkWidget * libbalsa_identity_combo_box(GList       * identities,
-                                            const gchar * active_name,
-                                            GCallback     changed_cb,
-                                            gpointer      changed_data);
-
     LibBalsaIdentity* libbalsa_identity_new_config(const gchar* name);
     void libbalsa_identity_save(LibBalsaIdentity* id, const gchar* prefix);
 
diff --git a/libbalsa/meson.build b/libbalsa/meson.build
index f8276aa..3292429 100644
--- a/libbalsa/meson.build
+++ b/libbalsa/meson.build
@@ -71,6 +71,8 @@ libbalsa_a_sources = [
   'html.h',
   'identity.c',
   'identity.h',
+  'identity-widgets.c',
+  'identity-widgets.h',
   'imap-server.c',
   'imap-server.h',
   'information.c',
diff --git a/src/mailbox-conf.c b/src/mailbox-conf.c
index 24d1783..a517706 100644
--- a/src/mailbox-conf.c
+++ b/src/mailbox-conf.c
@@ -51,6 +51,7 @@
 #include "pref-manager.h"
 #include "save-restore.h"
 
+#include "identity-widgets.h"
 #include "libbalsa.h"
 #include "imap-server.h"
 #include "mailbox-filter.h"
diff --git a/src/main-window.c b/src/main-window.c
index fbfe7c3..6c0ef62 100644
--- a/src/main-window.c
+++ b/src/main-window.c
@@ -36,6 +36,7 @@
 #include <gdk-pixbuf/gdk-pixbuf.h>
 
 #include "application-helpers.h"
+#include "identity-widgets.h"
 #include "imap-server.h"
 #include "libbalsa.h"
 #include "misc.h"
diff --git a/src/sendmsg-window.c b/src/sendmsg-window.c
index d4829c5..8853722 100644
--- a/src/sendmsg-window.c
+++ b/src/sendmsg-window.c
@@ -50,6 +50,7 @@
 #endif
 #include <errno.h>
 #include "application-helpers.h"
+#include "identity-widgets.h"
 #include "libbalsa.h"
 #include "misc.h"
 #include "send.h"
@@ -1185,8 +1186,7 @@ update_bsmsg_identity(BalsaSendmsg* bsmsg, LibBalsaIdentity* ident)
      * the signature if path changed */
 
     /* reconstruct the old signature to search with */
-    old_sig = libbalsa_identity_get_signature(old_ident,
-                                              GTK_WINDOW(bsmsg->window));
+    old_sig = libbalsa_identity_get_signature(old_ident, NULL);
 
     /* switch identities in bsmsg here so we can use read_signature
      * again */
@@ -1194,8 +1194,7 @@ update_bsmsg_identity(BalsaSendmsg* bsmsg, LibBalsaIdentity* ident)
     if ( (reply_type && ident->sig_whenreply)
          || (forward_type && ident->sig_whenforward)
          || (bsmsg->type == SEND_NORMAL && ident->sig_sending))
-        new_sig = libbalsa_identity_get_signature(ident,
-                                                  GTK_WINDOW(bsmsg->window));
+        new_sig = libbalsa_identity_get_signature(ident, NULL);
     else
         new_sig = NULL;
     if(!new_sig) new_sig = g_strdup("");
@@ -3627,11 +3626,10 @@ sw_insert_sig_activated(GSimpleAction * action,
 {
     BalsaSendmsg *bsmsg = data;
     gchar *signature;
+    GError *error = NULL;
+
+    signature = libbalsa_identity_get_signature(bsmsg->ident, &error);
 
-    if(!bsmsg->ident->signature_path || !bsmsg->ident->signature_path[0])
-        return;
-    signature = libbalsa_identity_get_signature(bsmsg->ident,
-                                                GTK_WINDOW(bsmsg->window));
     if (signature != NULL) {
         GtkTextBuffer *buffer =
             gtk_text_view_get_buffer(GTK_TEXT_VIEW(bsmsg->text));
@@ -3643,10 +3641,12 @@ sw_insert_sig_activated(GSimpleAction * action,
         sw_buffer_signals_unblock(bsmsg, buffer);
 
        g_free(signature);
-    } else
+    } else if (error != NULL) {
         balsa_information_parented(GTK_WINDOW(bsmsg->window),
                                    LIBBALSA_INFORMATION_ERROR,
-                                   _("No signature found!"));
+                                   error->message);
+        g_error_free(error);
+    }
 }
 
 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]