/*
 * Copyright 1990 by Baylor College of Medicine ALL RIGHTS RESERVED. 
 *
 * This program is subject to a license agreement between 
 * Baylor College of Medicine and MIT. Any use inconsistent with
 * said license and any use by persons other than the faculty, 
 * students and staff at MIT or any use on a computer not operated 
 * as part of the Athena Computing Environment (ACE) is expressly 
 * prohibited.
 */
/****************************************************************
 * File: Browser.c
 * Date: 05/07/91
 *
 * Description:
 *   This file contains the code for the Browser widget.
 *
 * Revisions:
 ****************************************************************/
#include <stdio.h>
#include <string.h>
#include <Xm/Xm.h>
#include <X11/Intrinsic.h>
#include <X11/IntrinsicP.h>
#include <X11/cursorfont.h>
#include <X11/StringDefs.h>

#include "BrowserP.h"

#define BR(w) (((BrowserWidget)(w))->browser)

#define PARENT(node)       ((node)->core->parent)
#define FIRSTCHILD(node)   ((node)->core->first_child)
#define NEXTSIBLING(node)  ((node)->core->sibling)

static Initialize();
static ClassInitialize();
static Destroy();
static Resize();
static ReDisplay();
static Boolean SetValues();
static void ac_select();
static void ac_unselect();

static XtResource resources[] =
{
	{XmNbrowserPolicy,
		XmCBrowserPolicy,
		XtRInt,
		sizeof(int),
		XtOffset(BrowserWidget,browser.policy),
		XtRString,
		"1"},
	{XmNtreeListChanged,
		XmCCallback,
		XtRCallback,
		sizeof(caddr_t),
		XtOffset(BrowserWidget,browser.list_changed),
		XtRCallback,
		NULL},
	{XmNbrowserStyle,
		XmCBrowserStyle,
		XtRInt,
		sizeof(int),
		XtOffset(BrowserWidget,browser.style),
		XtRString,
		"1"},
	{XmNdoubleClickDelay,
		XmCDoubleClickDelay,
		XtRInt,
		sizeof(int),
		XtOffset(BrowserWidget,browser.delay),
		XtRString,
		"250"},
	{XmNchildMarker,
		XmCChildMarker,
		XtRString,
		sizeof(String),
		XtOffset(BrowserWidget,browser.child_marker),
		XtRString,
		"*"},
	{XtNfont,
		XtCfont,
		XtRFontStruct,
		sizeof(XFontStruct *),
		XtOffset(BrowserWidget,browser.font),
		XtRString,
		"XtDefaultFont"},
	{XmNforeground,
		XmCForeground,
		XtRPixel,
		sizeof(Pixel),
		XtOffset(BrowserWidget,browser.foreground),
		XtRString,
		"XtDefaultForeground"},
	{XmNverticalSpacing,
		XmCVerticalSpacing,
		XtRInt,
		sizeof(int),
		XtOffset(BrowserWidget,browser.v_spacing),
		XtRString,
		"2"},
	{XmNhorizontalSpacing,
		XmCHorizontalSpacing,
		XtRInt,
		sizeof(int),
		XtOffset(BrowserWidget,browser.h_spacing),
		XtRString,
		"2"},
	{XmNoutlineIndention,
		XmCOutlineIndention,
		XtRInt,
		sizeof(int),
		XtOffset(BrowserWidget,browser.outline_indent),
		XtRString,
		"10"},
	{XmNshowNodeTag,
		XmCShowNodeTag,
		XtRBoolean,
		sizeof(Boolean),
		XtOffset(BrowserWidget,browser.show_node_tag),
		XtRString,
		"False"},
	{XmNmarginWidth,
		XmCMarginWidth,
		XtRInt,
		sizeof(int),
		XtOffset(BrowserWidget,browser.margin_width),
		XtRString,
		"5"},
	{XmNmarginHeight,
		XmCMarginHeight,
		XtRInt,
		sizeof(int),
		XtOffset(BrowserWidget,browser.margin_height),
		XtRString,
		"5"},
} ;

static XtActionsRec actions[] = {
	{"ac_select",    ac_select},
	{"ac_unselect",  ac_unselect},
	{NULL,      NULL}
};

static char defaultTranslations[] =
	"<BtnDown>:    ac_select() \n\
	 <BtnUp>:      ac_unselect()";

BrowserClassRec browserWidgetClassRec =
{
	{
		(WidgetClass)&xmPrimitiveClassRec,        /* superclass */
		"Browser",                              /* class name */
		sizeof(BrowserRec),                     /* widget size */
		(XtWidgetClassProc)ClassInitialize,     /* class_initialize */
		NULL,                                   /* class_part_initialize */
		FALSE,                                  /* class inited */
		(XtInitProc)Initialize,                 /* initialize */
		NULL,                                   /* initialize_hook */
		XtInheritRealize,                       /* realize */
		actions,                                /* actions */
		XtNumber(actions),                      /* num_actions */
		resources,								/* resources */
		XtNumber(resources),					/* num_resources */
		NULLQUARK,								/* xrm_class */
		TRUE,									/* compress_motion */
		FALSE,									/* compress_exposure */
		TRUE,									/* compress_enterleave */
		FALSE,									/* visible_interest */
		(XtWidgetProc)Destroy,					/* destroy */
		(XtWidgetProc)Resize,					/* resize */
		(XtExposeProc)ReDisplay,				/* expose */
		SetValues,								/* set_values */
		NULL,									/* set_values_hook */
		XtInheritSetValuesAlmost,				/* set_values_almost */
		NULL,									/* get_values_hook */
		XtInheritAcceptFocus,					/* accept_focus */
		XtVersion,								/* version */
		NULL,									/* callback private */
		defaultTranslations,                    /* tm_table */
		NULL,									/* query_geometry */
		XtInheritDisplayAccelerator,			/* display_accelerator */
		NULL									/* extension */
	},
	{  /* XmPrimitive fields */
		NULL,
		NULL,
		NULL,
		NULL,
		NULL,
		0,
		NULL
	},
	{
		0,
	}
} ;

