[gnome-keyring] gcr: Sortable selector



commit 60c77d997d71510195743d54ac4305638364aeb0
Author: Stef Walter <stef memberwebs com>
Date:   Sat Oct 23 16:47:43 2010 +0000

    gcr: Sortable selector

 gcr/gcr-certificate.c      |   41 +++--
 gcr/gcr-collection-model.c |  409 +++++++++++++++++++++-----------------------
 gcr/gcr-collection-model.h |    8 +-
 gcr/gcr-column.h           |   17 ++-
 gcr/gcr-selector.c         |  146 +++++++++++++---
 gcr/gcr-selector.h         |    9 -
 6 files changed, 363 insertions(+), 267 deletions(-)
---
diff --git a/gcr/gcr-certificate.c b/gcr/gcr-certificate.c
index b7a1289..9a3ebb9 100644
--- a/gcr/gcr-certificate.c
+++ b/gcr/gcr-certificate.c
@@ -266,20 +266,25 @@ calculate_markup (GcrCertificate *self)
 	return markup;
 }
 
-static gchar*
-calculate_expiry (GcrCertificate *self)
+static void
+on_transform_date_to_string (const GValue *src, GValue *dest)
 {
+	static const gsize len = 256;
 	GDate *date;
 	gchar *result;
 
-	date = gcr_certificate_get_expiry_date (self);
-	result = g_malloc0 (256);
-	if (!g_date_strftime (result, 256, "%s", date)) {
+	g_return_if_fail (G_VALUE_TYPE (src) == G_TYPE_DATE);
+
+	date = g_value_get_boxed (src);
+	g_return_if_fail (date);
+
+	result = g_malloc0 (len);
+	if (!g_date_strftime (result, len, "%x", date)) {
 		g_free (result);
 		result = NULL;
 	}
-	g_date_free (date);
-	return result;
+
+	g_value_take_string (dest, result);
 }
 
 /* ---------------------------------------------------------------------------------
@@ -321,8 +326,8 @@ gcr_certificate_iface_init (gpointer gobject_iface)
 		                                "", G_PARAM_READABLE));
 
 		g_object_interface_install_property (gobject_iface,
-		           g_param_spec_string ("expiry", "Expiry", "Certificate expiry",
-		                                "", G_PARAM_READABLE));
+		           g_param_spec_boxed ("expiry", "Expiry", "Certificate expiry",
+		                               G_TYPE_DATE, G_PARAM_READABLE));
 
 		g_once_init_leave (&initialized, 1);
 	}
@@ -359,16 +364,18 @@ const GcrColumn*
 gcr_certificate_get_columns (void)
 {
 	static GcrColumn columns[] = {
-		{ "icon", 0, NULL, 0 },
-		{ "label", G_TYPE_STRING, N_("Name"), 0 },
-		{ "description", G_TYPE_STRING, N_("Type"), 0 },
-		{ "subject", G_TYPE_STRING, N_("Subject"), 0 },
-		{ "issuer", G_TYPE_STRING, N_("Issued By"), 0 },
-		{ "expiry", G_TYPE_STRING, N_("Expires"), 0 },
+		{ "icon", /* later */ 0, /* later */ 0, NULL, 0 },
+		{ "label", G_TYPE_STRING, G_TYPE_STRING, N_("Name"),
+		  GCR_COLUMN_SORTABLE },
+		{ "issuer", G_TYPE_STRING, G_TYPE_STRING, N_("Issued By"),
+		  GCR_COLUMN_SORTABLE },
+		{ "expiry", /* later */ 0, G_TYPE_STRING, N_("Expires"),
+		  GCR_COLUMN_SORTABLE, on_transform_date_to_string },
 		{ NULL }
 	};
 
-	columns[0].type = G_TYPE_ICON;
+	columns[0].property_type = columns[0].column_type = G_TYPE_ICON;
+	columns[3].property_type = G_TYPE_DATE;
 	return columns;
 }
 
@@ -1016,7 +1023,7 @@ gcr_certificate_mixin_get_property (GObject *obj, guint prop_id,
 		g_value_take_string (value, gcr_certificate_get_issuer_cn (cert));
 		break;
 	case PROP_EXPIRY:
-		g_value_take_string (value, calculate_expiry (cert));
+		g_value_take_boxed (value, gcr_certificate_get_expiry_date (cert));
 		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
diff --git a/gcr/gcr-collection-model.c b/gcr/gcr-collection-model.c
index 5c8d753..9812d62 100644
--- a/gcr/gcr-collection-model.c
+++ b/gcr/gcr-collection-model.c
@@ -23,93 +23,105 @@
 
 #include "gcr-collection-model.h"
 
+#include <gtk/gtk.h>
+
 #include <string.h>
+#include <unistd.h>
+
+#define COLLECTION_MODEL_STAMP 0xAABBCCDD
 
 enum {
 	PROP_0,
-	PROP_COLLECTION
+	PROP_COLLECTION,
+	PROP_COLUMNS
 };
 
 struct _GcrCollectionModelPrivate {
 	GcrCollection *collection;
-	GHashTable *object_to_index;
-	GHashTable *toggled_active;
-
-	gint cache_stamp;
-	gint last_stamp;
-
-	GPtrArray *objects;
+	GHashTable *selected;
+	GSequence *objects;
 
-	gchar **column_names;
+	const GcrColumn *columns;
 	guint n_columns;
-	GType *column_types;
 };
 
 /* Forward declarations */
-static void gcr_collection_model_tree_model (GtkTreeModelIface *iface);
+static void gcr_collection_model_tree_model_init (GtkTreeModelIface *iface);
 
-G_DEFINE_TYPE_EXTENDED (GcrCollectionModel, gcr_collection_model, G_TYPE_OBJECT, 0,
-                        G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_MODEL, gcr_collection_model_tree_model));
+G_DEFINE_TYPE_WITH_CODE (GcrCollectionModel, gcr_collection_model, G_TYPE_OBJECT,
+	G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_MODEL, gcr_collection_model_tree_model_init);
+);
 
