mfplat: Read queue subscriber within the critical section.
[wine/zf.git] / dlls / comctl32 / button.c
blobb5a9ee7f7f601e1ff851838eef985a82570b0912
1 /*
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
21 * TODO
22 * Styles
23 * - BS_NOTIFY: is it complete?
24 * - BS_RIGHTBUTTON: same as BS_LEFTTEXT
26 * Messages
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.
30 * - WM_SYSKEYUP
32 * Notifications
33 * - BCN_HOTITEMCHANGE
34 * - BN_DISABLE
35 * - BN_PUSHED/BN_HILITE
36 * + BN_KILLFOCUS: is it OK?
37 * - BN_PAINT
38 * + BN_SETFOCUS: is it OK?
39 * - BN_UNPUSHED/BN_UNHILITE
41 * Structures/Macros/Definitions
42 * - NMBCHOTITEM
45 #include <stdarg.h>
46 #include <string.h>
47 #include <stdlib.h>
49 #define OEMRESOURCE
51 #include "windef.h"
52 #include "winbase.h"
53 #include "wingdi.h"
54 #include "winuser.h"
55 #include "uxtheme.h"
56 #include "vssym32.h"
57 #include "wine/debug.h"
58 #include "wine/heap.h"
60 #include "comctl32.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)), \
75 (LPARAM)(hWnd)); \
76 } while(0)
78 typedef struct _BUTTON_INFO
80 HWND hwnd;
81 HWND parent;
82 LONG style;
83 LONG state;
84 HFONT font;
85 WCHAR *note;
86 INT note_length;
87 DWORD image_type; /* IMAGE_BITMAP or IMAGE_ICON */
88 BUTTON_IMAGELIST imagelist;
89 UINT split_style;
90 HIMAGELIST glyph; /* this is a font character code when split_style doesn't have BCSS_IMAGE */
91 SIZE glyph_size;
92 RECT text_margin;
93 HANDLE image; /* Original handle set with BM_SETIMAGE and returned with BM_GETIMAGE. */
94 union
96 HICON icon;
97 HBITMAP bitmap;
98 HANDLE image; /* Duplicated handle used for drawing. */
99 } u;
100 } BUTTON_INFO;
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 */
138 enum draw_state
140 STATE_NORMAL,
141 STATE_DISABLED,
142 STATE_HOT,
143 STATE_PRESSED,
144 STATE_DEFAULTED,
145 DRAW_STATE_COUNT
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) );
259 if (buffer)
260 GetWindowTextW( infoPtr->hwnd, buffer, len + 1 );
261 return buffer;
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 */
270 int w, h;
271 if (!ImageList_GetIconSize(infoPtr->glyph, &w, &h)) w = 0;
272 return w + GetSystemMetrics(SM_CXEDGE) * 2;
275 /* The glyph size relies on the default menu font's cell height */
276 return GetSystemMetrics(SM_CYMENUCHECK);
279 static BOOL is_themed_paint_supported(HTHEME theme, UINT btn_type)
281 if (!theme || !btnThemedPaintFunc[btn_type])
282 return FALSE;
284 if (btn_type == BS_COMMANDLINK || btn_type == BS_DEFCOMMANDLINK)
286 if (!IsThemePartDefined(theme, BP_COMMANDLINK, 0))
287 return FALSE;
290 return TRUE;
293 static void init_custom_draw(NMCUSTOMDRAW *nmcd, const BUTTON_INFO *infoPtr, HDC hdc, const RECT *rc)
295 nmcd->hdr.hwndFrom = infoPtr->hwnd;
296 nmcd->hdr.idFrom = GetWindowLongPtrW(infoPtr->hwnd, GWLP_ID);
297 nmcd->hdr.code = NM_CUSTOMDRAW;
298 nmcd->hdc = hdc;
299 nmcd->rc = *rc;
300 nmcd->dwDrawStage = CDDS_PREERASE;
301 nmcd->dwItemSpec = 0;
302 nmcd->lItemlParam = 0;
303 nmcd->uItemState = IsWindowEnabled(infoPtr->hwnd) ? 0 : CDIS_DISABLED;
304 if (infoPtr->state & BST_PUSHED) nmcd->uItemState |= CDIS_SELECTED;
305 if (infoPtr->state & BST_FOCUS) nmcd->uItemState |= CDIS_FOCUS;
306 if (infoPtr->state & BST_HOT) nmcd->uItemState |= CDIS_HOT;
307 if (infoPtr->state & BST_INDETERMINATE)
308 nmcd->uItemState |= CDIS_INDETERMINATE;
310 /* Windows doesn't seem to send CDIS_CHECKED (it fails the tests) */
311 /* CDIS_SHOWKEYBOARDCUES is misleading, as the meaning is reversed */
312 /* FIXME: Handle it properly when we support keyboard cues? */
315 HRGN set_control_clipping( HDC hdc, const RECT *rect )
317 RECT rc = *rect;
318 HRGN hrgn = CreateRectRgn( 0, 0, 0, 0 );
320 if (GetClipRgn( hdc, hrgn ) != 1)
322 DeleteObject( hrgn );
323 hrgn = 0;
325 DPtoLP( hdc, (POINT *)&rc, 2 );
326 if (GetLayout( hdc ) & LAYOUT_RTL) /* compensate for the shifting done by IntersectClipRect */
328 rc.left++;
329 rc.right++;
331 IntersectClipRect( hdc, rc.left, rc.top, rc.right, rc.bottom );
332 return hrgn;
335 static WCHAR *heap_strndupW(const WCHAR *src, size_t length)
337 size_t size = (length + 1) * sizeof(WCHAR);
338 WCHAR *dst = heap_alloc(size);
339 if (dst) memcpy(dst, src, size);
340 return dst;
343 /**********************************************************************
344 * Convert button styles to flags used by DrawText.
346 static UINT BUTTON_BStoDT( DWORD style, DWORD ex_style )
348 UINT dtStyle = DT_NOCLIP; /* We use SelectClipRgn to limit output */
350 /* "Convert" pushlike buttons to pushbuttons */
351 if (style & BS_PUSHLIKE)
352 style &= ~BS_TYPEMASK;
354 if (!(style & BS_MULTILINE))
355 dtStyle |= DT_SINGLELINE;
356 else
357 dtStyle |= DT_WORDBREAK;
359 switch (style & BS_CENTER)
361 case BS_LEFT: /* DT_LEFT is 0 */ break;
362 case BS_RIGHT: dtStyle |= DT_RIGHT; break;
363 case BS_CENTER: dtStyle |= DT_CENTER; break;
364 default:
365 if (button_centers_text(style)) dtStyle |= DT_CENTER;
368 if (ex_style & WS_EX_RIGHT) dtStyle = DT_RIGHT | (dtStyle & ~(DT_LEFT | DT_CENTER));
370 /* DrawText ignores vertical alignment for multiline text,
371 * but we use these flags to align label manually.
373 if (get_button_type(style) != BS_GROUPBOX)
375 switch (style & BS_VCENTER)
377 case BS_TOP: /* DT_TOP is 0 */ break;
378 case BS_BOTTOM: dtStyle |= DT_BOTTOM; break;
379 case BS_VCENTER: /* fall through */
380 default: dtStyle |= DT_VCENTER; break;
384 return dtStyle;
387 static int get_draw_state(const BUTTON_INFO *infoPtr)
389 static const int pb_states[DRAW_STATE_COUNT] = { PBS_NORMAL, PBS_DISABLED, PBS_HOT, PBS_PRESSED, PBS_DEFAULTED };
390 static const int cb_states[3][DRAW_STATE_COUNT] =
392 { CBS_UNCHECKEDNORMAL, CBS_UNCHECKEDDISABLED, CBS_UNCHECKEDHOT, CBS_UNCHECKEDPRESSED, CBS_UNCHECKEDNORMAL },
393 { CBS_CHECKEDNORMAL, CBS_CHECKEDDISABLED, CBS_CHECKEDHOT, CBS_CHECKEDPRESSED, CBS_CHECKEDNORMAL },
394 { CBS_MIXEDNORMAL, CBS_MIXEDDISABLED, CBS_MIXEDHOT, CBS_MIXEDPRESSED, CBS_MIXEDNORMAL }
396 static const int rb_states[2][DRAW_STATE_COUNT] =
398 { RBS_UNCHECKEDNORMAL, RBS_UNCHECKEDDISABLED, RBS_UNCHECKEDHOT, RBS_UNCHECKEDPRESSED, RBS_UNCHECKEDNORMAL },
399 { RBS_CHECKEDNORMAL, RBS_CHECKEDDISABLED, RBS_CHECKEDHOT, RBS_CHECKEDPRESSED, RBS_CHECKEDNORMAL }
401 static const int gb_states[DRAW_STATE_COUNT] = { GBS_NORMAL, GBS_DISABLED, GBS_NORMAL, GBS_NORMAL, GBS_NORMAL };
402 LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
403 UINT type = get_button_type(style);
404 int check_state = infoPtr->state & 3;
405 enum draw_state state;
407 if (!IsWindowEnabled(infoPtr->hwnd))
408 state = STATE_DISABLED;
409 else if (infoPtr->state & BST_PUSHED)
410 state = STATE_PRESSED;
411 else if (infoPtr->state & BST_HOT)
412 state = STATE_HOT;
413 else if (infoPtr->state & BST_FOCUS)
414 state = STATE_DEFAULTED;
415 else
416 state = STATE_NORMAL;
418 switch (type)
420 case BS_PUSHBUTTON:
421 case BS_DEFPUSHBUTTON:
422 case BS_USERBUTTON:
423 case BS_SPLITBUTTON:
424 case BS_DEFSPLITBUTTON:
425 case BS_COMMANDLINK:
426 case BS_DEFCOMMANDLINK:
427 return pb_states[state];
428 case BS_CHECKBOX:
429 case BS_AUTOCHECKBOX:
430 return cb_states[check_state][state];
431 case BS_RADIOBUTTON:
432 case BS_3STATE:
433 case BS_AUTO3STATE:
434 case BS_AUTORADIOBUTTON:
435 return rb_states[check_state][state];
436 case BS_GROUPBOX:
437 return gb_states[state];
438 default:
439 WARN("Unsupported button type 0x%08x\n", type);
440 return PBS_NORMAL;
444 static LRESULT CALLBACK BUTTON_WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
446 BUTTON_INFO *infoPtr = (BUTTON_INFO *)GetWindowLongPtrW(hWnd, 0);
447 RECT rect;
448 POINT pt;
449 LONG style = GetWindowLongW( hWnd, GWL_STYLE );
450 UINT btn_type = get_button_type( style );
451 LONG state, new_state;
452 HANDLE oldHbitmap;
453 HTHEME theme;
455 if (!IsWindow( hWnd )) return 0;
457 if (!infoPtr && (uMsg != WM_NCCREATE))
458 return DefWindowProcW(hWnd, uMsg, wParam, lParam);
460 pt.x = (short)LOWORD(lParam);
461 pt.y = (short)HIWORD(lParam);
463 switch (uMsg)
465 case WM_GETDLGCODE:
466 switch(btn_type)
468 case BS_COMMANDLINK:
469 case BS_USERBUTTON:
470 case BS_PUSHBUTTON: return DLGC_BUTTON | DLGC_UNDEFPUSHBUTTON;
471 case BS_DEFCOMMANDLINK:
472 case BS_DEFPUSHBUTTON: return DLGC_BUTTON | DLGC_DEFPUSHBUTTON;
473 case BS_RADIOBUTTON:
474 case BS_AUTORADIOBUTTON: return DLGC_BUTTON | DLGC_RADIOBUTTON;
475 case BS_GROUPBOX: return DLGC_STATIC;
476 case BS_SPLITBUTTON: return DLGC_BUTTON | DLGC_UNDEFPUSHBUTTON | DLGC_WANTARROWS;
477 case BS_DEFSPLITBUTTON: return DLGC_BUTTON | DLGC_DEFPUSHBUTTON | DLGC_WANTARROWS;
478 default: return DLGC_BUTTON;
481 case WM_ENABLE:
482 theme = GetWindowTheme( hWnd );
483 if (theme)
484 RedrawWindow( hWnd, NULL, NULL, RDW_FRAME | RDW_INVALIDATE | RDW_UPDATENOW );
485 else
486 paint_button( infoPtr, btn_type, ODA_DRAWENTIRE );
487 break;
489 case WM_NCCREATE:
491 CREATESTRUCTW *cs = (CREATESTRUCTW *)lParam;
493 infoPtr = heap_alloc_zero( sizeof(*infoPtr) );
494 SetWindowLongPtrW( hWnd, 0, (LONG_PTR)infoPtr );
495 infoPtr->hwnd = hWnd;
496 infoPtr->parent = cs->hwndParent;
497 infoPtr->style = cs->style;
498 infoPtr->split_style = BCSS_STRETCH;
499 infoPtr->glyph = (HIMAGELIST)0x36; /* Marlett down arrow char code */
500 infoPtr->glyph_size.cx = get_default_glyph_size(infoPtr);
501 return DefWindowProcW(hWnd, uMsg, wParam, lParam);
504 case WM_NCDESTROY:
505 SetWindowLongPtrW( hWnd, 0, 0 );
506 if (infoPtr->image_type == IMAGE_BITMAP)
507 DeleteObject(infoPtr->u.bitmap);
508 else if (infoPtr->image_type == IMAGE_ICON)
509 DestroyIcon(infoPtr->u.icon);
510 heap_free(infoPtr->note);
511 heap_free(infoPtr);
512 break;
514 case WM_CREATE:
515 if (btn_type >= MAX_BTN_TYPE)
516 return -1; /* abort */
518 /* XP turns a BS_USERBUTTON into BS_PUSHBUTTON */
519 if (btn_type == BS_USERBUTTON )
521 style = (style & ~BS_TYPEMASK) | BS_PUSHBUTTON;
522 SetWindowLongW( hWnd, GWL_STYLE, style );
524 infoPtr->state = BST_UNCHECKED;
525 OpenThemeData( hWnd, WC_BUTTONW );
526 return 0;
528 case WM_DESTROY:
529 theme = GetWindowTheme( hWnd );
530 CloseThemeData( theme );
531 break;
533 case WM_THEMECHANGED:
534 theme = GetWindowTheme( hWnd );
535 CloseThemeData( theme );
536 OpenThemeData( hWnd, WC_BUTTONW );
537 break;
539 case WM_ERASEBKGND:
540 if (btn_type == BS_OWNERDRAW)
542 HDC hdc = (HDC)wParam;
543 RECT rc;
544 HBRUSH hBrush;
545 HWND parent = GetParent(hWnd);
546 if (!parent) parent = hWnd;
547 hBrush = (HBRUSH)SendMessageW(parent, WM_CTLCOLORBTN, (WPARAM)hdc, (LPARAM)hWnd);
548 if (!hBrush) /* did the app forget to call defwindowproc ? */
549 hBrush = (HBRUSH)DefWindowProcW(parent, WM_CTLCOLORBTN,
550 (WPARAM)hdc, (LPARAM)hWnd);
551 GetClientRect(hWnd, &rc);
552 FillRect(hdc, &rc, hBrush);
554 return 1;
556 case WM_PRINTCLIENT:
557 case WM_PAINT:
559 PAINTSTRUCT ps;
560 HDC hdc;
562 theme = GetWindowTheme( hWnd );
563 hdc = wParam ? (HDC)wParam : BeginPaint( hWnd, &ps );
565 if (is_themed_paint_supported(theme, btn_type))
567 int drawState = get_draw_state(infoPtr);
568 UINT dtflags = BUTTON_BStoDT(style, GetWindowLongW(hWnd, GWL_EXSTYLE));
570 btnThemedPaintFunc[btn_type](theme, infoPtr, hdc, drawState, dtflags, infoPtr->state & BST_FOCUS);
572 else if (btnPaintFunc[btn_type])
574 int nOldMode = SetBkMode( hdc, OPAQUE );
575 btnPaintFunc[btn_type]( infoPtr, hdc, ODA_DRAWENTIRE );
576 SetBkMode(hdc, nOldMode); /* reset painting mode */
579 if ( !wParam ) EndPaint( hWnd, &ps );
580 break;
583 case WM_KEYDOWN:
584 if (wParam == VK_SPACE)
586 SendMessageW( hWnd, BM_SETSTATE, TRUE, 0 );
587 infoPtr->state |= BUTTON_BTNPRESSED;
588 SetCapture( hWnd );
590 else if (wParam == VK_UP || wParam == VK_DOWN)
592 /* Up and down arrows work on every button, and even with BCSS_NOSPLIT */
593 notify_split_button_dropdown(infoPtr, NULL, hWnd);
595 break;
597 case WM_LBUTTONDBLCLK:
598 if(style & BS_NOTIFY ||
599 btn_type == BS_RADIOBUTTON ||
600 btn_type == BS_USERBUTTON ||
601 btn_type == BS_OWNERDRAW)
603 BUTTON_NOTIFY_PARENT(hWnd, BN_DOUBLECLICKED);
604 break;
606 /* fall through */
607 case WM_LBUTTONDOWN:
608 SetFocus( hWnd );
610 if ((btn_type == BS_SPLITBUTTON || btn_type == BS_DEFSPLITBUTTON) &&
611 !(infoPtr->split_style & BCSS_NOSPLIT) &&
612 notify_split_button_dropdown(infoPtr, &pt, hWnd))
613 break;
615 SetCapture( hWnd );
616 infoPtr->state |= BUTTON_BTNPRESSED;
617 SendMessageW( hWnd, BM_SETSTATE, TRUE, 0 );
618 break;
620 case WM_KEYUP:
621 if (wParam != VK_SPACE)
622 break;
623 /* fall through */
624 case WM_LBUTTONUP:
625 state = infoPtr->state;
626 if (state & BST_DROPDOWNPUSHED)
627 SendMessageW(hWnd, BCM_SETDROPDOWNSTATE, FALSE, 0);
628 if (!(state & BUTTON_BTNPRESSED)) break;
629 infoPtr->state &= BUTTON_NSTATES | BST_HOT;
630 if (!(state & BST_PUSHED))
632 ReleaseCapture();
633 break;
635 SendMessageW( hWnd, BM_SETSTATE, FALSE, 0 );
636 GetClientRect( hWnd, &rect );
637 if (uMsg == WM_KEYUP || PtInRect( &rect, pt ))
639 switch(btn_type)
641 case BS_AUTOCHECKBOX:
642 SendMessageW( hWnd, BM_SETCHECK, !(infoPtr->state & BST_CHECKED), 0 );
643 break;
644 case BS_AUTORADIOBUTTON:
645 SendMessageW( hWnd, BM_SETCHECK, TRUE, 0 );
646 break;
647 case BS_AUTO3STATE:
648 SendMessageW( hWnd, BM_SETCHECK, (infoPtr->state & BST_INDETERMINATE) ? 0 :
649 ((infoPtr->state & 3) + 1), 0 );
650 break;
652 ReleaseCapture();
653 BUTTON_NOTIFY_PARENT(hWnd, BN_CLICKED);
655 else
657 ReleaseCapture();
659 break;
661 case WM_CAPTURECHANGED:
662 TRACE("WM_CAPTURECHANGED %p\n", hWnd);
663 if (hWnd == (HWND)lParam) break;
664 if (infoPtr->state & BUTTON_BTNPRESSED)
666 infoPtr->state &= BUTTON_NSTATES;
667 if (infoPtr->state & BST_PUSHED)
668 SendMessageW( hWnd, BM_SETSTATE, FALSE, 0 );
670 break;
672 case WM_MOUSEMOVE:
674 TRACKMOUSEEVENT mouse_event;
676 mouse_event.cbSize = sizeof(TRACKMOUSEEVENT);
677 mouse_event.dwFlags = TME_QUERY;
678 if (!TrackMouseEvent(&mouse_event) || !(mouse_event.dwFlags & (TME_HOVER | TME_LEAVE)))
680 mouse_event.dwFlags = TME_HOVER | TME_LEAVE;
681 mouse_event.hwndTrack = hWnd;
682 mouse_event.dwHoverTime = 1;
683 TrackMouseEvent(&mouse_event);
686 if ((wParam & MK_LBUTTON) && GetCapture() == hWnd)
688 GetClientRect( hWnd, &rect );
689 SendMessageW( hWnd, BM_SETSTATE, PtInRect(&rect, pt), 0 );
691 break;
694 case WM_MOUSEHOVER:
696 infoPtr->state |= BST_HOT;
697 InvalidateRect( hWnd, NULL, FALSE );
698 break;
701 case WM_MOUSELEAVE:
703 infoPtr->state &= ~BST_HOT;
704 InvalidateRect( hWnd, NULL, FALSE );
705 break;
708 case WM_SETTEXT:
710 /* Clear an old text here as Windows does */
711 if (IsWindowVisible(hWnd))
713 HDC hdc = GetDC(hWnd);
714 HBRUSH hbrush;
715 RECT client, rc;
716 HWND parent = GetParent(hWnd);
717 UINT message = (btn_type == BS_PUSHBUTTON ||
718 btn_type == BS_DEFPUSHBUTTON ||
719 btn_type == BS_USERBUTTON ||
720 btn_type == BS_OWNERDRAW) ?
721 WM_CTLCOLORBTN : WM_CTLCOLORSTATIC;
723 if (!parent) parent = hWnd;
724 hbrush = (HBRUSH)SendMessageW(parent, message,
725 (WPARAM)hdc, (LPARAM)hWnd);
726 if (!hbrush) /* did the app forget to call DefWindowProc ? */
727 hbrush = (HBRUSH)DefWindowProcW(parent, message,
728 (WPARAM)hdc, (LPARAM)hWnd);
730 GetClientRect(hWnd, &client);
731 rc = client;
732 /* FIXME: check other BS_* handlers */
733 if (btn_type == BS_GROUPBOX)
734 InflateRect(&rc, -7, 1); /* GB_Paint does this */
735 BUTTON_CalcLayoutRects(infoPtr, hdc, &rc, NULL, NULL);
736 /* Clip by client rect bounds */
737 if (rc.right > client.right) rc.right = client.right;
738 if (rc.bottom > client.bottom) rc.bottom = client.bottom;
739 FillRect(hdc, &rc, hbrush);
740 ReleaseDC(hWnd, hdc);
743 DefWindowProcW( hWnd, WM_SETTEXT, wParam, lParam );
744 if (btn_type == BS_GROUPBOX) /* Yes, only for BS_GROUPBOX */
745 InvalidateRect( hWnd, NULL, TRUE );
746 else if (GetWindowTheme( hWnd ))
747 RedrawWindow( hWnd, NULL, NULL, RDW_INVALIDATE | RDW_UPDATENOW );
748 else
749 paint_button( infoPtr, btn_type, ODA_DRAWENTIRE );
750 return 1; /* success. FIXME: check text length */
753 case BCM_SETNOTE:
755 WCHAR *note = (WCHAR *)lParam;
756 if (btn_type != BS_COMMANDLINK && btn_type != BS_DEFCOMMANDLINK)
758 SetLastError(ERROR_NOT_SUPPORTED);
759 return FALSE;
762 heap_free(infoPtr->note);
763 if (note)
765 infoPtr->note_length = lstrlenW(note);
766 infoPtr->note = heap_strndupW(note, infoPtr->note_length);
769 if (!note || !infoPtr->note)
771 infoPtr->note_length = 0;
772 infoPtr->note = heap_alloc_zero(sizeof(WCHAR));
775 SetLastError(NO_ERROR);
776 return TRUE;
779 case BCM_GETNOTE:
781 DWORD *size = (DWORD *)wParam;
782 WCHAR *buffer = (WCHAR *)lParam;
783 INT length = 0;
785 if (btn_type != BS_COMMANDLINK && btn_type != BS_DEFCOMMANDLINK)
787 SetLastError(ERROR_NOT_SUPPORTED);
788 return FALSE;
791 if (!buffer || !size || !infoPtr->note)
793 SetLastError(ERROR_INVALID_PARAMETER);
794 return FALSE;
797 if (*size > 0)
799 length = min(*size - 1, infoPtr->note_length);
800 memcpy(buffer, infoPtr->note, length * sizeof(WCHAR));
801 buffer[length] = '\0';
804 if (*size < infoPtr->note_length + 1)
806 *size = infoPtr->note_length + 1;
807 SetLastError(ERROR_INSUFFICIENT_BUFFER);
808 return FALSE;
810 else
812 SetLastError(NO_ERROR);
813 return TRUE;
817 case BCM_GETNOTELENGTH:
819 if (btn_type != BS_COMMANDLINK && btn_type != BS_DEFCOMMANDLINK)
821 SetLastError(ERROR_NOT_SUPPORTED);
822 return 0;
825 return infoPtr->note_length;
828 case WM_SETFONT:
829 infoPtr->font = (HFONT)wParam;
830 if (lParam) InvalidateRect(hWnd, NULL, TRUE);
831 break;
833 case WM_GETFONT:
834 return (LRESULT)infoPtr->font;
836 case WM_SETFOCUS:
837 TRACE("WM_SETFOCUS %p\n",hWnd);
838 infoPtr->state |= BST_FOCUS;
840 if (btn_type == BS_OWNERDRAW)
841 paint_button( infoPtr, btn_type, ODA_FOCUS );
842 else
843 InvalidateRect(hWnd, NULL, FALSE);
845 if (style & BS_NOTIFY)
846 BUTTON_NOTIFY_PARENT(hWnd, BN_SETFOCUS);
847 break;
849 case WM_KILLFOCUS:
850 TRACE("WM_KILLFOCUS %p\n",hWnd);
851 infoPtr->state &= ~BST_FOCUS;
853 if ((infoPtr->state & BUTTON_BTNPRESSED) && GetCapture() == hWnd)
854 ReleaseCapture();
855 if (style & BS_NOTIFY)
856 BUTTON_NOTIFY_PARENT(hWnd, BN_KILLFOCUS);
858 InvalidateRect( hWnd, NULL, FALSE );
859 break;
861 case WM_SYSCOLORCHANGE:
862 InvalidateRect( hWnd, NULL, FALSE );
863 break;
865 case BM_SETSTYLE:
867 DWORD new_btn_type;
869 new_btn_type= wParam & BS_TYPEMASK;
870 if (btn_type >= BS_SPLITBUTTON && new_btn_type <= BS_DEFPUSHBUTTON)
871 new_btn_type = (btn_type & ~BS_DEFPUSHBUTTON) | new_btn_type;
873 style = (style & ~BS_TYPEMASK) | new_btn_type;
874 SetWindowLongW( hWnd, GWL_STYLE, style );
876 /* Only redraw if lParam flag is set.*/
877 if (lParam)
878 InvalidateRect( hWnd, NULL, TRUE );
880 break;
882 case BM_CLICK:
883 SendMessageW( hWnd, WM_LBUTTONDOWN, 0, 0 );
884 SendMessageW( hWnd, WM_LBUTTONUP, 0, 0 );
885 break;
887 case BM_SETIMAGE:
888 infoPtr->image_type = (DWORD)wParam;
889 oldHbitmap = infoPtr->image;
890 infoPtr->u.image = CopyImage((HANDLE)lParam, infoPtr->image_type, 0, 0, 0);
891 infoPtr->image = (HANDLE)lParam;
892 InvalidateRect( hWnd, NULL, FALSE );
893 return (LRESULT)oldHbitmap;
895 case BM_GETIMAGE:
896 return (LRESULT)infoPtr->image;
898 case BCM_SETIMAGELIST:
900 BUTTON_IMAGELIST *imagelist = (BUTTON_IMAGELIST *)lParam;
902 if (!imagelist) return FALSE;
904 infoPtr->imagelist = *imagelist;
905 return TRUE;
908 case BCM_GETIMAGELIST:
910 BUTTON_IMAGELIST *imagelist = (BUTTON_IMAGELIST *)lParam;
912 if (!imagelist) return FALSE;
914 *imagelist = infoPtr->imagelist;
915 return TRUE;
918 case BCM_SETSPLITINFO:
920 BUTTON_SPLITINFO *info = (BUTTON_SPLITINFO*)lParam;
922 if (!info) return TRUE;
924 if (info->mask & (BCSIF_GLYPH | BCSIF_IMAGE))
926 infoPtr->split_style &= ~BCSS_IMAGE;
927 if (!(info->mask & BCSIF_GLYPH))
928 infoPtr->split_style |= BCSS_IMAGE;
929 infoPtr->glyph = info->himlGlyph;
930 infoPtr->glyph_size.cx = infoPtr->glyph_size.cy = 0;
933 if (info->mask & BCSIF_STYLE)
934 infoPtr->split_style = info->uSplitStyle;
935 if (info->mask & BCSIF_SIZE)
936 infoPtr->glyph_size = info->size;
938 /* Calculate fitting value for cx if invalid (cy is untouched) */
939 if (infoPtr->glyph_size.cx <= 0)
940 infoPtr->glyph_size.cx = get_default_glyph_size(infoPtr);
942 /* Windows doesn't invalidate or redraw it, so we don't, either */
943 return TRUE;
946 case BCM_GETSPLITINFO:
948 BUTTON_SPLITINFO *info = (BUTTON_SPLITINFO*)lParam;
950 if (!info) return FALSE;
952 if (info->mask & BCSIF_STYLE)
953 info->uSplitStyle = infoPtr->split_style;
954 if (info->mask & (BCSIF_GLYPH | BCSIF_IMAGE))
955 info->himlGlyph = infoPtr->glyph;
956 if (info->mask & BCSIF_SIZE)
957 info->size = infoPtr->glyph_size;
959 return TRUE;
962 case BM_GETCHECK:
963 return infoPtr->state & 3;
965 case BM_SETCHECK:
966 if (wParam > maxCheckState[btn_type]) wParam = maxCheckState[btn_type];
967 if ((btn_type == BS_RADIOBUTTON) || (btn_type == BS_AUTORADIOBUTTON))
969 style = wParam ? style | WS_TABSTOP : style & ~WS_TABSTOP;
970 SetWindowLongW( hWnd, GWL_STYLE, style );
972 if ((infoPtr->state & 3) != wParam)
974 infoPtr->state = (infoPtr->state & ~3) | wParam;
975 InvalidateRect( hWnd, NULL, FALSE );
977 if ((btn_type == BS_AUTORADIOBUTTON) && (wParam == BST_CHECKED) && (style & WS_CHILD))
978 BUTTON_CheckAutoRadioButton( hWnd );
979 break;
981 case BM_GETSTATE:
982 return infoPtr->state;
984 case BM_SETSTATE:
985 state = infoPtr->state;
986 new_state = wParam ? BST_PUSHED : 0;
988 if ((state ^ new_state) & BST_PUSHED)
990 if (wParam)
991 state |= BST_PUSHED;
992 else
993 state &= ~BST_PUSHED;
995 if (btn_type == BS_USERBUTTON)
996 BUTTON_NOTIFY_PARENT( hWnd, (state & BST_PUSHED) ? BN_HILITE : BN_UNHILITE );
997 infoPtr->state = state;
999 InvalidateRect( hWnd, NULL, FALSE );
1001 break;
1003 case BCM_SETDROPDOWNSTATE:
1004 new_state = wParam ? BST_DROPDOWNPUSHED : 0;
1006 if ((infoPtr->state ^ new_state) & BST_DROPDOWNPUSHED)
1008 infoPtr->state &= ~BST_DROPDOWNPUSHED;
1009 infoPtr->state |= new_state;
1010 InvalidateRect(hWnd, NULL, FALSE);
1012 break;
1014 case BCM_SETTEXTMARGIN:
1016 RECT *text_margin = (RECT *)lParam;
1018 if (!text_margin) return FALSE;
1020 infoPtr->text_margin = *text_margin;
1021 return TRUE;
1024 case BCM_GETTEXTMARGIN:
1026 RECT *text_margin = (RECT *)lParam;
1028 if (!text_margin) return FALSE;
1030 *text_margin = infoPtr->text_margin;
1031 return TRUE;
1034 case BCM_GETIDEALSIZE:
1036 SIZE *size = (SIZE *)lParam;
1038 if (!size) return FALSE;
1040 return btnGetIdealSizeFunc[btn_type](infoPtr, size);
1043 case WM_NCHITTEST:
1044 if(btn_type == BS_GROUPBOX) return HTTRANSPARENT;
1045 /* fall through */
1046 default:
1047 return DefWindowProcW(hWnd, uMsg, wParam, lParam);
1049 return 0;
1052 /* If maxWidth is zero, rectangle width is unlimited */
1053 static RECT BUTTON_GetTextRect(const BUTTON_INFO *infoPtr, HDC hdc, const WCHAR *text, LONG maxWidth)
1055 LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1056 LONG exStyle = GetWindowLongW(infoPtr->hwnd, GWL_EXSTYLE);
1057 UINT dtStyle = BUTTON_BStoDT(style, exStyle);
1058 HFONT hPrevFont;
1059 RECT rect = {0};
1061 rect.right = maxWidth;
1062 hPrevFont = SelectObject(hdc, infoPtr->font);
1063 /* Calculate height without DT_VCENTER and DT_BOTTOM to get the correct height */
1064 DrawTextW(hdc, text, -1, &rect, (dtStyle & ~(DT_VCENTER | DT_BOTTOM)) | DT_CALCRECT);
1065 if (hPrevFont) SelectObject(hdc, hPrevFont);
1067 return rect;
1070 static BOOL show_image_only(const BUTTON_INFO *infoPtr)
1072 LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1073 return (style & (BS_ICON | BS_BITMAP)) && (infoPtr->u.image || infoPtr->imagelist.himl);
1076 static BOOL show_image_and_text(const BUTTON_INFO *infoPtr)
1078 LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1079 UINT type = get_button_type(style);
1080 return !(style & (BS_ICON | BS_BITMAP))
1081 && ((infoPtr->u.image
1082 && (type == BS_PUSHBUTTON || type == BS_DEFPUSHBUTTON || type == BS_USERBUTTON || type == BS_SPLITBUTTON
1083 || type == BS_DEFSPLITBUTTON || type == BS_COMMANDLINK || type == BS_DEFCOMMANDLINK))
1084 || (infoPtr->imagelist.himl && type != BS_GROUPBOX));
1087 static BOOL show_image(const BUTTON_INFO *infoPtr)
1089 return show_image_only(infoPtr) || show_image_and_text(infoPtr);
1092 /* Get a bounding rectangle that is large enough to contain a image and a text side by side.
1093 * Note: (left,top) of the result rectangle may not be (0,0), offset it by yourself if needed */
1094 static RECT BUTTON_GetBoundingLabelRect(LONG style, const RECT *textRect, const RECT *imageRect)
1096 RECT labelRect;
1097 RECT rect = *imageRect;
1098 INT textWidth = textRect->right - textRect->left;
1099 INT textHeight = textRect->bottom - textRect->top;
1100 INT imageWidth = imageRect->right - imageRect->left;
1101 INT imageHeight = imageRect->bottom - imageRect->top;
1103 if ((style & BS_CENTER) == BS_RIGHT)
1104 OffsetRect(&rect, textWidth, 0);
1105 else if ((style & BS_CENTER) == BS_LEFT)
1106 OffsetRect(&rect, -imageWidth, 0);
1107 else if ((style & BS_VCENTER) == BS_BOTTOM)
1108 OffsetRect(&rect, 0, textHeight);
1109 else if ((style & BS_VCENTER) == BS_TOP)
1110 OffsetRect(&rect, 0, -imageHeight);
1111 else
1112 OffsetRect(&rect, -imageWidth, 0);
1114 UnionRect(&labelRect, textRect, &rect);
1115 return labelRect;
1118 /* Position a rectangle inside a bounding rectangle according to button alignment flags */
1119 static void BUTTON_PositionRect(LONG style, const RECT *outerRect, RECT *innerRect, const RECT *margin)
1121 INT width = innerRect->right - innerRect->left;
1122 INT height = innerRect->bottom - innerRect->top;
1124 if ((style & WS_EX_RIGHT) && !(style & BS_CENTER)) style |= BS_CENTER;
1126 if (!(style & BS_CENTER))
1128 if (button_centers_text(style))
1129 style |= BS_CENTER;
1130 else
1131 style |= BS_LEFT;
1134 if (!(style & BS_VCENTER))
1136 /* Group box's text is top aligned by default */
1137 if (get_button_type(style) == BS_GROUPBOX)
1138 style |= BS_TOP;
1141 switch (style & BS_CENTER)
1143 case BS_CENTER:
1144 innerRect->left = outerRect->left + (outerRect->right - outerRect->left - width) / 2;
1145 innerRect->right = innerRect->left + width;
1146 break;
1147 case BS_RIGHT:
1148 innerRect->right = outerRect->right - margin->right;
1149 innerRect->left = innerRect->right - width;
1150 break;
1151 case BS_LEFT:
1152 default:
1153 innerRect->left = outerRect->left + margin->left;
1154 innerRect->right = innerRect->left + width;
1155 break;
1158 switch (style & BS_VCENTER)
1160 case BS_TOP:
1161 innerRect->top = outerRect->top + margin->top;
1162 innerRect->bottom = innerRect->top + height;
1163 break;
1164 case BS_BOTTOM:
1165 innerRect->bottom = outerRect->bottom - margin->bottom;
1166 innerRect->top = innerRect->bottom - height;
1167 break;
1168 case BS_VCENTER:
1169 default:
1170 innerRect->top = outerRect->top + (outerRect->bottom - outerRect->top - height) / 2;
1171 innerRect->bottom = innerRect->top + height;
1172 break;
1176 /* Convert imagelist align style to button align style */
1177 static UINT BUTTON_ILStoBS(UINT align)
1179 switch (align)
1181 case BUTTON_IMAGELIST_ALIGN_TOP:
1182 return BS_CENTER | BS_TOP;
1183 case BUTTON_IMAGELIST_ALIGN_BOTTOM:
1184 return BS_CENTER | BS_BOTTOM;
1185 case BUTTON_IMAGELIST_ALIGN_CENTER:
1186 return BS_CENTER | BS_VCENTER;
1187 case BUTTON_IMAGELIST_ALIGN_RIGHT:
1188 return BS_RIGHT | BS_VCENTER;
1189 case BUTTON_IMAGELIST_ALIGN_LEFT:
1190 default:
1191 return BS_LEFT | BS_VCENTER;
1195 static SIZE BUTTON_GetImageSize(const BUTTON_INFO *infoPtr)
1197 ICONINFO iconInfo;
1198 BITMAP bm = {0};
1199 SIZE size = {0};
1201 /* ImageList has priority over image */
1202 if (infoPtr->imagelist.himl)
1203 ImageList_GetIconSize(infoPtr->imagelist.himl, &size.cx, &size.cy);
1204 else if (infoPtr->u.image)
1206 if (infoPtr->image_type == IMAGE_ICON)
1208 GetIconInfo(infoPtr->u.icon, &iconInfo);
1209 GetObjectW(iconInfo.hbmColor, sizeof(bm), &bm);
1210 DeleteObject(iconInfo.hbmColor);
1211 DeleteObject(iconInfo.hbmMask);
1213 else if (infoPtr->image_type == IMAGE_BITMAP)
1214 GetObjectW(infoPtr->u.bitmap, sizeof(bm), &bm);
1216 size.cx = bm.bmWidth;
1217 size.cy = bm.bmHeight;
1220 return size;
1223 static const RECT *BUTTON_GetTextMargin(const BUTTON_INFO *infoPtr)
1225 static const RECT oneMargin = {1, 1, 1, 1};
1227 /* Use text margin only when showing both image and text, and image is not imagelist */
1228 if (show_image_and_text(infoPtr) && !infoPtr->imagelist.himl)
1229 return &infoPtr->text_margin;
1230 else
1231 return &oneMargin;
1234 static void BUTTON_GetClientRectSize(BUTTON_INFO *infoPtr, SIZE *size)
1236 RECT rect;
1237 GetClientRect(infoPtr->hwnd, &rect);
1238 size->cx = rect.right - rect.left;
1239 size->cy = rect.bottom - rect.top;
1242 static void BUTTON_GetTextIdealSize(BUTTON_INFO *infoPtr, LONG maxWidth, SIZE *size)
1244 WCHAR *text = get_button_text(infoPtr);
1245 HDC hdc;
1246 RECT rect;
1247 const RECT *margin = BUTTON_GetTextMargin(infoPtr);
1249 if (maxWidth != 0)
1251 maxWidth -= margin->right + margin->right;
1252 if (maxWidth <= 0) maxWidth = 1;
1255 hdc = GetDC(infoPtr->hwnd);
1256 rect = BUTTON_GetTextRect(infoPtr, hdc, text, maxWidth);
1257 ReleaseDC(infoPtr->hwnd, hdc);
1258 heap_free(text);
1260 size->cx = rect.right - rect.left + margin->left + margin->right;
1261 size->cy = rect.bottom - rect.top + margin->top + margin->bottom;
1264 static void BUTTON_GetLabelIdealSize(BUTTON_INFO *infoPtr, LONG maxWidth, SIZE *size)
1266 LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1267 SIZE imageSize;
1268 SIZE textSize;
1269 BOOL horizontal;
1271 imageSize = BUTTON_GetImageSize(infoPtr);
1272 if (infoPtr->imagelist.himl)
1274 imageSize.cx += infoPtr->imagelist.margin.left + infoPtr->imagelist.margin.right;
1275 imageSize.cy += infoPtr->imagelist.margin.top + infoPtr->imagelist.margin.bottom;
1276 if (infoPtr->imagelist.uAlign == BUTTON_IMAGELIST_ALIGN_TOP
1277 || infoPtr->imagelist.uAlign == BUTTON_IMAGELIST_ALIGN_BOTTOM)
1278 horizontal = FALSE;
1279 else
1280 horizontal = TRUE;
1282 else
1284 /* horizontal alignment flags has priority over vertical ones if both are specified */
1285 if (!(style & (BS_CENTER | BS_VCENTER)) || ((style & BS_CENTER) && (style & BS_CENTER) != BS_CENTER)
1286 || !(style & BS_VCENTER) || (style & BS_VCENTER) == BS_VCENTER)
1287 horizontal = TRUE;
1288 else
1289 horizontal = FALSE;
1292 if (horizontal)
1294 if (maxWidth != 0)
1296 maxWidth -= imageSize.cx;
1297 if (maxWidth <= 0) maxWidth = 1;
1299 BUTTON_GetTextIdealSize(infoPtr, maxWidth, &textSize);
1300 size->cx = textSize.cx + imageSize.cx;
1301 size->cy = max(textSize.cy, imageSize.cy);
1303 else
1305 BUTTON_GetTextIdealSize(infoPtr, maxWidth, &textSize);
1306 size->cx = max(textSize.cx, imageSize.cx);
1307 size->cy = textSize.cy + imageSize.cy;
1311 static BOOL GB_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size)
1313 BUTTON_GetClientRectSize(infoPtr, size);
1314 return TRUE;
1317 static BOOL CB_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size)
1319 LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1320 HDC hdc;
1321 HFONT hfont;
1322 SIZE labelSize;
1323 INT textOffset;
1324 double scaleX;
1325 double scaleY;
1326 LONG checkboxWidth, checkboxHeight;
1327 LONG maxWidth = 0;
1329 if (SendMessageW(infoPtr->hwnd, WM_GETTEXTLENGTH, 0, 0) == 0)
1331 BUTTON_GetClientRectSize(infoPtr, size);
1332 return TRUE;
1335 hdc = GetDC(infoPtr->hwnd);
1336 scaleX = GetDeviceCaps(hdc, LOGPIXELSX) / 96.0;
1337 scaleY = GetDeviceCaps(hdc, LOGPIXELSY) / 96.0;
1338 if ((hfont = infoPtr->font)) SelectObject(hdc, hfont);
1339 GetCharWidthW(hdc, '0', '0', &textOffset);
1340 textOffset /= 2;
1341 ReleaseDC(infoPtr->hwnd, hdc);
1343 checkboxWidth = 12 * scaleX + 1;
1344 checkboxHeight = 12 * scaleY + 1;
1345 if (size->cx)
1347 maxWidth = size->cx - checkboxWidth - textOffset;
1348 if (maxWidth <= 0) maxWidth = 1;
1351 /* Checkbox doesn't support both image(but not image list) and text */
1352 if (!(style & (BS_ICON | BS_BITMAP)) && infoPtr->u.image)
1353 BUTTON_GetTextIdealSize(infoPtr, maxWidth, &labelSize);
1354 else
1355 BUTTON_GetLabelIdealSize(infoPtr, maxWidth, &labelSize);
1357 size->cx = labelSize.cx + checkboxWidth + textOffset;
1358 size->cy = max(labelSize.cy, checkboxHeight);
1360 return TRUE;
1363 static BOOL PB_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size)
1365 SIZE labelSize;
1367 if (SendMessageW(infoPtr->hwnd, WM_GETTEXTLENGTH, 0, 0) == 0)
1368 BUTTON_GetClientRectSize(infoPtr, size);
1369 else
1371 /* Ideal size include text size even if image only flags(BS_ICON, BS_BITMAP) are specified */
1372 BUTTON_GetLabelIdealSize(infoPtr, size->cx, &labelSize);
1374 size->cx = labelSize.cx;
1375 size->cy = labelSize.cy;
1377 return TRUE;
1380 static BOOL SB_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size)
1382 LONG extra_width = infoPtr->glyph_size.cx * 2 + GetSystemMetrics(SM_CXEDGE);
1383 SIZE label_size;
1385 if (SendMessageW(infoPtr->hwnd, WM_GETTEXTLENGTH, 0, 0) == 0)
1387 BUTTON_GetClientRectSize(infoPtr, size);
1388 size->cx = max(size->cx, extra_width);
1390 else
1392 BUTTON_GetLabelIdealSize(infoPtr, size->cx, &label_size);
1393 size->cx = label_size.cx + ((size->cx == 0) ? extra_width : 0);
1394 size->cy = label_size.cy;
1396 return TRUE;
1399 static BOOL CL_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size)
1401 HTHEME theme = GetWindowTheme(infoPtr->hwnd);
1402 HDC hdc = GetDC(infoPtr->hwnd);
1403 LONG w, text_w = 0, text_h = 0;
1404 UINT flags = DT_TOP | DT_LEFT;
1405 HFONT font, old_font = NULL;
1406 RECT text_bound = { 0 };
1407 SIZE img_size;
1408 RECT margin;
1409 WCHAR *text;
1411 /* Get the image size */
1412 if (infoPtr->u.image || infoPtr->imagelist.himl)
1413 img_size = BUTTON_GetImageSize(infoPtr);
1414 else
1416 if (theme)
1417 GetThemePartSize(theme, NULL, BP_COMMANDLINKGLYPH, CMDLS_NORMAL, NULL, TS_DRAW, &img_size);
1418 else
1419 img_size.cx = img_size.cy = command_link_defglyph_size;
1422 /* Get the content margins */
1423 if (theme)
1425 RECT r = { 0, 0, 0xffff, 0xffff };
1426 GetThemeBackgroundContentRect(theme, hdc, BP_COMMANDLINK, CMDLS_NORMAL, &r, &margin);
1427 margin.left -= r.left;
1428 margin.top -= r.top;
1429 margin.right = r.right - margin.right;
1430 margin.bottom = r.bottom - margin.bottom;
1432 else
1434 margin.left = margin.right = command_link_margin;
1435 margin.top = margin.bottom = command_link_margin;
1438 /* Account for the border margins and the margin between image and text */
1439 w = margin.left + margin.right + (img_size.cx ? (img_size.cx + command_link_margin) : 0);
1441 /* If a rectangle with a specific width was requested, bound the text to it */
1442 if (size->cx > w)
1444 text_bound.right = size->cx - w;
1445 flags |= DT_WORDBREAK;
1448 if (theme)
1450 if (infoPtr->font) old_font = SelectObject(hdc, infoPtr->font);
1452 /* Find the text's rect */
1453 if ((text = get_button_text(infoPtr)))
1455 RECT r;
1456 GetThemeTextExtent(theme, hdc, BP_COMMANDLINK, CMDLS_NORMAL,
1457 text, -1, flags, &text_bound, &r);
1458 heap_free(text);
1459 text_w = r.right - r.left;
1460 text_h = r.bottom - r.top;
1463 /* Find the note's rect */
1464 if (infoPtr->note)
1466 DTTOPTS opts;
1468 opts.dwSize = sizeof(opts);
1469 opts.dwFlags = DTT_FONTPROP | DTT_CALCRECT;
1470 opts.iFontPropId = TMT_BODYFONT;
1471 DrawThemeTextEx(theme, hdc, BP_COMMANDLINK, CMDLS_NORMAL,
1472 infoPtr->note, infoPtr->note_length,
1473 flags | DT_NOPREFIX | DT_CALCRECT, &text_bound, &opts);
1474 text_w = max(text_w, text_bound.right - text_bound.left);
1475 text_h += text_bound.bottom - text_bound.top;
1478 else
1480 NONCLIENTMETRICSW ncm;
1482 ncm.cbSize = sizeof(ncm);
1483 if (SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, sizeof(ncm), &ncm, 0))
1485 LONG note_weight = ncm.lfMessageFont.lfWeight;
1487 /* Find the text's rect */
1488 ncm.lfMessageFont.lfWeight = FW_BOLD;
1489 if ((font = CreateFontIndirectW(&ncm.lfMessageFont)))
1491 if ((text = get_button_text(infoPtr)))
1493 RECT r = text_bound;
1494 old_font = SelectObject(hdc, font);
1495 DrawTextW(hdc, text, -1, &r, flags | DT_CALCRECT);
1496 heap_free(text);
1498 text_w = r.right - r.left;
1499 text_h = r.bottom - r.top;
1501 DeleteObject(font);
1504 /* Find the note's rect */
1505 ncm.lfMessageFont.lfWeight = note_weight;
1506 if (infoPtr->note && (font = CreateFontIndirectW(&ncm.lfMessageFont)))
1508 HFONT tmp = SelectObject(hdc, font);
1509 if (!old_font) old_font = tmp;
1511 DrawTextW(hdc, infoPtr->note, infoPtr->note_length, &text_bound,
1512 flags | DT_NOPREFIX | DT_CALCRECT);
1513 DeleteObject(font);
1515 text_w = max(text_w, text_bound.right - text_bound.left);
1516 text_h += text_bound.bottom - text_bound.top + 2;
1520 w += text_w;
1522 size->cx = min(size->cx, w);
1523 size->cy = max(text_h, img_size.cy) + margin.top + margin.bottom;
1525 if (old_font) SelectObject(hdc, old_font);
1526 ReleaseDC(infoPtr->hwnd, hdc);
1527 return TRUE;
1530 /**********************************************************************
1531 * BUTTON_CalcLayoutRects
1533 * Calculates the rectangles of the button label(image and text) and its parts depending on a button's style.
1535 * Returns flags to be passed to DrawText.
1536 * Calculated rectangle doesn't take into account button state
1537 * (pushed, etc.). If there is nothing to draw (no text/image) output
1538 * rectangle is empty, and return value is (UINT)-1.
1540 * PARAMS:
1541 * infoPtr [I] Button pointer
1542 * hdc [I] Handle to device context to draw to
1543 * labelRc [I/O] Input the rect the label to be positioned in, and output the label rect
1544 * imageRc [O] Optional, output the image rect
1545 * textRc [O] Optional, output the text rect
1547 static UINT BUTTON_CalcLayoutRects(const BUTTON_INFO *infoPtr, HDC hdc, RECT *labelRc, RECT *imageRc, RECT *textRc)
1549 LONG style = GetWindowLongW( infoPtr->hwnd, GWL_STYLE );
1550 LONG ex_style = GetWindowLongW( infoPtr->hwnd, GWL_EXSTYLE );
1551 LONG split_style = infoPtr->imagelist.himl ? BUTTON_ILStoBS(infoPtr->imagelist.uAlign) : style;
1552 WCHAR *text = get_button_text(infoPtr);
1553 SIZE imageSize = BUTTON_GetImageSize(infoPtr);
1554 UINT dtStyle = BUTTON_BStoDT(style, ex_style);
1555 RECT labelRect, imageRect, imageRectWithMargin, textRect;
1556 LONG imageMarginWidth, imageMarginHeight;
1557 const RECT *textMargin = BUTTON_GetTextMargin(infoPtr);
1558 RECT emptyMargin = {0};
1559 LONG maxTextWidth;
1561 /* Calculate label rectangle according to label type */
1562 if ((imageSize.cx == 0 && imageSize.cy == 0) && (text == NULL || text[0] == '\0'))
1564 SetRectEmpty(labelRc);
1565 SetRectEmpty(imageRc);
1566 SetRectEmpty(textRc);
1567 heap_free(text);
1568 return (UINT)-1;
1571 SetRect(&imageRect, 0, 0, imageSize.cx, imageSize.cy);
1572 imageRectWithMargin = imageRect;
1573 if (infoPtr->imagelist.himl)
1575 imageRectWithMargin.top -= infoPtr->imagelist.margin.top;
1576 imageRectWithMargin.bottom += infoPtr->imagelist.margin.bottom;
1577 imageRectWithMargin.left -= infoPtr->imagelist.margin.left;
1578 imageRectWithMargin.right += infoPtr->imagelist.margin.right;
1581 /* Show image only */
1582 if (show_image_only(infoPtr))
1584 BUTTON_PositionRect(style, labelRc, &imageRect,
1585 infoPtr->imagelist.himl ? &infoPtr->imagelist.margin : &emptyMargin);
1586 labelRect = imageRect;
1587 SetRectEmpty(&textRect);
1589 else
1591 /* Get text rect */
1592 maxTextWidth = labelRc->right - labelRc->left;
1593 textRect = BUTTON_GetTextRect(infoPtr, hdc, text, maxTextWidth);
1595 /* Show image and text */
1596 if (show_image_and_text(infoPtr))
1598 RECT boundingLabelRect, boundingImageRect, boundingTextRect;
1600 /* Get label rect */
1601 /* Image list may have different alignment than the button, use the whole rect for label in this case */
1602 if (infoPtr->imagelist.himl)
1603 labelRect = *labelRc;
1604 else
1606 /* Get a label bounding rectangle to position the label in the user specified label rectangle because
1607 * text and image need to align together. */
1608 boundingLabelRect = BUTTON_GetBoundingLabelRect(split_style, &textRect, &imageRectWithMargin);
1609 BUTTON_PositionRect(split_style, labelRc, &boundingLabelRect, &emptyMargin);
1610 labelRect = boundingLabelRect;
1613 /* When imagelist has center align, use the whole rect for imagelist and text */
1614 if(infoPtr->imagelist.himl && infoPtr->imagelist.uAlign == BUTTON_IMAGELIST_ALIGN_CENTER)
1616 boundingImageRect = labelRect;
1617 boundingTextRect = labelRect;
1618 BUTTON_PositionRect(split_style, &boundingImageRect, &imageRect,
1619 infoPtr->imagelist.himl ? &infoPtr->imagelist.margin : &emptyMargin);
1620 /* Text doesn't use imagelist align */
1621 BUTTON_PositionRect(style, &boundingTextRect, &textRect, textMargin);
1623 else
1625 /* Get image rect */
1626 /* Split the label rect to two halves as two bounding rectangles for image and text */
1627 boundingImageRect = labelRect;
1628 imageMarginWidth = imageRectWithMargin.right - imageRectWithMargin.left;
1629 imageMarginHeight = imageRectWithMargin.bottom - imageRectWithMargin.top;
1630 if ((split_style & BS_CENTER) == BS_RIGHT)
1631 boundingImageRect.left = boundingImageRect.right - imageMarginWidth;
1632 else if ((split_style & BS_CENTER) == BS_LEFT)
1633 boundingImageRect.right = boundingImageRect.left + imageMarginWidth;
1634 else if ((split_style & BS_VCENTER) == BS_BOTTOM)
1635 boundingImageRect.top = boundingImageRect.bottom - imageMarginHeight;
1636 else if ((split_style & BS_VCENTER) == BS_TOP)
1637 boundingImageRect.bottom = boundingImageRect.top + imageMarginHeight;
1638 else
1639 boundingImageRect.right = boundingImageRect.left + imageMarginWidth;
1640 BUTTON_PositionRect(split_style, &boundingImageRect, &imageRect,
1641 infoPtr->imagelist.himl ? &infoPtr->imagelist.margin : &emptyMargin);
1643 /* Get text rect */
1644 SubtractRect(&boundingTextRect, &labelRect, &boundingImageRect);
1645 /* Text doesn't use imagelist align */
1646 BUTTON_PositionRect(style, &boundingTextRect, &textRect, textMargin);
1649 /* Show text only */
1650 else
1652 if (get_button_type(style) != BS_GROUPBOX)
1653 BUTTON_PositionRect(style, labelRc, &textRect, textMargin);
1654 else
1655 /* GroupBox is always top aligned */
1656 BUTTON_PositionRect((style & ~BS_VCENTER) | BS_TOP, labelRc, &textRect, textMargin);
1657 labelRect = textRect;
1658 SetRectEmpty(&imageRect);
1661 heap_free(text);
1663 CopyRect(labelRc, &labelRect);
1664 CopyRect(imageRc, &imageRect);
1665 CopyRect(textRc, &textRect);
1667 return dtStyle;
1671 /**********************************************************************
1672 * BUTTON_DrawImage
1674 * Draw the button's image into the specified rectangle.
1676 static void BUTTON_DrawImage(const BUTTON_INFO *infoPtr, HDC hdc, HBRUSH hbr, UINT flags, const RECT *rect)
1678 if (infoPtr->imagelist.himl)
1680 int i = (ImageList_GetImageCount(infoPtr->imagelist.himl) == 1) ? 0 : get_draw_state(infoPtr) - 1;
1682 ImageList_Draw(infoPtr->imagelist.himl, i, hdc, rect->left, rect->top, ILD_NORMAL);
1684 else
1686 switch (infoPtr->image_type)
1688 case IMAGE_ICON:
1689 flags |= DST_ICON;
1690 break;
1691 case IMAGE_BITMAP:
1692 flags |= DST_BITMAP;
1693 break;
1694 default:
1695 return;
1698 DrawStateW(hdc, hbr, NULL, (LPARAM)infoPtr->u.image, 0, rect->left, rect->top,
1699 rect->right - rect->left, rect->bottom - rect->top, flags);
1704 /**********************************************************************
1705 * BUTTON_DrawTextCallback
1707 * Callback function used by DrawStateW function.
1709 static BOOL CALLBACK BUTTON_DrawTextCallback(HDC hdc, LPARAM lp, WPARAM wp, int cx, int cy)
1711 RECT rc;
1713 SetRect(&rc, 0, 0, cx, cy);
1714 DrawTextW(hdc, (LPCWSTR)lp, -1, &rc, (UINT)wp);
1715 return TRUE;
1718 /**********************************************************************
1719 * BUTTON_DrawLabel
1721 * Common function for drawing button label.
1723 * FIXME:
1724 * 1. When BS_SINGLELINE is specified and text contains '\t', '\n' or '\r' in the middle, they are rendered as
1725 * squares now whereas they should be ignored.
1726 * 2. When BS_MULTILINE is specified and text contains space in the middle, the space mistakenly be rendered as newline.
1728 static void BUTTON_DrawLabel(const BUTTON_INFO *infoPtr, HDC hdc, UINT dtFlags, const RECT *imageRect,
1729 const RECT *textRect)
1731 HBRUSH hbr = 0;
1732 UINT flags = IsWindowEnabled(infoPtr->hwnd) ? DSS_NORMAL : DSS_DISABLED;
1733 LONG style = GetWindowLongW( infoPtr->hwnd, GWL_STYLE );
1734 WCHAR *text;
1736 /* FIXME: To draw disabled label in Win31 look-and-feel, we probably
1737 * must use DSS_MONO flag and COLOR_GRAYTEXT brush (or maybe DSS_UNION).
1738 * I don't have Win31 on hand to verify that, so I leave it as is.
1741 if ((style & BS_PUSHLIKE) && (infoPtr->state & BST_INDETERMINATE))
1743 hbr = GetSysColorBrush(COLOR_GRAYTEXT);
1744 flags |= DSS_MONO;
1747 if (show_image(infoPtr)) BUTTON_DrawImage(infoPtr, hdc, hbr, flags, imageRect);
1748 if (show_image_only(infoPtr)) return;
1750 /* DST_COMPLEX -- is 0 */
1751 if (!(text = get_button_text(infoPtr))) return;
1752 DrawStateW(hdc, hbr, BUTTON_DrawTextCallback, (LPARAM)text, dtFlags, textRect->left, textRect->top,
1753 textRect->right - textRect->left, textRect->bottom - textRect->top, flags);
1754 heap_free(text);
1757 /**********************************************************************
1758 * Push Button Functions
1760 static void PB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action )
1762 RECT rc, labelRect, imageRect, textRect;
1763 UINT dtFlags, uState;
1764 HPEN hOldPen, hpen;
1765 HBRUSH hOldBrush;
1766 INT oldBkMode;
1767 COLORREF oldTxtColor;
1768 LRESULT cdrf;
1769 HFONT hFont;
1770 NMCUSTOMDRAW nmcd;
1771 LONG state = infoPtr->state;
1772 LONG style = GetWindowLongW( infoPtr->hwnd, GWL_STYLE );
1773 BOOL pushedState = (state & BST_PUSHED);
1774 HWND parent;
1775 HRGN hrgn;
1777 GetClientRect( infoPtr->hwnd, &rc );
1779 /* Send WM_CTLCOLOR to allow changing the font (the colors are fixed) */
1780 if ((hFont = infoPtr->font)) SelectObject( hDC, hFont );
1781 parent = GetParent(infoPtr->hwnd);
1782 if (!parent) parent = infoPtr->hwnd;
1783 SendMessageW( parent, WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)infoPtr->hwnd );
1785 hrgn = set_control_clipping( hDC, &rc );
1787 hpen = CreatePen( PS_SOLID, 1, GetSysColor(COLOR_WINDOWFRAME));
1788 hOldPen = SelectObject(hDC, hpen);
1789 hOldBrush = SelectObject(hDC,GetSysColorBrush(COLOR_BTNFACE));
1790 oldBkMode = SetBkMode(hDC, TRANSPARENT);
1792 init_custom_draw(&nmcd, infoPtr, hDC, &rc);
1794 /* Send erase notifications */
1795 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
1796 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
1798 if (get_button_type(style) == BS_DEFPUSHBUTTON)
1800 if (action != ODA_FOCUS)
1801 Rectangle(hDC, rc.left, rc.top, rc.right, rc.bottom);
1802 InflateRect( &rc, -1, -1 );
1805 /* Skip the frame drawing if only focus has changed */
1806 if (action != ODA_FOCUS)
1808 uState = DFCS_BUTTONPUSH;
1810 if (style & BS_FLAT)
1811 uState |= DFCS_MONO;
1812 else if (pushedState)
1814 if (get_button_type(style) == BS_DEFPUSHBUTTON )
1815 uState |= DFCS_FLAT;
1816 else
1817 uState |= DFCS_PUSHED;
1820 if (state & (BST_CHECKED | BST_INDETERMINATE))
1821 uState |= DFCS_CHECKED;
1823 DrawFrameControl( hDC, &rc, DFC_BUTTON, uState );
1826 if (cdrf & CDRF_NOTIFYPOSTERASE)
1828 nmcd.dwDrawStage = CDDS_POSTERASE;
1829 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
1832 /* Send paint notifications */
1833 nmcd.dwDrawStage = CDDS_PREPAINT;
1834 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
1835 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
1837 if (!(cdrf & CDRF_DOERASE) && action != ODA_FOCUS)
1839 /* draw button label */
1840 labelRect = rc;
1841 /* Shrink label rect at all sides by 2 so that the content won't touch the surrounding frame */
1842 InflateRect(&labelRect, -2, -2);
1843 dtFlags = BUTTON_CalcLayoutRects(infoPtr, hDC, &labelRect, &imageRect, &textRect);
1845 if (dtFlags != (UINT)-1L)
1847 if (pushedState) OffsetRect(&labelRect, 1, 1);
1849 oldTxtColor = SetTextColor( hDC, GetSysColor(COLOR_BTNTEXT) );
1851 BUTTON_DrawLabel(infoPtr, hDC, dtFlags, &imageRect, &textRect);
1853 SetTextColor( hDC, oldTxtColor );
1857 if (cdrf & CDRF_NOTIFYPOSTPAINT)
1859 nmcd.dwDrawStage = CDDS_POSTPAINT;
1860 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
1862 if ((cdrf & CDRF_SKIPPOSTPAINT) || dtFlags == (UINT)-1L) goto cleanup;
1864 if (action == ODA_FOCUS || (state & BST_FOCUS))
1866 InflateRect( &rc, -2, -2 );
1867 DrawFocusRect( hDC, &rc );
1870 cleanup:
1871 SelectObject( hDC, hOldPen );
1872 SelectObject( hDC, hOldBrush );
1873 SetBkMode(hDC, oldBkMode);
1874 SelectClipRgn( hDC, hrgn );
1875 if (hrgn) DeleteObject( hrgn );
1876 DeleteObject( hpen );
1879 /**********************************************************************
1880 * Check Box & Radio Button Functions
1883 static void CB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action )
1885 RECT rbox, labelRect, imageRect, textRect, client;
1886 HBRUSH hBrush;
1887 int delta, text_offset, checkBoxWidth, checkBoxHeight;
1888 UINT dtFlags;
1889 LRESULT cdrf;
1890 HFONT hFont;
1891 NMCUSTOMDRAW nmcd;
1892 LONG state = infoPtr->state;
1893 LONG style = GetWindowLongW( infoPtr->hwnd, GWL_STYLE );
1894 LONG ex_style = GetWindowLongW( infoPtr->hwnd, GWL_EXSTYLE );
1895 HWND parent;
1896 HRGN hrgn;
1898 if (style & BS_PUSHLIKE)
1900 PB_Paint( infoPtr, hDC, action );
1901 return;
1904 GetClientRect(infoPtr->hwnd, &client);
1905 rbox = labelRect = client;
1907 checkBoxWidth = 12 * GetDpiForWindow( infoPtr->hwnd ) / 96 + 1;
1908 checkBoxHeight = 12 * GetDpiForWindow( infoPtr->hwnd ) / 96 + 1;
1910 if ((hFont = infoPtr->font)) SelectObject( hDC, hFont );
1911 GetCharWidthW( hDC, '0', '0', &text_offset );
1912 text_offset /= 2;
1914 parent = GetParent(infoPtr->hwnd);
1915 if (!parent) parent = infoPtr->hwnd;
1916 hBrush = (HBRUSH)SendMessageW(parent, WM_CTLCOLORSTATIC, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
1917 if (!hBrush) /* did the app forget to call defwindowproc ? */
1918 hBrush = (HBRUSH)DefWindowProcW(parent, WM_CTLCOLORSTATIC, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
1919 hrgn = set_control_clipping( hDC, &client );
1921 if (style & BS_LEFTTEXT || ex_style & WS_EX_RIGHT)
1923 labelRect.right -= checkBoxWidth + text_offset;
1924 rbox.left = rbox.right - checkBoxWidth;
1926 else
1928 labelRect.left += checkBoxWidth + text_offset;
1929 rbox.right = checkBoxWidth;
1932 init_custom_draw(&nmcd, infoPtr, hDC, &client);
1934 /* Send erase notifications */
1935 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
1936 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
1938 /* Since WM_ERASEBKGND does nothing, first prepare background */
1939 if (action == ODA_SELECT) FillRect( hDC, &rbox, hBrush );
1940 if (action == ODA_DRAWENTIRE) FillRect( hDC, &client, hBrush );
1941 if (cdrf & CDRF_NOTIFYPOSTERASE)
1943 nmcd.dwDrawStage = CDDS_POSTERASE;
1944 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
1947 /* Draw label */
1948 client = labelRect;
1949 dtFlags = BUTTON_CalcLayoutRects(infoPtr, hDC, &labelRect, &imageRect, &textRect);
1951 /* Only adjust rbox when rtext is valid */
1952 if (dtFlags != (UINT)-1L)
1954 rbox.top = labelRect.top;
1955 rbox.bottom = labelRect.bottom;
1958 /* Send paint notifications */
1959 nmcd.dwDrawStage = CDDS_PREPAINT;
1960 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
1961 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
1963 /* Draw the check-box bitmap */
1964 if (!(cdrf & CDRF_DOERASE))
1966 if (action == ODA_DRAWENTIRE || action == ODA_SELECT)
1968 UINT flags;
1970 if ((get_button_type(style) == BS_RADIOBUTTON) ||
1971 (get_button_type(style) == BS_AUTORADIOBUTTON)) flags = DFCS_BUTTONRADIO;
1972 else if (state & BST_INDETERMINATE) flags = DFCS_BUTTON3STATE;
1973 else flags = DFCS_BUTTONCHECK;
1975 if (state & (BST_CHECKED | BST_INDETERMINATE)) flags |= DFCS_CHECKED;
1976 if (state & BST_PUSHED) flags |= DFCS_PUSHED;
1977 if (style & WS_DISABLED) flags |= DFCS_INACTIVE;
1979 /* rbox must have the correct height */
1980 delta = rbox.bottom - rbox.top - checkBoxHeight;
1982 if ((style & BS_VCENTER) == BS_TOP)
1984 if (delta > 0)
1985 rbox.bottom = rbox.top + checkBoxHeight;
1986 else
1988 rbox.top -= -delta / 2 + 1;
1989 rbox.bottom = rbox.top + checkBoxHeight;
1992 else if ((style & BS_VCENTER) == BS_BOTTOM)
1994 if (delta > 0)
1995 rbox.top = rbox.bottom - checkBoxHeight;
1996 else
1998 rbox.bottom += -delta / 2 + 1;
1999 rbox.top = rbox.bottom - checkBoxHeight;
2002 else /* Default */
2004 if (delta > 0)
2006 int ofs = delta / 2;
2007 rbox.bottom -= ofs + 1;
2008 rbox.top = rbox.bottom - checkBoxHeight;
2010 else if (delta < 0)
2012 int ofs = -delta / 2;
2013 rbox.top -= ofs + 1;
2014 rbox.bottom = rbox.top + checkBoxHeight;
2018 DrawFrameControl(hDC, &rbox, DFC_BUTTON, flags);
2021 if (dtFlags != (UINT)-1L) /* Something to draw */
2022 if (action == ODA_DRAWENTIRE) BUTTON_DrawLabel(infoPtr, hDC, dtFlags, &imageRect, &textRect);
2025 if (cdrf & CDRF_NOTIFYPOSTPAINT)
2027 nmcd.dwDrawStage = CDDS_POSTPAINT;
2028 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2030 if ((cdrf & CDRF_SKIPPOSTPAINT) || dtFlags == (UINT)-1L) goto cleanup;
2032 /* ... and focus */
2033 if (action == ODA_FOCUS || (state & BST_FOCUS))
2035 labelRect.left--;
2036 labelRect.right++;
2037 IntersectRect(&labelRect, &labelRect, &client);
2038 DrawFocusRect(hDC, &labelRect);
2041 cleanup:
2042 SelectClipRgn( hDC, hrgn );
2043 if (hrgn) DeleteObject( hrgn );
2047 /**********************************************************************
2048 * BUTTON_CheckAutoRadioButton
2050 * hwnd is checked, uncheck every other auto radio button in group
2052 static void BUTTON_CheckAutoRadioButton( HWND hwnd )
2054 HWND parent, sibling, start;
2056 parent = GetParent(hwnd);
2057 /* make sure that starting control is not disabled or invisible */
2058 start = sibling = GetNextDlgGroupItem( parent, hwnd, TRUE );
2061 if (!sibling) break;
2062 if ((hwnd != sibling) &&
2063 ((GetWindowLongW( sibling, GWL_STYLE) & BS_TYPEMASK) == BS_AUTORADIOBUTTON))
2064 SendMessageW( sibling, BM_SETCHECK, BST_UNCHECKED, 0 );
2065 sibling = GetNextDlgGroupItem( parent, sibling, FALSE );
2066 } while (sibling != start);
2070 /**********************************************************************
2071 * Group Box Functions
2074 static void GB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action )
2076 RECT labelRect, imageRect, textRect, rcFrame;
2077 HBRUSH hbr;
2078 HFONT hFont;
2079 UINT dtFlags;
2080 TEXTMETRICW tm;
2081 LONG style = GetWindowLongW( infoPtr->hwnd, GWL_STYLE );
2082 HWND parent;
2083 HRGN hrgn;
2085 if ((hFont = infoPtr->font)) SelectObject( hDC, hFont );
2086 /* GroupBox acts like static control, so it sends CTLCOLORSTATIC */
2087 parent = GetParent(infoPtr->hwnd);
2088 if (!parent) parent = infoPtr->hwnd;
2089 hbr = (HBRUSH)SendMessageW(parent, WM_CTLCOLORSTATIC, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
2090 if (!hbr) /* did the app forget to call defwindowproc ? */
2091 hbr = (HBRUSH)DefWindowProcW(parent, WM_CTLCOLORSTATIC, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
2092 GetClientRect(infoPtr->hwnd, &labelRect);
2093 rcFrame = labelRect;
2094 hrgn = set_control_clipping(hDC, &labelRect);
2096 GetTextMetricsW (hDC, &tm);
2097 rcFrame.top += (tm.tmHeight / 2) - 1;
2098 DrawEdge (hDC, &rcFrame, EDGE_ETCHED, BF_RECT | ((style & BS_FLAT) ? BF_FLAT : 0));
2100 InflateRect(&labelRect, -7, 1);
2101 dtFlags = BUTTON_CalcLayoutRects(infoPtr, hDC, &labelRect, &imageRect, &textRect);
2103 if (dtFlags != (UINT)-1)
2105 /* Because buttons have CS_PARENTDC class style, there is a chance
2106 * that label will be drawn out of client rect.
2107 * But Windows doesn't clip label's rect, so do I.
2110 /* There is 1-pixel margin at the left, right, and bottom */
2111 labelRect.left--;
2112 labelRect.right++;
2113 labelRect.bottom++;
2114 FillRect(hDC, &labelRect, hbr);
2115 labelRect.left++;
2116 labelRect.right--;
2117 labelRect.bottom--;
2119 BUTTON_DrawLabel(infoPtr, hDC, dtFlags, &imageRect, &textRect);
2121 SelectClipRgn( hDC, hrgn );
2122 if (hrgn) DeleteObject( hrgn );
2126 /**********************************************************************
2127 * User Button Functions
2130 static void UB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action )
2132 RECT rc;
2133 HBRUSH hBrush;
2134 LRESULT cdrf;
2135 HFONT hFont;
2136 NMCUSTOMDRAW nmcd;
2137 LONG state = infoPtr->state;
2138 HWND parent;
2140 GetClientRect( infoPtr->hwnd, &rc);
2142 if ((hFont = infoPtr->font)) SelectObject( hDC, hFont );
2144 parent = GetParent(infoPtr->hwnd);
2145 if (!parent) parent = infoPtr->hwnd;
2146 hBrush = (HBRUSH)SendMessageW(parent, WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
2147 if (!hBrush) /* did the app forget to call defwindowproc ? */
2148 hBrush = (HBRUSH)DefWindowProcW(parent, WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
2150 if (action == ODA_FOCUS || (state & BST_FOCUS))
2152 init_custom_draw(&nmcd, infoPtr, hDC, &rc);
2154 /* Send erase notifications */
2155 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2156 if (cdrf & CDRF_SKIPDEFAULT) goto notify;
2159 FillRect( hDC, &rc, hBrush );
2160 if (action == ODA_FOCUS || (state & BST_FOCUS))
2162 if (cdrf & CDRF_NOTIFYPOSTERASE)
2164 nmcd.dwDrawStage = CDDS_POSTERASE;
2165 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2168 /* Send paint notifications */
2169 nmcd.dwDrawStage = CDDS_PREPAINT;
2170 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2171 if (cdrf & CDRF_SKIPDEFAULT) goto notify;
2172 if (cdrf & CDRF_NOTIFYPOSTPAINT)
2174 nmcd.dwDrawStage = CDDS_POSTPAINT;
2175 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2178 if (!(cdrf & CDRF_SKIPPOSTPAINT))
2179 DrawFocusRect( hDC, &rc );
2182 notify:
2183 switch (action)
2185 case ODA_FOCUS:
2186 BUTTON_NOTIFY_PARENT( infoPtr->hwnd, (state & BST_FOCUS) ? BN_SETFOCUS : BN_KILLFOCUS );
2187 break;
2189 case ODA_SELECT:
2190 BUTTON_NOTIFY_PARENT( infoPtr->hwnd, (state & BST_PUSHED) ? BN_HILITE : BN_UNHILITE );
2191 break;
2193 default:
2194 break;
2199 /**********************************************************************
2200 * Ownerdrawn Button Functions
2203 static void OB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action )
2205 LONG state = infoPtr->state;
2206 DRAWITEMSTRUCT dis;
2207 LONG_PTR id = GetWindowLongPtrW( infoPtr->hwnd, GWLP_ID );
2208 HWND parent;
2209 HFONT hFont;
2210 HRGN hrgn;
2212 dis.CtlType = ODT_BUTTON;
2213 dis.CtlID = id;
2214 dis.itemID = 0;
2215 dis.itemAction = action;
2216 dis.itemState = ((state & BST_FOCUS) ? ODS_FOCUS : 0) |
2217 ((state & BST_PUSHED) ? ODS_SELECTED : 0) |
2218 (IsWindowEnabled(infoPtr->hwnd) ? 0: ODS_DISABLED);
2219 dis.hwndItem = infoPtr->hwnd;
2220 dis.hDC = hDC;
2221 dis.itemData = 0;
2222 GetClientRect( infoPtr->hwnd, &dis.rcItem );
2224 if ((hFont = infoPtr->font)) SelectObject( hDC, hFont );
2225 parent = GetParent(infoPtr->hwnd);
2226 if (!parent) parent = infoPtr->hwnd;
2227 SendMessageW( parent, WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)infoPtr->hwnd );
2229 hrgn = set_control_clipping( hDC, &dis.rcItem );
2231 SendMessageW( GetParent(infoPtr->hwnd), WM_DRAWITEM, id, (LPARAM)&dis );
2232 SelectClipRgn( hDC, hrgn );
2233 if (hrgn) DeleteObject( hrgn );
2237 /**********************************************************************
2238 * Split Button Functions
2240 static void SB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action )
2242 LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2243 LONG state = infoPtr->state;
2244 UINT dtFlags = (UINT)-1L;
2246 RECT rc, push_rect, dropdown_rect;
2247 NMCUSTOMDRAW nmcd;
2248 HPEN pen, old_pen;
2249 HBRUSH old_brush;
2250 INT old_bk_mode;
2251 LRESULT cdrf;
2252 HWND parent;
2253 HRGN hrgn;
2255 GetClientRect(infoPtr->hwnd, &rc);
2257 /* Send WM_CTLCOLOR to allow changing the font (the colors are fixed) */
2258 if (infoPtr->font) SelectObject(hDC, infoPtr->font);
2259 if (!(parent = GetParent(infoPtr->hwnd))) parent = infoPtr->hwnd;
2260 SendMessageW(parent, WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
2262 hrgn = set_control_clipping(hDC, &rc);
2264 pen = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_WINDOWFRAME));
2265 old_pen = SelectObject(hDC, pen);
2266 old_brush = SelectObject(hDC, GetSysColorBrush(COLOR_BTNFACE));
2267 old_bk_mode = SetBkMode(hDC, TRANSPARENT);
2269 init_custom_draw(&nmcd, infoPtr, hDC, &rc);
2271 /* Send erase notifications */
2272 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2273 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2275 if (get_button_type(style) == BS_DEFSPLITBUTTON)
2277 if (action != ODA_FOCUS)
2278 Rectangle(hDC, rc.left, rc.top, rc.right, rc.bottom);
2279 InflateRect(&rc, -1, -1);
2280 /* The split will now be off by 1 pixel, but
2281 that's exactly what Windows does as well */
2284 get_split_button_rects(infoPtr, &rc, &push_rect, &dropdown_rect);
2285 if (infoPtr->split_style & BCSS_NOSPLIT)
2286 push_rect = rc;
2288 /* Skip the frame drawing if only focus has changed */
2289 if (action != ODA_FOCUS)
2291 UINT flags = DFCS_BUTTONPUSH;
2293 if (style & BS_FLAT) flags |= DFCS_MONO;
2294 else if (state & BST_PUSHED)
2295 flags |= (get_button_type(style) == BS_DEFSPLITBUTTON)
2296 ? DFCS_FLAT : DFCS_PUSHED;
2298 if (state & (BST_CHECKED | BST_INDETERMINATE))
2299 flags |= DFCS_CHECKED;
2301 if (infoPtr->split_style & BCSS_NOSPLIT)
2302 DrawFrameControl(hDC, &push_rect, DFC_BUTTON, flags);
2303 else
2305 UINT dropdown_flags = flags & ~DFCS_CHECKED;
2307 if (state & BST_DROPDOWNPUSHED)
2308 dropdown_flags = (dropdown_flags & ~DFCS_FLAT) | DFCS_PUSHED;
2310 /* Adjust for shadow and draw order so it looks properly */
2311 if (infoPtr->split_style & BCSS_ALIGNLEFT)
2313 dropdown_rect.right++;
2314 DrawFrameControl(hDC, &dropdown_rect, DFC_BUTTON, dropdown_flags);
2315 dropdown_rect.right--;
2316 DrawFrameControl(hDC, &push_rect, DFC_BUTTON, flags);
2318 else
2320 push_rect.right++;
2321 DrawFrameControl(hDC, &push_rect, DFC_BUTTON, flags);
2322 push_rect.right--;
2323 DrawFrameControl(hDC, &dropdown_rect, DFC_BUTTON, dropdown_flags);
2328 if (cdrf & CDRF_NOTIFYPOSTERASE)
2330 nmcd.dwDrawStage = CDDS_POSTERASE;
2331 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2334 /* Send paint notifications */
2335 nmcd.dwDrawStage = CDDS_PREPAINT;
2336 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2337 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2339 /* Shrink push button rect so that the content won't touch the surrounding frame */
2340 InflateRect(&push_rect, -2, -2);
2342 if (!(cdrf & CDRF_DOERASE) && action != ODA_FOCUS)
2344 COLORREF old_color = SetTextColor(hDC, GetSysColor(COLOR_BTNTEXT));
2345 RECT label_rect = push_rect, image_rect, text_rect;
2347 dtFlags = BUTTON_CalcLayoutRects(infoPtr, hDC, &label_rect, &image_rect, &text_rect);
2349 if (dtFlags != (UINT)-1L)
2350 BUTTON_DrawLabel(infoPtr, hDC, dtFlags, &image_rect, &text_rect);
2352 draw_split_button_dropdown_glyph(infoPtr, hDC, &dropdown_rect);
2353 SetTextColor(hDC, old_color);
2356 if (cdrf & CDRF_NOTIFYPOSTPAINT)
2358 nmcd.dwDrawStage = CDDS_POSTPAINT;
2359 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2361 if ((cdrf & CDRF_SKIPPOSTPAINT) || dtFlags == (UINT)-1L) goto cleanup;
2363 if (action == ODA_FOCUS || (state & BST_FOCUS))
2364 DrawFocusRect(hDC, &push_rect);
2366 cleanup:
2367 SelectObject(hDC, old_pen);
2368 SelectObject(hDC, old_brush);
2369 SetBkMode(hDC, old_bk_mode);
2370 SelectClipRgn(hDC, hrgn);
2371 if (hrgn) DeleteObject(hrgn);
2372 DeleteObject(pen);
2375 /* Given the full button rect of the split button, retrieve the push part and the dropdown part */
2376 static inline void get_split_button_rects(const BUTTON_INFO *infoPtr, const RECT *button_rect,
2377 RECT *push_rect, RECT *dropdown_rect)
2379 *push_rect = *dropdown_rect = *button_rect;
2381 /* The dropdown takes priority if the client rect is too small, it will only have a dropdown */
2382 if (infoPtr->split_style & BCSS_ALIGNLEFT)
2384 dropdown_rect->right = min(button_rect->left + infoPtr->glyph_size.cx, button_rect->right);
2385 push_rect->left = dropdown_rect->right;
2387 else
2389 dropdown_rect->left = max(button_rect->right - infoPtr->glyph_size.cx, button_rect->left);
2390 push_rect->right = dropdown_rect->left;
2394 /* Notify the parent if the point is within the dropdown and return TRUE (always notify if NULL) */
2395 static BOOL notify_split_button_dropdown(const BUTTON_INFO *infoPtr, const POINT *pt, HWND hwnd)
2397 NMBCDROPDOWN nmbcd;
2399 GetClientRect(hwnd, &nmbcd.rcButton);
2400 if (pt)
2402 RECT push_rect, dropdown_rect;
2404 get_split_button_rects(infoPtr, &nmbcd.rcButton, &push_rect, &dropdown_rect);
2405 if (!PtInRect(&dropdown_rect, *pt))
2406 return FALSE;
2408 /* If it's already down (set manually via BCM_SETDROPDOWNSTATE), fake the notify */
2409 if (infoPtr->state & BST_DROPDOWNPUSHED)
2410 return TRUE;
2412 SendMessageW(hwnd, BCM_SETDROPDOWNSTATE, TRUE, 0);
2414 nmbcd.hdr.hwndFrom = hwnd;
2415 nmbcd.hdr.idFrom = GetWindowLongPtrW(hwnd, GWLP_ID);
2416 nmbcd.hdr.code = BCN_DROPDOWN;
2417 SendMessageW(GetParent(hwnd), WM_NOTIFY, nmbcd.hdr.idFrom, (LPARAM)&nmbcd);
2419 SendMessageW(hwnd, BCM_SETDROPDOWNSTATE, FALSE, 0);
2420 return TRUE;
2423 /* Draw the split button dropdown glyph or image */
2424 static void draw_split_button_dropdown_glyph(const BUTTON_INFO *infoPtr, HDC hdc, RECT *rect)
2426 if (infoPtr->split_style & BCSS_IMAGE)
2428 int w, h;
2430 /* When the glyph is an image list, Windows is very buggy with BCSS_STRETCH,
2431 positions it weirdly and doesn't even stretch it, but instead extends the
2432 image, leaking into other images in the list (or black if none). Instead,
2433 we'll ignore this and just position it at center as without BCSS_STRETCH. */
2434 if (!ImageList_GetIconSize(infoPtr->glyph, &w, &h)) return;
2436 ImageList_Draw(infoPtr->glyph,
2437 (ImageList_GetImageCount(infoPtr->glyph) == 1) ? 0 : get_draw_state(infoPtr) - 1,
2438 hdc, rect->left + (rect->right - rect->left - w) / 2,
2439 rect->top + (rect->bottom - rect->top - h) / 2, ILD_NORMAL);
2441 else if (infoPtr->glyph_size.cy >= 0)
2443 /* infoPtr->glyph is a character code from Marlett */
2444 HFONT font, old_font;
2445 LOGFONTW logfont = { 0, 0, 0, 0, FW_NORMAL, 0, 0, 0, SYMBOL_CHARSET, 0, 0, 0, 0,
2446 L"Marlett" };
2447 if (infoPtr->glyph_size.cy)
2449 /* BCSS_STRETCH preserves aspect ratio, uses minimum as size */
2450 if (infoPtr->split_style & BCSS_STRETCH)
2451 logfont.lfHeight = min(infoPtr->glyph_size.cx, infoPtr->glyph_size.cy);
2452 else
2454 logfont.lfWidth = infoPtr->glyph_size.cx;
2455 logfont.lfHeight = infoPtr->glyph_size.cy;
2458 else logfont.lfHeight = infoPtr->glyph_size.cx;
2460 if ((font = CreateFontIndirectW(&logfont)))
2462 old_font = SelectObject(hdc, font);
2463 DrawTextW(hdc, (const WCHAR*)&infoPtr->glyph, 1, rect,
2464 DT_CENTER | DT_VCENTER | DT_SINGLELINE | DT_NOCLIP | DT_NOPREFIX);
2465 SelectObject(hdc, old_font);
2466 DeleteObject(font);
2472 /**********************************************************************
2473 * Command Link Functions
2475 static void CL_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action )
2477 LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2478 LONG state = infoPtr->state;
2480 RECT rc, content_rect;
2481 NMCUSTOMDRAW nmcd;
2482 HPEN pen, old_pen;
2483 HBRUSH old_brush;
2484 INT old_bk_mode;
2485 LRESULT cdrf;
2486 HWND parent;
2487 HRGN hrgn;
2489 GetClientRect(infoPtr->hwnd, &rc);
2491 /* Command Links are not affected by the button's font, and are based
2492 on the default message font. Furthermore, they are not affected by
2493 any of the alignment styles (and always align with the top-left). */
2494 if (!(parent = GetParent(infoPtr->hwnd))) parent = infoPtr->hwnd;
2495 SendMessageW(parent, WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
2497 hrgn = set_control_clipping(hDC, &rc);
2499 pen = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_WINDOWFRAME));
2500 old_pen = SelectObject(hDC, pen);
2501 old_brush = SelectObject(hDC, GetSysColorBrush(COLOR_BTNFACE));
2502 old_bk_mode = SetBkMode(hDC, TRANSPARENT);
2504 init_custom_draw(&nmcd, infoPtr, hDC, &rc);
2506 /* Send erase notifications */
2507 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2508 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2509 content_rect = rc;
2511 if (get_button_type(style) == BS_DEFCOMMANDLINK)
2513 if (action != ODA_FOCUS)
2514 Rectangle(hDC, rc.left, rc.top, rc.right, rc.bottom);
2515 InflateRect(&rc, -1, -1);
2518 /* Skip the frame drawing if only focus has changed */
2519 if (action != ODA_FOCUS)
2521 if (!(state & (BST_HOT | BST_PUSHED | BST_CHECKED | BST_INDETERMINATE)))
2522 FillRect(hDC, &rc, GetSysColorBrush(COLOR_BTNFACE));
2523 else
2525 UINT flags = DFCS_BUTTONPUSH;
2527 if (style & BS_FLAT) flags |= DFCS_MONO;
2528 else if (state & BST_PUSHED) flags |= DFCS_PUSHED;
2530 if (state & (BST_CHECKED | BST_INDETERMINATE))
2531 flags |= DFCS_CHECKED;
2532 DrawFrameControl(hDC, &rc, DFC_BUTTON, flags);
2536 if (cdrf & CDRF_NOTIFYPOSTERASE)
2538 nmcd.dwDrawStage = CDDS_POSTERASE;
2539 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2542 /* Send paint notifications */
2543 nmcd.dwDrawStage = CDDS_PREPAINT;
2544 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2545 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2547 if (!(cdrf & CDRF_DOERASE) && action != ODA_FOCUS)
2549 UINT flags = IsWindowEnabled(infoPtr->hwnd) ? DSS_NORMAL : DSS_DISABLED;
2550 COLORREF old_color = SetTextColor(hDC, GetSysColor(flags == DSS_NORMAL ?
2551 COLOR_BTNTEXT : COLOR_GRAYTEXT));
2552 HIMAGELIST defimg = NULL;
2553 NONCLIENTMETRICSW ncm;
2554 UINT txt_h = 0;
2555 SIZE img_size;
2557 /* Command Links ignore the margins of the image list or its alignment */
2558 if (infoPtr->u.image || infoPtr->imagelist.himl)
2559 img_size = BUTTON_GetImageSize(infoPtr);
2560 else
2562 img_size.cx = img_size.cy = command_link_defglyph_size;
2563 defimg = ImageList_LoadImageW(COMCTL32_hModule, (LPCWSTR)MAKEINTRESOURCE(IDB_CMDLINK),
2564 img_size.cx, 3, CLR_NONE, IMAGE_BITMAP, LR_CREATEDIBSECTION);
2567 /* Shrink rect by the command link margin, except on bottom (just the frame) */
2568 InflateRect(&content_rect, -command_link_margin, -command_link_margin);
2569 content_rect.bottom += command_link_margin - 2;
2571 ncm.cbSize = sizeof(ncm);
2572 if (SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, sizeof(ncm), &ncm, 0))
2574 LONG note_weight = ncm.lfMessageFont.lfWeight;
2575 RECT r = content_rect;
2576 WCHAR *text;
2577 HFONT font;
2579 if (img_size.cx) r.left += img_size.cx + command_link_margin;
2581 /* Draw the text */
2582 ncm.lfMessageFont.lfWeight = FW_BOLD;
2583 if ((font = CreateFontIndirectW(&ncm.lfMessageFont)))
2585 if ((text = get_button_text(infoPtr)))
2587 SelectObject(hDC, font);
2588 txt_h = DrawTextW(hDC, text, -1, &r,
2589 DT_TOP | DT_LEFT | DT_WORDBREAK | DT_END_ELLIPSIS);
2590 heap_free(text);
2592 DeleteObject(font);
2595 /* Draw the note */
2596 ncm.lfMessageFont.lfWeight = note_weight;
2597 if (infoPtr->note && (font = CreateFontIndirectW(&ncm.lfMessageFont)))
2599 r.top += txt_h + 2;
2600 SelectObject(hDC, font);
2601 DrawTextW(hDC, infoPtr->note, infoPtr->note_length, &r,
2602 DT_TOP | DT_LEFT | DT_WORDBREAK | DT_NOPREFIX);
2603 DeleteObject(font);
2607 /* Position the image at the vertical center of the drawn text (not note) */
2608 txt_h = min(txt_h, content_rect.bottom - content_rect.top);
2609 if (img_size.cy < txt_h) content_rect.top += (txt_h - img_size.cy) / 2;
2611 content_rect.right = content_rect.left + img_size.cx;
2612 content_rect.bottom = content_rect.top + img_size.cy;
2614 if (defimg)
2616 int i = 0;
2617 if (flags == DSS_DISABLED) i = 2;
2618 else if (state & BST_HOT) i = 1;
2620 ImageList_Draw(defimg, i, hDC, content_rect.left, content_rect.top, ILD_NORMAL);
2621 ImageList_Destroy(defimg);
2623 else
2624 BUTTON_DrawImage(infoPtr, hDC, NULL, flags, &content_rect);
2626 SetTextColor(hDC, old_color);
2629 if (cdrf & CDRF_NOTIFYPOSTPAINT)
2631 nmcd.dwDrawStage = CDDS_POSTPAINT;
2632 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2634 if (cdrf & CDRF_SKIPPOSTPAINT) goto cleanup;
2636 if (action == ODA_FOCUS || (state & BST_FOCUS))
2638 InflateRect(&rc, -2, -2);
2639 DrawFocusRect(hDC, &rc);
2642 cleanup:
2643 SelectObject(hDC, old_pen);
2644 SelectObject(hDC, old_brush);
2645 SetBkMode(hDC, old_bk_mode);
2646 SelectClipRgn(hDC, hrgn);
2647 if (hrgn) DeleteObject(hrgn);
2648 DeleteObject(pen);
2652 /**********************************************************************
2653 * Themed Paint Functions
2655 static void PB_ThemedPaint(HTHEME theme, const BUTTON_INFO *infoPtr, HDC hDC, int state, UINT dtFlags, BOOL focused)
2657 RECT bgRect, textRect;
2658 HFONT font = infoPtr->font;
2659 HFONT hPrevFont = font ? SelectObject(hDC, font) : NULL;
2660 NMCUSTOMDRAW nmcd;
2661 LRESULT cdrf;
2662 HWND parent;
2663 WCHAR *text;
2665 GetClientRect(infoPtr->hwnd, &bgRect);
2666 GetThemeBackgroundContentRect(theme, hDC, BP_PUSHBUTTON, state, &bgRect, &textRect);
2667 init_custom_draw(&nmcd, infoPtr, hDC, &bgRect);
2669 parent = GetParent(infoPtr->hwnd);
2670 if (!parent) parent = infoPtr->hwnd;
2672 /* Send erase notifications */
2673 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2674 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2676 if (IsThemeBackgroundPartiallyTransparent(theme, BP_PUSHBUTTON, state))
2677 DrawThemeParentBackground(infoPtr->hwnd, hDC, NULL);
2678 DrawThemeBackground(theme, hDC, BP_PUSHBUTTON, state, &bgRect, NULL);
2680 if (cdrf & CDRF_NOTIFYPOSTERASE)
2682 nmcd.dwDrawStage = CDDS_POSTERASE;
2683 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2686 /* Send paint notifications */
2687 nmcd.dwDrawStage = CDDS_PREPAINT;
2688 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2689 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2691 if (!(cdrf & CDRF_DOERASE) && (text = get_button_text(infoPtr)))
2693 DrawThemeText(theme, hDC, BP_PUSHBUTTON, state, text, lstrlenW(text), dtFlags, 0, &textRect);
2694 heap_free(text);
2697 if (cdrf & CDRF_NOTIFYPOSTPAINT)
2699 nmcd.dwDrawStage = CDDS_POSTPAINT;
2700 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2702 if (cdrf & CDRF_SKIPPOSTPAINT) goto cleanup;
2704 if (focused)
2706 MARGINS margins;
2707 RECT focusRect = bgRect;
2709 GetThemeMargins(theme, hDC, BP_PUSHBUTTON, state, TMT_CONTENTMARGINS, NULL, &margins);
2711 focusRect.left += margins.cxLeftWidth;
2712 focusRect.top += margins.cyTopHeight;
2713 focusRect.right -= margins.cxRightWidth;
2714 focusRect.bottom -= margins.cyBottomHeight;
2716 DrawFocusRect( hDC, &focusRect );
2719 cleanup:
2720 if (hPrevFont) SelectObject(hDC, hPrevFont);
2723 static void CB_ThemedPaint(HTHEME theme, const BUTTON_INFO *infoPtr, HDC hDC, int state, UINT dtFlags, BOOL focused)
2725 SIZE sz;
2726 RECT bgRect, textRect;
2727 HFONT font, hPrevFont = NULL;
2728 DWORD dwStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2729 UINT btn_type = get_button_type( dwStyle );
2730 int part = (btn_type == BS_RADIOBUTTON) || (btn_type == BS_AUTORADIOBUTTON) ? BP_RADIOBUTTON : BP_CHECKBOX;
2731 NMCUSTOMDRAW nmcd;
2732 LRESULT cdrf;
2733 LOGFONTW lf;
2734 HWND parent;
2735 WCHAR *text;
2736 BOOL created_font = FALSE;
2738 HRESULT hr = GetThemeFont(theme, hDC, part, state, TMT_FONT, &lf);
2739 if (SUCCEEDED(hr)) {
2740 font = CreateFontIndirectW(&lf);
2741 if (!font)
2742 TRACE("Failed to create font\n");
2743 else {
2744 TRACE("font = %s\n", debugstr_w(lf.lfFaceName));
2745 hPrevFont = SelectObject(hDC, font);
2746 created_font = TRUE;
2748 } else {
2749 font = (HFONT)SendMessageW(infoPtr->hwnd, WM_GETFONT, 0, 0);
2750 hPrevFont = SelectObject(hDC, font);
2753 if (FAILED(GetThemePartSize(theme, hDC, part, state, NULL, TS_DRAW, &sz)))
2754 sz.cx = sz.cy = 13;
2756 GetClientRect(infoPtr->hwnd, &bgRect);
2757 GetThemeBackgroundContentRect(theme, hDC, part, state, &bgRect, &textRect);
2758 init_custom_draw(&nmcd, infoPtr, hDC, &bgRect);
2760 if (dtFlags & DT_SINGLELINE) /* Center the checkbox / radio button to the text. */
2761 bgRect.top = bgRect.top + (textRect.bottom - textRect.top - sz.cy) / 2;
2763 /* adjust for the check/radio marker */
2764 bgRect.bottom = bgRect.top + sz.cy;
2765 bgRect.right = bgRect.left + sz.cx;
2766 textRect.left = bgRect.right + 6;
2768 parent = GetParent(infoPtr->hwnd);
2769 if (!parent) parent = infoPtr->hwnd;
2771 /* Send erase notifications */
2772 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2773 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2775 DrawThemeParentBackground(infoPtr->hwnd, hDC, NULL);
2776 DrawThemeBackground(theme, hDC, part, state, &bgRect, NULL);
2778 if (cdrf & CDRF_NOTIFYPOSTERASE)
2780 nmcd.dwDrawStage = CDDS_POSTERASE;
2781 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2784 /* Send paint notifications */
2785 nmcd.dwDrawStage = CDDS_PREPAINT;
2786 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2787 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2789 text = get_button_text(infoPtr);
2790 if (!(cdrf & CDRF_DOERASE) && text)
2791 DrawThemeText(theme, hDC, part, state, text, lstrlenW(text), dtFlags, 0, &textRect);
2793 if (cdrf & CDRF_NOTIFYPOSTPAINT)
2795 nmcd.dwDrawStage = CDDS_POSTPAINT;
2796 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2799 if (text)
2801 if (!(cdrf & CDRF_SKIPPOSTPAINT) && focused)
2803 RECT focusRect;
2805 focusRect = textRect;
2807 DrawTextW(hDC, text, lstrlenW(text), &focusRect, dtFlags | DT_CALCRECT);
2809 if (focusRect.right < textRect.right) focusRect.right++;
2810 focusRect.bottom = textRect.bottom;
2812 DrawFocusRect( hDC, &focusRect );
2815 heap_free(text);
2818 cleanup:
2819 if (created_font) DeleteObject(font);
2820 if (hPrevFont) SelectObject(hDC, hPrevFont);
2823 static void GB_ThemedPaint(HTHEME theme, const BUTTON_INFO *infoPtr, HDC hDC, int state, UINT dtFlags, BOOL focused)
2825 RECT bgRect, textRect, contentRect;
2826 WCHAR *text = get_button_text(infoPtr);
2827 LOGFONTW lf;
2828 HFONT font, hPrevFont = NULL;
2829 BOOL created_font = FALSE;
2831 HRESULT hr = GetThemeFont(theme, hDC, BP_GROUPBOX, state, TMT_FONT, &lf);
2832 if (SUCCEEDED(hr)) {
2833 font = CreateFontIndirectW(&lf);
2834 if (!font)
2835 TRACE("Failed to create font\n");
2836 else {
2837 hPrevFont = SelectObject(hDC, font);
2838 created_font = TRUE;
2840 } else {
2841 font = (HFONT)SendMessageW(infoPtr->hwnd, WM_GETFONT, 0, 0);
2842 hPrevFont = SelectObject(hDC, font);
2845 GetClientRect(infoPtr->hwnd, &bgRect);
2846 textRect = bgRect;
2848 if (text)
2850 SIZE textExtent;
2851 GetTextExtentPoint32W(hDC, text, lstrlenW(text), &textExtent);
2852 bgRect.top += (textExtent.cy / 2);
2853 textRect.left += 10;
2854 textRect.bottom = textRect.top + textExtent.cy;
2855 textRect.right = textRect.left + textExtent.cx + 4;
2857 ExcludeClipRect(hDC, textRect.left, textRect.top, textRect.right, textRect.bottom);
2860 GetThemeBackgroundContentRect(theme, hDC, BP_GROUPBOX, state, &bgRect, &contentRect);
2861 ExcludeClipRect(hDC, contentRect.left, contentRect.top, contentRect.right, contentRect.bottom);
2863 if (IsThemeBackgroundPartiallyTransparent(theme, BP_GROUPBOX, state))
2864 DrawThemeParentBackground(infoPtr->hwnd, hDC, NULL);
2865 DrawThemeBackground(theme, hDC, BP_GROUPBOX, state, &bgRect, NULL);
2867 SelectClipRgn(hDC, NULL);
2869 if (text)
2871 InflateRect(&textRect, -2, 0);
2872 DrawThemeText(theme, hDC, BP_GROUPBOX, state, text, lstrlenW(text), 0, 0, &textRect);
2873 heap_free(text);
2876 if (created_font) DeleteObject(font);
2877 if (hPrevFont) SelectObject(hDC, hPrevFont);
2880 static void SB_ThemedPaint(HTHEME theme, const BUTTON_INFO *infoPtr, HDC hDC, int state, UINT dtFlags, BOOL focused)
2882 HFONT old_font = infoPtr->font ? SelectObject(hDC, infoPtr->font) : NULL;
2883 RECT rc, content_rect, push_rect, dropdown_rect;
2884 NMCUSTOMDRAW nmcd;
2885 LRESULT cdrf;
2886 HWND parent;
2888 GetClientRect(infoPtr->hwnd, &rc);
2889 init_custom_draw(&nmcd, infoPtr, hDC, &rc);
2891 parent = GetParent(infoPtr->hwnd);
2892 if (!parent) parent = infoPtr->hwnd;
2894 /* Send erase notifications */
2895 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2896 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2898 if (IsThemeBackgroundPartiallyTransparent(theme, BP_PUSHBUTTON, state))
2899 DrawThemeParentBackground(infoPtr->hwnd, hDC, NULL);
2901 /* The zone outside the content is ignored for the dropdown (draws over) */
2902 GetThemeBackgroundContentRect(theme, hDC, BP_PUSHBUTTON, state, &rc, &content_rect);
2903 get_split_button_rects(infoPtr, &rc, &push_rect, &dropdown_rect);
2905 if (infoPtr->split_style & BCSS_NOSPLIT)
2907 push_rect = rc;
2908 DrawThemeBackground(theme, hDC, BP_PUSHBUTTON, state, &rc, NULL);
2910 else
2912 RECT r = { dropdown_rect.left, content_rect.top, dropdown_rect.right, content_rect.bottom };
2913 UINT edge = (infoPtr->split_style & BCSS_ALIGNLEFT) ? BF_RIGHT : BF_LEFT;
2914 const RECT *clip = NULL;
2916 /* If only the dropdown is pressed, we need to draw it separately */
2917 if (state != PBS_PRESSED && (infoPtr->state & BST_DROPDOWNPUSHED))
2919 DrawThemeBackground(theme, hDC, BP_PUSHBUTTON, PBS_PRESSED, &rc, &dropdown_rect);
2920 clip = &push_rect;
2922 DrawThemeBackground(theme, hDC, BP_PUSHBUTTON, state, &rc, clip);
2924 /* Draw the separator */
2925 DrawThemeEdge(theme, hDC, BP_PUSHBUTTON, state, &r, EDGE_ETCHED, edge, NULL);
2927 /* The content rect should be the content area of the push button */
2928 GetThemeBackgroundContentRect(theme, hDC, BP_PUSHBUTTON, state, &push_rect, &content_rect);
2931 if (cdrf & CDRF_NOTIFYPOSTERASE)
2933 nmcd.dwDrawStage = CDDS_POSTERASE;
2934 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2937 /* Send paint notifications */
2938 nmcd.dwDrawStage = CDDS_PREPAINT;
2939 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2940 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2942 if (!(cdrf & CDRF_DOERASE))
2944 COLORREF old_color, color;
2945 INT old_bk_mode;
2946 WCHAR *text;
2948 if ((text = get_button_text(infoPtr)))
2950 DrawThemeText(theme, hDC, BP_PUSHBUTTON, state, text, lstrlenW(text), dtFlags, 0, &content_rect);
2951 heap_free(text);
2954 GetThemeColor(theme, BP_PUSHBUTTON, state, TMT_TEXTCOLOR, &color);
2955 old_bk_mode = SetBkMode(hDC, TRANSPARENT);
2956 old_color = SetTextColor(hDC, color);
2958 draw_split_button_dropdown_glyph(infoPtr, hDC, &dropdown_rect);
2960 SetTextColor(hDC, old_color);
2961 SetBkMode(hDC, old_bk_mode);
2964 if (cdrf & CDRF_NOTIFYPOSTPAINT)
2966 nmcd.dwDrawStage = CDDS_POSTPAINT;
2967 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2969 if (cdrf & CDRF_SKIPPOSTPAINT) goto cleanup;
2971 if (focused)
2973 MARGINS margins;
2975 GetThemeMargins(theme, hDC, BP_PUSHBUTTON, state, TMT_CONTENTMARGINS, NULL, &margins);
2977 push_rect.left += margins.cxLeftWidth;
2978 push_rect.top += margins.cyTopHeight;
2979 push_rect.right -= margins.cxRightWidth;
2980 push_rect.bottom -= margins.cyBottomHeight;
2981 DrawFocusRect(hDC, &push_rect);
2984 cleanup:
2985 if (old_font) SelectObject(hDC, old_font);
2988 static void CL_ThemedPaint(HTHEME theme, const BUTTON_INFO *infoPtr, HDC hDC, int state, UINT dtFlags, BOOL focused)
2990 HFONT old_font = infoPtr->font ? SelectObject(hDC, infoPtr->font) : NULL;
2991 NMCUSTOMDRAW nmcd;
2992 LRESULT cdrf;
2993 HWND parent;
2994 RECT rc;
2996 GetClientRect(infoPtr->hwnd, &rc);
2997 init_custom_draw(&nmcd, infoPtr, hDC, &rc);
2999 parent = GetParent(infoPtr->hwnd);
3000 if (!parent) parent = infoPtr->hwnd;
3002 /* Send erase notifications */
3003 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
3004 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
3006 if (IsThemeBackgroundPartiallyTransparent(theme, BP_COMMANDLINK, state))
3007 DrawThemeParentBackground(infoPtr->hwnd, hDC, NULL);
3008 DrawThemeBackground(theme, hDC, BP_COMMANDLINK, state, &rc, NULL);
3010 if (cdrf & CDRF_NOTIFYPOSTERASE)
3012 nmcd.dwDrawStage = CDDS_POSTERASE;
3013 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
3016 /* Send paint notifications */
3017 nmcd.dwDrawStage = CDDS_PREPAINT;
3018 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
3019 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
3021 if (!(cdrf & CDRF_DOERASE))
3023 RECT r, img_rect;
3024 UINT txt_h = 0;
3025 SIZE img_size;
3026 WCHAR *text;
3028 GetThemeBackgroundContentRect(theme, hDC, BP_COMMANDLINK, state, &rc, &r);
3030 /* The text alignment and styles are fixed and don't depend on button styles */
3031 dtFlags = DT_TOP | DT_LEFT | DT_WORDBREAK;
3033 /* Command Links ignore the margins of the image list or its alignment */
3034 if (infoPtr->u.image || infoPtr->imagelist.himl)
3035 img_size = BUTTON_GetImageSize(infoPtr);
3036 else
3037 GetThemePartSize(theme, NULL, BP_COMMANDLINKGLYPH, state, NULL, TS_DRAW, &img_size);
3039 img_rect = r;
3040 if (img_size.cx) r.left += img_size.cx + command_link_margin;
3042 /* Draw the text */
3043 if ((text = get_button_text(infoPtr)))
3045 UINT len = lstrlenW(text);
3046 RECT text_rect;
3048 GetThemeTextExtent(theme, hDC, BP_COMMANDLINK, state, text, len,
3049 dtFlags | DT_END_ELLIPSIS, &r, &text_rect);
3050 DrawThemeText(theme, hDC, BP_COMMANDLINK, state, text, len,
3051 dtFlags | DT_END_ELLIPSIS, 0, &r);
3053 txt_h = text_rect.bottom - text_rect.top;
3054 heap_free(text);
3057 /* Draw the note */
3058 if (infoPtr->note)
3060 DTTOPTS opts;
3062 r.top += txt_h;
3063 opts.dwSize = sizeof(opts);
3064 opts.dwFlags = DTT_FONTPROP;
3065 opts.iFontPropId = TMT_BODYFONT;
3066 DrawThemeTextEx(theme, hDC, BP_COMMANDLINK, state,
3067 infoPtr->note, infoPtr->note_length,
3068 dtFlags | DT_NOPREFIX, &r, &opts);
3071 /* Position the image at the vertical center of the drawn text (not note) */
3072 txt_h = min(txt_h, img_rect.bottom - img_rect.top);
3073 if (img_size.cy < txt_h) img_rect.top += (txt_h - img_size.cy) / 2;
3075 img_rect.right = img_rect.left + img_size.cx;
3076 img_rect.bottom = img_rect.top + img_size.cy;
3078 if (infoPtr->u.image || infoPtr->imagelist.himl)
3079 BUTTON_DrawImage(infoPtr, hDC, NULL,
3080 (state == CMDLS_DISABLED) ? DSS_DISABLED : DSS_NORMAL,
3081 &img_rect);
3082 else
3083 DrawThemeBackground(theme, hDC, BP_COMMANDLINKGLYPH, state, &img_rect, NULL);
3086 if (cdrf & CDRF_NOTIFYPOSTPAINT)
3088 nmcd.dwDrawStage = CDDS_POSTPAINT;
3089 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
3091 if (cdrf & CDRF_SKIPPOSTPAINT) goto cleanup;
3093 if (focused)
3095 MARGINS margins;
3097 /* The focus rect has margins of a push button rather than command link... */
3098 GetThemeMargins(theme, hDC, BP_PUSHBUTTON, state, TMT_CONTENTMARGINS, NULL, &margins);
3100 rc.left += margins.cxLeftWidth;
3101 rc.top += margins.cyTopHeight;
3102 rc.right -= margins.cxRightWidth;
3103 rc.bottom -= margins.cyBottomHeight;
3104 DrawFocusRect(hDC, &rc);
3107 cleanup:
3108 if (old_font) SelectObject(hDC, old_font);
3111 void BUTTON_Register(void)
3113 WNDCLASSW wndClass;
3115 memset(&wndClass, 0, sizeof(wndClass));
3116 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS | CS_VREDRAW | CS_HREDRAW | CS_PARENTDC;
3117 wndClass.lpfnWndProc = BUTTON_WindowProc;
3118 wndClass.cbClsExtra = 0;
3119 wndClass.cbWndExtra = sizeof(BUTTON_INFO *);
3120 wndClass.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW);
3121 wndClass.hbrBackground = NULL;
3122 wndClass.lpszClassName = WC_BUTTONW;
3123 RegisterClassW(&wndClass);