diff --git a/redo/basicctrls_darwin.m b/redo/basicctrls_darwin.m index 8e16eb1..6f2769c 100644 --- a/redo/basicctrls_darwin.m +++ b/redo/basicctrls_darwin.m @@ -7,6 +7,7 @@ #define toNSButton(x) ((NSButton *) (x)) #define toNSTextField(x) ((NSTextField *) (x)) #define toNSView(x) ((NSView *) (x)) +#define toNSPopover(x) ((NSPopover *) (x)) #define toNSBox(x) ((NSBox *) (x)) @interface goControlDelegate : NSObject { @@ -162,6 +163,54 @@ void textFieldSetText(id t, char *text) [toNSTextField(t) setStringValue:[NSString stringWithUTF8String:text]]; } +id textfieldOpenInvalidPopover(id textfield, char *reason) +{ + // step 1: set up the display + NSTextField *label; + NSTextAttachmentCell *cell; + NSTextAttachment *attachment; + NSAttributedString *strImage; + NSAttributedString *strText; + NSFont *font; + NSMutableAttributedString *str; + + // method thanks to Anne in http://stackoverflow.com/a/5303517/3408572 + // TODO improve appearance + label = toNSTextField(newLabel()); + cell = [[NSTextAttachmentCell alloc] initImageCell:[NSImage imageNamed:NSImageNameCaution]]; + attachment = [NSTextAttachment new]; + [attachment setAttachmentCell:cell]; + strImage = [NSAttributedString attributedStringWithAttachment:attachment]; + font = [NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSRegularControlSize]]; + strText = [[NSAttributedString alloc] initWithString:[NSString stringWithUTF8String:reason] attributes:[[font fontDescriptor] fontAttributes]]; + str = [[NSMutableAttributedString alloc] initWithAttributedString:strImage]; + [str appendAttributedString:strText]; + [label setAttributedStringValue:str]; + + // step 2: set up the popover + NSPopover *popover; + NSViewController *vc; + + vc = [NSViewController new]; + [vc setView:label]; + popover = [NSPopover new]; + [popover setContentViewController:vc]; + [label sizeToFit]; + [popover setContentSize:[label frame].size]; + + // step 3: show the popover + // NSMaxYEdge is the bottom edge when looking (maximum edge in window coordinates) + [popover showRelativeToRect:NSZeroRect ofView:toNSView(textfield) preferredEdge:NSMaxYEdge]; + + return (id) popover; +} + +void textfieldCloseInvalidPopover(id popover) +{ + [toNSPopover(popover) close]; + [toNSPopover(popover) release]; +} + id newLabel(void) { NSTextField *l; diff --git a/redo/objc_darwin.h b/redo/objc_darwin.h index dca8894..d4a58d0 100644 --- a/redo/objc_darwin.h +++ b/redo/objc_darwin.h @@ -71,6 +71,8 @@ extern id newPasswordField(void); extern void textfieldSetDelegate(id, void *); extern const char *textFieldText(id); extern void textFieldSetText(id, char *); +extern id textfieldOpenInvalidPopover(id, char *); +extern void textfieldCloseInvalidPopover(id); extern id newLabel(void); extern id newGroup(id); extern const char *groupText(id); diff --git a/redo/textfield_darwin.go b/redo/textfield_darwin.go index 1b6ff82..cbe5f8b 100644 --- a/redo/textfield_darwin.go +++ b/redo/textfield_darwin.go @@ -12,6 +12,7 @@ import "C" type textfield struct { _id C.id changed *event + invalid C.id } func finishNewTextField(id C.id) *textfield { @@ -24,7 +25,7 @@ func finishNewTextField(id C.id) *textfield { } func newTextField() *textfield { - return finishNewTextField(C.newTextField() + return finishNewTextField(C.newTextField()) } func newPasswordField() *textfield { @@ -46,7 +47,18 @@ func (t *textfield) OnChanged(f func()) { } func (t *textfield) Invalid(reason string) { - // TODO + // TODO disable animations if reason is still valid + // TODO don't steal focus + if t.invalid != nil { + C.textfieldCloseInvalidPopover(t.invalid) + t.invalid = nil + } + if reason == "" { + return + } + creason := C.CString(reason) + defer C.free(unsafe.Pointer(creason)) + t.invalid = C.textfieldOpenInvalidPopover(t._id, creason) } //export textfieldChanged