The Perl and Raku Conference 2025: Greenville, South Carolina - June 27-29 Learn more

#include "apricot.h"
#include "guts.h"
#include "Widget.h"
#include "private/Widget.h"
#ifdef __cplusplus
extern "C" {
#endif
/*
* 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.
*
* RCS: @(#) $Id: tkGrid.c,v 1.25 2002/10/10 21:07:51 pspjuth Exp $
*/
/*
* Convenience Macros
*/
#ifdef MAX
# undef MAX
#endif
#define MAX(x,y) ((x) > (y) ? (x) : (y))
#ifdef MIN
# undef MIN
#endif
#define MIN(x,y) ((x) > (y) ? (y) : (x))
#define COLUMN (1) /* working on column offsets */
#define ROW (2) /* working on row offsets */
#define CHECK_ONLY (1) /* check max slot constraint */
#define CHECK_SPACE (2) /* alloc more space, don't change max */
/*
* Pre-allocate enough row and column slots for "typical" sized tables
* this value should be chosen so by the time the extra malloc's are
* required, the layout calculations overwehlm them. [A "slot" contains
* information for either a row or column, depending upon the context.]
*/
#define TYPICAL_SIZE 25 /* (arbitrary guess) */
#define PREALLOC 10 /* extra slots to allocate */
/*
* 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. */
/*
* Structure to hold information for grid masters. A slot is either
* a row or column.
*/
typedef struct SlotInfo {
int minSize; /* The minimum size of this slot (in pixels).
* It is set via the rowconfigure or
* columnconfigure commands. */
int weight; /* The resize weight of this slot. (0) means
* this slot doesn't resize. Extra space in
* the layout is given distributed among slots
* inproportion to their weights. */
int pad; /* Extra padding, in pixels, required for
* this slot. This amount is "added" to the
* largest slave in the slot. */
int 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. */
unsigned int 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
* right/bottom edge of the slot calculated
* from top/left to bottom/right. */
int maxOffset; /* The maximum offset, in pixels, from
* the beginning of the layout to the
* right-or-bottom edge of the slot calculated
* from bottom-or-right to top-or-left. */
} GridLayout;
/*
* Keep one of these for each geometry master.
*/
typedef struct {
SlotInfo *columnPtr; /* Pointer to array of column constraints. */
SlotInfo *rowPtr; /* Pointer to array of row constraints. */
int columnEnd; /* The last column occupied by any slave. */
int columnMax; /* The number of columns with constraints. */
int columnSpace; /* The number of slots currently allocated for
* column constraints. */
int rowEnd; /* The last row occupied by any slave. */
int rowMax; /* The number of rows with constraints. */
int rowSpace; /* The number of slots currently allocated
* for row constraints. */
int startX; /* Pixel offset of this layout within its
* parent. */
int startY; /* Pixel offset of this layout within its
* parent. */
} GridMaster;
/*
* For each window that the grid cares about (either because
* the window is managed by the grid or because the window
* has slaves that are managed by the grid), there is a
* structure of the following type:
*/
typedef struct Gridder {
Handle window; /* Tk token for window. NULL means that
* the window has been deleted, but the
* gridder hasn't had a chance to clean up
* yet because the structure is still in
* use. */
struct Gridder *masterPtr; /* Master window within which this window
* is managed (NULL means this window
* isn't managed by the gridder). */
struct Gridder *nextPtr; /* Next window managed within same
* parent. List order doesn't matter. */
struct Gridder *slavePtr; /* First in list of slaves managed
* inside this window (NULL means
* no grid slaves). */
GridMaster *masterDataPtr; /* Additional data for geometry master. */
int column, row; /* Location in the grid (starting
* from zero). */
int numCols, numRows; /* Number of columns or rows this slave spans.
* Should be at least 1. */
int padX, padY; /* Total additional pixels to leave around the
* window. 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, padBottom; /* 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 *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 window or any of its slaves
* is deleted. */
int flags; /* Miscellaneous flags; see below
* for definitions. */
Handle saved_in; /* remember eventual in, if not empty, otherwise owner */
int lock; /* Tk's ConfigureSlaves delays rearrangements, Prima doesn't - but there's locking for it */
/*
* 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 {
unsigned int group;
int minSize;
} UniformGroup;
/*
* Flag values for Grid structures:
*
* 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 DONT_PROPAGATE 2
/*
* 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, register SlotInfo *slotPtr);
static void ArrangeGrid(Gridder *masterPtr);
static Bool CheckSlotData(Gridder *masterPtr, int slot, int slotType, int checkOnly);
static void ConfigureSlaves(Handle self, SV * window, HV * profile);
static void DestroyGrid(Handle self);
static Gridder* GetGrid(Handle self);
static Bool GridBboxCommand(Handle self, PList in, PList out);
static Bool GridInfoCommand(Handle self, PList in, PList out);
static Bool GridForgetRemoveCommand(Handle self, Bool forget, PList in, PList out);
static Bool GridLocationCommand(Handle self, PList in, PList out);
static Bool GridPropagateCommand(Handle self, PList in, PList out);
static Bool GridRowColumnConfigureCommand(Handle self, int slotType, PList in, PList out);
static Bool GridSizeCommand(Handle self, PList in, PList out);
static void InitMasterData(Gridder *masterPtr);
static int ResolveConstraints(Gridder *gridPtr, int rowOrColumn, int maxOffset);
static void SetGridSize(Gridder *gridPtr);
static void StickyToString(int flags, char *result);
static int StringToSticky(char *string);
static void Unlink(Gridder *gridPtr);
#define ARGsv(x) ((SV*)(in->items[x]))
#define ARGi(x) SvIV(ARGsv(x))
#define ARGs(x) SvPV_nolen(ARGsv(x))
#define RETsv(x) list_add(out, (Handle)(x))
#define RETi(x) list_add(out, (Handle) newSViv(x))
#define RETs(x) list_add(out, (Handle) newSVpv(x,0))
/*
*----------------------------------------------------------------------
*
* GridBboxCommand --
*
* Implementation of the [grid bbox] subcommand.
*
*
*----------------------------------------------------------------------
*/
static Bool
GridBboxCommand(Handle self, PList in, PList out)
{
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 */
Box r = {0,0,0,0};
switch (in->count) {
case 0:
row = column = row2 = column2 = 0;
break;
case 2:
column = column2 = ARGi(0);
row = row2 = ARGi(1);
break;
case 4:
column = ARGi(0);
row = ARGi(1);
column2 = ARGi(2);
row2 = ARGi(3);
break;
default:
return false;
}
masterPtr = GetGrid(self);
if ( !( gridPtr = masterPtr->masterDataPtr))
return false;
SetGridSize(masterPtr);
endX = MAX(gridPtr->columnEnd, gridPtr->columnMax);
endY = MAX(gridPtr->rowEnd, gridPtr->rowMax);
if ((endX == 0) || (endY == 0))
goto EXIT;
if ( in-> count == 0 ) {
row = column = 0;
row2 = endY;
column2 = endX;
}
if (column > column2) {
int temp = column;
column = column2, column2 = temp;
}
if (row > row2) {
int temp = row;
row = row2, row2 = temp;
}
if (column > 0 && column < endX) {
r.x = gridPtr->columnPtr[column-1].offset;
} else if (column > 0) {
r.x = gridPtr->columnPtr[endX-1].offset;
}
if (row > 0 && row < endY) {
r.y = gridPtr->rowPtr[row-1].offset;
} else if (row > 0) {
r.y = gridPtr->rowPtr[endY-1].offset;
}
if (column2 < 0) {
r.width = 0;
} else if (column2 >= endX) {
r.width = gridPtr->columnPtr[endX-1].offset - r.x;
} else {
r.width = gridPtr->columnPtr[column2].offset - r.x;
}
if (row2 < 0) {
r.height = 0;
} else if (row2 >= endY) {
r.height = gridPtr->rowPtr[endY-1].offset - r.y;
} else {
r.height = gridPtr->rowPtr[row2].offset - r.y;
}
r.x += gridPtr->startX;
r.y += gridPtr->startY;
EXIT:
RETi(r.x);
RETi(r.y);
RETi(r.width);
RETi(r.height);
return true;
}
/*
*----------------------------------------------------------------------
*
* GridForgetRemoveCommand --
*
* Implementation of the [grid forget]/[grid remove] subcommands.
* See the user documentation for details on what these do.
*
* Side effects:
* Removes a window from a grid layout.
*
*----------------------------------------------------------------------
*/
static Handle
get_slave( Handle self, SV * window)
{
if ( !window )
return self;
if ( !SvOK(window))
return NULL_HANDLE;
return SvROK(window) ?
gimme_the_mate(window) :
my->bring(self, SvPV_nolen(window), is_opt(optDeepLookup) ? 1000 : 0);
}
static Bool
GridForgetRemoveCommand(Handle self, Bool forget, PList in, PList out)
{
int i;
for (i = 0; i < in->count; i++) {
Handle slave = get_slave(self, ARGsv(i));
Gridder *slavePtr = GetGrid(slave), *masterPtr = slavePtr->masterPtr;
if (!masterPtr)
continue;
/*
* For "forget", reset all the settings to their defaults
*/
if (forget) {
slavePtr->column = slavePtr->row = -1;
slavePtr->numCols = 1;
slavePtr->numRows = 1;
slavePtr->padX = slavePtr->padY = 0;
slavePtr->padLeft = slavePtr->padBottom = 0;
slavePtr->iPadX = slavePtr->iPadY = 0;
slavePtr->flags = 0;
slavePtr->sticky = 0;
}
Unlink(slavePtr);
CWidget(slavePtr->window)->hide(slavePtr->window);
CWidget(slave)->set_geometry(self, gtGrowMode);
SetGridSize(masterPtr);
ArrangeGrid(masterPtr);
}
return true;
}
/*
*----------------------------------------------------------------------
*
* GridInfoCommand --
*
* Implementation of the [grid info] subcommand. See the user
* documentation for details on what it does.
*
*----------------------------------------------------------------------
*/
static SV*
two_ints(int a, int b)
{
AV * av = newAV();
av_push(av, newSViv(a));
av_push(av, newSViv(b-a));
return newRV_noinc((SV*) av);
}
static Bool
GridInfoCommand(Handle self, PList in, PList out)
{
HV * profile = newHV();
register Gridder *p;
char buffer[4];
if ( in->count != 0 )
return false;
p = GetGrid(self);
pset_H( in , p-> saved_in ? p-> saved_in : var->owner );
pset_i( column , p-> column );
pset_i( row , p-> row );
pset_i( colspan , p-> numCols );
pset_i( rowspan , p-> numRows );
pset_i( ipadx , p-> iPadX );
pset_i( ipady , p-> iPadY );
if ( p->padX * 2 != p->padLeft )
pset_sv( padx, two_ints(p->padLeft,p->padX) );
else
pset_i( padx, p-> padX );
if ( p->padY * 2 != p->padBottom )
pset_sv( pady, two_ints(p->padBottom,p->padY) );
else
pset_i( pady, p-> padY );
StickyToString(p->sticky,buffer);
pset_c( sticky , buffer);
RETsv(newRV_noinc(( SV *) profile));
return true;
}
/*
*----------------------------------------------------------------------
*
* GridLocationCommand --
*
* Implementation of the [grid location] subcommand. See the user
* documentation for details on what it does.
*
*----------------------------------------------------------------------
*/
static Bool
GridLocationCommand(Handle self, PList in, PList out)
{
Gridder *masterPtr; /* master grid record */
GridMaster *gridPtr; /* pointer to grid data */
register SlotInfo *slotPtr;
int x, y; /* Offset in pixels, from edge of parent. */
int endX, endY; /* end of grid */
Point r = {-1,-1}; /* Corresponding column and row indeces. */
if (in->count != 2)
return false;
masterPtr = GetGrid(self);
if (masterPtr->masterDataPtr == NULL)
return false;
x = ARGi(0);
y = ARGi(1);
gridPtr = masterPtr->masterDataPtr;
SetGridSize(masterPtr);
endX = MAX(gridPtr->columnEnd, gridPtr->columnMax);
endY = MAX(gridPtr->rowEnd, gridPtr->rowMax);
slotPtr = masterPtr->masterDataPtr->columnPtr;
if (x >= masterPtr->masterDataPtr->startX) {
x -= masterPtr->masterDataPtr->startX;
for (r.x = 0; slotPtr[r.x].offset < x && r.x < endX; r.x++) {
/* null body */
}
}
slotPtr = masterPtr->masterDataPtr->rowPtr;
if (y >= masterPtr->masterDataPtr->startY) {
y -= masterPtr->masterDataPtr->startY;
for (r.y = 0; slotPtr[r.y].offset < y && r.y < endY; r.y++) {
/* null body */
}
}
RETi(r.x);
RETi(r.y);
return true;
}
/*
*----------------------------------------------------------------------
*
* GridPropagateCommand --
*
* Implementation of the [grid propagate] subcommand. See the user
* documentation for details on what it does.
*
* Side effects:
* May alter geometry propagation for a widget.
*
*----------------------------------------------------------------------
*/
static Bool
GridPropagateCommand(Handle self, PList in, PList out)
{
Gridder *masterPtr;
int propagate, old;
if ( in->count > 1 )
return false;
masterPtr = GetGrid(self);
if ( in->count == 0 ) {
RETi( !(masterPtr->flags & DONT_PROPAGATE)) ;
return true;
}
propagate = SvBOOL(ARGsv(1));
/* Only request a relayout if the propagation bit changes */
old = !(masterPtr->flags & DONT_PROPAGATE);
if (propagate != old) {
if (propagate) {
masterPtr->flags &= ~DONT_PROPAGATE;
} else {
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;
}
ArrangeGrid (masterPtr);
}
return true;
}
/*
*----------------------------------------------------------------------
*
* GridRowColumnConfigureCommand --
*
* Implementation of the [grid rowconfigure] and [grid columnconfigure]
* subcommands. See the user documentation for details on what these
* do.
*
* Side effects:
* Depends on arguments; see user documentation.
*
*----------------------------------------------------------------------
*/
static Bool
GridRowColumnConfigureCommand(Handle self, int slotType, PList in, PList out)
{
Gridder *masterPtr;
SlotInfo *slotPtr = NULL;
int slot; /* the column or row number */
//int slotType; /* COLUMN or ROW */
int checkOnly; /* check the size only */
int j, *indexes, n_indexes;
SV *index;
#define METHOD (slotType == COLUMN) ? "column" : "row"
if ((((in->count % 2) == 0) && (in->count > 3)) || (in->count < 1))
return false;
index = ARGsv(0);
if (!SvOK(index) || (SvROK(index) && SvTYPE(SvRV(index)) != SVt_PVAV))
croak("Widget::grid(%sconfigure): index is neither a number nor an array of numbers", METHOD);
if (SvROK(index)) {
if ( !( indexes = (int*) prima_read_array(index, "Widget::gridInfo", 'i', 1, 0, 0, &n_indexes, NULL)))
croak("Widget::grid(%sconfigure): cannot read array of indexes, or the array is empty", METHOD);
} else {
indexes = malloc(sizeof(int));
*indexes = SvIV(index);
n_indexes = 1;
}
masterPtr = GetGrid(self);
checkOnly = ((in->count == 1) || (in->count == 2));
if (checkOnly && n_indexes > 1)
croak("Widget::grid(%sconfigure): row must be a single element", METHOD);
for ( slot = 0; slot < n_indexes; slot++) {
Bool ok = CheckSlotData(masterPtr, slot, slotType, checkOnly);
if (!ok && !checkOnly)
croak("Widget::grid(%sconfigure %d): is out of range", METHOD, slot);
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 (in->count == 1) {
int minsize = 0, pad = 0, weight = 0, uniform = 0;
if (ok) {
minsize = slotPtr[slot].minSize;
pad = slotPtr[slot].pad;
weight = slotPtr[slot].weight;
uniform = slotPtr[slot].uniform;
}
RETs("minsize");
RETi(minsize);
RETs("pad");
RETi(pad);
RETs("uniform");
RETi(uniform);
RETs("weight");
RETi(weight);
return true;
}
/*
* Loop through each option value pair, setting the values as
* required. If only one option is given, with no value, the
* current value is returned.
*/
for ( j = 1; j < in->count; j += 2) {
int i, *p;
char *f = ARGs(j);
if ( strcmp(f, "minsize") == 0) p = &slotPtr[slot].minSize;
else if ( strcmp(f, "weight" ) == 0) p = &slotPtr[slot].weight ;
else if ( strcmp(f, "uniform") == 0) p = &slotPtr[slot].uniform;
else if ( strcmp(f, "pad" ) == 0) p = &slotPtr[slot].pad ;
else croak("Widget::grid(%sconfigure): bad option `%s'", METHOD, f);
if ( in->count == 2 ) {
i = ok ? *p : 0;
RETi(i);
} else {
i = ARGi(j+1);
if ( i < 0 )
croak("Widget::grid(%sconfigure): bad option `%s': should be non-negative", METHOD, f);
*p = i;
}
}
}
/*
* If we changed a property, re-arrange the table,
* and check for constraint shrinkage.
*/
if ( in->count != 2 ) {
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 == 0)) {
last--;
}
masterPtr->masterDataPtr->rowMax = last+1;
} else {
int last = masterPtr->masterDataPtr->columnMax - 1;
while ((last >= 0) && (slotPtr[last].weight == 0)
&& (slotPtr[last].pad == 0)
&& (slotPtr[last].minSize == 0)
&& (slotPtr[last].uniform == 0)) {
last--;
}
masterPtr->masterDataPtr->columnMax = last + 1;
}
}
if (masterPtr->abortPtr != NULL) {
*masterPtr->abortPtr = 1;
}
ArrangeGrid(masterPtr);
#undef METHOD
return true;
}
/*
*----------------------------------------------------------------------
*
* GridSizeCommand --
*
* Implementation of the [grid size] subcommand. See the user
* documentation for details on what it does.
*
*----------------------------------------------------------------------
*/
static Bool
GridSizeCommand(Handle self, PList in, PList out)
{
Gridder *masterPtr;
GridMaster *gridPtr; /* pointer to grid data */
Point r = {0,0};
if ( in->count != 0 )
return false;
masterPtr = GetGrid(self);
if (masterPtr->masterDataPtr != NULL) {
SetGridSize(masterPtr);
gridPtr = masterPtr->masterDataPtr;
r.x = MAX(gridPtr->columnEnd, gridPtr->columnMax);
r.y = MAX(gridPtr->rowEnd, gridPtr->rowEnd);
}
RETi(r.x);
RETi(r.y);
return true;
}
/*
*----------------------------------------------------------------------
*
* GridSlavesCommand --
*
* Implementation of the [grid slaves] subcommand. See the user
* documentation for details on what it does.
*
*----------------------------------------------------------------------
*/
Bool
GridSlavesCommand( Handle self, PList in, PList out)
{
int slot, slotType;
char *cmd;
Gridder *masterPtr;
Gridder *slavePtr;
if (in->count != 2)
return false;
cmd = ARGs(0);
if ( strcmp(cmd, "row") == 0)
slotType = ROW;
else if ( strcmp(cmd, "column") == 0)
slotType = COLUMN;
else
return false;
slot = ARGi(1);
if ( slot < 0 )
croak("Widget::grid(slaves): is an invalid value: should NOT be < 0");
masterPtr = GetGrid(self);
for (slavePtr = masterPtr->slavePtr; slavePtr != NULL; slavePtr = slavePtr->nextPtr) {
if (slotType == COLUMN && (slavePtr->column > slot
|| slavePtr->column+slavePtr->numCols-1 < slot)) {
continue;
}
if (slotType == ROW && (slavePtr->row > slot ||
slavePtr->row+slavePtr->numRows-1 < slot)) {
continue;
}
if (slavePtr->window == NULL_HANDLE)
continue;
RETsv( newSVsv((( PAnyObject) slavePtr->window)-> mate));
}
return true;
}
/*
*--------------------------------------------------------------
*
* AdjustOffsets --
*
* This procedure adjusts the size of the layout to fit in the
* space provided. If it needs more space, the extra is added
* according to the weights. If it needs less, the space is removed
* according to the weights, but at no time does the size drop below
* the minsize specified for that slot.
*
* Results:
* The initial offset of the layout,
* if all the weights are zero, else 0.
*
* Side effects:
* The slot offsets are modified to shrink the layout.
*
*--------------------------------------------------------------
*/
static int
AdjustOffsets(
int size, /* The total layout size (in pixels). */
int slots, /* Number of slots. */
register SlotInfo *slotPtr /* Pointer to slot array. */
) {
register int slot; /* Current slot. */
int diff; /* Extra pixels needed to add to the layout. */
int totalWeight = 0; /* Sum of the weights for all the slots. */
int weight = 0; /* Sum of the weights so far. */
int minSize = 0; /* Minimum possible layout size. */
int newDiff; /* The most pixels that can be added on * the current pass. */
diff = size - slotPtr[slots-1].offset;
/*
* The layout is already the correct size; all done.
*/
if (diff == 0) {
return(0);
}
/*
* If all the weights are zero, center the layout in its parent if
* there is extra space, else clip on the bottom/right.
*/
for (slot=0; slot < slots; slot++) {
totalWeight += slotPtr[slot].weight;
}
if (totalWeight == 0 ) {
return(diff > 0 ? diff/2 : 0);
}
/*
* Add extra space according to the slot weights. This is done
* cumulatively to prevent round-off error accumulation.
*/
if (diff > 0) {
for (weight=slot=0; slot < slots; slot++) {
weight += slotPtr[slot].weight;
slotPtr[slot].offset += diff * weight / totalWeight;
}
return(0);
}
/*
* The layout must shrink below its requested size. Compute the
* minimum possible size by looking at the slot minSizes.
*/
for (slot=0; slot < slots; slot++) {
if (slotPtr[slot].weight > 0) {
minSize += slotPtr[slot].minSize;
} else if (slot > 0) {
minSize += slotPtr[slot].offset - slotPtr[slot-1].offset;
} else {
minSize += slotPtr[slot].offset;
}
}
/*
* If the requested size is less than the minimum required size,
* set the slot sizes to their minimum values, then clip on the
* bottom/right.
*/
if (size <= minSize) {
int offset = 0;
for (slot=0; slot < slots; slot++) {
if (slotPtr[slot].weight > 0) {
offset += slotPtr[slot].minSize;
} else if (slot > 0) {
offset += slotPtr[slot].offset - slotPtr[slot-1].offset;
} else {
offset += slotPtr[slot].offset;
}
slotPtr[slot].offset = offset;
}
return(0);
}
/*
* Remove space from slots according to their weights. The weights
* get renormalized anytime a slot shrinks to its minimum size.
*/
while (diff < 0) {
/*
* Find the total weight for the shrinkable slots.
*/
for (totalWeight=slot=0; slot < slots; slot++) {
int current = (slot == 0) ? slotPtr[slot].offset :
slotPtr[slot].offset - slotPtr[slot-1].offset;
if (current > slotPtr[slot].minSize) {
totalWeight += slotPtr[slot].weight;
slotPtr[slot].temp = slotPtr[slot].weight;
} else {
slotPtr[slot].temp = 0;
}
}
if (totalWeight == 0) {
break;
}
/*
* Find the maximum amount of space we can distribute this pass.
*/
newDiff = diff;
for (slot = 0; slot < slots; slot++) {
int current; /* current size of this slot */
int maxDiff; /* max diff that would cause this slot to equal its minsize */
if (slotPtr[slot].temp == 0) {
continue;
}
current = (slot == 0) ? slotPtr[slot].offset :
slotPtr[slot].offset - slotPtr[slot-1].offset;
maxDiff = totalWeight * (slotPtr[slot].minSize - current)
/ slotPtr[slot].temp;
if (maxDiff > newDiff) {
newDiff = maxDiff;
}
}
/*
* Now distribute the space.
*/
for (weight=slot=0; slot < slots; slot++) {
weight += slotPtr[slot].temp;
slotPtr[slot].offset += newDiff * weight / totalWeight;
}
diff -= newDiff;
}
return(0);
}
/*
*--------------------------------------------------------------
*
* AdjustForSticky --
*
* This procedure adjusts the size of a slave in its cavity based
* on its "sticky" flags.
*
* Results:
* The input x, y, width, and height are changed to represent the
* desired coordinates of the slave.
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
static void
AdjustForSticky(
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;
Point sz;
*xPtr += slavePtr->padLeft;
*widthPtr -= slavePtr->padX;
*yPtr += slavePtr->padBottom;
*heightPtr -= slavePtr->padY;
sz = PWidget(slavePtr->window)->geomSize;
if (*widthPtr > sz.x + slavePtr->iPadX) {
diffx = *widthPtr - (sz.x + slavePtr->iPadX);
*widthPtr = sz.x + slavePtr->iPadX;
}
if (*heightPtr > (sz.y + slavePtr->iPadY)) {
diffy = *heightPtr - (sz.y + slavePtr->iPadY);
*heightPtr = sz.y + 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_SOUTH)) {
*yPtr += (sticky&STICK_NORTH) ? diffy : diffy/2;
}
}
/*
*--------------------------------------------------------------
*
* ArrangeGrid --
*
* This procedure is invoked to re-layout a set of windows managed by
* the grid. Contrary to the Tk version, it is NOT invoked
* at idle time
*
* Side effects:
* The slaves of masterPtr may get resized or moved.
*
*--------------------------------------------------------------
*/
static void
ArrangeGrid(Gridder *masterPtr)
{
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 */
Point sz;
if (masterPtr->lock > 0)
return;
/*
* If the parent has no slaves anymore, then don't do anything
* at all: just leave the parent's size as-is. Otherwise there is
* no way to "relinquish" control over the parent so another geometry
* manager can take over.
*/
if (masterPtr->slavePtr == NULL) {
return;
}
if (masterPtr->masterDataPtr == NULL) {
return;
}
/*
* Abort any nested call to ArrangeGrid for this window, since
* we'll do everything necessary here, and set up so this call
* can be aborted if necessary.
*/
if (masterPtr->abortPtr != NULL) {
*masterPtr->abortPtr = 1;
}
masterPtr->abortPtr = &abort;
abort = 0;
/*
* Call the constraint engine to fill in the row and column offsets.
*/
SetGridSize(masterPtr);
width = ResolveConstraints(masterPtr, COLUMN, 0);
height = ResolveConstraints(masterPtr, ROW, 0);
if (width < PWidget(masterPtr->window)->sizeMin.x) {
width = PWidget(masterPtr->window)->sizeMin.x;
}
if (height < PWidget(masterPtr->window)->sizeMin.y) {
height = PWidget(masterPtr->window)->sizeMin.y;
}
sz = PWidget(masterPtr->window)->geomSize;
if (
(width != sz.x || height != sz.y ) &&
!(masterPtr->flags & DONT_PROPAGATE)
) {
Point p = {width,height};
CWidget(masterPtr->window)->set_geomSize(masterPtr->window,p);
masterPtr->abortPtr = NULL;
return;
}
/*
* If the currently requested layout size doesn't match the parent's
* window size, then adjust the slot offsets according to the
* weights. If all of the weights are zero, center the layout in
* its parent. I haven't decided what to do if the parent is smaller
* than the requested size.
*/
sz = CWidget(masterPtr->window)->get_size(masterPtr->window);
realWidth = sz.x;
realHeight = sz.y;
slotPtr->startX = AdjustOffsets(realWidth,
MAX(slotPtr->columnEnd,slotPtr->columnMax), slotPtr->columnPtr);
slotPtr->startY = AdjustOffsets(realHeight,
MAX(slotPtr->rowEnd,slotPtr->rowMax), slotPtr->rowPtr);
/*
* 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);
if (masterPtr->window == PWidget(slavePtr-> window)-> owner) {
Rect r;
r. left = x;
r. bottom = y;
r. right = x + width;
r. top = y + height;
CWidget(slavePtr-> window)-> set_rect(slavePtr->window, r);
}
}
masterPtr->abortPtr = NULL;
}
/*
*--------------------------------------------------------------
*
* 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;
/*
* 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 + 2];
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) {
if ( !( layoutPtr = (GridLayout *) malloc(sizeof(GridLayout) * (1+gridCount)))) {
warn("Not enough memory");
return 1;
}
} 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 = 0;
layoutPtr[slot].pad = 0;
layoutPtr[slot].binNextPtr = NULL;
}
/*
* Step 2.
* Slaves with a span of 1 are used to determine the minimum size of
* each slot. Slaves whose span is two or more slots don't
* contribute to the minimum size of each slot directly, but can cause
* slots to grow if their size exceeds the the sizes of the slots they
* span.
*
* Bin all slaves whose spans are > 1 by their right edges. This
* allows the computation on minimum and maximum possible layout
* sizes at each slot boundary, without the need to re-sort the slaves.
*/
switch (slotType) {
case COLUMN:
for (slavePtr = masterPtr->slavePtr; slavePtr != NULL; slavePtr = slavePtr->nextPtr) {
int rightEdge = slavePtr->column + slavePtr->numCols - 1;
slavePtr->size = PWidget(slavePtr->window)->geomSize.x + slavePtr->padX + slavePtr->iPadX;
if (slavePtr->numCols > 1) {
slavePtr->binNextPtr = layoutPtr[rightEdge].binNextPtr;
layoutPtr[rightEdge].binNextPtr = slavePtr;
} else {
int size = slavePtr->size + layoutPtr[rightEdge].pad;
if (size > layoutPtr[rightEdge].minSize) {
layoutPtr[rightEdge].minSize = size;
}
}
}
break;
case ROW:
for (slavePtr = masterPtr->slavePtr; slavePtr != NULL; slavePtr = slavePtr->nextPtr) {
int rightEdge = slavePtr->row + slavePtr->numRows - 1;
slavePtr->size = PWidget(slavePtr->window)->geomSize.y + slavePtr->padY + slavePtr->iPadY;
if (slavePtr->numRows > 1) {
slavePtr->binNextPtr = layoutPtr[rightEdge].binNextPtr;
layoutPtr[rightEdge].binNextPtr = slavePtr;
} else {
int size = slavePtr->size + layoutPtr[rightEdge].pad;
if (size > layoutPtr[rightEdge].minSize) {
layoutPtr[rightEdge].minSize = size;
}
}
}
break;
}
/*
* Step 2b.
* Consider demands on uniform sizes.
*/
uniformGroupPtr = uniformPre;
uniformGroupsAlloced = UNIFORM_PREALLOC;
uniformGroups = 0;
for (slot = 0; slot < gridCount; slot++) {
if (layoutPtr[slot].uniform != 0) {
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 newSize = (uniformGroupsAlloced + UNIFORM_PREALLOC)
* sizeof(UniformGroup);
UniformGroup *n = (UniformGroup *) realloc( uniformGroupPtr, newSize);
if (!n) {
warn("Not enough memory");
return 1;
}
uniformGroupPtr = n;
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 != 0) {
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) {
free(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=slot=0; slot < gridCount; slot++) {
layoutPtr[slot].minOffset = layoutPtr[slot].minSize + offset;
for (slavePtr = layoutPtr[slot].binNextPtr; slavePtr != NULL;
slavePtr = slavePtr->binNextPtr) {
int span = (slotType == COLUMN) ? slavePtr->numCols : slavePtr->numRows;
int required = slavePtr->size + layoutPtr[slot - span].minOffset;
if (required > layoutPtr[slot].minOffset) {
layoutPtr[slot].minOffset = required;
}
}
offset = layoutPtr[slot].minOffset;
}
/*
* At this point, we know the minimum required size of the entire layout.
* It might be prudent to stop here if our "master" will resize itself
* to this size.
*/
requiredSize = offset;
if (maxOffset > offset) {
offset=maxOffset;
}
/*
* Step 4.
* Determine the minimum slot offsets going from right to left,
* bounding the pixel range of each slot boundary.
* Pre-fill all of the right offsets with the actual size of the table;
* they will be reduced as required.
*/
for (slot=0; slot < gridCount; slot++) {
layoutPtr[slot].maxOffset = offset;
}
for (slot=gridCount-1; slot > 0;) {
for (slavePtr = layoutPtr[slot].binNextPtr; slavePtr != NULL;
slavePtr = slavePtr->binNextPtr) {
int span = (slotType == COLUMN) ? slavePtr->numCols : slavePtr->numRows;
int require = offset - slavePtr->size;
int startSlot = slot - span;
if (startSlot >=0 && require < layoutPtr[startSlot].maxOffset) {
layoutPtr[startSlot].maxOffset = require;
}
}
offset -= layoutPtr[slot].minSize;
slot--;
if (layoutPtr[slot].maxOffset < offset) {
offset = layoutPtr[slot].maxOffset;
} else {
layoutPtr[slot].maxOffset = offset;
}
}
/*
* Step 5.
* At this point, each slot boundary has a range of values that
* will satisfy the overall layout size.
* Make repeated passes over the layout structure looking for
* spans of slot boundaries where the minOffsets are less than
* the maxOffsets, and adjust the offsets according to the slot
* weights. At each pass, at least one slot boundary will have
* its range of possible values fixed at a single value.
*/
for (start=0; start < gridCount;) {
int totalWeight = 0; /* Sum of the weights for all of the
* slots in this span. */
int need = 0; /* The minimum space needed to layout
* this span. */
int have; /* The actual amount of space that will
* be taken up by this span. */
int weight; /* Cumulative weights of the columns in
* this span. */
int noWeights = 0; /* True if the span has no weights. */
/*
* Find a span by identifying ranges of slots whose edges are
* already constrained at fixed offsets, but whose internal
* slot boundaries have a range of possible positions.
*/
if (layoutPtr[start].minOffset == layoutPtr[start].maxOffset) {
start++;
continue;
}
for (end=start+1; end<gridCount; end++) {
if (layoutPtr[end].minOffset == layoutPtr[end].maxOffset) {
break;
}
}
/*
* We found a span. Compute the total weight, minumum space required,
* for this span, and the actual amount of space the span should
* use.
*/
for (slot=start; slot<=end; slot++) {
totalWeight += layoutPtr[slot].weight;
need += layoutPtr[slot].minSize;
}
have = layoutPtr[end].maxOffset - layoutPtr[start-1].minOffset;
/*
* If all the weights in the span are zero, then distribute the
* extra space evenly.
*/
if (totalWeight == 0) {
noWeights++;
totalWeight = end - start + 1;
}
/*
* It might not be possible to give the span all of the space
* available on this pass without violating the size constraints
* of one or more of the internal slot boundaries.
* Determine the maximum amount of space that when added to the
* entire span, would cause a slot boundary to have its possible
* range reduced to one value, and reduce the amount of extra
* space allocated on this pass accordingly.
*
* The calculation is done cumulatively to avoid accumulating
* roundoff errors.
*/
for (weight=0,slot=start; slot<end; slot++) {
int diff = layoutPtr[slot].maxOffset - layoutPtr[slot].minOffset;
weight += noWeights ? 1 : layoutPtr[slot].weight;
if ((noWeights || layoutPtr[slot].weight>0) &&
(diff*totalWeight/weight) < (have-need)) {
have = diff * totalWeight / weight + need;
}
}
/*
* Now distribute the extra space among the slots by
* adjusting the minSizes and minOffsets.
*/
for (weight=0,slot=start; slot<end; slot++) {
weight += noWeights ? 1 : layoutPtr[slot].weight;
layoutPtr[slot].minOffset +=
(int)((double) (have-need) * weight/totalWeight + 0.5);
layoutPtr[slot].minSize = layoutPtr[slot].minOffset
- layoutPtr[slot-1].minOffset;
}
layoutPtr[slot].minSize = layoutPtr[slot].minOffset
- layoutPtr[slot-1].minOffset;
/*
* Having pushed the top/left boundaries of the slots to
* take up extra space, the bottom/right space is recalculated
* to propagate the new space allocation.
*/
for (slot=end; slot > start; slot--) {
layoutPtr[slot-1].maxOffset =
layoutPtr[slot].maxOffset-layoutPtr[slot].minSize;
}
}
/*
* Step 6.
* All of the space has been apportioned; copy the
* layout information back into the master.
*/
for (slot=0; slot < gridCount; slot++) {
slotPtr[slot].offset = layoutPtr[slot].minOffset;
}
--layoutPtr;
if (layoutPtr != layoutData) {
free(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 window.
*
* Side effects:
* A new grid structure may be created.
*
*--------------------------------------------------------------
*/
static Gridder *
GetGrid(
Handle self /* Token for window for which grid structure is desired. */
) {
register Gridder *gridPtr;
if ( var-> gridder )
return (Gridder*) var->gridder;
/*
* See if there's already grid for this window. If not,
* then create a new one.
*/
if ( !( gridPtr = (Gridder *) malloc(sizeof(Gridder))))
croak("Not enough memory");
gridPtr->window = self;
gridPtr->masterPtr = NULL;
gridPtr->masterDataPtr = NULL;
gridPtr->nextPtr = NULL;
gridPtr->slavePtr = NULL;
gridPtr->binNextPtr = NULL;
gridPtr->column = gridPtr->row = -1;
gridPtr->numCols = 1;
gridPtr->numRows = 1;
gridPtr->padX = gridPtr->padY = 0;
gridPtr->padLeft = gridPtr->padBottom = 0;
gridPtr->iPadX = gridPtr->iPadY = 0;
gridPtr->abortPtr = NULL;
gridPtr->flags = 0;
gridPtr->sticky = 0;
gridPtr->size = 0;
gridPtr->saved_in = NULL_HANDLE;
gridPtr->lock = 0;
var->gridder = 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);
}
/*
*--------------------------------------------------------------
*
* 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 Bool
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) {
warn("grid row or column out of range");
return false;
}
if ((checkOnly == CHECK_ONLY) && (masterPtr->masterDataPtr == NULL)) {
return false;
}
/*
* 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;
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 *old = (slotType == ROW) ?
masterPtr->masterDataPtr->rowPtr :
masterPtr->masterDataPtr->columnPtr;
SlotInfo *newx = (SlotInfo *) realloc(old, newSize);
if ( !newx ) {
warn("not enough memory");
return false;
}
bzero(newx + numSlot, newSize - oldSize );
if (slotType == ROW) {
masterPtr->masterDataPtr->rowPtr = newx;
masterPtr->masterDataPtr->rowSpace = newNumSlot ;
} else {
masterPtr->masterDataPtr->columnPtr = newx;
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 true;
}
/*
*--------------------------------------------------------------
*
* 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)
{
size_t size;
if (masterPtr->masterDataPtr == NULL) {
GridMaster *gridPtr = masterPtr->masterDataPtr =
(GridMaster *) malloc(sizeof(GridMaster));
size = sizeof(SlotInfo) * TYPICAL_SIZE;
gridPtr->columnEnd = 0;
gridPtr->columnMax = 0;
gridPtr->columnPtr = (SlotInfo *) malloc(size);
gridPtr->columnSpace = TYPICAL_SIZE;
gridPtr->rowEnd = 0;
gridPtr->rowMax = 0;
gridPtr->rowPtr = (SlotInfo *) malloc(size);
gridPtr->rowSpace = TYPICAL_SIZE;
gridPtr->startX = 0;
gridPtr->startY = 0;
bzero(gridPtr->columnPtr, size);
bzero(gridPtr->rowPtr, size);
}
}
/*
*----------------------------------------------------------------------
*
* Unlink --
*
* Remove a grid from its parent's list of slaves.
*
*----------------------------------------------------------------------
*/
static void
Unlink( 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) {
croak("Widget.gridUnlink: couldn't find previous window");
}
if (slavePtr2->nextPtr == slavePtr) {
slavePtr2->nextPtr = slavePtr->nextPtr;
break;
}
}
}
if (masterPtr->abortPtr != NULL) {
*masterPtr->abortPtr = 1;
}
SetGridSize(slavePtr->masterPtr);
slavePtr->masterPtr = NULL;
}
/*
*----------------------------------------------------------------------
*
* DestroyGrid --
*
* This procedure is invoked
* 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.
*
* Side effects:
* Everything associated with the grid is freed up.
*
*----------------------------------------------------------------------
*/
static void
DestroyGrid(Handle self)
{
Gridder *gridPtr = (Gridder*) var-> gridder, *gridPtr2, *nextPtr;
if ( !gridPtr )
return;
if (gridPtr->masterPtr != NULL) {
Unlink(gridPtr);
}
for (gridPtr2 = gridPtr->slavePtr; gridPtr2 != NULL; gridPtr2 = nextPtr) {
gridPtr2->masterPtr = NULL;
nextPtr = gridPtr2->nextPtr;
gridPtr2->nextPtr = NULL;
}
if (gridPtr->masterDataPtr != NULL) {
if (gridPtr->masterDataPtr->rowPtr != NULL) {
free(gridPtr->masterDataPtr -> rowPtr);
}
if (gridPtr->masterDataPtr->columnPtr != NULL) {
free(gridPtr->masterDataPtr -> columnPtr);
}
free(gridPtr->masterDataPtr);
}
free(gridPtr);
var-> gridder = NULL;
}
/*
*----------------------------------------------------------------------
*
* 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 a window or an arrayref of windows followed by
* a profile hash
*
* 1.Contrary to the Tk syntax, window names such as 'xe' and 'xy' will be treated as true window names,
* not REL_SKIP commands. Only string 'x' will be.
*
* 2. s/columnspan/colspan/, hello html
*
* Side effects:
* Slave windows get taken over by the grid.
*
*----------------------------------------------------------------------
*/
#define METHOD "Widget::grid_configure_slaves"
static void
parse_pad_amount(SV *src, int *i1, int *i2)
{
if (SvOK(src) && SvROK(src) && SvTYPE(SvRV(src)) == SVt_PVAV) {
int *p = prima_read_array(src, METHOD, 'i', 1, 2, 2, NULL, NULL);
*i1 = p[0];
*i2 = p[0] + p[1];
free(p);
} else {
*i1 = SvIV(src);
*i2 = *i1 * 2;
}
}
static Gridder*
parse_info( Gridder *slavePtr, HV *profile)
{
dPROFILE;
Gridder *masterPtr = NULL;
if ( pexist(column)) {
int c = pget_i(column);
if ( c < 0 ) croak("%s: column value must be a non-negative integer", METHOD);
slavePtr->column = c;
}
if ( pexist(colspan)) {
int c = pget_i(colspan);
if ( c < 0 ) croak("%s: colspan value must be a non-negative integer", METHOD);
slavePtr->numCols = c;
}
if ( pexist(in)) {
Handle in, h;
if ( ( h = in = pget_H(in)) != NULL_HANDLE ) {
if (in == slavePtr->window)
croak("%s: widget can't be managed in itself", METHOD);
while ( h) {
h = PWidget(h)-> owner;
if (in == slavePtr->window)
croak("%s: widget can't be managed by its child", METHOD);
}
masterPtr = GetGrid(in);
InitMasterData(masterPtr);
}
slavePtr->saved_in = in;
}
if (pexist(ipadx)) slavePtr->iPadX = 2 * pget_i(ipadx);
if (pexist(ipady)) slavePtr->iPadY = 2 * pget_i(ipady);
if (pexist(padx)) parse_pad_amount( pget_sv(padx), &slavePtr->padLeft, &slavePtr->padX);
if (pexist(pady)) parse_pad_amount( pget_sv(pady), &slavePtr->padBottom, &slavePtr->padY);
if ( pexist(row)) {
int c = pget_i(row);
if ( c < 0 ) croak("%s: row value must be a non-negative integer", METHOD);
slavePtr->row = c;
}
if ( pexist(rowspan)) {
int c = pget_i(rowspan);
if ( c < 0 ) croak("%s: rowspan value must be a non-negative integer", METHOD);
slavePtr->numRows = c;
}
if (pexist(sticky)) {
char *s = pget_c(sticky);
int sticky = StringToSticky(s);
if (sticky == -1)
croak("%s: bad stickyness value \"%s\": must be a string containing n, e, s, and/or w",
METHOD, s);
slavePtr->sticky = sticky;
}
return masterPtr;
}
static void
ConfigureSlaves(Handle self, SV* window, HV *profile)
{
SV **slaves;
int n_slaves;
Gridder *masterPtr;
Gridder *slavePtr;
Handle slave;
int i;
int width;
int defaultColumn = 0; /* default column number */
int defaultColumnSpan = 1; /* default number of columns */
Handle lastWindow; /* use this window to base current row/col on */
int numSkip; /* number of 'x' found */
STRLEN len;
char *firstStr, *prevStr;
if (window && !(
SvOK(window) && (
!SvROK(window) || (
(SvTYPE(SvRV(window)) == SVt_PVAV) ||
(SvTYPE(SvRV(window)) == SVt_PVHV)
)
)
))
croak("%s: window is neither an object nor an array of objects", METHOD);
if (window && SvROK(window) && (SvTYPE(SvRV(window)) == SVt_PVAV)) {
if ( !( slaves = (SV**) prima_read_array(window, METHOD, 'H', 1, 0, 0, &n_slaves, NULL)))
croak("%s: cannot read array of windows, or the array is empty", METHOD);
} else {
slaves = malloc(sizeof(SV*));
n_slaves = 1;
*slaves = window;
}
firstStr = "";
for (i = 0; i < n_slaves; i++) {
if ( slaves[i] && SvROK( slaves[i]))
continue;
prevStr = firstStr;
firstStr = slaves[i] ? SvPV( slaves[i], len) : "";
if (*firstStr == REL_HORIZ) {
if (
i==0 ||
(prevStr[0] == REL_SKIP && prevStr[1] == 0) ||
(strcmp(prevStr, "^") == 0)
)
croak("%s: Must specify window before shortcut '-'.", METHOD);
continue;
}
}
/*
* Iterate over all of the slave windows and short-cuts, parsing
* options for each slave. It's a bit wasteful to re-parse the
* options for each slave, but things get too messy if we try to
* parse the arguments just once at the beginning. For example,
* if a slave already is managed we want to just change a few
* existing values without resetting everything. If there are
* multiple windows, the -in option only gets processed for the
* first window.
*/
masterPtr = NULL;
for (i = 0; i < n_slaves; i++) {
if ( slaves[i] && !SvROK(slaves[i])) {
firstStr = SvPV( slaves[i], len);
/*
* '^' and 'x' cause us to skip a column. '-' is processed
* as part of its preceeding slave.
*/
if (len == 1 && (*firstStr == REL_VERT || *firstStr == REL_SKIP)) {
defaultColumn++;
continue;
}
if (*firstStr == REL_HORIZ) {
continue;
}
for (defaultColumnSpan = 1; i + defaultColumnSpan < n_slaves; defaultColumnSpan++) {
STRLEN l = 1;
char *string = SvROK( slaves[i+defaultColumnSpan] ) ? "" : SvPV(slaves[i + defaultColumnSpan], l);
if ( l != 1 || *string != REL_HORIZ) {
break;
}
}
}
if (( slave = get_slave(self, slaves[i])) == NULL_HANDLE )
croak("%s: cannot locate child widget %s", METHOD, firstStr);
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_HANDLE 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.
*/
masterPtr = parse_info( slavePtr, profile );
if ( slavePtr->saved_in || slave != self )
Widget_check_in(slave, self, gtGrid);
/*
* Make sure we have a geometry master. We look at:
* 1) the -in flag
* 2) the geometry master of the first slave (if specified)
* 3) the parent of the first slave.
*/
if (masterPtr == NULL) {
masterPtr = slavePtr->masterPtr;
}
if (masterPtr == NULL) {
masterPtr = GetGrid(PWidget(slave)->owner);
InitMasterData(masterPtr);
}
if (slavePtr->masterPtr != NULL && slavePtr->masterPtr != masterPtr) {
Unlink(slavePtr);
slavePtr->masterPtr = NULL;
}
if (slavePtr->masterPtr == NULL) {
Gridder *tempPtr = masterPtr->slavePtr;
slavePtr->masterPtr = masterPtr;
masterPtr->slavePtr = slavePtr;
slavePtr->nextPtr = tempPtr;
}
/*
* Try to make sure our master isn't managed by us.
*/
if (masterPtr->masterPtr == slavePtr) {
Unlink(slavePtr);
croak("%s: can't put that slave here", METHOD);
}
/*
* Assign default position information.
*/
if (slavePtr->column == -1) {
slavePtr->column = defaultColumn;
}
slavePtr->numCols += defaultColumnSpan - 1;
if (slavePtr->row == -1) {
if (masterPtr->masterDataPtr == NULL) {
slavePtr->row = 0;
} else {
slavePtr->row = masterPtr->masterDataPtr->rowEnd;
}
}
defaultColumn += slavePtr->numCols;
defaultColumnSpan = 1;
/*
* Arrange for the parent to be re-arranged at the first
* idle moment.
*/
if (masterPtr->abortPtr != NULL) {
*masterPtr->abortPtr = 1;
}
if ( PWidget(slave)->geometry == gtGrid) {
SetGridSize(masterPtr);
ArrangeGrid(masterPtr);
} else {
CWidget(slave)->set_geometry(slave, gtGrid);
}
}
/* Now look for all the "^"'s. */
lastWindow = -1;
numSkip = 0;
for (i = 0; i < n_slaves; i++) {
struct Gridder *otherPtr;
int match; /* found a match for the ^ */
int lastRow, lastColumn; /* implied end of table */
len = 0;
firstStr = (!slaves[i] || SvROK(slaves[i])) ? "" :
SvPV( slaves[i], len);
if ( len == 1 && *firstStr == REL_SKIP ) {
numSkip++;
} else if ( len == 1 && *firstStr == REL_VERT) {
/* do nothing */
} else {
numSkip = 0;
lastWindow = get_slave(self, slaves[i]);
continue;
}
if (masterPtr == NULL) {
croak("%s: can't use '^', can't find master", METHOD);
}
/* Count the number of consecutive ^'s starting from this position */
for (width = 1; width + i < n_slaves; width++) {
len = 0;
firstStr = SvROK(slaves[i+width]) ? "" : SvPV( slaves[i+width], len);
if (len != 1 || *firstStr != REL_VERT )
break;
}
/*
* Find the implied grid location of the ^
*/
if (lastWindow < 0) {
if (masterPtr->masterDataPtr != NULL) {
SetGridSize(masterPtr);
lastRow = masterPtr->masterDataPtr->rowEnd - 2;
} else {
lastRow = 0;
}
lastColumn = 0;
} else {
otherPtr = GetGrid(lastWindow);
lastRow = otherPtr->row + otherPtr->numRows - 2;
lastColumn = otherPtr->column + otherPtr->numCols;
}
lastColumn += numSkip;
for (match=0, slavePtr = masterPtr->slavePtr; slavePtr != NULL;
slavePtr = slavePtr->nextPtr) {
if (slavePtr->column == lastColumn
&& slavePtr->row + slavePtr->numRows - 1 == lastRow) {
if (slavePtr->numCols <= width) {
slavePtr->numRows++;
match++;
i += slavePtr->numCols - 1;
lastWindow = slavePtr->window;
numSkip = 0;
break;
}
}
}
if (!match) {
croak("%s: can't find slave to extend with \"^\".", METHOD);
}
}
if (masterPtr == NULL) {
croak("%s: can't determine master window", METHOD);
}
SetGridSize(masterPtr);
}
#undef METHOD
/*
*----------------------------------------------------------------------
*
* StickyToString
*
* Converts the internal boolean combination of "sticky" bits onto
* a TCL list element containing zero or mor of n, s, e, or w.
*
* Results:
* A string is placed into the "result" pointer.
*
* Side effects:
* none.
*
*----------------------------------------------------------------------
*/
static void
StickyToString(
int flags, /* the sticky flags */
char *result /* where to put the result */
) {
int count = 0;
if (flags&STICK_NORTH) {
result[count++] = 'n';
}
if (flags&STICK_EAST) {
result[count++] = 'e';
}
if (flags&STICK_SOUTH) {
result[count++] = 's';
}
if (flags&STICK_WEST) {
result[count++] = 'w';
}
if (count) {
result[count] = '\0';
} else {
sprintf(result,"{}");
}
}
/*
*----------------------------------------------------------------------
*
* StringToSticky --
*
* Converts an ascii string representing a widgets stickyness
* into the boolean result.
*
* Results:
* The boolean combination of the "sticky" bits is retuned. If an
* error occurs, such as an invalid character, -1 is returned instead.
*
* Side effects:
* none
*
*----------------------------------------------------------------------
*/
static int
StringToSticky( 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;
}
/* ------- end of Tk ---------- */
void
Widget_grid_slaves( Handle self)
{
if ( !var-> gridder )
return;
ArrangeGrid((Gridder*) var->gridder);
}
/* removes widget from list of grid slaves */
void
Widget_grid_leave( Handle self)
{
Gridder *g = (Gridder*) var-> gridder;
if ( !g)
return;
Unlink(g);
g->masterPtr = NULL;
}
/* become a slave to masterPtr */
static void
link_slave( Gridder *slavePtr, Gridder* masterPtr)
{
slavePtr->masterPtr = masterPtr;
InitMasterData(slavePtr->masterPtr);
slavePtr->nextPtr = masterPtr->slavePtr;
masterPtr->slavePtr = slavePtr;
if (masterPtr->abortPtr != NULL) {
*masterPtr->abortPtr = 1;
}
SetGridSize(slavePtr->masterPtr);
}
/* applies grid parameters and enters grid slaves chain */
void
Widget_grid_enter( Handle self)
{
Gridder *slavePtr = (Gridder*) var-> gridder;
if (slavePtr->masterPtr != NULL) {
Unlink(slavePtr);
slavePtr->masterPtr = NULL;
}
if (
slavePtr->saved_in &&
!hash_fetch( prima_guts.objects, &slavePtr->saved_in, sizeof(Handle))
)
slavePtr->saved_in = NULL_HANDLE;
link_slave(slavePtr, GetGrid(slavePtr->saved_in ? slavePtr->saved_in : var->owner));
}
void
Widget_grid_destroy( Handle self)
{
DestroyGrid(self);
}
static Bool
grid_lock( Handle self, PList in, PList out)
{
Gridder *gridPtr = (Gridder*) var-> gridder;
if ( in-> count == 0 ) {
RETi( gridPtr ? gridPtr->lock : 0);
} else if ( in-> count == 1 ) {
int d = ARGi(0);
if ( d > 0 )
gridPtr->lock++;
else if (d < 0 && gridPtr->lock > 0 ) {
gridPtr->lock--;
if ( gridPtr->lock == 0 )
ArrangeGrid(gridPtr);
}
} else
return false;
return true;
}
static HV*
opt2hv( PList in, int start )
{
HV *profile = prima_hash_create();
for ( ; start < in->count; start += 2 ) {
STRLEN l;
char *key = SvPV((SV*) in->items[start], l);
SV *val = (SV*) in->items[start+1];
(void) hv_store( profile, key, (I32) l, val, 0);
}
return profile;
}
XS( Widget_grid_action_FROMPERL)
{
dXSARGS;
Handle self;
char *selector = "";
int i, ok = false;
List in, out;
list_create(&in, items, 1);
list_create(&out, 32, 32);
if (items < 2)
goto FAIL;
if ( !( self = gimme_the_mate( ST( 0))))
goto FAIL;
selector = SvPV_nolen(ST(1));
for ( i = 2; i < items; i++)
list_add( &in, (Handle)(ST(i)));
SPAGAIN;
SP -= items;
if ( strcmp(selector, "bbox") == 0) {
ok = GridBboxCommand(self, &in, &out);
} else if (
(strcmp(selector, "colconfigure") == 0) ||
(strcmp(selector, "rowconfigure") == 0)
) {
ok = GridRowColumnConfigureCommand(self, (*selector == 'c') ? COLUMN : ROW, &in, &out);
} else if ( strcmp(selector, "configure") == 0) {
if (( ok = ( in.count > 0 ))) {
HV * profile = opt2hv(&in, in.count % 2);
ConfigureSlaves(self,
(in.count % 2) ? (SV*)in.items[0] : NULL,
profile
);
hash_destroy(profile, false);
}
} else if (
(strcmp(selector, "forget") == 0) ||
(strcmp(selector, "remove") == 0)
) {
ok = GridForgetRemoveCommand(self, *selector == 'f', &in, &out);
} else if ( strcmp(selector, "info") == 0) {
ok = GridInfoCommand(self, &in, &out);
} else if ( strcmp(selector, "location") == 0) {
ok = GridLocationCommand(self, &in, &out);
} else if ( strcmp(selector, "propagate") == 0) {
ok = GridPropagateCommand(self, &in, &out);
} else if ( strcmp(selector, "size") == 0) {
ok = GridSizeCommand(self, &in, &out);
} else if ( strcmp(selector, "slaves") == 0) {
ok = GridSlavesCommand(self, &in, &out);
} else if ( strcmp(selector, "lock") == 0) {
ok = grid_lock(self, &in, &out);
} else {
croak("Widget.grid_action: not such subcommand");
}
if ( out.count > 0 ) {
EXTEND(sp, out.count);
for ( i = 0; i < out.count; i++)
PUSHs(sv_2mortal((SV*) out.items[i]));
}
list_destroy(&in);
list_destroy(&out);
PUTBACK;
FAIL:
if ( !ok ) croak ("Invalid usage of Widget.grid_action(%s)", selector);
return;
}
void Widget_grid_action ( Handle self) { warn("Invalid call of Widget::grid_action"); }
void Widget_grid_action_REDEFINED( Handle self) { warn("Invalid call of Widget::grid_action"); }
Bool
Widget_is_grid_slave( Handle self, Handle slave)
{
Gridder *slavePtr = (Gridder*) var-> gridder;
if ( !slavePtr ) return false;
for (slavePtr = slavePtr->slavePtr; slavePtr != NULL; slavePtr = slavePtr->nextPtr) {
if ( slavePtr-> window == slave )
return true;
}
return false;
}
Bool
Widget_has_grid_slaves(Handle self)
{
Gridder *slavePtr = (Gridder*) var-> gridder;
if ( !slavePtr ) return false;
return slavePtr->slavePtr != NULL;
}
#ifdef __cplusplus
}
#endif