/* Miranda IM Copyright (C) 2002 Robert Rainwater This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "common.h" // TODO: // - Support for bitmap buttons (simple call to DrawIconEx()) static LRESULT CALLBACK MButtonWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); typedef struct { HWND hwnd; int stateId; // button state int focus; // has focus (1 or 0) HFONT hFont; // font HICON arrow; // uses down arrow int defbutton; // default button HICON hIcon; HBITMAP hBitmap; int pushBtn; int pbState; HANDLE hThemeButton; HANDLE hThemeToolbar; char cHot; int flatBtn; } MButtonCtrl; // External theme methods and properties static HMODULE themeAPIHandle = NULL; // handle to uxtheme.dll static HANDLE (WINAPI *MyOpenThemeData)(HWND, LPCWSTR); static HRESULT (WINAPI *MyCloseThemeData)(HANDLE); static BOOL (WINAPI *MyIsThemeBackgroundPartiallyTransparent)(HANDLE, int, int); static HRESULT (WINAPI *MyDrawThemeParentBackground)(HWND, HDC, RECT *); static HRESULT (WINAPI *MyDrawThemeBackground)(HANDLE, HDC, int, int, const RECT *, const RECT *); static HRESULT (WINAPI *MyDrawThemeText)(HANDLE, HDC, int, int, LPCWSTR, int, DWORD, DWORD, const RECT *); static CRITICAL_SECTION csTips; static HWND hwndToolTips = NULL; int UnloadButtonModule(WPARAM wParam, LPARAM lParam) { DeleteCriticalSection(&csTips); return 0; } int LoadButtonModule(void) { WNDCLASSEX wc = { 0 }; wc.cbSize = sizeof(wc); wc.lpszClassName = MYMIRANDABUTTONCLASS; wc.lpfnWndProc = MButtonWndProc; wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.cbWndExtra = sizeof(MButtonCtrl*); wc.hbrBackground = 0; wc.style = CS_GLOBALCLASS; RegisterClassEx(&wc); InitializeCriticalSection(&csTips); HookEvent(ME_SYSTEM_SHUTDOWN, UnloadButtonModule); return 0; } // Used for our own cheap TrackMouseEvent #define BUTTON_POLLID 100 #define BUTTON_POLLDELAY 50 #define MGPROC(x) GetProcAddress(themeAPIHandle, x) static int ThemeSupport() { if (IsWinVerXPPlus()) { if (!themeAPIHandle) { themeAPIHandle = GetModuleHandle("uxtheme"); if (themeAPIHandle) { MyOpenThemeData = (HANDLE (WINAPI *)(HWND, LPCWSTR))MGPROC("OpenThemeData"); MyCloseThemeData = (HRESULT (WINAPI *)(HANDLE))MGPROC("CloseThemeData"); MyIsThemeBackgroundPartiallyTransparent = (BOOL (WINAPI *)(HANDLE, int, int))MGPROC("IsThemeBackgroundPartiallyTransparent"); MyDrawThemeParentBackground = (HRESULT (WINAPI *)(HWND, HDC, RECT *))MGPROC("DrawThemeParentBackground"); MyDrawThemeBackground = (HRESULT (WINAPI *)(HANDLE, HDC, int, int, const RECT *, const RECT *))MGPROC("DrawThemeBackground"); MyDrawThemeText = (HRESULT (WINAPI *)(HANDLE, HDC, int, int, LPCWSTR, int, DWORD, DWORD, const RECT *))MGPROC("DrawThemeText"); } } // Make sure all of these methods are valid (i would hope either all or none work) if (MyOpenThemeData &&MyCloseThemeData &&MyIsThemeBackgroundPartiallyTransparent &&MyDrawThemeParentBackground &&MyDrawThemeBackground &&MyDrawThemeText) { return 1; } } return 0; } static void DestroyTheme(MButtonCtrl *ctl) { if (ThemeSupport()) { if (ctl->hThemeButton) { MyCloseThemeData(ctl->hThemeButton); ctl->hThemeButton = NULL; } if (ctl->hThemeToolbar) { MyCloseThemeData(ctl->hThemeToolbar); ctl->hThemeToolbar = NULL; } } } static void LoadTheme(MButtonCtrl *ctl) { if (ThemeSupport()) { DestroyTheme(ctl); ctl->hThemeButton = MyOpenThemeData(ctl->hwnd, L"BUTTON"); ctl->hThemeToolbar = MyOpenThemeData(ctl->hwnd, L"TOOLBAR"); } } static int TBStateConvert2Flat(int state) { switch(state) { case PBS_NORMAL: return TS_NORMAL; case PBS_HOT: return TS_HOT; case PBS_PRESSED: return TS_PRESSED; case PBS_DISABLED: return TS_DISABLED; case PBS_DEFAULTED: return TS_NORMAL; } return TS_NORMAL; } static void PaintWorker(MButtonCtrl *ctl, HDC hdcPaint) { if (hdcPaint) { RECT rcClient; GetClientRect(ctl->hwnd, &rcClient); // If its a push button, check to see if it should stay pressed if (ctl->pushBtn && ctl->pbState) ctl->stateId = PBS_PRESSED; // Draw the flat button if (ctl->flatBtn) { if (ctl->hThemeToolbar) { int state = IsWindowEnabled(ctl->hwnd)?(ctl->stateId == PBS_NORMAL&&ctl->defbutton?PBS_DEFAULTED:ctl->stateId):PBS_DISABLED; if (MyIsThemeBackgroundPartiallyTransparent(ctl->hThemeToolbar, TP_BUTTON, TBStateConvert2Flat(state))) { MyDrawThemeParentBackground(ctl->hwnd, hdcPaint, &rcClient); } MyDrawThemeBackground(ctl->hThemeToolbar, hdcPaint, TP_BUTTON, TBStateConvert2Flat(state), &rcClient, &rcClient); } else { HBRUSH hbr; if (ctl->stateId == PBS_PRESSED||ctl->stateId == PBS_HOT) hbr = GetSysColorBrush(COLOR_3DLIGHT); else { HDC dc; HWND hwndParent; hwndParent = GetParent(ctl->hwnd); dc = GetDC(hwndParent); hbr = (HBRUSH)SendMessage(hwndParent, WM_CTLCOLORDLG, (WPARAM)dc, (LPARAM)hwndParent); ReleaseDC(hwndParent, dc); } if (hbr) { FillRect(hdcPaint, &rcClient, hbr); DeleteObject(hbr); } if (ctl->stateId == PBS_HOT||ctl->focus) { if (ctl->pbState) DrawEdge(hdcPaint, &rcClient, EDGE_ETCHED, BF_RECT|BF_SOFT); else DrawEdge(hdcPaint, &rcClient, BDR_RAISEDOUTER, BF_RECT|BF_SOFT|BF_FLAT); } else if (ctl->stateId == PBS_PRESSED) DrawEdge(hdcPaint, &rcClient, BDR_SUNKENOUTER, BF_RECT|BF_SOFT); } } else { // Draw background/border if (ctl->hThemeButton) { int state = IsWindowEnabled(ctl->hwnd)?(ctl->stateId == PBS_NORMAL&&ctl->defbutton?PBS_DEFAULTED:ctl->stateId):PBS_DISABLED; if (MyIsThemeBackgroundPartiallyTransparent(ctl->hThemeButton, BP_PUSHBUTTON, state)) { MyDrawThemeParentBackground(ctl->hwnd, hdcPaint, &rcClient); } MyDrawThemeBackground(ctl->hThemeButton, hdcPaint, BP_PUSHBUTTON, state, &rcClient, &rcClient); } else { UINT uState = DFCS_BUTTONPUSH|((ctl->stateId == PBS_HOT)?DFCS_HOT:0)|((ctl->stateId == PBS_PRESSED)?DFCS_PUSHED:0); if (ctl->defbutton&&ctl->stateId == PBS_NORMAL) uState |= DLGC_DEFPUSHBUTTON; DrawFrameControl(hdcPaint, &rcClient, DFC_BUTTON, uState); } // Draw focus rectangle if button has focus if (ctl->focus) { RECT focusRect = rcClient; InflateRect(&focusRect, -3, -3); DrawFocusRect(hdcPaint, &focusRect); } } // If we have an icon or a bitmap, ignore text and only draw the image on the button if (ctl->hIcon) { int ix = (rcClient.right-rcClient.left)/2 - (GetSystemMetrics(SM_CXSMICON)/2); int iy = (rcClient.bottom-rcClient.top)/2 - (GetSystemMetrics(SM_CYSMICON)/2); if (ctl->stateId == PBS_PRESSED) { ix++; iy++; } DrawState(hdcPaint, NULL, NULL, (LPARAM)ctl->hIcon, 0, ix, iy, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), IsWindowEnabled(ctl->hwnd)?DST_ICON:DST_ICON|DSS_DISABLED); } else if (ctl->hBitmap) { BITMAP bminfo; int ix, iy; GetObject(ctl->hBitmap, sizeof(bminfo), &bminfo); ix = (rcClient.right-rcClient.left)/2 - (bminfo.bmWidth/2); iy = (rcClient.bottom-rcClient.top)/2 - (bminfo.bmHeight/2); if (ctl->stateId == PBS_PRESSED) { ix++; iy++; } DrawState(hdcPaint, NULL, NULL, (LPARAM)ctl->hBitmap, 0, ix, iy, bminfo.bmWidth, bminfo.bmHeight, IsWindowEnabled(ctl->hwnd)?DST_BITMAP:DST_BITMAP|DSS_DISABLED); } else if (GetWindowTextLength(ctl->hwnd)) { // Draw the text and optinally the arrow char szText[MAX_PATH]; SIZE sz; RECT rcText; CopyRect(&rcText, &rcClient); GetWindowText(ctl->hwnd, szText, sizeof(szText)); SetBkMode(hdcPaint, TRANSPARENT); SelectObject(hdcPaint, ctl->hFont); // XP w/themes doesn't used the glossy disabled text. Is it always using COLOR_GRAYTEXT? Seems so. SetTextColor(hdcPaint, IsWindowEnabled(ctl->hwnd)||!ctl->hThemeButton?GetSysColor(COLOR_BTNTEXT):GetSysColor(COLOR_GRAYTEXT)); GetTextExtentPoint32(hdcPaint, szText, lstrlen(szText), &sz); if (ctl->cHot) { SIZE szHot; GetTextExtentPoint32(hdcPaint, "&", 1, &szHot); sz.cx -= szHot.cx; } if (ctl->arrow) { DrawState(hdcPaint, NULL, NULL, (LPARAM)ctl->arrow, 0, rcClient.right-rcClient.left-5-GetSystemMetrics(SM_CXSMICON)+(!ctl->hThemeButton&&ctl->stateId == PBS_PRESSED?1:0), (rcClient.bottom-rcClient.top)/2-GetSystemMetrics(SM_CYSMICON)/2+(!ctl->hThemeButton&&ctl->stateId == PBS_PRESSED?1:0), GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), IsWindowEnabled(ctl->hwnd)?DST_ICON:DST_ICON|DSS_DISABLED); } SelectObject(hdcPaint, ctl->hFont); DrawState(hdcPaint, NULL, NULL, (LPARAM)szText, 0, (rcText.right-rcText.left-sz.cx)/2+(!ctl->hThemeButton&&ctl->stateId == PBS_PRESSED?1:0), ctl->hThemeButton?(rcText.bottom-rcText.top-sz.cy)/2:(rcText.bottom-rcText.top-sz.cy)/2-(ctl->stateId == PBS_PRESSED?0:1), sz.cx, sz.cy, IsWindowEnabled(ctl->hwnd)||ctl->hThemeButton?DST_PREFIXTEXT|DSS_NORMAL:DST_PREFIXTEXT|DSS_DISABLED); } } } static LRESULT CALLBACK MButtonWndProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) { MButtonCtrl* bct = (MButtonCtrl *)GetWindowLongPtr(hwndDlg, 0); switch(msg) { case WM_NCCREATE: SetWindowLong(hwndDlg, GWL_STYLE, GetWindowLongPtr(hwndDlg, GWL_STYLE)|BS_OWNERDRAW); bct = ( MButtonCtrl* )malloc(sizeof(MButtonCtrl)); if (bct == NULL) return FALSE; bct->hwnd = hwndDlg; bct->stateId = PBS_NORMAL; bct->focus = 0; bct->hFont = (HFONT)GetStockObject(DEFAULT_GUI_FONT); bct->arrow = NULL; bct->defbutton = 0; bct->hIcon = NULL; bct->hBitmap = NULL; bct->pushBtn = 0; bct->pbState = 0; bct->hThemeButton = NULL; bct->hThemeToolbar = NULL; bct->cHot = 0; bct->flatBtn = 0; LoadTheme(bct); SetWindowLong(hwndDlg, 0, (LONG)bct); if (((CREATESTRUCT *)lParam)->lpszName) SetWindowText(hwndDlg, ((CREATESTRUCT *)lParam)->lpszName); return TRUE; case WM_DESTROY: if (bct) { EnterCriticalSection(&csTips); if (hwndToolTips) { TOOLINFO ti; ZeroMemory(&ti, sizeof(ti)); ti.cbSize = sizeof(ti); ti.uFlags = TTF_IDISHWND; ti.hwnd = bct->hwnd; ti.uId = (UINT)bct->hwnd; if (SendMessage(hwndToolTips, TTM_GETTOOLINFO, 0, (LPARAM)&ti)) { SendMessage(hwndToolTips, TTM_DELTOOL, 0, (LPARAM)&ti); } if (SendMessage(hwndToolTips, TTM_GETTOOLCOUNT, 0, (LPARAM)&ti) == 0) { DestroyWindow(hwndToolTips); hwndToolTips = NULL; } } LeaveCriticalSection(&csTips); DestroyTheme(bct); free(bct); } SetWindowLong(hwndDlg, 0, (LONG)NULL); break; // DONT! fall thru case WM_SETTEXT: bct->cHot = 0; if ((char*)lParam) { char *tmp = (char*)lParam; while (*tmp) { if (*tmp == '&' && *(tmp+1)) { bct->cHot = tolower(*(tmp+1)); break; } tmp++; } InvalidateRect(bct->hwnd, NULL, TRUE); } break; case WM_SYSKEYUP: if (bct->stateId != PBS_DISABLED && bct->cHot && bct->cHot == tolower((int)wParam)) { if (bct->pushBtn) { if (bct->pbState) bct->pbState = 0; else bct->pbState = 1; InvalidateRect(bct->hwnd, NULL, TRUE); } SendMessage(GetParent(hwndDlg), WM_COMMAND, MAKELONG(GetDlgCtrlID(hwndDlg), BN_CLICKED), (LPARAM)hwndDlg); return 0; } break; case WM_THEMECHANGED: // themed changed, reload theme object LoadTheme(bct); InvalidateRect(bct->hwnd, NULL, TRUE); // repaint it break; case WM_SETFONT: // remember the font so we can use it later bct->hFont = (HFONT)wParam; // maybe we should redraw? break; case WM_PAINT: { PAINTSTRUCT ps; HDC hdcPaint = BeginPaint(hwndDlg, &ps); if (hdcPaint) { PaintWorker(bct, hdcPaint); EndPaint(hwndDlg, &ps); } } break; case BM_SETIMAGE: if (wParam == IMAGE_ICON) { bct->hIcon = (HICON)lParam; bct->hBitmap = NULL; InvalidateRect(bct->hwnd, NULL, TRUE); } else if (wParam == IMAGE_BITMAP) { bct->hBitmap = (HBITMAP)lParam; bct->hIcon = NULL; InvalidateRect(bct->hwnd, NULL, TRUE); } break; case BM_SETCHECK: if (!bct->pushBtn) break; if (wParam == BST_CHECKED) { bct->pbState = 1; bct->stateId = PBS_PRESSED; } else if (wParam == BST_UNCHECKED) { bct->pbState = 0; bct->stateId = PBS_NORMAL; } InvalidateRect(bct->hwnd, NULL, TRUE); break; case BM_GETCHECK: if (bct->pushBtn) return bct->pbState?BST_CHECKED:BST_UNCHECKED; return 0; case BUTTONSETARROW: // turn arrow on/off if (wParam) { //if (!bct->arrow) //bct->arrow = (HICON)LoadImage(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_DOWNARROW), IMAGE_ICON, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 0); } else { if (bct->arrow) { DestroyIcon(bct->arrow); bct->arrow = NULL; } } InvalidateRect(bct->hwnd, NULL, TRUE); break; case BUTTONSETDEFAULT: bct->defbutton = wParam?1:0; InvalidateRect(bct->hwnd, NULL, TRUE); break; case BUTTONSETASPUSHBTN: bct->pushBtn = 1; InvalidateRect(bct->hwnd, NULL, TRUE); break; case BUTTONSETASFLATBTN: bct->flatBtn = 1; InvalidateRect(bct->hwnd, NULL, TRUE); break; case BUTTONADDTOOLTIP: if (wParam) { EnterCriticalSection(&csTips); if (!hwndToolTips) hwndToolTips = CreateWindowEx(WS_EX_TOPMOST, TOOLTIPS_CLASS, "", WS_POPUP, 0, 0, 0, 0, NULL, NULL, GetModuleHandle(NULL), NULL); TOOLINFO ti = { 0 }; ti.cbSize = sizeof(ti); ti.uFlags = TTF_IDISHWND; ti.hwnd = bct->hwnd; ti.uId = (UINT)bct->hwnd; if (SendMessage(hwndToolTips, TTM_GETTOOLINFO, 0, (LPARAM)&ti)) { SendMessage(hwndToolTips, TTM_DELTOOL, 0, (LPARAM)&ti); } ti.uFlags = TTF_IDISHWND|TTF_SUBCLASS; ti.uId = (UINT)bct->hwnd; ti.lpszText = (char*)wParam; SendMessage(hwndToolTips, TTM_ADDTOOL, 0, (LPARAM)&ti); LeaveCriticalSection(&csTips); } break; case WM_SETFOCUS: // set keybord focus and redraw bct->focus = 1; InvalidateRect(bct->hwnd, NULL, TRUE); break; case WM_KILLFOCUS: // kill focus and redraw bct->focus = 0; InvalidateRect(bct->hwnd, NULL, TRUE); break; case WM_WINDOWPOSCHANGED: InvalidateRect(bct->hwnd, NULL, TRUE); break; case WM_ENABLE: // windows tells us to enable/disable bct->stateId = wParam?PBS_NORMAL:PBS_DISABLED; InvalidateRect(bct->hwnd, NULL, TRUE); break; case WM_MOUSELEAVE: // faked by the WM_TIMER if (bct->stateId != PBS_DISABLED) { // don't change states if disabled bct->stateId = PBS_NORMAL; InvalidateRect(bct->hwnd, NULL, TRUE); } break; case WM_LBUTTONDOWN: if (bct->stateId != PBS_DISABLED) { // don't change states if disabled bct->stateId = PBS_PRESSED; InvalidateRect(bct->hwnd, NULL, TRUE); } break; case WM_LBUTTONUP: if (bct->pushBtn) { if (bct->pbState) bct->pbState = 0; else bct->pbState = 1; } if (bct->stateId != PBS_DISABLED) { // don't change states if disabled if (msg == WM_LBUTTONUP) bct->stateId = PBS_HOT; else bct->stateId = PBS_NORMAL; InvalidateRect(bct->hwnd, NULL, TRUE); } // Tell your daddy you got clicked. SendMessage(GetParent(hwndDlg), WM_COMMAND, MAKELONG(GetDlgCtrlID(hwndDlg), BN_CLICKED), (LPARAM)hwndDlg); break; case WM_MOUSEMOVE: if (bct->stateId == PBS_NORMAL) { bct->stateId = PBS_HOT; InvalidateRect(bct->hwnd, NULL, TRUE); } // Call timer, used to start cheesy TrackMouseEvent faker SetTimer(hwndDlg, BUTTON_POLLID, BUTTON_POLLDELAY, NULL); break; case WM_TIMER: // use a timer to check if they have did a mouseout if (wParam == BUTTON_POLLID) { RECT rc; POINT pt; GetWindowRect(hwndDlg, &rc); GetCursorPos(&pt); if (!PtInRect(&rc, pt)) { // mouse must be gone, trigger mouse leave PostMessage(hwndDlg, WM_MOUSELEAVE, 0, 0L); KillTimer(hwndDlg, BUTTON_POLLID); } } break; case WM_ERASEBKGND: return 1; } return DefWindowProc(hwndDlg, msg, wParam, lParam); }