diff --git a/combobox.go b/combobox.go new file mode 100644 index 0000000..08d4605 --- /dev/null +++ b/combobox.go @@ -0,0 +1,63 @@ +// 14 february 2014 +//package ui +package main + +import ( + "sync" +) + +// A Combobox is a drop-down list of items, of which only one can be selected at any given time. You may optionally make the combobox editable to allow custom items. +type Combobox struct { + // TODO Select event + + lock sync.Mutex + created bool + sysData *sysData + initItems []string +} + +// NewCombobox makes a new combobox with the given items. If editable is true, the combobox is editable. +func NewCombobox(editable bool, items ...string) (c *Combobox) { + c = &Combobox{ + sysData: mksysdata(c_combobox), + initItems: items, + } + c.sysData.editable = editable + return c +} + +// TODO Append, InsertBefore, Delete + +// Selection returns the current selection. +func (c *Combobox) Selection() (string, error) { + c.lock.Lock() + defer c.lock.Unlock() + + return c.sysData.text() +} + +// TODO SelectedIndex + +func (c *Combobox) make(window *sysData) (err error) { + c.lock.Lock() + defer c.lock.Unlock() + + err = c.sysData.make("", 300, 300, window) + if err != nil { + return err + } + for _, s := range c.initItems { + err = c.sysData.append(s) + if err != nil { + return err + } + } + return nil +} + +func (c *Combobox) setRect(x int, y int, width int, height int) error { + c.lock.Lock() + defer c.lock.Unlock() + + return c.sysData.setRect(x, y, width, height) +} diff --git a/controls_windows.go b/controls_windows.go index 3a6f472..8a3de3e 100644 --- a/controls_windows.go +++ b/controls_windows.go @@ -82,6 +82,7 @@ const ( _BST_CHECKED = 0x0001 _BST_INDETERMINATE = 0x0002 ) + /* var ( checkDlgButton = user32.NewProc("CheckDlgButton") @@ -119,84 +120,87 @@ func IsDlgButtonChecked(hDlg HWND, nIDButton int) (state uint32, err error) { uintptr(nIDButton)) return uint32(r1), nil } +*/ // Combobox styles. const ( // from winuser.h - CBS_SIMPLE = 0x0001 - CBS_DROPDOWN = 0x0002 - CBS_DROPDOWNLIST = 0x0003 - CBS_OWNERDRAWFIXED = 0x0010 - CBS_OWNERDRAWVARIABLE = 0x0020 - CBS_AUTOHSCROLL = 0x0040 - CBS_OEMCONVERT = 0x0080 - CBS_SORT = 0x0100 - CBS_HASSTRINGS = 0x0200 - CBS_NOINTEGRALHEIGHT = 0x0400 - CBS_DISABLENOSCROLL = 0x0800 - CBS_UPPERCASE = 0x2000 - CBS_LOWERCASE = 0x4000 + _CBS_SIMPLE = 0x0001 + _CBS_DROPDOWN = 0x0002 + _CBS_DROPDOWNLIST = 0x0003 + _CBS_OWNERDRAWFIXED = 0x0010 + _CBS_OWNERDRAWVARIABLE = 0x0020 + _CBS_AUTOHSCROLL = 0x0040 + _CBS_OEMCONVERT = 0x0080 + _CBS_SORT = 0x0100 + _CBS_HASSTRINGS = 0x0200 + _CBS_NOINTEGRALHEIGHT = 0x0400 + _CBS_DISABLENOSCROLL = 0x0800 + _CBS_UPPERCASE = 0x2000 + _CBS_LOWERCASE = 0x4000 ) // Combobox messages. // TODO filter out messages not provided in windows 2000 const ( // from winuser.h - CB_GETEDITSEL = 0x0140 - CB_LIMITTEXT = 0x0141 - CB_SETEDITSEL = 0x0142 - CB_ADDSTRING = 0x0143 - CB_DELETESTRING = 0x0144 - CB_DIR = 0x0145 - CB_GETCOUNT = 0x0146 - CB_GETCURSEL = 0x0147 - CB_GETLBTEXT = 0x0148 - CB_GETLBTEXTLEN = 0x0149 - CB_INSERTSTRING = 0x014A - CB_RESETCONTENT = 0x014B - CB_FINDSTRING = 0x014C - CB_SELECTSTRING = 0x014D - CB_SETCURSEL = 0x014E - CB_SHOWDROPDOWN = 0x014F - CB_GETITEMDATA = 0x0150 - CB_SETITEMDATA = 0x0151 - CB_GETDROPPEDCONTROLRECT = 0x0152 - CB_SETITEMHEIGHT = 0x0153 - CB_GETITEMHEIGHT = 0x0154 - CB_SETEXTENDEDUI = 0x0155 - CB_GETEXTENDEDUI = 0x0156 - CB_GETDROPPEDSTATE = 0x0157 - CB_FINDSTRINGEXACT = 0x0158 - CB_SETLOCALE = 0x0159 - CB_GETLOCALE = 0x015A - CB_GETTOPINDEX = 0x015B - CB_SETTOPINDEX = 0x015C - CB_GETHORIZONTALEXTENT = 0x015D - CB_SETHORIZONTALEXTENT = 0x015E - CB_GETDROPPEDWIDTH = 0x015F - CB_SETDROPPEDWIDTH = 0x0160 - CB_INITSTORAGE = 0x0161 - CB_MULTIPLEADDSTRING = 0x0163 - CB_GETCOMBOBOXINFO = 0x0164 + _CB_GETEDITSEL = 0x0140 + _CB_LIMITTEXT = 0x0141 + _CB_SETEDITSEL = 0x0142 + _CB_ADDSTRING = 0x0143 + _CB_DELETESTRING = 0x0144 + _CB_DIR = 0x0145 + _CB_GETCOUNT = 0x0146 + _CB_GETCURSEL = 0x0147 + _CB_GETLBTEXT = 0x0148 + _CB_GETLBTEXTLEN = 0x0149 + _CB_INSERTSTRING = 0x014A + _CB_RESETCONTENT = 0x014B + _CB_FINDSTRING = 0x014C + _CB_SELECTSTRING = 0x014D + _CB_SETCURSEL = 0x014E + _CB_SHOWDROPDOWN = 0x014F + _CB_GETITEMDATA = 0x0150 + _CB_SETITEMDATA = 0x0151 + _CB_GETDROPPEDCONTROLRECT = 0x0152 + _CB_SETITEMHEIGHT = 0x0153 + _CB_GETITEMHEIGHT = 0x0154 + _CB_SETEXTENDEDUI = 0x0155 + _CB_GETEXTENDEDUI = 0x0156 + _CB_GETDROPPEDSTATE = 0x0157 + _CB_FINDSTRINGEXACT = 0x0158 + _CB_SETLOCALE = 0x0159 + _CB_GETLOCALE = 0x015A + _CB_GETTOPINDEX = 0x015B + _CB_SETTOPINDEX = 0x015C + _CB_GETHORIZONTALEXTENT = 0x015D + _CB_SETHORIZONTALEXTENT = 0x015E + _CB_GETDROPPEDWIDTH = 0x015F + _CB_SETDROPPEDWIDTH = 0x0160 + _CB_INITSTORAGE = 0x0161 + _CB_MULTIPLEADDSTRING = 0x0163 + _CB_GETCOMBOBOXINFO = 0x0164 ) // Combobox WM_COMMAND notificaitons. // TODO filter out notifications not provided in windows 2000 const ( // from winuser.h - CBN_ERRSPACE = (-1) // TODO this will blow up the Go compiler if it's used - CBN_SELCHANGE = 1 - CBN_DBLCLK = 2 - CBN_SETFOCUS = 3 - CBN_KILLFOCUS = 4 - CBN_EDITCHANGE = 5 - CBN_EDITUPDATE = 6 - CBN_DROPDOWN = 7 - CBN_CLOSEUP = 8 - CBN_SELENDOK = 9 - CBN_SELENDCANCEL = 10 + // TODO get _CB_ERR out + _CBN_ERRSPACE = (-1) // TODO this will blow up the Go compiler if it's used + _CBN_SELCHANGE = 1 + _CBN_DBLCLK = 2 + _CBN_SETFOCUS = 3 + _CBN_KILLFOCUS = 4 + _CBN_EDITCHANGE = 5 + _CBN_EDITUPDATE = 6 + _CBN_DROPDOWN = 7 + _CBN_CLOSEUP = 8 + _CBN_SELENDOK = 9 + _CBN_SELENDCANCEL = 10 ) +/* // Edit control styles. const ( // from winuser.h diff --git a/main.go b/main.go index beb2f0e..81e48fa 100644 --- a/main.go +++ b/main.go @@ -10,7 +10,9 @@ func main() { w.Closing = make(chan struct{}) b := NewButton("Click Me") c := NewCheckbox("Check Me") - s := NewStack(Vertical, b, c) + cb1 := NewCombobox(true, "You can edit me!", "Yes you can!", "Yes you will!") + cb2 := NewCombobox(false, "You can't edit me!", "No you can't!", "No you won't!") + s := NewStack(Vertical, b, c, cb1, cb2) err := w.Open(s) if err != nil { panic(err) @@ -22,7 +24,15 @@ mainloop: case <-w.Closing: break mainloop case <-b.Clicked: - err := w.SetTitle(fmt.Sprintf("Check State: %v", c.Checked())) + cs1, err := cb1.Selection() + if err != nil { + panic(err) + } + cs2, err := cb2.Selection() + if err != nil { + panic(err) + } + err = w.SetTitle(fmt.Sprintf("%v | %s | %s", c.Checked(), cs1, cs2)) if err != nil { panic(err) } diff --git a/sysdata.go b/sysdata.go index eda749e..1f46ac9 100644 --- a/sysdata.go +++ b/sysdata.go @@ -10,6 +10,7 @@ type cSysData struct { ctype int event chan struct{} resize func(x int, y int, width int, height int) error + editable bool // for Combobox } func (c *cSysData) make(initText string, initWidth int, initHeight int, window *sysData) error { panic(runtime.GOOS + " sysData does not define make()") @@ -29,11 +30,19 @@ func (c *cSysData) setRect(x int, y int, width int, height int) error { func (c *cSysData) isChecked() (bool, error) { panic(runtime.GOOS + " sysData does not define isChecked()") } +func (c *cSysData) text() (string, error) { + panic(runtime.GOOS + " sysData does not define text()") +} +func (c *cSysData) append(string) error { + panic(runtime.GOOS + " sysData does not define append()") +} +// TODO insertAfter const ( c_window = iota c_button c_checkbox + c_combobox nctypes ) diff --git a/sysdata_windows.go b/sysdata_windows.go index b62d90a..07a387d 100644 --- a/sysdata_windows.go +++ b/sysdata_windows.go @@ -19,29 +19,42 @@ type sysData struct { } type classData struct { - name string - style uint32 - xstyle uint32 - mkid bool + name string + style uint32 + xstyle uint32 + mkid bool + editStyle uint32 + appendMsg uintptr + insertAfterMsg uintptr + deleteMsg uintptr } const controlstyle = _WS_CHILD | _WS_VISIBLE | _WS_TABSTOP const controlxstyle = 0 var classTypes = [nctypes]*classData{ - c_window: &classData{ - style: _WS_OVERLAPPEDWINDOW, - xstyle: 0, + c_window: &classData{ + style: _WS_OVERLAPPEDWINDOW, + xstyle: 0, }, c_button: &classData{ - name: "BUTTON", - style: _BS_PUSHBUTTON | controlstyle, - xstyle: 0 | controlxstyle, + name: "BUTTON", + style: _BS_PUSHBUTTON | controlstyle, + xstyle: 0 | controlxstyle, }, c_checkbox: &classData{ - name: "BUTTON", - style: _BS_AUTOCHECKBOX | controlstyle, - xstyle: 0 | controlxstyle, + name: "BUTTON", + style: _BS_AUTOCHECKBOX | controlstyle, + xstyle: 0 | controlxstyle, + }, + c_combobox: &classData{ + name: "COMBOBOX", + style: _CBS_DROPDOWNLIST | controlstyle, + xstyle: 0 | controlxstyle, + editStyle: _CBS_DROPDOWN | _CBS_AUTOHSCROLL | controlstyle, + appendMsg: _CB_ADDSTRING, + insertAfterMsg: _CB_INSERTSTRING, + deleteMsg: _CB_DELETESTRING, }, } @@ -80,13 +93,17 @@ func (s *sysData) make(initText string, initWidth int, initHeight int, window *s } classname = n } + style := uintptr(ct.style) + if s.editable { + style = uintptr(ct.editStyle) + } uitask <- &uimsg{ call: _createWindowEx, p: []uintptr{ uintptr(ct.xstyle), uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(classname))), uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(initText))), - uintptr(ct.style), + style, uintptr(_CW_USEDEFAULT), // TODO uintptr(_CW_USEDEFAULT), uintptr(initWidth), @@ -219,3 +236,58 @@ func (s *sysData) isChecked() (bool, error) { r := <-ret return r.ret == _BST_CHECKED, nil } + +// TODO adorn error messages with which part failed +func (s *sysData) text() (str string, err error) { + var tc []uint16 + + ret := make(chan uiret) + defer close(ret) + // TODO figure out how to handle errors + 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) + // TODO figure out how to handle errors + uitask <- &uimsg{ + call: _sendMessage, + p: []uintptr{ + uintptr(s.hwnd), + uintptr(_WM_GETTEXT), + uintptr(_WPARAM(length)), + uintptr(_LPARAM(unsafe.Pointer(&tc[0]))), + }, + ret: ret, + } + <-ret + // TODO check character count + return syscall.UTF16ToString(tc), nil +} + +// TODO figure out how to handle errors +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, + } + <-ret + // TODO error handling + return nil +} diff --git a/todo.md b/todo.md index 6cc362d..f50e4d7 100644 --- a/todo.md +++ b/todo.md @@ -3,6 +3,7 @@ so I don't forget: - Control.Show()/Control.Hide() - Control.SetText() - Groupbox +- determine if a selection in a non-editable combobox has been made super ultra important things: - the windows build appears to be unstable: