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 void init_custom_draw(NMCUSTOMDRAW
*nmcd
, const BUTTON_INFO
*infoPtr
, HDC hdc
, const RECT
*rc
)
281 nmcd
->hdr
.hwndFrom
= infoPtr
->hwnd
;
282 nmcd
->hdr
.idFrom
= GetWindowLongPtrW(infoPtr
->hwnd
, GWLP_ID
);
283 nmcd
->hdr
.code
= NM_CUSTOMDRAW
;
286 nmcd
->dwDrawStage
= CDDS_PREERASE
;
287 nmcd
->dwItemSpec
= 0;
288 nmcd
->lItemlParam
= 0;
289 nmcd
->uItemState
= IsWindowEnabled(infoPtr
->hwnd
) ? 0 : CDIS_DISABLED
;
290 if (infoPtr
->state
& BST_PUSHED
) nmcd
->uItemState
|= CDIS_SELECTED
;
291 if (infoPtr
->state
& BST_FOCUS
) nmcd
->uItemState
|= CDIS_FOCUS
;
292 if (infoPtr
->state
& BST_HOT
) nmcd
->uItemState
|= CDIS_HOT
;
293 if (infoPtr
->state
& BST_INDETERMINATE
)
294 nmcd
->uItemState
|= CDIS_INDETERMINATE
;
296 /* Windows doesn't seem to send CDIS_CHECKED (it fails the tests) */
297 /* CDIS_SHOWKEYBOARDCUES is misleading, as the meaning is reversed */
298 /* FIXME: Handle it properly when we support keyboard cues? */
301 HRGN
set_control_clipping( HDC hdc
, const RECT
*rect
)
304 HRGN hrgn
= CreateRectRgn( 0, 0, 0, 0 );
306 if (GetClipRgn( hdc
, hrgn
) != 1)
308 DeleteObject( hrgn
);
311 DPtoLP( hdc
, (POINT
*)&rc
, 2 );
312 if (GetLayout( hdc
) & LAYOUT_RTL
) /* compensate for the shifting done by IntersectClipRect */
317 IntersectClipRect( hdc
, rc
.left
, rc
.top
, rc
.right
, rc
.bottom
);
321 static WCHAR
*heap_strndupW(const WCHAR
*src
, size_t length
)
323 size_t size
= (length
+ 1) * sizeof(WCHAR
);
324 WCHAR
*dst
= heap_alloc(size
);
325 if (dst
) memcpy(dst
, src
, size
);
329 /**********************************************************************
330 * Convert button styles to flags used by DrawText.
332 static UINT
BUTTON_BStoDT( DWORD style
, DWORD ex_style
)
334 UINT dtStyle
= DT_NOCLIP
; /* We use SelectClipRgn to limit output */
336 /* "Convert" pushlike buttons to pushbuttons */
337 if (style
& BS_PUSHLIKE
)
338 style
&= ~BS_TYPEMASK
;
340 if (!(style
& BS_MULTILINE
))
341 dtStyle
|= DT_SINGLELINE
;
343 dtStyle
|= DT_WORDBREAK
;
345 switch (style
& BS_CENTER
)
347 case BS_LEFT
: /* DT_LEFT is 0 */ break;
348 case BS_RIGHT
: dtStyle
|= DT_RIGHT
; break;
349 case BS_CENTER
: dtStyle
|= DT_CENTER
; break;
351 if (button_centers_text(style
)) dtStyle
|= DT_CENTER
;
354 if (ex_style
& WS_EX_RIGHT
) dtStyle
= DT_RIGHT
| (dtStyle
& ~(DT_LEFT
| DT_CENTER
));
356 /* DrawText ignores vertical alignment for multiline text,
357 * but we use these flags to align label manually.
359 if (get_button_type(style
) != BS_GROUPBOX
)
361 switch (style
& BS_VCENTER
)
363 case BS_TOP
: /* DT_TOP is 0 */ break;
364 case BS_BOTTOM
: dtStyle
|= DT_BOTTOM
; break;
365 case BS_VCENTER
: /* fall through */
366 default: dtStyle
|= DT_VCENTER
; break;
373 static int get_draw_state(const BUTTON_INFO
*infoPtr
)
375 static const int pb_states
[DRAW_STATE_COUNT
] = { PBS_NORMAL
, PBS_DISABLED
, PBS_HOT
, PBS_PRESSED
, PBS_DEFAULTED
};
376 static const int cb_states
[3][DRAW_STATE_COUNT
] =
378 { CBS_UNCHECKEDNORMAL
, CBS_UNCHECKEDDISABLED
, CBS_UNCHECKEDHOT
, CBS_UNCHECKEDPRESSED
, CBS_UNCHECKEDNORMAL
},
379 { CBS_CHECKEDNORMAL
, CBS_CHECKEDDISABLED
, CBS_CHECKEDHOT
, CBS_CHECKEDPRESSED
, CBS_CHECKEDNORMAL
},
380 { CBS_MIXEDNORMAL
, CBS_MIXEDDISABLED
, CBS_MIXEDHOT
, CBS_MIXEDPRESSED
, CBS_MIXEDNORMAL
}
382 static const int rb_states
[2][DRAW_STATE_COUNT
] =
384 { RBS_UNCHECKEDNORMAL
, RBS_UNCHECKEDDISABLED
, RBS_UNCHECKEDHOT
, RBS_UNCHECKEDPRESSED
, RBS_UNCHECKEDNORMAL
},
385 { RBS_CHECKEDNORMAL
, RBS_CHECKEDDISABLED
, RBS_CHECKEDHOT
, RBS_CHECKEDPRESSED
, RBS_CHECKEDNORMAL
}
387 static const int gb_states
[DRAW_STATE_COUNT
] = { GBS_NORMAL
, GBS_DISABLED
, GBS_NORMAL
, GBS_NORMAL
, GBS_NORMAL
};
388 LONG style
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
389 UINT type
= get_button_type(style
);
390 int check_state
= infoPtr
->state
& 3;
391 enum draw_state state
;
393 if (!IsWindowEnabled(infoPtr
->hwnd
))
394 state
= STATE_DISABLED
;
395 else if (infoPtr
->state
& BST_PUSHED
)
396 state
= STATE_PRESSED
;
397 else if (infoPtr
->state
& BST_HOT
)
399 else if (infoPtr
->state
& BST_FOCUS
)
400 state
= STATE_DEFAULTED
;
402 state
= STATE_NORMAL
;
407 case BS_DEFPUSHBUTTON
:
410 case BS_DEFSPLITBUTTON
:
412 case BS_DEFCOMMANDLINK
:
413 return pb_states
[state
];
415 case BS_AUTOCHECKBOX
:
416 return cb_states
[check_state
][state
];
420 case BS_AUTORADIOBUTTON
:
421 return rb_states
[check_state
][state
];
423 return gb_states
[state
];
425 WARN("Unsupported button type 0x%08x\n", type
);
430 static LRESULT CALLBACK
BUTTON_WindowProc(HWND hWnd
, UINT uMsg
, WPARAM wParam
, LPARAM lParam
)
432 BUTTON_INFO
*infoPtr
= (BUTTON_INFO
*)GetWindowLongPtrW(hWnd
, 0);
435 LONG style
= GetWindowLongW( hWnd
, GWL_STYLE
);
436 UINT btn_type
= get_button_type( style
);
437 LONG state
, new_state
;
441 if (!IsWindow( hWnd
)) return 0;
443 if (!infoPtr
&& (uMsg
!= WM_NCCREATE
))
444 return DefWindowProcW(hWnd
, uMsg
, wParam
, lParam
);
446 pt
.x
= (short)LOWORD(lParam
);
447 pt
.y
= (short)HIWORD(lParam
);
456 case BS_PUSHBUTTON
: return DLGC_BUTTON
| DLGC_UNDEFPUSHBUTTON
;
457 case BS_DEFCOMMANDLINK
:
458 case BS_DEFPUSHBUTTON
: return DLGC_BUTTON
| DLGC_DEFPUSHBUTTON
;
460 case BS_AUTORADIOBUTTON
: return DLGC_BUTTON
| DLGC_RADIOBUTTON
;
461 case BS_GROUPBOX
: return DLGC_STATIC
;
462 case BS_SPLITBUTTON
: return DLGC_BUTTON
| DLGC_UNDEFPUSHBUTTON
| DLGC_WANTARROWS
;
463 case BS_DEFSPLITBUTTON
: return DLGC_BUTTON
| DLGC_DEFPUSHBUTTON
| DLGC_WANTARROWS
;
464 default: return DLGC_BUTTON
;
468 theme
= GetWindowTheme( hWnd
);
470 RedrawWindow( hWnd
, NULL
, NULL
, RDW_FRAME
| RDW_INVALIDATE
| RDW_UPDATENOW
);
472 paint_button( infoPtr
, btn_type
, ODA_DRAWENTIRE
);
477 CREATESTRUCTW
*cs
= (CREATESTRUCTW
*)lParam
;
479 infoPtr
= heap_alloc_zero( sizeof(*infoPtr
) );
480 SetWindowLongPtrW( hWnd
, 0, (LONG_PTR
)infoPtr
);
481 infoPtr
->hwnd
= hWnd
;
482 infoPtr
->parent
= cs
->hwndParent
;
483 infoPtr
->style
= cs
->style
;
484 infoPtr
->split_style
= BCSS_STRETCH
;
485 infoPtr
->glyph
= (HIMAGELIST
)0x36; /* Marlett down arrow char code */
486 infoPtr
->glyph_size
.cx
= get_default_glyph_size(infoPtr
);
487 return DefWindowProcW(hWnd
, uMsg
, wParam
, lParam
);
491 SetWindowLongPtrW( hWnd
, 0, 0 );
492 if (infoPtr
->image_type
== IMAGE_BITMAP
)
493 DeleteObject(infoPtr
->u
.bitmap
);
494 else if (infoPtr
->image_type
== IMAGE_ICON
)
495 DestroyIcon(infoPtr
->u
.icon
);
496 heap_free(infoPtr
->note
);
501 if (btn_type
>= MAX_BTN_TYPE
)
502 return -1; /* abort */
504 /* XP turns a BS_USERBUTTON into BS_PUSHBUTTON */
505 if (btn_type
== BS_USERBUTTON
)
507 style
= (style
& ~BS_TYPEMASK
) | BS_PUSHBUTTON
;
508 SetWindowLongW( hWnd
, GWL_STYLE
, style
);
510 infoPtr
->state
= BST_UNCHECKED
;
511 OpenThemeData( hWnd
, WC_BUTTONW
);
515 theme
= GetWindowTheme( hWnd
);
516 CloseThemeData( theme
);
519 case WM_THEMECHANGED
:
520 theme
= GetWindowTheme( hWnd
);
521 CloseThemeData( theme
);
522 OpenThemeData( hWnd
, WC_BUTTONW
);
526 if (btn_type
== BS_OWNERDRAW
)
528 HDC hdc
= (HDC
)wParam
;
531 HWND parent
= GetParent(hWnd
);
532 if (!parent
) parent
= hWnd
;
533 hBrush
= (HBRUSH
)SendMessageW(parent
, WM_CTLCOLORBTN
, (WPARAM
)hdc
, (LPARAM
)hWnd
);
534 if (!hBrush
) /* did the app forget to call defwindowproc ? */
535 hBrush
= (HBRUSH
)DefWindowProcW(parent
, WM_CTLCOLORBTN
,
536 (WPARAM
)hdc
, (LPARAM
)hWnd
);
537 GetClientRect(hWnd
, &rc
);
538 FillRect(hdc
, &rc
, hBrush
);
548 theme
= GetWindowTheme( hWnd
);
549 hdc
= wParam
? (HDC
)wParam
: BeginPaint( hWnd
, &ps
);
551 if (theme
&& btnThemedPaintFunc
[btn_type
])
553 int drawState
= get_draw_state(infoPtr
);
554 UINT dtflags
= BUTTON_BStoDT(style
, GetWindowLongW(hWnd
, GWL_EXSTYLE
));
556 btnThemedPaintFunc
[btn_type
](theme
, infoPtr
, hdc
, drawState
, dtflags
, infoPtr
->state
& BST_FOCUS
);
558 else if (btnPaintFunc
[btn_type
])
560 int nOldMode
= SetBkMode( hdc
, OPAQUE
);
561 btnPaintFunc
[btn_type
]( infoPtr
, hdc
, ODA_DRAWENTIRE
);
562 SetBkMode(hdc
, nOldMode
); /* reset painting mode */
565 if ( !wParam
) EndPaint( hWnd
, &ps
);
570 if (wParam
== VK_SPACE
)
572 SendMessageW( hWnd
, BM_SETSTATE
, TRUE
, 0 );
573 infoPtr
->state
|= BUTTON_BTNPRESSED
;
576 else if (wParam
== VK_UP
|| wParam
== VK_DOWN
)
578 /* Up and down arrows work on every button, and even with BCSS_NOSPLIT */
579 notify_split_button_dropdown(infoPtr
, NULL
, hWnd
);
583 case WM_LBUTTONDBLCLK
:
584 if(style
& BS_NOTIFY
||
585 btn_type
== BS_RADIOBUTTON
||
586 btn_type
== BS_USERBUTTON
||
587 btn_type
== BS_OWNERDRAW
)
589 BUTTON_NOTIFY_PARENT(hWnd
, BN_DOUBLECLICKED
);
596 if ((btn_type
== BS_SPLITBUTTON
|| btn_type
== BS_DEFSPLITBUTTON
) &&
597 !(infoPtr
->split_style
& BCSS_NOSPLIT
) &&
598 notify_split_button_dropdown(infoPtr
, &pt
, hWnd
))
602 infoPtr
->state
|= BUTTON_BTNPRESSED
;
603 SendMessageW( hWnd
, BM_SETSTATE
, TRUE
, 0 );
607 if (wParam
!= VK_SPACE
)
611 state
= infoPtr
->state
;
612 if (state
& BST_DROPDOWNPUSHED
)
613 SendMessageW(hWnd
, BCM_SETDROPDOWNSTATE
, FALSE
, 0);
614 if (!(state
& BUTTON_BTNPRESSED
)) break;
615 infoPtr
->state
&= BUTTON_NSTATES
| BST_HOT
;
616 if (!(state
& BST_PUSHED
))
621 SendMessageW( hWnd
, BM_SETSTATE
, FALSE
, 0 );
622 GetClientRect( hWnd
, &rect
);
623 if (uMsg
== WM_KEYUP
|| PtInRect( &rect
, pt
))
627 case BS_AUTOCHECKBOX
:
628 SendMessageW( hWnd
, BM_SETCHECK
, !(infoPtr
->state
& BST_CHECKED
), 0 );
630 case BS_AUTORADIOBUTTON
:
631 SendMessageW( hWnd
, BM_SETCHECK
, TRUE
, 0 );
634 SendMessageW( hWnd
, BM_SETCHECK
, (infoPtr
->state
& BST_INDETERMINATE
) ? 0 :
635 ((infoPtr
->state
& 3) + 1), 0 );
639 BUTTON_NOTIFY_PARENT(hWnd
, BN_CLICKED
);
647 case WM_CAPTURECHANGED
:
648 TRACE("WM_CAPTURECHANGED %p\n", hWnd
);
649 if (hWnd
== (HWND
)lParam
) break;
650 if (infoPtr
->state
& BUTTON_BTNPRESSED
)
652 infoPtr
->state
&= BUTTON_NSTATES
;
653 if (infoPtr
->state
& BST_PUSHED
)
654 SendMessageW( hWnd
, BM_SETSTATE
, FALSE
, 0 );
660 TRACKMOUSEEVENT mouse_event
;
662 mouse_event
.cbSize
= sizeof(TRACKMOUSEEVENT
);
663 mouse_event
.dwFlags
= TME_QUERY
;
664 if (!TrackMouseEvent(&mouse_event
) || !(mouse_event
.dwFlags
& (TME_HOVER
| TME_LEAVE
)))
666 mouse_event
.dwFlags
= TME_HOVER
| TME_LEAVE
;
667 mouse_event
.hwndTrack
= hWnd
;
668 mouse_event
.dwHoverTime
= 1;
669 TrackMouseEvent(&mouse_event
);
672 if ((wParam
& MK_LBUTTON
) && GetCapture() == hWnd
)
674 GetClientRect( hWnd
, &rect
);
675 SendMessageW( hWnd
, BM_SETSTATE
, PtInRect(&rect
, pt
), 0 );
682 infoPtr
->state
|= BST_HOT
;
683 InvalidateRect( hWnd
, NULL
, FALSE
);
689 infoPtr
->state
&= ~BST_HOT
;
690 InvalidateRect( hWnd
, NULL
, FALSE
);
696 /* Clear an old text here as Windows does */
697 if (IsWindowVisible(hWnd
))
699 HDC hdc
= GetDC(hWnd
);
702 HWND parent
= GetParent(hWnd
);
703 UINT message
= (btn_type
== BS_PUSHBUTTON
||
704 btn_type
== BS_DEFPUSHBUTTON
||
705 btn_type
== BS_USERBUTTON
||
706 btn_type
== BS_OWNERDRAW
) ?
707 WM_CTLCOLORBTN
: WM_CTLCOLORSTATIC
;
709 if (!parent
) parent
= hWnd
;
710 hbrush
= (HBRUSH
)SendMessageW(parent
, message
,
711 (WPARAM
)hdc
, (LPARAM
)hWnd
);
712 if (!hbrush
) /* did the app forget to call DefWindowProc ? */
713 hbrush
= (HBRUSH
)DefWindowProcW(parent
, message
,
714 (WPARAM
)hdc
, (LPARAM
)hWnd
);
716 GetClientRect(hWnd
, &client
);
718 /* FIXME: check other BS_* handlers */
719 if (btn_type
== BS_GROUPBOX
)
720 InflateRect(&rc
, -7, 1); /* GB_Paint does this */
721 BUTTON_CalcLayoutRects(infoPtr
, hdc
, &rc
, NULL
, NULL
);
722 /* Clip by client rect bounds */
723 if (rc
.right
> client
.right
) rc
.right
= client
.right
;
724 if (rc
.bottom
> client
.bottom
) rc
.bottom
= client
.bottom
;
725 FillRect(hdc
, &rc
, hbrush
);
726 ReleaseDC(hWnd
, hdc
);
729 DefWindowProcW( hWnd
, WM_SETTEXT
, wParam
, lParam
);
730 if (btn_type
== BS_GROUPBOX
) /* Yes, only for BS_GROUPBOX */
731 InvalidateRect( hWnd
, NULL
, TRUE
);
733 paint_button( infoPtr
, btn_type
, ODA_DRAWENTIRE
);
734 return 1; /* success. FIXME: check text length */
739 WCHAR
*note
= (WCHAR
*)lParam
;
740 if (btn_type
!= BS_COMMANDLINK
&& btn_type
!= BS_DEFCOMMANDLINK
)
742 SetLastError(ERROR_NOT_SUPPORTED
);
746 heap_free(infoPtr
->note
);
749 infoPtr
->note_length
= lstrlenW(note
);
750 infoPtr
->note
= heap_strndupW(note
, infoPtr
->note_length
);
753 if (!note
|| !infoPtr
->note
)
755 infoPtr
->note_length
= 0;
756 infoPtr
->note
= heap_alloc_zero(sizeof(WCHAR
));
759 SetLastError(NO_ERROR
);
765 DWORD
*size
= (DWORD
*)wParam
;
766 WCHAR
*buffer
= (WCHAR
*)lParam
;
769 if (btn_type
!= BS_COMMANDLINK
&& btn_type
!= BS_DEFCOMMANDLINK
)
771 SetLastError(ERROR_NOT_SUPPORTED
);
775 if (!buffer
|| !size
|| !infoPtr
->note
)
777 SetLastError(ERROR_INVALID_PARAMETER
);
783 length
= min(*size
- 1, infoPtr
->note_length
);
784 memcpy(buffer
, infoPtr
->note
, length
* sizeof(WCHAR
));
785 buffer
[length
] = '\0';
788 if (*size
< infoPtr
->note_length
+ 1)
790 *size
= infoPtr
->note_length
+ 1;
791 SetLastError(ERROR_INSUFFICIENT_BUFFER
);
796 SetLastError(NO_ERROR
);
801 case BCM_GETNOTELENGTH
:
803 if (btn_type
!= BS_COMMANDLINK
&& btn_type
!= BS_DEFCOMMANDLINK
)
805 SetLastError(ERROR_NOT_SUPPORTED
);
809 return infoPtr
->note_length
;
813 infoPtr
->font
= (HFONT
)wParam
;
814 if (lParam
) InvalidateRect(hWnd
, NULL
, TRUE
);
818 return (LRESULT
)infoPtr
->font
;
821 TRACE("WM_SETFOCUS %p\n",hWnd
);
822 infoPtr
->state
|= BST_FOCUS
;
824 if (btn_type
== BS_OWNERDRAW
)
825 paint_button( infoPtr
, btn_type
, ODA_FOCUS
);
827 InvalidateRect(hWnd
, NULL
, FALSE
);
829 if (style
& BS_NOTIFY
)
830 BUTTON_NOTIFY_PARENT(hWnd
, BN_SETFOCUS
);
834 TRACE("WM_KILLFOCUS %p\n",hWnd
);
835 infoPtr
->state
&= ~BST_FOCUS
;
837 if ((infoPtr
->state
& BUTTON_BTNPRESSED
) && GetCapture() == hWnd
)
839 if (style
& BS_NOTIFY
)
840 BUTTON_NOTIFY_PARENT(hWnd
, BN_KILLFOCUS
);
842 InvalidateRect( hWnd
, NULL
, FALSE
);
845 case WM_SYSCOLORCHANGE
:
846 InvalidateRect( hWnd
, NULL
, FALSE
);
853 new_btn_type
= wParam
& BS_TYPEMASK
;
854 if (btn_type
>= BS_SPLITBUTTON
&& new_btn_type
<= BS_DEFPUSHBUTTON
)
855 new_btn_type
= (btn_type
& ~BS_DEFPUSHBUTTON
) | new_btn_type
;
857 style
= (style
& ~BS_TYPEMASK
) | new_btn_type
;
858 SetWindowLongW( hWnd
, GWL_STYLE
, style
);
860 /* Only redraw if lParam flag is set.*/
862 InvalidateRect( hWnd
, NULL
, TRUE
);
867 SendMessageW( hWnd
, WM_LBUTTONDOWN
, 0, 0 );
868 SendMessageW( hWnd
, WM_LBUTTONUP
, 0, 0 );
872 infoPtr
->image_type
= (DWORD
)wParam
;
873 oldHbitmap
= infoPtr
->image
;
874 infoPtr
->u
.image
= CopyImage((HANDLE
)lParam
, infoPtr
->image_type
, 0, 0, 0);
875 infoPtr
->image
= (HANDLE
)lParam
;
876 InvalidateRect( hWnd
, NULL
, FALSE
);
877 return (LRESULT
)oldHbitmap
;
880 return (LRESULT
)infoPtr
->image
;
882 case BCM_SETIMAGELIST
:
884 BUTTON_IMAGELIST
*imagelist
= (BUTTON_IMAGELIST
*)lParam
;
886 if (!imagelist
) return FALSE
;
888 infoPtr
->imagelist
= *imagelist
;
892 case BCM_GETIMAGELIST
:
894 BUTTON_IMAGELIST
*imagelist
= (BUTTON_IMAGELIST
*)lParam
;
896 if (!imagelist
) return FALSE
;
898 *imagelist
= infoPtr
->imagelist
;
902 case BCM_SETSPLITINFO
:
904 BUTTON_SPLITINFO
*info
= (BUTTON_SPLITINFO
*)lParam
;
906 if (!info
) return TRUE
;
908 if (info
->mask
& (BCSIF_GLYPH
| BCSIF_IMAGE
))
910 infoPtr
->split_style
&= ~BCSS_IMAGE
;
911 if (!(info
->mask
& BCSIF_GLYPH
))
912 infoPtr
->split_style
|= BCSS_IMAGE
;
913 infoPtr
->glyph
= info
->himlGlyph
;
914 infoPtr
->glyph_size
.cx
= infoPtr
->glyph_size
.cy
= 0;
917 if (info
->mask
& BCSIF_STYLE
)
918 infoPtr
->split_style
= info
->uSplitStyle
;
919 if (info
->mask
& BCSIF_SIZE
)
920 infoPtr
->glyph_size
= info
->size
;
922 /* Calculate fitting value for cx if invalid (cy is untouched) */
923 if (infoPtr
->glyph_size
.cx
<= 0)
924 infoPtr
->glyph_size
.cx
= get_default_glyph_size(infoPtr
);
926 /* Windows doesn't invalidate or redraw it, so we don't, either */
930 case BCM_GETSPLITINFO
:
932 BUTTON_SPLITINFO
*info
= (BUTTON_SPLITINFO
*)lParam
;
934 if (!info
) return FALSE
;
936 if (info
->mask
& BCSIF_STYLE
)
937 info
->uSplitStyle
= infoPtr
->split_style
;
938 if (info
->mask
& (BCSIF_GLYPH
| BCSIF_IMAGE
))
939 info
->himlGlyph
= infoPtr
->glyph
;
940 if (info
->mask
& BCSIF_SIZE
)
941 info
->size
= infoPtr
->glyph_size
;
947 return infoPtr
->state
& 3;
950 if (wParam
> maxCheckState
[btn_type
]) wParam
= maxCheckState
[btn_type
];
951 if ((btn_type
== BS_RADIOBUTTON
) || (btn_type
== BS_AUTORADIOBUTTON
))
953 style
= wParam
? style
| WS_TABSTOP
: style
& ~WS_TABSTOP
;
954 SetWindowLongW( hWnd
, GWL_STYLE
, style
);
956 if ((infoPtr
->state
& 3) != wParam
)
958 infoPtr
->state
= (infoPtr
->state
& ~3) | wParam
;
959 InvalidateRect( hWnd
, NULL
, FALSE
);
961 if ((btn_type
== BS_AUTORADIOBUTTON
) && (wParam
== BST_CHECKED
) && (style
& WS_CHILD
))
962 BUTTON_CheckAutoRadioButton( hWnd
);
966 return infoPtr
->state
;
969 state
= infoPtr
->state
;
970 new_state
= wParam
? BST_PUSHED
: 0;
972 if ((state
^ new_state
) & BST_PUSHED
)
977 state
&= ~BST_PUSHED
;
979 if (btn_type
== BS_USERBUTTON
)
980 BUTTON_NOTIFY_PARENT( hWnd
, (state
& BST_PUSHED
) ? BN_HILITE
: BN_UNHILITE
);
981 infoPtr
->state
= state
;
983 InvalidateRect( hWnd
, NULL
, FALSE
);
987 case BCM_SETDROPDOWNSTATE
:
988 new_state
= wParam
? BST_DROPDOWNPUSHED
: 0;
990 if ((infoPtr
->state
^ new_state
) & BST_DROPDOWNPUSHED
)
992 infoPtr
->state
&= ~BST_DROPDOWNPUSHED
;
993 infoPtr
->state
|= new_state
;
994 InvalidateRect(hWnd
, NULL
, FALSE
);
998 case BCM_SETTEXTMARGIN
:
1000 RECT
*text_margin
= (RECT
*)lParam
;
1002 if (!text_margin
) return FALSE
;
1004 infoPtr
->text_margin
= *text_margin
;
1008 case BCM_GETTEXTMARGIN
:
1010 RECT
*text_margin
= (RECT
*)lParam
;
1012 if (!text_margin
) return FALSE
;
1014 *text_margin
= infoPtr
->text_margin
;
1018 case BCM_GETIDEALSIZE
:
1020 SIZE
*size
= (SIZE
*)lParam
;
1022 if (!size
) return FALSE
;
1024 return btnGetIdealSizeFunc
[btn_type
](infoPtr
, size
);
1028 if(btn_type
== BS_GROUPBOX
) return HTTRANSPARENT
;
1031 return DefWindowProcW(hWnd
, uMsg
, wParam
, lParam
);
1036 /* If maxWidth is zero, rectangle width is unlimited */
1037 static RECT
BUTTON_GetTextRect(const BUTTON_INFO
*infoPtr
, HDC hdc
, const WCHAR
*text
, LONG maxWidth
)
1039 LONG style
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
1040 LONG exStyle
= GetWindowLongW(infoPtr
->hwnd
, GWL_EXSTYLE
);
1041 UINT dtStyle
= BUTTON_BStoDT(style
, exStyle
);
1045 rect
.right
= maxWidth
;
1046 hPrevFont
= SelectObject(hdc
, infoPtr
->font
);
1047 /* Calculate height without DT_VCENTER and DT_BOTTOM to get the correct height */
1048 DrawTextW(hdc
, text
, -1, &rect
, (dtStyle
& ~(DT_VCENTER
| DT_BOTTOM
)) | DT_CALCRECT
);
1049 if (hPrevFont
) SelectObject(hdc
, hPrevFont
);
1054 static BOOL
show_image_only(const BUTTON_INFO
*infoPtr
)
1056 LONG style
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
1057 return (style
& (BS_ICON
| BS_BITMAP
)) && (infoPtr
->u
.image
|| infoPtr
->imagelist
.himl
);
1060 static BOOL
show_image_and_text(const BUTTON_INFO
*infoPtr
)
1062 LONG style
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
1063 UINT type
= get_button_type(style
);
1064 return !(style
& (BS_ICON
| BS_BITMAP
))
1065 && ((infoPtr
->u
.image
1066 && (type
== BS_PUSHBUTTON
|| type
== BS_DEFPUSHBUTTON
|| type
== BS_USERBUTTON
|| type
== BS_SPLITBUTTON
1067 || type
== BS_DEFSPLITBUTTON
|| type
== BS_COMMANDLINK
|| type
== BS_DEFCOMMANDLINK
))
1068 || (infoPtr
->imagelist
.himl
&& type
!= BS_GROUPBOX
));
1071 static BOOL
show_image(const BUTTON_INFO
*infoPtr
)
1073 return show_image_only(infoPtr
) || show_image_and_text(infoPtr
);
1076 /* Get a bounding rectangle that is large enough to contain a image and a text side by side.
1077 * Note: (left,top) of the result rectangle may not be (0,0), offset it by yourself if needed */
1078 static RECT
BUTTON_GetBoundingLabelRect(LONG style
, const RECT
*textRect
, const RECT
*imageRect
)
1081 RECT rect
= *imageRect
;
1082 INT textWidth
= textRect
->right
- textRect
->left
;
1083 INT textHeight
= textRect
->bottom
- textRect
->top
;
1084 INT imageWidth
= imageRect
->right
- imageRect
->left
;
1085 INT imageHeight
= imageRect
->bottom
- imageRect
->top
;
1087 if ((style
& BS_CENTER
) == BS_RIGHT
)
1088 OffsetRect(&rect
, textWidth
, 0);
1089 else if ((style
& BS_CENTER
) == BS_LEFT
)
1090 OffsetRect(&rect
, -imageWidth
, 0);
1091 else if ((style
& BS_VCENTER
) == BS_BOTTOM
)
1092 OffsetRect(&rect
, 0, textHeight
);
1093 else if ((style
& BS_VCENTER
) == BS_TOP
)
1094 OffsetRect(&rect
, 0, -imageHeight
);
1096 OffsetRect(&rect
, -imageWidth
, 0);
1098 UnionRect(&labelRect
, textRect
, &rect
);
1102 /* Position a rectangle inside a bounding rectangle according to button alignment flags */
1103 static void BUTTON_PositionRect(LONG style
, const RECT
*outerRect
, RECT
*innerRect
, const RECT
*margin
)
1105 INT width
= innerRect
->right
- innerRect
->left
;
1106 INT height
= innerRect
->bottom
- innerRect
->top
;
1108 if ((style
& WS_EX_RIGHT
) && !(style
& BS_CENTER
)) style
|= BS_CENTER
;
1110 if (!(style
& BS_CENTER
))
1112 if (button_centers_text(style
))
1118 if (!(style
& BS_VCENTER
))
1120 /* Group box's text is top aligned by default */
1121 if (get_button_type(style
) == BS_GROUPBOX
)
1125 switch (style
& BS_CENTER
)
1128 innerRect
->left
= outerRect
->left
+ (outerRect
->right
- outerRect
->left
- width
) / 2;
1129 innerRect
->right
= innerRect
->left
+ width
;
1132 innerRect
->right
= outerRect
->right
- margin
->right
;
1133 innerRect
->left
= innerRect
->right
- width
;
1137 innerRect
->left
= outerRect
->left
+ margin
->left
;
1138 innerRect
->right
= innerRect
->left
+ width
;
1142 switch (style
& BS_VCENTER
)
1145 innerRect
->top
= outerRect
->top
+ margin
->top
;
1146 innerRect
->bottom
= innerRect
->top
+ height
;
1149 innerRect
->bottom
= outerRect
->bottom
- margin
->bottom
;
1150 innerRect
->top
= innerRect
->bottom
- height
;
1154 innerRect
->top
= outerRect
->top
+ (outerRect
->bottom
- outerRect
->top
- height
) / 2;
1155 innerRect
->bottom
= innerRect
->top
+ height
;
1160 /* Convert imagelist align style to button align style */
1161 static UINT
BUTTON_ILStoBS(UINT align
)
1165 case BUTTON_IMAGELIST_ALIGN_TOP
:
1166 return BS_CENTER
| BS_TOP
;
1167 case BUTTON_IMAGELIST_ALIGN_BOTTOM
:
1168 return BS_CENTER
| BS_BOTTOM
;
1169 case BUTTON_IMAGELIST_ALIGN_CENTER
:
1170 return BS_CENTER
| BS_VCENTER
;
1171 case BUTTON_IMAGELIST_ALIGN_RIGHT
:
1172 return BS_RIGHT
| BS_VCENTER
;
1173 case BUTTON_IMAGELIST_ALIGN_LEFT
:
1175 return BS_LEFT
| BS_VCENTER
;
1179 static SIZE
BUTTON_GetImageSize(const BUTTON_INFO
*infoPtr
)
1185 /* ImageList has priority over image */
1186 if (infoPtr
->imagelist
.himl
)
1187 ImageList_GetIconSize(infoPtr
->imagelist
.himl
, &size
.cx
, &size
.cy
);
1188 else if (infoPtr
->u
.image
)
1190 if (infoPtr
->image_type
== IMAGE_ICON
)
1192 GetIconInfo(infoPtr
->u
.icon
, &iconInfo
);
1193 GetObjectW(iconInfo
.hbmColor
, sizeof(bm
), &bm
);
1194 DeleteObject(iconInfo
.hbmColor
);
1195 DeleteObject(iconInfo
.hbmMask
);
1197 else if (infoPtr
->image_type
== IMAGE_BITMAP
)
1198 GetObjectW(infoPtr
->u
.bitmap
, sizeof(bm
), &bm
);
1200 size
.cx
= bm
.bmWidth
;
1201 size
.cy
= bm
.bmHeight
;
1207 static const RECT
*BUTTON_GetTextMargin(const BUTTON_INFO
*infoPtr
)
1209 static const RECT oneMargin
= {1, 1, 1, 1};
1211 /* Use text margin only when showing both image and text, and image is not imagelist */
1212 if (show_image_and_text(infoPtr
) && !infoPtr
->imagelist
.himl
)
1213 return &infoPtr
->text_margin
;
1218 static void BUTTON_GetClientRectSize(BUTTON_INFO
*infoPtr
, SIZE
*size
)
1221 GetClientRect(infoPtr
->hwnd
, &rect
);
1222 size
->cx
= rect
.right
- rect
.left
;
1223 size
->cy
= rect
.bottom
- rect
.top
;
1226 static void BUTTON_GetTextIdealSize(BUTTON_INFO
*infoPtr
, LONG maxWidth
, SIZE
*size
)
1228 WCHAR
*text
= get_button_text(infoPtr
);
1231 const RECT
*margin
= BUTTON_GetTextMargin(infoPtr
);
1235 maxWidth
-= margin
->right
+ margin
->right
;
1236 if (maxWidth
<= 0) maxWidth
= 1;
1239 hdc
= GetDC(infoPtr
->hwnd
);
1240 rect
= BUTTON_GetTextRect(infoPtr
, hdc
, text
, maxWidth
);
1241 ReleaseDC(infoPtr
->hwnd
, hdc
);
1244 size
->cx
= rect
.right
- rect
.left
+ margin
->left
+ margin
->right
;
1245 size
->cy
= rect
.bottom
- rect
.top
+ margin
->top
+ margin
->bottom
;
1248 static void BUTTON_GetLabelIdealSize(BUTTON_INFO
*infoPtr
, LONG maxWidth
, SIZE
*size
)
1250 LONG style
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
1255 imageSize
= BUTTON_GetImageSize(infoPtr
);
1256 if (infoPtr
->imagelist
.himl
)
1258 imageSize
.cx
+= infoPtr
->imagelist
.margin
.left
+ infoPtr
->imagelist
.margin
.right
;
1259 imageSize
.cy
+= infoPtr
->imagelist
.margin
.top
+ infoPtr
->imagelist
.margin
.bottom
;
1260 if (infoPtr
->imagelist
.uAlign
== BUTTON_IMAGELIST_ALIGN_TOP
1261 || infoPtr
->imagelist
.uAlign
== BUTTON_IMAGELIST_ALIGN_BOTTOM
)
1268 /* horizontal alignment flags has priority over vertical ones if both are specified */
1269 if (!(style
& (BS_CENTER
| BS_VCENTER
)) || ((style
& BS_CENTER
) && (style
& BS_CENTER
) != BS_CENTER
)
1270 || !(style
& BS_VCENTER
) || (style
& BS_VCENTER
) == BS_VCENTER
)
1280 maxWidth
-= imageSize
.cx
;
1281 if (maxWidth
<= 0) maxWidth
= 1;
1283 BUTTON_GetTextIdealSize(infoPtr
, maxWidth
, &textSize
);
1284 size
->cx
= textSize
.cx
+ imageSize
.cx
;
1285 size
->cy
= max(textSize
.cy
, imageSize
.cy
);
1289 BUTTON_GetTextIdealSize(infoPtr
, maxWidth
, &textSize
);
1290 size
->cx
= max(textSize
.cx
, imageSize
.cx
);
1291 size
->cy
= textSize
.cy
+ imageSize
.cy
;
1295 static BOOL
GB_GetIdealSize(BUTTON_INFO
*infoPtr
, SIZE
*size
)
1297 BUTTON_GetClientRectSize(infoPtr
, size
);
1301 static BOOL
CB_GetIdealSize(BUTTON_INFO
*infoPtr
, SIZE
*size
)
1303 LONG style
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
1310 LONG checkboxWidth
, checkboxHeight
;
1313 if (SendMessageW(infoPtr
->hwnd
, WM_GETTEXTLENGTH
, 0, 0) == 0)
1315 BUTTON_GetClientRectSize(infoPtr
, size
);
1319 hdc
= GetDC(infoPtr
->hwnd
);
1320 scaleX
= GetDeviceCaps(hdc
, LOGPIXELSX
) / 96.0;
1321 scaleY
= GetDeviceCaps(hdc
, LOGPIXELSY
) / 96.0;
1322 if ((hfont
= infoPtr
->font
)) SelectObject(hdc
, hfont
);
1323 GetCharWidthW(hdc
, '0', '0', &textOffset
);
1325 ReleaseDC(infoPtr
->hwnd
, hdc
);
1327 checkboxWidth
= 12 * scaleX
+ 1;
1328 checkboxHeight
= 12 * scaleY
+ 1;
1331 maxWidth
= size
->cx
- checkboxWidth
- textOffset
;
1332 if (maxWidth
<= 0) maxWidth
= 1;
1335 /* Checkbox doesn't support both image(but not image list) and text */
1336 if (!(style
& (BS_ICON
| BS_BITMAP
)) && infoPtr
->u
.image
)
1337 BUTTON_GetTextIdealSize(infoPtr
, maxWidth
, &labelSize
);
1339 BUTTON_GetLabelIdealSize(infoPtr
, maxWidth
, &labelSize
);
1341 size
->cx
= labelSize
.cx
+ checkboxWidth
+ textOffset
;
1342 size
->cy
= max(labelSize
.cy
, checkboxHeight
);
1347 static BOOL
PB_GetIdealSize(BUTTON_INFO
*infoPtr
, SIZE
*size
)
1351 if (SendMessageW(infoPtr
->hwnd
, WM_GETTEXTLENGTH
, 0, 0) == 0)
1352 BUTTON_GetClientRectSize(infoPtr
, size
);
1355 /* Ideal size include text size even if image only flags(BS_ICON, BS_BITMAP) are specified */
1356 BUTTON_GetLabelIdealSize(infoPtr
, size
->cx
, &labelSize
);
1358 size
->cx
= labelSize
.cx
;
1359 size
->cy
= labelSize
.cy
;
1364 static BOOL
SB_GetIdealSize(BUTTON_INFO
*infoPtr
, SIZE
*size
)
1366 LONG extra_width
= infoPtr
->glyph_size
.cx
* 2 + GetSystemMetrics(SM_CXEDGE
);
1369 if (SendMessageW(infoPtr
->hwnd
, WM_GETTEXTLENGTH
, 0, 0) == 0)
1371 BUTTON_GetClientRectSize(infoPtr
, size
);
1372 size
->cx
= max(size
->cx
, extra_width
);
1376 BUTTON_GetLabelIdealSize(infoPtr
, size
->cx
, &label_size
);
1377 size
->cx
= label_size
.cx
+ ((size
->cx
== 0) ? extra_width
: 0);
1378 size
->cy
= label_size
.cy
;
1383 static BOOL
CL_GetIdealSize(BUTTON_INFO
*infoPtr
, SIZE
*size
)
1385 HTHEME theme
= GetWindowTheme(infoPtr
->hwnd
);
1386 HDC hdc
= GetDC(infoPtr
->hwnd
);
1387 LONG w
, text_w
= 0, text_h
= 0;
1388 UINT flags
= DT_TOP
| DT_LEFT
;
1389 HFONT font
, old_font
= NULL
;
1390 RECT text_bound
= { 0 };
1395 /* Get the image size */
1396 if (infoPtr
->u
.image
|| infoPtr
->imagelist
.himl
)
1397 img_size
= BUTTON_GetImageSize(infoPtr
);
1401 GetThemePartSize(theme
, NULL
, BP_COMMANDLINKGLYPH
, CMDLS_NORMAL
, NULL
, TS_DRAW
, &img_size
);
1403 img_size
.cx
= img_size
.cy
= command_link_defglyph_size
;
1406 /* Get the content margins */
1409 RECT r
= { 0, 0, 0xffff, 0xffff };
1410 GetThemeBackgroundContentRect(theme
, hdc
, BP_COMMANDLINK
, CMDLS_NORMAL
, &r
, &margin
);
1411 margin
.left
-= r
.left
;
1412 margin
.top
-= r
.top
;
1413 margin
.right
= r
.right
- margin
.right
;
1414 margin
.bottom
= r
.bottom
- margin
.bottom
;
1418 margin
.left
= margin
.right
= command_link_margin
;
1419 margin
.top
= margin
.bottom
= command_link_margin
;
1422 /* Account for the border margins and the margin between image and text */
1423 w
= margin
.left
+ margin
.right
+ (img_size
.cx
? (img_size
.cx
+ command_link_margin
) : 0);
1425 /* If a rectangle with a specific width was requested, bound the text to it */
1428 text_bound
.right
= size
->cx
- w
;
1429 flags
|= DT_WORDBREAK
;
1434 if (infoPtr
->font
) old_font
= SelectObject(hdc
, infoPtr
->font
);
1436 /* Find the text's rect */
1437 if ((text
= get_button_text(infoPtr
)))
1440 GetThemeTextExtent(theme
, hdc
, BP_COMMANDLINK
, CMDLS_NORMAL
,
1441 text
, -1, flags
, &text_bound
, &r
);
1443 text_w
= r
.right
- r
.left
;
1444 text_h
= r
.bottom
- r
.top
;
1447 /* Find the note's rect */
1452 opts
.dwSize
= sizeof(opts
);
1453 opts
.dwFlags
= DTT_FONTPROP
| DTT_CALCRECT
;
1454 opts
.iFontPropId
= TMT_BODYFONT
;
1455 DrawThemeTextEx(theme
, hdc
, BP_COMMANDLINK
, CMDLS_NORMAL
,
1456 infoPtr
->note
, infoPtr
->note_length
,
1457 flags
| DT_NOPREFIX
| DT_CALCRECT
, &text_bound
, &opts
);
1458 text_w
= max(text_w
, text_bound
.right
- text_bound
.left
);
1459 text_h
+= text_bound
.bottom
- text_bound
.top
;
1464 NONCLIENTMETRICSW ncm
;
1466 ncm
.cbSize
= sizeof(ncm
);
1467 if (SystemParametersInfoW(SPI_GETNONCLIENTMETRICS
, sizeof(ncm
), &ncm
, 0))
1469 LONG note_weight
= ncm
.lfMessageFont
.lfWeight
;
1471 /* Find the text's rect */
1472 ncm
.lfMessageFont
.lfWeight
= FW_BOLD
;
1473 if ((font
= CreateFontIndirectW(&ncm
.lfMessageFont
)))
1475 if ((text
= get_button_text(infoPtr
)))
1477 RECT r
= text_bound
;
1478 old_font
= SelectObject(hdc
, font
);
1479 DrawTextW(hdc
, text
, -1, &r
, flags
| DT_CALCRECT
);
1482 text_w
= r
.right
- r
.left
;
1483 text_h
= r
.bottom
- r
.top
;
1488 /* Find the note's rect */
1489 ncm
.lfMessageFont
.lfWeight
= note_weight
;
1490 if (infoPtr
->note
&& (font
= CreateFontIndirectW(&ncm
.lfMessageFont
)))
1492 HFONT tmp
= SelectObject(hdc
, font
);
1493 if (!old_font
) old_font
= tmp
;
1495 DrawTextW(hdc
, infoPtr
->note
, infoPtr
->note_length
, &text_bound
,
1496 flags
| DT_NOPREFIX
| DT_CALCRECT
);
1499 text_w
= max(text_w
, text_bound
.right
- text_bound
.left
);
1500 text_h
+= text_bound
.bottom
- text_bound
.top
+ 2;
1506 size
->cx
= min(size
->cx
, w
);
1507 size
->cy
= max(text_h
, img_size
.cy
) + margin
.top
+ margin
.bottom
;
1509 if (old_font
) SelectObject(hdc
, old_font
);
1510 ReleaseDC(infoPtr
->hwnd
, hdc
);
1514 /**********************************************************************
1515 * BUTTON_CalcLayoutRects
1517 * Calculates the rectangles of the button label(image and text) and its parts depending on a button's style.
1519 * Returns flags to be passed to DrawText.
1520 * Calculated rectangle doesn't take into account button state
1521 * (pushed, etc.). If there is nothing to draw (no text/image) output
1522 * rectangle is empty, and return value is (UINT)-1.
1525 * infoPtr [I] Button pointer
1526 * hdc [I] Handle to device context to draw to
1527 * labelRc [I/O] Input the rect the label to be positioned in, and output the label rect
1528 * imageRc [O] Optional, output the image rect
1529 * textRc [O] Optional, output the text rect
1531 static UINT
BUTTON_CalcLayoutRects(const BUTTON_INFO
*infoPtr
, HDC hdc
, RECT
*labelRc
, RECT
*imageRc
, RECT
*textRc
)
1533 LONG style
= GetWindowLongW( infoPtr
->hwnd
, GWL_STYLE
);
1534 LONG ex_style
= GetWindowLongW( infoPtr
->hwnd
, GWL_EXSTYLE
);
1535 LONG split_style
= infoPtr
->imagelist
.himl
? BUTTON_ILStoBS(infoPtr
->imagelist
.uAlign
) : style
;
1536 WCHAR
*text
= get_button_text(infoPtr
);
1537 SIZE imageSize
= BUTTON_GetImageSize(infoPtr
);
1538 UINT dtStyle
= BUTTON_BStoDT(style
, ex_style
);
1539 RECT labelRect
, imageRect
, imageRectWithMargin
, textRect
;
1540 LONG imageMarginWidth
, imageMarginHeight
;
1541 const RECT
*textMargin
= BUTTON_GetTextMargin(infoPtr
);
1542 RECT emptyMargin
= {0};
1545 /* Calculate label rectangle according to label type */
1546 if ((imageSize
.cx
== 0 && imageSize
.cy
== 0) && (text
== NULL
|| text
[0] == '\0'))
1548 SetRectEmpty(labelRc
);
1549 SetRectEmpty(imageRc
);
1550 SetRectEmpty(textRc
);
1555 SetRect(&imageRect
, 0, 0, imageSize
.cx
, imageSize
.cy
);
1556 imageRectWithMargin
= imageRect
;
1557 if (infoPtr
->imagelist
.himl
)
1559 imageRectWithMargin
.top
-= infoPtr
->imagelist
.margin
.top
;
1560 imageRectWithMargin
.bottom
+= infoPtr
->imagelist
.margin
.bottom
;
1561 imageRectWithMargin
.left
-= infoPtr
->imagelist
.margin
.left
;
1562 imageRectWithMargin
.right
+= infoPtr
->imagelist
.margin
.right
;
1565 /* Show image only */
1566 if (show_image_only(infoPtr
))
1568 BUTTON_PositionRect(style
, labelRc
, &imageRect
,
1569 infoPtr
->imagelist
.himl
? &infoPtr
->imagelist
.margin
: &emptyMargin
);
1570 labelRect
= imageRect
;
1571 SetRectEmpty(&textRect
);
1576 maxTextWidth
= labelRc
->right
- labelRc
->left
;
1577 textRect
= BUTTON_GetTextRect(infoPtr
, hdc
, text
, maxTextWidth
);
1579 /* Show image and text */
1580 if (show_image_and_text(infoPtr
))
1582 RECT boundingLabelRect
, boundingImageRect
, boundingTextRect
;
1584 /* Get label rect */
1585 /* Image list may have different alignment than the button, use the whole rect for label in this case */
1586 if (infoPtr
->imagelist
.himl
)
1587 labelRect
= *labelRc
;
1590 /* Get a label bounding rectangle to position the label in the user specified label rectangle because
1591 * text and image need to align together. */
1592 boundingLabelRect
= BUTTON_GetBoundingLabelRect(split_style
, &textRect
, &imageRectWithMargin
);
1593 BUTTON_PositionRect(split_style
, labelRc
, &boundingLabelRect
, &emptyMargin
);
1594 labelRect
= boundingLabelRect
;
1597 /* When imagelist has center align, use the whole rect for imagelist and text */
1598 if(infoPtr
->imagelist
.himl
&& infoPtr
->imagelist
.uAlign
== BUTTON_IMAGELIST_ALIGN_CENTER
)
1600 boundingImageRect
= labelRect
;
1601 boundingTextRect
= labelRect
;
1602 BUTTON_PositionRect(split_style
, &boundingImageRect
, &imageRect
,
1603 infoPtr
->imagelist
.himl
? &infoPtr
->imagelist
.margin
: &emptyMargin
);
1604 /* Text doesn't use imagelist align */
1605 BUTTON_PositionRect(style
, &boundingTextRect
, &textRect
, textMargin
);
1609 /* Get image rect */
1610 /* Split the label rect to two halves as two bounding rectangles for image and text */
1611 boundingImageRect
= labelRect
;
1612 imageMarginWidth
= imageRectWithMargin
.right
- imageRectWithMargin
.left
;
1613 imageMarginHeight
= imageRectWithMargin
.bottom
- imageRectWithMargin
.top
;
1614 if ((split_style
& BS_CENTER
) == BS_RIGHT
)
1615 boundingImageRect
.left
= boundingImageRect
.right
- imageMarginWidth
;
1616 else if ((split_style
& BS_CENTER
) == BS_LEFT
)
1617 boundingImageRect
.right
= boundingImageRect
.left
+ imageMarginWidth
;
1618 else if ((split_style
& BS_VCENTER
) == BS_BOTTOM
)
1619 boundingImageRect
.top
= boundingImageRect
.bottom
- imageMarginHeight
;
1620 else if ((split_style
& BS_VCENTER
) == BS_TOP
)
1621 boundingImageRect
.bottom
= boundingImageRect
.top
+ imageMarginHeight
;
1623 boundingImageRect
.right
= boundingImageRect
.left
+ imageMarginWidth
;
1624 BUTTON_PositionRect(split_style
, &boundingImageRect
, &imageRect
,
1625 infoPtr
->imagelist
.himl
? &infoPtr
->imagelist
.margin
: &emptyMargin
);
1628 SubtractRect(&boundingTextRect
, &labelRect
, &boundingImageRect
);
1629 /* Text doesn't use imagelist align */
1630 BUTTON_PositionRect(style
, &boundingTextRect
, &textRect
, textMargin
);
1633 /* Show text only */
1636 if (get_button_type(style
) != BS_GROUPBOX
)
1637 BUTTON_PositionRect(style
, labelRc
, &textRect
, textMargin
);
1639 /* GroupBox is always top aligned */
1640 BUTTON_PositionRect((style
& ~BS_VCENTER
) | BS_TOP
, labelRc
, &textRect
, textMargin
);
1641 labelRect
= textRect
;
1642 SetRectEmpty(&imageRect
);
1647 CopyRect(labelRc
, &labelRect
);
1648 CopyRect(imageRc
, &imageRect
);
1649 CopyRect(textRc
, &textRect
);
1655 /**********************************************************************
1658 * Draw the button's image into the specified rectangle.
1660 static void BUTTON_DrawImage(const BUTTON_INFO
*infoPtr
, HDC hdc
, HBRUSH hbr
, UINT flags
, const RECT
*rect
)
1662 if (infoPtr
->imagelist
.himl
)
1664 int i
= (ImageList_GetImageCount(infoPtr
->imagelist
.himl
) == 1) ? 0 : get_draw_state(infoPtr
) - 1;
1666 ImageList_Draw(infoPtr
->imagelist
.himl
, i
, hdc
, rect
->left
, rect
->top
, ILD_NORMAL
);
1670 switch (infoPtr
->image_type
)
1676 flags
|= DST_BITMAP
;
1682 DrawStateW(hdc
, hbr
, NULL
, (LPARAM
)infoPtr
->u
.image
, 0, rect
->left
, rect
->top
,
1683 rect
->right
- rect
->left
, rect
->bottom
- rect
->top
, flags
);
1688 /**********************************************************************
1689 * BUTTON_DrawTextCallback
1691 * Callback function used by DrawStateW function.
1693 static BOOL CALLBACK
BUTTON_DrawTextCallback(HDC hdc
, LPARAM lp
, WPARAM wp
, int cx
, int cy
)
1697 SetRect(&rc
, 0, 0, cx
, cy
);
1698 DrawTextW(hdc
, (LPCWSTR
)lp
, -1, &rc
, (UINT
)wp
);
1702 /**********************************************************************
1705 * Common function for drawing button label.
1708 * 1. When BS_SINGLELINE is specified and text contains '\t', '\n' or '\r' in the middle, they are rendered as
1709 * squares now whereas they should be ignored.
1710 * 2. When BS_MULTILINE is specified and text contains space in the middle, the space mistakenly be rendered as newline.
1712 static void BUTTON_DrawLabel(const BUTTON_INFO
*infoPtr
, HDC hdc
, UINT dtFlags
, const RECT
*imageRect
,
1713 const RECT
*textRect
)
1716 UINT flags
= IsWindowEnabled(infoPtr
->hwnd
) ? DSS_NORMAL
: DSS_DISABLED
;
1717 LONG style
= GetWindowLongW( infoPtr
->hwnd
, GWL_STYLE
);
1720 /* FIXME: To draw disabled label in Win31 look-and-feel, we probably
1721 * must use DSS_MONO flag and COLOR_GRAYTEXT brush (or maybe DSS_UNION).
1722 * I don't have Win31 on hand to verify that, so I leave it as is.
1725 if ((style
& BS_PUSHLIKE
) && (infoPtr
->state
& BST_INDETERMINATE
))
1727 hbr
= GetSysColorBrush(COLOR_GRAYTEXT
);
1731 if (show_image(infoPtr
)) BUTTON_DrawImage(infoPtr
, hdc
, hbr
, flags
, imageRect
);
1732 if (show_image_only(infoPtr
)) return;
1734 /* DST_COMPLEX -- is 0 */
1735 if (!(text
= get_button_text(infoPtr
))) return;
1736 DrawStateW(hdc
, hbr
, BUTTON_DrawTextCallback
, (LPARAM
)text
, dtFlags
, textRect
->left
, textRect
->top
,
1737 textRect
->right
- textRect
->left
, textRect
->bottom
- textRect
->top
, flags
);
1741 /**********************************************************************
1742 * Push Button Functions
1744 static void PB_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
)
1746 RECT rc
, labelRect
, imageRect
, textRect
;
1747 UINT dtFlags
, uState
;
1751 COLORREF oldTxtColor
;
1755 LONG state
= infoPtr
->state
;
1756 LONG style
= GetWindowLongW( infoPtr
->hwnd
, GWL_STYLE
);
1757 BOOL pushedState
= (state
& BST_PUSHED
);
1761 GetClientRect( infoPtr
->hwnd
, &rc
);
1763 /* Send WM_CTLCOLOR to allow changing the font (the colors are fixed) */
1764 if ((hFont
= infoPtr
->font
)) SelectObject( hDC
, hFont
);
1765 parent
= GetParent(infoPtr
->hwnd
);
1766 if (!parent
) parent
= infoPtr
->hwnd
;
1767 SendMessageW( parent
, WM_CTLCOLORBTN
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
1769 hrgn
= set_control_clipping( hDC
, &rc
);
1771 hpen
= CreatePen( PS_SOLID
, 1, GetSysColor(COLOR_WINDOWFRAME
));
1772 hOldPen
= SelectObject(hDC
, hpen
);
1773 hOldBrush
= SelectObject(hDC
,GetSysColorBrush(COLOR_BTNFACE
));
1774 oldBkMode
= SetBkMode(hDC
, TRANSPARENT
);
1776 init_custom_draw(&nmcd
, infoPtr
, hDC
, &rc
);
1778 /* Send erase notifications */
1779 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
1780 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
1782 if (get_button_type(style
) == BS_DEFPUSHBUTTON
)
1784 if (action
!= ODA_FOCUS
)
1785 Rectangle(hDC
, rc
.left
, rc
.top
, rc
.right
, rc
.bottom
);
1786 InflateRect( &rc
, -1, -1 );
1789 /* Skip the frame drawing if only focus has changed */
1790 if (action
!= ODA_FOCUS
)
1792 uState
= DFCS_BUTTONPUSH
;
1794 if (style
& BS_FLAT
)
1795 uState
|= DFCS_MONO
;
1796 else if (pushedState
)
1798 if (get_button_type(style
) == BS_DEFPUSHBUTTON
)
1799 uState
|= DFCS_FLAT
;
1801 uState
|= DFCS_PUSHED
;
1804 if (state
& (BST_CHECKED
| BST_INDETERMINATE
))
1805 uState
|= DFCS_CHECKED
;
1807 DrawFrameControl( hDC
, &rc
, DFC_BUTTON
, uState
);
1810 if (cdrf
& CDRF_NOTIFYPOSTERASE
)
1812 nmcd
.dwDrawStage
= CDDS_POSTERASE
;
1813 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
1816 /* Send paint notifications */
1817 nmcd
.dwDrawStage
= CDDS_PREPAINT
;
1818 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
1819 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
1821 if (!(cdrf
& CDRF_DOERASE
) && action
!= ODA_FOCUS
)
1823 /* draw button label */
1825 /* Shrink label rect at all sides by 2 so that the content won't touch the surrounding frame */
1826 InflateRect(&labelRect
, -2, -2);
1827 dtFlags
= BUTTON_CalcLayoutRects(infoPtr
, hDC
, &labelRect
, &imageRect
, &textRect
);
1829 if (dtFlags
!= (UINT
)-1L)
1831 if (pushedState
) OffsetRect(&labelRect
, 1, 1);
1833 oldTxtColor
= SetTextColor( hDC
, GetSysColor(COLOR_BTNTEXT
) );
1835 BUTTON_DrawLabel(infoPtr
, hDC
, dtFlags
, &imageRect
, &textRect
);
1837 SetTextColor( hDC
, oldTxtColor
);
1841 if (cdrf
& CDRF_NOTIFYPOSTPAINT
)
1843 nmcd
.dwDrawStage
= CDDS_POSTPAINT
;
1844 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
1846 if ((cdrf
& CDRF_SKIPPOSTPAINT
) || dtFlags
== (UINT
)-1L) goto cleanup
;
1848 if (action
== ODA_FOCUS
|| (state
& BST_FOCUS
))
1850 InflateRect( &rc
, -2, -2 );
1851 DrawFocusRect( hDC
, &rc
);
1855 SelectObject( hDC
, hOldPen
);
1856 SelectObject( hDC
, hOldBrush
);
1857 SetBkMode(hDC
, oldBkMode
);
1858 SelectClipRgn( hDC
, hrgn
);
1859 if (hrgn
) DeleteObject( hrgn
);
1860 DeleteObject( hpen
);
1863 /**********************************************************************
1864 * Check Box & Radio Button Functions
1867 static void CB_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
)
1869 RECT rbox
, labelRect
, imageRect
, textRect
, client
;
1871 int delta
, text_offset
, checkBoxWidth
, checkBoxHeight
;
1876 LONG state
= infoPtr
->state
;
1877 LONG style
= GetWindowLongW( infoPtr
->hwnd
, GWL_STYLE
);
1878 LONG ex_style
= GetWindowLongW( infoPtr
->hwnd
, GWL_EXSTYLE
);
1882 if (style
& BS_PUSHLIKE
)
1884 PB_Paint( infoPtr
, hDC
, action
);
1888 GetClientRect(infoPtr
->hwnd
, &client
);
1889 rbox
= labelRect
= client
;
1891 checkBoxWidth
= 12 * GetDpiForWindow( infoPtr
->hwnd
) / 96 + 1;
1892 checkBoxHeight
= 12 * GetDpiForWindow( infoPtr
->hwnd
) / 96 + 1;
1894 if ((hFont
= infoPtr
->font
)) SelectObject( hDC
, hFont
);
1895 GetCharWidthW( hDC
, '0', '0', &text_offset
);
1898 parent
= GetParent(infoPtr
->hwnd
);
1899 if (!parent
) parent
= infoPtr
->hwnd
;
1900 hBrush
= (HBRUSH
)SendMessageW(parent
, WM_CTLCOLORSTATIC
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
1901 if (!hBrush
) /* did the app forget to call defwindowproc ? */
1902 hBrush
= (HBRUSH
)DefWindowProcW(parent
, WM_CTLCOLORSTATIC
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
1903 hrgn
= set_control_clipping( hDC
, &client
);
1905 if (style
& BS_LEFTTEXT
|| ex_style
& WS_EX_RIGHT
)
1907 labelRect
.right
-= checkBoxWidth
+ text_offset
;
1908 rbox
.left
= rbox
.right
- checkBoxWidth
;
1912 labelRect
.left
+= checkBoxWidth
+ text_offset
;
1913 rbox
.right
= checkBoxWidth
;
1916 init_custom_draw(&nmcd
, infoPtr
, hDC
, &client
);
1918 /* Send erase notifications */
1919 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
1920 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
1922 /* Since WM_ERASEBKGND does nothing, first prepare background */
1923 if (action
== ODA_SELECT
) FillRect( hDC
, &rbox
, hBrush
);
1924 if (action
== ODA_DRAWENTIRE
) FillRect( hDC
, &client
, hBrush
);
1925 if (cdrf
& CDRF_NOTIFYPOSTERASE
)
1927 nmcd
.dwDrawStage
= CDDS_POSTERASE
;
1928 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
1933 dtFlags
= BUTTON_CalcLayoutRects(infoPtr
, hDC
, &labelRect
, &imageRect
, &textRect
);
1935 /* Only adjust rbox when rtext is valid */
1936 if (dtFlags
!= (UINT
)-1L)
1938 rbox
.top
= labelRect
.top
;
1939 rbox
.bottom
= labelRect
.bottom
;
1942 /* Send paint notifications */
1943 nmcd
.dwDrawStage
= CDDS_PREPAINT
;
1944 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
1945 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
1947 /* Draw the check-box bitmap */
1948 if (!(cdrf
& CDRF_DOERASE
))
1950 if (action
== ODA_DRAWENTIRE
|| action
== ODA_SELECT
)
1954 if ((get_button_type(style
) == BS_RADIOBUTTON
) ||
1955 (get_button_type(style
) == BS_AUTORADIOBUTTON
)) flags
= DFCS_BUTTONRADIO
;
1956 else if (state
& BST_INDETERMINATE
) flags
= DFCS_BUTTON3STATE
;
1957 else flags
= DFCS_BUTTONCHECK
;
1959 if (state
& (BST_CHECKED
| BST_INDETERMINATE
)) flags
|= DFCS_CHECKED
;
1960 if (state
& BST_PUSHED
) flags
|= DFCS_PUSHED
;
1961 if (style
& WS_DISABLED
) flags
|= DFCS_INACTIVE
;
1963 /* rbox must have the correct height */
1964 delta
= rbox
.bottom
- rbox
.top
- checkBoxHeight
;
1966 if ((style
& BS_VCENTER
) == BS_TOP
)
1969 rbox
.bottom
= rbox
.top
+ checkBoxHeight
;
1972 rbox
.top
-= -delta
/ 2 + 1;
1973 rbox
.bottom
= rbox
.top
+ checkBoxHeight
;
1976 else if ((style
& BS_VCENTER
) == BS_BOTTOM
)
1979 rbox
.top
= rbox
.bottom
- checkBoxHeight
;
1982 rbox
.bottom
+= -delta
/ 2 + 1;
1983 rbox
.top
= rbox
.bottom
- checkBoxHeight
;
1990 int ofs
= delta
/ 2;
1991 rbox
.bottom
-= ofs
+ 1;
1992 rbox
.top
= rbox
.bottom
- checkBoxHeight
;
1996 int ofs
= -delta
/ 2;
1997 rbox
.top
-= ofs
+ 1;
1998 rbox
.bottom
= rbox
.top
+ checkBoxHeight
;
2002 DrawFrameControl(hDC
, &rbox
, DFC_BUTTON
, flags
);
2005 if (dtFlags
!= (UINT
)-1L) /* Something to draw */
2006 if (action
== ODA_DRAWENTIRE
) BUTTON_DrawLabel(infoPtr
, hDC
, dtFlags
, &imageRect
, &textRect
);
2009 if (cdrf
& CDRF_NOTIFYPOSTPAINT
)
2011 nmcd
.dwDrawStage
= CDDS_POSTPAINT
;
2012 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2014 if ((cdrf
& CDRF_SKIPPOSTPAINT
) || dtFlags
== (UINT
)-1L) goto cleanup
;
2017 if (action
== ODA_FOCUS
|| (state
& BST_FOCUS
))
2021 IntersectRect(&labelRect
, &labelRect
, &client
);
2022 DrawFocusRect(hDC
, &labelRect
);
2026 SelectClipRgn( hDC
, hrgn
);
2027 if (hrgn
) DeleteObject( hrgn
);
2031 /**********************************************************************
2032 * BUTTON_CheckAutoRadioButton
2034 * hwnd is checked, uncheck every other auto radio button in group
2036 static void BUTTON_CheckAutoRadioButton( HWND hwnd
)
2038 HWND parent
, sibling
, start
;
2040 parent
= GetParent(hwnd
);
2041 /* make sure that starting control is not disabled or invisible */
2042 start
= sibling
= GetNextDlgGroupItem( parent
, hwnd
, TRUE
);
2045 if (!sibling
) break;
2046 if ((hwnd
!= sibling
) &&
2047 ((GetWindowLongW( sibling
, GWL_STYLE
) & BS_TYPEMASK
) == BS_AUTORADIOBUTTON
))
2048 SendMessageW( sibling
, BM_SETCHECK
, BST_UNCHECKED
, 0 );
2049 sibling
= GetNextDlgGroupItem( parent
, sibling
, FALSE
);
2050 } while (sibling
!= start
);
2054 /**********************************************************************
2055 * Group Box Functions
2058 static void GB_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
)
2060 RECT labelRect
, imageRect
, textRect
, rcFrame
;
2065 LONG style
= GetWindowLongW( infoPtr
->hwnd
, GWL_STYLE
);
2069 if ((hFont
= infoPtr
->font
)) SelectObject( hDC
, hFont
);
2070 /* GroupBox acts like static control, so it sends CTLCOLORSTATIC */
2071 parent
= GetParent(infoPtr
->hwnd
);
2072 if (!parent
) parent
= infoPtr
->hwnd
;
2073 hbr
= (HBRUSH
)SendMessageW(parent
, WM_CTLCOLORSTATIC
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
2074 if (!hbr
) /* did the app forget to call defwindowproc ? */
2075 hbr
= (HBRUSH
)DefWindowProcW(parent
, WM_CTLCOLORSTATIC
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
2076 GetClientRect(infoPtr
->hwnd
, &labelRect
);
2077 rcFrame
= labelRect
;
2078 hrgn
= set_control_clipping(hDC
, &labelRect
);
2080 GetTextMetricsW (hDC
, &tm
);
2081 rcFrame
.top
+= (tm
.tmHeight
/ 2) - 1;
2082 DrawEdge (hDC
, &rcFrame
, EDGE_ETCHED
, BF_RECT
| ((style
& BS_FLAT
) ? BF_FLAT
: 0));
2084 InflateRect(&labelRect
, -7, 1);
2085 dtFlags
= BUTTON_CalcLayoutRects(infoPtr
, hDC
, &labelRect
, &imageRect
, &textRect
);
2087 if (dtFlags
!= (UINT
)-1)
2089 /* Because buttons have CS_PARENTDC class style, there is a chance
2090 * that label will be drawn out of client rect.
2091 * But Windows doesn't clip label's rect, so do I.
2094 /* There is 1-pixel margin at the left, right, and bottom */
2098 FillRect(hDC
, &labelRect
, hbr
);
2103 BUTTON_DrawLabel(infoPtr
, hDC
, dtFlags
, &imageRect
, &textRect
);
2105 SelectClipRgn( hDC
, hrgn
);
2106 if (hrgn
) DeleteObject( hrgn
);
2110 /**********************************************************************
2111 * User Button Functions
2114 static void UB_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
)
2121 LONG state
= infoPtr
->state
;
2124 GetClientRect( infoPtr
->hwnd
, &rc
);
2126 if ((hFont
= infoPtr
->font
)) SelectObject( hDC
, hFont
);
2128 parent
= GetParent(infoPtr
->hwnd
);
2129 if (!parent
) parent
= infoPtr
->hwnd
;
2130 hBrush
= (HBRUSH
)SendMessageW(parent
, WM_CTLCOLORBTN
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
2131 if (!hBrush
) /* did the app forget to call defwindowproc ? */
2132 hBrush
= (HBRUSH
)DefWindowProcW(parent
, WM_CTLCOLORBTN
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
2134 if (action
== ODA_FOCUS
|| (state
& BST_FOCUS
))
2136 init_custom_draw(&nmcd
, infoPtr
, hDC
, &rc
);
2138 /* Send erase notifications */
2139 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2140 if (cdrf
& CDRF_SKIPDEFAULT
) goto notify
;
2143 FillRect( hDC
, &rc
, hBrush
);
2144 if (action
== ODA_FOCUS
|| (state
& BST_FOCUS
))
2146 if (cdrf
& CDRF_NOTIFYPOSTERASE
)
2148 nmcd
.dwDrawStage
= CDDS_POSTERASE
;
2149 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2152 /* Send paint notifications */
2153 nmcd
.dwDrawStage
= CDDS_PREPAINT
;
2154 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2155 if (cdrf
& CDRF_SKIPDEFAULT
) goto notify
;
2156 if (cdrf
& CDRF_NOTIFYPOSTPAINT
)
2158 nmcd
.dwDrawStage
= CDDS_POSTPAINT
;
2159 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2162 if (!(cdrf
& CDRF_SKIPPOSTPAINT
))
2163 DrawFocusRect( hDC
, &rc
);
2170 BUTTON_NOTIFY_PARENT( infoPtr
->hwnd
, (state
& BST_FOCUS
) ? BN_SETFOCUS
: BN_KILLFOCUS
);
2174 BUTTON_NOTIFY_PARENT( infoPtr
->hwnd
, (state
& BST_PUSHED
) ? BN_HILITE
: BN_UNHILITE
);
2183 /**********************************************************************
2184 * Ownerdrawn Button Functions
2187 static void OB_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
)
2189 LONG state
= infoPtr
->state
;
2191 LONG_PTR id
= GetWindowLongPtrW( infoPtr
->hwnd
, GWLP_ID
);
2196 dis
.CtlType
= ODT_BUTTON
;
2199 dis
.itemAction
= action
;
2200 dis
.itemState
= ((state
& BST_FOCUS
) ? ODS_FOCUS
: 0) |
2201 ((state
& BST_PUSHED
) ? ODS_SELECTED
: 0) |
2202 (IsWindowEnabled(infoPtr
->hwnd
) ? 0: ODS_DISABLED
);
2203 dis
.hwndItem
= infoPtr
->hwnd
;
2206 GetClientRect( infoPtr
->hwnd
, &dis
.rcItem
);
2208 if ((hFont
= infoPtr
->font
)) SelectObject( hDC
, hFont
);
2209 parent
= GetParent(infoPtr
->hwnd
);
2210 if (!parent
) parent
= infoPtr
->hwnd
;
2211 SendMessageW( parent
, WM_CTLCOLORBTN
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
2213 hrgn
= set_control_clipping( hDC
, &dis
.rcItem
);
2215 SendMessageW( GetParent(infoPtr
->hwnd
), WM_DRAWITEM
, id
, (LPARAM
)&dis
);
2216 SelectClipRgn( hDC
, hrgn
);
2217 if (hrgn
) DeleteObject( hrgn
);
2221 /**********************************************************************
2222 * Split Button Functions
2224 static void SB_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
)
2226 LONG style
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
2227 LONG state
= infoPtr
->state
;
2228 UINT dtFlags
= (UINT
)-1L;
2230 RECT rc
, push_rect
, dropdown_rect
;
2239 GetClientRect(infoPtr
->hwnd
, &rc
);
2241 /* Send WM_CTLCOLOR to allow changing the font (the colors are fixed) */
2242 if (infoPtr
->font
) SelectObject(hDC
, infoPtr
->font
);
2243 if (!(parent
= GetParent(infoPtr
->hwnd
))) parent
= infoPtr
->hwnd
;
2244 SendMessageW(parent
, WM_CTLCOLORBTN
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
2246 hrgn
= set_control_clipping(hDC
, &rc
);
2248 pen
= CreatePen(PS_SOLID
, 1, GetSysColor(COLOR_WINDOWFRAME
));
2249 old_pen
= SelectObject(hDC
, pen
);
2250 old_brush
= SelectObject(hDC
, GetSysColorBrush(COLOR_BTNFACE
));
2251 old_bk_mode
= SetBkMode(hDC
, TRANSPARENT
);
2253 init_custom_draw(&nmcd
, infoPtr
, hDC
, &rc
);
2255 /* Send erase notifications */
2256 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2257 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
2259 if (get_button_type(style
) == BS_DEFSPLITBUTTON
)
2261 if (action
!= ODA_FOCUS
)
2262 Rectangle(hDC
, rc
.left
, rc
.top
, rc
.right
, rc
.bottom
);
2263 InflateRect(&rc
, -1, -1);
2264 /* The split will now be off by 1 pixel, but
2265 that's exactly what Windows does as well */
2268 get_split_button_rects(infoPtr
, &rc
, &push_rect
, &dropdown_rect
);
2269 if (infoPtr
->split_style
& BCSS_NOSPLIT
)
2272 /* Skip the frame drawing if only focus has changed */
2273 if (action
!= ODA_FOCUS
)
2275 UINT flags
= DFCS_BUTTONPUSH
;
2277 if (style
& BS_FLAT
) flags
|= DFCS_MONO
;
2278 else if (state
& BST_PUSHED
)
2279 flags
|= (get_button_type(style
) == BS_DEFSPLITBUTTON
)
2280 ? DFCS_FLAT
: DFCS_PUSHED
;
2282 if (state
& (BST_CHECKED
| BST_INDETERMINATE
))
2283 flags
|= DFCS_CHECKED
;
2285 if (infoPtr
->split_style
& BCSS_NOSPLIT
)
2286 DrawFrameControl(hDC
, &push_rect
, DFC_BUTTON
, flags
);
2289 UINT dropdown_flags
= flags
& ~DFCS_CHECKED
;
2291 if (state
& BST_DROPDOWNPUSHED
)
2292 dropdown_flags
= (dropdown_flags
& ~DFCS_FLAT
) | DFCS_PUSHED
;
2294 /* Adjust for shadow and draw order so it looks properly */
2295 if (infoPtr
->split_style
& BCSS_ALIGNLEFT
)
2297 dropdown_rect
.right
++;
2298 DrawFrameControl(hDC
, &dropdown_rect
, DFC_BUTTON
, dropdown_flags
);
2299 dropdown_rect
.right
--;
2300 DrawFrameControl(hDC
, &push_rect
, DFC_BUTTON
, flags
);
2305 DrawFrameControl(hDC
, &push_rect
, DFC_BUTTON
, flags
);
2307 DrawFrameControl(hDC
, &dropdown_rect
, DFC_BUTTON
, dropdown_flags
);
2312 if (cdrf
& CDRF_NOTIFYPOSTERASE
)
2314 nmcd
.dwDrawStage
= CDDS_POSTERASE
;
2315 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2318 /* Send paint notifications */
2319 nmcd
.dwDrawStage
= CDDS_PREPAINT
;
2320 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2321 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
2323 /* Shrink push button rect so that the content won't touch the surrounding frame */
2324 InflateRect(&push_rect
, -2, -2);
2326 if (!(cdrf
& CDRF_DOERASE
) && action
!= ODA_FOCUS
)
2328 COLORREF old_color
= SetTextColor(hDC
, GetSysColor(COLOR_BTNTEXT
));
2329 RECT label_rect
= push_rect
, image_rect
, text_rect
;
2331 dtFlags
= BUTTON_CalcLayoutRects(infoPtr
, hDC
, &label_rect
, &image_rect
, &text_rect
);
2333 if (dtFlags
!= (UINT
)-1L)
2334 BUTTON_DrawLabel(infoPtr
, hDC
, dtFlags
, &image_rect
, &text_rect
);
2336 draw_split_button_dropdown_glyph(infoPtr
, hDC
, &dropdown_rect
);
2337 SetTextColor(hDC
, old_color
);
2340 if (cdrf
& CDRF_NOTIFYPOSTPAINT
)
2342 nmcd
.dwDrawStage
= CDDS_POSTPAINT
;
2343 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2345 if ((cdrf
& CDRF_SKIPPOSTPAINT
) || dtFlags
== (UINT
)-1L) goto cleanup
;
2347 if (action
== ODA_FOCUS
|| (state
& BST_FOCUS
))
2348 DrawFocusRect(hDC
, &push_rect
);
2351 SelectObject(hDC
, old_pen
);
2352 SelectObject(hDC
, old_brush
);
2353 SetBkMode(hDC
, old_bk_mode
);
2354 SelectClipRgn(hDC
, hrgn
);
2355 if (hrgn
) DeleteObject(hrgn
);
2359 /* Given the full button rect of the split button, retrieve the push part and the dropdown part */
2360 static inline void get_split_button_rects(const BUTTON_INFO
*infoPtr
, const RECT
*button_rect
,
2361 RECT
*push_rect
, RECT
*dropdown_rect
)
2363 *push_rect
= *dropdown_rect
= *button_rect
;
2365 /* The dropdown takes priority if the client rect is too small, it will only have a dropdown */
2366 if (infoPtr
->split_style
& BCSS_ALIGNLEFT
)
2368 dropdown_rect
->right
= min(button_rect
->left
+ infoPtr
->glyph_size
.cx
, button_rect
->right
);
2369 push_rect
->left
= dropdown_rect
->right
;
2373 dropdown_rect
->left
= max(button_rect
->right
- infoPtr
->glyph_size
.cx
, button_rect
->left
);
2374 push_rect
->right
= dropdown_rect
->left
;
2378 /* Notify the parent if the point is within the dropdown and return TRUE (always notify if NULL) */
2379 static BOOL
notify_split_button_dropdown(const BUTTON_INFO
*infoPtr
, const POINT
*pt
, HWND hwnd
)
2383 GetClientRect(hwnd
, &nmbcd
.rcButton
);
2386 RECT push_rect
, dropdown_rect
;
2388 get_split_button_rects(infoPtr
, &nmbcd
.rcButton
, &push_rect
, &dropdown_rect
);
2389 if (!PtInRect(&dropdown_rect
, *pt
))
2392 /* If it's already down (set manually via BCM_SETDROPDOWNSTATE), fake the notify */
2393 if (infoPtr
->state
& BST_DROPDOWNPUSHED
)
2396 SendMessageW(hwnd
, BCM_SETDROPDOWNSTATE
, TRUE
, 0);
2398 nmbcd
.hdr
.hwndFrom
= hwnd
;
2399 nmbcd
.hdr
.idFrom
= GetWindowLongPtrW(hwnd
, GWLP_ID
);
2400 nmbcd
.hdr
.code
= BCN_DROPDOWN
;
2401 SendMessageW(GetParent(hwnd
), WM_NOTIFY
, nmbcd
.hdr
.idFrom
, (LPARAM
)&nmbcd
);
2403 SendMessageW(hwnd
, BCM_SETDROPDOWNSTATE
, FALSE
, 0);
2407 /* Draw the split button dropdown glyph or image */
2408 static void draw_split_button_dropdown_glyph(const BUTTON_INFO
*infoPtr
, HDC hdc
, RECT
*rect
)
2410 if (infoPtr
->split_style
& BCSS_IMAGE
)
2414 /* When the glyph is an image list, Windows is very buggy with BCSS_STRETCH,
2415 positions it weirdly and doesn't even stretch it, but instead extends the
2416 image, leaking into other images in the list (or black if none). Instead,
2417 we'll ignore this and just position it at center as without BCSS_STRETCH. */
2418 if (!ImageList_GetIconSize(infoPtr
->glyph
, &w
, &h
)) return;
2420 ImageList_Draw(infoPtr
->glyph
,
2421 (ImageList_GetImageCount(infoPtr
->glyph
) == 1) ? 0 : get_draw_state(infoPtr
) - 1,
2422 hdc
, rect
->left
+ (rect
->right
- rect
->left
- w
) / 2,
2423 rect
->top
+ (rect
->bottom
- rect
->top
- h
) / 2, ILD_NORMAL
);
2425 else if (infoPtr
->glyph_size
.cy
>= 0)
2427 /* infoPtr->glyph is a character code from Marlett */
2428 HFONT font
, old_font
;
2429 LOGFONTW logfont
= { 0, 0, 0, 0, FW_NORMAL
, 0, 0, 0, SYMBOL_CHARSET
, 0, 0, 0, 0,
2431 if (infoPtr
->glyph_size
.cy
)
2433 /* BCSS_STRETCH preserves aspect ratio, uses minimum as size */
2434 if (infoPtr
->split_style
& BCSS_STRETCH
)
2435 logfont
.lfHeight
= min(infoPtr
->glyph_size
.cx
, infoPtr
->glyph_size
.cy
);
2438 logfont
.lfWidth
= infoPtr
->glyph_size
.cx
;
2439 logfont
.lfHeight
= infoPtr
->glyph_size
.cy
;
2442 else logfont
.lfHeight
= infoPtr
->glyph_size
.cx
;
2444 if ((font
= CreateFontIndirectW(&logfont
)))
2446 old_font
= SelectObject(hdc
, font
);
2447 DrawTextW(hdc
, (const WCHAR
*)&infoPtr
->glyph
, 1, rect
,
2448 DT_CENTER
| DT_VCENTER
| DT_SINGLELINE
| DT_NOCLIP
| DT_NOPREFIX
);
2449 SelectObject(hdc
, old_font
);
2456 /**********************************************************************
2457 * Command Link Functions
2459 static void CL_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
)
2461 LONG style
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
2462 LONG state
= infoPtr
->state
;
2464 RECT rc
, content_rect
;
2473 GetClientRect(infoPtr
->hwnd
, &rc
);
2475 /* Command Links are not affected by the button's font, and are based
2476 on the default message font. Furthermore, they are not affected by
2477 any of the alignment styles (and always align with the top-left). */
2478 if (!(parent
= GetParent(infoPtr
->hwnd
))) parent
= infoPtr
->hwnd
;
2479 SendMessageW(parent
, WM_CTLCOLORBTN
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
2481 hrgn
= set_control_clipping(hDC
, &rc
);
2483 pen
= CreatePen(PS_SOLID
, 1, GetSysColor(COLOR_WINDOWFRAME
));
2484 old_pen
= SelectObject(hDC
, pen
);
2485 old_brush
= SelectObject(hDC
, GetSysColorBrush(COLOR_BTNFACE
));
2486 old_bk_mode
= SetBkMode(hDC
, TRANSPARENT
);
2488 init_custom_draw(&nmcd
, infoPtr
, hDC
, &rc
);
2490 /* Send erase notifications */
2491 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2492 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
2495 if (get_button_type(style
) == BS_DEFCOMMANDLINK
)
2497 if (action
!= ODA_FOCUS
)
2498 Rectangle(hDC
, rc
.left
, rc
.top
, rc
.right
, rc
.bottom
);
2499 InflateRect(&rc
, -1, -1);
2502 /* Skip the frame drawing if only focus has changed */
2503 if (action
!= ODA_FOCUS
)
2505 if (!(state
& (BST_HOT
| BST_PUSHED
| BST_CHECKED
| BST_INDETERMINATE
)))
2506 FillRect(hDC
, &rc
, GetSysColorBrush(COLOR_BTNFACE
));
2509 UINT flags
= DFCS_BUTTONPUSH
;
2511 if (style
& BS_FLAT
) flags
|= DFCS_MONO
;
2512 else if (state
& BST_PUSHED
) flags
|= DFCS_PUSHED
;
2514 if (state
& (BST_CHECKED
| BST_INDETERMINATE
))
2515 flags
|= DFCS_CHECKED
;
2516 DrawFrameControl(hDC
, &rc
, DFC_BUTTON
, flags
);
2520 if (cdrf
& CDRF_NOTIFYPOSTERASE
)
2522 nmcd
.dwDrawStage
= CDDS_POSTERASE
;
2523 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2526 /* Send paint notifications */
2527 nmcd
.dwDrawStage
= CDDS_PREPAINT
;
2528 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2529 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
2531 if (!(cdrf
& CDRF_DOERASE
) && action
!= ODA_FOCUS
)
2533 UINT flags
= IsWindowEnabled(infoPtr
->hwnd
) ? DSS_NORMAL
: DSS_DISABLED
;
2534 COLORREF old_color
= SetTextColor(hDC
, GetSysColor(flags
== DSS_NORMAL
?
2535 COLOR_BTNTEXT
: COLOR_GRAYTEXT
));
2536 HIMAGELIST defimg
= NULL
;
2537 NONCLIENTMETRICSW ncm
;
2541 /* Command Links ignore the margins of the image list or its alignment */
2542 if (infoPtr
->u
.image
|| infoPtr
->imagelist
.himl
)
2543 img_size
= BUTTON_GetImageSize(infoPtr
);
2546 img_size
.cx
= img_size
.cy
= command_link_defglyph_size
;
2547 defimg
= ImageList_LoadImageW(COMCTL32_hModule
, (LPCWSTR
)MAKEINTRESOURCE(IDB_CMDLINK
),
2548 img_size
.cx
, 3, CLR_NONE
, IMAGE_BITMAP
, LR_CREATEDIBSECTION
);
2551 /* Shrink rect by the command link margin, except on bottom (just the frame) */
2552 InflateRect(&content_rect
, -command_link_margin
, -command_link_margin
);
2553 content_rect
.bottom
+= command_link_margin
- 2;
2555 ncm
.cbSize
= sizeof(ncm
);
2556 if (SystemParametersInfoW(SPI_GETNONCLIENTMETRICS
, sizeof(ncm
), &ncm
, 0))
2558 LONG note_weight
= ncm
.lfMessageFont
.lfWeight
;
2559 RECT r
= content_rect
;
2563 if (img_size
.cx
) r
.left
+= img_size
.cx
+ command_link_margin
;
2566 ncm
.lfMessageFont
.lfWeight
= FW_BOLD
;
2567 if ((font
= CreateFontIndirectW(&ncm
.lfMessageFont
)))
2569 if ((text
= get_button_text(infoPtr
)))
2571 SelectObject(hDC
, font
);
2572 txt_h
= DrawTextW(hDC
, text
, -1, &r
,
2573 DT_TOP
| DT_LEFT
| DT_WORDBREAK
| DT_END_ELLIPSIS
);
2580 ncm
.lfMessageFont
.lfWeight
= note_weight
;
2581 if (infoPtr
->note
&& (font
= CreateFontIndirectW(&ncm
.lfMessageFont
)))
2584 SelectObject(hDC
, font
);
2585 DrawTextW(hDC
, infoPtr
->note
, infoPtr
->note_length
, &r
,
2586 DT_TOP
| DT_LEFT
| DT_WORDBREAK
| DT_NOPREFIX
);
2591 /* Position the image at the vertical center of the drawn text (not note) */
2592 txt_h
= min(txt_h
, content_rect
.bottom
- content_rect
.top
);
2593 if (img_size
.cy
< txt_h
) content_rect
.top
+= (txt_h
- img_size
.cy
) / 2;
2595 content_rect
.right
= content_rect
.left
+ img_size
.cx
;
2596 content_rect
.bottom
= content_rect
.top
+ img_size
.cy
;
2601 if (flags
== DSS_DISABLED
) i
= 2;
2602 else if (state
& BST_HOT
) i
= 1;
2604 ImageList_Draw(defimg
, i
, hDC
, content_rect
.left
, content_rect
.top
, ILD_NORMAL
);
2605 ImageList_Destroy(defimg
);
2608 BUTTON_DrawImage(infoPtr
, hDC
, NULL
, flags
, &content_rect
);
2610 SetTextColor(hDC
, old_color
);
2613 if (cdrf
& CDRF_NOTIFYPOSTPAINT
)
2615 nmcd
.dwDrawStage
= CDDS_POSTPAINT
;
2616 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2618 if (cdrf
& CDRF_SKIPPOSTPAINT
) goto cleanup
;
2620 if (action
== ODA_FOCUS
|| (state
& BST_FOCUS
))
2622 InflateRect(&rc
, -2, -2);
2623 DrawFocusRect(hDC
, &rc
);
2627 SelectObject(hDC
, old_pen
);
2628 SelectObject(hDC
, old_brush
);
2629 SetBkMode(hDC
, old_bk_mode
);
2630 SelectClipRgn(hDC
, hrgn
);
2631 if (hrgn
) DeleteObject(hrgn
);
2636 /**********************************************************************
2637 * Themed Paint Functions
2639 static void PB_ThemedPaint(HTHEME theme
, const BUTTON_INFO
*infoPtr
, HDC hDC
, int state
, UINT dtFlags
, BOOL focused
)
2641 RECT bgRect
, textRect
;
2642 HFONT font
= infoPtr
->font
;
2643 HFONT hPrevFont
= font
? SelectObject(hDC
, font
) : NULL
;
2649 GetClientRect(infoPtr
->hwnd
, &bgRect
);
2650 GetThemeBackgroundContentRect(theme
, hDC
, BP_PUSHBUTTON
, state
, &bgRect
, &textRect
);
2651 init_custom_draw(&nmcd
, infoPtr
, hDC
, &bgRect
);
2653 parent
= GetParent(infoPtr
->hwnd
);
2654 if (!parent
) parent
= infoPtr
->hwnd
;
2656 /* Send erase notifications */
2657 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2658 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
2660 if (IsThemeBackgroundPartiallyTransparent(theme
, BP_PUSHBUTTON
, state
))
2661 DrawThemeParentBackground(infoPtr
->hwnd
, hDC
, NULL
);
2662 DrawThemeBackground(theme
, hDC
, BP_PUSHBUTTON
, state
, &bgRect
, NULL
);
2664 if (cdrf
& CDRF_NOTIFYPOSTERASE
)
2666 nmcd
.dwDrawStage
= CDDS_POSTERASE
;
2667 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2670 /* Send paint notifications */
2671 nmcd
.dwDrawStage
= CDDS_PREPAINT
;
2672 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2673 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
2675 if (!(cdrf
& CDRF_DOERASE
) && (text
= get_button_text(infoPtr
)))
2677 DrawThemeText(theme
, hDC
, BP_PUSHBUTTON
, state
, text
, lstrlenW(text
), dtFlags
, 0, &textRect
);
2681 if (cdrf
& CDRF_NOTIFYPOSTPAINT
)
2683 nmcd
.dwDrawStage
= CDDS_POSTPAINT
;
2684 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2686 if (cdrf
& CDRF_SKIPPOSTPAINT
) goto cleanup
;
2691 RECT focusRect
= bgRect
;
2693 GetThemeMargins(theme
, hDC
, BP_PUSHBUTTON
, state
, TMT_CONTENTMARGINS
, NULL
, &margins
);
2695 focusRect
.left
+= margins
.cxLeftWidth
;
2696 focusRect
.top
+= margins
.cyTopHeight
;
2697 focusRect
.right
-= margins
.cxRightWidth
;
2698 focusRect
.bottom
-= margins
.cyBottomHeight
;
2700 DrawFocusRect( hDC
, &focusRect
);
2704 if (hPrevFont
) SelectObject(hDC
, hPrevFont
);
2707 static void CB_ThemedPaint(HTHEME theme
, const BUTTON_INFO
*infoPtr
, HDC hDC
, int state
, UINT dtFlags
, BOOL focused
)
2710 RECT bgRect
, textRect
;
2711 HFONT font
, hPrevFont
= NULL
;
2712 DWORD dwStyle
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
2713 UINT btn_type
= get_button_type( dwStyle
);
2714 int part
= (btn_type
== BS_RADIOBUTTON
) || (btn_type
== BS_AUTORADIOBUTTON
) ? BP_RADIOBUTTON
: BP_CHECKBOX
;
2720 BOOL created_font
= FALSE
;
2722 HRESULT hr
= GetThemeFont(theme
, hDC
, part
, state
, TMT_FONT
, &lf
);
2723 if (SUCCEEDED(hr
)) {
2724 font
= CreateFontIndirectW(&lf
);
2726 TRACE("Failed to create font\n");
2728 TRACE("font = %s\n", debugstr_w(lf
.lfFaceName
));
2729 hPrevFont
= SelectObject(hDC
, font
);
2730 created_font
= TRUE
;
2733 font
= (HFONT
)SendMessageW(infoPtr
->hwnd
, WM_GETFONT
, 0, 0);
2734 hPrevFont
= SelectObject(hDC
, font
);
2737 if (FAILED(GetThemePartSize(theme
, hDC
, part
, state
, NULL
, TS_DRAW
, &sz
)))
2740 GetClientRect(infoPtr
->hwnd
, &bgRect
);
2741 GetThemeBackgroundContentRect(theme
, hDC
, part
, state
, &bgRect
, &textRect
);
2742 init_custom_draw(&nmcd
, infoPtr
, hDC
, &bgRect
);
2744 if (dtFlags
& DT_SINGLELINE
) /* Center the checkbox / radio button to the text. */
2745 bgRect
.top
= bgRect
.top
+ (textRect
.bottom
- textRect
.top
- sz
.cy
) / 2;
2747 /* adjust for the check/radio marker */
2748 bgRect
.bottom
= bgRect
.top
+ sz
.cy
;
2749 bgRect
.right
= bgRect
.left
+ sz
.cx
;
2750 textRect
.left
= bgRect
.right
+ 6;
2752 parent
= GetParent(infoPtr
->hwnd
);
2753 if (!parent
) parent
= infoPtr
->hwnd
;
2755 /* Send erase notifications */
2756 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2757 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
2759 DrawThemeParentBackground(infoPtr
->hwnd
, hDC
, NULL
);
2760 DrawThemeBackground(theme
, hDC
, part
, state
, &bgRect
, NULL
);
2762 if (cdrf
& CDRF_NOTIFYPOSTERASE
)
2764 nmcd
.dwDrawStage
= CDDS_POSTERASE
;
2765 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2768 /* Send paint notifications */
2769 nmcd
.dwDrawStage
= CDDS_PREPAINT
;
2770 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2771 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
2773 text
= get_button_text(infoPtr
);
2774 if (!(cdrf
& CDRF_DOERASE
) && text
)
2775 DrawThemeText(theme
, hDC
, part
, state
, text
, lstrlenW(text
), dtFlags
, 0, &textRect
);
2777 if (cdrf
& CDRF_NOTIFYPOSTPAINT
)
2779 nmcd
.dwDrawStage
= CDDS_POSTPAINT
;
2780 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2785 if (!(cdrf
& CDRF_SKIPPOSTPAINT
) && focused
)
2789 focusRect
= textRect
;
2791 DrawTextW(hDC
, text
, lstrlenW(text
), &focusRect
, dtFlags
| DT_CALCRECT
);
2793 if (focusRect
.right
< textRect
.right
) focusRect
.right
++;
2794 focusRect
.bottom
= textRect
.bottom
;
2796 DrawFocusRect( hDC
, &focusRect
);
2803 if (created_font
) DeleteObject(font
);
2804 if (hPrevFont
) SelectObject(hDC
, hPrevFont
);
2807 static void GB_ThemedPaint(HTHEME theme
, const BUTTON_INFO
*infoPtr
, HDC hDC
, int state
, UINT dtFlags
, BOOL focused
)
2809 RECT bgRect
, textRect
, contentRect
;
2810 WCHAR
*text
= get_button_text(infoPtr
);
2812 HFONT font
, hPrevFont
= NULL
;
2813 BOOL created_font
= FALSE
;
2815 HRESULT hr
= GetThemeFont(theme
, hDC
, BP_GROUPBOX
, state
, TMT_FONT
, &lf
);
2816 if (SUCCEEDED(hr
)) {
2817 font
= CreateFontIndirectW(&lf
);
2819 TRACE("Failed to create font\n");
2821 hPrevFont
= SelectObject(hDC
, font
);
2822 created_font
= TRUE
;
2825 font
= (HFONT
)SendMessageW(infoPtr
->hwnd
, WM_GETFONT
, 0, 0);
2826 hPrevFont
= SelectObject(hDC
, font
);
2829 GetClientRect(infoPtr
->hwnd
, &bgRect
);
2835 GetTextExtentPoint32W(hDC
, text
, lstrlenW(text
), &textExtent
);
2836 bgRect
.top
+= (textExtent
.cy
/ 2);
2837 textRect
.left
+= 10;
2838 textRect
.bottom
= textRect
.top
+ textExtent
.cy
;
2839 textRect
.right
= textRect
.left
+ textExtent
.cx
+ 4;
2841 ExcludeClipRect(hDC
, textRect
.left
, textRect
.top
, textRect
.right
, textRect
.bottom
);
2844 GetThemeBackgroundContentRect(theme
, hDC
, BP_GROUPBOX
, state
, &bgRect
, &contentRect
);
2845 ExcludeClipRect(hDC
, contentRect
.left
, contentRect
.top
, contentRect
.right
, contentRect
.bottom
);
2847 if (IsThemeBackgroundPartiallyTransparent(theme
, BP_GROUPBOX
, state
))
2848 DrawThemeParentBackground(infoPtr
->hwnd
, hDC
, NULL
);
2849 DrawThemeBackground(theme
, hDC
, BP_GROUPBOX
, state
, &bgRect
, NULL
);
2851 SelectClipRgn(hDC
, NULL
);
2855 InflateRect(&textRect
, -2, 0);
2856 DrawThemeText(theme
, hDC
, BP_GROUPBOX
, state
, text
, lstrlenW(text
), 0, 0, &textRect
);
2860 if (created_font
) DeleteObject(font
);
2861 if (hPrevFont
) SelectObject(hDC
, hPrevFont
);
2864 static void SB_ThemedPaint(HTHEME theme
, const BUTTON_INFO
*infoPtr
, HDC hDC
, int state
, UINT dtFlags
, BOOL focused
)
2866 HFONT old_font
= infoPtr
->font
? SelectObject(hDC
, infoPtr
->font
) : NULL
;
2867 RECT rc
, content_rect
, push_rect
, dropdown_rect
;
2872 GetClientRect(infoPtr
->hwnd
, &rc
);
2873 init_custom_draw(&nmcd
, infoPtr
, hDC
, &rc
);
2875 parent
= GetParent(infoPtr
->hwnd
);
2876 if (!parent
) parent
= infoPtr
->hwnd
;
2878 /* Send erase notifications */
2879 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2880 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
2882 if (IsThemeBackgroundPartiallyTransparent(theme
, BP_PUSHBUTTON
, state
))
2883 DrawThemeParentBackground(infoPtr
->hwnd
, hDC
, NULL
);
2885 /* The zone outside the content is ignored for the dropdown (draws over) */
2886 GetThemeBackgroundContentRect(theme
, hDC
, BP_PUSHBUTTON
, state
, &rc
, &content_rect
);
2887 get_split_button_rects(infoPtr
, &rc
, &push_rect
, &dropdown_rect
);
2889 if (infoPtr
->split_style
& BCSS_NOSPLIT
)
2892 DrawThemeBackground(theme
, hDC
, BP_PUSHBUTTON
, state
, &rc
, NULL
);
2896 RECT r
= { dropdown_rect
.left
, content_rect
.top
, dropdown_rect
.right
, content_rect
.bottom
};
2897 UINT edge
= (infoPtr
->split_style
& BCSS_ALIGNLEFT
) ? BF_RIGHT
: BF_LEFT
;
2898 const RECT
*clip
= NULL
;
2900 /* If only the dropdown is pressed, we need to draw it separately */
2901 if (state
!= PBS_PRESSED
&& (infoPtr
->state
& BST_DROPDOWNPUSHED
))
2903 DrawThemeBackground(theme
, hDC
, BP_PUSHBUTTON
, PBS_PRESSED
, &rc
, &dropdown_rect
);
2906 DrawThemeBackground(theme
, hDC
, BP_PUSHBUTTON
, state
, &rc
, clip
);
2908 /* Draw the separator */
2909 DrawThemeEdge(theme
, hDC
, BP_PUSHBUTTON
, state
, &r
, EDGE_ETCHED
, edge
, NULL
);
2911 /* The content rect should be the content area of the push button */
2912 GetThemeBackgroundContentRect(theme
, hDC
, BP_PUSHBUTTON
, state
, &push_rect
, &content_rect
);
2915 if (cdrf
& CDRF_NOTIFYPOSTERASE
)
2917 nmcd
.dwDrawStage
= CDDS_POSTERASE
;
2918 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2921 /* Send paint notifications */
2922 nmcd
.dwDrawStage
= CDDS_PREPAINT
;
2923 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2924 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
2926 if (!(cdrf
& CDRF_DOERASE
))
2928 COLORREF old_color
, color
;
2932 if ((text
= get_button_text(infoPtr
)))
2934 DrawThemeText(theme
, hDC
, BP_PUSHBUTTON
, state
, text
, lstrlenW(text
), dtFlags
, 0, &content_rect
);
2938 GetThemeColor(theme
, BP_PUSHBUTTON
, state
, TMT_TEXTCOLOR
, &color
);
2939 old_bk_mode
= SetBkMode(hDC
, TRANSPARENT
);
2940 old_color
= SetTextColor(hDC
, color
);
2942 draw_split_button_dropdown_glyph(infoPtr
, hDC
, &dropdown_rect
);
2944 SetTextColor(hDC
, old_color
);
2945 SetBkMode(hDC
, old_bk_mode
);
2948 if (cdrf
& CDRF_NOTIFYPOSTPAINT
)
2950 nmcd
.dwDrawStage
= CDDS_POSTPAINT
;
2951 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2953 if (cdrf
& CDRF_SKIPPOSTPAINT
) goto cleanup
;
2959 GetThemeMargins(theme
, hDC
, BP_PUSHBUTTON
, state
, TMT_CONTENTMARGINS
, NULL
, &margins
);
2961 push_rect
.left
+= margins
.cxLeftWidth
;
2962 push_rect
.top
+= margins
.cyTopHeight
;
2963 push_rect
.right
-= margins
.cxRightWidth
;
2964 push_rect
.bottom
-= margins
.cyBottomHeight
;
2965 DrawFocusRect(hDC
, &push_rect
);
2969 if (old_font
) SelectObject(hDC
, old_font
);
2972 static void CL_ThemedPaint(HTHEME theme
, const BUTTON_INFO
*infoPtr
, HDC hDC
, int state
, UINT dtFlags
, BOOL focused
)
2974 HFONT old_font
= infoPtr
->font
? SelectObject(hDC
, infoPtr
->font
) : NULL
;
2980 GetClientRect(infoPtr
->hwnd
, &rc
);
2981 init_custom_draw(&nmcd
, infoPtr
, hDC
, &rc
);
2983 parent
= GetParent(infoPtr
->hwnd
);
2984 if (!parent
) parent
= infoPtr
->hwnd
;
2986 /* Send erase notifications */
2987 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2988 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
2990 if (IsThemeBackgroundPartiallyTransparent(theme
, BP_COMMANDLINK
, state
))
2991 DrawThemeParentBackground(infoPtr
->hwnd
, hDC
, NULL
);
2992 DrawThemeBackground(theme
, hDC
, BP_COMMANDLINK
, state
, &rc
, NULL
);
2994 if (cdrf
& CDRF_NOTIFYPOSTERASE
)
2996 nmcd
.dwDrawStage
= CDDS_POSTERASE
;
2997 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
3000 /* Send paint notifications */
3001 nmcd
.dwDrawStage
= CDDS_PREPAINT
;
3002 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
3003 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
3005 if (!(cdrf
& CDRF_DOERASE
))
3012 GetThemeBackgroundContentRect(theme
, hDC
, BP_COMMANDLINK
, state
, &rc
, &r
);
3014 /* The text alignment and styles are fixed and don't depend on button styles */
3015 dtFlags
= DT_TOP
| DT_LEFT
| DT_WORDBREAK
;
3017 /* Command Links ignore the margins of the image list or its alignment */
3018 if (infoPtr
->u
.image
|| infoPtr
->imagelist
.himl
)
3019 img_size
= BUTTON_GetImageSize(infoPtr
);
3021 GetThemePartSize(theme
, NULL
, BP_COMMANDLINKGLYPH
, state
, NULL
, TS_DRAW
, &img_size
);
3024 if (img_size
.cx
) r
.left
+= img_size
.cx
+ command_link_margin
;
3027 if ((text
= get_button_text(infoPtr
)))
3029 UINT len
= lstrlenW(text
);
3032 GetThemeTextExtent(theme
, hDC
, BP_COMMANDLINK
, state
, text
, len
,
3033 dtFlags
| DT_END_ELLIPSIS
, &r
, &text_rect
);
3034 DrawThemeText(theme
, hDC
, BP_COMMANDLINK
, state
, text
, len
,
3035 dtFlags
| DT_END_ELLIPSIS
, 0, &r
);
3037 txt_h
= text_rect
.bottom
- text_rect
.top
;
3047 opts
.dwSize
= sizeof(opts
);
3048 opts
.dwFlags
= DTT_FONTPROP
;
3049 opts
.iFontPropId
= TMT_BODYFONT
;
3050 DrawThemeTextEx(theme
, hDC
, BP_COMMANDLINK
, state
,
3051 infoPtr
->note
, infoPtr
->note_length
,
3052 dtFlags
| DT_NOPREFIX
, &r
, &opts
);
3055 /* Position the image at the vertical center of the drawn text (not note) */
3056 txt_h
= min(txt_h
, img_rect
.bottom
- img_rect
.top
);
3057 if (img_size
.cy
< txt_h
) img_rect
.top
+= (txt_h
- img_size
.cy
) / 2;
3059 img_rect
.right
= img_rect
.left
+ img_size
.cx
;
3060 img_rect
.bottom
= img_rect
.top
+ img_size
.cy
;
3062 if (infoPtr
->u
.image
|| infoPtr
->imagelist
.himl
)
3063 BUTTON_DrawImage(infoPtr
, hDC
, NULL
,
3064 (state
== CMDLS_DISABLED
) ? DSS_DISABLED
: DSS_NORMAL
,
3067 DrawThemeBackground(theme
, hDC
, BP_COMMANDLINKGLYPH
, state
, &img_rect
, NULL
);
3070 if (cdrf
& CDRF_NOTIFYPOSTPAINT
)
3072 nmcd
.dwDrawStage
= CDDS_POSTPAINT
;
3073 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
3075 if (cdrf
& CDRF_SKIPPOSTPAINT
) goto cleanup
;
3081 /* The focus rect has margins of a push button rather than command link... */
3082 GetThemeMargins(theme
, hDC
, BP_PUSHBUTTON
, state
, TMT_CONTENTMARGINS
, NULL
, &margins
);
3084 rc
.left
+= margins
.cxLeftWidth
;
3085 rc
.top
+= margins
.cyTopHeight
;
3086 rc
.right
-= margins
.cxRightWidth
;
3087 rc
.bottom
-= margins
.cyBottomHeight
;
3088 DrawFocusRect(hDC
, &rc
);
3092 if (old_font
) SelectObject(hDC
, old_font
);
3095 void BUTTON_Register(void)
3099 memset(&wndClass
, 0, sizeof(wndClass
));
3100 wndClass
.style
= CS_GLOBALCLASS
| CS_DBLCLKS
| CS_VREDRAW
| CS_HREDRAW
| CS_PARENTDC
;
3101 wndClass
.lpfnWndProc
= BUTTON_WindowProc
;
3102 wndClass
.cbClsExtra
= 0;
3103 wndClass
.cbWndExtra
= sizeof(BUTTON_INFO
*);
3104 wndClass
.hCursor
= LoadCursorW(0, (LPWSTR
)IDC_ARROW
);
3105 wndClass
.hbrBackground
= NULL
;
3106 wndClass
.lpszClassName
= WC_BUTTONW
;
3107 RegisterClassW(&wndClass
);