2015-08-14 20:46:35 -05:00
|
|
|
// 14 august 2015
|
2015-10-09 17:49:41 -05:00
|
|
|
#import "uipriv_darwin.h"
|
2015-08-14 20:46:35 -05:00
|
|
|
|
2015-10-09 13:39:26 -05:00
|
|
|
// NSComboBoxes have no intrinsic width; we'll use the default Interface Builder width for them.
|
|
|
|
// NSPopUpButton is fine.
|
|
|
|
#define comboboxWidth 96
|
|
|
|
|
|
|
|
@interface libui_intrinsicWidthNSComboBox : NSComboBox
|
|
|
|
@end
|
|
|
|
|
|
|
|
@implementation libui_intrinsicWidthNSComboBox
|
|
|
|
|
|
|
|
- (NSSize)intrinsicContentSize
|
|
|
|
{
|
|
|
|
NSSize s;
|
|
|
|
|
|
|
|
s = [super intrinsicContentSize];
|
|
|
|
s.width = comboboxWidth;
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
2015-08-14 20:46:35 -05:00
|
|
|
struct uiCombobox {
|
|
|
|
uiDarwinControl c;
|
|
|
|
BOOL editable;
|
|
|
|
NSPopUpButton *pb;
|
2015-08-22 11:48:48 -05:00
|
|
|
NSArrayController *pbac;
|
2015-08-14 20:46:35 -05:00
|
|
|
NSComboBox *cb;
|
2015-08-16 22:44:23 -05:00
|
|
|
NSObject *handle; // for uiControlHandle()
|
2015-10-08 15:32:12 -05:00
|
|
|
void (*onSelected)(uiCombobox *, void *);
|
|
|
|
void *onSelectedData;
|
2015-08-14 20:46:35 -05:00
|
|
|
};
|
|
|
|
|
2015-10-08 15:32:12 -05:00
|
|
|
@interface comboboxDelegateClass : NSObject<NSComboBoxDelegate> {
|
2016-01-07 13:41:20 -06:00
|
|
|
struct mapTable *comboboxes;
|
2015-10-08 15:32:12 -05:00
|
|
|
}
|
|
|
|
- (void)comboBoxSelectionDidChange:(NSNotification *)note;
|
|
|
|
- (IBAction)onSelected:(id)sender;
|
|
|
|
- (void)registerCombobox:(uiCombobox *)c;
|
|
|
|
- (void)unregisterCombobox:(uiCombobox *)c;
|
|
|
|
@end
|
|
|
|
|
|
|
|
@implementation comboboxDelegateClass
|
|
|
|
|
|
|
|
- (id)init
|
|
|
|
{
|
|
|
|
self = [super init];
|
|
|
|
if (self)
|
|
|
|
self->comboboxes = newMap();
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)dealloc
|
|
|
|
{
|
2016-01-07 13:41:20 -06:00
|
|
|
mapDestroy(self->comboboxes);
|
2015-10-08 15:32:12 -05:00
|
|
|
[super dealloc];
|
|
|
|
}
|
|
|
|
|
|
|
|
// note: does not trigger when text changed
|
|
|
|
// TODO not perfect either:
|
|
|
|
// - triggered when keyboard navigating the open menu
|
|
|
|
// - does not trigger when the text is changed to a menu item (which normally selects that item; IDK how to inhibit that behavior - TODO)
|
|
|
|
- (void)comboBoxSelectionDidChange:(NSNotification *)note
|
|
|
|
{
|
|
|
|
[self onSelected:[note object]];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (IBAction)onSelected:(id)sender
|
|
|
|
{
|
|
|
|
uiCombobox *c;
|
|
|
|
|
|
|
|
c = (uiCombobox *) mapGet(self->comboboxes, sender);
|
|
|
|
(*(c->onSelected))(c, c->onSelectedData);
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)registerCombobox:(uiCombobox *)c
|
|
|
|
{
|
|
|
|
mapSet(self->comboboxes, c->handle, c);
|
|
|
|
if (c->editable)
|
|
|
|
[c->cb setDelegate:self];
|
|
|
|
else {
|
|
|
|
[c->pb setTarget:self];
|
|
|
|
[c->pb setAction:@selector(onSelected:)];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)unregisterCombobox:(uiCombobox *)c
|
|
|
|
{
|
|
|
|
if (c->editable)
|
|
|
|
[c->cb setDelegate:nil];
|
|
|
|
else
|
|
|
|
[c->pb setTarget:nil];
|
2016-01-07 13:41:20 -06:00
|
|
|
mapDelete(self->comboboxes, c->handle);
|
2015-10-08 15:32:12 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
static comboboxDelegateClass *comboboxDelegate = nil;
|
|
|
|
|
2015-08-22 11:48:48 -05:00
|
|
|
static void onDestroy(uiCombobox *);
|
|
|
|
|
|
|
|
uiDarwinDefineControlWithOnDestroy(
|
2015-08-14 20:46:35 -05:00
|
|
|
uiCombobox, // type name
|
|
|
|
uiComboboxType, // type function
|
2015-08-22 11:48:48 -05:00
|
|
|
handle, // handle
|
|
|
|
onDestroy(this); // on destroy
|
2015-08-14 20:46:35 -05:00
|
|
|
)
|
|
|
|
|
2015-08-22 11:48:48 -05:00
|
|
|
static void onDestroy(uiCombobox *c)
|
|
|
|
{
|
2015-10-08 15:32:12 -05:00
|
|
|
[comboboxDelegate unregisterCombobox:c];
|
2015-08-22 11:48:48 -05:00
|
|
|
if (!c->editable) {
|
|
|
|
[c->pb unbind:@"contentObjects"];
|
|
|
|
[c->pb unbind:@"selectedIndex"];
|
|
|
|
[c->pbac release];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-08-14 20:46:35 -05:00
|
|
|
void uiComboboxAppend(uiCombobox *c, const char *text)
|
|
|
|
{
|
2015-08-22 11:16:27 -05:00
|
|
|
if (c->editable)
|
|
|
|
[c->cb addItemWithObjectValue:toNSString(text)];
|
|
|
|
else
|
2015-08-22 11:48:48 -05:00
|
|
|
[c->pbac addObject:toNSString(text)];
|
2015-08-14 20:46:35 -05:00
|
|
|
}
|
|
|
|
|
2015-10-08 15:32:12 -05:00
|
|
|
intmax_t uiComboboxSelected(uiCombobox *c)
|
|
|
|
{
|
|
|
|
if (c->editable)
|
|
|
|
return [c->cb indexOfSelectedItem];
|
|
|
|
return [c->pb indexOfSelectedItem];
|
|
|
|
}
|
|
|
|
|
2015-10-09 13:39:26 -05:00
|
|
|
void uiComboboxSetSelected(uiCombobox *c, intmax_t n)
|
|
|
|
{
|
|
|
|
if (c->editable) {
|
|
|
|
// see https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ComboBox/Tasks/SettingComboBoxValue.html#//apple_ref/doc/uid/20000256
|
|
|
|
id delegate;
|
|
|
|
|
|
|
|
// this triggers the delegate; turn it off for now
|
|
|
|
delegate = [c->cb delegate];
|
|
|
|
[c->cb setDelegate:nil];
|
|
|
|
|
|
|
|
// this seems to work fine for -1 too
|
|
|
|
[c->cb selectItemAtIndex:n];
|
|
|
|
if (n == -1)
|
|
|
|
[c->cb setObjectValue:@""];
|
|
|
|
else
|
|
|
|
[c->cb setObjectValue:[c->cb objectValueOfSelectedItem]];
|
|
|
|
|
|
|
|
[c->cb setDelegate:delegate];
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
[c->pb selectItemAtIndex:n];
|
|
|
|
}
|
|
|
|
|
2015-10-08 15:32:12 -05:00
|
|
|
void uiComboboxOnSelected(uiCombobox *c, void (*f)(uiCombobox *c, void *data), void *data)
|
|
|
|
{
|
|
|
|
c->onSelected = f;
|
|
|
|
c->onSelectedData = data;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void defaultOnSelected(uiCombobox *c, void *data)
|
|
|
|
{
|
|
|
|
// do nothing
|
|
|
|
}
|
|
|
|
|
2015-08-14 20:46:35 -05:00
|
|
|
static uiCombobox *finishNewCombobox(BOOL editable)
|
|
|
|
{
|
|
|
|
uiCombobox *c;
|
|
|
|
|
|
|
|
c = (uiCombobox *) uiNewControl(uiComboboxType());
|
|
|
|
|
|
|
|
c->editable = editable;
|
|
|
|
if (c->editable) {
|
2015-10-09 13:39:26 -05:00
|
|
|
c->cb = [[libui_intrinsicWidthNSComboBox alloc] initWithFrame:NSZeroRect];
|
2015-08-14 20:46:35 -05:00
|
|
|
[c->cb setUsesDataSource:NO];
|
|
|
|
[c->cb setButtonBordered:YES];
|
|
|
|
[c->cb setCompletes:NO];
|
2015-08-14 21:50:20 -05:00
|
|
|
uiDarwinSetControlFont(c->cb, NSRegularControlSize);
|
2015-08-16 22:44:23 -05:00
|
|
|
c->handle = c->cb;
|
2015-08-14 20:46:35 -05:00
|
|
|
} else {
|
2015-08-22 11:16:27 -05:00
|
|
|
NSPopUpButtonCell *pbcell;
|
|
|
|
|
2015-08-14 20:46:35 -05:00
|
|
|
c->pb = [[NSPopUpButton alloc] initWithFrame:NSZeroRect pullsDown:NO];
|
2015-08-22 11:16:27 -05:00
|
|
|
[c->pb setPreferredEdge:NSMinYEdge];
|
|
|
|
pbcell = (NSPopUpButtonCell *) [c->pb cell];
|
|
|
|
[pbcell setArrowPosition:NSPopUpArrowAtBottom];
|
2015-08-14 20:46:35 -05:00
|
|
|
// TODO font
|
2015-08-16 22:44:23 -05:00
|
|
|
c->handle = c->pb;
|
2015-08-22 11:48:48 -05:00
|
|
|
|
|
|
|
// NSPopUpButton doesn't work like a combobox
|
|
|
|
// - it automatically selects the first item
|
|
|
|
// - it doesn't support duplicates
|
|
|
|
// but we can use a NSArrayController and Cocoa bindings to bypass these restrictions
|
|
|
|
c->pbac = [NSArrayController new];
|
|
|
|
[c->pbac setAvoidsEmptySelection:NO];
|
|
|
|
[c->pbac setSelectsInsertedObjects:NO];
|
|
|
|
[c->pbac setAutomaticallyRearrangesObjects:NO];
|
|
|
|
[c->pb bind:@"contentValues"
|
|
|
|
toObject:c->pbac
|
|
|
|
withKeyPath:@"arrangedObjects"
|
|
|
|
options:nil];
|
|
|
|
[c->pb bind:@"selectedIndex"
|
|
|
|
toObject:c->pbac
|
|
|
|
withKeyPath:@"selectionIndex"
|
|
|
|
options:nil];
|
2015-08-14 20:46:35 -05:00
|
|
|
}
|
|
|
|
|
2015-10-08 15:32:12 -05:00
|
|
|
if (comboboxDelegate == nil) {
|
|
|
|
comboboxDelegate = [comboboxDelegateClass new];
|
|
|
|
[delegates addObject:comboboxDelegate];
|
|
|
|
}
|
|
|
|
[comboboxDelegate registerCombobox:c];
|
|
|
|
uiComboboxOnSelected(c, defaultOnSelected, NULL);
|
|
|
|
|
2015-08-14 20:46:35 -05:00
|
|
|
uiDarwinFinishNewControl(c, uiCombobox);
|
|
|
|
|
|
|
|
return c;
|
|
|
|
}
|
|
|
|
|
|
|
|
uiCombobox *uiNewCombobox(void)
|
|
|
|
{
|
|
|
|
return finishNewCombobox(NO);
|
|
|
|
}
|
|
|
|
|
|
|
|
uiCombobox *uiNewEditableCombobox(void)
|
|
|
|
{
|
|
|
|
return finishNewCombobox(YES);
|
|
|
|
}
|