-#define UNUSED_VALUE GUINT_TO_POINTER (1)
+#define UNUSED_VALUE GINT_TO_POINTER (1)
 
-/* -----------------------------------------------------------------------------
- * INTERNAL
- */
+static gboolean
+column_id_is_valid_and_real (GcrCollectionModel *self, gint column_id)
+{
+	return (column_id >= 0 && column_id < self->pv->n_columns);
+}
+
+static gint
+on_sequence_compare (gconstpointer a, gconstpointer b, gpointer user_data)
+{
+	return (GObject*)a - (GObject*)b;
+}
 
 static gint
 index_for_iter (GcrCollectionModel *self, const GtkTreeIter *iter)
 {
+	GSequenceIter *seq;
 	gint index;
 
 	g_return_val_if_fail (iter, -1);
-	g_return_val_if_fail (iter->stamp == self->pv->last_stamp, -1);
-	g_return_val_if_fail (G_IS_OBJECT (iter->user_data), -1);
+	g_return_val_if_fail (iter->stamp == COLLECTION_MODEL_STAMP, -1);
 
-	index = GPOINTER_TO_INT (iter->user_data2);
-	g_assert (index >= 0 && index < self->pv->objects->len);
+	seq = iter->user_data2;
+	g_return_val_if_fail (g_sequence_iter_get_sequence (seq) ==
+	                      self->pv->objects, -1);
+
+	index = g_sequence_iter_get_position (seq);
+	g_assert (index >= 0 && index < g_sequence_get_length (self->pv->objects));
 	return index;
 }
 
 static gboolean
-iter_for_index (GcrCollectionModel *self, gint index, GtkTreeIter *iter)
+iter_for_seq (GcrCollectionModel *self, GSequenceIter *seq, GtkTreeIter *iter)
 {
 	GObject *object;
 
-	if (index < 0 || index >= self->pv->objects->len)
-		return FALSE;
-
-	object = g_ptr_array_index (self->pv->objects, index);
+	object = g_sequence_get (seq);
 	g_return_val_if_fail (G_IS_OBJECT (object), FALSE);
 
 	memset (iter, 0, sizeof (*iter));
-	iter->stamp = self->pv->last_stamp;
+	iter->stamp = COLLECTION_MODEL_STAMP;
 	iter->user_data = object;
-	iter->user_data2 = GINT_TO_POINTER (index);
+	iter->user_data2 = seq;
 	return TRUE;
 }
 
+static gboolean
+iter_for_index (GcrCollectionModel *self, gint index, GtkTreeIter *iter)
+{
+	GSequenceIter *seq;
+
+	if (index < 0 || index >= g_sequence_get_length (self->pv->objects))
+		return FALSE;
+
+	seq = g_sequence_get_iter_at_pos (self->pv->objects, index);
+	return iter_for_seq (self, seq, iter);
+}
+
 static gint
 index_for_object (GcrCollectionModel *self, GObject *object)
 {
-	gpointer value;
-	guint i;
+	GSequenceIter *seq;
 
-	/* Build the index if not valid */
-	if (self->pv->cache_stamp != self->pv->last_stamp) {
-		g_hash_table_remove_all (self->pv->object_to_index);
-		for (i = 0; i < self->pv->objects->len; ++i) {
-			g_hash_table_insert (self->pv->object_to_index,
-			                     g_ptr_array_index (self->pv->objects, i),
-			                     GUINT_TO_POINTER (i));
-		}
-		self->pv->cache_stamp = self->pv->last_stamp;
-	}
-
-	if (!g_hash_table_lookup_extended (self->pv->object_to_index, object, NULL, &value))
+	seq = g_sequence_lookup (self->pv->objects, object,
+	                         on_sequence_compare, NULL);
+	if (seq == NULL)
 		return -1;
 
-	return GPOINTER_TO_INT (value);
+	return g_sequence_iter_get_position (seq);
 }
 
 static void
@@ -122,20 +134,21 @@ on_object_notify (GObject *object, GParamSpec *spec, GcrCollectionModel *self)
 	g_return_if_fail (spec->name);
 
 	for (i = 0; i < self->pv->n_columns; ++i) {
-		g_assert (self->pv->column_names[i]);
-		if (g_str_equal (self->pv->column_names[i], spec->name)) {
-			if (!gcr_collection_model_iter_for_object (self, object, &iter))
-				g_return_if_reached ();
-
-			path = gtk_tree_model_get_path (GTK_TREE_MODEL (self), &iter);
-			g_return_if_fail (path);
-
-			gtk_tree_model_row_changed (GTK_TREE_MODEL (self), path, &iter);
-			gtk_tree_path_free (path);
-
-			return;
+		g_assert (self->pv->columns[i].property_name);
+		if (g_str_equal (self->pv->columns[i].property_name, spec->name)) {
+			break;
 		}
 	}
+
+	/* Tell the tree view that this row changed */
+	if (column_id_is_valid_and_real (self, i)) {
+		if (!gcr_collection_model_iter_for_object (self, object, &iter))
+			g_return_if_reached ();
+		path = gtk_tree_model_get_path (GTK_TREE_MODEL (self), &iter);
+		g_return_if_fail (path);
+		gtk_tree_model_row_changed (GTK_TREE_MODEL (self), path, &iter);
+		gtk_tree_path_free (path);
+	}
 }
 
 static void
