2 * Copyright (C) 1993 Johannes Ruscheinski
3 * Copyright (C) 1993 David Metcalfe
4 * Copyright (C) 1994 Alexandre Julliard
5 * Copyright (C) 2008 by Reece H. Dunn
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
23 * - BS_NOTIFY: is it complete?
24 * - BS_RIGHTBUTTON: same as BS_LEFTTEXT
27 * - WM_CHAR: Checks a (manual or automatic) check box on '+' or '=', clears it on '-' key.
28 * - WM_SETFOCUS: For (manual or automatic) radio buttons, send the parent window BN_CLICKED
29 * - WM_NCCREATE: Turns any BS_OWNERDRAW button into a BS_PUSHBUTTON button.
35 * - BN_PUSHED/BN_HILITE
36 * + BN_KILLFOCUS: is it OK?
38 * + BN_SETFOCUS: is it OK?
39 * - BN_UNPUSHED/BN_UNHILITE
41 * Structures/Macros/Definitions
57 #include "wine/debug.h"
58 #include "wine/heap.h"
62 WINE_DEFAULT_DEBUG_CHANNEL(button
);
64 /* undocumented flags */
65 #define BUTTON_NSTATES 0x0F
66 #define BUTTON_BTNPRESSED 0x40
67 #define BUTTON_UNKNOWN2 0x20
68 #define BUTTON_UNKNOWN3 0x10
70 #define BUTTON_NOTIFY_PARENT(hWnd, code) \
71 do { /* Notify parent which has created this button control */ \
72 TRACE("notification " #code " sent to hwnd=%p\n", GetParent(hWnd)); \
73 SendMessageW(GetParent(hWnd), WM_COMMAND, \
74 MAKEWPARAM(GetWindowLongPtrW((hWnd),GWLP_ID), (code)), \
78 typedef struct _BUTTON_INFO
87 DWORD image_type
; /* IMAGE_BITMAP or IMAGE_ICON */
88 BUTTON_IMAGELIST imagelist
;
90 HIMAGELIST glyph
; /* this is a font character code when split_style doesn't have BCSS_IMAGE */
101 static UINT
BUTTON_CalcLayoutRects( const BUTTON_INFO
*infoPtr
, HDC hdc
, RECT
*labelRc
, RECT
*imageRc
, RECT
*textRc
);
102 static void PB_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
);
103 static void CB_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
);
104 static void GB_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
);
105 static void UB_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
);
106 static void OB_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
);
107 static void SB_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
);
108 static void CL_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
);
109 static void BUTTON_CheckAutoRadioButton( HWND hwnd
);
110 static void get_split_button_rects(const BUTTON_INFO
*, const RECT
*, RECT
*, RECT
*);
111 static BOOL
notify_split_button_dropdown(const BUTTON_INFO
*, const POINT
*, HWND
);
112 static void draw_split_button_dropdown_glyph(const BUTTON_INFO
*, HDC
, RECT
*);
114 #define MAX_BTN_TYPE 16
116 static const WORD maxCheckState
[MAX_BTN_TYPE
] =
118 BST_UNCHECKED
, /* BS_PUSHBUTTON */
119 BST_UNCHECKED
, /* BS_DEFPUSHBUTTON */
120 BST_CHECKED
, /* BS_CHECKBOX */
121 BST_CHECKED
, /* BS_AUTOCHECKBOX */
122 BST_CHECKED
, /* BS_RADIOBUTTON */
123 BST_INDETERMINATE
, /* BS_3STATE */
124 BST_INDETERMINATE
, /* BS_AUTO3STATE */
125 BST_UNCHECKED
, /* BS_GROUPBOX */
126 BST_UNCHECKED
, /* BS_USERBUTTON */
127 BST_CHECKED
, /* BS_AUTORADIOBUTTON */
128 BST_UNCHECKED
, /* BS_PUSHBOX */
129 BST_UNCHECKED
, /* BS_OWNERDRAW */
130 BST_UNCHECKED
, /* BS_SPLITBUTTON */
131 BST_UNCHECKED
, /* BS_DEFSPLITBUTTON */
132 BST_UNCHECKED
, /* BS_COMMANDLINK */
133 BST_UNCHECKED
/* BS_DEFCOMMANDLINK */
136 /* Generic draw states, use get_draw_state() to get specific state for button type */
147 typedef void (*pfPaint
)( const BUTTON_INFO
*infoPtr
, HDC hdc
, UINT action
);
149 static const pfPaint btnPaintFunc
[MAX_BTN_TYPE
] =
151 PB_Paint
, /* BS_PUSHBUTTON */
152 PB_Paint
, /* BS_DEFPUSHBUTTON */
153 CB_Paint
, /* BS_CHECKBOX */
154 CB_Paint
, /* BS_AUTOCHECKBOX */
155 CB_Paint
, /* BS_RADIOBUTTON */
156 CB_Paint
, /* BS_3STATE */
157 CB_Paint
, /* BS_AUTO3STATE */
158 GB_Paint
, /* BS_GROUPBOX */
159 UB_Paint
, /* BS_USERBUTTON */
160 CB_Paint
, /* BS_AUTORADIOBUTTON */
161 NULL
, /* BS_PUSHBOX */
162 OB_Paint
, /* BS_OWNERDRAW */
163 SB_Paint
, /* BS_SPLITBUTTON */
164 SB_Paint
, /* BS_DEFSPLITBUTTON */
165 CL_Paint
, /* BS_COMMANDLINK */
166 CL_Paint
/* BS_DEFCOMMANDLINK */
169 typedef void (*pfThemedPaint
)( HTHEME theme
, const BUTTON_INFO
*infoPtr
, HDC hdc
, int drawState
, UINT dtflags
, BOOL focused
);
171 static void PB_ThemedPaint( HTHEME theme
, const BUTTON_INFO
*infoPtr
, HDC hdc
, int drawState
, UINT dtflags
, BOOL focused
);
172 static void CB_ThemedPaint( HTHEME theme
, const BUTTON_INFO
*infoPtr
, HDC hdc
, int drawState
, UINT dtflags
, BOOL focused
);
173 static void GB_ThemedPaint( HTHEME theme
, const BUTTON_INFO
*infoPtr
, HDC hdc
, int drawState
, UINT dtflags
, BOOL focused
);
174 static void SB_ThemedPaint( HTHEME theme
, const BUTTON_INFO
*infoPtr
, HDC hdc
, int drawState
, UINT dtflags
, BOOL focused
);
175 static void CL_ThemedPaint( HTHEME theme
, const BUTTON_INFO
*infoPtr
, HDC hdc
, int drawState
, UINT dtflags
, BOOL focused
);
177 static const pfThemedPaint btnThemedPaintFunc
[MAX_BTN_TYPE
] =
179 PB_ThemedPaint
, /* BS_PUSHBUTTON */
180 PB_ThemedPaint
, /* BS_DEFPUSHBUTTON */
181 CB_ThemedPaint
, /* BS_CHECKBOX */
182 CB_ThemedPaint
, /* BS_AUTOCHECKBOX */
183 CB_ThemedPaint
, /* BS_RADIOBUTTON */
184 CB_ThemedPaint
, /* BS_3STATE */
185 CB_ThemedPaint
, /* BS_AUTO3STATE */
186 GB_ThemedPaint
, /* BS_GROUPBOX */
187 NULL
, /* BS_USERBUTTON */
188 CB_ThemedPaint
, /* BS_AUTORADIOBUTTON */
189 NULL
, /* BS_PUSHBOX */
190 NULL
, /* BS_OWNERDRAW */
191 SB_ThemedPaint
, /* BS_SPLITBUTTON */
192 SB_ThemedPaint
, /* BS_DEFSPLITBUTTON */
193 CL_ThemedPaint
, /* BS_COMMANDLINK */
194 CL_ThemedPaint
/* BS_DEFCOMMANDLINK */
197 typedef BOOL (*pfGetIdealSize
)(BUTTON_INFO
*infoPtr
, SIZE
*size
);
199 static BOOL
PB_GetIdealSize(BUTTON_INFO
*infoPtr
, SIZE
*size
);
200 static BOOL
CB_GetIdealSize(BUTTON_INFO
*infoPtr
, SIZE
*size
);
201 static BOOL
GB_GetIdealSize(BUTTON_INFO
*infoPtr
, SIZE
*size
);
202 static BOOL
SB_GetIdealSize(BUTTON_INFO
*infoPtr
, SIZE
*size
);
203 static BOOL
CL_GetIdealSize(BUTTON_INFO
*infoPtr
, SIZE
*size
);
205 static const pfGetIdealSize btnGetIdealSizeFunc
[MAX_BTN_TYPE
] = {
206 PB_GetIdealSize
, /* BS_PUSHBUTTON */
207 PB_GetIdealSize
, /* BS_DEFPUSHBUTTON */
208 CB_GetIdealSize
, /* BS_CHECKBOX */
209 CB_GetIdealSize
, /* BS_AUTOCHECKBOX */
210 CB_GetIdealSize
, /* BS_RADIOBUTTON */
211 GB_GetIdealSize
, /* BS_3STATE */
212 GB_GetIdealSize
, /* BS_AUTO3STATE */
213 GB_GetIdealSize
, /* BS_GROUPBOX */
214 PB_GetIdealSize
, /* BS_USERBUTTON */
215 CB_GetIdealSize
, /* BS_AUTORADIOBUTTON */
216 GB_GetIdealSize
, /* BS_PUSHBOX */
217 GB_GetIdealSize
, /* BS_OWNERDRAW */
218 SB_GetIdealSize
, /* BS_SPLITBUTTON */
219 SB_GetIdealSize
, /* BS_DEFSPLITBUTTON */
220 CL_GetIdealSize
, /* BS_COMMANDLINK */
221 CL_GetIdealSize
/* BS_DEFCOMMANDLINK */
224 /* Fixed margin for command links, regardless of DPI (based on tests done on Windows) */
225 enum { command_link_margin
= 6 };
227 /* The width and height for the default command link glyph (when there's no image) */
228 enum { command_link_defglyph_size
= 17 };
230 static inline UINT
get_button_type( LONG window_style
)
232 return (window_style
& BS_TYPEMASK
);
235 static inline BOOL
button_centers_text( LONG window_style
)
237 /* Push button's text is centered by default, same for split buttons */
238 UINT type
= get_button_type(window_style
);
239 return type
<= BS_DEFPUSHBUTTON
|| type
== BS_SPLITBUTTON
|| type
== BS_DEFSPLITBUTTON
;
242 /* paint a button of any type */
243 static inline void paint_button( BUTTON_INFO
*infoPtr
, LONG style
, UINT action
)
245 if (btnPaintFunc
[style
] && IsWindowVisible(infoPtr
->hwnd
))
247 HDC hdc
= GetDC( infoPtr
->hwnd
);
248 btnPaintFunc
[style
]( infoPtr
, hdc
, action
);
249 ReleaseDC( infoPtr
->hwnd
, hdc
);
253 /* retrieve the button text; returned buffer must be freed by caller */
254 static inline WCHAR
*get_button_text( const BUTTON_INFO
*infoPtr
)
256 INT len
= GetWindowTextLengthW( infoPtr
->hwnd
);
257 WCHAR
*buffer
= heap_alloc( (len
+ 1) * sizeof(WCHAR
) );
259 GetWindowTextW( infoPtr
->hwnd
, buffer
, len
+ 1 );
263 /* get the default glyph size for split buttons */
264 static LONG
get_default_glyph_size(const BUTTON_INFO
*infoPtr
)
266 if (infoPtr
->split_style
& BCSS_IMAGE
)
268 /* Size it to fit, including the left and right edges */
270 if (!ImageList_GetIconSize(infoPtr
->glyph
, &w
, &h
)) w
= 0;
271 return w
+ GetSystemMetrics(SM_CXEDGE
) * 2;
274 /* The glyph size relies on the default menu font's cell height */
275 return GetSystemMetrics(SM_CYMENUCHECK
);
278 static void init_custom_draw(NMCUSTOMDRAW
*nmcd
, const BUTTON_INFO
*infoPtr
, HDC hdc
, const RECT
*rc
)
280 nmcd
->hdr
.hwndFrom
= infoPtr
->hwnd
;
281 nmcd
->hdr
.idFrom
= GetWindowLongPtrW(infoPtr
->hwnd
, GWLP_ID
);
282 nmcd
->hdr
.code
= NM_CUSTOMDRAW
;
285 nmcd
->dwDrawStage
= CDDS_PREERASE
;
286 nmcd
->dwItemSpec
= 0;
287 nmcd
->lItemlParam
= 0;
288 nmcd
->uItemState
= IsWindowEnabled(infoPtr
->hwnd
) ? 0 : CDIS_DISABLED
;
289 if (infoPtr
->state
& BST_PUSHED
) nmcd
->uItemState
|= CDIS_SELECTED
;
290 if (infoPtr
->state
& BST_FOCUS
) nmcd
->uItemState
|= CDIS_FOCUS
;
291 if (infoPtr
->state
& BST_HOT
) nmcd
->uItemState
|= CDIS_HOT
;
292 if (infoPtr
->state
& BST_INDETERMINATE
)
293 nmcd
->uItemState
|= CDIS_INDETERMINATE
;
295 /* Windows doesn't seem to send CDIS_CHECKED (it fails the tests) */
296 /* CDIS_SHOWKEYBOARDCUES is misleading, as the meaning is reversed */
297 /* FIXME: Handle it properly when we support keyboard cues? */
300 HRGN
set_control_clipping( HDC hdc
, const RECT
*rect
)
303 HRGN hrgn
= CreateRectRgn( 0, 0, 0, 0 );
305 if (GetClipRgn( hdc
, hrgn
) != 1)
307 DeleteObject( hrgn
);
310 DPtoLP( hdc
, (POINT
*)&rc
, 2 );
311 if (GetLayout( hdc
) & LAYOUT_RTL
) /* compensate for the shifting done by IntersectClipRect */
316 IntersectClipRect( hdc
, rc
.left
, rc
.top
, rc
.right
, rc
.bottom
);
320 static WCHAR
*heap_strndupW(const WCHAR
*src
, size_t length
)
322 size_t size
= (length
+ 1) * sizeof(WCHAR
);
323 WCHAR
*dst
= heap_alloc(size
);
324 if (dst
) memcpy(dst
, src
, size
);
328 /**********************************************************************
329 * Convert button styles to flags used by DrawText.
331 static UINT
BUTTON_BStoDT( DWORD style
, DWORD ex_style
)
333 UINT dtStyle
= DT_NOCLIP
; /* We use SelectClipRgn to limit output */
335 /* "Convert" pushlike buttons to pushbuttons */
336 if (style
& BS_PUSHLIKE
)
337 style
&= ~BS_TYPEMASK
;
339 if (!(style
& BS_MULTILINE
))
340 dtStyle
|= DT_SINGLELINE
;
342 dtStyle
|= DT_WORDBREAK
;
344 switch (style
& BS_CENTER
)
346 case BS_LEFT
: /* DT_LEFT is 0 */ break;
347 case BS_RIGHT
: dtStyle
|= DT_RIGHT
; break;
348 case BS_CENTER
: dtStyle
|= DT_CENTER
; break;
350 if (button_centers_text(style
)) dtStyle
|= DT_CENTER
;
353 if (ex_style
& WS_EX_RIGHT
) dtStyle
= DT_RIGHT
| (dtStyle
& ~(DT_LEFT
| DT_CENTER
));
355 /* DrawText ignores vertical alignment for multiline text,
356 * but we use these flags to align label manually.
358 if (get_button_type(style
) != BS_GROUPBOX
)
360 switch (style
& BS_VCENTER
)
362 case BS_TOP
: /* DT_TOP is 0 */ break;
363 case BS_BOTTOM
: dtStyle
|= DT_BOTTOM
; break;
364 case BS_VCENTER
: /* fall through */
365 default: dtStyle
|= DT_VCENTER
; break;
372 static int get_draw_state(const BUTTON_INFO
*infoPtr
)
374 static const int pb_states
[DRAW_STATE_COUNT
] = { PBS_NORMAL
, PBS_DISABLED
, PBS_HOT
, PBS_PRESSED
, PBS_DEFAULTED
};
375 static const int cb_states
[3][DRAW_STATE_COUNT
] =
377 { CBS_UNCHECKEDNORMAL
, CBS_UNCHECKEDDISABLED
, CBS_UNCHECKEDHOT
, CBS_UNCHECKEDPRESSED
, CBS_UNCHECKEDNORMAL
},
378 { CBS_CHECKEDNORMAL
, CBS_CHECKEDDISABLED
, CBS_CHECKEDHOT
, CBS_CHECKEDPRESSED
, CBS_CHECKEDNORMAL
},
379 { CBS_MIXEDNORMAL
, CBS_MIXEDDISABLED
, CBS_MIXEDHOT
, CBS_MIXEDPRESSED
, CBS_MIXEDNORMAL
}
381 static const int rb_states
[2][DRAW_STATE_COUNT
] =
383 { RBS_UNCHECKEDNORMAL
, RBS_UNCHECKEDDISABLED
, RBS_UNCHECKEDHOT
, RBS_UNCHECKEDPRESSED
, RBS_UNCHECKEDNORMAL
},
384 { RBS_CHECKEDNORMAL
, RBS_CHECKEDDISABLED
, RBS_CHECKEDHOT
, RBS_CHECKEDPRESSED
, RBS_CHECKEDNORMAL
}
386 static const int gb_states
[DRAW_STATE_COUNT
] = { GBS_NORMAL
, GBS_DISABLED
, GBS_NORMAL
, GBS_NORMAL
, GBS_NORMAL
};
387 LONG style
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
388 UINT type
= get_button_type(style
);
389 int check_state
= infoPtr
->state
& 3;
390 enum draw_state state
;
392 if (!IsWindowEnabled(infoPtr
->hwnd
))
393 state
= STATE_DISABLED
;
394 else if (infoPtr
->state
& BST_PUSHED
)
395 state
= STATE_PRESSED
;
396 else if (infoPtr
->state
& BST_HOT
)
398 else if (infoPtr
->state
& BST_FOCUS
)
399 state
= STATE_DEFAULTED
;
401 state
= STATE_NORMAL
;
406 case BS_DEFPUSHBUTTON
:
409 case BS_DEFSPLITBUTTON
:
411 case BS_DEFCOMMANDLINK
:
412 return pb_states
[state
];
414 case BS_AUTOCHECKBOX
:
415 return cb_states
[check_state
][state
];
419 case BS_AUTORADIOBUTTON
:
420 return rb_states
[check_state
][state
];
422 return gb_states
[state
];
424 WARN("Unsupported button type 0x%08x\n", type
);
429 static LRESULT CALLBACK
BUTTON_WindowProc(HWND hWnd
, UINT uMsg
, WPARAM wParam
, LPARAM lParam
)
431 BUTTON_INFO
*infoPtr
= (BUTTON_INFO
*)GetWindowLongPtrW(hWnd
, 0);
434 LONG style
= GetWindowLongW( hWnd
, GWL_STYLE
);
435 UINT btn_type
= get_button_type( style
);
436 LONG state
, new_state
;
440 if (!IsWindow( hWnd
)) return 0;
442 if (!infoPtr
&& (uMsg
!= WM_NCCREATE
))
443 return DefWindowProcW(hWnd
, uMsg
, wParam
, lParam
);
445 pt
.x
= (short)LOWORD(lParam
);
446 pt
.y
= (short)HIWORD(lParam
);
455 case BS_PUSHBUTTON
: return DLGC_BUTTON
| DLGC_UNDEFPUSHBUTTON
;
456 case BS_DEFCOMMANDLINK
:
457 case BS_DEFPUSHBUTTON
: return DLGC_BUTTON
| DLGC_DEFPUSHBUTTON
;
459 case BS_AUTORADIOBUTTON
: return DLGC_BUTTON
| DLGC_RADIOBUTTON
;
460 case BS_GROUPBOX
: return DLGC_STATIC
;
461 case BS_SPLITBUTTON
: return DLGC_BUTTON
| DLGC_UNDEFPUSHBUTTON
| DLGC_WANTARROWS
;
462 case BS_DEFSPLITBUTTON
: return DLGC_BUTTON
| DLGC_DEFPUSHBUTTON
| DLGC_WANTARROWS
;
463 default: return DLGC_BUTTON
;
467 theme
= GetWindowTheme( hWnd
);
469 RedrawWindow( hWnd
, NULL
, NULL
, RDW_FRAME
| RDW_INVALIDATE
| RDW_UPDATENOW
);
471 paint_button( infoPtr
, btn_type
, ODA_DRAWENTIRE
);
476 CREATESTRUCTW
*cs
= (CREATESTRUCTW
*)lParam
;
478 infoPtr
= heap_alloc_zero( sizeof(*infoPtr
) );
479 SetWindowLongPtrW( hWnd
, 0, (LONG_PTR
)infoPtr
);
480 infoPtr
->hwnd
= hWnd
;
481 infoPtr
->parent
= cs
->hwndParent
;
482 infoPtr
->style
= cs
->style
;
483 infoPtr
->split_style
= BCSS_STRETCH
;
484 infoPtr
->glyph
= (HIMAGELIST
)0x36; /* Marlett down arrow char code */
485 infoPtr
->glyph_size
.cx
= get_default_glyph_size(infoPtr
);
486 return DefWindowProcW(hWnd
, uMsg
, wParam
, lParam
);
490 SetWindowLongPtrW( hWnd
, 0, 0 );
491 heap_free(infoPtr
->note
);
496 if (btn_type
>= MAX_BTN_TYPE
)
497 return -1; /* abort */
499 /* XP turns a BS_USERBUTTON into BS_PUSHBUTTON */
500 if (btn_type
== BS_USERBUTTON
)
502 style
= (style
& ~BS_TYPEMASK
) | BS_PUSHBUTTON
;
503 SetWindowLongW( hWnd
, GWL_STYLE
, style
);
505 infoPtr
->state
= BST_UNCHECKED
;
506 OpenThemeData( hWnd
, WC_BUTTONW
);
510 theme
= GetWindowTheme( hWnd
);
511 CloseThemeData( theme
);
514 case WM_THEMECHANGED
:
515 theme
= GetWindowTheme( hWnd
);
516 CloseThemeData( theme
);
517 OpenThemeData( hWnd
, WC_BUTTONW
);
521 if (btn_type
== BS_OWNERDRAW
)
523 HDC hdc
= (HDC
)wParam
;
526 HWND parent
= GetParent(hWnd
);
527 if (!parent
) parent
= hWnd
;
528 hBrush
= (HBRUSH
)SendMessageW(parent
, WM_CTLCOLORBTN
, (WPARAM
)hdc
, (LPARAM
)hWnd
);
529 if (!hBrush
) /* did the app forget to call defwindowproc ? */
530 hBrush
= (HBRUSH
)DefWindowProcW(parent
, WM_CTLCOLORBTN
,
531 (WPARAM
)hdc
, (LPARAM
)hWnd
);
532 GetClientRect(hWnd
, &rc
);
533 FillRect(hdc
, &rc
, hBrush
);
543 theme
= GetWindowTheme( hWnd
);
544 hdc
= wParam
? (HDC
)wParam
: BeginPaint( hWnd
, &ps
);
546 if (theme
&& btnThemedPaintFunc
[btn_type
])
548 int drawState
= get_draw_state(infoPtr
);
549 UINT dtflags
= BUTTON_BStoDT(style
, GetWindowLongW(hWnd
, GWL_EXSTYLE
));
551 btnThemedPaintFunc
[btn_type
](theme
, infoPtr
, hdc
, drawState
, dtflags
, infoPtr
->state
& BST_FOCUS
);
553 else if (btnPaintFunc
[btn_type
])
555 int nOldMode
= SetBkMode( hdc
, OPAQUE
);
556 btnPaintFunc
[btn_type
]( infoPtr
, hdc
, ODA_DRAWENTIRE
);
557 SetBkMode(hdc
, nOldMode
); /* reset painting mode */
560 if ( !wParam
) EndPaint( hWnd
, &ps
);
565 if (wParam
== VK_SPACE
)
567 SendMessageW( hWnd
, BM_SETSTATE
, TRUE
, 0 );
568 infoPtr
->state
|= BUTTON_BTNPRESSED
;
571 else if (wParam
== VK_UP
|| wParam
== VK_DOWN
)
573 /* Up and down arrows work on every button, and even with BCSS_NOSPLIT */
574 notify_split_button_dropdown(infoPtr
, NULL
, hWnd
);
578 case WM_LBUTTONDBLCLK
:
579 if(style
& BS_NOTIFY
||
580 btn_type
== BS_RADIOBUTTON
||
581 btn_type
== BS_USERBUTTON
||
582 btn_type
== BS_OWNERDRAW
)
584 BUTTON_NOTIFY_PARENT(hWnd
, BN_DOUBLECLICKED
);
591 if ((btn_type
== BS_SPLITBUTTON
|| btn_type
== BS_DEFSPLITBUTTON
) &&
592 !(infoPtr
->split_style
& BCSS_NOSPLIT
) &&
593 notify_split_button_dropdown(infoPtr
, &pt
, hWnd
))
597 infoPtr
->state
|= BUTTON_BTNPRESSED
;
598 SendMessageW( hWnd
, BM_SETSTATE
, TRUE
, 0 );
602 if (wParam
!= VK_SPACE
)
606 state
= infoPtr
->state
;
607 if (state
& BST_DROPDOWNPUSHED
)
608 SendMessageW(hWnd
, BCM_SETDROPDOWNSTATE
, FALSE
, 0);
609 if (!(state
& BUTTON_BTNPRESSED
)) break;
610 infoPtr
->state
&= BUTTON_NSTATES
| BST_HOT
;
611 if (!(state
& BST_PUSHED
))
616 SendMessageW( hWnd
, BM_SETSTATE
, FALSE
, 0 );
617 GetClientRect( hWnd
, &rect
);
618 if (uMsg
== WM_KEYUP
|| PtInRect( &rect
, pt
))
622 case BS_AUTOCHECKBOX
:
623 SendMessageW( hWnd
, BM_SETCHECK
, !(infoPtr
->state
& BST_CHECKED
), 0 );
625 case BS_AUTORADIOBUTTON
:
626 SendMessageW( hWnd
, BM_SETCHECK
, TRUE
, 0 );
629 SendMessageW( hWnd
, BM_SETCHECK
, (infoPtr
->state
& BST_INDETERMINATE
) ? 0 :
630 ((infoPtr
->state
& 3) + 1), 0 );
634 BUTTON_NOTIFY_PARENT(hWnd
, BN_CLICKED
);
642 case WM_CAPTURECHANGED
:
643 TRACE("WM_CAPTURECHANGED %p\n", hWnd
);
644 if (hWnd
== (HWND
)lParam
) break;
645 if (infoPtr
->state
& BUTTON_BTNPRESSED
)
647 infoPtr
->state
&= BUTTON_NSTATES
;
648 if (infoPtr
->state
& BST_PUSHED
)
649 SendMessageW( hWnd
, BM_SETSTATE
, FALSE
, 0 );
655 TRACKMOUSEEVENT mouse_event
;
657 mouse_event
.cbSize
= sizeof(TRACKMOUSEEVENT
);
658 mouse_event
.dwFlags
= TME_QUERY
;
659 if (!TrackMouseEvent(&mouse_event
) || !(mouse_event
.dwFlags
& (TME_HOVER
| TME_LEAVE
)))
661 mouse_event
.dwFlags
= TME_HOVER
| TME_LEAVE
;
662 mouse_event
.hwndTrack
= hWnd
;
663 mouse_event
.dwHoverTime
= 1;
664 TrackMouseEvent(&mouse_event
);
667 if ((wParam
& MK_LBUTTON
) && GetCapture() == hWnd
)
669 GetClientRect( hWnd
, &rect
);
670 SendMessageW( hWnd
, BM_SETSTATE
, PtInRect(&rect
, pt
), 0 );
677 infoPtr
->state
|= BST_HOT
;
678 InvalidateRect( hWnd
, NULL
, FALSE
);
684 infoPtr
->state
&= ~BST_HOT
;
685 InvalidateRect( hWnd
, NULL
, FALSE
);
691 /* Clear an old text here as Windows does */
692 if (IsWindowVisible(hWnd
))
694 HDC hdc
= GetDC(hWnd
);
697 HWND parent
= GetParent(hWnd
);
698 UINT message
= (btn_type
== BS_PUSHBUTTON
||
699 btn_type
== BS_DEFPUSHBUTTON
||
700 btn_type
== BS_USERBUTTON
||
701 btn_type
== BS_OWNERDRAW
) ?
702 WM_CTLCOLORBTN
: WM_CTLCOLORSTATIC
;
704 if (!parent
) parent
= hWnd
;
705 hbrush
= (HBRUSH
)SendMessageW(parent
, message
,
706 (WPARAM
)hdc
, (LPARAM
)hWnd
);
707 if (!hbrush
) /* did the app forget to call DefWindowProc ? */
708 hbrush
= (HBRUSH
)DefWindowProcW(parent
, message
,
709 (WPARAM
)hdc
, (LPARAM
)hWnd
);
711 GetClientRect(hWnd
, &client
);
713 /* FIXME: check other BS_* handlers */
714 if (btn_type
== BS_GROUPBOX
)
715 InflateRect(&rc
, -7, 1); /* GB_Paint does this */
716 BUTTON_CalcLayoutRects(infoPtr
, hdc
, &rc
, NULL
, NULL
);
717 /* Clip by client rect bounds */
718 if (rc
.right
> client
.right
) rc
.right
= client
.right
;
719 if (rc
.bottom
> client
.bottom
) rc
.bottom
= client
.bottom
;
720 FillRect(hdc
, &rc
, hbrush
);
721 ReleaseDC(hWnd
, hdc
);
724 DefWindowProcW( hWnd
, WM_SETTEXT
, wParam
, lParam
);
725 if (btn_type
== BS_GROUPBOX
) /* Yes, only for BS_GROUPBOX */
726 InvalidateRect( hWnd
, NULL
, TRUE
);
728 paint_button( infoPtr
, btn_type
, ODA_DRAWENTIRE
);
729 return 1; /* success. FIXME: check text length */
734 WCHAR
*note
= (WCHAR
*)lParam
;
735 if (btn_type
!= BS_COMMANDLINK
&& btn_type
!= BS_DEFCOMMANDLINK
)
737 SetLastError(ERROR_NOT_SUPPORTED
);
741 heap_free(infoPtr
->note
);
744 infoPtr
->note_length
= lstrlenW(note
);
745 infoPtr
->note
= heap_strndupW(note
, infoPtr
->note_length
);
748 if (!note
|| !infoPtr
->note
)
750 infoPtr
->note_length
= 0;
751 infoPtr
->note
= heap_alloc_zero(sizeof(WCHAR
));
754 SetLastError(NO_ERROR
);
760 DWORD
*size
= (DWORD
*)wParam
;
761 WCHAR
*buffer
= (WCHAR
*)lParam
;
764 if (btn_type
!= BS_COMMANDLINK
&& btn_type
!= BS_DEFCOMMANDLINK
)
766 SetLastError(ERROR_NOT_SUPPORTED
);
770 if (!buffer
|| !size
|| !infoPtr
->note
)
772 SetLastError(ERROR_INVALID_PARAMETER
);
778 length
= min(*size
- 1, infoPtr
->note_length
);
779 memcpy(buffer
, infoPtr
->note
, length
* sizeof(WCHAR
));
780 buffer
[length
] = '\0';
783 if (*size
< infoPtr
->note_length
+ 1)
785 *size
= infoPtr
->note_length
+ 1;
786 SetLastError(ERROR_INSUFFICIENT_BUFFER
);
791 SetLastError(NO_ERROR
);
796 case BCM_GETNOTELENGTH
:
798 if (btn_type
!= BS_COMMANDLINK
&& btn_type
!= BS_DEFCOMMANDLINK
)
800 SetLastError(ERROR_NOT_SUPPORTED
);
804 return infoPtr
->note_length
;
808 infoPtr
->font
= (HFONT
)wParam
;
809 if (lParam
) InvalidateRect(hWnd
, NULL
, TRUE
);
813 return (LRESULT
)infoPtr
->font
;
816 TRACE("WM_SETFOCUS %p\n",hWnd
);
817 infoPtr
->state
|= BST_FOCUS
;
819 if (btn_type
== BS_OWNERDRAW
)
820 paint_button( infoPtr
, btn_type
, ODA_FOCUS
);
822 InvalidateRect(hWnd
, NULL
, FALSE
);
824 if (style
& BS_NOTIFY
)
825 BUTTON_NOTIFY_PARENT(hWnd
, BN_SETFOCUS
);
829 TRACE("WM_KILLFOCUS %p\n",hWnd
);
830 infoPtr
->state
&= ~BST_FOCUS
;
832 if ((infoPtr
->state
& BUTTON_BTNPRESSED
) && GetCapture() == hWnd
)
834 if (style
& BS_NOTIFY
)
835 BUTTON_NOTIFY_PARENT(hWnd
, BN_KILLFOCUS
);
837 InvalidateRect( hWnd
, NULL
, FALSE
);
840 case WM_SYSCOLORCHANGE
:
841 InvalidateRect( hWnd
, NULL
, FALSE
);
845 btn_type
= wParam
& BS_TYPEMASK
;
846 style
= (style
& ~BS_TYPEMASK
) | btn_type
;
847 SetWindowLongW( hWnd
, GWL_STYLE
, style
);
849 /* Only redraw if lParam flag is set.*/
851 InvalidateRect( hWnd
, NULL
, TRUE
);
856 SendMessageW( hWnd
, WM_LBUTTONDOWN
, 0, 0 );
857 SendMessageW( hWnd
, WM_LBUTTONUP
, 0, 0 );
861 infoPtr
->image_type
= (DWORD
)wParam
;
862 oldHbitmap
= infoPtr
->u
.image
;
863 infoPtr
->u
.image
= (HANDLE
)lParam
;
864 InvalidateRect( hWnd
, NULL
, FALSE
);
865 return (LRESULT
)oldHbitmap
;
868 return (LRESULT
)infoPtr
->u
.image
;
870 case BCM_SETIMAGELIST
:
872 BUTTON_IMAGELIST
*imagelist
= (BUTTON_IMAGELIST
*)lParam
;
874 if (!imagelist
) return FALSE
;
876 infoPtr
->imagelist
= *imagelist
;
880 case BCM_GETIMAGELIST
:
882 BUTTON_IMAGELIST
*imagelist
= (BUTTON_IMAGELIST
*)lParam
;
884 if (!imagelist
) return FALSE
;
886 *imagelist
= infoPtr
->imagelist
;
890 case BCM_SETSPLITINFO
:
892 BUTTON_SPLITINFO
*info
= (BUTTON_SPLITINFO
*)lParam
;
894 if (!info
) return TRUE
;
896 if (info
->mask
& (BCSIF_GLYPH
| BCSIF_IMAGE
))
898 infoPtr
->split_style
&= ~BCSS_IMAGE
;
899 if (!(info
->mask
& BCSIF_GLYPH
))
900 infoPtr
->split_style
|= BCSS_IMAGE
;
901 infoPtr
->glyph
= info
->himlGlyph
;
902 infoPtr
->glyph_size
.cx
= infoPtr
->glyph_size
.cy
= 0;
905 if (info
->mask
& BCSIF_STYLE
)
906 infoPtr
->split_style
= info
->uSplitStyle
;
907 if (info
->mask
& BCSIF_SIZE
)
908 infoPtr
->glyph_size
= info
->size
;
910 /* Calculate fitting value for cx if invalid (cy is untouched) */
911 if (infoPtr
->glyph_size
.cx
<= 0)
912 infoPtr
->glyph_size
.cx
= get_default_glyph_size(infoPtr
);
914 /* Windows doesn't invalidate or redraw it, so we don't, either */
918 case BCM_GETSPLITINFO
:
920 BUTTON_SPLITINFO
*info
= (BUTTON_SPLITINFO
*)lParam
;
922 if (!info
) return FALSE
;
924 if (info
->mask
& BCSIF_STYLE
)
925 info
->uSplitStyle
= infoPtr
->split_style
;
926 if (info
->mask
& (BCSIF_GLYPH
| BCSIF_IMAGE
))
927 info
->himlGlyph
= infoPtr
->glyph
;
928 if (info
->mask
& BCSIF_SIZE
)
929 info
->size
= infoPtr
->glyph_size
;
935 return infoPtr
->state
& 3;
938 if (wParam
> maxCheckState
[btn_type
]) wParam
= maxCheckState
[btn_type
];
939 if ((btn_type
== BS_RADIOBUTTON
) || (btn_type
== BS_AUTORADIOBUTTON
))
941 style
= wParam
? style
| WS_TABSTOP
: style
& ~WS_TABSTOP
;
942 SetWindowLongW( hWnd
, GWL_STYLE
, style
);
944 if ((infoPtr
->state
& 3) != wParam
)
946 infoPtr
->state
= (infoPtr
->state
& ~3) | wParam
;
947 InvalidateRect( hWnd
, NULL
, FALSE
);
949 if ((btn_type
== BS_AUTORADIOBUTTON
) && (wParam
== BST_CHECKED
) && (style
& WS_CHILD
))
950 BUTTON_CheckAutoRadioButton( hWnd
);
954 return infoPtr
->state
;
957 state
= infoPtr
->state
;
958 new_state
= wParam
? BST_PUSHED
: 0;
960 if ((state
^ new_state
) & BST_PUSHED
)
965 state
&= ~BST_PUSHED
;
967 if (btn_type
== BS_USERBUTTON
)
968 BUTTON_NOTIFY_PARENT( hWnd
, (state
& BST_PUSHED
) ? BN_HILITE
: BN_UNHILITE
);
969 infoPtr
->state
= state
;
971 InvalidateRect( hWnd
, NULL
, FALSE
);
975 case BCM_SETDROPDOWNSTATE
:
976 new_state
= wParam
? BST_DROPDOWNPUSHED
: 0;
978 if ((infoPtr
->state
^ new_state
) & BST_DROPDOWNPUSHED
)
980 infoPtr
->state
&= ~BST_DROPDOWNPUSHED
;
981 infoPtr
->state
|= new_state
;
982 InvalidateRect(hWnd
, NULL
, FALSE
);
986 case BCM_SETTEXTMARGIN
:
988 RECT
*text_margin
= (RECT
*)lParam
;
990 if (!text_margin
) return FALSE
;
992 infoPtr
->text_margin
= *text_margin
;
996 case BCM_GETTEXTMARGIN
:
998 RECT
*text_margin
= (RECT
*)lParam
;
1000 if (!text_margin
) return FALSE
;
1002 *text_margin
= infoPtr
->text_margin
;
1006 case BCM_GETIDEALSIZE
:
1008 SIZE
*size
= (SIZE
*)lParam
;
1010 if (!size
) return FALSE
;
1012 return btnGetIdealSizeFunc
[btn_type
](infoPtr
, size
);
1016 if(btn_type
== BS_GROUPBOX
) return HTTRANSPARENT
;
1019 return DefWindowProcW(hWnd
, uMsg
, wParam
, lParam
);
1024 /* If maxWidth is zero, rectangle width is unlimited */
1025 static RECT
BUTTON_GetTextRect(const BUTTON_INFO
*infoPtr
, HDC hdc
, const WCHAR
*text
, LONG maxWidth
)
1027 LONG style
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
1028 LONG exStyle
= GetWindowLongW(infoPtr
->hwnd
, GWL_EXSTYLE
);
1029 UINT dtStyle
= BUTTON_BStoDT(style
, exStyle
);
1033 rect
.right
= maxWidth
;
1034 hPrevFont
= SelectObject(hdc
, infoPtr
->font
);
1035 /* Calculate height without DT_VCENTER and DT_BOTTOM to get the correct height */
1036 DrawTextW(hdc
, text
, -1, &rect
, (dtStyle
& ~(DT_VCENTER
| DT_BOTTOM
)) | DT_CALCRECT
);
1037 if (hPrevFont
) SelectObject(hdc
, hPrevFont
);
1042 static BOOL
show_image_only(const BUTTON_INFO
*infoPtr
)
1044 LONG style
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
1045 return (style
& (BS_ICON
| BS_BITMAP
)) && (infoPtr
->u
.image
|| infoPtr
->imagelist
.himl
);
1048 static BOOL
show_image_and_text(const BUTTON_INFO
*infoPtr
)
1050 LONG style
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
1051 UINT type
= get_button_type(style
);
1052 return !(style
& (BS_ICON
| BS_BITMAP
))
1053 && ((infoPtr
->u
.image
1054 && (type
== BS_PUSHBUTTON
|| type
== BS_DEFPUSHBUTTON
|| type
== BS_USERBUTTON
|| type
== BS_SPLITBUTTON
1055 || type
== BS_DEFSPLITBUTTON
|| type
== BS_COMMANDLINK
|| type
== BS_DEFCOMMANDLINK
))
1056 || (infoPtr
->imagelist
.himl
&& type
!= BS_GROUPBOX
));
1059 static BOOL
show_image(const BUTTON_INFO
*infoPtr
)
1061 return show_image_only(infoPtr
) || show_image_and_text(infoPtr
);
1064 /* Get a bounding rectangle that is large enough to contain a image and a text side by side.
1065 * Note: (left,top) of the result rectangle may not be (0,0), offset it by yourself if needed */
1066 static RECT
BUTTON_GetBoundingLabelRect(LONG style
, const RECT
*textRect
, const RECT
*imageRect
)
1069 RECT rect
= *imageRect
;
1070 INT textWidth
= textRect
->right
- textRect
->left
;
1071 INT textHeight
= textRect
->bottom
- textRect
->top
;
1072 INT imageWidth
= imageRect
->right
- imageRect
->left
;
1073 INT imageHeight
= imageRect
->bottom
- imageRect
->top
;
1075 if ((style
& BS_CENTER
) == BS_RIGHT
)
1076 OffsetRect(&rect
, textWidth
, 0);
1077 else if ((style
& BS_CENTER
) == BS_LEFT
)
1078 OffsetRect(&rect
, -imageWidth
, 0);
1079 else if ((style
& BS_VCENTER
) == BS_BOTTOM
)
1080 OffsetRect(&rect
, 0, textHeight
);
1081 else if ((style
& BS_VCENTER
) == BS_TOP
)
1082 OffsetRect(&rect
, 0, -imageHeight
);
1084 OffsetRect(&rect
, -imageWidth
, 0);
1086 UnionRect(&labelRect
, textRect
, &rect
);
1090 /* Position a rectangle inside a bounding rectangle according to button alignment flags */
1091 static void BUTTON_PositionRect(LONG style
, const RECT
*outerRect
, RECT
*innerRect
, const RECT
*margin
)
1093 INT width
= innerRect
->right
- innerRect
->left
;
1094 INT height
= innerRect
->bottom
- innerRect
->top
;
1096 if ((style
& WS_EX_RIGHT
) && !(style
& BS_CENTER
)) style
|= BS_CENTER
;
1098 if (!(style
& BS_CENTER
))
1100 if (button_centers_text(style
))
1106 if (!(style
& BS_VCENTER
))
1108 /* Group box's text is top aligned by default */
1109 if (get_button_type(style
) == BS_GROUPBOX
)
1113 switch (style
& BS_CENTER
)
1116 innerRect
->left
= outerRect
->left
+ (outerRect
->right
- outerRect
->left
- width
) / 2;
1117 innerRect
->right
= innerRect
->left
+ width
;
1120 innerRect
->right
= outerRect
->right
- margin
->right
;
1121 innerRect
->left
= innerRect
->right
- width
;
1125 innerRect
->left
= outerRect
->left
+ margin
->left
;
1126 innerRect
->right
= innerRect
->left
+ width
;
1130 switch (style
& BS_VCENTER
)
1133 innerRect
->top
= outerRect
->top
+ margin
->top
;
1134 innerRect
->bottom
= innerRect
->top
+ height
;
1137 innerRect
->bottom
= outerRect
->bottom
- margin
->bottom
;
1138 innerRect
->top
= innerRect
->bottom
- height
;
1142 innerRect
->top
= outerRect
->top
+ (outerRect
->bottom
- outerRect
->top
- height
) / 2;
1143 innerRect
->bottom
= innerRect
->top
+ height
;
1148 /* Convert imagelist align style to button align style */
1149 static UINT
BUTTON_ILStoBS(UINT align
)
1153 case BUTTON_IMAGELIST_ALIGN_TOP
:
1154 return BS_CENTER
| BS_TOP
;
1155 case BUTTON_IMAGELIST_ALIGN_BOTTOM
:
1156 return BS_CENTER
| BS_BOTTOM
;
1157 case BUTTON_IMAGELIST_ALIGN_CENTER
:
1158 return BS_CENTER
| BS_VCENTER
;
1159 case BUTTON_IMAGELIST_ALIGN_RIGHT
:
1160 return BS_RIGHT
| BS_VCENTER
;
1161 case BUTTON_IMAGELIST_ALIGN_LEFT
:
1163 return BS_LEFT
| BS_VCENTER
;
1167 static SIZE
BUTTON_GetImageSize(const BUTTON_INFO
*infoPtr
)
1173 /* ImageList has priority over image */
1174 if (infoPtr
->imagelist
.himl
)
1175 ImageList_GetIconSize(infoPtr
->imagelist
.himl
, &size
.cx
, &size
.cy
);
1176 else if (infoPtr
->u
.image
)
1178 if (infoPtr
->image_type
== IMAGE_ICON
)
1180 GetIconInfo(infoPtr
->u
.icon
, &iconInfo
);
1181 GetObjectW(iconInfo
.hbmColor
, sizeof(bm
), &bm
);
1182 DeleteObject(iconInfo
.hbmColor
);
1183 DeleteObject(iconInfo
.hbmMask
);
1185 else if (infoPtr
->image_type
== IMAGE_BITMAP
)
1186 GetObjectW(infoPtr
->u
.bitmap
, sizeof(bm
), &bm
);
1188 size
.cx
= bm
.bmWidth
;
1189 size
.cy
= bm
.bmHeight
;
1195 static const RECT
*BUTTON_GetTextMargin(const BUTTON_INFO
*infoPtr
)
1197 static const RECT oneMargin
= {1, 1, 1, 1};
1199 /* Use text margin only when showing both image and text, and image is not imagelist */
1200 if (show_image_and_text(infoPtr
) && !infoPtr
->imagelist
.himl
)
1201 return &infoPtr
->text_margin
;
1206 static void BUTTON_GetClientRectSize(BUTTON_INFO
*infoPtr
, SIZE
*size
)
1209 GetClientRect(infoPtr
->hwnd
, &rect
);
1210 size
->cx
= rect
.right
- rect
.left
;
1211 size
->cy
= rect
.bottom
- rect
.top
;
1214 static void BUTTON_GetTextIdealSize(BUTTON_INFO
*infoPtr
, LONG maxWidth
, SIZE
*size
)
1216 WCHAR
*text
= get_button_text(infoPtr
);
1219 const RECT
*margin
= BUTTON_GetTextMargin(infoPtr
);
1223 maxWidth
-= margin
->right
+ margin
->right
;
1224 if (maxWidth
<= 0) maxWidth
= 1;
1227 hdc
= GetDC(infoPtr
->hwnd
);
1228 rect
= BUTTON_GetTextRect(infoPtr
, hdc
, text
, maxWidth
);
1229 ReleaseDC(infoPtr
->hwnd
, hdc
);
1232 size
->cx
= rect
.right
- rect
.left
+ margin
->left
+ margin
->right
;
1233 size
->cy
= rect
.bottom
- rect
.top
+ margin
->top
+ margin
->bottom
;
1236 static void BUTTON_GetLabelIdealSize(BUTTON_INFO
*infoPtr
, LONG maxWidth
, SIZE
*size
)
1238 LONG style
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
1243 imageSize
= BUTTON_GetImageSize(infoPtr
);
1244 if (infoPtr
->imagelist
.himl
)
1246 imageSize
.cx
+= infoPtr
->imagelist
.margin
.left
+ infoPtr
->imagelist
.margin
.right
;
1247 imageSize
.cy
+= infoPtr
->imagelist
.margin
.top
+ infoPtr
->imagelist
.margin
.bottom
;
1248 if (infoPtr
->imagelist
.uAlign
== BUTTON_IMAGELIST_ALIGN_TOP
1249 || infoPtr
->imagelist
.uAlign
== BUTTON_IMAGELIST_ALIGN_BOTTOM
)
1256 /* horizontal alignment flags has priority over vertical ones if both are specified */
1257 if (!(style
& (BS_CENTER
| BS_VCENTER
)) || ((style
& BS_CENTER
) && (style
& BS_CENTER
) != BS_CENTER
)
1258 || !(style
& BS_VCENTER
) || (style
& BS_VCENTER
) == BS_VCENTER
)
1268 maxWidth
-= imageSize
.cx
;
1269 if (maxWidth
<= 0) maxWidth
= 1;
1271 BUTTON_GetTextIdealSize(infoPtr
, maxWidth
, &textSize
);
1272 size
->cx
= textSize
.cx
+ imageSize
.cx
;
1273 size
->cy
= max(textSize
.cy
, imageSize
.cy
);
1277 BUTTON_GetTextIdealSize(infoPtr
, maxWidth
, &textSize
);
1278 size
->cx
= max(textSize
.cx
, imageSize
.cx
);
1279 size
->cy
= textSize
.cy
+ imageSize
.cy
;
1283 static BOOL
GB_GetIdealSize(BUTTON_INFO
*infoPtr
, SIZE
*size
)
1285 BUTTON_GetClientRectSize(infoPtr
, size
);
1289 static BOOL
CB_GetIdealSize(BUTTON_INFO
*infoPtr
, SIZE
*size
)
1291 LONG style
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
1298 LONG checkboxWidth
, checkboxHeight
;
1301 if (SendMessageW(infoPtr
->hwnd
, WM_GETTEXTLENGTH
, 0, 0) == 0)
1303 BUTTON_GetClientRectSize(infoPtr
, size
);
1307 hdc
= GetDC(infoPtr
->hwnd
);
1308 scaleX
= GetDeviceCaps(hdc
, LOGPIXELSX
) / 96.0;
1309 scaleY
= GetDeviceCaps(hdc
, LOGPIXELSY
) / 96.0;
1310 if ((hfont
= infoPtr
->font
)) SelectObject(hdc
, hfont
);
1311 GetCharWidthW(hdc
, '0', '0', &textOffset
);
1313 ReleaseDC(infoPtr
->hwnd
, hdc
);
1315 checkboxWidth
= 12 * scaleX
+ 1;
1316 checkboxHeight
= 12 * scaleY
+ 1;
1319 maxWidth
= size
->cx
- checkboxWidth
- textOffset
;
1320 if (maxWidth
<= 0) maxWidth
= 1;
1323 /* Checkbox doesn't support both image(but not image list) and text */
1324 if (!(style
& (BS_ICON
| BS_BITMAP
)) && infoPtr
->u
.image
)
1325 BUTTON_GetTextIdealSize(infoPtr
, maxWidth
, &labelSize
);
1327 BUTTON_GetLabelIdealSize(infoPtr
, maxWidth
, &labelSize
);
1329 size
->cx
= labelSize
.cx
+ checkboxWidth
+ textOffset
;
1330 size
->cy
= max(labelSize
.cy
, checkboxHeight
);
1335 static BOOL
PB_GetIdealSize(BUTTON_INFO
*infoPtr
, SIZE
*size
)
1339 if (SendMessageW(infoPtr
->hwnd
, WM_GETTEXTLENGTH
, 0, 0) == 0)
1340 BUTTON_GetClientRectSize(infoPtr
, size
);
1343 /* Ideal size include text size even if image only flags(BS_ICON, BS_BITMAP) are specified */
1344 BUTTON_GetLabelIdealSize(infoPtr
, size
->cx
, &labelSize
);
1346 size
->cx
= labelSize
.cx
;
1347 size
->cy
= labelSize
.cy
;
1352 static BOOL
SB_GetIdealSize(BUTTON_INFO
*infoPtr
, SIZE
*size
)
1354 LONG extra_width
= infoPtr
->glyph_size
.cx
* 2 + GetSystemMetrics(SM_CXEDGE
);
1357 if (SendMessageW(infoPtr
->hwnd
, WM_GETTEXTLENGTH
, 0, 0) == 0)
1359 BUTTON_GetClientRectSize(infoPtr
, size
);
1360 size
->cx
= max(size
->cx
, extra_width
);
1364 BUTTON_GetLabelIdealSize(infoPtr
, size
->cx
, &label_size
);
1365 size
->cx
= label_size
.cx
+ ((size
->cx
== 0) ? extra_width
: 0);
1366 size
->cy
= label_size
.cy
;
1371 static BOOL
CL_GetIdealSize(BUTTON_INFO
*infoPtr
, SIZE
*size
)
1373 HTHEME theme
= GetWindowTheme(infoPtr
->hwnd
);
1374 HDC hdc
= GetDC(infoPtr
->hwnd
);
1375 LONG w
, text_w
= 0, text_h
= 0;
1376 UINT flags
= DT_TOP
| DT_LEFT
;
1377 HFONT font
, old_font
= NULL
;
1378 RECT text_bound
= { 0 };
1383 /* Get the image size */
1384 if (infoPtr
->u
.image
|| infoPtr
->imagelist
.himl
)
1385 img_size
= BUTTON_GetImageSize(infoPtr
);
1389 GetThemePartSize(theme
, NULL
, BP_COMMANDLINKGLYPH
, CMDLS_NORMAL
, NULL
, TS_DRAW
, &img_size
);
1391 img_size
.cx
= img_size
.cy
= command_link_defglyph_size
;
1394 /* Get the content margins */
1397 RECT r
= { 0, 0, 0xffff, 0xffff };
1398 GetThemeBackgroundContentRect(theme
, hdc
, BP_COMMANDLINK
, CMDLS_NORMAL
, &r
, &margin
);
1399 margin
.left
-= r
.left
;
1400 margin
.top
-= r
.top
;
1401 margin
.right
= r
.right
- margin
.right
;
1402 margin
.bottom
= r
.bottom
- margin
.bottom
;
1406 margin
.left
= margin
.right
= command_link_margin
;
1407 margin
.top
= margin
.bottom
= command_link_margin
;
1410 /* Account for the border margins and the margin between image and text */
1411 w
= margin
.left
+ margin
.right
+ (img_size
.cx
? (img_size
.cx
+ command_link_margin
) : 0);
1413 /* If a rectangle with a specific width was requested, bound the text to it */
1416 text_bound
.right
= size
->cx
- w
;
1417 flags
|= DT_WORDBREAK
;
1422 if (infoPtr
->font
) old_font
= SelectObject(hdc
, infoPtr
->font
);
1424 /* Find the text's rect */
1425 if ((text
= get_button_text(infoPtr
)))
1428 GetThemeTextExtent(theme
, hdc
, BP_COMMANDLINK
, CMDLS_NORMAL
,
1429 text
, -1, flags
, &text_bound
, &r
);
1431 text_w
= r
.right
- r
.left
;
1432 text_h
= r
.bottom
- r
.top
;
1435 /* Find the note's rect */
1440 opts
.dwSize
= sizeof(opts
);
1441 opts
.dwFlags
= DTT_FONTPROP
| DTT_CALCRECT
;
1442 opts
.iFontPropId
= TMT_BODYFONT
;
1443 DrawThemeTextEx(theme
, hdc
, BP_COMMANDLINK
, CMDLS_NORMAL
,
1444 infoPtr
->note
, infoPtr
->note_length
,
1445 flags
| DT_NOPREFIX
| DT_CALCRECT
, &text_bound
, &opts
);
1446 text_w
= max(text_w
, text_bound
.right
- text_bound
.left
);
1447 text_h
+= text_bound
.bottom
- text_bound
.top
;
1452 NONCLIENTMETRICSW ncm
;
1454 ncm
.cbSize
= sizeof(ncm
);
1455 if (SystemParametersInfoW(SPI_GETNONCLIENTMETRICS
, sizeof(ncm
), &ncm
, 0))
1457 LONG note_weight
= ncm
.lfMessageFont
.lfWeight
;
1459 /* Find the text's rect */
1460 ncm
.lfMessageFont
.lfWeight
= FW_BOLD
;
1461 if ((font
= CreateFontIndirectW(&ncm
.lfMessageFont
)))
1463 if ((text
= get_button_text(infoPtr
)))
1465 RECT r
= text_bound
;
1466 old_font
= SelectObject(hdc
, font
);
1467 DrawTextW(hdc
, text
, -1, &r
, flags
| DT_CALCRECT
);
1470 text_w
= r
.right
- r
.left
;
1471 text_h
= r
.bottom
- r
.top
;
1476 /* Find the note's rect */
1477 ncm
.lfMessageFont
.lfWeight
= note_weight
;
1478 if (infoPtr
->note
&& (font
= CreateFontIndirectW(&ncm
.lfMessageFont
)))
1480 HFONT tmp
= SelectObject(hdc
, font
);
1481 if (!old_font
) old_font
= tmp
;
1483 DrawTextW(hdc
, infoPtr
->note
, infoPtr
->note_length
, &text_bound
,
1484 flags
| DT_NOPREFIX
| DT_CALCRECT
);
1487 text_w
= max(text_w
, text_bound
.right
- text_bound
.left
);
1488 text_h
+= text_bound
.bottom
- text_bound
.top
+ 2;
1494 size
->cx
= min(size
->cx
, w
);
1495 size
->cy
= max(text_h
, img_size
.cy
) + margin
.top
+ margin
.bottom
;
1497 if (old_font
) SelectObject(hdc
, old_font
);
1498 ReleaseDC(infoPtr
->hwnd
, hdc
);
1502 /**********************************************************************
1503 * BUTTON_CalcLayoutRects
1505 * Calculates the rectangles of the button label(image and text) and its parts depending on a button's style.
1507 * Returns flags to be passed to DrawText.
1508 * Calculated rectangle doesn't take into account button state
1509 * (pushed, etc.). If there is nothing to draw (no text/image) output
1510 * rectangle is empty, and return value is (UINT)-1.
1513 * infoPtr [I] Button pointer
1514 * hdc [I] Handle to device context to draw to
1515 * labelRc [I/O] Input the rect the label to be positioned in, and output the label rect
1516 * imageRc [O] Optional, output the image rect
1517 * textRc [O] Optional, output the text rect
1519 static UINT
BUTTON_CalcLayoutRects(const BUTTON_INFO
*infoPtr
, HDC hdc
, RECT
*labelRc
, RECT
*imageRc
, RECT
*textRc
)
1521 LONG style
= GetWindowLongW( infoPtr
->hwnd
, GWL_STYLE
);
1522 LONG ex_style
= GetWindowLongW( infoPtr
->hwnd
, GWL_EXSTYLE
);
1523 LONG split_style
= infoPtr
->imagelist
.himl
? BUTTON_ILStoBS(infoPtr
->imagelist
.uAlign
) : style
;
1524 WCHAR
*text
= get_button_text(infoPtr
);
1525 SIZE imageSize
= BUTTON_GetImageSize(infoPtr
);
1526 UINT dtStyle
= BUTTON_BStoDT(style
, ex_style
);
1527 RECT labelRect
, imageRect
, imageRectWithMargin
, textRect
;
1528 LONG imageMarginWidth
, imageMarginHeight
;
1529 const RECT
*textMargin
= BUTTON_GetTextMargin(infoPtr
);
1530 RECT emptyMargin
= {0};
1533 /* Calculate label rectangle according to label type */
1534 if ((imageSize
.cx
== 0 && imageSize
.cy
== 0) && (text
== NULL
|| text
[0] == '\0'))
1536 SetRectEmpty(labelRc
);
1537 SetRectEmpty(imageRc
);
1538 SetRectEmpty(textRc
);
1543 SetRect(&imageRect
, 0, 0, imageSize
.cx
, imageSize
.cy
);
1544 imageRectWithMargin
= imageRect
;
1545 if (infoPtr
->imagelist
.himl
)
1547 imageRectWithMargin
.top
-= infoPtr
->imagelist
.margin
.top
;
1548 imageRectWithMargin
.bottom
+= infoPtr
->imagelist
.margin
.bottom
;
1549 imageRectWithMargin
.left
-= infoPtr
->imagelist
.margin
.left
;
1550 imageRectWithMargin
.right
+= infoPtr
->imagelist
.margin
.right
;
1553 /* Show image only */
1554 if (show_image_only(infoPtr
))
1556 BUTTON_PositionRect(style
, labelRc
, &imageRect
,
1557 infoPtr
->imagelist
.himl
? &infoPtr
->imagelist
.margin
: &emptyMargin
);
1558 labelRect
= imageRect
;
1559 SetRectEmpty(&textRect
);
1564 maxTextWidth
= labelRc
->right
- labelRc
->left
;
1565 textRect
= BUTTON_GetTextRect(infoPtr
, hdc
, text
, maxTextWidth
);
1567 /* Show image and text */
1568 if (show_image_and_text(infoPtr
))
1570 RECT boundingLabelRect
, boundingImageRect
, boundingTextRect
;
1572 /* Get label rect */
1573 /* Image list may have different alignment than the button, use the whole rect for label in this case */
1574 if (infoPtr
->imagelist
.himl
)
1575 labelRect
= *labelRc
;
1578 /* Get a label bounding rectangle to position the label in the user specified label rectangle because
1579 * text and image need to align together. */
1580 boundingLabelRect
= BUTTON_GetBoundingLabelRect(split_style
, &textRect
, &imageRectWithMargin
);
1581 BUTTON_PositionRect(split_style
, labelRc
, &boundingLabelRect
, &emptyMargin
);
1582 labelRect
= boundingLabelRect
;
1585 /* When imagelist has center align, use the whole rect for imagelist and text */
1586 if(infoPtr
->imagelist
.himl
&& infoPtr
->imagelist
.uAlign
== BUTTON_IMAGELIST_ALIGN_CENTER
)
1588 boundingImageRect
= labelRect
;
1589 boundingTextRect
= labelRect
;
1590 BUTTON_PositionRect(split_style
, &boundingImageRect
, &imageRect
,
1591 infoPtr
->imagelist
.himl
? &infoPtr
->imagelist
.margin
: &emptyMargin
);
1592 /* Text doesn't use imagelist align */
1593 BUTTON_PositionRect(style
, &boundingTextRect
, &textRect
, textMargin
);
1597 /* Get image rect */
1598 /* Split the label rect to two halves as two bounding rectangles for image and text */
1599 boundingImageRect
= labelRect
;
1600 imageMarginWidth
= imageRectWithMargin
.right
- imageRectWithMargin
.left
;
1601 imageMarginHeight
= imageRectWithMargin
.bottom
- imageRectWithMargin
.top
;
1602 if ((split_style
& BS_CENTER
) == BS_RIGHT
)
1603 boundingImageRect
.left
= boundingImageRect
.right
- imageMarginWidth
;
1604 else if ((split_style
& BS_CENTER
) == BS_LEFT
)
1605 boundingImageRect
.right
= boundingImageRect
.left
+ imageMarginWidth
;
1606 else if ((split_style
& BS_VCENTER
) == BS_BOTTOM
)
1607 boundingImageRect
.top
= boundingImageRect
.bottom
- imageMarginHeight
;
1608 else if ((split_style
& BS_VCENTER
) == BS_TOP
)
1609 boundingImageRect
.bottom
= boundingImageRect
.top
+ imageMarginHeight
;
1611 boundingImageRect
.right
= boundingImageRect
.left
+ imageMarginWidth
;
1612 BUTTON_PositionRect(split_style
, &boundingImageRect
, &imageRect
,
1613 infoPtr
->imagelist
.himl
? &infoPtr
->imagelist
.margin
: &emptyMargin
);
1616 SubtractRect(&boundingTextRect
, &labelRect
, &boundingImageRect
);
1617 /* Text doesn't use imagelist align */
1618 BUTTON_PositionRect(style
, &boundingTextRect
, &textRect
, textMargin
);
1621 /* Show text only */
1624 if (get_button_type(style
) != BS_GROUPBOX
)
1625 BUTTON_PositionRect(style
, labelRc
, &textRect
, textMargin
);
1627 /* GroupBox is always top aligned */
1628 BUTTON_PositionRect((style
& ~BS_VCENTER
) | BS_TOP
, labelRc
, &textRect
, textMargin
);
1629 labelRect
= textRect
;
1630 SetRectEmpty(&imageRect
);
1635 CopyRect(labelRc
, &labelRect
);
1636 CopyRect(imageRc
, &imageRect
);
1637 CopyRect(textRc
, &textRect
);
1643 /**********************************************************************
1646 * Draw the button's image into the specified rectangle.
1648 static void BUTTON_DrawImage(const BUTTON_INFO
*infoPtr
, HDC hdc
, HBRUSH hbr
, UINT flags
, const RECT
*rect
)
1650 if (infoPtr
->imagelist
.himl
)
1652 int i
= (ImageList_GetImageCount(infoPtr
->imagelist
.himl
) == 1) ? 0 : get_draw_state(infoPtr
) - 1;
1654 ImageList_Draw(infoPtr
->imagelist
.himl
, i
, hdc
, rect
->left
, rect
->top
, ILD_NORMAL
);
1658 switch (infoPtr
->image_type
)
1664 flags
|= DST_BITMAP
;
1670 DrawStateW(hdc
, hbr
, NULL
, (LPARAM
)infoPtr
->u
.image
, 0, rect
->left
, rect
->top
,
1671 rect
->right
- rect
->left
, rect
->bottom
- rect
->top
, flags
);
1676 /**********************************************************************
1677 * BUTTON_DrawTextCallback
1679 * Callback function used by DrawStateW function.
1681 static BOOL CALLBACK
BUTTON_DrawTextCallback(HDC hdc
, LPARAM lp
, WPARAM wp
, int cx
, int cy
)
1685 SetRect(&rc
, 0, 0, cx
, cy
);
1686 DrawTextW(hdc
, (LPCWSTR
)lp
, -1, &rc
, (UINT
)wp
);
1690 /**********************************************************************
1693 * Common function for drawing button label.
1696 * 1. When BS_SINGLELINE is specified and text contains '\t', '\n' or '\r' in the middle, they are rendered as
1697 * squares now whereas they should be ignored.
1698 * 2. When BS_MULTILINE is specified and text contains space in the middle, the space mistakenly be rendered as newline.
1700 static void BUTTON_DrawLabel(const BUTTON_INFO
*infoPtr
, HDC hdc
, UINT dtFlags
, const RECT
*imageRect
,
1701 const RECT
*textRect
)
1704 UINT flags
= IsWindowEnabled(infoPtr
->hwnd
) ? DSS_NORMAL
: DSS_DISABLED
;
1705 LONG style
= GetWindowLongW( infoPtr
->hwnd
, GWL_STYLE
);
1708 /* FIXME: To draw disabled label in Win31 look-and-feel, we probably
1709 * must use DSS_MONO flag and COLOR_GRAYTEXT brush (or maybe DSS_UNION).
1710 * I don't have Win31 on hand to verify that, so I leave it as is.
1713 if ((style
& BS_PUSHLIKE
) && (infoPtr
->state
& BST_INDETERMINATE
))
1715 hbr
= GetSysColorBrush(COLOR_GRAYTEXT
);
1719 if (show_image(infoPtr
)) BUTTON_DrawImage(infoPtr
, hdc
, hbr
, flags
, imageRect
);
1720 if (show_image_only(infoPtr
)) return;
1722 /* DST_COMPLEX -- is 0 */
1723 if (!(text
= get_button_text(infoPtr
))) return;
1724 DrawStateW(hdc
, hbr
, BUTTON_DrawTextCallback
, (LPARAM
)text
, dtFlags
, textRect
->left
, textRect
->top
,
1725 textRect
->right
- textRect
->left
, textRect
->bottom
- textRect
->top
, flags
);
1729 /**********************************************************************
1730 * Push Button Functions
1732 static void PB_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
)
1734 RECT rc
, labelRect
, imageRect
, textRect
;
1735 UINT dtFlags
, uState
;
1739 COLORREF oldTxtColor
;
1743 LONG state
= infoPtr
->state
;
1744 LONG style
= GetWindowLongW( infoPtr
->hwnd
, GWL_STYLE
);
1745 BOOL pushedState
= (state
& BST_PUSHED
);
1749 GetClientRect( infoPtr
->hwnd
, &rc
);
1751 /* Send WM_CTLCOLOR to allow changing the font (the colors are fixed) */
1752 if ((hFont
= infoPtr
->font
)) SelectObject( hDC
, hFont
);
1753 parent
= GetParent(infoPtr
->hwnd
);
1754 if (!parent
) parent
= infoPtr
->hwnd
;
1755 SendMessageW( parent
, WM_CTLCOLORBTN
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
1757 hrgn
= set_control_clipping( hDC
, &rc
);
1759 hpen
= CreatePen( PS_SOLID
, 1, GetSysColor(COLOR_WINDOWFRAME
));
1760 hOldPen
= SelectObject(hDC
, hpen
);
1761 hOldBrush
= SelectObject(hDC
,GetSysColorBrush(COLOR_BTNFACE
));
1762 oldBkMode
= SetBkMode(hDC
, TRANSPARENT
);
1764 init_custom_draw(&nmcd
, infoPtr
, hDC
, &rc
);
1766 /* Send erase notifications */
1767 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
1768 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
1770 if (get_button_type(style
) == BS_DEFPUSHBUTTON
)
1772 if (action
!= ODA_FOCUS
)
1773 Rectangle(hDC
, rc
.left
, rc
.top
, rc
.right
, rc
.bottom
);
1774 InflateRect( &rc
, -1, -1 );
1777 /* Skip the frame drawing if only focus has changed */
1778 if (action
!= ODA_FOCUS
)
1780 uState
= DFCS_BUTTONPUSH
;
1782 if (style
& BS_FLAT
)
1783 uState
|= DFCS_MONO
;
1784 else if (pushedState
)
1786 if (get_button_type(style
) == BS_DEFPUSHBUTTON
)
1787 uState
|= DFCS_FLAT
;
1789 uState
|= DFCS_PUSHED
;
1792 if (state
& (BST_CHECKED
| BST_INDETERMINATE
))
1793 uState
|= DFCS_CHECKED
;
1795 DrawFrameControl( hDC
, &rc
, DFC_BUTTON
, uState
);
1798 if (cdrf
& CDRF_NOTIFYPOSTERASE
)
1800 nmcd
.dwDrawStage
= CDDS_POSTERASE
;
1801 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
1804 /* Send paint notifications */
1805 nmcd
.dwDrawStage
= CDDS_PREPAINT
;
1806 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
1807 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
1809 if (!(cdrf
& CDRF_DOERASE
) && action
!= ODA_FOCUS
)
1811 /* draw button label */
1813 /* Shrink label rect at all sides by 2 so that the content won't touch the surrounding frame */
1814 InflateRect(&labelRect
, -2, -2);
1815 dtFlags
= BUTTON_CalcLayoutRects(infoPtr
, hDC
, &labelRect
, &imageRect
, &textRect
);
1817 if (dtFlags
!= (UINT
)-1L)
1819 if (pushedState
) OffsetRect(&labelRect
, 1, 1);
1821 oldTxtColor
= SetTextColor( hDC
, GetSysColor(COLOR_BTNTEXT
) );
1823 BUTTON_DrawLabel(infoPtr
, hDC
, dtFlags
, &imageRect
, &textRect
);
1825 SetTextColor( hDC
, oldTxtColor
);
1829 if (cdrf
& CDRF_NOTIFYPOSTPAINT
)
1831 nmcd
.dwDrawStage
= CDDS_POSTPAINT
;
1832 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
1834 if ((cdrf
& CDRF_SKIPPOSTPAINT
) || dtFlags
== (UINT
)-1L) goto cleanup
;
1836 if (action
== ODA_FOCUS
|| (state
& BST_FOCUS
))
1838 InflateRect( &rc
, -2, -2 );
1839 DrawFocusRect( hDC
, &rc
);
1843 SelectObject( hDC
, hOldPen
);
1844 SelectObject( hDC
, hOldBrush
);
1845 SetBkMode(hDC
, oldBkMode
);
1846 SelectClipRgn( hDC
, hrgn
);
1847 if (hrgn
) DeleteObject( hrgn
);
1848 DeleteObject( hpen
);
1851 /**********************************************************************
1852 * Check Box & Radio Button Functions
1855 static void CB_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
)
1857 RECT rbox
, labelRect
, imageRect
, textRect
, client
;
1859 int delta
, text_offset
, checkBoxWidth
, checkBoxHeight
;
1864 LONG state
= infoPtr
->state
;
1865 LONG style
= GetWindowLongW( infoPtr
->hwnd
, GWL_STYLE
);
1866 LONG ex_style
= GetWindowLongW( infoPtr
->hwnd
, GWL_EXSTYLE
);
1870 if (style
& BS_PUSHLIKE
)
1872 PB_Paint( infoPtr
, hDC
, action
);
1876 GetClientRect(infoPtr
->hwnd
, &client
);
1877 rbox
= labelRect
= client
;
1879 checkBoxWidth
= 12 * GetDpiForWindow( infoPtr
->hwnd
) / 96 + 1;
1880 checkBoxHeight
= 12 * GetDpiForWindow( infoPtr
->hwnd
) / 96 + 1;
1882 if ((hFont
= infoPtr
->font
)) SelectObject( hDC
, hFont
);
1883 GetCharWidthW( hDC
, '0', '0', &text_offset
);
1886 parent
= GetParent(infoPtr
->hwnd
);
1887 if (!parent
) parent
= infoPtr
->hwnd
;
1888 hBrush
= (HBRUSH
)SendMessageW(parent
, WM_CTLCOLORSTATIC
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
1889 if (!hBrush
) /* did the app forget to call defwindowproc ? */
1890 hBrush
= (HBRUSH
)DefWindowProcW(parent
, WM_CTLCOLORSTATIC
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
1891 hrgn
= set_control_clipping( hDC
, &client
);
1893 if (style
& BS_LEFTTEXT
|| ex_style
& WS_EX_RIGHT
)
1895 labelRect
.right
-= checkBoxWidth
+ text_offset
;
1896 rbox
.left
= rbox
.right
- checkBoxWidth
;
1900 labelRect
.left
+= checkBoxWidth
+ text_offset
;
1901 rbox
.right
= checkBoxWidth
;
1904 init_custom_draw(&nmcd
, infoPtr
, hDC
, &client
);
1906 /* Send erase notifications */
1907 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
1908 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
1910 /* Since WM_ERASEBKGND does nothing, first prepare background */
1911 if (action
== ODA_SELECT
) FillRect( hDC
, &rbox
, hBrush
);
1912 if (action
== ODA_DRAWENTIRE
) FillRect( hDC
, &client
, hBrush
);
1913 if (cdrf
& CDRF_NOTIFYPOSTERASE
)
1915 nmcd
.dwDrawStage
= CDDS_POSTERASE
;
1916 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
1921 dtFlags
= BUTTON_CalcLayoutRects(infoPtr
, hDC
, &labelRect
, &imageRect
, &textRect
);
1923 /* Only adjust rbox when rtext is valid */
1924 if (dtFlags
!= (UINT
)-1L)
1926 rbox
.top
= labelRect
.top
;
1927 rbox
.bottom
= labelRect
.bottom
;
1930 /* Send paint notifications */
1931 nmcd
.dwDrawStage
= CDDS_PREPAINT
;
1932 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
1933 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
1935 /* Draw the check-box bitmap */
1936 if (!(cdrf
& CDRF_DOERASE
))
1938 if (action
== ODA_DRAWENTIRE
|| action
== ODA_SELECT
)
1942 if ((get_button_type(style
) == BS_RADIOBUTTON
) ||
1943 (get_button_type(style
) == BS_AUTORADIOBUTTON
)) flags
= DFCS_BUTTONRADIO
;
1944 else if (state
& BST_INDETERMINATE
) flags
= DFCS_BUTTON3STATE
;
1945 else flags
= DFCS_BUTTONCHECK
;
1947 if (state
& (BST_CHECKED
| BST_INDETERMINATE
)) flags
|= DFCS_CHECKED
;
1948 if (state
& BST_PUSHED
) flags
|= DFCS_PUSHED
;
1949 if (style
& WS_DISABLED
) flags
|= DFCS_INACTIVE
;
1951 /* rbox must have the correct height */
1952 delta
= rbox
.bottom
- rbox
.top
- checkBoxHeight
;
1954 if ((style
& BS_VCENTER
) == BS_TOP
)
1957 rbox
.bottom
= rbox
.top
+ checkBoxHeight
;
1960 rbox
.top
-= -delta
/ 2 + 1;
1961 rbox
.bottom
= rbox
.top
+ checkBoxHeight
;
1964 else if ((style
& BS_VCENTER
) == BS_BOTTOM
)
1967 rbox
.top
= rbox
.bottom
- checkBoxHeight
;
1970 rbox
.bottom
+= -delta
/ 2 + 1;
1971 rbox
.top
= rbox
.bottom
- checkBoxHeight
;
1978 int ofs
= delta
/ 2;
1979 rbox
.bottom
-= ofs
+ 1;
1980 rbox
.top
= rbox
.bottom
- checkBoxHeight
;
1984 int ofs
= -delta
/ 2;
1985 rbox
.top
-= ofs
+ 1;
1986 rbox
.bottom
= rbox
.top
+ checkBoxHeight
;
1990 DrawFrameControl(hDC
, &rbox
, DFC_BUTTON
, flags
);
1993 if (dtFlags
!= (UINT
)-1L) /* Something to draw */
1994 if (action
== ODA_DRAWENTIRE
) BUTTON_DrawLabel(infoPtr
, hDC
, dtFlags
, &imageRect
, &textRect
);
1997 if (cdrf
& CDRF_NOTIFYPOSTPAINT
)
1999 nmcd
.dwDrawStage
= CDDS_POSTPAINT
;
2000 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2002 if ((cdrf
& CDRF_SKIPPOSTPAINT
) || dtFlags
== (UINT
)-1L) goto cleanup
;
2005 if (action
== ODA_FOCUS
|| (state
& BST_FOCUS
))
2009 IntersectRect(&labelRect
, &labelRect
, &client
);
2010 DrawFocusRect(hDC
, &labelRect
);
2014 SelectClipRgn( hDC
, hrgn
);
2015 if (hrgn
) DeleteObject( hrgn
);
2019 /**********************************************************************
2020 * BUTTON_CheckAutoRadioButton
2022 * hwnd is checked, uncheck every other auto radio button in group
2024 static void BUTTON_CheckAutoRadioButton( HWND hwnd
)
2026 HWND parent
, sibling
, start
;
2028 parent
= GetParent(hwnd
);
2029 /* make sure that starting control is not disabled or invisible */
2030 start
= sibling
= GetNextDlgGroupItem( parent
, hwnd
, TRUE
);
2033 if (!sibling
) break;
2034 if ((hwnd
!= sibling
) &&
2035 ((GetWindowLongW( sibling
, GWL_STYLE
) & BS_TYPEMASK
) == BS_AUTORADIOBUTTON
))
2036 SendMessageW( sibling
, BM_SETCHECK
, BST_UNCHECKED
, 0 );
2037 sibling
= GetNextDlgGroupItem( parent
, sibling
, FALSE
);
2038 } while (sibling
!= start
);
2042 /**********************************************************************
2043 * Group Box Functions
2046 static void GB_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
)
2048 RECT labelRect
, imageRect
, textRect
, rcFrame
;
2053 LONG style
= GetWindowLongW( infoPtr
->hwnd
, GWL_STYLE
);
2057 if ((hFont
= infoPtr
->font
)) SelectObject( hDC
, hFont
);
2058 /* GroupBox acts like static control, so it sends CTLCOLORSTATIC */
2059 parent
= GetParent(infoPtr
->hwnd
);
2060 if (!parent
) parent
= infoPtr
->hwnd
;
2061 hbr
= (HBRUSH
)SendMessageW(parent
, WM_CTLCOLORSTATIC
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
2062 if (!hbr
) /* did the app forget to call defwindowproc ? */
2063 hbr
= (HBRUSH
)DefWindowProcW(parent
, WM_CTLCOLORSTATIC
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
2064 GetClientRect(infoPtr
->hwnd
, &labelRect
);
2065 rcFrame
= labelRect
;
2066 hrgn
= set_control_clipping(hDC
, &labelRect
);
2068 GetTextMetricsW (hDC
, &tm
);
2069 rcFrame
.top
+= (tm
.tmHeight
/ 2) - 1;
2070 DrawEdge (hDC
, &rcFrame
, EDGE_ETCHED
, BF_RECT
| ((style
& BS_FLAT
) ? BF_FLAT
: 0));
2072 InflateRect(&labelRect
, -7, 1);
2073 dtFlags
= BUTTON_CalcLayoutRects(infoPtr
, hDC
, &labelRect
, &imageRect
, &textRect
);
2075 if (dtFlags
!= (UINT
)-1)
2077 /* Because buttons have CS_PARENTDC class style, there is a chance
2078 * that label will be drawn out of client rect.
2079 * But Windows doesn't clip label's rect, so do I.
2082 /* There is 1-pixel margin at the left, right, and bottom */
2086 FillRect(hDC
, &labelRect
, hbr
);
2091 BUTTON_DrawLabel(infoPtr
, hDC
, dtFlags
, &imageRect
, &textRect
);
2093 SelectClipRgn( hDC
, hrgn
);
2094 if (hrgn
) DeleteObject( hrgn
);
2098 /**********************************************************************
2099 * User Button Functions
2102 static void UB_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
)
2109 LONG state
= infoPtr
->state
;
2112 GetClientRect( infoPtr
->hwnd
, &rc
);
2114 if ((hFont
= infoPtr
->font
)) SelectObject( hDC
, hFont
);
2116 parent
= GetParent(infoPtr
->hwnd
);
2117 if (!parent
) parent
= infoPtr
->hwnd
;
2118 hBrush
= (HBRUSH
)SendMessageW(parent
, WM_CTLCOLORBTN
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
2119 if (!hBrush
) /* did the app forget to call defwindowproc ? */
2120 hBrush
= (HBRUSH
)DefWindowProcW(parent
, WM_CTLCOLORBTN
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
2122 if (action
== ODA_FOCUS
|| (state
& BST_FOCUS
))
2124 init_custom_draw(&nmcd
, infoPtr
, hDC
, &rc
);
2126 /* Send erase notifications */
2127 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2128 if (cdrf
& CDRF_SKIPDEFAULT
) goto notify
;
2131 FillRect( hDC
, &rc
, hBrush
);
2132 if (action
== ODA_FOCUS
|| (state
& BST_FOCUS
))
2134 if (cdrf
& CDRF_NOTIFYPOSTERASE
)
2136 nmcd
.dwDrawStage
= CDDS_POSTERASE
;
2137 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2140 /* Send paint notifications */
2141 nmcd
.dwDrawStage
= CDDS_PREPAINT
;
2142 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2143 if (cdrf
& CDRF_SKIPDEFAULT
) goto notify
;
2144 if (cdrf
& CDRF_NOTIFYPOSTPAINT
)
2146 nmcd
.dwDrawStage
= CDDS_POSTPAINT
;
2147 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2150 if (!(cdrf
& CDRF_SKIPPOSTPAINT
))
2151 DrawFocusRect( hDC
, &rc
);
2158 BUTTON_NOTIFY_PARENT( infoPtr
->hwnd
, (state
& BST_FOCUS
) ? BN_SETFOCUS
: BN_KILLFOCUS
);
2162 BUTTON_NOTIFY_PARENT( infoPtr
->hwnd
, (state
& BST_PUSHED
) ? BN_HILITE
: BN_UNHILITE
);
2171 /**********************************************************************
2172 * Ownerdrawn Button Functions
2175 static void OB_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
)
2177 LONG state
= infoPtr
->state
;
2179 LONG_PTR id
= GetWindowLongPtrW( infoPtr
->hwnd
, GWLP_ID
);
2184 dis
.CtlType
= ODT_BUTTON
;
2187 dis
.itemAction
= action
;
2188 dis
.itemState
= ((state
& BST_FOCUS
) ? ODS_FOCUS
: 0) |
2189 ((state
& BST_PUSHED
) ? ODS_SELECTED
: 0) |
2190 (IsWindowEnabled(infoPtr
->hwnd
) ? 0: ODS_DISABLED
);
2191 dis
.hwndItem
= infoPtr
->hwnd
;
2194 GetClientRect( infoPtr
->hwnd
, &dis
.rcItem
);
2196 if ((hFont
= infoPtr
->font
)) SelectObject( hDC
, hFont
);
2197 parent
= GetParent(infoPtr
->hwnd
);
2198 if (!parent
) parent
= infoPtr
->hwnd
;
2199 SendMessageW( parent
, WM_CTLCOLORBTN
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
2201 hrgn
= set_control_clipping( hDC
, &dis
.rcItem
);
2203 SendMessageW( GetParent(infoPtr
->hwnd
), WM_DRAWITEM
, id
, (LPARAM
)&dis
);
2204 SelectClipRgn( hDC
, hrgn
);
2205 if (hrgn
) DeleteObject( hrgn
);
2209 /**********************************************************************
2210 * Split Button Functions
2212 static void SB_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
)
2214 LONG style
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
2215 LONG state
= infoPtr
->state
;
2216 UINT dtFlags
= (UINT
)-1L;
2218 RECT rc
, push_rect
, dropdown_rect
;
2227 GetClientRect(infoPtr
->hwnd
, &rc
);
2229 /* Send WM_CTLCOLOR to allow changing the font (the colors are fixed) */
2230 if (infoPtr
->font
) SelectObject(hDC
, infoPtr
->font
);
2231 if (!(parent
= GetParent(infoPtr
->hwnd
))) parent
= infoPtr
->hwnd
;
2232 SendMessageW(parent
, WM_CTLCOLORBTN
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
2234 hrgn
= set_control_clipping(hDC
, &rc
);
2236 pen
= CreatePen(PS_SOLID
, 1, GetSysColor(COLOR_WINDOWFRAME
));
2237 old_pen
= SelectObject(hDC
, pen
);
2238 old_brush
= SelectObject(hDC
, GetSysColorBrush(COLOR_BTNFACE
));
2239 old_bk_mode
= SetBkMode(hDC
, TRANSPARENT
);
2241 init_custom_draw(&nmcd
, infoPtr
, hDC
, &rc
);
2243 /* Send erase notifications */
2244 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2245 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
2247 if (get_button_type(style
) == BS_DEFSPLITBUTTON
)
2249 if (action
!= ODA_FOCUS
)
2250 Rectangle(hDC
, rc
.left
, rc
.top
, rc
.right
, rc
.bottom
);
2251 InflateRect(&rc
, -1, -1);
2252 /* The split will now be off by 1 pixel, but
2253 that's exactly what Windows does as well */
2256 get_split_button_rects(infoPtr
, &rc
, &push_rect
, &dropdown_rect
);
2257 if (infoPtr
->split_style
& BCSS_NOSPLIT
)
2260 /* Skip the frame drawing if only focus has changed */
2261 if (action
!= ODA_FOCUS
)
2263 UINT flags
= DFCS_BUTTONPUSH
;
2265 if (style
& BS_FLAT
) flags
|= DFCS_MONO
;
2266 else if (state
& BST_PUSHED
)
2267 flags
|= (get_button_type(style
) == BS_DEFSPLITBUTTON
)
2268 ? DFCS_FLAT
: DFCS_PUSHED
;
2270 if (state
& (BST_CHECKED
| BST_INDETERMINATE
))
2271 flags
|= DFCS_CHECKED
;
2273 if (infoPtr
->split_style
& BCSS_NOSPLIT
)
2274 DrawFrameControl(hDC
, &push_rect
, DFC_BUTTON
, flags
);
2277 UINT dropdown_flags
= flags
& ~DFCS_CHECKED
;
2279 if (state
& BST_DROPDOWNPUSHED
)
2280 dropdown_flags
= (dropdown_flags
& ~DFCS_FLAT
) | DFCS_PUSHED
;
2282 /* Adjust for shadow and draw order so it looks properly */
2283 if (infoPtr
->split_style
& BCSS_ALIGNLEFT
)
2285 dropdown_rect
.right
++;
2286 DrawFrameControl(hDC
, &dropdown_rect
, DFC_BUTTON
, dropdown_flags
);
2287 dropdown_rect
.right
--;
2288 DrawFrameControl(hDC
, &push_rect
, DFC_BUTTON
, flags
);
2293 DrawFrameControl(hDC
, &push_rect
, DFC_BUTTON
, flags
);
2295 DrawFrameControl(hDC
, &dropdown_rect
, DFC_BUTTON
, dropdown_flags
);
2300 if (cdrf
& CDRF_NOTIFYPOSTERASE
)
2302 nmcd
.dwDrawStage
= CDDS_POSTERASE
;
2303 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2306 /* Send paint notifications */
2307 nmcd
.dwDrawStage
= CDDS_PREPAINT
;
2308 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2309 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
2311 /* Shrink push button rect so that the content won't touch the surrounding frame */
2312 InflateRect(&push_rect
, -2, -2);
2314 if (!(cdrf
& CDRF_DOERASE
) && action
!= ODA_FOCUS
)
2316 COLORREF old_color
= SetTextColor(hDC
, GetSysColor(COLOR_BTNTEXT
));
2317 RECT label_rect
= push_rect
, image_rect
, text_rect
;
2319 dtFlags
= BUTTON_CalcLayoutRects(infoPtr
, hDC
, &label_rect
, &image_rect
, &text_rect
);
2321 if (dtFlags
!= (UINT
)-1L)
2322 BUTTON_DrawLabel(infoPtr
, hDC
, dtFlags
, &image_rect
, &text_rect
);
2324 draw_split_button_dropdown_glyph(infoPtr
, hDC
, &dropdown_rect
);
2325 SetTextColor(hDC
, old_color
);
2328 if (cdrf
& CDRF_NOTIFYPOSTPAINT
)
2330 nmcd
.dwDrawStage
= CDDS_POSTPAINT
;
2331 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2333 if ((cdrf
& CDRF_SKIPPOSTPAINT
) || dtFlags
== (UINT
)-1L) goto cleanup
;
2335 if (action
== ODA_FOCUS
|| (state
& BST_FOCUS
))
2336 DrawFocusRect(hDC
, &push_rect
);
2339 SelectObject(hDC
, old_pen
);
2340 SelectObject(hDC
, old_brush
);
2341 SetBkMode(hDC
, old_bk_mode
);
2342 SelectClipRgn(hDC
, hrgn
);
2343 if (hrgn
) DeleteObject(hrgn
);
2347 /* Given the full button rect of the split button, retrieve the push part and the dropdown part */
2348 static inline void get_split_button_rects(const BUTTON_INFO
*infoPtr
, const RECT
*button_rect
,
2349 RECT
*push_rect
, RECT
*dropdown_rect
)
2351 *push_rect
= *dropdown_rect
= *button_rect
;
2353 /* The dropdown takes priority if the client rect is too small, it will only have a dropdown */
2354 if (infoPtr
->split_style
& BCSS_ALIGNLEFT
)
2356 dropdown_rect
->right
= min(button_rect
->left
+ infoPtr
->glyph_size
.cx
, button_rect
->right
);
2357 push_rect
->left
= dropdown_rect
->right
;
2361 dropdown_rect
->left
= max(button_rect
->right
- infoPtr
->glyph_size
.cx
, button_rect
->left
);
2362 push_rect
->right
= dropdown_rect
->left
;
2366 /* Notify the parent if the point is within the dropdown and return TRUE (always notify if NULL) */
2367 static BOOL
notify_split_button_dropdown(const BUTTON_INFO
*infoPtr
, const POINT
*pt
, HWND hwnd
)
2371 GetClientRect(hwnd
, &nmbcd
.rcButton
);
2374 RECT push_rect
, dropdown_rect
;
2376 get_split_button_rects(infoPtr
, &nmbcd
.rcButton
, &push_rect
, &dropdown_rect
);
2377 if (!PtInRect(&dropdown_rect
, *pt
))
2380 /* If it's already down (set manually via BCM_SETDROPDOWNSTATE), fake the notify */
2381 if (infoPtr
->state
& BST_DROPDOWNPUSHED
)
2384 SendMessageW(hwnd
, BCM_SETDROPDOWNSTATE
, TRUE
, 0);
2386 nmbcd
.hdr
.hwndFrom
= hwnd
;
2387 nmbcd
.hdr
.idFrom
= GetWindowLongPtrW(hwnd
, GWLP_ID
);
2388 nmbcd
.hdr
.code
= BCN_DROPDOWN
;
2389 SendMessageW(GetParent(hwnd
), WM_NOTIFY
, nmbcd
.hdr
.idFrom
, (LPARAM
)&nmbcd
);
2391 SendMessageW(hwnd
, BCM_SETDROPDOWNSTATE
, FALSE
, 0);
2395 /* Draw the split button dropdown glyph or image */
2396 static void draw_split_button_dropdown_glyph(const BUTTON_INFO
*infoPtr
, HDC hdc
, RECT
*rect
)
2398 if (infoPtr
->split_style
& BCSS_IMAGE
)
2402 /* When the glyph is an image list, Windows is very buggy with BCSS_STRETCH,
2403 positions it weirdly and doesn't even stretch it, but instead extends the
2404 image, leaking into other images in the list (or black if none). Instead,
2405 we'll ignore this and just position it at center as without BCSS_STRETCH. */
2406 if (!ImageList_GetIconSize(infoPtr
->glyph
, &w
, &h
)) return;
2408 ImageList_Draw(infoPtr
->glyph
,
2409 (ImageList_GetImageCount(infoPtr
->glyph
) == 1) ? 0 : get_draw_state(infoPtr
) - 1,
2410 hdc
, rect
->left
+ (rect
->right
- rect
->left
- w
) / 2,
2411 rect
->top
+ (rect
->bottom
- rect
->top
- h
) / 2, ILD_NORMAL
);
2413 else if (infoPtr
->glyph_size
.cy
>= 0)
2415 /* infoPtr->glyph is a character code from Marlett */
2416 HFONT font
, old_font
;
2417 LOGFONTW logfont
= { 0, 0, 0, 0, FW_NORMAL
, 0, 0, 0, SYMBOL_CHARSET
, 0, 0, 0, 0,
2418 { 'M','a','r','l','e','t','t',0 } };
2419 if (infoPtr
->glyph_size
.cy
)
2421 /* BCSS_STRETCH preserves aspect ratio, uses minimum as size */
2422 if (infoPtr
->split_style
& BCSS_STRETCH
)
2423 logfont
.lfHeight
= min(infoPtr
->glyph_size
.cx
, infoPtr
->glyph_size
.cy
);
2426 logfont
.lfWidth
= infoPtr
->glyph_size
.cx
;
2427 logfont
.lfHeight
= infoPtr
->glyph_size
.cy
;
2430 else logfont
.lfHeight
= infoPtr
->glyph_size
.cx
;
2432 if ((font
= CreateFontIndirectW(&logfont
)))
2434 old_font
= SelectObject(hdc
, font
);
2435 DrawTextW(hdc
, (const WCHAR
*)&infoPtr
->glyph
, 1, rect
,
2436 DT_CENTER
| DT_VCENTER
| DT_SINGLELINE
| DT_NOCLIP
| DT_NOPREFIX
);
2437 SelectObject(hdc
, old_font
);
2444 /**********************************************************************
2445 * Command Link Functions
2447 static void CL_Paint( const BUTTON_INFO
*infoPtr
, HDC hDC
, UINT action
)
2449 LONG style
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
2450 LONG state
= infoPtr
->state
;
2452 RECT rc
, content_rect
;
2461 GetClientRect(infoPtr
->hwnd
, &rc
);
2463 /* Command Links are not affected by the button's font, and are based
2464 on the default message font. Furthermore, they are not affected by
2465 any of the alignment styles (and always align with the top-left). */
2466 if (!(parent
= GetParent(infoPtr
->hwnd
))) parent
= infoPtr
->hwnd
;
2467 SendMessageW(parent
, WM_CTLCOLORBTN
, (WPARAM
)hDC
, (LPARAM
)infoPtr
->hwnd
);
2469 hrgn
= set_control_clipping(hDC
, &rc
);
2471 pen
= CreatePen(PS_SOLID
, 1, GetSysColor(COLOR_WINDOWFRAME
));
2472 old_pen
= SelectObject(hDC
, pen
);
2473 old_brush
= SelectObject(hDC
, GetSysColorBrush(COLOR_BTNFACE
));
2474 old_bk_mode
= SetBkMode(hDC
, TRANSPARENT
);
2476 init_custom_draw(&nmcd
, infoPtr
, hDC
, &rc
);
2478 /* Send erase notifications */
2479 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2480 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
2483 if (get_button_type(style
) == BS_DEFCOMMANDLINK
)
2485 if (action
!= ODA_FOCUS
)
2486 Rectangle(hDC
, rc
.left
, rc
.top
, rc
.right
, rc
.bottom
);
2487 InflateRect(&rc
, -1, -1);
2490 /* Skip the frame drawing if only focus has changed */
2491 if (action
!= ODA_FOCUS
)
2493 if (!(state
& (BST_HOT
| BST_PUSHED
| BST_CHECKED
| BST_INDETERMINATE
)))
2494 FillRect(hDC
, &rc
, GetSysColorBrush(COLOR_BTNFACE
));
2497 UINT flags
= DFCS_BUTTONPUSH
;
2499 if (style
& BS_FLAT
) flags
|= DFCS_MONO
;
2500 else if (state
& BST_PUSHED
) flags
|= DFCS_PUSHED
;
2502 if (state
& (BST_CHECKED
| BST_INDETERMINATE
))
2503 flags
|= DFCS_CHECKED
;
2504 DrawFrameControl(hDC
, &rc
, DFC_BUTTON
, flags
);
2508 if (cdrf
& CDRF_NOTIFYPOSTERASE
)
2510 nmcd
.dwDrawStage
= CDDS_POSTERASE
;
2511 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2514 /* Send paint notifications */
2515 nmcd
.dwDrawStage
= CDDS_PREPAINT
;
2516 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2517 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
2519 if (!(cdrf
& CDRF_DOERASE
) && action
!= ODA_FOCUS
)
2521 UINT flags
= IsWindowEnabled(infoPtr
->hwnd
) ? DSS_NORMAL
: DSS_DISABLED
;
2522 COLORREF old_color
= SetTextColor(hDC
, GetSysColor(flags
== DSS_NORMAL
?
2523 COLOR_BTNTEXT
: COLOR_GRAYTEXT
));
2524 HIMAGELIST defimg
= NULL
;
2525 NONCLIENTMETRICSW ncm
;
2529 /* Command Links ignore the margins of the image list or its alignment */
2530 if (infoPtr
->u
.image
|| infoPtr
->imagelist
.himl
)
2531 img_size
= BUTTON_GetImageSize(infoPtr
);
2534 img_size
.cx
= img_size
.cy
= command_link_defglyph_size
;
2535 defimg
= ImageList_LoadImageW(COMCTL32_hModule
, (LPCWSTR
)MAKEINTRESOURCE(IDB_CMDLINK
),
2536 img_size
.cx
, 3, CLR_NONE
, IMAGE_BITMAP
, LR_CREATEDIBSECTION
);
2539 /* Shrink rect by the command link margin, except on bottom (just the frame) */
2540 InflateRect(&content_rect
, -command_link_margin
, -command_link_margin
);
2541 content_rect
.bottom
+= command_link_margin
- 2;
2543 ncm
.cbSize
= sizeof(ncm
);
2544 if (SystemParametersInfoW(SPI_GETNONCLIENTMETRICS
, sizeof(ncm
), &ncm
, 0))
2546 LONG note_weight
= ncm
.lfMessageFont
.lfWeight
;
2547 RECT r
= content_rect
;
2551 if (img_size
.cx
) r
.left
+= img_size
.cx
+ command_link_margin
;
2554 ncm
.lfMessageFont
.lfWeight
= FW_BOLD
;
2555 if ((font
= CreateFontIndirectW(&ncm
.lfMessageFont
)))
2557 if ((text
= get_button_text(infoPtr
)))
2559 SelectObject(hDC
, font
);
2560 txt_h
= DrawTextW(hDC
, text
, -1, &r
,
2561 DT_TOP
| DT_LEFT
| DT_WORDBREAK
| DT_END_ELLIPSIS
);
2568 ncm
.lfMessageFont
.lfWeight
= note_weight
;
2569 if (infoPtr
->note
&& (font
= CreateFontIndirectW(&ncm
.lfMessageFont
)))
2572 SelectObject(hDC
, font
);
2573 DrawTextW(hDC
, infoPtr
->note
, infoPtr
->note_length
, &r
,
2574 DT_TOP
| DT_LEFT
| DT_WORDBREAK
| DT_NOPREFIX
);
2579 /* Position the image at the vertical center of the drawn text (not note) */
2580 txt_h
= min(txt_h
, content_rect
.bottom
- content_rect
.top
);
2581 if (img_size
.cy
< txt_h
) content_rect
.top
+= (txt_h
- img_size
.cy
) / 2;
2583 content_rect
.right
= content_rect
.left
+ img_size
.cx
;
2584 content_rect
.bottom
= content_rect
.top
+ img_size
.cy
;
2589 if (flags
== DSS_DISABLED
) i
= 2;
2590 else if (state
& BST_HOT
) i
= 1;
2592 ImageList_Draw(defimg
, i
, hDC
, content_rect
.left
, content_rect
.top
, ILD_NORMAL
);
2593 ImageList_Destroy(defimg
);
2596 BUTTON_DrawImage(infoPtr
, hDC
, NULL
, flags
, &content_rect
);
2598 SetTextColor(hDC
, old_color
);
2601 if (cdrf
& CDRF_NOTIFYPOSTPAINT
)
2603 nmcd
.dwDrawStage
= CDDS_POSTPAINT
;
2604 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2606 if (cdrf
& CDRF_SKIPPOSTPAINT
) goto cleanup
;
2608 if (action
== ODA_FOCUS
|| (state
& BST_FOCUS
))
2610 InflateRect(&rc
, -2, -2);
2611 DrawFocusRect(hDC
, &rc
);
2615 SelectObject(hDC
, old_pen
);
2616 SelectObject(hDC
, old_brush
);
2617 SetBkMode(hDC
, old_bk_mode
);
2618 SelectClipRgn(hDC
, hrgn
);
2619 if (hrgn
) DeleteObject(hrgn
);
2624 /**********************************************************************
2625 * Themed Paint Functions
2627 static void PB_ThemedPaint(HTHEME theme
, const BUTTON_INFO
*infoPtr
, HDC hDC
, int state
, UINT dtFlags
, BOOL focused
)
2629 RECT bgRect
, textRect
;
2630 HFONT font
= infoPtr
->font
;
2631 HFONT hPrevFont
= font
? SelectObject(hDC
, font
) : NULL
;
2637 GetClientRect(infoPtr
->hwnd
, &bgRect
);
2638 GetThemeBackgroundContentRect(theme
, hDC
, BP_PUSHBUTTON
, state
, &bgRect
, &textRect
);
2639 init_custom_draw(&nmcd
, infoPtr
, hDC
, &bgRect
);
2641 parent
= GetParent(infoPtr
->hwnd
);
2642 if (!parent
) parent
= infoPtr
->hwnd
;
2644 /* Send erase notifications */
2645 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2646 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
2648 if (IsThemeBackgroundPartiallyTransparent(theme
, BP_PUSHBUTTON
, state
))
2649 DrawThemeParentBackground(infoPtr
->hwnd
, hDC
, NULL
);
2650 DrawThemeBackground(theme
, hDC
, BP_PUSHBUTTON
, state
, &bgRect
, NULL
);
2652 if (cdrf
& CDRF_NOTIFYPOSTERASE
)
2654 nmcd
.dwDrawStage
= CDDS_POSTERASE
;
2655 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2658 /* Send paint notifications */
2659 nmcd
.dwDrawStage
= CDDS_PREPAINT
;
2660 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2661 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
2663 if (!(cdrf
& CDRF_DOERASE
) && (text
= get_button_text(infoPtr
)))
2665 DrawThemeText(theme
, hDC
, BP_PUSHBUTTON
, state
, text
, lstrlenW(text
), dtFlags
, 0, &textRect
);
2669 if (cdrf
& CDRF_NOTIFYPOSTPAINT
)
2671 nmcd
.dwDrawStage
= CDDS_POSTPAINT
;
2672 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2674 if (cdrf
& CDRF_SKIPPOSTPAINT
) goto cleanup
;
2679 RECT focusRect
= bgRect
;
2681 GetThemeMargins(theme
, hDC
, BP_PUSHBUTTON
, state
, TMT_CONTENTMARGINS
, NULL
, &margins
);
2683 focusRect
.left
+= margins
.cxLeftWidth
;
2684 focusRect
.top
+= margins
.cyTopHeight
;
2685 focusRect
.right
-= margins
.cxRightWidth
;
2686 focusRect
.bottom
-= margins
.cyBottomHeight
;
2688 DrawFocusRect( hDC
, &focusRect
);
2692 if (hPrevFont
) SelectObject(hDC
, hPrevFont
);
2695 static void CB_ThemedPaint(HTHEME theme
, const BUTTON_INFO
*infoPtr
, HDC hDC
, int state
, UINT dtFlags
, BOOL focused
)
2698 RECT bgRect
, textRect
;
2699 HFONT font
, hPrevFont
= NULL
;
2700 DWORD dwStyle
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
2701 UINT btn_type
= get_button_type( dwStyle
);
2702 int part
= (btn_type
== BS_RADIOBUTTON
) || (btn_type
== BS_AUTORADIOBUTTON
) ? BP_RADIOBUTTON
: BP_CHECKBOX
;
2708 BOOL created_font
= FALSE
;
2710 HRESULT hr
= GetThemeFont(theme
, hDC
, part
, state
, TMT_FONT
, &lf
);
2711 if (SUCCEEDED(hr
)) {
2712 font
= CreateFontIndirectW(&lf
);
2714 TRACE("Failed to create font\n");
2716 TRACE("font = %s\n", debugstr_w(lf
.lfFaceName
));
2717 hPrevFont
= SelectObject(hDC
, font
);
2718 created_font
= TRUE
;
2721 font
= (HFONT
)SendMessageW(infoPtr
->hwnd
, WM_GETFONT
, 0, 0);
2722 hPrevFont
= SelectObject(hDC
, font
);
2725 if (FAILED(GetThemePartSize(theme
, hDC
, part
, state
, NULL
, TS_DRAW
, &sz
)))
2728 GetClientRect(infoPtr
->hwnd
, &bgRect
);
2729 GetThemeBackgroundContentRect(theme
, hDC
, part
, state
, &bgRect
, &textRect
);
2730 init_custom_draw(&nmcd
, infoPtr
, hDC
, &bgRect
);
2732 if (dtFlags
& DT_SINGLELINE
) /* Center the checkbox / radio button to the text. */
2733 bgRect
.top
= bgRect
.top
+ (textRect
.bottom
- textRect
.top
- sz
.cy
) / 2;
2735 /* adjust for the check/radio marker */
2736 bgRect
.bottom
= bgRect
.top
+ sz
.cy
;
2737 bgRect
.right
= bgRect
.left
+ sz
.cx
;
2738 textRect
.left
= bgRect
.right
+ 6;
2740 parent
= GetParent(infoPtr
->hwnd
);
2741 if (!parent
) parent
= infoPtr
->hwnd
;
2743 /* Send erase notifications */
2744 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2745 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
2747 DrawThemeParentBackground(infoPtr
->hwnd
, hDC
, NULL
);
2748 DrawThemeBackground(theme
, hDC
, part
, state
, &bgRect
, NULL
);
2750 if (cdrf
& CDRF_NOTIFYPOSTERASE
)
2752 nmcd
.dwDrawStage
= CDDS_POSTERASE
;
2753 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2756 /* Send paint notifications */
2757 nmcd
.dwDrawStage
= CDDS_PREPAINT
;
2758 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2759 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
2761 text
= get_button_text(infoPtr
);
2762 if (!(cdrf
& CDRF_DOERASE
) && text
)
2763 DrawThemeText(theme
, hDC
, part
, state
, text
, lstrlenW(text
), dtFlags
, 0, &textRect
);
2765 if (cdrf
& CDRF_NOTIFYPOSTPAINT
)
2767 nmcd
.dwDrawStage
= CDDS_POSTPAINT
;
2768 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2773 if (!(cdrf
& CDRF_SKIPPOSTPAINT
) && focused
)
2777 focusRect
= textRect
;
2779 DrawTextW(hDC
, text
, lstrlenW(text
), &focusRect
, dtFlags
| DT_CALCRECT
);
2781 if (focusRect
.right
< textRect
.right
) focusRect
.right
++;
2782 focusRect
.bottom
= textRect
.bottom
;
2784 DrawFocusRect( hDC
, &focusRect
);
2791 if (created_font
) DeleteObject(font
);
2792 if (hPrevFont
) SelectObject(hDC
, hPrevFont
);
2795 static void GB_ThemedPaint(HTHEME theme
, const BUTTON_INFO
*infoPtr
, HDC hDC
, int state
, UINT dtFlags
, BOOL focused
)
2797 RECT bgRect
, textRect
, contentRect
;
2798 WCHAR
*text
= get_button_text(infoPtr
);
2800 HFONT font
, hPrevFont
= NULL
;
2801 BOOL created_font
= FALSE
;
2803 HRESULT hr
= GetThemeFont(theme
, hDC
, BP_GROUPBOX
, state
, TMT_FONT
, &lf
);
2804 if (SUCCEEDED(hr
)) {
2805 font
= CreateFontIndirectW(&lf
);
2807 TRACE("Failed to create font\n");
2809 hPrevFont
= SelectObject(hDC
, font
);
2810 created_font
= TRUE
;
2813 font
= (HFONT
)SendMessageW(infoPtr
->hwnd
, WM_GETFONT
, 0, 0);
2814 hPrevFont
= SelectObject(hDC
, font
);
2817 GetClientRect(infoPtr
->hwnd
, &bgRect
);
2823 GetTextExtentPoint32W(hDC
, text
, lstrlenW(text
), &textExtent
);
2824 bgRect
.top
+= (textExtent
.cy
/ 2);
2825 textRect
.left
+= 10;
2826 textRect
.bottom
= textRect
.top
+ textExtent
.cy
;
2827 textRect
.right
= textRect
.left
+ textExtent
.cx
+ 4;
2829 ExcludeClipRect(hDC
, textRect
.left
, textRect
.top
, textRect
.right
, textRect
.bottom
);
2832 GetThemeBackgroundContentRect(theme
, hDC
, BP_GROUPBOX
, state
, &bgRect
, &contentRect
);
2833 ExcludeClipRect(hDC
, contentRect
.left
, contentRect
.top
, contentRect
.right
, contentRect
.bottom
);
2835 if (IsThemeBackgroundPartiallyTransparent(theme
, BP_GROUPBOX
, state
))
2836 DrawThemeParentBackground(infoPtr
->hwnd
, hDC
, NULL
);
2837 DrawThemeBackground(theme
, hDC
, BP_GROUPBOX
, state
, &bgRect
, NULL
);
2839 SelectClipRgn(hDC
, NULL
);
2843 InflateRect(&textRect
, -2, 0);
2844 DrawThemeText(theme
, hDC
, BP_GROUPBOX
, state
, text
, lstrlenW(text
), 0, 0, &textRect
);
2848 if (created_font
) DeleteObject(font
);
2849 if (hPrevFont
) SelectObject(hDC
, hPrevFont
);
2852 static void SB_ThemedPaint(HTHEME theme
, const BUTTON_INFO
*infoPtr
, HDC hDC
, int state
, UINT dtFlags
, BOOL focused
)
2854 HFONT old_font
= infoPtr
->font
? SelectObject(hDC
, infoPtr
->font
) : NULL
;
2855 RECT rc
, content_rect
, push_rect
, dropdown_rect
;
2860 GetClientRect(infoPtr
->hwnd
, &rc
);
2861 init_custom_draw(&nmcd
, infoPtr
, hDC
, &rc
);
2863 parent
= GetParent(infoPtr
->hwnd
);
2864 if (!parent
) parent
= infoPtr
->hwnd
;
2866 /* Send erase notifications */
2867 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2868 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
2870 if (IsThemeBackgroundPartiallyTransparent(theme
, BP_PUSHBUTTON
, state
))
2871 DrawThemeParentBackground(infoPtr
->hwnd
, hDC
, NULL
);
2873 /* The zone outside the content is ignored for the dropdown (draws over) */
2874 GetThemeBackgroundContentRect(theme
, hDC
, BP_PUSHBUTTON
, state
, &rc
, &content_rect
);
2875 get_split_button_rects(infoPtr
, &rc
, &push_rect
, &dropdown_rect
);
2877 if (infoPtr
->split_style
& BCSS_NOSPLIT
)
2880 DrawThemeBackground(theme
, hDC
, BP_PUSHBUTTON
, state
, &rc
, NULL
);
2884 RECT r
= { dropdown_rect
.left
, content_rect
.top
, dropdown_rect
.right
, content_rect
.bottom
};
2885 UINT edge
= (infoPtr
->split_style
& BCSS_ALIGNLEFT
) ? BF_RIGHT
: BF_LEFT
;
2886 const RECT
*clip
= NULL
;
2888 /* If only the dropdown is pressed, we need to draw it separately */
2889 if (state
!= PBS_PRESSED
&& (infoPtr
->state
& BST_DROPDOWNPUSHED
))
2891 DrawThemeBackground(theme
, hDC
, BP_PUSHBUTTON
, PBS_PRESSED
, &rc
, &dropdown_rect
);
2894 DrawThemeBackground(theme
, hDC
, BP_PUSHBUTTON
, state
, &rc
, clip
);
2896 /* Draw the separator */
2897 DrawThemeEdge(theme
, hDC
, BP_PUSHBUTTON
, state
, &r
, EDGE_ETCHED
, edge
, NULL
);
2899 /* The content rect should be the content area of the push button */
2900 GetThemeBackgroundContentRect(theme
, hDC
, BP_PUSHBUTTON
, state
, &push_rect
, &content_rect
);
2903 if (cdrf
& CDRF_NOTIFYPOSTERASE
)
2905 nmcd
.dwDrawStage
= CDDS_POSTERASE
;
2906 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2909 /* Send paint notifications */
2910 nmcd
.dwDrawStage
= CDDS_PREPAINT
;
2911 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2912 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
2914 if (!(cdrf
& CDRF_DOERASE
))
2916 COLORREF old_color
, color
;
2920 if ((text
= get_button_text(infoPtr
)))
2922 DrawThemeText(theme
, hDC
, BP_PUSHBUTTON
, state
, text
, lstrlenW(text
), dtFlags
, 0, &content_rect
);
2926 GetThemeColor(theme
, BP_PUSHBUTTON
, state
, TMT_TEXTCOLOR
, &color
);
2927 old_bk_mode
= SetBkMode(hDC
, TRANSPARENT
);
2928 old_color
= SetTextColor(hDC
, color
);
2930 draw_split_button_dropdown_glyph(infoPtr
, hDC
, &dropdown_rect
);
2932 SetTextColor(hDC
, old_color
);
2933 SetBkMode(hDC
, old_bk_mode
);
2936 if (cdrf
& CDRF_NOTIFYPOSTPAINT
)
2938 nmcd
.dwDrawStage
= CDDS_POSTPAINT
;
2939 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2941 if (cdrf
& CDRF_SKIPPOSTPAINT
) goto cleanup
;
2947 GetThemeMargins(theme
, hDC
, BP_PUSHBUTTON
, state
, TMT_CONTENTMARGINS
, NULL
, &margins
);
2949 push_rect
.left
+= margins
.cxLeftWidth
;
2950 push_rect
.top
+= margins
.cyTopHeight
;
2951 push_rect
.right
-= margins
.cxRightWidth
;
2952 push_rect
.bottom
-= margins
.cyBottomHeight
;
2953 DrawFocusRect(hDC
, &push_rect
);
2957 if (old_font
) SelectObject(hDC
, old_font
);
2960 static void CL_ThemedPaint(HTHEME theme
, const BUTTON_INFO
*infoPtr
, HDC hDC
, int state
, UINT dtFlags
, BOOL focused
)
2962 HFONT old_font
= infoPtr
->font
? SelectObject(hDC
, infoPtr
->font
) : NULL
;
2968 GetClientRect(infoPtr
->hwnd
, &rc
);
2969 init_custom_draw(&nmcd
, infoPtr
, hDC
, &rc
);
2971 parent
= GetParent(infoPtr
->hwnd
);
2972 if (!parent
) parent
= infoPtr
->hwnd
;
2974 /* Send erase notifications */
2975 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2976 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
2978 if (IsThemeBackgroundPartiallyTransparent(theme
, BP_COMMANDLINK
, state
))
2979 DrawThemeParentBackground(infoPtr
->hwnd
, hDC
, NULL
);
2980 DrawThemeBackground(theme
, hDC
, BP_COMMANDLINK
, state
, &rc
, NULL
);
2982 if (cdrf
& CDRF_NOTIFYPOSTERASE
)
2984 nmcd
.dwDrawStage
= CDDS_POSTERASE
;
2985 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2988 /* Send paint notifications */
2989 nmcd
.dwDrawStage
= CDDS_PREPAINT
;
2990 cdrf
= SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
2991 if (cdrf
& CDRF_SKIPDEFAULT
) goto cleanup
;
2993 if (!(cdrf
& CDRF_DOERASE
))
3000 GetThemeBackgroundContentRect(theme
, hDC
, BP_COMMANDLINK
, state
, &rc
, &r
);
3002 /* The text alignment and styles are fixed and don't depend on button styles */
3003 dtFlags
= DT_TOP
| DT_LEFT
| DT_WORDBREAK
;
3005 /* Command Links ignore the margins of the image list or its alignment */
3006 if (infoPtr
->u
.image
|| infoPtr
->imagelist
.himl
)
3007 img_size
= BUTTON_GetImageSize(infoPtr
);
3009 GetThemePartSize(theme
, NULL
, BP_COMMANDLINKGLYPH
, state
, NULL
, TS_DRAW
, &img_size
);
3012 if (img_size
.cx
) r
.left
+= img_size
.cx
+ command_link_margin
;
3015 if ((text
= get_button_text(infoPtr
)))
3017 UINT len
= lstrlenW(text
);
3020 GetThemeTextExtent(theme
, hDC
, BP_COMMANDLINK
, state
, text
, len
,
3021 dtFlags
| DT_END_ELLIPSIS
, &r
, &text_rect
);
3022 DrawThemeText(theme
, hDC
, BP_COMMANDLINK
, state
, text
, len
,
3023 dtFlags
| DT_END_ELLIPSIS
, 0, &r
);
3025 txt_h
= text_rect
.bottom
- text_rect
.top
;
3035 opts
.dwSize
= sizeof(opts
);
3036 opts
.dwFlags
= DTT_FONTPROP
;
3037 opts
.iFontPropId
= TMT_BODYFONT
;
3038 DrawThemeTextEx(theme
, hDC
, BP_COMMANDLINK
, state
,
3039 infoPtr
->note
, infoPtr
->note_length
,
3040 dtFlags
| DT_NOPREFIX
, &r
, &opts
);
3043 /* Position the image at the vertical center of the drawn text (not note) */
3044 txt_h
= min(txt_h
, img_rect
.bottom
- img_rect
.top
);
3045 if (img_size
.cy
< txt_h
) img_rect
.top
+= (txt_h
- img_size
.cy
) / 2;
3047 img_rect
.right
= img_rect
.left
+ img_size
.cx
;
3048 img_rect
.bottom
= img_rect
.top
+ img_size
.cy
;
3050 if (infoPtr
->u
.image
|| infoPtr
->imagelist
.himl
)
3051 BUTTON_DrawImage(infoPtr
, hDC
, NULL
,
3052 (state
== CMDLS_DISABLED
) ? DSS_DISABLED
: DSS_NORMAL
,
3055 DrawThemeBackground(theme
, hDC
, BP_COMMANDLINKGLYPH
, state
, &img_rect
, NULL
);
3058 if (cdrf
& CDRF_NOTIFYPOSTPAINT
)
3060 nmcd
.dwDrawStage
= CDDS_POSTPAINT
;
3061 SendMessageW(parent
, WM_NOTIFY
, nmcd
.hdr
.idFrom
, (LPARAM
)&nmcd
);
3063 if (cdrf
& CDRF_SKIPPOSTPAINT
) goto cleanup
;
3069 /* The focus rect has margins of a push button rather than command link... */
3070 GetThemeMargins(theme
, hDC
, BP_PUSHBUTTON
, state
, TMT_CONTENTMARGINS
, NULL
, &margins
);
3072 rc
.left
+= margins
.cxLeftWidth
;
3073 rc
.top
+= margins
.cyTopHeight
;
3074 rc
.right
-= margins
.cxRightWidth
;
3075 rc
.bottom
-= margins
.cyBottomHeight
;
3076 DrawFocusRect(hDC
, &rc
);
3080 if (old_font
) SelectObject(hDC
, old_font
);
3083 void BUTTON_Register(void)
3087 memset(&wndClass
, 0, sizeof(wndClass
));
3088 wndClass
.style
= CS_GLOBALCLASS
| CS_DBLCLKS
| CS_VREDRAW
| CS_HREDRAW
| CS_PARENTDC
;
3089 wndClass
.lpfnWndProc
= BUTTON_WindowProc
;
3090 wndClass
.cbClsExtra
= 0;
3091 wndClass
.cbWndExtra
= sizeof(BUTTON_INFO
*);
3092 wndClass
.hCursor
= LoadCursorW(0, (LPWSTR
)IDC_ARROW
);
3093 wndClass
.hbrBackground
= NULL
;
3094 wndClass
.lpszClassName
= WC_BUTTONW
;
3095 RegisterClassW(&wndClass
);