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
1132 lines
26 KiB
C
1132 lines
26 KiB
C
/* -*- 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));
|
||
}
|
||
|