#include "alfpriv.h" /* Themed "NT" Button for Win2k and up */ #define BP_PUSHBUTTON 1 #define PBS_NORMAL 1 #define PBS_HOT 2 #define PBS_PRESSED 3 #define PBS_DISABLED 4 #define PBS_DEFAULTED 5 #define PBS_DEFAULTED_ANIMATING 6 #define BP_CHECKBOX 3 #define CBS_UNCHECKEDNORMAL 1 #define CBS_UNCHECKEDHOT 2 #define CBS_UNCHECKEDPRESSED 3 #define CBS_UNCHECKEDDISABLED 4 #define CBS_CHECKEDNORMAL 5 #define CBS_CHECKEDHOT 6 #define CBS_CHECKEDPRESSED 7 #define CBS_CHECKEDDISABLED 8 #define CBS_MIXEDNORMAL 9 #define CBS_MIXEDHOT 10 #define CBS_MIXEDPRESSED 11 #define CBS_MIXEDDISABLED 12 #define BP_RADIOBUTTON 2 #define RBS_UNCHECKEDNORMAL 1 #define RBS_UNCHECKEDHOT 2 #define RBS_UNCHECKEDPRESSED 3 #define RBS_UNCHECKEDDISABLED 4 #define RBS_CHECKEDNORMAL 5 #define RBS_CHECKEDHOT 6 #define RBS_CHECKEDPRESSED 7 #define RBS_CHECKEDDISABLED 8 #define ALF_NTBTN_FLAG_UXTHEME 1 #define ALF_NTBTN_FLAG_IS_DISABLED 2 #define ALF_NTBTN_FLAG_IS_DEFAULT 4 #define ALF_NTBTN_FLAG_IS_HOT 8 #define ALF_NTBTN_FLAG_IS_FOCUSED 16 #define ALF_NTBTN_FLAG_IS_PRESSED 32 #define ALF_NTBTN_FLAG_HIDEFOCUS 64 #define ALF_NTBTN_FLAG_HIDEACCEL 128 #define ALF_NTBTN_FLAG_DEFAULT_ANIMATING 256 #define ALF_NTBTN_FLAG_IS_CHECKED 512 #define ALF_NTBTN_FLAG_DRAW_CHECKBOX 1024 #define ALF_NTBTN_FLAG_DRAW_RADIOBTN 2048 #define ALF_NTBTN_FLAG_DRAW_TRISTATE 4096 typedef struct { DWORD drawFlags; HTHEME hTheme; int uxStatePrev; int uxStateCurrent; DWORD uxDrawFlagsCurrent; DWORD uxDrawFlagsPrev; DWORD uxDefaultAnimationDuration; int uxPartPrev; int uxPartCurrent; int dpi; HFONT font; WCHAR *text; ALFColor bgcolor; } ALFNtButtonPriv; static BOOL CALLBACK ALF_Button_DrawDisabledTextW_DrawStateProc(HDC hdc, LPARAM lData, WPARAM wData, int cx, int cy) { RECT rc = { 0, 0, cx, cy }; DrawTextW(hdc, (const WCHAR *)lData, -1, &rc, (UINT)wData); return TRUE; } static int ALF_Button_DrawDisabledTextNtW(HDC hdc, LPCWSTR lpchText, int cchText, LPRECT lprc, UINT format) { (void)cchText; return DrawStateW(hdc, NULL, ALF_Button_DrawDisabledTextW_DrawStateProc, (LPARAM)lpchText, (WPARAM)format, lprc->left, lprc->top, lprc->right - lprc->left, lprc->bottom - lprc->top, DST_COMPLEX | DSS_DISABLED); } // FIXME! this doesn’t really look correct static int ALF_Button_CalcCheckboxTextSpace(HDC hdc) { SIZE s = {0,0}; GetTextExtentPoint(hdc, TEXT("0"), 1, &s); s.cx /= 2; if (s.cx < 2) s.cx = 2; return s.cx; } static ALFNtButtonPriv * ALF_NtButton_InitializePriv(CREATESTRUCTW *cs) { ALFNtButtonPriv *priv = ALF_New(ALFNtButtonPriv, 1); priv->drawFlags = ALF_NTBTN_FLAG_UXTHEME; priv->uxStateCurrent = -1; priv->uxStatePrev = -1; priv->dpi = 96; priv->bgcolor = ALF_COLOR_SYS(COLOR_BTNFACE); SIZE_T l = (SIZE_T)lstrlenW(cs->lpszName); priv->text = ALF_New(WCHAR, l + 1); CopyMemory(priv->text, cs->lpszName, l * sizeof(WCHAR)); return priv; } static void ALF_NtButton_FreePriv(ALFNtButtonPriv *priv) { ALF_Compat_CloseThemeData(priv->hTheme); ALF_Free(priv->text); ALF_Free(priv); } static void ALF_NtButton_ModifyDrawFlags(HWND hwnd, ALFNtButtonPriv *priv, DWORD add, DWORD remove, BOOL redraw) { DWORD newFlags = priv->drawFlags; newFlags |= add; newFlags &= ~remove; if (newFlags != priv->drawFlags) { priv->drawFlags = newFlags; if (redraw) { InvalidateRect(hwnd, NULL, TRUE); } } } static void ALF_NtButton_HandleUIState(HWND hwnd, ALFNtButtonPriv *priv) { LRESULT uiState = SendMessageW(hwnd, WM_QUERYUISTATE, 0, 0); DWORD add = 0; DWORD remove = 0; if (uiState & UISF_HIDEACCEL) { add |= ALF_NTBTN_FLAG_HIDEACCEL; } else { remove |= ALF_NTBTN_FLAG_HIDEACCEL; } if (uiState & UISF_HIDEFOCUS) { add |= ALF_NTBTN_FLAG_HIDEFOCUS; } else { remove |= ALF_NTBTN_FLAG_HIDEFOCUS; } ALF_NtButton_ModifyDrawFlags(hwnd, priv, add, remove, TRUE); } static void ALF_NtButton_HandleThemeChange(HWND hwnd, ALFNtButtonPriv *priv) { ALF_Compat_CloseThemeData(priv->hTheme); priv->hTheme = NULL; if (ALF_Compat_IsAppThemed()) priv->hTheme = ALF_Compat_OpenThemeData(hwnd, L"Button"); priv->uxDefaultAnimationDuration = 0; ALF_Compat_GetThemeTransitionDuration(priv->hTheme, BP_PUSHBUTTON, PBS_DEFAULTED, PBS_DEFAULTED_ANIMATING, TMT_TRANSITIONDURATION, &priv->uxDefaultAnimationDuration); InvalidateRect(hwnd, NULL, TRUE); } static void ALF_NtButton_CalculateSize(HWND hwnd, ALFNtButtonPriv *priv, SIZE *pSize) { HDC hdc = GetDC(hwnd); HFONT oldFont = SelectFont(hdc, priv->font); // calc drawtext style UINT format = DT_LEFT | DT_EXPANDTABS | DT_CALCRECT; RECT r = { 0, 0, 0x7FFFFFFF, 100 }; DrawTextW(hdc, priv->text, -1, &r, format); if (priv->drawFlags & (ALF_NTBTN_FLAG_DRAW_CHECKBOX | ALF_NTBTN_FLAG_DRAW_RADIOBTN | ALF_NTBTN_FLAG_DRAW_TRISTATE)) { int checkwidth = 12 * priv->dpi / 96 + 1; int space = ALF_Button_CalcCheckboxTextSpace(hdc); int cx = checkwidth + space + r.right - r.left + 1; int cy = r.bottom - r.top; if (cy < checkwidth) cy = checkwidth; if (pSize->cx < cx) pSize->cx = cx; if (pSize->cy < cy) pSize->cy = cy; } else { int xpadding = ALF_Compat_GetSystemMetricsForDpi(SM_CXEDGE, (UINT)priv->dpi) * 2 + 6; int ypadding = ALF_Compat_GetSystemMetricsForDpi(SM_CYEDGE, (UINT)priv->dpi) * 2 + 4; if (pSize->cx < r.right - r.left + xpadding) { pSize->cx = r.right - r.left + xpadding; } if (pSize->cy < r.bottom - r.top + ypadding) { pSize->cy = r.bottom - r.top + ypadding; } if (pSize->cx < pSize->cy) { pSize->cx = pSize->cy; } } SelectFont(hdc, oldFont); ReleaseDC(hwnd, hdc); } static void CALLBACK ALF_NtButton_DefaultAnimatingTimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime) { (void)uMsg; (void)dwTime; ALFNtButtonPriv *priv = (ALFNtButtonPriv *)idEvent; if (priv->drawFlags & ALF_NTBTN_FLAG_DEFAULT_ANIMATING) { ALF_NtButton_ModifyDrawFlags(hwnd, priv, 0, ALF_NTBTN_FLAG_DEFAULT_ANIMATING, TRUE); } else { ALF_NtButton_ModifyDrawFlags(hwnd, priv, ALF_NTBTN_FLAG_DEFAULT_ANIMATING, 0, TRUE); } } static void ALF_NtButton_RenderUxtheme(HWND hwnd, ALFNtButtonPriv *priv, int uxpart, int uxstate, DWORD drawFlags, HDC hDC, RECT *rcPaint) { HFONT oldfont = SelectFont(hDC, priv->font); RECT r; GetClientRect(hwnd, &r); if (uxpart == BP_CHECKBOX || uxpart == BP_RADIOBUTTON) { if (priv->bgcolor == ALF_COLOR_TRANSPARENT) { ALF_Compat_DrawThemeParentBackground(hwnd, hDC, rcPaint); } else { ALF_FillRect(hDC, rcPaint, priv->bgcolor); } int checkwidth = 12 * priv->dpi / 96 + 1; RECT rcCheckmark = { 0, 0, checkwidth, checkwidth }; rcCheckmark.top += (r.bottom - r.top - checkwidth) / 2; rcCheckmark.bottom = rcCheckmark.top + checkwidth; int space = ALF_Button_CalcCheckboxTextSpace(hDC); RECT rcText = { checkwidth + space, 0, r.right - r.left, r.bottom - r.top }; ALF_Compat_DrawThemeBackground(priv->hTheme, hDC, uxpart, uxstate, &rcCheckmark, rcPaint); RECT textbounds = rcText; DrawTextW(hDC, priv->text, -1, &textbounds, DT_LEFT | DT_CALCRECT | DT_EXPANDTABS); RECT texttarget = rcText; texttarget.top += (texttarget.bottom - texttarget.top - textbounds.bottom + textbounds.top) / 2; texttarget.bottom = texttarget.top + (textbounds.bottom - textbounds.top); texttarget.right = texttarget.left + textbounds.right - textbounds.left; UINT style = DT_LEFT | DT_NOCLIP | DT_EXPANDTABS; if (priv->drawFlags & ALF_NTBTN_FLAG_HIDEACCEL) style |= DT_HIDEPREFIX; ALF_Compat_DrawThemeText(priv->hTheme, hDC, uxpart, uxstate, priv->text, -1, style, 0, &texttarget); if ((priv->drawFlags & ALF_NTBTN_FLAG_IS_FOCUSED) && !(priv->drawFlags & ALF_NTBTN_FLAG_HIDEFOCUS)) { RECT f = texttarget; InflateRect(&f, 1, 0); DrawFocusRect(hDC, &f); } } else { if (ALF_Compat_IsThemeBackgroundPartiallyTransparent(priv->hTheme, uxpart, uxstate)) { if (priv->bgcolor == ALF_COLOR_TRANSPARENT) { ALF_Compat_DrawThemeParentBackground(hwnd, hDC, rcPaint); } else { ALF_FillRect(hDC, rcPaint, priv->bgcolor); } } ALF_Compat_DrawThemeBackground(priv->hTheme, hDC, BP_PUSHBUTTON, uxstate, &r, rcPaint); RECT content = r; ALF_Compat_GetThemeBackgroundContentRect(priv->hTheme, hDC, BP_PUSHBUTTON, uxstate, &r, &content); if ((drawFlags & ALF_NTBTN_FLAG_IS_FOCUSED) && !(drawFlags & ALF_NTBTN_FLAG_HIDEFOCUS)) DrawFocusRect(hDC, &content); UINT style = DT_CENTER | DT_NOCLIP; if (drawFlags & ALF_NTBTN_FLAG_HIDEACCEL) style |= DT_HIDEPREFIX; RECT textbounds = content; ALF_Compat_GetThemeTextExtent(priv->hTheme, hDC, BP_PUSHBUTTON, uxstate, priv->text, -1, style, &content, &textbounds); RECT texttarget = content; texttarget.top += ((content.bottom - content.top) - (textbounds.bottom - textbounds.top)) / 2; texttarget.left += ((content.right - content.left) - (textbounds.right - textbounds.left)) / 2; texttarget.right = texttarget.left + (textbounds.right - textbounds.left); texttarget.bottom = texttarget.top + (textbounds.bottom - textbounds.top); ALF_Compat_DrawThemeText(priv->hTheme, hDC, BP_PUSHBUTTON, uxstate, priv->text, -1, style, 0, &texttarget); } SelectFont(hDC, oldfont); } static void ALF_NtButton_RenderClassicCheckbox(HWND hwnd, ALFNtButtonPriv *priv, HDC dc, RECT *rcPaint) { int checkwidth = 12 * priv->dpi / 96 + 1; RECT rcClient; GetClientRect(hwnd, &rcClient); if (priv->bgcolor == ALF_COLOR_TRANSPARENT) { ALF_Compat_DrawThemeParentBackground(hwnd, dc, rcPaint); } else { ALF_FillRect(dc, rcPaint, priv->bgcolor); } HFONT oldfont = SelectFont(dc, priv->font); UINT dfcs = priv->drawFlags & ALF_NTBTN_FLAG_DRAW_RADIOBTN ? DFCS_BUTTONRADIO : DFCS_BUTTONCHECK; if (priv->drawFlags & ALF_NTBTN_FLAG_IS_CHECKED) dfcs |= DFCS_CHECKED; if (priv->drawFlags & ALF_NTBTN_FLAG_IS_PRESSED) dfcs |= DFCS_PUSHED; if (priv->drawFlags & ALF_NTBTN_FLAG_IS_DISABLED) dfcs |= DFCS_INACTIVE; RECT rcCheckmark = { 0, 0, checkwidth, checkwidth }; rcCheckmark.top += (rcClient.bottom - rcClient.top - checkwidth) / 2; rcCheckmark.bottom = rcCheckmark.top + checkwidth; // lol int space = ALF_Button_CalcCheckboxTextSpace(dc); RECT rcText = { checkwidth + space, 0, rcClient.right - rcClient.left, rcClient.bottom - rcClient.top }; DrawFrameControl(dc, &rcCheckmark, DFC_BUTTON, dfcs); RECT textbounds = rcText; DrawTextW(dc, priv->text, -1, &textbounds, DT_LEFT | DT_CALCRECT | DT_EXPANDTABS); RECT texttarget = rcText; texttarget.top += (texttarget.bottom - texttarget.top - textbounds.bottom + textbounds.top) / 2; texttarget.bottom = texttarget.top + (textbounds.bottom - textbounds.top); texttarget.right = texttarget.left + textbounds.right - textbounds.left; UINT style = DT_LEFT | DT_NOCLIP | DT_EXPANDTABS; if (priv->drawFlags & ALF_NTBTN_FLAG_HIDEACCEL) style |= DT_HIDEPREFIX; COLORREF oldBkColor = SetBkColor(dc, GetSysColor(COLOR_BTNFACE)); if (priv->drawFlags & ALF_NTBTN_FLAG_IS_DISABLED) { ALF_Button_DrawDisabledTextNtW(dc, priv->text, -1, &texttarget, style); } else { DrawTextW(dc, priv->text, -1, &texttarget, style); } SetBkColor(dc, oldBkColor); if ((priv->drawFlags & ALF_NTBTN_FLAG_IS_FOCUSED) && !(priv->drawFlags & ALF_NTBTN_FLAG_HIDEFOCUS)) { RECT f = texttarget; InflateRect(&f, 1, 0); DrawFocusRect(dc, &f); } SelectFont(dc, oldfont); } static void ALF_NtButton_RenderClassicPushBtn(HWND hwnd, ALFNtButtonPriv *priv, HDC dc, RECT *rcPaint) { (void)rcPaint; RECT rcClient; GetClientRect(hwnd, &rcClient); HFONT oldfont = SelectFont(dc, priv->font); RECT r = rcClient; if (priv->drawFlags & ALF_NTBTN_FLAG_IS_DEFAULT) { HBRUSH framecolor = GetSysColorBrush(COLOR_WINDOWFRAME); FrameRect(dc, &r, framecolor); InflateRect(&r, -1, -1); } UINT dfcs = DFCS_BUTTONPUSH | DFCS_ADJUSTRECT; if (priv->drawFlags & ALF_NTBTN_FLAG_IS_PRESSED) dfcs |= DFCS_FLAT; if (priv->drawFlags & ALF_NTBTN_FLAG_IS_DISABLED) dfcs |= DFCS_INACTIVE; if (priv->drawFlags & ALF_NTBTN_FLAG_IS_HOT) dfcs |= DFCS_HOT; DrawFrameControl(dc, &r, DFC_BUTTON, dfcs); RECT textbounds = r; DrawTextW(dc, priv->text, -1, &textbounds, DT_LEFT | DT_CALCRECT); textbounds.bottom++; // this appears to be the secret for correct vertical centering RECT texttarget = r; texttarget.top += (texttarget.bottom - texttarget.top - textbounds.bottom + textbounds.top) / 2; texttarget.left += (texttarget.right - texttarget.left - textbounds.right + textbounds.left) / 2; texttarget.right = texttarget.left + (textbounds.right - textbounds.left); texttarget.bottom = texttarget.top + (textbounds.bottom - textbounds.top); if (priv->drawFlags & ALF_NTBTN_FLAG_IS_PRESSED) { texttarget.top += 1; texttarget.left += 1; texttarget.right += 1; texttarget.bottom += 1; } UINT style = DT_CENTER | DT_NOCLIP; if (priv->drawFlags & ALF_NTBTN_FLAG_HIDEACCEL) style |= DT_HIDEPREFIX; COLORREF oldBkColor = SetBkColor(dc, GetSysColor(COLOR_BTNFACE)); if (priv->drawFlags & ALF_NTBTN_FLAG_IS_DISABLED) { ALF_Button_DrawDisabledTextNtW(dc, priv->text, -1, &texttarget, style); } else { DrawTextW(dc, priv->text, -1, &texttarget, style); } SetBkColor(dc, oldBkColor); if ((priv->drawFlags & ALF_NTBTN_FLAG_IS_FOCUSED) && !(priv->drawFlags & ALF_NTBTN_FLAG_HIDEFOCUS)) { RECT f = r; InflateRect(&f, -1, -1); DrawFocusRect(dc, &f); } SelectFont(dc, oldfont); } static void ALF_NtButton_RenderClassic(HWND hwnd, ALFNtButtonPriv *priv, HDC dc, RECT *rcPaint) { if (priv->drawFlags & (ALF_NTBTN_FLAG_DRAW_CHECKBOX | ALF_NTBTN_FLAG_DRAW_RADIOBTN)) { ALF_NtButton_RenderClassicCheckbox(hwnd, priv, dc, rcPaint); } else { ALF_NtButton_RenderClassicPushBtn(hwnd, priv, dc, rcPaint); } } static void ALF_NtButton_Paint(HWND hwnd, ALFNtButtonPriv *priv, HDC dc, RECT *r) { if (!ALF_Compat_BufferedPaintRenderAnimation(hwnd, dc)) { if ((priv->drawFlags & ALF_NTBTN_FLAG_UXTHEME) && priv->hTheme) { // Draw XP style themed button int partid = 0; int stateid = 0; if (priv->drawFlags & ALF_NTBTN_FLAG_DRAW_CHECKBOX) { partid = BP_CHECKBOX; if (priv->drawFlags & ALF_NTBTN_FLAG_IS_CHECKED) { stateid = CBS_CHECKEDNORMAL; if (priv->drawFlags & ALF_NTBTN_FLAG_IS_HOT) stateid = CBS_CHECKEDHOT; if (priv->drawFlags & ALF_NTBTN_FLAG_IS_PRESSED) stateid = CBS_CHECKEDPRESSED; if (priv->drawFlags & ALF_NTBTN_FLAG_IS_DISABLED) stateid = CBS_CHECKEDDISABLED; } else { stateid = CBS_UNCHECKEDNORMAL; if (priv->drawFlags & ALF_NTBTN_FLAG_IS_HOT) stateid = CBS_UNCHECKEDHOT; if (priv->drawFlags & ALF_NTBTN_FLAG_IS_PRESSED) stateid = CBS_UNCHECKEDPRESSED; if (priv->drawFlags & ALF_NTBTN_FLAG_IS_DISABLED) stateid = CBS_UNCHECKEDDISABLED; } } else if (priv->drawFlags & ALF_NTBTN_FLAG_DRAW_RADIOBTN) { partid = BP_RADIOBUTTON; if (priv->drawFlags & ALF_NTBTN_FLAG_IS_CHECKED) { stateid = RBS_CHECKEDNORMAL; if (priv->drawFlags & ALF_NTBTN_FLAG_IS_HOT) stateid = RBS_CHECKEDHOT; if (priv->drawFlags & ALF_NTBTN_FLAG_IS_PRESSED) stateid = RBS_CHECKEDPRESSED; if (priv->drawFlags & ALF_NTBTN_FLAG_IS_DISABLED) stateid = RBS_CHECKEDDISABLED; } else { stateid = RBS_UNCHECKEDNORMAL; if (priv->drawFlags & ALF_NTBTN_FLAG_IS_HOT) stateid = RBS_UNCHECKEDHOT; if (priv->drawFlags & ALF_NTBTN_FLAG_IS_PRESSED) stateid = RBS_UNCHECKEDPRESSED; if (priv->drawFlags & ALF_NTBTN_FLAG_IS_DISABLED) stateid = RBS_UNCHECKEDDISABLED; } } else { partid = BP_PUSHBUTTON; stateid = PBS_NORMAL; if (priv->drawFlags & ALF_NTBTN_FLAG_IS_DEFAULT) stateid = (priv->drawFlags & ALF_NTBTN_FLAG_DEFAULT_ANIMATING) ? PBS_DEFAULTED_ANIMATING : PBS_DEFAULTED; if (priv->drawFlags & ALF_NTBTN_FLAG_IS_HOT) stateid = PBS_HOT; if (priv->drawFlags & ALF_NTBTN_FLAG_IS_PRESSED) stateid = PBS_PRESSED; if (priv->drawFlags & ALF_NTBTN_FLAG_IS_DISABLED) stateid = PBS_DISABLED; } if (priv->uxStatePrev == -1) { // initial draw priv->uxStateCurrent = stateid; priv->uxPartCurrent = partid; priv->uxDrawFlagsCurrent = priv->drawFlags; if (priv->uxStateCurrent == PBS_DEFAULTED || priv->uxStateCurrent == PBS_DEFAULTED_ANIMATING) priv->uxStateCurrent = PBS_NORMAL; } priv->uxPartPrev = priv->uxPartCurrent; priv->uxStatePrev = priv->uxStateCurrent; priv->uxStateCurrent = stateid; priv->uxDrawFlagsPrev = priv->uxDrawFlagsCurrent; priv->uxDrawFlagsCurrent = priv->drawFlags; ALF_Compat_BP_ANIMATIONPARAMS animParams; ZeroMemory(&animParams, sizeof(animParams)); animParams.cbSize = sizeof(animParams); animParams.style = ALF_Compat_BPAS_LINEAR; if (priv->uxStateCurrent != priv->uxStatePrev && priv->uxPartCurrent == priv->uxPartPrev) { if (priv->uxPartCurrent == BP_PUSHBUTTON && ((priv->uxStateCurrent == PBS_DEFAULTED && priv->uxStatePrev == PBS_DEFAULTED_ANIMATING) || (priv->uxStatePrev == PBS_DEFAULTED && priv->uxStateCurrent == PBS_DEFAULTED_ANIMATING))) { animParams.dwDuration = priv->uxDefaultAnimationDuration; } else if (priv->uxPartCurrent == BP_PUSHBUTTON && priv->uxStateCurrent == PBS_DEFAULTED_ANIMATING) { // Win7 misses these transition times, use the one for PBS_DEFAULTED ALF_Compat_GetThemeTransitionDuration(priv->hTheme, BP_PUSHBUTTON, priv->uxStatePrev, PBS_DEFAULTED, TMT_TRANSITIONDURATION, &animParams.dwDuration); } else if (priv->uxPartCurrent == BP_PUSHBUTTON && priv->uxStatePrev == PBS_DEFAULTED_ANIMATING) { // Win7 misses these transition times, use the one for PBS_DEFAULTED ALF_Compat_GetThemeTransitionDuration(priv->hTheme, BP_PUSHBUTTON, PBS_DEFAULTED, priv->uxStateCurrent, TMT_TRANSITIONDURATION, &animParams.dwDuration); } else { ALF_Compat_GetThemeTransitionDuration(priv->hTheme, priv->uxPartCurrent, priv->uxStatePrev, priv->uxStateCurrent, TMT_TRANSITIONDURATION, &animParams.dwDuration); } if ((priv->uxPartCurrent == BP_PUSHBUTTON || priv->uxPartPrev == BP_PUSHBUTTON) && ((priv->uxStatePrev == PBS_DEFAULTED || priv->uxStatePrev == PBS_DEFAULTED_ANIMATING) && !(priv->uxPartCurrent == BP_PUSHBUTTON && (priv->uxStateCurrent == PBS_DEFAULTED || priv->uxStateCurrent == PBS_DEFAULTED_ANIMATING)))) { KillTimer(hwnd, (UINT_PTR)priv); ALF_NtButton_ModifyDrawFlags(hwnd, priv, 0, ALF_NTBTN_FLAG_DEFAULT_ANIMATING, FALSE); } if (priv->uxPartCurrent == BP_PUSHBUTTON && (!(priv->uxStatePrev == PBS_DEFAULTED || priv->uxStatePrev == PBS_DEFAULTED_ANIMATING) && (priv->uxStateCurrent == PBS_DEFAULTED || priv->uxStateCurrent == PBS_DEFAULTED_ANIMATING) && priv->uxDefaultAnimationDuration)) { SetTimer(hwnd, (UINT_PTR)priv, priv->uxDefaultAnimationDuration, ALF_NtButton_DefaultAnimatingTimerProc); } } HDC hdcFrom = NULL, hdcTo = NULL; ALF_Compat_HANIMATIONBUFFER hbpAnimation; hbpAnimation = ALF_Compat_BeginBufferedAnimation(hwnd, dc, r, 0, NULL, &animParams, &hdcFrom, &hdcTo); if (hbpAnimation) { if (hdcFrom) { ALF_NtButton_RenderUxtheme(hwnd, priv, priv->uxPartPrev, priv->uxStatePrev, priv->uxDrawFlagsPrev, hdcFrom, r); } if (hdcTo) { ALF_NtButton_RenderUxtheme(hwnd, priv, priv->uxPartCurrent, priv->uxStateCurrent, priv->uxDrawFlagsCurrent, hdcTo, r); } ALF_Compat_EndBufferedAnimation(hbpAnimation, TRUE); } else { ALF_NtButton_RenderUxtheme(hwnd, priv, priv->uxPartCurrent, priv->uxStateCurrent, priv->uxDrawFlagsCurrent, dc, r); } } else { // Draw classic unthemed button ALF_NtButton_RenderClassic(hwnd, priv, dc, r); } } } static void ALF_NtButton_FixRadioButtonCheck(HWND hwnd, ALFNtButtonPriv *priv, BOOL unselectOthers) { if ((priv->drawFlags & ALF_NTBTN_FLAG_DRAW_RADIOBTN) == 0) return; // fix WS_TABSTOP style LONG s = GetWindowLong(hwnd, GWL_STYLE); if (priv->drawFlags & ALF_NTBTN_FLAG_IS_CHECKED) s |= WS_TABSTOP; else s &= ~WS_TABSTOP; SetWindowLong(hwnd, GWL_STYLE, s); if (priv->drawFlags & ALF_NTBTN_FLAG_IS_CHECKED && unselectOthers) { // uncheck all other radio buttons in the group HWND p = GetParent(hwnd); HWND h = GetNextDlgGroupItem(p, hwnd, FALSE); for (int limit = 1000; limit > 0; --limit) { if (h == hwnd || h == NULL) break; LRESULT dlgc = SendMessage(h, WM_GETDLGCODE, 0, 0); if (dlgc & DLGC_RADIOBUTTON) { SendMessage(h, BM_SETCHECK, BST_UNCHECKED, 0); } h = GetNextDlgGroupItem(p, h, FALSE); } } } static void ALF_NtButton_Clicked(HWND hwnd, ALFNtButtonPriv *priv) { if (priv->drawFlags & ALF_NTBTN_FLAG_DRAW_CHECKBOX) { if (priv->drawFlags & ALF_NTBTN_FLAG_IS_CHECKED) { ALF_NtButton_ModifyDrawFlags(hwnd, priv, 0, ALF_NTBTN_FLAG_IS_CHECKED, TRUE); } else { ALF_NtButton_ModifyDrawFlags(hwnd, priv, ALF_NTBTN_FLAG_IS_CHECKED, 0, TRUE); } } else if (priv->drawFlags & ALF_NTBTN_FLAG_DRAW_RADIOBTN) { if (priv->drawFlags & ALF_NTBTN_FLAG_IS_CHECKED) return; ALF_NtButton_ModifyDrawFlags(hwnd, priv, ALF_NTBTN_FLAG_IS_CHECKED, 0, TRUE); ALF_NtButton_FixRadioButtonCheck(hwnd, priv, TRUE); } SendMessageW(GetParent(hwnd), WM_COMMAND, MAKEWPARAM(GetWindowLong(hwnd, GWL_ID), BN_CLICKED), (LPARAM)hwnd); } static LRESULT CALLBACK ALF_NtButton_WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { ALFNtButtonPriv *priv = (ALFNtButtonPriv *)GetWindowLongPtr(hwnd, 0); if (uMsg == WM_CREATE) { priv = ALF_NtButton_InitializePriv((CREATESTRUCTW *)lParam); if ((((CREATESTRUCTW *)lParam)->style & 0x0C0F) == BS_AUTOCHECKBOX) priv->drawFlags |= ALF_NTBTN_FLAG_DRAW_CHECKBOX; if ((((CREATESTRUCTW *)lParam)->style & 0x0C0F) == BS_AUTORADIOBUTTON) priv->drawFlags |= ALF_NTBTN_FLAG_DRAW_RADIOBTN; SetWindowLongPtrW(hwnd, 0, (LONG_PTR)priv); ALF_NtButton_HandleThemeChange(hwnd, priv); ALF_NtButton_HandleUIState(hwnd, priv); } else if (uMsg == ALF_WM_QUERYSIZE) { ALF_NtButton_CalculateSize(hwnd, priv, (SIZE*)lParam);; return TRUE; } else if (uMsg == WM_PAINT) { PAINTSTRUCT ps; HDC hdc = BeginPaint(hwnd, &ps); ALF_NtButton_Paint(hwnd, priv, hdc, &ps.rcPaint); EndPaint(hwnd, &ps); return TRUE; } else if (uMsg == WM_PRINTCLIENT) { RECT rc; GetClientRect(hwnd, &rc); ALF_NtButton_Paint(hwnd, priv, (HDC)wParam, &rc); } else if (uMsg == WM_GETDLGCODE) { if (priv->drawFlags & ALF_NTBTN_FLAG_DRAW_CHECKBOX || priv->drawFlags & ALF_NTBTN_FLAG_DRAW_TRISTATE) { if (lParam && ((MSG *)lParam)->message == WM_CHAR && ( ((MSG *)lParam)->wParam == '+' || ((MSG *)lParam)->wParam == '-' || ((MSG *)lParam)->wParam == '=')) { return DLGC_WANTCHARS | DLGC_BUTTON; } return DLGC_BUTTON; } else if (priv->drawFlags & ALF_NTBTN_FLAG_DRAW_RADIOBTN) { return DLGC_RADIOBUTTON; } else if (priv->drawFlags & ALF_NTBTN_FLAG_IS_DEFAULT) { return DLGC_DEFPUSHBUTTON | DLGC_BUTTON; } else { return DLGC_UNDEFPUSHBUTTON | DLGC_BUTTON; } } else if (uMsg == BM_SETSTYLE) { BOOL newDefault = (wParam & BS_DEFPUSHBUTTON) ? 1 : 0; if (newDefault) { ALF_NtButton_ModifyDrawFlags(hwnd, priv, ALF_NTBTN_FLAG_IS_DEFAULT, 0, TRUE); } else { ALF_NtButton_ModifyDrawFlags(hwnd, priv, 0, ALF_NTBTN_FLAG_IS_DEFAULT, TRUE); } } else if (uMsg == BM_SETCHECK) { if (wParam & BST_CHECKED) { ALF_NtButton_ModifyDrawFlags(hwnd, priv, ALF_NTBTN_FLAG_IS_CHECKED, 0, TRUE); } else { ALF_NtButton_ModifyDrawFlags(hwnd, priv, 0, ALF_NTBTN_FLAG_IS_CHECKED, TRUE); } ALF_NtButton_FixRadioButtonCheck(hwnd, priv, FALSE); return 0; } else if (uMsg == BM_GETCHECK) { if (priv->drawFlags & ALF_NTBTN_FLAG_IS_CHECKED) return BST_CHECKED; else return BST_UNCHECKED; } else if (uMsg == BM_CLICK) { ALF_NtButton_Clicked(hwnd, priv); return 0; } else if (uMsg == WM_STYLECHANGED) { BOOL newDefault = (GetWindowLong(hwnd, GWL_STYLE) & BS_DEFPUSHBUTTON) ? 1 : 0; if (newDefault) { ALF_NtButton_ModifyDrawFlags(hwnd, priv, ALF_NTBTN_FLAG_IS_DEFAULT, 0, TRUE); } else { ALF_NtButton_ModifyDrawFlags(hwnd, priv, 0, ALF_NTBTN_FLAG_IS_DEFAULT, TRUE); } } else if (uMsg == WM_THEMECHANGED) { ALF_NtButton_HandleThemeChange(hwnd, priv); } else if (uMsg == WM_MOUSEMOVE) { if (!(priv->drawFlags & ALF_NTBTN_FLAG_IS_HOT)) { TRACKMOUSEEVENT tme; ZeroMemory(&tme, sizeof(tme)); tme.cbSize = sizeof(tme); tme.dwFlags = TME_LEAVE; tme.hwndTrack = hwnd; if (ALF_Compat_TrackMouseEvent(&tme)) { ALF_NtButton_ModifyDrawFlags(hwnd, priv, ALF_NTBTN_FLAG_IS_HOT, 0, TRUE); } } RECT rcWindow; GetWindowRect(hwnd, &rcWindow); DWORD pos = GetMessagePos(); if (GET_X_LPARAM(pos) >= rcWindow.left && GET_X_LPARAM(pos) < rcWindow.right && GET_Y_LPARAM(pos) >= rcWindow.top && GET_Y_LPARAM(pos) < rcWindow.bottom) { if (wParam & MK_LBUTTON) { ALF_NtButton_ModifyDrawFlags(hwnd, priv, ALF_NTBTN_FLAG_IS_PRESSED, 0, TRUE); } } else { ALF_NtButton_ModifyDrawFlags(hwnd, priv, 0, ALF_NTBTN_FLAG_IS_PRESSED, TRUE); } } else if (uMsg == WM_MOUSELEAVE) { ALF_NtButton_ModifyDrawFlags(hwnd, priv, 0, ALF_NTBTN_FLAG_IS_HOT, TRUE); } else if (uMsg == WM_SETFOCUS) { ALF_NtButton_ModifyDrawFlags(hwnd, priv, ALF_NTBTN_FLAG_IS_FOCUSED, 0, TRUE); if ((priv->drawFlags & ALF_NTBTN_FLAG_DRAW_RADIOBTN) && !(priv->drawFlags & ALF_NTBTN_FLAG_IS_CHECKED)) { ALF_NtButton_Clicked(hwnd, priv); } } else if (uMsg == WM_KILLFOCUS) { ALF_NtButton_ModifyDrawFlags(hwnd, priv, 0, ALF_NTBTN_FLAG_IS_FOCUSED | ALF_NTBTN_FLAG_IS_PRESSED, TRUE); } else if (uMsg == WM_LBUTTONDOWN) { if (IsWindowEnabled(hwnd)) { if (!(priv->drawFlags & ALF_NTBTN_FLAG_IS_FOCUSED)) { SetFocus(hwnd); } ALF_NtButton_ModifyDrawFlags(hwnd, priv, ALF_NTBTN_FLAG_IS_PRESSED, 0, TRUE); SetCapture(hwnd); } return 0; } else if (uMsg == WM_LBUTTONUP) { ReleaseCapture(); if (IsWindowEnabled(hwnd)) { RECT rcClient; GetClientRect(hwnd, &rcClient); ALF_NtButton_ModifyDrawFlags(hwnd, priv, 0, ALF_NTBTN_FLAG_IS_PRESSED, TRUE); if (GET_X_LPARAM(lParam) >= rcClient.left && GET_X_LPARAM(lParam) < rcClient.right && GET_Y_LPARAM(lParam) >= rcClient.top && GET_Y_LPARAM(lParam) < rcClient.bottom) { ALF_NtButton_Clicked(hwnd, priv); } } return 0; } else if (uMsg == WM_KEYDOWN) { if (GetCapture() != hwnd && IsWindowEnabled(hwnd)) { if (wParam == VK_SPACE && LOWORD(lParam) == 1) { ALF_NtButton_ModifyDrawFlags(hwnd, priv, ALF_NTBTN_FLAG_IS_PRESSED, 0, TRUE); } else { ALF_NtButton_ModifyDrawFlags(hwnd, priv, 0, ALF_NTBTN_FLAG_IS_PRESSED, TRUE); } } } else if (uMsg == WM_KEYUP) { if (GetCapture() != hwnd && IsWindowEnabled(hwnd)) { if (wParam == VK_SPACE) { if (priv->drawFlags & ALF_NTBTN_FLAG_IS_PRESSED) { ALF_NtButton_ModifyDrawFlags(hwnd, priv, 0, ALF_NTBTN_FLAG_IS_PRESSED, TRUE); ALF_NtButton_Clicked(hwnd, priv); } } } } else if (uMsg == WM_CHAR) { if (priv->drawFlags & ALF_NTBTN_FLAG_DRAW_CHECKBOX) { if (wParam == '+' || wParam == '=') { // check if (!(priv->drawFlags & ALF_NTBTN_FLAG_IS_CHECKED)) { ALF_NtButton_Clicked(hwnd, priv); } } else if (wParam == '-') { // uncheck if (priv->drawFlags & ALF_NTBTN_FLAG_IS_CHECKED) { ALF_NtButton_Clicked(hwnd, priv); } } } } else if (uMsg == WM_ERASEBKGND) { return TRUE; } else if (uMsg == WM_SETTEXT) { const WCHAR *newtext = (const WCHAR *)lParam; if (newtext) { SIZE_T len = (SIZE_T)lstrlenW(newtext); ALF_Free(priv->text); priv->text = ALF_New(WCHAR, len+1); CopyMemory(priv->text, newtext, (len+1)*sizeof(WCHAR)); ALF_InvalidateLayout(GetParent(hwnd)); return TRUE; } return FALSE; } else if (uMsg == WM_GETTEXTLENGTH) { return (LRESULT)lstrlenW(priv->text); } else if (uMsg == WM_GETTEXT) { if (wParam < 1) return 0; SIZE_T selflen = (SIZE_T)lstrlenW(priv->text); SIZE_T buflen = (SIZE_T)wParam - 1; SIZE_T tocopy = selflen < buflen ? selflen : buflen; CopyMemory((WCHAR*)lParam, priv->text, tocopy * sizeof(WCHAR)); ((WCHAR*)lParam)[tocopy] = 0; return (LRESULT)tocopy; } else if (uMsg == WM_SETFONT) { priv->font = (HFONT)wParam; if (LOWORD(lParam) != 0) InvalidateRect(hwnd, NULL, TRUE); ALF_InvalidateLayout(GetParent(hwnd)); return 0; } else if (uMsg == WM_GETFONT) { return (LRESULT)priv->font; } else if (uMsg == ALF_WM_DPICHANGE) { priv->dpi = (int)lParam; ALF_NtButton_HandleThemeChange(hwnd, priv); // theme caches bitmaps, need to reload them ALF_InvalidateLayout(GetParent(hwnd)); return TRUE; } else if (uMsg == ALF_WM_SETBGCOLOR) { ALFColor newcolor = (ALFColor)lParam; if (priv->bgcolor != newcolor) { priv->bgcolor = newcolor; if (priv->hTheme) { InvalidateRect(hwnd, NULL, TRUE); } } } else if (uMsg == ALF_WM_GETBGCOLOR) { return (LRESULT)priv->bgcolor; } else if (uMsg == ALF_WM_BACKGROUNDCHANGE) { if (priv->bgcolor == ALF_COLOR_TRANSPARENT && priv->hTheme) { InvalidateRect(hwnd, NULL, TRUE); } } else if (uMsg == WM_SIZE) { InvalidateRect(hwnd, NULL, TRUE); } else if (uMsg == WM_UPDATEUISTATE) { LRESULT rv = DefWindowProcW(hwnd, uMsg, wParam, lParam); ALF_NtButton_HandleUIState(hwnd, priv); return rv; } else if (uMsg == WM_ENABLE) { if (wParam) { ALF_NtButton_ModifyDrawFlags(hwnd, priv, 0, ALF_NTBTN_FLAG_IS_DISABLED, TRUE); } else { ALF_NtButton_ModifyDrawFlags(hwnd, priv, ALF_NTBTN_FLAG_IS_DISABLED, ALF_NTBTN_FLAG_IS_PRESSED, TRUE); } } else if (uMsg == WM_DESTROY) { ALF_NtButton_FreePriv(priv); priv = NULL; SetWindowLongPtrW(hwnd, 0, 0); } return DefWindowProcW(hwnd, uMsg, wParam, lParam); } #ifdef UNICODE # define ALF_NtButton_WndProcStart ALF_NtButton_WndProc #else // HACK to get a unicode window static LRESULT CALLBACK ALF_NtButton_WndProcStart(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { SetWindowLongPtrW(hwnd, GWLP_WNDPROC, (LONG_PTR)ALF_NtButton_WndProc); WNDPROC thunk = (WNDPROC)GetWindowLongPtr(hwnd, GWLP_WNDPROC); return CallWindowProc(thunk, hwnd, msg, wparam, lparam); } #endif static HWND ALF_NtButton_Create(HWND win, WORD id, int x, int y, const TCHAR *text, DWORD addstyle) { HWND hwndButton = ALF_CreateControlWindow(0, text, WS_CHILD | WS_VISIBLE | addstyle, 0, 0, 100, 100, win, (HMENU)(ULONG_PTR)id, ALF_NtButton_WndProcStart, NULL); int minwidth = 5625; int minheight = 1725; if (addstyle & (BS_CHECKBOX | BS_RADIOBUTTON | BS_AUTORADIOBUTTON)) { minwidth = 0; minheight = 0; } ALF_AddControl(win, x, y, hwndButton, minwidth, minheight, ALF_LAYOUT_SIZE_QUERY | ALF_LAYOUT_INHERITFONT | ALF_LAYOUT_SENDDPICHANGE | ALF_LAYOUT_INHERITBGCOLOR | ALF_LAYOUT_SENDBGCHANGE); return hwndButton; } /* Classic Button for Win9x and NT3.51 */ // Win9x/Win32s absolutely needs a real button window, otherwise the dialog manager will get confused #define ALF_CLSCBTN_FLAG_IS_DEFAULT ((DWORD)1) #define ALF_CLSCBTN_FLAG_DRAW_CHECKBOX ((DWORD)2) #define ALF_CLSCBTN_FLAG_IS_CHECKED ((DWORD)4) #define ALF_CLSCBTN_FLAG_DRAW_RADIO ((DWORD)8) typedef struct { WNDPROC origWndProc; DWORD flags; BYTE thunk[12]; } ALFClassicButtonPriv; static LRESULT CALLBACK ALF_ClassicButton_WndProc(ALFClassicButtonPriv *priv, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); static ALFClassicButtonPriv * ALF_ClassicButton_InitializePriv(void) { ALFClassicButtonPriv *priv = ALF_New(ALFClassicButtonPriv, 1); // HACK! use thunk since we want to leave GWLP_USERDATA to the control user // this doesn't work on non-x86 and also doesn't work with DEP, // but it's ok since that code is only running on Win9x and NT4 priv->thunk[0] = 0x58; // pop eax priv->thunk[1] = 0x68; // push ptr DWORD privptr = (DWORD)(ULONG_PTR)priv; CopyMemory(&priv->thunk[2], &privptr, 4); priv->thunk[6] = 0x50; // push eax priv->thunk[7] = 0xe9; // jmp DWORD procptr = (DWORD)((ULONG_PTR)ALF_ClassicButton_WndProc - ((ULONG_PTR)priv->thunk + sizeof(priv->thunk))); CopyMemory(&priv->thunk[8], &procptr, 4); return priv; } static void ALF_ClassicButton_FreePriv(ALFClassicButtonPriv *priv) { ALF_Free(priv); } static void ALF_ClassicButton_CalculateSize(HWND hwnd, ALFClassicButtonPriv *priv, SIZE *pSize) { HDC hdc = GetDC(hwnd); HFONT font = (HFONT)SendMessage(hwnd, WM_GETFONT, 0, 0); HFONT oldFont = SelectFont(hdc, font); // calc drawtext style UINT format = DT_LEFT | DT_EXPANDTABS | DT_CALCRECT; RECT r = { 0, 0, 0x7FFFFFFF, 100 }; TCHAR *textbuf = ALF_Text(hwnd); DrawText(hdc, textbuf, -1, &r, format); ALF_Free(textbuf); if (priv->flags & (ALF_CLSCBTN_FLAG_DRAW_CHECKBOX | ALF_CLSCBTN_FLAG_DRAW_RADIO)) { int checkwidth = 13; int space = ALF_Button_CalcCheckboxTextSpace(hdc); int cx = checkwidth + space + r.right - r.left + 1; int cy = r.bottom - r.top; if (cy < checkwidth) cy = checkwidth; if (pSize->cx < cx) pSize->cx = cx; if (pSize->cy < cy) pSize->cy = cy; } else { int xpadding = GetSystemMetrics(SM_CXEDGE) * 2 + 6; int ypadding = GetSystemMetrics(SM_CYEDGE) * 2 + 4; if (pSize->cx < r.right - r.left + xpadding) { pSize->cx = r.right - r.left + xpadding; } if (pSize->cy < r.bottom - r.top + ypadding) { pSize->cy = r.bottom - r.top + ypadding; } if (pSize->cx < pSize->cy) { pSize->cx = pSize->cy; } } SelectFont(hdc, oldFont); ReleaseDC(hwnd, hdc); } static void ALF_ClassicButton_PaintCheckbox(HWND hwnd, ALFClassicButtonPriv *priv, DRAWITEMSTRUCT *dis) { int checkwidth = 13; RECT rcClient; GetClientRect(hwnd, &rcClient); if (dis->itemAction & ODA_DRAWENTIRE) { //if (priv->bgcolor == ALF_COLOR_TRANSPARENT) { // ALF_Compat_DrawThemeParentBackground(hwnd, dc, rcPaint); //} else { ALF_FillRect(dis->hDC, &dis->rcItem, ALF_COLOR_SYS(COLOR_BTNFACE)); //} } if (dis->itemAction & (ODA_DRAWENTIRE | ODA_SELECT)) { UINT dfcs = priv->flags & ALF_CLSCBTN_FLAG_DRAW_RADIO ? DFCS_BUTTONRADIO : DFCS_BUTTONCHECK; if (priv->flags & ALF_CLSCBTN_FLAG_IS_CHECKED) dfcs |= DFCS_CHECKED; if (dis->itemState & ODS_SELECTED) dfcs |= DFCS_PUSHED; if (dis->itemState & ODS_DISABLED) dfcs |= DFCS_INACTIVE; RECT rcCheckmark = { 0, 0, checkwidth, checkwidth }; rcCheckmark.top += (rcClient.bottom - rcClient.top - checkwidth) / 2; rcCheckmark.bottom = rcCheckmark.top + checkwidth; DrawFrameControl(dis->hDC, &rcCheckmark, DFC_BUTTON, dfcs); } if (dis->itemAction & (ODA_DRAWENTIRE | ODA_FOCUS)) { // lol int space = ALF_Button_CalcCheckboxTextSpace(dis->hDC); RECT rcText = { checkwidth + space, 0, rcClient.right - rcClient.left, rcClient.bottom - rcClient.top }; TCHAR *textbuf = ALF_Text(hwnd); RECT textbounds = rcText; DrawText(dis->hDC, textbuf, -1, &textbounds, DT_LEFT | DT_CALCRECT | DT_EXPANDTABS); RECT texttarget = rcText; texttarget.top += (texttarget.bottom - texttarget.top - textbounds.bottom + textbounds.top) / 2; texttarget.bottom = texttarget.top + (textbounds.bottom - textbounds.top); texttarget.right = texttarget.left + textbounds.right - textbounds.left; if (dis->itemAction & ODA_DRAWENTIRE) { UINT style = DT_LEFT | DT_NOCLIP | DT_EXPANDTABS; COLORREF oldBkColor = SetBkColor(dis->hDC, GetSysColor(COLOR_BTNFACE)); if (dis->itemState & ODS_DISABLED) { ALF_Compat_DrawDisabledText(dis->hDC, textbuf, -1, &texttarget, style); } else { DrawText(dis->hDC, textbuf, -1, &texttarget, style); } SetBkColor(dis->hDC, oldBkColor); } if (dis->itemState & ODS_FOCUS) { RECT f = texttarget; InflateRect(&f, 1, 0); DrawFocusRect(dis->hDC, &f); } ALF_Free(textbuf); } } static void ALF_ClassicButton_PaintButton(HWND hwnd, ALFClassicButtonPriv *priv, DRAWITEMSTRUCT *dis) { RECT r = dis->rcItem; TCHAR *textbuf = ALF_Text(hwnd); if (priv->flags & ALF_CLSCBTN_FLAG_IS_DEFAULT) { HBRUSH framecolor = GetSysColorBrush(COLOR_WINDOWFRAME); FrameRect(dis->hDC, &r, framecolor); InflateRect(&r, -1, -1); } UINT dfcs = DFCS_BUTTONPUSH | DFCS_ADJUSTRECT; if (dis->itemState & ODS_SELECTED) dfcs |= DFCS_FLAT; if (dis->itemState & ODS_DISABLED) dfcs |= DFCS_INACTIVE; DrawFrameControl(dis->hDC, &r, DFC_BUTTON, dfcs); RECT textbounds = r; DrawText(dis->hDC, textbuf, -1, &textbounds, DT_LEFT | DT_CALCRECT | DT_NOCLIP); textbounds.bottom++; // this appears to be the secret for correct vertical centering RECT texttarget = r; texttarget.top += (texttarget.bottom - texttarget.top - textbounds.bottom + textbounds.top) / 2; texttarget.left += (texttarget.right - texttarget.left - textbounds.right + textbounds.left) / 2; texttarget.right = texttarget.left + (textbounds.right - textbounds.left); texttarget.bottom = texttarget.top + (textbounds.bottom - textbounds.top); if (dis->itemState & ODS_SELECTED) { texttarget.top += 1; texttarget.left += 1; texttarget.right += 1; texttarget.bottom += 1; } UINT style = DT_CENTER; COLORREF oldBkColor = SetBkColor(dis->hDC, GetSysColor(COLOR_BTNFACE)); if (dis->itemState & ODS_DISABLED) { ALF_Compat_DrawDisabledText(dis->hDC, textbuf, -1, &texttarget, style); } else { COLORREF oldTextColor = SetTextColor(dis->hDC, GetSysColor(COLOR_BTNTEXT)); DrawText(dis->hDC, textbuf, -1, &texttarget, style); SetTextColor(dis->hDC, oldTextColor); } SetBkColor(dis->hDC, oldBkColor); if (dis->itemState & ODS_FOCUS) { RECT f = r; InflateRect(&f, -1, -1); DrawFocusRect(dis->hDC, &f); } ALF_Free(textbuf); } static void ALF_ClassicButton_Paint(HWND hwnd, ALFClassicButtonPriv *priv, DRAWITEMSTRUCT *dis) { if (priv->flags & (ALF_CLSCBTN_FLAG_DRAW_CHECKBOX | ALF_CLSCBTN_FLAG_DRAW_RADIO)) { ALF_ClassicButton_PaintCheckbox(hwnd, priv, dis); } else { ALF_ClassicButton_PaintButton(hwnd, priv, dis); } } static void ALF_ClassicButton_FixRadioButtonCheck(HWND hwnd, ALFClassicButtonPriv *priv, BOOL unselectOthers) { if ((priv->flags & ALF_CLSCBTN_FLAG_DRAW_RADIO) == 0) return; // fix WS_TABSTOP style LONG s = GetWindowLong(hwnd, GWL_STYLE); if (priv->flags & ALF_CLSCBTN_FLAG_IS_CHECKED) s |= WS_TABSTOP; else s &= ~WS_TABSTOP; SetWindowLong(hwnd, GWL_STYLE, s); if (priv->flags & ALF_CLSCBTN_FLAG_IS_CHECKED && unselectOthers) { // uncheck all other radio buttons in the group HWND p = GetParent(hwnd); HWND h = GetNextDlgGroupItem(p, hwnd, FALSE); for (int limit = 1000; limit > 0; --limit) { if (h == hwnd || h == NULL) break; LRESULT dlgc = SendMessage(h, WM_GETDLGCODE, 0, 0); if (dlgc & DLGC_RADIOBUTTON) { SendMessage(h, BM_SETCHECK, BST_UNCHECKED, 0); } h = GetNextDlgGroupItem(p, h, FALSE); } } } static void ALF_ClassicButton_HandleClick(ALFClassicButtonPriv *priv, HWND hwnd) { if (priv->flags & ALF_CLSCBTN_FLAG_DRAW_CHECKBOX) { if (priv->flags & ALF_CLSCBTN_FLAG_IS_CHECKED) { priv->flags &= ~ALF_CLSCBTN_FLAG_IS_CHECKED; } else { priv->flags |= ALF_CLSCBTN_FLAG_IS_CHECKED; } RECT rcCheck; RECT rcClient; GetClientRect(hwnd, &rcClient); rcCheck.left = 0; rcCheck.top = 0; rcCheck.right = 13; rcCheck.bottom = rcClient.bottom - rcClient.top; InvalidateRect(hwnd, &rcCheck, TRUE); } else if (priv->flags & ALF_CLSCBTN_FLAG_DRAW_RADIO) { priv->flags |= ALF_CLSCBTN_FLAG_IS_CHECKED; InvalidateRect(hwnd, NULL, TRUE); ALF_ClassicButton_FixRadioButtonCheck(hwnd, priv, TRUE); } } static LRESULT CALLBACK ALF_ClassicButton_WndProc(ALFClassicButtonPriv *priv, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { if (!priv) return DefWindowProc(hwnd, uMsg, wParam, lParam); if (uMsg == ALF_WM_QUERYSIZE) { ALF_ClassicButton_CalculateSize(hwnd, priv, (SIZE*)lParam); return TRUE; } else if (uMsg == 0x2000 + WM_DRAWITEM) { LPDRAWITEMSTRUCT dis = (DRAWITEMSTRUCT *)lParam; ALF_ClassicButton_Paint(hwnd, priv, dis); return TRUE; } else if (uMsg == 0x2000 + WM_COMMAND) { if (HIWORD(wParam) == BN_CLICKED) { ALF_ClassicButton_HandleClick(priv, hwnd); } } else if (uMsg == WM_ERASEBKGND) { return TRUE; } else if (uMsg == BM_SETSTYLE) { priv->flags &= ~ALF_CLSCBTN_FLAG_IS_DEFAULT; if (wParam & BS_DEFPUSHBUTTON) priv->flags |= ALF_CLSCBTN_FLAG_IS_DEFAULT; wParam = BS_OWNERDRAW; } else if (uMsg == BM_SETCHECK) { priv->flags &= ~ALF_CLSCBTN_FLAG_IS_CHECKED; if (wParam & BST_CHECKED) priv->flags |= ALF_CLSCBTN_FLAG_IS_CHECKED; ALF_ClassicButton_FixRadioButtonCheck(hwnd, priv, FALSE); InvalidateRect(hwnd, NULL, TRUE); return 0; } else if (uMsg == BM_GETCHECK) { if (priv->flags & ALF_CLSCBTN_FLAG_IS_CHECKED) { return BST_CHECKED; } else { return BST_UNCHECKED; } } else if (uMsg == WM_GETDLGCODE) { if (priv->flags & ALF_CLSCBTN_FLAG_DRAW_CHECKBOX) { if (lParam && ((MSG *)lParam)->message == WM_CHAR && ( ((MSG *)lParam)->wParam == '+' || ((MSG *)lParam)->wParam == '-' || ((MSG *)lParam)->wParam == '=')) { return DLGC_WANTCHARS | DLGC_BUTTON; } return (LRESULT)DLGC_BUTTON; } else if (priv->flags & ALF_CLSCBTN_FLAG_DRAW_RADIO) { return (LRESULT)DLGC_RADIOBUTTON; } else if (priv->flags & ALF_CLSCBTN_FLAG_IS_DEFAULT) { return (LRESULT)DLGC_DEFPUSHBUTTON; } else { return (LRESULT)DLGC_UNDEFPUSHBUTTON; } } else if (uMsg == WM_SETFOCUS) { if (priv->flags & ALF_CLSCBTN_FLAG_DRAW_RADIO && !(priv->flags & ALF_CLSCBTN_FLAG_IS_CHECKED)) { priv->flags |= ALF_CLSCBTN_FLAG_IS_CHECKED; ALF_ClassicButton_FixRadioButtonCheck(hwnd, priv, TRUE); InvalidateRect(hwnd, NULL, TRUE); } } else if (uMsg == WM_SETFONT) { ALF_InvalidateLayout(GetParent(hwnd)); InvalidateRect(hwnd, NULL, TRUE); } else if (uMsg == WM_CHAR) { if (priv->flags & ALF_CLSCBTN_FLAG_DRAW_CHECKBOX) { if (wParam == '+' || wParam == '=') { // check if (!(priv->flags & ALF_CLSCBTN_FLAG_IS_CHECKED)) { ALF_ClassicButton_HandleClick(priv, hwnd); } } else if (wParam == '-') { // uncheck if (priv->flags & ALF_CLSCBTN_FLAG_IS_CHECKED) { ALF_ClassicButton_HandleClick(priv, hwnd); } } } } else if (uMsg == WM_DESTROY) { WNDPROC o = priv->origWndProc; SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)o); ALF_ClassicButton_FreePriv(priv); return CallWindowProc(o, hwnd, uMsg, wParam, lParam); } return CallWindowProc(priv->origWndProc, hwnd, uMsg, wParam, lParam); } static HWND ALF_ClassicButton_Create(HWND win, WORD id, int x, int y, const TCHAR *text, DWORD style) { HWND hwndButton = CreateWindowEx(0, TEXT("BUTTON"), text, WS_CHILD | WS_TABSTOP | WS_VISIBLE | BS_OWNERDRAW, 0, 0, 100, 100, win, (HMENU)(ULONG_PTR)id, ALF_HINSTANCE, NULL); ALFClassicButtonPriv *priv = ALF_ClassicButton_InitializePriv(); int minwidth = 5625; int minheight = 1725; if ((style & 0x0C0F) == BS_AUTOCHECKBOX) { minwidth = 0; minheight = 0; priv->flags |= ALF_CLSCBTN_FLAG_DRAW_CHECKBOX; } if ((style & 0x0C0F) == BS_AUTORADIOBUTTON) { minwidth = 0; minheight = 0; priv->flags |= ALF_CLSCBTN_FLAG_DRAW_RADIO; LONG s = GetWindowLong(hwndButton, GWL_STYLE); s &= ~WS_TABSTOP; SetWindowLong(hwndButton, GWL_STYLE, s); } priv->origWndProc = (WNDPROC)GetWindowLongPtr(hwndButton, GWLP_WNDPROC); SetWindowLongPtr(hwndButton, GWLP_WNDPROC, (LONG_PTR)priv->thunk); ALF_AddControl(win, x, y, hwndButton, minwidth, minheight, ALF_LAYOUT_SIZE_QUERY | ALF_LAYOUT_INHERITFONT); return hwndButton; } /* Factory and helper functions for both button types */ HWND ALF_AddButton(HWND win, WORD id, int x, int y, const TCHAR *text) { if (ALF_Compat_Is2k()) { return ALF_NtButton_Create(win, id, x, y, text, WS_TABSTOP); } else { return ALF_ClassicButton_Create(win, id, x, y, text, 0); } } HWND ALF_AddCheckbox(HWND win, WORD id, int x, int y, const TCHAR *text) { if (ALF_Compat_Is2k()) { return ALF_NtButton_Create(win, id, x, y, text, WS_TABSTOP | BS_AUTOCHECKBOX); } else { return ALF_ClassicButton_Create(win, id, x, y, text, BS_AUTOCHECKBOX); } } HWND ALF_AddRadioButton(HWND parent, WORD id, int x, int y, const TCHAR *text) { if (ALF_Compat_Is2k()) { return ALF_NtButton_Create(parent, id, x, y, text, BS_AUTORADIOBUTTON); } else { return ALF_ClassicButton_Create(parent, id, x, y, text, BS_AUTORADIOBUTTON); } }