widl: Generate helper macros for WinRT implementation.
[wine/zf.git] / dlls / comctl32 / button.c
blob0dcbb38d2025b256f59ffee49240cb68596b42dd
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
747 paint_button( infoPtr, btn_type, ODA_DRAWENTIRE );
748 return 1; /* success. FIXME: check text length */
751 case BCM_SETNOTE:
753 WCHAR *note = (WCHAR *)lParam;
754 if (btn_type != BS_COMMANDLINK && btn_type != BS_DEFCOMMANDLINK)
756 SetLastError(ERROR_NOT_SUPPORTED);
757 return FALSE;
760 heap_free(infoPtr->note);
761 if (note)
763 infoPtr->note_length = lstrlenW(note);
764 infoPtr->note = heap_strndupW(note, infoPtr->note_length);
767 if (!note || !infoPtr->note)
769 infoPtr->note_length = 0;
770 infoPtr->note = heap_alloc_zero(sizeof(WCHAR));
773 SetLastError(NO_ERROR);
774 return TRUE;
777 case BCM_GETNOTE:
779 DWORD *size = (DWORD *)wParam;
780 WCHAR *buffer = (WCHAR *)lParam;
781 INT length = 0;
783 if (btn_type != BS_COMMANDLINK && btn_type != BS_DEFCOMMANDLINK)
785 SetLastError(ERROR_NOT_SUPPORTED);
786 return FALSE;
789 if (!buffer || !size || !infoPtr->note)
791 SetLastError(ERROR_INVALID_PARAMETER);
792 return FALSE;
795 if (*size > 0)
797 length = min(*size - 1, infoPtr->note_length);
798 memcpy(buffer, infoPtr->note, length * sizeof(WCHAR));
799 buffer[length] = '\0';
802 if (*size < infoPtr->note_length + 1)
804 *size = infoPtr->note_length + 1;
805 SetLastError(ERROR_INSUFFICIENT_BUFFER);
806 return FALSE;
808 else
810 SetLastError(NO_ERROR);
811 return TRUE;
815 case BCM_GETNOTELENGTH:
817 if (btn_type != BS_COMMANDLINK && btn_type != BS_DEFCOMMANDLINK)
819 SetLastError(ERROR_NOT_SUPPORTED);
820 return 0;
823 return infoPtr->note_length;
826 case WM_SETFONT:
827 infoPtr->font = (HFONT)wParam;
828 if (lParam) InvalidateRect(hWnd, NULL, TRUE);
829 break;
831 case WM_GETFONT:
832 return (LRESULT)infoPtr->font;
834 case WM_SETFOCUS:
835 TRACE("WM_SETFOCUS %p\n",hWnd);
836 infoPtr->state |= BST_FOCUS;
838 if (btn_type == BS_OWNERDRAW)
839 paint_button( infoPtr, btn_type, ODA_FOCUS );
840 else
841 InvalidateRect(hWnd, NULL, FALSE);
843 if (style & BS_NOTIFY)
844 BUTTON_NOTIFY_PARENT(hWnd, BN_SETFOCUS);
845 break;
847 case WM_KILLFOCUS:
848 TRACE("WM_KILLFOCUS %p\n",hWnd);
849 infoPtr->state &= ~BST_FOCUS;
851 if ((infoPtr->state & BUTTON_BTNPRESSED) && GetCapture() == hWnd)
852 ReleaseCapture();
853 if (style & BS_NOTIFY)
854 BUTTON_NOTIFY_PARENT(hWnd, BN_KILLFOCUS);
856 InvalidateRect( hWnd, NULL, FALSE );
857 break;
859 case WM_SYSCOLORCHANGE:
860 InvalidateRect( hWnd, NULL, FALSE );
861 break;
863 case BM_SETSTYLE:
865 DWORD new_btn_type;
867 new_btn_type= wParam & BS_TYPEMASK;
868 if (btn_type >= BS_SPLITBUTTON && new_btn_type <= BS_DEFPUSHBUTTON)
869 new_btn_type = (btn_type & ~BS_DEFPUSHBUTTON) | new_btn_type;
871 style = (style & ~BS_TYPEMASK) | new_btn_type;
872 SetWindowLongW( hWnd, GWL_STYLE, style );
874 /* Only redraw if lParam flag is set.*/
875 if (lParam)
876 InvalidateRect( hWnd, NULL, TRUE );
878 break;
880 case BM_CLICK:
881 SendMessageW( hWnd, WM_LBUTTONDOWN, 0, 0 );
882 SendMessageW( hWnd, WM_LBUTTONUP, 0, 0 );
883 break;
885 case BM_SETIMAGE:
886 infoPtr->image_type = (DWORD)wParam;
887 oldHbitmap = infoPtr->image;
888 infoPtr->u.image = CopyImage((HANDLE)lParam, infoPtr->image_type, 0, 0, 0);
889 infoPtr->image = (HANDLE)lParam;
890 InvalidateRect( hWnd, NULL, FALSE );
891 return (LRESULT)oldHbitmap;
893 case BM_GETIMAGE:
894 return (LRESULT)infoPtr->image;
896 case BCM_SETIMAGELIST:
898 BUTTON_IMAGELIST *imagelist = (BUTTON_IMAGELIST *)lParam;
900 if (!imagelist) return FALSE;
902 infoPtr->imagelist = *imagelist;
903 return TRUE;
906 case BCM_GETIMAGELIST:
908 BUTTON_IMAGELIST *imagelist = (BUTTON_IMAGELIST *)lParam;
910 if (!imagelist) return FALSE;
912 *imagelist = infoPtr->imagelist;
913 return TRUE;
916 case BCM_SETSPLITINFO:
918 BUTTON_SPLITINFO *info = (BUTTON_SPLITINFO*)lParam;
920 if (!info) return TRUE;
922 if (info->mask & (BCSIF_GLYPH | BCSIF_IMAGE))
924 infoPtr->split_style &= ~BCSS_IMAGE;
925 if (!(info->mask & BCSIF_GLYPH))
926 infoPtr->split_style |= BCSS_IMAGE;
927 infoPtr->glyph = info->himlGlyph;
928 infoPtr->glyph_size.cx = infoPtr->glyph_size.cy = 0;
931 if (info->mask & BCSIF_STYLE)
932 infoPtr->split_style = info->uSplitStyle;
933 if (info->mask & BCSIF_SIZE)
934 infoPtr->glyph_size = info->size;
936 /* Calculate fitting value for cx if invalid (cy is untouched) */
937 if (infoPtr->glyph_size.cx <= 0)
938 infoPtr->glyph_size.cx = get_default_glyph_size(infoPtr);
940 /* Windows doesn't invalidate or redraw it, so we don't, either */
941 return TRUE;
944 case BCM_GETSPLITINFO:
946 BUTTON_SPLITINFO *info = (BUTTON_SPLITINFO*)lParam;
948 if (!info) return FALSE;
950 if (info->mask & BCSIF_STYLE)
951 info->uSplitStyle = infoPtr->split_style;
952 if (info->mask & (BCSIF_GLYPH | BCSIF_IMAGE))
953 info->himlGlyph = infoPtr->glyph;
954 if (info->mask & BCSIF_SIZE)
955 info->size = infoPtr->glyph_size;
957 return TRUE;
960 case BM_GETCHECK:
961 return infoPtr->state & 3;
963 case BM_SETCHECK:
964 if (wParam > maxCheckState[btn_type]) wParam = maxCheckState[btn_type];
965 if ((btn_type == BS_RADIOBUTTON) || (btn_type == BS_AUTORADIOBUTTON))
967 style = wParam ? style | WS_TABSTOP : style & ~WS_TABSTOP;
968 SetWindowLongW( hWnd, GWL_STYLE, style );
970 if ((infoPtr->state & 3) != wParam)
972 infoPtr->state = (infoPtr->state & ~3) | wParam;
973 InvalidateRect( hWnd, NULL, FALSE );
975 if ((btn_type == BS_AUTORADIOBUTTON) && (wParam == BST_CHECKED) && (style & WS_CHILD))
976 BUTTON_CheckAutoRadioButton( hWnd );
977 break;
979 case BM_GETSTATE:
980 return infoPtr->state;
982 case BM_SETSTATE:
983 state = infoPtr->state;
984 new_state = wParam ? BST_PUSHED : 0;
986 if ((state ^ new_state) & BST_PUSHED)
988 if (wParam)
989 state |= BST_PUSHED;
990 else
991 state &= ~BST_PUSHED;
993 if (btn_type == BS_USERBUTTON)
994 BUTTON_NOTIFY_PARENT( hWnd, (state & BST_PUSHED) ? BN_HILITE : BN_UNHILITE );
995 infoPtr->state = state;
997 InvalidateRect( hWnd, NULL, FALSE );
999 break;
1001 case BCM_SETDROPDOWNSTATE:
1002 new_state = wParam ? BST_DROPDOWNPUSHED : 0;
1004 if ((infoPtr->state ^ new_state) & BST_DROPDOWNPUSHED)
1006 infoPtr->state &= ~BST_DROPDOWNPUSHED;
1007 infoPtr->state |= new_state;
1008 InvalidateRect(hWnd, NULL, FALSE);
1010 break;
1012 case BCM_SETTEXTMARGIN:
1014 RECT *text_margin = (RECT *)lParam;
1016 if (!text_margin) return FALSE;
1018 infoPtr->text_margin = *text_margin;
1019 return TRUE;
1022 case BCM_GETTEXTMARGIN:
1024 RECT *text_margin = (RECT *)lParam;
1026 if (!text_margin) return FALSE;
1028 *text_margin = infoPtr->text_margin;
1029 return TRUE;
1032 case BCM_GETIDEALSIZE:
1034 SIZE *size = (SIZE *)lParam;
1036 if (!size) return FALSE;
1038 return btnGetIdealSizeFunc[btn_type](infoPtr, size);
1041 case WM_NCHITTEST:
1042 if(btn_type == BS_GROUPBOX) return HTTRANSPARENT;
1043 /* fall through */
1044 default:
1045 return DefWindowProcW(hWnd, uMsg, wParam, lParam);
1047 return 0;
1050 /* If maxWidth is zero, rectangle width is unlimited */
1051 static RECT BUTTON_GetTextRect(const BUTTON_INFO *infoPtr, HDC hdc, const WCHAR *text, LONG maxWidth)
1053 LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1054 LONG exStyle = GetWindowLongW(infoPtr->hwnd, GWL_EXSTYLE);
1055 UINT dtStyle = BUTTON_BStoDT(style, exStyle);
1056 HFONT hPrevFont;
1057 RECT rect = {0};
1059 rect.right = maxWidth;
1060 hPrevFont = SelectObject(hdc, infoPtr->font);
1061 /* Calculate height without DT_VCENTER and DT_BOTTOM to get the correct height */
1062 DrawTextW(hdc, text, -1, &rect, (dtStyle & ~(DT_VCENTER | DT_BOTTOM)) | DT_CALCRECT);
1063 if (hPrevFont) SelectObject(hdc, hPrevFont);
1065 return rect;
1068 static BOOL show_image_only(const BUTTON_INFO *infoPtr)
1070 LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1071 return (style & (BS_ICON | BS_BITMAP)) && (infoPtr->u.image || infoPtr->imagelist.himl);
1074 static BOOL show_image_and_text(const BUTTON_INFO *infoPtr)
1076 LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1077 UINT type = get_button_type(style);
1078 return !(style & (BS_ICON | BS_BITMAP))
1079 && ((infoPtr->u.image
1080 && (type == BS_PUSHBUTTON || type == BS_DEFPUSHBUTTON || type == BS_USERBUTTON || type == BS_SPLITBUTTON
1081 || type == BS_DEFSPLITBUTTON || type == BS_COMMANDLINK || type == BS_DEFCOMMANDLINK))
1082 || (infoPtr->imagelist.himl && type != BS_GROUPBOX));
1085 static BOOL show_image(const BUTTON_INFO *infoPtr)
1087 return show_image_only(infoPtr) || show_image_and_text(infoPtr);
1090 /* Get a bounding rectangle that is large enough to contain a image and a text side by side.
1091 * Note: (left,top) of the result rectangle may not be (0,0), offset it by yourself if needed */
1092 static RECT BUTTON_GetBoundingLabelRect(LONG style, const RECT *textRect, const RECT *imageRect)
1094 RECT labelRect;
1095 RECT rect = *imageRect;
1096 INT textWidth = textRect->right - textRect->left;
1097 INT textHeight = textRect->bottom - textRect->top;
1098 INT imageWidth = imageRect->right - imageRect->left;
1099 INT imageHeight = imageRect->bottom - imageRect->top;
1101 if ((style & BS_CENTER) == BS_RIGHT)
1102 OffsetRect(&rect, textWidth, 0);
1103 else if ((style & BS_CENTER) == BS_LEFT)
1104 OffsetRect(&rect, -imageWidth, 0);
1105 else if ((style & BS_VCENTER) == BS_BOTTOM)
1106 OffsetRect(&rect, 0, textHeight);
1107 else if ((style & BS_VCENTER) == BS_TOP)
1108 OffsetRect(&rect, 0, -imageHeight);
1109 else
1110 OffsetRect(&rect, -imageWidth, 0);
1112 UnionRect(&labelRect, textRect, &rect);
1113 return labelRect;
1116 /* Position a rectangle inside a bounding rectangle according to button alignment flags */
1117 static void BUTTON_PositionRect(LONG style, const RECT *outerRect, RECT *innerRect, const RECT *margin)
1119 INT width = innerRect->right - innerRect->left;
1120 INT height = innerRect->bottom - innerRect->top;
1122 if ((style & WS_EX_RIGHT) && !(style & BS_CENTER)) style |= BS_CENTER;
1124 if (!(style & BS_CENTER))
1126 if (button_centers_text(style))
1127 style |= BS_CENTER;
1128 else
1129 style |= BS_LEFT;
1132 if (!(style & BS_VCENTER))
1134 /* Group box's text is top aligned by default */
1135 if (get_button_type(style) == BS_GROUPBOX)
1136 style |= BS_TOP;
1139 switch (style & BS_CENTER)
1141 case BS_CENTER:
1142 innerRect->left = outerRect->left + (outerRect->right - outerRect->left - width) / 2;
1143 innerRect->right = innerRect->left + width;
1144 break;
1145 case BS_RIGHT:
1146 innerRect->right = outerRect->right - margin->right;
1147 innerRect->left = innerRect->right - width;
1148 break;
1149 case BS_LEFT:
1150 default:
1151 innerRect->left = outerRect->left + margin->left;
1152 innerRect->right = innerRect->left + width;
1153 break;
1156 switch (style & BS_VCENTER)
1158 case BS_TOP:
1159 innerRect->top = outerRect->top + margin->top;
1160 innerRect->bottom = innerRect->top + height;
1161 break;
1162 case BS_BOTTOM:
1163 innerRect->bottom = outerRect->bottom - margin->bottom;
1164 innerRect->top = innerRect->bottom - height;
1165 break;
1166 case BS_VCENTER:
1167 default:
1168 innerRect->top = outerRect->top + (outerRect->bottom - outerRect->top - height) / 2;
1169 innerRect->bottom = innerRect->top + height;
1170 break;
1174 /* Convert imagelist align style to button align style */
1175 static UINT BUTTON_ILStoBS(UINT align)
1177 switch (align)
1179 case BUTTON_IMAGELIST_ALIGN_TOP:
1180 return BS_CENTER | BS_TOP;
1181 case BUTTON_IMAGELIST_ALIGN_BOTTOM:
1182 return BS_CENTER | BS_BOTTOM;
1183 case BUTTON_IMAGELIST_ALIGN_CENTER:
1184 return BS_CENTER | BS_VCENTER;
1185 case BUTTON_IMAGELIST_ALIGN_RIGHT:
1186 return BS_RIGHT | BS_VCENTER;
1187 case BUTTON_IMAGELIST_ALIGN_LEFT:
1188 default:
1189 return BS_LEFT | BS_VCENTER;
1193 static SIZE BUTTON_GetImageSize(const BUTTON_INFO *infoPtr)
1195 ICONINFO iconInfo;
1196 BITMAP bm = {0};
1197 SIZE size = {0};
1199 /* ImageList has priority over image */
1200 if (infoPtr->imagelist.himl)
1201 ImageList_GetIconSize(infoPtr->imagelist.himl, &size.cx, &size.cy);
1202 else if (infoPtr->u.image)
1204 if (infoPtr->image_type == IMAGE_ICON)
1206 GetIconInfo(infoPtr->u.icon, &iconInfo);
1207 GetObjectW(iconInfo.hbmColor, sizeof(bm), &bm);
1208 DeleteObject(iconInfo.hbmColor);
1209 DeleteObject(iconInfo.hbmMask);
1211 else if (infoPtr->image_type == IMAGE_BITMAP)
1212 GetObjectW(infoPtr->u.bitmap, sizeof(bm), &bm);
1214 size.cx = bm.bmWidth;
1215 size.cy = bm.bmHeight;
1218 return size;
1221 static const RECT *BUTTON_GetTextMargin(const BUTTON_INFO *infoPtr)
1223 static const RECT oneMargin = {1, 1, 1, 1};
1225 /* Use text margin only when showing both image and text, and image is not imagelist */
1226 if (show_image_and_text(infoPtr) && !infoPtr->imagelist.himl)
1227 return &infoPtr->text_margin;
1228 else
1229 return &oneMargin;
1232 static void BUTTON_GetClientRectSize(BUTTON_INFO *infoPtr, SIZE *size)
1234 RECT rect;
1235 GetClientRect(infoPtr->hwnd, &rect);
1236 size->cx = rect.right - rect.left;
1237 size->cy = rect.bottom - rect.top;
1240 static void BUTTON_GetTextIdealSize(BUTTON_INFO *infoPtr, LONG maxWidth, SIZE *size)
1242 WCHAR *text = get_button_text(infoPtr);
1243 HDC hdc;
1244 RECT rect;
1245 const RECT *margin = BUTTON_GetTextMargin(infoPtr);
1247 if (maxWidth != 0)
1249 maxWidth -= margin->right + margin->right;
1250 if (maxWidth <= 0) maxWidth = 1;
1253 hdc = GetDC(infoPtr->hwnd);
1254 rect = BUTTON_GetTextRect(infoPtr, hdc, text, maxWidth);
1255 ReleaseDC(infoPtr->hwnd, hdc);
1256 heap_free(text);
1258 size->cx = rect.right - rect.left + margin->left + margin->right;
1259 size->cy = rect.bottom - rect.top + margin->top + margin->bottom;
1262 static void BUTTON_GetLabelIdealSize(BUTTON_INFO *infoPtr, LONG maxWidth, SIZE *size)
1264 LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1265 SIZE imageSize;
1266 SIZE textSize;
1267 BOOL horizontal;
1269 imageSize = BUTTON_GetImageSize(infoPtr);
1270 if (infoPtr->imagelist.himl)
1272 imageSize.cx += infoPtr->imagelist.margin.left + infoPtr->imagelist.margin.right;
1273 imageSize.cy += infoPtr->imagelist.margin.top + infoPtr->imagelist.margin.bottom;
1274 if (infoPtr->imagelist.uAlign == BUTTON_IMAGELIST_ALIGN_TOP
1275 || infoPtr->imagelist.uAlign == BUTTON_IMAGELIST_ALIGN_BOTTOM)
1276 horizontal = FALSE;
1277 else
1278 horizontal = TRUE;
1280 else
1282 /* horizontal alignment flags has priority over vertical ones if both are specified */
1283 if (!(style & (BS_CENTER | BS_VCENTER)) || ((style & BS_CENTER) && (style & BS_CENTER) != BS_CENTER)
1284 || !(style & BS_VCENTER) || (style & BS_VCENTER) == BS_VCENTER)
1285 horizontal = TRUE;
1286 else
1287 horizontal = FALSE;
1290 if (horizontal)
1292 if (maxWidth != 0)
1294 maxWidth -= imageSize.cx;
1295 if (maxWidth <= 0) maxWidth = 1;
1297 BUTTON_GetTextIdealSize(infoPtr, maxWidth, &textSize);
1298 size->cx = textSize.cx + imageSize.cx;
1299 size->cy = max(textSize.cy, imageSize.cy);
1301 else
1303 BUTTON_GetTextIdealSize(infoPtr, maxWidth, &textSize);
1304 size->cx = max(textSize.cx, imageSize.cx);
1305 size->cy = textSize.cy + imageSize.cy;
1309 static BOOL GB_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size)
1311 BUTTON_GetClientRectSize(infoPtr, size);
1312 return TRUE;
1315 static BOOL CB_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size)
1317 LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1318 HDC hdc;
1319 HFONT hfont;
1320 SIZE labelSize;
1321 INT textOffset;
1322 double scaleX;
1323 double scaleY;
1324 LONG checkboxWidth, checkboxHeight;
1325 LONG maxWidth = 0;
1327 if (SendMessageW(infoPtr->hwnd, WM_GETTEXTLENGTH, 0, 0) == 0)
1329 BUTTON_GetClientRectSize(infoPtr, size);
1330 return TRUE;
1333 hdc = GetDC(infoPtr->hwnd);
1334 scaleX = GetDeviceCaps(hdc, LOGPIXELSX) / 96.0;
1335 scaleY = GetDeviceCaps(hdc, LOGPIXELSY) / 96.0;
1336 if ((hfont = infoPtr->font)) SelectObject(hdc, hfont);
1337 GetCharWidthW(hdc, '0', '0', &textOffset);
1338 textOffset /= 2;
1339 ReleaseDC(infoPtr->hwnd, hdc);
1341 checkboxWidth = 12 * scaleX + 1;
1342 checkboxHeight = 12 * scaleY + 1;
1343 if (size->cx)
1345 maxWidth = size->cx - checkboxWidth - textOffset;
1346 if (maxWidth <= 0) maxWidth = 1;
1349 /* Checkbox doesn't support both image(but not image list) and text */
1350 if (!(style & (BS_ICON | BS_BITMAP)) && infoPtr->u.image)
1351 BUTTON_GetTextIdealSize(infoPtr, maxWidth, &labelSize);
1352 else
1353 BUTTON_GetLabelIdealSize(infoPtr, maxWidth, &labelSize);
1355 size->cx = labelSize.cx + checkboxWidth + textOffset;
1356 size->cy = max(labelSize.cy, checkboxHeight);
1358 return TRUE;
1361 static BOOL PB_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size)
1363 SIZE labelSize;
1365 if (SendMessageW(infoPtr->hwnd, WM_GETTEXTLENGTH, 0, 0) == 0)
1366 BUTTON_GetClientRectSize(infoPtr, size);
1367 else
1369 /* Ideal size include text size even if image only flags(BS_ICON, BS_BITMAP) are specified */
1370 BUTTON_GetLabelIdealSize(infoPtr, size->cx, &labelSize);
1372 size->cx = labelSize.cx;
1373 size->cy = labelSize.cy;
1375 return TRUE;
1378 static BOOL SB_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size)
1380 LONG extra_width = infoPtr->glyph_size.cx * 2 + GetSystemMetrics(SM_CXEDGE);
1381 SIZE label_size;
1383 if (SendMessageW(infoPtr->hwnd, WM_GETTEXTLENGTH, 0, 0) == 0)
1385 BUTTON_GetClientRectSize(infoPtr, size);
1386 size->cx = max(size->cx, extra_width);
1388 else
1390 BUTTON_GetLabelIdealSize(infoPtr, size->cx, &label_size);
1391 size->cx = label_size.cx + ((size->cx == 0) ? extra_width : 0);
1392 size->cy = label_size.cy;
1394 return TRUE;
1397 static BOOL CL_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size)
1399 HTHEME theme = GetWindowTheme(infoPtr->hwnd);
1400 HDC hdc = GetDC(infoPtr->hwnd);
1401 LONG w, text_w = 0, text_h = 0;
1402 UINT flags = DT_TOP | DT_LEFT;
1403 HFONT font, old_font = NULL;
1404 RECT text_bound = { 0 };
1405 SIZE img_size;
1406 RECT margin;
1407 WCHAR *text;
1409 /* Get the image size */
1410 if (infoPtr->u.image || infoPtr->imagelist.himl)
1411 img_size = BUTTON_GetImageSize(infoPtr);
1412 else
1414 if (theme)
1415 GetThemePartSize(theme, NULL, BP_COMMANDLINKGLYPH, CMDLS_NORMAL, NULL, TS_DRAW, &img_size);
1416 else
1417 img_size.cx = img_size.cy = command_link_defglyph_size;
1420 /* Get the content margins */
1421 if (theme)
1423 RECT r = { 0, 0, 0xffff, 0xffff };
1424 GetThemeBackgroundContentRect(theme, hdc, BP_COMMANDLINK, CMDLS_NORMAL, &r, &margin);
1425 margin.left -= r.left;
1426 margin.top -= r.top;
1427 margin.right = r.right - margin.right;
1428 margin.bottom = r.bottom - margin.bottom;
1430 else
1432 margin.left = margin.right = command_link_margin;
1433 margin.top = margin.bottom = command_link_margin;
1436 /* Account for the border margins and the margin between image and text */
1437 w = margin.left + margin.right + (img_size.cx ? (img_size.cx + command_link_margin) : 0);
1439 /* If a rectangle with a specific width was requested, bound the text to it */
1440 if (size->cx > w)
1442 text_bound.right = size->cx - w;
1443 flags |= DT_WORDBREAK;
1446 if (theme)
1448 if (infoPtr->font) old_font = SelectObject(hdc, infoPtr->font);
1450 /* Find the text's rect */
1451 if ((text = get_button_text(infoPtr)))
1453 RECT r;
1454 GetThemeTextExtent(theme, hdc, BP_COMMANDLINK, CMDLS_NORMAL,
1455 text, -1, flags, &text_bound, &r);
1456 heap_free(text);
1457 text_w = r.right - r.left;
1458 text_h = r.bottom - r.top;
1461 /* Find the note's rect */
1462 if (infoPtr->note)
1464 DTTOPTS opts;
1466 opts.dwSize = sizeof(opts);
1467 opts.dwFlags = DTT_FONTPROP | DTT_CALCRECT;
1468 opts.iFontPropId = TMT_BODYFONT;
1469 DrawThemeTextEx(theme, hdc, BP_COMMANDLINK, CMDLS_NORMAL,
1470 infoPtr->note, infoPtr->note_length,
1471 flags | DT_NOPREFIX | DT_CALCRECT, &text_bound, &opts);
1472 text_w = max(text_w, text_bound.right - text_bound.left);
1473 text_h += text_bound.bottom - text_bound.top;
1476 else
1478 NONCLIENTMETRICSW ncm;
1480 ncm.cbSize = sizeof(ncm);
1481 if (SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, sizeof(ncm), &ncm, 0))
1483 LONG note_weight = ncm.lfMessageFont.lfWeight;
1485 /* Find the text's rect */
1486 ncm.lfMessageFont.lfWeight = FW_BOLD;
1487 if ((font = CreateFontIndirectW(&ncm.lfMessageFont)))
1489 if ((text = get_button_text(infoPtr)))
1491 RECT r = text_bound;
1492 old_font = SelectObject(hdc, font);
1493 DrawTextW(hdc, text, -1, &r, flags | DT_CALCRECT);
1494 heap_free(text);
1496 text_w = r.right - r.left;
1497 text_h = r.bottom - r.top;
1499 DeleteObject(font);
1502 /* Find the note's rect */
1503 ncm.lfMessageFont.lfWeight = note_weight;
1504 if (infoPtr->note && (font = CreateFontIndirectW(&ncm.lfMessageFont)))
1506 HFONT tmp = SelectObject(hdc, font);
1507 if (!old_font) old_font = tmp;
1509 DrawTextW(hdc, infoPtr->note, infoPtr->note_length, &text_bound,
1510 flags | DT_NOPREFIX | DT_CALCRECT);
1511 DeleteObject(font);
1513 text_w = max(text_w, text_bound.right - text_bound.left);
1514 text_h += text_bound.bottom - text_bound.top + 2;
1518 w += text_w;
1520 size->cx = min(size->cx, w);
1521 size->cy = max(text_h, img_size.cy) + margin.top + margin.bottom;
1523 if (old_font) SelectObject(hdc, old_font);
1524 ReleaseDC(infoPtr->hwnd, hdc);
1525 return TRUE;
1528 /**********************************************************************
1529 * BUTTON_CalcLayoutRects
1531 * Calculates the rectangles of the button label(image and text) and its parts depending on a button's style.
1533 * Returns flags to be passed to DrawText.
1534 * Calculated rectangle doesn't take into account button state
1535 * (pushed, etc.). If there is nothing to draw (no text/image) output
1536 * rectangle is empty, and return value is (UINT)-1.
1538 * PARAMS:
1539 * infoPtr [I] Button pointer
1540 * hdc [I] Handle to device context to draw to
1541 * labelRc [I/O] Input the rect the label to be positioned in, and output the label rect
1542 * imageRc [O] Optional, output the image rect
1543 * textRc [O] Optional, output the text rect
1545 static UINT BUTTON_CalcLayoutRects(const BUTTON_INFO *infoPtr, HDC hdc, RECT *labelRc, RECT *imageRc, RECT *textRc)
1547 LONG style = GetWindowLongW( infoPtr->hwnd, GWL_STYLE );
1548 LONG ex_style = GetWindowLongW( infoPtr->hwnd, GWL_EXSTYLE );
1549 LONG split_style = infoPtr->imagelist.himl ? BUTTON_ILStoBS(infoPtr->imagelist.uAlign) : style;
1550 WCHAR *text = get_button_text(infoPtr);
1551 SIZE imageSize = BUTTON_GetImageSize(infoPtr);
1552 UINT dtStyle = BUTTON_BStoDT(style, ex_style);
1553 RECT labelRect, imageRect, imageRectWithMargin, textRect;
1554 LONG imageMarginWidth, imageMarginHeight;
1555 const RECT *textMargin = BUTTON_GetTextMargin(infoPtr);
1556 RECT emptyMargin = {0};
1557 LONG maxTextWidth;
1559 /* Calculate label rectangle according to label type */
1560 if ((imageSize.cx == 0 && imageSize.cy == 0) && (text == NULL || text[0] == '\0'))
1562 SetRectEmpty(labelRc);
1563 SetRectEmpty(imageRc);
1564 SetRectEmpty(textRc);
1565 heap_free(text);
1566 return (UINT)-1;
1569 SetRect(&imageRect, 0, 0, imageSize.cx, imageSize.cy);
1570 imageRectWithMargin = imageRect;
1571 if (infoPtr->imagelist.himl)
1573 imageRectWithMargin.top -= infoPtr->imagelist.margin.top;
1574 imageRectWithMargin.bottom += infoPtr->imagelist.margin.bottom;
1575 imageRectWithMargin.left -= infoPtr->imagelist.margin.left;
1576 imageRectWithMargin.right += infoPtr->imagelist.margin.right;
1579 /* Show image only */
1580 if (show_image_only(infoPtr))
1582 BUTTON_PositionRect(style, labelRc, &imageRect,
1583 infoPtr->imagelist.himl ? &infoPtr->imagelist.margin : &emptyMargin);
1584 labelRect = imageRect;
1585 SetRectEmpty(&textRect);
1587 else
1589 /* Get text rect */
1590 maxTextWidth = labelRc->right - labelRc->left;
1591 textRect = BUTTON_GetTextRect(infoPtr, hdc, text, maxTextWidth);
1593 /* Show image and text */
1594 if (show_image_and_text(infoPtr))
1596 RECT boundingLabelRect, boundingImageRect, boundingTextRect;
1598 /* Get label rect */
1599 /* Image list may have different alignment than the button, use the whole rect for label in this case */
1600 if (infoPtr->imagelist.himl)
1601 labelRect = *labelRc;
1602 else
1604 /* Get a label bounding rectangle to position the label in the user specified label rectangle because
1605 * text and image need to align together. */
1606 boundingLabelRect = BUTTON_GetBoundingLabelRect(split_style, &textRect, &imageRectWithMargin);
1607 BUTTON_PositionRect(split_style, labelRc, &boundingLabelRect, &emptyMargin);
1608 labelRect = boundingLabelRect;
1611 /* When imagelist has center align, use the whole rect for imagelist and text */
1612 if(infoPtr->imagelist.himl && infoPtr->imagelist.uAlign == BUTTON_IMAGELIST_ALIGN_CENTER)
1614 boundingImageRect = labelRect;
1615 boundingTextRect = labelRect;
1616 BUTTON_PositionRect(split_style, &boundingImageRect, &imageRect,
1617 infoPtr->imagelist.himl ? &infoPtr->imagelist.margin : &emptyMargin);
1618 /* Text doesn't use imagelist align */
1619 BUTTON_PositionRect(style, &boundingTextRect, &textRect, textMargin);
1621 else
1623 /* Get image rect */
1624 /* Split the label rect to two halves as two bounding rectangles for image and text */
1625 boundingImageRect = labelRect;
1626 imageMarginWidth = imageRectWithMargin.right - imageRectWithMargin.left;
1627 imageMarginHeight = imageRectWithMargin.bottom - imageRectWithMargin.top;
1628 if ((split_style & BS_CENTER) == BS_RIGHT)
1629 boundingImageRect.left = boundingImageRect.right - imageMarginWidth;
1630 else if ((split_style & BS_CENTER) == BS_LEFT)
1631 boundingImageRect.right = boundingImageRect.left + imageMarginWidth;
1632 else if ((split_style & BS_VCENTER) == BS_BOTTOM)
1633 boundingImageRect.top = boundingImageRect.bottom - imageMarginHeight;
1634 else if ((split_style & BS_VCENTER) == BS_TOP)
1635 boundingImageRect.bottom = boundingImageRect.top + imageMarginHeight;
1636 else
1637 boundingImageRect.right = boundingImageRect.left + imageMarginWidth;
1638 BUTTON_PositionRect(split_style, &boundingImageRect, &imageRect,
1639 infoPtr->imagelist.himl ? &infoPtr->imagelist.margin : &emptyMargin);
1641 /* Get text rect */
1642 SubtractRect(&boundingTextRect, &labelRect, &boundingImageRect);
1643 /* Text doesn't use imagelist align */
1644 BUTTON_PositionRect(style, &boundingTextRect, &textRect, textMargin);
1647 /* Show text only */
1648 else
1650 if (get_button_type(style) != BS_GROUPBOX)
1651 BUTTON_PositionRect(style, labelRc, &textRect, textMargin);
1652 else
1653 /* GroupBox is always top aligned */
1654 BUTTON_PositionRect((style & ~BS_VCENTER) | BS_TOP, labelRc, &textRect, textMargin);
1655 labelRect = textRect;
1656 SetRectEmpty(&imageRect);
1659 heap_free(text);
1661 CopyRect(labelRc, &labelRect);
1662 CopyRect(imageRc, &imageRect);
1663 CopyRect(textRc, &textRect);
1665 return dtStyle;
1669 /**********************************************************************
1670 * BUTTON_DrawImage
1672 * Draw the button's image into the specified rectangle.
1674 static void BUTTON_DrawImage(const BUTTON_INFO *infoPtr, HDC hdc, HBRUSH hbr, UINT flags, const RECT *rect)
1676 if (infoPtr->imagelist.himl)
1678 int i = (ImageList_GetImageCount(infoPtr->imagelist.himl) == 1) ? 0 : get_draw_state(infoPtr) - 1;
1680 ImageList_Draw(infoPtr->imagelist.himl, i, hdc, rect->left, rect->top, ILD_NORMAL);
1682 else
1684 switch (infoPtr->image_type)
1686 case IMAGE_ICON:
1687 flags |= DST_ICON;
1688 break;
1689 case IMAGE_BITMAP:
1690 flags |= DST_BITMAP;
1691 break;
1692 default:
1693 return;
1696 DrawStateW(hdc, hbr, NULL, (LPARAM)infoPtr->u.image, 0, rect->left, rect->top,
1697 rect->right - rect->left, rect->bottom - rect->top, flags);
1702 /**********************************************************************
1703 * BUTTON_DrawTextCallback
1705 * Callback function used by DrawStateW function.
1707 static BOOL CALLBACK BUTTON_DrawTextCallback(HDC hdc, LPARAM lp, WPARAM wp, int cx, int cy)
1709 RECT rc;
1711 SetRect(&rc, 0, 0, cx, cy);
1712 DrawTextW(hdc, (LPCWSTR)lp, -1, &rc, (UINT)wp);
1713 return TRUE;
1716 /**********************************************************************
1717 * BUTTON_DrawLabel
1719 * Common function for drawing button label.
1721 * FIXME:
1722 * 1. When BS_SINGLELINE is specified and text contains '\t', '\n' or '\r' in the middle, they are rendered as
1723 * squares now whereas they should be ignored.
1724 * 2. When BS_MULTILINE is specified and text contains space in the middle, the space mistakenly be rendered as newline.
1726 static void BUTTON_DrawLabel(const BUTTON_INFO *infoPtr, HDC hdc, UINT dtFlags, const RECT *imageRect,
1727 const RECT *textRect)
1729 HBRUSH hbr = 0;
1730 UINT flags = IsWindowEnabled(infoPtr->hwnd) ? DSS_NORMAL : DSS_DISABLED;
1731 LONG style = GetWindowLongW( infoPtr->hwnd, GWL_STYLE );
1732 WCHAR *text;
1734 /* FIXME: To draw disabled label in Win31 look-and-feel, we probably
1735 * must use DSS_MONO flag and COLOR_GRAYTEXT brush (or maybe DSS_UNION).
1736 * I don't have Win31 on hand to verify that, so I leave it as is.
1739 if ((style & BS_PUSHLIKE) && (infoPtr->state & BST_INDETERMINATE))
1741 hbr = GetSysColorBrush(COLOR_GRAYTEXT);
1742 flags |= DSS_MONO;
1745 if (show_image(infoPtr)) BUTTON_DrawImage(infoPtr, hdc, hbr, flags, imageRect);
1746 if (show_image_only(infoPtr)) return;
1748 /* DST_COMPLEX -- is 0 */
1749 if (!(text = get_button_text(infoPtr))) return;
1750 DrawStateW(hdc, hbr, BUTTON_DrawTextCallback, (LPARAM)text, dtFlags, textRect->left, textRect->top,
1751 textRect->right - textRect->left, textRect->bottom - textRect->top, flags);
1752 heap_free(text);
1755 /**********************************************************************
1756 * Push Button Functions
1758 static void PB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action )
1760 RECT rc, labelRect, imageRect, textRect;
1761 UINT dtFlags, uState;
1762 HPEN hOldPen, hpen;
1763 HBRUSH hOldBrush;
1764 INT oldBkMode;
1765 COLORREF oldTxtColor;
1766 LRESULT cdrf;
1767 HFONT hFont;
1768 NMCUSTOMDRAW nmcd;
1769 LONG state = infoPtr->state;
1770 LONG style = GetWindowLongW( infoPtr->hwnd, GWL_STYLE );
1771 BOOL pushedState = (state & BST_PUSHED);
1772 HWND parent;
1773 HRGN hrgn;
1775 GetClientRect( infoPtr->hwnd, &rc );
1777 /* Send WM_CTLCOLOR to allow changing the font (the colors are fixed) */
1778 if ((hFont = infoPtr->font)) SelectObject( hDC, hFont );
1779 parent = GetParent(infoPtr->hwnd);
1780 if (!parent) parent = infoPtr->hwnd;
1781 SendMessageW( parent, WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)infoPtr->hwnd );
1783 hrgn = set_control_clipping( hDC, &rc );
1785 hpen = CreatePen( PS_SOLID, 1, GetSysColor(COLOR_WINDOWFRAME));
1786 hOldPen = SelectObject(hDC, hpen);
1787 hOldBrush = SelectObject(hDC,GetSysColorBrush(COLOR_BTNFACE));
1788 oldBkMode = SetBkMode(hDC, TRANSPARENT);
1790 init_custom_draw(&nmcd, infoPtr, hDC, &rc);
1792 /* Send erase notifications */
1793 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
1794 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
1796 if (get_button_type(style) == BS_DEFPUSHBUTTON)
1798 if (action != ODA_FOCUS)
1799 Rectangle(hDC, rc.left, rc.top, rc.right, rc.bottom);
1800 InflateRect( &rc, -1, -1 );
1803 /* Skip the frame drawing if only focus has changed */
1804 if (action != ODA_FOCUS)
1806 uState = DFCS_BUTTONPUSH;
1808 if (style & BS_FLAT)
1809 uState |= DFCS_MONO;
1810 else if (pushedState)
1812 if (get_button_type(style) == BS_DEFPUSHBUTTON )
1813 uState |= DFCS_FLAT;
1814 else
1815 uState |= DFCS_PUSHED;
1818 if (state & (BST_CHECKED | BST_INDETERMINATE))
1819 uState |= DFCS_CHECKED;
1821 DrawFrameControl( hDC, &rc, DFC_BUTTON, uState );
1824 if (cdrf & CDRF_NOTIFYPOSTERASE)
1826 nmcd.dwDrawStage = CDDS_POSTERASE;
1827 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
1830 /* Send paint notifications */
1831 nmcd.dwDrawStage = CDDS_PREPAINT;
1832 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
1833 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
1835 if (!(cdrf & CDRF_DOERASE) && action != ODA_FOCUS)
1837 /* draw button label */
1838 labelRect = rc;
1839 /* Shrink label rect at all sides by 2 so that the content won't touch the surrounding frame */
1840 InflateRect(&labelRect, -2, -2);
1841 dtFlags = BUTTON_CalcLayoutRects(infoPtr, hDC, &labelRect, &imageRect, &textRect);
1843 if (dtFlags != (UINT)-1L)
1845 if (pushedState) OffsetRect(&labelRect, 1, 1);
1847 oldTxtColor = SetTextColor( hDC, GetSysColor(COLOR_BTNTEXT) );
1849 BUTTON_DrawLabel(infoPtr, hDC, dtFlags, &imageRect, &textRect);
1851 SetTextColor( hDC, oldTxtColor );
1855 if (cdrf & CDRF_NOTIFYPOSTPAINT)
1857 nmcd.dwDrawStage = CDDS_POSTPAINT;
1858 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
1860 if ((cdrf & CDRF_SKIPPOSTPAINT) || dtFlags == (UINT)-1L) goto cleanup;
1862 if (action == ODA_FOCUS || (state & BST_FOCUS))
1864 InflateRect( &rc, -2, -2 );
1865 DrawFocusRect( hDC, &rc );
1868 cleanup:
1869 SelectObject( hDC, hOldPen );
1870 SelectObject( hDC, hOldBrush );
1871 SetBkMode(hDC, oldBkMode);
1872 SelectClipRgn( hDC, hrgn );
1873 if (hrgn) DeleteObject( hrgn );
1874 DeleteObject( hpen );
1877 /**********************************************************************
1878 * Check Box & Radio Button Functions
1881 static void CB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action )
1883 RECT rbox, labelRect, imageRect, textRect, client;
1884 HBRUSH hBrush;
1885 int delta, text_offset, checkBoxWidth, checkBoxHeight;
1886 UINT dtFlags;
1887 LRESULT cdrf;
1888 HFONT hFont;
1889 NMCUSTOMDRAW nmcd;
1890 LONG state = infoPtr->state;
1891 LONG style = GetWindowLongW( infoPtr->hwnd, GWL_STYLE );
1892 LONG ex_style = GetWindowLongW( infoPtr->hwnd, GWL_EXSTYLE );
1893 HWND parent;
1894 HRGN hrgn;
1896 if (style & BS_PUSHLIKE)
1898 PB_Paint( infoPtr, hDC, action );
1899 return;
1902 GetClientRect(infoPtr->hwnd, &client);
1903 rbox = labelRect = client;
1905 checkBoxWidth = 12 * GetDpiForWindow( infoPtr->hwnd ) / 96 + 1;
1906 checkBoxHeight = 12 * GetDpiForWindow( infoPtr->hwnd ) / 96 + 1;
1908 if ((hFont = infoPtr->font)) SelectObject( hDC, hFont );
1909 GetCharWidthW( hDC, '0', '0', &text_offset );
1910 text_offset /= 2;
1912 parent = GetParent(infoPtr->hwnd);
1913 if (!parent) parent = infoPtr->hwnd;
1914 hBrush = (HBRUSH)SendMessageW(parent, WM_CTLCOLORSTATIC, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
1915 if (!hBrush) /* did the app forget to call defwindowproc ? */
1916 hBrush = (HBRUSH)DefWindowProcW(parent, WM_CTLCOLORSTATIC, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
1917 hrgn = set_control_clipping( hDC, &client );
1919 if (style & BS_LEFTTEXT || ex_style & WS_EX_RIGHT)
1921 labelRect.right -= checkBoxWidth + text_offset;
1922 rbox.left = rbox.right - checkBoxWidth;
1924 else
1926 labelRect.left += checkBoxWidth + text_offset;
1927 rbox.right = checkBoxWidth;
1930 init_custom_draw(&nmcd, infoPtr, hDC, &client);
1932 /* Send erase notifications */
1933 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
1934 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
1936 /* Since WM_ERASEBKGND does nothing, first prepare background */
1937 if (action == ODA_SELECT) FillRect( hDC, &rbox, hBrush );
1938 if (action == ODA_DRAWENTIRE) FillRect( hDC, &client, hBrush );
1939 if (cdrf & CDRF_NOTIFYPOSTERASE)
1941 nmcd.dwDrawStage = CDDS_POSTERASE;
1942 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
1945 /* Draw label */
1946 client = labelRect;
1947 dtFlags = BUTTON_CalcLayoutRects(infoPtr, hDC, &labelRect, &imageRect, &textRect);
1949 /* Only adjust rbox when rtext is valid */
1950 if (dtFlags != (UINT)-1L)
1952 rbox.top = labelRect.top;
1953 rbox.bottom = labelRect.bottom;
1956 /* Send paint notifications */
1957 nmcd.dwDrawStage = CDDS_PREPAINT;
1958 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
1959 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
1961 /* Draw the check-box bitmap */
1962 if (!(cdrf & CDRF_DOERASE))
1964 if (action == ODA_DRAWENTIRE || action == ODA_SELECT)
1966 UINT flags;
1968 if ((get_button_type(style) == BS_RADIOBUTTON) ||
1969 (get_button_type(style) == BS_AUTORADIOBUTTON)) flags = DFCS_BUTTONRADIO;
1970 else if (state & BST_INDETERMINATE) flags = DFCS_BUTTON3STATE;
1971 else flags = DFCS_BUTTONCHECK;
1973 if (state & (BST_CHECKED | BST_INDETERMINATE)) flags |= DFCS_CHECKED;
1974 if (state & BST_PUSHED) flags |= DFCS_PUSHED;
1975 if (style & WS_DISABLED) flags |= DFCS_INACTIVE;
1977 /* rbox must have the correct height */
1978 delta = rbox.bottom - rbox.top - checkBoxHeight;
1980 if ((style & BS_VCENTER) == BS_TOP)
1982 if (delta > 0)
1983 rbox.bottom = rbox.top + checkBoxHeight;
1984 else
1986 rbox.top -= -delta / 2 + 1;
1987 rbox.bottom = rbox.top + checkBoxHeight;
1990 else if ((style & BS_VCENTER) == BS_BOTTOM)
1992 if (delta > 0)
1993 rbox.top = rbox.bottom - checkBoxHeight;
1994 else
1996 rbox.bottom += -delta / 2 + 1;
1997 rbox.top = rbox.bottom - checkBoxHeight;
2000 else /* Default */
2002 if (delta > 0)
2004 int ofs = delta / 2;
2005 rbox.bottom -= ofs + 1;
2006 rbox.top = rbox.bottom - checkBoxHeight;
2008 else if (delta < 0)
2010 int ofs = -delta / 2;
2011 rbox.top -= ofs + 1;
2012 rbox.bottom = rbox.top + checkBoxHeight;
2016 DrawFrameControl(hDC, &rbox, DFC_BUTTON, flags);
2019 if (dtFlags != (UINT)-1L) /* Something to draw */
2020 if (action == ODA_DRAWENTIRE) BUTTON_DrawLabel(infoPtr, hDC, dtFlags, &imageRect, &textRect);
2023 if (cdrf & CDRF_NOTIFYPOSTPAINT)
2025 nmcd.dwDrawStage = CDDS_POSTPAINT;
2026 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2028 if ((cdrf & CDRF_SKIPPOSTPAINT) || dtFlags == (UINT)-1L) goto cleanup;
2030 /* ... and focus */
2031 if (action == ODA_FOCUS || (state & BST_FOCUS))
2033 labelRect.left--;
2034 labelRect.right++;
2035 IntersectRect(&labelRect, &labelRect, &client);
2036 DrawFocusRect(hDC, &labelRect);
2039 cleanup:
2040 SelectClipRgn( hDC, hrgn );
2041 if (hrgn) DeleteObject( hrgn );
2045 /**********************************************************************
2046 * BUTTON_CheckAutoRadioButton
2048 * hwnd is checked, uncheck every other auto radio button in group
2050 static void BUTTON_CheckAutoRadioButton( HWND hwnd )
2052 HWND parent, sibling, start;
2054 parent = GetParent(hwnd);
2055 /* make sure that starting control is not disabled or invisible */
2056 start = sibling = GetNextDlgGroupItem( parent, hwnd, TRUE );
2059 if (!sibling) break;
2060 if ((hwnd != sibling) &&
2061 ((GetWindowLongW( sibling, GWL_STYLE) & BS_TYPEMASK) == BS_AUTORADIOBUTTON))
2062 SendMessageW( sibling, BM_SETCHECK, BST_UNCHECKED, 0 );
2063 sibling = GetNextDlgGroupItem( parent, sibling, FALSE );
2064 } while (sibling != start);
2068 /**********************************************************************
2069 * Group Box Functions
2072 static void GB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action )
2074 RECT labelRect, imageRect, textRect, rcFrame;
2075 HBRUSH hbr;
2076 HFONT hFont;
2077 UINT dtFlags;
2078 TEXTMETRICW tm;
2079 LONG style = GetWindowLongW( infoPtr->hwnd, GWL_STYLE );
2080 HWND parent;
2081 HRGN hrgn;
2083 if ((hFont = infoPtr->font)) SelectObject( hDC, hFont );
2084 /* GroupBox acts like static control, so it sends CTLCOLORSTATIC */
2085 parent = GetParent(infoPtr->hwnd);
2086 if (!parent) parent = infoPtr->hwnd;
2087 hbr = (HBRUSH)SendMessageW(parent, WM_CTLCOLORSTATIC, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
2088 if (!hbr) /* did the app forget to call defwindowproc ? */
2089 hbr = (HBRUSH)DefWindowProcW(parent, WM_CTLCOLORSTATIC, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
2090 GetClientRect(infoPtr->hwnd, &labelRect);
2091 rcFrame = labelRect;
2092 hrgn = set_control_clipping(hDC, &labelRect);
2094 GetTextMetricsW (hDC, &tm);
2095 rcFrame.top += (tm.tmHeight / 2) - 1;
2096 DrawEdge (hDC, &rcFrame, EDGE_ETCHED, BF_RECT | ((style & BS_FLAT) ? BF_FLAT : 0));
2098 InflateRect(&labelRect, -7, 1);
2099 dtFlags = BUTTON_CalcLayoutRects(infoPtr, hDC, &labelRect, &imageRect, &textRect);
2101 if (dtFlags != (UINT)-1)
2103 /* Because buttons have CS_PARENTDC class style, there is a chance
2104 * that label will be drawn out of client rect.
2105 * But Windows doesn't clip label's rect, so do I.
2108 /* There is 1-pixel margin at the left, right, and bottom */
2109 labelRect.left--;
2110 labelRect.right++;
2111 labelRect.bottom++;
2112 FillRect(hDC, &labelRect, hbr);
2113 labelRect.left++;
2114 labelRect.right--;
2115 labelRect.bottom--;
2117 BUTTON_DrawLabel(infoPtr, hDC, dtFlags, &imageRect, &textRect);
2119 SelectClipRgn( hDC, hrgn );
2120 if (hrgn) DeleteObject( hrgn );
2124 /**********************************************************************
2125 * User Button Functions
2128 static void UB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action )
2130 RECT rc;
2131 HBRUSH hBrush;
2132 LRESULT cdrf;
2133 HFONT hFont;
2134 NMCUSTOMDRAW nmcd;
2135 LONG state = infoPtr->state;
2136 HWND parent;
2138 GetClientRect( infoPtr->hwnd, &rc);
2140 if ((hFont = infoPtr->font)) SelectObject( hDC, hFont );
2142 parent = GetParent(infoPtr->hwnd);
2143 if (!parent) parent = infoPtr->hwnd;
2144 hBrush = (HBRUSH)SendMessageW(parent, WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
2145 if (!hBrush) /* did the app forget to call defwindowproc ? */
2146 hBrush = (HBRUSH)DefWindowProcW(parent, WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
2148 if (action == ODA_FOCUS || (state & BST_FOCUS))
2150 init_custom_draw(&nmcd, infoPtr, hDC, &rc);
2152 /* Send erase notifications */
2153 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2154 if (cdrf & CDRF_SKIPDEFAULT) goto notify;
2157 FillRect( hDC, &rc, hBrush );
2158 if (action == ODA_FOCUS || (state & BST_FOCUS))
2160 if (cdrf & CDRF_NOTIFYPOSTERASE)
2162 nmcd.dwDrawStage = CDDS_POSTERASE;
2163 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2166 /* Send paint notifications */
2167 nmcd.dwDrawStage = CDDS_PREPAINT;
2168 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2169 if (cdrf & CDRF_SKIPDEFAULT) goto notify;
2170 if (cdrf & CDRF_NOTIFYPOSTPAINT)
2172 nmcd.dwDrawStage = CDDS_POSTPAINT;
2173 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2176 if (!(cdrf & CDRF_SKIPPOSTPAINT))
2177 DrawFocusRect( hDC, &rc );
2180 notify:
2181 switch (action)
2183 case ODA_FOCUS:
2184 BUTTON_NOTIFY_PARENT( infoPtr->hwnd, (state & BST_FOCUS) ? BN_SETFOCUS : BN_KILLFOCUS );
2185 break;
2187 case ODA_SELECT:
2188 BUTTON_NOTIFY_PARENT( infoPtr->hwnd, (state & BST_PUSHED) ? BN_HILITE : BN_UNHILITE );
2189 break;
2191 default:
2192 break;
2197 /**********************************************************************
2198 * Ownerdrawn Button Functions
2201 static void OB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action )
2203 LONG state = infoPtr->state;
2204 DRAWITEMSTRUCT dis;
2205 LONG_PTR id = GetWindowLongPtrW( infoPtr->hwnd, GWLP_ID );
2206 HWND parent;
2207 HFONT hFont;
2208 HRGN hrgn;
2210 dis.CtlType = ODT_BUTTON;
2211 dis.CtlID = id;
2212 dis.itemID = 0;
2213 dis.itemAction = action;
2214 dis.itemState = ((state & BST_FOCUS) ? ODS_FOCUS : 0) |
2215 ((state & BST_PUSHED) ? ODS_SELECTED : 0) |
2216 (IsWindowEnabled(infoPtr->hwnd) ? 0: ODS_DISABLED);
2217 dis.hwndItem = infoPtr->hwnd;
2218 dis.hDC = hDC;
2219 dis.itemData = 0;
2220 GetClientRect( infoPtr->hwnd, &dis.rcItem );
2222 if ((hFont = infoPtr->font)) SelectObject( hDC, hFont );
2223 parent = GetParent(infoPtr->hwnd);
2224 if (!parent) parent = infoPtr->hwnd;
2225 SendMessageW( parent, WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)infoPtr->hwnd );
2227 hrgn = set_control_clipping( hDC, &dis.rcItem );
2229 SendMessageW( GetParent(infoPtr->hwnd), WM_DRAWITEM, id, (LPARAM)&dis );
2230 SelectClipRgn( hDC, hrgn );
2231 if (hrgn) DeleteObject( hrgn );
2235 /**********************************************************************
2236 * Split Button Functions
2238 static void SB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action )
2240 LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2241 LONG state = infoPtr->state;
2242 UINT dtFlags = (UINT)-1L;
2244 RECT rc, push_rect, dropdown_rect;
2245 NMCUSTOMDRAW nmcd;
2246 HPEN pen, old_pen;
2247 HBRUSH old_brush;
2248 INT old_bk_mode;
2249 LRESULT cdrf;
2250 HWND parent;
2251 HRGN hrgn;
2253 GetClientRect(infoPtr->hwnd, &rc);
2255 /* Send WM_CTLCOLOR to allow changing the font (the colors are fixed) */
2256 if (infoPtr->font) SelectObject(hDC, infoPtr->font);
2257 if (!(parent = GetParent(infoPtr->hwnd))) parent = infoPtr->hwnd;
2258 SendMessageW(parent, WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
2260 hrgn = set_control_clipping(hDC, &rc);
2262 pen = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_WINDOWFRAME));
2263 old_pen = SelectObject(hDC, pen);
2264 old_brush = SelectObject(hDC, GetSysColorBrush(COLOR_BTNFACE));
2265 old_bk_mode = SetBkMode(hDC, TRANSPARENT);
2267 init_custom_draw(&nmcd, infoPtr, hDC, &rc);
2269 /* Send erase notifications */
2270 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2271 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2273 if (get_button_type(style) == BS_DEFSPLITBUTTON)
2275 if (action != ODA_FOCUS)
2276 Rectangle(hDC, rc.left, rc.top, rc.right, rc.bottom);
2277 InflateRect(&rc, -1, -1);
2278 /* The split will now be off by 1 pixel, but
2279 that's exactly what Windows does as well */
2282 get_split_button_rects(infoPtr, &rc, &push_rect, &dropdown_rect);
2283 if (infoPtr->split_style & BCSS_NOSPLIT)
2284 push_rect = rc;
2286 /* Skip the frame drawing if only focus has changed */
2287 if (action != ODA_FOCUS)
2289 UINT flags = DFCS_BUTTONPUSH;
2291 if (style & BS_FLAT) flags |= DFCS_MONO;
2292 else if (state & BST_PUSHED)
2293 flags |= (get_button_type(style) == BS_DEFSPLITBUTTON)
2294 ? DFCS_FLAT : DFCS_PUSHED;
2296 if (state & (BST_CHECKED | BST_INDETERMINATE))
2297 flags |= DFCS_CHECKED;
2299 if (infoPtr->split_style & BCSS_NOSPLIT)
2300 DrawFrameControl(hDC, &push_rect, DFC_BUTTON, flags);
2301 else
2303 UINT dropdown_flags = flags & ~DFCS_CHECKED;
2305 if (state & BST_DROPDOWNPUSHED)
2306 dropdown_flags = (dropdown_flags & ~DFCS_FLAT) | DFCS_PUSHED;
2308 /* Adjust for shadow and draw order so it looks properly */
2309 if (infoPtr->split_style & BCSS_ALIGNLEFT)
2311 dropdown_rect.right++;
2312 DrawFrameControl(hDC, &dropdown_rect, DFC_BUTTON, dropdown_flags);
2313 dropdown_rect.right--;
2314 DrawFrameControl(hDC, &push_rect, DFC_BUTTON, flags);
2316 else
2318 push_rect.right++;
2319 DrawFrameControl(hDC, &push_rect, DFC_BUTTON, flags);
2320 push_rect.right--;
2321 DrawFrameControl(hDC, &dropdown_rect, DFC_BUTTON, dropdown_flags);
2326 if (cdrf & CDRF_NOTIFYPOSTERASE)
2328 nmcd.dwDrawStage = CDDS_POSTERASE;
2329 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2332 /* Send paint notifications */
2333 nmcd.dwDrawStage = CDDS_PREPAINT;
2334 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2335 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2337 /* Shrink push button rect so that the content won't touch the surrounding frame */
2338 InflateRect(&push_rect, -2, -2);
2340 if (!(cdrf & CDRF_DOERASE) && action != ODA_FOCUS)
2342 COLORREF old_color = SetTextColor(hDC, GetSysColor(COLOR_BTNTEXT));
2343 RECT label_rect = push_rect, image_rect, text_rect;
2345 dtFlags = BUTTON_CalcLayoutRects(infoPtr, hDC, &label_rect, &image_rect, &text_rect);
2347 if (dtFlags != (UINT)-1L)
2348 BUTTON_DrawLabel(infoPtr, hDC, dtFlags, &image_rect, &text_rect);
2350 draw_split_button_dropdown_glyph(infoPtr, hDC, &dropdown_rect);
2351 SetTextColor(hDC, old_color);
2354 if (cdrf & CDRF_NOTIFYPOSTPAINT)
2356 nmcd.dwDrawStage = CDDS_POSTPAINT;
2357 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2359 if ((cdrf & CDRF_SKIPPOSTPAINT) || dtFlags == (UINT)-1L) goto cleanup;
2361 if (action == ODA_FOCUS || (state & BST_FOCUS))
2362 DrawFocusRect(hDC, &push_rect);
2364 cleanup:
2365 SelectObject(hDC, old_pen);
2366 SelectObject(hDC, old_brush);
2367 SetBkMode(hDC, old_bk_mode);
2368 SelectClipRgn(hDC, hrgn);
2369 if (hrgn) DeleteObject(hrgn);
2370 DeleteObject(pen);
2373 /* Given the full button rect of the split button, retrieve the push part and the dropdown part */
2374 static inline void get_split_button_rects(const BUTTON_INFO *infoPtr, const RECT *button_rect,
2375 RECT *push_rect, RECT *dropdown_rect)
2377 *push_rect = *dropdown_rect = *button_rect;
2379 /* The dropdown takes priority if the client rect is too small, it will only have a dropdown */
2380 if (infoPtr->split_style & BCSS_ALIGNLEFT)
2382 dropdown_rect->right = min(button_rect->left + infoPtr->glyph_size.cx, button_rect->right);
2383 push_rect->left = dropdown_rect->right;
2385 else
2387 dropdown_rect->left = max(button_rect->right - infoPtr->glyph_size.cx, button_rect->left);
2388 push_rect->right = dropdown_rect->left;
2392 /* Notify the parent if the point is within the dropdown and return TRUE (always notify if NULL) */
2393 static BOOL notify_split_button_dropdown(const BUTTON_INFO *infoPtr, const POINT *pt, HWND hwnd)
2395 NMBCDROPDOWN nmbcd;
2397 GetClientRect(hwnd, &nmbcd.rcButton);
2398 if (pt)
2400 RECT push_rect, dropdown_rect;
2402 get_split_button_rects(infoPtr, &nmbcd.rcButton, &push_rect, &dropdown_rect);
2403 if (!PtInRect(&dropdown_rect, *pt))
2404 return FALSE;
2406 /* If it's already down (set manually via BCM_SETDROPDOWNSTATE), fake the notify */
2407 if (infoPtr->state & BST_DROPDOWNPUSHED)
2408 return TRUE;
2410 SendMessageW(hwnd, BCM_SETDROPDOWNSTATE, TRUE, 0);
2412 nmbcd.hdr.hwndFrom = hwnd;
2413 nmbcd.hdr.idFrom = GetWindowLongPtrW(hwnd, GWLP_ID);
2414 nmbcd.hdr.code = BCN_DROPDOWN;
2415 SendMessageW(GetParent(hwnd), WM_NOTIFY, nmbcd.hdr.idFrom, (LPARAM)&nmbcd);
2417 SendMessageW(hwnd, BCM_SETDROPDOWNSTATE, FALSE, 0);
2418 return TRUE;
2421 /* Draw the split button dropdown glyph or image */
2422 static void draw_split_button_dropdown_glyph(const BUTTON_INFO *infoPtr, HDC hdc, RECT *rect)
2424 if (infoPtr->split_style & BCSS_IMAGE)
2426 int w, h;
2428 /* When the glyph is an image list, Windows is very buggy with BCSS_STRETCH,
2429 positions it weirdly and doesn't even stretch it, but instead extends the
2430 image, leaking into other images in the list (or black if none). Instead,
2431 we'll ignore this and just position it at center as without BCSS_STRETCH. */
2432 if (!ImageList_GetIconSize(infoPtr->glyph, &w, &h)) return;
2434 ImageList_Draw(infoPtr->glyph,
2435 (ImageList_GetImageCount(infoPtr->glyph) == 1) ? 0 : get_draw_state(infoPtr) - 1,
2436 hdc, rect->left + (rect->right - rect->left - w) / 2,
2437 rect->top + (rect->bottom - rect->top - h) / 2, ILD_NORMAL);
2439 else if (infoPtr->glyph_size.cy >= 0)
2441 /* infoPtr->glyph is a character code from Marlett */
2442 HFONT font, old_font;
2443 LOGFONTW logfont = { 0, 0, 0, 0, FW_NORMAL, 0, 0, 0, SYMBOL_CHARSET, 0, 0, 0, 0,
2444 L"Marlett" };
2445 if (infoPtr->glyph_size.cy)
2447 /* BCSS_STRETCH preserves aspect ratio, uses minimum as size */
2448 if (infoPtr->split_style & BCSS_STRETCH)
2449 logfont.lfHeight = min(infoPtr->glyph_size.cx, infoPtr->glyph_size.cy);
2450 else
2452 logfont.lfWidth = infoPtr->glyph_size.cx;
2453 logfont.lfHeight = infoPtr->glyph_size.cy;
2456 else logfont.lfHeight = infoPtr->glyph_size.cx;
2458 if ((font = CreateFontIndirectW(&logfont)))
2460 old_font = SelectObject(hdc, font);
2461 DrawTextW(hdc, (const WCHAR*)&infoPtr->glyph, 1, rect,
2462 DT_CENTER | DT_VCENTER | DT_SINGLELINE | DT_NOCLIP | DT_NOPREFIX);
2463 SelectObject(hdc, old_font);
2464 DeleteObject(font);
2470 /**********************************************************************
2471 * Command Link Functions
2473 static void CL_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action )
2475 LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2476 LONG state = infoPtr->state;
2478 RECT rc, content_rect;
2479 NMCUSTOMDRAW nmcd;
2480 HPEN pen, old_pen;
2481 HBRUSH old_brush;
2482 INT old_bk_mode;
2483 LRESULT cdrf;
2484 HWND parent;
2485 HRGN hrgn;
2487 GetClientRect(infoPtr->hwnd, &rc);
2489 /* Command Links are not affected by the button's font, and are based
2490 on the default message font. Furthermore, they are not affected by
2491 any of the alignment styles (and always align with the top-left). */
2492 if (!(parent = GetParent(infoPtr->hwnd))) parent = infoPtr->hwnd;
2493 SendMessageW(parent, WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
2495 hrgn = set_control_clipping(hDC, &rc);
2497 pen = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_WINDOWFRAME));
2498 old_pen = SelectObject(hDC, pen);
2499 old_brush = SelectObject(hDC, GetSysColorBrush(COLOR_BTNFACE));
2500 old_bk_mode = SetBkMode(hDC, TRANSPARENT);
2502 init_custom_draw(&nmcd, infoPtr, hDC, &rc);
2504 /* Send erase notifications */
2505 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2506 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2507 content_rect = rc;
2509 if (get_button_type(style) == BS_DEFCOMMANDLINK)
2511 if (action != ODA_FOCUS)
2512 Rectangle(hDC, rc.left, rc.top, rc.right, rc.bottom);
2513 InflateRect(&rc, -1, -1);
2516 /* Skip the frame drawing if only focus has changed */
2517 if (action != ODA_FOCUS)
2519 if (!(state & (BST_HOT | BST_PUSHED | BST_CHECKED | BST_INDETERMINATE)))
2520 FillRect(hDC, &rc, GetSysColorBrush(COLOR_BTNFACE));
2521 else
2523 UINT flags = DFCS_BUTTONPUSH;
2525 if (style & BS_FLAT) flags |= DFCS_MONO;
2526 else if (state & BST_PUSHED) flags |= DFCS_PUSHED;
2528 if (state & (BST_CHECKED | BST_INDETERMINATE))
2529 flags |= DFCS_CHECKED;
2530 DrawFrameControl(hDC, &rc, DFC_BUTTON, flags);
2534 if (cdrf & CDRF_NOTIFYPOSTERASE)
2536 nmcd.dwDrawStage = CDDS_POSTERASE;
2537 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2540 /* Send paint notifications */
2541 nmcd.dwDrawStage = CDDS_PREPAINT;
2542 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2543 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2545 if (!(cdrf & CDRF_DOERASE) && action != ODA_FOCUS)
2547 UINT flags = IsWindowEnabled(infoPtr->hwnd) ? DSS_NORMAL : DSS_DISABLED;
2548 COLORREF old_color = SetTextColor(hDC, GetSysColor(flags == DSS_NORMAL ?
2549 COLOR_BTNTEXT : COLOR_GRAYTEXT));
2550 HIMAGELIST defimg = NULL;
2551 NONCLIENTMETRICSW ncm;
2552 UINT txt_h = 0;
2553 SIZE img_size;
2555 /* Command Links ignore the margins of the image list or its alignment */
2556 if (infoPtr->u.image || infoPtr->imagelist.himl)
2557 img_size = BUTTON_GetImageSize(infoPtr);
2558 else
2560 img_size.cx = img_size.cy = command_link_defglyph_size;
2561 defimg = ImageList_LoadImageW(COMCTL32_hModule, (LPCWSTR)MAKEINTRESOURCE(IDB_CMDLINK),
2562 img_size.cx, 3, CLR_NONE, IMAGE_BITMAP, LR_CREATEDIBSECTION);
2565 /* Shrink rect by the command link margin, except on bottom (just the frame) */
2566 InflateRect(&content_rect, -command_link_margin, -command_link_margin);
2567 content_rect.bottom += command_link_margin - 2;
2569 ncm.cbSize = sizeof(ncm);
2570 if (SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, sizeof(ncm), &ncm, 0))
2572 LONG note_weight = ncm.lfMessageFont.lfWeight;
2573 RECT r = content_rect;
2574 WCHAR *text;
2575 HFONT font;
2577 if (img_size.cx) r.left += img_size.cx + command_link_margin;
2579 /* Draw the text */
2580 ncm.lfMessageFont.lfWeight = FW_BOLD;
2581 if ((font = CreateFontIndirectW(&ncm.lfMessageFont)))
2583 if ((text = get_button_text(infoPtr)))
2585 SelectObject(hDC, font);
2586 txt_h = DrawTextW(hDC, text, -1, &r,
2587 DT_TOP | DT_LEFT | DT_WORDBREAK | DT_END_ELLIPSIS);
2588 heap_free(text);
2590 DeleteObject(font);
2593 /* Draw the note */
2594 ncm.lfMessageFont.lfWeight = note_weight;
2595 if (infoPtr->note && (font = CreateFontIndirectW(&ncm.lfMessageFont)))
2597 r.top += txt_h + 2;
2598 SelectObject(hDC, font);
2599 DrawTextW(hDC, infoPtr->note, infoPtr->note_length, &r,
2600 DT_TOP | DT_LEFT | DT_WORDBREAK | DT_NOPREFIX);
2601 DeleteObject(font);
2605 /* Position the image at the vertical center of the drawn text (not note) */
2606 txt_h = min(txt_h, content_rect.bottom - content_rect.top);
2607 if (img_size.cy < txt_h) content_rect.top += (txt_h - img_size.cy) / 2;
2609 content_rect.right = content_rect.left + img_size.cx;
2610 content_rect.bottom = content_rect.top + img_size.cy;
2612 if (defimg)
2614 int i = 0;
2615 if (flags == DSS_DISABLED) i = 2;
2616 else if (state & BST_HOT) i = 1;
2618 ImageList_Draw(defimg, i, hDC, content_rect.left, content_rect.top, ILD_NORMAL);
2619 ImageList_Destroy(defimg);
2621 else
2622 BUTTON_DrawImage(infoPtr, hDC, NULL, flags, &content_rect);
2624 SetTextColor(hDC, old_color);
2627 if (cdrf & CDRF_NOTIFYPOSTPAINT)
2629 nmcd.dwDrawStage = CDDS_POSTPAINT;
2630 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2632 if (cdrf & CDRF_SKIPPOSTPAINT) goto cleanup;
2634 if (action == ODA_FOCUS || (state & BST_FOCUS))
2636 InflateRect(&rc, -2, -2);
2637 DrawFocusRect(hDC, &rc);
2640 cleanup:
2641 SelectObject(hDC, old_pen);
2642 SelectObject(hDC, old_brush);
2643 SetBkMode(hDC, old_bk_mode);
2644 SelectClipRgn(hDC, hrgn);
2645 if (hrgn) DeleteObject(hrgn);
2646 DeleteObject(pen);
2650 /**********************************************************************
2651 * Themed Paint Functions
2653 static void PB_ThemedPaint(HTHEME theme, const BUTTON_INFO *infoPtr, HDC hDC, int state, UINT dtFlags, BOOL focused)
2655 RECT bgRect, textRect;
2656 HFONT font = infoPtr->font;
2657 HFONT hPrevFont = font ? SelectObject(hDC, font) : NULL;
2658 NMCUSTOMDRAW nmcd;
2659 LRESULT cdrf;
2660 HWND parent;
2661 WCHAR *text;
2663 GetClientRect(infoPtr->hwnd, &bgRect);
2664 GetThemeBackgroundContentRect(theme, hDC, BP_PUSHBUTTON, state, &bgRect, &textRect);
2665 init_custom_draw(&nmcd, infoPtr, hDC, &bgRect);
2667 parent = GetParent(infoPtr->hwnd);
2668 if (!parent) parent = infoPtr->hwnd;
2670 /* Send erase notifications */
2671 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2672 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2674 if (IsThemeBackgroundPartiallyTransparent(theme, BP_PUSHBUTTON, state))
2675 DrawThemeParentBackground(infoPtr->hwnd, hDC, NULL);
2676 DrawThemeBackground(theme, hDC, BP_PUSHBUTTON, state, &bgRect, NULL);
2678 if (cdrf & CDRF_NOTIFYPOSTERASE)
2680 nmcd.dwDrawStage = CDDS_POSTERASE;
2681 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2684 /* Send paint notifications */
2685 nmcd.dwDrawStage = CDDS_PREPAINT;
2686 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2687 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2689 if (!(cdrf & CDRF_DOERASE) && (text = get_button_text(infoPtr)))
2691 DrawThemeText(theme, hDC, BP_PUSHBUTTON, state, text, lstrlenW(text), dtFlags, 0, &textRect);
2692 heap_free(text);
2695 if (cdrf & CDRF_NOTIFYPOSTPAINT)
2697 nmcd.dwDrawStage = CDDS_POSTPAINT;
2698 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2700 if (cdrf & CDRF_SKIPPOSTPAINT) goto cleanup;
2702 if (focused)
2704 MARGINS margins;
2705 RECT focusRect = bgRect;
2707 GetThemeMargins(theme, hDC, BP_PUSHBUTTON, state, TMT_CONTENTMARGINS, NULL, &margins);
2709 focusRect.left += margins.cxLeftWidth;
2710 focusRect.top += margins.cyTopHeight;
2711 focusRect.right -= margins.cxRightWidth;
2712 focusRect.bottom -= margins.cyBottomHeight;
2714 DrawFocusRect( hDC, &focusRect );
2717 cleanup:
2718 if (hPrevFont) SelectObject(hDC, hPrevFont);
2721 static void CB_ThemedPaint(HTHEME theme, const BUTTON_INFO *infoPtr, HDC hDC, int state, UINT dtFlags, BOOL focused)
2723 SIZE sz;
2724 RECT bgRect, textRect;
2725 HFONT font, hPrevFont = NULL;
2726 DWORD dwStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2727 UINT btn_type = get_button_type( dwStyle );
2728 int part = (btn_type == BS_RADIOBUTTON) || (btn_type == BS_AUTORADIOBUTTON) ? BP_RADIOBUTTON : BP_CHECKBOX;
2729 NMCUSTOMDRAW nmcd;
2730 LRESULT cdrf;
2731 LOGFONTW lf;
2732 HWND parent;
2733 WCHAR *text;
2734 BOOL created_font = FALSE;
2736 HRESULT hr = GetThemeFont(theme, hDC, part, state, TMT_FONT, &lf);
2737 if (SUCCEEDED(hr)) {
2738 font = CreateFontIndirectW(&lf);
2739 if (!font)
2740 TRACE("Failed to create font\n");
2741 else {
2742 TRACE("font = %s\n", debugstr_w(lf.lfFaceName));
2743 hPrevFont = SelectObject(hDC, font);
2744 created_font = TRUE;
2746 } else {
2747 font = (HFONT)SendMessageW(infoPtr->hwnd, WM_GETFONT, 0, 0);
2748 hPrevFont = SelectObject(hDC, font);
2751 if (FAILED(GetThemePartSize(theme, hDC, part, state, NULL, TS_DRAW, &sz)))
2752 sz.cx = sz.cy = 13;
2754 GetClientRect(infoPtr->hwnd, &bgRect);
2755 GetThemeBackgroundContentRect(theme, hDC, part, state, &bgRect, &textRect);
2756 init_custom_draw(&nmcd, infoPtr, hDC, &bgRect);
2758 if (dtFlags & DT_SINGLELINE) /* Center the checkbox / radio button to the text. */
2759 bgRect.top = bgRect.top + (textRect.bottom - textRect.top - sz.cy) / 2;
2761 /* adjust for the check/radio marker */
2762 bgRect.bottom = bgRect.top + sz.cy;
2763 bgRect.right = bgRect.left + sz.cx;
2764 textRect.left = bgRect.right + 6;
2766 parent = GetParent(infoPtr->hwnd);
2767 if (!parent) parent = infoPtr->hwnd;
2769 /* Send erase notifications */
2770 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2771 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2773 DrawThemeParentBackground(infoPtr->hwnd, hDC, NULL);
2774 DrawThemeBackground(theme, hDC, part, state, &bgRect, NULL);
2776 if (cdrf & CDRF_NOTIFYPOSTERASE)
2778 nmcd.dwDrawStage = CDDS_POSTERASE;
2779 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2782 /* Send paint notifications */
2783 nmcd.dwDrawStage = CDDS_PREPAINT;
2784 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2785 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2787 text = get_button_text(infoPtr);
2788 if (!(cdrf & CDRF_DOERASE) && text)
2789 DrawThemeText(theme, hDC, part, state, text, lstrlenW(text), dtFlags, 0, &textRect);
2791 if (cdrf & CDRF_NOTIFYPOSTPAINT)
2793 nmcd.dwDrawStage = CDDS_POSTPAINT;
2794 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2797 if (text)
2799 if (!(cdrf & CDRF_SKIPPOSTPAINT) && focused)
2801 RECT focusRect;
2803 focusRect = textRect;
2805 DrawTextW(hDC, text, lstrlenW(text), &focusRect, dtFlags | DT_CALCRECT);
2807 if (focusRect.right < textRect.right) focusRect.right++;
2808 focusRect.bottom = textRect.bottom;
2810 DrawFocusRect( hDC, &focusRect );
2813 heap_free(text);
2816 cleanup:
2817 if (created_font) DeleteObject(font);
2818 if (hPrevFont) SelectObject(hDC, hPrevFont);
2821 static void GB_ThemedPaint(HTHEME theme, const BUTTON_INFO *infoPtr, HDC hDC, int state, UINT dtFlags, BOOL focused)
2823 RECT bgRect, textRect, contentRect;
2824 WCHAR *text = get_button_text(infoPtr);
2825 LOGFONTW lf;
2826 HFONT font, hPrevFont = NULL;
2827 BOOL created_font = FALSE;
2829 HRESULT hr = GetThemeFont(theme, hDC, BP_GROUPBOX, state, TMT_FONT, &lf);
2830 if (SUCCEEDED(hr)) {
2831 font = CreateFontIndirectW(&lf);
2832 if (!font)
2833 TRACE("Failed to create font\n");
2834 else {
2835 hPrevFont = SelectObject(hDC, font);
2836 created_font = TRUE;
2838 } else {
2839 font = (HFONT)SendMessageW(infoPtr->hwnd, WM_GETFONT, 0, 0);
2840 hPrevFont = SelectObject(hDC, font);
2843 GetClientRect(infoPtr->hwnd, &bgRect);
2844 textRect = bgRect;
2846 if (text)
2848 SIZE textExtent;
2849 GetTextExtentPoint32W(hDC, text, lstrlenW(text), &textExtent);
2850 bgRect.top += (textExtent.cy / 2);
2851 textRect.left += 10;
2852 textRect.bottom = textRect.top + textExtent.cy;
2853 textRect.right = textRect.left + textExtent.cx + 4;
2855 ExcludeClipRect(hDC, textRect.left, textRect.top, textRect.right, textRect.bottom);
2858 GetThemeBackgroundContentRect(theme, hDC, BP_GROUPBOX, state, &bgRect, &contentRect);
2859 ExcludeClipRect(hDC, contentRect.left, contentRect.top, contentRect.right, contentRect.bottom);
2861 if (IsThemeBackgroundPartiallyTransparent(theme, BP_GROUPBOX, state))
2862 DrawThemeParentBackground(infoPtr->hwnd, hDC, NULL);
2863 DrawThemeBackground(theme, hDC, BP_GROUPBOX, state, &bgRect, NULL);
2865 SelectClipRgn(hDC, NULL);
2867 if (text)
2869 InflateRect(&textRect, -2, 0);
2870 DrawThemeText(theme, hDC, BP_GROUPBOX, state, text, lstrlenW(text), 0, 0, &textRect);
2871 heap_free(text);
2874 if (created_font) DeleteObject(font);
2875 if (hPrevFont) SelectObject(hDC, hPrevFont);
2878 static void SB_ThemedPaint(HTHEME theme, const BUTTON_INFO *infoPtr, HDC hDC, int state, UINT dtFlags, BOOL focused)
2880 HFONT old_font = infoPtr->font ? SelectObject(hDC, infoPtr->font) : NULL;
2881 RECT rc, content_rect, push_rect, dropdown_rect;
2882 NMCUSTOMDRAW nmcd;
2883 LRESULT cdrf;
2884 HWND parent;
2886 GetClientRect(infoPtr->hwnd, &rc);
2887 init_custom_draw(&nmcd, infoPtr, hDC, &rc);
2889 parent = GetParent(infoPtr->hwnd);
2890 if (!parent) parent = infoPtr->hwnd;
2892 /* Send erase notifications */
2893 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2894 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2896 if (IsThemeBackgroundPartiallyTransparent(theme, BP_PUSHBUTTON, state))
2897 DrawThemeParentBackground(infoPtr->hwnd, hDC, NULL);
2899 /* The zone outside the content is ignored for the dropdown (draws over) */
2900 GetThemeBackgroundContentRect(theme, hDC, BP_PUSHBUTTON, state, &rc, &content_rect);
2901 get_split_button_rects(infoPtr, &rc, &push_rect, &dropdown_rect);
2903 if (infoPtr->split_style & BCSS_NOSPLIT)
2905 push_rect = rc;
2906 DrawThemeBackground(theme, hDC, BP_PUSHBUTTON, state, &rc, NULL);
2908 else
2910 RECT r = { dropdown_rect.left, content_rect.top, dropdown_rect.right, content_rect.bottom };
2911 UINT edge = (infoPtr->split_style & BCSS_ALIGNLEFT) ? BF_RIGHT : BF_LEFT;
2912 const RECT *clip = NULL;
2914 /* If only the dropdown is pressed, we need to draw it separately */
2915 if (state != PBS_PRESSED && (infoPtr->state & BST_DROPDOWNPUSHED))
2917 DrawThemeBackground(theme, hDC, BP_PUSHBUTTON, PBS_PRESSED, &rc, &dropdown_rect);
2918 clip = &push_rect;
2920 DrawThemeBackground(theme, hDC, BP_PUSHBUTTON, state, &rc, clip);
2922 /* Draw the separator */
2923 DrawThemeEdge(theme, hDC, BP_PUSHBUTTON, state, &r, EDGE_ETCHED, edge, NULL);
2925 /* The content rect should be the content area of the push button */
2926 GetThemeBackgroundContentRect(theme, hDC, BP_PUSHBUTTON, state, &push_rect, &content_rect);
2929 if (cdrf & CDRF_NOTIFYPOSTERASE)
2931 nmcd.dwDrawStage = CDDS_POSTERASE;
2932 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2935 /* Send paint notifications */
2936 nmcd.dwDrawStage = CDDS_PREPAINT;
2937 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2938 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2940 if (!(cdrf & CDRF_DOERASE))
2942 COLORREF old_color, color;
2943 INT old_bk_mode;
2944 WCHAR *text;
2946 if ((text = get_button_text(infoPtr)))
2948 DrawThemeText(theme, hDC, BP_PUSHBUTTON, state, text, lstrlenW(text), dtFlags, 0, &content_rect);
2949 heap_free(text);
2952 GetThemeColor(theme, BP_PUSHBUTTON, state, TMT_TEXTCOLOR, &color);
2953 old_bk_mode = SetBkMode(hDC, TRANSPARENT);
2954 old_color = SetTextColor(hDC, color);
2956 draw_split_button_dropdown_glyph(infoPtr, hDC, &dropdown_rect);
2958 SetTextColor(hDC, old_color);
2959 SetBkMode(hDC, old_bk_mode);
2962 if (cdrf & CDRF_NOTIFYPOSTPAINT)
2964 nmcd.dwDrawStage = CDDS_POSTPAINT;
2965 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2967 if (cdrf & CDRF_SKIPPOSTPAINT) goto cleanup;
2969 if (focused)
2971 MARGINS margins;
2973 GetThemeMargins(theme, hDC, BP_PUSHBUTTON, state, TMT_CONTENTMARGINS, NULL, &margins);
2975 push_rect.left += margins.cxLeftWidth;
2976 push_rect.top += margins.cyTopHeight;
2977 push_rect.right -= margins.cxRightWidth;
2978 push_rect.bottom -= margins.cyBottomHeight;
2979 DrawFocusRect(hDC, &push_rect);
2982 cleanup:
2983 if (old_font) SelectObject(hDC, old_font);
2986 static void CL_ThemedPaint(HTHEME theme, const BUTTON_INFO *infoPtr, HDC hDC, int state, UINT dtFlags, BOOL focused)
2988 HFONT old_font = infoPtr->font ? SelectObject(hDC, infoPtr->font) : NULL;
2989 NMCUSTOMDRAW nmcd;
2990 LRESULT cdrf;
2991 HWND parent;
2992 RECT rc;
2994 GetClientRect(infoPtr->hwnd, &rc);
2995 init_custom_draw(&nmcd, infoPtr, hDC, &rc);
2997 parent = GetParent(infoPtr->hwnd);
2998 if (!parent) parent = infoPtr->hwnd;
3000 /* Send erase notifications */
3001 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
3002 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
3004 if (IsThemeBackgroundPartiallyTransparent(theme, BP_COMMANDLINK, state))
3005 DrawThemeParentBackground(infoPtr->hwnd, hDC, NULL);
3006 DrawThemeBackground(theme, hDC, BP_COMMANDLINK, state, &rc, NULL);
3008 if (cdrf & CDRF_NOTIFYPOSTERASE)
3010 nmcd.dwDrawStage = CDDS_POSTERASE;
3011 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
3014 /* Send paint notifications */
3015 nmcd.dwDrawStage = CDDS_PREPAINT;
3016 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
3017 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
3019 if (!(cdrf & CDRF_DOERASE))
3021 RECT r, img_rect;
3022 UINT txt_h = 0;
3023 SIZE img_size;
3024 WCHAR *text;
3026 GetThemeBackgroundContentRect(theme, hDC, BP_COMMANDLINK, state, &rc, &r);
3028 /* The text alignment and styles are fixed and don't depend on button styles */
3029 dtFlags = DT_TOP | DT_LEFT | DT_WORDBREAK;
3031 /* Command Links ignore the margins of the image list or its alignment */
3032 if (infoPtr->u.image || infoPtr->imagelist.himl)
3033 img_size = BUTTON_GetImageSize(infoPtr);
3034 else
3035 GetThemePartSize(theme, NULL, BP_COMMANDLINKGLYPH, state, NULL, TS_DRAW, &img_size);
3037 img_rect = r;
3038 if (img_size.cx) r.left += img_size.cx + command_link_margin;
3040 /* Draw the text */
3041 if ((text = get_button_text(infoPtr)))
3043 UINT len = lstrlenW(text);
3044 RECT text_rect;
3046 GetThemeTextExtent(theme, hDC, BP_COMMANDLINK, state, text, len,
3047 dtFlags | DT_END_ELLIPSIS, &r, &text_rect);
3048 DrawThemeText(theme, hDC, BP_COMMANDLINK, state, text, len,
3049 dtFlags | DT_END_ELLIPSIS, 0, &r);
3051 txt_h = text_rect.bottom - text_rect.top;
3052 heap_free(text);
3055 /* Draw the note */
3056 if (infoPtr->note)
3058 DTTOPTS opts;
3060 r.top += txt_h;
3061 opts.dwSize = sizeof(opts);
3062 opts.dwFlags = DTT_FONTPROP;
3063 opts.iFontPropId = TMT_BODYFONT;
3064 DrawThemeTextEx(theme, hDC, BP_COMMANDLINK, state,
3065 infoPtr->note, infoPtr->note_length,
3066 dtFlags | DT_NOPREFIX, &r, &opts);
3069 /* Position the image at the vertical center of the drawn text (not note) */
3070 txt_h = min(txt_h, img_rect.bottom - img_rect.top);
3071 if (img_size.cy < txt_h) img_rect.top += (txt_h - img_size.cy) / 2;
3073 img_rect.right = img_rect.left + img_size.cx;
3074 img_rect.bottom = img_rect.top + img_size.cy;
3076 if (infoPtr->u.image || infoPtr->imagelist.himl)
3077 BUTTON_DrawImage(infoPtr, hDC, NULL,
3078 (state == CMDLS_DISABLED) ? DSS_DISABLED : DSS_NORMAL,
3079 &img_rect);
3080 else
3081 DrawThemeBackground(theme, hDC, BP_COMMANDLINKGLYPH, state, &img_rect, NULL);
3084 if (cdrf & CDRF_NOTIFYPOSTPAINT)
3086 nmcd.dwDrawStage = CDDS_POSTPAINT;
3087 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
3089 if (cdrf & CDRF_SKIPPOSTPAINT) goto cleanup;
3091 if (focused)
3093 MARGINS margins;
3095 /* The focus rect has margins of a push button rather than command link... */
3096 GetThemeMargins(theme, hDC, BP_PUSHBUTTON, state, TMT_CONTENTMARGINS, NULL, &margins);
3098 rc.left += margins.cxLeftWidth;
3099 rc.top += margins.cyTopHeight;
3100 rc.right -= margins.cxRightWidth;
3101 rc.bottom -= margins.cyBottomHeight;
3102 DrawFocusRect(hDC, &rc);
3105 cleanup:
3106 if (old_font) SelectObject(hDC, old_font);
3109 void BUTTON_Register(void)
3111 WNDCLASSW wndClass;
3113 memset(&wndClass, 0, sizeof(wndClass));
3114 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS | CS_VREDRAW | CS_HREDRAW | CS_PARENTDC;
3115 wndClass.lpfnWndProc = BUTTON_WindowProc;
3116 wndClass.cbClsExtra = 0;
3117 wndClass.cbWndExtra = sizeof(BUTTON_INFO *);
3118 wndClass.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW);
3119 wndClass.hbrBackground = NULL;
3120 wndClass.lpszClassName = WC_BUTTONW;
3121 RegisterClassW(&wndClass);