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

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

#ifndef NO_HIERTABLE

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

static int StringToColumn _ANSI_ARGS_((ClientData clientData,
	Tcl_Interp *interp, Tk_Window tkwin, char *string, char *widgRec,
	int offset));
static char *ColumnToString _ANSI_ARGS_((ClientData clientData,
	Tk_Window tkwin, char *widgRec, int offset,
	Tcl_FreeProc **freeProcPtrPtr));

static int StringToData _ANSI_ARGS_((ClientData clientData,
	Tcl_Interp *interp, Tk_Window tkwin, char *string, char *widgRec,
	int offset));
static char *DataToString _ANSI_ARGS_((ClientData clientData,
	Tk_Window tkwin, char *widgRec, int offset,
	Tcl_FreeProc **freeProcPtrPtr));

static char *sortModeStrings[] = {
    "dictionary", "ascii", "integer", "real", "command", "none", NULL
};

enum SortModeValues { 
    SORT_DICTIONARY, SORT_ASCII, SORT_INTEGER, SORT_REAL, SORT_COMMAND, 
    SORT_NONE, 
};

static Tk_CustomOption modeOption =
{
    Blt_StringToEnum, Blt_EnumToString, (ClientData)sortModeStrings
};

static Tk_CustomOption columnOption =
{
    StringToColumn, ColumnToString, (ClientData)0
};

Tk_CustomOption bltHiertableDataOption =
{
    StringToData, DataToString, (ClientData)0,
};


#define DEF_SORT_COLUMN		(char *)NULL
#define DEF_SORT_COMMAND	(char *)NULL
#define DEF_SORT_DECREASING	"no"
#define DEF_SORT_MODE		"dictionary"

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

#ifdef WIN32
#define DEF_COLUMN_ACTIVE_TITLE_BG	RGB_GREY85
#else
#define DEF_COLUMN_ACTIVE_TITLE_BG	RGB_GREY90
#endif
#define DEF_COLUMN_ACTIVE_TITLE_FG	STD_COLOR_ACTIVE_FG
#define DEF_COLUMN_BACKGROUND		RGB_WHITE
#define DEF_COLUMN_BIND_TAGS		"all"
#define DEF_COLUMN_BORDER_WIDTH		STD_BORDERWIDTH
#define DEF_COLUMN_COLOR		RGB_BLACK
#define DEF_COLUMN_EDIT			"yes"
#define DEF_COLUMN_FONT			STD_FONT
#define DEF_COLUMN_COMMAND		(char *)NULL
#define DEF_COLUMN_FORMAT_COMMAND	(char *)NULL
#define DEF_COLUMN_HIDE			"no"
#define DEF_COLUMN_JUSTIFY		"center"
#define DEF_COLUMN_MAX			"0"
#define DEF_COLUMN_MIN			"0"
#define DEF_COLUMN_PAD			"2"
#define DEF_COLUMN_RELIEF		"flat"
#define DEF_COLUMN_STATE		"normal"
#define DEF_COLUMN_TEXT			(char *)NULL
#define DEF_COLUMN_TITLE		(char *)NULL
#define DEF_COLUMN_TITLE_BACKGROUND	STD_COLOR_NORMAL_BG
#define DEF_COLUMN_TITLE_FONT		STD_FONT
#define DEF_COLUMN_TITLE_FOREGROUND	STD_COLOR_NORMAL_FG
#define DEF_COLUMN_TITLE_LABEL		(char *)NULL
#define DEF_COLUMN_TITLE_SHADOW		(char *)NULL
#define DEF_COLUMN_WEIGHT		"1.0"
#define DEF_COLUMN_WIDTH		"0"
#define DEF_COLUMN_RULE_DASHES		"dot"

#ifdef __STDC__
static Blt_TreeCompareNodesProc CompareNodes;
static Blt_TreeApplyProc SortApplyProc;
#endif /* __STDC__ */

extern Tk_CustomOption bltDistanceOption;
extern Tk_CustomOption bltPadOption;
extern Tk_CustomOption bltShadowOption;
extern Tk_CustomOption bltStateOption;
extern Tk_CustomOption bltHtUidOption;
extern Tk_CustomOption bltDashesOption;
static Blt_TreeApplyProc SortApplyProc;

