Logo Search packages:      
Sourcecode: saods9 version File versions

bltTreeViewCmd.c

/*
 * bltTreeViewCmd.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 "treeview" 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_TREEVIEW

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

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

static TreeViewCompareProc ExactCompare, GlobCompare, RegexpCompare;
static TreeViewApplyProc ShowEntryApplyProc, HideEntryApplyProc, 
      MapAncestorsApplyProc, FixSelectionsApplyProc;
static Tk_LostSelProc LostSelection;
static TreeViewApplyProc SelectEntryApplyProc;

extern Blt_CustomOption bltTreeViewIconsOption;
extern Blt_CustomOption bltTreeViewUidOption;
extern Blt_CustomOption bltTreeViewTreeOption;

extern Blt_ConfigSpec bltTreeViewButtonSpecs[];
extern Blt_ConfigSpec bltTreeViewSpecs[];
extern Blt_ConfigSpec bltTreeViewEntrySpecs[];

#define TAG_UNKNOWN      (1<<0)
#define TAG_RESERVED     (1<<1)
#define TAG_USER_DEFINED (1<<2)

#define TAG_SINGLE      (1<<3)
#define TAG_MULTIPLE    (1<<4)
#define TAG_ALL         (1<<5)

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

/*
 *----------------------------------------------------------------------
 *
 * DeleteNode --
 *
 *    Delete the node and its descendants.  Don't remove the root
 *    node, though.  If the root node is specified, simply remove
 *    all its children.
 *
 *---------------------------------------------------------------------- 
 */
static void
DeleteNode(tvPtr, node)
    TreeView *tvPtr;
    Blt_TreeNode node;
{
    Blt_TreeNode root;

    if (!Blt_TreeTagTableIsShared(tvPtr->tree)) {
      Blt_TreeClearTags(tvPtr->tree, node);
    }
    root = Blt_TreeRootNode(tvPtr->tree);
    if (node == root) {
      Blt_TreeNode next;
      /* Don't delete the root node. Simply clean out the tree. */
      for (node = Blt_TreeFirstChild(node); node != NULL; node = next) {
          next = Blt_TreeNextSibling(node);
          Blt_TreeDeleteNode(tvPtr->tree, node);
      }         
    } else if (Blt_TreeIsAncestor(root, node)) {
      Blt_TreeDeleteNode(tvPtr->tree, node);
    }
}

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

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

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

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

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


