/* * Theming - Button control * * Copyright (c) 2008 by Reece H. Dunn * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA * */ #include <stdarg.h> #include <string.h> #include <stdlib.h> #include "windef.h" #include "winbase.h" #include "wingdi.h" #include "winuser.h" #include "uxtheme.h" #include "vssym32.h" #include "comctl32.h" #define BUTTON_TYPE 0x0f /* bit mask for the available button types */ /* These are indices into a states array to determine the theme state for a given theme part. */ typedef enum { STATE_NORMAL, STATE_DISABLED, STATE_HOT, STATE_PRESSED, STATE_DEFAULTED } ButtonState; typedef void (*pfThemedPaint)(HTHEME theme, HWND hwnd, HDC hdc, ButtonState drawState, UINT dtFlags); static UINT get_drawtext_flags(DWORD style, DWORD ex_style) { UINT flags = 0; if (style & BS_PUSHLIKE) style &= ~BUTTON_TYPE; if (!(style & BS_MULTILINE)) flags |= DT_SINGLELINE; else flags |= DT_WORDBREAK; switch (style & BS_CENTER) { case BS_LEFT: flags |= DT_LEFT; break; case BS_RIGHT: flags |= DT_RIGHT; break; case BS_CENTER: flags |= DT_CENTER; break; default: flags |= ((style & BUTTON_TYPE) <= BS_DEFPUSHBUTTON) ? DT_CENTER : DT_LEFT; } if (ex_style & WS_EX_RIGHT) flags = DT_RIGHT | (flags & ~(DT_LEFT | DT_CENTER)); if ((style & BUTTON_TYPE) != BS_GROUPBOX) { switch (style & BS_VCENTER) { case BS_TOP: flags |= DT_TOP; break; case BS_BOTTOM: flags |= DT_BOTTOM; break; case BS_VCENTER: /* fall through */ default: flags |= DT_VCENTER; break; } } else /* GroupBox's text is always single line and is top aligned. */ flags |= DT_SINGLELINE | DT_TOP; return flags; } static inline WCHAR *get_button_text(HWND hwnd) { INT len = 512; WCHAR *text; text = HeapAlloc(GetProcessHeap(), 0, (len + 1) * sizeof(WCHAR)); if (text) InternalGetWindowText(hwnd, text, len + 1); return text; } static void PB_draw(HTHEME theme, HWND hwnd, HDC hDC, ButtonState drawState, UINT dtFlags) { static const int states[] = { PBS_NORMAL, PBS_DISABLED, PBS_HOT, PBS_PRESSED, PBS_DEFAULTED }; RECT bgRect, textRect; HFONT font = (HFONT)SendMessageW(hwnd, WM_GETFONT, 0, 0); HFONT hPrevFont = font ? SelectObject(hDC, font) : NULL; int state = states[ drawState ]; WCHAR *text = get_button_text(hwnd); GetClientRect(hwnd, &bgRect); GetThemeBackgroundContentRect(theme, hDC, BP_PUSHBUTTON, state, &bgRect, &textRect); if (IsThemeBackgroundPartiallyTransparent(theme, BP_PUSHBUTTON, state)) DrawThemeParentBackground(hwnd, hDC, NULL); DrawThemeBackground(theme, hDC, BP_PUSHBUTTON, state, &bgRect, NULL); if (text) { DrawThemeText(theme, hDC, BP_PUSHBUTTON, state, text, lstrlenW(text), dtFlags, 0, &textRect); HeapFree(GetProcessHeap(), 0, text); } if (hPrevFont) SelectObject(hDC, hPrevFont); } static void CB_draw(HTHEME theme, HWND hwnd, HDC hDC, ButtonState drawState, UINT dtFlags) { static const int cb_states[3][5] = { { CBS_UNCHECKEDNORMAL, CBS_UNCHECKEDDISABLED, CBS_UNCHECKEDHOT, CBS_UNCHECKEDPRESSED, CBS_UNCHECKEDNORMAL }, { CBS_CHECKEDNORMAL, CBS_CHECKEDDISABLED, CBS_CHECKEDHOT, CBS_CHECKEDPRESSED, CBS_CHECKEDNORMAL }, { CBS_MIXEDNORMAL, CBS_MIXEDDISABLED, CBS_MIXEDHOT, CBS_MIXEDPRESSED, CBS_MIXEDNORMAL } }; static const int rb_states[2][5] = { { RBS_UNCHECKEDNORMAL, RBS_UNCHECKEDDISABLED, RBS_UNCHECKEDHOT, RBS_UNCHECKEDPRESSED, RBS_UNCHECKEDNORMAL }, { RBS_CHECKEDNORMAL, RBS_CHECKEDDISABLED, RBS_CHECKEDHOT, RBS_CHECKEDPRESSED, RBS_CHECKEDNORMAL } }; static const int cb_size = 13; RECT bgRect, textRect; HFONT font = (HFONT)SendMessageW(hwnd, WM_GETFONT, 0, 0); HFONT hPrevFont = font ? SelectObject(hDC, font) : NULL; LRESULT checkState = SendMessageW(hwnd, BM_GETCHECK, 0, 0); DWORD dwStyle = GetWindowLongW(hwnd, GWL_STYLE); int part = ((dwStyle & BUTTON_TYPE) == BS_RADIOBUTTON) || ((dwStyle & BUTTON_TYPE) == BS_AUTORADIOBUTTON) ? BP_RADIOBUTTON : BP_CHECKBOX; int state = (part == BP_CHECKBOX) ? cb_states[ checkState ][ drawState ] : rb_states[ checkState ][ drawState ]; WCHAR *text = get_button_text(hwnd); GetClientRect(hwnd, &bgRect); GetThemeBackgroundContentRect(theme, hDC, part, state, &bgRect, &textRect); if (dtFlags & DT_SINGLELINE) /* Center the checkbox / radio button to the text. */ bgRect.top = bgRect.top + (textRect.bottom - textRect.top - cb_size) / 2; /* adjust for the check/radio marker */ bgRect.bottom = bgRect.top + cb_size; bgRect.right = bgRect.left + cb_size; textRect.left = bgRect.right + 6; if (IsThemeBackgroundPartiallyTransparent(theme, part, state)) DrawThemeParentBackground(hwnd, hDC, NULL); DrawThemeBackground(theme, hDC, part, state, &bgRect, NULL); if (text) { DrawThemeText(theme, hDC, part, state, text, lstrlenW(text), dtFlags, 0, &textRect); HeapFree(GetProcessHeap(), 0, text); } if (hPrevFont) SelectObject(hDC, hPrevFont); } static void GB_draw(HTHEME theme, HWND hwnd, HDC hDC, ButtonState drawState, UINT dtFlags) { static const int states[] = { GBS_NORMAL, GBS_DISABLED, GBS_NORMAL, GBS_NORMAL, GBS_NORMAL }; RECT bgRect, textRect, contentRect; HFONT font = (HFONT)SendMessageW(hwnd, WM_GETFONT, 0, 0); HFONT hPrevFont = font ? SelectObject(hDC, font) : NULL; int state = states[ drawState ]; WCHAR *text = get_button_text(hwnd); GetClientRect(hwnd, &bgRect); textRect = bgRect; if (text) { SIZE textExtent; GetTextExtentPoint32W(hDC, text, lstrlenW(text), &textExtent); bgRect.top += (textExtent.cy / 2); textRect.left += 10; textRect.bottom = textRect.top + textExtent.cy; textRect.right = textRect.left + textExtent.cx + 4; ExcludeClipRect(hDC, textRect.left, textRect.top, textRect.right, textRect.bottom); } GetThemeBackgroundContentRect(theme, hDC, BP_GROUPBOX, state, &bgRect, &contentRect); ExcludeClipRect(hDC, contentRect.left, contentRect.top, contentRect.right, contentRect.bottom); if (IsThemeBackgroundPartiallyTransparent(theme, BP_GROUPBOX, state)) DrawThemeParentBackground(hwnd, hDC, NULL); DrawThemeBackground(theme, hDC, BP_GROUPBOX, state, &bgRect, NULL); SelectClipRgn(hDC, NULL); if (text) { textRect.left += 2; textRect.right -= 2; DrawThemeText(theme, hDC, BP_GROUPBOX, state, text, lstrlenW(text), 0, 0, &textRect); HeapFree(GetProcessHeap(), 0, text); } if (hPrevFont) SelectObject(hDC, hPrevFont); } static const pfThemedPaint btnThemedPaintFunc[BUTTON_TYPE + 1] = { PB_draw, /* BS_PUSHBUTTON */ PB_draw, /* BS_DEFPUSHBUTTON */ CB_draw, /* BS_CHECKBOX */ CB_draw, /* BS_AUTOCHECKBOX */ CB_draw, /* BS_RADIOBUTTON */ CB_draw, /* BS_3STATE */ CB_draw, /* BS_AUTO3STATE */ GB_draw, /* BS_GROUPBOX */ NULL, /* BS_USERBUTTON */ CB_draw, /* BS_AUTORADIOBUTTON */ NULL, /* Not defined */ NULL, /* BS_OWNERDRAW */ NULL, /* Not defined */ NULL, /* Not defined */ NULL, /* Not defined */ NULL, /* Not defined */ }; static BOOL BUTTON_Paint(HTHEME theme, HWND hwnd, HDC hParamDC) { PAINTSTRUCT ps; HDC hDC; DWORD dwStyle = GetWindowLongW(hwnd, GWL_STYLE); DWORD dwStyleEx = GetWindowLongW(hwnd, GWL_EXSTYLE); UINT dtFlags = get_drawtext_flags(dwStyle, dwStyleEx); int state = (int)SendMessageW(hwnd, BM_GETSTATE, 0, 0); ButtonState drawState; pfThemedPaint paint = btnThemedPaintFunc[ dwStyle & BUTTON_TYPE ]; if(!paint) return FALSE; if(IsWindowEnabled(hwnd)) { if(state & BST_PUSHED) drawState = STATE_PRESSED; else if(state & BST_HOT) drawState = STATE_HOT; else if(state & BST_FOCUS) drawState = STATE_DEFAULTED; else drawState = STATE_NORMAL; } else drawState = STATE_DISABLED; hDC = hParamDC ? hParamDC : BeginPaint(hwnd, &ps); paint(theme, hwnd, hDC, drawState, dtFlags); if (!hParamDC) EndPaint(hwnd, &ps); return TRUE; } /********************************************************************** * The button control subclass window proc. */ LRESULT CALLBACK THEMING_ButtonSubclassProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, ULONG_PTR dwRefData) { const WCHAR* themeClass = WC_BUTTONW; HTHEME theme; LRESULT result; switch (msg) { case WM_CREATE: result = THEMING_CallOriginalClass(hwnd, msg, wParam, lParam); OpenThemeData(hwnd, themeClass); return result; case WM_DESTROY: theme = GetWindowTheme(hwnd); CloseThemeData (theme); return THEMING_CallOriginalClass(hwnd, msg, wParam, lParam); case WM_THEMECHANGED: theme = GetWindowTheme(hwnd); CloseThemeData (theme); OpenThemeData(hwnd, themeClass); break; case WM_SYSCOLORCHANGE: theme = GetWindowTheme(hwnd); if (!theme) return THEMING_CallOriginalClass(hwnd, msg, wParam, lParam); /* Do nothing. When themed, a WM_THEMECHANGED will be received, too, * which will do the repaint. */ break; case WM_PAINT: theme = GetWindowTheme(hwnd); if (theme && BUTTON_Paint(theme, hwnd, (HDC)wParam)) return 0; else return THEMING_CallOriginalClass(hwnd, msg, wParam, lParam); case WM_ENABLE: theme = GetWindowTheme(hwnd); if (theme) RedrawWindow(hwnd, NULL, NULL, RDW_FRAME | RDW_INVALIDATE | RDW_UPDATENOW); return THEMING_CallOriginalClass(hwnd, msg, wParam, lParam); case WM_MOUSEMOVE: { TRACKMOUSEEVENT mouse_event; mouse_event.cbSize = sizeof(TRACKMOUSEEVENT); mouse_event.dwFlags = TME_QUERY; if(!TrackMouseEvent(&mouse_event) || !(mouse_event.dwFlags&(TME_HOVER|TME_LEAVE))) { mouse_event.dwFlags = TME_HOVER|TME_LEAVE; mouse_event.hwndTrack = hwnd; mouse_event.dwHoverTime = 1; TrackMouseEvent(&mouse_event); } break; } case WM_MOUSEHOVER: { int state = (int)SendMessageW(hwnd, BM_GETSTATE, 0, 0); SetWindowLongW(hwnd, 0, state|BST_HOT); InvalidateRect(hwnd, NULL, FALSE); break; } case WM_MOUSELEAVE: { int state = (int)SendMessageW(hwnd, BM_GETSTATE, 0, 0); SetWindowLongW(hwnd, 0, state&(~BST_HOT)); InvalidateRect(hwnd, NULL, FALSE); break; } default: /* Call old proc */ return THEMING_CallOriginalClass(hwnd, msg, wParam, lParam); } return 0; }