Logo Search packages:      
Sourcecode: saods9 version File versions

bltHierbox.c

/*
 * bltHierbox.c --
 *
 *    This module implements an hierarchy widget for the BLT toolkit.
 *
 * Copyright -1998 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 "hierbox" 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_HIERBOX
#include "bltBind.h"
#include "bltImage.h"
#include "bltHash.h"
#include "bltChain.h"
#include "bltList.h"
#include "bltTile.h"
#include <X11/Xutil.h>
#include <X11/Xatom.h>

#if HAVE_UTF
#else
#define Tcl_NumUtfChars(s,n)  (((n) == -1) ? strlen((s)) : (n))
#define Tcl_UtfAtIndex(s,i)   ((s) + (i))
#endif

#define SEPARATOR_NONE        ((char *)-1)
#define SEPARATOR_LIST        ((char *)NULL)
#define APPEND                (-1)

/*
 * The macro below is used to modify a "char" value (e.g. by casting
 * it to an unsigned character) so that it can be used safely with
 * macros such as isspace.
 */
#define UCHAR(c) ((unsigned char) (c))

#define APPLY_BEFORE          (1<<0)
#define APPLY_OPEN_ONLY       (1<<1)
#define APPLY_RECURSE         (1<<2)

#define BUTTON_IPAD           1
#define BUTTON_SIZE           7
#define INSET_PAD       2
#define ICON_PADX       2
#define ICON_PADY       1
#define LABEL_PADX            3
#define LABEL_PADY            0
#define FOCUS_WIDTH           1

#define CLAMP(val,low,hi)     \
      (((val) < (low)) ? (low) : ((val) > (hi)) ? (hi) : (val))
#define ODD(x)                ((x) | 0x01)
#define TOGGLE(x, mask)       \
      (((x) & (mask)) ? ((x) & ~(mask)) : ((x) | (mask)))

#define VPORTWIDTH(h)         (Tk_Width((h)->tkwin) - 2 * (h)->inset)
#define VPORTHEIGHT(h)        (Tk_Height((h)->tkwin) - 2 * (h)->inset)

#define WORLDX(h, sx)         ((sx) - (h)->inset + (h)->xOffset)
#define WORLDY(h, sy)         ((sy) - (h)->inset + (h)->yOffset)

#define SCREENX(h, wx)        ((wx) - (h)->xOffset + (h)->inset)
#define SCREENY(h, wy)        ((wy) - (h)->yOffset + (h)->inset)

#define LEVELWIDTH(d)         (hboxPtr->levelInfo[(d)].width)
#define LEVELX(d)       (hboxPtr->levelInfo[(d)].x)

#define GETFONT(h, f)         (((f) == NULL) ? (h)->defFont : (f))
#define GETCOLOR(h, c)        (((c) == NULL) ? (h)->defColor : (c))

/*
 * ----------------------------------------------------------------------------
 *
 *  Internal hierarchy widget flags:
 *
 *    HIERBOX_LAYOUT          The layout of the hierarchy needs to be
 *                      recomputed.
 *
 *    HIERBOX_REDRAW          A redraw request is pending for the widget.
 *
 *    HIERBOX_XSCROLL         X-scroll request is pending.
 *    HIERBOX_YSCROLL         Y-scroll request is pending.
 *    HIERBOX_SCROLL          Both X-scroll and  Y-scroll requests are
 *                      pending.
 *
 *    HIERBOX_FOCUS           The widget is receiving keyboard events.
 *                      Draw the focus highlight border around the
 *                      widget.
 *
 *    HIERBOX_DIRTY           The hierarchy has changed, possibly invalidating
 *                      locations and pointers to entries.  This widget
 *                      need to recompute its layout.
 *
 *    HIERBOX_BORDERS         The borders of the widget (highlight ring and
 *                      3-D border) need to be redrawn.
 *
 *
 *  Selection related flags:
 *
 *    SELECTION_EXPORT  Export the selection to X.
 *
 *    SELECTION_PENDING A selection command idle task is pending.
 *
 *    SELECTION_CLEAR         Entry's selection flag is to be cleared.
 *
 *    SELECTION_SET           Entry's selection flag is to be set.
 *
 *    SELECTION_TOGGLE  Entry's selection flag is to be toggled.
 *
 *    SELECTION_MASK          Mask of selection set/clear/toggle flags.
 *
 * ---------------------------------------------------------------------------
 */
#define HIERBOX_LAYOUT        (1<<0)
#define HIERBOX_REDRAW        (1<<1)
#define HIERBOX_XSCROLL       (1<<2)
#define HIERBOX_YSCROLL       (1<<3)
#define HIERBOX_SCROLL        (HIERBOX_XSCROLL | HIERBOX_YSCROLL)
#define HIERBOX_FOCUS         (1<<4)
#define HIERBOX_DIRTY         (1<<5)
#define HIERBOX_BORDERS       (1<<6)

#define SELECTION_PENDING     (1<<15)
#define SELECTION_EXPORT      (1<<16)
#define SELECTION_CLEAR       (1<<17)
#define SELECTION_SET         (1<<18)
#define SELECTION_TOGGLE      (SELECTION_SET | SELECTION_CLEAR)
#define SELECTION_MASK        (SELECTION_SET | SELECTION_CLEAR)

/*
 * -------------------------------------------------------------------------
 *
 *  Internal entry flags:
 *
 *    ENTRY_BUTTON            Indicates that a button needs to be
 *                      drawn for this entry.
 *
 *    ENTRY_OPEN        Indicates that the entry is open and
 *                      its subentries should also be displayed.
 *
 *    ENTRY_MAPPED            Indicates that the entry is mapped (i.e.
 *                      can be viewed by opening or scrolling.
 *
 *    BUTTON_AUTO
 *    BUTTON_SHOW
 *    BUTTON_MASK
 *
 * -------------------------------------------------------------------------
 */
#define ENTRY_BUTTON    (1<<0)
#define ENTRY_OPEN      (1<<2)
#define ENTRY_MAPPED    (1<<3)
#define BUTTON_AUTO     (1<<8)
#define BUTTON_SHOW     (1<<9)
#define BUTTON_MASK     (BUTTON_AUTO | BUTTON_SHOW)

#define DEF_ENTRY_BACKGROUND        (char *)NULL
#define DEF_ENTRY_BG_MONO           (char *)NULL
#define DEF_ENTRY_BIND_TAGS         "Entry all"
#define DEF_ENTRY_BUTTON            "auto"
#define DEF_ENTRY_COMMAND           (char *)NULL
#define DEF_ENTRY_DATA              (char *)NULL
#define DEF_ENTRY_FOREGROUND        (char *)NULL
#define DEF_ENTRY_FG_MONO           (char *)NULL
#define DEF_ENTRY_FONT              (char *)NULL
#define DEF_ENTRY_ICONS             (char *)NULL
#define DEF_ENTRY_ACTIVE_ICONS            (char *)NULL
#define DEF_ENTRY_IMAGES            (char *)NULL
#define DEF_ENTRY_LABEL             (char *)NULL
#define DEF_ENTRY_SHADOW_COLOR            (char *)NULL
#define DEF_ENTRY_SHADOW_MONO       (char *)NULL
#define DEF_ENTRY_TEXT              (char *)NULL

#define DEF_BUTTON_ACTIVE_BACKGROUND      RGB_WHITE
#define DEF_BUTTON_ACTIVE_BG_MONO   STD_ACTIVE_BG_MONO
#define DEF_BUTTON_ACTIVE_FOREGROUND      STD_ACTIVE_FOREGROUND
#define DEF_BUTTON_ACTIVE_FG_MONO   STD_ACTIVE_FG_MONO
#define DEF_BUTTON_BORDERWIDTH            "1"
#if (TK_MAJOR_VERSION == 4) 
#define DEF_BUTTON_CLOSE_RELIEF           "flat"
#define DEF_BUTTON_OPEN_RELIEF            "flat"
#else
#define DEF_BUTTON_CLOSE_RELIEF           "solid"
#define DEF_BUTTON_OPEN_RELIEF            "solid"
#endif
#define DEF_BUTTON_IMAGES           (char *)NULL
#define DEF_BUTTON_NORMAL_BACKGROUND      RGB_WHITE
#define DEF_BUTTON_NORMAL_BG_MONO   STD_NORMAL_BG_MONO
#define DEF_BUTTON_NORMAL_FOREGROUND      STD_NORMAL_FOREGROUND
#define DEF_BUTTON_NORMAL_FG_MONO   STD_NORMAL_FG_MONO
#define DEF_BUTTON_SIZE             "7"

#define DEF_HIERBOX_ACTIVE_BACKGROUND     RGB_LIGHTBLUE0
#define DEF_HIERBOX_ACTIVE_SELECT_BACKGROUND    RGB_LIGHTBLUE1
#define DEF_HIERBOX_ACTIVE_BG_MONO  STD_ACTIVE_BG_MONO
#define DEF_HIERBOX_ACTIVE_FOREGROUND     RGB_BLACK
#define DEF_HIERBOX_ACTIVE_RELIEF   "flat"
#define DEF_HIERBOX_ACTIVE_STIPPLE  "gray25"
#define DEF_HIERBOX_ALLOW_DUPLICATES      "yes"
#define DEF_HIERBOX_BACKGROUND            RGB_WHITE
#define DEF_HIERBOX_BORDERWIDTH     STD_BORDERWIDTH
#define DEF_HIERBOX_COMMAND         (char *)NULL
#define DEF_HIERBOX_CURSOR          (char *)NULL
#define DEF_HIERBOX_DASHES          "dot"
#define DEF_HIERBOX_EXPORT_SELECTION      "no"
#define DEF_HIERBOX_FOREGROUND            STD_NORMAL_FOREGROUND
#define DEF_HIERBOX_FG_MONO         STD_NORMAL_FG_MONO
#define DEF_HIERBOX_FOCUS_DASHES    "dot"
#define DEF_HIERBOX_FOCUS_EDIT            "no"
#define DEF_HIERBOX_FOCUS_FOREGROUND      STD_ACTIVE_FOREGROUND
#define DEF_HIERBOX_FOCUS_FG_MONO   STD_ACTIVE_FG_MONO
#define DEF_HIERBOX_FONT            STD_FONT
#define DEF_HIERBOX_HEIGHT          "400"
#define DEF_HIERBOX_HIDE_ROOT       "no"
#define DEF_HIERBOX_HIGHLIGHT_BACKGROUND  STD_NORMAL_BACKGROUND
#define DEF_HIERBOX_HIGHLIGHT_BG_MONO     STD_NORMAL_BG_MONO
#define DEF_HIERBOX_HIGHLIGHT_COLOR RGB_BLACK
#define DEF_HIERBOX_HIGHLIGHT_WIDTH "2"
#define DEF_HIERBOX_LINE_COLOR            RGB_GREY50
#define DEF_HIERBOX_LINE_MONO       STD_NORMAL_FG_MONO
#define DEF_HIERBOX_LINE_SPACING    "0"
#define DEF_HIERBOX_LINE_WIDTH            "1"
#define DEF_HIERBOX_MAKE_PATH       "no"
#define DEF_HIERBOX_NORMAL_BACKGROUND     STD_NORMAL_BACKGROUND
#define DEF_HIERBOX_NORMAL_FG_MONO  STD_ACTIVE_FG_MONO
#define DEF_HIERBOX_RELIEF          "sunken"
#define DEF_HIERBOX_SCROLL_INCREMENT      "0"
#define DEF_HIERBOX_SCROLL_MODE           "hierbox"
#define DEF_HIERBOX_SCROLL_TILE           "yes"
#define DEF_HIERBOX_SELECT_BACKGROUND     RGB_LIGHTSKYBLUE1
#define DEF_HIERBOX_SELECT_BG_MONO        STD_SELECT_BG_MONO
#define DEF_HIERBOX_SELECT_BORDERWIDTH "1"
#define DEF_HIERBOX_SELECT_CMD            (char *)NULL
#define DEF_HIERBOX_SELECT_FOREGROUND     STD_SELECT_FOREGROUND
#define DEF_HIERBOX_SELECT_FG_MONO        STD_SELECT_FG_MONO
#define DEF_HIERBOX_SELECT_MODE           "single"
#define DEF_HIERBOX_SELECT_RELIEF   "flat"
#define DEF_HIERBOX_SEPARATOR       (char *)NULL
#define DEF_HIERBOX_SHOW_ROOT       "yes"
#define DEF_HIERBOX_SORT_SELECTION  "no"
#define DEF_HIERBOX_TAKE_FOCUS            "1"
#define DEF_HIERBOX_TEXT_COLOR            STD_NORMAL_FOREGROUND
#define DEF_HIERBOX_TEXT_MONO       STD_NORMAL_FG_MONO
#define DEF_HIERBOX_TILE            (char *)NULL
#define DEF_HIERBOX_TRIMLEFT        ""
#define DEF_HIERBOX_WIDTH           "200"

typedef struct HierboxStruct Hierbox;
typedef struct EntryStruct Entry;
typedef struct TreeStruct Tree;

typedef int (CompareProc) _ANSI_ARGS_((Tcl_Interp *interp, char *name,
      char *pattern));
typedef int (ApplyProc) _ANSI_ARGS_((Hierbox *hboxPtr, Tree * treePtr));
typedef Tree *(IterProc) _ANSI_ARGS_((Tree * treePtr, unsigned int mask));

extern Tk_CustomOption bltDashesOption;
extern Tk_CustomOption bltDistanceOption;
extern Tk_CustomOption bltShadowOption;
extern Tk_CustomOption bltTileOption;
extern Tk_CustomOption bltUidOption;

static Tk_OptionParseProc StringToButton;
static Tk_OptionPrintProc ButtonToString;
static Tk_OptionParseProc StringToImages;
static Tk_OptionPrintProc ImagesToString;
static Tk_OptionParseProc StringToScrollMode;
static Tk_OptionPrintProc ScrollModeToString;
static Tk_OptionParseProc StringToSeparator;
static Tk_OptionPrintProc SeparatorToString;
/*
 * Contains a pointer to the widget that's currently being configured.
 * This is used in the custom configuration parse routine for images.
 */
static Hierbox *hierBox;

static Tk_CustomOption imagesOption =
{
    StringToImages, ImagesToString, (ClientData)&hierBox,
};
static Tk_CustomOption buttonOption =
{
    StringToButton, ButtonToString, (ClientData)0,
};
static Tk_CustomOption scrollModeOption =
{
    StringToScrollMode, ScrollModeToString, (ClientData)0,
};
static Tk_CustomOption separatorOption = 
{
    StringToSeparator, SeparatorToString, (ClientData)0,
};
/*
 * CachedImage --
 *
 *    Since instances of the same Tk image can be displayed in
 *    different windows with possibly different color palettes, Tk
 *    internally stores each instance in a linked list.  But if
 *    the instances are used in the same widget and therefore use
 *    the same color palette, this adds a lot of overhead,
 *    especially when deleting instances from the linked list.
 *
 *    For the hierbox widget, we never need more than a single
 *    instance of an image, regardless of how many times it's used.
 *    So one solution is to cache the image, maintaining a reference
 *    count for each image used in the widget.  It's likely that the
 *    hierarchy widget will use many instances of the same image
 *    (for example the open/close icons).
 */

typedef struct CachedImageStruct {
    Tk_Image tkImage;         /* The Tk image being cached. */
    int refCount;       /* Reference count for this image. */
    short int width, height;  /* Dimensions of the cached image. */
    Blt_HashEntry *hashPtr;   /* Hash table pointer to the image. */
} *CachedImage;

#define ImageHeight(image)          ((image)->height)
#define ImageWidth(image)           ((image)->width)
#define ImageBits(image)            ((image)->tkImage)

/*
 * Tree --
 *
 *    Structure representing a general order tree.
 *
 */
struct TreeStruct {
    Blt_Uid nameId;           /* String identifying the node within this
                         * hierarchy.  Does not necessarily have to
                         * be unique. */

    Entry *entryPtr;          /* Points to the entry structure at this
                         * node. */

    Tree *parentPtr;          /* Pointer to the parent node.
                         * If NULL, this is the root node. */

    Blt_Chain *chainPtr;      /* Pointer to list of child nodes. The list
                         * isn't allocated until the child nodes are
                         * to be inserted. */

    Blt_ChainLink *linkPtr;   /* List link containing this node in parent.
                         * This is used to remove the node from
                         * its parent when destroying the node. */

    short int level;          /* The level of this node in the tree. */
};

/*
 * Entry --
 *
 *    Contains data-specific information how to represent the data
 *    of a node of the hierarchy.
 *
 */
struct EntryStruct {
    int worldX, worldY;       /* X-Y position in world coordinates
                         * where the entry is positioned. */

    short int width, height;  /* Dimensions of the entry. */

    int lineHeight;           /* Length of the vertical line segment. */

    unsigned int flags;       /* Flags for this entry. For the
                         * definitions of the various bit
                         * fields see below. */

    Blt_Uid dataUid;          /* This value isn't used in C code.
                         * It may be used in Tcl bindings to
                         * associate extra data (other than
                         * the label, image, or text) with the
                         * entry. */

    Blt_Uid tags;       /* List of binding tags for this
                         * entry.  Blt_Uid, not a string,
                         * because in the typical case most
                         * entries will have the same
                         * bintags. */
    Blt_HashEntry *hashPtr;   /* Points the hash entry containing this
                         * entry.  This is how the node index is
                         * stored--as the key of the hash entry. */
    Hierbox *hboxPtr;

    Blt_Uid openCmd, closeCmd;      /* Tcl commands to invoke when entries
                         * are opened or closed. They override
                         * those specified globally. */
    /*
     * Button information:
     */
    short int buttonX, buttonY;     /* X-Y coordinate offsets from to
                         * upper left corner of the entry to
                         * the upper-left corner of the
                         * button.  Used to pick the
                         * button quickly */

    CachedImage *icons;       /* Tk images displayed for the entry.
                         * The first image is the icon
                         * displayed to the left of the
                         * entry's label. The second is icon
                         * displayed when entry is "open". */

    GC iconGC;                /* GC for drawing default bitmaps. */

    short int iconWidth, iconHeight;
                        /* Maximum dimensions for icons and
                         * buttons for this entry. This is
                         * used to align the button, icon, and
                         * text. */

    CachedImage *activeIcons; /* Tk images displayed for the entry.
                         * The first image is the icon
                         * displayed to the left of the
                         * entry's label. The second is icon
                         * displayed when entry is "open". */

    short int levelX;         /* X-coordinate offset of data image
                         * or text for children nodes. */
    /*
     * Label information:
     */
    short int labelWidth, labelHeight;
    char *labelText;          /* Text displayed right of the icon. */


    Tk_Font labelFont;        /* Font of label. Overrides global font
                         * specification. */

    XColor *labelColor;       /* Color of label. Overrides default text color
                         * specification. */

    GC labelGC;

    Shadow labelShadow;

    /*
     * Data (text or image) information:
     */
    Blt_Uid dataText;

    Tk_Font dataFont;

    XColor *dataColor;

    Shadow dataShadow;

    GC dataGC;

    CachedImage *images;

};


static Tk_ConfigSpec entryConfigSpecs[] =
{
    {TK_CONFIG_CUSTOM, "-activeicons", "activeIcons", "Icons",
      DEF_ENTRY_ICONS, Tk_Offset(Entry, activeIcons),
      TK_CONFIG_NULL_OK, &imagesOption},
    {TK_CONFIG_CUSTOM, "-bindtags", "bindTags", "BindTags",
      DEF_ENTRY_BIND_TAGS, Tk_Offset(Entry, tags),
      TK_CONFIG_NULL_OK, &bltUidOption},
    {TK_CONFIG_CUSTOM, "-closecommand", "entryCloseCommand",
      "EntryCloseCommand",
      DEF_ENTRY_COMMAND, Tk_Offset(Entry, closeCmd),
      TK_CONFIG_NULL_OK, &bltUidOption},
    {TK_CONFIG_CUSTOM, "-data", "data", "data",
      DEF_ENTRY_DATA, Tk_Offset(Entry, dataUid),
      TK_CONFIG_NULL_OK, &bltUidOption},
    {TK_CONFIG_CUSTOM, "-button", "button", "Button",
      DEF_ENTRY_BUTTON, Tk_Offset(Entry, flags),
      TK_CONFIG_DONT_SET_DEFAULT, &buttonOption},
    {TK_CONFIG_CUSTOM, "-icons", "icons", "Icons",
      DEF_ENTRY_ICONS, Tk_Offset(Entry, icons),
      TK_CONFIG_NULL_OK, &imagesOption},
    {TK_CONFIG_CUSTOM, "-images", "images", "Images",
      DEF_ENTRY_IMAGES, Tk_Offset(Entry, images),
      TK_CONFIG_NULL_OK, &imagesOption},
    {TK_CONFIG_STRING, "-label", "label", "Label",
      DEF_ENTRY_LABEL, Tk_Offset(Entry, labelText), 0},
    {TK_CONFIG_COLOR, "-labelcolor", "labelColor", "LabelColor",
      DEF_ENTRY_FOREGROUND, Tk_Offset(Entry, labelColor),
      TK_CONFIG_NULL_OK | TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_COLOR, "-labelcolor", "labelColor", "LabelColor",
      DEF_ENTRY_FG_MONO, Tk_Offset(Entry, labelColor),
      TK_CONFIG_NULL_OK | TK_CONFIG_MONO_ONLY},
    {TK_CONFIG_FONT, "-labelfont", "labelFont", "LabelFont",
      DEF_ENTRY_FONT, Tk_Offset(Entry, labelFont), 0},
    {TK_CONFIG_CUSTOM, "-labelshadow", "labelShadow", "LabelShadow",
      DEF_ENTRY_SHADOW_COLOR, Tk_Offset(Entry, labelShadow),
      TK_CONFIG_NULL_OK | TK_CONFIG_COLOR_ONLY, &bltShadowOption},
    {TK_CONFIG_CUSTOM, "-labelshadow", "labelShadow", "LabelShadow",
      DEF_ENTRY_SHADOW_MONO, Tk_Offset(Entry, labelShadow),
      TK_CONFIG_NULL_OK | TK_CONFIG_MONO_ONLY, &bltShadowOption},
    {TK_CONFIG_CUSTOM, "-opencommand", "entryOpenCommand", "EntryOpenCommand",
      DEF_ENTRY_COMMAND, Tk_Offset(Entry, openCmd),
      TK_CONFIG_NULL_OK, &bltUidOption},
    {TK_CONFIG_CUSTOM, "-text", "text", "Text",
      DEF_ENTRY_LABEL, Tk_Offset(Entry, dataText),
      TK_CONFIG_NULL_OK, &bltUidOption},
    {TK_CONFIG_COLOR, "-textcolor", "textColor", "TextColor",
      DEF_ENTRY_FOREGROUND, Tk_Offset(Entry, dataColor),
      TK_CONFIG_NULL_OK | TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_FONT, "-textfont", "textFont", "TextFont",
      DEF_ENTRY_FONT, Tk_Offset(Entry, dataFont), 0},
    {TK_CONFIG_CUSTOM, "-textshadow", "textShadow", "Shadow",
      DEF_ENTRY_SHADOW_COLOR, Tk_Offset(Entry, dataShadow),
      TK_CONFIG_NULL_OK | TK_CONFIG_COLOR_ONLY, &bltShadowOption},
    {TK_CONFIG_CUSTOM, "-textShadow", "textShadow", "Shadow",
      DEF_ENTRY_SHADOW_MONO, Tk_Offset(Entry, dataShadow),
      TK_CONFIG_NULL_OK | TK_CONFIG_MONO_ONLY, &bltShadowOption},
    {TK_CONFIG_END, (char *)NULL, (char *)NULL, (char *)NULL,
      (char *)NULL, 0, 0}
};

/*
 * ButtonAttributes --
 *
 *    A button is the open/close indicator at the far left of the
 *    entry.  It is displayed as a plus or minus in a solid
 *    colored box with optionally an border. It has both "active"
 *    and "normal" colors.
 */

typedef struct {
    XColor *fgColor;          /* Foreground color. */
    Tk_3DBorder border;       /* Background color. */
    XColor *activeFgColor;    /* Active foreground color. */
    Tk_3DBorder activeBorder; /* Active background color. */
    GC lineGC, normalGC, activeGC;
    int reqSize;
    int borderWidth;
    int openRelief, closeRelief;
    int width, height;
    CachedImage *images;

} ButtonAttributes;

/*
 * TextEdit --
 *
 *    This structure is shared by entries when their labels are
 *    edited via the keyboard.  It maintains the location of the
 *    insertion cursor and the text selection for the editted entry.
 *    The structure is shared since we need only one.  The "focus"
 *    entry should be the only entry receiving KeyPress/KeyRelease
 *    events at any time.  Information from the previously editted
 *    entry is overwritten.
 *
 *    Note that all the indices internally are in terms of bytes,
 *    not characters.  This is because UTF-8 strings may encode a
 *    single character into a multi-byte sequence.  To find the
 *    respective character position
 *
 *          n = Tcl_NumUtfChars(string, index);
 *
 *    where n is the resulting character number.
 */
typedef struct {
    int active;
    int insertPos;            /* Position of the cursor within the
                         * array of bytes of the entry's label. */
    int x, y;                 /* Offsets of the insertion cursolsr from the
                         * start of the label.  Used to draw the
                         * the cursor relative to the entry. */
    int width, height;        /* Size of the insertion cursor symbol. */

    int selAnchor;            /* Fixed end of selection. Used to extend
                         * the selection while maintaining the
                         * other end of the selection. */
    int selFirst;       /* Position of the first character in the
                         * selection. */
    int selLast;        /* Position of the last character in the
                         * selection. */

    int cursorOn;       /* Indicates if the cursor is displayed. */
    int onTime, offTime;      /* Time in milliseconds to wait before
                         * changing the cursor from off-to-on
                         * and on-to-off. Setting offTime to 0 makes
                         * the cursor steady. */
    Tcl_TimerToken timerToken;      /* Handle for a timer event called periodically
                         * to blink the cursor. */

} TextEdit;

/*
 * LevelInfo --
 *
 */

typedef struct {
    int x;
    int width;
} LevelInfo;

/*
 * Hierbox --
 *
 *    Represents the hierarchy widget that contains one or more
 *    entries.
 *
 *    Entries are positioned in "world" coordinates, refering to
 *    the virtual hierbox space.  Coordinate 0,0 is the upper-left
 *    of the hierarchy box and the bottom is the end of the last
 *    entry.  The widget's Tk window acts as view port into this
 *    virtual space. The hierbox's xOffset and yOffset fields specify
 *    the location of the view port in the virtual world.  Scrolling
 *    the viewport is therefore simply changing the xOffset and/or
 *    yOffset fields and redrawing.
 *
 *    Note that world coordinates are integers, not signed short
 *    integers like X11 screen coordinates.  It's very easy to
 *    create a hierarchy that is more than 0x7FFF pixels high.
 *
 */
struct HierboxStruct {
    Tk_Window tkwin;          /* Window that embodies the widget.
                                 * NULL means that the window has been
                                 * destroyed but the data structures
                                 * haven't yet been cleaned up.*/

    Display *display;         /* Display containing widget; needed,
                                 * among other things, to release
                                 * resources after tkwin has already
                                 * gone away. */

    Tcl_Interp *interp;       /* Interpreter associated with widget. */

    Tcl_Command cmdToken;     /* Token for widget's command. */

    int flags;                /* For bitfield definitions, see below */

    int allowDuplicates;      /* Allow duplicate entries. */

    int autoCreate;           /* Automatically create path entries
                         * as needed. */
    int exportSelection;      /* Export the selection to X. */

    int hideRoot;       /* Don't display the root entry. */

    int scrollTile;           /* Adjust the tile along with viewport
                         * offsets as the widget is
                         * scrolled. */

    int inset;                /* Total width of all borders,
                         * including traversal highlight and
                         * 3-D border.  Indicates how much
                         * interior stuff must be offset from
                         * outside edges to leave room for
                         * borders. */

    Tk_3DBorder border;       /* 3D border surrounding the window
                         * (viewport). */
    int borderWidth;          /* Width of 3D border. */
    int relief;               /* 3D border relief. */


    int highlightWidth;       /* Width in pixels of highlight to draw
                         * around widget when it has the focus.
                         * <= 0 means don't draw a highlight. */
    XColor *highlightBgColor; /* Color for drawing traversal highlight
                         * area when highlight is off. */
    XColor *highlightColor;   /* Color for drawing traversal highlight. */

    Blt_Tile tile;            /* Tiled background */

    char *separator;          /* Pathname separators */
    char *trimLeft;           /* Leading characters to trim from
                         * pathnames */

    /*
     * Entries are connected by horizontal and vertical lines. They
     * may be drawn dashed or solid.
     */
    int lineWidth;            /* Width of lines connecting entries */
    int dashes;               /* Dash on-off value. */
    XColor *lineColor;        /* Color of connecting lines. */

    /*
     * Button Information:
     *
     * The button is the open/close indicator at the far left of the
     * entry.  It is usually displayed as a plus or minus in a solid
     * colored box with optionally an border. It has both "active"
     * and "normal" colors.
     */
    ButtonAttributes button;


    /*
     * Selection Information:
     *
     * The selection is the rectangle that contains a selected entry.
     * There may be many selected entries.  It is displayed as a
     * solid colored box with optionally a 3D border.
     */
    Tk_3DBorder selBorder;    /* Background color of an highlighted entry.*/

    int selRelief;            /* Relief of selected items. Currently
                         * is always raised. */

    int selBorderWidth;       /* Border width of a selected entry.*/

    XColor *selFgColor;       /* Text color of a selected entry. */

    Tree *selAnchorPtr;       /* Fixed end of selection (i.e. node
                         * at which selection was started.) */

    Blt_HashTable selectTable;
    Blt_Chain selectChain;

    int sortSelection;        /* Indicates if the entries in the
                         * selection should be sorted or
                         * displayed in the order they were
                         * selected. */

    char *selectMode;         /* Selection style.  This value isn't
                         * used in C code (it's just a place
                         * holder), but for the widget's Tcl
                         * bindings. */

    char *selectCmd;          /* Tcl script that's invoked whenever
                         * the selection changes. */

    int leader;               /* Number of pixels padding between entries */

    Tk_Cursor cursor;         /* X Cursor */

    int reqWidth, reqHeight;  /* Requested dimensions of the widget's
                         * window. */

    GC lineGC;                /* GC for drawing dotted line between
                         * entries. */

    XColor *activeFgColor;
    Tk_3DBorder activeBorder;

    XColor *focusColor;
    Blt_Dashes focusDashes;   /* Dash on-off value. */
    GC focusGC;               /* Graphics context for the active label */
    int focusEdit;            /* Indicates if the label of the "focus" entry
                         * can be editted. */
    TextEdit labelEdit;

    Tree *activePtr;
    Tree *focusPtr;           /* Pointer to node that's currently active. */
    Tree *activeButtonPtr;    /* Pointer to last active button */

    /* Number of pixels to move for 1 scroll unit. */
    int reqScrollX, reqScrollY;
    int xScrollUnits, yScrollUnits;

    /* Command strings to control horizontal and vertical scrollbars. */
    char *xScrollCmdPrefix, *yScrollCmdPrefix;

    int scrollMode;           /* Selects mode of scrolling: either
                         * BLT_SCROLL_MODE_HIERBOX, 
                         * BLT_SCROLL_MODE_LISTBOX, or
                         * BLT_SCROLL_MODE_CANVAS. */

    /*
     * Total size of all "open" entries. This represents the range of
     * world coordinates.
     */
    int worldWidth, worldHeight;
    int xOffset, yOffset;     /* Translation between view port and
                         * world origin. */
    int minHeight;            /* Minimum entry height. Used to
                         * to compute what the y-scroll unit
                         * should be. */
    LevelInfo *levelInfo;
    /*
     * Scanning information:
     */
    int scanAnchorX, scanAnchorY;
    /* Scan anchor in screen coordinates. */
    int scanX, scanY;         /* X-Y world coordinate where the scan
                         * started. */


    Blt_HashTable nodeTable;  /* Table of node identifiers */
    Blt_HashTable imageTable; /* Table of Tk images */
    Tree *rootPtr;            /* Root of hierarchy */
    int depth;                /* Maximum depth of the hierarchy. */

    Tree **visibleArr;        /* Array of visible entries */
    int nVisible;       /* Number of entries in the above array */

    int nextSerial;

    char *openCmd, *closeCmd; /* Tcl commands to invoke when entries
                         * are opened or closed. */

