stk/Tk/generic/tkGrid.c

2053 lines
57 KiB
C
Raw Normal View History

1996-09-27 06:29:02 -04:00
/*
* tkGrid.c --
*
* Grid based geometry manager.
*
* Copyright (c) 1996 by Sun Microsystems, Inc.
*
* See the file "license.terms" for information on usage and redistribution
* of this file, and for a DISCLAIMER OF ALL WARRANTIES.
*
*
* SCCS: @(#) tkGrid.c 1.21 96/02/21 10:50:58
*/
#include "tkInt.h"
/*
* LayoutInfo structure. We shouldn't be using hard-wired limits!
*/
#define MAXGRIDSIZE 128
#ifndef MAXINT
# define MAXINT 0x7fff
#endif
#define MINWEIGHT 0.0001 /* weight totals < this are considered to be zero */
/*
* Special characters to support relative layouts
*/
#define REL_SKIP 'x' /* skip this column */
#define REL_HORIZ '-' /* extend previous widget horizontally */
#define REL_VERT '^' /* extend previous widget verticallly */
/*
* structure to hold collected constraints temporarily:
* needs to use a "Constrain" thingy
*/
typedef struct {
int width, height; /* number of cells horizontally, vertically */
int lastRow; /* last cell with a window in it */
int minWidth[MAXGRIDSIZE]; /* largest minWidth in each column */
int minHeight[MAXGRIDSIZE]; /* largest minHeight in each row */
double weightX[MAXGRIDSIZE]; /* largest weight in each column */
double weightY[MAXGRIDSIZE]; /* largest weight in each row */
} LayoutInfo;
/* structure for holding row and column constraints */
typedef struct {
int used; /* maximum element used */
int max; /* maximum element allocated */
int *minsize; /* array of minimum column/row sizes */
double *weight; /* array of column/row weights */
} Constrain;
/* For each window that the gridbag cares about (either because
* the window is managed by the gridbag or because the window
* has slaves that are managed by the gridbag), there is a
* structure of the following type:
*/
typedef struct GridBag {
Tk_Window tkwin; /* Tk token for window. NULL means that
* the window has been deleted, but the
* packet hasn't had a chance to clean up
* yet because the structure is still in
* use. */
struct GridBag *masterPtr; /* Master window within which this window
* is managed (NULL means this window
* isn't managed by the gridbag). */
struct GridBag *nextPtr; /* Next window managed within same
* parent. List is priority-ordered:
* first on list gets layed out first. */
struct GridBag *slavePtr; /* First in list of slaves managed
* inside this window (NULL means
* no gridbag slaves). */
int gridColumn, gridRow;
int gridWidth, gridHeight;
int tempX, tempY;
int tempWidth, tempHeight;
double weightX, weightY;
int minWidth, minHeight;
int padX, padY; /* Total additional pixels to leave around the
* window (half of this space is left on each
* side). This is space *outside* the window:
* we'll allocate extra space in frame but
* won't enlarge window). */
int iPadX, iPadY; /* Total extra pixels to allocate inside the
* window (half this amount will appear on
* each side). */
int startx, starty; /* starting location of layout */
int doubleBw; /* Twice the window's last known border
* width. If this changes, the window
* must be re-arranged within its parent. */
int *abortPtr; /* If non-NULL, it means that there is a nested
* call to ArrangeGrid already working on
* this window. *abortPtr may be set to 1 to
* abort that nested call. This happens, for
* example, if tkwin or any of its slaves
* is deleted. */
int flags; /* Miscellaneous flags; see below
* for definitions. */
Constrain row, column; /* column and row constraints */
int valid;
LayoutInfo *layoutCache;
} GridBag;
/*
* Flag values for GridBag structures:
*
* REQUESTED_RELAYOUT: 1 means a Tk_DoWhenIdle request
* has already been made to re-arrange
* all the slaves of this window.
* STICK_NORTH 1 means this window sticks to the edgth of its
* STICK_EAST cavity
* STICK_SOUTH
* STICK_WEST
*
* DONT_PROPAGATE: 1 means don't set this window's requested
* size. 0 means if this window is a master
* then Tk will set its requested size to fit
* the needs of its slaves.
*/
#define STICK_NORTH 1
#define STICK_EAST 2
#define STICK_SOUTH 4
#define STICK_WEST 8
#define STICK_ALL (STICK_NORTH|STICK_EAST|STICK_SOUTH|STICK_WEST)
#define REQUESTED_RELAYOUT 16
#define DONT_PROPAGATE 32
/*
* Hash table used to map from Tk_Window tokens to corresponding
* GridBag structures:
*/
static Tcl_HashTable gridBagHashTable;
/*
* Have statics in this module been initialized?
*/
static initialized = 0;
/*
* Prototypes for procedures used only in this file:
*/
static void ArrangeGrid _ANSI_ARGS_((ClientData clientData));
static int ConfigureSlaves _ANSI_ARGS_((Tcl_Interp *interp,
Tk_Window tkwin, int argc, char *argv[]));
static void DestroyGridBag _ANSI_ARGS_((char *memPtr));
static void GetCachedLayoutInfo _ANSI_ARGS_((GridBag *masterPtr));
static GridBag * GetGridBag _ANSI_ARGS_((Tk_Window tkwin));
static void GetLayoutInfo _ANSI_ARGS_((GridBag *masterPtr,
LayoutInfo *r));
static void GetMinSize _ANSI_ARGS_((GridBag *masterPtr,
LayoutInfo *info, int *minw, int *minh));
static void GridBagStructureProc _ANSI_ARGS_((
ClientData clientData, XEvent *eventPtr));
static void GridLostSlaveProc _ANSI_ARGS_((ClientData clientData,
Tk_Window tkwin));
static void GridReqProc _ANSI_ARGS_((ClientData clientData,
Tk_Window tkwin));
static void GridBagStructureProc _ANSI_ARGS_((
ClientData clientData, XEvent *eventPtr));
static void StickyToString _ANSI_ARGS_((int flags, char *result));
static int StringToSticky _ANSI_ARGS_((char *string));
static void Unlink _ANSI_ARGS_((GridBag *gridPtr));
static Tk_GeomMgr gridMgrType = {
"grid", /* name */
GridReqProc, /* requestProc */
GridLostSlaveProc, /* lostSlaveProc */
};
/*
*--------------------------------------------------------------
*
* Tk_GridCmd --
*
* This procedure is invoked to process the "grid" Tcl command.
* See the user documentation for details on what it does.
*
* Results:
* A standard Tcl result.
*
* Side effects:
* See the user documentation.
*
*--------------------------------------------------------------
*/
int
Tk_GridCmd(clientData, interp, argc, argv)
ClientData clientData; /* Main window associated with
* interpreter. */
Tcl_Interp *interp; /* Current interpreter. */
int argc; /* Number of arguments. */
char **argv; /* Argument strings. */
{
Tk_Window tkwin = (Tk_Window) clientData;
size_t length;
char c;
if ((argc >= 2) && (argv[1][0] == '.')) {
return ConfigureSlaves(interp, tkwin, argc-1, argv+1);
}
if (argc < 3) {
Tcl_AppendResult(interp, "wrong # args: should be \"",
argv[0], " option arg ?arg ...?\"", (char *) NULL);
return TCL_ERROR;
}
c = argv[1][0];
length = strlen(argv[1]);
if ((c == 'b') && (strncmp(argv[1], "bbox", length) == 0)) {
Tk_Window master;
GridBag *masterPtr;
int row, column;
int i, x, y;
int prevX, prevY;
int width, height;
double weight;
int diff;
if (argc != 5) {
Tcl_AppendResult(interp, "Wrong number of arguments: ",
"must be \"",argv[0],
" bbox <master> <column> <row>\"", (char *) NULL);
return TCL_ERROR;
}
master = Tk_NameToWindow(interp, argv[2], tkwin);
if (master == NULL) {
return TCL_ERROR;
}
if (Tcl_GetInt(interp, argv[3], &column) != TCL_OK) {
return TCL_ERROR;
}
if (Tcl_GetInt(interp, argv[4], &row) != TCL_OK) {
return TCL_ERROR;
}
masterPtr = GetGridBag(master);
/* make sure the grid is up to snuff */
while ((masterPtr->flags & REQUESTED_RELAYOUT)) {
Tk_CancelIdleCall(ArrangeGrid, (ClientData) masterPtr);
ArrangeGrid((ClientData) masterPtr);
}
GetCachedLayoutInfo(masterPtr);
if (row < 0 || column < 0) {
*interp->result = '\0';
return TCL_OK;
}
if (column >= masterPtr->layoutCache->width ||
row >= masterPtr->layoutCache->height) {
*interp->result = '\0';
return TCL_OK;
}
x = masterPtr->startx;
y = masterPtr->starty;
GetMinSize(masterPtr, masterPtr->layoutCache, &width, &height);
diff = Tk_Width(masterPtr->tkwin) - (width + masterPtr->iPadX);
for (weight=0.0, i=0; i<masterPtr->layoutCache->width; i++)
weight += masterPtr->layoutCache->weightX[i];
prevX = 0; /* Needed to prevent gcc warning. */
for (i=0; i<=column; i++) {
int dx = 0;
if (weight > MINWEIGHT) {
dx = (int)((((double)diff) * masterPtr->layoutCache->weightX[i])
/ weight);
}
prevX = x;
x += masterPtr->layoutCache->minWidth[i] + dx;
}
diff = Tk_Height(masterPtr->tkwin) - (height + masterPtr->iPadY);
for (weight=0.0, i=0; i<masterPtr->layoutCache->width; i++) {
weight += masterPtr->layoutCache->weightY[i];
}
prevY = 0; /* Needed to prevent gcc warning. */
for (i=0; i<=row; i++) {
int dy = 0;
if (weight > MINWEIGHT) {
dy = (int)((((double)diff) * masterPtr->layoutCache->weightY[i])
/ weight);
}
prevY = y;
y += masterPtr->layoutCache->minHeight[i] + dy;
}
sprintf(interp->result,"%d %d %d %d",prevX,prevY,x - prevX,y - prevY);
} else if ((c == 'c') && (strncmp(argv[1], "configure", length) == 0)) {
if (argv[2][0] != '.') {
Tcl_AppendResult(interp, "bad argument \"", argv[2],
"\": must be name of window", (char *) NULL);
return TCL_ERROR;
}
return ConfigureSlaves(interp, tkwin, argc-2, argv+2);
} else if ((c == 'f') && (strncmp(argv[1], "forget", length) == 0)) {
Tk_Window slave;
GridBag *slavePtr;
int i;
for (i = 2; i < argc; i++) {
slave = Tk_NameToWindow(interp, argv[i], tkwin);
if (slave == NULL) {
return TCL_ERROR;
}
slavePtr = GetGridBag(slave);
if (slavePtr->masterPtr != NULL) {
Tk_ManageGeometry(slave, (Tk_GeomMgr *) NULL,
(ClientData) NULL);
Unlink(slavePtr);
Tk_UnmapWindow(slavePtr->tkwin);
}
}
} else if ((c == 'i') && (strncmp(argv[1], "info", length) == 0)) {
register GridBag *slavePtr;
Tk_Window slave;
char buffer[64];
if (argc != 3) {
Tcl_AppendResult(interp, "wrong # args: should be \"",
argv[0], " info window\"", (char *) NULL);
return TCL_ERROR;
}
slave = Tk_NameToWindow(interp, argv[2], tkwin);
if (slave == NULL) {
return TCL_ERROR;
}
slavePtr = GetGridBag(slave);
if (slavePtr->masterPtr == NULL) {
interp->result[0] = '\0';
return TCL_OK;
}
Tcl_AppendElement(interp, "-in");
Tcl_AppendElement(interp, Tk_PathName(slavePtr->masterPtr->tkwin));
sprintf(buffer, " -column %d -row %d -columnspan %d -rowspan %d",
slavePtr->gridColumn, slavePtr->gridRow,
slavePtr->gridWidth, slavePtr->gridHeight);
Tcl_AppendResult(interp, buffer, (char *) NULL);
sprintf(buffer, " -ipadx %d -ipady %d -padx %d -pady %d",
slavePtr->iPadX/2, slavePtr->iPadY/2, slavePtr->padX/2,
slavePtr->padY/2);
Tcl_AppendResult(interp, buffer, (char *) NULL);
StickyToString(slavePtr->flags,buffer);
Tcl_AppendResult(interp, " -sticky ", buffer, (char *) NULL);
/*
sprintf(buffer, " -weightx %.2f -weighty %.2f",
slavePtr->weightX, slavePtr->weightY);
Tcl_AppendResult(interp, buffer, (char *) NULL);
*/
} else if ((c == 'p') && (strncmp(argv[1], "propagate", length) == 0)) {
Tk_Window master;
GridBag *masterPtr;
int propagate;
if (argc > 4) {
Tcl_AppendResult(interp, "wrong # args: should be \"",
argv[0], " propagate window ?boolean?\"",
(char *) NULL);
return TCL_ERROR;
}
master = Tk_NameToWindow(interp, argv[2], tkwin);
if (master == NULL) {
return TCL_ERROR;
}
masterPtr = GetGridBag(master);
if (argc == 3) {
#ifdef STk_CODE
interp->result = (masterPtr->flags & DONT_PROPAGATE) ? "#f" : "#t";
#else
interp->result = (masterPtr->flags & DONT_PROPAGATE) ? "0" : "1";
#endif
return TCL_OK;
}
if (Tcl_GetBoolean(interp, argv[3], &propagate) != TCL_OK) {
return TCL_ERROR;
}
if (propagate) {
masterPtr->flags &= ~DONT_PROPAGATE;
/*
* Re-arrange the master to allow new geometry information to
* propagate upwards to the master\'s master.
*/
if (masterPtr->abortPtr != NULL) {
*masterPtr->abortPtr = 1;
}
masterPtr->valid = 0;
if (!(masterPtr->flags & REQUESTED_RELAYOUT)) {
masterPtr->flags |= REQUESTED_RELAYOUT;
Tk_DoWhenIdle(ArrangeGrid, (ClientData) masterPtr);
}
} else {
masterPtr->flags |= DONT_PROPAGATE;
}
} else if ((c == 's') && (strncmp(argv[1], "size", length) == 0)) {
Tk_Window master;
GridBag *masterPtr;
if (argc != 3) {
Tcl_AppendResult(interp, "wrong # args: should be \"",
argv[0], " size window\"", (char *) NULL);
return TCL_ERROR;
}
master = Tk_NameToWindow(interp, argv[2], tkwin);
if (master == NULL)
return TCL_ERROR;
masterPtr = GetGridBag(master);
GetCachedLayoutInfo(masterPtr);
sprintf(interp->result, "%d %d", masterPtr->layoutCache->width,
masterPtr->layoutCache->height);
} else if ((c == 's') && (strncmp(argv[1], "slaves", length) == 0)) {
Tk_Window master;
GridBag *masterPtr, *slavePtr;
int i, value;
int row = -1, column = -1;
if (argc < 3 || argc%2 ==0) {
Tcl_AppendResult(interp, "wrong # args: should be \"",
argv[0], " slaves window ?-option value...?\"",
(char *) NULL);
return TCL_ERROR;
}
for (i=3; i<argc; i+=2) {
if (*argv[i] != '-' || (length = strlen(argv[i])) < 2) {
Tcl_AppendResult(interp, "Invalid args: should be \"",
argv[0], " slaves window ?-option value...?\"",
(char *) NULL);
return TCL_ERROR;
}
if (Tcl_GetInt(interp, argv[i+1], &value) != TCL_OK) {
return TCL_ERROR;
}
if (value < 0) {
Tcl_AppendResult(interp, argv[i],
" is an invalid value: should NOT be < 0",
(char *) NULL);
return TCL_ERROR;
}
if (strncmp(argv[i], "-column", length) == 0) {
column = value;
} else if (strncmp(argv[i], "-row", length) == 0) {
row = value;
} else {
Tcl_AppendResult(interp, argv[i],
" is an invalid option: should be \"",
"-row, -column\"",
(char *) NULL);
return TCL_ERROR;
}
}
master = Tk_NameToWindow(interp, argv[2], tkwin);
if (master == NULL) {
return TCL_ERROR;
}
masterPtr = GetGridBag(master);
#ifdef STk_CODE
Tcl_AppendResult(interp, "(", NULL);
#endif
for (slavePtr = masterPtr->slavePtr; slavePtr != NULL;
slavePtr = slavePtr->nextPtr) {
if (column>=0 && (slavePtr->gridColumn > column
|| slavePtr->gridColumn+slavePtr->gridWidth-1 < column)) {
continue;
}
if (row>=0 && (slavePtr->gridRow > row ||
slavePtr->gridRow+slavePtr->gridHeight-1 < row)) {
continue;
}
Tcl_AppendElement(interp, Tk_PathName(slavePtr->tkwin));
}
#ifdef STk_CODE
Tcl_AppendResult(interp, ")", NULL);
#endif
/*
* grid columnconfigure <master> <index> -option
* grid columnconfigure <master> <index> -option value -option value
* grid rowconfigure <master> <index> -option
* grid rowconfigure <master> <index> -option value -option value
*/
} else if(((c=='c') && (strncmp(argv[1], "columnconfigure", length) == 0)) ||
((c=='r') && (strncmp(argv[1], "rowconfigure", length) == 0))) {
Tk_Window master;
GridBag *masterPtr;
Constrain *con;
int index, i, size;
double weight;
if (argc != 5 && (argc < 5 || argc%2 == 1)) {
Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
" ", argv[1], " master index ?-option value...?\"",
(char *)NULL);
return TCL_ERROR;
}
master = Tk_NameToWindow(interp, argv[2], tkwin);
if (master == NULL) {
return TCL_ERROR;
}
masterPtr = GetGridBag(master);
con = (c=='c') ? &(masterPtr->column) : &(masterPtr->row);
if (Tcl_GetInt(interp, argv[3], &index) != TCL_OK) {
return TCL_ERROR;
}
if (index < 0 || index >= MAXGRIDSIZE) {
Tcl_AppendResult(interp, argv[3], " is out of range",
(char *)NULL);
return TCL_ERROR;
}
/*
* make sure the row/column constraint array is allocated. This
* Should be changed to avoid hard-wired limits. We'll wimp out
* for now.
*/
if (con->max == 0) {
unsigned int size;
con->max = MAXGRIDSIZE;
con->used = 0;
size = MAXGRIDSIZE * sizeof(con->minsize[0]);
con->minsize = (int *) ckalloc(size);
memset(con->minsize, 0, size);
size = MAXGRIDSIZE * sizeof(con->weight[0]);
con->weight = (double *) ckalloc(size);
memset(con->weight, 0, size);
}
for (i=4; i<argc; i+=2) {
if (*argv[i] != '-' || (length = strlen(argv[i])) < 2) {
Tcl_AppendResult(interp, "Invalid arg: \"",
argv[0], "\" expecting -minsize or -weight",
(char *) NULL);
return TCL_ERROR;
}
if (strncmp(argv[i], "-minsize", length) == 0) {
if (argc == 5) {
size = con->used <= index ? 0 : con->minsize[index];
sprintf(interp->result, "%d", size);
} else if (Tk_GetPixels(interp, master, argv[i+1], &size)
!= TCL_OK) {
return TCL_ERROR;
} else {
con->minsize[index] = size;
if (size > 0 && index >= con->used)
con->used = index+1;
else if (size == 0 && index+1 == con->used) {
while (index >= 0 && (con->minsize[index]==0) &&
(con->weight[index] == 0.0)) {
index--;
}
con->used = index + 1;
}
}
} else if (strncmp(argv[i], "-weight", length) == 0) {
if (argc == 5) {
weight = con->used <= index ? 0 : con->weight[index];
sprintf(interp->result, "%.2f", weight);
} else if (Tcl_GetDouble(interp, argv[i+1], &weight) != TCL_OK) {
return TCL_ERROR;
} else {
con->weight[index] = weight;
if (weight > MINWEIGHT && index >= con->used)
con->used = index+1;
else if (weight == 0.0 && index+1 == con->used) {
while (index >= 0 && (con->minsize[index]==0) &&
(con->weight[index] == 0.0)) {
index--;
}
con->used = index + 1;
}
}
} else {
Tcl_AppendResult(interp, argv[i],
" is an invalid option: should be \"",
"-minsize, -weight\"",
(char *) NULL);
return TCL_ERROR;
}
}
/* if we changed a property, re-arrange the table */
if (argc != 5) {
if (masterPtr->abortPtr != NULL) {
*masterPtr->abortPtr = 1;
}
masterPtr->valid = 0;
if (!(masterPtr->flags & REQUESTED_RELAYOUT)) {
masterPtr->flags |= REQUESTED_RELAYOUT;
Tk_DoWhenIdle(ArrangeGrid, (ClientData) masterPtr);
}
}
} else if((c == 'l') && (strncmp(argv[1], "location", length) == 0)) {
Tk_Window master;
GridBag *masterPtr;
int x, y, i, j, w, h;
int width, height;
double weight;
int diff;
if (argc != 5) {
Tcl_AppendResult(interp, "wrong # args: should be \"",
argv[0], " location master x y\"", (char *)NULL);
return TCL_ERROR;
}
master = Tk_NameToWindow(interp, argv[2], tkwin);
if (master == NULL) {
return TCL_ERROR;
}
masterPtr = GetGridBag(master);
if (Tk_GetPixels(interp, master, argv[3], &x) != TCL_OK) {
return TCL_ERROR;
}
if (Tk_GetPixels(interp, master, argv[4], &y) != TCL_OK) {
return TCL_ERROR;
}
/* make sure the grid is up to snuff */
while ((masterPtr->flags & REQUESTED_RELAYOUT)) {
Tk_CancelIdleCall(ArrangeGrid, (ClientData) masterPtr);
ArrangeGrid((ClientData) masterPtr);
}
GetCachedLayoutInfo(masterPtr);
GetMinSize(masterPtr, masterPtr->layoutCache, &width, &height);
diff = Tk_Width(masterPtr->tkwin) - (width + masterPtr->iPadX);
for (weight=0.0, i=0; i<masterPtr->layoutCache->width; i++) {
weight += masterPtr->layoutCache->weightX[i];
}
w = masterPtr->startx;
if (w > x) {
i = -1;
} else {
for (i=0; i<masterPtr->layoutCache->width; i++) {
int dx = 0;
if (weight > MINWEIGHT) {
dx = (int)((((double)diff) * masterPtr->layoutCache->weightX[i])
/ weight);
}
w += masterPtr->layoutCache->minWidth[i] + dx;
if (w > x) {
break;
}
}
}
diff = Tk_Height(masterPtr->tkwin) - (height + masterPtr->iPadY);
for (weight=0.0, j = 0; j < masterPtr->layoutCache->height; j++)
weight += masterPtr->layoutCache->weightY[j];
h = masterPtr->starty;
if (h > y) {
j = -1;
} else {
for (j=0; j<masterPtr->layoutCache->height; j++) {
int dy = 0;
if (weight > MINWEIGHT) {
dy = (int)((((double)diff) * masterPtr->layoutCache->weightY[j])
/ weight);
}
h += masterPtr->layoutCache->minHeight[j] + dy;
if (h > y) {
break;
}
}
}
sprintf(interp->result, "%d %d", i, j);
} else {
Tcl_AppendResult(interp, "bad option \"", argv[1],
"\": must be bbox, columnconfigure, configure, forget, info, ",
"location, propagate, rowconfigure, size, or slaves",
(char *) NULL);
return TCL_ERROR;
}
return TCL_OK;
}
/*
*--------------------------------------------------------------
*
* GridReqProc --
*
* This procedure is invoked by Tk_GeometryRequest for
* windows managed by the gridbag.
*
* Results:
* None.
*
* Side effects:
* Arranges for tkwin, and all its managed siblings, to
* be re-arranged at the next idle point.
*
*--------------------------------------------------------------
*/
/* ARGSUSED */
static void
GridReqProc(clientData, tkwin)
ClientData clientData; /* GridBag's information about
* window that got new preferred
* geometry. */
Tk_Window tkwin; /* Other Tk-related information
* about the window. */
{
register GridBag *gridPtr = (GridBag *) clientData;
gridPtr = gridPtr->masterPtr;
gridPtr->valid = 0;
if (!(gridPtr->flags & REQUESTED_RELAYOUT)) {
gridPtr->flags |= REQUESTED_RELAYOUT;
Tk_DoWhenIdle(ArrangeGrid, (ClientData) gridPtr);
}
}
/*
*--------------------------------------------------------------
*
* GridLostSlaveProc --
*
* This procedure is invoked by Tk whenever some other geometry
* claims control over a slave that used to be managed by us.
*
* Results:
* None.
*
* Side effects:
* Forgets all grid-related information about the slave.
*
*--------------------------------------------------------------
*/
/* ARGSUSED */
static void
GridLostSlaveProc(clientData, tkwin)
ClientData clientData; /* GridBag structure for slave window that
* was stolen away. */
Tk_Window tkwin; /* Tk's handle for the slave window. */
{
register GridBag *slavePtr = (GridBag *) clientData;
if (slavePtr->masterPtr->tkwin != Tk_Parent(slavePtr->tkwin)) {
Tk_UnmaintainGeometry(slavePtr->tkwin, slavePtr->masterPtr->tkwin);
}
Unlink(slavePtr);
Tk_UnmapWindow(slavePtr->tkwin);
}
/*
* Fill in an instance of the above structure for the current set
* of managed children. This requires two passes through the
* set of children, first to figure out what cells they occupy
* and how many rows and columns there are, and then to distribute
* the weights and min sizes amoung the rows/columns.
*
* This also caches the minsizes for all the children when they are
* first encountered.
*/
static void
GetLayoutInfo(masterPtr, r)
GridBag *masterPtr;
LayoutInfo *r;
{
register GridBag *slavePtr;
int i, k, px, py, pixels_diff, nextSize;
double weight_diff, weight;
register int curX, curY, curWidth, curHeight, curRow, curCol;
int xMax[MAXGRIDSIZE];
int yMax[MAXGRIDSIZE];
/*
* Pass #1
*
* Figure out the dimensions of the layout grid.
*/
r->width = r->height = 0;
curRow = curCol = -1;
memset(xMax, 0, sizeof(int) * MAXGRIDSIZE);
memset(yMax, 0, sizeof(int) * MAXGRIDSIZE);
for (slavePtr = masterPtr->slavePtr; slavePtr != NULL;
slavePtr = slavePtr->nextPtr) {
curX = slavePtr->gridColumn;
curY = slavePtr->gridRow;
curWidth = slavePtr->gridWidth;
curHeight = slavePtr->gridHeight;
/* Adjust the grid width and height */
for (px = curX + curWidth; r->width < px; r->width++) {
/* Null body. */
}
for (py = curY + curHeight; r->height < py; r->height++) {
/* Null body. */
}
/* Adjust the xMax and yMax arrays */
for (i = curX; i < (curX + curWidth); i++) {
yMax[i] = py;
}
for (i = curY; i < (curY + curHeight); i++) {
xMax[i] = px;
}
/* Cache the current slave's size. */
slavePtr->minWidth = Tk_ReqWidth(slavePtr->tkwin) + slavePtr->doubleBw;
slavePtr->minHeight = Tk_ReqHeight(slavePtr->tkwin) + slavePtr->doubleBw;
}
/*
* Apply minimum row/column dimensions
*/
if (r->width < masterPtr->column.used) {
r->width = masterPtr->column.used;
}
r->lastRow = r->height;
if (r->height < masterPtr->row.used) {
r->height = masterPtr->row.used;
}
/*
* Pass #2
*/
curRow = curCol = -1;
memset(xMax, 0, sizeof(int) * MAXGRIDSIZE);
memset(yMax, 0, sizeof(int) * MAXGRIDSIZE);
for (slavePtr = masterPtr->slavePtr; slavePtr != NULL;
slavePtr = slavePtr->nextPtr) {
curX = slavePtr->gridColumn;
curY = slavePtr->gridRow;
curWidth = slavePtr->gridWidth;
curHeight = slavePtr->gridHeight;
px = curX + curWidth;
py = curY + curHeight;
for (i = curX; i < (curX + curWidth); i++) {
yMax[i] = py;
}
for (i = curY; i < (curY + curHeight); i++) {
xMax[i] = px;
}
/* Assign the new values to the gridbag slave */
slavePtr->tempX = curX;
slavePtr->tempY = curY;
slavePtr->tempWidth = curWidth;
slavePtr->tempHeight = curHeight;
}
/*
* Pass #3
*
* Distribute the minimun widths and weights:
*/
/* Initialize arrays to zero */
memset(r->minWidth, 0, r->width * sizeof(int));
memset(r->minHeight, 0, r->height * sizeof(int));
memset(r->weightX, 0, r->width * sizeof(double));
memset(r->weightY, 0, r->height * sizeof(double));
nextSize = MAXINT;
for (i = 1; i != MAXINT; i = nextSize, nextSize = MAXINT) {
for (slavePtr = masterPtr->slavePtr; slavePtr != NULL;
slavePtr = slavePtr->nextPtr) {
if (slavePtr->tempWidth == i) {
px = slavePtr->tempX + slavePtr->tempWidth; /* right column */
/*
* Figure out if we should use this slave\'s weight. If the weight
* is less than the total weight spanned by the width of the cell,
* then discard the weight. Otherwise split it the difference
* according to the existing weights.
*/
weight_diff = slavePtr->weightX;
for (k = slavePtr->tempX; k < px; k++)
weight_diff -= r->weightX[k];
if (weight_diff > 0.0) {
weight = 0.0;
for (k = slavePtr->tempX; k < px; k++)
weight += r->weightX[k];
for (k = slavePtr->tempX; weight > MINWEIGHT; k++) {
double wt = r->weightX[k];
double dx = (wt * weight_diff) / weight;
r->weightX[k] += dx;
weight_diff -= dx;
weight -= wt;
}
/* Assign the remainder to the rightmost cell */
r->weightX[px-1] += weight_diff;
}
/*
* Calculate the minWidth array values.
* First, figure out how wide the current slave needs to be.
* Then, see if it will fit within the current minWidth values.
* If it won\'t fit, add the difference according to the weightX array.
*/
pixels_diff = slavePtr->minWidth + slavePtr->padX + slavePtr->iPadX;
for (k = slavePtr->tempX; k < px; k++)
pixels_diff -= r->minWidth[k];
if (pixels_diff > 0) {
weight = 0.0;
for (k = slavePtr->tempX; k < px; k++)
weight += r->weightX[k];
for (k = slavePtr->tempX; weight > MINWEIGHT; k++) {
double wt = r->weightX[k];
int dx = (int)((wt * ((double)pixels_diff)) / weight);
r->minWidth[k] += dx;
pixels_diff -= dx;
weight -= wt;
}
/* Any leftovers go into the rightmost cell */
r->minWidth[px-1] += pixels_diff;
}
}
else if (slavePtr->tempWidth > i && slavePtr->tempWidth < nextSize)
nextSize = slavePtr->tempWidth;
if (slavePtr->tempHeight == i) {
py = slavePtr->tempY + slavePtr->tempHeight; /* bottom row */
/*
* Figure out if we should use this slave\'s weight. If the weight
* is less than the total weight spanned by the height of the cell,
* then discard the weight. Otherwise split it the difference
* according to the existing weights.
*/
weight_diff = slavePtr->weightY;
for (k = slavePtr->tempY; k < py; k++)
weight_diff -= r->weightY[k];
if (weight_diff > 0.0) {
weight = 0.0;
for (k = slavePtr->tempY; k < py; k++)
weight += r->weightY[k];
for (k = slavePtr->tempY; weight > MINWEIGHT; k++) {
double wt = r->weightY[k];
double dy = (wt * weight_diff) / weight;
r->weightY[k] += dy;
weight_diff -= dy;
weight -= wt;
}
/* Assign the remainder to the bottom cell */
r->weightY[py-1] += weight_diff;
}
/*
* Calculate the minHeight array values.
* First, figure out how tall the current slave needs to be.
* Then, see if it will fit within the current minHeight values.
* If it won\'t fit, add the difference according to the weightY array.
*/
pixels_diff = slavePtr->minHeight + slavePtr->padY + slavePtr->iPadY;
for (k = slavePtr->tempY; k < py; k++)
pixels_diff -= r->minHeight[k];
if (pixels_diff > 0) {
weight = 0.0;
for (k = slavePtr->tempY; k < py; k++)
weight += r->weightY[k];
for (k = slavePtr->tempY; weight > MINWEIGHT; k++) {
double wt = r->weightY[k];
int dy = (int)((wt * ((double)pixels_diff)) / weight);
r->minHeight[k] += dy;
pixels_diff -= dy;
weight -= wt;
}
/* Any leftovers go into the bottom cell */
r->minHeight[py-1] += pixels_diff;
}
}
else if (slavePtr->tempHeight > i && slavePtr->tempHeight < nextSize)
nextSize = slavePtr->tempHeight;
}
}
/*
* Apply minimum row/column dimensions
*/
for (i=0; i<masterPtr->column.used; i++) {
if (r->minWidth[i] < masterPtr->column.minsize[i])
r->minWidth[i] = masterPtr->column.minsize[i];
if (r->weightX[i] < masterPtr->column.weight[i])
r->weightX[i] = masterPtr->column.weight[i];
}
for (i=0; i<masterPtr->row.used; i++) {
if (r->minHeight[i] < masterPtr->row.minsize[i])
r->minHeight[i] = masterPtr->row.minsize[i];
if (r->weightY[i] < masterPtr->row.weight[i])
r->weightY[i] = masterPtr->row.weight[i];
}
}
/*
* Cache the layout info after it is calculated.
*/
static void
GetCachedLayoutInfo(masterPtr)
GridBag *masterPtr;
{
if (masterPtr->valid == 0) {
if (!masterPtr->layoutCache)
masterPtr->layoutCache = (LayoutInfo *)ckalloc(sizeof(LayoutInfo));
GetLayoutInfo(masterPtr, masterPtr->layoutCache);
masterPtr->valid = 1;
}
}
/*
* Adjusts the x, y, width, and height fields to the correct
* values depending on the constraint geometry and pads.
*/
static void
AdjustForGravity(gridPtr, x, y, width, height)
GridBag *gridPtr;
int *x;
int *y;
int *width;
int *height;
{
int diffx=0, diffy=0;
int sticky = gridPtr->flags&STICK_ALL;
*x += gridPtr->padX/2;
*width -= gridPtr->padX;
*y += gridPtr->padY/2;
*height -= gridPtr->padY;
if (*width > (gridPtr->minWidth + gridPtr->iPadX)) {
diffx = *width - (gridPtr->minWidth + gridPtr->iPadX);
*width = gridPtr->minWidth + gridPtr->iPadX;
}
if (*height > (gridPtr->minHeight + gridPtr->iPadY)) {
diffy = *height - (gridPtr->minHeight + gridPtr->iPadY);
*height = gridPtr->minHeight + gridPtr->iPadY;
}
if (sticky&STICK_EAST && sticky&STICK_WEST)
*width += diffx;
if (sticky&STICK_NORTH && sticky&STICK_SOUTH)
*height += diffy;
if (!(sticky&STICK_WEST)) {
if (sticky&STICK_EAST)
*x += diffx;
else
*x += diffx/2;
}
if (!(sticky&STICK_NORTH)) {
if (sticky&STICK_SOUTH)
*y += diffy;
else
*y += diffy/2;
}
}
/*
* Figure out the minimum size (not counting the X border) of the
* master based on the information from GetLayoutInfo()
*/
static void
GetMinSize(masterPtr, info, minw, minh)
GridBag *masterPtr;
LayoutInfo *info;
int *minw;
int *minh;
{
int i, t;
int intBWidth; /* Width of internal border in parent window,
* if any. */
intBWidth = Tk_InternalBorderWidth(masterPtr->tkwin);
t = 0;
for(i = 0; i < info->width; i++)
t += info->minWidth[i];
*minw = t + 2*intBWidth;
t = 0;
for(i = 0; i < info->height; i++)
t += info->minHeight[i];
*minh = t + 2*intBWidth;
}
/*
*--------------------------------------------------------------
*
* ArrangeGrid --
*
* This procedure is invoked (using the Tk_DoWhenIdle
* mechanism) to re-layout a set of windows managed by
* the gridbag. It is invoked at idle time so that a
* series of gridbag requests can be merged into a single
* layout operation.
*
* Results:
* None.
*
* Side effects:
* The slaves of masterPtr may get resized or moved.
*
*--------------------------------------------------------------
*/
static void
ArrangeGrid(clientData)
ClientData clientData; /* Structure describing parent whose slaves
* are to be re-layed out. */
{
register GridBag *masterPtr = (GridBag *) clientData;
register GridBag *slavePtr;
int abort;
int i, x, y, width, height;
int diffw, diffh;
double weight;
Tk_Window parent, ancestor;
LayoutInfo info;
int intBWidth; /* Width of internal border in parent window,
* if any. */
int iPadX, iPadY;
masterPtr->flags &= ~REQUESTED_RELAYOUT;
/*
* If the parent has no slaves anymore, then don't do anything
* at all: just leave the parent's size as-is.
* Even if row and column constraints have been set!
*/
if (masterPtr->slavePtr == 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;
Tk_Preserve((ClientData) masterPtr);
/*
* Pass #1: scan all the slaves to figure out the total amount
* of space needed.
*/
GetLayoutInfo(masterPtr, &info);
GetMinSize(masterPtr, &info, &width, &height);
if (((width != Tk_ReqWidth(masterPtr->tkwin))
|| (height != Tk_ReqHeight(masterPtr->tkwin)))
&& !(masterPtr->flags & DONT_PROPAGATE)) {
Tk_GeometryRequest(masterPtr->tkwin, width, height);
masterPtr->flags |= REQUESTED_RELAYOUT;
masterPtr->valid = 0;
Tk_DoWhenIdle(ArrangeGrid, (ClientData) masterPtr);
goto done;
}
/*
* If the parent isn't mapped then don't do anything more: wait
* until it gets mapped again. Need to get at least to here to
* reflect size needs up the window hierarchy, but there's no
* point in actually mapping the slaves.
*/
if (!Tk_IsMapped(masterPtr->tkwin)) {
goto done;
}
/*
* If the current dimensions of the window don't match the desired
* dimensions, then adjust the minWidth and minHeight arrays
* according to the weights.
*/
diffw = Tk_Width(masterPtr->tkwin) - (width + masterPtr->iPadX);
if (diffw != 0) {
weight = 0.0;
for (i = 0; i < info.width; i++)
weight += info.weightX[i];
if (weight > MINWEIGHT) {
for (i = 0; i < info.width; i++) {
int dx = (int)(( ((double)diffw) * info.weightX[i]) / weight);
info.minWidth[i] += dx;
width += dx;
if (info.minWidth[i] < 0) {
width -= info.minWidth[i];
info.minWidth[i] = 0;
}
}
}
diffw = Tk_Width(masterPtr->tkwin) - (width + masterPtr->iPadX);
}
else {
diffw = 0;
}
diffh = Tk_Height(masterPtr->tkwin) - (height + masterPtr->iPadY);
if (diffh != 0) {
weight = 0.0;
for (i = 0; i < info.height; i++)
weight += info.weightY[i];
if (weight > MINWEIGHT) {
for (i = 0; i < info.height; i++) {
int dy = (int)(( ((double)diffh) * info.weightY[i]) / weight);
info.minHeight[i] += dy;
height += dy;
if (info.minHeight[i] < 0) {
height -= info.minHeight[i];
info.minHeight[i] = 0;
}
}
}
diffh = Tk_Height(masterPtr->tkwin) - (height + masterPtr->iPadY);
}
else {
diffh = 0;
}
/*
* Now do the actual layout of the slaves using the layout information
* that has been collected.
*/
iPadX = masterPtr->iPadX/2;
iPadY = masterPtr->iPadY/2;
intBWidth = Tk_InternalBorderWidth(masterPtr->tkwin);
for (slavePtr = masterPtr->slavePtr; slavePtr != NULL;
slavePtr = slavePtr->nextPtr) {
masterPtr->startx = x = diffw/2 + intBWidth + iPadX;
for(i = 0; i < slavePtr->tempX; i++)
x += info.minWidth[i];
masterPtr->starty = y = diffh/2 + intBWidth + iPadY;
for(i = 0; i < slavePtr->tempY; i++)
y += info.minHeight[i];
width = 0;
for(i = slavePtr->tempX; i < (slavePtr->tempX + slavePtr->tempWidth); i++)
width += info.minWidth[i];
height = 0;
for(i = slavePtr->tempY; i < (slavePtr->tempY + slavePtr->tempHeight); i++)
height += info.minHeight[i];
AdjustForGravity(slavePtr, &x, &y, &width, &height);
/*
* If the window in which slavePtr is managed is not its
* parent in the window hierarchy, translate the coordinates
* to the coordinate system of the real X parent.
*/
parent = Tk_Parent(slavePtr->tkwin);
for (ancestor = masterPtr->tkwin; ancestor != parent;
ancestor = Tk_Parent(ancestor)) {
x += Tk_X(ancestor) + Tk_Changes(ancestor)->border_width;
y += Tk_Y(ancestor) + Tk_Changes(ancestor)->border_width;
}
/*
* If the window is too small to be interesting then
* unmap it. Otherwise configure it and then make sure
* it's mapped.
*/
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) {
goto done;
}
Tk_MapWindow(slavePtr->tkwin);
}
/*
* Changes to the window's structure could cause almost anything
* to happen, including deleting the parent or child. If this
* happens, we'll be told to abort.
*/
if (abort) {
goto done;
}
}
done:
masterPtr->abortPtr = NULL;
Tk_Release((ClientData) masterPtr);
}
/*
*--------------------------------------------------------------
*
* GetGridBag --
*
* This internal procedure is used to locate a GridBag
* structure for a given window, creating one if one
* doesn't exist already.
*
* Results:
* The return value is a pointer to the GridBag structure
* corresponding to tkwin.
*
* Side effects:
* A new gridbag structure may be created. If so, then
* a callback is set up to clean things up when the
* window is deleted.
*
*--------------------------------------------------------------
*/
static GridBag *
GetGridBag(tkwin)
Tk_Window tkwin; /* Token for window for which
* gridbag structure is desired. */
{
register GridBag *gridPtr;
Tcl_HashEntry *hPtr;
int new;
if (!initialized) {
initialized = 1;
Tcl_InitHashTable(&gridBagHashTable, TCL_ONE_WORD_KEYS);
}
/*
* See if there's already gridbag for this window. If not,
* then create a new one.
*/
hPtr = Tcl_CreateHashEntry(&gridBagHashTable, (char *) tkwin, &new);
if (!new) {
return (GridBag *) Tcl_GetHashValue(hPtr);
}
gridPtr = (GridBag *) ckalloc(sizeof(GridBag));
gridPtr->tkwin = tkwin;
gridPtr->masterPtr = NULL;
gridPtr->nextPtr = NULL;
gridPtr->slavePtr = NULL;
gridPtr->gridColumn = gridPtr->gridRow = -1;
gridPtr->gridWidth = gridPtr->gridHeight = 1;
gridPtr->weightX = gridPtr->weightY = 0.0;
gridPtr->minWidth = gridPtr->minHeight = 0;
gridPtr->padX = gridPtr->padY = 0;
gridPtr->iPadX = gridPtr->iPadY = 0;
gridPtr->startx = gridPtr->starty = 0;
gridPtr->doubleBw = 2*Tk_Changes(tkwin)->border_width;
gridPtr->abortPtr = NULL;
gridPtr->flags = 0;
gridPtr->column.max = 0;
gridPtr->row.max = 0;
gridPtr->column.used = 0;
gridPtr->row.used = 0;
gridPtr->valid = 0;
gridPtr->layoutCache = NULL;
Tcl_SetHashValue(hPtr, gridPtr);
Tk_CreateEventHandler(tkwin, StructureNotifyMask,
GridBagStructureProc, (ClientData) gridPtr);
return gridPtr;
}
/*
*----------------------------------------------------------------------
*
* Unlink --
*
* Remove a gridbag from its parent's list of slaves.
*
* Results:
* None.
*
* Side effects:
* The parent will be scheduled for re-arranging.
*
*----------------------------------------------------------------------
*/
static void
Unlink(gridPtr)
register GridBag *gridPtr; /* Window to unlink. */
{
register GridBag *masterPtr, *gridPtr2;
masterPtr = gridPtr->masterPtr;
if (masterPtr == NULL) {
return;
}
if (masterPtr->slavePtr == gridPtr) {
masterPtr->slavePtr = gridPtr->nextPtr;
}
else {
for (gridPtr2 = masterPtr->slavePtr; ; gridPtr2 = gridPtr2->nextPtr) {
if (gridPtr2 == NULL) {
panic("Unlink couldn't find previous window");
}
if (gridPtr2->nextPtr == gridPtr) {
gridPtr2->nextPtr = gridPtr->nextPtr;
break;
}
}
}
masterPtr->valid = 0;
if (!(masterPtr->flags & REQUESTED_RELAYOUT)) {
masterPtr->flags |= REQUESTED_RELAYOUT;
Tk_DoWhenIdle(ArrangeGrid, (ClientData) masterPtr);
}
if (masterPtr->abortPtr != NULL) {
*masterPtr->abortPtr = 1;
}
gridPtr->masterPtr = NULL;
}
/*
*----------------------------------------------------------------------
*
* DestroyGridBag --
*
* This procedure is invoked by Tk_EventuallyFree or Tk_Release
* to clean up the internal structure of a gridbag at a safe time
* (when no-one is using it anymore).
*
* Results:
* None.
*
* Side effects:
* Everything associated with the gridbag is freed up.
*
*----------------------------------------------------------------------
*/
static void
DestroyGridBag(memPtr)
char *memPtr; /* Info about window that is now dead. */
{
register GridBag *gridPtr = (GridBag *) memPtr;
if (gridPtr->column.max) {
ckfree((char *) gridPtr->column.minsize);
ckfree((char *) gridPtr->column.weight);
}
if (gridPtr->row.max) {
ckfree((char *) gridPtr->row.minsize);
ckfree((char *) gridPtr->row.weight);
}
if (gridPtr->layoutCache)
ckfree((char *) gridPtr->layoutCache);
ckfree((char *) gridPtr);
}
/*
*----------------------------------------------------------------------
*
* GridBagStructureProc --
*
* 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 gridbag-related
* information. If it was just resized, re-configure its slaves, if
* any.
*
*----------------------------------------------------------------------
*/
static void
GridBagStructureProc(clientData, eventPtr)
ClientData clientData; /* Our information about window
* referred to by eventPtr. */
XEvent *eventPtr; /* Describes what just happened. */
{
register GridBag *gridPtr = (GridBag *) clientData;
if (eventPtr->type == ConfigureNotify) {
gridPtr->valid = 0;
if (!(gridPtr->flags & REQUESTED_RELAYOUT)) {
gridPtr->flags |= REQUESTED_RELAYOUT;
Tk_DoWhenIdle(ArrangeGrid, (ClientData) gridPtr);
}
if (gridPtr->doubleBw != 2*Tk_Changes(gridPtr->tkwin)->border_width) {
if ((gridPtr->masterPtr != NULL) &&
!(gridPtr->masterPtr->flags & REQUESTED_RELAYOUT)) {
gridPtr->doubleBw = 2*Tk_Changes(gridPtr->tkwin)->border_width;
gridPtr->masterPtr->flags |= REQUESTED_RELAYOUT;
Tk_DoWhenIdle(ArrangeGrid, (ClientData) gridPtr->masterPtr);
}
}
}
else if (eventPtr->type == DestroyNotify) {
register GridBag *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(&gridBagHashTable,
(char *) gridPtr->tkwin));
if (gridPtr->flags & REQUESTED_RELAYOUT) {
Tk_CancelIdleCall(ArrangeGrid, (ClientData) gridPtr);
}
gridPtr->tkwin = NULL;
Tk_EventuallyFree((ClientData) gridPtr, DestroyGridBag);
}
else if (eventPtr->type == MapNotify) {
gridPtr->valid = 0;
if (!(gridPtr->flags & REQUESTED_RELAYOUT)) {
gridPtr->flags |= REQUESTED_RELAYOUT;
Tk_DoWhenIdle(ArrangeGrid, (ClientData) gridPtr);
}
}
else if (eventPtr->type == UnmapNotify) {
register GridBag *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
* gridbag to manage the slaves and sets the specified options.
*
* Results:
* TCL_OK is returned if all went well. Otherwise, TCL_ERROR is
* returned and interp->result is set to contain an error message.
*
* Side effects:
* Slave windows get taken over by the gridbag.
*
*----------------------------------------------------------------------
*/
static int
ConfigureSlaves(interp, tkwin, argc, argv)
Tcl_Interp *interp; /* Interpreter for error reporting. */
Tk_Window tkwin; /* Any window in application containing
* slaves. Used to look up slave names. */
int argc; /* Numb = 0er of elements in argv. */
char *argv[]; /* Argument strings: contains one or more
* window names followed by any number
* of "option value" pairs. Caller must
* make sure that there is at least one
* window name. */
{
GridBag *masterPtr, *slavePtr, *prevPtr;
Tk_Window other, slave, parent, ancestor;
int i, j, numWindows, c, length, tmp, positionGiven;
int currentColumn=0, numColumns=1;
int gotLayout = 0;
int gotWidth = 0;
int width;
/*
* Find out how many windows are specified. (shouldn't use harwired symbols)
*/
for (numWindows = 0; numWindows < argc; numWindows++) {
if (argv[numWindows][0] != '.'
&& strcmp(argv[numWindows],"-")!=0
&& strcmp(argv[numWindows],"^")!=0
&& strcmp(argv[numWindows],"x")!=0) {
break;
}
}
slave = NULL;
/*
* Iterate over all of the slave windows, parsing the configuration
* options for each slave. It's a bit wasteful to re-parse the
* options for each slave, but things get too messy if we try to
* parse the arguments just once at the beginning. For example,
* if a slave already is managed we want to just change a few
* existing values without resetting everything. If there are
* multiple windows, the -in option only gets processed for the
* first window.
*/
masterPtr = NULL;
prevPtr = NULL;
positionGiven = 0;
for (j = 0; j < numWindows; j++) {
/* adjust default widget location for non-widgets */
if (*argv[j] != '.') {
switch (*argv[j]) {
case '^': /* extend the widget in the previous row
* Since we don't know who the master is yet,
* handle these in a separate pass at the end
*/
/* no break */
case REL_SKIP: /* skip over the next column */
currentColumn++;
break;
case REL_HORIZ: /* increase the span, already dealt with */
/* not quite right */
if (j>0 && (*argv[j-1] == REL_SKIP || *argv[j-1] == '^')) {
Tcl_AppendResult(interp, "Invalid grid combination:",
" \"-\" can't follow \"", argv[j-1], "\"",NULL);
return TCL_ERROR;
}
break;
default:
panic("Invalid grid position indicator");
}
continue;
}
for (numColumns=1; j+numColumns < numWindows && *argv[j+numColumns] == REL_HORIZ;
numColumns++) {
/* null body */
}
slave = Tk_NameToWindow(interp, argv[j], tkwin);
if (slave == NULL) {
return TCL_ERROR;
}
if (Tk_IsTopLevel(slave)) {
Tcl_AppendResult(interp, "can't manage \"", argv[j],
"\": it's a top-level window", (char *) NULL);
return TCL_ERROR;
}
slavePtr = GetGridBag(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 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 she can reset the
* defaults for herself, but she will never have to worry about keeping
* track of the old state.
*/
for (i = numWindows; i < argc; i+=2) {
if ((i+2) > argc) {
Tcl_AppendResult(interp, "extra option \"", argv[i],
"\" (option with no value?)", (char *) NULL);
return TCL_ERROR;
}
length = strlen(argv[i]);
if (length < 2) {
goto badOption;
}
c = argv[i][1];
if ((c == 'i') && (strcmp(argv[i], "-in") == 0)) {
if (j == 0) {
other = Tk_NameToWindow(interp, argv[i+1], tkwin);
if (other == NULL) {
return TCL_ERROR;
}
if (other == slave) {
sprintf(interp->result,"Window can't be managed in itself");
return TCL_ERROR;
}
masterPtr = GetGridBag(other);
prevPtr = masterPtr->slavePtr;
if (prevPtr != NULL) {
while (prevPtr->nextPtr != NULL) {
prevPtr = prevPtr->nextPtr;
}
}
positionGiven = 1;
}
} else if ((c == 'i') && (strcmp(argv[i], "-ipadx") == 0)) {
if ((Tk_GetPixels(interp, slave, argv[i+1], &tmp) != TCL_OK)
|| (tmp < 0)) {
Tcl_ResetResult(interp);
Tcl_AppendResult(interp, "bad ipadx value \"", argv[i+1],
"\": must be positive screen distance",
(char *) NULL);
return TCL_ERROR;
}
slavePtr->iPadX = tmp*2;
} else if ((c == 'i') && (strcmp(argv[i], "-ipady") == 0)) {
if ((Tk_GetPixels(interp, slave, argv[i+1], &tmp) != TCL_OK)
|| (tmp< 0)) {
Tcl_ResetResult(interp);
Tcl_AppendResult(interp, "bad ipady value \"", argv[i+1],
"\": must be positive screen distance",
(char *) NULL);
return TCL_ERROR;
}
slavePtr->iPadY = tmp*2;
} else if ((c == 'p') && (strcmp(argv[i], "-padx") == 0)) {
if ((Tk_GetPixels(interp, slave, argv[i+1], &tmp) != TCL_OK)
|| (tmp< 0)) {
Tcl_ResetResult(interp);
Tcl_AppendResult(interp, "bad padx value \"", argv[i+1],
"\": must be positive screen distance",
(char *) NULL);
return TCL_ERROR;
}
slavePtr->padX = tmp*2;
} else if ((c == 'p') && (strcmp(argv[i], "-pady") == 0)) {
if ((Tk_GetPixels(interp, slave, argv[i+1], &tmp) != TCL_OK)
|| (tmp< 0)) {
Tcl_ResetResult(interp);
Tcl_AppendResult(interp, "bad pady value \"", argv[i+1],
"\": must be positive screen distance",
(char *) NULL);
return TCL_ERROR;
}
slavePtr->padY = tmp*2;
} else if ((c == 'c') && (strcmp(argv[i], "-column") == 0)) {
if (Tcl_GetInt(interp, argv[i+1], &tmp) != TCL_OK || tmp<0) {
Tcl_ResetResult(interp);
Tcl_AppendResult(interp, "bad column value \"", argv[i+1],
"\": must be a non-negative integer", (char *)NULL);
return TCL_ERROR;
}
slavePtr->gridColumn = tmp;
} else if ((c == 'r') && (strcmp(argv[i], "-row") == 0)) {
if (Tcl_GetInt(interp, argv[i+1], &tmp) != TCL_OK || tmp<0) {
Tcl_ResetResult(interp);
Tcl_AppendResult(interp, "bad grid value \"", argv[i+1],
"\": must be a non-negative integer", (char *)NULL);
return TCL_ERROR;
}
slavePtr->gridRow = tmp;
} else if ((c == 'c') && (strcmp(argv[i], "-columnspan") == 0)) {
if (Tcl_GetInt(interp, argv[i+1], &tmp) != TCL_OK || tmp <= 0) {
Tcl_ResetResult(interp);
Tcl_AppendResult(interp, "bad columnspan value \"", argv[i+1],
"\": must be a positive integer", (char *)NULL);
return TCL_ERROR;
}
slavePtr->gridWidth = tmp;
gotWidth++;
} else if ((c == 'r') && (strcmp(argv[i], "-rowspan") == 0)) {
if (Tcl_GetInt(interp, argv[i+1], &tmp) != TCL_OK) {
Tcl_ResetResult(interp);
Tcl_AppendResult(interp, "bad rowspan value \"", argv[i+1],
"\": must be a positive integer", (char *)NULL);
return TCL_ERROR;
}
slavePtr->gridHeight = tmp;
/*
} else if ((c == 'w') &&
(!strcmp(argv[i], "-weightx") || !strcmp(argv[i], "-wx"))) {
if (Tcl_GetDouble(interp, argv[i+1], &tmp_dbl) != TCL_OK) {
Tcl_ResetResult(interp);
Tcl_AppendResult(interp, "bad weight value \"", argv[i+1],
"\": must be a double", (char *)NULL);
return TCL_ERROR;
}
slavePtr->weightX = tmp_dbl;
}
else if ((c == 'w') &&
(!strcmp(argv[i], "-weighty") || !strcmp(argv[i], "-wy"))) {
if (Tcl_GetDouble(interp, argv[i+1], &tmp_dbl) != TCL_OK) {
Tcl_ResetResult(interp);
Tcl_AppendResult(interp, "bad weight value \"", argv[i+1],
"\": must be a double", (char *)NULL);
return TCL_ERROR;
}
slavePtr->weightY = tmp_dbl;
*/
} else if ((c == 's') && strcmp(argv[i], "-sticky") == 0) {
int sticky = StringToSticky(argv[i+1]);
if (sticky == -1) {
Tcl_AppendResult(interp, "bad stickyness value \"", argv[i+1],
"\": must be a string containing n, e, s, and/or w", (char *)NULL);
return TCL_ERROR;
}
slavePtr->flags = sticky | (slavePtr->flags & ~STICK_ALL);
} else {
badOption:
Tcl_AppendResult(interp, "unknown or ambiguous option \"",
argv[i], "\": must be -in, -sticky, ",
"-row, -column, -rowspan, -columnspan, ",
"-ipadx, -ipady, -padx or -pady.",
(char *) NULL);
return TCL_ERROR;
}
}
/*
* If no position in a gridbag list was specified and the slave
* is already managed, then leave it in its current location in
* its current gridbag list.
*/
if (!positionGiven && (slavePtr->masterPtr != NULL)) {
masterPtr = slavePtr->masterPtr;
goto scheduleLayout;
}
/*
* If the slave is going to be put back after itself then
* skip the whole operation, since it won't work anyway.
*/
if (prevPtr == slavePtr) {
masterPtr = slavePtr->masterPtr;
goto scheduleLayout;
}
/*
* If the "-in" option has not been specified, arrange for the
* slave to go at the end of the order for its parent.
*/
if (!positionGiven) {
masterPtr = GetGridBag(Tk_Parent(slave));
prevPtr = masterPtr->slavePtr;
if (prevPtr != NULL) {
while (prevPtr->nextPtr != NULL) {
prevPtr = prevPtr->nextPtr;
}
}
}
/*
* Make sure that the slave's parent is either the master or
* an ancestor of the master.
*/
parent = Tk_Parent(slave);
for (ancestor = masterPtr->tkwin; ; ancestor = Tk_Parent(ancestor)) {
if (ancestor == parent) {
break;
}
if (Tk_IsTopLevel(ancestor)) {
Tcl_AppendResult(interp, "can't put ", argv[j],
" inside ", Tk_PathName(masterPtr->tkwin),
(char *) NULL);
return TCL_ERROR;
}
}
/*
* Unlink the slave if it's currently managed, then position it
* after prevPtr.
*/
if (slavePtr->masterPtr != NULL) {
Unlink(slavePtr);
}
slavePtr->masterPtr = masterPtr;
if (prevPtr == NULL) {
slavePtr->nextPtr = masterPtr->slavePtr;
masterPtr->slavePtr = slavePtr;
} else {
slavePtr->nextPtr = prevPtr->nextPtr;
prevPtr->nextPtr = slavePtr;
}
Tk_ManageGeometry(slave, &gridMgrType, (ClientData) slavePtr);
prevPtr = slavePtr;
/* assign default row and column */
if (slavePtr->gridColumn == -1) {
slavePtr->gridColumn = currentColumn;
}
slavePtr->gridWidth += numColumns - 1;
if (slavePtr->gridRow == -1) {
if (!gotLayout++) GetCachedLayoutInfo(masterPtr);
slavePtr->gridRow = masterPtr->layoutCache->lastRow;
}
/*
* Arrange for the parent to be re-arranged at the first
* idle moment.
*/
scheduleLayout:
if (masterPtr->abortPtr != NULL) {
*masterPtr->abortPtr = 1;
}
masterPtr->valid = 0;
if (!(masterPtr->flags & REQUESTED_RELAYOUT)) {
masterPtr->flags |= REQUESTED_RELAYOUT;
Tk_DoWhenIdle(ArrangeGrid, (ClientData) masterPtr);
}
currentColumn += slavePtr->gridWidth;
numColumns = 1;
}
/* now look for all the "^"'s */
for (j = 0; j < numWindows; j++) {
struct GridBag *otherPtr;
char *lastWindow; /* use this window to base current row/col on */
int match; /* found a match for the ^ */
if (*argv[j] == '.') {
lastWindow = argv[j];
}
if (*argv[j] != '^') {
continue;
}
for (width=1; width+j < numWindows && *argv[j+width] == '^'; width++) {
/* Null Body */
}
other = Tk_NameToWindow(interp, lastWindow, tkwin);
otherPtr = GetGridBag(other);
if (!gotLayout++) GetCachedLayoutInfo(masterPtr);
for (match=0, slavePtr = masterPtr->slavePtr; slavePtr != NULL;
slavePtr = slavePtr->nextPtr) {
if (slavePtr->gridWidth == width
&& slavePtr->gridColumn == otherPtr->gridColumn + otherPtr->gridWidth
&& slavePtr->gridRow + slavePtr->gridHeight == otherPtr->gridRow) {
slavePtr->gridHeight++;
match++;
}
lastWindow = Tk_PathName(slavePtr->tkwin);
}
if (!match) {
Tcl_AppendResult(interp, "can't find slave to extend with \"^\"",
" after ",lastWindow,
(char *) NULL);
return TCL_ERROR;
}
j += width - 1;
}
return TCL_OK;
}
/* convert "Sticky" bits into a string */
static void
StickyToString(flags, result)
int flags; /* the sticky flags */
char *result; /* where to put the result */
{
int count = 0;
if (flags&STICK_NORTH) result[count++] = 'n';
if (flags&STICK_EAST) result[count++] = 'e';
if (flags&STICK_SOUTH) result[count++] = 's';
if (flags&STICK_WEST) result[count++] = 'w';
if (count) {
result[count] = '\0';
} else {
sprintf(result,"{}");
}
}
/* convert sticky string to flags */
static int
StringToSticky(string)
char *string;
{
int sticky = 0;
char c;
while ((c = *string++) != '\0') {
switch (c) {
case 'n': case 'N': sticky |= STICK_NORTH; break;
case 'e': case 'E': sticky |= STICK_EAST; break;
case 's': case 'S': sticky |= STICK_SOUTH; break;
case 'w': case 'W': sticky |= STICK_WEST; break;
case ' ': case ',': case '\t': case '\r': case '\n': break;
default: return -1;
}
}
return sticky;
}