#include "alfpriv.h" #include "alfcompat.h" #define ALF_NB_ADDTAB (ALF_WM__BASE + 300) #define ALF_NB_TABCOUNT (ALF_WM__BASE + 301) #define ALF_NB_GETPANEL (ALF_WM__BASE + 302) #define ALF_NB_GETSELINDEX (ALF_WM__BASE + 303) #define ALF_NB_GETPNLINDEX (ALF_WM__BASE + 304) #define TABP_BODY 10 #define TMT_FILLCOLORHINT 3821 TCHAR *_alf_notebookClass = NULL; typedef struct { HWND hwndTabCtrl; HTHEME hTheme; ALFColor themeTabBgColor; DWORD flags; } ALFNotebookPriv; static ALFNotebookPriv * ALF_Notebook_CreatePriv(void) { ALFNotebookPriv *priv = ALF_New(ALFNotebookPriv, 1); priv->themeTabBgColor = ALF_COLOR_SYS(COLOR_BTNFACE); return priv; } void ALF_Notebook_FreePriv(ALFNotebookPriv *priv) { ALF_Compat_CloseThemeData(priv->hTheme); priv->hTheme = NULL; ALF_Free(priv); } static int ALF_Notebook_InternalTabCount(HWND notebook, HWND tabControl) { (void)notebook; return TabCtrl_GetItemCount(tabControl); } static HWND ALF_Notebook_InternalTabPanel(HWND notebook, HWND tabControl, int index) { (void)notebook; TCITEM tci; ZeroMemory(&tci, sizeof(tci)); tci.mask = TCIF_PARAM; if (TabCtrl_GetItem(tabControl, index, &tci)) { return (HWND)tci.lParam; } else { return NULL; } } static int ALF_Notebook_InternalSelectedIndex(HWND notebook, HWND tabControl) { (void)notebook; return TabCtrl_GetCurSel(tabControl); } static void ALF_Notebook_InternalHandleTabChange(HWND hwndNotebook, HWND hwndTabCtrl) { int selectedIndex = ALF_Notebook_InternalSelectedIndex(hwndNotebook, hwndTabCtrl); int n = ALF_Notebook_InternalTabCount(hwndNotebook, hwndTabCtrl); for (int i = 0; i < n; ++i) { if (i == selectedIndex) { SetWindowPos(ALF_Notebook_InternalTabPanel(hwndNotebook, hwndTabCtrl, i), HWND_TOP, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOMOVE | SWP_SHOWWINDOW); } else { ShowWindow(ALF_Notebook_InternalTabPanel(hwndNotebook, hwndTabCtrl, i), SW_HIDE); } } } static void ALF_Notebook_SetSingleTabBackground(HWND hwndNotebook, ALFNotebookPriv *priv, HWND panel) { (void)hwndNotebook; if (priv->hTheme && !(priv->flags & ALF_NOTEBOOK_SOLID_TAB_BACKGROUND)) { SendMessage(panel, ALF_WM_SETBGCOLOR, 0, (LPARAM)ALF_COLOR_TRANSPARENT); } else { SendMessage(panel, ALF_WM_SETBGCOLOR, 0, (LPARAM)priv->themeTabBgColor); } } static void ALF_Notebook_SetAllTabBackgrounds(HWND hwndNotebook, ALFNotebookPriv *priv) { int n = ALF_Notebook_InternalTabCount(hwndNotebook, priv->hwndTabCtrl); for (int i = 0; i < n; ++i) { HWND panel = ALF_Notebook_InternalTabPanel(hwndNotebook, priv->hwndTabCtrl, i); ALF_Notebook_SetSingleTabBackground(hwndNotebook, priv, panel); } } static void ALF_Notebook_InternalHandleThemeChange(HWND hwndNotebook, ALFNotebookPriv *priv) { ALF_Compat_CloseThemeData(priv->hTheme); priv->hTheme = NULL; if (ALF_Compat_IsAppThemed()) priv->hTheme = ALF_Compat_OpenThemeData(hwndNotebook, L"Tab"); InvalidateRect(hwndNotebook, NULL, TRUE); ALF_InvalidateLayout(hwndNotebook); // tab fill color for solid bg color mode priv->themeTabBgColor = ALF_COLOR_SYS(COLOR_BTNFACE); if (priv->hTheme) { COLORREF c; if (SUCCEEDED(ALF_Compat_GetThemeColor(priv->hTheme, TABP_BODY, 0, TMT_FILLCOLORHINT, &c))) { priv->themeTabBgColor = (ALFColor)c; } } ALF_Notebook_SetAllTabBackgrounds(hwndNotebook, priv); } static HWND ALF_Notebook_InternalAddTab(HWND notebook, ALFNotebookPriv *priv, const TCHAR *title) { RECT r; GetClientRect(priv->hwndTabCtrl, &r); TabCtrl_AdjustRect(priv->hwndTabCtrl, FALSE, &r); MapWindowRect(priv->hwndTabCtrl, notebook, &r); HWND hwndPanel = ALF_CreatePanelWindow(notebook, (WORD)-1); SetWindowPos(hwndPanel, NULL, r.left, r.top, r.right - r.left, r.bottom - r.top, SWP_HIDEWINDOW|SWP_NOACTIVATE|SWP_NOZORDER|SWP_NOOWNERZORDER); SendMessage(hwndPanel, WM_SETFONT, (WPARAM)SendMessage(priv->hwndTabCtrl, WM_GETFONT, 0, 0), 0); ALF_Notebook_SetSingleTabBackground(notebook, priv, hwndPanel); TCITEM tie; ZeroMemory(&tie, sizeof(tie)); tie.mask = TCIF_TEXT | TCIF_PARAM; tie.pszText = (TCHAR*)title; tie.lParam = (LPARAM)hwndPanel; TabCtrl_InsertItem(priv->hwndTabCtrl, ALF_Notebook_InternalTabCount(notebook, priv->hwndTabCtrl), &tie); ALF_Notebook_InternalHandleTabChange(notebook, priv->hwndTabCtrl); ALF_InvalidateLayout(notebook); return hwndPanel; } static LRESULT CALLBACK ALF_Notebook_TabCtrlSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData) { (void)uIdSubclass; (void)dwRefData; if (uMsg == WM_ERASEBKGND) { // see: http://www.virtualdub.org/blog/pivot/entry.php?id=291 HDC hdc = (HDC)wParam; RECT r; GetClientRect(hwnd, &r); TabCtrl_AdjustRect(hwnd, FALSE, &r); ExcludeClipRect(hdc, r.left, r.top, r.right, r.bottom); } if (uMsg == 0x2000 + WM_NOTIFY) { NMHDR *pnmh = (NMHDR*)lParam; if (pnmh->code == TCN_SELCHANGE) { ALF_Notebook_InternalHandleTabChange(GetParent(hwnd), hwnd); return TRUE; } } if (uMsg == WM_DESTROY) { ALF_Compat_RemoveWindowSubclass(hwnd, ALF_Notebook_TabCtrlSubclassProc, 0); } return ALF_Compat_DefSubclassProc(hwnd, uMsg, wParam, lParam); } static void ALF_Notebook_InternalPaint(HWND hwnd, ALFNotebookPriv *priv, HDC dc, RECT *f) { if (priv->hTheme != NULL) { RECT r; GetClientRect(priv->hwndTabCtrl, &r); TabCtrl_AdjustRect(priv->hwndTabCtrl, FALSE, &r); MapWindowRect(priv->hwndTabCtrl, hwnd, &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; 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); } FillRect(dc, f, GetSysColorBrush(COLOR_BTNFACE)); } static LRESULT CALLBACK ALF__NotebookWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { if (ALF_ShouldMessageBubble(hwnd, uMsg, wParam, lParam)) return SendMessage(GetParent(hwnd), uMsg, wParam, lParam); ALFNotebookPriv *priv = (ALFNotebookPriv *)GetWindowLongPtr(hwnd, 0); if (uMsg == WM_CREATE && priv == NULL) { priv = ALF_Notebook_CreatePriv(); priv->hwndTabCtrl = CreateWindowEx(0, WC_TABCONTROL, TEXT(""), WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_CLIPSIBLINGS, 0, 0, 100, 100, hwnd, NULL, ALF_HINSTANCE, NULL); ALF_Compat_SetWindowSubclass(priv->hwndTabCtrl, ALF_Notebook_TabCtrlSubclassProc, 0, 0); SetWindowLongPtr(hwnd, 0, (LONG_PTR)priv); ALF_Notebook_InternalHandleThemeChange(hwnd, priv); } if (!priv) return DefWindowProc(hwnd, uMsg, wParam, lParam); if (uMsg == WM_DESTROY) { ALF_Notebook_FreePriv(priv); SetWindowLongPtr(hwnd, 0, 0); priv = NULL; } else if (uMsg == WM_SETFONT) { SendMessage(priv->hwndTabCtrl, WM_SETFONT, wParam, lParam); int n = ALF_Notebook_InternalTabCount(hwnd, priv->hwndTabCtrl); for (int i = 0; i < n; ++i) { HWND p = ALF_Notebook_InternalTabPanel(hwnd, priv->hwndTabCtrl, i); SendMessage(p, WM_SETFONT, wParam, lParam); } ALF_InvalidateLayout(hwnd); } else if (uMsg == WM_GETFONT) { return SendMessage(priv->hwndTabCtrl, WM_GETFONT, wParam, lParam); } else if (uMsg == ALF_WM_QUERYSIZE) { int n = ALF_Notebook_InternalTabCount(hwnd, priv->hwndTabCtrl); RECT r = { 0, 0, 0, 0 }; for (int i = 0; i < n; ++i) { HWND p = ALF_Notebook_InternalTabPanel(hwnd, priv->hwndTabCtrl, i); SIZE s = { 0, 0 }; SendMessage(p, 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! TabCtrl_AdjustRect(priv->hwndTabCtrl, TRUE, &r); SIZE *ps = (SIZE*)lParam; if (!ps->cx) { ps->cx = r.right - r.left; } if (!ps->cy) { ps->cy = r.bottom - r.top; } return 0; } else if (uMsg == WM_WINDOWPOSCHANGED) { WINDOWPOS *pos = (WINDOWPOS *)lParam; if (!(pos->flags & SWP_NOSIZE)) { RECT r; GetClientRect(hwnd, &r); int n = ALF_Notebook_InternalTabCount(hwnd, priv->hwndTabCtrl); RECT oldR; GetWindowRect(priv->hwndTabCtrl, &oldR); HDWP hdwp = BeginDeferWindowPos(n+1); // SWP_COPYBITS: NT 3.51 sadness, will not invalidate bottom edge properly on resize // even though it redraws everything else with great flicker // the flicker problem is present in every other version of windows, too hdwp = DeferWindowPos(hdwp, priv->hwndTabCtrl, NULL, 0, 0, r.right-r.left, r.bottom-r.top, SWP_NOMOVE|SWP_NOACTIVATE|SWP_NOZORDER|SWP_NOCOPYBITS); TabCtrl_AdjustRect(priv->hwndTabCtrl, FALSE, &r); for (int i = 0; i < n; ++i) { HWND p = ALF_Notebook_InternalTabPanel(hwnd, priv->hwndTabCtrl, i); hdwp = DeferWindowPos(hdwp, p, NULL, r.left, r.top, r.right - r.left, r.bottom - r.top, SWP_NOACTIVATE|SWP_NOZORDER); } EndDeferWindowPos(hdwp); if (priv->hTheme && ((oldR.bottom - oldR.top) != (r.bottom - r.top))) { // only needed when height changes, since bg is tiled horizontally HWND panel = ALF_NotebookSelectedPanel(hwnd); if (panel != NULL) ALF_InvalidateBackground(panel); } } } else if (uMsg == ALF_NB_ADDTAB) { return (LRESULT)ALF_Notebook_InternalAddTab(hwnd, priv, (TCHAR *)lParam); } else if (uMsg == ALF_NB_TABCOUNT) { return (LRESULT)ALF_Notebook_InternalTabCount(hwnd, priv->hwndTabCtrl); } else if (uMsg == ALF_NB_GETPANEL) { return (LRESULT)ALF_Notebook_InternalTabPanel(hwnd, priv->hwndTabCtrl, (int)lParam); } else if (uMsg == ALF_NB_GETSELINDEX) { return (LRESULT)ALF_Notebook_InternalSelectedIndex(hwnd, priv->hwndTabCtrl); } 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_InternalPaint(hwnd, priv, (HDC)wParam, &r); return TRUE; } else if (uMsg == WM_PAINT) { PAINTSTRUCT ps; HDC dc = BeginPaint(hwnd, &ps); ALF_Notebook_InternalPaint(hwnd, priv, dc, &ps.rcPaint); EndPaint(hwnd, &ps); return 0; } else if (uMsg == ALF_WM_INVALIDATELAYOUT) { ALF_InvalidateLayout(GetParent(hwnd)); } else if (uMsg == ALF_WM_NTBK_GETFLAGS) { return (LRESULT)priv->flags; } else if (uMsg == ALF_WM_NTBK_SETFLAGS) { DWORD oldFlags = priv->flags; priv->flags = (DWORD)lParam; if ((oldFlags & ALF_NOTEBOOK_SOLID_TAB_BACKGROUND) != (priv->flags & ALF_NOTEBOOK_SOLID_TAB_BACKGROUND)) { ALF_Notebook_SetAllTabBackgrounds(hwnd, priv); } } return DefWindowProc(hwnd, uMsg, wParam, lParam); } void ALF_RegisterNotebookClass(void) { WNDCLASS cls; ZeroMemory(&cls, sizeof(cls)); TCHAR classNameBuf[256]; ALF_BuildUniqueName(classNameBuf, TEXT("ALFNotebook."), (ULONG_PTR)&_alf_notebookClass); cls.hInstance = ALF_HINSTANCE; cls.hCursor = LoadCursor(NULL, (LPTSTR)IDC_ARROW); cls.lpszClassName = classNameBuf; cls.cbWndExtra = sizeof(ALFNotebookPriv*); cls.lpfnWndProc = ALF__NotebookWindowProc; ATOM classatom = RegisterClass(&cls); if (!classatom) MessageBox(NULL, TEXT("FATAL: Could not register notebook class"), NULL, MB_OK); _alf_notebookClass = MAKEINTATOM(classatom); } // tab control HWND ALF_AddNotebook(HWND parent, WORD id, int x, int y) { HWND hwndNtbk = CreateWindowEx(WS_EX_CONTROLPARENT, _alf_notebookClass, TEXT(""), WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN, 0, 0, 100, 100, parent, (HMENU)(int)id, ALF_HINSTANCE, NULL); ALF_AddWidget(parent, x, y, hwndNtbk, 0, 0, ALF_LAYOUT_SIZE_QUERY | ALF_LAYOUT_INHERITFONT); return hwndNtbk; } HWND ALF_NotebookAddTab(HWND notebook, const TCHAR *title) { return (HWND)SendMessage(notebook, ALF_NB_ADDTAB, 0, (LPARAM)title); } int ALF_NotebookTabCount(HWND notebook) { return (int)SendMessage(notebook, ALF_NB_TABCOUNT, 0, 0); } int ALF_NotebookTabIndex(HWND notebook, HWND tabPanel); HWND ALF_NotebookTabPanel(HWND notebook, int index) { return (HWND)SendMessage(notebook, ALF_NB_GETPANEL, 0, (LPARAM)index); } int ALF_NotebookSelectedIndex(HWND notebook) { return (int)SendMessage(notebook, ALF_NB_GETSELINDEX, 0, 0); } HWND ALF_NotebookSelectedPanel(HWND notebook) { int i = ALF_NotebookSelectedIndex(notebook); if (i >= 0) { return ALF_NotebookTabPanel(notebook, i); } else { return NULL; } } DWORD ALF_NotebookFlags(HWND notebook) { return (DWORD)SendMessage(notebook, ALF_WM_NTBK_GETFLAGS, 0, 0); } void ALF_NotebookSetFlags(HWND notebook, DWORD flags) { SendMessage(notebook, ALF_WM_NTBK_SETFLAGS, 0, (LPARAM)flags); }