/*
 * bltTable.c --
 *
 *	This module implements a table-based geometry manager
 *	for the BLT toolkit.
 *
 * Copyright 1993-1998 Lucent Technologies, Inc.
 *
 * Permission to use, copy, modify, and distribute this software and
 * its documentation for any purpose and without fee is hereby
 * granted, provided that the above copyright notice appear in all
 * copies and that both that the copyright notice and warranty
 * disclaimer appear in supporting documentation, and that the names
 * of Lucent Technologies 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 "blttable" geometry manager was created by George Howlett.
 */

/*
 * To do:
 *
 * 3) No way to detect if widget is already a container of another
 *    geometry manager.  This one is especially bad with toplevel
 *    widgets, causing the window manager to lock-up trying to handle the
 *    myriads of resize requests.
 *
 *    Note: This problem continues in Tk 8.x.  It's possible for a widget
 *	    to be a container for two different geometry managers.  Each manager
 *	    will set its own requested geometry for the container widget. The
 *	    winner sets the geometry last (sometimes ad infinitum).
 *
 * 7) Relative sizing of partitions?
 *
 */

#include "bltInt.h"

#include "bltTable.h"

#define TABLE_THREAD_KEY	"BLT Table Data"
#define TABLE_DEF_PAD		0

/*
 * Default values for widget attributes.
 */
#define DEF_TABLE_ANCHOR	"center"
#define DEF_TABLE_COLUMNS 	"0"
#define DEF_TABLE_FILL		"none"
#define DEF_TABLE_PAD		"0"
#define DEF_TABLE_PROPAGATE 	"1"
#define DEF_TABLE_RESIZE	"both"
#define DEF_TABLE_ROWS		"0"
#define DEF_TABLE_SPAN		"1"
#define DEF_TABLE_CONTROL	"normal"
#define DEF_TABLE_WEIGHT	"1.0"

#define ENTRY_DEF_PAD		0
#define ENTRY_DEF_ANCHOR	TK_ANCHOR_CENTER
#define ENTRY_DEF_FILL		FILL_NONE
#define ENTRY_DEF_SPAN		1
#define ENTRY_DEF_CONTROL	CONTROL_NORMAL
#define ENTRY_DEF_IPAD		0

#define ROWCOL_DEF_RESIZE	(RESIZE_BOTH | RESIZE_VIRGIN)
#define ROWCOL_DEF_PAD		0
#define ROWCOL_DEF_WEIGHT	1.0

static Blt_Uid rowUid, columnUid;

static void WidgetGeometryProc _ANSI_ARGS_((ClientData clientData,
	Tk_Window tkwin));
static void WidgetCustodyProc _ANSI_ARGS_((ClientData clientData,
	Tk_Window tkwin));

static Tk_GeomMgr tableMgrInfo =
{
    "blttable",			/* Name of geometry manager used by winfo */
    WidgetGeometryProc,		/* Procedure to for new geometry requests */
    WidgetCustodyProc,		/* Procedure when widget is taken away */
};

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

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

static Tk_CustomOption limitsOption =
{
    StringToLimits, LimitsToString, (ClientData)0
};

static int StringToResize _ANSI_ARGS_((ClientData clientData,
	Tcl_Interp *interp, Tk_Window tkwin, char *string, char *widgRec,
	int offset));
static char *ResizeToString _ANSI_ARGS_((ClientData clientData,
	Tk_Window tkwin, char *widgRec, int offset,
	Tcl_FreeProc **freeProcPtr));

static Tk_CustomOption resizeOption =
{
    StringToResize, ResizeToString, (ClientData)0
};

static int StringToControl _ANSI_ARGS_((ClientData clientData,
	Tcl_Interp *interp, Tk_Window tkwin, char *string, char *widgRec,
	int offset));
static char *ControlToString _ANSI_ARGS_((ClientData clientData,
	Tk_Window tkwin, char *widgRec, int offset,
	Tcl_FreeProc **freeProcPtr));

static Tk_CustomOption controlOption =
{
    StringToControl, ControlToString, (ClientData)0
};

extern Tk_CustomOption bltPadOption;
extern Tk_CustomOption bltFillOption;
extern Tk_CustomOption bltDistanceOption;

static Tk_ConfigSpec rowConfigSpecs[] =
{
    {TK_CONFIG_CUSTOM, "-height", (char *)NULL, (char *)NULL,
	(char *)NULL, Tk_Offset(RowColumn, reqSize), TK_CONFIG_NULL_OK,
	&limitsOption},
    {TK_CONFIG_CUSTOM, "-pady", (char *)NULL, (char *)NULL,
	DEF_TABLE_PAD, Tk_Offset(RowColumn, pad),
	TK_CONFIG_DONT_SET_DEFAULT, &bltPadOption},
    {TK_CONFIG_CUSTOM, "-resize", (char *)NULL, (char *)NULL,
	DEF_TABLE_RESIZE, Tk_Offset(RowColumn, resize),
	TK_CONFIG_DONT_SET_DEFAULT, &resizeOption},
    {TK_CONFIG_DOUBLE, "-weight", (char *)NULL, (char *)NULL,
	DEF_TABLE_WEIGHT, Tk_Offset(RowColumn, weight),
	TK_CONFIG_NULL_OK | TK_CONFIG_DONT_SET_DEFAULT, &limitsOption},
    {TK_CONFIG_END, NULL, NULL, NULL, NULL, 0, 0}
};

static Tk_ConfigSpec columnConfigSpecs[] =
{
    {TK_CONFIG_CUSTOM, "-padx", (char *)NULL, (char *)NULL,
	DEF_TABLE_PAD, Tk_Offset(RowColumn, pad),
	TK_CONFIG_DONT_SET_DEFAULT, &bltPadOption},
    {TK_CONFIG_CUSTOM, "-resize", (char *)NULL, (char *)NULL,
	DEF_TABLE_RESIZE, Tk_Offset(RowColumn, resize),
	TK_CONFIG_DONT_SET_DEFAULT, &resizeOption},
    {TK_CONFIG_DOUBLE, "-weight", (char *)NULL, (char *)NULL,
	DEF_TABLE_WEIGHT, Tk_Offset(RowColumn, weight),
	TK_CONFIG_NULL_OK | TK_CONFIG_DONT_SET_DEFAULT, &limitsOption},
    {TK_CONFIG_CUSTOM, "-width", (char *)NULL, (char *)NULL,
	(char *)NULL, Tk_Offset(RowColumn, reqSize), TK_CONFIG_NULL_OK,
	&limitsOption},
    {TK_CONFIG_END, NULL, NULL, NULL, NULL, 0, 0}
};


static Tk_ConfigSpec entryConfigSpecs[] =
{
    {TK_CONFIG_ANCHOR, "-anchor", (char *)NULL, (char *)NULL,
	DEF_TABLE_ANCHOR, Tk_Offset(Entry, anchor),
	TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_INT, "-columnspan", "columnSpan", (char *)NULL,
	DEF_TABLE_SPAN, Tk_Offset(Entry, column.span),
	TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_CUSTOM, "-columncontrol", "columnControl", (char *)NULL,
	DEF_TABLE_CONTROL, Tk_Offset(Entry, column.control),
	TK_CONFIG_DONT_SET_DEFAULT, &controlOption},
    {TK_CONFIG_SYNONYM, "-cspan", "columnSpan", (char *)NULL,
	(char *)NULL, Tk_Offset(Entry, column.span), 0},
    {TK_CONFIG_SYNONYM, "-ccontrol", "columnControl", (char *)NULL,
	(char *)NULL, Tk_Offset(Entry, column.control), 0},
    {TK_CONFIG_CUSTOM, "-fill", (char *)NULL, (char *)NULL,
	DEF_TABLE_FILL, Tk_Offset(Entry, fill),
	TK_CONFIG_DONT_SET_DEFAULT, &bltFillOption},
    {TK_CONFIG_SYNONYM, "-height", "reqHeight", (char *)NULL,
	(char *)NULL, Tk_Offset(Entry, reqHeight), 0},
    {TK_CONFIG_CUSTOM, "-padx", (char *)NULL, (char *)NULL,
	(char *)NULL, Tk_Offset(Entry, padX), 0, &bltPadOption},
    {TK_CONFIG_CUSTOM, "-pady", (char *)NULL, (char *)NULL,
	(char *)NULL, Tk_Offset(Entry, padY), 0, &bltPadOption},
    {TK_CONFIG_CUSTOM, "-ipadx", (char *)NULL, (char *)NULL,
	(char *)NULL, Tk_Offset(Entry, ipadX), 0, &bltDistanceOption},
    {TK_CONFIG_CUSTOM, "-ipady", (char *)NULL, (char *)NULL,
	(char *)NULL, Tk_Offset(Entry, ipadY), 0, &bltDistanceOption},
    {TK_CONFIG_CUSTOM, "-reqheight", "reqHeight", (char *)NULL,
	(char *)NULL, Tk_Offset(Entry, reqHeight), TK_CONFIG_NULL_OK,
	&limitsOption},
    {TK_CONFIG_CUSTOM, "-reqwidth", "reqWidth", (char *)NULL,
	(char *)NULL, Tk_Offset(Entry, reqWidth), TK_CONFIG_NULL_OK,
	&limitsOption},
    {TK_CONFIG_INT, "-rowspan", "rowSpan", (char *)NULL,
	DEF_TABLE_SPAN, Tk_Offset(Entry, row.span),
	TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_CUSTOM, "-rowcontrol", "rowControl", (char *)NULL,
	DEF_TABLE_CONTROL, Tk_Offset(Entry, row.control),
	TK_CONFIG_DONT_SET_DEFAULT, &controlOption},
    {TK_CONFIG_SYNONYM, "-rspan", "rowSpan", (char *)NULL,
	(char *)NULL, Tk_Offset(Entry, row.span), 0},
    {TK_CONFIG_SYNONYM, "-rcontrol", "rowControl", (char *)NULL,
	(char *)NULL, Tk_Offset(Entry, row.control), 0},
    {TK_CONFIG_SYNONYM, "-width", "reqWidth", (char *)NULL,
	(char *)NULL, Tk_Offset(Entry, reqWidth), 0},
    {TK_CONFIG_END, NULL, NULL, NULL, NULL, 0, 0}
};


static Tk_ConfigSpec tableConfigSpecs[] =
{
    {TK_CONFIG_CUSTOM, "-padx", (char *)NULL, (char *)NULL,
	DEF_TABLE_PAD, Tk_Offset(Table, padX),
	TK_CONFIG_DONT_SET_DEFAULT, &bltPadOption},
    {TK_CONFIG_CUSTOM, "-pady", (char *)NULL, (char *)NULL,
	DEF_TABLE_PAD, Tk_Offset(Table, padY),
	TK_CONFIG_DONT_SET_DEFAULT, &bltPadOption},
    {TK_CONFIG_BOOLEAN, "-propagate", (char *)NULL, (char *)NULL,
	DEF_TABLE_PROPAGATE, Tk_Offset(Table, propagate),
	TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_CUSTOM, "-reqheight", (char *)NULL, (char *)NULL,
	(char *)NULL, Tk_Offset(Table, reqHeight), TK_CONFIG_NULL_OK,
	&limitsOption},
    {TK_CONFIG_CUSTOM, "-reqwidth", (char *)NULL, (char *)NULL,
	(char *)NULL, Tk_Offset(Table, reqWidth), TK_CONFIG_NULL_OK,
	&limitsOption},
    {TK_CONFIG_END, NULL, NULL, NULL, NULL, 0, 0}
};

/*
 * Forward declarations
 */
static void ArrangeTable _ANSI_ARGS_((ClientData clientData));
static void DestroyTable _ANSI_ARGS_((DestroyData dataPtr));
static void DestroyEntry _ANSI_ARGS_((Entry * entryPtr));
static void TableEventProc _ANSI_ARGS_((ClientData clientData,
	XEvent *eventPtr));
static void BinEntry _ANSI_ARGS_((Table *tablePtr, Entry * entryPtr));
static RowColumn *InitSpan _ANSI_ARGS_((PartitionInfo * infoPtr, int start,
	int span));

static EntrySearchProc FindEntry;
static Tcl_CmdProc TableCmd;
static Tcl_InterpDeleteProc TableInterpDeleteProc;
static Tk_EventProc WidgetEventProc;

/*
 * ----------------------------------------------------------------------------
 *
 * StringToLimits --
 *
 *	Converts the list of elements into zero or more pixel values which
 *	determine the range of pixel values possible.  An element can be in
 *	any form accepted by Tk_GetPixels. The list has a different meaning
 *	based upon the number of elements.
 *
 *	    # of elements:
 *
 *	    0 - the limits are reset to the defaults.
 *	    1 - the minimum and maximum values are set to this
 *		value, freezing the range at a single value.
 *	    2 - first element is the minimum, the second is the
 *		maximum.
 *	    3 - first element is the minimum, the second is the
 *		maximum, and the third is the nominal value.
 *
 *	Any element may be the empty string which indicates the default.
 *
 * Results:
 *	The return value is a standard Tcl result.  The min and max fields
 *	of the range are set.
 *
 * ----------------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
StringToLimits(clientData, interp, tkwin, string, widgRec, offset)
    ClientData clientData;	/* Not used. */
    Tcl_Interp *interp;		/* Interpreter to send results back to */
    Tk_Window tkwin;		/* Widget of table */
    char *string;		/* New width list */
    char *widgRec;		/* Widget record */
    int offset;			/* Offset of limits */
{
    Limits *limitsPtr = (Limits *)(widgRec + offset);
    char **elemArr;
    int nElem;
    int limArr[3];
    Tk_Window winArr[3];
    int flags;

    elemArr = NULL;
    nElem = 0;

    /* Initialize limits to default values */
    limArr[2] = LIMITS_NOM;
    limArr[1] = LIMITS_MAX;
    limArr[0] = LIMITS_MIN;
    winArr[0] = winArr[1] = winArr[2] = NULL;
    flags = 0;

    if (string != NULL) {
	int size;
	int i;

	if (Tcl_SplitList(interp, string, &nElem, &elemArr) != TCL_OK) {
	    return TCL_ERROR;
	}
	if (nElem > 3) {
	    Tcl_AppendResult(interp, "wrong # limits \"", string, "\"",
		(char *)NULL);
	    goto error;
	}
	for (i = 0; i < nElem; i++) {
	    if (elemArr[i][0] == '\0') {
		continue;	/* Empty string: use default value */
	    }
	    flags |= (LIMITS_SET_BIT << i);
	    if ((elemArr[i][0] == '.') &&
		((elemArr[i][1] == '\0') || isalpha(UCHAR(elemArr[i][1])))) {
		Tk_Window tkwin2;

		/* Widget specified: save pointer to widget */
		tkwin2 = Tk_NameToWindow(interp, elemArr[i], tkwin);
		if (tkwin2 == NULL) {
		    goto error;
		}
		winArr[i] = tkwin2;
	    } else {
		if (Tk_GetPixels(interp, tkwin, elemArr[i], &size) != TCL_OK) {
		    goto error;
		}
		if ((size < LIMITS_MIN) || (size > LIMITS_MAX)) {
		    Tcl_AppendResult(interp, "bad limits \"", string, "\"",
			(char *)NULL);
		    goto error;
		}
		limArr[i] = size;
	    }
	}
	Blt_Free(elemArr);
    }
    /*
    * Check the limits specified.  We can't check the requested
    * size of widgets.
    */
    switch (nElem) {
    case 1:
	flags |= (LIMITS_SET_MIN | LIMITS_SET_MAX);
	if (winArr[0] == NULL) {
	    limArr[1] = limArr[0];	/* Set minimum and maximum to value */
	} else {
	    winArr[1] = winArr[0];
	}
	break;

    case 2:
	if ((winArr[0] == NULL) && (winArr[1] == NULL) &&
	    (limArr[1] < limArr[0])) {
	    Tcl_AppendResult(interp, "bad range \"", string,
		"\": min > max", (char *)NULL);
	    return TCL_ERROR;	/* Minimum is greater than maximum */
	}
	break;

    case 3:
	if ((winArr[0] == NULL) && (winArr[1] == NULL)) {
	    if (limArr[1] < limArr[0]) {
		Tcl_AppendResult(interp, "bad range \"", string,
		    "\": min > max", (char *)NULL);
		return TCL_ERROR;	/* Minimum is greater than maximum */
	    }
	    if ((winArr[2] == NULL) &&
		((limArr[2] < limArr[0]) || (limArr[2] > limArr[1]))) {
		Tcl_AppendResult(interp, "nominal value \"", string,
		    "\" out of range", (char *)NULL);
		return TCL_ERROR;	/* Nominal is outside of range defined
					 * by minimum and maximum */
	    }
	}
	break;
    }
    limitsPtr->min = limArr[0];
    limitsPtr->max = limArr[1];
    limitsPtr->nom = limArr[2];
    limitsPtr->wMin = winArr[0];
    limitsPtr->wMax = winArr[1];
    limitsPtr->wNom = winArr[2];
    limitsPtr->flags = flags;
    return TCL_OK;
  error:
    Blt_Free(elemArr);
    return TCL_ERROR;
}

/*
 * ----------------------------------------------------------------------------
 *
 * ResetLimits --
 *
 *	Resets the limits to their default values.
 *
 * Results:
 *	None.
 *
 * ----------------------------------------------------------------------------
 */
INLINE static void
ResetLimits(limitsPtr)
    Limits *limitsPtr;		/* Limits to be imposed on the value */
{
    limitsPtr->flags = 0;
    limitsPtr->min = LIMITS_MIN;
    limitsPtr->max = LIMITS_MAX;
    limitsPtr->nom = LIMITS_NOM;
    limitsPtr->wNom = limitsPtr->wMax = limitsPtr->wMin = NULL;
}

/*
 * ----------------------------------------------------------------------------
 *
 * GetBoundedWidth --
 *
 *	Bounds a given width value to the limits described in the limit
 *	structure.  The initial starting value may be overridden by the
 *	nominal value in the limits.
 *
 * Results:
 *	Returns the constrained value.
 *
 * ----------------------------------------------------------------------------
 */
static int
GetBoundedWidth(width, limitsPtr)
    int width;			/* Initial value to be constrained */
    Limits *limitsPtr;		/* Limits to be imposed on the value */
{
    /*
     * Check widgets for requested width values;
     */
    if (limitsPtr->wMin != NULL) {
	limitsPtr->min = Tk_ReqWidth(limitsPtr->wMin);
    }
    if (limitsPtr->wMax != NULL) {
	limitsPtr->max = Tk_ReqWidth(limitsPtr->wMax);
    }
    if (limitsPtr->wNom != NULL) {
	limitsPtr->nom = Tk_ReqWidth(limitsPtr->wNom);
    }
    if (limitsPtr->flags & LIMITS_SET_NOM) {
	width = limitsPtr->nom;	/* Override initial value */
    }
    if (width < limitsPtr->min) {
	width = limitsPtr->min;	/* Bounded by minimum value */
    } else if (width > limitsPtr->max) {
	width = limitsPtr->max;	/* Bounded by maximum value */
    }
    return width;
}

/*
 * ----------------------------------------------------------------------------
 *
 * GetBoundedHeight --
 *
 *	Bounds a given value to the limits described in the limit
 *	structure.  The initial starting value may be overridden by the
 *	nominal value in the limits.
 *
 * Results:
 *	Returns the constrained value.
 *
 * ----------------------------------------------------------------------------
 */
static int
GetBoundedHeight(height, limitsPtr)
    int height;			/* Initial value to be constrained */
    Limits *limitsPtr;		/* Limits to be imposed on the value */
{
    /*
     * Check widgets for requested height values;
     */
    if (limitsPtr->wMin != NULL) {
	limitsPtr->min = Tk_ReqHeight(limitsPtr->wMin);
    }
    if (limitsPtr->wMax != NULL) {
	limitsPtr->max = Tk_ReqHeight(limitsPtr->wMax);
    }
    if (limitsPtr->wNom != NULL) {
	limitsPtr->nom = Tk_ReqHeight(limitsPtr->wNom);
    }
    if (limitsPtr->flags & LIMITS_SET_NOM) {
	height = limitsPtr->nom;/* Override initial value */
    }
    if (height < limitsPtr->min) {
	height = limitsPtr->min;/* Bounded by minimum value */
    } else if (height > limitsPtr->max) {
	height = limitsPtr->max;/* Bounded by maximum value */
    }
    return height;
}

/*
 * ----------------------------------------------------------------------------
 *
 * NameOfLimits --
 *
 *	Convert the values into a list representing the limits.
 *
 * Results:
 *	The static string representation of the limits is returned.
 *
 * ----------------------------------------------------------------------------
 */
static char *
NameOfLimits(limitsPtr)
    Limits *limitsPtr;
{
    Tcl_DString buffer;
#define STRING_SPACE 200
    static char string[STRING_SPACE + 1];

    Tcl_DStringInit(&buffer);

    if (limitsPtr->wMin != NULL) {
	Tcl_DStringAppendElement(&buffer, Tk_PathName(limitsPtr->wMin));
    } else if (limitsPtr->flags & LIMITS_SET_MIN) {
	Tcl_DStringAppendElement(&buffer, Blt_Itoa(limitsPtr->min));
    } else {
	Tcl_DStringAppendElement(&buffer, "");
    }

    if (limitsPtr->wMax != NULL) {
	Tcl_DStringAppendElement(&buffer, Tk_PathName(limitsPtr->wMax));
    } else if (limitsPtr->flags & LIMITS_SET_MAX) {
	Tcl_DStringAppendElement(&buffer, Blt_Itoa(limitsPtr->max));
    } else {
	Tcl_DStringAppendElement(&buffer, "");
    }

    if (limitsPtr->wNom != NULL) {
	Tcl_DStringAppendElement(&buffer, Tk_PathName(limitsPtr->wNom));
    } else if (limitsPtr->flags & LIMITS_SET_NOM) {
	Tcl_DStringAppendElement(&buffer, Blt_Itoa(limitsPtr->nom));
    } else {
	Tcl_DStringAppendElement(&buffer, "");
    }
    strncpy(string, Tcl_DStringValue(&buffer), STRING_SPACE);
    string[STRING_SPACE] = '\0';
    return string;
}

