/*
 * 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.
 *
 * SCCS: @(#) tkGrid.c 1.39 97/10/10 10:12:03
 */

#include "tkInt.h"

/*
 * Convenience Macros
 */

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

#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 */

/* 
 * 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. */

/*
 *  Structure to hold information for grid masters.  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 slave in the slot. */
	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 slave window in this bin.
    				 * Each bin contains a list of all
    				 * slaves 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 slaves 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. */
    int minOffset;		/* The minimum offset, in pixels, from
    				 * the beginning of the layout to the
    				 * right/bottom 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
    				 * right-or-bottom edge of the slot calculated
    				 * from bottom-or-right to top-or-left. */
} GridLayout;

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

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 slave. */
    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 slave. */
    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
    				 * parent. */
    int startY;			/* Pixel offset of this layout within its
    				 * parent. */
} GridMaster;

/*
 * For each window that the grid cares about (either because
 * the window is managed by the grid or because the window
 * has slaves 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 *masterPtr;	/* Master 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
				 * parent.  List order doesn't matter. */
    struct Gridder *slavePtr;	/* First in list of slaves managed
				 * inside this window (NULL means
				 * no grid slaves). */
    GridMaster *masterDataPtr;	/* Additional data for geometry master. */
    int column, row;		/* Location in the grid (starting
				 * from zero). */
    int numCols, numRows;	/* Number of columns or rows this slave spans.
				 * Should be at least 1. */
    int padX, padY;		/* Total additional pixels to leave around the
				 * window (half of this space is left on each
				 * side).  This is space *outside* the window:
				 * we'll allocate extra space in frame but
				 * won't enlarge window). */
    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 parent. */
    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 slaves
				 * 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 slave in this bin. */
    int size;			/* Nominal size (width or height) in pixels
    				 * of the slave.  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

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

#define REQUESTED_RELAYOUT	1
#define DONT_PROPAGATE		2

/*
 * Hash table used to map from Tk_Window tokens to corresponding
 * Grid structures:
 */

static Tcl_HashTable gridHashTable;
static int initialized = 0;

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

static void	AdjustForSticky _ANSI_ARGS_((Gridder *slavePtr, int *xPtr, 
		    int *yPtr, int *widthPtr, int *heightPtr));
static int	AdjustOffsets _ANSI_ARGS_((int width,
			int elements, SlotInfo *slotPtr));
static void	ArrangeGrid _ANSI_ARGS_((ClientData clientData));
static int	CheckSlotData _ANSI_ARGS_((Gridder *masterPtr, int slot,
			int slotType, int checkOnly));
static int	ConfigureSlaves _ANSI_ARGS_((Tcl_Interp *interp,
			Tk_Window tkwin, int argc, char *argv[]));
static void	DestroyGrid _ANSI_ARGS_((char *memPtr));
static Gridder *GetGrid _ANSI_ARGS_((Tk_Window tkwin));
static void	GridStructureProc _ANSI_ARGS_((
			ClientData clientData, XEvent *eventPtr));
static void	GridLostSlaveProc _ANSI_ARGS_((ClientData clientData,
			Tk_Window tkwin));
static void	GridReqProc _ANSI_ARGS_((ClientData clientData,
			Tk_Window tkwin));
static void 	InitMasterData _ANSI_ARGS_((Gridder *masterPtr));
static int	ResolveConstraints _ANSI_ARGS_((Gridder *gridPtr,
			int rowOrColumn, int maxOffset));
static void	SetGridSize _ANSI_ARGS_((Gridder *gridPtr));
static void	StickyToString _ANSI_ARGS_((int flags, char *result));
static int	StringToSticky _ANSI_ARGS_((char *string));
static void	Unlink _ANSI_ARGS_((Gridder *gridPtr));

