/* This is -*- C -*- */
/* $Id: guppi-data-tree.c,v 1.20 2002/01/15 04:13:39 trow Exp $ */
/*
* guppi-data-tree.c
*
* Copyright (C) 2000 EMC Capital Management, Inc.
*
* Developed by Jon Trowbridge <trow@gnu.org> and
* Havoc Pennington <hp@pobox.com>.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA
*/
#include <config.h>
#include "guppi-data-tree.h"
#include <stdio.h>
#include <gtk/gtksignal.h>
#include <guppi-convenient.h>
/*
There are a whole bunch of private functions for manipulating
GuppiDataTreeNode objects. Nobody should be playing with these
objects except for the GuppiDataTree.
The only reason we expose them at all is to allow for the possibility
that other pieces of code might want to "walk the tree" in an efficient
manner. Instead, they should probably be totally opaque and we should
have a guppi_data_tree_for_each-style function... but that can come
later.
*/
static GuppiDataTreeNode *guppi_data_tree_node_add_child (GuppiDataTreeNode *,
GuppiData *);
static void
guppi_data_tree_node_destroy (GuppiDataTreeNode * node)
{
g_return_if_fail (node != NULL);
if (node->child)
guppi_data_tree_node_destroy (node->child);
gtk_signal_disconnect_by_data (GTK_OBJECT (node->data), node);
guppi_unref0 (node->data);
if (node->parent) {
node->parent->child =
node->sibling_prev ? node->sibling_prev : node->sibling_next;
if (node->parent->child)
node->parent->child->parent = node->parent;
}
if (node->sibling_prev)
node->sibling_prev->sibling_next = node->sibling_next;
if (node->sibling_next)
node->sibling_next->sibling_prev = node->sibling_prev;
guppi_free (node);
}
static void
guppi_data_tree_node_destroy_children (GuppiDataTreeNode * node)
{
g_return_if_fail (node != NULL);
if (node->child)
guppi_data_tree_node_destroy (node->child);
}
static void
guppi_data_tree_node_destroy_downhill (GuppiDataTreeNode *node)
{
g_return_if_fail (node != NULL);
if (node->child)
guppi_data_tree_node_destroy_downhill (node->child);
if (node->sibling_next)
guppi_data_tree_node_destroy_downhill (node->sibling_next);
guppi_data_tree_node_destroy (node);
}
static void
add_data_fn (GuppiData *subdata, gpointer user_data)
{
guppi_data_tree_node_add_child ((GuppiDataTreeNode *)user_data, subdata);
}
static void
guppi_data_tree_node_handle_subdata (GuppiDataTreeNode * node)
{
g_return_if_fail (node != NULL);
g_return_if_fail (node->data != NULL);
guppi_data_foreach_subdata (node->data, add_data_fn, node);
}
/* We should really just change the row in question */
static void
changed_label_cb (GuppiData * data, const gchar * name,
GuppiDataTreeNode * node)
{
if (node->reserved)
gtk_signal_emit_by_name (GTK_OBJECT (node->reserved), "changed");
}
static void
changed_subdata_cb (GuppiData * data, GuppiDataTreeNode * node)
{
g_return_if_fail (data != NULL);
g_return_if_fail (node != NULL);
guppi_data_tree_node_destroy_children (node);
guppi_data_tree_node_handle_subdata (node);
if (node->reserved)
gtk_signal_emit_by_name (GTK_OBJECT (node->reserved), "changed");
/* We should do the right things with the fine-grained signals as well. */
}
static GuppiDataTreeNode *
guppi_data_tree_node_new (GuppiData * data)
{
GuppiDataTreeNode *node;
g_return_val_if_fail (data != NULL, NULL);
node = guppi_new0 (GuppiDataTreeNode, 1);
node->data = data;
guppi_ref (data);
guppi_data_tree_node_handle_subdata (node);
gtk_signal_connect_after (GTK_OBJECT (data),
"changed_label",
GTK_SIGNAL_FUNC (changed_label_cb), node);
gtk_signal_connect_after (GTK_OBJECT (data),
"changed_subdata",
GTK_SIGNAL_FUNC (changed_subdata_cb), node);
return node;
}
static GuppiDataTreeNode *
guppi_data_tree_node_add_child (GuppiDataTreeNode * node, GuppiData * data)
{
GuppiDataTreeNode *new_child;
GuppiDataTreeNode *iter;
g_return_val_if_fail (node != NULL, NULL);
g_return_val_if_fail (data != NULL, NULL);
new_child = guppi_data_tree_node_new (data);
if (node->child == NULL) {
new_child->parent = node;
node->child = new_child;
} else {
iter = node->child;
while (iter->sibling_next)
iter = iter->sibling_next;
iter->sibling_next = new_child;
new_child->sibling_prev = iter;
}
return new_child;
}
static GuppiDataTreeNode *
guppi_data_tree_node_add_sibling (GuppiDataTreeNode * node, GuppiData * data)
{
GuppiDataTreeNode *new_sib;
GuppiDataTreeNode *iter;
g_return_val_if_fail (node != NULL, NULL);
g_return_val_if_fail (data != NULL, NULL);
new_sib = guppi_data_tree_node_new (data);
iter = node;
while (iter->sibling_next)
iter = iter->sibling_next;
iter->sibling_next = new_sib;
new_sib->sibling_prev = iter;
return new_sib;
}
static GuppiDataTreeNode *
guppi_data_tree_node_add_sibling_here (GuppiDataTreeNode * node,
GuppiData * data)
{
GuppiDataTreeNode *new_sib;
g_return_val_if_fail (node != NULL, NULL);
g_return_val_if_fail (data != NULL, NULL);
new_sib = guppi_data_tree_node_new (data);
new_sib->sibling_next = node->sibling_next;
if (node->sibling_next)
node->sibling_next->sibling_prev = new_sib;
node->sibling_next = new_sib;
new_sib->sibling_prev = node;
return new_sib;
}
static void
guppi_data_tree_node_set_reserved (GuppiDataTreeNode * node, gpointer r)
{
g_return_if_fail (node != NULL);
node->reserved = r;
if (node->sibling_next)
guppi_data_tree_node_set_reserved (node->sibling_next, r);
if (node->child)
guppi_data_tree_node_set_reserved (node->child, r);
}
#if 0
static GuppiDataTreeNode *
guppi_data_tree_node_parent (GuppiDataTreeNode * node)
{
g_return_val_if_fail (node != NULL, NULL);
/* The first sibling should hold the link to the parent. */
while (node) {
if (node->parent)
return node->parent;
node = node->sibling_prev;
}
return NULL;
}
#endif
static GuppiDataTreeNode *
guppi_data_tree_node_search (GuppiDataTreeNode * node, GuppiData * data)
{
GuppiDataTreeNode *result;
g_return_val_if_fail (node != NULL, NULL);
g_return_val_if_fail (data != NULL, NULL);
if (node->data == data)
return node;
if (node->sibling_next) {
result = guppi_data_tree_node_search (node->sibling_next, data);
if (result)
return result;
}
if (node->child) {
result = guppi_data_tree_node_search (node->child, data);
if (result)
return result;
}
return NULL;
}
static gsize
guppi_data_tree_node_downhill_size (const GuppiDataTreeNode * node)
{
const GuppiDataTreeNode *iter = node;
gsize size = 0;
iter = node;
while (iter) {
++size;
if (iter->child)
size += guppi_data_tree_node_downhill_size (iter->child);
iter = iter->sibling_next;
}
return size;
}
/***************************************************************************/
static GtkObjectClass *parent_class = NULL;
enum {
CHANGED,
ADDED,
REMOVED,
LAST_SIGNAL
};
static guint tree_signals[LAST_SIGNAL] = { 0 };
static void
guppi_data_tree_finalize (GtkObject * obj)
{
GuppiDataTree *tree = GUPPI_DATA_TREE (obj);
guppi_finalized (obj);
if (tree->root) {
guppi_data_tree_node_destroy_downhill (tree->root);
tree->root = NULL;
}
if (parent_class->finalize)
parent_class->finalize (obj);
}
static void
guppi_data_tree_class_init (GuppiDataTreeClass * klass)
{
GtkObjectClass *object_class = (GtkObjectClass *) klass;
parent_class = gtk_type_class (GTK_TYPE_OBJECT);
tree_signals[CHANGED] =
gtk_signal_new ("changed",
GTK_RUN_FIRST,
object_class->type,
GTK_SIGNAL_OFFSET (GuppiDataTreeClass, changed),
gtk_marshal_NONE__NONE, GTK_TYPE_NONE, 0);
tree_signals[ADDED] =
gtk_signal_new ("added",
GTK_RUN_FIRST,
object_class->type,
GTK_SIGNAL_OFFSET (GuppiDataTreeClass, added),
gtk_marshal_NONE__POINTER,
GTK_TYPE_NONE, 1, GTK_TYPE_POINTER);
tree_signals[REMOVED] =
gtk_signal_new ("removed",
GTK_RUN_FIRST,
object_class->type,
GTK_SIGNAL_OFFSET (GuppiDataTreeClass, removed),
gtk_marshal_NONE__POINTER,
GTK_TYPE_NONE, 1, GTK_TYPE_POINTER);
gtk_object_class_add_signals (object_class, tree_signals, LAST_SIGNAL);
object_class->finalize = guppi_data_tree_finalize;
}
static void
guppi_data_tree_init (GuppiDataTree * obj)
{
}
GtkType guppi_data_tree_get_type (void)
{
static GtkType guppi_data_tree_type = 0;
if (!guppi_data_tree_type) {
static const GtkTypeInfo guppi_data_tree_info = {
"GuppiDataTree",
sizeof (GuppiDataTree),
sizeof (GuppiDataTreeClass),
(GtkClassInitFunc) guppi_data_tree_class_init,
(GtkObjectInitFunc) guppi_data_tree_init,
NULL, NULL, (GtkClassInitFunc) NULL
};
guppi_data_tree_type =
gtk_type_unique (GTK_TYPE_OBJECT, &guppi_data_tree_info);
}
return guppi_data_tree_type;
}
GuppiDataTree *
guppi_data_tree_new (void)
{
return GUPPI_DATA_TREE (guppi_type_new (guppi_data_tree_get_type ()));
}
void
guppi_data_tree_add (GuppiDataTree * tree, GuppiData * data)
{
GuppiDataTreeNode *node;
g_return_if_fail (tree != NULL);
g_return_if_fail (data != NULL);
if (tree->root == NULL)
node = tree->root = guppi_data_tree_node_new (data);
else
node = guppi_data_tree_node_add_sibling (tree->root, data);
guppi_data_tree_node_set_reserved (node, tree);
gtk_signal_emit (GTK_OBJECT (tree), tree_signals[ADDED], data);
gtk_signal_emit (GTK_OBJECT (tree), tree_signals[CHANGED]);
}
void
guppi_data_tree_add_beside (GuppiDataTree * tree,
GuppiData * exist, GuppiData * add)
{
GuppiDataTreeNode *node = NULL;
g_return_if_fail (tree != NULL);
g_return_if_fail (exist != NULL);
g_return_if_fail (add != NULL);
if (tree->root)
node = guppi_data_tree_node_search (tree->root, exist);
if (node == NULL) {
g_warning ("%s not in tree", guppi_data_get_label (exist));
return;
}
node = guppi_data_tree_node_add_sibling_here (node, add);
guppi_data_tree_node_set_reserved (node, tree);
gtk_signal_emit (GTK_OBJECT (tree), tree_signals[ADDED], add);
gtk_signal_emit (GTK_OBJECT (tree), tree_signals[CHANGED]);
}
void
guppi_data_tree_add_below (GuppiDataTree * tree,
GuppiData * exist, GuppiData * add)
{
GuppiDataTreeNode *node = NULL;
g_return_if_fail (tree != NULL);
g_return_if_fail (exist != NULL);
g_return_if_fail (add != NULL);
if (tree->root)
node = guppi_data_tree_node_search (tree->root, exist);
if (node == NULL) {
g_warning ("%s not in tree", guppi_data_get_label (exist));
return;
}
node = guppi_data_tree_node_add_child (node, add);
guppi_data_tree_node_set_reserved (node, tree);
gtk_signal_emit (GTK_OBJECT (tree), tree_signals[ADDED], add);
gtk_signal_emit (GTK_OBJECT (tree), tree_signals[CHANGED]);
}
void
guppi_data_tree_remove (GuppiDataTree * tree, GuppiData * remv)
{
GuppiDataTreeNode *node = NULL;
GuppiData *data;
g_return_if_fail (tree != NULL);
g_return_if_fail (remv != NULL);
if (tree->root)
node = guppi_data_tree_node_search (tree->root, remv);
if (node == NULL) {
g_warning ("%s not in tree", guppi_data_get_label (remv));
return;
}
if (node == tree->root)
tree->root = node->sibling_next;
data = node->data;
guppi_ref (data);
guppi_data_tree_node_destroy (node);
gtk_signal_emit (GTK_OBJECT (tree), tree_signals[REMOVED], data);
gtk_signal_emit (GTK_OBJECT (tree), tree_signals[CHANGED]);
guppi_unref (data);
}
gsize guppi_data_tree_size (const GuppiDataTree * tree)
{
g_return_val_if_fail (tree != NULL, 0);
return tree->root ? guppi_data_tree_node_downhill_size (tree->root) : 0;
}
/* Recursion is your friend. */
static void
get_all_iter (GuppiDataTreeNode * node, GuppiData ** vec, gsize * i,
gsize max)
{
g_return_if_fail (node != NULL);
g_return_if_fail (vec != NULL);
g_return_if_fail (*i < max);
vec[*i] = node->data;
guppi_ref (vec[*i]);
++*i;
if (node->child)
get_all_iter (node->child, vec, i, max);
if (node->sibling_next)
get_all_iter (node->sibling_next, vec, i, max);
}
GuppiData **
guppi_data_tree_get_all (const GuppiDataTree * tree)
{
GuppiData **vec;
gsize i, N;
g_return_val_if_fail (tree != NULL, NULL);
N = guppi_data_tree_size (tree);
vec = guppi_new0 (GuppiData *, N + 1);
vec[N] = NULL; /* it never hurts to be explicit */
if (tree->root) {
i = 0;
get_all_iter (tree->root, vec, &i, N);
}
return vec;
}
static void
get_by_type_iter (GuppiDataTreeNode * node, GList ** accum,
gsize * N, GtkType type)
{
g_return_if_fail (node != NULL);
g_return_if_fail (accum != NULL);
g_return_if_fail (type != 0);
if (GTK_OBJECT_TYPE (node->data) == type) {
*accum = g_list_append (*accum, node->data);
++*N;
}
if (node->child)
get_by_type_iter (node->child, accum, N, type);
if (node->sibling_next)
get_by_type_iter (node->sibling_next, accum, N, type);
}
GuppiData **
guppi_data_tree_get_by_type (const GuppiDataTree * tree, GtkType type)
{
GuppiData **vec;
GList *accum = NULL;
GList *iter;
gsize i, N = 0;
g_return_val_if_fail (tree != NULL, NULL);
if (type == 0)
return guppi_data_tree_get_all (tree);
if (tree->root == NULL) {
vec = guppi_new0 (GuppiData *, 1);
vec[0] = NULL;
return vec;
}
get_by_type_iter (tree->root, &accum, &N, type);
vec = guppi_new0 (GuppiData *, N + 1);
iter = accum;
i = 0;
while (iter) {
vec[i] = (GuppiData *) iter->data;
guppi_ref (vec[i]);
++i;
iter = g_list_next (iter);
}
g_list_free (accum);
return vec;
}
GuppiDataTree *
guppi_data_tree_main (void)
{
static GuppiDataTree *tree = NULL;
if (tree == NULL) {
tree = guppi_data_tree_new ();
}
return tree;
}
static void
spew_iter (const GuppiDataTreeNode * node, gint level)
{
gint i;
for (i = 0; i < level; ++i) {
printf (" ");
}
printf ("%s\n", guppi_data_get_label (node->data));
if (node->child)
spew_iter (node->child, level + 1);
if (node->sibling_next)
spew_iter (node->sibling_next, level);
}
void
guppi_data_tree_spew (const GuppiDataTree * tree)
{
if (tree == NULL || tree->root == NULL)
return;
spew_iter (tree->root, 0);
}
/* $Id: guppi-data-tree.c,v 1.20 2002/01/15 04:13:39 trow Exp $ */
syntax highlighted by Code2HTML, v. 0.9.1