WidgetClass browserWidgetClass = (WidgetClass)&browserWidgetClassRec ;

/****************************************************************
 *                    Utility Functions
 ****************************************************************/

static
void
watch_on(w)
	Widget w;
{
	static Cursor watch;
	if (watch == NULL)
	{
		watch = XCreateFontCursor(XtDisplay(w),XC_watch);
	}
	XDefineCursor(XtDisplay(w),XtWindow(w),watch);
	XFlush(XtDisplay(w));
}

static
void
watch_off(w)
	Widget w;
{
	XUndefineCursor(XtDisplay(w),XtWindow(w));
}

static
void
make_gcs(w)
	Widget w;
{
	XGCValues values;

	values.foreground = BR(w).foreground;
	values.background = w->core.background_pixel;
	values.font = BR(w).font->fid;
	BR(w).normgc = XtGetGC(w, (unsigned) (GCForeground | GCBackground | GCFont), &values);

	values.foreground = w->core.background_pixel;
	values.background = BR(w).foreground;
	BR(w).revgc = XtGetGC(w, (unsigned) (GCForeground | GCBackground | GCFont), &values);
}

static
void
release_gcs(w)
	Widget w;
{
	XtDestroyGC(BR(w).normgc);
	XtDestroyGC(BR(w).revgc);
}

static
Node *
last_child(node)
	Node *node;
{
	if (node == NULL)
	{
		return NULL;
	}
	else
	{
		Node *child = node->core->first_child;
		Node *last;

		if (child == NULL)
		{
			return NULL;
		}
		else
		{
			while (child != NULL)
			{
				last = child;
				child = child->core->sibling;
			}
		}
		return last;
	}
}

static
char *
get_text_for_node(w,node)
	Widget w;
	Node *node;
{
	static char buf[300];
	int len_needed = 0;
	char *ptr;
	int i;

	if (node->core->state == NotAsked)
	{
		if (node->core->first_child == NULL)
		{
			XmBrowserCallbackStruct info;

			info.reason = XmCR_CHECK_FOR_CHILDREN;
			info.event = NULL;
			info.node = node;
			info.has_children = FALSE;

			/* we should call the callback to see if node has children */
			XmBrowserCallCallbacks(w,node->tree,XmNcheckForChildrenCallback,(XtPointer)&info);

			if (info.has_children)
			{
				node->core->state = HideChildren;
			}
			else
			{
				node->core->state = NoChildren;
			}
		}
		else
		{
			node->core->state = HideChildren;
		}
	}

	len_needed = 3 + strlen(BR(w).child_marker) + (node->core->level * BR(w).outline_indent);

	if (node->node_tag != NULL)
	{
		len_needed += strlen(node->node_tag);
	}

	if (node->node_title != NULL)
	{
		len_needed += strlen(node->node_title);
	}

	if (len_needed < 300)
	{
		ptr = buf;
	}
	else
	{
		ptr = XtMalloc((unsigned)len_needed);
	}

	if (node->node_tag != NULL)
	{
		strcpy(ptr," ");
		strcat(ptr,node->node_tag);
		strcat(ptr," ");
	}
	else
	{
		strcpy(ptr,"    ");
	}

	if (node->core->state == HideChildren)
	{
		strcat(ptr," ");
		strcat(ptr,BR(w).child_marker);
		strcat(ptr," ");
	}
	else
	{
		strcat(ptr,"    ");
	}

	for (i = 0; i < node->core->level; i++)
	{
		strncat(ptr,"                   ",BR(w).outline_indent);
	}

	if (node->node_title != NULL)
	{
		strcat(ptr,node->node_title);
	}

	/* truncate text if pointer was allocated and free space */
	if (ptr != buf)
	{
		strncpy(buf,ptr,299);
		buf[299] = 0;
		XtFree(ptr);
	}

	return buf;
}

static
void
recursive_layout(w,node)
	Widget w;
	Node *node;
{
	Node *child = node->core->first_child;
	char *str;
	int len;

	node->core->level = BR(w).level;
	node->core->x = BR(w).x;
	node->core->y = BR(w).y;
	node->core->width = XTextWidth(BR(w).font,node->node_title,strlen(node->node_title));
	str = get_text_for_node(w,node);
	len = XTextWidth(BR(w).font,str,strlen(str));
	node->core->x_str = BR(w).x + len - node->core->width;
	node->core->height = BR(w).font->ascent + BR(w).font->descent + BR(w).v_spacing;
	if (len > BR(w).max_width)
	{
		BR(w).max_width = len;
	}
	if (BR(w).y + node->core->height > BR(w).max_height)
	{
		BR(w).max_height = BR(w).y + node->core->height;
	}

	if (node->core->state == ShowChildren)
	{
		if (child == NULL)
		{
			return;
		}
		else
		{
			++BR(w).level;
			while(child != NULL)
			{
				BR(w).y = BR(w).y + BR(w).font->ascent + BR(w).font->descent + BR(w).v_spacing;

				recursive_layout(w,child);

				child = child->core->sibling;
			}
			--BR(w).level;
		}
	}
}

