andlabs-ui/sysdata_darwin.go

489 lines
12 KiB
Go

// 1 march 2014
package ui
import (
"fmt"
"sync"
)
// #cgo LDFLAGS: -lobjc -framework Foundation -framework AppKit
// #include "objc_darwin.h"
// #include "sysdata_darwin.h"
import "C"
type sysData struct {
cSysData
id C.id
trackingArea C.id // for Area
}
type classData struct {
make func(parentWindow C.id, alternate bool, s *sysData) C.id
getinside func(scrollview C.id) C.id
show func(what C.id)
hide func(what C.id)
// settext func(what C.id, text string)
settextsel C.SEL
// text func(what C.id) string
textsel C.SEL
// alttextsel func(what C.id) string
alttextsel C.SEL
append func(id C.id, what string, alternate bool)
insertBefore func(id C.id, what string, before int, alternate bool)
selIndex func(id C.id) int
selIndices func(id C.id) []int
selTexts func(id C.id) []string
delete func(id C.id, index int)
len func(id C.id) int
selectIndex func(id C.id, index int, alternate bool)
selectIndices func(id C.id, indices []int)
}
var (
_NSWindow = objc_getClass("NSWindow")
_NSButton = objc_getClass("NSButton")
_NSPopUpButton = objc_getClass("NSPopUpButton")
_NSComboBox = objc_getClass("NSComboBox")
_NSTextField = objc_getClass("NSTextField")
_NSSecureTextField = objc_getClass("NSSecureTextField")
_NSProgressIndicator = objc_getClass("NSProgressIndicator")
_initWithContentRect = sel_getUid("initWithContentRect:styleMask:backing:defer:")
_initWithFrame = sel_getUid("initWithFrame:")
_setDelegate = sel_getUid("setDelegate:")
_makeKeyAndOrderFront = sel_getUid("makeKeyAndOrderFront:")
_orderOut = sel_getUid("orderOut:")
_setHidden = sel_getUid("setHidden:")
_setTitle = sel_getUid("setTitle:")
_setStringValue = sel_getUid("setStringValue:")
_setFrame = sel_getUid("setFrame:")
_state = sel_getUid("state")
_title = sel_getUid("title")
_stringValue = sel_getUid("stringValue")
_frame = sel_getUid("frame")
_setContentSize = sel_getUid("setContentSize:")
_setBezelStyle = sel_getUid("setBezelStyle:")
_setTarget = sel_getUid("setTarget:")
_setAction = sel_getUid("setAction:")
_contentView = sel_getUid("contentView")
_addSubview = sel_getUid("addSubview:")
_setButtonType = sel_getUid("setButtonType:")
_initWithFramePullsDown = sel_getUid("initWithFrame:pullsDown:")
_setUsesDataSource = sel_getUid("setUsesDataSource:")
_addItemWithTitle = sel_getUid("addItemWithTitle:")
_insertItemWithTitleAtIndex = sel_getUid("insertItemWithTitle:atIndex:")
_removeItemAtIndex = sel_getUid("removeItemAtIndex:")
_titleOfSelectedItem = sel_getUid("titleOfSelectedItem")
_indexOfSelectedItem = sel_getUid("indexOfSelectedItem")
_addItemWithObjectValue = sel_getUid("addItemWithObjectValue:")
_insertItemWithObjectValueAtIndex = sel_getUid("insertItemWithObjectValue:atIndex:")
_setEditable = sel_getUid("setEditable:")
_setBordered = sel_getUid("setBordered:")
_setDrawsBackground = sel_getUid("setDrawsBackground:")
_cell = sel_getUid("cell")
_setLineBreakMode = sel_getUid("setLineBreakMode:")
_setStyle = sel_getUid("setStyle:")
_setControlSize = sel_getUid("setControlSize:")
_setIndeterminate = sel_getUid("setIndeterminate:")
_startAnimation = sel_getUid("startAnimation:")
_stopAnimation = sel_getUid("stopAnimation:")
_setDoubleValue = sel_getUid("setDoubleValue:")
_numberOfItems = sel_getUid("numberOfItems")
_selectItemAtIndex = sel_getUid("selectItemAtIndex:")
_deselectItemAtIndex = sel_getUid("deselectItemAtIndex:")
)
// because the only way to make a new NSControl/NSView is with a frame (it gets overridden later)
func initWithDummyFrame(self C.id) C.id {
return C.objc_msgSend_rect(self, _initWithFrame,
C.int64_t(0), C.int64_t(0), C.int64_t(100), C.int64_t(100))
}
func addControl(parentWindow C.id, control C.id) {
C.addControl(parentWindow, control)
}
func controlShow(what C.id) {
C.controlShow(what)
}
func controlHide(what C.id) {
C.controlHide(what)
}
// TODO move to objc_darwin.go unless the only thing that uses it is alternate
func toBOOL(what bool) C.BOOL {
if what {
return C.YES
}
return C.NO
}
// By default some controls do not use the correct font.
// These functions set the appropriate control font.
// Which one is used on each control was determined by comparing https://developer.apple.com/library/mac/documentation/UserExperience/Conceptual/AppleHIGuidelines/Characteristics/Characteristics.html#//apple_ref/doc/uid/TP40002721-SW10 to what Interface Builder says for each control.
// (not applicable to ProgressBar, Area)
// Button, Checkbox, Combobox, LineEdit, Label, Listbox
func applyStandardControlFont(id C.id) {
C.applyStandardControlFont(id)
}
var classTypes = [nctypes]*classData{
c_window: &classData{
make: func(parentWindow C.id, alternate bool, s *sysData) C.id {
return C.makeWindow(appDelegate)
},
show: func(what C.id) {
C.windowShow(what)
},
hide: func(what C.id) {
C.windowHide(what)
},
settextsel: _setTitle,
textsel: _title,
},
c_button: &classData{
make: func(parentWindow C.id, alternate bool, s *sysData) C.id {
button := C.makeButton()
C.buttonSetTargetAction(button, appDelegate)
applyStandardControlFont(button)
addControl(parentWindow, button)
return button
},
show: controlShow,
hide: controlHide,
settextsel: _setTitle,
textsel: _title,
},
c_checkbox: &classData{
make: func(parentWindow C.id, alternate bool, s *sysData) C.id {
checkbox := C.makeCheckbox()
applyStandardControlFont(checkbox)
addControl(parentWindow, checkbox)
return checkbox
},
show: controlShow,
hide: controlHide,
settextsel: _setTitle,
textsel: _title,
},
c_combobox: &classData{
make: func(parentWindow C.id, alternate bool, s *sysData) C.id {
combobox := C.makeCombobox(toBOOL(alternate))
applyStandardControlFont(combobox)
addControl(parentWindow, combobox)
return combobox
},
show: controlShow,
hide: controlHide,
textsel: _titleOfSelectedItem,
alttextsel: _stringValue,
append: func(id C.id, what string, alternate bool) {
C.comboboxAppend(id, toBOOL(alternate), toNSString(what))
},
insertBefore: func(id C.id, what string, before int, alternate bool) {
C.comboboxInsertBefore(id, toBOOL(alternate),
toNSString(what), C.intptr_t(before))
},
selIndex: func(id C.id) int {
return int(C.comboboxSelectedIndex(id))
},
delete: func(id C.id, index int) {
C.comboboxDelete(id, C.intptr_t(index))
},
len: func(id C.id) int {
return int(C.comboboxLen(id))
},
selectIndex: func(id C.id, index int, alternate bool) {
C.comboboxSelectIndex(id, toBOOL(alternate), C.intptr_t(index))
},
},
c_lineedit: &classData{
make: func(parentWindow C.id, alternate bool, s *sysData) C.id {
lineedit := C.makeLineEdit(toBOOL(alternate))
applyStandardControlFont(lineedit)
addControl(parentWindow, lineedit)
return lineedit
},
show: controlShow,
hide: controlHide,
settextsel: _setStringValue,
textsel: _stringValue,
alttextsel: _stringValue,
},
c_label: &classData{
make: func(parentWindow C.id, alternate bool, s *sysData) C.id {
label := C.makeLabel()
applyStandardControlFont(label)
addControl(parentWindow, label)
return label
},
show: controlShow,
hide: controlHide,
settextsel: _setStringValue,
textsel: _stringValue,
},
c_listbox: &classData{
make: makeListbox,
show: controlShow,
hide: controlHide,
append: appendListbox,
insertBefore: insertListboxBefore,
selIndices: selectedListboxIndices,
selTexts: selectedListboxTexts,
delete: deleteListbox,
len: listboxLen,
selectIndices: selectListboxIndices,
},
c_progressbar: &classData{
make: func(parentWindow C.id, alternate bool, s *sysData) C.id {
pbar := C.makeProgressBar()
addControl(parentWindow, pbar)
return pbar
},
show: controlShow,
hide: controlHide,
},
c_area: &classData{
make: makeArea,
getinside: areaInScrollView,
show: controlShow,
hide: controlHide,
},
}
// I need to access sysData from appDelegate, but appDelegate doesn't store any data. So, this.
var (
sysdatas = make(map[C.id]*sysData)
sysdatalock sync.Mutex
)
func addSysData(key C.id, value *sysData) {
sysdatalock.Lock()
sysdatas[key] = value
sysdatalock.Unlock()
}
func getSysData(key C.id) *sysData {
sysdatalock.Lock()
defer sysdatalock.Unlock()
v, ok := sysdatas[key]
if !ok {
panic(fmt.Errorf("internal error: getSysData(%v) unknown", key))
}
return v
}
func (s *sysData) make(window *sysData) error {
var parentWindow C.id
ct := classTypes[s.ctype]
if window != nil {
parentWindow = window.id
}
ret := make(chan C.id)
defer close(ret)
uitask <- func() {
ret <- ct.make(parentWindow, s.alternate, s)
}
s.id = <-ret
if ct.getinside != nil {
uitask <- func() {
ret <- ct.getinside(s.id)
}
addSysData(<-ret, s)
} else {
addSysData(s.id, s)
}
return nil
}
// used for Windows; nothing special needed elsewhere
func (s *sysData) firstShow() error {
s.show()
return nil
}
func (s *sysData) show() {
ret := make(chan struct{})
defer close(ret)
uitask <- func() {
classTypes[s.ctype].show(s.id)
ret <- struct{}{}
}
<-ret
}
func (s *sysData) hide() {
ret := make(chan struct{})
defer close(ret)
uitask <- func() {
classTypes[s.ctype].hide(s.id)
ret <- struct{}{}
}
<-ret
}
func (s *sysData) setText(text string) {
ret := make(chan struct{})
defer close(ret)
uitask <- func() {
C.objc_msgSend_id(s.id, classTypes[s.ctype].settextsel, toNSString(text))
ret <- struct{}{}
}
<-ret
}
func (s *sysData) setRect(x int, y int, width int, height int, winheight int) error {
// winheight - y because (0,0) is the bottom-left corner of the window and not the top-left corner
// (winheight - y) - height because (x, y) is the bottom-left corner of the control and not the top-left
C.setRect(s.id,
C.intptr_t(x), C.intptr_t((winheight - y) - height),
C.intptr_t(width), C.intptr_t(height))
return nil
}
func (s *sysData) isChecked() bool {
ret := make(chan bool)
defer close(ret)
uitask <- func() {
ret <- C.isCheckboxChecked(s.id) != C.NO
}
return <-ret
}
func (s *sysData) text() string {
sel := classTypes[s.ctype].textsel
if s.alternate {
sel = classTypes[s.ctype].alttextsel
}
ret := make(chan string)
defer close(ret)
uitask <- func() {
str := C.objc_msgSend_noargs(s.id, sel)
ret <- fromNSString(str)
}
return <-ret
}
func (s *sysData) append(what string) {
ret := make(chan struct{})
defer close(ret)
uitask <- func() {
classTypes[s.ctype].append(s.id, what, s.alternate)
ret <- struct{}{}
}
<-ret
}
func (s *sysData) insertBefore(what string, before int) {
ret := make(chan struct{})
defer close(ret)
uitask <- func() {
classTypes[s.ctype].insertBefore(s.id, what, before, s.alternate)
ret <- struct{}{}
}
<-ret
}
func (s *sysData) selectedIndex() int {
ret := make(chan int)
defer close(ret)
uitask <- func() {
ret <- classTypes[s.ctype].selIndex(s.id)
}
return <-ret
}
func (s *sysData) selectedIndices() []int {
ret := make(chan []int)
defer close(ret)
uitask <- func() {
ret <- classTypes[s.ctype].selIndices(s.id)
}
return <-ret
}
func (s *sysData) selectedTexts() []string {
ret := make(chan []string)
defer close(ret)
uitask <- func() {
ret <- classTypes[s.ctype].selTexts(s.id)
}
return <-ret
}
func (s *sysData) setWindowSize(width int, height int) error {
ret := make(chan struct{})
defer close(ret)
uitask <- func() {
C.windowSetContentSize(s.id, C.intptr_t(width), C.intptr_t(height))
ret <- struct{}{}
}
<-ret
return nil
}
func (s *sysData) delete(index int) {
ret := make(chan struct{})
defer close(ret)
uitask <- func() {
classTypes[s.ctype].delete(s.id, index)
ret <- struct{}{}
}
<-ret
}
func (s *sysData) setProgress(percent int) {
ret := make(chan struct{})
defer close(ret)
uitask <- func() {
C.setProgress(s.id, C.intptr_t(percent))
ret <- struct{}{}
}
<-ret
}
func (s *sysData) len() int {
ret := make(chan int)
defer close(ret)
uitask <- func() {
ret <- classTypes[s.ctype].len(s.id)
}
return <-ret
}
func (s *sysData) setAreaSize(width int, height int) {
ret := make(chan struct{})
defer close(ret)
uitask <- func() {
C.setAreaSize(s.id, C.intptr_t(width), C.intptr_t(height))
ret <- struct{}{}
}
<-ret
}
func (s *sysData) selectIndex(index int) {
ret := make(chan struct{})
defer close(ret)
uitask <- func() {
classTypes[s.ctype].selectIndex(s.id, index, s.alternate)
ret <- struct{}{}
}
<-ret
}
func (s *sysData) selectIndices(indices []int) {
ret := make(chan struct{})
defer close(ret)
uitask <- func() {
classTypes[s.ctype].selectIndices(s.id, indices)
ret <- struct{}{}
}
<-ret
}