diff --git a/futureplans.md b/futureplans.md index a75fe02..baab440 100644 --- a/futureplans.md +++ b/futureplans.md @@ -102,6 +102,7 @@ big dumb things: - and raymond suggests GWL_USERDATA here: http://blogs.msdn.com/b/oldnewthing/archive/2005/03/03/384285.aspx - listboxes should have horizontal scrollbars on all platforms; this is way too hard on OS X and doesn't work; my code is in experiments/ - also moved the Windows code there for the sake of efficiency + - GTK+ works just fine though specifics: diff --git a/listbox.go b/listbox.go index 4e26d3d..0601c23 100644 --- a/listbox.go +++ b/listbox.go @@ -9,7 +9,8 @@ import ( // A Listbox is a vertical list of items, of which either at most one or any number of items can be selected at any given time. // On creation, no item is selected. -// Listboxes have both horizontal and vertical scrollbars that are hidden when not needed. +// Listboxes have vertical scrollbars that are hidden when not needed. +// The presence of horizontal scrollbars is currently undefined. type Listbox struct { // TODO Select event diff --git a/prefsize_windows.go b/prefsize_windows.go index 07a633f..850fdb5 100644 --- a/prefsize_windows.go +++ b/prefsize_windows.go @@ -150,127 +150,6 @@ func muldiv(ma int, mb int, div int) int { return int(int32(r1)) } -// List Boxes do not dynamically handle horizontal scrollbars. -// We have to manually handle this ourselves. -// TODO make this run on the main thread when we switch to uitask taking function literals -// TODO this is inefficient; some caching would be nice -func recalcListboxWidth(hwnd _HWND) { - var size _SIZE - - ret := make(chan uiret) - defer close(ret) - uitask <- &uimsg{ - call: _sendMessage, - p: []uintptr{ - uintptr(hwnd), - uintptr(_LB_GETCOUNT), - uintptr(0), - uintptr(0), - }, - ret: ret, - } - r := <-ret - if r.ret == uintptr(_LB_ERR) { // failure - panic(fmt.Errorf("error getting number of items for Listbox width calculations: %v", r.err)) - } - n := int(r.ret) - uitask <- &uimsg{ - call: _getWindowDC, - p: []uintptr{uintptr(hwnd)}, - ret: ret, - } - r = <-ret - if r.ret == 0 { // failure - panic(fmt.Errorf("error getting DC for Listbox width calculations: %v", r.err)) - } - dc := _HANDLE(r.ret) - uitask <- &uimsg{ - call: _selectObject, - p: []uintptr{ - uintptr(dc), - uintptr(controlFont), - }, - ret: ret, - } - r = <-ret - if r.ret == 0 { // failure - panic(fmt.Errorf("error loading control font into device context for Listbox width calculation: %v", r.err)) - } - hextent := uintptr(0) - for i := 0; i < n; i++ { - uitask <- &uimsg{ - call: _sendMessage, - p: []uintptr{ - uintptr(hwnd), - uintptr(_LB_GETTEXTLEN), - uintptr(_WPARAM(i)), - uintptr(0), - }, - ret: ret, - } - r := <-ret - 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(hwnd), - uintptr(_LB_GETTEXT), - uintptr(_WPARAM(i)), - uintptr(_LPARAM(unsafe.Pointer(&str[0]))), - }, - ret: ret, - } - r = <-ret - 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)") - } - // r.ret is still the length of the string; this time without the null terminator - uitask <- &uimsg{ - call: _getTextExtentPoint32, - p: []uintptr{ - uintptr(dc), - uintptr(unsafe.Pointer(&str[0])), - r.ret, - uintptr(unsafe.Pointer(&size)), - }, - ret: ret, - } - r = <-ret - if r.ret == 0 { // failure - panic(fmt.Errorf("error getting width of item %d text for Listbox width calculation: %v", i, r.err)) - } - if hextent < uintptr(size.cx) { - hextent = uintptr(size.cx) - } - } - uitask <- &uimsg{ - call: _releaseDC, - p: []uintptr{ - uintptr(hwnd), - uintptr(dc), - }, - ret: ret, - } - r = <-ret - if r.ret == 0 { // failure - panic(fmt.Errorf("error releasing DC for Listbox width calculations: %v", r.err)) - } - uitask <- &uimsg{ - call: _sendMessage, - p: []uintptr{ - uintptr(hwnd), - uintptr(_LB_SETHORIZONTALEXTENT), - hextent, - uintptr(0), - }, - ret: ret, - } - <-ret -} - type _SIZE struct { cx int32 // originally LONG cy int32 diff --git a/sysdata_windows.go b/sysdata_windows.go index 5fcd742..57f7d58 100644 --- a/sysdata_windows.go +++ b/sysdata_windows.go @@ -87,13 +87,12 @@ var classTypes = [nctypes]*classData{ }, c_listbox: &classData{ name: "LISTBOX", - // TODO also _WS_HSCROLL? // we don't use _LBS_STANDARD because it sorts (and has WS_BORDER; see above) // _LBS_NOINTEGRALHEIGHT gives us exactly the size we want // TODO say why we don't use LBS_MULTISEL (listbox docs and http://msdn.microsoft.com/en-us/library/windows/desktop/aa511485.aspx) - style: _LBS_NOTIFY | _LBS_NOINTEGRALHEIGHT | _WS_HSCROLL | _WS_VSCROLL | controlstyle, + style: _LBS_NOTIFY | _LBS_NOINTEGRALHEIGHT | _WS_VSCROLL | controlstyle, xstyle: _WS_EX_CLIENTEDGE | controlxstyle, - altStyle: _LBS_EXTENDEDSEL | _LBS_NOTIFY | _LBS_NOINTEGRALHEIGHT | _WS_HSCROLL | _WS_VSCROLL | controlstyle, + altStyle: _LBS_EXTENDEDSEL | _LBS_NOTIFY | _LBS_NOINTEGRALHEIGHT | _WS_VSCROLL | controlstyle, appendMsg: _LB_ADDSTRING, insertBeforeMsg: _LB_INSERTSTRING, deleteMsg: _LB_DELETESTRING, @@ -355,7 +354,6 @@ func (s *sysData) append(what string) { } else if r.ret == uintptr(classTypes[s.ctype].selectedIndexErr) { panic(fmt.Errorf("failed to add item to combobox/listbox (last error: %v)", r.err)) } - recalcListboxWidth(s.hwnd) } func (s *sysData) insertBefore(what string, index int) { @@ -377,7 +375,6 @@ func (s *sysData) insertBefore(what string, index int) { } else if r.ret == uintptr(classTypes[s.ctype].selectedIndexErr) { panic(fmt.Errorf("failed to add item to combobox/listbox (last error: %v)", r.err)) } - recalcListboxWidth(s.hwnd) } func (s *sysData) selectedIndex() int { @@ -528,7 +525,6 @@ func (s *sysData) delete(index int) { if r.ret == uintptr(classTypes[s.ctype].selectedIndexErr) { panic(fmt.Errorf("failed to delete item from combobox/listbox (last error: %v)", r.err)) } - recalcListboxWidth(s.hwnd) } func (s *sysData) setIndeterminate() { diff --git a/unmigrated/hscrolllistbox.go b/unmigrated/hscrolllistbox.go new file mode 100644 index 0000000..3975a56 --- /dev/null +++ b/unmigrated/hscrolllistbox.go @@ -0,0 +1,178 @@ +// WINDOWS (works) + + style: _LBS_NOTIFY | _LBS_NOINTEGRALHEIGHT | _WS_HSCROLL | _WS_VSCROLL | controlstyle, + xstyle: _WS_EX_CLIENTEDGE | controlxstyle, + altStyle: _LBS_EXTENDEDSEL | _LBS_NOTIFY | _LBS_NOINTEGRALHEIGHT | _WS_HSCROLL | _WS_VSCROLL | controlstyle, + +(call recalcListboxWidth() from sysData.append(), sysData.insertBefore(), and sysData.delete()) + +// List Boxes do not dynamically handle horizontal scrollbars. +// We have to manually handle this ourselves. +// TODO make this run on the main thread when we switch to uitask taking function literals +// TODO this is inefficient; some caching would be nice +func recalcListboxWidth(hwnd _HWND) { + var size _SIZE + + ret := make(chan uiret) + defer close(ret) + uitask <- &uimsg{ + call: _sendMessage, + p: []uintptr{ + uintptr(hwnd), + uintptr(_LB_GETCOUNT), + uintptr(0), + uintptr(0), + }, + ret: ret, + } + r := <-ret + if r.ret == uintptr(_LB_ERR) { // failure + panic(fmt.Errorf("error getting number of items for Listbox width calculations: %v", r.err)) + } + n := int(r.ret) + uitask <- &uimsg{ + call: _getWindowDC, + p: []uintptr{uintptr(hwnd)}, + ret: ret, + } + r = <-ret + if r.ret == 0 { // failure + panic(fmt.Errorf("error getting DC for Listbox width calculations: %v", r.err)) + } + dc := _HANDLE(r.ret) + uitask <- &uimsg{ + call: _selectObject, + p: []uintptr{ + uintptr(dc), + uintptr(controlFont), + }, + ret: ret, + } + r = <-ret + if r.ret == 0 { // failure + panic(fmt.Errorf("error loading control font into device context for Listbox width calculation: %v", r.err)) + } + hextent := uintptr(0) + for i := 0; i < n; i++ { + uitask <- &uimsg{ + call: _sendMessage, + p: []uintptr{ + uintptr(hwnd), + uintptr(_LB_GETTEXTLEN), + uintptr(_WPARAM(i)), + uintptr(0), + }, + ret: ret, + } + r := <-ret + 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(hwnd), + uintptr(_LB_GETTEXT), + uintptr(_WPARAM(i)), + uintptr(_LPARAM(unsafe.Pointer(&str[0]))), + }, + ret: ret, + } + r = <-ret + 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)") + } + // r.ret is still the length of the string; this time without the null terminator + uitask <- &uimsg{ + call: _getTextExtentPoint32, + p: []uintptr{ + uintptr(dc), + uintptr(unsafe.Pointer(&str[0])), + r.ret, + uintptr(unsafe.Pointer(&size)), + }, + ret: ret, + } + r = <-ret + if r.ret == 0 { // failure + panic(fmt.Errorf("error getting width of item %d text for Listbox width calculation: %v", i, r.err)) + } + if hextent < uintptr(size.cx) { + hextent = uintptr(size.cx) + } + } + uitask <- &uimsg{ + call: _releaseDC, + p: []uintptr{ + uintptr(hwnd), + uintptr(dc), + }, + ret: ret, + } + r = <-ret + if r.ret == 0 { // failure + panic(fmt.Errorf("error releasing DC for Listbox width calculations: %v", r.err)) + } + uitask <- &uimsg{ + call: _sendMessage, + p: []uintptr{ + uintptr(hwnd), + uintptr(_LB_SETHORIZONTALEXTENT), + hextent, + uintptr(0), + }, + ret: ret, + } + <-ret +} + +// DARWIN (does not work) + +// NSTableView is actually in a NSScrollView so we have to get it out first +// NSTableView and NSTableColumn both provide sizeToFit methods, but they don't do what we want (NSTableView's sizes to fit the parent; NSTableColumn's sizes to fit the column header) +// We have to get the width manually; see also http://stackoverflow.com/questions/4674163/nstablecolumn-size-to-fit-contents +// We can use the NSTableView sizeToFit to get the height, though. +// TODO this is inefficient! +// TODO move this to listbox_darwin.go +var ( + _dataCellForRow = sel_getUid("dataCellForRow:") + _cellSize = sel_getUid("cellSize") + _setMinWidth = sel_getUid("setMinWidth:") + _setWidth = sel_getUid("setWidth:") +) + +func listboxPrefSize(control C.id) (width int, height int) { + var maxwidth C.int64_t + + listbox := listboxInScrollView(control) + _, height = controlPrefSize(listbox) + column := listboxTableColumn(listbox) + n := C.objc_msgSend_intret_noargs(listbox, _numberOfRows) + for i := C.intptr_t(0); i < n; i++ { + cell := C.objc_msgSend_int(column, _dataCellForRow, i) + csize := C.objc_msgSend_stret_size_noargs(cell, _cellSize) + if maxwidth < csize.width { + maxwidth = csize.width + } + } + // and in order for horizontal scrolling to work, we need to set the column width to this + C.objc_msgSend_cgfloat(column, _setMinWidth, C.double(maxwidth)) + C.objc_msgSend_cgfloat(column, _setWidth, C.double(maxwidth)) + return int(maxwidth), height +} + +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.objc_msgSend_rect(s.id, _setFrame, + C.int64_t(x), C.int64_t((winheight - y) - height), C.int64_t(width), C.int64_t(height)) + // TODO having this here is a hack; split it into a separate function in listbox_darwin.go + // the NSTableView:NSTableColumn ratio is what determines horizontal scrolling; see http://stackoverflow.com/questions/7050497/enable-scrolling-for-nstableview + if s.ctype == c_listbox { + listbox := listboxInScrollView(s.id) + C.objc_msgSend_rect(listbox, _setFrame, + 0, 0, C.int64_t(width), C.int64_t(height)) + } + return nil +}