#include "alfpriv.h" #include "alfcompat.h" #define TABP_TOPTABITEM 5 #define TABP_TOPTABITEMLEFTEDGE 6 #define TABP_TOPTABITEMRIGHTEDGE 7 #define TABP_TOPTABITEMBOTHEDGE 8 #define TABP_PANE 9 #define TABP_BODY 10 #define TIS_NORMAL 1 #define TIS_HOT 2 #define TIS_SELECTED 3 #define TIS_DISABLED 4 #define TIS_FOCUSED 5 #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; HBRUSH tabPaneBgBrush; HBITMAP tabPaneBgBrushBitmap; 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 + 9; } static void ALF_Notebook_PanelRect(ALFNotebookPriv *priv, RECT *rc) { GetClientRect(priv->hwndContainer, rc); rc->right = rc->right - rc->left - 4; rc->bottom = rc->bottom - rc->top - 4; rc->left = 4; rc->top = ALF_Notebook_SwitcherHeight(priv); } static void ALF_Notebook_PanelToContainerRect(ALFNotebookPriv *priv, RECT *rc) { rc->left -= 4; rc->right += 4; rc->top -= ALF_Notebook_SwitcherHeight(priv); 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); DeleteBrush(priv->tabPaneBgBrush); DeleteBitmap(priv->tabPaneBgBrushBitmap); 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; // FIXME! emulate min size of original tab control // maybe its actually font-dependent instead of dpi? // should we even set a minimum size? if (page->contentSize.cx < MulDiv(30, priv->dpi, 96)) page->contentSize.cx = MulDiv(30, priv->dpi, 96); } 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); DeleteBrush(priv->tabPaneBgBrush); DeleteBitmap(priv->tabPaneBgBrushBitmap); priv->hTheme = NULL; priv->tabPaneBgBrush = NULL; priv->tabPaneBgBrushBitmap = 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); } if (priv->tabPaneBgColor == ALF_COLOR_TRANSPARENT) { // no luck at finding a single color, create a brush now priv->tabPaneBgBrushBitmap = CreateCompatibleBitmap(dcNotebook, s.cx, s.cy); HBITMAP hbmOld = SelectBitmap(dcMem, priv->tabPaneBgBrushBitmap); RECT r = { 0, 0, s.cx, s.cy }; ALF_Compat_DrawThemeBackground(priv->hTheme, dcMem, TABP_BODY, 0, &r, NULL); GdiFlush(); SelectBitmap(dcMem, hbmOld); priv->tabPaneBgBrush = CreatePatternBrush(priv->tabPaneBgBrushBitmap); } } 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 BOOL ALF_Notebook_DoRectsIntersect(const RECT *rc1, const RECT *rc2) { return rc1->left < rc2->right && rc2->left < rc1->right && rc1->top < rc2->bottom && rc2->top < rc1->bottom; } static void ALF_Notebook_SwitcherPaintClassic(ALFNotebookPriv *priv, HDC dc, RECT *rcDraw) { 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 rcLeftTopClip = { 0, 0, 2, rcClient.bottom - rcClient.top }; if (ALF_Notebook_DoRectsIntersect(&rcLeftTopClip, rcDraw)) { RECT rcLeftTop = { 0, rcClient.bottom - rcClient.top - 2, 2, rcClient.bottom - rcClient.top }; 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) { if (!ALF_Notebook_DoRectsIntersect(&r, rcDraw)) continue; // FIXME! correct clip rect calculation when next to active tab RECT rBottom = { r.left + 2, r.bottom - 2, r.right - 2, r.bottom }; r.bottom -= 2; 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); if (!ALF_Notebook_DoRectsIntersect(&r, rcDraw)) continue; RECT rLeft = { r.left, r.top + 2, r.left + 2, r.bottom - 1 }; RECT rRight = { r.right - 2, r.top + 2, r.right, r.bottom - 1 }; RECT rTop = { r.left + 2, r.top, r.right - 2, r.top + 2 }; RECT rBottom1 = { r.left + 2, r.bottom - 4, r.right - 2, r.bottom - 1 }; RECT rBottom2 = { r.left, r.bottom - 1, r.right, r.bottom }; if (r.left == 0) { rLeft.bottom += 1; rBottom2.left += 2; } if (r.right == rcClient.right-rcClient.left) { rRight.bottom += 1; rBottom2.right -= 2; } 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); FillRect(dc, &rBottom1, hbrBtnFace); FillRect(dc, &rBottom2, hbrBtnFace); r.left += 2; r.right -= 2; r.bottom -= 4; } r.top += 2; 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 - 3 }; DrawFocusRect(dc, &rcFocus); } } RECT rcRightTop = { priv->totalSwitcherWidth, rcClient.bottom - rcClient.top - 2, rcClient.right-rcClient.left, rcClient.bottom - rcClient.top }; if (!priv->selectedPage || priv->pages.prev != &priv->selectedPage->list) { rcRightTop.left -= 2; } if (rcRightTop.left < rcRightTop.right) { RECT rcRightTopClip = { rcRightTop.left, 0, rcClient.right-rcClient.left, rcClient.bottom-rcClient.top }; if (ALF_Notebook_DoRectsIntersect(&rcRightTopClip, rcDraw)) { DrawEdge(dc, &rcRightTop, EDGE_RAISED, BF_SOFT|BF_TOPRIGHT); RECT rcRightSpace = { rcRightTop.left, 0, rcRightTop.right, rcRightTop.top }; FillRect(dc, &rcRightSpace, hbrBtnFace); } } SelectFont(dc, oldfont); } static void ALF_Notebook_SwitcherPaintUx(ALFNotebookPriv *priv, HDC dc, RECT *rcDraw) { HFONT oldfont = SelectFont(dc, priv->font); RECT rcClient; GetClientRect(priv->hwndSwitcher, &rcClient); HBRUSH hbrBtnFace = GetSysColorBrush(COLOR_BTNFACE); // fill left top space if (!priv->selectedPage || priv->pages.next != &priv->selectedPage->list) { RECT rcLeftTopClip = { 0, 0, 2, rcClient.bottom - rcClient.top }; if (ALF_Notebook_DoRectsIntersect(&rcLeftTopClip, rcDraw)) { RECT rcLeftSpace = { 0, 0, 2, rcClient.bottom - rcClient.top - 2 }; FillRect(dc, &rcLeftSpace, hbrBtnFace); } } ALF_FOR_LIST(ALFNotebookPage, list, &priv->pages, p) { RECT r = p->calculatedTabRect; int part = TABP_TOPTABITEM; if (r.left <= 4 && r.right >= rcClient.right - rcClient.left - 4) { part = TABP_TOPTABITEMBOTHEDGE; } else if (r.left <= 4) { part = TABP_TOPTABITEMLEFTEDGE; } else if (r.right >= rcClient.right - rcClient.left - 4) { part = TABP_TOPTABITEMRIGHTEDGE; } if (p != priv->selectedPage) { r.bottom -= 2; if (!ALF_Notebook_DoRectsIntersect(&r, rcDraw)) continue; // FIXME! correct clip rect calculation when next to active tab r.top += 2; RECT rTabDraw = r; RECT rClip = { r.left + 2, r.top, r.right - 2, r.bottom }; if (!priv->selectedPage || p->list.prev != &priv->selectedPage->list) { rClip.left -= 2; } if (!priv->selectedPage || p->list.next != &priv->selectedPage->list) { rClip.right += 2; } RECT rTopSpace = { rClip.left, r.top-2, rClip.right, r.top }; FillRect(dc, &rTopSpace, hbrBtnFace); ALF_Compat_DrawThemeBackground(priv->hTheme, dc, part, TIS_NORMAL, &rTabDraw, &rClip); r.left += 2; r.right -= 2; } else { InflateRect(&r, 2, 0); if (!ALF_Notebook_DoRectsIntersect(&r, rcDraw)) continue; RECT rTabDraw = { r.left, r.top, r.right, r.bottom }; ALF_Compat_DrawThemeBackground(priv->hTheme, dc, part, TIS_SELECTED, &rTabDraw, rcDraw); r.left += 2; r.right -= 2; r.bottom -= 2; } r.top += 1; SetBkMode(dc, TRANSPARENT); SetTextColor(dc, GetSysColor(COLOR_BTNTEXT)); // FIXME! theme color DrawText(dc, p->title, -1, &r, DT_NOPREFIX|DT_SINGLELINE|DT_CENTER|DT_VCENTER); 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); } } // fill right top space RECT rcRightTop = { priv->totalSwitcherWidth, 0, rcClient.right-rcClient.left, rcClient.bottom - rcClient.top - 2 }; if (!priv->selectedPage || priv->pages.prev != &priv->selectedPage->list) { rcRightTop.left -= 2; } if (rcRightTop.left < rcRightTop.right) { RECT rcRightTopClip = { rcRightTop.left, 0, rcClient.right-rcClient.left, rcClient.bottom-rcClient.top }; if (ALF_Notebook_DoRectsIntersect(&rcRightTopClip, rcDraw)) { FillRect(dc, &rcRightTop, hbrBtnFace); } } // at last, paint the bottom border if (priv->selectedPage) { RECT rcSelected = priv->selectedPage->calculatedTabRect; InflateRect(&rcSelected, 2, 0); ExcludeClipRect(dc, rcSelected.left, rcSelected.top, rcSelected.right, rcSelected.bottom); } RECT rcContainerClient; GetClientRect(priv->hwndContainer, &rcContainerClient); RECT rcBottomClip = { 0, rcClient.bottom-rcClient.top-2, rcClient.right-rcClient.left, rcClient.bottom-rcClient.top }; RECT rcBottomFake = { rcBottomClip.left, rcBottomClip.top, rcBottomClip.right, rcContainerClient.bottom-rcContainerClient.top }; ALF_Compat_DrawThemeBackground(priv->hTheme, dc, TABP_PANE, 0, &rcBottomFake, &rcBottomClip); SelectFont(dc, oldfont); } static void ALF_Notebook_SwitcherPaint(ALFNotebookPriv *priv, HDC dc, RECT *rcDraw) { if (priv->hTheme) { ALF_Notebook_SwitcherPaintUx(priv, dc, rcDraw); } else { ALF_Notebook_SwitcherPaintClassic(priv, dc, rcDraw); } } 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 (priv->hTheme) { // bottom border might be a stretched bitmap, just redraw the whole thing if (rcClient.right - rcClient.left != priv->oldSwitcherSize.cx) { RECT rc = { 0, rcClient.bottom-rcClient.top-2, rcClient.right-rcClient.left, rcClient.bottom-rcClient.top }; InvalidateRect(hwnd, &rc, TRUE); } } else { 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 - 2, rcClient.right - rcClient.left, rcClient.bottom - rcClient.top }; InvalidateRect(hwnd, &rc, TRUE); } } // if the right border now straddles the rightmost tab or has done // so before the resize, redraw the rightmost tab (uxtheme only) // uxtheme draws a different bitmap when it straddles the border if (priv->hTheme && !ALF_ListIsEmpty(&priv->pages) && ((rcClient.right - rcClient.left) - 4 <= ALF_LIST_CONTAINER(ALFNotebookPage, list, priv->pages.prev)->calculatedTabRect.right) != (priv->oldSwitcherSize.cx - 4 <= ALF_LIST_CONTAINER(ALFNotebookPage, list, priv->pages.prev)->calculatedTabRect.right)) { ALFNotebookPage *lastpage = ALF_LIST_CONTAINER(ALFNotebookPage, list, priv->pages.prev); RECT rc = { lastpage->calculatedTabRect.left, 0, 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); if (priv->tabPaneBgBrush != NULL) { POINT vpo; if (GetViewportOrgEx(dc, &vpo)) { SetBrushOrgEx(dc, vpo.x, vpo.y, NULL); } FillRect(dc, &r, priv->tabPaneBgBrush); } else { // old version - let uxtheme stretch it ALF_Compat_DrawThemeBackground(priv->hTheme, dc, TABP_BODY, 0, &r, f); } ExcludeClipRect(dc, r.left, r.top, r.right, r.bottom); r.left -= 4; r.top -= 2; r.right += 4; r.bottom += 4; ALF_Compat_DrawThemeBackground(priv->hTheme, dc, TABP_PANE, 0, &r, f); } else { 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) != (rcPanel.bottom - rcPanel.top))) { // only needed when height changes, since bg is tiled horizontally ALF_InvalidateBackground(priv->selectedPage->hwndPanel); } if (priv->hTheme) { // themed background does contain gradients on XP // FIXME! do we always need to invalidate everyting? InvalidateRect(hwnd, NULL, TRUE); } else { 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); }