static
void
do_layout(w)
	Widget w;
{
	/* reset temporary storage for recursion */
	BR(w).x = BR(w).margin_width;
	BR(w).y = BR(w).margin_height;
	BR(w).max_width = w->core.width;
	BR(w).max_height = w->core.height;
	BR(w).level = 0;

	/* call recursive layout function */
	if (BR(w).current_tree != NULL)
	{
		XtWidgetGeometry req, reply;

		recursive_layout(w,BR(w).current_tree->root);

		req.request_mode = 0;
		if (BR(w).max_width > w->core.width)
		{
			req.request_mode |= CWWidth;
			req.width = BR(w).max_width + BR(w).margin_width;
		}

		if (BR(w).max_height > w->core.height)
		{
			req.request_mode |= CWHeight;
			req.height = BR(w).max_height + BR(w).margin_height;
		}

		if (req.request_mode)
		{
			XtMakeGeometryRequest(w,&req,&reply);
		}
	}
}

static
Node *
recursive_search(w,node,cmp,data)
	Widget w;
	Node *node;
	int (*cmp)();
	XtPointer data;
{
	if (node != NULL)
	{
		if (cmp(node,data) == 0)
		{
			return node;
		}

		if (node->core->state == ShowChildren)
		{
			Node *child = node->core->first_child;

			if (child == NULL)
			{
				return NULL;
			}
			else
			{
				while(child != NULL)
				{
					Node *rnode;

					rnode = recursive_search(w,child,cmp,data);
					if (rnode != NULL)
					{
						return rnode;
					}
	
					child = child->core->sibling;
				}

				return NULL;
			}
		}
	}
	return NULL;
}

static
Node *
search_tree(w,tree,cmp,data)
	Widget w;
	Tree *tree;
	int (*cmp)();
	XtPointer data;
{
	return recursive_search(w,tree->root,cmp,data);
}

static
int
check_xy(node,event)
	Node *node;
	XButtonEvent *event;
{
	int y = event->y;
	if (node != NULL)
	{
		if ((y > node->core->y) && (y < node->core->y + node->core->height))
		{
			return 0;
		}
	}
	return 1;
}

/*
 * This function finds the node located where the button was clicked.
 * This function only applies the the current tree being browsed.
 */
static
Node *
find_node_at_point(w,event)
	Widget w;
	XButtonEvent *event;
{
	if (BR(w).current_tree != NULL)
	{
		return search_tree(w,BR(w).current_tree,check_xy,(XtPointer)event);
	}
	else
	{
		return NULL;
	}
}

static
void
draw_text(w,gc,node)
	Widget w;
	GC gc;
	Node *node;
{
	int y = node->core->y + BR(w).font->ascent + (BR(w).v_spacing / 2);
	XDrawString(XtDisplay(w),XtWindow(w),gc,node->core->x_str,y,
		node->node_title,strlen(node->node_title)); 
}

static
void
draw_node(w,gc,node)
	Widget w;
	GC gc;
	Node *node;
{
	int y = node->core->y + BR(w).font->ascent + (BR(w).v_spacing / 2);
	char *str;

	str = get_text_for_node(w,node);

	XDrawString(XtDisplay(w),XtWindow(w),gc,node->core->x,y,str,strlen(str)); 
}

static
void
highlight_node(w,node)
	Widget w;
	Node *node;
{
	if (node != NULL)
	{
		if (node->core->selected)
		{
			int y = node->core->y + BR(w).font->ascent;
			XFillRectangle(XtDisplay(w),XtWindow(w),BR(w).revgc,node->core->x_str,node->core->y,
				node->core->width,node->core->height);
			draw_text(w,BR(w).normgc,node);
			node->core->selected = FALSE;
		}
		else
		{
			int y = node->core->y + BR(w).font->ascent;
			XFillRectangle(XtDisplay(w),XtWindow(w),BR(w).normgc,node->core->x_str,node->core->y,
				node->core->width,node->core->height);
			draw_text(w,BR(w).revgc,node);
			node->core->selected = TRUE;
		}
	}
}

/*
 * This function allows us to redisplay a node in the browser.
 * It simply clears the entire node entry in the browser and
 * lets the expose event (i.e. ReDisplay callback) handle the
 * redrawing.
 */
static
void
redisplay_node(w,node)
	Widget w;
	Node *node;
{
	if (node != NULL)
	{
		if (node->tree == BR(w).current_tree)
		{
			XClearArea(XtDisplay(w),XtWindow(w),w->core.x,node->core->y,
				w->core.width,node->core->height,True);
		}
	}
}

