#include "ctlspriv.h" #pragma hdrstop #include "usrctl32.h" #include "button.h" // // ButtonCalcRect codes // #define CBR_CLIENTRECT 0 #define CBR_CHECKBOX 1 #define CBR_CHECKTEXT 2 #define CBR_GROUPTEXT 3 #define CBR_GROUPFRAME 4 #define CBR_PUSHBUTTON 5 #define CBR_RADIOBUTTON 6 #define Button_IsThemed(pbutn) ((pbutn)->hTheme && (pbutn)->hImage == NULL) //---------------------------------------------------------------------------// CONST BYTE mpStyleCbr[] = { CBR_PUSHBUTTON, // BS_PUSHBUTTON CBR_PUSHBUTTON, // BS_DEFPUSHBUTTON CBR_CHECKTEXT, // BS_CHECKBOX CBR_CHECKTEXT, // BS_AUTOCHECKBOX CBR_CHECKTEXT, // BS_RADIOBUTTON CBR_CHECKTEXT, // BS_3STATE CBR_CHECKTEXT, // BS_AUTO3STATE CBR_GROUPTEXT, // BS_GROUPBOX CBR_CLIENTRECT, // BS_USERBUTTON CBR_CHECKTEXT, // BS_AUTORADIOBUTTON CBR_CLIENTRECT, // BS_PUSHBOX CBR_CLIENTRECT, // BS_OWNERDRAW }; #define IMAGE_BMMAX IMAGE_CURSOR+1 static CONST BYTE rgbType[IMAGE_BMMAX] = { BS_BITMAP, // IMAGE_BITMAP BS_ICON, // IMAGE_CURSOR BS_ICON // IMAGE_ICON }; #define IsValidImage(imageType, realType, max) \ ((imageType < max) && (rgbType[imageType] == realType)) typedef struct tagBTNDATA { LPTSTR pszText; // Text string INT cchText; // char count of string PBUTN pbutn; // Button data WORD wFlags; // Alignment flags } BTNDATA, *LPBTNDATA; //---- to support multiple themes in a single process, move these into PBUTN ---- static SIZE sizeCheckBox = {0}; static SIZE sizeRadioBox = {0}; //---------------------------------------------------------------------------// // // Forwards // VOID Button_DrawPush(PBUTN pbutn, HDC hdc, UINT pbfPush); VOID GetCheckBoxSize(HDC hdc, PBUTN pbutn, BOOL fCheckBox, LPSIZE psize); WORD GetAlignment(PBUTN pbutn); VOID Button_CalcRect(PBUTN pbutn, HDC hdc, LPRECT lprc, int iCode, UINT uFlags); VOID Button_MultiExtent(WORD wFlags, HDC hdc, LPRECT lprcMax, LPTSTR lpsz, INT cch, PINT pcx, PINT pcy); __inline UINT IsPushButton(PBUTN pbutn); __inline ULONG GetButtonType(ULONG ulWinStyle); //---------------------------------------------------------------------------// // // InitButtonClass() - Registers the control's window class // BOOL InitButtonClass(HINSTANCE hInstance) { WNDCLASS wc; wc.lpfnWndProc = Button_WndProc; wc.lpszClassName = WC_BUTTON; wc.style = CS_GLOBALCLASS | CS_PARENTDC | CS_DBLCLKS | CS_VREDRAW | CS_HREDRAW; wc.cbClsExtra = 0; wc.cbWndExtra = sizeof(PBUTN); wc.hInstance = hInstance; wc.hIcon = NULL; wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = NULL; wc.lpszMenuName = NULL; return (RegisterClass(&wc) || (GetLastError() == ERROR_CLASS_ALREADY_EXISTS)); } //---------------------------------------------------------------------------// // // Button_GetThemeIds() - Gets the associated iPartId and iStateId needed for // the theme manager APIs for the button control passed // in pbutn. // HRESULT Button_GetThemeIds(PBUTN pbutn, LPINT piPartId, LPINT piStateId) { if ( piPartId ) { ULONG ulStyle = GET_STYLE(pbutn); if (IsPushButton(pbutn)) { *piPartId = BP_PUSHBUTTON; } else { switch (GetButtonType(ulStyle)) { case BS_CHECKBOX: case BS_AUTOCHECKBOX: case BS_3STATE: case BS_AUTO3STATE: *piPartId = BP_CHECKBOX; break; case BS_RADIOBUTTON: case BS_AUTORADIOBUTTON: *piPartId = BP_RADIOBUTTON; break; case BS_GROUPBOX: *piPartId = BP_GROUPBOX; break; case BS_OWNERDRAW: // // don't do anything with owerdrawn buttons // return E_FAIL; default: TraceMsg(TF_STANDARD, "What kind of buttonType is this, %#.2x", GetButtonType(ulStyle)); *piPartId = BP_PUSHBUTTON; break; } } if (piStateId) { switch (*piPartId) { case BP_PUSHBUTTON: if ((pbutn->buttonState & BST_PUSHED) || ((pbutn->buttonState & (BST_CHECKED|BST_HOT)) == BST_CHECKED)) { *piStateId = PBS_PRESSED; } else if (!IsWindowEnabled(pbutn->ci.hwnd)) { *piStateId = PBS_DISABLED; } else if (pbutn->buttonState & BST_HOT) { *piStateId = PBS_HOT; } else if (ulStyle & BS_DEFPUSHBUTTON) { *piStateId = PBS_DEFAULTED; } else { *piStateId = PBS_NORMAL; } break; case BP_CHECKBOX: case BP_RADIOBUTTON: // // NOTE (phellyar): We're relying on the order of the RADIOBUTTONSTATES and // CHECKBOXSTATES enums in tmdefs.h to calculate the correct // StateId. If the ordering of those enums changes, revisit // the logic here. // Note also that CHECKBOXSTATES is a super set of // RADIOBUTTONSTATES which is why we're using CBS_* here. // if ( pbutn->buttonState & BST_CHECKED ) { // // button is checked // *piStateId = CBS_CHECKEDNORMAL; } else if ( pbutn->buttonState & BST_INDETERMINATE ) { // // button is intedeterminate // *piStateId = CBS_MIXEDNORMAL; } else { // // button is unchecked // *piStateId = CBS_UNCHECKEDNORMAL; } if ( pbutn->buttonState & BST_PUSHED ) { // // being pressed // *piStateId += 2; } else if (!IsWindowEnabled(pbutn->ci.hwnd)) { // // disabled // *piStateId += 3; } else if (pbutn->buttonState & BST_HOT ) { // // mouse over // *piStateId += 1; } break; case BP_GROUPBOX: if (!IsWindowEnabled(pbutn->ci.hwnd)) { *piStateId = GBS_DISABLED; } else { *piStateId = GBS_NORMAL; } break; } } } return S_OK; } //---------------------------------------------------------------------------// // // Button_GetTextFlags() - Returns the DrawTextEx flags that should be used // when rendering text for this control, needed by // DrawThemeText. // DWORD Button_GetTextFlags(PBUTN pbutn) { DWORD dwTextFlags = 0; WORD wAlign = GetAlignment(pbutn); ULONG ulStyle = GET_STYLE(pbutn); // // Set up text flags // // // horizontal text alignment // switch (wAlign & HIBYTE(BS_HORZMASK)) { case HIBYTE(BS_LEFT): dwTextFlags |= DT_LEFT; break; case HIBYTE(BS_RIGHT): dwTextFlags |= DT_RIGHT; break; case HIBYTE(BS_CENTER): dwTextFlags |= DT_CENTER; break; } // // vertical text alignment // switch (wAlign & HIBYTE(BS_VERTMASK)) { case HIBYTE(BS_TOP): dwTextFlags |= DT_TOP; break; case HIBYTE(BS_BOTTOM): dwTextFlags |= DT_BOTTOM; break; case HIBYTE(BS_VCENTER): dwTextFlags |= DT_VCENTER; break; } // // line break // if (ulStyle & BS_MULTILINE) { dwTextFlags |= (DT_WORDBREAK | DT_EDITCONTROL); } else { dwTextFlags |= DT_SINGLELINE; } if (ulStyle & SS_NOPREFIX) { dwTextFlags |= DT_NOPREFIX; } // // Draw the underscore for accelorators? // if (TESTFLAG(GET_EXSTYLE(pbutn), WS_EXP_UIACCELHIDDEN)) { dwTextFlags |= DT_HIDEPREFIX; } return dwTextFlags; } DWORD ButtonStateToCustomDrawState(PBUTN pbutn) { DWORD itemState = 0; if (TESTFLAG(GET_EXSTYLE(pbutn), WS_EXP_UIFOCUSHIDDEN)) { itemState |= CDIS_SHOWKEYBOARDCUES; } if (TESTFLAG(GET_EXSTYLE(pbutn), WS_EXP_UIACCELHIDDEN)) { itemState |= CDIS_SHOWKEYBOARDCUES; } if (BUTTONSTATE(pbutn) & BST_FOCUS) { itemState |= CDIS_FOCUS; } if (BUTTONSTATE(pbutn) & BST_PUSHED) { itemState |= CDIS_SELECTED; } if (BUTTONSTATE(pbutn) & BST_HOT) { itemState |= CDIS_HOT; } if (!IsWindowEnabled(pbutn->ci.hwnd)) { itemState |= CDIS_DISABLED; } return itemState; } void Button_GetImagePosition(PBUTN pbutn, RECT* prc, int* px, int* py) { int cx = 0; int cy = 0; CCGetIconSize(&pbutn->ci, pbutn->himl, &cx, &cy); cx += pbutn->rcIcon.left + pbutn->rcIcon.right; cy += pbutn->rcIcon.top + pbutn->rcIcon.bottom; switch (pbutn->uAlign) { case BUTTON_IMAGELIST_ALIGN_RIGHT: *px = prc->right - cx; *py = prc->top + (RECTHEIGHT(*prc) - cy) / 2 + pbutn->rcIcon.top; prc->right -= cx; break; case BUTTON_IMAGELIST_ALIGN_CENTER: // This means no text *px = prc->left + (RECTWIDTH(*prc) - cx) / 2 + pbutn->rcIcon.left; *py = prc->top + (RECTHEIGHT(*prc) - cy) / 2 + pbutn->rcIcon.top; break; case BUTTON_IMAGELIST_ALIGN_TOP: *px = prc->left + (RECTWIDTH(*prc) - cx) / 2 + pbutn->rcIcon.left; *py = pbutn->rcIcon.top; prc->top += cy; break; case BUTTON_IMAGELIST_ALIGN_BOTTOM: *px = (RECTWIDTH(*prc) - cx) / 2 + pbutn->rcIcon.left; *py = prc->bottom - cy; prc->bottom -= cy; break; case BUTTON_IMAGELIST_ALIGN_LEFT: // Fall default: *px = prc->left + pbutn->rcIcon.left; *py = prc->top + (RECTHEIGHT(*prc) - cy) / 2 + pbutn->rcIcon.top; prc->left += cx; break; } } //---------------------------------------------------------------------------// // // Button_DrawThemed() - Renders button control according to the current // theme. // pbutn - the button control to render // hdc - the hdc to draw on // iPartId - the button part // iStateId - the button state // HRESULT Button_DrawThemed(PBUTN pbutn, HDC hdc, int iPartId, int iStateId) { HRESULT hr; RECT rcClient; RECT rcContent; RECT rcFocus; RECT rcCheck; DWORD dwTextFlags; LPWSTR pszText; INT cchText; NMCUSTOMDRAW nmcd = {0}; BOOL fRadioOrCheck = (iPartId == BP_RADIOBUTTON || iPartId == BP_CHECKBOX ); // // Render the button background // GetClientRect(pbutn->ci.hwnd, &rcClient); rcCheck = rcContent = rcClient; if ( fRadioOrCheck ) { SIZE sizeChar; SIZE sizeCheck; int iCode; // // Compat.... // GetTextExtentPoint32(hdc, TEXT("0"), 1, &sizeChar); GetCheckBoxSize(hdc, pbutn, (iPartId == BP_CHECKBOX), &sizeCheck); if (iPartId == BP_CHECKBOX) iCode = CBR_CHECKBOX; else iCode = CBR_RADIOBUTTON; Button_CalcRect(pbutn, hdc, &rcCheck, iCode, 0); rcCheck.bottom = rcCheck.top + sizeCheck.cx; if ((GET_STYLE(pbutn) & BS_RIGHTBUTTON) != 0) { rcCheck.left = rcContent.right - sizeCheck.cx; rcContent.right = rcCheck.left - (sizeChar.cx/2); } else { rcCheck.right = rcContent.left + sizeCheck.cx; rcContent.left = rcCheck.right + (sizeChar.cx/2); } //---- shrink radiobutton/checkbox button to fix client rect ---- if (RECTWIDTH(rcClient) < RECTWIDTH(rcCheck)) { rcCheck.right = rcCheck.left + RECTWIDTH(rcClient); } if (RECTHEIGHT(rcClient) < RECTHEIGHT(rcCheck)) { rcCheck.bottom = rcCheck.top + RECTHEIGHT(rcClient); } } nmcd.hdc = hdc; nmcd.rc = rcClient; nmcd.dwItemSpec = GetWindowID(pbutn->ci.hwnd); nmcd.uItemState = ButtonStateToCustomDrawState(pbutn); pbutn->ci.dwCustom = CICustomDrawNotify(&pbutn->ci, CDDS_PREERASE, &nmcd); if (!(pbutn->ci.dwCustom & CDRF_SKIPDEFAULT)) { hr = DrawThemeBackground(pbutn->hTheme, hdc, iPartId, iStateId, &rcCheck, 0); if (FAILED(hr)) { TraceMsg(TF_STANDARD, "Failed to render theme background"); return hr; } if (pbutn->ci.dwCustom & CDRF_NOTIFYPOSTERASE) CICustomDrawNotify(&pbutn->ci, CDDS_POSTERASE, &nmcd); pbutn->ci.dwCustom = CICustomDrawNotify(&pbutn->ci, CDDS_PREPAINT, &nmcd); if (!(pbutn->ci.dwCustom & CDRF_SKIPDEFAULT)) { // // Render the button text // GetThemeBackgroundContentRect(pbutn->hTheme, hdc, iPartId, iStateId, &rcContent, &rcContent); rcFocus = rcContent; if (pbutn->himl) { int x, y; int iImage = 0; if (ImageList_GetImageCount(pbutn->himl) > 1) { iImage = (iStateId - PBS_NORMAL); } Button_GetImagePosition(pbutn, &rcContent, &x, &y); ImageList_Draw(pbutn->himl, iImage, hdc, x, y, ILD_TRANSPARENT | (CCDPIScale(pbutn->ci)?ILD_DPISCALE:0)); } // // Get the button text // cchText = GetWindowTextLength(pbutn->ci.hwnd); if (cchText <= 0) { // // Nothing to draw // return hr; } pszText = UserLocalAlloc(0, (cchText+1)*SIZEOF(WCHAR)); if (pszText == NULL) { TraceMsg(TF_STANDARD, "Can't allocate buffer"); return E_FAIL; } GetWindowTextW(pbutn->ci.hwnd, pszText, cchText+1); dwTextFlags = Button_GetTextFlags(pbutn); if ( TESTFLAG(GET_STYLE(pbutn), BS_MULTILINE) || fRadioOrCheck ) { int cxWidth, cyHeight; TEXTMETRIC tm; if ( TESTFLAG(GET_STYLE(pbutn), BS_MULTILINE) ) { RECT rcTextExtent = rcContent; cyHeight = DrawTextEx(hdc, pszText, cchText, &rcTextExtent, dwTextFlags|DT_CALCRECT, NULL); cxWidth = RECTWIDTH(rcTextExtent); } else { SIZE size; LPWSTR pszStrip = UserLocalAlloc(0, (cchText+1)*SIZEOF(WCHAR)); if (pszStrip) { INT cchStrip = StripAccelerators(pszText, pszStrip, TRUE); GetTextExtentPoint32(hdc, pszStrip, cchStrip, &size); UserLocalFree(pszStrip); } else { GetTextExtentPoint32(hdc, pszText, cchText, &size); } cyHeight = size.cy; cxWidth = size.cx; } if (fRadioOrCheck && (cyHeight < RECTHEIGHT(rcCheck))) { // optimization for single line check/radios, align them with the top // of the check no matter when the vertical alignment rcContent.top = rcCheck.top; } else { if (dwTextFlags & DT_VCENTER) { rcContent.top += (RECTHEIGHT(rcContent) - cyHeight) / 2; } else if (dwTextFlags & DT_BOTTOM) { rcContent.top = rcContent.bottom - cyHeight; } } if ( GetTextMetrics( hdc, &tm ) && (tm.tmInternalLeading == 0) ) { // Far East fonts that have no leading. Leave space to prevent // focus rect from obscuring text. rcContent.top += g_cyBorder; } rcContent.bottom = rcContent.top + cyHeight; if (dwTextFlags & DT_CENTER) { rcContent.left += (RECTWIDTH(rcContent) - cxWidth) / 2; } else if (dwTextFlags & DT_RIGHT) { rcContent.left = rcContent.right - cxWidth; } rcContent.right= rcContent.left + cxWidth; if ( fRadioOrCheck ) { // // Inflate the bounding rect a litte, but contrained to // within the client area. // rcFocus.top = max(rcClient.top, rcContent.top-1); rcFocus.bottom = min(rcClient.bottom, rcContent.bottom+1); rcFocus.left = max(rcClient.left, rcContent.left-1); rcFocus.right = min(rcClient.right, rcContent.right+1); } } hr = DrawThemeText(pbutn->hTheme, hdc, iPartId, iStateId, pszText, cchText, dwTextFlags, 0, &rcContent); if (FAILED(hr)) { TraceMsg(TF_STANDARD, "Failed to render button text"); } if (!TESTFLAG(GET_EXSTYLE(pbutn), WS_EXP_UIFOCUSHIDDEN) && (BUTTONSTATE(pbutn) & BST_FOCUS)) { DrawFocusRect(hdc, &rcFocus); } UserLocalFree(pszText); if (pbutn->ci.dwCustom & CDRF_NOTIFYPOSTPAINT) { CICustomDrawNotify(&pbutn->ci, CDDS_POSTPAINT, &nmcd); } } } return hr; } //---------------------------------------------------------------------------// // // Button_GetTheme() - Get a handle to the theme for this button control // HTHEME Button_GetTheme(PBUTN pbutn) { // // Button's with predefined IDs can be // themed differently // static LPWSTR szButtonClasses[] = { L"Button", // =0 L"Button-OK;Button", // IDOK=1 L"Button-CANCEL;Button", // IDCANCEL=2 L"Button-ABORT;Button", // IDABORT=3 L"Button-RETRY;Button", // IDRETRY=4 L"Button-IGNORE;Button", // IDIGNORE=5 L"Button-YES;Button", // IDYES=6 L"Button-NO;Button", // IDNO=7 L"Button-CLOSE;Button", // IDCLOSE=8 L"Button-HELP;Button", // IDHELP=9 L"Button-TRYAGAIN;Button", // IDTRYAGAIN=10 L"Button-CONTINUE;Button", // IDCONTINUE=11 L"Button-APPLY;Button", // IDAPPLY=12 (not yet std) }; int iButtonId = GetWindowID(pbutn->ci.hwnd); if (iButtonId < 0 || iButtonId >= ARRAYSIZE(szButtonClasses)) // outside range { iButtonId = 0; } EnableThemeDialogTexture(GetParent(pbutn->ci.hwnd), ETDT_ENABLE); return OpenThemeData(pbutn->ci.hwnd, szButtonClasses[iButtonId]); } //---------------------------------------------------------------------------// // VOID GetCheckBoxSize(HDC hdc, PBUTN pbutn, BOOL fCheckBox, LPSIZE psize) { SIZE *psz; if (fCheckBox) psz = &sizeCheckBox; else psz = &sizeRadioBox; if ((! psz->cx) && (! psz->cy)) // not yet calculated { BOOL fGotSize = FALSE; if (pbutn->hTheme) // get themed size { int iPartId; HRESULT hr; if (fCheckBox) iPartId = BP_CHECKBOX; else iPartId = BP_RADIOBUTTON; hr = GetThemePartSize(pbutn->hTheme, hdc, iPartId, 1, NULL, TS_DRAW, psz); if (FAILED(hr)) { TraceMsg(TF_STANDARD, "Failed to get theme part size for checkbox/radiobutton"); } else { fGotSize = TRUE; } } if (! fGotSize) // get classic size (use checkbox for both) { HBITMAP hbmp = LoadBitmap(NULL, MAKEINTRESOURCE(OBM_CHECKBOXES)); if (hbmp != NULL) { BITMAP bmp; GetObject(hbmp, sizeof(BITMAP), &bmp); // // Checkbox bitmap is arranged 4 over and three down. Only need to get // the size of a single checkbox, so do the math here. // psz->cx = bmp.bmWidth / 4; psz->cy = bmp.bmHeight / 3; DeleteObject(hbmp); } else { AssertMsg(hbmp != NULL, TEXT("Unable to load checkbox bitmap")); } } } *psize = *psz; } //---------------------------------------------------------------------------// // __inline BYTE GetButtonStyle(ULONG ulWinStyle) { return (BYTE) LOBYTE(ulWinStyle & BS_TYPEMASK); } //---------------------------------------------------------------------------// // __inline ULONG GetButtonType(ULONG ulWinStyle) { return ulWinStyle & BS_TYPEMASK; } //---------------------------------------------------------------------------// // // IsPushButton() // // Returns non-zero if the window is a push button. Returns flags that // are interesting if it is. These flags are // UINT IsPushButton(PBUTN pbutn) { BYTE bStyle; UINT flags; ULONG ulStyle = GET_STYLE(pbutn); bStyle = GetButtonStyle(ulStyle); flags = 0; switch (bStyle) { case LOBYTE(BS_PUSHBUTTON): flags |= PBF_PUSHABLE; break; case LOBYTE(BS_DEFPUSHBUTTON): flags |= PBF_PUSHABLE | PBF_DEFAULT; break; default: if (ulStyle & BS_PUSHLIKE) { flags |= PBF_PUSHABLE; } } return flags; } //---------------------------------------------------------------------------// // // GetAlignment() // // Gets default alignment of button. If BS_HORZMASK and/or BS_VERTMASK // is specified, uses those. Otherwise, uses default for button. // // It's probably a fine time to describe what alignment flags mean for // each type of button. Note that the presence of a bitmap/icon affects // the meaning of alignments. // // (1) Push like buttons // With one of {bitmap, icon, text}: // Just like you'd expect // With one of {bitmap, icon} AND text: // Image & text are centered as a unit; alignment means where // the image shows up. E.G., left-aligned means the image // on the left, text on the right. // (2) Radio/check like buttons // Left aligned means check/radio box is on left, then bitmap/icon // and text follows, left justified. // Right aligned means checkk/radio box is on right, preceded by // text and bitmap/icon, right justified. // Centered has no meaning. // With one of {bitmap, icon} AND text: // Top aligned means bitmap/icon above, text below // Bottom aligned means text above, bitmap/icon below // With one of {bitmap, icon, text} // Alignments mean what you'd expect. // (3) Group boxes // Left aligned means text is left justified on left side // Right aligned means text is right justified on right side // Center aligned means text is in middle // WORD GetAlignment(PBUTN pbutn) { BYTE bHorz; BYTE bVert; ULONG ulStyle = GET_STYLE(pbutn); bHorz = HIBYTE(ulStyle & BS_HORZMASK); bVert = HIBYTE(ulStyle & BS_VERTMASK); if (!bHorz || !bVert) { if (IsPushButton(pbutn)) { if (!bHorz) { bHorz = HIBYTE(BS_CENTER); } } else { if (!bHorz) { bHorz = HIBYTE(BS_LEFT); } } if (GetButtonStyle(ulStyle) == BS_GROUPBOX) { if (!bVert) { bVert = HIBYTE(BS_TOP); } } else { if (!bVert) { bVert = HIBYTE(BS_VCENTER); } } } return bHorz | bVert; } //---------------------------------------------------------------------------// // // Button_SetFont() // // Changes button font, and decides if we can use real bold font for default // push buttons or if we have to simulate it. // VOID Button_SetFont(PBUTN pbutn, HFONT hFont, BOOL fRedraw) { pbutn->hFont = hFont; if (fRedraw && IsWindowVisible(pbutn->ci.hwnd)) { InvalidateRect(pbutn->ci.hwnd, NULL, TRUE); } } //---------------------------------------------------------------------------// // HBRUSH Button_InitDC(PBUTN pbutn, HDC hdc) { UINT uMsg; BYTE bStyle; HBRUSH hBrush; ULONG ulStyle = GET_STYLE(pbutn); ULONG ulStyleEx = GET_EXSTYLE(pbutn); // // Set BkMode before getting brush so that the app can change it to // transparent if it wants. // SetBkMode(hdc, OPAQUE); bStyle = GetButtonStyle(ulStyle); switch (bStyle) { default: if (TESTFLAG(GET_STATE2(pbutn), WS_S2_WIN40COMPAT) && ((ulStyle & BS_PUSHLIKE) == 0)) { uMsg = WM_CTLCOLORSTATIC; break; } case LOBYTE(BS_PUSHBUTTON): case LOBYTE(BS_DEFPUSHBUTTON): case LOBYTE(BS_OWNERDRAW): case LOBYTE(BS_USERBUTTON): uMsg = WM_CTLCOLORBTN; break; } hBrush = (HBRUSH)SendMessage(GetParent(pbutn->ci.hwnd), uMsg, (WPARAM)hdc, (LPARAM)pbutn->ci.hwnd); // // Select in the user's font if set, and save the old font so that we can // restore it when we release the dc. // if (pbutn->hFont) { SelectObject(hdc, pbutn->hFont); } // // Clip output to the window rect if needed. // if (bStyle != LOBYTE(BS_GROUPBOX)) { RECT rcClient; GetClientRect(pbutn->ci.hwnd, &rcClient); IntersectClipRect(hdc, 0, 0, rcClient.right, rcClient.bottom); } if ((ulStyleEx & WS_EX_RTLREADING) != 0) { SetTextAlign(hdc, TA_RTLREADING | GetTextAlign(hdc)); } return hBrush; } //---------------------------------------------------------------------------// // HDC Button_GetDC(PBUTN pbutn, HBRUSH *phBrush) { HDC hdc = NULL; if (IsWindowVisible(pbutn->ci.hwnd)) { HBRUSH hBrush; hdc = GetDC(pbutn->ci.hwnd); hBrush = Button_InitDC(pbutn, hdc); if ((phBrush != NULL) && hBrush) { *phBrush = hBrush; } } return hdc; } //---------------------------------------------------------------------------// // VOID Button_ReleaseDC(PBUTN pbutn, HDC hdc, HBRUSH *phBrush) { ULONG ulStyleEx = GET_EXSTYLE(pbutn); if ((ulStyleEx & WS_EX_RTLREADING) != 0) { SetTextAlign(hdc, GetTextAlign(hdc) & ~TA_RTLREADING); } if (pbutn->hFont) { SelectObject(hdc, GetStockObject(SYSTEM_FONT)); } ReleaseDC(pbutn->ci.hwnd, hdc); } //---------------------------------------------------------------------------// // VOID Button_OwnerDraw(PBUTN pbutn, HDC hdc, UINT itemAction) { DRAWITEMSTRUCT drawItemStruct; UINT itemState = 0; int iButtonId = GetWindowID(pbutn->ci.hwnd); if (TESTFLAG(GET_EXSTYLE(pbutn), WS_EXP_UIFOCUSHIDDEN)) { itemState |= ODS_NOFOCUSRECT; } if (TESTFLAG(GET_EXSTYLE(pbutn), WS_EXP_UIACCELHIDDEN)) { itemState |= ODS_NOACCEL; } if (TESTFLAG(BUTTONSTATE(pbutn), BST_FOCUS)) { itemState |= ODS_FOCUS; } if (TESTFLAG(BUTTONSTATE(pbutn), BST_PUSHED)) { itemState |= ODS_SELECTED; } if (!IsWindowEnabled(pbutn->ci.hwnd)) { itemState |= ODS_DISABLED; } // // Populate the draw item struct // drawItemStruct.CtlType = ODT_BUTTON; drawItemStruct.CtlID = iButtonId; drawItemStruct.itemAction = itemAction; drawItemStruct.itemState = itemState; drawItemStruct.hwndItem = pbutn->ci.hwnd; drawItemStruct.hDC = hdc; GetClientRect(pbutn->ci.hwnd, &drawItemStruct.rcItem); drawItemStruct.itemData = 0L; // // Send a WM_DRAWITEM message to our parent // SendMessage(GetParent(pbutn->ci.hwnd), WM_DRAWITEM, (WPARAM)iButtonId, (LPARAM)&drawItemStruct); } //---------------------------------------------------------------------------// // VOID Button_CalcRect(PBUTN pbutn, HDC hdc, LPRECT lprc, int iCode, UINT uFlags) { CONST TCHAR szOneChar[] = TEXT("0"); SIZE sizeExtent; int dy; WORD wAlign; int cxEdge, cyEdge; int cxBorder, cyBorder; ULONG ulStyle = GET_STYLE(pbutn); ULONG ulStyleEx = GET_EXSTYLE(pbutn); cxEdge = GetSystemMetrics(SM_CXEDGE); cyEdge = GetSystemMetrics(SM_CYEDGE); cxBorder = GetSystemMetrics(SM_CXBORDER); cyBorder = GetSystemMetrics(SM_CYBORDER); GetClientRect(pbutn->ci.hwnd, lprc); wAlign = GetAlignment(pbutn); switch (iCode) { case CBR_PUSHBUTTON: // // Subtract out raised edge all around // InflateRect(lprc, -cxEdge, -cyEdge); if (uFlags & PBF_DEFAULT) { InflateRect(lprc, -cxBorder, -cyBorder); } break; case CBR_CHECKBOX: case CBR_RADIOBUTTON: { SIZE sizeChk = {0}; GetCheckBoxSize(hdc, pbutn, (iCode == CBR_CHECKBOX), &sizeChk); switch (wAlign & HIBYTE(BS_VERTMASK)) { case HIBYTE(BS_VCENTER): lprc->top = (lprc->top + lprc->bottom - sizeChk.cy) / 2; break; case HIBYTE(BS_TOP): case HIBYTE(BS_BOTTOM): GetTextExtentPoint32(hdc, (LPTSTR)szOneChar, 1, &sizeExtent); dy = sizeExtent.cy + sizeExtent.cy/4; // // Save vertical extent // sizeExtent.cx = dy; // // Get centered amount // dy = (dy - sizeChk.cy) / 2; if ((wAlign & HIBYTE(BS_VERTMASK)) == HIBYTE(BS_TOP)) { lprc->top += dy; } else { lprc->top = lprc->bottom - sizeExtent.cx + dy; } break; } if ((ulStyle & BS_RIGHTBUTTON) != 0) { lprc->left = lprc->right - sizeChk.cx; } else { lprc->right = lprc->left + sizeChk.cx; } break; } case CBR_CHECKTEXT: { SIZE sizeChk = {0}; GetCheckBoxSize(hdc, pbutn, TRUE, &sizeChk); if ((ulStyle & BS_RIGHTBUTTON) != 0) { lprc->right -= sizeChk.cx; // // More spacing for 4.0 dudes // if (TESTFLAG(GET_STATE2(pbutn), WS_S2_WIN40COMPAT)) { GetTextExtentPoint32(hdc, szOneChar, 1, &sizeExtent); lprc->right -= sizeExtent.cx / 2; } } else { lprc->left += sizeChk.cx; // // More spacing for 4.0 dudes // if (TESTFLAG(GET_STATE2(pbutn), WS_S2_WIN40COMPAT)) { GetTextExtentPoint32(hdc, szOneChar, 1, &sizeExtent); lprc->left += sizeExtent.cx / 2; } } break; } case CBR_GROUPTEXT: { LPWSTR pszText = NULL; INT cchText = GetWindowTextLength(pbutn->ci.hwnd); BOOL fSucceeded = FALSE; if (cchText > 0) { pszText = UserLocalAlloc(0, (cchText+1)*SIZEOF(WCHAR)); if (pszText) { if (GetWindowText(pbutn->ci.hwnd, pszText, cchText+1) > 0) { // // if not themed // if (!Button_IsThemed(pbutn)) { GetTextExtentPoint32(hdc, pszText, cchText, &sizeExtent); } else { DWORD dwTextFlags = Button_GetTextFlags(pbutn); RECT rcExtent; GetThemeTextExtent(pbutn->hTheme, hdc, BP_GROUPBOX, 0, pszText, cchText, dwTextFlags, lprc, &rcExtent); sizeExtent.cx = RECTWIDTH(rcExtent); sizeExtent.cy = RECTHEIGHT(rcExtent); } sizeExtent.cx += GetSystemMetrics(SM_CXEDGE) * 2; switch (wAlign & HIBYTE(BS_HORZMASK)) { // // BFLEFT, nothing // case HIBYTE(BS_LEFT): lprc->left += (SYSFONT_CXCHAR - GetSystemMetrics(SM_CXBORDER)); lprc->right = lprc->left + (int)(sizeExtent.cx); break; case HIBYTE(BS_RIGHT): lprc->right -= (SYSFONT_CXCHAR - GetSystemMetrics(SM_CXBORDER)); lprc->left = lprc->right - (int)(sizeExtent.cx); break; case HIBYTE(BS_CENTER): lprc->left = (lprc->left + lprc->right - (int)(sizeExtent.cx)) / 2; lprc->right = lprc->left + (int)(sizeExtent.cx); break; } // // Center aligned. // lprc->bottom = lprc->top + sizeExtent.cy + GetSystemMetrics(SM_CYEDGE); fSucceeded = TRUE; } UserLocalFree(pszText); } } if (!fSucceeded) { SetRectEmpty(lprc); } break; } case CBR_GROUPFRAME: GetTextExtentPoint32(hdc, (LPTSTR)szOneChar, 1, &sizeExtent); lprc->top += sizeExtent.cy / 2; break; } } //---------------------------------------------------------------------------// // // Button_MultiExtent() // // Calculates button text extent, given alignment flags. // VOID Button_MultiExtent(WORD wFlags, HDC hdc, LPRECT lprcMax, LPTSTR pszBuffer, INT cchBuffer, PINT pcx, PINT pcy) { RECT rc; UINT dtFlags = DT_CALCRECT | DT_WORDBREAK | DT_EDITCONTROL; CopyRect(&rc, lprcMax); // // Note that since we're just calculating the maximum dimensions, // left-justification and top-justification are not important. // Also, remember to leave margins horz and vert that follow our rules // in DrawBtnText(). // InflateRect(&rc, -GetSystemMetrics(SM_CXEDGE), -GetSystemMetrics(SM_CYBORDER)); if ((wFlags & LOWORD(BS_HORZMASK)) == LOWORD(BS_CENTER)) { dtFlags |= DT_CENTER; } if ((wFlags & LOWORD(BS_VERTMASK)) == LOWORD(BS_VCENTER)) { dtFlags |= DT_VCENTER; } DrawTextEx(hdc, pszBuffer, cchBuffer, &rc, dtFlags, NULL); if (pcx) { *pcx = rc.right-rc.left; } if (pcy) { *pcy = rc.bottom-rc.top; } } //---------------------------------------------------------------------------// // // Button_MultiDraw() // // Draws multiline button text // BOOL Button_MultiDraw(HDC hdc, LPARAM lParam, WPARAM wParam, INT cx, INT cy) { BTNDATA *pBtnData = (BTNDATA *)lParam; if (pBtnData) { RECT rc; UINT dtFlags = DT_WORDBREAK | DT_EDITCONTROL; PBUTN pbutn = pBtnData->pbutn; SetRect(&rc, 0, 0, cx, cy); if (TESTFLAG(GET_EXSTYLE(pbutn), WS_EXP_UIACCELHIDDEN)) { dtFlags |= DT_HIDEPREFIX; } else if (pbutn->fPaintKbdCuesOnly) { dtFlags |= DT_PREFIXONLY; } // // Horizontal alignment // switch (pBtnData->wFlags & LOWORD(BS_HORZMASK)) { case LOWORD(BS_CENTER): dtFlags |= DT_CENTER; break; case LOWORD(BS_RIGHT): dtFlags |= DT_RIGHT; break; } // // Vertical alignment // switch (pBtnData->wFlags & LOWORD(BS_VERTMASK)) { case LOWORD(BS_VCENTER): dtFlags |= DT_VCENTER; break; case LOWORD(BS_BOTTOM): dtFlags |= DT_BOTTOM; break; } DrawTextEx(hdc, pBtnData->pszText, pBtnData->cchText, &rc, dtFlags, NULL); } return TRUE; } //---------------------------------------------------------------------------// // BOOL Button_SetCapture(PBUTN pbutn, UINT uCodeMouse) { BUTTONSTATE(pbutn) |= uCodeMouse; if (!(BUTTONSTATE(pbutn) & BST_CAPTURED)) { SetCapture(pbutn->ci.hwnd); BUTTONSTATE(pbutn) |= BST_CAPTURED; // // To prevent redundant CLICK messages, we set the INCLICK bit so // the WM_SETFOCUS code will not do a Button_NotifyParent(BN_CLICKED). // BUTTONSTATE(pbutn) |= BST_INCLICK; SetFocus(pbutn->ci.hwnd); BUTTONSTATE(pbutn) &= ~BST_INCLICK; } return BUTTONSTATE(pbutn) & BST_CAPTURED; } //---------------------------------------------------------------------------// // VOID Button_NotifyParent(PBUTN pbutn, UINT uCode) { HWND hwndParent = GetParent(pbutn->ci.hwnd); int iButtonId = GetWindowID(pbutn->ci.hwnd); if ( !hwndParent ) { hwndParent = pbutn->ci.hwnd; } SendMessage(hwndParent, WM_COMMAND, MAKELONG(iButtonId, uCode), (LPARAM)pbutn->ci.hwnd); } //---------------------------------------------------------------------------// // VOID Button_ReleaseCapture(PBUTN pbutn, BOOL fCheck) { UINT uCheck; BOOL fNotifyParent = FALSE; ULONG ulStyle = GET_STYLE(pbutn); if (BUTTONSTATE(pbutn) & BST_PUSHED) { SendMessage(pbutn->ci.hwnd, BM_SETSTATE, FALSE, 0); if (fCheck) { switch (GetButtonType(ulStyle)) { case BS_AUTOCHECKBOX: case BS_AUTO3STATE: uCheck = (UINT)((BUTTONSTATE(pbutn) & BST_CHECKMASK) + 1); if (uCheck > (UINT)(GetButtonType(ulStyle) == BS_AUTO3STATE ? BST_INDETERMINATE : BST_CHECKED)) { uCheck = BST_UNCHECKED; } SendMessage(pbutn->ci.hwnd, BM_SETCHECK, uCheck, 0); break; case BS_AUTORADIOBUTTON: { // // Walk the radio buttons in the same group as us. Check ourself // and uncheck everyone else. // HWND hwndNext = pbutn->ci.hwnd; HWND hwndParent = GetParent(pbutn->ci.hwnd); do { if ((UINT)SendMessage(hwndNext, WM_GETDLGCODE, 0, 0L) & DLGC_RADIOBUTTON) { SendMessage(hwndNext, BM_SETCHECK, hwndNext == pbutn->ci.hwnd, 0L); } hwndNext = GetNextDlgGroupItem(hwndParent, hwndNext, FALSE); } // // Loop until we see ourself again // while (hwndNext != pbutn->ci.hwnd); break; } } fNotifyParent = TRUE; } } if (BUTTONSTATE(pbutn) & BST_CAPTURED) { BUTTONSTATE(pbutn) &= ~(BST_CAPTURED | BST_MOUSE); ReleaseCapture(); } if (fNotifyParent) { // // We have to do the notification after setting the buttonstate bits. // Button_NotifyParent(pbutn, BN_CLICKED); } } //---------------------------------------------------------------------------// // // Button_DrawText() // // Draws text of button. // VOID Button_DrawText(PBUTN pbutn, HDC hdc, DWORD dwFlags, BOOL fDepress) { ULONG ulStyle = GET_STYLE(pbutn); BYTE bStyle = GetButtonStyle(ulStyle); if ((bStyle != LOBYTE(BS_GROUPBOX)) || TESTFLAG(dwFlags, DBT_TEXT)) { LPTSTR pszText = NULL; INT cchText = GetWindowTextLength(pbutn->ci.hwnd); if (cchText >= 0) { pszText = UserLocalAlloc(0, (cchText+1)*SIZEOF(WCHAR)); if (pszText) { UINT pbfPush = IsPushButton(pbutn); RECT rc; int x = 0, y = 0; int cx = 0, cy = 0; GetWindowText(pbutn->ci.hwnd, pszText, cchText+1); if (!pbfPush && (bStyle == LOBYTE(BS_OWNERDRAW))) { // // Skip stuff for ownerdraw buttons, since we aren't going to // draw text/image. // Button_CalcRect(pbutn, hdc, &rc, mpStyleCbr[bStyle], pbfPush); } else if (!Button_IsThemed(pbutn)) { HBRUSH hbr; UINT dsFlags; BTNDATA btnData; LPARAM lData = 0; WPARAM wData; WORD wFlags = GetAlignment(pbutn); if (pbfPush) { Button_CalcRect(pbutn, hdc, &rc, CBR_PUSHBUTTON, pbfPush); IntersectClipRect(hdc, rc.left, rc.top, rc.right, rc.bottom); // // This is because we didn't have WM_CTLCOLOR, // CTLCOLOR_BTN actually set up the button colors. For // old apps, CTLCOLOR_BTN needs to work like CTLCOLOR_STATIC. // SetBkColor(hdc, GetSysColor(COLOR_3DFACE)); SetTextColor(hdc, GetSysColor(COLOR_BTNTEXT)); hbr = GetSysColorBrush(COLOR_BTNTEXT); } else { Button_CalcRect(pbutn, hdc, &rc, mpStyleCbr[bStyle], pbfPush); hbr = GetSysColorBrush(COLOR_WINDOWTEXT); } if (pbutn->himl) { int x, y; Button_GetImagePosition(pbutn, &rc, &x, &y); if (fDepress) { x += GetSystemMetrics(SM_CXBORDER); y += GetSystemMetrics(SM_CYBORDER); } ImageList_Draw(pbutn->himl, 0, hdc, x, y, ILD_TRANSPARENT | (CCDPIScale(pbutn->ci)?ILD_DPISCALE:0)); } // Initialize data for DrawState if ((ulStyle & BS_BITMAP) != 0) { // bitmap // lData is an hbitmap // wData is 0 BITMAP bmp; GetObject(pbutn->hImage, sizeof(BITMAP), &bmp); cx = bmp.bmWidth; cy = bmp.bmHeight; dsFlags = DST_BITMAP; lData = (LPARAM)pbutn->hImage; wData = 0; } else if ((ulStyle & BS_ICON) != 0) { // icon // lData is an hicon // wData is 0 SIZE sizeIcon; GetIconSize(pbutn->hImage, &sizeIcon); cx = sizeIcon.cx; cy = sizeIcon.cy; dsFlags = DST_ICON; lData = (LPARAM)pbutn->hImage; wData = 0; } else { if ((ulStyle & BS_MULTILINE) != 0) { // multiline // package the button data in bdt. DrawState wull call Button_MultiDraw // lData is PBTNDATA // wData is 0 Button_MultiExtent(wFlags, hdc, &rc, pszText, cchText, &cx, &cy); btnData.pszText = pszText; btnData.cchText = cchText; btnData.pbutn = pbutn; btnData.wFlags = wFlags; dsFlags = DST_COMPLEX; lData = (LPARAM)&btnData; wData = 0; } else { // simple text button // lData is pszText // wData is cchText // Try to get the text extent with mnemonics stripped. SIZE size; LPWSTR pszStrip = UserLocalAlloc(0, (cchText+1)*SIZEOF(WCHAR)); if (pszStrip != NULL) { INT cchStrip = StripAccelerators(pszText, pszStrip, TRUE); GetTextExtentPoint32(hdc, pszStrip, cchStrip, &size); UserLocalFree(pszStrip); } else { GetTextExtentPoint32(hdc, pszText, cchText, &size); } cx = size.cx; cy = size.cy; // // If the control doesn't need underlines, set DST_HIDEPREFIX and // also do not show the focus indicator // dsFlags = DST_PREFIXTEXT; if (TESTFLAG(GET_EXSTYLE(pbutn), WS_EXP_UIACCELHIDDEN)) { dsFlags |= DSS_HIDEPREFIX; } else if (pbutn->fPaintKbdCuesOnly) { dsFlags |= DSS_PREFIXONLY; } lData = (LPARAM)pszText; wData = cchText; } // // Add on a pixel or two of vertical space to make centering // happier. That way underline won't abut focus rect unless // spacing is really tight. // cy++; } // // ALIGNMENT // // // Horizontal // switch (wFlags & HIBYTE(BS_HORZMASK)) { // // For left & right justified, we leave a margin of CXEDGE on either // side for eye-pleasing space. // case HIBYTE(BS_LEFT): x = rc.left + GetSystemMetrics(SM_CXEDGE); break; case HIBYTE(BS_RIGHT): x = rc.right - cx - GetSystemMetrics(SM_CXEDGE); break; default: x = (rc.left + rc.right - cx) / 2; break; } // // Vertical // switch (wFlags & HIBYTE(BS_VERTMASK)) { // // For top & bottom justified, we leave a margin of CYBORDER on // either side for more eye-pleasing space. // case HIBYTE(BS_TOP): y = rc.top + GetSystemMetrics(SM_CYBORDER); break; case HIBYTE(BS_BOTTOM): y = rc.bottom - cy - GetSystemMetrics(SM_CYBORDER); break; default: y = (rc.top + rc.bottom - cy) / 2; break; } // // Draw the text // if (lData && TESTFLAG(dwFlags, DBT_TEXT)) { // // This isn't called for USER buttons. // UserAssert(bStyle != LOBYTE(BS_USERBUTTON)); if (fDepress) { x += GetSystemMetrics(SM_CXBORDER); y += GetSystemMetrics(SM_CYBORDER); } if (!IsWindowEnabled(pbutn->ci.hwnd)) { UserAssert(HIBYTE(BS_ICON) == HIBYTE(BS_BITMAP)); if (GetSystemMetrics(SM_SLOWMACHINE) && ((ulStyle & (BS_ICON | BS_BITMAP)) != 0) && (GetBkColor(hdc) != GetSysColor(COLOR_GRAYTEXT))) { // // Perf && consistency with menus, statics // SetTextColor(hdc, GetSysColor(COLOR_GRAYTEXT)); } else { dsFlags |= DSS_DISABLED; } } // // Use transparent mode for checked push buttons since we're going to // fill background with dither. // if (pbfPush) { switch (BUTTONSTATE(pbutn) & BST_CHECKMASK) { case BST_INDETERMINATE: hbr = GetSysColorBrush(COLOR_GRAYTEXT); dsFlags |= DSS_MONO; // // FALL THRU // case BST_CHECKED: // // Drawing on dithered background... // SetBkMode(hdc, TRANSPARENT); break; } } // // Use brush and colors currently selected into hdc when we grabbed // color // DrawState(hdc, hbr, (DRAWSTATEPROC)Button_MultiDraw, lData, wData, x, y, cx, cy, dsFlags); } } // Draw focus rect. // // This can get called for OWNERDRAW and USERDRAW buttons. However, only // OWNERDRAW buttons let the owner change the drawing of the focus button. if (TESTFLAG(dwFlags, DBT_FOCUS)) { if (bStyle == LOBYTE(BS_OWNERDRAW)) { // // For ownerdraw buttons, this is only called in response to a // WM_SETFOCUS or WM_KILL FOCUS message. So, we can check the // new state of the focus by looking at the BUTTONSTATE bits // which are set before this procedure is called. // Button_OwnerDraw(pbutn, hdc, ODA_FOCUS); } else { // // Don't draw the focus if underlines are not turned on // if (!TESTFLAG(GET_EXSTYLE(pbutn), WS_EXP_UIFOCUSHIDDEN)) { // // Let focus rect always hug edge of push buttons. We already // have the client area setup for push buttons, so we don't have // to do anything. // if (!pbfPush) { RECT rcClient; GetClientRect(pbutn->ci.hwnd, &rcClient); if (bStyle == LOBYTE(BS_USERBUTTON)) { CopyRect(&rc, &rcClient); } else if (Button_IsThemed(pbutn)) { // // if themed // int iPartId = 0; int iStateId = 0; Button_GetThemeIds(pbutn, &iPartId, &iStateId); GetThemeBackgroundContentRect(pbutn->hTheme, hdc, iPartId, iStateId, &rcClient, &rc); GetThemeTextExtent(pbutn->hTheme, hdc, iPartId, iStateId, pszText, -1, Button_GetTextFlags(pbutn), &rc, &rc); // // Inflate the bounding rect a litte, but contrained to // within the client area. // rc.top = max(rcClient.top, rc.top-1); rc.bottom = min(rcClient.bottom, rc.bottom+1); rc.left = max(rcClient.left, rc.left-1); rc.right = min(rcClient.right, rc.right+1); } else { // // Try to leave a border all around text. That causes // focus to hug text. // rc.top = max(rcClient.top, y-GetSystemMetrics(SM_CYBORDER)); rc.bottom = min(rcClient.bottom, rc.top + GetSystemMetrics(SM_CYEDGE) + cy); rc.left = max(rcClient.left, x-GetSystemMetrics(SM_CXBORDER)); rc.right = min(rcClient.right, rc.left + GetSystemMetrics(SM_CXEDGE) + cx); } } else { InflateRect(&rc, -GetSystemMetrics(SM_CXBORDER), -GetSystemMetrics(SM_CYBORDER)); } // // Are back & fore colors set properly? // DrawFocusRect(hdc, &rc); } } } UserLocalFree(pszText); } } } } //---------------------------------------------------------------------------// // // DrawCheck() // VOID Button_DrawCheck(PBUTN pbutn, HDC hdc, HBRUSH hBrush) { // // if not themed // if (!Button_IsThemed(pbutn)) // Images don't have a mask so look ugly. Need to use old painting { RECT rc; UINT uFlags; BOOL fDoubleBlt = FALSE; ULONG ulStyle = GET_STYLE(pbutn); SIZE sizeChk = {0}; Button_CalcRect(pbutn, hdc, &rc, CBR_CHECKBOX, 0); uFlags = 0; if ( BUTTONSTATE(pbutn) & BST_CHECKMASK ) { uFlags |= DFCS_CHECKED; } if ( BUTTONSTATE(pbutn) & BST_PUSHED ) { uFlags |= DFCS_PUSHED; } if ( !IsWindowEnabled(pbutn->ci.hwnd) ) { uFlags |= DFCS_INACTIVE; } switch (GetButtonType(ulStyle)) { case BS_AUTORADIOBUTTON: case BS_RADIOBUTTON: fDoubleBlt = TRUE; uFlags |= DFCS_BUTTONRADIO; break; case BS_3STATE: case BS_AUTO3STATE: if ((BUTTONSTATE(pbutn) & BST_CHECKMASK) == BST_INDETERMINATE) { uFlags |= DFCS_BUTTON3STATE; break; } // // FALL THRU // default: uFlags |= DFCS_BUTTONCHECK; break; } if ((ulStyle & BS_FLAT) != 0) { uFlags |= DFCS_FLAT | DFCS_MONO; } GetCheckBoxSize(hdc, pbutn, TRUE, &sizeChk); rc.right = rc.left + sizeChk.cx; rc.bottom = rc.top + sizeChk.cy; FillRect(hdc, &rc, hBrush); DrawFrameControl(hdc, &rc, DFC_BUTTON, uFlags); } else { int iStateId = 0; int iPartId = 0; Button_GetThemeIds(pbutn, &iPartId, &iStateId); if ((iPartId != BP_RADIOBUTTON) && (iPartId != BP_CHECKBOX)) { TraceMsg(TF_STANDARD, "Button_DrawCheck: Not a radio or check, iPartId = %d", iPartId); return; } Button_DrawThemed(pbutn, hdc, iPartId, iStateId); } } //---------------------------------------------------------------------------// // VOID Button_DrawNewState(PBUTN pbutn, HDC hdc, HBRUSH hbr, UINT sOld) { if (sOld != (UINT)(BUTTONSTATE(pbutn) & BST_PUSHED)) { UINT pbfPush; ULONG ulStyle = GET_STYLE(pbutn); pbfPush = IsPushButton(pbutn); switch (GetButtonType(ulStyle)) { case BS_GROUPBOX: case BS_OWNERDRAW: break; default: if (!pbfPush) { Button_DrawCheck(pbutn, hdc, hbr); break; } case BS_PUSHBUTTON: case BS_DEFPUSHBUTTON: case BS_PUSHBOX: Button_DrawPush(pbutn, hdc, pbfPush); break; } } } //---------------------------------------------------------------------------// // // Button_DrawPush() // // Draws push-like button with text // VOID Button_DrawPush(PBUTN pbutn, HDC hdc, UINT pbfPush) { // // if not themed // if (!Button_IsThemed(pbutn)) { RECT rc; UINT uFlags = 0; UINT uState = 0; ULONG ulStyle = GET_STYLE(pbutn); NMCUSTOMDRAW nmcd = {0}; // // Always a push button if calling this function // uState = DFCS_BUTTONPUSH; GetClientRect(pbutn->ci.hwnd, &rc); nmcd.hdc = hdc; nmcd.rc = rc; nmcd.dwItemSpec = GetWindowID(pbutn->ci.hwnd); nmcd.uItemState = ButtonStateToCustomDrawState(pbutn); if (BUTTONSTATE(pbutn) & BST_PUSHED) { uState |= DFCS_PUSHED; } pbutn->ci.dwCustom = CICustomDrawNotify(&pbutn->ci, CDDS_PREERASE, &nmcd); if (!(pbutn->ci.dwCustom & CDRF_SKIPDEFAULT)) { if (!pbutn->fPaintKbdCuesOnly) { if (BUTTONSTATE(pbutn) & BST_CHECKMASK) { uState |= DFCS_CHECKED; } if (TESTFLAG(GET_STATE2(pbutn), WS_S2_WIN40COMPAT)) { uFlags = BF_SOFT; } if ((ulStyle & BS_FLAT) != 0) { uFlags |= DFCS_FLAT | DFCS_MONO; } if (pbfPush & PBF_DEFAULT) { int cxBorder = GetSystemMetrics(SM_CXBORDER); int cyBorder = GetSystemMetrics(SM_CYBORDER); int clFrame = 1; int x = rc.left; int y = rc.top; int cxWidth = cxBorder * clFrame; int cyWidth = cyBorder * clFrame; int cx = rc.right - x - cxWidth; int cy = rc.bottom - y - cyWidth; HBRUSH hbrFill = GetSysColorBrush(COLOR_WINDOWFRAME); HBRUSH hbrSave = SelectObject(hdc, hbrFill); PatBlt(hdc, x, y, cxWidth, cy, PATCOPY); PatBlt(hdc, x + cxWidth, y, cx, cyWidth, PATCOPY); PatBlt(hdc, x, y + cy, cx, cyWidth, PATCOPY); PatBlt(hdc, x + cx, y + cyWidth, cxWidth, cy, PATCOPY); SelectObject(hdc, hbrSave); InflateRect(&rc, -cxBorder, -cyBorder); if (uState & DFCS_PUSHED) { uFlags |= DFCS_FLAT; } } DrawFrameControl(hdc, &rc, DFC_BUTTON, uState | uFlags); } if (pbutn->ci.dwCustom & CDRF_NOTIFYPOSTERASE) CICustomDrawNotify(&pbutn->ci, CDDS_POSTERASE, &nmcd); pbutn->ci.dwCustom = CICustomDrawNotify(&pbutn->ci, CDDS_PREPAINT, &nmcd); if (!(pbutn->ci.dwCustom & CDRF_SKIPDEFAULT)) { Button_DrawText(pbutn, hdc, DBT_TEXT | (BUTTONSTATE(pbutn) & BST_FOCUS ? DBT_FOCUS : 0), (uState & DFCS_PUSHED)); if (pbutn->ci.dwCustom & CDRF_NOTIFYPOSTPAINT) CICustomDrawNotify(&pbutn->ci, CDDS_POSTPAINT, &nmcd); } } } else { int iStateId = 0; int iPartId = 0; Button_GetThemeIds(pbutn, &iPartId, &iStateId); if (iPartId != BP_PUSHBUTTON) { TraceMsg(TF_STANDARD, "Not a Pushbutton"); return; } Button_DrawThemed(pbutn, hdc, iPartId, iStateId); } } BOOL Button_OnSetImageList(PBUTN pbutn, BUTTON_IMAGELIST* biml) { BOOL fRet = FALSE; if (biml) { if (biml->himl) { pbutn->rcIcon = biml->margin; pbutn->himl = biml->himl; pbutn->uAlign = biml->uAlign; fRet = TRUE; } } return fRet; } void ApplyMarginsToRect(RECT* prcm, RECT* prc) { prc->left -= prcm->left; prc->top -= prcm->top; prc->right += prcm->right; prc->bottom += prcm->bottom; } BOOL Button_OnGetIdealSize(PBUTN pbutn, PSIZE psize) { UINT bsWnd; RECT rc = {0}; HBRUSH hBrush; HDC hdc; if (psize == NULL) return FALSE; GetWindowRect(pbutn->ci.hwnd, &rc); hdc = GetDC (pbutn->ci.hwnd); if (hdc) { ULONG ulStyle = GET_STYLE(pbutn); bsWnd = GetButtonType(ulStyle); hBrush = Button_InitDC(pbutn, hdc); switch (bsWnd) { case BS_PUSHBUTTON: case BS_DEFPUSHBUTTON: { LPWSTR pszText = NULL; INT cchText = GetWindowTextLength(pbutn->ci.hwnd); if (cchText > 0) { pszText = UserLocalAlloc(0, (cchText+1)*SIZEOF(WCHAR)); if (pszText) { RECT rcText = {0}; RECT rcIcon = {0}; int cx = 0, cy = 0; int iStateId = 0; int iPartId = 0; GetWindowText(pbutn->ci.hwnd, pszText, cchText+1); if (Button_IsThemed(pbutn)) { Button_GetThemeIds(pbutn, &iPartId, &iStateId); // First: Get the text rect GetThemeTextExtent(pbutn->hTheme, hdc, iPartId, iStateId, pszText, cchText, 0, &rcText, &rcText); ApplyMarginsToRect(&pbutn->rcText, &rcText); rc = rcText; // We should now have The button with text. } else { int cxWidth = 2 * GetSystemMetrics(SM_CXEDGE); int cyWidth = 3 * GetSystemMetrics(SM_CYEDGE); if (IsPushButton(pbutn) & PBF_DEFAULT) { cxWidth += 2 * GetSystemMetrics(SM_CXBORDER); cyWidth += 2 * GetSystemMetrics(SM_CXBORDER); } DrawText(hdc, pszText, cchText, &rcText, DT_CALCRECT); ApplyMarginsToRect(&pbutn->rcText, &rcText); rcText.bottom += cyWidth + 1; // +1 because draw text adds a single pixel to the first char, but not the last... rcText.right += cxWidth + 1; } if (pbutn->himl) { rc.top = rc.left = 0; // We turn this into a width not a position CCGetIconSize(&pbutn->ci, pbutn->himl, &cx, &cy); rcIcon.bottom = cy; rcIcon.right = cx; ApplyMarginsToRect(&pbutn->rcIcon, &rcIcon); switch (pbutn->uAlign) { case BUTTON_IMAGELIST_ALIGN_TOP: case BUTTON_IMAGELIST_ALIGN_BOTTOM: rc.bottom = RECTHEIGHT(rcIcon) + RECTHEIGHT(rcText); rc.right = max(RECTWIDTH(rcIcon), RECTWIDTH(rcText)); break; case BUTTON_IMAGELIST_ALIGN_CENTER: // This means no text rc.bottom = RECTHEIGHT(rcIcon); rc.right = RECTWIDTH(rcIcon); break; case BUTTON_IMAGELIST_ALIGN_RIGHT: case BUTTON_IMAGELIST_ALIGN_LEFT: // Fall default: rc.right = RECTWIDTH(rcIcon) + RECTWIDTH(rcText); rc.bottom = max(RECTHEIGHT(rcIcon), RECTHEIGHT(rcText)); break; } } else { rc = rcText; } if (Button_IsThemed(pbutn)) { GetThemeBackgroundExtent(pbutn->hTheme, hdc, iPartId, iStateId, &rc, &rc); } UserLocalFree(pszText); } } break; } } // // Release the font which may have been loaded by ButtonInitDC. // if (pbutn->hFont) { SelectObject(hdc, GetStockObject(SYSTEM_FONT)); } ReleaseDC(pbutn->ci.hwnd, hdc); } psize->cx = RECTWIDTH(rc); psize->cy = RECTHEIGHT(rc); return TRUE; } //---------------------------------------------------------------------------// // VOID Button_Paint(PBUTN pbutn, HDC hdc) { RECT rc; RECT rcText; HBRUSH hBrush; HBRUSH hBrushSave = NULL; BOOL fDrawBackground = TRUE; ULONG ulStyle = GET_STYLE(pbutn); CCDBUFFER db = {0}; UINT bsWnd = GetButtonType(ulStyle); UINT pbfPush = IsPushButton(pbutn); BOOL fTransparent = FALSE; int iPartId = 0; int iStateId = 0; GetClientRect(pbutn->ci.hwnd, &rc); if (Button_IsThemed(pbutn) && (bsWnd != LOBYTE(BS_GROUPBOX)) && (bsWnd != LOBYTE(BS_OWNERDRAW)) && !pbutn->fPaintKbdCuesOnly) { hdc = CCBeginDoubleBuffer(hdc, &rc, &db); Button_GetThemeIds(pbutn, &iPartId, &iStateId); fTransparent = CCShouldAskForBits(&pbutn->ci, pbutn->hTheme, iPartId, iStateId); if (fTransparent) { fDrawBackground = (TRUE != CCSendPrint(&pbutn->ci, hdc)); } } hBrush = Button_InitDC(pbutn, hdc); if ((!pbfPush || fTransparent) && !pbutn->fPaintKbdCuesOnly && fDrawBackground) { if ((bsWnd != LOBYTE(BS_OWNERDRAW)) && (bsWnd != LOBYTE(BS_GROUPBOX))) { // // Fill the client area with the background brush // before we begin painting. // FillRect(hdc, &rc, hBrush); } hBrushSave = SelectObject(hdc, hBrush); } switch (bsWnd) { case BS_CHECKBOX: case BS_RADIOBUTTON: case BS_AUTORADIOBUTTON: case BS_3STATE: case BS_AUTOCHECKBOX: case BS_AUTO3STATE: if (!pbfPush) { if (!Button_IsThemed(pbutn)) { Button_DrawText(pbutn, hdc, DBT_TEXT | (BUTTONSTATE(pbutn) & BST_FOCUS ? DBT_FOCUS : 0), FALSE); } if (!pbutn->fPaintKbdCuesOnly || Button_IsThemed(pbutn)) { Button_DrawCheck(pbutn, hdc, hBrush); } break; } // // Fall through for PUSHLIKE buttons // case BS_PUSHBUTTON: case BS_DEFPUSHBUTTON: Button_DrawPush(pbutn, hdc, pbfPush); break; case BS_PUSHBOX: Button_DrawText(pbutn, hdc, DBT_TEXT | (BUTTONSTATE(pbutn) & BST_FOCUS ? DBT_FOCUS : 0), FALSE); Button_DrawNewState(pbutn, hdc, hBrush, 0); break; case BS_USERBUTTON: // Don't support USERBUTTON in v6. This has been superceded by OWNERDRAW in win32. break; case BS_OWNERDRAW: Button_OwnerDraw(pbutn, hdc, ODA_DRAWENTIRE); break; case BS_GROUPBOX: Button_CalcRect(pbutn, hdc, &rcText, CBR_GROUPTEXT, 0); //----- get theme part, state for groupbox ---- if (Button_IsThemed(pbutn)) { Button_GetThemeIds(pbutn, &iPartId, &iStateId); } if (!pbutn->fPaintKbdCuesOnly) { UINT uFlags; BOOL fFillMyself = TRUE; Button_CalcRect(pbutn, hdc, &rc, CBR_GROUPFRAME, 0); uFlags = ((ulStyle & BS_FLAT) != 0) ? BF_FLAT | BF_MONO : 0; if (!Button_IsThemed(pbutn)) { DrawEdge(hdc, &rc, EDGE_ETCHED, BF_RECT | uFlags); } else { DrawThemeBackground(pbutn->hTheme, hdc, iPartId, iStateId, &rc, 0); fFillMyself = (FALSE == CCSendPrintRect(&pbutn->ci, hdc, &rcText)); } if (fFillMyself) { FillRect(hdc, &rcText, hBrush); } } // FillRect(hdc, &rc, hBrush); if (!Button_IsThemed(pbutn)) { Button_DrawText(pbutn, hdc, DBT_TEXT, FALSE); } else { LPWSTR pszText = NULL; INT cchText = GetWindowTextLength(pbutn->ci.hwnd); if (cchText > 0) { pszText = UserLocalAlloc(0, (cchText+1)*SIZEOF(WCHAR)); if (pszText) { DWORD dwTextFlags = Button_GetTextFlags(pbutn); GetWindowTextW(pbutn->ci.hwnd, pszText, cchText+1); // // Button_CalcRect padded by a CXEDGE so the groupbox frame wouldn't // be flush with the Group text // rcText.left += GetSystemMetrics(SM_CXEDGE); if (FAILED(DrawThemeText(pbutn->hTheme, hdc, iPartId, iStateId, pszText, cchText, dwTextFlags, 0, &rcText))) { TraceMsg(TF_STANDARD, "Button_Paint failed to render groupbox text"); } UserLocalFree(pszText); } } } break; } if (!pbfPush && hBrushSave) { SelectObject(hdc, hBrushSave); } // // Release the font which may have been loaded by ButtonInitDC. // if (pbutn->hFont) { SelectObject(hdc, GetStockObject(SYSTEM_FONT)); } CCEndDoubleBuffer(&db); } //---------------------------------------------------------------------------// // VOID Button_Repaint(PBUTN pbutn) { HDC hdc = Button_GetDC(pbutn, NULL); if (hdc != NULL) { Button_Paint(pbutn, hdc); Button_ReleaseDC(pbutn, hdc, NULL); } } VOID Button_SetHot(PBUTN pbutn, BOOL fHot, DWORD dwReason) { NMBCHOTITEM nmhot = {0}; // Send a notification about the hot item change if (fHot) { nmhot.dwFlags = HICF_ENTERING; pbutn->buttonState |= BST_HOT; } else { nmhot.dwFlags = HICF_LEAVING; pbutn->buttonState &= ~BST_HOT; } nmhot.dwFlags |= dwReason; CCSendNotify(&pbutn->ci, BCN_HOTITEMCHANGE, &nmhot.hdr); } void Button_EraseOwnerDraw(PBUTN pbutn, HDC hdc) { if (GetButtonType(GET_STYLE(pbutn)) == LOBYTE(BS_OWNERDRAW)) { RECT rc; HBRUSH hbr; // // Handle erase background for owner draw buttons. // GetClientRect(pbutn->ci.hwnd, &rc); hbr = (HBRUSH)SendMessage(GetParent(pbutn->ci.hwnd), WM_CTLCOLORBTN, (WPARAM)hdc, (LPARAM)pbutn->ci.hwnd); FillRect(hdc, &rc, hbr); } } //---------------------------------------------------------------------------// // // Button_WndProc // // WndProc for buttons, check boxes, etc. // LRESULT APIENTRY Button_WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { UINT wOldState; RECT rc; HDC hdc; HBRUSH hbr; PAINTSTRUCT ps; PBUTN pbutn; LRESULT lResult = FALSE; // // Get the instance data for this button control // pbutn = Button_GetPtr(hwnd); if (!pbutn && uMsg != WM_NCCREATE) { goto CallDWP; } switch (uMsg) { case WM_NCHITTEST: if (GetButtonType(GET_STYLE(pbutn)) == LOBYTE(BS_GROUPBOX)) { lResult = (LONG)HTTRANSPARENT; } else { lResult = DefWindowProc(hwnd, uMsg, wParam, lParam); if ( lResult == HTCLIENT && Button_IsThemed(pbutn)) { HRESULT hr; int iPartId = 0; int iStateId = 0; POINT pt; WORD wHitTestCode; hr = Button_GetThemeIds(pbutn, &iPartId, &iStateId); if ( SUCCEEDED(hr) ) GetWindowRect(pbutn->ci.hwnd, &rc); pt.x = GET_X_LPARAM(lParam); pt.y = GET_Y_LPARAM(lParam); hr = HitTestThemeBackground(pbutn->hTheme, NULL, iPartId, iStateId, 0, &rc, NULL, pt, &wHitTestCode); if ( SUCCEEDED(hr) && wHitTestCode == HTTRANSPARENT) { lResult = (LRESULT)HTTRANSPARENT; } } } break; case WM_ERASEBKGND: Button_EraseOwnerDraw(pbutn, (HDC)wParam); // // Do nothing for other buttons, but don't let DefWndProc() do it // either. It will be erased in Button_Paint(). // lResult = (LONG)TRUE; break; case WM_PRINTCLIENT: Button_EraseOwnerDraw(pbutn, (HDC)wParam); Button_Paint(pbutn, (HDC)wParam); break; case WM_CREATE: pbutn->hTheme = Button_GetTheme(pbutn); CIInitialize(&pbutn->ci, hwnd, (LPCREATESTRUCT)lParam); SendMessage(hwnd, WM_CHANGEUISTATE, MAKEWPARAM(UIS_INITIALIZE, 0), 0); break; case WM_PAINT: { // // If wParam != NULL, then this is a subclassed paint. // if (wParam) { hdc = (HDC)wParam; } else { hdc = BeginPaint(hwnd, &ps); } if (IsWindowVisible(pbutn->ci.hwnd)) { Button_Paint(pbutn, hdc); } if (!wParam) { EndPaint(hwnd, &ps); } } break; case WM_SETFOCUS: BUTTONSTATE(pbutn) |= BST_FOCUS; if (GetButtonType(GET_STYLE(pbutn)) == LOBYTE(BS_OWNERDRAW)) { HDC hdc = Button_GetDC(pbutn, NULL); if (hdc) { Button_DrawText(pbutn, hdc, DBT_FOCUS, FALSE); Button_ReleaseDC(pbutn, hdc, NULL); } } else { InvalidateRect(pbutn->ci.hwnd, NULL, FALSE); } if ((GET_STYLE(pbutn)& BS_NOTIFY) != 0) { Button_NotifyParent(pbutn, BN_SETFOCUS); } if (!(BUTTONSTATE(pbutn) & BST_INCLICK)) { switch (GetButtonType(GET_STYLE(pbutn))) { case LOBYTE(BS_RADIOBUTTON): case LOBYTE(BS_AUTORADIOBUTTON): if (!(BUTTONSTATE(pbutn) & BST_DONTCLICK)) { if (!(BUTTONSTATE(pbutn) & BST_CHECKMASK)) { Button_NotifyParent(pbutn, BN_CLICKED); } } break; } } break; case WM_GETDLGCODE: lResult = DLGC_BUTTON; switch (GetButtonType(GET_STYLE(pbutn))) { case LOBYTE(BS_DEFPUSHBUTTON): lResult |= DLGC_DEFPUSHBUTTON; break; case LOBYTE(BS_PUSHBUTTON): case LOBYTE(BS_PUSHBOX): lResult |= DLGC_UNDEFPUSHBUTTON; break; case LOBYTE(BS_AUTORADIOBUTTON): case LOBYTE(BS_RADIOBUTTON): lResult |= DLGC_RADIOBUTTON; break; case LOBYTE(BS_GROUPBOX): // // remove DLGC_BUTTON // lResult = DLGC_STATIC; break; case LOBYTE(BS_CHECKBOX): case LOBYTE(BS_AUTOCHECKBOX): // // If this is a char that is a '=/+', or '-', we want it // if (lParam && ((LPMSG)lParam)->message == WM_CHAR) { switch (wParam) { case TEXT('='): case TEXT('+'): case TEXT('-'): lResult |= DLGC_WANTCHARS; break; } } break; } break; case WM_CAPTURECHANGED: if (BUTTONSTATE(pbutn) & BST_CAPTURED) { // // Unwittingly, we've been kicked out of capture, // so undepress etc. // if (BUTTONSTATE(pbutn) & BST_MOUSE) { SendMessage(pbutn->ci.hwnd, BM_SETSTATE, FALSE, 0); } BUTTONSTATE(pbutn) &= ~(BST_CAPTURED | BST_MOUSE); } break; case WM_KILLFOCUS: // // If we are losing the focus and we are in "capture mode", click // the button. This allows tab and space keys to overlap for // fast toggle of a series of buttons. // if (BUTTONSTATE(pbutn) & BST_MOUSE) { // // If for some reason we are killing the focus, and we have the // mouse captured, don't notify the parent we got clicked. This // breaks Omnis Quartz otherwise. // SendMessage(pbutn->ci.hwnd, BM_SETSTATE, FALSE, 0); } Button_ReleaseCapture(pbutn, TRUE); BUTTONSTATE(pbutn) &= ~BST_FOCUS; if ((GET_STYLE(pbutn) & BS_NOTIFY) != 0) { Button_NotifyParent(pbutn, BN_KILLFOCUS); } // // Since the bold border around the defpushbutton is done by // someone else, we need to invalidate the rect so that the // focus rect is repainted properly. // InvalidateRect(hwnd, NULL, FALSE); break; case WM_LBUTTONDBLCLK: // // Double click messages are recognized for BS_RADIOBUTTON, // BS_USERBUTTON, and BS_OWNERDRAW styles. For all other buttons, // double click is handled like a normal button down. // switch (GetButtonType(GET_STYLE(pbutn))) { default: if ((GET_STYLE(pbutn) & BS_NOTIFY) == 0) goto btnclick; case LOBYTE(BS_USERBUTTON): case LOBYTE(BS_RADIOBUTTON): case LOBYTE(BS_OWNERDRAW): Button_NotifyParent(pbutn, BN_DOUBLECLICKED); break; } break; case WM_LBUTTONUP: if (BUTTONSTATE(pbutn) & BST_MOUSE) { Button_ReleaseCapture(pbutn, TRUE); } break; case WM_MOUSELEAVE: { // // We should only be requesting mouseleave messages // if we are themed but check anyway // if (pbutn->buttonState & BST_HOT) { Button_SetHot(pbutn, FALSE, HICF_MOUSE); InvalidateRect(pbutn->ci.hwnd, NULL, TRUE); } } break; case WM_MOUSEMOVE: { // // If the hot bit is not already set // // 300925: Can't hottrack ownerdraw buttons for app compat reasons // if (!TESTFLAG(pbutn->buttonState, BST_HOT) && GetButtonType(GET_STYLE(pbutn)) != LOBYTE(BS_OWNERDRAW)) { TRACKMOUSEEVENT tme; // // Set the hot bit and request that // we be notified when the mouse leaves // Button_SetHot(pbutn, TRUE, HICF_MOUSE); tme.cbSize = sizeof(tme); tme.dwFlags = TME_LEAVE; tme.hwndTrack = pbutn->ci.hwnd; tme.dwHoverTime = 0; TrackMouseEvent(&tme); InvalidateRect(pbutn->ci.hwnd, NULL, TRUE); } if (!(BUTTONSTATE(pbutn) & BST_MOUSE)) { break; } } // // FALL THRU // case WM_LBUTTONDOWN: btnclick: if (Button_SetCapture(pbutn, BST_MOUSE)) { POINT pt; GetClientRect(pbutn->ci.hwnd, &rc); POINTSTOPOINT(pt, lParam); SendMessage(pbutn->ci.hwnd, BM_SETSTATE, PtInRect(&rc, pt), 0); } break; case WM_CHAR: if (BUTTONSTATE(pbutn) & BST_MOUSE) { goto CallDWP; } if (GetButtonType(GET_STYLE(pbutn)) != LOBYTE(BS_CHECKBOX) && GetButtonType(GET_STYLE(pbutn)) != LOBYTE(BS_AUTOCHECKBOX)) { goto CallDWP; } switch (wParam) { case TEXT('+'): case TEXT('='): // // we must Set the check mark on. // wParam = 1; goto SetCheck; case TEXT('-'): // // Set the check mark off. // wParam = 0; SetCheck: // // Must notify only if the check status changes // if ((WORD)(BUTTONSTATE(pbutn) & BST_CHECKMASK) != (WORD)wParam) { // // We must check/uncheck only if it is AUTO // if (GetButtonType(GET_STYLE(pbutn)) == LOBYTE(BS_AUTOCHECKBOX)) { if (Button_SetCapture(pbutn, 0)) { SendMessage(pbutn->ci.hwnd, BM_SETCHECK, wParam, 0); Button_ReleaseCapture(pbutn, TRUE); } } Button_NotifyParent(pbutn, BN_CLICKED); } break; default: goto CallDWP; } break; case BCM_GETIDEALSIZE: return Button_OnGetIdealSize(pbutn, (PSIZE)lParam); case BCM_SETIMAGELIST: return Button_OnSetImageList(pbutn, (BUTTON_IMAGELIST*)lParam); case BCM_GETIMAGELIST: { BUTTON_IMAGELIST* biml = (BUTTON_IMAGELIST*)lParam; if (biml) { biml->himl = pbutn->himl; biml->uAlign = pbutn->uAlign; biml->margin = pbutn->rcIcon; return TRUE; } } break; case BCM_SETTEXTMARGIN: { RECT* prc = (RECT*)lParam; if (prc) { pbutn->rcText = *prc; return TRUE; } } break; case BCM_GETTEXTMARGIN: { RECT* prc = (RECT*)lParam; if (prc) { *prc = pbutn->rcText; return TRUE; } } break; case BM_CLICK: // // Don't recurse into this code! // if (BUTTONSTATE(pbutn) & BST_INBMCLICK) { break; } BUTTONSTATE(pbutn) |= BST_INBMCLICK; SendMessage(pbutn->ci.hwnd, WM_LBUTTONDOWN, 0, 0); SendMessage(pbutn->ci.hwnd, WM_LBUTTONUP, 0, 0); BUTTONSTATE(pbutn) &= ~BST_INBMCLICK; // // FALL THRU // case WM_KEYDOWN: if (BUTTONSTATE(pbutn) & BST_MOUSE) { break; } if (wParam == VK_SPACE) { if (Button_SetCapture(pbutn, 0)) { SendMessage(pbutn->ci.hwnd, BM_SETSTATE, TRUE, 0); } } else { Button_ReleaseCapture(pbutn, FALSE); } break; case WM_KEYUP: case WM_SYSKEYUP: if (BUTTONSTATE(pbutn) & BST_MOUSE) { goto CallDWP; } // // Don't cancel the capture mode on the up of the tab in case the // guy is overlapping tab and space keys. // if (wParam == VK_TAB) { goto CallDWP; } // // WARNING: pbutn->ci.hwnd is history after this call! // Button_ReleaseCapture(pbutn, (wParam == VK_SPACE)); if (uMsg == WM_SYSKEYUP) { goto CallDWP; } break; case BM_GETSTATE: lResult = (LONG)BUTTONSTATE(pbutn); break; case BM_SETSTATE: wOldState = (UINT)(BUTTONSTATE(pbutn) & BST_PUSHED); if (wParam) { BUTTONSTATE(pbutn) |= BST_PUSHED; } else { BUTTONSTATE(pbutn) &= ~BST_PUSHED; } if (GetButtonType(GET_STYLE(pbutn)) == LOBYTE(BS_USERBUTTON)) { Button_NotifyParent(pbutn, (UINT)(wParam ? BN_PUSHED : BN_UNPUSHED)); } if (wOldState != (BOOL)(BUTTONSTATE(pbutn) & BST_PUSHED)) { // Only invalidate if the state changed. InvalidateRect(pbutn->ci.hwnd, NULL, FALSE); NotifyWinEvent(EVENT_OBJECT_STATECHANGE, hwnd, OBJID_CLIENT, INDEXID_CONTAINER); } break; case BM_GETCHECK: lResult = (LONG)(BUTTONSTATE(pbutn) & BST_CHECKMASK); break; case BM_SETCHECK: switch (GetButtonType(GET_STYLE(pbutn))) { case LOBYTE(BS_RADIOBUTTON): case LOBYTE(BS_AUTORADIOBUTTON): if (wParam) { SetWindowState(pbutn->ci.hwnd, WS_TABSTOP); } else { ClearWindowState(pbutn->ci.hwnd, WS_TABSTOP); } // // FALL THRU // case LOBYTE(BS_CHECKBOX): case LOBYTE(BS_AUTOCHECKBOX): if (wParam) { wParam = 1; } goto CheckIt; case LOBYTE(BS_3STATE): case LOBYTE(BS_AUTO3STATE): if (wParam > BST_INDETERMINATE) { wParam = BST_INDETERMINATE; } CheckIt: if ((UINT)(BUTTONSTATE(pbutn) & BST_CHECKMASK) != (UINT)wParam) { BUTTONSTATE(pbutn) &= ~BST_CHECKMASK; BUTTONSTATE(pbutn) |= (UINT)wParam; if (!IsWindowVisible(pbutn->ci.hwnd)) { break; } InvalidateRect(pbutn->ci.hwnd, NULL, FALSE); NotifyWinEvent(EVENT_OBJECT_STATECHANGE, hwnd, OBJID_CLIENT, INDEXID_CONTAINER); } break; } break; case BM_SETSTYLE: AlterWindowStyle(hwnd, BS_TYPEMASK, (DWORD)wParam); if (lParam) { InvalidateRect(hwnd, NULL, TRUE); } NotifyWinEvent(EVENT_OBJECT_STATECHANGE, hwnd, OBJID_CLIENT, INDEXID_CONTAINER); break; case WM_SETTEXT: // // In case the new group name is longer than the old name, // this paints over the old name before repainting the group // box with the new name. // if (GetButtonType(GET_STYLE(pbutn)) == LOBYTE(BS_GROUPBOX)) { hdc = Button_GetDC(pbutn, &hbr); if (hdc != NULL) { Button_CalcRect(pbutn, hdc, &rc, CBR_GROUPTEXT, 0); InvalidateRect(hwnd, &rc, TRUE); FillRect(hdc, &rc, hbr); Button_ReleaseDC(pbutn, hdc, &hbr); } } lResult = DefWindowProc(hwnd, uMsg, wParam, lParam); NotifyWinEvent(EVENT_OBJECT_NAMECHANGE, hwnd, OBJID_WINDOW, INDEXID_CONTAINER); goto DoEnable; case WM_ENABLE: lResult = 0L; DoEnable: Button_Repaint(pbutn); break; case WM_SETFONT: // // wParam - handle to the font // lParam - if true, redraw else don't // Button_SetFont(pbutn, (HFONT)wParam, (BOOL)(lParam != 0)); break; case WM_GETFONT: lResult = (LRESULT)pbutn->hFont; break; case BM_GETIMAGE: case BM_SETIMAGE: if (!IsValidImage(wParam, (GET_STYLE(pbutn) & BS_IMAGEMASK) != 0, IMAGE_BMMAX)) { TraceMsg(TF_STANDARD, "UxButton: Invalid button image type"); SetLastError(ERROR_INVALID_PARAMETER); } else { HANDLE hOld = pbutn->hImage; if (uMsg == BM_SETIMAGE) { pbutn->hImage = (HANDLE)lParam; if (IsWindowVisible(pbutn->ci.hwnd)) { InvalidateRect(hwnd, NULL, TRUE); } } lResult = (LRESULT)hOld; } break; case WM_NCDESTROY: if (pbutn->hTheme) { CloseThemeData(pbutn->hTheme); } UserLocalFree(pbutn); TraceMsg(TF_STANDARD, "BUTTON: Clearing button instance pointer."); Button_SetPtr(hwnd, NULL); break; case WM_NCCREATE: pbutn = (PBUTN)UserLocalAlloc(HEAP_ZERO_MEMORY, sizeof(BUTN)); if (pbutn) { // // Success... store the instance pointer. // TraceMsg(TF_STANDARD, "BUTTON: Setting button instance pointer."); Button_SetPtr(hwnd, pbutn); pbutn->ci.hwnd = hwnd; pbutn->pww = (PWW)GetWindowLongPtr(hwnd, GWLP_WOWWORDS); SetRect(&pbutn->rcText, GetSystemMetrics(SM_CXEDGE) / 2, GetSystemMetrics(SM_CYEDGE) / 2, GetSystemMetrics(SM_CXEDGE) / 2, GetSystemMetrics(SM_CYEDGE) / 2); // // Borland's OBEX has a button with style 0x98; We didn't strip // these bits in win3.1 because we checked for 0x08. // Stripping these bits cause a GP Fault in OBEX. // For win3.1 guys, I use the old code to strip the style bits. // if (TESTFLAG(GET_STATE2(pbutn), WS_S2_WIN31COMPAT)) { if ((!TESTFLAG(GET_STATE2(pbutn), WS_S2_WIN40COMPAT) && (((LOBYTE(GET_STYLE(pbutn))) & (LOBYTE(~BS_LEFTTEXT))) == LOBYTE(BS_USERBUTTON))) || (TESTFLAG(GET_STATE2(pbutn), WS_S2_WIN40COMPAT) && (GetButtonType(GET_STYLE(pbutn)) == LOBYTE(BS_USERBUTTON)))) { // // BS_USERBUTTON is no longer allowed for 3.1 and beyond. // Just turn to normal push button. // AlterWindowStyle(hwnd, BS_TYPEMASK, 0); TraceMsg(TF_STANDARD, "UxButton: BS_USERBUTTON no longer supported"); } } if ((GET_EXSTYLE(pbutn) & WS_EX_RIGHT) != 0) { AlterWindowStyle(hwnd, BS_RIGHT | BS_RIGHTBUTTON, BS_RIGHT | BS_RIGHTBUTTON); } goto CallDWP; } else { // // Failed... return FALSE. // // From a WM_NCCREATE msg, this will cause the // CreateWindow call to fail. // TraceMsg(TF_STANDARD, "BUTTON: Unable to allocate button instance structure."); lResult = FALSE; } break; case WM_UPDATEUISTATE: DefWindowProc(hwnd, uMsg, wParam, lParam); if (ISBSTEXTOROD(GET_STYLE(pbutn))) { pbutn->fPaintKbdCuesOnly = !IsUsingCleartype(); Button_Repaint(pbutn); pbutn->fPaintKbdCuesOnly = FALSE; } break; case WM_GETOBJECT: if(lParam == OBJID_QUERYCLASSNAMEIDX) { lResult = MSAA_CLASSNAMEIDX_BUTTON; } else { lResult = FALSE; } break; case WM_THEMECHANGED: if ( pbutn->hTheme ) { CloseThemeData(pbutn->hTheme); } //---- reset cached sizes that may change with a theme change ---- sizeCheckBox.cx = 0; sizeCheckBox.cy = 0; sizeRadioBox.cx = 0; sizeRadioBox.cy = 0; pbutn->hTheme = Button_GetTheme(pbutn); InvalidateRect(pbutn->ci.hwnd, NULL, TRUE); CCSendNotify(&pbutn->ci, NM_THEMECHANGED, NULL); lResult = TRUE; break; default: if (CCWndProc(&pbutn->ci, uMsg, wParam, lParam, &lResult)) return lResult; CallDWP: lResult = DefWindowProc(hwnd, uMsg, wParam, lParam); } return lResult; }