#include "alfpriv.h" #include "alfcompat.h" #define TABP_BODY 10 #define ALF_NB_FLAG_TAB_RECTS_INVALID 1 #define ALF_NB_FLAG_SWITCHER_FOCUSED 2 #define ALF_NB_FLAG_SWITCHER_HIDEFOCUS 4 typedef struct { ALFListHeader list; TCHAR title[64]; // FIXME DWORD layoutFlags; HWND hwndPanel; RECT calculatedTabRect; SIZE contentSize; } ALFNotebookPage; typedef struct { HWND hwndContainer; HWND hwndSwitcher; HTHEME hTheme; ALFColor tabPaneBgColor; DWORD flags; int dpi; ALFListHeader pages; ALFNotebookPage *selectedPage; int totalSwitcherWidth; HFONT font; int fontHeight; SIZE oldContainerSize; SIZE oldSwitcherSize; } ALFNotebookPriv; static ALFNotebookPriv * ALF_Notebook_CreatePriv(void) { ALFNotebookPriv *priv = ALF_New(ALFNotebookPriv, 1); priv->tabPaneBgColor = ALF_COLOR_SYS(COLOR_BTNFACE); priv->fontHeight = 12; // FIXME! ALF_ListInit(&priv->pages); return priv; } static int ALF_Notebook_SwitcherHeight(ALFNotebookPriv *priv) { return priv->fontHeight + 8; } static void ALF_Notebook_PanelRect(ALFNotebookPriv *priv, RECT *rc) { GetClientRect(priv->hwndContainer, rc); rc->left += 4; rc->right -= 4; rc->top += ALF_Notebook_SwitcherHeight(priv) + 1; rc->bottom -= 4; } static void ALF_Notebook_PanelToContainerRect(ALFNotebookPriv *priv, RECT *rc) { rc->left -= 4; rc->right += 4; rc->top -= ALF_Notebook_SwitcherHeight(priv) + 1; rc->bottom += 4; } static void ALF_Notebook_FreePage(ALFNotebookPriv *priv, ALFNotebookPage *page) { (void)priv; DestroyWindow(page->hwndPanel); ALF_Free(page); } static void ALF_Notebook_FreePriv(ALFNotebookPriv *priv) { ALF_FOR_LIST(ALFNotebookPage, list, &priv->pages, p) { ALF_Notebook_FreePage(priv, p); } ALF_Compat_CloseThemeData(priv->hTheme); priv->hTheme = NULL; ALF_Free(priv); } static int ALF_Notebook_InternalTabCount(ALFNotebookPriv *priv) { int i = 0; ALF_FOR_LIST(ALFNotebookPage, list, &priv->pages, p) { ++i; } return i; } static HWND ALF_Notebook_InternalTabPanel(ALFNotebookPriv *priv, int index) { int i = 0; ALF_FOR_LIST(ALFNotebookPage, list, &priv->pages, p) { if (i == index) { return p->hwndPanel; } ++i; } return NULL; } static HWND ALF_Notebook_InternalSelectedPanel(ALFNotebookPriv *priv) { if (priv->selectedPage) { return priv->selectedPage->hwndPanel; } else { return NULL; } } static void ALF_Notebook_InternalDoTabChange(ALFNotebookPriv *priv, ALFNotebookPage *newSelected) { ALFNotebookPage *oldSelected = priv->selectedPage; priv->selectedPage = newSelected; ALF_FOR_LIST(ALFNotebookPage, list, &priv->pages, p) { if (p == priv->selectedPage) { SetWindowPos(p->hwndPanel, NULL, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOMOVE | SWP_SHOWWINDOW | SWP_NOZORDER); } else { SetWindowPos(p->hwndPanel, NULL, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOMOVE | SWP_HIDEWINDOW | SWP_NOZORDER); } } // redraw only part that changed if (oldSelected) { RECT rcOld = oldSelected->calculatedTabRect; InflateRect(&rcOld, 2, 0); InvalidateRect(priv->hwndSwitcher, &rcOld, TRUE); } RECT rcNew = newSelected->calculatedTabRect; InflateRect(&rcNew, 2, 0); InvalidateRect(priv->hwndSwitcher, &rcNew, TRUE); HWND currentFocus = GetFocus(); if (currentFocus != priv->hwndSwitcher) { HWND focusControl = GetNextDlgTabItem(priv->hwndContainer, priv->hwndSwitcher, FALSE); ALF_SetFocus(focusControl); } } static void ALF_Notebook_SetSingleTabBackground(ALFNotebookPriv *priv, HWND panel) { SendMessage(panel, ALF_WM_SETBGCOLOR, 0, (LPARAM)priv->tabPaneBgColor); } static void ALF_Notebook_SetAllTabBackgrounds(ALFNotebookPriv *priv) { ALF_FOR_LIST(ALFNotebookPage, list, &priv->pages, p) { if (p->layoutFlags & ALF_LAYOUT_INHERITBGCOLOR) { ALF_Notebook_SetSingleTabBackground(priv, p->hwndPanel); } } } static void ALF_Notebook_PropagateFontToPages(ALFNotebookPriv *priv, HFONT font, LPARAM lparam) { ALF_FOR_LIST(ALFNotebookPage, list, &priv->pages, p) { if (p->layoutFlags & ALF_LAYOUT_INHERITFONT) { SendMessage(p->hwndPanel, WM_SETFONT, (WPARAM)font, lparam); } } } static void ALF_Notebook_PropagateDpiChange(ALFNotebookPriv *priv, WPARAM wparam, LPARAM lparam) { ALF_FOR_LIST(ALFNotebookPage, list, &priv->pages, p) { SendMessage(p->hwndPanel, ALF_WM_DPICHANGE, wparam, lparam); } } static void ALF_Notebook_CalculateTabContentSize(ALFNotebookPriv *priv, ALFNotebookPage *page) { // FIXME! content size invalidation HDC dc = GetDC(priv->hwndSwitcher); HFONT oldfont = SelectFont(dc, priv->font); RECT rc = { 0, 0, 0, 0 }; DrawText(dc, page->title, -1, &rc, DT_SINGLELINE|DT_NOPREFIX|DT_CALCRECT); SelectFont(dc, oldfont); ReleaseDC(priv->hwndSwitcher, dc); page->contentSize.cx = rc.right - rc.left; page->contentSize.cy = priv->fontHeight; } static void ALF_Notebook_InvalidateTabRects(ALFNotebookPriv *priv) { priv->flags |= ALF_NB_FLAG_TAB_RECTS_INVALID; InvalidateRect(priv->hwndSwitcher, NULL, TRUE); ALF_InvalidateLayout(priv->hwndContainer); } static void ALF_Notebook_ValidateTabRects(ALFNotebookPriv *priv) { if (!(priv->flags & ALF_NB_FLAG_TAB_RECTS_INVALID)) return; priv->flags &= ~(DWORD)ALF_NB_FLAG_TAB_RECTS_INVALID; priv->totalSwitcherWidth = 2; ALF_FOR_LIST(ALFNotebookPage, list, &priv->pages, p) { ALF_Notebook_CalculateTabContentSize(priv, p); p->calculatedTabRect.left = priv->totalSwitcherWidth; p->calculatedTabRect.top = 0; p->calculatedTabRect.right = priv->totalSwitcherWidth + p->contentSize.cx + 12; p->calculatedTabRect.bottom = ALF_Notebook_SwitcherHeight(priv); priv->totalSwitcherWidth = p->calculatedTabRect.right; } priv->totalSwitcherWidth += 2; } static BOOL ALF_Notebook_CheckSingleColorBits(void *bits, SIZE s, COLORREF *outColor) { int l = s.cx * s.cy; DWORD c; CopyMemory(&c, bits, 4); c &= 0x00FFFFFF; for (int i = 1; i < l; ++i) { DWORD c2; CopyMemory(&c2, (char*)bits + i*4, 4); c2 &= 0x00FFFFFF; if (c2 != c) return FALSE; } *outColor = c; return TRUE; } static void ALF_Notebook_InternalHandleThemeChange(HWND hwndNotebook, ALFNotebookPriv *priv) { ALF_Compat_CloseThemeData(priv->hTheme); priv->hTheme = NULL; priv->tabPaneBgColor = ALF_COLOR_SYS(COLOR_BTNFACE); if (ALF_Compat_IsAppThemed()) priv->hTheme = ALF_Compat_OpenThemeData(hwndNotebook, L"Tab"); InvalidateRect(hwndNotebook, NULL, TRUE); ALF_InvalidateLayout(hwndNotebook); if (priv->hTheme) { priv->tabPaneBgColor = ALF_COLOR_TRANSPARENT; // SPEED HACK: check if tab pane background is a single solid color // Windows 10 really sucks at stretching a completely white bitmap HDC dcNotebook = GetDC(hwndNotebook); HDC dcMem = CreateCompatibleDC(dcNotebook); SIZE s; if (SUCCEEDED(ALF_Compat_GetThemePartSize(priv->hTheme, dcNotebook, TABP_BODY, 0, NULL, TS_TRUE, &s))) { BITMAPINFO bi; ZeroMemory(&bi, sizeof(bi)); bi.bmiHeader.biSize = sizeof(bi.bmiHeader); bi.bmiHeader.biWidth = s.cx; bi.bmiHeader.biHeight = s.cy; bi.bmiHeader.biPlanes = 1; bi.bmiHeader.biBitCount = 32; bi.bmiHeader.biCompression = BI_RGB; void *bits = NULL; HBITMAP hDib = CreateDIBSection(dcMem, &bi, DIB_RGB_COLORS, &bits, NULL, 0); if (hDib) { HBITMAP hbmOld = SelectBitmap(dcMem, hDib); RECT r = { 0, 0, s.cx, s.cy }; if (SUCCEEDED(ALF_Compat_DrawThemeBackground(priv->hTheme, dcMem, TABP_BODY, 0, &r, NULL))) { GdiFlush(); COLORREF c = 0; if (ALF_Notebook_CheckSingleColorBits(bits, s, &c)) { priv->tabPaneBgColor = (ALFColor)c; } } SelectBitmap(dcMem, hbmOld); DeleteObject(hDib); } } DeleteDC(dcMem); ReleaseDC(hwndNotebook, dcNotebook); } ALF_Notebook_SetAllTabBackgrounds(priv); } static ALFNotebookPage * ALF_Notebook_AppendPage(ALFNotebookPriv *priv, const TCHAR *title) { ALFNotebookPage *page = ALF_New(ALFNotebookPage, 1); page->hwndPanel = ALF_CreatePanelWindow(priv->hwndContainer, (WORD)-1); page->layoutFlags = ALF_LAYOUT_SENDBGCHANGE | ALF_LAYOUT_SENDDPICHANGE | ALF_LAYOUT_INHERITBGCOLOR | ALF_LAYOUT_INHERITFONT; lstrcpyn(page->title, title, sizeof(page->title)/sizeof(page->title[0])); RECT r; ALF_Notebook_PanelRect(priv, &r); SetWindowPos(page->hwndPanel, NULL, r.left, r.top, r.right - r.left, r.bottom - r.top, SWP_HIDEWINDOW|SWP_NOACTIVATE|SWP_NOZORDER|SWP_NOOWNERZORDER); SendMessage(page->hwndPanel, WM_SETFONT, (WPARAM)priv->font, 0); SendMessage(page->hwndPanel, ALF_WM_DPICHANGE, 0, (LPARAM)priv->dpi); ALF_Notebook_SetSingleTabBackground(priv, page->hwndPanel); ALF_ListInsert(priv->pages.prev, &page->list); ALF_Notebook_InvalidateTabRects(priv); if (page->list.prev == &priv->pages && page->list.next == &priv->pages) { // we are the only tab ALF_Notebook_InternalDoTabChange(priv, page); } return page; } static void ALF_Notebook_SwitcherPaint(ALFNotebookPriv *priv, HDC dc, RECT *rcDraw) { (void)rcDraw; // TODO: restrict some of our drawing to the invalidation rect ALF_Notebook_ValidateTabRects(priv); HFONT oldfont = SelectFont(dc, priv->font); RECT rcClient; GetClientRect(priv->hwndSwitcher, &rcClient); HBRUSH hbrBtnFace = GetSysColorBrush(COLOR_BTNFACE); COLORREF clrBtnFace = GetSysColor(COLOR_BTNFACE); COLORREF clrBtnHilight = GetSysColor(COLOR_3DHILIGHT); COLORREF clrBtnDdkShadow = GetSysColor(COLOR_3DDKSHADOW); COLORREF clrBtnText = GetSysColor(COLOR_BTNTEXT); if (!priv->selectedPage || priv->pages.next != &priv->selectedPage->list) { RECT rcLeftTop = { 0, ALF_Notebook_SwitcherHeight(priv) - 1, 2, ALF_Notebook_SwitcherHeight(priv) }; DrawEdge(dc, &rcLeftTop, EDGE_RAISED, BF_SOFT|BF_TOPLEFT); RECT rcLeftSpace = { 0, 0, rcLeftTop.right, rcLeftTop.top }; FillRect(dc, &rcLeftSpace, hbrBtnFace); } ALF_FOR_LIST(ALFNotebookPage, list, &priv->pages, p) { RECT r = p->calculatedTabRect; if (p != priv->selectedPage) { RECT rBottom = { r.left + 2, r.bottom - 1, r.right - 2, r.bottom }; r.bottom -= 1; r.top += 2; if (!priv->selectedPage || p->list.prev != &priv->selectedPage->list) { RECT rLeft = { r.left, r.top + 2, r.left + 2, r.bottom }; DrawEdge(dc, &rLeft, EDGE_RAISED, BF_LEFT|BF_SOFT); // could have used the BF_DIAGONAL_* funtions here, but we want // to draw each pixel at most once for flicker-free display SetPixel(dc, r.left + 1, r.top + 1, clrBtnHilight); SetPixel(dc, r.left, r.top, clrBtnFace); SetPixel(dc, r.left + 1, r.top, clrBtnFace); SetPixel(dc, r.left, r.top + 1, clrBtnFace); rBottom.left -= 2; } if (!priv->selectedPage || p->list.next != &priv->selectedPage->list) { RECT rRight = { r.right - 2, r.top + 2, r.right, r.bottom }; DrawEdge(dc, &rRight, EDGE_RAISED, BF_RIGHT|BF_SOFT); // could have used the BF_DIAGONAL_* funtions here, but we want // to draw each pixel at most once for flicker-free display SetPixel(dc, r.right - 2, r.top + 1, clrBtnDdkShadow); SetPixel(dc, r.right - 2, r.top, clrBtnFace); SetPixel(dc, r.right - 1, r.top, clrBtnFace); SetPixel(dc, r.right - 1, r.top + 1, clrBtnFace); rBottom.right += 2; } RECT rTop = { r.left + 2, r.top, r.right - 2, r.top + 2 }; DrawEdge(dc, &rTop, EDGE_RAISED, BF_TOP|BF_SOFT); DrawEdge(dc, &rBottom, EDGE_RAISED, BF_SOFT|BF_TOP); RECT rTopSpace = { rBottom.left, r.top-2, rBottom.right, r.top }; FillRect(dc, &rTopSpace, hbrBtnFace); r.left += 2; r.right -= 2; } else { InflateRect(&r, 2, 0); RECT rLeft = { r.left, r.top + 2, r.left + 2, r.bottom }; RECT rRight = { r.right - 2, r.top + 2, r.right, r.bottom }; RECT rTop = { r.left + 2, r.top, r.right - 2, r.top + 1 }; DrawEdge(dc, &rLeft, EDGE_RAISED, BF_SOFT|BF_LEFT); DrawEdge(dc, &rRight, EDGE_RAISED, BF_SOFT|BF_RIGHT); DrawEdge(dc, &rTop, EDGE_RAISED, BF_SOFT|BF_TOP); // could have used the BF_DIAGONAL_* funtions here, but we want // to draw each pixel at most once for flicker-free display SetPixel(dc, r.left + 1, r.top + 1, clrBtnHilight); SetPixel(dc, r.left, r.top, clrBtnFace); SetPixel(dc, r.left + 1, r.top, clrBtnFace); SetPixel(dc, r.left, r.top + 1, clrBtnFace); // could have used the BF_DIAGONAL_* funtions here, but we want // to draw each pixel at most once for flicker-free display SetPixel(dc, r.right - 2, r.top + 1, clrBtnDdkShadow); SetPixel(dc, r.right - 2, r.top, clrBtnFace); SetPixel(dc, r.right - 1, r.top, clrBtnFace); SetPixel(dc, r.right - 1, r.top + 1, clrBtnFace); r.left += 2; r.right -= 2; RECT rBottom = { r.left, r.bottom - 2, r.right, r.bottom }; FillRect(dc, &rBottom, hbrBtnFace); r.bottom -= 2; } r.top += 1; SetBkColor(dc, clrBtnFace); SetBkMode(dc, OPAQUE); SetTextColor(dc, clrBtnText); int textX = r.left + (r.right - r.left - p->contentSize.cx) / 2; int textY = r.top + (r.bottom - r.top - p->contentSize.cy) / 2; ExtTextOut(dc, textX, textY, ETO_OPAQUE, &r, p->title, (UINT)lstrlen(p->title), NULL); if (p == priv->selectedPage && priv->flags & ALF_NB_FLAG_SWITCHER_FOCUSED && !(priv->flags & ALF_NB_FLAG_SWITCHER_HIDEFOCUS)) { RECT rcFocus = { p->calculatedTabRect.left + 1, p->calculatedTabRect.top + 3, p->calculatedTabRect.right - 1, p->calculatedTabRect.bottom - 2 }; DrawFocusRect(dc, &rcFocus); } } RECT rcRightTop = { priv->totalSwitcherWidth, ALF_Notebook_SwitcherHeight(priv) - 1, rcClient.right-rcClient.left, ALF_Notebook_SwitcherHeight(priv) }; if (!priv->selectedPage || priv->pages.prev != &priv->selectedPage->list) { rcRightTop.left -= 2; } if (rcRightTop.left < rcRightTop.right) { DrawEdge(dc, &rcRightTop, EDGE_RAISED, BF_SOFT|BF_TOPRIGHT); RECT rcRightSpace = { rcRightTop.left, 0, rcRightTop.right, rcRightTop.top }; FillRect(dc, &rcRightSpace, GetSysColorBrush(COLOR_BTNFACE)); } SelectFont(dc, oldfont); } static void ALF_Notebook_HandleSwitcherUIState(ALFNotebookPriv *priv) { LRESULT uistate = SendMessage(priv->hwndSwitcher, WM_QUERYUISTATE, 0, 0); if (uistate & UISF_HIDEFOCUS) { priv->flags |= ALF_NB_FLAG_SWITCHER_HIDEFOCUS; } else { priv->flags &= ~(DWORD)ALF_NB_FLAG_SWITCHER_HIDEFOCUS; } if (priv->selectedPage) { InvalidateRect(priv->hwndSwitcher, &priv->selectedPage->calculatedTabRect, TRUE); } } static LRESULT CALLBACK ALF_Notebook_SwitcherWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { ALFNotebookPriv *priv = (ALFNotebookPriv *)GetWindowLongPtr(hwnd, 0); if (!priv) return DefWindowProc(hwnd, uMsg, wParam, lParam); if (uMsg == WM_ERASEBKGND) { return TRUE; } else if (uMsg == WM_PAINT) { PAINTSTRUCT ps; HDC dc = BeginPaint(hwnd, &ps); ALF_Notebook_SwitcherPaint(priv, dc, &ps.rcPaint); EndPaint(hwnd, &ps); return 0; } else if (uMsg == WM_LBUTTONDOWN) { int xpos = GET_X_LPARAM(lParam); int ypos = GET_Y_LPARAM(lParam); ALF_FOR_LIST(ALFNotebookPage, list, &priv->pages, p) { if (xpos >= p->calculatedTabRect.left && xpos < p->calculatedTabRect.right && ypos >= p->calculatedTabRect.top && ypos < p->calculatedTabRect.bottom) { if (p != priv->selectedPage) { ALF_Notebook_InternalDoTabChange(priv, p); } else { SetFocus(priv->hwndSwitcher); } break; } } } else if (uMsg == WM_WINDOWPOSCHANGED) { WINDOWPOS *pos = (WINDOWPOS *)lParam; if (!(pos->flags & SWP_NOSIZE)) { RECT rcClient; GetClientRect(hwnd, &rcClient); if (rcClient.bottom - rcClient.top != priv->oldSwitcherSize.cy) { // height changed - this should basically never happen, do it the easy way InvalidateRect(hwnd, NULL, TRUE); } if (rcClient.right - rcClient.left > priv->oldSwitcherSize.cx) { // width increased RECT rc = { priv->oldSwitcherSize.cx - 2, 0, rcClient.right - rcClient.left, rcClient.bottom - rcClient.top }; InvalidateRect(hwnd, &rc, TRUE); } if (rcClient.right - rcClient.left < priv->oldSwitcherSize.cx) { // width decreased RECT rc = { rcClient.right - rcClient.left - 2, rcClient.bottom - rcClient.top - 1, rcClient.right - rcClient.left, rcClient.bottom - rcClient.top }; InvalidateRect(hwnd, &rc, TRUE); } priv->oldSwitcherSize.cx = rcClient.right - rcClient.left; priv->oldSwitcherSize.cy = rcClient.bottom - rcClient.top; } } else if (uMsg == WM_SETFOCUS) { priv->flags |= ALF_NB_FLAG_SWITCHER_FOCUSED; if (priv->selectedPage) { InvalidateRect(hwnd, &priv->selectedPage->calculatedTabRect, TRUE); } } else if (uMsg == WM_KILLFOCUS) { priv->flags &= ~(DWORD)ALF_NB_FLAG_SWITCHER_FOCUSED; if (priv->selectedPage) { InvalidateRect(hwnd, &priv->selectedPage->calculatedTabRect, TRUE); } } else if (uMsg == WM_UPDATEUISTATE) { LRESULT r = DefWindowProc(hwnd, uMsg, wParam, lParam); ALF_Notebook_HandleSwitcherUIState(priv); return r; } else if (uMsg == WM_KEYDOWN) { if (wParam == VK_LEFT && priv->selectedPage && priv->selectedPage->list.prev != &priv->pages) { ALF_Notebook_InternalDoTabChange(priv, ALF_LIST_CONTAINER(ALFNotebookPage, list, priv->selectedPage->list.prev)); return 0; } if (wParam == VK_RIGHT && priv->selectedPage && priv->selectedPage->list.next != &priv->pages) { ALF_Notebook_InternalDoTabChange(priv, ALF_LIST_CONTAINER(ALFNotebookPage, list, priv->selectedPage->list.next)); return 0; } } else if (uMsg == WM_GETDLGCODE) { return DLGC_WANTARROWS; } else if (uMsg == WM_DESTROY) { SetWindowLongPtr(hwnd, GWLP_USERDATA, 0); } return DefWindowProc(hwnd, uMsg, wParam, lParam); } static void ALF_Notebook_ContainerPaint(ALFNotebookPriv *priv, HDC dc, RECT *f) { if (priv->hTheme != NULL) { RECT r; ALF_Notebook_PanelRect(priv, &r); SIZE s; if (SUCCEEDED(ALF_Compat_GetThemePartSize(priv->hTheme, dc, TABP_BODY, 0, NULL, TS_TRUE, &s))) { // windows dialog tab panes tile the background horizontally for (int x = r.left; x < r.right; x += s.cx) { RECT r2 = { x, r.top, x + s.cx, r.bottom }; if (r2.right > r.right) r2.right = r.right; if (f && r2.left > f->right && r2.right < f->left) continue; if (RectVisible(dc, &r2)) ALF_Compat_DrawThemeBackground(priv->hTheme, dc, TABP_BODY, 0, &r2, NULL); } } else { // old version - let uxtheme stretch it ALF_Compat_DrawThemeBackground(priv->hTheme, dc, TABP_BODY, 0, &r, NULL); } ExcludeClipRect(dc, r.left, r.top, r.right, r.bottom); } RECT rcClient; GetClientRect(priv->hwndContainer, &rcClient); RECT rcBorder = { 0, ALF_Notebook_SwitcherHeight(priv), rcClient.right - rcClient.left, rcClient.bottom - rcClient.top }; DrawEdge(dc, &rcBorder, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_RIGHT|BF_BOTTOM|BF_ADJUST); FillRect(dc, &rcBorder, GetSysColorBrush(COLOR_BTNFACE)); } static DWORD ALF_Notebook_GetControlFlags(ALFNotebookPriv *priv, HWND control) { ALF_FOR_LIST(ALFNotebookPage, list, &priv->pages, p) { if (p->hwndPanel == control) { return p->layoutFlags; } } return 0; } static BOOL ALF_Notebook_SetControlFlags(ALFNotebookPriv *priv, HWND control, DWORD flags) { ALF_FOR_LIST(ALFNotebookPage, list, &priv->pages, p) { if (p->hwndPanel == control) { if (p->layoutFlags == flags) return TRUE; if ((flags & ALF_LAYOUT_INHERITFONT) && !(p->layoutFlags & ALF_LAYOUT_INHERITFONT)) { SendMessage(p->hwndPanel, WM_SETFONT, (WPARAM)priv->font, TRUE); } if ((flags & ALF_LAYOUT_INHERITBGCOLOR) && !(p->layoutFlags & ALF_LAYOUT_INHERITBGCOLOR)) { ALF_Notebook_SetSingleTabBackground(priv, p->hwndPanel); } p->layoutFlags = flags; return TRUE; } } return FALSE; } static LRESULT CALLBACK ALF_Notebook_ContainerWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { ALFNotebookPriv *priv = (ALFNotebookPriv *)GetWindowLongPtr(hwnd, 0); if (ALF_ShouldMessageBubble(hwnd, uMsg, wParam, lParam)) return SendMessage(GetParent(hwnd), uMsg, wParam, lParam); if (uMsg == WM_CREATE && priv == NULL) { priv = ALF_Notebook_CreatePriv(); priv->hwndContainer = hwnd; priv->hwndSwitcher = ALF_CreateControlWindow(0, TEXT(""), WS_CHILD | WS_VISIBLE | WS_TABSTOP, 0, 0, 100, 40, // FIXME! priv->hwndContainer, NULL, ALF_Notebook_SwitcherWndProc, NULL); SetWindowLongPtr(priv->hwndSwitcher, 0, (LONG_PTR)priv); SetWindowLongPtr(priv->hwndContainer, 0, (LONG_PTR)priv); ALF_Notebook_InternalHandleThemeChange(hwnd, priv); ALF_Notebook_InvalidateTabRects(priv); ALF_Notebook_HandleSwitcherUIState(priv); } if (!priv) return DefWindowProc(hwnd, uMsg, wParam, lParam); if (uMsg == WM_DESTROY) { DestroyWindow(priv->hwndSwitcher); ALF_Notebook_FreePriv(priv); SetWindowLongPtr(hwnd, 0, 0); priv = NULL; } else if (uMsg == WM_SETFONT) { priv->font = (HFONT)wParam; HDC dc = GetDC(hwnd); HFONT oldfont = SelectFont(dc, priv->font); TEXTMETRIC tm; ZeroMemory(&tm, sizeof(tm)); if (GetTextMetrics(dc, &tm)) { priv->fontHeight = tm.tmHeight; } SelectFont(dc, oldfont); ReleaseDC(hwnd, dc); ALF_Notebook_PropagateFontToPages(priv, (HFONT)wParam, lParam); ALF_Notebook_InvalidateTabRects(priv); ALF_InvalidateLayout(hwnd); } else if (uMsg == WM_GETFONT) { return (LRESULT)priv->font; } else if (uMsg == ALF_WM_QUERYSIZE) { ALF_Notebook_ValidateTabRects(priv); RECT r = { 0, 0, 0, 0 }; ALF_FOR_LIST(ALFNotebookPage, list, &priv->pages, p) { SIZE s = { 0, 0 }; SendMessage(p->hwndPanel, ALF_WM_QUERYSIZE, 0, (LPARAM)&s); if (s.cx > r.right) r.right = s.cx; if (s.cy > r.bottom) r.bottom = s.cy; } if (!r.right) r.right = 100; // FIXME! if (!r.bottom) r.bottom = 100; // FIXME! ALF_Notebook_PanelToContainerRect(priv, &r); if (r.right - r.left < priv->totalSwitcherWidth) r.right = r.left + priv->totalSwitcherWidth; SIZE *ps = (SIZE*)lParam; if (ps->cx < r.right - r.left) { ps->cx = r.right - r.left; } if (ps->cy < r.bottom - r.top) { ps->cy = r.bottom - r.top; } return 0; } else if (uMsg == WM_WINDOWPOSCHANGED) { WINDOWPOS *pos = (WINDOWPOS *)lParam; if (!(pos->flags & SWP_NOSIZE)) { RECT rcPanel; RECT rcClient; ALF_Notebook_PanelRect(priv, &rcPanel); GetClientRect(hwnd, &rcClient); HDWP hdwp = BeginDeferWindowPos(10 /*FIXME*/); hdwp = DeferWindowPos(hdwp, priv->hwndSwitcher, NULL, 0, 0, rcClient.right-rcClient.left, ALF_Notebook_SwitcherHeight(priv), SWP_NOMOVE|SWP_NOACTIVATE|SWP_NOZORDER); RECT oldPanelRect = { 0,0,0,0 }; if (priv->selectedPage) { GetClientRect(priv->selectedPage->hwndPanel, &oldPanelRect); } ALF_FOR_LIST(ALFNotebookPage, list, &priv->pages, p) { hdwp = DeferWindowPos(hdwp, p->hwndPanel, NULL, rcPanel.left, rcPanel.top, rcPanel.right - rcPanel.left, rcPanel.bottom - rcPanel.top, SWP_NOACTIVATE|SWP_NOZORDER); } EndDeferWindowPos(hdwp); if (priv->selectedPage && priv->hTheme && priv->tabPaneBgColor == ALF_COLOR_TRANSPARENT && ((oldPanelRect.bottom - oldPanelRect.top) != (rcClient.bottom - rcClient.top))) { // only needed when height changes, since bg is tiled horizontally ALF_InvalidateBackground(priv->selectedPage->hwndPanel); } if (rcClient.bottom - rcClient.top > priv->oldContainerSize.cy) { // height increased RECT rc = { 0, priv->oldContainerSize.cy - 4, rcClient.right - rcClient.left, rcClient.bottom - rcClient.top }; InvalidateRect(hwnd, &rc, TRUE); } if (rcClient.bottom - rcClient.top < priv->oldContainerSize.cy) { // height decreased RECT rc = { 0, rcClient.bottom - rcClient.top - 4, rcClient.right - rcClient.left, rcClient.bottom - rcClient.top }; InvalidateRect(hwnd, &rc, TRUE); } if (rcClient.right - rcClient.left > priv->oldContainerSize.cx) { // width increased RECT rc = { priv->oldContainerSize.cx - 4, 0, rcClient.right - rcClient.left, rcClient.bottom - rcClient.top }; InvalidateRect(hwnd, &rc, TRUE); } if (rcClient.right - rcClient.left < priv->oldContainerSize.cx) { // width decreased RECT rc = { rcClient.right - rcClient.left - 4, 0, rcClient.right - rcClient.left, rcClient.bottom - rcClient.top }; InvalidateRect(hwnd, &rc, TRUE); } priv->oldContainerSize.cx = rcClient.right - rcClient.left; priv->oldContainerSize.cy = rcClient.bottom - rcClient.top; } } else if (uMsg == ALF_WM_NTBK_ADDTAB) { return (LRESULT)ALF_Notebook_AppendPage(priv, (TCHAR *)lParam)->hwndPanel; } else if (uMsg == ALF_WM_NTBK_TABCOUNT) { return (LRESULT)ALF_Notebook_InternalTabCount(priv); } else if (uMsg == ALF_WM_NTBK_GETPANEL) { return (LRESULT)ALF_Notebook_InternalTabPanel(priv, (int)lParam); } else if (uMsg == ALF_WM_NTBK_GETSELPANEL) { return (LRESULT)ALF_Notebook_InternalSelectedPanel(priv); } else if (uMsg == WM_THEMECHANGED) { ALF_Notebook_InternalHandleThemeChange(hwnd, priv); } else if (uMsg == WM_ERASEBKGND) { return TRUE; } else if (uMsg == WM_PRINTCLIENT) { RECT r = {0, 0, 0, 0}; GetClientRect(hwnd, &r); ALF_Notebook_ContainerPaint(priv, (HDC)wParam, &r); return TRUE; } else if (uMsg == WM_PAINT) { PAINTSTRUCT ps; HDC dc = BeginPaint(hwnd, &ps); ALF_Notebook_ContainerPaint(priv, dc, &ps.rcPaint); EndPaint(hwnd, &ps); return 0; } else if (uMsg == ALF_WM_INVALIDATELAYOUT) { ALF_InvalidateLayout(GetParent(hwnd)); } else if (uMsg == ALF_WM_LYT_GETCTLFLAGS) { return (LRESULT)ALF_Notebook_GetControlFlags(priv, (HWND)wParam); } else if (uMsg == ALF_WM_LYT_SETCTLFLAGS) { return (LRESULT)ALF_Notebook_SetControlFlags(priv, (HWND)wParam, (DWORD)lParam); } else if (uMsg == ALF_WM_DPICHANGE) { priv->dpi = (int)lParam; ALF_Notebook_PropagateDpiChange(priv, wParam, lParam); return TRUE; } return DefWindowProc(hwnd, uMsg, wParam, lParam); } // tab control HWND ALF_AddNotebook(HWND parent, WORD id, int x, int y) { HWND hwndNtbk = ALF_CreateControlWindow(WS_EX_CONTROLPARENT, TEXT(""), WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN, 0, 0, 100, 100, parent, (HMENU)(ULONG_PTR)id, ALF_Notebook_ContainerWindowProc, NULL); ALF_AddControl(parent, x, y, hwndNtbk, 0, 0, ALF_LAYOUT_SIZE_QUERY | ALF_LAYOUT_INHERITFONT | ALF_LAYOUT_SENDDPICHANGE); return hwndNtbk; } HWND ALF_Notebook_AddTab(HWND notebook, const TCHAR *title) { return (HWND)SendMessage(notebook, ALF_WM_NTBK_ADDTAB, 0, (LPARAM)title); } int ALF_Notebook_TabCount(HWND notebook) { return (int)SendMessage(notebook, ALF_WM_NTBK_TABCOUNT, 0, 0); } int ALF_Notebook_TabIndex(HWND notebook, HWND tabPanel); HWND ALF_Notebook_TabPanel(HWND notebook, int index) { return (HWND)SendMessage(notebook, ALF_WM_NTBK_GETPANEL, 0, (LPARAM)index); } HWND ALF_Notebook_SelectedPanel(HWND notebook) { return (HWND)SendMessage(notebook, ALF_WM_NTBK_GETSELPANEL, 0, 0); }