libui/darwin/editablecombo.m

187 lines
5.0 KiB
Objective-C

// 14 august 2015
#import "uipriv_darwin.h"
// So why did I split uiCombobox into uiCombobox and uiEditableCombobox? Here's (90% of the; the other 10% is GTK+ events) answer:
// When you type a value into a NSComboBox that just happens to be in the list, it will autoselect that item!
// I can't seem to find a workaround.
// Fortunately, there's other weird behaviors that made this split worth it.
// And besides, selected items make little sense with editable comboboxes... you either separate or combine them with the text entry :V
// NSComboBoxes have no intrinsic width; we'll use the default Interface Builder width for them.
#define comboboxWidth 96
@interface libui_intrinsicWidthNSComboBox : NSComboBox
@end
@implementation libui_intrinsicWidthNSComboBox
- (NSSize)intrinsicContentSize
{
NSSize s;
s = [super intrinsicContentSize];
s.width = comboboxWidth;
return s;
}
@end
struct uiEditableCombobox {
uiDarwinControl c;
NSComboBox *cb;
void (*onChanged)(uiEditableCombobox *, void *);
void *onChangedData;
};
@interface editableComboboxDelegateClass : NSObject<NSComboBoxDelegate> {
uiprivMap *comboboxes;
}
- (void)controlTextDidChange:(NSNotification *)note;
- (void)comboBoxSelectionDidChange:(NSNotification *)note;
- (void)registerCombobox:(uiEditableCombobox *)c;
- (void)unregisterCombobox:(uiEditableCombobox *)c;
@end
@implementation editableComboboxDelegateClass
- (id)init
{
self = [super init];
if (self)
self->comboboxes = uiprivNewMap();
return self;
}
- (void)dealloc
{
uiprivMapDestroy(self->comboboxes);
[super dealloc];
}
- (void)controlTextDidChange:(NSNotification *)note
{
uiEditableCombobox *c;
// TODO normalize the cast styles in these calls
c = uiEditableCombobox(uiprivMapGet(self->comboboxes, [note object]));
(*(c->onChanged))(c, c->onChangedData);
}
// the above doesn't handle when an item is selected; this will
- (void)comboBoxSelectionDidChange:(NSNotification *)note
{
// except this is sent BEFORE the entry is changed, and that doesn't send the above, so
// this is via http://stackoverflow.com/a/21059819/3408572 - it avoids the need to manage selected items
// this still isn't perfect — I get residual changes to the same value while navigating the list — but it's good enough
[self performSelector:@selector(controlTextDidChange:)
withObject:note
afterDelay:0];
}
- (void)registerCombobox:(uiEditableCombobox *)c
{
uiprivMapSet(self->comboboxes, c->cb, c);
[c->cb setDelegate:self];
}
- (void)unregisterCombobox:(uiEditableCombobox *)c
{
[c->cb setDelegate:nil];
uiprivMapDelete(self->comboboxes, c->cb);
}
@end
static editableComboboxDelegateClass *comboboxDelegate = nil;
uiDarwinControlAllDefaultsExceptDestroy(uiEditableCombobox, cb)
static void uiEditableComboboxDestroy(uiControl *cc)
{
uiEditableCombobox *c = uiEditableCombobox(cc);
[comboboxDelegate unregisterCombobox:c];
[c->cb release];
uiFreeControl(uiControl(c));
}
void uiEditableComboboxAppend(uiEditableCombobox *c, const char *text)
{
[c->cb addItemWithObjectValue:uiprivToNSString(text)];
}
char *uiEditableComboboxText(uiEditableCombobox *c)
{
return uiDarwinNSStringToText([c->cb stringValue]);
}
void uiEditableComboboxSetText(uiEditableCombobox *c, const char *text)
{
NSString *t;
t = uiprivToNSString(text);
[c->cb setStringValue:t];
// yes, let's imitate the behavior that caused uiEditableCombobox to be separate in the first place!
// just to avoid confusion when users see an option in the list in the text field but not selected in the list
[c->cb selectItemWithObjectValue:t];
}
#if 0
// LONGTERM
void uiEditableComboboxSetSelected(uiEditableCombobox *c, int 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];
}
#endif
void uiEditableComboboxOnChanged(uiEditableCombobox *c, void (*f)(uiEditableCombobox *c, void *data), void *data)
{
c->onChanged = f;
c->onChangedData = data;
}
static void defaultOnChanged(uiEditableCombobox *c, void *data)
{
// do nothing
}
uiEditableCombobox *uiNewEditableCombobox(void)
{
uiEditableCombobox *c;
uiDarwinNewControl(uiEditableCombobox, c);
c->cb = [[libui_intrinsicWidthNSComboBox alloc] initWithFrame:NSZeroRect];
[c->cb setUsesDataSource:NO];
[c->cb setButtonBordered:YES];
[c->cb setCompletes:NO];
uiDarwinSetControlFont(c->cb, NSRegularControlSize);
if (comboboxDelegate == nil) {
comboboxDelegate = [[editableComboboxDelegateClass new] autorelease];
[uiprivDelegates addObject:comboboxDelegate];
}
[comboboxDelegate registerCombobox:c];
uiEditableComboboxOnChanged(c, defaultOnChanged, NULL);
return c;
}