static
void
deselect_tree(w,node)
	Widget w;
	Node *node;
{
	if (node != NULL)
	{
		Node *child = node->core->first_child;
		Node *parent = node->core->parent;

		if (node->core->selected)
		{
			if (parent == NULL)
			{
				int y = node->core->y + BR(w).font->ascent;
				XFillRectangle(XtDisplay(w),XtWindow(w),BR(w).revgc,node->core->x_str,node->core->y,
						node->core->width,node->core->height);
				draw_node(w,BR(w).normgc,node);
				node->core->selected = FALSE;
			}
			else if (parent->core->state == ShowChildren)
			{
				int y = node->core->y + BR(w).font->ascent;
				XFillRectangle(XtDisplay(w),XtWindow(w),BR(w).revgc,node->core->x_str,node->core->y,
						node->core->width,node->core->height);
				draw_node(w,BR(w).normgc,node);
				node->core->selected = FALSE;
			}
			else
			{
				node->core->selected = FALSE;
			}
		}

		if (child == NULL)
		{
			return;
		}
		else
		{
			while(child != NULL)
			{
				deselect_tree(w,child);

				child = child->core->sibling;
			}
		}
	}
}

static
void
recursive_draw(w,node,x,y,width,height)
	Widget w;
	Node *node;
	int x;
	int y;
	int width;
	int height;
{
	if (node != NULL)
	{
		if (((node->core->y >= y) && (node->core->y <= y + height)) ||
			(node->core->y + node->core->height >= y) &&
			(node->core->y + node->core->height <= y + height))
		{
		if (node->core->selected)
		{
			int y = node->core->y + BR(w).font->ascent;
			draw_node(w,BR(w).normgc,node);
			XFillRectangle(XtDisplay(w),XtWindow(w),BR(w).normgc,node->core->x_str,node->core->y,
				node->core->width,node->core->height);
			draw_text(w,BR(w).revgc,node);
		}
		else
		{
			draw_node(w,BR(w).normgc,node);
		}
		}

		if (node->core->state == ShowChildren)
		{
			Node *child = node->core->first_child;

			if (child == NULL)
			{
				return;
			}
			else
			{
				while(child != NULL)
				{
					recursive_draw(w,child,x,y,width,height);

					child = child->core->sibling;
				}
			}
		}
	}
}

static
void
redo_everything(w)
	Widget w;
{
	do_layout(w);

	if (XtIsRealized(w) && XtIsManaged(w))
	{
		XClearWindow(XtDisplay(w),XtWindow(w));

		recursive_draw(w,BR(w).current_tree->root,0,0,w->core.width,w->core.height);
	}
}

static
void
free_node(w,node)
	Widget w;
	Node *node;
{
	if (node->tree->destroy_callback != NULL)
	{
		XmBrowserCallbackStruct info;

		info.reason = XmCR_DESTROY_NODE;
		info.node = node;
		info.event = NULL;
		info.has_children = FALSE;

		/* now call the destroy callbacks for this tree */
		XmBrowserCallCallbacks(w,node->tree,XmNdestroyNodeCallback,(XtPointer)&info);
	}
	else
	{
		XtFree((XtPointer)node->user_data);
	}
	XtFree((XtPointer)node->core);
	XtFree((XtPointer)node->node_title);
	XtFree((XtPointer)node->node_tag);
	XtFree((XtPointer)node);
}

static
void
destroy_subtree(w,node)
	Widget w;
	Node *node;
{
	if (node != NULL)
	{
		Node *tmp1, *tmp2;

		while ((tmp1 = FIRSTCHILD(node)) != NULL)
		{
			tmp2 = NEXTSIBLING(tmp1);
			destroy_subtree(w,tmp1);
			FIRSTCHILD(node) = tmp2;
		}

		free_node(w,node);
	}
}

static
void
delete_node(w,node)
	Widget w;
	Node *node;
{
	if (node != NULL)
	{
		Node *parent = node->core->parent;

		/* first we need to unhook the node from the tree */
		if (parent == NULL)
		{
			BR(w).root = NULL;
		}
		else
		{
			if (parent->core->first_child == node)
			{
				parent->core->first_child = node->core->sibling;
			}
			else
			{
				Node *child = parent->core->first_child;
				Node *last;
	
				while((child != node) && (child != NULL))
				{
					last = child;
					child = child->core->sibling;
				}

				if (last != NULL)
				{
					last->core->sibling = node->core->sibling;
				}
			}
		}

		/* then we destroy the node and the tree below */
		destroy_subtree(w,node);
	}
}

static
void
show_children(w,node)
	Widget w;
	Node *node;
{
	node->core->state = ShowChildren;

	if (node->core->first_child == NULL)
	{
		XmBrowserCallbackStruct info;

		info.reason = XmCR_GET_CHILDREN;
		info.node = node;
		info.event = NULL;
		info.has_children = TRUE;

		watch_on(w);
		XmBrowserCallCallbacks(w,node->tree,XmNgetChildrenCallback,(XtPointer)&info);
		watch_off(w);
	}

	redo_everything(w);
}

static
void
hide_children(w,node)
	Widget w;
	Node *node;
{
	node->core->state = HideChildren;

	redo_everything(w);
}

/****************************************************************
 *                    Methods
 ****************************************************************/

static
ClassInitialize()
{
}

/*ARGSUSED*/
static
Initialize(request,new)
	Widget request ;
	Widget new ;
{
	if (new->core.width < 1)
	{
		new->core.width = 1 ;
	}
	if (new->core.height < 1)
	{
		new->core.height = 1 ;
	}

	make_gcs(new) ;

	BR(new).last_time = 0;
	BR(new).root = NULL;
	BR(new).first_tree = NULL;
	BR(new).current_tree = NULL;
}

/*ARGSUSED*/
static
Destroy(w)
	Widget w ;
{
	release_gcs(w) ;
}

/*ARGSUSED*/
static
Resize(w)
	Widget w ;
{
}

