/*
 * bltHtCmd.c --
 *
 *	This module implements an hierarchy widget for the BLT toolkit.
 *
 * Copyright 1998-1999 Lucent Technologies, Inc.
 *
 * Permission to use, copy, modify, and distribute this software and
 * its documentation for any purpose and without fee is hereby
 * granted, provided that the above copyright notice appear in all
 * copies and that both that the copyright notice and warranty
 * disclaimer appear in supporting documentation, and that the names
 * of Lucent Technologies or any of their entities not be used in
 * advertising or publicity pertaining to distribution of the software
 * without specific, written prior permission.
 *
 * Lucent Technologies disclaims all warranties with regard to this
 * software, including all implied warranties of merchantability and
 * fitness.  In no event shall Lucent Technologies be liable for any
 * special, indirect or consequential damages or any damages
 * whatsoever resulting from loss of use, data or profits, whether in
 * an action of contract, negligence or other tortuous action, arising
 * out of or in connection with the use or performance of this
 * software.
 *
 *	The "hiertable" widget was created by George A. Howlett.
 */

/*
 * TODO:
 *
 * BUGS:
 *   1.  "open" operation should change scroll offset so that as many
 *	 new entries (up to half a screen) can be seen.
 *   2.  "open" needs to adjust the scrolloffset so that the same entry
 *	 is seen at the same place.
 */
#include "bltInt.h"

#ifndef NO_HIERTABLE

#include "bltHiertable.h"
#include "bltList.h"
#include <X11/Xutil.h>
#include <X11/Xatom.h>

#define CLAMP(val,low,hi)	\
	(((val) < (low)) ? (low) : ((val) > (hi)) ? (hi) : (val))

#ifdef __STDC__
static CompareProc 
	ExactCompare, 
	GlobCompare, 
	RegexpCompare;
static ApplyProc 
	ShowEntryApplyProc,
	HideEntryApplyProc, 
	MapAncestorsApplyProc, 
	FixSelectionsApplyProc;
static Tk_LostSelProc LostSelection;
static ApplyProc SelectEntryApplyProc;
#endif /* __STDC__ */

/*
 *----------------------------------------------------------------------
 *
 * SkipSeparators --
 *
 *	Moves the character pointer past one of more separators.
 *
 * Results:
 *	Returns the updates character pointer.
 *
 *----------------------------------------------------------------------
 */
static char *
SkipSeparators(path, pathSep, length)
    char *path, *pathSep;
    int length;
{
    while ((*path == pathSep[0]) && (strncmp(path, pathSep, length) == 0)) {
	path += length;
    }
    return path;
}

/*
 *----------------------------------------------------------------------
 *
 * SplitPath --
 *
 *	Returns the trailing component of the given path.  Trailing
 *	separators are ignored.
 *
 * Results:
 *	Returns the string of the tail component.
 *
 *----------------------------------------------------------------------
 */
static int
SplitPath(htabPtr, path, depthPtr, compPtrPtr)
    Hiertable *htabPtr;
    char *path;
    int *depthPtr;
    char ***compPtrPtr;
{
    int skipLen, pathLen;
    int depth, listSize;
    char **components;
    register char *p;
    char *sep;

    if (htabPtr->pathSep == SEPARATOR_LIST) {
	if (Tcl_SplitList(htabPtr->interp, path, depthPtr, compPtrPtr) 
	    != TCL_OK) {
	    return TCL_ERROR;
	}
	return TCL_OK;
    }
    pathLen = strlen(path);

    skipLen = strlen(htabPtr->pathSep);
    path = SkipSeparators(path, htabPtr->pathSep, skipLen);
    depth = pathLen / skipLen;

    listSize = (depth + 1) * sizeof(char *);
    components = (char **)malloc(listSize + (pathLen + 1));
    assert(components);
    p = (char *)components + listSize;
    strcpy(p, path);

    sep = strstr(p, htabPtr->pathSep);
    depth = 0;
    while ((*p != '\0') && (sep != NULL)) {
	*sep = '\0';
	components[depth++] = p;
	p = SkipSeparators(sep + skipLen, htabPtr->pathSep, skipLen);
	sep = strstr(p, htabPtr->pathSep);
    }
    if (*p != '\0') {
	components[depth++] = p;
    }
    components[depth] = NULL;
    *depthPtr = depth;
    *compPtrPtr = components;
    return TCL_OK;
}


static Entry *
LastEntry(htabPtr, entryPtr, mask)
    Hiertable *htabPtr;
    Entry *entryPtr;
    unsigned int mask;
{
    Blt_TreeNode next;
    Entry *nextPtr;

    next = Blt_TreeLastChild(entryPtr->node);
    while (next != NULL) {
	nextPtr = NodeToEntry(htabPtr, next);
	if ((nextPtr->flags & mask) != mask) {
	    break;
	}
	entryPtr = nextPtr;
	next = Blt_TreeLastChild(next);
    }
    return entryPtr;
}


/*
 *----------------------------------------------------------------------
 *
 * ShowEntryApplyProc --
 *
 * Results:
 *	Always returns TCL_OK.
 *
 *----------------------------------------------------------------------
 */
