// 16 may 2015 #include "uipriv_windows.hpp" // You don't add controls directly to a tab control on Windows; instead you make them siblings and swap between them on a TCN_SELCHANGING/TCN_SELCHANGE notification pair. // In addition, you use dialogs because they can be textured properly; other controls cannot. (Things will look wrong if the tab background in the current theme is fancy if you just use the tab background by itself; see http://stackoverflow.com/questions/30087540/why-are-my-programss-tab-controls-rendering-their-background-in-a-blocky-way-b.) struct uiTab { uiWindowsControl c; HWND hwnd; // of the outer container HWND tabHWND; // of the tab control itself std::vector *pages; HWND parent; }; // utility functions static LRESULT curpage(uiTab *t) { return SendMessageW(t->tabHWND, TCM_GETCURSEL, 0, 0); } static struct tabPage *tabPage(uiTab *t, int i) { return (*(t->pages))[i]; } static void tabPageRect(uiTab *t, RECT *r) { // this rect needs to be in parent window coordinates, but TCM_ADJUSTRECT wants a window rect, which is screen coordinates // because we have each page as a sibling of the tab, use the tab's own rect as the input rect uiWindowsEnsureGetWindowRect(t->tabHWND, r); SendMessageW(t->tabHWND, TCM_ADJUSTRECT, (WPARAM) FALSE, (LPARAM) r); // and get it in terms of the container instead of the screen mapWindowRect(NULL, t->hwnd, r); } static void tabRelayout(uiTab *t) { struct tabPage *page; RECT r; // first move the tab control itself uiWindowsEnsureGetClientRect(t->hwnd, &r); uiWindowsEnsureMoveWindowDuringResize(t->tabHWND, r.left, r.top, r.right - r.left, r.bottom - r.top); // then the current page if (t->pages->size() == 0) return; page = tabPage(t, curpage(t)); tabPageRect(t, &r); uiWindowsEnsureMoveWindowDuringResize(page->hwnd, r.left, r.top, r.right - r.left, r.bottom - r.top); } static void showHidePage(uiTab *t, LRESULT which, int hide) { struct tabPage *page; if (which == (LRESULT) (-1)) return; page = tabPage(t, which); if (hide) ShowWindow(page->hwnd, SW_HIDE); else { ShowWindow(page->hwnd, SW_SHOW); // we only resize the current page, so we have to resize it; before we can do that, we need to make sure we are of the right size uiWindowsControlMinimumSizeChanged(uiWindowsControl(t)); } } // control implementation static BOOL onWM_NOTIFY(uiControl *c, HWND hwnd, NMHDR *nm, LRESULT *lResult) { uiTab *t = uiTab(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 uiTabDestroy(uiControl *c) { uiTab *t = uiTab(c); uiControl *child; for (struct tabPage *&page : *(t->pages)) { child = page->child; tabPageDestroy(page); if (child != NULL) { uiControlSetParent(child, NULL); uiControlDestroy(child); } } delete t->pages; uiWindowsUnregisterWM_NOTIFYHandler(t->tabHWND); uiWindowsEnsureDestroyWindow(t->tabHWND); uiWindowsEnsureDestroyWindow(t->hwnd); uiFreeControl(uiControl(t)); } uiWindowsControlDefaultHandle(uiTab) uiWindowsControlDefaultParent(uiTab) uiWindowsControlDefaultSetParent(uiTab) uiWindowsControlDefaultToplevel(uiTab) uiWindowsControlDefaultVisible(uiTab) uiWindowsControlDefaultShow(uiTab) uiWindowsControlDefaultHide(uiTab) uiWindowsControlDefaultEnabled(uiTab) uiWindowsControlDefaultEnable(uiTab) uiWindowsControlDefaultDisable(uiTab) uiWindowsControlDefaultSetFocus(uiTab) static void uiTabSyncEnableState(uiWindowsControl *c, int enabled) { uiTab *t = uiTab(c); if (uiWindowsShouldStopSyncEnableState(uiWindowsControl(t), enabled)) return; EnableWindow(t->tabHWND, enabled); for (struct tabPage *&page : *(t->pages)) if (page->child != NULL) uiWindowsControlSyncEnableState(uiWindowsControl(page->child), enabled); } uiWindowsControlDefaultSetParentHWND(uiTab) static void uiTabMinimumSize(uiWindowsControl *c, int *width, int *height) { uiTab *t = uiTab(c); int pagewid, pageht; struct tabPage *page; RECT r; // only consider the current page pagewid = 0; pageht = 0; if (t->pages->size() != 0) { page = tabPage(t, curpage(t)); tabPageMinimumSize(page, &pagewid, &pageht); } r.left = 0; r.top = 0; r.right = pagewid; r.bottom = pageht; // this also includes the tabs themselves SendMessageW(t->tabHWND, TCM_ADJUSTRECT, (WPARAM) TRUE, (LPARAM) (&r)); *width = r.right - r.left; *height = r.bottom - r.top; } static void uiTabMinimumSizeChanged(uiWindowsControl *c) { uiTab *t = uiTab(c); if (uiWindowsControlTooSmall(uiWindowsControl(t))) { uiWindowsControlContinueMinimumSizeChanged(uiWindowsControl(t)); return; } tabRelayout(t); } static void uiTabSetMinSize(uiControl *c, int w, int h) { // checkme uiTabMinimumSizeChanged(uiWindowsControl(c)); } uiWindowsControlDefaultLayoutRect(uiTab) uiWindowsControlDefaultAssignControlIDZOrder(uiTab) static void uiTabChildVisibilityChanged(uiWindowsControl *c) { // TODO eliminate the redundancy uiWindowsControlMinimumSizeChanged(c); } static void tabArrangePages(uiTab *t) { LONG_PTR controlID = 100; HWND insertAfter = NULL; // TODO is this first or last? uiWindowsEnsureAssignControlIDZOrder(t->tabHWND, &controlID, &insertAfter); for (struct tabPage *&page : *(t->pages)) uiWindowsEnsureAssignControlIDZOrder(page->hwnd, &controlID, &insertAfter); } void uiTabAppend(uiTab *t, const char *name, uiControl *child) { uiTabInsertAt(t, name, t->pages->size(), child); } void uiTabInsertAt(uiTab *t, const char *name, int n, uiControl *child) { struct tabPage *page; LRESULT hide, show; TCITEMW item; WCHAR *wname; // see below hide = curpage(t); if (child != NULL) uiControlSetParent(child, uiControl(t)); page = newTabPage(child); uiWindowsEnsureSetParentHWND(page->hwnd, t->hwnd); t->pages->insert(t->pages->begin() + n, page); tabArrangePages(t); ZeroMemory(&item, sizeof (TCITEMW)); item.mask = TCIF_TEXT; wname = toUTF16(name); item.pszText = wname; if (SendMessageW(t->tabHWND, TCM_INSERTITEM, (WPARAM) n, (LPARAM) (&item)) == (LRESULT) -1) logLastError(L"error adding tab to uiTab"); 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); } } void uiTabDelete(uiTab *t, int n) { struct tabPage *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->tabHWND, TCM_DELETEITEM, (WPARAM) n, 0) == FALSE) logLastError(L"error deleting uiTab tab"); // now delete the page itself page = tabPage(t, n); if (page->child != NULL) uiControlSetParent(page->child, NULL); tabPageDestroy(page); t->pages->erase(t->pages->begin() + n); } int uiTabNumPages(uiTab *t) { return t->pages->size(); } int uiTabMargined(uiTab *t, int n) { return tabPage(t, n)->margined; } void uiTabSetMargined(uiTab *t, int n, int margined) { struct tabPage *page; page = tabPage(t, n); page->margined = margined; // even if the page doesn't have a child it might still have a new minimum size with margins; this is the easiest way to verify it uiWindowsControlMinimumSizeChanged(uiWindowsControl(t)); } static void onResize(uiWindowsControl *c) { tabRelayout(uiTab(c)); } uiTab *uiNewTab(void) { uiTab *t; uiWindowsNewControl(uiTab, t); t->hwnd = uiWindowsMakeContainer(uiWindowsControl(t), onResize); t->tabHWND = uiWindowsEnsureCreateControlHWND(0, WC_TABCONTROLW, L"", TCS_TOOLTIPS | WS_TABSTOP, hInstance, NULL, TRUE); uiWindowsEnsureSetParentHWND(t->tabHWND, t->hwnd); uiWindowsRegisterWM_NOTIFYHandler(t->tabHWND, onWM_NOTIFY, uiControl(t)); t->pages = new std::vector; return t; }