static Tk_GeomMgr gridMgrType = {
    "grid",			/* name */
    GridReqProc,		/* requestProc */
    GridLostSlaveProc,		/* 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_GridCmd(clientData, interp, argc, argv)
    ClientData clientData;	/* Main window associated with
				 * interpreter. */
    Tcl_Interp *interp;		/* Current interpreter. */
    int argc;			/* Number of arguments. */
    char **argv;		/* Argument strings. */
{
    Tk_Window tkwin = (Tk_Window) clientData;
    Gridder *masterPtr;		/* master grid record */
    GridMaster *gridPtr;	/* pointer to grid data */
    size_t length;		/* streing length of argument */
    char c;			/* 1st character of argument */
  
    if ((argc >= 2) && ((argv[1][0] == '.') || (argv[1][0] == REL_SKIP) ||
    		(argv[1][0] == REL_VERT))) {
	return ConfigureSlaves(interp, tkwin, argc-1, argv+1);
    }
    if (argc < 3) {
	Tcl_AppendResult(interp, "wrong # args: should be \"",
		argv[0], " option arg ?arg ...?\"", (char *) NULL);
	return TCL_ERROR;
    }
    c = argv[1][0];
    length = strlen(argv[1]);
  
    if ((c == 'b') && (strncmp(argv[1], "bbox", length) == 0)) {
	Tk_Window master;
	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 (argc!=3 && argc != 5 && argc != 7) {
	    Tcl_AppendResult(interp, "wrong number of arguments: ",
		    "must be \"",argv[0],
		    " bbox master ?column row ?column row??\"",
		    (char *) NULL);
	    return TCL_ERROR;
	}
        
	master = Tk_NameToWindow(interp, argv[2], tkwin);
	if (master == NULL) {
	    return TCL_ERROR;
	}
	masterPtr = GetGrid(master);

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

	if (argc == 7) {
	    if (Tcl_GetInt(interp, argv[5], &column2) != TCL_OK) {
		return TCL_ERROR;
	    }
	    if (Tcl_GetInt(interp, argv[6], &row2) != TCL_OK) {
		return TCL_ERROR;
	    }
	}

	gridPtr = masterPtr->masterDataPtr;
	if (gridPtr == NULL) {
	    sprintf(interp->result, "%d %d %d %d",0,0,0,0);
	    return(TCL_OK);
	}

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

	if ((endX == 0) || (endY == 0)) {
	    sprintf(interp->result, "%d %d %d %d",0,0,0,0);
	    return(TCL_OK);
	}
	if (argc == 3) {
	    row = 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;
	} 

	sprintf(interp->result, "%d %d %d %d",
		x + gridPtr->startX, y + gridPtr->startY, width, height);
    } else if ((c == 'c') && (strncmp(argv[1], "configure", length) == 0)) {
	if (argv[2][0] != '.') {
	    Tcl_AppendResult(interp, "bad argument \"", argv[2],
		    "\": must be name of window", (char *) NULL);
	    return TCL_ERROR;
	}
	return ConfigureSlaves(interp, tkwin, argc-2, argv+2);
    } else if (((c == 'f') && (strncmp(argv[1], "forget", length) == 0))  || 
	    ((c == 'r') && (strncmp(argv[1], "remove", length) == 0))) {
	Tk_Window slave;
	Gridder *slavePtr;
	int i;
    
	for (i = 2; i < argc; i++) {
	    slave = Tk_NameToWindow(interp, argv[i], tkwin);
	    if (slave == NULL) {
		return TCL_ERROR;
	    }
	    slavePtr = GetGrid(slave);
	    if (slavePtr->masterPtr != NULL) {

	    	/*
	    	 * For "forget", reset all the settings to their defaults
	    	 */

	    	if (c == 'f') {
		    slavePtr->column = slavePtr->row = -1;
		    slavePtr->numCols = 1;
		    slavePtr->numRows = 1;
		    slavePtr->padX = slavePtr->padY = 0;
		    slavePtr->iPadX = slavePtr->iPadY = 0;
		    slavePtr->doubleBw = 2*Tk_Changes(tkwin)->border_width;
		    slavePtr->flags = 0;
		    slavePtr->sticky = 0;
	    	}
		Tk_ManageGeometry(slave, (Tk_GeomMgr *) NULL,
			(ClientData) NULL);
		if (slavePtr->masterPtr->tkwin != Tk_Parent(slavePtr->tkwin)) {
		    Tk_UnmaintainGeometry(slavePtr->tkwin,
			    slavePtr->masterPtr->tkwin);
		}
		Unlink(slavePtr);
		Tk_UnmapWindow(slavePtr->tkwin);
	    }
	}
    } else if ((c == 'i') && (strncmp(argv[1], "info", length) == 0)) {
	register Gridder *slavePtr;
	Tk_Window slave;
	char buffer[70];
    
	if (argc != 3) {
	    Tcl_AppendResult(interp, "wrong # args: should be \"",
		    argv[0], " info window\"", (char *) NULL);
	    return TCL_ERROR;
	}
	slave = Tk_NameToWindow(interp, argv[2], tkwin);
	if (slave == NULL) {
	    return TCL_ERROR;
	}
	slavePtr = GetGrid(slave);
	if (slavePtr->masterPtr == NULL) {
	    interp->result[0] = '\0';
	    return TCL_OK;
	}
    
	Tcl_AppendElement(interp, "-in");
	Tcl_AppendElement(interp, Tk_PathName(slavePtr->masterPtr->tkwin));
	sprintf(buffer, " -column %d -row %d -columnspan %d -rowspan %d",
		slavePtr->column, slavePtr->row,
		slavePtr->numCols, slavePtr->numRows);
	Tcl_AppendResult(interp, buffer, (char *) NULL);
	sprintf(buffer, " -ipadx %d -ipady %d -padx %d -pady %d",
		slavePtr->iPadX/2, slavePtr->iPadY/2, slavePtr->padX/2,
		slavePtr->padY/2);
	Tcl_AppendResult(interp, buffer, (char *) NULL);
	StickyToString(slavePtr->sticky,buffer);
	Tcl_AppendResult(interp, " -sticky ", buffer, (char *) NULL);
    } else if((c == 'l') && (strncmp(argv[1], "location", length) == 0)) {
	Tk_Window master;
	register SlotInfo *slotPtr;
	int x, y;		/* Offset in pixels, from edge of parent. */
	int i, j;		/* Corresponding column and row indeces. */
	int endX, endY;		/* end of grid */

	if (argc != 5) {
	    Tcl_AppendResult(interp, "wrong # args: should be \"",
		    argv[0], " location master x y\"", (char *)NULL);
	    return TCL_ERROR;
	}

	master = Tk_NameToWindow(interp, argv[2], tkwin);
	if (master == NULL) {
	    return TCL_ERROR;
	}

	if (Tk_GetPixels(interp, master, argv[3], &x) != TCL_OK) {
	    return TCL_ERROR;
	}
	if (Tk_GetPixels(interp, master, argv[4], &y) != TCL_OK) {
	    return TCL_ERROR;
	}

	masterPtr = GetGrid(master);
	if (masterPtr->masterDataPtr == NULL) {
	    sprintf(interp->result, "%d %d", -1, -1);
	    return TCL_OK;
	}
	gridPtr = masterPtr->masterDataPtr;

	/* 
	 * 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 (masterPtr->flags & REQUESTED_RELAYOUT) {
	    Tk_CancelIdleCall(ArrangeGrid, (ClientData) masterPtr);
	    ArrangeGrid ((ClientData) masterPtr);
	}
	SetGridSize(masterPtr);
	endX = MAX(gridPtr->columnEnd, gridPtr->columnMax);
	endY = MAX(gridPtr->rowEnd, gridPtr->rowMax);

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

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

	sprintf(interp->result, "%d %d", i, j);
    } else if ((c == 'p') && (strncmp(argv[1], "propagate", length) == 0)) {
	Tk_Window master;
	int propagate;
    
	if (argc > 4) {
	    Tcl_AppendResult(interp, "wrong # args: should be \"",
		    argv[0], " propagate window ?boolean?\"",
		    (char *) NULL);
	    return TCL_ERROR;
	}
	master = Tk_NameToWindow(interp, argv[2], tkwin);
	if (master == NULL) {
	    return TCL_ERROR;
	}
	masterPtr = GetGrid(master);
	if (argc == 3) {
	    interp->result = (masterPtr->flags & DONT_PROPAGATE) ? "0" : "1";
	    return TCL_OK;
	}
	if (Tcl_GetBoolean(interp, argv[3], &propagate) != TCL_OK) {
	    return TCL_ERROR;
	}
	if ((!propagate) ^ (masterPtr->flags&DONT_PROPAGATE)) {
	    masterPtr->flags  ^= DONT_PROPAGATE;
      
	    /*
	     * Re-arrange the master to allow new geometry information to
	     * propagate upwards to the master's master.
	     */
      
	    if (masterPtr->abortPtr != NULL) {
		*masterPtr->abortPtr = 1;
	    }
	    if (!(masterPtr->flags & REQUESTED_RELAYOUT)) {
		masterPtr->flags |= REQUESTED_RELAYOUT;
		Tcl_DoWhenIdle(ArrangeGrid, (ClientData) masterPtr);
	    }
	}
    } else if ((c == 's') && (strncmp(argv[1], "size", length) == 0)
	    && (length > 1)) {
	Tk_Window master;

	if (argc != 3) {
	    Tcl_AppendResult(interp, "wrong # args: should be \"",
		    argv[0], " size window\"", (char *) NULL);
	    return TCL_ERROR;
	}
	master = Tk_NameToWindow(interp, argv[2], tkwin);
	if (master == NULL) {
	    return TCL_ERROR;
	}
	masterPtr = GetGrid(master);

	if (masterPtr->masterDataPtr != NULL) {
	    SetGridSize(masterPtr);
	    gridPtr = masterPtr->masterDataPtr;
	    sprintf(interp->result, "%d %d",
		MAX(gridPtr->columnEnd, gridPtr->columnMax),
		MAX(gridPtr->rowEnd, gridPtr->rowMax));
	} else {
	    sprintf(interp->result, "%d %d",0, 0);
	}
    } else if ((c == 's') && (strncmp(argv[1], "slaves", length) == 0)
	    && (length > 1)) {
	Tk_Window master;
	Gridder *slavePtr;
	int i, value;
	int row = -1, column = -1;
 
	if ((argc < 3) || ((argc%2) == 0)) {
	    Tcl_AppendResult(interp, "wrong # args: should be \"",
		    argv[0], " slaves window ?-option value...?\"",
		    (char *) NULL);
	    return TCL_ERROR;
	}

	for (i=3; i<argc; i+=2) {
	    length = strlen(argv[i]);
	    if ((*argv[i] != '-') || (length < 2)) {
		Tcl_AppendResult(interp, "invalid args: should be \"",
			argv[0], " slaves window ?-option value...?\"",
			(char *) NULL);
		return TCL_ERROR;
	    }
	    if (Tcl_GetInt(interp, argv[i+1], &value) != TCL_OK) {
		return TCL_ERROR;
	    }
	    if (value < 0) {
		Tcl_AppendResult(interp, argv[i],
			" is an invalid value: should NOT be < 0",
			(char *) NULL);
		return TCL_ERROR;
	    }
	    if (strncmp(argv[i], "-column", length) == 0) {
		column = value;
	    } else if (strncmp(argv[i], "-row", length) == 0) {
		row = value;
	    } else {
		Tcl_AppendResult(interp, argv[i],
			" is an invalid option: should be \"",
			"-row, -column\"",
			(char *) NULL);
		return TCL_ERROR;
	    }
	}
	master = Tk_NameToWindow(interp, argv[2], tkwin);
	if (master == NULL) {
	    return TCL_ERROR;
	}
	masterPtr = GetGrid(master);

	for (slavePtr = masterPtr->slavePtr; slavePtr != NULL;
					     slavePtr = slavePtr->nextPtr) {
	    if (column>=0 && (slavePtr->column > column
		    || slavePtr->column+slavePtr->numCols-1 < column)) {
		continue;
	    }
	    if (row>=0 && (slavePtr->row > row ||
		    slavePtr->row+slavePtr->numRows-1 < row)) {
		continue;
	    }
	    Tcl_AppendElement(interp, Tk_PathName(slavePtr->tkwin));
	}

    /*
     * Sample argument combinations:
     *  grid columnconfigure <master> <index> -option
     *  grid columnconfigure <master> <index> -option value -option value
     *  grid rowconfigure <master> <index>
     *  grid rowconfigure <master> <index> -option
     *  grid rowconfigure <master> <index> -option value -option value.
     */
   
    } else if(((c == 'c') && (strncmp(argv[1], "columnconfigure", length) == 0)
	    && (length >= 3)) ||
            ((c == 'r') && (strncmp(argv[1], "rowconfigure", length) == 0) 
            && (length >=2))) {
	Tk_Window master;
	SlotInfo *slotPtr = NULL;
	int slot;		/* the column or row number */
	size_t length;		/* the # of chars in the "-option" string */
	int slotType;		/* COLUMN or ROW */
	int size;		/* the configuration value */
	int checkOnly;		/* check the size only */
	int argcPtr;		/* Number of items in index list */
	char **argvPtr;		/* array of indeces */
	char **indexP;		/* String value of current index list item. */
	int ok;			/* temporary TCL result code */
	int i;

	if (((argc%2 != 0) && (argc>6)) || (argc < 4)) {
	    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
		    " ", argv[1], " master index ?-option value...?\"",
		    (char *)NULL);
	    return TCL_ERROR;
	}

	master = Tk_NameToWindow(interp, argv[2], tkwin);
	if (master == NULL) {
	    return TCL_ERROR;
	}

	if (Tcl_SplitList(interp, argv[3], &argcPtr, &argvPtr) != TCL_OK) {
	    return TCL_ERROR;
	}

	checkOnly = ((argc == 4) || (argc == 5));
	masterPtr = GetGrid(master);
	slotType = (c == 'c') ? COLUMN : ROW;
	if (checkOnly && argcPtr > 1) {
	    Tcl_AppendResult(interp, argv[3],
		    " must be a single element.", (char *) NULL);
	    Tcl_Free((char *)argvPtr);
	    return TCL_ERROR;
	}
	for (indexP=argvPtr; *indexP != NULL; indexP++) {
	    if (Tcl_GetInt(interp, *indexP, &slot) != TCL_OK) {
		Tcl_Free((char *)argvPtr);
		return TCL_ERROR;
	    }
	    ok = CheckSlotData(masterPtr, slot, slotType, checkOnly);
	    if ((ok!=TCL_OK) && ((argc<4) || (argc>5))) {
		Tcl_AppendResult(interp, argv[0],
			" ", argv[1], ": \"", *argvPtr,"\" is out of range",
			(char *) NULL);
		Tcl_Free((char *)argvPtr);
		return TCL_ERROR;
	    } else if (ok == TCL_OK) {
		slotPtr = (slotType == COLUMN) ?
			masterPtr->masterDataPtr->columnPtr :
			masterPtr->masterDataPtr->rowPtr;
	    }

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

	    if (argc == 4) {
		Tcl_Free((char *)argvPtr);
	    }
	    if ((argc == 4) && (ok == TCL_OK)) {
		sprintf(interp->result,"-minsize %d -pad %d -weight %d",
			slotPtr[slot].minSize,slotPtr[slot].pad,
			slotPtr[slot].weight);
		return (TCL_OK);
	    } else if (argc == 4) {
		sprintf(interp->result,"-minsize %d -pad %d -weight %d", 0,0,0);
		return (TCL_OK);
	    }

	    /*
	     * Loop through each option value pair, setting the values as required.
	     * If only one option is given, with no value, the current value is
	     * returned.
	     */

	    for (i=4; i<argc; i+=2) {
		length = strlen(argv[i]);
		if ((*argv[i] != '-') || length < 2) {
		    Tcl_AppendResult(interp, "invalid arg \"",
			    argv[i], "\" :expecting -minsize, -pad, or -weight.",
			    (char *) NULL);
		    Tcl_Free((char *)argvPtr);
		    return TCL_ERROR;
		}
		if (strncmp(argv[i], "-minsize", length) == 0) {
		    if (argc == 5) {
		    	int value =  ok == TCL_OK ? slotPtr[slot].minSize : 0;
			sprintf(interp->result,"%d",value);
		    } else if (Tk_GetPixels(interp, master, argv[i+1], &size)
			    != TCL_OK) {
			Tcl_Free((char *)argvPtr);
			return TCL_ERROR;
		    } else {
			slotPtr[slot].minSize = size;
		    }
		}
		else if (strncmp(argv[i], "-weight", length) == 0) {
		    int wt;
		    if (argc == 5) {
		    	int value =  ok == TCL_OK ? slotPtr[slot].weight : 0;
			sprintf(interp->result,"%d",value);
		    } else if (Tcl_GetInt(interp, argv[i+1], &wt) != TCL_OK) {
			Tcl_Free((char *)argvPtr);
			return TCL_ERROR;
		    } else if (wt < 0) {
			Tcl_AppendResult(interp, "invalid arg \"", argv[i],
				"\": should be non-negative", (char *) NULL);
			Tcl_Free((char *)argvPtr);
			return TCL_ERROR;
		    } else {
			slotPtr[slot].weight = wt;
		    }
		}
		else if (strncmp(argv[i], "-pad", length) == 0) {
		    if (argc == 5) {
		    	int value =  ok == TCL_OK ? slotPtr[slot].pad : 0;
			sprintf(interp->result,"%d",value);
		    } else if (Tk_GetPixels(interp, master, argv[i+1], &size)
			    != TCL_OK) {
			Tcl_Free((char *)argvPtr);
			return TCL_ERROR;
		    } else if (size < 0) {
			Tcl_AppendResult(interp, "invalid arg \"", argv[i],
				"\": should be non-negative", (char *) NULL);
			Tcl_Free((char *)argvPtr);
			return TCL_ERROR;
		    } else {
			slotPtr[slot].pad = size;
		    }
		} else {
		    Tcl_AppendResult(interp, "invalid arg \"",
			    argv[i], "\": expecting -minsize, -pad, or -weight.",
			    (char *) NULL);
		    Tcl_Free((char *)argvPtr);
		    return TCL_ERROR;
		}
	    }
	}
	Tcl_Free((char *)argvPtr);

	/*
	 * If we changed a property, re-arrange the table,
	 * and check for constraint shrinkage.
	 */

	if (argc != 5) {
	    if (slotType == ROW) {
		int last = masterPtr->masterDataPtr->rowMax - 1;
		while ((last >= 0) && (slotPtr[last].weight == 0)
			&& (slotPtr[last].pad == 0)
			&& (slotPtr[last].minSize == 0)) {
		    last--;
		}
		masterPtr->masterDataPtr->rowMax = last+1;
	    } else {
		int last = masterPtr->masterDataPtr->columnMax - 1;
		while ((last >= 0) && (slotPtr[last].weight == 0)
			&& (slotPtr[last].pad == 0)
			&& (slotPtr[last].minSize == 0)) {
		    last--;
		}
		masterPtr->masterDataPtr->columnMax = last + 1;
	    }

	    if (masterPtr->abortPtr != NULL) {
		*masterPtr->abortPtr = 1;
	    }
	    if (!(masterPtr->flags & REQUESTED_RELAYOUT)) {
		masterPtr->flags |= REQUESTED_RELAYOUT;
		Tcl_DoWhenIdle(ArrangeGrid, (ClientData) masterPtr);
	    }
	}
    } else {
	Tcl_AppendResult(interp, "bad option \"", argv[1],
		"\":  must be bbox, columnconfigure, configure, forget, info, ",
		"location, propagate, remove, rowconfigure, size, or slaves.",
		(char *) NULL);
	return TCL_ERROR;
    }
    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, tkwin)
    ClientData clientData;	/* Grid's information about
				 * window that got new preferred
				 * geometry.  */
    Tk_Window tkwin;		/* Other Tk-related information
				 * about the window. */
{
    register Gridder *gridPtr = (Gridder *) clientData;

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

static void
GridLostSlaveProc(clientData, tkwin)
    ClientData clientData;	/* Grid structure for slave window that
				 * was stolen away. */
    Tk_Window tkwin;		/* Tk's handle for the slave window. */
{
    register Gridder *slavePtr = (Gridder *) clientData;

    if (slavePtr->masterPtr->tkwin != Tk_Parent(slavePtr->tkwin)) {
	Tk_UnmaintainGeometry(slavePtr->tkwin, slavePtr->masterPtr->tkwin);
    }
    Unlink(slavePtr);
    Tk_UnmapWindow(slavePtr->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 initial offset of the layout,
 *	if all the weights are zero, else 0.
 *
 * Side effects:
 *	The slot offsets are modified to shrink the layout.
 *
 *--------------------------------------------------------------
 */

static int
AdjustOffsets(size, slots, slotPtr)
    int size;			/* The total layout size (in pixels). */
    int slots;			/* Number of slots. */
    register SlotInfo *slotPtr;	/* Pointer to slot array. */
{
    register int slot;		/* Current slot. */
    int diff;			/* Extra pixels needed to add to the layout. */
    int totalWeight = 0;	/* Sum of the weights for all the slots. */
    int weight = 0;		/* Sum of the weights so far. */
    int minSize = 0;		/* 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(0);
    }

    /*
     * If all the weights are zero, center the layout in its parent if 
     * there is extra space, else clip on the bottom/right.
     */

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

    if (totalWeight == 0 ) {
	return(diff > 0 ? diff/2 : 0);
    }

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

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

    /*
     * The layout must shrink below its requested size.  Compute the
     * minimum possible size by looking at the slot minSizes.
     */

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

    /*
     * If the requested size is less than the minimum required size,
     * set the slot sizes to their minimum values, then clip on the 
     * bottom/right.
     */

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

    /*
     * 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.
	 */

	for (totalWeight=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;		/* max 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.
	 */

	for (weight=slot=0; slot < slots; slot++) {
	    weight += slotPtr[slot].temp;
	    slotPtr[slot].offset += newDiff * weight / totalWeight;
	}
    	diff -= newDiff;
    }
    return(0);
}

/*
 *--------------------------------------------------------------
 *
 * AdjustForSticky --
 *
 *	This procedure adjusts the size of a slave 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 slave.
 *
 * Side effects:
 *	None.
 *
 *--------------------------------------------------------------
 */

static void
AdjustForSticky(slavePtr, xPtr, yPtr, widthPtr, heightPtr)
    Gridder *slavePtr;	/* Slave 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 - slave width. */
    int diffy=0;	/* Cavity hight - slave height. */
    int sticky = slavePtr->sticky;

    *xPtr += slavePtr->padX/2;
    *widthPtr -= slavePtr->padX;
    *yPtr += slavePtr->padY/2;
    *heightPtr -= slavePtr->padY;

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

    if (*heightPtr > (Tk_ReqHeight(slavePtr->tkwin) + slavePtr->iPadY)) {
	diffy = *heightPtr - (Tk_ReqHeight(slavePtr->tkwin) + slavePtr->iPadY);
	*heightPtr = Tk_ReqHeight(slavePtr->tkwin) + slavePtr->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 slaves of masterPtr may get resized or moved.
 *
 *--------------------------------------------------------------
 */

static void
ArrangeGrid(clientData)
    ClientData clientData;	/* Structure describing parent whose slaves
				 * are to be re-layed out. */
{
    register Gridder *masterPtr = (Gridder *) clientData;
    register Gridder *slavePtr;	
    GridMaster *slotPtr = masterPtr->masterDataPtr;
    int abort;
    int width, height;		/* requested size of layout, in pixels */
    int realWidth, realHeight;	/* actual size layout should take-up */

    masterPtr->flags &= ~REQUESTED_RELAYOUT;

    /*
     * If the parent has no slaves anymore, then don't do anything
     * at all:  just leave the parent's size as-is.  Otherwise there is
     * no way to "relinquish" control over the parent so another geometry
     * manager can take over.
     */

    if (masterPtr->slavePtr == NULL) {
	return;
    }

    if (masterPtr->masterDataPtr == 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 (masterPtr->abortPtr != NULL) {
	*masterPtr->abortPtr = 1;
    }
    masterPtr->abortPtr = &abort;
    abort = 0;
    Tcl_Preserve((ClientData) masterPtr);

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

    SetGridSize(masterPtr);
    width =  ResolveConstraints(masterPtr, COLUMN, 0);
    height = ResolveConstraints(masterPtr, ROW, 0);
    width += 2*Tk_InternalBorderWidth(masterPtr->tkwin);
    height += 2*Tk_InternalBorderWidth(masterPtr->tkwin);

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

    /*
     * If the currently requested layout size doesn't match the parent's
     * window size, then adjust the slot offsets according to the
     * weights.  If all of the weights are zero, center the layout in 
     * its parent.  I haven't decided what to do if the parent is smaller
     * than the requested size.
     */

    realWidth = Tk_Width(masterPtr->tkwin) -
	    2*Tk_InternalBorderWidth(masterPtr->tkwin);
    realHeight = Tk_Height(masterPtr->tkwin) -
	    2*Tk_InternalBorderWidth(masterPtr->tkwin);
    slotPtr->startX = AdjustOffsets(realWidth,
	    MAX(slotPtr->columnEnd,slotPtr->columnMax), slotPtr->columnPtr);
    slotPtr->startY = AdjustOffsets(realHeight,
	    MAX(slotPtr->rowEnd,slotPtr->rowMax), slotPtr->rowPtr);
    slotPtr->startX += Tk_InternalBorderWidth(masterPtr->tkwin);
    slotPtr->startY += Tk_InternalBorderWidth(masterPtr->tkwin);

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

    for (slavePtr = masterPtr->slavePtr; slavePtr != NULL && !abort;
	    slavePtr = slavePtr->nextPtr) {
	int x, y;			/* top left coordinate */
	int width, height;		/* slot or slave size */
	int col = slavePtr->column;
	int row = slavePtr->row;

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

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

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

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

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

        if (masterPtr->tkwin == Tk_Parent(slavePtr->tkwin)) {
            if ((width <= 0) || (height <= 0)) {
                Tk_UnmapWindow(slavePtr->tkwin);
            } else {
                if ((x != Tk_X(slavePtr->tkwin))
                        || (y != Tk_Y(slavePtr->tkwin))
                        || (width != Tk_Width(slavePtr->tkwin))
                        || (height != Tk_Height(slavePtr->tkwin))) {
                    Tk_MoveResizeWindow(slavePtr->tkwin, x, y, width, height);
                }
                if (abort) {
                    break;
                }
 
                /*
                 * Don't map the slave if the master isn't mapped: wait
                 * until the master gets mapped later.
                 */
 
                if (Tk_IsMapped(masterPtr->tkwin)) {
                    Tk_MapWindow(slavePtr->tkwin);
                }
            }
        } else {
            if ((width <= 0) || (height <= 0)) {
                Tk_UnmaintainGeometry(slavePtr->tkwin, masterPtr->tkwin);
                Tk_UnmapWindow(slavePtr->tkwin);
            } else {
                Tk_MaintainGeometry(slavePtr->tkwin, masterPtr->tkwin,
                        x, y, width, height);
            }
        }
    }

    masterPtr->abortPtr = NULL;
    Tcl_Release((ClientData) masterPtr);
}

/*
 *--------------------------------------------------------------
 *
 * 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 master.
 *
 *--------------------------------------------------------------
 */

static int
ResolveConstraints(masterPtr, slotType, maxOffset) 
    Gridder *masterPtr;		/* The geometry master 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). */
{
    register SlotInfo *slotPtr;	/* Pointer to row/col constraints. */
    register Gridder *slavePtr;	/* List of slave 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
				 * accomodate all of the slaves 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. */

    /*
     * 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 = masterPtr->masterDataPtr->columnMax;
	slotCount = masterPtr->masterDataPtr->columnEnd;
	slotPtr  = masterPtr->masterDataPtr->columnPtr;
    } else {
	constraintCount = masterPtr->masterDataPtr->rowMax;
	slotCount = masterPtr->masterDataPtr->rowEnd;
	slotPtr  = masterPtr->masterDataPtr->rowPtr;
    }

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

    gridCount = MAX(constraintCount,slotCount);
    if (gridCount >= TYPICAL_SIZE) {
	layoutPtr = (GridLayout *) Tcl_Alloc(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].pad =  slotPtr[slot].pad;
        layoutPtr[slot].binNextPtr = NULL;
    }
    for(;slot<gridCount;slot++) {
        layoutPtr[slot].minSize = 0;
        layoutPtr[slot].weight = 0;
        layoutPtr[slot].pad = 0;
        layoutPtr[slot].binNextPtr = NULL;
    }

    /*
     * Step 2.
     * Slaves with a span of 1 are used to determine the minimum size of
     * each slot.  Slaves 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 slaves 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 slaves.
     */
 
    switch (slotType) {
    	case COLUMN:
	    for (slavePtr = masterPtr->slavePtr; slavePtr != NULL;
			slavePtr = slavePtr->nextPtr) {
		int rightEdge = slavePtr->column + slavePtr->numCols - 1;
		slavePtr->size = Tk_ReqWidth(slavePtr->tkwin) +
			slavePtr->padX + slavePtr->iPadX + slavePtr->doubleBw;
		if (slavePtr->numCols > 1) {
		    slavePtr->binNextPtr = layoutPtr[rightEdge].binNextPtr;
		    layoutPtr[rightEdge].binNextPtr = slavePtr;
		} else {
		    int size = slavePtr->size + layoutPtr[rightEdge].pad;
		    if (size > layoutPtr[rightEdge].minSize) {
			layoutPtr[rightEdge].minSize = size;
		    }
		}
	    }
	    break;
    	case ROW:
	    for (slavePtr = masterPtr->slavePtr; slavePtr != NULL;
			slavePtr = slavePtr->nextPtr) {
		int rightEdge = slavePtr->row + slavePtr->numRows - 1;
		slavePtr->size = Tk_ReqHeight(slavePtr->tkwin) +
			slavePtr->padY + slavePtr->iPadY + slavePtr->doubleBw;
		if (slavePtr->numRows > 1) {
		    slavePtr->binNextPtr = layoutPtr[rightEdge].binNextPtr;
		    layoutPtr[rightEdge].binNextPtr = slavePtr;
		} else {
		    int size = slavePtr->size + layoutPtr[rightEdge].pad;
		    if (size > layoutPtr[rightEdge].minSize) {
			layoutPtr[rightEdge].minSize = size;
		    }
		}
	    }
	    break;
	}

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

    for (offset=slot=0; slot < gridCount; slot++) {
        layoutPtr[slot].minOffset = layoutPtr[slot].minSize + offset;
        for (slavePtr = layoutPtr[slot].binNextPtr; slavePtr != NULL;
                    slavePtr = slavePtr->binNextPtr) {
	    int span = (slotType == COLUMN) ? slavePtr->numCols : slavePtr->numRows;
            int required = slavePtr->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 "master" 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 (slavePtr = layoutPtr[slot].binNextPtr; slavePtr != NULL;
                    slavePtr = slavePtr->binNextPtr) {
	    int span = (slotType == COLUMN) ? slavePtr->numCols : slavePtr->numRows;
            int require = offset - slavePtr->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.
	 * 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.
	 */

	for (weight=0,slot=start; slot<end; slot++) {
	    int diff = layoutPtr[slot].maxOffset - layoutPtr[slot].minOffset;
	    weight += noWeights ? 1 : layoutPtr[slot].weight;
	    if ((noWeights || layoutPtr[slot].weight>0) &&
		    (diff*totalWeight/weight) < (have-need)) {
		have = diff * totalWeight / weight + need;
	    }
	}

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

	for (weight=0,slot=start; slot<end; slot++) {
	    weight += noWeights ? 1 : layoutPtr[slot].weight;
	    layoutPtr[slot].minOffset +=
		(int)((double) (have-need) * weight/totalWeight + 0.5);
	    layoutPtr[slot].minSize = layoutPtr[slot].minOffset 
		    - layoutPtr[slot-1].minOffset;
	}
	layoutPtr[slot].minSize = layoutPtr[slot].minOffset 
		- layoutPtr[slot-1].minOffset;

	/*
	 * 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--) {
	    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 master.
     */

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

    --layoutPtr;
    if (layoutPtr != layoutData) {
	Tcl_Free((char *)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(tkwin)
    Tk_Window tkwin;		/* Token for window for which
				 * grid structure is desired. */
{
    register Gridder *gridPtr;
    Tcl_HashEntry *hPtr;
    int new;

    if (!initialized) {
	initialized = 1;
	Tcl_InitHashTable(&gridHashTable, TCL_ONE_WORD_KEYS);
    }

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

    hPtr = Tcl_CreateHashEntry(&gridHashTable, (char *) tkwin, &new);
    if (!new) {
	return (Gridder *) Tcl_GetHashValue(hPtr);
    }
    gridPtr = (Gridder *) Tcl_Alloc(sizeof(Gridder));
    gridPtr->tkwin = tkwin;
    gridPtr->masterPtr = NULL;
    gridPtr->masterDataPtr = NULL;
    gridPtr->nextPtr = NULL;
    gridPtr->slavePtr = NULL;
    gridPtr->binNextPtr = NULL;

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

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

static void
SetGridSize(masterPtr)
    Gridder *masterPtr;			/* The geometry master for this grid. */
{
    register Gridder *slavePtr;		/* Current slave window. */
    int maxX = 0, maxY = 0;

    for (slavePtr = masterPtr->slavePtr; slavePtr != NULL;
		slavePtr = slavePtr->nextPtr) {
	maxX = MAX(maxX,slavePtr->numCols + slavePtr->column);
	maxY = MAX(maxY,slavePtr->numRows + slavePtr->row);
    }
    masterPtr->masterDataPtr->columnEnd = maxX;
    masterPtr->masterDataPtr->rowEnd = maxY;
    CheckSlotData(masterPtr, maxX, COLUMN, CHECK_SPACE);
    CheckSlotData(masterPtr, maxY, ROW, CHECK_SPACE);
}

/*
 *--------------------------------------------------------------
 *
 * 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 master 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(masterPtr, slot, slotType, checkOnly)
    Gridder *masterPtr;	/* the geometry master 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) && (masterPtr->masterDataPtr == 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.
     */

    InitMasterData(masterPtr);
    end = (slotType == ROW) ? masterPtr->masterDataPtr->rowMax :
	    masterPtr->masterDataPtr->columnMax;
    if (checkOnly == CHECK_ONLY) {
    	return  (end < slot) ? TCL_ERROR : TCL_OK;
    } else {
    	numSlot = (slotType == ROW) ? masterPtr->masterDataPtr->rowSpace 
	                            : masterPtr->masterDataPtr->columnSpace;
    	if (slot >= numSlot) {
	    int      newNumSlot = slot + PREALLOC ;
	    size_t   oldSize = numSlot    * sizeof(SlotInfo) ;
	    size_t   newSize = newNumSlot * sizeof(SlotInfo) ;
	    SlotInfo *new = (SlotInfo *) Tcl_Alloc(newSize);
	    SlotInfo *old = (slotType == ROW) ?
		    masterPtr->masterDataPtr->rowPtr :
		    masterPtr->masterDataPtr->columnPtr;
	    memcpy((VOID *) new, (VOID *) old, oldSize );
	    memset((VOID *) (new+numSlot), 0, newSize - oldSize );
	    Tcl_Free((char *) old);
	    if (slotType == ROW) {
	 	masterPtr->masterDataPtr->rowPtr = new ;
	    	masterPtr->masterDataPtr->rowSpace = newNumSlot ;
	    } else {
	    	masterPtr->masterDataPtr->columnPtr = new;
	    	masterPtr->masterDataPtr->columnSpace = newNumSlot ;
	    }
	}
	if (slot >= end && checkOnly != CHECK_SPACE) {
	    if (slotType == ROW) {
		masterPtr->masterDataPtr->rowMax = slot+1;
	    } else {
		masterPtr->masterDataPtr->columnMax = slot+1;
	    }
	}
    	return TCL_OK;
    }
}

/*
 *--------------------------------------------------------------
 *
 * InitMasterData --
 *
 *	This internal procedure is used to allocate and initialize
 *	the data for a geometry master, if the data
 *	doesn't exist already.
 *
 * Results:
 *	none
 *
 * Side effects:
 *	A new master grid structure may be created.  If so, then
 *	it is initialized.
 *
 *--------------------------------------------------------------
 */

static void
InitMasterData(masterPtr)
    Gridder *masterPtr;
{
    size_t size;
    if (masterPtr->masterDataPtr == NULL) {
	GridMaster *gridPtr = masterPtr->masterDataPtr =
		(GridMaster *) Tcl_Alloc(sizeof(GridMaster));
	size = sizeof(SlotInfo) * TYPICAL_SIZE;

	gridPtr->columnEnd = 0;
	gridPtr->columnMax = 0;
	gridPtr->columnPtr = (SlotInfo *) Tcl_Alloc(size);
	gridPtr->columnSpace = 0;
	gridPtr->columnSpace = TYPICAL_SIZE;
	gridPtr->rowEnd = 0;
	gridPtr->rowMax = 0;
	gridPtr->rowPtr = (SlotInfo *) Tcl_Alloc(size);
	gridPtr->rowSpace = 0;
	gridPtr->rowSpace = TYPICAL_SIZE;

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

static void
Unlink(slavePtr)
    register Gridder *slavePtr;		/* Window to unlink. */
{
    register Gridder *masterPtr, *slavePtr2;
    GridMaster *gridPtr;	/* pointer to grid data */

    masterPtr = slavePtr->masterPtr;
    if (masterPtr == NULL) {
	return;
    }

    gridPtr = masterPtr->masterDataPtr;
    if (masterPtr->slavePtr == slavePtr) {
	masterPtr->slavePtr = slavePtr->nextPtr;
    }
    else {
	for (slavePtr2 = masterPtr->slavePtr; ; slavePtr2 = slavePtr2->nextPtr) {
	    if (slavePtr2 == NULL) {
		panic("Unlink couldn't find previous window");
	    }
	    if (slavePtr2->nextPtr == slavePtr) {
		slavePtr2->nextPtr = slavePtr->nextPtr;
		break;
	    }
	}
    }
    if (!(masterPtr->flags & REQUESTED_RELAYOUT)) {
	masterPtr->flags |= REQUESTED_RELAYOUT;
	Tcl_DoWhenIdle(ArrangeGrid, (ClientData) masterPtr);
    }
    if (masterPtr->abortPtr != NULL) {
	*masterPtr->abortPtr = 1;
    }

    if ((slavePtr->numCols+slavePtr->column == gridPtr->columnMax)
	    || (slavePtr->numRows+slavePtr->row == gridPtr->rowMax)) {
    }
    slavePtr->masterPtr = NULL;
}

/*
 *----------------------------------------------------------------------
 *
 * DestroyGrid --
 *
 *	This procedure is invoked by Tk_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 master structure
 *	for geometry managers.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Everything associated with the grid is freed up.
 *
 *----------------------------------------------------------------------
 */

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

    if (gridPtr->masterDataPtr != NULL) {
	if (gridPtr->masterDataPtr->rowPtr != NULL) {
	    Tcl_Free((char *) gridPtr->masterDataPtr -> rowPtr);
	}
	if (gridPtr->masterDataPtr->columnPtr != NULL) {
	    Tcl_Free((char *) gridPtr->masterDataPtr -> columnPtr);
	}
	Tcl_Free((char *) gridPtr->masterDataPtr);
    }
    Tcl_Free((char *) 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 slaves, if
 *	any.
 *
 *----------------------------------------------------------------------
 */

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

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

	if (gridPtr->masterPtr != NULL) {
	    Unlink(gridPtr);
	}
	for (gridPtr2 = gridPtr->slavePtr; gridPtr2 != NULL;
					   gridPtr2 = nextPtr) {
	    Tk_UnmapWindow(gridPtr2->tkwin);
	    gridPtr2->masterPtr = NULL;
	    nextPtr = gridPtr2->nextPtr;
	    gridPtr2->nextPtr = NULL;
	}
	Tcl_DeleteHashEntry(Tcl_FindHashEntry(&gridHashTable,
		(char *) gridPtr->tkwin));
	if (gridPtr->flags & REQUESTED_RELAYOUT) {
	    Tk_CancelIdleCall(ArrangeGrid, (ClientData) gridPtr);
	}
	gridPtr->tkwin = NULL;
	Tk_EventuallyFree((ClientData) gridPtr, DestroyGrid);
    } else if (eventPtr->type == MapNotify) {
	if (!(gridPtr->flags & REQUESTED_RELAYOUT)) {
	    gridPtr->flags |= REQUESTED_RELAYOUT;
	    Tcl_DoWhenIdle(ArrangeGrid, (ClientData) gridPtr);
	}
    } else if (eventPtr->type == UnmapNotify) {
	register Gridder *gridPtr2;

	for (gridPtr2 = gridPtr->slavePtr; gridPtr2 != NULL;
					   gridPtr2 = gridPtr2->nextPtr) {
	    Tk_UnmapWindow(gridPtr2->tkwin);
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * ConfigureSlaves --
 *
 *	This implements the guts of the "grid configure" command.  Given
 *	a list of slaves and configuration options, it arranges for the
 *	grid to manage the slaves 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 interp->result is set to contain an error message.
 *
 * Side effects:
 *	Slave windows get taken over by the grid.
 *
 *----------------------------------------------------------------------
 */

static int
ConfigureSlaves(interp, tkwin, argc, argv)
    Tcl_Interp *interp;		/* Interpreter for error reporting. */
    Tk_Window tkwin;		/* Any window in application containing
				 * slaves.  Used to look up slave names. */
    int argc;			/* Number of elements in argv. */
    char *argv[];		/* Argument strings:  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 *masterPtr;
    Gridder *slavePtr;
    Tk_Window other, slave, parent, ancestor;
    int i, j, c, tmp;
    size_t length;
    int numWindows;
    int width;
    int defaultColumn = 0;	/* default column number */
    int defaultColumnSpan = 1;	/* default number of columns */
    char *lastWindow;		/* use this window to base current
				 * Row/col on */

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

    for(numWindows=i=0;i<argc;i++) {
    	char firstChar = *argv[i];
	if (firstChar == '.') {
	    numWindows++;
	    continue;
    	}
    	length = strlen(argv[i]);
    	if (length > 1 && firstChar == '-') {
	    break;
	}
	if (length > 1) {
	    Tcl_AppendResult(interp, "unexpected parameter, \"",
		    argv[i], "\", in configure list. ",
		    "Should be window name or option", (char *) NULL);
	    return TCL_ERROR;
	}

	if ((firstChar == REL_HORIZ) && ((numWindows == 0) ||
		(*argv[i-1] == REL_SKIP) || (*argv[i-1] == REL_VERT))) {
	    Tcl_AppendResult(interp,
		    "Must specify window before shortcut '-'.",
		    (char *) NULL);
	    return TCL_ERROR;
	}

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

	Tcl_AppendResult(interp, "invalid window shortcut, \"",
		argv[i], "\" should be '-', 'x', or '^'", (char *) NULL);
	return TCL_ERROR;
    }
    numWindows = i;

    if ((argc-numWindows)&1) {
	Tcl_AppendResult(interp, "extra option or",
		" option with no value", (char *) NULL);
	return TCL_ERROR;
    }

    /*
     * Iterate over all of the slave windows and short-cuts, parsing
     * options for each slave.  It's a bit wasteful to re-parse the
     * options for each slave, but things get too messy if we try to
     * parse the arguments just once at the beginning.  For example,
     * if a slave 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.
     */

    masterPtr = NULL;
    for (j = 0; j < numWindows; j++) {
    	char firstChar = *argv[j];

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

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

	for (defaultColumnSpan=1;
		j + defaultColumnSpan < numWindows &&
		(*argv[j+defaultColumnSpan] == REL_HORIZ);
		defaultColumnSpan++) {
	    /* null body */
	}

	slave = Tk_NameToWindow(interp, argv[j], tkwin);
	if (slave == NULL) {
	    return TCL_ERROR;
	}
	if (Tk_IsTopLevel(slave)) {
	    Tcl_AppendResult(interp, "can't manage \"", argv[j],
		    "\": it's a top-level window", (char *) NULL);
	    return TCL_ERROR;
	}
	slavePtr = GetGrid(slave);

	/*
	 * The following statement is taken from tkPack.c:
	 *
	 * "If the slave 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 slave 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 < argc; i+=2) {
	    length = strlen(argv[i]);
	    c = argv[i][1];

	    if (length < 2) {
		Tcl_AppendResult(interp, "unknown or ambiguous option \"",
			argv[i], "\": must be ",
			"-column, -columnspan, -in, -ipadx, -ipady, ",
			"-padx, -pady, -row, -rowspan, or -sticky",
			(char *) NULL);
		return TCL_ERROR;
	    }
	    if ((c == 'c') && (strncmp(argv[i], "-column", length) == 0)) {
		if (Tcl_GetInt(interp, argv[i+1], &tmp) != TCL_OK || tmp<0) {
		    Tcl_ResetResult(interp);
		    Tcl_AppendResult(interp, "bad column value \"", argv[i+1],
			    "\": must be a non-negative integer", (char *)NULL);
		    return TCL_ERROR;
		}
		slavePtr->column = tmp;
	    } else if ((c == 'c')
		    && (strncmp(argv[i], "-columnspan", length) == 0)) {
		if (Tcl_GetInt(interp, argv[i+1], &tmp) != TCL_OK || tmp <= 0) {
		    Tcl_ResetResult(interp);
		    Tcl_AppendResult(interp, "bad columnspan value \"", argv[i+1],
			    "\": must be a positive integer", (char *)NULL);
		    return TCL_ERROR;
		}
		slavePtr->numCols = tmp;
	    } else if ((c == 'i') && (strncmp(argv[i], "-in", length) == 0)) {
		other = Tk_NameToWindow(interp, argv[i+1], tkwin);
		if (other == NULL) {
		    return TCL_ERROR;
		}
		if (other == slave) {
		    sprintf(interp->result,"Window can't be managed in itself");
		    return TCL_ERROR;
		}
		masterPtr = GetGrid(other);
		InitMasterData(masterPtr);
	    } else if ((c == 'i')
		    && (strncmp(argv[i], "-ipadx", length) == 0)) {
		if ((Tk_GetPixels(interp, slave, argv[i+1], &tmp) != TCL_OK)
			|| (tmp < 0)) {
		    Tcl_ResetResult(interp);
		    Tcl_AppendResult(interp, "bad ipadx value \"", argv[i+1],
			    "\": must be positive screen distance",
			    (char *) NULL);
		    return TCL_ERROR;
		}
		slavePtr->iPadX = tmp*2;
	    } else if ((c == 'i')
		    && (strncmp(argv[i], "-ipady", length) == 0)) {
		if ((Tk_GetPixels(interp, slave, argv[i+1], &tmp) != TCL_OK)
			|| (tmp< 0)) {
		    Tcl_ResetResult(interp);
		    Tcl_AppendResult(interp, "bad ipady value \"", argv[i+1],
			    "\": must be positive screen distance",
			    (char *) NULL);
		    return TCL_ERROR;
		}
		slavePtr->iPadY = tmp*2;
	    } else if ((c == 'p')
		    && (strncmp(argv[i], "-padx", length) == 0)) {
		if ((Tk_GetPixels(interp, slave, argv[i+1], &tmp) != TCL_OK)
			|| (tmp< 0)) {
		    Tcl_ResetResult(interp);
		    Tcl_AppendResult(interp, "bad padx value \"", argv[i+1],
			    "\": must be positive screen distance",
			    (char *) NULL);
		    return TCL_ERROR;
		}
		slavePtr->padX = tmp*2;
	    } else if ((c == 'p')
		    && (strncmp(argv[i], "-pady", length) == 0)) {
		if ((Tk_GetPixels(interp, slave, argv[i+1], &tmp) != TCL_OK)
			|| (tmp< 0)) {
		    Tcl_ResetResult(interp);
		    Tcl_AppendResult(interp, "bad pady value \"", argv[i+1],
			    "\": must be positive screen distance",
			    (char *) NULL);
		    return TCL_ERROR;
		}
		slavePtr->padY = tmp*2;
	    } else if ((c == 'r') && (strncmp(argv[i], "-row", length) == 0)) {
		if (Tcl_GetInt(interp, argv[i+1], &tmp) != TCL_OK || tmp<0) {
		    Tcl_ResetResult(interp);
		    Tcl_AppendResult(interp, "bad grid value \"", argv[i+1],
			    "\": must be a non-negative integer", (char *)NULL);
		    return TCL_ERROR;
		}
		slavePtr->row = tmp;
	    } else if ((c == 'r')
		    && (strncmp(argv[i], "-rowspan", length) == 0)) {
		if ((Tcl_GetInt(interp, argv[i+1], &tmp) != TCL_OK) || tmp<=0) {
		    Tcl_ResetResult(interp);
		    Tcl_AppendResult(interp, "bad rowspan value \"", argv[i+1],
			    "\": must be a positive integer", (char *)NULL);
		    return TCL_ERROR;
		}
		slavePtr->numRows = tmp;
	    } else if ((c == 's')
		    && strncmp(argv[i], "-sticky", length) == 0) {
		int sticky = StringToSticky(argv[i+1]);
		if (sticky == -1) {
		    Tcl_AppendResult(interp, "bad stickyness value \"", argv[i+1],
			    "\": must be a string containing n, e, s, and/or w",
			    (char *)NULL);
		    return TCL_ERROR;
		}
		slavePtr->sticky = sticky;
	    } else {
		Tcl_AppendResult(interp, "unknown or ambiguous option \"",
			argv[i], "\": must be ",
			"-column, -columnspan, -in, -ipadx, -ipady, ",
			"-padx, -pady, -row, -rowspan, or -sticky",
			(char *) NULL);
		return TCL_ERROR;
	    }
	}

	/*
	 * Make sure we have a geometry master.  We look at:
	 *  1)   the -in flag
	 *  2)   the geometry master of the first slave (if specified)
	 *  3)   the parent of the first slave.
	 */
    
    	if (masterPtr == NULL) {
	    masterPtr = slavePtr->masterPtr;
    	}
	parent = Tk_Parent(slave);
    	if (masterPtr == NULL) {
	    masterPtr = GetGrid(parent);
	    InitMasterData(masterPtr);
    	}

	if (slavePtr->masterPtr != NULL && slavePtr->masterPtr != masterPtr) {
	    Unlink(slavePtr);
	    slavePtr->masterPtr = NULL;
	}

	if (slavePtr->masterPtr == NULL) {
	    Gridder *tempPtr = masterPtr->slavePtr;
	    slavePtr->masterPtr = masterPtr;
	    masterPtr->slavePtr = slavePtr;
	    slavePtr->nextPtr = tempPtr;
	}

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

	for (ancestor = masterPtr->tkwin; ; ancestor = Tk_Parent(ancestor)) {
	    if (ancestor == parent) {
		break;
	    }
	    if (Tk_IsTopLevel(ancestor)) {
		Tcl_AppendResult(interp, "can't put ", argv[j],
			" inside ", Tk_PathName(masterPtr->tkwin),
			(char *) NULL);
		Unlink(slavePtr);
		return TCL_ERROR;
	    }
	}

	/*
	 * Try to make sure our master isn't managed by us.
	 */

     	if (masterPtr->masterPtr == slavePtr) {
	    Tcl_AppendResult(interp, "can't put ", argv[j],
		    " inside ", Tk_PathName(masterPtr->tkwin),
		    ", would cause management loop.", 
		    (char *) NULL);
	    Unlink(slavePtr);
	    return TCL_ERROR;
     	}

	Tk_ManageGeometry(slave, &gridMgrType, (ClientData) slavePtr);

	/*
	 * Assign default position information.
	 */

	if (slavePtr->column == -1) {
	    slavePtr->column = defaultColumn;
	}
	slavePtr->numCols += defaultColumnSpan - 1;
	if (slavePtr->row == -1) {
	    if (masterPtr->masterDataPtr == NULL) {
	    	slavePtr->row = 0;
	    } else {
	    	slavePtr->row = masterPtr->masterDataPtr->rowEnd;
	    }
	}
	defaultColumn += slavePtr->numCols;
	defaultColumnSpan = 1;

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

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

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

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

    	if (*argv[j] == '.') {
	    lastWindow = argv[j];
	}
	if (*argv[j] != REL_VERT) {
	    continue;
	}

	if (masterPtr == NULL) {
	    Tcl_AppendResult(interp, "can't use '^', cant find master",
		    (char *) NULL);
	    return TCL_ERROR;
	}

	for (width=1; width+j < numWindows && *argv[j+width] == REL_VERT;
		width++) {
	    /* Null Body */
	}

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

	if (lastWindow == NULL) { 
	    if (masterPtr->masterDataPtr != NULL) {
		SetGridSize(masterPtr);
		lastRow = masterPtr->masterDataPtr->rowEnd - 1;
	    } else {
		lastRow = 0;
	    }
	    lastColumn = 0;
	} else {
	    other = Tk_NameToWindow(interp, lastWindow, tkwin);
	    otherPtr = GetGrid(other);
	    lastRow = otherPtr->row;
	    lastColumn = otherPtr->column + otherPtr->numCols;
	}

	for (match=0, slavePtr = masterPtr->slavePtr; slavePtr != NULL;
					 slavePtr = slavePtr->nextPtr) {

	    if (slavePtr->numCols == width
		    && slavePtr->column == lastColumn
		    && slavePtr->row + slavePtr->numRows == lastRow) {
		slavePtr->numRows++;
		match++;
	    }
	    lastWindow = Tk_PathName(slavePtr->tkwin);
	}
	if (!match) {
	    Tcl_AppendResult(interp, "can't find slave to extend with \"^\".",
		    (char *) NULL);
	    return TCL_ERROR;
	}
	j += width - 1;
    }

    if (masterPtr == NULL) {
	Tcl_AppendResult(interp, "can't determine master window",
		(char *) NULL);
	return TCL_ERROR;
    }
    SetGridSize(masterPtr);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * StickyToString
 *
 *	Converts the internal boolean combination of "sticky" bits onto
 *	a TCL list element containing zero or mor of n, s, e, or w.
 *
 * Results:
 *	A string is placed into the "result" pointer.
 *
 * Side effects:
 *	none.
 *
 *----------------------------------------------------------------------
 */

static void
StickyToString(flags, result)
    int flags;		/* the sticky flags */
    char *result;	/* where to put the result */
{
    int count = 0;
    if (flags&STICK_NORTH) {
    	result[count++] = 'n';
    }
    if (flags&STICK_EAST) {
    	result[count++] = 'e';
    }
    if (flags&STICK_SOUTH) {
    	result[count++] = 's';
    }
    if (flags&STICK_WEST) {
    	result[count++] = 'w';
    }
    if (count) {
	result[count] = '\0';
    } else {
	sprintf(result,"{}");
    }
}

/*
 *----------------------------------------------------------------------
 *
 * 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(string)
    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;
}		
