/*
 * tkGrid.c --
 *
 *	Grid based geometry manager.
 *
 * Copyright (c) 1996-1997 by Sun Microsystems, Inc.
 *
 * See the file "license.terms" for information on usage and redistribution of
 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
 */

#include "tkInt.h"

/*
 * Convenience Macros
 */

#ifdef MAX
#   undef MAX
#endif
#define MAX(x,y)	((x) > (y) ? (x) : (y))

#define COLUMN		(1)	/* Working on column offsets. */
#define ROW		(2)	/* Working on row offsets. */

#define CHECK_ONLY	(1)	/* Check max slot constraint. */
#define CHECK_SPACE	(2)	/* Alloc more space, don't change max. */

/*
 * Pre-allocate enough row and column slots for "typical" sized tables this
 * value should be chosen so by the time the extra malloc's are required, the
 * layout calculations overwehlm them. [A "slot" contains information for
 * either a row or column, depending upon the context.]
 */

#define TYPICAL_SIZE	25	/* (Arbitrary guess) */
#define PREALLOC	10	/* Extra slots to allocate. */

/*
 * Pre-allocate room for uniform groups during layout.
 */

#define UNIFORM_PREALLOC 10

/*
 * Data structures are allocated dynamically to support arbitrary sized
 * tables. However, the space is proportional to the highest numbered slot
 * with some non-default property. This limit is used to head off mistakes and
 * denial of service attacks by limiting the amount of storage required.
 */

#define MAX_ELEMENT	10000

/*
 * Special characters to support relative layouts.
 */

#define REL_SKIP	'x'	/* Skip this column. */
#define REL_HORIZ	'-'	/* Extend previous widget horizontally. */
#define REL_VERT	'^'	/* Extend widget from row above. */

/*
 * Default value for 'grid anchor'.
 */

#define GRID_DEFAULT_ANCHOR TK_ANCHOR_NW

/*
 * Structure to hold information for grid containers. A slot is either a row or
 * column.
 */

typedef struct SlotInfo {
    int minSize;		/* The minimum size of this slot (in pixels).
				 * It is set via the rowconfigure or
				 * columnconfigure commands. */
    int weight;			/* The resize weight of this slot. (0) means
				 * this slot doesn't resize. Extra space in
				 * the layout is given distributed among slots
				 * inproportion to their weights. */
    int pad;			/* Extra padding, in pixels, required for this
				 * slot. This amount is "added" to the largest
				 * content in the slot. */
    Tk_Uid uniform;		/* Value of -uniform option. It is used to
				 * group slots that should have the same
				 * size. */
    int offset;			/* This is a cached value used for
				 * introspection. It is the pixel offset of
				 * the right or bottom edge of this slot from
				 * the beginning of the layout. */
    int temp;			/* This is a temporary value used for
				 * calculating adjusted weights when shrinking
				 * the layout below its nominal size. */
} SlotInfo;

/*
 * Structure to hold information during layout calculations. There is one of
 * these for each slot, an array for each of the rows or columns.
 */

typedef struct GridLayout {
    struct Gridder *binNextPtr;	/* The next content window in this bin. Each bin
    				 * contains a list of all content whose spans
    				 * are >1 and whose right edges fall in this
    				 * slot. */
    int minSize;		/* Minimum size needed for this slot, in
    				 * pixels. This is the space required to hold
    				 * any content contained entirely in this slot,
    				 * adjusted for any slot constrants, such as
    				 * size or padding. */
    int pad;			/* Padding needed for this slot */
    int weight;			/* Slot weight, controls resizing. */
    Tk_Uid uniform;		/* Value of -uniform option. It is used to
				 * group slots that should have the same
				 * size. */
    int minOffset;		/* The minimum offset, in pixels, from the
    				 * beginning of the layout to the bottom/right
    				 * edge of the slot calculated from top/left
    				 * to bottom/right. */
    int maxOffset;		/* The maximum offset, in pixels, from the
    				 * beginning of the layout to the bottom/right
    				 * edge of the slot calculated from
    				 * bottom/right to top/left. */
} GridLayout;

/*
 * Keep one of these for each geometry container.
 */

typedef struct {
    SlotInfo *columnPtr;	/* Pointer to array of column constraints. */
    SlotInfo *rowPtr;		/* Pointer to array of row constraints. */
    int columnEnd;		/* The last column occupied by any content. */
    int columnMax;		/* The number of columns with constraints. */
    int columnSpace;		/* The number of slots currently allocated for
    				 * column constraints. */
    int rowEnd;			/* The last row occupied by any content. */
    int rowMax;			/* The number of rows with constraints. */
    int rowSpace;		/* The number of slots currently allocated for
    				 * row constraints. */
    int startX;			/* Pixel offset of this layout within its
    				 * container. */
    int startY;			/* Pixel offset of this layout within its
    				 * container. */
    Tk_Anchor anchor;		/* Value of anchor option: specifies where a
				 * grid without weight should be placed. */
} GridContainer;

/*
 * For each window that the grid cares about (either because the window is
 * managed by the grid or because the window has content that are managed by
 * the grid), there is a structure of the following type:
 */

typedef struct Gridder {
    Tk_Window tkwin;		/* Tk token for window. NULL means that the
				 * window has been deleted, but the gridder
				 * hasn't had a chance to clean up yet because
				 * the structure is still in use. */
    struct Gridder *containerPtr;	/* Container window within which this window is
				 * managed (NULL means this window isn't
				 * managed by the gridder). */
    struct Gridder *nextPtr;	/* Next window managed within same container.
				 * List order doesn't matter. */
    struct Gridder *contentPtr;	/* First in list of content managed inside this
				 * window (NULL means no grid content). */
    GridContainer *containerDataPtr;	/* Additional data for geometry container. */
    Tcl_Obj *in;                /* Store container name when removed. */
    int column, row;		/* Location in the grid (starting from
				 * zero). */
    int numCols, numRows;	/* Number of columns or rows this content spans.
				 * Should be at least 1. */
    int padX, padY;		/* Total additional pixels to leave around the
				 * window. Some is of this space is on each
				 * side. This is space *outside* the window:
				 * we'll allocate extra space in frame but
				 * won't enlarge window). */
    int padLeft, padTop;	/* The part of padX or padY to use on the left
				 * or top of the widget, respectively. By
				 * default, this is half of padX or padY. */
    int iPadX, iPadY;		/* Total extra pixels to allocate inside the
				 * window (half this amount will appear on
				 * each side). */
    int sticky;			/* which sides of its cavity this window
				 * sticks to. See below for definitions */
    int doubleBw;		/* Twice the window's last known border width.
				 * If this changes, the window must be
				 * re-arranged within its container. */
    int *abortPtr;		/* If non-NULL, it means that there is a
				 * nested call to ArrangeGrid already working
				 * on this window. *abortPtr may be set to 1
				 * to abort that nested call. This happens,
				 * for example, if tkwin or any of its content
				 * is deleted. */
    int flags;			/* Miscellaneous flags; see below for
				 * definitions. */

    /*
     * These fields are used temporarily for layout calculations only.
     */

    struct Gridder *binNextPtr;	/* Link to next span>1 content in this bin. */
    int size;			/* Nominal size (width or height) in pixels of
    				 * the content. This includes the padding. */
} Gridder;

/*
 * Flag values for "sticky"ness. The 16 combinations subsume the packer's
 * notion of anchor and fill.
 *
 * STICK_NORTH			This window sticks to the top of its cavity.
 * STICK_EAST			This window sticks to the right edge of its
 *				cavity.
 * STICK_SOUTH			This window sticks to the bottom of its cavity.
 * STICK_WEST			This window sticks to the left edge of its
 *				cavity.
 */

#define STICK_NORTH		1
#define STICK_EAST		2
#define STICK_SOUTH		4
#define STICK_WEST		8


/*
 * Structure to gather information about uniform groups during layout.
 */

typedef struct UniformGroup {
    Tk_Uid group;
    int minSize;
} UniformGroup;

/*
 * Flag values for Grid structures:
 *
 * REQUESTED_RELAYOUT		1 means a Tcl_DoWhenIdle request has already
 *				been made to re-arrange all the content of this
 *				window.
 * DONT_PROPAGATE		1 means don't set this window's requested
 *				size. 0 means if this window is a container then
 *				Tk will set its requested size to fit the
 *				needs of its content.
 * ALLOCED_CONTAINER		1 means that Grid has allocated itself as
 *				geometry container for this window.
 */

#define REQUESTED_RELAYOUT	1
#define DONT_PROPAGATE		2
#define ALLOCED_CONTAINER	4

/*
 * Prototypes for procedures used only in this file:
 */

static void		AdjustForSticky(Gridder *contentPtr, int *xPtr,
			    int *yPtr, int *widthPtr, int *heightPtr);
static int		AdjustOffsets(int width, int elements,
			    SlotInfo *slotPtr);
static void		ArrangeGrid(ClientData clientData);
static int		CheckSlotData(Gridder *containerPtr, int slot,
			    int slotType, int checkOnly);
static int		ConfigureContent(Tcl_Interp *interp, Tk_Window tkwin,
			    int objc, Tcl_Obj *const objv[]);
static void		DestroyGrid(void *memPtr);
static Gridder *	GetGrid(Tk_Window tkwin);
static int		GridAnchorCommand(Tk_Window tkwin, Tcl_Interp *interp,
			    int objc, Tcl_Obj *const objv[]);
static int		GridBboxCommand(Tk_Window tkwin, Tcl_Interp *interp,
			    int objc, Tcl_Obj *const objv[]);
static int		GridForgetRemoveCommand(Tk_Window tkwin,
			    Tcl_Interp *interp, int objc,
			    Tcl_Obj *const objv[]);
static int		GridInfoCommand(Tk_Window tkwin, Tcl_Interp *interp,
			    int objc, Tcl_Obj *const objv[]);
static int		GridLocationCommand(Tk_Window tkwin,
			    Tcl_Interp *interp, int objc,
			    Tcl_Obj *const objv[]);
static int		GridPropagateCommand(Tk_Window tkwin,
			    Tcl_Interp *interp, int objc,
			    Tcl_Obj *const objv[]);
static int		GridRowColumnConfigureCommand(Tk_Window tkwin,
			    Tcl_Interp *interp, int objc,
			    Tcl_Obj *const objv[]);
static int		GridSizeCommand(Tk_Window tkwin, Tcl_Interp *interp,
			    int objc, Tcl_Obj *const objv[]);
static int		GridContentCommand(Tk_Window tkwin, Tcl_Interp *interp,
			    int objc, Tcl_Obj *const objv[]);
static void		GridStructureProc(ClientData clientData,
			    XEvent *eventPtr);
static void		GridLostContentProc(ClientData clientData,
			    Tk_Window tkwin);
static void		GridReqProc(ClientData clientData, Tk_Window tkwin);
static void		InitContainerData(Gridder *containerPtr);
static Tcl_Obj *	NewPairObj(int, int);
static Tcl_Obj *	NewQuadObj(int, int, int, int);
static int		ResolveConstraints(Gridder *gridPtr, int rowOrColumn,
			    int maxOffset);
static void		SetGridSize(Gridder *gridPtr);
static int		SetContentColumn(Tcl_Interp *interp, Gridder *contentPtr,
			    int column, int numCols);
static int		SetContentRow(Tcl_Interp *interp, Gridder *contentPtr,
			    int row, int numRows);
static Tcl_Obj *	StickyToObj(int flags);
static int		StringToSticky(const char *string);
static void		Unlink(Gridder *gridPtr);

static const Tk_GeomMgr gridMgrType = {
    "grid",			/* name */
    GridReqProc,		/* requestProc */
    GridLostContentProc,		/* lostSlaveProc */
};

/*
 *----------------------------------------------------------------------
 *
 * Tk_GridCmd --
 *
 *	This procedure is invoked to process the "grid" Tcl command. See the
 *	user documentation for details on what it does.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 *----------------------------------------------------------------------
 */

int
Tk_GridObjCmd(
    ClientData clientData,	/* Main window associated with interpreter. */
    Tcl_Interp *interp,		/* Current interpreter. */
    int objc,			/* Number of arguments. */
    Tcl_Obj *const objv[])	/* Argument objects. */
{
    Tk_Window tkwin = (Tk_Window)clientData;
    static const char *const optionStrings[] = {
	"anchor", "bbox", "columnconfigure", "configure",
	"content", "forget", "info", "location", "propagate",
	"remove", "rowconfigure", "size", "slaves", NULL
    };
    enum options {
	GRID_ANCHOR, GRID_BBOX, GRID_COLUMNCONFIGURE, GRID_CONFIGURE,
	GRID_CONTENT, GRID_FORGET, GRID_INFO, GRID_LOCATION, GRID_PROPAGATE,
	GRID_REMOVE, GRID_ROWCONFIGURE, GRID_SIZE, GRID_SLAVES
    };
    int index;

    if (objc >= 2) {
	const char *argv1 = Tcl_GetString(objv[1]);

	if ((argv1[0] == '.') || (argv1[0] == REL_SKIP) ||
    		(argv1[0] == REL_VERT)) {
	    return ConfigureContent(interp, tkwin, objc-1, objv+1);
	}
    }
    if (objc < 3) {
	Tcl_WrongNumArgs(interp, 1, objv, "option arg ?arg ...?");
	return TCL_ERROR;
    }

    if (Tcl_GetIndexFromObjStruct(interp, objv[1], optionStrings,
	    sizeof(char *), "option", 0, &index) != TCL_OK) {
	return TCL_ERROR;
    }

    switch ((enum options) index) {
    case GRID_ANCHOR:
	return GridAnchorCommand(tkwin, interp, objc, objv);
    case GRID_BBOX:
	return GridBboxCommand(tkwin, interp, objc, objv);
    case GRID_CONFIGURE:
	return ConfigureContent(interp, tkwin, objc-2, objv+2);
    case GRID_FORGET:
    case GRID_REMOVE:
	return GridForgetRemoveCommand(tkwin, interp, objc, objv);
    case GRID_INFO:
	return GridInfoCommand(tkwin, interp, objc, objv);
    case GRID_LOCATION:
	return GridLocationCommand(tkwin, interp, objc, objv);
    case GRID_PROPAGATE:
	return GridPropagateCommand(tkwin, interp, objc, objv);
    case GRID_SIZE:
	return GridSizeCommand(tkwin, interp, objc, objv);
    case GRID_SLAVES:
    case GRID_CONTENT:
	return GridContentCommand(tkwin, interp, objc, objv);

    /*
     * Sample argument combinations:
     *  grid columnconfigure <container> <index> -option
     *  grid columnconfigure <container> <index> -option value -option value
     *  grid rowconfigure <container> <index>
     *  grid rowconfigure <container> <index> -option
     *  grid rowconfigure <container> <index> -option value -option value.
     */

    case GRID_COLUMNCONFIGURE:
    case GRID_ROWCONFIGURE:
	return GridRowColumnConfigureCommand(tkwin, interp, objc, objv);
    }

    /* This should not happen */
    Tcl_SetObjResult(interp, Tcl_NewStringObj("internal error in grid", -1));
    Tcl_SetErrorCode(interp, "TK", "API_ABUSE", NULL);
    return TCL_ERROR;
}

/*
 *----------------------------------------------------------------------
 *
 * GridAnchorCommand --
 *
 *	Implementation of the [grid anchor] subcommand. See the user
 *	documentation for details on what it does.
 *
 * Results:
 *	Standard Tcl result.
 *
 * Side effects:
 *	May recompute grid geometry.
 *
 *----------------------------------------------------------------------
 */

