diff options
Diffstat (limited to 'e-util/e-tree-memory.c')
-rw-r--r-- | e-util/e-tree-memory.c | 743 |
1 files changed, 743 insertions, 0 deletions
diff --git a/e-util/e-tree-memory.c b/e-util/e-tree-memory.c new file mode 100644 index 0000000000..0af5d27b31 --- /dev/null +++ b/e-util/e-tree-memory.c @@ -0,0 +1,743 @@ +/* + * 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: + * @etmm: 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 *etmm) +{ + ETreeMemoryPrivate *priv = etmm->priv; + + if (priv->frozen == 0) + e_tree_model_pre_change (E_TREE_MODEL (etmm)); + + priv->frozen++; +} + +/** + * e_tree_memory_thaw: + * @etmm: 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 *etmm) +{ + ETreeMemoryPrivate *priv = etmm->priv; + + if (priv->frozen > 0) + priv->frozen--; + if (priv->frozen == 0) { + e_tree_model_node_changed (E_TREE_MODEL (etmm), priv->root); + } +} + +/* virtual methods */ + +static void +etmm_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 +etmm_get_root (ETreeModel *etm) +{ + ETreeMemoryPrivate *priv = E_TREE_MEMORY (etm)->priv; + return priv->root; +} + +static ETreePath +etmm_get_parent (ETreeModel *etm, + ETreePath node) +{ + ETreeMemoryPath *path = node; + return path->parent; +} + +static ETreePath +etmm_get_first_child (ETreeModel *etm, + ETreePath node) +{ + ETreeMemoryPath *path = node; + + check_children (E_TREE_MEMORY (etm), node); + return path->first_child; +} + +static ETreePath +etmm_get_last_child (ETreeModel *etm, + ETreePath node) +{ + ETreeMemoryPath *path = node; + + check_children (E_TREE_MEMORY (etm), node); + return path->last_child; +} + +static ETreePath +etmm_get_next (ETreeModel *etm, + ETreePath node) +{ + ETreeMemoryPath *path = node; + return path->next_sibling; +} + +static ETreePath +etmm_get_prev (ETreeModel *etm, + ETreePath node) +{ + ETreeMemoryPath *path = node; + return path->prev_sibling; +} + +static gboolean +etmm_is_root (ETreeModel *etm, + ETreePath node) +{ + ETreeMemoryPath *path = node; + return e_tree_memory_path_depth (path) == 0; +} + +static gboolean +etmm_is_expandable (ETreeModel *etm, + ETreePath node) +{ + ETreeMemoryPath *path = node; + + check_children (E_TREE_MEMORY (etm), node); + return path->first_child != NULL; +} + +static guint +etmm_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 +etmm_depth (ETreeModel *etm, + ETreePath path) +{ + return e_tree_memory_path_depth (path); +} + +static gboolean +etmm_get_expanded_default (ETreeModel *etm) +{ + ETreeMemory *etmm = E_TREE_MEMORY (etm); + ETreeMemoryPrivate *priv = etmm->priv; + + return priv->expanded_default; +} + +static void +etmm_clear_children_computed (ETreeMemoryPath *path) +{ + for (path = path->first_child; path; path = path->next_sibling) { + path->children_computed = FALSE; + etmm_clear_children_computed (path); + } +} + +static void +etmm_node_request_collapse (ETreeModel *etm, + ETreePath node) +{ + ETreeModelClass *parent_class; + + if (node) + etmm_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 = etmm_dispose; + + tree_model_class = E_TREE_MODEL_CLASS (class); + tree_model_class->get_root = etmm_get_root; + tree_model_class->get_prev = etmm_get_prev; + tree_model_class->get_next = etmm_get_next; + tree_model_class->get_first_child = etmm_get_first_child; + tree_model_class->get_last_child = etmm_get_last_child; + tree_model_class->get_parent = etmm_get_parent; + + tree_model_class->is_root = etmm_is_root; + tree_model_class->is_expandable = etmm_is_expandable; + tree_model_class->get_children = etmm_get_children; + tree_model_class->depth = etmm_depth; + tree_model_class->get_expanded_default = etmm_get_expanded_default; + + tree_model_class->node_request_collapse = etmm_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 *etmm) +{ + etmm->priv = E_TREE_MEMORY_GET_PRIVATE (etmm); +} + +/** + * e_tree_memory_construct: + * @etree: + * + * + **/ +void +e_tree_memory_construct (ETreeMemory *etmm) +{ +} + +/** + * 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 *etree, + gboolean expanded) +{ + g_return_if_fail (etree != NULL); + + etree->priv->expanded_default = expanded; +} + +/** + * e_tree_memory_node_get_data: + * @etmm: + * @node: + * + * + * + * Return value: + **/ +gpointer +e_tree_memory_node_get_data (ETreeMemory *etmm, + ETreePath node) +{ + ETreeMemoryPath *path = node; + + g_return_val_if_fail (path, NULL); + + return path->node_data; +} + +/** + * e_tree_memory_node_set_data: + * @etmm: + * @node: + * @node_data: + * + * + **/ +void +e_tree_memory_node_set_data (ETreeMemory *etmm, + ETreePath node, + gpointer node_data) +{ + ETreeMemoryPath *path = node; + + g_return_if_fail (path); + + path->node_data = node_data; +} + +/** + * e_tree_memory_node_insert: + * @tree_model: + * @parent_path: + * @position: + * @node_data: + * + * + * + * Return value: + **/ +ETreePath +e_tree_memory_node_insert (ETreeMemory *tree_model, + ETreePath parent_node, + gint position, + gpointer node_data) +{ + ETreeMemoryPrivate *priv; + ETreeMemoryPath *new_path; + ETreeMemoryPath *parent_path = parent_node; + + g_return_val_if_fail (tree_model != NULL, NULL); + + priv = tree_model->priv; + + g_return_val_if_fail (parent_path != NULL || priv->root == NULL, NULL); + + priv = tree_model->priv; + + if (!tree_model->priv->frozen) + e_tree_model_pre_change (E_TREE_MODEL (tree_model)); + + 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_model->priv->frozen) + e_tree_model_node_inserted ( + E_TREE_MODEL (tree_model), + parent_path, new_path); + } else { + priv->root = new_path; + if (!tree_model->priv->frozen) + e_tree_model_node_changed ( + E_TREE_MODEL (tree_model), new_path); + } + + return new_path; +} + +ETreePath +e_tree_memory_node_insert_id (ETreeMemory *etree, + ETreePath parent, + gint position, + gpointer node_data, + gchar *id) +{ + return e_tree_memory_node_insert (etree, parent, position, node_data); +} + +/** + * e_tree_memory_node_insert_before: + * @etree: + * @parent: + * @sibling: + * @node_data: + * + * + * + * Return value: + **/ +ETreePath +e_tree_memory_node_insert_before (ETreeMemory *etree, + 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 (etree != 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 (etree, parent, position, node_data); +} + +/* just blows away child data, doesn't take into account unlinking/etc */ +static void +child_free (ETreeMemory *etree, + ETreeMemoryPath *node) +{ + ETreeMemoryPath *child, *next; + + child = node->first_child; + while (child) { + next = child->next_sibling; + child_free (etree, child); + child = next; + } + + if (etree->priv->destroy_func) { + etree->priv->destroy_func (node->node_data, etree->priv->destroy_user_data); + } + + g_slice_free (ETreeMemoryPath, node); +} + +/** + * e_tree_memory_node_remove: + * @etree: + * @path: + * + * + * + * Return value: + **/ +gpointer +e_tree_memory_node_remove (ETreeMemory *etree, + 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 (etree != NULL, NULL); + + if (!etree->priv->frozen) { + e_tree_model_pre_change (E_TREE_MODEL (etree)); + 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 (!etree->priv->frozen) + e_tree_model_node_removed (E_TREE_MODEL (etree), parent, path, old_position); + + child_free (etree, path); + + if (path == etree->priv->root) + etree->priv->root = NULL; + + if (!etree->priv->frozen) + e_tree_model_node_deleted (E_TREE_MODEL (etree), 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 *etmm, + 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 (etmm)); + + 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 = etmm; + 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 (etmm), node); +} + +void +e_tree_memory_set_node_destroy_func (ETreeMemory *etmm, + GFunc destroy_func, + gpointer user_data) +{ + etmm->priv->destroy_func = destroy_func; + etmm->priv->destroy_user_data = user_data; +} |