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"
61 WINE_DEFAULT_DEBUG_CHANNEL(button
);
63 /* undocumented flags */
64 #define BUTTON_NSTATES 0x0F
65 #define BUTTON_BTNPRESSED 0x40
66 #define BUTTON_UNKNOWN2 0x20
67 #define BUTTON_UNKNOWN3 0x10
69 #define BUTTON_NOTIFY_PARENT(hWnd, code) \
70 do { /* Notify parent which has created this button control */ \
71 TRACE("notification " #code " sent to hwnd=%p\n", GetParent(hWnd)); \
72 SendMessageW(GetParent(hWnd), WM_COMMAND, \
73 MAKEWPARAM(GetWindowLongPtrW((hWnd),GWLP_ID), (code)), \
77 typedef struct _BUTTON_INFO
86 DWORD image_type
; /* IMAGE_BITMAP or IMAGE_ICON */
87 BUTTON_IMAGELIST imagelist
;
89 HIMAGELIST glyph
; /* this is a font character code when split_style doesn't have BCSS_IMAGE */
92 HANDLE image
; /* Original handle set with BM_SETIMAGE and returned with BM_GETIMAGE. */
97 HANDLE image
; /* Duplicated handle used for drawing. */
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 CL_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
);
109 static void BUTTON_CheckAutoRadioButton( HWND hwnd
);
110 static void get_split_button_rects(const BUTTON_INFO
*, const RECT
*, RECT
*, RECT
*);
111 static BOOL
notify_split_button_dropdown(const BUTTON_INFO
*, const POINT
*, HWND
);
112 static void draw_split_button_dropdown_glyph(const BUTTON_INFO
*, HDC
, RECT
*);
114 #define MAX_BTN_TYPE 16
116 static const WORD maxCheckState
[MAX_BTN_TYPE
] =
118 BST_UNCHECKED
, /* BS_PUSHBUTTON */
119 BST_UNCHECKED
, /* BS_DEFPUSHBUTTON */
120 BST_CHECKED
, /* BS_CHECKBOX */
121 BST_CHECKED
, /* BS_AUTOCHECKBOX */
122 BST_CHECKED
, /* BS_RADIOBUTTON */
123 BST_INDETERMINATE
, /* BS_3STATE */
124 BST_INDETERMINATE
, /* BS_AUTO3STATE */
125 BST_UNCHECKED
, /* BS_GROUPBOX */
126 BST_UNCHECKED
, /* BS_USERBUTTON */
127 BST_CHECKED
, /* BS_AUTORADIOBUTTON */
128 BST_UNCHECKED
, /* BS_PUSHBOX */
129 BST_UNCHECKED
, /* BS_OWNERDRAW */
130 BST_UNCHECKED
, /* BS_SPLITBUTTON */
131 BST_UNCHECKED
, /* BS_DEFSPLITBUTTON */
132 BST_UNCHECKED
, /* BS_COMMANDLINK */
133 BST_UNCHECKED
/* BS_DEFCOMMANDLINK */
136 /* Generic draw states, use get_draw_state() to get specific state for button type */
147 typedef void (*pfPaint
)( const BUTTON_INFO
*infoPtr
, HDC hdc
, UINT action
);
149 static const pfPaint btnPaintFunc
[MAX_BTN_TYPE
] =
151 PB_Paint
, /* BS_PUSHBUTTON */
152 PB_Paint
, /* BS_DEFPUSHBUTTON */
153 CB_Paint
, /* BS_CHECKBOX */
154 CB_Paint
, /* BS_AUTOCHECKBOX */
155 CB_Paint
, /* BS_RADIOBUTTON */
156 CB_Paint
, /* BS_3STATE */
157 CB_Paint
, /* BS_AUTO3STATE */
158 GB_Paint
, /* BS_GROUPBOX */
159 UB_Paint
, /* BS_USERBUTTON */
160 CB_Paint
, /* BS_AUTORADIOBUTTON */
161 NULL
, /* BS_PUSHBOX */
162 OB_Paint
, /* BS_OWNERDRAW */
163 SB_Paint
, /* BS_SPLITBUTTON */
164 SB_Paint
, /* BS_DEFSPLITBUTTON */
165 CL_Paint
, /* BS_COMMANDLINK */
166 CL_Paint
/* BS_DEFCOMMANDLINK */
169 typedef void (*pfThemedPaint
)( HTHEME theme
, const BUTTON_INFO
*infoPtr
, HDC hdc
, int drawState
, UINT dtflags
, BOOL focused
);
171 static void PB_ThemedPaint( HTHEME theme
, const BUTTON_INFO
*infoPtr
, HDC hdc
, int drawState
, UINT dtflags
, BOOL focused
);
172 static void CB_ThemedPaint( HTHEME theme
, const BUTTON_INFO
*infoPtr
, HDC hdc
, int drawState
, UINT dtflags
, BOOL focused
);
173 static void GB_ThemedPaint( HTHEME theme
, const BUTTON_INFO
*infoPtr
, HDC hdc
, int drawState
, UINT dtflags
, BOOL focused
);
174 static void SB_ThemedPaint( HTHEME theme
, const BUTTON_INFO
*infoPtr
, HDC hdc
, int drawState
, UINT dtflags
, BOOL focused
);
175 static void CL_ThemedPaint( HTHEME theme
, const BUTTON_INFO
*infoPtr
, HDC hdc
, int drawState
, UINT dtflags
, BOOL focused
);
177 static const pfThemedPaint btnThemedPaintFunc
[MAX_BTN_TYPE
] =
179 PB_ThemedPaint
, /* BS_PUSHBUTTON */
180 PB_ThemedPaint
, /* BS_DEFPUSHBUTTON */
181 CB_ThemedPaint
, /* BS_CHECKBOX */
182 CB_ThemedPaint
, /* BS_AUTOCHECKBOX */
183 CB_ThemedPaint
, /* BS_RADIOBUTTON */
184 CB_ThemedPaint
, /* BS_3STATE */
185 CB_ThemedPaint
, /* BS_AUTO3STATE */
186 GB_ThemedPaint
, /* BS_GROUPBOX */
187 NULL
, /* BS_USERBUTTON */
188 CB_ThemedPaint
, /* BS_AUTORADIOBUTTON */
189 NULL
, /* BS_PUSHBOX */
190 NULL
, /* BS_OWNERDRAW */
191 SB_ThemedPaint
, /* BS_SPLITBUTTON */
192 SB_ThemedPaint
, /* BS_DEFSPLITBUTTON */
193 CL_ThemedPaint
, /* BS_COMMANDLINK */
194 CL_ThemedPaint
/* BS_DEFCOMMANDLINK */
197 typedef BOOL (*pfGetIdealSize
)(BUTTON_INFO
*infoPtr
, SIZE
*size
);
199 static BOOL
PB_GetIdealSize(BUTTON_INFO
*infoPtr
, SIZE
*size
);
200 static BOOL
CB_GetIdealSize(BUTTON_INFO
*infoPtr
, SIZE
*size
);
201 static BOOL
GB_GetIdealSize(BUTTON_INFO
*infoPtr
, SIZE
*size
);
202 static BOOL
SB_GetIdealSize(BUTTON_INFO
*infoPtr
, SIZE
*size
);
203 static BOOL
CL_GetIdealSize(BUTTON_INFO
*infoPtr
, SIZE
*size
);
205 static const pfGetIdealSize btnGetIdealSizeFunc
[MAX_BTN_TYPE
] = {
206 PB_GetIdealSize
, /* BS_PUSHBUTTON */
207 PB_GetIdealSize
, /* BS_DEFPUSHBUTTON */
208 CB_GetIdealSize
, /* BS_CHECKBOX */
209 CB_GetIdealSize
, /* BS_AUTOCHECKBOX */
210 CB_GetIdealSize
, /* BS_RADIOBUTTON */
211 GB_GetIdealSize
, /* BS_3STATE */
212 GB_GetIdealSize
, /* BS_AUTO3STATE */
213 GB_GetIdealSize
, /* BS_GROUPBOX */
214 PB_GetIdealSize
, /* BS_USERBUTTON */
215 CB_GetIdealSize
, /* BS_AUTORADIOBUTTON */
216 GB_GetIdealSize
, /* BS_PUSHBOX */
217 GB_GetIdealSize
, /* BS_OWNERDRAW */
218 SB_GetIdealSize
, /* BS_SPLITBUTTON */
219 SB_GetIdealSize
, /* BS_DEFSPLITBUTTON */
220 CL_GetIdealSize
, /* BS_COMMANDLINK */
221 CL_GetIdealSize
/* BS_DEFCOMMANDLINK */
224 /* Fixed margin for command links, regardless of DPI (based on tests done on Windows) */
225 enum { command_link_margin
= 6 };
227 /* The width and height for the default command link glyph (when there's no image) */
228 enum { command_link_defglyph_size
= 17 };
230 static inline UINT
get_button_type( LONG window_style
)
232 return (window_style
& BS_TYPEMASK
);
235 static inline BOOL
button_centers_text( LONG window_style
)
237 /* Push button's text is centered by default, same for split buttons */
238 UINT type
= get_button_type(window_style
);
239 return type
<= BS_DEFPUSHBUTTON
|| type
== BS_SPLITBUTTON
|| type
== BS_DEFSPLITBUTTON
;
242 /* paint a button of any type */
243 static inline void paint_button( BUTTON_INFO
*infoPtr
, LONG style
, UINT action
)
245 if (btnPaintFunc
[style
] && IsWindowVisible(infoPtr
->hwnd
))
247 HDC hdc
= GetDC( infoPtr
->hwnd
);
248 btnPaintFunc
[style
]( infoPtr
, hdc
, action
);
249 ReleaseDC( infoPtr
->hwnd
, hdc
);
253 /* retrieve the button text; returned buffer must be freed by caller */
254 static inline WCHAR
*get_button_text( const BUTTON_INFO
*infoPtr
)
256 INT len
= GetWindowTextLengthW( infoPtr
->hwnd
);
257 WCHAR
*buffer
= Alloc( (len
+ 1) * sizeof(WCHAR
) );
259 GetWindowTextW( infoPtr
->hwnd
, buffer
, len
+ 1 );
263 /* get the default glyph size for split buttons */
264 static LONG
get_default_glyph_size(const BUTTON_INFO
*infoPtr
)
266 if (infoPtr
->split_style
& BCSS_IMAGE
)
268 /* Size it to fit, including the left and right edges */
270 if (!ImageList_GetIconSize(infoPtr
->glyph
, &w
, &h
)) w
= 0;
271 return w
+ GetSystemMetrics(SM_CXEDGE
) * 2;
274 /* The glyph size relies on the default menu font's cell height */
275 return GetSystemMetrics(SM_CYMENUCHECK
);
278 static BOOL
is_themed_paint_supported(HTHEME theme
, UINT btn_type
)
280 if (!theme
|| !btnThemedPaintFunc
[btn_type
])
283 if (btn_type
== BS_COMMANDLINK
|| btn_type
== BS_DEFCOMMANDLINK
)
285 if (!IsThemePartDefined(theme
, BP_COMMANDLINK
, 0))
292 static void init_custom_draw(NMCUSTOMDRAW
*nmcd
, const BUTTON_INFO
*infoPtr
, HDC hdc
, const RECT
*rc
)
294 nmcd
->hdr
.hwndFrom
= infoPtr
->hwnd
;
295 nmcd
->hdr
.idFrom
= GetWindowLongPtrW(infoPtr
->hwnd
, GWLP_ID
);
296 nmcd
->hdr
.code
= NM_CUSTOMDRAW
;
299 nmcd
->dwDrawStage
= CDDS_PREERASE
;
300 nmcd
->dwItemSpec
= 0;
301 nmcd
->lItemlParam
= 0;
302 nmcd
->uItemState
= IsWindowEnabled(infoPtr
->hwnd
) ? 0 : CDIS_DISABLED
;
303 if (infoPtr
->state
& BST_PUSHED
) nmcd
->uItemState
|= CDIS_SELECTED
;
304 if (infoPtr
->state
& BST_FOCUS
) nmcd
->uItemState
|= CDIS_FOCUS
;
305 if (infoPtr
->state
& BST_HOT
) nmcd
->uItemState
|= CDIS_HOT
;
306 if (infoPtr
->state
& BST_INDETERMINATE
)
307 nmcd
->uItemState
|= CDIS_INDETERMINATE
;
309 /* Windows doesn't seem to send CDIS_CHECKED (it fails the tests) */
310 /* CDIS_SHOWKEYBOARDCUES is misleading, as the meaning is reversed */
311 /* FIXME: Handle it properly when we support keyboard cues? */
314 HRGN
set_control_clipping( HDC hdc
, const RECT
*rect
)
317 HRGN hrgn
= CreateRectRgn( 0, 0, 0, 0 );
319 if (GetClipRgn( hdc
, hrgn
) != 1)
321 DeleteObject( hrgn
);
324 DPtoLP( hdc
, (POINT
*)&rc
, 2 );
325 if (GetLayout( hdc
) & LAYOUT_RTL
) /* compensate for the shifting done by IntersectClipRect */
330 IntersectClipRect( hdc
, rc
.left
, rc
.top
, rc
.right
, rc
.bottom
);
334 static WCHAR
*heap_strndupW(const WCHAR
*src
, size_t length
)
336 size_t size
= (length
+ 1) * sizeof(WCHAR
);
337 WCHAR
*dst
= Alloc(size
);
338 if (dst
) memcpy(dst
, src
, size
);
342 /**********************************************************************
343 * Convert button styles to flags used by DrawText.
345 static UINT
BUTTON_BStoDT( DWORD style
, DWORD ex_style
)
347 UINT dtStyle
= DT_NOCLIP
; /* We use SelectClipRgn to limit output */
349 /* "Convert" pushlike buttons to pushbuttons */
350 if (style
& BS_PUSHLIKE
)
351 style
&= ~BS_TYPEMASK
;
353 if (!(style
& BS_MULTILINE
))
354 dtStyle
|= DT_SINGLELINE
;
356 dtStyle
|= DT_WORDBREAK
;
358 switch (style
& BS_CENTER
)
360 case BS_LEFT
: /* DT_LEFT is 0 */ break;
361 case BS_RIGHT
: dtStyle
|= DT_RIGHT
; break;
362 case BS_CENTER
: dtStyle
|= DT_CENTER
; break;
364 if (button_centers_text(style
)) dtStyle
|= DT_CENTER
;
367 if (ex_style
& WS_EX_RIGHT
) dtStyle
= DT_RIGHT
| (dtStyle
& ~(DT_LEFT
| DT_CENTER
));
369 /* DrawText ignores vertical alignment for multiline text,
370 * but we use these flags to align label manually.
372 if (get_button_type(style
) != BS_GROUPBOX
)
374 switch (style
& BS_VCENTER
)
376 case BS_TOP
: /* DT_TOP is 0 */ break;
377 case BS_BOTTOM
: dtStyle
|= DT_BOTTOM
; break;
378 case BS_VCENTER
: /* fall through */
379 default: dtStyle
|= DT_VCENTER
; break;
386 static int get_draw_state(const BUTTON_INFO
*infoPtr
)
388 static const int pb_states
[DRAW_STATE_COUNT
] = { PBS_NORMAL
, PBS_DISABLED
, PBS_HOT
, PBS_PRESSED
, PBS_DEFAULTED
};
389 static const int cb_states
[3][DRAW_STATE_COUNT
] =
391 { CBS_UNCHECKEDNORMAL
, CBS_UNCHECKEDDISABLED
, CBS_UNCHECKEDHOT
, CBS_UNCHECKEDPRESSED
, CBS_UNCHECKEDNORMAL
},
392 { CBS_CHECKEDNORMAL
, CBS_CHECKEDDISABLED
, CBS_CHECKEDHOT
, CBS_CHECKEDPRESSED
, CBS_CHECKEDNORMAL
},
393 { CBS_MIXEDNORMAL
, CBS_MIXEDDISABLED
, CBS_MIXEDHOT
, CBS_MIXEDPRESSED
, CBS_MIXEDNORMAL
}
395 static const int pushlike_cb_states
[3][DRAW_STATE_COUNT
] =
397 { PBS_NORMAL
, PBS_DISABLED
, PBS_HOT
, PBS_PRESSED
, PBS_NORMAL
},
398 { PBS_PRESSED
, PBS_PRESSED
, PBS_PRESSED
, PBS_PRESSED
, PBS_PRESSED
},
399 { PBS_NORMAL
, PBS_DISABLED
, PBS_HOT
, PBS_PRESSED
, PBS_NORMAL
}
401 static const int rb_states
[2][DRAW_STATE_COUNT
] =
403 { RBS_UNCHECKEDNORMAL
, RBS_UNCHECKEDDISABLED
, RBS_UNCHECKEDHOT
, RBS_UNCHECKEDPRESSED
, RBS_UNCHECKEDNORMAL
},
404 { RBS_CHECKEDNORMAL
, RBS_CHECKEDDISABLED
, RBS_CHECKEDHOT
, RBS_CHECKEDPRESSED
, RBS_CHECKEDNORMAL
}
406 static const int pushlike_rb_states
[2][DRAW_STATE_COUNT
] =
408 { PBS_NORMAL
, PBS_DISABLED
, PBS_HOT
, PBS_PRESSED
, PBS_NORMAL
},
409 { PBS_PRESSED
, PBS_PRESSED
, PBS_PRESSED
, PBS_PRESSED
, PBS_PRESSED
}
411 static const int gb_states
[DRAW_STATE_COUNT
] = { GBS_NORMAL
, GBS_DISABLED
, GBS_NORMAL
, GBS_NORMAL
, GBS_NORMAL
};
412 LONG style
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
413 UINT type
= get_button_type(style
);
414 int check_state
= infoPtr
->state
& 3;
415 enum draw_state state
;
417 if (!IsWindowEnabled(infoPtr
->hwnd
))
418 state
= STATE_DISABLED
;
419 else if (infoPtr
->state
& BST_PUSHED
)
420 state
= STATE_PRESSED
;
421 else if (infoPtr
->state
& BST_HOT
)
423 else if (infoPtr
->state
& BST_FOCUS
|| type
== BS_DEFPUSHBUTTON
|| type
== BS_DEFSPLITBUTTON
424 || (type
== BS_DEFCOMMANDLINK
&& !(style
& BS_PUSHLIKE
)))
425 state
= STATE_DEFAULTED
;
427 state
= STATE_NORMAL
;
432 case BS_DEFPUSHBUTTON
:
435 case BS_DEFSPLITBUTTON
:
437 case BS_DEFCOMMANDLINK
:
438 return pb_states
[state
];
440 case BS_AUTOCHECKBOX
:
443 return style
& BS_PUSHLIKE
? pushlike_cb_states
[check_state
][state
]
444 : cb_states
[check_state
][state
];
446 case BS_AUTORADIOBUTTON
:
447 return style
& BS_PUSHLIKE
? pushlike_rb_states
[check_state
][state
]
448 : rb_states
[check_state
][state
];
450 return style
& BS_PUSHLIKE
? pb_states
[state
] : gb_states
[state
];
452 WARN("Unsupported button type 0x%08x\n", type
);
457 static LRESULT CALLBACK
BUTTON_WindowProc(HWND hWnd
, UINT uMsg
, WPARAM wParam
, LPARAM lParam
)
459 BUTTON_INFO
*infoPtr
= (BUTTON_INFO
*)GetWindowLongPtrW(hWnd
, 0);
462 LONG style
= GetWindowLongW( hWnd
, GWL_STYLE
);
463 UINT btn_type
= get_button_type( style
);
464 LONG state
, new_state
;
468 if (!IsWindow( hWnd
)) return 0;
470 if (!infoPtr
&& (uMsg
!= WM_NCCREATE
))
471 return DefWindowProcW(hWnd
, uMsg
, wParam
, lParam
);
473 pt
.x
= (short)LOWORD(lParam
);
474 pt
.y
= (short)HIWORD(lParam
);
483 case BS_PUSHBUTTON
: return DLGC_BUTTON
| DLGC_UNDEFPUSHBUTTON
;
484 case BS_DEFCOMMANDLINK
:
485 case BS_DEFPUSHBUTTON
: return DLGC_BUTTON
| DLGC_DEFPUSHBUTTON
;
487 case BS_AUTORADIOBUTTON
: return DLGC_BUTTON
| DLGC_RADIOBUTTON
;
488 case BS_GROUPBOX
: return DLGC_STATIC
;
489 case BS_SPLITBUTTON
: return DLGC_BUTTON
| DLGC_UNDEFPUSHBUTTON
| DLGC_WANTARROWS
;
490 case BS_DEFSPLITBUTTON
: return DLGC_BUTTON
| DLGC_DEFPUSHBUTTON
| DLGC_WANTARROWS
;
491 default: return DLGC_BUTTON
;
495 theme
= GetWindowTheme( hWnd
);
497 RedrawWindow( hWnd
, NULL
, NULL
, RDW_FRAME
| RDW_INVALIDATE
| RDW_UPDATENOW
);
499 paint_button( infoPtr
, btn_type
, ODA_DRAWENTIRE
);
504 CREATESTRUCTW
*cs
= (CREATESTRUCTW
*)lParam
;
506 infoPtr
= Alloc( sizeof(*infoPtr
) );
507 SetWindowLongPtrW( hWnd
, 0, (LONG_PTR
)infoPtr
);
508 infoPtr
->hwnd
= hWnd
;
509 infoPtr
->parent
= cs
->hwndParent
;
510 infoPtr
->style
= cs
->style
;
511 infoPtr
->split_style
= BCSS_STRETCH
;
512 infoPtr
->glyph
= (HIMAGELIST
)0x36; /* Marlett down arrow char code */
513 infoPtr
->glyph_size
.cx
= get_default_glyph_size(infoPtr
);
514 return DefWindowProcW(hWnd
, uMsg
, wParam
, lParam
);
518 SetWindowLongPtrW( hWnd
, 0, 0 );
519 if (infoPtr
->image_type
== IMAGE_BITMAP
)
520 DeleteObject(infoPtr
->u
.bitmap
);
521 else if (infoPtr
->image_type
== IMAGE_ICON
)
522 DestroyIcon(infoPtr
->u
.icon
);
531 if (btn_type
>= MAX_BTN_TYPE
)
532 return -1; /* abort */
534 /* XP turns a BS_USERBUTTON into BS_PUSHBUTTON */
535 if (btn_type
== BS_USERBUTTON
)
537 style
= (style
& ~BS_TYPEMASK
) | BS_PUSHBUTTON
;
538 SetWindowLongW( hWnd
, GWL_STYLE
, style
);
540 infoPtr
->state
= BST_UNCHECKED
;
541 OpenThemeData( hWnd
, WC_BUTTONW
);
543 parent
= GetParent( hWnd
);
545 EnableThemeDialogTexture( parent
, ETDT_ENABLE
);
550 theme
= GetWindowTheme( hWnd
);
551 CloseThemeData( theme
);
554 case WM_THEMECHANGED
:
555 theme
= GetWindowTheme( hWnd
);
556 CloseThemeData( theme
);
557 OpenThemeData( hWnd
, WC_BUTTONW
);
558 InvalidateRect( hWnd
, NULL
, TRUE
);
562 if (btn_type
== BS_OWNERDRAW
)
564 HDC hdc
= (HDC
)wParam
;
567 HWND parent
= GetParent(hWnd
);
568 if (!parent
) parent
= hWnd
;
569 hBrush
= (HBRUSH
)SendMessageW(parent
, WM_CTLCOLORBTN
, (WPARAM
)hdc
, (LPARAM
)hWnd
);
570 if (!hBrush
) /* did the app forget to call defwindowproc ? */
571 hBrush
= (HBRUSH
)DefWindowProcW(parent
, WM_CTLCOLORBTN
,
572 (WPARAM
)hdc
, (LPARAM
)hWnd
);
573 GetClientRect(hWnd
, &rc
);
574 FillRect(hdc
, &rc
, hBrush
);
584 theme
= GetWindowTheme( hWnd
);
585 hdc
= wParam
? (HDC
)wParam
: BeginPaint( hWnd
, &ps
);
587 if (is_themed_paint_supported(theme
, btn_type
))
589 int drawState
= get_draw_state(infoPtr
);
590 UINT dtflags
= BUTTON_BStoDT(style
, GetWindowLongW(hWnd
, GWL_EXSTYLE
));
592 btnThemedPaintFunc
[btn_type
](theme
, infoPtr
, hdc
, drawState
, dtflags
, infoPtr
->state
& BST_FOCUS
);
594 else if (btnPaintFunc
[btn_type
])
596 int nOldMode
= SetBkMode( hdc
, OPAQUE
);
597 btnPaintFunc
[btn_type
]( infoPtr
, hdc
, ODA_DRAWENTIRE
);
598 SetBkMode(hdc
, nOldMode
); /* reset painting mode */
601 if ( !wParam
) EndPaint( hWnd
, &ps
);
606 if (wParam
== VK_SPACE
)
608 SendMessageW( hWnd
, BM_SETSTATE
, TRUE
, 0 );
609 infoPtr
->state
|= BUTTON_BTNPRESSED
;
612 else if (wParam
== VK_UP
|| wParam
== VK_DOWN
)
614 /* Up and down arrows work on every button, and even with BCSS_NOSPLIT */
615 notify_split_button_dropdown(infoPtr
, NULL
, hWnd
);
619 case WM_LBUTTONDBLCLK
:
620 if(style
& BS_NOTIFY
||
621 btn_type
== BS_RADIOBUTTON
||
622 btn_type
== BS_USERBUTTON
||
623 btn_type
== BS_OWNERDRAW
)
625 BUTTON_NOTIFY_PARENT(hWnd
, BN_DOUBLECLICKED
);
632 if ((btn_type
== BS_SPLITBUTTON
|| btn_type
== BS_DEFSPLITBUTTON
) &&
633 !(infoPtr
->split_style
& BCSS_NOSPLIT
) &&
634 notify_split_button_dropdown(infoPtr
, &pt
, hWnd
))
638 infoPtr
->state
|= BUTTON_BTNPRESSED
;
639 SendMessageW( hWnd
, BM_SETSTATE
, TRUE
, 0 );
643 if (wParam
!= VK_SPACE
)
647 state
= infoPtr
->state
;
648 if (state
& BST_DROPDOWNPUSHED
)
649 SendMessageW(hWnd
, BCM_SETDROPDOWNSTATE
, FALSE
, 0);
650 if (!(state
& BUTTON_BTNPRESSED
)) break;
651 infoPtr
->state
&= BUTTON_NSTATES
| BST_HOT
;
652 if (!(state
& BST_PUSHED
))
657 SendMessageW( hWnd
, BM_SETSTATE
, FALSE
, 0 );
658 GetClientRect( hWnd
, &rect
);
659 if (uMsg
== WM_KEYUP
|| PtInRect( &rect
, pt
))
663 case BS_AUTOCHECKBOX
:
664 SendMessageW( hWnd
, BM_SETCHECK
, !(infoPtr
->state
& BST_CHECKED
), 0 );
666 case BS_AUTORADIOBUTTON
:
667 SendMessageW( hWnd
, BM_SETCHECK
, TRUE
, 0 );
670 SendMessageW( hWnd
, BM_SETCHECK
, (infoPtr
->state
& BST_INDETERMINATE
) ? 0 :
671 ((infoPtr
->state
& 3) + 1), 0 );
675 BUTTON_NOTIFY_PARENT(hWnd
, BN_CLICKED
);
683 case WM_CAPTURECHANGED
:
684 TRACE("WM_CAPTURECHANGED %p\n", hWnd
);
685 if (hWnd
== (HWND
)lParam
) break;
686 if (infoPtr
->state
& BUTTON_BTNPRESSED
)
688 infoPtr
->state
&= BUTTON_NSTATES
;
689 if (infoPtr
->state
& BST_PUSHED
)
690 SendMessageW( hWnd
, BM_SETSTATE
, FALSE
, 0 );
696 TRACKMOUSEEVENT mouse_event
;
698 mouse_event
.cbSize
= sizeof(TRACKMOUSEEVENT
);
699 mouse_event
.dwFlags
= TME_QUERY
;
700 if (!TrackMouseEvent(&mouse_event
) || !(mouse_event
.dwFlags
& (TME_HOVER
| TME_LEAVE
)))
702 mouse_event
.dwFlags
= TME_HOVER
| TME_LEAVE
;
703 mouse_event
.hwndTrack
= hWnd
;
704 mouse_event
.dwHoverTime
= 1;
705 TrackMouseEvent(&mouse_event
);
708 if ((wParam
& MK_LBUTTON
) && GetCapture() == hWnd
)
710 GetClientRect( hWnd
, &rect
);
711 SendMessageW( hWnd
, BM_SETSTATE
, PtInRect(&rect
, pt
), 0 );
718 infoPtr
->state
|= BST_HOT
;
719 InvalidateRect( hWnd
, NULL
, FALSE
);
725 infoPtr
->state
&= ~BST_HOT
;
726 InvalidateRect( hWnd
, NULL
, FALSE
);
732 /* Clear an old text here as Windows does */
733 if (IsWindowVisible(hWnd
))
735 HDC hdc
= GetDC(hWnd
);
738 HWND parent
= GetParent(hWnd
);
739 UINT message
= (btn_type
== BS_PUSHBUTTON
||
740 btn_type
== BS_DEFPUSHBUTTON
||
741 btn_type
== BS_USERBUTTON
||
742 btn_type
== BS_OWNERDRAW
) ?
743 WM_CTLCOLORBTN
: WM_CTLCOLORSTATIC
;
745 if (!parent
) parent
= hWnd
;
746 hbrush
= (HBRUSH
)SendMessageW(parent
, message
,
747 (WPARAM
)hdc
, (LPARAM
)hWnd
);
748 if (!hbrush
) /* did the app forget to call DefWindowProc ? */
749 hbrush
= (HBRUSH
)DefWindowProcW(parent
, message
,
750 (WPARAM
)hdc
, (LPARAM
)hWnd
);
752 GetClientRect(hWnd
, &client
);
754 /* FIXME: check other BS_* handlers */
755 if (btn_type
== BS_GROUPBOX
)
756 InflateRect(&rc
, -7, 1); /* GB_Paint does this */
757 BUTTON_CalcLayoutRects(infoPtr
, hdc
, &rc
, NULL
, NULL
);
758 /* Clip by client rect bounds */
759 if (rc
.right
> client
.right
) rc
.right
= client
.right
;
760 if (rc
.bottom
> client
.bottom
) rc
.bottom
= client
.bottom
;
761 FillRect(hdc
, &rc
, hbrush
);
762 ReleaseDC(hWnd
, hdc
);
765 DefWindowProcW( hWnd
, WM_SETTEXT
, wParam
, lParam
);
766 if (btn_type
== BS_GROUPBOX
) /* Yes, only for BS_GROUPBOX */
767 InvalidateRect( hWnd
, NULL
, TRUE
);
768 else if (GetWindowTheme( hWnd
))
769 RedrawWindow( hWnd
, NULL
, NULL
, RDW_INVALIDATE
| RDW_UPDATENOW
);
771 paint_button( infoPtr
, btn_type
, ODA_DRAWENTIRE
);
772 return 1; /* success. FIXME: check text length */
777 WCHAR
*note
= (WCHAR
*)lParam
;
778 if (btn_type
!= BS_COMMANDLINK
&& btn_type
!= BS_DEFCOMMANDLINK
)
780 SetLastError(ERROR_NOT_SUPPORTED
);
787 infoPtr
->note_length
= lstrlenW(note
);
788 infoPtr
->note
= heap_strndupW(note
, infoPtr
->note_length
);
791 if (!note
|| !infoPtr
->note
)
793 infoPtr
->note_length
= 0;
794 infoPtr
->note
= Alloc(sizeof(WCHAR
));
797 SetLastError(NO_ERROR
);
803 DWORD
*size
= (DWORD
*)wParam
;
804 WCHAR
*buffer
= (WCHAR
*)lParam
;
807 if (btn_type
!= BS_COMMANDLINK
&& btn_type
!= BS_DEFCOMMANDLINK
)
809 SetLastError(ERROR_NOT_SUPPORTED
);
813 if (!buffer
|| !size
|| !infoPtr
->note
)
815 SetLastError(ERROR_INVALID_PARAMETER
);
821 length
= min(*size
- 1, infoPtr
->note_length
);
822 memcpy(buffer
, infoPtr
->note
, length
* sizeof(WCHAR
));
823 buffer
[length
] = '\0';
826 if (*size
< infoPtr
->note_length
+ 1)
828 *size
= infoPtr
->note_length
+ 1;
829 SetLastError(ERROR_INSUFFICIENT_BUFFER
);
834 SetLastError(NO_ERROR
);
839 case BCM_GETNOTELENGTH
:
841 if (btn_type
!= BS_COMMANDLINK
&& btn_type
!= BS_DEFCOMMANDLINK
)
843 SetLastError(ERROR_NOT_SUPPORTED
);
847 return infoPtr
->note_length
;
851 infoPtr
->font
= (HFONT
)wParam
;
852 if (lParam
) InvalidateRect(hWnd
, NULL
, TRUE
);
856 return (LRESULT
)infoPtr
->font
;
859 TRACE("WM_SETFOCUS %p\n",hWnd
);
860 infoPtr
->state
|= BST_FOCUS
;
862 if (btn_type
== BS_OWNERDRAW
)
863 paint_button( infoPtr
, btn_type
, ODA_FOCUS
);
865 InvalidateRect(hWnd
, NULL
, FALSE
);
867 if (style
& BS_NOTIFY
)
868 BUTTON_NOTIFY_PARENT(hWnd
, BN_SETFOCUS
);
872 TRACE("WM_KILLFOCUS %p\n",hWnd
);
873 infoPtr
->state
&= ~BST_FOCUS
;
875 if ((infoPtr
->state
& BUTTON_BTNPRESSED
) && GetCapture() == hWnd
)
877 if (style
& BS_NOTIFY
)
878 BUTTON_NOTIFY_PARENT(hWnd
, BN_KILLFOCUS
);
880 InvalidateRect( hWnd
, NULL
, FALSE
);
883 case WM_SYSCOLORCHANGE
:
884 InvalidateRect( hWnd
, NULL
, FALSE
);
891 new_btn_type
= wParam
& BS_TYPEMASK
;
892 if (btn_type
>= BS_SPLITBUTTON
&& new_btn_type
<= BS_DEFPUSHBUTTON
)
893 new_btn_type
= (btn_type
& ~BS_DEFPUSHBUTTON
) | new_btn_type
;
895 style
= (style
& ~BS_TYPEMASK
) | new_btn_type
;
896 SetWindowLongW( hWnd
, GWL_STYLE
, style
);
898 /* Only redraw if lParam flag is set.*/
900 InvalidateRect( hWnd
, NULL
, TRUE
);
905 SendMessageW( hWnd
, WM_LBUTTONDOWN
, 0, 0 );
906 SendMessageW( hWnd
, WM_LBUTTONUP
, 0, 0 );
910 infoPtr
->image_type
= (DWORD
)wParam
;
911 oldHbitmap
= infoPtr
->image
;
912 infoPtr
->u
.image
= CopyImage((HANDLE
)lParam
, infoPtr
->image_type
, 0, 0, 0);
913 infoPtr
->image
= (HANDLE
)lParam
;
914 InvalidateRect( hWnd
, NULL
, FALSE
);
915 return (LRESULT
)oldHbitmap
;
918 return (LRESULT
)infoPtr
->image
;
920 case BCM_SETIMAGELIST
:
922 BUTTON_IMAGELIST
*imagelist
= (BUTTON_IMAGELIST
*)lParam
;
924 if (!imagelist
) return FALSE
;
926 infoPtr
->imagelist
= *imagelist
;
930 case BCM_GETIMAGELIST
:
932 BUTTON_IMAGELIST
*imagelist
= (BUTTON_IMAGELIST
*)lParam
;
934 if (!imagelist
) return FALSE
;
936 *imagelist
= infoPtr
->imagelist
;
940 case BCM_SETSPLITINFO
:
942 BUTTON_SPLITINFO
*info
= (BUTTON_SPLITINFO
*)lParam
;
944 if (!info
) return TRUE
;
946 if (info
->mask
& (BCSIF_GLYPH
| BCSIF_IMAGE
))
948 infoPtr
->split_style
&= ~BCSS_IMAGE
;
949 if (!(info
->mask
& BCSIF_GLYPH
))
950 infoPtr
->split_style
|= BCSS_IMAGE
;
951 infoPtr
->glyph
= info
->himlGlyph
;
952 infoPtr
->glyph_size
.cx
= infoPtr
->glyph_size
.cy
= 0;
955 if (info
->mask
& BCSIF_STYLE
)
956 infoPtr
->split_style
= info
->uSplitStyle
;
957 if (info
->mask
& BCSIF_SIZE
)
958 infoPtr
->glyph_size
= info
->size
;
960 /* Calculate fitting value for cx if invalid (cy is untouched) */
961 if (infoPtr
->glyph_size
.cx
<= 0)
962 infoPtr
->glyph_size
.cx
= get_default_glyph_size(infoPtr
);
964 /* Windows doesn't invalidate or redraw it, so we don't, either */
968 case BCM_GETSPLITINFO
:
970 BUTTON_SPLITINFO
*info
= (BUTTON_SPLITINFO
*)lParam
;
972 if (!info
) return FALSE
;
974 if (info
->mask
& BCSIF_STYLE
)
975 info
->uSplitStyle
= infoPtr
->split_style
;
976 if (info
->mask
& (BCSIF_GLYPH
| BCSIF_IMAGE
))
977 info
->himlGlyph
= infoPtr
->glyph
;
978 if (info
->mask
& BCSIF_SIZE
)
979 info
->size
= infoPtr
->glyph_size
;
985 return infoPtr
->state
& 3;
988 if (wParam
> maxCheckState
[btn_type
]) wParam
= maxCheckState
[btn_type
];
989 if ((btn_type
== BS_RADIOBUTTON
) || (btn_type
== BS_AUTORADIOBUTTON
))
991 style
= wParam
? style
| WS_TABSTOP
: style
& ~WS_TABSTOP
;
992 SetWindowLongW( hWnd
, GWL_STYLE
, style
);
994 if ((infoPtr
->state
& 3) != wParam
)
996 infoPtr
->state
= (infoPtr
->state
& ~3) | wParam
;
997 InvalidateRect( hWnd
, NULL
, FALSE
);
999 if ((btn_type
== BS_AUTORADIOBUTTON
) && (wParam
== BST_CHECKED
) && (style
& WS_CHILD
))
1000 BUTTON_CheckAutoRadioButton( hWnd
);
1004 return infoPtr
->state
;
1007 state
= infoPtr
->state
;
1008 new_state
= wParam
? BST_PUSHED
: 0;
1010 if ((state
^ new_state
) & BST_PUSHED
)
1013 state
|= BST_PUSHED
;
1015 state
&= ~BST_PUSHED
;
1017 if (btn_type
== BS_USERBUTTON
)
1018 BUTTON_NOTIFY_PARENT( hWnd
, (state
& BST_PUSHED
) ? BN_HILITE
: BN_UNHILITE
);
1019 infoPtr
->state
= state
;
1021 InvalidateRect( hWnd
, NULL
, FALSE
);
1025 case BCM_SETDROPDOWNSTATE
:
1026 new_state
= wParam
? BST_DROPDOWNPUSHED
: 0;
1028 if ((infoPtr
->state
^ new_state
) & BST_DROPDOWNPUSHED
)
1030 infoPtr
->state
&= ~BST_DROPDOWNPUSHED
;
1031 infoPtr
->state
|= new_state
;
1032 InvalidateRect(hWnd
, NULL
, FALSE
);
1036 case BCM_SETTEXTMARGIN
:
1038 RECT
*text_margin
= (RECT
*)lParam
;
1040 if (!text_margin
) return FALSE
;
1042 infoPtr
->text_margin
= *text_margin
;
1046 case BCM_GETTEXTMARGIN
:
1048 RECT
*text_margin
= (RECT
*)lParam
;
1050 if (!text_margin
) return FALSE
;
1052 *text_margin
= infoPtr
->text_margin
;
1056 case BCM_GETIDEALSIZE
:
1058 SIZE
*size
= (SIZE
*)lParam
;
1060 if (!size
) return FALSE
;
1062 return btnGetIdealSizeFunc
[btn_type
](infoPtr
, size
);
1066 if(btn_type
== BS_GROUPBOX
) return HTTRANSPARENT
;
1069 return DefWindowProcW(hWnd
, uMsg
, wParam
, lParam
);
1074 /* If maxWidth is zero, rectangle width is unlimited */
1075 static RECT
BUTTON_GetTextRect(const BUTTON_INFO
*infoPtr
, HDC hdc
, const WCHAR
*text
, LONG maxWidth
)
1077 LONG style
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
1078 LONG exStyle
= GetWindowLongW(infoPtr
->hwnd
, GWL_EXSTYLE
);
1079 UINT dtStyle
= BUTTON_BStoDT(style
, exStyle
);
1083 rect
.right
= maxWidth
;
1084 hPrevFont
= SelectObject(hdc
, infoPtr
->font
);
1085 /* Calculate height without DT_VCENTER and DT_BOTTOM to get the correct height */
1086 DrawTextW(hdc
, text
, -1, &rect
, (dtStyle
& ~(DT_VCENTER
| DT_BOTTOM
)) | DT_CALCRECT
);
1087 if (hPrevFont
) SelectObject(hdc
, hPrevFont
);
1092 static BOOL
show_image_only(const BUTTON_INFO
*infoPtr
)
1094 LONG style
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
1095 return (style
& (BS_ICON
| BS_BITMAP
)) && (infoPtr
->u
.image
|| infoPtr
->imagelist
.himl
);
1098 static BOOL
show_image_and_text(const BUTTON_INFO
*infoPtr
)
1100 LONG style
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
1101 UINT type
= get_button_type(style
);
1102 return !(style
& (BS_ICON
| BS_BITMAP
))
1103 && ((infoPtr
->u
.image
1104 && (type
== BS_PUSHBUTTON
|| type
== BS_DEFPUSHBUTTON
|| type
== BS_USERBUTTON
|| type
== BS_SPLITBUTTON
1105 || type
== BS_DEFSPLITBUTTON
|| type
== BS_COMMANDLINK
|| type
== BS_DEFCOMMANDLINK
))
1106 || (infoPtr
->imagelist
.himl
&& type
!= BS_GROUPBOX
));
1109 static BOOL
show_image(const BUTTON_INFO
*infoPtr
)
1111 return show_image_only(infoPtr
) || show_image_and_text(infoPtr
);
1114 /* Get a bounding rectangle that is large enough to contain a image and a text side by side.
1115 * Note: (left,top) of the result rectangle may not be (0,0), offset it by yourself if needed */
1116 static RECT
BUTTON_GetBoundingLabelRect(LONG style
, const RECT
*textRect
, const RECT
*imageRect
)
1119 RECT rect
= *imageRect
;
1120 INT textWidth
= textRect
->right
- textRect
->left
;
1121 INT textHeight
= textRect
->bottom
- textRect
->top
;
1122 INT imageWidth
= imageRect
->right
- imageRect
->left
;
1123 INT imageHeight
= imageRect
->bottom
- imageRect
->top
;
1125 if ((style
& BS_CENTER
) == BS_RIGHT
)
1126 OffsetRect(&rect
, textWidth
, 0);
1127 else if ((style
& BS_CENTER
) == BS_LEFT
)
1128 OffsetRect(&rect
, -imageWidth
, 0);
1129 else if ((style
& BS_VCENTER
) == BS_BOTTOM
)
1130 OffsetRect(&rect
, 0, textHeight
);
1131 else if ((style
& BS_VCENTER
) == BS_TOP
)
1132 OffsetRect(&rect
, 0, -imageHeight
);
1134 OffsetRect(&rect
, -imageWidth
, 0);
1136 UnionRect(&labelRect
, textRect
, &rect
);
1140 /* Position a rectangle inside a bounding rectangle according to button alignment flags */
1141 static void BUTTON_PositionRect(LONG style
, const RECT
*outerRect
, RECT
*innerRect
, const RECT
*margin
)
1143 INT width
= innerRect
->right
- innerRect
->left
;
1144 INT height
= innerRect
->bottom
- innerRect
->top
;
1146 if ((style
& BS_PUSHLIKE
) && !(style
& BS_CENTER
)) style
|= BS_CENTER
;
1148 if (!(style
& BS_CENTER
))
1150 if (button_centers_text(style
))
1156 if (!(style
& BS_VCENTER
))
1158 /* Group box's text is top aligned by default */
1159 if (get_button_type(style
) == BS_GROUPBOX
)
1163 switch (style
& BS_CENTER
)
1166 /* The left and right margins are added to the inner rectangle to get a new rectangle. Then
1167 * the new rectangle is adjusted to be in the horizontal center */
1168 innerRect
->left
= outerRect
->left
+ (outerRect
->right
- outerRect
->left
- width
1169 + margin
->left
- margin
->right
) / 2;
1170 innerRect
->right
= innerRect
->left
+ width
;
1173 innerRect
->right
= outerRect
->right
- margin
->right
;
1174 innerRect
->left
= innerRect
->right
- width
;
1178 innerRect
->left
= outerRect
->left
+ margin
->left
;
1179 innerRect
->right
= innerRect
->left
+ width
;
1183 switch (style
& BS_VCENTER
)
1186 innerRect
->top
= outerRect
->top
+ margin
->top
;
1187 innerRect
->bottom
= innerRect
->top
+ height
;
1190 innerRect
->bottom
= outerRect
->bottom
- margin
->bottom
;
1191 innerRect
->top
= innerRect
->bottom
- height
;
1195 /* The top and bottom margins are added to the inner rectangle to get a new rectangle. Then
1196 * the new rectangle is adjusted to be in the vertical center */
1197 innerRect
->top
= outerRect
->top
+ (outerRect
->bottom
- outerRect
->top
- height
1198 + margin
->top
- margin
->bottom
) / 2;
1199 innerRect
->bottom
= innerRect
->top
+ height
;
1204 /* Convert imagelist align style to button align style */
1205 static UINT
BUTTON_ILStoBS(UINT align
)
1209 case BUTTON_IMAGELIST_ALIGN_TOP
:
1210 return BS_CENTER
| BS_TOP
;
1211 case BUTTON_IMAGELIST_ALIGN_BOTTOM
:
1212 return BS_CENTER
| BS_BOTTOM
;
1213 case BUTTON_IMAGELIST_ALIGN_CENTER
:
1214 return BS_CENTER
| BS_VCENTER
;
1215 case BUTTON_IMAGELIST_ALIGN_RIGHT
:
1216 return BS_RIGHT
| BS_VCENTER
;
1217 case BUTTON_IMAGELIST_ALIGN_LEFT
:
1219 return BS_LEFT
| BS_VCENTER
;
1223 static SIZE
BUTTON_GetImageSize(const BUTTON_INFO
*infoPtr
)
1229 /* ImageList has priority over image */
1230 if (infoPtr
->imagelist
.himl
)
1233 ImageList_GetIconSize(infoPtr
->imagelist
.himl
, &scx
, &scy
);
1237 else if (infoPtr
->u
.image
)
1239 if (infoPtr
->image_type
== IMAGE_ICON
)
1241 GetIconInfo(infoPtr
->u
.icon
, &iconInfo
);
1242 GetObjectW(iconInfo
.hbmColor
, sizeof(bm
), &bm
);
1243 DeleteObject(iconInfo
.hbmColor
);
1244 DeleteObject(iconInfo
.hbmMask
);
1246 else if (infoPtr
->image_type
== IMAGE_BITMAP
)
1247 GetObjectW(infoPtr
->u
.bitmap
, sizeof(bm
), &bm
);
1249 size
.cx
= bm
.bmWidth
;
1250 size
.cy
= bm
.bmHeight
;
1256 static const RECT
*BUTTON_GetTextMargin(const BUTTON_INFO
*infoPtr
)
1258 static const RECT oneMargin
= {1, 1, 1, 1};
1260 /* Use text margin only when showing both image and text, and image is not imagelist */
1261 if (show_image_and_text(infoPtr
) && !infoPtr
->imagelist
.himl
)
1262 return &infoPtr
->text_margin
;
1267 static void BUTTON_GetClientRectSize(BUTTON_INFO
*infoPtr
, SIZE
*size
)
1270 GetClientRect(infoPtr
->hwnd
, &rect
);
1271 size
->cx
= rect
.right
- rect
.left
;
1272 size
->cy
= rect
.bottom
- rect
.top
;
1275 static void BUTTON_GetTextIdealSize(BUTTON_INFO
*infoPtr
, LONG maxWidth
, SIZE
*size
)
1277 WCHAR
*text
= get_button_text(infoPtr
);
1280 const RECT
*margin
= BUTTON_GetTextMargin(infoPtr
);
1284 maxWidth
-= margin
->right
+ margin
->right
;
1285 if (maxWidth
<= 0) maxWidth
= 1;
1288 hdc
= GetDC(infoPtr
->hwnd
);
1289 rect
= BUTTON_GetTextRect(infoPtr
, hdc
, text
, maxWidth
);
1290 ReleaseDC(infoPtr
->hwnd
, hdc
);
1293 size
->cx
= rect
.right
- rect
.left
+ margin
->left
+ margin
->right
;
1294 size
->cy
= rect
.bottom
- rect
.top
+ margin
->top
+ margin
->bottom
;
1297 static void BUTTON_GetLabelIdealSize(BUTTON_INFO
*infoPtr
, LONG maxWidth
, SIZE
*size
)
1299 LONG style
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
1304 imageSize
= BUTTON_GetImageSize(infoPtr
);
1305 if (infoPtr
->imagelist
.himl
)
1307 imageSize
.cx
+= infoPtr
->imagelist
.margin
.left
+ infoPtr
->imagelist
.margin
.right
;
1308 imageSize
.cy
+= infoPtr
->imagelist
.margin
.top
+ infoPtr
->imagelist
.margin
.bottom
;
1309 if (infoPtr
->imagelist
.uAlign
== BUTTON_IMAGELIST_ALIGN_TOP
1310 || infoPtr
->imagelist
.uAlign
== BUTTON_IMAGELIST_ALIGN_BOTTOM
)
1317 /* horizontal alignment flags has priority over vertical ones if both are specified */
1318 if (!(style
& (BS_CENTER
| BS_VCENTER
)) || ((style
& BS_CENTER
) && (style
& BS_CENTER
) != BS_CENTER
)
1319 || !(style
& BS_VCENTER
) || (style
& BS_VCENTER
) == BS_VCENTER
)
1329 maxWidth
-= imageSize
.cx
;
1330 if (maxWidth
<= 0) maxWidth
= 1;
1332 BUTTON_GetTextIdealSize(infoPtr
, maxWidth
, &textSize
);
1333 size
->cx
= textSize
.cx
+ imageSize
.cx
;
1334 size
->cy
= max(textSize
.cy
, imageSize
.cy
);
1338 BUTTON_GetTextIdealSize(infoPtr
, maxWidth
, &textSize
);
1339 size
->cx
= max(textSize
.cx
, imageSize
.cx
);
1340 size
->cy
= textSize
.cy
+ imageSize
.cy
;
1344 static BOOL
GB_GetIdealSize(BUTTON_INFO
*infoPtr
, SIZE
*size
)
1346 BUTTON_GetClientRectSize(infoPtr
, size
);
1350 static BOOL
CB_GetIdealSize(BUTTON_INFO
*infoPtr
, SIZE
*size
)
1352 LONG style
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
1359 LONG checkboxWidth
, checkboxHeight
;
1362 if (SendMessageW(infoPtr
->hwnd
, WM_GETTEXTLENGTH
, 0, 0) == 0)
1364 BUTTON_GetClientRectSize(infoPtr
, size
);
1368 hdc
= GetDC(infoPtr
->hwnd
);
1369 scaleX
= GetDeviceCaps(hdc
, LOGPIXELSX
) / 96.0;
1370 scaleY
= GetDeviceCaps(hdc
, LOGPIXELSY
) / 96.0;
1371 if ((hfont
= infoPtr
->font
)) SelectObject(hdc
, hfont
);
1372 GetCharWidthW(hdc
, '0', '0', &textOffset
);
1374 ReleaseDC(infoPtr
->hwnd
, hdc
);
1376 checkboxWidth
= 12 * scaleX
+ 1;
1377 checkboxHeight
= 12 * scaleY
+ 1;
1380 maxWidth
= size
->cx
- checkboxWidth
- textOffset
;
1381 if (maxWidth
<= 0) maxWidth
= 1;
1384 /* Checkbox doesn't support both image(but not image list) and text */
1385 if (!(style
& (BS_ICON
| BS_BITMAP
)) && infoPtr
->u
.image
)
1386 BUTTON_GetTextIdealSize(infoPtr
, maxWidth
, &labelSize
);
1388 BUTTON_GetLabelIdealSize(infoPtr
, maxWidth
, &labelSize
);
1390 size
->cx
= labelSize
.cx
+ checkboxWidth
+ textOffset
;
1391 size
->cy
= max(labelSize
.cy
, checkboxHeight
);
1396 static BOOL
PB_GetIdealSize(BUTTON_INFO
*infoPtr
, SIZE
*size
)
1400 if (SendMessageW(infoPtr
->hwnd
, WM_GETTEXTLENGTH
, 0, 0) == 0)
1401 BUTTON_GetClientRectSize(infoPtr
, size
);
1404 /* Ideal size include text size even if image only flags(BS_ICON, BS_BITMAP) are specified */
1405 BUTTON_GetLabelIdealSize(infoPtr
, size
->cx
, &labelSize
);
1407 size
->cx
= labelSize
.cx
;
1408 size
->cy
= labelSize
.cy
;
1413 static BOOL
SB_GetIdealSize(BUTTON_INFO
*infoPtr
, SIZE
*size
)
1415 LONG extra_width
= infoPtr
->glyph_size
.cx
* 2 + GetSystemMetrics(SM_CXEDGE
);
1418 if (SendMessageW(infoPtr
->hwnd
, WM_GETTEXTLENGTH
, 0, 0) == 0)
1420 BUTTON_GetClientRectSize(infoPtr
, size
);
1421 size
->cx
= max(size
->cx
, extra_width
);
1425 BUTTON_GetLabelIdealSize(infoPtr
, size
->cx
, &label_size
);
1426 size
->cx
= label_size
.cx
+ ((size
->cx
== 0) ? extra_width
: 0);
1427 size
->cy
= label_size
.cy
;
1432 static BOOL
CL_GetIdealSize(BUTTON_INFO
*infoPtr
, SIZE
*size
)
1434 HTHEME theme
= GetWindowTheme(infoPtr
->hwnd
);
1435 HDC hdc
= GetDC(infoPtr
->hwnd
);
1436 LONG w
, text_w
= 0, text_h
= 0;
1437 UINT flags
= DT_TOP
| DT_LEFT
;
1438 HFONT font
, old_font
= NULL
;
1439 RECT text_bound
= { 0 };
1444 /* Get the image size */
1445 if (infoPtr
->u
.image
|| infoPtr
->imagelist
.himl
)
1446 img_size
= BUTTON_GetImageSize(infoPtr
);
1450 GetThemePartSize(theme
, NULL
, BP_COMMANDLINKGLYPH
, CMDLS_NORMAL
, NULL
, TS_DRAW
, &img_size
);
1452 img_size
.cx
= img_size
.cy
= command_link_defglyph_size
;
1455 /* Get the content margins */
1458 RECT r
= { 0, 0, 0xffff, 0xffff };
1459 GetThemeBackgroundContentRect(theme
, hdc
, BP_COMMANDLINK
, CMDLS_NORMAL
, &r
, &margin
);
1460 margin
.left
-= r
.left
;
1461 margin
.top
-= r
.top
;
1462 margin
.right
= r
.right
- margin
.right
;
1463 margin
.bottom
= r
.bottom
- margin
.bottom
;
1467 margin
.left
= margin
.right
= command_link_margin
;
1468 margin
.top
= margin
.bottom
= command_link_margin
;
1471 /* Account for the border margins and the margin between image and text */
1472 w
= margin
.left
+ margin
.right
+ (img_size
.cx
? (img_size
.cx
+ command_link_margin
) : 0);
1474 /* If a rectangle with a specific width was requested, bound the text to it */
1477 text_bound
.right
= size
->cx
- w
;
1478 flags
|= DT_WORDBREAK
;
1483 if (infoPtr
->font
) old_font
= SelectObject(hdc
, infoPtr
->font
);
1485 /* Find the text's rect */
1486 if ((text
= get_button_text(infoPtr
)))
1489 GetThemeTextExtent(theme
, hdc
, BP_COMMANDLINK
, CMDLS_NORMAL
,
1490 text
, -1, flags
, &text_bound
, &r
);
1492 text_w
= r
.right
- r
.left
;
1493 text_h
= r
.bottom
- r
.top
;
1496 /* Find the note's rect */
1501 opts
.dwSize
= sizeof(opts
);
1502 opts
.dwFlags
= DTT_FONTPROP
| DTT_CALCRECT
;
1503 opts
.iFontPropId
= TMT_BODYFONT
;
1504 DrawThemeTextEx(theme
, hdc
, BP_COMMANDLINK
, CMDLS_NORMAL
,
1505 infoPtr
->note
, infoPtr
->note_length
,
1506 flags
| DT_NOPREFIX
| DT_CALCRECT
, &text_bound
, &opts
);
1507 text_w
= max(text_w
, text_bound
.right
- text_bound
.left
);
1508 text_h
+= text_bound
.bottom
- text_bound
.top
;
1513 NONCLIENTMETRICSW ncm
;
1515 ncm
.cbSize
= sizeof(ncm
);
1516 if (SystemParametersInfoW(SPI_GETNONCLIENTMETRICS
, sizeof(ncm
), &ncm
, 0))
1518 LONG note_weight
= ncm
.lfMessageFont
.lfWeight
;
1520 /* Find the text's rect */
1521 ncm
.lfMessageFont
.lfWeight
= FW_BOLD
;
1522 if ((font
= CreateFontIndirectW(&ncm
.lfMessageFont
)))
1524 if ((text
= get_button_text(infoPtr
)))
1526 RECT r
= text_bound
;
1527 old_font
= SelectObject(hdc
, font
);
1528 DrawTextW(hdc
, text
, -1, &r
, flags
| DT_CALCRECT
);
1531 text_w
= r
.right
- r
.left
;
1532 text_h
= r
.bottom
- r
.top
;
1537 /* Find the note's rect */
1538 ncm
.lfMessageFont
.lfWeight
= note_weight
;
1539 if (infoPtr
->note
&& (font
= CreateFontIndirectW(&ncm
.lfMessageFont
)))
1541 HFONT tmp
= SelectObject(hdc
, font
);
1542 if (!old_font
) old_font
= tmp
;
1544 DrawTextW(hdc
, infoPtr
->note
, infoPtr
->note_length
, &text_bound
,
1545 flags
| DT_NOPREFIX
| DT_CALCRECT
);
1548 text_w
= max(text_w
, text_bound
.right
- text_bound
.left
);
1549 text_h
+= text_bound
.bottom
- text_bound
.top
+ 2;
1555 size
->cx
= min(size
->cx
, w
);
1556 size
->cy
= max(text_h
, img_size
.cy
) + margin
.top
+ margin
.bottom
;
1558 if (old_font
) SelectObject(hdc
, old_font
);
1559 ReleaseDC(infoPtr
->hwnd
, hdc
);
1563 /**********************************************************************
1564 * BUTTON_CalcLayoutRects
1566 * Calculates the rectangles of the button label(image and text) and its parts depending on a button's style.
1568 * Returns flags to be passed to DrawText.
1569 * Calculated rectangle doesn't take into account button state
1570 * (pushed, etc.). If there is nothing to draw (no text/image) output
1571 * rectangle is empty, and return value is (UINT)-1.
1574 * infoPtr [I] Button pointer
1575 * hdc [I] Handle to device context to draw to
1576 * labelRc [I/O] Input the rect the label to be positioned in, and output the label rect
1577 * imageRc [O] Optional, output the image rect
1578 * textRc [O] Optional, output the text rect
1580 static UINT
BUTTON_CalcLayoutRects(const BUTTON_INFO
*infoPtr
, HDC hdc
, RECT
*labelRc
, RECT
*imageRc
, RECT
*textRc
)
1582 WCHAR
*text
= get_button_text(infoPtr
);
1583 SIZE imageSize
= BUTTON_GetImageSize(infoPtr
);
1584 RECT labelRect
, imageRect
, imageRectWithMargin
, textRect
;
1585 LONG imageMarginWidth
, imageMarginHeight
;
1586 const RECT
*textMargin
= BUTTON_GetTextMargin(infoPtr
);
1587 LONG style
, ex_style
, split_style
;
1588 RECT emptyMargin
= {0};
1592 /* Calculate label rectangle according to label type */
1593 if ((imageSize
.cx
== 0 && imageSize
.cy
== 0) && (text
== NULL
|| text
[0] == '\0'))
1595 SetRectEmpty(labelRc
);
1596 SetRectEmpty(imageRc
);
1597 SetRectEmpty(textRc
);
1602 style
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
1603 ex_style
= GetWindowLongW(infoPtr
->hwnd
, GWL_EXSTYLE
);
1604 /* Add BS_RIGHT directly. When both WS_EX_RIGHT and BS_LEFT are present, it becomes BS_CENTER */
1605 if (ex_style
& WS_EX_RIGHT
)
1607 split_style
= infoPtr
->imagelist
.himl
? BUTTON_ILStoBS(infoPtr
->imagelist
.uAlign
) : style
;
1608 dtStyle
= BUTTON_BStoDT(style
, ex_style
);
1610 /* Group boxes are top aligned unless BS_PUSHLIKE is set and it's not themed */
1611 if (get_button_type(style
) == BS_GROUPBOX
1612 && (!(style
& BS_PUSHLIKE
) || GetWindowTheme(infoPtr
->hwnd
)))
1613 style
&= ~BS_VCENTER
| BS_TOP
;
1615 SetRect(&imageRect
, 0, 0, imageSize
.cx
, imageSize
.cy
);
1616 imageRectWithMargin
= imageRect
;
1617 if (infoPtr
->imagelist
.himl
)
1619 imageRectWithMargin
.top
-= infoPtr
->imagelist
.margin
.top
;
1620 imageRectWithMargin
.bottom
+= infoPtr
->imagelist
.margin
.bottom
;
1621 imageRectWithMargin
.left
-= infoPtr
->imagelist
.margin
.left
;
1622 imageRectWithMargin
.right
+= infoPtr
->imagelist
.margin
.right
;
1625 /* Show image only */
1626 if (show_image_only(infoPtr
))
1628 BUTTON_PositionRect(style
, labelRc
, &imageRect
,
1629 infoPtr
->imagelist
.himl
? &infoPtr
->imagelist
.margin
: &emptyMargin
);
1630 labelRect
= imageRect
;
1631 SetRectEmpty(&textRect
);
1636 maxTextWidth
= labelRc
->right
- labelRc
->left
;
1637 textRect
= BUTTON_GetTextRect(infoPtr
, hdc
, text
, maxTextWidth
);
1639 /* Show image and text */
1640 if (show_image_and_text(infoPtr
))
1642 RECT boundingLabelRect
, boundingImageRect
, boundingTextRect
;
1644 /* Get label rect */
1645 /* Image list may have different alignment than the button, use the whole rect for label in this case */
1646 if (infoPtr
->imagelist
.himl
)
1647 labelRect
= *labelRc
;
1650 /* Get a label bounding rectangle to position the label in the user specified label rectangle because
1651 * text and image need to align together. */
1652 boundingLabelRect
= BUTTON_GetBoundingLabelRect(split_style
, &textRect
, &imageRectWithMargin
);
1653 BUTTON_PositionRect(split_style
, labelRc
, &boundingLabelRect
, &emptyMargin
);
1654 labelRect
= boundingLabelRect
;
1657 /* When imagelist has center align, use the whole rect for imagelist and text */
1658 if(infoPtr
->imagelist
.himl
&& infoPtr
->imagelist
.uAlign
== BUTTON_IMAGELIST_ALIGN_CENTER
)
1660 boundingImageRect
= labelRect
;
1661 boundingTextRect
= labelRect
;
1662 BUTTON_PositionRect(split_style
, &boundingImageRect
, &imageRect
,
1663 infoPtr
->imagelist
.himl
? &infoPtr
->imagelist
.margin
: &emptyMargin
);
1664 /* Text doesn't use imagelist align */
1665 BUTTON_PositionRect(style
, &boundingTextRect
, &textRect
, textMargin
);
1669 /* Get image rect */
1670 /* Split the label rect to two halves as two bounding rectangles for image and text */
1671 boundingImageRect
= labelRect
;
1672 imageMarginWidth
= imageRectWithMargin
.right
- imageRectWithMargin
.left
;
1673 imageMarginHeight
= imageRectWithMargin
.bottom
- imageRectWithMargin
.top
;
1674 if ((split_style
& BS_CENTER
) == BS_RIGHT
)
1675 boundingImageRect
.left
= boundingImageRect
.right
- imageMarginWidth
;
1676 else if ((split_style
& BS_CENTER
) == BS_LEFT
)
1677 boundingImageRect
.right
= boundingImageRect
.left
+ imageMarginWidth
;
1678 else if ((split_style
& BS_VCENTER
) == BS_BOTTOM
)
1679 boundingImageRect
.top
= boundingImageRect
.bottom
- imageMarginHeight
;
1680 else if ((split_style
& BS_VCENTER
) == BS_TOP
)
1681 boundingImageRect
.bottom
= boundingImageRect
.top
+ imageMarginHeight
;
1683 boundingImageRect
.right
= boundingImageRect
.left
+ imageMarginWidth
;
1684 BUTTON_PositionRect(split_style
, &boundingImageRect
, &imageRect
,
1685 infoPtr
->imagelist
.himl
? &infoPtr
->imagelist
.margin
: &emptyMargin
);
1688 SubtractRect(&boundingTextRect
, &labelRect
, &boundingImageRect
);
1689 /* Text doesn't use imagelist align */
1690 BUTTON_PositionRect(style
, &boundingTextRect
, &textRect
, textMargin
);
1693 /* Show text only */
1696 BUTTON_PositionRect(style
, labelRc
, &textRect
, textMargin
);
1697 labelRect
= textRect
;
1698 SetRectEmpty(&imageRect
);
1703 CopyRect(labelRc
, &labelRect
);
1704 CopyRect(imageRc
, &imageRect
);
1705 CopyRect(textRc
, &textRect
);
1711 /**********************************************************************
1714 * Draw the button's image into the specified rectangle.
1716 static void BUTTON_DrawImage(const BUTTON_INFO
*infoPtr
, HDC hdc
, HBRUSH hbr
, UINT flags
, const RECT
*rect
)
1718 if (infoPtr
->imagelist
.himl
)
1720 int i
= (ImageList_GetImageCount(infoPtr
->imagelist
.himl
) == 1) ? 0 : get_draw_state(infoPtr
) - 1;
1722 ImageList_Draw(infoPtr
->imagelist
.himl
, i
, hdc
, rect
->left
, rect
->top
, ILD_NORMAL
);
1726 switch (infoPtr
->image_type
)
1732 flags
|= DST_BITMAP
;
1738 DrawStateW(hdc
, hbr
, NULL
, (LPARAM
)infoPtr
->u
.image
, 0, rect
->left
, rect
->top
,
1739 rect
->right
- rect
->left
, rect
->bottom
- rect
->top
, flags
);
1744 /**********************************************************************
1745 * BUTTON_DrawTextCallback
1747 * Callback function used by DrawStateW function.
1749 static BOOL CALLBACK
BUTTON_DrawTextCallback(HDC hdc
, LPARAM lp
, WPARAM wp
, int cx
, int cy
)
1753 SetRect(&rc
, 0, 0, cx
, cy
);
1754 DrawTextW(hdc
, (LPCWSTR
)lp
, -1, &rc
, (UINT
)wp
);
1758 /**********************************************************************
1761 * Common function for drawing button label.
1764 * 1. When BS_SINGLELINE is specified and text contains '\t', '\n' or '\r' in the middle, they are rendered as
1765 * squares now whereas they should be ignored.
1766 * 2. When BS_MULTILINE is specified and text contains space in the middle, the space mistakenly be rendered as newline.
1768 static void BUTTON_DrawLabel(const BUTTON_INFO
*infoPtr
, HDC hdc
, UINT dtFlags
, const RECT
*imageRect
,
1769 const RECT
*textRect
)
1772 UINT flags
= IsWindowEnabled(infoPtr
->hwnd
) ? DSS_NORMAL
: DSS_DISABLED
;
1773 LONG style
= GetWindowLongW( infoPtr
->hwnd
, GWL_STYLE
);
1776 /* FIXME: To draw disabled label in Win31 look-and-feel, we probably
1777 * must use DSS_MONO flag and COLOR_GRAYTEXT brush (or maybe DSS_UNION).
1778 * I don't have Win31 on hand to verify that, so I leave it as is.
1781 if ((style
& BS_PUSHLIKE
) && (infoPtr
->state
& BST_INDETERMINATE
))
1783 hbr
= GetSysColorBrush(COLOR_GRAYTEXT
);
1787 if (show_image(infoPtr
)) BUTTON_DrawImage(infoPtr
, hdc
, hbr
, flags
, imageRect
);
1788 if (show_image_only(infoPtr
)) return;
1790 /* DST_COMPLEX -- is 0 */
1791 if (!(text
= get_button_text(infoPtr
))) return;
1792 DrawStateW(hdc
, hbr
, BUTTON_DrawTextCallback
, (LPARAM
)text
, dtFlags
, textRect
->left
, textRect
->top
,
1793 textRect
->right
- textRect
->left
, textRect
->bottom
- textRect
->top
, flags
);
1797 static void BUTTON_DrawThemedLabel(const BUTTON_INFO
*info
, HDC hdc
, UINT text_flags
,
1798 const RECT
*image_rect
, const RECT
*text_rect
, HTHEME theme
,
1799 int part
, int state
)
1801 HBRUSH brush
= NULL
;
1805 if (show_image(info
))
1807 image_flags
= IsWindowEnabled(info
->hwnd
) ? DSS_NORMAL
: DSS_DISABLED
;
1809 if ((GetWindowLongW(info
->hwnd
, GWL_STYLE
) & BS_PUSHLIKE
)
1810 && (info
->state
& BST_INDETERMINATE
))
1812 brush
= GetSysColorBrush(COLOR_GRAYTEXT
);
1813 image_flags
|= DSS_MONO
;
1816 BUTTON_DrawImage(info
, hdc
, brush
, image_flags
, image_rect
);
1819 if (show_image_only(info
))
1822 if (!(text
= get_button_text(info
)))
1825 DrawThemeText(theme
, hdc
, part
, state
, text
, lstrlenW(text
), text_flags
, 0, text_rect
);
1829 /**********************************************************************
1830 * Push Button Functions
1832 static void PB_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
)
1834 RECT rc
, labelRect
, imageRect
, textRect
;
1835 UINT dtFlags
= (UINT
)-1, uState
;
1839 COLORREF oldTxtColor
;
1843 LONG state
= infoPtr
->state
;
1844 LONG style
= GetWindowLongW( infoPtr
->hwnd
, GWL_STYLE
);
1845 BOOL pushedState
= (state
& BST_PUSHED
);
1849 GetClientRect( infoPtr
->hwnd
, &rc
);
1851 /* Send WM_CTLCOLOR to allow changing the font (the colors are fixed) */
1852 if ((hFont
= infoPtr
->font
)) SelectObject( hDC
, hFont
);
1853 parent
= GetParent(infoPtr
->hwnd
);
1854 if (!parent
) parent
= infoPtr
->hwnd
;
1855 SendMessageW( parent
, WM_CTLCOLORBTN
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
1857 hrgn
= set_control_clipping( hDC
, &rc
);
1859 hpen
= CreatePen( PS_SOLID
, 1, GetSysColor(COLOR_WINDOWFRAME
));
1860 hOldPen
= SelectObject(hDC
, hpen
);
1861 hOldBrush
= SelectObject(hDC
,GetSysColorBrush(COLOR_BTNFACE
));
1862 oldBkMode
= SetBkMode(hDC
, TRANSPARENT
);
1864 init_custom_draw(&nmcd
, infoPtr
, hDC
, &rc
);
1866 /* Send erase notifications */
1867 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
1868 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
1870 if (get_button_type(style
) == BS_DEFPUSHBUTTON
)
1872 if (action
!= ODA_FOCUS
)
1873 Rectangle(hDC
, rc
.left
, rc
.top
, rc
.right
, rc
.bottom
);
1874 InflateRect( &rc
, -1, -1 );
1877 /* Skip the frame drawing if only focus has changed */
1878 if (action
!= ODA_FOCUS
)
1880 uState
= DFCS_BUTTONPUSH
;
1882 if (style
& BS_FLAT
)
1883 uState
|= DFCS_MONO
;
1884 else if (pushedState
)
1886 if (get_button_type(style
) == BS_DEFPUSHBUTTON
)
1887 uState
|= DFCS_FLAT
;
1889 uState
|= DFCS_PUSHED
;
1892 if (state
& (BST_CHECKED
| BST_INDETERMINATE
))
1893 uState
|= DFCS_CHECKED
;
1895 DrawFrameControl( hDC
, &rc
, DFC_BUTTON
, uState
);
1898 if (cdrf
& CDRF_NOTIFYPOSTERASE
)
1900 nmcd
.dwDrawStage
= CDDS_POSTERASE
;
1901 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
1904 /* Send paint notifications */
1905 nmcd
.dwDrawStage
= CDDS_PREPAINT
;
1906 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
1907 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
1909 if (!(cdrf
& CDRF_DOERASE
) && action
!= ODA_FOCUS
)
1911 /* draw button label */
1913 /* Shrink label rect at all sides by 2 so that the content won't touch the surrounding frame */
1914 InflateRect(&labelRect
, -2, -2);
1915 dtFlags
= BUTTON_CalcLayoutRects(infoPtr
, hDC
, &labelRect
, &imageRect
, &textRect
);
1917 if (dtFlags
!= (UINT
)-1L)
1919 if (pushedState
) OffsetRect(&labelRect
, 1, 1);
1921 oldTxtColor
= SetTextColor( hDC
, GetSysColor(COLOR_BTNTEXT
) );
1923 BUTTON_DrawLabel(infoPtr
, hDC
, dtFlags
, &imageRect
, &textRect
);
1925 SetTextColor( hDC
, oldTxtColor
);
1929 if (cdrf
& CDRF_NOTIFYPOSTPAINT
)
1931 nmcd
.dwDrawStage
= CDDS_POSTPAINT
;
1932 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
1934 if ((cdrf
& CDRF_SKIPPOSTPAINT
) || dtFlags
== (UINT
)-1L) goto cleanup
;
1936 if (action
== ODA_FOCUS
|| (state
& BST_FOCUS
))
1938 InflateRect( &rc
, -2, -2 );
1939 DrawFocusRect( hDC
, &rc
);
1943 SelectObject( hDC
, hOldPen
);
1944 SelectObject( hDC
, hOldBrush
);
1945 SetBkMode(hDC
, oldBkMode
);
1946 SelectClipRgn( hDC
, hrgn
);
1947 if (hrgn
) DeleteObject( hrgn
);
1948 DeleteObject( hpen
);
1951 /**********************************************************************
1952 * Check Box & Radio Button Functions
1955 /* Get adjusted check box or radio box rectangle */
1956 static RECT
get_box_rect(LONG style
, LONG ex_style
, const RECT
*content_rect
,
1957 const RECT
*label_rect
, BOOL has_label
, SIZE box_size
)
1962 rect
= *content_rect
;
1964 if (style
& BS_LEFTTEXT
|| ex_style
& WS_EX_RIGHT
)
1965 rect
.left
= rect
.right
- box_size
.cx
;
1967 rect
.right
= rect
.left
+ box_size
.cx
;
1969 /* Adjust box when label is valid */
1972 rect
.top
= label_rect
->top
;
1973 rect
.bottom
= label_rect
->bottom
;
1976 /* Box must have the correct height */
1977 delta
= rect
.bottom
- rect
.top
- box_size
.cy
;
1978 if ((style
& BS_VCENTER
) == BS_TOP
)
1981 rect
.top
-= -delta
/ 2 + 1;
1983 rect
.bottom
= rect
.top
+ box_size
.cy
;
1985 else if ((style
& BS_VCENTER
) == BS_BOTTOM
)
1988 rect
.bottom
+= -delta
/ 2 + 1;
1990 rect
.top
= rect
.bottom
- box_size
.cy
;
1996 rect
.bottom
-= delta
/ 2 + 1;
1997 rect
.top
= rect
.bottom
- box_size
.cy
;
2001 rect
.top
-= -delta
/ 2 + 1;
2002 rect
.bottom
= rect
.top
+ box_size
.cy
;
2009 static void CB_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
)
2011 RECT rbox
, labelRect
, oldLabelRect
, imageRect
, textRect
, client
;
2018 LONG state
= infoPtr
->state
;
2019 LONG style
= GetWindowLongW( infoPtr
->hwnd
, GWL_STYLE
);
2020 LONG ex_style
= GetWindowLongW( infoPtr
->hwnd
, GWL_EXSTYLE
);
2025 if (style
& BS_PUSHLIKE
)
2027 PB_Paint( infoPtr
, hDC
, action
);
2031 GetClientRect(infoPtr
->hwnd
, &client
);
2034 box_size
.cx
= 12 * GetDpiForWindow(infoPtr
->hwnd
) / 96 + 1;
2035 box_size
.cy
= box_size
.cx
;
2037 if ((hFont
= infoPtr
->font
)) SelectObject( hDC
, hFont
);
2038 GetCharWidthW( hDC
, '0', '0', &text_offset
);
2041 parent
= GetParent(infoPtr
->hwnd
);
2042 if (!parent
) parent
= infoPtr
->hwnd
;
2043 hBrush
= (HBRUSH
)SendMessageW(parent
, WM_CTLCOLORSTATIC
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
2044 if (!hBrush
) /* did the app forget to call defwindowproc ? */
2045 hBrush
= (HBRUSH
)DefWindowProcW(parent
, WM_CTLCOLORSTATIC
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
2046 hrgn
= set_control_clipping( hDC
, &client
);
2048 if (style
& BS_LEFTTEXT
|| ex_style
& WS_EX_RIGHT
)
2049 labelRect
.right
-= box_size
.cx
+ text_offset
;
2051 labelRect
.left
+= box_size
.cx
+ text_offset
;
2053 oldLabelRect
= labelRect
;
2054 dtFlags
= BUTTON_CalcLayoutRects(infoPtr
, hDC
, &labelRect
, &imageRect
, &textRect
);
2055 rbox
= get_box_rect(style
, ex_style
, &client
, &labelRect
, dtFlags
!= (UINT
)-1L, box_size
);
2057 init_custom_draw(&nmcd
, infoPtr
, hDC
, &client
);
2059 /* Send erase notifications */
2060 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2061 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
2063 /* Since WM_ERASEBKGND does nothing, first prepare background */
2064 if (action
== ODA_SELECT
) FillRect( hDC
, &rbox
, hBrush
);
2065 if (action
== ODA_DRAWENTIRE
) FillRect( hDC
, &client
, hBrush
);
2066 if (cdrf
& CDRF_NOTIFYPOSTERASE
)
2068 nmcd
.dwDrawStage
= CDDS_POSTERASE
;
2069 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2073 /* Send paint notifications */
2074 nmcd
.dwDrawStage
= CDDS_PREPAINT
;
2075 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2076 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
2078 /* Draw the check-box bitmap */
2079 if (!(cdrf
& CDRF_DOERASE
))
2081 if (action
== ODA_DRAWENTIRE
|| action
== ODA_SELECT
)
2085 if ((get_button_type(style
) == BS_RADIOBUTTON
) ||
2086 (get_button_type(style
) == BS_AUTORADIOBUTTON
)) flags
= DFCS_BUTTONRADIO
;
2087 else if (state
& BST_INDETERMINATE
) flags
= DFCS_BUTTON3STATE
;
2088 else flags
= DFCS_BUTTONCHECK
;
2090 if (state
& (BST_CHECKED
| BST_INDETERMINATE
)) flags
|= DFCS_CHECKED
;
2091 if (state
& BST_PUSHED
) flags
|= DFCS_PUSHED
;
2092 if (style
& WS_DISABLED
) flags
|= DFCS_INACTIVE
;
2094 DrawFrameControl(hDC
, &rbox
, DFC_BUTTON
, flags
);
2097 if (dtFlags
!= (UINT
)-1L) /* Something to draw */
2098 if (action
== ODA_DRAWENTIRE
) BUTTON_DrawLabel(infoPtr
, hDC
, dtFlags
, &imageRect
, &textRect
);
2101 if (cdrf
& CDRF_NOTIFYPOSTPAINT
)
2103 nmcd
.dwDrawStage
= CDDS_POSTPAINT
;
2104 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2106 if ((cdrf
& CDRF_SKIPPOSTPAINT
) || dtFlags
== (UINT
)-1L) goto cleanup
;
2109 if (action
== ODA_FOCUS
|| (state
& BST_FOCUS
))
2113 IntersectRect(&labelRect
, &labelRect
, &oldLabelRect
);
2114 DrawFocusRect(hDC
, &labelRect
);
2118 SelectClipRgn( hDC
, hrgn
);
2119 if (hrgn
) DeleteObject( hrgn
);
2123 /**********************************************************************
2124 * BUTTON_CheckAutoRadioButton
2126 * hwnd is checked, uncheck every other auto radio button in group
2128 static void BUTTON_CheckAutoRadioButton( HWND hwnd
)
2130 HWND parent
, sibling
, start
;
2132 parent
= GetParent(hwnd
);
2133 /* make sure that starting control is not disabled or invisible */
2134 start
= sibling
= GetNextDlgGroupItem( parent
, hwnd
, TRUE
);
2137 if (!sibling
) break;
2138 if ((hwnd
!= sibling
) &&
2139 ((GetWindowLongW( sibling
, GWL_STYLE
) & BS_TYPEMASK
) == BS_AUTORADIOBUTTON
))
2140 SendMessageW( sibling
, BM_SETCHECK
, BST_UNCHECKED
, 0 );
2141 sibling
= GetNextDlgGroupItem( parent
, sibling
, FALSE
);
2142 } while (sibling
!= start
);
2146 /**********************************************************************
2147 * Group Box Functions
2150 static void GB_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
)
2152 RECT labelRect
, imageRect
, textRect
, rcFrame
;
2157 LONG style
= GetWindowLongW( infoPtr
->hwnd
, GWL_STYLE
);
2161 if ((hFont
= infoPtr
->font
)) SelectObject( hDC
, hFont
);
2162 /* GroupBox acts like static control, so it sends CTLCOLORSTATIC */
2163 parent
= GetParent(infoPtr
->hwnd
);
2164 if (!parent
) parent
= infoPtr
->hwnd
;
2165 hbr
= (HBRUSH
)SendMessageW(parent
, WM_CTLCOLORSTATIC
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
2166 if (!hbr
) /* did the app forget to call defwindowproc ? */
2167 hbr
= (HBRUSH
)DefWindowProcW(parent
, WM_CTLCOLORSTATIC
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
2168 GetClientRect(infoPtr
->hwnd
, &labelRect
);
2169 rcFrame
= labelRect
;
2170 hrgn
= set_control_clipping(hDC
, &labelRect
);
2172 GetTextMetricsW (hDC
, &tm
);
2173 rcFrame
.top
+= (tm
.tmHeight
/ 2) - 1;
2174 DrawEdge (hDC
, &rcFrame
, EDGE_ETCHED
, BF_RECT
| ((style
& BS_FLAT
) ? BF_FLAT
: 0));
2176 InflateRect(&labelRect
, -7, 1);
2177 dtFlags
= BUTTON_CalcLayoutRects(infoPtr
, hDC
, &labelRect
, &imageRect
, &textRect
);
2179 if (dtFlags
!= (UINT
)-1)
2181 /* Because buttons have CS_PARENTDC class style, there is a chance
2182 * that label will be drawn out of client rect.
2183 * But Windows doesn't clip label's rect, so do I.
2186 /* There is 1-pixel margin at the left, right, and bottom */
2190 FillRect(hDC
, &labelRect
, hbr
);
2191 BUTTON_DrawLabel(infoPtr
, hDC
, dtFlags
, &imageRect
, &textRect
);
2193 SelectClipRgn( hDC
, hrgn
);
2194 if (hrgn
) DeleteObject( hrgn
);
2198 /**********************************************************************
2199 * User Button Functions
2202 static void UB_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
)
2209 LONG state
= infoPtr
->state
;
2212 GetClientRect( infoPtr
->hwnd
, &rc
);
2214 if ((hFont
= infoPtr
->font
)) SelectObject( hDC
, hFont
);
2216 parent
= GetParent(infoPtr
->hwnd
);
2217 if (!parent
) parent
= infoPtr
->hwnd
;
2218 hBrush
= (HBRUSH
)SendMessageW(parent
, WM_CTLCOLORBTN
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
2219 if (!hBrush
) /* did the app forget to call defwindowproc ? */
2220 hBrush
= (HBRUSH
)DefWindowProcW(parent
, WM_CTLCOLORBTN
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
2222 if (action
== ODA_FOCUS
|| (state
& BST_FOCUS
))
2224 init_custom_draw(&nmcd
, infoPtr
, hDC
, &rc
);
2226 /* Send erase notifications */
2227 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2228 if (cdrf
& CDRF_SKIPDEFAULT
) goto notify
;
2231 FillRect( hDC
, &rc
, hBrush
);
2232 if (action
== ODA_FOCUS
|| (state
& BST_FOCUS
))
2234 if (cdrf
& CDRF_NOTIFYPOSTERASE
)
2236 nmcd
.dwDrawStage
= CDDS_POSTERASE
;
2237 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2240 /* Send paint notifications */
2241 nmcd
.dwDrawStage
= CDDS_PREPAINT
;
2242 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2243 if (cdrf
& CDRF_SKIPDEFAULT
) goto notify
;
2244 if (cdrf
& CDRF_NOTIFYPOSTPAINT
)
2246 nmcd
.dwDrawStage
= CDDS_POSTPAINT
;
2247 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2250 if (!(cdrf
& CDRF_SKIPPOSTPAINT
))
2251 DrawFocusRect( hDC
, &rc
);
2258 BUTTON_NOTIFY_PARENT( infoPtr
->hwnd
, (state
& BST_FOCUS
) ? BN_SETFOCUS
: BN_KILLFOCUS
);
2262 BUTTON_NOTIFY_PARENT( infoPtr
->hwnd
, (state
& BST_PUSHED
) ? BN_HILITE
: BN_UNHILITE
);
2271 /**********************************************************************
2272 * Ownerdrawn Button Functions
2275 static void OB_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
)
2277 LONG state
= infoPtr
->state
;
2279 LONG_PTR id
= GetWindowLongPtrW( infoPtr
->hwnd
, GWLP_ID
);
2284 dis
.CtlType
= ODT_BUTTON
;
2287 dis
.itemAction
= action
;
2288 dis
.itemState
= ((state
& BST_FOCUS
) ? ODS_FOCUS
: 0) |
2289 ((state
& BST_PUSHED
) ? ODS_SELECTED
: 0) |
2290 (IsWindowEnabled(infoPtr
->hwnd
) ? 0: ODS_DISABLED
);
2291 dis
.hwndItem
= infoPtr
->hwnd
;
2294 GetClientRect( infoPtr
->hwnd
, &dis
.rcItem
);
2296 if ((hFont
= infoPtr
->font
)) SelectObject( hDC
, hFont
);
2297 parent
= GetParent(infoPtr
->hwnd
);
2298 if (!parent
) parent
= infoPtr
->hwnd
;
2299 SendMessageW( parent
, WM_CTLCOLORBTN
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
2301 hrgn
= set_control_clipping( hDC
, &dis
.rcItem
);
2303 SendMessageW( GetParent(infoPtr
->hwnd
), WM_DRAWITEM
, id
, (LPARAM
)&dis
);
2304 SelectClipRgn( hDC
, hrgn
);
2305 if (hrgn
) DeleteObject( hrgn
);
2309 /**********************************************************************
2310 * Split Button Functions
2312 static void SB_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
)
2314 LONG style
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
2315 LONG state
= infoPtr
->state
;
2316 UINT dtFlags
= (UINT
)-1L;
2318 RECT rc
, push_rect
, dropdown_rect
;
2327 GetClientRect(infoPtr
->hwnd
, &rc
);
2329 /* Send WM_CTLCOLOR to allow changing the font (the colors are fixed) */
2330 if (infoPtr
->font
) SelectObject(hDC
, infoPtr
->font
);
2331 if (!(parent
= GetParent(infoPtr
->hwnd
))) parent
= infoPtr
->hwnd
;
2332 SendMessageW(parent
, WM_CTLCOLORBTN
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
2334 hrgn
= set_control_clipping(hDC
, &rc
);
2336 pen
= CreatePen(PS_SOLID
, 1, GetSysColor(COLOR_WINDOWFRAME
));
2337 old_pen
= SelectObject(hDC
, pen
);
2338 old_brush
= SelectObject(hDC
, GetSysColorBrush(COLOR_BTNFACE
));
2339 old_bk_mode
= SetBkMode(hDC
, TRANSPARENT
);
2341 init_custom_draw(&nmcd
, infoPtr
, hDC
, &rc
);
2343 /* Send erase notifications */
2344 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2345 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
2347 if (get_button_type(style
) == BS_DEFSPLITBUTTON
)
2349 if (action
!= ODA_FOCUS
)
2350 Rectangle(hDC
, rc
.left
, rc
.top
, rc
.right
, rc
.bottom
);
2351 InflateRect(&rc
, -1, -1);
2352 /* The split will now be off by 1 pixel, but
2353 that's exactly what Windows does as well */
2356 get_split_button_rects(infoPtr
, &rc
, &push_rect
, &dropdown_rect
);
2357 if (infoPtr
->split_style
& BCSS_NOSPLIT
)
2360 /* Skip the frame drawing if only focus has changed */
2361 if (action
!= ODA_FOCUS
)
2363 UINT flags
= DFCS_BUTTONPUSH
;
2365 if (style
& BS_FLAT
) flags
|= DFCS_MONO
;
2366 else if (state
& BST_PUSHED
)
2367 flags
|= (get_button_type(style
) == BS_DEFSPLITBUTTON
)
2368 ? DFCS_FLAT
: DFCS_PUSHED
;
2370 if (state
& (BST_CHECKED
| BST_INDETERMINATE
))
2371 flags
|= DFCS_CHECKED
;
2373 if (infoPtr
->split_style
& BCSS_NOSPLIT
)
2374 DrawFrameControl(hDC
, &push_rect
, DFC_BUTTON
, flags
);
2377 UINT dropdown_flags
= flags
& ~DFCS_CHECKED
;
2379 if (state
& BST_DROPDOWNPUSHED
)
2380 dropdown_flags
= (dropdown_flags
& ~DFCS_FLAT
) | DFCS_PUSHED
;
2382 /* Adjust for shadow and draw order so it looks properly */
2383 if (infoPtr
->split_style
& BCSS_ALIGNLEFT
)
2385 dropdown_rect
.right
++;
2386 DrawFrameControl(hDC
, &dropdown_rect
, DFC_BUTTON
, dropdown_flags
);
2387 dropdown_rect
.right
--;
2388 DrawFrameControl(hDC
, &push_rect
, DFC_BUTTON
, flags
);
2393 DrawFrameControl(hDC
, &push_rect
, DFC_BUTTON
, flags
);
2395 DrawFrameControl(hDC
, &dropdown_rect
, DFC_BUTTON
, dropdown_flags
);
2400 if (cdrf
& CDRF_NOTIFYPOSTERASE
)
2402 nmcd
.dwDrawStage
= CDDS_POSTERASE
;
2403 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2406 /* Send paint notifications */
2407 nmcd
.dwDrawStage
= CDDS_PREPAINT
;
2408 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2409 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
2411 /* Shrink push button rect so that the content won't touch the surrounding frame */
2412 InflateRect(&push_rect
, -2, -2);
2414 if (!(cdrf
& CDRF_DOERASE
) && action
!= ODA_FOCUS
)
2416 COLORREF old_color
= SetTextColor(hDC
, GetSysColor(COLOR_BTNTEXT
));
2417 RECT label_rect
= push_rect
, image_rect
, text_rect
;
2419 dtFlags
= BUTTON_CalcLayoutRects(infoPtr
, hDC
, &label_rect
, &image_rect
, &text_rect
);
2421 if (dtFlags
!= (UINT
)-1L)
2422 BUTTON_DrawLabel(infoPtr
, hDC
, dtFlags
, &image_rect
, &text_rect
);
2424 draw_split_button_dropdown_glyph(infoPtr
, hDC
, &dropdown_rect
);
2425 SetTextColor(hDC
, old_color
);
2428 if (cdrf
& CDRF_NOTIFYPOSTPAINT
)
2430 nmcd
.dwDrawStage
= CDDS_POSTPAINT
;
2431 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2433 if ((cdrf
& CDRF_SKIPPOSTPAINT
) || dtFlags
== (UINT
)-1L) goto cleanup
;
2435 if (action
== ODA_FOCUS
|| (state
& BST_FOCUS
))
2436 DrawFocusRect(hDC
, &push_rect
);
2439 SelectObject(hDC
, old_pen
);
2440 SelectObject(hDC
, old_brush
);
2441 SetBkMode(hDC
, old_bk_mode
);
2442 SelectClipRgn(hDC
, hrgn
);
2443 if (hrgn
) DeleteObject(hrgn
);
2447 /* Given the full button rect of the split button, retrieve the push part and the dropdown part */
2448 static inline void get_split_button_rects(const BUTTON_INFO
*infoPtr
, const RECT
*button_rect
,
2449 RECT
*push_rect
, RECT
*dropdown_rect
)
2451 *push_rect
= *dropdown_rect
= *button_rect
;
2453 /* The dropdown takes priority if the client rect is too small, it will only have a dropdown */
2454 if (infoPtr
->split_style
& BCSS_ALIGNLEFT
)
2456 dropdown_rect
->right
= min(button_rect
->left
+ infoPtr
->glyph_size
.cx
, button_rect
->right
);
2457 push_rect
->left
= dropdown_rect
->right
;
2461 dropdown_rect
->left
= max(button_rect
->right
- infoPtr
->glyph_size
.cx
, button_rect
->left
);
2462 push_rect
->right
= dropdown_rect
->left
;
2466 /* Notify the parent if the point is within the dropdown and return TRUE (always notify if NULL) */
2467 static BOOL
notify_split_button_dropdown(const BUTTON_INFO
*infoPtr
, const POINT
*pt
, HWND hwnd
)
2471 GetClientRect(hwnd
, &nmbcd
.rcButton
);
2474 RECT push_rect
, dropdown_rect
;
2476 get_split_button_rects(infoPtr
, &nmbcd
.rcButton
, &push_rect
, &dropdown_rect
);
2477 if (!PtInRect(&dropdown_rect
, *pt
))
2480 /* If it's already down (set manually via BCM_SETDROPDOWNSTATE), fake the notify */
2481 if (infoPtr
->state
& BST_DROPDOWNPUSHED
)
2484 SendMessageW(hwnd
, BCM_SETDROPDOWNSTATE
, TRUE
, 0);
2486 nmbcd
.hdr
.hwndFrom
= hwnd
;
2487 nmbcd
.hdr
.idFrom
= GetWindowLongPtrW(hwnd
, GWLP_ID
);
2488 nmbcd
.hdr
.code
= BCN_DROPDOWN
;
2489 SendMessageW(GetParent(hwnd
), WM_NOTIFY
, nmbcd
.hdr
.idFrom
, (LPARAM
)&nmbcd
);
2491 SendMessageW(hwnd
, BCM_SETDROPDOWNSTATE
, FALSE
, 0);
2495 /* Draw the split button dropdown glyph or image */
2496 static void draw_split_button_dropdown_glyph(const BUTTON_INFO
*infoPtr
, HDC hdc
, RECT
*rect
)
2498 if (infoPtr
->split_style
& BCSS_IMAGE
)
2502 /* When the glyph is an image list, Windows is very buggy with BCSS_STRETCH,
2503 positions it weirdly and doesn't even stretch it, but instead extends the
2504 image, leaking into other images in the list (or black if none). Instead,
2505 we'll ignore this and just position it at center as without BCSS_STRETCH. */
2506 if (!ImageList_GetIconSize(infoPtr
->glyph
, &w
, &h
)) return;
2508 ImageList_Draw(infoPtr
->glyph
,
2509 (ImageList_GetImageCount(infoPtr
->glyph
) == 1) ? 0 : get_draw_state(infoPtr
) - 1,
2510 hdc
, rect
->left
+ (rect
->right
- rect
->left
- w
) / 2,
2511 rect
->top
+ (rect
->bottom
- rect
->top
- h
) / 2, ILD_NORMAL
);
2513 else if (infoPtr
->glyph_size
.cy
>= 0)
2515 /* infoPtr->glyph is a character code from Marlett */
2516 HFONT font
, old_font
;
2517 LOGFONTW logfont
= { 0, 0, 0, 0, FW_NORMAL
, 0, 0, 0, SYMBOL_CHARSET
, 0, 0, 0, 0,
2519 if (infoPtr
->glyph_size
.cy
)
2521 /* BCSS_STRETCH preserves aspect ratio, uses minimum as size */
2522 if (infoPtr
->split_style
& BCSS_STRETCH
)
2523 logfont
.lfHeight
= min(infoPtr
->glyph_size
.cx
, infoPtr
->glyph_size
.cy
);
2526 logfont
.lfWidth
= infoPtr
->glyph_size
.cx
;
2527 logfont
.lfHeight
= infoPtr
->glyph_size
.cy
;
2530 else logfont
.lfHeight
= infoPtr
->glyph_size
.cx
;
2532 if ((font
= CreateFontIndirectW(&logfont
)))
2534 old_font
= SelectObject(hdc
, font
);
2535 DrawTextW(hdc
, (const WCHAR
*)&infoPtr
->glyph
, 1, rect
,
2536 DT_CENTER
| DT_VCENTER
| DT_SINGLELINE
| DT_NOCLIP
| DT_NOPREFIX
);
2537 SelectObject(hdc
, old_font
);
2544 /**********************************************************************
2545 * Command Link Functions
2547 static void CL_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
)
2549 LONG style
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
2550 LONG state
= infoPtr
->state
;
2552 RECT rc
, content_rect
;
2561 GetClientRect(infoPtr
->hwnd
, &rc
);
2563 /* Command Links are not affected by the button's font, and are based
2564 on the default message font. Furthermore, they are not affected by
2565 any of the alignment styles (and always align with the top-left). */
2566 if (!(parent
= GetParent(infoPtr
->hwnd
))) parent
= infoPtr
->hwnd
;
2567 SendMessageW(parent
, WM_CTLCOLORBTN
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
2569 hrgn
= set_control_clipping(hDC
, &rc
);
2571 pen
= CreatePen(PS_SOLID
, 1, GetSysColor(COLOR_WINDOWFRAME
));
2572 old_pen
= SelectObject(hDC
, pen
);
2573 old_brush
= SelectObject(hDC
, GetSysColorBrush(COLOR_BTNFACE
));
2574 old_bk_mode
= SetBkMode(hDC
, TRANSPARENT
);
2576 init_custom_draw(&nmcd
, infoPtr
, hDC
, &rc
);
2578 /* Send erase notifications */
2579 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2580 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
2583 if (get_button_type(style
) == BS_DEFCOMMANDLINK
)
2585 if (action
!= ODA_FOCUS
)
2586 Rectangle(hDC
, rc
.left
, rc
.top
, rc
.right
, rc
.bottom
);
2587 InflateRect(&rc
, -1, -1);
2590 /* Skip the frame drawing if only focus has changed */
2591 if (action
!= ODA_FOCUS
)
2593 if (!(state
& (BST_HOT
| BST_PUSHED
| BST_CHECKED
| BST_INDETERMINATE
)))
2594 FillRect(hDC
, &rc
, GetSysColorBrush(COLOR_BTNFACE
));
2597 UINT flags
= DFCS_BUTTONPUSH
;
2599 if (style
& BS_FLAT
) flags
|= DFCS_MONO
;
2600 else if (state
& BST_PUSHED
) flags
|= DFCS_PUSHED
;
2602 if (state
& (BST_CHECKED
| BST_INDETERMINATE
))
2603 flags
|= DFCS_CHECKED
;
2604 DrawFrameControl(hDC
, &rc
, DFC_BUTTON
, flags
);
2608 if (cdrf
& CDRF_NOTIFYPOSTERASE
)
2610 nmcd
.dwDrawStage
= CDDS_POSTERASE
;
2611 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2614 /* Send paint notifications */
2615 nmcd
.dwDrawStage
= CDDS_PREPAINT
;
2616 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2617 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
2619 if (!(cdrf
& CDRF_DOERASE
) && action
!= ODA_FOCUS
)
2621 UINT flags
= IsWindowEnabled(infoPtr
->hwnd
) ? DSS_NORMAL
: DSS_DISABLED
;
2622 COLORREF old_color
= SetTextColor(hDC
, GetSysColor(flags
== DSS_NORMAL
?
2623 COLOR_BTNTEXT
: COLOR_GRAYTEXT
));
2624 HIMAGELIST defimg
= NULL
;
2625 NONCLIENTMETRICSW ncm
;
2629 /* Command Links ignore the margins of the image list or its alignment */
2630 if (infoPtr
->u
.image
|| infoPtr
->imagelist
.himl
)
2631 img_size
= BUTTON_GetImageSize(infoPtr
);
2634 img_size
.cx
= img_size
.cy
= command_link_defglyph_size
;
2635 defimg
= ImageList_LoadImageW(COMCTL32_hModule
, (LPCWSTR
)MAKEINTRESOURCE(IDB_CMDLINK
),
2636 img_size
.cx
, 3, CLR_NONE
, IMAGE_BITMAP
, LR_CREATEDIBSECTION
);
2639 /* Shrink rect by the command link margin, except on bottom (just the frame) */
2640 InflateRect(&content_rect
, -command_link_margin
, -command_link_margin
);
2641 content_rect
.bottom
+= command_link_margin
- 2;
2643 ncm
.cbSize
= sizeof(ncm
);
2644 if (SystemParametersInfoW(SPI_GETNONCLIENTMETRICS
, sizeof(ncm
), &ncm
, 0))
2646 LONG note_weight
= ncm
.lfMessageFont
.lfWeight
;
2647 RECT r
= content_rect
;
2651 if (img_size
.cx
) r
.left
+= img_size
.cx
+ command_link_margin
;
2654 ncm
.lfMessageFont
.lfWeight
= FW_BOLD
;
2655 if ((font
= CreateFontIndirectW(&ncm
.lfMessageFont
)))
2657 if ((text
= get_button_text(infoPtr
)))
2659 SelectObject(hDC
, font
);
2660 txt_h
= DrawTextW(hDC
, text
, -1, &r
,
2661 DT_TOP
| DT_LEFT
| DT_WORDBREAK
| DT_END_ELLIPSIS
);
2668 ncm
.lfMessageFont
.lfWeight
= note_weight
;
2669 if (infoPtr
->note
&& (font
= CreateFontIndirectW(&ncm
.lfMessageFont
)))
2672 SelectObject(hDC
, font
);
2673 DrawTextW(hDC
, infoPtr
->note
, infoPtr
->note_length
, &r
,
2674 DT_TOP
| DT_LEFT
| DT_WORDBREAK
| DT_NOPREFIX
);
2679 /* Position the image at the vertical center of the drawn text (not note) */
2680 txt_h
= min(txt_h
, content_rect
.bottom
- content_rect
.top
);
2681 if (img_size
.cy
< txt_h
) content_rect
.top
+= (txt_h
- img_size
.cy
) / 2;
2683 content_rect
.right
= content_rect
.left
+ img_size
.cx
;
2684 content_rect
.bottom
= content_rect
.top
+ img_size
.cy
;
2689 if (flags
== DSS_DISABLED
) i
= 2;
2690 else if (state
& BST_HOT
) i
= 1;
2692 ImageList_Draw(defimg
, i
, hDC
, content_rect
.left
, content_rect
.top
, ILD_NORMAL
);
2693 ImageList_Destroy(defimg
);
2696 BUTTON_DrawImage(infoPtr
, hDC
, NULL
, flags
, &content_rect
);
2698 SetTextColor(hDC
, old_color
);
2701 if (cdrf
& CDRF_NOTIFYPOSTPAINT
)
2703 nmcd
.dwDrawStage
= CDDS_POSTPAINT
;
2704 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2706 if (cdrf
& CDRF_SKIPPOSTPAINT
) goto cleanup
;
2708 if (action
== ODA_FOCUS
|| (state
& BST_FOCUS
))
2710 InflateRect(&rc
, -2, -2);
2711 DrawFocusRect(hDC
, &rc
);
2715 SelectObject(hDC
, old_pen
);
2716 SelectObject(hDC
, old_brush
);
2717 SetBkMode(hDC
, old_bk_mode
);
2718 SelectClipRgn(hDC
, hrgn
);
2719 if (hrgn
) DeleteObject(hrgn
);
2724 /**********************************************************************
2725 * Themed Paint Functions
2727 static void PB_ThemedPaint(HTHEME theme
, const BUTTON_INFO
*infoPtr
, HDC hDC
, int state
, UINT dtFlags
, BOOL focused
)
2729 RECT bgRect
, labelRect
, imageRect
, textRect
, focusRect
;
2735 if (infoPtr
->font
) SelectObject(hDC
, infoPtr
->font
);
2737 GetClientRect(infoPtr
->hwnd
, &bgRect
);
2738 GetThemeBackgroundContentRect(theme
, hDC
, BP_PUSHBUTTON
, state
, &bgRect
, &labelRect
);
2739 focusRect
= labelRect
;
2741 init_custom_draw(&nmcd
, infoPtr
, hDC
, &bgRect
);
2743 parent
= GetParent(infoPtr
->hwnd
);
2744 if (!parent
) parent
= infoPtr
->hwnd
;
2746 /* Send erase notifications */
2747 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2748 if (cdrf
& CDRF_SKIPDEFAULT
) return;
2750 if (IsThemeBackgroundPartiallyTransparent(theme
, BP_PUSHBUTTON
, state
))
2752 DrawThemeParentBackground(infoPtr
->hwnd
, hDC
, NULL
);
2753 /* Tests show that the brush from WM_CTLCOLORBTN is used for filling background after a
2754 * DrawThemeParentBackground() call */
2755 brush
= (HBRUSH
)SendMessageW(parent
, WM_CTLCOLORBTN
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
2756 FillRect(hDC
, &bgRect
, brush
? brush
: GetSysColorBrush(COLOR_BTNFACE
));
2758 DrawThemeBackground(theme
, hDC
, BP_PUSHBUTTON
, state
, &bgRect
, NULL
);
2760 if (cdrf
& CDRF_NOTIFYPOSTERASE
)
2762 nmcd
.dwDrawStage
= CDDS_POSTERASE
;
2763 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2766 /* Send paint notifications */
2767 nmcd
.dwDrawStage
= CDDS_PREPAINT
;
2768 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2769 if (cdrf
& CDRF_SKIPDEFAULT
) return;
2771 if (!(cdrf
& CDRF_DOERASE
))
2773 dtFlags
= BUTTON_CalcLayoutRects(infoPtr
, hDC
, &labelRect
, &imageRect
, &textRect
);
2774 if (dtFlags
!= (UINT
)-1L)
2775 BUTTON_DrawThemedLabel(infoPtr
, hDC
, dtFlags
, &imageRect
, &textRect
, theme
,
2776 BP_PUSHBUTTON
, state
);
2779 if (cdrf
& CDRF_NOTIFYPOSTPAINT
)
2781 nmcd
.dwDrawStage
= CDDS_POSTPAINT
;
2782 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2784 if (cdrf
& CDRF_SKIPPOSTPAINT
) return;
2786 if (focused
) DrawFocusRect(hDC
, &focusRect
);
2789 static void CB_ThemedPaint(HTHEME theme
, const BUTTON_INFO
*infoPtr
, HDC hDC
, int state
, UINT dtFlags
, BOOL focused
)
2791 RECT client_rect
, content_rect
, old_label_rect
, label_rect
, box_rect
, image_rect
, text_rect
;
2792 HFONT font
, hPrevFont
= NULL
;
2793 DWORD dwStyle
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
2794 LONG ex_style
= GetWindowLongW(infoPtr
->hwnd
, GWL_EXSTYLE
);
2795 UINT btn_type
= get_button_type( dwStyle
);
2796 int part
= (btn_type
== BS_RADIOBUTTON
) || (btn_type
== BS_AUTORADIOBUTTON
) ? BP_RADIOBUTTON
: BP_CHECKBOX
;
2802 BOOL created_font
= FALSE
;
2808 if (dwStyle
& BS_PUSHLIKE
)
2810 PB_ThemedPaint(theme
, infoPtr
, hDC
, state
, dtFlags
, focused
);
2814 hr
= GetThemeFont(theme
, hDC
, part
, state
, TMT_FONT
, &lf
);
2815 if (SUCCEEDED(hr
)) {
2816 font
= CreateFontIndirectW(&lf
);
2818 TRACE("Failed to create font\n");
2820 TRACE("font = %s\n", debugstr_w(lf
.lfFaceName
));
2821 hPrevFont
= SelectObject(hDC
, font
);
2822 created_font
= TRUE
;
2825 if (infoPtr
->font
) SelectObject(hDC
, infoPtr
->font
);
2828 GetClientRect(infoPtr
->hwnd
, &client_rect
);
2829 GetThemeBackgroundContentRect(theme
, hDC
, part
, state
, &client_rect
, &content_rect
);
2830 region
= set_control_clipping(hDC
, &client_rect
);
2832 if (FAILED(GetThemePartSize(theme
, hDC
, part
, state
, &content_rect
, TS_DRAW
, &box_size
)))
2834 box_size
.cx
= 12 * GetDpiForWindow(infoPtr
->hwnd
) / 96 + 1;
2835 box_size
.cy
= box_size
.cx
;
2838 GetCharWidthW(hDC
, '0', '0', &text_offset
);
2841 label_rect
= content_rect
;
2842 if (dwStyle
& BS_LEFTTEXT
|| ex_style
& WS_EX_RIGHT
)
2843 label_rect
.right
-= box_size
.cx
+ text_offset
;
2845 label_rect
.left
+= box_size
.cx
+ text_offset
;
2847 old_label_rect
= label_rect
;
2848 dtFlags
= BUTTON_CalcLayoutRects(infoPtr
, hDC
, &label_rect
, &image_rect
, &text_rect
);
2849 box_rect
= get_box_rect(dwStyle
, ex_style
, &content_rect
, &label_rect
, dtFlags
!= (UINT
)-1L,
2852 init_custom_draw(&nmcd
, infoPtr
, hDC
, &client_rect
);
2854 parent
= GetParent(infoPtr
->hwnd
);
2855 if (!parent
) parent
= infoPtr
->hwnd
;
2857 /* Send erase notifications */
2858 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2859 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
2861 DrawThemeParentBackground(infoPtr
->hwnd
, hDC
, NULL
);
2862 /* Tests show that the brush from WM_CTLCOLORSTATIC is used for filling background after a
2863 * DrawThemeParentBackground() call */
2864 brush
= (HBRUSH
)SendMessageW(parent
, WM_CTLCOLORSTATIC
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
2865 FillRect(hDC
, &client_rect
, brush
? brush
: GetSysColorBrush(COLOR_BTNFACE
));
2867 if (cdrf
& CDRF_NOTIFYPOSTERASE
)
2869 nmcd
.dwDrawStage
= CDDS_POSTERASE
;
2870 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2873 /* Send paint notifications */
2874 nmcd
.dwDrawStage
= CDDS_PREPAINT
;
2875 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2876 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
2879 if (!(cdrf
& CDRF_DOERASE
))
2881 DrawThemeBackground(theme
, hDC
, part
, state
, &box_rect
, NULL
);
2882 if (dtFlags
!= (UINT
)-1L)
2883 BUTTON_DrawThemedLabel(infoPtr
, hDC
, dtFlags
, &image_rect
, &text_rect
, theme
, part
, state
);
2886 if (cdrf
& CDRF_NOTIFYPOSTPAINT
)
2888 nmcd
.dwDrawStage
= CDDS_POSTPAINT
;
2889 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2891 if ((cdrf
& CDRF_SKIPPOSTPAINT
) || dtFlags
== (UINT
)-1L) goto cleanup
;
2897 IntersectRect(&label_rect
, &label_rect
, &old_label_rect
);
2898 DrawFocusRect(hDC
, &label_rect
);
2902 SelectClipRgn(hDC
, region
);
2903 if (region
) DeleteObject(region
);
2904 if (created_font
) DeleteObject(font
);
2905 if (hPrevFont
) SelectObject(hDC
, hPrevFont
);
2908 static void GB_ThemedPaint(HTHEME theme
, const BUTTON_INFO
*infoPtr
, HDC hDC
, int state
, UINT dtFlags
, BOOL focused
)
2910 RECT clientRect
, contentRect
, labelRect
, imageRect
, textRect
, bgRect
;
2911 HRGN region
, textRegion
= NULL
;
2913 HFONT font
, hPrevFont
= NULL
;
2914 BOOL created_font
= FALSE
;
2915 TEXTMETRICW textMetric
;
2922 /* DrawThemeParentBackground() is used for filling content background. The brush from
2923 * WM_CTLCOLORSTATIC is used for filling text background */
2924 parent
= GetParent(infoPtr
->hwnd
);
2926 parent
= infoPtr
->hwnd
;
2927 brush
= (HBRUSH
)SendMessageW(parent
, WM_CTLCOLORSTATIC
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
2929 hr
= GetThemeFont(theme
, hDC
, BP_GROUPBOX
, state
, TMT_FONT
, &lf
);
2930 if (SUCCEEDED(hr
)) {
2931 font
= CreateFontIndirectW(&lf
);
2933 TRACE("Failed to create font\n");
2935 hPrevFont
= SelectObject(hDC
, font
);
2936 created_font
= TRUE
;
2940 SelectObject(hDC
, infoPtr
->font
);
2943 GetClientRect(infoPtr
->hwnd
, &clientRect
);
2944 region
= set_control_clipping(hDC
, &clientRect
);
2946 bgRect
= clientRect
;
2947 GetTextMetricsW(hDC
, &textMetric
);
2948 bgRect
.top
+= (textMetric
.tmHeight
/ 2) - 1;
2950 labelRect
= clientRect
;
2951 InflateRect(&labelRect
, -7, 1);
2952 dtFlags
= BUTTON_CalcLayoutRects(infoPtr
, hDC
, &labelRect
, &imageRect
, &textRect
);
2953 if (dtFlags
!= (UINT
)-1 && !show_image_only(infoPtr
))
2955 textRegion
= CreateRectRgnIndirect(&textRect
);
2956 ExtSelectClipRgn(hDC
, textRegion
, RGN_DIFF
);
2959 style
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
2960 if (style
& BS_PUSHLIKE
)
2962 part
= BP_PUSHBUTTON
;
2967 GetThemeBackgroundContentRect(theme
, hDC
, part
, state
, &bgRect
, &contentRect
);
2968 ExcludeClipRect(hDC
, contentRect
.left
, contentRect
.top
, contentRect
.right
, contentRect
.bottom
);
2970 if (IsThemeBackgroundPartiallyTransparent(theme
, part
, state
))
2971 DrawThemeParentBackground(infoPtr
->hwnd
, hDC
, NULL
);
2972 DrawThemeBackground(theme
, hDC
, part
, state
, &bgRect
, NULL
);
2974 if (dtFlags
!= (UINT
)-1)
2978 SelectClipRgn(hDC
, textRegion
);
2979 DeleteObject(textRegion
);
2981 FillRect(hDC
, &textRect
, brush
? brush
: GetSysColorBrush(COLOR_BTNFACE
));
2982 BUTTON_DrawThemedLabel(infoPtr
, hDC
, dtFlags
, &imageRect
, &textRect
, theme
, part
, state
);
2985 SelectClipRgn(hDC
, region
);
2986 if (region
) DeleteObject(region
);
2987 if (created_font
) DeleteObject(font
);
2988 if (hPrevFont
) SelectObject(hDC
, hPrevFont
);
2991 static void SB_ThemedPaint(HTHEME theme
, const BUTTON_INFO
*infoPtr
, HDC hDC
, int state
, UINT dtFlags
, BOOL focused
)
2993 RECT rc
, content_rect
, push_rect
, dropdown_rect
, focus_rect
, label_rect
, image_rect
, text_rect
;
2998 if (infoPtr
->font
) SelectObject(hDC
, infoPtr
->font
);
3000 GetClientRect(infoPtr
->hwnd
, &rc
);
3001 init_custom_draw(&nmcd
, infoPtr
, hDC
, &rc
);
3003 parent
= GetParent(infoPtr
->hwnd
);
3004 if (!parent
) parent
= infoPtr
->hwnd
;
3006 /* Send erase notifications */
3007 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
3008 if (cdrf
& CDRF_SKIPDEFAULT
) return;
3010 if (IsThemeBackgroundPartiallyTransparent(theme
, BP_PUSHBUTTON
, state
))
3011 DrawThemeParentBackground(infoPtr
->hwnd
, hDC
, NULL
);
3013 /* The zone outside the content is ignored for the dropdown (draws over) */
3014 GetThemeBackgroundContentRect(theme
, hDC
, BP_PUSHBUTTON
, state
, &rc
, &content_rect
);
3015 get_split_button_rects(infoPtr
, &rc
, &push_rect
, &dropdown_rect
);
3017 if (infoPtr
->split_style
& BCSS_NOSPLIT
)
3020 DrawThemeBackground(theme
, hDC
, BP_PUSHBUTTON
, state
, &rc
, NULL
);
3021 GetThemeBackgroundContentRect(theme
, hDC
, BP_PUSHBUTTON
, state
, &push_rect
, &focus_rect
);
3025 RECT r
= { dropdown_rect
.left
, content_rect
.top
, dropdown_rect
.right
, content_rect
.bottom
};
3026 UINT edge
= (infoPtr
->split_style
& BCSS_ALIGNLEFT
) ? BF_RIGHT
: BF_LEFT
;
3027 const RECT
*clip
= NULL
;
3029 /* If only the dropdown is pressed, we need to draw it separately */
3030 if (state
!= PBS_PRESSED
&& (infoPtr
->state
& BST_DROPDOWNPUSHED
))
3032 DrawThemeBackground(theme
, hDC
, BP_PUSHBUTTON
, PBS_PRESSED
, &rc
, &dropdown_rect
);
3035 DrawThemeBackground(theme
, hDC
, BP_PUSHBUTTON
, state
, &rc
, clip
);
3037 /* Draw the separator */
3038 DrawThemeEdge(theme
, hDC
, BP_PUSHBUTTON
, state
, &r
, EDGE_ETCHED
, edge
, NULL
);
3040 /* The content rect should be the content area of the push button */
3041 GetThemeBackgroundContentRect(theme
, hDC
, BP_PUSHBUTTON
, state
, &push_rect
, &content_rect
);
3042 focus_rect
= content_rect
;
3045 if (cdrf
& CDRF_NOTIFYPOSTERASE
)
3047 nmcd
.dwDrawStage
= CDDS_POSTERASE
;
3048 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
3051 /* Send paint notifications */
3052 nmcd
.dwDrawStage
= CDDS_PREPAINT
;
3053 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
3054 if (cdrf
& CDRF_SKIPDEFAULT
) return;
3056 if (!(cdrf
& CDRF_DOERASE
))
3058 COLORREF old_color
, color
;
3061 label_rect
= content_rect
;
3062 dtFlags
= BUTTON_CalcLayoutRects(infoPtr
, hDC
, &label_rect
, &image_rect
, &text_rect
);
3063 if (dtFlags
!= (UINT
)-1L)
3064 BUTTON_DrawThemedLabel(infoPtr
, hDC
, dtFlags
, &image_rect
, &text_rect
, theme
,
3065 BP_PUSHBUTTON
, state
);
3067 GetThemeColor(theme
, BP_PUSHBUTTON
, state
, TMT_TEXTCOLOR
, &color
);
3068 old_bk_mode
= SetBkMode(hDC
, TRANSPARENT
);
3069 old_color
= SetTextColor(hDC
, color
);
3071 draw_split_button_dropdown_glyph(infoPtr
, hDC
, &dropdown_rect
);
3073 SetTextColor(hDC
, old_color
);
3074 SetBkMode(hDC
, old_bk_mode
);
3077 if (cdrf
& CDRF_NOTIFYPOSTPAINT
)
3079 nmcd
.dwDrawStage
= CDDS_POSTPAINT
;
3080 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
3082 if (cdrf
& CDRF_SKIPPOSTPAINT
) return;
3084 if (focused
) DrawFocusRect(hDC
, &focus_rect
);
3087 static void CL_ThemedPaint(HTHEME theme
, const BUTTON_INFO
*infoPtr
, HDC hDC
, int state
, UINT dtFlags
, BOOL focused
)
3095 if (infoPtr
->font
) SelectObject(hDC
, infoPtr
->font
);
3097 GetClientRect(infoPtr
->hwnd
, &rc
);
3098 init_custom_draw(&nmcd
, infoPtr
, hDC
, &rc
);
3100 parent
= GetParent(infoPtr
->hwnd
);
3101 if (!parent
) parent
= infoPtr
->hwnd
;
3103 /* Send erase notifications */
3104 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
3105 if (cdrf
& CDRF_SKIPDEFAULT
) return;
3107 part
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
) & BS_PUSHLIKE
? BP_PUSHBUTTON
: BP_COMMANDLINK
;
3108 if (IsThemeBackgroundPartiallyTransparent(theme
, part
, state
))
3109 DrawThemeParentBackground(infoPtr
->hwnd
, hDC
, NULL
);
3110 DrawThemeBackground(theme
, hDC
, part
, state
, &rc
, NULL
);
3112 if (cdrf
& CDRF_NOTIFYPOSTERASE
)
3114 nmcd
.dwDrawStage
= CDDS_POSTERASE
;
3115 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
3118 /* Send paint notifications */
3119 nmcd
.dwDrawStage
= CDDS_PREPAINT
;
3120 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
3121 if (cdrf
& CDRF_SKIPDEFAULT
) return;
3123 if (!(cdrf
& CDRF_DOERASE
))
3130 GetThemeBackgroundContentRect(theme
, hDC
, part
, state
, &rc
, &r
);
3132 /* The text alignment and styles are fixed and don't depend on button styles */
3133 dtFlags
= DT_TOP
| DT_LEFT
| DT_WORDBREAK
;
3135 /* Command Links ignore the margins of the image list or its alignment */
3136 if (infoPtr
->u
.image
|| infoPtr
->imagelist
.himl
)
3137 img_size
= BUTTON_GetImageSize(infoPtr
);
3139 GetThemePartSize(theme
, NULL
, BP_COMMANDLINKGLYPH
, state
, NULL
, TS_DRAW
, &img_size
);
3142 if (img_size
.cx
) r
.left
+= img_size
.cx
+ command_link_margin
;
3145 if ((text
= get_button_text(infoPtr
)))
3147 UINT len
= lstrlenW(text
);
3150 GetThemeTextExtent(theme
, hDC
, part
, state
, text
, len
, dtFlags
| DT_END_ELLIPSIS
, &r
,
3152 DrawThemeText(theme
, hDC
, part
, state
, text
, len
, dtFlags
| DT_END_ELLIPSIS
, 0, &r
);
3154 txt_h
= text_rect
.bottom
- text_rect
.top
;
3164 opts
.dwSize
= sizeof(opts
);
3165 opts
.dwFlags
= DTT_FONTPROP
;
3166 opts
.iFontPropId
= TMT_BODYFONT
;
3167 DrawThemeTextEx(theme
, hDC
, part
, state
, infoPtr
->note
, infoPtr
->note_length
,
3168 dtFlags
| DT_NOPREFIX
, &r
, &opts
);
3171 /* Position the image at the vertical center of the drawn text (not note) */
3172 txt_h
= min(txt_h
, img_rect
.bottom
- img_rect
.top
);
3173 if (img_size
.cy
< txt_h
) img_rect
.top
+= (txt_h
- img_size
.cy
) / 2;
3175 img_rect
.right
= img_rect
.left
+ img_size
.cx
;
3176 img_rect
.bottom
= img_rect
.top
+ img_size
.cy
;
3178 if (infoPtr
->u
.image
|| infoPtr
->imagelist
.himl
)
3179 BUTTON_DrawImage(infoPtr
, hDC
, NULL
,
3180 (state
== CMDLS_DISABLED
) ? DSS_DISABLED
: DSS_NORMAL
,
3183 DrawThemeBackground(theme
, hDC
, BP_COMMANDLINKGLYPH
, state
, &img_rect
, NULL
);
3186 if (cdrf
& CDRF_NOTIFYPOSTPAINT
)
3188 nmcd
.dwDrawStage
= CDDS_POSTPAINT
;
3189 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
3191 if (cdrf
& CDRF_SKIPPOSTPAINT
) return;
3197 /* The focus rect has margins of a push button rather than command link... */
3198 GetThemeMargins(theme
, hDC
, BP_PUSHBUTTON
, state
, TMT_CONTENTMARGINS
, NULL
, &margins
);
3200 rc
.left
+= margins
.cxLeftWidth
;
3201 rc
.top
+= margins
.cyTopHeight
;
3202 rc
.right
-= margins
.cxRightWidth
;
3203 rc
.bottom
-= margins
.cyBottomHeight
;
3204 DrawFocusRect(hDC
, &rc
);
3208 void BUTTON_Register(void)
3212 memset(&wndClass
, 0, sizeof(wndClass
));
3213 wndClass
.style
= CS_GLOBALCLASS
| CS_DBLCLKS
| CS_VREDRAW
| CS_HREDRAW
| CS_PARENTDC
;
3214 wndClass
.lpfnWndProc
= BUTTON_WindowProc
;
3215 wndClass
.cbClsExtra
= 0;
3216 wndClass
.cbWndExtra
= sizeof(BUTTON_INFO
*);
3217 wndClass
.hCursor
= LoadCursorW(0, (LPWSTR
)IDC_ARROW
);
3218 wndClass
.hbrBackground
= NULL
;
3219 wndClass
.lpszClassName
= WC_BUTTONW
;
3220 RegisterClassW(&wndClass
);