static int
GridAnchorCommand(
    Tk_Window tkwin,		/* Main window of the application. */
    Tcl_Interp *interp,		/* Current interpreter. */
    int objc,			/* Number of arguments. */
    Tcl_Obj *const objv[])	/* Argument objects. */
{
    Tk_Window container;
    Gridder *containerPtr;
    GridContainer *gridPtr;
    Tk_Anchor old;

    if (objc > 4) {
	Tcl_WrongNumArgs(interp, 2, objv, "window ?anchor?");
	return TCL_ERROR;
    }

    if (TkGetWindowFromObj(interp, tkwin, objv[2], &container) != TCL_OK) {
	return TCL_ERROR;
    }
    containerPtr = GetGrid(container);

    if (objc == 3) {
	gridPtr = containerPtr->containerDataPtr;
	Tcl_SetObjResult(interp, Tcl_NewStringObj(
		Tk_NameOfAnchor(gridPtr?gridPtr->anchor:GRID_DEFAULT_ANCHOR),
		-1));
	return TCL_OK;
    }

    InitContainerData(containerPtr);
    gridPtr = containerPtr->containerDataPtr;
    old = gridPtr->anchor;
    if (Tk_GetAnchorFromObj(interp, objv[3], &gridPtr->anchor) != TCL_OK) {
	return TCL_ERROR;
    }

    /*
     * Only request a relayout if the anchor changes.
     */

    if (old != gridPtr->anchor) {
	if (containerPtr->abortPtr != NULL) {
	    *containerPtr->abortPtr = 1;
	}
	if (!(containerPtr->flags & REQUESTED_RELAYOUT)) {
	    containerPtr->flags |= REQUESTED_RELAYOUT;
	    Tcl_DoWhenIdle(ArrangeGrid, containerPtr);
	}
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * GridBboxCommand --
 *
 *	Implementation of the [grid bbox] subcommand.
 *
 * Results:
 *	Standard Tcl result.
 *
 * Side effects:
 *	Places bounding box information in the interp's result field.
 *
 *----------------------------------------------------------------------
 */

static int
GridBboxCommand(
    Tk_Window tkwin,		/* Main window of the application. */
    Tcl_Interp *interp,		/* Current interpreter. */
    int objc,			/* Number of arguments. */
    Tcl_Obj *const objv[])	/* Argument objects. */
{
    Tk_Window container;
    Gridder *containerPtr;		/* container grid record */
    GridContainer *gridPtr;	/* pointer to grid data */
    int row, column;		/* origin for bounding box */
    int row2, column2;		/* end of bounding box */
    int endX, endY;		/* last column/row in the layout */
    int x=0, y=0;		/* starting pixels for this bounding box */
    int width, height;		/* size of the bounding box */

    if (objc!=3 && objc != 5 && objc != 7) {
	Tcl_WrongNumArgs(interp, 2, objv, "master ?column row ?column row??");
	return TCL_ERROR;
    }

    if (TkGetWindowFromObj(interp, tkwin, objv[2], &container) != TCL_OK) {
	return TCL_ERROR;
    }
    containerPtr = GetGrid(container);

    if (objc >= 5) {
	if (Tcl_GetIntFromObj(interp, objv[3], &column) != TCL_OK) {
	    return TCL_ERROR;
	}
	if (Tcl_GetIntFromObj(interp, objv[4], &row) != TCL_OK) {
	    return TCL_ERROR;
	}
	column2 = column;
	row2 = row;
    }

    if (objc == 7) {
	if (Tcl_GetIntFromObj(interp, objv[5], &column2) != TCL_OK) {
	    return TCL_ERROR;
	}
	if (Tcl_GetIntFromObj(interp, objv[6], &row2) != TCL_OK) {
	    return TCL_ERROR;
	}
    }

    gridPtr = containerPtr->containerDataPtr;
    if (gridPtr == NULL) {
	Tcl_SetObjResult(interp, NewQuadObj(0, 0, 0, 0));
	return TCL_OK;
    }

    SetGridSize(containerPtr);
    endX = MAX(gridPtr->columnEnd, gridPtr->columnMax);
    endY = MAX(gridPtr->rowEnd, gridPtr->rowMax);

    if ((endX == 0) || (endY == 0)) {
	Tcl_SetObjResult(interp, NewQuadObj(0, 0, 0, 0));
	return TCL_OK;
    }
    if (objc == 3) {
	row = 0;
	column = 0;
	row2 = endY;
	column2 = endX;
    }

    if (column > column2) {
	int temp = column;

	column = column2;
	column2 = temp;
    }
    if (row > row2) {
	int temp = row;

	row = row2;
	row2 = temp;
    }

    if (column > 0 && column < endX) {
	x = gridPtr->columnPtr[column-1].offset;
    } else if (column > 0) {
	x = gridPtr->columnPtr[endX-1].offset;
    }

    if (row > 0 && row < endY) {
	y = gridPtr->rowPtr[row-1].offset;
    } else if (row > 0) {
	y = gridPtr->rowPtr[endY-1].offset;
    }

    if (column2 < 0) {
	width = 0;
    } else if (column2 >= endX) {
	width = gridPtr->columnPtr[endX-1].offset - x;
    } else {
	width = gridPtr->columnPtr[column2].offset - x;
    }

    if (row2 < 0) {
	height = 0;
    } else if (row2 >= endY) {
	height = gridPtr->rowPtr[endY-1].offset - y;
    } else {
	height = gridPtr->rowPtr[row2].offset - y;
    }

    Tcl_SetObjResult(interp, NewQuadObj(
	    x + gridPtr->startX, y + gridPtr->startY, width, height));
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * GridForgetRemoveCommand --
 *
 *	Implementation of the [grid forget]/[grid remove] subcommands. See the
 *	user documentation for details on what these do.
 *
 * Results:
 *	Standard Tcl result.
 *
 * Side effects:
 *	Removes a window from a grid layout.
 *
 *----------------------------------------------------------------------
 */

static int
GridForgetRemoveCommand(
    Tk_Window tkwin,		/* Main window of the application. */
    Tcl_Interp *interp,		/* Current interpreter. */
    int objc,			/* Number of arguments. */
    Tcl_Obj *const objv[])	/* Argument objects. */
{
    Tk_Window content;
    Gridder *contentPtr;
    int i;
    const char *string = Tcl_GetString(objv[1]);
    char c = string[0];

    for (i = 2; i < objc; i++) {
	if (TkGetWindowFromObj(interp, tkwin, objv[i], &content) != TCL_OK) {
	    return TCL_ERROR;
	}

	contentPtr = GetGrid(content);
	if (contentPtr->containerPtr != NULL) {
	    /*
	     * For "forget", reset all the settings to their defaults
	     */

	    if (c == 'f') {
		contentPtr->column = -1;
		contentPtr->row = -1;
		contentPtr->numCols = 1;
		contentPtr->numRows = 1;
		contentPtr->padX = 0;
		contentPtr->padY = 0;
		contentPtr->padLeft = 0;
		contentPtr->padTop = 0;
		contentPtr->iPadX = 0;
		contentPtr->iPadY = 0;
		if (contentPtr->in != NULL) {
		    Tcl_DecrRefCount(contentPtr->in);
		    contentPtr->in = NULL;
		}
		contentPtr->doubleBw = 2*Tk_Changes(tkwin)->border_width;
		if (contentPtr->flags & REQUESTED_RELAYOUT) {
		    Tcl_CancelIdleCall(ArrangeGrid, contentPtr);
		}
		contentPtr->flags = 0;
		contentPtr->sticky = 0;
	    } else {
		/*
		 * When removing, store name of container to be able to
		 * restore it later, even if the container is recreated.
		 */

		if (contentPtr->in != NULL) {
		    Tcl_DecrRefCount(contentPtr->in);
		    contentPtr->in = NULL;
		}
		if (contentPtr->containerPtr != NULL) {
		    contentPtr->in = Tcl_NewStringObj(
			    Tk_PathName(contentPtr->containerPtr->tkwin), -1);
		    Tcl_IncrRefCount(contentPtr->in);
		}
	    }
	    Tk_ManageGeometry(content, NULL, NULL);
	    if (contentPtr->containerPtr->tkwin != Tk_Parent(contentPtr->tkwin)) {
		Tk_UnmaintainGeometry(contentPtr->tkwin,
			contentPtr->containerPtr->tkwin);
	    }
	    Unlink(contentPtr);
	    Tk_UnmapWindow(contentPtr->tkwin);
	}
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * GridInfoCommand --
 *
 *	Implementation of the [grid info] subcommand. See the user
 *	documentation for details on what it does.
 *
 * Results:
 *	Standard Tcl result.
 *
 * Side effects:
 *	Puts gridding information in the interpreter's result.
 *
 *----------------------------------------------------------------------
 */

static int
GridInfoCommand(
    Tk_Window tkwin,		/* Main window of the application. */
    Tcl_Interp *interp,		/* Current interpreter. */
    int objc,			/* Number of arguments. */
    Tcl_Obj *const objv[])	/* Argument objects. */
{
    Gridder *contentPtr;
    Tk_Window content;
    Tcl_Obj *infoObj;

    if (objc != 3) {
	Tcl_WrongNumArgs(interp, 2, objv, "window");
	return TCL_ERROR;
    }
    if (TkGetWindowFromObj(interp, tkwin, objv[2], &content) != TCL_OK) {
	return TCL_ERROR;
    }
    contentPtr = GetGrid(content);
    if (contentPtr->containerPtr == NULL) {
	Tcl_ResetResult(interp);
	return TCL_OK;
    }

    infoObj = Tcl_NewObj();
    Tcl_DictObjPut(NULL, infoObj, Tcl_NewStringObj("-in", -1),
	    TkNewWindowObj(contentPtr->containerPtr->tkwin));
    Tcl_DictObjPut(NULL, infoObj, Tcl_NewStringObj("-column", -1),
	    Tcl_NewIntObj(contentPtr->column));
    Tcl_DictObjPut(NULL, infoObj, Tcl_NewStringObj("-row", -1),
	    Tcl_NewIntObj(contentPtr->row));
    Tcl_DictObjPut(NULL, infoObj, Tcl_NewStringObj("-columnspan", -1),
	    Tcl_NewIntObj(contentPtr->numCols));
    Tcl_DictObjPut(NULL, infoObj, Tcl_NewStringObj("-rowspan", -1),
	    Tcl_NewIntObj(contentPtr->numRows));
    TkAppendPadAmount(infoObj, "-ipadx", contentPtr->iPadX/2, contentPtr->iPadX);
    TkAppendPadAmount(infoObj, "-ipady", contentPtr->iPadY/2, contentPtr->iPadY);
    TkAppendPadAmount(infoObj, "-padx", contentPtr->padLeft, contentPtr->padX);
    TkAppendPadAmount(infoObj, "-pady", contentPtr->padTop, contentPtr->padY);
    Tcl_DictObjPut(NULL, infoObj, Tcl_NewStringObj("-sticky", -1),
	    StickyToObj(contentPtr->sticky));
    Tcl_SetObjResult(interp, infoObj);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * GridLocationCommand --
 *
 *	Implementation of the [grid location] subcommand. See the user
 *	documentation for details on what it does.
 *
 * Results:
 *	Standard Tcl result.
 *
 * Side effects:
 *	Puts location information in the interpreter's result field.
 *
 *----------------------------------------------------------------------
 */

static int
GridLocationCommand(
    Tk_Window tkwin,		/* Main window of the application. */
    Tcl_Interp *interp,		/* Current interpreter. */
    int objc,			/* Number of arguments. */
    Tcl_Obj *const objv[])	/* Argument objects. */
{
    Tk_Window container;
    Gridder *containerPtr;		/* Container grid record. */
    GridContainer *gridPtr;	/* Pointer to grid data. */
    SlotInfo *slotPtr;
    int x, y;			/* Offset in pixels, from edge of container. */
    int i, j;			/* Corresponding column and row indeces. */
    int endX, endY;		/* End of grid. */

    if (objc != 5) {
	Tcl_WrongNumArgs(interp, 2, objv, "master x y");
	return TCL_ERROR;
    }

    if (TkGetWindowFromObj(interp, tkwin, objv[2], &container) != TCL_OK) {
	return TCL_ERROR;
    }

    if (Tk_GetPixelsFromObj(interp, container, objv[3], &x) != TCL_OK) {
	return TCL_ERROR;
    }
    if (Tk_GetPixelsFromObj(interp, container, objv[4], &y) != TCL_OK) {
	return TCL_ERROR;
    }

    containerPtr = GetGrid(container);
    if (containerPtr->containerDataPtr == NULL) {
	Tcl_SetObjResult(interp, NewPairObj(-1, -1));
	return TCL_OK;
    }
    gridPtr = containerPtr->containerDataPtr;

    /*
     * Update any pending requests. This is not always the steady state value,
     * as more configure events could be in the pipeline, but its as close as
     * its easy to get.
     */

    while (containerPtr->flags & REQUESTED_RELAYOUT) {
	Tcl_CancelIdleCall(ArrangeGrid, containerPtr);
	ArrangeGrid(containerPtr);
    }
    SetGridSize(containerPtr);
    endX = MAX(gridPtr->columnEnd, gridPtr->columnMax);
    endY = MAX(gridPtr->rowEnd, gridPtr->rowMax);

    slotPtr = containerPtr->containerDataPtr->columnPtr;
    if (x < containerPtr->containerDataPtr->startX) {
	i = -1;
    } else {
	x -= containerPtr->containerDataPtr->startX;
	for (i = 0; slotPtr[i].offset < x && i < endX; i++) {
	    /* null body */
	}
    }

    slotPtr = containerPtr->containerDataPtr->rowPtr;
    if (y < containerPtr->containerDataPtr->startY) {
	j = -1;
    } else {
	y -= containerPtr->containerDataPtr->startY;
	for (j = 0; slotPtr[j].offset < y && j < endY; j++) {
	    /* null body */
	}
    }

    Tcl_SetObjResult(interp, NewPairObj(i, j));
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * GridPropagateCommand --
 *
 *	Implementation of the [grid propagate] subcommand. See the user
 *	documentation for details on what it does.
 *
 * Results:
 *	Standard Tcl result.
 *
 * Side effects:
 *	May alter geometry propagation for a widget.
 *
 *----------------------------------------------------------------------
 */

static int
GridPropagateCommand(
    Tk_Window tkwin,		/* Main window of the application. */
    Tcl_Interp *interp,		/* Current interpreter. */
    int objc,			/* Number of arguments. */
    Tcl_Obj *const objv[])	/* Argument objects. */
{
    Tk_Window container;
    Gridder *containerPtr;
    int propagate, old;

    if (objc > 4) {
	Tcl_WrongNumArgs(interp, 2, objv, "window ?boolean?");
	return TCL_ERROR;
    }

    if (TkGetWindowFromObj(interp, tkwin, objv[2], &container) != TCL_OK) {
	return TCL_ERROR;
    }
    containerPtr = GetGrid(container);
    if (objc == 3) {
	Tcl_SetObjResult(interp,
		Tcl_NewBooleanObj(!(containerPtr->flags & DONT_PROPAGATE)));
	return TCL_OK;
    }
    if (Tcl_GetBooleanFromObj(interp, objv[3], &propagate) != TCL_OK) {
	return TCL_ERROR;
    }

    /*
     * Only request a relayout if the propagation bit changes.
     */

    old = !(containerPtr->flags & DONT_PROPAGATE);
    if (propagate != old) {
	if (propagate) {
	    /*
	     * If we have content, we need to register as geometry container.
	     */

	    if (containerPtr->contentPtr != NULL) {
		if (TkSetGeometryContainer(interp, container, "grid")	!= TCL_OK) {
		    return TCL_ERROR;
		}
		containerPtr->flags |= ALLOCED_CONTAINER;
	    }
	    containerPtr->flags &= ~DONT_PROPAGATE;
	} else {
	    if (containerPtr->flags & ALLOCED_CONTAINER) {
		TkFreeGeometryContainer(container, "grid");
		containerPtr->flags &= ~ALLOCED_CONTAINER;
	    }
	    containerPtr->flags |= DONT_PROPAGATE;
	}

	/*
	 * Re-arrange the container to allow new geometry information to
	 * propagate upwards to the container's container.
	 */

	if (containerPtr->abortPtr != NULL) {
	    *containerPtr->abortPtr = 1;
	}
	if (!(containerPtr->flags & REQUESTED_RELAYOUT)) {
	    containerPtr->flags |= REQUESTED_RELAYOUT;
	    Tcl_DoWhenIdle(ArrangeGrid, containerPtr);
	}
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * GridRowColumnConfigureCommand --
 *
 *	Implementation of the [grid rowconfigure] and [grid columnconfigure]
 *	subcommands. See the user documentation for details on what these do.
 *
 * Results:
 *	Standard Tcl result.
 *
 * Side effects:
 *	Depends on arguments; see user documentation.
 *
 *----------------------------------------------------------------------
 */

static int
GridRowColumnConfigureCommand(
    Tk_Window tkwin,		/* Main window of the application. */
    Tcl_Interp *interp,		/* Current interpreter. */
    int objc,			/* Number of arguments. */
    Tcl_Obj *const objv[])	/* Argument objects. */
{
    Tk_Window container, content;
    Gridder *containerPtr, *contentPtr;
    SlotInfo *slotPtr = NULL;
    int slot;			/* the column or row number */
    int slotType;		/* COLUMN or ROW */
    int size;			/* the configuration value */
    int lObjc;			/* Number of items in index list */
    Tcl_Obj **lObjv;		/* array of indices */
    int ok;			/* temporary TCL result code */
    int i, j, first, last;
    const char *string;
    static const char *const optionStrings[] = {
	"-minsize", "-pad", "-uniform", "-weight", NULL
    };
    enum options {
	ROWCOL_MINSIZE, ROWCOL_PAD, ROWCOL_UNIFORM, ROWCOL_WEIGHT
    };
    int index;
    Tcl_Obj *listCopy;

    if (((objc % 2 != 0) && (objc > 6)) || (objc < 4)) {
	Tcl_WrongNumArgs(interp, 2, objv, "master index ?-option value ...?");
	return TCL_ERROR;
    }

    if (TkGetWindowFromObj(interp, tkwin, objv[2], &container) != TCL_OK) {
	return TCL_ERROR;
    }

    listCopy = Tcl_DuplicateObj(objv[3]);
    Tcl_IncrRefCount(listCopy);
    if (Tcl_ListObjGetElements(interp, listCopy, &lObjc, &lObjv) != TCL_OK) {
	Tcl_DecrRefCount(listCopy);
	return TCL_ERROR;
    }

    string = Tcl_GetString(objv[1]);
    slotType = (*string == 'c') ? COLUMN : ROW;
    if (lObjc == 0) {
	Tcl_SetObjResult(interp, Tcl_ObjPrintf("no %s indices specified",
		(slotType == COLUMN) ? "column" : "row"));
	Tcl_SetErrorCode(interp, "TK", "GRID", "NO_INDEX", NULL);
	Tcl_DecrRefCount(listCopy);
	return TCL_ERROR;
    }

    containerPtr = GetGrid(container);
    first = 0;
    last = 0;

    if ((objc == 4) || (objc == 5)) {
	if (lObjc != 1) {
	    Tcl_SetObjResult(interp, Tcl_NewStringObj(
		    "must specify a single element on retrieval", -1));
	    Tcl_SetErrorCode(interp, "TK", "GRID", "USAGE", NULL);
	    Tcl_DecrRefCount(listCopy);
	    return TCL_ERROR;
	}
	if (Tcl_GetIntFromObj(interp, lObjv[0], &slot) != TCL_OK) {
	    Tcl_AppendResult(interp,
		    " (when retrieving options only integer indices are "
		    "allowed)", NULL);
	    Tcl_SetErrorCode(interp, "TK", "GRID", "INDEX_FORMAT", NULL);
	    Tcl_DecrRefCount(listCopy);
	    return TCL_ERROR;
	}
	ok = CheckSlotData(containerPtr, slot, slotType, /* checkOnly */ 1);
	if (ok == TCL_OK) {
	    slotPtr = (slotType == COLUMN) ?
		    containerPtr->containerDataPtr->columnPtr :
		    containerPtr->containerDataPtr->rowPtr;
	}

	/*
	 * Return all of the options for this row or column. If the request is
	 * out of range, return all 0's.
	 */

	if (objc == 4) {
	    int minsize = 0, pad = 0, weight = 0;
	    Tk_Uid uniform = NULL;
	    Tcl_Obj *res = Tcl_NewListObj(0, NULL);

	    if (ok == TCL_OK) {
		minsize = slotPtr[slot].minSize;
		pad     = slotPtr[slot].pad;
		weight  = slotPtr[slot].weight;
		uniform = slotPtr[slot].uniform;
	    }

	    Tcl_ListObjAppendElement(interp, res,
		    Tcl_NewStringObj("-minsize", -1));
	    Tcl_ListObjAppendElement(interp, res, Tcl_NewIntObj(minsize));
	    Tcl_ListObjAppendElement(interp, res,
		    Tcl_NewStringObj("-pad", -1));
	    Tcl_ListObjAppendElement(interp, res, Tcl_NewIntObj(pad));
	    Tcl_ListObjAppendElement(interp, res,
		    Tcl_NewStringObj("-uniform", -1));
	    Tcl_ListObjAppendElement(interp, res,
		    Tcl_NewStringObj(uniform == NULL ? "" : uniform, -1));
	    Tcl_ListObjAppendElement(interp, res,
		    Tcl_NewStringObj("-weight", -1));
	    Tcl_ListObjAppendElement(interp, res, Tcl_NewIntObj(weight));
	    Tcl_SetObjResult(interp, res);
	    Tcl_DecrRefCount(listCopy);
	    return TCL_OK;
	}

	/*
	 * If only one option is given, with no value, the current value is
	 * returned.
	 */

	if (Tcl_GetIndexFromObjStruct(interp, objv[4], optionStrings,
		sizeof(char *), "option", 0, &index) != TCL_OK) {
	    Tcl_DecrRefCount(listCopy);
	    return TCL_ERROR;
	}
	if (index == ROWCOL_MINSIZE) {
	    Tcl_SetObjResult(interp, Tcl_NewIntObj(
		    (ok == TCL_OK) ? slotPtr[slot].minSize : 0));
	} else if (index == ROWCOL_WEIGHT) {
	    Tcl_SetObjResult(interp, Tcl_NewIntObj(
		    (ok == TCL_OK) ? slotPtr[slot].weight : 0));
	} else if (index == ROWCOL_UNIFORM) {
	    Tk_Uid value = (ok == TCL_OK) ? slotPtr[slot].uniform : "";

	    Tcl_SetObjResult(interp, Tcl_NewStringObj(
		    (value == NULL) ? "" : value, -1));
	} else if (index == ROWCOL_PAD) {
	    Tcl_SetObjResult(interp, Tcl_NewIntObj(
		    (ok == TCL_OK) ? slotPtr[slot].pad : 0));
	}
	Tcl_DecrRefCount(listCopy);
	return TCL_OK;
    }

    for (j = 0; j < lObjc; j++) {
	int allContent = 0;

	if (Tcl_GetIntFromObj(NULL, lObjv[j], &slot) == TCL_OK) {
	    first = slot;
	    last = slot;
	    contentPtr = NULL;
	} else if (strcmp(Tcl_GetString(lObjv[j]), "all") == 0) {
	    /*
	     * Make sure container is initialised.
	     */

	    InitContainerData(containerPtr);

	    contentPtr = containerPtr->contentPtr;
	    if (contentPtr == NULL) {
		continue;
	    }
	    allContent = 1;
	} else if (TkGetWindowFromObj(NULL, tkwin, lObjv[j], &content)
		== TCL_OK) {
	    /*
	     * Is it gridded in this container?
	     */

	    contentPtr = GetGrid(content);
	    if (contentPtr->containerPtr != containerPtr) {
		Tcl_SetObjResult(interp, Tcl_ObjPrintf(
			"the window \"%s\" is not managed by \"%s\"",
			Tcl_GetString(lObjv[j]), Tcl_GetString(objv[2])));
		Tcl_SetErrorCode(interp, "TK", "GRID", "NOT_MASTER", NULL);
		Tcl_DecrRefCount(listCopy);
		return TCL_ERROR;
	    }
	} else {
	    Tcl_SetObjResult(interp, Tcl_ObjPrintf(
		    "illegal index \"%s\"", Tcl_GetString(lObjv[j])));
	    Tcl_SetErrorCode(interp, "TK", "VALUE", "GRID_INDEX", NULL);
	    Tcl_DecrRefCount(listCopy);
	    return TCL_ERROR;
	}

	/*
	 * The outer loop is only to handle "all".
	 */

	do {
	    if (contentPtr != NULL) {
		first = (slotType == COLUMN) ?
			contentPtr->column : contentPtr->row;
		last = first - 1 + ((slotType == COLUMN) ?
			contentPtr->numCols : contentPtr->numRows);
	    }

	    for (slot = first; slot <= last; slot++) {
		ok = CheckSlotData(containerPtr, slot, slotType, /*checkOnly*/ 0);
		if (ok != TCL_OK) {
		    Tcl_SetObjResult(interp, Tcl_ObjPrintf(
			    "\"%s\" is out of range",
			    Tcl_GetString(lObjv[j])));
		    Tcl_SetErrorCode(interp, "TK", "GRID", "INDEX_RANGE",
			    NULL);
		    Tcl_DecrRefCount(listCopy);
		    return TCL_ERROR;
		}
		slotPtr = (slotType == COLUMN) ?
			containerPtr->containerDataPtr->columnPtr :
			containerPtr->containerDataPtr->rowPtr;

		/*
		 * Loop through each option value pair, setting the values as
		 * required.
		 */

		for (i = 4; i < objc; i += 2) {
		    if (Tcl_GetIndexFromObjStruct(interp, objv[i], optionStrings,
			    sizeof(char *), "option", 0, &index) != TCL_OK) {
			Tcl_DecrRefCount(listCopy);
			return TCL_ERROR;
		    }
		    if (index == ROWCOL_MINSIZE) {
			if (Tk_GetPixelsFromObj(interp, container, objv[i+1],
				&size) != TCL_OK) {
			    Tcl_DecrRefCount(listCopy);
			    return TCL_ERROR;
			} else {
			    slotPtr[slot].minSize = size;
			}
		    } else if (index == ROWCOL_WEIGHT) {
			int wt;

			if (Tcl_GetIntFromObj(interp,objv[i+1],&wt)!=TCL_OK) {
			    Tcl_DecrRefCount(listCopy);
			    return TCL_ERROR;
			} else if (wt < 0) {
			    Tcl_DecrRefCount(listCopy);
			    goto negativeIndex;
			} else {
			    slotPtr[slot].weight = wt;
			}
		    } else if (index == ROWCOL_UNIFORM) {
			slotPtr[slot].uniform =
				Tk_GetUid(Tcl_GetString(objv[i+1]));
			if (slotPtr[slot].uniform != NULL &&
				slotPtr[slot].uniform[0] == 0) {
			    slotPtr[slot].uniform = NULL;
			}
		    } else if (index == ROWCOL_PAD) {
			if (Tk_GetPixelsFromObj(interp, container, objv[i+1],
				&size) != TCL_OK) {
			    Tcl_DecrRefCount(listCopy);
			    return TCL_ERROR;
			} else if (size < 0) {
			    Tcl_DecrRefCount(listCopy);
			    goto negativeIndex;
			} else {
			    slotPtr[slot].pad = size;
			}
		    }
		}
	    }
	    if (contentPtr != NULL) {
		contentPtr = contentPtr->nextPtr;
	    }
	} while ((allContent == 1) && (contentPtr != NULL));
    }
    Tcl_DecrRefCount(listCopy);

    /*
     * We changed a property, re-arrange the table, and check for constraint
     * shrinkage. A null slotPtr will occur for 'all' checks.
     */

    if (slotPtr != NULL) {
	if (slotType == ROW) {
	    last = containerPtr->containerDataPtr->rowMax - 1;

	    while ((last >= 0) && (slotPtr[last].weight == 0)
		    && (slotPtr[last].pad == 0) && (slotPtr[last].minSize == 0)
		    && (slotPtr[last].uniform == NULL)) {
		last--;
	    }
	    containerPtr->containerDataPtr->rowMax = last+1;
	} else {
	    last = containerPtr->containerDataPtr->columnMax - 1;

	    while ((last >= 0) && (slotPtr[last].weight == 0)
		    && (slotPtr[last].pad == 0) && (slotPtr[last].minSize == 0)
		    && (slotPtr[last].uniform == NULL)) {
		last--;
	    }
	    containerPtr->containerDataPtr->columnMax = last + 1;
	}
    }

    if (containerPtr->abortPtr != NULL) {
	*containerPtr->abortPtr = 1;
    }
    if (!(containerPtr->flags & REQUESTED_RELAYOUT)) {
	containerPtr->flags |= REQUESTED_RELAYOUT;
	Tcl_DoWhenIdle(ArrangeGrid, containerPtr);
    }
    return TCL_OK;

  negativeIndex:
    Tcl_SetObjResult(interp, Tcl_ObjPrintf(
	    "invalid arg \"%s\": should be non-negative",
	    Tcl_GetString(objv[i])));
    Tcl_SetErrorCode(interp, "TK", "GRID", "NEG_INDEX", NULL);
    return TCL_ERROR;
}

/*
 *----------------------------------------------------------------------
 *
 * GridSizeCommand --
 *
 *	Implementation of the [grid size] subcommand. See the user
 *	documentation for details on what it does.
 *
 * Results:
 *	Standard Tcl result.
 *
 * Side effects:
 *	Puts grid size information in the interpreter's result.
 *
 *----------------------------------------------------------------------
 */

static int
GridSizeCommand(
    Tk_Window tkwin,		/* Main window of the application. */
    Tcl_Interp *interp,		/* Current interpreter. */
    int objc,			/* Number of arguments. */
    Tcl_Obj *const objv[])	/* Argument objects. */
{
    Tk_Window container;
    Gridder *containerPtr;
    GridContainer *gridPtr;	/* pointer to grid data */

    if (objc != 3) {
	Tcl_WrongNumArgs(interp, 2, objv, "window");
	return TCL_ERROR;
    }

    if (TkGetWindowFromObj(interp, tkwin, objv[2], &container) != TCL_OK) {
	return TCL_ERROR;
    }
    containerPtr = GetGrid(container);

    if (containerPtr->containerDataPtr != NULL) {
	SetGridSize(containerPtr);
	gridPtr = containerPtr->containerDataPtr;
	Tcl_SetObjResult(interp, NewPairObj(
		MAX(gridPtr->columnEnd, gridPtr->columnMax),
		MAX(gridPtr->rowEnd, gridPtr->rowMax)));
    } else {
	Tcl_SetObjResult(interp, NewPairObj(0, 0));
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * GridContentCommand --
 *
 *	Implementation of the [grid content] subcommand. See the user
 *	documentation for details on what it does.
 *
 * Results:
 *	Standard Tcl result.
 *
 * Side effects:
 *	Places a list of content windows of the specified window in the
 *	interpreter's result field.
 *
 *----------------------------------------------------------------------
 */

static int
GridContentCommand(
    Tk_Window tkwin,		/* Main window of the application. */
    Tcl_Interp *interp,		/* Current interpreter. */
    int objc,			/* Number of arguments. */
    Tcl_Obj *const objv[])	/* Argument objects. */
{
    Tk_Window container;
    Gridder *containerPtr;		/* container grid record */
    Gridder *contentPtr;
    int i, value, index;
    int row = -1, column = -1;
    static const char *const optionStrings[] = {
	"-column", "-row", NULL
    };
    enum options { CONTENT_COLUMN, CONTENT_ROW };
    Tcl_Obj *res;

    if ((objc < 3) || ((objc % 2) == 0)) {
	Tcl_WrongNumArgs(interp, 2, objv, "window ?-option value ...?");
	return TCL_ERROR;
    }

    for (i = 3; i < objc; i += 2) {
	if (Tcl_GetIndexFromObjStruct(interp, objv[i], optionStrings,
		sizeof(char *), "option", 0, &index) != TCL_OK) {
	    return TCL_ERROR;
	}
	if (Tcl_GetIntFromObj(interp, objv[i+1], &value) != TCL_OK) {
	    return TCL_ERROR;
	}
	if (value < 0) {
	    Tcl_SetObjResult(interp, Tcl_ObjPrintf(
		    "%d is an invalid value: should NOT be < 0", value));
	    Tcl_SetErrorCode(interp, "TK", "GRID", "NEG_INDEX", NULL);
	    return TCL_ERROR;
	}
	if (index == CONTENT_COLUMN) {
	    column = value;
	} else {
	    row = value;
	}
    }

    if (TkGetWindowFromObj(interp, tkwin, objv[2], &container) != TCL_OK) {
	return TCL_ERROR;
    }
    containerPtr = GetGrid(container);

    res = Tcl_NewListObj(0, NULL);
    for (contentPtr = containerPtr->contentPtr; contentPtr != NULL;
	    contentPtr = contentPtr->nextPtr) {
	if ((column >= 0) && (contentPtr->column > column
		|| contentPtr->column+contentPtr->numCols-1 < column)) {
	    continue;
	}
	if ((row >= 0) && (contentPtr->row > row ||
		contentPtr->row+contentPtr->numRows-1 < row)) {
	    continue;
	}
	Tcl_ListObjAppendElement(interp,res, TkNewWindowObj(contentPtr->tkwin));
    }
    Tcl_SetObjResult(interp, res);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * GridReqProc --
 *
 *	This procedure is invoked by Tk_GeometryRequest for windows managed by
 *	the grid.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Arranges for tkwin, and all its managed siblings, to be re-arranged at
 *	the next idle point.
 *
 *----------------------------------------------------------------------
 */

static void
GridReqProc(
    ClientData clientData,	/* Grid's information about window that got
				 * new preferred geometry. */
    TCL_UNUSED(Tk_Window))		/* Other Tk-related information about the
				 * window. */
{
    Gridder *gridPtr = (Gridder *)clientData;

    gridPtr = gridPtr->containerPtr;
    if (gridPtr && !(gridPtr->flags & REQUESTED_RELAYOUT)) {
	gridPtr->flags |= REQUESTED_RELAYOUT;
	Tcl_DoWhenIdle(ArrangeGrid, gridPtr);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * GridLostContentProc --
 *
 *	This procedure is invoked by Tk whenever some other geometry claims
 *	control over a content that used to be managed by us.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Forgets all grid-related information about the content.
 *
 *----------------------------------------------------------------------
 */

static void
GridLostContentProc(
    ClientData clientData,	/* Grid structure for content window that was
				 * stolen away. */
    TCL_UNUSED(Tk_Window))		/* Tk's handle for the content window. */
{
    Gridder *contentPtr = (Gridder *)clientData;

    if (contentPtr->containerPtr->tkwin != Tk_Parent(contentPtr->tkwin)) {
	Tk_UnmaintainGeometry(contentPtr->tkwin, contentPtr->containerPtr->tkwin);
    }
    Unlink(contentPtr);
    Tk_UnmapWindow(contentPtr->tkwin);
}

/*
 *----------------------------------------------------------------------
 *
 * AdjustOffsets --
 *
 *	This procedure adjusts the size of the layout to fit in the space
 *	provided. If it needs more space, the extra is added according to the
 *	weights. If it needs less, the space is removed according to the
 *	weights, but at no time does the size drop below the minsize specified
 *	for that slot.
 *
 * Results:
 *	The size used by the layout.
 *
 * Side effects:
 *	The slot offsets are modified to shrink the layout.
 *
 *----------------------------------------------------------------------
 */

static int
AdjustOffsets(
    int size,			/* The total layout size (in pixels). */
    int slots,			/* Number of slots. */
    SlotInfo *slotPtr)	/* Pointer to slot array. */
{
    int slot;		/* Current slot. */
    int diff;			/* Extra pixels needed to add to the layout. */
    int totalWeight;		/* Sum of the weights for all the slots. */
    int weight;			/* Sum of the weights so far. */
    int minSize;		/* Minimum possible layout size. */
    int newDiff;		/* The most pixels that can be added on the
    				 * current pass. */

    diff = size - slotPtr[slots-1].offset;

    /*
     * The layout is already the correct size; all done.
     */

    if (diff == 0) {
	return size;
    }

    /*
     * If all the weights are zero, there is nothing more to do.
     */

    totalWeight = 0;
    for (slot = 0; slot < slots; slot++) {
	totalWeight += slotPtr[slot].weight;
    }

    if (totalWeight == 0) {
	return slotPtr[slots-1].offset;
    }

    /*
     * Add extra space according to the slot weights. This is done
     * cumulatively to prevent round-off error accumulation.
     */

    if (diff > 0) {
	weight = 0;
	for (slot = 0; slot < slots; slot++) {
	    weight += slotPtr[slot].weight;
	    slotPtr[slot].offset += diff * weight / totalWeight;
	}
	return size;
    }

    /*
     * The layout must shrink below its requested size. Compute the minimum
     * possible size by looking at the slot minSizes. Store each slot's
     * minimum size in temp.
     */

    minSize = 0;
    for (slot = 0; slot < slots; slot++) {
    	if (slotPtr[slot].weight > 0) {
	    slotPtr[slot].temp = slotPtr[slot].minSize;
	} else if (slot > 0) {
	    slotPtr[slot].temp = slotPtr[slot].offset - slotPtr[slot-1].offset;
	} else {
	    slotPtr[slot].temp = slotPtr[slot].offset;
	}
	minSize += slotPtr[slot].temp;
    }

    /*
     * If the requested size is less than the minimum required size, set the
     * slot sizes to their minimum values.
     */

    if (size <= minSize) {
    	int offset = 0;

	for (slot = 0; slot < slots; slot++) {
	    offset += slotPtr[slot].temp;
	    slotPtr[slot].offset = offset;
	}
	return minSize;
    }

    /*
     * Remove space from slots according to their weights. The weights get
     * renormalized anytime a slot shrinks to its minimum size.
     */

    while (diff < 0) {
	/*
	 * Find the total weight for the shrinkable slots.
	 */

	totalWeight = 0;
	for (slot = 0; slot < slots; slot++) {
	    int current = (slot == 0) ? slotPtr[slot].offset :
		    slotPtr[slot].offset - slotPtr[slot-1].offset;

	    if (current > slotPtr[slot].minSize) {
		totalWeight += slotPtr[slot].weight;
		slotPtr[slot].temp = slotPtr[slot].weight;
	    } else {
		slotPtr[slot].temp = 0;
	    }
	}
	if (totalWeight == 0) {
	    break;
	}

	/*
	 * Find the maximum amount of space we can distribute this pass.
	 */

	newDiff = diff;
	for (slot = 0; slot < slots; slot++) {
	    int current;	/* Current size of this slot. */
	    int maxDiff;	/* Maximum diff that would cause this slot to
	    			 * equal its minsize. */

	    if (slotPtr[slot].temp == 0) {
	    	continue;
	    }
	    current = (slot == 0) ? slotPtr[slot].offset :
		    slotPtr[slot].offset - slotPtr[slot-1].offset;
	    maxDiff = totalWeight * (slotPtr[slot].minSize - current)
		    / slotPtr[slot].temp;
	    if (maxDiff > newDiff) {
	    	newDiff = maxDiff;
	    }
	}

	/*
	 * Now distribute the space.
	 */

	weight = 0;
	for (slot = 0; slot < slots; slot++) {
	    weight += slotPtr[slot].temp;
	    slotPtr[slot].offset += newDiff * weight / totalWeight;
	}
    	diff -= newDiff;
    }
    return size;
}

/*
 *----------------------------------------------------------------------
 *
 * AdjustForSticky --
 *
 *	This procedure adjusts the size of a content in its cavity based on its
 *	"sticky" flags.
 *
 * Results:
 *	The input x, y, width, and height are changed to represent the desired
 *	coordinates of the content.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static void
AdjustForSticky(
    Gridder *contentPtr,	/* Content window to arrange in its cavity. */
    int *xPtr,		/* Pixel location of the left edge of the cavity. */
    int *yPtr,		/* Pixel location of the top edge of the cavity. */
    int *widthPtr,	/* Width of the cavity (in pixels). */
    int *heightPtr)	/* Height of the cavity (in pixels). */
{
    int diffx = 0;	/* Cavity width - content width. */
    int diffy = 0;	/* Cavity hight - content height. */
    int sticky = contentPtr->sticky;

    *xPtr += contentPtr->padLeft;
    *widthPtr -= contentPtr->padX;
    *yPtr += contentPtr->padTop;
    *heightPtr -= contentPtr->padY;

    if (*widthPtr > (Tk_ReqWidth(contentPtr->tkwin) + contentPtr->iPadX)) {
	diffx = *widthPtr - (Tk_ReqWidth(contentPtr->tkwin) + contentPtr->iPadX);
	*widthPtr = Tk_ReqWidth(contentPtr->tkwin) + contentPtr->iPadX;
    }

    if (*heightPtr > (Tk_ReqHeight(contentPtr->tkwin) + contentPtr->iPadY)) {
	diffy = *heightPtr - (Tk_ReqHeight(contentPtr->tkwin) + contentPtr->iPadY);
	*heightPtr = Tk_ReqHeight(contentPtr->tkwin) + contentPtr->iPadY;
    }

    if (sticky&STICK_EAST && sticky&STICK_WEST) {
	*widthPtr += diffx;
    }
    if (sticky&STICK_NORTH && sticky&STICK_SOUTH) {
	*heightPtr += diffy;
    }
    if (!(sticky&STICK_WEST)) {
    	*xPtr += (sticky&STICK_EAST) ? diffx : diffx/2;
    }
    if (!(sticky&STICK_NORTH)) {
    	*yPtr += (sticky&STICK_SOUTH) ? diffy : diffy/2;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * ArrangeGrid --
 *
 *	This procedure is invoked (using the Tcl_DoWhenIdle mechanism) to
 *	re-layout a set of windows managed by the grid. It is invoked at idle
 *	time so that a series of grid requests can be merged into a single
 *	layout operation.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The content of containerPtr may get resized or moved.
 *
 *----------------------------------------------------------------------
 */

static void
ArrangeGrid(
    ClientData clientData)	/* Structure describing container whose content
				 * are to be re-layed out. */
{
    Gridder *containerPtr = (Gridder *)clientData;
    Gridder *contentPtr;
    GridContainer *slotPtr = containerPtr->containerDataPtr;
    int abort;
    int width, height;		/* Requested size of layout, in pixels. */
    int realWidth, realHeight;	/* Actual size layout should take-up. */
    int usedX, usedY;

    containerPtr->flags &= ~REQUESTED_RELAYOUT;

    /*
     * If the container has no content anymore, then don't change the container size.
     * Otherwise there is no way to "relinquish" control over the container
     * so another geometry manager can take over.
     */

    if (containerPtr->contentPtr == NULL) {
	return;
    }

    if (containerPtr->containerDataPtr == NULL) {
	return;
    }

    /*
     * Abort any nested call to ArrangeGrid for this window, since we'll do
     * everything necessary here, and set up so this call can be aborted if
     * necessary.
     */

    if (containerPtr->abortPtr != NULL) {
	*containerPtr->abortPtr = 1;
    }
    containerPtr->abortPtr = &abort;
    abort = 0;
    Tcl_Preserve(containerPtr);

    /*
     * Call the constraint engine to fill in the row and column offsets.
     */

    SetGridSize(containerPtr);
    width = ResolveConstraints(containerPtr, COLUMN, 0);
    height = ResolveConstraints(containerPtr, ROW, 0);
    width += Tk_InternalBorderLeft(containerPtr->tkwin) +
	    Tk_InternalBorderRight(containerPtr->tkwin);
    height += Tk_InternalBorderTop(containerPtr->tkwin) +
	    Tk_InternalBorderBottom(containerPtr->tkwin);

    if (width < Tk_MinReqWidth(containerPtr->tkwin)) {
	width = Tk_MinReqWidth(containerPtr->tkwin);
    }
    if (height < Tk_MinReqHeight(containerPtr->tkwin)) {
	height = Tk_MinReqHeight(containerPtr->tkwin);
    }

    if (((width != Tk_ReqWidth(containerPtr->tkwin))
	    || (height != Tk_ReqHeight(containerPtr->tkwin)))
	    && !(containerPtr->flags & DONT_PROPAGATE)) {
	Tk_GeometryRequest(containerPtr->tkwin, width, height);
	if (width>1 && height>1) {
	    containerPtr->flags |= REQUESTED_RELAYOUT;
	    Tcl_DoWhenIdle(ArrangeGrid, containerPtr);
	}
	containerPtr->abortPtr = NULL;
	Tcl_Release(containerPtr);
	return;
    }

    /*
     * If the currently requested layout size doesn't match the container's
     * window size, then adjust the slot offsets according to the weights. If
     * all of the weights are zero, place the layout according to the anchor
     * value.
     */

    realWidth = Tk_Width(containerPtr->tkwin) -
	    Tk_InternalBorderLeft(containerPtr->tkwin) -
	    Tk_InternalBorderRight(containerPtr->tkwin);
    realHeight = Tk_Height(containerPtr->tkwin) -
	    Tk_InternalBorderTop(containerPtr->tkwin) -
	    Tk_InternalBorderBottom(containerPtr->tkwin);
    usedX = AdjustOffsets(realWidth,
	    MAX(slotPtr->columnEnd, slotPtr->columnMax), slotPtr->columnPtr);
    usedY = AdjustOffsets(realHeight, MAX(slotPtr->rowEnd, slotPtr->rowMax),
	    slotPtr->rowPtr);
    TkComputeAnchor(containerPtr->containerDataPtr->anchor, containerPtr->tkwin,
	    0, 0, usedX, usedY, &slotPtr->startX, &slotPtr->startY);

    /*
     * Now adjust the actual size of the content to its cavity by computing the
     * cavity size, and adjusting the widget according to its stickyness.
     */

    for (contentPtr = containerPtr->contentPtr; contentPtr != NULL && !abort;
	    contentPtr = contentPtr->nextPtr) {
	int x, y;			/* Top left coordinate */
	int col = contentPtr->column;
	int row = contentPtr->row;

	x = (col>0) ? slotPtr->columnPtr[col-1].offset : 0;
	y = (row>0) ? slotPtr->rowPtr[row-1].offset : 0;

	width = slotPtr->columnPtr[contentPtr->numCols+col-1].offset - x;
	height = slotPtr->rowPtr[contentPtr->numRows+row-1].offset - y;

	x += slotPtr->startX;
	y += slotPtr->startY;

	AdjustForSticky(contentPtr, &x, &y, &width, &height);

	/*
	 * Now put the window in the proper spot. (This was taken directly
	 * from tkPack.c.) If the content is a child of the container, then do this
	 * here. Otherwise let Tk_MaintainGeometry do the work.
	 */

	if (containerPtr->tkwin == Tk_Parent(contentPtr->tkwin)) {
	    if ((width <= 0) || (height <= 0)) {
		Tk_UnmapWindow(contentPtr->tkwin);
	    } else {
		if ((x != Tk_X(contentPtr->tkwin))
			|| (y != Tk_Y(contentPtr->tkwin))
			|| (width != Tk_Width(contentPtr->tkwin))
			|| (height != Tk_Height(contentPtr->tkwin))) {
		    Tk_MoveResizeWindow(contentPtr->tkwin, x, y, width, height);
		}
		if (abort) {
		    break;
		}

		/*
		 * Don't map the content if the container isn't mapped: wait until
		 * the container gets mapped later.
		 */

		if (Tk_IsMapped(containerPtr->tkwin)) {
		    Tk_MapWindow(contentPtr->tkwin);
		}
	    }
	} else if ((width <= 0) || (height <= 0)) {
	    Tk_UnmaintainGeometry(contentPtr->tkwin, containerPtr->tkwin);
	    Tk_UnmapWindow(contentPtr->tkwin);
	} else {
	    Tk_MaintainGeometry(contentPtr->tkwin, containerPtr->tkwin, x, y,
		    width, height);
	}
    }

    containerPtr->abortPtr = NULL;
    Tcl_Release(containerPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * ResolveConstraints --
 *
 *	Resolve all of the column and row boundaries. Most of the calculations
 *	are identical for rows and columns, so this procedure is called twice,
 *	once for rows, and again for columns.
 *
 * Results:
 *	The offset (in pixels) from the left/top edge of this layout is
 *	returned.
 *
 * Side effects:
 *	The slot offsets are copied into the SlotInfo structure for the
 *	geometry container.
 *
 *----------------------------------------------------------------------
 */

static int
ResolveConstraints(
    Gridder *containerPtr,		/* The geometry container for this grid. */
    int slotType,		/* Either ROW or COLUMN. */
    int maxOffset)		/* The actual maximum size of this layout in
				 * pixels, or 0 (not currently used). */
{
    SlotInfo *slotPtr;	/* Pointer to row/col constraints. */
    Gridder *contentPtr;	/* List of content windows in this grid. */
    int constraintCount;	/* Count of rows or columns that have
				 * constraints. */
    int slotCount;		/* Last occupied row or column. */
    int gridCount;		/* The larger of slotCount and
				 * constraintCount. */
    GridLayout *layoutPtr;	/* Temporary layout structure. */
    int requiredSize;		/* The natural size of the grid (pixels).
				 * This is the minimum size needed to
				 * accommodate all of the content at their
				 * requested sizes. */
    int offset;			/* The pixel offset of the right edge of the
				 * current slot from the beginning of the
				 * layout. */
    int slot;			/* The current slot. */
    int start;			/* The first slot of a contiguous set whose
				 * constraints are not yet fully resolved. */
    int end;			/* The Last slot of a contiguous set whose
				 * constraints are not yet fully resolved. */
    UniformGroup uniformPre[UNIFORM_PREALLOC];
				/* Pre-allocated space for uniform groups. */
    UniformGroup *uniformGroupPtr;
				/* Uniform groups data. */
    int uniformGroups;		/* Number of currently used uniform groups. */
    int uniformGroupsAlloced;	/* Size of allocated space for uniform
				 * groups. */
    int minSize;
    int prevGrow, accWeight, grow;

    /*
     * For typical sized tables, we'll use stack space for the layout data to
     * avoid the overhead of a malloc and free for every layout.
     */

    GridLayout layoutData[TYPICAL_SIZE + 1];

    if (slotType == COLUMN) {
	constraintCount = containerPtr->containerDataPtr->columnMax;
	slotCount = containerPtr->containerDataPtr->columnEnd;
	slotPtr = containerPtr->containerDataPtr->columnPtr;
    } else {
	constraintCount = containerPtr->containerDataPtr->rowMax;
	slotCount = containerPtr->containerDataPtr->rowEnd;
	slotPtr = containerPtr->containerDataPtr->rowPtr;
    }

    /*
     * Make sure there is enough memory for the layout.
     */

    gridCount = MAX(constraintCount, slotCount);
    if (gridCount >= TYPICAL_SIZE) {
	layoutPtr = (GridLayout *)ckalloc(sizeof(GridLayout) * (1+gridCount));
    } else {
	layoutPtr = layoutData;
    }

    /*
     * Allocate an extra layout slot to represent the left/top edge of the 0th
     * slot to make it easier to calculate slot widths from offsets without
     * special case code.
     *
     * Initialize the "dummy" slot to the left/top of the table. This slot
     * avoids special casing the first slot.
     */

    layoutPtr->minOffset = 0;
    layoutPtr->maxOffset = 0;
    layoutPtr++;

    /*
     * Step 1.
     * Copy the slot constraints into the layout structure, and initialize the
     * rest of the fields.
     */

    for (slot=0; slot < constraintCount; slot++) {
	layoutPtr[slot].minSize = slotPtr[slot].minSize;
	layoutPtr[slot].weight = slotPtr[slot].weight;
	layoutPtr[slot].uniform = slotPtr[slot].uniform;
	layoutPtr[slot].pad = slotPtr[slot].pad;
	layoutPtr[slot].binNextPtr = NULL;
    }
    for (; slot<gridCount; slot++) {
	layoutPtr[slot].minSize = 0;
	layoutPtr[slot].weight = 0;
	layoutPtr[slot].uniform = NULL;
	layoutPtr[slot].pad = 0;
	layoutPtr[slot].binNextPtr = NULL;
    }

    /*
     * Step 2.
     * Content with a span of 1 are used to determine the minimum size of each
     * slot. Content whose span is two or more slots don't contribute to the
     * minimum size of each slot directly, but can cause slots to grow if
     * their size exceeds the the sizes of the slots they span.
     *
     * Bin all content whose spans are > 1 by their right edges. This allows
     * the computation on minimum and maximum possible layout sizes at each
     * slot boundary, without the need to re-sort the content.
     */

    switch (slotType) {
    case COLUMN:
	for (contentPtr = containerPtr->contentPtr; contentPtr != NULL;
		contentPtr = contentPtr->nextPtr) {
	    int rightEdge = contentPtr->column + contentPtr->numCols - 1;

	    contentPtr->size = Tk_ReqWidth(contentPtr->tkwin) + contentPtr->padX
		    + contentPtr->iPadX + contentPtr->doubleBw;
	    if (contentPtr->numCols > 1) {
		contentPtr->binNextPtr = layoutPtr[rightEdge].binNextPtr;
		layoutPtr[rightEdge].binNextPtr = contentPtr;
	    } else if (rightEdge >= 0) {
		int size = contentPtr->size + layoutPtr[rightEdge].pad;

		if (size > layoutPtr[rightEdge].minSize) {
		    layoutPtr[rightEdge].minSize = size;
		}
	    }
	}
	break;
    case ROW:
	for (contentPtr = containerPtr->contentPtr; contentPtr != NULL;
		contentPtr = contentPtr->nextPtr) {
	    int rightEdge = contentPtr->row + contentPtr->numRows - 1;

	    contentPtr->size = Tk_ReqHeight(contentPtr->tkwin) + contentPtr->padY
		    + contentPtr->iPadY + contentPtr->doubleBw;
	    if (contentPtr->numRows > 1) {
		contentPtr->binNextPtr = layoutPtr[rightEdge].binNextPtr;
		layoutPtr[rightEdge].binNextPtr = contentPtr;
	    } else if (rightEdge >= 0) {
		int size = contentPtr->size + layoutPtr[rightEdge].pad;

		if (size > layoutPtr[rightEdge].minSize) {
		    layoutPtr[rightEdge].minSize = size;
		}
	    }
	}
	break;
    }

    /*
     * Step 2b.
     * Consider demands on uniform sizes.
     */

    uniformGroupPtr = uniformPre;
    uniformGroupsAlloced = UNIFORM_PREALLOC;
    uniformGroups = 0;

    for (slot = 0; slot < gridCount; slot++) {
	if (layoutPtr[slot].uniform != NULL) {
		int weight;
	    for (start = 0; start < uniformGroups; start++) {
		if (uniformGroupPtr[start].group == layoutPtr[slot].uniform) {
		    break;
		}
	    }
	    if (start >= uniformGroups) {
		/*
		 * Have not seen that group before, set up data for it.
		 */

		if (uniformGroups >= uniformGroupsAlloced) {
		    /*
		     * We need to allocate more space.
		     */

		    size_t oldSize = uniformGroupsAlloced
			    * sizeof(UniformGroup);
		    size_t newSize = (uniformGroupsAlloced + UNIFORM_PREALLOC)
			    * sizeof(UniformGroup);
		    UniformGroup *newUG = (UniformGroup *)ckalloc(newSize);
		    UniformGroup *oldUG = uniformGroupPtr;

		    memcpy(newUG, oldUG, oldSize);
		    if (oldUG != uniformPre) {
			ckfree(oldUG);
		    }
		    uniformGroupPtr = newUG;
		    uniformGroupsAlloced += UNIFORM_PREALLOC;
		}
		uniformGroups++;
		uniformGroupPtr[start].group = layoutPtr[slot].uniform;
		uniformGroupPtr[start].minSize = 0;
	    }
	    weight = layoutPtr[slot].weight;
	    weight = weight > 0 ? weight : 1;
	    minSize = (layoutPtr[slot].minSize + weight - 1) / weight;
	    if (minSize > uniformGroupPtr[start].minSize) {
		uniformGroupPtr[start].minSize = minSize;
	    }
	}
    }

    /*
     * Data has been gathered about uniform groups. Now relayout accordingly.
     */

    if (uniformGroups > 0) {
	for (slot = 0; slot < gridCount; slot++) {
	    if (layoutPtr[slot].uniform != NULL) {
		for (start = 0; start < uniformGroups; start++) {
		    if (uniformGroupPtr[start].group ==
			    layoutPtr[slot].uniform) {
			int weight = layoutPtr[slot].weight;
			weight = weight > 0 ? weight : 1;
			layoutPtr[slot].minSize =
				uniformGroupPtr[start].minSize * weight;
			break;
		    }
		}
	    }
	}
    }

    if (uniformGroupPtr != uniformPre) {
	ckfree(uniformGroupPtr);
    }

    /*
     * Step 3.
     * Determine the minimum slot offsets going from left to right that would
     * fit all of the content. This determines the minimum
     */

    for (offset=0,slot=0; slot < gridCount; slot++) {
	layoutPtr[slot].minOffset = layoutPtr[slot].minSize + offset;
	for (contentPtr = layoutPtr[slot].binNextPtr; contentPtr != NULL;
		contentPtr = contentPtr->binNextPtr) {
	    int span = (slotType == COLUMN) ?
		    contentPtr->numCols : contentPtr->numRows;
	    int required = contentPtr->size + layoutPtr[slot - span].minOffset;

	    if (required > layoutPtr[slot].minOffset) {
		layoutPtr[slot].minOffset = required;
	    }
	}
	offset = layoutPtr[slot].minOffset;
    }

    /*
     * At this point, we know the minimum required size of the entire layout.
     * It might be prudent to stop here if our "container" will resize itself to
     * this size.
     */

    requiredSize = offset;
    if (maxOffset > offset) {
	offset=maxOffset;
    }

    /*
     * Step 4.
     * Determine the minimum slot offsets going from right to left, bounding
     * the pixel range of each slot boundary. Pre-fill all of the right
     * offsets with the actual size of the table; they will be reduced as
     * required.
     */

    for (slot=0; slot < gridCount; slot++) {
	layoutPtr[slot].maxOffset = offset;
    }
    for (slot=gridCount-1; slot > 0;) {
	for (contentPtr = layoutPtr[slot].binNextPtr; contentPtr != NULL;
		contentPtr = contentPtr->binNextPtr) {
	    int span = (slotType == COLUMN) ?
		    contentPtr->numCols : contentPtr->numRows;
	    int require = offset - contentPtr->size;
	    int startSlot = slot - span;

	    if (startSlot >=0 && require < layoutPtr[startSlot].maxOffset) {
		layoutPtr[startSlot].maxOffset = require;
	    }
	}
	offset -= layoutPtr[slot].minSize;
	slot--;
	if (layoutPtr[slot].maxOffset < offset) {
	    offset = layoutPtr[slot].maxOffset;
	} else {
	    layoutPtr[slot].maxOffset = offset;
	}
    }

    /*
     * Step 5.
     * At this point, each slot boundary has a range of values that will
     * satisfy the overall layout size. Make repeated passes over the layout
     * structure looking for spans of slot boundaries where the minOffsets are
     * less than the maxOffsets, and adjust the offsets according to the slot
     * weights. At each pass, at least one slot boundary will have its range
     * of possible values fixed at a single value.
     */

    for (start = 0; start < gridCount;) {
	int totalWeight = 0;	/* Sum of the weights for all of the slots in
				 * this span. */
	int need = 0;		/* The minimum space needed to layout this
				 * span. */
	int have;		/* The actual amount of space that will be
				 * taken up by this span. */
	int weight;		/* Cumulative weights of the columns in this
				 * span. */
	int noWeights = 0;	/* True if the span has no weights. */

	/*
	 * Find a span by identifying ranges of slots whose edges are already
	 * constrained at fixed offsets, but whose internal slot boundaries
	 * have a range of possible positions.
	 */

	if (layoutPtr[start].minOffset == layoutPtr[start].maxOffset) {
	    start++;
	    continue;
	}

	for (end = start + 1; end < gridCount; end++) {
	    if (layoutPtr[end].minOffset == layoutPtr[end].maxOffset) {
		break;
	    }
	}

	/*
	 * We found a span. Compute the total weight, minumum space required,
	 * for this span, and the actual amount of space the span should use.
	 */

	for (slot = start; slot <= end; slot++) {
	    totalWeight += layoutPtr[slot].weight;
	    need += layoutPtr[slot].minSize;
	}
	have = layoutPtr[end].maxOffset - layoutPtr[start-1].minOffset;

	/*
	 * If all the weights in the span are zero, then distribute the extra
	 * space evenly.
	 */

	if (totalWeight == 0) {
	    noWeights++;
	    totalWeight = end - start + 1;
	}

	/*
	 * It might not be possible to give the span all of the space
	 * available on this pass without violating the size constraints of
	 * one or more of the internal slot boundaries. Try to determine the
	 * maximum amount of space that when added to the entire span, would
	 * cause a slot boundary to have its possible range reduced to one
	 * value, and reduce the amount of extra space allocated on this pass
	 * accordingly.
	 *
	 * The calculation is done cumulatively to avoid accumulating roundoff
	 * errors.
	 */

	do {
	    int prevMinOffset = layoutPtr[start - 1].minOffset;

	    prevGrow = 0;
	    accWeight = 0;
	    for (slot = start; slot <= end; slot++) {
		weight = noWeights ? 1 : layoutPtr[slot].weight;
		accWeight += weight;
		grow = (have - need) * accWeight / totalWeight - prevGrow;
		prevGrow += grow;

		if ((weight > 0) &&
			((prevMinOffset + layoutPtr[slot].minSize + grow)
			> layoutPtr[slot].maxOffset)) {
		    int newHave;

		    /*
		     * There is not enough room to grow that much. Calculate
		     * how much this slot can grow and how much "have" that
		     * corresponds to.
		     */

		    grow = layoutPtr[slot].maxOffset -
			    layoutPtr[slot].minSize - prevMinOffset;
		    newHave = grow * totalWeight / weight;
		    if (newHave > totalWeight) {
			/*
			 * By distributing multiples of totalWeight we
			 * minimize rounding errors since they will only
			 * happen in the last loop(s).
			 */

			newHave = newHave / totalWeight * totalWeight;
		    }
		    if (newHave <= 0) {
			/*
			 * We can end up with a "have" of 0 here if the
			 * previous slots have taken all the space. In that
			 * case we cannot guess an appropriate "have" so we
			 * just try some lower "have" that is >= 1, to make
			 * sure this terminates.
			 */

			newHave = (have - need) - 1;
			if (newHave > (3 * totalWeight)) {
			    /*
			     * Go down 25% for large values.
			     */
			    newHave = newHave * 3 / 4;
			}

			if (newHave > totalWeight) {
			    /*
			     * Round down to a multiple of totalWeight.
			     */
			    newHave = newHave / totalWeight * totalWeight;
			}

			if (newHave <= 0) {
			    newHave = 1;
			}
		    }
		    have = newHave + need;

		    /*
		     * Restart loop to check if the new "have" will fit.
		     */

		    break;
		}
		prevMinOffset += layoutPtr[slot].minSize + grow;
		if (prevMinOffset < layoutPtr[slot].minOffset) {
		    prevMinOffset = layoutPtr[slot].minOffset;
		}
	    }

	    /*
	     * Quit the outer loop if the inner loop ran all the way.
	     */
	} while (slot <= end);

	/*
	 * Now distribute the extra space among the slots by adjusting the
	 * minSizes and minOffsets.
	 */

	prevGrow = 0;
	accWeight = 0;
	for (slot = start; slot <= end; slot++) {
	    accWeight += noWeights ? 1 : layoutPtr[slot].weight;
	    grow = (have - need) * accWeight / totalWeight - prevGrow;
	    prevGrow += grow;
	    layoutPtr[slot].minSize += grow;
	    if ((layoutPtr[slot-1].minOffset + layoutPtr[slot].minSize)
		    > layoutPtr[slot].minOffset) {
		layoutPtr[slot].minOffset = layoutPtr[slot-1].minOffset +
			layoutPtr[slot].minSize;
	    }
	}

	/*
	 * Having pushed the top/left boundaries of the slots to take up extra
	 * space, the bottom/right space is recalculated to propagate the new
	 * space allocation.
	 */

	for (slot = end; slot > start; slot--) {
	    /*
	     * maxOffset may not go up.
	     */

	    if ((layoutPtr[slot].maxOffset-layoutPtr[slot].minSize)
		    < layoutPtr[slot-1].maxOffset) {
		layoutPtr[slot-1].maxOffset =
			layoutPtr[slot].maxOffset-layoutPtr[slot].minSize;
	    }
	}
    }

    /*
     * Step 6.
     * All of the space has been apportioned; copy the layout information back
     * into the container.
     */

    for (slot=0; slot < gridCount; slot++) {
	slotPtr[slot].offset = layoutPtr[slot].minOffset;
    }

    --layoutPtr;
    if (layoutPtr != layoutData) {
	ckfree(layoutPtr);
    }
    return requiredSize;
}

/*
 *----------------------------------------------------------------------
 *
 * GetGrid --
 *
 *	This internal procedure is used to locate a Grid structure for a given
 *	window, creating one if one doesn't exist already.
 *
 * Results:
 *	The return value is a pointer to the Grid structure corresponding to
 *	tkwin.
 *
 * Side effects:
 *	A new grid structure may be created. If so, then a callback is set up
 *	to clean things up when the window is deleted.
 *
 *----------------------------------------------------------------------
 */

static Gridder *
GetGrid(
    Tk_Window tkwin)		/* Token for window for which grid structure
				 * is desired. */
{
    Gridder *gridPtr;
    Tcl_HashEntry *hPtr;
    int isNew;
    TkDisplay *dispPtr = ((TkWindow *) tkwin)->dispPtr;

    if (!dispPtr->gridInit) {
	Tcl_InitHashTable(&dispPtr->gridHashTable, TCL_ONE_WORD_KEYS);
	dispPtr->gridInit = 1;
    }

    /*
     * See if there's already grid for this window. If not, then create a new
     * one.
     */

    hPtr = Tcl_CreateHashEntry(&dispPtr->gridHashTable, (char*) tkwin, &isNew);
    if (!isNew) {
	return (Gridder *)Tcl_GetHashValue(hPtr);
    }
    gridPtr = (Gridder *)ckalloc(sizeof(Gridder));
    gridPtr->tkwin = tkwin;
    gridPtr->containerPtr = NULL;
    gridPtr->containerDataPtr = NULL;
    gridPtr->nextPtr = NULL;
    gridPtr->contentPtr = NULL;
    gridPtr->binNextPtr = NULL;

    gridPtr->column = -1;
    gridPtr->row = -1;
    gridPtr->numCols = 1;
    gridPtr->numRows = 1;

    gridPtr->padX = 0;
    gridPtr->padY = 0;
    gridPtr->padLeft = 0;
    gridPtr->padTop = 0;
    gridPtr->iPadX = 0;
    gridPtr->iPadY = 0;
    gridPtr->doubleBw = 2 * Tk_Changes(tkwin)->border_width;
    gridPtr->abortPtr = NULL;
    gridPtr->flags = 0;
    gridPtr->sticky = 0;
    gridPtr->size = 0;
    gridPtr->in = NULL;
    gridPtr->containerDataPtr = NULL;
    Tcl_SetHashValue(hPtr, gridPtr);
    Tk_CreateEventHandler(tkwin, StructureNotifyMask,
	    GridStructureProc, gridPtr);
    return gridPtr;
}

/*
 *----------------------------------------------------------------------
 *
 * SetGridSize --
 *
 *	This internal procedure sets the size of the grid occupied by content.
 *
 * Results:
 *	None
 *
 * Side effects:
 *	The width and height arguments are filled in the container data
 *	structure. Additional space is allocated for the constraints to
 *	accommodate the offsets.
 *
 *----------------------------------------------------------------------
 */

static void
SetGridSize(
    Gridder *containerPtr)		/* The geometry container for this grid. */
{
    Gridder *contentPtr;	/* Current content window. */
    int maxX = 0, maxY = 0;

    for (contentPtr = containerPtr->contentPtr; contentPtr != NULL;
	    contentPtr = contentPtr->nextPtr) {
	maxX = MAX(maxX, contentPtr->numCols + contentPtr->column);
	maxY = MAX(maxY, contentPtr->numRows + contentPtr->row);
    }
    containerPtr->containerDataPtr->columnEnd = maxX;
    containerPtr->containerDataPtr->rowEnd = maxY;
    CheckSlotData(containerPtr, maxX, COLUMN, CHECK_SPACE);
    CheckSlotData(containerPtr, maxY, ROW, CHECK_SPACE);
}

/*
 *----------------------------------------------------------------------
 *
 * SetContentColumn --
 *
 *	Update column data for a content, checking that MAX_ELEMENT bound
 *      is not passed.
 *
 * Results:
 *	TCL_ERROR if out of bounds, TCL_OK otherwise
 *
 * Side effects:
 *	Content fields are updated.
 *
 *----------------------------------------------------------------------
 */

static int
SetContentColumn(
    Tcl_Interp *interp,		/* Interp for error message. */
    Gridder *contentPtr,		/* Content to be updated. */
    int column,			/* New column or -1 to be unchanged. */
    int numCols)		/* New columnspan or -1 to be unchanged. */
{
    int newColumn, newNumCols, lastCol;

    newColumn = (column >= 0) ? column : contentPtr->column;
    newNumCols = (numCols >= 1) ? numCols : contentPtr->numCols;

    lastCol = ((newColumn >= 0) ? newColumn : 0) + newNumCols;
    if (lastCol >= MAX_ELEMENT) {
	Tcl_SetObjResult(interp, Tcl_NewStringObj("column out of bounds",-1));
	Tcl_SetErrorCode(interp, "TK", "GRID", "BAD_COLUMN", NULL);
	return TCL_ERROR;
    }

    contentPtr->column = newColumn;
    contentPtr->numCols = newNumCols;
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * SetContentRow --
 *
 *	Update row data for a content, checking that MAX_ELEMENT bound
 *      is not passed.
 *
 * Results:
 *	TCL_ERROR if out of bounds, TCL_OK otherwise
 *
 * Side effects:
 *	Content fields are updated.
 *
 *----------------------------------------------------------------------
 */

static int
SetContentRow(
    Tcl_Interp *interp,		/* Interp for error message. */
    Gridder *contentPtr,		/* Content to be updated. */
    int row,			/* New row or -1 to be unchanged. */
    int numRows)		/* New rowspan or -1 to be unchanged. */
{
    int newRow, newNumRows, lastRow;

    newRow = (row >= 0) ? row : contentPtr->row;
    newNumRows = (numRows >= 1) ? numRows : contentPtr->numRows;

    lastRow = ((newRow >= 0) ? newRow : 0) + newNumRows;
    if (lastRow >= MAX_ELEMENT) {
	Tcl_SetObjResult(interp, Tcl_NewStringObj("row out of bounds", -1));
	Tcl_SetErrorCode(interp, "TK", "GRID", "BAD_ROW", NULL);
	return TCL_ERROR;
    }

    contentPtr->row = newRow;
    contentPtr->numRows = newNumRows;
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * CheckSlotData --
 *
 *	This internal procedure is used to manage the storage for row and
 *	column (slot) constraints.
 *
 * Results:
 *	TRUE if the index is OK, False otherwise.
 *
 * Side effects:
 *	A new container grid structure may be created. If so, then it is
 *	initialized. In addition, additional storage for a row or column
 *	constraints may be allocated, and the constraint maximums are
 *	adjusted.
 *
 *----------------------------------------------------------------------
 */

static int
CheckSlotData(
    Gridder *containerPtr,		/* The geometry container for this grid. */
    int slot,			/* Which slot to look at. */
    int slotType,		/* ROW or COLUMN. */
    int checkOnly)		/* Don't allocate new space if true. */
{
    int numSlot;		/* Number of slots already allocated (Space) */
    int end;			/* Last used constraint. */

    /*
     * If slot is out of bounds, return immediately.
     */

    if (slot < 0 || slot >= MAX_ELEMENT) {
	return TCL_ERROR;
    }

    if ((checkOnly == CHECK_ONLY) && (containerPtr->containerDataPtr == NULL)) {
	return TCL_ERROR;
    }

    /*
     * If we need to allocate more space, allocate a little extra to avoid
     * repeated re-alloc's for large tables. We need enough space to hold all
     * of the offsets as well.
     */

    InitContainerData(containerPtr);
    end = (slotType == ROW) ? containerPtr->containerDataPtr->rowMax :
	    containerPtr->containerDataPtr->columnMax;
    if (checkOnly == CHECK_ONLY) {
    	return ((end < slot) ? TCL_ERROR : TCL_OK);
    } else {
    	numSlot = (slotType == ROW) ? containerPtr->containerDataPtr->rowSpace
		: containerPtr->containerDataPtr->columnSpace;
    	if (slot >= numSlot) {
	    int newNumSlot = slot + PREALLOC;
	    size_t oldSize = numSlot * sizeof(SlotInfo);
	    size_t newSize = newNumSlot * sizeof(SlotInfo);
	    SlotInfo *newSI = (SlotInfo *)ckalloc(newSize);
	    SlotInfo *oldSI = (slotType == ROW)
		    ? containerPtr->containerDataPtr->rowPtr
		    : containerPtr->containerDataPtr->columnPtr;

	    memcpy(newSI, oldSI, oldSize);
	    memset(newSI+numSlot, 0, newSize - oldSize);
	    ckfree(oldSI);
	    if (slotType == ROW) {
	 	containerPtr->containerDataPtr->rowPtr = newSI;
	    	containerPtr->containerDataPtr->rowSpace = newNumSlot;
	    } else {
	    	containerPtr->containerDataPtr->columnPtr = newSI;
	    	containerPtr->containerDataPtr->columnSpace = newNumSlot;
	    }
	}
	if (slot >= end && checkOnly != CHECK_SPACE) {
	    if (slotType == ROW) {
		containerPtr->containerDataPtr->rowMax = slot+1;
	    } else {
		containerPtr->containerDataPtr->columnMax = slot+1;
	    }
	}
    	return TCL_OK;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * InitContainerData --
 *
 *	This internal procedure is used to allocate and initialize the data
 *	for a geometry container, if the data doesn't exist already.
 *
 * Results:
 *	none
 *
 * Side effects:
 *	A new container grid structure may be created. If so, then it is
 *	initialized.
 *
 *----------------------------------------------------------------------
 */

static void
InitContainerData(
    Gridder *containerPtr)
{
    if (containerPtr->containerDataPtr == NULL) {
	GridContainer *gridPtr = containerPtr->containerDataPtr = (GridContainer *)
		ckalloc(sizeof(GridContainer));
	size_t size = sizeof(SlotInfo) * TYPICAL_SIZE;

	gridPtr->columnEnd = 0;
	gridPtr->columnMax = 0;
	gridPtr->columnPtr = (SlotInfo *)ckalloc(size);
	gridPtr->columnSpace = TYPICAL_SIZE;
	gridPtr->rowEnd = 0;
	gridPtr->rowMax = 0;
	gridPtr->rowPtr = (SlotInfo *)ckalloc(size);
	gridPtr->rowSpace = TYPICAL_SIZE;
	gridPtr->startX = 0;
	gridPtr->startY = 0;
	gridPtr->anchor = GRID_DEFAULT_ANCHOR;

	memset(gridPtr->columnPtr, 0, size);
	memset(gridPtr->rowPtr, 0, size);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * Unlink --
 *
 *	Remove a grid from its container's list of content.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The container will be scheduled for re-arranging, and the size of the
 *	grid will be adjusted accordingly
 *
 *----------------------------------------------------------------------
 */

static void
Unlink(
    Gridder *contentPtr)	/* Window to unlink. */
{
    Gridder *containerPtr, *contentPtr2;

    containerPtr = contentPtr->containerPtr;
    if (containerPtr == NULL) {
	return;
    }

    if (containerPtr->contentPtr == contentPtr) {
	containerPtr->contentPtr = contentPtr->nextPtr;
    } else {
	for (contentPtr2=containerPtr->contentPtr ; ; contentPtr2=contentPtr2->nextPtr) {
	    if (contentPtr2 == NULL) {
		Tcl_Panic("Unlink couldn't find previous window");
	    }
	    if (contentPtr2->nextPtr == contentPtr) {
		contentPtr2->nextPtr = contentPtr->nextPtr;
		break;
	    }
	}
    }
    if (!(containerPtr->flags & REQUESTED_RELAYOUT)) {
	containerPtr->flags |= REQUESTED_RELAYOUT;
	Tcl_DoWhenIdle(ArrangeGrid, containerPtr);
    }
    if (containerPtr->abortPtr != NULL) {
	*containerPtr->abortPtr = 1;
    }

    SetGridSize(contentPtr->containerPtr);
    contentPtr->containerPtr = NULL;

    /*
     * If we have emptied this container from content it means we are no longer
     * handling it and should mark it as free.
     */

    if ((containerPtr->contentPtr == NULL) && (containerPtr->flags & ALLOCED_CONTAINER)) {
	TkFreeGeometryContainer(containerPtr->tkwin, "grid");
	containerPtr->flags &= ~ALLOCED_CONTAINER;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * DestroyGrid --
 *
 *	This procedure is invoked by Tcl_EventuallyFree or Tcl_Release to
 *	clean up the internal structure of a grid at a safe time (when no-one
 *	is using it anymore). Cleaning up the grid involves freeing the main
 *	structure for all windows and the container structure for geometry
 *	managers.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Everything associated with the grid is freed up.
 *
 *----------------------------------------------------------------------
 */

static void
DestroyGrid(
    void *memPtr)		/* Info about window that is now dead. */
{
    Gridder *gridPtr = (Gridder *)memPtr;

    if (gridPtr->containerDataPtr != NULL) {
	if (gridPtr->containerDataPtr->rowPtr != NULL) {
	    ckfree(gridPtr->containerDataPtr -> rowPtr);
	}
	if (gridPtr->containerDataPtr->columnPtr != NULL) {
	    ckfree(gridPtr->containerDataPtr -> columnPtr);
	}
	ckfree(gridPtr->containerDataPtr);
    }
    if (gridPtr->in != NULL) {
	Tcl_DecrRefCount(gridPtr->in);
    }
    ckfree(gridPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * GridStructureProc --
 *
 *	This procedure is invoked by the Tk event dispatcher in response to
 *	StructureNotify events.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	If a window was just deleted, clean up all its grid-related
 *	information. If it was just resized, re-configure its content, if any.
 *
 *----------------------------------------------------------------------
 */

static void
GridStructureProc(
    ClientData clientData,	/* Our information about window referred to by
				 * eventPtr. */
    XEvent *eventPtr)		/* Describes what just happened. */
{
    Gridder *gridPtr = (Gridder *)clientData;
    TkDisplay *dispPtr = ((TkWindow *) gridPtr->tkwin)->dispPtr;

    if (eventPtr->type == ConfigureNotify) {
	if ((gridPtr->contentPtr != NULL)
		&& !(gridPtr->flags & REQUESTED_RELAYOUT)) {
	    gridPtr->flags |= REQUESTED_RELAYOUT;
	    Tcl_DoWhenIdle(ArrangeGrid, gridPtr);
	}
	if ((gridPtr->containerPtr != NULL) &&
		(gridPtr->doubleBw != 2*Tk_Changes(gridPtr->tkwin)->border_width)) {
	    if (!(gridPtr->containerPtr->flags & REQUESTED_RELAYOUT)) {
		gridPtr->doubleBw = 2*Tk_Changes(gridPtr->tkwin)->border_width;
		gridPtr->containerPtr->flags |= REQUESTED_RELAYOUT;
		Tcl_DoWhenIdle(ArrangeGrid, gridPtr->containerPtr);
	    }
	}
    } else if (eventPtr->type == DestroyNotify) {
	Gridder *contentPtr, *nextPtr;

	if (gridPtr->containerPtr != NULL) {
	    Unlink(gridPtr);
	}
	for (contentPtr = gridPtr->contentPtr; contentPtr != NULL;
		contentPtr = nextPtr) {
	    Tk_ManageGeometry(contentPtr->tkwin, NULL, NULL);
	    Tk_UnmapWindow(contentPtr->tkwin);
	    contentPtr->containerPtr = NULL;
	    nextPtr = contentPtr->nextPtr;
	    contentPtr->nextPtr = NULL;
	}
	Tcl_DeleteHashEntry(Tcl_FindHashEntry(&dispPtr->gridHashTable,
		(char *)gridPtr->tkwin));
	if (gridPtr->flags & REQUESTED_RELAYOUT) {
	    Tcl_CancelIdleCall(ArrangeGrid, gridPtr);
	}
	gridPtr->tkwin = NULL;
	Tcl_EventuallyFree(gridPtr, (Tcl_FreeProc *)DestroyGrid);
    } else if (eventPtr->type == MapNotify) {
	if ((gridPtr->contentPtr != NULL)
		&& !(gridPtr->flags & REQUESTED_RELAYOUT)) {
	    gridPtr->flags |= REQUESTED_RELAYOUT;
	    Tcl_DoWhenIdle(ArrangeGrid, gridPtr);
	}
    } else if (eventPtr->type == UnmapNotify) {
	Gridder *contentPtr;

	for (contentPtr = gridPtr->contentPtr; contentPtr != NULL;
		contentPtr = contentPtr->nextPtr) {
	    Tk_UnmapWindow(contentPtr->tkwin);
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * ConfigureContent --
 *
 *	This implements the guts of the "grid configure" command. Given a list
 *	of content and configuration options, it arranges for the grid to
 *	manage the content and sets the specified options. Arguments consist
 *	of windows or window shortcuts followed by "-option value" pairs.
 *
 * Results:
 *	TCL_OK is returned if all went well. Otherwise, TCL_ERROR is returned
 *	and the interp's result is set to contain an error message.
 *
 * Side effects:
 *	Content windows get taken over by the grid.
 *
 *----------------------------------------------------------------------
 */

static int
ConfigureContent(
    Tcl_Interp *interp,		/* Interpreter for error reporting. */
    Tk_Window tkwin,		/* Any window in application containing
				 * content. Used to look up content names. */
    int objc,			/* Number of elements in argv. */
    Tcl_Obj *const objv[])	/* Argument objects: contains one or more
				 * window names followed by any number of
				 * "option value" pairs. Caller must make sure
				 * that there is at least one window name. */
{
    Gridder *containerPtr = NULL;
    Gridder *contentPtr;
    Tk_Window other, content, parent, ancestor;
    TkWindow *container;
    int i, j, tmp;
    int numWindows;
    int width;
    int defaultRow = -1;
    int defaultColumn = 0;	/* Default column number */
    int defaultColumnSpan = 1;	/* Default number of columns */
    const char *lastWindow;	/* Use this window to base current row/col
				 * on */
    int numSkip;		/* Number of 'x' found */
    static const char *const optionStrings[] = {
	"-column", "-columnspan", "-in", "-ipadx", "-ipady",
	"-padx", "-pady", "-row", "-rowspan", "-sticky", NULL
    };
    enum options {
	CONF_COLUMN, CONF_COLUMNSPAN, CONF_IN, CONF_IPADX, CONF_IPADY,
	CONF_PADX, CONF_PADY, CONF_ROW, CONF_ROWSPAN, CONF_STICKY };
    int index;
    const char *string;
    char firstChar;
    int positionGiven;

    /*
     * Count the number of windows, or window short-cuts.
     */

    firstChar = 0;
    for (numWindows=0, i=0; i < objc; i++) {
	int length;
	char prevChar = firstChar;

	string = Tcl_GetStringFromObj(objv[i], &length);
    	firstChar = string[0];

	if (firstChar == '.') {
	    /*
	     * Check that windows are valid, and locate the first content's
	     * parent window (default for -in).
	     */

	    if (TkGetWindowFromObj(interp, tkwin, objv[i], &content) != TCL_OK) {
		return TCL_ERROR;
	    }
	    if (containerPtr == NULL) {
		/*
		 * Is there any saved -in from a removed content?
		 * If there is, it becomes default for -in.
		 * If the stored container does not exist, just ignore it.
		 */

		contentPtr = GetGrid(content);
		if (contentPtr->in != NULL) {
		    if (TkGetWindowFromObj(interp, content, contentPtr->in, &parent)
			    == TCL_OK) {
			containerPtr = GetGrid(parent);
			InitContainerData(containerPtr);
		    }
		}
	    }
	    if (containerPtr == NULL) {
		parent = Tk_Parent(content);
		if (parent != NULL) {
		    containerPtr = GetGrid(parent);
		    InitContainerData(containerPtr);
		}
	    }
	    numWindows++;
	    continue;
    	}
	if (length > 1 && i == 0) {
	    Tcl_SetObjResult(interp, Tcl_ObjPrintf(
		    "bad argument \"%s\": must be name of window", string));
	    Tcl_SetErrorCode(interp, "TK", "GRID", "BAD_PARAMETER", NULL);
	    return TCL_ERROR;
	}
    	if (length > 1 && firstChar == '-') {
	    break;
	}
	if (length > 1) {
	    Tcl_SetObjResult(interp, Tcl_ObjPrintf(
		    "unexpected parameter \"%s\" in configure list:"
		    " should be window name or option", string));
	    Tcl_SetErrorCode(interp, "TK", "GRID", "BAD_PARAMETER", NULL);
	    return TCL_ERROR;
	}

	if ((firstChar == REL_HORIZ) && ((numWindows == 0) ||
		(prevChar == REL_SKIP) || (prevChar == REL_VERT))) {
	    Tcl_SetObjResult(interp, Tcl_NewStringObj(
		    "must specify window before shortcut '-'", -1));
	    Tcl_SetErrorCode(interp, "TK", "GRID", "SHORTCUT_USAGE", NULL);
	    return TCL_ERROR;
	}

	if ((firstChar == REL_VERT) || (firstChar == REL_SKIP)
		|| (firstChar == REL_HORIZ)) {
	    continue;
	}

	Tcl_SetObjResult(interp, Tcl_ObjPrintf(
		"invalid window shortcut, \"%s\" should be '-', 'x', or '^'",
		string));
	Tcl_SetErrorCode(interp, "TK", "GRID", "SHORTCUT_USAGE", NULL);
	return TCL_ERROR;
    }
    numWindows = i;

    if ((objc - numWindows) & 1) {
	Tcl_SetObjResult(interp, Tcl_NewStringObj(
		"extra option or option with no value", -1));
	Tcl_SetErrorCode(interp, "TK", "GRID", "BAD_PARAMETER", NULL);
	return TCL_ERROR;
    }

    /*
     * Go through all options looking for -in and -row, which are needed to be
     * found first to handle the special case where ^ is used on a row without
     * windows names, but with an -in option. Since all options are checked
     * here, we do not need to handle the error case again later.
     */

    for (i = numWindows; i < objc; i += 2) {
	if (Tcl_GetIndexFromObjStruct(interp, objv[i], optionStrings,
		sizeof(char *), "option", 0, &index) != TCL_OK) {
	    return TCL_ERROR;
	}
	if (index == CONF_IN) {
	    if (TkGetWindowFromObj(interp, tkwin, objv[i+1], &other) !=
		    TCL_OK) {
		return TCL_ERROR;
	    }
	    containerPtr = GetGrid(other);
	    InitContainerData(containerPtr);
	} else if (index == CONF_ROW) {
	    if (Tcl_GetIntFromObj(interp, objv[i+1], &tmp) != TCL_OK
		    || tmp < 0) {
		Tcl_SetObjResult(interp, Tcl_ObjPrintf(
			"bad row value \"%s\": must be a non-negative integer",
			Tcl_GetString(objv[i+1])));
		Tcl_SetErrorCode(interp, "TK", "VALUE", "POSITIVE_INT", NULL);
		return TCL_ERROR;
	    }
	    defaultRow = tmp;
	}
    }

    /*
     * If no -row is given, use the next row after the highest occupied row
     * of the container.
     */

    if (defaultRow < 0) {
	if (containerPtr != NULL && containerPtr->containerDataPtr != NULL) {
	    SetGridSize(containerPtr);
	    defaultRow = containerPtr->containerDataPtr->rowEnd;
	} else {
	    defaultRow = 0;
	}
    }

    /*
     * Iterate over all of the content windows and short-cuts, parsing options
     * for each content. It's a bit wasteful to re-parse the options for each
     * content, but things get too messy if we try to parse the arguments just
     * once at the beginning. For example, if a content already is managed we
     * want to just change a few existing values without resetting everything.
     * If there are multiple windows, the -in option only gets processed for
     * the first window.
     */

    positionGiven = 0;
    for (j = 0; j < numWindows; j++) {
	string = Tcl_GetString(objv[j]);
    	firstChar = string[0];

	/*
	 * '^' and 'x' cause us to skip a column. '-' is processed as part of
	 * its preceeding content.
	 */

	if ((firstChar == REL_VERT) || (firstChar == REL_SKIP)) {
	    defaultColumn++;
	    continue;
	}
	if (firstChar == REL_HORIZ) {
	    continue;
	}

	for (defaultColumnSpan = 1; j + defaultColumnSpan < numWindows;
		defaultColumnSpan++) {
	    string = Tcl_GetString(objv[j + defaultColumnSpan]);

	    if (*string != REL_HORIZ) {
		break;
	    }
	}

	if (TkGetWindowFromObj(interp, tkwin, objv[j], &content) != TCL_OK) {
	    return TCL_ERROR;
	}

	if (Tk_TopWinHierarchy(content)) {
	    Tcl_SetObjResult(interp, Tcl_ObjPrintf(
		    "can't manage \"%s\": it's a top-level window",
		    Tcl_GetString(objv[j])));
	    Tcl_SetErrorCode(interp, "TK", "GEOMETRY", "TOPLEVEL", NULL);
	    return TCL_ERROR;
	}
	contentPtr = GetGrid(content);

	/*
	 * The following statement is taken from tkPack.c:
	 *
	 * "If the content isn't currently managed, reset all of its
	 * configuration information to default values (there could be old
	 * values left from a previous packer)."
	 *
	 * I [D.S.] disagree with this statement. If a content is disabled
	 * (using "forget") and then re-enabled, I submit that 90% of the time
	 * the programmer will want it to retain its old configuration
	 * information. If the programmer doesn't want this behavior, then the
	 * defaults can be reestablished by hand, without having to worry
	 * about keeping track of the old state.
	 */

	for (i = numWindows; i < objc; i += 2) {
	    Tcl_GetIndexFromObjStruct(interp, objv[i], optionStrings,
		    sizeof(char *), "option", 0, &index);
	    switch ((enum options) index) {
	    case CONF_COLUMN:
		if (Tcl_GetIntFromObj(NULL, objv[i+1], &tmp) != TCL_OK
			|| tmp < 0) {
		    Tcl_SetObjResult(interp, Tcl_ObjPrintf(
			    "bad column value \"%s\": must be a non-negative integer",
			    Tcl_GetString(objv[i+1])));
		    Tcl_SetErrorCode(interp, "TK", "VALUE", "COLUMN", NULL);
		    return TCL_ERROR;
		}
		if (SetContentColumn(interp, contentPtr, tmp, -1) != TCL_OK) {
		    return TCL_ERROR;
		}
		break;
	    case CONF_COLUMNSPAN:
		if (Tcl_GetIntFromObj(NULL, objv[i+1], &tmp) != TCL_OK
			|| tmp <= 0) {
		    Tcl_SetObjResult(interp, Tcl_ObjPrintf(
			    "bad columnspan value \"%s\": must be a positive integer",
			    Tcl_GetString(objv[i+1])));
		    Tcl_SetErrorCode(interp, "TK", "VALUE", "SPAN", NULL);
		    return TCL_ERROR;
		}
		if (SetContentColumn(interp, contentPtr, -1, tmp) != TCL_OK) {
		    return TCL_ERROR;
		}
		break;
	    case CONF_IN:
		if (TkGetWindowFromObj(interp, tkwin, objv[i+1],
			&other) != TCL_OK) {
		    return TCL_ERROR;
		}
		if (other == content) {
		    Tcl_SetObjResult(interp, Tcl_NewStringObj(
			    "window can't be managed in itself", -1));
		    Tcl_SetErrorCode(interp, "TK", "GEOMETRY", "SELF", NULL);
		    return TCL_ERROR;
		}
		positionGiven = 1;
		containerPtr = GetGrid(other);
		InitContainerData(containerPtr);
		break;
	    case CONF_STICKY: {
		int sticky = StringToSticky(Tcl_GetString(objv[i+1]));

		if (sticky == -1) {
		    Tcl_SetObjResult(interp, Tcl_ObjPrintf(
			    "bad stickyness value \"%s\": must be"
			    " a string containing n, e, s, and/or w",
			    Tcl_GetString(objv[i+1])));
		    Tcl_SetErrorCode(interp, "TK", "VALUE", "STICKY", NULL);
		    return TCL_ERROR;
		}
		contentPtr->sticky = sticky;
		break;
	    }
	    case CONF_IPADX:
		if ((Tk_GetPixelsFromObj(NULL, content, objv[i+1],
			&tmp) != TCL_OK) || (tmp < 0)) {
		    Tcl_SetObjResult(interp, Tcl_ObjPrintf(
			    "bad ipadx value \"%s\": must be positive screen distance",
			    Tcl_GetString(objv[i+1])));
		    Tcl_SetErrorCode(interp, "TK", "VALUE", "INT_PAD", NULL);
		    return TCL_ERROR;
		}
		contentPtr->iPadX = tmp * 2;
		break;
	    case CONF_IPADY:
		if ((Tk_GetPixelsFromObj(NULL, content, objv[i+1],
			&tmp) != TCL_OK) || (tmp < 0)) {
		    Tcl_SetObjResult(interp, Tcl_ObjPrintf(
			    "bad ipady value \"%s\": must be positive screen distance",
			    Tcl_GetString(objv[i+1])));
		    Tcl_SetErrorCode(interp, "TK", "VALUE", "INT_PAD", NULL);
		    return TCL_ERROR;
		}
		contentPtr->iPadY = tmp * 2;
		break;
	    case CONF_PADX:
		if (TkParsePadAmount(interp, tkwin, objv[i+1],
			&contentPtr->padLeft, &contentPtr->padX) != TCL_OK) {
		    return TCL_ERROR;
		}
		break;
	    case CONF_PADY:
		if (TkParsePadAmount(interp, tkwin, objv[i+1],
			&contentPtr->padTop, &contentPtr->padY) != TCL_OK) {
		    return TCL_ERROR;
		}
		break;
	    case CONF_ROW:
		if (Tcl_GetIntFromObj(NULL, objv[i+1], &tmp) != TCL_OK
			|| tmp < 0) {
		    Tcl_SetObjResult(interp, Tcl_ObjPrintf(
			    "bad row value \"%s\": must be a non-negative integer",
			    Tcl_GetString(objv[i+1])));
		    Tcl_SetErrorCode(interp, "TK", "VALUE", "COLUMN", NULL);
		    return TCL_ERROR;
		}
		if (SetContentRow(interp, contentPtr, tmp, -1) != TCL_OK) {
		    return TCL_ERROR;
		}
		break;
	    case CONF_ROWSPAN:
		if ((Tcl_GetIntFromObj(NULL, objv[i+1], &tmp) != TCL_OK)
			|| tmp <= 0) {
		    Tcl_SetObjResult(interp, Tcl_ObjPrintf(
			    "bad rowspan value \"%s\": must be a positive integer",
			    Tcl_GetString(objv[i+1])));
		    Tcl_SetErrorCode(interp, "TK", "VALUE", "SPAN", NULL);
		    return TCL_ERROR;
		}
		if (SetContentRow(interp, contentPtr, -1, tmp) != TCL_OK) {
		    return TCL_ERROR;
		}
		break;
	    }
	}

	/*
	 * If no position was specified via -in and the content is already
	 * packed, then leave it in its current location.
	 */

    	if (!positionGiven && (contentPtr->containerPtr != NULL)) {
	    containerPtr = contentPtr->containerPtr;
	    goto scheduleLayout;
    	}

	/*
	 * If the same -in window is passed in again, then just leave it in
	 * its current location.
	 */

	if (positionGiven && (containerPtr == contentPtr->containerPtr)) {
	    goto scheduleLayout;
	}

	/*
	 * Make sure we have a geometry container. We look at:
	 *  1)   the -in flag
	 *  2)   the parent of the first content.
	 */

	parent = Tk_Parent(content);
    	if (containerPtr == NULL) {
	    containerPtr = GetGrid(parent);
	    InitContainerData(containerPtr);
    	}

	if (contentPtr->containerPtr != NULL && contentPtr->containerPtr != containerPtr) {
            if (contentPtr->containerPtr->tkwin != Tk_Parent(contentPtr->tkwin)) {
                Tk_UnmaintainGeometry(contentPtr->tkwin, contentPtr->containerPtr->tkwin);
            }
	    Unlink(contentPtr);
	    contentPtr->containerPtr = NULL;
	}

	if (contentPtr->containerPtr == NULL) {
	    Gridder *tempPtr = containerPtr->contentPtr;

	    contentPtr->containerPtr = containerPtr;
	    containerPtr->contentPtr = contentPtr;
	    contentPtr->nextPtr = tempPtr;
	}

	/*
	 * Make sure that the content's parent is either the container or an
	 * ancestor of the container, and that the container and content aren't the
	 * same.
	 */

	for (ancestor = containerPtr->tkwin; ; ancestor = Tk_Parent(ancestor)) {
	    if (ancestor == parent) {
		break;
	    }
	    if (Tk_TopWinHierarchy(ancestor)) {
		Tcl_SetObjResult(interp, Tcl_ObjPrintf(
			"can't put %s inside %s", Tcl_GetString(objv[j]),
			Tk_PathName(containerPtr->tkwin)));
		Tcl_SetErrorCode(interp, "TK", "GEOMETRY", "HIERARCHY", NULL);
		Unlink(contentPtr);
		return TCL_ERROR;
	    }
	}

	/*
	 * Check for management loops.
	 */

	for (container = (TkWindow *)containerPtr->tkwin; container != NULL;
	     container = (TkWindow *)TkGetContainer(container)) {
	    if (container == (TkWindow *)content) {
		Tcl_SetObjResult(interp, Tcl_ObjPrintf(
		    "can't put %s inside %s, would cause management loop",
	            Tcl_GetString(objv[j]), Tk_PathName(containerPtr->tkwin)));
		Tcl_SetErrorCode(interp, "TK", "GEOMETRY", "LOOP", NULL);
		Unlink(contentPtr);
		return TCL_ERROR;
	    }
	}
	if (containerPtr->tkwin != Tk_Parent(content)) {
	    ((TkWindow *)content)->maintainerPtr = (TkWindow *)containerPtr->tkwin;
	}

	Tk_ManageGeometry(content, &gridMgrType, contentPtr);

	if (!(containerPtr->flags & DONT_PROPAGATE)) {
	    if (TkSetGeometryContainer(interp, containerPtr->tkwin, "grid")
		    != TCL_OK) {
		Tk_ManageGeometry(content, NULL, NULL);
		Unlink(contentPtr);
		return TCL_ERROR;
	    }
	    containerPtr->flags |= ALLOCED_CONTAINER;
	}

	/*
	 * Assign default position information.
	 */

	if (contentPtr->column == -1) {
	    if (SetContentColumn(interp, contentPtr, defaultColumn,-1) != TCL_OK){
		return TCL_ERROR;
	    }
	}
	if (SetContentColumn(interp, contentPtr, -1,
		contentPtr->numCols + defaultColumnSpan - 1) != TCL_OK) {
	    return TCL_ERROR;
	}
	if (contentPtr->row == -1) {
	    if (SetContentRow(interp, contentPtr, defaultRow, -1) != TCL_OK) {
		return TCL_ERROR;
	    }
	}
	defaultColumn += contentPtr->numCols;
	defaultColumnSpan = 1;

	/*
	 * Arrange for the container to be re-arranged at the first idle moment.
	 */

    scheduleLayout:
	if (containerPtr->abortPtr != NULL) {
	    *containerPtr->abortPtr = 1;
	}
	if (!(containerPtr->flags & REQUESTED_RELAYOUT)) {
	    containerPtr->flags |= REQUESTED_RELAYOUT;
	    Tcl_DoWhenIdle(ArrangeGrid, containerPtr);
	}
    }

    /*
     * Now look for all the "^"'s.
     */

    lastWindow = NULL;
    numSkip = 0;
    for (j = 0; j < numWindows; j++) {
	struct Gridder *otherPtr;
	int match;			/* Found a match for the ^ */
	int lastRow, lastColumn;	/* Implied end of table. */

	string = Tcl_GetString(objv[j]);
	firstChar = string[0];

	if (firstChar == '.') {
	    lastWindow = string;
	    numSkip = 0;
	}
	if (firstChar == REL_SKIP) {
	    numSkip++;
	}
	if (firstChar != REL_VERT) {
	    continue;
	}

	if (containerPtr == NULL) {
	    Tcl_SetObjResult(interp, Tcl_NewStringObj(
		    "can't use '^', cant find master", -1));
	    Tcl_SetErrorCode(interp, "TK", "GRID", "SHORTCUT_USAGE", NULL);
	    return TCL_ERROR;
	}

	/*
	 * Count the number of consecutive ^'s starting from this position.
	 */

	for (width = 1; width + j < numWindows; width++) {
	    string = Tcl_GetString(objv[j+width]);

	    if (*string != REL_VERT) {
		break;
	    }
	}

	/*
	 * Find the implied grid location of the ^
	 */

	if (lastWindow == NULL) {
	    lastRow = defaultRow - 1;
	    lastColumn = 0;
	} else {
	    other = Tk_NameToWindow(interp, lastWindow, tkwin);
	    otherPtr = GetGrid(other);
	    lastRow = otherPtr->row + otherPtr->numRows - 2;
	    lastColumn = otherPtr->column + otherPtr->numCols;
	}

	lastColumn += numSkip;

	match = 0;
	for (contentPtr = containerPtr->contentPtr; contentPtr != NULL;
		contentPtr = contentPtr->nextPtr) {

	    if (contentPtr->column == lastColumn
		    && contentPtr->row + contentPtr->numRows - 1 == lastRow) {
		if (contentPtr->numCols <= width) {
		    if (SetContentRow(interp, contentPtr, -1,
			    contentPtr->numRows + 1) != TCL_OK) {
			return TCL_ERROR;
		    }
		    match++;
		    j += contentPtr->numCols - 1;
		    lastWindow = Tk_PathName(contentPtr->tkwin);
		    numSkip = 0;
		    break;
		}
	    }
	}
	if (!match) {
	    Tcl_SetObjResult(interp, Tcl_NewStringObj(
		    "can't find slave to extend with \"^\"", -1));
	    Tcl_SetErrorCode(interp, "TK", "GRID", "SHORTCUT_USAGE", NULL);
	    return TCL_ERROR;
	}
    }

    if (containerPtr == NULL) {
	Tcl_SetObjResult(interp, Tcl_NewStringObj(
		"can't determine master window", -1));
	Tcl_SetErrorCode(interp, "TK", "GRID", "SHORTCUT_USAGE", NULL);
	return TCL_ERROR;
    }
    SetGridSize(containerPtr);

    /*
     * If we have emptied this container from content it means we are no longer
     * handling it and should mark it as free.
     */

    if (containerPtr->contentPtr == NULL && containerPtr->flags & ALLOCED_CONTAINER) {
	TkFreeGeometryContainer(containerPtr->tkwin, "grid");
	containerPtr->flags &= ~ALLOCED_CONTAINER;
    }

    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * StickyToObj
 *
 *	Converts the internal boolean combination of "sticky" bits onto a Tcl
 *	list element containing zero or more of n, s, e, or w.
 *
 * Results:
 *	A new object is returned that holds the sticky representation.
 *
 * Side effects:
 *	none.
 *
 *----------------------------------------------------------------------
 */

static Tcl_Obj *
StickyToObj(
    int flags)			/* The sticky flags. */
{
    int count = 0;
    char buffer[4];

    if (flags & STICK_NORTH) {
    	buffer[count++] = 'n';
    }
    if (flags & STICK_EAST) {
    	buffer[count++] = 'e';
    }
    if (flags & STICK_SOUTH) {
    	buffer[count++] = 's';
    }
    if (flags & STICK_WEST) {
    	buffer[count++] = 'w';
    }
    return Tcl_NewStringObj(buffer, count);
}

/*
 *----------------------------------------------------------------------
 *
 * StringToSticky --
 *
 *	Converts an ascii string representing a widgets stickyness into the
 *	boolean result.
 *
 * Results:
 *	The boolean combination of the "sticky" bits is retuned. If an error
 *	occurs, such as an invalid character, -1 is returned instead.
 *
 * Side effects:
 *	none
 *
 *----------------------------------------------------------------------
 */

static int
StringToSticky(
    const char *string)
{
    int sticky = 0;
    char c;

    while ((c = *string++) != '\0') {
	switch (c) {
	case 'n': case 'N':
	    sticky |= STICK_NORTH;
	    break;
	case 'e': case 'E':
	    sticky |= STICK_EAST;
	    break;
	case 's': case 'S':
	    sticky |= STICK_SOUTH;
	    break;
	case 'w': case 'W':
	    sticky |= STICK_WEST;
	    break;
	case ' ': case ',': case '\t': case '\r': case '\n':
	    break;
	default:
	    return -1;
	}
    }
    return sticky;
}

/*
 *----------------------------------------------------------------------
 *
 * NewPairObj --
 *
 *	Creates a new list object and fills it with two integer objects.
 *
 * Results:
 *	The newly created list object is returned.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static Tcl_Obj *
NewPairObj(
    int val1, int val2)
{
    Tcl_Obj *ary[2];

    ary[0] = Tcl_NewIntObj(val1);
    ary[1] = Tcl_NewIntObj(val2);
    return Tcl_NewListObj(2, ary);
}

/*
 *----------------------------------------------------------------------
 *
 * NewQuadObj --
 *
 *	Creates a new list object and fills it with four integer objects.
 *
 * Results:
 *	The newly created list object is returned.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static Tcl_Obj *
NewQuadObj(
    int val1, int val2, int val3, int val4)
{
    Tcl_Obj *ary[4];

    ary[0] = Tcl_NewIntObj(val1);
    ary[1] = Tcl_NewIntObj(val2);
    ary[2] = Tcl_NewIntObj(val3);
    ary[3] = Tcl_NewIntObj(val4);
    return Tcl_NewListObj(4, ary);
}

/*
 * Local Variables:
 * mode: c
 * c-basic-offset: 4
 * fill-column: 78
 * End:
 */
