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
);
537 InvalidateRect( hWnd
, NULL
, TRUE
);
541 if (btn_type
== BS_OWNERDRAW
)
543 HDC hdc
= (HDC
)wParam
;
546 HWND parent
= GetParent(hWnd
);
547 if (!parent
) parent
= hWnd
;
548 hBrush
= (HBRUSH
)SendMessageW(parent
, WM_CTLCOLORBTN
, (WPARAM
)hdc
, (LPARAM
)hWnd
);
549 if (!hBrush
) /* did the app forget to call defwindowproc ? */
550 hBrush
= (HBRUSH
)DefWindowProcW(parent
, WM_CTLCOLORBTN
,
551 (WPARAM
)hdc
, (LPARAM
)hWnd
);
552 GetClientRect(hWnd
, &rc
);
553 FillRect(hdc
, &rc
, hBrush
);
563 theme
= GetWindowTheme( hWnd
);
564 hdc
= wParam
? (HDC
)wParam
: BeginPaint( hWnd
, &ps
);
566 if (is_themed_paint_supported(theme
, btn_type
))
568 int drawState
= get_draw_state(infoPtr
);
569 UINT dtflags
= BUTTON_BStoDT(style
, GetWindowLongW(hWnd
, GWL_EXSTYLE
));
571 btnThemedPaintFunc
[btn_type
](theme
, infoPtr
, hdc
, drawState
, dtflags
, infoPtr
->state
& BST_FOCUS
);
573 else if (btnPaintFunc
[btn_type
])
575 int nOldMode
= SetBkMode( hdc
, OPAQUE
);
576 btnPaintFunc
[btn_type
]( infoPtr
, hdc
, ODA_DRAWENTIRE
);
577 SetBkMode(hdc
, nOldMode
); /* reset painting mode */
580 if ( !wParam
) EndPaint( hWnd
, &ps
);
585 if (wParam
== VK_SPACE
)
587 SendMessageW( hWnd
, BM_SETSTATE
, TRUE
, 0 );
588 infoPtr
->state
|= BUTTON_BTNPRESSED
;
591 else if (wParam
== VK_UP
|| wParam
== VK_DOWN
)
593 /* Up and down arrows work on every button, and even with BCSS_NOSPLIT */
594 notify_split_button_dropdown(infoPtr
, NULL
, hWnd
);
598 case WM_LBUTTONDBLCLK
:
599 if(style
& BS_NOTIFY
||
600 btn_type
== BS_RADIOBUTTON
||
601 btn_type
== BS_USERBUTTON
||
602 btn_type
== BS_OWNERDRAW
)
604 BUTTON_NOTIFY_PARENT(hWnd
, BN_DOUBLECLICKED
);
611 if ((btn_type
== BS_SPLITBUTTON
|| btn_type
== BS_DEFSPLITBUTTON
) &&
612 !(infoPtr
->split_style
& BCSS_NOSPLIT
) &&
613 notify_split_button_dropdown(infoPtr
, &pt
, hWnd
))
617 infoPtr
->state
|= BUTTON_BTNPRESSED
;
618 SendMessageW( hWnd
, BM_SETSTATE
, TRUE
, 0 );
622 if (wParam
!= VK_SPACE
)
626 state
= infoPtr
->state
;
627 if (state
& BST_DROPDOWNPUSHED
)
628 SendMessageW(hWnd
, BCM_SETDROPDOWNSTATE
, FALSE
, 0);
629 if (!(state
& BUTTON_BTNPRESSED
)) break;
630 infoPtr
->state
&= BUTTON_NSTATES
| BST_HOT
;
631 if (!(state
& BST_PUSHED
))
636 SendMessageW( hWnd
, BM_SETSTATE
, FALSE
, 0 );
637 GetClientRect( hWnd
, &rect
);
638 if (uMsg
== WM_KEYUP
|| PtInRect( &rect
, pt
))
642 case BS_AUTOCHECKBOX
:
643 SendMessageW( hWnd
, BM_SETCHECK
, !(infoPtr
->state
& BST_CHECKED
), 0 );
645 case BS_AUTORADIOBUTTON
:
646 SendMessageW( hWnd
, BM_SETCHECK
, TRUE
, 0 );
649 SendMessageW( hWnd
, BM_SETCHECK
, (infoPtr
->state
& BST_INDETERMINATE
) ? 0 :
650 ((infoPtr
->state
& 3) + 1), 0 );
654 BUTTON_NOTIFY_PARENT(hWnd
, BN_CLICKED
);
662 case WM_CAPTURECHANGED
:
663 TRACE("WM_CAPTURECHANGED %p\n", hWnd
);
664 if (hWnd
== (HWND
)lParam
) break;
665 if (infoPtr
->state
& BUTTON_BTNPRESSED
)
667 infoPtr
->state
&= BUTTON_NSTATES
;
668 if (infoPtr
->state
& BST_PUSHED
)
669 SendMessageW( hWnd
, BM_SETSTATE
, FALSE
, 0 );
675 TRACKMOUSEEVENT mouse_event
;
677 mouse_event
.cbSize
= sizeof(TRACKMOUSEEVENT
);
678 mouse_event
.dwFlags
= TME_QUERY
;
679 if (!TrackMouseEvent(&mouse_event
) || !(mouse_event
.dwFlags
& (TME_HOVER
| TME_LEAVE
)))
681 mouse_event
.dwFlags
= TME_HOVER
| TME_LEAVE
;
682 mouse_event
.hwndTrack
= hWnd
;
683 mouse_event
.dwHoverTime
= 1;
684 TrackMouseEvent(&mouse_event
);
687 if ((wParam
& MK_LBUTTON
) && GetCapture() == hWnd
)
689 GetClientRect( hWnd
, &rect
);
690 SendMessageW( hWnd
, BM_SETSTATE
, PtInRect(&rect
, pt
), 0 );
697 infoPtr
->state
|= BST_HOT
;
698 InvalidateRect( hWnd
, NULL
, FALSE
);
704 infoPtr
->state
&= ~BST_HOT
;
705 InvalidateRect( hWnd
, NULL
, FALSE
);
711 /* Clear an old text here as Windows does */
712 if (IsWindowVisible(hWnd
))
714 HDC hdc
= GetDC(hWnd
);
717 HWND parent
= GetParent(hWnd
);
718 UINT message
= (btn_type
== BS_PUSHBUTTON
||
719 btn_type
== BS_DEFPUSHBUTTON
||
720 btn_type
== BS_USERBUTTON
||
721 btn_type
== BS_OWNERDRAW
) ?
722 WM_CTLCOLORBTN
: WM_CTLCOLORSTATIC
;
724 if (!parent
) parent
= hWnd
;
725 hbrush
= (HBRUSH
)SendMessageW(parent
, message
,
726 (WPARAM
)hdc
, (LPARAM
)hWnd
);
727 if (!hbrush
) /* did the app forget to call DefWindowProc ? */
728 hbrush
= (HBRUSH
)DefWindowProcW(parent
, message
,
729 (WPARAM
)hdc
, (LPARAM
)hWnd
);
731 GetClientRect(hWnd
, &client
);
733 /* FIXME: check other BS_* handlers */
734 if (btn_type
== BS_GROUPBOX
)
735 InflateRect(&rc
, -7, 1); /* GB_Paint does this */
736 BUTTON_CalcLayoutRects(infoPtr
, hdc
, &rc
, NULL
, NULL
);
737 /* Clip by client rect bounds */
738 if (rc
.right
> client
.right
) rc
.right
= client
.right
;
739 if (rc
.bottom
> client
.bottom
) rc
.bottom
= client
.bottom
;
740 FillRect(hdc
, &rc
, hbrush
);
741 ReleaseDC(hWnd
, hdc
);
744 DefWindowProcW( hWnd
, WM_SETTEXT
, wParam
, lParam
);
745 if (btn_type
== BS_GROUPBOX
) /* Yes, only for BS_GROUPBOX */
746 InvalidateRect( hWnd
, NULL
, TRUE
);
747 else if (GetWindowTheme( hWnd
))
748 RedrawWindow( hWnd
, NULL
, NULL
, RDW_INVALIDATE
| RDW_UPDATENOW
);
750 paint_button( infoPtr
, btn_type
, ODA_DRAWENTIRE
);
751 return 1; /* success. FIXME: check text length */
756 WCHAR
*note
= (WCHAR
*)lParam
;
757 if (btn_type
!= BS_COMMANDLINK
&& btn_type
!= BS_DEFCOMMANDLINK
)
759 SetLastError(ERROR_NOT_SUPPORTED
);
763 heap_free(infoPtr
->note
);
766 infoPtr
->note_length
= lstrlenW(note
);
767 infoPtr
->note
= heap_strndupW(note
, infoPtr
->note_length
);
770 if (!note
|| !infoPtr
->note
)
772 infoPtr
->note_length
= 0;
773 infoPtr
->note
= heap_alloc_zero(sizeof(WCHAR
));
776 SetLastError(NO_ERROR
);
782 DWORD
*size
= (DWORD
*)wParam
;
783 WCHAR
*buffer
= (WCHAR
*)lParam
;
786 if (btn_type
!= BS_COMMANDLINK
&& btn_type
!= BS_DEFCOMMANDLINK
)
788 SetLastError(ERROR_NOT_SUPPORTED
);
792 if (!buffer
|| !size
|| !infoPtr
->note
)
794 SetLastError(ERROR_INVALID_PARAMETER
);
800 length
= min(*size
- 1, infoPtr
->note_length
);
801 memcpy(buffer
, infoPtr
->note
, length
* sizeof(WCHAR
));
802 buffer
[length
] = '\0';
805 if (*size
< infoPtr
->note_length
+ 1)
807 *size
= infoPtr
->note_length
+ 1;
808 SetLastError(ERROR_INSUFFICIENT_BUFFER
);
813 SetLastError(NO_ERROR
);
818 case BCM_GETNOTELENGTH
:
820 if (btn_type
!= BS_COMMANDLINK
&& btn_type
!= BS_DEFCOMMANDLINK
)
822 SetLastError(ERROR_NOT_SUPPORTED
);
826 return infoPtr
->note_length
;
830 infoPtr
->font
= (HFONT
)wParam
;
831 if (lParam
) InvalidateRect(hWnd
, NULL
, TRUE
);
835 return (LRESULT
)infoPtr
->font
;
838 TRACE("WM_SETFOCUS %p\n",hWnd
);
839 infoPtr
->state
|= BST_FOCUS
;
841 if (btn_type
== BS_OWNERDRAW
)
842 paint_button( infoPtr
, btn_type
, ODA_FOCUS
);
844 InvalidateRect(hWnd
, NULL
, FALSE
);
846 if (style
& BS_NOTIFY
)
847 BUTTON_NOTIFY_PARENT(hWnd
, BN_SETFOCUS
);
851 TRACE("WM_KILLFOCUS %p\n",hWnd
);
852 infoPtr
->state
&= ~BST_FOCUS
;
854 if ((infoPtr
->state
& BUTTON_BTNPRESSED
) && GetCapture() == hWnd
)
856 if (style
& BS_NOTIFY
)
857 BUTTON_NOTIFY_PARENT(hWnd
, BN_KILLFOCUS
);
859 InvalidateRect( hWnd
, NULL
, FALSE
);
862 case WM_SYSCOLORCHANGE
:
863 InvalidateRect( hWnd
, NULL
, FALSE
);
870 new_btn_type
= wParam
& BS_TYPEMASK
;
871 if (btn_type
>= BS_SPLITBUTTON
&& new_btn_type
<= BS_DEFPUSHBUTTON
)
872 new_btn_type
= (btn_type
& ~BS_DEFPUSHBUTTON
) | new_btn_type
;
874 style
= (style
& ~BS_TYPEMASK
) | new_btn_type
;
875 SetWindowLongW( hWnd
, GWL_STYLE
, style
);
877 /* Only redraw if lParam flag is set.*/
879 InvalidateRect( hWnd
, NULL
, TRUE
);
884 SendMessageW( hWnd
, WM_LBUTTONDOWN
, 0, 0 );
885 SendMessageW( hWnd
, WM_LBUTTONUP
, 0, 0 );
889 infoPtr
->image_type
= (DWORD
)wParam
;
890 oldHbitmap
= infoPtr
->image
;
891 infoPtr
->u
.image
= CopyImage((HANDLE
)lParam
, infoPtr
->image_type
, 0, 0, 0);
892 infoPtr
->image
= (HANDLE
)lParam
;
893 InvalidateRect( hWnd
, NULL
, FALSE
);
894 return (LRESULT
)oldHbitmap
;
897 return (LRESULT
)infoPtr
->image
;
899 case BCM_SETIMAGELIST
:
901 BUTTON_IMAGELIST
*imagelist
= (BUTTON_IMAGELIST
*)lParam
;
903 if (!imagelist
) return FALSE
;
905 infoPtr
->imagelist
= *imagelist
;
909 case BCM_GETIMAGELIST
:
911 BUTTON_IMAGELIST
*imagelist
= (BUTTON_IMAGELIST
*)lParam
;
913 if (!imagelist
) return FALSE
;
915 *imagelist
= infoPtr
->imagelist
;
919 case BCM_SETSPLITINFO
:
921 BUTTON_SPLITINFO
*info
= (BUTTON_SPLITINFO
*)lParam
;
923 if (!info
) return TRUE
;
925 if (info
->mask
& (BCSIF_GLYPH
| BCSIF_IMAGE
))
927 infoPtr
->split_style
&= ~BCSS_IMAGE
;
928 if (!(info
->mask
& BCSIF_GLYPH
))
929 infoPtr
->split_style
|= BCSS_IMAGE
;
930 infoPtr
->glyph
= info
->himlGlyph
;
931 infoPtr
->glyph_size
.cx
= infoPtr
->glyph_size
.cy
= 0;
934 if (info
->mask
& BCSIF_STYLE
)
935 infoPtr
->split_style
= info
->uSplitStyle
;
936 if (info
->mask
& BCSIF_SIZE
)
937 infoPtr
->glyph_size
= info
->size
;
939 /* Calculate fitting value for cx if invalid (cy is untouched) */
940 if (infoPtr
->glyph_size
.cx
<= 0)
941 infoPtr
->glyph_size
.cx
= get_default_glyph_size(infoPtr
);
943 /* Windows doesn't invalidate or redraw it, so we don't, either */
947 case BCM_GETSPLITINFO
:
949 BUTTON_SPLITINFO
*info
= (BUTTON_SPLITINFO
*)lParam
;
951 if (!info
) return FALSE
;
953 if (info
->mask
& BCSIF_STYLE
)
954 info
->uSplitStyle
= infoPtr
->split_style
;
955 if (info
->mask
& (BCSIF_GLYPH
| BCSIF_IMAGE
))
956 info
->himlGlyph
= infoPtr
->glyph
;
957 if (info
->mask
& BCSIF_SIZE
)
958 info
->size
= infoPtr
->glyph_size
;
964 return infoPtr
->state
& 3;
967 if (wParam
> maxCheckState
[btn_type
]) wParam
= maxCheckState
[btn_type
];
968 if ((btn_type
== BS_RADIOBUTTON
) || (btn_type
== BS_AUTORADIOBUTTON
))
970 style
= wParam
? style
| WS_TABSTOP
: style
& ~WS_TABSTOP
;
971 SetWindowLongW( hWnd
, GWL_STYLE
, style
);
973 if ((infoPtr
->state
& 3) != wParam
)
975 infoPtr
->state
= (infoPtr
->state
& ~3) | wParam
;
976 InvalidateRect( hWnd
, NULL
, FALSE
);
978 if ((btn_type
== BS_AUTORADIOBUTTON
) && (wParam
== BST_CHECKED
) && (style
& WS_CHILD
))
979 BUTTON_CheckAutoRadioButton( hWnd
);
983 return infoPtr
->state
;
986 state
= infoPtr
->state
;
987 new_state
= wParam
? BST_PUSHED
: 0;
989 if ((state
^ new_state
) & BST_PUSHED
)
994 state
&= ~BST_PUSHED
;
996 if (btn_type
== BS_USERBUTTON
)
997 BUTTON_NOTIFY_PARENT( hWnd
, (state
& BST_PUSHED
) ? BN_HILITE
: BN_UNHILITE
);
998 infoPtr
->state
= state
;
1000 InvalidateRect( hWnd
, NULL
, FALSE
);
1004 case BCM_SETDROPDOWNSTATE
:
1005 new_state
= wParam
? BST_DROPDOWNPUSHED
: 0;
1007 if ((infoPtr
->state
^ new_state
) & BST_DROPDOWNPUSHED
)
1009 infoPtr
->state
&= ~BST_DROPDOWNPUSHED
;
1010 infoPtr
->state
|= new_state
;
1011 InvalidateRect(hWnd
, NULL
, FALSE
);
1015 case BCM_SETTEXTMARGIN
:
1017 RECT
*text_margin
= (RECT
*)lParam
;
1019 if (!text_margin
) return FALSE
;
1021 infoPtr
->text_margin
= *text_margin
;
1025 case BCM_GETTEXTMARGIN
:
1027 RECT
*text_margin
= (RECT
*)lParam
;
1029 if (!text_margin
) return FALSE
;
1031 *text_margin
= infoPtr
->text_margin
;
1035 case BCM_GETIDEALSIZE
:
1037 SIZE
*size
= (SIZE
*)lParam
;
1039 if (!size
) return FALSE
;
1041 return btnGetIdealSizeFunc
[btn_type
](infoPtr
, size
);
1045 if(btn_type
== BS_GROUPBOX
) return HTTRANSPARENT
;
1048 return DefWindowProcW(hWnd
, uMsg
, wParam
, lParam
);
1053 /* If maxWidth is zero, rectangle width is unlimited */
1054 static RECT
BUTTON_GetTextRect(const BUTTON_INFO
*infoPtr
, HDC hdc
, const WCHAR
*text
, LONG maxWidth
)
1056 LONG style
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
1057 LONG exStyle
= GetWindowLongW(infoPtr
->hwnd
, GWL_EXSTYLE
);
1058 UINT dtStyle
= BUTTON_BStoDT(style
, exStyle
);
1062 rect
.right
= maxWidth
;
1063 hPrevFont
= SelectObject(hdc
, infoPtr
->font
);
1064 /* Calculate height without DT_VCENTER and DT_BOTTOM to get the correct height */
1065 DrawTextW(hdc
, text
, -1, &rect
, (dtStyle
& ~(DT_VCENTER
| DT_BOTTOM
)) | DT_CALCRECT
);
1066 if (hPrevFont
) SelectObject(hdc
, hPrevFont
);
1071 static BOOL
show_image_only(const BUTTON_INFO
*infoPtr
)
1073 LONG style
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
1074 return (style
& (BS_ICON
| BS_BITMAP
)) && (infoPtr
->u
.image
|| infoPtr
->imagelist
.himl
);
1077 static BOOL
show_image_and_text(const BUTTON_INFO
*infoPtr
)
1079 LONG style
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
1080 UINT type
= get_button_type(style
);
1081 return !(style
& (BS_ICON
| BS_BITMAP
))
1082 && ((infoPtr
->u
.image
1083 && (type
== BS_PUSHBUTTON
|| type
== BS_DEFPUSHBUTTON
|| type
== BS_USERBUTTON
|| type
== BS_SPLITBUTTON
1084 || type
== BS_DEFSPLITBUTTON
|| type
== BS_COMMANDLINK
|| type
== BS_DEFCOMMANDLINK
))
1085 || (infoPtr
->imagelist
.himl
&& type
!= BS_GROUPBOX
));
1088 static BOOL
show_image(const BUTTON_INFO
*infoPtr
)
1090 return show_image_only(infoPtr
) || show_image_and_text(infoPtr
);
1093 /* Get a bounding rectangle that is large enough to contain a image and a text side by side.
1094 * Note: (left,top) of the result rectangle may not be (0,0), offset it by yourself if needed */
1095 static RECT
BUTTON_GetBoundingLabelRect(LONG style
, const RECT
*textRect
, const RECT
*imageRect
)
1098 RECT rect
= *imageRect
;
1099 INT textWidth
= textRect
->right
- textRect
->left
;
1100 INT textHeight
= textRect
->bottom
- textRect
->top
;
1101 INT imageWidth
= imageRect
->right
- imageRect
->left
;
1102 INT imageHeight
= imageRect
->bottom
- imageRect
->top
;
1104 if ((style
& BS_CENTER
) == BS_RIGHT
)
1105 OffsetRect(&rect
, textWidth
, 0);
1106 else if ((style
& BS_CENTER
) == BS_LEFT
)
1107 OffsetRect(&rect
, -imageWidth
, 0);
1108 else if ((style
& BS_VCENTER
) == BS_BOTTOM
)
1109 OffsetRect(&rect
, 0, textHeight
);
1110 else if ((style
& BS_VCENTER
) == BS_TOP
)
1111 OffsetRect(&rect
, 0, -imageHeight
);
1113 OffsetRect(&rect
, -imageWidth
, 0);
1115 UnionRect(&labelRect
, textRect
, &rect
);
1119 /* Position a rectangle inside a bounding rectangle according to button alignment flags */
1120 static void BUTTON_PositionRect(LONG style
, const RECT
*outerRect
, RECT
*innerRect
, const RECT
*margin
)
1122 INT width
= innerRect
->right
- innerRect
->left
;
1123 INT height
= innerRect
->bottom
- innerRect
->top
;
1125 if ((style
& WS_EX_RIGHT
) && !(style
& BS_CENTER
)) style
|= BS_CENTER
;
1127 if (!(style
& BS_CENTER
))
1129 if (button_centers_text(style
))
1135 if (!(style
& BS_VCENTER
))
1137 /* Group box's text is top aligned by default */
1138 if (get_button_type(style
) == BS_GROUPBOX
)
1142 switch (style
& BS_CENTER
)
1145 innerRect
->left
= outerRect
->left
+ (outerRect
->right
- outerRect
->left
- width
) / 2;
1146 innerRect
->right
= innerRect
->left
+ width
;
1149 innerRect
->right
= outerRect
->right
- margin
->right
;
1150 innerRect
->left
= innerRect
->right
- width
;
1154 innerRect
->left
= outerRect
->left
+ margin
->left
;
1155 innerRect
->right
= innerRect
->left
+ width
;
1159 switch (style
& BS_VCENTER
)
1162 innerRect
->top
= outerRect
->top
+ margin
->top
;
1163 innerRect
->bottom
= innerRect
->top
+ height
;
1166 innerRect
->bottom
= outerRect
->bottom
- margin
->bottom
;
1167 innerRect
->top
= innerRect
->bottom
- height
;
1171 innerRect
->top
= outerRect
->top
+ (outerRect
->bottom
- outerRect
->top
- height
) / 2;
1172 innerRect
->bottom
= innerRect
->top
+ height
;
1177 /* Convert imagelist align style to button align style */
1178 static UINT
BUTTON_ILStoBS(UINT align
)
1182 case BUTTON_IMAGELIST_ALIGN_TOP
:
1183 return BS_CENTER
| BS_TOP
;
1184 case BUTTON_IMAGELIST_ALIGN_BOTTOM
:
1185 return BS_CENTER
| BS_BOTTOM
;
1186 case BUTTON_IMAGELIST_ALIGN_CENTER
:
1187 return BS_CENTER
| BS_VCENTER
;
1188 case BUTTON_IMAGELIST_ALIGN_RIGHT
:
1189 return BS_RIGHT
| BS_VCENTER
;
1190 case BUTTON_IMAGELIST_ALIGN_LEFT
:
1192 return BS_LEFT
| BS_VCENTER
;
1196 static SIZE
BUTTON_GetImageSize(const BUTTON_INFO
*infoPtr
)
1202 /* ImageList has priority over image */
1203 if (infoPtr
->imagelist
.himl
)
1204 ImageList_GetIconSize(infoPtr
->imagelist
.himl
, &size
.cx
, &size
.cy
);
1205 else if (infoPtr
->u
.image
)
1207 if (infoPtr
->image_type
== IMAGE_ICON
)
1209 GetIconInfo(infoPtr
->u
.icon
, &iconInfo
);
1210 GetObjectW(iconInfo
.hbmColor
, sizeof(bm
), &bm
);
1211 DeleteObject(iconInfo
.hbmColor
);
1212 DeleteObject(iconInfo
.hbmMask
);
1214 else if (infoPtr
->image_type
== IMAGE_BITMAP
)
1215 GetObjectW(infoPtr
->u
.bitmap
, sizeof(bm
), &bm
);
1217 size
.cx
= bm
.bmWidth
;
1218 size
.cy
= bm
.bmHeight
;
1224 static const RECT
*BUTTON_GetTextMargin(const BUTTON_INFO
*infoPtr
)
1226 static const RECT oneMargin
= {1, 1, 1, 1};
1228 /* Use text margin only when showing both image and text, and image is not imagelist */
1229 if (show_image_and_text(infoPtr
) && !infoPtr
->imagelist
.himl
)
1230 return &infoPtr
->text_margin
;
1235 static void BUTTON_GetClientRectSize(BUTTON_INFO
*infoPtr
, SIZE
*size
)
1238 GetClientRect(infoPtr
->hwnd
, &rect
);
1239 size
->cx
= rect
.right
- rect
.left
;
1240 size
->cy
= rect
.bottom
- rect
.top
;
1243 static void BUTTON_GetTextIdealSize(BUTTON_INFO
*infoPtr
, LONG maxWidth
, SIZE
*size
)
1245 WCHAR
*text
= get_button_text(infoPtr
);
1248 const RECT
*margin
= BUTTON_GetTextMargin(infoPtr
);
1252 maxWidth
-= margin
->right
+ margin
->right
;
1253 if (maxWidth
<= 0) maxWidth
= 1;
1256 hdc
= GetDC(infoPtr
->hwnd
);
1257 rect
= BUTTON_GetTextRect(infoPtr
, hdc
, text
, maxWidth
);
1258 ReleaseDC(infoPtr
->hwnd
, hdc
);
1261 size
->cx
= rect
.right
- rect
.left
+ margin
->left
+ margin
->right
;
1262 size
->cy
= rect
.bottom
- rect
.top
+ margin
->top
+ margin
->bottom
;
1265 static void BUTTON_GetLabelIdealSize(BUTTON_INFO
*infoPtr
, LONG maxWidth
, SIZE
*size
)
1267 LONG style
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
1272 imageSize
= BUTTON_GetImageSize(infoPtr
);
1273 if (infoPtr
->imagelist
.himl
)
1275 imageSize
.cx
+= infoPtr
->imagelist
.margin
.left
+ infoPtr
->imagelist
.margin
.right
;
1276 imageSize
.cy
+= infoPtr
->imagelist
.margin
.top
+ infoPtr
->imagelist
.margin
.bottom
;
1277 if (infoPtr
->imagelist
.uAlign
== BUTTON_IMAGELIST_ALIGN_TOP
1278 || infoPtr
->imagelist
.uAlign
== BUTTON_IMAGELIST_ALIGN_BOTTOM
)
1285 /* horizontal alignment flags has priority over vertical ones if both are specified */
1286 if (!(style
& (BS_CENTER
| BS_VCENTER
)) || ((style
& BS_CENTER
) && (style
& BS_CENTER
) != BS_CENTER
)
1287 || !(style
& BS_VCENTER
) || (style
& BS_VCENTER
) == BS_VCENTER
)
1297 maxWidth
-= imageSize
.cx
;
1298 if (maxWidth
<= 0) maxWidth
= 1;
1300 BUTTON_GetTextIdealSize(infoPtr
, maxWidth
, &textSize
);
1301 size
->cx
= textSize
.cx
+ imageSize
.cx
;
1302 size
->cy
= max(textSize
.cy
, imageSize
.cy
);
1306 BUTTON_GetTextIdealSize(infoPtr
, maxWidth
, &textSize
);
1307 size
->cx
= max(textSize
.cx
, imageSize
.cx
);
1308 size
->cy
= textSize
.cy
+ imageSize
.cy
;
1312 static BOOL
GB_GetIdealSize(BUTTON_INFO
*infoPtr
, SIZE
*size
)
1314 BUTTON_GetClientRectSize(infoPtr
, size
);
1318 static BOOL
CB_GetIdealSize(BUTTON_INFO
*infoPtr
, SIZE
*size
)
1320 LONG style
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
1327 LONG checkboxWidth
, checkboxHeight
;
1330 if (SendMessageW(infoPtr
->hwnd
, WM_GETTEXTLENGTH
, 0, 0) == 0)
1332 BUTTON_GetClientRectSize(infoPtr
, size
);
1336 hdc
= GetDC(infoPtr
->hwnd
);
1337 scaleX
= GetDeviceCaps(hdc
, LOGPIXELSX
) / 96.0;
1338 scaleY
= GetDeviceCaps(hdc
, LOGPIXELSY
) / 96.0;
1339 if ((hfont
= infoPtr
->font
)) SelectObject(hdc
, hfont
);
1340 GetCharWidthW(hdc
, '0', '0', &textOffset
);
1342 ReleaseDC(infoPtr
->hwnd
, hdc
);
1344 checkboxWidth
= 12 * scaleX
+ 1;
1345 checkboxHeight
= 12 * scaleY
+ 1;
1348 maxWidth
= size
->cx
- checkboxWidth
- textOffset
;
1349 if (maxWidth
<= 0) maxWidth
= 1;
1352 /* Checkbox doesn't support both image(but not image list) and text */
1353 if (!(style
& (BS_ICON
| BS_BITMAP
)) && infoPtr
->u
.image
)
1354 BUTTON_GetTextIdealSize(infoPtr
, maxWidth
, &labelSize
);
1356 BUTTON_GetLabelIdealSize(infoPtr
, maxWidth
, &labelSize
);
1358 size
->cx
= labelSize
.cx
+ checkboxWidth
+ textOffset
;
1359 size
->cy
= max(labelSize
.cy
, checkboxHeight
);
1364 static BOOL
PB_GetIdealSize(BUTTON_INFO
*infoPtr
, SIZE
*size
)
1368 if (SendMessageW(infoPtr
->hwnd
, WM_GETTEXTLENGTH
, 0, 0) == 0)
1369 BUTTON_GetClientRectSize(infoPtr
, size
);
1372 /* Ideal size include text size even if image only flags(BS_ICON, BS_BITMAP) are specified */
1373 BUTTON_GetLabelIdealSize(infoPtr
, size
->cx
, &labelSize
);
1375 size
->cx
= labelSize
.cx
;
1376 size
->cy
= labelSize
.cy
;
1381 static BOOL
SB_GetIdealSize(BUTTON_INFO
*infoPtr
, SIZE
*size
)
1383 LONG extra_width
= infoPtr
->glyph_size
.cx
* 2 + GetSystemMetrics(SM_CXEDGE
);
1386 if (SendMessageW(infoPtr
->hwnd
, WM_GETTEXTLENGTH
, 0, 0) == 0)
1388 BUTTON_GetClientRectSize(infoPtr
, size
);
1389 size
->cx
= max(size
->cx
, extra_width
);
1393 BUTTON_GetLabelIdealSize(infoPtr
, size
->cx
, &label_size
);
1394 size
->cx
= label_size
.cx
+ ((size
->cx
== 0) ? extra_width
: 0);
1395 size
->cy
= label_size
.cy
;
1400 static BOOL
CL_GetIdealSize(BUTTON_INFO
*infoPtr
, SIZE
*size
)
1402 HTHEME theme
= GetWindowTheme(infoPtr
->hwnd
);
1403 HDC hdc
= GetDC(infoPtr
->hwnd
);
1404 LONG w
, text_w
= 0, text_h
= 0;
1405 UINT flags
= DT_TOP
| DT_LEFT
;
1406 HFONT font
, old_font
= NULL
;
1407 RECT text_bound
= { 0 };
1412 /* Get the image size */
1413 if (infoPtr
->u
.image
|| infoPtr
->imagelist
.himl
)
1414 img_size
= BUTTON_GetImageSize(infoPtr
);
1418 GetThemePartSize(theme
, NULL
, BP_COMMANDLINKGLYPH
, CMDLS_NORMAL
, NULL
, TS_DRAW
, &img_size
);
1420 img_size
.cx
= img_size
.cy
= command_link_defglyph_size
;
1423 /* Get the content margins */
1426 RECT r
= { 0, 0, 0xffff, 0xffff };
1427 GetThemeBackgroundContentRect(theme
, hdc
, BP_COMMANDLINK
, CMDLS_NORMAL
, &r
, &margin
);
1428 margin
.left
-= r
.left
;
1429 margin
.top
-= r
.top
;
1430 margin
.right
= r
.right
- margin
.right
;
1431 margin
.bottom
= r
.bottom
- margin
.bottom
;
1435 margin
.left
= margin
.right
= command_link_margin
;
1436 margin
.top
= margin
.bottom
= command_link_margin
;
1439 /* Account for the border margins and the margin between image and text */
1440 w
= margin
.left
+ margin
.right
+ (img_size
.cx
? (img_size
.cx
+ command_link_margin
) : 0);
1442 /* If a rectangle with a specific width was requested, bound the text to it */
1445 text_bound
.right
= size
->cx
- w
;
1446 flags
|= DT_WORDBREAK
;
1451 if (infoPtr
->font
) old_font
= SelectObject(hdc
, infoPtr
->font
);
1453 /* Find the text's rect */
1454 if ((text
= get_button_text(infoPtr
)))
1457 GetThemeTextExtent(theme
, hdc
, BP_COMMANDLINK
, CMDLS_NORMAL
,
1458 text
, -1, flags
, &text_bound
, &r
);
1460 text_w
= r
.right
- r
.left
;
1461 text_h
= r
.bottom
- r
.top
;
1464 /* Find the note's rect */
1469 opts
.dwSize
= sizeof(opts
);
1470 opts
.dwFlags
= DTT_FONTPROP
| DTT_CALCRECT
;
1471 opts
.iFontPropId
= TMT_BODYFONT
;
1472 DrawThemeTextEx(theme
, hdc
, BP_COMMANDLINK
, CMDLS_NORMAL
,
1473 infoPtr
->note
, infoPtr
->note_length
,
1474 flags
| DT_NOPREFIX
| DT_CALCRECT
, &text_bound
, &opts
);
1475 text_w
= max(text_w
, text_bound
.right
- text_bound
.left
);
1476 text_h
+= text_bound
.bottom
- text_bound
.top
;
1481 NONCLIENTMETRICSW ncm
;
1483 ncm
.cbSize
= sizeof(ncm
);
1484 if (SystemParametersInfoW(SPI_GETNONCLIENTMETRICS
, sizeof(ncm
), &ncm
, 0))
1486 LONG note_weight
= ncm
.lfMessageFont
.lfWeight
;
1488 /* Find the text's rect */
1489 ncm
.lfMessageFont
.lfWeight
= FW_BOLD
;
1490 if ((font
= CreateFontIndirectW(&ncm
.lfMessageFont
)))
1492 if ((text
= get_button_text(infoPtr
)))
1494 RECT r
= text_bound
;
1495 old_font
= SelectObject(hdc
, font
);
1496 DrawTextW(hdc
, text
, -1, &r
, flags
| DT_CALCRECT
);
1499 text_w
= r
.right
- r
.left
;
1500 text_h
= r
.bottom
- r
.top
;
1505 /* Find the note's rect */
1506 ncm
.lfMessageFont
.lfWeight
= note_weight
;
1507 if (infoPtr
->note
&& (font
= CreateFontIndirectW(&ncm
.lfMessageFont
)))
1509 HFONT tmp
= SelectObject(hdc
, font
);
1510 if (!old_font
) old_font
= tmp
;
1512 DrawTextW(hdc
, infoPtr
->note
, infoPtr
->note_length
, &text_bound
,
1513 flags
| DT_NOPREFIX
| DT_CALCRECT
);
1516 text_w
= max(text_w
, text_bound
.right
- text_bound
.left
);
1517 text_h
+= text_bound
.bottom
- text_bound
.top
+ 2;
1523 size
->cx
= min(size
->cx
, w
);
1524 size
->cy
= max(text_h
, img_size
.cy
) + margin
.top
+ margin
.bottom
;
1526 if (old_font
) SelectObject(hdc
, old_font
);
1527 ReleaseDC(infoPtr
->hwnd
, hdc
);
1531 /**********************************************************************
1532 * BUTTON_CalcLayoutRects
1534 * Calculates the rectangles of the button label(image and text) and its parts depending on a button's style.
1536 * Returns flags to be passed to DrawText.
1537 * Calculated rectangle doesn't take into account button state
1538 * (pushed, etc.). If there is nothing to draw (no text/image) output
1539 * rectangle is empty, and return value is (UINT)-1.
1542 * infoPtr [I] Button pointer
1543 * hdc [I] Handle to device context to draw to
1544 * labelRc [I/O] Input the rect the label to be positioned in, and output the label rect
1545 * imageRc [O] Optional, output the image rect
1546 * textRc [O] Optional, output the text rect
1548 static UINT
BUTTON_CalcLayoutRects(const BUTTON_INFO
*infoPtr
, HDC hdc
, RECT
*labelRc
, RECT
*imageRc
, RECT
*textRc
)
1550 LONG style
= GetWindowLongW( infoPtr
->hwnd
, GWL_STYLE
);
1551 LONG ex_style
= GetWindowLongW( infoPtr
->hwnd
, GWL_EXSTYLE
);
1552 LONG split_style
= infoPtr
->imagelist
.himl
? BUTTON_ILStoBS(infoPtr
->imagelist
.uAlign
) : style
;
1553 WCHAR
*text
= get_button_text(infoPtr
);
1554 SIZE imageSize
= BUTTON_GetImageSize(infoPtr
);
1555 UINT dtStyle
= BUTTON_BStoDT(style
, ex_style
);
1556 RECT labelRect
, imageRect
, imageRectWithMargin
, textRect
;
1557 LONG imageMarginWidth
, imageMarginHeight
;
1558 const RECT
*textMargin
= BUTTON_GetTextMargin(infoPtr
);
1559 RECT emptyMargin
= {0};
1562 /* Calculate label rectangle according to label type */
1563 if ((imageSize
.cx
== 0 && imageSize
.cy
== 0) && (text
== NULL
|| text
[0] == '\0'))
1565 SetRectEmpty(labelRc
);
1566 SetRectEmpty(imageRc
);
1567 SetRectEmpty(textRc
);
1572 SetRect(&imageRect
, 0, 0, imageSize
.cx
, imageSize
.cy
);
1573 imageRectWithMargin
= imageRect
;
1574 if (infoPtr
->imagelist
.himl
)
1576 imageRectWithMargin
.top
-= infoPtr
->imagelist
.margin
.top
;
1577 imageRectWithMargin
.bottom
+= infoPtr
->imagelist
.margin
.bottom
;
1578 imageRectWithMargin
.left
-= infoPtr
->imagelist
.margin
.left
;
1579 imageRectWithMargin
.right
+= infoPtr
->imagelist
.margin
.right
;
1582 /* Show image only */
1583 if (show_image_only(infoPtr
))
1585 BUTTON_PositionRect(style
, labelRc
, &imageRect
,
1586 infoPtr
->imagelist
.himl
? &infoPtr
->imagelist
.margin
: &emptyMargin
);
1587 labelRect
= imageRect
;
1588 SetRectEmpty(&textRect
);
1593 maxTextWidth
= labelRc
->right
- labelRc
->left
;
1594 textRect
= BUTTON_GetTextRect(infoPtr
, hdc
, text
, maxTextWidth
);
1596 /* Show image and text */
1597 if (show_image_and_text(infoPtr
))
1599 RECT boundingLabelRect
, boundingImageRect
, boundingTextRect
;
1601 /* Get label rect */
1602 /* Image list may have different alignment than the button, use the whole rect for label in this case */
1603 if (infoPtr
->imagelist
.himl
)
1604 labelRect
= *labelRc
;
1607 /* Get a label bounding rectangle to position the label in the user specified label rectangle because
1608 * text and image need to align together. */
1609 boundingLabelRect
= BUTTON_GetBoundingLabelRect(split_style
, &textRect
, &imageRectWithMargin
);
1610 BUTTON_PositionRect(split_style
, labelRc
, &boundingLabelRect
, &emptyMargin
);
1611 labelRect
= boundingLabelRect
;
1614 /* When imagelist has center align, use the whole rect for imagelist and text */
1615 if(infoPtr
->imagelist
.himl
&& infoPtr
->imagelist
.uAlign
== BUTTON_IMAGELIST_ALIGN_CENTER
)
1617 boundingImageRect
= labelRect
;
1618 boundingTextRect
= labelRect
;
1619 BUTTON_PositionRect(split_style
, &boundingImageRect
, &imageRect
,
1620 infoPtr
->imagelist
.himl
? &infoPtr
->imagelist
.margin
: &emptyMargin
);
1621 /* Text doesn't use imagelist align */
1622 BUTTON_PositionRect(style
, &boundingTextRect
, &textRect
, textMargin
);
1626 /* Get image rect */
1627 /* Split the label rect to two halves as two bounding rectangles for image and text */
1628 boundingImageRect
= labelRect
;
1629 imageMarginWidth
= imageRectWithMargin
.right
- imageRectWithMargin
.left
;
1630 imageMarginHeight
= imageRectWithMargin
.bottom
- imageRectWithMargin
.top
;
1631 if ((split_style
& BS_CENTER
) == BS_RIGHT
)
1632 boundingImageRect
.left
= boundingImageRect
.right
- imageMarginWidth
;
1633 else if ((split_style
& BS_CENTER
) == BS_LEFT
)
1634 boundingImageRect
.right
= boundingImageRect
.left
+ imageMarginWidth
;
1635 else if ((split_style
& BS_VCENTER
) == BS_BOTTOM
)
1636 boundingImageRect
.top
= boundingImageRect
.bottom
- imageMarginHeight
;
1637 else if ((split_style
& BS_VCENTER
) == BS_TOP
)
1638 boundingImageRect
.bottom
= boundingImageRect
.top
+ imageMarginHeight
;
1640 boundingImageRect
.right
= boundingImageRect
.left
+ imageMarginWidth
;
1641 BUTTON_PositionRect(split_style
, &boundingImageRect
, &imageRect
,
1642 infoPtr
->imagelist
.himl
? &infoPtr
->imagelist
.margin
: &emptyMargin
);
1645 SubtractRect(&boundingTextRect
, &labelRect
, &boundingImageRect
);
1646 /* Text doesn't use imagelist align */
1647 BUTTON_PositionRect(style
, &boundingTextRect
, &textRect
, textMargin
);
1650 /* Show text only */
1653 if (get_button_type(style
) != BS_GROUPBOX
)
1654 BUTTON_PositionRect(style
, labelRc
, &textRect
, textMargin
);
1656 /* GroupBox is always top aligned */
1657 BUTTON_PositionRect((style
& ~BS_VCENTER
) | BS_TOP
, labelRc
, &textRect
, textMargin
);
1658 labelRect
= textRect
;
1659 SetRectEmpty(&imageRect
);
1664 CopyRect(labelRc
, &labelRect
);
1665 CopyRect(imageRc
, &imageRect
);
1666 CopyRect(textRc
, &textRect
);
1672 /**********************************************************************
1675 * Draw the button's image into the specified rectangle.
1677 static void BUTTON_DrawImage(const BUTTON_INFO
*infoPtr
, HDC hdc
, HBRUSH hbr
, UINT flags
, const RECT
*rect
)
1679 if (infoPtr
->imagelist
.himl
)
1681 int i
= (ImageList_GetImageCount(infoPtr
->imagelist
.himl
) == 1) ? 0 : get_draw_state(infoPtr
) - 1;
1683 ImageList_Draw(infoPtr
->imagelist
.himl
, i
, hdc
, rect
->left
, rect
->top
, ILD_NORMAL
);
1687 switch (infoPtr
->image_type
)
1693 flags
|= DST_BITMAP
;
1699 DrawStateW(hdc
, hbr
, NULL
, (LPARAM
)infoPtr
->u
.image
, 0, rect
->left
, rect
->top
,
1700 rect
->right
- rect
->left
, rect
->bottom
- rect
->top
, flags
);
1705 /**********************************************************************
1706 * BUTTON_DrawTextCallback
1708 * Callback function used by DrawStateW function.
1710 static BOOL CALLBACK
BUTTON_DrawTextCallback(HDC hdc
, LPARAM lp
, WPARAM wp
, int cx
, int cy
)
1714 SetRect(&rc
, 0, 0, cx
, cy
);
1715 DrawTextW(hdc
, (LPCWSTR
)lp
, -1, &rc
, (UINT
)wp
);
1719 /**********************************************************************
1722 * Common function for drawing button label.
1725 * 1. When BS_SINGLELINE is specified and text contains '\t', '\n' or '\r' in the middle, they are rendered as
1726 * squares now whereas they should be ignored.
1727 * 2. When BS_MULTILINE is specified and text contains space in the middle, the space mistakenly be rendered as newline.
1729 static void BUTTON_DrawLabel(const BUTTON_INFO
*infoPtr
, HDC hdc
, UINT dtFlags
, const RECT
*imageRect
,
1730 const RECT
*textRect
)
1733 UINT flags
= IsWindowEnabled(infoPtr
->hwnd
) ? DSS_NORMAL
: DSS_DISABLED
;
1734 LONG style
= GetWindowLongW( infoPtr
->hwnd
, GWL_STYLE
);
1737 /* FIXME: To draw disabled label in Win31 look-and-feel, we probably
1738 * must use DSS_MONO flag and COLOR_GRAYTEXT brush (or maybe DSS_UNION).
1739 * I don't have Win31 on hand to verify that, so I leave it as is.
1742 if ((style
& BS_PUSHLIKE
) && (infoPtr
->state
& BST_INDETERMINATE
))
1744 hbr
= GetSysColorBrush(COLOR_GRAYTEXT
);
1748 if (show_image(infoPtr
)) BUTTON_DrawImage(infoPtr
, hdc
, hbr
, flags
, imageRect
);
1749 if (show_image_only(infoPtr
)) return;
1751 /* DST_COMPLEX -- is 0 */
1752 if (!(text
= get_button_text(infoPtr
))) return;
1753 DrawStateW(hdc
, hbr
, BUTTON_DrawTextCallback
, (LPARAM
)text
, dtFlags
, textRect
->left
, textRect
->top
,
1754 textRect
->right
- textRect
->left
, textRect
->bottom
- textRect
->top
, flags
);
1758 /**********************************************************************
1759 * Push Button Functions
1761 static void PB_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
)
1763 RECT rc
, labelRect
, imageRect
, textRect
;
1764 UINT dtFlags
, uState
;
1768 COLORREF oldTxtColor
;
1772 LONG state
= infoPtr
->state
;
1773 LONG style
= GetWindowLongW( infoPtr
->hwnd
, GWL_STYLE
);
1774 BOOL pushedState
= (state
& BST_PUSHED
);
1778 GetClientRect( infoPtr
->hwnd
, &rc
);
1780 /* Send WM_CTLCOLOR to allow changing the font (the colors are fixed) */
1781 if ((hFont
= infoPtr
->font
)) SelectObject( hDC
, hFont
);
1782 parent
= GetParent(infoPtr
->hwnd
);
1783 if (!parent
) parent
= infoPtr
->hwnd
;
1784 SendMessageW( parent
, WM_CTLCOLORBTN
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
1786 hrgn
= set_control_clipping( hDC
, &rc
);
1788 hpen
= CreatePen( PS_SOLID
, 1, GetSysColor(COLOR_WINDOWFRAME
));
1789 hOldPen
= SelectObject(hDC
, hpen
);
1790 hOldBrush
= SelectObject(hDC
,GetSysColorBrush(COLOR_BTNFACE
));
1791 oldBkMode
= SetBkMode(hDC
, TRANSPARENT
);
1793 init_custom_draw(&nmcd
, infoPtr
, hDC
, &rc
);
1795 /* Send erase notifications */
1796 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
1797 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
1799 if (get_button_type(style
) == BS_DEFPUSHBUTTON
)
1801 if (action
!= ODA_FOCUS
)
1802 Rectangle(hDC
, rc
.left
, rc
.top
, rc
.right
, rc
.bottom
);
1803 InflateRect( &rc
, -1, -1 );
1806 /* Skip the frame drawing if only focus has changed */
1807 if (action
!= ODA_FOCUS
)
1809 uState
= DFCS_BUTTONPUSH
;
1811 if (style
& BS_FLAT
)
1812 uState
|= DFCS_MONO
;
1813 else if (pushedState
)
1815 if (get_button_type(style
) == BS_DEFPUSHBUTTON
)
1816 uState
|= DFCS_FLAT
;
1818 uState
|= DFCS_PUSHED
;
1821 if (state
& (BST_CHECKED
| BST_INDETERMINATE
))
1822 uState
|= DFCS_CHECKED
;
1824 DrawFrameControl( hDC
, &rc
, DFC_BUTTON
, uState
);
1827 if (cdrf
& CDRF_NOTIFYPOSTERASE
)
1829 nmcd
.dwDrawStage
= CDDS_POSTERASE
;
1830 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
1833 /* Send paint notifications */
1834 nmcd
.dwDrawStage
= CDDS_PREPAINT
;
1835 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
1836 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
1838 if (!(cdrf
& CDRF_DOERASE
) && action
!= ODA_FOCUS
)
1840 /* draw button label */
1842 /* Shrink label rect at all sides by 2 so that the content won't touch the surrounding frame */
1843 InflateRect(&labelRect
, -2, -2);
1844 dtFlags
= BUTTON_CalcLayoutRects(infoPtr
, hDC
, &labelRect
, &imageRect
, &textRect
);
1846 if (dtFlags
!= (UINT
)-1L)
1848 if (pushedState
) OffsetRect(&labelRect
, 1, 1);
1850 oldTxtColor
= SetTextColor( hDC
, GetSysColor(COLOR_BTNTEXT
) );
1852 BUTTON_DrawLabel(infoPtr
, hDC
, dtFlags
, &imageRect
, &textRect
);
1854 SetTextColor( hDC
, oldTxtColor
);
1858 if (cdrf
& CDRF_NOTIFYPOSTPAINT
)
1860 nmcd
.dwDrawStage
= CDDS_POSTPAINT
;
1861 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
1863 if ((cdrf
& CDRF_SKIPPOSTPAINT
) || dtFlags
== (UINT
)-1L) goto cleanup
;
1865 if (action
== ODA_FOCUS
|| (state
& BST_FOCUS
))
1867 InflateRect( &rc
, -2, -2 );
1868 DrawFocusRect( hDC
, &rc
);
1872 SelectObject( hDC
, hOldPen
);
1873 SelectObject( hDC
, hOldBrush
);
1874 SetBkMode(hDC
, oldBkMode
);
1875 SelectClipRgn( hDC
, hrgn
);
1876 if (hrgn
) DeleteObject( hrgn
);
1877 DeleteObject( hpen
);
1880 /**********************************************************************
1881 * Check Box & Radio Button Functions
1884 static void CB_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
)
1886 RECT rbox
, labelRect
, imageRect
, textRect
, client
;
1888 int delta
, text_offset
, checkBoxWidth
, checkBoxHeight
;
1893 LONG state
= infoPtr
->state
;
1894 LONG style
= GetWindowLongW( infoPtr
->hwnd
, GWL_STYLE
);
1895 LONG ex_style
= GetWindowLongW( infoPtr
->hwnd
, GWL_EXSTYLE
);
1899 if (style
& BS_PUSHLIKE
)
1901 PB_Paint( infoPtr
, hDC
, action
);
1905 GetClientRect(infoPtr
->hwnd
, &client
);
1906 rbox
= labelRect
= client
;
1908 checkBoxWidth
= 12 * GetDpiForWindow( infoPtr
->hwnd
) / 96 + 1;
1909 checkBoxHeight
= 12 * GetDpiForWindow( infoPtr
->hwnd
) / 96 + 1;
1911 if ((hFont
= infoPtr
->font
)) SelectObject( hDC
, hFont
);
1912 GetCharWidthW( hDC
, '0', '0', &text_offset
);
1915 parent
= GetParent(infoPtr
->hwnd
);
1916 if (!parent
) parent
= infoPtr
->hwnd
;
1917 hBrush
= (HBRUSH
)SendMessageW(parent
, WM_CTLCOLORSTATIC
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
1918 if (!hBrush
) /* did the app forget to call defwindowproc ? */
1919 hBrush
= (HBRUSH
)DefWindowProcW(parent
, WM_CTLCOLORSTATIC
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
1920 hrgn
= set_control_clipping( hDC
, &client
);
1922 if (style
& BS_LEFTTEXT
|| ex_style
& WS_EX_RIGHT
)
1924 labelRect
.right
-= checkBoxWidth
+ text_offset
;
1925 rbox
.left
= rbox
.right
- checkBoxWidth
;
1929 labelRect
.left
+= checkBoxWidth
+ text_offset
;
1930 rbox
.right
= checkBoxWidth
;
1933 init_custom_draw(&nmcd
, infoPtr
, hDC
, &client
);
1935 /* Send erase notifications */
1936 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
1937 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
1939 /* Since WM_ERASEBKGND does nothing, first prepare background */
1940 if (action
== ODA_SELECT
) FillRect( hDC
, &rbox
, hBrush
);
1941 if (action
== ODA_DRAWENTIRE
) FillRect( hDC
, &client
, hBrush
);
1942 if (cdrf
& CDRF_NOTIFYPOSTERASE
)
1944 nmcd
.dwDrawStage
= CDDS_POSTERASE
;
1945 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
1950 dtFlags
= BUTTON_CalcLayoutRects(infoPtr
, hDC
, &labelRect
, &imageRect
, &textRect
);
1952 /* Only adjust rbox when rtext is valid */
1953 if (dtFlags
!= (UINT
)-1L)
1955 rbox
.top
= labelRect
.top
;
1956 rbox
.bottom
= labelRect
.bottom
;
1959 /* Send paint notifications */
1960 nmcd
.dwDrawStage
= CDDS_PREPAINT
;
1961 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
1962 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
1964 /* Draw the check-box bitmap */
1965 if (!(cdrf
& CDRF_DOERASE
))
1967 if (action
== ODA_DRAWENTIRE
|| action
== ODA_SELECT
)
1971 if ((get_button_type(style
) == BS_RADIOBUTTON
) ||
1972 (get_button_type(style
) == BS_AUTORADIOBUTTON
)) flags
= DFCS_BUTTONRADIO
;
1973 else if (state
& BST_INDETERMINATE
) flags
= DFCS_BUTTON3STATE
;
1974 else flags
= DFCS_BUTTONCHECK
;
1976 if (state
& (BST_CHECKED
| BST_INDETERMINATE
)) flags
|= DFCS_CHECKED
;
1977 if (state
& BST_PUSHED
) flags
|= DFCS_PUSHED
;
1978 if (style
& WS_DISABLED
) flags
|= DFCS_INACTIVE
;
1980 /* rbox must have the correct height */
1981 delta
= rbox
.bottom
- rbox
.top
- checkBoxHeight
;
1983 if ((style
& BS_VCENTER
) == BS_TOP
)
1986 rbox
.bottom
= rbox
.top
+ checkBoxHeight
;
1989 rbox
.top
-= -delta
/ 2 + 1;
1990 rbox
.bottom
= rbox
.top
+ checkBoxHeight
;
1993 else if ((style
& BS_VCENTER
) == BS_BOTTOM
)
1996 rbox
.top
= rbox
.bottom
- checkBoxHeight
;
1999 rbox
.bottom
+= -delta
/ 2 + 1;
2000 rbox
.top
= rbox
.bottom
- checkBoxHeight
;
2007 int ofs
= delta
/ 2;
2008 rbox
.bottom
-= ofs
+ 1;
2009 rbox
.top
= rbox
.bottom
- checkBoxHeight
;
2013 int ofs
= -delta
/ 2;
2014 rbox
.top
-= ofs
+ 1;
2015 rbox
.bottom
= rbox
.top
+ checkBoxHeight
;
2019 DrawFrameControl(hDC
, &rbox
, DFC_BUTTON
, flags
);
2022 if (dtFlags
!= (UINT
)-1L) /* Something to draw */
2023 if (action
== ODA_DRAWENTIRE
) BUTTON_DrawLabel(infoPtr
, hDC
, dtFlags
, &imageRect
, &textRect
);
2026 if (cdrf
& CDRF_NOTIFYPOSTPAINT
)
2028 nmcd
.dwDrawStage
= CDDS_POSTPAINT
;
2029 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2031 if ((cdrf
& CDRF_SKIPPOSTPAINT
) || dtFlags
== (UINT
)-1L) goto cleanup
;
2034 if (action
== ODA_FOCUS
|| (state
& BST_FOCUS
))
2038 IntersectRect(&labelRect
, &labelRect
, &client
);
2039 DrawFocusRect(hDC
, &labelRect
);
2043 SelectClipRgn( hDC
, hrgn
);
2044 if (hrgn
) DeleteObject( hrgn
);
2048 /**********************************************************************
2049 * BUTTON_CheckAutoRadioButton
2051 * hwnd is checked, uncheck every other auto radio button in group
2053 static void BUTTON_CheckAutoRadioButton( HWND hwnd
)
2055 HWND parent
, sibling
, start
;
2057 parent
= GetParent(hwnd
);
2058 /* make sure that starting control is not disabled or invisible */
2059 start
= sibling
= GetNextDlgGroupItem( parent
, hwnd
, TRUE
);
2062 if (!sibling
) break;
2063 if ((hwnd
!= sibling
) &&
2064 ((GetWindowLongW( sibling
, GWL_STYLE
) & BS_TYPEMASK
) == BS_AUTORADIOBUTTON
))
2065 SendMessageW( sibling
, BM_SETCHECK
, BST_UNCHECKED
, 0 );
2066 sibling
= GetNextDlgGroupItem( parent
, sibling
, FALSE
);
2067 } while (sibling
!= start
);
2071 /**********************************************************************
2072 * Group Box Functions
2075 static void GB_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
)
2077 RECT labelRect
, imageRect
, textRect
, rcFrame
;
2082 LONG style
= GetWindowLongW( infoPtr
->hwnd
, GWL_STYLE
);
2086 if ((hFont
= infoPtr
->font
)) SelectObject( hDC
, hFont
);
2087 /* GroupBox acts like static control, so it sends CTLCOLORSTATIC */
2088 parent
= GetParent(infoPtr
->hwnd
);
2089 if (!parent
) parent
= infoPtr
->hwnd
;
2090 hbr
= (HBRUSH
)SendMessageW(parent
, WM_CTLCOLORSTATIC
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
2091 if (!hbr
) /* did the app forget to call defwindowproc ? */
2092 hbr
= (HBRUSH
)DefWindowProcW(parent
, WM_CTLCOLORSTATIC
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
2093 GetClientRect(infoPtr
->hwnd
, &labelRect
);
2094 rcFrame
= labelRect
;
2095 hrgn
= set_control_clipping(hDC
, &labelRect
);
2097 GetTextMetricsW (hDC
, &tm
);
2098 rcFrame
.top
+= (tm
.tmHeight
/ 2) - 1;
2099 DrawEdge (hDC
, &rcFrame
, EDGE_ETCHED
, BF_RECT
| ((style
& BS_FLAT
) ? BF_FLAT
: 0));
2101 InflateRect(&labelRect
, -7, 1);
2102 dtFlags
= BUTTON_CalcLayoutRects(infoPtr
, hDC
, &labelRect
, &imageRect
, &textRect
);
2104 if (dtFlags
!= (UINT
)-1)
2106 /* Because buttons have CS_PARENTDC class style, there is a chance
2107 * that label will be drawn out of client rect.
2108 * But Windows doesn't clip label's rect, so do I.
2111 /* There is 1-pixel margin at the left, right, and bottom */
2115 FillRect(hDC
, &labelRect
, hbr
);
2120 BUTTON_DrawLabel(infoPtr
, hDC
, dtFlags
, &imageRect
, &textRect
);
2122 SelectClipRgn( hDC
, hrgn
);
2123 if (hrgn
) DeleteObject( hrgn
);
2127 /**********************************************************************
2128 * User Button Functions
2131 static void UB_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
)
2138 LONG state
= infoPtr
->state
;
2141 GetClientRect( infoPtr
->hwnd
, &rc
);
2143 if ((hFont
= infoPtr
->font
)) SelectObject( hDC
, hFont
);
2145 parent
= GetParent(infoPtr
->hwnd
);
2146 if (!parent
) parent
= infoPtr
->hwnd
;
2147 hBrush
= (HBRUSH
)SendMessageW(parent
, WM_CTLCOLORBTN
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
2148 if (!hBrush
) /* did the app forget to call defwindowproc ? */
2149 hBrush
= (HBRUSH
)DefWindowProcW(parent
, WM_CTLCOLORBTN
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
2151 if (action
== ODA_FOCUS
|| (state
& BST_FOCUS
))
2153 init_custom_draw(&nmcd
, infoPtr
, hDC
, &rc
);
2155 /* Send erase notifications */
2156 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2157 if (cdrf
& CDRF_SKIPDEFAULT
) goto notify
;
2160 FillRect( hDC
, &rc
, hBrush
);
2161 if (action
== ODA_FOCUS
|| (state
& BST_FOCUS
))
2163 if (cdrf
& CDRF_NOTIFYPOSTERASE
)
2165 nmcd
.dwDrawStage
= CDDS_POSTERASE
;
2166 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2169 /* Send paint notifications */
2170 nmcd
.dwDrawStage
= CDDS_PREPAINT
;
2171 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2172 if (cdrf
& CDRF_SKIPDEFAULT
) goto notify
;
2173 if (cdrf
& CDRF_NOTIFYPOSTPAINT
)
2175 nmcd
.dwDrawStage
= CDDS_POSTPAINT
;
2176 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2179 if (!(cdrf
& CDRF_SKIPPOSTPAINT
))
2180 DrawFocusRect( hDC
, &rc
);
2187 BUTTON_NOTIFY_PARENT( infoPtr
->hwnd
, (state
& BST_FOCUS
) ? BN_SETFOCUS
: BN_KILLFOCUS
);
2191 BUTTON_NOTIFY_PARENT( infoPtr
->hwnd
, (state
& BST_PUSHED
) ? BN_HILITE
: BN_UNHILITE
);
2200 /**********************************************************************
2201 * Ownerdrawn Button Functions
2204 static void OB_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
)
2206 LONG state
= infoPtr
->state
;
2208 LONG_PTR id
= GetWindowLongPtrW( infoPtr
->hwnd
, GWLP_ID
);
2213 dis
.CtlType
= ODT_BUTTON
;
2216 dis
.itemAction
= action
;
2217 dis
.itemState
= ((state
& BST_FOCUS
) ? ODS_FOCUS
: 0) |
2218 ((state
& BST_PUSHED
) ? ODS_SELECTED
: 0) |
2219 (IsWindowEnabled(infoPtr
->hwnd
) ? 0: ODS_DISABLED
);
2220 dis
.hwndItem
= infoPtr
->hwnd
;
2223 GetClientRect( infoPtr
->hwnd
, &dis
.rcItem
);
2225 if ((hFont
= infoPtr
->font
)) SelectObject( hDC
, hFont
);
2226 parent
= GetParent(infoPtr
->hwnd
);
2227 if (!parent
) parent
= infoPtr
->hwnd
;
2228 SendMessageW( parent
, WM_CTLCOLORBTN
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
2230 hrgn
= set_control_clipping( hDC
, &dis
.rcItem
);
2232 SendMessageW( GetParent(infoPtr
->hwnd
), WM_DRAWITEM
, id
, (LPARAM
)&dis
);
2233 SelectClipRgn( hDC
, hrgn
);
2234 if (hrgn
) DeleteObject( hrgn
);
2238 /**********************************************************************
2239 * Split Button Functions
2241 static void SB_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
)
2243 LONG style
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
2244 LONG state
= infoPtr
->state
;
2245 UINT dtFlags
= (UINT
)-1L;
2247 RECT rc
, push_rect
, dropdown_rect
;
2256 GetClientRect(infoPtr
->hwnd
, &rc
);
2258 /* Send WM_CTLCOLOR to allow changing the font (the colors are fixed) */
2259 if (infoPtr
->font
) SelectObject(hDC
, infoPtr
->font
);
2260 if (!(parent
= GetParent(infoPtr
->hwnd
))) parent
= infoPtr
->hwnd
;
2261 SendMessageW(parent
, WM_CTLCOLORBTN
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
2263 hrgn
= set_control_clipping(hDC
, &rc
);
2265 pen
= CreatePen(PS_SOLID
, 1, GetSysColor(COLOR_WINDOWFRAME
));
2266 old_pen
= SelectObject(hDC
, pen
);
2267 old_brush
= SelectObject(hDC
, GetSysColorBrush(COLOR_BTNFACE
));
2268 old_bk_mode
= SetBkMode(hDC
, TRANSPARENT
);
2270 init_custom_draw(&nmcd
, infoPtr
, hDC
, &rc
);
2272 /* Send erase notifications */
2273 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2274 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
2276 if (get_button_type(style
) == BS_DEFSPLITBUTTON
)
2278 if (action
!= ODA_FOCUS
)
2279 Rectangle(hDC
, rc
.left
, rc
.top
, rc
.right
, rc
.bottom
);
2280 InflateRect(&rc
, -1, -1);
2281 /* The split will now be off by 1 pixel, but
2282 that's exactly what Windows does as well */
2285 get_split_button_rects(infoPtr
, &rc
, &push_rect
, &dropdown_rect
);
2286 if (infoPtr
->split_style
& BCSS_NOSPLIT
)
2289 /* Skip the frame drawing if only focus has changed */
2290 if (action
!= ODA_FOCUS
)
2292 UINT flags
= DFCS_BUTTONPUSH
;
2294 if (style
& BS_FLAT
) flags
|= DFCS_MONO
;
2295 else if (state
& BST_PUSHED
)
2296 flags
|= (get_button_type(style
) == BS_DEFSPLITBUTTON
)
2297 ? DFCS_FLAT
: DFCS_PUSHED
;
2299 if (state
& (BST_CHECKED
| BST_INDETERMINATE
))
2300 flags
|= DFCS_CHECKED
;
2302 if (infoPtr
->split_style
& BCSS_NOSPLIT
)
2303 DrawFrameControl(hDC
, &push_rect
, DFC_BUTTON
, flags
);
2306 UINT dropdown_flags
= flags
& ~DFCS_CHECKED
;
2308 if (state
& BST_DROPDOWNPUSHED
)
2309 dropdown_flags
= (dropdown_flags
& ~DFCS_FLAT
) | DFCS_PUSHED
;
2311 /* Adjust for shadow and draw order so it looks properly */
2312 if (infoPtr
->split_style
& BCSS_ALIGNLEFT
)
2314 dropdown_rect
.right
++;
2315 DrawFrameControl(hDC
, &dropdown_rect
, DFC_BUTTON
, dropdown_flags
);
2316 dropdown_rect
.right
--;
2317 DrawFrameControl(hDC
, &push_rect
, DFC_BUTTON
, flags
);
2322 DrawFrameControl(hDC
, &push_rect
, DFC_BUTTON
, flags
);
2324 DrawFrameControl(hDC
, &dropdown_rect
, DFC_BUTTON
, dropdown_flags
);
2329 if (cdrf
& CDRF_NOTIFYPOSTERASE
)
2331 nmcd
.dwDrawStage
= CDDS_POSTERASE
;
2332 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2335 /* Send paint notifications */
2336 nmcd
.dwDrawStage
= CDDS_PREPAINT
;
2337 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2338 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
2340 /* Shrink push button rect so that the content won't touch the surrounding frame */
2341 InflateRect(&push_rect
, -2, -2);
2343 if (!(cdrf
& CDRF_DOERASE
) && action
!= ODA_FOCUS
)
2345 COLORREF old_color
= SetTextColor(hDC
, GetSysColor(COLOR_BTNTEXT
));
2346 RECT label_rect
= push_rect
, image_rect
, text_rect
;
2348 dtFlags
= BUTTON_CalcLayoutRects(infoPtr
, hDC
, &label_rect
, &image_rect
, &text_rect
);
2350 if (dtFlags
!= (UINT
)-1L)
2351 BUTTON_DrawLabel(infoPtr
, hDC
, dtFlags
, &image_rect
, &text_rect
);
2353 draw_split_button_dropdown_glyph(infoPtr
, hDC
, &dropdown_rect
);
2354 SetTextColor(hDC
, old_color
);
2357 if (cdrf
& CDRF_NOTIFYPOSTPAINT
)
2359 nmcd
.dwDrawStage
= CDDS_POSTPAINT
;
2360 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2362 if ((cdrf
& CDRF_SKIPPOSTPAINT
) || dtFlags
== (UINT
)-1L) goto cleanup
;
2364 if (action
== ODA_FOCUS
|| (state
& BST_FOCUS
))
2365 DrawFocusRect(hDC
, &push_rect
);
2368 SelectObject(hDC
, old_pen
);
2369 SelectObject(hDC
, old_brush
);
2370 SetBkMode(hDC
, old_bk_mode
);
2371 SelectClipRgn(hDC
, hrgn
);
2372 if (hrgn
) DeleteObject(hrgn
);
2376 /* Given the full button rect of the split button, retrieve the push part and the dropdown part */
2377 static inline void get_split_button_rects(const BUTTON_INFO
*infoPtr
, const RECT
*button_rect
,
2378 RECT
*push_rect
, RECT
*dropdown_rect
)
2380 *push_rect
= *dropdown_rect
= *button_rect
;
2382 /* The dropdown takes priority if the client rect is too small, it will only have a dropdown */
2383 if (infoPtr
->split_style
& BCSS_ALIGNLEFT
)
2385 dropdown_rect
->right
= min(button_rect
->left
+ infoPtr
->glyph_size
.cx
, button_rect
->right
);
2386 push_rect
->left
= dropdown_rect
->right
;
2390 dropdown_rect
->left
= max(button_rect
->right
- infoPtr
->glyph_size
.cx
, button_rect
->left
);
2391 push_rect
->right
= dropdown_rect
->left
;
2395 /* Notify the parent if the point is within the dropdown and return TRUE (always notify if NULL) */
2396 static BOOL
notify_split_button_dropdown(const BUTTON_INFO
*infoPtr
, const POINT
*pt
, HWND hwnd
)
2400 GetClientRect(hwnd
, &nmbcd
.rcButton
);
2403 RECT push_rect
, dropdown_rect
;
2405 get_split_button_rects(infoPtr
, &nmbcd
.rcButton
, &push_rect
, &dropdown_rect
);
2406 if (!PtInRect(&dropdown_rect
, *pt
))
2409 /* If it's already down (set manually via BCM_SETDROPDOWNSTATE), fake the notify */
2410 if (infoPtr
->state
& BST_DROPDOWNPUSHED
)
2413 SendMessageW(hwnd
, BCM_SETDROPDOWNSTATE
, TRUE
, 0);
2415 nmbcd
.hdr
.hwndFrom
= hwnd
;
2416 nmbcd
.hdr
.idFrom
= GetWindowLongPtrW(hwnd
, GWLP_ID
);
2417 nmbcd
.hdr
.code
= BCN_DROPDOWN
;
2418 SendMessageW(GetParent(hwnd
), WM_NOTIFY
, nmbcd
.hdr
.idFrom
, (LPARAM
)&nmbcd
);
2420 SendMessageW(hwnd
, BCM_SETDROPDOWNSTATE
, FALSE
, 0);
2424 /* Draw the split button dropdown glyph or image */
2425 static void draw_split_button_dropdown_glyph(const BUTTON_INFO
*infoPtr
, HDC hdc
, RECT
*rect
)
2427 if (infoPtr
->split_style
& BCSS_IMAGE
)
2431 /* When the glyph is an image list, Windows is very buggy with BCSS_STRETCH,
2432 positions it weirdly and doesn't even stretch it, but instead extends the
2433 image, leaking into other images in the list (or black if none). Instead,
2434 we'll ignore this and just position it at center as without BCSS_STRETCH. */
2435 if (!ImageList_GetIconSize(infoPtr
->glyph
, &w
, &h
)) return;
2437 ImageList_Draw(infoPtr
->glyph
,
2438 (ImageList_GetImageCount(infoPtr
->glyph
) == 1) ? 0 : get_draw_state(infoPtr
) - 1,
2439 hdc
, rect
->left
+ (rect
->right
- rect
->left
- w
) / 2,
2440 rect
->top
+ (rect
->bottom
- rect
->top
- h
) / 2, ILD_NORMAL
);
2442 else if (infoPtr
->glyph_size
.cy
>= 0)
2444 /* infoPtr->glyph is a character code from Marlett */
2445 HFONT font
, old_font
;
2446 LOGFONTW logfont
= { 0, 0, 0, 0, FW_NORMAL
, 0, 0, 0, SYMBOL_CHARSET
, 0, 0, 0, 0,
2448 if (infoPtr
->glyph_size
.cy
)
2450 /* BCSS_STRETCH preserves aspect ratio, uses minimum as size */
2451 if (infoPtr
->split_style
& BCSS_STRETCH
)
2452 logfont
.lfHeight
= min(infoPtr
->glyph_size
.cx
, infoPtr
->glyph_size
.cy
);
2455 logfont
.lfWidth
= infoPtr
->glyph_size
.cx
;
2456 logfont
.lfHeight
= infoPtr
->glyph_size
.cy
;
2459 else logfont
.lfHeight
= infoPtr
->glyph_size
.cx
;
2461 if ((font
= CreateFontIndirectW(&logfont
)))
2463 old_font
= SelectObject(hdc
, font
);
2464 DrawTextW(hdc
, (const WCHAR
*)&infoPtr
->glyph
, 1, rect
,
2465 DT_CENTER
| DT_VCENTER
| DT_SINGLELINE
| DT_NOCLIP
| DT_NOPREFIX
);
2466 SelectObject(hdc
, old_font
);
2473 /**********************************************************************
2474 * Command Link Functions
2476 static void CL_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
)
2478 LONG style
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
2479 LONG state
= infoPtr
->state
;
2481 RECT rc
, content_rect
;
2490 GetClientRect(infoPtr
->hwnd
, &rc
);
2492 /* Command Links are not affected by the button's font, and are based
2493 on the default message font. Furthermore, they are not affected by
2494 any of the alignment styles (and always align with the top-left). */
2495 if (!(parent
= GetParent(infoPtr
->hwnd
))) parent
= infoPtr
->hwnd
;
2496 SendMessageW(parent
, WM_CTLCOLORBTN
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
2498 hrgn
= set_control_clipping(hDC
, &rc
);
2500 pen
= CreatePen(PS_SOLID
, 1, GetSysColor(COLOR_WINDOWFRAME
));
2501 old_pen
= SelectObject(hDC
, pen
);
2502 old_brush
= SelectObject(hDC
, GetSysColorBrush(COLOR_BTNFACE
));
2503 old_bk_mode
= SetBkMode(hDC
, TRANSPARENT
);
2505 init_custom_draw(&nmcd
, infoPtr
, hDC
, &rc
);
2507 /* Send erase notifications */
2508 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2509 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
2512 if (get_button_type(style
) == BS_DEFCOMMANDLINK
)
2514 if (action
!= ODA_FOCUS
)
2515 Rectangle(hDC
, rc
.left
, rc
.top
, rc
.right
, rc
.bottom
);
2516 InflateRect(&rc
, -1, -1);
2519 /* Skip the frame drawing if only focus has changed */
2520 if (action
!= ODA_FOCUS
)
2522 if (!(state
& (BST_HOT
| BST_PUSHED
| BST_CHECKED
| BST_INDETERMINATE
)))
2523 FillRect(hDC
, &rc
, GetSysColorBrush(COLOR_BTNFACE
));
2526 UINT flags
= DFCS_BUTTONPUSH
;
2528 if (style
& BS_FLAT
) flags
|= DFCS_MONO
;
2529 else if (state
& BST_PUSHED
) flags
|= DFCS_PUSHED
;
2531 if (state
& (BST_CHECKED
| BST_INDETERMINATE
))
2532 flags
|= DFCS_CHECKED
;
2533 DrawFrameControl(hDC
, &rc
, DFC_BUTTON
, flags
);
2537 if (cdrf
& CDRF_NOTIFYPOSTERASE
)
2539 nmcd
.dwDrawStage
= CDDS_POSTERASE
;
2540 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2543 /* Send paint notifications */
2544 nmcd
.dwDrawStage
= CDDS_PREPAINT
;
2545 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2546 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
2548 if (!(cdrf
& CDRF_DOERASE
) && action
!= ODA_FOCUS
)
2550 UINT flags
= IsWindowEnabled(infoPtr
->hwnd
) ? DSS_NORMAL
: DSS_DISABLED
;
2551 COLORREF old_color
= SetTextColor(hDC
, GetSysColor(flags
== DSS_NORMAL
?
2552 COLOR_BTNTEXT
: COLOR_GRAYTEXT
));
2553 HIMAGELIST defimg
= NULL
;
2554 NONCLIENTMETRICSW ncm
;
2558 /* Command Links ignore the margins of the image list or its alignment */
2559 if (infoPtr
->u
.image
|| infoPtr
->imagelist
.himl
)
2560 img_size
= BUTTON_GetImageSize(infoPtr
);
2563 img_size
.cx
= img_size
.cy
= command_link_defglyph_size
;
2564 defimg
= ImageList_LoadImageW(COMCTL32_hModule
, (LPCWSTR
)MAKEINTRESOURCE(IDB_CMDLINK
),
2565 img_size
.cx
, 3, CLR_NONE
, IMAGE_BITMAP
, LR_CREATEDIBSECTION
);
2568 /* Shrink rect by the command link margin, except on bottom (just the frame) */
2569 InflateRect(&content_rect
, -command_link_margin
, -command_link_margin
);
2570 content_rect
.bottom
+= command_link_margin
- 2;
2572 ncm
.cbSize
= sizeof(ncm
);
2573 if (SystemParametersInfoW(SPI_GETNONCLIENTMETRICS
, sizeof(ncm
), &ncm
, 0))
2575 LONG note_weight
= ncm
.lfMessageFont
.lfWeight
;
2576 RECT r
= content_rect
;
2580 if (img_size
.cx
) r
.left
+= img_size
.cx
+ command_link_margin
;
2583 ncm
.lfMessageFont
.lfWeight
= FW_BOLD
;
2584 if ((font
= CreateFontIndirectW(&ncm
.lfMessageFont
)))
2586 if ((text
= get_button_text(infoPtr
)))
2588 SelectObject(hDC
, font
);
2589 txt_h
= DrawTextW(hDC
, text
, -1, &r
,
2590 DT_TOP
| DT_LEFT
| DT_WORDBREAK
| DT_END_ELLIPSIS
);
2597 ncm
.lfMessageFont
.lfWeight
= note_weight
;
2598 if (infoPtr
->note
&& (font
= CreateFontIndirectW(&ncm
.lfMessageFont
)))
2601 SelectObject(hDC
, font
);
2602 DrawTextW(hDC
, infoPtr
->note
, infoPtr
->note_length
, &r
,
2603 DT_TOP
| DT_LEFT
| DT_WORDBREAK
| DT_NOPREFIX
);
2608 /* Position the image at the vertical center of the drawn text (not note) */
2609 txt_h
= min(txt_h
, content_rect
.bottom
- content_rect
.top
);
2610 if (img_size
.cy
< txt_h
) content_rect
.top
+= (txt_h
- img_size
.cy
) / 2;
2612 content_rect
.right
= content_rect
.left
+ img_size
.cx
;
2613 content_rect
.bottom
= content_rect
.top
+ img_size
.cy
;
2618 if (flags
== DSS_DISABLED
) i
= 2;
2619 else if (state
& BST_HOT
) i
= 1;
2621 ImageList_Draw(defimg
, i
, hDC
, content_rect
.left
, content_rect
.top
, ILD_NORMAL
);
2622 ImageList_Destroy(defimg
);
2625 BUTTON_DrawImage(infoPtr
, hDC
, NULL
, flags
, &content_rect
);
2627 SetTextColor(hDC
, old_color
);
2630 if (cdrf
& CDRF_NOTIFYPOSTPAINT
)
2632 nmcd
.dwDrawStage
= CDDS_POSTPAINT
;
2633 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2635 if (cdrf
& CDRF_SKIPPOSTPAINT
) goto cleanup
;
2637 if (action
== ODA_FOCUS
|| (state
& BST_FOCUS
))
2639 InflateRect(&rc
, -2, -2);
2640 DrawFocusRect(hDC
, &rc
);
2644 SelectObject(hDC
, old_pen
);
2645 SelectObject(hDC
, old_brush
);
2646 SetBkMode(hDC
, old_bk_mode
);
2647 SelectClipRgn(hDC
, hrgn
);
2648 if (hrgn
) DeleteObject(hrgn
);
2653 /**********************************************************************
2654 * Themed Paint Functions
2656 static void PB_ThemedPaint(HTHEME theme
, const BUTTON_INFO
*infoPtr
, HDC hDC
, int state
, UINT dtFlags
, BOOL focused
)
2658 RECT bgRect
, textRect
;
2659 HFONT font
= infoPtr
->font
;
2660 HFONT hPrevFont
= font
? SelectObject(hDC
, font
) : NULL
;
2666 GetClientRect(infoPtr
->hwnd
, &bgRect
);
2667 GetThemeBackgroundContentRect(theme
, hDC
, BP_PUSHBUTTON
, state
, &bgRect
, &textRect
);
2668 init_custom_draw(&nmcd
, infoPtr
, hDC
, &bgRect
);
2670 parent
= GetParent(infoPtr
->hwnd
);
2671 if (!parent
) parent
= infoPtr
->hwnd
;
2673 /* Send erase notifications */
2674 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2675 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
2677 if (IsThemeBackgroundPartiallyTransparent(theme
, BP_PUSHBUTTON
, state
))
2678 DrawThemeParentBackground(infoPtr
->hwnd
, hDC
, NULL
);
2679 DrawThemeBackground(theme
, hDC
, BP_PUSHBUTTON
, state
, &bgRect
, NULL
);
2681 if (cdrf
& CDRF_NOTIFYPOSTERASE
)
2683 nmcd
.dwDrawStage
= CDDS_POSTERASE
;
2684 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2687 /* Send paint notifications */
2688 nmcd
.dwDrawStage
= CDDS_PREPAINT
;
2689 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2690 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
2692 if (!(cdrf
& CDRF_DOERASE
) && (text
= get_button_text(infoPtr
)))
2694 DrawThemeText(theme
, hDC
, BP_PUSHBUTTON
, state
, text
, lstrlenW(text
), dtFlags
, 0, &textRect
);
2698 if (cdrf
& CDRF_NOTIFYPOSTPAINT
)
2700 nmcd
.dwDrawStage
= CDDS_POSTPAINT
;
2701 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2703 if (cdrf
& CDRF_SKIPPOSTPAINT
) goto cleanup
;
2708 RECT focusRect
= bgRect
;
2710 GetThemeMargins(theme
, hDC
, BP_PUSHBUTTON
, state
, TMT_CONTENTMARGINS
, NULL
, &margins
);
2712 focusRect
.left
+= margins
.cxLeftWidth
;
2713 focusRect
.top
+= margins
.cyTopHeight
;
2714 focusRect
.right
-= margins
.cxRightWidth
;
2715 focusRect
.bottom
-= margins
.cyBottomHeight
;
2717 DrawFocusRect( hDC
, &focusRect
);
2721 if (hPrevFont
) SelectObject(hDC
, hPrevFont
);
2724 static void CB_ThemedPaint(HTHEME theme
, const BUTTON_INFO
*infoPtr
, HDC hDC
, int state
, UINT dtFlags
, BOOL focused
)
2727 RECT bgRect
, textRect
;
2728 HFONT font
, hPrevFont
= NULL
;
2729 DWORD dwStyle
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
2730 UINT btn_type
= get_button_type( dwStyle
);
2731 int part
= (btn_type
== BS_RADIOBUTTON
) || (btn_type
== BS_AUTORADIOBUTTON
) ? BP_RADIOBUTTON
: BP_CHECKBOX
;
2737 BOOL created_font
= FALSE
;
2739 HRESULT hr
= GetThemeFont(theme
, hDC
, part
, state
, TMT_FONT
, &lf
);
2740 if (SUCCEEDED(hr
)) {
2741 font
= CreateFontIndirectW(&lf
);
2743 TRACE("Failed to create font\n");
2745 TRACE("font = %s\n", debugstr_w(lf
.lfFaceName
));
2746 hPrevFont
= SelectObject(hDC
, font
);
2747 created_font
= TRUE
;
2750 font
= (HFONT
)SendMessageW(infoPtr
->hwnd
, WM_GETFONT
, 0, 0);
2751 hPrevFont
= SelectObject(hDC
, font
);
2754 if (FAILED(GetThemePartSize(theme
, hDC
, part
, state
, NULL
, TS_DRAW
, &sz
)))
2757 GetClientRect(infoPtr
->hwnd
, &bgRect
);
2758 GetThemeBackgroundContentRect(theme
, hDC
, part
, state
, &bgRect
, &textRect
);
2759 init_custom_draw(&nmcd
, infoPtr
, hDC
, &bgRect
);
2761 if (dtFlags
& DT_SINGLELINE
) /* Center the checkbox / radio button to the text. */
2762 bgRect
.top
= bgRect
.top
+ (textRect
.bottom
- textRect
.top
- sz
.cy
) / 2;
2764 /* adjust for the check/radio marker */
2765 bgRect
.bottom
= bgRect
.top
+ sz
.cy
;
2766 bgRect
.right
= bgRect
.left
+ sz
.cx
;
2767 textRect
.left
= bgRect
.right
+ 6;
2769 parent
= GetParent(infoPtr
->hwnd
);
2770 if (!parent
) parent
= infoPtr
->hwnd
;
2772 /* Send erase notifications */
2773 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2774 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
2776 DrawThemeParentBackground(infoPtr
->hwnd
, hDC
, NULL
);
2777 DrawThemeBackground(theme
, hDC
, part
, state
, &bgRect
, NULL
);
2779 if (cdrf
& CDRF_NOTIFYPOSTERASE
)
2781 nmcd
.dwDrawStage
= CDDS_POSTERASE
;
2782 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2785 /* Send paint notifications */
2786 nmcd
.dwDrawStage
= CDDS_PREPAINT
;
2787 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2788 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
2790 text
= get_button_text(infoPtr
);
2791 if (!(cdrf
& CDRF_DOERASE
) && text
)
2792 DrawThemeText(theme
, hDC
, part
, state
, text
, lstrlenW(text
), dtFlags
, 0, &textRect
);
2794 if (cdrf
& CDRF_NOTIFYPOSTPAINT
)
2796 nmcd
.dwDrawStage
= CDDS_POSTPAINT
;
2797 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2802 if (!(cdrf
& CDRF_SKIPPOSTPAINT
) && focused
)
2806 focusRect
= textRect
;
2808 DrawTextW(hDC
, text
, lstrlenW(text
), &focusRect
, dtFlags
| DT_CALCRECT
);
2810 if (focusRect
.right
< textRect
.right
) focusRect
.right
++;
2811 focusRect
.bottom
= textRect
.bottom
;
2813 DrawFocusRect( hDC
, &focusRect
);
2820 if (created_font
) DeleteObject(font
);
2821 if (hPrevFont
) SelectObject(hDC
, hPrevFont
);
2824 static void GB_ThemedPaint(HTHEME theme
, const BUTTON_INFO
*infoPtr
, HDC hDC
, int state
, UINT dtFlags
, BOOL focused
)
2826 RECT bgRect
, textRect
, contentRect
;
2827 WCHAR
*text
= get_button_text(infoPtr
);
2829 HFONT font
, hPrevFont
= NULL
;
2830 BOOL created_font
= FALSE
;
2832 HRESULT hr
= GetThemeFont(theme
, hDC
, BP_GROUPBOX
, state
, TMT_FONT
, &lf
);
2833 if (SUCCEEDED(hr
)) {
2834 font
= CreateFontIndirectW(&lf
);
2836 TRACE("Failed to create font\n");
2838 hPrevFont
= SelectObject(hDC
, font
);
2839 created_font
= TRUE
;
2842 font
= (HFONT
)SendMessageW(infoPtr
->hwnd
, WM_GETFONT
, 0, 0);
2843 hPrevFont
= SelectObject(hDC
, font
);
2846 GetClientRect(infoPtr
->hwnd
, &bgRect
);
2852 GetTextExtentPoint32W(hDC
, text
, lstrlenW(text
), &textExtent
);
2853 bgRect
.top
+= (textExtent
.cy
/ 2);
2854 textRect
.left
+= 10;
2855 textRect
.bottom
= textRect
.top
+ textExtent
.cy
;
2856 textRect
.right
= textRect
.left
+ textExtent
.cx
+ 4;
2858 ExcludeClipRect(hDC
, textRect
.left
, textRect
.top
, textRect
.right
, textRect
.bottom
);
2861 GetThemeBackgroundContentRect(theme
, hDC
, BP_GROUPBOX
, state
, &bgRect
, &contentRect
);
2862 ExcludeClipRect(hDC
, contentRect
.left
, contentRect
.top
, contentRect
.right
, contentRect
.bottom
);
2864 if (IsThemeBackgroundPartiallyTransparent(theme
, BP_GROUPBOX
, state
))
2865 DrawThemeParentBackground(infoPtr
->hwnd
, hDC
, NULL
);
2866 DrawThemeBackground(theme
, hDC
, BP_GROUPBOX
, state
, &bgRect
, NULL
);
2868 SelectClipRgn(hDC
, NULL
);
2872 InflateRect(&textRect
, -2, 0);
2873 DrawThemeText(theme
, hDC
, BP_GROUPBOX
, state
, text
, lstrlenW(text
), 0, 0, &textRect
);
2877 if (created_font
) DeleteObject(font
);
2878 if (hPrevFont
) SelectObject(hDC
, hPrevFont
);
2881 static void SB_ThemedPaint(HTHEME theme
, const BUTTON_INFO
*infoPtr
, HDC hDC
, int state
, UINT dtFlags
, BOOL focused
)
2883 HFONT old_font
= infoPtr
->font
? SelectObject(hDC
, infoPtr
->font
) : NULL
;
2884 RECT rc
, content_rect
, push_rect
, dropdown_rect
;
2889 GetClientRect(infoPtr
->hwnd
, &rc
);
2890 init_custom_draw(&nmcd
, infoPtr
, hDC
, &rc
);
2892 parent
= GetParent(infoPtr
->hwnd
);
2893 if (!parent
) parent
= infoPtr
->hwnd
;
2895 /* Send erase notifications */
2896 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2897 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
2899 if (IsThemeBackgroundPartiallyTransparent(theme
, BP_PUSHBUTTON
, state
))
2900 DrawThemeParentBackground(infoPtr
->hwnd
, hDC
, NULL
);
2902 /* The zone outside the content is ignored for the dropdown (draws over) */
2903 GetThemeBackgroundContentRect(theme
, hDC
, BP_PUSHBUTTON
, state
, &rc
, &content_rect
);
2904 get_split_button_rects(infoPtr
, &rc
, &push_rect
, &dropdown_rect
);
2906 if (infoPtr
->split_style
& BCSS_NOSPLIT
)
2909 DrawThemeBackground(theme
, hDC
, BP_PUSHBUTTON
, state
, &rc
, NULL
);
2913 RECT r
= { dropdown_rect
.left
, content_rect
.top
, dropdown_rect
.right
, content_rect
.bottom
};
2914 UINT edge
= (infoPtr
->split_style
& BCSS_ALIGNLEFT
) ? BF_RIGHT
: BF_LEFT
;
2915 const RECT
*clip
= NULL
;
2917 /* If only the dropdown is pressed, we need to draw it separately */
2918 if (state
!= PBS_PRESSED
&& (infoPtr
->state
& BST_DROPDOWNPUSHED
))
2920 DrawThemeBackground(theme
, hDC
, BP_PUSHBUTTON
, PBS_PRESSED
, &rc
, &dropdown_rect
);
2923 DrawThemeBackground(theme
, hDC
, BP_PUSHBUTTON
, state
, &rc
, clip
);
2925 /* Draw the separator */
2926 DrawThemeEdge(theme
, hDC
, BP_PUSHBUTTON
, state
, &r
, EDGE_ETCHED
, edge
, NULL
);
2928 /* The content rect should be the content area of the push button */
2929 GetThemeBackgroundContentRect(theme
, hDC
, BP_PUSHBUTTON
, state
, &push_rect
, &content_rect
);
2932 if (cdrf
& CDRF_NOTIFYPOSTERASE
)
2934 nmcd
.dwDrawStage
= CDDS_POSTERASE
;
2935 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2938 /* Send paint notifications */
2939 nmcd
.dwDrawStage
= CDDS_PREPAINT
;
2940 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2941 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
2943 if (!(cdrf
& CDRF_DOERASE
))
2945 COLORREF old_color
, color
;
2949 if ((text
= get_button_text(infoPtr
)))
2951 DrawThemeText(theme
, hDC
, BP_PUSHBUTTON
, state
, text
, lstrlenW(text
), dtFlags
, 0, &content_rect
);
2955 GetThemeColor(theme
, BP_PUSHBUTTON
, state
, TMT_TEXTCOLOR
, &color
);
2956 old_bk_mode
= SetBkMode(hDC
, TRANSPARENT
);
2957 old_color
= SetTextColor(hDC
, color
);
2959 draw_split_button_dropdown_glyph(infoPtr
, hDC
, &dropdown_rect
);
2961 SetTextColor(hDC
, old_color
);
2962 SetBkMode(hDC
, old_bk_mode
);
2965 if (cdrf
& CDRF_NOTIFYPOSTPAINT
)
2967 nmcd
.dwDrawStage
= CDDS_POSTPAINT
;
2968 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2970 if (cdrf
& CDRF_SKIPPOSTPAINT
) goto cleanup
;
2976 GetThemeMargins(theme
, hDC
, BP_PUSHBUTTON
, state
, TMT_CONTENTMARGINS
, NULL
, &margins
);
2978 push_rect
.left
+= margins
.cxLeftWidth
;
2979 push_rect
.top
+= margins
.cyTopHeight
;
2980 push_rect
.right
-= margins
.cxRightWidth
;
2981 push_rect
.bottom
-= margins
.cyBottomHeight
;
2982 DrawFocusRect(hDC
, &push_rect
);
2986 if (old_font
) SelectObject(hDC
, old_font
);
2989 static void CL_ThemedPaint(HTHEME theme
, const BUTTON_INFO
*infoPtr
, HDC hDC
, int state
, UINT dtFlags
, BOOL focused
)
2991 HFONT old_font
= infoPtr
->font
? SelectObject(hDC
, infoPtr
->font
) : NULL
;
2997 GetClientRect(infoPtr
->hwnd
, &rc
);
2998 init_custom_draw(&nmcd
, infoPtr
, hDC
, &rc
);
3000 parent
= GetParent(infoPtr
->hwnd
);
3001 if (!parent
) parent
= infoPtr
->hwnd
;
3003 /* Send erase notifications */
3004 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
3005 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
3007 if (IsThemeBackgroundPartiallyTransparent(theme
, BP_COMMANDLINK
, state
))
3008 DrawThemeParentBackground(infoPtr
->hwnd
, hDC
, NULL
);
3009 DrawThemeBackground(theme
, hDC
, BP_COMMANDLINK
, state
, &rc
, NULL
);
3011 if (cdrf
& CDRF_NOTIFYPOSTERASE
)
3013 nmcd
.dwDrawStage
= CDDS_POSTERASE
;
3014 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
3017 /* Send paint notifications */
3018 nmcd
.dwDrawStage
= CDDS_PREPAINT
;
3019 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
3020 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
3022 if (!(cdrf
& CDRF_DOERASE
))
3029 GetThemeBackgroundContentRect(theme
, hDC
, BP_COMMANDLINK
, state
, &rc
, &r
);
3031 /* The text alignment and styles are fixed and don't depend on button styles */
3032 dtFlags
= DT_TOP
| DT_LEFT
| DT_WORDBREAK
;
3034 /* Command Links ignore the margins of the image list or its alignment */
3035 if (infoPtr
->u
.image
|| infoPtr
->imagelist
.himl
)
3036 img_size
= BUTTON_GetImageSize(infoPtr
);
3038 GetThemePartSize(theme
, NULL
, BP_COMMANDLINKGLYPH
, state
, NULL
, TS_DRAW
, &img_size
);
3041 if (img_size
.cx
) r
.left
+= img_size
.cx
+ command_link_margin
;
3044 if ((text
= get_button_text(infoPtr
)))
3046 UINT len
= lstrlenW(text
);
3049 GetThemeTextExtent(theme
, hDC
, BP_COMMANDLINK
, state
, text
, len
,
3050 dtFlags
| DT_END_ELLIPSIS
, &r
, &text_rect
);
3051 DrawThemeText(theme
, hDC
, BP_COMMANDLINK
, state
, text
, len
,
3052 dtFlags
| DT_END_ELLIPSIS
, 0, &r
);
3054 txt_h
= text_rect
.bottom
- text_rect
.top
;
3064 opts
.dwSize
= sizeof(opts
);
3065 opts
.dwFlags
= DTT_FONTPROP
;
3066 opts
.iFontPropId
= TMT_BODYFONT
;
3067 DrawThemeTextEx(theme
, hDC
, BP_COMMANDLINK
, state
,
3068 infoPtr
->note
, infoPtr
->note_length
,
3069 dtFlags
| DT_NOPREFIX
, &r
, &opts
);
3072 /* Position the image at the vertical center of the drawn text (not note) */
3073 txt_h
= min(txt_h
, img_rect
.bottom
- img_rect
.top
);
3074 if (img_size
.cy
< txt_h
) img_rect
.top
+= (txt_h
- img_size
.cy
) / 2;
3076 img_rect
.right
= img_rect
.left
+ img_size
.cx
;
3077 img_rect
.bottom
= img_rect
.top
+ img_size
.cy
;
3079 if (infoPtr
->u
.image
|| infoPtr
->imagelist
.himl
)
3080 BUTTON_DrawImage(infoPtr
, hDC
, NULL
,
3081 (state
== CMDLS_DISABLED
) ? DSS_DISABLED
: DSS_NORMAL
,
3084 DrawThemeBackground(theme
, hDC
, BP_COMMANDLINKGLYPH
, state
, &img_rect
, NULL
);
3087 if (cdrf
& CDRF_NOTIFYPOSTPAINT
)
3089 nmcd
.dwDrawStage
= CDDS_POSTPAINT
;
3090 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
3092 if (cdrf
& CDRF_SKIPPOSTPAINT
) goto cleanup
;
3098 /* The focus rect has margins of a push button rather than command link... */
3099 GetThemeMargins(theme
, hDC
, BP_PUSHBUTTON
, state
, TMT_CONTENTMARGINS
, NULL
, &margins
);
3101 rc
.left
+= margins
.cxLeftWidth
;
3102 rc
.top
+= margins
.cyTopHeight
;
3103 rc
.right
-= margins
.cxRightWidth
;
3104 rc
.bottom
-= margins
.cyBottomHeight
;
3105 DrawFocusRect(hDC
, &rc
);
3109 if (old_font
) SelectObject(hDC
, old_font
);
3112 void BUTTON_Register(void)
3116 memset(&wndClass
, 0, sizeof(wndClass
));
3117 wndClass
.style
= CS_GLOBALCLASS
| CS_DBLCLKS
| CS_VREDRAW
| CS_HREDRAW
| CS_PARENTDC
;
3118 wndClass
.lpfnWndProc
= BUTTON_WindowProc
;
3119 wndClass
.cbClsExtra
= 0;
3120 wndClass
.cbWndExtra
= sizeof(BUTTON_INFO
*);
3121 wndClass
.hCursor
= LoadCursorW(0, (LPWSTR
)IDC_ARROW
);
3122 wndClass
.hbrBackground
= NULL
;
3123 wndClass
.lpszClassName
= WC_BUTTONW
;
3124 RegisterClassW(&wndClass
);