diff options
Diffstat (limited to 'tk8.6/generic/tkGrid.c')
-rw-r--r-- | tk8.6/generic/tkGrid.c | 3669 |
1 files changed, 3669 insertions, 0 deletions
diff --git a/tk8.6/generic/tkGrid.c b/tk8.6/generic/tkGrid.c new file mode 100644 index 0000000..62c9c59 --- /dev/null +++ b/tk8.6/generic/tkGrid.c @@ -0,0 +1,3669 @@ +/* + * 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 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. */ + 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 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. */ + 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 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 + * master. */ + int startY; /* Pixel offset of this layout within its + * master. */ + Tk_Anchor anchor; /* Value of anchor option: specifies where a + * grid without weight should be placed. */ +} 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 master. + * 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. */ + Tcl_Obj *in; /* Store master name when removed. */ + 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. 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 master. */ + 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 + + +/* + * 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 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. + * ALLOCED_MASTER 1 means that Grid has allocated itself as + * geometry master for this window. + */ + +#define REQUESTED_RELAYOUT 1 +#define DONT_PROPAGATE 2 +#define ALLOCED_MASTER 4 + +/* + * Prototypes for procedures used only in this file: + */ + +static void AdjustForSticky(Gridder *slavePtr, 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 *masterPtr, int slot, + int slotType, int checkOnly); +static int ConfigureSlaves(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 GridSlavesCommand(Tk_Window tkwin, Tcl_Interp *interp, + int objc, Tcl_Obj *const objv[]); +static void GridStructureProc(ClientData clientData, + XEvent *eventPtr); +static void GridLostSlaveProc(ClientData clientData, + Tk_Window tkwin); +static void GridReqProc(ClientData clientData, Tk_Window tkwin); +static void InitMasterData(Gridder *masterPtr); +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 SetSlaveColumn(Tcl_Interp *interp, Gridder *slavePtr, + int column, int numCols); +static int SetSlaveRow(Tcl_Interp *interp, Gridder *slavePtr, + 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 */ + 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_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 = clientData; + static const char *const optionStrings[] = { + "anchor", "bbox", "columnconfigure", "configure", + "forget", "info", "location", "propagate", "remove", + "rowconfigure", "size", "slaves", NULL + }; + enum options { + GRID_ANCHOR, GRID_BBOX, GRID_COLUMNCONFIGURE, GRID_CONFIGURE, + 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 ConfigureSlaves(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 ConfigureSlaves(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: + return GridSlavesCommand(tkwin, interp, objc, objv); + + /* + * 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. + */ + + 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 master; + Gridder *masterPtr; + GridMaster *gridPtr; + Tk_Anchor old; + + if (objc > 4) { + Tcl_WrongNumArgs(interp, 2, objv, "window ?anchor?"); + return TCL_ERROR; + } + + if (TkGetWindowFromObj(interp, tkwin, objv[2], &master) != TCL_OK) { + return TCL_ERROR; + } + masterPtr = GetGrid(master); + + if (objc == 3) { + gridPtr = masterPtr->masterDataPtr; + Tcl_SetObjResult(interp, Tcl_NewStringObj( + Tk_NameOfAnchor(gridPtr?gridPtr->anchor:GRID_DEFAULT_ANCHOR), + -1)); + return TCL_OK; + } + + InitMasterData(masterPtr); + gridPtr = masterPtr->masterDataPtr; + 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 (masterPtr->abortPtr != NULL) { + *masterPtr->abortPtr = 1; + } + if (!(masterPtr->flags & REQUESTED_RELAYOUT)) { + masterPtr->flags |= REQUESTED_RELAYOUT; + Tcl_DoWhenIdle(ArrangeGrid, masterPtr); + } + } + 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 master; + Gridder *masterPtr; /* master grid record */ + GridMaster *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], &master) != TCL_OK) { + return TCL_ERROR; + } + masterPtr = GetGrid(master); + + 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 = masterPtr->masterDataPtr; + if (gridPtr == NULL) { + Tcl_SetObjResult(interp, NewQuadObj(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)) { + 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 slave; + Gridder *slavePtr; + 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], &slave) != TCL_OK) { + return TCL_ERROR; + } + + slavePtr = GetGrid(slave); + if (slavePtr->masterPtr != NULL) { + /* + * For "forget", reset all the settings to their defaults + */ + + if (c == 'f') { + slavePtr->column = -1; + slavePtr->row = -1; + slavePtr->numCols = 1; + slavePtr->numRows = 1; + slavePtr->padX = 0; + slavePtr->padY = 0; + slavePtr->padLeft = 0; + slavePtr->padTop = 0; + slavePtr->iPadX = 0; + slavePtr->iPadY = 0; + if (slavePtr->in != NULL) { + Tcl_DecrRefCount(slavePtr->in); + slavePtr->in = NULL; + } + slavePtr->doubleBw = 2*Tk_Changes(tkwin)->border_width; + if (slavePtr->flags & REQUESTED_RELAYOUT) { + Tcl_CancelIdleCall(ArrangeGrid, slavePtr); + } + slavePtr->flags = 0; + slavePtr->sticky = 0; + } else { + /* + * When removing, store name of master to be able to + * restore it later, even if the master is recreated. + */ + + if (slavePtr->in != NULL) { + Tcl_DecrRefCount(slavePtr->in); + slavePtr->in = NULL; + } + if (slavePtr->masterPtr != NULL) { + slavePtr->in = Tcl_NewStringObj( + Tk_PathName(slavePtr->masterPtr->tkwin), -1); + Tcl_IncrRefCount(slavePtr->in); + } + } + Tk_ManageGeometry(slave, NULL, NULL); + if (slavePtr->masterPtr->tkwin != Tk_Parent(slavePtr->tkwin)) { + Tk_UnmaintainGeometry(slavePtr->tkwin, + slavePtr->masterPtr->tkwin); + } + Unlink(slavePtr); + Tk_UnmapWindow(slavePtr->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. */ +{ + register Gridder *slavePtr; + Tk_Window slave; + Tcl_Obj *infoObj; + + if (objc != 3) { + Tcl_WrongNumArgs(interp, 2, objv, "window"); + return TCL_ERROR; + } + if (TkGetWindowFromObj(interp, tkwin, objv[2], &slave) != TCL_OK) { + return TCL_ERROR; + } + slavePtr = GetGrid(slave); + if (slavePtr->masterPtr == NULL) { + Tcl_ResetResult(interp); + return TCL_OK; + } + + infoObj = Tcl_NewObj(); + Tcl_DictObjPut(NULL, infoObj, Tcl_NewStringObj("-in", -1), + TkNewWindowObj(slavePtr->masterPtr->tkwin)); + Tcl_DictObjPut(NULL, infoObj, Tcl_NewStringObj("-column", -1), + Tcl_NewIntObj(slavePtr->column)); + Tcl_DictObjPut(NULL, infoObj, Tcl_NewStringObj("-row", -1), + Tcl_NewIntObj(slavePtr->row)); + Tcl_DictObjPut(NULL, infoObj, Tcl_NewStringObj("-columnspan", -1), + Tcl_NewIntObj(slavePtr->numCols)); + Tcl_DictObjPut(NULL, infoObj, Tcl_NewStringObj("-rowspan", -1), + Tcl_NewIntObj(slavePtr->numRows)); + TkAppendPadAmount(infoObj, "-ipadx", slavePtr->iPadX/2, slavePtr->iPadX); + TkAppendPadAmount(infoObj, "-ipady", slavePtr->iPadY/2, slavePtr->iPadY); + TkAppendPadAmount(infoObj, "-padx", slavePtr->padLeft, slavePtr->padX); + TkAppendPadAmount(infoObj, "-pady", slavePtr->padTop, slavePtr->padY); + Tcl_DictObjPut(NULL, infoObj, Tcl_NewStringObj("-sticky", -1), + StickyToObj(slavePtr->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 master; + Gridder *masterPtr; /* Master grid record. */ + GridMaster *gridPtr; /* Pointer to grid data. */ + register SlotInfo *slotPtr; + int x, y; /* Offset in pixels, from edge of master. */ + 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], &master) != TCL_OK) { + return TCL_ERROR; + } + + if (Tk_GetPixelsFromObj(interp, master, objv[3], &x) != TCL_OK) { + return TCL_ERROR; + } + if (Tk_GetPixelsFromObj(interp, master, objv[4], &y) != TCL_OK) { + return TCL_ERROR; + } + + masterPtr = GetGrid(master); + if (masterPtr->masterDataPtr == NULL) { + Tcl_SetObjResult(interp, NewPairObj(-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) { + Tcl_CancelIdleCall(ArrangeGrid, masterPtr); + ArrangeGrid(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 */ + } + } + + 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 master; + Gridder *masterPtr; + int propagate, old; + + if (objc > 4) { + Tcl_WrongNumArgs(interp, 2, objv, "window ?boolean?"); + return TCL_ERROR; + } + + if (TkGetWindowFromObj(interp, tkwin, objv[2], &master) != TCL_OK) { + return TCL_ERROR; + } + masterPtr = GetGrid(master); + if (objc == 3) { + Tcl_SetObjResult(interp, + Tcl_NewBooleanObj(!(masterPtr->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 = !(masterPtr->flags & DONT_PROPAGATE); + if (propagate != old) { + if (propagate) { + /* + * If we have slaves, we need to register as geometry master. + */ + + if (masterPtr->slavePtr != NULL) { + if (TkSetGeometryMaster(interp, master, "grid") != TCL_OK) { + return TCL_ERROR; + } + masterPtr->flags |= ALLOCED_MASTER; + } + masterPtr->flags &= ~DONT_PROPAGATE; + } else { + if (masterPtr->flags & ALLOCED_MASTER) { + TkFreeGeometryMaster(master, "grid"); + masterPtr->flags &= ~ALLOCED_MASTER; + } + 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, masterPtr); + } + } + 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 master, slave; + Gridder *masterPtr, *slavePtr; + 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], &master) != 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; + } + + masterPtr = GetGrid(master); + first = 0; /* lint */ + last = 0; /* lint */ + + 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(masterPtr, slot, slotType, /* checkOnly */ 1); + 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 (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 allSlaves = 0; + + if (Tcl_GetIntFromObj(NULL, lObjv[j], &slot) == TCL_OK) { + first = slot; + last = slot; + slavePtr = NULL; + } else if (strcmp(Tcl_GetString(lObjv[j]), "all") == 0) { + /* + * Make sure master is initialised. + */ + + InitMasterData(masterPtr); + + slavePtr = masterPtr->slavePtr; + if (slavePtr == NULL) { + continue; + } + allSlaves = 1; + } else if (TkGetWindowFromObj(NULL, tkwin, lObjv[j], &slave) + == TCL_OK) { + /* + * Is it gridded in this master? + */ + + slavePtr = GetGrid(slave); + if (slavePtr->masterPtr != masterPtr) { + 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 (slavePtr != NULL) { + first = (slotType == COLUMN) ? + slavePtr->column : slavePtr->row; + last = first - 1 + ((slotType == COLUMN) ? + slavePtr->numCols : slavePtr->numRows); + } + + for (slot = first; slot <= last; slot++) { + ok = CheckSlotData(masterPtr, 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) ? + masterPtr->masterDataPtr->columnPtr : + masterPtr->masterDataPtr->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, master, 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, master, 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 (slavePtr != NULL) { + slavePtr = slavePtr->nextPtr; + } + } while ((allSlaves == 1) && (slavePtr != 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) { + int last = masterPtr->masterDataPtr->rowMax - 1; + + while ((last >= 0) && (slotPtr[last].weight == 0) + && (slotPtr[last].pad == 0) && (slotPtr[last].minSize == 0) + && (slotPtr[last].uniform == NULL)) { + 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) + && (slotPtr[last].uniform == NULL)) { + 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, masterPtr); + } + 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 master; + Gridder *masterPtr; + GridMaster *gridPtr; /* pointer to grid data */ + + if (objc != 3) { + Tcl_WrongNumArgs(interp, 2, objv, "window"); + return TCL_ERROR; + } + + if (TkGetWindowFromObj(interp, tkwin, objv[2], &master) != TCL_OK) { + return TCL_ERROR; + } + masterPtr = GetGrid(master); + + if (masterPtr->masterDataPtr != NULL) { + SetGridSize(masterPtr); + gridPtr = masterPtr->masterDataPtr; + Tcl_SetObjResult(interp, NewPairObj( + MAX(gridPtr->columnEnd, gridPtr->columnMax), + MAX(gridPtr->rowEnd, gridPtr->rowMax))); + } else { + Tcl_SetObjResult(interp, NewPairObj(0, 0)); + } + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * GridSlavesCommand -- + * + * Implementation of the [grid slaves] subcommand. See the user + * documentation for details on what it does. + * + * Results: + * Standard Tcl result. + * + * Side effects: + * Places a list of slaves of the specified window in the interpreter's + * result field. + * + *---------------------------------------------------------------------- + */ + +static int +GridSlavesCommand( + 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 master; + Gridder *masterPtr; /* master grid record */ + Gridder *slavePtr; + int i, value, index; + int row = -1, column = -1; + static const char *const optionStrings[] = { + "-column", "-row", NULL + }; + enum options { SLAVES_COLUMN, SLAVES_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 == SLAVES_COLUMN) { + column = value; + } else { + row = value; + } + } + + if (TkGetWindowFromObj(interp, tkwin, objv[2], &master) != TCL_OK) { + return TCL_ERROR; + } + masterPtr = GetGrid(master); + + res = Tcl_NewListObj(0, NULL); + 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_ListObjAppendElement(interp,res, TkNewWindowObj(slavePtr->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. */ + Tk_Window tkwin) /* Other Tk-related information about the + * window. */ +{ + register Gridder *gridPtr = clientData; + + gridPtr = gridPtr->masterPtr; + if (gridPtr && !(gridPtr->flags & REQUESTED_RELAYOUT)) { + gridPtr->flags |= REQUESTED_RELAYOUT; + Tcl_DoWhenIdle(ArrangeGrid, 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 clientData, /* Grid structure for slave window that was + * stolen away. */ + Tk_Window tkwin) /* Tk's handle for the slave window. */ +{ + register Gridder *slavePtr = 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 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. */ + register SlotInfo *slotPtr) /* Pointer to slot array. */ +{ + register 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 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( + 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->padLeft; + *widthPtr -= slavePtr->padX; + *yPtr += slavePtr->padTop; + *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) /* Structure describing master whose slaves + * are to be re-layed out. */ +{ + register Gridder *masterPtr = 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. */ + int usedX, usedY; + + masterPtr->flags &= ~REQUESTED_RELAYOUT; + + /* + * If the master has no slaves anymore, then don't do anything at all: + * just leave the master's size as-is. Otherwise there is no way to + * "relinquish" control over the master 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(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 += Tk_InternalBorderLeft(masterPtr->tkwin) + + Tk_InternalBorderRight(masterPtr->tkwin); + height += Tk_InternalBorderTop(masterPtr->tkwin) + + Tk_InternalBorderBottom(masterPtr->tkwin); + + if (width < Tk_MinReqWidth(masterPtr->tkwin)) { + width = Tk_MinReqWidth(masterPtr->tkwin); + } + if (height < Tk_MinReqHeight(masterPtr->tkwin)) { + height = Tk_MinReqHeight(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, masterPtr); + } + masterPtr->abortPtr = NULL; + Tcl_Release(masterPtr); + return; + } + + /* + * If the currently requested layout size doesn't match the master'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(masterPtr->tkwin) - + Tk_InternalBorderLeft(masterPtr->tkwin) - + Tk_InternalBorderRight(masterPtr->tkwin); + realHeight = Tk_Height(masterPtr->tkwin) - + Tk_InternalBorderTop(masterPtr->tkwin) - + Tk_InternalBorderBottom(masterPtr->tkwin); + usedX = AdjustOffsets(realWidth, + MAX(slotPtr->columnEnd, slotPtr->columnMax), slotPtr->columnPtr); + usedY = AdjustOffsets(realHeight, MAX(slotPtr->rowEnd, slotPtr->rowMax), + slotPtr->rowPtr); + TkComputeAnchor(masterPtr->masterDataPtr->anchor, masterPtr->tkwin, + 0, 0, usedX, usedY, &slotPtr->startX, &slotPtr->startY); + + /* + * 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(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( + 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. */ + 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 weight, 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 = 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 = 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. + * 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 if (rightEdge >= 0) { + 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 if (rightEdge >= 0) { + int size = slavePtr->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) { + 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 = 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) { + 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 slaves. This determines the minimum + */ + + for (offset=0,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. 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 master. + */ + + 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. */ +{ + register 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 Tcl_GetHashValue(hPtr); + } + gridPtr = ckalloc(sizeof(Gridder)); + gridPtr->tkwin = tkwin; + gridPtr->masterPtr = NULL; + gridPtr->masterDataPtr = NULL; + gridPtr->nextPtr = NULL; + gridPtr->slavePtr = 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->masterDataPtr = 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 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( + 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); +} + +/* + *---------------------------------------------------------------------- + * + * SetSlaveColumn -- + * + * Update column data for a slave, checking that MAX_ELEMENT bound + * is not passed. + * + * Results: + * TCL_ERROR if out of bounds, TCL_OK otherwise + * + * Side effects: + * Slave fields are updated. + * + *---------------------------------------------------------------------- + */ + +static int +SetSlaveColumn( + Tcl_Interp *interp, /* Interp for error message. */ + Gridder *slavePtr, /* Slave 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 : slavePtr->column; + newNumCols = (numCols >= 1) ? numCols : slavePtr->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; + } + + slavePtr->column = newColumn; + slavePtr->numCols = newNumCols; + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * SetSlaveRow -- + * + * Update row data for a slave, checking that MAX_ELEMENT bound + * is not passed. + * + * Results: + * TCL_ERROR if out of bounds, TCL_OK otherwise + * + * Side effects: + * Slave fields are updated. + * + *---------------------------------------------------------------------- + */ + +static int +SetSlaveRow( + Tcl_Interp *interp, /* Interp for error message. */ + Gridder *slavePtr, /* Slave 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 : slavePtr->row; + newNumRows = (numRows >= 1) ? numRows : slavePtr->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; + } + + slavePtr->row = newRow; + slavePtr->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 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( + 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 *newSI = ckalloc(newSize); + SlotInfo *oldSI = (slotType == ROW) + ? masterPtr->masterDataPtr->rowPtr + : masterPtr->masterDataPtr->columnPtr; + + memcpy(newSI, oldSI, oldSize); + memset(newSI+numSlot, 0, newSize - oldSize); + ckfree(oldSI); + if (slotType == ROW) { + masterPtr->masterDataPtr->rowPtr = newSI; + masterPtr->masterDataPtr->rowSpace = newNumSlot; + } else { + masterPtr->masterDataPtr->columnPtr = newSI; + 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( + Gridder *masterPtr) +{ + if (masterPtr->masterDataPtr == NULL) { + GridMaster *gridPtr = masterPtr->masterDataPtr = + ckalloc(sizeof(GridMaster)); + size_t size = sizeof(SlotInfo) * TYPICAL_SIZE; + + gridPtr->columnEnd = 0; + gridPtr->columnMax = 0; + gridPtr->columnPtr = ckalloc(size); + gridPtr->columnSpace = TYPICAL_SIZE; + gridPtr->rowEnd = 0; + gridPtr->rowMax = 0; + gridPtr->rowPtr = 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 master's list of slaves. + * + * Results: + * None. + * + * Side effects: + * The master will be scheduled for re-arranging, and the size of the + * grid will be adjusted accordingly + * + *---------------------------------------------------------------------- + */ + +static void +Unlink( + register Gridder *slavePtr) /* Window to unlink. */ +{ + register Gridder *masterPtr, *slavePtr2; + + masterPtr = slavePtr->masterPtr; + if (masterPtr == NULL) { + return; + } + + if (masterPtr->slavePtr == slavePtr) { + masterPtr->slavePtr = slavePtr->nextPtr; + } else { + for (slavePtr2=masterPtr->slavePtr ; ; slavePtr2=slavePtr2->nextPtr) { + if (slavePtr2 == NULL) { + Tcl_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, masterPtr); + } + if (masterPtr->abortPtr != NULL) { + *masterPtr->abortPtr = 1; + } + + SetGridSize(slavePtr->masterPtr); + slavePtr->masterPtr = NULL; + + /* + * If we have emptied this master from slaves it means we are no longer + * handling it and should mark it as free. + */ + + if ((masterPtr->slavePtr == NULL) && (masterPtr->flags & ALLOCED_MASTER)) { + TkFreeGeometryMaster(masterPtr->tkwin, "grid"); + masterPtr->flags &= ~ALLOCED_MASTER; + } +} + +/* + *---------------------------------------------------------------------- + * + * 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 master 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. */ +{ + register Gridder *gridPtr = memPtr; + + if (gridPtr->masterDataPtr != NULL) { + if (gridPtr->masterDataPtr->rowPtr != NULL) { + ckfree(gridPtr->masterDataPtr -> rowPtr); + } + if (gridPtr->masterDataPtr->columnPtr != NULL) { + ckfree(gridPtr->masterDataPtr -> columnPtr); + } + ckfree(gridPtr->masterDataPtr); + } + 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 slaves, if any. + * + *---------------------------------------------------------------------- + */ + +static void +GridStructureProc( + ClientData clientData, /* Our information about window referred to by + * eventPtr. */ + XEvent *eventPtr) /* Describes what just happened. */ +{ + register Gridder *gridPtr = clientData; + TkDisplay *dispPtr = ((TkWindow *) gridPtr->tkwin)->dispPtr; + + if (eventPtr->type == ConfigureNotify) { + if ((gridPtr->slavePtr != NULL) + && !(gridPtr->flags & REQUESTED_RELAYOUT)) { + gridPtr->flags |= REQUESTED_RELAYOUT; + Tcl_DoWhenIdle(ArrangeGrid, gridPtr); + } + if ((gridPtr->masterPtr != NULL) && + (gridPtr->doubleBw != 2*Tk_Changes(gridPtr->tkwin)->border_width)) { + if (!(gridPtr->masterPtr->flags & REQUESTED_RELAYOUT)) { + gridPtr->doubleBw = 2*Tk_Changes(gridPtr->tkwin)->border_width; + gridPtr->masterPtr->flags |= REQUESTED_RELAYOUT; + Tcl_DoWhenIdle(ArrangeGrid, 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(&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->slavePtr != NULL) + && !(gridPtr->flags & REQUESTED_RELAYOUT)) { + gridPtr->flags |= REQUESTED_RELAYOUT; + Tcl_DoWhenIdle(ArrangeGrid, 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 the interp's result is set to contain an error message. + * + * Side effects: + * Slave windows get taken over by the grid. + * + *---------------------------------------------------------------------- + */ + +static int +ConfigureSlaves( + Tcl_Interp *interp, /* Interpreter for error reporting. */ + Tk_Window tkwin, /* Any window in application containing + * slaves. Used to look up slave 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 *masterPtr = NULL; + Gridder *slavePtr; + Tk_Window other, slave, parent, ancestor; + 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 slave's + * parent window (default for -in). + */ + + if (TkGetWindowFromObj(interp, tkwin, objv[i], &slave) != TCL_OK) { + return TCL_ERROR; + } + if (masterPtr == NULL) { + /* + * Is there any saved -in from a removed slave? + * If there is, it becomes default for -in. + * If the stored master does not exist, just ignore it. + */ + + struct Gridder *slavePtr = GetGrid(slave); + if (slavePtr->in != NULL) { + if (TkGetWindowFromObj(interp, slave, slavePtr->in, &parent) + == TCL_OK) { + masterPtr = GetGrid(parent); + InitMasterData(masterPtr); + } + } + } + if (masterPtr == NULL) { + parent = Tk_Parent(slave); + if (parent != NULL) { + masterPtr = GetGrid(parent); + InitMasterData(masterPtr); + } + } + 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; + } + masterPtr = GetGrid(other); + InitMasterData(masterPtr); + } 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 first unoccupied row of the master. + */ + + if (defaultRow < 0) { + if (masterPtr != NULL && masterPtr->masterDataPtr != NULL) { + SetGridSize(masterPtr); + defaultRow = masterPtr->masterDataPtr->rowEnd; + } else { + defaultRow = 0; + } + } + + /* + * 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. + */ + + 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 slave. + */ + + if ((firstChar == REL_VERT) || (firstChar == REL_SKIP)) { + defaultColumn++; + continue; + } + if (firstChar == REL_HORIZ) { + continue; + } + + for (defaultColumnSpan = 1; j + defaultColumnSpan < numWindows; + defaultColumnSpan++) { + const char *string = Tcl_GetString(objv[j + defaultColumnSpan]); + + if (*string != REL_HORIZ) { + break; + } + } + + if (TkGetWindowFromObj(interp, tkwin, objv[j], &slave) != TCL_OK) { + return TCL_ERROR; + } + + if (Tk_TopWinHierarchy(slave)) { + 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; + } + 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 < 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 (SetSlaveColumn(interp, slavePtr, 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 (SetSlaveColumn(interp, slavePtr, -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 == slave) { + 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; + masterPtr = GetGrid(other); + InitMasterData(masterPtr); + 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; + } + slavePtr->sticky = sticky; + break; + } + case CONF_IPADX: + if ((Tk_GetPixelsFromObj(NULL, slave, 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; + } + slavePtr->iPadX = tmp * 2; + break; + case CONF_IPADY: + if ((Tk_GetPixelsFromObj(NULL, slave, 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; + } + slavePtr->iPadY = tmp * 2; + break; + case CONF_PADX: + if (TkParsePadAmount(interp, tkwin, objv[i+1], + &slavePtr->padLeft, &slavePtr->padX) != TCL_OK) { + return TCL_ERROR; + } + break; + case CONF_PADY: + if (TkParsePadAmount(interp, tkwin, objv[i+1], + &slavePtr->padTop, &slavePtr->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 (SetSlaveRow(interp, slavePtr, 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 (SetSlaveRow(interp, slavePtr, -1, tmp) != TCL_OK) { + return TCL_ERROR; + } + break; + } + } + + /* + * If no position was specified via -in and the slave is already + * packed, then leave it in its current location. + */ + + if (!positionGiven && (slavePtr->masterPtr != NULL)) { + masterPtr = slavePtr->masterPtr; + goto scheduleLayout; + } + + /* + * If the same -in window is passed in again, then just leave it in + * its current location. + */ + + if (positionGiven && (masterPtr == slavePtr->masterPtr)) { + goto scheduleLayout; + } + + /* + * Make sure we have a geometry master. We look at: + * 1) the -in flag + * 2) the parent of the first slave. + */ + + parent = Tk_Parent(slave); + if (masterPtr == NULL) { + masterPtr = GetGrid(parent); + InitMasterData(masterPtr); + } + + if (slavePtr->masterPtr != NULL && slavePtr->masterPtr != masterPtr) { + if (slavePtr->masterPtr->tkwin != Tk_Parent(slavePtr->tkwin)) { + Tk_UnmaintainGeometry(slavePtr->tkwin, slavePtr->masterPtr->tkwin); + } + 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_TopWinHierarchy(ancestor)) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "can't put %s inside %s", Tcl_GetString(objv[j]), + Tk_PathName(masterPtr->tkwin))); + Tcl_SetErrorCode(interp, "TK", "GEOMETRY", "HIERARCHY", NULL); + Unlink(slavePtr); + return TCL_ERROR; + } + } + + /* + * Try to make sure our master isn't managed by us. + */ + + if (masterPtr->masterPtr == slavePtr) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "can't put %s inside %s, would cause management loop", + Tcl_GetString(objv[j]), Tk_PathName(masterPtr->tkwin))); + Tcl_SetErrorCode(interp, "TK", "GEOMETRY", "LOOP", NULL); + Unlink(slavePtr); + return TCL_ERROR; + } + + Tk_ManageGeometry(slave, &gridMgrType, slavePtr); + + if (!(masterPtr->flags & DONT_PROPAGATE)) { + if (TkSetGeometryMaster(interp, masterPtr->tkwin, "grid") + != TCL_OK) { + Tk_ManageGeometry(slave, NULL, NULL); + Unlink(slavePtr); + return TCL_ERROR; + } + masterPtr->flags |= ALLOCED_MASTER; + } + + /* + * Assign default position information. + */ + + if (slavePtr->column == -1) { + if (SetSlaveColumn(interp, slavePtr, defaultColumn,-1) != TCL_OK){ + return TCL_ERROR; + } + } + if (SetSlaveColumn(interp, slavePtr, -1, + slavePtr->numCols + defaultColumnSpan - 1) != TCL_OK) { + return TCL_ERROR; + } + if (slavePtr->row == -1) { + if (SetSlaveRow(interp, slavePtr, defaultRow, -1) != TCL_OK) { + return TCL_ERROR; + } + } + defaultColumn += slavePtr->numCols; + defaultColumnSpan = 1; + + /* + * Arrange for the master to be re-arranged at the first idle moment. + */ + + scheduleLayout: + if (masterPtr->abortPtr != NULL) { + *masterPtr->abortPtr = 1; + } + if (!(masterPtr->flags & REQUESTED_RELAYOUT)) { + masterPtr->flags |= REQUESTED_RELAYOUT; + Tcl_DoWhenIdle(ArrangeGrid, masterPtr); + } + } + + /* + * 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 (masterPtr == 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++) { + const char *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 (slavePtr = masterPtr->slavePtr; slavePtr != NULL; + slavePtr = slavePtr->nextPtr) { + + if (slavePtr->column == lastColumn + && slavePtr->row + slavePtr->numRows - 1 == lastRow) { + if (slavePtr->numCols <= width) { + if (SetSlaveRow(interp, slavePtr, -1, + slavePtr->numRows + 1) != TCL_OK) { + return TCL_ERROR; + } + match++; + j += slavePtr->numCols - 1; + lastWindow = Tk_PathName(slavePtr->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 (masterPtr == NULL) { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "can't determine master window", -1)); + Tcl_SetErrorCode(interp, "TK", "GRID", "SHORTCUT_USAGE", NULL); + return TCL_ERROR; + } + SetGridSize(masterPtr); + + /* + * If we have emptied this master from slaves it means we are no longer + * handling it and should mark it as free. + */ + + if (masterPtr->slavePtr == NULL && masterPtr->flags & ALLOCED_MASTER) { + TkFreeGeometryMaster(masterPtr->tkwin, "grid"); + masterPtr->flags &= ~ALLOCED_MASTER; + } + + 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: + */ |