[seahorse/pgp/subkey-list-box] pgp: Create specific widget for subkey lists




commit 4a125ec25c9d86446279ef104f6d81929790b290
Author: Niels De Graef <nielsdegraef gmail com>
Date:   Tue Jul 27 23:16:20 2021 +0200

    pgp: Create specific widget for subkey lists
    
    Try to show a cleaner UI by getting rid of the `GtkTreeView`. This also
    allows us to do things like showing tooltips on the specific
    capabilities of each subkey (for example "Sign").

 data/seahorse.gresource.xml                |   1 +
 libseahorse/seahorse.css                   |  12 +
 pgp/meson.build                            |   1 +
 pgp/seahorse-gpgme-key-op.c                |  14 +-
 pgp/seahorse-gpgme-key.c                   |   2 +-
 pgp/seahorse-gpgme-subkey.c                |  91 ++----
 pgp/seahorse-gpgme-subkey.h                |   8 +-
 pgp/seahorse-pgp-key-properties.c          | 197 +-----------
 pgp/seahorse-pgp-key.c                     |   1 +
 pgp/seahorse-pgp-private-key-properties.ui | 148 +--------
 pgp/seahorse-pgp-public-key-properties.ui  |  37 +--
 pgp/seahorse-pgp-subkey-list-box-row.ui    | 233 +++++++++++++++
 pgp/seahorse-pgp-subkey-list-box.c         | 464 +++++++++++++++++++++++++++++
 pgp/seahorse-pgp-subkey-list-box.h         |  42 +++
 pgp/seahorse-pgp-subkey.c                  |  89 +++++-
 pgp/seahorse-pgp-subkey.h                  |   9 +
 po/POTFILES.in                             |   2 +
 17 files changed, 918 insertions(+), 433 deletions(-)
---
diff --git a/data/seahorse.gresource.xml b/data/seahorse.gresource.xml
index da8c3f67..32aa3b2d 100644
--- a/data/seahorse.gresource.xml
+++ b/data/seahorse.gresource.xml
@@ -38,6 +38,7 @@
     <file alias="seahorse-keyserver-sync.ui" 
preprocess="xml-stripblanks">../pgp/seahorse-keyserver-sync.ui</file>
     <file alias="seahorse-pgp-private-key-properties.ui" 
preprocess="xml-stripblanks">../pgp/seahorse-pgp-private-key-properties.ui</file>
     <file alias="seahorse-pgp-public-key-properties.ui" 
preprocess="xml-stripblanks">../pgp/seahorse-pgp-public-key-properties.ui</file>
+    <file alias="seahorse-pgp-subkey-list-box-row.ui" 
preprocess="xml-stripblanks">../pgp/seahorse-pgp-subkey-list-box-row.ui</file>
 
     <!-- PKCS#11 -->
     <file alias="seahorse-pkcs11-generate.ui" 
preprocess="xml-stripblanks">../pkcs11/seahorse-pkcs11-generate.ui</file>
diff --git a/libseahorse/seahorse.css b/libseahorse/seahorse.css
index e80e048c..0cc49df0 100644
--- a/libseahorse/seahorse.css
+++ b/libseahorse/seahorse.css
@@ -38,6 +38,18 @@
     border-color: shade (@theme_bg_color, 0.66);
 }
 