static int
ShowEntryApplyProc(htabPtr, entryPtr)
    Hiertable *htabPtr;
    Entry *entryPtr;
{
    entryPtr->flags &= ~ENTRY_HIDDEN;
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * HideEntryApplyProc --
 *
 * Results:
 *	Always returns TCL_OK.
 *
 *----------------------------------------------------------------------
 */
static int
HideEntryApplyProc(htabPtr, entryPtr)
    Hiertable *htabPtr;
    Entry *entryPtr;
{
    entryPtr->flags |= ENTRY_HIDDEN;
    return TCL_OK;
}

static void
MapAncestors(htabPtr, entryPtr)
    Hiertable *htabPtr;
    Entry *entryPtr;
{
    while (entryPtr != htabPtr->rootPtr) {
	entryPtr = Blt_HtParentEntry(htabPtr, entryPtr);
	if (entryPtr->flags & (ENTRY_CLOSED | ENTRY_HIDDEN)) {
	    htabPtr->flags |= HT_LAYOUT;
	    entryPtr->flags &= ~(ENTRY_CLOSED | ENTRY_HIDDEN);
	} 
    }
}


/*
 *----------------------------------------------------------------------
 *
 * MapAncestorsApplyProc --
 *
 *	If a node in mapped, then all its ancestors must be mapped also.
 *	This routine traverses upwards and maps each unmapped ancestor.
 *	It's assumed that for any mapped ancestor, all it's ancestors
 *	will already be mapped too.
 *
 * Results:
 *	Always returns TCL_OK.
 *
 *----------------------------------------------------------------------
 */
static int
MapAncestorsApplyProc(htabPtr, entryPtr)
    Hiertable *htabPtr;
    Entry *entryPtr;
{
    /*
     * Make sure that all the ancestors of this entry are mapped too.
     */
    while (entryPtr != htabPtr->rootPtr) {
	entryPtr = Blt_HtParentEntry(htabPtr, entryPtr);
	if ((entryPtr->flags & (ENTRY_HIDDEN | ENTRY_CLOSED)) == 0) {
	    break;		/* Assume ancestors are also mapped. */
	}
	entryPtr->flags &= ~(ENTRY_HIDDEN | ENTRY_CLOSED);
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * FindPath --
 *
 *	Finds the node designated by the given path.  Each path
 *	component is searched for as the tree is traversed.
 *
 *	A leading character string is trimmed off the path if it
 *	matches the one designated (see the -trimleft option).
 *
 *	If no separator is designated (see the -separator
 *	configuration option), the path is considered a Tcl list.
 *	Otherwise the each component of the path is separated by a
 *	character string.  Leading and trailing separators are
 *	ignored.  Multiple separators are treated as one.
 *
 * Results:
 *	Returns the pointer to the designated node.  If any component
 *	can't be found, NULL is returned.
 *
 *----------------------------------------------------------------------
 */
static Entry *
FindPath(htabPtr, rootPtr, path)
    Hiertable *htabPtr;
    Entry *rootPtr;
    char *path;
{
    Blt_TreeNode child;
    char **compArr;
    char *name;
    int nComp;
    register char **p;
    Entry *entryPtr;
    char *fullName;

    /* Trim off characters that we don't want */
    if (htabPtr->trimLeft != NULL) {
	register char *s1, *s2;
	
	/* Trim off leading character string if one exists. */
	for (s1 = path, s2 = htabPtr->trimLeft; *s2 != '\0'; s2++, s1++) {
	    if (*s1 != *s2) {
		break;
	    }
	}
	if (*s2 == '\0') {
	    path = s1;
	}
    }
    if (*path == '\0') {
	return rootPtr;
    }
    name = path;
    entryPtr = rootPtr;
    if (htabPtr->pathSep == SEPARATOR_NONE) {
	child = Blt_TreeFindChild(entryPtr->node, name);
	if (child == NULL) {
	    goto error;
	}
	return NodeToEntry(htabPtr, child);
    }

    if (SplitPath(htabPtr, path, &nComp, &compArr) != TCL_OK) {
	return NULL;
    }
    for (p = compArr; *p != NULL; p++) {
	name = *p;
	child = Blt_TreeFindChild(entryPtr->node, name);
	if (child == NULL) {
	    free((char *)compArr);
	    goto error;
	}
	entryPtr = NodeToEntry(htabPtr, child);
    }
    free((char *)compArr);
    return entryPtr;
 error:
    fullName = Blt_HtGetFullName(htabPtr, entryPtr, FALSE);
    Tcl_AppendResult(htabPtr->interp, "can't find node \"", name,
	     "\" in parent node \"", fullName, "\"", (char *)NULL);
    free(fullName);
    return NULL;

}

/*
 *----------------------------------------------------------------------
 *
 * NodeToString --
 *
 *	Converts a node pointer to a string representation.
 *	The string contains the node's index which is unique.
 *
 * Results:
 *	The string representation of the node is returned.  Note that
 *	the string is stored statically, so that callers must save the
 *	string before the next call to this routine overwrites the
 *	static array again.
 *
 *----------------------------------------------------------------------
 */
static char *
NodeToString(node)
    Blt_TreeNode node;
{
    static char string[200];

    sprintf(string, "%d", Blt_TreeNodeId(node));
    return string;
}


/*
 *----------------------------------------------------------------------
 *
 * GetEntry2 --
 *
 *	Converts a string into node pointer.  The string may be in one
 *	of the following forms:
 *
 *	    @x,y		- Closest node to the specified X-Y position.
 *	    NNN			- inode.
 *	    "active"		- Currently active node.
 *	    "anchor"		- anchor of selected region.
 *	    "current"		- Currently picked node in bindtable.
 *	    "focus"		- The node currently with focus.
 *	    "root"		- Root node.
 *	    "end"		- Last open node in the entire hierarchy.
 *	    "next"		- Next open node from the currently active
 *				  node. Wraps around back to top.
 *	    "last"		- Previous open node from the currently active
 *				  node. Wraps around back to bottom.
 *	    "up"		- Next open node from the currently active
 *				  node. Does not wrap around.
 *	    "down"		- Previous open node from the currently active
 *				  node. Does not wrap around.
 *	    "nextsibling"	- Next sibling of the current node.
 *	    "prevsibling"	- Previous sibling of the current node.
 *	    "parent"		- Parent of the current node.
 *	    "view.top"		- Top of viewport.
 *	    "view.bottom"	- Bottom of viewport.
 *	    @path		- Absolute path to a node.
 *
 * Results:
 *	If the string is successfully converted, TCL_OK is returned.
 *	The pointer to the node is returned via nodePtr.
 *	Otherwise, TCL_ERROR is returned and an error message is left
 *	in interpreter's result field.
 *
 *----------------------------------------------------------------------
 */
static int
GetEntry2(interp, htabPtr, string, entryPtrPtr)
    Tcl_Interp *interp;
    Hiertable *htabPtr;
    char *string;
    Entry **entryPtrPtr;
{
    Entry *fromPtr;
    Blt_TreeNode node;
    char c;
    Entry *entryPtr;
    int i;

    c = string[0];
    fromPtr = *entryPtrPtr;
    *entryPtrPtr = NULL;
    if (fromPtr == NULL) {
	fromPtr = htabPtr->focusPtr;
    }
    if (fromPtr == NULL) {
	fromPtr = htabPtr->rootPtr;
    }
    entryPtr = NULL;
    if (isdigit(UCHAR(string[0]))) {
	int inode;

	if (Tcl_GetInt(interp, string, &inode) != TCL_OK) {
	    return TCL_ERROR;
	}
	node = Blt_TreeGetNode(htabPtr->tree, inode);
	if (node != NULL) {
	    entryPtr = NodeToEntry(htabPtr, node);
	}
    } else if ((c == 'b') && (strcmp(string, "bottom") == 0)) {
	if (htabPtr->flatView) {
	    entryPtr = htabPtr->flatArr[htabPtr->nEntries - 1];
	} else {
	    entryPtr = LastEntry(htabPtr, htabPtr->rootPtr, ENTRY_MASK);
	}
    } else if ((c == 't') && (strcmp(string, "top") == 0)) {
	if (htabPtr->flatView) {
	    entryPtr = htabPtr->flatArr[0];
	} else {
	    entryPtr = htabPtr->rootPtr;
	    if (htabPtr->hideRoot) {
		entryPtr = Blt_HtNextEntry(htabPtr, entryPtr, ENTRY_MASK);
	    }
	}
    } else if ((c == 'e') && (strcmp(string, "end") == 0)) {
	entryPtr = LastEntry(htabPtr, htabPtr->rootPtr, ENTRY_MASK);
    } else if ((c == 'a') && (strcmp(string, "anchor") == 0)) {
	entryPtr = htabPtr->selAnchorPtr;
    } else if ((c == 'f') && (strcmp(string, "focus") == 0)) {
	entryPtr = htabPtr->focusPtr;
	if ((entryPtr == htabPtr->rootPtr) && (htabPtr->hideRoot)) {
	    entryPtr = Blt_HtNextEntry(htabPtr, htabPtr->rootPtr, ENTRY_MASK);
	}
    } else if ((c == 'r') && (strcmp(string, "root") == 0)) {
	entryPtr = htabPtr->rootPtr;
    } else if ((c == 'p') && (strcmp(string, "parent") == 0)) {
	if (fromPtr != htabPtr->rootPtr) {
	    entryPtr = Blt_HtParentEntry(htabPtr, fromPtr);
	}
    } else if ((c == 'c') && (strcmp(string, "current") == 0)) {
	/* Can't trust picked item, if entries have been 
	 * added or deleted. */
	if (!(htabPtr->flags & HT_DIRTY)) {
	    entryPtr = (Entry *)Blt_GetCurrentItem(htabPtr->bindTable);
	    if (entryPtr == NULL) {
		entryPtr =(Entry *)Blt_GetCurrentItem(htabPtr->buttonBindTable);
	    }
	}
    } else if ((c == 'u') && (strcmp(string, "up") == 0)) {
	entryPtr = fromPtr;
	if (htabPtr->flatView) {
	    i = entryPtr->flatIndex - 1;
	    if (i >= 0) {
		entryPtr = htabPtr->flatArr[i];
	    }
	} else {
	    entryPtr = Blt_HtPrevEntry(htabPtr, fromPtr, ENTRY_MASK);
	    if (entryPtr == NULL) {
		entryPtr = fromPtr;
	    }
	    if ((entryPtr == htabPtr->rootPtr) && (htabPtr->hideRoot)) {
		entryPtr = Blt_HtNextEntry(htabPtr, entryPtr, ENTRY_MASK);
	    }
	}
    } else if ((c == 'd') && (strcmp(string, "down") == 0)) {
	entryPtr = fromPtr;
	if (htabPtr->flatView) {
	    i = entryPtr->flatIndex + 1;
	    if (i < htabPtr->nEntries) {
		entryPtr = htabPtr->flatArr[i];
	    }
	} else {
	    entryPtr = Blt_HtNextEntry(htabPtr, fromPtr, ENTRY_MASK);
	    if (entryPtr == NULL) {
		entryPtr = fromPtr;
	    }
	    if ((entryPtr == htabPtr->rootPtr) && (htabPtr->hideRoot)) {
		entryPtr = Blt_HtNextEntry(htabPtr, entryPtr, ENTRY_MASK);
	    }
	}
    } else if (((c == 'l') && (strcmp(string, "last") == 0)) ||
	((c == 'p') && (strcmp(string, "prev") == 0))) {
	entryPtr = fromPtr;
	if (htabPtr->flatView) {
	    i = entryPtr->flatIndex - 1;
	    if (i < 0) {
		i = htabPtr->nEntries - 1;
	    }
	    entryPtr = htabPtr->flatArr[i];
	} else {
	    entryPtr = Blt_HtPrevEntry(htabPtr, fromPtr, ENTRY_MASK);
	    if (entryPtr == NULL) {
		entryPtr = LastEntry(htabPtr, htabPtr->rootPtr, ENTRY_MASK);
	    }
	    if ((entryPtr == htabPtr->rootPtr) && (htabPtr->hideRoot)) {
		entryPtr = Blt_HtNextEntry(htabPtr, entryPtr, ENTRY_MASK);
	    }
	}
    } else if ((c == 'n') && (strcmp(string, "next") == 0)) {
	entryPtr = fromPtr;
	if (htabPtr->flatView) {
	    i = entryPtr->flatIndex + 1; 
	    if (i >= htabPtr->nEntries) {
		i = 0;
	    }
	    entryPtr = htabPtr->flatArr[i];
	} else {
	    entryPtr = Blt_HtNextEntry(htabPtr, fromPtr, ENTRY_MASK);
	    if (entryPtr == NULL) {
		if (htabPtr->hideRoot) {
		    entryPtr = Blt_HtNextEntry(htabPtr, htabPtr->rootPtr, 
					       ENTRY_MASK);
		} else {
		    entryPtr = htabPtr->rootPtr;
		}
	    }
	}
    } else if ((c == 'n') && (strcmp(string, "nextsibling") == 0)) {
	node = Blt_TreeNextSibling(fromPtr->node);
	if (node != NULL) {
	    entryPtr = NodeToEntry(htabPtr, node);
	}
    } else if ((c == 'p') && (strcmp(string, "prevsibling") == 0)) {
	node = Blt_TreePrevSibling(fromPtr->node);
	if (node != NULL) {
	    entryPtr = NodeToEntry(htabPtr, node);
	}
    } else if ((c == 'v') && (strcmp(string, "view.top") == 0)) {
	if (htabPtr->nVisible > 0) {
	    entryPtr = htabPtr->visibleArr[0];
	}
    } else if ((c == 'v') && (strcmp(string, "view.bottom") == 0)) {
	if (htabPtr->nVisible > 0) {
	    entryPtr = htabPtr->visibleArr[htabPtr->nVisible - 1];
	}
    } else if (c == '@') {
	int x, y;

	if (Blt_GetXY(interp, htabPtr->tkwin, string, &x, &y) == TCL_OK) {
	    entryPtr = Blt_HtNearestEntry(htabPtr, x, y, TRUE);
	} else {
	    entryPtr = FindPath(htabPtr, htabPtr->rootPtr, string + 1);
	}
	if (entryPtr == NULL) {
	    Tcl_ResetResult(interp);
	    Tcl_AppendResult(interp, "can't find entry \"", string,
		"\" in \"", Tk_PathName(htabPtr->tkwin), "\"", (char *)NULL);
	    return TCL_ERROR;
	}
    } else {
	entryPtr = FindPath(htabPtr, htabPtr->rootPtr, string);
	if (entryPtr == NULL) {
	    Tcl_ResetResult(interp);
	    Tcl_AppendResult(interp, "can't find entry \"", string, 
		"\" in \"", Tk_PathName(htabPtr->tkwin), "\"", (char *)NULL);
	    return TCL_ERROR;
	}
    }
    *entryPtrPtr = entryPtr;
    return TCL_OK;
}

static int
StringToEntry(htabPtr, string, entryPtrPtr)
     Hiertable *htabPtr;
     char *string;
     Entry **entryPtrPtr;
{
    *entryPtrPtr = NULL;
    return GetEntry2(htabPtr->interp, htabPtr, string, entryPtrPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * Blt_HtGetNode --
 *
 *	Like GetNode but also finds nodes by serial number.
 *	If the string starts with a digit, it's converted into a
 *	number and then looked-up in a hash table.  This means that
 *	serial identifiers take precedence over node names with
 *	the contain only numbers.
 *
 * Results:
 *	If the string is successfully converted, TCL_OK is returned.
 *	The pointer to the node is returned via nodePtr.
 *	Otherwise, TCL_ERROR is returned and an error message is left
 *	in interpreter's result field.
 *
 *----------------------------------------------------------------------
 */
int
Blt_HtGetNode(htabPtr, string, nodePtr)
    Hiertable *htabPtr;
    char *string;
    Blt_TreeNode *nodePtr;
{
    Entry *entryPtr;

    if (StringToEntry(htabPtr, string, &entryPtr) != TCL_OK) {
	return TCL_ERROR;
    }
    if (entryPtr == NULL) {
	Tcl_ResetResult(htabPtr->interp);
	Tcl_AppendResult(htabPtr->interp, "can't find entry \"", string,
	    "\" in \"", Tk_PathName(htabPtr->tkwin), "\"", (char *)NULL);
	return TCL_ERROR;
    }
    *nodePtr = entryPtr->node;
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * Blt_HtGetEntry --
 *
 *	Like GetNode but also finds nodes by serial number.
 *	If the string starts with a digit, it's converted into a
 *	number and then looked-up in a hash table.  This means that
 *	serial identifiers take precedence over node names with
 *	the contain only numbers.
 *
 * Results:
 *	If the string is successfully converted, TCL_OK is returned.
 *	The pointer to the node is returned via nodePtr.
 *	Otherwise, TCL_ERROR is returned and an error message is left
 *	in interpreter's result field.
 *
 *----------------------------------------------------------------------
 */
int
Blt_HtGetEntry(htabPtr, string, entryPtrPtr)
    Hiertable *htabPtr;
    char *string;
    Entry **entryPtrPtr;
{
    Entry *entryPtr;

    if (StringToEntry(htabPtr, string, &entryPtr) != TCL_OK) {
	return TCL_ERROR;
    }
    if (entryPtr == NULL) {
	Tcl_ResetResult(htabPtr->interp);
	Tcl_AppendResult(htabPtr->interp, "can't find entry \"", string,
	    "\" in \"", Tk_PathName(htabPtr->tkwin), "\"", (char *)NULL);
	return TCL_ERROR;
    }
    *entryPtrPtr = entryPtr;
    return TCL_OK;
}

static Blt_TreeNode 
GetNthNode(parent, position)
    Blt_TreeNode parent;
    int position;
{
    Blt_TreeNode node;
    int count;

    count = 0;
    for(node = Blt_TreeFirstChild(parent); node != NULL; 
	node = Blt_TreeNextSibling(node)) {
	if (count == position) {
	    return node;
	}
    }
    return Blt_TreeLastChild(parent);
}

/*
 * Preprocess the command string for percent substitution.
 */
void
Blt_HtPercentSubst(htabPtr, entryPtr, command, dStrPtr)
    Hiertable *htabPtr;
    Entry *entryPtr;
    char *command;
    Tcl_DString *dStrPtr;
{
    register char *last, *p;
    char *fullName;

    /*
     * Get the full path name of the node, in case we need to
     * substitute for it.
     */
    fullName = Blt_HtGetFullName(htabPtr, entryPtr, TRUE);
    Tcl_DStringInit(dStrPtr);
    for (last = p = command; *p != '\0'; p++) {
	if (*p == '%') {
	    char *string;
	    char buf[3];

	    if (p > last) {
		*p = '\0';
		Tcl_DStringAppend(dStrPtr, last, -1);
		*p = '%';
	    }
	    switch (*(p + 1)) {
	    case '%':		/* Percent sign */
		string = "%";
		break;
	    case 'W':		/* Widget name */
		string = Tk_PathName(htabPtr->tkwin);
		break;
	    case 'P':		/* Full pathname */
		string = fullName;
		break;
	    case 'p':		/* Name of the node */
		string = GETLABEL(entryPtr);
		break;
	    case '#':		/* Node identifier */
		string = NodeToString(entryPtr->node);
		break;
	    default:
		if (*(p + 1) == '\0') {
		    p--;
		}
		buf[0] = *p, buf[1] = *(p + 1), buf[2] = '\0';
		string = buf;
		break;
	    }
	    Tcl_DStringAppend(dStrPtr, string, -1);
	    p++;
	    last = p + 1;
	}
    }
    if (p > last) {
	*p = '\0';
	Tcl_DStringAppend(dStrPtr, last, -1);
    }
    free(fullName);
}

/*
 *----------------------------------------------------------------------
 *
 * SelectEntryApplyProc --
 *
 *	Sets the selection flag for a node.  The selection flag is
 *	set/cleared/toggled based upon the flag set in the hierarchy
 *	widget.
 *
 * Results:
 *	Always returns TCL_OK.
 *
 *----------------------------------------------------------------------
 */
static int
SelectEntryApplyProc(htabPtr, entryPtr)
    Hiertable *htabPtr;
    Entry *entryPtr;
{
    Tcl_HashEntry *hPtr;

    switch (htabPtr->flags & SELECTION_MASK) {
    case SELECTION_CLEAR:
	Blt_HtDeselectEntry(htabPtr, entryPtr);
	break;

    case SELECTION_SET:
	Blt_HtSelectEntry(htabPtr, entryPtr);
	break;

    case SELECTION_TOGGLE:
	hPtr = Tcl_FindHashEntry(&(htabPtr->selectTable), (char *)entryPtr);
	if (hPtr != NULL) {
	    Blt_HtDeselectEntry(htabPtr, entryPtr);
	} else {
	    Blt_HtSelectEntry(htabPtr, entryPtr);
	}
	break;
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * EventuallyInvokeSelectCmd --
 *
 *      Queues a request to execute the -selectcommand code associated
 *      with the widget at the next idle point.  Invoked whenever the
 *      selection changes.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      Tcl code gets executed for some application-specific task.
 *
 *----------------------------------------------------------------------
 */
static void
EventuallyInvokeSelectCmd(htabPtr)
    Hiertable *htabPtr;
{
    if (!(htabPtr->flags & SELECTION_PENDING)) {
	htabPtr->flags |= SELECTION_PENDING;
	Tcl_DoWhenIdle(Blt_HtSelectCmdProc, (ClientData)htabPtr);
    }
}


void
Blt_HtPruneSelection(htabPtr, rootPtr)
    Hiertable *htabPtr;
    Entry *rootPtr;
{
    Blt_ChainLink *linkPtr, *nextPtr;
    Entry *entryPtr;

    for (linkPtr = Blt_ChainFirstLink(htabPtr->selectPtr); linkPtr != NULL; 
	linkPtr = nextPtr) {
	nextPtr = Blt_ChainNextLink(linkPtr);
	entryPtr = (Entry *)Blt_ChainGetValue(linkPtr);
	if (Blt_TreeIsAncestor(rootPtr->node, entryPtr->node)) {
	    Blt_HtDeselectEntry(htabPtr, entryPtr);
	}
    }
    Blt_HtEventuallyRedraw(htabPtr);
    if (htabPtr->selectCmd != NULL) {
	EventuallyInvokeSelectCmd(htabPtr);
    }
}


/*
 * --------------------------------------------------------------
 *
 * Hiertable operations
 *
 * --------------------------------------------------------------
 */

/*ARGSUSED*/
static int
FocusOp(htabPtr, interp, argc, argv)
    Hiertable *htabPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    if (argc == 3) {
	Entry *entryPtr;

	if (StringToEntry(htabPtr, argv[2], &entryPtr) != TCL_OK) {
	    return TCL_ERROR;
	}
	if ((entryPtr != NULL) && (entryPtr != htabPtr->focusPtr)) {
	    if (entryPtr->flags & ENTRY_HIDDEN) {
		/* Doesn't make sense to set focus to a node you can't see. */
		MapAncestors(htabPtr, entryPtr);
	    }
	    /* Changing focus can only affect the visible entries.  The
	     * entry layout stays the same. */
	    if (htabPtr->focusPtr != NULL) {
		htabPtr->focusPtr->flags |= ENTRY_REDRAW;
	    } 
	    entryPtr->flags |= ENTRY_REDRAW;
	    htabPtr->flags |= HT_SCROLL;
	    htabPtr->focusPtr = entryPtr;
	}
	Blt_HtEventuallyRedraw(htabPtr);
    }
    Blt_SetFocusItem(htabPtr->bindTable, htabPtr->focusPtr);
    if (htabPtr->focusPtr != NULL) {
	Tcl_SetResult(interp, NodeToString(htabPtr->focusPtr->node),
	    TCL_VOLATILE);
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * BboxOp --
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
BboxOp(htabPtr, interp, argc, argv)
    Hiertable *htabPtr;
    Tcl_Interp *interp;
    int argc;			/* Not used. */
    char **argv;
{
    register int i;
    Entry *entryPtr;
    char string[200];
    int width, height, yBot;
    int left, top, right, bottom;
    int screen;
    int lWidth;

    if (htabPtr->flags & HT_LAYOUT) {
	/*
	 * The layout is dirty.  Recompute it now, before we use the
	 * world dimensions.  But remember, the "bbox" operation isn't
	 * valid for hidden entries (since they're not visible, they
	 * don't have world coordinates).
	 */
	Blt_HtComputeLayout(htabPtr);
    }
    left = htabPtr->worldWidth;
    top = htabPtr->worldHeight;
    right = bottom = 0;

    screen = FALSE;
    if ((argv[2][0] == '-') && (strcmp(argv[2], "-screen") == 0)) {
	screen = TRUE;
	argc--, argv++;
    }
    for (i = 2; i < argc; i++) {
	if ((argv[i][0] == 'a') && (strcmp(argv[i], "all") == 0)) {
	    left = top = 0;
	    right = htabPtr->worldWidth;
	    bottom = htabPtr->worldHeight;
	    break;
	}
	if (StringToEntry(htabPtr, argv[i], &entryPtr) != TCL_OK) {
	    return TCL_ERROR;
	}
	if (entryPtr == NULL) {
	    continue;
	}
	if (entryPtr->flags & ENTRY_HIDDEN) {
	    continue;
	}
	yBot = entryPtr->worldY + entryPtr->height;
	height = VPORTHEIGHT(htabPtr);
	if ((yBot <= htabPtr->yOffset) &&
	    (entryPtr->worldY >= (htabPtr->yOffset + height))) {
	    continue;
	}
	if (bottom < yBot) {
	    bottom = yBot;
	}
	if (top > entryPtr->worldY) {
	    top = entryPtr->worldY;
	}
	lWidth = ICONWIDTH(DEPTH(htabPtr, entryPtr->node));
	if (right < (entryPtr->worldX + entryPtr->width + lWidth)) {
	    right = (entryPtr->worldX + entryPtr->width + lWidth);
	}
	if (left > entryPtr->worldX) {
	    left = entryPtr->worldX;
	}
    }

    if (screen) {
	width = VPORTWIDTH(htabPtr);
	height = VPORTHEIGHT(htabPtr);
	/*
	 * Do a min-max text for the intersection of the viewport and
	 * the computed bounding box.  If there is no intersection, return
	 * the empty string.
	 */
	if ((right < htabPtr->xOffset) || (bottom < htabPtr->yOffset) ||
	    (left >= (htabPtr->xOffset + width)) ||
	    (top >= (htabPtr->yOffset + height))) {
	    return TCL_OK;
	}
	/* Otherwise clip the coordinates at the view port boundaries. */
	if (left < htabPtr->xOffset) {
	    left = htabPtr->xOffset;
	} else if (right > (htabPtr->xOffset + width)) {
	    right = htabPtr->xOffset + width;
	}
	if (top < htabPtr->yOffset) {
	    top = htabPtr->yOffset;
	} else if (bottom > (htabPtr->yOffset + height)) {
	    bottom = htabPtr->yOffset + height;
	}
	left = SCREENX(htabPtr, left), top = SCREENY(htabPtr, top);
	right = SCREENX(htabPtr, right), bottom = SCREENY(htabPtr, bottom);
    }
    if ((left < right) && (top < bottom)) {
	sprintf(string, "%d %d %d %d", left, top, right - left, bottom - top);
	Tcl_SetResult(interp, string, TCL_VOLATILE);
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ButtonActivateOp --
 *
 *	Selects the button to appear active.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
ButtonActivateOp(htabPtr, interp, argc, argv)
    Hiertable *htabPtr;
    Tcl_Interp *interp;
    int argc;			/* Not used. */
    char **argv;
{
    Entry *oldPtr, *newPtr;

    if (argv[3][0] == '\0') {
	newPtr = NULL;
    } else if (StringToEntry(htabPtr, argv[3], &newPtr) != TCL_OK) {
	return TCL_ERROR;
    }
    if (htabPtr->treeColumnPtr->hidden) {
	return TCL_OK;
    }
    if ((newPtr != NULL) && !(newPtr->flags & ENTRY_BUTTON)) {
	newPtr = NULL;
    }
    oldPtr = htabPtr->activeButtonPtr;
    htabPtr->activeButtonPtr = newPtr;
    if (newPtr != oldPtr) {
	Drawable drawable;

	drawable = Tk_WindowId(htabPtr->tkwin);
	if ((oldPtr != NULL) && (oldPtr != htabPtr->rootPtr)) {
	    Blt_HtDrawButton(htabPtr, oldPtr, drawable);
	}
	if ((newPtr != NULL) && (newPtr != htabPtr->rootPtr)) {
	    Blt_HtDrawButton(htabPtr, newPtr, drawable);
	}
	Blt_HtDrawOuterBorders(htabPtr, drawable);
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ButtonBindOp --
 *
 *	  .t bind tag sequence command
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
ButtonBindOp(htabPtr, interp, argc, argv)
    Hiertable *htabPtr;
    Tcl_Interp *interp;
    int argc;			/* Not used. */
    char **argv;
{
    ClientData object;
    int inode;
    /*
     * Individual entries are selected by inode only.  All other
     * strings are interpreted as a binding tag.  For example, if one
     * binds to "focus", it is assumed that this refers to a bind tag,
     * not the entry with focus.
     */
    if (Tcl_GetInt(NULL, argv[3], &inode) != TCL_OK) {
	object = (ClientData)Blt_HtGetUid(htabPtr, argv[3]);
    } else {
	Blt_TreeNode node;

	node = Blt_TreeGetNode(htabPtr->tree, inode);
	object = (ClientData)node;
    }
    return Blt_ConfigureBindings(interp, htabPtr->buttonBindTable, object,
	argc - 4, argv + 4);
}

/*
 *----------------------------------------------------------------------
 *
 * ButtonCgetOp --
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
ButtonCgetOp(htabPtr, interp, argc, argv)
    Hiertable *htabPtr;
    Tcl_Interp *interp;
    int argc;			/* Not used. */
    char **argv;
{
    return Tk_ConfigureValue(interp, htabPtr->tkwin, htabPtr->buttonSpecs, 
	(char *)htabPtr, argv[3], 0);
}

/*
 *----------------------------------------------------------------------
 *
 * ButtonConfigureOp --
 *
 * 	This procedure is called to process a list of configuration
 *	options database, in order to reconfigure the one of more
 *	entries in the widget.
 *
 *	  .h button configure option value
 *
 * Results:
 *	A standard Tcl result.  If TCL_ERROR is returned, then
 *	interp->result contains an error message.
 *
 * Side effects:
 *	Configuration information, such as text string, colors, font,
 *	etc. get set for htabPtr; old resources get freed, if there
 *	were any.  The hypertext is redisplayed.
 *
 *----------------------------------------------------------------------
 */
static int
ButtonConfigureOp(htabPtr, interp, argc, argv)
    Hiertable *htabPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    if (argc == 3) {
	return Tk_ConfigureInfo(interp, htabPtr->tkwin, htabPtr->buttonSpecs, 
		(char *)htabPtr, (char *)NULL, 0);
    } else if (argc == 4) {
	return Tk_ConfigureInfo(interp, htabPtr->tkwin, htabPtr->buttonSpecs, 
		(char *)htabPtr, argv[3], 0);
    }
    if (Tk_ConfigureWidget(htabPtr->interp, htabPtr->tkwin, 
		htabPtr->buttonSpecs, argc - 3, argv + 3, (char *)htabPtr, 
		TK_CONFIG_ARGV_ONLY) != TCL_OK) {
	return TCL_ERROR;
    }
    Blt_HtConfigureButtons(htabPtr);
    Blt_HtEventuallyRedraw(htabPtr);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ButtonOp --
 *
 *	This procedure handles button operations.
 *
 * Results:
 *	A standard Tcl result.
 *
 *----------------------------------------------------------------------
 */
static Blt_OpSpec buttonOps[] =
{
    {"activate", 1, (Blt_OpProc)ButtonActivateOp, 4, 4, "index",},
    {"bind", 1, (Blt_OpProc)ButtonBindOp, 4, 6,
	"tagName ?sequence command?",},
    {"cget", 2, (Blt_OpProc)ButtonCgetOp, 4, 4, "option",},
    {"configure", 2, (Blt_OpProc)ButtonConfigureOp, 3, 0,
	"?option value?...",},
    {"highlight", 1, (Blt_OpProc)ButtonActivateOp, 4, 4, "index",},
};

static int nButtonOps = sizeof(buttonOps) / sizeof(Blt_OpSpec);

static int
ButtonOp(htabPtr, interp, argc, argv)
    Hiertable *htabPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    Blt_OpProc opProc;
    int result;

    opProc = Blt_GetOperation(interp, nButtonOps, buttonOps, BLT_OPER_ARG2, 
	argc, argv, 0);
    if (opProc == NULL) {
	return TCL_ERROR;
    }
    result = (*opProc) (htabPtr, interp, argc, argv);
    return result;
}

/*
 *----------------------------------------------------------------------
 *
 * CgetOp --
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
CgetOp(htabPtr, interp, argc, argv)
    Hiertable *htabPtr;
    Tcl_Interp *interp;
    int argc;			/* Not used. */
    char **argv;
{
    return Tk_ConfigureValue(interp, htabPtr->tkwin, htabPtr->widgetSpecs,
	(char *)htabPtr, argv[2], 0);
}

/*ARGSUSED*/
static int
CloseOp(htabPtr, interp, argc, argv)
    Hiertable *htabPtr;
    Tcl_Interp *interp;		/* Not used. */
    int argc;
    char **argv;
{
    Entry *entryPtr;
    int recurse, result;
    int length;
    register int i;

    recurse = FALSE;
    length = strlen(argv[2]);
    if ((argv[2][0] == '-') && (length > 1) &&
	(strncmp(argv[2], "-recurse", length) == 0)) {
	argv++, argc--;
	recurse = TRUE;
    }
    for (i = 2; i < argc; i++) {
	if (StringToEntry(htabPtr, argv[i], &entryPtr) != TCL_OK) {
	    return TCL_ERROR;
	}
	if (entryPtr == NULL) {
	    continue;
	}
	/* 
	 * Clear the selections for any entries that may have become
	 * hidden by closing the node.  
	 */
	Blt_HtPruneSelection(htabPtr, entryPtr);

	/*
	 * -----------------------------------------------------------
	 *
	 *  Check if either the "focus" entry or selection anchor
	 *  is in this hierarchy.  Must move it or disable it before
	 *  we close the node.  Otherwise it may be deleted by a Tcl
	 *  "close" script, and we'll be left pointing to a bogus
	 *  memory location.
	 *
	 * -----------------------------------------------------------
	 */
	if ((htabPtr->focusPtr != NULL) && 
	    (Blt_TreeIsAncestor(entryPtr->node, htabPtr->focusPtr->node))) {
	    htabPtr->focusPtr = entryPtr;
	    Blt_SetFocusItem(htabPtr->bindTable, htabPtr->focusPtr);
	}
	if ((htabPtr->selAnchorPtr != NULL) && 
	    (Blt_TreeIsAncestor(entryPtr->node, htabPtr->selAnchorPtr->node))) {
	    htabPtr->selAnchorPtr = NULL;
	}
	if ((htabPtr->activePtr != NULL) && 
	    (Blt_TreeIsAncestor(entryPtr->node, htabPtr->activePtr->node))) {
	    htabPtr->activePtr = entryPtr;
	}
	if (recurse) {
	    result = Blt_HtTreeApply(htabPtr, entryPtr, Blt_HtCloseEntry, 0);
	} else {
	    result = Blt_HtCloseEntry(htabPtr, entryPtr);
	}
	if (result != TCL_OK) {
	    return TCL_ERROR;
	}	
    }
    /* Closing a node may affect the visible entries but not the 
     * the world layout of the entries. */
    /*FIXME: This is only for flattened entries.  */
    htabPtr->flags |= (HT_LAYOUT | HT_DIRTY);
    Blt_HtEventuallyRedraw(htabPtr);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ConfigureOp --
 *
 * 	This procedure is called to process an argv/argc list, plus
 *	the Tk option database, in order to configure (or reconfigure)
 *	the widget.
 *
 * Results:
 *	A standard Tcl result.  If TCL_ERROR is returned, then
 *	interp->result contains an error message.
 *
 * Side effects:
 *	Configuration information, such as text string, colors, font,
 *	etc. get set for htabPtr; old resources get freed, if there
 *	were any.  The widget is redisplayed.
 *
 *----------------------------------------------------------------------
 */
static int
ConfigureOp(htabPtr, interp, argc, argv)
    Hiertable *htabPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    if (argc == 2) {
	return Tk_ConfigureInfo(interp, htabPtr->tkwin, htabPtr->widgetSpecs, 
		(char *)htabPtr, (char *)NULL, 0);
    } else if (argc == 3) {
	return Tk_ConfigureInfo(interp, htabPtr->tkwin, htabPtr->widgetSpecs, 
		(char *)htabPtr, argv[2], 0);
    }
    bltHiertableLastInstance = htabPtr;
    if (Blt_HtConfigureHiertable(interp, htabPtr, argc - 2, argv + 2, 
	TK_CONFIG_ARGV_ONLY) != TCL_OK) {
	return TCL_ERROR;
    }
    Blt_HtEventuallyRedraw(htabPtr);
    return TCL_OK;
}


/*ARGSUSED*/
static int
CurselectionOp(htabPtr, interp, argc, argv)
    Hiertable *htabPtr;
    Tcl_Interp *interp;		/* Not used. */
    int argc;			/* Not used. */
    char **argv;		/* Not used. */
{
    Entry *entryPtr;

    if (htabPtr->sortSelection) {
	Blt_ChainLink *linkPtr;

	for (linkPtr = Blt_ChainFirstLink(htabPtr->selectPtr); linkPtr != NULL;
		linkPtr = Blt_ChainNextLink(linkPtr)) {
	    entryPtr = (Entry *)Blt_ChainGetValue(linkPtr);
	    Tcl_AppendElement(interp, NodeToString(entryPtr->node));
	}
    } else {
	for (entryPtr = htabPtr->rootPtr; entryPtr != NULL; 
	     entryPtr = Blt_HtNextEntry(htabPtr, entryPtr, ENTRY_MASK)) {
	    if (Blt_HtEntryIsSelected(htabPtr, entryPtr)) {
		Tcl_AppendElement(interp, NodeToString(entryPtr->node));
	    }
	}
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * BindOp --
 *
 *	  .t bind index sequence command
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
BindOp(htabPtr, interp, argc, argv)
    Hiertable *htabPtr;
    Tcl_Interp *interp;
    int argc;			/* Not used. */
    char **argv;
{
    ClientData object;
    int inode;

    /*
     * Individual entries are selected by inode only.  All other strings
     * are interpreted as a binding tag.
     */
    if (Tcl_GetInt(NULL, argv[2], &inode) != TCL_OK) {
	object = (ClientData)Blt_HtGetUid(htabPtr, argv[2]);
    } else {
	Blt_TreeNode node;

	node = Blt_TreeGetNode(htabPtr->tree, inode);
	object = (ClientData)node;
    }
    return Blt_ConfigureBindings(interp, htabPtr->bindTable, object, argc - 3, 
	argv + 3);
}

/*
 *----------------------------------------------------------------------
 *
 * EntryActivateOp --
 *
 *	Selects the entry to appear active.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
EntryActivateOp(htabPtr, interp, argc, argv)
    Hiertable *htabPtr;
    Tcl_Interp *interp;
    int argc;			/* Not used. */
    char **argv;
{
    Entry *newPtr, *oldPtr;

    if (argv[3][0] == '\0') {
	newPtr = NULL;
    } else if (StringToEntry(htabPtr, argv[3], &newPtr) != TCL_OK) {
	return TCL_ERROR;
    }
    if (htabPtr->treeColumnPtr->hidden) {
	return TCL_OK;
    }
    oldPtr = htabPtr->activePtr;
    htabPtr->activePtr = newPtr;
    if (newPtr != oldPtr) {
	if (htabPtr->flags & HT_DIRTY) {
	    Blt_HtEventuallyRedraw(htabPtr);
	} else {
	    Drawable drawable;
	    int x, y;

	    drawable = Tk_WindowId(htabPtr->tkwin);
	    if (oldPtr != NULL) {
		x = SCREENX(htabPtr, oldPtr->worldX);
		if (!htabPtr->flatView) {
		    x += ICONWIDTH(DEPTH(htabPtr, oldPtr->node));
		}
		y = SCREENY(htabPtr, oldPtr->worldY);
		oldPtr->flags |= ENTRY_ICON;
		Blt_HtDrawIcon(htabPtr, oldPtr, x, y, drawable);
	    }
	    if (newPtr != NULL) {
		x = SCREENX(htabPtr, newPtr->worldX);
		if (!htabPtr->flatView) {
		    x += ICONWIDTH(DEPTH(htabPtr, newPtr->node));
		}
		y = SCREENY(htabPtr, newPtr->worldY);
		newPtr->flags |= ENTRY_ICON;
		Blt_HtDrawIcon(htabPtr, newPtr, x, y, drawable);
	    }
	}
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * EntryCgetOp --
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
EntryCgetOp(htabPtr, interp, argc, argv)
    Hiertable *htabPtr;
    Tcl_Interp *interp;
    int argc;			/* Not used. */
    char **argv;
{
    Entry *entryPtr;
    int result;

    if (Blt_HtGetEntry(htabPtr, argv[3], &entryPtr) != TCL_OK) {
	return TCL_ERROR;
    }
    result = Tk_ConfigureValue(interp, htabPtr->tkwin, 
	htabPtr->entrySpecs, (char *)entryPtr, argv[4], 0);
    return result;
}

/*
 *----------------------------------------------------------------------
 *
 * EntryConfigureOp --
 *
 * 	This procedure is called to process a list of configuration
 *	options database, in order to reconfigure the one of more
 *	entries in the widget.
 *
 *	  .h entryconfigure node node node node option value
 *
 * Results:
 *	A standard Tcl result.  If TCL_ERROR is returned, then
 *	interp->result contains an error message.
 *
 * Side effects:
 *	Configuration information, such as text string, colors, font,
 *	etc. get set for htabPtr; old resources get freed, if there
 *	were any.  The hypertext is redisplayed.
 *
 *----------------------------------------------------------------------
 */
static int
EntryConfigureOp(htabPtr, interp, argc, argv)
    Hiertable *htabPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    int nIds, nOpts;
    char **options;
    register int i;
    Entry *entryPtr;
    int result;

    /* Figure out where the option value pairs begin */
    argc -= 3, argv += 3;
    for (i = 0; i < argc; i++) {
	if (argv[i][0] == '-') {
	    break;
	}
	if (Blt_HtGetEntry(htabPtr, argv[i], &entryPtr) != TCL_OK) {
	    return TCL_ERROR;	/* Can't find node. */
	}
    }
    nIds = i;			/* Number of element names specified */
    nOpts = argc - i;		/* Number of options specified */
    options = argv + i;		/* Start of options in argv  */

    for (i = 0; i < nIds; i++) {
	Blt_HtGetEntry(htabPtr, argv[i], &entryPtr);
	if (nOpts == 0) {
	    return Tk_ConfigureInfo(interp, htabPtr->tkwin, 
		htabPtr->entrySpecs, (char *)entryPtr, (char *)NULL, 0);
	} else if (nOpts == 1) {
	    return Tk_ConfigureInfo(interp, htabPtr->tkwin, 
		htabPtr->entrySpecs, (char *)entryPtr, options[0], 0);
	}
	bltHiertableLastInstance = htabPtr;
	result = Tk_ConfigureWidget(htabPtr->interp, htabPtr->tkwin, 
		htabPtr->entrySpecs, nOpts, options, (char *)entryPtr, 
		TK_CONFIG_ARGV_ONLY);
	if (result != TCL_OK) {
	    return TCL_ERROR;
	}
	Blt_HtConfigureEntry(htabPtr, entryPtr);
	if (Blt_ConfigModified(htabPtr->entrySpecs, "-font", (char *)NULL)) {
	    htabPtr->flags |= HT_UPDATE;
	}
    }
    htabPtr->flags |= (HT_DIRTY | HT_LAYOUT | HT_SCROLL);
    Blt_HtEventuallyRedraw(htabPtr);
    return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * EntryIsOpenOp --
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
EntryIsBeforeOp(htabPtr, interp, argc, argv)
    Hiertable *htabPtr;
    Tcl_Interp *interp;
    int argc;			/* Not used. */
    char **argv;
{
    Entry *e1Ptr, *e2Ptr;

    if ((Blt_HtGetEntry(htabPtr, argv[3], &e1Ptr) != TCL_OK) ||
	(Blt_HtGetEntry(htabPtr, argv[4], &e2Ptr) != TCL_OK)) {
	return TCL_ERROR;
    }
    Tcl_SetResult(interp, 
	  Blt_TreeIsBefore(e1Ptr->node, e2Ptr->node) ? "1" : "0", 
	  TCL_STATIC);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * EntryIsHiddenOp --
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
EntryIsHiddenOp(htabPtr, interp, argc, argv)
    Hiertable *htabPtr;
    Tcl_Interp *interp;
    int argc;			/* Not used. */
    char **argv;
{
    Entry *entryPtr;

    if (Blt_HtGetEntry(htabPtr, argv[3], &entryPtr) != TCL_OK) {
	return TCL_ERROR;
    }
    Tcl_SetResult(interp, (entryPtr->flags & ENTRY_HIDDEN) ? "1" : "0", 
	TCL_STATIC);
    return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * EntryIsOpenOp --
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
EntryIsOpenOp(htabPtr, interp, argc, argv)
    Hiertable *htabPtr;
    Tcl_Interp *interp;
    int argc;			/* Not used. */
    char **argv;
{
    Entry *entryPtr;

    if (Blt_HtGetEntry(htabPtr, argv[3], &entryPtr) != TCL_OK) {
	return TCL_ERROR;
    }
    Tcl_SetResult(interp, (entryPtr->flags & ENTRY_CLOSED) ? "0" : "1", 
	TCL_STATIC);
    return TCL_OK;
}

/*ARGSUSED*/
static int
EntryChildrenOp(htabPtr, interp, argc, argv)
    Hiertable *htabPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    Entry *parentPtr;

    if (Blt_HtGetEntry(htabPtr, argv[3], &parentPtr) != TCL_OK) {
	return TCL_ERROR;
    }
    if (argc == 4) {
	Blt_TreeNode node;

	for (node = Blt_TreeFirstChild(parentPtr->node); node != NULL;
	    node = Blt_TreeNextSibling(node)) {
	    Tcl_AppendElement(interp, NodeToString(node));
	}
    } else if (argc == 6) {
	Blt_TreeNode first, last;
	Entry *entryPtr, *lastPtr, *firstPtr;
	int firstPos, lastPos;
	int nNodes;

	if ((Blt_GetPosition(interp, argv[4], &firstPos) != TCL_OK) ||
	    (Blt_GetPosition(interp, argv[5], &lastPos) != TCL_OK)) {
	    return TCL_ERROR;
	}
	nNodes = Blt_TreeNodeDegree(parentPtr->node);
	if (nNodes == 0) {
	    return TCL_OK;
	}
	if ((lastPos == END) || (lastPos >= nNodes)) {
	    last = Blt_TreeLastChild(parentPtr->node);
	} else {
	    last = GetNthNode(parentPtr->node, lastPos);
	}
	if ((firstPos == END) || (firstPos >= nNodes)) {
	    first = Blt_TreeLastChild(parentPtr->node);
	} else {
	    first = GetNthNode(parentPtr->node, firstPos);
	}
	firstPtr = NodeToEntry(htabPtr, first);
	lastPtr = NodeToEntry(htabPtr, last);
	if ((lastPos != END) && (firstPos > lastPos)) {
	    for (entryPtr = lastPtr; entryPtr != NULL; 
		entryPtr = Blt_HtPrevEntry(htabPtr, entryPtr, 0)) {
		Tcl_AppendElement(interp, NodeToString(entryPtr->node));
		if (entryPtr == firstPtr) {
		    break;
		}
	    }
	} else {
	    for (entryPtr = firstPtr; entryPtr != NULL; 
		  entryPtr = Blt_HtNextEntry(htabPtr, entryPtr, 0)) {
		Tcl_AppendElement(interp, NodeToString(entryPtr->node));
		if (entryPtr == lastPtr) {
		    break;
		}
	    }
	}
    } else {
	Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " ",
	    argv[1], " ", argv[2], " index ?first last?", (char *)NULL);
	return TCL_ERROR;
    }
    return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * EntryDeleteOp --
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
EntryDeleteOp(htabPtr, interp, argc, argv)
    Hiertable *htabPtr;
    Tcl_Interp *interp;
    int argc;			/* Not used. */
    char **argv;
{
    Entry *entryPtr;

    if (Blt_HtGetEntry(htabPtr, argv[3], &entryPtr) != TCL_OK) {
	return TCL_ERROR;
    }
    if (argc == 5) {
	int entryPos;
	Blt_TreeNode node;
	/*
	 * Delete a single child node from a hierarchy specified 
	 * by its numeric position.
	 */
	if (Blt_GetPosition(interp, argv[3], &entryPos) != TCL_OK) {
	    return TCL_ERROR;
	}
	if (entryPos >= Blt_TreeNodeDegree(entryPtr->node)) {
	    return TCL_OK;	/* Bad first index */
	}
	if (entryPos == END) {
	    node = Blt_TreeLastChild(entryPtr->node);
	} else {
	    node = GetNthNode(entryPtr->node, entryPos);
	}
	Blt_TreeDeleteNode(htabPtr->tree, node);
    } else {
	int firstPos, lastPos;
	Blt_TreeNode node, first, last, next;
	int nEntries;
	/*
	 * Delete range of nodes in hierarchy specified by first/last
	 * positions.
	 */
	if ((Blt_GetPosition(interp, argv[4], &firstPos) != TCL_OK) ||
	    (Blt_GetPosition(interp, argv[5], &lastPos) != TCL_OK)) {
	    return TCL_ERROR;
	}
	nEntries = Blt_TreeNodeDegree(entryPtr->node);
	if (nEntries == 0) {
	    return TCL_OK;
	}
	if (firstPos == END) {
	    firstPos = nEntries - 1;
	}
	if (firstPos >= nEntries) {
	    Tcl_AppendResult(interp, "first position \"", argv[4],
			     " is out of range", (char *)NULL);
	    return TCL_ERROR;
	}
	if ((lastPos == END) || (lastPos >= nEntries)) {
	    lastPos = nEntries - 1;
	}
	if (firstPos > lastPos) {
	    Tcl_AppendResult(interp, "bad range: \"", argv[4], " > ", argv[5],
			     "\"", (char *)NULL);
	    return TCL_ERROR;
	}
	first = GetNthNode(entryPtr->node, firstPos);
	last = GetNthNode(entryPtr->node, lastPos);
	for (node = first; node != NULL; node = next) {
	    next = Blt_TreeNextSibling(node);
	    Blt_TreeDeleteNode(htabPtr->tree, node);
	    if (node == last) {
		break;
	    }
	}
    }
    htabPtr->flags |= (HT_LAYOUT | HT_DIRTY);
    Blt_HtEventuallyRedraw(htabPtr);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * EntrySizeOp --
 *
 *	Counts the number of entries at this node.
 *
 * Results:
 *	A standard Tcl result.  If an error occurred TCL_ERROR is
 *	returned and interp->result will contain an error message.
 *	Otherwise, TCL_OK is returned and interp->result contains
 *	the number of entries.
 *
 *----------------------------------------------------------------------
 */
static int
EntrySizeOp(htabPtr, interp, argc, argv)
    Hiertable *htabPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    Entry *entryPtr;
    int length, sum, recurse;

    recurse = FALSE;
    length = strlen(argv[3]);
    if ((argv[3][0] == '-') && (length > 1) &&
	(strncmp(argv[3], "-recurse", length) == 0)) {
	argv++, argc--;
	recurse = TRUE;
    }
    if (argc == 3) {
	Tcl_AppendResult(interp, "missing node argument: should be \"",
	    argv[0], " entry open node\"", (char *)NULL);
	return TCL_ERROR;
    }
    if (Blt_HtGetEntry(htabPtr, argv[3], &entryPtr) != TCL_OK) {
	return TCL_ERROR;
    }
    if (recurse) {
	sum = Blt_TreeSize(entryPtr->node);
    } else {
	sum = Blt_TreeNodeDegree(entryPtr->node);
    }
    Tcl_SetResult(interp, Blt_Itoa(sum), TCL_VOLATILE);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * EntryOp --
 *
 *	This procedure handles entry operations.
 *
 * Results:
 *	A standard Tcl result.
 *
 *----------------------------------------------------------------------
 */

static Blt_OpSpec entryOps[] =
{
    {"activate", 1, (Blt_OpProc)EntryActivateOp, 4, 4, "index",},
    /*bbox*/
    /*bind*/
    {"cget", 2, (Blt_OpProc)EntryCgetOp, 5, 5, "index option",},
    {"children", 2, (Blt_OpProc)EntryChildrenOp, 4, 6, 
	"index firstPos lastPos",},
    /*close*/
    {"configure", 2, (Blt_OpProc)EntryConfigureOp, 4, 0,
	"index ?index...? ?option value?...",},
    {"delete", 2, (Blt_OpProc)EntryDeleteOp, 5, 6, "index firstPos ?lastPos?",},
    /*focus*/
    /*hide*/
    {"highlight", 1, (Blt_OpProc)EntryActivateOp, 4, 4, "index",},
    /*index*/
    {"isbefore", 3, (Blt_OpProc)EntryIsBeforeOp, 5, 5, "index index",},
    {"ishidden", 3, (Blt_OpProc)EntryIsHiddenOp, 4, 4, "index",},
    {"isopen", 3, (Blt_OpProc)EntryIsOpenOp, 4, 4, "index",},
    /*move*/
    /*nearest*/
    /*open*/
    /*see*/
    /*show*/
    {"size", 1, (Blt_OpProc)EntrySizeOp, 4, 5, "?-recurse? index",},
    /*toggle*/
};
static int nEntryOps = sizeof(entryOps) / sizeof(Blt_OpSpec);

static int
EntryOp(htabPtr, interp, argc, argv)
    Hiertable *htabPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    Blt_OpProc opProc;
    int result;

    opProc = Blt_GetOperation(interp, nEntryOps, entryOps, BLT_OPER_ARG2, 
	argc, argv, 0);
    if (opProc == NULL) {
	return TCL_ERROR;
    }
    result = (*opProc) (htabPtr, interp, argc, argv);
    return result;
}

/*ARGSUSED*/
static int
ExactCompare(interp, name, pattern)
    Tcl_Interp *interp;		/* Not used. */
    char *name;
    char *pattern;
{
    return (strcmp(name, pattern) == 0);
}

/*ARGSUSED*/
static int
GlobCompare(interp, name, pattern)
    Tcl_Interp *interp;		/* Not used. */
    char *name;
    char *pattern;
{
    return Tcl_StringMatch(name, pattern);
}

static int
RegexpCompare(interp, name, pattern)
    Tcl_Interp *interp;
    char *name;
    char *pattern;
{
    return Tcl_RegExpMatch(interp, name, pattern);
}

/*
 *----------------------------------------------------------------------
 *
 * FindOp --
 *
 *	Find one or more nodes based upon the pattern provided.
 *
 * Results:
 *	A standard Tcl result.  The interpreter result will contain a
 *	list of the node serial identifiers.
 *
 *----------------------------------------------------------------------
 */
static int
FindOp(htabPtr, interp, argc, argv)
    Hiertable *htabPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    Entry *firstPtr, *lastPtr;
    int nMatches, maxMatches;
    char c;
    int length;
    CompareProc *compareProc;
    IterProc *nextProc;
    int invertMatch;		/* normal search mode (matching entries) */
    char *namePattern, *fullPattern;
    char *execCmd;
    register int i;
    int result;
    char *pattern, *option, *value;
    Tcl_DString dString;
    Blt_List optionList;
    Blt_ListNode node;
    register Entry *entryPtr;

    invertMatch = FALSE;
    maxMatches = 0;
    execCmd = namePattern = fullPattern = NULL;
    compareProc = ExactCompare;
    nextProc = Blt_HtNextEntry;
    optionList = Blt_ListCreate(TCL_STRING_KEYS);

    entryPtr = htabPtr->rootPtr;
    /*
     * Step 1:  Process flags for find operation.
     */
    for (i = 2; i < argc; i++) {
	if (argv[i][0] != '-') {
	    break;
	}
	option = argv[i] + 1;
	length = strlen(option);
	c = option[0];
	if ((c == 'e') && (length > 2) &&
	    (strncmp(option, "exact", length) == 0)) {
	    compareProc = ExactCompare;
	} else if ((c == 'g') && (strncmp(option, "glob", length) == 0)) {
	    compareProc = GlobCompare;
	} else if ((c == 'r') && (strncmp(option, "regexp", length) == 0)) {
	    compareProc = RegexpCompare;
	} else if ((c == 'n') && (length > 1) &&
	    (strncmp(option, "nonmatching", length) == 0)) {
	    invertMatch = TRUE;
	} else if ((c == 'n') && (length > 1) &&
	    (strncmp(option, "name", length) == 0)) {
	    if ((i + 1) == argc) {
		goto missingArg;
	    }
	    i++;
	    namePattern = argv[i];
	} else if ((c == 'f') && (strncmp(option, "full", length) == 0)) {
	    if ((i + 1) == argc) {
		goto missingArg;
	    }
	    i++;
	    fullPattern = argv[i];
	} else if ((c == 'e') && (length > 2) &&
	    (strncmp(option, "exec", length) == 0)) {
	    if ((i + 1) == argc) {
		goto missingArg;
	    }
	    i++;
	    execCmd = argv[i];
	} else if ((c == 'c') && (strncmp(option, "count", length) == 0)) {
	    if ((i + 1) == argc) {
		goto missingArg;
	    }
	    i++;
	    if (Tcl_GetInt(interp, argv[i], &maxMatches) != TCL_OK) {
		return TCL_ERROR;
	    }
	    if (maxMatches < 0) {
		Tcl_AppendResult(interp, "bad match count \"", argv[i],
		    "\": should be a positive number", (char *)NULL);
		Blt_ListDestroy(optionList);
		return TCL_ERROR;
	    }
	} else if ((option[0] == '-') && (option[1] == '\0')) {
	    break;
	} else {
	    /*
	     * Verify that the switch is actually an entry configuration
	     * option.
	     */
	    if (Tk_ConfigureValue(interp, htabPtr->tkwin, htabPtr->entrySpecs,
		 (char *)entryPtr, argv[i], 0) != TCL_OK) {
		Tcl_ResetResult(interp);
		Tcl_AppendResult(interp, "bad find switch \"", argv[i], "\"",
		    (char *)NULL);
		Blt_ListDestroy(optionList);
		return TCL_ERROR;
	    }
	    if ((i + 1) == argc) {
		goto missingArg;
	    }
	    /* Save the option in the list of configuration options */
	    node = Blt_ListGetNode(optionList, argv[i]);
	    if (node == NULL) {
		node = Blt_ListCreateNode(optionList, argv[i]);
		Blt_ListAppendNode(optionList, node);
	    }
	    Blt_ListSetValue(node, (ClientData)argv[i + 1]);
	    i++;
	}
    }

    if ((argc - i) > 2) {
	Blt_ListDestroy(optionList);
	Tcl_AppendResult(interp, "too many args", (char *)NULL);
	return TCL_ERROR;
    }
    /*
     * Step 2:  Find the range of the search.  Check the order of two
     *		nodes and arrange the search accordingly.
     *
     *	Note:	Be careful to treat "end" as the end of all nodes, instead
     *		of the end of visible nodes.  That way, we can search the
     *		entire tree, even if the last folder is closed.
     */
    firstPtr = htabPtr->rootPtr;	/* Default to root node */
    lastPtr = LastEntry(htabPtr, firstPtr, 0);

    if (i < argc) {
	if ((argv[i][0] == 'e') && (strcmp(argv[i], "end") == 0)) {
	    firstPtr = LastEntry(htabPtr, htabPtr->rootPtr, 0);
	} else if (Blt_HtGetEntry(htabPtr, argv[i], &firstPtr) != TCL_OK) {
	    return TCL_ERROR;
	}
	i++;
    }
    if (i < argc) {
	if ((argv[i][0] == 'e') && (strcmp(argv[i], "end") == 0)) {
	    lastPtr = LastEntry(htabPtr, htabPtr->rootPtr, 0);
	} else if (Blt_HtGetEntry(htabPtr, argv[i], &lastPtr) != TCL_OK) {
	    return TCL_ERROR;
	}
    }
    if (Blt_TreeIsBefore(lastPtr->node, firstPtr->node)) {
	nextProc = Blt_HtPrevEntry;
    }
    nMatches = 0;

    /*
     * Step 3:	Search through the tree and look for nodes that match the
     *		current pattern specifications.  Save the name of each of
     *		the matching nodes.
     */
    Tcl_DStringInit(&dString);
    for (entryPtr = firstPtr; entryPtr != NULL; 
	 entryPtr = (*nextProc) (htabPtr, entryPtr, 0)) {
	if (namePattern != NULL) {
	    result = (*compareProc) (interp, Blt_TreeNodeLabel(entryPtr->node), 
			     namePattern);
	    if (result == invertMatch) {
		goto nextEntry;	/* Failed to match */
	    }
	}
	if (fullPattern != NULL) {
	    char *fullName;

	    fullName = Blt_HtGetFullName(htabPtr, entryPtr, FALSE);
	    result = (*compareProc) (interp, fullName, fullPattern);
	    free(fullName);
	    if (result == invertMatch) {
		goto nextEntry;	/* Failed to match */
	    }
	}
	for (node = Blt_ListFirstNode(optionList); node != NULL;
	    node = Blt_ListNextNode(node)) {
	    option = Blt_ListGetKey(node);
	    Tcl_ResetResult(interp);
	    if (Tk_ConfigureValue(interp, htabPtr->tkwin, htabPtr->entrySpecs,
		    (char *)entryPtr, option, 0) != TCL_OK) {
		goto error;	/* This shouldn't happen. */
	    }
	    pattern = (char *)Blt_ListGetValue(node);
	    value = Tcl_GetStringResult(interp);
	    result = (*compareProc) (interp, value, pattern);
	    if (result == invertMatch) {
		goto nextEntry;	/* Failed to match */
	    }
	}
	/* 
	 * Someone may actually delete the current node in the "exec"
	 * callback.  Preserve the entry.
	 */
	Tcl_Preserve(entryPtr);
	if (execCmd != NULL) {
	    Tcl_DString cmdString;

	    Blt_HtPercentSubst(htabPtr, entryPtr, execCmd, &cmdString);
	    result = Tcl_GlobalEval(interp, Tcl_DStringValue(&cmdString));
	    Tcl_DStringFree(&cmdString);
	    if (result != TCL_OK) {
		Tcl_Release(entryPtr);
		goto error;
	    }
	}
	/* A NULL node reference in an entry indicates that the entry
	 * was deleted, but its memory not released yet. */
	if (entryPtr->node != NULL) {
	    /* Finally, save the matching node name. */
	    Tcl_DStringAppendElement(&dString, NodeToString(entryPtr->node));
	}
	Tcl_Release(entryPtr);
	nMatches++;
	if ((nMatches == maxMatches) && (maxMatches > 0)) {
	    break;
	}
      nextEntry:
	if (entryPtr == lastPtr) {
	    break;
	}
    }
    Tcl_ResetResult(interp);
    Blt_ListDestroy(optionList);
    Tcl_DStringResult(interp, &dString);
    return TCL_OK;

  missingArg:
    Tcl_AppendResult(interp, "missing argument for find option \"", argv[i],
	"\"", (char *)NULL);
  error:
    Tcl_DStringFree(&dString);
    Blt_ListDestroy(optionList);
    return TCL_ERROR;
}

/*
 *----------------------------------------------------------------------
 *
 * GetOp --
 *
 *	Converts one or more node identifiers to its path component.
 *	The path may be either the single entry name or the full path
 *	of the entry.
 *
 * Results:
 *	A standard Tcl result.  The interpreter result will contain a
 *	list of the convert names.
 *
 *----------------------------------------------------------------------
 */
static int
GetOp(htabPtr, interp, argc, argv)
    Hiertable *htabPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    int useFullName;
    Entry *entryPtr;
    Tcl_DString dString;
    register int i;

    useFullName = FALSE;
    if ((argc > 2) && (argv[2][0] == '-') && (strcmp(argv[2], "-full") == 0)) {
	useFullName = TRUE;
	argv++, argc--;
    }
    Tcl_DStringInit(&dString);
    for (i = 2; i < argc; i++) {
	if (StringToEntry(htabPtr, argv[i], &entryPtr) != TCL_OK) {
	    return TCL_ERROR;
	}
	if (entryPtr->node == NULL) {
	    Tcl_DStringAppendElement(&dString, "");
	    continue;
	}
	if (useFullName) {
	    char *fullName;

	    fullName = Blt_HtGetFullName(htabPtr, entryPtr, FALSE);
	    Tcl_DStringAppendElement(&dString, fullName);
	    free(fullName);
	} else {
	    Tcl_DStringAppendElement(&dString, 
		     Blt_TreeNodeLabel(entryPtr->node));
	}
    }
    Tcl_DStringResult(interp, &dString);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * SearchAndApplyToTree --
 *
 *	Searches through the current tree and applies a procedure
 *	to matching nodes.  The search specification is taken from
 *	the following command-line arguments:
 *
 *      ?-exact? ?-glob? ?-regexp? ?-nonmatching?
 *      ?-data string?
 *      ?-name string?
 *      ?-full string?
 *      ?--?
 *      ?inode...?
 *
 * Results:
 *	A standard Tcl result.  If the result is valid, and if the
 *      nonmatchPtr is specified, it returns a boolean value
 *      indicating whether or not the search was inverted.  This
 *      is needed to fix things properly for the "hide nonmatching"
 *      case.
 *
 *----------------------------------------------------------------------
 */
static int
SearchAndApplyToTree(htabPtr, interp, argc, argv, proc, nonMatchPtr)
    Hiertable *htabPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
    ApplyProc *proc;
    int *nonMatchPtr;		/* returns: inverted search indicator */
{
    CompareProc *compareProc;
    int invertMatch;		/* normal search mode (matching entries) */
    char *namePattern, *fullPattern;
    register int i;
    int length;
    int result;
    char *option, *pattern, *value;
    char c;
    Blt_List optionList;
    Entry *entryPtr;
    register Blt_ListNode node;

    optionList = Blt_ListCreate(TCL_STRING_KEYS);
    invertMatch = FALSE;
    namePattern = fullPattern = NULL;
    compareProc = ExactCompare;

    entryPtr = htabPtr->rootPtr;
    for (i = 2; i < argc; i++) {
	if (argv[i][0] != '-') {
	    break;
	}
	option = argv[i] + 1;
	length = strlen(option);
	c = option[0];
	if ((c == 'e') && (strncmp(option, "exact", length) == 0)) {
	    compareProc = ExactCompare;
	} else if ((c == 'g') && (strncmp(option, "glob", length) == 0)) {
	    compareProc = GlobCompare;
	} else if ((c == 'r') && (strncmp(option, "regexp", length) == 0)) {
	    compareProc = RegexpCompare;
	} else if ((c == 'n') && (length > 1) &&
	    (strncmp(option, "nonmatching", length) == 0)) {
	    invertMatch = TRUE;
	} else if ((c == 'f') && (strncmp(option, "full", length) == 0)) {
	    if ((i + 1) == argc) {
		goto missingArg;
	    }
	    i++;
	    fullPattern = argv[i];
	} else if ((c == 'n') && (length > 1) &&
	    (strncmp(option, "name", length) == 0)) {
	    if ((i + 1) == argc) {
		goto missingArg;
	    }
	    i++;
	    namePattern = argv[i];
	} else if ((option[0] == '-') && (option[1] == '\0')) {
	    break;
	} else {
	    /*
	     * Verify that the switch is actually an entry configuration option.
	     */
	    if (Tk_ConfigureValue(interp, htabPtr->tkwin, htabPtr->entrySpecs,
		    (char *)entryPtr, argv[i], 0) != TCL_OK) {
		Tcl_ResetResult(interp);
		Tcl_AppendResult(interp, "bad switch \"", argv[i],
	    "\": must be -exact, -glob, -regexp, -name, -full, or -nonmatching",
		    (char *)NULL);
		return TCL_ERROR;
	    }
	    if ((i + 1) == argc) {
		goto missingArg;
	    }
	    /* Save the option in the list of configuration options */
	    node = Blt_ListGetNode(optionList, argv[i]);
	    if (node == NULL) {
		node = Blt_ListCreateNode(optionList, argv[i]);
		Blt_ListAppendNode(optionList, node);
	    }
	    Blt_ListSetValue(node, (ClientData)argv[i + 1]);
	}
    }

    if ((namePattern != NULL) || (fullPattern != NULL) ||
	(Blt_ListGetLength(optionList) > 0)) {
	/*
	 * Search through the tree and look for nodes that match the
	 * current spec.  Apply the input procedure to each of the
	 * matching nodes.
	 */
	for (entryPtr = htabPtr->rootPtr; entryPtr != NULL; 
	     entryPtr = Blt_HtNextEntry(htabPtr, entryPtr, 0)) {
	    if (namePattern != NULL) {
		result = (*compareProc) (interp, 
			Blt_TreeNodeLabel(entryPtr->node), namePattern);
		if (result == invertMatch) {
		    continue;	/* Failed to match */
		}
	    }
	    if (fullPattern != NULL) {
		char *fullName;

		fullName = Blt_HtGetFullName(htabPtr, entryPtr, FALSE);
		result = (*compareProc) (interp, fullName, fullPattern);
		free(fullName);
		if (result == invertMatch) {
		    continue;	/* Failed to match */
		}
	    }
	    for (node = Blt_ListFirstNode(optionList); node != NULL;
		node = Blt_ListNextNode(node)) {
		option = Blt_ListGetKey(node);
		Tcl_ResetResult(interp);
		if (Tk_ConfigureValue(interp, htabPtr->tkwin, 
			htabPtr->entrySpecs, (char *)entryPtr, 
			option, 0) != TCL_OK) {
		    return TCL_ERROR;	/* This shouldn't happen. */
		}
		pattern = (char *)Blt_ListGetValue(node);
		value = Tcl_GetStringResult(interp);
		result = (*compareProc) (interp, value, pattern);
		if (result == invertMatch) {
		    continue;	/* Failed to match */
		}
	    }
	    /* Finally, apply the procedure to the node */
	    (*proc) (htabPtr, entryPtr);
	}
	Tcl_ResetResult(interp);
	Blt_ListDestroy(optionList);
    }
    /*
     * Apply the procedure to nodes that have been specified
     * individually.
     */
    for ( /*empty*/ ; i < argc; i++) {
	if ((argv[i][0] == 'a') && (strcmp(argv[i], "all") == 0)) {
	    return Blt_HtTreeApply(htabPtr, htabPtr->rootPtr, proc, 0);
	}
	if (Blt_HtGetEntry(htabPtr, argv[i], &entryPtr) != TCL_OK) {
	    return TCL_ERROR;
	}
	if ((*proc) (htabPtr, entryPtr) != TCL_OK) {
	    return TCL_ERROR;
	}
    }

    if (nonMatchPtr != NULL) {
	*nonMatchPtr = invertMatch;	/* return "inverted search" status */
    }
    return TCL_OK;

  missingArg:
    Blt_ListDestroy(optionList);
    Tcl_AppendResult(interp, "missing pattern for search option \"", argv[i],
	"\"", (char *)NULL);
    return TCL_ERROR;

}

static int
FixSelectionsApplyProc(htabPtr, entryPtr)
    Hiertable *htabPtr;
    Entry *entryPtr;
{
    if (entryPtr->flags & ENTRY_HIDDEN) {
	Blt_HtDeselectEntry(htabPtr, entryPtr);
	if ((htabPtr->focusPtr != NULL) &&
	    (Blt_TreeIsAncestor(entryPtr->node, htabPtr->focusPtr->node))) {
	    if (entryPtr != htabPtr->rootPtr) {
		entryPtr = Blt_HtParentEntry(htabPtr, entryPtr);
		htabPtr->focusPtr = (entryPtr == NULL) 
		    ? htabPtr->focusPtr : entryPtr;
		Blt_SetFocusItem(htabPtr->bindTable, htabPtr->focusPtr);
	    }
	}
	if ((htabPtr->selAnchorPtr != NULL) &&
	    (Blt_TreeIsAncestor(entryPtr->node, htabPtr->selAnchorPtr->node))) {
	    htabPtr->selAnchorPtr = NULL;
	}
	if ((htabPtr->activePtr != NULL) &&
	    (Blt_TreeIsAncestor(entryPtr->node, htabPtr->activePtr->node))) {
	    htabPtr->activePtr = NULL;
	}
	Blt_HtPruneSelection(htabPtr, entryPtr);
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * HideOp --
 *
 *	Hides one or more nodes.  Nodes can be specified by their
 *      inode, or by matching a name or data value pattern.  By
 *      default, the patterns are matched exactly.  They can also
 *      be matched using glob-style and regular expression rules.
 *
 * Results:
 *	A standard Tcl result.
 *
 *----------------------------------------------------------------------
 */
static int
HideOp(htabPtr, interp, argc, argv)
    Hiertable *htabPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    int status, nonmatching;

    status = SearchAndApplyToTree(htabPtr, interp, argc, argv, 
	HideEntryApplyProc, &nonmatching);

    if (status != TCL_OK) {
	return TCL_ERROR;
    }
    /*
     * If this was an inverted search, scan back through the
     * tree and make sure that the parents for all visible
     * nodes are also visible.  After all, if a node is supposed
     * to be visible, its parent can't be hidden.
     */
    if (nonmatching) {
	Blt_HtTreeApply(htabPtr, htabPtr->rootPtr, MapAncestorsApplyProc, 0);
    }
    /*
     * Make sure that selections are cleared from any hidden
     * nodes.  This wasn't done earlier--we had to delay it until
     * we fixed the visibility status for the parents.
     */
    Blt_HtTreeApply(htabPtr, htabPtr->rootPtr, FixSelectionsApplyProc, 0);

    /* Hiding an entry only effects the visible nodes. */
    htabPtr->flags |= (HT_LAYOUT | HT_SCROLL);
    Blt_HtEventuallyRedraw(htabPtr);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ShowOp --
 *
 *	Mark one or more nodes to be exposed.  Nodes can be specified
 *	by their inode, or by matching a name or data value pattern.  By
 *      default, the patterns are matched exactly.  They can also
 *      be matched using glob-style and regular expression rules.
 *
 * Results:
 *	A standard Tcl result.
 *
 *----------------------------------------------------------------------
 */
static int
ShowOp(htabPtr, interp, argc, argv)
    Hiertable *htabPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    if (SearchAndApplyToTree(htabPtr, interp, argc, argv, ShowEntryApplyProc,
	    (int *)NULL) != TCL_OK) {
	return TCL_ERROR;
    }
    htabPtr->flags |= (HT_LAYOUT | HT_SCROLL);
    Blt_HtEventuallyRedraw(htabPtr);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * IndexOp --
 *
 *	Converts one of more words representing indices of the entries
 *	in the hierarchy widget to their respective serial identifiers.
 *
 * Results:
 *	A standard Tcl result.  Interp->result will contain the
 *	identifier of each inode found. If an inode could not be found,
 *	then the serial identifier will be the empty string.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
IndexOp(htabPtr, interp, argc, argv)
    Hiertable *htabPtr;
    Tcl_Interp *interp;
    int argc;			/* Not used. */
    char **argv;
{
    Entry *entryPtr;

    entryPtr = NULL;
    if ((argv[2][0] == '-') && (strcmp(argv[2], "-at") == 0)) {
	if (Blt_HtGetEntry(htabPtr, argv[3], &entryPtr) != TCL_OK) {
	    return TCL_ERROR;
	}
	argv += 2, argc -= 2;
    }
    if (argc != 3) {
	Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
	    " index ?-at index? index\"", (char *)NULL);
	return TCL_ERROR;
    }
    if ((GetEntry2(interp, htabPtr, argv[2], &entryPtr) == TCL_OK) && 
	(entryPtr != NULL)) {
	Tcl_SetResult(interp, NodeToString(entryPtr->node), TCL_VOLATILE);
    } else {
	Tcl_SetResult(interp, "", TCL_STATIC);
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * InsertOp --
 *
 *	Add new entries into a hierarchy.  If no node is specified,
 *	new entries will be added to the root of the hierarchy.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
InsertOp(htabPtr, interp, argc, argv)
    Hiertable *htabPtr;
    Tcl_Interp *interp;
    int argc;			/* Not used. */
    char **argv;
{
    Blt_TreeNode node, parent;
    int insertPos;
    int depth, count;
    char *path, **options;
    Tcl_DString dString;
    char **compArr;
    register char **p;
    register int n;
    Entry *rootPtr;

    rootPtr = htabPtr->rootPtr;
    if ((argv[2][0] == '-') && (strcmp(argv[2], "-at") == 0)) {
	if (argc > 2) {
	    if (Blt_HtGetEntry(htabPtr, argv[3], &rootPtr) != TCL_OK) {
		return TCL_ERROR;
	    }
	    argv += 2, argc -= 2;
	} else {
	    Tcl_AppendResult(interp, "missing argument for \"-at\" flag",
		     (char *)NULL);
	    return TCL_ERROR;
	}
    }
    if (argc == 2) {
	Tcl_AppendResult(interp, "missing position argument", (char *)NULL);
	return TCL_ERROR;
    }
    if (Blt_GetPosition(htabPtr->interp, argv[2], &insertPos) != TCL_OK) {
	return TCL_ERROR;
    }
    node = NULL;
    argc -= 3, argv += 3;
    Tcl_DStringInit(&dString);
    while (argc > 0) {
	path = argv[0];
	argv++, argc--;
	/*
	 * Count the option-value pairs that follow.  Count until we
	 * spot one that looks like an entry name (i.e. doesn't start
	 * with a minus "-").
	 */
	for (count = 0; count < argc; count += 2) {
	    if (argv[count][0] != '-') {
		break;
	    }
	}
	if (count > argc) {
	    count = argc;
	}
	options = argv;
	argc -= count, argv += count;

	if (htabPtr->trimLeft != NULL) {
	    register char *s1, *s2;

	    /* Trim off leading character string if one exists. */
	    for (s1 = path, s2 = htabPtr->trimLeft; *s2 != '\0'; s2++, s1++) {
		if (*s1 != *s2) {
		    break;
		}
	    }
	    if (*s2 == '\0') {
		path = s1;
	    }
	}
	/*
	 * Split the path and find the parent node of the path.
	 */
	compArr = &path;
	depth = 1;
	if (htabPtr->pathSep != SEPARATOR_NONE) {
	    if (SplitPath(htabPtr, path, &depth, &compArr) != TCL_OK) {
		goto error;
	    }
	    if (depth == 0) {
		continue;		/* Root already exists. */
	    }
	}
	parent = rootPtr->node;
	depth--;		

	/* Verify the path preceding the tail component.  */
	for (n = 0, p = compArr; n < depth; n++, p++) {
	    node = Blt_TreeFindChild(parent, *p);
	    if (node == NULL) {
		if (!htabPtr->autoFill) {
		    Tcl_AppendResult(interp, "can't find path component \"",
		         *p, "\" in \"", path, "\"", (char *)NULL);
		    goto error;
		}
		node = Blt_TreeCreateNode(htabPtr->tree, parent, *p, END);
	    }
	    parent = node;
	}
	node = NULL;
	if ((!htabPtr->allowDups) && (Blt_TreeFindChild(parent, *p) != NULL)) {
	    Tcl_AppendResult(interp, "entry \"", *p, "\" already exists in \"",
		 path, "\"", (char *)NULL);
	    goto error;
	}
	node = Blt_TreeCreateNode(htabPtr->tree, parent, *p, insertPos);
	if (node == NULL) {
	    goto error;
	}
	if (Blt_HtCreateEntry(htabPtr, node, count, options) != TCL_OK) {
	    goto error;
	}
	if (compArr != &path) {
	    free((char *)compArr);
	}
	Tcl_DStringAppendElement(&dString, NodeToString(node));
    }
    htabPtr->flags |= (HT_LAYOUT | HT_SCROLL | HT_DIRTY);
    Blt_HtEventuallyRedraw(htabPtr);
    Tcl_DStringResult(htabPtr->interp, &dString);
    return TCL_OK;

  error:
    if (compArr != &path) {
	free((char *)compArr);
    }
    Tcl_DStringFree(&dString);
    if (node != NULL) {
	Blt_TreeDeleteNode(htabPtr->tree, node);
    }
    return TCL_ERROR;
}

/*
 *----------------------------------------------------------------------
 *
 * DeleteOp --
 *
 *	Deletes nodes from the hierarchy. Deletes one or more entries 
 *	(except root). In all cases, nodes are removed recursively.
 *
 *	Note: There's no need to explicitly clean up Entry structures 
 *	      or request a redraw of the widget. When a node is 
 *	      deleted in the tree, all of the Tcl_Objs representing
 *	      the various data fields are also removed.  The hiertable 
 *	      widget store the Entry structure in a data field. So it's
 *	      automatically cleaned up when FreeEntryInternalRep is
 *	      called.
 *
 *---------------------------------------------------------------------- 
 */
/*ARGSUSED*/
static int
DeleteOp(htabPtr, interp, argc, argv)
    Hiertable *htabPtr;
    Tcl_Interp *interp;
    int argc;			/* Not used. */
    char **argv;
{
    Entry *entryPtr;
    register int i;

    for (i = 2; i < argc; i++) {
	if (Blt_HtGetEntry(htabPtr, argv[i], &entryPtr) != TCL_OK) {
	    return TCL_ERROR;	/* Node or path doesn't already exist */
	}
	/* 
	 * -----------------------------------------------------------
	 *
	 *   Check if this is the root node.  We assume every tree has
	 *   at least a root node.  
	 *
	 * ----------------------------------------------------------- 
	 */
	if (entryPtr == htabPtr->rootPtr) {
	    Blt_TreeNode next, node;

	    /* Delete all the children of the root instead. */
	    for (node = Blt_TreeFirstChild(entryPtr->node); node != NULL; 
		 node = next) {
		next = Blt_TreeNextSibling(node);
		Blt_TreeDeleteNode(htabPtr->tree, node);
	    }
	} else {
	    Blt_TreeDeleteNode(htabPtr->tree, entryPtr->node);
	}
    } 
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * MoveOp --
 *
 *	Move an entry into a new location in the hierarchy.
 *
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
MoveOp(htabPtr, interp, argc, argv)
    Hiertable *htabPtr;
    Tcl_Interp *interp;
    int argc;			/* Not used. */
    char **argv;
{
    Blt_TreeNode parent;
    Entry *entryPtr, *destPtr;
    char c;
    int action;

#define MOVE_INTO	(1<<0)
#define MOVE_BEFORE	(1<<1)
#define MOVE_AFTER	(1<<2)
    if (Blt_HtGetEntry(htabPtr, argv[2], &entryPtr) != TCL_OK) {
	return TCL_ERROR;
    }
    c = argv[3][0];
    action = MOVE_INTO;
    if ((c == 'i') && (strcmp(argv[3], "into") == 0)) {
	action = MOVE_INTO;
    } else if ((c == 'b') && (strcmp(argv[3], "before") == 0)) {
	action = MOVE_BEFORE;
    } else if ((c == 'a') && (strcmp(argv[3], "after") == 0)) {
	action = MOVE_AFTER;
    } else {
	Tcl_AppendResult(interp, "bad position \"", argv[3],
	    "\": should be into, before, or after", (char *)NULL);
	return TCL_ERROR;
    }
    if (Blt_HtGetEntry(htabPtr, argv[4], &destPtr) != TCL_OK) {
	return TCL_ERROR;
    }
    /* Verify they aren't ancestors. */
    if (Blt_TreeIsAncestor(entryPtr->node, destPtr->node)) {
	Tcl_AppendResult(interp, "can't move node: \"", argv[2],
	    "\" is an ancestor of \"", argv[4], "\"", (char *)NULL);
	return TCL_ERROR;
    }
    parent = Blt_TreeNodeParent(destPtr->node);
    if (parent == NULL) {
	action = MOVE_INTO;
    }
    switch (action) {
    case MOVE_INTO:
	Blt_TreeMoveNode(htabPtr->tree, entryPtr->node, destPtr->node, 
		(Blt_TreeNode)NULL);
	break;

    case MOVE_BEFORE:
	Blt_TreeMoveNode(htabPtr->tree, entryPtr->node, parent, destPtr->node);
	break;

    case MOVE_AFTER:
	Blt_TreeMoveNode(htabPtr->tree, entryPtr->node, parent, 
		 Blt_TreeNextSibling(destPtr->node));
	break;
    }
    htabPtr->flags |= (HT_LAYOUT | HT_DIRTY);
    Blt_HtEventuallyRedraw(htabPtr);
    return TCL_OK;
}

/*ARGSUSED*/
static int
NearestOp(htabPtr, interp, argc, argv)
    Hiertable *htabPtr;
    Tcl_Interp *interp;
    int argc;			/* Not used. */
    char **argv;
{
    Button *buttonPtr = &(htabPtr->button);
    int x, y;			/* Screen coordinates of the test point. */
    register Entry *entryPtr;

    if ((Tk_GetPixels(interp, htabPtr->tkwin, argv[2], &x) != TCL_OK) ||
	(Tk_GetPixels(interp, htabPtr->tkwin, argv[3], &y) != TCL_OK)) {
	return TCL_ERROR;
    }
    if (htabPtr->nVisible == 0) {
	return TCL_OK;
    }
    entryPtr = Blt_HtNearestEntry(htabPtr, x, y, TRUE);
    if (entryPtr == NULL) {
	return TCL_OK;
    }
    x = WORLDX(htabPtr, x);
    y = WORLDY(htabPtr, y);
    if (argc > 4) {
	char *where;
	int labelX, depth;

	where = "";
	if (entryPtr->flags & ENTRY_BUTTON) {
	    int buttonX, buttonY;

	    buttonX = entryPtr->worldX + entryPtr->buttonX;
	    buttonY = entryPtr->worldY + entryPtr->buttonY;
	    if ((x >= buttonX) && (x < (buttonX + buttonPtr->width)) &&
		(y >= buttonY) && (y < (buttonY + buttonPtr->height))) {
		where = "button";
	    }
	}
	depth = DEPTH(htabPtr, entryPtr->node);
	labelX = entryPtr->worldX + ICONWIDTH(depth);
	if ((x >= labelX) &&
	    (x < (labelX + ICONWIDTH(depth + 1) + entryPtr->width))) {
	    where = "select";
	}
	if (Tcl_SetVar(interp, argv[4], where, TCL_LEAVE_ERR_MSG) == NULL) {
	    return TCL_ERROR;
	}
    }
    Tcl_SetResult(interp, NodeToString(entryPtr->node), TCL_VOLATILE);
    return TCL_OK;
}


/*ARGSUSED*/
static int
OpenOp(htabPtr, interp, argc, argv)
    Hiertable *htabPtr;
    Tcl_Interp *interp;		/* Not used. */
    int argc;
    char **argv;
{
    Entry *entryPtr;
    int length;
    int recurse, result;
    register int i;

    recurse = FALSE;
    length = strlen(argv[2]);
    if ((argv[2][0] == '-') && (length > 1) &&
	(strncmp(argv[2], "-recurse", length) == 0)) {
	argv++, argc--;
	recurse = TRUE;
    }
    for (i = 2; i < argc; i++) {
	if (StringToEntry(htabPtr, argv[i], &entryPtr) != TCL_OK) {
	    return TCL_ERROR;
	}
	if (entryPtr == NULL) {
	    continue;
	}
	if (recurse) {
	    result = Blt_HtTreeApply(htabPtr, entryPtr, Blt_HtOpenEntry, 0);
	} else {
	    result = Blt_HtOpenEntry(htabPtr, entryPtr);
	}
	if (result != TCL_OK) {
	    return TCL_ERROR;
	}
	/* Make sure ancestors of this node aren't hidden. */
	MapAncestors(htabPtr, entryPtr);
    }
    /*FIXME: This is only for flattened entries.  */
    htabPtr->flags |= (HT_LAYOUT | HT_DIRTY);
    Blt_HtEventuallyRedraw(htabPtr);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * RangeOp --
 *
 *	Returns the node identifiers in a given range.
 *
 *----------------------------------------------------------------------
 */
static int
RangeOp(htabPtr, interp, argc, argv)
    Hiertable *htabPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    Entry *entryPtr, *firstPtr, *lastPtr;
    unsigned int mask;
    int length;

    length = strlen(argv[2]);
    mask = 0;
    if ((argv[2][0] == '-') && (length > 1) &&
	(strncmp(argv[2], "-open", length) == 0)) {
	argv++, argc--;
	mask |= ENTRY_CLOSED;
    }
    if (Blt_HtGetEntry(htabPtr, argv[2], &firstPtr) != TCL_OK) {
	return TCL_ERROR;
    }
    if (argc > 3) {
	if (Blt_HtGetEntry(htabPtr, argv[3], &lastPtr) != TCL_OK) {
	    return TCL_ERROR;
	}
    } else {
	lastPtr = LastEntry(htabPtr, firstPtr, mask);
    }    
    if (mask & ENTRY_CLOSED) {
	if (firstPtr->flags & ENTRY_HIDDEN) {
	    Tcl_AppendResult(interp, "first node \"", argv[2], "\" is hidden.",
		(char *)NULL);
	    return TCL_ERROR;
	}
	if (lastPtr->flags & ENTRY_HIDDEN) {
	    Tcl_AppendResult(interp, "last node \"", argv[3], "\" is hidden.",
		(char *)NULL);
	    return TCL_ERROR;
	}
    }

    /*
     * The relative order of the first/last markers determines the
     * direction.
     */
    if (Blt_TreeIsBefore(lastPtr->node, firstPtr->node)) {
	for (entryPtr = lastPtr; entryPtr != NULL; 
	     entryPtr = Blt_HtPrevEntry(htabPtr, entryPtr, mask)) {
	    Tcl_AppendElement(interp, NodeToString(entryPtr->node));
	    if (entryPtr == firstPtr) {
		break;
	    }
	}
    } else {
	for (entryPtr = firstPtr; entryPtr != NULL; 
	     entryPtr = Blt_HtNextEntry(htabPtr, entryPtr, mask)) {
	    Tcl_AppendElement(interp, NodeToString(entryPtr->node));
	    if (entryPtr == lastPtr) {
		break;
	    }
	}
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ScanOp --
 *
 *	Implements the quick scan.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
ScanOp(htabPtr, interp, argc, argv)
    Hiertable *htabPtr;
    Tcl_Interp *interp;
    int argc;			/* Not used. */
    char **argv;
{
    int x, y;
    char c;
    unsigned int length;
    int oper;

#define SCAN_MARK		1
#define SCAN_DRAGTO	2
    c = argv[2][0];
    length = strlen(argv[2]);
    if ((c == 'm') && (strncmp(argv[2], "mark", length) == 0)) {
	oper = SCAN_MARK;
    } else if ((c == 'd') && (strncmp(argv[2], "dragto", length) == 0)) {
	oper = SCAN_DRAGTO;
    } else {
	Tcl_AppendResult(interp, "bad scan operation \"", argv[2],
	    "\": should be either \"mark\" or \"dragto\"", (char *)NULL);
	return TCL_ERROR;
    }
    if ((Tk_GetPixels(interp, htabPtr->tkwin, argv[3], &x) != TCL_OK) ||
	(Tk_GetPixels(interp, htabPtr->tkwin, argv[4], &y) != TCL_OK)) {
	return TCL_ERROR;
    }
    if (oper == SCAN_MARK) {
	htabPtr->scanAnchorX = x;
	htabPtr->scanAnchorY = y;
	htabPtr->scanX = htabPtr->xOffset;
	htabPtr->scanY = htabPtr->yOffset;
    } else {
	int worldX, worldY;
	int dx, dy;

	dx = htabPtr->scanAnchorX - x;
	dy = htabPtr->scanAnchorY - y;
	worldX = htabPtr->scanX + (10 * dx);
	worldY = htabPtr->scanY + (10 * dy);

	if (worldX < 0) {
	    worldX = 0;
	} else if (worldX >= htabPtr->worldWidth) {
	    worldX = htabPtr->worldWidth - htabPtr->xScrollUnits;
	}
	if (worldY < 0) {
	    worldY = 0;
	} else if (worldY >= htabPtr->worldHeight) {
	    worldY = htabPtr->worldHeight - htabPtr->yScrollUnits;
	}
	htabPtr->xOffset = worldX;
	htabPtr->yOffset = worldY;
	htabPtr->flags |= HT_SCROLL;
	Blt_HtEventuallyRedraw(htabPtr);
    }
    return TCL_OK;
}

/*ARGSUSED*/
static int
SeeOp(htabPtr, interp, argc, argv)
    Hiertable *htabPtr;
    Tcl_Interp *interp;		/* Not used. */
    int argc;
    char **argv;
{
    Entry *entryPtr;
    int width, height;
    int x, y;
    Tk_Anchor anchor;
    int left, right, top, bottom;

    anchor = TK_ANCHOR_W;	/* Default anchor is West */
    if ((argv[2][0] == '-') && (strcmp(argv[2], "-anchor") == 0)) {
	if (argc == 3) {
	    Tcl_AppendResult(interp, "missing \"-anchor\" argument",
		(char *)NULL);
	    return TCL_ERROR;
	}
	if (Tk_GetAnchor(interp, argv[3], &anchor) != TCL_OK) {
	    return TCL_ERROR;
	}
	argc -= 2, argv += 2;
    }
    if (argc == 2) {
	Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
	    "see ?-anchor anchor? index\"", (char *)NULL);
	return TCL_ERROR;
    }
    if (StringToEntry(htabPtr, argv[2], &entryPtr) != TCL_OK) {
	return TCL_ERROR;
    }
    if (entryPtr == NULL) {
	return TCL_OK;
    }
    if (entryPtr->flags & ENTRY_HIDDEN) {
	MapAncestors(htabPtr, entryPtr);
	htabPtr->flags |= HT_SCROLL;
	/*
	 * If the entry wasn't previously exposed, its world coordinates
	 * aren't likely to be valid.  So re-compute the layout before
	 * we try to see the viewport to the entry's location.
	 */
	Blt_HtComputeLayout(htabPtr);
    }
    width = VPORTWIDTH(htabPtr);
    height = VPORTHEIGHT(htabPtr);

    /*
     * XVIEW:	If the entry is left or right of the current view, adjust
     *		the offset.  If the entry is nearby, adjust the view just
     *		a bit.  Otherwise, center the entry.
     */
    left = htabPtr->xOffset;
    right = htabPtr->xOffset + width;

    switch (anchor) {
    case TK_ANCHOR_W:
    case TK_ANCHOR_NW:
    case TK_ANCHOR_SW:
	x = 0;
	break;
    case TK_ANCHOR_E:
    case TK_ANCHOR_NE:
    case TK_ANCHOR_SE:
	x = entryPtr->worldX + entryPtr->width + 
	    ICONWIDTH(DEPTH(htabPtr, entryPtr->node)) - width;
	break;
    default:
	if (entryPtr->worldX < left) {
	    x = entryPtr->worldX;
	} else if ((entryPtr->worldX + entryPtr->width) > right) {
	    x = entryPtr->worldX + entryPtr->width - width;
	} else {
	    x = htabPtr->xOffset;
	}
	break;
    }
    /*
     * YVIEW:	If the entry is above or below the current view, adjust
     *		the offset.  If the entry is nearby, adjust the view just
     *		a bit.  Otherwise, center the entry.
     */
    top = htabPtr->yOffset;
    bottom = htabPtr->yOffset + height;

    switch (anchor) {
    case TK_ANCHOR_N:
	y = htabPtr->yOffset;
	break;
    case TK_ANCHOR_NE:
    case TK_ANCHOR_NW:
	y = entryPtr->worldY - (height / 2);
	break;
    case TK_ANCHOR_S:
    case TK_ANCHOR_SE:
    case TK_ANCHOR_SW:
	y = entryPtr->worldY + entryPtr->height - height;
	break;
    default:
	if (entryPtr->worldY < top) {
	    y = entryPtr->worldY;
	} else if ((entryPtr->worldY + entryPtr->height) > bottom) {
	    y = entryPtr->worldY + entryPtr->height - height;
	} else {
	    y = htabPtr->yOffset;
	}
	break;
    }
    if ((y != htabPtr->yOffset) || (x != htabPtr->xOffset)) {
	/* htabPtr->xOffset = x; */
	htabPtr->yOffset = y;
	htabPtr->flags |= HT_SCROLL;
    }
    Blt_HtEventuallyRedraw(htabPtr);
    return TCL_OK;
}

void
Blt_HtClearSelection(htabPtr)
    Hiertable *htabPtr;
{
    Tcl_DeleteHashTable(&(htabPtr->selectTable));
    Tcl_InitHashTable(&(htabPtr->selectTable), TCL_ONE_WORD_KEYS);
    Blt_ChainReset(htabPtr->selectPtr);
    Blt_HtEventuallyRedraw(htabPtr);
    if (htabPtr->selectCmd != NULL) {
	EventuallyInvokeSelectCmd(htabPtr);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * LostSelection --
 *
 *	This procedure is called back by Tk when the selection is grabbed
 *	away.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The existing selection is unhighlighted, and the window is
 *	marked as not containing a selection.
 *
 *----------------------------------------------------------------------
 */
static void
LostSelection(clientData)
    ClientData clientData;	/* Information about the widget. */
{
    Hiertable *htabPtr = (Hiertable *)clientData;

    if (!htabPtr->exportSelection) {
	return;
    }
    Blt_HtClearSelection(htabPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * SelectRange --
 *
 *	Sets the selection flag for a range of nodes.  The range is
 *	determined by two pointers which designate the first/last
 *	nodes of the range.
 *
 * Results:
 *	Always returns TCL_OK.
 *
 *----------------------------------------------------------------------
 */
static int
SelectRange(htabPtr, fromPtr, toPtr)
    Hiertable *htabPtr;
    Entry *fromPtr, *toPtr;
{
    if (htabPtr->flatView) {
	register int i;

	if (fromPtr->flatIndex > toPtr->flatIndex) {
	    for (i = fromPtr->flatIndex; i >= toPtr->flatIndex; i--) {
		SelectEntryApplyProc(htabPtr, htabPtr->flatArr[i]);
	    }
	} else {
	    for (i = fromPtr->flatIndex; i <= toPtr->flatIndex; i++) {
		SelectEntryApplyProc(htabPtr, htabPtr->flatArr[i]);
	    }
	}	    
    } else {
	Entry *entryPtr;
	IterProc *proc;
	/* From the range determine the direction to select entries. */

	proc = (Blt_TreeIsBefore(toPtr->node, fromPtr->node)) 
	    ? Blt_HtPrevEntry : Blt_HtNextEntry;
	for (entryPtr = fromPtr; entryPtr != NULL;
	     entryPtr = (*proc)(htabPtr, entryPtr, ENTRY_MASK)) {
	    SelectEntryApplyProc(htabPtr, entryPtr);
	    if (entryPtr == toPtr) {
		break;
	    }
	}
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * SelectionAnchorOp --
 *
 *	Sets the selection anchor to the element given by a index.
 *	The selection anchor is the end of the selection that is fixed
 *	while dragging out a selection with the mouse.  The index
 *	"anchor" may be used to refer to the anchor element.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The selection changes.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
SelectionAnchorOp(htabPtr, interp, argc, argv)
    Hiertable *htabPtr;
    Tcl_Interp *interp;		/* Not used. */
    int argc;			/* Not used. */
    char **argv;
{
    Entry *entryPtr;

    if (StringToEntry(htabPtr, argv[3], &entryPtr) != TCL_OK) {
	return TCL_ERROR;
    }
    /* Set both the anchor and the mark. Indicates that a single entry
     * is selected. */
    htabPtr->selAnchorPtr = entryPtr;
    if (entryPtr != NULL) {
	Tcl_SetResult(interp, NodeToString(entryPtr->node), TCL_VOLATILE);
    }
    Blt_HtEventuallyRedraw(htabPtr);
    return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * SelectionClearallOp
 *
 *	Clears the entire selection.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The selection changes.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
SelectionClearallOp(htabPtr, interp, argc, argv)
    Hiertable *htabPtr;
    Tcl_Interp *interp;
    int argc;			/* Not used. */
    char **argv;
{
    Blt_HtClearSelection(htabPtr);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * SelectionIncludesOp
 *
 *	Returns 1 if the element indicated by index is currently
 *	selected, 0 if it isn't.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The selection changes.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
SelectionIncludesOp(htabPtr, interp, argc, argv)
    Hiertable *htabPtr;
    Tcl_Interp *interp;
    int argc;			/* Not used. */
    char **argv;
{
    Entry *entryPtr;
    int bool;

    if (Blt_HtGetEntry(htabPtr, argv[3], &entryPtr) != TCL_OK) {
	return TCL_ERROR;
    }
    bool = Blt_HtEntryIsSelected(htabPtr, entryPtr);
    Tcl_SetResult(interp, bool ? "1" : "0", TCL_STATIC);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * SelectionMarkOp --
 *
 *	Sets the selection mark to the element given by a index.
 *	The selection anchor is the end of the selection that is movable
 *	while dragging out a selection with the mouse.  The index
 *	"mark" may be used to refer to the anchor element.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The selection changes.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
SelectionMarkOp(htabPtr, interp, argc, argv)
    Hiertable *htabPtr;
    Tcl_Interp *interp;		/* Not used. */
    int argc;			/* Not used. */
    char **argv;
{
    Entry *entryPtr;
    Blt_ChainLink *linkPtr, *nextPtr;
    Entry *selectPtr;

    if (StringToEntry(htabPtr, argv[3], &entryPtr) != TCL_OK) {
	return TCL_ERROR;
    }
    if (htabPtr->selAnchorPtr == NULL) {
	Tcl_AppendResult(interp, "selection anchor must be set first", 
		 (char *)NULL);
	return TCL_ERROR;
    }

    /* Deselect entry from the list all the way back to the anchor. */
    for (linkPtr = Blt_ChainLastLink(htabPtr->selectPtr); linkPtr != NULL; 
	linkPtr = nextPtr) {
	nextPtr = Blt_ChainPrevLink(linkPtr);
	selectPtr = (Entry *)Blt_ChainGetValue(linkPtr);
	if (selectPtr == htabPtr->selAnchorPtr) {
	    break;
	}
	Blt_HtDeselectEntry(htabPtr, selectPtr);
    }
    htabPtr->flags &= ~SELECTION_MASK;
    htabPtr->flags |= SELECTION_SET;
    SelectRange(htabPtr, htabPtr->selAnchorPtr, entryPtr);
    Tcl_SetResult(interp, NodeToString(entryPtr->node), TCL_VOLATILE);
    Blt_HtEventuallyRedraw(htabPtr);
    if (htabPtr->selectCmd != NULL) {
	EventuallyInvokeSelectCmd(htabPtr);
    }
    return TCL_OK;
}

#ifdef notdef
/*
 *----------------------------------------------------------------------
 *
 * SelectionDragOp --
 *
 *	Sets the selection anchor to the element given by a index.
 *	The selection anchor is the end of the selection that is fixed
 *	while dragging out a selection with the mouse.  The index
 *	"anchor" may be used to refer to the anchor element.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The selection changes.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
SelectionDragOp(htabPtr, interp, argc, argv)
    Hiertable *htabPtr;
    Tcl_Interp *interp;
    int argc;			/* Not used. */
    char **argv;
{
    Entry *entryPtr;
    char c;
    int length;

    if (StringToEntry(htabPtr, argv[3], &entryPtr) != TCL_OK) {
	return TCL_ERROR;
    }
    c = argv[4][0];
    length = strlen(argv[4]);
    htabPtr->flags &= ~SELECTION_MASK;
    if ((c == 'c') && (strncmp(argv[4], "clear", length) == 0)) {
	htabPtr->flags |= SELECTION_CLEAR;
    } else if ((c == 's') && (strncmp(argv[4], "set", length) == 0)) {
	htabPtr->flags |= SELECTION_SET;
    } else if ((c == 't') && (strncmp(argv[4], "toggle", length) == 0)) {
	htabPtr->flags |= SELECTION_TOGGLE;
    } else {
	Tcl_AppendResult(interp, "bad option \"", argv[4],
	    "\": should be \"clear\", \"set\", or \"toggle\"", (char *)NULL);
	return TCL_ERROR;
    }
    Blt_HtEventuallyRedraw(htabPtr);
    return TCL_OK;
}
#endif

/*
 *----------------------------------------------------------------------
 *
 * SelectionPresentOp
 *
 *	Returns 1 if there is a selection and 0 if it isn't.
 *
 * Results:
 *	A standard Tcl result.  interp->result will contain a
 *	boolean string indicating if there is a selection.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
SelectionPresentOp(htabPtr, interp, argc, argv)
    Hiertable *htabPtr;
    Tcl_Interp *interp;
    int argc;			/* Not used. */
    char **argv;
{
    int bool;

    bool = (Blt_ChainGetLength(htabPtr->selectPtr) > 0);
    Tcl_SetResult(interp, bool ? "1" : "0", TCL_STATIC);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * SelectionSetOp
 *
 *	Selects, deselects, or toggles all of the elements in the
 *	range between first and last, inclusive, without affecting the
 *	selection state of elements outside that range.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The selection changes.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
SelectionSetOp(htabPtr, interp, argc, argv)
    Hiertable *htabPtr;
    Tcl_Interp *interp;
    int argc;			/* Not used. */
    char **argv;
{
    Entry *firstPtr, *lastPtr;

    htabPtr->flags &= ~SELECTION_MASK;
    switch (argv[2][0]) {
    case 's':
	htabPtr->flags |= SELECTION_SET;
	break;
    case 'c':
	htabPtr->flags |= SELECTION_CLEAR;
	break;
    case 't':
	htabPtr->flags |= SELECTION_TOGGLE;
	break;
    }
    if (Blt_HtGetEntry(htabPtr, argv[3], &firstPtr) != TCL_OK) {
	return TCL_ERROR;
    }
    if ((firstPtr->flags & ENTRY_HIDDEN) && 
	(!(htabPtr->flags & SELECTION_CLEAR))) {
	Tcl_AppendResult(interp, "can't select hidden node \"", argv[3], "\"",
	    (char *)NULL);
	return TCL_ERROR;
    }
    lastPtr = firstPtr;
    if (argc > 4) {
	if (Blt_HtGetEntry(htabPtr, argv[4], &lastPtr) != TCL_OK) {
	    return TCL_ERROR;
	}
	if ((lastPtr->flags & ENTRY_HIDDEN) && 
		(!(htabPtr->flags & SELECTION_CLEAR))) {
	    Tcl_AppendResult(interp, "can't select hidden node \"", argv[4],
		"\"", (char *)NULL);
	    return TCL_ERROR;
	}
    }
    if (firstPtr == lastPtr) {
	SelectEntryApplyProc(htabPtr, firstPtr);
    } else {
	SelectRange(htabPtr, firstPtr, lastPtr);
    }
    if (htabPtr->flags & SELECTION_EXPORT) {
	Tk_OwnSelection(htabPtr->tkwin, XA_PRIMARY, LostSelection,
	    (ClientData)htabPtr);
    }
    Blt_HtEventuallyRedraw(htabPtr);
    if (htabPtr->selectCmd != NULL) {
	EventuallyInvokeSelectCmd(htabPtr);
    }
    return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * SelectionOp --
 *
 *	This procedure handles the individual options for text
 *	selections.  The selected text is designated by start and end
 *	indices into the text pool.  The selected segment has both a
 *	anchored and unanchored ends.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The selection changes.
 *
 *----------------------------------------------------------------------
 */
static Blt_OpSpec selectionOps[] =
{
    {"anchor", 1, (Blt_OpProc)SelectionAnchorOp, 4, 4, "index",},
    {"clear", 5, (Blt_OpProc)SelectionSetOp, 4, 5, "firstIndex ?lastIndex?",},
    {"clearall", 6, (Blt_OpProc)SelectionClearallOp, 3, 3, "",},
#ifdef notdef
    {"dragto", 1, (Blt_OpProc)SelectionDragOp, 5, 5, "index action",},
#endif
    {"includes", 1, (Blt_OpProc)SelectionIncludesOp, 4, 4, "index",},
    {"mark", 1, (Blt_OpProc)SelectionMarkOp, 4, 4, "index",},
    {"present", 1, (Blt_OpProc)SelectionPresentOp, 3, 3, "",},
    {"set", 1, (Blt_OpProc)SelectionSetOp, 4, 5, "firstIndex ?lastIndex?",},
    {"toggle", 1, (Blt_OpProc)SelectionSetOp, 4, 5, "firstIndex ?lastIndex?",},
};
static int nSelectionOps = sizeof(selectionOps) / sizeof(Blt_OpSpec);

static int
SelectionOp(htabPtr, interp, argc, argv)
    Hiertable *htabPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    Blt_OpProc opProc;
    int result;

    opProc = Blt_GetOperation(interp, nSelectionOps, selectionOps,
	BLT_OPER_ARG2, argc, argv, 0);
    if (opProc == NULL) {
	return TCL_ERROR;
    }
    result = (*opProc) (htabPtr, interp, argc, argv);
    return result;
}


/*ARGSUSED*/
static int
ToggleOp(htabPtr, interp, argc, argv)
    Hiertable *htabPtr;
    Tcl_Interp *interp;		/* Not used. */
    int argc;
    char **argv;
{
    Entry *entryPtr;

    if (StringToEntry(htabPtr, argv[2], &entryPtr) != TCL_OK) {
	return TCL_ERROR;
    }
    if (entryPtr == NULL) {
	return TCL_OK;
    }
    if (entryPtr->flags & ENTRY_CLOSED) {
	Blt_HtOpenEntry(htabPtr, entryPtr);
    } else {
	Blt_HtPruneSelection(htabPtr, entryPtr);
	if ((htabPtr->focusPtr != NULL) && 
	    (Blt_TreeIsAncestor(entryPtr->node, htabPtr->focusPtr->node))) {
	    htabPtr->focusPtr = entryPtr;
	    Blt_SetFocusItem(htabPtr->bindTable, htabPtr->focusPtr);
	}
	if ((htabPtr->selAnchorPtr != NULL) &&
	    (Blt_TreeIsAncestor(entryPtr->node, htabPtr->selAnchorPtr->node))) {
	    htabPtr->selAnchorPtr = NULL;
	}
	Blt_HtCloseEntry(htabPtr, entryPtr);
    }
    htabPtr->flags |= HT_SCROLL;
    Blt_HtEventuallyRedraw(htabPtr);
    return TCL_OK;
}

static int
XViewOp(htabPtr, interp, argc, argv)
    Hiertable *htabPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    int width, worldWidth;

    width = VPORTWIDTH(htabPtr);
    worldWidth = htabPtr->worldWidth;
    if (argc == 2) {
	double fract;

	/*
	 * Note that we are bounding the fractions between 0.0 and 1.0
	 * to support the "canvas"-style of scrolling.
	 */
	fract = (double)htabPtr->xOffset / worldWidth;
	Tcl_AppendElement(interp, Blt_Dtoa(interp, CLAMP(fract, 0.0, 1.0)));
	fract = (double)(htabPtr->xOffset + width) / worldWidth;
	Tcl_AppendElement(interp, Blt_Dtoa(interp, CLAMP(fract, 0.0, 1.0)));
	return TCL_OK;
    }
    if (Blt_GetScrollInfo(interp, argc - 2, argv + 2, &(htabPtr->xOffset),
	    worldWidth, width, htabPtr->xScrollUnits, htabPtr->scrollMode) 
	    != TCL_OK) {
	return TCL_ERROR;
    }
    htabPtr->flags |= HT_XSCROLL;
    Blt_HtEventuallyRedraw(htabPtr);
    return TCL_OK;
}

static int
YViewOp(htabPtr, interp, argc, argv)
    Hiertable *htabPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    int height, worldHeight;

    height = VPORTHEIGHT(htabPtr);
    worldHeight = htabPtr->worldHeight;
    if (argc == 2) {
	double fract;

	/* Report first and last fractions */
	fract = (double)htabPtr->yOffset / worldHeight;
	Tcl_AppendElement(interp, Blt_Dtoa(interp, CLAMP(fract, 0.0, 1.0)));
	fract = (double)(htabPtr->yOffset + height) / worldHeight;
	Tcl_AppendElement(interp, Blt_Dtoa(interp, CLAMP(fract, 0.0, 1.0)));
	return TCL_OK;
    }
    if (Blt_GetScrollInfo(interp, argc - 2, argv + 2, &(htabPtr->yOffset),
	    worldHeight, height, htabPtr->yScrollUnits, htabPtr->scrollMode)
	!= TCL_OK) {
	return TCL_ERROR;
    }
    htabPtr->flags |= HT_SCROLL;
    Blt_HtEventuallyRedraw(htabPtr);
    return TCL_OK;
}

/*
 * --------------------------------------------------------------
 *
 * Blt_HtWidgetInstCmd --
 *
 * 	This procedure is invoked to process commands on behalf of
 *	the hiertable widget.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 * --------------------------------------------------------------
 */
static Blt_OpSpec operSpecs[] =
{
    {"bbox", 2, (Blt_OpProc)BboxOp, 3, 0, "index...",}, /**/
    {"bind", 2, (Blt_OpProc)BindOp, 3, 5, "tagName ?sequence command?",}, /**/
    {"button", 2, (Blt_OpProc)ButtonOp, 2, 0, "args",},
    {"cget", 2, (Blt_OpProc)CgetOp, 3, 3, "option",}, /**/
    {"close", 2, (Blt_OpProc)CloseOp, 2, 0, "?-recurse? index...",}, /**/
    {"column", 3, (Blt_OpProc)Blt_HtColumnOp, 2, 0, "oper args",}, 
    {"configure", 3, (Blt_OpProc)ConfigureOp, 2, 0, "?option value?...",},/**/
    {"curselection", 2, (Blt_OpProc)CurselectionOp, 2, 2, "",},
    {"delete", 1, (Blt_OpProc)DeleteOp, 2, 0, "index ?index...?",}, /**/
    {"entry", 1, (Blt_OpProc)EntryOp, 2, 0, "oper args",},
    {"find", 2, (Blt_OpProc)FindOp, 2, 0, "?flags...? ?firstIndex lastIndex?",}, /**/
    {"focus", 2, (Blt_OpProc)FocusOp, 3, 3, "index",}, /**/
    {"get", 1, (Blt_OpProc)GetOp, 2, 0, "?-full? index ?index...?",}, /**/
    {"hide", 1, (Blt_OpProc)HideOp, 2, 0, "?-exact? ?-glob? ?-regexp? ?-nonmatching? ?-name string? ?-full string? ?-data string? ?--? ?index...?",},
    {"index", 3, (Blt_OpProc)IndexOp, 3, 5, "?-at index? string",},
    {"insert", 3, (Blt_OpProc)InsertOp, 3, 0, "?-at index? position label ?label...? ?option value?",},
    {"move", 1, (Blt_OpProc)MoveOp, 5, 5, "index into|before|after index",},
    {"nearest", 1, (Blt_OpProc)NearestOp, 4, 5, "x y ?varName?",}, /**/
    {"open", 1, (Blt_OpProc)OpenOp, 2, 0, "?-recurse? index...",}, /**/
    {"range", 1, (Blt_OpProc)RangeOp, 4, 5, "?-open? firstIndex lastIndex",},
    {"scan", 2, (Blt_OpProc)ScanOp, 5, 5, "dragto|mark x y",},
    {"see", 3, (Blt_OpProc)SeeOp, 3, 0, "?-anchor anchor? index",}, /**/
    {"selection", 3, (Blt_OpProc)SelectionOp, 2, 0, "oper args",},
    {"show", 2, (Blt_OpProc)ShowOp, 2, 0, "?-exact? ?-glob? ?-regexp? ?-nonmatching? ?-name string? ?-full string? ?-data string? ?--? ?index...?",},
    {"sort", 2, (Blt_OpProc)Blt_HtSortOp, 2, 0, "args",},
    {"text", 2, (Blt_OpProc)Blt_HtTextOp, 2, 0, "args",},
    {"toggle", 2, (Blt_OpProc)ToggleOp, 3, 3, "index",},
    {"xview", 1, (Blt_OpProc)XViewOp, 2, 5, "?moveto fract? ?scroll number what?",},
    {"yview", 1, (Blt_OpProc)YViewOp, 2, 5, "?moveto fract? ?scroll number what?",},
};

static int nSpecs = sizeof(operSpecs) / sizeof(Blt_OpSpec);

int
Blt_HtWidgetInstCmd(clientData, interp, argc, argv)
    ClientData clientData;	/* Information about the widget. */
    Tcl_Interp *interp;		/* Interpreter to report errors back to. */
    int argc;			/* Number of arguments. */
    char **argv;		/* Vector of argument strings. */
{
    Blt_OpProc opProc;
    Hiertable *htabPtr = (Hiertable *)clientData;
    int result;

    opProc = Blt_GetOperation(interp, nSpecs, operSpecs, BLT_OPER_ARG1, 
	argc, argv, 0);
    if (opProc == NULL) {
	return TCL_ERROR;
    }
    Tcl_Preserve((ClientData)htabPtr);
    result = (*opProc) (htabPtr, interp, argc, argv);
    Tcl_Release((ClientData)htabPtr);
    return result;
}

#endif /* NO_HIERTABLE */
