Files
evolution/widgets/table/e-tree-model.c
Christopher James Lahey e16e25daa7 Changed these to match the new ETable system.
2000-10-11  Christopher James Lahey  <clahey@helixcode.com>

	* tests/test-tree-1.c, tests/test-tree-2.c, tests/test-tree-3.c:
	Changed these to match the new ETable system.

	* gal/Makefile.am: Added e-table-column-specification.lo,
	e-table-extras.lo, e-table-specification.lo, and e-table-state.lo.

From gal/e-table/ChangeLog:

2000-10-11  Christopher James Lahey  <clahey@helixcode.com>

	* Makefile.am: Added e-table-column-specification.c,
	e-table-column-specification.h, e-table-extras.c,
	e-table-extras.h, e-table-specification.c,
	e-table-specification.h, e-table-state.c, and e-table-state.h.
	Removed some duplicated .h files.

	* e-cell-tree.c: Ref, sink, and unref the subcell instead of destroying
	it when done.

	* e-table-column-specification.c, e-table-column-specification.h:
	New class which describes a column without having a table get
	instantiated.

	* e-table-config.c: Changed get_specification to get_state to get
	this to compile.

	* e-table-defines.h, e-table-item.h: Moved the definition of
	ETableCursorMode from e-table-item.h to e-table-defines.h.

	* e-table-extras.c, e-table-extras.h: New class which acts as a
	set of 3 hash tables.  All from char * and to alternately, ECells,
	GCompareFuncs, and GdkPxibufs.

	* e-table-scrolled.c, e-table-scrolled.h: Changed this to match
	the new ETable function declarations.

	* e-table-sort-info.c, e-table-sort-info.h: Added functions for
	saving to and loading from xml.

	* e-table-specification.c, e-table-specification.h: New class
	which describes a table without having to instantiate it.

	* e-table-state.c, e-table-state.h: New class which describes the
	state of a table without having to instantiate the table.

	* e-table.c, e-table.h: Changed this to accept both a state and a
	specification instead of just a specification.  You then save only
	the state.  The specification stays exactly the same.  Also, you
	no longer need to pass in an ETableHeader.  Most of the
	information contained in the ETableHeader are in the
	specification.  However you may need to translate some of the
	strings in the specification to objects.  If you need anything
	other than the builtin choices, you need to create an ETableExtras
	and pass it in.

	* e-tree-model.c: Removed an unused variable.

svn path=/trunk/; revision=5837
2000-10-11 08:16:37 +00:00

