gcr: Add support for GcrCollectionModel to have child rows
authorStef Walter <stefw@collabora.co.uk>
Fri, 2 Sep 2011 19:10:06 +0000 (21:10 +0200)
committerStef Walter <stefw@collabora.co.uk>
Mon, 5 Sep 2011 06:25:29 +0000 (08:25 +0200)
 * A child row is represented by an object that implements the
   GcrCollection

gcr/gcr-collection-model.c
gcr/gcr-collection-model.h
gcr/tests/frob-tree-selector.c

index 2ffb89d..16d15d0 100644 (file)
@@ -76,6 +76,12 @@ enum {
 };
 
 typedef struct {
+       GObject *object;
+       GSequenceIter *parent;
+       GSequence *children;
+} GcrCollectionRow;
+
+typedef struct {
        GtkTreeIterCompareFunc sort_func;
        gpointer user_data;
        GDestroyNotify destroy_func;
@@ -92,8 +98,8 @@ typedef struct _GcrCollectionColumn {
 struct _GcrCollectionModelPrivate {
        GcrCollection *collection;
        GHashTable *selected;
-       GSequence *object_sequence;
-       GHashTable *object_to_sequence;
+       GSequence *root_sequence;
+       GHashTable *object_to_seq;
 
        const GcrColumn *columns;
        guint n_columns;
@@ -237,15 +243,17 @@ order_sequence_by_closure (gconstpointer a,
 {
        GcrCollectionModel *self = GCR_COLLECTION_MODEL (user_data);
        GcrCollectionSortClosure *closure = self->pv->order_argument;
+       const GcrCollectionRow *row_a = a;
+       const GcrCollectionRow *row_b = b;
        GtkTreeIter iter_a;
        GtkTreeIter iter_b;
 
        g_assert (closure);
        g_assert (closure->sort_func);
 
-       if (!gcr_collection_model_iter_for_object (self, (GObject *)a, &iter_a))
+       if (!gcr_collection_model_iter_for_object (self, row_a->object, &iter_a))
                g_return_val_if_reached (0);
-       if (!gcr_collection_model_iter_for_object (self, (GObject *)b, &iter_b))
+       if (!gcr_collection_model_iter_for_object (self, row_b->object, &iter_b))
                g_return_val_if_reached (0);
 
        return (closure->sort_func) (GTK_TREE_MODEL (self),
@@ -265,7 +273,9 @@ order_sequence_as_unsorted (gconstpointer a,
                             gconstpointer b,
                             gpointer user_data)
 {
-       return GPOINTER_TO_INT (a) - GPOINTER_TO_INT (b);
+       const GcrCollectionRow *row_a = a;
+       const GcrCollectionRow *row_b = b;
+       return GPOINTER_TO_INT (row_a->object) - GPOINTER_TO_INT (row_b->object);
 }
 
 static gint
@@ -273,7 +283,22 @@ order_sequence_as_unsorted_reverse (gconstpointer a,
                                     gconstpointer b,
                                     gpointer user_data)
 {
-       return GPOINTER_TO_INT (b) - GPOINTER_TO_INT (a);
+       const GcrCollectionRow *row_a = a;
+       const GcrCollectionRow *row_b = b;
+       return GPOINTER_TO_INT (row_b->object) - GPOINTER_TO_INT (row_a->object);
+}
+
+static void
+lookup_object_property (GObject *object,
+                        const gchar *property_name,
+                        GValue *value)
+{
+       if (g_object_class_find_property (G_OBJECT_GET_CLASS (object), property_name))
+               g_object_get_property (object, property_name, value);
+
+       /* Other types have sane defaults */
+       else if (G_VALUE_TYPE (value) == G_TYPE_STRING)
+               g_value_set_string (value, "");
 }
 
 static gint
@@ -281,6 +306,8 @@ order_sequence_by_property (gconstpointer a,
                             gconstpointer b,
                             gpointer user_data)
 {
+       const GcrCollectionRow *row_a = a;
+       const GcrCollectionRow *row_b = b;
        GcrCollectionModel *self = GCR_COLLECTION_MODEL (user_data);
        const GcrColumn *column = self->pv->order_argument;
        GValue value_a = { 0, };
@@ -293,9 +320,9 @@ order_sequence_by_property (gconstpointer a,
        /* Sort according to property values */
        column = &self->pv->columns[self->pv->sort_column_id];
        g_value_init (&value_a, column->property_type);
-       g_object_get_property ((GObject *)a, column->property_name, &value_a);
+       lookup_object_property (row_a->object, column->property_name, &value_a);
        g_value_init (&value_b, column->property_type);
-       g_object_get_property ((GObject *)b, column->property_name, &value_b);
+       lookup_object_property (row_b->object, column->property_name, &value_b);
 
        compare = lookup_compare_func (column->property_type);
        g_assert (compare != NULL);
@@ -322,61 +349,68 @@ selected_hash_table_new (void)
        return g_hash_table_new (g_direct_hash, g_direct_equal);
 }
 
-static gint
-index_for_iter (GcrCollectionModel *self, const GtkTreeIter *iter)
+static gboolean
+sequence_iter_to_tree (GcrCollectionModel *self,
+                       GSequenceIter *seq,
+                       GtkTreeIter *iter)
 {
-       GSequenceIter *seq;
-       gint index;
+       GcrCollectionRow *row;
 
-       g_return_val_if_fail (iter, -1);
-       g_return_val_if_fail (iter->stamp == COLLECTION_MODEL_STAMP, -1);
+       g_return_val_if_fail (seq != NULL, FALSE);
 
-       seq = iter->user_data2;
-       g_return_val_if_fail (g_sequence_iter_get_sequence (seq) ==
-                             self->pv->object_sequence, -1);
-
-       index = g_sequence_iter_get_position (seq);
-       g_assert (index >= 0 && index < g_sequence_get_length (self->pv->object_sequence));
-       return index;
-}
-
-static gboolean
-iter_for_seq (GcrCollectionModel *self, GSequenceIter *seq, GtkTreeIter *iter)
-{
-       GObject *object;
+       if (g_sequence_iter_is_end (seq))
+               return FALSE;
 
-       object = g_sequence_get (seq);
-       g_return_val_if_fail (G_IS_OBJECT (object), FALSE);
+       row = g_sequence_get (seq);
+       g_return_val_if_fail (row != NULL && G_IS_OBJECT (row->object), FALSE);
 
        memset (iter, 0, sizeof (*iter));
        iter->stamp = COLLECTION_MODEL_STAMP;
-       iter->user_data = object;
+       iter->user_data = row->object;
        iter->user_data2 = seq;
        return TRUE;
 }
 
-static gboolean
-iter_for_index (GcrCollectionModel *self, gint index, GtkTreeIter *iter)
+static GSequenceIter *
+sequence_iter_for_tree (GcrCollectionModel *self,
+                        GtkTreeIter *iter)
 {
-       GSequenceIter *seq;
+       g_return_val_if_fail (iter != NULL, NULL);
+       g_return_val_if_fail (iter->stamp == COLLECTION_MODEL_STAMP, NULL);
+       return iter->user_data2;
+}
 
-       if (index < 0 || index >= g_sequence_get_length (self->pv->object_sequence))
-               return FALSE;
+static GtkTreePath *
+sequence_iter_to_path (GcrCollectionModel *self,
+                       GSequenceIter *seq)
+{
+       GcrCollectionRow *row;
+       GtkTreePath *path;
 
-       seq = g_sequence_get_iter_at_pos (self->pv->object_sequence, index);
-       return iter_for_seq (self, seq, iter);
+       path = gtk_tree_path_new ();
+       while (seq) {
+               gtk_tree_path_prepend_index (path, g_sequence_iter_get_position (seq));
+               row = g_sequence_get (seq);
+               seq = row->parent;
+       }
+       return path;
 }
 
-static gint
-index_for_object (GcrCollectionModel *self, GObject *object)
+static GSequence *
+child_sequence_for_tree (GcrCollectionModel *self,
+                         GtkTreeIter *iter)
 {
+       GcrCollectionRow *row;
        GSequenceIter *seq;
 
-       seq = g_hash_table_lookup (self->pv->object_to_sequence, object);
-       if (seq == NULL)
-               return -1;
-
-       return g_sequence_iter_get_position (seq);
+       if (iter == NULL) {
+               return self->pv->root_sequence;
+       } else {
+               seq = sequence_iter_for_tree (self, iter);
+               g_return_val_if_fail (seq != NULL, NULL);
+               row = g_sequence_get (seq);
+               return row->children;
+       }
 }
 
 static void
@@ -415,77 +449,196 @@ on_object_gone (gpointer unused, GObject *was_object)
                   "was destroyed before it was removed from the collection");
 }
 
+static void      on_collection_added              (GcrCollection *collection,
+                                                   GObject *object,
+                                                   gpointer user_data);
+
+static void      on_collection_removed            (GcrCollection *collection,
+                                                   GObject *object,
+                                                   gpointer user_data);
+
+static void      add_object_to_sequence           (GcrCollectionModel *self,
+                                                   GSequence *sequence,
+                                                   GSequenceIter *parent,
+                                                   GObject *object,
+                                                   gboolean emit);
+
+static void      remove_object_from_sequence      (GcrCollectionModel *self,
+                                                   GSequence *sequence,
+                                                   GSequenceIter *seq,
+                                                   GObject *object,
+                                                   gboolean emit);
+
+static void
+add_children_to_sequence (GcrCollectionModel *self,
+                          GSequence *sequence,
+                          GSequenceIter *parent,
+                          GcrCollection *collection,
+                          gboolean emit)
+{
+       GList *children, *l;
+
+       children = gcr_collection_get_objects (collection);
+       for (l = children; l; l = g_list_next (l))
+               add_object_to_sequence (self, sequence, parent, l->data, emit);
+       g_list_free (children);
+
+       /* Now listen in for any changes */
+       g_signal_connect_after (collection, "added", G_CALLBACK (on_collection_added), self);
+       g_signal_connect_after (collection, "removed", G_CALLBACK (on_collection_removed), self);
+}
+
 static void
-on_collection_added (GcrCollection *collection, GObject *object, GcrCollectionModel *self)
+add_object_to_sequence (GcrCollectionModel *self,
+                        GSequence *sequence,
+                        GSequenceIter *parent,
+                        GObject *object,
+                        gboolean emit)
 {
+       GcrCollectionRow *row;
        GSequenceIter *seq;
        GtkTreeIter iter;
        GtkTreePath *path;
 
-       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));
        g_assert (self->pv->order_current);
 
-       seq = g_sequence_insert_sorted (self->pv->object_sequence, object,
-                                       self->pv->order_current, self);
-       g_hash_table_insert (self->pv->object_to_sequence, object, seq);
+       if (g_hash_table_lookup (self->pv->object_to_seq, object)) {
+               g_warning ("object was already added to the GcrCollectionModel. Perhaps "
+                          "a loop exists in a tree structure?");
+               return;
+       }
+
+       row = g_slice_new0 (GcrCollectionRow);
+       row->object = object;
+       row->parent = parent;
+       row->children = NULL;
+
+       seq = g_sequence_insert_sorted (sequence, row, self->pv->order_current, self);
+       g_hash_table_insert (self->pv->object_to_seq, object, seq);
        g_object_weak_ref (G_OBJECT (object), (GWeakNotify)on_object_gone, self);
        g_signal_connect (object, "notify", G_CALLBACK (on_object_notify), self);
 
-       /* Fire signal for this added row */
-       if (!iter_for_seq (self, seq, &iter))
-               g_assert_not_reached ();
-
-       path = gtk_tree_path_new_from_indices (g_sequence_iter_get_position (seq), -1);
+       if (emit) {
+               if (!sequence_iter_to_tree (self, seq, &iter))
+                       g_assert_not_reached ();
+               path = sequence_iter_to_path (self, seq);
+               g_assert (path != NULL);
+               gtk_tree_model_row_inserted (GTK_TREE_MODEL (self), path, &iter);
+               gtk_tree_path_free (path);
+       }
 
-       gtk_tree_model_row_inserted (GTK_TREE_MODEL (self), path, &iter);
-       gtk_tree_path_free (path);
+       if (GCR_IS_COLLECTION (object)) {
+               row->children = g_sequence_new (NULL);
+               add_children_to_sequence (self, row->children, seq,
+                                         GCR_COLLECTION (object), emit);
+       }
 }
 
 static void
-disconnect_object (GcrCollectionModel *self, GObject *object)
+on_collection_added (GcrCollection *collection,
+                     GObject *object,
+                     gpointer user_data)
 {
-       g_object_weak_unref (G_OBJECT (object), on_object_gone, self);
-       g_signal_handlers_disconnect_by_func (object, on_object_notify, self);
+       GcrCollectionModel *self = GCR_COLLECTION_MODEL (user_data);
+       GSequence *sequence;
+       GSequenceIter *parent;
+       GcrCollectionRow *row;
+
+       if (collection == self->pv->collection) {
+               sequence = self->pv->root_sequence;
+               parent = NULL;
+       } else {
+               parent = g_hash_table_lookup (self->pv->object_to_seq, G_OBJECT (collection));
+               row = g_sequence_get (parent);
+               g_assert (row->children);
+               sequence = row->children;
+       }
+
+       add_object_to_sequence (self, sequence, parent, object, TRUE);
 }
 
 static void
-on_collection_removed (GcrCollection *collection, GObject *object,
-                       GcrCollectionModel *self)
+remove_children_from_sequence (GcrCollectionModel *self,
+                               GSequence *sequence,
+                               GcrCollection *collection,
+                               gboolean emit)
 {
-       GtkTreePath *path;
-       GSequenceIter *seq;
+       GSequenceIter *seq, *next;
+       GcrCollectionRow *row;
 
-       g_return_if_fail (GCR_COLLECTION_MODEL (self));
-       g_return_if_fail (G_IS_OBJECT (object));
+       g_signal_handlers_disconnect_by_func (collection, on_collection_added, self);
+       g_signal_handlers_disconnect_by_func (collection, on_collection_removed, self);
 
-       seq = g_hash_table_lookup (self->pv->object_to_sequence, object);
-       g_return_if_fail (seq != NULL);
+       for (seq = g_sequence_get_begin_iter (sequence);
+            !g_sequence_iter_is_end (seq); seq = next) {
+               next = g_sequence_iter_next (seq);
+               row = g_sequence_get (seq);
+               remove_object_from_sequence (self, sequence, seq, row->object, emit);
+       }
+}
 
-       path = gtk_tree_path_new_from_indices (g_sequence_iter_get_position (seq), -1);
+static void
+remove_object_from_sequence (GcrCollectionModel *self,
+                             GSequence *sequence,
+                             GSequenceIter *seq,
+                             GObject *object,
+                             gboolean emit)
+{
+       GcrCollectionRow *row;
+       GtkTreePath *path = NULL;
+
+       if (emit) {
+               path = sequence_iter_to_path (self, seq);
+               g_assert (path != NULL);
+       }
 
-       disconnect_object (self, object);
+       row = g_sequence_get (seq);
+       g_assert (row->object == object);
+
+       g_object_weak_unref (object, on_object_gone, self);
+       g_signal_handlers_disconnect_by_func (object, on_object_notify, self);
+
+       if (row->children) {
+               g_assert (GCR_IS_COLLECTION (object));
+               remove_children_from_sequence (self, row->children, GCR_COLLECTION (object), emit);
+               g_assert (g_sequence_get_length (row->children) == 0);
+               g_sequence_free (row->children);
+               row->children = NULL;
+       }
 
        g_hash_table_remove (self->pv->selected, object);
-       g_hash_table_remove (self->pv->object_to_sequence, object);
+       if (!g_hash_table_remove (self->pv->object_to_seq, object))
+               g_assert_not_reached ();
+
        g_sequence_remove (seq);
+       g_slice_free (GcrCollectionRow, row);
 
        /* Fire signal for this removed row */
-       gtk_tree_model_row_deleted (GTK_TREE_MODEL (self), path);
-       gtk_tree_path_free (path);
+       if (path != NULL) {
+               gtk_tree_model_row_deleted (GTK_TREE_MODEL (self), path);
+               gtk_tree_path_free (path);
+       }
+
 }
 
 static void
-populate_model (GcrCollectionModel *self)
+on_collection_removed (GcrCollection *collection,
+                       GObject *object,
+                       gpointer user_data)
 {
-       GList *objects, *l;
-       objects = gcr_collection_get_objects (self->pv->collection);
-       for (l = objects; l; l = g_list_next (l))
-               on_collection_added (self->pv->collection, G_OBJECT (l->data), self);
-       g_list_free (objects);
+       GcrCollectionModel *self = GCR_COLLECTION_MODEL (user_data);
+       GSequenceIter *seq;
+       GSequence *sequence;
+
+       seq = g_hash_table_lookup (self->pv->object_to_seq, object);
+       g_return_if_fail (seq != NULL);
+
+       sequence = g_sequence_iter_get_sequence (seq);
+       g_assert (sequence != NULL);
+
+       remove_object_from_sequence (self, sequence, seq, object, TRUE);
 }
 
 static void
@@ -514,7 +667,8 @@ gcr_collection_model_real_get_n_columns (GtkTreeModel *model)
 }
 
 static GType
-gcr_collection_model_real_get_column_type (GtkTreeModel *model, gint column_id)
+gcr_collection_model_real_get_column_type (GtkTreeModel *model,
+                                           gint column_id)
 {
        GcrCollectionModel *self = GCR_COLLECTION_MODEL (model);
        g_return_val_if_fail (column_id >= 0 && column_id <= self->pv->n_columns, 0);
@@ -527,43 +681,64 @@ gcr_collection_model_real_get_column_type (GtkTreeModel *model, gint column_id)
 }
 
 static gboolean
-gcr_collection_model_real_get_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreePath *path)
+gcr_collection_model_real_get_iter (GtkTreeModel *model,
+                                    GtkTreeIter *iter,
+                                    GtkTreePath *path)
 {
        GcrCollectionModel *self = GCR_COLLECTION_MODEL (model);
        const gint *indices;
+       GSequence *sequence;
+       GSequenceIter *seq;
+       GcrCollectionRow *row;
        gint count;
+       gint i;
 
-       count = gtk_tree_path_get_depth (path);
-       if (count != 1)
+       sequence = self->pv->root_sequence;
+       seq = NULL;
+
+       indices = gtk_tree_path_get_indices_with_depth (path, &count);
+       if (count == 0)
                return FALSE;
 
-       indices = gtk_tree_path_get_indices (path);
-       return iter_for_index (self, indices[0], iter);
+       for (i = 0; i < count; i++) {
+               if (!sequence)
+                       return FALSE;
+               seq = g_sequence_get_iter_at_pos (sequence, indices[i]);
+               if (g_sequence_iter_is_end (seq))
+                       return FALSE;
+               row = g_sequence_get (seq);
+               sequence = row->children;
+       }
+
+       return sequence_iter_to_tree (self, seq, iter);
 }
 
 static GtkTreePath*
-gcr_collection_model_real_get_path (GtkTreeModel *model, GtkTreeIter *iter)
+gcr_collection_model_real_get_path (GtkTreeModel *model,
+                                    GtkTreeIter *iter)
 {
        GcrCollectionModel *self = GCR_COLLECTION_MODEL (model);
-       GtkTreePath *path;
-       gint index;
+       GSequenceIter *seq;
 
-       index = index_for_iter (self, iter);
-       g_return_val_if_fail (index >= 0, NULL);
+       if (iter == NULL)
+               return gtk_tree_path_new ();
 
-       path = gtk_tree_path_new ();
-       gtk_tree_path_prepend_index (path, index);
-       return path;
+       seq = sequence_iter_for_tree (self, iter);
+       g_return_val_if_fail (seq != NULL, NULL);
+       return sequence_iter_to_path (self, seq);
 }
 
 static void
-gcr_collection_model_real_get_value (GtkTreeModel *model, GtkTreeIter *iter,
-                                     gint column_id, GValue *value)
+gcr_collection_model_real_get_value (GtkTreeModel *model,
+                                     GtkTreeIter *iter,
+                                     gint column_id,
+                                     GValue *value)
 {
        GcrCollectionModel *self = GCR_COLLECTION_MODEL (model);
        GObject *object;
        GValue original;
        const GcrColumn *column;
+       GParamSpec *spec;
 
        object = gcr_collection_model_object_for_iter (self, iter);
        g_return_if_fail (G_IS_OBJECT (object));
@@ -581,93 +756,122 @@ gcr_collection_model_real_get_value (GtkTreeModel *model, GtkTreeIter *iter,
        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);
+       /* Lookup the property on the object */
+       spec = g_object_class_find_property (G_OBJECT_GET_CLASS (object), column->property_name);
+       if (spec != NULL) {
+               /* 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 {
+                               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));
+                               spec = NULL;
+                       }
 
-               if (column->transformer) {
-                       (column->transformer) (&original, value);
+               /* Simple, no transformation necessary */
                } else {
-                       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));
+                       g_object_get_property (object, column->property_name, value);
                }
+       }
 
-       /* Simple, no transformation necessary */
-       } else {
-               g_object_get_property (object, column->property_name, value);
+       if (spec == NULL) {
+
+               /* All the number types have sane defaults */
+               if (column->column_type == G_TYPE_STRING)
+                       g_value_set_string (value, "");
        }
 }
 
 static gboolean
-gcr_collection_model_real_iter_next (GtkTreeModel *model, GtkTreeIter *iter)
+gcr_collection_model_real_iter_next (GtkTreeModel *model,
+                                     GtkTreeIter *iter)
 {
        GcrCollectionModel *self = GCR_COLLECTION_MODEL (model);
-       gint index;
-
-       index = index_for_iter (self, iter);
-       g_return_val_if_fail (index >= 0, FALSE);
-
-       return iter_for_index (self, index + 1, iter);
+       GSequenceIter *seq = sequence_iter_for_tree (self, iter);
+       g_return_val_if_fail (seq != NULL, FALSE);
+       return sequence_iter_to_tree (self, g_sequence_iter_next (seq), iter);
 }
 
 static gboolean
-gcr_collection_model_real_iter_children (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *parent)
+gcr_collection_model_real_iter_children (GtkTreeModel *model,
+                                         GtkTreeIter *iter,
+                                         GtkTreeIter *parent)
 {
        GcrCollectionModel *self = GCR_COLLECTION_MODEL (model);
-
-       if (parent != NULL)
-               return FALSE;
-
-       return iter_for_index (self, 0, iter);
+       GSequence *sequence = child_sequence_for_tree (self, parent);
+       return sequence && sequence_iter_to_tree (self, g_sequence_get_begin_iter (sequence), iter);
 }
 
 static gboolean