/*ARGSUSED*/
static
ReDisplay(w, event)
	Widget w ;
	XExposeEvent *event ;
{
	if (BR(w).current_tree != NULL)
	{
		recursive_draw(w,BR(w).current_tree->root,event->x,event->y,event->width,event->height);
	}
}

/*ARGSUSED*/
static
Boolean
SetValues(old,req,new)
	Widget old;
	Widget req;
	Widget new;
{
	/* check for correct width and height */
	if (new->core.width < 1)
	{
		new->core.width = 1;
	}
	if (new->core.height < 1)
	{
		new->core.height = 1;
	}

	if ((BR(old).root != BR(new).root) ||
		(BR(old).outline_indent != BR(new).outline_indent) ||
		(BR(old).show_node_tag != BR(new).show_node_tag))
	{
		do_layout(new);
	}

	release_gcs(old) ;
	make_gcs(new) ;

	return True ;
}

/****************************************************************
 *                    Action Functions
 ****************************************************************/

static
void
ac_select(w,event)
	Widget w;
	XButtonEvent *event;
{
	Node *node = find_node_at_point(w,event);

	if (node != NULL)
	{
		if (event->button == Button1)
		{
			BR(w).current = node;
			if ((BR(w).policy == XmBROWSER_SINGLE) || !(event->state & ShiftMask))
			{
				deselect_tree(w,BR(w).current_tree->root);
			}
			highlight_node(w,node);
		}
		else if (event->button == Button2)
		{
			BR(w).current = NULL;
			switch (node->core->state)
			{
				case NoChildren:
					/* do nothing */
					break;
				case ShowChildren:
					hide_children(w,node);
					break;
				case HideChildren:
					show_children(w,node);
					break;
				default:
					break;
			}
		}
	}
}

static
void
ac_unselect(w,event)
	Widget w;
	XButtonEvent *event;
{
	if (BR(w).current != NULL)
	{
		XmBrowserCallbackStruct info;
		Time diff;

		diff = event->time - BR(w).last_time;

		info.event = (XEvent *) event;
		info.node = BR(w).current;

		if (diff < BR(w).delay)
		{
			info.reason = XmCR_DOUBLE;
			XmBrowserCallCallbacks(w,info.node->tree,XmNdoubleClickCallback,(XtPointer)&info);
			BR(w).last_time = 0;
		}
		else
		{
			info.reason = XmCR_SINGLE;
			XmBrowserCallCallbacks(w,info.node->tree,XmNsingleSelectionCallback,(XtPointer)&info);
			BR(w).last_time = event->time;
		}
	}
}

/****************************************************************
 *                    Public Functions
 ****************************************************************/

/****************************************************************
 * Function: XmBrowserAddNode 
 * Date: 03/14/91
 *
 * Description:
 *   This function adds a node to the browser and displays
 *   the new node in the browser.
 *
 * Linkage: Node *XmBrowserAddNode(w,parent,node)
 *   Widget w     - browser widget
 *   Node *parent - parent node of the node to be added
 *   Node *node   - node to be added
 *
 * Revisions:
 ****************************************************************/
Node *
XmBrowserAddNode(w,tree,parent,node_title,node_tag,user_data)
	Widget w;
	Tree *tree;
	Node *parent;
	char *node_title;
	char *node_tag;
	char *user_data;
{
	Node *node;

	/* check to make sure the widget is correct */
	if (!XtIsSubclass(w, browserWidgetClass))
	{
		XtAppWarning(XtWidgetToApplicationContext(w),
			"XmBrowserAddNode: Non-Browser widget was passed.");
		return NULL;
	}

	if (tree == NULL)
	{
		fprintf(stderr,"XmBrowserAddNode: tree is NULL\n");
		return NULL;
	}

	node = (Node *)XtMalloc(sizeof(Node));
	if (node == NULL)
	{
		XtError("XmBrowserAddNode: XtMalloc failed to malloc node");
		exit(-1);
	}

	node->tree = tree;

	if (node_title)
	{
		node->node_title = strdup(node_title);
	}
	else
	{
		node->node_title = NULL;
	}

	if (node_tag)
	{
		node->node_tag = strdup(node_tag);
	}
	else
	{
		node->node_tag = NULL;
	}

	if (user_data)
	{
		node->user_data = user_data;
	}
	else
	{
		node->user_data = NULL;
	}
	

	node->core = (NodePartRec *)XtMalloc(sizeof(NodePartRec));
	if (node->core == NULL)
	{
		XtError("XmBrowserAddNode: XtMalloc failed to malloc node->core");
		exit(-1);
	}

	/* fill in the core node information */
	node->core->sibling = NULL;
	node->core->first_child = NULL;
	node->core->selected = FALSE;
	node->core->level = 0;
	node->core->state = NotAsked;
	node->core->x = 0;
	node->core->y = 0;
	node->core->width = 0;
	node->core->height = 0;

	if (parent == NULL)
	{
		node->core->parent = NULL;

		if (tree->root == NULL)
		{
			tree->root = node;
		}
	}
	else
	{
		node->core->parent = parent;

		switch (parent->core->state)
		{
			case NotAsked:
			case NoChildren:
				parent->core->state = HideChildren;
				break;
			case ShowChildren:
				/* redo layout */
				break;
			default:
				break;
		}

		if (parent->core->first_child == NULL)
		{
			parent->core->first_child = node;
		}
		else
		{
			Node *last = last_child(parent);
			last->core->sibling = node;
		}
	}

	if ((parent == NULL) || (parent->core->state == ShowChildren))
	{
		do_layout(w);
	}
	return node;
}

