From d018953d7ef1b276cc3229e04ba6fc75018c888a Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sat, 2 Aug 2014 22:35:58 -0400 Subject: Split all the Control implementations into their own files and renamed the containerctrls implementation files to say tab instead as they only hold Tab. This is the first part of what should hopefully be the final restructuring. --- redo/basicctrls_darwin.go | 156 --------------------------- redo/basicctrls_unix.go | 186 --------------------------------- redo/basicctrls_windows.go | 232 ----------------------------------------- redo/button_darwin.go | 52 +++++++++ redo/button_unix.go | 63 +++++++++++ redo/button_windows.go | 77 ++++++++++++++ redo/checkbox_darwin.go | 27 +++++ redo/checkbox_unix.go | 48 +++++++++ redo/checkbox_windows.go | 59 +++++++++++ redo/containerctrls_darwin.go | 50 --------- redo/containerctrls_darwin.m | 46 -------- redo/containerctrls_unix.go | 45 -------- redo/containerctrls_windows.c | 82 --------------- redo/containerctrls_windows.go | 110 ------------------- redo/label_darwin.go | 61 +++++++++++ redo/label_unix.go | 71 +++++++++++++ redo/label_windows.go | 68 ++++++++++++ redo/objc_darwin.h | 2 +- redo/tab_darwin.go | 50 +++++++++ redo/tab_darwin.m | 46 ++++++++ redo/tab_unix.go | 45 ++++++++ redo/tab_windows.c | 82 +++++++++++++++ redo/tab_windows.go | 110 +++++++++++++++++++ redo/textfield_darwin.go | 38 +++++++ redo/textfield_unix.go | 47 +++++++++ redo/textfield_windows.go | 50 +++++++++ 26 files changed, 995 insertions(+), 908 deletions(-) delete mode 100644 redo/basicctrls_darwin.go delete mode 100644 redo/basicctrls_unix.go delete mode 100644 redo/basicctrls_windows.go create mode 100644 redo/button_darwin.go create mode 100644 redo/button_unix.go create mode 100644 redo/button_windows.go create mode 100644 redo/checkbox_darwin.go create mode 100644 redo/checkbox_unix.go create mode 100644 redo/checkbox_windows.go delete mode 100644 redo/containerctrls_darwin.go delete mode 100644 redo/containerctrls_darwin.m delete mode 100644 redo/containerctrls_unix.go delete mode 100644 redo/containerctrls_windows.c delete mode 100644 redo/containerctrls_windows.go create mode 100644 redo/label_darwin.go create mode 100644 redo/label_unix.go create mode 100644 redo/label_windows.go create mode 100644 redo/tab_darwin.go create mode 100644 redo/tab_darwin.m create mode 100644 redo/tab_unix.go create mode 100644 redo/tab_windows.c create mode 100644 redo/tab_windows.go create mode 100644 redo/textfield_darwin.go create mode 100644 redo/textfield_unix.go create mode 100644 redo/textfield_windows.go (limited to 'redo') diff --git a/redo/basicctrls_darwin.go b/redo/basicctrls_darwin.go deleted file mode 100644 index 13470f5..0000000 --- a/redo/basicctrls_darwin.go +++ /dev/null @@ -1,156 +0,0 @@ -// 16 july 2014 - -package ui - -import ( - "unsafe" -) - -// #include "objc_darwin.h" -import "C" - -type button struct { - *controlbase - clicked *event -} - -func finishNewButton(id C.id, text string) *button { - ctext := C.CString(text) - defer C.free(unsafe.Pointer(ctext)) - b := &button{ - controlbase: newControl(id), - clicked: newEvent(), - } - C.buttonSetText(b.id, ctext) - C.buttonSetDelegate(b.id, unsafe.Pointer(b)) - return b -} - -func newButton(text string) *button { - return finishNewButton(C.newButton(), text) -} - -func (b *button) OnClicked(e func()) { - b.clicked.set(e) -} - -//export buttonClicked -func buttonClicked(xb unsafe.Pointer) { - b := (*button)(unsafe.Pointer(xb)) - b.clicked.fire() - println("button clicked") -} - -func (b *button) Text() string { - return C.GoString(C.buttonText(b.id)) -} - -func (b *button) SetText(text string) { - ctext := C.CString(text) - defer C.free(unsafe.Pointer(ctext)) - C.buttonSetText(b.id, ctext) -} - -type checkbox struct { - *button -} - -func newCheckbox(text string) *checkbox { - return &checkbox{ - button: finishNewButton(C.newCheckbox(), text), - } -} - -// we don't need to define our own event here; we can just reuse Button's -// (it's all target-action anyway) - -func (c *checkbox) Checked() bool { - return fromBOOL(C.checkboxChecked(c.id)) -} - -func (c *checkbox) SetChecked(checked bool) { - C.checkboxSetChecked(c.id, toBOOL(checked)) -} - -type textField struct { - *controlbase -} - -func finishNewTextField(id C.id) *textField { - return &textField{ - controlbase: newControl(id), - } -} - -func newTextField() *textField { - return finishNewTextField(C.newTextField()) -} - -func newPasswordField() *textField { - return finishNewTextField(C.newPasswordField()) -} - -func (t *textField) Text() string { - return C.GoString(C.textFieldText(t.id)) -} - -func (t *textField) SetText(text string) { - ctext := C.CString(text) - defer C.free(unsafe.Pointer(ctext)) - C.textFieldSetText(t.id, ctext) -} - -// cheap trick -type label struct { - *textField - standalone bool - supercommitResize func(c *allocation, d *sizing) -} - -func finishNewLabel(text string, standalone bool) *label { - l := &label{ - textField: finishNewTextField(C.newLabel()), - standalone: standalone, - } - l.SetText(text) - l.supercommitResize = l.fcommitResize - l.fcommitResize = l.labelcommitResize - return l -} - -func newLabel(text string) Label { - return finishNewLabel(text, false) -} - -func newStandaloneLabel(text string) Label { - return finishNewLabel(text, true) -} - -func (l *label) labelcommitResize(c *allocation, d *sizing) { - if !l.standalone && c.neighbor != nil { - c.neighbor.getAuxResizeInfo(d) - if d.neighborAlign.baseline != 0 { // no adjustment needed if the given control has no baseline - // in order for the baseline value to be correct, the label MUST BE AT THE HEIGHT THAT OS X WANTS IT TO BE! - // otherwise, the baseline calculation will be relative to the bottom of the control, and everything will be wrong - origsize := C.controlPrefSize(l.id) - c.height = int(origsize.height) - newrect := C.struct_xrect{ - x: C.intptr_t(c.x), - y: C.intptr_t(c.y), - width: C.intptr_t(c.width), - height: C.intptr_t(c.height), - } - ourAlign := C.alignmentInfo(l.id, newrect) - // we need to find the exact Y positions of the baselines - // fortunately, this is easy now that (x,y) is the bottom-left corner - thisbasey := ourAlign.rect.y + ourAlign.baseline - neighborbasey := d.neighborAlign.rect.y + d.neighborAlign.baseline - // now the amount we have to move the label down by is easy to find - yoff := neighborbasey - thisbasey - // and we just add that - c.y += int(yoff) - } - // TODO if there's no baseline, the alignment should be to the top /of the alignment rect/, not the frame - } - l.supercommitResize(c, d) -} diff --git a/redo/basicctrls_unix.go b/redo/basicctrls_unix.go deleted file mode 100644 index 734388c..0000000 --- a/redo/basicctrls_unix.go +++ /dev/null @@ -1,186 +0,0 @@ -// +build !windows,!darwin - -// 7 july 2014 - -package ui - -import ( - "unsafe" -) - -// #include "gtk_unix.h" -// extern void buttonClicked(GtkButton *, gpointer); -// extern void checkboxToggled(GtkToggleButton *, gpointer); -import "C" - -// TODOs: -// - standalone label on its own: should it be centered or not? - -type button struct { - *controlbase - button *C.GtkButton - clicked *event -} - -// shared code for setting up buttons, check boxes, etc. -func finishNewButton(widget *C.GtkWidget, event string, handler unsafe.Pointer) *button { - b := &button{ - controlbase: newControl(widget), - button: (*C.GtkButton)(unsafe.Pointer(widget)), - clicked: newEvent(), - } - g_signal_connect( - C.gpointer(unsafe.Pointer(b.button)), - event, - C.GCallback(handler), - C.gpointer(unsafe.Pointer(b))) - return b -} - -func newButton(text string) *button { - ctext := togstr(text) - defer freegstr(ctext) - widget := C.gtk_button_new_with_label(ctext) - return finishNewButton(widget, "clicked", C.buttonClicked) -} - -func (b *button) OnClicked(e func()) { - b.clicked.set(e) -} - -//export buttonClicked -func buttonClicked(bwid *C.GtkButton, data C.gpointer) { - b := (*button)(unsafe.Pointer(data)) - b.clicked.fire() - println("button clicked") -} - -func (b *button) Text() string { - return fromgstr(C.gtk_button_get_label(b.button)) -} - -func (b *button) SetText(text string) { - ctext := togstr(text) - defer freegstr(ctext) - C.gtk_button_set_label(b.button, ctext) -} - -type checkbox struct { - // embed button so its methods and events carry over - *button - toggle *C.GtkToggleButton - checkbox *C.GtkCheckButton -} - -func newCheckbox(text string) *checkbox { - ctext := togstr(text) - defer freegstr(ctext) - widget := C.gtk_check_button_new_with_label(ctext) - return &checkbox{ - button: finishNewButton(widget, "toggled", C.checkboxToggled), - toggle: (*C.GtkToggleButton)(unsafe.Pointer(widget)), - checkbox: (*C.GtkCheckButton)(unsafe.Pointer(widget)), - } -} - -//export checkboxToggled -func checkboxToggled(bwid *C.GtkToggleButton, data C.gpointer) { - // note that the finishNewButton() call uses the embedded *button as data - // this is fine because we're only deferring to buttonClicked() anyway - buttonClicked(nil, data) -} - -func (c *checkbox) Checked() bool { - return fromgbool(C.gtk_toggle_button_get_active(c.toggle)) -} - -func (c *checkbox) SetChecked(checked bool) { - C.gtk_toggle_button_set_active(c.toggle, togbool(checked)) -} - -type textField struct { - *controlbase - entry *C.GtkEntry -} - -func startNewTextField() *textField { - w := C.gtk_entry_new() - return &textField{ - controlbase: newControl(w), - entry: (*C.GtkEntry)(unsafe.Pointer(w)), - } -} - -func newTextField() *textField { - return startNewTextField() -} - -func newPasswordField() *textField { - t := startNewTextField() - C.gtk_entry_set_visibility(t.entry, C.FALSE) - return t -} - -func (t *textField) Text() string { - return fromgstr(C.gtk_entry_get_text(t.entry)) -} - -func (t *textField) SetText(text string) { - ctext := togstr(text) - defer freegstr(ctext) - C.gtk_entry_set_text(t.entry, ctext) -} - -type label struct { - *controlbase - misc *C.GtkMisc - label *C.GtkLabel - standalone bool - supercommitResize func(c *allocation, d *sizing) -} - -func finishNewLabel(text string, standalone bool) *label { - ctext := togstr(text) - defer freegstr(ctext) - widget := C.gtk_label_new(ctext) - l := &label{ - controlbase: newControl(widget), - misc: (*C.GtkMisc)(unsafe.Pointer(widget)), - label: (*C.GtkLabel)(unsafe.Pointer(widget)), - standalone: standalone, - } - l.supercommitResize = l.fcommitResize - l.fcommitResize = l.labelcommitResize - return l -} - -func newLabel(text string) Label { - return finishNewLabel(text, false) -} - -func newStandaloneLabel(text string) Label { - return finishNewLabel(text, true) -} - -func (l *label) Text() string { - return fromgstr(C.gtk_label_get_text(l.label)) -} - -func (l *label) SetText(text string) { - ctext := togstr(text) - defer freegstr(ctext) - C.gtk_label_set_text(l.label, ctext) -} - -func (l *label) labelcommitResize(c *allocation, d *sizing) { - if !l.standalone && c.neighbor != nil { - c.neighbor.getAuxResizeInfo(d) - if d.shouldVAlignTop { - // don't bother aligning it to the first line of text in the control; this is harder than it's worth (thanks gregier in irc.gimp.net/#gtk+) - C.gtk_misc_set_alignment(l.misc, 0, 0) - } else { - C.gtk_misc_set_alignment(l.misc, 0, 0.5) - } - } - l.supercommitResize(c, d) -} diff --git a/redo/basicctrls_windows.go b/redo/basicctrls_windows.go deleted file mode 100644 index 9f5fe54..0000000 --- a/redo/basicctrls_windows.go +++ /dev/null @@ -1,232 +0,0 @@ -// 15 july 2014 - -package ui - -import ( - "unsafe" -) - -// #include "winapi_windows.h" -import "C" - -type button struct { - *controlbase - clicked *event -} - -var buttonclass = toUTF16("BUTTON") - -func startNewButton(text string, style C.DWORD) *button { - c := newControl(buttonclass, - style | C.WS_TABSTOP, - 0) - c.setText(text) - C.controlSetControlFont(c.hwnd) - b := &button{ - controlbase: c, - clicked: newEvent(), - } - return b -} - -func newButton(text string) *button { - b := startNewButton(text, C.BS_PUSHBUTTON) - C.setButtonSubclass(b.hwnd, unsafe.Pointer(b)) - b.fpreferredSize = b.buttonpreferredSize - return b -} - -func (b *button) OnClicked(e func()) { - b.clicked.set(e) -} - -func (b *button) Text() string { - return b.text() -} - -func (b *button) SetText(text string) { - b.setText(text) -} - -//export buttonClicked -func buttonClicked(data unsafe.Pointer) { - b := (*button)(data) - b.clicked.fire() - println("button clicked") -} - -const ( - // from http://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing - buttonHeight = 14 -) - -func (b *button) buttonpreferredSize(d *sizing) (width, height int) { - // common controls 6 thankfully provides a method to grab this... - var size C.SIZE - - size.cx = 0 // explicitly ask for ideal size - size.cy = 0 - if C.SendMessageW(b.hwnd, C.BCM_GETIDEALSIZE, 0, C.LPARAM(uintptr(unsafe.Pointer(&size)))) != C.FALSE { - return int(size.cx), int(size.cy) - } - // that failed, fall back -println("message failed; falling back") - // don't worry about the error return from GetSystemMetrics(); there's no way to tell (explicitly documented as such) - xmargins := 2 * int(C.GetSystemMetrics(C.SM_CXEDGE)) - return xmargins + int(b.textlen), fromdlgunitsY(buttonHeight, d) -} - -type checkbox struct { - *button -} - -func newCheckbox(text string) *checkbox { - c := &checkbox{ - // don't use BS_AUTOCHECKBOX here because it creates problems when refocusing (see http://blogs.msdn.com/b/oldnewthing/archive/2014/05/22/10527522.aspx) - // we'll handle actually toggling the check state ourselves (see controls_windows.c) - button: startNewButton(text, C.BS_CHECKBOX), - } - c.fpreferredSize = c.checkboxpreferredSize - C.setCheckboxSubclass(c.hwnd, unsafe.Pointer(c)) - return c -} - -func (c *checkbox) Checked() bool { - if C.checkboxChecked(c.hwnd) == C.FALSE { - return false - } - return true -} - -func (c *checkbox) SetChecked(checked bool) { - if checked { - C.checkboxSetChecked(c.hwnd, C.TRUE) - return - } - C.checkboxSetChecked(c.hwnd, C.FALSE) -} - -//export checkboxToggled -func checkboxToggled(data unsafe.Pointer) { - c := (*checkbox)(data) - c.clicked.fire() - println("checkbox toggled") -} - -const ( - // from http://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing - checkboxHeight = 10 - // from http://msdn.microsoft.com/en-us/library/windows/desktop/bb226818%28v=vs.85%29.aspx - checkboxXFromLeftOfBoxToLeftOfLabel = 12 -) - -func (c *checkbox) checkboxpreferredSize(d *sizing) (width, height int) { - return fromdlgunitsX(checkboxXFromLeftOfBoxToLeftOfLabel, d) + int(c.textlen), - fromdlgunitsY(checkboxHeight, d) -} - -type textField struct { - *controlbase -} - -var editclass = toUTF16("EDIT") - -func startNewTextField(style C.DWORD) *textField { - c := newControl(editclass, - style | C.ES_AUTOHSCROLL | C.ES_LEFT | C.ES_NOHIDESEL | C.WS_TABSTOP, - C.WS_EX_CLIENTEDGE) // WS_EX_CLIENTEDGE without WS_BORDER will show the canonical visual styles border (thanks to MindChild in irc.efnet.net/#winprog) - C.controlSetControlFont(c.hwnd) - t := &textField{ - controlbase: c, - } - t.fpreferredSize = t.textfieldpreferredSize - return t -} - -func newTextField() *textField { - return startNewTextField(0) -} - -func newPasswordField() *textField { - return startNewTextField(C.ES_PASSWORD) -} - -func (t *textField) Text() string { - return t.text() -} - -func (t *textField) SetText(text string) { - t.setText(text) -} - -const ( - // from http://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing - textfieldWidth = 107 // this is actually the shorter progress bar width, but Microsoft only indicates as wide as necessary - textfieldHeight = 14 -) - -func (t *textField) textfieldpreferredSize(d *sizing) (width, height int) { - return fromdlgunitsX(textfieldWidth, d), fromdlgunitsY(textfieldHeight, d) -} - -type label struct { - *controlbase - standalone bool - supercommitResize func(c *allocation, d *sizing) -} - -var labelclass = toUTF16("STATIC") - -func finishNewLabel(text string, standalone bool) *label { - c := newControl(labelclass, - // 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) - C.SS_NOPREFIX | C.SS_LEFTNOWORDWRAP, - 0) - c.setText(text) - C.controlSetControlFont(c.hwnd) - l := &label{ - controlbase: c, - standalone: standalone, - } - l.fpreferredSize = l.labelpreferredSize - l.supercommitResize = l.fcommitResize - l.fcommitResize = l.labelcommitResize - return l -} - -func newLabel(text string) Label { - return finishNewLabel(text, false) -} - -func newStandaloneLabel(text string) Label { - return finishNewLabel(text, true) -} - -func (l *label) Text() string { - return l.text() -} - -func (l *label) SetText(text string) { - l.setText(text) -} - -const ( - // via http://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing - labelHeight = 8 - labelYOffset = 3 - // TODO the label is offset slightly by default... -) - -func (l *label) labelpreferredSize(d *sizing) (width, height int) { - return int(l.textlen), fromdlgunitsY(labelHeight, d) -} - -func (l *label) labelcommitResize(c *allocation, d *sizing) { - if !l.standalone { - yoff := fromdlgunitsY(labelYOffset, d) - c.y += yoff - c.height -= yoff - } - l.supercommitResize(c, d) -} diff --git a/redo/button_darwin.go b/redo/button_darwin.go new file mode 100644 index 0000000..e5a7f5b --- /dev/null +++ b/redo/button_darwin.go @@ -0,0 +1,52 @@ +// 16 july 2014 + +package ui + +import ( + "unsafe" +) + +// #include "objc_darwin.h" +import "C" + +type button struct { + *controlbase + clicked *event +} + +func finishNewButton(id C.id, text string) *button { + ctext := C.CString(text) + defer C.free(unsafe.Pointer(ctext)) + b := &button{ + controlbase: newControl(id), + clicked: newEvent(), + } + C.buttonSetText(b.id, ctext) + C.buttonSetDelegate(b.id, unsafe.Pointer(b)) + return b +} + +func newButton(text string) *button { + return finishNewButton(C.newButton(), text) +} + +func (b *button) OnClicked(e func()) { + b.clicked.set(e) +} + +//export buttonClicked +func buttonClicked(xb unsafe.Pointer) { + b := (*button)(unsafe.Pointer(xb)) + b.clicked.fire() + println("button clicked") +} + +func (b *button) Text() string { + return C.GoString(C.buttonText(b.id)) +} + +func (b *button) SetText(text string) { + ctext := C.CString(text) + defer C.free(unsafe.Pointer(ctext)) + C.buttonSetText(b.id, ctext) +} diff --git a/redo/button_unix.go b/redo/button_unix.go new file mode 100644 index 0000000..9811bda --- /dev/null +++ b/redo/button_unix.go @@ -0,0 +1,63 @@ +// +build !windows,!darwin + +// 7 july 2014 + +package ui + +import ( + "unsafe" +) + +// #include "gtk_unix.h" +// extern void buttonClicked(GtkButton *, gpointer); +// extern void checkboxToggled(GtkToggleButton *, gpointer); +import "C" + +type button struct { + *controlbase + button *C.GtkButton + clicked *event +} + +// shared code for setting up buttons, check boxes, etc. +func finishNewButton(widget *C.GtkWidget, event string, handler unsafe.Pointer) *button { + b := &button{ + controlbase: newControl(widget), + button: (*C.GtkButton)(unsafe.Pointer(widget)), + clicked: newEvent(), + } + g_signal_connect( + C.gpointer(unsafe.Pointer(b.button)), + event, + C.GCallback(handler), + C.gpointer(unsafe.Pointer(b))) + return b +} + +func newButton(text string) *button { + ctext := togstr(text) + defer freegstr(ctext) + widget := C.gtk_button_new_with_label(ctext) + return finishNewButton(widget, "clicked", C.buttonClicked) +} + +func (b *button) OnClicked(e func()) { + b.clicked.set(e) +} + +//export buttonClicked +func buttonClicked(bwid *C.GtkButton, data C.gpointer) { + b := (*button)(unsafe.Pointer(data)) + b.clicked.fire() + println("button clicked") +} + +func (b *button) Text() string { + return fromgstr(C.gtk_button_get_label(b.button)) +} + +func (b *button) SetText(text string) { + ctext := togstr(text) + defer freegstr(ctext) + C.gtk_button_set_label(b.button, ctext) +} diff --git a/redo/button_windows.go b/redo/button_windows.go new file mode 100644 index 0000000..aa7a60a --- /dev/null +++ b/redo/button_windows.go @@ -0,0 +1,77 @@ +// 15 july 2014 + +package ui + +import ( + "unsafe" +) + +// #include "winapi_windows.h" +import "C" + +type button struct { + *controlbase + clicked *event +} + +var buttonclass = toUTF16("BUTTON") + +func startNewButton(text string, style C.DWORD) *button { + c := newControl(buttonclass, + style | C.WS_TABSTOP, + 0) + c.setText(text) + C.controlSetControlFont(c.hwnd) + b := &button{ + controlbase: c, + clicked: newEvent(), + } + return b +} + +func newButton(text string) *button { + b := startNewButton(text, C.BS_PUSHBUTTON) + C.setButtonSubclass(b.hwnd, unsafe.Pointer(b)) + b.fpreferredSize = b.buttonpreferredSize + return b +} + +func (b *button) OnClicked(e func()) { + b.clicked.set(e) +} + +func (b *button) Text() string { + return b.text() +} + +func (b *button) SetText(text string) { + b.setText(text) +} + +//export buttonClicked +func buttonClicked(data unsafe.Pointer) { + b := (*button)(data) + b.clicked.fire() + println("button clicked") +} + +const ( + // from http://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing + buttonHeight = 14 +) + +func (b *button) buttonpreferredSize(d *sizing) (width, height int) { + // common controls 6 thankfully provides a method to grab this... + var size C.SIZE + + size.cx = 0 // explicitly ask for ideal size + size.cy = 0 + if C.SendMessageW(b.hwnd, C.BCM_GETIDEALSIZE, 0, C.LPARAM(uintptr(unsafe.Pointer(&size)))) != C.FALSE { + return int(size.cx), int(size.cy) + } + // that failed, fall back +println("message failed; falling back") + // don't worry about the error return from GetSystemMetrics(); there's no way to tell (explicitly documented as such) + xmargins := 2 * int(C.GetSystemMetrics(C.SM_CXEDGE)) + return xmargins + int(b.textlen), fromdlgunitsY(buttonHeight, d) +} diff --git a/redo/checkbox_darwin.go b/redo/checkbox_darwin.go new file mode 100644 index 0000000..ee7e88c --- /dev/null +++ b/redo/checkbox_darwin.go @@ -0,0 +1,27 @@ +// 16 july 2014 + +package ui + +// #include "objc_darwin.h" +import "C" + +type checkbox struct { + *button +} + +func newCheckbox(text string) *checkbox { + return &checkbox{ + button: finishNewButton(C.newCheckbox(), text), + } +} + +// we don't need to define our own event here; we can just reuse Button's +// (it's all target-action anyway) + +func (c *checkbox) Checked() bool { + return fromBOOL(C.checkboxChecked(c.id)) +} + +func (c *checkbox) SetChecked(checked bool) { + C.checkboxSetChecked(c.id, toBOOL(checked)) +} diff --git a/redo/checkbox_unix.go b/redo/checkbox_unix.go new file mode 100644 index 0000000..45048d1 --- /dev/null +++ b/redo/checkbox_unix.go @@ -0,0 +1,48 @@ +// +build !windows,!darwin + +// 7 july 2014 + +package ui + +import ( + "unsafe" +) + +// #include "gtk_unix.h" +// extern void buttonClicked(GtkButton *, gpointer); +// extern void checkboxToggled(GtkToggleButton *, gpointer); +import "C" + +type checkbox struct { + // embed button so its methods and events carry over + *button + toggle *C.GtkToggleButton + checkbox *C.GtkCheckButton +} + +func newCheckbox(text string) *checkbox { + ctext := togstr(text) + defer freegstr(ctext) + widget := C.gtk_check_button_new_with_label(ctext) + return &checkbox{ + button: finishNewButton(widget, "toggled", C.checkboxToggled), + toggle: (*C.GtkToggleButton)(unsafe.Pointer(widget)), + checkbox: (*C.GtkCheckButton)(unsafe.Pointer(widget)), + } +} + +//export checkboxToggled +func checkboxToggled(bwid *C.GtkToggleButton, data C.gpointer) { + // note that the finishNewButton() call uses the embedded *button as data + // this is fine because we're only deferring to buttonClicked() anyway + buttonClicked(nil, data) +} + +func (c *checkbox) Checked() bool { + return fromgbool(C.gtk_toggle_button_get_active(c.toggle)) +} + +func (c *checkbox) SetChecked(checked bool) { + C.gtk_toggle_button_set_active(c.toggle, togbool(checked)) +} + diff --git a/redo/checkbox_windows.go b/redo/checkbox_windows.go new file mode 100644 index 0000000..633f8e7 --- /dev/null +++ b/redo/checkbox_windows.go @@ -0,0 +1,59 @@ +// 15 july 2014 + +package ui + +import ( + "unsafe" +) + +// #include "winapi_windows.h" +import "C" + +type checkbox struct { + *button +} + +func newCheckbox(text string) *checkbox { + c := &checkbox{ + // don't use BS_AUTOCHECKBOX here because it creates problems when refocusing (see http://blogs.msdn.com/b/oldnewthing/archive/2014/05/22/10527522.aspx) + // we'll handle actually toggling the check state ourselves (see controls_windows.c) + button: startNewButton(text, C.BS_CHECKBOX), + } + c.fpreferredSize = c.checkboxpreferredSize + C.setCheckboxSubclass(c.hwnd, unsafe.Pointer(c)) + return c +} + +func (c *checkbox) Checked() bool { + if C.checkboxChecked(c.hwnd) == C.FALSE { + return false + } + return true +} + +func (c *checkbox) SetChecked(checked bool) { + if checked { + C.checkboxSetChecked(c.hwnd, C.TRUE) + return + } + C.checkboxSetChecked(c.hwnd, C.FALSE) +} + +//export checkboxToggled +func checkboxToggled(data unsafe.Pointer) { + c := (*checkbox)(data) + c.clicked.fire() + println("checkbox toggled") +} + +const ( + // from http://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing + checkboxHeight = 10 + // from http://msdn.microsoft.com/en-us/library/windows/desktop/bb226818%28v=vs.85%29.aspx + checkboxXFromLeftOfBoxToLeftOfLabel = 12 +) + +func (c *checkbox) checkboxpreferredSize(d *sizing) (width, height int) { + return fromdlgunitsX(checkboxXFromLeftOfBoxToLeftOfLabel, d) + int(c.textlen), + fromdlgunitsY(checkboxHeight, d) +} diff --git a/redo/containerctrls_darwin.go b/redo/containerctrls_darwin.go deleted file mode 100644 index ef834ad..0000000 --- a/redo/containerctrls_darwin.go +++ /dev/null @@ -1,50 +0,0 @@ -// 25 july 2014 - -package ui - -import ( - "unsafe" -) - -// #include "objc_darwin.h" -import "C" - -type tab struct { - *controlbase - - tabs []*sizer -} - -func newTab() Tab { - t := new(tab) - id := C.newTab(unsafe.Pointer(t)) - t.controlbase = newControl(id) - t.fpreferredSize = t.tabpreferredSize - return t -} - -func (t *tab) Append(name string, control Control) { - s := new(sizer) - t.tabs = append(t.tabs, s) - cname := C.CString(name) - defer C.free(unsafe.Pointer(cname)) - tabview := C.tabAppend(t.id, cname) - s.child = control - s.child.setParent(&controlParent{tabview}) -} - -func (t *tab) tabpreferredSize(d *sizing) (width, height int) { - s := C.tabPrefSize(t.id) - return int(s.width), int(s.height) -} - -// no need to override Control.commitResize() as only prepared the tabbed control; its children will be reallocated when that one is resized - -//export tabResized -func tabResized(data unsafe.Pointer, width C.intptr_t, height C.intptr_t) { - t := (*tab)(unsafe.Pointer(data)) - for _, s := range t.tabs { - // the tab area's coordinate system is localized, so the origin is (0, 0) - s.resize(0, 0, int(width), int(height)) - } -} diff --git a/redo/containerctrls_darwin.m b/redo/containerctrls_darwin.m deleted file mode 100644 index c99e275..0000000 --- a/redo/containerctrls_darwin.m +++ /dev/null @@ -1,46 +0,0 @@ -// 25 july 2014 - -#import "objc_darwin.h" -#import "_cgo_export.h" -#import - -#define toNSTabView(x) ((NSTabView *) (x)) - -@interface goTabView : NSTabView { -@public - void *gotab; -} -@end - -@implementation goTabView - -- (void)setFrame:(NSRect)r -{ - NSRect content; - - [super setFrame:r]; - content = [self contentRect]; - tabResized(self->gotab, (intptr_t) content.size.width, (intptr_t) content.size.height); -} - -@end - -id newTab(void *gotab) -{ - goTabView *t; - - t = [[goTabView alloc] initWithFrame:NSMakeRect(0, 0, 100, 100)]; - setStandardControlFont((id) t); // safe; same selector provided by NSTabView - t->gotab = gotab; - return (id) t; -} - -id tabAppend(id t, char *name) -{ - NSTabViewItem *i; - - i = [[NSTabViewItem alloc] initWithIdentifier:nil]; - [i setLabel:[NSString stringWithUTF8String:name]]; - [toNSTabView(t) addTabViewItem:i]; - return (id) [i view]; -} diff --git a/redo/containerctrls_unix.go b/redo/containerctrls_unix.go deleted file mode 100644 index 60978ea..0000000 --- a/redo/containerctrls_unix.go +++ /dev/null @@ -1,45 +0,0 @@ -// +build !windows,!darwin - -// 25 july 2014 - -package ui - -import ( - "unsafe" -) - -// #include "gtk_unix.h" -import "C" - -type tab struct { - *controlbase - notebook *C.GtkNotebook - - tabs []*layout -} - -func newTab() Tab { - widget := C.gtk_notebook_new() - t := &tab{ - controlbase: newControl(widget), - notebook: (*C.GtkNotebook)(unsafe.Pointer(widget)), - } - // there are no scrolling arrows by default; add them in case there are too many tabs - C.gtk_notebook_set_scrollable(t.notebook, C.TRUE) - return t -} - -func (t *tab) Append(name string, control Control) { - tl := newLayout(control) - t.tabs = append(t.tabs, tl) - cname := togstr(name) - defer freegstr(cname) - tab := C.gtk_notebook_append_page(t.notebook, - tl.layoutwidget, - C.gtk_label_new(cname)) - if tab == -1 { - panic("gtk_notebook_append_page() failed") - } -} - -// no need to override Control.commitResize() as only prepared the tabbed control; its children will be reallocated when that one is resized diff --git a/redo/containerctrls_windows.c b/redo/containerctrls_windows.c deleted file mode 100644 index 5a998b5..0000000 --- a/redo/containerctrls_windows.c +++ /dev/null @@ -1,82 +0,0 @@ -/* 25 july 2014 */ - -#include "winapi_windows.h" -#include "_cgo_export.h" - -/* provided for cgo's benefit */ -LPWSTR xWC_TABCONTROL = WC_TABCONTROL; - -static LRESULT CALLBACK tabSubProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR id, DWORD_PTR data) -{ - NMHDR *nmhdr = (NMHDR *) lParam; - LRESULT r; - - switch (uMsg) { - case msgNOTIFY: - switch (nmhdr->code) { - case TCN_SELCHANGING: - r = SendMessageW(hwnd, TCM_GETCURSEL, 0, 0); - if (r == (LRESULT) -1) /* no tab currently selected */ - return FALSE; - tabChanging((void *) data, r); - return FALSE; /* allow change */ - case TCN_SELCHANGE: - tabChanged((void *) data, SendMessageW(hwnd, TCM_GETCURSEL, 0, 0)); - return 0; - } - return (*fv_DefSubclassProc)(hwnd, uMsg, wParam, lParam); - case WM_NCDESTROY: - if ((*fv_RemoveWindowSubclass)(hwnd, tabSubProc, id) == FALSE) - xpanic("error removing Tab subclass (which was for its own event handler)", GetLastError()); - return (*fv_DefSubclassProc)(hwnd, uMsg, wParam, lParam); - default: - return (*fv_DefSubclassProc)(hwnd, uMsg, wParam, lParam); - } - xmissedmsg("Tab", "tabSubProc()", uMsg); - return 0; /* unreached */ -} - -void setTabSubclass(HWND hwnd, void *data) -{ - if ((*fv_SetWindowSubclass)(hwnd, tabSubProc, 0, (DWORD_PTR) data) == FALSE) - xpanic("error subclassing Tab to give it its own event handler", GetLastError()); -} - -void tabAppend(HWND hwnd, LPWSTR name) -{ - TCITEM item; - LRESULT n; - - ZeroMemory(&item, sizeof (TCITEM)); - item.mask = TCIF_TEXT; - item.pszText = name; - /* MSDN's example code uses the first invalid index directly for this */ - n = SendMessageW(hwnd, TCM_GETITEMCOUNT, 0, 0); - if (SendMessageW(hwnd, TCM_INSERTITEM, (WPARAM) n, (LPARAM) (&item)) == (LRESULT) -1) - xpanic("error adding tab to Tab", GetLastError()); -} - -void tabGetContentRect(HWND hwnd, RECT *r) -{ - /* not &r; already a pointer (thanks MindChild in irc.efnet.net/#winprog for spotting my failure) */ - SendMessageW(hwnd, TCM_ADJUSTRECT, FALSE, (LPARAM) r); -} - -/* theoretically we don't need to iterate over every tab for this, but let's do it just to be safe */ -LONG tabGetTabHeight(HWND hwnd) -{ - RECT r; - LRESULT i, n; - LONG tallest; - - n = SendMessageW(hwnd, TCM_GETITEMCOUNT, 0, 0); - /* if there are no tabs, then the control just draws a box over the full window rect, reserving no space for tabs (TODO check on windows xp and 7); this is handled here */ - tallest = 0; - for (i = 0; i < n; i++) { - if (SendMessageW(hwnd, TCM_GETITEMRECT, (WPARAM) i, (LPARAM) (&r)) == FALSE) - xpanic("error getting tab height for Tab.preferredSize()", GetLastError()); - if (tallest < (r.bottom - r.top)) - tallest = r.bottom - r.top; - } - return tallest; -} diff --git a/redo/containerctrls_windows.go b/redo/containerctrls_windows.go deleted file mode 100644 index e9d116b..0000000 --- a/redo/containerctrls_windows.go +++ /dev/null @@ -1,110 +0,0 @@ -// 25 july 2014 - -package ui - -import ( - "unsafe" -) - -// #include "winapi_windows.h" -import "C" - -/* -On Windows, container controls are just regular controls; their children have to be children of the parent window, and changing the contents of a switching container (such as a tab control) must be done manually. - -TODO -- make sure all tabs cannot be deselected (that is, make sure the current tab can never have index -1) -- see if we can safely make the controls children of the tab control itself or if that would just screw our subclassing -*/ - -type tab struct { - *controlbase - tabs []*sizer - supersetParent func(p *controlParent) - superallocate func(x int, y int, width int, height int, d *sizing) []*allocation -} - -func newTab() Tab { - c := newControl(C.xWC_TABCONTROL, - C.TCS_TOOLTIPS | C.WS_TABSTOP, - 0) - t := &tab{ - controlbase: c, - } - t.supersetParent = t.fsetParent - t.fsetParent = t.tabsetParent - t.fpreferredSize = t.tabpreferredSize - t.superallocate = t.fallocate - t.fallocate = t.taballocate - C.controlSetControlFont(t.hwnd) - C.setTabSubclass(t.hwnd, unsafe.Pointer(t)) - return t -} - -func (t *tab) tabsetParent(p *controlParent) { - t.supersetParent(p) - for _, c := range t.tabs { - c.child.setParent(p) - } -} - -func (t *tab) Append(name string, control Control) { - s := new(sizer) - t.tabs = append(t.tabs, s) - s.child = control - if t.parent != nil { - s.child.setParent(&controlParent{t.parent}) - } - // initially hide tab 1..n controls; if we don't, they'll appear over other tabs, resulting in weird behavior - if len(t.tabs) != 1 { - s.child.containerHide() - } - C.tabAppend(t.hwnd, toUTF16(name)) -} - -//export tabChanging -func tabChanging(data unsafe.Pointer, current C.LRESULT) { - t := (*tab)(data) - t.tabs[int(current)].child.containerHide() -} - -//export tabChanged -func tabChanged(data unsafe.Pointer, new C.LRESULT) { - t := (*tab)(data) - t.tabs[int(new)].child.containerShow() -} - -func (t *tab) tabpreferredSize(d *sizing) (width, height int) { - // TODO only consider the size of the current tab? - for _, s := range t.tabs { - w, h := s.child.preferredSize(d) - if width < w { - width = w - } - if height < h { - height = h - } - } - return width, height + int(C.tabGetTabHeight(t.hwnd)) -} - -// a tab control contains other controls; size appropriately -// TODO change this to commitResize() -func (t *tab) taballocate(x int, y int, width int, height int, d *sizing) []*allocation { - var r C.RECT - - // figure out what the rect for each child is... - r.left = C.LONG(x) // load structure with the window's rect - r.top = C.LONG(y) - r.right = C.LONG(x + width) - r.bottom = C.LONG(y + height) - C.tabGetContentRect(t.hwnd, &r) - // and allocate - // don't allocate to just the current tab; allocate to all tabs! - for _, s := range t.tabs { - // because each widget is actually a child of the Window, the origin is the one we calculated above - s.resize(int(r.left), int(r.top), int(r.right - r.left), int(r.bottom - r.top)) - } - // and now allocate the tab control itself - return t.superallocate(x, y, width, height, d) -} diff --git a/redo/label_darwin.go b/redo/label_darwin.go new file mode 100644 index 0000000..2e73b2e --- /dev/null +++ b/redo/label_darwin.go @@ -0,0 +1,61 @@ +// 16 july 2014 + +package ui + +// #include "objc_darwin.h" +import "C" + +// cheap trick +type label struct { + *textField + standalone bool + supercommitResize func(c *allocation, d *sizing) +} + +func finishNewLabel(text string, standalone bool) *label { + l := &label{ + textField: finishNewTextField(C.newLabel()), + standalone: standalone, + } + l.SetText(text) + l.supercommitResize = l.fcommitResize + l.fcommitResize = l.labelcommitResize + return l +} + +func newLabel(text string) Label { + return finishNewLabel(text, false) +} + +func newStandaloneLabel(text string) Label { + return finishNewLabel(text, true) +} + +func (l *label) labelcommitResize(c *allocation, d *sizing) { + if !l.standalone && c.neighbor != nil { + c.neighbor.getAuxResizeInfo(d) + if d.neighborAlign.baseline != 0 { // no adjustment needed if the given control has no baseline + // in order for the baseline value to be correct, the label MUST BE AT THE HEIGHT THAT OS X WANTS IT TO BE! + // otherwise, the baseline calculation will be relative to the bottom of the control, and everything will be wrong + origsize := C.controlPrefSize(l.id) + c.height = int(origsize.height) + newrect := C.struct_xrect{ + x: C.intptr_t(c.x), + y: C.intptr_t(c.y), + width: C.intptr_t(c.width), + height: C.intptr_t(c.height), + } + ourAlign := C.alignmentInfo(l.id, newrect) + // we need to find the exact Y positions of the baselines + // fortunately, this is easy now that (x,y) is the bottom-left corner + thisbasey := ourAlign.rect.y + ourAlign.baseline + neighborbasey := d.neighborAlign.rect.y + d.neighborAlign.baseline + // now the amount we have to move the label down by is easy to find + yoff := neighborbasey - thisbasey + // and we just add that + c.y += int(yoff) + } + // TODO if there's no baseline, the alignment should be to the top /of the alignment rect/, not the frame + } + l.supercommitResize(c, d) +} diff --git a/redo/label_unix.go b/redo/label_unix.go new file mode 100644 index 0000000..1776b73 --- /dev/null +++ b/redo/label_unix.go @@ -0,0 +1,71 @@ +// +build !windows,!darwin + +// 7 july 2014 + +package ui + +import ( + "unsafe" +) + +// #include "gtk_unix.h" +// extern void buttonClicked(GtkButton *, gpointer); +// extern void checkboxToggled(GtkToggleButton *, gpointer); +import "C" + +// TODOs: +// - standalone label on its own: should it be centered or not? + +type label struct { + *controlbase + misc *C.GtkMisc + label *C.GtkLabel + standalone bool + supercommitResize func(c *allocation, d *sizing) +} + +func finishNewLabel(text string, standalone bool) *label { + ctext := togstr(text) + defer freegstr(ctext) + widget := C.gtk_label_new(ctext) + l := &label{ + controlbase: newControl(widget), + misc: (*C.GtkMisc)(unsafe.Pointer(widget)), + label: (*C.GtkLabel)(unsafe.Pointer(widget)), + standalone: standalone, + } + l.supercommitResize = l.fcommitResize + l.fcommitResize = l.labelcommitResize + return l +} + +func newLabel(text string) Label { + return finishNewLabel(text, false) +} + +func newStandaloneLabel(text string) Label { + return finishNewLabel(text, true) +} + +func (l *label) Text() string { + return fromgstr(C.gtk_label_get_text(l.label)) +} + +func (l *label) SetText(text string) { + ctext := togstr(text) + defer freegstr(ctext) + C.gtk_label_set_text(l.label, ctext) +} + +func (l *label) labelcommitResize(c *allocation, d *sizing) { + if !l.standalone && c.neighbor != nil { + c.neighbor.getAuxResizeInfo(d) + if d.shouldVAlignTop { + // don't bother aligning it to the first line of text in the control; this is harder than it's worth (thanks gregier in irc.gimp.net/#gtk+) + C.gtk_misc_set_alignment(l.misc, 0, 0) + } else { + C.gtk_misc_set_alignment(l.misc, 0, 0.5) + } + } + l.supercommitResize(c, d) +} diff --git a/redo/label_windows.go b/redo/label_windows.go new file mode 100644 index 0000000..e6b3e7f --- /dev/null +++ b/redo/label_windows.go @@ -0,0 +1,68 @@ +// 15 july 2014 + +package ui + +// #include "winapi_windows.h" +import "C" + +type label struct { + *controlbase + standalone bool + supercommitResize func(c *allocation, d *sizing) +} + +var labelclass = toUTF16("STATIC") + +func finishNewLabel(text string, standalone bool) *label { + c := newControl(labelclass, + // 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) + C.SS_NOPREFIX | C.SS_LEFTNOWORDWRAP, + 0) + c.setText(text) + C.controlSetControlFont(c.hwnd) + l := &label{ + controlbase: c, + standalone: standalone, + } + l.fpreferredSize = l.labelpreferredSize + l.supercommitResize = l.fcommitResize + l.fcommitResize = l.labelcommitResize + return l +} + +func newLabel(text string) Label { + return finishNewLabel(text, false) +} + +func newStandaloneLabel(text string) Label { + return finishNewLabel(text, true) +} + +func (l *label) Text() string { + return l.text() +} + +func (l *label) SetText(text string) { + l.setText(text) +} + +const ( + // via http://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing + labelHeight = 8 + labelYOffset = 3 + // TODO the label is offset slightly by default... +) + +func (l *label) labelpreferredSize(d *sizing) (width, height int) { + return int(l.textlen), fromdlgunitsY(labelHeight, d) +} + +func (l *label) labelcommitResize(c *allocation, d *sizing) { + if !l.standalone { + yoff := fromdlgunitsY(labelYOffset, d) + c.y += yoff + c.height -= yoff + } + l.supercommitResize(c, d) +} diff --git a/redo/objc_darwin.h b/redo/objc_darwin.h index c745d1e..fd79c43 100644 --- a/redo/objc_darwin.h +++ b/redo/objc_darwin.h @@ -52,7 +52,7 @@ extern id newLabel(void); /* sizing_darwin.m */ extern void moveControl(id, intptr_t, intptr_t, intptr_t, intptr_t); -/* containerctrls_darwin.m */ +/* tab_darwin.m */ extern id newTab(void *); extern id tabAppend(id, char *); diff --git a/redo/tab_darwin.go b/redo/tab_darwin.go new file mode 100644 index 0000000..ef834ad --- /dev/null +++ b/redo/tab_darwin.go @@ -0,0 +1,50 @@ +// 25 july 2014 + +package ui + +import ( + "unsafe" +) + +// #include "objc_darwin.h" +import "C" + +type tab struct { + *controlbase + + tabs []*sizer +} + +func newTab() Tab { + t := new(tab) + id := C.newTab(unsafe.Pointer(t)) + t.controlbase = newControl(id) + t.fpreferredSize = t.tabpreferredSize + return t +} + +func (t *tab) Append(name string, control Control) { + s := new(sizer) + t.tabs = append(t.tabs, s) + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + tabview := C.tabAppend(t.id, cname) + s.child = control + s.child.setParent(&controlParent{tabview}) +} + +func (t *tab) tabpreferredSize(d *sizing) (width, height int) { + s := C.tabPrefSize(t.id) + return int(s.width), int(s.height) +} + +// no need to override Control.commitResize() as only prepared the tabbed control; its children will be reallocated when that one is resized + +//export tabResized +func tabResized(data unsafe.Pointer, width C.intptr_t, height C.intptr_t) { + t := (*tab)(unsafe.Pointer(data)) + for _, s := range t.tabs { + // the tab area's coordinate system is localized, so the origin is (0, 0) + s.resize(0, 0, int(width), int(height)) + } +} diff --git a/redo/tab_darwin.m b/redo/tab_darwin.m new file mode 100644 index 0000000..c99e275 --- /dev/null +++ b/redo/tab_darwin.m @@ -0,0 +1,46 @@ +// 25 july 2014 + +#import "objc_darwin.h" +#import "_cgo_export.h" +#import + +#define toNSTabView(x) ((NSTabView *) (x)) + +@interface goTabView : NSTabView { +@public + void *gotab; +} +@end + +@implementation goTabView + +- (void)setFrame:(NSRect)r +{ + NSRect content; + + [super setFrame:r]; + content = [self contentRect]; + tabResized(self->gotab, (intptr_t) content.size.width, (intptr_t) content.size.height); +} + +@end + +id newTab(void *gotab) +{ + goTabView *t; + + t = [[goTabView alloc] initWithFrame:NSMakeRect(0, 0, 100, 100)]; + setStandardControlFont((id) t); // safe; same selector provided by NSTabView + t->gotab = gotab; + return (id) t; +} + +id tabAppend(id t, char *name) +{ + NSTabViewItem *i; + + i = [[NSTabViewItem alloc] initWithIdentifier:nil]; + [i setLabel:[NSString stringWithUTF8String:name]]; + [toNSTabView(t) addTabViewItem:i]; + return (id) [i view]; +} diff --git a/redo/tab_unix.go b/redo/tab_unix.go new file mode 100644 index 0000000..60978ea --- /dev/null +++ b/redo/tab_unix.go @@ -0,0 +1,45 @@ +// +build !windows,!darwin + +// 25 july 2014 + +package ui + +import ( + "unsafe" +) + +// #include "gtk_unix.h" +import "C" + +type tab struct { + *controlbase + notebook *C.GtkNotebook + + tabs []*layout +} + +func newTab() Tab { + widget := C.gtk_notebook_new() + t := &tab{ + controlbase: newControl(widget), + notebook: (*C.GtkNotebook)(unsafe.Pointer(widget)), + } + // there are no scrolling arrows by default; add them in case there are too many tabs + C.gtk_notebook_set_scrollable(t.notebook, C.TRUE) + return t +} + +func (t *tab) Append(name string, control Control) { + tl := newLayout(control) + t.tabs = append(t.tabs, tl) + cname := togstr(name) + defer freegstr(cname) + tab := C.gtk_notebook_append_page(t.notebook, + tl.layoutwidget, + C.gtk_label_new(cname)) + if tab == -1 { + panic("gtk_notebook_append_page() failed") + } +} + +// no need to override Control.commitResize() as only prepared the tabbed control; its children will be reallocated when that one is resized diff --git a/redo/tab_windows.c b/redo/tab_windows.c new file mode 100644 index 0000000..5a998b5 --- /dev/null +++ b/redo/tab_windows.c @@ -0,0 +1,82 @@ +/* 25 july 2014 */ + +#include "winapi_windows.h" +#include "_cgo_export.h" + +/* provided for cgo's benefit */ +LPWSTR xWC_TABCONTROL = WC_TABCONTROL; + +static LRESULT CALLBACK tabSubProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR id, DWORD_PTR data) +{ + NMHDR *nmhdr = (NMHDR *) lParam; + LRESULT r; + + switch (uMsg) { + case msgNOTIFY: + switch (nmhdr->code) { + case TCN_SELCHANGING: + r = SendMessageW(hwnd, TCM_GETCURSEL, 0, 0); + if (r == (LRESULT) -1) /* no tab currently selected */ + return FALSE; + tabChanging((void *) data, r); + return FALSE; /* allow change */ + case TCN_SELCHANGE: + tabChanged((void *) data, SendMessageW(hwnd, TCM_GETCURSEL, 0, 0)); + return 0; + } + return (*fv_DefSubclassProc)(hwnd, uMsg, wParam, lParam); + case WM_NCDESTROY: + if ((*fv_RemoveWindowSubclass)(hwnd, tabSubProc, id) == FALSE) + xpanic("error removing Tab subclass (which was for its own event handler)", GetLastError()); + return (*fv_DefSubclassProc)(hwnd, uMsg, wParam, lParam); + default: + return (*fv_DefSubclassProc)(hwnd, uMsg, wParam, lParam); + } + xmissedmsg("Tab", "tabSubProc()", uMsg); + return 0; /* unreached */ +} + +void setTabSubclass(HWND hwnd, void *data) +{ + if ((*fv_SetWindowSubclass)(hwnd, tabSubProc, 0, (DWORD_PTR) data) == FALSE) + xpanic("error subclassing Tab to give it its own event handler", GetLastError()); +} + +void tabAppend(HWND hwnd, LPWSTR name) +{ + TCITEM item; + LRESULT n; + + ZeroMemory(&item, sizeof (TCITEM)); + item.mask = TCIF_TEXT; + item.pszText = name; + /* MSDN's example code uses the first invalid index directly for this */ + n = SendMessageW(hwnd, TCM_GETITEMCOUNT, 0, 0); + if (SendMessageW(hwnd, TCM_INSERTITEM, (WPARAM) n, (LPARAM) (&item)) == (LRESULT) -1) + xpanic("error adding tab to Tab", GetLastError()); +} + +void tabGetContentRect(HWND hwnd, RECT *r) +{ + /* not &r; already a pointer (thanks MindChild in irc.efnet.net/#winprog for spotting my failure) */ + SendMessageW(hwnd, TCM_ADJUSTRECT, FALSE, (LPARAM) r); +} + +/* theoretically we don't need to iterate over every tab for this, but let's do it just to be safe */ +LONG tabGetTabHeight(HWND hwnd) +{ + RECT r; + LRESULT i, n; + LONG tallest; + + n = SendMessageW(hwnd, TCM_GETITEMCOUNT, 0, 0); + /* if there are no tabs, then the control just draws a box over the full window rect, reserving no space for tabs (TODO check on windows xp and 7); this is handled here */ + tallest = 0; + for (i = 0; i < n; i++) { + if (SendMessageW(hwnd, TCM_GETITEMRECT, (WPARAM) i, (LPARAM) (&r)) == FALSE) + xpanic("error getting tab height for Tab.preferredSize()", GetLastError()); + if (tallest < (r.bottom - r.top)) + tallest = r.bottom - r.top; + } + return tallest; +} diff --git a/redo/tab_windows.go b/redo/tab_windows.go new file mode 100644 index 0000000..e9d116b --- /dev/null +++ b/redo/tab_windows.go @@ -0,0 +1,110 @@ +// 25 july 2014 + +package ui + +import ( + "unsafe" +) + +// #include "winapi_windows.h" +import "C" + +/* +On Windows, container controls are just regular controls; their children have to be children of the parent window, and changing the contents of a switching container (such as a tab control) must be done manually. + +TODO +- make sure all tabs cannot be deselected (that is, make sure the current tab can never have index -1) +- see if we can safely make the controls children of the tab control itself or if that would just screw our subclassing +*/ + +type tab struct { + *controlbase + tabs []*sizer + supersetParent func(p *controlParent) + superallocate func(x int, y int, width int, height int, d *sizing) []*allocation +} + +func newTab() Tab { + c := newControl(C.xWC_TABCONTROL, + C.TCS_TOOLTIPS | C.WS_TABSTOP, + 0) + t := &tab{ + controlbase: c, + } + t.supersetParent = t.fsetParent + t.fsetParent = t.tabsetParent + t.fpreferredSize = t.tabpreferredSize + t.superallocate = t.fallocate + t.fallocate = t.taballocate + C.controlSetControlFont(t.hwnd) + C.setTabSubclass(t.hwnd, unsafe.Pointer(t)) + return t +} + +func (t *tab) tabsetParent(p *controlParent) { + t.supersetParent(p) + for _, c := range t.tabs { + c.child.setParent(p) + } +} + +func (t *tab) Append(name string, control Control) { + s := new(sizer) + t.tabs = append(t.tabs, s) + s.child = control + if t.parent != nil { + s.child.setParent(&controlParent{t.parent}) + } + // initially hide tab 1..n controls; if we don't, they'll appear over other tabs, resulting in weird behavior + if len(t.tabs) != 1 { + s.child.containerHide() + } + C.tabAppend(t.hwnd, toUTF16(name)) +} + +//export tabChanging +func tabChanging(data unsafe.Pointer, current C.LRESULT) { + t := (*tab)(data) + t.tabs[int(current)].child.containerHide() +} + +//export tabChanged +func tabChanged(data unsafe.Pointer, new C.LRESULT) { + t := (*tab)(data) + t.tabs[int(new)].child.containerShow() +} + +func (t *tab) tabpreferredSize(d *sizing) (width, height int) { + // TODO only consider the size of the current tab? + for _, s := range t.tabs { + w, h := s.child.preferredSize(d) + if width < w { + width = w + } + if height < h { + height = h + } + } + return width, height + int(C.tabGetTabHeight(t.hwnd)) +} + +// a tab control contains other controls; size appropriately +// TODO change this to commitResize() +func (t *tab) taballocate(x int, y int, width int, height int, d *sizing) []*allocation { + var r C.RECT + + // figure out what the rect for each child is... + r.left = C.LONG(x) // load structure with the window's rect + r.top = C.LONG(y) + r.right = C.LONG(x + width) + r.bottom = C.LONG(y + height) + C.tabGetContentRect(t.hwnd, &r) + // and allocate + // don't allocate to just the current tab; allocate to all tabs! + for _, s := range t.tabs { + // because each widget is actually a child of the Window, the origin is the one we calculated above + s.resize(int(r.left), int(r.top), int(r.right - r.left), int(r.bottom - r.top)) + } + // and now allocate the tab control itself + return t.superallocate(x, y, width, height, d) +} diff --git a/redo/textfield_darwin.go b/redo/textfield_darwin.go new file mode 100644 index 0000000..0b248a7 --- /dev/null +++ b/redo/textfield_darwin.go @@ -0,0 +1,38 @@ +// 16 july 2014 + +package ui + +import ( + "unsafe" +) + +// #include "objc_darwin.h" +import "C" + +type textField struct { + *controlbase +} + +func finishNewTextField(id C.id) *textField { + return &textField{ + controlbase: newControl(id), + } +} + +func newTextField() *textField { + return finishNewTextField(C.newTextField()) +} + +func newPasswordField() *textField { + return finishNewTextField(C.newPasswordField()) +} + +func (t *textField) Text() string { + return C.GoString(C.textFieldText(t.id)) +} + +func (t *textField) SetText(text string) { + ctext := C.CString(text) + defer C.free(unsafe.Pointer(ctext)) + C.textFieldSetText(t.id, ctext) +} diff --git a/redo/textfield_unix.go b/redo/textfield_unix.go new file mode 100644 index 0000000..4d710b1 --- /dev/null +++ b/redo/textfield_unix.go @@ -0,0 +1,47 @@ +// +build !windows,!darwin + +// 7 july 2014 + +package ui + +import ( + "unsafe" +) + +// #include "gtk_unix.h" +// extern void buttonClicked(GtkButton *, gpointer); +// extern void checkboxToggled(GtkToggleButton *, gpointer); +import "C" + +type textField struct { + *controlbase + entry *C.GtkEntry +} + +func startNewTextField() *textField { + w := C.gtk_entry_new() + return &textField{ + controlbase: newControl(w), + entry: (*C.GtkEntry)(unsafe.Pointer(w)), + } +} + +func newTextField() *textField { + return startNewTextField() +} + +func newPasswordField() *textField { + t := startNewTextField() + C.gtk_entry_set_visibility(t.entry, C.FALSE) + return t +} + +func (t *textField) Text() string { + return fromgstr(C.gtk_entry_get_text(t.entry)) +} + +func (t *textField) SetText(text string) { + ctext := togstr(text) + defer freegstr(ctext) + C.gtk_entry_set_text(t.entry, ctext) +} diff --git a/redo/textfield_windows.go b/redo/textfield_windows.go new file mode 100644 index 0000000..d325a18 --- /dev/null +++ b/redo/textfield_windows.go @@ -0,0 +1,50 @@ +// 15 july 2014 + +package ui + +// #include "winapi_windows.h" +import "C" + +type textField struct { + *controlbase +} + +var editclass = toUTF16("EDIT") + +func startNewTextField(style C.DWORD) *textField { + c := newControl(editclass, + style | C.ES_AUTOHSCROLL | C.ES_LEFT | C.ES_NOHIDESEL | C.WS_TABSTOP, + C.WS_EX_CLIENTEDGE) // WS_EX_CLIENTEDGE without WS_BORDER will show the canonical visual styles border (thanks to MindChild in irc.efnet.net/#winprog) + C.controlSetControlFont(c.hwnd) + t := &textField{ + controlbase: c, + } + t.fpreferredSize = t.textfieldpreferredSize + return t +} + +func newTextField() *textField { + return startNewTextField(0) +} + +func newPasswordField() *textField { + return startNewTextField(C.ES_PASSWORD) +} + +func (t *textField) Text() string { + return t.text() +} + +func (t *textField) SetText(text string) { + t.setText(text) +} + +const ( + // from http://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing + textfieldWidth = 107 // this is actually the shorter progress bar width, but Microsoft only indicates as wide as necessary + textfieldHeight = 14 +) + +func (t *textField) textfieldpreferredSize(d *sizing) (width, height int) { + return fromdlgunitsX(textfieldWidth, d), fromdlgunitsY(textfieldHeight, d) +} -- cgit v1.2.3