-gcr_collection_model_real_iter_has_child (GtkTreeModel *model, GtkTreeIter *iter)
+gcr_collection_model_real_iter_has_child (GtkTreeModel *model,
+                                          GtkTreeIter *iter)
 {
        GcrCollectionModel *self = GCR_COLLECTION_MODEL (model);
-       if (iter == NULL)
-               return !g_sequence_iter_is_end (g_sequence_get_begin_iter (self->pv->object_sequence));
-       return FALSE;
+       GSequence *sequence = child_sequence_for_tree (self, iter);
+       return sequence && !g_sequence_iter_is_end (g_sequence_get_begin_iter (sequence));
 }
 
 static gint
-gcr_collection_model_real_iter_n_children (GtkTreeModel *model, GtkTreeIter *iter)
+gcr_collection_model_real_iter_n_children (GtkTreeModel *model,
+                                           GtkTreeIter *iter)
 {
        GcrCollectionModel *self = GCR_COLLECTION_MODEL (model);
-       if (iter == NULL)
-               return g_sequence_get_length (self->pv->object_sequence);
-       return 0;
+       GSequence *sequence = child_sequence_for_tree (self, iter);
+       return sequence ? g_sequence_get_length (sequence) : 0;
 }
 
 static gboolean
-gcr_collection_model_real_iter_nth_child (GtkTreeModel *model, GtkTreeIter *iter,
-                                          GtkTreeIter *parent, gint n)
+gcr_collection_model_real_iter_nth_child (GtkTreeModel *model,
+                                          GtkTreeIter *iter,
+                                          GtkTreeIter *parent,
+                                          gint n)
 {
        GcrCollectionModel *self = GCR_COLLECTION_MODEL (model);
-       if (parent != NULL)
+       GSequence *sequence;
+       GSequenceIter *seq;
+
+       sequence = child_sequence_for_tree (self, parent);
+       if (sequence == NULL)
                return FALSE;
-       return iter_for_index (self, n, iter);
+       seq = g_sequence_get_iter_at_pos (sequence, n);
+       return sequence_iter_to_tree (self, seq, iter);
 }
 
 static gboolean