+.pgp-subkey-usage-label {
+  border: 1px solid shade(@theme_bg_color, 0.9);
+  background-color: shade(@theme_bg_color, .95);
+  padding-left: 3px;
+  padding-right: 3px;
+  border-radius: 4px;
+}
+
+:selected .pgp-subkey-usage-label {
+  background-color: shade(@theme_selected_bg_color, .95);
+}
+
 levelbar .strength-weak {
   background-color: #cc0000;
   border-color: #cc0000;
diff --git a/pgp/meson.build b/pgp/meson.build
index 3576596d..3346c5d6 100644
--- a/pgp/meson.build
+++ b/pgp/meson.build
@@ -29,6 +29,7 @@ pgp_sources = files(
   'seahorse-pgp-photo.c',
   'seahorse-pgp-signature.c',
   'seahorse-pgp-subkey.c',
+  'seahorse-pgp-subkey-list-box.c',
   'seahorse-pgp-uid.c',
   'seahorse-transfer.c',
   'seahorse-unknown.c',
diff --git a/pgp/seahorse-gpgme-key-op.c b/pgp/seahorse-gpgme-key-op.c
index 76ff6b0d..374f3d7f 100644
--- a/pgp/seahorse-gpgme-key-op.c
+++ b/pgp/seahorse-gpgme-key-op.c
@@ -1138,6 +1138,7 @@ seahorse_gpgme_key_op_set_expires (SeahorseGpgmeSubkey *subkey,
     GDateTime *old_expires;
     ExpireParm exp_parm;
     SeahorseEditParm *parms;
+    SeahorsePgpKey *parent_key;
     gpgme_key_t key;
 
     g_return_val_if_fail (SEAHORSE_GPGME_IS_SUBKEY (subkey), GPG_E (GPG_ERR_WRONG_KEY_USAGE));
@@ -1149,7 +1150,8 @@ seahorse_gpgme_key_op_set_expires (SeahorseGpgmeSubkey *subkey,
         g_return_val_if_fail (!g_date_time_equal (old_expires, expires),
                               GPG_E (GPG_ERR_INV_VALUE));
 
-    key = seahorse_gpgme_subkey_get_pubkey (subkey);
+    parent_key = seahorse_pgp_subkey_get_parent_key (SEAHORSE_PGP_SUBKEY (subkey));
+    key = seahorse_gpgme_key_get_public (SEAHORSE_GPGME_KEY (parent_key));
     g_return_val_if_fail (key, GPG_E (GPG_ERR_INV_VALUE));
 
     exp_parm.index = seahorse_pgp_subkey_get_index (SEAHORSE_PGP_SUBKEY (subkey));
@@ -1555,13 +1557,15 @@ del_key_transit (unsigned int   current_state,
 gpgme_error_t
 seahorse_gpgme_key_op_del_subkey (SeahorseGpgmeSubkey *subkey)
 {
-    SeahorseEditParm *parms;
+    SeahorsePgpKey *parent_key;
     gpgme_key_t key;
+    SeahorseEditParm *parms;
     int index;
 
     g_return_val_if_fail (SEAHORSE_GPGME_IS_SUBKEY (subkey), GPG_E (GPG_ERR_WRONG_KEY_USAGE));
 
-    key = seahorse_gpgme_subkey_get_pubkey (subkey);
+    parent_key = seahorse_pgp_subkey_get_parent_key (SEAHORSE_PGP_SUBKEY (subkey));
+    key = seahorse_gpgme_key_get_public (SEAHORSE_GPGME_KEY (parent_key));
     g_return_val_if_fail (key, GPG_E (GPG_ERR_INV_VALUE));
 
     index = seahorse_pgp_subkey_get_index (SEAHORSE_PGP_SUBKEY (subkey));
@@ -1738,6 +1742,7 @@ seahorse_gpgme_key_op_revoke_subkey (SeahorseGpgmeSubkey *subkey, SeahorseRevoke
     RevSubkeyParm rev_parm;
     SeahorseEditParm *parms;
     gpgme_subkey_t gsubkey;
+    SeahorsePgpKey *parent_key;
     gpgme_key_t key;
 
     g_return_val_if_fail (SEAHORSE_GPGME_IS_SUBKEY (subkey), GPG_E (GPG_ERR_WRONG_KEY_USAGE));
@@ -1745,7 +1750,8 @@ seahorse_gpgme_key_op_revoke_subkey (SeahorseGpgmeSubkey *subkey, SeahorseRevoke
     gsubkey = seahorse_gpgme_subkey_get_subkey (subkey);
     g_return_val_if_fail (!gsubkey->revoked, GPG_E (GPG_ERR_INV_VALUE));
 
-    key = seahorse_gpgme_subkey_get_pubkey (subkey);
+    parent_key = seahorse_pgp_subkey_get_parent_key (SEAHORSE_PGP_SUBKEY (subkey));
+    key = seahorse_gpgme_key_get_public (SEAHORSE_GPGME_KEY (parent_key));
     g_return_val_if_fail (key, GPG_E (GPG_ERR_INV_VALUE));
 
     rev_parm.index = seahorse_pgp_subkey_get_index (SEAHORSE_PGP_SUBKEY (subkey));
diff --git a/pgp/seahorse-gpgme-key.c b/pgp/seahorse-gpgme-key.c
index 712d3196..b4cc07f5 100644
--- a/pgp/seahorse-gpgme-key.c
+++ b/pgp/seahorse-gpgme-key.c
@@ -280,7 +280,7 @@ realize_subkeys (SeahorseGpgmeKey *self)
         for (gsubkey = self->pubkey->subkeys; gsubkey; gsubkey = gsubkey->next) {
             g_autoptr(SeahorseGpgmeSubkey) subkey = NULL;
 
-            subkey = seahorse_gpgme_subkey_new (self->pubkey, gsubkey);
+            subkey = seahorse_gpgme_subkey_new (self, gsubkey);
             g_ptr_array_add (results, g_steal_pointer (&subkey));
         }
     }
diff --git a/pgp/seahorse-gpgme-subkey.c b/pgp/seahorse-gpgme-subkey.c
index a03d3d6a..e215131f 100644
--- a/pgp/seahorse-gpgme-subkey.c
+++ b/pgp/seahorse-gpgme-subkey.c
@@ -29,28 +29,18 @@
 
 enum {
     PROP_0,
-    PROP_PUBKEY,
-    PROP_SUBKEY
+    PROP_SUBKEY,
+    N_PROPS
 };
 
 struct _SeahorseGpgmeSubkey {
     SeahorsePgpSubkey parent_instance;
 
-    gpgme_key_t pubkey;         /* The public key that this subkey is part of */
-    gpgme_subkey_t subkey;      /* The subkey referred to */
+    gpgme_subkey_t subkey;
 };
 
 G_DEFINE_TYPE (SeahorseGpgmeSubkey, seahorse_gpgme_subkey, SEAHORSE_PGP_TYPE_SUBKEY);
 
-
-gpgme_key_t
-seahorse_gpgme_subkey_get_pubkey (SeahorseGpgmeSubkey *self)
-{
-    g_return_val_if_fail (SEAHORSE_GPGME_IS_SUBKEY (self), NULL);
-    g_return_val_if_fail (self->pubkey, NULL);
-    return self->pubkey;
-}
-
 gpgme_subkey_t
 seahorse_gpgme_subkey_get_subkey (SeahorseGpgmeSubkey *self)
 {
@@ -62,22 +52,25 @@ seahorse_gpgme_subkey_get_subkey (SeahorseGpgmeSubkey *self)
 void
 seahorse_gpgme_subkey_set_subkey (SeahorseGpgmeSubkey *self, gpgme_subkey_t subkey)
 {
+    SeahorsePgpSubkey *base = SEAHORSE_PGP_SUBKEY (self);
+    SeahorseGpgmeKey *parent;
+    gpgme_key_t pubkey;
     g_autofree char *description = NULL, *fingerprint = NULL, *name = NULL;
-    SeahorsePgpSubkey *base;
     const char *algo_type;
-    GObject *obj;
     gpgme_subkey_t sub;
     int i, index;
     g_autoptr(GDateTime) created = NULL;
     g_autoptr(GDateTime) expires = NULL;
-    guint flags;
+    unsigned int flags;
 
     g_return_if_fail (SEAHORSE_GPGME_IS_SUBKEY (self));
     g_return_if_fail (subkey);
 
     /* Make sure that this subkey is in the pubkey */
+    parent = SEAHORSE_GPGME_KEY (seahorse_pgp_subkey_get_parent_key (base));
+    pubkey = seahorse_gpgme_key_get_public (parent);
     index = -1;
-    for (i = 0, sub = self->pubkey->subkeys; sub; ++i, sub = sub->next) {
+    for (i = 0, sub = pubkey->subkeys; sub; ++i, sub = sub->next) {
         if (sub == subkey) {
             index = i;
             break;
@@ -94,15 +87,13 @@ seahorse_gpgme_subkey_set_subkey (SeahorseGpgmeSubkey *self, gpgme_subkey_t subk
 
     /* Additional properties */
     fingerprint = seahorse_pgp_subkey_calc_fingerprint (subkey->fpr);
-    name = seahorse_gpgme_uid_calc_name (self->pubkey->uids);
+    name = seahorse_gpgme_uid_calc_name (pubkey->uids);
     description = seahorse_pgp_subkey_calc_description (name, index);
 
     self->subkey = subkey;
 
-    obj = G_OBJECT (self);
-    g_object_freeze_notify (obj);
+    g_object_freeze_notify (G_OBJECT (self));
 
-    base = SEAHORSE_PGP_SUBKEY (self);
     seahorse_pgp_subkey_set_index (base, index);
     seahorse_pgp_subkey_set_keyid (base, subkey->keyid);
     seahorse_pgp_subkey_set_algorithm (base, algo_type);
@@ -138,8 +129,8 @@ seahorse_gpgme_subkey_set_subkey (SeahorseGpgmeSubkey *self, gpgme_subkey_t subk
 
     seahorse_pgp_subkey_set_flags (base, flags);
 
-    g_object_notify (obj, "subkey");
-    g_object_thaw_notify (obj);
+    g_object_notify (G_OBJECT (self), "subkey");
+    g_object_thaw_notify (G_OBJECT (self));
 }
 
 static void
@@ -147,31 +138,15 @@ seahorse_gpgme_subkey_init (SeahorseGpgmeSubkey *self)
 {
 }
 
-static GObject*
-seahorse_gpgme_subkey_constructor (GType type, guint n_props, GObjectConstructParam *props)
-{
-    GObject *obj;
-    SeahorseGpgmeSubkey *self = NULL;
-
-    obj = G_OBJECT_CLASS (seahorse_gpgme_subkey_parent_class)->constructor (type, n_props, props);
-    if (obj) {
-        self = SEAHORSE_GPGME_SUBKEY (obj);
-        g_return_val_if_fail (self->pubkey, NULL);
-    }
-
-    return obj;
-}
-
 static void
-seahorse_gpgme_subkey_get_property (GObject *object, guint prop_id,
-                                  GValue *value, GParamSpec *pspec)
+seahorse_gpgme_subkey_get_property (GObject      *object,
+                                    unsigned int  prop_id,
+                                    GValue       *value,
+                                    GParamSpec *pspec)
 {
     SeahorseGpgmeSubkey *self = SEAHORSE_GPGME_SUBKEY (object);
 
     switch (prop_id) {
-    case PROP_PUBKEY:
-        g_value_set_boxed (value, seahorse_gpgme_subkey_get_pubkey (self));
-        break;
     case PROP_SUBKEY:
         g_value_set_pointer (value, seahorse_gpgme_subkey_get_subkey (self));
         break;
@@ -179,18 +154,14 @@ seahorse_gpgme_subkey_get_property (GObject *object, guint prop_id,
 }
 
 static void
-seahorse_gpgme_subkey_set_property (GObject *object, guint prop_id, const GValue *value,
-                                  GParamSpec *pspec)
+seahorse_gpgme_subkey_set_property (GObject      *object,
+                                    unsigned int  prop_id,
+                                    const GValue *value,
+                                    GParamSpec   *pspec)
 {
     SeahorseGpgmeSubkey *self = SEAHORSE_GPGME_SUBKEY (object);
 
     switch (prop_id) {
-    case PROP_PUBKEY:
-        g_return_if_fail (!self->pubkey);
-        self->pubkey = g_value_get_boxed (value);
-        if (self->pubkey)
-            gpgme_key_ref (self->pubkey);
-        break;
     case PROP_SUBKEY:
         seahorse_gpgme_subkey_set_subkey (self, g_value_get_pointer (value));
         break;
@@ -202,10 +173,6 @@ seahorse_gpgme_subkey_finalize (GObject *gobject)
 {
     SeahorseGpgmeSubkey *self = SEAHORSE_GPGME_SUBKEY (gobject);
 
-    /* Unref the key */
-    if (self->pubkey)
-        gpgme_key_unref (self->pubkey);
-    self->pubkey = NULL;
     self->subkey = NULL;
 
     G_OBJECT_CLASS (seahorse_gpgme_subkey_parent_class)->finalize (gobject);
@@ -216,25 +183,23 @@ seahorse_gpgme_subkey_class_init (SeahorseGpgmeSubkeyClass *klass)
 {
     GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
 
-    gobject_class->constructor = seahorse_gpgme_subkey_constructor;
     gobject_class->finalize = seahorse_gpgme_subkey_finalize;
     gobject_class->set_property = seahorse_gpgme_subkey_set_property;
     gobject_class->get_property = seahorse_gpgme_subkey_get_property;
 
-    g_object_class_install_property (gobject_class, PROP_PUBKEY,
-        g_param_spec_boxed ("pubkey", "Public Key", "GPGME Public Key that this subkey is on",
-                            SEAHORSE_GPGME_BOXED_KEY,
-                            G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY));
-
     g_object_class_install_property (gobject_class, PROP_SUBKEY,
         g_param_spec_pointer ("subkey", "Subkey", "GPGME Subkey",
                               G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
 }
 
 SeahorseGpgmeSubkey*
-seahorse_gpgme_subkey_new (gpgme_key_t pubkey, gpgme_subkey_t subkey)
+seahorse_gpgme_subkey_new (SeahorseGpgmeKey *parent_key,
+                           gpgme_subkey_t    subkey)
 {
+    g_return_val_if_fail (SEAHORSE_GPGME_IS_KEY (parent_key), NULL);
+    g_return_val_if_fail (subkey, NULL);
+
     return g_object_new (SEAHORSE_GPGME_TYPE_SUBKEY,
-                         "pubkey", pubkey,
+                         "parent-key", parent_key,
                          "subkey", subkey, NULL);
 }
diff --git a/pgp/seahorse-gpgme-subkey.h b/pgp/seahorse-gpgme-subkey.h
index 248a269d..670fc670 100644
--- a/pgp/seahorse-gpgme-subkey.h
+++ b/pgp/seahorse-gpgme-subkey.h
@@ -2,6 +2,7 @@
  * Seahorse
  *
  * Copyright (C) 2008 Stefan Walter
+ * Copyright (C) 2021 Niels De Graef
  *
  * 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
@@ -24,16 +25,15 @@
 #include <gpgme.h>
 
 #include "pgp/seahorse-pgp-subkey.h"
+#include "pgp/seahorse-gpgme-key.h"
 
 #define SEAHORSE_GPGME_TYPE_SUBKEY (seahorse_gpgme_subkey_get_type ())
 G_DECLARE_FINAL_TYPE (SeahorseGpgmeSubkey, seahorse_gpgme_subkey,
                       SEAHORSE_GPGME, SUBKEY,
                       SeahorsePgpSubkey)
 
-SeahorseGpgmeSubkey*  seahorse_gpgme_subkey_new               (gpgme_key_t pubkey,
-                                                               gpgme_subkey_t subkey);
-
-gpgme_key_t           seahorse_gpgme_subkey_get_pubkey        (SeahorseGpgmeSubkey *self);
+SeahorseGpgmeSubkey*  seahorse_gpgme_subkey_new               (SeahorseGpgmeKey *parent_key,
+                                                               gpgme_subkey_t    subkey);
 
 gpgme_subkey_t        seahorse_gpgme_subkey_get_subkey        (SeahorseGpgmeSubkey *self);
 
diff --git a/pgp/seahorse-pgp-key-properties.c b/pgp/seahorse-pgp-key-properties.c
index 02551e94..46c97acd 100644
--- a/pgp/seahorse-pgp-key-properties.c
+++ b/pgp/seahorse-pgp-key-properties.c
@@ -40,6 +40,7 @@
 #include "seahorse-pgp-uid.h"
 #include "seahorse-pgp-signature.h"
 #include "seahorse-pgp-subkey.h"
+#include "seahorse-pgp-subkey-list-box.h"
 
 #include "seahorse-common.h"
 
@@ -88,7 +89,7 @@ struct _SeahorsePgpKeyProperties {
     GtkLabel *details_strength_label;
     GtkLabel *details_expires_label;
     GtkComboBox *details_trust_combobox;
-    GtkTreeView *details_subkey_tree;
+    GtkWidget *subkeys_container;
 
     /* Private key widgets */
     GtkTreeView *names_tree;
@@ -949,28 +950,6 @@ const GType trust_columns[] = {
     G_TYPE_INT      /* validity */
 };
 
-static SeahorsePgpSubkey*
-get_selected_subkey (SeahorsePgpKeyProperties *self)
-{
-    return get_selected_object (self->details_subkey_tree, SUBKEY_OBJECT);
-}
-
-static void
-details_subkey_selected (GtkTreeSelection *selection, SeahorsePgpKeyProperties *self)
-{
-    SeahorsePgpSubkey* subkey;
-    guint flags = 0;
-
-    subkey = get_selected_object (self->details_subkey_tree, SUBKEY_OBJECT);
-    if (subkey)
-        flags = seahorse_pgp_subkey_get_flags (subkey);
-
-    set_action_enabled (self, "subkeys.change-expires", subkey != NULL);
-    set_action_enabled (self, "subkeys.revoke",
-                        subkey != NULL && !(flags & SEAHORSE_FLAG_REVOKED));
-    set_action_enabled (self, "subkeys.delete", subkey != NULL);
-}
-
 static void
 on_add_subkey_completed (GObject *object, GAsyncResult *res, gpointer user_data)
 {
@@ -1014,81 +993,6 @@ on_subkeys_add (GSimpleAction *action, GVariant *param, gpointer user_data)
     gtk_widget_destroy (GTK_WIDGET (dialog));
 }
 
-static void
-on_subkeys_delete (GSimpleAction *action, GVariant *param, gpointer user_data)
-{
-    SeahorsePgpKeyProperties *self = SEAHORSE_PGP_KEY_PROPERTIES (user_data);
-    SeahorsePgpSubkey *subkey;
-    guint index;
-    gboolean ret;
-    const gchar *label;
-    g_autofree gchar *message = NULL;
-    gpgme_error_t err;
-
-    subkey = get_selected_subkey (self);
-    if (!subkey)
-        return;
-
-    g_return_if_fail (SEAHORSE_GPGME_IS_SUBKEY (subkey));
-
-    index = seahorse_pgp_subkey_get_index (subkey);
-    label = seahorse_object_get_label (SEAHORSE_OBJECT (self->key));
-    message = g_strdup_printf (_("Are you sure you want to permanently delete subkey %d of %s?"), index, 
label);
-    ret = seahorse_delete_dialog_prompt (GTK_WINDOW (self), message);
-
-    if (ret == FALSE)
-        return;
-
-    err = seahorse_gpgme_key_op_del_subkey (SEAHORSE_GPGME_SUBKEY (subkey));
-    if (!GPG_IS_OK (err))
-        seahorse_gpgme_handle_error (err, _("Couldn’t delete subkey"));
-}
-
-static void
-on_subkeys_revoke (GSimpleAction *action, GVariant *param, gpointer user_data)
-{
-    SeahorsePgpKeyProperties *self = SEAHORSE_PGP_KEY_PROPERTIES (user_data);
-    SeahorsePgpSubkey *subkey = get_selected_subkey (self);
-    GtkWidget *dialog;
-
-    if (!subkey)
-        return;
-
-    g_return_if_fail (SEAHORSE_GPGME_IS_SUBKEY (subkey));
-    dialog = seahorse_gpgme_revoke_dialog_new (SEAHORSE_GPGME_SUBKEY (subkey),
-                                               GTK_WINDOW (self));
-    gtk_dialog_run (GTK_DIALOG (dialog));
-    gtk_widget_destroy (dialog);
-}
-
-static void
-on_subkeys_change_expires (GSimpleAction *action, GVariant *param, gpointer user_data)
-{
-    SeahorsePgpKeyProperties *self = SEAHORSE_PGP_KEY_PROPERTIES (user_data);
-    g_autoptr(SeahorsePgpSubkey) subkey = NULL;
-    GtkDialog *dialog;
-
-    subkey = get_selected_subkey (self);
-    if (subkey == NULL) {
-        GListModel *subkeys;
-
-        subkeys = seahorse_pgp_key_get_subkeys (self->key);
-        subkey = g_list_model_get_item (subkeys, 0);
-    } else {
-        g_object_ref (subkey);
-    }
-
-    g_return_if_fail (SEAHORSE_GPGME_IS_SUBKEY (subkey));
-
-    if (subkey == NULL)
-        return;
-
-    dialog = seahorse_gpgme_expires_dialog_new (SEAHORSE_GPGME_SUBKEY (subkey),
-                                                GTK_WINDOW (self));
-    gtk_dialog_run (dialog);
-    gtk_widget_destroy (GTK_WIDGET (dialog));
-}
-
 static void
 on_pgp_details_trust_changed (GtkComboBox *selection, gpointer user_data)
 {
@@ -1237,7 +1141,6 @@ setup_trust_combobox (SeahorsePgpKeyProperties *self)
 static void
 do_details (SeahorsePgpKeyProperties *self)
 {
-    GtkListStore *store;
     GtkTreeModel *model;
     GtkTreeIter iter;
     char dbuffer[G_ASCII_DTOSTR_BUF_SIZE];
@@ -1310,85 +1213,6 @@ do_details (SeahorsePgpKeyProperties *self)
 
         valid = gtk_tree_model_iter_next (model, &iter);
     }
-
-    /* Clear/create table store */
-    store = GTK_LIST_STORE (gtk_tree_view_get_model (self->details_subkey_tree));
-    if (store) {
-        gtk_list_store_clear (store);
-    } else {
-
-        /* This is our first time so create a store */
-        store = gtk_list_store_newv (SUBKEY_N_COLUMNS, (GType*)subkey_columns);
-        gtk_tree_view_set_model (self->details_subkey_tree, GTK_TREE_MODEL (store));
-
-        /* Make the columns for the view */
-        gtk_tree_view_insert_column_with_attributes (self->details_subkey_tree,
-                                                     -1, _("ID"), gtk_cell_renderer_text_new (),
-                                                     "text", SUBKEY_ID, NULL);
-        gtk_tree_view_insert_column_with_attributes (self->details_subkey_tree,
-                                                     -1, _("Type"), gtk_cell_renderer_text_new (),
-                                                     "text", SUBKEY_TYPE, NULL);
-        gtk_tree_view_insert_column_with_attributes (self->details_subkey_tree,
-                                                     -1, _("Usage"), gtk_cell_renderer_text_new (),
-                                                     "text", SUBKEY_USAGE, NULL);
-        gtk_tree_view_insert_column_with_attributes (self->details_subkey_tree,
-                                                     -1, _("Created"), gtk_cell_renderer_text_new (),
-                                                     "text", SUBKEY_CREATED, NULL);
-        gtk_tree_view_insert_column_with_attributes (self->details_subkey_tree,
-                                                     -1, _("Expires"), gtk_cell_renderer_text_new (),
-                                                     "text", SUBKEY_EXPIRES, NULL);
-        gtk_tree_view_insert_column_with_attributes (self->details_subkey_tree,
-                                                     -1, _("Status"), gtk_cell_renderer_text_new (),
-                                                     "text", SUBKEY_STATUS, NULL);
-        gtk_tree_view_insert_column_with_attributes (self->details_subkey_tree,
-                                                     -1, _("Strength"), gtk_cell_renderer_text_new (),
-                                                     "text", SUBKEY_LENGTH, NULL);
-    }
-
-    for (guint i = 0; i < g_list_model_get_n_items (subkeys); i++) {
-        g_autoptr(SeahorsePgpSubkey) subkey = NULL;
-        const char *status = NULL;
-        g_autofree char *expiration_date = NULL;
-        g_autofree char *created_date = NULL;
-        g_autofree char *usage = NULL;
-        GDateTime *expires;
-        guint flags;
-
-        subkey = g_list_model_get_item (subkeys, i);
-        expires = seahorse_pgp_subkey_get_expires (subkey);
-        flags = seahorse_pgp_subkey_get_flags (subkey);
-        status = "";
-
-        if (flags & SEAHORSE_FLAG_REVOKED)
-            status = _("Revoked");
-        else if (flags & SEAHORSE_FLAG_EXPIRED)
-            status = _("Expired");
-        else if (flags & SEAHORSE_FLAG_DISABLED)
-            status = _("Disabled");
-        else if (flags & SEAHORSE_FLAG_IS_VALID)
-            status = _("Good");
-
-        if (!expires)
-            expiration_date = g_strdup (C_("Expires", "Never"));
-        else
-            expiration_date = g_date_time_format (expires, "%x");
-
-        created_date = g_date_time_format (seahorse_pgp_subkey_get_created (subkey), "%x");
-
-        usage = seahorse_pgp_subkey_get_usage (subkey);
-
-        gtk_list_store_append (store, &iter);
-        gtk_list_store_set (store, &iter,
-                            SUBKEY_OBJECT, subkey,
-                            SUBKEY_ID, seahorse_pgp_subkey_get_keyid (subkey),
-                            SUBKEY_TYPE, seahorse_pgp_subkey_get_algorithm (subkey),
-                            SUBKEY_USAGE, usage,
-                            SUBKEY_CREATED, created_date,
-                            SUBKEY_EXPIRES, expiration_date,
-                            SUBKEY_STATUS, status,
-                            SUBKEY_LENGTH, seahorse_pgp_subkey_get_length (subkey),
-                            -1);
-    }
 }
 
 /* -----------------------------------------------------------------------------
@@ -1716,9 +1540,6 @@ static const GActionEntry PRIVATE_KEY_ACTIONS[] = {
     { "photos.next",          on_photos_next          },
     { "photos.make-primary",  on_photos_make_primary  },
     { "subkeys.add",             on_subkeys_add             },
-    { "subkeys.delete",          on_subkeys_delete          },
-    { "subkeys.revoke",          on_subkeys_revoke          },
-    { "subkeys.change-expires",  on_subkeys_change_expires  },
 };
 
 static const GActionEntry PUBLIC_KEY_ACTIONS[] = {
@@ -1742,6 +1563,8 @@ key_notify (GObject *object, GParamSpec *pspec, gpointer user_data)
 static void
 get_common_widgets (SeahorsePgpKeyProperties *self, GtkBuilder *builder)
 {
+    GtkWidget *subkeys_listbox;
+
     self->owner_name_label = GTK_LABEL (gtk_builder_get_object (builder, "owner-name-label"));
     self->owner_email_label = GTK_LABEL (gtk_builder_get_object (builder, "owner-email-label"));
     self->owner_comment_label = GTK_LABEL (gtk_builder_get_object (builder, "owner-comment-label"));
@@ -1760,7 +1583,7 @@ get_common_widgets (SeahorsePgpKeyProperties *self, GtkBuilder *builder)
     self->details_strength_label = GTK_LABEL (gtk_builder_get_object (builder, "details-strength-label"));
     self->details_expires_label = GTK_LABEL (gtk_builder_get_object (builder, "details-expires-label"));
     self->details_trust_combobox = GTK_COMBO_BOX (gtk_builder_get_object (builder, 
"details-trust-combobox"));
-    self->details_subkey_tree = GTK_TREE_VIEW (gtk_builder_get_object (builder, "details-subkey-tree"));
+    self->subkeys_container = GTK_WIDGET (gtk_builder_get_object (builder, "subkeys_container"));
 
     g_signal_connect_object (self->photo_event_box, "scroll-event",
                              G_CALLBACK (on_pgp_owner_photoid_button),
@@ -1768,6 +1591,11 @@ get_common_widgets (SeahorsePgpKeyProperties *self, GtkBuilder *builder)
     g_signal_connect_object (self->details_trust_combobox, "changed",
                              G_CALLBACK (on_pgp_details_trust_changed),
                              self, 0);
+
+    subkeys_listbox = seahorse_pgp_subkey_list_box_new (self->key);
+    gtk_widget_show (subkeys_listbox);
+    gtk_container_add (GTK_CONTAINER (self->subkeys_container),
+                       subkeys_listbox);
 }
 
 static void
@@ -1888,11 +1716,6 @@ create_private_key_dialog (SeahorsePgpKeyProperties *self)
                        target_list, G_N_ELEMENTS (target_list),
                        GDK_ACTION_COPY);
 
-    /* Enable and disable buttons as subkeys are selected */
-    g_signal_connect (gtk_tree_view_get_selection (self->details_subkey_tree),
-        "changed", G_CALLBACK (details_subkey_selected), self);
-    details_subkey_selected (NULL, self);
-
     /* Enable and disable buttons as UIDs are selected */
     selection = gtk_tree_view_get_selection (self->names_tree);
     g_signal_connect (selection, "changed", G_CALLBACK (update_names), self);
diff --git a/pgp/seahorse-pgp-key.c b/pgp/seahorse-pgp-key.c
index 48768f11..df7920ff 100644
--- a/pgp/seahorse-pgp-key.c
+++ b/pgp/seahorse-pgp-key.c
@@ -380,6 +380,7 @@ seahorse_pgp_key_add_subkey (SeahorsePgpKey    *self,
             return;
     }
 
+    seahorse_pgp_subkey_set_parent_key (subkey, self);
     g_list_store_append (G_LIST_STORE (priv->subkeys), subkey);
 }
 
diff --git a/pgp/seahorse-pgp-private-key-properties.ui b/pgp/seahorse-pgp-private-key-properties.ui
index 071a1e95..f65e83f7 100644
--- a/pgp/seahorse-pgp-private-key-properties.ui
+++ b/pgp/seahorse-pgp-private-key-properties.ui
@@ -922,156 +922,38 @@
                   </object>
                 </child>
                 <child>
-                  <object class="GtkExpander" id="subkey-expander">
+                  <object class="GtkBox" id="subkeys_container">
                     <property name="visible">True</property>
-                    <property name="can_focus">True</property>
-                    <property name="expanded">True</property>
+                    <property name="orientation">vertical</property>
+                    <property name="spacing">6</property>
                     <child>
                       <object class="GtkBox">
                         <property name="visible">True</property>
                         <property name="orientation">horizontal</property>
                         <child>
-                          <object class="GtkBox">
+                          <object class="GtkLabel">
                             <property name="visible">True</property>
-                            <property name="orientation">vertical</property>
-                            <property name="margin">12</property>
-                            <child>
-                              <object class="GtkButton" id="details-add-button">
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
-                                <property name="receives_default">False</property>
-                                <property name="border_width">6</property>
-                                <property name="action-name">props.subkeys.add</property>
-                                <child>
-                                  <object class="GtkBox">
-                                    <property name="visible">True</property>
-                                    <property name="orientation">horizontal</property>
-                                    <property name="halign">center</property>
-                                    <property name="spacing">2</property>
-                                    <child>
-                                      <object class="GtkImage">
-                                        <property name="visible">True</property>
-                                        <property name="stock">gtk-add</property>
-                                      </object>
-                                    </child>
-                                    <child>
-                                      <object class="GtkLabel">
-                                        <property name="visible">True</property>
-                                        <property name="label" translatable="yes">Add</property>
-                                        <property name="use_underline">True</property>
-                                      </object>
-                                    </child>
-                                  </object>
-                                </child>
-                              </object>
-                            </child>
-                            <child>
-                              <object class="GtkButton" id="details-date-button">
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
-                                <property name="receives_default">False</property>
-                                <property name="border_width">6</property>
-                                <property name="action-name">props.subkeys.change-expires</property>
-                                <child>
-                                  <object class="GtkBox">
-                                    <property name="visible">True</property>
-                                    <property name="orientation">horizontal</property>
-                                    <property name="halign">center</property>
-                                    <property name="spacing">2</property>
-                                    <child>
-                                      <object class="GtkImage">
-                                        <property name="visible">True</property>
-                                        <property name="icon_name">x-office-calendar</property>
-                                      </object>
-                                    </child>
-                                    <child>
-                                      <object class="GtkLabel">
-                                        <property name="visible">True</property>
-                                        <property name="label" translatable="yes">Expire</property>
-                                        <property name="use_underline">True</property>
-                                      </object>
-                                    </child>
-                                  </object>
-                                </child>
-                              </object>
-                            </child>
-                            <child>
-                              <object class="GtkButton" id="details-revoke-button">
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
-                                <property name="receives_default">False</property>
-                                <property name="border_width">6</property>
-                                <property name="action-name">props.subkeys.revoke</property>
-                                <child>
-                                  <object class="GtkBox">
-                                    <property name="visible">True</property>
-                                    <property name="orientation">horizontal</property>
-                                    <property name="halign">center</property>
-                                    <property name="spacing">2</property>
-                                    <child>
-                                      <object class="GtkImage">
-                                        <property name="visible">True</property>
-                                        <property name="stock">gtk-close</property>
-                                      </object>
-                                    </child>
-                                    <child>
-                                      <object class="GtkLabel">
-                                        <property name="visible">True</property>
-                                        <property name="label" translatable="yes">Revoke</property>
-                                        <property name="use_underline">True</property>
-                                      </object>
-                                    </child>
-                                  </object>
-                                </child>
-                              </object>
-                            </child>
-                            <child>
-                              <object class="GtkButton" id="details-delete-button">
-                                <property name="label">gtk-delete</property>
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
-                                <property name="receives_default">False</property>
-                                <property name="border_width">6</property>
-                                <property name="use_stock">True</property>
-                                <property name="action-name">props.subkeys.delete</property>
-                              </object>
-                            </child>
+                            <property name="hexpand">True</property>
+                            <property name="xalign">0</property>
+                            <property name="label" translatable="yes">Subkeys</property>
+                            <attributes>
+                              <attribute name="weight" value="bold"/>
+                            </attributes>
                           </object>
                         </child>
                         <child>
-                          <object class="GtkScrolledWindow">
+                          <object class="GtkButton">
                             <property name="visible">True</property>
                             <property name="can_focus">True</property>
-                            <property name="hscrollbar_policy">automatic</property>
-                            <property name="vscrollbar_policy">automatic</property>
-                            <property name="shadow_type">in</property>
-                            <child>
-                              <object class="GtkTreeView" id="details-subkey-tree">
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
-                              </object>
-                            </child>
+                            <property name="receives_default">False</property>
+                            <property name="action-name">props.subkeys.add</property>
+                            <property name="use-underline">True</property>
+                            <property name="label" translatable="yes">_Add subkey</property>
                           </object>
-                          <packing>
-                            <property name="expand">True</property>
-                          </packing>
                         </child>
                       </object>
                     </child>
-                    <child type="label">
-                      <object class="GtkLabel">
-                        <property name="visible">True</property>
-                        <property name="label" translatable="yes">_Subkeys</property>
-                        <property name="use_underline">True</property>
-                        <attributes>
-                         <attribute name="weight" value="bold"/>
-                        </attributes>
-                      </object>
-                    </child>
                   </object>
-                  <packing>
-                    <property name="position">2</property>
-                  </packing>
                 </child>
               </object>
             </child>
diff --git a/pgp/seahorse-pgp-public-key-properties.ui b/pgp/seahorse-pgp-public-key-properties.ui
index 7b21ef48..9b861ad9 100644
--- a/pgp/seahorse-pgp-public-key-properties.ui
+++ b/pgp/seahorse-pgp-public-key-properties.ui
@@ -869,40 +869,27 @@ B4C3 4349 0932 7854 3452</property>
                   </object>
                 </child>
                 <child>
-                  <object class="GtkExpander" id="subkey-expander">
+                  <object class="GtkBox" id="subkeys_container">
                     <property name="visible">True</property>
-                    <property name="can_focus">True</property>
+                    <property name="orientation">vertical</property>
+                    <property name="spacing">6</property>
                     <child>
-                      <object class="GtkScrolledWindow">
+                      <object class="GtkBox">
                         <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="shadow_type">in</property>
-                        <property name="vexpand">True</property>
-                        <property name="margin-top">6</property>
-                        <property name="margin-start">12</property>
+                        <property name="orientation">horizontal</property>
                         <child>
-                          <object class="GtkTreeView" id="details-subkey-tree">
-                            <property name="height_request">100</property>
+                          <object class="GtkLabel">
                             <property name="visible">True</property>
-                            <property name="can_focus">True</property>
-                            <child internal-child="selection">
-                              <object class="GtkTreeSelection" id="treeview-selection3"/>
-                            </child>
+                            <property name="hexpand">True</property>
+                            <property name="xalign">0</property>
+                            <property name="label" translatable="yes">Subkeys</property>
+                            <attributes>
+                              <attribute name="weight" value="bold"/>
+                            </attributes>
                           </object>
                         </child>
                       </object>
                     </child>
-                    <child type="label">
-                      <object class="GtkLabel">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="label" translatable="yes">_Subkeys</property>
-                        <property name="use_underline">True</property>
-                        <attributes>
-                          <attribute name="weight" value="bold"/>
-                        </attributes>
-                      </object>
-                    </child>
                   </object>
                 </child>
               </object>
diff --git a/pgp/seahorse-pgp-subkey-list-box-row.ui b/pgp/seahorse-pgp-subkey-list-box-row.ui
new file mode 100644
index 00000000..9b1d0ba8
--- /dev/null
+++ b/pgp/seahorse-pgp-subkey-list-box-row.ui
@@ -0,0 +1,233 @@
+<?xml version="1.0"?>
+<interface>
+  <requires lib="gtk+" version="3.24"/>
+  <menu id="subkey_menu">
+    <section>
+      <item>
+        <attribute name="label" translatable="yes">Change expiration date</attribute>
+        <attribute name="action">subkey.change-expires</attribute>
+      </item>
+      <item>
+        <attribute name="label" translatable="yes">Revoke subkey</attribute>
+        <attribute name="action">subkey.revoke</attribute>
+      </item>
+      <item>
+        <attribute name="label" translatable="yes">Delete subkey</attribute>
+        <attribute name="action">subkey.delete</attribute>
+      </item>
+    </section>
+  </menu>
+  <template class="SeahorsePgpSubkeyListBoxRow" parent="GtkListBoxRow">
+    <property name="visible">True</property>
+    <child>
+      <object class="GtkGrid">
+        <property name="visible">True</property>
+        <property name="column-spacing">12</property>
+        <property name="margin">6</property>
+        <child>
+          <object class="GtkLabel" id="keyid_label">
+            <property name="visible">True</property>
+            <property name="selectable">True</property>
+            <property name="xalign">0</property>
+          </object>
+          <packing>
+            <property name="top_attach">0</property>
+            <property name="left_attach">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkBox" id="usages_box">
+            <property name="visible">True</property>
+            <property name="hexpand">True</property>
+            <property name="spacing">3</property>
+            <property name="orientation">horizontal</property>
+          </object>
+          <packing>
+            <property name="top_attach">0</property>
+            <property name="left_attach">2</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkImage" id="status_box">
+            <property name="visible">True</property>
+            <property name="icon-name">emblem-important-symbolic</property>
+            <style>
+              <class name="pgp-subkey-status-box" />
+            </style>
+          </object>
+          <packing>
+            <property name="top_attach">0</property>
+            <property name="left_attach">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkLabel" id="expires_label">
+            <property name="visible">True</property>
+            <property name="xalign">0</property>
+          </object>
+          <packing>
+            <property name="top_attach">0</property>
+            <property name="left_attach">3</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkRevealer" id="revealer">
+            <property name="visible">True</property>
+            <child>
+              <object class="GtkBox">
+                <property name="visible">True</property>
+                <property name="orientation">vertical</property>
+                <child>
+                  <object class="GtkSeparator">
+                    <property name="visible">True</property>
+                    <property name="margin">12</property>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkGrid">
+                    <property name="visible">True</property>
+                    <property name="row-spacing">6</property>
+                    <property name="column-spacing">6</property>
+                    <child>
+                      <object class="GtkLabel">
+                        <property name="visible">True</property>
+                        <property name="xalign">1</property>
+                        <property name="label" translatable="yes">Fingerprint</property>
+                        <style>
+                          <class name="dim-label"/>
+                        </style>
+                      </object>
+                      <packing>
+                        <property name="top_attach">0</property>
+                        <property name="left_attach">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="fingerprint_label">
+                        <property name="visible">True</property>
+                        <property name="selectable">True</property>
+                        <property name="xalign">0</property>
+                      </object>
+                      <packing>
+                        <property name="top_attach">0</property>
+                        <property name="left_attach">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel">
+                        <property name="visible">True</property>
+                        <property name="xalign">1</property>
+                        <property name="label" translatable="yes">Type</property>
+                        <style>
+                          <class name="dim-label"/>
+                        </style>
+                      </object>
+                      <packing>
+                        <property name="top_attach">1</property>
+                        <property name="left_attach">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="algo_label">
+                        <property name="visible">True</property>
+                        <property name="xalign">0</property>
+                      </object>
+                      <packing>
+                        <property name="top_attach">1</property>
+                        <property name="left_attach">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel">
+                        <property name="visible">True</property>
+                        <property name="xalign">1</property>
+                        <property name="label" translatable="yes">Created</property>
+                        <style>
+                          <class name="dim-label"/>
+                        </style>
+                      </object>
+                      <packing>
+                        <property name="top_attach">2</property>
+                        <property name="left_attach">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="created_label">
+                        <property name="visible">True</property>
+                        <property name="xalign">0</property>
+                      </object>
+                      <packing>
+                        <property name="top_attach">2</property>
+                        <property name="left_attach">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkBox" id="action_box">
+                        <property name="visible">True</property>
+                        <property name="orientation">horizontal</property>
+                        <property name="spacing">3</property>
+                        <property name="margin">6</property>
+                        <child>
+                          <object class="GtkButton">
+                            <property name="visible">True</property>
+                            <property name="label" translatable="yes">Change expiry date</property>
+                            <property name="action-name">subkey.change-expires</property>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkButton">
+                            <property name="visible">True</property>
+                            <property name="label" translatable="yes">Revoke</property>
+                            <property name="action-name">subkey.revoke</property>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkButton">
+                            <property name="visible">True</property>
+                            <property name="label" translatable="yes">Delete</property>
+                            <property name="action-name">subkey.delete</property>
+                          </object>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="top_attach">3</property>
+                        <property name="left_attach">0</property>
+                        <property name="width">2</property>
+                      </packing>
+                    </child>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="top_attach">1</property>
+            <property name="left_attach">0</property>
+            <property name="width">5</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkButton">
+            <property name="visible">True</property>
+            <property name="halign">end</property>
+            <property name="action-name">subkey.toggle-reveal</property>
+            <child>
+              <object class="GtkImage" id="reveal_icon">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="icon_name">pan-down-symbolic</property>
+              </object>
+            </child>
+            <style>
+              <class name="flat"/>
+            </style>
+          </object>
+          <packing>
+            <property name="top_attach">0</property>
+            <property name="left_attach">4</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/pgp/seahorse-pgp-subkey-list-box.c b/pgp/seahorse-pgp-subkey-list-box.c
new file mode 100644
index 00000000..f694dc35
--- /dev/null
+++ b/pgp/seahorse-pgp-subkey-list-box.c
@@ -0,0 +1,464 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2020 - Niels De Graef
+ *
+ * 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 of the License, 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/>.
+ */
+
+#include "config.h"
+
+#include "seahorse-gpgme-expires-dialog.h"
+#include "seahorse-gpgme-key-op.h"
+#include "seahorse-gpgme-revoke-dialog.h"
+#include "seahorse-pgp-key.h"
+#include "seahorse-pgp-subkey-list-box.h"
+#include "seahorse-pgp-subkey.h"
+
+#include <glib/gi18n.h>
+
+struct _SeahorsePgpSubkeyListBox {
+    GtkListBox parent_instance;
+
+    SeahorsePgpKey *key;
+};
+
+enum {
+    PROP_0,
+    PROP_KEY,
+    N_PROPS
+};
+static GParamSpec *obj_props[N_PROPS] = { NULL, };
+
+G_DEFINE_TYPE (SeahorsePgpSubkeyListBox, seahorse_pgp_subkey_list_box, GTK_TYPE_LIST_BOX)
+
+static void
+seahorse_pgp_subkey_list_box_set_property (GObject      *object,
+                                           unsigned int  prop_id,
+                                           const GValue *value,
+                                           GParamSpec   *pspec)
+{
+    SeahorsePgpSubkeyListBox *self = SEAHORSE_PGP_SUBKEY_LIST_BOX (object);
+
+    switch (prop_id) {
+    case PROP_KEY:
+        g_set_object (&self->key, g_value_dup_object (value));
+        break;
+    }
+}
+
+static void
+seahorse_pgp_subkey_list_box_get_property (GObject      *object,
+                                           unsigned int  prop_id,
+                                           GValue       *value,
+                                           GParamSpec   *pspec)
+{
+    SeahorsePgpSubkeyListBox *self = SEAHORSE_PGP_SUBKEY_LIST_BOX (object);
+
+    switch (prop_id) {
+    case PROP_KEY:
+        g_value_set_object (value, seahorse_pgp_subkey_list_box_get_key (self));
+        break;
+    }
+}
+
+static GtkWidget *
+create_subkey_row (void *item,
+                   void *user_data)
+{
+    SeahorsePgpSubkey *subkey = SEAHORSE_PGP_SUBKEY (item);
+
+    return g_object_new (SEAHORSE_PGP_TYPE_SUBKEY_LIST_BOX_ROW,
+                         "subkey", subkey,
+                         NULL);
+}
+
+static void
+seahorse_pgp_subkey_list_box_constructed (GObject *obj)
+{
+    SeahorsePgpSubkeyListBox *self = SEAHORSE_PGP_SUBKEY_LIST_BOX (obj);
+
+    G_OBJECT_CLASS (seahorse_pgp_subkey_list_box_parent_class)->constructed (obj);
+
+    gtk_list_box_bind_model (GTK_LIST_BOX (self),
+                             seahorse_pgp_key_get_subkeys (self->key),
+                             create_subkey_row,
+                             self,
+                             NULL);
+}
+
+static void
+seahorse_pgp_subkey_list_box_init (SeahorsePgpSubkeyListBox *self)
+{
+    GtkStyleContext *style_context;
+
+    style_context = gtk_widget_get_style_context (GTK_WIDGET (self));
+    gtk_style_context_add_class (style_context, "content");
+}
+
+static void
+seahorse_pgp_subkey_list_box_finalize (GObject *obj)
+{
+    SeahorsePgpSubkeyListBox *self = SEAHORSE_PGP_SUBKEY_LIST_BOX (obj);
+
+    g_clear_object (&self->key);
+
+    G_OBJECT_CLASS (seahorse_pgp_subkey_list_box_parent_class)->finalize (obj);
+}
+
+static void
+seahorse_pgp_subkey_list_box_class_init (SeahorsePgpSubkeyListBoxClass *klass)
+{
+    GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+    gobject_class->get_property = seahorse_pgp_subkey_list_box_get_property;
+    gobject_class->set_property = seahorse_pgp_subkey_list_box_set_property;
+    gobject_class->constructed = seahorse_pgp_subkey_list_box_constructed;
+    gobject_class->finalize = seahorse_pgp_subkey_list_box_finalize;
+
+    obj_props[PROP_KEY] =
+        g_param_spec_object ("key", "Pgp Key", "The key to list subkeys for",
+                             SEAHORSE_PGP_TYPE_KEY,
+                             G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
+
+    g_object_class_install_properties (gobject_class, N_PROPS, obj_props);
+}
+
+GtkWidget *
+seahorse_pgp_subkey_list_box_new (SeahorsePgpKey *key)
+{
+    return g_object_new (SEAHORSE_PGP_TYPE_SUBKEY_LIST_BOX,
+                         "selection-mode", GTK_SELECTION_NONE,
+                         "key", key,
+                         NULL);
+}
+
+SeahorsePgpKey *
+seahorse_pgp_subkey_list_box_get_key (SeahorsePgpSubkeyListBox *self)
+{
+    g_return_val_if_fail (SEAHORSE_PGP_IS_SUBKEY_LIST_BOX (self), NULL);
+    return self->key;
+}
+
+/* SeahorsePhpSubkeyListBoxRow */
+
+struct _SeahorsePgpSubkeyListBoxRow {
+    GtkListBoxRow parent_instance;
+
+    SeahorsePgpSubkey *subkey;
+
+    GSimpleActionGroup *action_group;
+
+    GtkWidget *status_box;
+    GtkWidget *keyid_label;
+    GtkWidget *usages_box;
+    GtkWidget *revealer;
+    GtkWidget *reveal_icon;
+    GtkWidget *algo_label;
+    GtkWidget *expires_label;
+    GtkWidget *created_label;
+    GtkWidget *fingerprint_label;
+    GtkWidget *action_box;
+};
+
+enum {
+    ROW_PROP_0,
+    ROW_PROP_SUBKEY,
+    ROW_N_PROPS
+};
+static GParamSpec *row_props[ROW_N_PROPS] = { NULL, };
+
+G_DEFINE_TYPE (SeahorsePgpSubkeyListBoxRow, seahorse_pgp_subkey_list_box_row, GTK_TYPE_LIST_BOX_ROW)
+
+static GtkWindow *
+get_toplevel_window (SeahorsePgpSubkeyListBoxRow *row)
+{
+    GtkWidget *toplevel = NULL;
+
+    toplevel = gtk_widget_get_toplevel (GTK_WIDGET (row));
+    if (GTK_IS_WINDOW (toplevel))
+        return GTK_WINDOW (toplevel);
+
+    return NULL;
+}
+
+static void
+on_subkey_delete (GSimpleAction *action, GVariant *param, void *user_data)
+{
+    SeahorsePgpSubkeyListBoxRow *row = SEAHORSE_PGP_SUBKEY_LIST_BOX_ROW (user_data);
+    const char *fingerprint;
+    g_autofree char *message = NULL;
+    gpgme_error_t err;
+
+    g_return_if_fail (SEAHORSE_GPGME_IS_SUBKEY (row->subkey));
+
+    fingerprint = seahorse_pgp_subkey_get_fingerprint (row->subkey);
+    message = g_strdup_printf (_("Are you sure you want to permanently delete subkey %s?"), fingerprint);
+
+    if (!seahorse_delete_dialog_prompt (get_toplevel_window (row), message))
+        return;
+
+    err = seahorse_gpgme_key_op_del_subkey (SEAHORSE_GPGME_SUBKEY (row->subkey));
+    if (!GPG_IS_OK (err))
+        seahorse_gpgme_handle_error (err, _("Couldn’t delete subkey"));
+}
+
+static void
+on_subkey_revoke (GSimpleAction *action, GVariant *param, void *user_data)
+{
+    SeahorsePgpSubkeyListBoxRow *row = SEAHORSE_PGP_SUBKEY_LIST_BOX_ROW (user_data);
+    GtkWidget *dialog = NULL;
+
+    g_return_if_fail (SEAHORSE_GPGME_IS_SUBKEY (row->subkey));
+
+    dialog = seahorse_gpgme_revoke_dialog_new (SEAHORSE_GPGME_SUBKEY (row->subkey),
+                                               get_toplevel_window (row));
+    gtk_dialog_run (GTK_DIALOG (dialog));
+    gtk_widget_destroy (dialog);
+}
+
+static void
+on_subkey_change_expires (GSimpleAction *action, GVariant *param, void *user_data)
+{
+    SeahorsePgpSubkeyListBoxRow *row = SEAHORSE_PGP_SUBKEY_LIST_BOX_ROW (user_data);
+    GtkDialog *dialog;
+
+    g_return_if_fail (SEAHORSE_GPGME_IS_SUBKEY (row->subkey));
+
+    dialog = seahorse_gpgme_expires_dialog_new (SEAHORSE_GPGME_SUBKEY (row->subkey),
+                                                get_toplevel_window (row));
+    gtk_dialog_run (dialog);
+    gtk_widget_destroy (GTK_WIDGET (dialog));
+}
+
+static void
+on_subkey_toggle_reveal (GSimpleAction *action, GVariant *param, void *user_data)
+{
+    SeahorsePgpSubkeyListBoxRow *row = SEAHORSE_PGP_SUBKEY_LIST_BOX_ROW (user_data);
+    gboolean reveal;
+    const char *icon_name;
+
+    reveal = !gtk_revealer_get_reveal_child (GTK_REVEALER (row->revealer));
+    gtk_revealer_set_reveal_child (GTK_REVEALER (row->revealer), reveal);
+
+    icon_name = reveal? "pan-up-symbolic" : "pan-down-symbolic";
+    gtk_image_set_from_icon_name (GTK_IMAGE (row->reveal_icon),
+                                  icon_name,
+                                  GTK_ICON_SIZE_BUTTON);
+}
+
+static const GActionEntry SUBKEY_ACTIONS[] = {
+    { "toggle-reveal", on_subkey_toggle_reveal },
+    { "delete", on_subkey_delete },
+    { "revoke", on_subkey_revoke },
+    { "change-expires", on_subkey_change_expires },
+};
+
+static void
+update_row (SeahorsePgpSubkeyListBoxRow *row)
+{
+    unsigned int flags;
+    g_auto(GStrv) usages = NULL, descriptions = NULL;
+    g_autofree char *algo_str = NULL;
+    GDateTime *expires, *created;
+    g_autofree char *expires_str = NULL, *created_str = NULL;
+    SeahorsePgpKey *parent_key;
+
+    /* Start with the flags, since these can largely affect what we show */
+    flags = seahorse_pgp_subkey_get_flags (row->subkey);
+    if (flags & SEAHORSE_FLAG_REVOKED) {
+        GAction *action;
+
+        gtk_widget_show (row->status_box);
+        gtk_widget_set_tooltip_text (row->status_box, _("Subkey was revoked"));
+
+        action = g_action_map_lookup_action (G_ACTION_MAP (row->action_group),
+                                             "revoke");
+        g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE);
+    } else if (flags & SEAHORSE_FLAG_EXPIRED) {
+        gtk_widget_show (row->status_box);
+        gtk_widget_set_tooltip_text (row->status_box, _("Subkey has expired"));
+    } else if (flags & SEAHORSE_FLAG_DISABLED) {
+        gtk_widget_show (row->status_box);
+        gtk_widget_set_tooltip_text (row->status_box, _("Subkey is disabled"));
+    } else if (flags & SEAHORSE_FLAG_IS_VALID) {
+        gtk_widget_hide (row->status_box);
+    }
+
+    /* Set the key id */
+    gtk_label_set_text (GTK_LABEL (row->keyid_label),
+                        seahorse_pgp_subkey_get_keyid (row->subkey));
+
+    /* Add the usage tags */
+    usages = seahorse_pgp_subkey_get_usages (row->subkey, &descriptions);
+    for (unsigned i = 0; i < g_strv_length (usages); i++) {
+        GtkWidget *label;
+
+        label = gtk_label_new (usages[i]);
+        gtk_widget_set_tooltip_text (label, descriptions[i]);
+        gtk_widget_set_valign (label, GTK_ALIGN_CENTER);
+        gtk_widget_show (label);
+        gtk_box_pack_start (GTK_BOX (row->usages_box), label, FALSE, FALSE, 0);
+        gtk_style_context_add_class (gtk_widget_get_style_context (label),
+                                     "pgp-subkey-usage-label");
+    }
+
+    /* Expiration date */
+    expires = seahorse_pgp_subkey_get_expires (row->subkey);
+    expires_str = expires? g_date_time_format (expires, "Expires on %x")
+                         : g_strdup (_("Never expires"));
+    gtk_label_set_text (GTK_LABEL (row->expires_label), expires_str);
+
+    /* Stuff that is hidden by default (but can be shown with the revealer) */
+
+    /* Full fingerprint */
+    gtk_label_set_text (GTK_LABEL (row->fingerprint_label),
+                        seahorse_pgp_subkey_get_fingerprint (row->subkey));
+
+    /* Translators: first part is the algorithm, second part is the length,
+     * e.g. "RSA" (2048 bit) */
+    algo_str = g_strdup_printf (_("%s (%d bit)"),
+                                seahorse_pgp_subkey_get_algorithm (row->subkey),
+                                seahorse_pgp_subkey_get_length (row->subkey));
+    gtk_label_set_text (GTK_LABEL (row->algo_label), algo_str);
+
+    /* Creation date */
+    created = seahorse_pgp_subkey_get_created (row->subkey);
+    created_str = created? g_date_time_format (created, "%x") : g_strdup (_("Unknown"));
+    gtk_label_set_text (GTK_LABEL (row->created_label), created_str);
+
+    parent_key = seahorse_pgp_subkey_get_parent_key (SEAHORSE_PGP_SUBKEY (row->subkey));
+    if (seahorse_object_get_usage (SEAHORSE_OBJECT (parent_key)) != SEAHORSE_USAGE_PRIVATE_KEY)
+        gtk_widget_hide (row->action_box);
+}
+
+static void
+seahorse_pgp_subkey_list_box_row_set_property (GObject      *object,
+                                               unsigned int  prop_id,
+                                               const GValue *value,
+                                               GParamSpec   *pspec)
+{
+    SeahorsePgpSubkeyListBoxRow *row = SEAHORSE_PGP_SUBKEY_LIST_BOX_ROW (object);
+
+    switch (prop_id) {
+    case ROW_PROP_SUBKEY:
+        g_set_object (&row->subkey, g_value_dup_object (value));
+        break;
+    }
+}
+
+static void
+seahorse_pgp_subkey_list_box_row_get_property (GObject      *object,
+                                               unsigned int  prop_id,
+                                               GValue       *value,
+                                               GParamSpec   *pspec)
+{
+    SeahorsePgpSubkeyListBoxRow *row = SEAHORSE_PGP_SUBKEY_LIST_BOX_ROW (object);
+
+    switch (prop_id) {
+    case ROW_PROP_SUBKEY:
+        g_value_set_object (value, seahorse_pgp_subkey_list_box_row_get_subkey (row));
+        break;
+    }
+}
+
+static void
+on_subkey_notify (GObject *object, GParamSpec *pspec, void *user_data)
+{
+    SeahorsePgpSubkeyListBoxRow *row = SEAHORSE_PGP_SUBKEY_LIST_BOX_ROW (user_data);
+
+    update_row (row);
+}
+
+static void
+seahorse_pgp_subkey_list_box_row_constructed (GObject *obj)
+{
+    SeahorsePgpSubkeyListBoxRow *row = SEAHORSE_PGP_SUBKEY_LIST_BOX_ROW (obj);
+
+    G_OBJECT_CLASS (seahorse_pgp_subkey_list_box_row_parent_class)->constructed (obj);
+
+    g_signal_connect_object (row->subkey, "notify",
+                             G_CALLBACK (on_subkey_notify), row, 0);
+    update_row (row);
+}
+
+static void
+seahorse_pgp_subkey_list_box_row_init (SeahorsePgpSubkeyListBoxRow *row)
+{
+    GtkStyleContext *style_context;
+
+    gtk_widget_init_template (GTK_WIDGET (row));
+    style_context = gtk_widget_get_style_context (GTK_WIDGET (row));
+    gtk_style_context_add_class (style_context, "pgp-subkey-row");
+
+    row->action_group = g_simple_action_group_new ();
+    g_action_map_add_action_entries (G_ACTION_MAP (row->action_group),
+                                     SUBKEY_ACTIONS,
+                                     G_N_ELEMENTS (SUBKEY_ACTIONS),
+                                     row);
+    gtk_widget_insert_action_group (GTK_WIDGET (row),
+                                    "subkey",
+                                    G_ACTION_GROUP (row->action_group));
+}
+
+static void
+seahorse_pgp_subkey_list_box_row_finalize (GObject *obj)
+{
+    SeahorsePgpSubkeyListBoxRow *row = SEAHORSE_PGP_SUBKEY_LIST_BOX_ROW (obj);
+
+    g_clear_object (&row->subkey);
+    g_clear_object (&row->action_group);
+
+    G_OBJECT_CLASS (seahorse_pgp_subkey_list_box_row_parent_class)->finalize (obj);
+}
+
+static void
+seahorse_pgp_subkey_list_box_row_class_init (SeahorsePgpSubkeyListBoxRowClass *klass)
+{
+    GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+    GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+    gobject_class->get_property = seahorse_pgp_subkey_list_box_row_get_property;
+    gobject_class->set_property = seahorse_pgp_subkey_list_box_row_set_property;
+    gobject_class->constructed = seahorse_pgp_subkey_list_box_row_constructed;
+    gobject_class->finalize = seahorse_pgp_subkey_list_box_row_finalize;
+
+    row_props[ROW_PROP_SUBKEY] =
+        g_param_spec_object ("subkey", "PGP subkey", "The subkey this row shows",
+                             SEAHORSE_PGP_TYPE_SUBKEY,
+                             G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
+
+    g_object_class_install_properties (gobject_class, ROW_N_PROPS, row_props);
+
+    /* GtkWidget template */
+    gtk_widget_class_set_template_from_resource (widget_class, 
"/org/gnome/Seahorse/seahorse-pgp-subkey-list-box-row.ui");
+
+    gtk_widget_class_bind_template_child (widget_class, SeahorsePgpSubkeyListBoxRow, status_box);
+    gtk_widget_class_bind_template_child (widget_class, SeahorsePgpSubkeyListBoxRow, keyid_label);
+    gtk_widget_class_bind_template_child (widget_class, SeahorsePgpSubkeyListBoxRow, usages_box);
+    gtk_widget_class_bind_template_child (widget_class, SeahorsePgpSubkeyListBoxRow, revealer);
+    gtk_widget_class_bind_template_child (widget_class, SeahorsePgpSubkeyListBoxRow, reveal_icon);
+    gtk_widget_class_bind_template_child (widget_class, SeahorsePgpSubkeyListBoxRow, algo_label);
+    gtk_widget_class_bind_template_child (widget_class, SeahorsePgpSubkeyListBoxRow, expires_label);
+    gtk_widget_class_bind_template_child (widget_class, SeahorsePgpSubkeyListBoxRow, created_label);
+    gtk_widget_class_bind_template_child (widget_class, SeahorsePgpSubkeyListBoxRow, fingerprint_label);
+    gtk_widget_class_bind_template_child (widget_class, SeahorsePgpSubkeyListBoxRow, action_box);
+}
+
+SeahorsePgpSubkey *
+seahorse_pgp_subkey_list_box_row_get_subkey (SeahorsePgpSubkeyListBoxRow *row)
+{
+    g_return_val_if_fail (SEAHORSE_PGP_IS_SUBKEY_LIST_BOX_ROW (row), NULL);
+    return row->subkey;
+}
diff --git a/pgp/seahorse-pgp-subkey-list-box.h b/pgp/seahorse-pgp-subkey-list-box.h
new file mode 100644
index 00000000..56b658a1
--- /dev/null
+++ b/pgp/seahorse-pgp-subkey-list-box.h
@@ -0,0 +1,42 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2008 Stefan Walter
+ *
+ * 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 of the License, 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/>.
+ */
+
+#pragma once
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+#include "seahorse-common.h"
+#include "seahorse-pgp-types.h"
+
+#define SEAHORSE_PGP_TYPE_SUBKEY_LIST_BOX (seahorse_pgp_subkey_list_box_get_type ())
+G_DECLARE_FINAL_TYPE (SeahorsePgpSubkeyListBox, seahorse_pgp_subkey_list_box,
+                      SEAHORSE_PGP, SUBKEY_LIST_BOX,
+                      GtkListBox)
+
+GtkWidget *          seahorse_pgp_subkey_list_box_new                (SeahorsePgpKey *key);
+
+SeahorsePgpKey *     seahorse_pgp_subkey_list_box_get_key            (SeahorsePgpSubkeyListBox *self);
+
+#define SEAHORSE_PGP_TYPE_SUBKEY_LIST_BOX_ROW (seahorse_pgp_subkey_list_box_row_get_type ())
+G_DECLARE_FINAL_TYPE (SeahorsePgpSubkeyListBoxRow, seahorse_pgp_subkey_list_box_row,
+                      SEAHORSE_PGP, SUBKEY_LIST_BOX_ROW,
+                      GtkListBoxRow)
+
+SeahorsePgpSubkey *   seahorse_pgp_subkey_list_box_row_get_subkey    (SeahorsePgpSubkeyListBoxRow *self);
diff --git a/pgp/seahorse-pgp-subkey.c b/pgp/seahorse-pgp-subkey.c
index e4e6fdd0..dbc55c66 100644
--- a/pgp/seahorse-pgp-subkey.c
+++ b/pgp/seahorse-pgp-subkey.c
@@ -29,6 +29,7 @@
 
 enum {
     PROP_0,
+    PROP_PARENT_KEY,
     PROP_INDEX,
     PROP_KEYID,
     PROP_FLAGS,
@@ -43,6 +44,7 @@ enum {
 static GParamSpec *obj_props[N_PROPS] = { NULL, };
 
 typedef struct _SeahorsePgpSubkeyPrivate {
+    SeahorsePgpKey *parent_key;
     unsigned int index;
     char *keyid;
     unsigned int flags;
@@ -56,6 +58,28 @@ typedef struct _SeahorsePgpSubkeyPrivate {
 
 G_DEFINE_TYPE_WITH_PRIVATE (SeahorsePgpSubkey, seahorse_pgp_subkey, G_TYPE_OBJECT);
 
+SeahorsePgpKey *
+seahorse_pgp_subkey_get_parent_key (SeahorsePgpSubkey *self)
+{
+    SeahorsePgpSubkeyPrivate *priv = seahorse_pgp_subkey_get_instance_private (self);
+
+    g_return_val_if_fail (SEAHORSE_PGP_IS_SUBKEY (self), NULL);
+    return priv->parent_key;
+}
+
+void
+seahorse_pgp_subkey_set_parent_key (SeahorsePgpSubkey *self,
+                                    SeahorsePgpKey    *parent_key)
+{
+    SeahorsePgpSubkeyPrivate *priv = seahorse_pgp_subkey_get_instance_private (self);
+
+    g_return_if_fail (SEAHORSE_PGP_IS_SUBKEY (self));
+    g_return_if_fail (SEAHORSE_PGP_IS_KEY (parent_key));
+
+    priv->parent_key = parent_key;
+    g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_PARENT_KEY]);
+}
+
 unsigned int
 seahorse_pgp_subkey_get_index (SeahorsePgpSubkey *self)
 {
@@ -157,40 +181,62 @@ seahorse_pgp_subkey_set_length (SeahorsePgpSubkey *self, unsigned int length)
  */
 char *
 seahorse_pgp_subkey_get_usage (SeahorsePgpSubkey *self)
+{
+    g_auto(GStrv) usages = NULL;
+
+    g_return_val_if_fail (SEAHORSE_PGP_IS_SUBKEY (self), NULL);
+
+    usages = seahorse_pgp_subkey_get_usages (self, NULL);
+    return g_strjoinv (", ", usages);
+}
+
+/**
+ * seahorse_pgp_subkey_get_usages:
+ * @self: A #SeahorsePgpSubkey
+ * @decriptions: (transfer full) (array zero-terminated=1) (optional):
+ *               The descriptions of the subkey's usages
+ *
+ * Returns: (transfer full) (array zero-terminated=1): the subkey's usages,
+ * for example: { "Encrypt" , "Sign" , "Certify", %NULL }
+ */
+char **
+seahorse_pgp_subkey_get_usages (SeahorsePgpSubkey *self, char ***descriptions)
 {
     typedef struct {
         unsigned int flag;
         const char *name;
+        const char *description;
     } FlagNames;
 
     const FlagNames flag_names[] = {
-        { SEAHORSE_FLAG_CAN_ENCRYPT,      N_("Encrypt") },
-        { SEAHORSE_FLAG_CAN_SIGN,         N_("Sign") },
-        { SEAHORSE_FLAG_CAN_CERTIFY,      N_("Certify") },
-        { SEAHORSE_FLAG_CAN_AUTHENTICATE, N_("Authenticate") }
+        { SEAHORSE_FLAG_CAN_ENCRYPT, N_("Encrypt"), N_("This subkey can be used for encryption") },
+        { SEAHORSE_FLAG_CAN_SIGN, N_("Sign"), N_("This subkey can be used to create data signatures") },
+        { SEAHORSE_FLAG_CAN_CERTIFY, N_("Certify"), N_("This subkey can be used to create certificates") },
+        { SEAHORSE_FLAG_CAN_AUTHENTICATE, N_("Authenticate"), N_("This subkey can be used for 
authentication") }
     };
 
     SeahorsePgpSubkeyPrivate *priv = seahorse_pgp_subkey_get_instance_private (self);
-    GString *str;
-    gboolean previous;
-    unsigned int i;
+    g_autoptr(GPtrArray) names = NULL, descs = NULL;
 
     g_return_val_if_fail (SEAHORSE_PGP_IS_SUBKEY (self), NULL);
 
-    str = g_string_new (NULL);
-    previous = FALSE;
+    names = g_ptr_array_new_with_free_func (g_free);
+    descs = g_ptr_array_new_with_free_func (g_free);
 
-    for (i = 0; i < G_N_ELEMENTS (flag_names); i++) {
+    for (unsigned i = 0; i < G_N_ELEMENTS (flag_names); i++) {
         if (priv->flags & flag_names[i].flag) {
-            if (previous)
-                g_string_append (str, ", ");
-
-            previous = TRUE;
-            g_string_append (str, _(flag_names[i].name));
+            g_ptr_array_add (names, g_strdup (_(flag_names[i].name)));
+            g_ptr_array_add (descs, g_strdup (_(flag_names[i].description)));
         }
     }
 
-    return g_string_free (str, FALSE);
+    g_ptr_array_add (names, NULL);
+    g_ptr_array_add (descs, NULL);
+
+    if (descriptions)
+        *descriptions = (char**) g_ptr_array_free (g_steal_pointer (&descs), FALSE);
+
+    return (char **) g_ptr_array_free (g_steal_pointer (&names), FALSE);
 }
 
 /**
@@ -413,6 +459,9 @@ seahorse_pgp_subkey_get_property (GObject      *object,
     SeahorsePgpSubkey *self = SEAHORSE_PGP_SUBKEY (object);
 
     switch (prop_id) {
+    case PROP_PARENT_KEY:
+        g_value_set_object (value, seahorse_pgp_subkey_get_parent_key (self));
+        break;
     case PROP_INDEX:
         g_value_set_uint (value, seahorse_pgp_subkey_get_index (self));
         break;
@@ -452,6 +501,9 @@ seahorse_pgp_subkey_set_property (GObject      *object,
     SeahorsePgpSubkey *self = SEAHORSE_PGP_SUBKEY (object);
 
     switch (prop_id) {
+    case PROP_PARENT_KEY:
+        seahorse_pgp_subkey_set_parent_key (self, g_value_get_object (value));
+        break;
     case PROP_INDEX:
         seahorse_pgp_subkey_set_index (self, g_value_get_uint (value));
         break;
@@ -513,6 +565,11 @@ seahorse_pgp_subkey_class_init (SeahorsePgpSubkeyClass *klass)
     gobject_class->set_property = seahorse_pgp_subkey_set_property;
     gobject_class->get_property = seahorse_pgp_subkey_get_property;
 
+    obj_props[PROP_PARENT_KEY] =
+        g_param_spec_object ("parent-key", "Parent Key", "Key this subkey belongs to",
+                             SEAHORSE_PGP_TYPE_KEY,
+                             G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
     obj_props[PROP_INDEX] =
         g_param_spec_uint ("index", "Index", "PGP subkey index",
                            0, G_MAXUINT, 0,
diff --git a/pgp/seahorse-pgp-subkey.h b/pgp/seahorse-pgp-subkey.h
index 47dbc830..ed5f81a4 100644
--- a/pgp/seahorse-pgp-subkey.h
+++ b/pgp/seahorse-pgp-subkey.h
@@ -22,6 +22,7 @@
 #include <glib-object.h>
 
 #include "seahorse-common.h"
+#include "pgp/seahorse-pgp-key.h"
 
 #define SEAHORSE_PGP_TYPE_SUBKEY            (seahorse_pgp_subkey_get_type ())
 G_DECLARE_DERIVABLE_TYPE (SeahorsePgpSubkey, seahorse_pgp_subkey,
@@ -34,6 +35,11 @@ struct _SeahorsePgpSubkeyClass {
 
 SeahorsePgpSubkey*  seahorse_pgp_subkey_new               (void);
 
+SeahorsePgpKey *    seahorse_pgp_subkey_get_parent_key    (SeahorsePgpSubkey *self);
+
+void                seahorse_pgp_subkey_set_parent_key    (SeahorsePgpSubkey *self,
+                                                           SeahorsePgpKey    *parent_key);
+
 unsigned int        seahorse_pgp_subkey_get_index         (SeahorsePgpSubkey *self);
 
 void                seahorse_pgp_subkey_set_index         (SeahorsePgpSubkey *self,
@@ -61,6 +67,9 @@ void                seahorse_pgp_subkey_set_length        (SeahorsePgpSubkey *se
 
 char *              seahorse_pgp_subkey_get_usage         (SeahorsePgpSubkey *self);
 
+char **             seahorse_pgp_subkey_get_usages        (SeahorsePgpSubkey  *self,
+                                                           char             ***descriptions);
+
 GDateTime *         seahorse_pgp_subkey_get_created       (SeahorsePgpSubkey *self);
 
 void                seahorse_pgp_subkey_set_created       (SeahorsePgpSubkey *self,
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 15cf5bae..67b76914 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -75,6 +75,8 @@ pgp/seahorse-pgp-key-properties.c
 pgp/seahorse-pgp-private-key-properties.ui
 pgp/seahorse-pgp-public-key-properties.ui
 pgp/seahorse-pgp-subkey.c
+pgp/seahorse-pgp-subkey-list-box.c
+pgp/seahorse-pgp-subkey-list-box-row.ui
 pgp/seahorse-revoke.ui
 pgp/seahorse-server-source.c
 pgp/seahorse-transfer.c


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