Enhanced GnuPG key selection support.
authorSergey Vlasov <vsu@users.sourceforge.net>
Sat, 21 Apr 2001 13:18:13 +0000 (13:18 +0000)
committerSergey Vlasov <vsu@users.sourceforge.net>
Sat, 21 Apr 2001 13:18:13 +0000 (13:18 +0000)
ChangeLog.claws
src/compose.c
src/prefs_account.c
src/prefs_account.h
src/rfc2015.c
src/rfc2015.h
src/select-keys.c

index 23e8f4d..408b63f 100644 (file)
@@ -1,6 +1,8 @@
 2001-04-21
 
-    * tag name claws added to prevent confusion with main branch [alfons]
+       * enhanced GnuPG key selection support [sergey]
+
+       * tag name claws added to prevent confusion with main branch [alfons]
 
        * Sync with sylpheed-0.4.65cvs4 and made it compile. [sergey]
 
index 9a0a686..51137ef 100644 (file)
@@ -1588,7 +1588,7 @@ static gint compose_write_to_file(Compose *compose, const gchar *file,
 
 #if USE_GPGME
        if (compose->use_signing) {
-               if (rfc2015_sign(file) < 0) {
+               if (rfc2015_sign(file, compose->account) < 0) {
                        unlink(file);
                        return -1;
                }
index c91b9f7..886503a 100644 (file)
@@ -104,6 +104,15 @@ static struct Compose {
        GtkWidget *sigpath_entry;
 } compose;
 
+#if USE_GPGME
+static struct Privacy {
+       GtkWidget *defaultkey_radiobtn;
+       GtkWidget *emailkey_radiobtn;
+       GtkWidget *customkey_radiobtn;
+       GtkWidget *customkey_entry;
+} privacy;
+#endif /* USE_GPGME */
+
 static struct Advanced {
        GtkWidget *smtpport_chkbtn;
        GtkWidget *smtpport_entry;
@@ -116,6 +125,10 @@ static struct Advanced {
 static void prefs_account_protocol_set_data_from_optmenu(PrefParam *pparam);
 static void prefs_account_protocol_set_optmenu         (PrefParam *pparam);
 static void prefs_account_protocol_activated           (GtkMenuItem *menuitem);
+#if USE_GPGME
+static void prefs_account_sign_key_set_data_from_radiobtn (PrefParam *pparam);
+static void prefs_account_sign_key_set_radiobtn                  (PrefParam *pparam);
+#endif /* USE_GPGME */
 
 static PrefParam param[] = {
        /* Basic */
@@ -228,6 +241,17 @@ static PrefParam param[] = {
         &compose.sigpath_entry,
         prefs_set_data_from_entry, prefs_set_entry},
 
+#if USE_GPGME
+       /* Privacy */
+       {"sign_key", NULL, &tmp_ac_prefs.sign_key, P_ENUM,
+        &privacy.defaultkey_radiobtn,
+        prefs_account_sign_key_set_data_from_radiobtn,
+        prefs_account_sign_key_set_radiobtn},
+       {"sign_key_id", "", &tmp_ac_prefs.sign_key_id, P_STRING,
+        &privacy.customkey_entry,
+        prefs_set_data_from_entry, prefs_set_entry},
+#endif /* USE_GPGME */
+
        /* Advanced */
        {"set_smtpport", "FALSE", &tmp_ac_prefs.set_smtpport, P_BOOL,
         &advanced.smtpport_chkbtn,
@@ -261,6 +285,9 @@ static void prefs_account_basic_create              (void);
 static void prefs_account_receive_create       (void);
 static void prefs_account_send_create          (void);
 static void prefs_account_compose_create       (void);
+#if USE_GPGME
+static void prefs_account_privacy_create       (void);
+#endif /* USE_GPGME */
 static void prefs_account_advanced_create      (void);
 
 static void prefs_account_key_pressed          (GtkWidget      *widget,
@@ -459,6 +486,10 @@ static void prefs_account_create(void)
        SET_NOTEBOOK_LABEL(dialog.notebook, _("Send"), page++);
        prefs_account_compose_create();
        SET_NOTEBOOK_LABEL(dialog.notebook, _("Compose"), page++);
+#if USE_GPGME
+       prefs_account_privacy_create();
+       SET_NOTEBOOK_LABEL(dialog.notebook, _("Privacy"), page++);
+#endif /* USE_GPGME */
        prefs_account_advanced_create();
        SET_NOTEBOOK_LABEL(dialog.notebook, _("Advanced"), page++);
 
@@ -903,6 +934,84 @@ static void prefs_account_compose_create(void)
        compose.sigpath_entry = sigpath_entry;
 }
 
+#if USE_GPGME
+static void prefs_account_privacy_create(void)
+{
+       GtkWidget *vbox1;
+       GtkWidget *frame1;
+       GtkWidget *vbox2;
+       GtkWidget *hbox1;
+       GtkWidget *label;
+       GtkWidget *defaultkey_radiobtn;
+       GtkWidget *emailkey_radiobtn;
+       GtkWidget *customkey_radiobtn;
+       GtkWidget *customkey_entry;
+
+       vbox1 = gtk_vbox_new (FALSE, VSPACING);
+       gtk_widget_show (vbox1);
+       gtk_container_add (GTK_CONTAINER (dialog.notebook), vbox1);
+       gtk_container_set_border_width (GTK_CONTAINER (vbox1), BOX_BORDER);
+
+       PACK_FRAME (vbox1, frame1, _("Sign key"));
+
+       vbox2 = gtk_vbox_new (FALSE, VSPACING_NARROW);
+       gtk_widget_show (vbox2);
+       gtk_container_add (GTK_CONTAINER (frame1), vbox2);
+       gtk_container_set_border_width (GTK_CONTAINER (vbox2), 8);
+
+       defaultkey_radiobtn = gtk_radio_button_new_with_label
+               (NULL, _("Use default GnuPG key"));
+       gtk_widget_show (defaultkey_radiobtn);
+       gtk_box_pack_start (GTK_BOX (vbox2), defaultkey_radiobtn,
+                           FALSE, FALSE, 0);
+       gtk_object_set_user_data (GTK_OBJECT (defaultkey_radiobtn),
+                                 GINT_TO_POINTER (SIGN_KEY_DEFAULT));
+
+       emailkey_radiobtn = gtk_radio_button_new_with_label_from_widget
+               (GTK_RADIO_BUTTON (defaultkey_radiobtn),
+                _("Select key by your email address"));
+       gtk_widget_show (emailkey_radiobtn);
+       gtk_box_pack_start (GTK_BOX (vbox2), emailkey_radiobtn,
+                           FALSE, FALSE, 0);
+       gtk_object_set_user_data (GTK_OBJECT (emailkey_radiobtn),
+                                 GINT_TO_POINTER (SIGN_KEY_BY_FROM));
+
+       customkey_radiobtn = gtk_radio_button_new_with_label_from_widget
+               (GTK_RADIO_BUTTON (defaultkey_radiobtn),
+                _("Specify key manually"));
+       gtk_widget_show (customkey_radiobtn);
+       gtk_box_pack_start (GTK_BOX (vbox2), customkey_radiobtn,
+                           FALSE, FALSE, 0);
+       gtk_object_set_user_data (GTK_OBJECT (customkey_radiobtn),
+                                 GINT_TO_POINTER (SIGN_KEY_CUSTOM));
+
+       hbox1 = gtk_hbox_new (FALSE, 8);
+       gtk_widget_show (hbox1);
+       gtk_box_pack_start (GTK_BOX (vbox2), hbox1, FALSE, FALSE, 0);
+
+       label = gtk_label_new ("");
+       gtk_widget_show (label);
+       gtk_box_pack_start (GTK_BOX (hbox1), label, FALSE, FALSE, 0);
+       gtk_widget_set_usize (label, 16, -1);
+
+       label = gtk_label_new (_("User or key ID:"));
+       gtk_widget_show (label);
+       gtk_box_pack_start (GTK_BOX (hbox1), label, FALSE, FALSE, 0);
+
+       customkey_entry = gtk_entry_new ();
+       gtk_widget_show (customkey_entry);
+       gtk_box_pack_start (GTK_BOX (hbox1), customkey_entry,
+                           TRUE, TRUE, 0);
+
+       SET_TOGGLE_SENSITIVITY (customkey_radiobtn, customkey_entry);
+
+       privacy.defaultkey_radiobtn = defaultkey_radiobtn;
+       privacy.emailkey_radiobtn = emailkey_radiobtn;
+       privacy.customkey_radiobtn = customkey_radiobtn;
+       privacy.customkey_entry = customkey_entry;
+}
+#endif /* USE_GPGME */
+
 static void prefs_account_advanced_create(void)
 {
        GtkWidget *vbox1;
@@ -1033,6 +1142,48 @@ static void prefs_account_cancel(void)
        gtk_main_quit();
 }
 
+#if USE_GPGME
+
+static void prefs_account_sign_key_set_data_from_radiobtn(PrefParam *pparam)
+{
+       GtkRadioButton *radiobtn;
+       GSList *group;
+
+       radiobtn = GTK_RADIO_BUTTON (*pparam->widget);
+       group = gtk_radio_button_group (radiobtn);
+       while (group != NULL) {
+               GtkToggleButton *btn = GTK_TOGGLE_BUTTON (group->data);
+               if (gtk_toggle_button_get_active (btn)) {
+                       *((SignKeyType *)pparam->data) = GPOINTER_TO_INT
+                               (gtk_object_get_user_data (GTK_OBJECT (btn)));
+                       break;
+               }
+               group = group->next;
+       }
+}
+
+static void prefs_account_sign_key_set_radiobtn(PrefParam *pparam)
+{
+       GtkRadioButton *radiobtn;
+       GSList *group;
+       gpointer data;
+
+       data = GINT_TO_POINTER (*((RecvProtocol *)pparam->data));
+       radiobtn = GTK_RADIO_BUTTON (*pparam->widget);
+       group = gtk_radio_button_group (radiobtn);
+       while (group != NULL) {
+               GtkToggleButton *btn = GTK_TOGGLE_BUTTON (group->data);
+               gpointer data1 = gtk_object_get_user_data (GTK_OBJECT (btn));
+               if (data1 == data) {
+                       gtk_toggle_button_set_active (btn, TRUE);
+                       break;
+               }
+               group = group->next;
+       }
+}
+
+#endif /* USE_GPGME */
+
 static void prefs_account_protocol_set_data_from_optmenu(PrefParam *pparam)
 {
        GtkWidget *menu;
index f8c6a0d..0881c7c 100644 (file)
@@ -35,6 +35,14 @@ typedef enum {
        A_LOCAL
 } RecvProtocol;
 
+#if USE_GPGME
+typedef enum {
+       SIGN_KEY_DEFAULT,
+       SIGN_KEY_BY_FROM,
+       SIGN_KEY_CUSTOM
+} SignKeyType;
+#endif /* USE_GPGME */
+
 struct _PrefsAccount
 {
        gchar *account_name;
@@ -79,6 +87,12 @@ struct _PrefsAccount
        /* Compose */
        gchar *sig_path;
 
+#if USE_GPGME
+       /* Privacy */
+       SignKeyType sign_key;
+       gchar *sign_key_id;
+#endif /* USE_GPGME */
+
        /* Advanced */
        gboolean  set_smtpport;
        gushort   smtpport;
index d064ec5..3791441 100644 (file)
@@ -867,12 +867,73 @@ failure:
     return -1; /* error */
 }
 
+static int
+set_signers (GpgmeCtx ctx, PrefsAccount *ac)
+{
+    GSList *key_list = NULL;
+    GpgmeCtx list_ctx = NULL;
+    const char *keyid = NULL;
+    GSList *p;
+    GpgmeError err;
+    GpgmeKey key;
+
+    if (ac == NULL)
+       return 0;
+
+    switch (ac->sign_key) {
+    case SIGN_KEY_DEFAULT:
+       return 0;               /* nothing to do */
+
+    case SIGN_KEY_BY_FROM:
+       keyid = ac->address;
+       break;
+
+    case SIGN_KEY_CUSTOM:
+       keyid = ac->sign_key_id;
+       break;
+
+    default:
+       g_assert_not_reached ();
+    }
+
+    err = gpgme_new (&list_ctx);
+    if (err)
+       goto leave;
+    err = gpgme_op_keylist_start (list_ctx, keyid, 1);
+    if (err)
+       goto leave;
+    while ( !(err = gpgme_op_keylist_next (list_ctx, &key)) ) {
+       key_list = g_slist_append (key_list, key);
+    }
+    if (err != GPGME_EOF)
+       goto leave;
+    if (key_list == NULL) {
+       g_warning ("no keys found for keyid \"%s\"", keyid);
+    }
+    gpgme_signers_clear (ctx);
+    for (p = key_list; p != NULL; p = p->next) {
+       err = gpgme_signers_add (ctx, (GpgmeKey) p->data);
+       if (err)
+           goto leave;
+    }
+
+leave:
+    if (err)
+        g_message ("** set_signers failed: %s", gpgme_strerror (err));
+    for (p = key_list; p != NULL; p = p->next)
+       gpgme_key_unref ((GpgmeKey) p->data);
+    g_slist_free (key_list);
+    if (list_ctx)
+       gpgme_release (list_ctx);
+    return err;
+}
+
 /* 
  * plain contains an entire mime object.  Sign it and return an
  * GpgmeData object with the signature of it or NULL in case of error.
  */
 static GpgmeData
-pgp_sign (GpgmeData plain)
+pgp_sign (GpgmeData plain, PrefsAccount *ac)
 {
     GpgmeCtx ctx = NULL;
     GpgmeError err;
@@ -894,6 +955,9 @@ pgp_sign (GpgmeData plain)
     }
     gpgme_set_textmode (ctx, 1);
     gpgme_set_armor (ctx, 1);
+    err = set_signers (ctx, ac);
+    if (err)
+       goto leave;
     err = gpgme_op_sign (ctx, plain, sig, GPGME_SIG_MODE_DETACH);
 
 leave:
@@ -914,7 +978,7 @@ leave:
  * Sign the file and replace its content with the signed one.
  */
 int
-rfc2015_sign (const char *file)
+rfc2015_sign (const char *file, PrefsAccount *ac)
 {
     FILE *fp = NULL;
     char buf[BUFFSIZE];
@@ -996,7 +1060,7 @@ rfc2015_sign (const char *file)
         goto failure;
     }
 
-    sigdata = pgp_sign (plain);
+    sigdata = pgp_sign (plain, ac);
     if (!sigdata) 
         goto failure;
 
index 6fd1921..98115b9 100644 (file)
@@ -24,6 +24,7 @@
 #include <stdio.h>
 
 #include "procmime.h"
+#include "prefs_account.h"
 
 void rfc2015_secure_remove (const char *fname);
 MimeInfo * rfc2015_find_signature (MimeInfo *mimeinfo);
@@ -32,6 +33,6 @@ void rfc2015_check_signature (MimeInfo *mimeinfo, FILE *fp);
 int rfc2015_is_encrypted (MimeInfo *mimeinfo);
 void rfc2015_decrypt_message (MsgInfo *msginfo, MimeInfo *mimeinfo, FILE *fp);
 int rfc2015_encrypt (const char *file, GSList *recp_list);
-int rfc2015_sign (const char *file);
+int rfc2015_sign (const char *file, PrefsAccount *ac);
 
 #endif /* __RFC2015_H__ */
index 04514be..b397ef8 100644 (file)
 #include <gtk/gtkentry.h>
 #include <gtk/gtkhbbox.h>
 #include <gtk/gtkbutton.h>
-#include <gtk/gtkprogressbar.h>
 #include <gtk/gtksignal.h>
 
 #include "intl.h"
 #include "select-keys.h"
 #include "utils.h"
 #include "gtkutils.h"
+#include "inputdialog.h"
+
+#define DIM(v) (sizeof(v)/sizeof((v)[0]))
+#define DIMof(type,member)   DIM(((type *)0)->member)
+
 
 enum col_titles { 
     COL_ALGO,
@@ -55,39 +59,51 @@ enum col_titles {
     N_COL_TITLES
 };
 
-static struct {
+struct select_keys_s {
     int okay;
     GtkWidget *window;
     GtkLabel *toplabel;
     GtkCList *clist;
-    GtkProgress *progress;
     const char *pattern;
     GpgmeRecipients rset;
-} select_keys;
+    GpgmeCtx select_ctx;
+
+    GtkSortType sort_type;
+    enum col_titles sort_column;
+    
+};
 
 
 static void set_row (GtkCList *clist, GpgmeKey key );
-static void fill_clist (GtkCList *clist, const char *pattern );
-static void create_dialog (void);
-static void open_dialog (void);
-static void close_dialog (void);
+static void fill_clist (struct select_keys_s *sk, const char *pattern );
+static void create_dialog (struct select_keys_s *sk);
+static void open_dialog (struct select_keys_s *sk);
+static void close_dialog (struct select_keys_s *sk);
 static void key_pressed_cb (GtkWidget *widget,
                             GdkEventKey *event, gpointer data);
 static void select_btn_cb (GtkWidget *widget, gpointer data);
 static void cancel_btn_cb (GtkWidget *widget, gpointer data);
+static void other_btn_cb (GtkWidget *widget, gpointer data);
+static void sort_keys ( struct select_keys_s *sk, enum col_titles column);
+static void sort_keys_name (GtkWidget *widget, gpointer data);
+static void sort_keys_email (GtkWidget *widget, gpointer data);
 
 
 static void
-update_progress (void)
+update_progress (struct select_keys_s *sk, int running, const char *pattern)
 {
-    if (select_keys.progress) {
-        gfloat val = gtk_progress_get_value (select_keys.progress);
-
-        val += 1;
-        gtk_progress_set_value (select_keys.progress, val);
-        if ( !GTK_WIDGET_VISIBLE (select_keys.progress) )
-            gtk_widget_show (GTK_WIDGET (select_keys.progress));
-    }
+    static int windmill[] = { '-', '\\', '|', '/' };
+    char *buf;
+
+    if (!running)
+        buf = g_strdup_printf (_("Please select key for `%s'"), 
+                               pattern);
+    else 
+        buf = g_strdup_printf (_("Collecting info for `%s' ... %c"), 
+                               pattern,
+                               windmill[running%DIM(windmill)] );
+    gtk_label_set_text (sk->toplabel, buf );
+    g_free (buf);
 }
 
 
@@ -104,38 +120,37 @@ update_progress (void)
 GpgmeRecipients
 gpgmegtk_recipient_selection (GSList *recp_names)
 {
+    struct select_keys_s sk;
     GpgmeError err;
 
-    err = gpgme_recipients_new (&select_keys.rset);
+    memset ( &sk, 0, sizeof sk);
+
+    err = gpgme_recipients_new (&sk.rset);
     if (err) {
         g_message ("** failed to allocate recipients set: %s",
                    gpgme_strerror (err) );
         return NULL;
     }
         
-    open_dialog ();
+    open_dialog (&sk);
 
     do {
-        select_keys.pattern = recp_names? recp_names->data:NULL;
-        gtk_label_set_text (select_keys.toplabel, select_keys.pattern );
-        gtk_clist_clear (select_keys.clist);
-        fill_clist (select_keys.clist, select_keys.pattern);
+        sk.pattern = recp_names? recp_names->data:NULL;
+        gtk_clist_clear (sk.clist);
+        fill_clist (&sk, sk.pattern);
+        update_progress (&sk, 0, sk.pattern);
         gtk_main ();
         if (recp_names)
             recp_names = recp_names->next;
-    } while (select_keys.okay && recp_names );
+    } while (sk.okay && recp_names );
 
-    close_dialog ();
+    close_dialog (&sk);
 
-    {
-        GpgmeRecipients rset = select_keys.rset;
-        select_keys.rset = NULL;
-        if (!rset) {
-            gpgme_recipients_release (rset);
-            rset = NULL;
-        }
-        return rset;
+    if (!sk.okay) {
+        gpgme_recipients_release (sk.rset);
+        sk.rset = NULL;
     }
+    return sk.rset;
 } 
 
 static void
@@ -180,18 +195,31 @@ set_row (GtkCList *clist, GpgmeKey key )
 
 
 static void 
-fill_clist (GtkCList *clist, const char *pattern )
+fill_clist (struct select_keys_s *sk, const char *pattern )
 {
+    GtkCList *clist;
     GpgmeCtx ctx;
     GpgmeError err;
     GpgmeKey key;
+    int running=0;
+
+    g_return_if_fail (sk);
+    clist = sk->clist;
+    g_return_if_fail (clist);
 
     debug_print ("select_keys:fill_clist:  pattern `%s'\n", pattern );
 
     /*gtk_clist_freeze (select_keys.clist);*/
     err = gpgme_new (&ctx);
-    g_assert (!err);
+    if (err) {
+        g_message ("** gpgme_new failed: %s",
+                   gpgme_strerror (err));
+        return;
+    }
 
+    sk->select_ctx = ctx;
+
+    update_progress (sk, ++running, pattern);
     while (gtk_events_pending ())
         gtk_main_iteration ();
 
@@ -199,21 +227,23 @@ fill_clist (GtkCList *clist, const char *pattern )
     if (err) {
         g_message ("** gpgme_op_keylist_start(%s) failed: %s",
                    pattern, gpgme_strerror (err));
+        sk->select_ctx = NULL;
+       gpgme_release (ctx);
         return;
     }
-    update_progress ();
+    update_progress (sk, ++running, pattern);
     while ( !(err = gpgme_op_keylist_next ( ctx, &key )) ) {
         debug_print ("%% %s:%d:  insert\n", __FILE__ ,__LINE__ );
         set_row (clist, key ); key = NULL;
-        update_progress ();
+        update_progress (sk, ++running, pattern);
         while (gtk_events_pending ())
             gtk_main_iteration ();
     }
     debug_print ("%% %s:%d:  ready\n", __FILE__ ,__LINE__ );
-    gtk_widget_hide (GTK_WIDGET (select_keys.progress));
     if ( err != GPGME_EOF )
         g_message ("** gpgme_op_keylist_next failed: %s",
                    gpgme_strerror (err));
+    sk->select_ctx = NULL;
     gpgme_release (ctx);
     /*gtk_clist_thaw (select_keys.clist);*/
 }
@@ -222,7 +252,7 @@ fill_clist (GtkCList *clist, const char *pattern )
 
 
 static void 
-create_dialog ()
+create_dialog (struct select_keys_s *sk)
 {
     GtkWidget *window;
     GtkWidget *vbox, *vbox2, *hbox;
@@ -230,28 +260,25 @@ create_dialog ()
     GtkWidget *scrolledwin;
     GtkWidget *clist;
     GtkWidget *label;
-    GtkWidget *progress;
-    GtkWidget *select_btn, *cancel_btn;
-    gchar *titles[N_COL_TITLES];
+    GtkWidget *other_btn, *select_btn, *cancel_btn;
+    const char *titles[N_COL_TITLES];
 
-    g_assert (!select_keys.window);
+    g_assert (!sk->window);
     window = gtk_window_new (GTK_WINDOW_DIALOG);
     gtk_widget_set_usize (window, 500, 320);
     gtk_container_set_border_width (GTK_CONTAINER (window), 8);
     gtk_window_set_title (GTK_WINDOW (window), _("Select Keys"));
     gtk_window_set_modal (GTK_WINDOW (window), TRUE);
     gtk_signal_connect (GTK_OBJECT (window), "delete_event",
-                        GTK_SIGNAL_FUNC (close_dialog), NULL);
+                        GTK_SIGNAL_FUNC (close_dialog), sk);
     gtk_signal_connect (GTK_OBJECT (window), "key_press_event",
-                        GTK_SIGNAL_FUNC (key_pressed_cb), NULL);
+                        GTK_SIGNAL_FUNC (key_pressed_cb), sk);
 
     vbox = gtk_vbox_new (FALSE, 8);
     gtk_container_add (GTK_CONTAINER (window), vbox);
 
     hbox  = gtk_hbox_new(FALSE, 4);
     gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
-    label = gtk_label_new ( _("Select key for: ") );
-    gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
     label = gtk_label_new ( "" );
     gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
 
@@ -273,7 +300,7 @@ create_dialog ()
     titles[COL_EMAIL]    = _("Address");
     titles[COL_VALIDITY] = _("Val");
 
-    clist = gtk_clist_new_with_titles (N_COL_TITLES, titles);
+    clist = gtk_clist_new_with_titles (N_COL_TITLES, (char**)titles);
     gtk_container_add (GTK_CONTAINER (scrolledwin), clist);
     gtk_clist_set_column_width (GTK_CLIST(clist), COL_ALGO,      40);
     gtk_clist_set_column_width (GTK_CLIST(clist), COL_KEYID,     60);
@@ -281,58 +308,70 @@ create_dialog ()
     gtk_clist_set_column_width (GTK_CLIST(clist), COL_EMAIL,    100);
     gtk_clist_set_column_width (GTK_CLIST(clist), COL_VALIDITY,  20);
     gtk_clist_set_selection_mode (GTK_CLIST(clist), GTK_SELECTION_BROWSE);
+    gtk_signal_connect (GTK_OBJECT(GTK_CLIST(clist)->column[COL_NAME].button),
+                       "clicked",
+                        GTK_SIGNAL_FUNC(sort_keys_name), sk);
+    gtk_signal_connect (GTK_OBJECT(GTK_CLIST(clist)->column[COL_EMAIL].button),
+                       "clicked",
+                        GTK_SIGNAL_FUNC(sort_keys_email), sk);
 
     hbox = gtk_hbox_new (FALSE, 8);
     gtk_box_pack_end (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
 
-    gtkut_button_set_create (&bbox, &select_btn, _("Select"),
-                             &cancel_btn, _("Cancel"), NULL, NULL);
+    gtkut_button_set_create (&bbox, 
+                             &other_btn,  _("Other"),
+                             &select_btn, _("Select"),
+                             &cancel_btn, _("Cancel"));
     gtk_box_pack_end (GTK_BOX (hbox), bbox, FALSE, FALSE, 0);
     gtk_widget_grab_default (select_btn);
 
+    gtk_signal_connect (GTK_OBJECT (other_btn), "clicked",
+                        GTK_SIGNAL_FUNC (other_btn_cb), sk);
     gtk_signal_connect (GTK_OBJECT (select_btn), "clicked",
-                        GTK_SIGNAL_FUNC (select_btn_cb), clist);
+                        GTK_SIGNAL_FUNC (select_btn_cb), sk);
     gtk_signal_connect (GTK_OBJECT(cancel_btn), "clicked",
-                        GTK_SIGNAL_FUNC (cancel_btn_cb), NULL);
+                        GTK_SIGNAL_FUNC (cancel_btn_cb), sk);
+    
 
     vbox2 = gtk_vbox_new (FALSE, 4);
     gtk_box_pack_start (GTK_BOX (hbox), vbox2, FALSE, FALSE, 0);
 
-    progress = gtk_progress_bar_new ();
-    gtk_box_pack_start (GTK_BOX (vbox2), progress, FALSE, FALSE, 4);
-    gtk_progress_set_activity_mode (GTK_PROGRESS (progress), 1);
-
     gtk_widget_show_all (window);
-    select_keys.window = window;
-    select_keys.toplabel = GTK_LABEL (label);
-    select_keys.clist  = GTK_CLIST (clist);
-    select_keys.progress = GTK_PROGRESS (progress);
+    sk->window = window;
+    sk->toplabel = GTK_LABEL (label);
+    sk->clist  = GTK_CLIST (clist);
 }
 
 
 static void
-open_dialog ()
+open_dialog (struct select_keys_s *sk)
 {
-    if ( !select_keys.window )
-        create_dialog ();
-    select_keys.okay = 0;
-    gtk_widget_show (select_keys.window);
+    if ( !sk->window )
+        create_dialog (sk);
+    sk->okay = 0;
+    sk->sort_column = N_COL_TITLES; /* use an invalid value */
+    sk->sort_type = GTK_SORT_ASCENDING;
+    gtk_widget_show (sk->window);
 }
 
 
 static void
-close_dialog ()
+close_dialog (struct select_keys_s *sk)
 {
-    gtk_widget_destroy (select_keys.window);
-    select_keys.window = NULL;
+    g_return_if_fail (sk);
+    gtk_widget_destroy (sk->window);
+    sk->window = NULL;
 }
 
 
 static void 
 key_pressed_cb (GtkWidget *widget, GdkEventKey *event, gpointer data)
 {
+    struct select_keys_s *sk = data;
+
+    g_return_if_fail (sk);
     if (event && event->keyval == GDK_Escape) {
-        select_keys.okay = 0;
+        sk->okay = 0;
         gtk_main_quit ();
     }
 }
@@ -341,24 +380,28 @@ key_pressed_cb (GtkWidget *widget, GdkEventKey *event, gpointer data)
 static void 
 select_btn_cb (GtkWidget *widget, gpointer data)
 {
-    GtkCList *clist = GTK_CLIST (data);
+    struct select_keys_s *sk = data;
     int row;
     GpgmeKey key;
 
-    if (!clist->selection) {
+    g_return_if_fail (sk);
+    if (!sk->clist->selection) {
         g_message ("** nothing selected");
         return;
     }
-    row = GPOINTER_TO_INT(clist->selection->data);
-    key = gtk_clist_get_row_data(clist, row);
+    row = GPOINTER_TO_INT(sk->clist->selection->data);
+    key = gtk_clist_get_row_data(sk->clist, row);
     if (key) {
         const char *s = gpgme_key_get_string_attr (key,
                                                    GPGME_ATTR_FPR,
                                                    NULL, 0 );
-        g_message ("** FIXME: we are faking the trust calculation");
-        if (!gpgme_recipients_add_name_with_validity (select_keys.rset, s,
+        if ( gpgme_key_get_ulong_attr (key, GPGME_ATTR_VALIDITY, NULL, 0 )
+             < GPGME_VALIDITY_FULL ) {
+            g_message ("** FIXME: we are faking the trust calculation");
+        }
+        if (!gpgme_recipients_add_name_with_validity (sk->rset, s,
                                                       GPGME_VALIDITY_FULL) ) {
-            select_keys.okay = 1;
+            sk->okay = 1;
             gtk_main_quit ();
         }
     }
@@ -368,8 +411,102 @@ select_btn_cb (GtkWidget *widget, gpointer data)
 static void 
 cancel_btn_cb (GtkWidget *widget, gpointer data)
 {
-    select_keys.okay = 0;
+    struct select_keys_s *sk = data;
+
+    g_return_if_fail (sk);
+    sk->okay = 0;
+    if (sk->select_ctx)
+        gpgme_cancel (sk->select_ctx);
     gtk_main_quit ();
 }
 
+
+static void
+other_btn_cb (GtkWidget *widget, gpointer data)
+{
+    struct select_keys_s *sk = data;
+    char *uid;
+
+    g_return_if_fail (sk);
+    uid = input_dialog ( _("Add key"),
+                         _("Enter another user or key ID\n"),
+                         NULL );
+    if (!uid)
+        return;
+    fill_clist (sk, uid);
+    update_progress (sk, 0, sk->pattern);
+    g_free (uid);
+}
+
+
+static gint 
+cmp_attr (gconstpointer pa, gconstpointer pb, GpgmeAttr attr)
+{
+    GpgmeKey a = ((GtkCListRow *)pa)->data;
+    GpgmeKey b = ((GtkCListRow *)pb)->data;
+    const char *sa, *sb;
+    
+    sa = a? gpgme_key_get_string_attr (a, attr, NULL, 0 ) : NULL;
+    sb = b? gpgme_key_get_string_attr (b, attr, NULL, 0 ) : NULL;
+    if (!sa)
+        return !!sb;
+    if (!sb)
+        return -1;
+    return strcasecmp(sa, sb);
+}
+
+static gint 
+cmp_name (GtkCList *clist, gconstpointer pa, gconstpointer pb)
+{
+    return cmp_attr (pa, pb, GPGME_ATTR_NAME);
+}
+
+static gint 
+cmp_email (GtkCList *clist, gconstpointer pa, gconstpointer pb)
+{
+    return cmp_attr (pa, pb, GPGME_ATTR_EMAIL);
+}
+
+static void
+sort_keys ( struct select_keys_s *sk, enum col_titles column)
+{
+    GtkCList *clist = sk->clist;
+
+    switch (column) {
+      case COL_NAME:
+        gtk_clist_set_compare_func (clist, cmp_name);
+        break;
+      case COL_EMAIL:
+        gtk_clist_set_compare_func (clist, cmp_email);
+        break;
+      default:
+        return;
+    }
+
+    /* column clicked again: toggle as-/decending */
+    if ( sk->sort_column == column) {
+        sk->sort_type = sk->sort_type == GTK_SORT_ASCENDING ?
+                        GTK_SORT_DESCENDING : GTK_SORT_ASCENDING;
+    }
+    else
+        sk->sort_type = GTK_SORT_ASCENDING;
+
+    sk->sort_column = column;
+    gtk_clist_set_sort_type (clist, sk->sort_type);
+    gtk_clist_sort (clist);
+}
+
+static void
+sort_keys_name (GtkWidget *widget, gpointer data)
+{
+    sort_keys ( (struct select_keys_s*)data, COL_NAME );
+}
+
+static void
+sort_keys_email (GtkWidget *widget, gpointer data)
+{
+    sort_keys ( (struct select_keys_s*)data, COL_EMAIL );
+}
+
+
 #endif /*USE_GPGME*/