2 * Copyright (C) 1993 Johannes Ruscheinski
3 * Copyright (C) 1993 David Metcalfe
4 * Copyright (C) 1994 Alexandre Julliard
5 * Copyright (C) 2008 by Reece H. Dunn
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
23 * - BS_NOTIFY: is it complete?
24 * - BS_RIGHTBUTTON: same as BS_LEFTTEXT
27 * - WM_CHAR: Checks a (manual or automatic) check box on '+' or '=', clears it on '-' key.
28 * - WM_SETFOCUS: For (manual or automatic) radio buttons, send the parent window BN_CLICKED
29 * - WM_NCCREATE: Turns any BS_OWNERDRAW button into a BS_PUSHBUTTON button.
35 * - BN_PUSHED/BN_HILITE
36 * + BN_KILLFOCUS: is it OK?
38 * + BN_SETFOCUS: is it OK?
39 * - BN_UNPUSHED/BN_UNHILITE
41 * Structures/Macros/Definitions
57 #include "wine/debug.h"
58 #include "wine/heap.h"
62 WINE_DEFAULT_DEBUG_CHANNEL(button
);
64 /* undocumented flags */
65 #define BUTTON_NSTATES 0x0F
66 #define BUTTON_BTNPRESSED 0x40
67 #define BUTTON_UNKNOWN2 0x20
68 #define BUTTON_UNKNOWN3 0x10
70 #define BUTTON_NOTIFY_PARENT(hWnd, code) \
71 do { /* Notify parent which has created this button control */ \
72 TRACE("notification " #code " sent to hwnd=%p\n", GetParent(hWnd)); \
73 SendMessageW(GetParent(hWnd), WM_COMMAND, \
74 MAKEWPARAM(GetWindowLongPtrW((hWnd),GWLP_ID), (code)), \
78 typedef struct _BUTTON_INFO
87 DWORD image_type
; /* IMAGE_BITMAP or IMAGE_ICON */
88 BUTTON_IMAGELIST imagelist
;
90 HIMAGELIST glyph
; /* this is a font character code when split_style doesn't have BCSS_IMAGE */
93 HANDLE image
; /* Original handle set with BM_SETIMAGE and returned with BM_GETIMAGE. */
98 HANDLE image
; /* Duplicated handle used for drawing. */
102 static UINT
BUTTON_CalcLayoutRects( const BUTTON_INFO
*infoPtr
, HDC hdc
, RECT
*labelRc
, RECT
*imageRc
, RECT
*textRc
);
103 static void PB_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
);
104 static void CB_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
);
105 static void GB_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
);
106 static void UB_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
);
107 static void OB_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
);
108 static void SB_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
);
109 static void CL_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
);
110 static void BUTTON_CheckAutoRadioButton( HWND hwnd
);
111 static void get_split_button_rects(const BUTTON_INFO
*, const RECT
*, RECT
*, RECT
*);
112 static BOOL
notify_split_button_dropdown(const BUTTON_INFO
*, const POINT
*, HWND
);
113 static void draw_split_button_dropdown_glyph(const BUTTON_INFO
*, HDC
, RECT
*);
115 #define MAX_BTN_TYPE 16
117 static const WORD maxCheckState
[MAX_BTN_TYPE
] =
119 BST_UNCHECKED
, /* BS_PUSHBUTTON */
120 BST_UNCHECKED
, /* BS_DEFPUSHBUTTON */
121 BST_CHECKED
, /* BS_CHECKBOX */
122 BST_CHECKED
, /* BS_AUTOCHECKBOX */
123 BST_CHECKED
, /* BS_RADIOBUTTON */
124 BST_INDETERMINATE
, /* BS_3STATE */
125 BST_INDETERMINATE
, /* BS_AUTO3STATE */
126 BST_UNCHECKED
, /* BS_GROUPBOX */
127 BST_UNCHECKED
, /* BS_USERBUTTON */
128 BST_CHECKED
, /* BS_AUTORADIOBUTTON */
129 BST_UNCHECKED
, /* BS_PUSHBOX */
130 BST_UNCHECKED
, /* BS_OWNERDRAW */
131 BST_UNCHECKED
, /* BS_SPLITBUTTON */
132 BST_UNCHECKED
, /* BS_DEFSPLITBUTTON */
133 BST_UNCHECKED
, /* BS_COMMANDLINK */
134 BST_UNCHECKED
/* BS_DEFCOMMANDLINK */
137 /* Generic draw states, use get_draw_state() to get specific state for button type */
148 typedef void (*pfPaint
)( const BUTTON_INFO
*infoPtr
, HDC hdc
, UINT action
);
150 static const pfPaint btnPaintFunc
[MAX_BTN_TYPE
] =
152 PB_Paint
, /* BS_PUSHBUTTON */
153 PB_Paint
, /* BS_DEFPUSHBUTTON */
154 CB_Paint
, /* BS_CHECKBOX */
155 CB_Paint
, /* BS_AUTOCHECKBOX */
156 CB_Paint
, /* BS_RADIOBUTTON */
157 CB_Paint
, /* BS_3STATE */
158 CB_Paint
, /* BS_AUTO3STATE */
159 GB_Paint
, /* BS_GROUPBOX */
160 UB_Paint
, /* BS_USERBUTTON */
161 CB_Paint
, /* BS_AUTORADIOBUTTON */
162 NULL
, /* BS_PUSHBOX */
163 OB_Paint
, /* BS_OWNERDRAW */
164 SB_Paint
, /* BS_SPLITBUTTON */
165 SB_Paint
, /* BS_DEFSPLITBUTTON */
166 CL_Paint
, /* BS_COMMANDLINK */
167 CL_Paint
/* BS_DEFCOMMANDLINK */
170 typedef void (*pfThemedPaint
)( HTHEME theme
, const BUTTON_INFO
*infoPtr
, HDC hdc
, int drawState
, UINT dtflags
, BOOL focused
);
172 static void PB_ThemedPaint( HTHEME theme
, const BUTTON_INFO
*infoPtr
, HDC hdc
, int drawState
, UINT dtflags
, BOOL focused
);
173 static void CB_ThemedPaint( HTHEME theme
, const BUTTON_INFO
*infoPtr
, HDC hdc
, int drawState
, UINT dtflags
, BOOL focused
);
174 static void GB_ThemedPaint( HTHEME theme
, const BUTTON_INFO
*infoPtr
, HDC hdc
, int drawState
, UINT dtflags
, BOOL focused
);
175 static void SB_ThemedPaint( HTHEME theme
, const BUTTON_INFO
*infoPtr
, HDC hdc
, int drawState
, UINT dtflags
, BOOL focused
);
176 static void CL_ThemedPaint( HTHEME theme
, const BUTTON_INFO
*infoPtr
, HDC hdc
, int drawState
, UINT dtflags
, BOOL focused
);
178 static const pfThemedPaint btnThemedPaintFunc
[MAX_BTN_TYPE
] =
180 PB_ThemedPaint
, /* BS_PUSHBUTTON */
181 PB_ThemedPaint
, /* BS_DEFPUSHBUTTON */
182 CB_ThemedPaint
, /* BS_CHECKBOX */
183 CB_ThemedPaint
, /* BS_AUTOCHECKBOX */
184 CB_ThemedPaint
, /* BS_RADIOBUTTON */
185 CB_ThemedPaint
, /* BS_3STATE */
186 CB_ThemedPaint
, /* BS_AUTO3STATE */
187 GB_ThemedPaint
, /* BS_GROUPBOX */
188 NULL
, /* BS_USERBUTTON */
189 CB_ThemedPaint
, /* BS_AUTORADIOBUTTON */
190 NULL
, /* BS_PUSHBOX */
191 NULL
, /* BS_OWNERDRAW */
192 SB_ThemedPaint
, /* BS_SPLITBUTTON */
193 SB_ThemedPaint
, /* BS_DEFSPLITBUTTON */
194 CL_ThemedPaint
, /* BS_COMMANDLINK */
195 CL_ThemedPaint
/* BS_DEFCOMMANDLINK */
198 typedef BOOL (*pfGetIdealSize
)(BUTTON_INFO
*infoPtr
, SIZE
*size
);
200 static BOOL
PB_GetIdealSize(BUTTON_INFO
*infoPtr
, SIZE
*size
);
201 static BOOL
CB_GetIdealSize(BUTTON_INFO
*infoPtr
, SIZE
*size
);
202 static BOOL
GB_GetIdealSize(BUTTON_INFO
*infoPtr
, SIZE
*size
);
203 static BOOL
SB_GetIdealSize(BUTTON_INFO
*infoPtr
, SIZE
*size
);
204 static BOOL
CL_GetIdealSize(BUTTON_INFO
*infoPtr
, SIZE
*size
);
206 static const pfGetIdealSize btnGetIdealSizeFunc
[MAX_BTN_TYPE
] = {
207 PB_GetIdealSize
, /* BS_PUSHBUTTON */
208 PB_GetIdealSize
, /* BS_DEFPUSHBUTTON */
209 CB_GetIdealSize
, /* BS_CHECKBOX */
210 CB_GetIdealSize
, /* BS_AUTOCHECKBOX */
211 CB_GetIdealSize
, /* BS_RADIOBUTTON */
212 GB_GetIdealSize
, /* BS_3STATE */
213 GB_GetIdealSize
, /* BS_AUTO3STATE */
214 GB_GetIdealSize
, /* BS_GROUPBOX */
215 PB_GetIdealSize
, /* BS_USERBUTTON */
216 CB_GetIdealSize
, /* BS_AUTORADIOBUTTON */
217 GB_GetIdealSize
, /* BS_PUSHBOX */
218 GB_GetIdealSize
, /* BS_OWNERDRAW */
219 SB_GetIdealSize
, /* BS_SPLITBUTTON */
220 SB_GetIdealSize
, /* BS_DEFSPLITBUTTON */
221 CL_GetIdealSize
, /* BS_COMMANDLINK */
222 CL_GetIdealSize
/* BS_DEFCOMMANDLINK */
225 /* Fixed margin for command links, regardless of DPI (based on tests done on Windows) */
226 enum { command_link_margin
= 6 };
228 /* The width and height for the default command link glyph (when there's no image) */
229 enum { command_link_defglyph_size
= 17 };
231 static inline UINT
get_button_type( LONG window_style
)
233 return (window_style
& BS_TYPEMASK
);
236 static inline BOOL
button_centers_text( LONG window_style
)
238 /* Push button's text is centered by default, same for split buttons */
239 UINT type
= get_button_type(window_style
);
240 return type
<= BS_DEFPUSHBUTTON
|| type
== BS_SPLITBUTTON
|| type
== BS_DEFSPLITBUTTON
;
243 /* paint a button of any type */
244 static inline void paint_button( BUTTON_INFO
*infoPtr
, LONG style
, UINT action
)
246 if (btnPaintFunc
[style
] && IsWindowVisible(infoPtr
->hwnd
))
248 HDC hdc
= GetDC( infoPtr
->hwnd
);
249 btnPaintFunc
[style
]( infoPtr
, hdc
, action
);
250 ReleaseDC( infoPtr
->hwnd
, hdc
);
254 /* retrieve the button text; returned buffer must be freed by caller */
255 static inline WCHAR
*get_button_text( const BUTTON_INFO
*infoPtr
)
257 INT len
= GetWindowTextLengthW( infoPtr
->hwnd
);
258 WCHAR
*buffer
= heap_alloc( (len
+ 1) * sizeof(WCHAR
) );
260 GetWindowTextW( infoPtr
->hwnd
, buffer
, len
+ 1 );
264 /* get the default glyph size for split buttons */
265 static LONG
get_default_glyph_size(const BUTTON_INFO
*infoPtr
)
267 if (infoPtr
->split_style
& BCSS_IMAGE
)
269 /* Size it to fit, including the left and right edges */
271 if (!ImageList_GetIconSize(infoPtr
->glyph
, &w
, &h
)) w
= 0;
272 return w
+ GetSystemMetrics(SM_CXEDGE
) * 2;
275 /* The glyph size relies on the default menu font's cell height */
276 return GetSystemMetrics(SM_CYMENUCHECK
);
279 static BOOL
is_themed_paint_supported(HTHEME theme
, UINT btn_type
)
281 if (!theme
|| !btnThemedPaintFunc
[btn_type
])
284 if (btn_type
== BS_COMMANDLINK
|| btn_type
== BS_DEFCOMMANDLINK
)
286 if (!IsThemePartDefined(theme
, BP_COMMANDLINK
, 0))
293 static void init_custom_draw(NMCUSTOMDRAW
*nmcd
, const BUTTON_INFO
*infoPtr
, HDC hdc
, const RECT
*rc
)
295 nmcd
->hdr
.hwndFrom
= infoPtr
->hwnd
;
296 nmcd
->hdr
.idFrom
= GetWindowLongPtrW(infoPtr
->hwnd
, GWLP_ID
);
297 nmcd
->hdr
.code
= NM_CUSTOMDRAW
;
300 nmcd
->dwDrawStage
= CDDS_PREERASE
;
301 nmcd
->dwItemSpec
= 0;
302 nmcd
->lItemlParam
= 0;
303 nmcd
->uItemState
= IsWindowEnabled(infoPtr
->hwnd
) ? 0 : CDIS_DISABLED
;
304 if (infoPtr
->state
& BST_PUSHED
) nmcd
->uItemState
|= CDIS_SELECTED
;
305 if (infoPtr
->state
& BST_FOCUS
) nmcd
->uItemState
|= CDIS_FOCUS
;
306 if (infoPtr
->state
& BST_HOT
) nmcd
->uItemState
|= CDIS_HOT
;
307 if (infoPtr
->state
& BST_INDETERMINATE
)
308 nmcd
->uItemState
|= CDIS_INDETERMINATE
;
310 /* Windows doesn't seem to send CDIS_CHECKED (it fails the tests) */
311 /* CDIS_SHOWKEYBOARDCUES is misleading, as the meaning is reversed */
312 /* FIXME: Handle it properly when we support keyboard cues? */
315 HRGN
set_control_clipping( HDC hdc
, const RECT
*rect
)
318 HRGN hrgn
= CreateRectRgn( 0, 0, 0, 0 );
320 if (GetClipRgn( hdc
, hrgn
) != 1)
322 DeleteObject( hrgn
);
325 DPtoLP( hdc
, (POINT
*)&rc
, 2 );
326 if (GetLayout( hdc
) & LAYOUT_RTL
) /* compensate for the shifting done by IntersectClipRect */
331 IntersectClipRect( hdc
, rc
.left
, rc
.top
, rc
.right
, rc
.bottom
);
335 static WCHAR
*heap_strndupW(const WCHAR
*src
, size_t length
)
337 size_t size
= (length
+ 1) * sizeof(WCHAR
);
338 WCHAR
*dst
= heap_alloc(size
);
339 if (dst
) memcpy(dst
, src
, size
);
343 /**********************************************************************
344 * Convert button styles to flags used by DrawText.
346 static UINT
BUTTON_BStoDT( DWORD style
, DWORD ex_style
)
348 UINT dtStyle
= DT_NOCLIP
; /* We use SelectClipRgn to limit output */
350 /* "Convert" pushlike buttons to pushbuttons */
351 if (style
& BS_PUSHLIKE
)
352 style
&= ~BS_TYPEMASK
;
354 if (!(style
& BS_MULTILINE
))
355 dtStyle
|= DT_SINGLELINE
;
357 dtStyle
|= DT_WORDBREAK
;
359 switch (style
& BS_CENTER
)
361 case BS_LEFT
: /* DT_LEFT is 0 */ break;
362 case BS_RIGHT
: dtStyle
|= DT_RIGHT
; break;
363 case BS_CENTER
: dtStyle
|= DT_CENTER
; break;
365 if (button_centers_text(style
)) dtStyle
|= DT_CENTER
;
368 if (ex_style
& WS_EX_RIGHT
) dtStyle
= DT_RIGHT
| (dtStyle
& ~(DT_LEFT
| DT_CENTER
));
370 /* DrawText ignores vertical alignment for multiline text,
371 * but we use these flags to align label manually.
373 if (get_button_type(style
) != BS_GROUPBOX
)
375 switch (style
& BS_VCENTER
)
377 case BS_TOP
: /* DT_TOP is 0 */ break;
378 case BS_BOTTOM
: dtStyle
|= DT_BOTTOM
; break;
379 case BS_VCENTER
: /* fall through */
380 default: dtStyle
|= DT_VCENTER
; break;
387 static int get_draw_state(const BUTTON_INFO
*infoPtr
)
389 static const int pb_states
[DRAW_STATE_COUNT
] = { PBS_NORMAL
, PBS_DISABLED
, PBS_HOT
, PBS_PRESSED
, PBS_DEFAULTED
};
390 static const int cb_states
[3][DRAW_STATE_COUNT
] =
392 { CBS_UNCHECKEDNORMAL
, CBS_UNCHECKEDDISABLED
, CBS_UNCHECKEDHOT
, CBS_UNCHECKEDPRESSED
, CBS_UNCHECKEDNORMAL
},
393 { CBS_CHECKEDNORMAL
, CBS_CHECKEDDISABLED
, CBS_CHECKEDHOT
, CBS_CHECKEDPRESSED
, CBS_CHECKEDNORMAL
},
394 { CBS_MIXEDNORMAL
, CBS_MIXEDDISABLED
, CBS_MIXEDHOT
, CBS_MIXEDPRESSED
, CBS_MIXEDNORMAL
}
396 static const int rb_states
[2][DRAW_STATE_COUNT
] =
398 { RBS_UNCHECKEDNORMAL
, RBS_UNCHECKEDDISABLED
, RBS_UNCHECKEDHOT
, RBS_UNCHECKEDPRESSED
, RBS_UNCHECKEDNORMAL
},
399 { RBS_CHECKEDNORMAL
, RBS_CHECKEDDISABLED
, RBS_CHECKEDHOT
, RBS_CHECKEDPRESSED
, RBS_CHECKEDNORMAL
}
401 static const int gb_states
[DRAW_STATE_COUNT
] = { GBS_NORMAL
, GBS_DISABLED
, GBS_NORMAL
, GBS_NORMAL
, GBS_NORMAL
};
402 LONG style
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
403 UINT type
= get_button_type(style
);
404 int check_state
= infoPtr
->state
& 3;
405 enum draw_state state
;
407 if (!IsWindowEnabled(infoPtr
->hwnd
))
408 state
= STATE_DISABLED
;
409 else if (infoPtr
->state
& BST_PUSHED
)
410 state
= STATE_PRESSED
;
411 else if (infoPtr
->state
& BST_HOT
)
413 else if (infoPtr
->state
& BST_FOCUS
)
414 state
= STATE_DEFAULTED
;
416 state
= STATE_NORMAL
;
421 case BS_DEFPUSHBUTTON
:
424 case BS_DEFSPLITBUTTON
:
426 case BS_DEFCOMMANDLINK
:
427 return pb_states
[state
];
429 case BS_AUTOCHECKBOX
:
430 return cb_states
[check_state
][state
];
434 case BS_AUTORADIOBUTTON
:
435 return rb_states
[check_state
][state
];
437 return gb_states
[state
];
439 WARN("Unsupported button type 0x%08x\n", type
);
444 static LRESULT CALLBACK
BUTTON_WindowProc(HWND hWnd
, UINT uMsg
, WPARAM wParam
, LPARAM lParam
)
446 BUTTON_INFO
*infoPtr
= (BUTTON_INFO
*)GetWindowLongPtrW(hWnd
, 0);
449 LONG style
= GetWindowLongW( hWnd
, GWL_STYLE
);
450 UINT btn_type
= get_button_type( style
);
451 LONG state
, new_state
;
455 if (!IsWindow( hWnd
)) return 0;
457 if (!infoPtr
&& (uMsg
!= WM_NCCREATE
))
458 return DefWindowProcW(hWnd
, uMsg
, wParam
, lParam
);
460 pt
.x
= (short)LOWORD(lParam
);
461 pt
.y
= (short)HIWORD(lParam
);
470 case BS_PUSHBUTTON
: return DLGC_BUTTON
| DLGC_UNDEFPUSHBUTTON
;
471 case BS_DEFCOMMANDLINK
:
472 case BS_DEFPUSHBUTTON
: return DLGC_BUTTON
| DLGC_DEFPUSHBUTTON
;
474 case BS_AUTORADIOBUTTON
: return DLGC_BUTTON
| DLGC_RADIOBUTTON
;
475 case BS_GROUPBOX
: return DLGC_STATIC
;
476 case BS_SPLITBUTTON
: return DLGC_BUTTON
| DLGC_UNDEFPUSHBUTTON
| DLGC_WANTARROWS
;
477 case BS_DEFSPLITBUTTON
: return DLGC_BUTTON
| DLGC_DEFPUSHBUTTON
| DLGC_WANTARROWS
;
478 default: return DLGC_BUTTON
;
482 theme
= GetWindowTheme( hWnd
);
484 RedrawWindow( hWnd
, NULL
, NULL
, RDW_FRAME
| RDW_INVALIDATE
| RDW_UPDATENOW
);
486 paint_button( infoPtr
, btn_type
, ODA_DRAWENTIRE
);
491 CREATESTRUCTW
*cs
= (CREATESTRUCTW
*)lParam
;
493 infoPtr
= heap_alloc_zero( sizeof(*infoPtr
) );
494 SetWindowLongPtrW( hWnd
, 0, (LONG_PTR
)infoPtr
);
495 infoPtr
->hwnd
= hWnd
;
496 infoPtr
->parent
= cs
->hwndParent
;
497 infoPtr
->style
= cs
->style
;
498 infoPtr
->split_style
= BCSS_STRETCH
;
499 infoPtr
->glyph
= (HIMAGELIST
)0x36; /* Marlett down arrow char code */
500 infoPtr
->glyph_size
.cx
= get_default_glyph_size(infoPtr
);
501 return DefWindowProcW(hWnd
, uMsg
, wParam
, lParam
);
505 SetWindowLongPtrW( hWnd
, 0, 0 );
506 if (infoPtr
->image_type
== IMAGE_BITMAP
)
507 DeleteObject(infoPtr
->u
.bitmap
);
508 else if (infoPtr
->image_type
== IMAGE_ICON
)
509 DestroyIcon(infoPtr
->u
.icon
);
510 heap_free(infoPtr
->note
);
515 if (btn_type
>= MAX_BTN_TYPE
)
516 return -1; /* abort */
518 /* XP turns a BS_USERBUTTON into BS_PUSHBUTTON */
519 if (btn_type
== BS_USERBUTTON
)
521 style
= (style
& ~BS_TYPEMASK
) | BS_PUSHBUTTON
;
522 SetWindowLongW( hWnd
, GWL_STYLE
, style
);
524 infoPtr
->state
= BST_UNCHECKED
;
525 OpenThemeData( hWnd
, WC_BUTTONW
);
529 theme
= GetWindowTheme( hWnd
);
530 CloseThemeData( theme
);
533 case WM_THEMECHANGED
:
534 theme
= GetWindowTheme( hWnd
);
535 CloseThemeData( theme
);
536 OpenThemeData( hWnd
, WC_BUTTONW
);
540 if (btn_type
== BS_OWNERDRAW
)
542 HDC hdc
= (HDC
)wParam
;
545 HWND parent
= GetParent(hWnd
);
546 if (!parent
) parent
= hWnd
;
547 hBrush
= (HBRUSH
)SendMessageW(parent
, WM_CTLCOLORBTN
, (WPARAM
)hdc
, (LPARAM
)hWnd
);
548 if (!hBrush
) /* did the app forget to call defwindowproc ? */
549 hBrush
= (HBRUSH
)DefWindowProcW(parent
, WM_CTLCOLORBTN
,
550 (WPARAM
)hdc
, (LPARAM
)hWnd
);
551 GetClientRect(hWnd
, &rc
);
552 FillRect(hdc
, &rc
, hBrush
);
562 theme
= GetWindowTheme( hWnd
);
563 hdc
= wParam
? (HDC
)wParam
: BeginPaint( hWnd
, &ps
);
565 if (is_themed_paint_supported(theme
, btn_type
))
567 int drawState
= get_draw_state(infoPtr
);
568 UINT dtflags
= BUTTON_BStoDT(style
, GetWindowLongW(hWnd
, GWL_EXSTYLE
));
570 btnThemedPaintFunc
[btn_type
](theme
, infoPtr
, hdc
, drawState
, dtflags
, infoPtr
->state
& BST_FOCUS
);
572 else if (btnPaintFunc
[btn_type
])
574 int nOldMode
= SetBkMode( hdc
, OPAQUE
);
575 btnPaintFunc
[btn_type
]( infoPtr
, hdc
, ODA_DRAWENTIRE
);
576 SetBkMode(hdc
, nOldMode
); /* reset painting mode */
579 if ( !wParam
) EndPaint( hWnd
, &ps
);
584 if (wParam
== VK_SPACE
)
586 SendMessageW( hWnd
, BM_SETSTATE
, TRUE
, 0 );
587 infoPtr
->state
|= BUTTON_BTNPRESSED
;
590 else if (wParam
== VK_UP
|| wParam
== VK_DOWN
)
592 /* Up and down arrows work on every button, and even with BCSS_NOSPLIT */
593 notify_split_button_dropdown(infoPtr
, NULL
, hWnd
);
597 case WM_LBUTTONDBLCLK
:
598 if(style
& BS_NOTIFY
||
599 btn_type
== BS_RADIOBUTTON
||
600 btn_type
== BS_USERBUTTON
||
601 btn_type
== BS_OWNERDRAW
)
603 BUTTON_NOTIFY_PARENT(hWnd
, BN_DOUBLECLICKED
);
610 if ((btn_type
== BS_SPLITBUTTON
|| btn_type
== BS_DEFSPLITBUTTON
) &&
611 !(infoPtr
->split_style
& BCSS_NOSPLIT
) &&
612 notify_split_button_dropdown(infoPtr
, &pt
, hWnd
))
616 infoPtr
->state
|= BUTTON_BTNPRESSED
;
617 SendMessageW( hWnd
, BM_SETSTATE
, TRUE
, 0 );
621 if (wParam
!= VK_SPACE
)
625 state
= infoPtr
->state
;
626 if (state
& BST_DROPDOWNPUSHED
)
627 SendMessageW(hWnd
, BCM_SETDROPDOWNSTATE
, FALSE
, 0);
628 if (!(state
& BUTTON_BTNPRESSED
)) break;
629 infoPtr
->state
&= BUTTON_NSTATES
| BST_HOT
;
630 if (!(state
& BST_PUSHED
))
635 SendMessageW( hWnd
, BM_SETSTATE
, FALSE
, 0 );
636 GetClientRect( hWnd
, &rect
);
637 if (uMsg
== WM_KEYUP
|| PtInRect( &rect
, pt
))
641 case BS_AUTOCHECKBOX
:
642 SendMessageW( hWnd
, BM_SETCHECK
, !(infoPtr
->state
& BST_CHECKED
), 0 );
644 case BS_AUTORADIOBUTTON
:
645 SendMessageW( hWnd
, BM_SETCHECK
, TRUE
, 0 );
648 SendMessageW( hWnd
, BM_SETCHECK
, (infoPtr
->state
& BST_INDETERMINATE
) ? 0 :
649 ((infoPtr
->state
& 3) + 1), 0 );
653 BUTTON_NOTIFY_PARENT(hWnd
, BN_CLICKED
);
661 case WM_CAPTURECHANGED
:
662 TRACE("WM_CAPTURECHANGED %p\n", hWnd
);
663 if (hWnd
== (HWND
)lParam
) break;
664 if (infoPtr
->state
& BUTTON_BTNPRESSED
)
666 infoPtr
->state
&= BUTTON_NSTATES
;
667 if (infoPtr
->state
& BST_PUSHED
)
668 SendMessageW( hWnd
, BM_SETSTATE
, FALSE
, 0 );
674 TRACKMOUSEEVENT mouse_event
;
676 mouse_event
.cbSize
= sizeof(TRACKMOUSEEVENT
);
677 mouse_event
.dwFlags
= TME_QUERY
;
678 if (!TrackMouseEvent(&mouse_event
) || !(mouse_event
.dwFlags
& (TME_HOVER
| TME_LEAVE
)))
680 mouse_event
.dwFlags
= TME_HOVER
| TME_LEAVE
;
681 mouse_event
.hwndTrack
= hWnd
;
682 mouse_event
.dwHoverTime
= 1;
683 TrackMouseEvent(&mouse_event
);
686 if ((wParam
& MK_LBUTTON
) && GetCapture() == hWnd
)
688 GetClientRect( hWnd
, &rect
);
689 SendMessageW( hWnd
, BM_SETSTATE
, PtInRect(&rect
, pt
), 0 );
696 infoPtr
->state
|= BST_HOT
;
697 InvalidateRect( hWnd
, NULL
, FALSE
);
703 infoPtr
->state
&= ~BST_HOT
;
704 InvalidateRect( hWnd
, NULL
, FALSE
);
710 /* Clear an old text here as Windows does */
711 if (IsWindowVisible(hWnd
))
713 HDC hdc
= GetDC(hWnd
);
716 HWND parent
= GetParent(hWnd
);
717 UINT message
= (btn_type
== BS_PUSHBUTTON
||
718 btn_type
== BS_DEFPUSHBUTTON
||
719 btn_type
== BS_USERBUTTON
||
720 btn_type
== BS_OWNERDRAW
) ?
721 WM_CTLCOLORBTN
: WM_CTLCOLORSTATIC
;
723 if (!parent
) parent
= hWnd
;
724 hbrush
= (HBRUSH
)SendMessageW(parent
, message
,
725 (WPARAM
)hdc
, (LPARAM
)hWnd
);
726 if (!hbrush
) /* did the app forget to call DefWindowProc ? */
727 hbrush
= (HBRUSH
)DefWindowProcW(parent
, message
,
728 (WPARAM
)hdc
, (LPARAM
)hWnd
);
730 GetClientRect(hWnd
, &client
);
732 /* FIXME: check other BS_* handlers */
733 if (btn_type
== BS_GROUPBOX
)
734 InflateRect(&rc
, -7, 1); /* GB_Paint does this */
735 BUTTON_CalcLayoutRects(infoPtr
, hdc
, &rc
, NULL
, NULL
);
736 /* Clip by client rect bounds */
737 if (rc
.right
> client
.right
) rc
.right
= client
.right
;
738 if (rc
.bottom
> client
.bottom
) rc
.bottom
= client
.bottom
;
739 FillRect(hdc
, &rc
, hbrush
);
740 ReleaseDC(hWnd
, hdc
);
743 DefWindowProcW( hWnd
, WM_SETTEXT
, wParam
, lParam
);
744 if (btn_type
== BS_GROUPBOX
) /* Yes, only for BS_GROUPBOX */
745 InvalidateRect( hWnd
, NULL
, TRUE
);
747 paint_button( infoPtr
, btn_type
, ODA_DRAWENTIRE
);
748 return 1; /* success. FIXME: check text length */
753 WCHAR
*note
= (WCHAR
*)lParam
;
754 if (btn_type
!= BS_COMMANDLINK
&& btn_type
!= BS_DEFCOMMANDLINK
)
756 SetLastError(ERROR_NOT_SUPPORTED
);
760 heap_free(infoPtr
->note
);
763 infoPtr
->note_length
= lstrlenW(note
);
764 infoPtr
->note
= heap_strndupW(note
, infoPtr
->note_length
);
767 if (!note
|| !infoPtr
->note
)
769 infoPtr
->note_length
= 0;
770 infoPtr
->note
= heap_alloc_zero(sizeof(WCHAR
));
773 SetLastError(NO_ERROR
);
779 DWORD
*size
= (DWORD
*)wParam
;
780 WCHAR
*buffer
= (WCHAR
*)lParam
;
783 if (btn_type
!= BS_COMMANDLINK
&& btn_type
!= BS_DEFCOMMANDLINK
)
785 SetLastError(ERROR_NOT_SUPPORTED
);
789 if (!buffer
|| !size
|| !infoPtr
->note
)
791 SetLastError(ERROR_INVALID_PARAMETER
);
797 length
= min(*size
- 1, infoPtr
->note_length
);
798 memcpy(buffer
, infoPtr
->note
, length
* sizeof(WCHAR
));
799 buffer
[length
] = '\0';
802 if (*size
< infoPtr
->note_length
+ 1)
804 *size
= infoPtr
->note_length
+ 1;
805 SetLastError(ERROR_INSUFFICIENT_BUFFER
);
810 SetLastError(NO_ERROR
);
815 case BCM_GETNOTELENGTH
:
817 if (btn_type
!= BS_COMMANDLINK
&& btn_type
!= BS_DEFCOMMANDLINK
)
819 SetLastError(ERROR_NOT_SUPPORTED
);
823 return infoPtr
->note_length
;
827 infoPtr
->font
= (HFONT
)wParam
;
828 if (lParam
) InvalidateRect(hWnd
, NULL
, TRUE
);
832 return (LRESULT
)infoPtr
->font
;
835 TRACE("WM_SETFOCUS %p\n",hWnd
);
836 infoPtr
->state
|= BST_FOCUS
;
838 if (btn_type
== BS_OWNERDRAW
)
839 paint_button( infoPtr
, btn_type
, ODA_FOCUS
);
841 InvalidateRect(hWnd
, NULL
, FALSE
);
843 if (style
& BS_NOTIFY
)
844 BUTTON_NOTIFY_PARENT(hWnd
, BN_SETFOCUS
);
848 TRACE("WM_KILLFOCUS %p\n",hWnd
);
849 infoPtr
->state
&= ~BST_FOCUS
;
851 if ((infoPtr
->state
& BUTTON_BTNPRESSED
) && GetCapture() == hWnd
)
853 if (style
& BS_NOTIFY
)
854 BUTTON_NOTIFY_PARENT(hWnd
, BN_KILLFOCUS
);
856 InvalidateRect( hWnd
, NULL
, FALSE
);
859 case WM_SYSCOLORCHANGE
:
860 InvalidateRect( hWnd
, NULL
, FALSE
);
867 new_btn_type
= wParam
& BS_TYPEMASK
;
868 if (btn_type
>= BS_SPLITBUTTON
&& new_btn_type
<= BS_DEFPUSHBUTTON
)
869 new_btn_type
= (btn_type
& ~BS_DEFPUSHBUTTON
) | new_btn_type
;
871 style
= (style
& ~BS_TYPEMASK
) | new_btn_type
;
872 SetWindowLongW( hWnd
, GWL_STYLE
, style
);
874 /* Only redraw if lParam flag is set.*/
876 InvalidateRect( hWnd
, NULL
, TRUE
);
881 SendMessageW( hWnd
, WM_LBUTTONDOWN
, 0, 0 );
882 SendMessageW( hWnd
, WM_LBUTTONUP
, 0, 0 );
886 infoPtr
->image_type
= (DWORD
)wParam
;
887 oldHbitmap
= infoPtr
->image
;
888 infoPtr
->u
.image
= CopyImage((HANDLE
)lParam
, infoPtr
->image_type
, 0, 0, 0);
889 infoPtr
->image
= (HANDLE
)lParam
;
890 InvalidateRect( hWnd
, NULL
, FALSE
);
891 return (LRESULT
)oldHbitmap
;
894 return (LRESULT
)infoPtr
->image
;
896 case BCM_SETIMAGELIST
:
898 BUTTON_IMAGELIST
*imagelist
= (BUTTON_IMAGELIST
*)lParam
;
900 if (!imagelist
) return FALSE
;
902 infoPtr
->imagelist
= *imagelist
;
906 case BCM_GETIMAGELIST
:
908 BUTTON_IMAGELIST
*imagelist
= (BUTTON_IMAGELIST
*)lParam
;
910 if (!imagelist
) return FALSE
;
912 *imagelist
= infoPtr
->imagelist
;
916 case BCM_SETSPLITINFO
:
918 BUTTON_SPLITINFO
*info
= (BUTTON_SPLITINFO
*)lParam
;
920 if (!info
) return TRUE
;
922 if (info
->mask
& (BCSIF_GLYPH
| BCSIF_IMAGE
))
924 infoPtr
->split_style
&= ~BCSS_IMAGE
;
925 if (!(info
->mask
& BCSIF_GLYPH
))
926 infoPtr
->split_style
|= BCSS_IMAGE
;
927 infoPtr
->glyph
= info
->himlGlyph
;
928 infoPtr
->glyph_size
.cx
= infoPtr
->glyph_size
.cy
= 0;
931 if (info
->mask
& BCSIF_STYLE
)
932 infoPtr
->split_style
= info
->uSplitStyle
;
933 if (info
->mask
& BCSIF_SIZE
)
934 infoPtr
->glyph_size
= info
->size
;
936 /* Calculate fitting value for cx if invalid (cy is untouched) */
937 if (infoPtr
->glyph_size
.cx
<= 0)
938 infoPtr
->glyph_size
.cx
= get_default_glyph_size(infoPtr
);
940 /* Windows doesn't invalidate or redraw it, so we don't, either */
944 case BCM_GETSPLITINFO
:
946 BUTTON_SPLITINFO
*info
= (BUTTON_SPLITINFO
*)lParam
;
948 if (!info
) return FALSE
;
950 if (info
->mask
& BCSIF_STYLE
)
951 info
->uSplitStyle
= infoPtr
->split_style
;
952 if (info
->mask
& (BCSIF_GLYPH
| BCSIF_IMAGE
))
953 info
->himlGlyph
= infoPtr
->glyph
;
954 if (info
->mask
& BCSIF_SIZE
)
955 info
->size
= infoPtr
->glyph_size
;
961 return infoPtr
->state
& 3;
964 if (wParam
> maxCheckState
[btn_type
]) wParam
= maxCheckState
[btn_type
];
965 if ((btn_type
== BS_RADIOBUTTON
) || (btn_type
== BS_AUTORADIOBUTTON
))
967 style
= wParam
? style
| WS_TABSTOP
: style
& ~WS_TABSTOP
;
968 SetWindowLongW( hWnd
, GWL_STYLE
, style
);
970 if ((infoPtr
->state
& 3) != wParam
)
972 infoPtr
->state
= (infoPtr
->state
& ~3) | wParam
;
973 InvalidateRect( hWnd
, NULL
, FALSE
);
975 if ((btn_type
== BS_AUTORADIOBUTTON
) && (wParam
== BST_CHECKED
) && (style
& WS_CHILD
))
976 BUTTON_CheckAutoRadioButton( hWnd
);
980 return infoPtr
->state
;
983 state
= infoPtr
->state
;
984 new_state
= wParam
? BST_PUSHED
: 0;
986 if ((state
^ new_state
) & BST_PUSHED
)
991 state
&= ~BST_PUSHED
;
993 if (btn_type
== BS_USERBUTTON
)
994 BUTTON_NOTIFY_PARENT( hWnd
, (state
& BST_PUSHED
) ? BN_HILITE
: BN_UNHILITE
);
995 infoPtr
->state
= state
;
997 InvalidateRect( hWnd
, NULL
, FALSE
);
1001 case BCM_SETDROPDOWNSTATE
:
1002 new_state
= wParam
? BST_DROPDOWNPUSHED
: 0;
1004 if ((infoPtr
->state
^ new_state
) & BST_DROPDOWNPUSHED
)
1006 infoPtr
->state
&= ~BST_DROPDOWNPUSHED
;
1007 infoPtr
->state
|= new_state
;
1008 InvalidateRect(hWnd
, NULL
, FALSE
);
1012 case BCM_SETTEXTMARGIN
:
1014 RECT
*text_margin
= (RECT
*)lParam
;
1016 if (!text_margin
) return FALSE
;
1018 infoPtr
->text_margin
= *text_margin
;
1022 case BCM_GETTEXTMARGIN
:
1024 RECT
*text_margin
= (RECT
*)lParam
;
1026 if (!text_margin
) return FALSE
;
1028 *text_margin
= infoPtr
->text_margin
;
1032 case BCM_GETIDEALSIZE
:
1034 SIZE
*size
= (SIZE
*)lParam
;
1036 if (!size
) return FALSE
;
1038 return btnGetIdealSizeFunc
[btn_type
](infoPtr
, size
);
1042 if(btn_type
== BS_GROUPBOX
) return HTTRANSPARENT
;
1045 return DefWindowProcW(hWnd
, uMsg
, wParam
, lParam
);
1050 /* If maxWidth is zero, rectangle width is unlimited */
1051 static RECT
BUTTON_GetTextRect(const BUTTON_INFO
*infoPtr
, HDC hdc
, const WCHAR
*text
, LONG maxWidth
)
1053 LONG style
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
1054 LONG exStyle
= GetWindowLongW(infoPtr
->hwnd
, GWL_EXSTYLE
);
1055 UINT dtStyle
= BUTTON_BStoDT(style
, exStyle
);
1059 rect
.right
= maxWidth
;
1060 hPrevFont
= SelectObject(hdc
, infoPtr
->font
);
1061 /* Calculate height without DT_VCENTER and DT_BOTTOM to get the correct height */
1062 DrawTextW(hdc
, text
, -1, &rect
, (dtStyle
& ~(DT_VCENTER
| DT_BOTTOM
)) | DT_CALCRECT
);
1063 if (hPrevFont
) SelectObject(hdc
, hPrevFont
);
1068 static BOOL
show_image_only(const BUTTON_INFO
*infoPtr
)
1070 LONG style
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
1071 return (style
& (BS_ICON
| BS_BITMAP
)) && (infoPtr
->u
.image
|| infoPtr
->imagelist
.himl
);
1074 static BOOL
show_image_and_text(const BUTTON_INFO
*infoPtr
)
1076 LONG style
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
1077 UINT type
= get_button_type(style
);
1078 return !(style
& (BS_ICON
| BS_BITMAP
))
1079 && ((infoPtr
->u
.image
1080 && (type
== BS_PUSHBUTTON
|| type
== BS_DEFPUSHBUTTON
|| type
== BS_USERBUTTON
|| type
== BS_SPLITBUTTON
1081 || type
== BS_DEFSPLITBUTTON
|| type
== BS_COMMANDLINK
|| type
== BS_DEFCOMMANDLINK
))
1082 || (infoPtr
->imagelist
.himl
&& type
!= BS_GROUPBOX
));
1085 static BOOL
show_image(const BUTTON_INFO
*infoPtr
)
1087 return show_image_only(infoPtr
) || show_image_and_text(infoPtr
);
1090 /* Get a bounding rectangle that is large enough to contain a image and a text side by side.
1091 * Note: (left,top) of the result rectangle may not be (0,0), offset it by yourself if needed */
1092 static RECT
BUTTON_GetBoundingLabelRect(LONG style
, const RECT
*textRect
, const RECT
*imageRect
)
1095 RECT rect
= *imageRect
;
1096 INT textWidth
= textRect
->right
- textRect
->left
;
1097 INT textHeight
= textRect
->bottom
- textRect
->top
;
1098 INT imageWidth
= imageRect
->right
- imageRect
->left
;
1099 INT imageHeight
= imageRect
->bottom
- imageRect
->top
;
1101 if ((style
& BS_CENTER
) == BS_RIGHT
)
1102 OffsetRect(&rect
, textWidth
, 0);
1103 else if ((style
& BS_CENTER
) == BS_LEFT
)
1104 OffsetRect(&rect
, -imageWidth
, 0);
1105 else if ((style
& BS_VCENTER
) == BS_BOTTOM
)
1106 OffsetRect(&rect
, 0, textHeight
);
1107 else if ((style
& BS_VCENTER
) == BS_TOP
)
1108 OffsetRect(&rect
, 0, -imageHeight
);
1110 OffsetRect(&rect
, -imageWidth
, 0);
1112 UnionRect(&labelRect
, textRect
, &rect
);
1116 /* Position a rectangle inside a bounding rectangle according to button alignment flags */
1117 static void BUTTON_PositionRect(LONG style
, const RECT
*outerRect
, RECT
*innerRect
, const RECT
*margin
)
1119 INT width
= innerRect
->right
- innerRect
->left
;
1120 INT height
= innerRect
->bottom
- innerRect
->top
;
1122 if ((style
& WS_EX_RIGHT
) && !(style
& BS_CENTER
)) style
|= BS_CENTER
;
1124 if (!(style
& BS_CENTER
))
1126 if (button_centers_text(style
))
1132 if (!(style
& BS_VCENTER
))
1134 /* Group box's text is top aligned by default */
1135 if (get_button_type(style
) == BS_GROUPBOX
)
1139 switch (style
& BS_CENTER
)
1142 innerRect
->left
= outerRect
->left
+ (outerRect
->right
- outerRect
->left
- width
) / 2;
1143 innerRect
->right
= innerRect
->left
+ width
;
1146 innerRect
->right
= outerRect
->right
- margin
->right
;
1147 innerRect
->left
= innerRect
->right
- width
;
1151 innerRect
->left
= outerRect
->left
+ margin
->left
;
1152 innerRect
->right
= innerRect
->left
+ width
;
1156 switch (style
& BS_VCENTER
)
1159 innerRect
->top
= outerRect
->top
+ margin
->top
;
1160 innerRect
->bottom
= innerRect
->top
+ height
;
1163 innerRect
->bottom
= outerRect
->bottom
- margin
->bottom
;
1164 innerRect
->top
= innerRect
->bottom
- height
;
1168 innerRect
->top
= outerRect
->top
+ (outerRect
->bottom
- outerRect
->top
- height
) / 2;
1169 innerRect
->bottom
= innerRect
->top
+ height
;
1174 /* Convert imagelist align style to button align style */
1175 static UINT
BUTTON_ILStoBS(UINT align
)
1179 case BUTTON_IMAGELIST_ALIGN_TOP
:
1180 return BS_CENTER
| BS_TOP
;
1181 case BUTTON_IMAGELIST_ALIGN_BOTTOM
:
1182 return BS_CENTER
| BS_BOTTOM
;
1183 case BUTTON_IMAGELIST_ALIGN_CENTER
:
1184 return BS_CENTER
| BS_VCENTER
;
1185 case BUTTON_IMAGELIST_ALIGN_RIGHT
:
1186 return BS_RIGHT
| BS_VCENTER
;
1187 case BUTTON_IMAGELIST_ALIGN_LEFT
:
1189 return BS_LEFT
| BS_VCENTER
;
1193 static SIZE
BUTTON_GetImageSize(const BUTTON_INFO
*infoPtr
)
1199 /* ImageList has priority over image */
1200 if (infoPtr
->imagelist
.himl
)
1201 ImageList_GetIconSize(infoPtr
->imagelist
.himl
, &size
.cx
, &size
.cy
);
1202 else if (infoPtr
->u
.image
)
1204 if (infoPtr
->image_type
== IMAGE_ICON
)
1206 GetIconInfo(infoPtr
->u
.icon
, &iconInfo
);
1207 GetObjectW(iconInfo
.hbmColor
, sizeof(bm
), &bm
);
1208 DeleteObject(iconInfo
.hbmColor
);
1209 DeleteObject(iconInfo
.hbmMask
);
1211 else if (infoPtr
->image_type
== IMAGE_BITMAP
)
1212 GetObjectW(infoPtr
->u
.bitmap
, sizeof(bm
), &bm
);
1214 size
.cx
= bm
.bmWidth
;
1215 size
.cy
= bm
.bmHeight
;
1221 static const RECT
*BUTTON_GetTextMargin(const BUTTON_INFO
*infoPtr
)
1223 static const RECT oneMargin
= {1, 1, 1, 1};
1225 /* Use text margin only when showing both image and text, and image is not imagelist */
1226 if (show_image_and_text(infoPtr
) && !infoPtr
->imagelist
.himl
)
1227 return &infoPtr
->text_margin
;
1232 static void BUTTON_GetClientRectSize(BUTTON_INFO
*infoPtr
, SIZE
*size
)
1235 GetClientRect(infoPtr
->hwnd
, &rect
);
1236 size
->cx
= rect
.right
- rect
.left
;
1237 size
->cy
= rect
.bottom
- rect
.top
;
1240 static void BUTTON_GetTextIdealSize(BUTTON_INFO
*infoPtr
, LONG maxWidth
, SIZE
*size
)
1242 WCHAR
*text
= get_button_text(infoPtr
);
1245 const RECT
*margin
= BUTTON_GetTextMargin(infoPtr
);
1249 maxWidth
-= margin
->right
+ margin
->right
;
1250 if (maxWidth
<= 0) maxWidth
= 1;
1253 hdc
= GetDC(infoPtr
->hwnd
);
1254 rect
= BUTTON_GetTextRect(infoPtr
, hdc
, text
, maxWidth
);
1255 ReleaseDC(infoPtr
->hwnd
, hdc
);
1258 size
->cx
= rect
.right
- rect
.left
+ margin
->left
+ margin
->right
;
1259 size
->cy
= rect
.bottom
- rect
.top
+ margin
->top
+ margin
->bottom
;
1262 static void BUTTON_GetLabelIdealSize(BUTTON_INFO
*infoPtr
, LONG maxWidth
, SIZE
*size
)
1264 LONG style
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
1269 imageSize
= BUTTON_GetImageSize(infoPtr
);
1270 if (infoPtr
->imagelist
.himl
)
1272 imageSize
.cx
+= infoPtr
->imagelist
.margin
.left
+ infoPtr
->imagelist
.margin
.right
;
1273 imageSize
.cy
+= infoPtr
->imagelist
.margin
.top
+ infoPtr
->imagelist
.margin
.bottom
;
1274 if (infoPtr
->imagelist
.uAlign
== BUTTON_IMAGELIST_ALIGN_TOP
1275 || infoPtr
->imagelist
.uAlign
== BUTTON_IMAGELIST_ALIGN_BOTTOM
)
1282 /* horizontal alignment flags has priority over vertical ones if both are specified */
1283 if (!(style
& (BS_CENTER
| BS_VCENTER
)) || ((style
& BS_CENTER
) && (style
& BS_CENTER
) != BS_CENTER
)
1284 || !(style
& BS_VCENTER
) || (style
& BS_VCENTER
) == BS_VCENTER
)
1294 maxWidth
-= imageSize
.cx
;
1295 if (maxWidth
<= 0) maxWidth
= 1;
1297 BUTTON_GetTextIdealSize(infoPtr
, maxWidth
, &textSize
);
1298 size
->cx
= textSize
.cx
+ imageSize
.cx
;
1299 size
->cy
= max(textSize
.cy
, imageSize
.cy
);
1303 BUTTON_GetTextIdealSize(infoPtr
, maxWidth
, &textSize
);
1304 size
->cx
= max(textSize
.cx
, imageSize
.cx
);
1305 size
->cy
= textSize
.cy
+ imageSize
.cy
;
1309 static BOOL
GB_GetIdealSize(BUTTON_INFO
*infoPtr
, SIZE
*size
)
1311 BUTTON_GetClientRectSize(infoPtr
, size
);
1315 static BOOL
CB_GetIdealSize(BUTTON_INFO
*infoPtr
, SIZE
*size
)
1317 LONG style
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
1324 LONG checkboxWidth
, checkboxHeight
;
1327 if (SendMessageW(infoPtr
->hwnd
, WM_GETTEXTLENGTH
, 0, 0) == 0)
1329 BUTTON_GetClientRectSize(infoPtr
, size
);
1333 hdc
= GetDC(infoPtr
->hwnd
);
1334 scaleX
= GetDeviceCaps(hdc
, LOGPIXELSX
) / 96.0;
1335 scaleY
= GetDeviceCaps(hdc
, LOGPIXELSY
) / 96.0;
1336 if ((hfont
= infoPtr
->font
)) SelectObject(hdc
, hfont
);
1337 GetCharWidthW(hdc
, '0', '0', &textOffset
);
1339 ReleaseDC(infoPtr
->hwnd
, hdc
);
1341 checkboxWidth
= 12 * scaleX
+ 1;
1342 checkboxHeight
= 12 * scaleY
+ 1;
1345 maxWidth
= size
->cx
- checkboxWidth
- textOffset
;
1346 if (maxWidth
<= 0) maxWidth
= 1;
1349 /* Checkbox doesn't support both image(but not image list) and text */
1350 if (!(style
& (BS_ICON
| BS_BITMAP
)) && infoPtr
->u
.image
)
1351 BUTTON_GetTextIdealSize(infoPtr
, maxWidth
, &labelSize
);
1353 BUTTON_GetLabelIdealSize(infoPtr
, maxWidth
, &labelSize
);
1355 size
->cx
= labelSize
.cx
+ checkboxWidth
+ textOffset
;
1356 size
->cy
= max(labelSize
.cy
, checkboxHeight
);
1361 static BOOL
PB_GetIdealSize(BUTTON_INFO
*infoPtr
, SIZE
*size
)
1365 if (SendMessageW(infoPtr
->hwnd
, WM_GETTEXTLENGTH
, 0, 0) == 0)
1366 BUTTON_GetClientRectSize(infoPtr
, size
);
1369 /* Ideal size include text size even if image only flags(BS_ICON, BS_BITMAP) are specified */
1370 BUTTON_GetLabelIdealSize(infoPtr
, size
->cx
, &labelSize
);
1372 size
->cx
= labelSize
.cx
;
1373 size
->cy
= labelSize
.cy
;
1378 static BOOL
SB_GetIdealSize(BUTTON_INFO
*infoPtr
, SIZE
*size
)
1380 LONG extra_width
= infoPtr
->glyph_size
.cx
* 2 + GetSystemMetrics(SM_CXEDGE
);
1383 if (SendMessageW(infoPtr
->hwnd
, WM_GETTEXTLENGTH
, 0, 0) == 0)
1385 BUTTON_GetClientRectSize(infoPtr
, size
);
1386 size
->cx
= max(size
->cx
, extra_width
);
1390 BUTTON_GetLabelIdealSize(infoPtr
, size
->cx
, &label_size
);
1391 size
->cx
= label_size
.cx
+ ((size
->cx
== 0) ? extra_width
: 0);
1392 size
->cy
= label_size
.cy
;
1397 static BOOL
CL_GetIdealSize(BUTTON_INFO
*infoPtr
, SIZE
*size
)
1399 HTHEME theme
= GetWindowTheme(infoPtr
->hwnd
);
1400 HDC hdc
= GetDC(infoPtr
->hwnd
);
1401 LONG w
, text_w
= 0, text_h
= 0;
1402 UINT flags
= DT_TOP
| DT_LEFT
;
1403 HFONT font
, old_font
= NULL
;
1404 RECT text_bound
= { 0 };
1409 /* Get the image size */
1410 if (infoPtr
->u
.image
|| infoPtr
->imagelist
.himl
)
1411 img_size
= BUTTON_GetImageSize(infoPtr
);
1415 GetThemePartSize(theme
, NULL
, BP_COMMANDLINKGLYPH
, CMDLS_NORMAL
, NULL
, TS_DRAW
, &img_size
);
1417 img_size
.cx
= img_size
.cy
= command_link_defglyph_size
;
1420 /* Get the content margins */
1423 RECT r
= { 0, 0, 0xffff, 0xffff };
1424 GetThemeBackgroundContentRect(theme
, hdc
, BP_COMMANDLINK
, CMDLS_NORMAL
, &r
, &margin
);
1425 margin
.left
-= r
.left
;
1426 margin
.top
-= r
.top
;
1427 margin
.right
= r
.right
- margin
.right
;
1428 margin
.bottom
= r
.bottom
- margin
.bottom
;
1432 margin
.left
= margin
.right
= command_link_margin
;
1433 margin
.top
= margin
.bottom
= command_link_margin
;
1436 /* Account for the border margins and the margin between image and text */
1437 w
= margin
.left
+ margin
.right
+ (img_size
.cx
? (img_size
.cx
+ command_link_margin
) : 0);
1439 /* If a rectangle with a specific width was requested, bound the text to it */
1442 text_bound
.right
= size
->cx
- w
;
1443 flags
|= DT_WORDBREAK
;
1448 if (infoPtr
->font
) old_font
= SelectObject(hdc
, infoPtr
->font
);
1450 /* Find the text's rect */
1451 if ((text
= get_button_text(infoPtr
)))
1454 GetThemeTextExtent(theme
, hdc
, BP_COMMANDLINK
, CMDLS_NORMAL
,
1455 text
, -1, flags
, &text_bound
, &r
);
1457 text_w
= r
.right
- r
.left
;
1458 text_h
= r
.bottom
- r
.top
;
1461 /* Find the note's rect */
1466 opts
.dwSize
= sizeof(opts
);
1467 opts
.dwFlags
= DTT_FONTPROP
| DTT_CALCRECT
;
1468 opts
.iFontPropId
= TMT_BODYFONT
;
1469 DrawThemeTextEx(theme
, hdc
, BP_COMMANDLINK
, CMDLS_NORMAL
,
1470 infoPtr
->note
, infoPtr
->note_length
,
1471 flags
| DT_NOPREFIX
| DT_CALCRECT
, &text_bound
, &opts
);
1472 text_w
= max(text_w
, text_bound
.right
- text_bound
.left
);
1473 text_h
+= text_bound
.bottom
- text_bound
.top
;
1478 NONCLIENTMETRICSW ncm
;
1480 ncm
.cbSize
= sizeof(ncm
);
1481 if (SystemParametersInfoW(SPI_GETNONCLIENTMETRICS
, sizeof(ncm
), &ncm
, 0))
1483 LONG note_weight
= ncm
.lfMessageFont
.lfWeight
;
1485 /* Find the text's rect */
1486 ncm
.lfMessageFont
.lfWeight
= FW_BOLD
;
1487 if ((font
= CreateFontIndirectW(&ncm
.lfMessageFont
)))
1489 if ((text
= get_button_text(infoPtr
)))
1491 RECT r
= text_bound
;
1492 old_font
= SelectObject(hdc
, font
);
1493 DrawTextW(hdc
, text
, -1, &r
, flags
| DT_CALCRECT
);
1496 text_w
= r
.right
- r
.left
;
1497 text_h
= r
.bottom
- r
.top
;
1502 /* Find the note's rect */
1503 ncm
.lfMessageFont
.lfWeight
= note_weight
;
1504 if (infoPtr
->note
&& (font
= CreateFontIndirectW(&ncm
.lfMessageFont
)))
1506 HFONT tmp
= SelectObject(hdc
, font
);
1507 if (!old_font
) old_font
= tmp
;
1509 DrawTextW(hdc
, infoPtr
->note
, infoPtr
->note_length
, &text_bound
,
1510 flags
| DT_NOPREFIX
| DT_CALCRECT
);
1513 text_w
= max(text_w
, text_bound
.right
- text_bound
.left
);
1514 text_h
+= text_bound
.bottom
- text_bound
.top
+ 2;
1520 size
->cx
= min(size
->cx
, w
);
1521 size
->cy
= max(text_h
, img_size
.cy
) + margin
.top
+ margin
.bottom
;
1523 if (old_font
) SelectObject(hdc
, old_font
);
1524 ReleaseDC(infoPtr
->hwnd
, hdc
);
1528 /**********************************************************************
1529 * BUTTON_CalcLayoutRects
1531 * Calculates the rectangles of the button label(image and text) and its parts depending on a button's style.
1533 * Returns flags to be passed to DrawText.
1534 * Calculated rectangle doesn't take into account button state
1535 * (pushed, etc.). If there is nothing to draw (no text/image) output
1536 * rectangle is empty, and return value is (UINT)-1.
1539 * infoPtr [I] Button pointer
1540 * hdc [I] Handle to device context to draw to
1541 * labelRc [I/O] Input the rect the label to be positioned in, and output the label rect
1542 * imageRc [O] Optional, output the image rect
1543 * textRc [O] Optional, output the text rect
1545 static UINT
BUTTON_CalcLayoutRects(const BUTTON_INFO
*infoPtr
, HDC hdc
, RECT
*labelRc
, RECT
*imageRc
, RECT
*textRc
)
1547 LONG style
= GetWindowLongW( infoPtr
->hwnd
, GWL_STYLE
);
1548 LONG ex_style
= GetWindowLongW( infoPtr
->hwnd
, GWL_EXSTYLE
);
1549 LONG split_style
= infoPtr
->imagelist
.himl
? BUTTON_ILStoBS(infoPtr
->imagelist
.uAlign
) : style
;
1550 WCHAR
*text
= get_button_text(infoPtr
);
1551 SIZE imageSize
= BUTTON_GetImageSize(infoPtr
);
1552 UINT dtStyle
= BUTTON_BStoDT(style
, ex_style
);
1553 RECT labelRect
, imageRect
, imageRectWithMargin
, textRect
;
1554 LONG imageMarginWidth
, imageMarginHeight
;
1555 const RECT
*textMargin
= BUTTON_GetTextMargin(infoPtr
);
1556 RECT emptyMargin
= {0};
1559 /* Calculate label rectangle according to label type */
1560 if ((imageSize
.cx
== 0 && imageSize
.cy
== 0) && (text
== NULL
|| text
[0] == '\0'))
1562 SetRectEmpty(labelRc
);
1563 SetRectEmpty(imageRc
);
1564 SetRectEmpty(textRc
);
1569 SetRect(&imageRect
, 0, 0, imageSize
.cx
, imageSize
.cy
);
1570 imageRectWithMargin
= imageRect
;
1571 if (infoPtr
->imagelist
.himl
)
1573 imageRectWithMargin
.top
-= infoPtr
->imagelist
.margin
.top
;
1574 imageRectWithMargin
.bottom
+= infoPtr
->imagelist
.margin
.bottom
;
1575 imageRectWithMargin
.left
-= infoPtr
->imagelist
.margin
.left
;
1576 imageRectWithMargin
.right
+= infoPtr
->imagelist
.margin
.right
;
1579 /* Show image only */
1580 if (show_image_only(infoPtr
))
1582 BUTTON_PositionRect(style
, labelRc
, &imageRect
,
1583 infoPtr
->imagelist
.himl
? &infoPtr
->imagelist
.margin
: &emptyMargin
);
1584 labelRect
= imageRect
;
1585 SetRectEmpty(&textRect
);
1590 maxTextWidth
= labelRc
->right
- labelRc
->left
;
1591 textRect
= BUTTON_GetTextRect(infoPtr
, hdc
, text
, maxTextWidth
);
1593 /* Show image and text */
1594 if (show_image_and_text(infoPtr
))
1596 RECT boundingLabelRect
, boundingImageRect
, boundingTextRect
;
1598 /* Get label rect */
1599 /* Image list may have different alignment than the button, use the whole rect for label in this case */
1600 if (infoPtr
->imagelist
.himl
)
1601 labelRect
= *labelRc
;
1604 /* Get a label bounding rectangle to position the label in the user specified label rectangle because
1605 * text and image need to align together. */
1606 boundingLabelRect
= BUTTON_GetBoundingLabelRect(split_style
, &textRect
, &imageRectWithMargin
);
1607 BUTTON_PositionRect(split_style
, labelRc
, &boundingLabelRect
, &emptyMargin
);
1608 labelRect
= boundingLabelRect
;
1611 /* When imagelist has center align, use the whole rect for imagelist and text */
1612 if(infoPtr
->imagelist
.himl
&& infoPtr
->imagelist
.uAlign
== BUTTON_IMAGELIST_ALIGN_CENTER
)
1614 boundingImageRect
= labelRect
;
1615 boundingTextRect
= labelRect
;
1616 BUTTON_PositionRect(split_style
, &boundingImageRect
, &imageRect
,
1617 infoPtr
->imagelist
.himl
? &infoPtr
->imagelist
.margin
: &emptyMargin
);
1618 /* Text doesn't use imagelist align */
1619 BUTTON_PositionRect(style
, &boundingTextRect
, &textRect
, textMargin
);
1623 /* Get image rect */
1624 /* Split the label rect to two halves as two bounding rectangles for image and text */
1625 boundingImageRect
= labelRect
;
1626 imageMarginWidth
= imageRectWithMargin
.right
- imageRectWithMargin
.left
;
1627 imageMarginHeight
= imageRectWithMargin
.bottom
- imageRectWithMargin
.top
;
1628 if ((split_style
& BS_CENTER
) == BS_RIGHT
)
1629 boundingImageRect
.left
= boundingImageRect
.right
- imageMarginWidth
;
1630 else if ((split_style
& BS_CENTER
) == BS_LEFT
)
1631 boundingImageRect
.right
= boundingImageRect
.left
+ imageMarginWidth
;
1632 else if ((split_style
& BS_VCENTER
) == BS_BOTTOM
)
1633 boundingImageRect
.top
= boundingImageRect
.bottom
- imageMarginHeight
;
1634 else if ((split_style
& BS_VCENTER
) == BS_TOP
)
1635 boundingImageRect
.bottom
= boundingImageRect
.top
+ imageMarginHeight
;
1637 boundingImageRect
.right
= boundingImageRect
.left
+ imageMarginWidth
;
1638 BUTTON_PositionRect(split_style
, &boundingImageRect
, &imageRect
,
1639 infoPtr
->imagelist
.himl
? &infoPtr
->imagelist
.margin
: &emptyMargin
);
1642 SubtractRect(&boundingTextRect
, &labelRect
, &boundingImageRect
);
1643 /* Text doesn't use imagelist align */
1644 BUTTON_PositionRect(style
, &boundingTextRect
, &textRect
, textMargin
);
1647 /* Show text only */
1650 if (get_button_type(style
) != BS_GROUPBOX
)
1651 BUTTON_PositionRect(style
, labelRc
, &textRect
, textMargin
);
1653 /* GroupBox is always top aligned */
1654 BUTTON_PositionRect((style
& ~BS_VCENTER
) | BS_TOP
, labelRc
, &textRect
, textMargin
);
1655 labelRect
= textRect
;
1656 SetRectEmpty(&imageRect
);
1661 CopyRect(labelRc
, &labelRect
);
1662 CopyRect(imageRc
, &imageRect
);
1663 CopyRect(textRc
, &textRect
);
1669 /**********************************************************************
1672 * Draw the button's image into the specified rectangle.
1674 static void BUTTON_DrawImage(const BUTTON_INFO
*infoPtr
, HDC hdc
, HBRUSH hbr
, UINT flags
, const RECT
*rect
)
1676 if (infoPtr
->imagelist
.himl
)
1678 int i
= (ImageList_GetImageCount(infoPtr
->imagelist
.himl
) == 1) ? 0 : get_draw_state(infoPtr
) - 1;
1680 ImageList_Draw(infoPtr
->imagelist
.himl
, i
, hdc
, rect
->left
, rect
->top
, ILD_NORMAL
);
1684 switch (infoPtr
->image_type
)
1690 flags
|= DST_BITMAP
;
1696 DrawStateW(hdc
, hbr
, NULL
, (LPARAM
)infoPtr
->u
.image
, 0, rect
->left
, rect
->top
,
1697 rect
->right
- rect
->left
, rect
->bottom
- rect
->top
, flags
);
1702 /**********************************************************************
1703 * BUTTON_DrawTextCallback
1705 * Callback function used by DrawStateW function.
1707 static BOOL CALLBACK
BUTTON_DrawTextCallback(HDC hdc
, LPARAM lp
, WPARAM wp
, int cx
, int cy
)
1711 SetRect(&rc
, 0, 0, cx
, cy
);
1712 DrawTextW(hdc
, (LPCWSTR
)lp
, -1, &rc
, (UINT
)wp
);
1716 /**********************************************************************
1719 * Common function for drawing button label.
1722 * 1. When BS_SINGLELINE is specified and text contains '\t', '\n' or '\r' in the middle, they are rendered as
1723 * squares now whereas they should be ignored.
1724 * 2. When BS_MULTILINE is specified and text contains space in the middle, the space mistakenly be rendered as newline.
1726 static void BUTTON_DrawLabel(const BUTTON_INFO
*infoPtr
, HDC hdc
, UINT dtFlags
, const RECT
*imageRect
,
1727 const RECT
*textRect
)
1730 UINT flags
= IsWindowEnabled(infoPtr
->hwnd
) ? DSS_NORMAL
: DSS_DISABLED
;
1731 LONG style
= GetWindowLongW( infoPtr
->hwnd
, GWL_STYLE
);
1734 /* FIXME: To draw disabled label in Win31 look-and-feel, we probably
1735 * must use DSS_MONO flag and COLOR_GRAYTEXT brush (or maybe DSS_UNION).
1736 * I don't have Win31 on hand to verify that, so I leave it as is.
1739 if ((style
& BS_PUSHLIKE
) && (infoPtr
->state
& BST_INDETERMINATE
))
1741 hbr
= GetSysColorBrush(COLOR_GRAYTEXT
);
1745 if (show_image(infoPtr
)) BUTTON_DrawImage(infoPtr
, hdc
, hbr
, flags
, imageRect
);
1746 if (show_image_only(infoPtr
)) return;
1748 /* DST_COMPLEX -- is 0 */
1749 if (!(text
= get_button_text(infoPtr
))) return;
1750 DrawStateW(hdc
, hbr
, BUTTON_DrawTextCallback
, (LPARAM
)text
, dtFlags
, textRect
->left
, textRect
->top
,
1751 textRect
->right
- textRect
->left
, textRect
->bottom
- textRect
->top
, flags
);
1755 /**********************************************************************
1756 * Push Button Functions
1758 static void PB_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
)
1760 RECT rc
, labelRect
, imageRect
, textRect
;
1761 UINT dtFlags
, uState
;
1765 COLORREF oldTxtColor
;
1769 LONG state
= infoPtr
->state
;
1770 LONG style
= GetWindowLongW( infoPtr
->hwnd
, GWL_STYLE
);
1771 BOOL pushedState
= (state
& BST_PUSHED
);
1775 GetClientRect( infoPtr
->hwnd
, &rc
);
1777 /* Send WM_CTLCOLOR to allow changing the font (the colors are fixed) */
1778 if ((hFont
= infoPtr
->font
)) SelectObject( hDC
, hFont
);
1779 parent
= GetParent(infoPtr
->hwnd
);
1780 if (!parent
) parent
= infoPtr
->hwnd
;
1781 SendMessageW( parent
, WM_CTLCOLORBTN
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
1783 hrgn
= set_control_clipping( hDC
, &rc
);
1785 hpen
= CreatePen( PS_SOLID
, 1, GetSysColor(COLOR_WINDOWFRAME
));
1786 hOldPen
= SelectObject(hDC
, hpen
);
1787 hOldBrush
= SelectObject(hDC
,GetSysColorBrush(COLOR_BTNFACE
));
1788 oldBkMode
= SetBkMode(hDC
, TRANSPARENT
);
1790 init_custom_draw(&nmcd
, infoPtr
, hDC
, &rc
);
1792 /* Send erase notifications */
1793 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
1794 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
1796 if (get_button_type(style
) == BS_DEFPUSHBUTTON
)
1798 if (action
!= ODA_FOCUS
)
1799 Rectangle(hDC
, rc
.left
, rc
.top
, rc
.right
, rc
.bottom
);
1800 InflateRect( &rc
, -1, -1 );
1803 /* Skip the frame drawing if only focus has changed */
1804 if (action
!= ODA_FOCUS
)
1806 uState
= DFCS_BUTTONPUSH
;
1808 if (style
& BS_FLAT
)
1809 uState
|= DFCS_MONO
;
1810 else if (pushedState
)
1812 if (get_button_type(style
) == BS_DEFPUSHBUTTON
)
1813 uState
|= DFCS_FLAT
;
1815 uState
|= DFCS_PUSHED
;
1818 if (state
& (BST_CHECKED
| BST_INDETERMINATE
))
1819 uState
|= DFCS_CHECKED
;
1821 DrawFrameControl( hDC
, &rc
, DFC_BUTTON
, uState
);
1824 if (cdrf
& CDRF_NOTIFYPOSTERASE
)
1826 nmcd
.dwDrawStage
= CDDS_POSTERASE
;
1827 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
1830 /* Send paint notifications */
1831 nmcd
.dwDrawStage
= CDDS_PREPAINT
;
1832 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
1833 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
1835 if (!(cdrf
& CDRF_DOERASE
) && action
!= ODA_FOCUS
)
1837 /* draw button label */
1839 /* Shrink label rect at all sides by 2 so that the content won't touch the surrounding frame */
1840 InflateRect(&labelRect
, -2, -2);
1841 dtFlags
= BUTTON_CalcLayoutRects(infoPtr
, hDC
, &labelRect
, &imageRect
, &textRect
);
1843 if (dtFlags
!= (UINT
)-1L)
1845 if (pushedState
) OffsetRect(&labelRect
, 1, 1);
1847 oldTxtColor
= SetTextColor( hDC
, GetSysColor(COLOR_BTNTEXT
) );
1849 BUTTON_DrawLabel(infoPtr
, hDC
, dtFlags
, &imageRect
, &textRect
);
1851 SetTextColor( hDC
, oldTxtColor
);
1855 if (cdrf
& CDRF_NOTIFYPOSTPAINT
)
1857 nmcd
.dwDrawStage
= CDDS_POSTPAINT
;
1858 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
1860 if ((cdrf
& CDRF_SKIPPOSTPAINT
) || dtFlags
== (UINT
)-1L) goto cleanup
;
1862 if (action
== ODA_FOCUS
|| (state
& BST_FOCUS
))
1864 InflateRect( &rc
, -2, -2 );
1865 DrawFocusRect( hDC
, &rc
);
1869 SelectObject( hDC
, hOldPen
);
1870 SelectObject( hDC
, hOldBrush
);
1871 SetBkMode(hDC
, oldBkMode
);
1872 SelectClipRgn( hDC
, hrgn
);
1873 if (hrgn
) DeleteObject( hrgn
);
1874 DeleteObject( hpen
);
1877 /**********************************************************************
1878 * Check Box & Radio Button Functions
1881 static void CB_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
)
1883 RECT rbox
, labelRect
, imageRect
, textRect
, client
;
1885 int delta
, text_offset
, checkBoxWidth
, checkBoxHeight
;
1890 LONG state
= infoPtr
->state
;
1891 LONG style
= GetWindowLongW( infoPtr
->hwnd
, GWL_STYLE
);
1892 LONG ex_style
= GetWindowLongW( infoPtr
->hwnd
, GWL_EXSTYLE
);
1896 if (style
& BS_PUSHLIKE
)
1898 PB_Paint( infoPtr
, hDC
, action
);
1902 GetClientRect(infoPtr
->hwnd
, &client
);
1903 rbox
= labelRect
= client
;
1905 checkBoxWidth
= 12 * GetDpiForWindow( infoPtr
->hwnd
) / 96 + 1;
1906 checkBoxHeight
= 12 * GetDpiForWindow( infoPtr
->hwnd
) / 96 + 1;
1908 if ((hFont
= infoPtr
->font
)) SelectObject( hDC
, hFont
);
1909 GetCharWidthW( hDC
, '0', '0', &text_offset
);
1912 parent
= GetParent(infoPtr
->hwnd
);
1913 if (!parent
) parent
= infoPtr
->hwnd
;
1914 hBrush
= (HBRUSH
)SendMessageW(parent
, WM_CTLCOLORSTATIC
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
1915 if (!hBrush
) /* did the app forget to call defwindowproc ? */
1916 hBrush
= (HBRUSH
)DefWindowProcW(parent
, WM_CTLCOLORSTATIC
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
1917 hrgn
= set_control_clipping( hDC
, &client
);
1919 if (style
& BS_LEFTTEXT
|| ex_style
& WS_EX_RIGHT
)
1921 labelRect
.right
-= checkBoxWidth
+ text_offset
;
1922 rbox
.left
= rbox
.right
- checkBoxWidth
;
1926 labelRect
.left
+= checkBoxWidth
+ text_offset
;
1927 rbox
.right
= checkBoxWidth
;
1930 init_custom_draw(&nmcd
, infoPtr
, hDC
, &client
);
1932 /* Send erase notifications */
1933 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
1934 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
1936 /* Since WM_ERASEBKGND does nothing, first prepare background */
1937 if (action
== ODA_SELECT
) FillRect( hDC
, &rbox
, hBrush
);
1938 if (action
== ODA_DRAWENTIRE
) FillRect( hDC
, &client
, hBrush
);
1939 if (cdrf
& CDRF_NOTIFYPOSTERASE
)
1941 nmcd
.dwDrawStage
= CDDS_POSTERASE
;
1942 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
1947 dtFlags
= BUTTON_CalcLayoutRects(infoPtr
, hDC
, &labelRect
, &imageRect
, &textRect
);
1949 /* Only adjust rbox when rtext is valid */
1950 if (dtFlags
!= (UINT
)-1L)
1952 rbox
.top
= labelRect
.top
;
1953 rbox
.bottom
= labelRect
.bottom
;
1956 /* Send paint notifications */
1957 nmcd
.dwDrawStage
= CDDS_PREPAINT
;
1958 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
1959 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
1961 /* Draw the check-box bitmap */
1962 if (!(cdrf
& CDRF_DOERASE
))
1964 if (action
== ODA_DRAWENTIRE
|| action
== ODA_SELECT
)
1968 if ((get_button_type(style
) == BS_RADIOBUTTON
) ||
1969 (get_button_type(style
) == BS_AUTORADIOBUTTON
)) flags
= DFCS_BUTTONRADIO
;
1970 else if (state
& BST_INDETERMINATE
) flags
= DFCS_BUTTON3STATE
;
1971 else flags
= DFCS_BUTTONCHECK
;
1973 if (state
& (BST_CHECKED
| BST_INDETERMINATE
)) flags
|= DFCS_CHECKED
;
1974 if (state
& BST_PUSHED
) flags
|= DFCS_PUSHED
;
1975 if (style
& WS_DISABLED
) flags
|= DFCS_INACTIVE
;
1977 /* rbox must have the correct height */
1978 delta
= rbox
.bottom
- rbox
.top
- checkBoxHeight
;
1980 if ((style
& BS_VCENTER
) == BS_TOP
)
1983 rbox
.bottom
= rbox
.top
+ checkBoxHeight
;
1986 rbox
.top
-= -delta
/ 2 + 1;
1987 rbox
.bottom
= rbox
.top
+ checkBoxHeight
;
1990 else if ((style
& BS_VCENTER
) == BS_BOTTOM
)
1993 rbox
.top
= rbox
.bottom
- checkBoxHeight
;
1996 rbox
.bottom
+= -delta
/ 2 + 1;
1997 rbox
.top
= rbox
.bottom
- checkBoxHeight
;
2004 int ofs
= delta
/ 2;
2005 rbox
.bottom
-= ofs
+ 1;
2006 rbox
.top
= rbox
.bottom
- checkBoxHeight
;
2010 int ofs
= -delta
/ 2;
2011 rbox
.top
-= ofs
+ 1;
2012 rbox
.bottom
= rbox
.top
+ checkBoxHeight
;
2016 DrawFrameControl(hDC
, &rbox
, DFC_BUTTON
, flags
);
2019 if (dtFlags
!= (UINT
)-1L) /* Something to draw */
2020 if (action
== ODA_DRAWENTIRE
) BUTTON_DrawLabel(infoPtr
, hDC
, dtFlags
, &imageRect
, &textRect
);
2023 if (cdrf
& CDRF_NOTIFYPOSTPAINT
)
2025 nmcd
.dwDrawStage
= CDDS_POSTPAINT
;
2026 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2028 if ((cdrf
& CDRF_SKIPPOSTPAINT
) || dtFlags
== (UINT
)-1L) goto cleanup
;
2031 if (action
== ODA_FOCUS
|| (state
& BST_FOCUS
))
2035 IntersectRect(&labelRect
, &labelRect
, &client
);
2036 DrawFocusRect(hDC
, &labelRect
);
2040 SelectClipRgn( hDC
, hrgn
);
2041 if (hrgn
) DeleteObject( hrgn
);
2045 /**********************************************************************
2046 * BUTTON_CheckAutoRadioButton
2048 * hwnd is checked, uncheck every other auto radio button in group
2050 static void BUTTON_CheckAutoRadioButton( HWND hwnd
)
2052 HWND parent
, sibling
, start
;
2054 parent
= GetParent(hwnd
);
2055 /* make sure that starting control is not disabled or invisible */
2056 start
= sibling
= GetNextDlgGroupItem( parent
, hwnd
, TRUE
);
2059 if (!sibling
) break;
2060 if ((hwnd
!= sibling
) &&
2061 ((GetWindowLongW( sibling
, GWL_STYLE
) & BS_TYPEMASK
) == BS_AUTORADIOBUTTON
))
2062 SendMessageW( sibling
, BM_SETCHECK
, BST_UNCHECKED
, 0 );
2063 sibling
= GetNextDlgGroupItem( parent
, sibling
, FALSE
);
2064 } while (sibling
!= start
);
2068 /**********************************************************************
2069 * Group Box Functions
2072 static void GB_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
)
2074 RECT labelRect
, imageRect
, textRect
, rcFrame
;
2079 LONG style
= GetWindowLongW( infoPtr
->hwnd
, GWL_STYLE
);
2083 if ((hFont
= infoPtr
->font
)) SelectObject( hDC
, hFont
);
2084 /* GroupBox acts like static control, so it sends CTLCOLORSTATIC */
2085 parent
= GetParent(infoPtr
->hwnd
);
2086 if (!parent
) parent
= infoPtr
->hwnd
;
2087 hbr
= (HBRUSH
)SendMessageW(parent
, WM_CTLCOLORSTATIC
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
2088 if (!hbr
) /* did the app forget to call defwindowproc ? */
2089 hbr
= (HBRUSH
)DefWindowProcW(parent
, WM_CTLCOLORSTATIC
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
2090 GetClientRect(infoPtr
->hwnd
, &labelRect
);
2091 rcFrame
= labelRect
;
2092 hrgn
= set_control_clipping(hDC
, &labelRect
);
2094 GetTextMetricsW (hDC
, &tm
);
2095 rcFrame
.top
+= (tm
.tmHeight
/ 2) - 1;
2096 DrawEdge (hDC
, &rcFrame
, EDGE_ETCHED
, BF_RECT
| ((style
& BS_FLAT
) ? BF_FLAT
: 0));
2098 InflateRect(&labelRect
, -7, 1);
2099 dtFlags
= BUTTON_CalcLayoutRects(infoPtr
, hDC
, &labelRect
, &imageRect
, &textRect
);
2101 if (dtFlags
!= (UINT
)-1)
2103 /* Because buttons have CS_PARENTDC class style, there is a chance
2104 * that label will be drawn out of client rect.
2105 * But Windows doesn't clip label's rect, so do I.
2108 /* There is 1-pixel margin at the left, right, and bottom */
2112 FillRect(hDC
, &labelRect
, hbr
);
2117 BUTTON_DrawLabel(infoPtr
, hDC
, dtFlags
, &imageRect
, &textRect
);
2119 SelectClipRgn( hDC
, hrgn
);
2120 if (hrgn
) DeleteObject( hrgn
);
2124 /**********************************************************************
2125 * User Button Functions
2128 static void UB_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
)
2135 LONG state
= infoPtr
->state
;
2138 GetClientRect( infoPtr
->hwnd
, &rc
);
2140 if ((hFont
= infoPtr
->font
)) SelectObject( hDC
, hFont
);
2142 parent
= GetParent(infoPtr
->hwnd
);
2143 if (!parent
) parent
= infoPtr
->hwnd
;
2144 hBrush
= (HBRUSH
)SendMessageW(parent
, WM_CTLCOLORBTN
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
2145 if (!hBrush
) /* did the app forget to call defwindowproc ? */
2146 hBrush
= (HBRUSH
)DefWindowProcW(parent
, WM_CTLCOLORBTN
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
2148 if (action
== ODA_FOCUS
|| (state
& BST_FOCUS
))
2150 init_custom_draw(&nmcd
, infoPtr
, hDC
, &rc
);
2152 /* Send erase notifications */
2153 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2154 if (cdrf
& CDRF_SKIPDEFAULT
) goto notify
;
2157 FillRect( hDC
, &rc
, hBrush
);
2158 if (action
== ODA_FOCUS
|| (state
& BST_FOCUS
))
2160 if (cdrf
& CDRF_NOTIFYPOSTERASE
)
2162 nmcd
.dwDrawStage
= CDDS_POSTERASE
;
2163 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2166 /* Send paint notifications */
2167 nmcd
.dwDrawStage
= CDDS_PREPAINT
;
2168 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2169 if (cdrf
& CDRF_SKIPDEFAULT
) goto notify
;
2170 if (cdrf
& CDRF_NOTIFYPOSTPAINT
)
2172 nmcd
.dwDrawStage
= CDDS_POSTPAINT
;
2173 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2176 if (!(cdrf
& CDRF_SKIPPOSTPAINT
))
2177 DrawFocusRect( hDC
, &rc
);
2184 BUTTON_NOTIFY_PARENT( infoPtr
->hwnd
, (state
& BST_FOCUS
) ? BN_SETFOCUS
: BN_KILLFOCUS
);
2188 BUTTON_NOTIFY_PARENT( infoPtr
->hwnd
, (state
& BST_PUSHED
) ? BN_HILITE
: BN_UNHILITE
);
2197 /**********************************************************************
2198 * Ownerdrawn Button Functions
2201 static void OB_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
)
2203 LONG state
= infoPtr
->state
;
2205 LONG_PTR id
= GetWindowLongPtrW( infoPtr
->hwnd
, GWLP_ID
);
2210 dis
.CtlType
= ODT_BUTTON
;
2213 dis
.itemAction
= action
;
2214 dis
.itemState
= ((state
& BST_FOCUS
) ? ODS_FOCUS
: 0) |
2215 ((state
& BST_PUSHED
) ? ODS_SELECTED
: 0) |
2216 (IsWindowEnabled(infoPtr
->hwnd
) ? 0: ODS_DISABLED
);
2217 dis
.hwndItem
= infoPtr
->hwnd
;
2220 GetClientRect( infoPtr
->hwnd
, &dis
.rcItem
);
2222 if ((hFont
= infoPtr
->font
)) SelectObject( hDC
, hFont
);
2223 parent
= GetParent(infoPtr
->hwnd
);
2224 if (!parent
) parent
= infoPtr
->hwnd
;
2225 SendMessageW( parent
, WM_CTLCOLORBTN
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
2227 hrgn
= set_control_clipping( hDC
, &dis
.rcItem
);
2229 SendMessageW( GetParent(infoPtr
->hwnd
), WM_DRAWITEM
, id
, (LPARAM
)&dis
);
2230 SelectClipRgn( hDC
, hrgn
);
2231 if (hrgn
) DeleteObject( hrgn
);
2235 /**********************************************************************
2236 * Split Button Functions
2238 static void SB_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
)
2240 LONG style
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
2241 LONG state
= infoPtr
->state
;
2242 UINT dtFlags
= (UINT
)-1L;
2244 RECT rc
, push_rect
, dropdown_rect
;
2253 GetClientRect(infoPtr
->hwnd
, &rc
);
2255 /* Send WM_CTLCOLOR to allow changing the font (the colors are fixed) */
2256 if (infoPtr
->font
) SelectObject(hDC
, infoPtr
->font
);
2257 if (!(parent
= GetParent(infoPtr
->hwnd
))) parent
= infoPtr
->hwnd
;
2258 SendMessageW(parent
, WM_CTLCOLORBTN
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
2260 hrgn
= set_control_clipping(hDC
, &rc
);
2262 pen
= CreatePen(PS_SOLID
, 1, GetSysColor(COLOR_WINDOWFRAME
));
2263 old_pen
= SelectObject(hDC
, pen
);
2264 old_brush
= SelectObject(hDC
, GetSysColorBrush(COLOR_BTNFACE
));
2265 old_bk_mode
= SetBkMode(hDC
, TRANSPARENT
);
2267 init_custom_draw(&nmcd
, infoPtr
, hDC
, &rc
);
2269 /* Send erase notifications */
2270 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2271 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
2273 if (get_button_type(style
) == BS_DEFSPLITBUTTON
)
2275 if (action
!= ODA_FOCUS
)
2276 Rectangle(hDC
, rc
.left
, rc
.top
, rc
.right
, rc
.bottom
);
2277 InflateRect(&rc
, -1, -1);
2278 /* The split will now be off by 1 pixel, but
2279 that's exactly what Windows does as well */
2282 get_split_button_rects(infoPtr
, &rc
, &push_rect
, &dropdown_rect
);
2283 if (infoPtr
->split_style
& BCSS_NOSPLIT
)
2286 /* Skip the frame drawing if only focus has changed */
2287 if (action
!= ODA_FOCUS
)
2289 UINT flags
= DFCS_BUTTONPUSH
;
2291 if (style
& BS_FLAT
) flags
|= DFCS_MONO
;
2292 else if (state
& BST_PUSHED
)
2293 flags
|= (get_button_type(style
) == BS_DEFSPLITBUTTON
)
2294 ? DFCS_FLAT
: DFCS_PUSHED
;
2296 if (state
& (BST_CHECKED
| BST_INDETERMINATE
))
2297 flags
|= DFCS_CHECKED
;
2299 if (infoPtr
->split_style
& BCSS_NOSPLIT
)
2300 DrawFrameControl(hDC
, &push_rect
, DFC_BUTTON
, flags
);
2303 UINT dropdown_flags
= flags
& ~DFCS_CHECKED
;
2305 if (state
& BST_DROPDOWNPUSHED
)
2306 dropdown_flags
= (dropdown_flags
& ~DFCS_FLAT
) | DFCS_PUSHED
;
2308 /* Adjust for shadow and draw order so it looks properly */
2309 if (infoPtr
->split_style
& BCSS_ALIGNLEFT
)
2311 dropdown_rect
.right
++;
2312 DrawFrameControl(hDC
, &dropdown_rect
, DFC_BUTTON
, dropdown_flags
);
2313 dropdown_rect
.right
--;
2314 DrawFrameControl(hDC
, &push_rect
, DFC_BUTTON
, flags
);
2319 DrawFrameControl(hDC
, &push_rect
, DFC_BUTTON
, flags
);
2321 DrawFrameControl(hDC
, &dropdown_rect
, DFC_BUTTON
, dropdown_flags
);
2326 if (cdrf
& CDRF_NOTIFYPOSTERASE
)
2328 nmcd
.dwDrawStage
= CDDS_POSTERASE
;
2329 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2332 /* Send paint notifications */
2333 nmcd
.dwDrawStage
= CDDS_PREPAINT
;
2334 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2335 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
2337 /* Shrink push button rect so that the content won't touch the surrounding frame */
2338 InflateRect(&push_rect
, -2, -2);
2340 if (!(cdrf
& CDRF_DOERASE
) && action
!= ODA_FOCUS
)
2342 COLORREF old_color
= SetTextColor(hDC
, GetSysColor(COLOR_BTNTEXT
));
2343 RECT label_rect
= push_rect
, image_rect
, text_rect
;
2345 dtFlags
= BUTTON_CalcLayoutRects(infoPtr
, hDC
, &label_rect
, &image_rect
, &text_rect
);
2347 if (dtFlags
!= (UINT
)-1L)
2348 BUTTON_DrawLabel(infoPtr
, hDC
, dtFlags
, &image_rect
, &text_rect
);
2350 draw_split_button_dropdown_glyph(infoPtr
, hDC
, &dropdown_rect
);
2351 SetTextColor(hDC
, old_color
);
2354 if (cdrf
& CDRF_NOTIFYPOSTPAINT
)
2356 nmcd
.dwDrawStage
= CDDS_POSTPAINT
;
2357 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2359 if ((cdrf
& CDRF_SKIPPOSTPAINT
) || dtFlags
== (UINT
)-1L) goto cleanup
;
2361 if (action
== ODA_FOCUS
|| (state
& BST_FOCUS
))
2362 DrawFocusRect(hDC
, &push_rect
);
2365 SelectObject(hDC
, old_pen
);
2366 SelectObject(hDC
, old_brush
);
2367 SetBkMode(hDC
, old_bk_mode
);
2368 SelectClipRgn(hDC
, hrgn
);
2369 if (hrgn
) DeleteObject(hrgn
);
2373 /* Given the full button rect of the split button, retrieve the push part and the dropdown part */
2374 static inline void get_split_button_rects(const BUTTON_INFO
*infoPtr
, const RECT
*button_rect
,
2375 RECT
*push_rect
, RECT
*dropdown_rect
)
2377 *push_rect
= *dropdown_rect
= *button_rect
;
2379 /* The dropdown takes priority if the client rect is too small, it will only have a dropdown */
2380 if (infoPtr
->split_style
& BCSS_ALIGNLEFT
)
2382 dropdown_rect
->right
= min(button_rect
->left
+ infoPtr
->glyph_size
.cx
, button_rect
->right
);
2383 push_rect
->left
= dropdown_rect
->right
;
2387 dropdown_rect
->left
= max(button_rect
->right
- infoPtr
->glyph_size
.cx
, button_rect
->left
);
2388 push_rect
->right
= dropdown_rect
->left
;
2392 /* Notify the parent if the point is within the dropdown and return TRUE (always notify if NULL) */
2393 static BOOL
notify_split_button_dropdown(const BUTTON_INFO
*infoPtr
, const POINT
*pt
, HWND hwnd
)
2397 GetClientRect(hwnd
, &nmbcd
.rcButton
);
2400 RECT push_rect
, dropdown_rect
;
2402 get_split_button_rects(infoPtr
, &nmbcd
.rcButton
, &push_rect
, &dropdown_rect
);
2403 if (!PtInRect(&dropdown_rect
, *pt
))
2406 /* If it's already down (set manually via BCM_SETDROPDOWNSTATE), fake the notify */
2407 if (infoPtr
->state
& BST_DROPDOWNPUSHED
)
2410 SendMessageW(hwnd
, BCM_SETDROPDOWNSTATE
, TRUE
, 0);
2412 nmbcd
.hdr
.hwndFrom
= hwnd
;
2413 nmbcd
.hdr
.idFrom
= GetWindowLongPtrW(hwnd
, GWLP_ID
);
2414 nmbcd
.hdr
.code
= BCN_DROPDOWN
;
2415 SendMessageW(GetParent(hwnd
), WM_NOTIFY
, nmbcd
.hdr
.idFrom
, (LPARAM
)&nmbcd
);
2417 SendMessageW(hwnd
, BCM_SETDROPDOWNSTATE
, FALSE
, 0);
2421 /* Draw the split button dropdown glyph or image */
2422 static void draw_split_button_dropdown_glyph(const BUTTON_INFO
*infoPtr
, HDC hdc
, RECT
*rect
)
2424 if (infoPtr
->split_style
& BCSS_IMAGE
)
2428 /* When the glyph is an image list, Windows is very buggy with BCSS_STRETCH,
2429 positions it weirdly and doesn't even stretch it, but instead extends the
2430 image, leaking into other images in the list (or black if none). Instead,
2431 we'll ignore this and just position it at center as without BCSS_STRETCH. */
2432 if (!ImageList_GetIconSize(infoPtr
->glyph
, &w
, &h
)) return;
2434 ImageList_Draw(infoPtr
->glyph
,
2435 (ImageList_GetImageCount(infoPtr
->glyph
) == 1) ? 0 : get_draw_state(infoPtr
) - 1,
2436 hdc
, rect
->left
+ (rect
->right
- rect
->left
- w
) / 2,
2437 rect
->top
+ (rect
->bottom
- rect
->top
- h
) / 2, ILD_NORMAL
);
2439 else if (infoPtr
->glyph_size
.cy
>= 0)
2441 /* infoPtr->glyph is a character code from Marlett */
2442 HFONT font
, old_font
;
2443 LOGFONTW logfont
= { 0, 0, 0, 0, FW_NORMAL
, 0, 0, 0, SYMBOL_CHARSET
, 0, 0, 0, 0,
2445 if (infoPtr
->glyph_size
.cy
)
2447 /* BCSS_STRETCH preserves aspect ratio, uses minimum as size */
2448 if (infoPtr
->split_style
& BCSS_STRETCH
)
2449 logfont
.lfHeight
= min(infoPtr
->glyph_size
.cx
, infoPtr
->glyph_size
.cy
);
2452 logfont
.lfWidth
= infoPtr
->glyph_size
.cx
;
2453 logfont
.lfHeight
= infoPtr
->glyph_size
.cy
;
2456 else logfont
.lfHeight
= infoPtr
->glyph_size
.cx
;
2458 if ((font
= CreateFontIndirectW(&logfont
)))
2460 old_font
= SelectObject(hdc
, font
);
2461 DrawTextW(hdc
, (const WCHAR
*)&infoPtr
->glyph
, 1, rect
,
2462 DT_CENTER
| DT_VCENTER
| DT_SINGLELINE
| DT_NOCLIP
| DT_NOPREFIX
);
2463 SelectObject(hdc
, old_font
);
2470 /**********************************************************************
2471 * Command Link Functions
2473 static void CL_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
)
2475 LONG style
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
2476 LONG state
= infoPtr
->state
;
2478 RECT rc
, content_rect
;
2487 GetClientRect(infoPtr
->hwnd
, &rc
);
2489 /* Command Links are not affected by the button's font, and are based
2490 on the default message font. Furthermore, they are not affected by
2491 any of the alignment styles (and always align with the top-left). */
2492 if (!(parent
= GetParent(infoPtr
->hwnd
))) parent
= infoPtr
->hwnd
;
2493 SendMessageW(parent
, WM_CTLCOLORBTN
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
2495 hrgn
= set_control_clipping(hDC
, &rc
);
2497 pen
= CreatePen(PS_SOLID
, 1, GetSysColor(COLOR_WINDOWFRAME
));
2498 old_pen
= SelectObject(hDC
, pen
);
2499 old_brush
= SelectObject(hDC
, GetSysColorBrush(COLOR_BTNFACE
));
2500 old_bk_mode
= SetBkMode(hDC
, TRANSPARENT
);
2502 init_custom_draw(&nmcd
, infoPtr
, hDC
, &rc
);
2504 /* Send erase notifications */
2505 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2506 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
2509 if (get_button_type(style
) == BS_DEFCOMMANDLINK
)
2511 if (action
!= ODA_FOCUS
)
2512 Rectangle(hDC
, rc
.left
, rc
.top
, rc
.right
, rc
.bottom
);
2513 InflateRect(&rc
, -1, -1);
2516 /* Skip the frame drawing if only focus has changed */
2517 if (action
!= ODA_FOCUS
)
2519 if (!(state
& (BST_HOT
| BST_PUSHED
| BST_CHECKED
| BST_INDETERMINATE
)))
2520 FillRect(hDC
, &rc
, GetSysColorBrush(COLOR_BTNFACE
));
2523 UINT flags
= DFCS_BUTTONPUSH
;
2525 if (style
& BS_FLAT
) flags
|= DFCS_MONO
;
2526 else if (state
& BST_PUSHED
) flags
|= DFCS_PUSHED
;
2528 if (state
& (BST_CHECKED
| BST_INDETERMINATE
))
2529 flags
|= DFCS_CHECKED
;
2530 DrawFrameControl(hDC
, &rc
, DFC_BUTTON
, flags
);
2534 if (cdrf
& CDRF_NOTIFYPOSTERASE
)
2536 nmcd
.dwDrawStage
= CDDS_POSTERASE
;
2537 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2540 /* Send paint notifications */
2541 nmcd
.dwDrawStage
= CDDS_PREPAINT
;
2542 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2543 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
2545 if (!(cdrf
& CDRF_DOERASE
) && action
!= ODA_FOCUS
)
2547 UINT flags
= IsWindowEnabled(infoPtr
->hwnd
) ? DSS_NORMAL
: DSS_DISABLED
;
2548 COLORREF old_color
= SetTextColor(hDC
, GetSysColor(flags
== DSS_NORMAL
?
2549 COLOR_BTNTEXT
: COLOR_GRAYTEXT
));
2550 HIMAGELIST defimg
= NULL
;
2551 NONCLIENTMETRICSW ncm
;
2555 /* Command Links ignore the margins of the image list or its alignment */
2556 if (infoPtr
->u
.image
|| infoPtr
->imagelist
.himl
)
2557 img_size
= BUTTON_GetImageSize(infoPtr
);
2560 img_size
.cx
= img_size
.cy
= command_link_defglyph_size
;
2561 defimg
= ImageList_LoadImageW(COMCTL32_hModule
, (LPCWSTR
)MAKEINTRESOURCE(IDB_CMDLINK
),
2562 img_size
.cx
, 3, CLR_NONE
, IMAGE_BITMAP
, LR_CREATEDIBSECTION
);
2565 /* Shrink rect by the command link margin, except on bottom (just the frame) */
2566 InflateRect(&content_rect
, -command_link_margin
, -command_link_margin
);
2567 content_rect
.bottom
+= command_link_margin
- 2;
2569 ncm
.cbSize
= sizeof(ncm
);
2570 if (SystemParametersInfoW(SPI_GETNONCLIENTMETRICS
, sizeof(ncm
), &ncm
, 0))
2572 LONG note_weight
= ncm
.lfMessageFont
.lfWeight
;
2573 RECT r
= content_rect
;
2577 if (img_size
.cx
) r
.left
+= img_size
.cx
+ command_link_margin
;
2580 ncm
.lfMessageFont
.lfWeight
= FW_BOLD
;
2581 if ((font
= CreateFontIndirectW(&ncm
.lfMessageFont
)))
2583 if ((text
= get_button_text(infoPtr
)))
2585 SelectObject(hDC
, font
);
2586 txt_h
= DrawTextW(hDC
, text
, -1, &r
,
2587 DT_TOP
| DT_LEFT
| DT_WORDBREAK
| DT_END_ELLIPSIS
);
2594 ncm
.lfMessageFont
.lfWeight
= note_weight
;
2595 if (infoPtr
->note
&& (font
= CreateFontIndirectW(&ncm
.lfMessageFont
)))
2598 SelectObject(hDC
, font
);
2599 DrawTextW(hDC
, infoPtr
->note
, infoPtr
->note_length
, &r
,
2600 DT_TOP
| DT_LEFT
| DT_WORDBREAK
| DT_NOPREFIX
);
2605 /* Position the image at the vertical center of the drawn text (not note) */
2606 txt_h
= min(txt_h
, content_rect
.bottom
- content_rect
.top
);
2607 if (img_size
.cy
< txt_h
) content_rect
.top
+= (txt_h
- img_size
.cy
) / 2;
2609 content_rect
.right
= content_rect
.left
+ img_size
.cx
;
2610 content_rect
.bottom
= content_rect
.top
+ img_size
.cy
;
2615 if (flags
== DSS_DISABLED
) i
= 2;
2616 else if (state
& BST_HOT
) i
= 1;
2618 ImageList_Draw(defimg
, i
, hDC
, content_rect
.left
, content_rect
.top
, ILD_NORMAL
);
2619 ImageList_Destroy(defimg
);
2622 BUTTON_DrawImage(infoPtr
, hDC
, NULL
, flags
, &content_rect
);
2624 SetTextColor(hDC
, old_color
);
2627 if (cdrf
& CDRF_NOTIFYPOSTPAINT
)
2629 nmcd
.dwDrawStage
= CDDS_POSTPAINT
;
2630 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2632 if (cdrf
& CDRF_SKIPPOSTPAINT
) goto cleanup
;
2634 if (action
== ODA_FOCUS
|| (state
& BST_FOCUS
))
2636 InflateRect(&rc
, -2, -2);
2637 DrawFocusRect(hDC
, &rc
);
2641 SelectObject(hDC
, old_pen
);
2642 SelectObject(hDC
, old_brush
);
2643 SetBkMode(hDC
, old_bk_mode
);
2644 SelectClipRgn(hDC
, hrgn
);
2645 if (hrgn
) DeleteObject(hrgn
);
2650 /**********************************************************************
2651 * Themed Paint Functions
2653 static void PB_ThemedPaint(HTHEME theme
, const BUTTON_INFO
*infoPtr
, HDC hDC
, int state
, UINT dtFlags
, BOOL focused
)
2655 RECT bgRect
, textRect
;
2656 HFONT font
= infoPtr
->font
;
2657 HFONT hPrevFont
= font
? SelectObject(hDC
, font
) : NULL
;
2663 GetClientRect(infoPtr
->hwnd
, &bgRect
);
2664 GetThemeBackgroundContentRect(theme
, hDC
, BP_PUSHBUTTON
, state
, &bgRect
, &textRect
);
2665 init_custom_draw(&nmcd
, infoPtr
, hDC
, &bgRect
);
2667 parent
= GetParent(infoPtr
->hwnd
);
2668 if (!parent
) parent
= infoPtr
->hwnd
;
2670 /* Send erase notifications */
2671 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2672 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
2674 if (IsThemeBackgroundPartiallyTransparent(theme
, BP_PUSHBUTTON
, state
))
2675 DrawThemeParentBackground(infoPtr
->hwnd
, hDC
, NULL
);
2676 DrawThemeBackground(theme
, hDC
, BP_PUSHBUTTON
, state
, &bgRect
, NULL
);
2678 if (cdrf
& CDRF_NOTIFYPOSTERASE
)
2680 nmcd
.dwDrawStage
= CDDS_POSTERASE
;
2681 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2684 /* Send paint notifications */
2685 nmcd
.dwDrawStage
= CDDS_PREPAINT
;
2686 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2687 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
2689 if (!(cdrf
& CDRF_DOERASE
) && (text
= get_button_text(infoPtr
)))
2691 DrawThemeText(theme
, hDC
, BP_PUSHBUTTON
, state
, text
, lstrlenW(text
), dtFlags
, 0, &textRect
);
2695 if (cdrf
& CDRF_NOTIFYPOSTPAINT
)
2697 nmcd
.dwDrawStage
= CDDS_POSTPAINT
;
2698 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2700 if (cdrf
& CDRF_SKIPPOSTPAINT
) goto cleanup
;
2705 RECT focusRect
= bgRect
;
2707 GetThemeMargins(theme
, hDC
, BP_PUSHBUTTON
, state
, TMT_CONTENTMARGINS
, NULL
, &margins
);
2709 focusRect
.left
+= margins
.cxLeftWidth
;
2710 focusRect
.top
+= margins
.cyTopHeight
;
2711 focusRect
.right
-= margins
.cxRightWidth
;
2712 focusRect
.bottom
-= margins
.cyBottomHeight
;
2714 DrawFocusRect( hDC
, &focusRect
);
2718 if (hPrevFont
) SelectObject(hDC
, hPrevFont
);
2721 static void CB_ThemedPaint(HTHEME theme
, const BUTTON_INFO
*infoPtr
, HDC hDC
, int state
, UINT dtFlags
, BOOL focused
)
2724 RECT bgRect
, textRect
;
2725 HFONT font
, hPrevFont
= NULL
;
2726 DWORD dwStyle
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
2727 UINT btn_type
= get_button_type( dwStyle
);
2728 int part
= (btn_type
== BS_RADIOBUTTON
) || (btn_type
== BS_AUTORADIOBUTTON
) ? BP_RADIOBUTTON
: BP_CHECKBOX
;
2734 BOOL created_font
= FALSE
;
2736 HRESULT hr
= GetThemeFont(theme
, hDC
, part
, state
, TMT_FONT
, &lf
);
2737 if (SUCCEEDED(hr
)) {
2738 font
= CreateFontIndirectW(&lf
);
2740 TRACE("Failed to create font\n");
2742 TRACE("font = %s\n", debugstr_w(lf
.lfFaceName
));
2743 hPrevFont
= SelectObject(hDC
, font
);
2744 created_font
= TRUE
;
2747 font
= (HFONT
)SendMessageW(infoPtr
->hwnd
, WM_GETFONT
, 0, 0);
2748 hPrevFont
= SelectObject(hDC
, font
);
2751 if (FAILED(GetThemePartSize(theme
, hDC
, part
, state
, NULL
, TS_DRAW
, &sz
)))
2754 GetClientRect(infoPtr
->hwnd
, &bgRect
);
2755 GetThemeBackgroundContentRect(theme
, hDC
, part
, state
, &bgRect
, &textRect
);
2756 init_custom_draw(&nmcd
, infoPtr
, hDC
, &bgRect
);
2758 if (dtFlags
& DT_SINGLELINE
) /* Center the checkbox / radio button to the text. */
2759 bgRect
.top
= bgRect
.top
+ (textRect
.bottom
- textRect
.top
- sz
.cy
) / 2;
2761 /* adjust for the check/radio marker */
2762 bgRect
.bottom
= bgRect
.top
+ sz
.cy
;
2763 bgRect
.right
= bgRect
.left
+ sz
.cx
;
2764 textRect
.left
= bgRect
.right
+ 6;
2766 parent
= GetParent(infoPtr
->hwnd
);
2767 if (!parent
) parent
= infoPtr
->hwnd
;
2769 /* Send erase notifications */
2770 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2771 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
2773 DrawThemeParentBackground(infoPtr
->hwnd
, hDC
, NULL
);
2774 DrawThemeBackground(theme
, hDC
, part
, state
, &bgRect
, NULL
);
2776 if (cdrf
& CDRF_NOTIFYPOSTERASE
)
2778 nmcd
.dwDrawStage
= CDDS_POSTERASE
;
2779 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2782 /* Send paint notifications */
2783 nmcd
.dwDrawStage
= CDDS_PREPAINT
;
2784 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2785 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
2787 text
= get_button_text(infoPtr
);
2788 if (!(cdrf
& CDRF_DOERASE
) && text
)
2789 DrawThemeText(theme
, hDC
, part
, state
, text
, lstrlenW(text
), dtFlags
, 0, &textRect
);
2791 if (cdrf
& CDRF_NOTIFYPOSTPAINT
)
2793 nmcd
.dwDrawStage
= CDDS_POSTPAINT
;
2794 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2799 if (!(cdrf
& CDRF_SKIPPOSTPAINT
) && focused
)
2803 focusRect
= textRect
;
2805 DrawTextW(hDC
, text
, lstrlenW(text
), &focusRect
, dtFlags
| DT_CALCRECT
);
2807 if (focusRect
.right
< textRect
.right
) focusRect
.right
++;
2808 focusRect
.bottom
= textRect
.bottom
;
2810 DrawFocusRect( hDC
, &focusRect
);
2817 if (created_font
) DeleteObject(font
);
2818 if (hPrevFont
) SelectObject(hDC
, hPrevFont
);
2821 static void GB_ThemedPaint(HTHEME theme
, const BUTTON_INFO
*infoPtr
, HDC hDC
, int state
, UINT dtFlags
, BOOL focused
)
2823 RECT bgRect
, textRect
, contentRect
;
2824 WCHAR
*text
= get_button_text(infoPtr
);
2826 HFONT font
, hPrevFont
= NULL
;
2827 BOOL created_font
= FALSE
;
2829 HRESULT hr
= GetThemeFont(theme
, hDC
, BP_GROUPBOX
, state
, TMT_FONT
, &lf
);
2830 if (SUCCEEDED(hr
)) {
2831 font
= CreateFontIndirectW(&lf
);
2833 TRACE("Failed to create font\n");
2835 hPrevFont
= SelectObject(hDC
, font
);
2836 created_font
= TRUE
;
2839 font
= (HFONT
)SendMessageW(infoPtr
->hwnd
, WM_GETFONT
, 0, 0);
2840 hPrevFont
= SelectObject(hDC
, font
);
2843 GetClientRect(infoPtr
->hwnd
, &bgRect
);
2849 GetTextExtentPoint32W(hDC
, text
, lstrlenW(text
), &textExtent
);
2850 bgRect
.top
+= (textExtent
.cy
/ 2);
2851 textRect
.left
+= 10;
2852 textRect
.bottom
= textRect
.top
+ textExtent
.cy
;
2853 textRect
.right
= textRect
.left
+ textExtent
.cx
+ 4;
2855 ExcludeClipRect(hDC
, textRect
.left
, textRect
.top
, textRect
.right
, textRect
.bottom
);
2858 GetThemeBackgroundContentRect(theme
, hDC
, BP_GROUPBOX
, state
, &bgRect
, &contentRect
);
2859 ExcludeClipRect(hDC
, contentRect
.left
, contentRect
.top
, contentRect
.right
, contentRect
.bottom
);
2861 if (IsThemeBackgroundPartiallyTransparent(theme
, BP_GROUPBOX
, state
))
2862 DrawThemeParentBackground(infoPtr
->hwnd
, hDC
, NULL
);
2863 DrawThemeBackground(theme
, hDC
, BP_GROUPBOX
, state
, &bgRect
, NULL
);
2865 SelectClipRgn(hDC
, NULL
);
2869 InflateRect(&textRect
, -2, 0);
2870 DrawThemeText(theme
, hDC
, BP_GROUPBOX
, state
, text
, lstrlenW(text
), 0, 0, &textRect
);
2874 if (created_font
) DeleteObject(font
);
2875 if (hPrevFont
) SelectObject(hDC
, hPrevFont
);
2878 static void SB_ThemedPaint(HTHEME theme
, const BUTTON_INFO
*infoPtr
, HDC hDC
, int state
, UINT dtFlags
, BOOL focused
)
2880 HFONT old_font
= infoPtr
->font
? SelectObject(hDC
, infoPtr
->font
) : NULL
;
2881 RECT rc
, content_rect
, push_rect
, dropdown_rect
;
2886 GetClientRect(infoPtr
->hwnd
, &rc
);
2887 init_custom_draw(&nmcd
, infoPtr
, hDC
, &rc
);
2889 parent
= GetParent(infoPtr
->hwnd
);
2890 if (!parent
) parent
= infoPtr
->hwnd
;
2892 /* Send erase notifications */
2893 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2894 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
2896 if (IsThemeBackgroundPartiallyTransparent(theme
, BP_PUSHBUTTON
, state
))
2897 DrawThemeParentBackground(infoPtr
->hwnd
, hDC
, NULL
);
2899 /* The zone outside the content is ignored for the dropdown (draws over) */
2900 GetThemeBackgroundContentRect(theme
, hDC
, BP_PUSHBUTTON
, state
, &rc
, &content_rect
);
2901 get_split_button_rects(infoPtr
, &rc
, &push_rect
, &dropdown_rect
);
2903 if (infoPtr
->split_style
& BCSS_NOSPLIT
)
2906 DrawThemeBackground(theme
, hDC
, BP_PUSHBUTTON
, state
, &rc
, NULL
);
2910 RECT r
= { dropdown_rect
.left
, content_rect
.top
, dropdown_rect
.right
, content_rect
.bottom
};
2911 UINT edge
= (infoPtr
->split_style
& BCSS_ALIGNLEFT
) ? BF_RIGHT
: BF_LEFT
;
2912 const RECT
*clip
= NULL
;
2914 /* If only the dropdown is pressed, we need to draw it separately */
2915 if (state
!= PBS_PRESSED
&& (infoPtr
->state
& BST_DROPDOWNPUSHED
))
2917 DrawThemeBackground(theme
, hDC
, BP_PUSHBUTTON
, PBS_PRESSED
, &rc
, &dropdown_rect
);
2920 DrawThemeBackground(theme
, hDC
, BP_PUSHBUTTON
, state
, &rc
, clip
);
2922 /* Draw the separator */
2923 DrawThemeEdge(theme
, hDC
, BP_PUSHBUTTON
, state
, &r
, EDGE_ETCHED
, edge
, NULL
);
2925 /* The content rect should be the content area of the push button */
2926 GetThemeBackgroundContentRect(theme
, hDC
, BP_PUSHBUTTON
, state
, &push_rect
, &content_rect
);
2929 if (cdrf
& CDRF_NOTIFYPOSTERASE
)
2931 nmcd
.dwDrawStage
= CDDS_POSTERASE
;
2932 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2935 /* Send paint notifications */
2936 nmcd
.dwDrawStage
= CDDS_PREPAINT
;
2937 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2938 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
2940 if (!(cdrf
& CDRF_DOERASE
))
2942 COLORREF old_color
, color
;
2946 if ((text
= get_button_text(infoPtr
)))
2948 DrawThemeText(theme
, hDC
, BP_PUSHBUTTON
, state
, text
, lstrlenW(text
), dtFlags
, 0, &content_rect
);
2952 GetThemeColor(theme
, BP_PUSHBUTTON
, state
, TMT_TEXTCOLOR
, &color
);
2953 old_bk_mode
= SetBkMode(hDC
, TRANSPARENT
);
2954 old_color
= SetTextColor(hDC
, color
);
2956 draw_split_button_dropdown_glyph(infoPtr
, hDC
, &dropdown_rect
);
2958 SetTextColor(hDC
, old_color
);
2959 SetBkMode(hDC
, old_bk_mode
);
2962 if (cdrf
& CDRF_NOTIFYPOSTPAINT
)
2964 nmcd
.dwDrawStage
= CDDS_POSTPAINT
;
2965 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2967 if (cdrf
& CDRF_SKIPPOSTPAINT
) goto cleanup
;
2973 GetThemeMargins(theme
, hDC
, BP_PUSHBUTTON
, state
, TMT_CONTENTMARGINS
, NULL
, &margins
);
2975 push_rect
.left
+= margins
.cxLeftWidth
;
2976 push_rect
.top
+= margins
.cyTopHeight
;
2977 push_rect
.right
-= margins
.cxRightWidth
;
2978 push_rect
.bottom
-= margins
.cyBottomHeight
;
2979 DrawFocusRect(hDC
, &push_rect
);
2983 if (old_font
) SelectObject(hDC
, old_font
);
2986 static void CL_ThemedPaint(HTHEME theme
, const BUTTON_INFO
*infoPtr
, HDC hDC
, int state
, UINT dtFlags
, BOOL focused
)
2988 HFONT old_font
= infoPtr
->font
? SelectObject(hDC
, infoPtr
->font
) : NULL
;
2994 GetClientRect(infoPtr
->hwnd
, &rc
);
2995 init_custom_draw(&nmcd
, infoPtr
, hDC
, &rc
);
2997 parent
= GetParent(infoPtr
->hwnd
);
2998 if (!parent
) parent
= infoPtr
->hwnd
;
3000 /* Send erase notifications */
3001 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
3002 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
3004 if (IsThemeBackgroundPartiallyTransparent(theme
, BP_COMMANDLINK
, state
))
3005 DrawThemeParentBackground(infoPtr
->hwnd
, hDC
, NULL
);
3006 DrawThemeBackground(theme
, hDC
, BP_COMMANDLINK
, state
, &rc
, NULL
);
3008 if (cdrf
& CDRF_NOTIFYPOSTERASE
)
3010 nmcd
.dwDrawStage
= CDDS_POSTERASE
;
3011 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
3014 /* Send paint notifications */
3015 nmcd
.dwDrawStage
= CDDS_PREPAINT
;
3016 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
3017 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
3019 if (!(cdrf
& CDRF_DOERASE
))
3026 GetThemeBackgroundContentRect(theme
, hDC
, BP_COMMANDLINK
, state
, &rc
, &r
);
3028 /* The text alignment and styles are fixed and don't depend on button styles */
3029 dtFlags
= DT_TOP
| DT_LEFT
| DT_WORDBREAK
;
3031 /* Command Links ignore the margins of the image list or its alignment */
3032 if (infoPtr
->u
.image
|| infoPtr
->imagelist
.himl
)
3033 img_size
= BUTTON_GetImageSize(infoPtr
);
3035 GetThemePartSize(theme
, NULL
, BP_COMMANDLINKGLYPH
, state
, NULL
, TS_DRAW
, &img_size
);
3038 if (img_size
.cx
) r
.left
+= img_size
.cx
+ command_link_margin
;
3041 if ((text
= get_button_text(infoPtr
)))
3043 UINT len
= lstrlenW(text
);
3046 GetThemeTextExtent(theme
, hDC
, BP_COMMANDLINK
, state
, text
, len
,
3047 dtFlags
| DT_END_ELLIPSIS
, &r
, &text_rect
);
3048 DrawThemeText(theme
, hDC
, BP_COMMANDLINK
, state
, text
, len
,
3049 dtFlags
| DT_END_ELLIPSIS
, 0, &r
);
3051 txt_h
= text_rect
.bottom
- text_rect
.top
;
3061 opts
.dwSize
= sizeof(opts
);
3062 opts
.dwFlags
= DTT_FONTPROP
;
3063 opts
.iFontPropId
= TMT_BODYFONT
;
3064 DrawThemeTextEx(theme
, hDC
, BP_COMMANDLINK
, state
,
3065 infoPtr
->note
, infoPtr
->note_length
,
3066 dtFlags
| DT_NOPREFIX
, &r
, &opts
);
3069 /* Position the image at the vertical center of the drawn text (not note) */
3070 txt_h
= min(txt_h
, img_rect
.bottom
- img_rect
.top
);
3071 if (img_size
.cy
< txt_h
) img_rect
.top
+= (txt_h
- img_size
.cy
) / 2;
3073 img_rect
.right
= img_rect
.left
+ img_size
.cx
;
3074 img_rect
.bottom
= img_rect
.top
+ img_size
.cy
;
3076 if (infoPtr
->u
.image
|| infoPtr
->imagelist
.himl
)
3077 BUTTON_DrawImage(infoPtr
, hDC
, NULL
,
3078 (state
== CMDLS_DISABLED
) ? DSS_DISABLED
: DSS_NORMAL
,
3081 DrawThemeBackground(theme
, hDC
, BP_COMMANDLINKGLYPH
, state
, &img_rect
, NULL
);
3084 if (cdrf
& CDRF_NOTIFYPOSTPAINT
)
3086 nmcd
.dwDrawStage
= CDDS_POSTPAINT
;
3087 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
3089 if (cdrf
& CDRF_SKIPPOSTPAINT
) goto cleanup
;
3095 /* The focus rect has margins of a push button rather than command link... */
3096 GetThemeMargins(theme
, hDC
, BP_PUSHBUTTON
, state
, TMT_CONTENTMARGINS
, NULL
, &margins
);
3098 rc
.left
+= margins
.cxLeftWidth
;
3099 rc
.top
+= margins
.cyTopHeight
;
3100 rc
.right
-= margins
.cxRightWidth
;
3101 rc
.bottom
-= margins
.cyBottomHeight
;
3102 DrawFocusRect(hDC
, &rc
);
3106 if (old_font
) SelectObject(hDC
, old_font
);
3109 void BUTTON_Register(void)
3113 memset(&wndClass
, 0, sizeof(wndClass
));
3114 wndClass
.style
= CS_GLOBALCLASS
| CS_DBLCLKS
| CS_VREDRAW
| CS_HREDRAW
| CS_PARENTDC
;
3115 wndClass
.lpfnWndProc
= BUTTON_WindowProc
;
3116 wndClass
.cbClsExtra
= 0;
3117 wndClass
.cbWndExtra
= sizeof(BUTTON_INFO
*);
3118 wndClass
.hCursor
= LoadCursorW(0, (LPWSTR
)IDC_ARROW
);
3119 wndClass
.hbrBackground
= NULL
;
3120 wndClass
.lpszClassName
= WC_BUTTONW
;
3121 RegisterClassW(&wndClass
);