-gcr_collection_model_real_iter_parent (GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreeIter *child)
+gcr_collection_model_real_iter_parent (GtkTreeModel *model,
+                                       GtkTreeIter *iter,
+                                       GtkTreeIter *child)
 {
-       return FALSE;
+       GcrCollectionModel *self = GCR_COLLECTION_MODEL (model);
+       GSequenceIter *seq;
+       GcrCollectionRow *row;
+
+       seq = sequence_iter_for_tree (self, child);
+       g_return_val_if_fail (seq != NULL, FALSE);
+       row = g_sequence_get (seq);
+       if (row->parent == NULL)
+               return FALSE;
+       return sequence_iter_to_tree (self, row->parent, iter);
 }
 
 static void
-gcr_collection_model_real_ref_node (GtkTreeModel *model, GtkTreeIter *iter)
+gcr_collection_model_real_ref_node (GtkTreeModel *model,
+                                    GtkTreeIter *iter)
 {
        /* Nothing to do */
 }
 
 static void
-gcr_collection_model_real_unref_node (GtkTreeModel *model, GtkTreeIter *iter)
+gcr_collection_model_real_unref_node (GtkTreeModel *model,
+                                      GtkTreeIter *iter)
 {
        /* Nothing to do */
 }
@@ -692,29 +896,38 @@ gcr_collection_model_tree_model_init (GtkTreeModelIface *iface)
 }
 
 static void
