andlabs-ui/sysdata_windows.go

526 lines
12 KiB
Go
Raw Normal View History

// 11 february 2014
package ui
import (
"fmt"
"syscall"
"unsafe"
2014-02-12 09:35:15 -06:00
"sync"
)
type sysData struct {
cSysData
hwnd _HWND
children map[_HMENU]*sysData
nextChildID _HMENU
childrenLock sync.Mutex
shownAlready bool
}
type classData struct {
2014-02-14 11:16:27 -06:00
name string
style uint32
xstyle uint32
mkid bool
altStyle uint32
font *_HANDLE
2014-02-14 11:16:27 -06:00
appendMsg uintptr
insertBeforeMsg uintptr
2014-02-14 11:16:27 -06:00
deleteMsg uintptr
selectedIndexMsg uintptr
selectedIndexErr int
2014-02-15 13:03:46 -06:00
addSpaceErr int
}
2014-02-12 10:29:20 -06:00
const controlstyle = _WS_CHILD | _WS_VISIBLE | _WS_TABSTOP
const controlxstyle = 0
var classTypes = [nctypes]*classData{
c_window: &classData{
2014-02-14 11:16:27 -06:00
style: _WS_OVERLAPPEDWINDOW,
xstyle: 0,
},
c_button: &classData{
2014-02-14 11:16:27 -06:00
name: "BUTTON",
style: _BS_PUSHBUTTON | controlstyle,
xstyle: 0 | controlxstyle,
font: &controlFont,
2014-02-12 10:29:20 -06:00
},
c_checkbox: &classData{
2014-02-14 11:16:27 -06:00
name: "BUTTON",
style: _BS_AUTOCHECKBOX | controlstyle,
xstyle: 0 | controlxstyle,
font: &controlFont,
2014-02-14 11:16:27 -06:00
},
c_combobox: &classData{
2014-02-14 11:16:27 -06:00
name: "COMBOBOX",
style: _CBS_DROPDOWNLIST | _WS_VSCROLL | controlstyle,
2014-02-14 11:16:27 -06:00
xstyle: 0 | controlxstyle,
altStyle: _CBS_DROPDOWN | _CBS_AUTOHSCROLL | _WS_VSCROLL | controlstyle,
font: &controlFont,
2014-02-14 11:16:27 -06:00
appendMsg: _CB_ADDSTRING,
2014-02-15 11:06:29 -06:00
insertBeforeMsg: _CB_INSERTSTRING,
2014-02-14 11:16:27 -06:00
deleteMsg: _CB_DELETESTRING,
selectedIndexMsg: _CB_GETCURSEL,
selectedIndexErr: _CB_ERR,
2014-02-15 13:03:46 -06:00
addSpaceErr: _CB_ERRSPACE,
},
c_lineedit: &classData{
2014-02-14 14:00:59 -06:00
name: "EDIT",
style: _ES_AUTOHSCROLL | _WS_BORDER | controlstyle,
xstyle: 0 | controlxstyle,
altStyle: _ES_PASSWORD | _ES_AUTOHSCROLL | _WS_BORDER | controlstyle,
font: &controlFont,
2014-02-14 14:00:59 -06:00
},
c_label: &classData{
2014-02-14 14:12:03 -06:00
name: "STATIC",
style: _SS_NOPREFIX | controlstyle,
xstyle: 0 | controlxstyle,
font: &controlFont,
2014-02-14 14:12:03 -06:00
},
c_listbox: &classData{
name: "LISTBOX",
2014-02-14 21:10:35 -06:00
// TODO also _WS_HSCROLL?
style: _WS_VSCROLL | controlstyle,
xstyle: 0 | controlxstyle,
2014-02-14 21:10:35 -06:00
altStyle: _LBS_EXTENDEDSEL | _WS_VSCROLL | controlstyle,
font: &controlFont,
appendMsg: _LB_ADDSTRING,
2014-02-15 11:06:29 -06:00
insertBeforeMsg: _LB_INSERTSTRING,
deleteMsg: _LB_DELETESTRING,
selectedIndexMsg: _LB_GETCURSEL,
selectedIndexErr: _LB_ERR,
2014-02-15 13:03:46 -06:00
addSpaceErr: _LB_ERRSPACE,
},
c_progressbar: &classData{
name: _PROGRESS_CLASS,
style: _PBS_SMOOTH | controlstyle,
xstyle: 0 | controlxstyle,
},
}
func (s *sysData) addChild(child *sysData) _HMENU {
s.childrenLock.Lock()
defer s.childrenLock.Unlock()
s.nextChildID++ // start at 1
if s.children == nil {
s.children = map[_HMENU]*sysData{}
}
s.children[s.nextChildID] = child
return s.nextChildID
}
2014-02-12 09:35:15 -06:00
func (s *sysData) delChild(id _HMENU) {
s.childrenLock.Lock()
defer s.childrenLock.Unlock()
delete(s.children, id)
2014-02-12 09:35:15 -06:00
}
func (s *sysData) make(initText string, window *sysData) (err error) {
ret := make(chan uiret)
defer close(ret)
ct := classTypes[s.ctype]
classname := ct.name
cid := _HMENU(0)
2014-02-12 10:29:20 -06:00
pwin := uintptr(_NULL)
if window != nil { // this is a child control
cid = window.addChild(s)
pwin = uintptr(window.hwnd)
} else { // need a new class
n, err := registerStdWndClass(s)
if err != nil {
return fmt.Errorf("error creating window class for new window: %v", err)
}
classname = n
2014-02-12 09:35:15 -06:00
}
2014-02-14 11:16:27 -06:00
style := uintptr(ct.style)
if s.alternate {
style = uintptr(ct.altStyle)
2014-02-14 11:16:27 -06:00
}
uitask <- &uimsg{
call: _createWindowEx,
p: []uintptr{
uintptr(ct.xstyle),
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(classname))),
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(initText))),
2014-02-14 11:16:27 -06:00
style,
uintptr(_CW_USEDEFAULT),
uintptr(_CW_USEDEFAULT),
uintptr(_CW_USEDEFAULT),
uintptr(_CW_USEDEFAULT),
2014-02-12 10:29:20 -06:00
pwin,
uintptr(cid),
uintptr(hInstance),
uintptr(_NULL),
},
ret: ret,
}
r := <-ret
if r.ret == 0 { // failure
if window != nil {
window.delChild(cid)
}
return fmt.Errorf("error actually creating window/control: %v", r.err)
}
s.hwnd = _HWND(r.ret)
if ct.font != nil {
uitask <- &uimsg{
call: _sendMessage,
p: []uintptr{
uintptr(s.hwnd),
uintptr(_WM_SETFONT),
uintptr(_WPARAM(*ct.font)),
uintptr(_LPARAM(_TRUE)),
},
ret: ret,
}
<-ret
}
return nil
}
var (
_updateWindow = user32.NewProc("UpdateWindow")
)
// if the object is a window, we need to do the following the first time
// ShowWindow(hwnd, nCmdShow);
// UpdateWindow(hwnd);
// otherwise we go ahead and show the object normally with SW_SHOW
func (s *sysData) show() (err error) {
if s.ctype != c_window { // don't do the init ShowWindow/UpdateWindow chain on non-windows
s.shownAlready = true
}
show := uintptr(_SW_SHOW)
if !s.shownAlready {
show = uintptr(nCmdShow)
}
ret := make(chan uiret)
defer close(ret)
// TODO figure out how to handle error
uitask <- &uimsg{
call: _showWindow,
p: []uintptr{uintptr(s.hwnd), show},
ret: ret,
}
<-ret
if !s.shownAlready {
uitask <- &uimsg{
call: _updateWindow,
p: []uintptr{uintptr(s.hwnd)},
ret: ret,
}
r := <-ret
if r.ret == 0 { // failure
return fmt.Errorf("error updating window for the first time: %v", r.err)
}
s.shownAlready = true
}
return nil
}
func (s *sysData) hide() (err error) {
ret := make(chan uiret)
defer close(ret)
// TODO figure out how to handle error
uitask <- &uimsg{
call: _showWindow,
p: []uintptr{uintptr(s.hwnd), _SW_HIDE},
ret: ret,
}
<-ret
return nil
}
func (s *sysData) setText(text string) error {
ret := make(chan uiret)
defer close(ret)
uitask <- &uimsg{
call: _setWindowText,
p: []uintptr{
uintptr(s.hwnd),
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(text))),
},
ret: ret,
}
r := <-ret
if r.ret == 0 { // failure
return r.err
}
return nil
}
func (s *sysData) setRect(x int, y int, width int, height int) error {
r1, _, err := _moveWindow.Call(
uintptr(s.hwnd),
uintptr(x),
uintptr(y),
uintptr(width),
uintptr(height),
uintptr(_TRUE))
if r1 == 0 { // failure
return fmt.Errorf("error setting window/control rect: %v", err)
}
return nil
}
2014-02-13 14:14:10 -06:00
func (s *sysData) isChecked() bool {
2014-02-13 14:14:10 -06:00
ret := make(chan uiret)
defer close(ret)
uitask <- &uimsg{
call: _sendMessage,
p: []uintptr{
uintptr(s.hwnd),
uintptr(_BM_GETCHECK),
uintptr(0),
uintptr(0),
},
ret: ret,
}
r := <-ret
return r.ret == _BST_CHECKED
2014-02-13 14:14:10 -06:00
}
2014-02-14 11:16:27 -06:00
func (s *sysData) text() (str string) {
2014-02-14 11:16:27 -06:00
var tc []uint16
ret := make(chan uiret)
defer close(ret)
uitask <- &uimsg{
call: _sendMessage,
p: []uintptr{
uintptr(s.hwnd),
uintptr(_WM_GETTEXTLENGTH),
uintptr(0),
uintptr(0),
},
ret: ret,
}
r := <-ret
length := r.ret + 1 // terminating null
tc = make([]uint16, length)
uitask <- &uimsg{
call: _sendMessage,
p: []uintptr{
uintptr(s.hwnd),
uintptr(_WM_GETTEXT),
uintptr(_WPARAM(length)),
uintptr(_LPARAM(unsafe.Pointer(&tc[0]))),
},
ret: ret,
}
<-ret
return syscall.UTF16ToString(tc)
2014-02-14 11:16:27 -06:00
}
func (s *sysData) append(what string) (err error) {
ret := make(chan uiret)
defer close(ret)
uitask <- &uimsg{
call: _sendMessage,
p: []uintptr{
uintptr(s.hwnd),
uintptr(classTypes[s.ctype].appendMsg),
uintptr(_WPARAM(0)),
uintptr(_LPARAM(unsafe.Pointer(syscall.StringToUTF16Ptr(what)))),
},
ret: ret,
}
2014-02-15 13:03:46 -06:00
r := <-ret
if r.ret == uintptr(classTypes[s.ctype].addSpaceErr) {
return fmt.Errorf("out of space adding item to combobox/listbox (last error: %v)", r.err)
} else if r.ret == uintptr(classTypes[s.ctype].selectedIndexErr) {
return fmt.Errorf("failed to add item to combobox/listbox (last error: %v)", r.err)
}
2014-02-14 11:16:27 -06:00
return nil
}
2014-02-15 11:06:29 -06:00
func (s *sysData) insertBefore(what string, index int) (err error) {
ret := make(chan uiret)
defer close(ret)
uitask <- &uimsg{
call: _sendMessage,
p: []uintptr{
uintptr(s.hwnd),
uintptr(classTypes[s.ctype].insertBeforeMsg),
uintptr(_WPARAM(index)),
uintptr(_LPARAM(unsafe.Pointer(syscall.StringToUTF16Ptr(what)))),
},
ret: ret,
}
2014-02-15 13:03:46 -06:00
r := <-ret
if r.ret == uintptr(classTypes[s.ctype].addSpaceErr) {
return fmt.Errorf("out of space adding item to combobox/listbox (last error: %v)", r.err)
} else if r.ret == uintptr(classTypes[s.ctype].selectedIndexErr) {
return fmt.Errorf("failed to add item to combobox/listbox (last error: %v)", r.err)
}
2014-02-15 11:06:29 -06:00
return nil
}
2014-02-15 13:03:46 -06:00
func (s *sysData) selectedIndex() int {
ret := make(chan uiret)
defer close(ret)
uitask <- &uimsg{
call: _sendMessage,
p: []uintptr{
uintptr(s.hwnd),
uintptr(classTypes[s.ctype].selectedIndexMsg),
uintptr(_WPARAM(0)),
uintptr(_LPARAM(0)),
},
ret: ret,
}
r := <-ret
2014-02-15 17:27:34 -06:00
if r.ret == uintptr(classTypes[s.ctype].selectedIndexErr) { // no selection or manually entered text (apparently, for the latter)
2014-02-15 13:03:46 -06:00
return -1
}
2014-02-15 13:03:46 -06:00
return int(r.ret)
}
2014-02-15 13:03:46 -06:00
func (s *sysData) selectedIndices() []int {
if !s.alternate { // single-selection list box; use single-selection method
index := s.selectedIndex()
if index == -1 {
return nil
}
return []int{index}
}
ret := make(chan uiret)
defer close(ret)
uitask <- &uimsg{
call: _sendMessage,
p: []uintptr{
uintptr(s.hwnd),
uintptr(_LB_GETSELCOUNT),
uintptr(0),
uintptr(0),
},
ret: ret,
}
r := <-ret
2014-02-15 13:03:46 -06:00
if r.ret == uintptr(_LB_ERR) {
panic("UI library internal error: LB_ERR from LB_GETSELCOUNT in what we know is a multi-selection listbox")
}
if r.ret == 0 { // nothing selected
return nil
}
indices := make([]int, r.ret)
uitask <- &uimsg{
call: _sendMessage,
p: []uintptr{
uintptr(s.hwnd),
uintptr(_LB_GETSELITEMS),
uintptr(_WPARAM(r.ret)),
uintptr(_LPARAM(unsafe.Pointer(&indices[0]))),
},
ret: ret,
}
2014-02-15 13:03:46 -06:00
r = <-ret
if r.ret == uintptr(_LB_ERR) {
panic("UI library internal error: LB_ERR from LB_GETSELITEMS in what we know is a multi-selection listbox")
}
return indices
}
2014-02-15 13:03:46 -06:00
func (s *sysData) selectedTexts() []string {
indices := s.selectedIndices()
ret := make(chan uiret)
defer close(ret)
strings := make([]string, len(indices))
for i, v := range indices {
uitask <- &uimsg{
call: _sendMessage,
p: []uintptr{
uintptr(s.hwnd),
uintptr(_LB_GETTEXTLEN),
uintptr(_WPARAM(v)),
uintptr(0),
},
ret: ret,
}
r := <-ret
2014-02-15 13:03:46 -06:00
if r.ret == uintptr(_LB_ERR) {
panic("UI library internal error: LB_ERR from LB_GETTEXTLEN in what we know is a valid listbox index (came from LB_GETSELITEMS")
}
str := make([]uint16, r.ret)
uitask <- &uimsg{
call: _sendMessage,
p: []uintptr{
uintptr(s.hwnd),
uintptr(_LB_GETTEXT),
uintptr(_WPARAM(v)),
uintptr(_LPARAM(unsafe.Pointer(&str[0]))),
},
ret: ret,
}
<-ret
2014-02-15 13:03:46 -06:00
if r.ret == uintptr(_LB_ERR) {
panic("UI library internal error: LB_ERR from LB_GETTEXT in what we know is a valid listbox index (came from LB_GETSELITEMS")
}
strings[i] = syscall.UTF16ToString(str)
}
2014-02-15 13:03:46 -06:00
return strings
}
func (s *sysData) setWindowSize(width int, height int) error {
var rect _RECT
ret := make(chan uiret)
defer close(ret)
uitask <- &uimsg{
call: _getClientRect,
p: []uintptr{
uintptr(s.hwnd),
uintptr(unsafe.Pointer(&rect)),
},
ret: ret,
}
r := <-ret
if r.ret == 0 {
return fmt.Errorf("error getting upper-left of window for resize: %v", r.err)
}
err := s.setRect(int(rect.Left), int(rect.Top), width, height)
if err != nil {
return fmt.Errorf("error actually resizing window: %v", err)
}
return nil
}
func (s *sysData) delete(index int) (err error) {
ret := make(chan uiret)
defer close(ret)
uitask <- &uimsg{
call: _sendMessage,
p: []uintptr{
uintptr(s.hwnd),
uintptr(classTypes[s.ctype].deleteMsg),
uintptr(_WPARAM(index)),
uintptr(0),
},
ret: ret,
}
r := <-ret
if r.ret == uintptr(classTypes[s.ctype].selectedIndexErr) {
return fmt.Errorf("failed to delete item from combobox/listbox (last error: %v)", r.err)
}
return nil
}
func (s *sysData) setProgress(percent int) {
ret := make(chan uiret)
defer close(ret)
uitask <- &uimsg{
call: _sendMessage,
p: []uintptr{
uintptr(s.hwnd),
uintptr(_PBM_SETPOS),
uintptr(_WPARAM(percent)),
uintptr(0),
},
ret: ret,
}
<-ret
}