/*
 * ----------------------------------------------------------------------------
 *
 * LimitsToString --
 *
 *	Convert the limits of the pixel values allowed into a list.
 *
 * Results:
 *	The string representation of the limits is returned.
 *
 * ----------------------------------------------------------------------------
 */
/*ARGSUSED*/
static char *
LimitsToString(clientData, tkwin, widgRec, offset, freeProcPtr)
    ClientData clientData;	/* Not used. */
    Tk_Window tkwin;		/* Not used. */
    char *widgRec;		/* Row/column structure record */
    int offset;			/* Offset of widget RowColumn record */
    Tcl_FreeProc **freeProcPtr;	/* Memory deallocation routine */
{
    Limits *limitsPtr = (Limits *)(widgRec + offset);

    return NameOfLimits(limitsPtr);
}

/*
 * ----------------------------------------------------------------------------
 *
 * StringToResize --
 *
 *	Converts the resize mode into its numeric representation.  Valid
 *	mode strings are "none", "expand", "shrink", or "both".
 *
 * ----------------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
StringToResize(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;		/* Resize style string */
    char *widgRec;		/* Entry structure record */
    int offset;			/* Offset of style in record */
{
    int *resizePtr = (int *)(widgRec + offset);
    unsigned int length;
    char c;

    c = string[0];
    length = strlen(string);
    if ((c == 'n') && (strncmp(string, "none", length) == 0)) {
	*resizePtr = RESIZE_NONE;
    } else if ((c == 'b') && (strncmp(string, "both", length) == 0)) {
	*resizePtr = RESIZE_BOTH;
    } else if ((c == 'e') && (strncmp(string, "expand", length) == 0)) {
	*resizePtr = RESIZE_EXPAND;
    } else if ((c == 's') && (strncmp(string, "shrink", length) == 0)) {
	*resizePtr = RESIZE_SHRINK;
    } else {
	Tcl_AppendResult(interp, "bad resize argument \"", string,
	    "\": should be \"none\", \"expand\", \"shrink\", or \"both\"",
	    (char *)NULL);
	return TCL_ERROR;
    }
    return TCL_OK;
}

/*
 * ----------------------------------------------------------------------------
 *
 * NameOfResize --
 *
 *	Converts the resize value into its string representation.
 *
 * Results:
 *	Returns a pointer to the static name string.
 *
 * ----------------------------------------------------------------------------
 */
static char *
NameOfResize(resize)
    int resize;
{
    switch (resize & RESIZE_BOTH) {
    case RESIZE_NONE:
	return "none";
    case RESIZE_EXPAND:
	return "expand";
    case RESIZE_SHRINK:
	return "shrink";
    case RESIZE_BOTH:
	return "both";
    default:
	return "unknown resize value";
    }
}

/*
 * ----------------------------------------------------------------------------
 *
 * ResizeToString --
 *
 *	Returns resize mode string based upon the resize flags.
 *
 * Results:
 *	The resize mode string is returned.
 *
 * ----------------------------------------------------------------------------
 */
/*ARGSUSED*/
static char *
ResizeToString(clientData, tkwin, widgRec, offset, freeProcPtr)
    ClientData clientData;	/* Not used. */
    Tk_Window tkwin;		/* Not used. */
    char *widgRec;		/* Row/column structure record */
    int offset;			/* Offset of resize in RowColumn record */
    Tcl_FreeProc **freeProcPtr;	/* Not used. */
{
    int resize = *(int *)(widgRec + offset);

    return NameOfResize(resize);
}

/*
 * ----------------------------------------------------------------------------
 *
 * StringToControl --
 *
 *	Converts the control string into its numeric representation.
 *	Valid control strings are "none", "normal", and "full".
 *
 * ----------------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
StringToControl(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;		/* Control style string */
    char *widgRec;		/* Entry structure record */
    int offset;			/* Offset of style in record */
{
    double *controlPtr = (double *)(widgRec + offset);
    unsigned int length;
    int bool;
    char c;

    c = string[0];
    length = strlen(string);
    if (Tcl_GetBoolean(NULL, string, &bool) == TCL_OK) {
	*controlPtr = bool;
	return TCL_OK;
    }
    if ((c == 'n') && (length > 1) &&
	(strncmp(string, "normal", length) == 0)) {
	*controlPtr = CONTROL_NORMAL;
    } else if ((c == 'n') && (length > 1) &&
	(strncmp(string, "none", length) == 0)) {
	*controlPtr = CONTROL_NONE;
    } else if ((c == 'f') && (strncmp(string, "full", length) == 0)) {
	*controlPtr = CONTROL_FULL;
    } else {
	double control;

	if ((Tcl_GetDouble(interp, string, &control) != TCL_OK) ||
	    (control < 0.0)) {
	    Tcl_AppendResult(interp, "bad control argument \"", string,
		"\": should be \"normal\", \"none\", or \"full\"",
		(char *)NULL);
	    return TCL_ERROR;
	}
	*controlPtr = control;
    }
    return TCL_OK;
}

/*
 * ----------------------------------------------------------------------------
 *
 * NameOfControl --
 *
 *	Converts the control value into its string representation.
 *
 * Results:
 *	Returns a pointer to the static name string.
 *
 * ----------------------------------------------------------------------------
 */
static char *
NameOfControl(control)
    double control;
{
    if (control == CONTROL_NORMAL) {
	return "normal";
    } else if (control == CONTROL_NONE) {
	return "none";
    } else if (control == CONTROL_FULL) {
	return "full";
    } else {
	static char string[TCL_DOUBLE_SPACE + 1];

	sprintf(string, "%g", control);
	return string;
    }
}

/*
 * ----------------------------------------------------------------------------
 *
 * ControlToString --
 *
 *	Returns control mode string based upon the control flags.
 *
 * Results:
 *	The control mode string is returned.
 *
 * ----------------------------------------------------------------------------
 */
/*ARGSUSED*/
static char *
ControlToString(clientData, tkwin, widgRec, offset, freeProcPtr)
    ClientData clientData;	/* Not used. */
    Tk_Window tkwin;		/* Not used. */
    char *widgRec;		/* Row/column structure record */
    int offset;			/* Offset of control in RowColumn record */
    Tcl_FreeProc **freeProcPtr;	/* Not used. */
{
    double control = *(double *)(widgRec + offset);

    return NameOfControl(control);
}


static void
EventuallyArrangeTable(tablePtr)
    Table *tablePtr;
{
    if (!(tablePtr->flags & ARRANGE_PENDING)) {
	tablePtr->flags |= ARRANGE_PENDING;
	Tcl_DoWhenIdle(ArrangeTable, tablePtr);
    }
}

/*
 * ----------------------------------------------------------------------------
 *
 * TableEventProc --
 *
 *	This procedure is invoked by the Tk event handler when the
 *	container widget is reconfigured or destroyed.
 *
 *	The table will be rearranged at the next idle point if the
 *	container widget has been resized or moved. There's a
 *	distinction made between parent and non-parent container
 *	arrangements.  If the container is moved and it's the parent
 *	of the widgets, they're are moved automatically.  If it's
 *	not the parent, those widgets need to be moved manually.
 *	This can be a performance hit in rare cases where we're
 *	scrolling the container (by moving the window) and there
 *	are lots of non-child widgets arranged insided.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Arranges for the table associated with tkwin to have its
 *	layout re-computed and drawn at the next idle point.
 *
 * ----------------------------------------------------------------------------
 */
static void
TableEventProc(clientData, eventPtr)
    ClientData clientData;	/* Information about widget */
    XEvent *eventPtr;		/* Information about event */
{
    register Table *tablePtr = clientData;

    if (eventPtr->type == ConfigureNotify) {
	if ((tablePtr->container.width != Tk_Width(tablePtr->tkwin)) ||
	    (tablePtr->container.height != Tk_Height(tablePtr->tkwin))
	    || (tablePtr->flags & NON_PARENT)) {
	    EventuallyArrangeTable(tablePtr);
	}
    } else if (eventPtr->type == DestroyNotify) {
	if (tablePtr->flags & ARRANGE_PENDING) {
	    Tcl_CancelIdleCall(ArrangeTable, tablePtr);
	}
	tablePtr->tkwin = NULL;
	Tcl_EventuallyFree(tablePtr, DestroyTable);
    }
}

/*
 * ----------------------------------------------------------------------------
 *
 * WidgetEventProc --
 *
 *	This procedure is invoked by the Tk event handler when
 *	StructureNotify events occur in a widget managed by the table.
 *	For example, when a managed widget is destroyed, it frees the
 *	corresponding entry structure and arranges for the table
 *	layout to be re-computed at the next idle point.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	If the managed widget was deleted, the Entry structure gets
 *	cleaned up and the table is rearranged.
 *
 * ----------------------------------------------------------------------------
 */
static void
WidgetEventProc(clientData, eventPtr)
    ClientData clientData;	/* Pointer to Entry structure for widget
				 * referred to by eventPtr. */
    XEvent *eventPtr;		/* Describes what just happened. */
{
    Entry *entryPtr = (Entry *) clientData;
    Table *tablePtr = entryPtr->tablePtr;

    if (eventPtr->type == ConfigureNotify) {
	int borderWidth;

	tablePtr->flags |= REQUEST_LAYOUT;
	borderWidth = Tk_Changes(entryPtr->tkwin)->border_width;
	if (entryPtr->borderWidth != borderWidth) {
	    entryPtr->borderWidth = borderWidth;
	    EventuallyArrangeTable(tablePtr);
	}
    } else if (eventPtr->type == DestroyNotify) {
	entryPtr->tkwin = NULL;
	DestroyEntry(entryPtr);
	tablePtr->flags |= REQUEST_LAYOUT;
	EventuallyArrangeTable(tablePtr);
    }
}

/*
 * ----------------------------------------------------------------------------
 *
 * WidgetCustodyProc --
 *
 * 	This procedure is invoked when a widget has been stolen by
 * 	another geometry manager.  The information and memory
 * 	associated with the widget is released.
 *
 * Results:
 *	None.
 *
 * Side effects:
  *	Arranges for the table to have its layout re-arranged at the
 *	next idle point.
 *
 * ----------------------------------------------------------------------------
 */
/* ARGSUSED */
static void
WidgetCustodyProc(clientData, tkwin)
    ClientData clientData;	/* Information about the widget */
    Tk_Window tkwin;		/* Not used. */
{
    Entry *entryPtr = (Entry *) clientData;
    Table *tablePtr = entryPtr->tablePtr;

    if (Tk_IsMapped(entryPtr->tkwin)) {
	Tk_UnmapWindow(entryPtr->tkwin);
    }
    Tk_UnmaintainGeometry(entryPtr->tkwin, tablePtr->tkwin);
    entryPtr->tkwin = NULL;
    DestroyEntry(entryPtr);
    tablePtr->flags |= REQUEST_LAYOUT;
    EventuallyArrangeTable(tablePtr);
}

/*
 * ----------------------------------------------------------------------------
 *
 * WidgetGeometryProc --
 *
 *	This procedure is invoked by Tk_GeometryRequest for widgets
 *	managed by the table geometry manager.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Arranges for the table to have its layout re-computed and
 *	re-arranged at the next idle point.
 *
 * ---------------------------------------------------------------------------- */
/* ARGSUSED */
static void
WidgetGeometryProc(clientData, tkwin)
    ClientData clientData;	/* Information about widget that got new
				 * preferred geometry.  */
    Tk_Window tkwin;		/* Other Tk-related information about the
			         * widget. */
{
    Entry *entryPtr = (Entry *) clientData;

    entryPtr->tablePtr->flags |= REQUEST_LAYOUT;
    EventuallyArrangeTable(entryPtr->tablePtr);
}

/*
 * ----------------------------------------------------------------------------
 *
 * FindEntry --
 *
 *	Searches for the table entry corresponding to the given
 *	widget.
 *
 * Results:
 *	If a structure associated with the widget exists, a pointer to
 *	that structure is returned. Otherwise NULL.
 *
 * ----------------------------------------------------------------------------
 */
static Entry *
FindEntry(tablePtr, tkwin)
    Table *tablePtr;
    Tk_Window tkwin;		/* Widget associated with table entry */
{
    Blt_HashEntry *hPtr;

    hPtr = Blt_FindHashEntry(&(tablePtr->entryTable), (char *)tkwin);
    if (hPtr == NULL) {
	return NULL;
    }
    return (Entry *) Blt_GetHashValue(hPtr);
}


static int
GetEntry(interp, tablePtr, string, entryPtrPtr)
    Tcl_Interp *interp;
    Table *tablePtr;
    char *string;
    Entry **entryPtrPtr;
{
    Tk_Window tkwin;
    Entry *entryPtr;

    tkwin = Tk_NameToWindow(interp, string, tablePtr->tkwin);
    if (tkwin == NULL) {
	return TCL_ERROR;
    }
    entryPtr = FindEntry(tablePtr, tkwin);
    if (entryPtr == NULL) {
	Tcl_AppendResult(interp, "\"", Tk_PathName(tkwin),
	    "\" is not managed by any table", (char *)NULL);
	return TCL_ERROR;
    }
    *entryPtrPtr = entryPtr;
    return TCL_OK;
}

/*
 * ----------------------------------------------------------------------------
 *
 * CreateEntry --
 *
 *	This procedure creates and initializes a new Entry structure
 *	to hold a widget.  A valid widget has a parent widget that is
 *	either a) the container widget itself or b) a mutual ancestor
 *	of the container widget.
 *
 * Results:
 *	Returns a pointer to the new structure describing the new
 *	widget entry.  If an error occurred, then the return
 *	value is NULL and an error message is left in interp->result.
 *
 * Side effects:
 *	Memory is allocated and initialized for the Entry structure.
 *
 * ---------------------------------------------------------------------------- */
static Entry *
CreateEntry(tablePtr, tkwin)
    Table *tablePtr;
    Tk_Window tkwin;
{
    register Entry *entryPtr;
    int dummy;
    Tk_Window parent, ancestor;

    /*
     * Check that this widget can be managed by this table.  A valid
     * widget has a parent widget that either
     *
     *    1) is the container widget, or
     *    2) is a mutual ancestor of the container widget.
     */
    ancestor = Tk_Parent(tkwin);
    for (parent = tablePtr->tkwin; (parent != ancestor) &&
	(!Tk_IsTopLevel(parent)); parent = Tk_Parent(parent)) {
	/* empty */
    }
    if (ancestor != parent) {
	Tcl_AppendResult(tablePtr->interp, "can't manage \"",
	    Tk_PathName(tkwin), "\" in table \"", Tk_PathName(tablePtr->tkwin),
	    "\"", (char *)NULL);
	return NULL;
    }
    entryPtr = Blt_Calloc(1, sizeof(Entry));
    assert(entryPtr);

    /* Initialize the entry structure */

    entryPtr->tkwin = tkwin;
    entryPtr->tablePtr = tablePtr;
    entryPtr->borderWidth = Tk_Changes(tkwin)->border_width;
    entryPtr->fill = ENTRY_DEF_FILL;
    entryPtr->row.control = entryPtr->column.control = ENTRY_DEF_CONTROL;
    entryPtr->anchor = ENTRY_DEF_ANCHOR;
    entryPtr->row.span = entryPtr->column.span = ENTRY_DEF_SPAN;
    ResetLimits(&(entryPtr->reqWidth));
    ResetLimits(&(entryPtr->reqHeight));

    /*
     * Add the entry to the following data structures.
     *
     * 	1) A chain of widgets managed by the table.
     *   2) A hash table of widgets managed by the table.
     */
    entryPtr->linkPtr = Blt_ChainAppend(tablePtr->chainPtr, entryPtr);
    entryPtr->hashPtr = Blt_CreateHashEntry(&(tablePtr->entryTable),
	(char *)tkwin, &dummy);
    Blt_SetHashValue(entryPtr->hashPtr, entryPtr);

    Tk_CreateEventHandler(tkwin, StructureNotifyMask, WidgetEventProc, 
	entryPtr);
    Tk_ManageGeometry(tkwin, &tableMgrInfo, (ClientData)entryPtr);

    return entryPtr;
}

/*
 * ----------------------------------------------------------------------------
 *
 * DestroyEntry --
 *
 *	Removes the Entry structure from the hash table and frees
 *	the memory allocated by it.  If the table is still in use
 *	(i.e. was not called from DestoryTable), remove its entries
 *	from the lists of row and column sorted partitions.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Everything associated with the entry is freed up.
 *
 * ----------------------------------------------------------------------------
 */
static void
DestroyEntry(entryPtr)
    Entry *entryPtr;
{
    Table *tablePtr = entryPtr->tablePtr;

    if (entryPtr->row.linkPtr != NULL) {
	Blt_ChainDeleteLink(entryPtr->row.chainPtr, entryPtr->row.linkPtr);
    }
    if (entryPtr->column.linkPtr != NULL) {
	Blt_ChainDeleteLink(entryPtr->column.chainPtr,
	    entryPtr->column.linkPtr);
    }
    if (entryPtr->linkPtr != NULL) {
	Blt_ChainDeleteLink(tablePtr->chainPtr, entryPtr->linkPtr);
    }
    if (entryPtr->tkwin != NULL) {
	Tk_DeleteEventHandler(entryPtr->tkwin, StructureNotifyMask,
	      WidgetEventProc, (ClientData)entryPtr);
	Tk_ManageGeometry(entryPtr->tkwin, (Tk_GeomMgr *)NULL,
			  (ClientData)entryPtr);
	if ((tablePtr->tkwin != NULL) && 
	    (Tk_Parent(entryPtr->tkwin) != tablePtr->tkwin)) {
	    Tk_UnmaintainGeometry(entryPtr->tkwin, tablePtr->tkwin);
	}
	if (Tk_IsMapped(entryPtr->tkwin)) {
	    Tk_UnmapWindow(entryPtr->tkwin);
	}
    }
    if (entryPtr->hashPtr != NULL) {
	Blt_DeleteHashEntry(&(tablePtr->entryTable), entryPtr->hashPtr);
    }
    Blt_Free(entryPtr);
}

/*
 * ----------------------------------------------------------------------------
 *
 * ConfigureEntry --
 *
 *	This procedure is called to process an argv/argc list, plus
 *	the Tk option database, in order to configure (or reconfigure)
 *	one or more entries.  Entries hold information about widgets
 *	managed by the table geometry manager.
 *
 * 	Note: You can query only one widget at a time.  But several
 * 	      can be reconfigured at once.
 *
 * Results:
 *	The return value is a standard Tcl result.  If TCL_ERROR is
 *	returned, then interp->result contains an error message.
 *
 * Side effects:
 *	The table layout is recomputed and rearranged at the next idle
 *	point.
 *
 * ----------------------------------------------------------------------------
 */
static int
ConfigureEntry(tablePtr, interp, entryPtr, argc, argv)
    Table *tablePtr;
    Tcl_Interp *interp;
    Entry *entryPtr;
    int argc;			/* Option-value arguments */
    char **argv;
{
    int oldRowSpan, oldColSpan;

    if (entryPtr->tablePtr != tablePtr) {
	Tcl_AppendResult(interp, "widget  \"", Tk_PathName(entryPtr->tkwin),
	    "\" does not belong to table \"", Tk_PathName(tablePtr->tkwin),
	    "\"", (char *)NULL);
	return TCL_ERROR;
    }
    if (argc == 0) {
	return Tk_ConfigureInfo(interp, entryPtr->tkwin, entryConfigSpecs,
	    (char *)entryPtr, (char *)NULL, 0);
    } else if (argc == 1) {
	return Tk_ConfigureInfo(interp, entryPtr->tkwin, entryConfigSpecs,
	    (char *)entryPtr, argv[0], 0);
    }
    oldRowSpan = entryPtr->row.span;
    oldColSpan = entryPtr->column.span;

    if (Tk_ConfigureWidget(interp, entryPtr->tkwin, entryConfigSpecs,
	    argc, argv, (char *)entryPtr, TK_CONFIG_ARGV_ONLY) != TCL_OK) {
	return TCL_ERROR;
    }
    if ((entryPtr->column.span < 1) || (entryPtr->column.span > USHRT_MAX)) {
	Tcl_AppendResult(interp, "bad column span specified for \"",
	    Tk_PathName(entryPtr->tkwin), "\"", (char *)NULL);
	return TCL_ERROR;
    }
    if ((entryPtr->row.span < 1) || (entryPtr->row.span > USHRT_MAX)) {
	Tcl_AppendResult(interp, "bad row span specified for \"",
	    Tk_PathName(entryPtr->tkwin), "\"", (char *)NULL);
	return TCL_ERROR;
    }
    if ((oldColSpan != entryPtr->column.span) ||
	(oldRowSpan != entryPtr->row.span)) {
	BinEntry(tablePtr, entryPtr);
    }
    return TCL_OK;
}

/*
 * ----------------------------------------------------------------------------
 *
 * PrintEntry --
 *
 *	Returns the name, position and options of a widget in the table.
 *
 * Results:
 *	Returns a standard Tcl result.  A list of the widget
 *	attributes is left in interp->result.
 *
 * ----------------------------------------------------------------------------
 */