/****************************************************************
 * Function: XmBrowserRedisplay 
 * Date: 03/14/91
 *
 * Description:
 *   This function redisplays the current tree displayed in the
 *   browser.
 *
 * Linkage: void XmBrowserRedisplay(w)
 *   Widget w   - browser widget 
 *
 * Revisions:
 ****************************************************************/
void
XmBrowserRedisplay(w)
	Widget w;
{
	/* check to make sure the widget is correct */
	if (!XtIsSubclass(w, browserWidgetClass))
	{
		XtAppWarning(XtWidgetToApplicationContext(w),
			"XmBrowserDeleteNode: Non-Browser widget was passed.");
		return;
	}

	redo_everything(w);
}

/****************************************************************
 * Function: XmBrowserDeleteNode 
 * Date: 03/14/91
 *
 * Description:
 *   This function deletes a node from the browser. The destroy
 *   callback is first called to delete any application specific
 *   data attached to the node, the node is then delete from the
 *   node tree and from the browser display.
 *
 * Linkage: void XmBrowserDeleteNode(w,node)
 *   Widget w   - browser widget 
 *   Node *node - pointer to the node to be deleted 
 *
 * Revisions:
 ****************************************************************/
void
XmBrowserDeleteNode(w,node)
	Widget w;
	Node *node;
{
	/* check to make sure the widget is correct */
	if (!XtIsSubclass(w, browserWidgetClass))
	{
		XtAppWarning(XtWidgetToApplicationContext(w),
			"XmBrowserDeleteNode: Non-Browser widget was passed.");
		return;
	}

	if (node == NULL) return;

	delete_node(w,node);

	redo_everything(w);
}

/****************************************************************
 * Function: XmBrowserParent 
 * Date: 03/14/91
 *
 * Description:
 *   This function returns the parent node for a specified node.
 *
 * Linkage: Node *XmBrowserParent(node)
 *   Node *node - target node
 *
 * Revisions:
 ****************************************************************/
Node *
XmBrowserParent(node)
	Node *node;
{
	if (node != NULL)
	{
		return node->core->parent;
	}
	return NULL;
}
/****************************************************************
 * Function: XmBrowserNextSibling 
 * Date: 03/14/91
 *
 * Description:
 *   This function returns the next sibling node for a specified
 *   target node. It returns NULL if the next sibling is NULL;
 *
 * Linkage: Node *XmBrowserNextSibling(node)
 *   Node *node - target node
 *
 * Revisions:
 ****************************************************************/
Node *
XmBrowserNextSibling(node)
	Node *node;
{
	if (node != NULL)
	{
		return node->core->sibling;
	}
	return NULL;
}

/****************************************************************
 * Function: XmBrowserFirstChild 
 * Date: 03/14/91
 *
 * Description:
 *   This function returns the first child node for a specified
 *   target node.
 *
 * Linkage: Node *XmBrowserFirstChild(node)
 *   Node *node - target node
 *
 * Revisions:
 ****************************************************************/
Node *
XmBrowserFirstChild(node)
	Node *node;
{
	if (node != NULL)
	{
		return node->core->first_child;
	}
	return NULL;
}

/****************************************************************
 * Function: XmBrowserUpdateNode  
 * Date: 03/14/91
 *
 * Description:
 *   This function updates a node in the browser.
 *
 * Linkage: void XmBrowserUpdateNode(w,node)
 *   Widget w   - browser widget
 *   Node *node - modified node
 *
 * Revisions:
 ****************************************************************/
void
XmBrowserUpdateNode(w,node)
	Widget w;
	Node *node;
{
	/* check to make sure the widget is correct */
	if (!XtIsSubclass(w, browserWidgetClass))
	{
		XtAppWarning(XtWidgetToApplicationContext(w),
			"XmBrowserUpdateNode: Non-Browser widget was passed.");
		return;
	}

	if (node == NULL) return;

	do_layout(w);

	redisplay_node(w,node);
}

/****************************************************************
 * Function: XmBrowserSelectNode
 * Date: 03/15/91
 *
 * Description:
 *   This functions deselects all currently selected nodes in
 *   the browser.
 *
 * Linkage: void XmBrowserSelectNode(w,node)
 *   Widget w   - browser widget
 *   Node *node - node in browser to be highlighted
 *
 * Revisions:
 ****************************************************************/
void
XmBrowserSelectNode(w,node)
	Widget w;
	Node *node;
{
	/* check to make sure the widget is correct */
	if (!XtIsSubclass(w, browserWidgetClass))
	{
		XtAppWarning(XtWidgetToApplicationContext(w),
			"XmBrowserDeselectAll: Non-Browser widget was passed.");
		return;
	}

	if (BR(w).current_tree != NULL)
	{
		if (BR(w).policy == XmBROWSER_SINGLE)
		{
			deselect_tree(w,BR(w).current_tree->root);
		}
		highlight_node(w,node);
	}
}

/****************************************************************
 * Function: XmBrowserDeselectAll 
 * Date: 03/15/91
 *
 * Description:
 *   This functions deselects all currently selected nodes in
 *   the browser.
 *
 * Linkage: void XmBrowserDeselectAll(w)
 *   Widget w - browser widget
 *
 * Revisions:
 ****************************************************************/
