xenocara/app/editres/wtree.c

690 lines
18 KiB
C
Raw Normal View History

2006-11-25 13:07:29 -07:00
/*
*
Copyright 1989, 1998 The Open Group
Permission to use, copy, modify, distribute, and sell this software and its
documentation for any purpose is hereby granted without fee, provided that
the above copyright notice appear in all copies and that both that
copyright notice and this permission notice appear in supporting
documentation.
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
OPEN GROUP BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Except as contained in this notice, the name of The Open Group shall not be
used in advertising or otherwise to promote the sale, use or other dealings
in this Software without prior written authorization from The Open Group.
*/
#include <stdio.h>
#include <X11/Intrinsic.h>
#include <X11/Xutil.h>
#include <X11/StringDefs.h>
#include <X11/Xaw/Cardinals.h>
#include <X11/Xaw/Toggle.h>
2018-05-21 09:15:36 -06:00
#include <X11/Xaw/Viewport.h>
2006-11-25 13:07:29 -07:00
#include <X11/Xaw/Tree.h>
#include "editresP.h"
static void AddNodeToActiveList ( WNode * node );
static void RemoveNodeFromActiveList ( WNode * node );
static Boolean IsActiveNode ( WNode * node );
2018-05-21 09:15:36 -06:00
static void AddNode ( WNode ** top_node, WidgetTreeInfo * info,
2006-11-25 13:07:29 -07:00
TreeInfo * tree_info );
2018-05-21 09:15:36 -06:00
static void FillNode ( WidgetTreeInfo * info, WNode * node,
2006-11-25 13:07:29 -07:00
TreeInfo * tree_info );
static void AddChild ( WNode * parent, WNode * child );
static WNode ** CopyActiveNodes ( TreeInfo * tree_info );
/* Function Name: BuildVisualTree
* Description: Creates the Tree and shows it.
* Arguments: tree_parent - parent of the tree widget.
* event - the event that caused this action.
* Returns: none.
*/
/* ARGSUSED */
void
2010-05-31 13:33:38 -06:00
BuildVisualTree(Widget tree_parent, Event *event)
2006-11-25 13:07:29 -07:00
{
WNode * top;
char msg[BUFSIZ];
if (global_tree_info != NULL) {
XtDestroyWidget(global_tree_info->tree_widget);
XtFree((char *)global_tree_info->active_nodes);
XtFree((char *)global_tree_info);
}
global_tree_info = CreateTree(event);
top = global_tree_info->top_node;
global_tree_info->tree_widget = XtCreateWidget("tree", treeWidgetClass,
tree_parent, NULL, ZERO);
if (top == NULL) {
SetMessage(global_screen_data.info_label,
res_labels[27]);
return;
}
AddTreeNode(global_tree_info->tree_widget, top);
if (XtIsRealized(tree_parent)) /* hack around problems in Xt. */
XtRealizeWidget(global_tree_info->tree_widget);
XtManageChild(global_tree_info->tree_widget);
2018-05-21 09:15:36 -06:00
snprintf(msg, sizeof(msg), res_labels[11], top->name, top->class);
2006-11-25 13:07:29 -07:00
SetMessage(global_screen_data.info_label, msg);
}
/* Function Name: AddTreeNode
* Description: Adds all nodes below this to the Tree widget.
* Arguments: parent - parent of the tree widget.
* top - the top node of the tree.
* Returns: the tree widget.
*
* NOTE: This is a recursive function.
*/
void
2010-05-31 13:33:38 -06:00
AddTreeNode(Widget tree, WNode *top)
2006-11-25 13:07:29 -07:00
{
2018-05-21 09:15:36 -06:00
Cardinal i;
2006-11-25 13:07:29 -07:00
Arg args[1];
Cardinal num_args = 0;
char msg[BUFSIZ];
if (top->parent != NULL) {
if (top->parent->widget == NULL) {
2018-05-21 09:15:36 -06:00
snprintf(msg, sizeof(msg), res_labels[28],
top->name, top->parent->name, "not been created yet");
2006-11-25 13:07:29 -07:00
SetMessage(global_screen_data.info_label, msg);
}
XtSetArg(args[num_args], XtNtreeParent, top->parent->widget);
num_args++;
}
top->widget = XtCreateManagedWidget(top->name, toggleWidgetClass, tree,
args, num_args);
2018-05-21 09:15:36 -06:00
if (XSaveContext(XtDisplay(top->widget), (Window) top->widget,
2006-11-25 13:07:29 -07:00
NODE_INFO, (XPointer) top) != 0) {
2018-05-21 09:15:36 -06:00
snprintf(msg, sizeof(msg), res_labels[29], top->name);
2006-11-25 13:07:29 -07:00
SetMessage(global_screen_data.info_label, msg);
2018-05-21 09:15:36 -06:00
}
2006-11-25 13:07:29 -07:00
XtAddCallback(top->widget, XtNcallback, TreeToggle, (XtPointer) top);
2018-05-21 09:15:36 -06:00
for (i = 0; i < top->num_children; i++)
2006-11-25 13:07:29 -07:00
AddTreeNode(tree, top->children[i]);
}
/* Function Name: TreeToggle
* Description: Called whenever a tree node is toggled.
* Arguments: w - the tree widget.
* node_ptr - pointer to this node's information.
* state_ptr - state of the toggle.
* Returns: none.
*/
/* ARGSUSED */
void
2010-05-31 13:33:38 -06:00
TreeToggle(Widget w, XtPointer node_ptr, XtPointer state_ptr)
2006-11-25 13:07:29 -07:00
{
Boolean state = (Boolean)(long) state_ptr;
WNode * node = (WNode *) node_ptr;
2018-05-21 09:15:36 -06:00
if (state)
2006-11-25 13:07:29 -07:00
AddNodeToActiveList(node);
else
RemoveNodeFromActiveList(node);
}
/* Function Name: AddNodeToActiveList
* Description: Adds this node to the list of active toggles.
* Arguments: node - node to add.
* Returns: none.
*/
static void
2010-05-31 13:33:38 -06:00
AddNodeToActiveList(WNode *node)
2006-11-25 13:07:29 -07:00
{
TreeInfo * info = node->tree_info;
if (IsActiveNode(node)) /* node already active. */
return;
if (info->num_nodes >= info->alloc_nodes) {
info->alloc_nodes += NUM_INC;
info->active_nodes =(WNode **)XtRealloc((XtPointer) info->active_nodes,
2018-05-21 09:15:36 -06:00
sizeof(WNode *) *
2006-11-25 13:07:29 -07:00
info->alloc_nodes);
}
info->active_nodes[info->num_nodes++] = node;
}
/* Function Name: RemoveNodeFromActiveList
* Description: Removes a node from the active list.
* Arguments: node - node to remove.
* Returns: none.
*/
static void
2010-05-31 13:33:38 -06:00
RemoveNodeFromActiveList(WNode *node)
2006-11-25 13:07:29 -07:00
{
TreeInfo * info = node->tree_info;
Boolean found_node = FALSE;
2018-05-21 09:15:36 -06:00
Cardinal i;
2006-11-25 13:07:29 -07:00
if (!IsActiveNode(node)) /* This node is not active. */
return;
for (i = 0; i < info->num_nodes; i++) {
if (found_node)
info->active_nodes[i - 1] = info->active_nodes[i];
2018-05-21 09:15:36 -06:00
else if (info->active_nodes[i] == node)
2006-11-25 13:07:29 -07:00
found_node = TRUE;
}
info->num_nodes--;
}
/* Function Name: IsActiveNode
* Description: returns TRUE is this node is on the active list.
* Arguments: node - node to check.
* Returns: see above.
*/
static Boolean
2010-05-31 13:33:38 -06:00
IsActiveNode(WNode *node)
2006-11-25 13:07:29 -07:00
{
TreeInfo * info = node->tree_info;
2018-05-21 09:15:36 -06:00
Cardinal i;
2006-11-25 13:07:29 -07:00
2018-05-21 09:15:36 -06:00
for (i = 0; i < info->num_nodes; i++)
2006-11-25 13:07:29 -07:00
if (info->active_nodes[i] == node)
return(TRUE);
return(FALSE);
}
2018-05-21 09:15:36 -06:00
2006-11-25 13:07:29 -07:00
/* Function Name: CreateTree
* Description: Creates a widget tree give a list of names and classes.
* Arguments: event - the information from the client.
* Returns: The tree_info about this new tree.
*/
2018-05-21 09:15:36 -06:00
2006-11-25 13:07:29 -07:00
TreeInfo *
2010-05-31 13:33:38 -06:00
CreateTree(Event *event)
2006-11-25 13:07:29 -07:00
{
SendWidgetTreeEvent * send_event = (SendWidgetTreeEvent *) event;
2018-05-21 09:15:36 -06:00
unsigned short i;
2006-11-25 13:07:29 -07:00
TreeInfo * tree_info;
tree_info = (TreeInfo *) XtMalloc( (Cardinal) sizeof(TreeInfo));
tree_info->tree_widget = NULL;
tree_info->top_node = NULL;
tree_info->active_nodes = NULL;
tree_info->num_nodes = tree_info->alloc_nodes = 0;
tree_info->flash_widgets = NULL;
tree_info->num_flash_widgets = tree_info->alloc_flash_widgets = 0;
2018-05-21 09:15:36 -06:00
for ( i = 0; i < send_event->num_entries; i++)
2006-11-25 13:07:29 -07:00
AddNode(&(tree_info->top_node), (send_event->info + i), tree_info);
return(tree_info);
}
/* Function Name: PrintNodes
* Description: Prints all nodes.
* Arguments: top - the top node.
* Returns: none.
*/
void
2010-05-31 13:33:38 -06:00
PrintNodes(WNode *top)
2006-11-25 13:07:29 -07:00
{
2018-05-21 09:15:36 -06:00
Cardinal i;
2006-11-25 13:07:29 -07:00
2018-05-21 09:15:36 -06:00
if (top->parent == NULL)
printf("Top of Tree, Name: %10s, ID: %10ld, Class: %10s\n",
2006-11-25 13:07:29 -07:00
top->name, top->id, top->class);
else
2018-05-21 09:15:36 -06:00
printf("Parent %10s, Name: %10s, ID: %10ld, Class: %10s\n",
2006-11-25 13:07:29 -07:00
top->parent->name, top->name, top->id, top->class);
2018-05-21 09:15:36 -06:00
for (i = 0; i < top->num_children; i++)
2006-11-25 13:07:29 -07:00
PrintNodes(top->children[i]);
}
/* Function Name: _TreeRelabel
* Description: Modifies the selected elements of the tree
* Arguments: tree_info - the tree we are working on.
* type - type of selection to perform
* Returns: none.
*/
void
2010-05-31 13:33:38 -06:00
_TreeRelabel(TreeInfo *tree_info, LabelTypes type)
2006-11-25 13:07:29 -07:00
{
WNode * top;
if (tree_info == NULL) {
SetMessage(global_screen_data.info_label,
res_labels[17]);
return;
}
top = tree_info->top_node;
2018-05-21 09:15:36 -06:00
PrepareToLayoutTree(tree_info->tree_widget);
2006-11-25 13:07:29 -07:00
_TreeRelabelNode(top, type, TRUE);
2018-05-21 09:15:36 -06:00
LayoutTree(tree_info->tree_widget);
2006-11-25 13:07:29 -07:00
}
/* Function Name: _TreeSelect
* Description: Activates relatives of the active nodes, as specified
* by type, or Selects all nodes as specified by type.
* Arguments: tree_info - information about the tree to work on.
* type - type of activate to invode.
* Returns: none.
*/
void
_TreeSelect(TreeInfo *tree_info, SelectTypes type)
{
WNode ** active_nodes;
Cardinal num_active_nodes;
2018-05-21 09:15:36 -06:00
Cardinal i;
2006-11-25 13:07:29 -07:00
if (tree_info == NULL) {
SetMessage(global_screen_data.info_label,
res_labels[17]);
return;
}
switch(type) {
case SelectNone:
case SelectAll:
case SelectInvert:
_TreeSelectNode(tree_info->top_node, type, TRUE);
return;
default:
break; /* otherwise continue. */
}
if (tree_info->num_nodes == 0) {
SetMessage(global_screen_data.info_label,
res_labels[18]);
return;
}
active_nodes = CopyActiveNodes(tree_info);
num_active_nodes = tree_info->num_nodes;
for (i = 0; i < num_active_nodes; i++)
_TreeActivateNode(active_nodes[i], type);
XtFree((XtPointer) active_nodes);
}
/* Function Name: _TreeSelectNode
* Description: Modifies the state of a node and all its decendants.
* Arguments: node - node to operate on.
* type - type of selection to perform.
* recurse - whether to continue on down the tree.
* Returns: none.
*/
void
_TreeSelectNode(WNode *node, SelectTypes type, Boolean recurse)
{
2018-05-21 09:15:36 -06:00
Cardinal i;
2006-11-25 13:07:29 -07:00
Arg args[1];
Boolean state;
switch(type) {
case SelectAll:
state = TRUE;
break;
case SelectNone:
state = FALSE;
break;
case SelectInvert:
XtSetArg(args[0], XtNstate, &state);
XtGetValues(node->widget, args, ONE);
2018-05-21 09:15:36 -06:00
2006-11-25 13:07:29 -07:00
state = !state;
break;
default:
SetMessage(global_screen_data.info_label,
res_labels[16]);
return;
}
XtSetArg(args[0], XtNstate, state);
XtSetValues(node->widget, args, ONE);
TreeToggle(node->widget, (XtPointer) node, (XtPointer)(long) state);
if (!recurse)
return;
2018-05-21 09:15:36 -06:00
for (i = 0; i < node->num_children; i++)
2006-11-25 13:07:29 -07:00
_TreeSelectNode(node->children[i], type, recurse);
}
/* Function Name: _TreeRelabelNodes
* Description: Modifies the node and all its decendants label.
* Arguments: node - node to operate on.
* type - type of selection to perform.
* recurse - whether to continue on down the tree.
* Returns: none.
*/
void
_TreeRelabelNode(WNode *node, LabelTypes type, Boolean recurse)
{
2018-05-21 09:15:36 -06:00
Cardinal i;
2006-11-25 13:07:29 -07:00
Arg args[1];
char buf[30];
char *label;
switch(type) {
case ClassLabel:
XtSetArg(args[0], XtNlabel, node->class);
break;
case NameLabel:
XtSetArg(args[0], XtNlabel, node->name);
break;
case IDLabel:
2018-05-21 09:15:36 -06:00
snprintf(buf, sizeof(buf), "id: 0x%lx", node->id);
2006-11-25 13:07:29 -07:00
XtSetArg(args[0], XtNlabel, buf);
break;
case WindowLabel:
2018-05-21 09:15:36 -06:00
if (node->window == EDITRES_IS_UNREALIZED)
2006-11-25 13:07:29 -07:00
strcpy(buf, "unrealized widget");
2018-05-21 09:15:36 -06:00
else if (node->window == EDITRES_IS_OBJECT)
strcpy(buf, "non windowed object");
2006-11-25 13:07:29 -07:00
else
2018-05-21 09:15:36 -06:00
snprintf(buf, sizeof(buf), "win: 0x%lx", node->window);
2006-11-25 13:07:29 -07:00
XtSetArg(args[0], XtNlabel, buf);
break;
case ToggleLabel:
XtSetArg(args[0], XtNlabel, &label);
XtGetValues(node->widget, args, ONE);
if (label && !strcmp(label, node->name))
XtSetArg(args[0], XtNlabel, node->class);
else
XtSetArg(args[0], XtNlabel, node->name);
break;
default:
SetMessage(global_screen_data.info_label,
res_labels[32]);
return;
}
XtSetValues(node->widget, args, ONE);
if (!recurse)
return;
2018-05-21 09:15:36 -06:00
for (i = 0; i < node->num_children; i++)
2006-11-25 13:07:29 -07:00
_TreeRelabelNode(node->children[i], type, recurse);
}
/* Function Name: _TreeActivateNode
* Description: Activates relatives of the node specfied, as specified
* by type.
* Arguments: node - node to opererate on.
* type - type of activate to invode.
* Returns: none.
*/
void
2010-05-31 13:33:38 -06:00
_TreeActivateNode(WNode* node, SelectTypes type)
2006-11-25 13:07:29 -07:00
{
Arg args[1];
2018-05-21 09:15:36 -06:00
Cardinal i;
2006-11-25 13:07:29 -07:00
XtSetArg(args[0], XtNstate, TRUE);
if ((type == SelectParent) || (type == SelectAncestors)) {
node = node->parent;
if (node == NULL)
return;
2018-05-21 09:15:36 -06:00
XtSetValues(node->widget, args, ONE);
2006-11-25 13:07:29 -07:00
AddNodeToActiveList(node);
if (type == SelectAncestors)
2018-05-21 09:15:36 -06:00
_TreeActivateNode(node, type);
2006-11-25 13:07:29 -07:00
}
2018-05-21 09:15:36 -06:00
else if ((type == SelectChildren) || (type == SelectDescendants))
2006-11-25 13:07:29 -07:00
for (i = 0; i < node->num_children; i++) {
AddNodeToActiveList(node->children[i]);
XtSetValues(node->children[i]->widget, args, ONE);
if (type == SelectDescendants)
_TreeActivateNode(node->children[i], type);
}
else
SetMessage(global_screen_data.info_label,
2018-05-21 09:15:36 -06:00
res_labels[33]);
2006-11-25 13:07:29 -07:00
}
/************************************************************
*
2018-05-21 09:15:36 -06:00
* Non - Exported Functions.
2006-11-25 13:07:29 -07:00
*
************************************************************/
/* Function Name: AddNode
* Description: adds a node to the widget tree.
* Arguments: top_node - a pointer to the current top node.
* info - the info from the client about the widget tree.
* tree_info - global information on this tree.
* Returns: none.
*/
static void
2010-05-31 13:33:38 -06:00
AddNode(WNode **top_node, WidgetTreeInfo *info, TreeInfo *tree_info)
2006-11-25 13:07:29 -07:00
{
WNode *node, *parent;
Boolean early_break = FALSE;
Cardinal number = info->widgets.num_widgets;
if ( (node = FindNode(*top_node, info->widgets.ids, number)) == NULL) {
node = (WNode *) XtCalloc(sizeof(WNode), ONE);
node->id = info->widgets.ids[number - 1];
FillNode(info, node, tree_info);
for ( number--; number > 0; number--, node = parent) {
parent = FindNode(*top_node, info->widgets.ids, number);
if (parent == NULL) {
parent = (WNode *) XtCalloc(sizeof(WNode), ONE);
parent->id = info->widgets.ids[number - 1];
}
else
early_break = TRUE;
AddChild(parent, node);
2018-05-21 09:15:36 -06:00
if (early_break)
2006-11-25 13:07:29 -07:00
break;
}
if (!early_break) {
if (node->parent == NULL)
*top_node = node;
else
*top_node = node->parent;
}
}
else
FillNode(info, node, tree_info);
}
/* Function Name: FillNode
* Description: Fills in everything but the node id in the node.
* Arguments: info - the info from the client.
* node - node to fill.
* tree_info - global information on this tree.
* Returns: none
*/
static void
2010-05-31 13:33:38 -06:00
FillNode(WidgetTreeInfo *info, WNode *node, TreeInfo *tree_info)
2006-11-25 13:07:29 -07:00
{
node->class = info->class;
info->class = NULL; /* keeps it from deallocating. */
node->name = info->name;
info->name = NULL;
node->window = info->window;
node->tree_info = tree_info;
}
/* Function Name: AddChild
* Description: Adds a child to an existing node.
* Arguments: parent - parent node.
* child - child node to add.
* Returns: none.
*/
static void
2010-05-31 13:33:38 -06:00
AddChild(WNode *parent, WNode *child)
2006-11-25 13:07:29 -07:00
{
if (parent->num_children >= parent->alloc_children) {
parent->alloc_children += NUM_INC;
2018-05-21 09:15:36 -06:00
parent->children = (WNode **) XtRealloc((char *)parent->children,
2006-11-25 13:07:29 -07:00
sizeof(WNode *) * parent->alloc_children);
}
parent->children[parent->num_children] = child;
(parent->num_children)++;
child->parent = parent;
}
/************************************************************
*
* Functions that operate of the current tree.
2018-05-21 09:15:36 -06:00
*
2006-11-25 13:07:29 -07:00
************************************************************/
2018-05-21 09:15:36 -06:00
2006-11-25 13:07:29 -07:00
/* Function Name: CopyActiveNodes
* Description: returns a copy of the currently selected nodes.
* Arguments: tree_info - the tree info struct.
* Returns: a copy of the selected nodes.
*/
2018-05-21 09:15:36 -06:00
static WNode **
2010-05-31 13:33:38 -06:00
CopyActiveNodes(TreeInfo *tree_info)
2006-11-25 13:07:29 -07:00
{
WNode ** list;
2018-05-21 09:15:36 -06:00
Cardinal i;
2006-11-25 13:07:29 -07:00
if ( (tree_info == NULL) || (tree_info->num_nodes == 0))
return(NULL);
list = (WNode **) XtMalloc(sizeof(WNode *) * tree_info->num_nodes);
for (i = 0; i < tree_info->num_nodes; i++)
list[i] = tree_info->active_nodes[i];
return(list);
}
/* Function Name: SetAndCenterTreeNode
* Description: Deactivates all nodes, activates the one specified, and
* and moves the tree to be centered on the current node.
* Arguments: node - node to use.
* Returns: none.
*/
void
2010-05-31 13:33:38 -06:00
SetAndCenterTreeNode(WNode *node)
2006-11-25 13:07:29 -07:00
{
Arg args[5];
Cardinal num_args;
Position node_x, node_y;
Dimension port_width, port_height;
Dimension node_width, node_height, node_bw;
_TreeSelect(node->tree_info, SelectNone); /* Unselect all nodes */
_TreeSelectNode(node, SelectAll, FALSE); /* Select this node */
/*
* Get porthole dimensions.
*/
num_args = 0;
XtSetArg(args[num_args], XtNwidth, &port_width); num_args++;
XtSetArg(args[num_args], XtNheight, &port_height); num_args++;
XtGetValues(XtParent(node->tree_info->tree_widget), args, num_args);
/*
* Get node widget dimensions.
*/
num_args = 0;
XtSetArg(args[num_args], XtNwidth, &node_width); num_args++;
XtSetArg(args[num_args], XtNheight, &node_height); num_args++;
XtSetArg(args[num_args], XtNborderWidth, &node_bw); num_args++;
XtSetArg(args[num_args], XtNx, &node_x); num_args++;
XtSetArg(args[num_args], XtNy, &node_y); num_args++;
XtGetValues(node->widget, args, num_args);
/*
* reset the node x and y location to be the new x and y location of
* the tree relative to the porthole.
*/
2018-05-21 09:15:36 -06:00
2006-11-25 13:07:29 -07:00
node_x = port_width/2 - (node_x + node_width/2 + node_bw);
node_y = port_height/2 - (node_y + node_height/2 + node_bw);
num_args = 0;
XtSetArg(args[num_args], XtNx, node_x); num_args++;
XtSetArg(args[num_args], XtNy, node_y); num_args++;
2018-05-21 09:15:36 -06:00
XtSetValues(node->tree_info->tree_widget, args, num_args);
2006-11-25 13:07:29 -07:00
}
/* Function Name: PerformTreeToFileDump
* Description: Dumps the contents of the current widget tree to
2018-05-21 09:15:36 -06:00
* the file specified.
2006-11-25 13:07:29 -07:00
* Arguments: node - node to dump.
* num_tabs - number of spaces to indent.
* fp - pointer to the file to write to.
* Returns: none.
*/
void
2018-05-21 09:15:36 -06:00
PerformTreeToFileDump(WNode *node, Cardinal num_tabs, FILE *fp)
2006-11-25 13:07:29 -07:00
{
2018-05-21 09:15:36 -06:00
Cardinal i;
2006-11-25 13:07:29 -07:00
2018-05-21 09:15:36 -06:00
for (i = 0; i < num_tabs; i++)
2006-11-25 13:07:29 -07:00
fprintf(fp, "\t");
fprintf(fp, "%s %s\n", node->class, node->name);
num_tabs++;
for (i = 0; i < node->num_children; i++)
PerformTreeToFileDump(node->children[i], num_tabs, fp);
}