/*ARGSUSED*/
static void
PrintEntry(entryPtr, resultPtr)
    Entry *entryPtr;
    Tcl_DString *resultPtr;
{
    char string[200];

    sprintf(string, "    %d,%d  ", entryPtr->row.rcPtr->index,
	entryPtr->column.rcPtr->index);
    Tcl_DStringAppend(resultPtr, string, -1);
    Tcl_DStringAppend(resultPtr, Tk_PathName(entryPtr->tkwin), -1);
    if (entryPtr->ipadX != ENTRY_DEF_PAD) {
	Tcl_DStringAppend(resultPtr, " -ipadx ", -1);
	Tcl_DStringAppend(resultPtr, Blt_Itoa(entryPtr->ipadX), -1);
    }
    if (entryPtr->ipadY != ENTRY_DEF_PAD) {
	Tcl_DStringAppend(resultPtr, " -ipady ", -1);
	Tcl_DStringAppend(resultPtr, Blt_Itoa(entryPtr->ipadY), -1);
    }
    if (entryPtr->row.span != ENTRY_DEF_SPAN) {
	Tcl_DStringAppend(resultPtr, " -rowspan ", -1);
	Tcl_DStringAppend(resultPtr, Blt_Itoa(entryPtr->row.span), -1);
    }
    if (entryPtr->column.span != ENTRY_DEF_SPAN) {
	Tcl_DStringAppend(resultPtr, " -columnspan ", -1);
	Tcl_DStringAppend(resultPtr, Blt_Itoa(entryPtr->column.span), -1);
    }
    if (entryPtr->anchor != ENTRY_DEF_ANCHOR) {
	Tcl_DStringAppend(resultPtr, " -anchor ", -1);
	Tcl_DStringAppend(resultPtr, Tk_NameOfAnchor(entryPtr->anchor), -1);
    }
    if ((entryPtr->padLeft != ENTRY_DEF_PAD) ||
	(entryPtr->padRight != ENTRY_DEF_PAD)) {
	Tcl_DStringAppend(resultPtr, " -padx ", -1);
	sprintf(string, "{%d %d}", entryPtr->padLeft, entryPtr->padRight);
	Tcl_DStringAppend(resultPtr, string, -1);
    }
    if ((entryPtr->padTop != ENTRY_DEF_PAD) ||
	(entryPtr->padBottom != ENTRY_DEF_PAD)) {
	Tcl_DStringAppend(resultPtr, " -pady ", -1);
	sprintf(string, "{%d %d}", entryPtr->padTop, entryPtr->padBottom);
	Tcl_DStringAppend(resultPtr, string, -1);
    }
    if (entryPtr->fill != ENTRY_DEF_FILL) {
	Tcl_DStringAppend(resultPtr, " -fill ", -1);
	Tcl_DStringAppend(resultPtr, Blt_NameOfFill(entryPtr->fill), -1);
    }
    if (entryPtr->column.control != ENTRY_DEF_CONTROL) {
	Tcl_DStringAppend(resultPtr, " -columncontrol ", -1);
	Tcl_DStringAppend(resultPtr, NameOfControl(entryPtr->column.control), -1);
    }
    if (entryPtr->row.control != ENTRY_DEF_CONTROL) {
	Tcl_DStringAppend(resultPtr, " -rowcontrol ", -1);
	Tcl_DStringAppend(resultPtr, NameOfControl(entryPtr->row.control), -1);
    }
    if ((entryPtr->reqWidth.nom != LIMITS_NOM) ||
	(entryPtr->reqWidth.min != LIMITS_MIN) ||
	(entryPtr->reqWidth.max != LIMITS_MAX)) {
	Tcl_DStringAppend(resultPtr, " -reqwidth {", -1);
	Tcl_DStringAppend(resultPtr, NameOfLimits(&(entryPtr->reqWidth)), -1);
	Tcl_DStringAppend(resultPtr, "}", -1);
    }
    if ((entryPtr->reqHeight.nom != LIMITS_NOM) ||
	(entryPtr->reqHeight.min != LIMITS_MIN) ||
	(entryPtr->reqHeight.max != LIMITS_MAX)) {
	Tcl_DStringAppend(resultPtr, " -reqheight {", -1);
	Tcl_DStringAppend(resultPtr, NameOfLimits(&(entryPtr->reqHeight)), -1);
	Tcl_DStringAppend(resultPtr, "}", -1);
    }
}

/*
 * ----------------------------------------------------------------------------
 *
 * InfoEntry --
 *
 *	Returns the name, position and options of a widget in the table.
 *
 * Results:
 *	Returns a standard Tcl result.  A list of the widget
 *	attributes is left in interp->result.
 *
 * ----------------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
InfoEntry(interp, tablePtr, entryPtr)
    Tcl_Interp *interp;
    Table *tablePtr;
    Entry *entryPtr;
{
    Tcl_DString dString;

    if (entryPtr->tablePtr != tablePtr) {
	Tcl_AppendResult(interp, "widget  \"", Tk_PathName(entryPtr->tkwin),
	    "\" does not belong to table \"", Tk_PathName(tablePtr->tkwin),
	    "\"", (char *)NULL);
	return TCL_ERROR;
    }
    Tcl_DStringInit(&dString);
    PrintEntry(entryPtr, &dString);
    Tcl_DStringResult(interp, &dString);
    return TCL_OK;
}

/*
 * ----------------------------------------------------------------------------
 *
 * CreateRowColumn --
 *
 *	Creates and initializes a structure that manages the size of a
 *	row or column in the table. There will be one of these
 *	structures allocated for each row and column in the table,
 *	regardless if a widget is contained in it or not.
 *
 * Results:
 *	Returns a pointer to the newly allocated row or column
 *	structure.
 *
 * ----------------------------------------------------------------------------
 */
static RowColumn *
CreateRowColumn()
{
    RowColumn *rcPtr;

    rcPtr = Blt_Malloc(sizeof(RowColumn));
    rcPtr->resize = ROWCOL_DEF_RESIZE;
    ResetLimits(&(rcPtr->reqSize));
    rcPtr->nomSize = LIMITS_NOM;
    rcPtr->pad.side1 = rcPtr->pad.side2 = ROWCOL_DEF_PAD;
    rcPtr->size = rcPtr->index = rcPtr->minSpan = 0;
    rcPtr->weight = ROWCOL_DEF_WEIGHT;
    return rcPtr;
}

static PartitionInfo *
ParseRowColumn2(tablePtr, string, numberPtr)
    Table *tablePtr;
    char *string;
    int *numberPtr;
{
    char c;
    int n;
    PartitionInfo *infoPtr;

    c = tolower(string[0]);
    if (c == 'c') {
	infoPtr = &(tablePtr->columnInfo);
    } else if (c == 'r') {
	infoPtr = &(tablePtr->rowInfo);
    } else {
	Tcl_AppendResult(tablePtr->interp, "bad index \"", string,
	    "\": must start with \"r\" or \"c\"", (char *)NULL);
	return NULL;
    }
    /* Handle row or column configuration queries */
    if (Tcl_GetInt(tablePtr->interp, string + 1, &n) != TCL_OK) {
	return NULL;
    }
    *numberPtr = (int)n;
    return infoPtr;
}

static PartitionInfo *
ParseRowColumn(tablePtr, string, numberPtr)
    Table *tablePtr;
    char *string;
    int *numberPtr;
{
    int n;
    PartitionInfo *infoPtr;

    infoPtr = ParseRowColumn2(tablePtr, string, &n);
    if (infoPtr == NULL) {
	return NULL;
    }
    if ((n < 0) || (n >= Blt_ChainGetLength(infoPtr->chainPtr))) {
	Tcl_AppendResult(tablePtr->interp, "bad ", infoPtr->type, " index \"",
	    string, "\"", (char *)NULL);
	return NULL;
    }
    *numberPtr = (int)n;
    return infoPtr;
}

/*
 * ----------------------------------------------------------------------------
 *
 * GetRowColumn --
 *
 *	Gets the designated row or column from the table.  If the row
 *	or column index is greater than the size of the table, new
 *	rows/columns will be automatically allocated.
 *
 * Results:
 *	Returns a pointer to the row or column structure.
 *
 * ----------------------------------------------------------------------------
 */
static RowColumn *
GetRowColumn(infoPtr, n)
    PartitionInfo *infoPtr;
    int n;
{
    Blt_ChainLink *linkPtr;
    RowColumn *rcPtr;
    register int i;

    for (i = Blt_ChainGetLength(infoPtr->chainPtr); i <= n; i++) {
	rcPtr = CreateRowColumn();
	rcPtr->index = i;
	rcPtr->linkPtr = Blt_ChainAppend(infoPtr->chainPtr, (ClientData)rcPtr);
    }
    linkPtr = Blt_ChainGetNthLink(infoPtr->chainPtr, n);
    if (linkPtr == NULL) {
	return NULL;
    }
    return Blt_ChainGetValue(linkPtr);
}

/*
 * ----------------------------------------------------------------------------
 *
 * DeleteRowColumn --
 *
 *	Deletes a span of rows/columns from the table. The number of
 *	rows/columns to be deleted is given by span.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The size of the column partition array may be extended and
 *	initialized.
 *
 * ----------------------------------------------------------------------------
 */
static void
DeleteRowColumn(tablePtr, infoPtr, rcPtr)
    Table *tablePtr;
    PartitionInfo *infoPtr;
    RowColumn *rcPtr;
{
    Blt_ChainLink *linkPtr, *nextPtr;
    Entry *entryPtr;

    /*
     * Remove any entries that start in the row/column to be deleted.
     * They point to memory that will be freed.
     */
    if (infoPtr->type == rowUid) {
	for (linkPtr = Blt_ChainFirstLink(tablePtr->chainPtr); linkPtr != NULL;
	    linkPtr = nextPtr) {
	    nextPtr = Blt_ChainNextLink(linkPtr);
	    entryPtr = Blt_ChainGetValue(linkPtr);
	    if (entryPtr->row.rcPtr->index == rcPtr->index) {
		DestroyEntry(entryPtr);
	    }
	}
    } else {
	for (linkPtr = Blt_ChainFirstLink(tablePtr->chainPtr); linkPtr != NULL;
	    linkPtr = nextPtr) {
	    nextPtr = Blt_ChainNextLink(linkPtr);
	    entryPtr = Blt_ChainGetValue(linkPtr);
	    if (entryPtr->column.rcPtr->index == rcPtr->index) {
		DestroyEntry(entryPtr);
	    }
	}
    }
}

/*
 * ----------------------------------------------------------------------------
 *
 * ConfigureRowColumn --
 *
 *	This procedure is called to process an argv/argc list in order
 *	to configure a row or column in the table geometry manager.
 *
 * Results:
 *	The return value is a standard Tcl result.  If TCL_ERROR is
 *	returned, then interp->result holds an error message.
 *
 * Side effects:
 *	Partition configuration options (bounds, resize flags, etc)
 *	get set.  New partitions may be created as necessary. The
 *	table is recalculated and arranged at the next idle point.
 *
 * ----------------------------------------------------------------------------
 */
static int
ConfigureRowColumn(tablePtr, infoPtr, pattern, argc, argv)
    Table *tablePtr;		/* Table to be configured */
    PartitionInfo *infoPtr;
    char *pattern;
    int argc;
    char **argv;
{
    RowColumn *rcPtr;
    register Blt_ChainLink *linkPtr;
    char string[200];
    int nMatches;

    nMatches = 0;
    for (linkPtr = Blt_ChainFirstLink(infoPtr->chainPtr); linkPtr != NULL;
	linkPtr = Blt_ChainNextLink(linkPtr)) {
	rcPtr = Blt_ChainGetValue(linkPtr);
	sprintf(string, "%c%d", pattern[0], rcPtr->index);
	if (Tcl_StringMatch(string, pattern)) {
	    if (argc == 0) {
		return Tk_ConfigureInfo(tablePtr->interp, tablePtr->tkwin,
		    infoPtr->configSpecs, (char *)rcPtr, NULL, 0);
	    } else if (argc == 1) {
		return Tk_ConfigureInfo(tablePtr->interp, tablePtr->tkwin,
		    infoPtr->configSpecs, (char *)rcPtr, argv[0], 0);
	    } else {
		if (Tk_ConfigureWidget(tablePtr->interp, tablePtr->tkwin,
			infoPtr->configSpecs, argc, argv, (char *)rcPtr,
			TK_CONFIG_ARGV_ONLY) != TCL_OK) {
		    return TCL_ERROR;
		}
	    }
	    nMatches++;
	}
    }
    if (nMatches == 0) {
	int n;

	/* 
	 * We found no existing partitions matching this pattern, so 
	 * see if this designates an new partition (one beyond the 
	 * current range).  
	 */
	if ((Tcl_GetInt(NULL, pattern + 1, &n) != TCL_OK) || (n < 0)) {
	    Tcl_AppendResult(tablePtr->interp, "pattern \"", pattern, 
		     "\" matches no ", infoPtr->type, " in table \"", 
		     Tk_PathName(tablePtr->tkwin), "\"", (char *)NULL);
	    return TCL_ERROR;
	}
	rcPtr = GetRowColumn(infoPtr, n);
	assert(rcPtr);
	if (Tk_ConfigureWidget(tablePtr->interp, tablePtr->tkwin,
	       infoPtr->configSpecs, argc, argv, (char *)rcPtr,
	       TK_CONFIG_ARGV_ONLY) != TCL_OK) {
	    return TCL_ERROR;
	}
    }
    EventuallyArrangeTable(tablePtr);
    return TCL_OK;
}

static void
PrintRowColumn(interp, infoPtr, rcPtr, resultPtr)
    Tcl_Interp *interp;
    PartitionInfo *infoPtr;
    RowColumn *rcPtr;
    Tcl_DString *resultPtr;
{
    char string[200];
    char *padFmt, *sizeFmt;

    if (infoPtr->type == rowUid) {
	padFmt = " -pady {%d %d}";
	sizeFmt = " -height {%s}";
    } else {
	padFmt = " -padx {%d %d}";
	sizeFmt = " -width {%s}";
    }
    if (rcPtr->resize != ROWCOL_DEF_RESIZE) {
	Tcl_DStringAppend(resultPtr, " -resize ", -1);
	Tcl_DStringAppend(resultPtr, NameOfResize(rcPtr->resize), -1);
    }
    if ((rcPtr->pad.side1 != ROWCOL_DEF_PAD) ||
	(rcPtr->pad.side2 != ROWCOL_DEF_PAD)) {
	sprintf(string, padFmt, rcPtr->pad.side1, rcPtr->pad.side2);
	Tcl_DStringAppend(resultPtr, string, -1);
    }
    if (rcPtr->weight != ROWCOL_DEF_WEIGHT) {
	Tcl_DStringAppend(resultPtr, " -weight ", -1);
	Tcl_DStringAppend(resultPtr, Blt_Dtoa(interp, rcPtr->weight), -1);
    }
    if ((rcPtr->reqSize.min != LIMITS_MIN) ||
	(rcPtr->reqSize.nom != LIMITS_NOM) ||
	(rcPtr->reqSize.max != LIMITS_MAX)) {
	sprintf(string, sizeFmt, NameOfLimits(&(rcPtr->reqSize)));
	Tcl_DStringAppend(resultPtr, string, -1);
    }
}

/*
 * ----------------------------------------------------------------------------
 *
 * InfoRowColumn --
 *
 *	Returns the options of a partition in the table.
 *
 * Results:
 *	Returns a standard Tcl result.  A list of the partition
 *	attributes is left in interp->result.
 *
 * ----------------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
InfoRowColumn(tablePtr, interp, pattern)
    Table *tablePtr;
    Tcl_Interp *interp;
    char *pattern;
{
    RowColumn *rcPtr;
    char string[200];
    PartitionInfo *infoPtr;
    char c;
    Blt_ChainLink *linkPtr, *lastPtr;
    Tcl_DString dString;

    c = pattern[0];
    if ((c == 'r') || (c == 'R')) {
	infoPtr = &(tablePtr->rowInfo);
    } else {
	infoPtr = &(tablePtr->columnInfo);
    }
    Tcl_DStringInit(&dString);
    lastPtr = Blt_ChainLastLink(infoPtr->chainPtr);
    for (linkPtr = Blt_ChainFirstLink(infoPtr->chainPtr); linkPtr != NULL;
	linkPtr = Blt_ChainNextLink(linkPtr)) {
	rcPtr = Blt_ChainGetValue(linkPtr);
	sprintf(string, "%c%d", infoPtr->type[0], rcPtr->index);
	if (Tcl_StringMatch(string, pattern)) {
	    Tcl_DStringAppend(&dString, string, -1);
	    PrintRowColumn(interp, infoPtr, rcPtr, &dString);
	    if (linkPtr != lastPtr) {
		Tcl_DStringAppend(&dString, " \\\n", -1);
	    } else {
		Tcl_DStringAppend(&dString, "\n", -1);
	    }
	}
    }
    Tcl_DStringResult(interp, &dString);
    return TCL_OK;
}

/*
 * ----------------------------------------------------------------------------
 *
 * InitSpan --
 *
 *	Checks the size of the column partitions and extends the size
 *	if a larger array is needed.
 *
 * Results:
 *	Returns 1 if the column exists.  Otherwise 0 is returned and
 *	interp->result contains an error message.
 *
 * Side effects:
 *	The size of the column partition array may be extended and
 *	initialized.
 *
 * ----------------------------------------------------------------------------
 */
static RowColumn *
InitSpan(infoPtr, start, span)
    PartitionInfo *infoPtr;
    int start, span;
{
    int length;
    RowColumn *rcPtr;
    register int i;
    Blt_ChainLink *linkPtr;

    length = Blt_ChainGetLength(infoPtr->chainPtr);
    for (i = length; i < (start + span); i++) {
	rcPtr = CreateRowColumn();
	rcPtr->index = i;
	rcPtr->linkPtr = Blt_ChainAppend(infoPtr->chainPtr, (ClientData)rcPtr);
    }
    linkPtr = Blt_ChainGetNthLink(infoPtr->chainPtr, start);
    return Blt_ChainGetValue(linkPtr);
}

/*
 * ----------------------------------------------------------------------------
 *
 * Blt_GetTable --
 *
 *	Searches for a table associated by the path name of the widget
 *	container.
 *
 *	Errors may occur because
 *	  1) pathName isn't a valid for any Tk widget, or
 *	  2) there's no table associated with that widget as a container.
 *
 * Results:
 *	If a table entry exists, a pointer to the Table structure is
 *	returned. Otherwise NULL is returned.
 *
 * ----------------------------------------------------------------------------
 */
/*LINTLIBRARY*/
int
Blt_GetTable(dataPtr, interp, pathName, tablePtrPtr)
    TableInterpData *dataPtr;	/* Interpreter-specific data. */
    Tcl_Interp *interp;		/* Interpreter to report errors back to. */
    char *pathName;		/* Path name of the container widget. */
    Table **tablePtrPtr;
{
    Blt_HashEntry *hPtr;
    Tk_Window tkwin;

    tkwin = Tk_NameToWindow(interp, pathName, Tk_MainWindow(interp));
    if (tkwin == NULL) {
	return TCL_ERROR;
    }
    hPtr = Blt_FindHashEntry(&(dataPtr->tableTable), (char *)tkwin);
    if (hPtr == NULL) {
	Tcl_AppendResult(interp, "no table associated with widget \"",
	    pathName, "\"", (char *)NULL);
	return TCL_ERROR;
    }
    *tablePtrPtr = (Table *)Blt_GetHashValue(hPtr);
    return TCL_OK;
}

/*
 * ----------------------------------------------------------------------------
 *
 * CreateTable --
 *
 *	This procedure creates and initializes a new Table structure
 *	with tkwin as its container widget. The internal structures
 *	associated with the table are initialized.
 *
 * Results:
 *	Returns the pointer to the new Table structure describing the
 *	new table geometry manager.  If an error occurred, the return
 *	value will be NULL and an error message is left in
 *	interp->result.
 *
 * Side effects:
 *	Memory is allocated and initialized, an event handler is set
 *	up to watch tkwin, etc.
 *
 * ----------------------------------------------------------------------------
 */
static Table *
CreateTable(dataPtr, interp, pathName)
    TableInterpData *dataPtr;
    Tcl_Interp *interp;		/* Interpreter associated with table. */
    char *pathName;		/* Path name of the container widget to be
				 * associated with the new table. */
{
    register Table *tablePtr;
    Tk_Window tkwin;
    int dummy;
    Blt_HashEntry *hPtr;

    tkwin = Tk_NameToWindow(interp, pathName, Tk_MainWindow(interp));
    if (tkwin == NULL) {
	return NULL;
    }
    tablePtr = Blt_Calloc(1, sizeof(Table));
    assert(tablePtr);
    tablePtr->tkwin = tkwin;
    tablePtr->interp = interp;
    tablePtr->rowInfo.type = rowUid;
    tablePtr->rowInfo.configSpecs = rowConfigSpecs;
    tablePtr->rowInfo.chainPtr = Blt_ChainCreate();
    tablePtr->columnInfo.type = columnUid;
    tablePtr->columnInfo.configSpecs = columnConfigSpecs;
    tablePtr->columnInfo.chainPtr = Blt_ChainCreate();
    tablePtr->propagate = TRUE;

    tablePtr->arrangeProc = ArrangeTable;
    Blt_InitHashTable(&(tablePtr->entryTable), BLT_ONE_WORD_KEYS);
    tablePtr->findEntryProc = FindEntry;

    ResetLimits(&(tablePtr->reqWidth));
    ResetLimits(&(tablePtr->reqHeight));

    tablePtr->chainPtr = Blt_ChainCreate();
    tablePtr->rowInfo.list = Blt_ListCreate(BLT_ONE_WORD_KEYS);
    tablePtr->columnInfo.list = Blt_ListCreate(BLT_ONE_WORD_KEYS);

    Tk_CreateEventHandler(tablePtr->tkwin, StructureNotifyMask,
	TableEventProc, (ClientData)tablePtr);
    hPtr = Blt_CreateHashEntry(&(dataPtr->tableTable), (char *)tkwin, &dummy);
    tablePtr->hashPtr = hPtr;
    tablePtr->tablePtr = &(dataPtr->tableTable);
    Blt_SetHashValue(hPtr, (ClientData)tablePtr);
    return tablePtr;
}

