/*
 *
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.
 */

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#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>
#include <X11/Xaw/Viewport.h>
#include <X11/Xaw/Tree.h>

#include "editresP.h"

static void AddNodeToActiveList ( WNode * node );
static void RemoveNodeFromActiveList ( WNode * node );
static Boolean IsActiveNode ( WNode * node );
static void AddNode ( WNode ** top_node, WidgetTreeInfo * info,
		      TreeInfo * tree_info );
static void FillNode ( WidgetTreeInfo * info, WNode * node,
		       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
BuildVisualTree(Widget tree_parent, Event *event)
{
    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);

    snprintf(msg, sizeof(msg), res_labels[11], top->name, top->class);
    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
AddTreeNode(Widget tree, WNode *top)
{
    Cardinal i;
    Arg args[1];
    Cardinal num_args = 0;
    char msg[BUFSIZ];

    if (top->parent != NULL) {
	if (top->parent->widget == NULL) {
	    snprintf(msg, sizeof(msg), res_labels[28],
                     top->name, top->parent->name, "not been created yet");
	    SetMessage(global_screen_data.info_label, msg);
	}
	XtSetArg(args[num_args], (String)XtNtreeParent, top->parent->widget);
	num_args++;
    }

    top->widget = XtCreateManagedWidget(top->name, toggleWidgetClass, tree,
					args, num_args);

    if (XSaveContext(XtDisplay(top->widget), (Window) top->widget,
		     NODE_INFO, (XPointer) top) != 0) {
	snprintf(msg, sizeof(msg), res_labels[29], top->name);
	SetMessage(global_screen_data.info_label, msg);
    }

    XtAddCallback(top->widget, XtNcallback, TreeToggle, (XtPointer) top);

    for (i = 0; i < top->num_children; i++)
	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
TreeToggle(Widget w, XtPointer node_ptr, XtPointer state_ptr)
{
    Boolean state = (Boolean)(long) state_ptr;
    WNode * node = (WNode *) node_ptr;

    if (state)
	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
AddNodeToActiveList(WNode *node)
{
    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,
						sizeof(WNode *) *
						     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
RemoveNodeFromActiveList(WNode *node)
{
    TreeInfo * info = node->tree_info;
    Boolean found_node = FALSE;
    Cardinal i;

    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];
	else if (info->active_nodes[i] == node)
	    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
IsActiveNode(WNode *node)
{
    TreeInfo * info = node->tree_info;
    Cardinal i;

    for (i = 0; i < info->num_nodes; i++)
	if (info->active_nodes[i] == node)
	    return(TRUE);

    return(FALSE);
}

/*	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.
 */

TreeInfo *
CreateTree(Event *event)
{
    SendWidgetTreeEvent * send_event = (SendWidgetTreeEvent *) event;
    unsigned short i;

    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;

    for ( i = 0; i < send_event->num_entries; i++)
	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
PrintNodes(WNode *top)
{
    Cardinal i;

    if (top->parent == NULL)
	printf("Top of Tree, Name: %10s, ID: %10ld, Class: %10s\n",
	       top->name, top->id, top->class);
    else
	printf("Parent %10s, Name: %10s, ID: %10ld, Class: %10s\n",
	       top->parent->name, top->name, top->id, top->class);

    for (i = 0; i < top->num_children; i++)
	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
_TreeRelabel(TreeInfo *tree_info, LabelTypes type)
{
    WNode * top;

    if (tree_info == NULL) {
	SetMessage(global_screen_data.info_label,
		   res_labels[17]);
	return;
    }

    top = tree_info->top_node;

    PrepareToLayoutTree(tree_info->tree_widget);
    _TreeRelabelNode(top, type, TRUE);
    LayoutTree(tree_info->tree_widget);
}

/*	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;
    Cardinal i;

    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 descendants.
 *	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)
{
    Cardinal i;
    Arg args[1];
    Boolean state;

    switch(type) {
    case SelectAll:
	state = TRUE;
	break;
    case SelectNone:
	state = FALSE;
	break;
    case SelectInvert:
	XtSetArg(args[0], (String)XtNstate, &state);
	XtGetValues(node->widget, args, ONE);

	state = !state;
	break;
    default:
	SetMessage(global_screen_data.info_label,
		   res_labels[16]);
	return;
    }

    XtSetArg(args[0], (String)XtNstate, state);
    XtSetValues(node->widget, args, ONE);
    TreeToggle(node->widget, (XtPointer) node, (XtPointer)(long) state);

    if (!recurse)
	return;

    for (i = 0; i < node->num_children; i++)
	_TreeSelectNode(node->children[i], type, recurse);
}

/*	Function Name: _TreeRelabelNodes
 *	Description: Modifies the node and all its descendants 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)
{
    Cardinal i;
    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:
	snprintf(buf, sizeof(buf), "id: 0x%lx", node->id);
	XtSetArg(args[0], XtNlabel, buf);
	break;
    case WindowLabel:
	if (node->window == EDITRES_IS_UNREALIZED)
	    strcpy(buf, "unrealized widget");
	else if (node->window == EDITRES_IS_OBJECT)
	    strcpy(buf, "non windowed object");
	else
	    snprintf(buf, sizeof(buf), "win: 0x%lx", node->window);

	XtSetArg(args[0], (String)XtNlabel, buf);
	break;
    case ToggleLabel:
	XtSetArg(args[0], (String)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;

    for (i = 0; i < node->num_children; i++)
	_TreeRelabelNode(node->children[i], type, recurse);
}

/*	Function Name: _TreeActivateNode
 *	Description: Activates relatives of the node specified, as specified
 *                   by type.
 *	Arguments: node - node to opererate on.
 *                 type - type of activate to invode.
 *	Returns: none.
 */

void
_TreeActivateNode(WNode* node, SelectTypes type)
{
    Arg args[1];
    Cardinal i;

    XtSetArg(args[0], (String)XtNstate, TRUE);

    if ((type == SelectParent) || (type == SelectAncestors)) {
	node = node->parent;
	if (node == NULL)
	    return;

	XtSetValues(node->widget, args, ONE);
	AddNodeToActiveList(node);

	if (type == SelectAncestors)
	    _TreeActivateNode(node, type);
    }
    else if ((type == SelectChildren) || (type == SelectDescendants))
	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,
		   res_labels[33]);
}

/************************************************************
 *
 * Non - Exported Functions.
 *
 ************************************************************/


/*	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
AddNode(WNode **top_node, WidgetTreeInfo *info, TreeInfo *tree_info)
{
    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);

	    if (early_break)
		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
FillNode(WidgetTreeInfo *info, WNode *node, TreeInfo *tree_info)
{
    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
AddChild(WNode *parent, WNode *child)
{
    if (parent->num_children >= parent->alloc_children) {
	parent->alloc_children += NUM_INC;
	parent->children = (WNode **) XtRealloc((char *)parent->children,
				     sizeof(WNode *) * parent->alloc_children);
    }

    parent->children[parent->num_children] = child;
    (parent->num_children)++;

    child->parent = parent;
}

/************************************************************
 *
 *  Functions that operate of the current tree.
 *
 ************************************************************/

/*	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.
 */

static WNode **
CopyActiveNodes(TreeInfo *tree_info)
{
    WNode ** list;
    Cardinal i;

    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
SetAndCenterTreeNode(WNode *node)
{
    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.
     */

    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++;
    XtSetValues(node->tree_info->tree_widget, args, num_args);
}

/*	Function Name: PerformTreeToFileDump
 *	Description: Dumps the contents of the current widget tree to
 *                   the file specified.
 *	Arguments: node - node to dump.
 *                 num_tabs - number of spaces to indent.
 *                 fp - pointer to the file to write to.
 *	Returns: none.
 */

void
PerformTreeToFileDump(WNode *node, Cardinal num_tabs, FILE *fp)
{
    Cardinal i;

    for (i = 0; i < num_tabs; i++)
	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);
}

