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

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

#include "bltInt.h"

#ifndef NO_HIERTABLE

#include "bltHiertable.h"

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

#define ODD(x)			((x) | 0x01)

#include <X11/Xutil.h>
#include <X11/Xatom.h>

Hiertable *bltHiertableLastInstance;

#define DEF_ICON_WIDTH 16
#define DEF_ICON_HEIGHT 16

static void FreeEntryInternalRep _ANSI_ARGS_((Tcl_Obj *objPtr));
static void UpdateStringOfEntry  _ANSI_ARGS_((Tcl_Obj *objPtr));
static int SetEntryObjFromAny _ANSI_ARGS_((Tcl_Interp *interp, 
	Tcl_Obj *objPtr));

static Tcl_ObjType entryObjType = {
    "BLT Hiertable Entry",
    FreeEntryInternalRep,	/* Called when an object is freed. */
    NULL,			/* Copies an internal representation 
				 * from one object to another. */
    UpdateStringOfEntry,	/* Creates string representation from
				 * an object's internal representation. */
    SetEntryObjFromAny,		/* Creates valid internal representation 
				 * from an object's string representation. */
};

static Blt_TreeApplyProc DeleteApplyProc;
static Blt_TreeApplyProc CreateApplyProc;

extern Tk_CustomOption bltDashesOption;
extern Tk_CustomOption bltDistanceOption;
extern Tk_CustomOption bltListOption;
extern Tk_CustomOption bltShadowOption;
extern Tk_CustomOption bltPadOption;
extern Tk_CustomOption bltHiertableDataOption;

static int StringToTree _ANSI_ARGS_((ClientData clientData,
	Tcl_Interp *interp, Tk_Window tkwin, char *string, char *widgRec,
	int offset));

static char *TreeToString _ANSI_ARGS_((ClientData clientData,
	Tk_Window tkwin, char *widgRec, int offset,
	Tcl_FreeProc **freeProcPtrPtr));

static Tk_CustomOption treeOption =
{
    StringToTree, TreeToString, (ClientData)0,
};

static int StringToImagelist _ANSI_ARGS_((ClientData clientData,
	Tcl_Interp *interp, Tk_Window tkwin, char *string, char *widgRec,
	int offset));

static char *ImagelistToString _ANSI_ARGS_((ClientData clientData,
	Tk_Window tkwin, char *widgRec, int offset,
	Tcl_FreeProc **freeProcPtrPtr));
/*
 * Contains a pointer to the widget that's currently being configured.
 * This is used in the custom configuration parse routine for images.
 */
static Tk_CustomOption imagelistOption =
{
    StringToImagelist, ImagelistToString, (ClientData)&bltHiertableLastInstance,
};

static int StringToButton _ANSI_ARGS_((ClientData clientData,
	Tcl_Interp *interp, Tk_Window tkwin, char *string, char *widgRec,
	int offset));

static char *ButtonToString _ANSI_ARGS_((ClientData clientData,
	Tk_Window tkwin, char *widgRec, int offset,
	Tcl_FreeProc **freeProcPtrPtr));

static Tk_CustomOption buttonOption = {
    StringToButton, ButtonToString, (ClientData)0,
};

static int StringToUid _ANSI_ARGS_((ClientData clientData,
	Tcl_Interp *interp, Tk_Window tkwin, char *string, char *widgRec,
	int offset));

static char *UidToString _ANSI_ARGS_((ClientData clientData,
	Tk_Window tkwin, char *widgRec, int offset,
	Tcl_FreeProc **freeProcPtrPtr));

Tk_CustomOption bltHtUidOption = {
    StringToUid, UidToString, (ClientData)0,
};

static int StringToScrollmode _ANSI_ARGS_((ClientData clientData,
	Tcl_Interp *interp, Tk_Window tkwin, char *string, char *widgRec,
	int offset));

static char *ScrollmodeToString _ANSI_ARGS_((ClientData clientData,
	Tk_Window tkwin, char *widgRec, int offset,
	Tcl_FreeProc **freeProcPtrPtr));

static Tk_CustomOption scrollmodeOption = {
    StringToScrollmode, ScrollmodeToString, (ClientData)0,
};

static int StringToSelectmode _ANSI_ARGS_((ClientData clientData,
	Tcl_Interp *interp, Tk_Window tkwin, char *string, char *widgRec,
	int offset));

static char *SelectmodeToString _ANSI_ARGS_((ClientData clientData,
	Tk_Window tkwin, char *widgRec, int offset,
	Tcl_FreeProc **freeProcPtrPtr));

static Tk_CustomOption selectmodeOption = {
    StringToSelectmode, SelectmodeToString, (ClientData)0,
};

static int StringToSeparator _ANSI_ARGS_((ClientData clientData,
	Tcl_Interp *interp, Tk_Window tkwin, char *string, char *widgRec,
	int offset));

static char *SeparatorToString _ANSI_ARGS_((ClientData clientData,
	Tk_Window tkwin, char *widgRec, int offset,
	Tcl_FreeProc **freeProcPtrPtr));

static Tk_CustomOption separatorOption = {
    StringToSeparator, SeparatorToString, (ClientData)0,
};

#define DEF_BUTTON_ACTIVE_BG_COLOR	RGB_WHITE
#define DEF_BUTTON_ACTIVE_BG_MONO	STD_MONO_ACTIVE_BG
#define DEF_BUTTON_ACTIVE_FG_COLOR	STD_COLOR_ACTIVE_FG
#define DEF_BUTTON_ACTIVE_FG_MONO	STD_MONO_ACTIVE_FG
#define DEF_BUTTON_BORDER_WIDTH		"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_BG_COLOR	RGB_WHITE
#define DEF_BUTTON_NORMAL_BG_MONO	STD_MONO_NORMAL_BG
#define DEF_BUTTON_NORMAL_FG_COLOR	STD_COLOR_NORMAL_FG
#define DEF_BUTTON_NORMAL_FG_MONO	STD_MONO_NORMAL_FG
#define DEF_BUTTON_SIZE			"7"

#define DEF_ENTRY_BG_COLOR		(char *)NULL
#define DEF_ENTRY_BG_MONO		(char *)NULL
#define DEF_ENTRY_BIND_TAGS		"Entry all"
#define DEF_ENTRY_COMMAND		(char *)NULL
#define DEF_ENTRY_DATA			(char *)NULL
#define DEF_ENTRY_FG_COLOR		(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_HT_ICONS \
	"blt::Hiertable::OpenNormalFolder blt::Hiertable::CloseNormalFolder"
#define DEF_HT_ACTIVE_ICONS \
	"blt::Hiertable::OpenActiveFolder blt::Hiertable::CloseActiveFolder"
#define DEF_HT_ACTIVE_BG_COLOR	RGB_LIGHTBLUE0
#define DEF_HT_ACTIVE_BG_MONO	STD_MONO_ACTIVE_BG
#define DEF_HT_ACTIVE_FG_COLOR	RGB_BLACK
#define DEF_HT_ACTIVE_RELIEF	"flat"
#define DEF_HT_ACTIVE_SELECT_BG_COLOR	"#ffffea"
#define DEF_HT_ACTIVE_STIPPLE	"gray25"
#define DEF_HT_ALLOW_DUPLICATES	"yes"
#define DEF_HT_BACKGROUND	RGB_WHITE
#define DEF_HT_BORDER_WIDTH	STD_BORDERWIDTH
#define DEF_HT_BUTTON		"auto"
#define DEF_HT_COMMAND		(char *)NULL
#define DEF_HT_CURSOR		(char *)NULL
#define DEF_HT_DASHES		"dot"
#define DEF_HT_EXPORT_SELECTION	"no"
#define DEF_HT_FG_COLOR		STD_COLOR_NORMAL_FG
#define DEF_HT_FG_MONO		STD_MONO_NORMAL_FG
#define DEF_HT_FLAT		"no"
#define DEF_HT_FOCUS_DASHES	"dot"
#define DEF_HT_FOCUS_EDIT	"no"
#define DEF_HT_FOCUS_FG_COLOR	STD_COLOR_ACTIVE_FG
#define DEF_HT_FOCUS_FG_MONO	STD_MONO_ACTIVE_FG
#define DEF_HT_FONT		STD_FONT
#define DEF_HT_HEIGHT		"400"
#define DEF_HT_HIDE_ROOT	"no"
#define DEF_HT_HIGHLIGHT_BG_COLOR STD_COLOR_NORMAL_BG
#define DEF_HT_HIGHLIGHT_BG_MONO STD_MONO_NORMAL_BG
#define DEF_HT_HIGHLIGHT_COLOR	RGB_BLACK
#define DEF_HT_HIGHLIGHT_WIDTH	"2"
#define DEF_HT_LINE_COLOR	RGB_GREY50
#define DEF_HT_LINE_MONO	STD_MONO_NORMAL_FG
#define DEF_HT_LINE_SPACING	"0"
#define DEF_HT_LINE_WIDTH	"1"
#define DEF_HT_MAKE_PATH	"no"
#define DEF_HT_NORMAL_BG_COLOR 	STD_COLOR_NORMAL_BG
#define DEF_HT_NORMAL_FG_MONO	STD_MONO_ACTIVE_FG
#define DEF_HT_RELIEF		"sunken"
#define DEF_HT_SCROLL_INCREMENT "20"
#define DEF_HT_SCROLL_MODE	"hierbox"
#define DEF_HT_SELECT_BG_COLOR 	RGB_LIGHTBLUE1
#define DEF_HT_SELECT_BG_MONO  	STD_MONO_SELECT_BG
#define DEF_HT_SELECT_BORDER_WIDTH "1"
#define DEF_HT_SELECT_CMD	(char *)NULL
#define DEF_HT_SELECT_FG_COLOR 	STD_COLOR_SELECT_FG
#define DEF_HT_SELECT_FG_MONO  	STD_MONO_SELECT_FG
#define DEF_HT_SELECT_MODE	"single"
#define DEF_HT_SELECT_RELIEF	"flat"
#define DEF_HT_SEPARATOR	(char *)NULL
#define DEF_HT_SHOW_ROOT	"yes"
#define DEF_HT_SHOW_TITLES	"yes"
#define DEF_HT_SORT_SELECTION	"no"
#define DEF_HT_TAKE_FOCUS	"1"
#define DEF_HT_TEXT_COLOR	STD_COLOR_NORMAL_FG
#define DEF_HT_TEXT_MONO	STD_MONO_NORMAL_FG
#define DEF_HT_TRIMLEFT		""
#define DEF_HT_WIDTH		"200"

