diff --git a/newctrl/area_darwin.go b/newctrl/area_darwin.go new file mode 100644 index 0000000..6288ac9 --- /dev/null +++ b/newctrl/area_darwin.go @@ -0,0 +1,244 @@ +// 29 march 2014 + +package ui + +import ( + "fmt" + "image" + "unsafe" +) + +//// #include +// #include "objc_darwin.h" +import "C" + +type area struct { + *areabase + + *scroller + textfield C.id + textfielddone *event +} + +func newArea(ab *areabase) Area { + a := &area{ + areabase: ab, + textfielddone: newEvent(), + } + id := C.newArea(unsafe.Pointer(a)) + a.scroller = newScroller(id, false) // no border on Area + a.fpreferredSize = a.xpreferredSize + a.SetSize(a.width, a.height) + a.textfield = C.newTextField() + C.areaSetTextField(a.id, a.textfield) + return a +} + +func (a *area) SetSize(width, height int) { + a.width = width + a.height = height + // set the frame size to set the area's effective size on the Cocoa side + C.moveControl(a.id, 0, 0, C.intptr_t(a.width), C.intptr_t(a.height)) +} + +func (a *area) Repaint(r image.Rectangle) { + var s C.struct_xrect + + r = image.Rect(0, 0, a.width, a.height).Intersect(r) + if r.Empty() { + return + } + s.x = C.intptr_t(r.Min.X) + s.y = C.intptr_t(r.Min.Y) + s.width = C.intptr_t(r.Dx()) + s.height = C.intptr_t(r.Dy()) + C.areaRepaint(a.id, s) +} + +func (a *area) RepaintAll() { + C.areaRepaintAll(a.id) +} + +func (a *area) OpenTextFieldAt(x, y int) { + if x < 0 || x >= a.width || y < 0 || y >= a.height { + panic(fmt.Errorf("point (%d,%d) outside Area in Area.OpenTextFieldAt()", x, y)) + } + C.areaTextFieldOpen(a.id, a.textfield, C.intptr_t(x), C.intptr_t(y)) +} + +func (a *area) TextFieldText() string { + return C.GoString(C.textfieldText(a.textfield)) +} + +func (a *area) SetTextFieldText(text string) { + ctext := C.CString(text) + defer C.free(unsafe.Pointer(ctext)) + C.textfieldSetText(a.textfield, ctext) +} + +func (a *area) OnTextFieldDismissed(f func()) { + a.textfielddone.set(f) +} + +//export areaTextFieldDismissed +func areaTextFieldDismissed(data unsafe.Pointer) { + a := (*area)(unsafe.Pointer(data)) + C.controlSetHidden(a.textfield, C.YES) + a.textfielddone.fire() +} + +//export areaView_drawRect +func areaView_drawRect(self C.id, rect C.struct_xrect, data unsafe.Pointer) { + a := (*area)(data) + // no need to clear the clip rect; the NSScrollView does that for us (see the setDrawsBackground: call in objc_darwin.m) + // rectangles in Cocoa are origin/size, not point0/point1; if we don't watch for this, weird things will happen when scrolling + cliprect := image.Rect(int(rect.x), int(rect.y), int(rect.x+rect.width), int(rect.y+rect.height)) + cliprect = image.Rect(0, 0, int(a.width), int(a.height)).Intersect(cliprect) + if cliprect.Empty() { // no intersection; nothing to paint + return + } + i := a.handler.Paint(cliprect) + success := C.drawImage( + unsafe.Pointer(pixelData(i)), C.intptr_t(i.Rect.Dx()), C.intptr_t(i.Rect.Dy()), C.intptr_t(i.Stride), + C.intptr_t(cliprect.Min.X), C.intptr_t(cliprect.Min.Y)) + if success == C.NO { + panic("error drawing into Area (exactly what is unknown)") + } +} + +func parseModifiers(e C.id) (m Modifiers) { + mods := C.modifierFlags(e) + if (mods & C.cNSControlKeyMask) != 0 { + m |= Ctrl + } + if (mods & C.cNSAlternateKeyMask) != 0 { + m |= Alt + } + if (mods & C.cNSShiftKeyMask) != 0 { + m |= Shift + } + if (mods & C.cNSCommandKeyMask) != 0 { + m |= Super + } + return m +} + +func areaMouseEvent(self C.id, e C.id, click bool, up bool, data unsafe.Pointer) { + var me MouseEvent + + a := (*area)(data) + xp := C.getTranslatedEventPoint(self, e) + me.Pos = image.Pt(int(xp.x), int(xp.y)) + // for the most part, Cocoa won't geenerate an event outside the Area... except when dragging outside the Area, so check for this + if !me.Pos.In(image.Rect(0, 0, int(a.width), int(a.height))) { + return + } + me.Modifiers = parseModifiers(e) + which := uint(C.buttonNumber(e)) + 1 + if which == 3 { // swap middle and right button numbers + which = 2 + } else if which == 2 { + which = 3 + } + if click && up { + me.Up = which + } else if click { + me.Down = which + // this already works the way we want it to so nothing special needed like with Windows and GTK+ + me.Count = uint(C.clickCount(e)) + } else { + which = 0 // reset for Held processing below + } + // the docs do say don't use this for tracking (mouseMoved:) since it returns the state now, and mouse move events work by tracking, but as far as I can tell dragging the mouse over the inactive window does not generate an event on Mac OS X, so :/ (tracking doesn't touch dragging anyway except during mouseEntered: and mouseExited:, which we don't handle, and the only other tracking message, cursorChanged:, we also don't handle (yet...? need to figure out if this is how to set custom cursors or not), so) + held := C.pressedMouseButtons() + if which != 1 && (held&1) != 0 { // button 1 + me.Held = append(me.Held, 1) + } + if which != 2 && (held&4) != 0 { // button 2; mind the swap + me.Held = append(me.Held, 2) + } + if which != 3 && (held&2) != 0 { // button 3 + me.Held = append(me.Held, 3) + } + held >>= 3 + for i := uint(4); held != 0; i++ { + if which != i && (held&1) != 0 { + me.Held = append(me.Held, i) + } + held >>= 1 + } + a.handler.Mouse(me) +} + +//export areaView_mouseMoved_mouseDragged +func areaView_mouseMoved_mouseDragged(self C.id, e C.id, data unsafe.Pointer) { + // for moving, this is handled by the tracking rect stuff above + // for dragging, if multiple buttons are held, only one of their xxxMouseDragged: messages will be sent, so this is OK to do + areaMouseEvent(self, e, false, false, data) +} + +//export areaView_mouseDown +func areaView_mouseDown(self C.id, e C.id, data unsafe.Pointer) { + // no need to manually set focus; Mac OS X has already done that for us by this point since we set our view to be a first responder + areaMouseEvent(self, e, true, false, data) +} + +//export areaView_mouseUp +func areaView_mouseUp(self C.id, e C.id, data unsafe.Pointer) { + areaMouseEvent(self, e, true, true, data) +} + +func sendKeyEvent(self C.id, ke KeyEvent, data unsafe.Pointer) C.BOOL { + a := (*area)(data) + handled := a.handler.Key(ke) + return toBOOL(handled) +} + +func areaKeyEvent(self C.id, e C.id, up bool, data unsafe.Pointer) C.BOOL { + var ke KeyEvent + + keyCode := uintptr(C.keyCode(e)) + ke, ok := fromKeycode(keyCode) + if !ok { + // no such key; modifiers by themselves are handled by -[self flagsChanged:] + return C.NO + } + // either ke.Key or ke.ExtKey will be set at this point + ke.Modifiers = parseModifiers(e) + ke.Up = up + return sendKeyEvent(self, ke, data) +} + +//export areaView_keyDown +func areaView_keyDown(self C.id, e C.id, data unsafe.Pointer) C.BOOL { + return areaKeyEvent(self, e, false, data) +} + +//export areaView_keyUp +func areaView_keyUp(self C.id, e C.id, data unsafe.Pointer) C.BOOL { + return areaKeyEvent(self, e, true, data) +} + +//export areaView_flagsChanged +func areaView_flagsChanged(self C.id, e C.id, data unsafe.Pointer) C.BOOL { + var ke KeyEvent + + // Mac OS X sends this event on both key up and key down. + // Fortunately -[e keyCode] IS valid here, so we can simply map from key code to Modifiers, get the value of [e modifierFlags], and check if the respective bit is set or not — that will give us the up/down state + keyCode := uintptr(C.keyCode(e)) + mod, ok := keycodeModifiers[keyCode] // comma-ok form to avoid adding entries + if !ok { // unknown modifier; ignore + return C.NO + } + ke.Modifiers = parseModifiers(e) + ke.Up = (ke.Modifiers & mod) == 0 + ke.Modifier = mod + // don't include the modifier in ke.Modifiers + ke.Modifiers &^= mod + return sendKeyEvent(self, ke, data) +} + +func (a *area) xpreferredSize(d *sizing) (width, height int) { + // the preferred size of an Area is its size + return a.width, a.height +} diff --git a/newctrl/button_darwin.go b/newctrl/button_darwin.go index 65262c1..1a0cd1b 100644 --- a/newctrl/button_darwin.go +++ b/newctrl/button_darwin.go @@ -21,8 +21,8 @@ func newButton(text string) *button { controlSingleObject: newControlSingleObject(C.newButton()), clicked: newEvent(), } - C.buttonSetText(b._id, ctext) - C.buttonSetDelegate(b._id, unsafe.Pointer(b)) + C.buttonSetText(b.id, ctext) + C.buttonSetDelegate(b.id, unsafe.Pointer(b)) return b } @@ -31,13 +31,13 @@ func (b *button) OnClicked(e func()) { } func (b *button) Text() string { - return C.GoString(C.buttonText(b._id)) + return C.GoString(C.buttonText(b.id)) } func (b *button) SetText(text string) { ctext := C.CString(text) defer C.free(unsafe.Pointer(ctext)) - C.buttonSetText(b._id, ctext) + C.buttonSetText(b.id, ctext) } //export buttonClicked diff --git a/newctrl/checkbox_darwin.go b/newctrl/checkbox_darwin.go index 741b163..1578add 100644 --- a/newctrl/checkbox_darwin.go +++ b/newctrl/checkbox_darwin.go @@ -21,8 +21,8 @@ func newCheckbox(text string) *checkbox { controlSingleObject: newControlSingleObject(C.newCheckbox()), toggled: newEvent(), } - C.buttonSetText(c._id, ctext) - C.checkboxSetDelegate(c._id, unsafe.Pointer(c)) + C.buttonSetText(c.id, ctext) + C.checkboxSetDelegate(c.id, unsafe.Pointer(c)) return c } @@ -31,21 +31,21 @@ func (c *checkbox) OnToggled(e func()) { } func (c *checkbox) Text() string { - return C.GoString(C.buttonText(c._id)) + return C.GoString(C.buttonText(c.id)) } func (c *checkbox) SetText(text string) { ctext := C.CString(text) defer C.free(unsafe.Pointer(ctext)) - C.buttonSetText(c._id, ctext) + C.buttonSetText(c.id, ctext) } func (c *checkbox) Checked() bool { - return fromBOOL(C.checkboxChecked(c._id)) + return fromBOOL(C.checkboxChecked(c.id)) } func (c *checkbox) SetChecked(checked bool) { - C.checkboxSetChecked(c._id, toBOOL(checked)) + C.checkboxSetChecked(c.id, toBOOL(checked)) } //export checkboxToggled diff --git a/newctrl/group_darwin.go b/newctrl/group_darwin.go new file mode 100644 index 0000000..23dd563 --- /dev/null +++ b/newctrl/group_darwin.go @@ -0,0 +1,60 @@ +// 16 august 2014 + +package ui + +import ( + "unsafe" +) + +// #include "objc_darwin.h" +import "C" + +type group struct { + *controlSingleObject + + child Control + container *container + + margined bool + + chainresize func(x int, y int, width int, height int, d *sizing) +} + +func newGroup(text string, control Control) Group { + g := new(group) + g.container = newContainer() + g.controlSingleObject = newControlSingleObject(C.newGroup(g.container.id)) + g.child = control + g.child.setParent(g.container.parent()) + g.SetText(text) + g.chainresize = g.fresize + g.fresize = g.xresize + return g +} + +func (g *group) Text() string { + return C.GoString(C.groupText(g.id)) +} + +func (g *group) SetText(text string) { + ctext := C.CString(text) + defer C.free(unsafe.Pointer(ctext)) + C.groupSetText(g.id, ctext) +} + +func (g *group) Margined() bool { + return g.margined +} + +func (g *group) SetMargined(margined bool) { + g.margined = margined +} + +func (g *group) xresize(x int, y int, width int, height int, d *sizing) { + // first, chain up to change the GtkFrame and its child container + g.chainresize(x, y, width, height, d) + + // now that the container has the correct size, we can resize the child + a := g.container.allocation(g.margined) + g.child.resize(int(a.x), int(a.y), int(a.width), int(a.height), d) +} diff --git a/newctrl/label_darwin.go b/newctrl/label_darwin.go index 1233735..64a15b1 100644 --- a/newctrl/label_darwin.go +++ b/newctrl/label_darwin.go @@ -22,21 +22,13 @@ func newLabel(text string) Label { } func (l *label) Text() string { - return C.GoString(C.textfieldText(l._id)) + return C.GoString(C.textfieldText(l.id)) } func (l *label) SetText(text string) { ctext := C.CString(text) defer C.free(unsafe.Pointer(ctext)) - C.textfieldSetText(l._id, ctext) -} - -func (l *label) isStandalone() bool { - return l.standalone -} - -func (l *label) id() C.id { - return l._id + C.textfieldSetText(l.id, ctext) } /*TODO diff --git a/newctrl/tab_darwin.go b/newctrl/tab_darwin.go new file mode 100644 index 0000000..8bc1276 --- /dev/null +++ b/newctrl/tab_darwin.go @@ -0,0 +1,53 @@ +// 25 july 2014 + +package ui + +import ( + "unsafe" +) + +// #include "objc_darwin.h" +import "C" + +type tab struct { + *controlSingleObject + tabs []*container + children []Control + chainresize func(x int, y int, width int, height int +} + +func newTab() Tab { + t := &tab{ + controlSingleObject: newControlSingleObject(C.newTab()), + } + t.fpreferredsize = t.xpreferredsize + t.chainresize = t.fresize + t.fresize = t.xresize + return t +} + +func (t *tab) Append(name string, control Control) { + c := newContainer() + t.tabs = append(t.tabs, c) + control.setParent(c.parent()) + t.children = append(t.children, control) + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + C.tabAppend(t.id, cname, c.id) +} + +func (t *tab) xpreferredSize(d *sizing) (width, height int) { + s := C.tabPreferredSize(t.id) + return int(s.width), int(s.height) +} + +func (t *tab) xresize(x int, y int, width int, height int, d *sizing) { + // first, chain up to change the GtkFrame and its child container + t.chainresize(x, y, width, height, d) + + // now that the containers have the correct size, we can resize the children + for i, _ := range t.tabs { + a := t.tabs[i].allocation(false/*TODO*/) + t.children[i].resize(int(a.x), int(a.y), int(a.width), int(a.height), d) + } +} diff --git a/newctrl/table_darwin.go b/newctrl/table_darwin.go new file mode 100644 index 0000000..60a3614 --- /dev/null +++ b/newctrl/table_darwin.go @@ -0,0 +1,137 @@ +// 29 july 2014 + +package ui + +import ( + "fmt" + "reflect" + "unsafe" +) + +// #include "objc_darwin.h" +import "C" + +type table struct { + *tablebase + + *scroller + + images []C.id + selected *event +} + +func finishNewTable(b *tablebase, ty reflect.Type) Table { + id := C.newTable() + t := &table{ + scroller: newScroller(id, true), // border on Table + tablebase: b, + selected: newEvent(), + } + t.fpreferredSize = t.xpreferredSize + // also sets the delegate + C.tableMakeDataSource(t.id, unsafe.Pointer(t)) + for i := 0; i < ty.NumField(); i++ { + cname := C.CString(ty.Field(i).Name) + coltype := C.colTypeText + editable := false + switch { + case ty.Field(i).Type == reflect.TypeOf(ImageIndex(0)): + coltype = C.colTypeImage + case ty.Field(i).Type.Kind() == reflect.Bool: + coltype = C.colTypeCheckbox + editable = true + } + C.tableAppendColumn(t.id, C.intptr_t(i), cname, C.int(coltype), toBOOL(editable)) + C.free(unsafe.Pointer(cname)) // free now (not deferred) to conserve memory + } + return t +} + +func (t *table) Unlock() { + t.unlock() + // there's a possibility that user actions can happen at this point, before the view is updated + // alas, this is something we have to deal with, because Unlock() can be called from any thread + go func() { + Do(func() { + t.RLock() + defer t.RUnlock() + C.tableUpdate(t.id) + }) + }() +} + +func (t *table) LoadImageList(i ImageList) { + i.apply(&t.images) +} + +func (t *table) Selected() int { + t.RLock() + defer t.RUnlock() + return int(C.tableSelected(t.id)) +} + +func (t *table) Select(index int) { + t.RLock() + defer t.RUnlock() + C.tableSelect(t.id, C.intptr_t(index)) +} + +func (t *table) OnSelected(f func()) { + t.selected.set(f) +} + +//export goTableDataSource_getValue +func goTableDataSource_getValue(data unsafe.Pointer, row C.intptr_t, col C.intptr_t, outtype *C.int) unsafe.Pointer { + t := (*table)(data) + t.RLock() + defer t.RUnlock() + d := reflect.Indirect(reflect.ValueOf(t.data)) + datum := d.Index(int(row)).Field(int(col)) + switch { + case datum.Type() == reflect.TypeOf(ImageIndex(0)): + *outtype = C.colTypeImage + d := datum.Interface().(ImageIndex) + return unsafe.Pointer(t.images[d]) + case datum.Kind() == reflect.Bool: + *outtype = C.colTypeCheckbox + if datum.Bool() == true { + // return a non-nil pointer + // outtype isn't Go-side so it'll work + return unsafe.Pointer(outtype) + } + return nil + default: + s := fmt.Sprintf("%v", datum) + return unsafe.Pointer(C.CString(s)) + } +} + +//export goTableDataSource_getRowCount +func goTableDataSource_getRowCount(data unsafe.Pointer) C.intptr_t { + t := (*table)(data) + t.RLock() + defer t.RUnlock() + d := reflect.Indirect(reflect.ValueOf(t.data)) + return C.intptr_t(d.Len()) +} + +//export goTableDataSource_toggled +func goTableDataSource_toggled(data unsafe.Pointer, row C.intptr_t, col C.intptr_t, checked C.BOOL) { + t := (*table)(data) + t.Lock() + defer t.Unlock() + d := reflect.Indirect(reflect.ValueOf(t.data)) + datum := d.Index(int(row)).Field(int(col)) + datum.SetBool(fromBOOL(checked)) +} + +//export tableSelectionChanged +func tableSelectionChanged(data unsafe.Pointer) { + t := (*table)(data) + t.selected.fire() +} + +func (t *table) xpreferredSize(d *sizing) (width, height int) { + s := C.tablePreferredSize(t.id) + return int(s.width), int(s.height) +} diff --git a/newctrl/textfield_darwin.go b/newctrl/textfield_darwin.go index c9ca78b..bf63073 100644 --- a/newctrl/textfield_darwin.go +++ b/newctrl/textfield_darwin.go @@ -21,7 +21,7 @@ func finishNewTextField(id C.id) *textfield { controlSingleObject: newControlSingleObject(id), changed: newEvent(), } - C.textfieldSetDelegate(t._id, unsafe.Pointer(t)) + C.textfieldSetDelegate(t.id, unsafe.Pointer(t)) t.chainpreferredSize = t.fpreferredSize t.fpreferredSize = t.xpreferredSize return t @@ -36,13 +36,13 @@ func newPasswordField() *textfield { } func (t *textfield) Text() string { - return C.GoString(C.textfieldText(t._id)) + return C.GoString(C.textfieldText(t.id)) } func (t *textfield) SetText(text string) { ctext := C.CString(text) defer C.free(unsafe.Pointer(ctext)) - C.textfieldSetText(t._id, ctext) + C.textfieldSetText(t.id, ctext) } func (t *textfield) OnChanged(f func()) { @@ -59,7 +59,7 @@ func (t *textfield) Invalid(reason string) { } creason := C.CString(reason) defer C.free(unsafe.Pointer(creason)) - t.invalid = C.textfieldOpenInvalidPopover(t._id, creason) + t.invalid = C.textfieldOpenInvalidPopover(t.id, creason) } //export textfieldChanged