/*
* This program 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 of the License, or (at your option) version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with the program; if not, see <http://www.gnu.org/licenses/>
*
*
* Authors:
* Chris Lahey <clahey@ximian.com>
* Chris Toshok <toshok@ximian.com>
*
* Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include "e-tree-memory.h"
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <libxml/parser.h>
#include <libxml/xmlmemory.h>
#include "e-xml-utils.h"
#define E_TREE_MEMORY_GET_PRIVATE(obj) \
(G_TYPE_INSTANCE_GET_PRIVATE \
((obj), E_TYPE_TREE_MEMORY, ETreeMemoryPrivate))
G_DEFINE_TYPE (ETreeMemory, e_tree_memory, E_TYPE_TREE_MODEL)
enum {
FILL_IN_CHILDREN,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL] = { 0, };
typedef struct ETreeMemoryPath ETreeMemoryPath;
struct ETreeMemoryPath {
gpointer node_data;
guint children_computed : 1;
/* parent/child/sibling pointers */
ETreeMemoryPath *parent;
ETreeMemoryPath *next_sibling;
ETreeMemoryPath *prev_sibling;
ETreeMemoryPath *first_child;
ETreeMemoryPath *last_child;
gint num_children;
};
struct _ETreeMemoryPrivate {
ETreeMemoryPath *root;
/* whether nodes are created expanded
* or collapsed by default */
gboolean expanded_default;
gint frozen;
GFunc destroy_func;
gpointer destroy_user_data;
};
/* ETreeMemoryPath functions */
static inline void
check_children (ETreeMemory *memory,
ETreePath node)
{
ETreeMemoryPath *path = node;
if (!path->children_computed) {
g_signal_emit (memory, signals[FILL_IN_CHILDREN], 0, node);
path->children_computed = TRUE;
}
}
static gint
e_tree_memory_path_depth (ETreeMemoryPath *path)
{
gint depth = 0;
g_return_val_if_fail (path != NULL, -1);
for (path = path->parent; path; path = path->parent)
depth++;
return depth;
}
static void
e_tree_memory_path_insert (ETreeMemoryPath *parent,
gint position,
ETreeMemoryPath *child)
{
g_return_if_fail (position <= parent->num_children && position >= -1);
child->parent = parent;
if (parent->first_child == NULL)
parent->first_child = child;
if (position == -1 || position == parent->num_children) {
child->prev_sibling = parent->last_child;
if (parent->last_child)
parent->last_child->next_sibling = child;
parent->last_child = child;
} else {
ETreeMemoryPath *c;
for (c = parent->first_child; c; c = c->next_sibling) {
if (position == 0) {
child->next_sibling = c;
child->prev_sibling = c->prev_sibling;
if (child->next_sibling)
child->next_sibling->prev_sibling = child;
if (child->prev_sibling)
child->prev_sibling->next_sibling = child;
if (parent->first_child == c)
parent->first_child = child;
break;
}
position--;
}
}
parent->num_children++;
}
static void
e_tree_path_unlink (ETreeMemoryPath *path)
{
ETreeMemoryPath *parent = path->parent;
/* unlink first/last child if applicable */
if (parent) {
if (path == parent->first_child)
parent->first_child = path->next_sibling;
if (path == parent->last_child)
parent->last_child = path->prev_sibling;
parent->num_children--;
}
/* unlink prev/next sibling links */
if (path->next_sibling)
path->next_sibling->prev_sibling = path->prev_sibling;
if (path->prev_sibling)
path->prev_sibling->next_sibling = path->next_sibling;
path->parent = NULL;
path->next_sibling = NULL;
path->prev_sibling = NULL;
}
/**
* e_tree_memory_freeze:
* @tree_memory: the ETreeModel to freeze.
*
* This function prepares an ETreeModel for a period of much change.
* All signals regarding changes to the tree are deferred until we
* thaw the tree.
*
**/
void
e_tree_memory_freeze (ETreeMemory *tree_memory)
{
ETreeMemoryPrivate *priv = tree_memory->priv;
if (priv->frozen == 0)
e_tree_model_pre_change (E_TREE_MODEL (tree_memory));
priv->frozen++;
}
/**
* e_tree_memory_thaw:
* @tree_memory: the ETreeMemory to thaw.
*
* This function thaws an ETreeMemory. All the defered signals can add
* up to a lot, we don't know - so we just emit a model_changed
* signal.
*
**/
void
e_tree_memory_thaw (ETreeMemory *tree_memory)
{
ETreeMemoryPrivate *priv = tree_memory->priv;
if (priv->frozen > 0)
priv->frozen--;
if (priv->frozen == 0) {
e_tree_model_node_changed (E_TREE_MODEL (tree_memory), priv->root);
}
}
/* virtual methods */
static void
tree_memory_dispose (GObject *object)
{
ETreeMemoryPrivate *priv;
priv = E_TREE_MEMORY_GET_PRIVATE (object);
if (priv->root)
e_tree_memory_node_remove (
E_TREE_MEMORY (object), priv->root);
G_OBJECT_CLASS (e_tree_memory_parent_class)->dispose (object);
}
static ETreePath
tree_memory_get_root (ETreeModel *etm)
{
ETreeMemoryPrivate *priv = E_TREE_MEMORY (etm)->priv;
return priv->root;
}
static ETreePath
tree_memory_get_parent (ETreeModel *etm,
ETreePath node)
{
ETreeMemoryPath *path = node;
return path->parent;
}
static ETreePath
tree_memory_get_first_child (ETreeModel *etm,
ETreePath node)
{
ETreeMemoryPath *path = node;
check_children (E_TREE_MEMORY (etm), node);
return path->first_child;
}
static ETreePath
tree_memory_get_last_child (ETreeModel *etm,
ETreePath node)
{
ETreeMemoryPath *path = node;
check_children (E_TREE_MEMORY (etm), node);
return path->last_child;
}
static ETreePath
tree_memory_get_next (ETreeModel *etm,
ETreePath node)
{
ETreeMemoryPath *path = node;
return path->next_sibling;
}
static ETreePath
tree_memory_get_prev (ETreeModel *etm,
ETreePath node)
{
ETreeMemoryPath *path = node;
return path->prev_sibling;
}
static gboolean
tree_memory_is_root (ETreeModel *etm,
ETreePath node)
{
ETreeMemoryPath *path = node;
return e_tree_memory_path_depth (path) == 0;
}
static gboolean
tree_memory_is_expandable (ETreeModel *etm,
ETreePath node)
{
ETreeMemoryPath *path = node;
check_children (E_TREE_MEMORY (etm), node);
return path->first_child != NULL;
}
static guint
tree_memory_get_children (ETreeModel *etm,
ETreePath node,
ETreePath **nodes)
{
ETreeMemoryPath *path = node;
guint n_children;
check_children (E_TREE_MEMORY (etm), node);
n_children = path->num_children;
if (nodes) {
ETreeMemoryPath *p;
gint i = 0;
(*nodes) = g_new (ETreePath, n_children);
for (p = path->first_child; p; p = p->next_sibling) {
(*nodes)[i++] = p;
}
}
return n_children;
}
static guint
tree_memory_depth (ETreeModel *etm,
ETreePath path)
{
return e_tree_memory_path_depth (path);
}
static gboolean
tree_memory_get_expanded_default (ETreeModel *etm)
{
ETreeMemory *tree_memory = E_TREE_MEMORY (etm);
ETreeMemoryPrivate *priv = tree_memory->priv;
return priv->expanded_default;
}
static void
tree_memory_clear_children_computed (ETreeMemoryPath *path)
{
for (path = path->first_child; path; path = path->next_sibling) {
path->children_computed = FALSE;
tree_memory_clear_children_computed (path);
}
}
static void
tree_memory_node_request_collapse (ETreeModel *etm,
ETreePath node)
{
ETreeModelClass *parent_class;
if (node)
tree_memory_clear_children_computed (node);
parent_class = E_TREE_MODEL_CLASS (e_tree_memory_parent_class);
if (parent_class->node_request_collapse != NULL)
parent_class->node_request_collapse (etm, node);
}
static void
e_tree_memory_class_init (ETreeMemoryClass *class)
{
GObjectClass *object_class;
ETreeModelClass *tree_model_class;
g_type_class_add_private (class, sizeof (ETreeMemoryPrivate));
object_class = G_OBJECT_CLASS (class);
object_class->dispose = tree_memory_dispose;
tree_model_class = E_TREE_MODEL_CLASS (class);
tree_model_class->get_root = tree_memory_get_root;
tree_model_class->get_prev = tree_memory_get_prev;
tree_model_class->get_next = tree_memory_get_next;
tree_model_class->get_first_child = tree_memory_get_first_child;
tree_model_class->get_last_child = tree_memory_get_last_child;
tree_model_class->get_parent = tree_memory_get_parent;
tree_model_class->is_root = tree_memory_is_root;
tree_model_class->is_expandable = tree_memory_is_expandable;
tree_model_class->get_children = tree_memory_get_children;
tree_model_class->depth = tree_memory_depth;
tree_model_class->get_expanded_default = tree_memory_get_expanded_default;
tree_model_class->node_request_collapse = tree_memory_node_request_collapse;
class->fill_in_children = NULL;
signals[FILL_IN_CHILDREN] = g_signal_new (
"fill_in_children",
G_TYPE_FROM_CLASS (object_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (ETreeMemoryClass, fill_in_children),
(GSignalAccumulator) NULL, NULL,
g_cclosure_marshal_VOID__POINTER,
G_TYPE_NONE, 1,
G_TYPE_POINTER);
}
static void
e_tree_memory_init (ETreeMemory *tree_memory)
{
tree_memory->priv = E_TREE_MEMORY_GET_PRIVATE (tree_memory);
}
/**
* e_tree_memory_construct:
* @tree_memory:
*
*
**/
void
e_tree_memory_construct (ETreeMemory *tree_memory)
{
}
/**
* e_tree_memory_new
*
* XXX docs here.
*
* return values: a newly constructed ETreeMemory.
*/
ETreeMemory *
e_tree_memory_new (void)
{
return g_object_new (E_TYPE_TREE_MEMORY, NULL);
}
/**
* e_tree_memory_set_expanded_default
*
* Sets the state of nodes to be append to a thread.
* They will either be expanded or collapsed, according to
* the value of @expanded.
*/
void
e_tree_memory_set_expanded_default (ETreeMemory *tree_memory,
gboolean expanded)
{
g_return_if_fail (tree_memory != NULL);
tree_memory->priv->expanded_default = expanded;
}
/**
* e_tree_memory_node_get_data:
* @tree_memory:
* @node:
*
*
*
* Return value:
**/
gpointer
e_tree_memory_node_get_data (ETreeMemory *tree_memory,
ETreePath node)
{
ETreeMemoryPath *path = node;
g_return_val_if_fail (path, NULL);
return path->node_data;
}
/**
* e_tree_memory_node_set_data:
* @tree_memory:
* @node:
* @node_data:
*
*
**/
void
e_tree_memory_node_set_data (ETreeMemory *tree_memory,
ETreePath node,
gpointer node_data)
{
ETreeMemoryPath *path = node;
g_return_if_fail (path);
path->node_data = node_data;
}
/**
* e_tree_memory_node_insert:
* @tree_memory:
* @parent_node:
* @position:
* @node_data:
*
*
*
* Return value:
**/
ETreePath
e_tree_memory_node_insert (ETreeMemory *tree_memory,
ETreePath parent_node,
gint position,
gpointer node_data)
{
ETreeMemoryPrivate *priv;
ETreeMemoryPath *new_path;
ETreeMemoryPath *parent_path = parent_node;
g_return_val_if_fail (tree_memory != NULL, NULL);
priv = tree_memory->priv;
g_return_val_if_fail (parent_path != NULL || priv->root == NULL, NULL);
priv = tree_memory->priv;
if (!tree_memory->priv->frozen)
e_tree_model_pre_change (E_TREE_MODEL (tree_memory));
new_path = g_slice_new0 (ETreeMemoryPath);
new_path->node_data = node_data;
new_path->children_computed = FALSE;
if (parent_path != NULL) {
e_tree_memory_path_insert (parent_path, position, new_path);
if (!tree_memory->priv->frozen)
e_tree_model_node_inserted (
E_TREE_MODEL (tree_memory),
parent_path, new_path);
} else {
priv->root = new_path;
if (!tree_memory->priv->frozen)
e_tree_model_node_changed (
E_TREE_MODEL (tree_memory), new_path);
}
return new_path;
}
ETreePath
e_tree_memory_node_insert_id (ETreeMemory *tree_memory,
ETreePath parent,
gint position,
gpointer node_data,
gchar *id)
{
return e_tree_memory_node_insert (tree_memory, parent, position, node_data);
}
/**
* e_tree_memory_node_insert_before:
* @tree_memory:
* @parent:
* @sibling:
* @node_data:
*
*
*
* Return value:
**/
ETreePath
e_tree_memory_node_insert_before (ETreeMemory *tree_memory,
ETreePath parent,
ETreePath sibling,
gpointer node_data)
{
ETreeMemoryPath *child;
ETreeMemoryPath *parent_path = parent;
ETreeMemoryPath *sibling_path = sibling;
gint position = 0;
g_return_val_if_fail (tree_memory != NULL, NULL);
if (sibling != NULL) {
for (child = parent_path->first_child; child; child = child->next_sibling) {
if (child == sibling_path)
break;
position++;
}
} else
position = parent_path->num_children;
return e_tree_memory_node_insert (tree_memory, parent, position, node_data);
}
/* just blows away child data, doesn't take into account unlinking/etc */
static void
child_free (ETreeMemory *tree_memory,
ETreeMemoryPath *node)
{
ETreeMemoryPath *child, *next;
child = node->first_child;
while (child) {
next = child->next_sibling;
child_free (tree_memory, child);
child = next;
}
if (tree_memory->priv->destroy_func) {
tree_memory->priv->destroy_func (node->node_data, tree_memory->priv->destroy_user_data);
}
g_slice_free (ETreeMemoryPath, node);
}
/**
* e_tree_memory_node_remove:
* @tree_memory:
* @path:
*
*
*
* Return value:
**/
gpointer
e_tree_memory_node_remove (ETreeMemory *tree_memory,
ETreePath node)
{
ETreeMemoryPath *path = node;
ETreeMemoryPath *parent = path->parent;
ETreeMemoryPath *sibling;
gpointer ret = path->node_data;
gint old_position = 0;
g_return_val_if_fail (tree_memory != NULL, NULL);
if (!tree_memory->priv->frozen) {
e_tree_model_pre_change (E_TREE_MODEL (tree_memory));
for (old_position = 0, sibling = path;
sibling;
old_position++, sibling = sibling->prev_sibling)
/* Empty intentionally*/;
old_position--;
}
/* unlink this node - we only have to unlink the root node being removed,
* since the others are only references from this node */
e_tree_path_unlink (path);
/*printf("removing %d nodes from position %d\n", visible, base);*/
if (!tree_memory->priv->frozen)
e_tree_model_node_removed (E_TREE_MODEL (tree_memory), parent, path, old_position);
child_free (tree_memory, path);
if (path == tree_memory->priv->root)
tree_memory->priv->root = NULL;
if (!tree_memory->priv->frozen)
e_tree_model_node_deleted (E_TREE_MODEL (tree_memory), path);
return ret;
}
typedef struct {
ETreeMemory *memory;
gpointer closure;
ETreeMemorySortCallback callback;
} MemoryAndClosure;
static gint
sort_callback (gconstpointer data1,
gconstpointer data2,
gpointer user_data)
{
ETreePath path1 = *(ETreePath *) data1;
ETreePath path2 = *(ETreePath *) data2;
MemoryAndClosure *mac = user_data;
return (*mac->callback) (mac->memory, path1, path2, mac->closure);
}
void
e_tree_memory_sort_node (ETreeMemory *tree_memory,
ETreePath node,
ETreeMemorySortCallback callback,
gpointer user_data)
{
ETreeMemoryPath **children;
ETreeMemoryPath *child;
gint count;
gint i;
ETreeMemoryPath *path = node;
MemoryAndClosure mac;
ETreeMemoryPath *last;
e_tree_model_pre_change (E_TREE_MODEL (tree_memory));
i = 0;
for (child = path->first_child; child; child = child->next_sibling)
i++;
children = g_new (ETreeMemoryPath *, i);
count = i;
for (child = path->first_child, i = 0;
child;
child = child->next_sibling, i++) {
children[i] = child;
}
mac.memory = tree_memory;
mac.closure = user_data;
mac.callback = callback;
g_qsort_with_data (
children, count, sizeof (ETreeMemoryPath *),
sort_callback, &mac);
path->first_child = NULL;
last = NULL;
for (i = 0;
i < count;
i++) {
children[i]->prev_sibling = last;
if (last)
last->next_sibling = children[i];
else
path->first_child = children[i];
last = children[i];
}
if (last)
last->next_sibling = NULL;
path->last_child = last;
g_free (children);
e_tree_model_node_changed (E_TREE_MODEL (tree_memory), node);
}
void
e_tree_memory_set_node_destroy_func (ETreeMemory *tree_memory,
GFunc destroy_func,
gpointer user_data)
{
tree_memory->priv->destroy_func = destroy_func;
tree_memory->priv->destroy_user_data = user_data;
}