Converted uiRadioButtons on OS X to use a NSView of NSButtons instead of NSMatrix; the latter was deprecated on 10.8 and has lots of little quirks that made it annoying to use.

This commit is contained in:
Pietro Gagliardi 2016-05-21 02:00:08 -04:00
parent 46a280cf46
commit 2c0e333ca0
2 changed files with 102 additions and 39 deletions

View File

@ -110,6 +110,7 @@ uiCheckbox *uiNewCheckbox(const char *text)
uiDarwinNewControl(uiCheckbox, c);
// TODO bezel style? transparent?
c->button = [[NSButton alloc] initWithFrame:NSZeroRect];
[c->button setTitle:toNSString(text)];
[c->button setButtonType:NSSwitchButton];

View File

@ -1,68 +1,130 @@
// 14 august 2015
#import "uipriv_darwin.h"
// TODO the selection should NOT be lost when starting a new drag
// In the old days you would use a NSMatrix for this; as of OS X 10.8 this was deprecated and now you need just a bunch of NSButtons with the same superview AND same action method.
// This is documented on the NSMatrix page, but the rest of the OS X documentation says to still use NSMatrix.
// NSMatrix has weird quirks anyway...
// TODO verify this works on 10.7.
// TODO
// - check that multiple radio buttons on the same parent container work right
// - 6 units of spacing between buttons, as suggested by Interface Builder?
struct uiRadioButtons {
uiDarwinControl c;
NSMatrix *matrix;
NSView *view;
NSMutableArray *buttons;
NSMutableArray *constraints;
NSLayoutConstraint *lastv;
};
uiDarwinControlAllDefaults(uiRadioButtons, matrix)
uiDarwinControlAllDefaultsExceptDestroy(uiRadioButtons, view)
static NSButtonCell *cellAt(uiRadioButtons *r, uintmax_t n)
static void uiRadioButtonsDestroy(uiControl *c)
{
return (NSButtonCell *) [r->matrix cellAtRow:n column:0];
uiRadioButtons *r = uiRadioButtons(c);
NSButton *b;
// drop the constraints
[r->view removeConstraints:r->constraints];
[r->constraints release];
if (r->lastv != nil)
[r->lastv release];
// destroy the buttons
for (b in r->buttons)
[b removeFromSuperview];
[r->buttons release];
// and destroy ourselves
[r->view release];
uiFreeControl(uiControl(r));
}
static NSButton *buttonAt(uiRadioButtons *r, uintmax_t n)
{
return (NSButton *) [r->buttons objectAtIndex:n];
}
void uiRadioButtonsAppend(uiRadioButtons *r, const char *text)
{
intmax_t prevSelection;
NSButton *b, *b2;
NSLayoutConstraint *constraint;
// renewRows:columns: will reset the selection
prevSelection = [r->matrix selectedRow];
// TODO bezel style? transparent?
b = [[NSButton alloc] initWithFrame:NSZeroRect];
[b setTitle:toNSString(text)];
[b setButtonType:NSRadioButton];
[b setBordered:NO];
uiDarwinSetControlFont(b, NSRegularControlSize);
[b setTranslatesAutoresizingMaskIntoConstraints:NO];
[r->matrix renewRows:([r->matrix numberOfRows] + 1) columns:1];
[cellAt(r, [r->matrix numberOfRows] - 1) setTitle:toNSString(text)];
// TODO set target
[b setAction:@selector(onClicked:)];
// this will definitely cause a resize in at least the vertical direction, even if not in the horizontal
// DO NOT CALL sizeToCells! this will glitch out; see http://stackoverflow.com/questions/32162562/dynamically-adding-cells-to-a-nsmatrix-laid-out-with-auto-layout-has-weird-effec
[r->buttons addObject:b];
[r->view addSubview:b];
// and renew the previous selection
// we need to turn on allowing empty selection for this to work properly on the initial state
// TODO this doesn't actually work
[r->matrix setAllowsEmptySelection:YES];
[r->matrix selectCellAtRow:prevSelection column:0];
[r->matrix setAllowsEmptySelection:NO];
// pin horizontally to the edges of the superview
constraint = mkConstraint(b, NSLayoutAttributeLeading,
NSLayoutRelationEqual,
r->view, NSLayoutAttributeLeading,
1, 0,
@"uiRadioButtons button leading constraint");
[r->view addConstraint:constraint];
[r->constraints addObject:constraint];
constraint = mkConstraint(b, NSLayoutAttributeTrailing,
NSLayoutRelationEqual,
r->view, NSLayoutAttributeTrailing,
1, 0,
@"uiRadioButtons button trailing constraint");
[r->view addConstraint:constraint];
[r->constraints addObject:constraint];
// if this is the first view, pin it to the top
// otherwise pin to the bottom of the last
if ([r->buttons count] == 1)
constraint = mkConstraint(b, NSLayoutAttributeTop,
NSLayoutRelationEqual,
r->view, NSLayoutAttributeTop,
1, 0,
@"uiRadioButtons first button top constraint");
else {
b2 = buttonAt(r, [r->buttons count] - 2);
constraint = mkConstraint(b, NSLayoutAttributeTop,
NSLayoutRelationEqual,
b2, NSLayoutAttributeBottom,
1, 0,
@"uiRadioButtons non-first button top constraint");
}
[r->view addConstraint:constraint];
[r->constraints addObject:constraint];
// if there is a previous bottom constraint, remove it
if (r->lastv != nil) {
[r->view removeConstraint:r->lastv];
[r->constraints removeObject:r->lastv];
[r->lastv release];
}
// and make the new bottom constraint
r->lastv = mkConstraint(b, NSLayoutAttributeBottom,
NSLayoutRelationEqual,
r->view, NSLayoutAttributeBottom,
1, 0,
@"uiRadioButtons last button bottom constraint");
[r->view addConstraint:r->lastv];
[r->constraints addObject:r->lastv];
[r->lastv retain];
}
uiRadioButtons *uiNewRadioButtons(void)
{
uiRadioButtons *r;
NSButtonCell *cell;
uiDarwinNewControl(uiRadioButtons, r);
// we have to set up the NSMatrix this way (prototype first)
// otherwise we won't be able to change its properties (such as the button type)
cell = [NSButtonCell new];
[cell setButtonType:NSRadioButton];
// works on NSCells too (same selector)
uiDarwinSetControlFont((NSControl *) cell, NSRegularControlSize);
r->matrix = [[NSMatrix alloc] initWithFrame:NSZeroRect
mode:NSRadioModeMatrix
prototype:cell
numberOfRows:0
numberOfColumns:0];
// we manually twiddle this property to allow programmatic non-selection state
[r->matrix setAllowsEmptySelection:NO];
[r->matrix setSelectionByRect:YES];
[r->matrix setIntercellSpacing:NSMakeSize(4, 2)];
[r->matrix setAutorecalculatesCellSize:YES];
[r->matrix setDrawsBackground:NO];
[r->matrix setDrawsCellBackground:NO];
[r->matrix setAutosizesCells:YES];
r->view = [[NSView alloc] initWithFrame:NSZeroRect];
r->buttons = [NSMutableArray new];
r->constraints = [NSMutableArray new];
return r;
}