[gnome-keyring/ui-widgets: 6/7] [gcr] Implement rudimentary key view.



commit 9a806907aceba77ca9baa7da9566c9b116e2cf9c
Author: Stef Walter <stef memberwebs com>
Date:   Tue Jul 13 03:45:37 2010 +0000

    [gcr] Implement rudimentary key view.

 gcr/Makefile.am                                    |    2 +
 gcr/gcr-certificate-widget.c                       |    1 -
 gcr/gcr-key-widget.c                               |  325 ++++++++++++++++++++
 gcr/gcr-key-widget.h                               |   57 ++++
 gcr/tests/.gitignore                               |    3 +-
 gcr/tests/Makefile.am                              |   29 ++-
 .../{ui-test-details.c => ui-test-certificate.c}   |   20 +-
 gcr/tests/{ui-test-details.c => ui-test-key.c}     |   62 +++--
 8 files changed, 453 insertions(+), 46 deletions(-)
---
diff --git a/gcr/Makefile.am b/gcr/Makefile.am
index c4b205f..f56900a 100644
--- a/gcr/Makefile.am
+++ b/gcr/Makefile.am
@@ -20,6 +20,7 @@ inc_HEADERS = \
 	gcr-certificate-basics-widget.h \
 	gcr-certificate-details-widget.h \
 	gcr-certificate-widget.h \
+	gcr-key-widget.h \
 	gcr-importer.h \
 	gcr-parser.h \
 	gcr-types.h \
@@ -48,6 +49,7 @@ libgcr_la_SOURCES = \
 	gcr-certificate-widget.c gcr-certificate-widget.h \
 	gcr-certificate-basics-widget.c gcr-certificate-basics-widget.h \
 	gcr-certificate-details-widget.c gcr-certificate-details-widget.h \
+	gcr-key-widget.c gcr-key-widget.h \
 	gcr-display-view.c gcr-display-view.h \
 	gcr-icons.c gcr-icons.h \
 	gcr-import-dialog.c gcr-import-dialog.h \
diff --git a/gcr/gcr-certificate-widget.c b/gcr/gcr-certificate-widget.c
index 7d178a6..7a55a6e 100644
--- a/gcr/gcr-certificate-widget.c
+++ b/gcr/gcr-certificate-widget.c
@@ -507,6 +507,5 @@ gcr_certificate_widget_set_certificate (GcrCertificateWidget *self, GcrCertifica
 		g_object_ref (self->pv->certificate);
 
 	refresh_display (self);
-	refresh_display (self);
 	g_object_notify (G_OBJECT (self), "certificate");
 }
