aboutsummaryrefslogtreecommitdiffstats
path: root/e-util/e-tree-model-generator.c
diff options
context:
space:
mode:
Diffstat (limited to 'e-util/e-tree-model-generator.c')
-rw-r--r--e-util/e-tree-model-generator.c1345
1 files changed, 1345 insertions, 0 deletions
diff --git a/e-util/e-tree-model-generator.c b/e-util/e-tree-model-generator.c
new file mode 100644
index 0000000000..aff912998c
--- /dev/null
+++ b/e-util/e-tree-model-generator.c
@@ -0,0 +1,1345 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/* e-tree-model-generator.c - Model wrapper that permutes underlying rows.
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Authors: Hans Petter Jansson <hpj@novell.com>
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <string.h>
+#include <glib/gi18n-lib.h>
+#include "e-tree-model-generator.h"
+
+#define ETMG_DEBUG(x)
+
+#define ITER_IS_VALID(tree_model_generator, iter) \
+ ((iter)->stamp == (tree_model_generator)->priv->stamp)
+#define ITER_GET(iter, group, index) \
+ G_STMT_START { \
+ *(group) = (iter)->user_data; \
+ *(index) = GPOINTER_TO_INT ((iter)->user_data2); \
+ } G_STMT_END
+
+#define ITER_SET(tree_model_generator, iter, group, index) \
+ G_STMT_START { \
+ (iter)->stamp = (tree_model_generator)->priv->stamp; \
+ (iter)->user_data = group; \
+ (iter)->user_data2 = GINT_TO_POINTER (index); \
+ } G_STMT_END
+
+#define E_TREE_MODEL_GENERATOR_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_TREE_MODEL_GENERATOR, ETreeModelGeneratorPrivate))
+
+struct _ETreeModelGeneratorPrivate {
+ GtkTreeModel *child_model;
+ GArray *root_nodes;
+ gint stamp;
+
+ ETreeModelGeneratorGenerateFunc generate_func;
+ gpointer generate_func_data;
+
+ ETreeModelGeneratorModifyFunc modify_func;
+ gpointer modify_func_data;
+};
+
+static void e_tree_model_generator_tree_model_init (GtkTreeModelIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (
+ ETreeModelGenerator, e_tree_model_generator, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_MODEL, e_tree_model_generator_tree_model_init))
+
+static GtkTreeModelFlags e_tree_model_generator_get_flags (GtkTreeModel *tree_model);
+static gint e_tree_model_generator_get_n_columns (GtkTreeModel *tree_model);
+static GType e_tree_model_generator_get_column_type (GtkTreeModel *tree_model,
+ gint index);
+static gboolean e_tree_model_generator_get_iter (GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ GtkTreePath *path);
+static GtkTreePath *e_tree_model_generator_get_path (GtkTreeModel *tree_model,
+ GtkTreeIter *iter);
+static void e_tree_model_generator_get_value (GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ gint column,
+ GValue *value);
+static gboolean e_tree_model_generator_iter_next (GtkTreeModel *tree_model,
+ GtkTreeIter *iter);
+static gboolean e_tree_model_generator_iter_children (GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ GtkTreeIter *parent);
+static gboolean e_tree_model_generator_iter_has_child (GtkTreeModel *tree_model,
+ GtkTreeIter *iter);
+static gint e_tree_model_generator_iter_n_children (GtkTreeModel *tree_model,
+ GtkTreeIter *iter);
+static gboolean e_tree_model_generator_iter_nth_child (GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ GtkTreeIter *parent,
+ gint n);
+static gboolean e_tree_model_generator_iter_parent (GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ GtkTreeIter *child);
+
+static GArray *build_node_map (ETreeModelGenerator *tree_model_generator, GtkTreeIter *parent_iter,
+ GArray *parent_group, gint parent_index);
+static void release_node_map (GArray *group);
+
+static void child_row_changed (ETreeModelGenerator *tree_model_generator, GtkTreePath *path, GtkTreeIter *iter);
+static void child_row_inserted (ETreeModelGenerator *tree_model_generator, GtkTreePath *path, GtkTreeIter *iter);
+static void child_row_deleted (ETreeModelGenerator *tree_model_generator, GtkTreePath *path);
+
+typedef struct {
+ GArray *parent_group;
+ gint parent_index;
+
+ gint n_generated;
+ GArray *child_nodes;
+}
+Node;
+
+enum {
+ PROP_0,
+ PROP_CHILD_MODEL
+};
+
+/* ------------------ *
+ * Class/object setup *
+ * ------------------ */
+
+static void
+tree_model_generator_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ ETreeModelGenerator *tree_model_generator = E_TREE_MODEL_GENERATOR (object);
+
+ switch (prop_id)
+ {
+ case PROP_CHILD_MODEL:
+ tree_model_generator->priv->child_model = g_value_get_object (value);
+ g_object_ref (tree_model_generator->priv->child_model);
+
+ if (tree_model_generator->priv->root_nodes)
+ release_node_map (tree_model_generator->priv->root_nodes);
+ tree_model_generator->priv->root_nodes =
+ build_node_map (tree_model_generator, NULL, NULL, -1);
+
+ g_signal_connect_swapped (
+ tree_model_generator->priv->child_model, "row-changed",
+ G_CALLBACK (child_row_changed), tree_model_generator);
+ g_signal_connect_swapped (
+ tree_model_generator->priv->child_model, "row-deleted",
+ G_CALLBACK (child_row_deleted), tree_model_generator);
+ g_signal_connect_swapped (
+ tree_model_generator->priv->child_model, "row-inserted",
+ G_CALLBACK (child_row_inserted), tree_model_generator);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+tree_model_generator_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ ETreeModelGenerator *tree_model_generator = E_TREE_MODEL_GENERATOR (object);
+
+ switch (prop_id)
+ {
+ case PROP_CHILD_MODEL:
+ g_value_set_object (value, tree_model_generator->priv->child_model);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+tree_model_generator_finalize (GObject *object)
+{
+ ETreeModelGenerator *tree_model_generator = E_TREE_MODEL_GENERATOR (object);
+
+ if (tree_model_generator->priv->child_model) {
+ g_signal_handlers_disconnect_matched (
+ tree_model_generator->priv->child_model,
+ G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL,
+ tree_model_generator);
+ g_object_unref (tree_model_generator->priv->child_model);
+ }
+
+ if (tree_model_generator->priv->root_nodes)
+ release_node_map (tree_model_generator->priv->root_nodes);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (e_tree_model_generator_parent_class)->finalize (object);
+}
+
+static void
+e_tree_model_generator_class_init (ETreeModelGeneratorClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (class, sizeof (ETreeModelGeneratorPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->get_property = tree_model_generator_get_property;
+ object_class->set_property = tree_model_generator_set_property;
+ object_class->finalize = tree_model_generator_finalize;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_CHILD_MODEL,
+ g_param_spec_object (
+ "child-model",
+ "Child Model",
+ "The child model to extend",
+ G_TYPE_OBJECT,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+e_tree_model_generator_tree_model_init (GtkTreeModelIface *iface)
+{
+ iface->get_flags = e_tree_model_generator_get_flags;
+ iface->get_n_columns = e_tree_model_generator_get_n_columns;
+ iface->get_column_type = e_tree_model_generator_get_column_type;
+ iface->get_iter = e_tree_model_generator_get_iter;
+ iface->get_path = e_tree_model_generator_get_path;
+ iface->get_value = e_tree_model_generator_get_value;
+ iface->iter_next = e_tree_model_generator_iter_next;
+ iface->iter_children = e_tree_model_generator_iter_children;
+ iface->iter_has_child = e_tree_model_generator_iter_has_child;
+ iface->iter_n_children = e_tree_model_generator_iter_n_children;
+ iface->iter_nth_child = e_tree_model_generator_iter_nth_child;
+ iface->iter_parent = e_tree_model_generator_iter_parent;
+}
+
+static void
+e_tree_model_generator_init (ETreeModelGenerator *tree_model_generator)
+{
+ tree_model_generator->priv =
+ E_TREE_MODEL_GENERATOR_GET_PRIVATE (tree_model_generator);
+
+ tree_model_generator->priv->stamp = g_random_int ();
+ tree_model_generator->priv->root_nodes = g_array_new (FALSE, FALSE, sizeof (Node));
+}
+
+/* ------------------ *
+ * Row update helpers *
+ * ------------------ */
+
+static void
+row_deleted (ETreeModelGenerator *tree_model_generator,
+ GtkTreePath *path)
+{
+ g_assert (path);
+
+ ETMG_DEBUG (g_print ("row_deleted emitting\n"));
+ gtk_tree_model_row_deleted (GTK_TREE_MODEL (tree_model_generator), path);
+}
+
+static void
+row_inserted (ETreeModelGenerator *tree_model_generator,
+ GtkTreePath *path)
+{
+ GtkTreeIter iter;
+
+ g_assert (path);
+
+ if (gtk_tree_model_get_iter (GTK_TREE_MODEL (tree_model_generator), &iter, path)) {
+ ETMG_DEBUG (g_print ("row_inserted emitting\n"));
+ gtk_tree_model_row_inserted (GTK_TREE_MODEL (tree_model_generator), path, &iter);
+ } else {
+ ETMG_DEBUG (g_print ("row_inserted could not get iter!\n"));
+ }
+}
+
+static void
+row_changed (ETreeModelGenerator *tree_model_generator,
+ GtkTreePath *path)
+{
+ GtkTreeIter iter;
+
+ g_assert (path);
+
+ if (gtk_tree_model_get_iter (GTK_TREE_MODEL (tree_model_generator), &iter, path)) {
+ ETMG_DEBUG (g_print ("row_changed emitting\n"));
+ gtk_tree_model_row_changed (GTK_TREE_MODEL (tree_model_generator), path, &iter);
+ } else {
+ ETMG_DEBUG (g_print ("row_changed could not get iter!\n"));
+ }
+}
+
+/* -------------------- *
+ * Node map translation *
+ * -------------------- */
+
+static gint
+generated_offset_to_child_offset (GArray *group,
+ gint offset,
+ gint *internal_offset)
+{
+ gboolean success = FALSE;
+ gint accum_offset = 0;
+ gint i;
+
+ for (i = 0; i < group->len; i++) {
+ Node *node = &g_array_index (group, Node, i);
+
+ accum_offset += node->n_generated;
+ if (accum_offset > offset) {
+ accum_offset -= node->n_generated;
+ success = TRUE;
+ break;
+ }
+ }
+
+ if (!success)
+ return -1;
+
+ if (internal_offset)
+ *internal_offset = offset - accum_offset;
+
+ return i;
+}
+
+static gint
+child_offset_to_generated_offset (GArray *group,
+ gint offset)
+{
+ gint accum_offset = 0;
+ gint i;
+
+ g_return_val_if_fail (group != NULL, -1);
+
+ for (i = 0; i < group->len && i < offset; i++) {
+ Node *node = &g_array_index (group, Node, i);
+
+ accum_offset += node->n_generated;
+ }
+
+ return accum_offset;
+}
+
+static gint
+count_generated_nodes (GArray *group)
+{
+ gint accum_offset = 0;
+ gint i;
+
+ for (i = 0; i < group->len; i++) {
+ Node *node = &g_array_index (group, Node, i);
+
+ accum_offset += node->n_generated;
+ }
+
+ return accum_offset;
+}
+
+/* ------------------- *
+ * Node map management *
+ * ------------------- */
+
+static void
+release_node_map (GArray *group)
+{
+ gint i;
+
+ for (i = 0; i < group->len; i++) {
+ Node *node = &g_array_index (group, Node, i);
+
+ if (node->child_nodes)
+ release_node_map (node->child_nodes);
+ }
+
+ g_array_free (group, TRUE);
+}
+
+static gint
+append_node (GArray *group)
+{
+ g_array_set_size (group, group->len + 1);
+ return group->len - 1;
+}
+
+static GArray *
+build_node_map (ETreeModelGenerator *tree_model_generator,
+ GtkTreeIter *parent_iter,
+ GArray *parent_group,
+ gint parent_index)
+{
+ GArray *group;
+ GtkTreeIter iter;
+ gboolean result;
+
+ if (parent_iter)
+ result = gtk_tree_model_iter_children (tree_model_generator->priv->child_model, &iter, parent_iter);
+ else
+ result = gtk_tree_model_get_iter_first (tree_model_generator->priv->child_model, &iter);
+
+ if (!result)
+ return NULL;
+
+ group = g_array_new (FALSE, FALSE, sizeof (Node));
+
+ do {
+ Node *node;
+ gint i;
+
+ i = append_node (group);
+ node = &g_array_index (group, Node, i);
+
+ node->parent_group = parent_group;
+ node->parent_index = parent_index;
+
+ if (tree_model_generator->priv->generate_func)
+ node->n_generated =
+ tree_model_generator->priv->generate_func (tree_model_generator->priv->child_model,
+ &iter, tree_model_generator->priv->generate_func_data);
+ else
+ node->n_generated = 1;
+
+ node->child_nodes = build_node_map (tree_model_generator, &iter, group, i);
+ } while (gtk_tree_model_iter_next (tree_model_generator->priv->child_model, &iter));
+
+ return group;
+}
+
+static gint
+get_first_visible_index_from (GArray *group,
+ gint index)
+{
+ gint i;
+
+ for (i = index; i < group->len; i++) {
+ Node *node = &g_array_index (group, Node, i);
+
+ if (node->n_generated)
+ break;
+ }
+
+ if (i >= group->len)
+ i = -1;
+
+ return i;
+}
+
+static Node *
+get_node_by_child_path (ETreeModelGenerator *tree_model_generator,
+ GtkTreePath *path,
+ GArray **node_group)
+{
+ Node *node = NULL;
+ GArray *group;
+ gint depth;
+
+ group = tree_model_generator->priv->root_nodes;
+
+ for (depth = 0; depth < gtk_tree_path_get_depth (path); depth++) {
+ gint index;
+
+ if (!group) {
+ g_warning ("ETreeModelGenerator got unknown child element!");
+ break;
+ }
+
+ index = gtk_tree_path_get_indices (path)[depth];
+ node = &g_array_index (group, Node, index);
+
+ if (depth + 1 < gtk_tree_path_get_depth (path))
+ group = node->child_nodes;
+ }
+
+ if (!node)
+ group = NULL;
+
+ if (node_group)
+ *node_group = group;
+
+ return node;
+}
+
+static Node *
+create_node_at_child_path (ETreeModelGenerator *tree_model_generator,
+ GtkTreePath *path)
+{
+ GtkTreePath *parent_path;
+ gint parent_index;
+ GArray *parent_group;
+ GArray *group;
+ gint index;
+ Node *node;
+
+ parent_path = gtk_tree_path_copy (path);
+ gtk_tree_path_up (parent_path);
+ node = get_node_by_child_path (tree_model_generator, parent_path, &parent_group);
+
+ if (node) {
+ if (!node->child_nodes)
+ node->child_nodes = g_array_new (FALSE, FALSE, sizeof (Node));
+
+ group = node->child_nodes;
+ parent_index = gtk_tree_path_get_indices (parent_path)[gtk_tree_path_get_depth (parent_path) - 1];
+ } else {
+ if (!tree_model_generator->priv->root_nodes)
+ tree_model_generator->priv->root_nodes = g_array_new (FALSE, FALSE, sizeof (Node));
+
+ group = tree_model_generator->priv->root_nodes;
+ parent_index = -1;
+ }
+
+ gtk_tree_path_free (parent_path);
+
+ index = gtk_tree_path_get_indices (path)[gtk_tree_path_get_depth (path) - 1];
+ ETMG_DEBUG (g_print ("Inserting index %d into group of length %d\n", index, group->len));
+ index = MIN (index, group->len);
+
+ append_node (group);
+
+ if (group->len - 1 - index > 0) {
+ gint i;
+
+ memmove (
+ (Node *) group->data + index + 1,
+ (Node *) group->data + index,
+ (group->len - 1 - index) * sizeof (Node));
+
+ /* Update parent pointers */
+ for (i = index + 1; i < group->len; i++) {
+ Node *pnode = &g_array_index (group, Node, i);
+ GArray *child_group;
+ gint j;
+
+ child_group = pnode->child_nodes;
+ if (!child_group)
+ continue;
+
+ for (j = 0; j < child_group->len; j++) {
+ Node *child_node = &g_array_index (child_group, Node, j);
+ child_node->parent_index = i;
+ }
+ }
+ }
+
+ node = &g_array_index (group, Node, index);
+ node->parent_group = parent_group;
+ node->parent_index = parent_index;
+ node->n_generated = 0;
+ node->child_nodes = NULL;
+
+ ETMG_DEBUG (
+ g_print ("Created node at offset %d, parent_group = %p, parent_index = %d\n",
+ index, node->parent_group, node->parent_index));
+
+ return node;
+}
+
+ETMG_DEBUG (
+
+static void
+dump_group (GArray *group)
+{
+ gint i;
+
+ g_print ("\nGroup %p:\n", group);
+
+ for (i = 0; i < group->len; i++) {
+ Node *node = &g_array_index (group, Node, i);
+ g_print (
+ " %04d: pgroup=%p, pindex=%d, n_generated=%d, child_nodes=%p\n",
+ i, node->parent_group, node->parent_index, node->n_generated, node->child_nodes);
+ }
+}
+
+)
+
+static void
+delete_node_at_child_path (ETreeModelGenerator *tree_model_generator,
+ GtkTreePath *path)
+{
+ GtkTreePath *parent_path;
+ GArray *parent_group;
+ GArray *group;
+ gint index;
+ Node *node;
+ gint i;
+
+ parent_path = gtk_tree_path_copy (path);
+ gtk_tree_path_up (parent_path);
+ node = get_node_by_child_path (tree_model_generator, parent_path, &parent_group);
+
+ if (node) {
+ group = node->child_nodes;
+ } else {
+ group = tree_model_generator->priv->root_nodes;
+ }
+
+ gtk_tree_path_free (parent_path);
+
+ if (!group)
+ return;
+
+ index = gtk_tree_path_get_indices (path)[gtk_tree_path_get_depth (path) - 1];
+ if (index >= group->len)
+ return;
+
+ node = &g_array_index (group, Node, index);
+ if (node->child_nodes)
+ release_node_map (node->child_nodes);
+ g_array_remove_index (group, index);
+
+ /* Update parent pointers */
+ for (i = index; i < group->len; i++) {
+ Node *pnode = &g_array_index (group, Node, i);
+ GArray *child_group;
+ gint j;
+
+ child_group = pnode->child_nodes;
+ if (!child_group)
+ continue;
+
+ for (j = 0; j < child_group->len; j++) {
+ Node *child_node = &g_array_index (child_group, Node, j);
+ child_node->parent_index = i;
+ }
+ }
+}
+
+static void
+child_row_changed (ETreeModelGenerator *tree_model_generator,
+ GtkTreePath *path,
+ GtkTreeIter *iter)
+{
+ GtkTreePath *generated_path;
+ Node *node;
+ gint n_generated;
+ gint i;
+
+ if (tree_model_generator->priv->generate_func)
+ n_generated =
+ tree_model_generator->priv->generate_func (tree_model_generator->priv->child_model,
+ iter, tree_model_generator->priv->generate_func_data);
+ else
+ n_generated = 1;
+
+ node = get_node_by_child_path (tree_model_generator, path, NULL);
+ if (!node)
+ return;
+
+ generated_path = e_tree_model_generator_convert_child_path_to_path (tree_model_generator, path);
+
+ /* FIXME: Converting the path to an iter every time is inefficient */
+
+ for (i = 0; i < n_generated && i < node->n_generated; i++) {
+ row_changed (tree_model_generator, generated_path);
+ gtk_tree_path_next (generated_path);
+ }
+
+ for (; i < node->n_generated; ) {
+ node->n_generated--;
+ row_deleted (tree_model_generator, generated_path);
+ }
+
+ for (; i < n_generated; i++) {
+ node->n_generated++;
+ row_inserted (tree_model_generator, generated_path);
+ gtk_tree_path_next (generated_path);
+ }
+
+ gtk_tree_path_free (generated_path);
+}
+
+static void
+child_row_inserted (ETreeModelGenerator *tree_model_generator,
+ GtkTreePath *path,
+ GtkTreeIter *iter)
+{
+ GtkTreePath *generated_path;
+ Node *node;
+ gint n_generated;
+
+ if (tree_model_generator->priv->generate_func)
+ n_generated =
+ tree_model_generator->priv->generate_func (tree_model_generator->priv->child_model,
+ iter, tree_model_generator->priv->generate_func_data);
+ else
+ n_generated = 1;
+
+ node = create_node_at_child_path (tree_model_generator, path);
+ if (!node)
+ return;
+
+ generated_path = e_tree_model_generator_convert_child_path_to_path (tree_model_generator, path);
+
+ /* FIXME: Converting the path to an iter every time is inefficient */
+
+ for (node->n_generated = 0; node->n_generated < n_generated; ) {
+ node->n_generated++;
+ row_inserted (tree_model_generator, generated_path);
+ gtk_tree_path_next (generated_path);
+ }
+
+ gtk_tree_path_free (generated_path);
+}
+
+static void
+child_row_deleted (ETreeModelGenerator *tree_model_generator,
+ GtkTreePath *path)
+{
+ GtkTreePath *generated_path;
+ Node *node;
+
+ node = get_node_by_child_path (tree_model_generator, path, NULL);
+ if (!node)
+ return;
+
+ generated_path = e_tree_model_generator_convert_child_path_to_path (tree_model_generator, path);
+
+ /* FIXME: Converting the path to an iter every time is inefficient */
+
+ for (; node->n_generated; ) {
+ node->n_generated--;
+ row_deleted (tree_model_generator, generated_path);
+ }
+
+ delete_node_at_child_path (tree_model_generator, path);
+ gtk_tree_path_free (generated_path);
+}
+
+/* ----------------------- *
+ * ETreeModelGenerator API *
+ * ----------------------- */
+
+/**
+ * e_tree_model_generator_new:
+ * @child_model: a #GtkTreeModel
+ *
+ * Creates a new #ETreeModelGenerator wrapping @child_model.
+ *
+ * Returns: A new #ETreeModelGenerator.
+ **/
+ETreeModelGenerator *
+e_tree_model_generator_new (GtkTreeModel *child_model)
+{
+ g_return_val_if_fail (GTK_IS_TREE_MODEL (child_model), NULL);
+
+ return E_TREE_MODEL_GENERATOR (
+ g_object_new (E_TYPE_TREE_MODEL_GENERATOR,
+ "child-model", child_model, NULL));
+}
+
+/**
+ * e_tree_model_generator_get_model:
+ * @tree_model_generator: an #ETreeModelGenerator
+ *
+ * Gets the child model being wrapped by @tree_model_generator.
+ *
+ * Returns: A #GtkTreeModel being wrapped.
+ **/
+GtkTreeModel *
+e_tree_model_generator_get_model (ETreeModelGenerator *tree_model_generator)
+{
+ g_return_val_if_fail (E_IS_TREE_MODEL_GENERATOR (tree_model_generator), NULL);
+
+ return tree_model_generator->priv->child_model;
+}
+
+/**
+ * e_tree_model_generator_set_generate_func:
+ * @tree_model_generator: an #ETreeModelGenerator
+ * @func: an #ETreeModelGeneratorGenerateFunc, or %NULL
+ * @data: user data to pass to @func
+ * @destroy:
+ *
+ * Sets the callback function used to filter or generate additional rows
+ * based on the child model's data. This function is called for each child
+ * row, and returns a value indicating the number of rows that will be
+ * used to represent the child row - 0 or more.
+ *
+ * If @func is %NULL, a filtering/generating function will not be applied.
+ **/
+void
+e_tree_model_generator_set_generate_func (ETreeModelGenerator *tree_model_generator,
+ ETreeModelGeneratorGenerateFunc func,
+ gpointer data,
+ GDestroyNotify destroy)
+{
+ g_return_if_fail (E_IS_TREE_MODEL_GENERATOR (tree_model_generator));
+
+ tree_model_generator->priv->generate_func = func;
+ tree_model_generator->priv->generate_func_data = data;
+}
+
+/**
+ * e_tree_model_generator_set_modify_func:
+ * @tree_model_generator: an #ETreeModelGenerator
+ * @func: an @ETreeModelGeneratorModifyFunc, or %NULL
+ * @data: user data to pass to @func
+ * @destroy:
+ *
+ * Sets the callback function used to override values for the child row's
+ * columns and specify values for generated rows' columns.
+ *
+ * If @func is %NULL, the child model's values will always be used.
+ **/
+void
+e_tree_model_generator_set_modify_func (ETreeModelGenerator *tree_model_generator,
+ ETreeModelGeneratorModifyFunc func,
+ gpointer data,
+ GDestroyNotify destroy)
+{
+ g_return_if_fail (E_IS_TREE_MODEL_GENERATOR (tree_model_generator));
+
+ tree_model_generator->priv->modify_func = func;
+ tree_model_generator->priv->modify_func_data = data;
+}
+
+/**
+ * e_tree_model_generator_convert_child_path_to_path:
+ * @tree_model_generator: an #ETreeModelGenerator
+ * @child_path: a #GtkTreePath
+ *
+ * Convert a path to a child row to a path to a @tree_model_generator row.
+ *
+ * Returns: A new GtkTreePath, owned by the caller.
+ **/
+GtkTreePath *
+e_tree_model_generator_convert_child_path_to_path (ETreeModelGenerator *tree_model_generator,
+ GtkTreePath *child_path)
+{
+ GtkTreePath *path;
+ GArray *group;
+ gint depth;
+
+ g_return_val_if_fail (E_IS_TREE_MODEL_GENERATOR (tree_model_generator), NULL);
+ g_return_val_if_fail (child_path != NULL, NULL);
+
+ path = gtk_tree_path_new ();
+
+ group = tree_model_generator->priv->root_nodes;
+
+ for (depth = 0; depth < gtk_tree_path_get_depth (child_path); depth++) {
+ Node *node;
+ gint index;
+ gint generated_index;
+
+ if (!group) {
+ g_warning ("ETreeModelGenerator was asked for path to unknown child element!");
+ break;
+ }
+
+ index = gtk_tree_path_get_indices (child_path)[depth];
+ generated_index = child_offset_to_generated_offset (group, index);
+ node = &g_array_index (group, Node, index);
+ group = node->child_nodes;
+
+ gtk_tree_path_append_index (path, generated_index);
+ }
+
+ return path;
+}
+
+/**
+ * e_tree_model_generator_convert_child_iter_to_iter:
+ * @tree_model_generator: an #ETreeModelGenerator
+ * @generator_iter: a #GtkTreeIter to set
+ * @child_iter: a #GtkTreeIter to convert
+ *
+ * Convert @child_iter to a corresponding #GtkTreeIter for @tree_model_generator,
+ * storing the result in @generator_iter.
+ **/
+void
+e_tree_model_generator_convert_child_iter_to_iter (ETreeModelGenerator *tree_model_generator,
+ GtkTreeIter *generator_iter,
+ GtkTreeIter *child_iter)
+{
+ GtkTreePath *path;
+ GArray *group;
+ gint depth;
+ gint index = 0;
+
+ g_return_if_fail (E_IS_TREE_MODEL_GENERATOR (tree_model_generator));
+
+ path = gtk_tree_model_get_path (tree_model_generator->priv->child_model, child_iter);
+ if (!path)
+ return;
+
+ group = tree_model_generator->priv->root_nodes;
+
+ for (depth = 0; depth < gtk_tree_path_get_depth (path); depth++) {
+ Node *node;
+
+ index = gtk_tree_path_get_indices (path)[depth];
+ node = &g_array_index (group, Node, index);
+
+ if (depth + 1 < gtk_tree_path_get_depth (path))
+ group = node->child_nodes;
+
+ if (!group) {
+ g_warning ("ETreeModelGenerator was asked for iter to unknown child element!");
+ break;
+ }
+ }
+
+ g_return_if_fail (group != NULL);
+
+ index = child_offset_to_generated_offset (group, index);
+ ITER_SET (tree_model_generator, generator_iter, group, index);
+ gtk_tree_path_free (path);
+}
+
+/**
+ * e_tree_model_generator_convert_path_to_child_path:
+ * @tree_model_generator: an #ETreeModelGenerator
+ * @generator_path: a #GtkTreePath to a @tree_model_generator row
+ *
+ * Converts @generator_path to a corresponding #GtkTreePath in the child model.
+ *
+ * Returns: A new #GtkTreePath, owned by the caller.
+ **/
+GtkTreePath *
+e_tree_model_generator_convert_path_to_child_path (ETreeModelGenerator *tree_model_generator,
+ GtkTreePath *generator_path)
+{
+ GtkTreePath *path;
+ GArray *group;
+ gint depth;
+
+ g_return_val_if_fail (E_IS_TREE_MODEL_GENERATOR (tree_model_generator), NULL);
+ g_return_val_if_fail (generator_path != NULL, NULL);
+
+ path = gtk_tree_path_new ();
+
+ group = tree_model_generator->priv->root_nodes;
+
+ for (depth = 0; depth < gtk_tree_path_get_depth (generator_path); depth++) {
+ Node *node;
+ gint index;
+ gint child_index;
+
+ if (!group) {
+ g_warning ("ETreeModelGenerator was asked for path to unknown child element!");
+ break;
+ }
+
+ index = gtk_tree_path_get_indices (generator_path)[depth];
+ child_index = generated_offset_to_child_offset (group, index, NULL);
+ node = &g_array_index (group, Node, child_index);
+ group = node->child_nodes;
+
+ gtk_tree_path_append_index (path, child_index);
+ }
+
+ return path;
+}
+
+/**
+ * e_tree_model_generator_convert_iter_to_child_iter:
+ * @tree_model_generator: an #ETreeModelGenerator
+ * @child_iter: a #GtkTreeIter to set
+ * @permutation_n: a permutation index to set
+ * @generator_iter: a #GtkTreeIter indicating the row to convert
+ *
+ * Converts a @tree_model_generator row into a child row and permutation index.
+ * The permutation index is the index of the generated row based on this
+ * child row, with the first generated row based on this child row being 0.
+ **/
+void
+e_tree_model_generator_convert_iter_to_child_iter (ETreeModelGenerator *tree_model_generator,
+ GtkTreeIter *child_iter,
+ gint *permutation_n,
+ GtkTreeIter *generator_iter)
+{
+ GtkTreePath *path;
+ GArray *group;
+ gint index;
+ gint internal_offset = 0;
+
+ g_return_if_fail (E_IS_TREE_MODEL_GENERATOR (tree_model_generator));
+ g_return_if_fail (ITER_IS_VALID (tree_model_generator, generator_iter));
+
+ path = gtk_tree_path_new ();
+ ITER_GET (generator_iter, &group, &index);
+
+ index = generated_offset_to_child_offset (group, index, &internal_offset);
+ gtk_tree_path_prepend_index (path, index);
+
+ while (group) {
+ Node *node = &g_array_index (group, Node, index);
+
+ group = node->parent_group;
+ index = node->parent_index;
+
+ if (group)
+ gtk_tree_path_prepend_index (path, index);
+ }
+
+ if (child_iter)
+ gtk_tree_model_get_iter (tree_model_generator->priv->child_model, child_iter, path);
+ if (permutation_n)
+ *permutation_n = internal_offset;
+
+ gtk_tree_path_free (path);
+}
+
+/* ---------------- *
+ * GtkTreeModel API *
+ * ---------------- */
+
+static GtkTreeModelFlags
+e_tree_model_generator_get_flags (GtkTreeModel *tree_model)
+{
+ ETreeModelGenerator *tree_model_generator = E_TREE_MODEL_GENERATOR (tree_model);
+
+ g_return_val_if_fail (E_IS_TREE_MODEL_GENERATOR (tree_model), 0);
+
+ return gtk_tree_model_get_flags (tree_model_generator->priv->child_model);
+}
+
+static gint
+e_tree_model_generator_get_n_columns (GtkTreeModel *tree_model)
+{
+ ETreeModelGenerator *tree_model_generator = E_TREE_MODEL_GENERATOR (tree_model);
+
+ g_return_val_if_fail (E_IS_TREE_MODEL_GENERATOR (tree_model), 0);
+
+ return gtk_tree_model_get_n_columns (tree_model_generator->priv->child_model);
+}
+
+static GType
+e_tree_model_generator_get_column_type (GtkTreeModel *tree_model,
+ gint index)
+{
+ ETreeModelGenerator *tree_model_generator = E_TREE_MODEL_GENERATOR (tree_model);
+
+ g_return_val_if_fail (E_IS_TREE_MODEL_GENERATOR (tree_model), G_TYPE_INVALID);
+
+ return gtk_tree_model_get_column_type (tree_model_generator->priv->child_model, index);
+}
+
+static gboolean
+e_tree_model_generator_get_iter (GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ GtkTreePath *path)
+{
+ ETreeModelGenerator *tree_model_generator = E_TREE_MODEL_GENERATOR (tree_model);
+ GArray *group;
+ gint depth;
+ gint index = 0;
+
+ g_return_val_if_fail (E_IS_TREE_MODEL_GENERATOR (tree_model), FALSE);
+ g_return_val_if_fail (gtk_tree_path_get_depth (path) > 0, FALSE);
+
+ group = tree_model_generator->priv->root_nodes;
+ if (!group)
+ return FALSE;
+
+ for (depth = 0; depth < gtk_tree_path_get_depth (path); depth++) {
+ Node *node;
+ gint child_index;
+
+ index = gtk_tree_path_get_indices (path)[depth];
+ child_index = generated_offset_to_child_offset (group, index, NULL);
+ if (child_index < 0)
+ return FALSE;
+
+ node = &g_array_index (group, Node, child_index);
+
+ if (depth + 1 < gtk_tree_path_get_depth (path)) {
+ group = node->child_nodes;
+ if (!group)
+ return FALSE;
+ }
+ }
+
+ ITER_SET (tree_model_generator, iter, group, index);
+ return TRUE;
+}
+
+static GtkTreePath *
+e_tree_model_generator_get_path (GtkTreeModel *tree_model,
+ GtkTreeIter *iter)
+{
+ ETreeModelGenerator *tree_model_generator = E_TREE_MODEL_GENERATOR (tree_model);
+ GtkTreePath *path;
+ GArray *group;
+ gint index;
+
+ g_return_val_if_fail (E_IS_TREE_MODEL_GENERATOR (tree_model), NULL);
+ g_return_val_if_fail (ITER_IS_VALID (tree_model_generator, iter), NULL);
+
+ ITER_GET (iter, &group, &index);
+ path = gtk_tree_path_new ();
+
+ /* FIXME: Converting a path to an iter is a destructive operation, because
+ * we don't store a node for each generated entry... Doesn't matter for
+ * lists, not sure about trees. */
+
+ gtk_tree_path_prepend_index (path, index);
+ index = generated_offset_to_child_offset (group, index, NULL);
+
+ while (group) {
+ Node *node = &g_array_index (group, Node, index);
+ gint generated_index;
+
+ group = node->parent_group;
+ index = node->parent_index;
+ if (group) {
+ generated_index = child_offset_to_generated_offset (group, index);
+ gtk_tree_path_prepend_index (path, generated_index);
+ }
+ }
+
+ return path;
+}
+
+static gboolean
+e_tree_model_generator_iter_next (GtkTreeModel *tree_model,
+ GtkTreeIter *iter)
+{
+ ETreeModelGenerator *tree_model_generator = E_TREE_MODEL_GENERATOR (tree_model);
+ Node *node;
+ GArray *group;
+ gint index;
+ gint child_index;
+ gint internal_offset = 0;
+
+ g_return_val_if_fail (E_IS_TREE_MODEL_GENERATOR (tree_model), FALSE);
+ g_return_val_if_fail (ITER_IS_VALID (tree_model_generator, iter), FALSE);
+
+ ITER_GET (iter, &group, &index);
+ child_index = generated_offset_to_child_offset (group, index, &internal_offset);
+ node = &g_array_index (group, Node, child_index);
+
+ if (internal_offset + 1 < node->n_generated ||
+ get_first_visible_index_from (group, child_index + 1) >= 0) {
+ ITER_SET (tree_model_generator, iter, group, index + 1);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+e_tree_model_generator_iter_children (GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ GtkTreeIter *parent)
+{
+ ETreeModelGenerator *tree_model_generator = E_TREE_MODEL_GENERATOR (tree_model);
+ Node *node;
+ GArray *group;
+ gint index;
+
+ g_return_val_if_fail (E_IS_TREE_MODEL_GENERATOR (tree_model), FALSE);
+
+ if (!parent) {
+ if (!tree_model_generator->priv->root_nodes ||
+ !count_generated_nodes (tree_model_generator->priv->root_nodes))
+ return FALSE;
+
+ ITER_SET (tree_model_generator, iter, tree_model_generator->priv->root_nodes, 0);
+ return TRUE;
+ }
+
+ ITER_GET (parent, &group, &index);
+ index = generated_offset_to_child_offset (group, index, NULL);
+ if (index < 0)
+ return FALSE;
+
+ node = &g_array_index (group, Node, index);
+
+ if (!node->child_nodes)
+ return FALSE;
+
+ if (!count_generated_nodes (node->child_nodes))
+ return FALSE;
+
+ ITER_SET (tree_model_generator, iter, node->child_nodes, 0);
+ return TRUE;
+}
+
+static gboolean
+e_tree_model_generator_iter_has_child (GtkTreeModel *tree_model,
+ GtkTreeIter *iter)
+{
+ ETreeModelGenerator *tree_model_generator = E_TREE_MODEL_GENERATOR (tree_model);
+ Node *node;
+ GArray *group;
+ gint index;
+
+ g_return_val_if_fail (E_IS_TREE_MODEL_GENERATOR (tree_model), FALSE);
+
+ if (iter == NULL) {
+ if (!tree_model_generator->priv->root_nodes ||
+ !count_generated_nodes (tree_model_generator->priv->root_nodes))
+ return FALSE;
+
+ return TRUE;
+ }
+
+ ITER_GET (iter, &group, &index);
+ index = generated_offset_to_child_offset (group, index, NULL);
+ if (index < 0)
+ return FALSE;
+
+ node = &g_array_index (group, Node, index);
+
+ if (!node->child_nodes)
+ return FALSE;
+
+ if (!count_generated_nodes (node->child_nodes))
+ return FALSE;
+
+ return TRUE;
+}
+
+static gint
+e_tree_model_generator_iter_n_children (GtkTreeModel *tree_model,
+ GtkTreeIter *iter)
+{
+ ETreeModelGenerator *tree_model_generator = E_TREE_MODEL_GENERATOR (tree_model);
+ Node *node;
+ GArray *group;
+ gint index;
+
+ g_return_val_if_fail (E_IS_TREE_MODEL_GENERATOR (tree_model), 0);
+
+ if (iter == NULL)
+ return tree_model_generator->priv->root_nodes ?
+ count_generated_nodes (tree_model_generator->priv->root_nodes) : 0;
+
+ ITER_GET (iter, &group, &index);
+ index = generated_offset_to_child_offset (group, index, NULL);
+ if (index < 0)
+ return 0;
+
+ node = &g_array_index (group, Node, index);
+
+ if (!node->child_nodes)
+ return 0;
+
+ return count_generated_nodes (node->child_nodes);
+}
+
+static gboolean
+e_tree_model_generator_iter_nth_child (GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ GtkTreeIter *parent,
+ gint n)
+{
+ ETreeModelGenerator *tree_model_generator = E_TREE_MODEL_GENERATOR (tree_model);
+ Node *node;
+ GArray *group;
+ gint index;
+
+ g_return_val_if_fail (E_IS_TREE_MODEL_GENERATOR (tree_model), FALSE);
+
+ if (!parent) {
+ if (!tree_model_generator->priv->root_nodes)
+ return FALSE;
+
+ if (n >= count_generated_nodes (tree_model_generator->priv->root_nodes))
+ return FALSE;
+
+ ITER_SET (tree_model_generator, iter, tree_model_generator->priv->root_nodes, n);
+ return TRUE;
+ }
+
+ ITER_GET (parent, &group, &index);
+ index = generated_offset_to_child_offset (group, index, NULL);
+ if (index < 0)
+ return FALSE;
+
+ node = &g_array_index (group, Node, index);
+
+ if (!node->child_nodes)
+ return FALSE;
+
+ if (n >= count_generated_nodes (node->child_nodes))
+ return FALSE;
+
+ ITER_SET (tree_model_generator, iter, node->child_nodes, n);
+ return TRUE;
+}
+
+static gboolean
+e_tree_model_generator_iter_parent (GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ GtkTreeIter *child)
+{
+ ETreeModelGenerator *tree_model_generator = E_TREE_MODEL_GENERATOR (tree_model);
+ Node *node;
+ GArray *group;
+ gint index;
+
+ g_return_val_if_fail (E_IS_TREE_MODEL_GENERATOR (tree_model), FALSE);
+ g_return_val_if_fail (ITER_IS_VALID (tree_model_generator, iter), FALSE);
+
+ ITER_GET (child, &group, &index);
+ index = generated_offset_to_child_offset (group, index, NULL);
+ if (index < 0)
+ return FALSE;
+
+ node = &g_array_index (group, Node, index);
+
+ group = node->parent_group;
+ if (!group)
+ return FALSE;
+
+ ITER_SET (tree_model_generator, iter, group, node->parent_index);
+ return TRUE;
+}
+
+static void
+e_tree_model_generator_get_value (GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ gint column,
+ GValue *value)
+{
+ ETreeModelGenerator *tree_model_generator = E_TREE_MODEL_GENERATOR (tree_model);
+ GtkTreeIter child_iter;
+ gint permutation_n;
+
+ g_return_if_fail (E_IS_TREE_MODEL_GENERATOR (tree_model));
+ g_return_if_fail (ITER_IS_VALID (tree_model_generator, iter));
+
+ e_tree_model_generator_convert_iter_to_child_iter (
+ tree_model_generator, &child_iter,
+ &permutation_n, iter);
+
+ if (tree_model_generator->priv->modify_func) {
+ tree_model_generator->priv->modify_func (tree_model_generator->priv->child_model,
+ &child_iter, permutation_n,
+ column, value,
+ tree_model_generator->priv->modify_func_data);
+ return;
+ }
+
+ gtk_tree_model_get_value (tree_model_generator->priv->child_model, &child_iter, column, value);
+}