diff options
-rw-r--r-- | gtk/gtk.h | 1 | ||||
-rw-r--r-- | gtk/gtkcssrbtree.c | 750 | ||||
-rw-r--r-- | gtk/gtkcssrbtreeprivate.h | 89 | ||||
-rw-r--r-- | gtk/gtktreelist.c | 569 | ||||
-rw-r--r-- | gtk/gtktreelist.h | 64 | ||||
-rw-r--r-- | gtk/meson.build | 3 | ||||
-rw-r--r-- | tests/meson.build | 1 | ||||
-rw-r--r-- | tests/testtreelist.c | 151 |
8 files changed, 1628 insertions, 0 deletions
@@ -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; +} |