#import "uipriv_darwin.h"
@interface gridChild : NSView
@property uiControl *c;
@property int left;
@property int top;
@property int xspan;
@property int yspan;
@property int hexpand;
@property uiAlign halign;
@property int vexpand;
@property uiAlign valign;
@property (strong) NSLayoutConstraint *leadingc;
@property (strong) NSLayoutConstraint *topc;
@property (strong) NSLayoutConstraint *trailingc;
@property (strong) NSLayoutConstraint *bottomc;
@property (strong) NSLayoutConstraint *xcenterc;
@property (strong) NSLayoutConstraint *ycenterc;
@property NSLayoutPriority oldHorzHuggingPri;
@property NSLayoutPriority oldVertHuggingPri;
- (void)setC:(uiControl *)c grid:(uiGrid *)g;
- (void)onDestroy;
- (NSView *)view;
@end
@interface gridView : NSView {
uiGrid *g;
NSMutableArray *children;
int padded;
NSMutableArray *edges;
NSMutableArray *inBetweens;
NSMutableArray *emptyCellViews;
}
- (id)initWithG:(uiGrid *)gg;
- (void)onDestroy;
- (void)removeOurConstraints;
- (void)syncEnableStates:(int)enabled;
- (CGFloat)paddingAmount;
- (void)establishOurConstraints;
- (void)append:(gridChild *)gc;
- (void)insert:(gridChild *)gc after:(uiControl *)c at:(uiAt)at;
- (int)isPadded;
- (void)setPadded:(int)p;
- (BOOL)hugsTrailing;
- (BOOL)hugsBottom;
- (int)nhexpand;
- (int)nvexpand;
@end
struct uiGrid {
uiDarwinControl c;
gridView *view;
};
@implementation gridChild
- (void)setC:(uiControl *)c grid:(uiGrid *)g
{
self.c = c;
self.oldHorzHuggingPri = uiDarwinControlHuggingPriority(uiDarwinControl(self.c), NSLayoutConstraintOrientationHorizontal);
self.oldVertHuggingPri = uiDarwinControlHuggingPriority(uiDarwinControl(self.c), NSLayoutConstraintOrientationVertical);
uiControlSetParent(self.c, uiControl(g));
uiDarwinControlSetSuperview(uiDarwinControl(self.c), self);
uiDarwinControlSyncEnableState(uiDarwinControl(self.c), uiControlEnabledToUser(uiControl(g)));
if (self.halign == uiAlignStart || self.halign == uiAlignFill) {
self.leadingc = uiprivMkConstraint(self, NSLayoutAttributeLeading,
NSLayoutRelationEqual,
[self view], NSLayoutAttributeLeading,
1, 0,
@"uiGrid child horizontal alignment start constraint");
[self addConstraint:self.leadingc];
}
if (self.halign == uiAlignCenter) {
self.xcenterc = uiprivMkConstraint(self, NSLayoutAttributeCenterX,
NSLayoutRelationEqual,
[self view], NSLayoutAttributeCenterX,
1, 0,
@"uiGrid child horizontal alignment center constraint");
[self addConstraint:self.xcenterc];
}
if (self.halign == uiAlignEnd || self.halign == uiAlignFill) {
self.trailingc = uiprivMkConstraint(self, NSLayoutAttributeTrailing,
NSLayoutRelationEqual,
[self view], NSLayoutAttributeTrailing,
1, 0,
@"uiGrid child horizontal alignment end constraint");
[self addConstraint:self.trailingc];
}
if (self.valign == uiAlignStart || self.valign == uiAlignFill) {
self.topc = uiprivMkConstraint(self, NSLayoutAttributeTop,
NSLayoutRelationEqual,
[self view], NSLayoutAttributeTop,
1, 0,
@"uiGrid child vertical alignment start constraint");
[self addConstraint:self.topc];
}
if (self.valign == uiAlignCenter) {
self.ycenterc = uiprivMkConstraint(self, NSLayoutAttributeCenterY,
NSLayoutRelationEqual,
[self view], NSLayoutAttributeCenterY,
1, 0,
@"uiGrid child vertical alignment center constraint");
[self addConstraint:self.ycenterc];
}
if (self.valign == uiAlignEnd || self.valign == uiAlignFill) {
self.bottomc = uiprivMkConstraint(self, NSLayoutAttributeBottom,
NSLayoutRelationEqual,
[self view], NSLayoutAttributeBottom,
1, 0,
@"uiGrid child vertical alignment end constraint");
[self addConstraint:self.bottomc];
}
}
- (void)onDestroy
{
if (self.leadingc != nil) {
[self removeConstraint:self.leadingc];
self.leadingc = nil;
}
if (self.topc != nil) {
[self removeConstraint:self.topc];
self.topc = nil;
}
if (self.trailingc != nil) {
[self removeConstraint:self.trailingc];
self.trailingc = nil;
}
if (self.bottomc != nil) {
[self removeConstraint:self.bottomc];
self.bottomc = nil;
}
if (self.xcenterc != nil) {
[self removeConstraint:self.xcenterc];
self.xcenterc = nil;
}
if (self.ycenterc != nil) {
[self removeConstraint:self.ycenterc];
self.ycenterc = nil;
}
uiControlSetParent(self.c, NULL);
uiDarwinControlSetSuperview(uiDarwinControl(self.c), nil);
uiDarwinControlSetHuggingPriority(uiDarwinControl(self.c), self.oldHorzHuggingPri, NSLayoutConstraintOrientationHorizontal);
uiDarwinControlSetHuggingPriority(uiDarwinControl(self.c), self.oldVertHuggingPri, NSLayoutConstraintOrientationVertical);
}
- (NSView *)view
{
return (NSView *) uiControlHandle(self.c);
}
@end
@implementation gridView
- (id)initWithG:(uiGrid *)gg
{
self = [super initWithFrame:NSZeroRect];
if (self != nil) {
self->g = gg;
self->padded = 0;
self->children = [NSMutableArray new];
self->edges = [NSMutableArray new];
self->inBetweens = [NSMutableArray new];
self->emptyCellViews = [NSMutableArray new];
}
return self;
}
- (void)onDestroy
{
gridChild *gc;
[self removeOurConstraints];
[self->edges release];
[self->inBetweens release];
[self->emptyCellViews release];
for (gc in self->children) {
[gc onDestroy];
uiControlDestroy(gc.c);
[gc removeFromSuperview];
}
[self->children release];
}
- (void)removeOurConstraints
{
NSView *v;
if ([self->edges count] != 0) {
[self removeConstraints:self->edges];
[self->edges removeAllObjects];
}
if ([self->inBetweens count] != 0) {
[self removeConstraints:self->inBetweens];
[self->inBetweens removeAllObjects];
}
for (v in self->emptyCellViews)
[v removeFromSuperview];
[self->emptyCellViews removeAllObjects];
}
- (void)syncEnableStates:(int)enabled
{
gridChild *gc;
for (gc in self->children)
uiDarwinControlSyncEnableState(uiDarwinControl(gc.c), enabled);
}
- (CGFloat)paddingAmount
{
if (!self->padded)
return 0.0;
return uiDarwinPaddingAmount(NULL);
}
- (void)establishOurConstraints
{
gridChild *gc;
CGFloat padding;
int xmin, ymin;
int xmax, ymax;
int xcount, ycount;
BOOL first;
int **gg;
NSView ***gv;
BOOL **gspan;
int x, y;
NSUInteger i;
NSLayoutConstraint *c;
int firstx, firsty;
BOOL *hexpand, *vexpand;
BOOL doit;
BOOL onlyEmptyAndSpanning;
[self removeOurConstraints];
if ([self->children count] == 0)
return;
padding = [self paddingAmount];
first = YES;
for (gc in self->children) {
if (!uiControlVisible(gc.c))
continue;
if (first) {
xmin = gc.left;
ymin = gc.top;
xmax = gc.left + gc.xspan;
ymax = gc.top + gc.yspan;
first = NO;
continue;
}
if (xmin > gc.left)
xmin = gc.left;
if (ymin > gc.top)
ymin = gc.top;
if (xmax < (gc.left + gc.xspan))
xmax = gc.left + gc.xspan;
if (ymax < (gc.top + gc.yspan))
ymax = gc.top + gc.yspan;
}
if (first != NO) return;
xcount = xmax - xmin;
ycount = ymax - ymin;
gg = (int **) uiprivAlloc(ycount * sizeof (int *), "int[][]");
gspan = (BOOL **) uiprivAlloc(ycount * sizeof (BOOL *), "BOOL[][]");
for (y = 0; y < ycount; y++) {
gg[y] = (int *) uiprivAlloc(xcount * sizeof (int), "int[]");
gspan[y] = (BOOL *) uiprivAlloc(xcount * sizeof (BOOL), "BOOL[]");
for (x = 0; x < xcount; x++)
gg[y][x] = -1; }
for (i = 0; i < [self->children count]; i++) {
gc = (gridChild *) [self->children objectAtIndex:i];
if (!uiControlVisible(gc.c))
continue;
for (y = gc.top; y < gc.top + gc.yspan; y++)
for (x = gc.left; x < gc.left + gc.xspan; x++) {
gg[y - ymin][x - xmin] = i;
if (x != gc.left || y != gc.top)
gspan[y - ymin][x - xmin] = YES;
}
}
for (y = 0; y < ycount; y++) {
onlyEmptyAndSpanning = YES;
for (x = 0; x < xcount; x++)
if (gg[y][x] != -1) {
gc = (gridChild *) [self->children objectAtIndex:gg[y][x]];
if (gc.yspan == 1 || gc.top - ymin == y) {
onlyEmptyAndSpanning = NO;
break;
}
}
if (onlyEmptyAndSpanning)
for (x = 0; x < xcount; x++) {
gg[y][x] = gg[y - 1][x];
gspan[y][x] = YES;
}
}
for (x = 0; x < xcount; x++) {
onlyEmptyAndSpanning = YES;
for (y = 0; y < ycount; y++)
if (gg[y][x] != -1) {
gc = (gridChild *) [self->children objectAtIndex:gg[y][x]];
if (gc.xspan == 1 || gc.left - xmin == x) {
onlyEmptyAndSpanning = NO;
break;
}
}
if (onlyEmptyAndSpanning)
for (y = 0; y < ycount; y++) {
gg[y][x] = gg[y][x - 1];
gspan[y][x] = YES;
}
}
gv = (NSView ***) uiprivAlloc(ycount * sizeof (NSView **), "NSView *[][]");
for (y = 0; y < ycount; y++) {
gv[y] = (NSView **) uiprivAlloc(xcount * sizeof (NSView *), "NSView *[]");
for (x = 0; x < xcount; x++)
if (gg[y][x] == -1) {
gv[y][x] = [[NSView alloc] initWithFrame:NSZeroRect];
[gv[y][x] setTranslatesAutoresizingMaskIntoConstraints:NO];
[self addSubview:gv[y][x]];
[self->emptyCellViews addObject:gv[y][x]];
} else {
gc = (gridChild *) [self->children objectAtIndex:gg[y][x]];
gv[y][x] = gc;
}
}
hexpand = (BOOL *) uiprivAlloc(xcount * sizeof (BOOL), "BOOL[]");
vexpand = (BOOL *) uiprivAlloc(ycount * sizeof (BOOL), "BOOL[]");
for (gc in self->children) {
if (!uiControlVisible(gc.c))
continue;
if (gc.hexpand && gc.xspan == 1)
hexpand[gc.left - xmin] = YES;
if (gc.vexpand && gc.yspan == 1)
vexpand[gc.top - ymin] = YES;
}
for (gc in self->children) {
if (!uiControlVisible(gc.c))
continue;
if (gc.hexpand && gc.xspan != 1) {
doit = YES;
for (x = gc.left; x < gc.left + gc.xspan; x++)
if (hexpand[x - xmin]) {
doit = NO;
break;
}
if (doit)
for (x = gc.left; x < gc.left + gc.xspan; x++)
hexpand[x - xmin] = YES;
}
if (gc.vexpand && gc.yspan != 1) {
doit = YES;
for (y = gc.top; y < gc.top + gc.yspan; y++)
if (vexpand[y - ymin]) {
doit = NO;
break;
}
if (doit)
for (y = gc.top; y < gc.top + gc.yspan; y++)
vexpand[y - ymin] = YES;
}
}
for (y = 0; y < ycount; y++) {
c = uiprivMkConstraint(self, NSLayoutAttributeLeading,
NSLayoutRelationEqual,
gv[y][0], NSLayoutAttributeLeading,
1, 0,
@"uiGrid leading edge constraint");
[self addConstraint:c];
[self->edges addObject:c];
c = uiprivMkConstraint(self, NSLayoutAttributeTrailing,
NSLayoutRelationEqual,
gv[y][xcount - 1], NSLayoutAttributeTrailing,
1, 0,
@"uiGrid trailing edge constraint");
[self addConstraint:c];
[self->edges addObject:c];
}
for (x = 0; x < xcount; x++) {
c = uiprivMkConstraint(self, NSLayoutAttributeTop,
NSLayoutRelationEqual,
gv[0][x], NSLayoutAttributeTop,
1, 0,
@"uiGrid top edge constraint");
[self addConstraint:c];
[self->edges addObject:c];
c = uiprivMkConstraint(self, NSLayoutAttributeBottom,
NSLayoutRelationEqual,
gv[ycount - 1][x], NSLayoutAttributeBottom,
1, 0,
@"uiGrid bottom edge constraint");
[self addConstraint:c];
[self->edges addObject:c];
}
for (x = 0; x < xcount; x++) {
for (y = 0; y < ycount; y++)
if (!gspan[y][x])
break;
firsty = y;
for (y++; y < ycount; y++) {
if (gspan[y][x])
continue;
c = uiprivMkConstraint(gv[firsty][x], NSLayoutAttributeLeading,
NSLayoutRelationEqual,
gv[y][x], NSLayoutAttributeLeading,
1, 0,
@"uiGrid column leading constraint");
[self addConstraint:c];
[self->edges addObject:c];
}
}
for (y = 0; y < ycount; y++) {
for (x = 0; x < xcount; x++)
if (!gspan[y][x])
break;
firstx = x;
for (x++; x < xcount; x++) {
if (gspan[y][x])
continue;
c = uiprivMkConstraint(gv[y][firstx], NSLayoutAttributeTop,
NSLayoutRelationEqual,
gv[y][x], NSLayoutAttributeTop,
1, 0,
@"uiGrid row top constraint");
[self addConstraint:c];
[self->edges addObject:c];
}
}
for (y = 0; y < ycount; y++)
for (x = 1; x < xcount; x++)
if (gv[y][x - 1] != gv[y][x]) {
c = uiprivMkConstraint(gv[y][x - 1], NSLayoutAttributeTrailing,
NSLayoutRelationEqual,
gv[y][x], NSLayoutAttributeLeading,
1, -padding,
@"uiGrid internal horizontal constraint");
[self addConstraint:c];
[self->inBetweens addObject:c];
}
for (x = 0; x < xcount; x++)
for (y = 1; y < ycount; y++)
if (gv[y - 1][x] != gv[y][x]) {
c = uiprivMkConstraint(gv[y - 1][x], NSLayoutAttributeBottom,
NSLayoutRelationEqual,
gv[y][x], NSLayoutAttributeTop,
1, -padding,
@"uiGrid internal vertical constraint");
[self addConstraint:c];
[self->inBetweens addObject:c];
}
for (gc in self->children) {
NSLayoutPriority priority;
if (!uiControlVisible(gc.c))
continue;
if (hexpand[gc.left - xmin] || gc.xspan != 1)
priority = NSLayoutPriorityDefaultLow;
else
priority = NSLayoutPriorityDefaultHigh;
uiDarwinControlSetHuggingPriority(uiDarwinControl(gc.c), priority, NSLayoutConstraintOrientationHorizontal);
if (vexpand[gc.top - ymin] || gc.yspan != 1)
priority = NSLayoutPriorityDefaultLow;
else
priority = NSLayoutPriorityDefaultHigh;
uiDarwinControlSetHuggingPriority(uiDarwinControl(gc.c), priority, NSLayoutConstraintOrientationVertical);
}
uiprivFree(hexpand);
uiprivFree(vexpand);
for (y = 0; y < ycount; y++) {
uiprivFree(gg[y]);
uiprivFree(gv[y]);
uiprivFree(gspan[y]);
}
uiprivFree(gg);
uiprivFree(gv);
uiprivFree(gspan);
}
- (void)append:(gridChild *)gc
{
BOOL update;
int oldnh, oldnv;
[gc setTranslatesAutoresizingMaskIntoConstraints:NO];
[self addSubview:gc];
oldnh = [self nhexpand];
oldnv = [self nvexpand];
[self->children addObject:gc];
[self establishOurConstraints];
update = NO;
if (gc.hexpand)
if (oldnh == 0)
update = YES;
if (gc.vexpand)
if (oldnv == 0)
update = YES;
if (update)
uiDarwinNotifyEdgeHuggingChanged(uiDarwinControl(self->g));
[gc release]; }
- (void)insert:(gridChild *)gc after:(uiControl *)c at:(uiAt)at
{
gridChild *other;
BOOL found;
found = NO;
for (other in self->children)
if (other.c == c) {
found = YES;
break;
}
if (!found)
uiprivUserBug("Existing control %p is not in grid %p; you cannot add other controls next to it", c, self->g);
switch (at) {
case uiAtLeading:
gc.left = other.left - gc.xspan;
gc.top = other.top;
break;
case uiAtTop:
gc.left = other.left;
gc.top = other.top - gc.yspan;
break;
case uiAtTrailing:
gc.left = other.left + other.xspan;
gc.top = other.top;
break;
case uiAtBottom:
gc.left = other.left;
gc.top = other.top + other.yspan;
break;
}
[self append:gc];
}
- (int)isPadded
{
return self->padded;
}
- (void)setPadded:(int)p
{
CGFloat padding;
NSLayoutConstraint *c;
#if 0#endif
self->padded = p;
padding = [self paddingAmount];
for (c in self->inBetweens)
switch ([c firstAttribute]) {
case NSLayoutAttributeLeading:
case NSLayoutAttributeTop:
[c setConstant:padding];
break;
case NSLayoutAttributeTrailing:
case NSLayoutAttributeBottom:
[c setConstant:-padding];
break;
}
}
- (BOOL)hugsTrailing
{
return [self nhexpand] != 0;
}
- (BOOL)hugsBottom
{
return [self nvexpand] != 0;
}
- (int)nhexpand
{
gridChild *gc;
int n;
n = 0;
for (gc in self->children) {
if (!uiControlVisible(gc.c))
continue;
if (gc.hexpand)
n++;
}
return n;
}
- (int)nvexpand
{
gridChild *gc;
int n;
n = 0;
for (gc in self->children) {
if (!uiControlVisible(gc.c))
continue;
if (gc.vexpand)
n++;
}
return n;
}
@end
static void uiGridDestroy(uiControl *c)
{
uiGrid *g = uiGrid(c);
[g->view onDestroy];
[g->view release];
uiFreeControl(uiControl(g));
}
uiDarwinControlDefaultHandle(uiGrid, view)
uiDarwinControlDefaultParent(uiGrid, view)
uiDarwinControlDefaultSetParent(uiGrid, view)
uiDarwinControlDefaultToplevel(uiGrid, view)
uiDarwinControlDefaultVisible(uiGrid, view)
uiDarwinControlDefaultShow(uiGrid, view)
uiDarwinControlDefaultHide(uiGrid, view)
uiDarwinControlDefaultEnabled(uiGrid, view)
uiDarwinControlDefaultEnable(uiGrid, view)
uiDarwinControlDefaultDisable(uiGrid, view)
static void uiGridSyncEnableState(uiDarwinControl *c, int enabled)
{
uiGrid *g = uiGrid(c);
if (uiDarwinShouldStopSyncEnableState(uiDarwinControl(g), enabled))
return;
[g->view syncEnableStates:enabled];
}
uiDarwinControlDefaultSetSuperview(uiGrid, view)
static BOOL uiGridHugsTrailingEdge(uiDarwinControl *c)
{
uiGrid *g = uiGrid(c);
return [g->view hugsTrailing];
}
static BOOL uiGridHugsBottom(uiDarwinControl *c)
{
uiGrid *g = uiGrid(c);
return [g->view hugsBottom];
}
static void uiGridChildEdgeHuggingChanged(uiDarwinControl *c)
{
uiGrid *g = uiGrid(c);
[g->view establishOurConstraints];
}
uiDarwinControlDefaultHuggingPriority(uiGrid, view)
uiDarwinControlDefaultSetHuggingPriority(uiGrid, view)
static void uiGridChildVisibilityChanged(uiDarwinControl *c)
{
uiGrid *g = uiGrid(c);
[g->view establishOurConstraints];
}
static gridChild *toChild(uiControl *c, int xspan, int yspan, int hexpand, uiAlign halign, int vexpand, uiAlign valign, uiGrid *g)
{
gridChild *gc;
if (xspan < 0)
uiprivUserBug("You cannot have a negative xspan in a uiGrid cell.");
if (yspan < 0)
uiprivUserBug("You cannot have a negative yspan in a uiGrid cell.");
gc = [gridChild new];
gc.xspan = xspan;
gc.yspan = yspan;
gc.hexpand = hexpand;
gc.halign = halign;
gc.vexpand = vexpand;
gc.valign = valign;
[gc setC:c grid:g];
return gc;
}
void uiGridAppend(uiGrid *g, uiControl *c, int left, int top, int xspan, int yspan, int hexpand, uiAlign halign, int vexpand, uiAlign valign)
{
gridChild *gc;
if (c == NULL)
uiprivUserBug("You cannot add NULL to a uiGrid.");
gc = toChild(c, xspan, yspan, hexpand, halign, vexpand, valign, g);
gc.left = left;
gc.top = top;
[g->view append:gc];
}
void uiGridInsertAt(uiGrid *g, uiControl *c, uiControl *existing, uiAt at, int xspan, int yspan, int hexpand, uiAlign halign, int vexpand, uiAlign valign)
{
gridChild *gc;
gc = toChild(c, xspan, yspan, hexpand, halign, vexpand, valign, g);
[g->view insert:gc after:existing at:at];
}
int uiGridPadded(uiGrid *g)
{
return [g->view isPadded];
}
void uiGridSetPadded(uiGrid *g, int padded)
{
[g->view setPadded:padded];
}
uiGrid *uiNewGrid(void)
{
uiGrid *g;
uiDarwinNewControl(uiGrid, g);
g->view = [[gridView alloc] initWithG:g];
return g;
}