void
XmBrowserDeselectAll(w)
	Widget w;
{
	/* check to make sure the widget is correct */
	if (!XtIsSubclass(w, browserWidgetClass))
	{
		XtAppWarning(XtWidgetToApplicationContext(w),
			"XmBrowserDeselectAll: Non-Browser widget was passed.");
		return;
	}

	if (BR(w).current_tree != NULL)
	{
		deselect_tree(w,BR(w).current_tree->root);
	}
}

/****************************************************************
 * Function: XmBrowserGetRootNode 
 * Date: 04/01/91
 *
 * Description:
 *   The function returns the current root node of the browser
 *   tree.
 *
 * Linkage: Node *XmBrowserGetRootNode(w)
 *   Widget w - browser widget
 *
 * Revisions:
 ****************************************************************/
Node *
XmBrowserGetRootNode(w,tree)
	Widget w;
	Tree *tree;
{
	/* check to make sure the widget is correct */
	if (!XtIsSubclass(w, browserWidgetClass))
	{
		XtAppWarning(XtWidgetToApplicationContext(w),
			"XmBrowserGetRootNode: Non-Browser widget was passed.");
		return NULL;
	}

	if (tree == NULL)
	{
		return NULL;
	}
	else
	{
		return tree->root;
	}
}

/****************************************************************
 * Function: XmBrowserSearchTree 
 * Date: 04/01/91
 *
 * Description:
 *   The function allows the user to search the browser tree.
 *
 * Linkage: Node *XmBrowserSearchTree(w, cmp)
 *   Widget w     - browser widget
 *   int (*cmp)() - comparison function
 *
 * Revisions:
 ****************************************************************/
Node *
XmBrowserSearchTree(w,tree,cmp,data)
	Widget w;
	Tree *tree;
	int (*cmp)();
	XtPointer data;
{
	/* check to make sure the widget is correct */
	if (!XtIsSubclass(w, browserWidgetClass))
	{
		XtAppWarning(XtWidgetToApplicationContext(w),
			"XmBrowserSearchTree: Non-Browser widget was passed.");
		return;
	}

	if (tree == NULL)
	{
		fprintf(stderr,"XmBrowserSearchTree: tree is NULL\n");
		return NULL;
	}

	/* we should supply our own built in search function, but for now.. */
	if (cmp == NULL)
	{
		fprintf(stderr,"XmBrowserSearchTree: compare function is NULL\n");
		return NULL;
	}

	return search_tree(w,tree,cmp,data);
}

/****************************************************************
 * Function: XmBrowserCreateTree 
 * Date: 04/04/91
 *
 * Description:
 *   The function creates a tree of nodes in the browser and
 *   adds this tree to the list of selectable browser trees.
 *
 * Linkage: Tree *XmBrowserCreateTree(w,name)
 *   Widget w   - browser widget
 *   char *name - name of tree
 *
 * Revisions:
 ****************************************************************/
Tree *
XmBrowserCreateTree(w,name)
	Widget w;
	char *name;
{
	static int count = 1;
	char *tree_name = NULL;
	Tree *tree;

	/* check to make sure the widget is correct */
	if (!XtIsSubclass(w, browserWidgetClass))
	{
		XtAppWarning(XtWidgetToApplicationContext(w),
			"XmBrowserCreateTree: Non-Browser widget was passed.");
		return NULL;
	}

	if (name == NULL)
	{
		char buf[30];
		sprintf(buf,"Tree %d",count);
		tree_name = &buf[0];
	}
	else
	{
		tree_name = name;
	}

	tree = (Tree *)XtMalloc(sizeof(Tree));

	tree->name = strdup(tree_name);
	tree->root = NULL;
	tree->single_callback = NULL;
	tree->double_callback = NULL;
	tree->destroy_callback = NULL;
	tree->check_callback = NULL;
	tree->get_children_callback = NULL;

	tree->next = BR(w).first_tree;
	BR(w).first_tree = tree;

	/* always set the new tree as the current tree */
	BR(w).current_tree = tree;

	/* inform callbacks that a new tree has been created */
	{
		XtCallCallbacks(w,XmNtreeListChanged,(XtPointer)NULL);
	}

	return tree;
}

/****************************************************************
 * Function: XmBrowserSetTree 
 * Date: 04/04/91
 *
 * Description:
 *   The function sets the specified tree to the current tree
 *   to be browsed.
 *
 * Linkage: void XmBrowserSetTree(w,tree)
 *   Widget w   - browser widget
 *   Tree *tree - current tree to be set
 *
 * Revisions:
 ****************************************************************/
void
XmBrowserSetTree(w,tree)
	Widget w;
	Tree *tree;
{
	/* check to make sure the widget is correct */
	if (!XtIsSubclass(w, browserWidgetClass))
	{
		XtAppWarning(XtWidgetToApplicationContext(w),
			"XmBrowserSetTree: Non-Browser widget was passed.");
		return;
	}

	if (tree != NULL)
	{
		BR(w).current_tree = tree;
/*
		do_layout(w);
*/
		redo_everything(w);
	}
}

/****************************************************************
 * Function: XmBrowserFindTree 
 * Date: 04/04/91
 *
 * Description:
 *   The function finds a tree by name.
 *
 * Linkage: Tree *XmBrowserFindTree(w,name)
 *   Widget w   - browser widget
 *   char *name - tree name
 *
 * Revisions:
 ****************************************************************/