/*
 * ----------------------------------------------------------------------------
 *
 * ConfigureTable --
 *
 *	This procedure is called to process an argv/argc list in order
 *	to configure the table geometry manager.
 *
 * Results:
 *	The return value is a standard Tcl result.  If TCL_ERROR is
 *	returned, then interp->result contains an error message.
 *
 * Side effects:
 *	Table configuration options (-padx, -pady, etc.) get set.  The
 *	table is recalculated and arranged at the next idle point.
 *
 * ----------------------------------------------------------------------------
 */
static int
ConfigureTable(tablePtr, interp, argc, argv)
    Table *tablePtr;		/* Table to be configured */
    Tcl_Interp *interp;		/* Interpreter to report results back to */
    int argc;
    char **argv;		/* Option-value pairs */
{
    if (argc == 0) {
	return Tk_ConfigureInfo(interp, tablePtr->tkwin, tableConfigSpecs,
	    (char *)tablePtr, (char *)NULL, 0);
    } else if (argc == 1) {
	return Tk_ConfigureInfo(interp, tablePtr->tkwin, tableConfigSpecs,
	    (char *)tablePtr, argv[0], 0);
    }
    if (Tk_ConfigureWidget(interp, tablePtr->tkwin, tableConfigSpecs,
	    argc, argv, (char *)tablePtr, TK_CONFIG_ARGV_ONLY) != TCL_OK) {
	return TCL_ERROR;
    }
    /* Arrange for the table layout to be computed at the next idle point. */
    tablePtr->flags |= REQUEST_LAYOUT;
    EventuallyArrangeTable(tablePtr);
    return TCL_OK;
}

static void
PrintTable(tablePtr, resultPtr)
    Table *tablePtr;
    Tcl_DString *resultPtr;
{
    char string[200];

    if ((tablePtr->padLeft != TABLE_DEF_PAD) ||
	(tablePtr->padRight != TABLE_DEF_PAD)) {
	sprintf(string, " -padx {%d %d}", tablePtr->padLeft, tablePtr->padRight);
	Tcl_DStringAppend(resultPtr, string, -1);
    }
    if ((tablePtr->padTop != TABLE_DEF_PAD) ||
	(tablePtr->padBottom != TABLE_DEF_PAD)) {
	sprintf(string, " -pady {%d %d}", tablePtr->padTop, tablePtr->padBottom);
	Tcl_DStringAppend(resultPtr, string, -1);
    }
    if (!tablePtr->propagate) {
	Tcl_DStringAppend(resultPtr, " -propagate no", -1);
    }
    if ((tablePtr->reqWidth.min != LIMITS_MIN) ||
	(tablePtr->reqWidth.nom != LIMITS_NOM) ||
	(tablePtr->reqWidth.max != LIMITS_MAX)) {
	Tcl_DStringAppend(resultPtr, " -reqwidth {%s}", -1);
	Tcl_DStringAppend(resultPtr, NameOfLimits(&(tablePtr->reqWidth)), -1);
    }
    if ((tablePtr->reqHeight.min != LIMITS_MIN) ||
	(tablePtr->reqHeight.nom != LIMITS_NOM) ||
	(tablePtr->reqHeight.max != LIMITS_MAX)) {
	Tcl_DStringAppend(resultPtr, " -reqheight {%s}", -1);
	Tcl_DStringAppend(resultPtr, NameOfLimits(&(tablePtr->reqHeight)), -1);
    }
}

/*
 * ----------------------------------------------------------------------------
 *
 * DestroyPartitions --
 *
 *	Clear each of the lists managing the entries.  The entries in
 *	the lists of row and column spans are themselves lists which
 *	need to be cleared.
 *
 * ----------------------------------------------------------------------------
 */
static void
DestroyPartitions(infoPtr)
    PartitionInfo *infoPtr;
{
    if (infoPtr->list != NULL) {
	Blt_Chain *chainPtr;
	Blt_ListNode node;

	for (node = Blt_ListFirstNode(infoPtr->list); node != NULL;
	    node = Blt_ListNextNode(node)) {
	    chainPtr = (Blt_Chain *)Blt_ListGetValue(node);
	    if (chainPtr != NULL) {
		Blt_ChainDestroy(chainPtr);
	    }
	}
	Blt_ListDestroy(infoPtr->list);
    }
    if (infoPtr->chainPtr != NULL) {
	Blt_ChainLink *linkPtr;
	RowColumn *rcPtr;

	for (linkPtr = Blt_ChainFirstLink(infoPtr->chainPtr);
	    linkPtr != NULL; linkPtr = Blt_ChainNextLink(linkPtr)) {
	    rcPtr = Blt_ChainGetValue(linkPtr);
	    Blt_Free(rcPtr);
	}
	Blt_ChainDestroy(infoPtr->chainPtr);
    }
}

/*
 * ----------------------------------------------------------------------------
 *
 * DestroyTable --
 *
 *	This procedure is invoked by Tcl_EventuallyFree or Tcl_Release to
 *	clean up the Table structure at a safe time (when no-one is using
 *	it anymore).
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Everything associated with the table geometry manager is freed up.
 *
 * ----------------------------------------------------------------------------
 */
static void
DestroyTable(dataPtr)
    DestroyData dataPtr;	/* Table structure */
{
    Blt_ChainLink *linkPtr;
    Entry *entryPtr;
    Table *tablePtr = (Table *)dataPtr;

    /* Release the chain of entries. */
    for (linkPtr = Blt_ChainFirstLink(tablePtr->chainPtr); linkPtr != NULL;
	linkPtr = Blt_ChainNextLink(linkPtr)) {
	entryPtr = Blt_ChainGetValue(linkPtr);
	entryPtr->linkPtr = NULL; /* Don't disrupt this chain of entries */
	DestroyEntry(entryPtr);
    }
    Blt_ChainDestroy(tablePtr->chainPtr);

    DestroyPartitions(&(tablePtr->rowInfo));
    DestroyPartitions(&(tablePtr->columnInfo));
    Blt_DeleteHashTable(&(tablePtr->entryTable));
    if (tablePtr->hashPtr != NULL) {
	Blt_DeleteHashEntry(tablePtr->tablePtr, tablePtr->hashPtr);
    }
    Blt_Free(tablePtr);
}

/*
 * ----------------------------------------------------------------------------
 *
 * BinEntry --
 *
 *	Adds the entry to the lists of both row and column spans.  The
 *	layout of the table is done in order of partition spans, from
 *	shorted to longest.  The widgets spanning a particular number of
 *	partitions are stored in a linked list.  Each list is in turn,
 *	contained within a master list.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The entry is added to both the lists of row and columns spans.
 *	This will effect the layout of the widgets.
 *
 * ----------------------------------------------------------------------------
 */
static void
BinEntry(tablePtr, entryPtr)
    Table *tablePtr;
    Entry *entryPtr;
{
    Blt_ListNode node;
    Blt_List list;
    Blt_Chain *chainPtr;
    int key;

    /*
     * Remove the entry from both row and column lists.  It will be
     * re-inserted into the table at the new position.
     */
    if (entryPtr->column.linkPtr != NULL) {
	Blt_ChainUnlinkLink(entryPtr->column.chainPtr,
	    entryPtr->column.linkPtr);
    }
    if (entryPtr->row.linkPtr != NULL) {
	Blt_ChainUnlinkLink(entryPtr->row.chainPtr, entryPtr->row.linkPtr);
    }
    list = tablePtr->rowInfo.list;
    key = 0;			/* Initialize key to bogus span */
    for (node = Blt_ListFirstNode(list); node != NULL;
	node = Blt_ListNextNode(node)) {
	key = (int)Blt_ListGetKey(node);
	if (entryPtr->row.span <= key) {
	    break;
	}
    }
    if (key != entryPtr->row.span) {
	Blt_ListNode newNode;

	/*
	 * Create a new list (bucket) to hold entries of that size
	 * span and and link it into the list of buckets.
	 */
	newNode = Blt_ListCreateNode(list, (char *)entryPtr->row.span);
	Blt_ListSetValue(newNode, (char *)Blt_ChainCreate());
	Blt_ListLinkBefore(list, newNode, node);
	node = newNode;
    }
    chainPtr = (Blt_Chain *) Blt_ListGetValue(node);
    if (entryPtr->row.linkPtr == NULL) {
	entryPtr->row.linkPtr = Blt_ChainAppend(chainPtr, entryPtr);
    } else {
	Blt_ChainLinkBefore(chainPtr, entryPtr->row.linkPtr, NULL);
    }
    entryPtr->row.chainPtr = chainPtr;

    list = tablePtr->columnInfo.list;
    key = 0;
    for (node = Blt_ListFirstNode(list); node != NULL;
	node = Blt_ListNextNode(node)) {
	key = (int)Blt_ListGetKey(node);
	if (entryPtr->column.span <= key) {
	    break;
	}
    }
    if (key != entryPtr->column.span) {
	Blt_ListNode newNode;

	/*
	 * Create a new list (bucket) to hold entries of that size
	 * span and and link it into the list of buckets.
	 */
	newNode = Blt_ListCreateNode(list, (char *)entryPtr->column.span);
	Blt_ListSetValue(newNode, (char *)Blt_ChainCreate());
	Blt_ListLinkBefore(list, newNode, node);
	node = newNode;
    }
    chainPtr = (Blt_Chain *) Blt_ListGetValue(node);

    /* Add the new entry to the span bucket */
    if (entryPtr->column.linkPtr == NULL) {
	entryPtr->column.linkPtr =
	    Blt_ChainAppend(chainPtr, entryPtr);
    } else {
	Blt_ChainLinkBefore(chainPtr, entryPtr->column.linkPtr, NULL);
    }
    entryPtr->column.chainPtr = chainPtr;
}

/*
 * ----------------------------------------------------------------------------
 *
 * ParseIndex --
 *
 *	Parse the entry index string and return the row and column
 *	numbers in their respective parameters.  The format of a table
 *	entry index is row,column where row is the row number and
 *	column is the column number.  Rows and columns are numbered
 *	starting from zero.
 *
 * Results:
 *	Returns a standard Tcl result.  If TCL_OK is returned, the row
 *	and column numbers are returned via rowPtr and columnPtr
 *	respectively.
 *
 * ----------------------------------------------------------------------------
 */
static int
ParseIndex(interp, string, rowPtr, columnPtr)
    Tcl_Interp *interp;
    char *string;
    int *rowPtr, *columnPtr;
{
    char *comma;
    long row, column;
    int result;

    comma = strchr(string, ',');
    if (comma == NULL) {
	Tcl_AppendResult(interp, "bad index \"", string,
	    "\": should be \"row,column\"", (char *)NULL);
	return TCL_ERROR;

    }
    *comma = '\0';
    result = ((Tcl_ExprLong(interp, string, &row) != TCL_OK) ||
	(Tcl_ExprLong(interp, comma + 1, &column) != TCL_OK));
    *comma = ',';		/* Repair the argument */
    if (result) {
	return TCL_ERROR;
    }
    if ((row < 0) || (row > (long)USHRT_MAX)) {
	Tcl_AppendResult(interp, "bad index \"", string,
	    "\": row is out of range", (char *)NULL);
	return TCL_ERROR;

    }
    if ((column < 0) || (column > (long)USHRT_MAX)) {
	Tcl_AppendResult(interp, "bad index \"", string,
	    "\": column is out of range", (char *)NULL);
	return TCL_ERROR;
    }
    *rowPtr = (int)row;
    *columnPtr = (int)column;
    return TCL_OK;
}

/*
 * ----------------------------------------------------------------------------
 *
 * ManageEntry --
 *
 *	Inserts the given widget into the table at a given row and
 *	column position.  The widget can already be managed by this or
 *	another table.  The widget will be simply moved to the new
 *	location in this table.
 *
 *	The new widget is inserted into both a hash table (this is
 *	used to locate the information associated with the widget) and
 *	a list (used to indicate relative ordering of widgets).
 *
 * Results:
 *	Returns a standard Tcl result.  If an error occurred, TCL_ERROR is
 *	returned and an error message is left in interp->result.
 *
 * Side Effects:
 *	The table is re-computed and arranged at the next idle point.
 *
 * ---------------------------------------------------------------------------- */
static int
ManageEntry(interp, tablePtr, tkwin, row, column, argc, argv)
    Tcl_Interp *interp;
    Table *tablePtr;
    Tk_Window tkwin;
    int row, column;
    int argc;
    char **argv;
{
    Entry *entryPtr;
    int result = TCL_OK;

    entryPtr = FindEntry(tablePtr, tkwin);
    if ((entryPtr != NULL) && (entryPtr->tablePtr != tablePtr)) {
	/* The entry for the widget already exists. If it's
	 * managed by another table, delete it.  */
	DestroyEntry(entryPtr);
	entryPtr = NULL;
    }
    if (entryPtr == NULL) {
	entryPtr = CreateEntry(tablePtr, tkwin);
	if (entryPtr == NULL) {
	    return TCL_ERROR;
	}
    }
    if (argc > 0) {
	result = Tk_ConfigureWidget(tablePtr->interp, entryPtr->tkwin,
	    entryConfigSpecs, argc, argv, (char *)entryPtr,
	    TK_CONFIG_ARGV_ONLY);
    }
    if ((entryPtr->column.span < 1) || (entryPtr->row.span < 1)) {
	Tcl_AppendResult(tablePtr->interp, "bad span specified for \"",
	    Tk_PathName(tkwin), "\"", (char *)NULL);
	DestroyEntry(entryPtr);
	return TCL_ERROR;
    }
    entryPtr->column.rcPtr = InitSpan(&(tablePtr->columnInfo), column,
	entryPtr->column.span);
    entryPtr->row.rcPtr = InitSpan(&(tablePtr->rowInfo), row,
	entryPtr->row.span);
    /*
     * Insert the entry into both the row and column layout lists
     */
    BinEntry(tablePtr, entryPtr);

    return result;
}

/*
 * ----------------------------------------------------------------------------
 *
 * BuildTable --
 *
 *	Processes an argv/argc list of table entries to add and
 *	configure new widgets into the table.  A table entry consists
 *	of the widget path name, table index, and optional
 *	configuration options.  The first argument in the argv list is
 *	the name of the table.  If no table exists for the given
 *	widget, a new one is created.
 *
 * Results:
 *	Returns a standard Tcl result.  If an error occurred,
 *	TCL_ERROR is returned and an error message is left in
 *	interp->result.
 *
 * Side Effects:
 *	Memory is allocated, a new table is possibly created, etc.
 *	The table is re-computed and arranged at the next idle point.
 *
 * ----------------------------------------------------------------------------
 */
static int
BuildTable(tablePtr, interp, argc, argv)
    Table *tablePtr;		/* Table to manage new widgets */
    Tcl_Interp *interp;		/* Interpreter to report errors back to */
    int argc;			/*  */
    char **argv;		/* List of widgets, indices, and options */
{
    Tk_Window tkwin;
    int row, column;
    int nextRow, nextColumn;
    register int i;

    /* Process any options specific to the table */
    for (i = 2; i < argc; i += 2) {
	if (argv[i][0] != '-') {
	    break;
	}
    }
    if (i > argc) {
	i = argc;
    }
    if (i > 2) {
	if (ConfigureTable(tablePtr, interp, i - 2, argv + 2) != TCL_OK) {
	    return TCL_ERROR;
	}
    }
    nextRow = tablePtr->nRows;
    nextColumn = 0;
    argc -= i, argv += i;
    while (argc > 0) {
	/*
	 * Allow the name of the widget and row/column index to be
	 * specified in any order.
	 */
	if (argv[0][0] == '.') {
	    tkwin = Tk_NameToWindow(interp, argv[0], tablePtr->tkwin);
	    if (tkwin == NULL) {
		return TCL_ERROR;
	    }
	    if ((argc == 1) || (argv[1][0] == '-')) {
		/* No row,column index, use defaults instead */
		row = nextRow, column = nextColumn;
		argc--, argv++;
	    } else {
		if (ParseIndex(interp, argv[1], &row, &column) != TCL_OK) {
		    return TCL_ERROR;	/* Invalid row,column index */
		}
		/* Skip over the widget pathname and table index. */
		argc -= 2, argv += 2;
	    }
	} else {
	    if (ParseIndex(interp, argv[0], &row, &column) != TCL_OK) {
		return TCL_ERROR;
	    }
	    if (argc == 1) {
		Tcl_AppendResult(interp, "missing widget pathname after \"",
			 argv[0], "\"", (char *)NULL);
		return TCL_ERROR;
	    }
	    tkwin = Tk_NameToWindow(interp, argv[1], tablePtr->tkwin);
	    if (tkwin == NULL) {
		return TCL_ERROR;
	    }
	    /* Skip over the widget pathname and table index. */
	    argc -= 2, argv += 2;
	}

	/* Find the end of the widget's option-value pairs */
	for (i = 0; i < argc; i += 2) {
	    if (argv[i][0] != '-') {
		break;
	    }
	}
	if (i > argc) {
	    i = argc;
	}
	if (ManageEntry(interp, tablePtr, tkwin, row,
		column, i, argv) != TCL_OK) {
	    return TCL_ERROR;
	}
	nextColumn = column + 1;
	argc -= i, argv += i;
    }
    /* Arrange for the new table layout to be calculated. */
    tablePtr->flags |= REQUEST_LAYOUT;
    EventuallyArrangeTable(tablePtr);

    Tcl_SetResult(interp, Tk_PathName(tablePtr->tkwin), TCL_VOLATILE);
    return TCL_OK;
}

/*
 * ----------------------------------------------------------------------------
 *
 * ParseItem --
 *
 *	Parses a string representing an item in the table.  An item
 *	may be one of the following:
 *		Rn	- Row index, where n is the index of row
 *		Cn	- Column index, where n is the index of column
 *		r,c	- Cell index, where r is the row index and c
 *			  is the column index.
 *
 * Results:
 *	Returns a standard Tcl result.  If no error occurred, TCL_OK
 *	is returned.  *RowPtr* will return the row index.  *ColumnPtr*
 *	will return the column index.  If the row or column index is
 *	not applicable, -1 is returned via *rowPtr* or *columnPtr*.
 *
 * ----------------------------------------------------------------------------
 */
static int
ParseItem(tablePtr, string, rowPtr, columnPtr)
    Table *tablePtr;
    char *string;
    int *rowPtr, *columnPtr;
{
    char c;
    long partNum;

    c = tolower(string[0]);
    *rowPtr = *columnPtr = -1;
    if (c == 'r') {
	if (Tcl_ExprLong(tablePtr->interp, string + 1, &partNum) != TCL_OK) {
	    return TCL_ERROR;
	}
	if ((partNum < 0) || (partNum >= tablePtr->nRows)) {
	    Tcl_AppendResult(tablePtr->interp, "row index \"", string,
		"\" is out of range", (char *)NULL);
	    return TCL_ERROR;
	}
	*rowPtr = (int)partNum;
    } else if (c == 'c') {
	if (Tcl_ExprLong(tablePtr->interp, string + 1, &partNum) != TCL_OK) {
	    return TCL_ERROR;
	}
	if ((partNum < 0) || (partNum >= tablePtr->nColumns)) {
	    Tcl_AppendResult(tablePtr->interp, "column index \"", string,
		"\" is out of range", (char *)NULL);
	    return TCL_ERROR;
	}
	*columnPtr = (int)partNum;
    } else {
	if (ParseIndex(tablePtr->interp, string,
		rowPtr, columnPtr) != TCL_OK) {
	    return TCL_ERROR;	/* Invalid row,column index */
	}
	if ((*rowPtr < 0) || (*rowPtr >= tablePtr->nRows) ||
	    (*columnPtr < 0) || (*columnPtr >= tablePtr->nColumns)) {
	    Tcl_AppendResult(tablePtr->interp, "index \"", string,
		"\" is out of range", (char *)NULL);
	    return TCL_ERROR;
	}
    }
    return TCL_OK;
}

/*
 * ----------------------------------------------------------------------------
 *
 * TranslateAnchor --
 *
 * 	Translate the coordinates of a given bounding box based upon
 * 	the anchor specified.  The anchor indicates where the given xy
 * 	position is in relation to the bounding box.
 *
 *  		nw --- n --- ne
 *  		|            |     x,y ---+
 *  		w   center   e      |     |
 *  		|            |      +-----+
 *  		sw --- s --- se
 *
 * Results:
 *	The translated coordinates of the bounding box are returned.
 *
 * ----------------------------------------------------------------------------
 */
static void
TranslateAnchor(dx, dy, anchor, xPtr, yPtr)
    int dx, dy;			/* Difference between outer and inner regions
				 */
    Tk_Anchor anchor;		/* Direction of the anchor */
    int *xPtr, *yPtr;
{
    int x, y;

    x = y = 0;
    switch (anchor) {
    case TK_ANCHOR_NW:		/* Upper left corner */
	break;
    case TK_ANCHOR_W:		/* Left center */
	y = (dy / 2);
	break;
    case TK_ANCHOR_SW:		/* Lower left corner */
	y = dy;
	break;
    case TK_ANCHOR_N:		/* Top center */
	x = (dx / 2);
	break;
    case TK_ANCHOR_CENTER:	/* Centered */
	x = (dx / 2);
	y = (dy / 2);
	break;
    case TK_ANCHOR_S:		/* Bottom center */
	x = (dx / 2);
	y = dy;
	break;
    case TK_ANCHOR_NE:		/* Upper right corner */
	x = dx;
	break;
    case TK_ANCHOR_E:		/* Right center */
	x = dx;
	y = (dy / 2);
	break;
    case TK_ANCHOR_SE:		/* Lower right corner */
	x = dx;
	y = dy;
	break;
    }
    *xPtr = (*xPtr) + x;
    *yPtr = (*yPtr) + y;
}

