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 */
101 static UINT
BUTTON_CalcLayoutRects( const BUTTON_INFO
*infoPtr
, HDC hdc
, RECT
*labelRc
, RECT
*imageRc
, RECT
*textRc
);
102 static void PB_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
);
103 static void CB_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
);
104 static void GB_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
);
105 static void UB_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
);
106 static void OB_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
);
107 static void SB_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
);
108 static void BUTTON_CheckAutoRadioButton( HWND hwnd
);
109 static void get_split_button_rects(const BUTTON_INFO
*, const RECT
*, RECT
*, RECT
*);
110 static BOOL
notify_split_button_dropdown(const BUTTON_INFO
*, const POINT
*, HWND
);
111 static void draw_split_button_dropdown_glyph(const BUTTON_INFO
*, HDC
, RECT
*);
113 #define MAX_BTN_TYPE 16
115 static const WORD maxCheckState
[MAX_BTN_TYPE
] =
117 BST_UNCHECKED
, /* BS_PUSHBUTTON */
118 BST_UNCHECKED
, /* BS_DEFPUSHBUTTON */
119 BST_CHECKED
, /* BS_CHECKBOX */
120 BST_CHECKED
, /* BS_AUTOCHECKBOX */
121 BST_CHECKED
, /* BS_RADIOBUTTON */
122 BST_INDETERMINATE
, /* BS_3STATE */
123 BST_INDETERMINATE
, /* BS_AUTO3STATE */
124 BST_UNCHECKED
, /* BS_GROUPBOX */
125 BST_UNCHECKED
, /* BS_USERBUTTON */
126 BST_CHECKED
, /* BS_AUTORADIOBUTTON */
127 BST_UNCHECKED
, /* BS_PUSHBOX */
128 BST_UNCHECKED
, /* BS_OWNERDRAW */
129 BST_UNCHECKED
, /* BS_SPLITBUTTON */
130 BST_UNCHECKED
, /* BS_DEFSPLITBUTTON */
131 BST_UNCHECKED
, /* BS_COMMANDLINK */
132 BST_UNCHECKED
/* BS_DEFCOMMANDLINK */
135 /* Generic draw states, use get_draw_state() to get specific state for button type */
146 typedef void (*pfPaint
)( const BUTTON_INFO
*infoPtr
, HDC hdc
, UINT action
);
148 static const pfPaint btnPaintFunc
[MAX_BTN_TYPE
] =
150 PB_Paint
, /* BS_PUSHBUTTON */
151 PB_Paint
, /* BS_DEFPUSHBUTTON */
152 CB_Paint
, /* BS_CHECKBOX */
153 CB_Paint
, /* BS_AUTOCHECKBOX */
154 CB_Paint
, /* BS_RADIOBUTTON */
155 CB_Paint
, /* BS_3STATE */
156 CB_Paint
, /* BS_AUTO3STATE */
157 GB_Paint
, /* BS_GROUPBOX */
158 UB_Paint
, /* BS_USERBUTTON */
159 CB_Paint
, /* BS_AUTORADIOBUTTON */
160 NULL
, /* BS_PUSHBOX */
161 OB_Paint
, /* BS_OWNERDRAW */
162 SB_Paint
, /* BS_SPLITBUTTON */
163 SB_Paint
, /* BS_DEFSPLITBUTTON */
164 PB_Paint
, /* BS_COMMANDLINK */
165 PB_Paint
/* BS_DEFCOMMANDLINK */
168 typedef void (*pfThemedPaint
)( HTHEME theme
, const BUTTON_INFO
*infoPtr
, HDC hdc
, int drawState
, UINT dtflags
, BOOL focused
);
170 static void PB_ThemedPaint( HTHEME theme
, const BUTTON_INFO
*infoPtr
, HDC hdc
, int drawState
, UINT dtflags
, BOOL focused
);
171 static void CB_ThemedPaint( HTHEME theme
, const BUTTON_INFO
*infoPtr
, HDC hdc
, int drawState
, UINT dtflags
, BOOL focused
);
172 static void GB_ThemedPaint( HTHEME theme
, const BUTTON_INFO
*infoPtr
, HDC hdc
, int drawState
, UINT dtflags
, BOOL focused
);
173 static void SB_ThemedPaint( HTHEME theme
, const BUTTON_INFO
*infoPtr
, HDC hdc
, int drawState
, UINT dtflags
, BOOL focused
);
175 static const pfThemedPaint btnThemedPaintFunc
[MAX_BTN_TYPE
] =
177 PB_ThemedPaint
, /* BS_PUSHBUTTON */
178 PB_ThemedPaint
, /* BS_DEFPUSHBUTTON */
179 CB_ThemedPaint
, /* BS_CHECKBOX */
180 CB_ThemedPaint
, /* BS_AUTOCHECKBOX */
181 CB_ThemedPaint
, /* BS_RADIOBUTTON */
182 CB_ThemedPaint
, /* BS_3STATE */
183 CB_ThemedPaint
, /* BS_AUTO3STATE */
184 GB_ThemedPaint
, /* BS_GROUPBOX */
185 NULL
, /* BS_USERBUTTON */
186 CB_ThemedPaint
, /* BS_AUTORADIOBUTTON */
187 NULL
, /* BS_PUSHBOX */
188 NULL
, /* BS_OWNERDRAW */
189 SB_ThemedPaint
, /* BS_SPLITBUTTON */
190 SB_ThemedPaint
, /* BS_DEFSPLITBUTTON */
191 NULL
, /* BS_COMMANDLINK */
192 NULL
, /* BS_DEFCOMMANDLINK */
195 typedef BOOL (*pfGetIdealSize
)(BUTTON_INFO
*infoPtr
, SIZE
*size
);
197 static BOOL
PB_GetIdealSize(BUTTON_INFO
*infoPtr
, SIZE
*size
);
198 static BOOL
CB_GetIdealSize(BUTTON_INFO
*infoPtr
, SIZE
*size
);
199 static BOOL
GB_GetIdealSize(BUTTON_INFO
*infoPtr
, SIZE
*size
);
200 static BOOL
SB_GetIdealSize(BUTTON_INFO
*infoPtr
, SIZE
*size
);
202 static const pfGetIdealSize btnGetIdealSizeFunc
[MAX_BTN_TYPE
] = {
203 PB_GetIdealSize
, /* BS_PUSHBUTTON */
204 PB_GetIdealSize
, /* BS_DEFPUSHBUTTON */
205 CB_GetIdealSize
, /* BS_CHECKBOX */
206 CB_GetIdealSize
, /* BS_AUTOCHECKBOX */
207 CB_GetIdealSize
, /* BS_RADIOBUTTON */
208 GB_GetIdealSize
, /* BS_3STATE */
209 GB_GetIdealSize
, /* BS_AUTO3STATE */
210 GB_GetIdealSize
, /* BS_GROUPBOX */
211 PB_GetIdealSize
, /* BS_USERBUTTON */
212 CB_GetIdealSize
, /* BS_AUTORADIOBUTTON */
213 GB_GetIdealSize
, /* BS_PUSHBOX */
214 GB_GetIdealSize
, /* BS_OWNERDRAW */
215 SB_GetIdealSize
, /* BS_SPLITBUTTON */
216 SB_GetIdealSize
, /* BS_DEFSPLITBUTTON */
217 /* GetIdealSize() for following types are unimplemented, use BS_PUSHBUTTON's for now */
218 PB_GetIdealSize
, /* BS_COMMANDLINK */
219 PB_GetIdealSize
/* BS_DEFCOMMANDLINK */
222 static inline UINT
get_button_type( LONG window_style
)
224 return (window_style
& BS_TYPEMASK
);
227 static inline BOOL
button_centers_text( LONG window_style
)
229 /* Push button's text is centered by default, same for split buttons */
230 UINT type
= get_button_type(window_style
);
231 return type
<= BS_DEFPUSHBUTTON
|| type
== BS_SPLITBUTTON
|| type
== BS_DEFSPLITBUTTON
;
234 /* paint a button of any type */
235 static inline void paint_button( BUTTON_INFO
*infoPtr
, LONG style
, UINT action
)
237 if (btnPaintFunc
[style
] && IsWindowVisible(infoPtr
->hwnd
))
239 HDC hdc
= GetDC( infoPtr
->hwnd
);
240 btnPaintFunc
[style
]( infoPtr
, hdc
, action
);
241 ReleaseDC( infoPtr
->hwnd
, hdc
);
245 /* retrieve the button text; returned buffer must be freed by caller */
246 static inline WCHAR
*get_button_text( const BUTTON_INFO
*infoPtr
)
248 INT len
= GetWindowTextLengthW( infoPtr
->hwnd
);
249 WCHAR
*buffer
= heap_alloc( (len
+ 1) * sizeof(WCHAR
) );
251 GetWindowTextW( infoPtr
->hwnd
, buffer
, len
+ 1 );
255 /* get the default glyph size for split buttons */
256 static LONG
get_default_glyph_size(const BUTTON_INFO
*infoPtr
)
258 if (infoPtr
->split_style
& BCSS_IMAGE
)
260 /* Size it to fit, including the left and right edges */
262 if (!ImageList_GetIconSize(infoPtr
->glyph
, &w
, &h
)) w
= 0;
263 return w
+ GetSystemMetrics(SM_CXEDGE
) * 2;
266 /* The glyph size relies on the default menu font's cell height */
267 return GetSystemMetrics(SM_CYMENUCHECK
);
270 static void init_custom_draw(NMCUSTOMDRAW
*nmcd
, const BUTTON_INFO
*infoPtr
, HDC hdc
, const RECT
*rc
)
272 nmcd
->hdr
.hwndFrom
= infoPtr
->hwnd
;
273 nmcd
->hdr
.idFrom
= GetWindowLongPtrW(infoPtr
->hwnd
, GWLP_ID
);
274 nmcd
->hdr
.code
= NM_CUSTOMDRAW
;
277 nmcd
->dwDrawStage
= CDDS_PREERASE
;
278 nmcd
->dwItemSpec
= 0;
279 nmcd
->lItemlParam
= 0;
280 nmcd
->uItemState
= IsWindowEnabled(infoPtr
->hwnd
) ? 0 : CDIS_DISABLED
;
281 if (infoPtr
->state
& BST_PUSHED
) nmcd
->uItemState
|= CDIS_SELECTED
;
282 if (infoPtr
->state
& BST_FOCUS
) nmcd
->uItemState
|= CDIS_FOCUS
;
283 if (infoPtr
->state
& BST_HOT
) nmcd
->uItemState
|= CDIS_HOT
;
284 if (infoPtr
->state
& BST_INDETERMINATE
)
285 nmcd
->uItemState
|= CDIS_INDETERMINATE
;
287 /* Windows doesn't seem to send CDIS_CHECKED (it fails the tests) */
288 /* CDIS_SHOWKEYBOARDCUES is misleading, as the meaning is reversed */
289 /* FIXME: Handle it properly when we support keyboard cues? */
292 HRGN
set_control_clipping( HDC hdc
, const RECT
*rect
)
295 HRGN hrgn
= CreateRectRgn( 0, 0, 0, 0 );
297 if (GetClipRgn( hdc
, hrgn
) != 1)
299 DeleteObject( hrgn
);
302 DPtoLP( hdc
, (POINT
*)&rc
, 2 );
303 if (GetLayout( hdc
) & LAYOUT_RTL
) /* compensate for the shifting done by IntersectClipRect */
308 IntersectClipRect( hdc
, rc
.left
, rc
.top
, rc
.right
, rc
.bottom
);
312 static WCHAR
*heap_strndupW(const WCHAR
*src
, size_t length
)
314 size_t size
= (length
+ 1) * sizeof(WCHAR
);
315 WCHAR
*dst
= heap_alloc(size
);
316 if (dst
) memcpy(dst
, src
, size
);
320 /**********************************************************************
321 * Convert button styles to flags used by DrawText.
323 static UINT
BUTTON_BStoDT( DWORD style
, DWORD ex_style
)
325 UINT dtStyle
= DT_NOCLIP
; /* We use SelectClipRgn to limit output */
327 /* "Convert" pushlike buttons to pushbuttons */
328 if (style
& BS_PUSHLIKE
)
329 style
&= ~BS_TYPEMASK
;
331 if (!(style
& BS_MULTILINE
))
332 dtStyle
|= DT_SINGLELINE
;
334 dtStyle
|= DT_WORDBREAK
;
336 switch (style
& BS_CENTER
)
338 case BS_LEFT
: /* DT_LEFT is 0 */ break;
339 case BS_RIGHT
: dtStyle
|= DT_RIGHT
; break;
340 case BS_CENTER
: dtStyle
|= DT_CENTER
; break;
342 if (button_centers_text(style
)) dtStyle
|= DT_CENTER
;
345 if (ex_style
& WS_EX_RIGHT
) dtStyle
= DT_RIGHT
| (dtStyle
& ~(DT_LEFT
| DT_CENTER
));
347 /* DrawText ignores vertical alignment for multiline text,
348 * but we use these flags to align label manually.
350 if (get_button_type(style
) != BS_GROUPBOX
)
352 switch (style
& BS_VCENTER
)
354 case BS_TOP
: /* DT_TOP is 0 */ break;
355 case BS_BOTTOM
: dtStyle
|= DT_BOTTOM
; break;
356 case BS_VCENTER
: /* fall through */
357 default: dtStyle
|= DT_VCENTER
; break;
364 static int get_draw_state(const BUTTON_INFO
*infoPtr
)
366 static const int pb_states
[DRAW_STATE_COUNT
] = { PBS_NORMAL
, PBS_DISABLED
, PBS_HOT
, PBS_PRESSED
, PBS_DEFAULTED
};
367 static const int cb_states
[3][DRAW_STATE_COUNT
] =
369 { CBS_UNCHECKEDNORMAL
, CBS_UNCHECKEDDISABLED
, CBS_UNCHECKEDHOT
, CBS_UNCHECKEDPRESSED
, CBS_UNCHECKEDNORMAL
},
370 { CBS_CHECKEDNORMAL
, CBS_CHECKEDDISABLED
, CBS_CHECKEDHOT
, CBS_CHECKEDPRESSED
, CBS_CHECKEDNORMAL
},
371 { CBS_MIXEDNORMAL
, CBS_MIXEDDISABLED
, CBS_MIXEDHOT
, CBS_MIXEDPRESSED
, CBS_MIXEDNORMAL
}
373 static const int rb_states
[2][DRAW_STATE_COUNT
] =
375 { RBS_UNCHECKEDNORMAL
, RBS_UNCHECKEDDISABLED
, RBS_UNCHECKEDHOT
, RBS_UNCHECKEDPRESSED
, RBS_UNCHECKEDNORMAL
},
376 { RBS_CHECKEDNORMAL
, RBS_CHECKEDDISABLED
, RBS_CHECKEDHOT
, RBS_CHECKEDPRESSED
, RBS_CHECKEDNORMAL
}
378 static const int gb_states
[DRAW_STATE_COUNT
] = { GBS_NORMAL
, GBS_DISABLED
, GBS_NORMAL
, GBS_NORMAL
, GBS_NORMAL
};
379 LONG style
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
380 UINT type
= get_button_type(style
);
381 int check_state
= infoPtr
->state
& 3;
382 enum draw_state state
;
384 if (!IsWindowEnabled(infoPtr
->hwnd
))
385 state
= STATE_DISABLED
;
386 else if (infoPtr
->state
& BST_PUSHED
)
387 state
= STATE_PRESSED
;
388 else if (infoPtr
->state
& BST_HOT
)
390 else if (infoPtr
->state
& BST_FOCUS
)
391 state
= STATE_DEFAULTED
;
393 state
= STATE_NORMAL
;
398 case BS_DEFPUSHBUTTON
:
401 case BS_DEFSPLITBUTTON
:
403 case BS_DEFCOMMANDLINK
:
404 return pb_states
[state
];
406 case BS_AUTOCHECKBOX
:
407 return cb_states
[check_state
][state
];
411 case BS_AUTORADIOBUTTON
:
412 return rb_states
[check_state
][state
];
414 return gb_states
[state
];
416 WARN("Unsupported button type 0x%08x\n", type
);
421 static LRESULT CALLBACK
BUTTON_WindowProc(HWND hWnd
, UINT uMsg
, WPARAM wParam
, LPARAM lParam
)
423 BUTTON_INFO
*infoPtr
= (BUTTON_INFO
*)GetWindowLongPtrW(hWnd
, 0);
426 LONG style
= GetWindowLongW( hWnd
, GWL_STYLE
);
427 UINT btn_type
= get_button_type( style
);
428 LONG state
, new_state
;
432 if (!IsWindow( hWnd
)) return 0;
434 if (!infoPtr
&& (uMsg
!= WM_NCCREATE
))
435 return DefWindowProcW(hWnd
, uMsg
, wParam
, lParam
);
437 pt
.x
= (short)LOWORD(lParam
);
438 pt
.y
= (short)HIWORD(lParam
);
447 case BS_PUSHBUTTON
: return DLGC_BUTTON
| DLGC_UNDEFPUSHBUTTON
;
448 case BS_DEFCOMMANDLINK
:
449 case BS_DEFPUSHBUTTON
: return DLGC_BUTTON
| DLGC_DEFPUSHBUTTON
;
451 case BS_AUTORADIOBUTTON
: return DLGC_BUTTON
| DLGC_RADIOBUTTON
;
452 case BS_GROUPBOX
: return DLGC_STATIC
;
453 case BS_SPLITBUTTON
: return DLGC_BUTTON
| DLGC_UNDEFPUSHBUTTON
| DLGC_WANTARROWS
;
454 case BS_DEFSPLITBUTTON
: return DLGC_BUTTON
| DLGC_DEFPUSHBUTTON
| DLGC_WANTARROWS
;
455 default: return DLGC_BUTTON
;
459 theme
= GetWindowTheme( hWnd
);
461 RedrawWindow( hWnd
, NULL
, NULL
, RDW_FRAME
| RDW_INVALIDATE
| RDW_UPDATENOW
);
463 paint_button( infoPtr
, btn_type
, ODA_DRAWENTIRE
);
468 CREATESTRUCTW
*cs
= (CREATESTRUCTW
*)lParam
;
470 infoPtr
= heap_alloc_zero( sizeof(*infoPtr
) );
471 SetWindowLongPtrW( hWnd
, 0, (LONG_PTR
)infoPtr
);
472 infoPtr
->hwnd
= hWnd
;
473 infoPtr
->parent
= cs
->hwndParent
;
474 infoPtr
->style
= cs
->style
;
475 infoPtr
->split_style
= BCSS_STRETCH
;
476 infoPtr
->glyph
= (HIMAGELIST
)0x36; /* Marlett down arrow char code */
477 infoPtr
->glyph_size
.cx
= get_default_glyph_size(infoPtr
);
478 return DefWindowProcW(hWnd
, uMsg
, wParam
, lParam
);
482 SetWindowLongPtrW( hWnd
, 0, 0 );
483 heap_free(infoPtr
->note
);
488 if (btn_type
>= MAX_BTN_TYPE
)
489 return -1; /* abort */
491 /* XP turns a BS_USERBUTTON into BS_PUSHBUTTON */
492 if (btn_type
== BS_USERBUTTON
)
494 style
= (style
& ~BS_TYPEMASK
) | BS_PUSHBUTTON
;
495 SetWindowLongW( hWnd
, GWL_STYLE
, style
);
497 infoPtr
->state
= BST_UNCHECKED
;
498 OpenThemeData( hWnd
, WC_BUTTONW
);
502 theme
= GetWindowTheme( hWnd
);
503 CloseThemeData( theme
);
506 case WM_THEMECHANGED
:
507 theme
= GetWindowTheme( hWnd
);
508 CloseThemeData( theme
);
509 OpenThemeData( hWnd
, WC_BUTTONW
);
513 if (btn_type
== BS_OWNERDRAW
)
515 HDC hdc
= (HDC
)wParam
;
518 HWND parent
= GetParent(hWnd
);
519 if (!parent
) parent
= hWnd
;
520 hBrush
= (HBRUSH
)SendMessageW(parent
, WM_CTLCOLORBTN
, (WPARAM
)hdc
, (LPARAM
)hWnd
);
521 if (!hBrush
) /* did the app forget to call defwindowproc ? */
522 hBrush
= (HBRUSH
)DefWindowProcW(parent
, WM_CTLCOLORBTN
,
523 (WPARAM
)hdc
, (LPARAM
)hWnd
);
524 GetClientRect(hWnd
, &rc
);
525 FillRect(hdc
, &rc
, hBrush
);
535 theme
= GetWindowTheme( hWnd
);
536 hdc
= wParam
? (HDC
)wParam
: BeginPaint( hWnd
, &ps
);
538 if (theme
&& btnThemedPaintFunc
[btn_type
])
540 int drawState
= get_draw_state(infoPtr
);
541 UINT dtflags
= BUTTON_BStoDT(style
, GetWindowLongW(hWnd
, GWL_EXSTYLE
));
543 btnThemedPaintFunc
[btn_type
](theme
, infoPtr
, hdc
, drawState
, dtflags
, infoPtr
->state
& BST_FOCUS
);
545 else if (btnPaintFunc
[btn_type
])
547 int nOldMode
= SetBkMode( hdc
, OPAQUE
);
548 btnPaintFunc
[btn_type
]( infoPtr
, hdc
, ODA_DRAWENTIRE
);
549 SetBkMode(hdc
, nOldMode
); /* reset painting mode */
552 if ( !wParam
) EndPaint( hWnd
, &ps
);
557 if (wParam
== VK_SPACE
)
559 SendMessageW( hWnd
, BM_SETSTATE
, TRUE
, 0 );
560 infoPtr
->state
|= BUTTON_BTNPRESSED
;
563 else if (wParam
== VK_UP
|| wParam
== VK_DOWN
)
565 /* Up and down arrows work on every button, and even with BCSS_NOSPLIT */
566 notify_split_button_dropdown(infoPtr
, NULL
, hWnd
);
570 case WM_LBUTTONDBLCLK
:
571 if(style
& BS_NOTIFY
||
572 btn_type
== BS_RADIOBUTTON
||
573 btn_type
== BS_USERBUTTON
||
574 btn_type
== BS_OWNERDRAW
)
576 BUTTON_NOTIFY_PARENT(hWnd
, BN_DOUBLECLICKED
);
583 if ((btn_type
== BS_SPLITBUTTON
|| btn_type
== BS_DEFSPLITBUTTON
) &&
584 !(infoPtr
->split_style
& BCSS_NOSPLIT
) &&
585 notify_split_button_dropdown(infoPtr
, &pt
, hWnd
))
589 infoPtr
->state
|= BUTTON_BTNPRESSED
;
590 SendMessageW( hWnd
, BM_SETSTATE
, TRUE
, 0 );
594 if (wParam
!= VK_SPACE
)
598 state
= infoPtr
->state
;
599 if (state
& BST_DROPDOWNPUSHED
)
600 SendMessageW(hWnd
, BCM_SETDROPDOWNSTATE
, FALSE
, 0);
601 if (!(state
& BUTTON_BTNPRESSED
)) break;
602 infoPtr
->state
&= BUTTON_NSTATES
;
603 if (!(state
& BST_PUSHED
))
608 SendMessageW( hWnd
, BM_SETSTATE
, FALSE
, 0 );
609 GetClientRect( hWnd
, &rect
);
610 if (uMsg
== WM_KEYUP
|| PtInRect( &rect
, pt
))
614 case BS_AUTOCHECKBOX
:
615 SendMessageW( hWnd
, BM_SETCHECK
, !(infoPtr
->state
& BST_CHECKED
), 0 );
617 case BS_AUTORADIOBUTTON
:
618 SendMessageW( hWnd
, BM_SETCHECK
, TRUE
, 0 );
621 SendMessageW( hWnd
, BM_SETCHECK
, (infoPtr
->state
& BST_INDETERMINATE
) ? 0 :
622 ((infoPtr
->state
& 3) + 1), 0 );
626 BUTTON_NOTIFY_PARENT(hWnd
, BN_CLICKED
);
634 case WM_CAPTURECHANGED
:
635 TRACE("WM_CAPTURECHANGED %p\n", hWnd
);
636 if (hWnd
== (HWND
)lParam
) break;
637 if (infoPtr
->state
& BUTTON_BTNPRESSED
)
639 infoPtr
->state
&= BUTTON_NSTATES
;
640 if (infoPtr
->state
& BST_PUSHED
)
641 SendMessageW( hWnd
, BM_SETSTATE
, FALSE
, 0 );
647 TRACKMOUSEEVENT mouse_event
;
649 mouse_event
.cbSize
= sizeof(TRACKMOUSEEVENT
);
650 mouse_event
.dwFlags
= TME_QUERY
;
651 if (!TrackMouseEvent(&mouse_event
) || !(mouse_event
.dwFlags
& (TME_HOVER
| TME_LEAVE
)))
653 mouse_event
.dwFlags
= TME_HOVER
| TME_LEAVE
;
654 mouse_event
.hwndTrack
= hWnd
;
655 mouse_event
.dwHoverTime
= 1;
656 TrackMouseEvent(&mouse_event
);
659 if ((wParam
& MK_LBUTTON
) && GetCapture() == hWnd
)
661 GetClientRect( hWnd
, &rect
);
662 SendMessageW( hWnd
, BM_SETSTATE
, PtInRect(&rect
, pt
), 0 );
669 infoPtr
->state
|= BST_HOT
;
670 InvalidateRect( hWnd
, NULL
, FALSE
);
676 infoPtr
->state
&= ~BST_HOT
;
677 InvalidateRect( hWnd
, NULL
, FALSE
);
683 /* Clear an old text here as Windows does */
684 if (IsWindowVisible(hWnd
))
686 HDC hdc
= GetDC(hWnd
);
689 HWND parent
= GetParent(hWnd
);
690 UINT message
= (btn_type
== BS_PUSHBUTTON
||
691 btn_type
== BS_DEFPUSHBUTTON
||
692 btn_type
== BS_USERBUTTON
||
693 btn_type
== BS_OWNERDRAW
) ?
694 WM_CTLCOLORBTN
: WM_CTLCOLORSTATIC
;
696 if (!parent
) parent
= hWnd
;
697 hbrush
= (HBRUSH
)SendMessageW(parent
, message
,
698 (WPARAM
)hdc
, (LPARAM
)hWnd
);
699 if (!hbrush
) /* did the app forget to call DefWindowProc ? */
700 hbrush
= (HBRUSH
)DefWindowProcW(parent
, message
,
701 (WPARAM
)hdc
, (LPARAM
)hWnd
);
703 GetClientRect(hWnd
, &client
);
705 /* FIXME: check other BS_* handlers */
706 if (btn_type
== BS_GROUPBOX
)
707 InflateRect(&rc
, -7, 1); /* GB_Paint does this */
708 BUTTON_CalcLayoutRects(infoPtr
, hdc
, &rc
, NULL
, NULL
);
709 /* Clip by client rect bounds */
710 if (rc
.right
> client
.right
) rc
.right
= client
.right
;
711 if (rc
.bottom
> client
.bottom
) rc
.bottom
= client
.bottom
;
712 FillRect(hdc
, &rc
, hbrush
);
713 ReleaseDC(hWnd
, hdc
);
716 DefWindowProcW( hWnd
, WM_SETTEXT
, wParam
, lParam
);
717 if (btn_type
== BS_GROUPBOX
) /* Yes, only for BS_GROUPBOX */
718 InvalidateRect( hWnd
, NULL
, TRUE
);
720 paint_button( infoPtr
, btn_type
, ODA_DRAWENTIRE
);
721 return 1; /* success. FIXME: check text length */
726 WCHAR
*note
= (WCHAR
*)lParam
;
727 if (btn_type
!= BS_COMMANDLINK
&& btn_type
!= BS_DEFCOMMANDLINK
)
729 SetLastError(ERROR_NOT_SUPPORTED
);
733 heap_free(infoPtr
->note
);
736 infoPtr
->note_length
= lstrlenW(note
);
737 infoPtr
->note
= heap_strndupW(note
, infoPtr
->note_length
);
740 if (!note
|| !infoPtr
->note
)
742 infoPtr
->note_length
= 0;
743 infoPtr
->note
= heap_alloc_zero(sizeof(WCHAR
));
746 SetLastError(NO_ERROR
);
752 DWORD
*size
= (DWORD
*)wParam
;
753 WCHAR
*buffer
= (WCHAR
*)lParam
;
756 if (btn_type
!= BS_COMMANDLINK
&& btn_type
!= BS_DEFCOMMANDLINK
)
758 SetLastError(ERROR_NOT_SUPPORTED
);
762 if (!buffer
|| !size
|| !infoPtr
->note
)
764 SetLastError(ERROR_INVALID_PARAMETER
);
770 length
= min(*size
- 1, infoPtr
->note_length
);
771 memcpy(buffer
, infoPtr
->note
, length
* sizeof(WCHAR
));
772 buffer
[length
] = '\0';
775 if (*size
< infoPtr
->note_length
+ 1)
777 *size
= infoPtr
->note_length
+ 1;
778 SetLastError(ERROR_INSUFFICIENT_BUFFER
);
783 SetLastError(NO_ERROR
);
788 case BCM_GETNOTELENGTH
:
790 if (btn_type
!= BS_COMMANDLINK
&& btn_type
!= BS_DEFCOMMANDLINK
)
792 SetLastError(ERROR_NOT_SUPPORTED
);
796 return infoPtr
->note_length
;
800 infoPtr
->font
= (HFONT
)wParam
;
801 if (lParam
) InvalidateRect(hWnd
, NULL
, TRUE
);
805 return (LRESULT
)infoPtr
->font
;
808 TRACE("WM_SETFOCUS %p\n",hWnd
);
809 infoPtr
->state
|= BST_FOCUS
;
810 paint_button( infoPtr
, btn_type
, ODA_FOCUS
);
811 if (style
& BS_NOTIFY
)
812 BUTTON_NOTIFY_PARENT(hWnd
, BN_SETFOCUS
);
816 TRACE("WM_KILLFOCUS %p\n",hWnd
);
817 infoPtr
->state
&= ~BST_FOCUS
;
819 if ((infoPtr
->state
& BUTTON_BTNPRESSED
) && GetCapture() == hWnd
)
821 if (style
& BS_NOTIFY
)
822 BUTTON_NOTIFY_PARENT(hWnd
, BN_KILLFOCUS
);
824 InvalidateRect( hWnd
, NULL
, FALSE
);
827 case WM_SYSCOLORCHANGE
:
828 InvalidateRect( hWnd
, NULL
, FALSE
);
832 btn_type
= wParam
& BS_TYPEMASK
;
833 style
= (style
& ~BS_TYPEMASK
) | btn_type
;
834 SetWindowLongW( hWnd
, GWL_STYLE
, style
);
836 /* Only redraw if lParam flag is set.*/
838 InvalidateRect( hWnd
, NULL
, TRUE
);
843 SendMessageW( hWnd
, WM_LBUTTONDOWN
, 0, 0 );
844 SendMessageW( hWnd
, WM_LBUTTONUP
, 0, 0 );
848 infoPtr
->image_type
= (DWORD
)wParam
;
849 oldHbitmap
= infoPtr
->u
.image
;
850 infoPtr
->u
.image
= (HANDLE
)lParam
;
851 InvalidateRect( hWnd
, NULL
, FALSE
);
852 return (LRESULT
)oldHbitmap
;
855 return (LRESULT
)infoPtr
->u
.image
;
857 case BCM_SETIMAGELIST
:
859 BUTTON_IMAGELIST
*imagelist
= (BUTTON_IMAGELIST
*)lParam
;
861 if (!imagelist
) return FALSE
;
863 infoPtr
->imagelist
= *imagelist
;
867 case BCM_GETIMAGELIST
:
869 BUTTON_IMAGELIST
*imagelist
= (BUTTON_IMAGELIST
*)lParam
;
871 if (!imagelist
) return FALSE
;
873 *imagelist
= infoPtr
->imagelist
;
877 case BCM_SETSPLITINFO
:
879 BUTTON_SPLITINFO
*info
= (BUTTON_SPLITINFO
*)lParam
;
881 if (!info
) return TRUE
;
883 if (info
->mask
& (BCSIF_GLYPH
| BCSIF_IMAGE
))
885 infoPtr
->split_style
&= ~BCSS_IMAGE
;
886 if (!(info
->mask
& BCSIF_GLYPH
))
887 infoPtr
->split_style
|= BCSS_IMAGE
;
888 infoPtr
->glyph
= info
->himlGlyph
;
889 infoPtr
->glyph_size
.cx
= infoPtr
->glyph_size
.cy
= 0;
892 if (info
->mask
& BCSIF_STYLE
)
893 infoPtr
->split_style
= info
->uSplitStyle
;
894 if (info
->mask
& BCSIF_SIZE
)
895 infoPtr
->glyph_size
= info
->size
;
897 /* Calculate fitting value for cx if invalid (cy is untouched) */
898 if (infoPtr
->glyph_size
.cx
<= 0)
899 infoPtr
->glyph_size
.cx
= get_default_glyph_size(infoPtr
);
901 /* Windows doesn't invalidate or redraw it, so we don't, either */
905 case BCM_GETSPLITINFO
:
907 BUTTON_SPLITINFO
*info
= (BUTTON_SPLITINFO
*)lParam
;
909 if (!info
) return FALSE
;
911 if (info
->mask
& BCSIF_STYLE
)
912 info
->uSplitStyle
= infoPtr
->split_style
;
913 if (info
->mask
& (BCSIF_GLYPH
| BCSIF_IMAGE
))
914 info
->himlGlyph
= infoPtr
->glyph
;
915 if (info
->mask
& BCSIF_SIZE
)
916 info
->size
= infoPtr
->glyph_size
;
922 return infoPtr
->state
& 3;
925 if (wParam
> maxCheckState
[btn_type
]) wParam
= maxCheckState
[btn_type
];
926 if ((btn_type
== BS_RADIOBUTTON
) || (btn_type
== BS_AUTORADIOBUTTON
))
928 style
= wParam
? style
| WS_TABSTOP
: style
& ~WS_TABSTOP
;
929 SetWindowLongW( hWnd
, GWL_STYLE
, style
);
931 if ((infoPtr
->state
& 3) != wParam
)
933 infoPtr
->state
= (infoPtr
->state
& ~3) | wParam
;
934 InvalidateRect( hWnd
, NULL
, FALSE
);
936 if ((btn_type
== BS_AUTORADIOBUTTON
) && (wParam
== BST_CHECKED
) && (style
& WS_CHILD
))
937 BUTTON_CheckAutoRadioButton( hWnd
);
941 return infoPtr
->state
;
944 state
= infoPtr
->state
;
945 new_state
= wParam
? BST_PUSHED
: 0;
947 if ((state
^ new_state
) & BST_PUSHED
)
952 state
&= ~BST_PUSHED
;
954 if (btn_type
== BS_USERBUTTON
)
955 BUTTON_NOTIFY_PARENT( hWnd
, (state
& BST_PUSHED
) ? BN_HILITE
: BN_UNHILITE
);
956 infoPtr
->state
= state
;
958 InvalidateRect( hWnd
, NULL
, FALSE
);
962 case BCM_SETDROPDOWNSTATE
:
963 new_state
= wParam
? BST_DROPDOWNPUSHED
: 0;
965 if ((infoPtr
->state
^ new_state
) & BST_DROPDOWNPUSHED
)
967 infoPtr
->state
&= ~BST_DROPDOWNPUSHED
;
968 infoPtr
->state
|= new_state
;
969 InvalidateRect(hWnd
, NULL
, FALSE
);
973 case BCM_SETTEXTMARGIN
:
975 RECT
*text_margin
= (RECT
*)lParam
;
977 if (!text_margin
) return FALSE
;
979 infoPtr
->text_margin
= *text_margin
;
983 case BCM_GETTEXTMARGIN
:
985 RECT
*text_margin
= (RECT
*)lParam
;
987 if (!text_margin
) return FALSE
;
989 *text_margin
= infoPtr
->text_margin
;
993 case BCM_GETIDEALSIZE
:
995 SIZE
*size
= (SIZE
*)lParam
;
997 if (!size
) return FALSE
;
999 return btnGetIdealSizeFunc
[btn_type
](infoPtr
, size
);
1003 if(btn_type
== BS_GROUPBOX
) return HTTRANSPARENT
;
1006 return DefWindowProcW(hWnd
, uMsg
, wParam
, lParam
);
1011 /* If maxWidth is zero, rectangle width is unlimited */
1012 static RECT
BUTTON_GetTextRect(const BUTTON_INFO
*infoPtr
, HDC hdc
, const WCHAR
*text
, LONG maxWidth
)
1014 LONG style
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
1015 LONG exStyle
= GetWindowLongW(infoPtr
->hwnd
, GWL_EXSTYLE
);
1016 UINT dtStyle
= BUTTON_BStoDT(style
, exStyle
);
1020 rect
.right
= maxWidth
;
1021 hPrevFont
= SelectObject(hdc
, infoPtr
->font
);
1022 /* Calculate height without DT_VCENTER and DT_BOTTOM to get the correct height */
1023 DrawTextW(hdc
, text
, -1, &rect
, (dtStyle
& ~(DT_VCENTER
| DT_BOTTOM
)) | DT_CALCRECT
);
1024 if (hPrevFont
) SelectObject(hdc
, hPrevFont
);
1029 static BOOL
show_image_only(const BUTTON_INFO
*infoPtr
)
1031 LONG style
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
1032 return (style
& (BS_ICON
| BS_BITMAP
)) && (infoPtr
->u
.image
|| infoPtr
->imagelist
.himl
);
1035 static BOOL
show_image_and_text(const BUTTON_INFO
*infoPtr
)
1037 LONG style
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
1038 UINT type
= get_button_type(style
);
1039 return !(style
& (BS_ICON
| BS_BITMAP
))
1040 && ((infoPtr
->u
.image
1041 && (type
== BS_PUSHBUTTON
|| type
== BS_DEFPUSHBUTTON
|| type
== BS_USERBUTTON
|| type
== BS_SPLITBUTTON
1042 || type
== BS_DEFSPLITBUTTON
|| type
== BS_COMMANDLINK
|| type
== BS_DEFCOMMANDLINK
))
1043 || (infoPtr
->imagelist
.himl
&& type
!= BS_GROUPBOX
));
1046 static BOOL
show_image(const BUTTON_INFO
*infoPtr
)
1048 return show_image_only(infoPtr
) || show_image_and_text(infoPtr
);
1051 /* Get a bounding rectangle that is large enough to contain a image and a text side by side.
1052 * Note: (left,top) of the result rectangle may not be (0,0), offset it by yourself if needed */
1053 static RECT
BUTTON_GetBoundingLabelRect(LONG style
, const RECT
*textRect
, const RECT
*imageRect
)
1056 RECT rect
= *imageRect
;
1057 INT textWidth
= textRect
->right
- textRect
->left
;
1058 INT textHeight
= textRect
->bottom
- textRect
->top
;
1059 INT imageWidth
= imageRect
->right
- imageRect
->left
;
1060 INT imageHeight
= imageRect
->bottom
- imageRect
->top
;
1062 if ((style
& BS_CENTER
) == BS_RIGHT
)
1063 OffsetRect(&rect
, textWidth
, 0);
1064 else if ((style
& BS_CENTER
) == BS_LEFT
)
1065 OffsetRect(&rect
, -imageWidth
, 0);
1066 else if ((style
& BS_VCENTER
) == BS_BOTTOM
)
1067 OffsetRect(&rect
, 0, textHeight
);
1068 else if ((style
& BS_VCENTER
) == BS_TOP
)
1069 OffsetRect(&rect
, 0, -imageHeight
);
1071 OffsetRect(&rect
, -imageWidth
, 0);
1073 UnionRect(&labelRect
, textRect
, &rect
);
1077 /* Position a rectangle inside a bounding rectangle according to button alignment flags */
1078 static void BUTTON_PositionRect(LONG style
, const RECT
*outerRect
, RECT
*innerRect
, const RECT
*margin
)
1080 INT width
= innerRect
->right
- innerRect
->left
;
1081 INT height
= innerRect
->bottom
- innerRect
->top
;
1083 if ((style
& WS_EX_RIGHT
) && !(style
& BS_CENTER
)) style
|= BS_CENTER
;
1085 if (!(style
& BS_CENTER
))
1087 if (button_centers_text(style
))
1093 if (!(style
& BS_VCENTER
))
1095 /* Group box's text is top aligned by default */
1096 if (get_button_type(style
) == BS_GROUPBOX
)
1100 switch (style
& BS_CENTER
)
1103 innerRect
->left
= outerRect
->left
+ (outerRect
->right
- outerRect
->left
- width
) / 2;
1104 innerRect
->right
= innerRect
->left
+ width
;
1107 innerRect
->right
= outerRect
->right
- margin
->right
;
1108 innerRect
->left
= innerRect
->right
- width
;
1112 innerRect
->left
= outerRect
->left
+ margin
->left
;
1113 innerRect
->right
= innerRect
->left
+ width
;
1117 switch (style
& BS_VCENTER
)
1120 innerRect
->top
= outerRect
->top
+ margin
->top
;
1121 innerRect
->bottom
= innerRect
->top
+ height
;
1124 innerRect
->bottom
= outerRect
->bottom
- margin
->bottom
;
1125 innerRect
->top
= innerRect
->bottom
- height
;
1129 innerRect
->top
= outerRect
->top
+ (outerRect
->bottom
- outerRect
->top
- height
) / 2;
1130 innerRect
->bottom
= innerRect
->top
+ height
;
1135 /* Convert imagelist align style to button align style */
1136 static UINT
BUTTON_ILStoBS(UINT align
)
1140 case BUTTON_IMAGELIST_ALIGN_TOP
:
1141 return BS_CENTER
| BS_TOP
;
1142 case BUTTON_IMAGELIST_ALIGN_BOTTOM
:
1143 return BS_CENTER
| BS_BOTTOM
;
1144 case BUTTON_IMAGELIST_ALIGN_CENTER
:
1145 return BS_CENTER
| BS_VCENTER
;
1146 case BUTTON_IMAGELIST_ALIGN_RIGHT
:
1147 return BS_RIGHT
| BS_VCENTER
;
1148 case BUTTON_IMAGELIST_ALIGN_LEFT
:
1150 return BS_LEFT
| BS_VCENTER
;
1154 static SIZE
BUTTON_GetImageSize(const BUTTON_INFO
*infoPtr
)
1160 /* ImageList has priority over image */
1161 if (infoPtr
->imagelist
.himl
)
1162 ImageList_GetIconSize(infoPtr
->imagelist
.himl
, &size
.cx
, &size
.cy
);
1163 else if (infoPtr
->u
.image
)
1165 if (infoPtr
->image_type
== IMAGE_ICON
)
1167 GetIconInfo(infoPtr
->u
.icon
, &iconInfo
);
1168 GetObjectW(iconInfo
.hbmColor
, sizeof(bm
), &bm
);
1169 DeleteObject(iconInfo
.hbmColor
);
1170 DeleteObject(iconInfo
.hbmMask
);
1172 else if (infoPtr
->image_type
== IMAGE_BITMAP
)
1173 GetObjectW(infoPtr
->u
.bitmap
, sizeof(bm
), &bm
);
1175 size
.cx
= bm
.bmWidth
;
1176 size
.cy
= bm
.bmHeight
;
1182 static const RECT
*BUTTON_GetTextMargin(const BUTTON_INFO
*infoPtr
)
1184 static const RECT oneMargin
= {1, 1, 1, 1};
1186 /* Use text margin only when showing both image and text, and image is not imagelist */
1187 if (show_image_and_text(infoPtr
) && !infoPtr
->imagelist
.himl
)
1188 return &infoPtr
->text_margin
;
1193 static void BUTTON_GetClientRectSize(BUTTON_INFO
*infoPtr
, SIZE
*size
)
1196 GetClientRect(infoPtr
->hwnd
, &rect
);
1197 size
->cx
= rect
.right
- rect
.left
;
1198 size
->cy
= rect
.bottom
- rect
.top
;
1201 static void BUTTON_GetTextIdealSize(BUTTON_INFO
*infoPtr
, LONG maxWidth
, SIZE
*size
)
1203 WCHAR
*text
= get_button_text(infoPtr
);
1206 const RECT
*margin
= BUTTON_GetTextMargin(infoPtr
);
1210 maxWidth
-= margin
->right
+ margin
->right
;
1211 if (maxWidth
<= 0) maxWidth
= 1;
1214 hdc
= GetDC(infoPtr
->hwnd
);
1215 rect
= BUTTON_GetTextRect(infoPtr
, hdc
, text
, maxWidth
);
1216 ReleaseDC(infoPtr
->hwnd
, hdc
);
1219 size
->cx
= rect
.right
- rect
.left
+ margin
->left
+ margin
->right
;
1220 size
->cy
= rect
.bottom
- rect
.top
+ margin
->top
+ margin
->bottom
;
1223 static void BUTTON_GetLabelIdealSize(BUTTON_INFO
*infoPtr
, LONG maxWidth
, SIZE
*size
)
1225 LONG style
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
1230 imageSize
= BUTTON_GetImageSize(infoPtr
);
1231 if (infoPtr
->imagelist
.himl
)
1233 imageSize
.cx
+= infoPtr
->imagelist
.margin
.left
+ infoPtr
->imagelist
.margin
.right
;
1234 imageSize
.cy
+= infoPtr
->imagelist
.margin
.top
+ infoPtr
->imagelist
.margin
.bottom
;
1235 if (infoPtr
->imagelist
.uAlign
== BUTTON_IMAGELIST_ALIGN_TOP
1236 || infoPtr
->imagelist
.uAlign
== BUTTON_IMAGELIST_ALIGN_BOTTOM
)
1243 /* horizontal alignment flags has priority over vertical ones if both are specified */
1244 if (!(style
& (BS_CENTER
| BS_VCENTER
)) || ((style
& BS_CENTER
) && (style
& BS_CENTER
) != BS_CENTER
)
1245 || !(style
& BS_VCENTER
) || (style
& BS_VCENTER
) == BS_VCENTER
)
1255 maxWidth
-= imageSize
.cx
;
1256 if (maxWidth
<= 0) maxWidth
= 1;
1258 BUTTON_GetTextIdealSize(infoPtr
, maxWidth
, &textSize
);
1259 size
->cx
= textSize
.cx
+ imageSize
.cx
;
1260 size
->cy
= max(textSize
.cy
, imageSize
.cy
);
1264 BUTTON_GetTextIdealSize(infoPtr
, maxWidth
, &textSize
);
1265 size
->cx
= max(textSize
.cx
, imageSize
.cx
);
1266 size
->cy
= textSize
.cy
+ imageSize
.cy
;
1270 static BOOL
GB_GetIdealSize(BUTTON_INFO
*infoPtr
, SIZE
*size
)
1272 BUTTON_GetClientRectSize(infoPtr
, size
);
1276 static BOOL
CB_GetIdealSize(BUTTON_INFO
*infoPtr
, SIZE
*size
)
1278 LONG style
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
1285 LONG checkboxWidth
, checkboxHeight
;
1288 if (SendMessageW(infoPtr
->hwnd
, WM_GETTEXTLENGTH
, 0, 0) == 0)
1290 BUTTON_GetClientRectSize(infoPtr
, size
);
1294 hdc
= GetDC(infoPtr
->hwnd
);
1295 scaleX
= GetDeviceCaps(hdc
, LOGPIXELSX
) / 96.0;
1296 scaleY
= GetDeviceCaps(hdc
, LOGPIXELSY
) / 96.0;
1297 if ((hfont
= infoPtr
->font
)) SelectObject(hdc
, hfont
);
1298 GetCharWidthW(hdc
, '0', '0', &textOffset
);
1300 ReleaseDC(infoPtr
->hwnd
, hdc
);
1302 checkboxWidth
= 12 * scaleX
+ 1;
1303 checkboxHeight
= 12 * scaleY
+ 1;
1306 maxWidth
= size
->cx
- checkboxWidth
- textOffset
;
1307 if (maxWidth
<= 0) maxWidth
= 1;
1310 /* Checkbox doesn't support both image(but not image list) and text */
1311 if (!(style
& (BS_ICON
| BS_BITMAP
)) && infoPtr
->u
.image
)
1312 BUTTON_GetTextIdealSize(infoPtr
, maxWidth
, &labelSize
);
1314 BUTTON_GetLabelIdealSize(infoPtr
, maxWidth
, &labelSize
);
1316 size
->cx
= labelSize
.cx
+ checkboxWidth
+ textOffset
;
1317 size
->cy
= max(labelSize
.cy
, checkboxHeight
);
1322 static BOOL
PB_GetIdealSize(BUTTON_INFO
*infoPtr
, SIZE
*size
)
1326 if (SendMessageW(infoPtr
->hwnd
, WM_GETTEXTLENGTH
, 0, 0) == 0)
1327 BUTTON_GetClientRectSize(infoPtr
, size
);
1330 /* Ideal size include text size even if image only flags(BS_ICON, BS_BITMAP) are specified */
1331 BUTTON_GetLabelIdealSize(infoPtr
, size
->cx
, &labelSize
);
1333 size
->cx
= labelSize
.cx
;
1334 size
->cy
= labelSize
.cy
;
1339 static BOOL
SB_GetIdealSize(BUTTON_INFO
*infoPtr
, SIZE
*size
)
1341 LONG extra_width
= infoPtr
->glyph_size
.cx
* 2 + GetSystemMetrics(SM_CXEDGE
);
1344 if (SendMessageW(infoPtr
->hwnd
, WM_GETTEXTLENGTH
, 0, 0) == 0)
1346 BUTTON_GetClientRectSize(infoPtr
, size
);
1347 size
->cx
= max(size
->cx
, extra_width
);
1351 BUTTON_GetLabelIdealSize(infoPtr
, size
->cx
, &label_size
);
1352 size
->cx
= label_size
.cx
+ ((size
->cx
== 0) ? extra_width
: 0);
1353 size
->cy
= label_size
.cy
;
1358 /**********************************************************************
1359 * BUTTON_CalcLayoutRects
1361 * Calculates the rectangles of the button label(image and text) and its parts depending on a button's style.
1363 * Returns flags to be passed to DrawText.
1364 * Calculated rectangle doesn't take into account button state
1365 * (pushed, etc.). If there is nothing to draw (no text/image) output
1366 * rectangle is empty, and return value is (UINT)-1.
1369 * infoPtr [I] Button pointer
1370 * hdc [I] Handle to device context to draw to
1371 * labelRc [I/O] Input the rect the label to be positioned in, and output the label rect
1372 * imageRc [O] Optional, output the image rect
1373 * textRc [O] Optional, output the text rect
1375 static UINT
BUTTON_CalcLayoutRects(const BUTTON_INFO
*infoPtr
, HDC hdc
, RECT
*labelRc
, RECT
*imageRc
, RECT
*textRc
)
1377 LONG style
= GetWindowLongW( infoPtr
->hwnd
, GWL_STYLE
);
1378 LONG ex_style
= GetWindowLongW( infoPtr
->hwnd
, GWL_EXSTYLE
);
1379 LONG split_style
= infoPtr
->imagelist
.himl
? BUTTON_ILStoBS(infoPtr
->imagelist
.uAlign
) : style
;
1380 WCHAR
*text
= get_button_text(infoPtr
);
1381 SIZE imageSize
= BUTTON_GetImageSize(infoPtr
);
1382 UINT dtStyle
= BUTTON_BStoDT(style
, ex_style
);
1383 RECT labelRect
, imageRect
, imageRectWithMargin
, textRect
;
1384 LONG imageMarginWidth
, imageMarginHeight
;
1385 const RECT
*textMargin
= BUTTON_GetTextMargin(infoPtr
);
1386 RECT emptyMargin
= {0};
1389 /* Calculate label rectangle according to label type */
1390 if ((imageSize
.cx
== 0 && imageSize
.cy
== 0) && (text
== NULL
|| text
[0] == '\0'))
1392 SetRectEmpty(labelRc
);
1393 SetRectEmpty(imageRc
);
1394 SetRectEmpty(textRc
);
1399 SetRect(&imageRect
, 0, 0, imageSize
.cx
, imageSize
.cy
);
1400 imageRectWithMargin
= imageRect
;
1401 if (infoPtr
->imagelist
.himl
)
1403 imageRectWithMargin
.top
-= infoPtr
->imagelist
.margin
.top
;
1404 imageRectWithMargin
.bottom
+= infoPtr
->imagelist
.margin
.bottom
;
1405 imageRectWithMargin
.left
-= infoPtr
->imagelist
.margin
.left
;
1406 imageRectWithMargin
.right
+= infoPtr
->imagelist
.margin
.right
;
1409 /* Show image only */
1410 if (show_image_only(infoPtr
))
1412 BUTTON_PositionRect(style
, labelRc
, &imageRect
,
1413 infoPtr
->imagelist
.himl
? &infoPtr
->imagelist
.margin
: &emptyMargin
);
1414 labelRect
= imageRect
;
1415 SetRectEmpty(&textRect
);
1420 maxTextWidth
= labelRc
->right
- labelRc
->left
;
1421 textRect
= BUTTON_GetTextRect(infoPtr
, hdc
, text
, maxTextWidth
);
1423 /* Show image and text */
1424 if (show_image_and_text(infoPtr
))
1426 RECT boundingLabelRect
, boundingImageRect
, boundingTextRect
;
1428 /* Get label rect */
1429 /* Image list may have different alignment than the button, use the whole rect for label in this case */
1430 if (infoPtr
->imagelist
.himl
)
1431 labelRect
= *labelRc
;
1434 /* Get a label bounding rectangle to position the label in the user specified label rectangle because
1435 * text and image need to align together. */
1436 boundingLabelRect
= BUTTON_GetBoundingLabelRect(split_style
, &textRect
, &imageRectWithMargin
);
1437 BUTTON_PositionRect(split_style
, labelRc
, &boundingLabelRect
, &emptyMargin
);
1438 labelRect
= boundingLabelRect
;
1441 /* When imagelist has center align, use the whole rect for imagelist and text */
1442 if(infoPtr
->imagelist
.himl
&& infoPtr
->imagelist
.uAlign
== BUTTON_IMAGELIST_ALIGN_CENTER
)
1444 boundingImageRect
= labelRect
;
1445 boundingTextRect
= labelRect
;
1446 BUTTON_PositionRect(split_style
, &boundingImageRect
, &imageRect
,
1447 infoPtr
->imagelist
.himl
? &infoPtr
->imagelist
.margin
: &emptyMargin
);
1448 /* Text doesn't use imagelist align */
1449 BUTTON_PositionRect(style
, &boundingTextRect
, &textRect
, textMargin
);
1453 /* Get image rect */
1454 /* Split the label rect to two halves as two bounding rectangles for image and text */
1455 boundingImageRect
= labelRect
;
1456 imageMarginWidth
= imageRectWithMargin
.right
- imageRectWithMargin
.left
;
1457 imageMarginHeight
= imageRectWithMargin
.bottom
- imageRectWithMargin
.top
;
1458 if ((split_style
& BS_CENTER
) == BS_RIGHT
)
1459 boundingImageRect
.left
= boundingImageRect
.right
- imageMarginWidth
;
1460 else if ((split_style
& BS_CENTER
) == BS_LEFT
)
1461 boundingImageRect
.right
= boundingImageRect
.left
+ imageMarginWidth
;
1462 else if ((split_style
& BS_VCENTER
) == BS_BOTTOM
)
1463 boundingImageRect
.top
= boundingImageRect
.bottom
- imageMarginHeight
;
1464 else if ((split_style
& BS_VCENTER
) == BS_TOP
)
1465 boundingImageRect
.bottom
= boundingImageRect
.top
+ imageMarginHeight
;
1467 boundingImageRect
.right
= boundingImageRect
.left
+ imageMarginWidth
;
1468 BUTTON_PositionRect(split_style
, &boundingImageRect
, &imageRect
,
1469 infoPtr
->imagelist
.himl
? &infoPtr
->imagelist
.margin
: &emptyMargin
);
1472 SubtractRect(&boundingTextRect
, &labelRect
, &boundingImageRect
);
1473 /* Text doesn't use imagelist align */
1474 BUTTON_PositionRect(style
, &boundingTextRect
, &textRect
, textMargin
);
1477 /* Show text only */
1480 if (get_button_type(style
) != BS_GROUPBOX
)
1481 BUTTON_PositionRect(style
, labelRc
, &textRect
, textMargin
);
1483 /* GroupBox is always top aligned */
1484 BUTTON_PositionRect((style
& ~BS_VCENTER
) | BS_TOP
, labelRc
, &textRect
, textMargin
);
1485 labelRect
= textRect
;
1486 SetRectEmpty(&imageRect
);
1491 CopyRect(labelRc
, &labelRect
);
1492 CopyRect(imageRc
, &imageRect
);
1493 CopyRect(textRc
, &textRect
);
1499 /**********************************************************************
1500 * BUTTON_DrawTextCallback
1502 * Callback function used by DrawStateW function.
1504 static BOOL CALLBACK
BUTTON_DrawTextCallback(HDC hdc
, LPARAM lp
, WPARAM wp
, int cx
, int cy
)
1508 SetRect(&rc
, 0, 0, cx
, cy
);
1509 DrawTextW(hdc
, (LPCWSTR
)lp
, -1, &rc
, (UINT
)wp
);
1513 /**********************************************************************
1516 * Common function for drawing button label.
1519 * 1. When BS_SINGLELINE is specified and text contains '\t', '\n' or '\r' in the middle, they are rendered as
1520 * squares now whereas they should be ignored.
1521 * 2. When BS_MULTILINE is specified and text contains space in the middle, the space mistakenly be rendered as newline.
1523 static void BUTTON_DrawLabel(const BUTTON_INFO
*infoPtr
, HDC hdc
, UINT dtFlags
, const RECT
*imageRect
,
1524 const RECT
*textRect
)
1526 DRAWSTATEPROC lpOutputProc
= NULL
;
1530 UINT flags
= IsWindowEnabled(infoPtr
->hwnd
) ? DSS_NORMAL
: DSS_DISABLED
;
1532 LONG state
= infoPtr
->state
;
1534 LONG style
= GetWindowLongW( infoPtr
->hwnd
, GWL_STYLE
);
1537 /* FIXME: To draw disabled label in Win31 look-and-feel, we probably
1538 * must use DSS_MONO flag and COLOR_GRAYTEXT brush (or maybe DSS_UNION).
1539 * I don't have Win31 on hand to verify that, so I leave it as is.
1542 if ((style
& BS_PUSHLIKE
) && (state
& BST_INDETERMINATE
))
1544 hbr
= GetSysColorBrush(COLOR_GRAYTEXT
);
1548 if (show_image(infoPtr
))
1550 if (infoPtr
->imagelist
.himl
)
1552 if (ImageList_GetImageCount(infoPtr
->imagelist
.himl
) == 1)
1553 ImageList_Draw(infoPtr
->imagelist
.himl
, 0, hdc
, imageRect
->left
, imageRect
->top
, ILD_NORMAL
);
1556 draw_state
= get_draw_state(infoPtr
);
1557 ImageList_Draw(infoPtr
->imagelist
.himl
, draw_state
- 1, hdc
, imageRect
->left
, imageRect
->top
,
1563 switch (infoPtr
->image_type
)
1566 imageFlags
= flags
| DST_ICON
;
1567 lp
= (LPARAM
)infoPtr
->u
.icon
;
1570 imageFlags
= flags
| DST_BITMAP
;
1571 lp
= (LPARAM
)infoPtr
->u
.bitmap
;
1577 DrawStateW(hdc
, hbr
, lpOutputProc
, lp
, wp
, imageRect
->left
, imageRect
->top
,
1578 imageRect
->right
- imageRect
->left
, imageRect
->bottom
- imageRect
->top
, imageFlags
);
1582 if (show_image_only(infoPtr
)) return;
1584 /* DST_COMPLEX -- is 0 */
1585 lpOutputProc
= BUTTON_DrawTextCallback
;
1586 if (!(text
= get_button_text(infoPtr
))) return;
1589 DrawStateW(hdc
, hbr
, lpOutputProc
, lp
, wp
, textRect
->left
, textRect
->top
, textRect
->right
- textRect
->left
,
1590 textRect
->bottom
- textRect
->top
, flags
);
1594 /**********************************************************************
1595 * Push Button Functions
1597 static void PB_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
)
1599 RECT rc
, labelRect
, imageRect
, textRect
;
1600 UINT dtFlags
, uState
;
1604 COLORREF oldTxtColor
;
1608 LONG state
= infoPtr
->state
;
1609 LONG style
= GetWindowLongW( infoPtr
->hwnd
, GWL_STYLE
);
1610 BOOL pushedState
= (state
& BST_PUSHED
);
1614 GetClientRect( infoPtr
->hwnd
, &rc
);
1616 /* Send WM_CTLCOLOR to allow changing the font (the colors are fixed) */
1617 if ((hFont
= infoPtr
->font
)) SelectObject( hDC
, hFont
);
1618 parent
= GetParent(infoPtr
->hwnd
);
1619 if (!parent
) parent
= infoPtr
->hwnd
;
1620 SendMessageW( parent
, WM_CTLCOLORBTN
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
1622 hrgn
= set_control_clipping( hDC
, &rc
);
1624 hpen
= CreatePen( PS_SOLID
, 1, GetSysColor(COLOR_WINDOWFRAME
));
1625 hOldPen
= SelectObject(hDC
, hpen
);
1626 hOldBrush
= SelectObject(hDC
,GetSysColorBrush(COLOR_BTNFACE
));
1627 oldBkMode
= SetBkMode(hDC
, TRANSPARENT
);
1629 init_custom_draw(&nmcd
, infoPtr
, hDC
, &rc
);
1631 /* Send erase notifications */
1632 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
1633 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
1635 if (get_button_type(style
) == BS_DEFPUSHBUTTON
)
1637 if (action
!= ODA_FOCUS
)
1638 Rectangle(hDC
, rc
.left
, rc
.top
, rc
.right
, rc
.bottom
);
1639 InflateRect( &rc
, -1, -1 );
1642 /* Skip the frame drawing if only focus has changed */
1643 if (action
!= ODA_FOCUS
)
1645 uState
= DFCS_BUTTONPUSH
;
1647 if (style
& BS_FLAT
)
1648 uState
|= DFCS_MONO
;
1649 else if (pushedState
)
1651 if (get_button_type(style
) == BS_DEFPUSHBUTTON
)
1652 uState
|= DFCS_FLAT
;
1654 uState
|= DFCS_PUSHED
;
1657 if (state
& (BST_CHECKED
| BST_INDETERMINATE
))
1658 uState
|= DFCS_CHECKED
;
1660 DrawFrameControl( hDC
, &rc
, DFC_BUTTON
, uState
);
1663 if (cdrf
& CDRF_NOTIFYPOSTERASE
)
1665 nmcd
.dwDrawStage
= CDDS_POSTERASE
;
1666 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
1669 /* Send paint notifications */
1670 nmcd
.dwDrawStage
= CDDS_PREPAINT
;
1671 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
1672 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
1674 if (!(cdrf
& CDRF_DOERASE
) && action
!= ODA_FOCUS
)
1676 /* draw button label */
1678 /* Shrink label rect at all sides by 2 so that the content won't touch the surrounding frame */
1679 InflateRect(&labelRect
, -2, -2);
1680 dtFlags
= BUTTON_CalcLayoutRects(infoPtr
, hDC
, &labelRect
, &imageRect
, &textRect
);
1682 if (dtFlags
!= (UINT
)-1L)
1684 if (pushedState
) OffsetRect(&labelRect
, 1, 1);
1686 oldTxtColor
= SetTextColor( hDC
, GetSysColor(COLOR_BTNTEXT
) );
1688 BUTTON_DrawLabel(infoPtr
, hDC
, dtFlags
, &imageRect
, &textRect
);
1690 SetTextColor( hDC
, oldTxtColor
);
1694 if (cdrf
& CDRF_NOTIFYPOSTPAINT
)
1696 nmcd
.dwDrawStage
= CDDS_POSTPAINT
;
1697 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
1699 if ((cdrf
& CDRF_SKIPPOSTPAINT
) || dtFlags
== (UINT
)-1L) goto cleanup
;
1701 if (action
== ODA_FOCUS
|| (state
& BST_FOCUS
))
1703 InflateRect( &rc
, -2, -2 );
1704 DrawFocusRect( hDC
, &rc
);
1708 SelectObject( hDC
, hOldPen
);
1709 SelectObject( hDC
, hOldBrush
);
1710 SetBkMode(hDC
, oldBkMode
);
1711 SelectClipRgn( hDC
, hrgn
);
1712 if (hrgn
) DeleteObject( hrgn
);
1713 DeleteObject( hpen
);
1716 /**********************************************************************
1717 * Check Box & Radio Button Functions
1720 static void CB_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
)
1722 RECT rbox
, labelRect
, imageRect
, textRect
, client
;
1724 int delta
, text_offset
, checkBoxWidth
, checkBoxHeight
;
1729 LONG state
= infoPtr
->state
;
1730 LONG style
= GetWindowLongW( infoPtr
->hwnd
, GWL_STYLE
);
1731 LONG ex_style
= GetWindowLongW( infoPtr
->hwnd
, GWL_EXSTYLE
);
1735 if (style
& BS_PUSHLIKE
)
1737 PB_Paint( infoPtr
, hDC
, action
);
1741 GetClientRect(infoPtr
->hwnd
, &client
);
1742 rbox
= labelRect
= client
;
1744 checkBoxWidth
= 12 * GetDpiForWindow( infoPtr
->hwnd
) / 96 + 1;
1745 checkBoxHeight
= 12 * GetDpiForWindow( infoPtr
->hwnd
) / 96 + 1;
1747 if ((hFont
= infoPtr
->font
)) SelectObject( hDC
, hFont
);
1748 GetCharWidthW( hDC
, '0', '0', &text_offset
);
1751 parent
= GetParent(infoPtr
->hwnd
);
1752 if (!parent
) parent
= infoPtr
->hwnd
;
1753 hBrush
= (HBRUSH
)SendMessageW(parent
, WM_CTLCOLORSTATIC
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
1754 if (!hBrush
) /* did the app forget to call defwindowproc ? */
1755 hBrush
= (HBRUSH
)DefWindowProcW(parent
, WM_CTLCOLORSTATIC
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
1756 hrgn
= set_control_clipping( hDC
, &client
);
1758 if (style
& BS_LEFTTEXT
|| ex_style
& WS_EX_RIGHT
)
1760 labelRect
.right
-= checkBoxWidth
+ text_offset
;
1761 rbox
.left
= rbox
.right
- checkBoxWidth
;
1765 labelRect
.left
+= checkBoxWidth
+ text_offset
;
1766 rbox
.right
= checkBoxWidth
;
1769 init_custom_draw(&nmcd
, infoPtr
, hDC
, &client
);
1771 /* Send erase notifications */
1772 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
1773 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
1775 /* Since WM_ERASEBKGND does nothing, first prepare background */
1776 if (action
== ODA_SELECT
) FillRect( hDC
, &rbox
, hBrush
);
1777 if (action
== ODA_DRAWENTIRE
) FillRect( hDC
, &client
, hBrush
);
1778 if (cdrf
& CDRF_NOTIFYPOSTERASE
)
1780 nmcd
.dwDrawStage
= CDDS_POSTERASE
;
1781 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
1786 dtFlags
= BUTTON_CalcLayoutRects(infoPtr
, hDC
, &labelRect
, &imageRect
, &textRect
);
1788 /* Only adjust rbox when rtext is valid */
1789 if (dtFlags
!= (UINT
)-1L)
1791 rbox
.top
= labelRect
.top
;
1792 rbox
.bottom
= labelRect
.bottom
;
1795 /* Send paint notifications */
1796 nmcd
.dwDrawStage
= CDDS_PREPAINT
;
1797 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
1798 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
1800 /* Draw the check-box bitmap */
1801 if (!(cdrf
& CDRF_DOERASE
))
1803 if (action
== ODA_DRAWENTIRE
|| action
== ODA_SELECT
)
1807 if ((get_button_type(style
) == BS_RADIOBUTTON
) ||
1808 (get_button_type(style
) == BS_AUTORADIOBUTTON
)) flags
= DFCS_BUTTONRADIO
;
1809 else if (state
& BST_INDETERMINATE
) flags
= DFCS_BUTTON3STATE
;
1810 else flags
= DFCS_BUTTONCHECK
;
1812 if (state
& (BST_CHECKED
| BST_INDETERMINATE
)) flags
|= DFCS_CHECKED
;
1813 if (state
& BST_PUSHED
) flags
|= DFCS_PUSHED
;
1814 if (style
& WS_DISABLED
) flags
|= DFCS_INACTIVE
;
1816 /* rbox must have the correct height */
1817 delta
= rbox
.bottom
- rbox
.top
- checkBoxHeight
;
1819 if ((style
& BS_VCENTER
) == BS_TOP
)
1822 rbox
.bottom
= rbox
.top
+ checkBoxHeight
;
1825 rbox
.top
-= -delta
/ 2 + 1;
1826 rbox
.bottom
= rbox
.top
+ checkBoxHeight
;
1829 else if ((style
& BS_VCENTER
) == BS_BOTTOM
)
1832 rbox
.top
= rbox
.bottom
- checkBoxHeight
;
1835 rbox
.bottom
+= -delta
/ 2 + 1;
1836 rbox
.top
= rbox
.bottom
- checkBoxHeight
;
1843 int ofs
= delta
/ 2;
1844 rbox
.bottom
-= ofs
+ 1;
1845 rbox
.top
= rbox
.bottom
- checkBoxHeight
;
1849 int ofs
= -delta
/ 2;
1850 rbox
.top
-= ofs
+ 1;
1851 rbox
.bottom
= rbox
.top
+ checkBoxHeight
;
1855 DrawFrameControl(hDC
, &rbox
, DFC_BUTTON
, flags
);
1858 if (dtFlags
!= (UINT
)-1L) /* Something to draw */
1859 if (action
== ODA_DRAWENTIRE
) BUTTON_DrawLabel(infoPtr
, hDC
, dtFlags
, &imageRect
, &textRect
);
1862 if (cdrf
& CDRF_NOTIFYPOSTPAINT
)
1864 nmcd
.dwDrawStage
= CDDS_POSTPAINT
;
1865 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
1867 if ((cdrf
& CDRF_SKIPPOSTPAINT
) || dtFlags
== (UINT
)-1L) goto cleanup
;
1870 if (action
== ODA_FOCUS
|| (state
& BST_FOCUS
))
1874 IntersectRect(&labelRect
, &labelRect
, &client
);
1875 DrawFocusRect(hDC
, &labelRect
);
1879 SelectClipRgn( hDC
, hrgn
);
1880 if (hrgn
) DeleteObject( hrgn
);
1884 /**********************************************************************
1885 * BUTTON_CheckAutoRadioButton
1887 * hwnd is checked, uncheck every other auto radio button in group
1889 static void BUTTON_CheckAutoRadioButton( HWND hwnd
)
1891 HWND parent
, sibling
, start
;
1893 parent
= GetParent(hwnd
);
1894 /* make sure that starting control is not disabled or invisible */
1895 start
= sibling
= GetNextDlgGroupItem( parent
, hwnd
, TRUE
);
1898 if (!sibling
) break;
1899 if ((hwnd
!= sibling
) &&
1900 ((GetWindowLongW( sibling
, GWL_STYLE
) & BS_TYPEMASK
) == BS_AUTORADIOBUTTON
))
1901 SendMessageW( sibling
, BM_SETCHECK
, BST_UNCHECKED
, 0 );
1902 sibling
= GetNextDlgGroupItem( parent
, sibling
, FALSE
);
1903 } while (sibling
!= start
);
1907 /**********************************************************************
1908 * Group Box Functions
1911 static void GB_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
)
1913 RECT labelRect
, imageRect
, textRect
, rcFrame
;
1918 LONG style
= GetWindowLongW( infoPtr
->hwnd
, GWL_STYLE
);
1922 if ((hFont
= infoPtr
->font
)) SelectObject( hDC
, hFont
);
1923 /* GroupBox acts like static control, so it sends CTLCOLORSTATIC */
1924 parent
= GetParent(infoPtr
->hwnd
);
1925 if (!parent
) parent
= infoPtr
->hwnd
;
1926 hbr
= (HBRUSH
)SendMessageW(parent
, WM_CTLCOLORSTATIC
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
1927 if (!hbr
) /* did the app forget to call defwindowproc ? */
1928 hbr
= (HBRUSH
)DefWindowProcW(parent
, WM_CTLCOLORSTATIC
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
1929 GetClientRect(infoPtr
->hwnd
, &labelRect
);
1930 rcFrame
= labelRect
;
1931 hrgn
= set_control_clipping(hDC
, &labelRect
);
1933 GetTextMetricsW (hDC
, &tm
);
1934 rcFrame
.top
+= (tm
.tmHeight
/ 2) - 1;
1935 DrawEdge (hDC
, &rcFrame
, EDGE_ETCHED
, BF_RECT
| ((style
& BS_FLAT
) ? BF_FLAT
: 0));
1937 InflateRect(&labelRect
, -7, 1);
1938 dtFlags
= BUTTON_CalcLayoutRects(infoPtr
, hDC
, &labelRect
, &imageRect
, &textRect
);
1940 if (dtFlags
!= (UINT
)-1)
1942 /* Because buttons have CS_PARENTDC class style, there is a chance
1943 * that label will be drawn out of client rect.
1944 * But Windows doesn't clip label's rect, so do I.
1947 /* There is 1-pixel margin at the left, right, and bottom */
1951 FillRect(hDC
, &labelRect
, hbr
);
1956 BUTTON_DrawLabel(infoPtr
, hDC
, dtFlags
, &imageRect
, &textRect
);
1958 SelectClipRgn( hDC
, hrgn
);
1959 if (hrgn
) DeleteObject( hrgn
);
1963 /**********************************************************************
1964 * User Button Functions
1967 static void UB_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
)
1974 LONG state
= infoPtr
->state
;
1977 GetClientRect( infoPtr
->hwnd
, &rc
);
1979 if ((hFont
= infoPtr
->font
)) SelectObject( hDC
, hFont
);
1981 parent
= GetParent(infoPtr
->hwnd
);
1982 if (!parent
) parent
= infoPtr
->hwnd
;
1983 hBrush
= (HBRUSH
)SendMessageW(parent
, WM_CTLCOLORBTN
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
1984 if (!hBrush
) /* did the app forget to call defwindowproc ? */
1985 hBrush
= (HBRUSH
)DefWindowProcW(parent
, WM_CTLCOLORBTN
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
1987 if (action
== ODA_FOCUS
|| (state
& BST_FOCUS
))
1989 init_custom_draw(&nmcd
, infoPtr
, hDC
, &rc
);
1991 /* Send erase notifications */
1992 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
1993 if (cdrf
& CDRF_SKIPDEFAULT
) goto notify
;
1996 FillRect( hDC
, &rc
, hBrush
);
1997 if (action
== ODA_FOCUS
|| (state
& BST_FOCUS
))
1999 if (cdrf
& CDRF_NOTIFYPOSTERASE
)
2001 nmcd
.dwDrawStage
= CDDS_POSTERASE
;
2002 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2005 /* Send paint notifications */
2006 nmcd
.dwDrawStage
= CDDS_PREPAINT
;
2007 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2008 if (cdrf
& CDRF_SKIPDEFAULT
) goto notify
;
2009 if (cdrf
& CDRF_NOTIFYPOSTPAINT
)
2011 nmcd
.dwDrawStage
= CDDS_POSTPAINT
;
2012 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2015 if (!(cdrf
& CDRF_SKIPPOSTPAINT
))
2016 DrawFocusRect( hDC
, &rc
);
2023 BUTTON_NOTIFY_PARENT( infoPtr
->hwnd
, (state
& BST_FOCUS
) ? BN_SETFOCUS
: BN_KILLFOCUS
);
2027 BUTTON_NOTIFY_PARENT( infoPtr
->hwnd
, (state
& BST_PUSHED
) ? BN_HILITE
: BN_UNHILITE
);
2036 /**********************************************************************
2037 * Ownerdrawn Button Functions
2040 static void OB_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
)
2042 LONG state
= infoPtr
->state
;
2044 LONG_PTR id
= GetWindowLongPtrW( infoPtr
->hwnd
, GWLP_ID
);
2049 dis
.CtlType
= ODT_BUTTON
;
2052 dis
.itemAction
= action
;
2053 dis
.itemState
= ((state
& BST_FOCUS
) ? ODS_FOCUS
: 0) |
2054 ((state
& BST_PUSHED
) ? ODS_SELECTED
: 0) |
2055 (IsWindowEnabled(infoPtr
->hwnd
) ? 0: ODS_DISABLED
);
2056 dis
.hwndItem
= infoPtr
->hwnd
;
2059 GetClientRect( infoPtr
->hwnd
, &dis
.rcItem
);
2061 if ((hFont
= infoPtr
->font
)) SelectObject( hDC
, hFont
);
2062 parent
= GetParent(infoPtr
->hwnd
);
2063 if (!parent
) parent
= infoPtr
->hwnd
;
2064 SendMessageW( parent
, WM_CTLCOLORBTN
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
2066 hrgn
= set_control_clipping( hDC
, &dis
.rcItem
);
2068 SendMessageW( GetParent(infoPtr
->hwnd
), WM_DRAWITEM
, id
, (LPARAM
)&dis
);
2069 SelectClipRgn( hDC
, hrgn
);
2070 if (hrgn
) DeleteObject( hrgn
);
2074 /**********************************************************************
2075 * Split Button Functions
2077 static void SB_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
)
2079 LONG style
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
2080 LONG state
= infoPtr
->state
;
2081 UINT dtFlags
= (UINT
)-1L;
2083 RECT rc
, push_rect
, dropdown_rect
;
2092 GetClientRect(infoPtr
->hwnd
, &rc
);
2094 /* Send WM_CTLCOLOR to allow changing the font (the colors are fixed) */
2095 if (infoPtr
->font
) SelectObject(hDC
, infoPtr
->font
);
2096 if (!(parent
= GetParent(infoPtr
->hwnd
))) parent
= infoPtr
->hwnd
;
2097 SendMessageW(parent
, WM_CTLCOLORBTN
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
2099 hrgn
= set_control_clipping(hDC
, &rc
);
2101 pen
= CreatePen(PS_SOLID
, 1, GetSysColor(COLOR_WINDOWFRAME
));
2102 old_pen
= SelectObject(hDC
, pen
);
2103 old_brush
= SelectObject(hDC
, GetSysColorBrush(COLOR_BTNFACE
));
2104 old_bk_mode
= SetBkMode(hDC
, TRANSPARENT
);
2106 init_custom_draw(&nmcd
, infoPtr
, hDC
, &rc
);
2108 /* Send erase notifications */
2109 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2110 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
2112 if (get_button_type(style
) == BS_DEFSPLITBUTTON
)
2114 if (action
!= ODA_FOCUS
)
2115 Rectangle(hDC
, rc
.left
, rc
.top
, rc
.right
, rc
.bottom
);
2116 InflateRect(&rc
, -1, -1);
2117 /* The split will now be off by 1 pixel, but
2118 that's exactly what Windows does as well */
2121 get_split_button_rects(infoPtr
, &rc
, &push_rect
, &dropdown_rect
);
2122 if (infoPtr
->split_style
& BCSS_NOSPLIT
)
2125 /* Skip the frame drawing if only focus has changed */
2126 if (action
!= ODA_FOCUS
)
2128 UINT flags
= DFCS_BUTTONPUSH
;
2130 if (style
& BS_FLAT
) flags
|= DFCS_MONO
;
2131 else if (state
& BST_PUSHED
)
2132 flags
|= (get_button_type(style
) == BS_DEFSPLITBUTTON
)
2133 ? DFCS_FLAT
: DFCS_PUSHED
;
2135 if (state
& (BST_CHECKED
| BST_INDETERMINATE
))
2136 flags
|= DFCS_CHECKED
;
2138 if (infoPtr
->split_style
& BCSS_NOSPLIT
)
2139 DrawFrameControl(hDC
, &push_rect
, DFC_BUTTON
, flags
);
2142 UINT dropdown_flags
= flags
& ~DFCS_CHECKED
;
2144 if (state
& BST_DROPDOWNPUSHED
)
2145 dropdown_flags
= (dropdown_flags
& ~DFCS_FLAT
) | DFCS_PUSHED
;
2147 /* Adjust for shadow and draw order so it looks properly */
2148 if (infoPtr
->split_style
& BCSS_ALIGNLEFT
)
2150 dropdown_rect
.right
++;
2151 DrawFrameControl(hDC
, &dropdown_rect
, DFC_BUTTON
, dropdown_flags
);
2152 dropdown_rect
.right
--;
2153 DrawFrameControl(hDC
, &push_rect
, DFC_BUTTON
, flags
);
2158 DrawFrameControl(hDC
, &push_rect
, DFC_BUTTON
, flags
);
2160 DrawFrameControl(hDC
, &dropdown_rect
, DFC_BUTTON
, dropdown_flags
);
2165 if (cdrf
& CDRF_NOTIFYPOSTERASE
)
2167 nmcd
.dwDrawStage
= CDDS_POSTERASE
;
2168 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2171 /* Send paint notifications */
2172 nmcd
.dwDrawStage
= CDDS_PREPAINT
;
2173 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2174 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
2176 /* Shrink push button rect so that the content won't touch the surrounding frame */
2177 InflateRect(&push_rect
, -2, -2);
2179 if (!(cdrf
& CDRF_DOERASE
) && action
!= ODA_FOCUS
)
2181 COLORREF old_color
= SetTextColor(hDC
, GetSysColor(COLOR_BTNTEXT
));
2182 RECT label_rect
= push_rect
, image_rect
, text_rect
;
2184 dtFlags
= BUTTON_CalcLayoutRects(infoPtr
, hDC
, &label_rect
, &image_rect
, &text_rect
);
2186 if (dtFlags
!= (UINT
)-1L)
2187 BUTTON_DrawLabel(infoPtr
, hDC
, dtFlags
, &image_rect
, &text_rect
);
2189 draw_split_button_dropdown_glyph(infoPtr
, hDC
, &dropdown_rect
);
2190 SetTextColor(hDC
, old_color
);
2193 if (cdrf
& CDRF_NOTIFYPOSTPAINT
)
2195 nmcd
.dwDrawStage
= CDDS_POSTPAINT
;
2196 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2198 if ((cdrf
& CDRF_SKIPPOSTPAINT
) || dtFlags
== (UINT
)-1L) goto cleanup
;
2200 if (action
== ODA_FOCUS
|| (state
& BST_FOCUS
))
2201 DrawFocusRect(hDC
, &push_rect
);
2204 SelectObject(hDC
, old_pen
);
2205 SelectObject(hDC
, old_brush
);
2206 SetBkMode(hDC
, old_bk_mode
);
2207 SelectClipRgn(hDC
, hrgn
);
2208 if (hrgn
) DeleteObject(hrgn
);
2212 /* Given the full button rect of the split button, retrieve the push part and the dropdown part */
2213 static inline void get_split_button_rects(const BUTTON_INFO
*infoPtr
, const RECT
*button_rect
,
2214 RECT
*push_rect
, RECT
*dropdown_rect
)
2216 *push_rect
= *dropdown_rect
= *button_rect
;
2218 /* The dropdown takes priority if the client rect is too small, it will only have a dropdown */
2219 if (infoPtr
->split_style
& BCSS_ALIGNLEFT
)
2221 dropdown_rect
->right
= min(button_rect
->left
+ infoPtr
->glyph_size
.cx
, button_rect
->right
);
2222 push_rect
->left
= dropdown_rect
->right
;
2226 dropdown_rect
->left
= max(button_rect
->right
- infoPtr
->glyph_size
.cx
, button_rect
->left
);
2227 push_rect
->right
= dropdown_rect
->left
;
2231 /* Notify the parent if the point is within the dropdown and return TRUE (always notify if NULL) */
2232 static BOOL
notify_split_button_dropdown(const BUTTON_INFO
*infoPtr
, const POINT
*pt
, HWND hwnd
)
2236 GetClientRect(hwnd
, &nmbcd
.rcButton
);
2239 RECT push_rect
, dropdown_rect
;
2241 get_split_button_rects(infoPtr
, &nmbcd
.rcButton
, &push_rect
, &dropdown_rect
);
2242 if (!PtInRect(&dropdown_rect
, *pt
))
2245 /* If it's already down (set manually via BCM_SETDROPDOWNSTATE), fake the notify */
2246 if (infoPtr
->state
& BST_DROPDOWNPUSHED
)
2249 SendMessageW(hwnd
, BCM_SETDROPDOWNSTATE
, TRUE
, 0);
2251 nmbcd
.hdr
.hwndFrom
= hwnd
;
2252 nmbcd
.hdr
.idFrom
= GetWindowLongPtrW(hwnd
, GWLP_ID
);
2253 nmbcd
.hdr
.code
= BCN_DROPDOWN
;
2254 SendMessageW(GetParent(hwnd
), WM_NOTIFY
, nmbcd
.hdr
.idFrom
, (LPARAM
)&nmbcd
);
2256 SendMessageW(hwnd
, BCM_SETDROPDOWNSTATE
, FALSE
, 0);
2260 /* Draw the split button dropdown glyph or image */
2261 static void draw_split_button_dropdown_glyph(const BUTTON_INFO
*infoPtr
, HDC hdc
, RECT
*rect
)
2263 if (infoPtr
->split_style
& BCSS_IMAGE
)
2267 /* When the glyph is an image list, Windows is very buggy with BCSS_STRETCH,
2268 positions it weirdly and doesn't even stretch it, but instead extends the
2269 image, leaking into other images in the list (or black if none). Instead,
2270 we'll ignore this and just position it at center as without BCSS_STRETCH. */
2271 if (!ImageList_GetIconSize(infoPtr
->glyph
, &w
, &h
)) return;
2273 ImageList_Draw(infoPtr
->glyph
,
2274 (ImageList_GetImageCount(infoPtr
->glyph
) == 1) ? 0 : get_draw_state(infoPtr
) - 1,
2275 hdc
, rect
->left
+ (rect
->right
- rect
->left
- w
) / 2,
2276 rect
->top
+ (rect
->bottom
- rect
->top
- h
) / 2, ILD_NORMAL
);
2278 else if (infoPtr
->glyph_size
.cy
>= 0)
2280 /* infoPtr->glyph is a character code from Marlett */
2281 HFONT font
, old_font
;
2282 LOGFONTW logfont
= { 0, 0, 0, 0, FW_NORMAL
, 0, 0, 0, SYMBOL_CHARSET
, 0, 0, 0, 0,
2283 { 'M','a','r','l','e','t','t',0 } };
2284 if (infoPtr
->glyph_size
.cy
)
2286 /* BCSS_STRETCH preserves aspect ratio, uses minimum as size */
2287 if (infoPtr
->split_style
& BCSS_STRETCH
)
2288 logfont
.lfHeight
= min(infoPtr
->glyph_size
.cx
, infoPtr
->glyph_size
.cy
);
2291 logfont
.lfWidth
= infoPtr
->glyph_size
.cx
;
2292 logfont
.lfHeight
= infoPtr
->glyph_size
.cy
;
2295 else logfont
.lfHeight
= infoPtr
->glyph_size
.cx
;
2297 if ((font
= CreateFontIndirectW(&logfont
)))
2299 old_font
= SelectObject(hdc
, font
);
2300 DrawTextW(hdc
, (const WCHAR
*)&infoPtr
->glyph
, 1, rect
,
2301 DT_CENTER
| DT_VCENTER
| DT_SINGLELINE
| DT_NOCLIP
| DT_NOPREFIX
);
2302 SelectObject(hdc
, old_font
);
2309 /**********************************************************************
2310 * Themed Paint Functions
2312 static void PB_ThemedPaint(HTHEME theme
, const BUTTON_INFO
*infoPtr
, HDC hDC
, int state
, UINT dtFlags
, BOOL focused
)
2314 RECT bgRect
, textRect
;
2315 HFONT font
= infoPtr
->font
;
2316 HFONT hPrevFont
= font
? SelectObject(hDC
, font
) : NULL
;
2322 GetClientRect(infoPtr
->hwnd
, &bgRect
);
2323 GetThemeBackgroundContentRect(theme
, hDC
, BP_PUSHBUTTON
, state
, &bgRect
, &textRect
);
2324 init_custom_draw(&nmcd
, infoPtr
, hDC
, &bgRect
);
2326 parent
= GetParent(infoPtr
->hwnd
);
2327 if (!parent
) parent
= infoPtr
->hwnd
;
2329 /* Send erase notifications */
2330 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2331 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
2333 if (IsThemeBackgroundPartiallyTransparent(theme
, BP_PUSHBUTTON
, state
))
2334 DrawThemeParentBackground(infoPtr
->hwnd
, hDC
, NULL
);
2335 DrawThemeBackground(theme
, hDC
, BP_PUSHBUTTON
, state
, &bgRect
, NULL
);
2337 if (cdrf
& CDRF_NOTIFYPOSTERASE
)
2339 nmcd
.dwDrawStage
= CDDS_POSTERASE
;
2340 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2343 /* Send paint notifications */
2344 nmcd
.dwDrawStage
= CDDS_PREPAINT
;
2345 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2346 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
2348 if (!(cdrf
& CDRF_DOERASE
) && (text
= get_button_text(infoPtr
)))
2350 DrawThemeText(theme
, hDC
, BP_PUSHBUTTON
, state
, text
, lstrlenW(text
), dtFlags
, 0, &textRect
);
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
) goto cleanup
;
2364 RECT focusRect
= bgRect
;
2366 GetThemeMargins(theme
, hDC
, BP_PUSHBUTTON
, state
, TMT_CONTENTMARGINS
, NULL
, &margins
);
2368 focusRect
.left
+= margins
.cxLeftWidth
;
2369 focusRect
.top
+= margins
.cyTopHeight
;
2370 focusRect
.right
-= margins
.cxRightWidth
;
2371 focusRect
.bottom
-= margins
.cyBottomHeight
;
2373 DrawFocusRect( hDC
, &focusRect
);
2377 if (hPrevFont
) SelectObject(hDC
, hPrevFont
);
2380 static void CB_ThemedPaint(HTHEME theme
, const BUTTON_INFO
*infoPtr
, HDC hDC
, int state
, UINT dtFlags
, BOOL focused
)
2383 RECT bgRect
, textRect
;
2384 HFONT font
, hPrevFont
= NULL
;
2385 DWORD dwStyle
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
2386 UINT btn_type
= get_button_type( dwStyle
);
2387 int part
= (btn_type
== BS_RADIOBUTTON
) || (btn_type
== BS_AUTORADIOBUTTON
) ? BP_RADIOBUTTON
: BP_CHECKBOX
;
2393 BOOL created_font
= FALSE
;
2395 HRESULT hr
= GetThemeFont(theme
, hDC
, part
, state
, TMT_FONT
, &lf
);
2396 if (SUCCEEDED(hr
)) {
2397 font
= CreateFontIndirectW(&lf
);
2399 TRACE("Failed to create font\n");
2401 TRACE("font = %s\n", debugstr_w(lf
.lfFaceName
));
2402 hPrevFont
= SelectObject(hDC
, font
);
2403 created_font
= TRUE
;
2406 font
= (HFONT
)SendMessageW(infoPtr
->hwnd
, WM_GETFONT
, 0, 0);
2407 hPrevFont
= SelectObject(hDC
, font
);
2410 if (FAILED(GetThemePartSize(theme
, hDC
, part
, state
, NULL
, TS_DRAW
, &sz
)))
2413 GetClientRect(infoPtr
->hwnd
, &bgRect
);
2414 GetThemeBackgroundContentRect(theme
, hDC
, part
, state
, &bgRect
, &textRect
);
2415 init_custom_draw(&nmcd
, infoPtr
, hDC
, &bgRect
);
2417 if (dtFlags
& DT_SINGLELINE
) /* Center the checkbox / radio button to the text. */
2418 bgRect
.top
= bgRect
.top
+ (textRect
.bottom
- textRect
.top
- sz
.cy
) / 2;
2420 /* adjust for the check/radio marker */
2421 bgRect
.bottom
= bgRect
.top
+ sz
.cy
;
2422 bgRect
.right
= bgRect
.left
+ sz
.cx
;
2423 textRect
.left
= bgRect
.right
+ 6;
2425 parent
= GetParent(infoPtr
->hwnd
);
2426 if (!parent
) parent
= infoPtr
->hwnd
;
2428 /* Send erase notifications */
2429 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2430 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
2432 DrawThemeParentBackground(infoPtr
->hwnd
, hDC
, NULL
);
2433 DrawThemeBackground(theme
, hDC
, part
, state
, &bgRect
, NULL
);
2435 if (cdrf
& CDRF_NOTIFYPOSTERASE
)
2437 nmcd
.dwDrawStage
= CDDS_POSTERASE
;
2438 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2441 /* Send paint notifications */
2442 nmcd
.dwDrawStage
= CDDS_PREPAINT
;
2443 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2444 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
2446 text
= get_button_text(infoPtr
);
2447 if (!(cdrf
& CDRF_DOERASE
) && text
)
2448 DrawThemeText(theme
, hDC
, part
, state
, text
, lstrlenW(text
), dtFlags
, 0, &textRect
);
2450 if (cdrf
& CDRF_NOTIFYPOSTPAINT
)
2452 nmcd
.dwDrawStage
= CDDS_POSTPAINT
;
2453 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2458 if (!(cdrf
& CDRF_SKIPPOSTPAINT
) && focused
)
2462 focusRect
= textRect
;
2464 DrawTextW(hDC
, text
, lstrlenW(text
), &focusRect
, dtFlags
| DT_CALCRECT
);
2466 if (focusRect
.right
< textRect
.right
) focusRect
.right
++;
2467 focusRect
.bottom
= textRect
.bottom
;
2469 DrawFocusRect( hDC
, &focusRect
);
2476 if (created_font
) DeleteObject(font
);
2477 if (hPrevFont
) SelectObject(hDC
, hPrevFont
);
2480 static void GB_ThemedPaint(HTHEME theme
, const BUTTON_INFO
*infoPtr
, HDC hDC
, int state
, UINT dtFlags
, BOOL focused
)
2482 RECT bgRect
, textRect
, contentRect
;
2483 WCHAR
*text
= get_button_text(infoPtr
);
2485 HFONT font
, hPrevFont
= NULL
;
2486 BOOL created_font
= FALSE
;
2488 HRESULT hr
= GetThemeFont(theme
, hDC
, BP_GROUPBOX
, state
, TMT_FONT
, &lf
);
2489 if (SUCCEEDED(hr
)) {
2490 font
= CreateFontIndirectW(&lf
);
2492 TRACE("Failed to create font\n");
2494 hPrevFont
= SelectObject(hDC
, font
);
2495 created_font
= TRUE
;
2498 font
= (HFONT
)SendMessageW(infoPtr
->hwnd
, WM_GETFONT
, 0, 0);
2499 hPrevFont
= SelectObject(hDC
, font
);
2502 GetClientRect(infoPtr
->hwnd
, &bgRect
);
2508 GetTextExtentPoint32W(hDC
, text
, lstrlenW(text
), &textExtent
);
2509 bgRect
.top
+= (textExtent
.cy
/ 2);
2510 textRect
.left
+= 10;
2511 textRect
.bottom
= textRect
.top
+ textExtent
.cy
;
2512 textRect
.right
= textRect
.left
+ textExtent
.cx
+ 4;
2514 ExcludeClipRect(hDC
, textRect
.left
, textRect
.top
, textRect
.right
, textRect
.bottom
);
2517 GetThemeBackgroundContentRect(theme
, hDC
, BP_GROUPBOX
, state
, &bgRect
, &contentRect
);
2518 ExcludeClipRect(hDC
, contentRect
.left
, contentRect
.top
, contentRect
.right
, contentRect
.bottom
);
2520 if (IsThemeBackgroundPartiallyTransparent(theme
, BP_GROUPBOX
, state
))
2521 DrawThemeParentBackground(infoPtr
->hwnd
, hDC
, NULL
);
2522 DrawThemeBackground(theme
, hDC
, BP_GROUPBOX
, state
, &bgRect
, NULL
);
2524 SelectClipRgn(hDC
, NULL
);
2528 InflateRect(&textRect
, -2, 0);
2529 DrawThemeText(theme
, hDC
, BP_GROUPBOX
, state
, text
, lstrlenW(text
), 0, 0, &textRect
);
2533 if (created_font
) DeleteObject(font
);
2534 if (hPrevFont
) SelectObject(hDC
, hPrevFont
);
2537 static void SB_ThemedPaint(HTHEME theme
, const BUTTON_INFO
*infoPtr
, HDC hDC
, int state
, UINT dtFlags
, BOOL focused
)
2539 HFONT old_font
= infoPtr
->font
? SelectObject(hDC
, infoPtr
->font
) : NULL
;
2540 RECT rc
, content_rect
, push_rect
, dropdown_rect
;
2545 GetClientRect(infoPtr
->hwnd
, &rc
);
2546 init_custom_draw(&nmcd
, infoPtr
, hDC
, &rc
);
2548 parent
= GetParent(infoPtr
->hwnd
);
2549 if (!parent
) parent
= infoPtr
->hwnd
;
2551 /* Send erase notifications */
2552 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2553 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
2555 if (IsThemeBackgroundPartiallyTransparent(theme
, BP_PUSHBUTTON
, state
))
2556 DrawThemeParentBackground(infoPtr
->hwnd
, hDC
, NULL
);
2558 /* The zone outside the content is ignored for the dropdown (draws over) */
2559 GetThemeBackgroundContentRect(theme
, hDC
, BP_PUSHBUTTON
, state
, &rc
, &content_rect
);
2560 get_split_button_rects(infoPtr
, &rc
, &push_rect
, &dropdown_rect
);
2562 if (infoPtr
->split_style
& BCSS_NOSPLIT
)
2565 DrawThemeBackground(theme
, hDC
, BP_PUSHBUTTON
, state
, &rc
, NULL
);
2569 RECT r
= { dropdown_rect
.left
, content_rect
.top
, dropdown_rect
.right
, content_rect
.bottom
};
2570 UINT edge
= (infoPtr
->split_style
& BCSS_ALIGNLEFT
) ? BF_RIGHT
: BF_LEFT
;
2571 const RECT
*clip
= NULL
;
2573 /* If only the dropdown is pressed, we need to draw it separately */
2574 if (state
!= PBS_PRESSED
&& (infoPtr
->state
& BST_DROPDOWNPUSHED
))
2576 DrawThemeBackground(theme
, hDC
, BP_PUSHBUTTON
, PBS_PRESSED
, &rc
, &dropdown_rect
);
2579 DrawThemeBackground(theme
, hDC
, BP_PUSHBUTTON
, state
, &rc
, clip
);
2581 /* Draw the separator */
2582 DrawThemeEdge(theme
, hDC
, BP_PUSHBUTTON
, state
, &r
, EDGE_ETCHED
, edge
, NULL
);
2584 /* The content rect should be the content area of the push button */
2585 GetThemeBackgroundContentRect(theme
, hDC
, BP_PUSHBUTTON
, state
, &push_rect
, &content_rect
);
2588 if (cdrf
& CDRF_NOTIFYPOSTERASE
)
2590 nmcd
.dwDrawStage
= CDDS_POSTERASE
;
2591 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2594 /* Send paint notifications */
2595 nmcd
.dwDrawStage
= CDDS_PREPAINT
;
2596 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2597 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
2599 if (!(cdrf
& CDRF_DOERASE
))
2601 COLORREF old_color
, color
;
2605 if ((text
= get_button_text(infoPtr
)))
2607 DrawThemeText(theme
, hDC
, BP_PUSHBUTTON
, state
, text
, lstrlenW(text
), dtFlags
, 0, &content_rect
);
2611 GetThemeColor(theme
, BP_PUSHBUTTON
, state
, TMT_TEXTCOLOR
, &color
);
2612 old_bk_mode
= SetBkMode(hDC
, TRANSPARENT
);
2613 old_color
= SetTextColor(hDC
, color
);
2615 draw_split_button_dropdown_glyph(infoPtr
, hDC
, &dropdown_rect
);
2617 SetTextColor(hDC
, old_color
);
2618 SetBkMode(hDC
, old_bk_mode
);
2621 if (cdrf
& CDRF_NOTIFYPOSTPAINT
)
2623 nmcd
.dwDrawStage
= CDDS_POSTPAINT
;
2624 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2626 if (cdrf
& CDRF_SKIPPOSTPAINT
) goto cleanup
;
2632 GetThemeMargins(theme
, hDC
, BP_PUSHBUTTON
, state
, TMT_CONTENTMARGINS
, NULL
, &margins
);
2634 push_rect
.left
+= margins
.cxLeftWidth
;
2635 push_rect
.top
+= margins
.cyTopHeight
;
2636 push_rect
.right
-= margins
.cxRightWidth
;
2637 push_rect
.bottom
-= margins
.cyBottomHeight
;
2638 DrawFocusRect(hDC
, &push_rect
);
2642 if (old_font
) SelectObject(hDC
, old_font
);
2645 void BUTTON_Register(void)
2649 memset(&wndClass
, 0, sizeof(wndClass
));
2650 wndClass
.style
= CS_GLOBALCLASS
| CS_DBLCLKS
| CS_VREDRAW
| CS_HREDRAW
| CS_PARENTDC
;
2651 wndClass
.lpfnWndProc
= BUTTON_WindowProc
;
2652 wndClass
.cbClsExtra
= 0;
2653 wndClass
.cbWndExtra
= sizeof(BUTTON_INFO
*);
2654 wndClass
.hCursor
= LoadCursorW(0, (LPWSTR
)IDC_ARROW
);
2655 wndClass
.hbrBackground
= NULL
;
2656 wndClass
.lpszClassName
= WC_BUTTONW
;
2657 RegisterClassW(&wndClass
);