summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--redo/comctl32_windows.c2
-rw-r--r--redo/containers_windows.c64
-rw-r--r--redo/containers_windows.go93
-rw-r--r--redo/controls_windows.c11
-rw-r--r--redo/uitask_windows.c2
-rw-r--r--redo/uitask_windows.go2
-rw-r--r--redo/winapi_windows.h9
-rw-r--r--redo/window_windows.c2
-rw-r--r--redo/window_windows.go5
-rw-r--r--redo/zz_test.go15
10 files changed, 196 insertions, 9 deletions
diff --git a/redo/comctl32_windows.c b/redo/comctl32_windows.c
index 2fc978c..68d58cb 100644
--- a/redo/comctl32_windows.c
+++ b/redo/comctl32_windows.c
@@ -35,7 +35,7 @@ DWORD initCommonControls(LPCWSTR manifest, char **errmsg)
ZeroMemory(&icc, sizeof (INITCOMMONCONTROLSEX));
icc.dwSize = sizeof (INITCOMMONCONTROLSEX);
- icc.dwICC = ICC_PROGRESS_CLASS;
+ icc.dwICC = ICC_PROGRESS_CLASS | ICC_TAB_CLASSES;
comctl32 = LoadLibraryW(L"comctl32.dll");
if (comctl32 == NULL) {
diff --git a/redo/containers_windows.c b/redo/containers_windows.c
new file mode 100644
index 0000000..7b4f255
--- /dev/null
+++ b/redo/containers_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());
+}
+
+extern 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
new file mode 100644
index 0000000..a5e8ec6
--- /dev/null
+++ b/redo/containers_windows.go
@@ -0,0 +1,93 @@
+// 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. Mind the odd code here.
+
+TODO
+- make sure all tabs cannot be deselected (that is, make sure the current tab can never have index -1)
+*/
+
+type tab struct {
+ *widgetbase
+ tabs []Control
+ curparent *window
+}
+
+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) unparent() {
+ t.widgetbase.unparent()
+ for _, c := range t.tabs {
+ c.unparent()
+ }
+ t.curparent = nil
+}
+
+func (t *tab) parent(win *window) {
+ t.widgetbase.parent(win)
+ for _, c := range t.tabs {
+ c.parent(win)
+ }
+ t.curparent = win
+}
+
+func (t *tab) Append(name string, control Control) {
+ t.tabs = append(t.tabs, control)
+ if t.curparent == nil {
+ control.unparent()
+ } else {
+ control.parent(t.curparent)
+ }
+ C.tabAppend(t.hwnd, toUTF16(name))
+}
+
+//export tabChanging
+func tabChanging(data unsafe.Pointer, current C.LRESULT) {
+ t := (*tab)(data)
+ t.tabs[int(current)].containerHide()
+}
+
+//export tabChanged
+func tabChanged(data unsafe.Pointer, new C.LRESULT) {
+ t := (*tab)(data)
+ t.tabs[int(new)].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
+
+ // first, append the tab control itself
+ a := t.widgetbase.allocate(x, y, width, height, d)
+ // now figure out what the rect for each child is
+ r.left = C.LONG(x) // load rect with existing values
+ 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 hte current tab; allocate to all tabs!
+ for _, c := range t.tabs {
+ a = append(a, c.allocate(int(r.left), int(r.top), int(r.right - r.left), int(r.bottom - r.top), d)...)
+ }
+ return a
+}
diff --git a/redo/controls_windows.c b/redo/controls_windows.c
index b3edb71..8974bdb 100644
--- a/redo/controls_windows.c
+++ b/redo/controls_windows.c
@@ -50,6 +50,17 @@ LRESULT forwardCommand(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM 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) {
diff --git a/redo/uitask_windows.c b/redo/uitask_windows.c
index e9f7e8a..479b48a 100644
--- a/redo/uitask_windows.c
+++ b/redo/uitask_windows.c
@@ -37,6 +37,8 @@ static LRESULT CALLBACK msgwinproc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM l
switch (uMsg) {
case WM_COMMAND:
return forwardCommand(hwnd, uMsg, wParam, lParam);
+ case WM_NOTIFY:
+ return forwardNotify(hwnd, uMsg, wParam, lParam);
case msgRequest:
doissue((void *) lParam);
return 0;
diff --git a/redo/uitask_windows.go b/redo/uitask_windows.go
index 38f6bfe..7913a5c 100644
--- a/redo/uitask_windows.go
+++ b/redo/uitask_windows.go
@@ -8,7 +8,7 @@ import (
"unsafe"
)
-// #cgo LDFLAGS: -luser32 -lkernel32 -lgdi32
+// #cgo LDFLAGS: -luser32 -lkernel32 -lgdi32 -luxtheme
// #include "winapi_windows.h"
import "C"
diff --git a/redo/winapi_windows.h b/redo/winapi_windows.h
index d620879..dcfd1fe 100644
--- a/redo/winapi_windows.h
+++ b/redo/winapi_windows.h
@@ -17,11 +17,13 @@
#include <windows.h>
#include <commctrl.h>
#include <stdint.h>
+#include <uxtheme.h>
/* global messages unique to everything */
enum {
msgRequest = WM_APP + 1, /* + 1 just to be safe */
msgCOMMAND, /* WM_COMMAND proxy; see forwardCommand() in controls_windows.go */
+ msgNOTIFY, /* WM_NOTIFY proxy */
};
/* uitask_windows.c */
@@ -42,6 +44,7 @@ extern HWND newWidget(LPCWSTR, DWORD, DWORD);
extern void controlSetParent(HWND, HWND);
extern void controlSetControlFont(HWND);
extern LRESULT forwardCommand(HWND, UINT, WPARAM, LPARAM);
+extern LRESULT forwardNotify(HWND, UINT, WPARAM, LPARAM);
extern void setButtonSubclass(HWND, void *);
extern void setCheckboxSubclass(HWND, void *);
extern BOOL checkboxChecked(HWND);
@@ -77,4 +80,10 @@ extern void setWindowText(HWND, LPCWSTR);
extern void updateWindow(HWND);
extern void storelpParam(HWND, LPARAM);
+/* containers_windows.go */
+extern LPCWSTR xWC_TABCONTROL;
+extern void setTabSubclass(HWND, void *);
+extern void tabAppend(HWND, LPCWSTR);
+extern void tabGetContentRect(HWND, RECT *);
+
#endif
diff --git a/redo/window_windows.c b/redo/window_windows.c
index 4380094..2b1b12d 100644
--- a/redo/window_windows.c
+++ b/redo/window_windows.c
@@ -25,6 +25,8 @@ static LRESULT CALLBACK windowWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARA
switch (uMsg) {
case WM_COMMAND:
return forwardCommand(hwnd, uMsg, wParam, lParam);
+ case WM_NOTIFY:
+ return forwardNotify(hwnd, uMsg, wParam, lParam);
case WM_SIZE:
if (GetClientRect(hwnd, &r) == 0)
xpanic("error getting client rect for Window in WM_SIZE", GetLastError());
diff --git a/redo/window_windows.go b/redo/window_windows.go
index 01bc751..43a7631 100644
--- a/redo/window_windows.go
+++ b/redo/window_windows.go
@@ -44,6 +44,11 @@ func newWindow(title string, width int, height int) *window {
if hwnd != w.hwnd {
panic(fmt.Errorf("inconsistency: hwnd returned by CreateWindowEx() (%p) and hwnd stored in window (%p) differ", hwnd, w.hwnd))
}
+ // TODO keep?
+ hresult := C.EnableThemeDialogTexture(w.hwnd, C.ETDT_ENABLE | C.ETDT_USETABTEXTURE)
+ if hresult != C.S_OK {
+ panic(fmt.Errorf("error setting tab background texture on Window; HRESULT: 0x%X", hresult))
+ }
return w
}
diff --git a/redo/zz_test.go b/redo/zz_test.go
index f71fe5f..884d1d0 100644
--- a/redo/zz_test.go
+++ b/redo/zz_test.go
@@ -20,7 +20,8 @@ func init() {
Do(func() {
w := NewWindow("Hello", 320, 240)
b := NewButton("There")
- w.SetControl(b)
+ t := NewTab()
+ w.SetControl(t)
if *closeOnClick {
b.SetText("Click to Close")
}
@@ -40,14 +41,14 @@ func init() {
w.Close()
Stop()
done <- struct{}{}
- } else {
- c := NewCheckbox("You Should Now See Me Instead")
- w.SetControl(c)
- c.OnClicked(func() {
- w.SetTitle(fmt.Sprint(c.Checked()))
- })
}
})
+ t.Append("Button", b)
+ c := NewCheckbox("You Should Now See Me Instead")
+ t.Append("Checkbox", c)
+ c.OnClicked(func() {
+ w.SetTitle(fmt.Sprint(c.Checked()))
+ })
w.Show()
})
<-done