/*
 * ----------------------------------------------------------------------------
 *
 * GetReqWidth --
 *
 *	Returns the width requested by the widget starting in the
 *	given entry.  The requested space also includes any internal
 *	padding which has been designated for this widget.
 *
 *	The requested width of the widget is always bounded by the limits
 *	set in entryPtr->reqWidth.
 *
 * Results:
 *	Returns the requested width of the widget.
 *
 * ----------------------------------------------------------------------------
 */
static int
GetReqWidth(entryPtr)
    Entry *entryPtr;
{
    int width;

    width = Tk_ReqWidth(entryPtr->tkwin) + (2 * entryPtr->ipadX);
    width = GetBoundedWidth(width, &(entryPtr->reqWidth));
    return width;
}

/*
 * ----------------------------------------------------------------------------
 *
 * GetReqHeight --
 *
 *	Returns the height requested by the widget starting in the
 *	given entry.  The requested space also includes any internal
 *	padding which has been designated for this widget.
 *
 *	The requested height of the widget is always bounded by the
 *	limits set in entryPtr->reqHeight.
 *
 * Results:
 *	Returns the requested height of the widget.
 *
 * ----------------------------------------------------------------------------
 */
static int
GetReqHeight(entryPtr)
    Entry *entryPtr;
{
    int height;

    height = Tk_ReqHeight(entryPtr->tkwin) + (2 * entryPtr->ipadY);
    height = GetBoundedHeight(height, &(entryPtr->reqHeight));
    return height;
}

/*
 * ----------------------------------------------------------------------------
 *
 * GetTotalSpan --
 *
 *	Sums the row/column space requirements for the entire table.
 *
 * Results:
 *	Returns the space currently used in the span of partitions.
 *
 * ----------------------------------------------------------------------------
 */
static int
GetTotalSpan(infoPtr)
    PartitionInfo *infoPtr;
{
    register int spaceUsed;
    Blt_ChainLink *linkPtr;
    RowColumn *rcPtr;		/* Start of partitions */

    spaceUsed = 0;
    for (linkPtr = Blt_ChainFirstLink(infoPtr->chainPtr); linkPtr != NULL;
	linkPtr = Blt_ChainNextLink(linkPtr)) {
	rcPtr = Blt_ChainGetValue(linkPtr);
	spaceUsed += rcPtr->size;
    }
    return spaceUsed;
}

/*
 * ----------------------------------------------------------------------------
 *
 * GetSpan --
 *
 *	Determines the space used by rows/columns for an entry.
 *
 * Results:
 *	Returns the space currently used in the span of partitions.
 *
 * ----------------------------------------------------------------------------
 */
static int
GetSpan(infoPtr, entryPtr)
    PartitionInfo *infoPtr;
    Entry *entryPtr;
{
    RowColumn *startPtr;
    register int spaceUsed;
    int count;
    Blt_ChainLink *linkPtr;
    RowColumn *rcPtr;		/* Start of partitions */
    int span;			/* Number of partitions spanned */

    if (infoPtr->type == rowUid) {
	rcPtr = entryPtr->row.rcPtr;
	span = entryPtr->row.span;
    } else {
	rcPtr = entryPtr->column.rcPtr;
	span = entryPtr->column.span;
    }

    count = spaceUsed = 0;
    linkPtr = rcPtr->linkPtr;
    startPtr = Blt_ChainGetValue(linkPtr);
    for ( /*empty*/ ; (linkPtr != NULL) && (count < span);
	linkPtr = Blt_ChainNextLink(linkPtr)) {
	rcPtr = Blt_ChainGetValue(linkPtr);
	spaceUsed += rcPtr->size;
	count++;
    }
    /*
     * Subtract off the padding on either side of the span, since the
     * widget can't grow into it.
     */
    spaceUsed -= (startPtr->pad.side1 + rcPtr->pad.side2 + infoPtr->ePad);
    return spaceUsed;
}

/*
 * ----------------------------------------------------------------------------
 *
 * GrowSpan --
 *
 *	Expand the span by the amount of the extra space needed.  This
 *	procedure is used in LayoutPartitions to grow the partitions
 *	to their minimum nominal size, starting from a zero width and
 *	height space.
 *
 *	This looks more complicated than it really is.  The idea is to
 *	make the size of the partitions correspond to the smallest
 *	entry spans.  For example, if widget A is in column 1 and
 *	widget B spans both columns 0 and 1, any extra space needed to
 *	fit widget B should come from column 0.
 *
 *	On the first pass we try to add space to partitions which have
 *	not been touched yet (i.e. have no nominal size).  Since the
 *	row and column lists are sorted in ascending order of the
 *	number of rows or columns spanned, the space is distributed
 *	amongst the smallest spans first.
 *
 *	The second pass handles the case of widgets which have the
 *	same span.  For example, if A and B, which span the same
 *	number of partitions are the only widgets to span column 1,
 *	column 1 would grow to contain the bigger of the two slices of
 *	space.
 *
 *	If there is still extra space after the first two passes, this
 *	means that there were no partitions of with no widget spans or
 *	the same order span that could be expanded. The third pass
 *	will try to remedy this by parcelling out the left over space
 *	evenly among the rest of the partitions.
 *
 *	On each pass, we have to keep iterating over the span, evenly
 *	doling out slices of extra space, because we may hit partition
 *	limits as space is donated.  In addition, if there are left
 *	over pixels because of round-off, this will distribute them as
 *	evenly as possible.  For the worst case, it will take *span*
 *	passes to expand the span.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 * 	The partitions in the span may be expanded.
 *
 * ----------------------------------------------------------------------------
 */
static void
GrowSpan(infoPtr, entryPtr, growth)
    PartitionInfo *infoPtr;
    Entry *entryPtr;
    int growth;			/* The amount of extra space needed to
				 * grow the span. */
{
    register RowColumn *rcPtr;
    Blt_ChainLink *linkPtr;
    int spaceLeft, ration;
    int nOpen;			/* # of partitions with space available */
    register int n;
    RowColumn *startPtr;	/* Starting (column/row) partition  */
    int span;			/* Number of partitions in the span */

    if (infoPtr->type == rowUid) {
	startPtr = entryPtr->row.rcPtr;
	span = entryPtr->row.span;
    } else {
	startPtr = entryPtr->column.rcPtr;
	span = entryPtr->column.span;
    }

    /*
     * ------------------------------------------------------------------------
     *
     * Pass 1: First add space to rows/columns that haven't determined
     *	       their nominal sizes yet.
     *
     * ------------------------------------------------------------------------
     */

    nOpen = 0;
    /* Find out how many partitions have no size yet */
    linkPtr = startPtr->linkPtr;
    for (n = 0; n < span; n++) {
	rcPtr = Blt_ChainGetValue(linkPtr);
	if ((rcPtr->nomSize == LIMITS_NOM) && (rcPtr->maxSize > rcPtr->size)) {
	    nOpen++;
	}
	linkPtr = Blt_ChainNextLink(linkPtr);
    }

    while ((nOpen > 0) && (growth > 0)) {
	ration = growth / nOpen;
	if (ration == 0) {
	    ration = 1;
	}
	linkPtr = startPtr->linkPtr;
	for (n = 0; (n < span) && (growth > 0); n++) {
	    rcPtr = Blt_ChainGetValue(linkPtr);
	    spaceLeft = rcPtr->maxSize - rcPtr->size;
	    if ((rcPtr->nomSize == LIMITS_NOM) && (spaceLeft > 0)) {
		if (ration < spaceLeft) {
		    growth -= ration;
		    rcPtr->size += ration;
		} else {
		    growth -= spaceLeft;
		    rcPtr->size += spaceLeft;
		    nOpen--;
		}
		rcPtr->minSpan = span;
		rcPtr->control = entryPtr;
	    }
	    linkPtr = Blt_ChainNextLink(linkPtr);
	}
    }

    /*
     * ------------------------------------------------------------------------
     *
     * Pass 2: Add space to partitions which have the same minimum span
     *
     * ------------------------------------------------------------------------
     */

    nOpen = 0;
    linkPtr = startPtr->linkPtr;
    for (n = 0; n < span; n++) {
	rcPtr = Blt_ChainGetValue(linkPtr);
	if ((rcPtr->minSpan == span) && (rcPtr->maxSize > rcPtr->size)) {
	    nOpen++;
	}
	linkPtr = Blt_ChainNextLink(linkPtr);
    }
    while ((nOpen > 0) && (growth > 0)) {
	ration = growth / nOpen;
	if (ration == 0) {
	    ration = 1;
	}
	linkPtr = startPtr->linkPtr;
	for (n = 0; (n < span) && (growth > 0); n++) {
	    rcPtr = Blt_ChainGetValue(linkPtr);
	    spaceLeft = rcPtr->maxSize - rcPtr->size;
	    if ((rcPtr->minSpan == span) && (spaceLeft > 0)) {
		if (ration < spaceLeft) {
		    growth -= ration;
		    rcPtr->size += ration;
		} else {
		    growth -= spaceLeft;
		    rcPtr->size += spaceLeft;
		    nOpen--;
		}
		rcPtr->control = entryPtr;
	    }
	    linkPtr = Blt_ChainNextLink(linkPtr);
	}
    }

    /*
     * ------------------------------------------------------------------------
     *
     * Pass 3: Try to expand all the partitions with space still available
     *
     * ------------------------------------------------------------------------
     */

    /* Find out how many partitions still have space available */
    nOpen = 0;
    linkPtr = startPtr->linkPtr;
    for (n = 0; n < span; n++) {
	rcPtr = Blt_ChainGetValue(linkPtr);
	if ((rcPtr->resize & RESIZE_EXPAND) && (rcPtr->maxSize > rcPtr->size)) {
	    nOpen++;
	}
	/* Set the nominal size of the row/column. */
	rcPtr->nomSize = rcPtr->size;
	linkPtr = Blt_ChainNextLink(linkPtr);
    }
    while ((nOpen > 0) && (growth > 0)) {
	ration = growth / nOpen;
	if (ration == 0) {
	    ration = 1;
	}
	linkPtr = startPtr->linkPtr;
	for (n = 0; (n < span) && (growth > 0); n++) {
	    rcPtr = Blt_ChainGetValue(linkPtr);
	    linkPtr = Blt_ChainNextLink(linkPtr);
	    if (!(rcPtr->resize & RESIZE_EXPAND)) {
		continue;
	    }
	    spaceLeft = rcPtr->maxSize - rcPtr->size;
	    if (spaceLeft > 0) {
		if (ration < spaceLeft) {
		    growth -= ration;
		    rcPtr->size += ration;
		} else {
		    growth -= spaceLeft;
		    rcPtr->size += spaceLeft;
		    nOpen--;
		}
		rcPtr->nomSize = rcPtr->size;
		rcPtr->control = entryPtr;
	    }
	}
    }
}

/*
 * ----------------------------------------------------------------------------
 *
 * AdjustPartitions --
 *
 *	Adjust the span by the amount of the extra space needed.  If
 *	the amount (adjustSpace) is negative, shrink the span,
 *	otherwise expand it.  Size constraints on the partitions may
 *	prevent any or all of the spacing adjustments.
 *
 *	This is very much like the GrowSpan procedure, but in this
 *	case we are shrinking or expanding all the (row or column)
 *	partitions. It uses a two pass approach, first giving space to
 *	partitions which not are smaller/larger than their nominal
 *	sizes. This is because constraints on the partitions may cause
 *	resizing to be non-linear.
 *
 *	If there is still extra space, this means that all partitions
 *	are at least to their nominal sizes.  The second pass will try
 *	to add/remove the left over space evenly among all the
 *	partitions which still have space available.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	The size of the partitions in the span may be increased or
 *	decreased.
 *
 * ----------------------------------------------------------------------------
 */
static void
AdjustPartitions(infoPtr, adjustment)
    PartitionInfo *infoPtr;	/* Array of (column/row) partitions  */
    int adjustment;		/* The amount of extra space to grow or shrink
				 * the span. If negative, it represents the
				 * amount of space to remove */
{
    register RowColumn *rcPtr;
    int ration;			/* Amount of space to ration to each
				 * row/column. */
    int delta;			/* Amount of space needed */
    int spaceLeft;		/* Amount of space still available */
    int size;			/* Amount of space requested for a particular
				 * row/column. */
    int nOpen;			/* Number of rows/columns that still can
				 * be adjusted. */
    Blt_Chain *chainPtr;
    Blt_ChainLink *linkPtr;
    double totalWeight;

    chainPtr = infoPtr->chainPtr;

    /*
     * ------------------------------------------------------------------------
     *
     * Pass 1: First adjust the size of rows/columns that still haven't
     *	      reached their nominal size.
     *
     * ------------------------------------------------------------------------
     */
    delta = adjustment;

    nOpen = 0;
    totalWeight = 0.0;
    for (linkPtr = Blt_ChainFirstLink(chainPtr); linkPtr != NULL;
	linkPtr = Blt_ChainNextLink(linkPtr)) {
	rcPtr = Blt_ChainGetValue(linkPtr);
	if (rcPtr->weight > 0.0) {
	    if (delta < 0) {
		spaceLeft = rcPtr->size - rcPtr->nomSize;
	    } else {
		spaceLeft = rcPtr->nomSize - rcPtr->size;
	    }
	    if (spaceLeft > 0) {
		nOpen++;
		totalWeight += rcPtr->weight;
	    }
	}
    }

    while ((nOpen > 0) && (totalWeight > 0.0) && (delta != 0)) {
	ration = (int)(delta / totalWeight);
	if (ration == 0) {
	    ration = (delta > 0) ? 1 : -1;
	}
	for (linkPtr = Blt_ChainFirstLink(chainPtr);
	    (linkPtr != NULL) && (delta != 0);
	    linkPtr = Blt_ChainNextLink(linkPtr)) {
	    rcPtr = Blt_ChainGetValue(linkPtr);
	    if (rcPtr->weight > 0.0) {
		spaceLeft = rcPtr->nomSize - rcPtr->size;
		if (((delta > 0) && (spaceLeft > 0)) ||
		    ((delta < 0) && (spaceLeft < 0))) {
		    size = (int)(ration * rcPtr->weight);
		    if (size > delta) {
			size = delta;
		    }
		    if (ABS(size) < ABS(spaceLeft)) {
			delta -= size;
			rcPtr->size += size;
		    } else {
			delta -= spaceLeft;
			rcPtr->size += spaceLeft;
			nOpen--;
			totalWeight -= rcPtr->weight;
		    }
		}
	    }
	}
    }
    /*
     * ------------------------------------------------------------------------
     *
     * Pass 2: Adjust the partitions with space still available
     *
     * ------------------------------------------------------------------------
     */

    nOpen = 0;
    totalWeight = 0.0;
    for (linkPtr = Blt_ChainFirstLink(chainPtr); linkPtr != NULL;
	linkPtr = Blt_ChainNextLink(linkPtr)) {
	rcPtr = Blt_ChainGetValue(linkPtr);
	if (rcPtr->weight > 0.0) {
	    if (delta > 0) {
		spaceLeft = rcPtr->maxSize - rcPtr->size;
	    } else {
		spaceLeft = rcPtr->size - rcPtr->minSize;
	    }
	    if (spaceLeft > 0) {
		nOpen++;
		totalWeight += rcPtr->weight;
	    }
	}
    }
    while ((nOpen > 0) && (totalWeight > 0.0) && (delta != 0)) {
	ration = (int)(delta / totalWeight);
	if (ration == 0) {
	    ration = (delta > 0) ? 1 : -1;
	}
	linkPtr = Blt_ChainFirstLink(chainPtr);
	for ( /*empty*/ ; (linkPtr != NULL) && (delta != 0);
	    linkPtr = Blt_ChainNextLink(linkPtr)) {
	    rcPtr = Blt_ChainGetValue(linkPtr);
	    if (rcPtr->weight > 0.0) {
		if (delta > 0) {
		    spaceLeft = rcPtr->maxSize - rcPtr->size;
		} else {
		    spaceLeft = rcPtr->minSize - rcPtr->size;
		}
		if (((delta > 0) && (spaceLeft > 0)) ||
		    ((delta < 0) && (spaceLeft < 0))) {
		    size = (int)(ration * rcPtr->weight);
		    if (size > delta) {
			size = delta;
		    }
		    if (ABS(size) < ABS(spaceLeft)) {
			delta -= size;
			rcPtr->size += size;
		    } else {
			delta -= spaceLeft;
			rcPtr->size += spaceLeft;
			nOpen--;
			totalWeight -= rcPtr->weight;
		    }
		}
	    }
	}
    }
}

/*
 * ----------------------------------------------------------------------------
 *
 * ResetPartitions --
 *
 *	Sets/resets the size of each row and column partition to the
 *	minimum limit of the partition (this is usually zero). This
 *	routine gets called when new widgets are added, deleted, or
 *	resized.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 * 	The size of each partition is re-initialized to its minimum
 * 	size.
 *
 * ----------------------------------------------------------------------------
 */
static void
ResetPartitions(tablePtr, infoPtr, limitsProc)
    Table *tablePtr;
    PartitionInfo *infoPtr;
    LimitsProc *limitsProc;
{
    register RowColumn *rcPtr;
    register Blt_ChainLink *linkPtr;
    int pad, size;

    for (linkPtr = Blt_ChainFirstLink(infoPtr->chainPtr); linkPtr != NULL;
	linkPtr = Blt_ChainNextLink(linkPtr)) {
	rcPtr = Blt_ChainGetValue(linkPtr);

	/*
	 * The constraint procedure below also has the desired
	 * side-effect of setting the minimum, maximum, and nominal
	 * values to the requested size of its associated widget (if
	 * one exists).
	 */
	size = (*limitsProc) (0, &(rcPtr->reqSize));

	pad = PADDING(rcPtr->pad) + infoPtr->ePad;
	if (rcPtr->reqSize.flags & LIMITS_SET_NOM) {

	    /*
	     * This could be done more cleanly.  We want to ensure
	     * that the requested nominal size is not overridden when
	     * determining the normal sizes.  So temporarily fix min
	     * and max to the nominal size and reset them back later.
	     */
	    rcPtr->minSize = rcPtr->maxSize = rcPtr->size =
		rcPtr->nomSize = size + pad;

	} else {
	    /* The range defaults to 0..MAXINT */
	    rcPtr->minSize = rcPtr->reqSize.min + pad;
	    rcPtr->maxSize = rcPtr->reqSize.max + pad;
	    rcPtr->nomSize = LIMITS_NOM;
	    rcPtr->size = pad;
	}
	rcPtr->minSpan = 0;
	rcPtr->control = NULL;
	rcPtr->count = 0;
    }
}

/*
 * ----------------------------------------------------------------------------
 *
 * SetNominalSizes
 *
 *	Sets the normal sizes for each partition.  The partition size
 *	is the requested widget size plus an amount of padding.  In
 *	addition, adjust the min/max bounds of the partition depending
 *	upon the resize flags (whether the partition can be expanded
 *	or shrunk from its normal size).
 *
 * Results:
 *	Returns the total space needed for the all the partitions.
 *
 * Side Effects:
 *	The nominal size of each partition is set.  This is later used
 *	to determine how to shrink or grow the table if the container
 *	can't be resized to accommodate the exact size requirements
 *	of all the partitions.
 *
 * ----------------------------------------------------------------------------
 */
static int
SetNominalSizes(tablePtr, infoPtr)
    Table *tablePtr;
    PartitionInfo *infoPtr;
{
    register RowColumn *rcPtr;
    Blt_ChainLink *linkPtr;
    int pad, size, total;

    total = 0;
    for (linkPtr = Blt_ChainFirstLink(infoPtr->chainPtr); linkPtr != NULL;
	linkPtr = Blt_ChainNextLink(linkPtr)) {
	rcPtr = Blt_ChainGetValue(linkPtr);
	pad = PADDING(rcPtr->pad) + infoPtr->ePad;

	/*
	 * Restore the real bounds after temporarily setting nominal
	 * size.  These values may have been set in ResetPartitions to
	 * restrict the size of the paritition to the requested range.
	 */

	rcPtr->minSize = rcPtr->reqSize.min + pad;
	rcPtr->maxSize = rcPtr->reqSize.max + pad;

	size = rcPtr->size;
	if (size > rcPtr->maxSize) {
	    size = rcPtr->maxSize;
	} else if (size < rcPtr->minSize) {
	    size = rcPtr->minSize;
	}
	if ((infoPtr->ePad > 0) && (size < tablePtr->editPtr->minSize)) {
	    size = tablePtr->editPtr->minSize;
	}
	rcPtr->nomSize = rcPtr->size = size;

	/*
	 * If a partition can't be resized (to either expand or
	 * shrink), hold its respective limit at its normal size.
	 */
	if (!(rcPtr->resize & RESIZE_EXPAND)) {
	    rcPtr->maxSize = rcPtr->nomSize;
	}
	if (!(rcPtr->resize & RESIZE_SHRINK)) {
	    rcPtr->minSize = rcPtr->nomSize;
	}
	if (rcPtr->control == NULL) {
	    /* If a row/column contains no entries, then its size
	     * should be locked. */
	    if (rcPtr->resize & RESIZE_VIRGIN) {
		rcPtr->maxSize = rcPtr->minSize = size;
	    } else {
		if (!(rcPtr->resize & RESIZE_EXPAND)) {
		    rcPtr->maxSize = size;
		}
		if (!(rcPtr->resize & RESIZE_SHRINK)) {
		    rcPtr->minSize = size;
		}
	    }
	    rcPtr->nomSize = size;
	}
	total += rcPtr->nomSize;
    }
    return total;
}

