From 5a51263adc6d2e8ee7ea0dac4d92a66755c07cb1 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Wed, 30 Jul 2014 00:54:50 -0400 Subject: Renamed the controls* and containers* files to basicctrls* and containerctrls*, respectively, in preparation for the widget hierarchy redo. --- redo/basicctrls.go | 100 +++++++++++++++++++ redo/basicctrls_darwin.go | 155 ++++++++++++++++++++++++++++++ redo/basicctrls_darwin.m | 157 ++++++++++++++++++++++++++++++ redo/basicctrls_unix.go | 212 +++++++++++++++++++++++++++++++++++++++++ redo/basicctrls_windows.c | 138 +++++++++++++++++++++++++++ redo/basicctrls_windows.go | 203 +++++++++++++++++++++++++++++++++++++++ redo/containerctrls.go | 25 +++++ redo/containerctrls_darwin.go | 48 ++++++++++ redo/containerctrls_darwin.m | 46 +++++++++ redo/containerctrls_unix.go | 71 ++++++++++++++ redo/containerctrls_windows.c | 64 +++++++++++++ redo/containerctrls_windows.go | 88 +++++++++++++++++ redo/containers.go | 25 ----- redo/containers_darwin.go | 48 ---------- redo/containers_darwin.m | 46 --------- redo/containers_unix.go | 71 -------------- redo/containers_windows.c | 64 ------------- redo/containers_windows.go | 88 ----------------- redo/controls.go | 100 ------------------- redo/controls_darwin.go | 155 ------------------------------ redo/controls_darwin.m | 157 ------------------------------ redo/controls_unix.go | 212 ----------------------------------------- redo/controls_windows.c | 138 --------------------------- redo/controls_windows.go | 203 --------------------------------------- 24 files changed, 1307 insertions(+), 1307 deletions(-) create mode 100644 redo/basicctrls.go create mode 100644 redo/basicctrls_darwin.go create mode 100644 redo/basicctrls_darwin.m create mode 100644 redo/basicctrls_unix.go create mode 100644 redo/basicctrls_windows.c create mode 100644 redo/basicctrls_windows.go create mode 100644 redo/containerctrls.go create mode 100644 redo/containerctrls_darwin.go create mode 100644 redo/containerctrls_darwin.m create mode 100644 redo/containerctrls_unix.go create mode 100644 redo/containerctrls_windows.c create mode 100644 redo/containerctrls_windows.go delete mode 100644 redo/containers.go delete mode 100644 redo/containers_darwin.go delete mode 100644 redo/containers_darwin.m delete mode 100644 redo/containers_unix.go delete mode 100644 redo/containers_windows.c delete mode 100644 redo/containers_windows.go delete mode 100644 redo/controls.go delete mode 100644 redo/controls_darwin.go delete mode 100644 redo/controls_darwin.m delete mode 100644 redo/controls_unix.go delete mode 100644 redo/controls_windows.c delete mode 100644 redo/controls_windows.go (limited to 'redo') diff --git a/redo/basicctrls.go b/redo/basicctrls.go new file mode 100644 index 0000000..cda67db --- /dev/null +++ b/redo/basicctrls.go @@ -0,0 +1,100 @@ +// 7 july 2014 + +package ui + +// Control represents a control. +// All Controls have event handlers that take a single argument (the Doer active during the event) and return nothing. +type Control interface { + setParent(p *controlParent) // controlParent defined per-platform + // TODO enable/disable (public) + // TODO show/hide (public) + containerShow() // for Windows, where all controls need ot belong to an overlapped window, not to a container control; these respect programmer settings + containerHide() + controlSizing +} + +// Button is a clickable button that performs some task. +type Button interface { + Control + + // OnClicked sets the event handler for when the Button is clicked. + OnClicked(func()) + + // Text and SetText get and set the Button's label text. + Text() string + SetText(text string) +} + +// NewButton creates a new Button with the given label text. +func NewButton(text string) Button { + return newButton(text) +} + +// Checkbox is a clickable box that indicates some Boolean value. +type Checkbox interface { + Control + + // OnClicked sets the event handler for when the Checkbox is clicked (to change its toggle state). + // TODO change to OnToggled() or OnChecked()? + OnClicked(func()) + + // Text and SetText get and set the Checkbox's label text. + Text() string + SetText(text string) + + // Checked and SetChecked get and set the Checkbox's check state. + Checked() bool + SetChecked(checked bool) +} + +// NewCheckbox creates a new Checkbox with the given label text. +// The Checkbox will be initially unchecked. +func NewCheckbox(text string) Checkbox { + return newCheckbox(text) +} + +// TextField is a Control in which the user can enter a single line of text. +type TextField interface { + Control + + // TODO figure out what events are appropriate + + // Text and SetText are Requests that get and set the TextField's text. + Text() string + SetText(text string) +} + +// NewTextField creates a new TextField. +func NewTextField() TextField { + return newTextField() +} + +// NewPasswordField creates a new TextField for entering passwords; that is, it hides the text being entered. +func NewPasswordField() TextField { + return newPasswordField() +} + + +// Label is a Control that shows a static line of text. +// Label shows one line of text; any text that does not fit is truncated. +// A Label can either have smart vertical alignment relative to the control to its right or just be vertically aligned to the top (standalone). +type Label interface { + Control + + // Text and SetText get and set the Label's text. + Text() string + SetText(text string) +} + +// NewLabel creates a new Label with the given text. +// The Label will smartly vertically position itself relative to the control to its immediate right. +// TODO Grids on GTK+ will not respect this unless SetFilling() +func NewLabel(text string) Label { + return newLabel(text) +} + +// NewStandaloneLabel creates a new Label with the given text. +// The Label will be vertically positioned at the top of its allocated space. +func NewStandaloneLabel(text string) Label { + return newStandaloneLabel(text) +} diff --git a/redo/basicctrls_darwin.go b/redo/basicctrls_darwin.go new file mode 100644 index 0000000..78e9138 --- /dev/null +++ b/redo/basicctrls_darwin.go @@ -0,0 +1,155 @@ +// 16 july 2014 + +package ui + +import ( + "unsafe" +) + +// #include "objc_darwin.h" +import "C" + +type widgetbase struct { + id C.id +} + +func newWidget(id C.id) *widgetbase { + return &widgetbase{ + id: id, + } +} + +// these few methods are embedded by all the various Controls since they all will do the same thing + +type controlParent struct { + id C.id +} + +func (w *widgetbase) setParent(parent *controlParent) { + // redrawing the new window handled by C.parent() + C.parent(w.id, parent.id) +} + +func (w *widgetbase) containerShow() { + C.controlSetHidden(w.id, C.NO) +} + +func (w *widgetbase) containerHide() { + C.controlSetHidden(w.id, C.YES) +} + +type button struct { + *widgetbase + clicked *event +} + +func finishNewButton(id C.id, text string) *button { + ctext := C.CString(text) + defer C.free(unsafe.Pointer(ctext)) + b := &button{ + widgetbase: newWidget(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 { + *widgetbase +} + +func finishNewTextField(id C.id) *textField { + return &textField{ + widgetbase: newWidget(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 +} + +func finishNewLabel(text string, standalone bool) *label { + l := &label{ + textField: finishNewTextField(C.newLabel()), + standalone: standalone, + } + l.SetText(text) + return l +} + +func newLabel(text string) Label { + return finishNewLabel(text, false) +} + +func newStandaloneLabel(text string) Label { + return finishNewLabel(text, true) +} + +// TODO label commitResize diff --git a/redo/basicctrls_darwin.m b/redo/basicctrls_darwin.m new file mode 100644 index 0000000..c482e5e --- /dev/null +++ b/redo/basicctrls_darwin.m @@ -0,0 +1,157 @@ +// 16 july 2014 + +#import "objc_darwin.h" +#import "_cgo_export.h" +#import + +#define toNSView(x) ((NSView *) (x)) +#define toNSWindow(x) ((NSWindow *) (x)) +#define toNSControl(x) ((NSControl *) (x)) +#define toNSButton(x) ((NSButton *) (x)) +#define toNSTextField(x) ((NSTextField *) (x)) + +void parent(id control, id parentid) +{ + [toNSView(parentid) addSubview:toNSView(control)]; +} + +void controlSetHidden(id control, BOOL hidden) +{ + [toNSView(control) setHidden:hidden]; +} + +void setStandardControlFont(id control) +{ + [toNSControl(control) setFont:[NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSRegularControlSize]]]; +} + +@interface goControlDelegate : NSObject { +@public + void *gocontrol; +} +@end + +@implementation goControlDelegate + +- (IBAction)buttonClicked:(id)sender +{ + buttonClicked(self->gocontrol); +} + +@end + +id newButton(void) +{ + NSButton *b; + + // TODO cache the initial rect? + b = [[NSButton alloc] initWithFrame:NSMakeRect(0, 0, 100, 100)]; + [b setButtonType:NSMomentaryPushInButton]; + [b setBordered:YES]; + [b setBezelStyle:NSRoundedBezelStyle]; + setStandardControlFont((id) b); + return (id) b; +} + +void buttonSetDelegate(id button, void *b) +{ + goControlDelegate *d; + + d = [goControlDelegate new]; + d->gocontrol = b; + [toNSButton(button) setTarget:d]; + [toNSButton(button) setAction:@selector(buttonClicked:)]; +} + +const char *buttonText(id button) +{ + return [[toNSButton(button) title] UTF8String]; +} + +void buttonSetText(id button, char *text) +{ + [toNSButton(button) setTitle:[NSString stringWithUTF8String:text]]; +} + +id newCheckbox(void) +{ + NSButton *c; + + c = [[NSButton alloc] initWithFrame:NSMakeRect(0, 0, 100, 100)]; + [c setButtonType:NSSwitchButton]; + [c setBordered:NO]; + setStandardControlFont((id) c); + return (id) c; +} + +BOOL checkboxChecked(id c) +{ + if ([toNSButton(c) state] == NSOnState) + return YES; + return NO; +} + +void checkboxSetChecked(id c, BOOL checked) +{ + NSInteger state; + + state = NSOnState; + if (checked == NO) + state = NSOffState; + [toNSButton(c) setState:state]; +} + +static id finishNewTextField(NSTextField *t, BOOL bordered) +{ + // same for text fields, password fields, and labels + setStandardControlFont((id) t); + // TODO text field/password field border (Interface Builder setting is confusing) + if (!bordered) + [t setBordered:NO]; + // smart quotes and other autocorrect features are handled by the window; see newWindow() in window_darwin.m for details + // Interface Builder does this to make the text box behave properly + // this disables both word wrap AND ellipsizing in one fell swoop + // however, we need to send it to the control's cell, not to the control directly + [[t cell] setLineBreakMode:NSLineBreakByClipping]; + // Interface Builder also sets this to allow horizontal scrolling + // it also sets this for labels, despite those not being scrollable + [[t cell] setScrollable:YES]; + return (id) t; +} + +id newTextField(void) +{ + NSTextField *t; + + t = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 100, 100)]; + return finishNewTextField(t, YES); +} + +id newPasswordField(void) +{ + NSSecureTextField *t; + + t = [[NSSecureTextField alloc] initWithFrame:NSMakeRect(0, 0, 100, 100)]; + return finishNewTextField(toNSTextField(t), YES); +} + +const char *textFieldText(id t) +{ + return [[toNSTextField(t) stringValue] UTF8String]; +} + +void textFieldSetText(id t, char *text) +{ + [toNSTextField(t) setStringValue:[NSString stringWithUTF8String:text]]; +} + +id newLabel(void) +{ + NSTextField *l; + + l = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 100, 100)]; + [l setEditable:NO]; + [l setSelectable:NO]; + [l setDrawsBackground:NO]; + return finishNewTextField(l, NO); +} diff --git a/redo/basicctrls_unix.go b/redo/basicctrls_unix.go new file mode 100644 index 0000000..67d844b --- /dev/null +++ b/redo/basicctrls_unix.go @@ -0,0 +1,212 @@ +// +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 widgetbase struct { + widget *C.GtkWidget +} + +func newWidget(w *C.GtkWidget) *widgetbase { + return &widgetbase{ + widget: w, + } +} + +// these few methods are embedded by all the various Controls since they all will do the same thing + +type controlParent struct { + c *C.GtkContainer +} + +func (w *widgetbase) setParent(c *controlParent) { + C.gtk_container_add(c.c, w.widget) + // make sure the new widget is shown + C.gtk_widget_show_all(w.widget) +} + +func (w *widgetbase) containerShow() { + C.gtk_widget_show_all(w.widget) +} + +func (w *widgetbase) containerHide() { + C.gtk_widget_hide(w.widget) +} + +type button struct { + *widgetbase + 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{ + widgetbase: newWidget(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 { + *widgetbase + entry *C.GtkEntry +} + +func startNewTextField() *textField { + w := C.gtk_entry_new() + return &textField{ + widgetbase: newWidget(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 { + *widgetbase + misc *C.GtkMisc + label *C.GtkLabel + standalone bool +} + +func finishNewLabel(text string, standalone bool) *label { + ctext := togstr(text) + defer freegstr(ctext) + widget := C.gtk_label_new(ctext) + return &label{ + widgetbase: newWidget(widget), + misc: (*C.GtkMisc)(unsafe.Pointer(widget)), + label: (*C.GtkLabel)(unsafe.Pointer(widget)), + standalone: standalone, + } +} + +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) commitResize(c *allocation, d *sizing) { + if !l.standalone && c.neighbor != nil { + c.neighbor.getAuxResizeInfo(d) + if d.shouldVAlignTop { + // TODO should it be center-aligned to the first line or not + C.gtk_misc_set_alignment(l.misc, 0, 0) + } else { + C.gtk_misc_set_alignment(l.misc, 0, 0.5) + } + } + l.widgetbase.commitResize(c, d) +} diff --git a/redo/basicctrls_windows.c b/redo/basicctrls_windows.c new file mode 100644 index 0000000..8974bdb --- /dev/null +++ b/redo/basicctrls_windows.c @@ -0,0 +1,138 @@ +/* 17 july 2014 */ + +#include "winapi_windows.h" +#include "_cgo_export.h" + +HWND newWidget(LPCWSTR class, DWORD style, DWORD extstyle) +{ + HWND hwnd; + + hwnd = CreateWindowExW( + extstyle, + class, L"", + style | WS_CHILD | WS_VISIBLE, + CW_USEDEFAULT, CW_USEDEFAULT, + CW_USEDEFAULT, CW_USEDEFAULT, + /* + the following has the consequence of making the control message-only at first + this shouldn't cause any problems... hopefully not + but see the msgwndproc() for caveat info + also don't use low control IDs as they will conflict with dialog boxes (IDCANCEL, etc.) + */ + msgwin, (HMENU) 100, hInstance, NULL); + if (hwnd == NULL) + xpanic("error creating control", GetLastError()); + return hwnd; +} + +void controlSetParent(HWND control, HWND parent) +{ + if (SetParent(control, parent) == NULL) + xpanic("error changing control parent", GetLastError()); +} + +void controlSetControlFont(HWND which) +{ + SendMessageW(which, WM_SETFONT, (WPARAM) controlFont, TRUE); +} + +/* +all controls that have events receive the events themselves through subclasses +to do this, all windows (including the message-only window; see http://support.microsoft.com/default.aspx?scid=KB;EN-US;Q104069) forward WM_COMMAND to each control with this function +*/ +LRESULT forwardCommand(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + HWND control = (HWND) lParam; + + /* don't generate an event if the control (if there is one) is unparented (a child of the message-only window) */ + if (control != NULL && IsChild(msgwin, control) == 0) + return SendMessageW(control, msgCOMMAND, wParam, lParam); + return DefWindowProcW(hwnd, uMsg, wParam, lParam); +} + +LRESULT forwardNotify(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + NMHDR *nmhdr = (NMHDR *) lParam; + HWND control = nmhdr->hwndFrom; + + /* don't generate an event if the control (if there is one) is unparented (a child of the message-only window) */ + if (control != NULL && IsChild(msgwin, control) == 0) + return SendMessageW(control, msgNOTIFY, wParam, lParam); + return DefWindowProcW(hwnd, uMsg, wParam, lParam); +} + +static LRESULT CALLBACK buttonSubProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR id, DWORD_PTR data) +{ + switch (uMsg) { + case msgCOMMAND: + if (HIWORD(wParam) == BN_CLICKED) { + buttonClicked((void *) data); + return 0; + } + return (*fv_DefSubclassProc)(hwnd, uMsg, wParam, lParam); + case WM_NCDESTROY: + if ((*fv_RemoveWindowSubclass)(hwnd, buttonSubProc, id) == FALSE) + xpanic("error removing Button 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("Button", "buttonSubProc()", uMsg); + return 0; /* unreached */ +} + +void setButtonSubclass(HWND hwnd, void *data) +{ + if ((*fv_SetWindowSubclass)(hwnd, buttonSubProc, 0, (DWORD_PTR) data) == FALSE) + xpanic("error subclassing Button to give it its own event handler", GetLastError()); +} + +static LRESULT CALLBACK checkboxSubProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR id, DWORD_PTR data) +{ + switch (uMsg) { + case msgCOMMAND: + if (HIWORD(wParam) == BN_CLICKED) { + WPARAM check; + + /* we didn't use BS_AUTOCHECKBOX (see controls_windows.go) so we have to manage the check state ourselves */ + check = BST_CHECKED; + if (SendMessage(hwnd, BM_GETCHECK, 0, 0) == BST_CHECKED) + check = BST_UNCHECKED; + SendMessage(hwnd, BM_SETCHECK, check, 0); + checkboxToggled((void *) data); + return 0; + } + return (*fv_DefSubclassProc)(hwnd, uMsg, wParam, lParam); + case WM_NCDESTROY: + if ((*fv_RemoveWindowSubclass)(hwnd, checkboxSubProc, id) == FALSE) + xpanic("error removing Checkbox 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("Checkbox", "checkboxSubProc()", uMsg); + return 0; /* unreached */ +} + +void setCheckboxSubclass(HWND hwnd, void *data) +{ + if ((*fv_SetWindowSubclass)(hwnd, checkboxSubProc, 0, (DWORD_PTR) data) == FALSE) + xpanic("error subclassing Checkbox to give it its own event handler", GetLastError()); +} + +BOOL checkboxChecked(HWND hwnd) +{ + if (SendMessage(hwnd, BM_GETCHECK, 0, 0) == BST_UNCHECKED) + return FALSE; + return TRUE; +} + +void checkboxSetChecked(HWND hwnd, BOOL c) +{ + WPARAM check; + + check = BST_CHECKED; + if (c == FALSE) + check = BST_UNCHECKED; + SendMessage(hwnd, BM_SETCHECK, check, 0); +} diff --git a/redo/basicctrls_windows.go b/redo/basicctrls_windows.go new file mode 100644 index 0000000..ecc1494 --- /dev/null +++ b/redo/basicctrls_windows.go @@ -0,0 +1,203 @@ +// 15 july 2014 + +package ui + +import ( + "unsafe" +) + +// #include "winapi_windows.h" +import "C" + +type widgetbase struct { + hwnd C.HWND + parent C.HWND +} + +func newWidget(class C.LPCWSTR, style C.DWORD, extstyle C.DWORD) *widgetbase { + return &widgetbase{ + hwnd: C.newWidget(class, style, extstyle), + } +} + +// these few methods are embedded by all the various Controls since they all will do the same thing + +type controlParent struct { + hwnd C.HWND +} + +func (w *widgetbase) setParent(win *controlParent) { + C.controlSetParent(w.hwnd, win.hwnd) + w.parent = win.hwnd +} + +func (w *widgetbase) containerShow() { + C.ShowWindow(w.hwnd, C.SW_SHOW) +} + +func (w *widgetbase) containerHide() { + C.ShowWindow(w.hwnd, C.SW_HIDE) +} + +// don't embed these as exported; let each Control decide if it should + +func (w *widgetbase) text() string { + return getWindowText(w.hwnd) +} + +func (w *widgetbase) settext(text string) { + C.setWindowText(w.hwnd, toUTF16(text)) +} + +type button struct { + *widgetbase + clicked *event +} + +var buttonclass = toUTF16("BUTTON") + +func startNewButton(text string, style C.DWORD) *button { + w := newWidget(buttonclass, + style | C.WS_TABSTOP, + 0) + C.setWindowText(w.hwnd, toUTF16(text)) + C.controlSetControlFont(w.hwnd) + b := &button{ + widgetbase: w, + clicked: newEvent(), + } + return b +} + +func newButton(text string) *button { + b := startNewButton(text, C.BS_PUSHBUTTON) + C.setButtonSubclass(b.hwnd, unsafe.Pointer(b)) + 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") +} + +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.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") +} + +type textField struct { + *widgetbase +} + +var editclass = toUTF16("EDIT") + +func startNewTextField(style C.DWORD) *textField { + w := newWidget(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(w.hwnd) + return &textField{ + widgetbase: w, + } +} + +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) +} + +type label struct { + *widgetbase + standalone bool +} + +var labelclass = toUTF16("STATIC") + +func finishNewLabel(text string, standalone bool) *label { + w := newWidget(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.setWindowText(w.hwnd, toUTF16(text)) + C.controlSetControlFont(w.hwnd) + l := &label{ + widgetbase: w, + standalone: standalone, + } + 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) +} + +// TODO label commitResize diff --git a/redo/containerctrls.go b/redo/containerctrls.go new file mode 100644 index 0000000..ec44501 --- /dev/null +++ b/redo/containerctrls.go @@ -0,0 +1,25 @@ +// 25 july 2014 + +package ui + +// Tab is a Control that contains multiple pages of tabs, each containing a single Control. +// You can add and remove tabs from the Tab at any time. +// TODO rename? +// TODO implement containerShow()/containerHide() on this +type Tab interface { + Control + + // Append adds a new tab to Tab. + // The tab is added to the end of the current list of tabs. + Append(name string, control Control) + + // Delete removes the given tab. + // It panics if index is out of range. +// Delete(index int) +//TODO +} + +// NewTab creates a new Tab with no tabs. +func NewTab() Tab { + return newTab() +} diff --git a/redo/containerctrls_darwin.go b/redo/containerctrls_darwin.go new file mode 100644 index 0000000..0e0221a --- /dev/null +++ b/redo/containerctrls_darwin.go @@ -0,0 +1,48 @@ +// 25 july 2014 + +package ui + +import ( + "unsafe" +) + +// #include "objc_darwin.h" +import "C" + +type tab struct { + *widgetbase + + containers []*container +} + +func newTab() Tab { + t := new(tab) + id := C.newTab(unsafe.Pointer(t)) + t.widgetbase = newWidget(id) + return t +} + +func (t *tab) Append(name string, control Control) { + // TODO isolate and standardize + c := new(container) + t.containers = append(t.containers, c) + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + tabview := C.tabAppend(t.id, cname) + c.child = control + c.child.setParent(&controlParent{tabview}) +} + +func (t *tab) allocate(x int, y int, width int, height int, d *sizing) []*allocation { + // only prepared the tabbed control; its children will be reallocated when that one is resized + return t.widgetbase.allocate(x, y, width, height, d) +} + +//export tabResized +func tabResized(data unsafe.Pointer, width C.intptr_t, height C.intptr_t) { + t := (*tab)(unsafe.Pointer(data)) + for _, c := range t.containers { + // the tab area's coordinate system is localized, so the origin is (0, 0) + c.resize(0, 0, int(width), int(height)) + } +} diff --git a/redo/containerctrls_darwin.m b/redo/containerctrls_darwin.m new file mode 100644 index 0000000..c99e275 --- /dev/null +++ b/redo/containerctrls_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/containerctrls_unix.go b/redo/containerctrls_unix.go new file mode 100644 index 0000000..36118fe --- /dev/null +++ b/redo/containerctrls_unix.go @@ -0,0 +1,71 @@ +// +build !windows,!darwin + +// 25 july 2014 + +package ui + +import ( + "unsafe" +) + +// #include "gtk_unix.h" +// extern void layoutResizing(GtkWidget *, GdkRectangle *, gpointer); +import "C" + +type tab struct { + *widgetbase + notebook *C.GtkNotebook + + containers []*container + layoutws []*C.GtkWidget + layoutcs []*C.GtkContainer + layouts []*C.GtkLayout +} + +func newTab() Tab { + widget := C.gtk_notebook_new() + t := &tab{ + widgetbase: newWidget(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) { + // TODO isolate and standardize + layout := C.gtk_layout_new(nil, nil) + t.layoutws = append(t.layoutws, layout) + t.layoutcs = append(t.layoutcs, (*C.GtkContainer)(unsafe.Pointer(layout))) + t.layouts = append(t.layouts, (*C.GtkLayout)(unsafe.Pointer(layout))) + c := new(container) + t.containers = append(t.containers, c) + c.child = control + c.child.setParent(&controlParent{(*C.GtkContainer)(unsafe.Pointer(layout))}) + g_signal_connect_after( + C.gpointer(unsafe.Pointer(layout)), + "size-allocate", + C.GCallback(C.layoutResizing), + C.gpointer(unsafe.Pointer(c))) + cname := togstr(name) + defer freegstr(cname) + tab := C.gtk_notebook_append_page(t.notebook, + layout, + C.gtk_label_new(cname)) + if tab == -1 { + panic("gtk_notebook_append_page() failed") + } +} + +func (t *tab) allocate(x int, y int, width int, height int, d *sizing) []*allocation { + // only prepared the tabbed control; its children will be reallocated when that one is resized + return t.widgetbase.allocate(x, y, width, height, d) +} + +//export layoutResizing +func layoutResizing(wid *C.GtkWidget, r *C.GdkRectangle, data C.gpointer) { + c := (*container)(unsafe.Pointer(data)) + // the layout's coordinate system is localized, so the origin is (0, 0) + c.resize(0, 0, int(r.width), int(r.height)) +} diff --git a/redo/containerctrls_windows.c b/redo/containerctrls_windows.c new file mode 100644 index 0000000..b5f3a97 --- /dev/null +++ b/redo/containerctrls_windows.c @@ -0,0 +1,64 @@ +/* 25 july 2014 */ + +#include "winapi_windows.h" +#include "_cgo_export.h" + +/* provided for cgo's benefit */ +LPCWSTR 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, LPCWSTR name) +{ + TCITEM item; + LRESULT n; + + ZeroMemory(&item, sizeof (TCITEM)); + item.mask = TCIF_TEXT; + /* TODO the C means const; change everything to use LPWSTR instead */ + 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); +} diff --git a/redo/containerctrls_windows.go b/redo/containerctrls_windows.go new file mode 100644 index 0000000..cc881e7 --- /dev/null +++ b/redo/containerctrls_windows.go @@ -0,0 +1,88 @@ +// 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 { + *widgetbase + tabs []*container +} + +func newTab() Tab { + w := newWidget(C.xWC_TABCONTROL, + C.TCS_TOOLTIPS | C.WS_TABSTOP, + 0) + t := &tab{ + widgetbase: w, + } + C.controlSetControlFont(w.hwnd) + C.setTabSubclass(w.hwnd, unsafe.Pointer(t)) + return t +} + +func (t *tab) setParent(p *controlParent) { + t.widgetbase.setParent(p) + for _, c := range t.tabs { + c.child.setParent(p) + } +} + +func (t *tab) Append(name string, control Control) { + c := new(container) + t.tabs = append(t.tabs, c) + c.child = control + if t.parent != nil { + c.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 { + c.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() +} + +// a tab control contains other controls; size appropriately +func (t *tab) allocate(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 _, c := range t.tabs { + // because each widget is actually a child of the Window, the origin is the one we calculated above + c.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.widgetbase.allocate(x, y, width, height, d) +} diff --git a/redo/containers.go b/redo/containers.go deleted file mode 100644 index ec44501..0000000 --- a/redo/containers.go +++ /dev/null @@ -1,25 +0,0 @@ -// 25 july 2014 - -package ui - -// Tab is a Control that contains multiple pages of tabs, each containing a single Control. -// You can add and remove tabs from the Tab at any time. -// TODO rename? -// TODO implement containerShow()/containerHide() on this -type Tab interface { - Control - - // Append adds a new tab to Tab. - // The tab is added to the end of the current list of tabs. - Append(name string, control Control) - - // Delete removes the given tab. - // It panics if index is out of range. -// Delete(index int) -//TODO -} - -// NewTab creates a new Tab with no tabs. -func NewTab() Tab { - return newTab() -} diff --git a/redo/containers_darwin.go b/redo/containers_darwin.go deleted file mode 100644 index 0e0221a..0000000 --- a/redo/containers_darwin.go +++ /dev/null @@ -1,48 +0,0 @@ -// 25 july 2014 - -package ui - -import ( - "unsafe" -) - -// #include "objc_darwin.h" -import "C" - -type tab struct { - *widgetbase - - containers []*container -} - -func newTab() Tab { - t := new(tab) - id := C.newTab(unsafe.Pointer(t)) - t.widgetbase = newWidget(id) - return t -} - -func (t *tab) Append(name string, control Control) { - // TODO isolate and standardize - c := new(container) - t.containers = append(t.containers, c) - cname := C.CString(name) - defer C.free(unsafe.Pointer(cname)) - tabview := C.tabAppend(t.id, cname) - c.child = control - c.child.setParent(&controlParent{tabview}) -} - -func (t *tab) allocate(x int, y int, width int, height int, d *sizing) []*allocation { - // only prepared the tabbed control; its children will be reallocated when that one is resized - return t.widgetbase.allocate(x, y, width, height, d) -} - -//export tabResized -func tabResized(data unsafe.Pointer, width C.intptr_t, height C.intptr_t) { - t := (*tab)(unsafe.Pointer(data)) - for _, c := range t.containers { - // the tab area's coordinate system is localized, so the origin is (0, 0) - c.resize(0, 0, int(width), int(height)) - } -} diff --git a/redo/containers_darwin.m b/redo/containers_darwin.m deleted file mode 100644 index c99e275..0000000 --- a/redo/containers_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/containers_unix.go b/redo/containers_unix.go deleted file mode 100644 index 36118fe..0000000 --- a/redo/containers_unix.go +++ /dev/null @@ -1,71 +0,0 @@ -// +build !windows,!darwin - -// 25 july 2014 - -package ui - -import ( - "unsafe" -) - -// #include "gtk_unix.h" -// extern void layoutResizing(GtkWidget *, GdkRectangle *, gpointer); -import "C" - -type tab struct { - *widgetbase - notebook *C.GtkNotebook - - containers []*container - layoutws []*C.GtkWidget - layoutcs []*C.GtkContainer - layouts []*C.GtkLayout -} - -func newTab() Tab { - widget := C.gtk_notebook_new() - t := &tab{ - widgetbase: newWidget(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) { - // TODO isolate and standardize - layout := C.gtk_layout_new(nil, nil) - t.layoutws = append(t.layoutws, layout) - t.layoutcs = append(t.layoutcs, (*C.GtkContainer)(unsafe.Pointer(layout))) - t.layouts = append(t.layouts, (*C.GtkLayout)(unsafe.Pointer(layout))) - c := new(container) - t.containers = append(t.containers, c) - c.child = control - c.child.setParent(&controlParent{(*C.GtkContainer)(unsafe.Pointer(layout))}) - g_signal_connect_after( - C.gpointer(unsafe.Pointer(layout)), - "size-allocate", - C.GCallback(C.layoutResizing), - C.gpointer(unsafe.Pointer(c))) - cname := togstr(name) - defer freegstr(cname) - tab := C.gtk_notebook_append_page(t.notebook, - layout, - C.gtk_label_new(cname)) - if tab == -1 { - panic("gtk_notebook_append_page() failed") - } -} - -func (t *tab) allocate(x int, y int, width int, height int, d *sizing) []*allocation { - // only prepared the tabbed control; its children will be reallocated when that one is resized - return t.widgetbase.allocate(x, y, width, height, d) -} - -//export layoutResizing -func layoutResizing(wid *C.GtkWidget, r *C.GdkRectangle, data C.gpointer) { - c := (*container)(unsafe.Pointer(data)) - // the layout's coordinate system is localized, so the origin is (0, 0) - c.resize(0, 0, int(r.width), int(r.height)) -} diff --git a/redo/containers_windows.c b/redo/containers_windows.c deleted file mode 100644 index b5f3a97..0000000 --- a/redo/containers_windows.c +++ /dev/null @@ -1,64 +0,0 @@ -/* 25 july 2014 */ - -#include "winapi_windows.h" -#include "_cgo_export.h" - -/* provided for cgo's benefit */ -LPCWSTR 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, LPCWSTR name) -{ - TCITEM item; - LRESULT n; - - ZeroMemory(&item, sizeof (TCITEM)); - item.mask = TCIF_TEXT; - /* TODO the C means const; change everything to use LPWSTR instead */ - 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); -} diff --git a/redo/containers_windows.go b/redo/containers_windows.go deleted file mode 100644 index cc881e7..0000000 --- a/redo/containers_windows.go +++ /dev/null @@ -1,88 +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 { - *widgetbase - tabs []*container -} - -func newTab() Tab { - w := newWidget(C.xWC_TABCONTROL, - C.TCS_TOOLTIPS | C.WS_TABSTOP, - 0) - t := &tab{ - widgetbase: w, - } - C.controlSetControlFont(w.hwnd) - C.setTabSubclass(w.hwnd, unsafe.Pointer(t)) - return t -} - -func (t *tab) setParent(p *controlParent) { - t.widgetbase.setParent(p) - for _, c := range t.tabs { - c.child.setParent(p) - } -} - -func (t *tab) Append(name string, control Control) { - c := new(container) - t.tabs = append(t.tabs, c) - c.child = control - if t.parent != nil { - c.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 { - c.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() -} - -// a tab control contains other controls; size appropriately -func (t *tab) allocate(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 _, c := range t.tabs { - // because each widget is actually a child of the Window, the origin is the one we calculated above - c.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.widgetbase.allocate(x, y, width, height, d) -} diff --git a/redo/controls.go b/redo/controls.go deleted file mode 100644 index cda67db..0000000 --- a/redo/controls.go +++ /dev/null @@ -1,100 +0,0 @@ -// 7 july 2014 - -package ui - -// Control represents a control. -// All Controls have event handlers that take a single argument (the Doer active during the event) and return nothing. -type Control interface { - setParent(p *controlParent) // controlParent defined per-platform - // TODO enable/disable (public) - // TODO show/hide (public) - containerShow() // for Windows, where all controls need ot belong to an overlapped window, not to a container control; these respect programmer settings - containerHide() - controlSizing -} - -// Button is a clickable button that performs some task. -type Button interface { - Control - - // OnClicked sets the event handler for when the Button is clicked. - OnClicked(func()) - - // Text and SetText get and set the Button's label text. - Text() string - SetText(text string) -} - -// NewButton creates a new Button with the given label text. -func NewButton(text string) Button { - return newButton(text) -} - -// Checkbox is a clickable box that indicates some Boolean value. -type Checkbox interface { - Control - - // OnClicked sets the event handler for when the Checkbox is clicked (to change its toggle state). - // TODO change to OnToggled() or OnChecked()? - OnClicked(func()) - - // Text and SetText get and set the Checkbox's label text. - Text() string - SetText(text string) - - // Checked and SetChecked get and set the Checkbox's check state. - Checked() bool - SetChecked(checked bool) -} - -// NewCheckbox creates a new Checkbox with the given label text. -// The Checkbox will be initially unchecked. -func NewCheckbox(text string) Checkbox { - return newCheckbox(text) -} - -// TextField is a Control in which the user can enter a single line of text. -type TextField interface { - Control - - // TODO figure out what events are appropriate - - // Text and SetText are Requests that get and set the TextField's text. - Text() string - SetText(text string) -} - -// NewTextField creates a new TextField. -func NewTextField() TextField { - return newTextField() -} - -// NewPasswordField creates a new TextField for entering passwords; that is, it hides the text being entered. -func NewPasswordField() TextField { - return newPasswordField() -} - - -// Label is a Control that shows a static line of text. -// Label shows one line of text; any text that does not fit is truncated. -// A Label can either have smart vertical alignment relative to the control to its right or just be vertically aligned to the top (standalone). -type Label interface { - Control - - // Text and SetText get and set the Label's text. - Text() string - SetText(text string) -} - -// NewLabel creates a new Label with the given text. -// The Label will smartly vertically position itself relative to the control to its immediate right. -// TODO Grids on GTK+ will not respect this unless SetFilling() -func NewLabel(text string) Label { - return newLabel(text) -} - -// NewStandaloneLabel creates a new Label with the given text. -// The Label will be vertically positioned at the top of its allocated space. -func NewStandaloneLabel(text string) Label { - return newStandaloneLabel(text) -} diff --git a/redo/controls_darwin.go b/redo/controls_darwin.go deleted file mode 100644 index 78e9138..0000000 --- a/redo/controls_darwin.go +++ /dev/null @@ -1,155 +0,0 @@ -// 16 july 2014 - -package ui - -import ( - "unsafe" -) - -// #include "objc_darwin.h" -import "C" - -type widgetbase struct { - id C.id -} - -func newWidget(id C.id) *widgetbase { - return &widgetbase{ - id: id, - } -} - -// these few methods are embedded by all the various Controls since they all will do the same thing - -type controlParent struct { - id C.id -} - -func (w *widgetbase) setParent(parent *controlParent) { - // redrawing the new window handled by C.parent() - C.parent(w.id, parent.id) -} - -func (w *widgetbase) containerShow() { - C.controlSetHidden(w.id, C.NO) -} - -func (w *widgetbase) containerHide() { - C.controlSetHidden(w.id, C.YES) -} - -type button struct { - *widgetbase - clicked *event -} - -func finishNewButton(id C.id, text string) *button { - ctext := C.CString(text) - defer C.free(unsafe.Pointer(ctext)) - b := &button{ - widgetbase: newWidget(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 { - *widgetbase -} - -func finishNewTextField(id C.id) *textField { - return &textField{ - widgetbase: newWidget(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 -} - -func finishNewLabel(text string, standalone bool) *label { - l := &label{ - textField: finishNewTextField(C.newLabel()), - standalone: standalone, - } - l.SetText(text) - return l -} - -func newLabel(text string) Label { - return finishNewLabel(text, false) -} - -func newStandaloneLabel(text string) Label { - return finishNewLabel(text, true) -} - -// TODO label commitResize diff --git a/redo/controls_darwin.m b/redo/controls_darwin.m deleted file mode 100644 index c482e5e..0000000 --- a/redo/controls_darwin.m +++ /dev/null @@ -1,157 +0,0 @@ -// 16 july 2014 - -#import "objc_darwin.h" -#import "_cgo_export.h" -#import - -#define toNSView(x) ((NSView *) (x)) -#define toNSWindow(x) ((NSWindow *) (x)) -#define toNSControl(x) ((NSControl *) (x)) -#define toNSButton(x) ((NSButton *) (x)) -#define toNSTextField(x) ((NSTextField *) (x)) - -void parent(id control, id parentid) -{ - [toNSView(parentid) addSubview:toNSView(control)]; -} - -void controlSetHidden(id control, BOOL hidden) -{ - [toNSView(control) setHidden:hidden]; -} - -void setStandardControlFont(id control) -{ - [toNSControl(control) setFont:[NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSRegularControlSize]]]; -} - -@interface goControlDelegate : NSObject { -@public - void *gocontrol; -} -@end - -@implementation goControlDelegate - -- (IBAction)buttonClicked:(id)sender -{ - buttonClicked(self->gocontrol); -} - -@end - -id newButton(void) -{ - NSButton *b; - - // TODO cache the initial rect? - b = [[NSButton alloc] initWithFrame:NSMakeRect(0, 0, 100, 100)]; - [b setButtonType:NSMomentaryPushInButton]; - [b setBordered:YES]; - [b setBezelStyle:NSRoundedBezelStyle]; - setStandardControlFont((id) b); - return (id) b; -} - -void buttonSetDelegate(id button, void *b) -{ - goControlDelegate *d; - - d = [goControlDelegate new]; - d->gocontrol = b; - [toNSButton(button) setTarget:d]; - [toNSButton(button) setAction:@selector(buttonClicked:)]; -} - -const char *buttonText(id button) -{ - return [[toNSButton(button) title] UTF8String]; -} - -void buttonSetText(id button, char *text) -{ - [toNSButton(button) setTitle:[NSString stringWithUTF8String:text]]; -} - -id newCheckbox(void) -{ - NSButton *c; - - c = [[NSButton alloc] initWithFrame:NSMakeRect(0, 0, 100, 100)]; - [c setButtonType:NSSwitchButton]; - [c setBordered:NO]; - setStandardControlFont((id) c); - return (id) c; -} - -BOOL checkboxChecked(id c) -{ - if ([toNSButton(c) state] == NSOnState) - return YES; - return NO; -} - -void checkboxSetChecked(id c, BOOL checked) -{ - NSInteger state; - - state = NSOnState; - if (checked == NO) - state = NSOffState; - [toNSButton(c) setState:state]; -} - -static id finishNewTextField(NSTextField *t, BOOL bordered) -{ - // same for text fields, password fields, and labels - setStandardControlFont((id) t); - // TODO text field/password field border (Interface Builder setting is confusing) - if (!bordered) - [t setBordered:NO]; - // smart quotes and other autocorrect features are handled by the window; see newWindow() in window_darwin.m for details - // Interface Builder does this to make the text box behave properly - // this disables both word wrap AND ellipsizing in one fell swoop - // however, we need to send it to the control's cell, not to the control directly - [[t cell] setLineBreakMode:NSLineBreakByClipping]; - // Interface Builder also sets this to allow horizontal scrolling - // it also sets this for labels, despite those not being scrollable - [[t cell] setScrollable:YES]; - return (id) t; -} - -id newTextField(void) -{ - NSTextField *t; - - t = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 100, 100)]; - return finishNewTextField(t, YES); -} - -id newPasswordField(void) -{ - NSSecureTextField *t; - - t = [[NSSecureTextField alloc] initWithFrame:NSMakeRect(0, 0, 100, 100)]; - return finishNewTextField(toNSTextField(t), YES); -} - -const char *textFieldText(id t) -{ - return [[toNSTextField(t) stringValue] UTF8String]; -} - -void textFieldSetText(id t, char *text) -{ - [toNSTextField(t) setStringValue:[NSString stringWithUTF8String:text]]; -} - -id newLabel(void) -{ - NSTextField *l; - - l = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 100, 100)]; - [l setEditable:NO]; - [l setSelectable:NO]; - [l setDrawsBackground:NO]; - return finishNewTextField(l, NO); -} diff --git a/redo/controls_unix.go b/redo/controls_unix.go deleted file mode 100644 index 67d844b..0000000 --- a/redo/controls_unix.go +++ /dev/null @@ -1,212 +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 widgetbase struct { - widget *C.GtkWidget -} - -func newWidget(w *C.GtkWidget) *widgetbase { - return &widgetbase{ - widget: w, - } -} - -// these few methods are embedded by all the various Controls since they all will do the same thing - -type controlParent struct { - c *C.GtkContainer -} - -func (w *widgetbase) setParent(c *controlParent) { - C.gtk_container_add(c.c, w.widget) - // make sure the new widget is shown - C.gtk_widget_show_all(w.widget) -} - -func (w *widgetbase) containerShow() { - C.gtk_widget_show_all(w.widget) -} - -func (w *widgetbase) containerHide() { - C.gtk_widget_hide(w.widget) -} - -type button struct { - *widgetbase - 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{ - widgetbase: newWidget(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 { - *widgetbase - entry *C.GtkEntry -} - -func startNewTextField() *textField { - w := C.gtk_entry_new() - return &textField{ - widgetbase: newWidget(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 { - *widgetbase - misc *C.GtkMisc - label *C.GtkLabel - standalone bool -} - -func finishNewLabel(text string, standalone bool) *label { - ctext := togstr(text) - defer freegstr(ctext) - widget := C.gtk_label_new(ctext) - return &label{ - widgetbase: newWidget(widget), - misc: (*C.GtkMisc)(unsafe.Pointer(widget)), - label: (*C.GtkLabel)(unsafe.Pointer(widget)), - standalone: standalone, - } -} - -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) commitResize(c *allocation, d *sizing) { - if !l.standalone && c.neighbor != nil { - c.neighbor.getAuxResizeInfo(d) - if d.shouldVAlignTop { - // TODO should it be center-aligned to the first line or not - C.gtk_misc_set_alignment(l.misc, 0, 0) - } else { - C.gtk_misc_set_alignment(l.misc, 0, 0.5) - } - } - l.widgetbase.commitResize(c, d) -} diff --git a/redo/controls_windows.c b/redo/controls_windows.c deleted file mode 100644 index 8974bdb..0000000 --- a/redo/controls_windows.c +++ /dev/null @@ -1,138 +0,0 @@ -/* 17 july 2014 */ - -#include "winapi_windows.h" -#include "_cgo_export.h" - -HWND newWidget(LPCWSTR class, DWORD style, DWORD extstyle) -{ - HWND hwnd; - - hwnd = CreateWindowExW( - extstyle, - class, L"", - style | WS_CHILD | WS_VISIBLE, - CW_USEDEFAULT, CW_USEDEFAULT, - CW_USEDEFAULT, CW_USEDEFAULT, - /* - the following has the consequence of making the control message-only at first - this shouldn't cause any problems... hopefully not - but see the msgwndproc() for caveat info - also don't use low control IDs as they will conflict with dialog boxes (IDCANCEL, etc.) - */ - msgwin, (HMENU) 100, hInstance, NULL); - if (hwnd == NULL) - xpanic("error creating control", GetLastError()); - return hwnd; -} - -void controlSetParent(HWND control, HWND parent) -{ - if (SetParent(control, parent) == NULL) - xpanic("error changing control parent", GetLastError()); -} - -void controlSetControlFont(HWND which) -{ - SendMessageW(which, WM_SETFONT, (WPARAM) controlFont, TRUE); -} - -/* -all controls that have events receive the events themselves through subclasses -to do this, all windows (including the message-only window; see http://support.microsoft.com/default.aspx?scid=KB;EN-US;Q104069) forward WM_COMMAND to each control with this function -*/ -LRESULT forwardCommand(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - HWND control = (HWND) lParam; - - /* don't generate an event if the control (if there is one) is unparented (a child of the message-only window) */ - if (control != NULL && IsChild(msgwin, control) == 0) - return SendMessageW(control, msgCOMMAND, wParam, lParam); - return DefWindowProcW(hwnd, uMsg, wParam, lParam); -} - -LRESULT forwardNotify(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - NMHDR *nmhdr = (NMHDR *) lParam; - HWND control = nmhdr->hwndFrom; - - /* don't generate an event if the control (if there is one) is unparented (a child of the message-only window) */ - if (control != NULL && IsChild(msgwin, control) == 0) - return SendMessageW(control, msgNOTIFY, wParam, lParam); - return DefWindowProcW(hwnd, uMsg, wParam, lParam); -} - -static LRESULT CALLBACK buttonSubProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR id, DWORD_PTR data) -{ - switch (uMsg) { - case msgCOMMAND: - if (HIWORD(wParam) == BN_CLICKED) { - buttonClicked((void *) data); - return 0; - } - return (*fv_DefSubclassProc)(hwnd, uMsg, wParam, lParam); - case WM_NCDESTROY: - if ((*fv_RemoveWindowSubclass)(hwnd, buttonSubProc, id) == FALSE) - xpanic("error removing Button 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("Button", "buttonSubProc()", uMsg); - return 0; /* unreached */ -} - -void setButtonSubclass(HWND hwnd, void *data) -{ - if ((*fv_SetWindowSubclass)(hwnd, buttonSubProc, 0, (DWORD_PTR) data) == FALSE) - xpanic("error subclassing Button to give it its own event handler", GetLastError()); -} - -static LRESULT CALLBACK checkboxSubProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR id, DWORD_PTR data) -{ - switch (uMsg) { - case msgCOMMAND: - if (HIWORD(wParam) == BN_CLICKED) { - WPARAM check; - - /* we didn't use BS_AUTOCHECKBOX (see controls_windows.go) so we have to manage the check state ourselves */ - check = BST_CHECKED; - if (SendMessage(hwnd, BM_GETCHECK, 0, 0) == BST_CHECKED) - check = BST_UNCHECKED; - SendMessage(hwnd, BM_SETCHECK, check, 0); - checkboxToggled((void *) data); - return 0; - } - return (*fv_DefSubclassProc)(hwnd, uMsg, wParam, lParam); - case WM_NCDESTROY: - if ((*fv_RemoveWindowSubclass)(hwnd, checkboxSubProc, id) == FALSE) - xpanic("error removing Checkbox 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("Checkbox", "checkboxSubProc()", uMsg); - return 0; /* unreached */ -} - -void setCheckboxSubclass(HWND hwnd, void *data) -{ - if ((*fv_SetWindowSubclass)(hwnd, checkboxSubProc, 0, (DWORD_PTR) data) == FALSE) - xpanic("error subclassing Checkbox to give it its own event handler", GetLastError()); -} - -BOOL checkboxChecked(HWND hwnd) -{ - if (SendMessage(hwnd, BM_GETCHECK, 0, 0) == BST_UNCHECKED) - return FALSE; - return TRUE; -} - -void checkboxSetChecked(HWND hwnd, BOOL c) -{ - WPARAM check; - - check = BST_CHECKED; - if (c == FALSE) - check = BST_UNCHECKED; - SendMessage(hwnd, BM_SETCHECK, check, 0); -} diff --git a/redo/controls_windows.go b/redo/controls_windows.go deleted file mode 100644 index ecc1494..0000000 --- a/redo/controls_windows.go +++ /dev/null @@ -1,203 +0,0 @@ -// 15 july 2014 - -package ui - -import ( - "unsafe" -) - -// #include "winapi_windows.h" -import "C" - -type widgetbase struct { - hwnd C.HWND - parent C.HWND -} - -func newWidget(class C.LPCWSTR, style C.DWORD, extstyle C.DWORD) *widgetbase { - return &widgetbase{ - hwnd: C.newWidget(class, style, extstyle), - } -} - -// these few methods are embedded by all the various Controls since they all will do the same thing - -type controlParent struct { - hwnd C.HWND -} - -func (w *widgetbase) setParent(win *controlParent) { - C.controlSetParent(w.hwnd, win.hwnd) - w.parent = win.hwnd -} - -func (w *widgetbase) containerShow() { - C.ShowWindow(w.hwnd, C.SW_SHOW) -} - -func (w *widgetbase) containerHide() { - C.ShowWindow(w.hwnd, C.SW_HIDE) -} - -// don't embed these as exported; let each Control decide if it should - -func (w *widgetbase) text() string { - return getWindowText(w.hwnd) -} - -func (w *widgetbase) settext(text string) { - C.setWindowText(w.hwnd, toUTF16(text)) -} - -type button struct { - *widgetbase - clicked *event -} - -var buttonclass = toUTF16("BUTTON") - -func startNewButton(text string, style C.DWORD) *button { - w := newWidget(buttonclass, - style | C.WS_TABSTOP, - 0) - C.setWindowText(w.hwnd, toUTF16(text)) - C.controlSetControlFont(w.hwnd) - b := &button{ - widgetbase: w, - clicked: newEvent(), - } - return b -} - -func newButton(text string) *button { - b := startNewButton(text, C.BS_PUSHBUTTON) - C.setButtonSubclass(b.hwnd, unsafe.Pointer(b)) - 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") -} - -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.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") -} - -type textField struct { - *widgetbase -} - -var editclass = toUTF16("EDIT") - -func startNewTextField(style C.DWORD) *textField { - w := newWidget(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(w.hwnd) - return &textField{ - widgetbase: w, - } -} - -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) -} - -type label struct { - *widgetbase - standalone bool -} - -var labelclass = toUTF16("STATIC") - -func finishNewLabel(text string, standalone bool) *label { - w := newWidget(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.setWindowText(w.hwnd, toUTF16(text)) - C.controlSetControlFont(w.hwnd) - l := &label{ - widgetbase: w, - standalone: standalone, - } - 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) -} - -// TODO label commitResize -- cgit v1.2.3