From 9ea22218ffacc8a3e5395d8670e9d62584969753 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Fri, 25 Jul 2014 15:58:24 -0400 Subject: [PATCH] Implemented Tab on Windows. --- redo/comctl32_windows.c | 2 +- redo/containers_windows.c | 64 ++++++++++++++++++++++++++ redo/containers_windows.go | 93 ++++++++++++++++++++++++++++++++++++++ redo/controls_windows.c | 11 +++++ redo/uitask_windows.c | 2 + redo/uitask_windows.go | 2 +- redo/winapi_windows.h | 9 ++++ redo/window_windows.c | 2 + redo/window_windows.go | 5 ++ redo/zz_test.go | 15 +++--- 10 files changed, 196 insertions(+), 9 deletions(-) create mode 100644 redo/containers_windows.c create mode 100644 redo/containers_windows.go 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 #include #include +#include /* 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