/*
 * ----------------------------------------------------------------------------
 *
 * LockPartitions
 *
 *	Sets the maximum size of a row or column, if the partition
 *	has a widget that controls it.
 *
 * Results:
 *	None.
 *
 * ----------------------------------------------------------------------------
 */
static void
LockPartitions(infoPtr)
    PartitionInfo *infoPtr;
{
    register RowColumn *rcPtr;
    Blt_ChainLink *linkPtr;

    for (linkPtr = Blt_ChainFirstLink(infoPtr->chainPtr); linkPtr != NULL;
	linkPtr = Blt_ChainNextLink(linkPtr)) {
	rcPtr = Blt_ChainGetValue(linkPtr);
	if (rcPtr->control != NULL) {
	    /* Partition is controlled by this widget */
	    rcPtr->maxSize = rcPtr->size;
	}
    }
}

/*
 * ----------------------------------------------------------------------------
 *
 * LayoutPartitions --
 *
 *	Calculates the normal space requirements for both the row and
 *	column partitions.  Each widget is added in order of the
 *	number of rows or columns spanned, which defines the space
 *	needed among in the partitions spanned.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *
 * 	The sum of normal sizes set here will be used as the normal size
 * 	for the container widget.
 *
 * ----------------------------------------------------------------------------
 */
static void
LayoutPartitions(tablePtr)
    Table *tablePtr;
{
    register Blt_ListNode node;
    Blt_Chain *chainPtr;
    Blt_ChainLink *linkPtr;
    register Entry *entryPtr;
    int needed, used, total;
    PartitionInfo *infoPtr;

    infoPtr = &(tablePtr->columnInfo);

    ResetPartitions(tablePtr, infoPtr, GetBoundedWidth);

    for (node = Blt_ListFirstNode(infoPtr->list); node != NULL;
	node = Blt_ListNextNode(node)) {
	chainPtr = (Blt_Chain *) Blt_ListGetValue(node);

	for (linkPtr = Blt_ChainFirstLink(chainPtr); linkPtr != NULL;
	    linkPtr = Blt_ChainNextLink(linkPtr)) {
	    entryPtr = Blt_ChainGetValue(linkPtr);
	    if (entryPtr->column.control != CONTROL_FULL) {
		continue;
	    }
	    needed = GetReqWidth(entryPtr) + PADDING(entryPtr->padX) +
		2 * (entryPtr->borderWidth + tablePtr->eEntryPad);
	    if (needed <= 0) {
		continue;
	    }
	    used = GetSpan(infoPtr, entryPtr);
	    if (needed > used) {
		GrowSpan(infoPtr, entryPtr, needed - used);
	    }
	}
    }

    LockPartitions(infoPtr);

    for (node = Blt_ListFirstNode(infoPtr->list); node != NULL;
	node = Blt_ListNextNode(node)) {
	chainPtr = (Blt_Chain *) Blt_ListGetValue(node);

	for (linkPtr = Blt_ChainFirstLink(chainPtr); linkPtr != NULL;
	    linkPtr = Blt_ChainNextLink(linkPtr)) {
	    entryPtr = Blt_ChainGetValue(linkPtr);

	    needed = GetReqWidth(entryPtr) + PADDING(entryPtr->padX) +
		2 * (entryPtr->borderWidth + tablePtr->eEntryPad);

	    if (entryPtr->column.control >= 0.0) {
		needed = (int)(needed * entryPtr->column.control);
	    }
	    if (needed <= 0) {
		continue;
	    }
	    used = GetSpan(infoPtr, entryPtr);
	    if (needed > used) {
		GrowSpan(infoPtr, entryPtr, needed - used);
	    }
	}
    }
    total = SetNominalSizes(tablePtr, infoPtr);
    tablePtr->normal.width = GetBoundedWidth(total, &(tablePtr->reqWidth)) +
	PADDING(tablePtr->padX) +
	2 * (tablePtr->eTablePad + Tk_InternalBorderWidth(tablePtr->tkwin));

    infoPtr = &(tablePtr->rowInfo);

    ResetPartitions(tablePtr, infoPtr, GetBoundedHeight);

    for (node = Blt_ListFirstNode(infoPtr->list); node != NULL;
	node = Blt_ListNextNode(node)) {
	chainPtr = (Blt_Chain *) Blt_ListGetValue(node);

	for (linkPtr = Blt_ChainFirstLink(chainPtr); linkPtr != NULL;
	    linkPtr = Blt_ChainNextLink(linkPtr)) {
	    entryPtr = Blt_ChainGetValue(linkPtr);
	    if (entryPtr->row.control != CONTROL_FULL) {
		continue;
	    }
	    needed = GetReqHeight(entryPtr) + PADDING(entryPtr->padY) +
		2 * (entryPtr->borderWidth + tablePtr->eEntryPad);
	    if (needed <= 0) {
		continue;
	    }
	    used = GetSpan(infoPtr, entryPtr);
	    if (needed > used) {
		GrowSpan(infoPtr, entryPtr, needed - used);
	    }
	}
    }

    LockPartitions(&(tablePtr->rowInfo));

    for (node = Blt_ListFirstNode(infoPtr->list); node != NULL;
	node = Blt_ListNextNode(node)) {
	chainPtr = Blt_ChainGetValue(node);

	for (linkPtr = Blt_ChainFirstLink(chainPtr); linkPtr != NULL;
	    linkPtr = Blt_ChainNextLink(linkPtr)) {
	    entryPtr = Blt_ChainGetValue(linkPtr);
	    needed = GetReqHeight(entryPtr) + PADDING(entryPtr->padY) +
		2 * (entryPtr->borderWidth + tablePtr->eEntryPad);
	    if (entryPtr->row.control >= 0.0) {
		needed = (int)(needed * entryPtr->row.control);
	    }
	    if (needed <= 0) {
		continue;
	    }
	    used = GetSpan(infoPtr, entryPtr);
	    if (needed > used) {
		GrowSpan(infoPtr, entryPtr, needed - used);
	    }
	}
    }
    total = SetNominalSizes(tablePtr, infoPtr);
    tablePtr->normal.height = GetBoundedHeight(total, &(tablePtr->reqHeight)) +
	PADDING(tablePtr->padY) +
	2 * (tablePtr->eTablePad + Tk_InternalBorderWidth(tablePtr->tkwin));
}

/*
 * ----------------------------------------------------------------------------
 *
 * ArrangeEntries
 *
 *	Places each widget at its proper location.  First determines
 *	the size and position of the each widget.  It then considers the
 *	following:
 *
 *	  1. translation of widget position its parent widget.
 *	  2. fill style
 *	  3. anchor
 *	  4. external and internal padding
 *	  5. widget size must be greater than zero
 *
 * Results:
 *	None.
 *
 * Side Effects:
 * 	The size of each partition is re-initialized its minimum size.
 *
 * ----------------------------------------------------------------------------
 */
static void
ArrangeEntries(tablePtr)
    Table *tablePtr;		/* Table widget structure */
{
    register Blt_ChainLink *linkPtr;
    register Entry *entryPtr;
    register int spanWidth, spanHeight;
    int x, y;
    int winWidth, winHeight;
    int dx, dy;
    int maxX, maxY;
    int extra;

    maxX = tablePtr->container.width -
	(Tk_InternalBorderWidth(tablePtr->tkwin) + tablePtr->padRight +
	tablePtr->eTablePad);
    maxY = tablePtr->container.height -
	(Tk_InternalBorderWidth(tablePtr->tkwin) + tablePtr->padBottom +
	tablePtr->eTablePad);

    for (linkPtr = Blt_ChainFirstLink(tablePtr->chainPtr); linkPtr != NULL;
	linkPtr = Blt_ChainNextLink(linkPtr)) {
	entryPtr = Blt_ChainGetValue(linkPtr);

	x = entryPtr->column.rcPtr->offset +
	    entryPtr->column.rcPtr->pad.side1 +
	    entryPtr->padLeft +
	    Tk_Changes(entryPtr->tkwin)->border_width +
	    tablePtr->eEntryPad;
	y = entryPtr->row.rcPtr->offset +
	    entryPtr->row.rcPtr->pad.side1 +
	    entryPtr->padTop +
	    Tk_Changes(entryPtr->tkwin)->border_width +
	    tablePtr->eEntryPad;

	/*
	 * Unmap any widgets that start beyond of the right edge of
	 * the container.
	 */
	if ((x >= maxX) || (y >= maxY)) {
	    if (Tk_IsMapped(entryPtr->tkwin)) {
		if (Tk_Parent(entryPtr->tkwin) != tablePtr->tkwin) {
		    Tk_UnmaintainGeometry(entryPtr->tkwin, tablePtr->tkwin);
		}
		Tk_UnmapWindow(entryPtr->tkwin);
	    }
	    continue;
	}
	extra = 2 * (entryPtr->borderWidth + tablePtr->eEntryPad);
	spanWidth = GetSpan(&(tablePtr->columnInfo), entryPtr) -
	    (extra + PADDING(entryPtr->padX));
	spanHeight = GetSpan(&(tablePtr->rowInfo), entryPtr) -
	    (extra + PADDING(entryPtr->padY));

	winWidth = GetReqWidth(entryPtr);
	winHeight = GetReqHeight(entryPtr);

	/*
	 *
	 * Compare the widget's requested size to the size of the span.
	 *
	 * 1) If the widget is larger than the span or if the fill flag
	 *    is set, make the widget the size of the span. Check that the
	 *    new size is within the bounds set for the widget.
	 *
	 * 2) Otherwise, position the widget in the space according to its
	 *    anchor.
	 *
	 */
	if ((spanWidth <= winWidth) || (entryPtr->fill & FILL_X)) {
	    winWidth = spanWidth;
	    if (winWidth > entryPtr->reqWidth.max) {
		winWidth = entryPtr->reqWidth.max;
	    }
	}
	if ((spanHeight <= winHeight) || (entryPtr->fill & FILL_Y)) {
	    winHeight = spanHeight;
	    if (winHeight > entryPtr->reqHeight.max) {
		winHeight = entryPtr->reqHeight.max;
	    }
	}
	dx = dy = 0;
	if (spanWidth > winWidth) {
	    dx = (spanWidth - winWidth);
	}
	if (spanHeight > winHeight) {
	    dy = (spanHeight - winHeight);
	}
	if ((dx > 0) || (dy > 0)) {
	    TranslateAnchor(dx, dy, entryPtr->anchor, &x, &y);
	}
	/*
	 * Clip the widget at the bottom and/or right edge of the
	 * container.
	 */
	if (winWidth > (maxX - x)) {
	    winWidth = (maxX - x);
	}
	if (winHeight > (maxY - y)) {
	    winHeight = (maxY - y);
	}

	/*
	 * If the widget is too small (i.e. it has only an external
	 * border) then unmap it.
	 */
	if ((winWidth < 1) || (winHeight < 1)) {
	    if (Tk_IsMapped(entryPtr->tkwin)) {
		if (tablePtr->tkwin != Tk_Parent(entryPtr->tkwin)) {
		    Tk_UnmaintainGeometry(entryPtr->tkwin, tablePtr->tkwin);
		}
		Tk_UnmapWindow(entryPtr->tkwin);
	    }
	    continue;
	}

	/*
	 * Resize and/or move the widget as necessary.
	 */
	entryPtr->x = x;
	entryPtr->y = y;

	if (tablePtr->tkwin != Tk_Parent(entryPtr->tkwin)) {
	    Tk_MaintainGeometry(entryPtr->tkwin, tablePtr->tkwin, x, y,
		winWidth, winHeight);
	} else {
	    if ((x != Tk_X(entryPtr->tkwin)) || 
		(y != Tk_Y(entryPtr->tkwin)) ||
		(winWidth != Tk_Width(entryPtr->tkwin)) ||
		(winHeight != Tk_Height(entryPtr->tkwin))) {
		Tk_MoveResizeWindow(entryPtr->tkwin, x, y, winWidth, winHeight);
	    }
	    if (!Tk_IsMapped(entryPtr->tkwin)) {
		Tk_MapWindow(entryPtr->tkwin);
	    }
	}
    }
}

/*
 * ----------------------------------------------------------------------------
 *
 * ArrangeTable --
 *
 *
 * Results:
 *	None.
 *
 * Side Effects:
 * 	The widgets in the table are possibly resized and redrawn.
 *
 * ----------------------------------------------------------------------------
 */
static void
ArrangeTable(clientData)
    ClientData clientData;
{
    Table *tablePtr = clientData;
    int width, height;
    int offset;
    int padX, padY;
    int outerPad;
    RowColumn *columnPtr, *rowPtr;
    Blt_ChainLink *linkPtr;

#ifdef notdef
    fprintf(stderr, "ArrangeTable(%s)\n", Tk_PathName(tablePtr->tkwin));
#endif
    Tcl_Preserve(tablePtr);
    tablePtr->flags &= ~ARRANGE_PENDING;

    tablePtr->rowInfo.ePad = tablePtr->columnInfo.ePad = tablePtr->eTablePad =
	tablePtr->eEntryPad = 0;
    if (tablePtr->editPtr != NULL) {
	tablePtr->rowInfo.ePad = tablePtr->columnInfo.ePad =
	    tablePtr->editPtr->gridLineWidth;
	tablePtr->eTablePad = tablePtr->editPtr->gridLineWidth;
	tablePtr->eEntryPad = tablePtr->editPtr->entryPad;
    }
    /*
     * If the table has no children anymore, then don't do anything at
     * all: just leave the container widget's size as-is.
     */
    if ((Blt_ChainGetLength(tablePtr->chainPtr) == 0) ||
	(tablePtr->tkwin == NULL)) {
	Tcl_Release(tablePtr);
	return;
    }
    if (tablePtr->flags & REQUEST_LAYOUT) {
	tablePtr->flags &= ~REQUEST_LAYOUT;
	LayoutPartitions(tablePtr);
    }
    /*
     * Initially, try to fit the partitions exactly into the container
     * by resizing the container.  If the widget's requested size is
     * different, send a request to the container widget's geometry
     * manager to resize.
     */
    if ((tablePtr->propagate) &&
	((tablePtr->normal.width != Tk_ReqWidth(tablePtr->tkwin)) ||
	    (tablePtr->normal.height != Tk_ReqHeight(tablePtr->tkwin)))) {
	Tk_GeometryRequest(tablePtr->tkwin, tablePtr->normal.width,
	    tablePtr->normal.height);
	EventuallyArrangeTable(tablePtr);
	Tcl_Release(tablePtr);
	return;
    }
    /*
     * Save the width and height of the container so we know when its
     * size has changed during ConfigureNotify events.
     */
    tablePtr->container.width = Tk_Width(tablePtr->tkwin);
    tablePtr->container.height = Tk_Height(tablePtr->tkwin);
    outerPad = 2 * (Tk_InternalBorderWidth(tablePtr->tkwin) +
	tablePtr->eTablePad);
    padX = outerPad + tablePtr->columnInfo.ePad + PADDING(tablePtr->padX);
    padY = outerPad + tablePtr->rowInfo.ePad + PADDING(tablePtr->padY);

    width = GetTotalSpan(&(tablePtr->columnInfo)) + padX;
    height = GetTotalSpan(&(tablePtr->rowInfo)) + padY;

    /*
     * If the previous geometry request was not fulfilled (i.e. the
     * size of the container is different from partitions' space
     * requirements), try to adjust size of the partitions to fit the
     * widget.
     */
    if (tablePtr->container.width != width) {
	AdjustPartitions(&(tablePtr->columnInfo),
	    tablePtr->container.width - width);
	width = GetTotalSpan(&(tablePtr->columnInfo)) + padX;
    }
    if (tablePtr->container.height != height) {
	AdjustPartitions(&(tablePtr->rowInfo),
	    tablePtr->container.height - height);
	height = GetTotalSpan(&(tablePtr->rowInfo)) + padY;
    }
    /*
     * If after adjusting the size of the partitions the space
     * required does not equal the size of the widget, do one of the
     * following:
     *
     * 1) If it's smaller, center the table in the widget.
     * 2) If it's bigger, clip the partitions that extend beyond
     *    the edge of the container.
     *
     * Set the row and column offsets (including the container's
     * internal border width). To be used later when positioning the
     * widgets.
     */
    offset = Tk_InternalBorderWidth(tablePtr->tkwin) + tablePtr->padLeft +
	tablePtr->eTablePad;
    if (width < tablePtr->container.width) {
	offset += (tablePtr->container.width - width) / 2;
    }
    for (linkPtr = Blt_ChainFirstLink(tablePtr->columnInfo.chainPtr);
	linkPtr != NULL; linkPtr = Blt_ChainNextLink(linkPtr)) {
	columnPtr = Blt_ChainGetValue(linkPtr);
	columnPtr->offset = offset + tablePtr->columnInfo.ePad;
	offset += columnPtr->size;
    }
    offset = Tk_InternalBorderWidth(tablePtr->tkwin) + tablePtr->padTop +
	tablePtr->eTablePad;
    if (height < tablePtr->container.height) {
	offset += (tablePtr->container.height - height) / 2;
    }
    for (linkPtr = Blt_ChainFirstLink(tablePtr->rowInfo.chainPtr);
	linkPtr != NULL; linkPtr = Blt_ChainNextLink(linkPtr)) {
	rowPtr = Blt_ChainGetValue(linkPtr);
	rowPtr->offset = offset + tablePtr->rowInfo.ePad;
	offset += rowPtr->size;
    }
    ArrangeEntries(tablePtr);
    if (tablePtr->editPtr != NULL) {
	/* Redraw the editor */
	(*tablePtr->editPtr->drawProc) (tablePtr->editPtr);
    }
    Tcl_Release(tablePtr);
}

/*
 * ----------------------------------------------------------------------------
 *
 * ArrangeOp --
 *
 *	Forces layout of the table geometry manager.  This is useful
 *	mostly for debugging the geometry manager.  You can get the
 *	geometry manager to calculate the normal (requested) width and
 *	height of each row and column.  Otherwise, you need to first
 *	withdraw the container widget, invoke "update", and then query
 *	the geometry manager.
 *
 * Results:
 *	Returns a standard Tcl result.  If the table is successfully
 *	rearranged, TCL_OK is returned. Otherwise, TCL_ERROR is returned
 *	and an error message is left in interp->result.
 *
 * ----------------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
ArrangeOp(dataPtr, interp, argc, argv)
    TableInterpData *dataPtr;	/* Interpreter-specific data. */
    Tcl_Interp *interp;		/* Interpreter to report errors to */
    int argc;
    char **argv;		/* Path name of container associated with
				 * the table */
{
    Table *tablePtr;

    if (Blt_GetTable(dataPtr, interp, argv[2], &tablePtr) != TCL_OK) {
	return TCL_ERROR;
    }
    tablePtr->flags |= REQUEST_LAYOUT;
    ArrangeTable(tablePtr);
    return TCL_OK;
}

/*
 * ----------------------------------------------------------------------------
 *
 * CgetOp --
 *
 *	Returns the name, position and options of a widget in the table.
 *
 * Results:
 *	Returns a standard Tcl result.  A list of the widget attributes
 *	is left in interp->result.
 *
 * --------------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
CgetOp(dataPtr, interp, argc, argv)
    TableInterpData *dataPtr;	/* Interpreter-specific data. */
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    Table *tablePtr;
    int length;
    char c;
    int n;
    PartitionInfo *infoPtr;

    if (Blt_GetTable(dataPtr, interp, argv[2], &tablePtr) != TCL_OK) {
	return TCL_ERROR;
    }
    if (argc == 4) {
	return Tk_ConfigureValue(interp, tablePtr->tkwin, tableConfigSpecs,
	    (char *)tablePtr, argv[3], 0);
    }
    c = argv[3][0];
    length = strlen(argv[3]);
    if (c == '.') {		/* Configure widget */
	Entry *entryPtr;

	if (GetEntry(interp, tablePtr, argv[3], &entryPtr) != TCL_OK) {
	    return TCL_ERROR;
	}
	return Tk_ConfigureValue(interp, entryPtr->tkwin, entryConfigSpecs,
	    (char *)entryPtr, argv[4], 0);
    } else if ((c == 'c') && (strncmp(argv[3], "container", length) == 0)) {
	return Tk_ConfigureValue(interp, tablePtr->tkwin, tableConfigSpecs,
	    (char *)tablePtr, argv[4], 0);
    }
    infoPtr = ParseRowColumn(tablePtr, argv[3], &n);
    if (infoPtr == NULL) {
	return TCL_ERROR;
    }
    return Tk_ConfigureValue(interp, tablePtr->tkwin, infoPtr->configSpecs,
	(char *)GetRowColumn(infoPtr, n), argv[4], 0);
}

