diff options
Diffstat (limited to 'new/darwin')
| -rw-r--r-- | new/darwin/alloc.m | 44 | ||||
| -rw-r--r-- | new/darwin/button.m | 79 | ||||
| -rw-r--r-- | new/darwin/checkbox.m | 98 | ||||
| -rw-r--r-- | new/darwin/entry.m | 67 | ||||
| -rw-r--r-- | new/darwin/init.m | 67 | ||||
| -rw-r--r-- | new/darwin/label.m | 53 | ||||
| -rw-r--r-- | new/darwin/main.m | 27 | ||||
| -rw-r--r-- | new/darwin/newcontrol.m | 236 | ||||
| -rw-r--r-- | new/darwin/parent.m | 118 | ||||
| -rw-r--r-- | new/darwin/tab.m | 67 | ||||
| -rw-r--r-- | new/darwin/text.m | 19 | ||||
| -rw-r--r-- | new/darwin/util.m | 20 | ||||
| -rw-r--r-- | new/darwin/window.m | 145 |
13 files changed, 1040 insertions, 0 deletions
diff --git a/new/darwin/alloc.m b/new/darwin/alloc.m new file mode 100644 index 0000000..8f539af --- /dev/null +++ b/new/darwin/alloc.m @@ -0,0 +1,44 @@ +// 4 december 2014 +#import <stdio.h> +#import "uipriv_darwin.h" + +void *uiAlloc(size_t size, const char *type) +{ + void *out; + + out = malloc(size); + if (out == NULL) { + fprintf(stderr, "memory exhausted in uiAlloc() allocating %s\n", type); + abort(); + } + memset(out, 0, size); + if (options.debugLogAllocations) + fprintf(stderr, "%p alloc %s\n", out, type); + return out; +} + +void *uiRealloc(void *p, size_t size, const char *type) +{ + void *out; + + if (p == NULL) + return uiAlloc(size, type); + out = realloc(p, size); + if (out == NULL) { + fprintf(stderr, "memory exhausted in uiRealloc() reallocating %s\n", type); + abort(); + } + // TODO zero the extra memory + if (options.debugLogAllocations) + fprintf(stderr, "%p realloc %p\n", p, out); + return out; +} + +void uiFree(void *p) +{ + if (p == NULL) + return; + free(p); + if (options.debugLogAllocations) + fprintf(stderr, "%p free\n", p); +} diff --git a/new/darwin/button.m b/new/darwin/button.m new file mode 100644 index 0000000..92e2c76 --- /dev/null +++ b/new/darwin/button.m @@ -0,0 +1,79 @@ +// 7 april 2015 +#import "uipriv_darwin.h" + +@interface uiNSButton : NSButton +@property uiControl *uiC; +@property void (*uiOnClicked)(uiControl *, void *); +@property void *uiOnClickedData; +@end + +@implementation uiNSButton + +- (void)viewDidMoveToSuperview +{ + if (uiDarwinControlFreeWhenAppropriate(self.uiC, [self superview])) { + [self setTarget:nil]; + self.uiC = NULL; + } + [super viewDidMoveToSuperview]; +} + +- (IBAction)uiButtonClicked:(id)sender +{ + (*(self.uiOnClicked))(self.uiC, self.uiOnClickedData); +} + +@end + +static void defaultOnClicked(uiControl *c, void *data) +{ + // do nothing +} + +uiControl *uiNewButton(const char *text) +{ + uiControl *c; + uiNSButton *b; + + c = uiDarwinNewControl([uiNSButton class], NO, NO); + b = (uiNSButton *) uiControlHandle(c); + b.uiC = c; + + [b setTitle:toNSString(text)]; + [b setButtonType:NSMomentaryPushInButton]; + [b setBordered:YES]; + [b setBezelStyle:NSRoundedBezelStyle]; + setStandardControlFont((NSControl *) b); + + [b setTarget:b]; + [b setAction:@selector(uiButtonClicked:)]; + + b.uiOnClicked = defaultOnClicked; + + return b.uiC; +} + +char *uiButtonText(uiControl *c) +{ + uiNSButton *b; + + b = (uiNSButton *) uiControlHandle(c); + return uiDarwinNSStringToText([b title]); +} + +void uiButtonSetText(uiControl *c, const char *text) +{ + uiNSButton *b; + + b = (uiNSButton *) uiControlHandle(c); + [b setTitle:toNSString(text)]; +} + +void uiButtonOnClicked(uiControl *c, void (*f)(uiControl *, void *), void *data) +{ + uiNSButton *b; + + b = (uiNSButton *) uiControlHandle(c); + b.uiOnClicked = f; + b.uiOnClickedData = data; +} diff --git a/new/darwin/checkbox.m b/new/darwin/checkbox.m new file mode 100644 index 0000000..ca52389 --- /dev/null +++ b/new/darwin/checkbox.m @@ -0,0 +1,98 @@ +// 7 april 2015 +#import "uipriv_darwin.h" + +@interface uiCheckboxNSButton : NSButton +@property uiControl *uiC; +@property void (*uiOnToggled)(uiControl *, void *); +@property void *uiOnToggledData; +@end + +@implementation uiCheckboxNSButton + +- (void)viewDidMoveToSuperview +{ + if (uiDarwinControlFreeWhenAppropriate(self.uiC, [self superview])) { + [self setTarget:nil]; + self.uiC = NULL; + } + [super viewDidMoveToSuperview]; +} + +- (IBAction)uiCheckboxToggled:(id)sender +{ + (*(self.uiOnToggled))(self.uiC, self.uiOnToggledData); +} + +@end + +static void defaultOnToggled(uiControl *c, void *data) +{ + // do nothing +} + +uiControl *uiNewCheckbox(const char *text) +{ + uiControl *c; + uiCheckboxNSButton *cc; + + c = uiDarwinNewControl([uiCheckboxNSButton class], NO, NO); + cc = (uiCheckboxNSButton *) uiControlHandle(c); + cc.uiC = c; + + [cc setTitle:toNSString(text)]; + [cc setButtonType:NSSwitchButton]; + [cc setBordered:NO]; + setStandardControlFont((NSControl *) cc); + + [cc setTarget:cc]; + [cc setAction:@selector(uiCheckboxToggled:)]; + + cc.uiOnToggled = defaultOnToggled; + + return cc.uiC; +} + +char *uiCheckboxText(uiControl *c) +{ + uiCheckboxNSButton *cc; + + cc = (uiCheckboxNSButton *) uiControlHandle(c); + return uiDarwinNSStringToText([cc title]); +} + +void uiCheckboxSetText(uiControl *c, const char *text) +{ + uiCheckboxNSButton *cc; + + cc = (uiCheckboxNSButton *) uiControlHandle(c); + [cc setTitle:toNSString(text)]; +} + +void uiCheckboxOnToggled(uiControl *c, void (*f)(uiControl *, void *), void *data) +{ + uiCheckboxNSButton *cc; + + cc = (uiCheckboxNSButton *) uiControlHandle(c); + cc.uiOnToggled = f; + cc.uiOnToggledData = data; +} + +int uiCheckboxChecked(uiControl *c) +{ + uiCheckboxNSButton *cc; + + cc = (uiCheckboxNSButton *) uiControlHandle(c); + return [cc state] == NSOnState; +} + +void uiCheckboxSetChecked(uiControl *c, int checked) +{ + uiCheckboxNSButton *cc; + NSInteger state; + + cc = (uiCheckboxNSButton *) uiControlHandle(c); + state = NSOnState; + if (!checked) + state = NSOffState; + [cc setState:state]; +} diff --git a/new/darwin/entry.m b/new/darwin/entry.m new file mode 100644 index 0000000..f2b208b --- /dev/null +++ b/new/darwin/entry.m @@ -0,0 +1,67 @@ +// 9 april 2015 +#import "uipriv_darwin.h" + +@interface uiNSTextField : NSTextField +@property uiControl *uiC; +@end + +@implementation uiNSTextField + +- (void)viewDidMoveToSuperview +{ + if (uiDarwinControlFreeWhenAppropriate(self.uiC, [self superview])) { + [self setTarget:nil]; + self.uiC = NULL; + } + [super viewDidMoveToSuperview]; +} + +@end + +// TOOD move elsewhere +// these are based on interface builder defaults; my comments in the old code weren't very good so I don't really know what talked about what, sorry :/ +void finishNewTextField(NSTextField *t, BOOL isEntry) +{ + setStandardControlFont((id) t); + + // THE ORDER OF THESE CALLS IS IMPORTANT; CHANGE IT AND THE BORDERS WILL DISAPPEAR + [t setBordered:NO]; + [t setBezelStyle:NSTextFieldSquareBezel]; + [t setBezeled:isEntry]; + + // we don't need to worry about substitutions/autocorrect here; see window_darwin.m for details + + [[t cell] setLineBreakMode:NSLineBreakByClipping]; + [[t cell] setScrollable:YES]; +} + +uiControl *uiNewEntry(void) +{ + uiControl *c; + uiNSTextField *t; + + c = uiDarwinNewControl([uiNSTextField class], NO, NO); + t = (uiNSTextField *) uiControlHandle(c); + t.uiC = c; + + [t setSelectable:YES]; // otherwise the setting is masked by the editable default of YES + finishNewTextField((NSTextField *) t, YES); + + return t.uiC; +} + +char *uiEntryText(uiControl *c) +{ + uiNSTextField *t; + + t = (uiNSTextField *) uiControlHandle(c); + return uiDarwinNSStringToText([t stringValue]); +} + +void uiEntrySetText(uiControl *c, const char *text) +{ + uiNSTextField *t; + + t = (uiNSTextField *) uiControlHandle(c); + [t setStringValue:toNSString(text)]; +} diff --git a/new/darwin/init.m b/new/darwin/init.m new file mode 100644 index 0000000..4f14a0a --- /dev/null +++ b/new/darwin/init.m @@ -0,0 +1,67 @@ +// 6 april 2015 +#import "uipriv_darwin.h" + +@interface uiApplication : NSApplication +@end + +@implementation uiApplication + +// hey look! we're overriding terminate:! +// we're going to make sure we can go back to main() whether Cocoa likes it or not! +// and just how are we going to do that, hm? +// (note: this is called after applicationShouldTerminate:) +- (void)terminate:(id)sender +{ + // yes that's right folks: DO ABSOLUTELY NOTHING. + // the magic is [NSApp run] will just... stop. + + // for debugging + NSLog(@"in terminate:"); +} + +@end + +@interface uiAppDelegate : NSObject <NSApplicationDelegate> +@end + +@implementation uiAppDelegate + +- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)app +{ + // for debugging + NSLog(@"in applicationShouldTerminate:"); + return NSTerminateNow; +} + +- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)app +{ + return NO; +} + +@end + +// we are not in control of the actual lifetimes and refcounts of NSViews (see http://stackoverflow.com/a/29523141/3408572) +// when we're done with a view, it'll be added as a subview of this one, and this one will be released on application shutdown +// we need this separate view because it's possible for controls to have no parent but still be alive +NSView *destroyedControlsView; + +uiInitOptions options; + +const char *uiInit(uiInitOptions *o) +{ + options = *o; + [uiApplication sharedApplication]; + // don't check for a NO return; something (launch services?) causes running from application bundles to always return NO when asking to change activation policy, even if the change is to the same activation policy! + // see https://github.com/andlabs/ui/issues/6 + [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; + [NSApp setDelegate:[uiAppDelegate new]]; + + // we can use a stock NSView for this + destroyedControlsView = [[NSView alloc] initWithFrame:NSZeroRect]; + + return NULL; +} + +void uiFreeInitError(const char *err) +{ +} diff --git a/new/darwin/label.m b/new/darwin/label.m new file mode 100644 index 0000000..e3fb351 --- /dev/null +++ b/new/darwin/label.m @@ -0,0 +1,53 @@ +// 9 april 2015 +#import "uipriv_darwin.h" + +@interface uiLabelNSTextField : NSTextField +@property uiControl *uiC; +@end + +@implementation uiLabelNSTextField + +- (void)viewDidMoveToSuperview +{ + if (uiDarwinControlFreeWhenAppropriate(self.uiC, [self superview])) { + [self setTarget:nil]; + self.uiC = NULL; + } + [super viewDidMoveToSuperview]; +} + +@end + +uiControl *uiNewLabel(const char *text) +{ + uiControl *c; + uiLabelNSTextField *l; + + c = uiDarwinNewControl([uiLabelNSTextField class], NO, NO); + l = (uiLabelNSTextField *) uiControlHandle(c); + l.uiC = c; + + [l setStringValue:toNSString(text)]; + [l setEditable:NO]; + [l setSelectable:NO]; + [l setDrawsBackground:NO]; + finishNewTextField((NSTextField *) l, NO); + + return l.uiC; +} + +char *uiLabelText(uiControl *c) +{ + uiLabelNSTextField *t; + + t = (uiLabelNSTextField *) uiControlHandle(c); + return uiDarwinNSStringToText([t stringValue]); +} + +void uiLabelSetText(uiControl *c, const char *text) +{ + uiLabelNSTextField *t; + + t = (uiLabelNSTextField *) uiControlHandle(c); + [t setStringValue:toNSString(text)]; +} diff --git a/new/darwin/main.m b/new/darwin/main.m new file mode 100644 index 0000000..8663b58 --- /dev/null +++ b/new/darwin/main.m @@ -0,0 +1,27 @@ +// 6 april 2015 +#import "uipriv_darwin.h" + +// #qo LDFLAGS: -lobjc -framework Foundation -framework AppKit + +void uiMain(void) +{ + [NSApp run]; +} + +void uiQuit(void) +{ + NSEvent *e; + + [NSApp stop:NSApp]; + // stop: won't register until another event has passed; let's synthesize one + e = [NSEvent otherEventWithType:NSApplicationDefined + location:NSZeroPoint + modifierFlags:0 + timestamp:[[NSProcessInfo processInfo] systemUptime] + windowNumber:0 + context:[NSGraphicsContext currentContext] + subtype:0 + data1:0 + data2:0]; + [NSApp postEvent:e atStart:NO]; // let pending events take priority (this is what PostQuitMessage() on Windows does so we have to do it here too for parity; thanks to mikeash in irc.freenode.net/#macdev for confirming that this parameter should indeed be NO) +} diff --git a/new/darwin/newcontrol.m b/new/darwin/newcontrol.m new file mode 100644 index 0000000..9b7c6c4 --- /dev/null +++ b/new/darwin/newcontrol.m @@ -0,0 +1,236 @@ +// 7 april 2015 +#include "uipriv_darwin.h" + +typedef struct singleView singleView; + +struct singleView { + NSView *view; + NSScrollView *scrollView; + NSView *immediate; // the control that is added to the parent container; either view or scrollView + uiParent *parent; + BOOL userHid; + BOOL containerHid; + BOOL userDisabled; + BOOL containerDisabled; +}; + +static void singleDestroy(uiControl *c) +{ + singleView *s = (singleView *) (c->internal); + + [destroyedControlsView addSubview:s->immediate]; +} + +static uintptr_t singleHandle(uiControl *c) +{ + singleView *s = (singleView *) (c->internal); + + return (uintptr_t) (s->view); +} + +static void singleSetParent(uiControl *c, uiParent *parent) +{ + singleView *s = (singleView *) (c->internal); + NSView *parentView; + uiParent *oldparent; + + oldparent = s->parent; + s->parent = parent; + if (oldparent != NULL) { + [s->immediate removeFromSuperview]; + uiParentUpdate(oldparent); + } + if (s->parent != NULL) { + // TODO uiControlView(), uiParentView() + parentView = (NSView *) uiParentHandle(s->parent); + [parentView addSubview:s->immediate]; + uiParentUpdate(s->parent); + } +} + +// also good for NSBox and NSProgressIndicator +static void singlePreferredSize(uiControl *c, uiSizing *d, intmax_t *width, intmax_t *height) +{ + singleView *s = (singleView *) (c->internal); + NSControl *control; + NSRect r; + + control = (NSControl *) (s->view); + [control sizeToFit]; + // use alignmentRect here instead of frame because we'll be resizing based on that + r = [control alignmentRectForFrame:[control frame]]; + *width = (intmax_t) r.size.width; + *height = (intmax_t) r.size.height; +} + +static void singleResize(uiControl *c, intmax_t x, intmax_t y, intmax_t width, intmax_t height, uiSizing *d) +{ + singleView *s = (singleView *) (c->internal); + NSRect frame; + + frame.origin.x = x; + // mac os x coordinate system has (0,0) in the lower-left + frame.origin.y = ([[s->immediate superview] bounds].size.height - height) - y; + frame.size.width = width; + frame.size.height = height; + frame = [s->immediate frameForAlignmentRect:frame]; + [s->immediate setFrame:frame]; +} + +static int singleVisible(uiControl *c) +{ + singleView *s = (singleView *) (c->internal); + + if (s->userHid) + return 0; + return 1; +} + +static void singleShow(uiControl *c) +{ + singleView *s = (singleView *) (c->internal); + + s->userHid = NO; + if (!s->containerHid) { + [s->immediate setHidden:NO]; + if (s->parent != NULL) + uiParentUpdate(s->parent); + } +} + +static void singleHide(uiControl *c) +{ + singleView *s = (singleView *) (c->internal); + + s->userHid = YES; + [s->immediate setHidden:YES]; + if (s->parent != NULL) + uiParentUpdate(s->parent); +} + +static void singleContainerShow(uiControl *c) +{ + singleView *s = (singleView *) (c->internal); + + s->containerHid = NO; + if (!s->userHid) { + [s->immediate setHidden:NO]; + if (s->parent != NULL) + uiParentUpdate(s->parent); + } +} + +static void singleContainerHide(uiControl *c) +{ + singleView *s = (singleView *) (c->internal); + + s->containerHid = YES; + [s->immediate setHidden:YES]; + if (s->parent != NULL) + uiParentUpdate(s->parent); +} + +static void enable(singleView *s) +{ + if ([s->view respondsToSelector:@selector(setEnabled:)]) + [((NSControl *) (s->view)) setEnabled:YES]; +} + +static void disable(singleView *s) +{ + if ([s->view respondsToSelector:@selector(setEnabled:)]) + [((NSControl *) (s->view)) setEnabled:NO]; +} + +static void singleEnable(uiControl *c) +{ + singleView *s = (singleView *) (c->internal); + + s->userDisabled = NO; + if (!s->containerDisabled) + enable(s); +} + +static void singleDisable(uiControl *c) +{ + singleView *s = (singleView *) (c->internal); + + s->userDisabled = YES; + disable(s); +} + +static void singleContainerEnable(uiControl *c) +{ + singleView *s = (singleView *) (c->internal); + + s->containerDisabled = NO; + if (!s->userDisabled) + enable(s); +} + +static void singleContainerDisable(uiControl *c) +{ + singleView *s = (singleView *) (c->internal); + + s->containerDisabled = YES; + disable(s); +} + +uiControl *uiDarwinNewControl(Class class, BOOL inScrollView, BOOL scrollViewHasBorder) +{ + uiControl *c; + singleView *s; + + s = uiNew(singleView); + // thanks to autoxr and arwyn in irc.freenode.net/#macdev + s->view = (NSView *) [[class alloc] initWithFrame:NSZeroRect]; + s->immediate = s->view; + + if (inScrollView) { + s->scrollView = [[NSScrollView alloc] initWithFrame:NSZeroRect]; + [s->scrollView setDocumentView:s->view]; + [s->scrollView setHasHorizontalScroller:YES]; + [s->scrollView setHasVerticalScroller:YES]; + [s->scrollView setAutohidesScrollers:YES]; + if (scrollViewHasBorder) + [s->scrollView setBorderType:NSBezelBorder]; + else + [s->scrollView setBorderType:NSNoBorder]; + s->immediate = (NSView *) (s->scrollView); + } + + // and keep a reference to s->immediate for when we remove the control from its parent + [s->immediate retain]; + + c = uiNew(uiControl); + c->internal = s; + c->destroy = singleDestroy; + c->handle = singleHandle; + c->setParent = singleSetParent; + c->preferredSize = singlePreferredSize; + c->resize = singleResize; + c->visible = singleVisible; + c->show = singleShow; + c->hide = singleHide; + c->containerShow = singleContainerShow; + c->containerHide = singleContainerHide; + c->enable = singleEnable; + c->disable = singleDisable; + c->containerEnable = singleContainerEnable; + c->containerDisable = singleContainerDisable; + + return c; +} + +BOOL uiDarwinControlFreeWhenAppropriate(uiControl *c, NSView *newSuperview) +{ + singleView *s = (singleView *) (c->internal); + + if (newSuperview == destroyedControlsView) { + [s->immediate release]; // we don't need the reference anymore + uiFree(s); + uiFree(c); + return YES; + } + return NO; +} diff --git a/new/darwin/parent.m b/new/darwin/parent.m new file mode 100644 index 0000000..abf0e59 --- /dev/null +++ b/new/darwin/parent.m @@ -0,0 +1,118 @@ +// 4 august 2014 +#import "uipriv_darwin.h" + +// calling -[className] on the content views of NSWindow, NSTabItem, and NSBox all return NSView, so I'm assuming I just need to override these +// fornunately: +// - NSWindow resizing calls -[setFrameSize:] (but not -[setFrame:]) +// - NSTabView resizing calls both -[setFrame:] and -[setFrameSIze:] on the current tab +// - NSTabView switching tabs calls both -[setFrame:] and -[setFrameSize:] on the new tab +// so we just override setFrameSize: +// thanks to mikeash and JtRip in irc.freenode.net/#macdev +@interface uipParent : NSView { +// TODO +@public + uiControl *child; + intmax_t marginLeft; + intmax_t marginTop; + intmax_t marginRight; + intmax_t marginBottom; +} +- (void)uiUpdateNow; +@end + +@implementation uipParent + +uiLogObjCClassAllocations + +- (void)viewDidMoveToSuperview +{ + // we can't just use nil because NSTabView will set page views to nil when they're tabbed away + // this means that we have to explicitly move them to the destroyed controls view when we're done with them, and likewise in NSWindow + if ([self superview] == destroyedControlsView) + if (self->child != NULL) { + uiControlDestroy(self->child); + self->child = NULL; + [self release]; + } + [super viewDidMoveToSuperview]; +} + +- (void)setFrameSize:(NSSize)s +{ + [super setFrameSize:s]; + [self uiUpdateNow]; +} + +// These are based on measurements from Interface Builder. +// These seem to be based on Auto Layout constants, but I don't see an API that exposes these... +// This one is 8 for most pairs of controls that I've tried; the only difference is between two pushbuttons, where it's 12... +#define macXPadding 8 +// Likewise, this one appears to be 12 for pairs of push buttons... +#define macYPadding 8 + +- (void)uiUpdateNow +{ + uiSizing d; + intmax_t x, y, width, height; + + if (self->child == NULL) + return; + x = [self bounds].origin.x + self->marginLeft; + y = [self bounds].origin.y + self->marginTop; + width = [self bounds].size.width - (self->marginLeft + self->marginRight); + height = [self bounds].size.height - (self->marginTop + self->marginBottom); + d.xPadding = macXPadding; + d.yPadding = macYPadding; + uiControlResize(self->child, x, y, width, height, &d); +} + +@end + +static uintptr_t parentHandle(uiParent *p) +{ + uipParent *pp = (uipParent *) (p->Internal); + + return (uintptr_t) pp; +} + +static void parentSetChild(uiParent *p, uiControl *child) +{ + uipParent *pp = (uipParent *) (p->Internal); + + pp->child = child; + if (pp->child != NULL) + uiControlSetParent(child, p); +} + +static void parentSetMargins(uiParent *p, intmax_t left, intmax_t top, intmax_t right, intmax_t bottom) +{ + uipParent *pp = (uipParent *) (p->Internal); + + pp->marginLeft = left; + pp->marginTop = top; + pp->marginRight = right; + pp->marginBottom = bottom; +} + +static void parentUpdate(uiParent *p) +{ + uipParent *pp = (uipParent *) (p->Internal); + + [pp uiUpdateNow]; +} + +uiParent *uiNewParent(uintptr_t osParent) +{ + uiParent *p; + + p = uiNew(uiParent); + p->Internal = [[uipParent alloc] initWithFrame:NSZeroRect]; + p->Handle = parentHandle; + p->SetChild = parentSetChild; + p->SetMargins = parentSetMargins; + p->Update = parentUpdate; + // don't use osParent; we'll need to call specific selectors to set the parent view + // and keep the view alive so we can release it properly later + [((uipParent *) (p->Internal)) retain]; + return p; +}
\ No newline at end of file diff --git a/new/darwin/tab.m b/new/darwin/tab.m new file mode 100644 index 0000000..c36181e --- /dev/null +++ b/new/darwin/tab.m @@ -0,0 +1,67 @@ +// 12 april 2015 +#import "uipriv_darwin.h" + +// TODO +// - verify margins against extra space around the tab +// - free child containers properly + +@interface uiNSTabView : NSTabView +@property uiControl *uiC; +@end + +@implementation uiNSTabView + +- (void)viewDidMoveToSuperview +{ + // TODO free all tabs explicitly + if (uiDarwinControlFreeWhenAppropriate(self.uiC, [self superview])) + self.uiC = NULL; + [super viewDidMoveToSuperview]; +} + +@end + +// the default new control implementation uses -sizeToFit, which we don't have with NSTabView +// fortunately, we do have -minimumSize +static void preferredSize(uiControl *c, uiSizing *d, intmax_t *width, intmax_t *height) +{ + uiNSTabView *tv; + NSSize s; + + tv = (uiNSTabView *) uiControlHandle(c); + s = [tv minimumSize]; + *width = (intmax_t) (s.width); + *height = (intmax_t) (s.height); +} + +uiControl *uiNewTab(void) +{ + uiControl *c; + uiNSTabView *t; + + c = uiDarwinNewControl([uiNSTabView class], NO, NO); + c->preferredSize = preferredSize; + t = (uiNSTabView *) uiControlHandle(c); + t.uiC = c; + + // also good for NSTabView (same selector and everything) + setStandardControlFont((NSControl *) t); + + return c; +} + +void uiTabAddPage(uiControl *c, const char *name, uiControl *child) +{ + uiNSTabView *tv; + uiParent *content; + NSTabViewItem *i; + + content = uiNewParent(0); + uiParentSetChild(content, child); + + i = [[NSTabViewItem alloc] initWithIdentifier:nil]; + [i setLabel:toNSString(name)]; + [i setView:((NSView *) uiParentHandle(content))]; + tv = (uiNSTabView *) uiControlHandle(c); + [tv addTabViewItem:i]; +} diff --git a/new/darwin/text.m b/new/darwin/text.m new file mode 100644 index 0000000..f0d3dab --- /dev/null +++ b/new/darwin/text.m @@ -0,0 +1,19 @@ +// 10 april 2015 +#import "uipriv_darwin.h" + +char *uiDarwinNSStringToText(NSString *s) +{ + char *out; + + out = strdup([s UTF8String]); + if (out == NULL) { + fprintf(stderr, "memory exhausted in uiDarwinNSStringToText()\n"); + abort(); + } + return out; +} + +void uiFreeText(char *s) +{ + free(s); +} diff --git a/new/darwin/util.m b/new/darwin/util.m new file mode 100644 index 0000000..906a0ea --- /dev/null +++ b/new/darwin/util.m @@ -0,0 +1,20 @@ +// 7 april 2015 +#import "uipriv_darwin.h" + +// also fine for NSCells and NSTexts (NSTextViews) +void setStandardControlFont(NSControl *control) +{ + [control setFont:[NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSRegularControlSize]]]; +} + +void disableAutocorrect(NSTextView *tv) +{ + [tv setEnabledTextCheckingTypes:0]; + [tv setAutomaticDashSubstitutionEnabled:NO]; + // don't worry about automatic data detection; it won't change stringValue (thanks pretty_function in irc.freenode.net/#macdev) + [tv setAutomaticSpellingCorrectionEnabled:NO]; + [tv setAutomaticTextReplacementEnabled:NO]; + [tv setAutomaticQuoteSubstitutionEnabled:NO]; + [tv setAutomaticLinkDetectionEnabled:NO]; + [tv setSmartInsertDeleteEnabled:NO]; +} diff --git a/new/darwin/window.m b/new/darwin/window.m new file mode 100644 index 0000000..b0cab11 --- /dev/null +++ b/new/darwin/window.m @@ -0,0 +1,145 @@ +// 6 april 2015 +#import "uipriv_darwin.h" + +// TODO +// - free chilld containers properly + +@interface uiWindowDelegate : NSObject <NSWindowDelegate> +@property (assign) NSWindow *w; +@property uiParent *content; +@property int (*onClosing)(uiWindow *, void *); +@property void *onClosingData; +@property uiWindow *uiw; +@end + +@implementation uiWindowDelegate + +uiLogObjCClassAllocations + +- (BOOL)windowShouldClose:(id)win +{ + // return exact constants to be safe + if ((*(self.onClosing))(self.uiw, self.onClosingData)) + return YES; + return NO; +} + +// after this method returns we assume the window will be released (see below), so we can go too +- (void)windowWillClose:(NSNotification *)note +{ + [self.w setDelegate:nil]; // see http://stackoverflow.com/a/29523141/3408572 + + // when we reach this point, we need to ensure that all the window's children are destroyed (for OS parity) + // because we need to set the content view's superview to the destroyed controls view to trigger deletion, we need to do this manually + // first, replace the current content view... + [self.w setContentView:[[NSView alloc] initWithFrame:NSZeroRect]]; + // ...then, trigger the deletion + [destroyedControlsView addSubview:((NSView *) uiParentHandle(self.content))]; + + uiFree(self.uiw); + [self release]; +} + +@end + +struct uiWindow { + uiWindowDelegate *d; + int margined; +}; + +static int defaultOnClosing(uiWindow *w, void *data) +{ + return 1; +} + +uiWindow *uiNewWindow(char *title, int width, int height) +{ + uiWindowDelegate *d; + + d = [uiWindowDelegate new]; + + d.w = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, (CGFloat) width, (CGFloat) height) + styleMask:(NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask) + backing:NSBackingStoreBuffered + defer:YES]; + [d.w setTitle:toNSString(title)]; + + // we do not want substitutions + // text fields, labels, etc. take their smart quotes and other autocorrect settings from their parent window, which provides a shared "field editor" + // so we have to turn them off here + // thanks akempgen in irc.freenode.net/#macdev + // for some reason, this selector returns NSText but is documented to return NSTextView... + // NOTE: if you disagree with me about disabling substitutions, start a github issue with why and I'll be happy to consider it + disableAutocorrect((NSTextView *) [d.w fieldEditor:YES forObject:nil]); + + // this is what will destroy the window on close + [d.w setReleasedWhenClosed:YES]; + + d.content = uiNewParent(0); + [d.w setContentView:((NSView *) uiParentHandle(d.content))]; + + d.onClosing = defaultOnClosing; + [d.w setDelegate:d]; + + d.uiw = uiNew(uiWindow); + d.uiw->d = d; + return d.uiw; +} + +#define D w->d + +void uiWindowDestroy(uiWindow *w) +{ + [D.w close]; +} + +uintptr_t uiWindowHandle(uiWindow *w) +{ + return (uintptr_t) (D.w); +} + +char *uiWindowTitle(uiWindow *w) +{ + return uiDarwinNSStringToText([D.w title]); +} + +void uiWindowSetTitle(uiWindow *w, const char *title) +{ + [D.w setTitle:toNSString(title)]; +} + +void uiWindowShow(uiWindow *w) +{ + [D.w makeKeyAndOrderFront:D.w]; +} + +void uiWindowHide(uiWindow *w) +{ + [D.w orderOut:D.w]; +} + +void uiWindowOnClosing(uiWindow *w, int (*f)(uiWindow *, void *), void *data) +{ + D.onClosing = f; + D.onClosingData = data; +} + +void uiWindowSetChild(uiWindow *w, uiControl *c) +{ + uiParentSetChild(D.content, c); +} + +int uiWindowMargined(uiWindow *w) +{ + return w->margined; +} + +void uiWindowSetMargined(uiWindow *w, int margined) +{ + w->margined = margined; + if (w->margined) + uiParentSetMargins(D.content, macXMargin, macYMargin, macXMargin, macYMargin); + else + uiParentSetMargins(D.content, 0, 0, 0, 0); + uiParentUpdate(D.content); +} |