1132 lines
26 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* e-tree-model.c: a Tree Model
*
* Author:
* Chris Toshok (toshok@helixcode.com)
*
* Adapted from the gtree code and ETableModel.
*
* (C) 2000 Helix Code, Inc.
*/
#include <config.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <gnome-xml/parser.h>
#include <gnome-xml/xmlmemory.h>
#include <gtk/gtksignal.h>
#include <stdlib.h>
#include "gal/util/e-util.h"
#include "gal/util/e-xml-utils.h"
#include "e-tree-model.h"
#define ETM_CLASS(e) ((ETreeModelClass *)((GtkObject *)e)->klass)
#define PARENT_TYPE E_TABLE_MODEL_TYPE
static ETableModel *e_tree_model_parent_class;
typedef struct {
gboolean expanded;
guint visible_descendents;
char *save_id;
ETreePathCompareFunc compare;
gpointer node_data;
} ENode;
enum {
NODE_CHANGED,
NODE_INSERTED,
NODE_REMOVED,
NODE_COLLAPSED,
NODE_EXPANDED,
LAST_SIGNAL
};
static guint e_tree_model_signals [LAST_SIGNAL] = {0, };
static void add_visible_descendents_to_array (ETreeModel *etm, GNode *gnode, int *row, int *count);
/* virtual methods */
static gboolean
expanded_remove_func (gpointer key, gpointer value, gpointer user_data)
{
g_free (key);
return TRUE;
}
static void
etree_destroy (GtkObject *object)
{
ETreeModel *etree = E_TREE_MODEL (object);
/* XXX lots of stuff to free here */
g_array_free (etree->row_array, TRUE);
g_hash_table_foreach_remove (etree->expanded_state,
expanded_remove_func, NULL);
g_string_free(etree->sort_group, TRUE);
GTK_OBJECT_CLASS (e_tree_model_parent_class)->destroy (object);
}
static ETreePath*
etree_get_root (ETreeModel *etm)
{
return etm->root;
}
static ETreePath*
etree_get_parent (ETreeModel *etm, ETreePath *path)
{
g_return_val_if_fail (path, NULL);
return path->parent;
}
static ETreePath*
etree_get_next (ETreeModel *etm, ETreePath *node)
{
g_return_val_if_fail (node, NULL);
return g_node_next_sibling(node);
}
static ETreePath*
etree_get_prev (ETreeModel *etm, ETreePath *node)
{
g_return_val_if_fail (node, NULL);
return g_node_prev_sibling (node);
}
static guint
etree_get_children (ETreeModel *etm, ETreePath* node, ETreePath ***paths)
{
guint n_children;
g_return_val_if_fail (node, 0);
n_children = g_node_n_children (node);
if (paths) {
int i;
(*paths) = g_malloc (sizeof (ETreePath*) * n_children);
for (i = 0; i < n_children; i ++) {
(*paths)[i] = g_node_nth_child (node, i);
}
}
return n_children;
}
static gboolean
etree_is_expanded (ETreeModel *etm, ETreePath* node)
{
g_return_val_if_fail (node && node->data, FALSE);
return ((ENode*)node->data)->expanded;
}
static gboolean
etree_is_visible (ETreeModel *etm, ETreePath* node)
{
g_return_val_if_fail (node, FALSE);
for (node = node->parent; node; node = node->parent) {
if (!((ENode*)node->data)->expanded)
return FALSE;
}
return TRUE;
}
static void
etree_set_expanded (ETreeModel *etm, ETreePath* node, gboolean expanded)
{
GNode *child;
ENode *enode;
int row;
g_return_if_fail (node && node->data);
enode = ((ENode*)node->data);
if (enode->expanded == expanded)
return;
if (expanded) {
gboolean allow_expand = TRUE;
e_tree_model_node_expanded (etm, node, &allow_expand);
if (!allow_expand)
return;
}
enode->expanded = expanded;
if (enode->save_id) {
g_hash_table_insert (etm->expanded_state, enode->save_id, (gpointer)expanded);
if (expanded) {
/* the node previously was collapsed */
etm->num_collapsed_to_save --;
etm->num_expanded_to_save ++;
}
else {
/* the node previously was expanded */
etm->num_expanded_to_save --;
etm->num_collapsed_to_save ++;
}
}
/* if the node wasn't visible at present */
if ((row = e_tree_model_row_of_node (etm, node)) == -1) {
if (!expanded) {
e_tree_model_node_collapsed (etm, node);
}
return;
}
row++;
if (expanded) {
GNode *parent;
if (e_tree_model_node_is_visible (etm, node)) {
enode->visible_descendents = 0;
for (child = g_node_first_child (node); child;
child = g_node_next_sibling (child)) {
add_visible_descendents_to_array (etm, child, &row, &enode->visible_descendents);
}
}
/* now iterate back up the tree, adding to our
ancestors' visible descendents */
for (parent = node->parent; parent; parent = parent->parent) {
ENode *parent_enode = (ENode*)parent->data;
parent_enode->visible_descendents += enode->visible_descendents;
}
}
else {
int i;
GNode *parent;
if (e_tree_model_node_is_visible (etm, node)) {
for (i = 0; i < enode->visible_descendents; i ++) {
etm->row_array = g_array_remove_index (etm->row_array, row);
e_table_model_row_deleted (E_TABLE_MODEL (etm), row);
}
}
/* now iterate back up the tree, subtracting from our
ancestors' visible descendents */
for (parent = node->parent; parent; parent = parent->parent) {
ENode *parent_enode = (ENode*)parent->data;
parent_enode->visible_descendents -= enode->visible_descendents;
}
enode->visible_descendents = 0;
e_tree_model_node_collapsed (etm, node);
}
}
/* fairly naive implementation */
static void
etree_set_expanded_recurse (ETreeModel *etm, ETreePath* node, gboolean expanded)
{
ETreePath **paths;
guint num_children;
int i;
e_tree_model_node_set_expanded (etm, node, expanded);
num_children = e_tree_model_node_get_children (etm, node, &paths);
if (num_children) {
for (i = 0; i < num_children; i ++) {
e_tree_model_node_set_expanded_recurse (etm, paths[i], expanded);
}
g_free (paths);
}
}
static ETreePath *
etree_node_at_row (ETreeModel *etree, int row)
{
g_return_val_if_fail (row < etree->row_array->len, NULL);
return g_array_index (etree->row_array, GNode*, row);
}
/* ETable analogs */
static void*
etree_value_at (ETreeModel *etm, ETreePath* node, int col)
{
/* shouldn't be called */
g_assert (0);
return NULL;
}
static GdkPixbuf*
etree_icon_at (ETreeModel *etm, ETreePath* node)
{
/* shouldn't be called */
g_assert (0);
return NULL;
}
static void
etree_set_value_at (ETreeModel *etm, ETreePath* node, int col, const void *val)
{
/* shouldn't be called */
g_assert (0);
}
static gboolean
etree_is_editable (ETreeModel *etm, ETreePath* node, int col)
{
/* shouldn't be called */
g_assert(0);
return FALSE;
}
/* ETable virtual functions we map */
static int
etable_row_count (ETableModel *etm)
{
ETreeModel *tree = E_TREE_MODEL (etm);
return tree->row_array->len;
}
static void *
etable_value_at (ETableModel *etm, int col, int row)
{
ETreeModel *etree = E_TREE_MODEL(etm);
ETreeModelClass *et_class = ETM_CLASS(etm);
ETreePath* node = e_tree_model_node_at_row (etree, row);
g_return_val_if_fail (node, NULL);
if (col == -1)
return node;
else if (col == -2)
return etm;
else
return et_class->value_at (etree, node, col);
}
static void
etable_set_value_at (ETableModel *etm, int col, int row, const void *val)
{
ETreeModel *etree = E_TREE_MODEL(etm);
ETreeModelClass *et_class = ETM_CLASS(etm);
ETreePath* node = e_tree_model_node_at_row (etree, row);
g_return_if_fail (node);
et_class->set_value_at (etree, node, col, val);
}
static gboolean
etable_is_cell_editable (ETableModel *etm, int col, int row)
{
ETreeModel *etree = E_TREE_MODEL(etm);
ETreeModelClass *et_class = ETM_CLASS(etm);
ETreePath* node = e_tree_model_node_at_row (etree, row);
g_return_val_if_fail (node, FALSE);
return et_class->is_editable (etree, node, col);
}
static void
build_sort_group(GString *out, ETreePath *node)
{
if (node->parent) {
build_sort_group(out, node->parent);
}
g_string_sprintfa(out, "/%p", node);
}
static const char *
etable_row_sort_group(ETableModel *etm, int row)
{
ETreeModel *etree = E_TREE_MODEL(etm);
ETreePath* node = e_tree_model_node_at_row (etree, row);
g_return_val_if_fail (node, "");
g_string_truncate(etree->sort_group, 0);
if (node)
build_sort_group(etree->sort_group, node);
return etree->sort_group->str;
}
static gboolean
etable_has_sort_group(ETableModel *etm)
{
/* could optimise for the flat &/or unexpanded tree case */
return TRUE;
}
static void
e_tree_model_class_init (GtkObjectClass *klass)
{
ETableModelClass *table_class = (ETableModelClass *) klass;
ETreeModelClass *tree_class = (ETreeModelClass *) klass;
e_tree_model_parent_class = gtk_type_class (PARENT_TYPE);
klass->destroy = etree_destroy;
e_tree_model_signals [NODE_CHANGED] =
gtk_signal_new ("node_changed",
GTK_RUN_LAST,
klass->type,
GTK_SIGNAL_OFFSET (ETreeModelClass, node_changed),
gtk_marshal_NONE__POINTER,
GTK_TYPE_NONE, 1, GTK_TYPE_POINTER);
e_tree_model_signals [NODE_INSERTED] =
gtk_signal_new ("node_inserted",
GTK_RUN_LAST,
klass->type,
GTK_SIGNAL_OFFSET (ETreeModelClass, node_inserted),
gtk_marshal_NONE__POINTER_POINTER,
GTK_TYPE_NONE, 2, GTK_TYPE_POINTER, GTK_TYPE_POINTER);
e_tree_model_signals [NODE_REMOVED] =
gtk_signal_new ("node_removed",
GTK_RUN_LAST,
klass->type,
GTK_SIGNAL_OFFSET (ETreeModelClass, node_removed),
gtk_marshal_NONE__POINTER_POINTER,
GTK_TYPE_NONE, 2, GTK_TYPE_POINTER, GTK_TYPE_POINTER);
e_tree_model_signals [NODE_COLLAPSED] =
gtk_signal_new ("node_collapsed",
GTK_RUN_LAST,
klass->type,
GTK_SIGNAL_OFFSET (ETreeModelClass, node_collapsed),
gtk_marshal_NONE__POINTER,
GTK_TYPE_NONE, 1, GTK_TYPE_POINTER);
e_tree_model_signals [NODE_EXPANDED] =
gtk_signal_new ("node_expanded",
GTK_RUN_LAST,
klass->type,
GTK_SIGNAL_OFFSET (ETreeModelClass, node_expanded),
gtk_marshal_NONE__POINTER_POINTER,
GTK_TYPE_NONE, 2, GTK_TYPE_POINTER, GTK_TYPE_POINTER);
gtk_object_class_add_signals (klass, e_tree_model_signals, LAST_SIGNAL);
table_class->row_count = etable_row_count;
table_class->value_at = etable_value_at;
table_class->set_value_at = etable_set_value_at;
table_class->is_cell_editable = etable_is_cell_editable;
#if 0
/* XX need to pass these through */
table_class->duplicate_value = etable_duplicate_value;
table_class->free_value = etable_free_value;
table_class->initialize_value = etable_initialize_value;
table_class->value_is_empty = etable_value_is_empty;
table_class->value_to_string = etable_value_to_string;
table_class->thaw = etable_thaw;
#endif
table_class->row_sort_group = etable_row_sort_group;
table_class->has_sort_group = etable_has_sort_group;
tree_class->get_root = etree_get_root;
tree_class->get_prev = etree_get_prev;
tree_class->get_next = etree_get_next;
tree_class->get_parent = etree_get_parent;
tree_class->value_at = etree_value_at;
tree_class->icon_at = etree_icon_at;
tree_class->set_value_at = etree_set_value_at;
tree_class->is_editable = etree_is_editable;
tree_class->get_children = etree_get_children;
tree_class->is_expanded = etree_is_expanded;
tree_class->is_visible = etree_is_visible;
tree_class->set_expanded = etree_set_expanded;
tree_class->set_expanded_recurse = etree_set_expanded_recurse;
tree_class->node_at_row = etree_node_at_row;
}
static void
e_tree_init (GtkObject *object)
{
ETreeModel *etree = (ETreeModel *)object;
etree->sort_group = g_string_new("");
}
E_MAKE_TYPE(e_tree_model, "ETreeModel", ETreeModel, e_tree_model_class_init, e_tree_init, PARENT_TYPE)
/* signals */
void
e_tree_model_node_changed (ETreeModel *tree_model, ETreePath *node)
{
int row;
g_return_if_fail (tree_model != NULL);
g_return_if_fail (E_IS_TREE_MODEL (tree_model));
row = e_tree_model_row_of_node (tree_model, node);
if (row != -1)
e_table_model_row_changed (E_TABLE_MODEL (tree_model), row);
gtk_signal_emit (GTK_OBJECT (tree_model),
e_tree_model_signals [NODE_CHANGED]);
}
void
e_tree_model_node_inserted (ETreeModel *tree_model,
ETreePath *parent_node,
ETreePath *inserted_node)
{
int row;
g_return_if_fail (tree_model != NULL);
g_return_if_fail (E_IS_TREE_MODEL (tree_model));
row = e_tree_model_row_of_node (tree_model, inserted_node);
if (row != -1)
e_table_model_row_inserted (E_TABLE_MODEL (tree_model), row);
gtk_signal_emit (GTK_OBJECT (tree_model),
e_tree_model_signals [NODE_INSERTED],
parent_node, inserted_node);
}
void
e_tree_model_node_removed (ETreeModel *tree_model, ETreePath *parent_node, ETreePath *removed_node)
{
int row;
g_return_if_fail (tree_model != NULL);
g_return_if_fail (E_IS_TREE_MODEL (tree_model));
row = e_tree_model_row_of_node (tree_model, removed_node);
if (row != -1)
e_table_model_row_inserted (E_TABLE_MODEL (tree_model), row);
gtk_signal_emit (GTK_OBJECT (tree_model),
e_tree_model_signals [NODE_REMOVED],
parent_node, removed_node);
}
void
e_tree_model_node_collapsed (ETreeModel *tree_model, ETreePath *node)
{
g_return_if_fail (tree_model != NULL);
g_return_if_fail (E_IS_TREE_MODEL (tree_model));
gtk_signal_emit (GTK_OBJECT (tree_model),
e_tree_model_signals [NODE_COLLAPSED],
node);
}
void
e_tree_model_node_expanded (ETreeModel *tree_model, ETreePath *node, gboolean *allow_expand)
{
g_return_if_fail (tree_model != NULL);
g_return_if_fail (E_IS_TREE_MODEL (tree_model));
gtk_signal_emit (GTK_OBJECT (tree_model),
e_tree_model_signals [NODE_EXPANDED],
node, allow_expand);
}
void
e_tree_model_construct (ETreeModel *etree)
{
etree->root = NULL;
etree->root_visible = TRUE;
etree->row_array = g_array_new (FALSE, FALSE, sizeof(GNode*));
etree->expanded_state = g_hash_table_new (g_str_hash, g_str_equal);
}
ETreeModel *
e_tree_model_new ()
{
ETreeModel *et;
et = gtk_type_new (e_tree_model_get_type ());
return et;
}
ETreePath *
e_tree_model_get_root (ETreeModel *etree)
{
return ETM_CLASS(etree)->get_root(etree);
}
ETreePath *
e_tree_model_node_at_row (ETreeModel *etree, int row)
{
return ETM_CLASS(etree)->node_at_row (etree, row);
}
GdkPixbuf *
e_tree_model_icon_of_node (ETreeModel *etree, ETreePath *path)
{
return ETM_CLASS(etree)->icon_at (etree, path);
}
int
e_tree_model_row_of_node (ETreeModel *etree, ETreePath *node)
{
int i;
for (i = 0; i < etree->row_array->len; i ++)
if (g_array_index (etree->row_array, GNode*, i) == node)
return i;
return -1;
}
void
e_tree_model_root_node_set_visible (ETreeModel *etm, gboolean visible)
{
if (visible != etm->root_visible) {
etm->root_visible = visible;
if (etm->root) {
if (visible) {
etm->row_array = g_array_insert_val (etm->row_array, 0, etm->root);
}
else {
ETreePath *root_path = e_tree_model_get_root (etm);
e_tree_model_node_set_expanded (etm, root_path, TRUE);
etm->row_array = g_array_remove_index (etm->row_array, 0);
}
e_table_model_changed (E_TABLE_MODEL (etm));
}
}
}
gboolean
e_tree_model_root_node_is_visible (ETreeModel *etm)
{
return etm->root_visible;
}
ETreePath *
e_tree_model_node_get_next (ETreeModel *etree, ETreePath *node)
{
return ETM_CLASS(etree)->get_next(etree, node);
}
ETreePath *
e_tree_model_node_get_prev (ETreeModel *etree, ETreePath *node)
{
return ETM_CLASS(etree)->get_prev(etree, node);
}
guint
e_tree_model_node_depth (ETreeModel *etree, ETreePath *path)
{
return g_node_depth (path) - 1;
}
ETreePath *
e_tree_model_node_get_parent (ETreeModel *etree, ETreePath *path)
{
return ETM_CLASS(etree)->get_parent(etree, path);
}
gboolean
e_tree_model_node_is_root (ETreeModel *etree, ETreePath *path)
{
return (e_tree_model_node_depth (etree, path) == 0);
}
gboolean
e_tree_model_node_is_expandable (ETreeModel *etree, ETreePath *path)
{
return (e_tree_model_node_get_children (etree, path, NULL) > 0);
}
gboolean
e_tree_model_node_is_expanded (ETreeModel *etree, ETreePath *path)
{
return ETM_CLASS(etree)->is_expanded (etree, path);
}
gboolean
e_tree_model_node_is_visible (ETreeModel *etree, ETreePath *path)
{
return ETM_CLASS(etree)->is_visible (etree, path);
}
void
e_tree_model_node_set_expanded (ETreeModel *etree, ETreePath *path, gboolean expanded)
{
ETM_CLASS(etree)->set_expanded (etree, path, expanded);
}
void
e_tree_model_node_set_expanded_recurse (ETreeModel *etree, ETreePath *path, gboolean expanded)
{
ETM_CLASS(etree)->set_expanded_recurse (etree, path, expanded);
}
guint
e_tree_model_node_get_children (ETreeModel *etree, ETreePath *path, ETreePath ***paths)
{
return ETM_CLASS(etree)->get_children (etree, path, paths);
}
guint
e_tree_model_node_num_visible_descendents (ETreeModel *etm, ETreePath *node)
{
ENode *enode = (ENode*)node->data;
return enode->visible_descendents;
}
gpointer
e_tree_model_node_get_data (ETreeModel *etm, ETreePath *node)
{
ENode *enode;
g_return_val_if_fail (node && node->data, NULL);
enode = (ENode*)node->data;
return enode->node_data;
}
void
e_tree_model_node_set_data (ETreeModel *etm, ETreePath *node, gpointer node_data)
{
ENode *enode;
g_return_if_fail (node && node->data);
enode = (ENode*)node->data;
enode->node_data = node_data;
}
ETreePath*
e_tree_model_node_insert (ETreeModel *tree_model,
ETreePath *parent_path,
int position,
gpointer node_data)
{
ENode *node;
ETreePath *new_path;
g_return_val_if_fail (parent_path != NULL || tree_model->root == NULL, NULL);
node = g_new0 (ENode, 1);
node->expanded = FALSE;
node->node_data = node_data;
if (parent_path != NULL) {
new_path = g_node_new (node);
g_node_insert (parent_path, position, new_path);
if (e_tree_model_node_is_visible (tree_model, new_path)) {
int parent_row;
GNode *n;
/* we need to iterate back up to the root, incrementing the number of visible
descendents */
for (n = parent_path; n; n = n->parent) {
ENode *parent_enode = (ENode*)n->data;
parent_enode->visible_descendents ++;
}
/* finally, insert a row into the table */
if (position == -1)
position = e_tree_model_node_num_visible_descendents (tree_model, parent_path) - 1;
parent_row = e_tree_model_row_of_node (tree_model, parent_path);
tree_model->row_array = g_array_insert_val (tree_model->row_array,
parent_row + position + 1, new_path);
e_table_model_row_inserted (E_TABLE_MODEL(tree_model), parent_row + position + 1);
}
}
else {
tree_model->root = g_node_new (node);
if (tree_model->root_visible) {
tree_model->row_array = g_array_insert_val (tree_model->row_array, 0, tree_model->root);
e_table_model_row_inserted (E_TABLE_MODEL (tree_model), 0);
}
else {
/* need to mark the new node as expanded or
we'll never see it's children */
node->expanded = TRUE;
}
new_path = tree_model->root;
}
return new_path;
}
ETreePath*
e_tree_model_node_insert_id (ETreeModel *tree_model,
ETreePath *parent_path,
int position,
gpointer node_data,
const char *save_id)
{
ETreePath *path = e_tree_model_node_insert (tree_model, parent_path, position, node_data);
e_tree_model_node_set_save_id (tree_model, path, save_id);
return path;
}
ETreePath *
e_tree_model_node_insert_before (ETreeModel *etree,
ETreePath *parent,
ETreePath *sibling,
gpointer node_data)
{
return e_tree_model_node_insert (etree, parent,
g_node_child_position (parent, sibling),
node_data);
}
static void
child_remove (GNode *node, gpointer etree)
{
e_tree_model_node_remove (etree, node);
}
gpointer
e_tree_model_node_remove (ETreeModel *etree, ETreePath *path)
{
GNode *parent = path->parent;
ENode *enode = (ENode*)path->data;
gpointer ret = enode->node_data;
/* remove children */
g_node_children_foreach (path, G_TRAVERSE_ALL, child_remove, etree);
/* clean up the display */
if (parent) {
if (e_tree_model_node_is_visible (etree, path)) {
int row = e_tree_model_row_of_node (etree, path);
e_table_model_row_deleted (E_TABLE_MODEL (etree), row);
etree->row_array = g_array_remove_index (etree->row_array, row);
/* we need to iterate back up to the root, incrementing the number of visible
descendents */
for (; parent; parent = parent->parent) {
ENode *parent_enode = (ENode*)parent->data;
parent_enode->visible_descendents --;
}
}
}
else if (path == etree->root) {
etree->root = NULL;
if (etree->root_visible) {
etree->row_array = g_array_remove_index (etree->row_array, 0);
e_table_model_row_deleted (E_TABLE_MODEL (etree), 0);
}
}
else {
/* XXX invalid path */
return NULL;
}
/* now free up the storage from that path */
g_node_destroy (path);
g_free (enode);
return ret;
}
static void
add_visible_descendents_to_array (ETreeModel *etm, GNode *gnode, int *row, int *count)
{
GNode *child;
ENode *enode;
/* add a row for this node */
etm->row_array = g_array_insert_val (etm->row_array, (*row), gnode);
e_table_model_row_inserted (E_TABLE_MODEL (etm), (*row)++);
(*count) ++;
/* then loop over its children, calling this routine for each
of them */
enode = (ENode*)gnode->data;
if (enode->expanded) {
for (child = g_node_first_child (gnode); child;
child = g_node_next_sibling (child)) {
add_visible_descendents_to_array (etm, child, row, count);
}
}
}
static void
save_expanded_state_func (char *key, gboolean expanded, gpointer user_data)
{
if (expanded) {
xmlNode *root = (xmlNode*)user_data;
xmlNode *node_root = xmlNewNode (NULL, (xmlChar*) "node");
xmlAddChild (root, node_root);
xmlNewChild (node_root, NULL, (xmlChar *) "id", (xmlChar*) key);
}
}
gboolean
e_tree_model_save_expanded_state (ETreeModel *etm, const char *filename)
{
xmlDoc *doc;
xmlNode *root;
int fd, rv;
xmlChar *buf;
int buf_size;
gboolean save_expanded = etm->num_expanded_to_save < etm->num_collapsed_to_save;
g_print ("saving expanded state: %d expanded, %d collapsed, should save %s\n",
etm->num_expanded_to_save, etm->num_collapsed_to_save,
save_expanded ? "expanded" : "collapsed");
doc = xmlNewDoc ((xmlChar*) "1.0");
root = xmlNewDocNode (doc, NULL, (xmlChar *) save_expanded ? "expanded_state" : "collapsed_state", NULL);
xmlDocSetRootElement (doc, root);
g_hash_table_foreach (etm->expanded_state, (GHFunc)save_expanded_state_func, root);
fd = open (filename, O_CREAT | O_TRUNC | O_WRONLY, 0777);
xmlDocDumpMemory (doc, &buf, &buf_size);
if (buf == NULL) {
g_error ("Failed to write %s: xmlBufferCreate() == NULL", filename);
return FALSE;
}
rv = write (fd, buf, buf_size);
xmlFree (buf);
close (fd);
if (0 > rv) {
g_error ("Failed to write new %s: %d\n", filename, errno);
unlink (filename);
return FALSE;
}
return TRUE;
}
static char *
get_string_value (xmlNode *node,
const char *name)
{
xmlNode *p;
xmlChar *xml_string;
char *retval;
p = e_xml_get_child_by_name (node, (xmlChar *) name);
if (p == NULL)
return NULL;
p = e_xml_get_child_by_name (p, (xmlChar *) "text");
if (p == NULL) /* there's no text between the tags, return the empty string */
return g_strdup("");
xml_string = xmlNodeListGetString (node->doc, p, 1);
retval = g_strdup ((char *) xml_string);
xmlFree (xml_string);
return retval;
}
gboolean
e_tree_model_load_expanded_state (ETreeModel *etm, const char *filename)
{
xmlDoc *doc;
xmlNode *root;
xmlNode *child;
doc = xmlParseFile (filename);
if (!doc)
return FALSE;
root = xmlDocGetRootElement (doc);
if (root == NULL || strcmp (root->name, "expanded_state") != 0) {
xmlFreeDoc (doc);
return FALSE;
}
for (child = root->childs; child; child = child->next) {
char *id;
if (strcmp (child->name, "node")) {
g_warning ("unknown node '%s' in %s", child->name, filename);
continue;
}
id = get_string_value (child, "id");
g_hash_table_insert (etm->expanded_state, id, (gpointer)TRUE);
}
xmlFreeDoc (doc);
return TRUE;
}
void
e_tree_model_node_set_save_id (ETreeModel *etm, ETreePath *node, const char *id)
{
ENode *enode;
char *key;
gboolean expanded_state;
g_return_if_fail (E_TREE_MODEL (etm));
g_return_if_fail (node && node->data);
enode = (ENode*)node->data;
if (g_hash_table_lookup_extended (etm->expanded_state,
id, (gpointer*)&key, (gpointer*)&expanded_state)) {
e_tree_model_node_set_expanded (etm, node,
expanded_state);
if (expanded_state)
etm->num_expanded_to_save ++;
else
etm->num_collapsed_to_save ++;
/* important that this comes after the e_tree_model_node_set_expanded */
enode->save_id = key;
}
else {
enode->save_id = g_strdup (id);
g_hash_table_insert (etm->expanded_state, enode->save_id, (gpointer)enode->expanded);
if (enode->expanded)
etm->num_expanded_to_save ++;
else
etm->num_collapsed_to_save ++;
}
}
void
e_tree_model_node_set_compare_function (ETreeModel *tree_model,
ETreePath *node,
ETreePathCompareFunc compare)
{
ENode *enode;
gboolean need_sort;
g_return_if_fail (E_TREE_MODEL (tree_model));
g_return_if_fail (node && node->data);
enode = (ENode*)node->data;
need_sort = (compare != enode->compare);
enode->compare = compare;
if (need_sort)
e_tree_model_node_sort (tree_model, node);
}
typedef struct {
ETreeModel *model;
ETreePath *path;
ETreePathCompareFunc compare;
gboolean was_expanded;
} ETreeSortInfo;
static gint
e_tree_model_node_compare (ETreeSortInfo *info1, ETreeSortInfo *info2)
{
return info1->compare (info1->model, info1->path, info2->path);
}
void
e_tree_model_node_sort (ETreeModel *tree_model,
ETreePath *node)
{
int num_nodes = g_node_n_children (node);
ETreeSortInfo *sort_info;
int i;
int child_index;
gboolean node_expanded = e_tree_model_node_is_expanded (tree_model, node);
ENode *enode;
g_return_if_fail (E_TREE_MODEL (tree_model));
g_return_if_fail (node && node->data);
enode = (ENode*)node->data;
g_return_if_fail (enode->compare);
if (num_nodes == 0)
return;
sort_info = g_new (ETreeSortInfo, num_nodes);
child_index = e_tree_model_row_of_node (tree_model, node) + 1;
for (i = 0; i < num_nodes; i ++) {
sort_info[i].path = g_node_first_child(node);
sort_info[i].was_expanded = e_tree_model_node_is_expanded (tree_model, sort_info[i].path);
sort_info[i].model = tree_model;
sort_info[i].compare = enode->compare;
e_tree_model_node_set_expanded(tree_model, sort_info[i].path, FALSE);
if (node_expanded)
tree_model->row_array = g_array_remove_index (tree_model->row_array, child_index);
g_node_unlink (sort_info[i].path);
}
qsort (sort_info, num_nodes, sizeof(ETreeSortInfo), (GCompareFunc)e_tree_model_node_compare);
for (i = num_nodes - 1; i >= 0; i --) {
g_node_prepend (node, sort_info[i].path);
if (node_expanded)
tree_model->row_array = g_array_insert_val (tree_model->row_array, child_index, sort_info[i].path);
e_tree_model_node_set_expanded (tree_model, sort_info[i].path, sort_info[i].was_expanded);
}
g_free (sort_info);
e_table_model_changed (E_TABLE_MODEL (tree_model));
}