-collection_resort (GcrCollectionModel *self)
+collection_resort_sequence (GcrCollectionModel *self,
+                            GSequenceIter *parent,
+                            GSequence *sequence)
 {
        GPtrArray *previous;
-       GSequenceIter *seq;
+       GSequenceIter *seq, *next;
        gint *new_order;
        GtkTreePath *path;
+       GtkTreeIter iter;
+       GcrCollectionRow *row;
        gint index;
        gint i;
 
-       /* Make note of how things stand */
+       /* Make note of how things stand, and at same time resort all kids */
        previous = g_ptr_array_new ();
-       for (seq = g_sequence_get_begin_iter (self->pv->object_sequence);
-            !g_sequence_iter_is_end (seq); seq = g_sequence_iter_next (seq))
-               g_ptr_array_add (previous, g_sequence_get (seq));
+       for (seq = g_sequence_get_begin_iter (sequence);
+            !g_sequence_iter_is_end (seq); seq = next) {
+               next = g_sequence_iter_next (seq);
+               row = g_sequence_get (seq);
+               if (row->children)
+                       collection_resort_sequence (self, seq, row->children);
+               g_ptr_array_add (previous, row->object);
+       }
 
        /* Actually perform the sort */
-       g_sequence_sort (self->pv->object_sequence, self->pv->order_current, self);
+       g_sequence_sort (sequence, self->pv->order_current, self);
 
        /* Now go through and map out how things changed */
        new_order = g_new0 (gint, previous->len);
        for (i = 0; i < previous->len; i++) {
-               seq = g_hash_table_lookup (self->pv->object_to_sequence,
-                                          previous->pdata[i]);
+               seq = g_hash_table_lookup (self->pv->object_to_seq, previous->pdata[i]);
+               g_assert (seq != NULL);
                index = g_sequence_iter_get_position (seq);
                g_assert (index >= 0 && index < previous->len);
                new_order[index] = i;
@@ -722,8 +935,15 @@ collection_resort (GcrCollectionModel *self)
 
        g_ptr_array_free (previous, TRUE);
 
-       path = gtk_tree_path_new ();
-       gtk_tree_model_rows_reordered (GTK_TREE_MODEL (self), path, NULL, new_order);
+       path = sequence_iter_to_path (self, parent);
+       if (parent == NULL) {
+               gtk_tree_model_rows_reordered (GTK_TREE_MODEL (self), path, NULL, new_order);
+       } else {
+               if (!sequence_iter_to_tree (self, parent, &iter))
+                       g_assert_not_reached ();
+               gtk_tree_model_rows_reordered (GTK_TREE_MODEL (self), path, &iter, new_order);
+       }
+       gtk_tree_path_free (path);
        g_free (new_order);
 }
 
@@ -797,7 +1017,7 @@ gcr_collection_model_set_sort_column_id (GtkTreeSortable *sortable,
            argument != self->pv->order_argument) {
                self->pv->order_current = func;
                self->pv->order_argument = (gpointer)argument;
-               collection_resort (self);
+               collection_resort_sequence (self, NULL, self->pv->root_sequence);
        }
 }
 