@@ -145,35 +158,32 @@ on_object_gone (gpointer unused, GObject *was_object)
 	           "was destroyed before it was removed from the collection");
 }
 
-static gint
-add_object (GcrCollectionModel *self, GObject *object)
+static void
+on_collection_added (GcrCollection *collection, GObject *object, GcrCollectionModel *self)
 {
+	GSequenceIter *seq;
 	GtkTreeIter iter;
 	GtkTreePath *path;
-	gint index;
+
+	g_return_if_fail (GCR_COLLECTION_MODEL (self));
+	g_return_if_fail (G_IS_OBJECT (object));
 
 	g_assert (GCR_IS_COLLECTION_MODEL (self));
 	g_assert (G_IS_OBJECT (object));
 
-	index = self->pv->objects->len;
-
-	g_ptr_array_add (self->pv->objects, object);
+	seq = g_sequence_insert_sorted (self->pv->objects, object,
+	                                on_sequence_compare, self);
 	g_object_weak_ref (G_OBJECT (object), (GWeakNotify)on_object_gone, self);
 	g_signal_connect (object, "notify", G_CALLBACK (on_object_notify), self);
 
-	self->pv->last_stamp++;
-
 	/* Fire signal for this added row */
-	if (!iter_for_index (self, self->pv->objects->len - 1, &iter))
+	if (!iter_for_seq (self, seq, &iter))
 		g_assert_not_reached ();
 
-	path = gtk_tree_model_get_path (GTK_TREE_MODEL (self), &iter);
-	g_return_val_if_fail (path, -1);
+	path = gtk_tree_path_new_from_indices (g_sequence_iter_get_position (seq), -1);
 
 	gtk_tree_model_row_inserted (GTK_TREE_MODEL (self), path, &iter);
 	gtk_tree_path_free (path);
-
-	return index;
 }
 
 static void
@@ -184,46 +194,28 @@ disconnect_object (GcrCollectionModel *self, GObject *object)
 }
 
 static void
-remove_object (GcrCollectionModel *self, gint index, GObject *object)
+on_collection_removed (GcrCollection *collection, GObject *object,
+                       GcrCollectionModel *self)
 {
 	GtkTreePath *path;
+	GSequenceIter *seq;
 
-	path = gtk_tree_path_new ();
-	gtk_tree_path_append_index (path, index);
-
-	disconnect_object (self, object);
-	g_assert (g_ptr_array_index (self->pv->objects, index) == object);
-	g_ptr_array_remove_index (self->pv->objects, index);
-
-	self->pv->last_stamp++;
-
-	/* Fire signal for this removed row */
-	gtk_tree_model_row_deleted (GTK_TREE_MODEL (self), path);
-	gtk_tree_path_free (path);
-}
-
-static void
-on_collection_added (GcrCollection *collection, GObject *object, GcrCollectionModel *self)
-{
 	g_return_if_fail (GCR_COLLECTION_MODEL (self));
 	g_return_if_fail (G_IS_OBJECT (object));
 
-	add_object (self, object);
-}
+	seq = g_sequence_lookup (self->pv->objects, object, on_sequence_compare, NULL);
+	g_return_if_fail (seq != NULL);
 
-static void
-on_collection_removed (GcrCollection *collection, GObject *object,
-                       GcrCollectionModel *self)
-{
-	gint index;
+	path = gtk_tree_path_new_from_indices (g_sequence_iter_get_position (seq), -1);
 
-	g_return_if_fail (GCR_COLLECTION_MODEL (self));
-	g_return_if_fail (G_IS_OBJECT (object));
+	disconnect_object (self, object);
 
-	index = index_for_object (self, object);
-	g_return_if_fail (index < 0);
+	g_hash_table_remove (self->pv->selected, object);
+	g_sequence_remove (seq);
 
-	remove_object (self, index, object);
+	/* Fire signal for this removed row */
+	gtk_tree_model_row_deleted (GTK_TREE_MODEL (self), path);
+	gtk_tree_path_free (path);
 }
 
 static void
@@ -236,15 +228,22 @@ populate_model (GcrCollectionModel *self)
 	g_list_free (objects);
 }
 
-/* -----------------------------------------------------------------------------
- * OBJECT
- */
+static void
+free_owned_columns (gpointer data)
+{
+	GcrColumn *columns;
+	g_assert (data);
+
+	/* Only the property column is in use */
+	for (columns = data; columns->property_name; ++columns)
+		g_free ((gchar*)columns->property_name);
+	g_free (data);
+}
 
 static GtkTreeModelFlags
 gcr_collection_model_real_get_flags (GtkTreeModel *model)
 {
-	/* TODO: Maybe we can eventually GTK_TREE_MODEL_ITERS_PERSIST */
-	return 0;
+	return GTK_TREE_MODEL_ITERS_PERSIST;
 }
 
 static gint
@@ -255,11 +254,16 @@ gcr_collection_model_real_get_n_columns (GtkTreeModel *model)
 }
 
 static GType
