From 329b4f29ddd3215f92cc0226b6231c5a61bbbc01 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Wed, 6 May 2015 18:37:21 -0400 Subject: [PATCH] Implemented tab navigation across tabs on Windows. --- ui_windows.h | 2 +- windows/main.c | 34 ++++++++++++++++++++++++++-------- windows/newcontrol.c | 6 +++--- windows/tab.c | 37 +++++++++++++++++++++++++++++++++---- windows/uipriv_windows.h | 6 +++++- 5 files changed, 68 insertions(+), 17 deletions(-) diff --git a/ui_windows.h b/ui_windows.h index de8dcec9..b7f12c6c 100644 --- a/ui_windows.h +++ b/ui_windows.h @@ -66,7 +66,7 @@ enum { uiWindowsSysFuncContainerDisable, // This is interpreted by controls that are tab stops; the control should set HasTabStops to TRUE if so, and *LEAVE IT ALONE* if not. // You only need this if implementing your own uiControl. - // Controls created with uiWindowsMakeControl() check for the presence of WS_TABSTOP. + // Controls created with uiWindowsMakeControl() check for the window being enabled and the presence of WS_TABSTOP. // The name is "has tab stops" because it is used by uiTabs to say "does the current tab page have tab stops?". uiWindowsSysFuncHasTabStops, }; diff --git a/windows/main.c b/windows/main.c index bd973b72..44233c9a 100644 --- a/windows/main.c +++ b/windows/main.c @@ -3,7 +3,25 @@ // #qo LDFLAGS: -luser32 -lkernel32 -lgdi32 -luxtheme -lmsimg32 -lcomdlg32 -lole32 -loleaut32 -loleacc -luuid -static void uimsgloop_else(MSG *msg) +static void msgloop_tab(HWND active, HWND focus, MSG *msg) +{ + BOOL hasChildren; + BOOL idm; + + // THIS BIT IS IMPORTANT: if the current tab has no children, then there will be no children left in the dialog to tab to, and IsDialogMessageW() will loop forever + hasChildren = SendMessageW(focus, msgHasTabStops, 0, 0); + if (hasChildren) + tabEnterTabNavigation(focus); + idm = IsDialogMessageW(active, msg); + if (hasChildren) + tabLeaveTabNavigation(focus); + if (idm != 0) + return; + TranslateMessage(msg); + DispatchMessage(msg); +} + +static void msgloop_else(MSG *msg) { TranslateMessage(msg); DispatchMessage(msg); @@ -24,7 +42,7 @@ void uiMain(void) break; active = GetActiveWindow(); if (active == NULL) { - uimsgloop_else(&msg); + msgloop_else(&msg); continue; } @@ -33,20 +51,20 @@ void uiMain(void) // as for Tabs, we can't have both WS_TABSTOP and WS_EX_CONTROLPARENT set at the same time, so we hotswap the two styles to get the behavior we want focus = GetFocus(); if (focus != NULL) { -/*TODO switch (windowClassOf(focus, areaWindowClass, WC_TABCONTROLW, NULL)) { - case 0: // areaWindowClass - uimsgloop_area(active, focus, &msg); + switch (windowClassOf(focus, L"TODO Area not yet implemented", WC_TABCONTROLW, NULL)) { + case 0: // uiArea +// msgloop_area(active, focus, &msg); continue; case 1: // WC_TABCONTROLW - uimsgloop_tab(active, focus, &msg); + msgloop_tab(active, focus, &msg); continue; } // else fall through -*/ } + } if (IsDialogMessage(active, &msg) != 0) continue; - uimsgloop_else(&msg); + msgloop_else(&msg); } } diff --git a/windows/newcontrol.c b/windows/newcontrol.c index 5fe97b71..0539ad32 100644 --- a/windows/newcontrol.c +++ b/windows/newcontrol.c @@ -19,7 +19,6 @@ static void singleDestroy(uiControl *c) if (s->parent != NULL) complain("attempt to destroy a uiControl at %p while it still has a parent", c); - SendMessageW(s->hwnd, msgCanDestroyNow, 0, 0); (*(s->onDestroy))(s->onDestroyData); if (DestroyWindow(s->hwnd) == 0) logLastError("error destroying control in singleDestroy()"); @@ -119,8 +118,9 @@ static void singleSysFunc(uiControl *c, uiControlSysFuncParams *p) EnableWindow(s->hwnd, FALSE); return; case uiWindowsSysFuncHasTabStops: - if ((getStyle(s->hwnd) & WS_TABSTOP) != 0) - p->HasTabStops = TRUE; + if (IsWindowEnabled(s->hwnd) != 0) + if ((getStyle(s->hwnd) & WS_TABSTOP) != 0) + p->HasTabStops = TRUE; return; } complain("unknown p->Func %d in singleSysFunc()", p->Func); diff --git a/windows/tab.c b/windows/tab.c index 0c5874ae..ee9041b2 100644 --- a/windows/tab.c +++ b/windows/tab.c @@ -133,9 +133,11 @@ static void tabSysFunc(uiControl *c, uiControlSysFuncParams *p) // we handle tab stops specially if (p->Func == uiWindowsSysFuncHasTabStops) { - // if there are no tabs, it is not a tab stop - if (t->pages->len != 0) - p->HasTabStops = TRUE; + // if disabled, not a tab stop + if (IsWindowEnabled(t->hwnd) != 0) + // if there are no tabs, it is not a tab stop + if (t->pages->len != 0) + p->HasTabStops = TRUE; return; } // otherwise distribute it throughout all pages @@ -181,6 +183,9 @@ static LRESULT CALLBACK tabSubProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM l WINDOWPOS *wp = (WINDOWPOS *) lParam; LRESULT lResult; RECT r; + LRESULT n; + uiControlSysFuncParams p; + struct tabPage *page; switch (uMsg) { case WM_WINDOWPOSCHANGED: @@ -197,6 +202,15 @@ static LRESULT CALLBACK tabSubProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM l // these are in screen coordinates, which match what WM_WINDOWPOSCHANGED gave us (see http://stackoverflow.com/questions/29598334/are-the-coordinates-in-windowpos-on-wm-windowposchanged-in-parent-coordinates-or) resizeTab(t, r.right - r.left, r.bottom - r.top); return 0; + case msgHasTabStops: + n = SendMessageW(t->hwnd, TCM_GETCURSEL, 0, 0); + if (n == (LRESULT) (-1)) // no current selection == no tab stops + return FALSE; + p.Func = uiWindowsSysFuncHasTabStops; + p.HasTabStops = FALSE; + page = ptrArrayIndex(t->pages, struct tabPage *, n); + uiControlSysFunc(uiControl(page->bin), &p); + return p.HasTabStops; case WM_NCDESTROY: if ((*fv_RemoveWindowSubclass)(hwnd, tabSubProc, uIdSubclass) == FALSE) logLastError("error removing Tab resize handling subclass in tabSubProc()"); @@ -332,7 +346,7 @@ uiTab *uiNewTab(void) p.dwExStyle = 0; // don't set WS_EX_CONTROLPARENT yet; we do that dynamically in the message loop (see main_windows.c) p.lpClassName = WC_TABCONTROLW; p.lpWindowName = L""; - p.dwStyle = TCS_TOOLTIPS | WS_TABSTOP; // start with this; we will alternate between this and WS_EX_CONTROLPARENT as needed (see main.c and msgHasTabStops above) + p.dwStyle = TCS_TOOLTIPS | WS_TABSTOP; // start with this; we will alternate between this and WS_EX_CONTROLPARENT as needed (see main.c and msgHasTabStops above and the toggling functions below) p.hInstance = hInstance; p.useStandardControlFont = TRUE; p.onWM_COMMAND = onWM_COMMAND; @@ -364,3 +378,18 @@ uiTab *uiNewTab(void) return uiTab(t); } + +// unfortunately WS_TABSTOP and WS_EX_CONTROLPARENT are mutually exclusive, so we have to toggle between them +// see main.c for more details + +void tabEnterTabNavigation(HWND hwnd) +{ + setStyle(hwnd, getStyle(hwnd) & ~WS_TABSTOP); + setExStyle(hwnd, getExStyle(hwnd) | WS_EX_CONTROLPARENT); +} + +void tabLeaveTabNavigation(HWND hwnd) +{ + setExStyle(hwnd, getExStyle(hwnd) & ~WS_EX_CONTROLPARENT); + setStyle(hwnd, getStyle(hwnd) | WS_TABSTOP); +} diff --git a/windows/uipriv_windows.h b/windows/uipriv_windows.h index 26345c18..560deec0 100644 --- a/windows/uipriv_windows.h +++ b/windows/uipriv_windows.h @@ -35,7 +35,7 @@ enum { msgCOMMAND = WM_APP + 0x40, // start offset just to be safe msgNOTIFY, msgUpdateChild, // fake because Windows seems to SWP_NOSIZE MoveWindow()s and SetWindowPos()s that don't change the window size (even if SWP_NOSIZE isn't specified) - msgCanDestroyNow, + msgHasTabStops, }; // debug.c @@ -83,3 +83,7 @@ extern void freeMenubar(HMENU); // alloc.c extern int initAlloc(void); + +// tab.c +extern void tabEnterTabNavigation(HWND); +extern void tabLeaveTabNavigation(HWND);