@@ -886,8 +1106,8 @@ gcr_collection_model_init (GcrCollectionModel *self)
 {
        self->pv = G_TYPE_INSTANCE_GET_PRIVATE (self, GCR_TYPE_COLLECTION_MODEL, GcrCollectionModelPrivate);
 
-       self->pv->object_sequence = g_sequence_new (NULL);
-       self->pv->object_to_sequence = g_hash_table_new (g_direct_hash, g_direct_equal);
+       self->pv->root_sequence = g_sequence_new (NULL);
+       self->pv->object_to_seq = g_hash_table_new (g_direct_hash, g_direct_equal);
        self->pv->sort_column_id = GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID;
        self->pv->sort_order_type = GTK_SORT_ASCENDING;
        self->pv->order_current = order_sequence_as_unsorted;
@@ -904,10 +1124,11 @@ gcr_collection_model_set_property (GObject *object, guint prop_id,
        case PROP_COLLECTION:
                g_return_if_fail (self->pv->collection == NULL);
                self->pv->collection = g_value_dup_object (value);
+
+               /* During construction, so we don't emit anything */
                if (self->pv->collection) {
-                       g_signal_connect_after (self->pv->collection, "added", G_CALLBACK (on_collection_added), self);
-                       g_signal_connect_after (self->pv->collection, "removed", G_CALLBACK (on_collection_removed), self);
-                       populate_model (self);
+                       add_children_to_sequence (self, self->pv->root_sequence,
+                                                 NULL, self->pv->collection, FALSE);
                }
                break;
 
@@ -948,30 +1169,15 @@ static void
 gcr_collection_model_dispose (GObject *object)
 {
        GcrCollectionModel *self = GCR_COLLECTION_MODEL (object);
-       GSequenceIter *seq;
-       GSequenceIter *next;
 
        /* Disconnect from all rows */
-       for (seq = g_sequence_get_begin_iter (self->pv->object_sequence);
-            !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);
-       }
-
-       g_hash_table_remove_all (self->pv->object_to_sequence);
-
-       /* Disconnect from the collection */
        if (self->pv->collection) {
-               g_signal_handlers_disconnect_by_func (self->pv->collection, on_collection_added, self);
-               g_signal_handlers_disconnect_by_func (self->pv->collection, on_collection_removed, self);
+               remove_children_from_sequence (self, self->pv->root_sequence,
+                                              self->pv->collection, FALSE);
                g_object_unref (self->pv->collection);
                self->pv->collection = NULL;
        }
 
-       if (self->pv->selected)
-               g_hash_table_remove_all (self->pv->selected);
-
        G_OBJECT_CLASS (gcr_collection_model_parent_class)->dispose (object);
 }
 
@@ -983,11 +1189,12 @@ gcr_collection_model_finalize (GObject *object)
 
        g_assert (!self->pv->collection);
 
-       g_assert (g_sequence_get_length (self->pv->object_sequence) == 0);
-       g_sequence_free (self->pv->object_sequence);
-       g_assert (g_hash_table_size (self->pv->object_to_sequence) == 0);
-       g_hash_table_destroy (self->pv->object_to_sequence);
+       g_assert (g_sequence_get_length (self->pv->root_sequence) == 0);
+       g_sequence_free (self->pv->root_sequence);
+       g_assert (g_hash_table_size (self->pv->object_to_seq) == 0);
+       g_hash_table_destroy (self->pv->object_to_seq);
 
+       g_assert (g_hash_table_size (self->pv->selected) == 0);
        if (self->pv->selected)
                g_hash_table_destroy (self->pv->selected);
        self->pv->selected = NULL;
@@ -1153,17 +1360,17 @@ gboolean
 gcr_collection_model_iter_for_object (GcrCollectionModel *self, GObject *object,
                                       GtkTreeIter *iter)
 {
-       gint index;
+       GSequenceIter *seq;
 
        g_return_val_if_fail (GCR_IS_COLLECTION_MODEL (self), FALSE);
        g_return_val_if_fail (G_IS_OBJECT (object), FALSE);
        g_return_val_if_fail (iter, FALSE);
 
-       index = index_for_object (self, object);
-       if (index < 0)
+       seq = g_hash_table_lookup (self->pv->object_to_seq, object);
+       if (seq == NULL)
                return FALSE;
 
-       return iter_for_index (self, index, iter);
+       return sequence_iter_to_tree (self, seq, iter);
 }
 
 /**
index 4cfc293..c8a1775 100644 (file)
@@ -60,13 +60,6 @@ GcrCollectionModel*   gcr_collection_model_new_full            (GcrCollection *c
 void                  gcr_collection_model_set_columns         (GcrCollectionModel *self,
                                                                 const GcrColumn *columns);
 
-void                  gcr_collection_model_get_child_property  (GcrCollectionModel *self,
-                                                                const gchar *property_name);
-
-void                  gcr_collection_model_set_child_property  (GcrCollectionModel *self,
-                                                                const gchar *property_name,
-                                                                GValueTransform transformer);
-
 GObject*              gcr_collection_model_object_for_iter     (GcrCollectionModel *self,
                                                                 const GtkTreeIter *iter);
 
index d756f3d..313781e 100644 (file)
 #include <string.h>
 #include <errno.h>
 
+#define TEST_TYPE_COLLECTION               (test_collection_get_type ())
+#define TEST_COLLECTION(obj)               (G_TYPE_CHECK_INSTANCE_CAST ((obj), TEST_TYPE_COLLECTION, TestCollection))
+#define TEST_IS_COLLECTION(obj)            (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TEST_TYPE_COLLECTION))
+
+typedef struct _TestCollection TestCollection;
+typedef struct _TestCollectionClass TestCollectionClass;
+typedef struct _TestCollectionPrivate TestCollectionPrivate;
+
+struct _TestCollection {
+       GcrSimpleCollection parent;
+       gchar *label;
+};
+
+struct _TestCollectionClass {
+       GcrSimpleCollectionClass parent_class;
+};
+
+GType test_collection_get_type (void) G_GNUC_CONST;
+
+enum {
+       PROP_0,
+       PROP_LABEL,
+};
+
+G_DEFINE_TYPE (TestCollection, test_collection, GCR_TYPE_SIMPLE_COLLECTION);
+
+static GHashTable *all_collections = NULL;
+
+static void
+test_collection_init (TestCollection *self)
+{
+
+}
+
+static void
+test_collection_finalize (GObject *obj)
+{
+       TestCollection *self = TEST_COLLECTION (obj);
+       g_free (self->label);
+       g_hash_table_remove (all_collections, self);
+       G_OBJECT_CLASS (test_collection_parent_class)->finalize (obj);
+}
+
+static void
+test_collection_get_property (GObject *obj,
+                              guint prop_id,
+                              GValue *value,
+                              GParamSpec *pspec)
+{
+       TestCollection *self = TEST_COLLECTION (obj);
+       switch (prop_id) {
+       case PROP_LABEL:
+               g_value_set_string (value, self->label);
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
+               break;
+       }
+}
+
+static void
+test_collection_class_init (TestCollectionClass *klass)
+{
+       GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+       gobject_class->get_property = test_collection_get_property;
+       gobject_class->finalize = test_collection_finalize;
+
+       g_object_class_install_property (gobject_class, PROP_LABEL,
+                  g_param_spec_string ("label", "label", "label", NULL, G_PARAM_READABLE));
+}
+
+static GcrSimpleCollection *
+test_collection_instance (const gchar *label)
+{
+       TestCollection *collection = NULL;
+
+       g_assert (label);
+
+       if (!all_collections) {
+               all_collections = g_hash_table_new (g_str_hash, g_str_equal);
+       } else {
+               collection = g_hash_table_lookup (all_collections, label);
+               if (collection != NULL)
+                       return g_object_ref (collection);
+       }
+
+       collection = g_object_new (TEST_TYPE_COLLECTION, NULL);
+       collection->label = g_strdup (label);
+       g_hash_table_insert (all_collections, collection->label, collection);
+       return GCR_SIMPLE_COLLECTION (collection);
+}
+
 static void
 on_parser_parsed (GcrParser *parser, gpointer user_data)
 {
        GcrSimpleCollection *collection = user_data;
+       GcrSimpleCollection *testcol;
        GcrRenderer *renderer;
+       gchar *group;
 
        renderer = gcr_renderer_create (gcr_parser_get_parsed_label (parser),
                                        gcr_parser_get_parsed_attributes (parser));
+       if (renderer == NULL)
+               return;
 
-       if (renderer) {
-               gcr_simple_collection_add (collection, G_OBJECT (renderer));
-               g_object_unref (renderer);
-       }
+       if (GCR_IS_CERTIFICATE (renderer))
+               group = gcr_certificate_get_subject_part (GCR_CERTIFICATE (renderer), "O");
+       else
+               group = g_strdup (G_OBJECT_TYPE_NAME (renderer));
+
+
+       testcol = test_collection_instance (group);
+       if (!gcr_simple_collection_contains (collection, G_OBJECT (testcol)))
+               gcr_simple_collection_add (collection, G_OBJECT (testcol));
+
+       gcr_simple_collection_add (GCR_SIMPLE_COLLECTION (testcol), G_OBJECT (renderer));
+       g_object_unref (renderer);
+       g_object_unref (testcol);
+       g_free (group);
 }
 
 static void