diff options
Diffstat (limited to 'prevlib/sysdata_windows.go')
| -rw-r--r-- | prevlib/sysdata_windows.go | 525 |
1 files changed, 525 insertions, 0 deletions
diff --git a/prevlib/sysdata_windows.go b/prevlib/sysdata_windows.go new file mode 100644 index 0000000..1d3541a --- /dev/null +++ b/prevlib/sysdata_windows.go @@ -0,0 +1,525 @@ +// 11 february 2014 + +package ui + +import ( + "fmt" + "sync" + "syscall" + "unsafe" +) + +type sysData struct { + cSysData + + hwnd _HWND + children map[_HMENU]*sysData + nextChildID _HMENU + childrenLock sync.Mutex + isMarquee bool // for sysData.setProgress() + // unlike with GTK+ and Mac OS X, we're responsible for sizing Area properly ourselves + areawidth int + areaheight int + clickCounter clickCounter + lastfocus _HWND +} + +type classData struct { + name *uint16 + style uint32 + xstyle uint32 + altStyle uint32 + storeSysData bool + doNotLoadFont bool + appendMsg uintptr + insertBeforeMsg uintptr + deleteMsg uintptr + selectedIndexMsg uintptr + selectedIndexErr uintptr + addSpaceErr uintptr + lenMsg uintptr +} + +const controlstyle = _WS_CHILD | _WS_VISIBLE | _WS_TABSTOP +const controlxstyle = 0 + +var classTypes = [nctypes]*classData{ + c_window: &classData{ + name: stdWndClass, + style: _WS_OVERLAPPEDWINDOW, + xstyle: 0, + storeSysData: true, + doNotLoadFont: true, + }, + c_button: &classData{ + name: toUTF16("BUTTON"), + style: _BS_PUSHBUTTON | controlstyle, + xstyle: 0 | controlxstyle, + }, + c_checkbox: &classData{ + name: toUTF16("BUTTON"), + // don't use BS_AUTOCHECKBOX because http://blogs.msdn.com/b/oldnewthing/archive/2014/05/22/10527522.aspx + style: _BS_CHECKBOX | controlstyle, + xstyle: 0 | controlxstyle, + }, + c_combobox: &classData{ + name: toUTF16("COMBOBOX"), + style: _CBS_DROPDOWNLIST | _WS_VSCROLL | controlstyle, + xstyle: 0 | controlxstyle, + altStyle: _CBS_DROPDOWN | _CBS_AUTOHSCROLL | _WS_VSCROLL | controlstyle, + appendMsg: _CB_ADDSTRING, + insertBeforeMsg: _CB_INSERTSTRING, + deleteMsg: _CB_DELETESTRING, + selectedIndexMsg: _CB_GETCURSEL, + selectedIndexErr: negConst(_CB_ERR), + addSpaceErr: negConst(_CB_ERRSPACE), + lenMsg: _CB_GETCOUNT, + }, + c_lineedit: &classData{ + name: toUTF16("EDIT"), + // WS_EX_CLIENTEDGE without WS_BORDER will apply visual styles + // thanks to MindChild in irc.efnet.net/#winprog + style: _ES_AUTOHSCROLL | controlstyle, + xstyle: _WS_EX_CLIENTEDGE | controlxstyle, + altStyle: _ES_PASSWORD | _ES_AUTOHSCROLL | controlstyle, + }, + c_label: &classData{ + name: toUTF16("STATIC"), + // SS_NOPREFIX avoids accelerator translation; SS_LEFTNOWORDWRAP clips text past the end + // controls are vertically aligned to the top by default (thanks Xeek in irc.freenode.net/#winapi) + // also note that tab stops are remove dfor labels + style: (_SS_NOPREFIX | _SS_LEFTNOWORDWRAP | controlstyle) &^ _WS_TABSTOP, + xstyle: 0 | controlxstyle, + // MAKE SURE THIS IS THE SAME + altStyle: (_SS_NOPREFIX | _SS_LEFTNOWORDWRAP | controlstyle) &^ _WS_TABSTOP, + }, + c_listbox: &classData{ + name: toUTF16("LISTBOX"), + // we don't use LBS_STANDARD because it sorts (and has WS_BORDER; see above) + // LBS_NOINTEGRALHEIGHT gives us exactly the size we want + // LBS_MULTISEL sounds like it does what we want but it actually doesn't; instead, it toggles item selection regardless of modifier state, which doesn't work like anything else (see http://msdn.microsoft.com/en-us/library/windows/desktop/bb775149%28v=vs.85%29.aspx and http://msdn.microsoft.com/en-us/library/windows/desktop/aa511485.aspx) + style: _LBS_NOTIFY | _LBS_NOINTEGRALHEIGHT | _WS_VSCROLL | controlstyle, + xstyle: _WS_EX_CLIENTEDGE | controlxstyle, + altStyle: _LBS_EXTENDEDSEL | _LBS_NOTIFY | _LBS_NOINTEGRALHEIGHT | _WS_VSCROLL | controlstyle, + appendMsg: _LB_ADDSTRING, + insertBeforeMsg: _LB_INSERTSTRING, + deleteMsg: _LB_DELETESTRING, + selectedIndexMsg: _LB_GETCURSEL, + selectedIndexErr: negConst(_LB_ERR), + addSpaceErr: negConst(_LB_ERRSPACE), + lenMsg: _LB_GETCOUNT, + }, + c_progressbar: &classData{ + name: toUTF16(x_PROGRESS_CLASS), + // note that tab stops are disabled for progress bars + style: (_PBS_SMOOTH | controlstyle) &^ _WS_TABSTOP, + xstyle: 0 | controlxstyle, + doNotLoadFont: true, + }, + c_area: &classData{ + name: areaWndClass, + style: areastyle, + xstyle: areaxstyle, + storeSysData: true, + doNotLoadFont: true, + }, +} + +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 +} + +func (s *sysData) delChild(id _HMENU) { + s.childrenLock.Lock() + defer s.childrenLock.Unlock() + delete(s.children, id) +} + +var ( + _blankString = toUTF16("") + blankString = utf16ToArg(_blankString) +) + +func (s *sysData) make(window *sysData) (err error) { + ct := classTypes[s.ctype] + cid := _HMENU(0) + pwin := uintptr(_NULL) + if window != nil { // this is a child control + cid = window.addChild(s) + pwin = uintptr(window.hwnd) + } + style := uintptr(ct.style) + if s.alternate { + style = uintptr(ct.altStyle) + } + lpParam := uintptr(_NULL) + if ct.storeSysData { + lpParam = uintptr(unsafe.Pointer(s)) + } + r1, _, err := _createWindowEx.Call( + uintptr(ct.xstyle), + utf16ToArg(ct.name), + blankString, // we set the window text later + style, + negConst(_CW_USEDEFAULT), + negConst(_CW_USEDEFAULT), + negConst(_CW_USEDEFAULT), + negConst(_CW_USEDEFAULT), + pwin, + uintptr(cid), + uintptr(hInstance), + lpParam) + if r1 == 0 { // failure + if window != nil { + window.delChild(cid) + } + panic(fmt.Errorf("error actually creating window/control: %v", err)) + } + if !ct.storeSysData { // regular control; store s.hwnd ourselves + s.hwnd = _HWND(r1) + } else if s.hwnd != _HWND(r1) { // we store sysData in storeSysData(); sanity check + panic(fmt.Errorf("hwnd mismatch creating window/control: storeSysData() stored 0x%X but CreateWindowEx() returned 0x%X", s.hwnd, r1)) + } + if !ct.doNotLoadFont { + _sendMessage.Call( + uintptr(s.hwnd), + uintptr(_WM_SETFONT), + uintptr(_WPARAM(controlFont)), + uintptr(_LPARAM(_TRUE))) + } + 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); +func (s *sysData) firstShow() error { + _showWindow.Call( + uintptr(s.hwnd), + uintptr(nCmdShow)) + r1, _, err := _updateWindow.Call(uintptr(s.hwnd)) + if r1 == 0 { // failure + panic(fmt.Errorf("error updating window for the first time: %v", err)) + } + return nil +} + +func (s *sysData) show() { + _showWindow.Call( + uintptr(s.hwnd), + uintptr(_SW_SHOW)) +} + +func (s *sysData) hide() { + _showWindow.Call( + uintptr(s.hwnd), + uintptr(_SW_HIDE)) +} + +func (s *sysData) setText(text string) { + ptext := toUTF16(text) + r1, _, err := _setWindowText.Call( + uintptr(s.hwnd), + utf16ToArg(ptext)) + if r1 == 0 { // failure + panic(fmt.Errorf("error setting window/control text: %v", err)) + } +} + +func (s *sysData) setRect(x int, y int, width int, height int, winheight 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 +} + +func (s *sysData) isChecked() bool { + r1, _, _ := _sendMessage.Call( + uintptr(s.hwnd), + uintptr(_BM_GETCHECK), + uintptr(0), + uintptr(0)) + return r1 == _BST_CHECKED +} + +func (s *sysData) text() (str string) { + var tc []uint16 + + r1, _, _ := _sendMessage.Call( + uintptr(s.hwnd), + uintptr(_WM_GETTEXTLENGTH), + uintptr(0), + uintptr(0)) + length := r1 + 1 // terminating null + tc = make([]uint16, length) + _sendMessage.Call( + uintptr(s.hwnd), + uintptr(_WM_GETTEXT), + uintptr(_WPARAM(length)), + uintptr(_LPARAM(unsafe.Pointer(&tc[0])))) + return syscall.UTF16ToString(tc) +} + +func (s *sysData) append(what string) { + pwhat := toUTF16(what) + r1, _, err := _sendMessage.Call( + uintptr(s.hwnd), + uintptr(classTypes[s.ctype].appendMsg), + uintptr(_WPARAM(0)), + utf16ToLPARAM(pwhat)) + if r1 == uintptr(classTypes[s.ctype].addSpaceErr) { + panic(fmt.Errorf("out of space adding item to combobox/listbox (last error: %v)", err)) + } else if r1 == uintptr(classTypes[s.ctype].selectedIndexErr) { + panic(fmt.Errorf("failed to add item to combobox/listbox (last error: %v)", err)) + } +} + +func (s *sysData) insertBefore(what string, index int) { + pwhat := toUTF16(what) + r1, _, err := _sendMessage.Call( + uintptr(s.hwnd), + uintptr(classTypes[s.ctype].insertBeforeMsg), + uintptr(_WPARAM(index)), + utf16ToLPARAM(pwhat)) + if r1 == uintptr(classTypes[s.ctype].addSpaceErr) { + panic(fmt.Errorf("out of space adding item to combobox/listbox (last error: %v)", err)) + } else if r1 == uintptr(classTypes[s.ctype].selectedIndexErr) { + panic(fmt.Errorf("failed to add item to combobox/listbox (last error: %v)", err)) + } +} + +func (s *sysData) selectedIndex() int { + r1, _, _ := _sendMessage.Call( + uintptr(s.hwnd), + uintptr(classTypes[s.ctype].selectedIndexMsg), + uintptr(_WPARAM(0)), + uintptr(_LPARAM(0))) + if r1 == uintptr(classTypes[s.ctype].selectedIndexErr) { // no selection or manually entered text (apparently, for the latter) + return -1 + } + return int(r1) +} + +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} + } + + r1, _, err := _sendMessage.Call( + uintptr(s.hwnd), + uintptr(_LB_GETSELCOUNT), + uintptr(0), + uintptr(0)) + if r1 == negConst(_LB_ERR) { + panic(fmt.Errorf("error: LB_ERR from LB_GETSELCOUNT in what we know is a multi-selection listbox: %v", err)) + } + if r1 == 0 { // nothing selected + return nil + } + indices := make([]int, r1) + r1, _, err = _sendMessage.Call( + uintptr(s.hwnd), + uintptr(_LB_GETSELITEMS), + uintptr(_WPARAM(r1)), + uintptr(_LPARAM(unsafe.Pointer(&indices[0])))) + if r1 == negConst(_LB_ERR) { + panic(fmt.Errorf("error: LB_ERR from LB_GETSELITEMS in what we know is a multi-selection listbox: %v", err)) + } + return indices +} + +func (s *sysData) selectedTexts() []string { + indices := s.selectedIndices() + strings := make([]string, len(indices)) + for i, v := range indices { + r1, _, err := _sendMessage.Call( + uintptr(s.hwnd), + uintptr(_LB_GETTEXTLEN), + uintptr(_WPARAM(v)), + uintptr(0)) + if r1 == negConst(_LB_ERR) { + panic(fmt.Errorf("error: LB_ERR from LB_GETTEXTLEN in what we know is a valid listbox index (came from LB_GETSELITEMS): %v", err)) + } + str := make([]uint16, r1) + r1, _, err = _sendMessage.Call( + uintptr(s.hwnd), + uintptr(_LB_GETTEXT), + uintptr(_WPARAM(v)), + uintptr(_LPARAM(unsafe.Pointer(&str[0])))) + if r1 == negConst(_LB_ERR) { + panic(fmt.Errorf("error: LB_ERR from LB_GETTEXT in what we know is a valid listbox index (came from LB_GETSELITEMS): %v", err)) + } + strings[i] = syscall.UTF16ToString(str) + } + return strings +} + +func (s *sysData) setWindowSize(width int, height int) error { + var rect _RECT + + r1, _, err := _getClientRect.Call( + uintptr(s.hwnd), + uintptr(unsafe.Pointer(&rect))) + if r1 == 0 { + panic(fmt.Errorf("error getting upper-left of window for resize: %v", err)) + } + // TODO AdjustWindowRect() on the result + // 0 because (0,0) is top-left so no winheight + err = s.setRect(int(rect.left), int(rect.top), width, height, 0) + if err != nil { + panic(fmt.Errorf("error actually resizing window: %v", err)) + } + return nil +} + +func (s *sysData) delete(index int) { + r1, _, err := _sendMessage.Call( + uintptr(s.hwnd), + uintptr(classTypes[s.ctype].deleteMsg), + uintptr(_WPARAM(index)), + uintptr(0)) + if r1 == uintptr(classTypes[s.ctype].selectedIndexErr) { + panic(fmt.Errorf("failed to delete item from combobox/listbox (last error: %v)", err)) + } +} + +func (s *sysData) setIndeterminate() { + r1, _, err := _setWindowLongPtr.Call( + uintptr(s.hwnd), + negConst(_GWL_STYLE), + uintptr(classTypes[s.ctype].style | _PBS_MARQUEE)) + if r1 == 0 { + panic(fmt.Errorf("error setting progress bar style to enter indeterminate mode: %v", err)) + } + _sendMessage.Call( + uintptr(s.hwnd), + uintptr(_PBM_SETMARQUEE), + uintptr(_WPARAM(_TRUE)), + uintptr(0)) + s.isMarquee = true +} + +func (s *sysData) setProgress(percent int) { + if percent == -1 { + s.setIndeterminate() + return + } + if s.isMarquee { + // turn off marquee before switching back + _sendMessage.Call( + uintptr(s.hwnd), + uintptr(_PBM_SETMARQUEE), + uintptr(_WPARAM(_FALSE)), + uintptr(0)) + r1, _, err := _setWindowLongPtr.Call( + uintptr(s.hwnd), + negConst(_GWL_STYLE), + uintptr(classTypes[s.ctype].style)) + if r1 == 0 { + panic(fmt.Errorf("error setting progress bar style to leave indeterminate mode (percent %d): %v", percent, err)) + } + s.isMarquee = false + } + send := func(msg uintptr, n int, l _LPARAM) { + _sendMessage.Call( + uintptr(s.hwnd), + msg, + uintptr(_WPARAM(n)), + uintptr(l)) + } + // Windows 7 has a non-disableable slowly-animating progress bar increment + // there isn't one for decrement, so we'll work around by going one higher and then lower again + // for the case where percent == 100, we need to increase the range temporarily + // sources: http://social.msdn.microsoft.com/Forums/en-US/61350dc7-6584-4c4e-91b0-69d642c03dae/progressbar-disable-smooth-animation http://stackoverflow.com/questions/2217688/windows-7-aero-theme-progress-bar-bug http://discuss.joelonsoftware.com/default.asp?dotnet.12.600456.2 http://stackoverflow.com/questions/22469876/progressbar-lag-when-setting-position-with-pbm-setpos http://stackoverflow.com/questions/6128287/tprogressbar-never-fills-up-all-the-way-seems-to-be-updating-too-fast + if percent == 100 { + send(_PBM_SETRANGE32, 0, 101) + } + send(_PBM_SETPOS, percent+1, 0) + send(_PBM_SETPOS, percent, 0) + if percent == 100 { + send(_PBM_SETRANGE32, 0, 100) + } +} + +func (s *sysData) len() int { + r1, _, err := _sendMessage.Call( + uintptr(s.hwnd), + uintptr(classTypes[s.ctype].lenMsg), + uintptr(_WPARAM(0)), + uintptr(_LPARAM(0))) + if r1 == uintptr(classTypes[s.ctype].selectedIndexErr) { + panic(fmt.Errorf("unexpected error return from sysData.len(); GetLastError() says %v", err)) + } + return int(r1) +} + +func (s *sysData) setAreaSize(width int, height int) { + _sendMessage.Call( + uintptr(s.hwnd), + uintptr(msgSetAreaSize), + uintptr(width), // WPARAM is UINT_PTR on Windows XP and newer at least, so we're good with this + uintptr(height)) +} + +func (s *sysData) repaintAll() { + _sendMessage.Call( + uintptr(s.hwnd), + uintptr(msgRepaintAll), + uintptr(0), + uintptr(0)) +} + +func (s *sysData) center() { + var ws _RECT + + r1, _, err := _getWindowRect.Call( + uintptr(s.hwnd), + uintptr(unsafe.Pointer(&ws))) + if r1 == 0 { + panic(fmt.Errorf("error getting window rect for sysData.center(): %v", err)) + } + // TODO should this be using the monitor functions instead? http://blogs.msdn.com/b/oldnewthing/archive/2005/05/05/414910.aspx + // error returns from GetSystemMetrics() is meaningless because the return value, 0, is still valid + // TODO should this be using the client rect and not the window rect? + dw, _, _ := _getSystemMetrics.Call(uintptr(_SM_CXFULLSCREEN)) + dh, _, _ := _getSystemMetrics.Call(uintptr(_SM_CYFULLSCREEN)) + ww := ws.right - ws.left + wh := ws.bottom - ws.top + wx := (int32(dw) / 2) - (ww / 2) + wy := (int32(dh) / 2) - (wh / 2) + s.setRect(int(wx), int(wy), int(ww), int(wh), 0) +} + +func (s *sysData) setChecked(checked bool) { + c := uintptr(_BST_CHECKED) + if !checked { + c = uintptr(_BST_UNCHECKED) + } + _sendMessage.Call( + uintptr(s.hwnd), + uintptr(_BM_SETCHECK), + c, + uintptr(0)) +} |