/*
 * ----------------------------------------------------------------------------
 *
 * ConfigureOp --
 *
 *	Returns the name, position and options of a widget in the table.
 *
 * Results:
 *	Returns a standard Tcl result.  A list of the table configuration
 *	option information is left in interp->result.
 *
 * --------------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
ConfigureOp(dataPtr, interp, argc, argv)
    TableInterpData *dataPtr;	/* Interpreter-specific data. */
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    Table *tablePtr;
    int length;
    char c1, c2;
    int count;
    int result;
    char **items;
    register int i;

    if (Blt_GetTable(dataPtr, interp, argv[2], &tablePtr) != TCL_OK) {
	return TCL_ERROR;
    }
    /*
     * Find the end of the items. Search until we see an option (-).
     */
    argc -= 3, argv += 3;
    for (count = 0; count < argc; count++) {
	if (argv[count][0] == '-') {
	    break;
	}
    }
    items = argv;		/* Save the start of the item list */
    argc -= count;		/* Move beyond the items to the options */
    argv += count;

    result = TCL_ERROR;		/* Suppress compiler warning */

    if (count == 0) {
	result = ConfigureTable(tablePtr, interp, argc, argv);
    }
    for (i = 0; i < count; i++) {
	c1 = items[i][0];
	c2 = items[i][1];
	length = strlen(items[i]);
	if (c1 == '.') {		/* Configure widget */
	    Entry *entryPtr;

	    if (GetEntry(interp, tablePtr, items[i], &entryPtr) != TCL_OK) {
		return TCL_ERROR;
	    }
	    result = ConfigureEntry(tablePtr, interp, entryPtr, argc, argv);
	} else if ((c1 == 'r') || (c1 == 'R')) {
	    result = ConfigureRowColumn(tablePtr, &(tablePtr->rowInfo),
		items[i], argc, argv);
	} else if ((c1 == 'c') && (c2 == 'o') &&
	    (strncmp(argv[3], "container", length) == 0)) {
	    result = ConfigureTable(tablePtr, interp, argc, argv);
	} else if ((c1 == 'c') || (c1 == 'C')) {
	    result = ConfigureRowColumn(tablePtr, &(tablePtr->columnInfo),
		items[i], argc, argv);
	} else {
	    Tcl_AppendResult(interp, "unknown item \"", items[i],
		"\": should be widget, row or column index, or \"container\"",
		(char *)NULL);
	    return TCL_ERROR;
	}
	if (result == TCL_ERROR) {
	    break;
	}
	if ((i + 1) < count) {
	    Tcl_AppendResult(interp, "\n", (char *)NULL);
	}
    }
    tablePtr->flags |= REQUEST_LAYOUT;
    EventuallyArrangeTable(tablePtr);
    return result;
}

/*
 * ----------------------------------------------------------------------------
 *
 * DeleteOp --
 *
 *	Deletes the specified rows and/or columns from the table.
 *	Note that the row/column indices can be fixed only after
 *	all the deletions have occurred.
 *
 *		table delete .f r0 r1 r4 c0
 *
 * Results:
 *	Returns a standard Tcl result.
 *
 *
 * ----------------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
DeleteOp(dataPtr, interp, argc, argv)
    TableInterpData *dataPtr;	/* Interpreter-specific data. */
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    Table *tablePtr;
    char c;
    Blt_ChainLink *linkPtr, *nextPtr;
    PartitionInfo *infoPtr;
    char string[200];
    int matches;
    register int i;
    RowColumn *rcPtr;

    if (Blt_GetTable(dataPtr, interp, argv[2], &tablePtr) != TCL_OK) {
	return TCL_ERROR;
    }
    for (i = 3; i < argc; i++) {
	c = tolower(argv[i][0]);
	if ((c != 'r') && (c != 'c')) {
	    Tcl_AppendResult(interp, "bad index \"", argv[i],
		"\": must start with \"r\" or \"c\"", (char *)NULL);
	    return TCL_ERROR;
	}
    }
    matches = 0;
    for (i = 3; i < argc; i++) {
	c = tolower(argv[i][0]);
	infoPtr = (c == 'r') ? &(tablePtr->rowInfo) : &(tablePtr->columnInfo);
	for (linkPtr = Blt_ChainFirstLink(infoPtr->chainPtr); linkPtr != NULL;
	    linkPtr = nextPtr) {
	    nextPtr = Blt_ChainNextLink(linkPtr);
	    rcPtr = Blt_ChainGetValue(linkPtr);
	    sprintf(string, "%c%d", argv[i][0], rcPtr->index);
	    if (Tcl_StringMatch(string, argv[i])) {
		matches++;
		DeleteRowColumn(tablePtr, infoPtr, rcPtr);
		Blt_ChainDeleteLink(infoPtr->chainPtr, linkPtr);
	    }
	}
    }
    if (matches > 0) {		/* Fix indices */
	i = 0;
	for (linkPtr = Blt_ChainFirstLink(tablePtr->columnInfo.chainPtr);
	    linkPtr != NULL; linkPtr = Blt_ChainNextLink(linkPtr)) {
	    rcPtr = Blt_ChainGetValue(linkPtr);
	    rcPtr->index = i++;
	}
	i = 0;
	for (linkPtr = Blt_ChainFirstLink(tablePtr->rowInfo.chainPtr);
	    linkPtr != NULL; linkPtr = Blt_ChainNextLink(linkPtr)) {
	    rcPtr = Blt_ChainGetValue(linkPtr);
	    rcPtr->index = i++;
	}
	tablePtr->flags |= REQUEST_LAYOUT;
	EventuallyArrangeTable(tablePtr);
    }
    return TCL_OK;
}

/*
 * ----------------------------------------------------------------------------
 *
 * JoinOp --
 *
 *	Joins the specified span of rows/columns together into a
 *	partition.  The row/column indices can be fixed only after
 *	all the deletions have occurred.
 *
 *		table join .f r0 r3
 *		table join .f c2 c4
 * Results:
 *	Returns a standard Tcl result.
 *
 * ----------------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
JoinOp(dataPtr, interp, argc, argv)
    TableInterpData *dataPtr;	/* Interpreter-specific data. */
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    Table *tablePtr;
    Blt_ChainLink *linkPtr, *nextPtr, *fromPtr;
    PartitionInfo *infoPtr, *info2Ptr;
    Entry *entryPtr;
    int from, to;		/* Indices marking the span of
				 * partitions to be joined together.  */
    int start, end;		/* Entry indices. */
    register int i;
    RowColumn *rcPtr;

    if (Blt_GetTable(dataPtr, interp, argv[2], &tablePtr) != TCL_OK) {
	return TCL_ERROR;
    }
    infoPtr = ParseRowColumn(tablePtr, argv[3], &from);
    if (infoPtr == NULL) {
	return TCL_ERROR;
    }
    info2Ptr = ParseRowColumn(tablePtr, argv[4], &to);
    if (info2Ptr == NULL) {
	return TCL_ERROR;
    }
    if (infoPtr != info2Ptr) {
	Tcl_AppendResult(interp,
	    "\"from\" and \"to\" must both be rows or columns",
	    (char *)NULL);
	return TCL_ERROR;
    }
    if (from >= to) {
	return TCL_OK;		/* No-op. */
    }
    fromPtr = Blt_ChainGetNthLink(infoPtr->chainPtr, from);
    rcPtr = Blt_ChainGetValue(fromPtr);

    /*
     * ---------------------------------------------------------------
     *
     *	Reduce the span of all entries that currently cross any of the
     *	trailing rows/columns.  Also, if the entry starts in one of
     *	these rows/columns, moved it to the designated "joined"
     *	row/column.
     *
     * ---------------------------------------------------------------
     */
    if (infoPtr->type == rowUid) {
	for (linkPtr = Blt_ChainFirstLink(tablePtr->chainPtr); linkPtr != NULL;
	    linkPtr = Blt_ChainNextLink(linkPtr)) {
	    entryPtr = Blt_ChainGetValue(linkPtr);
	    start = entryPtr->row.rcPtr->index + 1;
	    end = entryPtr->row.rcPtr->index + entryPtr->row.span - 1;
	    if ((end < from) || ((start > to))) {
		continue;
	    }
	    entryPtr->row.span -= to - start + 1;
	    if (start >= from) {/* Entry starts in a trailing partition. */
		entryPtr->row.rcPtr = rcPtr;
	    }
	}
    } else {
	for (linkPtr = Blt_ChainFirstLink(tablePtr->chainPtr); linkPtr != NULL;
	    linkPtr = Blt_ChainNextLink(linkPtr)) {
	    entryPtr = Blt_ChainGetValue(linkPtr);
	    start = entryPtr->column.rcPtr->index + 1;
	    end = entryPtr->column.rcPtr->index + entryPtr->column.span - 1;
	    if ((end < from) || ((start > to))) {
		continue;
	    }
	    entryPtr->column.span -= to - start + 1;
	    if (start >= from) {/* Entry starts in a trailing partition. */
		entryPtr->column.rcPtr = rcPtr;
	    }
	}
    }
    linkPtr = Blt_ChainNextLink(fromPtr);
    for (i = from + 1; i <= to; i++) {
	nextPtr = Blt_ChainNextLink(linkPtr);
	rcPtr = Blt_ChainGetValue(linkPtr);
	DeleteRowColumn(tablePtr, infoPtr, rcPtr);
	Blt_ChainDeleteLink(infoPtr->chainPtr, linkPtr);
	linkPtr = nextPtr;
    }
    i = 0;
    for (linkPtr = Blt_ChainFirstLink(infoPtr->chainPtr);
	linkPtr != NULL; linkPtr = Blt_ChainNextLink(linkPtr)) {
	rcPtr = Blt_ChainGetValue(linkPtr);
	rcPtr->index = i++;
    }
    tablePtr->flags |= REQUEST_LAYOUT;
    EventuallyArrangeTable(tablePtr);
    return TCL_OK;
}

/*
 * ----------------------------------------------------------------------------
 *
 * ExtentsOp --
 *
 *	Returns a list of all the pathnames of the widgets managed by
 *	a table.  The table is determined from the name of the
 *	container widget associated with the table.
 *
 *		table extents .frame r0 c0 container
 *
 * Results:
 *	Returns a standard Tcl result.  If no error occurred, TCL_OK is
 *	returned and a list of widgets managed by the table is left in
 *	interp->result.
 *
 * ----------------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
ExtentsOp(dataPtr, interp, argc, argv)
    TableInterpData *dataPtr;	/* Interpreter-specific data. */
    Tcl_Interp *interp;		/* Interpreter to return results to. */
    int argc;			/* # of arguments */
    char **argv;		/* Command line arguments. */
{
    Table *tablePtr;
    Blt_ChainLink *linkPtr;
    RowColumn *rcPtr;
    RowColumn *c1Ptr, *r1Ptr, *c2Ptr, *r2Ptr;
    PartitionInfo *infoPtr;
    int x, y, width, height;
    char string[200];
    char c;

    if (Blt_GetTable(dataPtr, interp, argv[2], &tablePtr) != TCL_OK) {
	return TCL_ERROR;
    }
    c = tolower(argv[3][0]);
    if (c == 'r') {
	infoPtr = &(tablePtr->rowInfo);
    } else if (c == 'c') {
	infoPtr = &(tablePtr->columnInfo);
    } else {
	Tcl_AppendResult(interp, "unknown item \"", argv[3],
	    "\": should be widget, row, or column", (char *)NULL);
	return TCL_ERROR;
    }
    for (linkPtr = Blt_ChainFirstLink(infoPtr->chainPtr);
	linkPtr != NULL; linkPtr = Blt_ChainNextLink(linkPtr)) {
	rcPtr = Blt_ChainGetValue(linkPtr);
	sprintf(string, "%c%d", argv[3][0], rcPtr->index);
	if (Tcl_StringMatch(string, argv[3])) {
	    if (c == 'r') {
		r1Ptr = r2Ptr = rcPtr;
		c1Ptr = GetRowColumn(&(tablePtr->columnInfo), 0);
		c2Ptr = GetRowColumn(&(tablePtr->columnInfo), 
				     tablePtr->nColumns - 1);
	    } else {
		c1Ptr = c2Ptr = rcPtr;
		r1Ptr = GetRowColumn(&(tablePtr->rowInfo), 0);
		r2Ptr = GetRowColumn(&(tablePtr->rowInfo), 
				     tablePtr->nRows - 1);
	    }
	    x = c1Ptr->offset;
	    y = r1Ptr->offset;
	    width = c2Ptr->offset + c2Ptr->size - x;
	    height = r2Ptr->offset + r2Ptr->size - y;
	    sprintf(string, "%c%d %d %d %d %d\n", argv[3][0], rcPtr->index,
		x, y, width, height);
	    Tcl_AppendResult(interp, string, (char *)NULL);
	}
    }
    return TCL_OK;
}

/*
 * ----------------------------------------------------------------------------
 *
 * ForgetOp --
 *
 *	Processes an argv/argc list of widget names and purges their
 *	entries from their respective tables.  The widgets are unmapped and
 *	the tables are rearranged at the next idle point.  Note that all
 *	the named widgets do not need to exist in the same table.
 *
 * Results:
 *	Returns a standard Tcl result.  If an error occurred, TCL_ERROR is
 *	returned and an error message is left in interp->result.
 *
 * Side Effects:
 *	Memory is deallocated (the entry is destroyed), etc.  The
 *	affected tables are is re-computed and arranged at the next idle
 *	point.
 *
 * ----------------------------------------------------------------------------
 */
static int
ForgetOp(dataPtr, interp, argc, argv)
    TableInterpData *dataPtr;	/* Interpreter-specific data. */
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    Entry *entryPtr;
    register int i;
    Blt_HashEntry *hPtr;
    Blt_HashSearch cursor;
    Table *tablePtr;
    Tk_Window tkwin, mainWindow;
    
    tablePtr = NULL;
    mainWindow = Tk_MainWindow(interp);
    for (i = 2; i < argc; i++) {
	entryPtr = NULL;
	tkwin = Tk_NameToWindow(interp, argv[i], mainWindow);
	if (tkwin == NULL) {
	    return TCL_ERROR;
	}
	for (hPtr = Blt_FirstHashEntry(&(dataPtr->tableTable), &cursor);
	    hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) {
	    tablePtr = (Table *)Blt_GetHashValue(hPtr);
	    if (tablePtr->interp != interp) {
		continue;
	    }
	    entryPtr = FindEntry(tablePtr, tkwin);
	    if (entryPtr != NULL) {
		break;
	    }
	}
	if (entryPtr == NULL) {
	    Tcl_AppendResult(interp, "\"", argv[i],
		"\" is not managed by any table", (char *)NULL);
	    return TCL_ERROR;
	}
	if (Tk_IsMapped(entryPtr->tkwin)) {
	    Tk_UnmapWindow(entryPtr->tkwin);
	}
	/* Arrange for the call back here in the loop, because the
	 * widgets may not belong to the same table.  */
	tablePtr->flags |= REQUEST_LAYOUT;
	EventuallyArrangeTable(tablePtr);
	DestroyEntry(entryPtr);
    }
    return TCL_OK;
}

/*
 * ----------------------------------------------------------------------------
 *
 * InfoOp --
 *
 *	Returns the options of a widget or partition in the table.
 *
 * Results:
 *	Returns a standard Tcl result.  A list of the widget attributes
 *	is left in interp->result.
 *
 * ----------------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
InfoOp(dataPtr, interp, argc, argv)
    TableInterpData *dataPtr;	/* Interpreter-specific data. */
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    Table *tablePtr;
    int result;
    char c;
    register int i;

    if (Blt_GetTable(dataPtr, interp, argv[2], &tablePtr) != TCL_OK) {
	return TCL_ERROR;
    }
    for (i = 3; i < argc; i++) {
	c = argv[i][0];
	if (c == '.') {		/* Entry information */
	    Entry *entryPtr;

	    if (GetEntry(interp, tablePtr, argv[i], &entryPtr) != TCL_OK) {
		return TCL_ERROR;
	    }
	    result = InfoEntry(interp, tablePtr, entryPtr);
	} else if ((c == 'r') || (c == 'R') || (c == 'c') || (c == 'C')) {
	    result = InfoRowColumn(tablePtr, interp, argv[i]);
	} else {
	    Tcl_AppendResult(interp, "unknown item \"", argv[i],
		"\": should be widget, row, or column", (char *)NULL);
	    return TCL_ERROR;
	}
	if (result != TCL_OK) {
	    return TCL_ERROR;
	}
	if ((i + 1) < argc) {
	    Tcl_AppendResult(interp, "\n", (char *)NULL);
	}
    }
    return TCL_OK;
}

/*
 * ----------------------------------------------------------------------------
 *
 * InsertOp --
 *
 *	Inserts a span of rows/columns into the table.
 *
 *		table insert .f r0 2
 *		table insert .f c0 5
 *
 * Results:
 *	Returns a standard Tcl result.  A list of the widget
 *	attributes is left in interp->result.
 *
 * ----------------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
InsertOp(dataPtr, interp, argc, argv)
    TableInterpData *dataPtr;	/* Interpreter-specific data. */
    Tcl_Interp *interp;
    int argc;
    char **argv;
{ 
    Table *tablePtr;
    long int span;
    int before;
    PartitionInfo *infoPtr;
    RowColumn *rcPtr;
    register int i;
    Blt_ChainLink *beforePtr, *linkPtr;
    int linkBefore;

    if (Blt_GetTable(dataPtr, interp, argv[2], &tablePtr) != TCL_OK) {
	return TCL_ERROR;
    }
    linkBefore = TRUE;
    if (argv[3][0] == '-') {
	if (strcmp(argv[3], "-before") == 0) {
	    linkBefore = TRUE;
	    argv++; argc--;
	} else if (strcmp(argv[3], "-after") == 0) {
	    linkBefore = FALSE;
	    argv++; argc--;
	}	    
    } 
    if (argc == 3) {
	Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
			 "insert ", argv[2], "row|column ?span?", (char *)NULL);
	return TCL_ERROR;
    }
    infoPtr = ParseRowColumn(tablePtr, argv[3], &before);
    if (infoPtr == NULL) {
	return TCL_ERROR;
    }
    span = 1;
    if ((argc > 4) && (Tcl_ExprLong(interp, argv[4], &span) != TCL_OK)) {
	return TCL_ERROR;
    }
    if (span < 1) {
	Tcl_AppendResult(interp, "span value \"", argv[4],
	    "\" can't be negative", (char *)NULL);
	return TCL_ERROR;
    }
    beforePtr = Blt_ChainGetNthLink(infoPtr->chainPtr, before);
    /*
     * Insert the new rows/columns from the designated point in the
     * chain.
     */
    for (i = 0; i < span; i++) {
	rcPtr = CreateRowColumn();
	linkPtr = Blt_ChainNewLink();
	Blt_ChainSetValue(linkPtr, rcPtr);
	if (linkBefore) {
	    Blt_ChainLinkBefore(infoPtr->chainPtr, linkPtr, beforePtr);
	} else {
	    Blt_ChainLinkAfter(infoPtr->chainPtr, linkPtr, beforePtr);
	}
	rcPtr->linkPtr = linkPtr;
    }
    i = 0;
    for (linkPtr = Blt_ChainFirstLink(infoPtr->chainPtr); linkPtr != NULL;
	linkPtr = Blt_ChainNextLink(linkPtr)) {
	rcPtr = Blt_ChainGetValue(linkPtr);
	/* Reset the indices of the trailing rows/columns.  */
	rcPtr->index = i++;
    }
    tablePtr->flags |= REQUEST_LAYOUT;
    EventuallyArrangeTable(tablePtr);
    return TCL_OK;
}

/*
 * ----------------------------------------------------------------------------
 *
 * SplitOp --
 *
 *	Splits a single row/column into multiple partitions. Any
 *	widgets that span this row/column will be automatically
 *	corrected to include the new rows/columns.
 *
 *		table split .f r0 3
 *		table split .f c2 2
 * Results:
 *	Returns a standard Tcl result.  A list of the widget
 *	attributes is left in interp->result.
 *
 * ----------------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
SplitOp(dataPtr, interp, argc, argv)
    TableInterpData *dataPtr;	/* Interpreter-specific data. */
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    Table *tablePtr;
    int number, split;
    int start, end;
    PartitionInfo *infoPtr;
    RowColumn *rcPtr;
    register int i;
    Blt_ChainLink *afterPtr, *linkPtr;
    Entry *entryPtr;

    if (Blt_GetTable(dataPtr, interp, argv[2], &tablePtr) != TCL_OK) {
	return TCL_ERROR;
    }
    infoPtr = ParseRowColumn(tablePtr, argv[3], &number);
    if (infoPtr == NULL) {
	return TCL_ERROR;
    }
    split = 2;
    if (argc > 4) {
	if (Tcl_GetInt(interp, argv[4], &split) != TCL_OK) {
	    return TCL_ERROR;
	}
    }
    if (split < 2) {
	Tcl_AppendResult(interp, "bad split value \"", argv[4],
	    "\": should be 2 or greater", (char *)NULL);
	return TCL_ERROR;
    }
    afterPtr = Blt_ChainGetNthLink(infoPtr->chainPtr, number);

    /*
     * Append (split - 1) additional rows/columns starting
     * from the current point in the chain.
     */

    for (i = 1; i < split; i++) {
	rcPtr = CreateRowColumn();
	linkPtr = Blt_ChainNewLink();
	Blt_ChainSetValue(linkPtr, rcPtr);
	Blt_ChainLinkAfter(infoPtr->chainPtr, linkPtr, afterPtr);
	rcPtr->linkPtr = linkPtr;
    }

    /*
     * Also increase the span of all entries that span this
     * row/column by split - 1.
     */
    if (infoPtr->type == rowUid) {
	for (linkPtr = Blt_ChainFirstLink(tablePtr->chainPtr); linkPtr != NULL;
	    linkPtr = Blt_ChainNextLink(linkPtr)) {
	    entryPtr = Blt_ChainGetValue(linkPtr);
	    start = entryPtr->row.rcPtr->index;
	    end = entryPtr->row.rcPtr->index + entryPtr->row.span;
	    if ((start <= number) && (number < end)) {
		entryPtr->row.span += (split - 1);
	    }
	}
    } else {
	for (linkPtr = Blt_ChainFirstLink(tablePtr->chainPtr); linkPtr != NULL;
	    linkPtr = Blt_ChainNextLink(linkPtr)) {
	    entryPtr = Blt_ChainGetValue(linkPtr);
	    start = entryPtr->column.rcPtr->index;
	    end = entryPtr->column.rcPtr->index + entryPtr->column.span;
	    if ((start <= number) && (number < end)) {
		entryPtr->column.span += (split - 1);
	    }
	}
    }
    /*
     * Be careful to renumber the rows or columns only after
     * processing each entry.  Otherwise row/column numbering
     * will be out of sync with the index.
     */
    i = number;
    for (linkPtr = afterPtr; linkPtr != NULL;
	linkPtr = Blt_ChainNextLink(linkPtr)) {
	rcPtr = Blt_ChainGetValue(linkPtr);
	rcPtr->index = i++;	/* Renumber the trailing indices.  */
    }

    tablePtr->flags |= REQUEST_LAYOUT;
    EventuallyArrangeTable(tablePtr);
    return TCL_OK;
}