    Tk_Font defFont;          /* Specifies a default font for labels and
                         * text data.  It may be overridden for a
                         * single entry by its -*font option. */

    XColor *defColor;         /* Global text color for labels. This
                         * may be overridden by the -color
                         * option for an individual entry. */

    Pixmap iconBitmap, iconMask;/* Default icon bitmaps */
    XColor *iconColor;

    char *takeFocus;
    char *sortCmd;            /* Tcl command to invoke to sort entries */

    ClientData clientData;
    Blt_BindTable bindTable;  /* Binding information for entries. */
    Blt_BindTable buttonBindTable; /* Binding information for buttons. */
};

static Tk_ConfigSpec buttonConfigSpecs[] =
{
    {TK_CONFIG_BORDER, "-activebackground", "activeBackground", "Background",
      DEF_BUTTON_ACTIVE_BG_MONO, Tk_Offset(Hierbox, button.activeBorder),
      TK_CONFIG_MONO_ONLY},
    {TK_CONFIG_BORDER, "-activebackground", "activeBackground", "Background",
      DEF_BUTTON_ACTIVE_BACKGROUND, Tk_Offset(Hierbox, button.activeBorder),
      TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_COLOR, "-activeforeground", "activeForeground", "Foreground",
      DEF_BUTTON_ACTIVE_FOREGROUND, Tk_Offset(Hierbox, button.activeFgColor),
      TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_COLOR, "-activeforeground", "activeForeground", "Foreground",
      DEF_BUTTON_ACTIVE_FG_MONO, Tk_Offset(Hierbox, button.activeFgColor),
      TK_CONFIG_MONO_ONLY},
    {TK_CONFIG_BORDER, "-background", "background", "Background",
      DEF_BUTTON_NORMAL_BACKGROUND, Tk_Offset(Hierbox, button.border),
      TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_BORDER, "-background", "background", "Background",
      DEF_BUTTON_NORMAL_BG_MONO, Tk_Offset(Hierbox, button.border),
      TK_CONFIG_MONO_ONLY},
    {TK_CONFIG_SYNONYM, "-bd", "borderWidth", (char *)NULL, (char *)NULL, 0, 0},
    {TK_CONFIG_SYNONYM, "-bg", "background", (char *)NULL, (char *)NULL, 0, 0},
    {TK_CONFIG_CUSTOM, "-borderwidth", "borderWidth", "BorderWidth",
      DEF_BUTTON_BORDERWIDTH, Tk_Offset(Hierbox, button.borderWidth),
      TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
    {TK_CONFIG_RELIEF, "-closerelief", "closeRelief", "Relief",
      DEF_BUTTON_CLOSE_RELIEF, Tk_Offset(Hierbox, button.closeRelief),
      TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_SYNONYM, "-fg", "foreground", (char *)NULL, (char *)NULL, 0, 0},
    {TK_CONFIG_COLOR, "-foreground", "foreground", "Foreground",
      DEF_BUTTON_NORMAL_FOREGROUND, Tk_Offset(Hierbox, button.fgColor),
      TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_COLOR, "-foreground", "foreground", "Foreground",
      DEF_BUTTON_NORMAL_FG_MONO, Tk_Offset(Hierbox, button.fgColor),
      TK_CONFIG_MONO_ONLY},
    {TK_CONFIG_CUSTOM, "-images", "images", "Images",
      DEF_BUTTON_IMAGES, Tk_Offset(Hierbox, button.images),
      TK_CONFIG_NULL_OK, &imagesOption},
    {TK_CONFIG_RELIEF, "-openrelief", "openRelief", "Relief",
      DEF_BUTTON_OPEN_RELIEF, Tk_Offset(Hierbox, button.openRelief),
      TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_CUSTOM, "-size", "size", "Size", DEF_BUTTON_SIZE, 
      Tk_Offset(Hierbox, button.reqSize), 0, &bltDistanceOption},
    {TK_CONFIG_END, (char *)NULL, (char *)NULL, (char *)NULL,
      (char *)NULL, 0, 0}
};

static Tk_ConfigSpec configSpecs[] =
{
    {TK_CONFIG_BORDER, "-activebackground", "activeBackground",
      "ActiveBackground",
      DEF_HIERBOX_ACTIVE_BACKGROUND, Tk_Offset(Hierbox, activeBorder),
      TK_CONFIG_COLOR_ONLY | TK_CONFIG_NULL_OK},
    {TK_CONFIG_BORDER, "-activebackground", "activeBackground",
      "ActiveBackground",
      DEF_HIERBOX_ACTIVE_BG_MONO, Tk_Offset(Hierbox, activeBorder),
      TK_CONFIG_MONO_ONLY | TK_CONFIG_NULL_OK},
    {TK_CONFIG_COLOR, "-activeforeground", "activeForeground", "Foreground",
      DEF_HIERBOX_ACTIVE_FOREGROUND, Tk_Offset(Hierbox, activeFgColor), 0},
    {TK_CONFIG_BOOLEAN, "-allowduplicates", "allowDuplicates",
      "AllowDuplicates",
      DEF_HIERBOX_ALLOW_DUPLICATES, Tk_Offset(Hierbox, allowDuplicates),
      TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_BOOLEAN, "-autocreate", "autoCreate", "AutoCreate",
      DEF_HIERBOX_MAKE_PATH, Tk_Offset(Hierbox, autoCreate),
      TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_BORDER, "-background", "background", "Background",
      DEF_HIERBOX_BACKGROUND, Tk_Offset(Hierbox, border), 0},
    {TK_CONFIG_SYNONYM, "-bd", "borderWidth", (char *)NULL, (char *)NULL, 0, 0},
    {TK_CONFIG_SYNONYM, "-bg", "background", (char *)NULL, (char *)NULL, 0, 0},
    {TK_CONFIG_ACTIVE_CURSOR, "-cursor", "cursor", "Cursor",
      DEF_HIERBOX_CURSOR, Tk_Offset(Hierbox, cursor), TK_CONFIG_NULL_OK},
    {TK_CONFIG_CUSTOM, "-borderwidth", "borderWidth", "BorderWidth",
      DEF_HIERBOX_BORDERWIDTH, Tk_Offset(Hierbox, borderWidth),
      TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
    {TK_CONFIG_STRING, "-closecommand", "closeCommand", "CloseCommand",
      DEF_HIERBOX_COMMAND, Tk_Offset(Hierbox, closeCmd), TK_CONFIG_NULL_OK},
    {TK_CONFIG_CUSTOM, "-dashes", "dashes", "Dashes",
      DEF_HIERBOX_DASHES, Tk_Offset(Hierbox, dashes),
      TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
    {TK_CONFIG_BOOLEAN, "-exportselection", "exportSelection",
      "ExportSelection",
      DEF_HIERBOX_EXPORT_SELECTION, Tk_Offset(Hierbox, exportSelection),
      TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_SYNONYM, "-fg", "foreground", (char *)NULL, (char *)NULL, 0, 0},
    {TK_CONFIG_CUSTOM, "-focusdashes", "focusDashes", "FocusDashes",
      DEF_HIERBOX_FOCUS_DASHES, Tk_Offset(Hierbox, focusDashes),
      TK_CONFIG_NULL_OK, &bltDashesOption},
    {TK_CONFIG_BOOLEAN, "-focusedit", "focusEdit", "FocusEdit",
      DEF_HIERBOX_FOCUS_EDIT, Tk_Offset(Hierbox, focusEdit), 0},
    {TK_CONFIG_COLOR, "-focusforeground", "focusForeground",
      "FocusForeground",
      DEF_HIERBOX_FOCUS_FOREGROUND, Tk_Offset(Hierbox, focusColor),
      TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_COLOR, "-focusforeground", "focusForeground",
      "FocusForeground",
      DEF_HIERBOX_FOCUS_FG_MONO, Tk_Offset(Hierbox, focusColor),
      TK_CONFIG_MONO_ONLY},
    {TK_CONFIG_FONT, "-font", "font", "Font",
      DEF_HIERBOX_FONT, Tk_Offset(Hierbox, defFont), 0},
    {TK_CONFIG_COLOR, "-foreground", "foreground", "Foreground",
      DEF_HIERBOX_TEXT_COLOR, Tk_Offset(Hierbox, defColor),
      TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_COLOR, "-foreground", "foreground", "Foreground",
      DEF_HIERBOX_TEXT_MONO, Tk_Offset(Hierbox, defColor), TK_CONFIG_MONO_ONLY},
    {TK_CONFIG_CUSTOM, "-height", "height", "Height",
      DEF_HIERBOX_HEIGHT, Tk_Offset(Hierbox, reqHeight),
      TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
    {TK_CONFIG_BOOLEAN, "-hideroot", "hideRoot", "HideRoot",
      DEF_HIERBOX_HIDE_ROOT, Tk_Offset(Hierbox, hideRoot),
      TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_COLOR, "-highlightbackground", "highlightBackground",
      "HighlightBackground",
      DEF_HIERBOX_HIGHLIGHT_BACKGROUND, Tk_Offset(Hierbox, highlightBgColor),
      TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_COLOR, "-highlightbackground", "highlightBackground",
      "HighlightBackground",
      DEF_HIERBOX_HIGHLIGHT_BG_MONO, Tk_Offset(Hierbox, highlightBgColor),
      TK_CONFIG_MONO_ONLY},
    {TK_CONFIG_COLOR, "-highlightcolor", "highlightColor", "HighlightColor",
      DEF_HIERBOX_HIGHLIGHT_COLOR, Tk_Offset(Hierbox, highlightColor), 0},
    {TK_CONFIG_PIXELS, "-highlightthickness", "highlightThickness",
      "HighlightThickness",
      DEF_HIERBOX_HIGHLIGHT_WIDTH, Tk_Offset(Hierbox, highlightWidth),
      TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_COLOR, "-linecolor", "lineColor", "LineColor",
      DEF_HIERBOX_LINE_COLOR, Tk_Offset(Hierbox, lineColor),
      TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_COLOR, "-linecolor", "lineColor", "LineColor",
      DEF_HIERBOX_LINE_MONO, Tk_Offset(Hierbox, lineColor),
      TK_CONFIG_MONO_ONLY},
    {TK_CONFIG_CUSTOM, "-linespacing", "lineSpacing", "LineSpacing",
      DEF_HIERBOX_LINE_SPACING, Tk_Offset(Hierbox, leader),
      TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
    {TK_CONFIG_CUSTOM, "-linewidth", "lineWidth", "LineWidth",
      DEF_HIERBOX_LINE_WIDTH, Tk_Offset(Hierbox, lineWidth),
      TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
    {TK_CONFIG_STRING, "-opencommand", "openCommand", "OpenCommand",
      DEF_HIERBOX_COMMAND, Tk_Offset(Hierbox, openCmd), TK_CONFIG_NULL_OK},
    {TK_CONFIG_RELIEF, "-relief", "relief", "Relief",
      DEF_HIERBOX_RELIEF, Tk_Offset(Hierbox, relief), 0},
    {TK_CONFIG_CUSTOM, "-scrollmode", "scrollMode", "ScrollMode",
      DEF_HIERBOX_SCROLL_MODE, Tk_Offset(Hierbox, scrollMode),
      TK_CONFIG_DONT_SET_DEFAULT, &scrollModeOption},
    {TK_CONFIG_BOOLEAN, "-scrolltile", "scrollTile", "ScrollTile",
      DEF_HIERBOX_SCROLL_TILE, Tk_Offset(Hierbox, scrollTile),
      TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_BORDER, "-selectbackground", "selectBackground", "Foreground",
      DEF_HIERBOX_SELECT_BG_MONO, Tk_Offset(Hierbox, selBorder),
      TK_CONFIG_MONO_ONLY},
    {TK_CONFIG_BORDER, "-selectbackground", "selectBackground", "Foreground",
      DEF_HIERBOX_SELECT_BACKGROUND, Tk_Offset(Hierbox, selBorder),
      TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_CUSTOM, "-selectborderwidth", "selectBorderWidth", "BorderWidth",
      DEF_HIERBOX_SELECT_BORDERWIDTH, Tk_Offset(Hierbox, selBorderWidth),
      TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
    {TK_CONFIG_STRING, "-selectcommand", "selectCommand", "SelectCommand",
      DEF_HIERBOX_SELECT_CMD, Tk_Offset(Hierbox, selectCmd),
      TK_CONFIG_NULL_OK},
    {TK_CONFIG_COLOR, "-selectforeground", "selectForeground", "Background",
      DEF_HIERBOX_SELECT_FG_MONO, Tk_Offset(Hierbox, selFgColor),
      TK_CONFIG_MONO_ONLY},
    {TK_CONFIG_COLOR, "-selectforeground", "selectForeground", "Background",
      DEF_HIERBOX_SELECT_FOREGROUND, Tk_Offset(Hierbox, selFgColor),
      TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_STRING, "-selectmode", "selectMode", "SelectMode",
      DEF_HIERBOX_SELECT_MODE, Tk_Offset(Hierbox, selectMode),
      TK_CONFIG_NULL_OK},
    {TK_CONFIG_RELIEF, "-selectrelief", "selectRelief", "Relief",
      DEF_HIERBOX_SELECT_RELIEF, Tk_Offset(Hierbox, selRelief),
      TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_CUSTOM, "-separator", "separator", "Separator",
      DEF_HIERBOX_SEPARATOR, Tk_Offset(Hierbox, separator), 
      TK_CONFIG_NULL_OK, &separatorOption},
    {TK_CONFIG_STRING, "-takefocus", "takeFocus", "TakeFocus",
      DEF_HIERBOX_TAKE_FOCUS, Tk_Offset(Hierbox, takeFocus), TK_CONFIG_NULL_OK},
    {TK_CONFIG_CUSTOM, "-tile", "tile", "Tile",
      (char *)NULL, Tk_Offset(Hierbox, tile), TK_CONFIG_NULL_OK,
      &bltTileOption},
    {TK_CONFIG_STRING, "-trimleft", "trimLeft", "Trim",
      DEF_HIERBOX_TRIMLEFT, Tk_Offset(Hierbox, trimLeft), TK_CONFIG_NULL_OK},
    {TK_CONFIG_CUSTOM, "-width", "width", "Width",
      DEF_HIERBOX_WIDTH, Tk_Offset(Hierbox, reqWidth),
      TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
    {TK_CONFIG_STRING, "-xscrollcommand", "xScrollCommand", "ScrollCommand",
      (char *)NULL, Tk_Offset(Hierbox, xScrollCmdPrefix), TK_CONFIG_NULL_OK},
    {TK_CONFIG_CUSTOM, "-xscrollincrement", "xScrollIncrement",
      "ScrollIncrement",
      DEF_HIERBOX_SCROLL_INCREMENT, Tk_Offset(Hierbox, reqScrollX),
      TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
    {TK_CONFIG_STRING, "-yscrollcommand", "yScrollCommand", "ScrollCommand",
      (char *)NULL, Tk_Offset(Hierbox, yScrollCmdPrefix), TK_CONFIG_NULL_OK},
    {TK_CONFIG_CUSTOM, "-yscrollincrement", "yScrollIncrement",
      "ScrollIncrement",
      DEF_HIERBOX_SCROLL_INCREMENT, Tk_Offset(Hierbox, reqScrollY),
      TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
    {TK_CONFIG_END, (char *)NULL, (char *)NULL, (char *)NULL,
      (char *)NULL, 0, 0}
};

typedef struct {
    int x, y;                 /* Tracks the current world
                         * coordinates as we traverse through
                         * the tree. After a full-tree
                         * traversal, the y-coordinate will be
                         * the height of the virtual
                         * hierarchy. */
    int maxWidth;       /* Maximum entry width. This is the
                         * width of the virtual hierarchy. */
    int labelOffset;
    int minHeight;            /* Minimum entry height. Used to
                         * to compute what the y-scroll unit
                         * should be. */
    int maxIconWidth;
    int level, depth;
} LayoutInfo;

#define DEF_ICON_WIDTH 16
#define DEF_ICON_HEIGHT 16
static unsigned char folderBits[] =
{
    0x00, 0x00, 0x7c, 0x00, 0x82, 0x00, 0x81, 0x3f, 0x41, 0x40, 0x3f, 0xc0,
    0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0,
    0x01, 0xc0, 0xff, 0xff, 0xfe, 0xff, 0x00, 0x00};

static unsigned char folderMaskBits[] =
{
    0x00, 0x00, 0x7c, 0x00, 0xfe, 0x00, 0xff, 0x3f, 0xff, 0x7f, 0xff, 0xff,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0x00, 0x00};


/* Forward Declarations */
static void DestroyHierbox _ANSI_ARGS_((DestroyData dataPtr));
static void HierboxEventProc _ANSI_ARGS_((ClientData clientdata,
      XEvent *eventPtr));
static void DrawButton _ANSI_ARGS_((Hierbox *hboxPtr, Tree * treePtr,
      Drawable drawable));
static void DisplayHierbox _ANSI_ARGS_((ClientData clientData));
static void HierboxInstCmdDeleteProc _ANSI_ARGS_((ClientData clientdata));
static int HierboxInstCmd _ANSI_ARGS_((ClientData clientdata,
      Tcl_Interp *interp, int argc, char **argv));
static void EventuallyRedraw _ANSI_ARGS_((Hierbox *hboxPtr));
static void SelectCmdProc _ANSI_ARGS_((ClientData clientData));
static void EventuallyInvokeSelectCmd _ANSI_ARGS_((Hierbox *hboxPtr));
static int ComputeVisibleEntries _ANSI_ARGS_((Hierbox *hboxPtr));
static int ConfigureEntry _ANSI_ARGS_((Hierbox *hboxPtr, Entry * entryPtr,
      int argc, char **argv, int flags));
static void ComputeLayout _ANSI_ARGS_((Hierbox *hboxPtr));

static CompareProc ExactCompare, GlobCompare, RegexpCompare;
static ApplyProc SelectNode, GetSelectedLabels, CloseNode, SizeOfNode, 
    IsSelectedNode, MapAncestors, FixUnmappedSelections, UnmapNode, 
    SortNode, OpenNode;
static IterProc NextNode, LastNode;
static Tk_ImageChangedProc ImageChangedProc;
static Blt_ChainCompareProc CompareNodesByTclCmd, CompareNodesByName;
static Blt_BindPickProc PickButton, PickEntry;
static Blt_BindTagProc GetTags;
static Tk_SelectionProc SelectionProc;
static Tk_LostSelProc LostSelection;
static Tcl_CmdProc HierboxCmd;
static Blt_TileChangedProc TileChangedProc;
static Tcl_TimerProc LabelBlinkProc;
static Tcl_FreeProc DestroyNode;


/*
 *----------------------------------------------------------------------
 *
 * StringToScrollMode --
 *
 *    Convert the string reprsenting a scroll mode, to its numeric
 *    form.
 *
 * Results:
 *    If the string is successfully converted, TCL_OK is returned.
 *    Otherwise, TCL_ERROR is returned and an error message is left
 *    in interpreter's result field.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
StringToScrollMode(clientData, interp, tkwin, string, widgRec, offset)
    ClientData clientData;    /* Not used. */
    Tcl_Interp *interp;       /* Interpreter to send results back to */
    Tk_Window tkwin;          /* Not used. */
    char *string;       /* New legend position string */
    char *widgRec;            /* Widget record */
    int offset;               /* offset to XPoint structure */
{
    int *modePtr = (int *)(widgRec + offset);

    if ((string[0] == 'l') && (strcmp(string, "listbox") == 0)) {
      *modePtr = BLT_SCROLL_MODE_LISTBOX;
    } else if ((string[0] == 'h') && (strcmp(string, "hierbox") == 0)) {
      *modePtr = BLT_SCROLL_MODE_HIERBOX;
    } else if ((string[0] == 'c') && (strcmp(string, "canvas") == 0)) {
      *modePtr = BLT_SCROLL_MODE_CANVAS;
    } else {
      Tcl_AppendResult(interp, "bad scroll mode \"", string,
          "\": should be \"hierbox\", \"listbox\", or \"canvas\"", (char *)NULL);
      return TCL_ERROR;
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ScrollModeToString --
 *
 * Results:
 *    The string representation of the button boolean is returned.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static char *
ScrollModeToString(clientData, tkwin, widgRec, offset, freeProcPtr)
    ClientData clientData;    /* Not used. */
    Tk_Window tkwin;          /* Not used. */
    char *widgRec;            /* Widget record */
    int offset;               /* offset of flags field in record */
    Tcl_FreeProc **freeProcPtr;     /* Memory deallocation scheme to use */
{
    int mode = *(int *)(widgRec + offset);

    switch (mode) {
    case BLT_SCROLL_MODE_LISTBOX:
      return "listbox";
    case BLT_SCROLL_MODE_HIERBOX:
      return "hierbox";
    case BLT_SCROLL_MODE_CANVAS:
      return "canvas";
    default:
      return "unknown scroll mode";
    }
}

/*
 *----------------------------------------------------------------------
 *
 * StringToButton --
 *
 *    Convert a string to one of three values.
 *          0 - false, no, off
 *          1 - true, yes, on
 *          2 - auto
 * Results:
 *    If the string is successfully converted, TCL_OK is returned.
 *    Otherwise, TCL_ERROR is returned and an error message is left in
 *    interpreter's result field.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
StringToButton(clientData, interp, tkwin, string, widgRec, offset)
    ClientData clientData;    /* Not used. */
    Tcl_Interp *interp;       /* Interpreter to send results back to */
    Tk_Window tkwin;          /* Not used. */
    char *string;       /* New legend position string */
    char *widgRec;            /* Widget record */
    int offset;               /* offset to XPoint structure */
{
    int *flags = (int *)(widgRec + offset);

    *flags &= ~BUTTON_MASK;
    if ((string[0] == 'a') && (strcmp(string, "auto") == 0)) {
      *flags |= BUTTON_AUTO;
    } else {
      int value;

      if (Tcl_GetBoolean(interp, string, &value) != TCL_OK) {
          return TCL_ERROR;
      }
      if (value) {
          *flags |= BUTTON_SHOW;
      }
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ButtonToString --
 *
 * Results:
 *    The string representation of the button boolean is returned.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static char *
ButtonToString(clientData, tkwin, widgRec, offset, freeProcPtr)
    ClientData clientData;    /* Not used. */
    Tk_Window tkwin;          /* Not used. */
    char *widgRec;            /* Widget record */
    int offset;               /* offset of flags field in record */
    Tcl_FreeProc **freeProcPtr;     /* Memory deallocation scheme to use */
{
    unsigned int flags = *(int *)(widgRec + offset);

    switch (flags & BUTTON_MASK) {
    case 0:
      return "0";
    case BUTTON_SHOW:
      return "1";
    case BUTTON_AUTO:
      return "auto";
    default:
      return "unknown button value";
    }
}

/*
 *----------------------------------------------------------------------
 *
 * ImageChangedProc
 *
 *
 * Results:
 *    None.
 *
 *----------------------------------------------------------------------
 */
/* ARGSUSED */
static void
ImageChangedProc(clientData, x, y, width, height, imageWidth, imageHeight)
    ClientData clientData;
    int x, y, width, height;  /* Not used. */
    int imageWidth, imageHeight;/* Not used. */
{
    Hierbox *hboxPtr = clientData;

    hboxPtr->flags |= (HIERBOX_LAYOUT | HIERBOX_SCROLL);
    EventuallyRedraw(hboxPtr);
}

static CachedImage
GetCachedImage(hboxPtr, interp, tkwin, name)
    Hierbox *hboxPtr;
    Tcl_Interp *interp;
    Tk_Window tkwin;
    char *name;
{
    struct CachedImageStruct *imagePtr;
    int isNew;
    Blt_HashEntry *hPtr;

    hPtr = Blt_CreateHashEntry(&(hboxPtr->imageTable), (char *)name, &isNew);
    if (isNew) {
      Tk_Image tkImage;
      int width, height;

      tkImage = Tk_GetImage(interp, tkwin, name, ImageChangedProc, hboxPtr);
      if (tkImage == NULL) {
          Blt_DeleteHashEntry(&(hboxPtr->imageTable), hPtr);
          return NULL;
      }
      Tk_SizeOfImage(tkImage, &width, &height);
      imagePtr = Blt_Malloc(sizeof(struct CachedImageStruct));
      imagePtr->tkImage = tkImage;
      imagePtr->hashPtr = hPtr;
      imagePtr->refCount = 1;
      imagePtr->width = width, imagePtr->height = height;
      Blt_SetHashValue(hPtr, imagePtr);
    } else {
      imagePtr = (struct CachedImageStruct *)Blt_GetHashValue(hPtr);
      imagePtr->refCount++;
    }
    return imagePtr;
}

static void
FreeCachedImage(hboxPtr, imagePtr)
    Hierbox *hboxPtr;
    struct CachedImageStruct *imagePtr;
{
    imagePtr->refCount--;
    if (imagePtr->refCount == 0) {
      Blt_DeleteHashEntry(&(hboxPtr->imageTable), imagePtr->hashPtr);
      Tk_FreeImage(imagePtr->tkImage);
      Blt_Free(imagePtr);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * StringToImages --
 *
 *    Convert a list of image names into Tk images.
 *
 * Results:
 *    If the string is successfully converted, TCL_OK is returned.
 *    Otherwise, TCL_ERROR is returned and an error message is left in
 *    interpreter's result field.
 *
 *----------------------------------------------------------------------
 */
static int
StringToImages(clientData, interp, tkwin, string, widgRec, offset)
    ClientData clientData;    /* Not used. */
    Tcl_Interp *interp;       /* Interpreter to send results back to */
    Tk_Window tkwin;          /* Not used. */
    char *string;       /* New legend position string */
    char *widgRec;            /* Widget record */
    int offset;               /* offset to field in structure */
{
    Hierbox *hboxPtr = *(Hierbox **)clientData;
    CachedImage **imagePtrPtr = (CachedImage **) (widgRec + offset);
    CachedImage *imageArr;
    int result;

    result = TCL_OK;
    imageArr = NULL;
    if ((string != NULL) && (*string != '\0')) {
      int nNames;
      char **nameArr;

      if (Tcl_SplitList(interp, string, &nNames, &nameArr) != TCL_OK) {
          return TCL_ERROR;
      }
      if (nNames > 0) {
          register int i;

          imageArr = Blt_Malloc(sizeof(CachedImage *) * (nNames + 1));
          assert(imageArr);
          for (i = 0; i < nNames; i++) {
            imageArr[i] = GetCachedImage(hboxPtr, interp, tkwin, nameArr[i]);
            if (imageArr[i] == NULL) {
                result = TCL_ERROR;
                break;
            }
          }
          Blt_Free(nameArr);
          imageArr[nNames] = NULL;
      }
    }
    if (*imagePtrPtr != NULL) {
      register CachedImage *imagePtr;

      for (imagePtr = *imagePtrPtr; *imagePtr != NULL; imagePtr++) {
          FreeCachedImage(hboxPtr, *imagePtr);
      }
      Blt_Free(*imagePtrPtr);
    }
    *imagePtrPtr = imageArr;
    return result;
}

/*
 *----------------------------------------------------------------------
 *
 * ImagesToString --
 *
 *    Converts the image into its string representation (its name).
 *
 * Results:
 *    The name of the image is returned.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static char *
ImagesToString(clientData, tkwin, widgRec, offset, freeProcPtr)
    ClientData clientData;    /* Not used. */
    Tk_Window tkwin;          /* Not used. */
    char *widgRec;            /* Widget record */
    int offset;               /* offset of images array in record */
    Tcl_FreeProc **freeProcPtr;     /* Memory deallocation scheme to use */
{
    CachedImage **imagePtrPtr = (CachedImage **) (widgRec + offset);
    Tcl_DString dString;
    char *result;

    Tcl_DStringInit(&dString);
    if (*imagePtrPtr != NULL) {
      register CachedImage *imagePtr;

      for (imagePtr = *imagePtrPtr; *imagePtr != NULL; imagePtr++) {
          Tcl_DStringAppendElement(&dString, 
                       Blt_NameOfImage((*imagePtr)->tkImage));
      }
    }
    result = Blt_Strdup(Tcl_DStringValue(&dString));
    Tcl_DStringFree(&dString);
    *freeProcPtr = (Tcl_FreeProc *)Blt_Free;
    return result;
}


/*
 *----------------------------------------------------------------------
 *
 * StringToScrollmode --
 *
 *    Convert the string reprsenting a scroll mode, to its numeric
 *    form.
 *
 * Results:
 *    If the string is successfully converted, TCL_OK is returned.
 *    Otherwise, TCL_ERROR is returned and an error message is left
 *    in interpreter's result field.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
StringToSeparator(clientData, interp, tkwin, string, widgRec, offset)
    ClientData clientData;    /* Not used. */
    Tcl_Interp *interp;       /* Interpreter to send results back to */
    Tk_Window tkwin;          /* Not used. */
    char *string;       /* String representing new value */
    char *widgRec;            /* Widget record */
    int offset;               /* offset in structure */
{
    char **sepPtr = (char **)(widgRec + offset);

    if ((*sepPtr != SEPARATOR_LIST) && (*sepPtr != SEPARATOR_NONE)) {
      Blt_Free(*sepPtr);
    }
    if ((string == NULL) || (*string == '\0')) {
      *sepPtr = SEPARATOR_LIST;
    } else if (strcmp(string, "none") == 0) {
      *sepPtr = SEPARATOR_NONE;
    } else {
      *sepPtr = Blt_Strdup(string);
    } 
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * SeparatorToString --
 *
 * Results:
 *    The string representation of the separator is returned.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static char *
SeparatorToString(clientData, tkwin, widgRec, offset, freeProcPtr)
    ClientData clientData;    /* Not used. */
    Tk_Window tkwin;          /* Not used. */
    char *widgRec;            /* Widget record */
    int offset;               /* offset of flags field in record */
    Tcl_FreeProc **freeProcPtr;     /* Memory deallocation scheme to use */
{
    char *separator = *(char **)(widgRec + offset);

    if (separator == SEPARATOR_NONE) {
      return "";
    } else if (separator == SEPARATOR_LIST) {
      return "list";
    } 
    return separator;
}

static int
ApplyToTree(hboxPtr, rootPtr, proc, flags)
    Hierbox *hboxPtr;
    Tree *rootPtr;
    ApplyProc *proc;
    unsigned int flags;
{
    if (flags & APPLY_BEFORE) {
      if ((*proc) (hboxPtr, rootPtr) != TCL_OK) {
          return TCL_ERROR;
      }
    }
    if (flags & APPLY_RECURSE) {
      if (!(flags & APPLY_OPEN_ONLY) ||
          (rootPtr->entryPtr->flags & ENTRY_OPEN)) {
          Blt_ChainLink *linkPtr, *nextPtr;
          Tree *treePtr;

          for (linkPtr = Blt_ChainFirstLink(rootPtr->chainPtr); 
            linkPtr != NULL;  linkPtr = nextPtr) {
            /* Get the next link in the chain before calling
             * ApplyToTree.  This is because ApplyToTree may
             * delete the node and its link.  */
            nextPtr = Blt_ChainNextLink(linkPtr);
            treePtr = Blt_ChainGetValue(linkPtr);
            if (ApplyToTree(hboxPtr, treePtr, proc, flags) != TCL_OK) {
                return TCL_ERROR;
            }
          }
      }
    }
    if ((flags & APPLY_BEFORE) == 0) {
      if ((*proc) (hboxPtr, rootPtr) != TCL_OK) {
          return TCL_ERROR;
      }
    }
    return TCL_OK;
}

static void
ConfigureButtons(hboxPtr)
    Hierbox *hboxPtr;
{
    ButtonAttributes *buttonPtr = &(hboxPtr->button);
    unsigned long gcMask;
    XGCValues gcValues;
    GC newGC;

    gcMask = GCForeground;
    gcValues.foreground = buttonPtr->fgColor->pixel;
    newGC = Tk_GetGC(hboxPtr->tkwin, gcMask, &gcValues);
    if (buttonPtr->normalGC != NULL) {
      Tk_FreeGC(hboxPtr->display, buttonPtr->normalGC);
    }
    buttonPtr->normalGC = newGC;

    gcMask = (GCForeground | GCLineWidth);
    gcValues.foreground = hboxPtr->lineColor->pixel;
    gcValues.line_width = hboxPtr->lineWidth;
    newGC = Tk_GetGC(hboxPtr->tkwin, gcMask, &gcValues);
    if (buttonPtr->lineGC != NULL) {
      Tk_FreeGC(hboxPtr->display, buttonPtr->lineGC);
    }
    buttonPtr->lineGC = newGC;

    gcMask = GCForeground;
    gcValues.foreground = buttonPtr->activeFgColor->pixel;
    newGC = Tk_GetGC(hboxPtr->tkwin, gcMask, &gcValues);
    if (buttonPtr->activeGC != NULL) {
      Tk_FreeGC(hboxPtr->display, buttonPtr->activeGC);
    }
    buttonPtr->activeGC = newGC;

    buttonPtr->width = buttonPtr->height = ODD(buttonPtr->reqSize);
    if (buttonPtr->images != NULL) {
      register int i;

      for (i = 0; i < 2; i++) {
          if (buttonPtr->images[i] == NULL) {
            break;
          }
          if (buttonPtr->width < ImageWidth(buttonPtr->images[i])) {
            buttonPtr->width = ImageWidth(buttonPtr->images[i]);
          }
          if (buttonPtr->height < ImageHeight(buttonPtr->images[i])) {
            buttonPtr->height = ImageHeight(buttonPtr->images[i]);
          }
      }
    }
    buttonPtr->width += 2 * buttonPtr->borderWidth;
    buttonPtr->height += 2 * buttonPtr->borderWidth;
}

static void
DestroyEntry(entryPtr)
    Entry *entryPtr;
{
    Hierbox *hboxPtr = entryPtr->hboxPtr;
    register CachedImage *imagePtr;

    Tk_FreeOptions(entryConfigSpecs, (char *)entryPtr, hboxPtr->display, 0);
    if (entryPtr->labelGC != NULL) {
      Tk_FreeGC(hboxPtr->display, entryPtr->labelGC);
    }
    if (entryPtr->dataGC != NULL) {
      Tk_FreeGC(hboxPtr->display, entryPtr->dataGC);
    }
    if (entryPtr->hashPtr != NULL) {
      Blt_DeleteHashEntry(&(hboxPtr->nodeTable), entryPtr->hashPtr);
    }
    if (entryPtr->dataShadow.color != NULL) {
      Tk_FreeColor(entryPtr->dataShadow.color);
    }
    if (entryPtr->labelShadow.color != NULL) {
      Tk_FreeColor(entryPtr->labelShadow.color);
    }
    if (entryPtr->iconGC != NULL) {
      Tk_FreeGC(hboxPtr->display, entryPtr->iconGC);
    }
    if (entryPtr->openCmd != NULL) {
      Blt_FreeUid(entryPtr->openCmd);
    }
    if (entryPtr->closeCmd != NULL) {
      Blt_FreeUid(entryPtr->closeCmd);
    }
    if (entryPtr->dataUid != NULL) {
      Blt_FreeUid(entryPtr->dataUid);
    }
    if (entryPtr->dataText != NULL) {
      Blt_FreeUid(entryPtr->dataText);
    }
    if (entryPtr->tags != NULL) {
      Blt_FreeUid(entryPtr->tags);
    }
    if (entryPtr->icons != NULL) {
      for (imagePtr = entryPtr->icons; *imagePtr != NULL; imagePtr++) {
          FreeCachedImage(hboxPtr, *imagePtr);
      }
      Blt_Free(entryPtr->icons);
    }
    if (entryPtr->activeIcons != NULL) {
      for (imagePtr = entryPtr->activeIcons; *imagePtr != NULL; imagePtr++) {
          FreeCachedImage(hboxPtr, *imagePtr);
      }
      Blt_Free(entryPtr->activeIcons);
    }
    if (entryPtr->images != NULL) {
      for (imagePtr = entryPtr->images; *imagePtr != NULL; imagePtr++) {
          FreeCachedImage(hboxPtr, *imagePtr);
      }
      Blt_Free(entryPtr->images);
    }
    Blt_Free(entryPtr);
}

/*ARGSUSED*/
static Tree *
LastNode(treePtr, mask)
    register Tree *treePtr;
    unsigned int mask;
{
    Blt_ChainLink *linkPtr;

    if (treePtr->parentPtr == NULL) {
      return NULL;            /* The root is the first node. */
    }
    linkPtr = Blt_ChainPrevLink(treePtr->linkPtr);
    if (linkPtr == NULL) {
      /* There are no siblings previous to this one, so pick the parent. */
      return treePtr->parentPtr;
    }
    /*
     * Traverse down the right-most thread, in order to select the
     * next entry.  Stop if we find a "closed" entry or reach a leaf.
     */
    treePtr = Blt_ChainGetValue(linkPtr);
    while ((treePtr->entryPtr->flags & mask) == mask) {
      linkPtr = Blt_ChainLastLink(treePtr->chainPtr);
      if (linkPtr == NULL) {
          break;        /* Found a leaf. */
      }
      treePtr = Blt_ChainGetValue(linkPtr);
    }
    return treePtr;
}


static Tree *
NextNode(treePtr, mask)
    Tree *treePtr;
    unsigned int mask;
{
    Blt_ChainLink *linkPtr;

    if ((treePtr->entryPtr->flags & mask) == mask) {
      /* Pick the first sub-node. */
      linkPtr = Blt_ChainFirstLink(treePtr->chainPtr);
      if (linkPtr != NULL) {
          return Blt_ChainGetValue(linkPtr);
      }
    }
    /*
     * Back up until we can find a level where we can pick a "next" entry.
     * For the last entry we'll thread our way back to the root.
     */
    while (treePtr->parentPtr != NULL) {
      linkPtr = Blt_ChainNextLink(treePtr->linkPtr);
      if (linkPtr != NULL) {
          return Blt_ChainGetValue(linkPtr);
      }
      treePtr = treePtr->parentPtr;
    }
    return NULL;        /* At root, no next node. */
}

/*ARGSUSED*/
static Tree *
EndNode(treePtr, mask)
    Tree *treePtr;
    unsigned int mask;
{
    Blt_ChainLink *linkPtr;

    linkPtr = Blt_ChainLastLink(treePtr->chainPtr);
    while (linkPtr != NULL) {
      treePtr = Blt_ChainGetValue(linkPtr);
      if ((treePtr->entryPtr->flags & mask) != mask) {
          break;
      }
      linkPtr = Blt_ChainLastLink(treePtr->chainPtr);
    }
    return treePtr;
}

static void
ExposeAncestors(treePtr)
    register Tree *treePtr;
{
    treePtr = treePtr->parentPtr;
    while (treePtr != NULL) {
      treePtr->entryPtr->flags |= (ENTRY_OPEN | ENTRY_MAPPED);
      treePtr = treePtr->parentPtr;
    }
}

static int
IsBefore(t1Ptr, t2Ptr)
    register Tree *t1Ptr, *t2Ptr;
{
    int depth;
    register int i;
    Blt_ChainLink *linkPtr;
    Tree *treePtr;

    depth = MIN(t1Ptr->level, t2Ptr->level);

    if (depth == 0) {         /* One of the nodes is root. */
      if (t1Ptr->parentPtr == NULL) {
          return 1;
      }
      return 0;
    }
    /*
     * Traverse back from the deepest node, until the both nodes are at the
     * same depth.  Check if the ancestor node found is the other node.
     */
    for (i = t1Ptr->level; i > depth; i--) {
      t1Ptr = t1Ptr->parentPtr;
    }
    if (t1Ptr == t2Ptr) {
      return 0;
    }
    for (i = t2Ptr->level; i > depth; i--) {
      t2Ptr = t2Ptr->parentPtr;
    }
    if (t2Ptr == t1Ptr) {
      return 1;
    }
    /*
     * First find the mutual ancestor of both nodes.  Look at each
     * preceding ancestor level-by-level for both nodes.  Eventually
     * we'll find a node that's the parent of both ancestors.  Then
     * find the first ancestor in the parent's list of subnodes.
     */
    for (i = depth; i > 0; i--) {
      if (t1Ptr->parentPtr == t2Ptr->parentPtr) {
          break;
      }
      t1Ptr = t1Ptr->parentPtr;
      t2Ptr = t2Ptr->parentPtr;
    }
    for (linkPtr = Blt_ChainFirstLink(t1Ptr->parentPtr->chainPtr); linkPtr != NULL;
      linkPtr = Blt_ChainNextLink(linkPtr)) {
      treePtr = Blt_ChainGetValue(linkPtr);
      if (treePtr == t1Ptr) {
          return 1;
      } else if (treePtr == t2Ptr) {
          return 0;
      }
    }
    assert(linkPtr != NULL);
    return 0;
}

static int
IsAncestor(rootPtr, treePtr)
    Tree *rootPtr, *treePtr;
{
    if (treePtr != NULL) {
      treePtr = treePtr->parentPtr;
      while (treePtr != NULL) {
          if (treePtr == rootPtr) {
            return 1;
          }
          treePtr = treePtr->parentPtr;
      }
    }
    return 0;
}

static int
IsHidden(treePtr)
    register Tree *treePtr;
{
    if (treePtr != NULL) {
      unsigned int mask;

      if (!(treePtr->entryPtr->flags & ENTRY_MAPPED)) {
          return TRUE;
      }
      treePtr = treePtr->parentPtr;
      mask = (ENTRY_OPEN | ENTRY_MAPPED);
      while (treePtr != NULL) {
          if ((treePtr->entryPtr->flags & mask) != mask) {
            return TRUE;
          }
          treePtr = treePtr->parentPtr;
      }
    }
    return FALSE;
}

static void
SelectEntry(hboxPtr, treePtr)
    Hierbox *hboxPtr;
    Tree *treePtr;
{
    int isNew;
    Blt_HashEntry *hPtr;

    hPtr = Blt_CreateHashEntry(&(hboxPtr->selectTable), (char *)treePtr, 
       &isNew);
    if (isNew) {
      Blt_ChainLink *linkPtr;

      linkPtr = Blt_ChainAppend(&(hboxPtr->selectChain), treePtr);
      Blt_SetHashValue(hPtr, linkPtr);
    }
}

static void
DeselectEntry(hboxPtr, treePtr)
    Hierbox *hboxPtr;
    Tree *treePtr;
{
    Blt_HashEntry *hPtr;

    hPtr = Blt_FindHashEntry(&(hboxPtr->selectTable), (char *)treePtr);
    if (hPtr != NULL) {
      Blt_ChainLink *linkPtr;

      linkPtr = (Blt_ChainLink *)Blt_GetHashValue(hPtr);
      Blt_ChainDeleteLink(&(hboxPtr->selectChain), linkPtr);
      Blt_DeleteHashEntry(&(hboxPtr->selectTable), hPtr);
    }
}

static void
ClearSelection(hboxPtr)
    Hierbox *hboxPtr;
{
    Blt_DeleteHashTable(&(hboxPtr->selectTable));
    Blt_InitHashTable(&(hboxPtr->selectTable), BLT_ONE_WORD_KEYS);
    Blt_ChainReset(&(hboxPtr->selectChain));
    EventuallyRedraw(hboxPtr);
    if (hboxPtr->selectCmd != NULL) {
      EventuallyInvokeSelectCmd(hboxPtr);
    }
}

static void
PruneSelection(hboxPtr, rootPtr)
    Hierbox *hboxPtr;
    Tree *rootPtr;
{
    Blt_ChainLink *linkPtr, *nextPtr;
    Tree *treePtr;
    int selectionChanged;

    selectionChanged = FALSE;
    for (linkPtr = Blt_ChainFirstLink(&(hboxPtr->selectChain)); 
       linkPtr != NULL; linkPtr = nextPtr) {
      nextPtr = Blt_ChainNextLink(linkPtr);
      treePtr = Blt_ChainGetValue(linkPtr);
      if (IsAncestor(rootPtr, treePtr)) {
          DeselectEntry(hboxPtr, treePtr);
          selectionChanged = TRUE;
      }
    }
    if (selectionChanged) {
      EventuallyRedraw(hboxPtr);
      if (hboxPtr->selectCmd != NULL) {
          EventuallyInvokeSelectCmd(hboxPtr);
      }
    }
}

static int
IsSelected(hboxPtr, treePtr)
    Hierbox *hboxPtr;
    Tree *treePtr;
{
    Blt_HashEntry *hPtr;

    hPtr = Blt_FindHashEntry(&(hboxPtr->selectTable), (char *)treePtr);
    return (hPtr != NULL);
}


static void
GetFullPath(treePtr, separator, resultPtr)
    Tree *treePtr;
    char *separator;
    Tcl_DString *resultPtr;
{
    char **nameArr;           /* Used the stack the component names. */
    register int i;
    int level;

    level = treePtr->level;
    nameArr = Blt_Malloc((level + 1) * sizeof(char *));
    assert(nameArr);
    for (i = level; i >= 0; i--) {
      /* Save the name of each ancestor in the name array. */
      nameArr[i] = treePtr->nameId;
      treePtr = treePtr->parentPtr;
    }
    Tcl_DStringInit(resultPtr);
    if ((separator == SEPARATOR_LIST) || (separator == SEPARATOR_NONE)) {
      for (i = 0; i <= level; i++) {
          Tcl_DStringAppendElement(resultPtr, nameArr[i]);
      }
    } else {
      Tcl_DStringAppend(resultPtr, nameArr[0], -1);
      if (strcmp(nameArr[0], separator) != 0) {
          Tcl_DStringAppend(resultPtr, separator, -1);
      }
      if (level > 0) {
          for (i = 1; i < level; i++) {
            Tcl_DStringAppend(resultPtr, nameArr[i], -1);
            Tcl_DStringAppend(resultPtr, separator, -1);
          }
          Tcl_DStringAppend(resultPtr, nameArr[i], -1);
      }
    }
    Blt_Free(nameArr);
}

static void
InsertNode(parentPtr, position, nodePtr)
    Tree *parentPtr;          /* Parent node where the new node will
                         * be added. */
    int position;       /* Position in the parent of the new node. */
    Tree *nodePtr;            /* The node to be inserted. */
{
    Blt_ChainLink *linkPtr;

    /*
     * Create lists to contain subnodes as needed.  We don't want to
     * unnecessarily allocate storage for leaves (of which there may
     * be many).
     */
    if (parentPtr->chainPtr == NULL) {
      parentPtr->chainPtr = Blt_ChainCreate();
    }
    linkPtr = Blt_ChainNewLink();
    if (position == APPEND) {
      Blt_ChainAppendLink(parentPtr->chainPtr, linkPtr);
    } else {
      Blt_ChainLink *beforePtr;

      beforePtr = Blt_ChainGetNthLink(parentPtr->chainPtr, position);
      Blt_ChainLinkBefore(parentPtr->chainPtr, linkPtr, beforePtr);
    }
    nodePtr->level = parentPtr->level + 1;
    nodePtr->parentPtr = parentPtr;
    nodePtr->linkPtr = linkPtr;
    Blt_ChainSetValue(linkPtr, nodePtr);
}

static void
DestroyNode(data)
    DestroyData data;
{
    Tree *treePtr = (Tree *)data;

    if (treePtr->nameId != NULL) {
      Blt_FreeUid(treePtr->nameId);
    }
    if (treePtr->chainPtr != NULL) {
      Blt_ChainDestroy(treePtr->chainPtr);
    }
    if (treePtr->entryPtr != NULL) {
      DestroyEntry(treePtr->entryPtr);
    }
    treePtr->entryPtr = NULL;
    Blt_Free(treePtr);
}

/*
 *----------------------------------------------------------------------
 *
 * CreateNode --
 *
 *    Creates and inserts a new node into the given tree at the
 *    specified position.
 *
 * Results:
 *    Returns a pointer to the newly created node.  If an error
 *    occurred, such as the entry could not be configured, NULL
 *    is returned.
 *
 *----------------------------------------------------------------------
 */
static Tree *
CreateNode(hboxPtr, parentPtr, position, name)
    Hierbox *hboxPtr;         /* Hierarchy widget record */
    Tree *parentPtr;          /* Pointer to parent node. */
    int position;       /* Position in node list to insert node. */
    char *name;               /* Name identifier for the new node. */
{
    Entry *entryPtr;
    Tree *treePtr;
    Blt_HashEntry *hPtr;
    int isNew;
    int serial;

    /* Create the entry structure */
    entryPtr = Blt_Calloc(1, sizeof(Entry));
    assert(entryPtr);
    entryPtr->flags |= (BUTTON_AUTO | ENTRY_MAPPED);
    entryPtr->hboxPtr = hboxPtr;

    if (name == NULL) {
      name = "";
    }
    entryPtr->labelText = Blt_Strdup(name);

    if (ConfigureEntry(hboxPtr, entryPtr, 0, (char **)NULL, 0) != TCL_OK) {
      DestroyEntry(entryPtr);
      return NULL;
    }
    /* Create the container structure too */
    treePtr = Blt_Calloc(1, sizeof(Tree));
    assert(treePtr);
    treePtr->nameId = Blt_GetUid(name);
    treePtr->entryPtr = entryPtr;

    /* Generate a unique node serial number. */
    do {
      serial = hboxPtr->nextSerial++;
      hPtr = Blt_CreateHashEntry(&(hboxPtr->nodeTable), (char *)serial,
          &isNew);
    } while (!isNew);
    Blt_SetHashValue(hPtr, treePtr);
    entryPtr->hashPtr = hPtr;

    if (parentPtr != NULL) {
      InsertNode(parentPtr, position, treePtr);
    }
    return treePtr;
}

/*
 *----------------------------------------------------------------------
 *
 * FindComponent --
 *
 *    Searches for the given child node.  The node is designated
 *    by its node identifier string.
 *
 * Results:
 *    If found, returns a node pointer. Otherwise NULL.
 *
 *----------------------------------------------------------------------
 */
static Tree *
FindComponent(parentPtr, name)
    Tree *parentPtr;
    char *name;
{
    Blt_Uid nameId;

    nameId = Blt_FindUid(name);
    if (nameId != NULL) {
      register Tree *treePtr;
      Blt_ChainLink *linkPtr;

      /* The component identifier must already exist if the node exists. */
      for (linkPtr = Blt_ChainFirstLink(parentPtr->chainPtr); linkPtr != NULL;
          linkPtr = Blt_ChainNextLink(linkPtr)) {
          treePtr = Blt_ChainGetValue(linkPtr);
          if (nameId == treePtr->nameId) {
            return treePtr;
          }
      }
    }
    return NULL;
}

/*
 *----------------------------------------------------------------------
 *
 * 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 == separator[0]) && (strncmp(path, separator, length) == 0)) {
      path += length;
    }
    return path;
}

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

    skipLen = strlen(hboxPtr->separator);
    path = SkipSeparators(path, hboxPtr->separator, skipLen);
    pathLen = strlen(path);

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

    sep = strstr(p, hboxPtr->separator);
    level = 0;
    while ((*p != '\0') && (sep != NULL)) {
      *sep = '\0';
      components[level++] = p;
      p = SkipSeparators(sep + skipLen, hboxPtr->separator, skipLen);
      sep = strstr(p, hboxPtr->separator);
    }
    if (*p != '\0') {
      components[level++] = p;
    }
    components[level] = NULL;
    *levelPtr = level;
    *compPtrPtr = components;
    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 Tree *
FindPath(hboxPtr, rootPtr, path)
    Hierbox *hboxPtr;
    Tree *rootPtr;
    char *path;
{
    register char *p;
    int skip;
    char *sep;
    Tree *treePtr;
    char save;

    /* Trim off characters that we don't want */
    if (hboxPtr->trimLeft != NULL) {
      register char *s;

      /* Trim off leading character string if one exists. */
      for (p = path, s = hboxPtr->trimLeft; *s != '\0'; s++, p++) {
          if (*p != *s) {
            break;
          }
      }
      if (*s == '\0') {
          path = p;
      }
    }
    if (*path == '\0') {
      return rootPtr;
    }
    if (hboxPtr->separator == SEPARATOR_NONE) { 
      return FindComponent(rootPtr, path);
    } 
    if (hboxPtr->separator == SEPARATOR_LIST) {
      char **nameArr;
      int nComp;
      register int i;

      /*
       * No separator, so this must be a Tcl list of components.
       */
      if (Tcl_SplitList(hboxPtr->interp, path, &nComp, &nameArr)
          != TCL_OK) {
          return NULL;
      }
      for (i = 0; i < nComp; i++) {
          treePtr = FindComponent(rootPtr, nameArr[i]);
          if (treePtr == NULL) {
            Blt_Free(nameArr);
            return NULL;
          }
          rootPtr = treePtr;
      }
      Blt_Free(nameArr);
      return rootPtr;
    }
    skip = strlen(hboxPtr->separator);
    path = SkipSeparators(path, hboxPtr->separator, skip);
    sep = strstr(path, hboxPtr->separator);
    p = path;
    treePtr = rootPtr;
    while ((*p != '\0') && (sep != NULL)) {
      save = *sep, *sep = '\0';
      treePtr = FindComponent(treePtr, p);
      *sep = save;
      if (treePtr == NULL) {
          return NULL;  /* Bad component name. */
      }
      p = SkipSeparators(sep + skip, hboxPtr->separator, skip);
      sep = strstr(p, hboxPtr->separator);
    }
    if (*p != '\0') {
      treePtr = FindComponent(treePtr, p);
      if (treePtr == NULL) {
          return NULL;
      }
    }
    return treePtr;
}

/*
 *----------------------------------------------------------------------
 *
 * NearestNode --
 *
 *    Finds the entry closest to the given screen X-Y coordinates
 *    in the viewport.
 *
 * Results:
 *    Returns the pointer to the closest node.  If no node is
 *    visible (nodes may be hidden), NULL is returned.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static Tree *
NearestNode(hboxPtr, x, y, selectOne)
    Hierbox *hboxPtr;
    int x, y;
    int selectOne;
{
    register Tree *lastPtr, **treePtrPtr;
    register Entry *entryPtr;

    /*
     * We implicitly can pick only visible entries.  So make sure that
     * the tree exists.
     */
    if (hboxPtr->nVisible == 0) {
      return NULL;
    }
    /*
     * Since the entry positions were previously computed in world
     * coordinates, convert Y-coordinate from screen to world
     * coordinates too.
     */
    y = WORLDY(hboxPtr, y);
    treePtrPtr = hboxPtr->visibleArr;
    lastPtr = *treePtrPtr;
    for ( /* empty */ ; *treePtrPtr != NULL; treePtrPtr++) {
      entryPtr = (*treePtrPtr)->entryPtr;
      /*
       * If the start of the next entry starts beyond the point,
       * use the last entry.
       */
      if (entryPtr->worldY > y) {
          return (selectOne) ? lastPtr : NULL;
      }
      if (y < (entryPtr->worldY + entryPtr->height)) {
          return *treePtrPtr; /* Found it. */
      }
      lastPtr = *treePtrPtr;
    }
    return (selectOne) ? lastPtr : NULL;
}

static Tree *
GetNodeByIndex(hboxPtr, string)
    Hierbox *hboxPtr;
    char *string;
{
    if (isdigit(UCHAR(string[0]))) {
      int serial;

      if (Tcl_GetInt(NULL, string, &serial) == TCL_OK) {
          Blt_HashEntry *hPtr;

          hPtr = Blt_FindHashEntry(&(hboxPtr->nodeTable), (char *)serial);
          if (hPtr != NULL) {
            return (Tree *) Blt_GetHashValue(hPtr);
          }
      }
    }
    return NULL;
}

/*
 *----------------------------------------------------------------------
 *
 * NodeToString --
 *
 *    Converts a node pointer in its string representation.  The
 *    string is the node's identifier number.
 *
 * Results:
 *    The string representation of the node is returned.  Note that
 *    the string is stored statically, so that callers must save the
 *    string before the next call to this routine overwrites the
 *    static array again.
 *
 *----------------------------------------------------------------------
 */
static char *
NodeToString(hboxPtr, nodePtr)
    Hierbox *hboxPtr;
    Tree *nodePtr;
{
    static char string[200];
    int serial;

    /* Node table keys are integers.  Convert them to strings. */
    serial = (int)Blt_GetHashKey(&(hboxPtr->nodeTable),
      nodePtr->entryPtr->hashPtr);
    sprintf(string, "%d", serial);

    return string;
}

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

static int
GetNode(hboxPtr, string, treePtrPtr)
    Hierbox *hboxPtr;
    char *string;
    Tree **treePtrPtr;
{
    Tree *nodePtr;
    char c;
    Tree *fromPtr;

    c = string[0];
    fromPtr = *treePtrPtr;
    nodePtr = NULL;
    if (isdigit(UCHAR(string[0]))) {
      nodePtr = GetNodeByIndex(hboxPtr, string);
    } else if ((c == 'e') && (strcmp(string, "end") == 0)) {
      nodePtr = EndNode(hboxPtr->rootPtr, ENTRY_OPEN | ENTRY_MAPPED);
    } else if ((c == 'a') && (strcmp(string, "anchor") == 0)) {
      nodePtr = hboxPtr->selAnchorPtr;
    } else if ((c == 'f') && (strcmp(string, "focus") == 0)) {
      nodePtr = hboxPtr->focusPtr;
    } else if ((c == 'r') && (strcmp(string, "root") == 0)) {
      nodePtr = hboxPtr->rootPtr;
    } else if ((c == 'p') && (strcmp(string, "parent") == 0)) {
      nodePtr = fromPtr;
      if (nodePtr->parentPtr != NULL) {
          nodePtr = nodePtr->parentPtr;
      }
    } else if ((c == 'c') && (strcmp(string, "current") == 0)) {
      /* Can't trust picked item, if entries have been added or deleted. */
      if (!(hboxPtr->flags & HIERBOX_DIRTY)) {
          nodePtr = (Tree *) Blt_GetCurrentItem(hboxPtr->bindTable);
          if (nodePtr == NULL) {
            nodePtr = (Tree *)Blt_GetCurrentItem(hboxPtr->buttonBindTable);
          }
      }
    } else if ((c == 'u') && (strcmp(string, "up") == 0)) {
      nodePtr = LastNode(fromPtr, ENTRY_OPEN | ENTRY_MAPPED);
      if (nodePtr == NULL) {
          nodePtr = fromPtr;
      }
      if ((nodePtr == hboxPtr->rootPtr) && (hboxPtr->hideRoot)) {
          nodePtr = NextNode(hboxPtr->rootPtr, ENTRY_OPEN | ENTRY_MAPPED);
      }
    } else if ((c == 'd') && (strcmp(string, "down") == 0)) {
      nodePtr = NextNode(fromPtr, ENTRY_OPEN | ENTRY_MAPPED);
      if (nodePtr == NULL) {
          nodePtr = fromPtr;
      }
      if ((nodePtr == hboxPtr->rootPtr) && (hboxPtr->hideRoot)) {
          nodePtr = NextNode(hboxPtr->rootPtr, ENTRY_OPEN | ENTRY_MAPPED);
      }
    } else if (((c == 'l') && (strcmp(string, "last") == 0)) ||
      ((c == 'p') && (strcmp(string, "prev") == 0))) {
      nodePtr = LastNode(fromPtr, ENTRY_OPEN | ENTRY_MAPPED);
      if (nodePtr == NULL) {
          nodePtr = EndNode(hboxPtr->rootPtr, ENTRY_OPEN | ENTRY_MAPPED);
      }
      if ((nodePtr == hboxPtr->rootPtr) && (hboxPtr->hideRoot)) {
          nodePtr = NextNode(hboxPtr->rootPtr, ENTRY_OPEN | ENTRY_MAPPED);
      }
    } else if ((c == 'n') && (strcmp(string, "next") == 0)) {
      nodePtr = NextNode(fromPtr, ENTRY_OPEN | ENTRY_MAPPED);
      if (nodePtr == NULL) {
          if (hboxPtr->hideRoot) {
            nodePtr = NextNode(hboxPtr->rootPtr, ENTRY_OPEN | ENTRY_MAPPED);
          } else {
            nodePtr = hboxPtr->rootPtr;
          }
      }
    } else if ((c == 'n') && (strcmp(string, "nextsibling") == 0)) {
      if (fromPtr->linkPtr != NULL) {
          Blt_ChainLink *linkPtr;

          linkPtr = Blt_ChainNextLink(fromPtr->linkPtr);
          if (linkPtr != NULL) {
            nodePtr = Blt_ChainGetValue(linkPtr);
          }
      }
    } else if ((c == 'p') && (strcmp(string, "prevsibling") == 0)) {
      if (fromPtr->linkPtr != NULL) {
          Blt_ChainLink *linkPtr;

          linkPtr = Blt_ChainPrevLink(fromPtr->linkPtr);
          if (linkPtr != NULL) {
            nodePtr = Blt_ChainGetValue(linkPtr);
          }
      }
    } else if ((c == 'v') && (strcmp(string, "view.top") == 0)) {
      if (hboxPtr->nVisible > 0) {
          nodePtr = hboxPtr->visibleArr[0];
      }
    } else if ((c == 'v') && (strcmp(string, "view.bottom") == 0)) {
      if (hboxPtr->nVisible > 0) {
          nodePtr = hboxPtr->visibleArr[hboxPtr->nVisible - 1];
      }
    } else if (c == '@') {
      int x, y;

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

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

static int
StringToNode(hboxPtr, string, treePtrPtr)
    Hierbox *hboxPtr;
    char *string;
    Tree **treePtrPtr;
{
    *treePtrPtr = hboxPtr->focusPtr;
    if (GetNode(hboxPtr, string, treePtrPtr) != TCL_OK) {
      return TCL_ERROR;
    }
    if (*treePtrPtr == NULL) {
      Tcl_ResetResult(hboxPtr->interp);
      Tcl_AppendResult(hboxPtr->interp, "can't find node entry \"", string,
          "\" in \"", Tk_PathName(hboxPtr->tkwin), "\"", (char *)NULL);
      return TCL_ERROR;
    }
    return TCL_OK;
}

/*
 * Preprocess the command string for percent substitution.
 */
static void
PercentSubst(hboxPtr, treePtr, command, resultPtr)
    Hierbox *hboxPtr;
    Tree *treePtr;
    char *command;
    Tcl_DString *resultPtr;
{
    Tcl_DString dString;
    register char *last, *p;

    /*
     * Get the full path name of the node, in case we need to
     * substitute for it.
     */
    GetFullPath(treePtr, hboxPtr->separator, &dString);
    Tcl_DStringInit(resultPtr);
    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(hboxPtr->tkwin);
            break;
          case 'P':           /* Full pathname */
            string = Tcl_DStringValue(&dString);
            break;
          case 'p':           /* Name of the node */
            string = treePtr->nameId;
            break;
          case 'n':           /* Node identifier */
            string = NodeToString(hboxPtr, treePtr);
            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);
}

/*
 *----------------------------------------------------------------------
 *
 * CloseNode --
 *
 *    Closes the node.  If a close Tcl command is specified (see the
 *    "-close" option), it's invoked after the node is closed.
 *
 * Results:
 *    If an error occurred when executing the Tcl proc, TCL_ERROR is
 *    returned.  Otherwise TCL_OK is returned.
 *
 *----------------------------------------------------------------------
 */
static int
CloseNode(hboxPtr, treePtr)
    Hierbox *hboxPtr;
    Tree *treePtr;
{
    Entry *entryPtr = treePtr->entryPtr;
    char *command;
    int result;

    /*   
     * The tricky part here is that the "close" command can delete 
     * this node.  So we'll preserve it until after we mark it closed.
     */
    Tcl_Preserve(treePtr);

    /*
     * Invoke the entry's "close" command, if there is one. Otherwise
     * try the hierbox's global "close" command.
     */
    command = (entryPtr->closeCmd != NULL) ? entryPtr->closeCmd :
      hboxPtr->closeCmd;
    result = TCL_OK;
    if ((entryPtr->flags & ENTRY_OPEN) && (command != NULL)) {
      Tcl_DString cmdString;

      PercentSubst(hboxPtr, treePtr, command, &cmdString);
      result = Tcl_GlobalEval(hboxPtr->interp, Tcl_DStringValue(&cmdString));
      Tcl_DStringFree(&cmdString);
    }
    /*
     * Mark the entry closed, only after the Tcl proc callback.
     */
    entryPtr->flags &= ~ENTRY_OPEN;

    Tcl_Release(treePtr);
    return result;
}

/*
 *----------------------------------------------------------------------
 *
 * OpenNode --
 *
 *    Opens the node.  If an open Tcl command is specified (see the
 *    "-open" option), it's invoked after the node is opened.
 *
 * Results:
 *    If an error occurred when executing the Tcl proc, TCL_ERROR is
 *    returned.  Otherwise TCL_OK is returned.
 *
 *----------------------------------------------------------------------
 */
static int
OpenNode(hboxPtr, treePtr)
    Hierbox *hboxPtr;
    Tree *treePtr;
{
    Entry *entryPtr = treePtr->entryPtr;
    char *string;
    int result;

    /*   
     * The tricky part here is that the "open" command can delete 
     * this node.  So we'll preserve it until after we mark it closed.
     */
    Tcl_Preserve(treePtr);

    /*
     * Invoke the entry's "open" command, if there is one. Otherwise
     * try the hierbox's global "open" command.
     */
    result = TCL_OK;
    string = (entryPtr->openCmd != NULL) ? entryPtr->openCmd : hboxPtr->openCmd;
    if (!(entryPtr->flags & ENTRY_OPEN) && (string != NULL)) {
      Tcl_DString dString;

      PercentSubst(hboxPtr, treePtr, string, &dString);
      result = Tcl_GlobalEval(hboxPtr->interp, Tcl_DStringValue(&dString));
      Tcl_DStringFree(&dString);
    }
    /* 
     * Mark the entry open, only after the Tcl proc callback has run.
     */
    entryPtr->flags |= ENTRY_OPEN;

    Tcl_Release(treePtr);
    return result;
}

/*
 *----------------------------------------------------------------------
 *
 * SelectNode --
 *
 *    Sets the selection flag for a node.  The selection flag is
 *    set/cleared/toggled based upon the flag set in the hierarchy
 *    widget.
 *
 * Results:
 *    Always returns TCL_OK.
 *
 *----------------------------------------------------------------------
 */
static int
SelectNode(hboxPtr, treePtr)
    Hierbox *hboxPtr;
    Tree *treePtr;
{
    switch (hboxPtr->flags & SELECTION_MASK) {
    case SELECTION_CLEAR:
      DeselectEntry(hboxPtr, treePtr);
      break;

    case SELECTION_SET:
      SelectEntry(hboxPtr, treePtr);
      break;

    case SELECTION_TOGGLE:
      if (IsSelected(hboxPtr, treePtr)) {
          DeselectEntry(hboxPtr, treePtr);
      } else {
          SelectEntry(hboxPtr, treePtr);
      }
      break;
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * 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(hboxPtr, fromPtr, toPtr)
    Hierbox *hboxPtr;
    Tree *fromPtr, *toPtr;
{
    register Tree *treePtr;
    IterProc *proc;

    /* From the range determine the direction to select entries. */
    proc = (IsBefore(toPtr, fromPtr)) ? LastNode : NextNode;
    for (treePtr = fromPtr; treePtr != NULL;
       treePtr = (*proc)(treePtr, ENTRY_OPEN | ENTRY_MAPPED)) {
      SelectNode(hboxPtr, treePtr);
      if (treePtr == toPtr) {
          break;
      }
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * IsSelectedNode --
 *
 *    Adds the name of the node the interpreter result if it is
 *    currently selected.
 *
 * Results:
 *    Always returns TCL_OK.
 *
 *----------------------------------------------------------------------
 */
static int
IsSelectedNode(hboxPtr, treePtr)
    Hierbox *hboxPtr;
    Tree *treePtr;
{
    if (IsSelected(hboxPtr, treePtr)) {
      Tcl_AppendElement(hboxPtr->interp, NodeToString(hboxPtr, treePtr));
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * GetSelectedLabels --
 *
 *    This routine is invoked when the selection is exported.  Each
 *    selected entry is appended line-by-line to a dynamic string
 *    passed via the clientData field of the hierarchy widget.
 *
 * Results:
 *    Always returns TCL_OK.
 *
 *----------------------------------------------------------------------
 */
static int
GetSelectedLabels(hboxPtr, treePtr)
    Hierbox *hboxPtr;
    Tree *treePtr;
{
    if (IsSelected(hboxPtr, treePtr)) {
      Tcl_DString *resultPtr = (Tcl_DString *) hboxPtr->clientData;
      Entry *entryPtr = treePtr->entryPtr;

      Tcl_DStringAppend(resultPtr, entryPtr->labelText, -1);
      Tcl_DStringAppend(resultPtr, "\n", -1);
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * SizeOfNode --
 *
 *    Returns the number of children at the given node. The sum
 *    is passed via the clientData field of the hierarchy widget.
 *
 * Results:
 *    Always TCL_OK.
 *
 *----------------------------------------------------------------------
 */
static int
SizeOfNode(hboxPtr, treePtr)
    Hierbox *hboxPtr;
    Tree *treePtr;
{
    int *sumPtr = (int *)&(hboxPtr->clientData);

    *sumPtr += Blt_ChainGetLength(treePtr->chainPtr);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * CompareNodesByName --
 *
 *    Comparison routine (used by qsort) to sort a chain of subnodes.
 *    A simple string comparison is performed on each node name.
 *
 * Results:
 *    1 is the first is greater, -1 is the second is greater, 0
 *    if equal.
 *
 *----------------------------------------------------------------------
 */
static int
CompareNodesByName(link1PtrPtr, link2PtrPtr)
    Blt_ChainLink **link1PtrPtr, **link2PtrPtr;
{
    Tree *t1Ptr, *t2Ptr;

    t1Ptr = Blt_ChainGetValue(*link1PtrPtr);
    t2Ptr = Blt_ChainGetValue(*link2PtrPtr);
    return strcmp(t1Ptr->nameId, t2Ptr->nameId);
}

/*
 *----------------------------------------------------------------------
 *
 * CompareNodesByTclCmd --
 *
 *    Comparison routine (used by qsort) to sort a list of subnodes.
 *    A specified Tcl proc is invoked to compare the nodes.
 *
 * Results:
 *    1 is the first is greater, -1 is the second is greater, 0
 *    if equal.
 *
 *----------------------------------------------------------------------
 */
static int
CompareNodesByTclCmd(link1PtrPtr, link2PtrPtr)
    Blt_ChainLink **link1PtrPtr, **link2PtrPtr;
{
    int result;
    Tree *t1Ptr, *t2Ptr;
    Hierbox *hboxPtr = hierBox;
    Tcl_Interp *interp = hboxPtr->interp;

    t1Ptr = Blt_ChainGetValue(*link1PtrPtr);
    t2Ptr = Blt_ChainGetValue(*link2PtrPtr);
    result = 0;               /* Hopefully this will be Ok even if the
                         * Tcl command fails to return the correct
                         * result. */
    if ((Tcl_VarEval(interp, hboxPtr->sortCmd, " ",
      Tk_PathName(hboxPtr->tkwin), " ", NodeToString(hboxPtr, t1Ptr), " ",
      NodeToString(hboxPtr, t2Ptr)) != TCL_OK) ||
      (Tcl_GetInt(interp, Tcl_GetStringResult(interp), &result) != TCL_OK)) {
      Tcl_BackgroundError(interp);
    }
    Tcl_ResetResult(interp);
    return result;
}

/*
 *----------------------------------------------------------------------
 *
 * SortNode --
 *
 *    Sorts the subnodes at a given node.
 *
 * Results:
 *    Always returns TCL_OK.
 *
 *----------------------------------------------------------------------
 */
static int
SortNode(hboxPtr, treePtr)
    Hierbox *hboxPtr;
    Tree *treePtr;
{
    if (treePtr->chainPtr != NULL) {
      if (hboxPtr->sortCmd != NULL) {
          hierBox = hboxPtr;
          Blt_ChainSort(treePtr->chainPtr, CompareNodesByTclCmd);
      } else {
          Blt_ChainSort(treePtr->chainPtr, CompareNodesByName);
      }
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * UnmapNode --
 *
 *    Unmaps the given node.  The node will not be drawn.  Ignore
 *    unmapping of the root node (it makes no sense).
 *
 * Results:
 *    Always returns TCL_OK.
 *
 *----------------------------------------------------------------------
 */
static int
UnmapNode(hboxPtr, treePtr)
    Hierbox *hboxPtr;
    Tree *treePtr;
{
    if (treePtr != hboxPtr->rootPtr) {
      treePtr->entryPtr->flags &= ~ENTRY_MAPPED;
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * MapAncestors --
 *
 *    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.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
MapAncestors(hboxPtr, treePtr)
    Hierbox *hboxPtr;         /* Not used. */
    Tree *treePtr;
{
    /*
     * Make sure that all the ancestors of this node are mapped too.
     */
    treePtr = treePtr->parentPtr;
    while (treePtr != NULL) {
      if (treePtr->entryPtr->flags & ENTRY_MAPPED) {
          break;        /* Assume ancestors are also mapped. */
      }
      treePtr->entryPtr->flags |= ENTRY_MAPPED;
      treePtr = treePtr->parentPtr;
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * MapNode --
 *
 *    Maps the given node.  Only mapped nodes are drawn.
 *
 * Results:
 *    Always returns TCL_OK.
 *
 *----------------------------------------------------------------------
 */
static int
MapNode(hboxPtr, treePtr)
    Hierbox *hboxPtr;
    Tree *treePtr;
{
    treePtr->entryPtr->flags |= ENTRY_MAPPED;
    MapAncestors(hboxPtr, treePtr);
    return TCL_OK;
}

static int
FixUnmappedSelections(hboxPtr, treePtr)
    Hierbox *hboxPtr;
    Tree *treePtr;
{
    if (!(treePtr->entryPtr->flags & ENTRY_MAPPED)) {
      DeselectEntry(hboxPtr, treePtr);
      PruneSelection(hboxPtr, treePtr);
      if (IsAncestor(treePtr, hboxPtr->focusPtr)) {
          hboxPtr->focusPtr = treePtr->parentPtr;
          if (hboxPtr->focusPtr == NULL) {
            hboxPtr->focusPtr = hboxPtr->rootPtr;
          }
          Blt_SetFocusItem(hboxPtr->bindTable, hboxPtr->focusPtr, NULL);
      }
    }
    return TCL_OK;
}

static int
DeleteNode(hboxPtr, treePtr)
    Hierbox *hboxPtr;         
    Tree *treePtr;
{
    /*
     * Indicate that the screen layout of the hierarchy may have changed
     * because the node was deleted.  We don't want to access the
     * hboxPtr->visibleArr array if one of the nodes is bogus.
     */
    hboxPtr->flags |= HIERBOX_DIRTY;
    if (treePtr == hboxPtr->activePtr) {
      hboxPtr->activePtr = treePtr->parentPtr;
    }
    if (treePtr == hboxPtr->activeButtonPtr) {
      hboxPtr->activeButtonPtr = NULL;
    }
    if (treePtr == hboxPtr->focusPtr) {
      hboxPtr->focusPtr = treePtr->parentPtr;
      Blt_SetFocusItem(hboxPtr->bindTable, hboxPtr->focusPtr, NULL);
    }
    if (treePtr == hboxPtr->selAnchorPtr) {
      hboxPtr->selAnchorPtr = NULL;
    }
    DeselectEntry(hboxPtr, treePtr);
    PruneSelection(hboxPtr, treePtr);
    if (treePtr->linkPtr != NULL) { /* Remove from parent's list */
      Blt_ChainDeleteLink(treePtr->parentPtr->chainPtr, treePtr->linkPtr);
      treePtr->linkPtr = NULL;
    }
    /* 
     * Node may still be in use, so we can't free it right now.  We'll
     * mark the parent as NULL and remove it from the parent's list of
     * children. 
     */
    treePtr->parentPtr = NULL;      
    Blt_DeleteBindings(hboxPtr->bindTable, treePtr);
    Blt_DeleteBindings(hboxPtr->buttonBindTable, treePtr);
    Tcl_EventuallyFree(treePtr, DestroyNode);
    return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * DestroyTree --
 *
 *    Recursively deletes the given node and all its subnodes.
 *
 * Results:
 *    If successful, returns TCL_OK.  Otherwise TCL_ERROR is
 *    returned.
 *
 *----------------------------------------------------------------------
 */
static int
DestroyTree(hboxPtr, treePtr)
    Hierbox *hboxPtr;
    Tree *treePtr;
{
    return ApplyToTree(hboxPtr, treePtr, DeleteNode, APPLY_RECURSE);
}

/*ARGSUSED*/
static void
GetTags(table, object, context, list)
    Blt_BindTable table;      /* Not used. */
    ClientData object;
    ClientData context;       /* Not used. */
    Blt_List list;
{
    Tree *treePtr;

    Blt_ListAppend(list, (char *)object, 0);
    treePtr = (Tree *) object;
    if (treePtr->entryPtr->tags != NULL) {
      int nNames;
      char **names;
      register char **p;

      if (Tcl_SplitList((Tcl_Interp *)NULL, treePtr->entryPtr->tags, &nNames,
            &names) == TCL_OK) {
          for (p = names; *p != NULL; p++) {
            Blt_ListAppend(list, Tk_GetUid(*p), 0);
          }
          Blt_Free(names);
      }
    }
}

/*ARGSUSED*/
static ClientData
PickButton(clientData, x, y, contextPtr)
    ClientData clientData;
    int x, y;                 /* Screen coordinates of the test point. */
    ClientData *contextPtr;   /* Not used. */
{
    Hierbox *hboxPtr = clientData;
    Entry *entryPtr;
    Tree *treePtr;

    if (hboxPtr->flags & HIERBOX_DIRTY) {
      /* Can't trust selected entry, if entries have been added or deleted. */
      if (hboxPtr->flags & HIERBOX_LAYOUT) {
          ComputeLayout(hboxPtr);
      }
      ComputeVisibleEntries(hboxPtr);
    }
    if (hboxPtr->nVisible == 0) {
      return (ClientData) 0;
    }
    treePtr = NearestNode(hboxPtr, x, y, FALSE);
    if (treePtr == NULL) {
      return (ClientData) 0;
    }
    entryPtr = treePtr->entryPtr;
    if (entryPtr->flags & ENTRY_BUTTON) {
      ButtonAttributes *buttonPtr = &(hboxPtr->button);
      int left, right, top, bottom;
#define BUTTON_PAD 2
      left = entryPtr->worldX + entryPtr->buttonX - BUTTON_PAD;
      right = left + buttonPtr->width + 2 * BUTTON_PAD;
      top = entryPtr->worldY + entryPtr->buttonY - BUTTON_PAD;
      bottom = top + buttonPtr->height + 2 * BUTTON_PAD;
      x = WORLDX(hboxPtr, x);
      y = WORLDY(hboxPtr, y);
      if ((x >= left) && (x < right) && (y >= top) && (y < bottom)) {
          return treePtr;
      }
    }
    return (ClientData) 0;
}

/*ARGSUSED*/
static ClientData
PickEntry(clientData, x, y, contextPtr)
    ClientData clientData;
    int x, y;                 /* Screen coordinates of the test point. */
    ClientData *contextPtr;   /* Not used. */
{
    Hierbox *hboxPtr = clientData;
    Entry *entryPtr;
    Tree *treePtr;

    if (hboxPtr->flags & HIERBOX_DIRTY) {
      /* Can't trust selected entry, if entries have been added or deleted. */
      if (hboxPtr->flags & HIERBOX_LAYOUT) {
          ComputeLayout(hboxPtr);
      }
      ComputeVisibleEntries(hboxPtr);
    }
    if (hboxPtr->nVisible == 0) {
      return (ClientData) 0;
    }
    treePtr = NearestNode(hboxPtr, x, y, FALSE);
    if (treePtr == NULL) {
      return (ClientData) 0;
    }
    entryPtr = treePtr->entryPtr;
    if (entryPtr->flags & ENTRY_BUTTON) {
      ButtonAttributes *buttonPtr = &(hboxPtr->button);
      int left, right, top, bottom;
#define BUTTON_PAD 2
      left = entryPtr->worldX + entryPtr->buttonX - BUTTON_PAD;
      right = left + buttonPtr->width + 2 * BUTTON_PAD;
      top = entryPtr->worldY + entryPtr->buttonY - BUTTON_PAD;
      bottom = top + buttonPtr->height + 2 * BUTTON_PAD;
      x = WORLDX(hboxPtr, x);
      y = WORLDY(hboxPtr, y);
      if ((x >= left) && (x < right) && (y >= top) && (y < bottom)) {
          return NULL;
      }
    }
    return treePtr;
}

static int
ConfigureEntry(hboxPtr, entryPtr, argc, argv, flags)
    Hierbox *hboxPtr;
    Entry *entryPtr;
    int argc;
    char **argv;
    int flags;
{
    GC newGC;
    XGCValues gcValues;
    unsigned long gcMask;
    int entryWidth, entryHeight;
    int width, height;
    Tk_Font font;
    XColor *colorPtr;

    hierBox = hboxPtr;
    if (Tk_ConfigureWidget(hboxPtr->interp, hboxPtr->tkwin, entryConfigSpecs,
          argc, argv, (char *)entryPtr, flags) != TCL_OK) {
      return TCL_ERROR;
    }
    entryPtr->iconWidth = entryPtr->iconHeight = 0;
    if (entryPtr->icons != NULL) {
      register int i;

      for (i = 0; i < 2; i++) {
          if (entryPtr->icons[i] == NULL) {
            break;
          }
          if (entryPtr->iconWidth < ImageWidth(entryPtr->icons[i])) {
            entryPtr->iconWidth = ImageWidth(entryPtr->icons[i]);
          }
          if (entryPtr->iconHeight < ImageHeight(entryPtr->icons[i])) {
            entryPtr->iconHeight = ImageHeight(entryPtr->icons[i]);
          }
      }
    }
    newGC = NULL;
    if ((entryPtr->icons == NULL) || (entryPtr->icons[0] == NULL)) {
      gcMask = GCClipMask | GCBackground;
      gcValues.clip_mask = hboxPtr->iconMask;
      gcValues.background = hboxPtr->iconColor->pixel;
      newGC = Tk_GetGC(hboxPtr->tkwin, gcMask, &gcValues);
      entryPtr->iconWidth = DEF_ICON_WIDTH;
      entryPtr->iconHeight = DEF_ICON_HEIGHT;
    }
    entryPtr->iconWidth += 2 * ICON_PADX;
    entryPtr->iconHeight += 2 * ICON_PADY;
    if (entryPtr->iconGC != NULL) {
      Tk_FreeGC(hboxPtr->display, entryPtr->iconGC);
    }
    entryPtr->iconGC = newGC;

    entryHeight = MAX(entryPtr->iconHeight, hboxPtr->button.height);
    entryWidth = 0;

    gcMask = GCForeground | GCFont;
    colorPtr = GETCOLOR(hboxPtr, entryPtr->labelColor);
    gcValues.foreground = colorPtr->pixel;
    font = GETFONT(hboxPtr, entryPtr->labelFont);
    gcValues.font = Tk_FontId(font);
    newGC = Tk_GetGC(hboxPtr->tkwin, gcMask, &gcValues);
    if (entryPtr->labelGC != NULL) {
      Tk_FreeGC(hboxPtr->display, entryPtr->labelGC);
    }
    entryPtr->labelGC = newGC;

    if (*entryPtr->labelText == '\0') {
      Tk_FontMetrics fontMetrics;

      Tk_GetFontMetrics(font, &fontMetrics);
      width = height = fontMetrics.linespace;
    } else {
      TextStyle ts;

      Blt_InitTextStyle(&ts);
      ts.shadow.offset = entryPtr->labelShadow.offset;
      ts.font = font;
      Blt_GetTextExtents(&ts, entryPtr->labelText, &width, &height);
    }
    width += 2 * (FOCUS_WIDTH + LABEL_PADX + hboxPtr->selBorderWidth);
    height += 2 * (FOCUS_WIDTH + LABEL_PADY + hboxPtr->selBorderWidth);
    width = ODD(width);
    height = ODD(height);
    entryWidth += width;
    if (entryHeight < height) {
      entryHeight = height;
    }
    entryPtr->labelWidth = width;
    entryPtr->labelHeight = height;
    width = height = 0;
    if (entryPtr->images != NULL) {
      register CachedImage *imagePtr;

      for (imagePtr = entryPtr->images; *imagePtr != NULL; imagePtr++) {
          width += ImageWidth(*imagePtr);
          if (height < ImageHeight(*imagePtr)) {
            height = ImageHeight(*imagePtr);
          }
      }
    } else if (entryPtr->dataText != NULL) {
      TextStyle ts;

      gcMask = GCForeground | GCFont;
      colorPtr = GETCOLOR(hboxPtr, entryPtr->dataColor);
      gcValues.foreground = colorPtr->pixel;
      font = GETFONT(hboxPtr, entryPtr->dataFont);
      gcValues.font = Tk_FontId(font);
      newGC = Tk_GetGC(hboxPtr->tkwin, gcMask, &gcValues);
      if (entryPtr->dataGC != NULL) {
          Tk_FreeGC(hboxPtr->display, entryPtr->dataGC);
      }
      entryPtr->dataGC = newGC;

      Blt_InitTextStyle(&ts);
      ts.font = font;
      ts.shadow.offset = entryPtr->dataShadow.offset;
      Blt_GetTextExtents(&ts, entryPtr->dataText, &width, &height);
      width += 2 * LABEL_PADX;
      height += 2 * LABEL_PADY;
    }
    entryWidth += width;
    if (entryHeight < height) {
      entryHeight = height;
    }
    entryPtr->width = entryWidth + 4;
    entryPtr->height = entryHeight + hboxPtr->leader;

    /*
     * Force the height of the entry to an even number. This is to
     * make the dots or the vertical line segments coincide with the
     * start of the horizontal lines.
     */
    if (entryPtr->height & 0x01) {
      entryPtr->height++;
    }
    hboxPtr->flags |= HIERBOX_LAYOUT;
    EventuallyRedraw(hboxPtr);
    return TCL_OK;
}

/*
 * Hierbox Procedures
 */
/*
 *----------------------------------------------------------------------
 *
 * EventuallyRedraw --
 *
 *    Queues a request to redraw the widget at the next idle point.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    Information gets redisplayed.  Right now we don't do selective
 *    redisplays:  the whole window will be redrawn.
 *
 *----------------------------------------------------------------------
 */
static void
EventuallyRedraw(hboxPtr)
    Hierbox *hboxPtr;
{
    if ((hboxPtr->tkwin != NULL) && !(hboxPtr->flags & HIERBOX_REDRAW)) {
      hboxPtr->flags |= HIERBOX_REDRAW;
      Tcl_DoWhenIdle(DisplayHierbox, hboxPtr);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * LabelBlinkProc --
 *
 *    This procedure is called as a timer handler to blink the
 *    insertion cursor off and on.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    The cursor gets turned on or off, redisplay gets invoked,
 *    and this procedure reschedules itself.
 *
 *----------------------------------------------------------------------
 */

static void
LabelBlinkProc(clientData)
    ClientData clientData;    /* Pointer to record describing entry. */
{
    Hierbox *hboxPtr = clientData;
    TextEdit *editPtr = &(hboxPtr->labelEdit);
    int interval;

    if (!(hboxPtr->flags & HIERBOX_FOCUS) || (editPtr->offTime == 0)) {
      return;
    }
    if (editPtr->active) {
      editPtr->cursorOn ^= 1;
      interval = (editPtr->cursorOn) ? editPtr->onTime : editPtr->offTime;
      editPtr->timerToken = Tcl_CreateTimerHandler(interval, LabelBlinkProc,
           hboxPtr);
      EventuallyRedraw(hboxPtr);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * 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(hboxPtr)
    Hierbox *hboxPtr;
{
    if (!(hboxPtr->flags & SELECTION_PENDING)) {
      hboxPtr->flags |= SELECTION_PENDING;
      Tcl_DoWhenIdle(SelectCmdProc, hboxPtr);
    }
}

/*
 * ----------------------------------------------------------------------
 *
 * CreateHierbox --
 *
 * ----------------------------------------------------------------------
 */
static Hierbox *
CreateHierbox(interp, tkwin)
    Tcl_Interp *interp;
    Tk_Window tkwin;
{
    Hierbox *hboxPtr;

    hboxPtr = Blt_Calloc(1, sizeof(Hierbox));
    assert(hboxPtr);

    Tk_SetClass(tkwin, "Hierbox");
    hboxPtr->tkwin = tkwin;
    hboxPtr->display = Tk_Display(tkwin);
    hboxPtr->interp = interp;

    hboxPtr->leader = 0;
    hboxPtr->dashes = 1;
    hboxPtr->highlightWidth = 2;
    hboxPtr->selBorderWidth = 1;
    hboxPtr->borderWidth = 2;
    hboxPtr->relief = TK_RELIEF_SUNKEN;
    hboxPtr->selRelief = TK_RELIEF_FLAT;
    hboxPtr->scrollMode = BLT_SCROLL_MODE_HIERBOX;
    hboxPtr->button.closeRelief = hboxPtr->button.openRelief = TK_RELIEF_SOLID;
    hboxPtr->reqWidth = 200;
    hboxPtr->reqHeight = 400;
    hboxPtr->lineWidth = 1;
    hboxPtr->button.borderWidth = 1;
    hboxPtr->labelEdit.selAnchor = -1;
    hboxPtr->labelEdit.selFirst = hboxPtr->labelEdit.selLast = -1;
    hboxPtr->labelEdit.onTime = 600, hboxPtr->labelEdit.offTime = 300;
    Blt_ChainInit(&(hboxPtr->selectChain));
    Blt_InitHashTable(&(hboxPtr->selectTable), BLT_ONE_WORD_KEYS);
    Blt_InitHashTable(&(hboxPtr->nodeTable), BLT_ONE_WORD_KEYS);
    Blt_InitHashTable(&(hboxPtr->imageTable), BLT_STRING_KEYS);
    hboxPtr->bindTable = Blt_CreateBindingTable(interp, tkwin, hboxPtr, 
      PickEntry, GetTags);
    hboxPtr->buttonBindTable = Blt_CreateBindingTable(interp, tkwin, hboxPtr, 
      PickButton, GetTags);
#if (TK_MAJOR_VERSION > 4)
    Blt_SetWindowInstanceData(tkwin, hboxPtr);
#endif
    return hboxPtr;
}

/*
 * ----------------------------------------------------------------------
 *
 * DestroyHierbox --
 *
 *    This procedure is invoked by Tcl_EventuallyFree or Tcl_Release
 *    to clean up the internal structure of a Hierbox at a safe time
 *    (when no-one is using it anymore).
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    Everything associated with the widget is freed up.
 *
 * ----------------------------------------------------------------------
 */
static void
DestroyHierbox(dataPtr)
    DestroyData dataPtr;      /* Pointer to the widget record. */
{
    Hierbox *hboxPtr = (Hierbox *)dataPtr;
    ButtonAttributes *buttonPtr = &(hboxPtr->button);

    Tk_FreeOptions(configSpecs, (char *)hboxPtr, hboxPtr->display, 0);
    if (hboxPtr->tkwin != NULL) {
      Tk_DeleteSelHandler(hboxPtr->tkwin, XA_PRIMARY, XA_STRING);
    }
    if (hboxPtr->lineGC != NULL) {
      Tk_FreeGC(hboxPtr->display, hboxPtr->lineGC);
    }
    if (hboxPtr->focusGC != NULL) {
      Blt_FreePrivateGC(hboxPtr->display, hboxPtr->focusGC);
    }
    if (hboxPtr->tile != NULL) {
      Blt_FreeTile(hboxPtr->tile);
    }
    if (hboxPtr->visibleArr != NULL) {
      Blt_Free(hboxPtr->visibleArr);
    }
    if (hboxPtr->levelInfo != NULL) {
      Blt_Free(hboxPtr->levelInfo);
    }
    if (hboxPtr->iconBitmap != None) {
      Tk_FreeBitmap(hboxPtr->display, hboxPtr->iconBitmap);
    }
    if (hboxPtr->iconMask != None) {
      Tk_FreeBitmap(hboxPtr->display, hboxPtr->iconMask);
    }
    if (hboxPtr->iconColor != NULL) {
      Tk_FreeColor(hboxPtr->iconColor);
    }
    if (buttonPtr->images != NULL) {
      register CachedImage *imagePtr;

      for (imagePtr = buttonPtr->images; *imagePtr != NULL; imagePtr++) {
          FreeCachedImage(hboxPtr, *imagePtr);
      }
      Blt_Free(buttonPtr->images);
    }
    if (buttonPtr->activeGC != NULL) {
      Tk_FreeGC(hboxPtr->display, buttonPtr->activeGC);
    }
    if (buttonPtr->normalGC != NULL) {
      Tk_FreeGC(hboxPtr->display, buttonPtr->normalGC);
    }
    if (buttonPtr->lineGC != NULL) {
      Tk_FreeGC(hboxPtr->display, buttonPtr->lineGC);
    }
    DestroyTree(hboxPtr, hboxPtr->rootPtr);
    Blt_DeleteHashTable(&(hboxPtr->nodeTable));
    Blt_ChainReset(&(hboxPtr->selectChain));
    Blt_DeleteHashTable(&(hboxPtr->selectTable));
    Blt_DestroyBindingTable(hboxPtr->bindTable);
    Blt_DestroyBindingTable(hboxPtr->buttonBindTable);
    Blt_Free(hboxPtr);
}

/*
 * --------------------------------------------------------------
 *
 * HierboxEventProc --
 *
 *    This procedure is invoked by the Tk dispatcher for various
 *    events on hierarchy widgets.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    When the window gets deleted, internal structures get
 *    cleaned up.  When it gets exposed, it is redisplayed.
 *
 * --------------------------------------------------------------
 */
static void
HierboxEventProc(clientData, eventPtr)
    ClientData clientData;    /* Information about window. */
    XEvent *eventPtr;         /* Information about event. */
{
    Hierbox *hboxPtr = clientData;

    if (eventPtr->type == Expose) {
      if (eventPtr->xexpose.count == 0) {
          EventuallyRedraw(hboxPtr);
      }
    } else if (eventPtr->type == ConfigureNotify) {
      hboxPtr->flags |= (HIERBOX_LAYOUT | HIERBOX_SCROLL);
      EventuallyRedraw(hboxPtr);
    } else if ((eventPtr->type == FocusIn) || (eventPtr->type == FocusOut)) {
      if (eventPtr->xfocus.detail != NotifyInferior) {
          TextEdit *editPtr = &(hboxPtr->labelEdit);

          if (eventPtr->type == FocusIn) {
            hboxPtr->flags |= HIERBOX_FOCUS;
          } else {
            hboxPtr->flags &= ~HIERBOX_FOCUS;
          }
          Tcl_DeleteTimerHandler(editPtr->timerToken);
          if ((editPtr->active) && (hboxPtr->flags & HIERBOX_FOCUS)) {
            editPtr->cursorOn = TRUE;
            if (editPtr->offTime != 0) {
                editPtr->timerToken = 
                  Tcl_CreateTimerHandler(editPtr->onTime,
                  LabelBlinkProc, clientData);
            }
          } else {
            editPtr->cursorOn = FALSE;
            editPtr->timerToken = (Tcl_TimerToken) NULL;
          }
          EventuallyRedraw(hboxPtr);
      }
    } else if (eventPtr->type == DestroyNotify) {
      if (hboxPtr->tkwin != NULL) {
          hboxPtr->tkwin = NULL;
          Tcl_DeleteCommandFromToken(hboxPtr->interp, hboxPtr->cmdToken);
      }
      if (hboxPtr->flags & HIERBOX_REDRAW) {
          Tcl_CancelIdleCall(DisplayHierbox, hboxPtr);
      }
      if (hboxPtr->flags & SELECTION_PENDING) {
          Tcl_CancelIdleCall(SelectCmdProc, hboxPtr);
      }
      Tcl_EventuallyFree(hboxPtr, DestroyHierbox);
    }
}

/* Selection Procedures */
/*
 *----------------------------------------------------------------------
 *
 * SelectionProc --
 *
 *    This procedure is called back by Tk when the selection is
 *    requested by someone.  It returns part or all of the selection
 *    in a buffer provided by the caller.
 *
 * Results:
 *    The return value is the number of non-NULL bytes stored at
 *    buffer.  Buffer is filled (or partially filled) with a
 *    NUL-terminated string containing part or all of the
 *    selection, as given by offset and maxBytes.
 *
 * Side effects:
 *    None.
 *
 *----------------------------------------------------------------------
 */
static int
SelectionProc(clientData, offset, buffer, maxBytes)
    ClientData clientData;    /* Information about the widget. */
    int offset;               /* Offset within selection of first
                         * character to be returned. */
    char *buffer;       /* Location in which to place
                         * selection. */
    int maxBytes;       /* Maximum number of bytes to place
                         * at buffer, not including terminating
                         * NULL character. */
{
    Hierbox *hboxPtr = clientData;
    int size;
    Tcl_DString dString;

    if (!hboxPtr->exportSelection) {
      return -1;
    }
    /*
     * Retrieve the names of the selected entries.
     */
    Tcl_DStringInit(&dString);
    if (hboxPtr->sortSelection) {
      hboxPtr->clientData = &dString;
      ApplyToTree(hboxPtr, hboxPtr->rootPtr, GetSelectedLabels,
                APPLY_RECURSE | APPLY_BEFORE | APPLY_OPEN_ONLY);
    } else {
      Blt_ChainLink *linkPtr;
      Tree *treePtr;

      for (linkPtr = Blt_ChainFirstLink(&(hboxPtr->selectChain)); 
           linkPtr != NULL; linkPtr = Blt_ChainNextLink(linkPtr)) {
          treePtr = Blt_ChainGetValue(linkPtr);
          Tcl_DStringAppend(&dString, treePtr->entryPtr->labelText, -1);
          Tcl_DStringAppend(&dString, "\n", -1);
      }
    }
    size = Tcl_DStringLength(&dString) - offset;
    strncpy(buffer, Tcl_DStringValue(&dString) + offset, maxBytes);
    Tcl_DStringFree(&dString);
    buffer[maxBytes] = '\0';
    return (size > maxBytes) ? maxBytes : size;
}

/*
 *----------------------------------------------------------------------
 *
 * 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. */
{
    Hierbox *hboxPtr = clientData;

    if ((hboxPtr->selAnchorPtr != NULL) && (hboxPtr->exportSelection)) {
      ClearSelection(hboxPtr);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * HierboxInstCmdDeleteProc --
 *
 *    This procedure is invoked when a widget command is deleted.  If
 *    the widget isn't already in the process of being destroyed,
 *    this command destroys it.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    The widget is destroyed.
 *
 *----------------------------------------------------------------------
 */
static void
HierboxInstCmdDeleteProc(clientData)
    ClientData clientData;    /* Pointer to widget record for widget. */
{
    Hierbox *hboxPtr = clientData;

    /*
     * This procedure could be invoked either because the window was
     * destroyed and the command was then deleted (in which case tkwin
     * is NULL) or because the command was deleted, and then this
     * procedure destroys the widget.
     */
    if (hboxPtr->tkwin != NULL) {
      Tk_Window tkwin;

      tkwin = hboxPtr->tkwin;
      hboxPtr->tkwin = NULL;
      Tk_DestroyWindow(tkwin);
#ifdef ITCL_NAMESPACES
      Itk_SetWidgetCommand(tkwin, (Tcl_Command) NULL);
#endif /* ITCL_NAMESPACES */
    }
}

/*
 *----------------------------------------------------------------------
 *
 * TileChangedProc
 *
 *    Stub for image change notifications.  Since we immediately draw
 *    the image into a pixmap, we don't care about image changes.
 *
 *    It would be better if Tk checked for NULL proc pointers.
 *
 * Results:
 *    None.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static void
TileChangedProc(clientData, tile)
    ClientData clientData;
    Blt_Tile tile;            /* Not used. */
{
    Hierbox *hboxPtr = clientData;

    if (hboxPtr->tkwin != NULL) {
      EventuallyRedraw(hboxPtr);
    }
}

/*
 * ----------------------------------------------------------------------
 *
 * ConfigureHierbox --
 *
 *    This procedure is called to process an argv/argc list, plus
 *    the Tk option database, in order to configure (or reconfigure)
 *    the widget.
 *
 * Results:
 *    The return value is 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 hboxPtr; old resources get freed, if there
 *    were any.  The widget is redisplayed.
 *
 * ----------------------------------------------------------------------
 */
static int
ConfigureHierbox(interp, hboxPtr, argc, argv, flags)
    Tcl_Interp *interp;       /* Interpreter to report errors back to. */
    Hierbox *hboxPtr;         /* Information about widget; may or may not
                           * already have values for some fields. */
    int argc;
    char **argv;
    int flags;
{
    XGCValues gcValues;
    unsigned long gcMask;
    GC newGC;
    Tk_Uid nameId;
    Pixmap bitmap;
    hierBox = hboxPtr;
    if (Tk_ConfigureWidget(interp, hboxPtr->tkwin, configSpecs, argc, argv,
          (char *)hboxPtr, flags) != TCL_OK) {
      return TCL_ERROR;
    }
    if (Blt_ConfigModified(configSpecs, "-font", "-linespacing", "-width",
          "-height", "-hideroot", (char *)NULL)) {
      /*
       * These options change the layout of the box.  Mark for
       * update.
       */
      hboxPtr->flags |= (HIERBOX_LAYOUT | HIERBOX_SCROLL);
    }
    if ((hboxPtr->reqHeight != Tk_ReqHeight(hboxPtr->tkwin)) ||
      (hboxPtr->reqWidth != Tk_ReqWidth(hboxPtr->tkwin))) {
      Tk_GeometryRequest(hboxPtr->tkwin, hboxPtr->reqWidth,
          hboxPtr->reqHeight);
    }
    gcMask = (GCForeground | GCLineWidth);
    gcValues.foreground = hboxPtr->lineColor->pixel;
    gcValues.line_width = hboxPtr->lineWidth;
    if (hboxPtr->dashes > 0) {
      gcMask |= (GCLineStyle | GCDashList);
      gcValues.line_style = LineOnOffDash;
      gcValues.dashes = hboxPtr->dashes;
    }
    newGC = Tk_GetGC(hboxPtr->tkwin, gcMask, &gcValues);
    if (hboxPtr->lineGC != NULL) {
      Tk_FreeGC(hboxPtr->display, hboxPtr->lineGC);
    }
    hboxPtr->lineGC = newGC;

    /*
     * GC for active label. Dashed outline.
     */
    gcMask = GCForeground | GCLineStyle;
    gcValues.foreground = hboxPtr->focusColor->pixel;
    gcValues.line_style = (LineIsDashed(hboxPtr->focusDashes))
      ? LineOnOffDash : LineSolid;
    newGC = Blt_GetPrivateGC(hboxPtr->tkwin, gcMask, &gcValues);
    if (LineIsDashed(hboxPtr->focusDashes)) {
      hboxPtr->focusDashes.offset = 2;
      Blt_SetDashes(hboxPtr->display, newGC, &(hboxPtr->focusDashes));
    }
    if (hboxPtr->focusGC != NULL) {
      Blt_FreePrivateGC(hboxPtr->display, hboxPtr->focusGC);
    }
    hboxPtr->focusGC = newGC;

    if (hboxPtr->iconBitmap == None) {
      nameId = Tk_GetUid("HierboxFolder");
      bitmap = Tk_GetBitmap(interp, hboxPtr->tkwin, nameId);
      if (bitmap == None) {
          if (Tk_DefineBitmap(interp, nameId, (char *)folderBits,
                DEF_ICON_WIDTH, DEF_ICON_HEIGHT) != TCL_OK) {
            return TCL_ERROR;
          }
          bitmap = Tk_GetBitmap(interp, hboxPtr->tkwin, nameId);
          if (bitmap == None) {
            return TCL_ERROR;
          }
      }
      hboxPtr->iconBitmap = bitmap;
      Tcl_ResetResult(interp);
    }
    if (hboxPtr->iconMask == None) {
      nameId = Tk_GetUid("HierboxFolderMask");
      bitmap = Tk_GetBitmap(interp, hboxPtr->tkwin, nameId);
      if (bitmap == None) {
          if (Tk_DefineBitmap(interp, nameId, (char *)folderMaskBits,
                DEF_ICON_WIDTH, DEF_ICON_HEIGHT) != TCL_OK) {
            return TCL_ERROR;
          }
          bitmap = Tk_GetBitmap(interp, hboxPtr->tkwin, nameId);
          if (bitmap == None) {
            return TCL_ERROR;
          }
      }
      hboxPtr->iconMask = bitmap;
      Tcl_ResetResult(interp);
    }
    if (hboxPtr->iconColor == NULL) {
      hboxPtr->iconColor = Tk_GetColor(interp, hboxPtr->tkwin,
          Tk_GetUid("yellow"));
      if (hboxPtr->iconColor == NULL) {
          return TCL_ERROR;
      }
    }
    if (hboxPtr->tile != NULL) {
      Blt_SetTileChangedProc(hboxPtr->tile, TileChangedProc, hboxPtr);
    }
    ConfigureButtons(hboxPtr);

    hboxPtr->inset = hboxPtr->highlightWidth + hboxPtr->borderWidth + INSET_PAD;
    EventuallyRedraw(hboxPtr);
    return TCL_OK;
}

/*
 * ----------------------------------------------------------------------
 *
 * ResetCoordinates --
 *
 *    Determines the maximum height of all visible entries.
 *
 * Results:
 *    Returns 1 if beyond the last visible entry, 0 otherwise.
 *
 * Side effects:
 *    The array of visible nodes is filled.
 *
 * ----------------------------------------------------------------------
 */
static void
ResetCoordinates(hboxPtr, treePtr, infoPtr)
    Hierbox *hboxPtr;
    Tree *treePtr;
    LayoutInfo *infoPtr;
{
    Entry *entryPtr = treePtr->entryPtr;
    int width;

    /*
     * If the entry is hidden, then do nothing.
     * Otherwise, include it in the layout.
     */
    entryPtr->worldY = infoPtr->y;
    if (!(entryPtr->flags & ENTRY_MAPPED)) {
      return;
    }
    treePtr->level = infoPtr->level;
    if (infoPtr->depth < infoPtr->level) {
      infoPtr->depth = infoPtr->level;
    }
    if ((entryPtr->flags & BUTTON_SHOW) || ((entryPtr->flags & BUTTON_AUTO) &&
          (Blt_ChainGetLength(treePtr->chainPtr) > 0))) {
      entryPtr->flags |= ENTRY_BUTTON;
    } else {
      entryPtr->flags &= ~ENTRY_BUTTON;
    }
    if (entryPtr->height < infoPtr->minHeight) {
      infoPtr->minHeight = entryPtr->height;
    }
    /*
     * Note: The maximum entry width below does not take into account
     *       the space for the icon (level offset).  This has to be
     *       deferred because it's dependent upon the maximum icon
     *       size.
     */
    width = infoPtr->x + entryPtr->width;
    if (width > infoPtr->maxWidth) {
      infoPtr->maxWidth = width;
    }
    if (infoPtr->maxIconWidth < entryPtr->iconWidth) {
      infoPtr->maxIconWidth = entryPtr->iconWidth;
    }
    entryPtr->lineHeight = -(infoPtr->y);
    infoPtr->y += entryPtr->height;
    if (entryPtr->flags & ENTRY_OPEN) {
      Blt_ChainLink *linkPtr;
      int labelOffset;
      Tree *bottomPtr;

      infoPtr->level++;
      labelOffset = infoPtr->labelOffset;
      infoPtr->labelOffset = 0;
      bottomPtr = treePtr;
      for (linkPtr = Blt_ChainFirstLink(treePtr->chainPtr); linkPtr != NULL;
          linkPtr = Blt_ChainNextLink(linkPtr)) {
          treePtr = Blt_ChainGetValue(linkPtr);
          if (treePtr->entryPtr->flags & ENTRY_MAPPED) {
            ResetCoordinates(hboxPtr, treePtr, infoPtr);
            bottomPtr = treePtr;
          }
      }
      infoPtr->level--;
      entryPtr->lineHeight += bottomPtr->entryPtr->worldY;
      entryPtr->levelX = infoPtr->labelOffset;
      infoPtr->labelOffset = labelOffset;
    }
    if (infoPtr->labelOffset < entryPtr->labelWidth) {
      infoPtr->labelOffset = entryPtr->labelWidth;
    }
}

/*
 * ----------------------------------------------------------------------
 *
 * ComputeWidths --
 *
 *    Determines the maximum height of all visible entries.
 *
 * Results:
 *    Returns 1 if beyond the last visible entry, 0 otherwise.
 *
 * Side effects:
 *    The array of visible nodes is filled.
 *
 * ----------------------------------------------------------------------
 */
static void
ComputeWidths(hboxPtr, treePtr)
    Hierbox *hboxPtr;
    Tree *treePtr;
{
    Entry *entryPtr = treePtr->entryPtr;

    if (!(entryPtr->flags & ENTRY_MAPPED)) {
      return;
    }
    if (entryPtr->iconWidth > LEVELWIDTH(treePtr->level + 1)) {
      LEVELWIDTH(treePtr->level + 1) = entryPtr->iconWidth;
    }
    if (entryPtr->flags & ENTRY_OPEN) {
      Blt_ChainLink *linkPtr;

      for (linkPtr = Blt_ChainFirstLink(treePtr->chainPtr); linkPtr != NULL;
          linkPtr = Blt_ChainNextLink(linkPtr)) {
          treePtr = Blt_ChainGetValue(linkPtr);
          if (treePtr->entryPtr->flags & ENTRY_MAPPED) {
            ComputeWidths(hboxPtr, treePtr);
          }
      }
    }
}

/*
 * ----------------------------------------------------------------------
 *
 * ComputeLayout --
 *
 *    Recompute the layout when entries are opened/closed,
 *    inserted/deleted, or when text attributes change (such as
 *    font, linespacing).
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    The world coordinates are set for all the opened entries.
 *
 * ----------------------------------------------------------------------
 */
static void
ComputeLayout(hboxPtr)
    Hierbox *hboxPtr;
{
    LayoutInfo info;

    info.level = info.depth = 0;
    info.x = info.y = 0;
    info.maxWidth = hboxPtr->button.width;
    info.maxIconWidth = hboxPtr->button.width;
    info.minHeight = INT_MAX;
    info.labelOffset = 0;

    if (hboxPtr->hideRoot) {
      info.y = -(hboxPtr->rootPtr->entryPtr->height);
    }
    ResetCoordinates(hboxPtr, hboxPtr->rootPtr, &info);

    hboxPtr->xScrollUnits = info.maxIconWidth;
    hboxPtr->minHeight = hboxPtr->yScrollUnits = info.minHeight;
    if (hboxPtr->reqScrollX > 0) {
      hboxPtr->xScrollUnits = hboxPtr->reqScrollX;
    }
    if (hboxPtr->reqScrollY > 0) {
      hboxPtr->yScrollUnits = hboxPtr->reqScrollY;
    }
    hboxPtr->depth = info.depth + 1;
    hboxPtr->worldWidth = info.maxWidth + (hboxPtr->depth * info.maxIconWidth);
    if (hboxPtr->worldWidth < 1) {
      hboxPtr->worldWidth = 1;
    }
    hboxPtr->worldHeight = info.y;
    if (hboxPtr->worldHeight < 1) {
      hboxPtr->worldHeight = 1;
    }
    if (hboxPtr->yScrollUnits < 1) {
      hboxPtr->yScrollUnits = 1;
    }
    if (hboxPtr->xScrollUnits < 1) {
      hboxPtr->xScrollUnits = 1;
    }
    if (hboxPtr->levelInfo != NULL) {
      Blt_Free(hboxPtr->levelInfo);
    }
    hboxPtr->levelInfo = Blt_Calloc(hboxPtr->depth + 2, sizeof(LevelInfo));
    assert(hboxPtr->levelInfo);
    ComputeWidths(hboxPtr, hboxPtr->rootPtr);
    {
      int sum, width;
      register int i;

      sum = 0;
      for (i = 0; i <= hboxPtr->depth; i++) {
          width = hboxPtr->levelInfo[i].width;
          width |= 0x01;
          hboxPtr->levelInfo[i].width = width;
          sum += width;
          hboxPtr->levelInfo[i + 1].x = sum;
      }
    }
    hboxPtr->flags &= ~HIERBOX_LAYOUT;
}

/*
 * ----------------------------------------------------------------------
 *
 * ComputeVisibleEntries --
 *
 *    The entries visible in the viewport (the widget's window) are
 *    inserted into the array of visible nodes.
 *
 * Results:
 *    Returns 1 if beyond the last visible entry, 0 otherwise.
 *
 * Side effects:
 *    The array of visible nodes is filled.
 *
 * ----------------------------------------------------------------------
 */
static int
ComputeVisibleEntries(hboxPtr)
    Hierbox *hboxPtr;
{
    Entry *entryPtr;
    int height;
    Blt_ChainLink *linkPtr;
    register Tree *treePtr;
    int x, maxX;
    int nSlots;

    hboxPtr->xOffset = Blt_AdjustViewport(hboxPtr->xOffset, hboxPtr->worldWidth,
      VPORTWIDTH(hboxPtr), hboxPtr->xScrollUnits, hboxPtr->scrollMode);
    hboxPtr->yOffset = Blt_AdjustViewport(hboxPtr->yOffset,
      hboxPtr->worldHeight, VPORTHEIGHT(hboxPtr), hboxPtr->yScrollUnits,
      hboxPtr->scrollMode);

    height = VPORTHEIGHT(hboxPtr);

    /* Allocate worst case number of slots for entry array. */
    nSlots = (height / hboxPtr->minHeight) + 3;
    if ((nSlots != hboxPtr->nVisible) && (hboxPtr->visibleArr != NULL)) {
      Blt_Free(hboxPtr->visibleArr);
    }
    hboxPtr->visibleArr = Blt_Calloc(nSlots, sizeof(Tree *));
    assert(hboxPtr->visibleArr);
    hboxPtr->nVisible = 0;

    /* Find the node where the view port starts. */
    treePtr = hboxPtr->rootPtr;
    entryPtr = treePtr->entryPtr;
    while ((entryPtr->worldY + entryPtr->height) <= hboxPtr->yOffset) {
      for (linkPtr = Blt_ChainLastLink(treePtr->chainPtr); linkPtr != NULL;
          linkPtr = Blt_ChainPrevLink(linkPtr)) {
          treePtr = Blt_ChainGetValue(linkPtr);
          if (IsHidden(treePtr)) {
            continue;   /* Ignore hidden entries.  */
          }
          entryPtr = treePtr->entryPtr;
          if (entryPtr->worldY <= hboxPtr->yOffset) {
            break;
          }
      }

      /*
         * If we can't find the starting node, then the view must be
       * scrolled down, but some nodes were deleted.  Reset the view
         * back to the top and try again.
         */
      if (linkPtr == NULL) {
          if (hboxPtr->yOffset == 0) {
            return TCL_OK;    /* All entries are hidden. */
          }
          hboxPtr->yOffset = 0;
          continue;
      }
    }

    height += hboxPtr->yOffset;
    maxX = 0;
    while (treePtr != NULL) {
      if (!IsHidden(treePtr)) {
          entryPtr = treePtr->entryPtr;
          /*
           * Compute and save the entry's X-coordinate now that we know
           * what the maximum level offset for the entire Hierbox is.
           */
          entryPtr->worldX = LEVELX(treePtr->level);
          x = entryPtr->worldX + LEVELWIDTH(treePtr->level) +
            LEVELWIDTH(treePtr->level + 1) + entryPtr->width;
          if (x > maxX) {
            maxX = x;
          }
          if (entryPtr->worldY >= height) {
            break;
          }
          hboxPtr->visibleArr[hboxPtr->nVisible] = treePtr;
          hboxPtr->nVisible++;
      }
      treePtr = NextNode(treePtr, ENTRY_OPEN | ENTRY_MAPPED);
    }
    hboxPtr->worldWidth = maxX;

    /*
     * -------------------------------------------------------------------
     *
     * Note:      It's assumed that the view port always starts at or
     *            over an entry.  Check that a change in the hierarchy
     *            (e.g. closing a node) hasn't left the viewport beyond
     *            the last entry.  If so, adjust the viewport to start
     *            on the last entry.
     *
     * -------------------------------------------------------------------
     */
    if (hboxPtr->xOffset > (hboxPtr->worldWidth - hboxPtr->xScrollUnits)) {
      hboxPtr->xOffset = hboxPtr->worldWidth - hboxPtr->xScrollUnits;
    }
    if (hboxPtr->yOffset > (hboxPtr->worldHeight - hboxPtr->yScrollUnits)) {
      hboxPtr->yOffset = hboxPtr->worldHeight - hboxPtr->yScrollUnits;
    }
    hboxPtr->xOffset = Blt_AdjustViewport(hboxPtr->xOffset, hboxPtr->worldWidth,
      VPORTWIDTH(hboxPtr), hboxPtr->xScrollUnits, hboxPtr->scrollMode);
    hboxPtr->yOffset = Blt_AdjustViewport(hboxPtr->yOffset,
      hboxPtr->worldHeight, VPORTHEIGHT(hboxPtr), hboxPtr->yScrollUnits,
      hboxPtr->scrollMode);
    hboxPtr->flags &= ~HIERBOX_DIRTY;
    return TCL_OK;
}

static int
GetCursorLocation(hboxPtr, treePtr)
    Hierbox *hboxPtr;
    Tree *treePtr;
{
    TextEdit *editPtr = &(hboxPtr->labelEdit);
    int x, y;
    int maxLines;
    Tk_Font font;
    TextStyle ts;
    TextLayout *textPtr;
    Tk_FontMetrics fontMetrics;
    int nBytes;
    int sum;
    Entry *entryPtr;
    TextFragment *fragPtr;
    register int i;

    entryPtr = treePtr->entryPtr;
    font = GETFONT(hboxPtr, entryPtr->labelFont);
    memset(&ts, 0, sizeof(TextStyle));
    ts.font = font;
    ts.justify = TK_JUSTIFY_LEFT;
    ts.shadow.offset = entryPtr->labelShadow.offset;
    textPtr = Blt_GetTextLayout(entryPtr->labelText, &ts);

    Tk_GetFontMetrics(font, &fontMetrics);
    maxLines = (textPtr->height / fontMetrics.linespace) - 1;

    sum = 0;
    x = y = 0;

    fragPtr = textPtr->fragArr;
    for (i = 0; i <= maxLines; i++) {
      /* Total the number of bytes on each line.  Include newlines. */
      nBytes = fragPtr->count + 1;
      if ((sum + nBytes) > editPtr->insertPos) {
          x += Tk_TextWidth(font, fragPtr->text, editPtr->insertPos - sum);
          break;
      }
      y += fontMetrics.linespace;
      sum += nBytes;
      fragPtr++;
    }
    editPtr->x = x;
    editPtr->y = y;
    editPtr->height = fontMetrics.linespace;
    editPtr->width = 3;
    Blt_Free(textPtr);
    return TCL_OK;
}

/*
 * ---------------------------------------------------------------------------
 *
 * DrawVerticals --
 *
 *    Draws vertical lines for the ancestor nodes.  While the entry
 *    of the ancestor may not be visible, its vertical line segment
 *    does extent into the viewport.  So walk back up the hierarchy
 *    drawing lines until we get to the root.
 *
 * Results:
 *    None.
 *
 * Side Effects:
 *    Vertical lines are drawn for the ancestor nodes.
 *
 * ---------------------------------------------------------------------------
 */
static void
DrawVerticals(hboxPtr, treePtr, drawable)
    Hierbox *hboxPtr;         /* Widget record containing the attribute
                         * information for buttons. */
    Tree *treePtr;            /* Entry to be drawn. */
    Drawable drawable;        /* Pixmap or window to draw into. */
{
    Entry *entryPtr;          /* Entry to be drawn. */
    int x1, y1, x2, y2;
    int height;
    int x, y;

    while (treePtr->parentPtr != NULL) {
      treePtr = treePtr->parentPtr;
      entryPtr = treePtr->entryPtr;

      /*
       * World X-coordinates are computed only for entries that are in
       * the current view port.  So for each of the off-screen ancestor
       * nodes we must compute it here too.
       */
      entryPtr->worldX = LEVELX(treePtr->level);
      x = SCREENX(hboxPtr, entryPtr->worldX);
      y = SCREENY(hboxPtr, entryPtr->worldY);
      height = MAX(entryPtr->iconHeight, hboxPtr->button.height);
      y += (height - hboxPtr->button.height) / 2;
      x1 = x2 = x + LEVELWIDTH(treePtr->level) +
          LEVELWIDTH(treePtr->level + 1) / 2;
      y1 = y + hboxPtr->button.height / 2;
      y2 = y1 + entryPtr->lineHeight;
      if ((treePtr == hboxPtr->rootPtr) && (hboxPtr->hideRoot)) {
          y1 += entryPtr->height;
      }
      /*
       * Clip the line's Y-coordinates at the window border.
       */
      if (y1 < 0) {
          y1 = 0;
      }
      if (y2 > Tk_Height(hboxPtr->tkwin)) {
          y2 = Tk_Height(hboxPtr->tkwin);
      }
      if ((y1 < Tk_Height(hboxPtr->tkwin)) && (y2 > 0)) {
          XDrawLine(hboxPtr->display, drawable, hboxPtr->lineGC, x1, y1, 
                  x2, y2);
      }
    }
}

/*
 * ---------------------------------------------------------------------------
 *
 * DrawButton --
 *
 *    Draws a button for the given entry. The button is drawn
 *    centered in the region immediately to the left of the origin
 *    of the entry (computed in the layout routines). The height
 *    and width of the button were previously calculated from the
 *    average row height.
 *
 *          button height = entry height - (2 * some arbitrary padding).
 *          button width = button height.
 *
 *    The button may have a border.  The symbol (either a plus or
 *    minus) is slight smaller than the width or height minus the
 *    border.
 *
 *        x,y origin of entry
 *
 *              +---+
 *              | + | icon label
 *              +---+
 *             closed
 *
 *           |----|----| horizontal offset
 *
 *              +---+
 *              | - | icon label
 *              +---+
 *              open
 *
 * Results:
 *    None.
 *
 * Side Effects:
 *    A button is drawn for the entry.
 *
 * ---------------------------------------------------------------------------
 */
static void
DrawButton(hboxPtr, treePtr, drawable)
    Hierbox *hboxPtr;         /* Widget record containing the attribute
                         * information for buttons. */
    Tree *treePtr;            /* Node of entry. */
    Drawable drawable;        /* Pixmap or window to draw into. */
{
    ButtonAttributes *buttonPtr = &(hboxPtr->button);
    Entry *entryPtr;
    int relief;
    Tk_3DBorder border;
    GC gc, lineGC;
    int x, y;
    CachedImage image;
    int width, height;

    entryPtr = treePtr->entryPtr;

    width = LEVELWIDTH(treePtr->level);
    height = MAX(entryPtr->iconHeight, buttonPtr->height);
    entryPtr->buttonX = (width - buttonPtr->width) / 2;
    entryPtr->buttonY = (height - buttonPtr->height) / 2;

    x = SCREENX(hboxPtr, entryPtr->worldX) + entryPtr->buttonX;
    y = SCREENY(hboxPtr, entryPtr->worldY) + entryPtr->buttonY;

    if (treePtr == hboxPtr->activeButtonPtr) {
      border = buttonPtr->activeBorder;
      lineGC = buttonPtr->activeGC;
    } else {
      border = buttonPtr->border;
      lineGC = buttonPtr->lineGC;
    }
    relief = (entryPtr->flags & ENTRY_OPEN)
      ? buttonPtr->openRelief : buttonPtr->closeRelief;
    /*
     * FIXME: Reliefs "flat" and "solid" the same, since there's no 
     * "solid" in pre-8.0 releases.  Should change this when we go to a
     * pure 8.x release.
     */
    if (relief == TK_RELIEF_SOLID) {
      relief = TK_RELIEF_FLAT;
    }
    Blt_Fill3DRectangle(hboxPtr->tkwin, drawable, border, x, y,
      buttonPtr->width, buttonPtr->height, buttonPtr->borderWidth, relief);
    if (relief == TK_RELIEF_FLAT) {
      XDrawRectangle(hboxPtr->display, drawable, lineGC, x, y,
          buttonPtr->width - 1, buttonPtr->height - 1);
    }
    x += buttonPtr->borderWidth;
    y += buttonPtr->borderWidth;
    width = buttonPtr->width - (2 * buttonPtr->borderWidth);
    height = buttonPtr->height - (2 * buttonPtr->borderWidth);

    image = NULL;
    if (buttonPtr->images != NULL) {
      /* Open or close button image? */
      image = buttonPtr->images[0];
      if ((entryPtr->flags & ENTRY_OPEN) && (buttonPtr->images[1] != NULL)) {
          image = buttonPtr->images[1];
      }
    }
    /* Image or rectangle? */
    if (image != NULL) {
      Tk_RedrawImage(ImageBits(image), 0, 0, width, height, drawable, x, y);
    } else {
      XSegment segArr[2];
      int count;

      gc = (treePtr == hboxPtr->activeButtonPtr)
          ? buttonPtr->activeGC : buttonPtr->normalGC;
      count = 1;
      segArr[0].y1 = segArr[0].y2 = y + height / 2;
      segArr[0].x1 = x + BUTTON_IPAD;
#ifdef WIN32
      segArr[0].x2 = x + width - BUTTON_IPAD;
#else
      segArr[0].x2 = x + width - BUTTON_IPAD - 1;
#endif
      if (!(entryPtr->flags & ENTRY_OPEN)) {
          segArr[1].x1 = segArr[1].x2 = x + width / 2;
          segArr[1].y1 = y + BUTTON_IPAD;
#ifdef WIN32
          segArr[1].y2 = y + height - BUTTON_IPAD;
#else
          segArr[1].y2 = y + height - BUTTON_IPAD - 1;
#endif
          count++;
      }
      XDrawSegments(hboxPtr->display, drawable, gc, segArr, count);
    }
}


static void
DisplayIcon(hboxPtr, treePtr, x, y, drawable)
    Hierbox *hboxPtr;         /* Widget record containing the attribute
                         * information for buttons. */
    Tree *treePtr;            /* Node of entry. */
    int x, y;
    Drawable drawable;        /* Pixmap or window to draw into. */
{
    Entry *entryPtr = treePtr->entryPtr;
    int entryHeight;
    CachedImage image;
    int isActive;

    entryHeight = MAX(entryPtr->iconHeight, hboxPtr->button.height);
    isActive = (treePtr == hboxPtr->activePtr);
    image = NULL;
    if ((isActive) && (entryPtr->activeIcons != NULL)) {
      image = entryPtr->activeIcons[0];
      if ((treePtr == hboxPtr->focusPtr) && 
          (entryPtr->activeIcons[1] != NULL)) {
          image = entryPtr->activeIcons[1];
      }
    } else if (entryPtr->icons != NULL) { /* Selected or normal icon? */
      image = entryPtr->icons[0];
      if ((treePtr == hboxPtr->focusPtr) && (entryPtr->icons[1] != NULL)) {
          image = entryPtr->icons[1];
      }
    }
    if (image != NULL) {      /* Image or default icon bitmap? */
      int width, height;
      int top, bottom;
      int inset;
      int maxY;

      height = ImageHeight(image);
      width = ImageWidth(image);
      x += (LEVELWIDTH(treePtr->level + 1) - width) / 2;
      y += (entryHeight - height) / 2;
      inset = hboxPtr->inset - INSET_PAD;
      maxY = Tk_Height(hboxPtr->tkwin) - inset;
      top = 0;
      bottom = y + height;
      if (y < inset) {
          height += y - inset;
          top = -y + inset;
          y = inset;
      } else if (bottom >= maxY) {
          height = maxY - y;
      }
      Tk_RedrawImage(ImageBits(image), 0, top, width, height, drawable, x, y);
    } else {
      x += (LEVELWIDTH(treePtr->level + 1) - DEF_ICON_WIDTH) / 2;
      y += (entryHeight - DEF_ICON_HEIGHT) / 2;
      XSetClipOrigin(hboxPtr->display, entryPtr->iconGC, x, y);
      XCopyPlane(hboxPtr->display, hboxPtr->iconBitmap, drawable,
          entryPtr->iconGC, 0, 0, DEF_ICON_WIDTH, DEF_ICON_HEIGHT, x, y, 1);
    }
}

static void
DrawData(hboxPtr, treePtr, x, y, entryHeight, drawable)
    Hierbox *hboxPtr;         /* Widget record containing the attribute
                         * information for buttons. */
    Tree *treePtr;            /* Node of entry. */
    int x, y;
    int entryHeight;
    Drawable drawable;        /* Pixmap or window to draw into. */
{
    Entry *entryPtr = treePtr->entryPtr;
    /*
     * Auxillary data: text string or images.
     */
    if (entryPtr->images != NULL) {
      register CachedImage *imagePtr;
      int imageY;

      for (imagePtr = entryPtr->images; *imagePtr != NULL; imagePtr++) {
          imageY = y;
          if (ImageHeight(*imagePtr) < entryHeight) {
            imageY += (entryHeight - ImageHeight(*imagePtr)) / 2;
          }
          Tk_RedrawImage(ImageBits(*imagePtr), 0, 0, ImageWidth(*imagePtr),
            ImageHeight(*imagePtr), drawable, x, imageY);
          x += ImageWidth(*imagePtr);
      }
    } else if (entryPtr->dataText != NULL) {
      TextStyle ts;
      Tk_Font font;
      XColor *colorPtr;
      int width, height;

      font = GETFONT(hboxPtr, entryPtr->dataFont);
      colorPtr = GETCOLOR(hboxPtr, entryPtr->dataColor);
      y += hboxPtr->selBorderWidth + LABEL_PADY;

      Blt_SetDrawTextStyle(&ts, font, entryPtr->dataGC, colorPtr,
          hboxPtr->selFgColor, entryPtr->dataShadow.color, 0.0,
          TK_ANCHOR_NW, TK_JUSTIFY_LEFT, 0, entryPtr->dataShadow.offset);
      Blt_GetTextExtents(&ts, entryPtr->dataText, &width, &height);
      if (height < entryHeight) {
          y += (entryHeight - height) / 2;
      }
      Blt_DrawText(hboxPtr->tkwin, drawable, entryPtr->dataText, &ts, x, y);
    }
}


static int
DrawLabel(hboxPtr, treePtr, x, y, drawable)
    Hierbox *hboxPtr;         /* Widget record containing the attribute
                         * information for buttons. */
    Tree *treePtr;            /* Node of entry. */
    int x, y;
    Drawable drawable;        /* Pixmap or window to draw into. */
{
    Entry *entryPtr = treePtr->entryPtr;
    TextEdit *editPtr = &(hboxPtr->labelEdit);
    TextStyle ts;
    int width, height;        /* Width and height of label. */
    Tk_Font font;
    int isSelected, isFocused;
    int entryHeight;

    entryHeight = MAX(entryPtr->iconHeight, hboxPtr->button.height);
    font = GETFONT(hboxPtr, entryPtr->labelFont);
    isFocused = ((treePtr == hboxPtr->focusPtr) &&
      (hboxPtr->flags & HIERBOX_FOCUS));
    isSelected = IsSelected(hboxPtr, treePtr);

    /* Includes padding, selection 3-D border, and focus outline. */
    width = entryPtr->labelWidth;
    height = entryPtr->labelHeight;

    /* Center the label, if necessary, vertically along the entry row. */
    if (height < entryHeight) {
      y += (entryHeight - height) / 2;
    }

#ifdef notdef
    /* Normal background color */
    Blt_Fill3DRectangle(hboxPtr->tkwin, drawable, hboxPtr->border, x, y, width, 
             height, 0, TK_RELIEF_FLAT);
#endif
    if (isFocused) {          /* Focus outline */
      XDrawRectangle(hboxPtr->display, drawable, hboxPtr->focusGC,
          x, y, width - 1, height - 1);
    }
    x += FOCUS_WIDTH;
    y += FOCUS_WIDTH;
    if (isSelected) {
      Blt_Fill3DRectangle(hboxPtr->tkwin, drawable, hboxPtr->selBorder,
          x, y, width - 2 * FOCUS_WIDTH, height - 2 * FOCUS_WIDTH,
          hboxPtr->selBorderWidth, hboxPtr->selRelief);
    }
    x += LABEL_PADX + hboxPtr->selBorderWidth;
    y += LABEL_PADY + hboxPtr->selBorderWidth;

    if (*entryPtr->labelText != '\0') {
      XColor *normalColor;

      normalColor = GETCOLOR(hboxPtr, entryPtr->labelColor);
      Blt_SetDrawTextStyle(&ts, font, entryPtr->labelGC, normalColor,
          hboxPtr->selFgColor, entryPtr->labelShadow.color, 0.0, TK_ANCHOR_NW,
          TK_JUSTIFY_LEFT, 0, entryPtr->labelShadow.offset);
      ts.state = (isSelected) ? STATE_ACTIVE : 0;
      Blt_DrawText(hboxPtr->tkwin, drawable, entryPtr->labelText, &ts,
          x, y);
    }
    if ((isFocused) && (hboxPtr->focusEdit) && (editPtr->cursorOn)) {
      int x1, y1, x2, y2;

      GetCursorLocation(hboxPtr, treePtr);
      x1 = x + editPtr->x;
      x2 = x1 + 3;
      y1 = y + editPtr->y - 1;
      y2 = y1 + editPtr->height - 1;
      XDrawLine(hboxPtr->display, drawable, entryPtr->labelGC,
          x1, y1, x1, y2);
      XDrawLine(hboxPtr->display, drawable, entryPtr->labelGC,
          x1 - 2, y1, x2, y1);
      XDrawLine(hboxPtr->display, drawable, entryPtr->labelGC,
          x1 - 2, y2, x2, y2);
    }
    return entryHeight;
}

/*
 * ---------------------------------------------------------------------------
 *
 * DrawEntry --
 *
 *    Draws a button for the given entry.  Note that buttons should only
 *    be drawn if the entry has sub-entries to be opened or closed.  It's
 *    the responsibility of the calling routine to ensure this.
 *
 *    The button is drawn centered in the region immediately to the left
 *    of the origin of the entry (computed in the layout routines). The
 *    height and width of the button were previously calculated from the
 *    average row height.
 *
 *          button height = entry height - (2 * some arbitrary padding).
 *          button width = button height.
 *
 *    The button has a border.  The symbol (either a plus or minus) is
 *    slight smaller than the width or height minus the border.
 *
 *        x,y origin of entry
 *
 *              +---+
 *              | + | icon label
 *              +---+
 *             closed
 *
 *           |----|----| horizontal offset
 *
 *              +---+
 *              | - | icon label
 *              +---+
 *              open
 *
 * Results:
 *    None.
 *
 * Side Effects:
 *    A button is drawn for the entry.
 *
 * ---------------------------------------------------------------------------
 */
static void
DrawEntry(hboxPtr, treePtr, drawable)
    Hierbox *hboxPtr;         /* Widget record containing the attribute
                         * information for buttons. */
    Tree *treePtr;            /* Node of entry to be drawn. */
    Drawable drawable;        /* Pixmap or window to draw into. */
{
    ButtonAttributes *buttonPtr = &(hboxPtr->button);
    int x, y;
    int width, height;
    int entryHeight;
    int buttonY;
    int x1, y1, x2, y2;
    Entry *entryPtr;

    entryPtr = treePtr->entryPtr;
    x = SCREENX(hboxPtr, entryPtr->worldX);
    y = SCREENY(hboxPtr, entryPtr->worldY);

    width = LEVELWIDTH(treePtr->level);
    height = MAX(entryPtr->iconHeight, buttonPtr->height);

    entryPtr->buttonX = (width - buttonPtr->width) / 2;
    entryPtr->buttonY = (height - buttonPtr->height) / 2;

    buttonY = y + entryPtr->buttonY;

    x1 = x + (width / 2);
    y1 = y2 = buttonY + (buttonPtr->height / 2);
    x2 = x1 + (LEVELWIDTH(treePtr->level) + LEVELWIDTH(treePtr->level + 1)) / 2;

    if ((treePtr->parentPtr != NULL) && (hboxPtr->lineWidth > 0)) {
      /*
       * For every node except root, draw a horizontal line from
       * the vertical bar to the middle of the icon.
       */
      XDrawLine(hboxPtr->display, drawable, hboxPtr->lineGC, x1, y1, x2, y2);
    }
    if ((entryPtr->flags & ENTRY_OPEN) && (hboxPtr->lineWidth > 0)) {
      /*
       * Entry is open, draw vertical line.
       */
      y2 = y1 + entryPtr->lineHeight;
      if (y2 > Tk_Height(hboxPtr->tkwin)) {
          y2 = Tk_Height(hboxPtr->tkwin); /* Clip line at window border. */
      }
      XDrawLine(hboxPtr->display, drawable, hboxPtr->lineGC, x2, y1, x2, y2);
    }
    if ((entryPtr->flags & ENTRY_BUTTON) && (treePtr->parentPtr != NULL)) {
      /*
       * Except for root, draw a button for every entry that needs
       * one.  The displayed button can be either a Tk image or a
       * rectangle with plus or minus sign.
       */
      DrawButton(hboxPtr, treePtr, drawable);
    }
    x += LEVELWIDTH(treePtr->level);
    DisplayIcon(hboxPtr, treePtr, x, y, drawable);

    x += LEVELWIDTH(treePtr->level + 1) + 4;

    /* Entry label. */
    entryHeight = DrawLabel(hboxPtr, treePtr, x, y, drawable);
    if (treePtr->parentPtr != NULL) {
      x += treePtr->parentPtr->entryPtr->levelX + LABEL_PADX;
    } else {
      x += width + entryPtr->labelWidth + LABEL_PADX;
    }
    /* Auxilary data */
    DrawData(hboxPtr, treePtr, x, y, entryHeight, drawable);

}

static void
DrawOuterBorders(hboxPtr, drawable)
    Hierbox *hboxPtr;
    Drawable drawable;
{
    /* Draw 3D border just inside of the focus highlight ring. */
    if ((hboxPtr->borderWidth > 0) && (hboxPtr->relief != TK_RELIEF_FLAT)) {
      Blt_Draw3DRectangle(hboxPtr->tkwin, drawable, hboxPtr->border,
          hboxPtr->highlightWidth, hboxPtr->highlightWidth,
          Tk_Width(hboxPtr->tkwin) - 2 * hboxPtr->highlightWidth,
          Tk_Height(hboxPtr->tkwin) - 2 * hboxPtr->highlightWidth,
          hboxPtr->borderWidth, hboxPtr->relief);
    }
    /* Draw focus highlight ring. */
    if (hboxPtr->highlightWidth > 0) {
      XColor *color;
      GC gc;

      color = (hboxPtr->flags & HIERBOX_FOCUS)
          ? hboxPtr->highlightColor : hboxPtr->highlightBgColor;
      gc = Tk_GCForColor(color, drawable);
      Tk_DrawFocusHighlight(hboxPtr->tkwin, gc, hboxPtr->highlightWidth,
          drawable);
    }
    hboxPtr->flags &= ~HIERBOX_BORDERS;
}

/*
 * ----------------------------------------------------------------------
 *
 * DisplayHierbox --
 *
 *    This procedure is invoked to display the widget.
 *
 *      Recompute the layout of the text if necessary. This is
 *    necessary if the world coordinate system has changed.
 *    Specifically, the following may have occurred:
 *
 *      1.  a text attribute has changed (font, linespacing, etc.).
 *      2.  an entry's option changed, possibly resizing the entry.
 *
 *      This is deferred to the display routine since potentially
 *      many of these may occur.
 *
 *    Set the vertical and horizontal scrollbars.  This is done
 *    here since the window width and height are needed for the
 *    scrollbar calculations.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    The widget is redisplayed.
 *
 * ----------------------------------------------------------------------
 */
static void
DisplayHierbox(clientData)
    ClientData clientData;    /* Information about widget. */
{
    Hierbox *hboxPtr = clientData;
    Pixmap drawable;

    hboxPtr->flags &= ~HIERBOX_REDRAW;
    if (hboxPtr->tkwin == NULL) {
      return;                 /* Window has been destroyed. */
    }
    if (hboxPtr->flags & HIERBOX_LAYOUT) {
      /*
       * Recompute the layout when entries are opened/closed,
       * inserted/deleted, or when text attributes change
       * (such as font, linespacing).
       */
      ComputeLayout(hboxPtr);
    }
    if (hboxPtr->flags & HIERBOX_SCROLL) {
      int width, height;

      /* Scrolling means that the view port has changed and that the
       * visible entries need to be recomputed.  */
      ComputeVisibleEntries(hboxPtr);
      Blt_PickCurrentItem(hboxPtr->bindTable);
      Blt_PickCurrentItem(hboxPtr->buttonBindTable);

      width = VPORTWIDTH(hboxPtr);
      height = VPORTHEIGHT(hboxPtr);
      if (hboxPtr->flags & HIERBOX_XSCROLL) {
          if (hboxPtr->xScrollCmdPrefix != NULL) {
            Blt_UpdateScrollbar(hboxPtr->interp, hboxPtr->xScrollCmdPrefix,
                (double)hboxPtr->xOffset / hboxPtr->worldWidth,
                (double)(hboxPtr->xOffset + width) / hboxPtr->worldWidth);
          }
      }
      if (hboxPtr->flags & HIERBOX_YSCROLL) {
          if (hboxPtr->yScrollCmdPrefix != NULL) {
            Blt_UpdateScrollbar(hboxPtr->interp, hboxPtr->yScrollCmdPrefix,
                (double)hboxPtr->yOffset / hboxPtr->worldHeight,
                (double)(hboxPtr->yOffset + height) / hboxPtr->worldHeight);
          }
      }
      hboxPtr->flags &= ~HIERBOX_SCROLL;
    }
    if (!Tk_IsMapped(hboxPtr->tkwin)) {
      return;
    }
    drawable = Tk_GetPixmap(hboxPtr->display, Tk_WindowId(hboxPtr->tkwin),
      Tk_Width(hboxPtr->tkwin), Tk_Height(hboxPtr->tkwin),
      Tk_Depth(hboxPtr->tkwin));

    /*
     * Clear the background either by tiling a pixmap or filling with
     * a solid color. Tiling takes precedence.
     */
    if (hboxPtr->tile != NULL) {
      if (hboxPtr->scrollTile) {
          Blt_SetTSOrigin(hboxPtr->tkwin, hboxPtr->tile, -hboxPtr->xOffset,
            -hboxPtr->yOffset);
      } else {
          Blt_SetTileOrigin(hboxPtr->tkwin, hboxPtr->tile, 0, 0);
      }
      Blt_TileRectangle(hboxPtr->tkwin, drawable, hboxPtr->tile, 0, 0,
          Tk_Width(hboxPtr->tkwin), Tk_Height(hboxPtr->tkwin));
    } else {
      Blt_Fill3DRectangle(hboxPtr->tkwin, drawable, hboxPtr->border, 0, 0,
          Tk_Width(hboxPtr->tkwin), Tk_Height(hboxPtr->tkwin), 0,
          TK_RELIEF_FLAT);
    }

    if (hboxPtr->nVisible > 0) {
      register Tree **treePtrPtr;

      if (hboxPtr->activePtr != NULL) {
          int y, height;

          y = SCREENY(hboxPtr, hboxPtr->activePtr->entryPtr->worldY);
          height = MAX(hboxPtr->activePtr->entryPtr->iconHeight, 
            hboxPtr->activePtr->entryPtr->labelHeight);
          Blt_Fill3DRectangle(hboxPtr->tkwin, drawable, hboxPtr->activeBorder, 
             0, y, Tk_Width(hboxPtr->tkwin), height, 0, TK_RELIEF_FLAT);
      }
      if (hboxPtr->lineWidth > 0) {
          DrawVerticals(hboxPtr, hboxPtr->visibleArr[0], drawable);
      }
      for (treePtrPtr = hboxPtr->visibleArr; *treePtrPtr != NULL;
          treePtrPtr++) {
          DrawEntry(hboxPtr, *treePtrPtr, drawable);
      }
    }
    DrawOuterBorders(hboxPtr, drawable);
    /* Now copy the new view to the window. */
    XCopyArea(hboxPtr->display, drawable, Tk_WindowId(hboxPtr->tkwin),
      hboxPtr->lineGC, 0, 0, Tk_Width(hboxPtr->tkwin),
      Tk_Height(hboxPtr->tkwin), 0, 0);
    Tk_FreePixmap(hboxPtr->display, drawable);
}

/*
 *----------------------------------------------------------------------
 *
 * SelectCmdProc --
 *
 *      Invoked at the next idle point whenever the current
 *      selection changes.  Executes some application-specific code
 *      in the -selectcommand option.  This provides a way for
 *      applications to handle selection changes.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      Tcl code gets executed for some application-specific task.
 *
 *----------------------------------------------------------------------
 */
static void
SelectCmdProc(clientData)
    ClientData clientData;    /* Information about widget. */
{
    Hierbox *hboxPtr = clientData;

    /*
     * Preserve the widget structure in case the "select" callback
     * destroys it.
     */
    Tcl_Preserve(hboxPtr);
    if (hboxPtr->selectCmd != NULL) {
      hboxPtr->flags &= ~SELECTION_PENDING;
      if (Tcl_GlobalEval(hboxPtr->interp, hboxPtr->selectCmd) != TCL_OK) {
          Tcl_BackgroundError(hboxPtr->interp);
      }
    }
    Tcl_Release(hboxPtr);
}

/*
 * --------------------------------------------------------------
 *
 * HierboxCmd --
 *
 *    This procedure is invoked to process the Tcl command that
 *    corresponds to a widget managed by this module. See the user
 *    documentation for details on what it does.
 *
 * Results:
 *    A standard Tcl result.
 *
 * Side effects:
 *    See the user documentation.
 *
 * --------------------------------------------------------------
 */
/* ARGSUSED */
static int
HierboxCmd(clientData, interp, argc, argv)
    ClientData clientData;    /* Main window associated with interpreter. */
    Tcl_Interp *interp;       /* Current interpreter. */
    int argc;                 /* Number of arguments. */
    char **argv;        /* Argument strings. */
{
    Hierbox *hboxPtr;
    Tk_Window tkwin;
    Tree *treePtr;
    Tcl_CmdInfo cmdInfo;

    if (argc < 2) {
      Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
          " pathName ?option value?...\"", (char *)NULL);
      return TCL_ERROR;
    }
    tkwin = Tk_CreateWindowFromPath(interp, Tk_MainWindow(interp), argv[1],
      (char *)NULL);
    if (tkwin == NULL) {
      return TCL_ERROR;
    }
    hboxPtr = CreateHierbox(interp, tkwin);

    if (Blt_ConfigureWidgetComponent(interp, tkwin, "button", "Button",
          buttonConfigSpecs, 0, (char **)NULL, (char *)hboxPtr, 0) != TCL_OK) {
      goto error;
    }
    if (ConfigureHierbox(interp, hboxPtr, argc - 2, argv + 2, 0) != TCL_OK) {
      goto error;
    }
    treePtr = CreateNode(hboxPtr, (Tree *) NULL, APPEND, hboxPtr->separator);
    if (treePtr == NULL) {
      goto error;
    }
    hboxPtr->rootPtr = hboxPtr->focusPtr = treePtr;
    hboxPtr->selAnchorPtr = NULL;
    Blt_SetFocusItem(hboxPtr->bindTable, hboxPtr->focusPtr, NULL);

    Tk_CreateSelHandler(tkwin, XA_PRIMARY, XA_STRING, SelectionProc, hboxPtr, 
      XA_STRING);
    Tk_CreateEventHandler(tkwin, ExposureMask | StructureNotifyMask |
      FocusChangeMask, HierboxEventProc, hboxPtr);

    hboxPtr->cmdToken = Tcl_CreateCommand(interp, argv[1], HierboxInstCmd,
        hboxPtr, HierboxInstCmdDeleteProc);
#ifdef ITCL_NAMESPACES
    Itk_SetWidgetCommand(hboxPtr->tkwin, hboxPtr->cmdToken);
#endif

    /*
     * Invoke a procedure to initialize various bindings on hierbox
     * entries.  If the procedure doesn't already exist, source it
     * from "$blt_library/bltHierbox.tcl".  We deferred sourcing the file
     * until now so that the variable $blt_library could be set within a
     * script.
     */
    if (!Tcl_GetCommandInfo(interp, "blt::Hierbox::Init", &cmdInfo)) {
      static char initCmd[] =
      {
          "source [file join $blt_library hierbox.tcl]"
      };
      if (Tcl_GlobalEval(interp, initCmd) != TCL_OK) {
          char info[200];

          sprintf(info, "\n    (while loading bindings for %s)", argv[0]);
          Tcl_AddErrorInfo(interp, info);
          goto error;
      }
    }
    if (Tcl_VarEval(interp, "blt::Hierbox::Init ", argv[1], (char *)NULL)
      != TCL_OK) {
      goto error;
    }
    treePtr->entryPtr->flags = (ENTRY_MAPPED);
    if (OpenNode(hboxPtr, treePtr) != TCL_OK) {
      goto error;
    }
    Tcl_SetResult(interp, Tk_PathName(hboxPtr->tkwin), TCL_VOLATILE);
    return TCL_OK;
  error:
    Tk_DestroyWindow(tkwin);
    return TCL_ERROR;
}

/*
 * --------------------------------------------------------------
 *
 * Hierbox operations
 *
 * --------------------------------------------------------------
 */

/*ARGSUSED*/
static int
FocusOp(hboxPtr, interp, argc, argv)
    Hierbox *hboxPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    if (argc == 3) {
      Tree *treePtr;

      treePtr = hboxPtr->focusPtr;
      if (GetNode(hboxPtr, argv[2], &treePtr) != TCL_OK) {
          return TCL_ERROR;
      }
      if ((treePtr != NULL) && (treePtr != hboxPtr->focusPtr)) {
          if (IsHidden(treePtr)) {
            /* Doesn't make sense to set focus to a node you can't see. */
            ExposeAncestors(treePtr);
          }
          hboxPtr->flags |= (HIERBOX_LAYOUT | HIERBOX_SCROLL);
          hboxPtr->focusPtr = treePtr;
          hboxPtr->labelEdit.insertPos = strlen(treePtr->entryPtr->labelText);
      }
      EventuallyRedraw(hboxPtr);
    }
    Blt_SetFocusItem(hboxPtr->bindTable, hboxPtr->focusPtr, NULL);
    if (hboxPtr->focusPtr != NULL) {
      Tcl_SetResult(interp, NodeToString(hboxPtr, hboxPtr->focusPtr),
          TCL_VOLATILE);
    }
    return TCL_OK;
}

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

    if (hboxPtr->flags & HIERBOX_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).
       */
      ComputeLayout(hboxPtr);
    }
    left = hboxPtr->worldWidth;
    top = hboxPtr->worldHeight;
    right = bottom = 0;

    screen = FALSE;
    if ((argc > 2) && (argv[2][0] == '-') && 
      (strcmp(argv[2], "-screen") == 0)) {
      screen = TRUE;
      argc--, argv++;
    }
    for (i = 2; i < argc; i++) {
      if ((argv[i][0] == 'a') && (strcmp(argv[i], "all") == 0)) {
          left = top = 0;
          right = hboxPtr->worldWidth;
          bottom = hboxPtr->worldHeight;
          break;
      }
      treePtr = hboxPtr->focusPtr;
      if (GetNode(hboxPtr, argv[i], &treePtr) != TCL_OK) {
          return TCL_ERROR;
      }
      if ((treePtr == NULL) || (IsHidden(treePtr))) {
          continue;
      }
      entryPtr = treePtr->entryPtr;
      yBot = entryPtr->worldY + entryPtr->height;
      height = VPORTHEIGHT(hboxPtr);
      if ((yBot <= hboxPtr->yOffset) &&
          (entryPtr->worldY >= (hboxPtr->yOffset + height))) {
          continue;
      }
      if (bottom < yBot) {
          bottom = yBot;
      }
      if (top > entryPtr->worldY) {
          top = entryPtr->worldY;
      }
      if (right <
          (entryPtr->worldX + entryPtr->width + LEVELWIDTH(treePtr->level))) {
          right = (entryPtr->worldX + entryPtr->width +
            LEVELWIDTH(treePtr->level));
      }
      if (left > entryPtr->worldX) {
          left = entryPtr->worldX;
      }
    }

    if (screen) {
      width = VPORTWIDTH(hboxPtr);
      height = VPORTHEIGHT(hboxPtr);

      /*
       * 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 < hboxPtr->xOffset) || (bottom < hboxPtr->yOffset) ||
          (left >= (hboxPtr->xOffset + width)) ||
          (top >= (hboxPtr->yOffset + height))) {
          return TCL_OK;
      }
      /* Otherwise clip the coordinates at the view port boundaries. */
      if (left < hboxPtr->xOffset) {
          left = hboxPtr->xOffset;
      } else if (right > (hboxPtr->xOffset + width)) {
          right = hboxPtr->xOffset + width;
      }
      if (top < hboxPtr->yOffset) {
          top = hboxPtr->yOffset;
      } else if (bottom > (hboxPtr->yOffset + height)) {
          bottom = hboxPtr->yOffset + height;
      }
      left = SCREENX(hboxPtr, left), top = SCREENY(hboxPtr, top);
      right = SCREENX(hboxPtr, right), bottom = SCREENY(hboxPtr, bottom);
    }
    if ((left < right) && (top < bottom)) {
      sprintf(string, "%d %d %d %d", left, top, right - left, bottom - top);
      Tcl_SetResult(interp, string, TCL_VOLATILE);
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ButtonActivateOp --
 *
 *    Selects the button to appear active.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
ButtonActivateOp(hboxPtr, interp, argc, argv)
    Hierbox *hboxPtr;
    Tcl_Interp *interp;
    int argc;                 /* Not used. */
    char **argv;
{
    Tree *treePtr, *oldPtr;

    treePtr = hboxPtr->focusPtr;
    if (argv[3][0] == '\0') {
      treePtr = NULL;
    } else if (GetNode(hboxPtr, argv[3], &treePtr) != TCL_OK) {
      return TCL_ERROR;
    }
    oldPtr = hboxPtr->activeButtonPtr;
    hboxPtr->activeButtonPtr = treePtr;
    if (treePtr != oldPtr) {
      Drawable drawable;

      drawable = Tk_WindowId(hboxPtr->tkwin);
      if (oldPtr != NULL) {
          DrawButton(hboxPtr, oldPtr, drawable);
      }
      if (treePtr != NULL) {
          DrawButton(hboxPtr, treePtr, drawable);
      }
      DrawOuterBorders(hboxPtr, drawable);
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ButtonBindOp --
 *
 *      .t bind tag sequence command
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
ButtonBindOp(hboxPtr, interp, argc, argv)
    Hierbox *hboxPtr;
    Tcl_Interp *interp;
    int argc;                 /* Not used. */
    char **argv;
{
    ClientData object;
    /*
     * Individual entries are selected by inode only.  All other
     * strings are interpreted as a binding tag.  For example, if one
     * binds to "focus", it is assumed that this refers to a bind tag,
     * not the entry with focus.
     */
    object = GetNodeByIndex(hboxPtr, argv[3]);
    if (object == 0) {
      object = (ClientData)Tk_GetUid(argv[3]);
    }
    return Blt_ConfigureBindings(interp, hboxPtr->buttonBindTable, object,
      argc - 4, argv + 4);
}

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

/*
 *----------------------------------------------------------------------
 *
 * ButtonConfigureOp --
 *
 *    This procedure is called to process a list of configuration
 *    options database, in order to reconfigure the one of more
 *    entries in the widget.
 *
 *      .h 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 hboxPtr; old resources get freed, if there
 *    were any.  The hypertext is redisplayed.
 *
 *----------------------------------------------------------------------
 */
static int
ButtonConfigureOp(hboxPtr, interp, argc, argv)
    Hierbox *hboxPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    /* Figure out where the option value pairs begin */
    argc -= 3;
    argv += 3;

    if (argc == 0) {
      return Tk_ConfigureInfo(interp, hboxPtr->tkwin, buttonConfigSpecs,
          (char *)hboxPtr, (char *)NULL, 0);
    } else if (argc == 1) {
      return Tk_ConfigureInfo(interp, hboxPtr->tkwin, buttonConfigSpecs,
          (char *)hboxPtr, argv[0], 0);
    }
    if (Tk_ConfigureWidget(hboxPtr->interp, hboxPtr->tkwin, buttonConfigSpecs,
          argc, argv, (char *)hboxPtr, TK_CONFIG_ARGV_ONLY) != TCL_OK) {
      return TCL_ERROR;
    }
    ConfigureButtons(hboxPtr);
    EventuallyRedraw(hboxPtr);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ButtonOp --
 *
 *    This procedure handles button operations.
 *
 * Results:
 *    A standard Tcl result.
 *
 *----------------------------------------------------------------------
 */
static Blt_OpSpec buttonOperSpecs[] =
{
    {"activate", 1, (Blt_Op)ButtonActivateOp, 4, 4, "node",},
    {"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, "node",},
};

static int nButtonSpecs = sizeof(buttonOperSpecs) / sizeof(Blt_OpSpec);

static int
ButtonOp(hboxPtr, interp, argc, argv)
    Hierbox *hboxPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    Blt_Op proc;
    int result;

    proc = Blt_GetOp(interp, nButtonSpecs, buttonOperSpecs, BLT_OP_ARG2, argc,
       argv, 0);
    if (proc == NULL) {
      return TCL_ERROR;
    }
    result = (*proc) (hboxPtr, interp, argc, argv);
    return result;
}

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

/*ARGSUSED*/
static int
CloseOp(hboxPtr, interp, argc, argv)
    Hierbox *hboxPtr;
    Tcl_Interp *interp;       /* Not used. */
    int argc;
    char **argv;
{
    Tree *rootPtr;
    unsigned int flags;
    register int i;

    flags = 0;
    if (argc > 2) {
      int length;

      length = strlen(argv[2]);
      if ((argv[2][0] == '-') && (length > 1) &&
          (strncmp(argv[2], "-recurse", length) == 0)) {
          argv++, argc--;
          flags |= APPLY_RECURSE;
      }
    }
    for (i = 2; i < argc; i++) {
      rootPtr = hboxPtr->focusPtr;
      if (GetNode(hboxPtr, argv[i], &rootPtr) != TCL_OK) {
          return TCL_ERROR;
      }
      if (rootPtr == NULL) {
          continue;
      }
      /*
       * Clear any selected entries that may become hidden by
       * closing the node.
       */
      PruneSelection(hboxPtr, rootPtr);

      /*
       * -----------------------------------------------------------
       *
       *  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 (IsAncestor(rootPtr, hboxPtr->focusPtr)) {
          hboxPtr->focusPtr = rootPtr;
          Blt_SetFocusItem(hboxPtr->bindTable, hboxPtr->focusPtr, NULL);
      }
      if (IsAncestor(rootPtr, hboxPtr->selAnchorPtr)) {
          hboxPtr->selAnchorPtr = NULL;
      }
      if (IsAncestor(rootPtr, hboxPtr->activePtr)) {
          hboxPtr->activePtr = rootPtr;
      }
      if (ApplyToTree(hboxPtr, rootPtr, CloseNode, flags) != TCL_OK) {
          return TCL_ERROR;
      }
    }
    hboxPtr->flags |= (HIERBOX_LAYOUT | HIERBOX_SCROLL);
    EventuallyRedraw(hboxPtr);
    return TCL_OK;
}

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

/*ARGSUSED*/
static int
CurselectionOp(hboxPtr, interp, argc, argv)
    Hierbox *hboxPtr;
    Tcl_Interp *interp;       /* Not used. */
    int argc;                 /* Not used. */
    char **argv;        /* Not used. */
{
    if (hboxPtr->sortSelection) {
      ApplyToTree(hboxPtr, hboxPtr->rootPtr, IsSelectedNode,
                APPLY_RECURSE | APPLY_OPEN_ONLY | APPLY_BEFORE);
    } else {
      Blt_ChainLink *linkPtr;
      Tree *treePtr;

      for (linkPtr = Blt_ChainFirstLink(&(hboxPtr->selectChain)); 
           linkPtr != NULL; linkPtr = Blt_ChainNextLink(linkPtr)) {
          treePtr = Blt_ChainGetValue(linkPtr);
          Tcl_AppendElement(interp, NodeToString(hboxPtr, treePtr));
      }
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ActivateOpOp --
 *
 *    Selects the tab to appear active.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
ActivateOpOp(hboxPtr, interp, argc, argv)
    Hierbox *hboxPtr;
    Tcl_Interp *interp;
    int argc;                 /* Not used. */
    char **argv;
{
    Tree *treePtr, *oldPtr;

    treePtr = hboxPtr->focusPtr;
    if (argv[3][0] == '\0') {
      treePtr = NULL;
    } else if (GetNode(hboxPtr, argv[3], &treePtr) != TCL_OK) {
      return TCL_ERROR;
    }
    oldPtr = hboxPtr->activePtr;
    hboxPtr->activePtr = treePtr;
    if (treePtr != oldPtr) {
      EventuallyRedraw(hboxPtr);
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * BindOp --
 *
 *      .t bind index sequence command
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
BindOp(hboxPtr, interp, argc, argv)
    Hierbox *hboxPtr;
    Tcl_Interp *interp;
    int argc;                 /* Not used. */
    char **argv;
{
    ClientData item;

    /*
     * Individual entries are selected by inode only.  All other strings
     * are interpreted as a binding tag.
     */
    item = GetNodeByIndex(hboxPtr, argv[2]);
    if (item == 0) {
      item = (ClientData)Tk_GetUid(argv[2]);
    }
    return Blt_ConfigureBindings(interp, hboxPtr->bindTable, item, argc - 3, 
      argv + 3);
}

/*
 *----------------------------------------------------------------------
 *
 * CgetOpOp --
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
CgetOpOp(hboxPtr, interp, argc, argv)
    Hierbox *hboxPtr;
    Tcl_Interp *interp;
    int argc;                 /* Not used. */
    char **argv;
{
    Tree *treePtr;

    if (StringToNode(hboxPtr, argv[3], &treePtr) != TCL_OK) {
      return TCL_ERROR;
    }
    return Tk_ConfigureValue(interp, hboxPtr->tkwin, entryConfigSpecs,
      (char *)treePtr->entryPtr, argv[4], 0);
}

/*
 *----------------------------------------------------------------------
 *
 * ConfigureOpOp --
 *
 *    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 hboxPtr; old resources get freed, if there
 *    were any.  The hypertext is redisplayed.
 *
 *----------------------------------------------------------------------
 */
static int
ConfigureOpOp(hboxPtr, interp, argc, argv)
    Hierbox *hboxPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    int nIds, nOpts;
    char **options;
    register int i;
    Tree *treePtr;


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

    for (i = 0; i < nIds; i++) {
      StringToNode(hboxPtr, argv[i], &treePtr);
      if (argc == 1) {
          return Tk_ConfigureInfo(interp, hboxPtr->tkwin, entryConfigSpecs,
            (char *)treePtr->entryPtr, (char *)NULL, 0);
      } else if (argc == 2) {
          return Tk_ConfigureInfo(interp, hboxPtr->tkwin, entryConfigSpecs,
            (char *)treePtr->entryPtr, argv[2], 0);
      }
      if (ConfigureEntry(hboxPtr, treePtr->entryPtr, nOpts, options,
            TK_CONFIG_ARGV_ONLY) != TCL_OK) {
          return TCL_ERROR;
      }
    }
    hboxPtr->flags |= (HIERBOX_LAYOUT | HIERBOX_SCROLL);
    EventuallyRedraw(hboxPtr);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * IsHiddenOpOp --
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
IsHiddenOpOp(hboxPtr, interp, argc, argv)
    Hierbox *hboxPtr;
    Tcl_Interp *interp;
    int argc;                 /* Not used. */
    char **argv;
{
    Tree *treePtr;

    if (StringToNode(hboxPtr, argv[3], &treePtr) != TCL_OK) {
      return TCL_ERROR;
    }
    Blt_SetBooleanResult(interp, IsHidden(treePtr));
    return TCL_OK;
}

/*ARGSUSED*/
static int
IsBeforeOpOp(hboxPtr, interp, argc, argv)
    Hierbox *hboxPtr;
    Tcl_Interp *interp;
    int argc;                 /* Not used. */
    char **argv;
{
    Tree *n1Ptr, *n2Ptr;

    if ((StringToNode(hboxPtr, argv[3], &n1Ptr) != TCL_OK) ||
      (StringToNode(hboxPtr, argv[4], &n2Ptr) != TCL_OK)) {
      return TCL_ERROR;
    }
    Blt_SetBooleanResult(interp, IsBefore(n1Ptr, n2Ptr));
    return TCL_OK;
}


static int
ScreenToIndex(hboxPtr, x, y)
    Hierbox *hboxPtr;
    int x, y;
{
    Tk_Font font;
    TextStyle ts;
    TextLayout *textPtr;
    Tk_FontMetrics fontMetrics;
    TextFragment *fragPtr;
    int nBytes;
    Entry *entryPtr;
    Tree *treePtr;
    int lineNum;
    register int i;

    treePtr = hboxPtr->focusPtr;
    entryPtr = treePtr->entryPtr;

    if (*entryPtr->labelText == '\0') {
      return 0;
    }
    /*
     * Compute the X-Y offsets of the screen point from the start of
     * label.  Force the offsets to point within the label.
     */
    x -= SCREENX(hboxPtr, entryPtr->worldX) + LABEL_PADX +
      hboxPtr->selBorderWidth;
    y -= SCREENY(hboxPtr, entryPtr->worldY) + LABEL_PADY +
      hboxPtr->selBorderWidth;
    x -= LEVELWIDTH(treePtr->level) + LEVELWIDTH(treePtr->level + 1) + 4;

    font = GETFONT(hboxPtr, entryPtr->labelFont);
    memset(&ts, 0, sizeof(TextStyle));
    ts.font = font;
    ts.justify = TK_JUSTIFY_LEFT;
    ts.shadow.offset = entryPtr->labelShadow.offset;
    textPtr = Blt_GetTextLayout(entryPtr->labelText, &ts);

    if (y < 0) {
      y = 0;
    } else if (y >= textPtr->height) {
      y = textPtr->height - 1;
    }
    Tk_GetFontMetrics(font, &fontMetrics);
    lineNum = y / fontMetrics.linespace;
    fragPtr = textPtr->fragArr + lineNum;

    if (x < 0) {
      nBytes = 0;
    } else if (x >= textPtr->width) {
      nBytes = fragPtr->count;
    } else {
      int newX;
      /* Find the character underneath the pointer. */
      nBytes = Tk_MeasureChars(font, fragPtr->text, fragPtr->count, x, 0,
          &newX);
      if ((newX < x) && (nBytes < fragPtr->count)) {
          double fract;
          int length, charSize;
          char *next;

          next = fragPtr->text + nBytes;
#if HAVE_UTF
          {
            Tcl_UniChar dummy;

            length = Tcl_UtfToUniChar(next, &dummy);
          }
#else
          length = 1;
#endif
          charSize = Tk_TextWidth(font, next, length);
          fract = ((double)(x - newX) / (double)charSize);
          if (ROUND(fract)) {
            nBytes += length;
          }
      }
    }
    for (i = lineNum - 1; i >= 0; i--) {
      fragPtr--;
      nBytes += fragPtr->count + 1;
    }
    Blt_Free(textPtr);
    return nBytes;
}

/*
 *---------------------------------------------------------------------------
 *
 * GetLabelIndex --
 *
 *    Parse an index into an entry and return either its value
 *    or an error.
 *
 * Results:
 *    A standard Tcl result.  If all went well, then *indexPtr is
 *    filled in with the character index (into entryPtr) corresponding to
 *    string.  The index value is guaranteed to lie between 0 and
 *    the number of characters in the string, inclusive.  If an
 *    error occurs then an error message is left in the interp's result.
 *
 * Side effects:
 *    None.
 *
 *---------------------------------------------------------------------------
 */
static int
GetLabelIndex(hboxPtr, entryPtr, string, indexPtr)
    Hierbox *hboxPtr;
    Entry *entryPtr;
    char *string;
    int *indexPtr;
{
    Tcl_Interp *interp = hboxPtr->interp;
    TextEdit *editPtr = &(hboxPtr->labelEdit);
    char c;

    c = string[0];
    if ((c == 'a') && (strcmp(string, "anchor") == 0)) {
      *indexPtr = editPtr->selAnchor;
    } else if ((c == 'e') && (strcmp(string, "end") == 0)) {
      *indexPtr = strlen(entryPtr->labelText);
    } else if ((c == 'i') && (strcmp(string, "insert") == 0)) {
      *indexPtr = editPtr->insertPos;
    } else if ((c == 's') && (strcmp(string, "sel.first") == 0)) {
      if (editPtr->selFirst < 0) {
          Tcl_AppendResult(interp, "nothing is selected", (char *)NULL);
          return TCL_ERROR;
      }
      *indexPtr = editPtr->selFirst;
    } else if ((c == 's') && (strcmp(string, "sel.last") == 0)) {
      if (editPtr->selLast < 0) {
          Tcl_AppendResult(interp, "nothing is selected", (char *)NULL);
          return TCL_ERROR;
      }
      *indexPtr = editPtr->selLast;
    } else if (c == '@') {
      int x, y;

      if (Blt_GetXY(interp, hboxPtr->tkwin, string, &x, &y) != TCL_OK) {
          return TCL_ERROR;
      }
      *indexPtr = ScreenToIndex(hboxPtr, x, y);
    } else if (isdigit((int)c)) {
      int number;
      int maxChars;

      if (Tcl_GetInt(interp, string, &number) != TCL_OK) {
          return TCL_ERROR;
      }
      /* Don't allow the index to point outside the label. */
      maxChars = Tcl_NumUtfChars(entryPtr->labelText, -1);
      if (number < 0) {
          *indexPtr = 0;
      } else if (number > maxChars) {
          *indexPtr = strlen(entryPtr->labelText);
      } else {
          *indexPtr = Tcl_UtfAtIndex(entryPtr->labelText, number) -
            entryPtr->labelText;
      }
    } else {
      Tcl_AppendResult(interp, "bad label index \"", string, "\"",
          (char *)NULL);
      return TCL_ERROR;
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * IndexOpOp --
 *
 *    Returns the numeric index of the given string. Indices can be
 *    one of the following:
 *
 *    "anchor"    Selection anchor.
 *    "end"       End of the label.
 *    "insert"    Insertion cursor.
 *    "sel.first" First character selected.
 *    "sel.last"  Last character selected.
 *    @x,y        Index at X-Y screen coordinate.
 *    number            Returns the same number.
 *
 * Results:
 *    A standard Tcl result.  If the argument does not represent a
 *    valid label index, then TCL_ERROR is returned and the interpreter
 *    result will contain an error message.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
IndexOpOp(hboxPtr, interp, argc, argv)
    Hierbox *hboxPtr;
    Tcl_Interp *interp;
    int argc;                 /* Not used. */
    char **argv;
{
    Entry *entryPtr;
    int nBytes, nChars;

    entryPtr = hboxPtr->focusPtr->entryPtr;
    if (GetLabelIndex(hboxPtr, entryPtr, argv[3], &nBytes) != TCL_OK) {
      return TCL_ERROR;
    }
    nChars = Tcl_NumUtfChars(entryPtr->labelText, nBytes);
    Tcl_SetResult(interp, Blt_Itoa(nChars), TCL_VOLATILE);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * InsertOpOp --
 *
 *    Add new characters to the label of an entry.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    New information gets added to editPtr;  it will be redisplayed
 *    soon, but not necessarily immediately.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
InsertOpOp(hboxPtr, interp, argc, argv)
    Hierbox *hboxPtr;
    Tcl_Interp *interp;       /* Not used. */
    int argc;
    char **argv;
{
    Tree *treePtr;
    Entry *entryPtr;
    TextEdit *editPtr = &(hboxPtr->labelEdit);
    int oldSize, newSize;
    char *string;
    int extra;
    int insertPos;

    if (!hboxPtr->focusEdit) {
      return TCL_OK;          /* Not in edit mode. */
    }
    if (StringToNode(hboxPtr, argv[3], &treePtr) != TCL_OK) {
      return TCL_ERROR;
    }
    if (treePtr == NULL) {
      return TCL_OK;          /* Not in edit mode. */
    }
    entryPtr = treePtr->entryPtr;
    if (hboxPtr->focusPtr != treePtr) {
      hboxPtr->focusPtr = treePtr;
      editPtr->insertPos = strlen(entryPtr->labelText);
      editPtr->selAnchor = editPtr->selFirst = editPtr->selLast = -1;
    }
    if (GetLabelIndex(hboxPtr, entryPtr, argv[4], &insertPos) != TCL_OK) {
      return TCL_ERROR;
    }
    extra = strlen(argv[5]);
    if (extra == 0) {
      /* Nothing to insert. Move the cursor anyways. */
      editPtr->insertPos = insertPos;
      EventuallyRedraw(hboxPtr);
      return TCL_OK;
    }
    oldSize = strlen(entryPtr->labelText);
    newSize = oldSize + extra;
    string = Blt_Malloc(sizeof(char) * (newSize + 1));

    if (insertPos == oldSize) {     /* Append */
      strcpy(string, entryPtr->labelText);
      strcat(string, argv[5]);
    } else if (insertPos == 0) {/* Prepend */
      strcpy(string, argv[5]);
      strcat(string, entryPtr->labelText);
    } else {                  /* Insert into existing. */
      char *left, *right;
      char *p;

      left = entryPtr->labelText;
      right = left + insertPos;
      p = string;
      strncpy(p, left, insertPos);
      p += insertPos;
      strcpy(p, argv[5]);
      p += extra;
      strcpy(p, right);
    }
    /*
     * All indices from the start of the insertion to the end of the
     * string need to be updated.  Simply move the indices down by the
     * number of characters added.
     */
    if (editPtr->selFirst >= insertPos) {
      editPtr->selFirst += extra;
    }
    if (editPtr->selLast > insertPos) {
      editPtr->selLast += extra;
    }
    if ((editPtr->selAnchor > insertPos) || (editPtr->selFirst >= insertPos)) {
      editPtr->selAnchor += extra;
    }
    Blt_Free(entryPtr->labelText);
    entryPtr->labelText = string;

    editPtr->insertPos = insertPos + extra;
    GetCursorLocation(hboxPtr, treePtr);
    hboxPtr->flags |= (HIERBOX_LAYOUT | HIERBOX_SCROLL);
    EventuallyRedraw(hboxPtr);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * DeleteOpOp --
 *
 *    Remove one or more characters from the label of an entry.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    Memory gets freed, the entry gets modified and (eventually)
 *    redisplayed.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
DeleteOpOp(hboxPtr, interp, argc, argv)
    Hierbox *hboxPtr;
    Tcl_Interp *interp;       /* Not used. */
    int argc;
    char **argv;
{
    TextEdit *editPtr = &(hboxPtr->labelEdit);
    Tree *treePtr;
    Entry *entryPtr;
    int oldSize, newSize;
    int nDeleted;
    int first, last;
    char *string;
    char *p;

    if (!hboxPtr->focusEdit) {
      return TCL_OK;          /* Not in edit mode. */
    }
    if (StringToNode(hboxPtr, argv[3], &treePtr) != TCL_OK) {
      return TCL_ERROR;
    }
    if (treePtr == NULL) {
      return TCL_OK;          /* Not in edit mode. */
    }
    entryPtr = treePtr->entryPtr;
    if (hboxPtr->focusPtr != treePtr) {
      hboxPtr->focusPtr = treePtr;
      editPtr->insertPos = strlen(entryPtr->labelText);
      editPtr->selAnchor = editPtr->selFirst = editPtr->selLast = -1;
    }
    if ((GetLabelIndex(hboxPtr, entryPtr, argv[4], &first) != TCL_OK) ||
      (GetLabelIndex(hboxPtr, entryPtr, argv[5], &last) != TCL_OK)) {
      return TCL_ERROR;
    }
    if (first >= last) {
      return TCL_OK;
    }
    if ((!hboxPtr->focusEdit) || (entryPtr == NULL)) {
      return TCL_OK;          /* Not in edit mode. */
    }
    oldSize = strlen(entryPtr->labelText);
    newSize = oldSize - (last - first);
    p = string = Blt_Malloc(sizeof(char) * (newSize + 1));
    strncpy(p, entryPtr->labelText, first);
    p += first;
    strcpy(p, entryPtr->labelText + last);

    Blt_Free(entryPtr->labelText);
    entryPtr->labelText = string;
    nDeleted = last - first + 1;

    /*
     * Since deleting characters compacts the character array, we need to
     * update the various character indices according.  It depends where
     * the index occurs in relation to range of deleted characters:
     *
     *            before            Ignore.
     *            within            Move the index back to the start of the deletion.
     *            after       Subtract off the deleted number of characters,
     *                        since the array has been compressed by that
     *                        many characters.
     *
     */
    if (editPtr->selFirst >= first) {
      if (editPtr->selFirst >= last) {
          editPtr->selFirst -= nDeleted;
      } else {
          editPtr->selFirst = first;
      }
    }
    if (editPtr->selLast >= first) {
      if (editPtr->selLast >= last) {
          editPtr->selLast -= nDeleted;
      } else {
          editPtr->selLast = first;
      }
    }
    if (editPtr->selLast <= editPtr->selFirst) {
      editPtr->selFirst = editPtr->selLast = -1;      /* Cut away the entire
                                          * selection. */
    }
    if (editPtr->selAnchor >= first) {
      if (editPtr->selAnchor >= last) {
          editPtr->selAnchor -= nDeleted;
      } else {
          editPtr->selAnchor = first;
      }
    }
    if (editPtr->insertPos >= first) {
      if (editPtr->insertPos >= last) {
          editPtr->insertPos -= nDeleted;
      } else {
          editPtr->insertPos = first;
      }
    }
    hboxPtr->flags |= (HIERBOX_LAYOUT | HIERBOX_SCROLL);
    EventuallyRedraw(hboxPtr);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * IsOpenOpOp --
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
IsOpenOpOp(hboxPtr, interp, argc, argv)
    Hierbox *hboxPtr;
    Tcl_Interp *interp;
    int argc;                 /* Not used. */
    char **argv;
{
    Tree *treePtr;

    if (StringToNode(hboxPtr, argv[3], &treePtr) != TCL_OK) {
      return TCL_ERROR;
    }
    Blt_SetBooleanResult(interp, (treePtr->entryPtr->flags & ENTRY_OPEN));
    return TCL_OK;
}

/*ARGSUSED*/
static int
ChildrenOpOp(hboxPtr, interp, argc, argv)
    Hierbox *hboxPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    Tree *parentPtr, *nodePtr;

    if (StringToNode(hboxPtr, argv[3], &parentPtr) != TCL_OK) {
      return TCL_ERROR;
    }
    if (argc == 4) {
      Blt_ChainLink *linkPtr;

      for (linkPtr = Blt_ChainFirstLink(parentPtr->chainPtr); linkPtr != NULL;
          linkPtr = Blt_ChainNextLink(linkPtr)) {
          nodePtr = Blt_ChainGetValue(linkPtr);
          Tcl_AppendElement(interp, NodeToString(hboxPtr, nodePtr));
      }
    } else if (argc == 6) {
      Blt_ChainLink *firstPtr, *lastPtr;
      int first, last;
      int nNodes;

      if ((Blt_GetPosition(interp, argv[4], &first) != TCL_OK) ||
          (Blt_GetPosition(interp, argv[5], &last) != TCL_OK)) {
          return TCL_ERROR;
      }
      nNodes = Blt_ChainGetLength(parentPtr->chainPtr);
      if (nNodes == 0) {
          return TCL_OK;
      }
      if ((last == -1) || (last >= nNodes)) {
          last = nNodes - 1;
      }
      if ((first == -1) || (first >= nNodes)) {
          first = nNodes - 1;
      } 
      firstPtr = Blt_ChainGetNthLink(parentPtr->chainPtr, first);
      lastPtr = Blt_ChainGetNthLink(parentPtr->chainPtr, last);
      if (first > last) {
          for ( /*empty*/ ; lastPtr != NULL;
            lastPtr = Blt_ChainPrevLink(lastPtr)) {
            nodePtr = Blt_ChainGetValue(lastPtr);
            Tcl_AppendElement(interp, NodeToString(hboxPtr, nodePtr));
            if (lastPtr == firstPtr) {
                break;
            }
          }
      } else {
          for ( /*empty*/ ; firstPtr != NULL;
            firstPtr = Blt_ChainNextLink(firstPtr)) {
            nodePtr = Blt_ChainGetValue(firstPtr);
            Tcl_AppendElement(interp, NodeToString(hboxPtr, nodePtr));
            if (firstPtr == lastPtr) {
                break;
            }
          }
      }
    } else {
      Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " ",
          argv[1], " ", argv[2], " index ?first last?", (char *)NULL);
      return TCL_ERROR;
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * SizeOpOp --
 *
 *    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
SizeOpOp(hboxPtr, interp, argc, argv)
    Hierbox *hboxPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    int length;
    Tree *rootPtr;
    int *sumPtr = (int *)&(hboxPtr->clientData);

    length = strlen(argv[3]);
    if ((argv[3][0] == '-') && (length > 1) &&
      (strncmp(argv[3], "-recurse", length) == 0)) {
      argv++, argc--;
    }
    if (argc == 3) {
      Tcl_AppendResult(interp, "missing node argument: should be \"",
          argv[0], " entry open node\"", (char *)NULL);
      return TCL_ERROR;
    }
    if (StringToNode(hboxPtr, argv[3], &rootPtr) != TCL_OK) {
      return TCL_ERROR;
    }
    *sumPtr = 0;
    if (ApplyToTree(hboxPtr, rootPtr, SizeOfNode, 0) != TCL_OK) {
      return TCL_ERROR;
    }
    Tcl_SetResult(interp, Blt_Itoa(*sumPtr), TCL_VOLATILE);
    return TCL_OK;
}

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

static Blt_OpSpec entryOperSpecs[] =
{
    {"activate", 1, (Blt_Op)ActivateOpOp, 4, 4, "node",},
    {"cget", 2, (Blt_Op)CgetOpOp, 5, 5, "node option",},
    {"children", 2, (Blt_Op)ChildrenOpOp, 4, 6, "node first last",},
    {"configure", 2, (Blt_Op)ConfigureOpOp, 4, 0,
      "node ?node...? ?option value?...",},
    {"delete", 1, (Blt_Op)DeleteOpOp, 6, 6, "node first last"},
    {"highlight", 1, (Blt_Op)ActivateOpOp, 4, 4, "node",},
    {"index", 3, (Blt_Op)IndexOpOp, 6, 6, "index"},
    {"insert", 3, (Blt_Op)InsertOpOp, 6, 6, "node index string"},
    {"isbefore", 3, (Blt_Op)IsBeforeOpOp, 5, 5, "node node",},
    {"ishidden", 3, (Blt_Op)IsHiddenOpOp, 4, 4, "node",},
    {"isopen", 3, (Blt_Op)IsOpenOpOp, 4, 4, "node",},
    {"size", 1, (Blt_Op)SizeOpOp, 4, 5, "?-recurse? node",},
};
static int nEntrySpecs = sizeof(entryOperSpecs) / sizeof(Blt_OpSpec);

static int
EntryOp(hboxPtr, interp, argc, argv)
    Hierbox *hboxPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    Blt_Op proc;
    int result;

    proc = Blt_GetOp(interp, nEntrySpecs, entryOperSpecs, BLT_OP_ARG2, argc, 
      argv, 0);
    if (proc == NULL) {
      return TCL_ERROR;
    }
    result = (*proc) (hboxPtr, interp, argc, argv);
    return result;
}

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

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

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

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

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

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

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

    if (i < argc) {
      if ((argv[i][0] == 'e') && (strcmp(argv[i], "end") == 0)) {
          firstPtr = EndNode(hboxPtr->rootPtr, 0);
      } else if (StringToNode(hboxPtr, argv[i], &firstPtr) != TCL_OK) {
          return TCL_ERROR;
      }
      i++;
    }
    if (i < argc) {
      if ((argv[i][0] == 'e') && (strcmp(argv[i], "end") == 0)) {
          lastPtr = EndNode(hboxPtr->rootPtr, 0);
      } else if (StringToNode(hboxPtr, argv[i], &lastPtr) != TCL_OK) {
          return TCL_ERROR;
      }
    }
    if (IsBefore(lastPtr, firstPtr)) {
      nextProc = LastNode;
    }
    nMatches = 0;

    /*
     * Step 3:    Search through the tree and look for nodes that match the
     *            current pattern specifications.  Save the name of each of
     *            the matching nodes.
     */
    Tcl_DStringInit(&dString);
    for (treePtr = firstPtr; treePtr != NULL;
      treePtr = (*nextProc) (treePtr, 0)) {
      if (namePattern != NULL) {
          result = (*compareProc) (interp, treePtr->nameId, namePattern);
          if (result == invertMatch) {
            goto nextNode;    /* Failed to match */
          }
      }
      if (fullPattern != NULL) {
          GetFullPath(treePtr, hboxPtr->separator, &pathString);
          result = (*compareProc) (interp, Tcl_DStringValue(&pathString),
            fullPattern);
          Tcl_DStringFree(&pathString);
          if (result == invertMatch) {
            goto nextNode;    /* Failed to match */
          }
      }
      for (node = Blt_ListFirstNode(optionList); node != NULL;
          node = Blt_ListNextNode(node)) {
          option = (char *)Blt_ListGetKey(node);
          Tcl_ResetResult(interp);
          if (Tk_ConfigureValue(interp, hboxPtr->tkwin, entryConfigSpecs,
                (char *)treePtr->entryPtr, option, 0) != TCL_OK) {
            goto error; /* This shouldn't happen. */
          }
          pattern = (char *)Blt_ListGetValue(node);
          value = (char *)Tcl_GetStringResult(interp);
          result = (*compareProc) (interp, value, pattern);
          if (result == invertMatch) {
            goto nextNode;    /* Failed to match */
          }
      }
      /*
       * As unlikely as it sounds, the "exec" callback may delete this
       * node. We need to preverse it until we're not using it anymore
       * (i.e. no longer accessing its fields).
       */
      Tcl_Preserve(treePtr);
      if (execCmd != NULL) {
          Tcl_DString cmdString;

          PercentSubst(hboxPtr, treePtr, execCmd, &cmdString);
          result = Tcl_GlobalEval(interp, Tcl_DStringValue(&cmdString));
          Tcl_DStringFree(&cmdString);
          if (result != TCL_OK) {
            Tcl_Release(treePtr);
            goto error;
          }
      }
      /* Finally, save the matching node name. */
      Tcl_DStringAppendElement(&dString, NodeToString(hboxPtr, treePtr));
      Tcl_Release(treePtr);
      nMatches++;
      if ((nMatches == maxMatches) && (maxMatches > 0)) {
          break;
      }
      nextNode:
      if (treePtr == lastPtr) {
          break;
      }
    }
    Tcl_ResetResult(interp);
    Blt_ListDestroy(optionList);
    Tcl_DStringResult(interp, &dString);
    return TCL_OK;

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

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

    fullName = FALSE;
    if ((argc > 2) && (argv[2][0] == '-') && (strcmp(argv[2], "-full") == 0)) {
      fullName = TRUE;
      argv++, argc--;
    }
    Tcl_DStringInit(&dString);
    Tcl_DStringInit(&pathString);
    for (i = 2; i < argc; i++) {
      treePtr = hboxPtr->focusPtr;
      if (GetNode(hboxPtr, argv[i], &treePtr) != TCL_OK) {
          return TCL_ERROR;
      }
      if (treePtr == NULL) {
          Tcl_DStringAppendElement(&dString, "");
          continue;
      }
      if (fullName) {
          GetFullPath(treePtr, hboxPtr->separator, &pathString);
          Tcl_DStringAppendElement(&dString, Tcl_DStringValue(&pathString));
      } else {
          Tcl_DStringAppendElement(&dString, treePtr->nameId);
      }
    }
    Tcl_DStringFree(&pathString);
    Tcl_DStringResult(interp, &dString);
    return TCL_OK;
}

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

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

    if ((namePattern != NULL) || (fullPattern != NULL) ||
      (Blt_ListGetLength(optionList) > 0)) {
      /*
       * Search through the tree and look for nodes that match the
       * current spec.  Apply the input procedure to each of the
       * matching nodes.
       */
      for (treePtr = hboxPtr->rootPtr; treePtr != NULL;
          treePtr = NextNode(treePtr, 0)) {

          if (namePattern != NULL) {
            result = (*compareProc) (interp, treePtr->nameId, namePattern);
            if (result == invertMatch) {
                continue;     /* Failed to match */
            }
          }
          if (fullPattern != NULL) {
            Tcl_DString dString;

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

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

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

}

/*
 *----------------------------------------------------------------------
 *
 * 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(hboxPtr, interp, argc, argv)
    Hierbox *hboxPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    int status, nonmatching;

    status = SearchAndApplyToTree(hboxPtr, interp, argc, argv, UnmapNode,
      &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) {
      ApplyToTree(hboxPtr, hboxPtr->rootPtr, MapAncestors, APPLY_RECURSE);
    }
    /*
     * Make sure that selections arme cleared from any hidden
     * nodes.  This wasn't done earlier--we had to delay it until
     * we fixed the visibility status for the parents.
     */
    ApplyToTree(hboxPtr, hboxPtr->rootPtr, FixUnmappedSelections,
      APPLY_RECURSE);

    hboxPtr->flags |= (HIERBOX_LAYOUT | HIERBOX_SCROLL);
    EventuallyRedraw(hboxPtr);
    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(hboxPtr, interp, argc, argv)
    Hierbox *hboxPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    if (SearchAndApplyToTree(hboxPtr, interp, argc, argv, MapNode,
          (int *)NULL) != TCL_OK) {
      return TCL_ERROR;
    }
    hboxPtr->flags |= (HIERBOX_LAYOUT | HIERBOX_SCROLL);
    EventuallyRedraw(hboxPtr);
    return TCL_OK;
}

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

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

/*
 *----------------------------------------------------------------------
 *
 * InsertOp --
 *
 *    Add new entries into a hierarchy.  If no node is specified,
 *    new entries will be added to the root of the hierarchy.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
InsertOp(hboxPtr, interp, argc, argv)
    Hierbox *hboxPtr;
    Tcl_Interp *interp;
    int argc;                 /* Not used. */
    char **argv;
{
    Tree *rootPtr, *nodePtr, *parentPtr;
    int position;
    int level, count;
    char *path;
    Tcl_DString dString;
    register int i, l;
    int nOpts;
    char **options;
    char **nameArr;

    rootPtr = hboxPtr->rootPtr;
    if ((argv[2][0] == '-') && (strcmp(argv[2], "-at") == 0)) {
      if (StringToNode(hboxPtr, argv[3], &rootPtr) != TCL_OK) {
          return TCL_ERROR;
      }
      argv += 2, argc -= 2;
    }
    if (Blt_GetPosition(hboxPtr->interp, argv[2], &position) != TCL_OK) {
      return TCL_ERROR;
    }
    argc -= 3, argv += 3;

    /*
     * Count the pathnames that follow.  Count the arguments until we
     * spot one that looks like a configuration option (i.e. starts
     * with a minus ("-")).
     */
    for (count = 0; count < argc; count++) {
      if (argv[count][0] == '-') {
          break;
      }
    }
    nOpts = argc - count;
    options = argv + count;

    Tcl_DStringInit(&dString);
    for (i = 0; i < count; i++) {
      path = argv[i];
      if (hboxPtr->trimLeft != NULL) {
          register char *p, *s;

          /* Trim off leading character string if one exists. */
          for (p = path, s = hboxPtr->trimLeft; *s != '\0'; s++, p++) {
            if (*p != *s) {
                break;
            }
          }
          if (*s == '\0') {
            path = p;
          }
      }
      /*
       * Split the path and find the parent node of the path.
       */
      nameArr = &path;
      level = 1;
      if (hboxPtr->separator == SEPARATOR_LIST) {
          if (Tcl_SplitList(interp, path, &level, &nameArr) != TCL_OK) {
            goto error;
          }
      } else if (hboxPtr->separator != SEPARATOR_NONE) {
          if (SplitPath(hboxPtr, path, &level, &nameArr) != TCL_OK) {
            goto error;
          }
      }
      if (level == 0) {
          continue;           /* Root already exists. */
      }
      parentPtr = rootPtr;
      level--;
      for (l = 0; l < level; l++) {
          nodePtr = FindComponent(parentPtr, nameArr[l]);
          if (nodePtr == NULL) {
            if (!hboxPtr->autoCreate) {
                Tcl_AppendResult(interp, "can't find path component \"",
                  nameArr[l], "\" in \"", path, "\"", (char *)NULL);
                goto error;
            }
            nodePtr = CreateNode(hboxPtr, parentPtr, APPEND, nameArr[l]);
          }
          parentPtr = nodePtr;
      }
      nodePtr = NULL;
      if (!hboxPtr->allowDuplicates) {
          nodePtr = FindComponent(parentPtr, nameArr[level]);
      }
      if (nodePtr == NULL) {
          nodePtr = CreateNode(hboxPtr, parentPtr, position, nameArr[level]);
          if (nodePtr == NULL) {
            goto error;
          }
          if (ConfigureEntry(hboxPtr, nodePtr->entryPtr, nOpts, options,
                TK_CONFIG_ARGV_ONLY) != TCL_OK) {
            DeleteNode(hboxPtr, nodePtr);
            goto error;
          }
          Tcl_DStringAppendElement(&dString, NodeToString(hboxPtr, nodePtr));
      } else {
          if (ConfigureEntry(hboxPtr, nodePtr->entryPtr, nOpts, options,
                0) != TCL_OK) {
            goto error;
          }
      }
      if (nameArr != &path) {
          Blt_Free(nameArr);
      }
    }
    hboxPtr->flags |= (HIERBOX_LAYOUT | HIERBOX_SCROLL | HIERBOX_DIRTY);
    EventuallyRedraw(hboxPtr);
    Tcl_DStringResult(hboxPtr->interp, &dString);
    return TCL_OK;
  error:
    if (nameArr != &path) {
      Blt_Free(nameArr);
    }
    Tcl_DStringFree(&dString);
    return TCL_ERROR;
}

/*
 *----------------------------------------------------------------------
 *
 * DeleteOp --
 *
 *    Deletes nodes from the hierarchy. Deletes either a range of
 *    entries from a hierarchy or a single node (except root).
 *    In all cases, nodes are removed recursively.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
DeleteOp(hboxPtr, interp, argc, argv)
    Hierbox *hboxPtr;
    Tcl_Interp *interp;
    int argc;                 /* Not used. */
    char **argv;
{
    Tree *treePtr;
    Blt_ChainLink *linkPtr;
    Blt_ChainLink *firstPtr, *lastPtr, *nextPtr;

    if (argc == 2) {
      return TCL_OK;
    }
    if (StringToNode(hboxPtr, argv[2], &treePtr) != TCL_OK) {
      return TCL_ERROR; /* Node or path doesn't already exist */
    }
    firstPtr = lastPtr = NULL;
    switch (argc) {
    case 3:
      /*
       * Delete a single hierarchy. If the node specified is root,
       * delete only the children.
       */
      if (treePtr != hboxPtr->rootPtr) {
          DestroyTree(hboxPtr, treePtr);  /* Don't delete root */
          goto done;
      }
      firstPtr = Blt_ChainFirstLink(treePtr->chainPtr);
      lastPtr = Blt_ChainLastLink(treePtr->chainPtr);
      break;

    case 4:
      /*
       * Delete a single node from hierarchy specified by its
       * numeric position.
       */
      {
          int position;

          if (Blt_GetPosition(interp, argv[3], &position) != TCL_OK) {
            return TCL_ERROR;
          }
          if (position >= Blt_ChainGetLength(treePtr->chainPtr)) {
            return TCL_OK;    /* Bad first index */
          }
          if (position == APPEND) {
            linkPtr = Blt_ChainLastLink(treePtr->chainPtr);
          } else {
            linkPtr = Blt_ChainGetNthLink(treePtr->chainPtr, position);
          }
          firstPtr = lastPtr = linkPtr;
      }
      break;

    case 5:
      /*
       * Delete range of nodes in hierarchy specified by first/last
       * positions.
       */
      {
          int first, last;
          int nEntries;

          if ((Blt_GetPosition(interp, argv[3], &first) != TCL_OK) ||
            (Blt_GetPosition(interp, argv[4], &last) != TCL_OK)) {
            return TCL_ERROR;
          }
          nEntries = Blt_ChainGetLength(treePtr->chainPtr);
          if (nEntries == 0) {
            return TCL_OK;
          }
          if (first == APPEND) {
            first = nEntries - 1;
          }
          if (first >= nEntries) {
            Tcl_AppendResult(interp, "first position \"", argv[3],
                " is out of range", (char *)NULL);
            return TCL_ERROR;
          }
          if ((last == APPEND) || (last >= nEntries)) {
            last = nEntries - 1;
          }
          if (first > last) {
            Tcl_AppendResult(interp, "bad range: \"", argv[3],
                " > ", argv[4], "\"", (char *)NULL);
            return TCL_ERROR;
          }
          firstPtr = Blt_ChainGetNthLink(treePtr->chainPtr, first);
          lastPtr = Blt_ChainGetNthLink(treePtr->chainPtr, last);
      }
      break;
    }
    for (linkPtr = firstPtr; linkPtr != NULL; linkPtr = nextPtr) {
      nextPtr = Blt_ChainNextLink(linkPtr);
      treePtr = Blt_ChainGetValue(linkPtr);
      DestroyTree(hboxPtr, treePtr);
      if (linkPtr == lastPtr) {
          break;
      }
    }
  done:
    hboxPtr->flags |= (HIERBOX_LAYOUT | HIERBOX_SCROLL);
    EventuallyRedraw(hboxPtr);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * MoveOp --
 *
 *    Move an entry into a new location in the hierarchy.
 *
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
MoveOp(hboxPtr, interp, argc, argv)
    Hierbox *hboxPtr;
    Tcl_Interp *interp;
    int argc;                 /* Not used. */
    char **argv;
{
    Tree *srcPtr, *destPtr, *parentPtr;
    char c;
    int action;

#define MOVE_INTO (1<<0)
#define MOVE_BEFORE     (1<<1)
#define MOVE_AFTER      (1<<2)
    if (StringToNode(hboxPtr, argv[2], &srcPtr) != TCL_OK) {
      return TCL_ERROR;
    }
    c = argv[3][0];
    if ((c == 'i') && (strcmp(argv[3], "into") == 0)) {
      action = MOVE_INTO;
    } else if ((c == 'b') && (strcmp(argv[3], "before") == 0)) {
      action = MOVE_BEFORE;
    } else if ((c == 'a') && (strcmp(argv[3], "after") == 0)) {
      action = MOVE_AFTER;
    } else {
      Tcl_AppendResult(interp, "bad position \"", argv[3],
          "\": should be into, before, or after", (char *)NULL);
      return TCL_ERROR;
    }
    if (StringToNode(hboxPtr, argv[4], &destPtr) != TCL_OK) {
      return TCL_ERROR;
    }
    /* Verify they aren't ancestors. */
    if (IsAncestor(srcPtr, destPtr)) {
      Tcl_AppendResult(interp, "can't move node: \"", argv[2],
          "\" is an ancestor of \"", argv[4], "\"", (char *)NULL);
      return TCL_ERROR;
    }
    parentPtr = destPtr->parentPtr;
    if (parentPtr == NULL) {
      action = MOVE_INTO;
    }
    Blt_ChainUnlinkLink(srcPtr->parentPtr->chainPtr, srcPtr->linkPtr);
    switch (action) {
    case MOVE_INTO:
      Blt_ChainLinkBefore(destPtr->chainPtr, srcPtr->linkPtr,
          (Blt_ChainLink *) NULL);
      parentPtr = destPtr;
      break;

    case MOVE_BEFORE:
      Blt_ChainLinkBefore(parentPtr->chainPtr, srcPtr->linkPtr,
          destPtr->linkPtr);
      break;

    case MOVE_AFTER:
      Blt_ChainLinkAfter(parentPtr->chainPtr, srcPtr->linkPtr,
          destPtr->linkPtr);
      break;
    }
    srcPtr->parentPtr = parentPtr;
    srcPtr->level = parentPtr->level + 1;
    hboxPtr->flags |= (HIERBOX_LAYOUT | HIERBOX_SCROLL | HIERBOX_DIRTY);
    EventuallyRedraw(hboxPtr);
    return TCL_OK;
}

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

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

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

          buttonX = entryPtr->worldX + entryPtr->buttonX;
          buttonY = entryPtr->worldY + entryPtr->buttonY;
          if ((x >= buttonX) && (x < (buttonX + buttonPtr->width)) &&
            (y >= buttonY) && (y < (buttonY + buttonPtr->height))) {
            where = "gadget";
          }
      }
      labelX = entryPtr->worldX + LEVELWIDTH(treePtr->level);
      if ((x >= labelX) &&
          (x < (labelX + LEVELWIDTH(treePtr->level + 1) + entryPtr->width))) {
          where = "select";
      }
      if (Tcl_SetVar(interp, argv[4], where, TCL_LEAVE_ERR_MSG) == NULL) {
          return TCL_ERROR;
      }
    }
    Tcl_SetResult(interp, NodeToString(hboxPtr, treePtr), TCL_VOLATILE);
    return TCL_OK;
}

/*ARGSUSED*/
static int
OpenOp(hboxPtr, interp, argc, argv)
    Hierbox *hboxPtr;
    Tcl_Interp *interp;       /* Not used. */
    int argc;
    char **argv;
{
    Tree *rootPtr;
    unsigned int flags;
    register int i;

    flags = 0;
    if (argc > 2) {
      int length;

      length = strlen(argv[2]);
      if ((argv[2][0] == '-') && (length > 1) &&
          (strncmp(argv[2], "-recurse", length) == 0)) {
          argv++, argc--;
          flags |= APPLY_RECURSE;
      }
    }
    for (i = 2; i < argc; i++) {
      rootPtr = hboxPtr->focusPtr;
      if (GetNode(hboxPtr, argv[i], &rootPtr) != TCL_OK) {
          return TCL_ERROR;
      }
      if (rootPtr == NULL) {
          continue;
      }
      ExposeAncestors(rootPtr);     /* Also make sure that all the ancestors
                               * of this node are also not hidden. */
      if (ApplyToTree(hboxPtr, rootPtr, OpenNode, flags) != TCL_OK) {
          return TCL_ERROR;
      }
    }
    hboxPtr->flags |= (HIERBOX_LAYOUT | HIERBOX_SCROLL);
    EventuallyRedraw(hboxPtr);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * RangeOp --
 *
 *    Returns the node identifiers in a given range.
 *
 *----------------------------------------------------------------------
 */
static int
RangeOp(hboxPtr, interp, argc, argv)
    Hierbox *hboxPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    Tree *firstPtr, *lastPtr;
    register Tree *treePtr;
    unsigned int flags;
    int length;

    flags = 0;
    length = strlen(argv[2]);
    if ((argv[2][0] == '-') && (length > 1) &&
      (strncmp(argv[2], "-open", length) == 0)) {
      argv++, argc--;
      flags |= ENTRY_OPEN;
    }
    if (StringToNode(hboxPtr, argv[2], &firstPtr) != TCL_OK) {
      return TCL_ERROR;
    }
    lastPtr = EndNode(firstPtr, flags);
    if (argc > 3) {
      if (StringToNode(hboxPtr, argv[3], &lastPtr) != TCL_OK) {
          return TCL_ERROR;
      }
    }
    if (flags & ENTRY_OPEN) {
      if (IsHidden(firstPtr)) {
          Tcl_AppendResult(interp, "first node \"", argv[2], "\" is hidden.",
            (char *)NULL);
          return TCL_ERROR;
      }
      if (IsHidden(lastPtr)) {
          Tcl_AppendResult(interp, "last node \"", argv[3], "\" is hidden.",
            (char *)NULL);
          return TCL_ERROR;
      }
    }
    /*
     * The relative order of the first/last markers determines the
     * direction.
     */
    if (IsBefore(lastPtr, firstPtr)) {
      for (treePtr = lastPtr; treePtr != NULL;
          treePtr = LastNode(treePtr, flags)) {
          Tcl_AppendElement(interp, NodeToString(hboxPtr, treePtr));
          if (treePtr == firstPtr) {
            break;
          }
      }
    } else {
      for (treePtr = firstPtr; treePtr != NULL;
          treePtr = NextNode(treePtr, flags)) {
          Tcl_AppendElement(interp, NodeToString(hboxPtr, treePtr));
          if (treePtr == lastPtr) {
            break;
          }
      }
    }
    return TCL_OK;
}

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

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

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

      if (worldX < 0) {
          worldX = 0;
      } else if (worldX >= hboxPtr->worldWidth) {
          worldX = hboxPtr->worldWidth - hboxPtr->xScrollUnits;
      }
      if (worldY < 0) {
          worldY = 0;
      } else if (worldY >= hboxPtr->worldHeight) {
          worldY = hboxPtr->worldHeight - hboxPtr->yScrollUnits;
      }
      hboxPtr->xOffset = worldX;
      hboxPtr->yOffset = worldY;
      hboxPtr->flags |= (HIERBOX_LAYOUT | HIERBOX_SCROLL);
      EventuallyRedraw(hboxPtr);
    }
    return TCL_OK;
}

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

    anchor = TK_ANCHOR_W;     /* Default anchor is West */
    if ((argv[2][0] == '-') && (strcmp(argv[2], "-anchor") == 0)) {
      if (argc == 3) {
          Tcl_AppendResult(interp, "missing \"-anchor\" argument",
            (char *)NULL);
          return TCL_ERROR;
      }
      if (Tk_GetAnchor(interp, argv[3], &anchor) != TCL_OK) {
          return TCL_ERROR;
      }
      argc -= 2, argv += 2;
    }
    if (argc == 2) {
      Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
          "see ?-anchor anchor? index\"", (char *)NULL);
      return TCL_ERROR;
    }
    treePtr = hboxPtr->focusPtr;
    if (GetNode(hboxPtr, argv[2], &treePtr) != TCL_OK) {
      return TCL_ERROR;
    }
    if (treePtr == NULL) {
      return TCL_OK;
    }
    if (IsHidden(treePtr)) {
      ExposeAncestors(treePtr);
      hboxPtr->flags |= (HIERBOX_LAYOUT | HIERBOX_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.
       */
      ComputeLayout(hboxPtr);
    }
    entryPtr = treePtr->entryPtr;
    width = VPORTWIDTH(hboxPtr);
    height = VPORTHEIGHT(hboxPtr);

    /*
     * 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 = hboxPtr->xOffset;
    right = hboxPtr->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 + LEVELWIDTH(treePtr->level) -
          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 = hboxPtr->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 = hboxPtr->yOffset;
    bottom = hboxPtr->yOffset + height;

    switch (anchor) {
    case TK_ANCHOR_N:
      y = hboxPtr->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 = hboxPtr->yOffset;
      }
      break;
    }
    if ((y != hboxPtr->yOffset) || (x != hboxPtr->xOffset)) {
      hboxPtr->xOffset = x;
      hboxPtr->yOffset = y;
      hboxPtr->flags |= (HIERBOX_SCROLL | HIERBOX_LAYOUT);
    }
    EventuallyRedraw(hboxPtr);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * AnchorOpOp --
 *
 *    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
AnchorOpOp(hboxPtr, interp, argc, argv)
    Hierbox *hboxPtr;
    Tcl_Interp *interp;       /* Not used. */
    int argc;                 /* Not used. */
    char **argv;
{
    Tree *nodePtr;

    nodePtr = hboxPtr->focusPtr;
    if (GetNode(hboxPtr, argv[3], &nodePtr) != TCL_OK) {
      return TCL_ERROR;
    }
    hboxPtr->selAnchorPtr = nodePtr;
    if (nodePtr != NULL) {
      Tcl_SetResult(interp, NodeToString(hboxPtr, nodePtr), TCL_VOLATILE);
    }
    EventuallyRedraw(hboxPtr);
    return TCL_OK;
}



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

/*
 *----------------------------------------------------------------------
 *
 * IncludesOpOp
 *
 *    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
IncludesOpOp(hboxPtr, interp, argc, argv)
    Hierbox *hboxPtr;
    Tcl_Interp *interp;
    int argc;                 /* Not used. */
    char **argv;
{
    Tree *treePtr;

    treePtr = hboxPtr->focusPtr;
    if (GetNode(hboxPtr, argv[3], &treePtr) != TCL_OK) {
      return TCL_ERROR;
    }
    if (treePtr != NULL) {
      int bool;

      bool = IsSelected(hboxPtr, treePtr);
      Blt_SetBooleanResult(interp, bool);
    }
    return TCL_OK;
}

/*----------------------------------------------------------------------
 *
 * MarkOpOp --
 *
 *    Sets the selection mark to the element given by a index.  The
 *    selection mark is the end of the selection that is not fixed
 *    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
MarkOpOp(hboxPtr, interp, argc, argv)
    Hierbox *hboxPtr;
    Tcl_Interp *interp;       /* Not used. */
    int argc;                 /* Not used. */
    char **argv;
{
    Tree *nodePtr;
    Blt_ChainLink *linkPtr, *nextPtr;
    Tree *selectPtr;

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

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

/*
 *----------------------------------------------------------------------
 *
 * PresentOpOp
 *
 *    Indicates if there is a selection present.
 *
 * Results:
 *    A standard Tcl result.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
PresentOpOp(hboxPtr, interp, argc, argv)
    Hierbox *hboxPtr;
    Tcl_Interp *interp;
    int argc;                 /* Not used. */
    char **argv;
{
    int bool;

    bool = (Blt_ChainGetLength(&(hboxPtr->selectChain)) > 0);
    Blt_SetBooleanResult(interp, bool);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * SelectOpOp
 *
 *    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
SelectOpOp(hboxPtr, interp, argc, argv)
    Hierbox *hboxPtr;
    Tcl_Interp *interp;
    int argc;                 /* Not used. */
    char **argv;
{
    Tree *firstPtr, *lastPtr;

    hboxPtr->flags &= ~SELECTION_MASK;
    switch (argv[2][0]) {
    case 's':
      hboxPtr->flags |= SELECTION_SET;
      break;
    case 'c':
      hboxPtr->flags |= SELECTION_CLEAR;
      break;
    case 't':
      hboxPtr->flags |= SELECTION_TOGGLE;
      break;
    }

    if (StringToNode(hboxPtr, argv[3], &firstPtr) != TCL_OK) {
      return TCL_ERROR;
    }
    if ((IsHidden(firstPtr)) && !(hboxPtr->flags & SELECTION_CLEAR)) {
      Tcl_AppendResult(interp, "can't select hidden node \"", argv[3], "\"",
          (char *)NULL);
      return TCL_ERROR;
    }
    lastPtr = firstPtr;
    if (argc > 4) {
      if (StringToNode(hboxPtr, argv[4], &lastPtr) != TCL_OK) {
          return TCL_ERROR;
      }
      if ((IsHidden(lastPtr)) && !(hboxPtr->flags & SELECTION_CLEAR)) {
          Tcl_AppendResult(interp, "can't select hidden node \"", argv[4],
            "\"", (char *)NULL);
          return TCL_ERROR;
      }
    }
    if (firstPtr == lastPtr) {
      SelectNode(hboxPtr, firstPtr);
    } else {
      SelectRange(hboxPtr, firstPtr, lastPtr);
    }
    hboxPtr->flags &= ~SELECTION_MASK;
    if (hboxPtr->flags & SELECTION_EXPORT) {
      Tk_OwnSelection(hboxPtr->tkwin, XA_PRIMARY, LostSelection, hboxPtr);
    }
    EventuallyRedraw(hboxPtr);
    if (hboxPtr->selectCmd != NULL) {
      EventuallyInvokeSelectCmd(hboxPtr);
    }
    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 selectionOperSpecs[] =
{
    {"anchor", 1, (Blt_Op)AnchorOpOp, 4, 4, "index",},
    {"clear", 5, (Blt_Op)SelectOpOp, 4, 5, "firstIndex ?lastIndex?",},
    {"clearall", 6, (Blt_Op)ClearallOpOp, 3, 3, "",},
    {"includes", 2, (Blt_Op)IncludesOpOp, 4, 4, "index",},
    {"mark", 1, (Blt_Op)MarkOpOp, 4, 4, "index",},
    {"present", 1, (Blt_Op)PresentOpOp, 3, 3, "",},
    {"set", 1, (Blt_Op)SelectOpOp, 4, 5, "firstIndex ?lastIndex?",},
    {"toggle", 1, (Blt_Op)SelectOpOp, 4, 5, "firstIndex ?lastIndex?",},
};
static int nSelectionSpecs = sizeof(selectionOperSpecs) / sizeof(Blt_OpSpec);

static int
SelectionOp(hboxPtr, interp, argc, argv)
    Hierbox *hboxPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    Blt_Op proc;
    int result;

    proc = Blt_GetOp(interp, nSelectionSpecs, selectionOperSpecs, BLT_OP_ARG2,
      argc, argv, 0);
    if (proc == NULL) {
      return TCL_ERROR;
    }
    result = (*proc) (hboxPtr, interp, argc, argv);
    return result;
}

/*ARGSUSED*/
static int
SortOp(hboxPtr, interp, argc, argv)
    Hierbox *hboxPtr;
    Tcl_Interp *interp;       /* Not used. */
    int argc;
    char **argv;
{
    int length;
    Tree *rootPtr;
    register int i;
    unsigned int flags;

    flags = 0;
    hboxPtr->sortCmd = NULL;
    for (i = 2; i < argc; i++) {
      if (argv[i][0] != '-') {
          break;        /* Found start of indices */
      }
      length = strlen(argv[i]);
      if ((length > 1) && (strncmp(argv[i], "-recurse", length) == 0)) {
          flags |= APPLY_RECURSE;
      } else if ((length > 1) && (strncmp(argv[i], "-command", length) ==0)) {
          if ((i + 1) == argc) {
            Tcl_AppendResult(interp, "\"-command\" must be",
                " followed by comparison command", (char *)NULL);
            return TCL_ERROR;
          }
          i++;
          hboxPtr->sortCmd = argv[i];
      } else if ((argv[i][1] == '-') && (argv[i][2] == '\0')) {
          break;        /* Allow first index to start with a '-' */
      } else {
          Tcl_AppendResult(interp, "bad switch \"", argv[i],
            "\": must be -command or -recurse", (char *)NULL);
          return TCL_ERROR;
      }
    }
    for ( /*empty*/ ; i < argc; i++) {
      if (StringToNode(hboxPtr, argv[i], &rootPtr) != TCL_OK) {
          return TCL_ERROR;
      }
      if (ApplyToTree(hboxPtr, rootPtr, SortNode, flags) != TCL_OK) {
          return TCL_ERROR;
      }
    }
    hboxPtr->flags |= HIERBOX_LAYOUT;
    EventuallyRedraw(hboxPtr);
    return TCL_OK;
}

/*ARGSUSED*/
static int
ToggleOp(hboxPtr, interp, argc, argv)
    Hierbox *hboxPtr;
    Tcl_Interp *interp;       /* Not used. */
    int argc;
    char **argv;
{
    Tree *rootPtr;
    int result;

    rootPtr = hboxPtr->focusPtr;
    if (GetNode(hboxPtr, argv[2], &rootPtr) != TCL_OK) {
      return TCL_ERROR;
    }
    if (rootPtr == NULL) {
      return TCL_OK;
    }
    if (rootPtr->entryPtr->flags & ENTRY_OPEN) {
      PruneSelection(hboxPtr, rootPtr);
      if (IsAncestor(rootPtr, hboxPtr->focusPtr)) {
          hboxPtr->focusPtr = rootPtr;
          Blt_SetFocusItem(hboxPtr->bindTable, hboxPtr->focusPtr, NULL);
      }
      if (IsAncestor(rootPtr, hboxPtr->selAnchorPtr)) {
          hboxPtr->selAnchorPtr = NULL;
      }
      result = CloseNode(hboxPtr, rootPtr);
    } else {
      result = OpenNode(hboxPtr, rootPtr);
    }
    if (result != TCL_OK) {
      return TCL_ERROR;
    }
    hboxPtr->flags |= (HIERBOX_LAYOUT | HIERBOX_SCROLL);
    EventuallyRedraw(hboxPtr);
    return TCL_OK;
}

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

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

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

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

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

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

/*
 * --------------------------------------------------------------
 *
 * HierboxInstCmd --
 *
 *    This procedure is invoked to process the "hierbox" command.
 *    See the user documentation for details on what it does.
 *
 * Results:
 *    A standard Tcl result.
 *
 * Side effects:
 *    See the user documentation.
 *
 * --------------------------------------------------------------
 */
static Blt_OpSpec operSpecs[] =
{
    {"bbox", 2, (Blt_Op)BboxOp, 2, 0, "index...",},
    {"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? index...",},
    {"configure", 2, (Blt_Op)ConfigureOp, 2, 0, "?option value?...",},
    {"curselection", 2, (Blt_Op)CurselectionOp, 2, 2, "",},
    {"delete", 1, (Blt_Op)DeleteOp, 2, 0, "?-recurse? index ?index...?",},
    {"entry", 1, (Blt_Op)EntryOp, 2, 0, "oper args",},
    {"find", 2, (Blt_Op)FindOp, 2, 0, "?flags...? ?firstIndex lastIndex?",},
    {"focus", 2, (Blt_Op)FocusOp, 3, 3, "index",},
    {"get", 1, (Blt_Op)GetOp, 2, 0, "?-full? index ?index...?",},
    {"hide", 1, (Blt_Op)HideOp, 2, 0, "?-exact? ?-glob? ?-regexp? ?-nonmatching? ?-name string? ?-full string? ?-data string? ?--? ?index...?",},
    {"index", 3, (Blt_Op)IndexOp, 3, 5, "?-at index? string",},
    {"insert", 3, (Blt_Op)InsertOp, 3, 0, "?-at index? position label ?label...? ?option value?",},
    {"move", 1, (Blt_Op)MoveOp, 5, 5, "index into|before|after index",},
    {"nearest", 1, (Blt_Op)NearestOp, 4, 5, "x y ?varName?",},
    {"open", 1, (Blt_Op)OpenOp, 2, 0, "?-recurse? index...",},
    {"range", 1, (Blt_Op)RangeOp, 4, 5, "?-open? firstIndex lastIndex",},
    {"scan", 2, (Blt_Op)ScanOp, 5, 5, "dragto|mark x y",},
    {"see", 3, (Blt_Op)SeeOp, 3, 0, "?-anchor anchor? index",},
    {"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? ?--? ?index...?",},
    {"sort", 2, (Blt_Op)SortOp, 2, 0, "?-recurse? ?-command string? index...",},
    {"toggle", 1, (Blt_Op)ToggleOp, 3, 3, "index",},
    {"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 nSpecs = sizeof(operSpecs) / sizeof(Blt_OpSpec);

static int
HierboxInstCmd(clientData, interp, argc, argv)
    ClientData clientData;    /* Information about the widget. */
    Tcl_Interp *interp;       /* Interpreter to report errors back to. */
    int argc;                 /* Number of arguments. */
    char **argv;        /* Vector of argument strings. */
{
    Blt_Op proc;
    Hierbox *hboxPtr = clientData;
    int result;

    proc = Blt_GetOp(interp, nSpecs, operSpecs, BLT_OP_ARG1, argc, argv, 0);
    if (proc == NULL) {
      return TCL_ERROR;
    }
    Tcl_Preserve(hboxPtr);
    result = (*proc) (hboxPtr, interp, argc, argv);
    Tcl_Release(hboxPtr);
    return result;
}

int
Blt_HierboxInit(interp)
    Tcl_Interp *interp;
{
    static Blt_CmdSpec cmdSpec =
    {
      "hierbox", HierboxCmd,
    };

    if (Blt_InitCmd(interp, "blt", &cmdSpec) == NULL) {
      return TCL_ERROR;
    }
    return TCL_OK;
}

#ifdef notdef

/* Selection Procedures */
/*
 *----------------------------------------------------------------------
 *
 * SelectTextBlock --
 *
 *    Modify the selection by moving its un-anchored end.  This
 *    could make the selection either larger or smaller.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    The selection changes.
 *
 *----------------------------------------------------------------------
 */
static int
SelectTextBlock(hboxPtr, to)
    Hierbox *hboxPtr;         /* Information about widget. */
    int to;             /* Index of element that is to
                         * become the "other" end of the
                         * selection. */
{
    TextEdit *editPtr = &(hboxPtr->labelEdit);
    int first, last;

    /*  If the anchor hasn't been set yet, assume the beginning of the label. */
    if (editPtr->selAnchor < 0) {
      editPtr->selAnchor = 0;
    }
    if (editPtr->selAnchor <= to) {
      first = editPtr->selAnchor;
      last = to;
    } else {
      first = to;
      last = editPtr->selAnchor;
    }
    if ((editPtr->selFirst != first) || (editPtr->selLast != last)) {
      editPtr->selFirst = first, editPtr->selLast = last;
      EventuallyRedraw(hboxPtr);
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ClearOpOp --
 *
 *          Clears the selection from the label.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    The selection changes.
 *
 *----------------------------------------------------------------------
 */
static int
ClearOpOp(hboxPtr, interp, argc, argv)
    Hierbox *hboxPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    TextEdit *editPtr = &(hboxPtr->labelEdit);

    if (editPtr->selFirst != -1) {
      editPtr->selFirst = editPtr->selLast = -1;
      EventuallyRedraw(hboxPtr);
    }
    return TCL_OK;
}

static int
FromOpOp(hboxPtr, interp, argc, argv)
    Hierbox *hboxPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    TextEdit *editPtr = &(hboxPtr->labelEdit);
    int from;

    if (GetLabelIndex(hboxPtr, argv[3], &from) != TCL_OK) {
      return TCL_ERROR;
    }
    editPtr->selAnchor = from;
    return TCL_OK;
}

static int
PresentOpOp(hboxPtr, interp, argc, argv)
    Hierbox *hboxPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    TextEdit *editPtr = &(hboxPtr->labelEdit);
    Tcl_AppendResult(interp, (editPtr->selFirst != -1) ? "0" : "1", (char *)NULL);
    return TCL_OK;
}

static int
AdjustOpOp(hboxPtr, interp, argc, argv)
    Hierbox *hboxPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    TextEdit *editPtr = &(hboxPtr->labelEdit);
    int adjust;

    if (GetLabelIndex(hboxPtr, argv[3], &adjust) != TCL_OK) {
      return TCL_ERROR;
    }
    half1 = (editPtr->selFirst + editPtr->selLast) / 2;
    half2 = (editPtr->selFirst + editPtr->selLast + 1) / 2;
    if (adjust < half1) {
      editPtr->selAnchor = editPtr->selLast;
    } else if (adjust > half2) {
      editPtr->selAnchor = editPtr->selFirst;
    }
    result = SelectTextBlock(hboxPtr, adjust);
    return TCL_OK;
}

static int
ToOpOp(hboxPtr, interp, argc, argv)
    Hierbox *hboxPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    int to;

    if (GetLabelIndex(hboxPtr, argv[3], &to) != TCL_OK) {
      return TCL_ERROR;
    }
    SelectTextBlock(hboxPtr, to);
    return TCL_OK;
}

static int
RangeOpOp(hboxPtr, interp, argc, argv)
    Hierbox *hboxPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    TextEdit *editPtr = &(hboxPtr->labelEdit);
    int first, last;

    if (GetLabelIndex(hboxPtr, argv[4], &first) != TCL_OK) {
      return TCL_ERROR;
    }
    if (GetLabelIndex(hboxPtr, argv[5], &last) != TCL_OK) {
      return TCL_ERROR;
    }
    editPtr->selAnchor = first;
    return SelectTextBlock(hboxPtr, last);
}

/*
 *----------------------------------------------------------------------
 *
 * SelectionOp --
 *
 *    This procedure handles the individual options for text
 *    selections.  The selected text is designated by a starting
 *    and terminating index that points into the text.  The selected
 *    segment has both a anchored and unanchored ends.  The following
 *    selection operations are implemented:
 *
 *      adjust index          Resets either the first or last index
 *                      of the selection.
 *      clear                 Clears the selection. Sets first/last
 *                      indices to -1.
 *      from index            Sets the index of the selection anchor.
 *      present         Return "1" if a selection is available,
 *                      "0" otherwise.
 *      range first last      Sets the selection.
 *      to index        Sets the index of the un-anchored end.

 * Results:
 *    None.
 *
 * Side effects:
 *    The selection changes.
 *
 *----------------------------------------------------------------------
 */
static Blt_OpSpec entrySelectionOperSpecs[] =
{
    {"adjust", 2, (Blt_Op)AdjustOpOp, 5, 5, "index",},
    {"clear", 2, (Blt_Op)ClearOpOp, 4, 4, "",},
    {"from", 1, (Blt_Op)FromOpOp, 5, 5, "index",},
    {"present", 1, (Blt_Op)PresentOpOp, 4, 4, "",},
    {"range", 1, (Blt_Op)RangeOpOp, 4, 5, "firstIndex lastIndex",},
    {"to", 1, (Blt_Op)ToOpOp, 5, 5, "index",},
};
static int nEntrySelectionSpecs =
sizeof(entrySelectionOperSpecs) / sizeof(Blt_OpSpec);

static int
EntrySelectionOp(hboxPtr, interp, argc, argv)
    Hierbox *hboxPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    Blt_Op proc;
    int result;

    proc = Blt_GetOp(interp, nSelectionSpecs, selectionOperSpecs, BLT_OP_ARG2,
      argc, argv, 0);
    if (proc == NULL) {
      return TCL_ERROR;
    }
    result = (*proc) (hboxPtr, interp, argc, argv);
    return result;
}

#endif

#endif /* NO_HIERBOX */

Generated by  Doxygen 1.6.0   Back to index