/* * 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 * * * Authors: * Chris Lahey * Chris Toshok * * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include "e-util/e-util.h" #include "e-util/e-xml-utils.h" #include "e-tree-model.h" #define ETM_CLASS(e) (E_TREE_MODEL_GET_CLASS(e)) #define d(x) G_DEFINE_TYPE (ETreeModel, e_tree_model, G_TYPE_OBJECT) enum { PRE_CHANGE, NO_CHANGE, NODE_CHANGED, NODE_DATA_CHANGED, NODE_COL_CHANGED, NODE_INSERTED, NODE_REMOVED, NODE_DELETED, NODE_REQUEST_COLLAPSE, REBUILT, LAST_SIGNAL }; static guint signals[LAST_SIGNAL] = {0, }; static void e_tree_model_class_init (ETreeModelClass *class) { GObjectClass *object_class = G_OBJECT_CLASS (class); signals[PRE_CHANGE] = g_signal_new ("pre_change", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (ETreeModelClass, pre_change), (GSignalAccumulator) NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); signals[NO_CHANGE] = g_signal_new ("no_change", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (ETreeModelClass, no_change), (GSignalAccumulator) NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); signals[REBUILT] = g_signal_new ("rebuilt", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (ETreeModelClass, rebuilt), (GSignalAccumulator) NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); signals[NODE_CHANGED] = g_signal_new ("node_changed", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (ETreeModelClass, node_changed), (GSignalAccumulator) NULL, NULL, g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER); signals[NODE_DATA_CHANGED] = g_signal_new ("node_data_changed", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (ETreeModelClass, node_data_changed), (GSignalAccumulator) NULL, NULL, g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER); signals[NODE_COL_CHANGED] = g_signal_new ("node_col_changed", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (ETreeModelClass, node_col_changed), (GSignalAccumulator) NULL, NULL, e_marshal_VOID__POINTER_INT, G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_INT); signals[NODE_INSERTED] = g_signal_new ("node_inserted", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (ETreeModelClass, node_inserted), (GSignalAccumulator) NULL, NULL, e_marshal_VOID__POINTER_POINTER, G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_POINTER); signals[NODE_REMOVED] = g_signal_new ("node_removed", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (ETreeModelClass, node_removed), (GSignalAccumulator) NULL, NULL, e_marshal_VOID__POINTER_POINTER_INT, G_TYPE_NONE, 3, G_TYPE_POINTER, G_TYPE_POINTER, G_TYPE_INT); signals[NODE_DELETED] = g_signal_new ("node_deleted", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (ETreeModelClass, node_deleted), (GSignalAccumulator) NULL, NULL, g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER); signals[NODE_REQUEST_COLLAPSE] = g_signal_new ("node_request_collapse", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (ETreeModelClass, node_request_collapse), (GSignalAccumulator) NULL, NULL, g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER); class->get_root = NULL; class->get_parent = NULL; class->get_first_child = NULL; class->get_last_child = NULL; class->get_next = NULL; class->get_prev = NULL; class->is_root = NULL; class->is_expandable = NULL; class->get_children = NULL; class->depth = NULL; class->icon_at = NULL; class->get_expanded_default = NULL; class->column_count = NULL; class->has_save_id = NULL; class->get_save_id = NULL; class->has_get_node_by_id = NULL; class->get_node_by_id = NULL; class->has_change_pending = NULL; class->sort_value_at = NULL; class->value_at = NULL; class->set_value_at = NULL; class->is_editable = NULL; class->duplicate_value = NULL; class->free_value = NULL; class->initialize_value = NULL; class->value_is_empty = NULL; class->value_to_string = NULL; class->pre_change = NULL; class->no_change = NULL; class->rebuilt = NULL; class->node_changed = NULL; class->node_data_changed = NULL; class->node_col_changed = NULL; class->node_inserted = NULL; class->node_removed = NULL; class->node_deleted = NULL; class->node_request_collapse = NULL; } static void e_tree_model_init (ETreeModel *tree_model) { /* nothing to do */ } /* signals */ /** * e_tree_model_node_changed: * @tree_model: * @node: * * * * Return value: **/ void e_tree_model_pre_change (ETreeModel *tree_model) { g_return_if_fail (E_IS_TREE_MODEL (tree_model)); g_signal_emit (tree_model, signals[PRE_CHANGE], 0); } /** * e_tree_model_node_changed: * @tree_model: * @node: * * * * Return value: **/ void e_tree_model_no_change (ETreeModel *tree_model) { g_return_if_fail (E_IS_TREE_MODEL (tree_model)); g_signal_emit (tree_model, signals[NO_CHANGE], 0); } /** * e_tree_model_rebuilt: * @tree_model: * @node: * * * * Return value: **/ void e_tree_model_rebuilt (ETreeModel *tree_model) { g_return_if_fail (E_IS_TREE_MODEL (tree_model)); g_signal_emit (tree_model, signals[REBUILT], 0); } /** * e_tree_model_node_changed: * @tree_model: * @node: * * * * Return value: **/ void e_tree_model_node_changed (ETreeModel *tree_model, ETreePath node) { g_return_if_fail (E_IS_TREE_MODEL (tree_model)); g_signal_emit (tree_model, signals[NODE_CHANGED], 0, node); } /** * e_tree_model_node_data_changed: * @tree_model: * @node: * * * * Return value: **/ void e_tree_model_node_data_changed (ETreeModel *tree_model, ETreePath node) { g_return_if_fail (E_IS_TREE_MODEL (tree_model)); g_signal_emit (tree_model, signals[NODE_DATA_CHANGED], 0, node); } /** * e_tree_model_node_col_changed: * @tree_model: * @node: * * * * Return value: **/ void e_tree_model_node_col_changed (ETreeModel *tree_model, ETreePath node, gint col) { g_return_if_fail (E_IS_TREE_MODEL (tree_model)); g_signal_emit (tree_model, signals[NODE_COL_CHANGED], 0, node, col); } /** * e_tree_model_node_inserted: * @tree_model: * @parent_node: * @inserted_node: * * **/ void e_tree_model_node_inserted (ETreeModel *tree_model, ETreePath parent_node, ETreePath inserted_node) { g_return_if_fail (E_IS_TREE_MODEL (tree_model)); g_signal_emit ( tree_model, signals[NODE_INSERTED], 0, parent_node, inserted_node); } /** * e_tree_model_node_removed: * @tree_model: * @parent_node: * @removed_node: * * **/ void e_tree_model_node_removed (ETreeModel *tree_model, ETreePath parent_node, ETreePath removed_node, gint old_position) { g_return_if_fail (E_IS_TREE_MODEL (tree_model)); g_signal_emit ( tree_model, signals[NODE_REMOVED], 0, parent_node, removed_node, old_position); } /** * e_tree_model_node_deleted: * @tree_model: * @deleted_node: * * **/ void e_tree_model_node_deleted (ETreeModel *tree_model, ETreePath deleted_node) { g_return_if_fail (E_IS_TREE_MODEL (tree_model)); g_signal_emit (tree_model, signals[NODE_DELETED], 0, deleted_node); } /** * e_tree_model_node_request_collapse: * @tree_model: * @collapsed_node: * * **/ void e_tree_model_node_request_collapse (ETreeModel *tree_model, ETreePath collapsed_node) { g_return_if_fail (E_IS_TREE_MODEL (tree_model)); g_signal_emit (tree_model, signals[NODE_REQUEST_COLLAPSE], 0, collapsed_node); } /** * e_tree_model_new * * XXX docs here. * * return values: a newly constructed ETreeModel. */ ETreeModel * e_tree_model_new (void) { return g_object_new (E_TYPE_TREE_MODEL, NULL); } /** * e_tree_model_get_root * @etree: the ETreeModel of which we want the root node. * * Accessor for the root node of @etree. * * return values: the ETreePath corresponding to the root node. */ ETreePath e_tree_model_get_root (ETreeModel *etree) { g_return_val_if_fail (E_IS_TREE_MODEL (etree), NULL); if (ETM_CLASS (etree)->get_root) return ETM_CLASS (etree)->get_root (etree); else return NULL; } /** * e_tree_model_node_get_parent: * @etree: * @path: * * * * Return value: **/ ETreePath e_tree_model_node_get_parent (ETreeModel *etree, ETreePath node) { g_return_val_if_fail (E_IS_TREE_MODEL (etree), NULL); if (ETM_CLASS (etree)->get_parent) return ETM_CLASS (etree)->get_parent (etree, node); else return NULL; } /** * e_tree_model_node_get_first_child: * @etree: * @node: * * * * Return value: **/ ETreePath e_tree_model_node_get_first_child (ETreeModel *etree, ETreePath node) { g_return_val_if_fail (E_IS_TREE_MODEL (etree), NULL); if (ETM_CLASS (etree)->get_first_child) return ETM_CLASS (etree)->get_first_child (etree, node); else return NULL; } /** * e_tree_model_node_get_last_child: * @etree: * @node: * * * * Return value: **/ ETreePath e_tree_model_node_get_last_child (ETreeModel *etree, ETreePath node) { g_return_val_if_fail (E_IS_TREE_MODEL (etree), NULL); if (ETM_CLASS (etree)->get_last_child) return ETM_CLASS (etree)->get_last_child (etree, node); else return NULL; } /** * e_tree_model_node_get_next: * @etree: * @node: * * * * Return value: **/ ETreePath e_tree_model_node_get_next (ETreeModel *etree, ETreePath node) { g_return_val_if_fail (E_IS_TREE_MODEL (etree), NULL); if (ETM_CLASS (etree)->get_next) return ETM_CLASS (etree)->get_next (etree, node); else return NULL; } /** * e_tree_model_node_get_prev: * @etree: * @node: * * * * Return value: **/ ETreePath e_tree_model_node_get_prev (ETreeModel *etree, ETreePath node) { g_return_val_if_fail (E_IS_TREE_MODEL (etree), NULL); if (ETM_CLASS (etree)->get_prev) return ETM_CLASS (etree)->get_prev (etree, node); else return NULL; } /** * e_tree_model_node_is_root: * @etree: * @path: * * * * Return value: **/ gboolean e_tree_model_node_is_root (ETreeModel *etree, ETreePath node) { g_return_val_if_fail (etree != NULL, FALSE); if (ETM_CLASS (etree)->is_root) return ETM_CLASS (etree)->is_root (etree, node); else return FALSE; } /** * e_tree_model_node_is_expandable: * @etree: * @path: * * * * Return value: **/ gboolean e_tree_model_node_is_expandable (ETreeModel *etree, ETreePath node) { g_return_val_if_fail (etree != NULL, FALSE); g_return_val_if_fail (node != NULL, FALSE); if (ETM_CLASS (etree)->is_expandable) return ETM_CLASS (etree)->is_expandable (etree, node); else return FALSE; } guint e_tree_model_node_get_children (ETreeModel *etree, ETreePath node, ETreePath **nodes) { g_return_val_if_fail (etree != NULL, 0); if (ETM_CLASS (etree)->get_children) return ETM_CLASS (etree)->get_children (etree, node, nodes); else return 0; } /** * e_tree_model_node_depth: * @etree: * @path: * * * * Return value: **/ guint e_tree_model_node_depth (ETreeModel *etree, ETreePath node) { g_return_val_if_fail (E_IS_TREE_MODEL (etree), 0); if (ETM_CLASS (etree)->depth) return ETM_CLASS (etree)->depth (etree, node); else return 0; } /** * e_tree_model_icon_at * @etree: The ETreeModel. * @path: The ETreePath to the node we're getting the icon of. * * XXX docs here. * * return values: the GdkPixbuf associated with this node. */ GdkPixbuf * e_tree_model_icon_at (ETreeModel *etree, ETreePath node) { g_return_val_if_fail (E_IS_TREE_MODEL (etree), NULL); if (ETM_CLASS (etree)->icon_at) return ETM_CLASS (etree)->icon_at (etree, node); else return NULL; } /** * e_tree_model_get_expanded_default * @etree: The ETreeModel. * * XXX docs here. * * return values: Whether nodes should be expanded by default. */ gboolean e_tree_model_get_expanded_default (ETreeModel *etree) { g_return_val_if_fail (E_IS_TREE_MODEL (etree), FALSE); if (ETM_CLASS (etree)->get_expanded_default) return ETM_CLASS (etree)->get_expanded_default (etree); else return FALSE; } /** * e_tree_model_column_count * @etree: The ETreeModel. * * XXX docs here. * * return values: The number of columns */ gint e_tree_model_column_count (ETreeModel *etree) { g_return_val_if_fail (E_IS_TREE_MODEL (etree), 0); if (ETM_CLASS (etree)->column_count) return ETM_CLASS (etree)->column_count (etree); else return 0; } /** * e_tree_model_has_save_id * @etree: The ETreeModel. * * XXX docs here. * * return values: Whether this tree has valid save id data. */ gboolean e_tree_model_has_save_id (ETreeModel *etree) { g_return_val_if_fail (E_IS_TREE_MODEL (etree), FALSE); if (ETM_CLASS (etree)->has_save_id) return ETM_CLASS (etree)->has_save_id (etree); else return FALSE; } /** * e_tree_model_get_save_id * @etree: The ETreeModel. * @node: The ETreePath. * * XXX docs here. * * return values: The save id for this path. */ gchar * e_tree_model_get_save_id (ETreeModel *etree, ETreePath node) { g_return_val_if_fail (E_IS_TREE_MODEL (etree), NULL); if (ETM_CLASS (etree)->get_save_id) return ETM_CLASS (etree)->get_save_id (etree, node); else return NULL; } /** * e_tree_model_has_get_node_by_id * @etree: The ETreeModel. * * XXX docs here. * * return values: Whether this tree can quickly get a node from its save id. */ gboolean e_tree_model_has_get_node_by_id (ETreeModel *etree) { g_return_val_if_fail (E_IS_TREE_MODEL (etree), FALSE); if (ETM_CLASS (etree)->has_get_node_by_id) return ETM_CLASS (etree)->has_get_node_by_id (etree); else return FALSE; } /** * e_tree_model_get_node_by_id * @etree: The ETreeModel. * @node: The ETreePath. * * get_node_by_id(get_save_id(node)) should be the original node. * Likewise if get_node_by_id is not NULL, then * get_save_id(get_node_by_id(string)) should be a copy of the * original string. * * return values: The path for this save id. */ ETreePath e_tree_model_get_node_by_id (ETreeModel *etree, const gchar *save_id) { g_return_val_if_fail (E_IS_TREE_MODEL (etree), NULL); if (ETM_CLASS (etree)->get_node_by_id) return ETM_CLASS (etree)->get_node_by_id (etree, save_id); else return NULL; } /** * e_tree_model_has_change_pending * @etree: The ETreeModel. * * XXX docs here. * * return values: Whether this tree has valid save id data. */ gboolean e_tree_model_has_change_pending (ETreeModel *etree) { g_return_val_if_fail (E_IS_TREE_MODEL (etree), FALSE); if (ETM_CLASS (etree)->has_change_pending) return ETM_CLASS (etree)->has_change_pending (etree); else return FALSE; } /** * e_tree_model_sort_value_at: * @etree: The ETreeModel. * @node: The ETreePath to the node we're getting the data from. * @col: the column to retrieve data from * * Return value: This function returns the value that is stored by the * @etree in column @col and node @node. The data returned can be a * pointer or any data value that can be stored inside a pointer. * * The data returned is typically used by an sort renderer if it wants * to proxy the data of cell value_at at a better sorting order. * * The data returned must be valid until the model sends a signal that * affect that piece of data. node_changed and node_deleted affect * all data in tha t node and all nodes under that node. * node_data_changed affects the data in that node. node_col_changed * affects the data in that node for that column. node_inserted, * node_removed, and no_change don't affect any data in this way. **/ gpointer e_tree_model_sort_value_at (ETreeModel *etree, ETreePath node, gint col) { g_return_val_if_fail (E_IS_TREE_MODEL (etree), NULL); if (ETM_CLASS (etree)->sort_value_at) return ETM_CLASS (etree)->sort_value_at (etree, node, col); else return NULL; } /** * e_tree_model_value_at: * @etree: The ETreeModel. * @node: The ETreePath to the node we're getting the data from. * @col: the column to retrieve data from * * Return value: This function returns the value that is stored by the * @etree in column @col and node @node. The data returned can be a * pointer or any data value that can be stored inside a pointer. * * The data returned is typically used by an ECell renderer. * * The data returned must be valid until the model sends a signal that * affect that piece of data. node_changed and node_deleted affect * all data in tha t node and all nodes under that node. * node_data_changed affects the data in that node. node_col_changed * affects the data in that node for that column. node_inserted, * node_removed, and no_change don't affect any data in this way. **/ gpointer e_tree_model_value_at (ETreeModel *etree, ETreePath node, gint col) { g_return_val_if_fail (E_IS_TREE_MODEL (etree), NULL); if (ETM_CLASS (etree)->value_at) return ETM_CLASS (etree)->value_at (etree, node, col); else return NULL; } void e_tree_model_set_value_at (ETreeModel *etree, ETreePath node, gint col, gconstpointer val) { g_return_if_fail (E_IS_TREE_MODEL (etree)); if (ETM_CLASS (etree)->set_value_at) ETM_CLASS (etree)->set_value_at (etree, node, col, val); } /** * e_tree_model_node_is_editable: * @etree: * @path: * * * * Return value: **/ gboolean e_tree_model_node_is_editable (ETreeModel *etree, ETreePath node, gint col) { g_return_val_if_fail (etree != NULL, FALSE); if (ETM_CLASS (etree)->is_editable) return ETM_CLASS (etree)->is_editable (etree, node, col); else return FALSE; } /** * e_tree_model_duplicate_value: * @etree: * @path: * * * * Return value: **/ gpointer e_tree_model_duplicate_value (ETreeModel *etree, gint col, gconstpointer value) { g_return_val_if_fail (etree != NULL, NULL); if (ETM_CLASS (etree)->duplicate_value) return ETM_CLASS (etree)->duplicate_value (etree, col, value); else return NULL; } /** * e_tree_model_free_value: * @etree: * @path: * * * * Return value: **/ void e_tree_model_free_value (ETreeModel *etree, gint col, gpointer value) { g_return_if_fail (etree != NULL); if (ETM_CLASS (etree)->free_value) ETM_CLASS (etree)->free_value (etree, col, value); } /** * e_tree_model_initialize_value: * @etree: * @path: * * * * Return value: **/ gpointer e_tree_model_initialize_value (ETreeModel *etree, gint col) { g_return_val_if_fail (etree != NULL, NULL); if (ETM_CLASS (etree)->initialize_value) return ETM_CLASS (etree)->initialize_value (etree, col); else return NULL; } /** * e_tree_model_value_is_empty: * @etree: * @path: * * * * Return value: **/ gboolean e_tree_model_value_is_empty (ETreeModel *etree, gint col, gconstpointer value) { g_return_val_if_fail (etree != NULL, TRUE); if (ETM_CLASS (etree)->value_is_empty) return ETM_CLASS (etree)->value_is_empty (etree, col, value); else return TRUE; } /** * e_tree_model_value_to_string: * @etree: * @path: * * * * Return value: **/ gchar * e_tree_model_value_to_string (ETreeModel *etree, gint col, gconstpointer value) { g_return_val_if_fail(etree != NULL, g_strdup("")); if (ETM_CLASS (etree)->value_to_string) return ETM_CLASS (etree)->value_to_string (etree, col, value); else return g_strdup(""); } /** * e_tree_model_node_traverse: * @model: * @path: * @func: * @data: * * **/ void e_tree_model_node_traverse (ETreeModel *model, ETreePath path, ETreePathFunc func, gpointer data) { ETreePath child; g_return_if_fail (E_IS_TREE_MODEL (model)); g_return_if_fail (path != NULL); child = e_tree_model_node_get_first_child (model, path); while (child) { ETreePath next_child; next_child = e_tree_model_node_get_next (model, child); e_tree_model_node_traverse (model, child, func, data); if (func (model, child, data)) return; child = next_child; } } /** * e_tree_model_node_traverse_preorder: * @model: * @path: * @func: * @data: * * **/ void e_tree_model_node_traverse_preorder (ETreeModel *model, ETreePath path, ETreePathFunc func, gpointer data) { ETreePath child; g_return_if_fail (E_IS_TREE_MODEL (model)); g_return_if_fail (path != NULL); child = e_tree_model_node_get_first_child (model, path); while (child) { ETreePath next_child; if (func (model, child, data)) return; next_child = e_tree_model_node_get_next (model, child); e_tree_model_node_traverse_preorder (model, child, func, data); child = next_child; } } /** * e_tree_model_node_traverse_preorder: * @model: * @path: * @func: * @data: * * **/ static ETreePath e_tree_model_node_real_traverse (ETreeModel *model, ETreePath path, ETreePath end_path, gboolean forward_direction, ETreePathFunc func, gpointer data) { ETreePath child; g_return_val_if_fail (E_IS_TREE_MODEL (model), NULL); g_return_val_if_fail (path != NULL, NULL); if (forward_direction) child = e_tree_model_node_get_first_child (model, path); else child = e_tree_model_node_get_last_child (model, path); while (child) { ETreePath result; if (forward_direction && (child == end_path || func (model, child, data))) return child; if ((result = e_tree_model_node_real_traverse ( model, child, end_path, forward_direction, func, data))) return result; if (!forward_direction && (child == end_path || func (model, child, data))) return child; if (forward_direction) child = e_tree_model_node_get_next (model, child); else child = e_tree_model_node_get_prev (model, child); } return NULL; } /** * e_tree_model_node_traverse_preorder: * @model: * @path: * @func: * @data: * * **/ ETreePath e_tree_model_node_find (ETreeModel *model, ETreePath path, ETreePath end_path, gboolean forward_direction, ETreePathFunc func, gpointer data) { ETreePath result; ETreePath next; g_return_val_if_fail (E_IS_TREE_MODEL (model), NULL); /* Just search the whole tree in this case. */ if (path == NULL) { ETreePath root; root = e_tree_model_get_root (model); if (forward_direction && (end_path == root || func (model, root, data))) return root; result = e_tree_model_node_real_traverse ( model, root, end_path, forward_direction, func, data); if (result) return result; if (!forward_direction && (end_path == root || func (model, root, data))) return root; return NULL; } while (1) { if (forward_direction) { if ((result = e_tree_model_node_real_traverse ( model, path, end_path, forward_direction, func, data))) return result; next = e_tree_model_node_get_next (model, path); } else { next = e_tree_model_node_get_prev (model, path); if (next && (result = e_tree_model_node_real_traverse ( model, next, end_path, forward_direction, func, data))) return result; } while (next == NULL) { path = e_tree_model_node_get_parent (model, path); if (path == NULL) return NULL; if (forward_direction) next = e_tree_model_node_get_next (model, path); else next = path; } if (end_path == next || func (model, next, data)) return next; path = next; } }