/*
 * ----------------------------------------------------------------------
 *
 * RowColumnSearch --
 *
 * 	Searches for the row or column designated by an x or y
 *	coordinate.
 *
 * Results:
 *	Returns a pointer to the row/column containing the given point.
 *	If no row/column contains the coordinate, NULL is returned.
 *
 * ----------------------------------------------------------------------
 */
static RowColumn *
RowColumnSearch(infoPtr, x)
    PartitionInfo *infoPtr;
    int x;			/* Search coordinate  */
{
    Blt_ChainLink *linkPtr;
    RowColumn *rcPtr;

    for (linkPtr = Blt_ChainFirstLink(infoPtr->chainPtr);
	linkPtr != NULL; linkPtr = Blt_ChainNextLink(linkPtr)) {
	rcPtr = Blt_ChainGetValue(linkPtr);
	if (x > (rcPtr->offset + rcPtr->size)) {
	    break;		/* Too far, can't find row/column. */
	}
	if (x > rcPtr->offset) {
	    return rcPtr;
	}
    }
    return NULL;
}

/*
 *----------------------------------------------------------------------
 *
 * LocateOp --
 *
 *
 *	Returns the row,column index given a screen coordinate.
 *
 * Results:
 *	Returns a standard Tcl result.
 *
 *----------------------------------------------------------------------
 */
/* ARGSUSED */
static int
LocateOp(dataPtr, interp, argc, argv)
    TableInterpData *dataPtr;	/* Interpreter-specific data. */
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    int x, y;
    RowColumn *rowPtr, *columnPtr;
    Table *tablePtr;

    if (Blt_GetTable(dataPtr, interp, argv[2], &tablePtr) != TCL_OK) {
	return TCL_ERROR;
    }
    if (Blt_GetPixels(interp, tablePtr->tkwin, argv[3], PIXELS_ANY, &x)
	!= TCL_OK) {
	return TCL_ERROR;
    }
    if (Blt_GetPixels(interp, tablePtr->tkwin, argv[4], PIXELS_ANY, &y)
	!= TCL_OK) {
	return TCL_ERROR;
    }
    rowPtr = RowColumnSearch(&(tablePtr->rowInfo), y);
    if (rowPtr == NULL) {
	return TCL_OK;
    }
    columnPtr = RowColumnSearch(&(tablePtr->columnInfo), x);
    if (columnPtr == NULL) {
	return TCL_OK;
    }
    Tcl_AppendElement(interp, Blt_Itoa(rowPtr->index));
    Tcl_AppendElement(interp, Blt_Itoa(columnPtr->index));
    return TCL_OK;
}

/*
 * ----------------------------------------------------------------------------
 *
 * ContainersOp --
 *
 *	Returns a list of tables currently in use. A table is
 *	associated by the name of its container widget.  All tables
 *	matching a given pattern are included in this list.  If no
 *	pattern is present (argc == 0), all tables are included.
 *
 * Results:
 *	Returns a standard Tcl result.  If no error occurred, TCL_OK is
 *	returned and a list of tables is left in interp->result.
 *
 * ----------------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
ContainersOp(dataPtr, interp, argc, argv)
    TableInterpData *dataPtr;	/* Interpreter-specific data. */
    Tcl_Interp *interp;		/* Interpreter to return list of names to */
    int argc;
    char **argv;		/* Contains 0-1 arguments: search pattern */
{
    Blt_HashEntry *hPtr;
    Blt_HashSearch cursor;
    register Table *tablePtr;
    char *pattern;

    pattern = NULL;
    if (argc > 2) {
	if (argv[2][0] == '-') {
	    unsigned int length;

	    length = strlen(argv[2]);
	    if ((length > 1) && (argv[2][1] == 'p') &&
		(strncmp(argv[2], "-pattern", length) == 0)) {
		pattern = argv[3];
		goto search;
	    } else if ((length > 1) && (argv[2][1] == 's') &&
		(strncmp(argv[2], "-slave", length) == 0)) {
		Tk_Window tkwin;

		if (argc != 4) {
		    Tcl_AppendResult(interp, "needs widget argument for \"",
			argv[2], "\"", (char *)NULL);
		    return TCL_ERROR;
		}
		tkwin = Tk_NameToWindow(interp, argv[3],
		    Tk_MainWindow(interp));
		if (tkwin == NULL) {
		    return TCL_ERROR;
		}
		for (hPtr = Blt_FirstHashEntry(&(dataPtr->tableTable), &cursor);
		    hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) {
		    tablePtr = (Table *)Blt_GetHashValue(hPtr);
		    if (FindEntry(tablePtr, tkwin) != NULL) {
			Tcl_AppendElement(interp, Tk_PathName(tablePtr->tkwin));
		    }
		}
		return TCL_OK;
	    } else {
		Tcl_AppendResult(interp, "bad switch \"", argv[2], "\" : \
should be \"-pattern\", or \"-slave\"", (char *)NULL);
		return TCL_ERROR;
	    }
	} else {
	    pattern = argv[2];
	}
    }
  search:
    for (hPtr = Blt_FirstHashEntry(&(dataPtr->tableTable), &cursor);
	hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) {
	tablePtr = (Table *)Blt_GetHashValue(hPtr);
	if (tablePtr->interp == interp) {
	    if ((pattern == NULL) ||
		(Tcl_StringMatch(Tk_PathName(tablePtr->tkwin), pattern))) {
		Tcl_AppendElement(interp, Tk_PathName(tablePtr->tkwin));
	    }
	}
    }
    return TCL_OK;
}

/*
 * ----------------------------------------------------------------------------
 *
 * SaveOp --
 *
 *	Returns a list of all the commands necessary to rebuild the
 *	the table.  This includes the layout of the widgets and any
 *	row, column, or table options set.
 *
 * Results:
 *	Returns a standard Tcl result.  If no error occurred, TCL_OK is
 *	returned and a list of widget path names is left in interp->result.
 *
 * ----------------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
SaveOp(dataPtr, interp, argc, argv)
    TableInterpData *dataPtr;	/* Interpreter-specific data. */
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    Table *tablePtr;
    Blt_ChainLink *linkPtr, *lastPtr;
    Entry *entryPtr;
    PartitionInfo *infoPtr;
    RowColumn *rcPtr;
    Tcl_DString dString;
    int start, last;

    if (Blt_GetTable(dataPtr, interp, argv[2], &tablePtr) != TCL_OK) {
	return TCL_ERROR;
    }
    Tcl_DStringInit(&dString);
    Tcl_DStringAppend(&dString, "\n# Table widget layout\n\n", -1);
    Tcl_DStringAppend(&dString, argv[0], -1);
    Tcl_DStringAppend(&dString, " ", -1);
    Tcl_DStringAppend(&dString, Tk_PathName(tablePtr->tkwin), -1);
    Tcl_DStringAppend(&dString, " \\\n", -1);
    lastPtr = Blt_ChainLastLink(tablePtr->chainPtr);
    for (linkPtr = Blt_ChainFirstLink(tablePtr->chainPtr); linkPtr != NULL;
	linkPtr = Blt_ChainNextLink(linkPtr)) {
	entryPtr = Blt_ChainGetValue(linkPtr);
	PrintEntry(entryPtr, &dString);
	if (linkPtr != lastPtr) {
	    Tcl_DStringAppend(&dString, " \\\n", -1);
	}
    }
    Tcl_DStringAppend(&dString, "\n\n# Row configuration options\n\n", -1);
    infoPtr = &(tablePtr->rowInfo);
    for (linkPtr = Blt_ChainFirstLink(infoPtr->chainPtr); linkPtr != NULL;
	linkPtr = Blt_ChainNextLink(linkPtr)) {
	rcPtr = Blt_ChainGetValue(linkPtr);
	start = Tcl_DStringLength(&dString);
	Tcl_DStringAppend(&dString, argv[0], -1);
	Tcl_DStringAppend(&dString, " configure ", -1);
	Tcl_DStringAppend(&dString, Tk_PathName(tablePtr->tkwin), -1);
	Tcl_DStringAppend(&dString, " r", -1);
	Tcl_DStringAppend(&dString, Blt_Itoa(rcPtr->index), -1);
	last = Tcl_DStringLength(&dString);
	PrintRowColumn(interp, infoPtr, rcPtr, &dString);
	if (Tcl_DStringLength(&dString) == last) {
	    Tcl_DStringSetLength(&dString, start);
	} else {
	    Tcl_DStringAppend(&dString, "\n", -1);
	}
    }
    Tcl_DStringAppend(&dString, "\n\n# Column configuration options\n\n", -1);
    infoPtr = &(tablePtr->columnInfo);
    for (linkPtr = Blt_ChainFirstLink(infoPtr->chainPtr); linkPtr != NULL;
	linkPtr = Blt_ChainNextLink(linkPtr)) {
	rcPtr = Blt_ChainGetValue(linkPtr);
	start = Tcl_DStringLength(&dString);
	Tcl_DStringAppend(&dString, argv[0], -1);
	Tcl_DStringAppend(&dString, " configure ", -1);
	Tcl_DStringAppend(&dString, Tk_PathName(tablePtr->tkwin), -1);
	Tcl_DStringAppend(&dString, " c", -1);
	Tcl_DStringAppend(&dString, Blt_Itoa(rcPtr->index), -1);
	last = Tcl_DStringLength(&dString);
	PrintRowColumn(interp, infoPtr, rcPtr, &dString);
	if (Tcl_DStringLength(&dString) == last) {
	    Tcl_DStringSetLength(&dString, start);
	} else {
	    Tcl_DStringAppend(&dString, "\n", -1);
	}
    }
    start = Tcl_DStringLength(&dString);
    Tcl_DStringAppend(&dString, "\n\n# Table configuration options\n\n", -1);
    Tcl_DStringAppend(&dString, argv[0], -1);
    Tcl_DStringAppend(&dString, " configure ", -1);
    Tcl_DStringAppend(&dString, Tk_PathName(tablePtr->tkwin), -1);
    last = Tcl_DStringLength(&dString);
    PrintTable(tablePtr, &dString);
    if (Tcl_DStringLength(&dString) == last) {
	Tcl_DStringSetLength(&dString, start);
    } else {
	Tcl_DStringAppend(&dString, "\n", -1);
    }
    Tcl_DStringResult(interp, &dString);
    return TCL_OK;
}

/*
 * ----------------------------------------------------------------------------
 *
 * SearchOp --
 *
 *	Returns a list of all the pathnames of the widgets managed by
 *	a table geometry manager.  The table is given by the path name of a
 *	container widget associated with the table.
 *
 * Results:
 *	Returns a standard Tcl result.  If no error occurred, TCL_OK is
 *	returned and a list of widget path names is left in interp->result.
 *
 * ----------------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
SearchOp(dataPtr, interp, argc, argv)
    TableInterpData *dataPtr;	/* Interpreter-specific data. */
    Tcl_Interp *interp;		/* Interpreter to return list of names to */
    int argc;			/* Number of arguments */
    char **argv;		/* Contains 1-2 arguments: pathname of container
				 * widget associated with the table and search
				 * pattern */
{
    Table *tablePtr;
    Blt_ChainLink *linkPtr;
    Entry *entryPtr;
    int rspan, cspan, rstart, cstart;
    char *pattern;
    char c;
    int flags;
    register int i;

#define	MATCH_PATTERN		(1<<0)	/* Find widgets whose path names
					 * match a given pattern */
#define	MATCH_INDEX_SPAN	(1<<1)	/* Find widgets that span index  */
#define	MATCH_INDEX_START	(1<<2)	/* Find widgets that start at index */


    if (Blt_GetTable(dataPtr, interp, argv[2], &tablePtr) != TCL_OK) {
	return TCL_ERROR;
    }
    flags = 0;
    pattern = NULL;
    rspan = cspan = rstart = cstart = 0;

    /* Parse switches and arguments first */
    for (i = 3; i < argc; i += 2) {
	if (argv[i][0] == '-') {
	    unsigned int length;

	    if ((i + 1) == argc) {
		Tcl_AppendResult(interp, "switch \"", argv[i], "\" needs value",
		    (char *)NULL);
		return TCL_ERROR;
	    }
	    length = strlen(argv[i]);
	    c = argv[i][1];
	    if ((c == 'p') && (length > 1) &&
		(strncmp(argv[3], "-pattern", length) == 0)) {
		flags |= MATCH_PATTERN;
		pattern = argv[4];
	    } else if ((c == 's') && (length > 2) &&
		(strncmp(argv[i], "-start", length) == 0)) {
		flags |= MATCH_INDEX_START;
		if (ParseItem(tablePtr, argv[i + 1],
			&rstart, &cstart) != TCL_OK) {
		    return TCL_ERROR;
		}
	    } else if ((c == 's') && (length > 2) &&
		(strncmp(argv[i], "-span", length) == 0)) {
		flags |= MATCH_INDEX_SPAN;
		if (ParseItem(tablePtr, argv[4],
			&rspan, &cspan) != TCL_OK) {
		    return TCL_ERROR;
		}
	    } else {
		Tcl_AppendResult(interp, "bad switch \"", argv[3], "\" : \
should be \"-pattern\", \"-span\", or \"-start\"", (char *)NULL);
		return TCL_ERROR;
	    }
	} else {
	    if ((i + 1) == argc) {
		pattern = argv[i];
		flags |= MATCH_PATTERN;
	    }
	}
    }

    /* Then try to match entries with the search criteria */

    for (linkPtr = Blt_ChainFirstLink(tablePtr->chainPtr); linkPtr != NULL;
	linkPtr = Blt_ChainNextLink(linkPtr)) {
	entryPtr = Blt_ChainGetValue(linkPtr);
	if ((flags & MATCH_PATTERN) && (pattern != NULL)) {
	    if (Tcl_StringMatch(Tk_PathName(entryPtr->tkwin), pattern)) {
		goto match;
	    }
	}
	if (flags & MATCH_INDEX_SPAN) {
	    if ((rspan >= 0) && ((entryPtr->row.rcPtr->index <= rspan) ||
		    ((entryPtr->row.rcPtr->index + entryPtr->row.span) > rspan))) {
		goto match;
	    }
	    if ((cspan >= 0) && ((entryPtr->column.rcPtr->index <= cspan) ||
		    ((entryPtr->column.rcPtr->index + entryPtr->column.span)
			> cspan))) {
		goto match;
	    }
	}
	if (flags & MATCH_INDEX_START) {
	    if ((rstart >= 0) && (entryPtr->row.rcPtr->index == rstart)) {
		goto match;
	    }
	    if ((cstart >= 0) && (entryPtr->column.rcPtr->index == cstart)) {
		goto match;
	    }
	}
	continue;
      match:
	Tcl_AppendElement(interp, Tk_PathName(entryPtr->tkwin));
    }
    return TCL_OK;
}

/*
 * ----------------------------------------------------------------------------
 *
 * Table operations.
 *
 * The fields for Blt_OpSpec are as follows:
 *
 *   - operation name
 *   - minimum number of characters required to disambiguate the operation name.
 *   - function associated with operation.
 *   - minimum number of arguments required.
 *   - maximum number of arguments allowed (0 indicates no limit).
 *   - usage string
 *
 * ----------------------------------------------------------------------------
 */
static Blt_OpSpec operSpecs[] =
{
    {"arrange", 1, (Blt_Op)ArrangeOp, 3, 3, "container",},
    {"cget", 2, (Blt_Op)CgetOp, 4, 5,
	"container ?row|column|widget? option",},
    {"configure", 3, (Blt_Op)ConfigureOp, 3, 0,
	"container ?row|column|widget?... ?option value?...",},
    {"containers", 3, (Blt_Op)ContainersOp, 2, 4, "?switch? ?arg?",},
    {"delete", 1, (Blt_Op)DeleteOp, 3, 0,
	"container row|column ?row|column?",},
    {"extents", 1, (Blt_Op)ExtentsOp, 4, 4,
	"container row|column|widget",},
    {"forget", 1, (Blt_Op)ForgetOp, 3, 0, "widget ?widget?...",},
    {"info", 3, (Blt_Op)InfoOp, 3, 0,
	"container ?row|column|widget?...",},
    {"insert", 3, (Blt_Op)InsertOp, 4, 6,
	"container ?-before|-after? row|column ?count?",},
    {"join", 1, (Blt_Op)JoinOp, 5, 5, "container first last",},
    {"locate", 2, (Blt_Op)LocateOp, 5, 5, "container x y",},
    {"save", 2, (Blt_Op)SaveOp, 3, 3, "container",},
    {"search", 2, (Blt_Op)SearchOp, 3, 0, "container ?switch arg?...",},
    {"split", 2, (Blt_Op)SplitOp, 4, 5, "container row|column div",},
};

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

/*
 * ----------------------------------------------------------------------------
 *
 * TableCmd --
 *
 *	This procedure is invoked to process the Tcl command that
 *	corresponds to the table geometry manager.  See the user
 *	documentation for details on what it does.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 * ----------------------------------------------------------------------------
 */
static int
TableCmd(clientData, interp, argc, argv)
    ClientData clientData;	/* Interpreter-specific data. */
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    TableInterpData *dataPtr = clientData;
    Blt_Op proc;
    int result;

    if ((argc > 1) && (argv[1][0] == '.')) {
	Table *tablePtr;

	if (Blt_GetTable(clientData, interp, argv[1], &tablePtr) != TCL_OK) {
	    Tcl_ResetResult(interp);
	    tablePtr = CreateTable(dataPtr, interp, argv[1]);
	    if (tablePtr == NULL) {
		return TCL_ERROR;
	    }
	}
	return BuildTable(tablePtr, interp, argc, argv);
    }
    proc = Blt_GetOp(interp, nSpecs, operSpecs, BLT_OP_ARG1, argc, argv, 0);
    if (proc == NULL) {
	return TCL_ERROR;
    }
    result = (*proc) (dataPtr, interp, argc, argv);
    return result;
}


/*
 * -----------------------------------------------------------------------
 *
 * TableInterpDeleteProc --
 *
 *	This is called when the interpreter hosting the table command 
 *	is destroyed.  
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Destroys all the hash table maintaining the names of the table
 *	geomtry managers.
 *
 * ------------------------------------------------------------------------
 */
/* ARGSUSED */
static void
TableInterpDeleteProc(clientData, interp)
    ClientData clientData;	/* Thread-specific data. */
    Tcl_Interp *interp;
{
    TableInterpData *dataPtr = clientData;
    Blt_HashEntry *hPtr;
    Blt_HashSearch cursor;
    Table *tablePtr;

    for (hPtr = Blt_FirstHashEntry(&(dataPtr->tableTable), &cursor);
	 hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) {
	tablePtr = (Table *)Blt_GetHashValue(hPtr);
	tablePtr->hashPtr = NULL;
	DestroyTable((DestroyData)tablePtr);
    }
    Blt_DeleteHashTable(&(dataPtr->tableTable));
    Tcl_DeleteAssocData(interp, TABLE_THREAD_KEY);
    Blt_Free(dataPtr);
}

static TableInterpData *
GetTableInterpData(interp)
     Tcl_Interp *interp;
{
    TableInterpData *dataPtr;
    Tcl_InterpDeleteProc *proc;

    dataPtr = (TableInterpData *)
	Tcl_GetAssocData(interp, TABLE_THREAD_KEY, &proc);
    if (dataPtr == NULL) {
	dataPtr = Blt_Malloc(sizeof(TableInterpData));
	assert(dataPtr);
	Tcl_SetAssocData(interp, TABLE_THREAD_KEY, TableInterpDeleteProc, 
		dataPtr);
	Blt_InitHashTable(&(dataPtr->tableTable), BLT_ONE_WORD_KEYS);
    }
    return dataPtr;
}


/*
 * ----------------------------------------------------------------------------
 *
 * Blt_TableInit --
 *
 *	This procedure is invoked to initialize the Tcl command that
 *	corresponds to the table geometry manager.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Creates the new command and adds an entry into a global Tcl
 *	associative array.
 *
 * ---------------------------------------------------------------------------
 */
int
Blt_TableInit(interp)
    Tcl_Interp *interp;
{
    static Blt_CmdSpec cmdSpec = {"blttable", TableCmd, };
    TableInterpData *dataPtr;

    dataPtr = GetTableInterpData(interp);
    cmdSpec.clientData = dataPtr;
    if (Blt_InitCmd(interp, "blt", &cmdSpec) == NULL) {
	return TCL_ERROR;
    }
    rowUid = (Blt_Uid)Tk_GetUid("row");
    columnUid = (Blt_Uid)Tk_GetUid("column");
    return TCL_OK;
}