static Tk_ConfigSpec columnSpecs[] =
{
    {TK_CONFIG_BORDER, "-activetitlebackground", "activeTitleBackground", 
	"Background", DEF_COLUMN_ACTIVE_TITLE_BG, 
	Tk_Offset(Column, activeTitleBorder), 0},
    {TK_CONFIG_COLOR, "-activetitleforeground", "activeTitleForeground", 
	"Foreground", DEF_COLUMN_ACTIVE_TITLE_FG, 
	Tk_Offset(Column, activeTitleFgColor), 0},
    {TK_CONFIG_BORDER, "-background", "background", "Background",
	DEF_COLUMN_BACKGROUND, Tk_Offset(Column, 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, "-bindtags", "bindTags", "BindTags",
	DEF_COLUMN_BIND_TAGS, Tk_Offset(Column, tags),
	TK_CONFIG_NULL_OK, &bltHtUidOption},
    {TK_CONFIG_CUSTOM, "-borderwidth", "borderWidth", "BorderWidth",
	DEF_COLUMN_BORDER_WIDTH, Tk_Offset(Column, borderWidth),
	TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
    {TK_CONFIG_STRING, "-command", "command", "Command",
	DEF_COLUMN_COMMAND, Tk_Offset(Column, command),
	TK_CONFIG_DONT_SET_DEFAULT | TK_CONFIG_NULL_OK}, 
    {TK_CONFIG_BOOLEAN, "-edit", "edit", "Edit",
	DEF_COLUMN_STATE, Tk_Offset(Column, editable), 
        TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_SYNONYM, "-fg", "foreground", (char *)NULL, (char *)NULL, 0, 0},
    {TK_CONFIG_COLOR, "-foreground", "foreground", "Foreground",
	DEF_COLUMN_COLOR, Tk_Offset(Column, fgColor), TK_CONFIG_NULL_OK},
    {TK_CONFIG_FONT, "-font", "font", "Font",
	DEF_COLUMN_FONT, Tk_Offset(Column, font), 0},
    {TK_CONFIG_BOOLEAN, "-hide", "hide", "Hide",
	DEF_COLUMN_HIDE, Tk_Offset(Column, hidden),
	TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_JUSTIFY, "-justify", "justify", "Justify",
	DEF_COLUMN_JUSTIFY, Tk_Offset(Column, justify), 
        TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_CUSTOM, "-max", "max", "Max",
	DEF_COLUMN_MAX, Tk_Offset(Column, reqMax),
        TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
    {TK_CONFIG_CUSTOM, "-min", "min", "Min",
	DEF_COLUMN_MIN, Tk_Offset(Column, reqMin),
        TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
    {TK_CONFIG_CUSTOM, "-pad", "pad", "Pad",
	DEF_COLUMN_PAD, Tk_Offset(Column, pad), 
        TK_CONFIG_DONT_SET_DEFAULT, &bltPadOption},
    {TK_CONFIG_RELIEF, "-relief", "relief", "Relief",
	DEF_COLUMN_RELIEF, Tk_Offset(Column, relief), 
        TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_CUSTOM, "-ruledashes", "ruleDashes", "RuleDashes",
	DEF_COLUMN_RULE_DASHES, Tk_Offset(Column, rule.dashes),
	TK_CONFIG_NULL_OK, &bltDashesOption},
    {TK_CONFIG_STRING, "-sortcommand", "sortCommand", "SortCommand",
	DEF_SORT_COMMAND, Tk_Offset(Column, sortCmd), TK_CONFIG_NULL_OK}, 
    {TK_CONFIG_CUSTOM, "-state", "state", "State",
	DEF_COLUMN_STATE, Tk_Offset(Column, state), 
	TK_CONFIG_DONT_SET_DEFAULT, &bltStateOption},
    {TK_CONFIG_STRING, "-text", "text", "Text",
	DEF_COLUMN_TITLE_LABEL, Tk_Offset(Column, text), 0},
    {TK_CONFIG_BORDER, "-titlebackground", "titleBackground", "TitleBackground",
	DEF_COLUMN_TITLE_BACKGROUND, Tk_Offset(Column, titleBorder), 0},
    {TK_CONFIG_FONT, "-titlefont", "titleFont", "Font",
	DEF_COLUMN_TITLE_FONT, Tk_Offset(Column, titleFont), 0},
    {TK_CONFIG_COLOR, "-titleforeground", "titleForeground", "TitleForeground",
	DEF_COLUMN_TITLE_FOREGROUND, Tk_Offset(Column, titleFgColor), 0},
    {TK_CONFIG_CUSTOM, "-titleshadow", "titleShadow", "TitleShadow",
	DEF_COLUMN_TITLE_SHADOW, Tk_Offset(Column, titleShadow), 0,
        &bltShadowOption},
    {TK_CONFIG_DOUBLE, "-weight", (char *)NULL, (char *)NULL,
	DEF_COLUMN_WEIGHT, Tk_Offset(Column, weight), 
	TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_CUSTOM, "-width", "width", "Width",
	DEF_COLUMN_WIDTH, Tk_Offset(Column, reqWidth),
        TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
    {TK_CONFIG_END, (char *)NULL, (char *)NULL, (char *)NULL,
	(char *)NULL, 0, 0}
};

static Tk_ConfigSpec sortSpecs[] =
{
    {TK_CONFIG_STRING, "-command", "command", "Command",
	DEF_SORT_COMMAND, Tk_Offset(SortInfo, command),
	TK_CONFIG_DONT_SET_DEFAULT | TK_CONFIG_NULL_OK},
    {TK_CONFIG_CUSTOM, "-column", "column", "Column",
	DEF_SORT_COLUMN, Tk_Offset(SortInfo, columnPtr),
	TK_CONFIG_DONT_SET_DEFAULT, &columnOption},
    {TK_CONFIG_BOOLEAN, "-decreasing", "decreasing", "Decreasing",
	DEF_SORT_DECREASING, Tk_Offset(SortInfo, decreasing),
	TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_CUSTOM, "-mode", "mode", "Mode",
	DEF_SORT_MODE, Tk_Offset(SortInfo, mode), 0, &modeOption},
    {TK_CONFIG_END, (char *)NULL, (char *)NULL, (char *)NULL,
	(char *)NULL, 0, 0}
};

static int GetColumn _ANSI_ARGS_((Tcl_Interp *interp, Hiertable *htabPtr, 
	char *string, Column **columnPtrPtr));
/*
 *----------------------------------------------------------------------
 *
 * StringToColumn --
 *
 *	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
StringToColumn(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 */
{
    Column **columnPtrPtr = (Column **)(widgRec + offset);

    if ((string == NULL) || (*string == '\0')) {
	*columnPtrPtr = NULL;
    } else {
	Hiertable *htabPtr = bltHiertableLastInstance;

	if (GetColumn(interp, htabPtr, string, columnPtrPtr) != TCL_OK) {
	    return TCL_ERROR;
	}
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ColumnToString --
 *
 * Results:
 *	The string representation of the button boolean is returned.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static char *
ColumnToString(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 */
{
    Column *columnPtr = *(Column **)(widgRec + offset);

    if (columnPtr == NULL) {
	return "";
    }
    return columnPtr->keyUid;
}

/*
 *----------------------------------------------------------------------
 *
 * StringToData --
 *
 *	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
StringToData(clientData, interp, tkwin, string, widgRec, offset)
    ClientData clientData;	/* Node of entry. */
    Tcl_Interp *interp;		/* Interpreter to send results back to */
    Tk_Window tkwin;		/* Not used. */
    char *string;		/* New data string */
    char *widgRec;		/* Widget record */
    int offset;			/* key of data field. */
{
    Entry *entryPtr = (Entry *)widgRec;
    char **elemArr;
    int nElem;
    Tcl_Obj *objPtr;
    register int i;
    Column *columnPtr;

    if ((string == NULL) || (*string == '\0')) {
	return TCL_OK;
    } 
    if (Tcl_SplitList(interp, string, &nElem, &elemArr) != TCL_OK) {
	return TCL_ERROR;
    }
    if (nElem == 0) {
	return TCL_OK;
    }
    if (nElem & 0x1) {
	Tcl_AppendResult(interp, "data \"", string, 
		 "\" must in even name-value pairs", 
		(char *)NULL);
	return TCL_ERROR;
    }
    for (i = 0; i < nElem; i += 2) {
	Hiertable *htabPtr = entryPtr->htabPtr;

	if (GetColumn(interp, htabPtr, elemArr[i], &columnPtr) != TCL_OK) {
	    return TCL_ERROR;
	}
	objPtr = Tcl_NewStringObj(elemArr[i + 1], strlen(elemArr[i + 1]));
	Blt_TreeSetValueByUid(htabPtr->tree, entryPtr->node, 
		columnPtr->keyUid, objPtr);
	Blt_HtAddField(entryPtr, columnPtr);
    }
    free((char *)elemArr);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * DataToString --
 *
 * Results:
 *	The string representation of the button boolean is returned.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static char *
DataToString(clientData, tkwin, widgRec, offset, freeProcPtr)
    ClientData clientData;	/* Not used. */
    Tk_Window tkwin;		/* Not used. */
    char *widgRec;		/* Widget record */
    int offset;			/* key of data field. */
    Tcl_FreeProc **freeProcPtr;	/* Memory deallocation scheme to use */
{
    Entry *entryPtr = (Entry *)widgRec;
    Tcl_DString dString;
    Blt_ChainLink *linkPtr;
    char *result;
    Field *dataPtr;
    char *string;

    Tcl_DStringInit(&dString);
    for (linkPtr = Blt_ChainFirstLink(entryPtr->chainPtr); linkPtr != NULL;
	 linkPtr = Blt_ChainNextLink(linkPtr)) {
	dataPtr = (Field *)Blt_ChainGetValue(linkPtr);
	Tcl_DStringAppendElement(&dString, dataPtr->columnPtr->keyUid);
	string = Blt_HtGetData(entryPtr, dataPtr->columnPtr->keyUid);
	if (string == NULL) {
	    Tcl_DStringAppendElement(&dString, "");
	} else {
	    Tcl_DStringAppendElement(&dString, string);
	}
    }
    result = strdup(Tcl_DStringValue(&dString));
    Tcl_DStringFree(&dString);
    *freeProcPtr = (Tcl_FreeProc *)free;
    return result;
}



static int
GetColumn(interp, htabPtr, name, columnPtrPtr)
    Tcl_Interp *interp;
    Hiertable *htabPtr;
    char *name;
    Column **columnPtrPtr;
{
    if (strcmp(name, "treeView") == 0) {
	*columnPtrPtr = htabPtr->treeColumnPtr;
    } else {
	Tcl_HashEntry *hPtr;
	Blt_Uid keyUid;
    
	keyUid = Blt_FindUid(name);
	hPtr = NULL;
	if (keyUid != NULL) {
	    hPtr = Tcl_FindHashEntry(&(htabPtr->columnTable), keyUid);
	}
	if (hPtr == NULL) {
	    if (interp != NULL) {
		Tcl_AppendResult(interp, "can't find column \"", name, 
			"\" in \"", Tk_PathName(htabPtr->tkwin), "\"", 
			(char *)NULL);
	    }
	    return TCL_ERROR;
	} 
	*columnPtrPtr = (Column *)Tcl_GetHashValue(hPtr);
    }
    return TCL_OK;
}

void
Blt_HtConfigureColumn(htabPtr, columnPtr)
    Hiertable *htabPtr;
    Column *columnPtr;
{
    unsigned long gcMask;
    XGCValues gcValues;
    GC newGC;
    TextLayout *layoutPtr;
    TextStyle style;
    unsigned long pixel;
    int ruleDrawn;
    Drawable drawable;

    gcMask = GCForeground | GCFont;
    gcValues.foreground = columnPtr->fgColor->pixel;
    gcValues.font = Tk_FontId(columnPtr->font);
    newGC = Tk_GetGC(htabPtr->tkwin, gcMask, &gcValues);
    if (columnPtr->gc != NULL) {
	Tk_FreeGC(htabPtr->display, columnPtr->gc);
    }
    columnPtr->gc = newGC;

    gcValues.foreground = columnPtr->titleFgColor->pixel;
    gcValues.font = Tk_FontId(columnPtr->titleFont);
    newGC = Tk_GetGC(htabPtr->tkwin, gcMask, &gcValues);
    if (columnPtr->titleGC != NULL) {
	Tk_FreeGC(htabPtr->display, columnPtr->titleGC);
    }
    columnPtr->titleGC = newGC;

    gcValues.foreground = columnPtr->activeTitleFgColor->pixel;
    gcValues.font = Tk_FontId(columnPtr->titleFont);
    newGC = Tk_GetGC(htabPtr->tkwin, gcMask, &gcValues);
    if (columnPtr->activeTitleGC != NULL) {
	Tk_FreeGC(htabPtr->display, columnPtr->activeTitleGC);
    }
    columnPtr->activeTitleGC = newGC;

    memset(&style, 0, sizeof(TextStyle));
    style.font = columnPtr->titleFont;
    style.justify = TK_JUSTIFY_LEFT;
    style.shadow.offset = columnPtr->titleShadow.offset;
    layoutPtr = Blt_GetTextLayout(columnPtr->text, &style);
    if (columnPtr->layoutPtr != NULL) {
	free((char *)columnPtr->layoutPtr);
    }
    columnPtr->layoutPtr = layoutPtr;
    columnPtr->titleWidth = columnPtr->layoutPtr->width + 
	(SORT_MARKER_WIDTH + 1);
    gcMask = (GCFunction | GCLineWidth | GCLineStyle | GCForeground | GCFont);

    /* 
     * If the rule is active, turn it off (draw again to erase it) 
     * before changing the GC.  If the color changes, we won't be
     * able to erase the old line, since it no longer is correctly
     * XOR-ed with the background. 
     */
    drawable = Tk_WindowId(htabPtr->tkwin);
    ruleDrawn = ((htabPtr->flags & HT_RULE_ACTIVE) &&
		  (htabPtr->activeColumnPtr == columnPtr) &&
		 (drawable != None));
    if (ruleDrawn) {
	Blt_HtDrawRule(htabPtr, columnPtr, drawable);
    }
    gcValues.line_width = LineWidth(columnPtr->rule.lineWidth);
    gcValues.foreground = columnPtr->fgColor->pixel;
    if (columnPtr->rule.dashes.nValues > 0) {
	gcValues.line_style = LineOnOffDash;
    } else {
	gcValues.line_style = LineSolid;
    }
    gcValues.function = GXxor;
    pixel = Tk_3DBorderColor(columnPtr->border)->pixel;
    gcValues.foreground ^= pixel; 
    newGC = Blt_GetPrivateGC(htabPtr->tkwin, gcMask, &gcValues);
    if (columnPtr->rule.gc != NULL) {
	Blt_FreePrivateGC(htabPtr->display, columnPtr->rule.gc);
    }
    if (columnPtr->rule.dashes.nValues > 0) {
	Blt_SetDashes(htabPtr->display, newGC, &(columnPtr->rule.dashes));
    }
    columnPtr->rule.gc = newGC;
    if (ruleDrawn) {
	Blt_HtDrawRule(htabPtr, columnPtr, drawable);
    }
    columnPtr->flags |= COLUMN_DIRTY;
    htabPtr->flags |= HT_UPDATE;
}

static void
DestroyColumn(htabPtr, columnPtr)
    Hiertable *htabPtr;
    Column *columnPtr;
{
    Tcl_HashEntry *hPtr;

    Tk_FreeOptions(columnSpecs, (char *)columnPtr, htabPtr->display, 0);

    if (columnPtr->gc != NULL) {
	Tk_FreeGC(htabPtr->display, columnPtr->gc);
    }
    if (columnPtr->titleGC != NULL) {
	Tk_FreeGC(htabPtr->display, columnPtr->titleGC);
    }
    if (columnPtr->rule.gc != NULL) {
	Blt_FreePrivateGC(htabPtr->display, columnPtr->rule.gc);
    }
    hPtr = Tcl_FindHashEntry(&(htabPtr->columnTable), columnPtr->keyUid);
    if (hPtr != NULL) {
	Tcl_DeleteHashEntry(hPtr);
    }
    if (columnPtr->linkPtr != NULL) {
	Blt_ChainDeleteLink(htabPtr->chainPtr, columnPtr->linkPtr);
    }
    if (columnPtr->text != NULL) {
	free(columnPtr->text);
    }
    if (columnPtr->layoutPtr != NULL) {
	free((char *)columnPtr->layoutPtr);
    }
    if (columnPtr->keyUid != NULL) {
	Blt_FreeUid(columnPtr->keyUid);
    }
    if (columnPtr != htabPtr->treeColumnPtr) {
	free ((char *)columnPtr);
    }
}

void
Blt_HtDestroyColumns(htabPtr)
    Hiertable *htabPtr;
{
    if (htabPtr->chainPtr != NULL) {
	Column *columnPtr;
	Blt_ChainLink *linkPtr;
	
	for (linkPtr = Blt_ChainFirstLink(htabPtr->chainPtr); linkPtr != NULL;
	     linkPtr = Blt_ChainNextLink(linkPtr)) {
	    columnPtr = (Column *)Blt_ChainGetValue(linkPtr);
	    columnPtr->linkPtr = NULL;
	    DestroyColumn(htabPtr, columnPtr);
	}
	Blt_ChainDestroy(htabPtr->chainPtr);
	htabPtr->chainPtr = NULL;
    }
    Tcl_DeleteHashTable(&(htabPtr->columnTable));
}

int
Blt_HtInitColumn(htabPtr, columnPtr, name, defTitle, nOptions, options)
    Hiertable *htabPtr;
    Column *columnPtr;
    char *name, *defTitle;
    int nOptions;
    char **options;
{
    Tcl_HashEntry *hPtr;
    int isNew;

    columnPtr->keyUid = Blt_GetUid(name);
    columnPtr->text = strdup(defTitle);
    columnPtr->justify = TK_JUSTIFY_CENTER;
    columnPtr->relief = TK_RELIEF_FLAT;
    columnPtr->borderWidth = 1;
    columnPtr->pad.side1 = columnPtr->pad.side2 = 2;
    columnPtr->state = STATE_NORMAL;
    columnPtr->weight = 1.0;
    columnPtr->editable = FALSE;
    columnPtr->type = ITEM_COLUMN;
    columnPtr->rule.type = ITEM_RULE;
    columnPtr->rule.lineWidth = 1;
    columnPtr->rule.columnPtr = columnPtr;
    hPtr = Tcl_CreateHashEntry(&(htabPtr->columnTable), columnPtr->keyUid, 
       &isNew);
    Tcl_SetHashValue(hPtr, (ClientData)columnPtr);
    bltHiertableLastInstance = htabPtr;
    if (Blt_ConfigureWidgetComponent(htabPtr->interp, htabPtr->tkwin, 
	Tk_GetUid(name), Tk_GetUid("Column"), columnSpecs,
	nOptions, options, (char *)columnPtr, 0) != TCL_OK) {
	DestroyColumn(htabPtr, columnPtr);
	return TCL_ERROR;
    }
    /* 
     * Add callbacks for data field.
     */
    Blt_HtConfigureColumn(htabPtr, columnPtr);
    return TCL_OK;
}

static Column *
CreateColumn(htabPtr, name, defTitle, nOptions, options)
    Hiertable *htabPtr;
    char *name, *defTitle;
    int nOptions;
    char **options;
{
    Column *columnPtr;

    columnPtr = (Column *)calloc(1, sizeof(Column));
    assert(columnPtr);
    if (Blt_HtInitColumn(htabPtr, columnPtr, name, defTitle, nOptions, options) 
	!= TCL_OK) {
	return NULL;
    }
    return columnPtr;
}


Column *
Blt_HtNearestColumn(htabPtr, x, y, flags)
    Hiertable *htabPtr;
    int x, y;
    int flags;
{
    if (flags & SEARCH_Y) {
	if ((y < htabPtr->inset) || 
	    (y >= (htabPtr->titleHeight + htabPtr->inset))) {
	    return NULL;
	}
    }
    if (htabPtr->nVisible > 0) {
	Blt_ChainLink *linkPtr;
	Column *columnPtr;
	int right;

	/*
	 * Determine if the pointer is over the rightmost portion of the
	 * column.  This activates the rule.
	 */
	x = WORLDX(htabPtr, x);
	for(linkPtr = Blt_ChainFirstLink(htabPtr->chainPtr); linkPtr != NULL;
	    linkPtr = Blt_ChainNextLink(linkPtr)) {
	    columnPtr = (Column *)Blt_ChainGetValue(linkPtr);
	    right = columnPtr->worldX + columnPtr->width;
	    columnPtr->flags &= ~COLUMN_RULE_PICKED;
	    if ((x >= columnPtr->worldX) && (x <= right)) {
#define RULE_AREA		(8)
		if (x >= (right - RULE_AREA)) {
		    columnPtr->flags |= COLUMN_RULE_PICKED;
		}
		return columnPtr;
	    }
	}
    }
    return NULL;
}

/*
 *----------------------------------------------------------------------
 *
 * ColumnActivateOp --
 *
 *	Selects the button to appear active.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
ColumnActivateOp(htabPtr, interp, argc, argv)
    Hiertable *htabPtr;
    Tcl_Interp *interp;
    int argc;			/* Not used. */
    char **argv;
{
    if (argc == 4) {
	Column *columnPtr;
	Drawable drawable;

	if (argv[3][0] == '\0') {
	    columnPtr = NULL;
	} else {
	    if (GetColumn(interp, htabPtr, argv[3], &columnPtr) != TCL_OK) {
		return TCL_ERROR;
	    }
	    if ((columnPtr->hidden) || (columnPtr->state == STATE_DISABLED)) {
		columnPtr = NULL;
	    }
	}
	htabPtr->activeColumnPtr = columnPtr;
	drawable = Tk_WindowId(htabPtr->tkwin);
	if (drawable != None) {
	    Blt_HtDrawHeadings(htabPtr, drawable);
	    Blt_HtDrawOuterBorders(htabPtr, drawable);
	}
    }
    if (htabPtr->activeColumnPtr != NULL) {
	Tcl_SetResult(interp, htabPtr->activeColumnPtr->keyUid, TCL_VOLATILE);
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ColumnBindOp --
 *
 *	  .t bind tag sequence command
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
ColumnBindOp(htabPtr, interp, argc, argv)
    Hiertable *htabPtr;
    Tcl_Interp *interp;
    int argc;			/* Not used. */
    char **argv;
{
    ClientData object;
    Column *columnPtr;

    if (GetColumn(NULL, htabPtr, argv[3], &columnPtr) == TCL_OK) {
	object = (ClientData)columnPtr->keyUid;
    } else {
	object = (ClientData)Blt_HtGetUid(htabPtr, argv[3]);
    }
    return Blt_ConfigureBindings(interp, htabPtr->columnBindTable, object,
	argc - 4, argv + 4);
}


/*
 *----------------------------------------------------------------------
 *
 * ColumnCgetOp --
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
ColumnCgetOp(htabPtr, interp, argc, argv)
    Hiertable *htabPtr;
    Tcl_Interp *interp;
    int argc;			/* Not used. */
    char **argv;
{
    Column *columnPtr;

    if (GetColumn(interp, htabPtr, argv[3], &columnPtr) != TCL_OK) {
	return TCL_ERROR;
    }
    return Tk_ConfigureValue(interp, htabPtr->tkwin, columnSpecs, 
	(char *)columnPtr, argv[4], 0);
}

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

    /* Figure out where the option value pairs begin */
    for(i = 3; i < argc; i++) {
	if (argv[i][0] == '-') {
	    break;
	}
	if (GetColumn(interp, htabPtr, argv[i], &columnPtr) != TCL_OK) {
	    return TCL_ERROR;
	}
    }
    start = i;
    nOptions = argc - start;
    
    bltHiertableLastInstance = htabPtr;
    for (i = 3; i < start; i++) {
	if (GetColumn(interp, htabPtr, argv[i], &columnPtr) != TCL_OK) {
	    return TCL_ERROR;
	}
	if (nOptions == 0) {
	    return Tk_ConfigureInfo(interp, htabPtr->tkwin, columnSpecs, 
		(char *)columnPtr, (char *)NULL, 0);
	} else if (nOptions == 1) {
	    return Tk_ConfigureInfo(interp, htabPtr->tkwin, columnSpecs, 
		(char *)columnPtr, argv[start], 0);
	}
	if (Tk_ConfigureWidget(htabPtr->interp, htabPtr->tkwin, columnSpecs, 
		nOptions, argv + start, (char *)columnPtr, 
		TK_CONFIG_ARGV_ONLY) != TCL_OK) {
	    return TCL_ERROR;
	}
	Blt_HtConfigureColumn(htabPtr, columnPtr);
    }
    /*FIXME: Makes every change redo everything. */
    htabPtr->flags |= (HT_LAYOUT | HT_DIRTY);
    Blt_HtEventuallyRedraw(htabPtr);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ColumnDeleteOp --
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
ColumnDeleteOp(htabPtr, interp, argc, argv)
    Hiertable *htabPtr;
    Tcl_Interp *interp;		/* Not used. */
    int argc;
    char **argv;
{
    Column *columnPtr;
    register int i;
    Entry *entryPtr;

    for(i = 3; i < argc; i++) {
	if (GetColumn(interp, htabPtr, argv[i], &columnPtr) != TCL_OK) {
	    return TCL_ERROR;
	}
	/* Traverse the tree deleting column entries as necessary.  */
	for(entryPtr = htabPtr->rootPtr; entryPtr != NULL;
	    entryPtr = Blt_HtNextEntry(htabPtr, entryPtr, 0)) {
	    if (entryPtr != 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->columnPtr == columnPtr) {
			Blt_HtDestroyField(fieldPtr);
			Blt_ChainDeleteLink(entryPtr->chainPtr, linkPtr);
		    }
		}
	    }
	}
	DestroyColumn(htabPtr, columnPtr);
    }
    /* Deleting a column may affect the height of an entry. */
    htabPtr->flags |= (HT_LAYOUT | HT_DIRTY);
    Blt_HtEventuallyRedraw(htabPtr);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ColumnInsertOp --
 *
 *	Add new columns to the tree.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
ColumnInsertOp(htabPtr, interp, argc, argv)
    Hiertable *htabPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    int insertPos;
    register int i;
    int nOptions;
    char **options;
    int start;
    Column *columnPtr;
    Blt_ChainLink *beforePtr;
    Entry *entryPtr;

    if (Blt_GetPosition(htabPtr->interp, argv[3], &insertPos) != TCL_OK) {
	return TCL_ERROR;
    }
    if ((insertPos == -1) || 
	(insertPos >= Blt_ChainGetLength(htabPtr->chainPtr))) {
	beforePtr = NULL;
    } else {
	beforePtr =  Blt_ChainGetNthLink(htabPtr->chainPtr, insertPos);
    }
    /*
     * Count the pathnames that follow.  Count the arguments until we
     * spot one that looks like a configuration option (i.e. starts
     * with a minus ("-")).
     */
    for (i = 4; i < argc; i++) {
	if (argv[i][0] == '-') {
	    break;
	}
    }
    start = i;
    nOptions = argc - i;
    options = argv + start;

    for (i = 4; i < start; i++) {
	if (GetColumn(NULL, htabPtr, argv[i], &columnPtr) == TCL_OK) {
	    Tcl_AppendResult(interp, "column \"", argv[i], "\" already exists",
		     (char *)NULL);
	    return TCL_ERROR;
	}
	columnPtr = CreateColumn(htabPtr, argv[i], argv[i], nOptions, options);
	if (columnPtr == NULL) {
	    return TCL_ERROR;
	}
	if (beforePtr == NULL) {
	    columnPtr->linkPtr = 
		Blt_ChainAppend(htabPtr->chainPtr, (ClientData)columnPtr);
	} else {
	    columnPtr->linkPtr = Blt_ChainNewLink();
	    Blt_ChainSetValue(columnPtr->linkPtr, (ClientData)columnPtr);
	    Blt_ChainLinkBefore(htabPtr->chainPtr, columnPtr->linkPtr, 
		beforePtr);
	}
	/* 
	 * Traverse the tree adding column entries where needed.
	 */
	for(entryPtr = htabPtr->rootPtr; entryPtr != NULL;
	    entryPtr = Blt_HtNextEntry(htabPtr, entryPtr, 0)) {
	    Blt_HtAddField(entryPtr, columnPtr);
	}
    }
    Blt_HtEventuallyRedraw(htabPtr);
    return TCL_OK;
}



/*
 *----------------------------------------------------------------------
 *
 * ColumnCurrentOp --
 *
 *	Make the rule to appear active.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
ColumnCurrentOp(htabPtr, interp, argc, argv)
    Hiertable *htabPtr;
    Tcl_Interp *interp;
    int argc;			/* Not used. */
    char **argv;
{
    Column *columnPtr;

    columnPtr = (Column *)Blt_GetCurrentItem(htabPtr->columnBindTable);
    if (columnPtr != NULL) {
	if (columnPtr->type == ITEM_RULE) {
	    Rule *rulePtr = (Rule *)columnPtr;
	    Tcl_SetResult(interp, rulePtr->columnPtr->keyUid, TCL_VOLATILE);
	} else {
	    Tcl_SetResult(interp, columnPtr->keyUid, TCL_VOLATILE);
	}
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ColumnInvokeOp --
 *
 * 	This procedure is called to invoke a column command.
 *
 *	  .h column invoke columnName
 *
 * Results:
 *	A standard Tcl result.  If TCL_ERROR is returned, then
 *	interp->result contains an error message.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
ColumnInvokeOp(htabPtr, interp, argc, argv)
    Hiertable *htabPtr;
    Tcl_Interp *interp;		/* Not used. */
    int argc;
    char **argv;
{
    Column *columnPtr;

    if (argv[3][0] == '\0') {
	return TCL_OK;
    }
    if (GetColumn(interp, htabPtr, argv[3], &columnPtr) != TCL_OK) {
	return TCL_ERROR;
    }
    if ((columnPtr->state == STATE_NORMAL) && (columnPtr->command != NULL)) {
	int result;

	Tcl_Preserve(htabPtr);
	Tcl_Preserve(columnPtr);
	result = Tcl_GlobalEval(interp, columnPtr->command);
	Tcl_Release(columnPtr);
	Tcl_Release(htabPtr);
	return result;
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ColumnMoveOp --
 *
 *	Move a column.
 *
 * .h column move field1 position
 *----------------------------------------------------------------------
 */

/*
 *----------------------------------------------------------------------
 *
 * ColumnNamesOp --
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
ColumnNamesOp(htabPtr, interp, argc, argv)
    Hiertable *htabPtr;
    Tcl_Interp *interp;
    int argc;			/* Not used. */
    char **argv;
{
    Blt_ChainLink *linkPtr;
    Column *columnPtr;

    for(linkPtr = Blt_ChainFirstLink(htabPtr->chainPtr); linkPtr != NULL;
	linkPtr = Blt_ChainNextLink(linkPtr)) {
	columnPtr = (Column *)Blt_ChainGetValue(linkPtr);
	Tcl_AppendElement(interp, columnPtr->keyUid);
    }
    return TCL_OK;
}

/*ARGSUSED*/
static int
ColumnNearestOp(htabPtr, interp, argc, argv)
    Hiertable *htabPtr;
    Tcl_Interp *interp;
    int argc;			/* Not used. */
    char **argv;
{
    int x, y;			/* Screen coordinates of the test point. */
    Column *columnPtr;
    int flags;

    flags = 0;
    if (Tk_GetPixels(interp, htabPtr->tkwin, argv[3], &x) != TCL_OK) {
	return TCL_ERROR;
    } 
    if (argc == 5) {
	if (Tk_GetPixels(interp, htabPtr->tkwin, argv[4], &y) != TCL_OK) {
	    return TCL_ERROR;
	}
	flags |= SEARCH_Y;
    }
    columnPtr = Blt_HtNearestColumn(htabPtr, x, y, flags);
    if (columnPtr != NULL) {
	Tcl_SetResult(interp, columnPtr->keyUid, TCL_VOLATILE);
    }
    return TCL_OK;
}

static void
UpdateMark(htabPtr, newMark)
    Hiertable *htabPtr;
    int newMark;
{
    if (htabPtr->resizeColumnPtr != NULL) {
	Column *columnPtr;
	Drawable drawable;

	columnPtr = htabPtr->resizeColumnPtr;
	drawable = Tk_WindowId(htabPtr->tkwin);
	if (drawable != None) {
	    int width;
	    int dx;

	    if (htabPtr->flags & HT_RULE_ACTIVE) {
		/* Erase existing rule. */
		Blt_HtDrawRule(htabPtr, columnPtr, drawable);
	    }

	    dx = newMark - htabPtr->ruleAnchor; 
	    width = columnPtr->width - 
		(PADDING(columnPtr->pad) + 2 * columnPtr->borderWidth);
	    if ((columnPtr->reqMin > 0) && ((width + dx) < columnPtr->reqMin)) {
		dx = columnPtr->reqMin - width;
	    }
	    if ((columnPtr->reqMax > 0) && ((width + dx) > columnPtr->reqMax)) {
		dx = columnPtr->reqMax - width;
	    }
	    if ((width + dx) < 4) {
		dx = 4 - width;
	    }
	    htabPtr->ruleMark = htabPtr->ruleAnchor + dx;
	    if (htabPtr->flags & HT_RULE_NEEDED) {
		Blt_HtDrawRule(htabPtr, columnPtr, drawable);
	    }
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * ResizeActivateOp --
 *
 *	Turns on/off the resize cursor.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
ResizeActivateOp(htabPtr, interp, argc, argv)
    Hiertable *htabPtr;
    Tcl_Interp *interp;
    int argc;			/* Not used. */
    char **argv;
{
    Column *columnPtr;

    if (argv[4][0] == '\0') {
	if (htabPtr->cursor != None) {
	    Tk_DefineCursor(htabPtr->tkwin, htabPtr->cursor);
	} else {
	    Tk_UndefineCursor(htabPtr->tkwin);
	}
	htabPtr->resizeColumnPtr = NULL;
    } else if (GetColumn(interp, htabPtr, argv[4], &columnPtr) == TCL_OK) {
	if (htabPtr->resizeCursor != None) {
	    Tk_DefineCursor(htabPtr->tkwin, htabPtr->resizeCursor);
	} else {
	    Tk_DefineCursor(htabPtr->tkwin, htabPtr->defResizeCursor);
	}
	htabPtr->resizeColumnPtr = columnPtr;
    } else {
	return TCL_ERROR;
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ResizeAnchorOp --
 *
 *	Set the anchor for the resize.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
ResizeAnchorOp(htabPtr, interp, argc, argv)
    Hiertable *htabPtr;
    Tcl_Interp *interp;
    int argc;			/* Not used. */
    char **argv;
{
    int x;

    if (Tcl_GetInt(NULL, argv[4], &x) != TCL_OK) {
	return TCL_ERROR;
    } 
    htabPtr->ruleAnchor = x;
    htabPtr->flags |= HT_RULE_NEEDED;
    UpdateMark(htabPtr, x);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ResizeMarkOp --
 *
 *	Sets the resize mark.  The distance between the mark and the anchor
 *	is the delta to change the width of the active column.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
ResizeMarkOp(htabPtr, interp, argc, argv)
    Hiertable *htabPtr;
    Tcl_Interp *interp;
    int argc;			/* Not used. */
    char **argv;
{
    int x;

    if (Tcl_GetInt(NULL, argv[4], &x) != TCL_OK) {
	return TCL_ERROR;
    } 
    htabPtr->flags |= HT_RULE_NEEDED;
    UpdateMark(htabPtr, x);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ResizeSetOp --
 *
 *	Returns the new width of the column including the resize delta.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
ResizeSetOp(htabPtr, interp, argc, argv)
    Hiertable *htabPtr;
    Tcl_Interp *interp;
    int argc;			/* Not used. */
    char **argv;
{
    htabPtr->flags &= ~HT_RULE_NEEDED;
    UpdateMark(htabPtr, htabPtr->ruleMark);
    if (htabPtr->resizeColumnPtr != NULL) {
	int width;

	width = htabPtr->resizeColumnPtr->width + 
	    htabPtr->ruleMark - htabPtr->ruleAnchor - 
	    (PADDING(htabPtr->resizeColumnPtr->pad) + 
	     2 * htabPtr->resizeColumnPtr->borderWidth) - 1;
	Tcl_SetResult(interp, Blt_Itoa(width), TCL_VOLATILE);
    }
    return TCL_OK;
}

static Blt_OpSpec resizeOps[] =
{ 
    {"activate", 2, (Blt_OpProc)ResizeActivateOp, 5, 5, "column"},
    {"anchor", 2, (Blt_OpProc)ResizeAnchorOp, 5, 5, "x"},
    {"mark", 1, (Blt_OpProc)ResizeMarkOp, 5, 5, "x"},
    {"set", 1, (Blt_OpProc)ResizeSetOp, 4, 6, "set ?value?",},
};

static int nResizeOps = sizeof(resizeOps) / sizeof(Blt_OpSpec);

/*
 *----------------------------------------------------------------------
 *
 * ColumnResizeOp --
 *
 *----------------------------------------------------------------------
 */
int
ColumnResizeOp(htabPtr, interp, argc, argv)
    Hiertable *htabPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    Blt_OpProc opProc;
    int result;

    opProc = Blt_GetOperation(interp, nResizeOps, resizeOps, BLT_OPER_ARG3, 
	argc, argv, 0);
    if (opProc == NULL) {
	return TCL_ERROR;
    }
    result = (*opProc) (htabPtr, interp, argc, argv);
    return result;
}


static Blt_OpSpec columnOps[] =
{
    {"activate", 1, (Blt_OpProc)ColumnActivateOp, 3, 4, "?field?",},
    {"bind", 1, (Blt_OpProc)ColumnBindOp, 4, 6, "tagName ?sequence command?",},
    {"cget", 2, (Blt_OpProc)ColumnCgetOp, 5, 5, "field option",},
    {"configure", 2, (Blt_OpProc)ColumnConfigureOp, 4, 0, 
	"field ?option value?...",},
    {"current", 2, (Blt_OpProc)ColumnCurrentOp, 3, 3, "",},
    {"delete", 1, (Blt_OpProc)ColumnDeleteOp, 3, 0, "?field...?",},
    {"highlight", 1, (Blt_OpProc)ColumnActivateOp, 3, 4, "?field?",},
    {"insert", 3, (Blt_OpProc)ColumnInsertOp, 5, 0, 
	"position field ?field...? ?option value?...",},
    {"invoke", 3, (Blt_OpProc)ColumnInvokeOp, 4, 4, "field",},
    {"names", 2, (Blt_OpProc)ColumnNamesOp, 3, 3, "",},
    {"nearest", 2, (Blt_OpProc)ColumnNearestOp, 4, 5, "x ?y?",},
    {"resize", 1, (Blt_OpProc)ColumnResizeOp, 3, 0, "arg",},
};
static int nColumnOps = sizeof(columnOps) / sizeof(Blt_OpSpec);

/*
 *----------------------------------------------------------------------
 *
 * Blt_HtColumnOp --
 *
 *----------------------------------------------------------------------
 */
int
Blt_HtColumnOp(htabPtr, interp, argc, argv)
    Hiertable *htabPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    Blt_OpProc opProc;
    int result;

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


static int
InvokeCompare(htabPtr, e1Ptr, e2Ptr, command)
    Hiertable *htabPtr;
    Entry *e1Ptr, *e2Ptr;
    char *command;
{
    int result;
    Tcl_Interp *interp = htabPtr->interp;
    Tcl_DString dString;
    
    result = 0;			/* Hopefully this will be Ok even if the
				 * Tcl command fails to return the correct
				 * result. */
    Tcl_DStringInit(&dString);
    Tcl_DStringAppend(&dString, command, -1);
    Tcl_DStringAppendElement(&dString, Tk_PathName(htabPtr->tkwin));
    Tcl_DStringAppendElement(&dString, 
	     Blt_Itoa(Blt_TreeNodeId(e1Ptr->node)));
    Tcl_DStringAppendElement(&dString, 
	     Blt_Itoa(Blt_TreeNodeId(e2Ptr->node)));
    Tcl_DStringAppendElement(&dString, htabPtr->sortInfo.columnPtr->keyUid);
    if (htabPtr->flatView) {
	Tcl_DStringAppendElement(&dString, e1Ptr->fullName);
	Tcl_DStringAppendElement(&dString, e2Ptr->fullName);
    } else {
	Tcl_DStringAppendElement(&dString, GETLABEL(e1Ptr));
	Tcl_DStringAppendElement(&dString, GETLABEL(e2Ptr));
    }
    result = Tcl_GlobalEval(interp, Tcl_DStringValue(&dString));
    Tcl_DStringFree(&dString);

    if ((result != TCL_OK) ||
	(Tcl_GetInt(interp, Tcl_GetStringResult(interp), &result) != TCL_OK)) {
	Tcl_BackgroundError(interp);
    }
    Tcl_ResetResult(interp);
    return result;
}


static int
CompareEntries(e1PtrPtr, e2PtrPtr)
    Entry **e1PtrPtr, **e2PtrPtr;
{
    Hiertable *htabPtr = (*e1PtrPtr)->htabPtr;
    char *s1, *s2;
    int result;

    s1 = (char *)(*e1PtrPtr)->data;
    s2 = (char *)(*e2PtrPtr)->data;
    result = 0;
    switch (htabPtr->sortInfo.mode) {
    case SORT_ASCII:
	result = strcmp(s1, s2);
	break;

    case SORT_COMMAND:
	{
	    char *cmd;

	    cmd = htabPtr->sortInfo.columnPtr->sortCmd;
	    if (cmd == NULL) {
		cmd = htabPtr->sortInfo.command;
	    }
	    if (cmd == NULL) {
		result = Blt_DictionaryCompare(s1, s2);
	    } else {
		result = InvokeCompare(htabPtr, *e1PtrPtr, *e2PtrPtr, cmd);
	    }
	}
	break;

    case SORT_DICTIONARY:
	result = Blt_DictionaryCompare(s1, s2);
	break;

    case SORT_INTEGER:
	{
	    int i1, i2;

	    if (Tcl_GetInt(NULL, s1, &i1) == TCL_OK) {
		if (Tcl_GetInt(NULL, s2, &i2) == TCL_OK) {
		    result = i1 - i2;
		} else {
		    result = -1;
		} 
	    } else if (Tcl_GetInt(NULL, s2, &i2) == TCL_OK) {
		result = 1;
	    } else {
		result = Blt_DictionaryCompare(s1, s2);
	    }
	}
	break;

    case SORT_REAL:
	{
	    double r1, r2;

	    if (Tcl_GetDouble(NULL, s1, &r1) == TCL_OK) {
		if (Tcl_GetDouble(NULL, s2, &r2) == TCL_OK) {
		    result = (r1 < r2) ? -1 : (r1 > r2) ? 1 : 0;
		} else {
		    result = -1;
		} 
	    } else if (Tcl_GetDouble(NULL, s2, &r2) == TCL_OK) {
		result = 1;
	    } else {
		result = Blt_DictionaryCompare(s1, s2);
	    }
	}
	break;
    }
    if (htabPtr->sortInfo.decreasing) {
	return -result;
    } 
    return result;
}


/*
 *----------------------------------------------------------------------
 *
 * CompareNodes --
 *
 *	Comparison routine (used by qsort) to sort a chain of subnodes.
 *
 * Results:
 *	1 is the first is greater, -1 is the second is greater, 0
 *	if equal.
 *
 *----------------------------------------------------------------------
 */
static int
CompareNodes(n1Ptr, n2Ptr)
    Blt_TreeNode *n1Ptr, *n2Ptr;
{
    Hiertable *htabPtr = bltHiertableLastInstance;
    Entry *e1Ptr, *e2Ptr;

    e1Ptr = NodeToEntry(htabPtr, *n1Ptr);
    e2Ptr = NodeToEntry(htabPtr, *n2Ptr);

    /* Fetch the data for sorting. */
    if (htabPtr->sortInfo.mode == SORT_COMMAND) {
	e1Ptr->data = (ClientData)Blt_TreeNodeId(*n1Ptr);
	e2Ptr->data = (ClientData)Blt_TreeNodeId(*n2Ptr);
    } else if (htabPtr->sortInfo.columnPtr == htabPtr->treeColumnPtr) {
	if (e1Ptr->fullName == NULL) {
	    e1Ptr->fullName = Blt_HtGetFullName(htabPtr, e1Ptr, TRUE);
	}
	e1Ptr->data = (ClientData)e1Ptr->fullName;
	if (e2Ptr->fullName == NULL) {
	    e2Ptr->fullName = Blt_HtGetFullName(htabPtr, e2Ptr, TRUE);
	}
	e2Ptr->data = (ClientData)e2Ptr->fullName;
    } else {
	Blt_Uid keyUid;

	keyUid = htabPtr->sortInfo.columnPtr->keyUid;
	e1Ptr->data = (ClientData)Blt_HtGetData(e1Ptr, keyUid);
	if (e1Ptr->data == NULL) {
	    e1Ptr->data = (ClientData)"";
	}
	e2Ptr->data = (ClientData)Blt_HtGetData(e2Ptr, keyUid);
	if (e2Ptr->data == NULL) {
	    e2Ptr->data = (ClientData)"";
	}
    }
    return CompareEntries(&e1Ptr, &e2Ptr);
}

static int
SortAutoOp(htabPtr, interp, argc, argv)
    Hiertable *htabPtr;
    Tcl_Interp *interp;		/* Not used. */
    int argc;
    char **argv;
{
    SortInfo *infoPtr;

    infoPtr = &(htabPtr->sortInfo);
    if (argc == 4) {
	int bool;

	if (Tcl_GetBoolean(interp, argv[3], &bool) != TCL_OK) {
	    return TCL_ERROR;
	}
	bool = !bool;
	if (infoPtr->manual != bool) {
	    htabPtr->flags |= (HT_LAYOUT | HT_DIRTY);
	    Blt_HtEventuallyRedraw(htabPtr);
	}
	infoPtr->manual = bool;
    }
    Tcl_SetResult(interp, (infoPtr->manual) ? "0" : "1", TCL_STATIC);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * SortCgetOp --
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
SortCgetOp(htabPtr, interp, argc, argv)
    Hiertable *htabPtr;
    Tcl_Interp *interp;
    int argc;			/* Not used. */
    char **argv;
{
    SortInfo *infoPtr;

    bltHiertableLastInstance = htabPtr;
    infoPtr = &(htabPtr->sortInfo);
    return Tk_ConfigureValue(interp, htabPtr->tkwin, sortSpecs, 
	(char *)infoPtr, argv[3], 0);
}

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

    bltHiertableLastInstance = htabPtr;
    infoPtr = &(htabPtr->sortInfo);
    if (argc == 3) {
	return Tk_ConfigureInfo(interp, htabPtr->tkwin, sortSpecs, 
		(char *)infoPtr, (char *)NULL, 0);
    } else if (argc == 4) {
	return Tk_ConfigureInfo(interp, htabPtr->tkwin, sortSpecs, 
		(char *)infoPtr, argv[3], 0);
    }
    if (Tk_ConfigureWidget(htabPtr->interp, htabPtr->tkwin, sortSpecs, 
		argc - 3, argv + 3, (char *)infoPtr, 
			   TK_CONFIG_ARGV_ONLY) != TCL_OK) {
	return TCL_ERROR;
    }
    htabPtr->flags |= HT_DIRTY;
    /*FIXME: Makes every change redo everything. */
    htabPtr->flags |= (HT_LAYOUT | HT_DIRTY);
    Blt_HtEventuallyRedraw(htabPtr);
    return TCL_OK;
}

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

    recurse = FALSE;
    length = strlen(argv[3]);
    if ((argv[3][0] == '-') && (length > 1) &&
	(strncmp(argv[3], "-recurse", length) == 0)) {
	argv++, argc--;
	recurse = TRUE;
    }
    for (i = 3; i < argc; i++) {
	if (Blt_HtGetEntry(htabPtr, argv[i], &entryPtr) != TCL_OK) {
	    return TCL_ERROR;
	}
	if (recurse) {
	    result = Blt_TreeApply(entryPtr->node, SortApplyProc, 
			   (ClientData)htabPtr);
	} else {
	    result = SortApplyProc(entryPtr->node, (ClientData)htabPtr,
			   TREE_PREORDER);
	}
	if (result != TCL_OK) {
	    return TCL_ERROR;
	}
    }
    htabPtr->flags |= HT_LAYOUT;
    Blt_HtEventuallyRedraw(htabPtr);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * Blt_HtSortOp --
 *
 *	Comparison routine (used by qsort) to sort a chain of subnodes.
 *	A simple string comparison is performed on each node name.
 *
 *	.h sort auto
 *	.h sort once -recurse root
 *
 * Results:
 *	1 is the first is greater, -1 is the second is greater, 0
 *	if equal.
 *
 *----------------------------------------------------------------------
 */
static Blt_OpSpec sortOps[] =
{
    {"auto", 1, (Blt_OpProc)SortAutoOp, 3, 4, "?boolean?",},
    {"cget", 2, (Blt_OpProc)SortCgetOp, 4, 4, "option",},
    {"configure", 2, (Blt_OpProc)SortConfigureOp, 3, 0, "?option value?...",},
    {"once", 1, (Blt_OpProc)SortOnceOp, 3, 3, "?-recurse? node node",},
};
static int nSortOps = sizeof(sortOps) / sizeof(Blt_OpSpec);

/*ARGSUSED*/
int
Blt_HtSortOp(htabPtr, interp, argc, argv)
    Hiertable *htabPtr;
    Tcl_Interp *interp;		/* Not used. */
    int argc;
    char **argv;
{
    Blt_OpProc opProc;
    int result;

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

/*
 *----------------------------------------------------------------------
 *
 * SortApplyProc --
 *
 *	Sorts the subnodes at a given node.
 *
 * Results:
 *	Always returns TCL_OK.
 *
 *----------------------------------------------------------------------
 */
static int
SortApplyProc(node, clientData, order)
    Blt_TreeNode node;
    ClientData clientData;
    int order;			/* Not used. */
{
    Hiertable *htabPtr = (Hiertable *)clientData;
#ifdef notdef
    int sortNeeded;
    Entry *entryPtr;
    Blt_TreeNode child;

    /* Check if any of the children are dirty. */
    sortNeeded = TRUE;
    for(child = Blt_TreeFirstChild(node); child != NULL;
	child = Blt_TreeNextSibling(child)) {
	entryPtr = NodeToEntry(htabPtr, child);
	if (entryPtr->flags & ENTRY_DIRTY) {
	    sortNeeded = TRUE;
	    break;
	}
    }
    if ((sortNeeded) && (Blt_TreeNodeDegree(node) > 1)) {
	Blt_TreeSortNode(htabPtr->tree, node, CompareNodes);
    }
#else
    if (Blt_TreeNodeDegree(node) > 1) {
	Blt_TreeSortNode(htabPtr->tree, node, CompareNodes);
    }
#endif
    return TCL_OK;
}
 
/*
 *----------------------------------------------------------------------
 *
 * Blt_HtSortFlatView --
 *
 *	Sorts the flatten array of entries.
 *
 *----------------------------------------------------------------------
 */
void
Blt_HtSortFlatView(htabPtr)
    Hiertable *htabPtr;
{
    SortInfo *infoPtr;
    Entry *entryPtr, **p;

    infoPtr = &(htabPtr->sortInfo);
    if ((infoPtr->manual) || (infoPtr->mode == SORT_NONE) ||
	(infoPtr->columnPtr == NULL) || (htabPtr->nEntries == 1)) {
	return;
    }
    /* Prefetch the data for sorting. */
    if (htabPtr->sortInfo.columnPtr == htabPtr->treeColumnPtr) {
	for(p = htabPtr->flatArr; *p != NULL; p++) {
	    entryPtr = *p;
	    if (entryPtr->fullName == NULL) {
		entryPtr->fullName = Blt_HtGetFullName(htabPtr, entryPtr, TRUE);
	    }
	    entryPtr->data = (ClientData)entryPtr->fullName;
	}
    } else {
	Blt_Uid keyUid;

	keyUid = htabPtr->sortInfo.columnPtr->keyUid;
	for(p = htabPtr->flatArr; *p != NULL; p++) {
	    entryPtr = *p;
	    entryPtr->data = (ClientData)Blt_HtGetData(entryPtr, keyUid);
	    if (entryPtr->data == (ClientData)NULL) {
		entryPtr->data = (ClientData)"";
	    }
	}
    }
    qsort((char *)htabPtr->flatArr, htabPtr->nEntries, sizeof(Entry *),
	      (QSortCompareProc *)CompareEntries);
}

/*
 *----------------------------------------------------------------------
 *
 * Blt_HtSortTreeView --
 *
 *	Sorts the tree array of entries.
 *
 *----------------------------------------------------------------------
 */
void
Blt_HtSortTreeView(htabPtr)
    Hiertable *htabPtr;
{

    SortInfo *infoPtr;

    infoPtr = &(htabPtr->sortInfo);
    if ((!infoPtr->manual) && 
	(infoPtr->mode != SORT_NONE) && 
	(infoPtr->columnPtr != NULL)) {
	bltHiertableLastInstance = htabPtr;
	Blt_TreeApply(htabPtr->rootPtr->node, SortApplyProc, 
	      (ClientData)htabPtr);
    }
}


#endif /* NO_HIERTABLE */