static TreeViewEntry *
LastEntry(tvPtr, entryPtr, mask)
    TreeView *tvPtr;
    TreeViewEntry *entryPtr;
    unsigned int mask;
{
    Blt_TreeNode next;
    TreeViewEntry *nextPtr;

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


/*
 *----------------------------------------------------------------------
 *
 * ShowEntryApplyProc --
 *
 * Results:
 *    Always returns TCL_OK.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
ShowEntryApplyProc(tvPtr, entryPtr)
    TreeView *tvPtr;          /* Not used. */
    TreeViewEntry *entryPtr;
{
    entryPtr->flags &= ~ENTRY_HIDDEN;
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * HideEntryApplyProc --
 *
 * Results:
 *    Always returns TCL_OK.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
HideEntryApplyProc(tvPtr, entryPtr)
    TreeView *tvPtr;          /* Not used. */
    TreeViewEntry *entryPtr;
{
    entryPtr->flags |= ENTRY_HIDDEN;
    return TCL_OK;
}

static void
MapAncestors(tvPtr, entryPtr)
    TreeView *tvPtr;
    TreeViewEntry *entryPtr;
{
    while (entryPtr != tvPtr->rootPtr) {
      entryPtr = Blt_TreeViewParentEntry(entryPtr);
      if (entryPtr->flags & (ENTRY_CLOSED | ENTRY_HIDDEN)) {
          tvPtr->flags |= TV_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(tvPtr, entryPtr)
    TreeView *tvPtr;
    TreeViewEntry *entryPtr;
{
    /*
     * Make sure that all the ancestors of this entry are mapped too.
     */
    while (entryPtr != tvPtr->rootPtr) {
      entryPtr = Blt_TreeViewParentEntry(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 TreeViewEntry *
FindPath(tvPtr, rootPtr, path)
    TreeView *tvPtr;
    TreeViewEntry *rootPtr;
    char *path;
{
    Blt_TreeNode child;
    char **compArr;
    char *name;
    int nComp;
    register char **p;
    TreeViewEntry *entryPtr;

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

    if (SplitPath(tvPtr, path, &nComp, &compArr) != TCL_OK) {
      return NULL;
    }
    for (p = compArr; *p != NULL; p++) {
      name = *p;
      child = Blt_TreeFindChild(entryPtr->node, name);
      if (child == NULL) {
          Blt_Free(compArr);
          goto error;
      }
      entryPtr = Blt_NodeToEntry(tvPtr, child);
    }
    Blt_Free(compArr);
    return entryPtr;
 error:
    {
      Tcl_DString dString;

      Blt_TreeViewGetFullName(tvPtr, entryPtr, FALSE, &dString);
      Tcl_AppendResult(tvPtr->interp, "can't find node \"", name,
             "\" in parent node \"", Tcl_DStringValue(&dString), "\"", 
            (char *)NULL);
      Tcl_DStringFree(&dString);
    }
    return NULL;

}

/*
 *----------------------------------------------------------------------
 *
 * NodeToObj --
 *
 *    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 Tcl_Obj *
NodeToObj(node)
    Blt_TreeNode node;
{
    char string[200];

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


static int
GetEntryFromSpecialId(tvPtr, string, entryPtrPtr)
    TreeView *tvPtr;
    char *string;
    TreeViewEntry **entryPtrPtr;
{
    Blt_TreeNode node;
    TreeViewEntry *fromPtr, *entryPtr;
    char c;

    entryPtr = NULL;
    fromPtr = tvPtr->fromPtr;
    if (fromPtr == NULL) {
      fromPtr = tvPtr->focusPtr;
    } 
    if (fromPtr == NULL) {
      fromPtr = tvPtr->rootPtr;
    }
    c = string[0];
    if (c == '@') {
      int x, y;

      if (Blt_GetXY(tvPtr->interp, tvPtr->tkwin, string, &x, &y) == TCL_OK) {
          *entryPtrPtr = Blt_TreeViewNearestEntry(tvPtr, x, y, TRUE);
      }
    } else if ((c == 'b') && (strcmp(string, "bottom") == 0)) {
      if (tvPtr->flatView) {
          entryPtr = tvPtr->flatArr[tvPtr->nEntries - 1];
      } else {
          entryPtr = LastEntry(tvPtr, tvPtr->rootPtr, ENTRY_MASK);
      }
    } else if ((c == 't') && (strcmp(string, "top") == 0)) {
      if (tvPtr->flatView) {
          entryPtr = tvPtr->flatArr[0];
      } else {
          entryPtr = tvPtr->rootPtr;
          if (tvPtr->flags & TV_HIDE_ROOT) {
            entryPtr = Blt_TreeViewNextEntry(entryPtr, ENTRY_MASK);
          }
      }
    } else if ((c == 'e') && (strcmp(string, "end") == 0)) {
      entryPtr = LastEntry(tvPtr, tvPtr->rootPtr, ENTRY_MASK);
    } else if ((c == 'a') && (strcmp(string, "anchor") == 0)) {
      entryPtr = tvPtr->selAnchorPtr;
    } else if ((c == 'f') && (strcmp(string, "focus") == 0)) {
      entryPtr = tvPtr->focusPtr;
      if ((entryPtr == tvPtr->rootPtr) && (tvPtr->flags & TV_HIDE_ROOT)) {
          entryPtr = Blt_TreeViewNextEntry(tvPtr->rootPtr, ENTRY_MASK);
      }
    } else if ((c == 'r') && (strcmp(string, "root") == 0)) {
      entryPtr = tvPtr->rootPtr;
    } else if ((c == 'p') && (strcmp(string, "parent") == 0)) {
      if (fromPtr != tvPtr->rootPtr) {
          entryPtr = Blt_TreeViewParentEntry(fromPtr);
      }
    } else if ((c == 'c') && (strcmp(string, "current") == 0)) {
      /* Can't trust picked item, if entries have been 
       * added or deleted. */
      if (!(tvPtr->flags & TV_DIRTY)) {
          ClientData context;

          context = Blt_GetCurrentContext(tvPtr->bindTable);
          if ((context == ITEM_ENTRY) || 
            (context == ITEM_ENTRY_BUTTON) ||
            (context >= ITEM_STYLE)) {
            entryPtr = Blt_GetCurrentItem(tvPtr->bindTable);
          }
      }
    } else if ((c == 'u') && (strcmp(string, "up") == 0)) {
      entryPtr = fromPtr;
      if (tvPtr->flatView) {
          int i;

          i = entryPtr->flatIndex - 1;
          if (i >= 0) {
            entryPtr = tvPtr->flatArr[i];
          }
      } else {
          entryPtr = Blt_TreeViewPrevEntry(fromPtr, ENTRY_MASK);
          if (entryPtr == NULL) {
            entryPtr = fromPtr;
          }
          if ((entryPtr == tvPtr->rootPtr) && 
            (tvPtr->flags & TV_HIDE_ROOT)) {
            entryPtr = Blt_TreeViewNextEntry(entryPtr, ENTRY_MASK);
          }
      }
    } else if ((c == 'd') && (strcmp(string, "down") == 0)) {
      entryPtr = fromPtr;
      if (tvPtr->flatView) {
          int i;

          i = entryPtr->flatIndex + 1;
          if (i < tvPtr->nEntries) {
            entryPtr = tvPtr->flatArr[i];
          }
      } else {
          entryPtr = Blt_TreeViewNextEntry(fromPtr, ENTRY_MASK);
          if (entryPtr == NULL) {
            entryPtr = fromPtr;
          }
          if ((entryPtr == tvPtr->rootPtr) && 
            (tvPtr->flags & TV_HIDE_ROOT)) {
            entryPtr = Blt_TreeViewNextEntry(entryPtr, ENTRY_MASK);
          }
      }
    } else if (((c == 'l') && (strcmp(string, "last") == 0)) ||
             ((c == 'p') && (strcmp(string, "prev") == 0))) {
      entryPtr = fromPtr;
      if (tvPtr->flatView) {
          int i;

          i = entryPtr->flatIndex - 1;
          if (i < 0) {
            i = tvPtr->nEntries - 1;
          }
          entryPtr = tvPtr->flatArr[i];
      } else {
          entryPtr = Blt_TreeViewPrevEntry(fromPtr, ENTRY_MASK);
          if (entryPtr == NULL) {
            entryPtr = LastEntry(tvPtr, tvPtr->rootPtr, ENTRY_MASK);
          }
          if ((entryPtr == tvPtr->rootPtr) && 
            (tvPtr->flags & TV_HIDE_ROOT)) {
            entryPtr = Blt_TreeViewNextEntry(entryPtr, ENTRY_MASK);
          }
      }
    } else if ((c == 'n') && (strcmp(string, "next") == 0)) {
      entryPtr = fromPtr;
      if (tvPtr->flatView) {
          int i;

          i = entryPtr->flatIndex + 1; 
          if (i >= tvPtr->nEntries) {
            i = 0;
          }
          entryPtr = tvPtr->flatArr[i];
      } else {
          entryPtr = Blt_TreeViewNextEntry(fromPtr, ENTRY_MASK);
          if (entryPtr == NULL) {
            if (tvPtr->flags & TV_HIDE_ROOT) {
                entryPtr = Blt_TreeViewNextEntry(tvPtr->rootPtr,ENTRY_MASK);
            } else {
                entryPtr = tvPtr->rootPtr;
            }
          }
      }
    } else if ((c == 'n') && (strcmp(string, "nextsibling") == 0)) {
      node = Blt_TreeNextSibling(fromPtr->node);
      if (node != NULL) {
          entryPtr = Blt_NodeToEntry(tvPtr, node);
      }
    } else if ((c == 'p') && (strcmp(string, "prevsibling") == 0)) {
      node = Blt_TreePrevSibling(fromPtr->node);
      if (node != NULL) {
          entryPtr = Blt_NodeToEntry(tvPtr, node);
      }
    } else if ((c == 'v') && (strcmp(string, "view.top") == 0)) {
      if (tvPtr->nVisible > 0) {
          entryPtr = tvPtr->visibleArr[0];
      }
    } else if ((c == 'v') && (strcmp(string, "view.bottom") == 0)) {
      if (tvPtr->nVisible > 0) {
          entryPtr = tvPtr->visibleArr[tvPtr->nVisible - 1];
      } 
    } else {
      return TCL_ERROR;
    }
    *entryPtrPtr = entryPtr;
    return TCL_OK;
}

static int
GetTagInfo(tvPtr, tagName, infoPtr)
    TreeView *tvPtr;
    char *tagName;
    TreeViewTagInfo *infoPtr;
{
    
    infoPtr->tagType = TAG_RESERVED | TAG_SINGLE;
    infoPtr->entryPtr = NULL;

    if (strcmp(tagName, "all") == 0) {
      infoPtr->entryPtr = tvPtr->rootPtr;
      infoPtr->tagType |= TAG_ALL;
    } else {
      Blt_HashTable *tablePtr;

      tablePtr = Blt_TreeTagHashTable(tvPtr->tree, tagName);
      if (tablePtr != NULL) {
          Blt_HashEntry *hPtr;
          
          infoPtr->tagType = TAG_USER_DEFINED; /* Empty tags are not
                                      * an error. */
          hPtr = Blt_FirstHashEntry(tablePtr, &infoPtr->cursor); 
          if (hPtr != NULL) {
            Blt_TreeNode node;

            node = Blt_GetHashValue(hPtr);
            infoPtr->entryPtr = Blt_NodeToEntry(tvPtr, node);
            if (tablePtr->numEntries > 1) {
                infoPtr->tagType |= TAG_MULTIPLE;
            }
          }
      }  else {
          infoPtr->tagType = TAG_UNKNOWN;
          Tcl_AppendResult(tvPtr->interp, "can't find tag or id \"", tagName, 
            "\" in \"", Tk_PathName(tvPtr->tkwin), "\"", (char *)NULL);
          return TCL_ERROR;
      }
    }
    return TCL_OK;
}

/*ARGSUSED*/
void
Blt_TreeViewGetTags(interp, tvPtr, entryPtr, list)
    Tcl_Interp *interp;       /* Not used. */
    TreeView *tvPtr;
    TreeViewEntry *entryPtr;
    Blt_List list;
{
    Blt_HashEntry *hPtr;
    Blt_HashSearch cursor;
    Blt_TreeTagEntry *tPtr;

    for (hPtr = Blt_TreeFirstTag(tvPtr->tree, &cursor); hPtr != NULL; 
      hPtr = Blt_NextHashEntry(&cursor)) {
      tPtr = Blt_GetHashValue(hPtr);
      hPtr = Blt_FindHashEntry(&tPtr->nodeTable, (char *)entryPtr->node);
      if (hPtr != NULL) {
          Blt_ListAppend(list, Blt_TreeViewGetUid(tvPtr, tPtr->tagName),0);
      }
    }
}

/*
 *----------------------------------------------------------------------
 *
 * AddTag --
 *
 *---------------------------------------------------------------------- 
 */
static int
AddTag(tvPtr, node, tagName)
    TreeView *tvPtr;
    Blt_TreeNode node;
    char *tagName;
{
    TreeViewEntry *entryPtr;

    if (strcmp(tagName, "root") == 0) {
      Tcl_AppendResult(tvPtr->interp, "can't add reserved tag \"",
                   tagName, "\"", (char *)NULL);
      return TCL_ERROR;
    }
    if (isdigit(UCHAR(tagName[0]))) {
      Tcl_AppendResult(tvPtr->interp, "invalid tag \"", tagName, 
            "\": can't start with digit", (char *)NULL);
      return TCL_ERROR;
    } 
    if (isdigit(UCHAR(tagName[0]))) {
      Tcl_AppendResult(tvPtr->interp, "invalid tag \"", tagName, 
            "\": can't start with digit", (char *)NULL);
      return TCL_ERROR;
    } 
    if (tagName[0] == '@') {
      Tcl_AppendResult(tvPtr->interp, "invalid tag \"", tagName, 
            "\": can't start with \"@\"", (char *)NULL);
      return TCL_ERROR;
    } 
    tvPtr->fromPtr = NULL;
    if (GetEntryFromSpecialId(tvPtr, tagName, &entryPtr) == TCL_OK) {
      Tcl_AppendResult(tvPtr->interp, "invalid tag \"", tagName, 
            "\": is a special id", (char *)NULL);
      return TCL_ERROR;
    }
    /* Add the tag to the node. */
    Blt_TreeAddTag(tvPtr->tree, node, tagName);
    return TCL_OK;
}
    
TreeViewEntry *
Blt_TreeViewFirstTaggedEntry(infoPtr)     
    TreeViewTagInfo *infoPtr;
{
    return infoPtr->entryPtr;
}

int
Blt_TreeViewFindTaggedEntries(tvPtr, objPtr, infoPtr) 
    TreeView *tvPtr;
    Tcl_Obj *objPtr;
    TreeViewTagInfo *infoPtr;
{
    char *tagName;
    TreeViewEntry *entryPtr;

    tagName = Tcl_GetString(objPtr); 
    tvPtr->fromPtr = NULL;
    if (isdigit(UCHAR(tagName[0]))) {
      int inode;
      Blt_TreeNode node;

      if (Tcl_GetIntFromObj(tvPtr->interp, objPtr, &inode) != TCL_OK) {
          return TCL_ERROR;
      }
      node = Blt_TreeGetNode(tvPtr->tree, inode);
      infoPtr->entryPtr = Blt_NodeToEntry(tvPtr, node);
      infoPtr->tagType = (TAG_RESERVED | TAG_SINGLE);
    } else if (GetEntryFromSpecialId(tvPtr, tagName, &entryPtr) == TCL_OK) {
      infoPtr->entryPtr = entryPtr;
      infoPtr->tagType = (TAG_RESERVED | TAG_SINGLE);
    } else {
      if (GetTagInfo(tvPtr, tagName, infoPtr) != TCL_OK) {
          return TCL_ERROR;
      }
    }
    return TCL_OK;
}

TreeViewEntry *
Blt_TreeViewNextTaggedEntry(infoPtr) 
    TreeViewTagInfo *infoPtr;
{
    TreeViewEntry *entryPtr;

    entryPtr = NULL;
    if (infoPtr->entryPtr != NULL) {
      TreeView *tvPtr = infoPtr->entryPtr->tvPtr;

      if (infoPtr->tagType & TAG_ALL) {
          entryPtr = Blt_TreeViewNextEntry(infoPtr->entryPtr, 0);
      } else if (infoPtr->tagType & TAG_MULTIPLE) {
          Blt_HashEntry *hPtr;
          
          hPtr = Blt_NextHashEntry(&infoPtr->cursor);
          if (hPtr != NULL) {
            Blt_TreeNode node;

            node = Blt_GetHashValue(hPtr);
            entryPtr = Blt_NodeToEntry(tvPtr, node);
          }
      } 
      infoPtr->entryPtr = entryPtr;
    }
    return entryPtr;
}

/*
 *----------------------------------------------------------------------
 *
 * GetEntryFromObj2 --
 *
 *    Converts a string into node pointer.  The string may be in one
 *    of the following forms:
 *
 *        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.
 *        @x,y          - Closest node to the specified X-Y position.
 *
 * 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
GetEntryFromObj2(tvPtr, objPtr, entryPtrPtr)
    TreeView *tvPtr;
    Tcl_Obj *objPtr;
    TreeViewEntry **entryPtrPtr;
{
    Tcl_Interp *interp;
    char *string;
    TreeViewTagInfo info;

    interp = tvPtr->interp;

    string = Tcl_GetString(objPtr);
    *entryPtrPtr = NULL;
    if (isdigit(UCHAR(string[0]))) {    
      Blt_TreeNode node;
      int inode;

      if (Tcl_GetIntFromObj(interp, objPtr, &inode) != TCL_OK) {
          return TCL_ERROR;
      }
      node = Blt_TreeGetNode(tvPtr->tree, inode);
      if (node != NULL) {
          *entryPtrPtr = Blt_NodeToEntry(tvPtr, node);
      }
      return TCL_OK;          /* Node Id. */
    }
    if (GetEntryFromSpecialId(tvPtr, string, entryPtrPtr) == TCL_OK) {
      return TCL_OK;          /* Special Id. */
    }
    if (GetTagInfo(tvPtr, string, &info) != TCL_OK) {
      return TCL_ERROR;
    }
    if (info.tagType & TAG_MULTIPLE) {
      Tcl_AppendResult(interp, "more than one entry tagged as \"", string, 
            "\"", (char *)NULL);
      return TCL_ERROR;
    }
    *entryPtrPtr = info.entryPtr;
    return TCL_OK;            /* Singleton tag. */
}

static int
GetEntryFromObj(tvPtr, objPtr, entryPtrPtr)
    TreeView *tvPtr;
    Tcl_Obj *objPtr;
    TreeViewEntry **entryPtrPtr;
{
    tvPtr->fromPtr = NULL;
    return GetEntryFromObj2(tvPtr, objPtr, entryPtrPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * Blt_TreeViewGetEntry --
 *
 *    Returns an entry based upon its index.
 *
 * 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_TreeViewGetEntry(tvPtr, objPtr, entryPtrPtr)
    TreeView *tvPtr;
    Tcl_Obj *objPtr;
    TreeViewEntry **entryPtrPtr;
{
    TreeViewEntry *entryPtr;

    if (GetEntryFromObj(tvPtr, objPtr, &entryPtr) != TCL_OK) {
      return TCL_ERROR;
    }
    if (entryPtr == NULL) {
      Tcl_ResetResult(tvPtr->interp);
      Tcl_AppendResult(tvPtr->interp, "can't find entry \"", 
            Tcl_GetString(objPtr), "\" in \"", Tk_PathName(tvPtr->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);
}

static TreeViewEntry *
GetNthEntry(
    TreeViewEntry *parentPtr,
    int position,
    unsigned int mask)
{
    TreeViewEntry *entryPtr;
    int count;

    count = 0;
    for(entryPtr = Blt_TreeViewFirstChild(parentPtr, mask); entryPtr != NULL; 
      entryPtr = Blt_TreeViewNextSibling(entryPtr, mask)) {
      if (count == position) {
          return entryPtr;
      }
    }
    return Blt_TreeViewLastChild(parentPtr, mask);
}

/*
 * Preprocess the command string for percent substitution.
 */
void
Blt_TreeViewPercentSubst(tvPtr, entryPtr, command, resultPtr)
    TreeView *tvPtr;
    TreeViewEntry *entryPtr;
    char *command;
    Tcl_DString *resultPtr;
{
    register char *last, *p;
    char *fullName;
    Tcl_DString dString;

    /*
     * Get the full path name of the node, in case we need to
     * substitute for it.
     */
    fullName = Blt_TreeViewGetFullName(tvPtr, entryPtr, TRUE, &dString);
    Tcl_DStringInit(resultPtr);
    /* Append the widget name and the node .t 0 */
    for (last = p = command; *p != '\0'; p++) {
      if (*p == '%') {
          char *string;
          char buf[3];

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

/*
 *----------------------------------------------------------------------
 *
 * SelectEntryApplyProc --
 *
 *    Sets the selection flag for a node.  The selection flag is
 *    set/cleared/toggled based upon the flag set in the treeview
 *    widget.
 *
 * Results:
 *    Always returns TCL_OK.
 *
 *----------------------------------------------------------------------
 */
static int
SelectEntryApplyProc(tvPtr, entryPtr)
    TreeView *tvPtr;
    TreeViewEntry *entryPtr;
{
    Blt_HashEntry *hPtr;

    switch (tvPtr->flags & TV_SELECT_MASK) {
    case TV_SELECT_CLEAR:
      Blt_TreeViewDeselectEntry(tvPtr, entryPtr);
      break;

    case TV_SELECT_SET:
      Blt_TreeViewSelectEntry(tvPtr, entryPtr);
      break;

    case TV_SELECT_TOGGLE:
      hPtr = Blt_FindHashEntry(&tvPtr->selectTable, (char *)entryPtr);
      if (hPtr != NULL) {
          Blt_TreeViewDeselectEntry(tvPtr, entryPtr);
      } else {
          Blt_TreeViewSelectEntry(tvPtr, 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(tvPtr)
    TreeView *tvPtr;
{
    if (!(tvPtr->flags & TV_SELECT_PENDING)) {
      tvPtr->flags |= TV_SELECT_PENDING;
      Tcl_DoWhenIdle(Blt_TreeViewSelectCmdProc, tvPtr);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * Blt_TreeViewPruneSelection --
 *
 *    The root entry being deleted or closed.  Deselect any of its
 *    descendants that are currently selected. 
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      If any of the entry's descendants are deselected the widget
 *    is redrawn and the a selection command callback is invoked
 *    (if there's one configured).
 *
 *----------------------------------------------------------------------
 */
void
Blt_TreeViewPruneSelection(tvPtr, rootPtr)
    TreeView *tvPtr;
    TreeViewEntry *rootPtr;
{
    Blt_ChainLink *linkPtr, *nextPtr;
    TreeViewEntry *entryPtr;
    int selectionChanged;

    /* 
     * Check if any of the currently selected entries are a descendant
     * of of the current root entry.  Deselect the entry and indicate
     * that the treeview widget needs to be redrawn.
     */
    selectionChanged = FALSE;
    for (linkPtr = Blt_ChainFirstLink(tvPtr->selChainPtr); linkPtr != NULL; 
       linkPtr = nextPtr) {
      nextPtr = Blt_ChainNextLink(linkPtr);
      entryPtr = Blt_ChainGetValue(linkPtr);
      if (Blt_TreeIsAncestor(rootPtr->node, entryPtr->node)) {
          Blt_TreeViewDeselectEntry(tvPtr, entryPtr);
          selectionChanged = TRUE;
      }
    }
    if (selectionChanged) {
      Blt_TreeViewEventuallyRedraw(tvPtr);
      if (tvPtr->selectCmd != NULL) {
          EventuallyInvokeSelectCmd(tvPtr);
      }
    }
}


/*
 * --------------------------------------------------------------
 *
 * TreeView operations
 *
 * --------------------------------------------------------------
 */

/*ARGSUSED*/
static int
FocusOp(tvPtr, interp, objc, objv)
    TreeView *tvPtr;
    Tcl_Interp *interp;
    int objc;
    Tcl_Obj *CONST *objv;
{
    if (objc == 3) {
      TreeViewEntry *entryPtr;

      if (GetEntryFromObj(tvPtr, objv[2], &entryPtr) != TCL_OK) {
          return TCL_ERROR;
      }
      if ((entryPtr != NULL) && (entryPtr != tvPtr->focusPtr)) {
          if (entryPtr->flags & ENTRY_HIDDEN) {
            /* Doesn't make sense to set focus to a node you can't see. */
            MapAncestors(tvPtr, entryPtr);
          }
          /* Changing focus can only affect the visible entries.  The
           * entry layout stays the same. */
          if (tvPtr->focusPtr != NULL) {
            tvPtr->focusPtr->flags |= ENTRY_REDRAW;
          } 
          entryPtr->flags |= ENTRY_REDRAW;
          tvPtr->flags |= TV_SCROLL;
          tvPtr->focusPtr = entryPtr;
      }
      Blt_TreeViewEventuallyRedraw(tvPtr);
    }
    Blt_SetFocusItem(tvPtr->bindTable, tvPtr->focusPtr, ITEM_ENTRY);
    if (tvPtr->focusPtr != NULL) {
      Tcl_SetObjResult(interp, NodeToObj(tvPtr->focusPtr->node));
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * BboxOp --
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
BboxOp(tvPtr, interp, objc, objv)
    TreeView *tvPtr;
    Tcl_Interp *interp;
    int objc;                 /* Not used. */
    Tcl_Obj *CONST *objv;
{
    register int i;
    TreeViewEntry *entryPtr;
    int width, height, yBot;
    int left, top, right, bottom;
    int screen;
    int lWidth;
    char *string;

    if (tvPtr->flags & TV_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_TreeViewComputeLayout(tvPtr);
    }
    left = tvPtr->worldWidth;
    top = tvPtr->worldHeight;
    right = bottom = 0;

    screen = FALSE;
    string = Tcl_GetString(objv[2]);
    if ((string[0] == '-') && (strcmp(string, "-screen") == 0)) {
      screen = TRUE;
      objc--, objv++;
    }
    for (i = 2; i < objc; i++) {
      string = Tcl_GetString(objv[i]);
      if ((string[0] == 'a') && (strcmp(string, "all") == 0)) {
          left = top = 0;
          right = tvPtr->worldWidth;
          bottom = tvPtr->worldHeight;
          break;
      }
      if (GetEntryFromObj(tvPtr, objv[i], &entryPtr) != TCL_OK) {
          return TCL_ERROR;
      }
      if (entryPtr == NULL) {
          continue;
      }
      if (entryPtr->flags & ENTRY_HIDDEN) {
          continue;
      }
      yBot = entryPtr->worldY + entryPtr->height;
      height = VPORTHEIGHT(tvPtr);
      if ((yBot <= tvPtr->yOffset) &&
          (entryPtr->worldY >= (tvPtr->yOffset + height))) {
          continue;
      }
      if (bottom < yBot) {
          bottom = yBot;
      }
      if (top > entryPtr->worldY) {
          top = entryPtr->worldY;
      }
      lWidth = ICONWIDTH(DEPTH(tvPtr, 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(tvPtr);
      height = VPORTHEIGHT(tvPtr);
      /*
       * 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 < tvPtr->xOffset) || (bottom < tvPtr->yOffset) ||
          (left >= (tvPtr->xOffset + width)) ||
          (top >= (tvPtr->yOffset + height))) {
          return TCL_OK;
      }
      /* Otherwise clip the coordinates at the view port boundaries. */
      if (left < tvPtr->xOffset) {
          left = tvPtr->xOffset;
      } else if (right > (tvPtr->xOffset + width)) {
          right = tvPtr->xOffset + width;
      }
      if (top < tvPtr->yOffset) {
          top = tvPtr->yOffset;
      } else if (bottom > (tvPtr->yOffset + height)) {
          bottom = tvPtr->yOffset + height;
      }
      left = SCREENX(tvPtr, left), top = SCREENY(tvPtr, top);
      right = SCREENX(tvPtr, right), bottom = SCREENY(tvPtr, bottom);
    }
    if ((left < right) && (top < bottom)) {
      Tcl_Obj *listObjPtr;

      listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL);
      Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewIntObj(left));
      Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewIntObj(top));
      Tcl_ListObjAppendElement(interp, listObjPtr, 
                         Tcl_NewIntObj(right - left));
      Tcl_ListObjAppendElement(interp, listObjPtr, 
                         Tcl_NewIntObj(bottom - top));
      Tcl_SetObjResult(interp, listObjPtr);
    }
    return TCL_OK;
}

static void
DrawButton(tvPtr, entryPtr)
    TreeView *tvPtr;
    TreeViewEntry *entryPtr;
{
    Drawable drawable;
    int sx, sy, dx, dy;
    int width, height;
    int left, right, top, bottom;

    dx = SCREENX(tvPtr, entryPtr->worldX) + entryPtr->buttonX;
    dy = SCREENY(tvPtr, entryPtr->worldY) + entryPtr->buttonY;
    width = tvPtr->button.width;
    height = tvPtr->button.height;

    top = tvPtr->titleHeight + tvPtr->inset;
    bottom = Tk_Height(tvPtr->tkwin) - tvPtr->inset;
    left = tvPtr->inset;
    right = Tk_Width(tvPtr->tkwin) - tvPtr->inset;

    if (((dx + width) < left) || (dx > right) ||
      ((dy + height) < top) || (dy > bottom)) {
      return;                 /* Value is clipped. */
    }
    drawable = Tk_GetPixmap(tvPtr->display, Tk_WindowId(tvPtr->tkwin), 
      width, height, Tk_Depth(tvPtr->tkwin));
    /* Draw the background of the value. */
    Blt_TreeViewDrawButton(tvPtr, entryPtr, drawable, 0, 0);

    /* Clip the drawable if necessary */
    sx = sy = 0;
    if (dx < left) {
      width -= left - dx;
      sx += left - dx;
      dx = left;
    }
    if ((dx + width) >= right) {
      width -= (dx + width) - right;
    }
    if (dy < top) {
      height -= top - dy;
      sy += top - dy;
      dy = top;
    }
    if ((dy + height) >= bottom) {
      height -= (dy + height) - bottom;
    }
    XCopyArea(tvPtr->display, drawable, Tk_WindowId(tvPtr->tkwin), 
      tvPtr->lineGC, sx, sy, width,  height, dx, dy);
    Tk_FreePixmap(tvPtr->display, drawable);
}

/*
 *----------------------------------------------------------------------
 *
 * ButtonActivateOp --
 *
 *    Selects the button to appear active.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
ButtonActivateOp(tvPtr, interp, objc, objv)
    TreeView *tvPtr;
    Tcl_Interp *interp;
    int objc;                 /* Not used. */
    Tcl_Obj *CONST *objv;
{
    TreeViewEntry *oldPtr, *newPtr;
    char *string;

    string = Tcl_GetString(objv[3]);
    if (string[0] == '\0') {
      newPtr = NULL;
    } else if (GetEntryFromObj(tvPtr, objv[3], &newPtr) != TCL_OK) {
      return TCL_ERROR;
    }
    if (tvPtr->treeColumn.hidden) {
      return TCL_OK;
    }
    if ((newPtr != NULL) && !(newPtr->flags & ENTRY_HAS_BUTTON)) {
      newPtr = NULL;
    }
    oldPtr = tvPtr->activeButtonPtr;
    tvPtr->activeButtonPtr = newPtr;
    if (!(tvPtr->flags & TV_REDRAW) && (newPtr != oldPtr)) {
      if ((oldPtr != NULL) && (oldPtr != tvPtr->rootPtr)) {
          DrawButton(tvPtr, oldPtr);
      }
      if ((newPtr != NULL) && (newPtr != tvPtr->rootPtr)) {
          DrawButton(tvPtr, newPtr);
      }
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ButtonBindOp --
 *
 *      .t bind tag sequence command
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
ButtonBindOp(tvPtr, interp, objc, objv)
    TreeView *tvPtr;
    Tcl_Interp *interp;
    int objc;                 /* Not used. */
    Tcl_Obj *CONST *objv;
{
    ClientData object;
    char *string;

    string = Tcl_GetString(objv[3]);
    /* Assume that this is a binding tag. */
    object = Blt_TreeViewButtonTag(tvPtr, string);
    return Blt_ConfigureBindingsFromObj(interp, tvPtr->bindTable, object, 
      objc - 4, objv + 4);
}

/*
 *----------------------------------------------------------------------
 *
 * ButtonCgetOp --
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
ButtonCgetOp(tvPtr, interp, objc, objv)
    TreeView *tvPtr;
    Tcl_Interp *interp;
    int objc;                 /* Not used. */
    Tcl_Obj *CONST *objv;
{
    return Blt_ConfigureValueFromObj(interp, tvPtr->tkwin, 
      bltTreeViewButtonSpecs, (char *)tvPtr, objv[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 tvPtr; old resources get freed, if there
 *    were any.  The hypertext is redisplayed.
 *
 *----------------------------------------------------------------------
 */
static int
ButtonConfigureOp(tvPtr, interp, objc, objv)
    TreeView *tvPtr;
    Tcl_Interp *interp;
    int objc;
    Tcl_Obj *CONST *objv;
{
    if (objc == 3) {
      return Blt_ConfigureInfoFromObj(interp, tvPtr->tkwin, 
          bltTreeViewButtonSpecs, (char *)tvPtr, (Tcl_Obj *)NULL, 0);
    } else if (objc == 4) {
      return Blt_ConfigureInfoFromObj(interp, tvPtr->tkwin, 
            bltTreeViewButtonSpecs, (char *)tvPtr, objv[3], 0);
    }
    bltTreeViewIconsOption.clientData = tvPtr;
    if (Blt_ConfigureWidgetFromObj(tvPtr->interp, tvPtr->tkwin, 
            bltTreeViewButtonSpecs, objc - 3, objv + 3, (char *)tvPtr, 
            BLT_CONFIG_OBJV_ONLY) != TCL_OK) {
      return TCL_ERROR;
    }
    Blt_TreeViewConfigureButtons(tvPtr);
    Blt_TreeViewEventuallyRedraw(tvPtr);
    return TCL_OK;
}

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

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

static int
ButtonOp(tvPtr, interp, objc, objv)
    TreeView *tvPtr;
    Tcl_Interp *interp;
    int objc;
    Tcl_Obj *CONST *objv;
{
    Blt_Op proc;
    int result;

    proc = Blt_GetOpFromObj(interp, nButtonOps, buttonOps, BLT_OP_ARG2, objc, 
      objv, 0);
    if (proc == NULL) {
      return TCL_ERROR;
    }
    result = (*proc) (tvPtr, interp, objc, objv);
    return result;
}

/*
 *----------------------------------------------------------------------
 *
 * CgetOp --
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
CgetOp(tvPtr, interp, objc, objv)
    TreeView *tvPtr;
    Tcl_Interp *interp;
    int objc;                 /* Not used. */
    Tcl_Obj *CONST *objv;
{
    return Blt_ConfigureValueFromObj(interp, tvPtr->tkwin, bltTreeViewSpecs,
      (char *)tvPtr, objv[2], 0);
}

/*ARGSUSED*/
static int
CloseOp(tvPtr, interp, objc, objv)
    TreeView *tvPtr;
    Tcl_Interp *interp;       /* Not used. */
    int objc;
    Tcl_Obj *CONST *objv;
{
    TreeViewEntry *entryPtr;
    TreeViewTagInfo info;
    int recurse, result;
    register int i;

    recurse = FALSE;

    if (objc > 2) {
      char *string;
      int length;

      string = Tcl_GetStringFromObj(objv[2], &length);
      if ((string[0] == '-') && (length > 1) && 
          (strncmp(string, "-recurse", length) == 0)) {
          objv++, objc--;
          recurse = TRUE;
      }
    }
    for (i = 2; i < objc; i++) {
      if (Blt_TreeViewFindTaggedEntries(tvPtr, objv[i], &info) != TCL_OK) {
          return TCL_ERROR;
      }
      for (entryPtr = Blt_TreeViewFirstTaggedEntry(&info); entryPtr != NULL; 
           entryPtr = Blt_TreeViewNextTaggedEntry(&info)) {
          /* 
           * Clear the selections for any entries that may have become
           * hidden by closing the node.  
           */
          Blt_TreeViewPruneSelection(tvPtr, 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 ((tvPtr->focusPtr != NULL) && 
            (Blt_TreeIsAncestor(entryPtr->node, tvPtr->focusPtr->node))) {
            tvPtr->focusPtr = entryPtr;
            Blt_SetFocusItem(tvPtr->bindTable, tvPtr->focusPtr, ITEM_ENTRY);
          }
          if ((tvPtr->selAnchorPtr != NULL) && 
            (Blt_TreeIsAncestor(entryPtr->node, 
                            tvPtr->selAnchorPtr->node))) {
            tvPtr->selMarkPtr = tvPtr->selAnchorPtr = NULL;
          }
          if ((tvPtr->activePtr != NULL) && 
            (Blt_TreeIsAncestor(entryPtr->node, tvPtr->activePtr->node))) {
            tvPtr->activePtr = entryPtr;
          }
          if (recurse) {
            result = Blt_TreeViewApply(tvPtr, entryPtr, 
                                 Blt_TreeViewCloseEntry, 0);
          } else {
            result = Blt_TreeViewCloseEntry(tvPtr, entryPtr);
          }
          if (result != TCL_OK) {
            return TCL_ERROR;
          } 
      }
    }
    /* Closing a node may affect the visible entries and the 
     * the world layout of the entries. */
    tvPtr->flags |= (TV_LAYOUT | TV_DIRTY | TV_RESORT);
    Blt_TreeViewEventuallyRedraw(tvPtr);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ConfigureOp --
 *
 *    This procedure is called to process an objv/objc 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 tvPtr; old resources get freed, if there
 *    were any.  The widget is redisplayed.
 *
 *----------------------------------------------------------------------
 */
static int
ConfigureOp(tvPtr, interp, objc, objv)
    TreeView *tvPtr;
    Tcl_Interp *interp;
    int objc;
    Tcl_Obj *CONST *objv;
{
    if (objc == 2) {
      return Blt_ConfigureInfoFromObj(interp, tvPtr->tkwin, bltTreeViewSpecs,
            (char *)tvPtr, (Tcl_Obj *)NULL, 0);
    } else if (objc == 3) {
      return Blt_ConfigureInfoFromObj(interp, tvPtr->tkwin, 
            bltTreeViewSpecs, (char *)tvPtr, objv[2], 0);
    } 
    bltTreeViewIconsOption.clientData = tvPtr;
    bltTreeViewTreeOption.clientData = tvPtr;
    if (Blt_ConfigureWidgetFromObj(interp, tvPtr->tkwin, bltTreeViewSpecs, 
      objc - 2, objv + 2, (char *)tvPtr, BLT_CONFIG_OBJV_ONLY) != TCL_OK) {
      return TCL_ERROR;
    }
    if (Blt_TreeViewUpdateWidget(interp, tvPtr) != TCL_OK) {
      return TCL_ERROR;
    }
    Blt_TreeViewEventuallyRedraw(tvPtr);
    return TCL_OK;
}

/*ARGSUSED*/
static int
CurselectionOp(tvPtr, interp, objc, objv)
    TreeView *tvPtr;
    Tcl_Interp *interp;       /* Not used. */
    int objc;                 /* Not used. */
    Tcl_Obj *CONST *objv;     /* Not used. */
{
    TreeViewEntry *entryPtr;
    Tcl_Obj *listObjPtr, *objPtr;

    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL);
    if (tvPtr->flags & TV_SELECT_SORTED) {
      Blt_ChainLink *linkPtr;

      for (linkPtr = Blt_ChainFirstLink(tvPtr->selChainPtr); linkPtr != NULL;
           linkPtr = Blt_ChainNextLink(linkPtr)) {
          entryPtr = Blt_ChainGetValue(linkPtr);
          objPtr = NodeToObj(entryPtr->node);
          Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
                  
      }
    } else {
      for (entryPtr = tvPtr->rootPtr; entryPtr != NULL; 
           entryPtr = Blt_TreeViewNextEntry(entryPtr, ENTRY_MASK)) {
          if (Blt_TreeViewEntryIsSelected(tvPtr, entryPtr)) {
            objPtr = NodeToObj(entryPtr->node);
            Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
          }
      }
    }
    Tcl_SetObjResult(interp, listObjPtr);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * BindOp --
 *
 *      .t bind tagOrId sequence command
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
BindOp(tvPtr, interp, objc, objv)
    TreeView *tvPtr;
    Tcl_Interp *interp;
    int objc;
    Tcl_Obj *CONST *objv;
{
    ClientData object;
    TreeViewEntry *entryPtr;
    char *string;

    /*
     * Entries are selected by id only.  All other strings are
     * interpreted as a binding tag.
     */
    string = Tcl_GetString(objv[2]);
    if (isdigit(UCHAR(string[0]))) {
      Blt_TreeNode node;
      int inode;

      if (Tcl_GetIntFromObj(tvPtr->interp, objv[2], &inode) != TCL_OK) {
          return TCL_ERROR;
      }
      node = Blt_TreeGetNode(tvPtr->tree, inode);
      object = Blt_NodeToEntry(tvPtr, node);
    } else if (GetEntryFromSpecialId(tvPtr, string, &entryPtr) == TCL_OK) {
      if (entryPtr != NULL) {
          return TCL_OK;      /* Special id doesn't currently exist. */
      }
      object = entryPtr;
    } else {
      /* Assume that this is a binding tag. */
      object = Blt_TreeViewEntryTag(tvPtr, string);
    } 
    return Blt_ConfigureBindingsFromObj(interp, tvPtr->bindTable, object, 
       objc - 3, objv + 3);
}


/*ARGSUSED*/
static int
EditOp(tvPtr, interp, objc, objv)
    TreeView *tvPtr;
    Tcl_Interp *interp;       /* Not used. */
    int objc;
    Tcl_Obj *CONST *objv;
{
    TreeViewEntry *entryPtr;
    char *string;
    int isRoot, isTest;
    int x, y;

    isRoot = isTest = FALSE;
    string = Tcl_GetString(objv[2]);
    if (strcmp("-root", string) == 0) {
      isRoot = TRUE;
      objv++, objc--;
    }
    string = Tcl_GetString(objv[2]);
    if (strcmp("-test", string) == 0) {
      isTest = TRUE;
      objv++, objc--;
    }
    if (objc != 4) {
      Tcl_AppendResult(interp, "wrong # args: should be \"", 
            Tcl_GetString(objv[0]), " ", Tcl_GetString(objv[1]), 
                  " ?-root? x y\"", (char *)NULL);
      return TCL_ERROR;
                   
    }
    if ((Tcl_GetIntFromObj(interp, objv[2], &x) != TCL_OK) ||
      (Tcl_GetIntFromObj(interp, objv[3], &y) != TCL_OK)) {
      return TCL_ERROR;
    }
    if (isRoot) {
      int rootX, rootY;

      Tk_GetRootCoords(tvPtr->tkwin, &rootX, &rootY);
      x -= rootX;
      y -= rootY;
    }
    entryPtr = Blt_TreeViewNearestEntry(tvPtr, x, y, FALSE);
    if (entryPtr != NULL) {
      Blt_ChainLink *linkPtr;
      TreeViewColumn *columnPtr;
      int worldX;

      worldX = WORLDX(tvPtr, x);
      for (linkPtr = Blt_ChainFirstLink(tvPtr->colChainPtr); 
           linkPtr != NULL; linkPtr = Blt_ChainNextLink(linkPtr)) {
          columnPtr = Blt_ChainGetValue(linkPtr);
          if (!columnPtr->editable) {
            continue;         /* Column isn't editable. */
          }
          if ((worldX >= columnPtr->worldX) && 
            (worldX < (columnPtr->worldX + columnPtr->width))) {
            TreeViewValue *valuePtr;
            
            valuePtr = Blt_TreeViewFindValue(entryPtr, columnPtr);
            if (valuePtr != NULL) {
                TreeViewStyle *stylePtr;
                
                stylePtr = valuePtr->stylePtr;
                if (stylePtr == NULL) {
                  stylePtr = columnPtr->stylePtr;
                }
                if ((stylePtr->classPtr->editProc != NULL) && (!isTest)) {
                  if ((*stylePtr->classPtr->editProc)(tvPtr, entryPtr, 
                            valuePtr, stylePtr) != TCL_OK) {
                      return TCL_ERROR;
                  }
                  Blt_TreeViewEventuallyRedraw(tvPtr);
                }
                Tcl_SetObjResult(interp, Tcl_NewIntObj(1));
                return TCL_OK;
            }
          }
      }
    }
    Tcl_SetObjResult(interp, Tcl_NewIntObj(0));
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * EntryActivateOp --
 *
 *    Selects the entry to appear active.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
EntryActivateOp(tvPtr, interp, objc, objv)
    TreeView *tvPtr;
    Tcl_Interp *interp;
    int objc;                 /* Not used. */
    Tcl_Obj *CONST *objv;
{
    TreeViewEntry *newPtr, *oldPtr;
    char *string;

    string = Tcl_GetString(objv[3]);
    if (string[0] == '\0') {
      newPtr = NULL;
    } else if (GetEntryFromObj(tvPtr, objv[3], &newPtr) != TCL_OK) {
      return TCL_ERROR;
    }
    if (tvPtr->treeColumn.hidden) {
      return TCL_OK;
    }
    oldPtr = tvPtr->activePtr;
    tvPtr->activePtr = newPtr;
    if (!(tvPtr->flags & TV_REDRAW) && (newPtr != oldPtr)) {
      Drawable drawable;
      int x, y;
      
      drawable = Tk_WindowId(tvPtr->tkwin);
      if (oldPtr != NULL) {
          x = SCREENX(tvPtr, oldPtr->worldX);
          if (!tvPtr->flatView) {
            x += ICONWIDTH(DEPTH(tvPtr, oldPtr->node));
          }
          y = SCREENY(tvPtr, oldPtr->worldY);
          oldPtr->flags |= ENTRY_ICON;
          Blt_TreeViewDrawIcon(tvPtr, oldPtr, drawable, x, y);
      }
      if (newPtr != NULL) {
          x = SCREENX(tvPtr, newPtr->worldX);
          if (!tvPtr->flatView) {
            x += ICONWIDTH(DEPTH(tvPtr, newPtr->node));
          }
          y = SCREENY(tvPtr, newPtr->worldY);
          newPtr->flags |= ENTRY_ICON;
          Blt_TreeViewDrawIcon(tvPtr, newPtr, drawable, x, y);
      }
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * EntryCgetOp --
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
EntryCgetOp(tvPtr, interp, objc, objv)
    TreeView *tvPtr;
    Tcl_Interp *interp;
    int objc;                 /* Not used. */
    Tcl_Obj *CONST *objv;
{
    TreeViewEntry *entryPtr;

    if (Blt_TreeViewGetEntry(tvPtr, objv[3], &entryPtr) != TCL_OK) {
      return TCL_ERROR;
    }
    return Blt_ConfigureValueFromObj(interp, tvPtr->tkwin, 
            bltTreeViewEntrySpecs, (char *)entryPtr, objv[4], 0);
}

/*
 *----------------------------------------------------------------------
 *
 * 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 tvPtr; old resources get freed, if there
 *    were any.  The hypertext is redisplayed.
 *
 *----------------------------------------------------------------------
 */
static int
EntryConfigureOp(tvPtr, interp, objc, objv)
    TreeView *tvPtr;
    Tcl_Interp *interp;
    int objc;
    Tcl_Obj *CONST *objv;
{
    int nIds, configObjc;
    Tcl_Obj *CONST *configObjv;
    register int i;
    TreeViewEntry *entryPtr;
    TreeViewTagInfo info;
    char *string;

    /* Figure out where the option value pairs begin */
    objc -= 3, objv += 3;
    for (i = 0; i < objc; i++) {
      string = Tcl_GetString(objv[i]);
      if (string[0] == '-') {
          break;
      }
    }
    nIds = i;                 /* # of tags or ids specified */
    configObjc = objc - i;    /* # of options specified */
    configObjv = objv + i;    /* Start of options in objv  */

    bltTreeViewIconsOption.clientData = tvPtr;
    bltTreeViewUidOption.clientData = tvPtr;

    for (i = 0; i < nIds; i++) {
      if (Blt_TreeViewFindTaggedEntries(tvPtr, objv[i], &info) != TCL_OK) {
          return TCL_ERROR;
      }
      for (entryPtr = Blt_TreeViewFirstTaggedEntry(&info); entryPtr != NULL; 
           entryPtr = Blt_TreeViewNextTaggedEntry(&info)) {
          if (configObjc == 0) {
            return Blt_ConfigureInfoFromObj(interp, tvPtr->tkwin, 
                  bltTreeViewEntrySpecs, (char *)entryPtr, 
                  (Tcl_Obj *)NULL, 0);
          } else if (configObjc == 1) {
            return Blt_ConfigureInfoFromObj(interp, tvPtr->tkwin, 
                  bltTreeViewEntrySpecs, (char *)entryPtr, 
                  configObjv[0], 0);
          }
          if (Blt_TreeViewConfigureEntry(tvPtr, entryPtr, configObjc, 
            configObjv, BLT_CONFIG_OBJV_ONLY) != TCL_OK) {
            return TCL_ERROR;
          }
      }
    }
    tvPtr->flags |= (TV_DIRTY | TV_LAYOUT | TV_SCROLL | TV_RESORT);
    Blt_TreeViewEventuallyRedraw(tvPtr);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * EntryIsOpenOp --
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
EntryIsBeforeOp(tvPtr, interp, objc, objv)
    TreeView *tvPtr;
    Tcl_Interp *interp;
    int objc;                 /* Not used. */
    Tcl_Obj *CONST *objv;
{
    TreeViewEntry *e1Ptr, *e2Ptr;
    int bool;

    if ((Blt_TreeViewGetEntry(tvPtr, objv[3], &e1Ptr) != TCL_OK) ||
      (Blt_TreeViewGetEntry(tvPtr, objv[4], &e2Ptr) != TCL_OK)) {
      return TCL_ERROR;
    }
    bool = Blt_TreeIsBefore(e1Ptr->node, e2Ptr->node);
    Tcl_SetObjResult(interp, Tcl_NewBooleanObj(bool));
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * EntryIsHiddenOp --
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
EntryIsHiddenOp(tvPtr, interp, objc, objv)
    TreeView *tvPtr;
    Tcl_Interp *interp;
    int objc;                 /* Not used. */
    Tcl_Obj *CONST *objv;
{
    TreeViewEntry *entryPtr;
    int bool;

    if (Blt_TreeViewGetEntry(tvPtr, objv[3], &entryPtr) != TCL_OK) {
      return TCL_ERROR;
    }
    bool = (entryPtr->flags & ENTRY_HIDDEN);
    Tcl_SetObjResult(interp, Tcl_NewBooleanObj(bool));
    return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * EntryIsOpenOp --
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
EntryIsOpenOp(tvPtr, interp, objc, objv)
    TreeView *tvPtr;
    Tcl_Interp *interp;
    int objc;                 /* Not used. */
    Tcl_Obj *CONST *objv;
{
    TreeViewEntry *entryPtr;
    int bool;

    if (Blt_TreeViewGetEntry(tvPtr, objv[3], &entryPtr) != TCL_OK) {
      return TCL_ERROR;
    }
    bool = ((entryPtr->flags & ENTRY_CLOSED) == 0);
    Tcl_SetObjResult(interp, Tcl_NewBooleanObj(bool));
    return TCL_OK;
}

/*ARGSUSED*/
static int
EntryChildrenOp(tvPtr, interp, objc, objv)
    TreeView *tvPtr;
    Tcl_Interp *interp;
    int objc;
    Tcl_Obj *CONST *objv;
{
    TreeViewEntry *parentPtr;
    Tcl_Obj *listObjPtr, *objPtr;
    unsigned int mask;

    mask = 0;
    if (Blt_TreeViewGetEntry(tvPtr, objv[3], &parentPtr) != TCL_OK) {
      return TCL_ERROR;
    }
    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL);
    if (objc == 4) {
      TreeViewEntry *entryPtr;

      for (entryPtr = Blt_TreeViewFirstChild(parentPtr, mask); 
           entryPtr != NULL;
           entryPtr = Blt_TreeViewNextSibling(entryPtr, mask)) {
          objPtr = NodeToObj(entryPtr->node);
          Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
      }
    } else if (objc == 6) {
      TreeViewEntry *entryPtr, *lastPtr, *firstPtr;
      int firstPos, lastPos;
      int nNodes;

      if ((Blt_GetPositionFromObj(interp, objv[4], &firstPos) != TCL_OK) ||
          (Blt_GetPositionFromObj(interp, objv[5], &lastPos) != TCL_OK)) {
          return TCL_ERROR;
      }
      nNodes = Blt_TreeNodeDegree(parentPtr->node);
      if (nNodes == 0) {
          return TCL_OK;
      }
      if ((lastPos == END) || (lastPos >= nNodes)) {
          lastPtr = Blt_TreeViewLastChild(parentPtr, mask);
      } else {
          lastPtr = GetNthEntry(parentPtr, lastPos, mask);
      }
      if ((firstPos == END) || (firstPos >= nNodes)) {
          firstPtr = Blt_TreeViewLastChild(parentPtr, mask);
      } else {
          firstPtr = GetNthEntry(parentPtr, firstPos, mask);
      }
      if ((lastPos != END) && (firstPos > lastPos)) {
          for (entryPtr = lastPtr; entryPtr != NULL; 
            entryPtr = Blt_TreeViewPrevEntry(entryPtr, mask)) {
            objPtr = NodeToObj(entryPtr->node);
            Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
            if (entryPtr == firstPtr) {
                break;
            }
          }
      } else {
          for (entryPtr = firstPtr; entryPtr != NULL; 
             entryPtr = Blt_TreeViewNextEntry(entryPtr, mask)) {
            objPtr = NodeToObj(entryPtr->node);
            Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
            if (entryPtr == lastPtr) {
                break;
            }
          }
      }
    } else {
      Tcl_AppendResult(interp, "wrong # args: should be \"", 
                   Tcl_GetString(objv[0]), " ",
                   Tcl_GetString(objv[1]), " ", 
                   Tcl_GetString(objv[2]), " tagOrId ?first last?", 
                   (char *)NULL);
      return TCL_ERROR;
    }
    Tcl_SetObjResult(interp, listObjPtr);
    return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * EntryDeleteOp --
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
EntryDeleteOp(tvPtr, interp, objc, objv)
    TreeView *tvPtr;
    Tcl_Interp *interp;
    int objc;                 /* Not used. */
    Tcl_Obj *CONST *objv;
{
    TreeViewEntry *entryPtr;

    if (Blt_TreeViewGetEntry(tvPtr, objv[3], &entryPtr) != TCL_OK) {
      return TCL_ERROR;
    }
    if (objc == 5) {
      int entryPos;
      Blt_TreeNode node;
      /*
       * Delete a single child node from a hierarchy specified 
       * by its numeric position.
       */
      if (Blt_GetPositionFromObj(interp, objv[3], &entryPos) != TCL_OK) {
          return TCL_ERROR;
      }
      if (entryPos >= (int)Blt_TreeNodeDegree(entryPtr->node)) {
          return TCL_OK;      /* Bad first index */
      }
      if (entryPos == END) {
          node = Blt_TreeLastChild(entryPtr->node);
      } else {
          node = GetNthNode(entryPtr->node, entryPos);
      }
      DeleteNode(tvPtr, 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_GetPositionFromObj(interp, objv[4], &firstPos) != TCL_OK) ||
          (Blt_GetPositionFromObj(interp, objv[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 \"", 
            Tcl_GetString(objv[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: \"", Tcl_GetString(objv[4]), 
            " > ", Tcl_GetString(objv[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);
          DeleteNode(tvPtr, node);
          if (node == last) {
            break;
          }
      }
    }
    tvPtr->flags |= (TV_LAYOUT | TV_DIRTY | TV_RESORT);
    Blt_TreeViewEventuallyRedraw(tvPtr);
    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(tvPtr, interp, objc, objv)
    TreeView *tvPtr;
    Tcl_Interp *interp;
    int objc;
    Tcl_Obj *CONST *objv;
{
    TreeViewEntry *entryPtr;
    int length, sum, recurse;
    char *string;

    recurse = FALSE;
    string = Tcl_GetStringFromObj(objv[3], &length);
    if ((string[0] == '-') && (length > 1) &&
      (strncmp(string, "-recurse", length) == 0)) {
      objv++, objc--;
      recurse = TRUE;
    }
    if (objc == 3) {
      Tcl_AppendResult(interp, "missing node argument: should be \"",
          Tcl_GetString(objv[0]), " entry open node\"", (char *)NULL);
      return TCL_ERROR;
    }
    if (Blt_TreeViewGetEntry(tvPtr, objv[3], &entryPtr) != TCL_OK) {
      return TCL_ERROR;
    }
    if (recurse) {
      sum = Blt_TreeSize(entryPtr->node);
    } else {
      sum = Blt_TreeNodeDegree(entryPtr->node);
    }
    Tcl_SetObjResult(interp, Tcl_NewIntObj(sum));
    return TCL_OK;
}

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

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

static int
EntryOp(tvPtr, interp, objc, objv)
    TreeView *tvPtr;
    Tcl_Interp *interp;
    int objc;
    Tcl_Obj *CONST *objv;
{
    Blt_Op proc;
    int result;

    proc = Blt_GetOpFromObj(interp, nEntryOps, entryOps, BLT_OP_ARG2, objc, 
      objv, 0);
    if (proc == NULL) {
      return TCL_ERROR;
    }
    result = (*proc) (tvPtr, interp, objc, objv);
    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(tvPtr, interp, objc, objv)
    TreeView *tvPtr;
    Tcl_Interp *interp;
    int objc;
    Tcl_Obj *CONST *objv;
{
    TreeViewEntry *firstPtr, *lastPtr;
    int nMatches, maxMatches;
    char c;
    int length;
    TreeViewCompareProc *compareProc;
    TreeViewIterProc *nextProc;
    int invertMatch;          /* normal search mode (matching entries) */
    char *namePattern, *fullPattern;
    char *execCmd;
    register int i;
    int result;
    char *pattern, *option;
    Tcl_DString dString;
    Blt_List options;
    Blt_ListNode node;
    char *addTag, *withTag;
    register TreeViewEntry *entryPtr;
    char *string;
    Tcl_Obj *listObjPtr, *objPtr;

    invertMatch = FALSE;
    maxMatches = 0;
    execCmd = namePattern = fullPattern = NULL;
    compareProc = ExactCompare;
    nextProc = Blt_TreeViewNextEntry;
    options = Blt_ListCreate(BLT_ONE_WORD_KEYS);
    withTag = addTag = NULL;

    entryPtr = tvPtr->rootPtr;
    /*
     * Step 1:  Process flags for find operation.
     */
    for (i = 2; i < objc; i++) {
      string = Tcl_GetStringFromObj(objv[i], &length);
      if (string[0] != '-') {
          break;
      }
      option = string + 1;
      length--;
      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) == objc) {
            goto missingArg;
          }
          i++;
          namePattern = Tcl_GetString(objv[i]);
      } else if ((c == 'f') && (strncmp(option, "full", length) == 0)) {
          if ((i + 1) == objc) {
            goto missingArg;
          }
          i++;
          fullPattern = Tcl_GetString(objv[i]);
      } else if ((c == 'e') && (length > 2) &&
          (strncmp(option, "exec", length) == 0)) {
          if ((i + 1) == objc) {
            goto missingArg;
          }
          i++;
          execCmd = Tcl_GetString(objv[i]);
      } else if ((c == 'a') && (length > 1) &&
               (strncmp(option, "addtag", length) == 0)) {
          if ((i + 1) == objc) {
            goto missingArg;
          }
          i++;
          addTag = Tcl_GetString(objv[i]);
      } else if ((c == 't') && (length > 1) && 
               (strncmp(option, "tag", length) == 0)) {
          if ((i + 1) == objc) {
            goto missingArg;
          }
          i++;
          withTag = Tcl_GetString(objv[i]);
      } else if ((c == 'c') && (strncmp(option, "count", length) == 0)) {
          if ((i + 1) == objc) {
            goto missingArg;
          }
          i++;
          if (Tcl_GetIntFromObj(interp, objv[i], &maxMatches) != TCL_OK) {
            return TCL_ERROR;
          }
          if (maxMatches < 0) {
            Tcl_AppendResult(interp, "bad match count \"", objv[i],
                "\": should be a positive number", (char *)NULL);
            Blt_ListDestroy(options);
            return TCL_ERROR;
          }
      } else if ((option[0] == '-') && (option[1] == '\0')) {
          break;
      } else {
          /*
           * Verify that the switch is actually an entry configuration
           * option.
           */
          if (Blt_ConfigureValueFromObj(interp, tvPtr->tkwin, 
              bltTreeViewEntrySpecs, (char *)entryPtr, objv[i], 0) 
            != TCL_OK) {
            Tcl_ResetResult(interp);
            Tcl_AppendResult(interp, "bad find switch \"", string, "\"",
                (char *)NULL);
            Blt_ListDestroy(options);
            return TCL_ERROR;
          }
          if ((i + 1) == objc) {
            goto missingArg;
          }
          /* Save the option in the list of configuration options */
          node = Blt_ListGetNode(options, (char *)objv[i]);
          if (node == NULL) {
            node = Blt_ListCreateNode(options, (char *)objv[i]);
            Blt_ListAppendNode(options, node);
          }
          i++;
          Blt_ListSetValue(node, Tcl_GetString(objv[i]));
      }
    }

    if ((objc - i) > 2) {
      Blt_ListDestroy(options);
      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 = tvPtr->rootPtr;      /* Default to root node */
    lastPtr = LastEntry(tvPtr, firstPtr, 0);

    if (i < objc) {
      string = Tcl_GetString(objv[i]);
      if ((string[0] == 'e') && (strcmp(string, "end") == 0)) {
          firstPtr = LastEntry(tvPtr, tvPtr->rootPtr, 0);
      } else if (Blt_TreeViewGetEntry(tvPtr, objv[i], &firstPtr) != TCL_OK) {
          return TCL_ERROR;
      }
      i++;
    }
    if (i < objc) {
      string = Tcl_GetString(objv[i]);
      if ((string[0] == 'e') && (strcmp(string, "end") == 0)) {
          lastPtr = LastEntry(tvPtr, tvPtr->rootPtr, 0);
      } else if (Blt_TreeViewGetEntry(tvPtr, objv[i], &lastPtr) != TCL_OK) {
          return TCL_ERROR;
      }
    }
    if (Blt_TreeIsBefore(lastPtr->node, firstPtr->node)) {
      nextProc = Blt_TreeViewPrevEntry;
    }
    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.
     */
    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL);
    for (entryPtr = firstPtr; entryPtr != NULL; 
       entryPtr = (*nextProc) (entryPtr, 0)) {
      if (namePattern != NULL) {
          result = (*compareProc)(interp, Blt_TreeNodeLabel(entryPtr->node),
                 namePattern);
          if (result == invertMatch) {
            goto nextEntry;   /* Failed to match */
          }
      }
      if (fullPattern != NULL) {
          Tcl_DString fullName;

          Blt_TreeViewGetFullName(tvPtr, entryPtr, FALSE, &fullName);
          result = (*compareProc) (interp, Tcl_DStringValue(&fullName), 
            fullPattern);
          Tcl_DStringFree(&fullName);
          if (result == invertMatch) {
            goto nextEntry;   /* Failed to match */
          }
      }
      if (withTag != NULL) {
          result = Blt_TreeHasTag(tvPtr->tree, entryPtr->node, withTag);
          if (result == invertMatch) {
            goto nextEntry;   /* Failed to match */
          }
      }
      for (node = Blt_ListFirstNode(options); node != NULL;
          node = Blt_ListNextNode(node)) {
          objPtr = (Tcl_Obj *)Blt_ListGetKey(node);
          Tcl_ResetResult(interp);
          Blt_ConfigureValueFromObj(interp, tvPtr->tkwin, 
            bltTreeViewEntrySpecs, (char *)entryPtr, objPtr, 0);
          pattern = Blt_ListGetValue(node);
          objPtr = Tcl_GetObjResult(interp);
          result = (*compareProc) (interp, Tcl_GetString(objPtr), 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_TreeViewPercentSubst(tvPtr, 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. */
          objPtr = NodeToObj(entryPtr->node);
          Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
          if (addTag != NULL) {
            if (AddTag(tvPtr, entryPtr->node, addTag) != TCL_OK) {
                goto error;
            }
          }
      }
          
      Tcl_Release(entryPtr);
      nMatches++;
      if ((nMatches == maxMatches) && (maxMatches > 0)) {
          break;
      }
      nextEntry:
      if (entryPtr == lastPtr) {
          break;
      }
    }
    Tcl_ResetResult(interp);
    Blt_ListDestroy(options);
    Tcl_SetObjResult(interp, listObjPtr);
    return TCL_OK;

  missingArg:
    Tcl_AppendResult(interp, "missing argument for find option \"", objv[i],
      "\"", (char *)NULL);
  error:
    Tcl_DStringFree(&dString);
    Blt_ListDestroy(options);
    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(tvPtr, interp, objc, objv)
    TreeView *tvPtr;
    Tcl_Interp *interp;
    int objc;
    Tcl_Obj *CONST *objv;
{
    TreeViewTagInfo info;
    TreeViewEntry *entryPtr;
    int useFullName;
    register int i;
    Tcl_DString dString1, dString2;
    int count;

    useFullName = FALSE;
    if (objc > 2) {
      char *string;

      string = Tcl_GetString(objv[2]);
      if ((string[0] == '-') && (strcmp(string, "-full") == 0)) {
          useFullName = TRUE;
          objv++, objc--;
      }
    }
    Tcl_DStringInit(&dString1);
    Tcl_DStringInit(&dString2);
    count = 0;
    for (i = 2; i < objc; i++) {
      if (Blt_TreeViewFindTaggedEntries(tvPtr, objv[i], &info) != TCL_OK) {
          return TCL_ERROR;
      }
      for (entryPtr = Blt_TreeViewFirstTaggedEntry(&info); entryPtr != NULL; 
           entryPtr = Blt_TreeViewNextTaggedEntry(&info)) {
          Tcl_DStringSetLength(&dString2, 0);
          count++;
          if (entryPtr->node == NULL) {
            Tcl_DStringAppendElement(&dString1, "");
            continue;
          }
          if (useFullName) {
            Blt_TreeViewGetFullName(tvPtr, entryPtr, FALSE, &dString2);
            Tcl_DStringAppendElement(&dString1, 
                   Tcl_DStringValue(&dString2));
          } else {
            Tcl_DStringAppendElement(&dString1, 
                   Blt_TreeNodeLabel(entryPtr->node));
          }
      }
    }
    /* This handles the single element list problem. */
    if (count == 1) {
      Tcl_DStringResult(interp, &dString2);
      Tcl_DStringFree(&dString1);
    } else {
      Tcl_DStringResult(interp, &dString1);
      Tcl_DStringFree(&dString2);
    }
    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(tvPtr, interp, objc, objv, proc, nonMatchPtr)
    TreeView *tvPtr;
    Tcl_Interp *interp;
    int objc;
    Tcl_Obj *CONST *objv;
    TreeViewApplyProc *proc;
    int *nonMatchPtr;         /* returns: inverted search indicator */
{
    TreeViewCompareProc *compareProc;
    int invertMatch;          /* normal search mode (matching entries) */
    char *namePattern, *fullPattern;
    register int i;
    int length;
    int result;
    char *option, *pattern;
    char c;
    Blt_List options;
    TreeViewEntry *entryPtr;
    register Blt_ListNode node;
    char *string;
    char *withTag;
    Tcl_Obj *objPtr;
    TreeViewTagInfo info;

    options = Blt_ListCreate(BLT_ONE_WORD_KEYS);
    invertMatch = FALSE;
    namePattern = fullPattern = NULL;
    compareProc = ExactCompare;
    withTag = NULL;

    entryPtr = tvPtr->rootPtr;
    for (i = 2; i < objc; i++) {
      string = Tcl_GetStringFromObj(objv[i], &length);
      if (string[0] != '-') {
          break;
      }
      option = string + 1;
      length--;
      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) == objc) {
            goto missingArg;
          }
          i++;
          fullPattern = Tcl_GetString(objv[i]);
      } else if ((c == 'n') && (length > 1) &&
          (strncmp(option, "name", length) == 0)) {
          if ((i + 1) == objc) {
            goto missingArg;
          }
          i++;
          namePattern = Tcl_GetString(objv[i]);
      } else if ((c == 't') && (length > 1) && 
               (strncmp(option, "tag", length) == 0)) {
          if ((i + 1) == objc) {
            goto missingArg;
          }
          i++;
          withTag = Tcl_GetString(objv[i]);
      } else if ((option[0] == '-') && (option[1] == '\0')) {
          break;
      } else {
          /*
           * Verify that the switch is actually an entry configuration option.
           */
          if (Blt_ConfigureValueFromObj(interp, tvPtr->tkwin, 
            bltTreeViewEntrySpecs, (char *)entryPtr, objv[i], 0) 
            != TCL_OK) {
            Tcl_ResetResult(interp);
            Tcl_AppendResult(interp, "bad switch \"", string,
          "\": must be -exact, -glob, -regexp, -name, -full, or -nonmatching",
                (char *)NULL);
            return TCL_ERROR;
          }
          if ((i + 1) == objc) {
            goto missingArg;
          }
          /* Save the option in the list of configuration options */
          node = Blt_ListGetNode(options, (char *)objv[i]);
          if (node == NULL) {
            node = Blt_ListCreateNode(options, (char *)objv[i]);
            Blt_ListAppendNode(options, node);
          }
          i++;
          Blt_ListSetValue(node, Tcl_GetString(objv[i]));
      }
    }

    if ((namePattern != NULL) || (fullPattern != NULL) ||
      (Blt_ListGetLength(options) > 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 = tvPtr->rootPtr; entryPtr != NULL; 
           entryPtr = Blt_TreeViewNextEntry(entryPtr, 0)) {
          if (namePattern != NULL) {
            result = (*compareProc) (interp, 
                  Blt_TreeNodeLabel(entryPtr->node), namePattern);
            if (result == invertMatch) {
                continue;     /* Failed to match */
            }
          }
          if (fullPattern != NULL) {
            Tcl_DString dString;

            Blt_TreeViewGetFullName(tvPtr, entryPtr, FALSE, &dString);
            result = (*compareProc) (interp, Tcl_DStringValue(&dString), 
                  fullPattern);
            Tcl_DStringFree(&dString);
            if (result == invertMatch) {
                continue;     /* Failed to match */
            }
          }
          if (withTag != NULL) {
            result = Blt_TreeHasTag(tvPtr->tree, entryPtr->node, withTag);
            if (result == invertMatch) {
                continue;     /* Failed to match */
            }
          }
          for (node = Blt_ListFirstNode(options); node != NULL;
            node = Blt_ListNextNode(node)) {
            objPtr = (Tcl_Obj *)Blt_ListGetKey(node);
            Tcl_ResetResult(interp);
            if (Blt_ConfigureValueFromObj(interp, tvPtr->tkwin, 
                  bltTreeViewEntrySpecs, (char *)entryPtr, objPtr, 0) 
                != TCL_OK) {
                return TCL_ERROR;   /* This shouldn't happen. */
            }
            pattern = Blt_ListGetValue(node);
            objPtr = Tcl_GetObjResult(interp);
            result = (*compareProc)(interp, Tcl_GetString(objPtr), pattern);
            if (result == invertMatch) {
                continue;     /* Failed to match */
            }
          }
          /* Finally, apply the procedure to the node */
          (*proc) (tvPtr, entryPtr);
      }
      Tcl_ResetResult(interp);
      Blt_ListDestroy(options);
    }
    /*
     * Apply the procedure to nodes that have been specified
     * individually.
     */
    for ( /*empty*/ ; i < objc; i++) {
      if (Blt_TreeViewFindTaggedEntries(tvPtr, objv[i], &info) != TCL_OK) {
          return TCL_ERROR;
      }
      for (entryPtr = Blt_TreeViewFirstTaggedEntry(&info); entryPtr != NULL; 
           entryPtr = Blt_TreeViewNextTaggedEntry(&info)) {
          if ((*proc) (tvPtr, entryPtr) != TCL_OK) {
            return TCL_ERROR;
          }
      }
    }
    if (nonMatchPtr != NULL) {
      *nonMatchPtr = invertMatch;   /* return "inverted search" status */
    }
    return TCL_OK;

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

}

static int
FixSelectionsApplyProc(tvPtr, entryPtr)
    TreeView *tvPtr;
    TreeViewEntry *entryPtr;
{
    if (entryPtr->flags & ENTRY_HIDDEN) {
      Blt_TreeViewDeselectEntry(tvPtr, entryPtr);
      if ((tvPtr->focusPtr != NULL) &&
          (Blt_TreeIsAncestor(entryPtr->node, tvPtr->focusPtr->node))) {
          if (entryPtr != tvPtr->rootPtr) {
            entryPtr = Blt_TreeViewParentEntry(entryPtr);
            tvPtr->focusPtr = (entryPtr == NULL) 
                ? tvPtr->focusPtr : entryPtr;
            Blt_SetFocusItem(tvPtr->bindTable, tvPtr->focusPtr, ITEM_ENTRY);
          }
      }
      if ((tvPtr->selAnchorPtr != NULL) &&
          (Blt_TreeIsAncestor(entryPtr->node, tvPtr->selAnchorPtr->node))) {
          tvPtr->selMarkPtr = tvPtr->selAnchorPtr = NULL;
      }
      if ((tvPtr->activePtr != NULL) &&
          (Blt_TreeIsAncestor(entryPtr->node, tvPtr->activePtr->node))) {
          tvPtr->activePtr = NULL;
      }
      Blt_TreeViewPruneSelection(tvPtr, 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(tvPtr, interp, objc, objv)
    TreeView *tvPtr;
    Tcl_Interp *interp;
    int objc;
    Tcl_Obj *CONST *objv;
{
    int status, nonmatching;

    status = SearchAndApplyToTree(tvPtr, interp, objc, objv, 
      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_TreeViewApply(tvPtr, tvPtr->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_TreeViewApply(tvPtr, tvPtr->rootPtr, FixSelectionsApplyProc, 0);

    /* Hiding an entry only effects the visible nodes. */
    tvPtr->flags |= (TV_LAYOUT | TV_SCROLL);
    Blt_TreeViewEventuallyRedraw(tvPtr);
    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(tvPtr, interp, objc, objv)
    TreeView *tvPtr;
    Tcl_Interp *interp;
    int objc;
    Tcl_Obj *CONST *objv;
{
    if (SearchAndApplyToTree(tvPtr, interp, objc, objv, ShowEntryApplyProc,
          (int *)NULL) != TCL_OK) {
      return TCL_ERROR;
    }
    tvPtr->flags |= (TV_LAYOUT | TV_SCROLL);
    Blt_TreeViewEventuallyRedraw(tvPtr);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * IndexOp --
 *
 *    Converts one of more words representing indices of the entries
 *    in the treeview 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(tvPtr, interp, objc, objv)
    TreeView *tvPtr;
    Tcl_Interp *interp;
    int objc;                 /* Not used. */
    Tcl_Obj *CONST *objv;
{
    TreeViewEntry *entryPtr;
    char *string;
    TreeViewEntry *fromPtr;
    int usePath;

    usePath = FALSE;
    fromPtr = NULL;
    string = Tcl_GetString(objv[2]);
    if ((string[0] == '-') && (strcmp(string, "-path") == 0)) {
      usePath = TRUE;
      objv++, objc--;
    }
    if ((string[0] == '-') && (strcmp(string, "-at") == 0)) {
      if (Blt_TreeViewGetEntry(tvPtr, objv[3], &fromPtr) != TCL_OK) {
          return TCL_ERROR;
      }
      objv += 2, objc -= 2;
    }
    if (objc != 3) {
      Tcl_AppendResult(interp, "wrong # args: should be \"", 
            Tcl_GetString(objv[0]), 
            " index ?-at tagOrId? ?-path? tagOrId\"", 
            (char *)NULL);
      return TCL_ERROR;
    }
    tvPtr->fromPtr = fromPtr;
    if (tvPtr->fromPtr == NULL) {
      tvPtr->fromPtr = tvPtr->focusPtr;
    }
    if (tvPtr->fromPtr == NULL) {
      tvPtr->fromPtr = tvPtr->rootPtr;
    }
    if (usePath) {
      if (fromPtr == NULL) {
          fromPtr = tvPtr->rootPtr;
      }
      string = Tcl_GetString(objv[2]);
      entryPtr = FindPath(tvPtr, fromPtr, string);
      if (entryPtr != NULL) {
          Tcl_SetObjResult(interp, NodeToObj(entryPtr->node));
      }
    } else {
      if ((GetEntryFromObj2(tvPtr, objv[2], &entryPtr) == TCL_OK) && 
          (entryPtr != NULL)) {
          Tcl_SetObjResult(interp, NodeToObj(entryPtr->node));
      }
    }
    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.
 *
 *----------------------------------------------------------------------
 */
static int
InsertOp(tvPtr, interp, objc, objv)
    TreeView *tvPtr;
    Tcl_Interp *interp;
    int objc;
    Tcl_Obj *CONST *objv;
{
    Blt_TreeNode node, parent;
    int insertPos;
    int depth, count;
    char *path;
    Tcl_Obj *CONST *options;
    Tcl_Obj *listObjPtr;
    char **compArr;
    register char **p;
    register int n;
    TreeViewEntry *rootPtr;
    char *string;

    rootPtr = tvPtr->rootPtr;
    string = Tcl_GetString(objv[2]);
    if ((string[0] == '-') && (strcmp(string, "-at") == 0)) {
      if (objc > 2) {
          if (Blt_TreeViewGetEntry(tvPtr, objv[3], &rootPtr) != TCL_OK) {
            return TCL_ERROR;
          }
          objv += 2, objc -= 2;
      } else {
          Tcl_AppendResult(interp, "missing argument for \"-at\" flag",
                 (char *)NULL);
          return TCL_ERROR;
      }
    }
    if (objc == 2) {
      Tcl_AppendResult(interp, "missing position argument", (char *)NULL);
      return TCL_ERROR;
    }
    if (Blt_GetPositionFromObj(interp, objv[2], &insertPos) != TCL_OK) {
      return TCL_ERROR;
    }
    node = NULL;
    objc -= 3, objv += 3;

    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL);
    while (objc > 0) {
      path = Tcl_GetString(objv[0]);
      objv++, objc--;

      /*
       * 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 < objc; count += 2) {
          string = Tcl_GetString(objv[count]);
          if (string[0] != '-') {
            break;
          }
      }
      if (count > objc) {
          count = objc;
      }
      options = objv;
      objc -= count, objv += count;

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

          /* Trim off leading character string if one exists. */
          for (s1 = path, s2 = tvPtr->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 (tvPtr->pathSep != SEPARATOR_NONE) {
          if (SplitPath(tvPtr, path, &depth, &compArr) != TCL_OK) {
            goto error;
          }
          if (depth == 0) {
            Blt_Free(compArr);
            continue;         /* Root already exists. */
          }
      }
      parent = rootPtr->node;
      depth--;          

      /* Verify each component in the path preceding the tail.  */
      for (n = 0, p = compArr; n < depth; n++, p++) {
          node = Blt_TreeFindChild(parent, *p);
          if (node == NULL) {
            if ((tvPtr->flags & TV_FILL_ANCESTORS) == 0) {
                Tcl_AppendResult(interp, "can't find path component \"",
                     *p, "\" in \"", path, "\"", (char *)NULL);
                goto error;
            }
            node = Blt_TreeCreateNode(tvPtr->tree, parent, *p, END);
            if (node == NULL) {
                goto error;
            }
          }
          parent = node;
      }
      node = NULL;
      if (((tvPtr->flags & TV_ALLOW_DUPLICATES) == 0) && 
          (Blt_TreeFindChild(parent, *p) != NULL)) {
          Tcl_AppendResult(interp, "entry \"", *p, "\" already exists in \"",
             path, "\"", (char *)NULL);
          goto error;
      }
      node = Blt_TreeCreateNode(tvPtr->tree, parent, *p, insertPos);
      if (node == NULL) {
          goto error;
      }
      if (Blt_TreeViewCreateEntry(tvPtr, node, count, options, 0) != TCL_OK) {
          goto error;
      }
      if (compArr != &path) {
          Blt_Free(compArr);
      }
      Tcl_ListObjAppendElement(interp, listObjPtr, NodeToObj(node));
    }
    tvPtr->flags |= (TV_LAYOUT | TV_SCROLL | TV_DIRTY | TV_RESORT);
    Blt_TreeViewEventuallyRedraw(tvPtr);
    Tcl_SetObjResult(interp, listObjPtr);
    return TCL_OK;

  error:
    if (compArr != &path) {
      Blt_Free(compArr);
    }
    Tcl_DecrRefCount(listObjPtr);
    if (node != NULL) {
      DeleteNode(tvPtr, node);
    }
    return TCL_ERROR;
}

#ifdef notdef
/*
 *----------------------------------------------------------------------
 *
 * AddOp --
 *
 *    Add new entries into a hierarchy.  If no node is specified,
 *    new entries will be added to the root of the hierarchy.
 *
 *----------------------------------------------------------------------
 */

static Blt_SwitchParseProc StringToChild;
#define INSERT_BEFORE   (ClientData)0
#define INSERT_AFTER    (ClientData)1
static Blt_SwitchCustom beforeSwitch =
{
    StringToChild, (Blt_SwitchFreeProc *)NULL, INSERT_BEFORE,
};
static Blt_SwitchCustom afterSwitch =
{
    StringToChild, (Blt_SwitchFreeProc *)NULL, INSERT_AFTER,
};

typedef struct {
    int insertPos;
    Blt_TreeNode parent;
} InsertData;

static Blt_SwitchSpec insertSwitches[] = 
{
    {BLT_SWITCH_CUSTOM, "-after", Blt_Offset(InsertData, insertPos), 0, 
      &afterSwitch},
    {BLT_SWITCH_INT_NONNEGATIVE, "-at", Blt_Offset(InsertData, insertPos), 0},
    {BLT_SWITCH_CUSTOM, "-before", Blt_Offset(InsertData, insertPos), 0,
      &beforeSwitch},
    {BLT_SWITCH_END, NULL, 0, 0}
};

static int
AddOp(tvPtr, interp, objc, objv)
    TreeView *tvPtr;
    Tcl_Interp *interp;
    int objc;
    Tcl_Obj *CONST *objv;
{
    Blt_TreeNode node, parent;
    int insertPos;
    int depth, count;
    char *path;
    Tcl_Obj *CONST *options;
    Tcl_Obj *listObjPtr;
    char **compArr;
    register char **p;
    register int n;
    TreeViewEntry *rootPtr;
    char *string;

    memset(&data, 0, sizeof(data));
    data.maxDepth = -1;
    data.cmdPtr = cmdPtr;

    /* Process any leading switches  */
    i = Blt_ProcessObjSwitches(interp, addSwitches, objc - 2, objv + 2, 
           (char *)&data, BLT_CONFIG_OBJV_PARTIAL);
    if (i < 0) {
      return TCL_ERROR;
    }
    i += 2;
    /* Should have at the starting node */
    if (i >= objc) {
      Tcl_AppendResult(interp, "starting node argument is missing", 
            (char *)NULL);
      return TCL_ERROR;
    }
    if (Blt_TreeViewGetEntry(tvPtr, objv[i], &rootPtr) != TCL_OK) {
      return TCL_ERROR;
    }
    objv += i, objc -= i;
    node = NULL;

    /* Process sections of path ?options? */
    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL);
    while (objc > 0) {
      path = Tcl_GetString(objv[0]);
      objv++, objc--;
      /*
       * 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 < objc; count += 2) {
          if (!Blt_ObjIsOption(bltTreeViewEntrySpecs, objv[count], 0)) {
            break;
          }
      }
      if (count > objc) {
          count = objc;
      }
      options = objv;
      objc -= count, objv += count;

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

          /* Trim off leading character string if one exists. */
          for (s1 = path, s2 = tvPtr->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 (tvPtr->pathSep != SEPARATOR_NONE) {
          if (SplitPath(tvPtr, path, &depth, &compArr) != TCL_OK) {
            goto error;
          }
          if (depth == 0) {
            Blt_Free(compArr);
            continue;         /* Root already exists. */
          }
      }
      parent = rootPtr->node;
      depth--;          

      /* Verify each component in the path preceding the tail.  */
      for (n = 0, p = compArr; n < depth; n++, p++) {
          node = Blt_TreeFindChild(parent, *p);
          if (node == NULL) {
            if ((tvPtr->flags & TV_FILL_ANCESTORS) == 0) {
                Tcl_AppendResult(interp, "can't find path component \"",
                     *p, "\" in \"", path, "\"", (char *)NULL);
                goto error;
            }
            node = Blt_TreeCreateNode(tvPtr->tree, parent, *p, END);
            if (node == NULL) {
                goto error;
            }
          }
          parent = node;
      }
      node = NULL;
      if (((tvPtr->flags & TV_ALLOW_DUPLICATES) == 0) && 
          (Blt_TreeFindChild(parent, *p) != NULL)) {
          Tcl_AppendResult(interp, "entry \"", *p, "\" already exists in \"",
             path, "\"", (char *)NULL);
          goto error;
      }
      node = Blt_TreeCreateNode(tvPtr->tree, parent, *p, insertPos);
      if (node == NULL) {
          goto error;
      }
      if (Blt_TreeViewCreateEntry(tvPtr, node, count, options, 0) != TCL_OK) {
          goto error;
      }
      if (compArr != &path) {
          Blt_Free(compArr);
      }
      Tcl_ListObjAppendElement(interp, listObjPtr, NodeToObj(node));
    }
    tvPtr->flags |= (TV_LAYOUT | TV_SCROLL | TV_DIRTY | TV_RESORT);
    Blt_TreeViewEventuallyRedraw(tvPtr);
    Tcl_SetObjResult(interp, listObjPtr);
    return TCL_OK;

  error:
    if (compArr != &path) {
      Blt_Free(compArr);
    }
    Tcl_DecrRefCount(listObjPtr);
    if (node != NULL) {
      DeleteNode(tvPtr, node);
    }
    return TCL_ERROR;
}
#endif

/*
 *----------------------------------------------------------------------
 *
 * 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 treeview 
 *          widget store the Entry structure in a data field. So it's
 *          automatically cleaned up when FreeEntryInternalRep is
 *          called.
 *
 *---------------------------------------------------------------------- 
 */
/*ARGSUSED*/
static int
DeleteOp(tvPtr, interp, objc, objv)
    TreeView *tvPtr;
    Tcl_Interp *interp;
    int objc;                 /* Not used. */
    Tcl_Obj *CONST *objv;
{
    TreeViewTagInfo info;
    TreeViewEntry *entryPtr;
    register int i;

    for (i = 2; i < objc; i++) {
      if (Blt_TreeViewFindTaggedEntries(tvPtr, objv[i], &info) != TCL_OK) {
          return TCL_ERROR;
      }
      for (entryPtr = Blt_TreeViewFirstTaggedEntry(&info); entryPtr != NULL; 
           entryPtr = Blt_TreeViewNextTaggedEntry(&info)) {
          if (entryPtr == tvPtr->rootPtr) {
            Blt_TreeNode next, node;

            /* 
             *   Don't delete the root node.  We implicitly assume
             *   that even an empty tree has at a root.  Instead
             *   delete all the children regardless if they're closed
             *   or hidden.
             */
            for (node = Blt_TreeFirstChild(entryPtr->node); node != NULL; 
                 node = next) {
                next = Blt_TreeNextSibling(node);
                DeleteNode(tvPtr, node);
            }
          } else {
            DeleteNode(tvPtr, entryPtr->node);
          }
      }
    } 
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * MoveOp --
 *
 *    Move an entry into a new location in the hierarchy.
 *
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
MoveOp(tvPtr, interp, objc, objv)
    TreeView *tvPtr;
    Tcl_Interp *interp;
    int objc;                 /* Not used. */
    Tcl_Obj *CONST *objv;
{
    Blt_TreeNode parent;
    TreeViewEntry *srcPtr, *destPtr;
    char c;
    int action;
    char *string;
    TreeViewTagInfo info;

#define MOVE_INTO (1<<0)
#define MOVE_BEFORE     (1<<1)
#define MOVE_AFTER      (1<<2)
    if (Blt_TreeViewFindTaggedEntries(tvPtr, objv[2], &info) != TCL_OK) {
      return TCL_ERROR;
    }
    string = Tcl_GetString(objv[3]);
    c = string[0];
    if ((c == 'i') && (strcmp(string, "into") == 0)) {
      action = MOVE_INTO;
    } else if ((c == 'b') && (strcmp(string, "before") == 0)) {
      action = MOVE_BEFORE;
    } else if ((c == 'a') && (strcmp(string, "after") == 0)) {
      action = MOVE_AFTER;
    } else {
      Tcl_AppendResult(interp, "bad position \"", string,
          "\": should be into, before, or after", (char *)NULL);
      return TCL_ERROR;
    }
    if (Blt_TreeViewGetEntry(tvPtr, objv[4], &destPtr) != TCL_OK) {
      return TCL_ERROR;
    }
    for (srcPtr = Blt_TreeViewFirstTaggedEntry(&info); srcPtr != NULL; 
       srcPtr = Blt_TreeViewNextTaggedEntry(&info)) {
      /* Verify they aren't ancestors. */
      if (Blt_TreeIsAncestor(srcPtr->node, destPtr->node)) {
          Tcl_DString dString;
          char *path;

          path = Blt_TreeViewGetFullName(tvPtr, srcPtr, 1, &dString);
          Tcl_AppendResult(interp, "can't move node: \"", path, 
                  "\" is an ancestor of \"", Tcl_GetString(objv[4]), 
                  "\"", (char *)NULL);
          Tcl_DStringFree(&dString);
          return TCL_ERROR;
      }
      parent = Blt_TreeNodeParent(destPtr->node);
      if (parent == NULL) {
          action = MOVE_INTO;
      }
      switch (action) {
      case MOVE_INTO:
          Blt_TreeMoveNode(tvPtr->tree, srcPtr->node, destPtr->node, 
                       (Blt_TreeNode)NULL);
          break;
          
      case MOVE_BEFORE:
          Blt_TreeMoveNode(tvPtr->tree, srcPtr->node, parent, destPtr->node);
          break;
          
      case MOVE_AFTER:
          Blt_TreeMoveNode(tvPtr->tree, srcPtr->node, parent, 
                       Blt_TreeNextSibling(destPtr->node));
          break;
      }
    }
    tvPtr->flags |= (TV_LAYOUT | TV_DIRTY | TV_RESORT);
    Blt_TreeViewEventuallyRedraw(tvPtr);
    return TCL_OK;
}

/*ARGSUSED*/
static int
NearestOp(tvPtr, interp, objc, objv)
    TreeView *tvPtr;
    Tcl_Interp *interp;
    int objc;                 /* Not used. */
    Tcl_Obj *CONST *objv;
{
    TreeViewButton *buttonPtr = &tvPtr->button;
    int x, y;                 /* Screen coordinates of the test point. */
    register TreeViewEntry *entryPtr;
    int isRoot;
    char *string;

    isRoot = FALSE;
    string = Tcl_GetString(objv[2]);
    if (strcmp("-root", string) == 0) {
      isRoot = TRUE;
      objv++, objc--;
    } 
    if (objc < 4) {
      Tcl_AppendResult(interp, "wrong # args: should be \"", 
            Tcl_GetString(objv[0]), " ", Tcl_GetString(objv[1]), 
            " ?-root? x y\"", (char *)NULL);
      return TCL_ERROR;
                   
    }
    if ((Tk_GetPixelsFromObj(interp, tvPtr->tkwin, objv[2], &x) != TCL_OK) ||
      (Tk_GetPixelsFromObj(interp, tvPtr->tkwin, objv[3], &y) != TCL_OK)) {
      return TCL_ERROR;
    }
    if (tvPtr->nVisible == 0) {
      return TCL_OK;
    }
    if (isRoot) {
      int rootX, rootY;

      Tk_GetRootCoords(tvPtr->tkwin, &rootX, &rootY);
      x -= rootX;
      y -= rootY;
    }
    entryPtr = Blt_TreeViewNearestEntry(tvPtr, x, y, TRUE);
    if (entryPtr == NULL) {
      return TCL_OK;
    }
    x = WORLDX(tvPtr, x);
    y = WORLDY(tvPtr, y);
    if (objc > 4) {
      char *where;
      int labelX, labelY, depth;
      TreeViewIcon icon;

      where = "";
      if (entryPtr->flags & ENTRY_HAS_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";
            goto done;
          }
      } 
      depth = DEPTH(tvPtr, entryPtr->node);

      icon = Blt_TreeViewGetEntryIcon(tvPtr, entryPtr);
      if (icon != NULL) {
          int iconWidth, iconHeight, entryHeight;
          int iconX, iconY;
          
          entryHeight = MAX(entryPtr->iconHeight, tvPtr->button.height);
          iconHeight = TreeViewIconHeight(icon);
          iconWidth = TreeViewIconWidth(icon);
          iconX = entryPtr->worldX + ICONWIDTH(depth);
          iconY = entryPtr->worldY;
          if (tvPtr->flatView) {
            iconX += (ICONWIDTH(0) - iconWidth) / 2;
          } else {
            iconX += (ICONWIDTH(depth + 1) - iconWidth) / 2;
          }     
          iconY += (entryHeight - iconHeight) / 2;
          if ((x >= iconX) && (x <= (iconX + iconWidth)) &&
            (y >= iconY) && (y < (iconY + iconHeight))) {
            where = "icon";
            goto done;
          }
      }
      labelX = entryPtr->worldX + ICONWIDTH(depth);
      labelY = entryPtr->worldY;
      if (!tvPtr->flatView) {
          labelX += ICONWIDTH(depth + 1) + 4;
      }         
      if ((x >= labelX) && (x < (labelX + entryPtr->labelWidth)) &&
          (y >= labelY) && (y < (labelY + entryPtr->labelHeight))) {
          where = "label";
      }
    done:
      if (Tcl_SetVar(interp, Tcl_GetString(objv[4]), where, 
            TCL_LEAVE_ERR_MSG) == NULL) {
          return TCL_ERROR;
      }
    }
    Tcl_SetObjResult(interp, NodeToObj(entryPtr->node));
    return TCL_OK;
}


/*ARGSUSED*/
static int
OpenOp(tvPtr, interp, objc, objv)
    TreeView *tvPtr;
    Tcl_Interp *interp;       /* Not used. */
    int objc;
    Tcl_Obj *CONST *objv;
{
    TreeViewEntry *entryPtr;
    TreeViewTagInfo info;
    int recurse, result;
    register int i;

    recurse = FALSE;
    if (objc > 2) {
      int length;
      char *string;

      string = Tcl_GetStringFromObj(objv[2], &length);
      if ((string[0] == '-') && (length > 1) && 
          (strncmp(string, "-recurse", length) == 0)) {
          objv++, objc--;
          recurse = TRUE;
      }
    }
    for (i = 2; i < objc; i++) {
      if (Blt_TreeViewFindTaggedEntries(tvPtr, objv[i], &info) != TCL_OK) {
          return TCL_ERROR;
      }
      for (entryPtr = Blt_TreeViewFirstTaggedEntry(&info); entryPtr != NULL; 
           entryPtr = Blt_TreeViewNextTaggedEntry(&info)) {
          if (recurse) {
            result = Blt_TreeViewApply(tvPtr, entryPtr, 
                                 Blt_TreeViewOpenEntry, 0);
          } else {
            result = Blt_TreeViewOpenEntry(tvPtr, entryPtr);
          }
          if (result != TCL_OK) {
            return TCL_ERROR;
          }
          /* Make sure ancestors of this node aren't hidden. */
          MapAncestors(tvPtr, entryPtr);
      }
    }
    /*FIXME: This is only for flattened entries.  */
    tvPtr->flags |= (TV_LAYOUT | TV_DIRTY | TV_RESORT);
    Blt_TreeViewEventuallyRedraw(tvPtr);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * RangeOp --
 *
 *    Returns the node identifiers in a given range.
 *
 *----------------------------------------------------------------------
 */
static int
RangeOp(tvPtr, interp, objc, objv)
    TreeView *tvPtr;
    Tcl_Interp *interp;
    int objc;
    Tcl_Obj *CONST *objv;
{
    TreeViewEntry *entryPtr, *firstPtr, *lastPtr;
    unsigned int mask;
    int length;
    Tcl_Obj *listObjPtr, *objPtr;
    char *string;

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

    /*
     * The relative order of the first/last markers determines the
     * direction.
     */
    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL);
    if (Blt_TreeIsBefore(lastPtr->node, firstPtr->node)) {
      for (entryPtr = lastPtr; entryPtr != NULL; 
           entryPtr = Blt_TreeViewPrevEntry(entryPtr, mask)) {
          objPtr = NodeToObj(entryPtr->node);
          Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
          if (entryPtr == firstPtr) {
            break;
          }
      }
    } else {
      for (entryPtr = firstPtr; entryPtr != NULL; 
           entryPtr = Blt_TreeViewNextEntry(entryPtr, mask)) {
          objPtr = NodeToObj(entryPtr->node);
          Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
          if (entryPtr == lastPtr) {
            break;
          }
      }
    }
    Tcl_SetObjResult(interp, listObjPtr);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ScanOp --
 *
 *    Implements the quick scan.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
ScanOp(tvPtr, interp, objc, objv)
    TreeView *tvPtr;
    Tcl_Interp *interp;
    int objc;                 /* Not used. */
    Tcl_Obj *CONST *objv;
{
    int x, y;
    char c;
    int length;
    int oper;
    char *string;
    Tk_Window tkwin;

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

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

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

/*ARGSUSED*/
static int
SeeOp(tvPtr, interp, objc, objv)
    TreeView *tvPtr;
    Tcl_Interp *interp;       /* Not used. */
    int objc;
    Tcl_Obj *CONST *objv;
{
    TreeViewEntry *entryPtr;
    int width, height;
    int x, y;
    Tk_Anchor anchor;
    int left, right, top, bottom;
    char *string;

    string = Tcl_GetString(objv[2]);
    anchor = TK_ANCHOR_W;     /* Default anchor is West */
    if ((string[0] == '-') && (strcmp(string, "-anchor") == 0)) {
      if (objc == 3) {
          Tcl_AppendResult(interp, "missing \"-anchor\" argument",
            (char *)NULL);
          return TCL_ERROR;
      }
      if (Tk_GetAnchorFromObj(interp, objv[3], &anchor) != TCL_OK) {
          return TCL_ERROR;
      }
      objc -= 2, objv += 2;
    }
    if (objc == 2) {
      Tcl_AppendResult(interp, "wrong # args: should be \"", objv[0],
          "see ?-anchor anchor? tagOrId\"", (char *)NULL);
      return TCL_ERROR;
    }
    if (GetEntryFromObj(tvPtr, objv[2], &entryPtr) != TCL_OK) {
      return TCL_ERROR;
    }
    if (entryPtr == NULL) {
      return TCL_OK;
    }
    if (entryPtr->flags & ENTRY_HIDDEN) {
      MapAncestors(tvPtr, entryPtr);
      tvPtr->flags |= TV_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_TreeViewComputeLayout(tvPtr);
    }
    width = VPORTWIDTH(tvPtr);
    height = VPORTHEIGHT(tvPtr);

    /*
     * 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 = tvPtr->xOffset;
    right = tvPtr->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(tvPtr, 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 = tvPtr->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 = tvPtr->yOffset;
    bottom = tvPtr->yOffset + height;

    switch (anchor) {
    case TK_ANCHOR_N:
      y = tvPtr->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 = tvPtr->yOffset;
      }
      break;
    }
    if ((y != tvPtr->yOffset) || (x != tvPtr->xOffset)) {
      /* tvPtr->xOffset = x; */
      tvPtr->yOffset = y;
      tvPtr->flags |= TV_SCROLL;
    }
    Blt_TreeViewEventuallyRedraw(tvPtr);
    return TCL_OK;
}

void
Blt_TreeViewClearSelection(tvPtr)
    TreeView *tvPtr;
{
    Blt_DeleteHashTable(&tvPtr->selectTable);
    Blt_InitHashTable(&tvPtr->selectTable, BLT_ONE_WORD_KEYS);
    Blt_ChainReset(tvPtr->selChainPtr);
    Blt_TreeViewEventuallyRedraw(tvPtr);
    if (tvPtr->selectCmd != NULL) {
      EventuallyInvokeSelectCmd(tvPtr);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * 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. */
{
    TreeView *tvPtr = clientData;

    if ((tvPtr->flags & TV_SELECT_EXPORT) == 0) {
      return;
    }
    Blt_TreeViewClearSelection(tvPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * 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(tvPtr, fromPtr, toPtr)
    TreeView *tvPtr;
    TreeViewEntry *fromPtr, *toPtr;
{
    if (tvPtr->flatView) {
      register int i;

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

      proc = (Blt_TreeIsBefore(toPtr->node, fromPtr->node)) 
          ? Blt_TreeViewPrevEntry : Blt_TreeViewNextEntry;
      for (entryPtr = fromPtr; entryPtr != NULL;
           entryPtr = (*proc)(entryPtr, ENTRY_MASK)) {
          SelectEntryApplyProc(tvPtr, 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(tvPtr, interp, objc, objv)
    TreeView *tvPtr;
    Tcl_Interp *interp;       /* Not used. */
    int objc;                 /* Not used. */
    Tcl_Obj *CONST *objv;
{
    TreeViewEntry *entryPtr;

    if (GetEntryFromObj(tvPtr, objv[3], &entryPtr) != TCL_OK) {
      return TCL_ERROR;
    }
    /* Set both the anchor and the mark. Indicates that a single entry
     * is selected. */
    tvPtr->selAnchorPtr = entryPtr;
    tvPtr->selMarkPtr = NULL;
    if (entryPtr != NULL) {
      Tcl_SetObjResult(interp, NodeToObj(entryPtr->node));
    }
    Blt_TreeViewEventuallyRedraw(tvPtr);
    return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * SelectionClearallOp
 *
 *    Clears the entire selection.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    The selection changes.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
SelectionClearallOp(tvPtr, interp, objc, objv)
    TreeView *tvPtr;
    Tcl_Interp *interp;       /* Not used. */
    int objc;                 /* Not used. */
    Tcl_Obj *CONST *objv;     /* Not used. */
{
    Blt_TreeViewClearSelection(tvPtr);
    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(tvPtr, interp, objc, objv)
    TreeView *tvPtr;
    Tcl_Interp *interp;
    int objc;                 /* Not used. */
    Tcl_Obj *CONST *objv;
{
    TreeViewEntry *entryPtr;
    int bool;

    if (Blt_TreeViewGetEntry(tvPtr, objv[3], &entryPtr) != TCL_OK) {
      return TCL_ERROR;
    }
    bool = Blt_TreeViewEntryIsSelected(tvPtr, entryPtr);
    Tcl_SetObjResult(interp, Tcl_NewBooleanObj(bool));
    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(tvPtr, interp, objc, objv)
    TreeView *tvPtr;
    Tcl_Interp *interp;       /* Not used. */
    int objc;                 /* Not used. */
    Tcl_Obj *CONST *objv;
{
    TreeViewEntry *entryPtr;

    if (GetEntryFromObj(tvPtr, objv[3], &entryPtr) != TCL_OK) {
      return TCL_ERROR;
    }
    if (tvPtr->selAnchorPtr == NULL) {
      Tcl_AppendResult(interp, "selection anchor must be set first", 
             (char *)NULL);
      return TCL_ERROR;
    }
    if (tvPtr->selMarkPtr != entryPtr) {
      Blt_ChainLink *linkPtr, *nextPtr;
      TreeViewEntry *selectPtr;

      /* Deselect entry from the list all the way back to the anchor. */
      for (linkPtr = Blt_ChainLastLink(tvPtr->selChainPtr); linkPtr != NULL; 
           linkPtr = nextPtr) {
          nextPtr = Blt_ChainPrevLink(linkPtr);
          selectPtr = Blt_ChainGetValue(linkPtr);
          if (selectPtr == tvPtr->selAnchorPtr) {
            break;
          }
          Blt_TreeViewDeselectEntry(tvPtr, selectPtr);
      }
      tvPtr->flags &= ~TV_SELECT_MASK;
      tvPtr->flags |= TV_SELECT_SET;
      SelectRange(tvPtr, tvPtr->selAnchorPtr, entryPtr);
      Tcl_SetObjResult(interp, NodeToObj(entryPtr->node));
      tvPtr->selMarkPtr = entryPtr;

      Blt_TreeViewEventuallyRedraw(tvPtr);
      if (tvPtr->selectCmd != NULL) {
          EventuallyInvokeSelectCmd(tvPtr);
      }
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * 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(tvPtr, interp, objc, objv)
    TreeView *tvPtr;
    Tcl_Interp *interp;
    int objc;                 /* Not used. */
    Tcl_Obj *CONST *objv;
{
    int bool;

    bool = (Blt_ChainGetLength(tvPtr->selChainPtr) > 0);
    Tcl_SetObjResult(interp, Tcl_NewBooleanObj(bool));
    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(tvPtr, interp, objc, objv)
    TreeView *tvPtr;
    Tcl_Interp *interp;
    int objc;                 /* Not used. */
    Tcl_Obj *CONST *objv;
{
    TreeViewEntry *firstPtr, *lastPtr;
    char *string;

    tvPtr->flags &= ~TV_SELECT_MASK;
    string = Tcl_GetString(objv[2]);
    switch (string[0]) {
    case 's':
      tvPtr->flags |= TV_SELECT_SET;
      break;
    case 'c':
      tvPtr->flags |= TV_SELECT_CLEAR;
      break;
    case 't':
      tvPtr->flags |= TV_SELECT_TOGGLE;
      break;
    }
    if (Blt_TreeViewGetEntry(tvPtr, objv[3], &firstPtr) != TCL_OK) {
      return TCL_ERROR;
    }
    if ((firstPtr->flags & ENTRY_HIDDEN) && 
      (!(tvPtr->flags & TV_SELECT_CLEAR))) {
      Tcl_AppendResult(interp, "can't select hidden node \"", 
            Tcl_GetString(objv[3]), "\"", (char *)NULL);
      return TCL_ERROR;
    }
    lastPtr = firstPtr;
    if (objc > 4) {
      if (Blt_TreeViewGetEntry(tvPtr, objv[4], &lastPtr) != TCL_OK) {
          return TCL_ERROR;
      }
      if ((lastPtr->flags & ENTRY_HIDDEN) && 
            (!(tvPtr->flags & TV_SELECT_CLEAR))) {
          Tcl_AppendResult(interp, "can't select hidden node \"", 
                 Tcl_GetString(objv[4]), "\"", (char *)NULL);
          return TCL_ERROR;
      }
    }
    if (firstPtr == lastPtr) {
      SelectEntryApplyProc(tvPtr, firstPtr);
    } else {
      SelectRange(tvPtr, firstPtr, lastPtr);
    }
    /* Set both the anchor and the mark. Indicates that a single entry
     * is selected. */
    if (tvPtr->selAnchorPtr == NULL) {
      tvPtr->selAnchorPtr = firstPtr;
    }
    if (tvPtr->flags & TV_SELECT_EXPORT) {
      Tk_OwnSelection(tvPtr->tkwin, XA_PRIMARY, LostSelection, tvPtr);
    }
    Blt_TreeViewEventuallyRedraw(tvPtr);
    if (tvPtr->selectCmd != NULL) {
      EventuallyInvokeSelectCmd(tvPtr);
    }
    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_Op)SelectionAnchorOp, 4, 4, "tagOrId",},
    {"clear", 5, (Blt_Op)SelectionSetOp, 4, 5, "first ?last?",},
    {"clearall", 6, (Blt_Op)SelectionClearallOp, 3, 3, "",},
    {"includes", 1, (Blt_Op)SelectionIncludesOp, 4, 4, "tagOrId",},
    {"mark", 1, (Blt_Op)SelectionMarkOp, 4, 4, "tagOrId",},
    {"present", 1, (Blt_Op)SelectionPresentOp, 3, 3, "",},
    {"set", 1, (Blt_Op)SelectionSetOp, 4, 5, "first ?last?",},
    {"toggle", 1, (Blt_Op)SelectionSetOp, 4, 5, "first ?last?",},
};
static int nSelectionOps = sizeof(selectionOps) / sizeof(Blt_OpSpec);

static int
SelectionOp(tvPtr, interp, objc, objv)
    TreeView *tvPtr;
    Tcl_Interp *interp;
    int objc;
    Tcl_Obj *CONST *objv;
{
    Blt_Op proc;
    int result;

    proc = Blt_GetOpFromObj(interp, nSelectionOps, selectionOps, BLT_OP_ARG2, 
      objc, objv, 0);
    if (proc == NULL) {
      return TCL_ERROR;
    }
    result = (*proc) (tvPtr, interp, objc, objv);
    return result;
}


/*
 *----------------------------------------------------------------------
 *
 * TagForgetOp --
 *
 *---------------------------------------------------------------------- 
 */
/*ARGSUSED*/
static int
TagForgetOp(tvPtr, interp, objc, objv)
    TreeView *tvPtr;
    Tcl_Interp *interp;
    int objc;                 /* Not used. */
    Tcl_Obj *CONST *objv;
{
    register int i;

    for (i = 3; i < objc; i++) {
      Blt_TreeForgetTag(tvPtr->tree, Tcl_GetString(objv[i]));
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * TagNamesOp --
 *
 *---------------------------------------------------------------------- 
 */
static int
TagNamesOp(tvPtr, interp, objc, objv)
    TreeView *tvPtr;
    Tcl_Interp *interp;
    int objc;
    Tcl_Obj *CONST *objv;
{
    Tcl_Obj *listObjPtr, *objPtr;

    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
    objPtr = Tcl_NewStringObj("all", -1);
    Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
    if (objc == 3) {
      Blt_HashEntry *hPtr;
      Blt_HashSearch cursor;
      Blt_TreeTagEntry *tPtr;

      objPtr = Tcl_NewStringObj("root", -1);
      Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
      for (hPtr = Blt_TreeFirstTag(tvPtr->tree, &cursor); hPtr != NULL;
           hPtr = Blt_NextHashEntry(&cursor)) {
          tPtr = Blt_GetHashValue(hPtr);
          objPtr = Tcl_NewStringObj(tPtr->tagName, -1);
          Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
      }
    } else {
      register int i;
      TreeViewEntry *entryPtr;
      Blt_List list;
      Blt_ListNode listNode;

      for (i = 3; i < objc; i++) {
          if (Blt_TreeViewGetEntry(tvPtr, objv[i], &entryPtr) != TCL_OK) {
            return TCL_ERROR;
          }
          list = Blt_ListCreate(BLT_ONE_WORD_KEYS);
          Blt_TreeViewGetTags(interp, tvPtr, entryPtr, list);
          for (listNode = Blt_ListFirstNode(list); listNode != NULL; 
             listNode = Blt_ListNextNode(listNode)) {
            objPtr = Tcl_NewStringObj(Blt_ListGetKey(listNode), -1);
            Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
          }
          Blt_ListDestroy(list);
      }
    }
    Tcl_SetObjResult(interp, listObjPtr);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * TagNodesOp --
 *
 *---------------------------------------------------------------------- 
 */
static int
TagNodesOp(tvPtr, interp, objc, objv)
    TreeView *tvPtr;
    Tcl_Interp *interp;
    int objc;
    Tcl_Obj *CONST *objv;
{
    Blt_HashEntry *hPtr;
    Blt_HashSearch cursor;
    Blt_HashTable nodeTable;
    Blt_TreeNode node;
    TreeViewTagInfo info;
    Tcl_Obj *listObjPtr;
    Tcl_Obj *objPtr;
    TreeViewEntry *entryPtr;
    int isNew;
    register int i;

    Blt_InitHashTable(&nodeTable, BLT_ONE_WORD_KEYS);
    for (i = 3; i < objc; i++) {
      if (Blt_TreeViewFindTaggedEntries(tvPtr, objv[i], &info) != TCL_OK) {
          return TCL_ERROR;
      }
      for (entryPtr = Blt_TreeViewFirstTaggedEntry(&info); entryPtr != NULL; 
           entryPtr = Blt_TreeViewNextTaggedEntry(&info)) {
          Blt_CreateHashEntry(&nodeTable, (char *)entryPtr->node, &isNew);
      }
    }
    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
    for (hPtr = Blt_FirstHashEntry(&nodeTable, &cursor); hPtr != NULL; 
       hPtr = Blt_NextHashEntry(&cursor)) {
      node = (Blt_TreeNode)Blt_GetHashKey(&nodeTable, hPtr);
      objPtr = Tcl_NewIntObj(Blt_TreeNodeId(node));
      Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
    }
    Tcl_SetObjResult(interp, listObjPtr);
    Blt_DeleteHashTable(&nodeTable);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * TagAddOp --
 *
 *---------------------------------------------------------------------- 
 */
static int
TagAddOp(tvPtr, interp, objc, objv)
    TreeView *tvPtr;
    Tcl_Interp *interp;
    int objc;
    Tcl_Obj *CONST *objv;
{
    TreeViewEntry *entryPtr;
    register int i;
    char *tagName;
    TreeViewTagInfo info;

    tagName = Tcl_GetString(objv[3]);
    tvPtr->fromPtr = NULL;
    if (strcmp(tagName, "root") == 0) {
      Tcl_AppendResult(interp, "can't add reserved tag \"", tagName, "\"", 
            (char *)NULL);
      return TCL_ERROR;
    }
    if (isdigit(UCHAR(tagName[0]))) {
      Tcl_AppendResult(interp, "invalid tag \"", tagName, 
             "\": can't start with digit", (char *)NULL);
      return TCL_ERROR;
    }
    if (tagName[0] == '@') {
      Tcl_AppendResult(tvPtr->interp, "invalid tag \"", tagName, 
            "\": can't start with \"@\"", (char *)NULL);
      return TCL_ERROR;
    } 
    if (GetEntryFromSpecialId(tvPtr, tagName, &entryPtr) == TCL_OK) {
      Tcl_AppendResult(interp, "invalid tag \"", tagName, 
             "\": is a special id", (char *)NULL);
      return TCL_ERROR;
    }
    for (i = 4; i < objc; i++) {
      if (Blt_TreeViewFindTaggedEntries(tvPtr, objv[i], &info) != TCL_OK) {
          return TCL_ERROR;
      }
      for (entryPtr = Blt_TreeViewFirstTaggedEntry(&info); entryPtr != NULL; 
           entryPtr = Blt_TreeViewNextTaggedEntry(&info)) {
          if (AddTag(tvPtr, entryPtr->node, tagName) != TCL_OK) {
            return TCL_ERROR;
          }
      }
    }
    return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * TagDeleteOp --
 *
 *---------------------------------------------------------------------- 
 */
/*ARGSUSED*/
static int
TagDeleteOp(tvPtr, interp, objc, objv)
    TreeView *tvPtr;
    Tcl_Interp *interp;       /* Not used. */
    int objc;
    Tcl_Obj *CONST *objv;
{
    char *tagName;
    Blt_HashTable *tablePtr;
    TreeViewTagInfo info;

    tagName = Tcl_GetString(objv[3]);
    tablePtr = Blt_TreeTagHashTable(tvPtr->tree, tagName);
    if (tablePtr != NULL) {
        register int i;
        Blt_HashEntry *hPtr;
      TreeViewEntry *entryPtr;

        for (i = 4; i < objc; i++) {
          if (Blt_TreeViewFindTaggedEntries(tvPtr, objv[i], &info)!= TCL_OK) {
            return TCL_ERROR;
          }
          for (entryPtr = Blt_TreeViewFirstTaggedEntry(&info); 
            entryPtr != NULL; 
            entryPtr = Blt_TreeViewNextTaggedEntry(&info)) {
              hPtr = Blt_FindHashEntry(tablePtr, (char *)entryPtr->node);
              if (hPtr != NULL) {
                Blt_DeleteHashEntry(tablePtr, hPtr);
              }
         }
       }
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * TagOp --
 *
 *---------------------------------------------------------------------- 
 */
static Blt_OpSpec tagOps[] = {
    {"add", 1, (Blt_Op)TagAddOp, 5, 0, "tag id...",},
    {"delete", 2, (Blt_Op)TagDeleteOp, 5, 0, "tag id...",},
    {"forget", 1, (Blt_Op)TagForgetOp, 4, 0, "tag...",},
    {"names", 2, (Blt_Op)TagNamesOp, 3, 0, "?id...?",}, 
    {"nodes", 2, (Blt_Op)TagNodesOp, 4, 0, "tag ?tag...?",},
};

static int nTagOps = sizeof(tagOps) / sizeof(Blt_OpSpec);

static int
TagOp(tvPtr, interp, objc, objv)
    TreeView *tvPtr;
    Tcl_Interp *interp;
    int objc;
    Tcl_Obj *CONST *objv;
{
    Blt_Op proc;
    int result;

    proc = Blt_GetOpFromObj(interp, nTagOps, tagOps, BLT_OP_ARG2, objc, objv, 
      0);
    if (proc == NULL) {
      return TCL_ERROR;
    }
    result = (*proc)(tvPtr, interp, objc, objv);
    return result;
}

/*ARGSUSED*/
static int
ToggleOp(tvPtr, interp, objc, objv)
    TreeView *tvPtr;
    Tcl_Interp *interp;       /* Not used. */
    int objc;
    Tcl_Obj *CONST *objv;
{
    TreeViewEntry *entryPtr;
    TreeViewTagInfo info;

    if (Blt_TreeViewFindTaggedEntries(tvPtr, objv[2], &info) != TCL_OK) {
      return TCL_ERROR;
    }
    for (entryPtr = Blt_TreeViewFirstTaggedEntry(&info); entryPtr != NULL; 
       entryPtr = Blt_TreeViewNextTaggedEntry(&info)) {
      if (entryPtr == NULL) {
          return TCL_OK;
      }
      if (entryPtr->flags & ENTRY_CLOSED) {
          Blt_TreeViewOpenEntry(tvPtr, entryPtr);
      } else {
          Blt_TreeViewPruneSelection(tvPtr, entryPtr);
          if ((tvPtr->focusPtr != NULL) && 
            (Blt_TreeIsAncestor(entryPtr->node, tvPtr->focusPtr->node))) {
            tvPtr->focusPtr = entryPtr;
            Blt_SetFocusItem(tvPtr->bindTable, tvPtr->focusPtr, ITEM_ENTRY);
          }
          if ((tvPtr->selAnchorPtr != NULL) &&
            (Blt_TreeIsAncestor(entryPtr->node, 
                            tvPtr->selAnchorPtr->node))) {
            tvPtr->selAnchorPtr = NULL;
          }
          Blt_TreeViewCloseEntry(tvPtr, entryPtr);
      }
    }
    tvPtr->flags |= TV_SCROLL;
    Blt_TreeViewEventuallyRedraw(tvPtr);
    return TCL_OK;
}

static int
XViewOp(tvPtr, interp, objc, objv)
    TreeView *tvPtr;
    Tcl_Interp *interp;
    int objc;
    Tcl_Obj *CONST *objv;
{
    int width, worldWidth;

    width = VPORTWIDTH(tvPtr);
    worldWidth = tvPtr->worldWidth;
    if (objc == 2) {
      double fract;
      Tcl_Obj *listObjPtr;

      /*
       * Note that we are bounding the fractions between 0.0 and 1.0
       * to support the "canvas"-style of scrolling.
       */
      listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL);
      fract = (double)tvPtr->xOffset / worldWidth;
      fract = CLAMP(fract, 0.0, 1.0);
      Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewDoubleObj(fract));
      fract = (double)(tvPtr->xOffset + width) / worldWidth;
      fract = CLAMP(fract, 0.0, 1.0);
      Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewDoubleObj(fract));
      Tcl_SetObjResult(interp, listObjPtr);
      return TCL_OK;
    }
    if (Blt_GetScrollInfoFromObj(interp, objc - 2, objv + 2, &tvPtr->xOffset,
          worldWidth, width, tvPtr->xScrollUnits, tvPtr->scrollMode) 
          != TCL_OK) {
      return TCL_ERROR;
    }
    tvPtr->flags |= TV_XSCROLL;
    Blt_TreeViewEventuallyRedraw(tvPtr);
    return TCL_OK;
}

static int
YViewOp(tvPtr, interp, objc, objv)
    TreeView *tvPtr;
    Tcl_Interp *interp;
    int objc;
    Tcl_Obj *CONST *objv;
{
    int height, worldHeight;

    height = VPORTHEIGHT(tvPtr);
    worldHeight = tvPtr->worldHeight;
    if (objc == 2) {
      double fract;
      Tcl_Obj *listObjPtr;

      listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL);
      /* Report first and last fractions */
      fract = (double)tvPtr->yOffset / worldHeight;
      fract = CLAMP(fract, 0.0, 1.0);
      Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewDoubleObj(fract));
      fract = (double)(tvPtr->yOffset + height) / worldHeight;
      fract = CLAMP(fract, 0.0, 1.0);
      Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewDoubleObj(fract));
      Tcl_SetObjResult(interp, listObjPtr);
      return TCL_OK;
    }
    if (Blt_GetScrollInfoFromObj(interp, objc - 2, objv + 2, &tvPtr->yOffset,
          worldHeight, height, tvPtr->yScrollUnits, tvPtr->scrollMode)
      != TCL_OK) {
      return TCL_ERROR;
    }
    tvPtr->flags |= TV_SCROLL;
    Blt_TreeViewEventuallyRedraw(tvPtr);
    return TCL_OK;
}

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

static int nTreeViewOps = sizeof(treeViewOps) / sizeof(Blt_OpSpec);

int
Blt_TreeViewWidgetInstCmd(clientData, interp, objc, objv)
    ClientData clientData;    /* Information about the widget. */
    Tcl_Interp *interp;       /* Interpreter to report errors back to. */
    int objc;                 /* Number of arguments. */
    Tcl_Obj *CONST *objv;     /* Vector of argument strings. */
{
    Blt_Op proc;
    TreeView *tvPtr = clientData;
    int result;

    proc = Blt_GetOpFromObj(interp, nTreeViewOps, treeViewOps, BLT_OP_ARG1, 
      objc, objv, 0);
    if (proc == NULL) {
      return TCL_ERROR;
    }
    Tcl_Preserve(tvPtr);
    result = (*proc) (tvPtr, interp, objc, objv);
    Tcl_Release(tvPtr);
    return result;
}

#endif /* NO_TREEVIEW */

Generated by  Doxygen 1.6.0   Back to index