// 16 may 2015 #include "uipriv_windows.h" // TODO // - can't seem to tab away anymore // - removving the first page then pressing a BS_PUSHBUTTON button hangs struct tab { uiTab t; HWND hwnd; struct ptrArray *pages; void (*baseResize)(uiControl *, intmax_t, intmax_t, intmax_t, intmax_t, uiSizing *); void (*baseCommitDestroy)(uiControl *); }; uiDefineControlType(uiTab, uiTypeTab, struct tab) // utility functions static LRESULT curpage(struct tab *t) { return SendMessageW(t->hwnd, TCM_GETCURSEL, 0, 0); } static void showHidePage(struct tab *t, LRESULT which, int hide) { uiControl *page; if (which == (LRESULT) (-1)) return; page = ptrArrayIndex(t->pages, uiControl *, which); if (hide) uiControlHide(page); else { uiControlShow(page); // we only resize the current page, so we have to do this here uiControlQueueResize(page); } } // see below for info static void updateWS_TABSTOP(HWND hwnd) { LRESULT n; DWORD le; SetLastError(0); n = SendMessageW(hwnd, TCM_GETITEMCOUNT, 0, 0); le = GetLastError(); SetLastError(le); // just to be safe if (n != 0) setStyle(hwnd, getStyle(hwnd) | WS_TABSTOP); else if (le == 0) // truly no tabs setStyle(hwnd, getStyle(hwnd) & ~WS_TABSTOP); else logLastError("error getting number of tabs in updateWS_TABSTOP()"); } // control implementation static BOOL onWM_NOTIFY(uiControl *c, HWND hwnd, NMHDR *nm, LRESULT *lResult) { struct tab *t = (struct tab *) c; if (nm->code != TCN_SELCHANGING && nm->code != TCN_SELCHANGE) return FALSE; showHidePage(t, curpage(t), nm->code == TCN_SELCHANGING); *lResult = 0; if (nm->code == TCN_SELCHANGING) *lResult = FALSE; return TRUE; } static void tabCommitDestroy(uiControl *c) { struct tab *t = (struct tab *) c; uiControl *page; while (t->pages->len != 0) { page = ptrArrayIndex(t->pages, uiControl *, 0); ptrArrayDelete(t->pages, 0); tabPageDestroyChild(page); uiControlSetParent(page, NULL); uiControlDestroy(page); } ptrArrayDestroy(t->pages); uiWindowsUnregisterWM_NOTIFYHandler(t->hwnd); (*(t->baseCommitDestroy))(uiControl(t)); } static uintptr_t tabHandle(uiControl *c) { struct tab *t = (struct tab *) c; return (uintptr_t) (t->hwnd); } static void tabPreferredSize(uiControl *c, uiSizing *d, intmax_t *width, intmax_t *height) { struct tab *t = (struct tab *) c; intmax_t maxwid, maxht; intmax_t pagewid, pageht; uiControl *page; uintmax_t i; RECT r; maxwid = 0; maxht = 0; for (i = 0; i < t->pages->len; i++) { page = ptrArrayIndex(t->pages, uiControl *, i); uiControlPreferredSize(page, d, &pagewid, &pageht); if (maxwid < pagewid) maxwid = pagewid; if (maxht < pageht) maxht = pageht; } r.left = 0; r.top = 0; r.right = maxwid; r.bottom = maxht; // this also includes the tabs themselves SendMessageW(t->hwnd, TCM_ADJUSTRECT, (WPARAM) TRUE, (LPARAM) (&r)); *width = r.right - r.left; *height = r.bottom - r.top; } static void tabResize(uiControl *c, intmax_t x, intmax_t y, intmax_t width, intmax_t height, uiSizing *d) { struct tab *t = (struct tab *) c; LRESULT n; uiControl *page; RECT r; uiSizing *dchild; (*(t->baseResize))(uiControl(t), x, y, width, height, d); n = curpage(t); if (n == (LRESULT) (-1)) return; page = ptrArrayIndex(t->pages, uiControl *, n); dchild = uiControlSizing(uiControl(t)); // now we need to figure out what rect the child goes // this rect needs to be in toplevel window coordinates, but TCM_ADJUSTRECT wants a window rect, which is screen coordinates r.left = x; r.top = y; r.right = x + width; r.bottom = y + height; mapWindowRect(dchild->Sys->CoordFrom, NULL, &r); SendMessageW(t->hwnd, TCM_ADJUSTRECT, (WPARAM) FALSE, (LPARAM) (&r)); mapWindowRect(NULL, dchild->Sys->CoordFrom, &r); uiControlResize(page, r.left, r.top, r.right - r.left, r.bottom - r.top, dchild); uiFreeSizing(dchild); } static void tabContainerUpdateState(uiControl *c) { struct tab *t = (struct tab *) c; uiControl *page; uintmax_t i; for (i = 0; i < t->pages->len; i++) { page = ptrArrayIndex(t->pages, uiControl *, i); uiControlUpdateState(page); } } static void tabAppend(uiTab *tt, const char *name, uiControl *child) { struct tab *t = (struct tab *) tt; uiTabInsertAt(tt, name, t->pages->len, child); } static void tabInsertAt(uiTab *tt, const char *name, uintmax_t n, uiControl *child) { struct tab *t = (struct tab *) tt; uiControl *page; LRESULT hide, show; TCITEMW item; WCHAR *wname; // see below hide = curpage(t); page = newTabPage(); tabPageSetChild(page, child); uiControlSetParent(page, uiControl(t)); // and make it invisible at first; we show it later if needed uiControlHide(page); ptrArrayInsertAt(t->pages, n, page); ZeroMemory(&item, sizeof (TCITEMW)); item.mask = TCIF_TEXT; wname = toUTF16(name); item.pszText = wname; if (SendMessageW(t->hwnd, TCM_INSERTITEM, (WPARAM) n, (LPARAM) (&item)) == (LRESULT) -1) logLastError("error adding tab to uiTab in uiTabInsertAt()"); uiFree(wname); // we need to do this because adding the first tab doesn't send a TCN_SELCHANGE; it just shows the page show = curpage(t); if (show != hide) { showHidePage(t, hide, 1); showHidePage(t, show, 0); } updateWS_TABSTOP(t->hwnd); } static void tabDelete(uiTab *tt, uintmax_t n) { struct tab *t = (struct tab *) tt; uiControl *page; // first delete the tab from the tab control // if this is the current tab, no tab will be selected, which is good if (SendMessageW(t->hwnd, TCM_DELETEITEM, (WPARAM) n, 0) == FALSE) logLastError("error deleting uiTab tab in tabDelete()"); // now delete the page itself page = ptrArrayIndex(t->pages, uiControl *, n); ptrArrayDelete(t->pages, n); // and free the page tabPagePreserveChild(page); uiControlSetParent(page, NULL); uiControlDestroy(page); updateWS_TABSTOP(t->hwnd); } static uintmax_t tabNumPages(uiTab *tt) { struct tab *t = (struct tab *) tt; return t->pages->len; } static int tabMargined(uiTab *tt, uintmax_t n) { struct tab *t = (struct tab *) tt; uiControl *page; page = ptrArrayIndex(t->pages, uiControl *, n); return tabPageMargined(page); } static void tabSetMargined(uiTab *tt, uintmax_t n, int margined) { struct tab *t = (struct tab *) tt; uiControl *page; page = ptrArrayIndex(t->pages, uiControl *, n); tabPageSetMargined(page, margined); uiControlQueueResize(page); } // this handles tab navigation; see main.c for details static LRESULT CALLBACK tabSubProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData) { struct tab *t = (struct tab *) dwRefData; LRESULT n; uiControl *page; switch (uMsg) { case msgHasTabStops: n = SendMessageW(t->hwnd, TCM_GETCURSEL, 0, 0); if (n == (LRESULT) (-1)) // no current selection == no tab stops return FALSE; page = ptrArrayIndex(t->pages, uiControl *, n); if (uiControlHasTabStops(page)) return TRUE; return FALSE; case WM_NCDESTROY: if (RemoveWindowSubclass(hwnd, tabSubProc, uIdSubclass) == FALSE) logLastError("error removing Tab tab stop handling subclass in tabSubProc()"); break; } return DefSubclassProc(hwnd, uMsg, wParam, lParam); } uiTab *uiNewTab(void) { struct tab *t; t = (struct tab *) uiWindowsNewSingleHWNDControl(uiTypeTab()); t->hwnd = uiWindowsUtilCreateControlHWND(0, // don't set WS_EX_CONTROLPARENT yet; we do that dynamically in the message loop (see below) WC_TABCONTROLW, L"", // don't give WS_TABSTOP here; we only apply WS_TABSTOP if there are tabs TCS_TOOLTIPS, hInstance, NULL, TRUE); uiWindowsRegisterWM_NOTIFYHandler(t->hwnd, onWM_NOTIFY, uiControl(t)); if (SetWindowSubclass(t->hwnd, tabSubProc, 0, (DWORD_PTR) t) == FALSE) logLastError("error subclassing Tab to assist in tab stop handling in uiNewTab()"); t->pages = newPtrArray(); uiControl(t)->Handle = tabHandle; uiControl(t)->PreferredSize = tabPreferredSize; t->baseResize = uiControl(t)->Resize; uiControl(t)->Resize = tabResize; t->baseCommitDestroy = uiControl(t)->CommitDestroy; uiControl(t)->CommitDestroy = tabCommitDestroy; uiControl(t)->ContainerUpdateState = tabContainerUpdateState; uiTab(t)->Append = tabAppend; uiTab(t)->InsertAt = tabInsertAt; uiTab(t)->Delete = tabDelete; uiTab(t)->NumPages = tabNumPages; uiTab(t)->Margined = tabMargined; uiTab(t)->SetMargined = tabSetMargined; 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); updateWS_TABSTOP(hwnd); }