2 * Copyright (C) 1993 Johannes Ruscheinski
3 * Copyright (C) 1993 David Metcalfe
4 * Copyright (C) 1994 Alexandre Julliard
5 * Copyright (C) 2008 by Reece H. Dunn
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
23 * - BS_NOTIFY: is it complete?
24 * - BS_RIGHTBUTTON: same as BS_LEFTTEXT
27 * - WM_CHAR: Checks a (manual or automatic) check box on '+' or '=', clears it on '-' key.
28 * - WM_SETFOCUS: For (manual or automatic) radio buttons, send the parent window BN_CLICKED
29 * - WM_NCCREATE: Turns any BS_OWNERDRAW button into a BS_PUSHBUTTON button.
35 * - BN_PUSHED/BN_HILITE
36 * + BN_KILLFOCUS: is it OK?
38 * + BN_SETFOCUS: is it OK?
39 * - BN_UNPUSHED/BN_UNHILITE
41 * Structures/Macros/Definitions
57 #include "wine/debug.h"
58 #include "wine/heap.h"
62 WINE_DEFAULT_DEBUG_CHANNEL(button
);
64 /* undocumented flags */
65 #define BUTTON_NSTATES 0x0F
66 #define BUTTON_BTNPRESSED 0x40
67 #define BUTTON_UNKNOWN2 0x20
68 #define BUTTON_UNKNOWN3 0x10
70 #define BUTTON_NOTIFY_PARENT(hWnd, code) \
71 do { /* Notify parent which has created this button control */ \
72 TRACE("notification " #code " sent to hwnd=%p\n", GetParent(hWnd)); \
73 SendMessageW(GetParent(hWnd), WM_COMMAND, \
74 MAKEWPARAM(GetWindowLongPtrW((hWnd),GWLP_ID), (code)), \
78 typedef struct _BUTTON_INFO
87 DWORD image_type
; /* IMAGE_BITMAP or IMAGE_ICON */
88 BUTTON_IMAGELIST imagelist
;
90 HIMAGELIST glyph
; /* this is a font character code when split_style doesn't have BCSS_IMAGE */
93 HANDLE image
; /* Original handle set with BM_SETIMAGE and returned with BM_GETIMAGE. */
98 HANDLE image
; /* Duplicated handle used for drawing. */
102 static UINT
BUTTON_CalcLayoutRects( const BUTTON_INFO
*infoPtr
, HDC hdc
, RECT
*labelRc
, RECT
*imageRc
, RECT
*textRc
);
103 static void PB_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
);
104 static void CB_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
);
105 static void GB_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
);
106 static void UB_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
);
107 static void OB_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
);
108 static void SB_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
);
109 static void CL_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
);
110 static void BUTTON_CheckAutoRadioButton( HWND hwnd
);
111 static void get_split_button_rects(const BUTTON_INFO
*, const RECT
*, RECT
*, RECT
*);
112 static BOOL
notify_split_button_dropdown(const BUTTON_INFO
*, const POINT
*, HWND
);
113 static void draw_split_button_dropdown_glyph(const BUTTON_INFO
*, HDC
, RECT
*);
115 #define MAX_BTN_TYPE 16
117 static const WORD maxCheckState
[MAX_BTN_TYPE
] =
119 BST_UNCHECKED
, /* BS_PUSHBUTTON */
120 BST_UNCHECKED
, /* BS_DEFPUSHBUTTON */
121 BST_CHECKED
, /* BS_CHECKBOX */
122 BST_CHECKED
, /* BS_AUTOCHECKBOX */
123 BST_CHECKED
, /* BS_RADIOBUTTON */
124 BST_INDETERMINATE
, /* BS_3STATE */
125 BST_INDETERMINATE
, /* BS_AUTO3STATE */
126 BST_UNCHECKED
, /* BS_GROUPBOX */
127 BST_UNCHECKED
, /* BS_USERBUTTON */
128 BST_CHECKED
, /* BS_AUTORADIOBUTTON */
129 BST_UNCHECKED
, /* BS_PUSHBOX */
130 BST_UNCHECKED
, /* BS_OWNERDRAW */
131 BST_UNCHECKED
, /* BS_SPLITBUTTON */
132 BST_UNCHECKED
, /* BS_DEFSPLITBUTTON */
133 BST_UNCHECKED
, /* BS_COMMANDLINK */
134 BST_UNCHECKED
/* BS_DEFCOMMANDLINK */
137 /* Generic draw states, use get_draw_state() to get specific state for button type */
148 typedef void (*pfPaint
)( const BUTTON_INFO
*infoPtr
, HDC hdc
, UINT action
);
150 static const pfPaint btnPaintFunc
[MAX_BTN_TYPE
] =
152 PB_Paint
, /* BS_PUSHBUTTON */
153 PB_Paint
, /* BS_DEFPUSHBUTTON */
154 CB_Paint
, /* BS_CHECKBOX */
155 CB_Paint
, /* BS_AUTOCHECKBOX */
156 CB_Paint
, /* BS_RADIOBUTTON */
157 CB_Paint
, /* BS_3STATE */
158 CB_Paint
, /* BS_AUTO3STATE */
159 GB_Paint
, /* BS_GROUPBOX */
160 UB_Paint
, /* BS_USERBUTTON */
161 CB_Paint
, /* BS_AUTORADIOBUTTON */
162 NULL
, /* BS_PUSHBOX */
163 OB_Paint
, /* BS_OWNERDRAW */
164 SB_Paint
, /* BS_SPLITBUTTON */
165 SB_Paint
, /* BS_DEFSPLITBUTTON */
166 CL_Paint
, /* BS_COMMANDLINK */
167 CL_Paint
/* BS_DEFCOMMANDLINK */
170 typedef void (*pfThemedPaint
)( HTHEME theme
, const BUTTON_INFO
*infoPtr
, HDC hdc
, int drawState
, UINT dtflags
, BOOL focused
);
172 static void PB_ThemedPaint( HTHEME theme
, const BUTTON_INFO
*infoPtr
, HDC hdc
, int drawState
, UINT dtflags
, BOOL focused
);
173 static void CB_ThemedPaint( HTHEME theme
, const BUTTON_INFO
*infoPtr
, HDC hdc
, int drawState
, UINT dtflags
, BOOL focused
);
174 static void GB_ThemedPaint( HTHEME theme
, const BUTTON_INFO
*infoPtr
, HDC hdc
, int drawState
, UINT dtflags
, BOOL focused
);
175 static void SB_ThemedPaint( HTHEME theme
, const BUTTON_INFO
*infoPtr
, HDC hdc
, int drawState
, UINT dtflags
, BOOL focused
);
176 static void CL_ThemedPaint( HTHEME theme
, const BUTTON_INFO
*infoPtr
, HDC hdc
, int drawState
, UINT dtflags
, BOOL focused
);
178 static const pfThemedPaint btnThemedPaintFunc
[MAX_BTN_TYPE
] =
180 PB_ThemedPaint
, /* BS_PUSHBUTTON */
181 PB_ThemedPaint
, /* BS_DEFPUSHBUTTON */
182 CB_ThemedPaint
, /* BS_CHECKBOX */
183 CB_ThemedPaint
, /* BS_AUTOCHECKBOX */
184 CB_ThemedPaint
, /* BS_RADIOBUTTON */
185 CB_ThemedPaint
, /* BS_3STATE */
186 CB_ThemedPaint
, /* BS_AUTO3STATE */
187 GB_ThemedPaint
, /* BS_GROUPBOX */
188 NULL
, /* BS_USERBUTTON */
189 CB_ThemedPaint
, /* BS_AUTORADIOBUTTON */
190 NULL
, /* BS_PUSHBOX */
191 NULL
, /* BS_OWNERDRAW */
192 SB_ThemedPaint
, /* BS_SPLITBUTTON */
193 SB_ThemedPaint
, /* BS_DEFSPLITBUTTON */
194 CL_ThemedPaint
, /* BS_COMMANDLINK */
195 CL_ThemedPaint
/* BS_DEFCOMMANDLINK */
198 typedef BOOL (*pfGetIdealSize
)(BUTTON_INFO
*infoPtr
, SIZE
*size
);
200 static BOOL
PB_GetIdealSize(BUTTON_INFO
*infoPtr
, SIZE
*size
);
201 static BOOL
CB_GetIdealSize(BUTTON_INFO
*infoPtr
, SIZE
*size
);
202 static BOOL
GB_GetIdealSize(BUTTON_INFO
*infoPtr
, SIZE
*size
);
203 static BOOL
SB_GetIdealSize(BUTTON_INFO
*infoPtr
, SIZE
*size
);
204 static BOOL
CL_GetIdealSize(BUTTON_INFO
*infoPtr
, SIZE
*size
);
206 static const pfGetIdealSize btnGetIdealSizeFunc
[MAX_BTN_TYPE
] = {
207 PB_GetIdealSize
, /* BS_PUSHBUTTON */
208 PB_GetIdealSize
, /* BS_DEFPUSHBUTTON */
209 CB_GetIdealSize
, /* BS_CHECKBOX */
210 CB_GetIdealSize
, /* BS_AUTOCHECKBOX */
211 CB_GetIdealSize
, /* BS_RADIOBUTTON */
212 GB_GetIdealSize
, /* BS_3STATE */
213 GB_GetIdealSize
, /* BS_AUTO3STATE */
214 GB_GetIdealSize
, /* BS_GROUPBOX */
215 PB_GetIdealSize
, /* BS_USERBUTTON */
216 CB_GetIdealSize
, /* BS_AUTORADIOBUTTON */
217 GB_GetIdealSize
, /* BS_PUSHBOX */
218 GB_GetIdealSize
, /* BS_OWNERDRAW */
219 SB_GetIdealSize
, /* BS_SPLITBUTTON */
220 SB_GetIdealSize
, /* BS_DEFSPLITBUTTON */
221 CL_GetIdealSize
, /* BS_COMMANDLINK */
222 CL_GetIdealSize
/* BS_DEFCOMMANDLINK */
225 /* Fixed margin for command links, regardless of DPI (based on tests done on Windows) */
226 enum { command_link_margin
= 6 };
228 /* The width and height for the default command link glyph (when there's no image) */
229 enum { command_link_defglyph_size
= 17 };
231 static inline UINT
get_button_type( LONG window_style
)
233 return (window_style
& BS_TYPEMASK
);
236 static inline BOOL
button_centers_text( LONG window_style
)
238 /* Push button's text is centered by default, same for split buttons */
239 UINT type
= get_button_type(window_style
);
240 return type
<= BS_DEFPUSHBUTTON
|| type
== BS_SPLITBUTTON
|| type
== BS_DEFSPLITBUTTON
;
243 /* paint a button of any type */
244 static inline void paint_button( BUTTON_INFO
*infoPtr
, LONG style
, UINT action
)
246 if (btnPaintFunc
[style
] && IsWindowVisible(infoPtr
->hwnd
))
248 HDC hdc
= GetDC( infoPtr
->hwnd
);
249 btnPaintFunc
[style
]( infoPtr
, hdc
, action
);
250 ReleaseDC( infoPtr
->hwnd
, hdc
);
254 /* retrieve the button text; returned buffer must be freed by caller */
255 static inline WCHAR
*get_button_text( const BUTTON_INFO
*infoPtr
)
257 INT len
= GetWindowTextLengthW( infoPtr
->hwnd
);
258 WCHAR
*buffer
= heap_alloc( (len
+ 1) * sizeof(WCHAR
) );
260 GetWindowTextW( infoPtr
->hwnd
, buffer
, len
+ 1 );
264 /* get the default glyph size for split buttons */
265 static LONG
get_default_glyph_size(const BUTTON_INFO
*infoPtr
)
267 if (infoPtr
->split_style
& BCSS_IMAGE
)
269 /* Size it to fit, including the left and right edges */
271 if (!ImageList_GetIconSize(infoPtr
->glyph
, &w
, &h
)) w
= 0;
272 return w
+ GetSystemMetrics(SM_CXEDGE
) * 2;
275 /* The glyph size relies on the default menu font's cell height */
276 return GetSystemMetrics(SM_CYMENUCHECK
);
279 static BOOL
is_themed_paint_supported(HTHEME theme
, UINT btn_type
)
281 if (!theme
|| !btnThemedPaintFunc
[btn_type
])
284 if (btn_type
== BS_COMMANDLINK
|| btn_type
== BS_DEFCOMMANDLINK
)
286 if (!IsThemePartDefined(theme
, BP_COMMANDLINK
, 0))
293 static void init_custom_draw(NMCUSTOMDRAW
*nmcd
, const BUTTON_INFO
*infoPtr
, HDC hdc
, const RECT
*rc
)
295 nmcd
->hdr
.hwndFrom
= infoPtr
->hwnd
;
296 nmcd
->hdr
.idFrom
= GetWindowLongPtrW(infoPtr
->hwnd
, GWLP_ID
);
297 nmcd
->hdr
.code
= NM_CUSTOMDRAW
;
300 nmcd
->dwDrawStage
= CDDS_PREERASE
;
301 nmcd
->dwItemSpec
= 0;
302 nmcd
->lItemlParam
= 0;
303 nmcd
->uItemState
= IsWindowEnabled(infoPtr
->hwnd
) ? 0 : CDIS_DISABLED
;
304 if (infoPtr
->state
& BST_PUSHED
) nmcd
->uItemState
|= CDIS_SELECTED
;
305 if (infoPtr
->state
& BST_FOCUS
) nmcd
->uItemState
|= CDIS_FOCUS
;
306 if (infoPtr
->state
& BST_HOT
) nmcd
->uItemState
|= CDIS_HOT
;
307 if (infoPtr
->state
& BST_INDETERMINATE
)
308 nmcd
->uItemState
|= CDIS_INDETERMINATE
;
310 /* Windows doesn't seem to send CDIS_CHECKED (it fails the tests) */
311 /* CDIS_SHOWKEYBOARDCUES is misleading, as the meaning is reversed */
312 /* FIXME: Handle it properly when we support keyboard cues? */
315 HRGN
set_control_clipping( HDC hdc
, const RECT
*rect
)
318 HRGN hrgn
= CreateRectRgn( 0, 0, 0, 0 );
320 if (GetClipRgn( hdc
, hrgn
) != 1)
322 DeleteObject( hrgn
);
325 DPtoLP( hdc
, (POINT
*)&rc
, 2 );
326 if (GetLayout( hdc
) & LAYOUT_RTL
) /* compensate for the shifting done by IntersectClipRect */
331 IntersectClipRect( hdc
, rc
.left
, rc
.top
, rc
.right
, rc
.bottom
);
335 static WCHAR
*heap_strndupW(const WCHAR
*src
, size_t length
)
337 size_t size
= (length
+ 1) * sizeof(WCHAR
);
338 WCHAR
*dst
= heap_alloc(size
);
339 if (dst
) memcpy(dst
, src
, size
);
343 /**********************************************************************
344 * Convert button styles to flags used by DrawText.
346 static UINT
BUTTON_BStoDT( DWORD style
, DWORD ex_style
)
348 UINT dtStyle
= DT_NOCLIP
; /* We use SelectClipRgn to limit output */
350 /* "Convert" pushlike buttons to pushbuttons */
351 if (style
& BS_PUSHLIKE
)
352 style
&= ~BS_TYPEMASK
;
354 if (!(style
& BS_MULTILINE
))
355 dtStyle
|= DT_SINGLELINE
;
357 dtStyle
|= DT_WORDBREAK
;
359 switch (style
& BS_CENTER
)
361 case BS_LEFT
: /* DT_LEFT is 0 */ break;
362 case BS_RIGHT
: dtStyle
|= DT_RIGHT
; break;
363 case BS_CENTER
: dtStyle
|= DT_CENTER
; break;
365 if (button_centers_text(style
)) dtStyle
|= DT_CENTER
;
368 if (ex_style
& WS_EX_RIGHT
) dtStyle
= DT_RIGHT
| (dtStyle
& ~(DT_LEFT
| DT_CENTER
));
370 /* DrawText ignores vertical alignment for multiline text,
371 * but we use these flags to align label manually.
373 if (get_button_type(style
) != BS_GROUPBOX
)
375 switch (style
& BS_VCENTER
)
377 case BS_TOP
: /* DT_TOP is 0 */ break;
378 case BS_BOTTOM
: dtStyle
|= DT_BOTTOM
; break;
379 case BS_VCENTER
: /* fall through */
380 default: dtStyle
|= DT_VCENTER
; break;
387 static int get_draw_state(const BUTTON_INFO
*infoPtr
)
389 static const int pb_states
[DRAW_STATE_COUNT
] = { PBS_NORMAL
, PBS_DISABLED
, PBS_HOT
, PBS_PRESSED
, PBS_DEFAULTED
};
390 static const int cb_states
[3][DRAW_STATE_COUNT
] =
392 { CBS_UNCHECKEDNORMAL
, CBS_UNCHECKEDDISABLED
, CBS_UNCHECKEDHOT
, CBS_UNCHECKEDPRESSED
, CBS_UNCHECKEDNORMAL
},
393 { CBS_CHECKEDNORMAL
, CBS_CHECKEDDISABLED
, CBS_CHECKEDHOT
, CBS_CHECKEDPRESSED
, CBS_CHECKEDNORMAL
},
394 { CBS_MIXEDNORMAL
, CBS_MIXEDDISABLED
, CBS_MIXEDHOT
, CBS_MIXEDPRESSED
, CBS_MIXEDNORMAL
}
396 static const int pushlike_cb_states
[3][DRAW_STATE_COUNT
] =
398 { PBS_NORMAL
, PBS_DISABLED
, PBS_HOT
, PBS_PRESSED
, PBS_NORMAL
},
399 { PBS_PRESSED
, PBS_PRESSED
, PBS_HOT
, PBS_PRESSED
, PBS_PRESSED
},
400 { PBS_NORMAL
, PBS_DISABLED
, PBS_HOT
, PBS_PRESSED
, PBS_NORMAL
}
402 static const int rb_states
[2][DRAW_STATE_COUNT
] =
404 { RBS_UNCHECKEDNORMAL
, RBS_UNCHECKEDDISABLED
, RBS_UNCHECKEDHOT
, RBS_UNCHECKEDPRESSED
, RBS_UNCHECKEDNORMAL
},
405 { RBS_CHECKEDNORMAL
, RBS_CHECKEDDISABLED
, RBS_CHECKEDHOT
, RBS_CHECKEDPRESSED
, RBS_CHECKEDNORMAL
}
407 static const int pushlike_rb_states
[2][DRAW_STATE_COUNT
] =
409 { PBS_NORMAL
, PBS_DISABLED
, PBS_HOT
, PBS_PRESSED
, PBS_NORMAL
},
410 { PBS_PRESSED
, PBS_PRESSED
, PBS_HOT
, PBS_PRESSED
, PBS_PRESSED
}
412 static const int gb_states
[DRAW_STATE_COUNT
] = { GBS_NORMAL
, GBS_DISABLED
, GBS_NORMAL
, GBS_NORMAL
, GBS_NORMAL
};
413 LONG style
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
414 UINT type
= get_button_type(style
);
415 int check_state
= infoPtr
->state
& 3;
416 enum draw_state state
;
418 if (!IsWindowEnabled(infoPtr
->hwnd
))
419 state
= STATE_DISABLED
;
420 else if (infoPtr
->state
& BST_PUSHED
)
421 state
= STATE_PRESSED
;
422 else if (infoPtr
->state
& BST_HOT
)
424 else if (infoPtr
->state
& BST_FOCUS
|| type
== BS_DEFPUSHBUTTON
|| type
== BS_DEFSPLITBUTTON
425 || (type
== BS_DEFCOMMANDLINK
&& !(style
& BS_PUSHLIKE
)))
426 state
= STATE_DEFAULTED
;
428 state
= STATE_NORMAL
;
433 case BS_DEFPUSHBUTTON
:
436 case BS_DEFSPLITBUTTON
:
438 case BS_DEFCOMMANDLINK
:
439 return pb_states
[state
];
441 case BS_AUTOCHECKBOX
:
444 return style
& BS_PUSHLIKE
? pushlike_cb_states
[check_state
][state
]
445 : cb_states
[check_state
][state
];
447 case BS_AUTORADIOBUTTON
:
448 return style
& BS_PUSHLIKE
? pushlike_rb_states
[check_state
][state
]
449 : rb_states
[check_state
][state
];
451 return style
& BS_PUSHLIKE
? pb_states
[state
] : gb_states
[state
];
453 WARN("Unsupported button type 0x%08x\n", type
);
458 static LRESULT CALLBACK
BUTTON_WindowProc(HWND hWnd
, UINT uMsg
, WPARAM wParam
, LPARAM lParam
)
460 BUTTON_INFO
*infoPtr
= (BUTTON_INFO
*)GetWindowLongPtrW(hWnd
, 0);
463 LONG style
= GetWindowLongW( hWnd
, GWL_STYLE
);
464 UINT btn_type
= get_button_type( style
);
465 LONG state
, new_state
;
469 if (!IsWindow( hWnd
)) return 0;
471 if (!infoPtr
&& (uMsg
!= WM_NCCREATE
))
472 return DefWindowProcW(hWnd
, uMsg
, wParam
, lParam
);
474 pt
.x
= (short)LOWORD(lParam
);
475 pt
.y
= (short)HIWORD(lParam
);
484 case BS_PUSHBUTTON
: return DLGC_BUTTON
| DLGC_UNDEFPUSHBUTTON
;
485 case BS_DEFCOMMANDLINK
:
486 case BS_DEFPUSHBUTTON
: return DLGC_BUTTON
| DLGC_DEFPUSHBUTTON
;
488 case BS_AUTORADIOBUTTON
: return DLGC_BUTTON
| DLGC_RADIOBUTTON
;
489 case BS_GROUPBOX
: return DLGC_STATIC
;
490 case BS_SPLITBUTTON
: return DLGC_BUTTON
| DLGC_UNDEFPUSHBUTTON
| DLGC_WANTARROWS
;
491 case BS_DEFSPLITBUTTON
: return DLGC_BUTTON
| DLGC_DEFPUSHBUTTON
| DLGC_WANTARROWS
;
492 default: return DLGC_BUTTON
;
496 theme
= GetWindowTheme( hWnd
);
498 RedrawWindow( hWnd
, NULL
, NULL
, RDW_FRAME
| RDW_INVALIDATE
| RDW_UPDATENOW
);
500 paint_button( infoPtr
, btn_type
, ODA_DRAWENTIRE
);
505 CREATESTRUCTW
*cs
= (CREATESTRUCTW
*)lParam
;
507 infoPtr
= heap_alloc_zero( sizeof(*infoPtr
) );
508 SetWindowLongPtrW( hWnd
, 0, (LONG_PTR
)infoPtr
);
509 infoPtr
->hwnd
= hWnd
;
510 infoPtr
->parent
= cs
->hwndParent
;
511 infoPtr
->style
= cs
->style
;
512 infoPtr
->split_style
= BCSS_STRETCH
;
513 infoPtr
->glyph
= (HIMAGELIST
)0x36; /* Marlett down arrow char code */
514 infoPtr
->glyph_size
.cx
= get_default_glyph_size(infoPtr
);
515 return DefWindowProcW(hWnd
, uMsg
, wParam
, lParam
);
519 SetWindowLongPtrW( hWnd
, 0, 0 );
520 if (infoPtr
->image_type
== IMAGE_BITMAP
)
521 DeleteObject(infoPtr
->u
.bitmap
);
522 else if (infoPtr
->image_type
== IMAGE_ICON
)
523 DestroyIcon(infoPtr
->u
.icon
);
524 heap_free(infoPtr
->note
);
532 if (btn_type
>= MAX_BTN_TYPE
)
533 return -1; /* abort */
535 /* XP turns a BS_USERBUTTON into BS_PUSHBUTTON */
536 if (btn_type
== BS_USERBUTTON
)
538 style
= (style
& ~BS_TYPEMASK
) | BS_PUSHBUTTON
;
539 SetWindowLongW( hWnd
, GWL_STYLE
, style
);
541 infoPtr
->state
= BST_UNCHECKED
;
542 OpenThemeData( hWnd
, WC_BUTTONW
);
544 parent
= GetParent( hWnd
);
546 EnableThemeDialogTexture( parent
, ETDT_ENABLE
);
551 theme
= GetWindowTheme( hWnd
);
552 CloseThemeData( theme
);
555 case WM_THEMECHANGED
:
556 theme
= GetWindowTheme( hWnd
);
557 CloseThemeData( theme
);
558 OpenThemeData( hWnd
, WC_BUTTONW
);
559 InvalidateRect( hWnd
, NULL
, TRUE
);
563 if (btn_type
== BS_OWNERDRAW
)
565 HDC hdc
= (HDC
)wParam
;
568 HWND parent
= GetParent(hWnd
);
569 if (!parent
) parent
= hWnd
;
570 hBrush
= (HBRUSH
)SendMessageW(parent
, WM_CTLCOLORBTN
, (WPARAM
)hdc
, (LPARAM
)hWnd
);
571 if (!hBrush
) /* did the app forget to call defwindowproc ? */
572 hBrush
= (HBRUSH
)DefWindowProcW(parent
, WM_CTLCOLORBTN
,
573 (WPARAM
)hdc
, (LPARAM
)hWnd
);
574 GetClientRect(hWnd
, &rc
);
575 FillRect(hdc
, &rc
, hBrush
);
585 theme
= GetWindowTheme( hWnd
);
586 hdc
= wParam
? (HDC
)wParam
: BeginPaint( hWnd
, &ps
);
588 if (is_themed_paint_supported(theme
, btn_type
))
590 int drawState
= get_draw_state(infoPtr
);
591 UINT dtflags
= BUTTON_BStoDT(style
, GetWindowLongW(hWnd
, GWL_EXSTYLE
));
593 btnThemedPaintFunc
[btn_type
](theme
, infoPtr
, hdc
, drawState
, dtflags
, infoPtr
->state
& BST_FOCUS
);
595 else if (btnPaintFunc
[btn_type
])
597 int nOldMode
= SetBkMode( hdc
, OPAQUE
);
598 btnPaintFunc
[btn_type
]( infoPtr
, hdc
, ODA_DRAWENTIRE
);
599 SetBkMode(hdc
, nOldMode
); /* reset painting mode */
602 if ( !wParam
) EndPaint( hWnd
, &ps
);
607 if (wParam
== VK_SPACE
)
609 SendMessageW( hWnd
, BM_SETSTATE
, TRUE
, 0 );
610 infoPtr
->state
|= BUTTON_BTNPRESSED
;
613 else if (wParam
== VK_UP
|| wParam
== VK_DOWN
)
615 /* Up and down arrows work on every button, and even with BCSS_NOSPLIT */
616 notify_split_button_dropdown(infoPtr
, NULL
, hWnd
);
620 case WM_LBUTTONDBLCLK
:
621 if(style
& BS_NOTIFY
||
622 btn_type
== BS_RADIOBUTTON
||
623 btn_type
== BS_USERBUTTON
||
624 btn_type
== BS_OWNERDRAW
)
626 BUTTON_NOTIFY_PARENT(hWnd
, BN_DOUBLECLICKED
);
633 if ((btn_type
== BS_SPLITBUTTON
|| btn_type
== BS_DEFSPLITBUTTON
) &&
634 !(infoPtr
->split_style
& BCSS_NOSPLIT
) &&
635 notify_split_button_dropdown(infoPtr
, &pt
, hWnd
))
639 infoPtr
->state
|= BUTTON_BTNPRESSED
;
640 SendMessageW( hWnd
, BM_SETSTATE
, TRUE
, 0 );
644 if (wParam
!= VK_SPACE
)
648 state
= infoPtr
->state
;
649 if (state
& BST_DROPDOWNPUSHED
)
650 SendMessageW(hWnd
, BCM_SETDROPDOWNSTATE
, FALSE
, 0);
651 if (!(state
& BUTTON_BTNPRESSED
)) break;
652 infoPtr
->state
&= BUTTON_NSTATES
| BST_HOT
;
653 if (!(state
& BST_PUSHED
))
658 SendMessageW( hWnd
, BM_SETSTATE
, FALSE
, 0 );
659 GetClientRect( hWnd
, &rect
);
660 if (uMsg
== WM_KEYUP
|| PtInRect( &rect
, pt
))
664 case BS_AUTOCHECKBOX
:
665 SendMessageW( hWnd
, BM_SETCHECK
, !(infoPtr
->state
& BST_CHECKED
), 0 );
667 case BS_AUTORADIOBUTTON
:
668 SendMessageW( hWnd
, BM_SETCHECK
, TRUE
, 0 );
671 SendMessageW( hWnd
, BM_SETCHECK
, (infoPtr
->state
& BST_INDETERMINATE
) ? 0 :
672 ((infoPtr
->state
& 3) + 1), 0 );
676 BUTTON_NOTIFY_PARENT(hWnd
, BN_CLICKED
);
684 case WM_CAPTURECHANGED
:
685 TRACE("WM_CAPTURECHANGED %p\n", hWnd
);
686 if (hWnd
== (HWND
)lParam
) break;
687 if (infoPtr
->state
& BUTTON_BTNPRESSED
)
689 infoPtr
->state
&= BUTTON_NSTATES
;
690 if (infoPtr
->state
& BST_PUSHED
)
691 SendMessageW( hWnd
, BM_SETSTATE
, FALSE
, 0 );
697 TRACKMOUSEEVENT mouse_event
;
699 mouse_event
.cbSize
= sizeof(TRACKMOUSEEVENT
);
700 mouse_event
.dwFlags
= TME_QUERY
;
701 if (!TrackMouseEvent(&mouse_event
) || !(mouse_event
.dwFlags
& (TME_HOVER
| TME_LEAVE
)))
703 mouse_event
.dwFlags
= TME_HOVER
| TME_LEAVE
;
704 mouse_event
.hwndTrack
= hWnd
;
705 mouse_event
.dwHoverTime
= 1;
706 TrackMouseEvent(&mouse_event
);
709 if ((wParam
& MK_LBUTTON
) && GetCapture() == hWnd
)
711 GetClientRect( hWnd
, &rect
);
712 SendMessageW( hWnd
, BM_SETSTATE
, PtInRect(&rect
, pt
), 0 );
719 infoPtr
->state
|= BST_HOT
;
720 InvalidateRect( hWnd
, NULL
, FALSE
);
726 infoPtr
->state
&= ~BST_HOT
;
727 InvalidateRect( hWnd
, NULL
, FALSE
);
733 /* Clear an old text here as Windows does */
734 if (IsWindowVisible(hWnd
))
736 HDC hdc
= GetDC(hWnd
);
739 HWND parent
= GetParent(hWnd
);
740 UINT message
= (btn_type
== BS_PUSHBUTTON
||
741 btn_type
== BS_DEFPUSHBUTTON
||
742 btn_type
== BS_USERBUTTON
||
743 btn_type
== BS_OWNERDRAW
) ?
744 WM_CTLCOLORBTN
: WM_CTLCOLORSTATIC
;
746 if (!parent
) parent
= hWnd
;
747 hbrush
= (HBRUSH
)SendMessageW(parent
, message
,
748 (WPARAM
)hdc
, (LPARAM
)hWnd
);
749 if (!hbrush
) /* did the app forget to call DefWindowProc ? */
750 hbrush
= (HBRUSH
)DefWindowProcW(parent
, message
,
751 (WPARAM
)hdc
, (LPARAM
)hWnd
);
753 GetClientRect(hWnd
, &client
);
755 /* FIXME: check other BS_* handlers */
756 if (btn_type
== BS_GROUPBOX
)
757 InflateRect(&rc
, -7, 1); /* GB_Paint does this */
758 BUTTON_CalcLayoutRects(infoPtr
, hdc
, &rc
, NULL
, NULL
);
759 /* Clip by client rect bounds */
760 if (rc
.right
> client
.right
) rc
.right
= client
.right
;
761 if (rc
.bottom
> client
.bottom
) rc
.bottom
= client
.bottom
;
762 FillRect(hdc
, &rc
, hbrush
);
763 ReleaseDC(hWnd
, hdc
);
766 DefWindowProcW( hWnd
, WM_SETTEXT
, wParam
, lParam
);
767 if (btn_type
== BS_GROUPBOX
) /* Yes, only for BS_GROUPBOX */
768 InvalidateRect( hWnd
, NULL
, TRUE
);
769 else if (GetWindowTheme( hWnd
))
770 RedrawWindow( hWnd
, NULL
, NULL
, RDW_INVALIDATE
| RDW_UPDATENOW
);
772 paint_button( infoPtr
, btn_type
, ODA_DRAWENTIRE
);
773 return 1; /* success. FIXME: check text length */
778 WCHAR
*note
= (WCHAR
*)lParam
;
779 if (btn_type
!= BS_COMMANDLINK
&& btn_type
!= BS_DEFCOMMANDLINK
)
781 SetLastError(ERROR_NOT_SUPPORTED
);
785 heap_free(infoPtr
->note
);
788 infoPtr
->note_length
= lstrlenW(note
);
789 infoPtr
->note
= heap_strndupW(note
, infoPtr
->note_length
);
792 if (!note
|| !infoPtr
->note
)
794 infoPtr
->note_length
= 0;
795 infoPtr
->note
= heap_alloc_zero(sizeof(WCHAR
));
798 SetLastError(NO_ERROR
);
804 DWORD
*size
= (DWORD
*)wParam
;
805 WCHAR
*buffer
= (WCHAR
*)lParam
;
808 if (btn_type
!= BS_COMMANDLINK
&& btn_type
!= BS_DEFCOMMANDLINK
)
810 SetLastError(ERROR_NOT_SUPPORTED
);
814 if (!buffer
|| !size
|| !infoPtr
->note
)
816 SetLastError(ERROR_INVALID_PARAMETER
);
822 length
= min(*size
- 1, infoPtr
->note_length
);
823 memcpy(buffer
, infoPtr
->note
, length
* sizeof(WCHAR
));
824 buffer
[length
] = '\0';
827 if (*size
< infoPtr
->note_length
+ 1)
829 *size
= infoPtr
->note_length
+ 1;
830 SetLastError(ERROR_INSUFFICIENT_BUFFER
);
835 SetLastError(NO_ERROR
);
840 case BCM_GETNOTELENGTH
:
842 if (btn_type
!= BS_COMMANDLINK
&& btn_type
!= BS_DEFCOMMANDLINK
)
844 SetLastError(ERROR_NOT_SUPPORTED
);
848 return infoPtr
->note_length
;
852 infoPtr
->font
= (HFONT
)wParam
;
853 if (lParam
) InvalidateRect(hWnd
, NULL
, TRUE
);
857 return (LRESULT
)infoPtr
->font
;
860 TRACE("WM_SETFOCUS %p\n",hWnd
);
861 infoPtr
->state
|= BST_FOCUS
;
863 if (btn_type
== BS_OWNERDRAW
)
864 paint_button( infoPtr
, btn_type
, ODA_FOCUS
);
866 InvalidateRect(hWnd
, NULL
, FALSE
);
868 if (style
& BS_NOTIFY
)
869 BUTTON_NOTIFY_PARENT(hWnd
, BN_SETFOCUS
);
873 TRACE("WM_KILLFOCUS %p\n",hWnd
);
874 infoPtr
->state
&= ~BST_FOCUS
;
876 if ((infoPtr
->state
& BUTTON_BTNPRESSED
) && GetCapture() == hWnd
)
878 if (style
& BS_NOTIFY
)
879 BUTTON_NOTIFY_PARENT(hWnd
, BN_KILLFOCUS
);
881 InvalidateRect( hWnd
, NULL
, FALSE
);
884 case WM_SYSCOLORCHANGE
:
885 InvalidateRect( hWnd
, NULL
, FALSE
);
892 new_btn_type
= wParam
& BS_TYPEMASK
;
893 if (btn_type
>= BS_SPLITBUTTON
&& new_btn_type
<= BS_DEFPUSHBUTTON
)
894 new_btn_type
= (btn_type
& ~BS_DEFPUSHBUTTON
) | new_btn_type
;
896 style
= (style
& ~BS_TYPEMASK
) | new_btn_type
;
897 SetWindowLongW( hWnd
, GWL_STYLE
, style
);
899 /* Only redraw if lParam flag is set.*/
901 InvalidateRect( hWnd
, NULL
, TRUE
);
906 SendMessageW( hWnd
, WM_LBUTTONDOWN
, 0, 0 );
907 SendMessageW( hWnd
, WM_LBUTTONUP
, 0, 0 );
911 infoPtr
->image_type
= (DWORD
)wParam
;
912 oldHbitmap
= infoPtr
->image
;
913 infoPtr
->u
.image
= CopyImage((HANDLE
)lParam
, infoPtr
->image_type
, 0, 0, 0);
914 infoPtr
->image
= (HANDLE
)lParam
;
915 InvalidateRect( hWnd
, NULL
, FALSE
);
916 return (LRESULT
)oldHbitmap
;
919 return (LRESULT
)infoPtr
->image
;
921 case BCM_SETIMAGELIST
:
923 BUTTON_IMAGELIST
*imagelist
= (BUTTON_IMAGELIST
*)lParam
;
925 if (!imagelist
) return FALSE
;
927 infoPtr
->imagelist
= *imagelist
;
931 case BCM_GETIMAGELIST
:
933 BUTTON_IMAGELIST
*imagelist
= (BUTTON_IMAGELIST
*)lParam
;
935 if (!imagelist
) return FALSE
;
937 *imagelist
= infoPtr
->imagelist
;
941 case BCM_SETSPLITINFO
:
943 BUTTON_SPLITINFO
*info
= (BUTTON_SPLITINFO
*)lParam
;
945 if (!info
) return TRUE
;
947 if (info
->mask
& (BCSIF_GLYPH
| BCSIF_IMAGE
))
949 infoPtr
->split_style
&= ~BCSS_IMAGE
;
950 if (!(info
->mask
& BCSIF_GLYPH
))
951 infoPtr
->split_style
|= BCSS_IMAGE
;
952 infoPtr
->glyph
= info
->himlGlyph
;
953 infoPtr
->glyph_size
.cx
= infoPtr
->glyph_size
.cy
= 0;
956 if (info
->mask
& BCSIF_STYLE
)
957 infoPtr
->split_style
= info
->uSplitStyle
;
958 if (info
->mask
& BCSIF_SIZE
)
959 infoPtr
->glyph_size
= info
->size
;
961 /* Calculate fitting value for cx if invalid (cy is untouched) */
962 if (infoPtr
->glyph_size
.cx
<= 0)
963 infoPtr
->glyph_size
.cx
= get_default_glyph_size(infoPtr
);
965 /* Windows doesn't invalidate or redraw it, so we don't, either */
969 case BCM_GETSPLITINFO
:
971 BUTTON_SPLITINFO
*info
= (BUTTON_SPLITINFO
*)lParam
;
973 if (!info
) return FALSE
;
975 if (info
->mask
& BCSIF_STYLE
)
976 info
->uSplitStyle
= infoPtr
->split_style
;
977 if (info
->mask
& (BCSIF_GLYPH
| BCSIF_IMAGE
))
978 info
->himlGlyph
= infoPtr
->glyph
;
979 if (info
->mask
& BCSIF_SIZE
)
980 info
->size
= infoPtr
->glyph_size
;
986 return infoPtr
->state
& 3;
989 if (wParam
> maxCheckState
[btn_type
]) wParam
= maxCheckState
[btn_type
];
990 if ((btn_type
== BS_RADIOBUTTON
) || (btn_type
== BS_AUTORADIOBUTTON
))
992 style
= wParam
? style
| WS_TABSTOP
: style
& ~WS_TABSTOP
;
993 SetWindowLongW( hWnd
, GWL_STYLE
, style
);
995 if ((infoPtr
->state
& 3) != wParam
)
997 infoPtr
->state
= (infoPtr
->state
& ~3) | wParam
;
998 InvalidateRect( hWnd
, NULL
, FALSE
);
1000 if ((btn_type
== BS_AUTORADIOBUTTON
) && (wParam
== BST_CHECKED
) && (style
& WS_CHILD
))
1001 BUTTON_CheckAutoRadioButton( hWnd
);
1005 return infoPtr
->state
;
1008 state
= infoPtr
->state
;
1009 new_state
= wParam
? BST_PUSHED
: 0;
1011 if ((state
^ new_state
) & BST_PUSHED
)
1014 state
|= BST_PUSHED
;
1016 state
&= ~BST_PUSHED
;
1018 if (btn_type
== BS_USERBUTTON
)
1019 BUTTON_NOTIFY_PARENT( hWnd
, (state
& BST_PUSHED
) ? BN_HILITE
: BN_UNHILITE
);
1020 infoPtr
->state
= state
;
1022 InvalidateRect( hWnd
, NULL
, FALSE
);
1026 case BCM_SETDROPDOWNSTATE
:
1027 new_state
= wParam
? BST_DROPDOWNPUSHED
: 0;
1029 if ((infoPtr
->state
^ new_state
) & BST_DROPDOWNPUSHED
)
1031 infoPtr
->state
&= ~BST_DROPDOWNPUSHED
;
1032 infoPtr
->state
|= new_state
;
1033 InvalidateRect(hWnd
, NULL
, FALSE
);
1037 case BCM_SETTEXTMARGIN
:
1039 RECT
*text_margin
= (RECT
*)lParam
;
1041 if (!text_margin
) return FALSE
;
1043 infoPtr
->text_margin
= *text_margin
;
1047 case BCM_GETTEXTMARGIN
:
1049 RECT
*text_margin
= (RECT
*)lParam
;
1051 if (!text_margin
) return FALSE
;
1053 *text_margin
= infoPtr
->text_margin
;
1057 case BCM_GETIDEALSIZE
:
1059 SIZE
*size
= (SIZE
*)lParam
;
1061 if (!size
) return FALSE
;
1063 return btnGetIdealSizeFunc
[btn_type
](infoPtr
, size
);
1067 if(btn_type
== BS_GROUPBOX
) return HTTRANSPARENT
;
1070 return DefWindowProcW(hWnd
, uMsg
, wParam
, lParam
);
1075 /* If maxWidth is zero, rectangle width is unlimited */
1076 static RECT
BUTTON_GetTextRect(const BUTTON_INFO
*infoPtr
, HDC hdc
, const WCHAR
*text
, LONG maxWidth
)
1078 LONG style
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
1079 LONG exStyle
= GetWindowLongW(infoPtr
->hwnd
, GWL_EXSTYLE
);
1080 UINT dtStyle
= BUTTON_BStoDT(style
, exStyle
);
1084 rect
.right
= maxWidth
;
1085 hPrevFont
= SelectObject(hdc
, infoPtr
->font
);
1086 /* Calculate height without DT_VCENTER and DT_BOTTOM to get the correct height */
1087 DrawTextW(hdc
, text
, -1, &rect
, (dtStyle
& ~(DT_VCENTER
| DT_BOTTOM
)) | DT_CALCRECT
);
1088 if (hPrevFont
) SelectObject(hdc
, hPrevFont
);
1093 static BOOL
show_image_only(const BUTTON_INFO
*infoPtr
)
1095 LONG style
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
1096 return (style
& (BS_ICON
| BS_BITMAP
)) && (infoPtr
->u
.image
|| infoPtr
->imagelist
.himl
);
1099 static BOOL
show_image_and_text(const BUTTON_INFO
*infoPtr
)
1101 LONG style
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
1102 UINT type
= get_button_type(style
);
1103 return !(style
& (BS_ICON
| BS_BITMAP
))
1104 && ((infoPtr
->u
.image
1105 && (type
== BS_PUSHBUTTON
|| type
== BS_DEFPUSHBUTTON
|| type
== BS_USERBUTTON
|| type
== BS_SPLITBUTTON
1106 || type
== BS_DEFSPLITBUTTON
|| type
== BS_COMMANDLINK
|| type
== BS_DEFCOMMANDLINK
))
1107 || (infoPtr
->imagelist
.himl
&& type
!= BS_GROUPBOX
));
1110 static BOOL
show_image(const BUTTON_INFO
*infoPtr
)
1112 return show_image_only(infoPtr
) || show_image_and_text(infoPtr
);
1115 /* Get a bounding rectangle that is large enough to contain a image and a text side by side.
1116 * Note: (left,top) of the result rectangle may not be (0,0), offset it by yourself if needed */
1117 static RECT
BUTTON_GetBoundingLabelRect(LONG style
, const RECT
*textRect
, const RECT
*imageRect
)
1120 RECT rect
= *imageRect
;
1121 INT textWidth
= textRect
->right
- textRect
->left
;
1122 INT textHeight
= textRect
->bottom
- textRect
->top
;
1123 INT imageWidth
= imageRect
->right
- imageRect
->left
;
1124 INT imageHeight
= imageRect
->bottom
- imageRect
->top
;
1126 if ((style
& BS_CENTER
) == BS_RIGHT
)
1127 OffsetRect(&rect
, textWidth
, 0);
1128 else if ((style
& BS_CENTER
) == BS_LEFT
)
1129 OffsetRect(&rect
, -imageWidth
, 0);
1130 else if ((style
& BS_VCENTER
) == BS_BOTTOM
)
1131 OffsetRect(&rect
, 0, textHeight
);
1132 else if ((style
& BS_VCENTER
) == BS_TOP
)
1133 OffsetRect(&rect
, 0, -imageHeight
);
1135 OffsetRect(&rect
, -imageWidth
, 0);
1137 UnionRect(&labelRect
, textRect
, &rect
);
1141 /* Position a rectangle inside a bounding rectangle according to button alignment flags */
1142 static void BUTTON_PositionRect(LONG style
, const RECT
*outerRect
, RECT
*innerRect
, const RECT
*margin
)
1144 INT width
= innerRect
->right
- innerRect
->left
;
1145 INT height
= innerRect
->bottom
- innerRect
->top
;
1147 if ((style
& BS_PUSHLIKE
) && !(style
& BS_CENTER
)) style
|= BS_CENTER
;
1149 if (!(style
& BS_CENTER
))
1151 if (button_centers_text(style
))
1157 if (!(style
& BS_VCENTER
))
1159 /* Group box's text is top aligned by default */
1160 if (get_button_type(style
) == BS_GROUPBOX
)
1164 switch (style
& BS_CENTER
)
1167 /* The left and right margins are added to the inner rectangle to get a new rectangle. Then
1168 * the new rectangle is adjusted to be in the horizontal center */
1169 innerRect
->left
= outerRect
->left
+ (outerRect
->right
- outerRect
->left
- width
1170 + margin
->left
- margin
->right
) / 2;
1171 innerRect
->right
= innerRect
->left
+ width
;
1174 innerRect
->right
= outerRect
->right
- margin
->right
;
1175 innerRect
->left
= innerRect
->right
- width
;
1179 innerRect
->left
= outerRect
->left
+ margin
->left
;
1180 innerRect
->right
= innerRect
->left
+ width
;
1184 switch (style
& BS_VCENTER
)
1187 innerRect
->top
= outerRect
->top
+ margin
->top
;
1188 innerRect
->bottom
= innerRect
->top
+ height
;
1191 innerRect
->bottom
= outerRect
->bottom
- margin
->bottom
;
1192 innerRect
->top
= innerRect
->bottom
- height
;
1196 /* The top and bottom margins are added to the inner rectangle to get a new rectangle. Then
1197 * the new rectangle is adjusted to be in the vertical center */
1198 innerRect
->top
= outerRect
->top
+ (outerRect
->bottom
- outerRect
->top
- height
1199 + margin
->top
- margin
->bottom
) / 2;
1200 innerRect
->bottom
= innerRect
->top
+ height
;
1205 /* Convert imagelist align style to button align style */
1206 static UINT
BUTTON_ILStoBS(UINT align
)
1210 case BUTTON_IMAGELIST_ALIGN_TOP
:
1211 return BS_CENTER
| BS_TOP
;
1212 case BUTTON_IMAGELIST_ALIGN_BOTTOM
:
1213 return BS_CENTER
| BS_BOTTOM
;
1214 case BUTTON_IMAGELIST_ALIGN_CENTER
:
1215 return BS_CENTER
| BS_VCENTER
;
1216 case BUTTON_IMAGELIST_ALIGN_RIGHT
:
1217 return BS_RIGHT
| BS_VCENTER
;
1218 case BUTTON_IMAGELIST_ALIGN_LEFT
:
1220 return BS_LEFT
| BS_VCENTER
;
1224 static SIZE
BUTTON_GetImageSize(const BUTTON_INFO
*infoPtr
)
1230 /* ImageList has priority over image */
1231 if (infoPtr
->imagelist
.himl
)
1234 ImageList_GetIconSize(infoPtr
->imagelist
.himl
, &scx
, &scy
);
1238 else if (infoPtr
->u
.image
)
1240 if (infoPtr
->image_type
== IMAGE_ICON
)
1242 GetIconInfo(infoPtr
->u
.icon
, &iconInfo
);
1243 GetObjectW(iconInfo
.hbmColor
, sizeof(bm
), &bm
);
1244 DeleteObject(iconInfo
.hbmColor
);
1245 DeleteObject(iconInfo
.hbmMask
);
1247 else if (infoPtr
->image_type
== IMAGE_BITMAP
)
1248 GetObjectW(infoPtr
->u
.bitmap
, sizeof(bm
), &bm
);
1250 size
.cx
= bm
.bmWidth
;
1251 size
.cy
= bm
.bmHeight
;
1257 static const RECT
*BUTTON_GetTextMargin(const BUTTON_INFO
*infoPtr
)
1259 static const RECT oneMargin
= {1, 1, 1, 1};
1261 /* Use text margin only when showing both image and text, and image is not imagelist */
1262 if (show_image_and_text(infoPtr
) && !infoPtr
->imagelist
.himl
)
1263 return &infoPtr
->text_margin
;
1268 static void BUTTON_GetClientRectSize(BUTTON_INFO
*infoPtr
, SIZE
*size
)
1271 GetClientRect(infoPtr
->hwnd
, &rect
);
1272 size
->cx
= rect
.right
- rect
.left
;
1273 size
->cy
= rect
.bottom
- rect
.top
;
1276 static void BUTTON_GetTextIdealSize(BUTTON_INFO
*infoPtr
, LONG maxWidth
, SIZE
*size
)
1278 WCHAR
*text
= get_button_text(infoPtr
);
1281 const RECT
*margin
= BUTTON_GetTextMargin(infoPtr
);
1285 maxWidth
-= margin
->right
+ margin
->right
;
1286 if (maxWidth
<= 0) maxWidth
= 1;
1289 hdc
= GetDC(infoPtr
->hwnd
);
1290 rect
= BUTTON_GetTextRect(infoPtr
, hdc
, text
, maxWidth
);
1291 ReleaseDC(infoPtr
->hwnd
, hdc
);
1294 size
->cx
= rect
.right
- rect
.left
+ margin
->left
+ margin
->right
;
1295 size
->cy
= rect
.bottom
- rect
.top
+ margin
->top
+ margin
->bottom
;
1298 static void BUTTON_GetLabelIdealSize(BUTTON_INFO
*infoPtr
, LONG maxWidth
, SIZE
*size
)
1300 LONG style
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
1305 imageSize
= BUTTON_GetImageSize(infoPtr
);
1306 if (infoPtr
->imagelist
.himl
)
1308 imageSize
.cx
+= infoPtr
->imagelist
.margin
.left
+ infoPtr
->imagelist
.margin
.right
;
1309 imageSize
.cy
+= infoPtr
->imagelist
.margin
.top
+ infoPtr
->imagelist
.margin
.bottom
;
1310 if (infoPtr
->imagelist
.uAlign
== BUTTON_IMAGELIST_ALIGN_TOP
1311 || infoPtr
->imagelist
.uAlign
== BUTTON_IMAGELIST_ALIGN_BOTTOM
)
1318 /* horizontal alignment flags has priority over vertical ones if both are specified */
1319 if (!(style
& (BS_CENTER
| BS_VCENTER
)) || ((style
& BS_CENTER
) && (style
& BS_CENTER
) != BS_CENTER
)
1320 || !(style
& BS_VCENTER
) || (style
& BS_VCENTER
) == BS_VCENTER
)
1330 maxWidth
-= imageSize
.cx
;
1331 if (maxWidth
<= 0) maxWidth
= 1;
1333 BUTTON_GetTextIdealSize(infoPtr
, maxWidth
, &textSize
);
1334 size
->cx
= textSize
.cx
+ imageSize
.cx
;
1335 size
->cy
= max(textSize
.cy
, imageSize
.cy
);
1339 BUTTON_GetTextIdealSize(infoPtr
, maxWidth
, &textSize
);
1340 size
->cx
= max(textSize
.cx
, imageSize
.cx
);
1341 size
->cy
= textSize
.cy
+ imageSize
.cy
;
1345 static BOOL
GB_GetIdealSize(BUTTON_INFO
*infoPtr
, SIZE
*size
)
1347 BUTTON_GetClientRectSize(infoPtr
, size
);
1351 static BOOL
CB_GetIdealSize(BUTTON_INFO
*infoPtr
, SIZE
*size
)
1353 LONG style
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
1360 LONG checkboxWidth
, checkboxHeight
;
1363 if (SendMessageW(infoPtr
->hwnd
, WM_GETTEXTLENGTH
, 0, 0) == 0)
1365 BUTTON_GetClientRectSize(infoPtr
, size
);
1369 hdc
= GetDC(infoPtr
->hwnd
);
1370 scaleX
= GetDeviceCaps(hdc
, LOGPIXELSX
) / 96.0;
1371 scaleY
= GetDeviceCaps(hdc
, LOGPIXELSY
) / 96.0;
1372 if ((hfont
= infoPtr
->font
)) SelectObject(hdc
, hfont
);
1373 GetCharWidthW(hdc
, '0', '0', &textOffset
);
1375 ReleaseDC(infoPtr
->hwnd
, hdc
);
1377 checkboxWidth
= 12 * scaleX
+ 1;
1378 checkboxHeight
= 12 * scaleY
+ 1;
1381 maxWidth
= size
->cx
- checkboxWidth
- textOffset
;
1382 if (maxWidth
<= 0) maxWidth
= 1;
1385 /* Checkbox doesn't support both image(but not image list) and text */
1386 if (!(style
& (BS_ICON
| BS_BITMAP
)) && infoPtr
->u
.image
)
1387 BUTTON_GetTextIdealSize(infoPtr
, maxWidth
, &labelSize
);
1389 BUTTON_GetLabelIdealSize(infoPtr
, maxWidth
, &labelSize
);
1391 size
->cx
= labelSize
.cx
+ checkboxWidth
+ textOffset
;
1392 size
->cy
= max(labelSize
.cy
, checkboxHeight
);
1397 static BOOL
PB_GetIdealSize(BUTTON_INFO
*infoPtr
, SIZE
*size
)
1401 if (SendMessageW(infoPtr
->hwnd
, WM_GETTEXTLENGTH
, 0, 0) == 0)
1402 BUTTON_GetClientRectSize(infoPtr
, size
);
1405 /* Ideal size include text size even if image only flags(BS_ICON, BS_BITMAP) are specified */
1406 BUTTON_GetLabelIdealSize(infoPtr
, size
->cx
, &labelSize
);
1408 size
->cx
= labelSize
.cx
;
1409 size
->cy
= labelSize
.cy
;
1414 static BOOL
SB_GetIdealSize(BUTTON_INFO
*infoPtr
, SIZE
*size
)
1416 LONG extra_width
= infoPtr
->glyph_size
.cx
* 2 + GetSystemMetrics(SM_CXEDGE
);
1419 if (SendMessageW(infoPtr
->hwnd
, WM_GETTEXTLENGTH
, 0, 0) == 0)
1421 BUTTON_GetClientRectSize(infoPtr
, size
);
1422 size
->cx
= max(size
->cx
, extra_width
);
1426 BUTTON_GetLabelIdealSize(infoPtr
, size
->cx
, &label_size
);
1427 size
->cx
= label_size
.cx
+ ((size
->cx
== 0) ? extra_width
: 0);
1428 size
->cy
= label_size
.cy
;
1433 static BOOL
CL_GetIdealSize(BUTTON_INFO
*infoPtr
, SIZE
*size
)
1435 HTHEME theme
= GetWindowTheme(infoPtr
->hwnd
);
1436 HDC hdc
= GetDC(infoPtr
->hwnd
);
1437 LONG w
, text_w
= 0, text_h
= 0;
1438 UINT flags
= DT_TOP
| DT_LEFT
;
1439 HFONT font
, old_font
= NULL
;
1440 RECT text_bound
= { 0 };
1445 /* Get the image size */
1446 if (infoPtr
->u
.image
|| infoPtr
->imagelist
.himl
)
1447 img_size
= BUTTON_GetImageSize(infoPtr
);
1451 GetThemePartSize(theme
, NULL
, BP_COMMANDLINKGLYPH
, CMDLS_NORMAL
, NULL
, TS_DRAW
, &img_size
);
1453 img_size
.cx
= img_size
.cy
= command_link_defglyph_size
;
1456 /* Get the content margins */
1459 RECT r
= { 0, 0, 0xffff, 0xffff };
1460 GetThemeBackgroundContentRect(theme
, hdc
, BP_COMMANDLINK
, CMDLS_NORMAL
, &r
, &margin
);
1461 margin
.left
-= r
.left
;
1462 margin
.top
-= r
.top
;
1463 margin
.right
= r
.right
- margin
.right
;
1464 margin
.bottom
= r
.bottom
- margin
.bottom
;
1468 margin
.left
= margin
.right
= command_link_margin
;
1469 margin
.top
= margin
.bottom
= command_link_margin
;
1472 /* Account for the border margins and the margin between image and text */
1473 w
= margin
.left
+ margin
.right
+ (img_size
.cx
? (img_size
.cx
+ command_link_margin
) : 0);
1475 /* If a rectangle with a specific width was requested, bound the text to it */
1478 text_bound
.right
= size
->cx
- w
;
1479 flags
|= DT_WORDBREAK
;
1484 if (infoPtr
->font
) old_font
= SelectObject(hdc
, infoPtr
->font
);
1486 /* Find the text's rect */
1487 if ((text
= get_button_text(infoPtr
)))
1490 GetThemeTextExtent(theme
, hdc
, BP_COMMANDLINK
, CMDLS_NORMAL
,
1491 text
, -1, flags
, &text_bound
, &r
);
1493 text_w
= r
.right
- r
.left
;
1494 text_h
= r
.bottom
- r
.top
;
1497 /* Find the note's rect */
1502 opts
.dwSize
= sizeof(opts
);
1503 opts
.dwFlags
= DTT_FONTPROP
| DTT_CALCRECT
;
1504 opts
.iFontPropId
= TMT_BODYFONT
;
1505 DrawThemeTextEx(theme
, hdc
, BP_COMMANDLINK
, CMDLS_NORMAL
,
1506 infoPtr
->note
, infoPtr
->note_length
,
1507 flags
| DT_NOPREFIX
| DT_CALCRECT
, &text_bound
, &opts
);
1508 text_w
= max(text_w
, text_bound
.right
- text_bound
.left
);
1509 text_h
+= text_bound
.bottom
- text_bound
.top
;
1514 NONCLIENTMETRICSW ncm
;
1516 ncm
.cbSize
= sizeof(ncm
);
1517 if (SystemParametersInfoW(SPI_GETNONCLIENTMETRICS
, sizeof(ncm
), &ncm
, 0))
1519 LONG note_weight
= ncm
.lfMessageFont
.lfWeight
;
1521 /* Find the text's rect */
1522 ncm
.lfMessageFont
.lfWeight
= FW_BOLD
;
1523 if ((font
= CreateFontIndirectW(&ncm
.lfMessageFont
)))
1525 if ((text
= get_button_text(infoPtr
)))
1527 RECT r
= text_bound
;
1528 old_font
= SelectObject(hdc
, font
);
1529 DrawTextW(hdc
, text
, -1, &r
, flags
| DT_CALCRECT
);
1532 text_w
= r
.right
- r
.left
;
1533 text_h
= r
.bottom
- r
.top
;
1538 /* Find the note's rect */
1539 ncm
.lfMessageFont
.lfWeight
= note_weight
;
1540 if (infoPtr
->note
&& (font
= CreateFontIndirectW(&ncm
.lfMessageFont
)))
1542 HFONT tmp
= SelectObject(hdc
, font
);
1543 if (!old_font
) old_font
= tmp
;
1545 DrawTextW(hdc
, infoPtr
->note
, infoPtr
->note_length
, &text_bound
,
1546 flags
| DT_NOPREFIX
| DT_CALCRECT
);
1549 text_w
= max(text_w
, text_bound
.right
- text_bound
.left
);
1550 text_h
+= text_bound
.bottom
- text_bound
.top
+ 2;
1556 size
->cx
= min(size
->cx
, w
);
1557 size
->cy
= max(text_h
, img_size
.cy
) + margin
.top
+ margin
.bottom
;
1559 if (old_font
) SelectObject(hdc
, old_font
);
1560 ReleaseDC(infoPtr
->hwnd
, hdc
);
1564 /**********************************************************************
1565 * BUTTON_CalcLayoutRects
1567 * Calculates the rectangles of the button label(image and text) and its parts depending on a button's style.
1569 * Returns flags to be passed to DrawText.
1570 * Calculated rectangle doesn't take into account button state
1571 * (pushed, etc.). If there is nothing to draw (no text/image) output
1572 * rectangle is empty, and return value is (UINT)-1.
1575 * infoPtr [I] Button pointer
1576 * hdc [I] Handle to device context to draw to
1577 * labelRc [I/O] Input the rect the label to be positioned in, and output the label rect
1578 * imageRc [O] Optional, output the image rect
1579 * textRc [O] Optional, output the text rect
1581 static UINT
BUTTON_CalcLayoutRects(const BUTTON_INFO
*infoPtr
, HDC hdc
, RECT
*labelRc
, RECT
*imageRc
, RECT
*textRc
)
1583 WCHAR
*text
= get_button_text(infoPtr
);
1584 SIZE imageSize
= BUTTON_GetImageSize(infoPtr
);
1585 RECT labelRect
, imageRect
, imageRectWithMargin
, textRect
;
1586 LONG imageMarginWidth
, imageMarginHeight
;
1587 const RECT
*textMargin
= BUTTON_GetTextMargin(infoPtr
);
1588 LONG style
, ex_style
, split_style
;
1589 RECT emptyMargin
= {0};
1593 /* Calculate label rectangle according to label type */
1594 if ((imageSize
.cx
== 0 && imageSize
.cy
== 0) && (text
== NULL
|| text
[0] == '\0'))
1596 SetRectEmpty(labelRc
);
1597 SetRectEmpty(imageRc
);
1598 SetRectEmpty(textRc
);
1603 style
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
1604 ex_style
= GetWindowLongW(infoPtr
->hwnd
, GWL_EXSTYLE
);
1605 /* Add BS_RIGHT directly. When both WS_EX_RIGHT and BS_LEFT are present, it becomes BS_CENTER */
1606 if (ex_style
& WS_EX_RIGHT
)
1608 split_style
= infoPtr
->imagelist
.himl
? BUTTON_ILStoBS(infoPtr
->imagelist
.uAlign
) : style
;
1609 dtStyle
= BUTTON_BStoDT(style
, ex_style
);
1611 /* Group boxes are top aligned unless BS_PUSHLIKE is set and it's not themed */
1612 if (get_button_type(style
) == BS_GROUPBOX
1613 && (!(style
& BS_PUSHLIKE
) || GetWindowTheme(infoPtr
->hwnd
)))
1614 style
&= ~BS_VCENTER
| BS_TOP
;
1616 SetRect(&imageRect
, 0, 0, imageSize
.cx
, imageSize
.cy
);
1617 imageRectWithMargin
= imageRect
;
1618 if (infoPtr
->imagelist
.himl
)
1620 imageRectWithMargin
.top
-= infoPtr
->imagelist
.margin
.top
;
1621 imageRectWithMargin
.bottom
+= infoPtr
->imagelist
.margin
.bottom
;
1622 imageRectWithMargin
.left
-= infoPtr
->imagelist
.margin
.left
;
1623 imageRectWithMargin
.right
+= infoPtr
->imagelist
.margin
.right
;
1626 /* Show image only */
1627 if (show_image_only(infoPtr
))
1629 BUTTON_PositionRect(style
, labelRc
, &imageRect
,
1630 infoPtr
->imagelist
.himl
? &infoPtr
->imagelist
.margin
: &emptyMargin
);
1631 labelRect
= imageRect
;
1632 SetRectEmpty(&textRect
);
1637 maxTextWidth
= labelRc
->right
- labelRc
->left
;
1638 textRect
= BUTTON_GetTextRect(infoPtr
, hdc
, text
, maxTextWidth
);
1640 /* Show image and text */
1641 if (show_image_and_text(infoPtr
))
1643 RECT boundingLabelRect
, boundingImageRect
, boundingTextRect
;
1645 /* Get label rect */
1646 /* Image list may have different alignment than the button, use the whole rect for label in this case */
1647 if (infoPtr
->imagelist
.himl
)
1648 labelRect
= *labelRc
;
1651 /* Get a label bounding rectangle to position the label in the user specified label rectangle because
1652 * text and image need to align together. */
1653 boundingLabelRect
= BUTTON_GetBoundingLabelRect(split_style
, &textRect
, &imageRectWithMargin
);
1654 BUTTON_PositionRect(split_style
, labelRc
, &boundingLabelRect
, &emptyMargin
);
1655 labelRect
= boundingLabelRect
;
1658 /* When imagelist has center align, use the whole rect for imagelist and text */
1659 if(infoPtr
->imagelist
.himl
&& infoPtr
->imagelist
.uAlign
== BUTTON_IMAGELIST_ALIGN_CENTER
)
1661 boundingImageRect
= labelRect
;
1662 boundingTextRect
= labelRect
;
1663 BUTTON_PositionRect(split_style
, &boundingImageRect
, &imageRect
,
1664 infoPtr
->imagelist
.himl
? &infoPtr
->imagelist
.margin
: &emptyMargin
);
1665 /* Text doesn't use imagelist align */
1666 BUTTON_PositionRect(style
, &boundingTextRect
, &textRect
, textMargin
);
1670 /* Get image rect */
1671 /* Split the label rect to two halves as two bounding rectangles for image and text */
1672 boundingImageRect
= labelRect
;
1673 imageMarginWidth
= imageRectWithMargin
.right
- imageRectWithMargin
.left
;
1674 imageMarginHeight
= imageRectWithMargin
.bottom
- imageRectWithMargin
.top
;
1675 if ((split_style
& BS_CENTER
) == BS_RIGHT
)
1676 boundingImageRect
.left
= boundingImageRect
.right
- imageMarginWidth
;
1677 else if ((split_style
& BS_CENTER
) == BS_LEFT
)
1678 boundingImageRect
.right
= boundingImageRect
.left
+ imageMarginWidth
;
1679 else if ((split_style
& BS_VCENTER
) == BS_BOTTOM
)
1680 boundingImageRect
.top
= boundingImageRect
.bottom
- imageMarginHeight
;
1681 else if ((split_style
& BS_VCENTER
) == BS_TOP
)
1682 boundingImageRect
.bottom
= boundingImageRect
.top
+ imageMarginHeight
;
1684 boundingImageRect
.right
= boundingImageRect
.left
+ imageMarginWidth
;
1685 BUTTON_PositionRect(split_style
, &boundingImageRect
, &imageRect
,
1686 infoPtr
->imagelist
.himl
? &infoPtr
->imagelist
.margin
: &emptyMargin
);
1689 SubtractRect(&boundingTextRect
, &labelRect
, &boundingImageRect
);
1690 /* Text doesn't use imagelist align */
1691 BUTTON_PositionRect(style
, &boundingTextRect
, &textRect
, textMargin
);
1694 /* Show text only */
1697 BUTTON_PositionRect(style
, labelRc
, &textRect
, textMargin
);
1698 labelRect
= textRect
;
1699 SetRectEmpty(&imageRect
);
1704 CopyRect(labelRc
, &labelRect
);
1705 CopyRect(imageRc
, &imageRect
);
1706 CopyRect(textRc
, &textRect
);
1712 /**********************************************************************
1715 * Draw the button's image into the specified rectangle.
1717 static void BUTTON_DrawImage(const BUTTON_INFO
*infoPtr
, HDC hdc
, HBRUSH hbr
, UINT flags
, const RECT
*rect
)
1719 if (infoPtr
->imagelist
.himl
)
1721 int i
= (ImageList_GetImageCount(infoPtr
->imagelist
.himl
) == 1) ? 0 : get_draw_state(infoPtr
) - 1;
1723 ImageList_Draw(infoPtr
->imagelist
.himl
, i
, hdc
, rect
->left
, rect
->top
, ILD_NORMAL
);
1727 switch (infoPtr
->image_type
)
1733 flags
|= DST_BITMAP
;
1739 DrawStateW(hdc
, hbr
, NULL
, (LPARAM
)infoPtr
->u
.image
, 0, rect
->left
, rect
->top
,
1740 rect
->right
- rect
->left
, rect
->bottom
- rect
->top
, flags
);
1745 /**********************************************************************
1746 * BUTTON_DrawTextCallback
1748 * Callback function used by DrawStateW function.
1750 static BOOL CALLBACK
BUTTON_DrawTextCallback(HDC hdc
, LPARAM lp
, WPARAM wp
, int cx
, int cy
)
1754 SetRect(&rc
, 0, 0, cx
, cy
);
1755 DrawTextW(hdc
, (LPCWSTR
)lp
, -1, &rc
, (UINT
)wp
);
1759 /**********************************************************************
1762 * Common function for drawing button label.
1765 * 1. When BS_SINGLELINE is specified and text contains '\t', '\n' or '\r' in the middle, they are rendered as
1766 * squares now whereas they should be ignored.
1767 * 2. When BS_MULTILINE is specified and text contains space in the middle, the space mistakenly be rendered as newline.
1769 static void BUTTON_DrawLabel(const BUTTON_INFO
*infoPtr
, HDC hdc
, UINT dtFlags
, const RECT
*imageRect
,
1770 const RECT
*textRect
)
1773 UINT flags
= IsWindowEnabled(infoPtr
->hwnd
) ? DSS_NORMAL
: DSS_DISABLED
;
1774 LONG style
= GetWindowLongW( infoPtr
->hwnd
, GWL_STYLE
);
1777 /* FIXME: To draw disabled label in Win31 look-and-feel, we probably
1778 * must use DSS_MONO flag and COLOR_GRAYTEXT brush (or maybe DSS_UNION).
1779 * I don't have Win31 on hand to verify that, so I leave it as is.
1782 if ((style
& BS_PUSHLIKE
) && (infoPtr
->state
& BST_INDETERMINATE
))
1784 hbr
= GetSysColorBrush(COLOR_GRAYTEXT
);
1788 if (show_image(infoPtr
)) BUTTON_DrawImage(infoPtr
, hdc
, hbr
, flags
, imageRect
);
1789 if (show_image_only(infoPtr
)) return;
1791 /* DST_COMPLEX -- is 0 */
1792 if (!(text
= get_button_text(infoPtr
))) return;
1793 DrawStateW(hdc
, hbr
, BUTTON_DrawTextCallback
, (LPARAM
)text
, dtFlags
, textRect
->left
, textRect
->top
,
1794 textRect
->right
- textRect
->left
, textRect
->bottom
- textRect
->top
, flags
);
1798 static void BUTTON_DrawThemedLabel(const BUTTON_INFO
*info
, HDC hdc
, UINT text_flags
,
1799 const RECT
*image_rect
, const RECT
*text_rect
, HTHEME theme
,
1800 int part
, int state
)
1802 HBRUSH brush
= NULL
;
1806 if (show_image(info
))
1808 image_flags
= IsWindowEnabled(info
->hwnd
) ? DSS_NORMAL
: DSS_DISABLED
;
1810 if ((GetWindowLongW(info
->hwnd
, GWL_STYLE
) & BS_PUSHLIKE
)
1811 && (info
->state
& BST_INDETERMINATE
))
1813 brush
= GetSysColorBrush(COLOR_GRAYTEXT
);
1814 image_flags
|= DSS_MONO
;
1817 BUTTON_DrawImage(info
, hdc
, brush
, image_flags
, image_rect
);
1820 if (show_image_only(info
))
1823 if (!(text
= get_button_text(info
)))
1826 DrawThemeText(theme
, hdc
, part
, state
, text
, lstrlenW(text
), text_flags
, 0, text_rect
);
1830 /**********************************************************************
1831 * Push Button Functions
1833 static void PB_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
)
1835 RECT rc
, labelRect
, imageRect
, textRect
;
1836 UINT dtFlags
, uState
;
1840 COLORREF oldTxtColor
;
1844 LONG state
= infoPtr
->state
;
1845 LONG style
= GetWindowLongW( infoPtr
->hwnd
, GWL_STYLE
);
1846 BOOL pushedState
= (state
& BST_PUSHED
);
1850 GetClientRect( infoPtr
->hwnd
, &rc
);
1852 /* Send WM_CTLCOLOR to allow changing the font (the colors are fixed) */
1853 if ((hFont
= infoPtr
->font
)) SelectObject( hDC
, hFont
);
1854 parent
= GetParent(infoPtr
->hwnd
);
1855 if (!parent
) parent
= infoPtr
->hwnd
;
1856 SendMessageW( parent
, WM_CTLCOLORBTN
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
1858 hrgn
= set_control_clipping( hDC
, &rc
);
1860 hpen
= CreatePen( PS_SOLID
, 1, GetSysColor(COLOR_WINDOWFRAME
));
1861 hOldPen
= SelectObject(hDC
, hpen
);
1862 hOldBrush
= SelectObject(hDC
,GetSysColorBrush(COLOR_BTNFACE
));
1863 oldBkMode
= SetBkMode(hDC
, TRANSPARENT
);
1865 init_custom_draw(&nmcd
, infoPtr
, hDC
, &rc
);
1867 /* Send erase notifications */
1868 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
1869 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
1871 if (get_button_type(style
) == BS_DEFPUSHBUTTON
)
1873 if (action
!= ODA_FOCUS
)
1874 Rectangle(hDC
, rc
.left
, rc
.top
, rc
.right
, rc
.bottom
);
1875 InflateRect( &rc
, -1, -1 );
1878 /* Skip the frame drawing if only focus has changed */
1879 if (action
!= ODA_FOCUS
)
1881 uState
= DFCS_BUTTONPUSH
;
1883 if (style
& BS_FLAT
)
1884 uState
|= DFCS_MONO
;
1885 else if (pushedState
)
1887 if (get_button_type(style
) == BS_DEFPUSHBUTTON
)
1888 uState
|= DFCS_FLAT
;
1890 uState
|= DFCS_PUSHED
;
1893 if (state
& (BST_CHECKED
| BST_INDETERMINATE
))
1894 uState
|= DFCS_CHECKED
;
1896 DrawFrameControl( hDC
, &rc
, DFC_BUTTON
, uState
);
1899 if (cdrf
& CDRF_NOTIFYPOSTERASE
)
1901 nmcd
.dwDrawStage
= CDDS_POSTERASE
;
1902 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
1905 /* Send paint notifications */
1906 nmcd
.dwDrawStage
= CDDS_PREPAINT
;
1907 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
1908 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
1910 if (!(cdrf
& CDRF_DOERASE
) && action
!= ODA_FOCUS
)
1912 /* draw button label */
1914 /* Shrink label rect at all sides by 2 so that the content won't touch the surrounding frame */
1915 InflateRect(&labelRect
, -2, -2);
1916 dtFlags
= BUTTON_CalcLayoutRects(infoPtr
, hDC
, &labelRect
, &imageRect
, &textRect
);
1918 if (dtFlags
!= (UINT
)-1L)
1920 if (pushedState
) OffsetRect(&labelRect
, 1, 1);
1922 oldTxtColor
= SetTextColor( hDC
, GetSysColor(COLOR_BTNTEXT
) );
1924 BUTTON_DrawLabel(infoPtr
, hDC
, dtFlags
, &imageRect
, &textRect
);
1926 SetTextColor( hDC
, oldTxtColor
);
1930 if (cdrf
& CDRF_NOTIFYPOSTPAINT
)
1932 nmcd
.dwDrawStage
= CDDS_POSTPAINT
;
1933 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
1935 if ((cdrf
& CDRF_SKIPPOSTPAINT
) || dtFlags
== (UINT
)-1L) goto cleanup
;
1937 if (action
== ODA_FOCUS
|| (state
& BST_FOCUS
))
1939 InflateRect( &rc
, -2, -2 );
1940 DrawFocusRect( hDC
, &rc
);
1944 SelectObject( hDC
, hOldPen
);
1945 SelectObject( hDC
, hOldBrush
);
1946 SetBkMode(hDC
, oldBkMode
);
1947 SelectClipRgn( hDC
, hrgn
);
1948 if (hrgn
) DeleteObject( hrgn
);
1949 DeleteObject( hpen
);
1952 /**********************************************************************
1953 * Check Box & Radio Button Functions
1956 /* Get adjusted check box or radio box rectangle */
1957 static RECT
get_box_rect(LONG style
, LONG ex_style
, const RECT
*content_rect
,
1958 const RECT
*label_rect
, BOOL has_label
, SIZE box_size
)
1963 rect
= *content_rect
;
1965 if (style
& BS_LEFTTEXT
|| ex_style
& WS_EX_RIGHT
)
1966 rect
.left
= rect
.right
- box_size
.cx
;
1968 rect
.right
= rect
.left
+ box_size
.cx
;
1970 /* Adjust box when label is valid */
1973 rect
.top
= label_rect
->top
;
1974 rect
.bottom
= label_rect
->bottom
;
1977 /* Box must have the correct height */
1978 delta
= rect
.bottom
- rect
.top
- box_size
.cy
;
1979 if ((style
& BS_VCENTER
) == BS_TOP
)
1982 rect
.top
-= -delta
/ 2 + 1;
1984 rect
.bottom
= rect
.top
+ box_size
.cy
;
1986 else if ((style
& BS_VCENTER
) == BS_BOTTOM
)
1989 rect
.bottom
+= -delta
/ 2 + 1;
1991 rect
.top
= rect
.bottom
- box_size
.cy
;
1997 rect
.bottom
-= delta
/ 2 + 1;
1998 rect
.top
= rect
.bottom
- box_size
.cy
;
2002 rect
.top
-= -delta
/ 2 + 1;
2003 rect
.bottom
= rect
.top
+ box_size
.cy
;
2010 static void CB_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
)
2012 RECT rbox
, labelRect
, oldLabelRect
, imageRect
, textRect
, client
;
2019 LONG state
= infoPtr
->state
;
2020 LONG style
= GetWindowLongW( infoPtr
->hwnd
, GWL_STYLE
);
2021 LONG ex_style
= GetWindowLongW( infoPtr
->hwnd
, GWL_EXSTYLE
);
2026 if (style
& BS_PUSHLIKE
)
2028 PB_Paint( infoPtr
, hDC
, action
);
2032 GetClientRect(infoPtr
->hwnd
, &client
);
2035 box_size
.cx
= 12 * GetDpiForWindow(infoPtr
->hwnd
) / 96 + 1;
2036 box_size
.cy
= box_size
.cx
;
2038 if ((hFont
= infoPtr
->font
)) SelectObject( hDC
, hFont
);
2039 GetCharWidthW( hDC
, '0', '0', &text_offset
);
2042 parent
= GetParent(infoPtr
->hwnd
);
2043 if (!parent
) parent
= infoPtr
->hwnd
;
2044 hBrush
= (HBRUSH
)SendMessageW(parent
, WM_CTLCOLORSTATIC
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
2045 if (!hBrush
) /* did the app forget to call defwindowproc ? */
2046 hBrush
= (HBRUSH
)DefWindowProcW(parent
, WM_CTLCOLORSTATIC
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
2047 hrgn
= set_control_clipping( hDC
, &client
);
2049 if (style
& BS_LEFTTEXT
|| ex_style
& WS_EX_RIGHT
)
2050 labelRect
.right
-= box_size
.cx
+ text_offset
;
2052 labelRect
.left
+= box_size
.cx
+ text_offset
;
2054 oldLabelRect
= labelRect
;
2055 dtFlags
= BUTTON_CalcLayoutRects(infoPtr
, hDC
, &labelRect
, &imageRect
, &textRect
);
2056 rbox
= get_box_rect(style
, ex_style
, &client
, &labelRect
, dtFlags
!= (UINT
)-1L, box_size
);
2058 init_custom_draw(&nmcd
, infoPtr
, hDC
, &client
);
2060 /* Send erase notifications */
2061 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2062 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
2064 /* Since WM_ERASEBKGND does nothing, first prepare background */
2065 if (action
== ODA_SELECT
) FillRect( hDC
, &rbox
, hBrush
);
2066 if (action
== ODA_DRAWENTIRE
) FillRect( hDC
, &client
, hBrush
);
2067 if (cdrf
& CDRF_NOTIFYPOSTERASE
)
2069 nmcd
.dwDrawStage
= CDDS_POSTERASE
;
2070 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2074 /* Send paint notifications */
2075 nmcd
.dwDrawStage
= CDDS_PREPAINT
;
2076 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2077 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
2079 /* Draw the check-box bitmap */
2080 if (!(cdrf
& CDRF_DOERASE
))
2082 if (action
== ODA_DRAWENTIRE
|| action
== ODA_SELECT
)
2086 if ((get_button_type(style
) == BS_RADIOBUTTON
) ||
2087 (get_button_type(style
) == BS_AUTORADIOBUTTON
)) flags
= DFCS_BUTTONRADIO
;
2088 else if (state
& BST_INDETERMINATE
) flags
= DFCS_BUTTON3STATE
;
2089 else flags
= DFCS_BUTTONCHECK
;
2091 if (state
& (BST_CHECKED
| BST_INDETERMINATE
)) flags
|= DFCS_CHECKED
;
2092 if (state
& BST_PUSHED
) flags
|= DFCS_PUSHED
;
2093 if (style
& WS_DISABLED
) flags
|= DFCS_INACTIVE
;
2095 DrawFrameControl(hDC
, &rbox
, DFC_BUTTON
, flags
);
2098 if (dtFlags
!= (UINT
)-1L) /* Something to draw */
2099 if (action
== ODA_DRAWENTIRE
) BUTTON_DrawLabel(infoPtr
, hDC
, dtFlags
, &imageRect
, &textRect
);
2102 if (cdrf
& CDRF_NOTIFYPOSTPAINT
)
2104 nmcd
.dwDrawStage
= CDDS_POSTPAINT
;
2105 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2107 if ((cdrf
& CDRF_SKIPPOSTPAINT
) || dtFlags
== (UINT
)-1L) goto cleanup
;
2110 if (action
== ODA_FOCUS
|| (state
& BST_FOCUS
))
2114 IntersectRect(&labelRect
, &labelRect
, &oldLabelRect
);
2115 DrawFocusRect(hDC
, &labelRect
);
2119 SelectClipRgn( hDC
, hrgn
);
2120 if (hrgn
) DeleteObject( hrgn
);
2124 /**********************************************************************
2125 * BUTTON_CheckAutoRadioButton
2127 * hwnd is checked, uncheck every other auto radio button in group
2129 static void BUTTON_CheckAutoRadioButton( HWND hwnd
)
2131 HWND parent
, sibling
, start
;
2133 parent
= GetParent(hwnd
);
2134 /* make sure that starting control is not disabled or invisible */
2135 start
= sibling
= GetNextDlgGroupItem( parent
, hwnd
, TRUE
);
2138 if (!sibling
) break;
2139 if ((hwnd
!= sibling
) &&
2140 ((GetWindowLongW( sibling
, GWL_STYLE
) & BS_TYPEMASK
) == BS_AUTORADIOBUTTON
))
2141 SendMessageW( sibling
, BM_SETCHECK
, BST_UNCHECKED
, 0 );
2142 sibling
= GetNextDlgGroupItem( parent
, sibling
, FALSE
);
2143 } while (sibling
!= start
);
2147 /**********************************************************************
2148 * Group Box Functions
2151 static void GB_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
)
2153 RECT labelRect
, imageRect
, textRect
, rcFrame
;
2158 LONG style
= GetWindowLongW( infoPtr
->hwnd
, GWL_STYLE
);
2162 if ((hFont
= infoPtr
->font
)) SelectObject( hDC
, hFont
);
2163 /* GroupBox acts like static control, so it sends CTLCOLORSTATIC */
2164 parent
= GetParent(infoPtr
->hwnd
);
2165 if (!parent
) parent
= infoPtr
->hwnd
;
2166 hbr
= (HBRUSH
)SendMessageW(parent
, WM_CTLCOLORSTATIC
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
2167 if (!hbr
) /* did the app forget to call defwindowproc ? */
2168 hbr
= (HBRUSH
)DefWindowProcW(parent
, WM_CTLCOLORSTATIC
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
2169 GetClientRect(infoPtr
->hwnd
, &labelRect
);
2170 rcFrame
= labelRect
;
2171 hrgn
= set_control_clipping(hDC
, &labelRect
);
2173 GetTextMetricsW (hDC
, &tm
);
2174 rcFrame
.top
+= (tm
.tmHeight
/ 2) - 1;
2175 DrawEdge (hDC
, &rcFrame
, EDGE_ETCHED
, BF_RECT
| ((style
& BS_FLAT
) ? BF_FLAT
: 0));
2177 InflateRect(&labelRect
, -7, 1);
2178 dtFlags
= BUTTON_CalcLayoutRects(infoPtr
, hDC
, &labelRect
, &imageRect
, &textRect
);
2180 if (dtFlags
!= (UINT
)-1)
2182 /* Because buttons have CS_PARENTDC class style, there is a chance
2183 * that label will be drawn out of client rect.
2184 * But Windows doesn't clip label's rect, so do I.
2187 /* There is 1-pixel margin at the left, right, and bottom */
2191 FillRect(hDC
, &labelRect
, hbr
);
2192 BUTTON_DrawLabel(infoPtr
, hDC
, dtFlags
, &imageRect
, &textRect
);
2194 SelectClipRgn( hDC
, hrgn
);
2195 if (hrgn
) DeleteObject( hrgn
);
2199 /**********************************************************************
2200 * User Button Functions
2203 static void UB_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
)
2210 LONG state
= infoPtr
->state
;
2213 GetClientRect( infoPtr
->hwnd
, &rc
);
2215 if ((hFont
= infoPtr
->font
)) SelectObject( hDC
, hFont
);
2217 parent
= GetParent(infoPtr
->hwnd
);
2218 if (!parent
) parent
= infoPtr
->hwnd
;
2219 hBrush
= (HBRUSH
)SendMessageW(parent
, WM_CTLCOLORBTN
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
2220 if (!hBrush
) /* did the app forget to call defwindowproc ? */
2221 hBrush
= (HBRUSH
)DefWindowProcW(parent
, WM_CTLCOLORBTN
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
2223 if (action
== ODA_FOCUS
|| (state
& BST_FOCUS
))
2225 init_custom_draw(&nmcd
, infoPtr
, hDC
, &rc
);
2227 /* Send erase notifications */
2228 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2229 if (cdrf
& CDRF_SKIPDEFAULT
) goto notify
;
2232 FillRect( hDC
, &rc
, hBrush
);
2233 if (action
== ODA_FOCUS
|| (state
& BST_FOCUS
))
2235 if (cdrf
& CDRF_NOTIFYPOSTERASE
)
2237 nmcd
.dwDrawStage
= CDDS_POSTERASE
;
2238 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2241 /* Send paint notifications */
2242 nmcd
.dwDrawStage
= CDDS_PREPAINT
;
2243 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2244 if (cdrf
& CDRF_SKIPDEFAULT
) goto notify
;
2245 if (cdrf
& CDRF_NOTIFYPOSTPAINT
)
2247 nmcd
.dwDrawStage
= CDDS_POSTPAINT
;
2248 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2251 if (!(cdrf
& CDRF_SKIPPOSTPAINT
))
2252 DrawFocusRect( hDC
, &rc
);
2259 BUTTON_NOTIFY_PARENT( infoPtr
->hwnd
, (state
& BST_FOCUS
) ? BN_SETFOCUS
: BN_KILLFOCUS
);
2263 BUTTON_NOTIFY_PARENT( infoPtr
->hwnd
, (state
& BST_PUSHED
) ? BN_HILITE
: BN_UNHILITE
);
2272 /**********************************************************************
2273 * Ownerdrawn Button Functions
2276 static void OB_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
)
2278 LONG state
= infoPtr
->state
;
2280 LONG_PTR id
= GetWindowLongPtrW( infoPtr
->hwnd
, GWLP_ID
);
2285 dis
.CtlType
= ODT_BUTTON
;
2288 dis
.itemAction
= action
;
2289 dis
.itemState
= ((state
& BST_FOCUS
) ? ODS_FOCUS
: 0) |
2290 ((state
& BST_PUSHED
) ? ODS_SELECTED
: 0) |
2291 (IsWindowEnabled(infoPtr
->hwnd
) ? 0: ODS_DISABLED
);
2292 dis
.hwndItem
= infoPtr
->hwnd
;
2295 GetClientRect( infoPtr
->hwnd
, &dis
.rcItem
);
2297 if ((hFont
= infoPtr
->font
)) SelectObject( hDC
, hFont
);
2298 parent
= GetParent(infoPtr
->hwnd
);
2299 if (!parent
) parent
= infoPtr
->hwnd
;
2300 SendMessageW( parent
, WM_CTLCOLORBTN
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
2302 hrgn
= set_control_clipping( hDC
, &dis
.rcItem
);
2304 SendMessageW( GetParent(infoPtr
->hwnd
), WM_DRAWITEM
, id
, (LPARAM
)&dis
);
2305 SelectClipRgn( hDC
, hrgn
);
2306 if (hrgn
) DeleteObject( hrgn
);
2310 /**********************************************************************
2311 * Split Button Functions
2313 static void SB_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
)
2315 LONG style
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
2316 LONG state
= infoPtr
->state
;
2317 UINT dtFlags
= (UINT
)-1L;
2319 RECT rc
, push_rect
, dropdown_rect
;
2328 GetClientRect(infoPtr
->hwnd
, &rc
);
2330 /* Send WM_CTLCOLOR to allow changing the font (the colors are fixed) */
2331 if (infoPtr
->font
) SelectObject(hDC
, infoPtr
->font
);
2332 if (!(parent
= GetParent(infoPtr
->hwnd
))) parent
= infoPtr
->hwnd
;
2333 SendMessageW(parent
, WM_CTLCOLORBTN
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
2335 hrgn
= set_control_clipping(hDC
, &rc
);
2337 pen
= CreatePen(PS_SOLID
, 1, GetSysColor(COLOR_WINDOWFRAME
));
2338 old_pen
= SelectObject(hDC
, pen
);
2339 old_brush
= SelectObject(hDC
, GetSysColorBrush(COLOR_BTNFACE
));
2340 old_bk_mode
= SetBkMode(hDC
, TRANSPARENT
);
2342 init_custom_draw(&nmcd
, infoPtr
, hDC
, &rc
);
2344 /* Send erase notifications */
2345 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2346 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
2348 if (get_button_type(style
) == BS_DEFSPLITBUTTON
)
2350 if (action
!= ODA_FOCUS
)
2351 Rectangle(hDC
, rc
.left
, rc
.top
, rc
.right
, rc
.bottom
);
2352 InflateRect(&rc
, -1, -1);
2353 /* The split will now be off by 1 pixel, but
2354 that's exactly what Windows does as well */
2357 get_split_button_rects(infoPtr
, &rc
, &push_rect
, &dropdown_rect
);
2358 if (infoPtr
->split_style
& BCSS_NOSPLIT
)
2361 /* Skip the frame drawing if only focus has changed */
2362 if (action
!= ODA_FOCUS
)
2364 UINT flags
= DFCS_BUTTONPUSH
;
2366 if (style
& BS_FLAT
) flags
|= DFCS_MONO
;
2367 else if (state
& BST_PUSHED
)
2368 flags
|= (get_button_type(style
) == BS_DEFSPLITBUTTON
)
2369 ? DFCS_FLAT
: DFCS_PUSHED
;
2371 if (state
& (BST_CHECKED
| BST_INDETERMINATE
))
2372 flags
|= DFCS_CHECKED
;
2374 if (infoPtr
->split_style
& BCSS_NOSPLIT
)
2375 DrawFrameControl(hDC
, &push_rect
, DFC_BUTTON
, flags
);
2378 UINT dropdown_flags
= flags
& ~DFCS_CHECKED
;
2380 if (state
& BST_DROPDOWNPUSHED
)
2381 dropdown_flags
= (dropdown_flags
& ~DFCS_FLAT
) | DFCS_PUSHED
;
2383 /* Adjust for shadow and draw order so it looks properly */
2384 if (infoPtr
->split_style
& BCSS_ALIGNLEFT
)
2386 dropdown_rect
.right
++;
2387 DrawFrameControl(hDC
, &dropdown_rect
, DFC_BUTTON
, dropdown_flags
);
2388 dropdown_rect
.right
--;
2389 DrawFrameControl(hDC
, &push_rect
, DFC_BUTTON
, flags
);
2394 DrawFrameControl(hDC
, &push_rect
, DFC_BUTTON
, flags
);
2396 DrawFrameControl(hDC
, &dropdown_rect
, DFC_BUTTON
, dropdown_flags
);
2401 if (cdrf
& CDRF_NOTIFYPOSTERASE
)
2403 nmcd
.dwDrawStage
= CDDS_POSTERASE
;
2404 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2407 /* Send paint notifications */
2408 nmcd
.dwDrawStage
= CDDS_PREPAINT
;
2409 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2410 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
2412 /* Shrink push button rect so that the content won't touch the surrounding frame */
2413 InflateRect(&push_rect
, -2, -2);
2415 if (!(cdrf
& CDRF_DOERASE
) && action
!= ODA_FOCUS
)
2417 COLORREF old_color
= SetTextColor(hDC
, GetSysColor(COLOR_BTNTEXT
));
2418 RECT label_rect
= push_rect
, image_rect
, text_rect
;
2420 dtFlags
= BUTTON_CalcLayoutRects(infoPtr
, hDC
, &label_rect
, &image_rect
, &text_rect
);
2422 if (dtFlags
!= (UINT
)-1L)
2423 BUTTON_DrawLabel(infoPtr
, hDC
, dtFlags
, &image_rect
, &text_rect
);
2425 draw_split_button_dropdown_glyph(infoPtr
, hDC
, &dropdown_rect
);
2426 SetTextColor(hDC
, old_color
);
2429 if (cdrf
& CDRF_NOTIFYPOSTPAINT
)
2431 nmcd
.dwDrawStage
= CDDS_POSTPAINT
;
2432 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2434 if ((cdrf
& CDRF_SKIPPOSTPAINT
) || dtFlags
== (UINT
)-1L) goto cleanup
;
2436 if (action
== ODA_FOCUS
|| (state
& BST_FOCUS
))
2437 DrawFocusRect(hDC
, &push_rect
);
2440 SelectObject(hDC
, old_pen
);
2441 SelectObject(hDC
, old_brush
);
2442 SetBkMode(hDC
, old_bk_mode
);
2443 SelectClipRgn(hDC
, hrgn
);
2444 if (hrgn
) DeleteObject(hrgn
);
2448 /* Given the full button rect of the split button, retrieve the push part and the dropdown part */
2449 static inline void get_split_button_rects(const BUTTON_INFO
*infoPtr
, const RECT
*button_rect
,
2450 RECT
*push_rect
, RECT
*dropdown_rect
)
2452 *push_rect
= *dropdown_rect
= *button_rect
;
2454 /* The dropdown takes priority if the client rect is too small, it will only have a dropdown */
2455 if (infoPtr
->split_style
& BCSS_ALIGNLEFT
)
2457 dropdown_rect
->right
= min(button_rect
->left
+ infoPtr
->glyph_size
.cx
, button_rect
->right
);
2458 push_rect
->left
= dropdown_rect
->right
;
2462 dropdown_rect
->left
= max(button_rect
->right
- infoPtr
->glyph_size
.cx
, button_rect
->left
);
2463 push_rect
->right
= dropdown_rect
->left
;
2467 /* Notify the parent if the point is within the dropdown and return TRUE (always notify if NULL) */
2468 static BOOL
notify_split_button_dropdown(const BUTTON_INFO
*infoPtr
, const POINT
*pt
, HWND hwnd
)
2472 GetClientRect(hwnd
, &nmbcd
.rcButton
);
2475 RECT push_rect
, dropdown_rect
;
2477 get_split_button_rects(infoPtr
, &nmbcd
.rcButton
, &push_rect
, &dropdown_rect
);
2478 if (!PtInRect(&dropdown_rect
, *pt
))
2481 /* If it's already down (set manually via BCM_SETDROPDOWNSTATE), fake the notify */
2482 if (infoPtr
->state
& BST_DROPDOWNPUSHED
)
2485 SendMessageW(hwnd
, BCM_SETDROPDOWNSTATE
, TRUE
, 0);
2487 nmbcd
.hdr
.hwndFrom
= hwnd
;
2488 nmbcd
.hdr
.idFrom
= GetWindowLongPtrW(hwnd
, GWLP_ID
);
2489 nmbcd
.hdr
.code
= BCN_DROPDOWN
;
2490 SendMessageW(GetParent(hwnd
), WM_NOTIFY
, nmbcd
.hdr
.idFrom
, (LPARAM
)&nmbcd
);
2492 SendMessageW(hwnd
, BCM_SETDROPDOWNSTATE
, FALSE
, 0);
2496 /* Draw the split button dropdown glyph or image */
2497 static void draw_split_button_dropdown_glyph(const BUTTON_INFO
*infoPtr
, HDC hdc
, RECT
*rect
)
2499 if (infoPtr
->split_style
& BCSS_IMAGE
)
2503 /* When the glyph is an image list, Windows is very buggy with BCSS_STRETCH,
2504 positions it weirdly and doesn't even stretch it, but instead extends the
2505 image, leaking into other images in the list (or black if none). Instead,
2506 we'll ignore this and just position it at center as without BCSS_STRETCH. */
2507 if (!ImageList_GetIconSize(infoPtr
->glyph
, &w
, &h
)) return;
2509 ImageList_Draw(infoPtr
->glyph
,
2510 (ImageList_GetImageCount(infoPtr
->glyph
) == 1) ? 0 : get_draw_state(infoPtr
) - 1,
2511 hdc
, rect
->left
+ (rect
->right
- rect
->left
- w
) / 2,
2512 rect
->top
+ (rect
->bottom
- rect
->top
- h
) / 2, ILD_NORMAL
);
2514 else if (infoPtr
->glyph_size
.cy
>= 0)
2516 /* infoPtr->glyph is a character code from Marlett */
2517 HFONT font
, old_font
;
2518 LOGFONTW logfont
= { 0, 0, 0, 0, FW_NORMAL
, 0, 0, 0, SYMBOL_CHARSET
, 0, 0, 0, 0,
2520 if (infoPtr
->glyph_size
.cy
)
2522 /* BCSS_STRETCH preserves aspect ratio, uses minimum as size */
2523 if (infoPtr
->split_style
& BCSS_STRETCH
)
2524 logfont
.lfHeight
= min(infoPtr
->glyph_size
.cx
, infoPtr
->glyph_size
.cy
);
2527 logfont
.lfWidth
= infoPtr
->glyph_size
.cx
;
2528 logfont
.lfHeight
= infoPtr
->glyph_size
.cy
;
2531 else logfont
.lfHeight
= infoPtr
->glyph_size
.cx
;
2533 if ((font
= CreateFontIndirectW(&logfont
)))
2535 old_font
= SelectObject(hdc
, font
);
2536 DrawTextW(hdc
, (const WCHAR
*)&infoPtr
->glyph
, 1, rect
,
2537 DT_CENTER
| DT_VCENTER
| DT_SINGLELINE
| DT_NOCLIP
| DT_NOPREFIX
);
2538 SelectObject(hdc
, old_font
);
2545 /**********************************************************************
2546 * Command Link Functions
2548 static void CL_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
)
2550 LONG style
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
2551 LONG state
= infoPtr
->state
;
2553 RECT rc
, content_rect
;
2562 GetClientRect(infoPtr
->hwnd
, &rc
);
2564 /* Command Links are not affected by the button's font, and are based
2565 on the default message font. Furthermore, they are not affected by
2566 any of the alignment styles (and always align with the top-left). */
2567 if (!(parent
= GetParent(infoPtr
->hwnd
))) parent
= infoPtr
->hwnd
;
2568 SendMessageW(parent
, WM_CTLCOLORBTN
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
2570 hrgn
= set_control_clipping(hDC
, &rc
);
2572 pen
= CreatePen(PS_SOLID
, 1, GetSysColor(COLOR_WINDOWFRAME
));
2573 old_pen
= SelectObject(hDC
, pen
);
2574 old_brush
= SelectObject(hDC
, GetSysColorBrush(COLOR_BTNFACE
));
2575 old_bk_mode
= SetBkMode(hDC
, TRANSPARENT
);
2577 init_custom_draw(&nmcd
, infoPtr
, hDC
, &rc
);
2579 /* Send erase notifications */
2580 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2581 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
2584 if (get_button_type(style
) == BS_DEFCOMMANDLINK
)
2586 if (action
!= ODA_FOCUS
)
2587 Rectangle(hDC
, rc
.left
, rc
.top
, rc
.right
, rc
.bottom
);
2588 InflateRect(&rc
, -1, -1);
2591 /* Skip the frame drawing if only focus has changed */
2592 if (action
!= ODA_FOCUS
)
2594 if (!(state
& (BST_HOT
| BST_PUSHED
| BST_CHECKED
| BST_INDETERMINATE
)))
2595 FillRect(hDC
, &rc
, GetSysColorBrush(COLOR_BTNFACE
));
2598 UINT flags
= DFCS_BUTTONPUSH
;
2600 if (style
& BS_FLAT
) flags
|= DFCS_MONO
;
2601 else if (state
& BST_PUSHED
) flags
|= DFCS_PUSHED
;
2603 if (state
& (BST_CHECKED
| BST_INDETERMINATE
))
2604 flags
|= DFCS_CHECKED
;
2605 DrawFrameControl(hDC
, &rc
, DFC_BUTTON
, flags
);
2609 if (cdrf
& CDRF_NOTIFYPOSTERASE
)
2611 nmcd
.dwDrawStage
= CDDS_POSTERASE
;
2612 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2615 /* Send paint notifications */
2616 nmcd
.dwDrawStage
= CDDS_PREPAINT
;
2617 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2618 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
2620 if (!(cdrf
& CDRF_DOERASE
) && action
!= ODA_FOCUS
)
2622 UINT flags
= IsWindowEnabled(infoPtr
->hwnd
) ? DSS_NORMAL
: DSS_DISABLED
;
2623 COLORREF old_color
= SetTextColor(hDC
, GetSysColor(flags
== DSS_NORMAL
?
2624 COLOR_BTNTEXT
: COLOR_GRAYTEXT
));
2625 HIMAGELIST defimg
= NULL
;
2626 NONCLIENTMETRICSW ncm
;
2630 /* Command Links ignore the margins of the image list or its alignment */
2631 if (infoPtr
->u
.image
|| infoPtr
->imagelist
.himl
)
2632 img_size
= BUTTON_GetImageSize(infoPtr
);
2635 img_size
.cx
= img_size
.cy
= command_link_defglyph_size
;
2636 defimg
= ImageList_LoadImageW(COMCTL32_hModule
, (LPCWSTR
)MAKEINTRESOURCE(IDB_CMDLINK
),
2637 img_size
.cx
, 3, CLR_NONE
, IMAGE_BITMAP
, LR_CREATEDIBSECTION
);
2640 /* Shrink rect by the command link margin, except on bottom (just the frame) */
2641 InflateRect(&content_rect
, -command_link_margin
, -command_link_margin
);
2642 content_rect
.bottom
+= command_link_margin
- 2;
2644 ncm
.cbSize
= sizeof(ncm
);
2645 if (SystemParametersInfoW(SPI_GETNONCLIENTMETRICS
, sizeof(ncm
), &ncm
, 0))
2647 LONG note_weight
= ncm
.lfMessageFont
.lfWeight
;
2648 RECT r
= content_rect
;
2652 if (img_size
.cx
) r
.left
+= img_size
.cx
+ command_link_margin
;
2655 ncm
.lfMessageFont
.lfWeight
= FW_BOLD
;
2656 if ((font
= CreateFontIndirectW(&ncm
.lfMessageFont
)))
2658 if ((text
= get_button_text(infoPtr
)))
2660 SelectObject(hDC
, font
);
2661 txt_h
= DrawTextW(hDC
, text
, -1, &r
,
2662 DT_TOP
| DT_LEFT
| DT_WORDBREAK
| DT_END_ELLIPSIS
);
2669 ncm
.lfMessageFont
.lfWeight
= note_weight
;
2670 if (infoPtr
->note
&& (font
= CreateFontIndirectW(&ncm
.lfMessageFont
)))
2673 SelectObject(hDC
, font
);
2674 DrawTextW(hDC
, infoPtr
->note
, infoPtr
->note_length
, &r
,
2675 DT_TOP
| DT_LEFT
| DT_WORDBREAK
| DT_NOPREFIX
);
2680 /* Position the image at the vertical center of the drawn text (not note) */
2681 txt_h
= min(txt_h
, content_rect
.bottom
- content_rect
.top
);
2682 if (img_size
.cy
< txt_h
) content_rect
.top
+= (txt_h
- img_size
.cy
) / 2;
2684 content_rect
.right
= content_rect
.left
+ img_size
.cx
;
2685 content_rect
.bottom
= content_rect
.top
+ img_size
.cy
;
2690 if (flags
== DSS_DISABLED
) i
= 2;
2691 else if (state
& BST_HOT
) i
= 1;
2693 ImageList_Draw(defimg
, i
, hDC
, content_rect
.left
, content_rect
.top
, ILD_NORMAL
);
2694 ImageList_Destroy(defimg
);
2697 BUTTON_DrawImage(infoPtr
, hDC
, NULL
, flags
, &content_rect
);
2699 SetTextColor(hDC
, old_color
);
2702 if (cdrf
& CDRF_NOTIFYPOSTPAINT
)
2704 nmcd
.dwDrawStage
= CDDS_POSTPAINT
;
2705 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2707 if (cdrf
& CDRF_SKIPPOSTPAINT
) goto cleanup
;
2709 if (action
== ODA_FOCUS
|| (state
& BST_FOCUS
))
2711 InflateRect(&rc
, -2, -2);
2712 DrawFocusRect(hDC
, &rc
);
2716 SelectObject(hDC
, old_pen
);
2717 SelectObject(hDC
, old_brush
);
2718 SetBkMode(hDC
, old_bk_mode
);
2719 SelectClipRgn(hDC
, hrgn
);
2720 if (hrgn
) DeleteObject(hrgn
);
2725 /**********************************************************************
2726 * Themed Paint Functions
2728 static void PB_ThemedPaint(HTHEME theme
, const BUTTON_INFO
*infoPtr
, HDC hDC
, int state
, UINT dtFlags
, BOOL focused
)
2730 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
))
2751 DrawThemeParentBackground(infoPtr
->hwnd
, hDC
, NULL
);
2752 DrawThemeBackground(theme
, hDC
, BP_PUSHBUTTON
, state
, &bgRect
, NULL
);
2754 if (cdrf
& CDRF_NOTIFYPOSTERASE
)
2756 nmcd
.dwDrawStage
= CDDS_POSTERASE
;
2757 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2760 /* Send paint notifications */
2761 nmcd
.dwDrawStage
= CDDS_PREPAINT
;
2762 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2763 if (cdrf
& CDRF_SKIPDEFAULT
) return;
2765 if (!(cdrf
& CDRF_DOERASE
))
2767 dtFlags
= BUTTON_CalcLayoutRects(infoPtr
, hDC
, &labelRect
, &imageRect
, &textRect
);
2768 if (dtFlags
!= (UINT
)-1L)
2769 BUTTON_DrawThemedLabel(infoPtr
, hDC
, dtFlags
, &imageRect
, &textRect
, theme
,
2770 BP_PUSHBUTTON
, state
);
2773 if (cdrf
& CDRF_NOTIFYPOSTPAINT
)
2775 nmcd
.dwDrawStage
= CDDS_POSTPAINT
;
2776 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2778 if (cdrf
& CDRF_SKIPPOSTPAINT
) return;
2780 if (focused
) DrawFocusRect(hDC
, &focusRect
);
2783 static void CB_ThemedPaint(HTHEME theme
, const BUTTON_INFO
*infoPtr
, HDC hDC
, int state
, UINT dtFlags
, BOOL focused
)
2785 RECT client_rect
, content_rect
, old_label_rect
, label_rect
, box_rect
, image_rect
, text_rect
;
2786 HFONT font
, hPrevFont
= NULL
;
2787 DWORD dwStyle
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
2788 LONG ex_style
= GetWindowLongW(infoPtr
->hwnd
, GWL_EXSTYLE
);
2789 UINT btn_type
= get_button_type( dwStyle
);
2790 int part
= (btn_type
== BS_RADIOBUTTON
) || (btn_type
== BS_AUTORADIOBUTTON
) ? BP_RADIOBUTTON
: BP_CHECKBOX
;
2795 BOOL created_font
= FALSE
;
2801 if (dwStyle
& BS_PUSHLIKE
)
2803 PB_ThemedPaint(theme
, infoPtr
, hDC
, state
, dtFlags
, focused
);
2807 hr
= GetThemeFont(theme
, hDC
, part
, state
, TMT_FONT
, &lf
);
2808 if (SUCCEEDED(hr
)) {
2809 font
= CreateFontIndirectW(&lf
);
2811 TRACE("Failed to create font\n");
2813 TRACE("font = %s\n", debugstr_w(lf
.lfFaceName
));
2814 hPrevFont
= SelectObject(hDC
, font
);
2815 created_font
= TRUE
;
2818 if (infoPtr
->font
) SelectObject(hDC
, infoPtr
->font
);
2821 GetClientRect(infoPtr
->hwnd
, &client_rect
);
2822 GetThemeBackgroundContentRect(theme
, hDC
, part
, state
, &client_rect
, &content_rect
);
2823 region
= set_control_clipping(hDC
, &client_rect
);
2825 if (FAILED(GetThemePartSize(theme
, hDC
, part
, state
, NULL
, TS_DRAW
, &box_size
)))
2827 box_size
.cx
= 12 * GetDpiForWindow(infoPtr
->hwnd
) / 96 + 1;
2828 box_size
.cy
= box_size
.cx
;
2831 GetCharWidthW(hDC
, '0', '0', &text_offset
);
2834 label_rect
= content_rect
;
2835 if (dwStyle
& BS_LEFTTEXT
|| ex_style
& WS_EX_RIGHT
)
2836 label_rect
.right
-= box_size
.cx
+ text_offset
;
2838 label_rect
.left
+= box_size
.cx
+ text_offset
;
2840 old_label_rect
= label_rect
;
2841 dtFlags
= BUTTON_CalcLayoutRects(infoPtr
, hDC
, &label_rect
, &image_rect
, &text_rect
);
2842 box_rect
= get_box_rect(dwStyle
, ex_style
, &content_rect
, &label_rect
, dtFlags
!= (UINT
)-1L,
2845 init_custom_draw(&nmcd
, infoPtr
, hDC
, &client_rect
);
2847 parent
= GetParent(infoPtr
->hwnd
);
2848 if (!parent
) parent
= infoPtr
->hwnd
;
2850 /* Send erase notifications */
2851 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2852 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
2854 DrawThemeParentBackground(infoPtr
->hwnd
, hDC
, NULL
);
2856 if (cdrf
& CDRF_NOTIFYPOSTERASE
)
2858 nmcd
.dwDrawStage
= CDDS_POSTERASE
;
2859 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2862 /* Send paint notifications */
2863 nmcd
.dwDrawStage
= CDDS_PREPAINT
;
2864 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2865 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
2868 if (!(cdrf
& CDRF_DOERASE
))
2870 DrawThemeBackground(theme
, hDC
, part
, state
, &box_rect
, NULL
);
2871 if (dtFlags
!= (UINT
)-1L)
2872 BUTTON_DrawThemedLabel(infoPtr
, hDC
, dtFlags
, &image_rect
, &text_rect
, theme
, part
, state
);
2875 if (cdrf
& CDRF_NOTIFYPOSTPAINT
)
2877 nmcd
.dwDrawStage
= CDDS_POSTPAINT
;
2878 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2880 if ((cdrf
& CDRF_SKIPPOSTPAINT
) || dtFlags
== (UINT
)-1L) goto cleanup
;
2886 IntersectRect(&label_rect
, &label_rect
, &old_label_rect
);
2887 DrawFocusRect(hDC
, &label_rect
);
2891 SelectClipRgn(hDC
, region
);
2892 if (region
) DeleteObject(region
);
2893 if (created_font
) DeleteObject(font
);
2894 if (hPrevFont
) SelectObject(hDC
, hPrevFont
);
2897 static void GB_ThemedPaint(HTHEME theme
, const BUTTON_INFO
*infoPtr
, HDC hDC
, int state
, UINT dtFlags
, BOOL focused
)
2899 RECT clientRect
, contentRect
, labelRect
, imageRect
, textRect
, bgRect
;
2900 HRGN region
, textRegion
= NULL
;
2902 HFONT font
, hPrevFont
= NULL
;
2903 BOOL created_font
= FALSE
;
2904 TEXTMETRICW textMetric
;
2908 HRESULT hr
= GetThemeFont(theme
, hDC
, BP_GROUPBOX
, state
, TMT_FONT
, &lf
);
2909 if (SUCCEEDED(hr
)) {
2910 font
= CreateFontIndirectW(&lf
);
2912 TRACE("Failed to create font\n");
2914 hPrevFont
= SelectObject(hDC
, font
);
2915 created_font
= TRUE
;
2919 SelectObject(hDC
, infoPtr
->font
);
2922 GetClientRect(infoPtr
->hwnd
, &clientRect
);
2923 region
= set_control_clipping(hDC
, &clientRect
);
2925 bgRect
= clientRect
;
2926 GetTextMetricsW(hDC
, &textMetric
);
2927 bgRect
.top
+= (textMetric
.tmHeight
/ 2) - 1;
2929 labelRect
= clientRect
;
2930 InflateRect(&labelRect
, -7, 1);
2931 dtFlags
= BUTTON_CalcLayoutRects(infoPtr
, hDC
, &labelRect
, &imageRect
, &textRect
);
2932 if (dtFlags
!= (UINT
)-1 && !show_image_only(infoPtr
))
2934 textRegion
= CreateRectRgnIndirect(&textRect
);
2935 ExtSelectClipRgn(hDC
, textRegion
, RGN_DIFF
);
2938 style
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
2939 if (style
& BS_PUSHLIKE
)
2941 part
= BP_PUSHBUTTON
;
2946 GetThemeBackgroundContentRect(theme
, hDC
, part
, state
, &bgRect
, &contentRect
);
2947 ExcludeClipRect(hDC
, contentRect
.left
, contentRect
.top
, contentRect
.right
, contentRect
.bottom
);
2949 if (IsThemeBackgroundPartiallyTransparent(theme
, part
, state
))
2950 DrawThemeParentBackground(infoPtr
->hwnd
, hDC
, NULL
);
2951 DrawThemeBackground(theme
, hDC
, part
, state
, &bgRect
, NULL
);
2953 if (dtFlags
!= (UINT
)-1)
2957 SelectClipRgn(hDC
, textRegion
);
2958 DeleteObject(textRegion
);
2960 BUTTON_DrawThemedLabel(infoPtr
, hDC
, dtFlags
, &imageRect
, &textRect
, theme
, part
, state
);
2963 SelectClipRgn(hDC
, region
);
2964 if (region
) DeleteObject(region
);
2965 if (created_font
) DeleteObject(font
);
2966 if (hPrevFont
) SelectObject(hDC
, hPrevFont
);
2969 static void SB_ThemedPaint(HTHEME theme
, const BUTTON_INFO
*infoPtr
, HDC hDC
, int state
, UINT dtFlags
, BOOL focused
)
2971 RECT rc
, content_rect
, push_rect
, dropdown_rect
, focus_rect
, label_rect
, image_rect
, text_rect
;
2976 if (infoPtr
->font
) SelectObject(hDC
, infoPtr
->font
);
2978 GetClientRect(infoPtr
->hwnd
, &rc
);
2979 init_custom_draw(&nmcd
, infoPtr
, hDC
, &rc
);
2981 parent
= GetParent(infoPtr
->hwnd
);
2982 if (!parent
) parent
= infoPtr
->hwnd
;
2984 /* Send erase notifications */
2985 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2986 if (cdrf
& CDRF_SKIPDEFAULT
) return;
2988 if (IsThemeBackgroundPartiallyTransparent(theme
, BP_PUSHBUTTON
, state
))
2989 DrawThemeParentBackground(infoPtr
->hwnd
, hDC
, NULL
);
2991 /* The zone outside the content is ignored for the dropdown (draws over) */
2992 GetThemeBackgroundContentRect(theme
, hDC
, BP_PUSHBUTTON
, state
, &rc
, &content_rect
);
2993 get_split_button_rects(infoPtr
, &rc
, &push_rect
, &dropdown_rect
);
2995 if (infoPtr
->split_style
& BCSS_NOSPLIT
)
2998 DrawThemeBackground(theme
, hDC
, BP_PUSHBUTTON
, state
, &rc
, NULL
);
2999 GetThemeBackgroundContentRect(theme
, hDC
, BP_PUSHBUTTON
, state
, &push_rect
, &focus_rect
);
3003 RECT r
= { dropdown_rect
.left
, content_rect
.top
, dropdown_rect
.right
, content_rect
.bottom
};
3004 UINT edge
= (infoPtr
->split_style
& BCSS_ALIGNLEFT
) ? BF_RIGHT
: BF_LEFT
;
3005 const RECT
*clip
= NULL
;
3007 /* If only the dropdown is pressed, we need to draw it separately */
3008 if (state
!= PBS_PRESSED
&& (infoPtr
->state
& BST_DROPDOWNPUSHED
))
3010 DrawThemeBackground(theme
, hDC
, BP_PUSHBUTTON
, PBS_PRESSED
, &rc
, &dropdown_rect
);
3013 DrawThemeBackground(theme
, hDC
, BP_PUSHBUTTON
, state
, &rc
, clip
);
3015 /* Draw the separator */
3016 DrawThemeEdge(theme
, hDC
, BP_PUSHBUTTON
, state
, &r
, EDGE_ETCHED
, edge
, NULL
);
3018 /* The content rect should be the content area of the push button */
3019 GetThemeBackgroundContentRect(theme
, hDC
, BP_PUSHBUTTON
, state
, &push_rect
, &content_rect
);
3020 focus_rect
= content_rect
;
3023 if (cdrf
& CDRF_NOTIFYPOSTERASE
)
3025 nmcd
.dwDrawStage
= CDDS_POSTERASE
;
3026 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
3029 /* Send paint notifications */
3030 nmcd
.dwDrawStage
= CDDS_PREPAINT
;
3031 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
3032 if (cdrf
& CDRF_SKIPDEFAULT
) return;
3034 if (!(cdrf
& CDRF_DOERASE
))
3036 COLORREF old_color
, color
;
3039 label_rect
= content_rect
;
3040 dtFlags
= BUTTON_CalcLayoutRects(infoPtr
, hDC
, &label_rect
, &image_rect
, &text_rect
);
3041 if (dtFlags
!= (UINT
)-1L)
3042 BUTTON_DrawThemedLabel(infoPtr
, hDC
, dtFlags
, &image_rect
, &text_rect
, theme
,
3043 BP_PUSHBUTTON
, state
);
3045 GetThemeColor(theme
, BP_PUSHBUTTON
, state
, TMT_TEXTCOLOR
, &color
);
3046 old_bk_mode
= SetBkMode(hDC
, TRANSPARENT
);
3047 old_color
= SetTextColor(hDC
, color
);
3049 draw_split_button_dropdown_glyph(infoPtr
, hDC
, &dropdown_rect
);
3051 SetTextColor(hDC
, old_color
);
3052 SetBkMode(hDC
, old_bk_mode
);
3055 if (cdrf
& CDRF_NOTIFYPOSTPAINT
)
3057 nmcd
.dwDrawStage
= CDDS_POSTPAINT
;
3058 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
3060 if (cdrf
& CDRF_SKIPPOSTPAINT
) return;
3062 if (focused
) DrawFocusRect(hDC
, &focus_rect
);
3065 static void CL_ThemedPaint(HTHEME theme
, const BUTTON_INFO
*infoPtr
, HDC hDC
, int state
, UINT dtFlags
, BOOL focused
)
3073 if (infoPtr
->font
) SelectObject(hDC
, infoPtr
->font
);
3075 GetClientRect(infoPtr
->hwnd
, &rc
);
3076 init_custom_draw(&nmcd
, infoPtr
, hDC
, &rc
);
3078 parent
= GetParent(infoPtr
->hwnd
);
3079 if (!parent
) parent
= infoPtr
->hwnd
;
3081 /* Send erase notifications */
3082 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
3083 if (cdrf
& CDRF_SKIPDEFAULT
) return;
3085 part
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
) & BS_PUSHLIKE
? BP_PUSHBUTTON
: BP_COMMANDLINK
;
3086 if (IsThemeBackgroundPartiallyTransparent(theme
, part
, state
))
3087 DrawThemeParentBackground(infoPtr
->hwnd
, hDC
, NULL
);
3088 DrawThemeBackground(theme
, hDC
, part
, state
, &rc
, NULL
);
3090 if (cdrf
& CDRF_NOTIFYPOSTERASE
)
3092 nmcd
.dwDrawStage
= CDDS_POSTERASE
;
3093 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
3096 /* Send paint notifications */
3097 nmcd
.dwDrawStage
= CDDS_PREPAINT
;
3098 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
3099 if (cdrf
& CDRF_SKIPDEFAULT
) return;
3101 if (!(cdrf
& CDRF_DOERASE
))
3108 GetThemeBackgroundContentRect(theme
, hDC
, part
, state
, &rc
, &r
);
3110 /* The text alignment and styles are fixed and don't depend on button styles */
3111 dtFlags
= DT_TOP
| DT_LEFT
| DT_WORDBREAK
;
3113 /* Command Links ignore the margins of the image list or its alignment */
3114 if (infoPtr
->u
.image
|| infoPtr
->imagelist
.himl
)
3115 img_size
= BUTTON_GetImageSize(infoPtr
);
3117 GetThemePartSize(theme
, NULL
, BP_COMMANDLINKGLYPH
, state
, NULL
, TS_DRAW
, &img_size
);
3120 if (img_size
.cx
) r
.left
+= img_size
.cx
+ command_link_margin
;
3123 if ((text
= get_button_text(infoPtr
)))
3125 UINT len
= lstrlenW(text
);
3128 GetThemeTextExtent(theme
, hDC
, part
, state
, text
, len
, dtFlags
| DT_END_ELLIPSIS
, &r
,
3130 DrawThemeText(theme
, hDC
, part
, state
, text
, len
, dtFlags
| DT_END_ELLIPSIS
, 0, &r
);
3132 txt_h
= text_rect
.bottom
- text_rect
.top
;
3142 opts
.dwSize
= sizeof(opts
);
3143 opts
.dwFlags
= DTT_FONTPROP
;
3144 opts
.iFontPropId
= TMT_BODYFONT
;
3145 DrawThemeTextEx(theme
, hDC
, part
, state
, infoPtr
->note
, infoPtr
->note_length
,
3146 dtFlags
| DT_NOPREFIX
, &r
, &opts
);
3149 /* Position the image at the vertical center of the drawn text (not note) */
3150 txt_h
= min(txt_h
, img_rect
.bottom
- img_rect
.top
);
3151 if (img_size
.cy
< txt_h
) img_rect
.top
+= (txt_h
- img_size
.cy
) / 2;
3153 img_rect
.right
= img_rect
.left
+ img_size
.cx
;
3154 img_rect
.bottom
= img_rect
.top
+ img_size
.cy
;
3156 if (infoPtr
->u
.image
|| infoPtr
->imagelist
.himl
)
3157 BUTTON_DrawImage(infoPtr
, hDC
, NULL
,
3158 (state
== CMDLS_DISABLED
) ? DSS_DISABLED
: DSS_NORMAL
,
3161 DrawThemeBackground(theme
, hDC
, BP_COMMANDLINKGLYPH
, state
, &img_rect
, NULL
);
3164 if (cdrf
& CDRF_NOTIFYPOSTPAINT
)
3166 nmcd
.dwDrawStage
= CDDS_POSTPAINT
;
3167 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
3169 if (cdrf
& CDRF_SKIPPOSTPAINT
) return;
3175 /* The focus rect has margins of a push button rather than command link... */
3176 GetThemeMargins(theme
, hDC
, BP_PUSHBUTTON
, state
, TMT_CONTENTMARGINS
, NULL
, &margins
);
3178 rc
.left
+= margins
.cxLeftWidth
;
3179 rc
.top
+= margins
.cyTopHeight
;
3180 rc
.right
-= margins
.cxRightWidth
;
3181 rc
.bottom
-= margins
.cyBottomHeight
;
3182 DrawFocusRect(hDC
, &rc
);
3186 void BUTTON_Register(void)
3190 memset(&wndClass
, 0, sizeof(wndClass
));
3191 wndClass
.style
= CS_GLOBALCLASS
| CS_DBLCLKS
| CS_VREDRAW
| CS_HREDRAW
| CS_PARENTDC
;
3192 wndClass
.lpfnWndProc
= BUTTON_WindowProc
;
3193 wndClass
.cbClsExtra
= 0;
3194 wndClass
.cbWndExtra
= sizeof(BUTTON_INFO
*);
3195 wndClass
.hCursor
= LoadCursorW(0, (LPWSTR
)IDC_ARROW
);
3196 wndClass
.hbrBackground
= NULL
;
3197 wndClass
.lpszClassName
= WC_BUTTONW
;
3198 RegisterClassW(&wndClass
);