summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gtk/gtk.h1
-rw-r--r--gtk/gtkcssrbtree.c750
-rw-r--r--gtk/gtkcssrbtreeprivate.h89
-rw-r--r--gtk/gtktreelist.c569
-rw-r--r--gtk/gtktreelist.h64
-rw-r--r--gtk/meson.build3
-rw-r--r--tests/meson.build1
-rw-r--r--tests/testtreelist.c151
8 files changed, 1628 insertions, 0 deletions
diff --git a/gtk/gtk.h b/gtk/gtk.h
index ec151e5b87..25c2c60f67 100644
--- a/gtk/gtk.h
+++ b/gtk/gtk.h
@@ -220,6 +220,7 @@
#include <gtk/gtktooltip.h>
#include <gtk/gtktestutils.h>
#include <gtk/gtktreednd.h>
+#include <gtk/gtktreelist.h>
#include <gtk/gtktreemodel.h>
#include <gtk/gtktreemodelfilter.h>
#include <gtk/gtktreemodelsort.h>
diff --git a/gtk/gtkcssrbtree.c b/gtk/gtkcssrbtree.c
new file mode 100644
index 0000000000..7d37e09dc9
--- /dev/null
+++ b/gtk/gtkcssrbtree.c
@@ -0,0 +1,750 @@
+/* gtkrbtree.c
+ * Copyright (C) 2000 Red Hat, Inc., Jonathan Blandford <jrb@redhat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "gtkcssrbtreeprivate.h"
+
+#include "gtkdebug.h"
+
+typedef struct _GtkCssRbNode GtkCssRbNode;
+
+struct _GtkCssRbTree
+{
+ guint ref_count;
+
+ gsize element_size;
+ gsize augment_size;
+ GtkCssRbTreeAugmentFunc augment_func;
+ GDestroyNotify clear_func;
+ GDestroyNotify clear_augment_func;
+
+ GtkCssRbNode *root;
+};
+
+struct _GtkCssRbNode
+{
+ guint red :1;
+ guint dirty :1;
+
+ GtkCssRbNode *left;
+ GtkCssRbNode *right;
+ GtkCssRbNode *parent;
+};
+
+#define NODE_FROM_POINTER(ptr) ((GtkCssRbNode *) ((ptr) ? (((guchar *) (ptr)) - sizeof (GtkCssRbNode)) : NULL))
+#define NODE_TO_POINTER(node) ((gpointer) ((node) ? (((guchar *) (node)) + sizeof (GtkCssRbNode)) : NULL))
+#define NODE_TO_AUG_POINTER(tree, node) ((gpointer) ((node) ? (((guchar *) (node)) + sizeof (GtkCssRbNode) + (tree)->element_size) : NULL))
+
+static inline gsize
+gtk_css_rb_node_get_size (GtkCssRbTree *tree)
+{
+ return sizeof (GtkCssRbNode) + tree->element_size + tree->augment_size;
+}
+
+static GtkCssRbNode *
+gtk_css_rb_node_new (GtkCssRbTree *tree)
+{
+ GtkCssRbNode *result;
+
+ result = g_slice_alloc0 (gtk_css_rb_node_get_size (tree));
+
+ result->red = TRUE;
+ result->dirty = TRUE;
+
+ return result;
+}
+
+static void
+gtk_css_rb_node_free (GtkCssRbTree *tree,
+ GtkCssRbNode *node)
+{
+ if (tree->clear_func)
+ tree->clear_func (NODE_TO_POINTER (node));
+ if (tree->clear_augment_func)
+ tree->clear_augment_func (NODE_TO_AUG_POINTER (tree, node));
+
+ g_slice_free1 (gtk_css_rb_node_get_size (tree), node);
+}
+
+static void
+gtk_css_rb_node_free_deep (GtkCssRbTree *tree,
+ GtkCssRbNode *node)
+{
+ GtkCssRbNode *right = node->right;
+
+ if (node->left)
+ gtk_css_rb_node_free_deep (tree, node->left);
+
+ gtk_css_rb_node_free (tree, node);
+
+ if (right)
+ gtk_css_rb_node_free_deep (tree, right);
+}
+
+static void
+gtk_css_rb_node_mark_dirty (GtkCssRbNode *node,
+ gboolean mark_parent)
+{
+ if (node->dirty)
+ return;
+
+ node->dirty = TRUE;
+
+ if (mark_parent && node->parent)
+ gtk_css_rb_node_mark_dirty (node->parent, TRUE);
+}
+
+static void
+gtk_css_rb_node_clean (GtkCssRbTree *tree,
+ GtkCssRbNode *node)
+{
+ if (!node->dirty)
+ return;
+
+ node->dirty = FALSE;
+ if (tree->augment_func)
+ tree->augment_func (tree,
+ NODE_TO_AUG_POINTER (tree, node),
+ NODE_TO_POINTER (node),
+ NODE_TO_POINTER (node->left),
+ NODE_TO_POINTER (node->right));
+}
+
+static GtkCssRbNode *
+gtk_css_rb_node_get_first (GtkCssRbNode *node)
+{
+ while (node->left)
+ node = node->left;
+
+ return node;
+}
+
+static GtkCssRbNode *
+gtk_css_rb_node_get_last (GtkCssRbNode *node)
+{
+ while (node->right)
+ node = node->right;
+
+ return node;
+}
+
+static GtkCssRbNode *
+gtk_css_rb_node_get_previous (GtkCssRbNode *node)
+{
+ GtkCssRbNode *parent;
+
+ if (node->left)
+ return gtk_css_rb_node_get_last (node->left);
+
+ for (parent = node->parent; parent != NULL; parent = node->parent)
+ {
+ if (parent->right == node)
+ return parent;
+
+ node = parent;
+ }
+
+ return NULL;
+}
+
+static GtkCssRbNode *
+gtk_css_rb_node_get_next (GtkCssRbNode *node)
+{
+ GtkCssRbNode *parent;
+
+ if (node->right)
+ return gtk_css_rb_node_get_first (node->right);
+
+ for (parent = node->parent; parent != NULL; parent = node->parent)
+ {
+ if (parent->left == node)
+ return parent;
+
+ node = parent;
+ }
+
+ return NULL;
+}
+
+static void
+gtk_css_rb_node_rotate_left (GtkCssRbTree *tree,
+ GtkCssRbNode *node)
+{
+ GtkCssRbNode *right;
+
+ right = node->right;
+
+ node->right = right->left;
+ if (right->left)
+ right->left->parent = node;
+
+ right->parent = node->parent;
+ if (node->parent)
+ {
+ if (node == node->parent->left)
+ node->parent->left = right;
+ else
+ node->parent->right = right;
+ }
+ else
+ {
+ tree->root = right;
+ }
+
+ right->left = node;
+ node->parent = right;
+
+ gtk_css_rb_node_mark_dirty (node, FALSE);
+ gtk_css_rb_node_mark_dirty (right, FALSE);
+}
+
+static void
+gtk_css_rb_node_rotate_right (GtkCssRbTree *tree,
+ GtkCssRbNode *node)
+{
+ GtkCssRbNode *left;
+
+ left = node->left;
+
+ node->left = left->right;
+ if (left->right)
+ left->right->parent = node;
+
+ left->parent = node->parent;
+ if (node->parent)
+ {
+ if (node == node->parent->right)
+ node->parent->right = left;
+ else
+ node->parent->left = left;
+ }
+ else
+ {
+ tree->root = left;
+ }
+
+ /* link node and left */
+ left->right = node;
+ node->parent = left;
+
+ gtk_css_rb_node_mark_dirty (node, FALSE);
+ gtk_css_rb_node_mark_dirty (left, FALSE);
+}
+
+static gboolean
+is_red (GtkCssRbNode *node_or_null)
+{
+ if (node_or_null == NULL)
+ return FALSE;
+ else
+ return node_or_null->red;
+}
+
+static inline gboolean
+is_black (GtkCssRbNode *node_or_null)
+{
+ return !is_red (node_or_null);
+}
+
+static void
+set_black (GtkCssRbNode *node_or_null)
+{
+ if (node_or_null == NULL)
+ return;
+
+ node_or_null->red = FALSE;
+}
+
+static void
+set_red (GtkCssRbNode *node_or_null)
+{
+ if (node_or_null == NULL)
+ return;
+
+ node_or_null->red = TRUE;
+}
+
+static void
+gtk_css_rb_tree_insert_fixup (GtkCssRbTree *tree,
+ GtkCssRbNode *node)
+{
+
+ /* check Red-Black properties */
+ while (node->parent && is_red (node->parent))
+ {
+ /* we have a violation */
+ g_assert (node->parent->parent);
+
+ if (node->parent == node->parent->parent->left)
+ {
+ GtkCssRbNode *uncle = node->parent->parent->right;
+
+ if (is_red (uncle))
+ {
+ /* uncle is red */
+ set_black (node->parent);
+ set_black (uncle);
+ set_red (node->parent->parent);
+ node = node->parent->parent;
+ }
+ else
+ {
+ /* uncle is black */
+ if (node == node->parent->right)
+ {
+ /* make node a left child */
+ node = node->parent;
+ gtk_css_rb_node_rotate_left (tree, node);
+ }
+ /* recolor and rotate */
+ set_black (node->parent);
+ set_red (node->parent->parent);
+ gtk_css_rb_node_rotate_right (tree, node->parent->parent);
+ }
+ }
+ else
+ {
+ /* mirror image of above code */
+ GtkCssRbNode *uncle = node->parent->parent->left;
+
+ if (is_red (uncle))
+ {
+ /* uncle is red */
+ set_black (node->parent);
+ set_black (uncle);
+ set_red (node->parent->parent);
+ node = node->parent->parent;
+ }
+ else
+ {
+ /* uncle is black */
+ if (node == node->parent->left)
+ {
+ node = node->parent;
+ gtk_css_rb_node_rotate_right (tree, node);
+ }
+ set_black (node->parent);
+ set_red (node->parent->parent);
+ gtk_css_rb_node_rotate_left (tree, node->parent->parent);
+ }
+ }
+ }
+
+ set_black (tree->root);
+}
+
+static void
+gtk_css_rb_tree_remove_node_fixup (GtkCssRbTree *tree,
+ GtkCssRbNode *node,
+ GtkCssRbNode *parent)
+{
+ while (node->parent && is_black (node))
+ {
+ if (node == parent->left)
+ {
+ GtkCssRbNode *w = parent->right;
+
+ if (is_red (w))
+ {
+ set_black (w);
+ set_red (parent);
+ gtk_css_rb_node_rotate_left (tree, parent);
+ w = parent->right;
+ }
+ if (is_black (w->left) && is_black (w->right))
+ {
+ set_red (w);
+ node = parent;
+ }
+ else
+ {
+ if (is_black (w->right))
+ {
+ set_black (w->left);
+ set_red (w);
+ gtk_css_rb_node_rotate_right (tree, w);
+ w = parent->right;
+ }
+ w->red = parent->red;
+ set_black (parent);
+ set_black (w->right);
+ gtk_css_rb_node_rotate_left (tree, parent);
+ node = tree->root;
+ }
+ }
+ else
+ {
+ GtkCssRbNode *w = parent->left;
+ if (is_red (w))
+ {
+ set_black (w);
+ set_red (parent);
+ gtk_css_rb_node_rotate_right (tree, parent);
+ w = parent->left;
+ }
+ if (is_black (w->right) && is_black (w->left))
+ {
+ set_red (w);
+ node = parent;
+ }
+ else
+ {
+ if (is_black (w->left))
+ {
+ set_black (w->right);
+ set_red (w);
+ gtk_css_rb_node_rotate_left (tree, w);
+ w = parent->left;
+ }
+ w->red = parent->red;
+ set_black (parent);
+ set_black (w->left);
+ gtk_css_rb_node_rotate_right (tree, parent);
+ node = tree->root;
+ }
+ }
+
+ parent = node->parent;
+ }
+
+ set_black (node);
+}
+
+GtkCssRbTree *
+gtk_css_rb_tree_new_for_size (gsize element_size,
+ gsize augment_size,
+ GtkCssRbTreeAugmentFunc augment_func,
+ GDestroyNotify clear_func,
+ GDestroyNotify clear_augment_func)
+{
+ GtkCssRbTree *tree;
+
+ tree = g_slice_new0 (GtkCssRbTree);
+ tree->ref_count = 1;
+
+ tree->element_size = element_size;
+ tree->augment_size = augment_size;
+ tree->augment_func = augment_func;
+ tree->clear_func = clear_func;
+ tree->clear_augment_func = clear_augment_func;
+
+ return tree;
+}
+
+GtkCssRbTree *
+gtk_css_rb_tree_ref (GtkCssRbTree *tree)
+{
+ tree->ref_count++;
+
+ return tree;
+}
+
+void
+gtk_css_rb_tree_unref (GtkCssRbTree *tree)
+{
+ tree->ref_count--;
+ if (tree->ref_count > 0)
+ return;
+
+ if (tree->root)
+ gtk_css_rb_node_free_deep (tree, tree->root);
+
+ g_slice_free (GtkCssRbTree, tree);
+}
+
+gpointer
+gtk_css_rb_tree_get_first (GtkCssRbTree *tree)
+{
+ if (tree->root == NULL)
+ return NULL;
+
+ return NODE_TO_POINTER (gtk_css_rb_node_get_first (tree->root));
+}
+
+gpointer
+gtk_css_rb_tree_get_last (GtkCssRbTree *tree)
+{
+ if (tree->root == NULL)
+ return NULL;
+
+ return NODE_TO_POINTER (gtk_css_rb_node_get_last (tree->root));
+}
+
+gpointer
+gtk_css_rb_tree_get_previous (GtkCssRbTree *tree,
+ gpointer node)
+{
+ return NODE_TO_POINTER (gtk_css_rb_node_get_previous (NODE_FROM_POINTER (node)));
+}
+
+gpointer
+gtk_css_rb_tree_get_next (GtkCssRbTree *tree,
+ gpointer node)
+{
+ return NODE_TO_POINTER (gtk_css_rb_node_get_next (NODE_FROM_POINTER (node)));
+}
+
+gpointer
+gtk_css_rb_tree_get_root (GtkCssRbTree *tree)
+{
+ return NODE_TO_POINTER (tree->root);
+}
+
+gpointer
+gtk_css_rb_tree_get_parent (GtkCssRbTree *tree,
+ gpointer node)
+{
+ return NODE_TO_POINTER (NODE_FROM_POINTER (node)->parent);
+}
+
+gpointer
+gtk_css_rb_tree_get_left (GtkCssRbTree *tree,
+ gpointer node)
+{
+ return NODE_TO_POINTER (NODE_FROM_POINTER (node)->left);
+}
+
+gpointer
+gtk_css_rb_tree_get_right (GtkCssRbTree *tree,
+ gpointer node)
+{
+ return NODE_TO_POINTER (NODE_FROM_POINTER (node)->right);
+}
+
+gpointer
+gtk_css_rb_tree_get_augment (GtkCssRbTree *tree,
+ gpointer node)
+{
+ GtkCssRbNode *rbnode = NODE_FROM_POINTER (node);
+
+ gtk_css_rb_node_clean (tree, rbnode);
+
+ return NODE_TO_AUG_POINTER (tree, rbnode);
+}
+
+void
+gtk_css_rb_tree_mark_dirty (GtkCssRbTree *tree,
+ gpointer node)
+{
+ gtk_css_rb_node_mark_dirty (NODE_FROM_POINTER (node), TRUE);
+}
+
+gpointer
+gtk_css_rb_tree_insert_before (GtkCssRbTree *tree,
+ gpointer node)
+{
+ GtkCssRbNode *result;
+
+ /* setup new node */
+ result = gtk_css_rb_node_new (tree);
+
+ if (tree->root == NULL)
+ {
+ g_assert (node == NULL);
+ tree->root = result;
+ }
+ else if (node == NULL)
+ {
+ return gtk_css_rb_tree_insert_after (tree, gtk_css_rb_tree_get_last (tree));
+ }
+ else
+ {
+ GtkCssRbNode *current = NODE_FROM_POINTER (node);
+
+ if (current->left)
+ {
+ current = gtk_css_rb_node_get_last (current);
+ current->right = result;
+ }
+ else
+ {
+ current->left = result;
+ }
+ result->parent = current;
+ gtk_css_rb_node_mark_dirty (current, TRUE);
+ }
+
+ gtk_css_rb_tree_insert_fixup (tree, result);
+
+ return NODE_TO_POINTER (result);
+}
+
+gpointer
+gtk_css_rb_tree_insert_after (GtkCssRbTree *tree,
+ gpointer node)
+{
+ GtkCssRbNode *result;
+
+ /* setup new node */
+ result = gtk_css_rb_node_new (tree);
+
+ if (tree->root == NULL)
+ {
+ g_assert (node == NULL);
+ tree->root = result;
+ }
+ else if (node == NULL)
+ {
+ return gtk_css_rb_tree_insert_before (tree, gtk_css_rb_tree_get_first (tree));
+ }
+ else
+ {
+ GtkCssRbNode *current = NODE_FROM_POINTER (node);
+
+ if (current->right )
+ {
+ current = gtk_css_rb_node_get_first (current);
+ current->left = result;
+ }
+ else
+ {
+ current->right = result;
+ }
+ result->parent = current;
+ gtk_css_rb_node_mark_dirty (current, TRUE);
+ }
+
+ gtk_css_rb_tree_insert_fixup (tree, result);
+
+ return NODE_TO_POINTER (result);
+}
+
+void
+gtk_css_rb_tree_remove (GtkCssRbTree *tree,
+ gpointer node)
+{
+ GtkCssRbNode *x, *y, *real_node;
+
+ real_node = NODE_FROM_POINTER (node);
+ y = real_node;
+ if (y->left && y->right)
+ {
+ y = y->right;
+
+ while (y->left)
+ y = y->left;
+ }
+
+ /* x is y's only child, or nil */
+ if (y->left)
+ x = y->left;
+ else
+ x = y->right;
+
+ /* remove y from the parent chain */
+ if (x != NULL)
+ x->parent = y->parent;
+ if (y->parent)
+ {
+ if (y == y->parent->left)
+ y->parent->left = x;
+ else
+ y->parent->right = x;
+ }
+ else
+ {
+ tree->root = x;
+ }
+
+ /* We need to clean up the validity of the tree.
+ */
+ if (x && is_black (y))
+ gtk_css_rb_tree_remove_node_fixup (tree, x, y->parent);
+
+ if (y != real_node)
+ {
+ /* Move the node over */
+ if (is_red (real_node) != is_red (y))
+ y->red = !y->red;
+
+ y->left = real_node->left;
+ if (y->left)
+ y->left->parent = y;
+ y->right = real_node->right;
+ if (y->right)
+ y->right->parent = y;
+ y->parent = real_node->parent;
+ if (y->parent)
+ {
+ if (y->parent->left == real_node)
+ y->parent->left = y;
+ else
+ y->parent->right = y;
+ }
+ else
+ {
+ tree->root = y;
+ }
+ }
+
+ gtk_css_rb_node_free (tree, real_node);
+}
+
+
+gpointer
+gtk_css_rb_tree_find (GtkCssRbTree *tree,
+ gpointer *out_before,
+ gpointer *out_after,
+ GtkCssRbTreeFindFunc find_func,
+ gpointer user_data)
+{
+ GtkCssRbNode *node, *before = NULL, *after = NULL;
+ int cmp;
+
+ if (tree->root == NULL)
+ {
+ if (out_before)
+ *out_before = NULL;
+ if (out_after)
+ *out_after = NULL;
+
+ return NULL;
+ }
+
+ node = tree->root;
+ for (cmp = find_func (tree, NODE_TO_POINTER (node), user_data);
+ cmp != 0;
+ cmp = find_func (tree, NODE_TO_POINTER (node), user_data))
+ {
+ if (cmp < 0)
+ {
+ before = node;
+ node = node->right;
+ }
+ else /* cmp > 0 */
+ {
+ after = node;
+ node = node->left;
+ }
+ if (node == NULL)
+ {
+ if (out_before)
+ *out_before = NODE_TO_POINTER (before);
+ if (out_after)
+ *out_after = NODE_TO_POINTER (after);
+ return NULL;;
+ }
+ }
+
+ if (out_before)
+ *out_before = NODE_TO_POINTER (gtk_css_rb_node_get_previous (node));
+ if (out_after)
+ *out_after = NODE_TO_POINTER (gtk_css_rb_node_get_next (node));
+
+ return NODE_TO_POINTER (node);
+}
diff --git a/gtk/gtkcssrbtreeprivate.h b/gtk/gtkcssrbtreeprivate.h
new file mode 100644
index 0000000000..fd7dfbd595
--- /dev/null
+++ b/gtk/gtkcssrbtreeprivate.h
@@ -0,0 +1,89 @@
+/* gtkrb_tree.h
+ * Copyright (C) 2000 Red Hat, Inc., Jonathan Blandford <jrb@redhat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* A Red-Black Tree implementation used specifically by GtkTreeView.
+ */
+#ifndef __GTK_CSS_RB_TREE_H__
+#define __GTK_CSS_RB_TREE_H__
+
+#include <glib.h>
+
+
+G_BEGIN_DECLS
+
+
+typedef struct _GtkCssRbTree GtkCssRbTree;
+
+typedef void (* GtkCssRbTreeAugmentFunc) (GtkCssRbTree *tree,
+ gpointer node_augment,
+ gpointer node,
+ gpointer left,
+ gpointer right);
+typedef int (* GtkCssRbTreeFindFunc) (GtkCssRbTree *tree,
+ gpointer node,
+ gpointer user_data);
+
+GtkCssRbTree * gtk_css_rb_tree_new_for_size (gsize element_size,
+ gsize augment_size,
+ GtkCssRbTreeAugmentFunc augment_func,
+ GDestroyNotify clear_func,
+ GDestroyNotify clear_augment_func);
+#define gtk_css_rb_tree_new(type, augment_type, augment_func, clear_func, clear_augment_func) \
+ gtk_css_rb_tree_new_for_size (sizeof (type), sizeof (augment_type), (augment_func), (clear_func), (clear_augment_func))
+
+GtkCssRbTree * gtk_css_rb_tree_ref (GtkCssRbTree *tree);
+void gtk_css_rb_tree_unref (GtkCssRbTree *tree);
+
+gpointer gtk_css_rb_tree_get_first (GtkCssRbTree *tree);
+gpointer gtk_css_rb_tree_get_last (GtkCssRbTree *tree);
+gpointer gtk_css_rb_tree_get_previous (GtkCssRbTree *tree,
+ gpointer node);
+gpointer gtk_css_rb_tree_get_next (GtkCssRbTree *tree,
+ gpointer node);
+
+gpointer gtk_css_rb_tree_get_root (GtkCssRbTree *tree);
+gpointer gtk_css_rb_tree_get_parent (GtkCssRbTree *tree,
+ gpointer node);
+gpointer gtk_css_rb_tree_get_left (GtkCssRbTree *tree,
+ gpointer node);
+gpointer gtk_css_rb_tree_get_right (GtkCssRbTree *tree,
+ gpointer node);
+gpointer gtk_css_rb_tree_get_augment (GtkCssRbTree *tree,
+ gpointer node);
+
+void gtk_css_rb_tree_mark_dirty (GtkCssRbTree *tree,
+ gpointer node);
+
+gpointer gtk_css_rb_tree_insert_before (GtkCssRbTree *tree,
+ gpointer node);
+gpointer gtk_css_rb_tree_insert_after (GtkCssRbTree *tree,
+ gpointer node);
+void gtk_css_rb_tree_remove (GtkCssRbTree *tree,
+ gpointer node);
+
+gpointer gtk_css_rb_tree_find (GtkCssRbTree *tree,
+ gpointer *out_before,
+ gpointer *out_after,
+ GtkCssRbTreeFindFunc find_func,
+ gpointer user_data);
+
+
+
+G_END_DECLS
+
+
+#endif /* __GTK_CSS_RB_TREE_H__ */
diff --git a/gtk/gtktreelist.c b/gtk/gtktreelist.c
new file mode 100644
index 0000000000..7f52e5b639
--- /dev/null
+++ b/gtk/gtktreelist.c
@@ -0,0 +1,569 @@
+/*
+ * Copyright © 2018 Benjamin Otte
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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 library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte@gnome.org>
+ */
+
+#include "config.h"
+
+#include "gtktreelist.h"
+
+#include "gtkcssrbtreeprivate.h"
+#include "gtkintl.h"
+#include "gtkprivate.h"
+
+enum {
+ PROP_0,
+ PROP_ROOT_MODEL,
+ NUM_PROPERTIES
+};
+
+typedef struct _TreeNode TreeNode;
+typedef struct _TreeAugment TreeAugment;
+
+struct _TreeNode
+{
+ GListModel *model;
+ GtkCssRbTree *children;
+ union {
+ TreeNode *parent;
+ GtkTreeList *list;
+ };
+
+ guint empty : 1;
+ guint is_root : 1;
+};
+
+struct _TreeAugment
+{
+ guint n_items;
+ guint n_local;
+};
+
+struct _GtkTreeList
+{
+ GObject parent_instance;
+
+ TreeNode root_node;
+
+ GtkTreeListCreateModelFunc create_func;
+ gpointer user_data;
+ GDestroyNotify user_destroy;
+};
+
+struct _GtkTreeListClass
+{
+ GObjectClass parent_class;
+};
+
+static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
+
+static TreeNode *
+gtk_tree_list_get_nth (GtkTreeList *self,
+ guint position)
+{
+ GtkCssRbTree *tree;
+ TreeNode *node, *tmp;
+ TreeAugment *aug;
+
+ tree = self->root_node.children;
+ node = gtk_css_rb_tree_get_root (tree);
+ aug = gtk_css_rb_tree_get_augment (tree, node);
+ if (aug->n_items <= position)
+ return NULL;
+
+ while (TRUE)
+ {
+ tmp = gtk_css_rb_tree_get_left (tree, node);
+ if (tmp)
+ {
+ aug = gtk_css_rb_tree_get_augment (tree, tmp);
+ if (position < aug->n_items)
+ {
+ node = tmp;
+ continue;
+ }
+ position -= aug->n_items;
+ }
+
+ if (position == 0)
+ return node;
+
+ position--;
+
+ if (node->children)
+ {
+ tmp = gtk_css_rb_tree_get_root (node->children);
+ aug = gtk_css_rb_tree_get_augment (node->children, tmp);
+ if (position < aug->n_items)
+ {
+ tree = node->children;
+ node = tmp;
+ continue;
+ }
+ position -= aug->n_items;
+ }
+
+ node = gtk_css_rb_tree_get_right (tree, node);
+ }
+
+ g_return_val_if_reached (NULL);
+}
+
+static guint
+tree_node_get_local_position (GtkCssRbTree *tree,
+ TreeNode *node)
+{
+ TreeNode *left, *parent;
+ TreeAugment *left_aug;
+ guint n;
+
+ left = gtk_css_rb_tree_get_left (tree, node);
+ if (left)
+ {
+ left_aug = gtk_css_rb_tree_get_augment (tree, left);
+ n = left_aug->n_local;
+ }
+ else
+ {
+ n = 0;
+ }
+
+ for (parent = gtk_css_rb_tree_get_parent (tree, node);
+ parent;
+ parent = gtk_css_rb_tree_get_parent (tree, node))
+ {
+ left = gtk_css_rb_tree_get_left (tree, parent);
+ if (left == node)
+ {
+ /* we are the left node, nothing changes */
+ }
+ else
+ {
+ /* we are the right node */
+ n++;
+ if (left)
+ {
+ left_aug = gtk_css_rb_tree_get_augment (tree, left);
+ n += left_aug->n_local;
+ }
+ }
+ node = parent;
+ }
+
+ return n;
+}
+
+static GType
+gtk_tree_list_get_item_type (GListModel *list)
+{
+ GtkTreeList *self = GTK_TREE_LIST (list);
+
+ return g_list_model_get_item_type (self->root_node.model);
+}
+
+static void
+tree_node_mark_dirty (TreeNode *node)
+{
+ for (;
+ !node->is_root;
+ node = node->parent)
+ {
+ gtk_css_rb_tree_mark_dirty (node->parent->children, node);
+ }
+}
+
+static guint
+tree_node_get_n_children (TreeNode *node)
+{
+ TreeAugment *child_aug;
+ TreeNode *child_node;
+
+ if (node->children == NULL)
+ return 0;
+
+ child_node = gtk_css_rb_tree_get_root (node->children);
+ if (child_node == NULL)
+ return 0;
+
+ child_aug = gtk_css_rb_tree_get_augment (node->children, child_node);
+
+ return child_aug->n_items;
+}
+
+static guint
+gtk_tree_list_get_n_items (GListModel *list)
+{
+ GtkTreeList *self = GTK_TREE_LIST (list);
+
+ return tree_node_get_n_children (&self->root_node);
+}
+
+static gpointer
+gtk_tree_list_get_item (GListModel *list,
+ guint position)
+{
+ GtkTreeList *self = GTK_TREE_LIST (list);
+ TreeNode *node, *parent;
+
+ node = gtk_tree_list_get_nth (self, position);
+ if (node == NULL)
+ return NULL;
+
+ parent = node->parent;
+ return g_list_model_get_item (parent->model,
+ tree_node_get_local_position (parent->children, node));
+}
+
+static void
+gtk_tree_list_model_init (GListModelInterface *iface)
+{
+ iface->get_item_type = gtk_tree_list_get_item_type;
+ iface->get_n_items = gtk_tree_list_get_n_items;
+ iface->get_item = gtk_tree_list_get_item;
+}
+
+G_DEFINE_TYPE_WITH_CODE (GtkTreeList, gtk_tree_list, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, gtk_tree_list_model_init))
+
+static void
+gtk_tree_list_augment (GtkCssRbTree *tree,
+ gpointer _aug,
+ gpointer _node,
+ gpointer left,
+ gpointer right)
+{
+ TreeAugment *aug = _aug;
+
+ aug->n_items = 1;
+ aug->n_items += tree_node_get_n_children (_node);
+ aug->n_local = 1;
+
+ if (left)
+ {
+ TreeAugment *left_aug = gtk_css_rb_tree_get_augment (tree, left);
+ aug->n_items += left_aug->n_items;
+ aug->n_local += left_aug->n_local;
+ }
+ if (right)
+ {
+ TreeAugment *right_aug = gtk_css_rb_tree_get_augment (tree, right);
+ aug->n_items += right_aug->n_items;
+ aug->n_local += right_aug->n_local;
+ }
+}
+
+static void
+gtk_tree_list_clear_node (gpointer data)
+{
+ TreeNode *node = data;
+
+ if (node->model)
+ g_object_unref (node->model);
+ if (node->children)
+ gtk_css_rb_tree_unref (node->children);
+}
+
+static void
+gtk_tree_list_init_node (GtkTreeList *list,
+ TreeNode *self,
+ GListModel *model)
+{
+ gsize i, n;
+ TreeNode *node;
+
+ self->model = g_object_ref (model);
+ self->children = gtk_css_rb_tree_new (TreeNode,
+ TreeAugment,
+ gtk_tree_list_augment,
+ gtk_tree_list_clear_node,
+ NULL);
+
+ n = g_list_model_get_n_items (model);
+ node = NULL;
+ for (i = 0; i < n; i++)
+ {
+ node = gtk_css_rb_tree_insert_after (self->children, node);
+ node->parent = self;
+ }
+}
+
+static void
+gtk_tree_list_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ /* GtkTreeList *self = GTK_TREE_LIST (object); */
+
+ switch (prop_id)
+ {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gtk_tree_list_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GtkTreeList *self = GTK_TREE_LIST (object);
+
+ switch (prop_id)
+ {
+ case PROP_ROOT_MODEL:
+ g_value_set_object (value, self->root_node.model);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gtk_tree_list_finalize (GObject *object)
+{
+ GtkTreeList *self = GTK_TREE_LIST (object);
+
+ gtk_tree_list_clear_node (&self->root_node);
+ if (self->user_destroy)
+ self->user_destroy (self->user_data);
+
+ G_OBJECT_CLASS (gtk_tree_list_parent_class)->finalize (object);
+};
+
+static void
+gtk_tree_list_class_init (GtkTreeListClass *class)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (class);
+
+ gobject_class->set_property = gtk_tree_list_set_property;
+ gobject_class->get_property = gtk_tree_list_get_property;
+ gobject_class->finalize = gtk_tree_list_finalize;
+
+ /**
+ * GtkTreeList:root-model:
+ *
+ * The root model displayed
+ */
+ properties[PROP_ROOT_MODEL] =
+ g_param_spec_object ("root-model",
+ P_("Root model"),
+ P_("The root model displayed"),
+ G_TYPE_LIST_MODEL,
+ GTK_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY);
+
+ g_object_class_install_properties (gobject_class, NUM_PROPERTIES, properties);
+}
+
+static void
+gtk_tree_list_init (GtkTreeList *self)
+{
+ self->root_node.list = self;
+ self->root_node.is_root = TRUE;
+}
+
+/**
+ * gtk_tree_list_new:
+ * @root: The #GListModel to use as root
+ * @create_func: Function to call to create the #GListModel for the children
+ * of an item
+ * @user_data: Data to pass to @create_func
+ * @user_destroy: Function to call to free @user_data
+ *
+ * Creates a new empty #GtkTreeList displaying @root with all rows collapsed.
+ *
+ * Returns: a newly created #GtkTreeList.
+ **/
+GListModel *
+gtk_tree_list_new (GListModel *root,
+ GtkTreeListCreateModelFunc create_func,
+ gpointer user_data,
+ GDestroyNotify user_destroy)
+{
+ GtkTreeList *self;
+
+ g_return_val_if_fail (G_IS_LIST_MODEL (root), NULL);
+ g_return_val_if_fail (create_func != NULL, NULL);
+
+ self = g_object_new (GTK_TYPE_TREE_LIST,
+ NULL);
+
+ gtk_tree_list_init_node (self, &self->root_node, g_object_ref (root));
+ self->create_func = create_func;
+ self->user_data = user_data;
+ self->user_destroy = user_destroy;
+
+ return G_LIST_MODEL (self);
+}
+
+guint
+gtk_tree_list_get_depth (GtkTreeList *self,
+ guint position)
+{
+ TreeNode *node;
+ guint depth;
+
+ g_return_val_if_fail (GTK_IS_TREE_LIST (self), FALSE);
+
+ node = gtk_tree_list_get_nth (self, position);
+ if (node == NULL)
+ return 0;
+
+ depth = 0;
+ for (node = node->parent;
+ !node->is_root;
+ node = node->parent)
+ depth++;
+
+ return depth;
+}
+
+static GListModel *
+tree_node_create_model (GtkTreeList *self,
+ TreeNode *node)
+{
+ TreeNode *parent = node->parent;
+ GListModel *model;
+ GObject *item;
+
+ item = g_list_model_get_item (parent->model,
+ tree_node_get_local_position (parent->children, node));
+ model = self->create_func (item, self->user_data);
+ g_object_unref (item);
+ if (model == NULL)
+ node->empty = TRUE;
+
+ return model;
+}
+
+static void
+gtk_tree_list_expand_node (GtkTreeList *self,
+ guint position,
+ TreeNode *node)
+{
+ GListModel *model;
+ guint n_items;
+
+ if (node->empty)
+ return;
+
+ if (node->model != NULL)
+ return;
+
+ model = tree_node_create_model (self, node);
+
+ if (model == NULL)
+ return;
+
+ g_assert (g_list_model_get_item_type (model) == g_list_model_get_item_type (self->root_node.model));
+ gtk_tree_list_init_node (self, node, model);
+
+ tree_node_mark_dirty (node);
+ n_items = tree_node_get_n_children (node);
+
+ if (n_items > 0)
+ g_list_model_items_changed (G_LIST_MODEL (self), position + 1, 0, n_items);
+}
+
+static void
+gtk_tree_list_collapse_node (GtkTreeList *self,
+ guint position,
+ TreeNode *node)
+{
+ guint n_items;
+
+ if (node->model == NULL)
+ return;
+
+ n_items = tree_node_get_n_children (node);
+
+ g_clear_pointer (&node->children, gtk_css_rb_tree_unref);
+ g_clear_object (&node->model);
+
+ tree_node_mark_dirty (node);
+
+ if (n_items > 0)
+ g_list_model_items_changed (G_LIST_MODEL (self), position + 1, n_items, 0);
+}
+
+void
+gtk_tree_list_set_expanded (GtkTreeList *self,
+ guint position,
+ gboolean expanded)
+{
+ TreeNode *node;
+
+ g_return_if_fail (GTK_IS_TREE_LIST (self));
+
+ node = gtk_tree_list_get_nth (self, position);
+ if (node == NULL)
+ return;
+
+ if (expanded)
+ gtk_tree_list_expand_node (self, position, node);
+ else
+ gtk_tree_list_collapse_node (self, position, node);
+}
+
+gboolean
+gtk_tree_list_get_expanded (GtkTreeList *self,
+ guint position)
+{
+ TreeNode *node;
+
+ g_return_val_if_fail (GTK_IS_TREE_LIST (self), FALSE);
+
+ node = gtk_tree_list_get_nth (self, position);
+ if (node == NULL)
+ return FALSE;
+
+ return node->children != NULL;
+}
+
+gboolean
+gtk_tree_list_is_expandable (GtkTreeList *self,
+ guint position)
+{
+ GListModel *model;
+ TreeNode *node;
+
+ g_return_val_if_fail (GTK_IS_TREE_LIST (self), FALSE);
+
+ node = gtk_tree_list_get_nth (self, position);
+ if (node == NULL)
+ return FALSE;
+
+ if (node->empty)
+ return FALSE;
+
+ if (node->model)
+ return TRUE;
+
+ model = tree_node_create_model (self, node);
+ if (model)
+ {
+ g_object_unref (model);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
diff --git a/gtk/gtktreelist.h b/gtk/gtktreelist.h
new file mode 100644
index 0000000000..9fdb4d7be4
--- /dev/null
+++ b/gtk/gtktreelist.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright © 2018 Benjamin Otte
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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 library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte@gnome.org>
+ */
+
+#ifndef __GTK_TREE_LIST_H__
+#define __GTK_TREE_LIST_H__
+
+
+#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
+#error "Only <gtk/gtk.h> can be included directly."
+#endif
+
+#include <gio/gio.h>
+#include <gtk/gtkwidget.h>
+
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_TREE_LIST (gtk_tree_list_get_type ())
+
+GDK_AVAILABLE_IN_ALL
+G_DECLARE_FINAL_TYPE (GtkTreeList, gtk_tree_list, GTK, TREE_LIST, GObject)
+
+typedef GListModel * (* GtkTreeListCreateModelFunc) (gpointer item, gpointer data);
+
+GDK_AVAILABLE_IN_ALL
+GListModel * gtk_tree_list_new (GListModel *root,
+ GtkTreeListCreateModelFunc create_func,
+ gpointer data,
+ GDestroyNotify data_destroy);
+
+GDK_AVAILABLE_IN_ALL
+guint gtk_tree_list_get_depth (GtkTreeList *self,
+ guint position);
+GDK_AVAILABLE_IN_ALL
+void gtk_tree_list_set_expanded (GtkTreeList *self,
+ guint position,
+ gboolean expanded);
+GDK_AVAILABLE_IN_ALL
+gboolean gtk_tree_list_get_expanded (GtkTreeList *self,
+ guint position);
+GDK_AVAILABLE_IN_ALL
+gboolean gtk_tree_list_is_expandable (GtkTreeList *self,
+ guint position);
+
+
+G_END_DECLS
+
+#endif /* __GTK_TREE_LIST_H__ */
diff --git a/gtk/meson.build b/gtk/meson.build
index aea3b8e9b1..90688dad65 100644
--- a/gtk/meson.build
+++ b/gtk/meson.build
@@ -75,6 +75,7 @@ gtk_private_sources = files([
'gtkcssparser.c',
'gtkcsspathnode.c',
'gtkcsspositionvalue.c',
+ 'gtkcssrbtree.c',
'gtkcssrepeatvalue.c',
'gtkcssrgbavalue.c',
'gtkcssselector.c',
@@ -362,6 +363,7 @@ gtk_public_sources = files([
'gtktooltip.c',
'gtktooltipwindow.c',
'gtktreednd.c',
+ 'gtktreelist.c',
'gtktreemenu.c',
'gtktreemodel.c',
'gtktreemodelfilter.c',
@@ -583,6 +585,7 @@ gtk_public_headers = files([
'gtktoolshell.h',
'gtktooltip.h',
'gtktreednd.h',
+ 'gtktreelist.h',
'gtktreemodel.h',
'gtktreemodelfilter.h',
'gtktreemodelsort.h',
diff --git a/tests/meson.build b/tests/meson.build
index 772dc23e49..6f141ddfdc 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -113,6 +113,7 @@ gtk_tests = [
['testrevealer'],
['testrevealer2'],
['testtitlebar'],
+ ['testtreelist'],
['testsplitheaders'],
['teststackedheaders'],
['testactionbar'],
diff --git a/tests/testtreelist.c b/tests/testtreelist.c
new file mode 100644
index 0000000000..d08d2d9407
--- /dev/null
+++ b/tests/testtreelist.c
@@ -0,0 +1,151 @@
+#include <gtk/gtk.h>
+
+static GListModel *
+create_list_model_for_directory (gpointer file,
+ gpointer unused)
+{
+ GFileEnumerator *enumerate;
+ GListStore *store;
+ GFile *child;
+ GFileInfo *info;
+
+ enumerate = g_file_enumerate_children (file,
+ G_FILE_ATTRIBUTE_STANDARD_TYPE
+ "," G_FILE_ATTRIBUTE_STANDARD_NAME,
+ 0,
+ NULL,
+ NULL);
+ if (enumerate == NULL)
+ return NULL;
+
+ store = g_list_store_new (G_TYPE_FILE);
+
+ while (g_file_enumerator_iterate (enumerate, &info, NULL, NULL, NULL))
+ {
+ if (info == NULL)
+ break;
+
+ if (g_file_info_get_file_type (info) != G_FILE_TYPE_DIRECTORY)
+ continue;
+
+ child = g_file_get_child (file, g_file_info_get_name (info));
+ g_list_store_append (store, child);
+ g_object_unref (child);
+ }
+
+ g_object_unref (enumerate);
+
+ return G_LIST_MODEL (store);
+}
+
+static GtkTreeList *
+get_tree_list (GtkWidget *row)
+{
+ return GTK_TREE_LIST (g_object_get_data (G_OBJECT (gtk_widget_get_parent (row)), "model"));
+}
+
+static void
+expand_clicked (GtkWidget *button,
+ GtkWidget *row)
+{
+ gtk_tree_list_set_expanded (get_tree_list (row),
+ gtk_list_box_row_get_index (GTK_LIST_BOX_ROW (row)),
+ TRUE);
+}
+
+static void
+collapse_clicked (GtkWidget *button,
+ GtkWidget *row)
+{
+ gtk_tree_list_set_expanded (get_tree_list (row),
+ gtk_list_box_row_get_index (GTK_LIST_BOX_ROW (row)),
+ FALSE);
+}
+
+static GtkWidget *
+create_widget_for_model (gpointer file,
+ gpointer root)
+{
+ GtkWidget *row, *box, *child;
+ GFile *iter;
+ guint depth;
+
+ row = gtk_list_box_row_new ();
+
+ depth = 0;
+ for (iter = g_object_ref (g_file_get_parent (file));
+ !g_file_equal (root, iter);
+ g_set_object (&iter, g_file_get_parent (iter)))
+ {
+ g_object_unref (iter);
+ depth++;
+ }
+ g_object_unref (iter);
+ g_object_unref (iter);
+
+ box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+ gtk_container_add (GTK_CONTAINER (row), box);
+
+ if (depth > 0)
+ {
+ child = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+ gtk_widget_set_size_request (child, 16 * depth, 0);
+ gtk_container_add (GTK_CONTAINER (box), child);
+ }
+
+ child = gtk_button_new_from_icon_name ("list-remove-symbolic");
+ gtk_button_set_relief (GTK_BUTTON (child), GTK_RELIEF_NONE);
+ g_signal_connect (child, "clicked", G_CALLBACK (collapse_clicked), row);
+ gtk_container_add (GTK_CONTAINER (box), child);
+
+ child = gtk_button_new_from_icon_name ("list-add-symbolic");
+ gtk_button_set_relief (GTK_BUTTON (child), GTK_RELIEF_NONE);
+ g_signal_connect (child, "clicked", G_CALLBACK (expand_clicked), row);
+ gtk_container_add (GTK_CONTAINER (box), child);
+
+ child = gtk_label_new (g_file_get_basename (file));
+ gtk_container_add (GTK_CONTAINER (box), child);
+
+ return row;
+}
+
+int
+main (int argc, char *argv[])
+{
+ GtkWidget *win;
+ GtkWidget *sw;
+ GtkWidget *listbox;
+ GListModel *model, *dirmodel;
+ GFile *root;
+
+ gtk_init ();
+
+ win = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ gtk_window_set_default_size (GTK_WINDOW (win), 400, 600);
+ g_signal_connect (win, "destroy", G_CALLBACK (gtk_main_quit), win);
+
+ sw = gtk_scrolled_window_new (NULL, NULL);
+ gtk_container_add (GTK_CONTAINER (win), sw);
+
+ listbox = gtk_list_box_new ();
+ gtk_container_add (GTK_CONTAINER (sw), listbox);
+
+ root = g_file_new_for_path (g_get_current_dir ());
+ dirmodel = create_list_model_for_directory (root, NULL);
+ model = gtk_tree_list_new (dirmodel,
+ create_list_model_for_directory,
+ NULL, NULL);
+ g_object_unref (dirmodel);
+ gtk_list_box_bind_model (GTK_LIST_BOX (listbox),
+ model,
+ create_widget_for_model,
+ root, g_object_unref);
+ g_object_set_data (G_OBJECT (listbox), "model", model);
+ g_object_unref (model);
+
+ gtk_widget_show (win);
+
+ gtk_main ();
+
+ return 0;
+}