Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

3474 lines
92 KiB

#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 lpsz; // Text 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;
if (!RegisterClass(&wc) && !GetClassInfo(hInstance, WC_BUTTON, &wc))
return FALSE;
return TRUE;
}
//---------------------------------------------------------------------------//
//
// 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 cch;
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
//
cch = GetWindowTextLength(pbutn->ci.hwnd);
if (cch == 0)
{
//
// Nothing to draw
//
return hr;
}
pszText = UserLocalAlloc(0, sizeof(WCHAR)*(cch+1));
if (pszText == NULL)
{
TraceMsg(TF_STANDARD, "Can't allocate buffer");
return E_FAIL;
}
GetWindowTextW(pbutn->ci.hwnd, pszText, cch+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, cch, &rcTextExtent, dwTextFlags|DT_CALCRECT, NULL);
cxWidth = RECTWIDTH(rcTextExtent);
}
else
{
SIZE size;
LPWSTR pszStrip = UserLocalAlloc(0, sizeof(WCHAR)*(cch+1));
if ( pszStrip )
{
int cchStrip = StripAccelerators(pszText, pszStrip, TRUE);
GetTextExtentPoint32(hdc, pszStrip, cchStrip, &size);
UserLocalFree(pszStrip);
}
else
{
GetTextExtentPoint32(hdc, pszText, cch, &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, cch, 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;
LPTSTR lpName = NULL;
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:
{
int cch = GetWindowTextLength(pbutn->ci.hwnd);
if (cch != 0)
{
//
// Always use WCHAR for alloc because of MBCS
//
lpName = _alloca((cch+1)*sizeof(WCHAR));
}
if (cch == 0 || lpName == NULL || GetWindowText(pbutn->ci.hwnd, lpName, cch+1) == 0)
{
SetRectEmpty(lprc);
break;
}
//
// if not themed
//
if (!Button_IsThemed(pbutn))
{
GetTextExtentPoint32(hdc, lpName, cch, &sizeExtent);
}
else
{
DWORD dwTextFlags = Button_GetTextFlags(pbutn);
RECT rcExtent;
GetThemeTextExtent(pbutn->hTheme,
hdc,
BP_GROUPBOX,
0,
lpName,
cch,
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);
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 lpsz, int cch, 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, lpsz, cch, &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, LPBTNDATA lpbd, int cch, int cx, int cy)
{
RECT rc;
UINT dtFlags = DT_WORDBREAK | DT_EDITCONTROL;
PBUTN pbutn = lpbd->pbutn;
if (TESTFLAG(GET_EXSTYLE(pbutn), WS_EXP_UIACCELHIDDEN))
{
dtFlags |= DT_HIDEPREFIX;
}
else if (pbutn->fPaintKbdCuesOnly)
{
dtFlags |= DT_PREFIXONLY;
}
rc.left = 0;
rc.top = 0;
rc.right = cx;
rc.bottom = cy;
//
// Horizontal alignment
//
UserAssert(DT_LEFT == 0);
switch (lpbd->wFlags & LOWORD(BS_HORZMASK))
{
case LOWORD(BS_CENTER):
dtFlags |= DT_CENTER;
break;
case LOWORD(BS_RIGHT):
dtFlags |= DT_RIGHT;
break;
}
//
// Vertical alignment
//
UserAssert(DT_TOP == 0);
switch (lpbd->wFlags & LOWORD(BS_VERTMASK))
{
case LOWORD(BS_VCENTER):
dtFlags |= DT_VCENTER;
break;
case LOWORD(BS_BOTTOM):
dtFlags |= DT_BOTTOM;
break;
}
DrawTextEx(hdc, lpbd->lpsz, cch, &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, BOOL dbt, BOOL fDepress)
{
RECT rc;
HBRUSH hbr;
LPTSTR lpName;
int x = 0, y = 0;
int cx = 0, cy = 0;
BYTE bStyle;
UINT dsFlags;
BTNDATA bdt;
UINT pbfPush;
ULONG ulStyle = GET_STYLE(pbutn);
int cch;
bStyle = GetButtonStyle(ulStyle);
if ((bStyle == LOBYTE(BS_GROUPBOX)) && (dbt == DBT_FOCUS))
{
return;
}
pbfPush = IsPushButton(pbutn);
cch = GetWindowTextLength(pbutn->ci.hwnd);
lpName = _alloca((cch + 1) * sizeof(wchar_t));
if (lpName == NULL)
{
return;
}
GetWindowText(pbutn->ci.hwnd, lpName, cch + 1);
//
// if not themed
//
if (!Button_IsThemed(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);
//
// Skip stuff for ownerdraw buttons, since we aren't going to
// draw text/image.
//
if (bStyle == LOBYTE(BS_OWNERDRAW))
{
goto DrawFocus;
}
else
{
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));
}
//
// Alignment
//
bdt.wFlags = GetAlignment(pbutn);
bdt.pbutn = pbutn;
//
// Bail if we have nothing to draw
//
if ((ulStyle & BS_BITMAP) != 0)
{
BITMAP bmp;
//
// Bitmap button
//
if (!pbutn->hImage)
{
return;
}
GetObject(pbutn->hImage, sizeof(BITMAP), &bmp);
cx = bmp.bmWidth;
cy = bmp.bmHeight;
dsFlags = DST_BITMAP;
goto UseImageForName;
}
else if ((ulStyle & BS_ICON) != 0)
{
SIZE sizeIcon;
//
// Icon button
//
if (!pbutn->hImage)
{
return;
}
GetIconSize(pbutn->hImage, &sizeIcon);
cx = sizeIcon.cx;
cy = sizeIcon.cy;
dsFlags = DST_ICON;
UseImageForName:
lpName = (LPTSTR)pbutn->hImage;
cch = TRUE;
}
else
{
//
// Text button
//
if (cch == 0)
{
return;
}
if ((ulStyle & BS_MULTILINE) != 0)
{
bdt.lpsz = lpName;
Button_MultiExtent(bdt.wFlags, hdc, &rc, lpName, cch, &cx, &cy);
lpName = (LPTSTR)(LPBTNDATA)&bdt;
dsFlags = DST_COMPLEX;
}
else
{
SIZE size;
LPWSTR lpwstr = UserLocalAlloc(0, sizeof(WCHAR)*(cch+1));
//
// Try to get the text extent with mnemonics stripped.
//
if (lpwstr != NULL)
{
int iLen = StripAccelerators(lpName, lpwstr, TRUE);
GetTextExtentPoint32(hdc, lpwstr, iLen, &size);
UserLocalFree(lpwstr);
}
else
{
GetTextExtentPoint32(hdc, lpName, cch, &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;
}
}
//
// 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 (bdt.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 (bdt.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 (dbt & 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, (LPARAM)lpName,
(WPARAM)cch, 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.
DrawFocus:
if (dbt & 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,
lpName,
-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);
}
}
}
}
//---------------------------------------------------------------------------//
//
// 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:
{
PWSTR pName;
int cch = GetWindowTextLength(pbutn->ci.hwnd);
pName = _alloca((cch + 1) * sizeof(wchar_t));
if (pName)
{
RECT rcText={0};
RECT rcIcon={0};
int cx = 0, cy = 0;
int iStateId = 0;
int iPartId = 0;
GetWindowText(pbutn->ci.hwnd, pName, cch + 1);
if (Button_IsThemed(pbutn))
{
Button_GetThemeIds(pbutn, &iPartId, &iStateId);
// First: Get the text rect
GetThemeTextExtent(pbutn->hTheme, hdc, iPartId, iStateId, pName, cch, 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, pName, cch, &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);
}
}
}
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
{
DWORD dwTextFlags;
HRESULT hr;
int cch;
LPWSTR lpName;
cch = GetWindowTextLength(pbutn->ci.hwnd);
lpName = UserLocalAlloc(0, sizeof(WCHAR)*(cch+1));
if ( !lpName )
{
TraceMsg(TF_STANDARD, "Button_Paint couldn't allocate buffer");
return;
}
GetWindowTextW(pbutn->ci.hwnd, lpName, cch+1);
dwTextFlags = Button_GetTextFlags(pbutn);
//
// Button_CalcRect padded by a CXEDGE so the groupbox frame wouldn't
// be flush with the Group text
//
rcText.left += GetSystemMetrics(SM_CXEDGE);
hr = DrawThemeText(pbutn->hTheme,
hdc,
iPartId,
iStateId,
lpName,
cch,
dwTextFlags,
0,
&rcText);
if (FAILED(hr))
{
TraceMsg(TF_STANDARD, "Button_Paint failed to render groupbox text");
}
UserLocalFree(lpName);
}
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_INPUTLANGCHANGEREQUEST:
//
// #115190
// If the window is one of controls on top of dialogbox,
// let the parent dialog handle it.
//
#if 0 // Need to expose TestwndChild()
if (TestwndChild(pbutn->ci.hwnd) && pbutn->ci.hwnd->spbutn->ci.hwndParent)
{
PWND pbutn->ci.hwndParent = REBASEPWND(pbutn->ci.hwnd, spbutn->ci.hwndParent);
if (pbutn->ci.hwndParent)
{
PCLS pclsParent = REBASEALWAYS(pbutn->ci.hwndParent, pcls);
UserAssert(pclsParent != NULL);
if (pclsParent->atomClassName == gpsi->atomSysClass[ICLS_DIALOG])
{
TraceMsg(TF_STANDARD, "UxButton: WM_INPUTLANGCHANGEREQUEST is sent to parent.");
return SendMessage(pbutn->ci.hwndParent, uMsg, wParam, lParam);
}
}
}
#endif
goto CallDWP;
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;
}