-gcr_collection_model_real_get_column_type (GtkTreeModel *model, gint index)
+gcr_collection_model_real_get_column_type (GtkTreeModel *model, gint column_id)
 {
 	GcrCollectionModel *self = GCR_COLLECTION_MODEL (model);
-	g_return_val_if_fail (index >= 0 && index < self->pv->n_columns, 0);
-	return self->pv->column_types[index];
+	g_return_val_if_fail (column_id >= 0 && column_id <= self->pv->n_columns, 0);
+
+	/* The last is the selected column */
+	if (column_id == self->pv->n_columns)
+		return G_TYPE_BOOLEAN;
+
+	return self->pv->columns[column_id].column_type;
 }
 
 static gboolean
@@ -294,64 +298,48 @@ gcr_collection_model_real_get_path (GtkTreeModel *model, GtkTreeIter *iter)
 
 static void
 gcr_collection_model_real_get_value (GtkTreeModel *model, GtkTreeIter *iter,
-                                     gint column, GValue *value)
+                                     gint column_id, GValue *value)
 {
 	GcrCollectionModel *self = GCR_COLLECTION_MODEL (model);
-	const gchar *property;
-	GParamSpec *spec;
 	GObject *object;
 	GValue original;
-	GType type;
+	const GcrColumn *column;
 
 	object = gcr_collection_model_object_for_iter (self, iter);
 	g_return_if_fail (G_IS_OBJECT (object));
-	g_return_if_fail (column >= 0 && column < self->pv->n_columns);
+	g_return_if_fail (column_id >= 0 && column_id < self->pv->n_columns);
 
-	/* The selected column? */
-	if (column == self->pv->n_columns - 1) {
-		g_assert (!self->pv->column_names[column]);
-		g_assert (self->pv->column_types[column] == G_TYPE_BOOLEAN);
+	/* The selected column? Last one */
+	if (column_id == self->pv->n_columns - 1) {
 		g_value_init (value, G_TYPE_BOOLEAN);
 		g_value_set_boolean (value, gcr_collection_model_get_selected (self, iter));
 		return;
 	}
 
 	/* Figure out which property */
-	type = self->pv->column_types[column];
-	property = self->pv->column_names[column];
-	g_assert (property);
-	g_value_init (value, type);
-
-	/* Lookup the property on the object */
-	spec = g_object_class_find_property (G_OBJECT_GET_CLASS (object), property);
-	if (spec) {
-
-		/* Simple, no transformation necessary */
-		if (spec->value_type == type) {
-			g_object_get_property (object, property, value);
-
-		/* Not the same type, try to transform */
+	column = &self->pv->columns[column_id];
+	g_assert (column->property_name);
+	g_value_init (value, column->column_type);
+
+	/* A transformer is specified, or mismatched types */
+	if (column->transformer || column->column_type != column->property_type) {
+		memset (&original, 0, sizeof (original));
+		g_value_init (&original, column->property_type);
+		g_object_get_property (object, column->property_name, &original);
+
+		if (column->transformer) {
+			(column->transformer) (&original, value);
 		} else {
-
-			memset (&original, 0, sizeof (original));
-			g_value_init (&original, spec->value_type);
-
-			g_object_get_property (object, property, &original);
-			if (!g_value_transform (&original, value)) {
-				g_warning ("%s property of %s class was of type %s instead of type %s"
-				           " and cannot be converted", property, G_OBJECT_TYPE_NAME (object),
-				           g_type_name (spec->value_type), g_type_name (type));
-				spec = NULL;
-			}
+			g_warning ("%s property of %s class was of type %s instead of type %s"
+			           " and cannot be converted due to lack of transformer",
+			           column->property_name, G_OBJECT_TYPE_NAME (object),
+			           g_type_name (column->property_type),
+			           g_type_name (column->column_type));
 		}
-	}
 
-	/* No property present */
-	if (spec == NULL) {
-
-		/* All the number types have sane defaults */
-		if (type == G_TYPE_STRING)
-			g_value_set_string (value, "");
+	/* Simple, no transformation necessary */
+	} else {
+		g_object_get_property (object, column->property_name, value);
 	}
 }
 
@@ -383,7 +371,7 @@ gcr_collection_model_real_iter_has_child (GtkTreeModel *model, GtkTreeIter *iter
 {
 	GcrCollectionModel *self = GCR_COLLECTION_MODEL (model);
 	if (iter == NULL)
-		return self->pv->objects->len > 0;
+		return !g_sequence_iter_is_end (g_sequence_get_begin_iter (self->pv->objects));
 	return FALSE;
 }
 
@@ -392,7 +380,7 @@ gcr_collection_model_real_iter_n_children (GtkTreeModel *model, GtkTreeIter *ite
 {
 	GcrCollectionModel *self = GCR_COLLECTION_MODEL (model);
 	if (iter == NULL)
-		return self->pv->objects->len;
+		return g_sequence_get_length (self->pv->objects);
 	return 0;
 }
 
@@ -425,7 +413,7 @@ gcr_collection_model_real_unref_node (GtkTreeModel *model, GtkTreeIter *iter)
 }
 
 static void
-gcr_collection_model_tree_model (GtkTreeModelIface *iface)
+gcr_collection_model_tree_model_init (GtkTreeModelIface *iface)
 {
 	iface->get_flags = gcr_collection_model_real_get_flags;
 	iface->get_n_columns = gcr_collection_model_real_get_n_columns;
@@ -448,12 +436,10 @@ gcr_collection_model_init (GcrCollectionModel *self)
 {
 	self->pv = G_TYPE_INSTANCE_GET_PRIVATE (self, GCR_TYPE_COLLECTION_MODEL, GcrCollectionModelPrivate);
 
-	self->pv->object_to_index = g_hash_table_new (g_direct_hash, g_direct_equal);
-	self->pv->objects = g_ptr_array_new ();
-	self->pv->column_names = NULL;
+	self->pv->objects = g_sequence_new (NULL);
+	self->pv->selected = NULL;
+	self->pv->columns = NULL;
 	self->pv->n_columns = 0;
-	self->pv->column_types = NULL;
-	self->pv->last_stamp = 0x1000;
 }
 
 static void
@@ -461,6 +447,7 @@ gcr_collection_model_set_property (GObject *object, guint prop_id,
                                    const GValue *value, GParamSpec *pspec)
 {
 	GcrCollectionModel *self = GCR_COLLECTION_MODEL (object);
+	GcrColumn *columns;
 
 	switch (prop_id) {
 	case PROP_COLLECTION:
@@ -473,6 +460,12 @@ gcr_collection_model_set_property (GObject *object, guint prop_id,
 		}
 		break;
 
+	case PROP_COLUMNS:
+		columns = g_value_get_pointer (value);
+		if (columns)
+			gcr_collection_model_set_columns (self, columns);
+		break;
+
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 		break;
@@ -490,6 +483,10 @@ gcr_collection_model_get_property (GObject *object, guint prop_id,
 		g_value_set_object (value, self->pv->collection);
 		break;
 
+	case PROP_COLUMNS:
+		g_value_set_pointer (value, (gpointer)self->pv->columns);
+		break;
+
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 		break;
@@ -500,13 +497,15 @@ static void
 gcr_collection_model_dispose (GObject *object)
 {
 	GcrCollectionModel *self = GCR_COLLECTION_MODEL (object);
-	GObject *obj;
-	guint i;
+	GSequenceIter *seq;
+	GSequenceIter *next;
 
 	/* Disconnect from all rows */
-	for (i = self->pv->objects->len; i > 0; --i) {
-		obj = g_ptr_array_index (self->pv->objects, i - 1);
-		disconnect_object (self, obj);
+	for (seq = g_sequence_get_begin_iter (self->pv->objects);
+	     !g_sequence_iter_is_end (seq); seq = next) {
+		next = g_sequence_iter_next (seq);
+		disconnect_object (self, g_sequence_get (seq));
+		g_sequence_remove (seq);
 	}
 
 	/* Disconnect from the collection */
@@ -517,8 +516,8 @@ gcr_collection_model_dispose (GObject *object)
 		self->pv->collection = NULL;
 	}
 
-	if (self->pv->toggled_active)
-		g_hash_table_remove_all (self->pv->toggled_active);
+	if (self->pv->selected)
+		g_hash_table_remove_all (self->pv->selected);
 
 	G_OBJECT_CLASS (gcr_collection_model_parent_class)->dispose (object);
 }
@@ -530,29 +529,16 @@ gcr_collection_model_finalize (GObject *object)
 
 	g_assert (!self->pv->collection);
 
-	g_assert (self->pv->object_to_index);
-	g_assert (g_hash_table_size (self->pv->object_to_index) == 0);
-	g_hash_table_destroy (self->pv->object_to_index);
-	self->pv->object_to_index = NULL;
-
 	g_assert (self->pv->objects);
-	g_ptr_array_free (self->pv->objects, TRUE);
+	g_assert (g_sequence_get_length (self->pv->objects) == 0);
+	g_sequence_free (self->pv->objects);
 	self->pv->objects = NULL;
 
-	if (self->pv->toggled_active)
-		g_hash_table_destroy (self->pv->toggled_active);
-	self->pv->toggled_active = NULL;
+	if (self->pv->selected)
+		g_hash_table_destroy (self->pv->selected);
+	self->pv->selected = NULL;
 
-	if (self->pv->column_names) {
-		g_strfreev (self->pv->column_names);
-		self->pv->column_names = NULL;
-		self->pv->n_columns = 0;
-	}
-
-	if (self->pv->column_types) {
-		g_free (self->pv->column_types);
-		self->pv->column_types = NULL;
-	}
+	self->pv->columns = NULL;
 
 	G_OBJECT_CLASS (gcr_collection_model_parent_class)->finalize (object);
 }
@@ -572,6 +558,10 @@ gcr_collection_model_class_init (GcrCollectionModelClass *klass)
 		g_param_spec_object ("collection", "Object Collection", "Collection to get objects from",
 		                     GCR_TYPE_COLLECTION, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
 
+	g_object_class_install_property (gobject_class, PROP_COLUMNS,
+		g_param_spec_pointer ("columns", "Columns", "Columns for the model",
+		                      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
 	g_type_class_add_private (klass, sizeof (GcrCollectionModelPrivate));
 }
 
@@ -593,8 +583,9 @@ gcr_collection_model_new (GcrCollection *collection, ...)
 
 	va_start (va, collection);
 	while ((arg = va_arg (va, const gchar*)) != NULL) {
-		column.property = arg;
-		column.type = va_arg (va, GType);
+		column.property_name = g_strdup (arg);
+		column.property_type = va_arg (va, GType);
+		column.column_type = column.property_type;
 		column.reserved = NULL;
 		column.label = NULL;
 		g_array_append_val (array, column);
@@ -602,7 +593,8 @@ gcr_collection_model_new (GcrCollection *collection, ...)
 	va_end (va);
 
 	self = gcr_collection_model_new_full (collection, (GcrColumn*)array->data);
-	g_array_free (array, TRUE);
+	g_object_set_data_full (G_OBJECT (self), "gcr_collection_model_new",
+	                        g_array_free (array, FALSE), free_owned_columns);
 	return self;
 }
 
@@ -618,30 +610,21 @@ gint
 gcr_collection_model_set_columns (GcrCollectionModel *self, const GcrColumn *columns)
 {
 	const GcrColumn *col;
-	guint i, n_columns;
+	guint n_columns;
 
 	g_return_val_if_fail (GCR_IS_COLLECTION_MODEL (self), -1);
+	g_return_val_if_fail (columns, -1);
 	g_return_val_if_fail (self->pv->n_columns == 0, -1);
 
 	/* Count the number of columns, extra column for selected */
-	for (col = columns, n_columns = 1; col->property; ++col)
+	for (col = columns, n_columns = 1; col->property_name; ++col)
 		++n_columns;
 
-	self->pv->column_names = g_new0 (gchar*, n_columns + 1);
-	self->pv->column_types = g_new0 (GType, n_columns + 1);
+	/* We expect the columns to stay around */
+	self->pv->columns = columns;
 	self->pv->n_columns = n_columns;
 
-	/* All the columns, except the selected column */
-	for (i = 0; i < n_columns - 1; ++i) {
-		self->pv->column_names[i] = g_strdup (columns[i].property);
-		self->pv->column_types[i] = columns[i].type;
-	}
-
-	/* The selected column */
-	self->pv->column_names[i] = NULL;
-	self->pv->column_types[i] = G_TYPE_BOOLEAN;
-
-	return n_columns - 1;
+	return n_columns + 1;
 }
 
 GObject*
@@ -649,7 +632,7 @@ gcr_collection_model_object_for_iter (GcrCollectionModel *self, const GtkTreeIte
 {
 	g_return_val_if_fail (GCR_IS_COLLECTION_MODEL (self), NULL);
 	g_return_val_if_fail (iter, NULL);
-	g_return_val_if_fail (iter->stamp == self->pv->last_stamp, NULL);
+	g_return_val_if_fail (iter->stamp == COLLECTION_MODEL_STAMP, NULL);
 	g_return_val_if_fail (G_IS_OBJECT (iter->user_data), NULL);
 
 	return G_OBJECT (iter->user_data);
@@ -672,8 +655,8 @@ gcr_collection_model_iter_for_object (GcrCollectionModel *self, GObject *object,
 	return iter_for_index (self, index, iter);
 }
 
-guint
-gcr_collection_model_column_selected (GcrCollectionModel *self)
+gint
+gcr_collection_model_column_for_selected (GcrCollectionModel *self)
 {
 	g_return_val_if_fail (GCR_IS_COLLECTION_MODEL (self), 0);
 	g_assert (self->pv->n_columns > 0);
@@ -690,13 +673,13 @@ gcr_collection_model_toggle_selected (GcrCollectionModel *self, GtkTreeIter *ite
 	object = gcr_collection_model_object_for_iter (self, iter);
 	g_return_if_fail (G_IS_OBJECT (object));
 
-	if (!self->pv->toggled_active)
-		self->pv->toggled_active = g_hash_table_new (g_direct_hash, g_direct_equal);
+	if (!self->pv->selected)
+		self->pv->selected = g_hash_table_new (g_direct_hash, g_direct_equal);
 
-	if (g_hash_table_lookup (self->pv->toggled_active, object))
-		g_hash_table_remove (self->pv->toggled_active, object);
+	if (g_hash_table_lookup (self->pv->selected, object))
+		g_hash_table_remove (self->pv->selected, object);
 	else
-		g_hash_table_insert (self->pv->toggled_active, object, UNUSED_VALUE);
+		g_hash_table_insert (self->pv->selected, object, UNUSED_VALUE);
 }
 
 void
@@ -709,13 +692,13 @@ gcr_collection_model_set_selected (GcrCollectionModel *self, GtkTreeIter *iter,
 	object = gcr_collection_model_object_for_iter (self, iter);
 	g_return_if_fail (G_IS_OBJECT (object));
 
-	if (!self->pv->toggled_active)
-		self->pv->toggled_active = g_hash_table_new (g_direct_hash, g_direct_equal);
+	if (!self->pv->selected)
+		self->pv->selected = g_hash_table_new (g_direct_hash, g_direct_equal);
 
 	if (selected)
-		g_hash_table_insert (self->pv->toggled_active, object, UNUSED_VALUE);
+		g_hash_table_insert (self->pv->selected, object, UNUSED_VALUE);
 	else
-		g_hash_table_remove (self->pv->toggled_active, object);
+		g_hash_table_remove (self->pv->selected, object);
 }
 
 gboolean
@@ -728,8 +711,8 @@ gcr_collection_model_get_selected (GcrCollectionModel *self, GtkTreeIter *iter)
 	object = gcr_collection_model_object_for_iter (self, iter);
 	g_return_val_if_fail (G_IS_OBJECT (object), FALSE);
 
-	if (!self->pv->toggled_active)
+	if (!self->pv->selected)
 		return FALSE;
 
-	return g_hash_table_lookup (self->pv->toggled_active, object) ? TRUE : FALSE;
+	return g_hash_table_lookup (self->pv->selected, object) ? TRUE : FALSE;
 }
diff --git a/gcr/gcr-collection-model.h b/gcr/gcr-collection-model.h
index 1765126..931b1c3 100644
--- a/gcr/gcr-collection-model.h
+++ b/gcr/gcr-collection-model.h
@@ -66,7 +66,13 @@ gboolean              gcr_collection_model_iter_for_object     (GcrCollectionMod
                                                                 GObject *object,
                                                                 GtkTreeIter *iter);
 
-guint                 gcr_collection_model_column_selected     (GcrCollectionModel *self);
+gint                  gcr_collection_model_column_for_property (GcrCollectionModel *self,
+                                                                const gchar *property);
+
+gint                  gcr_collection_model_column_for_sortable (GcrCollectionModel *self,
+                                                                const gchar *property);
+
+gint                  gcr_collection_model_column_for_selected (GcrCollectionModel *self);
 
 void                  gcr_collection_model_toggle_selected     (GcrCollectionModel *self,
                                                                 GtkTreeIter *iter);
diff --git a/gcr/gcr-column.h b/gcr/gcr-column.h
index 53a3530..e6fe9ce 100644
--- a/gcr/gcr-column.h
+++ b/gcr/gcr-column.h
@@ -28,10 +28,21 @@
 
 G_BEGIN_DECLS
 
+enum {
+	GCR_COLUMN_HIDDEN = 0x01,
+	GCR_COLUMN_SORTABLE = 0x02,
+};
+
 typedef struct _GcrColumn {
-	const gchar *property;
-	GType type;
-	const gchar *label;
+	const gchar *property_name;     /* The property to retrieve */
+	GType property_type;            /* The property type */
+	GType column_type;              /* The resulting property type for this column */
+
+	const gchar *label;             /* The label for this column, or NULL */
+	guint flags;                    /* Column flags */
+
+	GValueTransform transformer;    /* The way to transform to this type or NULL */
+
 	gpointer user_data;
 	gpointer reserved;
 } GcrColumn;
diff --git a/gcr/gcr-selector.c b/gcr/gcr-selector.c
index 334c2d3..08aba15 100644
--- a/gcr/gcr-selector.c
+++ b/gcr/gcr-selector.c
@@ -25,6 +25,8 @@
 #include "gcr-internal.h"
 #include "gcr-selector.h"
 
+#include <string.h>
+
 enum {
 	PROP_0,
 	PROP_COLLECTION,
@@ -32,20 +34,12 @@ enum {
 	PROP_MODE
 };
 
-#if 0
-enum {
-	XXXX,
-	LAST_SIGNAL
-};
-
-static guint signals[LAST_SIGNAL] = { 0 };
-#endif
-
 struct _GcrSelectorPrivate {
 	GtkComboBox *combo;
 	GtkTreeView *tree;
 	GcrCollection *collection;
 	const GcrColumn *columns;
+	GtkTreeModel *sort;
 	GcrCollectionModel *model;
 	GcrSelectorMode mode;
 };
@@ -67,38 +61,126 @@ on_check_column_toggled (GtkCellRendererToggle *cell, gchar *path, GcrCollection
 		gcr_collection_model_toggle_selected (model, &iter);
 }
 
+typedef gint (*SortFunc) (GValue *, GValue *);
+
+static gint
+sort_string (GValue *val_a, GValue *val_b)
+{
+	const gchar *str_a = g_value_get_string (val_a);
+	const gchar *str_b = g_value_get_string (val_b);
+
+	if (str_a == str_b)
+		return 0;
+	else if (!str_a)
+		return -1;
+	else if (!str_b)
+		return 1;
+	else
+		return g_utf8_collate (str_a, str_b);
+}
+
+static gint
+sort_date (GValue *val_a, GValue *val_b)
+{
+	GDate *date_a = g_value_get_boxed (val_a);
+	GDate *date_b = g_value_get_boxed (val_b);
+
+	if (date_a == date_b)
+		return 0;
+	else if (!date_a)
+		return -1;
+	else if (!date_b)
+		return 1;
+	else
+		return g_date_compare (date_a, date_b);
+}
+
+static inline SortFunc
+sort_implementation_for_type (GType type)
+{
+	if (type == G_TYPE_STRING)
+		return sort_string;
+	else if (type == G_TYPE_DATE)
+		return sort_date;
+	else
+		return NULL;
+}
+
+static gint
+on_sort_column (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b,
+                gpointer user_data)
+{
+	GcrColumn *column = user_data;
+	SortFunc func;
+	GObject *object_a;
+	GObject *object_b;
+	GValue val_a;
+	GValue val_b;
+	gint ret;
+
+	object_a = gcr_collection_model_object_for_iter (GCR_COLLECTION_MODEL (model), a);
+	g_return_val_if_fail (G_IS_OBJECT (object_a), 0);
+	object_b = gcr_collection_model_object_for_iter (GCR_COLLECTION_MODEL (model), b);
+	g_return_val_if_fail (G_IS_OBJECT (object_b), 0);
+
+	memset (&val_a, 0, sizeof (val_a));
+	memset (&val_b, 0, sizeof (val_b));
+
+	g_value_init (&val_a, column->property_type);
+	g_value_init (&val_b, column->property_type);
+
+	g_object_get_property (object_a, column->property_name, &val_a);
+	g_object_get_property (object_b, column->property_name, &val_b);
+
+	func = sort_implementation_for_type (column->property_type);
+	g_return_val_if_fail (func, 0);
+
+	ret = (func) (&val_a, &val_b);
+
+	g_value_unset (&val_a);
+	g_value_unset (&val_b);
+
+	return ret;
+}
+
 static void
-add_string_column (GcrSelector *self, const GcrColumn *column, guint index)
+add_string_column (GcrSelector *self, const GcrColumn *column, gint column_id)
 {
 	GtkCellRenderer *cell;
 	GtkTreeViewColumn *col;
 
-	g_assert (column->type == G_TYPE_STRING);
+	g_assert (column->column_type == G_TYPE_STRING);
+	g_assert (!(column->flags & GCR_COLUMN_HIDDEN));
 
 	cell = gtk_cell_renderer_text_new ();
 	g_object_set (G_OBJECT (cell), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
-	col = gtk_tree_view_column_new_with_attributes (column->label, cell, "text", index, NULL);
+	col = gtk_tree_view_column_new_with_attributes (column->label, cell, "text", column_id, NULL);
 	gtk_tree_view_column_set_resizable (col, TRUE);
+	if (column->flags & GCR_COLUMN_SORTABLE)
+		gtk_tree_view_column_set_sort_column_id (col, column_id);
 	gtk_tree_view_append_column (self->pv->tree, col);
 }
 
 static void
-add_icon_column (GcrSelector *self, const GcrColumn *column, guint index)
+add_icon_column (GcrSelector *self, const GcrColumn *column, gint column_id)
 {
 	GtkCellRenderer *cell;
 	GtkTreeViewColumn *col;
 
-	g_assert (column->type == G_TYPE_ICON);
+	g_assert (column->column_type == G_TYPE_ICON);
+	g_assert (!(column->flags & GCR_COLUMN_HIDDEN));
 
 	cell = gtk_cell_renderer_pixbuf_new ();
 	g_object_set (cell, "stock-size", GTK_ICON_SIZE_BUTTON, NULL);
-	col = gtk_tree_view_column_new_with_attributes (column->label, cell, "gicon", index, NULL);
+	col = gtk_tree_view_column_new_with_attributes (column->label, cell, "gicon", column_id, NULL);
 	gtk_tree_view_column_set_resizable (col, TRUE);
+	if (column->flags & GCR_COLUMN_SORTABLE)
+		gtk_tree_view_column_set_sort_column_id (col, column_id);
 	gtk_tree_view_append_column (self->pv->tree, col);
 }
 
 static void
-add_check_column (GcrSelector *self, guint index)
+add_check_column (GcrSelector *self, guint column_id)
 {
 	GtkCellRenderer *cell;
 	GtkTreeViewColumn *col;
@@ -106,7 +188,7 @@ add_check_column (GcrSelector *self, guint index)
 	cell = gtk_cell_renderer_toggle_new ();
 	g_signal_connect (cell, "toggled", G_CALLBACK (on_check_column_toggled), self->pv->model);
 
-	col = gtk_tree_view_column_new_with_attributes ("", cell, "active", index, NULL);
+	col = gtk_tree_view_column_new_with_attributes ("", cell, "active", column_id, NULL);
 	gtk_tree_view_column_set_resizable (col, FALSE);
 	gtk_tree_view_append_column (self->pv->tree, col);
 }
@@ -145,25 +227,41 @@ construct_multiple_selector (GcrSelector *self)
 {
 	const GcrColumn *column;
 	GtkWidget *widget, *scroll;
+	GtkTreeSortable *sortable;
 	guint i;
 
 	self->pv->model = gcr_collection_model_new_full (self->pv->collection,
 	                                                 self->pv->columns);
 
-	widget = gtk_tree_view_new_with_model (GTK_TREE_MODEL (self->pv->model));
+	self->pv->sort = gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL (self->pv->model));
+	sortable = GTK_TREE_SORTABLE (self->pv->sort);
+
+	widget = gtk_tree_view_new_with_model (GTK_TREE_MODEL (self->pv->sort));
 	self->pv->tree = GTK_TREE_VIEW (widget);
 
 	/* First add the check mark column */
-	add_check_column (self, gcr_collection_model_column_selected (self->pv->model));
+	add_check_column (self, gcr_collection_model_column_for_selected (self->pv->model));
+
+	for (column = self->pv->columns, i = 0; column->property_name; ++column, ++i) {
+		if (column->flags & GCR_COLUMN_HIDDEN)
+			continue;
 
-	for (column = self->pv->columns, i = 0; column->property; ++column, ++i) {
-		if (column->type == G_TYPE_STRING)
+		if (column->column_type == G_TYPE_STRING)
 			add_string_column (self, column, i);
-		else if (column->type == G_TYPE_ICON)
+		else if (column->column_type == G_TYPE_ICON)
 			add_icon_column (self, column, i);
-		else {
+		else
 			g_warning ("skipping unsupported column '%s' of type: %s",
-			           column->label, g_type_name (column->type));
+			           column->property_name, g_type_name (column->column_type));
+
+		/* Setup the column itself */
+		if (column->flags & GCR_COLUMN_SORTABLE) {
+			if (sort_implementation_for_type (column->property_type))
+				gtk_tree_sortable_set_sort_func (sortable, i, on_sort_column,
+				                                 (gpointer)column, NULL);
+			else
+				g_warning ("no sort implementation defined for type '%s' on column '%s'",
+				           g_type_name (column->property_type), column->property_name);
 		}
 	}
 
diff --git a/gcr/gcr-selector.h b/gcr/gcr-selector.h
index 88fba51..6ddf6a5 100644
--- a/gcr/gcr-selector.h
+++ b/gcr/gcr-selector.h
@@ -52,15 +52,6 @@ struct _GcrSelector {
 
 struct _GcrSelectorClass {
 	GtkAlignmentClass parent_class;
-
-#if 0
-	/* signals --------------------------------------------------------- */
-
-	/* A callback for each password needed */
-	gboolean (*authenticate) (GcrSelector *self, gint count);
-
-	void     (*parsed) (GcrSelector *self);
-#endif
 };
 
 GType                    gcr_selector_get_type               (void);



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