Tree *
XmBrowserFindTree(w,name)
	Widget w;
	char *name;
{
	Tree *tree;

	/* check to make sure the widget is correct */
	if (!XtIsSubclass(w, browserWidgetClass))
	{
		XtAppWarning(XtWidgetToApplicationContext(w),
			"XmBrowserFindTree: Non-Browser widget was passed.");
		return NULL;
	}

	tree = BR(w).first_tree;
	while (tree != NULL)
	{
		if (strcmp(tree->name,name) == 0)
		{
			return tree;
		}
		tree = tree->next;
	}

	return NULL;
}

/****************************************************************
 * Function: XmBrowserGetTreeList 
 * Date: 04/04/91
 *
 * Description:
 *   The function returns the list of browser trees maintained
 *   by the specified browser widget starting with the first 
 *   tree.
 *
 * Linkage: Tree *XmBrowserGetTreeList(w)
 *   Widget w   - browser widget
 *
 * Revisions:
 ****************************************************************/
Tree *
XmBrowserGetTreeList(w)
	Widget w;
{
	/* check to make sure the widget is correct */
	if (!XtIsSubclass(w, browserWidgetClass))
	{
		XtAppWarning(XtWidgetToApplicationContext(w),
			"XmBrowserGetTree: Non-Browser widget was passed.");
		return NULL;
	}

	return BR(w).first_tree;
}

/****************************************************************
 * Function: XmBrowserAddCallback 
 * Date: 04/04/91
 *
 * Description:
 *   This function adds a callback function to a specified
 *   browser tree.
 *
 * Linkage: void XmBrowserAddCallback(w,tree,res,callback,data)
 *   Widget w           - browser widget
 *   Tree *tree         - browser tree
 *   char *res          - callback list resource
 *   void (*callback)() - pointer to callback function
 *   XtPointer data     - client data passed to callback
 *
 * Revisions:
 ****************************************************************/
void
XmBrowserAddCallback(w,tree,res,callback,data)
	Widget w;
	Tree *tree;
	char *res;
	void (*callback)();
	XtPointer data;
{
	XmBrowserCallback *new;

	/* check to make sure the widget is correct */
	if (!XtIsSubclass(w, browserWidgetClass))
	{
		XtAppWarning(XtWidgetToApplicationContext(w),
			"XmBrowserAddCallback: Non-Browser widget was passed.");
		return;
	}

	if (tree == NULL)
	{
		XtAppWarning(XtWidgetToApplicationContext(w),
			"XmBrowserAddCallback: Browser Tree passed is NULL");
		return;
	}

	if (res == NULL)
	{
		XtAppWarning(XtWidgetToApplicationContext(w),
			"XmBrowserAddCallback: resource name is NULL");
		return;
	}

	if (callback == NULL)
	{
		XtAppWarning(XtWidgetToApplicationContext(w),
			"XmBrowserAddCallback: callback function is NULL");
		return;
	}

	new = (XmBrowserCallback *)XtMalloc(sizeof(XmBrowserCallback));
	new->callback = callback;
	new->data = data;

	if (strcmp(res,XmNsingleSelectionCallback) == 0)
	{
		new->next = tree->single_callback;
		tree->single_callback = new;
	}
	if (strcmp(res,XmNdoubleClickCallback) == 0)
	{
		new->next = tree->double_callback;
		tree->double_callback = new;
	}
	if (strcmp(res,XmNcheckForChildrenCallback) == 0)
	{
		new->next = tree->check_callback;
		tree->check_callback = new;
	}
	if (strcmp(res,XmNgetChildrenCallback) == 0)
	{
		new->next = tree->get_children_callback;
		tree->get_children_callback = new;
	}
	if (strcmp(res,XmNdestroyNodeCallback) == 0)
	{
		new->next = tree->destroy_callback;
		tree->destroy_callback = new;
	}
}

/****************************************************************
 * Function: XmBrowserCallCallbacks
 * Date: 04/04/91
 *
 * Description:
 *   This function adds a callback function to a specified
 *   browser tree.
 *
 * Linkage: void XmBrowserCallCallbacks(w,tree,res,data)
 *   Widget w           - browser widget
 *   Tree *tree         - browser tree
 *   char *res          - callback list resource
 *   XtPointer data     - client data passed to callback
 *
 * Revisions:
 ****************************************************************/
void
XmBrowserCallCallbacks(w,tree,res,data)
	Widget w;
	Tree *tree;
	char *res;
	XtPointer data;
{
	XmBrowserCallback *list;

	/* check to make sure the widget is correct */
	if (!XtIsSubclass(w, browserWidgetClass))
	{
		XtAppWarning(XtWidgetToApplicationContext(w),
			"XmBrowserAddCallback: Non-Browser widget was passed.");
		return;
	}

	if (strcmp(res,XmNsingleSelectionCallback) == 0)
	{
		list = tree->single_callback;
	}
	else if (strcmp(res,XmNdoubleClickCallback) == 0)
	{
		list = tree->double_callback;
	}
	else if (strcmp(res,XmNcheckForChildrenCallback) == 0)
	{
		list = tree->check_callback;
	}
	else if (strcmp(res,XmNgetChildrenCallback) == 0)
	{
		list = tree->get_children_callback;
	}
	else if (strcmp(res,XmNdestroyNodeCallback) == 0)
	{
		list = tree->destroy_callback;
	}

	/* cycle through list and execute callbacks */
	while (list != NULL)
	{
		list->callback(w,list->data,data);
		list = list->next;
	}
}