diff --git a/gcr/gcr-key-widget.c b/gcr/gcr-key-widget.c
new file mode 100644
index 0000000..60b1ddc
--- /dev/null
+++ b/gcr/gcr-key-widget.c
@@ -0,0 +1,325 @@
+/*
+ * Copyright (C) 2008 Stefan Walter
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include "config.h"
+
+#include "gcr-key-widget.h"
+#include "gcr-display-view.h"
+#include "gcr-icons.h"
+#include "gcr-view.h"
+
+#include "egg/egg-asn1x.h"
+#include "egg/egg-oid.h"
+#include "egg/egg-hex.h"
+
+#include "gp11/gp11.h"
+
+#include <gdk/gdk.h>
+#include <glib/gi18n-lib.h>
+
+enum {
+	PROP_0,
+	PROP_LABEL,
+	PROP_ATTRIBUTES
+};
+
+struct _GcrKeyWidgetPrivate {
+	GcrDisplayView *view;
+	guint key_size;
+	gchar *label;
+	GP11Attributes *attributes;
+};
+
+static void gcr_view_iface_init (GcrViewIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (GcrKeyWidget, gcr_key_widget, GTK_TYPE_ALIGNMENT,
+                         G_IMPLEMENT_INTERFACE (GCR_TYPE_VIEW, gcr_view_iface_init));
+
+/* -----------------------------------------------------------------------------
+ * INTERNAL
+ */
+
+static gchar*
+calculate_label (GcrKeyWidget *self)
+{
+	gchar *label;
+
+	if (self->pv->label)
+		return g_strdup (self->pv->label);
+
+	if (self->pv->attributes) {
+		if (gp11_attributes_find_string (self->pv->attributes, CKA_LABEL, &label))
+			return label;
+	}
+
+	return g_strdup (_("Key"));
+}
+
+static gint
+calculate_rsa_key_size (GP11Attributes *attrs)
+{
+	GP11Attribute *attr;
+	gulong bits;
+
+	attr = gp11_attributes_find (attrs, CKA_MODULUS);
+
+	/* Calculate the bit length, and remove the complement */
+	if (attr != NULL)
+		return (attr->length / 2) * 2 * 8;
+
+	if (gp11_attributes_find_ulong (attrs, CKA_MODULUS_BITS, &bits))
+		return (gint)bits;
+
+	return -1;
+}
+
+static guint
+calculate_dsa_key_size (GP11Attributes *attrs)
+{
+	GP11Attribute *attr;
+	gulong bits;
+
+	attr = gp11_attributes_find (attrs, CKA_PRIME);
+
+	/* Calculate the bit length, and remove the complement */
+	if (attr != NULL)
+		return (attr->length / 2) * 2 * 8;
+
+	if (gp11_attributes_find_ulong (attrs, CKA_PRIME_BITS, &bits))
+		return (gint)bits;
+
+	return -1;
+}
+
+static gint
+calculate_key_size (GP11Attributes *attrs, gulong key_type)
+{
+	if (key_type == CKK_RSA)
+		return calculate_rsa_key_size (attrs);
+	else if (key_type == CKK_DSA)
+		return calculate_dsa_key_size (attrs);
+	else
+		return -1;
+}
+
+static void
+refresh_display (GcrKeyWidget *self)
+{
+	const gchar *text;
+	gchar *display;
+	gulong klass;
+	gulong key_type;
+	gint size;
+
+	_gcr_display_view_clear (self->pv->view);
+
+	if (!self->pv->attributes)
+		return;
+
+	if (!gp11_attributes_find_ulong (self->pv->attributes, CKA_CLASS, &klass) ||
+	    !gp11_attributes_find_ulong (self->pv->attributes, CKA_KEY_TYPE, &key_type)) {
+		g_warning ("private key does not have the CKA_CLASS and CKA_KEY_TYPE attributes");
+		return;
+	}
+
+	display = calculate_label (self);
+	_gcr_display_view_append_title (self->pv->view, display);
+	g_free (display);
+
+	if (klass == CKO_PRIVATE_KEY) {
+		if (key_type == CKK_RSA)
+			text = _("Private RSA Key");
+		else if (key_type == CKK_DSA)
+			text = _("Private DSA Key");
+		else
+			text = _("Private Key");
+	} else if (klass == CKO_PUBLIC_KEY) {
+		if (key_type == CKK_RSA)
+			text = _("Public DSA Key");
+		else if (key_type == CKK_DSA)
+			text = _("Public DSA Key");
+		else
+			text = _("Public Key");
+	}
+
+	_gcr_display_view_append_content (self->pv->view, text, NULL);
+
+	_gcr_display_view_start_details (self->pv->view);
+
+
+	if (key_type == CKK_RSA)
+		text = _("RSA");
+	else if (key_type == CKK_DSA)
+		text = _("DSA");
+	else
+		text = _("Unknown");
+	_gcr_display_view_append_value (self->pv->view, _("Algorithm"), text, FALSE);
+
+	size = calculate_key_size (self->pv->attributes, key_type);
+	if (size < 0)
+		display = g_strdup (_("Unknown"));
+	else
+		display = g_strdup_printf ("%d", size);
+	_gcr_display_view_append_value (self->pv->view, _("Size"), display, FALSE);
+	g_free (display);
+
+	/* TODO: We need to have consistent key fingerprints. */
+	_gcr_display_view_append_value (self->pv->view, _("Fingerprint"), "XX XX XX XX XX XX XX XX XX XX", TRUE);
+}
+
+/* -----------------------------------------------------------------------------
+ * OBJECT
+ */
+
+static GObject*
+gcr_key_widget_constructor (GType type, guint n_props, GObjectConstructParam *props)
+{
+	GObject *obj = G_OBJECT_CLASS (gcr_key_widget_parent_class)->constructor (type, n_props, props);
+	GcrKeyWidget *self = NULL;
+	GtkWidget *scroll;
+
+	g_return_val_if_fail (obj, NULL);
+
+	self = GCR_KEY_WIDGET (obj);
+
+	self->pv->view = _gcr_display_view_new ();
+	_gcr_display_view_set_stock_image (self->pv->view, GTK_STOCK_DIALOG_AUTHENTICATION);
+
+	scroll = gtk_scrolled_window_new (NULL, NULL);
+	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scroll), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
+	gtk_container_add (GTK_CONTAINER (scroll), GTK_WIDGET (self->pv->view));
+
+	gtk_container_add (GTK_CONTAINER (self), scroll);
+	gtk_widget_show_all (scroll);
+
+	refresh_display (self);
+
+	return obj;
+}
+
+static void
+gcr_key_widget_init (GcrKeyWidget *self)
+{
+	self->pv = (G_TYPE_INSTANCE_GET_PRIVATE (self, GCR_TYPE_KEY_WIDGET, GcrKeyWidgetPrivate));
+}
+
+static void
+gcr_key_widget_dispose (GObject *obj)
+{
+	G_OBJECT_CLASS (gcr_key_widget_parent_class)->dispose (obj);
+}
+
+static void
+gcr_key_widget_finalize (GObject *obj)
+{
+	GcrKeyWidget *self = GCR_KEY_WIDGET (obj);
+
+	if (self->pv->attributes)
+		gp11_attributes_unref (self->pv->attributes);
+	self->pv->attributes = NULL;
+
+	g_free (self->pv->label);
+	self->pv->label = NULL;
+
+	G_OBJECT_CLASS (gcr_key_widget_parent_class)->finalize (obj);
+}
+
+static void
+gcr_key_widget_set_property (GObject *obj, guint prop_id, const GValue *value,
+                                     GParamSpec *pspec)
+{
+	GcrKeyWidget *self = GCR_KEY_WIDGET (obj);
+
+	switch (prop_id) {
+	case PROP_LABEL:
+		g_free (self->pv->label);
+		self->pv->label = g_value_dup_string (value);
+		g_object_notify (obj, "label");
+		break;
+	case PROP_ATTRIBUTES:
+		g_return_if_fail (!self->pv->attributes);
+		self->pv->attributes = g_value_dup_boxed (value);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+gcr_key_widget_get_property (GObject *obj, guint prop_id, GValue *value,
+                                     GParamSpec *pspec)
+{
+	GcrKeyWidget *self = GCR_KEY_WIDGET (obj);
+
+	switch (prop_id) {
+	case PROP_LABEL:
+		g_value_take_string (value, calculate_label (self));
+		break;
+	case PROP_ATTRIBUTES:
+		g_value_set_boxed (value, self->pv->attributes);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+gcr_key_widget_class_init (GcrKeyWidgetClass *klass)
+{
+	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+	GP11Attributes *registered;
+
+	gcr_key_widget_parent_class = g_type_class_peek_parent (klass);
+	g_type_class_add_private (klass, sizeof (GcrKeyWidgetPrivate));
+
+	gobject_class->constructor = gcr_key_widget_constructor;
+	gobject_class->dispose = gcr_key_widget_dispose;
+	gobject_class->finalize = gcr_key_widget_finalize;
+	gobject_class->set_property = gcr_key_widget_set_property;
+	gobject_class->get_property = gcr_key_widget_get_property;
+
+	g_object_class_override_property (gobject_class, PROP_LABEL, "label");
+	g_object_class_override_property (gobject_class, PROP_ATTRIBUTES, "attributes");
+
+	_gcr_icons_register ();
+
+	/* Register this as a view which can be loaded */
+	registered = gp11_attributes_new ();
+	gp11_attributes_add_ulong (registered, CKA_CLASS, CKO_PRIVATE_KEY);
+	gcr_view_register (GCR_TYPE_KEY_WIDGET, registered);
+	gp11_attributes_unref (registered);
+}
+
+static void
+gcr_view_iface_init (GcrViewIface *iface)
+{
+	/* Nothing to do */
+}
+
+/* -----------------------------------------------------------------------------
+ * PUBLIC
+ */
+
+GcrKeyWidget*
+gcr_key_widget_new (const gchar *label, GP11Attributes *attrs)
+{
+	return g_object_new (GCR_TYPE_KEY_WIDGET, "label", label, "attributes", attrs, NULL);
+}
diff --git a/gcr/gcr-key-widget.h b/gcr/gcr-key-widget.h
new file mode 100644
index 0000000..0aeffac
--- /dev/null
+++ b/gcr/gcr-key-widget.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2010 Stefan Walter
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef __GCR_KEY_WIDGET_H__
+#define __GCR_KEY_WIDGET_H__
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+#include "gcr-types.h"
+
+G_BEGIN_DECLS
+
+#define GCR_TYPE_KEY_WIDGET               (gcr_key_widget_get_type ())
+#define GCR_KEY_WIDGET(obj)               (G_TYPE_CHECK_INSTANCE_CAST ((obj), GCR_TYPE_KEY_WIDGET, GcrKeyWidget))
+#define GCR_KEY_WIDGET_CLASS(klass)       (G_TYPE_CHECK_CLASS_CAST ((klass), GCR_TYPE_KEY_WIDGET, GcrKeyWidgetClass))
+#define GCR_IS_KEY_WIDGET(obj)            (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GCR_TYPE_KEY_WIDGET))
+#define GCR_IS_KEY_WIDGET_CLASS(klass)    (G_TYPE_CHECK_CLASS_TYPE ((klass), GCR_TYPE_KEY_WIDGET))
+#define GCR_KEY_WIDGET_GET_CLASS(obj)     (G_TYPE_INSTANCE_GET_CLASS ((obj), GCR_TYPE_KEY_WIDGET, GcrKeyWidgetClass))
+
+typedef struct _GcrKeyWidget GcrKeyWidget;
+typedef struct _GcrKeyWidgetClass GcrKeyWidgetClass;
+typedef struct _GcrKeyWidgetPrivate GcrKeyWidgetPrivate;
+
+struct _GcrKeyWidget {
+	GtkAlignment parent;
+	GcrKeyWidgetPrivate *pv;
+};
+
+struct _GcrKeyWidgetClass {
+	GtkAlignmentClass parent_class;
+};
+
+GType                   gcr_key_widget_get_type               (void);
+
+GcrKeyWidget*           gcr_key_widget_new                    (const gchar *label,
+                                                               struct _GP11Attributes *attrs);
+
+G_END_DECLS
+
+#endif /* __GCR_KEY_WIDGET_H__ */
diff --git a/gcr/tests/.gitignore b/gcr/tests/.gitignore
index b3085f5..48d49ed 100644
--- a/gcr/tests/.gitignore
+++ b/gcr/tests/.gitignore
@@ -1 +1,2 @@
-/ui-test-details
+/ui-test-key
+/ui-test-certificate
diff --git a/gcr/tests/Makefile.am b/gcr/tests/Makefile.am
index d100859..77e039e 100644
--- a/gcr/tests/Makefile.am
+++ b/gcr/tests/Makefile.am
@@ -15,22 +15,35 @@ TESTING_FLAGS = \
 
 include $(top_srcdir)/testing/testing.make
 
+EXTRA_DIST = \
+	test-data
+
 # ------------------------------------------------------------------
 
 noinst_PROGRAMS += \
-	ui-test-details
+	ui-test-certificate \
+	ui-test-key
 
-ui_test_details_SOURCES = \
-	ui-test-details.c
+ui_test_certificate_SOURCES = \
+	ui-test-certificate.c
 
-ui_test_details_CFLAGS = \
+ui_test_certificate_CFLAGS = \
 	-DGCR_API_SUBJECT_TO_CHANGE \
 	$(GTK_CFLAGS)
-	
-ui_test_details_LDADD = \
+
+ui_test_certificate_LDADD = \
 	$(top_builddir)/gcr/libgcr.la \
 	$(GTK_LIBS) \
 	$(LIBGCRYPT_LIBS)
 
-EXTRA_DIST = \
-	test-data
+ui_test_key_SOURCES = \
+	ui-test-key.c
+
+ui_test_key_CFLAGS = \
+	-DGCR_API_SUBJECT_TO_CHANGE \
+	$(GTK_CFLAGS)
+
+ui_test_key_LDADD = \
+	$(top_builddir)/gcr/libgcr.la \
+	$(GTK_LIBS) \
+	$(LIBGCRYPT_LIBS)
diff --git a/gcr/tests/ui-test-details.c b/gcr/tests/ui-test-certificate.c
similarity index 94%
copy from gcr/tests/ui-test-details.c
copy to gcr/tests/ui-test-certificate.c
index fdfb6b1..8711e7f 100644
--- a/gcr/tests/ui-test-details.c
+++ b/gcr/tests/ui-test-certificate.c
@@ -10,16 +10,16 @@
 #include <string.h>
 #include <errno.h>
 
-static void 
+static void
 chdir_base_dir (char* argv0)
 {
 	gchar *dir, *base;
 
 	dir = g_path_get_dirname (argv0);
 	if (chdir (dir) < 0)
-		g_warning ("couldn't change directory to: %s: %s", 
+		g_warning ("couldn't change directory to: %s: %s",
 		           dir, g_strerror (errno));
-	
+
 	base = g_path_get_basename (dir);
 	if (strcmp (base, ".libs") == 0) {
 		if (chdir ("..") < 0)
@@ -39,24 +39,24 @@ test_details (const gchar *path)
 	GtkDialog *dialog;
 	guchar *data;
 	gsize n_data;
-	
+
 	if (!g_file_get_contents (path, (gchar**)&data, &n_data, NULL))
 		g_error ("couldn't read file: %s", path);
-	
+
 	certificate = gcr_simple_certificate_new (data, n_data);
 	g_assert (certificate);
 	g_free (data);
-	
+
 	dialog = GTK_DIALOG (gtk_dialog_new ());
 	g_object_ref_sink (dialog);
-	
+
 	details = gcr_certificate_widget_new (certificate);
 	gtk_widget_show (GTK_WIDGET (details));
 	gtk_container_add (GTK_CONTAINER (gtk_dialog_get_content_area (dialog)), GTK_WIDGET (details));
 
 	gtk_window_set_default_size (GTK_WINDOW (dialog), 550, 400);
 	gtk_dialog_run (dialog);
-	
+
 	g_object_unref (dialog);
 	g_object_unref (certificate);
 	g_object_unref (details);
@@ -66,13 +66,13 @@ int
 main(int argc, char *argv[])
 {
 	gtk_init (&argc, &argv);
-	
+
 	if (argc > 1) {
 		test_details (argv[1]);
 	} else {
 		chdir_base_dir (argv[0]);
 		test_details ("test-data/der-certificate.crt");
 	}
-	
+
 	return 0;
 }
diff --git a/gcr/tests/ui-test-details.c b/gcr/tests/ui-test-key.c
similarity index 60%
rename from gcr/tests/ui-test-details.c
rename to gcr/tests/ui-test-key.c
index fdfb6b1..2d2403d 100644
--- a/gcr/tests/ui-test-details.c
+++ b/gcr/tests/ui-test-key.c
@@ -1,8 +1,8 @@
 
 #include "config.h"
 
-#include "gcr-certificate-widget.h"
-#include "gcr-simple-certificate.h"
+#include "gcr-key-widget.h"
+#include "gcr-parser.h"
 
 #include <gtk/gtk.h>
 
@@ -10,16 +10,16 @@
 #include <string.h>
 #include <errno.h>
 
-static void 
+static void
 chdir_base_dir (char* argv0)
 {
 	gchar *dir, *base;
 
 	dir = g_path_get_dirname (argv0);
 	if (chdir (dir) < 0)
-		g_warning ("couldn't change directory to: %s: %s", 
+		g_warning ("couldn't change directory to: %s: %s",
 		           dir, g_strerror (errno));
-	
+
 	base = g_path_get_basename (dir);
 	if (strcmp (base, ".libs") == 0) {
 		if (chdir ("..") < 0)
@@ -32,47 +32,57 @@ chdir_base_dir (char* argv0)
 }
 
 static void
-test_details (const gchar *path)
+on_parser_parsed (GcrParser *parser, gpointer unused)
 {
-	GcrCertificateWidget *details;
-	GcrCertificate *certificate;
+	GcrKeyWidget *details;
 	GtkDialog *dialog;
-	guchar *data;
-	gsize n_data;
-	
-	if (!g_file_get_contents (path, (gchar**)&data, &n_data, NULL))
-		g_error ("couldn't read file: %s", path);
-	
-	certificate = gcr_simple_certificate_new (data, n_data);
-	g_assert (certificate);
-	g_free (data);
-	
+
 	dialog = GTK_DIALOG (gtk_dialog_new ());
 	g_object_ref_sink (dialog);
-	
-	details = gcr_certificate_widget_new (certificate);
+
+	details = gcr_key_widget_new (gcr_parser_get_parsed_label (parser),
+	                              gcr_parser_get_parsed_attributes (parser));
 	gtk_widget_show (GTK_WIDGET (details));
 	gtk_container_add (GTK_CONTAINER (gtk_dialog_get_content_area (dialog)), GTK_WIDGET (details));
 
 	gtk_window_set_default_size (GTK_WINDOW (dialog), 550, 400);
 	gtk_dialog_run (dialog);
-	
+
 	g_object_unref (dialog);
-	g_object_unref (certificate);
 	g_object_unref (details);
 }
 
+static void
+test_key (const gchar *path)
+{
+	GcrParser *parser;
+	GError *err = NULL;
+	guchar *data;
+	gsize n_data;
+
+	if (!g_file_get_contents (path, (gchar**)&data, &n_data, NULL))
+		g_error ("couldn't read file: %s", path);
+
+	parser = gcr_parser_new ();
+	g_signal_connect (parser, "parsed", G_CALLBACK (on_parser_parsed), NULL);
+	if (!gcr_parser_parse_data (parser, data, n_data, &err))
+		g_error ("couldn't parse data: %s", err->message);
+
+	g_object_unref (parser);
+	g_free (data);
+}
+
 int
 main(int argc, char *argv[])
 {
 	gtk_init (&argc, &argv);
-	
+
 	if (argc > 1) {
-		test_details (argv[1]);
+		test_key (argv[1]);
 	} else {
 		chdir_base_dir (argv[0]);
-		test_details ("test-data/der-certificate.crt");
+		test_key ("test-data/pem-dsa-1024.key");
 	}
-	
+
 	return 0;
 }



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