static Tk_ConfigSpec buttonConfigSpecs[] =
{
    {TK_CONFIG_BORDER, "-activebackground", "activeBackground", "Background",
	DEF_BUTTON_ACTIVE_BG_MONO, Tk_Offset(Hiertable, button.activeBorder),
	TK_CONFIG_MONO_ONLY},
    {TK_CONFIG_BORDER, "-activebackground", "activeBackground", "Background",
	DEF_BUTTON_ACTIVE_BG_COLOR, Tk_Offset(Hiertable, button.activeBorder),
	TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_SYNONYM, "-activebg", "activeBackground", (char *)NULL, 
	(char *)NULL, 0, 0},
    {TK_CONFIG_SYNONYM, "-activefg", "activeForeground", (char *)NULL, 
	(char *)NULL, 0, 0},
    {TK_CONFIG_COLOR, "-activeforeground", "activeForeground", "Foreground",
	DEF_BUTTON_ACTIVE_FG_COLOR, Tk_Offset(Hiertable, button.activeFgColor),
	TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_COLOR, "-activeforeground", "activeForeground", "Foreground",
	DEF_BUTTON_ACTIVE_FG_MONO, Tk_Offset(Hiertable, button.activeFgColor),
	TK_CONFIG_MONO_ONLY},
    {TK_CONFIG_BORDER, "-background", "background", "Background",
	DEF_BUTTON_NORMAL_BG_COLOR, Tk_Offset(Hiertable, button.border),
	TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_BORDER, "-background", "background", "Background",
	DEF_BUTTON_NORMAL_BG_MONO, Tk_Offset(Hiertable, 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_BORDER_WIDTH, Tk_Offset(Hiertable, button.borderWidth),
	TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
    {TK_CONFIG_RELIEF, "-closerelief", "closeRelief", "Relief",
	DEF_BUTTON_CLOSE_RELIEF, Tk_Offset(Hiertable, 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_FG_COLOR, Tk_Offset(Hiertable, button.fgColor),
	TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_COLOR, "-foreground", "foreground", "Foreground",
	DEF_BUTTON_NORMAL_FG_MONO, Tk_Offset(Hiertable, button.fgColor),
	TK_CONFIG_MONO_ONLY},
    {TK_CONFIG_CUSTOM, "-images", "images", "Images",
	DEF_BUTTON_IMAGES, Tk_Offset(Hiertable, button.hImages),
	TK_CONFIG_NULL_OK, &imagelistOption},
    {TK_CONFIG_RELIEF, "-openrelief", "openRelief", "Relief",
	DEF_BUTTON_OPEN_RELIEF, Tk_Offset(Hiertable, button.openRelief),
	TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_CUSTOM, "-size", "size", "Size", DEF_BUTTON_SIZE, 
	Tk_Offset(Hiertable, button.reqSize), 0, &bltDistanceOption},
    {TK_CONFIG_END, (char *)NULL, (char *)NULL, (char *)NULL,
	(char *)NULL, 0, 0}
};

static Tk_ConfigSpec entryConfigSpecs[] =
{
    {TK_CONFIG_CUSTOM, "-activeicons", (char *)NULL, (char *)NULL,
	DEF_ENTRY_ICONS, Tk_Offset(Entry, activeIcons),
	TK_CONFIG_NULL_OK, &imagelistOption},
    {TK_CONFIG_CUSTOM, "-bindtags", "bindTags", "BindTags",
	DEF_ENTRY_BIND_TAGS, Tk_Offset(Entry, tags),
	TK_CONFIG_NULL_OK, &bltHtUidOption},
    {TK_CONFIG_CUSTOM, "-button", (char *)NULL, (char *)NULL,
	DEF_HT_BUTTON, Tk_Offset(Entry, flags),
	TK_CONFIG_DONT_SET_DEFAULT, &buttonOption},
    {TK_CONFIG_CUSTOM, "-closecommand", (char *)NULL, (char *)NULL,
	DEF_ENTRY_COMMAND, Tk_Offset(Entry, closeCmd),
	TK_CONFIG_NULL_OK, &bltHtUidOption},
    {TK_CONFIG_CUSTOM, "-data", "data", "data",
	DEF_ENTRY_DATA, 0, TK_CONFIG_NULL_OK, &bltHiertableDataOption},
    {TK_CONFIG_SYNONYM, "-fg", "foreground", (char *)NULL, (char *)NULL, 0, 0},
    {TK_CONFIG_FONT, "-font", "font", "Font",
	DEF_ENTRY_FONT, Tk_Offset(Entry, font), 0},
    {TK_CONFIG_COLOR, "-foreground", (char *)NULL, (char *)NULL,
	DEF_ENTRY_FG_COLOR, Tk_Offset(Entry, color), TK_CONFIG_NULL_OK},
    {TK_CONFIG_CUSTOM, "-icons", (char *)NULL, (char *)NULL,
	DEF_ENTRY_ICONS, Tk_Offset(Entry, icons),
	TK_CONFIG_NULL_OK, &imagelistOption},
    {TK_CONFIG_CUSTOM, "-label", "label", "Label",
	DEF_ENTRY_LABEL, Tk_Offset(Entry, labelUid), 0, &bltHtUidOption},
    {TK_CONFIG_CUSTOM, "-opencommand", (char *)NULL, (char *)NULL,
	DEF_ENTRY_COMMAND, Tk_Offset(Entry, openCmd),
	TK_CONFIG_NULL_OK, &bltHtUidOption},
    {TK_CONFIG_CUSTOM, "-shadow", "shadow", "Shadow",
	DEF_ENTRY_SHADOW_COLOR, Tk_Offset(Entry, shadow),
	TK_CONFIG_NULL_OK | TK_CONFIG_COLOR_ONLY, &bltShadowOption},
    {TK_CONFIG_CUSTOM, "-shadow", "shadow", "Shadow",
	DEF_ENTRY_SHADOW_MONO, Tk_Offset(Entry, shadow),
	TK_CONFIG_NULL_OK | TK_CONFIG_MONO_ONLY, &bltShadowOption},
    {TK_CONFIG_END, (char *)NULL, (char *)NULL, (char *)NULL,
	(char *)NULL, 0, 0}
};

static Tk_ConfigSpec configSpecs[] =
{
    {TK_CONFIG_BORDER, "-activebackground", "activeBackground",
	"ActiveBackground",
	DEF_HT_ACTIVE_BG_COLOR, Tk_Offset(Hiertable, activeBorder),
	TK_CONFIG_COLOR_ONLY | TK_CONFIG_NULL_OK},
    {TK_CONFIG_BORDER, "-activebackground", "activeBackground",
	"ActiveBackground",
	DEF_HT_ACTIVE_BG_MONO, Tk_Offset(Hiertable, activeBorder),
	TK_CONFIG_MONO_ONLY | TK_CONFIG_NULL_OK},
    {TK_CONFIG_SYNONYM, "-activebg", "activeBackground", (char *)NULL, 
	(char *)NULL, 0, 0},
    {TK_CONFIG_SYNONYM, "-activefg", "activeForeground", (char *)NULL, 
	(char *)NULL, 0, 0},
    {TK_CONFIG_COLOR, "-activeforeground", "activeForeground", "Foreground",
	DEF_HT_ACTIVE_FG_COLOR, Tk_Offset(Hiertable, activeFgColor), 0},
    {TK_CONFIG_CUSTOM, "-activeicons", "activeIcons", "Icons",
	DEF_HT_ACTIVE_ICONS, Tk_Offset(Hiertable, activeIcons),
	TK_CONFIG_NULL_OK, &imagelistOption},
    {TK_CONFIG_BOOLEAN, "-allowduplicates", "allowDuplicates",
	"AllowDuplicates",
	DEF_HT_ALLOW_DUPLICATES, Tk_Offset(Hiertable, allowDups),
	TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_BOOLEAN, "-autocreate", "autoCreate", "AutoCreate",
	DEF_HT_MAKE_PATH, Tk_Offset(Hiertable, autoFill),
	TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_BORDER, "-background", "background", "Background",
	DEF_HT_BACKGROUND, Tk_Offset(Hiertable, 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_CUSTOM, "-borderwidth", "borderWidth", "BorderWidth",
	DEF_HT_BORDER_WIDTH, Tk_Offset(Hiertable, borderWidth),
	TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
    {TK_CONFIG_CUSTOM, "-button", "button", "Button",
	DEF_HT_BUTTON, Tk_Offset(Hiertable, buttonFlags),
	TK_CONFIG_DONT_SET_DEFAULT, &buttonOption},
    {TK_CONFIG_STRING, "-closecommand", "closeCommand", "CloseCommand",
	DEF_HT_COMMAND, Tk_Offset(Hiertable, closeCmd), 
	TK_CONFIG_NULL_OK},
    {TK_CONFIG_ACTIVE_CURSOR, "-cursor", "cursor", "Cursor",
	DEF_HT_CURSOR, Tk_Offset(Hiertable, cursor), TK_CONFIG_NULL_OK},
    {TK_CONFIG_CUSTOM, "-dashes", "dashes", "Dashes",
	DEF_HT_DASHES, Tk_Offset(Hiertable, dashes),
	TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
    {TK_CONFIG_BOOLEAN, "-exportselection", "exportSelection",
	"ExportSelection",
	DEF_HT_EXPORT_SELECTION, Tk_Offset(Hiertable, exportSelection),
	TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_SYNONYM, "-fg", "foreground", (char *)NULL, (char *)NULL, 0, 0},
    {TK_CONFIG_BOOLEAN, "-flat", "flat", "Flat",
	DEF_HT_FLAT, Tk_Offset(Hiertable, flatView),
	TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_CUSTOM, "-focusdashes", "focusDashes", "FocusDashes",
	DEF_HT_FOCUS_DASHES, Tk_Offset(Hiertable, focusDashes),
	TK_CONFIG_NULL_OK, &bltDashesOption},
    {TK_CONFIG_COLOR, "-focusforeground", "focusForeground",
	"FocusForeground",
	DEF_HT_FOCUS_FG_COLOR, Tk_Offset(Hiertable, focusColor),
	TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_COLOR, "-focusforeground", "focusForeground",
	"FocusForeground",
	DEF_HT_FOCUS_FG_MONO, Tk_Offset(Hiertable, focusColor),
	TK_CONFIG_MONO_ONLY},
    {TK_CONFIG_FONT, "-font", "font", "Font",
	DEF_HT_FONT, Tk_Offset(Hiertable, defColumn.font), 0},
    {TK_CONFIG_COLOR, "-foreground", "foreground", "Foreground",
	DEF_HT_TEXT_COLOR, Tk_Offset(Hiertable, defColumn.fgColor),
	TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_COLOR, "-foreground", "foreground", "Foreground",
	DEF_HT_TEXT_MONO, Tk_Offset(Hiertable, defColumn.fgColor), 
	TK_CONFIG_MONO_ONLY},
    {TK_CONFIG_CUSTOM, "-height", "height", "Height",
	DEF_HT_HEIGHT, Tk_Offset(Hiertable, reqHeight),
	TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
    {TK_CONFIG_BOOLEAN, "-hideroot", "hideRoot", "HideRoot",
	DEF_HT_HIDE_ROOT, Tk_Offset(Hiertable, hideRoot),
	TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_COLOR, "-highlightbackground", "highlightBackground",
	"HighlightBackground", DEF_HT_HIGHLIGHT_BG_COLOR, 
        Tk_Offset(Hiertable, highlightBgColor), TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_COLOR, "-highlightbackground", "highlightBackground",
	"HighlightBackground",
	DEF_HT_HIGHLIGHT_BG_MONO, Tk_Offset(Hiertable, highlightBgColor),
	TK_CONFIG_MONO_ONLY},
    {TK_CONFIG_COLOR, "-highlightcolor", "highlightColor", "HighlightColor",
	DEF_HT_HIGHLIGHT_COLOR, Tk_Offset(Hiertable, highlightColor), 0},
    {TK_CONFIG_PIXELS, "-highlightthickness", "highlightThickness",
	"HighlightThickness",
	DEF_HT_HIGHLIGHT_WIDTH, Tk_Offset(Hiertable, highlightWidth),
	TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_CUSTOM, "-icons", "icons", "Icons",
	DEF_HT_ICONS, Tk_Offset(Hiertable, icons),
	TK_CONFIG_NULL_OK, &imagelistOption},
    {TK_CONFIG_COLOR, "-linecolor", "lineColor", "LineColor",
	DEF_HT_LINE_COLOR, Tk_Offset(Hiertable, lineColor),
	TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_COLOR, "-linecolor", "lineColor", "LineColor",
	DEF_HT_LINE_MONO, Tk_Offset(Hiertable, lineColor),
	TK_CONFIG_MONO_ONLY},
    {TK_CONFIG_CUSTOM, "-linespacing", "lineSpacing", "LineSpacing",
	DEF_HT_LINE_SPACING, Tk_Offset(Hiertable, leader),
	TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
    {TK_CONFIG_CUSTOM, "-linewidth", "lineWidth", "LineWidth",
	DEF_HT_LINE_WIDTH, Tk_Offset(Hiertable, lineWidth),
	TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
    {TK_CONFIG_STRING, "-opencommand", "openCommand", "OpenCommand",
	DEF_HT_COMMAND, Tk_Offset(Hiertable, openCmd), 
	TK_CONFIG_NULL_OK},
    {TK_CONFIG_RELIEF, "-relief", "relief", "Relief",
	DEF_HT_RELIEF, Tk_Offset(Hiertable, relief), 0},
    {TK_CONFIG_CURSOR, "-resizecursor", "resizeCursor", "ResizeCursor",
	(char *)NULL, Tk_Offset(Hiertable, resizeCursor), TK_CONFIG_NULL_OK},
    {TK_CONFIG_CUSTOM, "-scrollmode", "scrollMode", "ScrollMode",
	DEF_HT_SCROLL_MODE, Tk_Offset(Hiertable, scrollMode),
	TK_CONFIG_DONT_SET_DEFAULT, &scrollmodeOption},
    {TK_CONFIG_BORDER, "-selectbackground", "selectBackground", "Foreground",
	DEF_HT_SELECT_BG_MONO, Tk_Offset(Hiertable, selBorder),
	TK_CONFIG_MONO_ONLY},
    {TK_CONFIG_BORDER, "-selectbackground", "selectBackground", "Foreground",
	DEF_HT_SELECT_BG_COLOR, Tk_Offset(Hiertable, selBorder),
	TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_CUSTOM, "-selectborderwidth", "selectBorderWidth", "BorderWidth",
	DEF_HT_SELECT_BORDER_WIDTH, Tk_Offset(Hiertable, selBorderWidth),
	TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
    {TK_CONFIG_STRING, "-selectcommand", "selectCommand", "SelectCommand",
	DEF_HT_SELECT_CMD, Tk_Offset(Hiertable, selectCmd),
	TK_CONFIG_NULL_OK},
    {TK_CONFIG_COLOR, "-selectforeground", "selectForeground", "Background",
	DEF_HT_SELECT_FG_MONO, Tk_Offset(Hiertable, selFgColor),
	TK_CONFIG_MONO_ONLY},
    {TK_CONFIG_COLOR, "-selectforeground", "selectForeground", "Background",
	DEF_HT_SELECT_FG_COLOR, Tk_Offset(Hiertable, selFgColor),
	TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_CUSTOM, "-selectmode", "selectMode", "SelectMode",
	DEF_HT_SELECT_MODE, Tk_Offset(Hiertable, selectMode),
	TK_CONFIG_DONT_SET_DEFAULT, &selectmodeOption},
    {TK_CONFIG_RELIEF, "-selectrelief", "selectRelief", "Relief",
	DEF_HT_SELECT_RELIEF, Tk_Offset(Hiertable, selRelief),
	TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_CUSTOM, "-separator", "separator", "Separator",
	DEF_HT_SEPARATOR, Tk_Offset(Hiertable, pathSep), 
        TK_CONFIG_NULL_OK, &separatorOption},
    {TK_CONFIG_BOOLEAN, "-showtitles", "showTitles", "ShowTitles",
	DEF_HT_SHOW_TITLES, Tk_Offset(Hiertable, showTitles), 0},
    {TK_CONFIG_BOOLEAN, "-sortselection", "sortSelection", "SortSelection",
	DEF_HT_SORT_SELECTION, Tk_Offset(Hiertable, sortSelection), 
        TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_STRING, "-takefocus", "takeFocus", "TakeFocus",
	DEF_HT_TAKE_FOCUS, Tk_Offset(Hiertable, takeFocus), 
	TK_CONFIG_NULL_OK},
    {TK_CONFIG_CUSTOM, "-tree", "tree", "Tree",
	"", 0, TK_CONFIG_NULL_OK, &treeOption},
    {TK_CONFIG_STRING, "-trim", "trim", "Trim",
	DEF_HT_TRIMLEFT, Tk_Offset(Hiertable, trimLeft), 
	TK_CONFIG_NULL_OK},
    {TK_CONFIG_CUSTOM, "-width", "width", "Width",
	DEF_HT_WIDTH, Tk_Offset(Hiertable, reqWidth),
	TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
    {TK_CONFIG_STRING, "-xscrollcommand", "xScrollCommand", "ScrollCommand",
	(char *)NULL, Tk_Offset(Hiertable, xScrollCmdPrefix), 
	TK_CONFIG_NULL_OK},
    {TK_CONFIG_CUSTOM, "-xscrollincrement", "xScrollIncrement",
	"ScrollIncrement",
	DEF_HT_SCROLL_INCREMENT, Tk_Offset(Hiertable, xScrollUnits),
	TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
    {TK_CONFIG_STRING, "-yscrollcommand", "yScrollCommand", "ScrollCommand",
	(char *)NULL, Tk_Offset(Hiertable, yScrollCmdPrefix), 
	TK_CONFIG_NULL_OK},
    {TK_CONFIG_CUSTOM, "-yscrollincrement", "yScrollIncrement",
	"ScrollIncrement",
	DEF_HT_SCROLL_INCREMENT, Tk_Offset(Hiertable, yScrollUnits),
	TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
    {TK_CONFIG_END, (char *)NULL, (char *)NULL, (char *)NULL,
	(char *)NULL, 0, 0}
};

/* Forward Declarations */
static void DestroyHiertable _ANSI_ARGS_((DestroyData dataPtr));
static void HiertableEventProc _ANSI_ARGS_((ClientData clientdata,
	XEvent *eventPtr));
static void DisplayHiertable _ANSI_ARGS_((ClientData clientData));
static void WidgetInstCmdDeleteProc _ANSI_ARGS_((ClientData clientdata));
static int ComputeVisibleEntries _ANSI_ARGS_((Hiertable *htabPtr));
static int TreeEventProc _ANSI_ARGS_((ClientData clientData, 
	Blt_TreeNotifyEvent *eventPtr));
static int TreeTraceProc _ANSI_ARGS_((ClientData clientData, Tcl_Interp *interp,
	Blt_TreeNode node, Blt_Uid keyUid, unsigned int flags));

#ifdef __STDC__
extern Blt_TreeApplyProc Blt_HtSortApplyProc;
static Tk_ImageChangedProc ImageChangedProc;
static BindPickProc PickButton, PickEntry, PickColumn;
static BindTagProc GetTags, GetColumnTags;
static Tk_SelectionProc SelectionProc;
static Tcl_CmdProc HiertableCmd;
static Tcl_FreeProc DestroyEntry;
#endif /* __STDC__ */


/*
 *----------------------------------------------------------------------
 *
 * Blt_HtEventuallyRedraw --
 *
 *	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.
 *
 *----------------------------------------------------------------------
 */
void
Blt_HtEventuallyRedraw(htabPtr)
    Hiertable *htabPtr;
{
    if ((htabPtr->tkwin != NULL) && !(htabPtr->flags & HT_REDRAW)) {
	htabPtr->flags |= HT_REDRAW;
	Tcl_DoWhenIdle(DisplayHiertable, (ClientData)htabPtr);
    }
}

static void
TraceColumns(htabPtr)
    Hiertable *htabPtr;
{
    Blt_ChainLink *linkPtr;
    Column *columnPtr;

    for(linkPtr = Blt_ChainFirstLink(htabPtr->chainPtr); linkPtr != NULL;
	linkPtr = Blt_ChainNextLink(linkPtr)) {
	columnPtr = (Column *)Blt_ChainGetValue(linkPtr);
	Blt_TreeCreateTrace(htabPtr->tree, NULL, columnPtr->keyUid, 
		TREE_TRACE_FOREIGN_ONLY | TREE_TRACE_WRITE | TREE_TRACE_UNSET, 
		TreeTraceProc, (ClientData)htabPtr);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * StringToTree --
 *
 *	Convert the string representing the name of a tree object 
 *	into a tree token.
 *
 * 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
StringToTree(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 representation. */
    char *widgRec;		/* Widget record. */
    int offset;			/* Offset of field in structure. */
{
    Hiertable *htabPtr = (Hiertable *)widgRec;

    /* 
     * Release the current tree, removing any entry fields. 
     */
    if (htabPtr->tree != NULL) {
	Blt_TreeApply(htabPtr->rootPtr->node, DeleteApplyProc, 
	      (ClientData)htabPtr);
	Blt_HtClearSelection(htabPtr);
	Blt_TreeReleaseToken(htabPtr->tree);
	htabPtr->tree = NULL;
    }
    if ((string != NULL) && (string[0] != '\0')) {
	Blt_Tree token;

	if (Blt_TreeGetToken(interp, string, &token) != TCL_OK) {
	    return TCL_ERROR;
	}
	htabPtr->tree = token;
    }
    return TCL_OK;
}

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

    if (htabPtr->tree == NULL) {
	return "";
    }
    return Blt_TreeName(htabPtr->tree);
}

/*
 *----------------------------------------------------------------------
 *
 * 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 = SCROLL_MODE_LISTBOX;
    } else if ((string[0] == 'h') && (strcmp(string, "hiertable") == 0)) {
	*modePtr = SCROLL_MODE_HIERBOX;
    } else if ((string[0] == 'c') && (strcmp(string, "canvas") == 0)) {
	*modePtr = SCROLL_MODE_CANVAS;
    } else {
	Tcl_AppendResult(interp, "bad scroll mode \"", string,
	    "\": should be \"hiertable\", \"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 SCROLL_MODE_LISTBOX:
	return "listbox";
    case SCROLL_MODE_HIERBOX:
	return "hierbox";
    case SCROLL_MODE_CANVAS:
	return "canvas";
    default:
	return "unknown scroll mode";
    }
}

/*
 *----------------------------------------------------------------------
 *
 * StringToSelectmode --
 *
 *	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
StringToSelectmode(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] == 's') && (strcmp(string, "single") == 0)) {
	*modePtr = SELECT_MODE_SINGLE;
    } else if ((string[0] == 'm') && (strcmp(string, "multiple") == 0)) {
	*modePtr = SELECT_MODE_MULTIPLE;
    } else if ((string[0] == 'a') && (strcmp(string, "active") == 0)) {
	*modePtr = SELECT_MODE_SINGLE;
    } else {
	Tcl_AppendResult(interp, "bad select mode \"", string,
	    "\": should be \"single\" or \"multiple\"", (char *)NULL);
	return TCL_ERROR;
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * SelectmodeToString --
 *
 * Results:
 *	The string representation of the button boolean is returned.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static char *
SelectmodeToString(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 SELECT_MODE_SINGLE:
	return "single";
    case SELECT_MODE_MULTIPLE:
	return "multiple";
    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";
    }
}

/*
 *----------------------------------------------------------------------
 *
 * 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)) {
	free(*sepPtr);
    }
    if ((string == NULL) || (*string == '\0')) {
	*sepPtr = SEPARATOR_LIST;
    } else if (strcmp(string, "none") == 0) {
	*sepPtr = SEPARATOR_NONE;
    } else {
	*sepPtr = 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;
}

/*
 *----------------------------------------------------------------------
 *
 * Blt_HtGetUid --
 *
 *	Gets or creates a unique string identifier.  Strings are
 *	reference counted.  The string is placed into a hashed table
 *	local to the hiertable.
 *
 * Results:
 *	Returns the pointer to the hashed string.
 *
 *---------------------------------------------------------------------- 
 */
UID
Blt_HtGetUid(htabPtr, string)
    Hiertable *htabPtr;
    char *string;
{
    Tcl_HashEntry *hPtr;
    int refCount;
    int isNew;

    hPtr = Tcl_CreateHashEntry(&(htabPtr->uidTable), string, &isNew);
    if (isNew) {
	refCount = 1;
    } else {
	refCount = (int)Tcl_GetHashValue(hPtr);
	refCount++;
    }
    Tcl_SetHashValue(hPtr, (ClientData)refCount);
    return Tcl_GetHashKey(&(htabPtr->uidTable), hPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * Blt_HtFreeUid --
 *
 *	Releases the uid.  Uids are reference counted, so only when
 *	the reference count is zero (i.e. no one else is using the
 *	string) is the entry removed from the hash table.
 *
 * Results:
 *	None.
 *
 *---------------------------------------------------------------------- 
 */
void
Blt_HtFreeUid(htabPtr, uid)
    Hiertable *htabPtr;
    UID uid;
{
    Tcl_HashEntry *hPtr;
    int refCount;

    hPtr = Tcl_FindHashEntry(&(htabPtr->uidTable), uid);
    assert(hPtr != NULL);
    refCount = (int)Tcl_GetHashValue(hPtr);
    refCount--;
    if (refCount > 0) {
	Tcl_SetHashValue(hPtr, (ClientData)refCount);
    } else {
	Tcl_DeleteHashEntry(hPtr);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * StringToUid --
 *
 *	Converts the string to a Uid. Uid's are hashed, reference
 *	counted strings.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
StringToUid(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;		/* Fill style string */
    char *widgRec;		/* Cubicle structure record */
    int offset;			/* Offset of style in record */
{
    Hiertable *htabPtr = bltHiertableLastInstance;
    UID *uidPtr = (Tk_Uid *)(widgRec + offset);
    UID newId;

    newId = NULL;
    if ((string != NULL) && (*string != '\0')) {
	newId = Blt_HtGetUid(htabPtr, string);
    }
    if (*uidPtr != NULL) {
	Blt_HtFreeUid(htabPtr, *uidPtr);
    }
    *uidPtr = newId;
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * UidToString --
 *
 *	Returns the uid as a string.
 *
 * Results:
 *	The fill style string is returned.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static char *
UidToString(clientData, tkwin, widgRec, offset, freeProcPtr)
    ClientData clientData;	/* Not used. */
    Tk_Window tkwin;		/* Not used. */
    char *widgRec;		/* Widget structure record */
    int offset;			/* Offset of fill in widget record */
    Tcl_FreeProc **freeProcPtr;	/* Not used. */
{
    UID uid = *(UID *)(widgRec + offset);

    return (uid == NULL) ? "" : uid;
}

/*
 *----------------------------------------------------------------------
 *
 * 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. */
{
    Hiertable *htabPtr = (Hiertable *)clientData;

    htabPtr->flags |= (HT_DIRTY | HT_LAYOUT | HT_SCROLL);
    Blt_HtEventuallyRedraw(htabPtr);
}

HashedImage
Blt_HtGetImage(htabPtr, imageName)
    Hiertable *htabPtr;
    char *imageName;
{
    struct HashedImageRec *imgPtr;
    int isNew;
    Tcl_HashEntry *hPtr;

    hPtr = Tcl_CreateHashEntry(&(htabPtr->imageTable), imageName, &isNew);
    if (isNew) {
	Tk_Image tkImage;
	int width, height;

	tkImage = Tk_GetImage(htabPtr->interp, htabPtr->tkwin, imageName, 
		ImageChangedProc, (ClientData)htabPtr);
	if (tkImage == NULL) {
	    Tcl_DeleteHashEntry(hPtr);
	    return NULL;
	}
	Tk_SizeOfImage(tkImage, &width, &height);
	imgPtr = (struct HashedImageRec *)malloc(sizeof(struct HashedImageRec));
	imgPtr->tkImage = tkImage;
	imgPtr->hashPtr = hPtr;
	imgPtr->refCount = 1;
	imgPtr->width = width;
	imgPtr->height = height;
	Tcl_SetHashValue(hPtr, (ClientData)imgPtr);
    } else {
	imgPtr = (struct HashedImageRec *)Tcl_GetHashValue(hPtr);
	imgPtr->refCount++;
    }
    return imgPtr;
}

void
Blt_HtFreeImage(imgPtr)
    struct HashedImageRec *imgPtr;
{
    imgPtr->refCount--;
    if (imgPtr->refCount == 0) {
	Tcl_DeleteHashEntry(imgPtr->hashPtr);
	Tk_FreeImage(imgPtr->tkImage);
	free((char *)imgPtr);
    }
}

void
FreeImageTable(htabPtr)
    Hiertable *htabPtr;
{
    struct HashedImageRec *imgPtr;
    Tcl_HashEntry *hPtr;
    Tcl_HashSearch cursor;

    for (hPtr = Tcl_FirstHashEntry(&(htabPtr->imageTable), &cursor);
	 hPtr != NULL; hPtr = Tcl_NextHashEntry(&cursor)) {
	imgPtr = (struct HashedImageRec *)Tcl_GetHashValue(hPtr);
	Tk_FreeImage(imgPtr->tkImage);
	free((char *)imgPtr);
    }
    Tcl_DeleteHashTable(&(htabPtr->imageTable));
}

/*
 *----------------------------------------------------------------------
 *
 * StringToImagelist --
 *
 *	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
StringToImagelist(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 */
{
    Hiertable *htabPtr = *(Hiertable **)clientData;
    HashedImage **imgPtrPtr = (HashedImage **) (widgRec + offset);
    HashedImage *hImgArr;
    int result;

    result = TCL_OK;
    hImgArr = 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;

	    hImgArr = (HashedImage *)
		malloc(sizeof(HashedImage *) * (nNames + 1));
	    assert(hImgArr);
	    for (i = 0; i < nNames; i++) {
		hImgArr[i] = Blt_HtGetImage(htabPtr, nameArr[i]);
		if (hImgArr[i] == NULL) {
		    result = TCL_ERROR;
		    break;
		}
	    }
	    free((char *)nameArr);
	    hImgArr[nNames] = NULL;
	}
    }
    if (*imgPtrPtr != NULL) {
	register HashedImage *imgPtr;

	for (imgPtr = *imgPtrPtr; *imgPtr != NULL; imgPtr++) {
	    Blt_HtFreeImage(*imgPtr);
	}
	free((char *)*imgPtrPtr);
    }
    *imgPtrPtr = hImgArr;
    return result;
}

/*
 *----------------------------------------------------------------------
 *
 * ImagelistToString --
 *
 *	Converts the image into its string representation (its name).
 *
 * Results:
 *	The name of the image is returned.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static char *
ImagelistToString(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 */
{
    HashedImage **imgPtrPtr = (HashedImage **) (widgRec + offset);
    Tcl_DString dString;
    char *result;

    Tcl_DStringInit(&dString);
    if (*imgPtrPtr != NULL) {
	register HashedImage *imgPtr;
	Tk_ImageMaster imageMaster;

	for (imgPtr = *imgPtrPtr; *imgPtr != NULL; imgPtr++) {
	    imageMaster = Blt_TkImageMaster((*imgPtr)->tkImage);
	    Tcl_DStringAppendElement(&dString, Tk_NameOfImage(imageMaster));
	}
    }
    result = strdup(Tcl_DStringValue(&dString));
    Tcl_DStringFree(&dString);
    *freeProcPtr = (Tcl_FreeProc *)free;
    return result;
}


int
Blt_HtTreeApply(htabPtr, entryPtr, proc, checkFlags)
    Hiertable *htabPtr;
    Entry *entryPtr;		/* Root entry of subtree. */
    ApplyProc *proc;		/* Procedure to call for each entry. */
    unsigned int checkFlags;
{
    if ((checkFlags & ENTRY_HIDDEN) && (entryPtr->flags & ENTRY_HIDDEN)) {
	return TCL_OK;		/* Hidden node. */
    }
    if (!(checkFlags & ENTRY_CLOSED) || !(entryPtr->flags & ENTRY_CLOSED)) {
	Entry *childPtr;
	Blt_TreeNode node, next;

	next = NULL;
	for (node = Blt_TreeFirstChild(entryPtr->node); node != NULL; 
	     node = next) {
	    next = Blt_TreeNextSibling(node);
	    /* 
	     * Get the next child before calling Blt_HtTreeApply recursively.  
	     * This is because the apply callback may delete the node and 
	     * its link.  
	     */
	    childPtr = NodeToEntry(htabPtr, node);
	    if (Blt_HtTreeApply(htabPtr, childPtr, proc, checkFlags) 
		!= TCL_OK) {
		return TCL_ERROR;
	    }
	}
    }
    if ((*proc) (htabPtr, entryPtr) != TCL_OK) {
	return TCL_ERROR;
    }
    return TCL_OK;
}


int
Blt_HtEntryIsMapped(htabPtr, entryPtr)
    Hiertable *htabPtr;
    Entry *entryPtr;
{
    /* Don't check if the entry itself is open, only that its
     * ancestors are. */
    if (entryPtr->flags & ENTRY_HIDDEN) {
	return FALSE;
    }
    if (entryPtr == htabPtr->rootPtr) {
	return TRUE;
    }
    entryPtr = Blt_HtParentEntry(htabPtr, entryPtr);
    while (entryPtr != htabPtr->rootPtr) {
	if (entryPtr->flags & (ENTRY_CLOSED | ENTRY_HIDDEN)) {
	    return FALSE;
	}
	entryPtr = Blt_HtParentEntry(htabPtr, entryPtr);
    }
    return TRUE;
}

/*
 *----------------------------------------------------------------------
 *
 * PrevNode --
 *
 *	Returns the "previous" node in the tree.  This node (in 
 *	depth-first order) is its parent, if the node has no siblings
 *	that are previous to it.  Otherwise it is the last descendant 
 *	of the last sibling.  In this case, descend the sibling's
 *	hierarchy, using the last child at any ancestor, with we
 *	we find a leaf.
 *
 *----------------------------------------------------------------------
 */
Entry *
Blt_HtPrevEntry(htabPtr, entryPtr, mask)
    Hiertable *htabPtr;
    Entry *entryPtr;
    unsigned int mask;
{
    Blt_TreeNode prevNode;

    if (entryPtr->node == Blt_TreeRootNode(htabPtr->tree)) {
	return NULL;		/* The root is the first node. */
    }
    prevNode = Blt_TreePrevSibling(entryPtr->node);
    if (prevNode == NULL) {
	/* There are no siblings previous to this one, so pick the parent. */
	prevNode = Blt_TreeNodeParent(entryPtr->node);
	
    } else {
	Blt_TreeNode node;
	/*
	 * Traverse down the right-most thread in order to select the
	 * last entry.  Stop if we find a "closed" entry or reach a leaf.
	 */
	node = prevNode;
	entryPtr = NodeToEntry(htabPtr, node);
	while ((entryPtr->flags & mask) == 0) {
	    node = Blt_TreeLastChild(node);
	    if (node == NULL) {
		break;		/* Found a leaf. */
	    }
	    prevNode = node;
	    entryPtr = NodeToEntry(htabPtr, node);
	}
    }
    if (prevNode == NULL) {
	return NULL;
    }
    return NodeToEntry(htabPtr, prevNode);
}

/*
 *----------------------------------------------------------------------
 *
 * Blt_HtNextNode --
 *
 *	Returns the "next" node in relation to the given node.  
 *	The next node (in depth-first order) is either the first 
 *	child of the given node the next sibling if the node has
 *	no children (the node is a leaf).  If the given node is the 
 *	last sibling, then try it's parent next sibling.  Continue
 *	until we either find a next sibling for some ancestor or 
 *	we reach the root node.  In this case the current node is 
 *	the last node in the tree.
 *
 *----------------------------------------------------------------------
 */
Entry *
Blt_HtNextEntry(htabPtr, entryPtr, mask)
    Hiertable *htabPtr;
    Entry *entryPtr;
    unsigned int mask;
{
    Blt_TreeNode  next;

    if ((entryPtr->flags & mask) == 0) {
	/* Pick the first sub-node. */
	next = Blt_TreeFirstChild(entryPtr->node); 
	if (next != NULL) {
	    return NodeToEntry(htabPtr, next);
	}
    }
    /* 
     * Back up until we can find a level where we can pick a 
     * "next sibling".  For the last entry we'll thread our 
     * way back to the root.  
     */
    while (entryPtr != htabPtr->rootPtr) {
	next = Blt_TreeNextSibling(entryPtr->node);
	if (next != NULL) {
	    return NodeToEntry(htabPtr, next);
	}
	entryPtr = Blt_HtParentEntry(htabPtr, entryPtr);
    }
    return NULL;		/* At root, no next node. */
}

void
Blt_HtConfigureButtons(htabPtr)
    Hiertable *htabPtr;
{
    Button *buttonPtr = &(htabPtr->button);
    unsigned long gcMask;
    XGCValues gcValues;
    GC newGC;

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

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

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

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

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

void
Blt_HtConfigureEntry(htabPtr, entryPtr)
    Hiertable *htabPtr;
    Entry *entryPtr;
{
    GC newGC;

    newGC = NULL;
    if ((entryPtr->font != NULL) || (entryPtr->color != NULL)) {
	XColor *colorPtr;
	Tk_Font font;
	XGCValues gcValues;
	unsigned long gcMask;

	font = GETFONT(htabPtr->treeColumnPtr->font, entryPtr->font);
	colorPtr = GETCOLOR(htabPtr->treeColumnPtr->fgColor, entryPtr->color);
	gcMask = GCForeground | GCFont;
	gcValues.foreground = colorPtr->pixel;
	gcValues.font = Tk_FontId(font);
	newGC = Tk_GetGC(htabPtr->tkwin, gcMask, &gcValues);
    }
    if (entryPtr->gc != NULL) {
	Tk_FreeGC(htabPtr->display, entryPtr->gc);
    }
    /* Assume all changes require a new layout. */
    entryPtr->gc = newGC;
    entryPtr->flags |= ENTRY_DIRTY;
    htabPtr->flags |= (HT_LAYOUT | HT_DIRTY);
}

/*ARGSUSED*/
static int
SetEntryObjFromAny(interp, objPtr)
    Tcl_Interp *interp;
    Tcl_Obj *objPtr;
{
    fprintf(stderr, "SetEntryObjFromAny\n");
    Tcl_AppendResult(interp, "can't reset entry object", (char *)NULL);
    return TCL_ERROR;
}

static void
UpdateStringOfEntry(objPtr)
    Tcl_Obj *objPtr;		/* Entry object whose string rep to update. */
{
    Entry *entryPtr;
    char *label;

    fprintf(stderr, "UpdateStringOfEntry\n");
    entryPtr = (Entry *)objPtr->internalRep.otherValuePtr;
    label = GETLABEL(entryPtr);
    objPtr->bytes = label;
    objPtr->length = strlen(label);
}

void
Blt_HtDestroyField(fieldPtr)
    Field *fieldPtr;
{
    if (fieldPtr->hImage != NULL) {
	Blt_HtFreeImage(fieldPtr->hImage);
    }
    if (fieldPtr->layoutPtr != NULL) {
	free((char *)fieldPtr->layoutPtr);
    }
    free((char *)fieldPtr);
}


static void
DestroyEntry(data)
    DestroyData data;
{
    Entry *entryPtr = (Entry *)data;
    Hiertable *htabPtr = entryPtr->htabPtr;
    
    Tk_FreeOptions(htabPtr->entrySpecs, (char *)entryPtr, htabPtr->display, 0);

    if (entryPtr->gc != NULL) {
	Tk_FreeGC(htabPtr->display, entryPtr->gc);
    }
    if (entryPtr->shadow.color != NULL) {
	Tk_FreeColor(entryPtr->shadow.color);
    }
    if (htabPtr->flags & HT_DESTROYED) {
	if (entryPtr->icons != NULL) {
	    free((char *)entryPtr->icons);
	}
	if (entryPtr->activeIcons != NULL) {
	    free((char *)entryPtr->activeIcons);
	}
	/* Delete the chain of column fields from the entry. */
	if (entryPtr->chainPtr != NULL) {
	    Blt_ChainLink *linkPtr;
	    Field *fieldPtr;
	    
	    for (linkPtr = Blt_ChainFirstLink(entryPtr->chainPtr); 
		 linkPtr != NULL; linkPtr = Blt_ChainNextLink(linkPtr)) {
		fieldPtr = (Field *)Blt_ChainGetValue(linkPtr);
		if (fieldPtr->layoutPtr != NULL) {
		    free((char *)fieldPtr->layoutPtr);
		}
		free((char *)fieldPtr);
	    }
	    Blt_ChainDestroy(entryPtr->chainPtr);
	}
    } else {
	register HashedImage *imgPtr;

	if (entryPtr->openCmd != NULL) {
	    Blt_HtFreeUid(htabPtr, entryPtr->openCmd);
	}
	if (entryPtr->closeCmd != NULL) {
	    Blt_HtFreeUid(htabPtr, entryPtr->closeCmd);
	}
	if (entryPtr->tags != NULL) {
	    Blt_HtFreeUid(htabPtr, entryPtr->tags);
	}
	if (entryPtr->labelUid != NULL) {
	    Blt_HtFreeUid(htabPtr, entryPtr->labelUid);
	}
	if (entryPtr->icons != NULL) {
	    for (imgPtr = entryPtr->icons; *imgPtr != NULL; imgPtr++) {
		Blt_HtFreeImage(*imgPtr);
	    }
	    free((char *)entryPtr->icons);
	}
	if (entryPtr->activeIcons != NULL) {
	    for (imgPtr = entryPtr->activeIcons; *imgPtr != NULL; imgPtr++) {
		Blt_HtFreeImage(*imgPtr);
	    }
	    free((char *)entryPtr->activeIcons);
	}
	/* Delete the chain of column fields from the entry. */
	if (entryPtr->chainPtr != NULL) {
	    Blt_ChainLink *linkPtr;
	    Field *fieldPtr;
	    
	    for (linkPtr = Blt_ChainFirstLink(entryPtr->chainPtr); 
		 linkPtr != NULL; linkPtr = Blt_ChainNextLink(linkPtr)) {
		fieldPtr = (Field *)Blt_ChainGetValue(linkPtr);
		Blt_HtDestroyField(fieldPtr);
	    }
	    Blt_ChainDestroy(entryPtr->chainPtr);
	}
    }
    if (entryPtr->fullName != NULL) {
	free(entryPtr->fullName);
    }
    if (entryPtr->layoutPtr != NULL) {
	free((char *)entryPtr->layoutPtr);
    }
    free((char *)entryPtr);
}

Entry *
Blt_HtParentEntry(htabPtr, entryPtr) 
    Hiertable *htabPtr;
    Entry *entryPtr;
{
    Blt_TreeNode node;

    if (entryPtr->node == Blt_TreeRootNode(htabPtr->tree)) {
	return NULL;
    }
    node = Blt_TreeNodeParent(entryPtr->node);
    if (node == NULL) {
	return NULL;
    }
    return NodeToEntry(htabPtr, node);
}

static void
FreeEntryInternalRep(objPtr)
    Tcl_Obj *objPtr;		/* Entry object whose string rep to update. */
{
    Entry *entryPtr;
    Hiertable *htabPtr;

    /* fprintf(stderr, "FreeEntryInternalRep\n"); */
    entryPtr = (Entry *)objPtr->internalRep.otherValuePtr;

    htabPtr = entryPtr->htabPtr;
    if (htabPtr->flags & HT_DESTROYED) {
	/* 
	 * If we know that the hiertable is being destroyed, we don't
	 * need to reset the various focus/active/selection pointers
	 * or delete the individual bindings.
	 */
	DestroyEntry((DestroyData)entryPtr);
    } else {
	if (entryPtr == htabPtr->activePtr) {
	    htabPtr->activePtr = Blt_HtParentEntry(htabPtr, entryPtr);
	}
	if (entryPtr == htabPtr->activeButtonPtr) {
	    htabPtr->activeButtonPtr = NULL;
	}
	if (entryPtr == htabPtr->focusPtr) {
	    htabPtr->focusPtr = Blt_HtParentEntry(htabPtr, entryPtr);
	    Blt_SetFocusItem(htabPtr->bindTable, htabPtr->focusPtr);
	}
	if (entryPtr == htabPtr->selAnchorPtr) {
	    htabPtr->selAnchorPtr = NULL;
	}
	Blt_HtDeselectEntry(htabPtr, entryPtr);
	Blt_HtPruneSelection(htabPtr, entryPtr);
	Blt_DeleteBindings(htabPtr->bindTable, (ClientData)entryPtr);
	Blt_DeleteBindings(htabPtr->buttonBindTable, (ClientData)entryPtr);
	entryPtr->node = NULL;
	Tcl_EventuallyFree((ClientData)entryPtr, DestroyEntry);
	/*
	 * Indicate that the screen layout of the hierarchy may have changed
	 * because this node was deleted.  The screen positions of the nodes
	 * in htabPtr->visibleArr are invalidated.
	 */
	htabPtr->flags |= (HT_LAYOUT | HT_DIRTY);
	Blt_HtEventuallyRedraw(htabPtr);
    }
}


int
Blt_HtEntryIsSelected(htabPtr, entryPtr)
    Hiertable *htabPtr;
    Entry *entryPtr;
{
    Tcl_HashEntry *hPtr;

    hPtr = Tcl_FindHashEntry(&(htabPtr->selectTable), (char *)entryPtr);
    return (hPtr != NULL);
}


void
Blt_HtSelectEntry(htabPtr, entryPtr)
    Hiertable *htabPtr;
    Entry *entryPtr;
{
    int isNew;
    Tcl_HashEntry *hPtr;

    hPtr = Tcl_CreateHashEntry(&(htabPtr->selectTable), (char *)entryPtr, 
       &isNew);
    if (isNew) {
	Blt_ChainLink *linkPtr;

	linkPtr = Blt_ChainAppend(htabPtr->selectPtr, (ClientData)entryPtr);
	Tcl_SetHashValue(hPtr, (ClientData)linkPtr);
    }
}

void
Blt_HtDeselectEntry(htabPtr, entryPtr)
    Hiertable *htabPtr;
    Entry *entryPtr;
{
    Tcl_HashEntry *hPtr;

    hPtr = Tcl_FindHashEntry(&(htabPtr->selectTable), (char *)entryPtr);
    if (hPtr != NULL) {
	Blt_ChainLink *linkPtr;

	linkPtr = (Blt_ChainLink *)Tcl_GetHashValue(hPtr);
	Blt_ChainDeleteLink(htabPtr->selectPtr, linkPtr);
	Tcl_DeleteHashEntry(hPtr);
    }
}

char *
Blt_HtGetFullName(htabPtr, entryPtr, checkEntryLabel)
    Hiertable *htabPtr;
    Entry *entryPtr;
    int checkEntryLabel;
{
    char **nameArr;		/* Used the stack the component names. */
    char *staticSpace[64];
    register int i;
    int level;
    Tcl_DString dString;
    char *fullPath;
    Blt_TreeNode node;

    level = Blt_TreeNodeDepth(htabPtr->tree, entryPtr->node);
    if (level > 64) {
	nameArr = (char **)malloc(level * sizeof(char *));
	assert(nameArr);
    } else {
	nameArr = staticSpace;
    }
    for (i = level; i >= 0; i--) {
	/* Save the name of each ancestor in the name array. */
	if (checkEntryLabel) {
	    nameArr[i] = GETLABEL(entryPtr);
	} else {
	    nameArr[i] = Blt_TreeNodeLabel(entryPtr->node);
	}
	node = Blt_TreeNodeParent(entryPtr->node);
	if (node != NULL) {
	    entryPtr = NodeToEntry(htabPtr, node);
	}
    }
    Tcl_DStringInit(&dString);
    if ((htabPtr->pathSep == SEPARATOR_LIST) || 
	(htabPtr->pathSep == SEPARATOR_NONE)) {
	for (i = 0; i <= level; i++) {
	    Tcl_DStringAppendElement(&dString, nameArr[i]);
	}
    } else {
	Tcl_DStringAppend(&dString, nameArr[0], -1);
	if (strcmp(nameArr[0], htabPtr->pathSep) != 0) {
	    Tcl_DStringAppend(&dString, htabPtr->pathSep, -1);
	}
	if (level > 0) {
	    for (i = 1; i < level; i++) {
		Tcl_DStringAppend(&dString, nameArr[i], -1);
		Tcl_DStringAppend(&dString, htabPtr->pathSep, -1);
	    }
	    Tcl_DStringAppend(&dString, nameArr[i], -1);
	}
    }
    if (nameArr != staticSpace) {
	free((char *)nameArr);
    }
    fullPath = strdup(Tcl_DStringValue(&dString));
    Tcl_DStringFree(&dString);
    return fullPath;
}


int
Blt_HtCloseEntry(htabPtr, entryPtr)
    Hiertable *htabPtr;
    Entry *entryPtr;
{
    char *cmd;

    if (entryPtr->flags & ENTRY_CLOSED) {
	return TCL_OK;		/* Entry is already closed. */
    }
    entryPtr->flags |= ENTRY_CLOSED;

    /*
     * Invoke the entry's "close" command, if there is one. Otherwise
     * try the hiertable's global "close" command.
     */
    cmd = htabPtr->closeCmd;
    if (entryPtr->closeCmd != NULL) {
	cmd = entryPtr->closeCmd;
    }
    if (cmd != NULL) {
	Tcl_DString dString;
	int result;

	Blt_HtPercentSubst(htabPtr, entryPtr, cmd, &dString);
	Tcl_Preserve(entryPtr);
	result = Tcl_GlobalEval(htabPtr->interp, Tcl_DStringValue(&dString));
	Tcl_Release(entryPtr);
	Tcl_DStringFree(&dString);
	if (result != TCL_OK) {
	    return TCL_ERROR;
	}
    }
    htabPtr->flags |= HT_LAYOUT;
    return TCL_OK;
}


int
Blt_HtOpenEntry(htabPtr, entryPtr) 
    Hiertable *htabPtr;
    Entry *entryPtr;
{
    char *cmd;

    if (!(entryPtr->flags & ENTRY_CLOSED)) {
	return TCL_OK;		/* Entry is already open. */
    }
    entryPtr->flags &= ~ENTRY_CLOSED;
    /*
     * If there's a "open" command proc specified for the entry, use
     * that instead of the more general "open" proc for the entire 
     * hiertable.
     */
    cmd = htabPtr->openCmd;
    if (entryPtr->openCmd != NULL) {
	cmd = entryPtr->openCmd;
    }
    if (cmd != NULL) {
	Tcl_DString dString;
	int result;

	Blt_HtPercentSubst(htabPtr, entryPtr, cmd, &dString);
	Tcl_Preserve(entryPtr);
	result = Tcl_GlobalEval(htabPtr->interp, Tcl_DStringValue(&dString));
	Tcl_Release(entryPtr);
	Tcl_DStringFree(&dString);
	if (result != TCL_OK) {
	    return TCL_ERROR;
	}
    }
    htabPtr->flags |= HT_LAYOUT;
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * Blt_HtCreateEntry --
 *
 *	This procedure is called by the Tree object when a node is 
 *	created and inserted into the tree.  It adds a new hiertable 
 *	entry field to the node.
 *
 * Results:
 *	Returns TCL_OK is the entry was added, TCL_ERROR otherwise.
 *
 *----------------------------------------------------------------------
 */
int
Blt_HtCreateEntry(htabPtr, node, nOptions, options)
    Hiertable *htabPtr;
    Blt_TreeNode node;		/* Node that has just been created. */
    int nOptions;
    char **options;
{
    Entry *entryPtr;
    Tcl_Obj *objPtr;
    Blt_ChainLink *linkPtr;
    Column *columnPtr;

    /* Create the entry structure */
    entryPtr = (Entry *)calloc(1, sizeof(Entry));
    assert(entryPtr);
    entryPtr->flags = htabPtr->buttonFlags | ENTRY_CLOSED;
    entryPtr->htabPtr = htabPtr;

    entryPtr->labelUid = NULL;
    entryPtr->node = node;
    bltHiertableLastInstance = htabPtr;
    if (Tk_ConfigureWidget(htabPtr->interp, htabPtr->tkwin, htabPtr->entrySpecs,
	   nOptions, options, (char *)entryPtr, 0) != TCL_OK) {
	DestroyEntry((DestroyData)entryPtr);
	return TCL_ERROR;
    }
    Blt_HtConfigureEntry(htabPtr, entryPtr);

    objPtr = Tcl_NewObj(); 
    /* 
     * Reference counts for entry objects are initialized to 0. They
     * are incremented as they are inserted into the tree via the 
     * Blt_TreeSetValue call. 
     */
    objPtr->refCount = 0;	
    objPtr->internalRep.otherValuePtr = (VOID *)entryPtr;
    objPtr->bytes = NULL;
    objPtr->length = 0; 
    objPtr->typePtr = &entryObjType;
    /* 
     * Check if there are fields that need to be added 
     */
    for(linkPtr = Blt_ChainFirstLink(htabPtr->chainPtr); linkPtr != NULL;
	linkPtr = Blt_ChainNextLink(linkPtr)) {
	columnPtr = (Column *)Blt_ChainGetValue(linkPtr);
	Blt_HtAddField(entryPtr, columnPtr);
    }
    Blt_TreeSetValueByUid(htabPtr->tree, node, 
	htabPtr->treeColumnPtr->keyUid, objPtr);
    htabPtr->flags |= (HT_LAYOUT | HT_DIRTY);
    Blt_HtEventuallyRedraw(htabPtr);
    return TCL_OK;
}

static int
CreateApplyProc(node, clientData, order)
    Blt_TreeNode node;		/* Node that has just been created. */
    ClientData clientData;
    int order;			/* Not used. */
{
    Hiertable *htabPtr = (Hiertable *)clientData; 
    return Blt_HtCreateEntry(htabPtr, node, 0, (char **)NULL);
}

static int
DeleteApplyProc(node, clientData, order)
    Blt_TreeNode node;
    ClientData clientData;
    int order;			/* Not used. */
{
    Hiertable *htabPtr = (Hiertable *)clientData;
    /* 
     * Unsetting the tree field triggers a call back to destroy the entry
     * and also releases the Tcl_Obj that contains it. 
     */
    Blt_TreeUnsetValue(htabPtr->tree, node, 
		       htabPtr->treeColumnPtr->keyUid);
    return TCL_OK;
}

static int
TreeEventProc(clientData, eventPtr) 
    ClientData clientData;
    Blt_TreeNotifyEvent *eventPtr;
{
    Hiertable *htabPtr = (Hiertable *)clientData; 
    Entry *entryPtr;
    Blt_TreeNode node;

    node = Blt_TreeGetNode(eventPtr->tree, eventPtr->inode);
    switch (eventPtr->type) {
    case TREE_NOTIFY_CREATE:
	return Blt_HtCreateEntry(htabPtr, node, 0, (char **)NULL);
    case TREE_NOTIFY_DELETE:
	/*  
	 * Unsetting the tree field triggers a call back to destroy
	 * the entry and also releases the Tcl_Obj that contains it.  
	 */
	if (node != NULL) {
	    Blt_TreeUnsetValue(eventPtr->tree, node, 
		htabPtr->treeColumnPtr->keyUid); 
	}
	break;
    case TREE_NOTIFY_RELABEL:
	if (node != NULL) {
	    entryPtr = NodeToEntry(htabPtr, node);
	    entryPtr->flags |= ENTRY_DIRTY;
	}
	/*FALLTHRU*/
    case TREE_NOTIFY_MOVE:
    case TREE_NOTIFY_SORT:
	Blt_HtEventuallyRedraw(htabPtr);
	htabPtr->flags |= (HT_LAYOUT | HT_DIRTY);
	break;
    default:
	/* empty */
	break;
    }	
    return TCL_OK;
}

static Field *
FindField(entryPtr, columnPtr)
    Entry *entryPtr;
    Column *columnPtr;
{
    Blt_ChainLink *linkPtr;
    Field *fieldPtr;

    for (linkPtr = Blt_ChainFirstLink(entryPtr->chainPtr); linkPtr != NULL;
	 linkPtr = Blt_ChainNextLink(linkPtr)) {
	fieldPtr = (Field *)Blt_ChainGetValue(linkPtr);
	if (fieldPtr->columnPtr == columnPtr) {
	    return fieldPtr;
	}
    }
    return NULL;
}

char *
Blt_HtGetData(entryPtr, keyUid)
    Entry *entryPtr;
    Blt_Uid keyUid;
{
    Tcl_Obj *objPtr;
    int nBytes;			

    if (Blt_TreeGetValueByUid(entryPtr->htabPtr->tree, entryPtr->node, 
	keyUid, &objPtr) != TCL_OK) {
	return NULL;
    }
    return Tcl_GetStringFromObj(objPtr, &nBytes);
}

void
Blt_HtAddField(entryPtr, columnPtr)
    Entry *entryPtr;
    Column *columnPtr;
{
    Field *fieldPtr;

    fieldPtr = FindField(entryPtr, columnPtr);
    if (fieldPtr == NULL) {
	char *string;

	/* Add a new field only if a data entry exists. */
	string = Blt_HtGetData(entryPtr, columnPtr->keyUid);
#ifdef notdef
	if (string == NULL) {
	    fprintf(stderr, "no string rep? for field %s:%s\n", 
		    Blt_TreeNodeLabel(entryPtr->node), columnPtr->keyUid);
	}
#endif
	if (string != NULL) {
	    fieldPtr = (Field *)calloc(1, sizeof(Field));
	    assert(fieldPtr);
	    fieldPtr->columnPtr = columnPtr;
	    if (entryPtr->chainPtr == NULL) {
		entryPtr->chainPtr = Blt_ChainCreate();
	    }
	    Blt_ChainAppend(entryPtr->chainPtr, (ClientData)fieldPtr);
	}
    }
    entryPtr->htabPtr->flags |= (HT_LAYOUT | HT_DIRTY);
    entryPtr->flags |= ENTRY_DIRTY;
}

/*
 *----------------------------------------------------------------------
 *
 * TreeTraceProc --
 *
 *	Mirrors the individual fields of the tree object (they must
 *	also be listed in the widget's columns chain). This is because
 *	it must track and save the sizes of each individual data
 *	entry, rather than re-computing all the sizes each time the
 *	widget is redrawn.
 *
 *	This procedure is called by the Tree object when a node data
 *	field is set unset.
 *
 * Results:
 *	Returns TCL_OK.
 *
 *---------------------------------------------------------------------- 
 */
/*ARGSUSED*/
static int
TreeTraceProc(clientData, interp, node, keyUid, flags)
    ClientData clientData;
    Tcl_Interp *interp;
    Blt_TreeNode node;		/* Node that has just been updated. */
    Blt_Uid keyUid;		/* Key of field that's been updated. */
    unsigned int flags;
{
    Hiertable *htabPtr = (Hiertable *)clientData; 
    Entry *entryPtr;
    Blt_ChainLink *linkPtr;
    Column *columnPtr;
    Tcl_HashEntry *hPtr;
    Field *fieldPtr;
    
    entryPtr = NodeToEntry(htabPtr, node);
    if (entryPtr == NULL) {
	return TCL_OK;
    }
    switch (flags & (TREE_TRACE_WRITE | TREE_TRACE_READ | TREE_TRACE_UNSET)) {
    case TREE_TRACE_WRITE:
	hPtr = Tcl_FindHashEntry(&(htabPtr->columnTable), keyUid);
	if (hPtr == NULL) {
	    return TCL_OK;	/* Data field isn't used by widget. */
	}
	columnPtr = (Column *)Tcl_GetHashValue(hPtr);
	if (columnPtr != htabPtr->treeColumnPtr) {
	    Blt_HtAddField(entryPtr, columnPtr);
	}
	entryPtr->flags |= ENTRY_DIRTY;
	Blt_HtEventuallyRedraw(htabPtr);
	htabPtr->flags |= (HT_LAYOUT | HT_DIRTY);
	break;

    case TREE_TRACE_UNSET:
	for(linkPtr = Blt_ChainFirstLink(entryPtr->chainPtr); linkPtr != NULL;
	    linkPtr = Blt_ChainNextLink(linkPtr)) {
	    fieldPtr = (Field *)Blt_ChainGetValue(linkPtr);
	    if (fieldPtr->columnPtr->keyUid == keyUid) { 
		Blt_HtDestroyField(fieldPtr);
		entryPtr->flags |= ENTRY_DIRTY;
		Blt_HtEventuallyRedraw(htabPtr);
		htabPtr->flags |= (HT_LAYOUT | HT_DIRTY);
		break;
	    }
	}		
	break;

    default:
	break;
    }
    return TCL_OK;
}


static void
GetFieldSize(htabPtr, entryPtr, fieldPtr)
    Hiertable *htabPtr;
    Entry *entryPtr;
    Field *fieldPtr;
{
    Column *columnPtr;
    TextLayout *layoutPtr;
    HashedImage hImage;
    int width, height;
    char *string;

    columnPtr = fieldPtr->columnPtr;
    fieldPtr->width = fieldPtr->height = 0;
    string = Blt_HtGetData(entryPtr, columnPtr->keyUid);
    if (string == NULL) {
#ifdef notdef
	fprintf(stderr, "%s(%s) is empty\n", Blt_TreeNodeLabel(entryPtr->node), 
		columnPtr->keyUid);
#endif
	return;			/* No data ??? */
    }
    if (string[0] == '@') {	/* Name of Tk image. */
	hImage = Blt_HtGetImage(htabPtr, string + 1);
	if (hImage == NULL) {
	    goto handleString;
	}
	width = ImageWidth(hImage);
	height = ImageHeight(hImage);
	layoutPtr = NULL;
    } else {			/* Text string. */
	TextStyle style;

    handleString:
	Blt_InitTextStyle(&style);
	style.font = columnPtr->font;
	style.anchor = TK_ANCHOR_NW;
	style.justify = TK_JUSTIFY_LEFT;
	layoutPtr = Blt_GetTextLayout(string, &style);
	hImage = NULL;
	width = layoutPtr->width;
	height = layoutPtr->height;
    }
    fieldPtr->width = width;
    fieldPtr->height = height;
    if (fieldPtr->hImage != NULL) {
	Blt_HtFreeImage(fieldPtr->hImage);
    }
    if (fieldPtr->layoutPtr != NULL) {
	free((char *)fieldPtr->layoutPtr);
    }
    fieldPtr->hImage = hImage;
    fieldPtr->layoutPtr = layoutPtr;
}

static void
GetRowExtents(htabPtr, entryPtr, widthPtr, heightPtr)
    Hiertable *htabPtr;
    Entry *entryPtr;
    int *widthPtr, *heightPtr;
{
    Field *fieldPtr;
    int width, height;		/* Compute dimensions of row. */
    Blt_ChainLink *linkPtr;
    int fieldWidth;		/* Width of individual field.  */

    width = height = 0;
    for (linkPtr = Blt_ChainFirstLink(entryPtr->chainPtr); linkPtr != NULL;
	 linkPtr = Blt_ChainNextLink(linkPtr)) {
	fieldPtr = (Field *)Blt_ChainGetValue(linkPtr);
	if ((entryPtr->flags & ENTRY_DIRTY) || 
	    (fieldPtr->columnPtr->flags & COLUMN_DIRTY)) {
	    GetFieldSize(htabPtr, entryPtr, fieldPtr);
	}
	if (fieldPtr->height > height) {
	    height = fieldPtr->height;
	}
	if (fieldPtr->columnPtr->maxWidth < fieldPtr->width) {
	    fieldPtr->columnPtr->maxWidth = fieldPtr->width;
	}
	fieldWidth = fieldPtr->width;
	width += fieldWidth;
    }	    
    *widthPtr = width;
    *heightPtr = height;
}


/*
 *----------------------------------------------------------------------
 *
 * Blt_HtNearestEntry --
 *
 *	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*/
Entry *
Blt_HtNearestEntry(htabPtr, x, y, selectOne)
    Hiertable *htabPtr;
    int x, y;
    int selectOne;
{
    Entry *lastPtr, *entryPtr;
    register Entry **p;

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

static void
GetColumnTags(table, object, tagArr, nTagsPtr)
    BindTable table;
    ClientData object;
    ClientData tagArr[];
    int *nTagsPtr;
{
    Hiertable *htabPtr;
    Column *columnPtr;
    int nTags;

    htabPtr = (Hiertable *)Blt_GetBindingData(table);
    *nTagsPtr = 0;
    nTags = 1;
    columnPtr = (Column *)object;
    if (columnPtr->type == ITEM_RULE) {
	tagArr[0] = (ClientData)Blt_HtGetUid(htabPtr, "Rule");
    } else {
	tagArr[0] = object;	/* Don't want to use the column name and
				 * inadvertently tag more than one entry. */
	if (columnPtr->tags != NULL) {
	    int nNames;
	    char **nameArr;
	    register char **p;
	    
	    if (Tcl_SplitList(htabPtr->interp, columnPtr->tags, &nNames,
			      &nameArr) == TCL_OK) {
		/* FIXME: Arbitrary limit of 10 tags. */
		for (p = nameArr; (*p != NULL) && (nTags < 10); p++, nTags++) {
		    tagArr[nTags] = (ClientData)
			Blt_HtGetUid(htabPtr, (ClientData)*p);
		}
		free((char *)nameArr);
	    }
	}
    }
    *nTagsPtr = nTags;
}

/*ARGSUSED*/
static ClientData
PickColumn(clientData, x, y)
    ClientData clientData;
    int x, y;			/* Screen coordinates of the test point. */
{
    Hiertable *htabPtr = (Hiertable *)clientData;
    Column *columnPtr;

    if (htabPtr->flags & HT_DIRTY) {
	/* Can't trust selected entry, if entries have been added or
	 * deleted. */
	if (htabPtr->flags & HT_LAYOUT) {
	    Blt_HtComputeLayout(htabPtr);
	}
	ComputeVisibleEntries(htabPtr);
    }
    columnPtr = Blt_HtNearestColumn(htabPtr, x, y, SEARCH_Y);
    if ((columnPtr != NULL) && (columnPtr->flags & COLUMN_RULE_PICKED)) {
	columnPtr->flags &= ~COLUMN_RULE_PICKED;
	return (ClientData)&(columnPtr->rule);
    }
    return (ClientData)columnPtr;
}

static void
GetTags(table, object, tagArr, nTagsPtr)
    BindTable table;
    ClientData object;
    ClientData tagArr[];
    int *nTagsPtr;
{
    Hiertable *htabPtr;
    Entry *entryPtr;
    int nTags;

    *nTagsPtr = 0;
    htabPtr = (Hiertable *)Blt_GetBindingData(table);
    tagArr[0] = object;		/* Don't want to use the entry name and
				 * inadvertently tag more than one entry. */
    nTags = 1;
    entryPtr = (Entry *)object;
    if (entryPtr->tags != NULL) {
	int nNames;
	char **nameArr;
	register char **p;

	if (Tcl_SplitList(htabPtr->interp, entryPtr->tags, &nNames,
		&nameArr) == TCL_OK) {
	    /* FIXME: Arbitrary limit of 10 tags. */
	    for (p = nameArr; (*p != NULL) && (nTags < 10); p++, nTags++) {
		tagArr[nTags] = (ClientData)
			Blt_HtGetUid(htabPtr, (ClientData)*p);
	    }
	    free((char *)nameArr);
	}
    }
    *nTagsPtr = nTags;
}

/*ARGSUSED*/
static ClientData
PickButton(clientData, x, y)
    ClientData clientData;
    int x, y;			/* Screen coordinates of the test point. */
{
    Hiertable *htabPtr = (Hiertable *)clientData;
    register Entry *entryPtr;

    if (htabPtr->flags & HT_DIRTY) {
	/* Can't trust selected entry, if entries have been added or
	 * deleted. */
	if (htabPtr->flags & HT_LAYOUT) {
	    Blt_HtComputeLayout(htabPtr);
	}
	ComputeVisibleEntries(htabPtr);
    }
    if (htabPtr->nVisible == 0) {
	return (ClientData) 0;
    }
    entryPtr = Blt_HtNearestEntry(htabPtr, x, y, FALSE);
    if (entryPtr == NULL) {
	return (ClientData) 0;
    }
    if (entryPtr->flags & ENTRY_BUTTON) {
	Button *buttonPtr = &(htabPtr->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(htabPtr, x);
	y = WORLDY(htabPtr, y);
	if ((x >= left) && (x < right) && (y >= top) && (y < bottom)) {
	    return (ClientData)entryPtr;
	}
    }
    return (ClientData) 0;
}

/*ARGSUSED*/
static ClientData
PickEntry(clientData, x, y)
    ClientData clientData;
    int x, y;			/* Screen coordinates of the test point. */
{
    Hiertable *htabPtr = (Hiertable *)clientData;
    register Entry *entryPtr;
    int labelX;

    if (htabPtr->flags & HT_DIRTY) {
	/* Can't trust the selected entry if nodes have been added or
	 * deleted. So recompute the layout. */
	if (htabPtr->flags & HT_LAYOUT) {
	    Blt_HtComputeLayout(htabPtr);
	}
	ComputeVisibleEntries(htabPtr);
    }
    if (htabPtr->nVisible == 0) {
	return (ClientData) 0;
    }
    entryPtr = Blt_HtNearestEntry(htabPtr, x, y, FALSE);
    if (entryPtr == NULL) {
	return (ClientData) 0;
    }
    x = WORLDX(htabPtr, x);
    y = WORLDY(htabPtr, y);
    labelX = entryPtr->worldX;
    if (!htabPtr->flatView) {
	labelX += ICONWIDTH(DEPTH(htabPtr, entryPtr->node));
    }
    if (entryPtr->flags & ENTRY_BUTTON) {
	int left, right, top, bottom;
	Button *buttonPtr = &(htabPtr->button);

	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;
	if ((x >= left) && (x < right) && (y >= top) && (y < bottom)) {
	    return (ClientData)0;
	}
    }
    return (ClientData)entryPtr;
}

static void
GetEntryExtents(htabPtr, entryPtr)
    Hiertable *htabPtr;
    Entry *entryPtr;
{
    int entryWidth, entryHeight;
    int width, height;
    Tk_Font font;
    HashedImage *icons;
    char *label;

    /*
     * FIXME: Use of DIRTY flag inconsistent.  When does it
     *	      mean "dirty entry"? When does it mean "dirty column"?
     *	      Does it matter? probably
     */
    if ((entryPtr->flags & ENTRY_DIRTY) || (htabPtr->flags & HT_UPDATE)) {
	entryPtr->iconWidth = entryPtr->iconHeight = 0;
	icons = GETICONS(htabPtr, entryPtr);
	if (icons != NULL) {
	    register int i;
	    
	    for (i = 0; i < 2; i++) {
		if (icons[i] == NULL) {
		    break;
		}
		if (entryPtr->iconWidth < ImageWidth(icons[i])) {
		    entryPtr->iconWidth = ImageWidth(icons[i]);
		}
		if (entryPtr->iconHeight < ImageHeight(icons[i])) {
		    entryPtr->iconHeight = ImageHeight(icons[i]);
		}
	    }
	}
	if ((icons == NULL) || (icons[0] == NULL)) {
	    entryPtr->iconWidth = DEF_ICON_WIDTH;
	    entryPtr->iconHeight = DEF_ICON_HEIGHT;
	}
	entryPtr->iconWidth += 2 * ICON_PADX;
	entryPtr->iconHeight += 2 * ICON_PADY;
	entryHeight = MAX(entryPtr->iconHeight, htabPtr->button.height);
	entryWidth = 0;
	
	font = GETFONT(htabPtr->treeColumnPtr->font, entryPtr->font);
	if (entryPtr->fullName != NULL) {
	    free(entryPtr->fullName);
	    entryPtr->fullName = NULL;
	}
	if (entryPtr->layoutPtr != NULL) {
	    free((char *)entryPtr->layoutPtr);
	    entryPtr->layoutPtr = NULL;
	}
	
	label = GETLABEL(entryPtr);
	if (label[0] == '\0') {
	    Tk_FontMetrics fontMetrics;
	    
	    Tk_GetFontMetrics(font, &fontMetrics);
	    width = height = fontMetrics.linespace;
	} else {
	    TextStyle style;
	    Blt_InitTextStyle(&style);
	    style.shadow.offset = entryPtr->shadow.offset;
	    style.font = font;
	    
	    if (htabPtr->flatView) {
		entryPtr->fullName = Blt_HtGetFullName(htabPtr, entryPtr, TRUE);
		entryPtr->layoutPtr = Blt_GetTextLayout(entryPtr->fullName, 
			&style);
	    } else {
		entryPtr->layoutPtr = Blt_GetTextLayout(label, &style);
	    }
	    width = entryPtr->layoutPtr->width;
	    height = entryPtr->layoutPtr->height;
	}
	width += 2 * (FOCUS_WIDTH + LABEL_PADX + htabPtr->selBorderWidth);
	height += 2 * (FOCUS_WIDTH + LABEL_PADY + htabPtr->selBorderWidth);
	width = ODD(width);
	height = ODD(height);
	entryWidth = width;
	if (entryHeight < height) {
	    entryHeight = height;
	}
	entryPtr->labelWidth = width;
	entryPtr->labelHeight = height;
    } else {
	entryHeight = entryPtr->labelHeight;
	entryWidth = entryPtr->labelWidth;
    }
    /*  
     * Find the maximum height of the data field entries. This also has
     * the side effect of contributing the maximum width of the column. 
     */
    GetRowExtents(htabPtr, entryPtr, &width, &height);
    if (entryHeight < height) {
	entryHeight = height;
    }
    entryPtr->width = entryWidth + COLUMN_PAD;
    entryPtr->height = entryHeight + htabPtr->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++;
    }
    entryPtr->flags &= ~ENTRY_DIRTY;
}

/*
 * Hiertable Procedures
 */

#define RESIZE_WIDTH 16
#define RESIZE_HEIGHT 16
#define RESIZE_X_HOT 7
#define RESIZE_Y_HOT 7
static unsigned char resize_bits[] = {
   0x00, 0x00, 0x60, 0x06, 0x60, 0x06, 0x60, 0x06, 0x60, 0x06, 0x64, 0x26,
   0x66, 0x66, 0x7f, 0xfe, 0x66, 0x66, 0x64, 0x26, 0x60, 0x06, 0x60, 0x06,
   0x60, 0x06, 0x60, 0x06, 0x60, 0x06, 0x00, 0x00};

static unsigned char resize_mask_bits[] = {
   0xf0, 0x0f, 0xf0, 0x0f, 0xf0, 0x0f, 0xf0, 0x0f, 0xfc, 0x3f, 0xfe, 0x7f,
   0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x7f, 0xfc, 0x3f, 0xf0, 0x0f,
   0xf0, 0x0f, 0xf0, 0x0f, 0xf0, 0x0f, 0xf0, 0x0f};

/*
 * ----------------------------------------------------------------------
 *
 * CreateHiertable --
 *
 * ----------------------------------------------------------------------
 */
static int
CreateHiertable(interp, name, htabPtrPtr)
    Tcl_Interp *interp;
    char *name;
    Hiertable **htabPtrPtr;
{
    Hiertable *htabPtr;
    Tk_Window tkwin;
    Tcl_DString dString;
    int result;

    htabPtr = (Hiertable *)calloc(1, sizeof(Hiertable));
    assert(htabPtr);

    tkwin = Tk_CreateWindowFromPath(interp, Tk_MainWindow(interp), name,
	(char *)NULL);
    if (tkwin == NULL) {
	return TCL_ERROR;
    }
    Tk_SetClass(tkwin, "Hiertable");
    htabPtr->tkwin = tkwin;
    htabPtr->display = Tk_Display(tkwin);
    htabPtr->interp = interp;
    htabPtr->flags = HT_DIRTY | HT_LAYOUT;
    htabPtr->leader = 0;
    htabPtr->dashes = 1;
    htabPtr->highlightWidth = 2;
    htabPtr->selBorderWidth = 1;
    htabPtr->borderWidth = 2;
    htabPtr->relief = TK_RELIEF_SUNKEN;
    htabPtr->selRelief = TK_RELIEF_FLAT;
    htabPtr->scrollMode = SCROLL_MODE_HIERBOX;
    htabPtr->selectMode = SELECT_MODE_SINGLE;
    htabPtr->button.closeRelief = htabPtr->button.openRelief = TK_RELIEF_SOLID;
    htabPtr->reqWidth = 200;
    htabPtr->reqHeight = 400;
    htabPtr->xScrollUnits = htabPtr->yScrollUnits = 20;
    htabPtr->lineWidth = 1;
    htabPtr->button.borderWidth = 1;
    htabPtr->chainPtr = Blt_ChainCreate();
    htabPtr->entrySpecs = entryConfigSpecs;
    htabPtr->buttonSpecs = buttonConfigSpecs;
    htabPtr->widgetSpecs = configSpecs;
    htabPtr->showTitles = TRUE;
    htabPtr->buttonFlags = BUTTON_AUTO;
    htabPtr->selectPtr = Blt_ChainCreate();
    Tcl_InitHashTable(&(htabPtr->columnTable), TCL_ONE_WORD_KEYS);
    Tcl_InitHashTable(&(htabPtr->imageTable), TCL_STRING_KEYS);
    Tcl_InitHashTable(&(htabPtr->selectTable), TCL_ONE_WORD_KEYS);
    Tcl_InitHashTable(&(htabPtr->uidTable), TCL_STRING_KEYS);
    htabPtr->bindTable = Blt_CreateBindingTable(interp, tkwin,
	(ClientData)htabPtr, PickEntry, GetTags);
    htabPtr->buttonBindTable = Blt_CreateBindingTable(interp, tkwin,
	(ClientData)htabPtr, PickButton, GetTags);
    htabPtr->columnBindTable = Blt_CreateBindingTable(interp, tkwin,
	(ClientData)htabPtr, PickColumn, GetColumnTags);

    htabPtr->defResizeCursor = Tk_GetCursorFromData(interp, tkwin, 
	(char *)resize_bits, (char *)resize_mask_bits, 
	RESIZE_WIDTH, RESIZE_HEIGHT, RESIZE_X_HOT, RESIZE_Y_HOT, 
	Tk_GetUid("black"), Tk_GetUid("white"));

#if (TK_MAJOR_VERSION > 4)
    Blt_SetWindowInstanceData(tkwin, (ClientData)htabPtr);
#endif
    /* Create a default column to display the view of the tree. */
    htabPtr->treeColumnPtr = &(htabPtr->defColumn);

    *htabPtrPtr = htabPtr;
    Tcl_DStringInit(&dString);
    Tcl_DStringAppend(&dString, "BLT Hiertable ", -1);
    Tcl_DStringAppend(&dString, Tk_PathName(tkwin), -1);
    result = Blt_HtInitColumn(htabPtr, htabPtr->treeColumnPtr, 
	Tcl_DStringValue(&dString), "", 0, (char **)NULL);
    Tcl_DStringFree(&dString);
    if (result != TCL_OK) {
	Tk_DestroyWindow(tkwin);
	return TCL_ERROR;
    }
    Blt_ChainAppend(htabPtr->chainPtr, (ClientData)htabPtr->treeColumnPtr);
    htabPtr->editPtr = Blt_HtCreateEditor(htabPtr);
    return TCL_OK;
}

/*
 * ----------------------------------------------------------------------
 *
 * DestroyHiertable --
 *
 * 	This procedure is invoked by Tcl_EventuallyFree or Tcl_Release
 *	to clean up the internal structure of a Hiertable 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
DestroyHiertable(dataPtr)
    DestroyData dataPtr;	/* Pointer to the widget record. */
{
    Hiertable *htabPtr = (Hiertable *)dataPtr;
    Button *buttonPtr = &(htabPtr->button);
    
    htabPtr->flags |= HT_DESTROYED;
    Tk_FreeOptions(htabPtr->widgetSpecs, (char *)htabPtr, htabPtr->display, 0);

    if (htabPtr->tkwin != NULL) {
	Tk_DeleteSelHandler(htabPtr->tkwin, XA_PRIMARY, XA_STRING);
    }
    if (htabPtr->lineGC != NULL) {
	Tk_FreeGC(htabPtr->display, htabPtr->lineGC);
    }
    if (htabPtr->focusGC != NULL) {
	Blt_FreePrivateGC(htabPtr->display, htabPtr->focusGC);
    }
    if (htabPtr->visibleArr != NULL) {
	free((char *)htabPtr->visibleArr);
    }
    if (htabPtr->flatArr != NULL) {
	free((char *)htabPtr->flatArr);
    }
    if (htabPtr->levelInfo != NULL) {
	free((char *)htabPtr->levelInfo);
    }
    if (htabPtr->activeIcons != NULL) {
	free((char *)htabPtr->activeIcons);
    }
    if (htabPtr->icons != NULL) {
	free((char *)htabPtr->icons);
    }
    if (htabPtr->defResizeCursor != None) {
	Tk_FreeCursor(htabPtr->display, htabPtr->defResizeCursor);
    }
    if (htabPtr->resizeCursor != None) {
	Tk_FreeCursor(htabPtr->display, htabPtr->resizeCursor);
    }
    if (buttonPtr->activeGC != NULL) {
	Tk_FreeGC(htabPtr->display, buttonPtr->activeGC);
    }
    if (buttonPtr->normalGC != NULL) {
	Tk_FreeGC(htabPtr->display, buttonPtr->normalGC);
    }
    if (buttonPtr->lineGC != NULL) {
	Tk_FreeGC(htabPtr->display, buttonPtr->lineGC);
    }
    if (htabPtr->drawable != None) {
	Tk_FreePixmap(htabPtr->display, htabPtr->drawable);
    }

    if (htabPtr->tree != NULL) {
	Blt_TreeApply(htabPtr->rootPtr->node, DeleteApplyProc, 
		(ClientData)htabPtr);
	/* 
	 * We need to destroy the columns before releasing the tree.
	 * That's because columns use string identifiers (Uids)
	 * stored in the tree object itself. And if the tree is
	 * already destroyed, we'll try to release already-freed Uids.  
	 */
	Blt_HtDestroyColumns(htabPtr);
	Blt_TreeReleaseToken(htabPtr->tree);
    }
    Blt_DestroyBindingTable(htabPtr->bindTable);
    Blt_DestroyBindingTable(htabPtr->buttonBindTable);
    Blt_DestroyBindingTable(htabPtr->columnBindTable);
    Blt_ChainDestroy(htabPtr->selectPtr);
    Tcl_DeleteHashTable(&(htabPtr->selectTable));
    Tcl_DeleteHashTable(&(htabPtr->uidTable));
    FreeImageTable(htabPtr);
    free((char *)htabPtr);
}

/*
 * --------------------------------------------------------------
 *
 * HiertableEventProc --
 *
 * 	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
HiertableEventProc(clientData, eventPtr)
    ClientData clientData;	/* Information about window. */
    XEvent *eventPtr;		/* Information about event. */
{
    Hiertable *htabPtr = (Hiertable *)clientData;

    if (eventPtr->type == Expose) {
	if (eventPtr->xexpose.count == 0) {
	    Blt_HtEventuallyRedraw(htabPtr);
	}
    } else if (eventPtr->type == ConfigureNotify) {
	htabPtr->flags |= (HT_LAYOUT | HT_SCROLL);
	Blt_HtEventuallyRedraw(htabPtr);
    } else if ((eventPtr->type == FocusIn) || (eventPtr->type == FocusOut)) {
	if (eventPtr->xfocus.detail != NotifyInferior) {
	    if (eventPtr->type == FocusIn) {
		htabPtr->flags |= HT_FOCUS;
	    } else {
		htabPtr->flags &= ~HT_FOCUS;
	    }
	    Blt_HtEventuallyRedraw(htabPtr);
	}
    } else if (eventPtr->type == DestroyNotify) {
	if (htabPtr->tkwin != NULL) {
#ifdef ITCL_NAMESPACES
	    Itk_SetWidgetCommand(htabPtr->tkwin, (Tcl_Command) NULL);
#endif /* ITCL_NAMESPACES */
	    Tcl_DeleteCommandFromToken(htabPtr->interp, htabPtr->cmdToken);
	    htabPtr->tkwin = NULL;
	}
	if (htabPtr->flags & HT_REDRAW) {
	    Tcl_CancelIdleCall(DisplayHiertable, (ClientData)htabPtr);
	}
	if (htabPtr->flags & SELECTION_PENDING) {
	    Tcl_CancelIdleCall(Blt_HtSelectCmdProc, (ClientData)htabPtr);
	}
	Tcl_EventuallyFree((ClientData)htabPtr, DestroyHiertable);
    }
}

/* 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. */
{
    Hiertable *htabPtr = (Hiertable *)clientData;
    int size;
    Tcl_DString dString;
    Entry *entryPtr;

    if (!htabPtr->exportSelection) {
	return -1;
    }
    /*
     * Retrieve the names of the selected entries.
     */
    Tcl_DStringInit(&dString);
    if (htabPtr->sortSelection) {
	Blt_ChainLink *linkPtr;

	for (linkPtr = Blt_ChainFirstLink(htabPtr->selectPtr); linkPtr != NULL;
		linkPtr = Blt_ChainNextLink(linkPtr)) {
	    entryPtr = (Entry *)Blt_ChainGetValue(linkPtr);
	    Tcl_DStringAppend(&dString, GETLABEL(entryPtr), -1);
	    Tcl_DStringAppend(&dString, "\n", -1);
	}
    } else {
	for (entryPtr = htabPtr->rootPtr; entryPtr != NULL; 
	     entryPtr = Blt_HtNextEntry(htabPtr, entryPtr, ENTRY_MASK)) {
	    if (Blt_HtEntryIsSelected(htabPtr, entryPtr)) {
		Tcl_DStringAppend(&dString, GETLABEL(entryPtr), -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;
}

/*
 *----------------------------------------------------------------------
 *
 * WidgetInstCmdDeleteProc --
 *
 *	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
WidgetInstCmdDeleteProc(clientData)
    ClientData clientData;	/* Pointer to widget record for widget. */
{
    Hiertable *htabPtr = (Hiertable *)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 (htabPtr->tkwin != NULL) {
	Tk_Window tkwin;

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

/*
 * ----------------------------------------------------------------------
 *
 * Blt_HtConfigureHiertable --
 *
 * 	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 htabPtr; old resources get freed, if there
 *	were any.  The widget is redisplayed.
 *
 * ----------------------------------------------------------------------
 */
int
Blt_HtConfigureHiertable(interp, htabPtr, argc, argv, flags)
    Tcl_Interp *interp;
    Hiertable *htabPtr;		/* 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;
    int oldView, setupTree;

    oldView = htabPtr->flatView;
    if (Tk_ConfigureWidget(interp, htabPtr->tkwin, htabPtr->widgetSpecs, 
	argc, argv, (char *)htabPtr, flags) != TCL_OK) {
	return TCL_ERROR;
    }


    /*
     * GC for dotted vertical line.
     */
    gcMask = (GCForeground | GCLineWidth);
    gcValues.foreground = htabPtr->lineColor->pixel;
    gcValues.line_width = htabPtr->lineWidth;
    if (htabPtr->dashes > 0) {
	gcMask |= (GCLineStyle | GCDashList);
	gcValues.line_style = LineOnOffDash;
	gcValues.dashes = htabPtr->dashes;
    }
    newGC = Tk_GetGC(htabPtr->tkwin, gcMask, &gcValues);
    if (htabPtr->lineGC != NULL) {
	Tk_FreeGC(htabPtr->display, htabPtr->lineGC);
    }
    htabPtr->lineGC = newGC;

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

    Blt_HtConfigureButtons(htabPtr);
    htabPtr->inset = htabPtr->highlightWidth + htabPtr->borderWidth + INSET_PAD;

    setupTree = FALSE;

    /*
     * If no tree object was named, allocate a new one.  The name will
     * be the same as the widget pathname.
     */
    if (htabPtr->tree == NULL) {
	Blt_Tree token;
	char *string;

	string = Tk_PathName(htabPtr->tkwin);
	if ((Blt_TreeCreate(interp, string) != TCL_OK) ||
	    (Blt_TreeGetToken(interp, string, &token) != TCL_OK)) {
	    return TCL_ERROR;
	}
	htabPtr->tree = token;
	setupTree = TRUE;
    } 

    /*
     * If the tree object was changed, we need to setup the new one.
     */
    if (Blt_ConfigModified(htabPtr->widgetSpecs, "-tree", (char *)NULL)) {
	setupTree = TRUE;
    }

    /*
     * These options change the layout of the box.  Mark the widget for update.
     */
    if (Blt_ConfigModified(htabPtr->widgetSpecs, "-font", "-linespacing", 
	"-width", "-height", "-hideroot", "-tree", "-flat", (char *)NULL)) {
	htabPtr->flags |= (HT_LAYOUT | HT_SCROLL);
    }

    /*
     * If the tree view was changed, mark all the nodes dirty (we'll
     * be switching back to either the full path name or the label)
     * and free the array representing the flattened view of the tree.
     */
    if (htabPtr->flatView != oldView) {
	Entry *entryPtr;
	
	htabPtr->flags |= HT_DIRTY;
	/* Mark all entries dirty. */
	for (entryPtr = htabPtr->rootPtr; entryPtr != NULL; 
	     entryPtr = Blt_HtNextEntry(htabPtr, entryPtr, 0)) {
	    entryPtr->flags |= ENTRY_DIRTY;
	}
	if ((!htabPtr->flatView) && (htabPtr->flatArr != NULL)) {
	    free((char *)htabPtr->flatArr);
	    htabPtr->flatArr = NULL;
	}
    }
    if ((htabPtr->reqHeight != Tk_ReqHeight(htabPtr->tkwin)) ||
	(htabPtr->reqWidth != Tk_ReqWidth(htabPtr->tkwin))) {
	Tk_GeometryRequest(htabPtr->tkwin, htabPtr->reqWidth,
	    htabPtr->reqHeight);
    }

    if (setupTree) {
	Blt_TreeNode root;

	Blt_TreeCreateEventHandler(htabPtr->tree, 
		TREE_NOTIFY_ALL | TREE_NOTIFY_FOREIGN_ONLY, TreeEventProc, 
		(ClientData)htabPtr);
	TraceColumns(htabPtr);
	root = Blt_TreeRootNode(htabPtr->tree);

	/* Automatically add view-entry fields to the new tree. */
	Blt_TreeApply(root, CreateApplyProc, (ClientData)htabPtr);
	htabPtr->focusPtr = htabPtr->rootPtr = NodeToEntry(htabPtr, root);
	htabPtr->selAnchorPtr = NULL;
	Blt_SetFocusItem(htabPtr->bindTable, htabPtr->rootPtr);

	/* Automatically open the root node. */
	if (Blt_HtOpenEntry(htabPtr, htabPtr->rootPtr) != TCL_OK) {
	    return TCL_ERROR;
	}
    }

    if (Blt_ConfigModified(htabPtr->widgetSpecs, "-font", "-color", 
	   (char *)NULL)) {
	Blt_HtConfigureColumn(htabPtr, htabPtr->treeColumnPtr);
    }
    Blt_HtEventuallyRedraw(htabPtr);
    return TCL_OK;
}

/*
 * ----------------------------------------------------------------------
 *
 * ResetCoordinates --
 *
 *	Determines the maximum height of all visible entries.
 *
 *	1. Sets the worldY coordinate for all mapped/open entries.
 *	2. Determines if entry needs a button.
 *	3. Collects the minimum height of open/mapped entries. (Do for all
 *	   entries upon insert).
 *	4. Figures out horizontal extent of each entry (will be width of 
 *	   tree view column).
 *	5. Collects maximum icon size for each level.
 *	6. The height of its vertical line
 *
 * Results:
 *	Returns 1 if beyond the last visible entry, 0 otherwise.
 *
 * Side effects:
 *	The array of visible nodes is filled.
 *
 * ----------------------------------------------------------------------
 */
static void
ResetCoordinates(htabPtr, entryPtr, yPtr)
    Hiertable *htabPtr;
    Entry *entryPtr;
    int *yPtr;
{
    int depth;

    entryPtr->worldY = -1;
    entryPtr->lineHeight = -1;
    if (entryPtr->flags & ENTRY_HIDDEN) {
	return;     /* If the entry is hidden, then do nothing. */
    }
    entryPtr->worldY = *yPtr;
    entryPtr->lineHeight = -(*yPtr);
    *yPtr += entryPtr->height;

    depth = DEPTH(htabPtr, entryPtr->node) + 1;
    if (htabPtr->levelInfo[depth].labelWidth < entryPtr->labelWidth) {
	htabPtr->levelInfo[depth].labelWidth = entryPtr->labelWidth;
    }
    if (htabPtr->levelInfo[depth].iconWidth < entryPtr->iconWidth) {
	htabPtr->levelInfo[depth].iconWidth = entryPtr->iconWidth;
    }
    htabPtr->levelInfo[depth].iconWidth |= 0x01;

    if (!(entryPtr->flags & ENTRY_CLOSED)) {
	Entry *bottomPtr, *childPtr;
	Blt_TreeNode node;

	bottomPtr = entryPtr;
	for (node = Blt_TreeFirstChild(entryPtr->node); node != NULL;
	    node = Blt_TreeNextSibling(node)) {
	    childPtr = NodeToEntry(htabPtr, node);
	    if (!(childPtr->flags & ENTRY_HIDDEN)) {
		ResetCoordinates(htabPtr, childPtr, yPtr);
		bottomPtr = childPtr;
	    }
	}
	entryPtr->lineHeight += bottomPtr->worldY;
    }
}


static void
AdjustColumns(htabPtr)
    Hiertable *htabPtr;
{
    int size, avail, ration, growth;
    int nOpen;
    double weight;
    Blt_ChainLink *linkPtr;
    Column *columnPtr;

    growth = VPORTWIDTH(htabPtr) - htabPtr->worldWidth;
    nOpen = 0;
    weight = 0.0;
    /* Find out how many columns still have space available */
    for (linkPtr = Blt_ChainFirstLink(htabPtr->chainPtr); linkPtr != NULL;
	 linkPtr = Blt_ChainNextLink(linkPtr)) {
	columnPtr = (Column *)Blt_ChainGetValue(linkPtr);
	if ((columnPtr->hidden) || (columnPtr->weight == 0.0) ||
	    (columnPtr->width >= columnPtr->max)) {
	    continue;
	}
	nOpen++;
	weight += columnPtr->weight;
    }

    while ((nOpen > 0) && (weight > 0.0) && (growth > 0)) {
	ration = (int)(growth / weight);
	if (ration == 0) {
	    ration = 1;
	}
	for (linkPtr = Blt_ChainFirstLink(htabPtr->chainPtr); linkPtr != NULL;
	     linkPtr = Blt_ChainNextLink(linkPtr)) {
	    columnPtr = (Column *)Blt_ChainGetValue(linkPtr);
	    if ((columnPtr->hidden) || (columnPtr->weight == 0.0) ||
		(columnPtr->width >= columnPtr->max)) {
		continue;
	    }
	    size = (int)(ration * columnPtr->weight);
	    if (size > growth) {
		size = growth;	
	    }
	    avail = columnPtr->max - columnPtr->width;
	    if (size > avail) {
		size = avail;
		nOpen--;
		weight -= columnPtr->weight;
	    }
	    growth -= size;
	    columnPtr->width += size;
	}
    }
}

/*
 * ----------------------------------------------------------------------
 *
 * Blt_HtComputeLayout --
 *
 *	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
ComputeFlatLayout(htabPtr)
    Hiertable *htabPtr;
{
    Blt_ChainLink *linkPtr;
    Entry *entryPtr;
    Column *columnPtr;
    Entry **p;
    int y;
    int maxX;
    int count;
    /* 
     * Pass 1:	Reinitialize column sizes and loop through all nodes. 
     *
     *		1. Recalculate the size of each entry as needed. 
     *		2. The maximum depth of the tree. 
     *		3. Minimum height of an entry.  Dividing this by the
     *		   height of the widget gives a rough estimate of the 
     *		   maximum number of visible entries.
     *		4. Build an array to hold level information to be filled
     *		   in on pass 2.
     */
    if (htabPtr->flags & (HT_DIRTY | HT_UPDATE)) {
	int position;

	position = 1;
	for (linkPtr = Blt_ChainFirstLink(htabPtr->chainPtr); linkPtr != NULL;
	     linkPtr = Blt_ChainNextLink(linkPtr)) {
	    columnPtr = (Column *)Blt_ChainGetValue(linkPtr);
	    columnPtr->maxWidth = 0;
	    columnPtr->max = 
		(columnPtr->reqMax > 0) ? columnPtr->reqMax : SHRT_MAX;
	    columnPtr->position = position;

	    position++;
	}
	htabPtr->minHeight = SHRT_MAX;
	htabPtr->depth = 0;
	htabPtr->nEntries = Blt_TreeSize(htabPtr->rootPtr->node);
	if (htabPtr->flatArr != NULL) {
	    free((char *)htabPtr->flatArr);
	}
	htabPtr->flatArr = 
	    (Entry **)malloc(sizeof(Entry *) * (htabPtr->nEntries + 1));
	assert(htabPtr->flatArr);
	htabPtr->depth = 0;
	count = 0;
	p = htabPtr->flatArr;
	for (entryPtr = htabPtr->rootPtr; entryPtr != NULL; 
	     entryPtr = Blt_HtNextEntry(htabPtr, entryPtr, 0)) {
	    if ((htabPtr->hideRoot) && (entryPtr == htabPtr->rootPtr)) {
		continue;
	    }
	    entryPtr->lineHeight = 0;
	    if (Blt_HtEntryIsMapped(htabPtr, entryPtr)) {
		GetEntryExtents(htabPtr, entryPtr);
		if (htabPtr->minHeight > entryPtr->height) {
		    htabPtr->minHeight = entryPtr->height;
		}
		entryPtr->flags &= ~ENTRY_BUTTON;
		*p++ = entryPtr;
		count++;
	    }
	}
	htabPtr->flatArr[count] = NULL;
	htabPtr->nEntries = count;

	Blt_HtSortFlatView(htabPtr);

	if (htabPtr->levelInfo != NULL) {
	    free((char *)htabPtr->levelInfo);
	}
	htabPtr->levelInfo = (LevelInfo *)
	    calloc(htabPtr->depth + 2, sizeof(LevelInfo));
	assert(htabPtr->levelInfo);
	htabPtr->flags &= ~(HT_DIRTY | HT_UPDATE);
    }
    htabPtr->levelInfo[0].labelWidth = htabPtr->levelInfo[0].x = 
	    htabPtr->levelInfo[0].iconWidth = 0;
    /* 
     * Pass 2:	Loop through all open/mapped nodes. 
     *
     *		1. Set world y-coordinates for entries. We must defer
     *		   setting the x-coordinates until we know the maximum 
     *		   icon sizes at each level.
     *		2. Compute the maximum depth of the tree. 
     *		3. Build an array to hold level information.
     */
    y = 0;			
    count = 0;
    for(p = htabPtr->flatArr; *p != NULL; p++) {
	entryPtr = *p;
	entryPtr->flatIndex = count++;
	entryPtr->worldY = y;
	entryPtr->lineHeight = 0;
	y += entryPtr->height;
	if (htabPtr->levelInfo[0].labelWidth < entryPtr->labelWidth) {
	    htabPtr->levelInfo[0].labelWidth = entryPtr->labelWidth;
	}
	if (htabPtr->levelInfo[0].iconWidth < entryPtr->iconWidth) {
	    htabPtr->levelInfo[0].iconWidth = entryPtr->iconWidth;
	}
    }
    htabPtr->levelInfo[0].iconWidth |= 0x01;
    htabPtr->worldHeight = y;	/* Set the scroll height of the hierarchy. */
    if (htabPtr->worldHeight < 1) {
	htabPtr->worldHeight = 1;
    }
    maxX = htabPtr->levelInfo[0].iconWidth + htabPtr->levelInfo[0].labelWidth;
    htabPtr->treeColumnPtr->maxWidth = maxX;
    htabPtr->flags |= HT_VIEWPORT;
}

/*
 * ----------------------------------------------------------------------
 *
 * ComputeTreeLayout --
 *
 *	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
ComputeTreeLayout(htabPtr)
    Hiertable *htabPtr;
{
    Blt_ChainLink *linkPtr;
    Entry *entryPtr;
    Column *columnPtr;
    int sum;
    int maxX, x, y;
    register int i;

    /* 
     * Pass 1:	Reinitialize column sizes and loop through all nodes. 
     *
     *		1. Recalculate the size of each entry as needed. 
     *		2. The maximum depth of the tree. 
     *		3. Minimum height of an entry.  Dividing this by the
     *		   height of the widget gives a rough estimate of the 
     *		   maximum number of visible entries.
     *		4. Build an array to hold level information to be filled
     *		   in on pass 2.
     */
    if (htabPtr->flags & HT_DIRTY) {
	int position;

	position = 1;
	for (linkPtr = Blt_ChainFirstLink(htabPtr->chainPtr); linkPtr != NULL;
	     linkPtr = Blt_ChainNextLink(linkPtr)) {
	    columnPtr = (Column *)Blt_ChainGetValue(linkPtr);
	    columnPtr->maxWidth = 0;
	    columnPtr->max = 
		(columnPtr->reqMax > 0) ? columnPtr->reqMax : SHRT_MAX;
	    columnPtr->position = position;
	    position++;
	}
	htabPtr->minHeight = SHRT_MAX;
	htabPtr->depth = 0;
	for (entryPtr = htabPtr->rootPtr; entryPtr != NULL; 
	     entryPtr = Blt_HtNextEntry(htabPtr, entryPtr, 0)) {
	    GetEntryExtents(htabPtr, entryPtr);
	    if (htabPtr->minHeight > entryPtr->height) {
		htabPtr->minHeight = entryPtr->height;
	    }
	    /* 
	     * Determine if the entry should display a button
	     * (indicating that it has children) and mark the
	     * entry accordingly. 
	     */
	    if ((entryPtr->flags & BUTTON_SHOW) || 
		((entryPtr->flags & BUTTON_AUTO) && 
		 (!Blt_TreeIsLeaf(entryPtr->node)))) {
		entryPtr->flags |= ENTRY_BUTTON;
	    } else {
		entryPtr->flags &= ~ENTRY_BUTTON;
	    }
	    /* Figure out the depth of the tree. */
	    if (htabPtr->depth < DEPTH(htabPtr, entryPtr->node)) {
		htabPtr->depth = DEPTH(htabPtr, entryPtr->node);
	    }
	}
	Blt_HtSortTreeView(htabPtr);

	if (htabPtr->levelInfo != NULL) {
	    free((char *)htabPtr->levelInfo);
	}
	htabPtr->levelInfo = (LevelInfo *)
	    calloc(htabPtr->depth + 2, sizeof(LevelInfo));
	assert(htabPtr->levelInfo);
	htabPtr->flags &= ~HT_DIRTY;
    }
    for (i = 0; i <= htabPtr->depth; i++) {
	htabPtr->levelInfo[i].labelWidth = htabPtr->levelInfo[i].x = 
	    htabPtr->levelInfo[i].iconWidth = 0;
    }
    /* 
     * Pass 2:	Loop through all open/mapped nodes. 
     *
     *		1. Set world y-coordinates for entries. We must defer
     *		   setting the x-coordinates until we know the maximum 
     *		   icon sizes at each level.
     *		2. Compute the maximum depth of the tree. 
     *		3. Build an array to hold level information.
     */
    y = 0;
    if (htabPtr->hideRoot) {
	/* If the root entry is to be hidden, cheat by offsetting
	 * the y-coordinates by the height of the entry. */
	y = -(htabPtr->rootPtr->height);
    } 
    ResetCoordinates(htabPtr, htabPtr->rootPtr, &y);
    htabPtr->worldHeight = y;	/* Set the scroll height of the hierarchy. */
    if (htabPtr->worldHeight < 1) {
	htabPtr->worldHeight = 1;
    }
    sum = maxX = 0;
    for (i = 0; i <= htabPtr->depth; i++) {
	sum += htabPtr->levelInfo[i].iconWidth;
	htabPtr->levelInfo[i + 1].x = sum;
	x = sum + htabPtr->levelInfo[i].labelWidth;
	if (x > maxX) {
	    maxX = x;
	}
    }
    htabPtr->treeColumnPtr->maxWidth = maxX;
}

/*
 * ----------------------------------------------------------------------
 *
 * Blt_HtComputeLayout --
 *
 *	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.
 *
 * ----------------------------------------------------------------------
 */
void
Blt_HtComputeLayout(htabPtr)
    Hiertable *htabPtr;
{
    Blt_ChainLink *linkPtr;
    Column *columnPtr;
    int sum;

    if (htabPtr->flatView) {
	ComputeFlatLayout(htabPtr);
    } else {
	ComputeTreeLayout(htabPtr);
    }
    /* The width of the widget (in world coordinates) is the sum 
     * of the column widths. */

    htabPtr->worldWidth = htabPtr->titleHeight = 0;
    sum = 0;
    columnPtr = NULL;
    for (linkPtr = Blt_ChainFirstLink(htabPtr->chainPtr); linkPtr != NULL;
	 linkPtr = Blt_ChainNextLink(linkPtr)) {
	columnPtr = (Column *)Blt_ChainGetValue(linkPtr);
	columnPtr->width = 0;
	if (!columnPtr->hidden) {
	    if (htabPtr->titleHeight < columnPtr->layoutPtr->height) {
		htabPtr->titleHeight = columnPtr->layoutPtr->height;
	    }
	    if (columnPtr->reqWidth > 0) {
		columnPtr->width = columnPtr->reqWidth;
	    } else {
		/* The computed width of a column is the maximum of
		 * the title width and the widest entry. */
		columnPtr->width = MAX(columnPtr->titleWidth, 
				       columnPtr->maxWidth);
		/* Check that the width stays within any constraints that
		 * have been set. */
		if ((columnPtr->reqMin > 0) && 
		    (columnPtr->reqMin > columnPtr->width)) {
		    columnPtr->width = columnPtr->reqMin;
		}
		if ((columnPtr->reqMax > 0) && 
		    (columnPtr->reqMax < columnPtr->width)) {
		    columnPtr->width = columnPtr->reqMax;
		}
	    }
	    columnPtr->width += PADDING(columnPtr->pad) + 
		2 * columnPtr->borderWidth;
	}
	columnPtr->worldX = sum;
	sum += columnPtr->width;
    }
    htabPtr->worldWidth = sum;
    if (VPORTWIDTH(htabPtr) > sum) {
	AdjustColumns(htabPtr);
    }
    sum = 0;
    for (linkPtr = Blt_ChainFirstLink(htabPtr->chainPtr); linkPtr != NULL;
	 linkPtr = Blt_ChainNextLink(linkPtr)) {
	columnPtr = (Column *)Blt_ChainGetValue(linkPtr);
	columnPtr->worldX = sum;
	sum += columnPtr->width;
    }
    if (htabPtr->titleHeight > 0) {
	/* If any headings are displayed, add some extra padding to
	 * the height. */
	htabPtr->titleHeight += 4;
    }
    /* htabPtr->worldWidth += 10; */
    if (htabPtr->yScrollUnits < 1) {
	htabPtr->yScrollUnits = 1;
    }
    if (htabPtr->xScrollUnits < 1) {
	htabPtr->xScrollUnits = 1;
    }
    if (htabPtr->worldWidth < 1) {
	htabPtr->worldWidth = 1;
    }
    htabPtr->flags &= ~HT_LAYOUT;
    htabPtr->flags |= HT_SCROLL;
}

/*
 * ----------------------------------------------------------------------
 *
 * 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(htabPtr)
    Hiertable *htabPtr;
{
    int height;
    int x, maxX;
    int nSlots;
    int level;
    int xOffset, yOffset;

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

    if ((xOffset != htabPtr->xOffset) || (yOffset != htabPtr->yOffset)) {
	htabPtr->yOffset = yOffset;
	htabPtr->xOffset = xOffset;
	htabPtr->flags |= HT_VIEWPORT;
    }
    height = VPORTHEIGHT(htabPtr);

    /* Allocate worst case number of slots for entry array. */
    nSlots = (height / htabPtr->minHeight) + 3;
    if (nSlots != htabPtr->nVisible) {
	if (htabPtr->visibleArr != NULL) {
	    free((char *)htabPtr->visibleArr);
	}
	htabPtr->visibleArr = (Entry **)calloc(nSlots, sizeof(Entry *));
	assert(htabPtr->visibleArr);
    }
    htabPtr->nVisible = 0;

    if (htabPtr->rootPtr->flags & ENTRY_HIDDEN) {
	return TCL_OK;		/* Root node is hidden. */
    }
    /* Find the node where the view port starts. */
    if (htabPtr->flatView) {
	register Entry **p, *entryPtr;

	/* Find the starting entry visible in the viewport. It can't
	 * be hidden or any of it's ancestors closed. */
    again:
	for (p = htabPtr->flatArr; *p != NULL; p++) {
	    entryPtr = *p;
	    if ((entryPtr->worldY + entryPtr->height) > htabPtr->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 (*p == NULL) {
	    if (htabPtr->yOffset == 0) {
		return TCL_OK;	/* All entries are hidden. */
	    }
	    htabPtr->yOffset = 0;
	    goto again;
	}

	
	maxX = 0;
	height += htabPtr->yOffset;
	for (/* empty */; *p != NULL; p++) {
	    entryPtr = *p;
	    entryPtr->worldX = LEVELX(0) + htabPtr->treeColumnPtr->worldX;
	    x = entryPtr->worldX + ICONWIDTH(0) + entryPtr->width;
	    if (x > maxX) {
		maxX = x;
	    }
	    if (entryPtr->worldY >= height) {
		break;
	    }
	    htabPtr->visibleArr[htabPtr->nVisible] = *p;
	    htabPtr->nVisible++;
	}
	htabPtr->visibleArr[htabPtr->nVisible] = NULL;
    } else {
	Entry *entryPtr;
	Blt_TreeNode node;

	entryPtr = htabPtr->rootPtr;
	while ((entryPtr->worldY + entryPtr->height) <= htabPtr->yOffset) {
	    for (node = Blt_TreeLastChild(entryPtr->node); node != NULL;
		 node = Blt_TreePrevSibling(node)) {
		entryPtr = NodeToEntry(htabPtr, node);
		if (!(entryPtr->flags & ENTRY_HIDDEN) &&
		    (entryPtr->worldY <= htabPtr->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 (node == NULL) {
		if (htabPtr->yOffset == 0) {
		    return TCL_OK;	/* All entries are hidden. */
		}
		htabPtr->yOffset = 0;
		continue;
	    }
	}
	
	height += htabPtr->yOffset;
	maxX = 0;
	for (/* empty */; entryPtr != NULL; 
	    entryPtr = Blt_HtNextEntry(htabPtr, entryPtr, ENTRY_MASK)) {
	    /*
	     * Compute and save the entry's X-coordinate now that we know
	     * what the maximum level offset for the entire Hiertable is.
	     */
	    level = DEPTH(htabPtr, entryPtr->node);
	    entryPtr->worldX = LEVELX(level) + htabPtr->treeColumnPtr->worldX;
	    
	    x = entryPtr->worldX + ICONWIDTH(level) + ICONWIDTH(level + 1) + 
		entryPtr->width;
	    if (x > maxX) {
		maxX = x;
	    }
	    if (entryPtr->worldY >= height) {
		break;
	    }
	    htabPtr->visibleArr[htabPtr->nVisible] = entryPtr;
	    htabPtr->nVisible++;
	}
	htabPtr->visibleArr[htabPtr->nVisible] = NULL;
    }
    /*
     * -------------------------------------------------------------------
     *
     * 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 (htabPtr->xOffset > (htabPtr->worldWidth - htabPtr->xScrollUnits)) {
	htabPtr->xOffset = htabPtr->worldWidth - htabPtr->xScrollUnits;
    }
    if (htabPtr->yOffset > (htabPtr->worldHeight - htabPtr->yScrollUnits)) {
	htabPtr->yOffset = htabPtr->worldHeight - htabPtr->yScrollUnits;
    }
    htabPtr->xOffset = Blt_AdjustViewport(htabPtr->xOffset, 
	htabPtr->worldWidth, VPORTWIDTH(htabPtr), htabPtr->xScrollUnits, 
	htabPtr->scrollMode);
    htabPtr->yOffset = Blt_AdjustViewport(htabPtr->yOffset,
	htabPtr->worldHeight, VPORTHEIGHT(htabPtr), htabPtr->yScrollUnits,
	htabPtr->scrollMode);
    htabPtr->flags &= ~HT_DIRTY;
    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(htabPtr, entryPtr, drawable)
    Hiertable *htabPtr;		/* Widget record containing the attribute
				 * information for buttons. */
    Entry *entryPtr;		/* Entry to be drawn. */
    Drawable drawable;		/* Pixmap or window to draw into. */
{
    int x1, y1, x2, y2;
    int height, level;
    int x, y;

    while (entryPtr != htabPtr->rootPtr) {
	entryPtr = Blt_HtParentEntry(htabPtr, entryPtr);
	if (entryPtr == NULL) {
	    break;
	}
	level = DEPTH(htabPtr, entryPtr->node);
	/*
	 * 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(level) + htabPtr->treeColumnPtr->worldX;
	x = SCREENX(htabPtr, entryPtr->worldX);
	y = SCREENY(htabPtr, entryPtr->worldY);
	height = MAX(entryPtr->iconHeight, htabPtr->button.height);
	y += (height - htabPtr->button.height) / 2;
	x1 = x2 = x + ICONWIDTH(level) + ICONWIDTH(level + 1) / 2;
	y1 = y + htabPtr->button.height / 2;
	y2 = y1 + entryPtr->lineHeight;
	if ((entryPtr == htabPtr->rootPtr) && (htabPtr->hideRoot)) {
	    y1 += entryPtr->height;
	}
	/*
	 * Clip the line's Y-coordinates at the window border.
	 */
	if (y1 < 0) {
	    y1 = 0;
	}
	if (y2 > Tk_Height(htabPtr->tkwin)) {
	    y2 = Tk_Height(htabPtr->tkwin);
	}
	if ((y1 < Tk_Height(htabPtr->tkwin)) && (y2 > 0)) {
	    XDrawLine(htabPtr->display, drawable, htabPtr->lineGC, 
	      x1, y1, x2, y2);
	}
    }
}

void
Blt_HtDrawRule(htabPtr, columnPtr, drawable)
    Hiertable *htabPtr;		/* Widget record containing the attribute
				 * information for buttons. */
    Column *columnPtr;
    Drawable drawable;		/* Pixmap or window to draw into. */
{
    int x, y1, y2;

    x = SCREENX(htabPtr, columnPtr->worldX) + columnPtr->width +
	htabPtr->ruleMark - htabPtr->ruleAnchor - 1;
    y1 = htabPtr->titleHeight + htabPtr->inset;
    y2 = Tk_Height(htabPtr->tkwin) - htabPtr->inset;
    XDrawLine(htabPtr->display, drawable, columnPtr->rule.gc, x, y1, x, y2);
    htabPtr->flags = TOGGLE(htabPtr->flags, HT_RULE_ACTIVE);
}

/*
 * ---------------------------------------------------------------------------
 *
 * Blt_HtDrawButton --
 *
 * 	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.
 *
 * ---------------------------------------------------------------------------
 */
void
Blt_HtDrawButton(htabPtr, entryPtr, drawable)
    Hiertable *htabPtr;		/* Widget record containing the attribute
				 * information for buttons. */
    Entry *entryPtr;		/* Entry. */
    Drawable drawable;		/* Pixmap or window to draw into. */
{
    Button *buttonPtr = &(htabPtr->button);
    int relief;
    Tk_3DBorder border;
    GC gc, lineGC;
    int x, y;
    HashedImage image;
    int width, height;

    width = ICONWIDTH(DEPTH(htabPtr, entryPtr->node));
    height = MAX(entryPtr->iconHeight, buttonPtr->height);
    entryPtr->buttonX = (width - buttonPtr->width) / 2;
    entryPtr->buttonY = (height - buttonPtr->height) / 2;

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

    if (entryPtr == htabPtr->activeButtonPtr) {
	border = buttonPtr->activeBorder;
	lineGC = gc = buttonPtr->activeGC;
    } else {
	border = buttonPtr->border;
	gc = buttonPtr->normalGC;
	lineGC = buttonPtr->lineGC;
    }
    relief = (entryPtr->flags & ENTRY_CLOSED) 
	? buttonPtr->closeRelief : buttonPtr->openRelief;
    /*
     * 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;
    }
    Tk_Fill3DRectangle(htabPtr->tkwin, drawable, border, x, y,
	buttonPtr->width, buttonPtr->height, buttonPtr->borderWidth, relief);
    if (relief == TK_RELIEF_FLAT) {
	XDrawRectangle(htabPtr->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->hImages != NULL) {  /* Open or close button image? */
	image = buttonPtr->hImages[0];
	if (!(entryPtr->flags & ENTRY_CLOSED) && (buttonPtr->hImages[1] != NULL)) {
	    image = buttonPtr->hImages[1];
	}
    }
    if (image != NULL) {	/* Image or rectangle? */
	Tk_RedrawImage(ImageData(image), 0, 0, width, height, drawable, x, y);
    } else {
	XSegment segArr[2];
	int count;

	gc = (entryPtr == htabPtr->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_CLOSED) {
	    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(htabPtr->display, drawable, gc, segArr, count);
    }
}

int
Blt_HtDrawIcon(htabPtr, entryPtr, x, y, drawable)
    Hiertable *htabPtr;		/* Widget record containing the attribute
				 * information for buttons. */
    Entry *entryPtr;		/* Entry. */
    int x, y;
    Drawable drawable;		/* Pixmap or window to draw into. */
{
    HashedImage image;
    int isActive, hasFocus;
    HashedImage *icons;

    isActive = (entryPtr == htabPtr->activePtr);
    hasFocus = (entryPtr == htabPtr->focusPtr);
    if (isActive) {
	icons = GETACTIVEICONS(htabPtr, entryPtr);
	if (icons == NULL) {
	    icons = GETICONS(htabPtr, entryPtr);
	}
    } else {
	icons = GETICONS(htabPtr, entryPtr);
    }
    image = NULL;
    if (icons != NULL) {	/* Selected or normal icon? */
	image = icons[0];
	if ((hasFocus) && (icons[1] != NULL)) {
	    image = icons[1];
	}
    }
    if (image != NULL) {	/* Image or default icon bitmap? */
	int width, height;
	int top, bottom;
	int topInset, botInset;
	int maxY;
	int entryHeight;
	int level;

	level = DEPTH(htabPtr, entryPtr->node);
	entryHeight = MAX(entryPtr->iconHeight, htabPtr->button.height);
	height = ImageHeight(image);
	width = ImageWidth(image);
	if (htabPtr->flatView) {
	    x += (ICONWIDTH(0) - width) / 2;
	} else {
	    x += (ICONWIDTH(level + 1) - width) / 2;
	}	    
	y += (entryHeight - height) / 2;
	botInset = htabPtr->inset - INSET_PAD;
	topInset = htabPtr->titleHeight + htabPtr->inset;
	maxY = Tk_Height(htabPtr->tkwin) - botInset;
	top = 0;
	bottom = y + height;
	if (y < topInset) {
	    height += y - topInset;
	    top = -y + topInset;
	    y = topInset;
	} else if (bottom >= maxY) {
	    height = maxY - y;
	}
	Tk_RedrawImage(ImageData(image), 0, top, width, height, drawable, x, y);
    } 
    return (int)image;
}

static int
DrawLabel(htabPtr, entryPtr, x, y, drawable)
    Hiertable *htabPtr;		/* Widget record containing the attribute
				 * information for buttons. */
    Entry *entryPtr;
    int x, y;
    Drawable drawable;		/* Pixmap or window to draw into. */
{
    int width, height;		/* Width and height of label. */
    int isFocused;
    int entryHeight;
    int maxX, maxY;
    char *label;
    int x0, y0;

    entryHeight = MAX(entryPtr->iconHeight, htabPtr->button.height);
    isFocused = ((entryPtr == htabPtr->focusPtr) && 
		 (htabPtr->flags & HT_FOCUS));

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

    maxX = Tk_Width(htabPtr->tkwin) - htabPtr->inset;
    maxY = Tk_Height(htabPtr->tkwin) - htabPtr->inset;

    /* Center the label, if necessary, vertically along the entry row. */
    if (height < entryHeight) {
	y += (entryHeight - height) / 2;
    }
    x0 = x;
    y0 = y;
    if (isFocused) {		/* Focus outline */
	XDrawRectangle(htabPtr->display, drawable, htabPtr->focusGC,
	    x, y, width - 1, height - 1);
    }
    x += FOCUS_WIDTH + LABEL_PADX + htabPtr->selBorderWidth;
    y += FOCUS_WIDTH + LABEL_PADY + htabPtr->selBorderWidth;

    label = GETLABEL(entryPtr);
    if (label[0] != '\0') {
	XColor *normalColor, *activeColor;
	Tk_Font font;
	TextStyle style;
	GC gc;
	int selected;

	selected = Blt_HtEntryIsSelected(htabPtr, entryPtr);
	font = GETFONT(htabPtr->treeColumnPtr->font, entryPtr->font);
	normalColor = GETCOLOR(htabPtr->treeColumnPtr->fgColor,entryPtr->color);
	activeColor = (selected) ? htabPtr->selFgColor : normalColor;
	gc = (entryPtr->gc == NULL) ? htabPtr->treeColumnPtr->gc : entryPtr->gc;
	Blt_SetDrawTextStyle(&style, font, gc, normalColor, activeColor, 
		entryPtr->shadow.color, 0.0, TK_ANCHOR_NW, TK_JUSTIFY_LEFT, 0, 
		entryPtr->shadow.offset);
	style.state = (selected || (entryPtr->gc == NULL)) ? STATE_ACTIVE : 0;
	Blt_DrawTextLayout(htabPtr->tkwin, drawable, entryPtr->layoutPtr, 
		&style, x, y);
    }
    return entryHeight;
}

/*
 * ---------------------------------------------------------------------------
 *
 * DrawFlatEntry --
 *
 * 	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
DrawFlatEntry(htabPtr, entryPtr, drawable)
    Hiertable *htabPtr;		/* Widget record containing the attribute
				 * information for buttons. */
    Entry *entryPtr;		/* Entry to be drawn. */
    Drawable drawable;		/* Pixmap or window to draw into. */
{
    int x, y;
    int level;

    entryPtr->flags &= ~ENTRY_REDRAW;

    x = SCREENX(htabPtr, entryPtr->worldX);
    y = SCREENY(htabPtr, entryPtr->worldY);
    if (!Blt_HtDrawIcon(htabPtr, entryPtr, x, y, drawable)) {
	x -= (DEF_ICON_WIDTH * 2) / 3;
    }
    level = 0;
    x += ICONWIDTH(level);
    /* Entry label. */
    DrawLabel(htabPtr, entryPtr, x, y, drawable);
}

/*
 * ---------------------------------------------------------------------------
 *
 * DrawTreeEntry --
 *
 * 	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
DrawTreeEntry(htabPtr, entryPtr, drawable)
    Hiertable *htabPtr;		/* Widget record containing the attribute
				 * information for buttons. */
    Entry *entryPtr;		/* Entry to be drawn. */
    Drawable drawable;		/* Pixmap or window to draw into. */
{
    Button *buttonPtr = &(htabPtr->button);
    int x, y;
    int width, height;
    int buttonY;
    int x1, y1, x2, y2;
    int level;

    entryPtr->flags &= ~ENTRY_REDRAW;

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

    level = DEPTH(htabPtr, entryPtr->node);
    width = ICONWIDTH(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 + (ICONWIDTH(level) + ICONWIDTH(level + 1)) / 2;

    if ((Blt_TreeNodeParent(entryPtr->node) != NULL) && 
	(htabPtr->lineWidth > 0)) {
	/*
	 * For every node except root, draw a horizontal line from
	 * the vertical bar to the middle of the icon.
	 */
	XDrawLine(htabPtr->display, drawable, htabPtr->lineGC, x1, y1, x2, y2);
    }
    if (!(entryPtr->flags & ENTRY_CLOSED) && (htabPtr->lineWidth > 0)) {
	/*
	 * Entry is open, draw vertical line.
	 */
	y2 = y1 + entryPtr->lineHeight;
	if (y2 > Tk_Height(htabPtr->tkwin)) {
	    y2 = Tk_Height(htabPtr->tkwin); /* Clip line at window border. */
	}
	XDrawLine(htabPtr->display, drawable, htabPtr->lineGC, x2, y1, x2, y2);
    }
    if ((entryPtr->flags & ENTRY_BUTTON) && (entryPtr != htabPtr->rootPtr)) {
	/*
	 * 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.
	 */
	Blt_HtDrawButton(htabPtr, entryPtr, drawable);
    }
    x += ICONWIDTH(level);

    if (!Blt_HtDrawIcon(htabPtr, entryPtr, x, y, drawable)) {
	x -= (DEF_ICON_WIDTH * 2) / 3;
    }
    x += ICONWIDTH(level + 1) + 4;

    /* Entry label. */
    DrawLabel(htabPtr, entryPtr, x, y, drawable);
}

/*
 * ---------------------------------------------------------------------------
 *
 * DrawField --
 *
 * 	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
DrawField(htabPtr, columnPtr, entryPtr, drawable)
    Hiertable *htabPtr;		/* Widget record containing the attribute
				 * information for buttons. */
    Column *columnPtr;
    Entry *entryPtr;		/* Node of entry to be drawn. */
    Drawable drawable;		/* Pixmap or window to draw into. */
{
    int x, y;
    Field *fieldPtr;
    int width;

    /* Draw the background of the field. */
    x = SCREENX(htabPtr, columnPtr->worldX);
    y = SCREENY(htabPtr, entryPtr->worldY);
    if (Blt_HtEntryIsSelected(htabPtr, entryPtr)) {
	Tk_Fill3DRectangle(htabPtr->tkwin, drawable, htabPtr->selBorder,
	    x, y, columnPtr->width, entryPtr->height, htabPtr->selBorderWidth, 
	htabPtr->selRelief);
    }
    /* Check if there's a corresponding field in the entry. */
    fieldPtr = FindField(entryPtr, columnPtr);
    if (fieldPtr == NULL) {
#ifdef notdef
	fprintf(stderr, "can't find field %s in %s (%d fields)\n", 
		columnPtr->keyUid, Blt_TreeNodeLabel(entryPtr->node), 
		Blt_ChainGetLength(entryPtr->chainPtr));
#endif
	return;			/* No field. */
    }
    x += columnPtr->pad.side1 + columnPtr->borderWidth;
    width = columnPtr->width - (2 * columnPtr->borderWidth + 
	PADDING(columnPtr->pad));
    if (width > fieldPtr->width) {
	switch(columnPtr->justify) {
	case TK_JUSTIFY_RIGHT:
	    x += (width - fieldPtr->width);
	    break;
	case TK_JUSTIFY_CENTER:
	    x += (width - fieldPtr->width) / 2;
	    break;
	case TK_JUSTIFY_LEFT:
	    break;
	}
    }
    if (fieldPtr->hImage != NULL) {
	Tk_RedrawImage(ImageData(fieldPtr->hImage), 0, 0, 
	       fieldPtr->width, fieldPtr->height, drawable, x, y);
    } else {
	TextStyle style;
	XColor *color;

	if (entryPtr->color != NULL) {
	    XSetForeground(htabPtr->display, columnPtr->gc, 
		  entryPtr->color->pixel);
	    color = entryPtr->color;
	} else {
	    color = columnPtr->fgColor;
	}
	Blt_SetDrawTextStyle(&style, columnPtr->font, columnPtr->gc, color, 
		htabPtr->selFgColor, entryPtr->shadow.color, 0.0, TK_ANCHOR_NW,
	    TK_JUSTIFY_LEFT, 0, entryPtr->shadow.offset);
	Blt_DrawTextLayout(htabPtr->tkwin, drawable, fieldPtr->layoutPtr, 
		&style, x, y);
	if (entryPtr->color != NULL) {
	    XSetForeground(htabPtr->display, columnPtr->gc, 
		  columnPtr->fgColor->pixel);
	}
    }
}

static void
DrawTitle(htabPtr, columnPtr, drawable, x)
    Hiertable *htabPtr;
    Column *columnPtr;
    Drawable drawable;
    int x;
{
    TextStyle style;
    int columnWidth;
    int width;
    int x0, cx, xOffset;
    Tk_3DBorder border;
    GC gc;
    XColor *fgColor;

    columnWidth = columnPtr->width;
    cx = x;
    if (columnPtr->position == Blt_ChainGetLength(htabPtr->chainPtr)) {
	columnWidth = Tk_Width(htabPtr->tkwin) - x;
    } else if (columnPtr->position == 1) {
	columnWidth += x;
	cx = 0;
    }
    x0 = x + columnPtr->borderWidth;

    if (columnPtr == htabPtr->activeColumnPtr) {
	border = columnPtr->activeTitleBorder;
	gc = columnPtr->activeTitleGC;
	fgColor = columnPtr->activeTitleFgColor;
    } else {
	border = columnPtr->titleBorder;
	gc = columnPtr->titleGC;
	fgColor = columnPtr->titleFgColor;
    }
    Tk_Fill3DRectangle(htabPtr->tkwin, drawable, border, cx + 1, 
	htabPtr->inset + 1, columnWidth - 2, htabPtr->titleHeight - 2, 0, 
	TK_RELIEF_FLAT);
    width = columnPtr->width;
    xOffset = x0 + columnPtr->pad.side1 + 1;
    if (width > columnPtr->layoutPtr->width) {
	x += (width - columnPtr->layoutPtr->width) / 2;
    }
    if ((columnPtr == htabPtr->sortInfo.columnPtr) && 
	((x - xOffset) <= (SORT_MARKER_WIDTH + 1))) {
	x = xOffset + SORT_MARKER_WIDTH + 1;
    }
    Blt_SetDrawTextStyle(&style, columnPtr->titleFont, gc, fgColor,
	 htabPtr->selFgColor, columnPtr->titleShadow.color, 0.0, TK_ANCHOR_NW,
	 TK_JUSTIFY_LEFT, 0, columnPtr->titleShadow.offset);
    Blt_DrawTextLayout(htabPtr->tkwin, drawable, columnPtr->layoutPtr, &style, 
	x, htabPtr->inset + 1);
    if (columnPtr == htabPtr->sortInfo.columnPtr) {
	XPoint triangle[4];
	int y;

	y = htabPtr->inset + htabPtr->titleHeight / 2 - 2;
	if (htabPtr->sortInfo.decreasing) {
	    triangle[0].x = x0 + SORT_MARKER_OFFSET + 1;
	    triangle[0].y = y - SORT_MARKER_OFFSET / 2;
	    triangle[1].x = triangle[0].x + SORT_MARKER_OFFSET;
	    triangle[1].y = triangle[0].y + SORT_MARKER_OFFSET;
	    triangle[2].x = triangle[0].x - SORT_MARKER_OFFSET;
	    triangle[2].y = triangle[0].y + SORT_MARKER_OFFSET;
	    triangle[3].x = triangle[0].x;
	    triangle[3].y = triangle[0].y;
	} else {
	    triangle[0].x = x0 + SORT_MARKER_OFFSET + 1;
	    triangle[0].y = y + SORT_MARKER_OFFSET / 2;
	    triangle[1].x = triangle[0].x - SORT_MARKER_OFFSET;
	    triangle[1].y = triangle[0].y - SORT_MARKER_OFFSET;
	    triangle[2].x = triangle[0].x + SORT_MARKER_OFFSET;
	    triangle[2].y = triangle[0].y - SORT_MARKER_OFFSET;
	    triangle[3].x = triangle[0].x;
	    triangle[3].y = triangle[0].y;
	}
	XFillPolygon(htabPtr->display, drawable, gc, triangle, 4, Convex, 
		CoordModeOrigin);
	XDrawLines(htabPtr->display, drawable, gc, triangle, 4, 
		CoordModeOrigin);
    }
    Tk_Draw3DRectangle(htabPtr->tkwin, drawable, border, cx, htabPtr->inset, 
	columnWidth, htabPtr->titleHeight, 1, TK_RELIEF_RAISED);
}

void
Blt_HtDrawHeadings(htabPtr, drawable)
    Hiertable *htabPtr;
    Drawable drawable;
{
    Blt_ChainLink *linkPtr;
    Column *columnPtr;
    int x;

    for (linkPtr = Blt_ChainFirstLink(htabPtr->chainPtr); linkPtr != NULL;
	 linkPtr = Blt_ChainNextLink(linkPtr)) {
	columnPtr = (Column *)Blt_ChainGetValue(linkPtr);
	if (columnPtr->hidden) {
	    continue;
	}
	x = SCREENX(htabPtr, columnPtr->worldX);
	if ((x + columnPtr->width) < 0) {
	    continue;	/* Don't draw columns before the left edge. */
	}
	if (x > Tk_Width(htabPtr->tkwin)) {
	    break;		/* Discontinue when a column starts beyond
				 * the right edge. */
	}
	DrawTitle(htabPtr, columnPtr, drawable, x);
    }
}

static void
DrawTreeView(htabPtr, drawable, x)
    Hiertable *htabPtr;
    Drawable drawable;
    int x;
{
    register Entry **p;

    /* 
     * Draw the backgrounds of selected entries first.  The vertical
     * lines connecting child entries will be draw on top. 
     */
    for (p = htabPtr->visibleArr; *p != NULL; p++) {
	if (Blt_HtEntryIsSelected(htabPtr, *p)) {
	    int y;

	    y = SCREENY(htabPtr, (*p)->worldY);
	    Tk_Fill3DRectangle(htabPtr->tkwin, drawable, htabPtr->selBorder, 
		x, y, htabPtr->treeColumnPtr->width, (*p)->height, 
		htabPtr->selBorderWidth, htabPtr->selRelief);
	}
    }
    if (htabPtr->lineWidth > 0) { 
	/* Draw all the vertical lines from topmost node. */
	DrawVerticals(htabPtr, htabPtr->visibleArr[0], drawable);
    }

    for (p = htabPtr->visibleArr; *p != NULL; p++) {
	DrawTreeEntry(htabPtr, *p, drawable);
    }
}

static void
DrawFlatView(htabPtr, drawable, x)
    Hiertable *htabPtr;
    Drawable drawable;
    int x;
{
    register Entry **p;

    /* 
     * Draw the backgrounds of selected entries first.  The vertical
     * lines connecting child entries will be draw on top. 
     */
    for (p = htabPtr->visibleArr; *p != NULL; p++) {
	if (Blt_HtEntryIsSelected(htabPtr, *p)) {
	    int y;

	    y = SCREENY(htabPtr, (*p)->worldY);
	    Tk_Fill3DRectangle(htabPtr->tkwin, drawable, htabPtr->selBorder, 
		x, y, htabPtr->treeColumnPtr->width, (*p)->height, 
		htabPtr->selBorderWidth, htabPtr->selRelief);
	}
    }
    for (p = htabPtr->visibleArr; *p != NULL; p++) {
	DrawFlatEntry(htabPtr, *p, drawable);
    }
}

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

	color = (htabPtr->flags & HT_FOCUS)
	    ? htabPtr->highlightColor : htabPtr->highlightBgColor;
	gc = Tk_GCForColor(color, drawable);
	Tk_DrawFocusHighlight(htabPtr->tkwin, gc, htabPtr->highlightWidth,
	    drawable);
    }
    htabPtr->flags &= ~HT_BORDERS;
}

/*
 * ----------------------------------------------------------------------
 *
 * DisplayHiertable --
 *
 * 	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
DisplayHiertable(clientData)
    ClientData clientData;	/* Information about widget. */
{
    Hiertable *htabPtr = (Hiertable *)clientData;
    Blt_ChainLink *linkPtr;
    Column *columnPtr;
    int width, height;
    int x;

    htabPtr->flags &= ~HT_REDRAW;
    if (htabPtr->tkwin == NULL) {
	return;			/* Window has been destroyed. */
    }
    if (htabPtr->flags & HT_LAYOUT) {
	/*
	 * Recompute the layout when entries are opened/closed,
	 * inserted/deleted, or when text attributes change
	 * (such as font, linespacing).
	 */
	Blt_HtComputeLayout(htabPtr);
    }
    if (htabPtr->flags & HT_SCROLL) {
	/* 
	 * Scrolling means that the view port has changed and that the
	 * visible entries need to be recomputed.  
	 */
	ComputeVisibleEntries(htabPtr);
	Blt_PickCurrentItem(htabPtr->bindTable);
	Blt_PickCurrentItem(htabPtr->buttonBindTable);

	width = VPORTWIDTH(htabPtr);
	height = VPORTHEIGHT(htabPtr);
	if (htabPtr->flags & HT_XSCROLL) {
	    if (htabPtr->xScrollCmdPrefix != NULL) {
		Blt_UpdateScrollbar(htabPtr->interp, htabPtr->xScrollCmdPrefix,
		    (float)htabPtr->xOffset / htabPtr->worldWidth,
		    (float)(htabPtr->xOffset + width) / htabPtr->worldWidth);
	    }
	}
	if (htabPtr->flags & HT_YSCROLL) {
	    if (htabPtr->yScrollCmdPrefix != NULL) {
		Blt_UpdateScrollbar(htabPtr->interp, htabPtr->yScrollCmdPrefix,
		    (float)htabPtr->yOffset / htabPtr->worldHeight,
		    (float)(htabPtr->yOffset + height) / htabPtr->worldHeight);
	    }
	}
	htabPtr->flags &= ~HT_SCROLL;
    }
    if (htabPtr->reqWidth == 0) {
	htabPtr->reqWidth = htabPtr->worldWidth + 2 * htabPtr->inset;
	Tk_GeometryRequest(htabPtr->tkwin, htabPtr->reqWidth,
	    htabPtr->reqHeight);
    }
    if (!Tk_IsMapped(htabPtr->tkwin)) {
	return;
    }
    if ((htabPtr->drawable == None) || 
	(htabPtr->drawWidth != Tk_Width(htabPtr->tkwin)) ||
	(htabPtr->drawHeight != Tk_Height(htabPtr->tkwin))) {
	if (htabPtr->drawable != None) {
	    Tk_FreePixmap(htabPtr->display, htabPtr->drawable);
	}
	htabPtr->drawWidth = Tk_Width(htabPtr->tkwin);
	htabPtr->drawHeight = Tk_Height(htabPtr->tkwin);
	htabPtr->drawable = Tk_GetPixmap(htabPtr->display, 
		Tk_WindowId(htabPtr->tkwin), 
		htabPtr->drawWidth, htabPtr->drawHeight, 
		Tk_Depth(htabPtr->tkwin));
	htabPtr->flags |= HT_VIEWPORT;
    }

    if ((htabPtr->flags & HT_RULE_ACTIVE) &&
	(htabPtr->resizeColumnPtr != NULL)) {
	Blt_HtDrawRule(htabPtr, htabPtr->resizeColumnPtr, htabPtr->drawable);
    }
    Tk_Fill3DRectangle(htabPtr->tkwin, htabPtr->drawable, htabPtr->border, 0, 0,
       Tk_Width(htabPtr->tkwin), Tk_Height(htabPtr->tkwin), 0,
       TK_RELIEF_FLAT);

    if (htabPtr->nVisible > 0) {
	register Entry **p;

	for (linkPtr = Blt_ChainFirstLink(htabPtr->chainPtr); linkPtr != NULL;
	     linkPtr = Blt_ChainNextLink(linkPtr)) {
	    columnPtr = (Column *)Blt_ChainGetValue(linkPtr);
	    columnPtr->flags &= ~COLUMN_DIRTY;
	    if (columnPtr->hidden) {
		continue;
	    }
	    x = SCREENX(htabPtr, columnPtr->worldX);
	    if ((x + columnPtr->width) < 0) {
		continue;	/* Don't draw columns before the left edge. */
	    }
	    if (x > Tk_Width(htabPtr->tkwin)) {
		break;		/* Discontinue when a column starts beyond
				 * the right edge. */
	    }
	    /* Clear the column background. */
	    Tk_Fill3DRectangle(htabPtr->tkwin, htabPtr->drawable, 
		columnPtr->border, x, 0, columnPtr->width, 
		Tk_Height(htabPtr->tkwin), 0, TK_RELIEF_FLAT);

	    if (columnPtr != htabPtr->treeColumnPtr) {
		for (p = htabPtr->visibleArr; *p != NULL; p++) {
		    DrawField(htabPtr, columnPtr, *p, htabPtr->drawable);
		}
	    } else {
		if (htabPtr->flatView) {
		    DrawFlatView(htabPtr, htabPtr->drawable, x);
		} else {
		    DrawTreeView(htabPtr, htabPtr->drawable, x);
		}
	    }
	    if (columnPtr->relief != TK_RELIEF_FLAT) {
		Tk_Draw3DRectangle(htabPtr->tkwin, htabPtr->drawable, 
			columnPtr->border, x, 0, columnPtr->width, 
			Tk_Height(htabPtr->tkwin), 
			columnPtr->borderWidth, columnPtr->relief);
	    }
	}
    }

    if (htabPtr->showTitles) {
	Blt_HtDrawHeadings(htabPtr, htabPtr->drawable);
    }
    Blt_HtDrawOuterBorders(htabPtr, htabPtr->drawable);
    if ((htabPtr->flags & HT_RULE_NEEDED) &&
	(htabPtr->resizeColumnPtr != NULL)) {
	Blt_HtDrawRule(htabPtr, htabPtr->resizeColumnPtr, htabPtr->drawable);
    }
    /* Now copy the new view to the window. */
    XCopyArea(htabPtr->display, htabPtr->drawable, Tk_WindowId(htabPtr->tkwin),
	htabPtr->lineGC, 0, 0, Tk_Width(htabPtr->tkwin), 
	Tk_Height(htabPtr->tkwin), 0, 0);
    htabPtr->flags &= ~HT_VIEWPORT;
}

/*
 *----------------------------------------------------------------------
 *
 * Blt_HtSelectCmdProc --
 *
 *      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.
 *
 *----------------------------------------------------------------------
 */
void
Blt_HtSelectCmdProc(clientData)
    ClientData clientData;	/* Information about widget. */
{
    Hiertable *htabPtr = (Hiertable *)clientData;

    Tcl_Preserve(htabPtr);
    if (htabPtr->selectCmd != NULL) {
	htabPtr->flags &= ~SELECTION_PENDING;
	if (Tcl_GlobalEval(htabPtr->interp, htabPtr->selectCmd) != TCL_OK) {
	    Tcl_BackgroundError(htabPtr->interp);
	}
    }
    Tcl_Release(htabPtr);
}

/*
 * --------------------------------------------------------------
 *
 * HiertableCmd --
 *
 * 	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
HiertableCmd(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. */
{
    Hiertable *htabPtr;
    Tcl_CmdInfo cmdInfo;

    if (argc < 2) {
	Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
	    " pathName ?option value?...\"", (char *)NULL);
	return TCL_ERROR;
    }
    if (CreateHiertable(interp, argv[1], &htabPtr) != TCL_OK) {
	return TCL_ERROR;
    }
    htabPtr->cmdToken = Tcl_CreateCommand(interp, argv[1], 
	Blt_HtWidgetInstCmd, (ClientData)htabPtr, WidgetInstCmdDeleteProc);
#ifdef ITCL_NAMESPACES
    Itk_SetWidgetCommand(htabPtr->tkwin, htabPtr->cmdToken);
#endif
    Tk_CreateSelHandler(htabPtr->tkwin, XA_PRIMARY, XA_STRING, SelectionProc,
	(ClientData)htabPtr, XA_STRING);
    Tk_CreateEventHandler(htabPtr->tkwin, ExposureMask | StructureNotifyMask |
	FocusChangeMask, HiertableEventProc, (ClientData)htabPtr);
    /*
     * Invoke a procedure to initialize various bindings on hiertable
     * entries.  If the procedure doesn't already exist, source it
     * from "$blt_library/bltHiertable.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::Hiertable::Init", &cmdInfo)) {
	static char initCmd[] =
	{
	    "source [file join $blt_library bltHiertable.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::Hiertable::Init ", argv[1], (char *)NULL)
	!= TCL_OK) {
	goto error;
    }

    bltHiertableLastInstance = htabPtr;
    if (Blt_ConfigureWidgetComponent(interp, htabPtr->tkwin, "button", "Button",
	htabPtr->buttonSpecs, 0, (char **)NULL, (char *)htabPtr, 0) != TCL_OK) {
	goto error;
    }
    if (Blt_HtConfigureHiertable(interp, htabPtr, argc - 2, argv + 2, 0) 
	!= TCL_OK) {
	goto error;
    }
    Tcl_SetResult(interp, Tk_PathName(htabPtr->tkwin), TCL_STATIC);
    return TCL_OK;
  error:
    Tk_DestroyWindow(htabPtr->tkwin);
    return TCL_ERROR;
}

int
Blt_HiertableInit(interp)
    Tcl_Interp *interp;
{
    static Blt_CmdSpec cmdSpec = { "hiertable", HiertableCmd, };

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

#endif /* NO_HIERTABLE */
