forked from AuroraMiddleware/gtk
573c63973a
This is a GListModel implementation with a neat API that can be used to implement trees inside GtkListBox.
762 lines
16 KiB
C
762 lines
16 KiB
C
/* 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->left);
|
|
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->right);
|
|
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;
|
|
gtk_css_rb_node_mark_dirty (y->parent, TRUE);
|
|
}
|
|
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;
|
|
gtk_css_rb_node_mark_dirty (y->parent, TRUE);
|
|
}
|
|
else
|
|
{
|
|
tree->root = y;
|
|
}
|
|
gtk_css_rb_node_mark_dirty (y, TRUE);
|
|
}
|
|
|
|
gtk_css_rb_node_free (tree, real_node);
|
|
}
|
|
|
|
void
|
|
gtk_css_rb_tree_remove_all (GtkCssRbTree *tree)
|
|
{
|
|
if (tree->root)
|
|
gtk_css_rb_node_free_deep (tree, tree->root);
|
|
|
|
tree->root = NULL;
|
|
}
|
|
|
|
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);
|
|
}
|