///////////////////////////////////////////////////////////////////////////////////////// // Miranda NG: the free IM client for Microsoft* Windows* // // Copyright (C) 2012-23 Miranda NG team, // Copyright (c) 2000-09 Miranda ICQ/IM project, // all portions of this codebase are copyrighted to the people // listed in contributors.txt. // // 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. // // part of tabSRMM messaging plugin for Miranda. // // (C) 2005-2010 by silvercircle _at_ gmail _dot_ com and contributors // // A skinnable button class for tabSRMM. #include "stdafx.h" #define PBS_PUSHDOWNPRESSED 6 static LRESULT CALLBACK TSButtonWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); // External theme methods and properties static BLENDFUNCTION bf_buttonglyph; static HDC hdc_buttonglyph = nullptr; static HBITMAP hbm_buttonglyph, hbm_buttonglyph_old; // Used for our own cheap TrackMouseEvent #define BUTTON_POLLID 100 #define BUTTON_POLLDELAY 50 int TSAPI 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; } int TSAPI RBStateConvert2Flat(int state) { switch (state) { case PBS_NORMAL: return 1; case PBS_HOT: return 2; case PBS_PRESSED: return 3; case PBS_DISABLED: return 1; case PBS_DEFAULTED: return 1; } return 1; } /** * convert button state (hot, pressed, normal) to REBAR part state ids * * @param state int: button state * * @return int: state item id */ static void PaintWorker(TSButtonCtrl *ctl, HDC hdcPaint) { if (hdc_buttonglyph == nullptr) { hdc_buttonglyph = CreateCompatibleDC(hdcPaint); hbm_buttonglyph = CreateCompatibleBitmap(hdcPaint, 16, 16); hbm_buttonglyph_old = (HBITMAP)SelectObject(hdc_buttonglyph, hbm_buttonglyph); bf_buttonglyph.BlendFlags = 0; bf_buttonglyph.SourceConstantAlpha = 120; bf_buttonglyph.BlendOp = AC_SRC_OVER; bf_buttonglyph.AlphaFormat = 0; } if (ctl == nullptr || hdcPaint == nullptr) return; CMsgDialog *dat = (CMsgDialog*)GetWindowLongPtr(GetParent(ctl->hwnd), GWLP_USERDATA); if (dat == nullptr) return; HDC hdcMem; HBITMAP hbmMem, hOld; HANDLE hbp = nullptr; bool bAero = M.isAero(); RECT rcClient, rcContent; GetClientRect(ctl->hwnd, const_cast(&rcClient)); CopyRect(&rcContent, &rcClient); if (CMimAPI::m_haveBufferedPaint) { hbp = CMimAPI::m_pfnBeginBufferedPaint(hdcPaint, &rcContent, BPBF_TOPDOWNDIB, nullptr, &hdcMem); hbmMem = hOld = nullptr; } else { hdcMem = CreateCompatibleDC(hdcPaint); hbmMem = CreateCompatibleBitmap(hdcPaint, rcClient.right - rcClient.left, rcClient.bottom - rcClient.top); hOld = (HBITMAP)SelectObject(hdcMem, hbmMem); } CSkin::FillBack(hdcMem, &rcContent); if (ctl->bIsPushBtn && ctl->bIsPushed) ctl->stateId = PBS_PRESSED; if (ctl->bIsFlat) { if (ctl->pContainer && CSkin::m_skinEnabled) { CSkinItem *item, *realItem = nullptr; if (ctl->bTitleButton) item = &SkinItems[ctl->stateId == PBS_NORMAL ? ID_EXTBKTITLEBUTTON : (ctl->stateId == PBS_HOT ? ID_EXTBKTITLEBUTTONMOUSEOVER : ID_EXTBKTITLEBUTTONPRESSED)]; else { item = &SkinItems[(ctl->stateId == PBS_NORMAL || ctl->stateId == PBS_DISABLED) ? ID_EXTBKBUTTONSNPRESSED : (ctl->stateId == PBS_HOT ? ID_EXTBKBUTTONSMOUSEOVER : ID_EXTBKBUTTONSPRESSED)]; realItem = item; } CSkin::SkinDrawBG(ctl->hwnd, ctl->pContainer->m_hwnd, ctl->pContainer, &rcContent, hdcMem); if (!item->IGNORED) { RECT rc1 = rcClient; rc1.left += item->MARGIN_LEFT; rc1.right -= item->MARGIN_RIGHT; rc1.top += item->MARGIN_TOP; rc1.bottom -= item->MARGIN_BOTTOM; CSkin::DrawItem(hdcMem, &rc1, item); } else goto flat_themed; } else { flat_themed: int state = IsWindowEnabled(ctl->hwnd) ? (ctl->stateId == PBS_NORMAL && ctl->bIsDefault ? PBS_DEFAULTED : ctl->stateId) : PBS_DISABLED; if (ctl->bToolbarButton) { RECT rcWin; GetWindowRect(ctl->hwnd, &rcWin); POINT pt; pt.x = rcWin.left; ScreenToClient(dat->GetHwnd(), &pt); BitBlt(hdcMem, 0, 0, rcClient.right - rcClient.left, rcClient.bottom - rcClient.top, dat->m_pContainer->m_cachedToolbarDC, pt.x, 1, SRCCOPY); } if (ctl->hThemeToolbar && ctl->bIsThemed && 1 == dat->m_pContainer->m_bTBRenderingMode) { if (bAero || IsWinVerVistaPlus()) DrawThemeBackground(ctl->hThemeToolbar, hdcMem, 8, RBStateConvert2Flat(state), &rcClient, &rcClient); else DrawThemeBackground(ctl->hThemeToolbar, hdcMem, TP_BUTTON, TBStateConvert2Flat(state), &rcClient, &rcClient); } else { CSkin::m_switchBarItem->setAlphaFormat(AC_SRC_ALPHA, state == PBS_HOT ? 220 : 180); if (state == PBS_HOT || state == PBS_PRESSED) { if (state == PBS_PRESSED) { RECT rc = rcClient; InflateRect(&rc, -1, -1); HBRUSH bBack = CreateSolidBrush(PluginConfig.m_tbBackgroundLow ? PluginConfig.m_tbBackgroundLow : GetSysColor(COLOR_3DDKSHADOW)); FillRect(hdcMem, &rc, bBack); DeleteObject(bBack); } CSkin::m_switchBarItem->Render(hdcMem, &rcClient, true); } } } } else { if (ctl->pContainer && CSkin::m_skinEnabled) { CSkinItem *item; if (ctl->bTitleButton) item = &SkinItems[ctl->stateId == PBS_NORMAL ? ID_EXTBKTITLEBUTTON : (ctl->stateId == PBS_HOT ? ID_EXTBKTITLEBUTTONMOUSEOVER : ID_EXTBKTITLEBUTTONPRESSED)]; else item = &SkinItems[(ctl->stateId == PBS_NORMAL || ctl->stateId == PBS_DISABLED) ? ID_EXTBKBUTTONSNPRESSED : (ctl->stateId == PBS_HOT ? ID_EXTBKBUTTONSMOUSEOVER : ID_EXTBKBUTTONSPRESSED)]; CSkin::SkinDrawBG(ctl->hwnd, ctl->pContainer->m_hwnd, ctl->pContainer, &rcClient, hdcMem); if (!item->IGNORED) { RECT rc1 = rcClient; rc1.left += item->MARGIN_LEFT; rc1.right -= item->MARGIN_RIGHT; rc1.top += item->MARGIN_TOP; rc1.bottom -= item->MARGIN_BOTTOM; CSkin::DrawItem(hdcMem, &rc1, item); } else goto nonflat_themed; } else { nonflat_themed: int state = IsWindowEnabled(ctl->hwnd) ? (ctl->stateId == PBS_NORMAL && ctl->bIsDefault ? PBS_DEFAULTED : ctl->stateId) : PBS_DISABLED; if (ctl->hThemeButton && ctl->bIsThemed && 0 == PluginConfig.m_fillColor) { DrawThemeBackground(ctl->hThemeButton, hdcMem, BP_PUSHBUTTON, state, &rcClient, &rcClient); GetThemeBackgroundContentRect(ctl->hThemeToolbar, hdcMem, BP_PUSHBUTTON, PBS_NORMAL, &rcClient, &rcContent); } else { CSkin::m_switchBarItem->setAlphaFormat(AC_SRC_ALPHA, state == PBS_NORMAL ? 140 : 240); if (state == PBS_PRESSED) { RECT rc = rcClient; InflateRect(&rc, -1, -1); HBRUSH bBack = CreateSolidBrush(PluginConfig.m_tbBackgroundLow ? PluginConfig.m_tbBackgroundLow : GetSysColor(COLOR_3DDKSHADOW)); FillRect(hdcMem, &rc, bBack); DeleteObject(bBack); } CSkin::m_switchBarItem->Render(hdcMem, &rcClient, true); } // Draw focus rectangle if button has focus if (ctl->focus) { RECT focusRect = rcClient; InflateRect(&focusRect, -3, -3); DrawFocusRect(hdcMem, &focusRect); } } } // render content if (ctl->arrow) { rcContent.top += 2; rcContent.bottom -= 2; rcContent.left = rcClient.right - 12; rcContent.right = rcContent.left; DrawIconEx(hdcMem, rcClient.right - 15, (rcClient.bottom - rcClient.top) / 2 - (PluginConfig.m_smcyicon / 2), PluginConfig.g_buttonBarIcons[ICON_DEFAULT_PULLDOWN], 16, 16, 0, nullptr, DI_NORMAL); } if (ctl->hIcon || ctl->hIconPrivate) { int ix = (rcClient.right - rcClient.left) / 2 - 8; int iy = (rcClient.bottom - rcClient.top) / 2 - 8; HICON hIconNew = ctl->hIconPrivate != nullptr ? ctl->hIconPrivate : ctl->hIcon; if (ctl->stateId == PBS_PRESSED) { ix++; iy++; } if (ctl->arrow) ix -= 4; if (ctl->bDimmed && PluginConfig.m_bIdleDetect) CSkin::DrawDimmedIcon(hdcMem, ix, iy, PluginConfig.m_smcxicon, PluginConfig.m_smcyicon, hIconNew, 180); else { if (ctl->stateId != PBS_DISABLED) { DrawIconEx(hdcMem, ix, iy, hIconNew, 16, 16, 0, nullptr, DI_NORMAL); if (ctl->overlay) DrawIconEx(hdcMem, ix, iy, ctl->overlay, 16, 16, 0, nullptr, DI_NORMAL); } else { BitBlt(hdc_buttonglyph, 0, 0, 16, 16, hdcMem, ix, iy, SRCCOPY); DrawIconEx(hdc_buttonglyph, 0, 0, hIconNew, 16, 16, 0, nullptr, DI_NORMAL); if (ctl->overlay) DrawIconEx(hdc_buttonglyph, 0, 0, ctl->overlay, 16, 16, 0, nullptr, DI_NORMAL); GdiAlphaBlend(hdcMem, ix, iy, PluginConfig.m_smcxicon, PluginConfig.m_smcyicon, hdc_buttonglyph, 0, 0, 16, 16, bf_buttonglyph); } } } else if (GetWindowTextLength(ctl->hwnd)) { // Draw the text and optinally the arrow RECT rcText; CopyRect(&rcText, &rcClient); wchar_t szText[MAX_PATH]; GetWindowText(ctl->hwnd, szText, _countof(szText)); SetBkMode(hdcMem, TRANSPARENT); HFONT hOldFont = (HFONT)SelectObject(hdcMem, ctl->hFont); if (ctl->pContainer && CSkin::m_skinEnabled) SetTextColor(hdcMem, IsWindowEnabled(ctl->hwnd) ? CSkin::m_DefaultFontColor : GetSysColor(COLOR_GRAYTEXT)); else { if (PluginConfig.m_genericTxtColor) SetTextColor(hdcMem, PluginConfig.m_genericTxtColor); else SetTextColor(hdcMem, IsWindowEnabled(ctl->hwnd) || !ctl->hThemeButton ? GetSysColor(COLOR_BTNTEXT) : GetSysColor(COLOR_GRAYTEXT)); } SIZE sz; GetTextExtentPoint32(hdcMem, szText, (int)mir_wstrlen(szText), &sz); if (ctl->cHot) { SIZE szHot; GetTextExtentPoint32A(hdcMem, "&", 1, &szHot); sz.cx -= szHot.cx; } if (ctl->arrow) DrawState(hdcMem, nullptr, nullptr, (LPARAM)ctl->arrow, 0, rcClient.right - rcClient.left - 5 - PluginConfig.m_smcxicon + (!ctl->hThemeButton && ctl->stateId == PBS_PRESSED ? 1 : 0), (rcClient.bottom - rcClient.top) / 2 - PluginConfig.m_smcyicon / 2 + (!ctl->hThemeButton && ctl->stateId == PBS_PRESSED ? 1 : 0), PluginConfig.m_smcxicon, PluginConfig.m_smcyicon, IsWindowEnabled(ctl->hwnd) ? DST_ICON : DST_ICON | DSS_DISABLED); SelectObject(hdcMem, ctl->hFont); DrawState(hdcMem, nullptr, nullptr, (LPARAM)szText, mir_wstrlen(szText), (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); SelectObject(hdcMem, hOldFont); } if (hbp) CMimAPI::m_pfnEndBufferedPaint(hbp, TRUE); else { BitBlt(hdcPaint, 0, 0, rcClient.right - rcClient.left, rcClient.bottom - rcClient.top, hdcMem, 0, 0, SRCCOPY); SelectObject(hdcMem, hOld); DeleteObject(hbmMem); DeleteDC(hdcMem); } } static LRESULT CALLBACK TSButtonWndProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) { TSButtonCtrl *bct = (TSButtonCtrl*)GetWindowLongPtr(hwndDlg, 0); switch (msg) { case WM_DESTROY: if (bct) if (bct->hIconPrivate) DestroyIcon(bct->hIconPrivate); break; case WM_NCPAINT: return 0; case WM_PAINT: PAINTSTRUCT ps; { HDC hdcPaint = BeginPaint(hwndDlg, &ps); if (hdcPaint) { if (bct->sitem) bct->sitem->RenderThis(hdcPaint); else PaintWorker(bct, hdcPaint); EndPaint(hwndDlg, &ps); } } return 0; case BM_SETIMAGE: if (wParam == IMAGE_ICON) { if (bct->hIconPrivate) DestroyIcon(bct->hIconPrivate); ICONINFO ii; GetIconInfo((HICON)lParam, &ii); BITMAP bm; GetObject(ii.hbmColor, sizeof(bm), &bm); if (bm.bmWidth != PluginConfig.m_smcxicon || bm.bmHeight != PluginConfig.m_smcyicon) { HIMAGELIST hImageList = ImageList_Create(PluginConfig.m_smcxicon, PluginConfig.m_smcyicon, ILC_COLOR32 | ILC_MASK, 1, 0); ImageList_AddIcon(hImageList, (HICON)lParam); bct->hIconPrivate = ImageList_GetIcon(hImageList, 0, ILD_NORMAL); ImageList_RemoveAll(hImageList); ImageList_Destroy(hImageList); bct->hIcon = nullptr; } else { bct->hIcon = (HICON)lParam; bct->hIconPrivate = nullptr; } DeleteObject(ii.hbmMask); DeleteObject(ii.hbmColor); bct->hBitmap = nullptr; InvalidateRect(bct->hwnd, nullptr, TRUE); } else if (wParam == IMAGE_BITMAP) { bct->hBitmap = (HBITMAP)lParam; if (bct->hIconPrivate) DestroyIcon(bct->hIconPrivate); bct->hIcon = bct->hIconPrivate = nullptr; InvalidateRect(bct->hwnd, nullptr, TRUE); } return 0; case BUTTONSETARROW: // turn arrow on/off bct->arrow = (HICON)wParam; InvalidateRect(bct->hwnd, nullptr, TRUE); return 0; case BUTTONSETASDIMMED: bct->bDimmed = (wParam != 0); break; case BUTTONSETCONTAINER: bct->pContainer = (TContainerData*)wParam; break; case BUTTONSETASTITLE: bct->bTitleButton = TRUE; break; case BUTTONSETASNORMAL: bct->stateId = (wParam) ? PBS_NORMAL : PBS_DISABLED; InvalidateRect(bct->hwnd, nullptr, FALSE); break; case BUTTONGETSTATEID: return bct->stateId; case BUTTONSETASTOOLBARBUTTON: bct->bToolbarButton = (wParam != 0); break; case BUTTONSETASSIDEBARBUTTON: bct->sitem = reinterpret_cast(wParam); break; case BUTTONSETOVERLAYICON: bct->overlay = (HICON)wParam; break; case WM_CONTEXTMENU: if (bct->sitem) bct->sitem->invokeContextMenu(); break; case WM_MBUTTONUP: if (bct->sitem) if (bct->sitem->getDat()) SendMessage(bct->sitem->getDat()->GetHwnd(), WM_CLOSE, 1, 0); break; case WM_LBUTTONDOWN: if (bct->sitem) { if (bct->sitem->testCloseButton() != -1) return TRUE; bct->stateId = PBS_PRESSED; InvalidateRect(bct->hwnd, nullptr, TRUE); bct->sitem->activateSession(); } if (bct->arrow) { RECT rc; GetClientRect(bct->hwnd, &rc); if (LOWORD(lParam) < rc.right - 12 && bct->stateId != PBS_DISABLED) bct->stateId = PBS_PRESSED; else if (LOWORD(lParam) > rc.right - 12) { if (GetDlgCtrlID(hwndDlg) == IDOK || bct->stateId != PBS_DISABLED) { uint16_t w = LOWORD((INT_PTR)bct->arrow); SendMessage(GetParent(hwndDlg), WM_COMMAND, MAKELONG(w, BN_CLICKED), (LPARAM)hwndDlg); } } InvalidateRect(bct->hwnd, nullptr, TRUE); return 0; } break; case WM_LBUTTONUP: if (bct->sitem) { if (bct->sitem->testCloseButton() != -1) { SendMessage(bct->sitem->getDat()->GetHwnd(), WM_CLOSE, 1, 0); return TRUE; } } break; case WM_MOUSEMOVE: if (bct->arrow && bct->stateId == PBS_HOT) InvalidateRect(bct->hwnd, nullptr, TRUE); if (bct->sitem) { if (bct->sitem->testCloseButton() != -1) { if (bct->sitem->m_sideBar->getHoveredClose() != bct->sitem) { bct->sitem->m_sideBar->setHoveredClose(bct->sitem); InvalidateRect(hwndDlg, nullptr, FALSE); } } else { bct->sitem->m_sideBar->setHoveredClose(nullptr); InvalidateRect(hwndDlg, nullptr, FALSE); } } break; case WM_TIMER: // use a timer to check if they have did a mouseout if (wParam == BUTTON_POLLID) { RECT rc; GetWindowRect(hwndDlg, &rc); POINT pt; GetCursorPos(&pt); if (!PtInRect(&rc, pt)) { // mouse must be gone, trigger mouse leave PostMessage(hwndDlg, WM_MOUSELEAVE, 0, 0L); KillTimer(hwndDlg, BUTTON_POLLID); if (bct->sitem) { bct->sitem->m_sideBar->setHoveredClose(nullptr); InvalidateRect(hwndDlg, nullptr, FALSE); } } } return 0; } return mir_callNextSubclass(hwndDlg, TSButtonWndProc, msg, wParam, lParam); } int TSAPI UnloadTSButtonModule() { if (hdc_buttonglyph) { SelectObject(hdc_buttonglyph, hbm_buttonglyph_old); DeleteObject(hbm_buttonglyph); DeleteDC(hdc_buttonglyph); } return 0; } void CustomizeButton(HWND hwndButton) { SendMessage(hwndButton, BUTTONSETCUSTOMPAINT, sizeof(TSButtonCtrl), 0); mir_subclassWindow(hwndButton, TSButtonWndProc); TSButtonCtrl *bct = (TSButtonCtrl*)GetWindowLongPtr(hwndButton, 0); if (bct) bct->hThemeToolbar = (M.isAero() || IsWinVerVistaPlus()) ? OpenThemeData(bct->hwnd, L"MENU") : OpenThemeData(bct->hwnd, L"TOOLBAR"); }