dinput: Factor out IDirectInputDevice ansi vtable.
[wine.git] / dlls / comctl32 / button.c
blobd3dcc67b169318bb2e90503ee1be71eb8a40ede5
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 InvalidateRect( hWnd, NULL, TRUE );
538 break;
540 case WM_ERASEBKGND:
541 if (btn_type == BS_OWNERDRAW)
543 HDC hdc = (HDC)wParam;
544 RECT rc;
545 HBRUSH hBrush;
546 HWND parent = GetParent(hWnd);
547 if (!parent) parent = hWnd;
548 hBrush = (HBRUSH)SendMessageW(parent, WM_CTLCOLORBTN, (WPARAM)hdc, (LPARAM)hWnd);
549 if (!hBrush) /* did the app forget to call defwindowproc ? */
550 hBrush = (HBRUSH)DefWindowProcW(parent, WM_CTLCOLORBTN,
551 (WPARAM)hdc, (LPARAM)hWnd);
552 GetClientRect(hWnd, &rc);
553 FillRect(hdc, &rc, hBrush);
555 return 1;
557 case WM_PRINTCLIENT:
558 case WM_PAINT:
560 PAINTSTRUCT ps;
561 HDC hdc;
563 theme = GetWindowTheme( hWnd );
564 hdc = wParam ? (HDC)wParam : BeginPaint( hWnd, &ps );
566 if (is_themed_paint_supported(theme, btn_type))
568 int drawState = get_draw_state(infoPtr);
569 UINT dtflags = BUTTON_BStoDT(style, GetWindowLongW(hWnd, GWL_EXSTYLE));
571 btnThemedPaintFunc[btn_type](theme, infoPtr, hdc, drawState, dtflags, infoPtr->state & BST_FOCUS);
573 else if (btnPaintFunc[btn_type])
575 int nOldMode = SetBkMode( hdc, OPAQUE );
576 btnPaintFunc[btn_type]( infoPtr, hdc, ODA_DRAWENTIRE );
577 SetBkMode(hdc, nOldMode); /* reset painting mode */
580 if ( !wParam ) EndPaint( hWnd, &ps );
581 break;
584 case WM_KEYDOWN:
585 if (wParam == VK_SPACE)
587 SendMessageW( hWnd, BM_SETSTATE, TRUE, 0 );
588 infoPtr->state |= BUTTON_BTNPRESSED;
589 SetCapture( hWnd );
591 else if (wParam == VK_UP || wParam == VK_DOWN)
593 /* Up and down arrows work on every button, and even with BCSS_NOSPLIT */
594 notify_split_button_dropdown(infoPtr, NULL, hWnd);
596 break;
598 case WM_LBUTTONDBLCLK:
599 if(style & BS_NOTIFY ||
600 btn_type == BS_RADIOBUTTON ||
601 btn_type == BS_USERBUTTON ||
602 btn_type == BS_OWNERDRAW)
604 BUTTON_NOTIFY_PARENT(hWnd, BN_DOUBLECLICKED);
605 break;
607 /* fall through */
608 case WM_LBUTTONDOWN:
609 SetFocus( hWnd );
611 if ((btn_type == BS_SPLITBUTTON || btn_type == BS_DEFSPLITBUTTON) &&
612 !(infoPtr->split_style & BCSS_NOSPLIT) &&
613 notify_split_button_dropdown(infoPtr, &pt, hWnd))
614 break;
616 SetCapture( hWnd );
617 infoPtr->state |= BUTTON_BTNPRESSED;
618 SendMessageW( hWnd, BM_SETSTATE, TRUE, 0 );
619 break;
621 case WM_KEYUP:
622 if (wParam != VK_SPACE)
623 break;
624 /* fall through */
625 case WM_LBUTTONUP:
626 state = infoPtr->state;
627 if (state & BST_DROPDOWNPUSHED)
628 SendMessageW(hWnd, BCM_SETDROPDOWNSTATE, FALSE, 0);
629 if (!(state & BUTTON_BTNPRESSED)) break;
630 infoPtr->state &= BUTTON_NSTATES | BST_HOT;
631 if (!(state & BST_PUSHED))
633 ReleaseCapture();
634 break;
636 SendMessageW( hWnd, BM_SETSTATE, FALSE, 0 );
637 GetClientRect( hWnd, &rect );
638 if (uMsg == WM_KEYUP || PtInRect( &rect, pt ))
640 switch(btn_type)
642 case BS_AUTOCHECKBOX:
643 SendMessageW( hWnd, BM_SETCHECK, !(infoPtr->state & BST_CHECKED), 0 );
644 break;
645 case BS_AUTORADIOBUTTON:
646 SendMessageW( hWnd, BM_SETCHECK, TRUE, 0 );
647 break;
648 case BS_AUTO3STATE:
649 SendMessageW( hWnd, BM_SETCHECK, (infoPtr->state & BST_INDETERMINATE) ? 0 :
650 ((infoPtr->state & 3) + 1), 0 );
651 break;
653 ReleaseCapture();
654 BUTTON_NOTIFY_PARENT(hWnd, BN_CLICKED);
656 else
658 ReleaseCapture();
660 break;
662 case WM_CAPTURECHANGED:
663 TRACE("WM_CAPTURECHANGED %p\n", hWnd);
664 if (hWnd == (HWND)lParam) break;
665 if (infoPtr->state & BUTTON_BTNPRESSED)
667 infoPtr->state &= BUTTON_NSTATES;
668 if (infoPtr->state & BST_PUSHED)
669 SendMessageW( hWnd, BM_SETSTATE, FALSE, 0 );
671 break;
673 case WM_MOUSEMOVE:
675 TRACKMOUSEEVENT mouse_event;
677 mouse_event.cbSize = sizeof(TRACKMOUSEEVENT);
678 mouse_event.dwFlags = TME_QUERY;
679 if (!TrackMouseEvent(&mouse_event) || !(mouse_event.dwFlags & (TME_HOVER | TME_LEAVE)))
681 mouse_event.dwFlags = TME_HOVER | TME_LEAVE;
682 mouse_event.hwndTrack = hWnd;
683 mouse_event.dwHoverTime = 1;
684 TrackMouseEvent(&mouse_event);
687 if ((wParam & MK_LBUTTON) && GetCapture() == hWnd)
689 GetClientRect( hWnd, &rect );
690 SendMessageW( hWnd, BM_SETSTATE, PtInRect(&rect, pt), 0 );
692 break;
695 case WM_MOUSEHOVER:
697 infoPtr->state |= BST_HOT;
698 InvalidateRect( hWnd, NULL, FALSE );
699 break;
702 case WM_MOUSELEAVE:
704 infoPtr->state &= ~BST_HOT;
705 InvalidateRect( hWnd, NULL, FALSE );
706 break;
709 case WM_SETTEXT:
711 /* Clear an old text here as Windows does */
712 if (IsWindowVisible(hWnd))
714 HDC hdc = GetDC(hWnd);
715 HBRUSH hbrush;
716 RECT client, rc;
717 HWND parent = GetParent(hWnd);
718 UINT message = (btn_type == BS_PUSHBUTTON ||
719 btn_type == BS_DEFPUSHBUTTON ||
720 btn_type == BS_USERBUTTON ||
721 btn_type == BS_OWNERDRAW) ?
722 WM_CTLCOLORBTN : WM_CTLCOLORSTATIC;
724 if (!parent) parent = hWnd;
725 hbrush = (HBRUSH)SendMessageW(parent, message,
726 (WPARAM)hdc, (LPARAM)hWnd);
727 if (!hbrush) /* did the app forget to call DefWindowProc ? */
728 hbrush = (HBRUSH)DefWindowProcW(parent, message,
729 (WPARAM)hdc, (LPARAM)hWnd);
731 GetClientRect(hWnd, &client);
732 rc = client;
733 /* FIXME: check other BS_* handlers */
734 if (btn_type == BS_GROUPBOX)
735 InflateRect(&rc, -7, 1); /* GB_Paint does this */
736 BUTTON_CalcLayoutRects(infoPtr, hdc, &rc, NULL, NULL);
737 /* Clip by client rect bounds */
738 if (rc.right > client.right) rc.right = client.right;
739 if (rc.bottom > client.bottom) rc.bottom = client.bottom;
740 FillRect(hdc, &rc, hbrush);
741 ReleaseDC(hWnd, hdc);
744 DefWindowProcW( hWnd, WM_SETTEXT, wParam, lParam );
745 if (btn_type == BS_GROUPBOX) /* Yes, only for BS_GROUPBOX */
746 InvalidateRect( hWnd, NULL, TRUE );
747 else if (GetWindowTheme( hWnd ))
748 RedrawWindow( hWnd, NULL, NULL, RDW_INVALIDATE | RDW_UPDATENOW );
749 else
750 paint_button( infoPtr, btn_type, ODA_DRAWENTIRE );
751 return 1; /* success. FIXME: check text length */
754 case BCM_SETNOTE:
756 WCHAR *note = (WCHAR *)lParam;
757 if (btn_type != BS_COMMANDLINK && btn_type != BS_DEFCOMMANDLINK)
759 SetLastError(ERROR_NOT_SUPPORTED);
760 return FALSE;
763 heap_free(infoPtr->note);
764 if (note)
766 infoPtr->note_length = lstrlenW(note);
767 infoPtr->note = heap_strndupW(note, infoPtr->note_length);
770 if (!note || !infoPtr->note)
772 infoPtr->note_length = 0;
773 infoPtr->note = heap_alloc_zero(sizeof(WCHAR));
776 SetLastError(NO_ERROR);
777 return TRUE;
780 case BCM_GETNOTE:
782 DWORD *size = (DWORD *)wParam;
783 WCHAR *buffer = (WCHAR *)lParam;
784 INT length = 0;
786 if (btn_type != BS_COMMANDLINK && btn_type != BS_DEFCOMMANDLINK)
788 SetLastError(ERROR_NOT_SUPPORTED);
789 return FALSE;
792 if (!buffer || !size || !infoPtr->note)
794 SetLastError(ERROR_INVALID_PARAMETER);
795 return FALSE;
798 if (*size > 0)
800 length = min(*size - 1, infoPtr->note_length);
801 memcpy(buffer, infoPtr->note, length * sizeof(WCHAR));
802 buffer[length] = '\0';
805 if (*size < infoPtr->note_length + 1)
807 *size = infoPtr->note_length + 1;
808 SetLastError(ERROR_INSUFFICIENT_BUFFER);
809 return FALSE;
811 else
813 SetLastError(NO_ERROR);
814 return TRUE;
818 case BCM_GETNOTELENGTH:
820 if (btn_type != BS_COMMANDLINK && btn_type != BS_DEFCOMMANDLINK)
822 SetLastError(ERROR_NOT_SUPPORTED);
823 return 0;
826 return infoPtr->note_length;
829 case WM_SETFONT:
830 infoPtr->font = (HFONT)wParam;
831 if (lParam) InvalidateRect(hWnd, NULL, TRUE);
832 break;
834 case WM_GETFONT:
835 return (LRESULT)infoPtr->font;
837 case WM_SETFOCUS:
838 TRACE("WM_SETFOCUS %p\n",hWnd);
839 infoPtr->state |= BST_FOCUS;
841 if (btn_type == BS_OWNERDRAW)
842 paint_button( infoPtr, btn_type, ODA_FOCUS );
843 else
844 InvalidateRect(hWnd, NULL, FALSE);
846 if (style & BS_NOTIFY)
847 BUTTON_NOTIFY_PARENT(hWnd, BN_SETFOCUS);
848 break;
850 case WM_KILLFOCUS:
851 TRACE("WM_KILLFOCUS %p\n",hWnd);
852 infoPtr->state &= ~BST_FOCUS;
854 if ((infoPtr->state & BUTTON_BTNPRESSED) && GetCapture() == hWnd)
855 ReleaseCapture();
856 if (style & BS_NOTIFY)
857 BUTTON_NOTIFY_PARENT(hWnd, BN_KILLFOCUS);
859 InvalidateRect( hWnd, NULL, FALSE );
860 break;
862 case WM_SYSCOLORCHANGE:
863 InvalidateRect( hWnd, NULL, FALSE );
864 break;
866 case BM_SETSTYLE:
868 DWORD new_btn_type;
870 new_btn_type= wParam & BS_TYPEMASK;
871 if (btn_type >= BS_SPLITBUTTON && new_btn_type <= BS_DEFPUSHBUTTON)
872 new_btn_type = (btn_type & ~BS_DEFPUSHBUTTON) | new_btn_type;
874 style = (style & ~BS_TYPEMASK) | new_btn_type;
875 SetWindowLongW( hWnd, GWL_STYLE, style );
877 /* Only redraw if lParam flag is set.*/
878 if (lParam)
879 InvalidateRect( hWnd, NULL, TRUE );
881 break;
883 case BM_CLICK:
884 SendMessageW( hWnd, WM_LBUTTONDOWN, 0, 0 );
885 SendMessageW( hWnd, WM_LBUTTONUP, 0, 0 );
886 break;
888 case BM_SETIMAGE:
889 infoPtr->image_type = (DWORD)wParam;
890 oldHbitmap = infoPtr->image;
891 infoPtr->u.image = CopyImage((HANDLE)lParam, infoPtr->image_type, 0, 0, 0);
892 infoPtr->image = (HANDLE)lParam;
893 InvalidateRect( hWnd, NULL, FALSE );
894 return (LRESULT)oldHbitmap;
896 case BM_GETIMAGE:
897 return (LRESULT)infoPtr->image;
899 case BCM_SETIMAGELIST:
901 BUTTON_IMAGELIST *imagelist = (BUTTON_IMAGELIST *)lParam;
903 if (!imagelist) return FALSE;
905 infoPtr->imagelist = *imagelist;
906 return TRUE;
909 case BCM_GETIMAGELIST:
911 BUTTON_IMAGELIST *imagelist = (BUTTON_IMAGELIST *)lParam;
913 if (!imagelist) return FALSE;
915 *imagelist = infoPtr->imagelist;
916 return TRUE;
919 case BCM_SETSPLITINFO:
921 BUTTON_SPLITINFO *info = (BUTTON_SPLITINFO*)lParam;
923 if (!info) return TRUE;
925 if (info->mask & (BCSIF_GLYPH | BCSIF_IMAGE))
927 infoPtr->split_style &= ~BCSS_IMAGE;
928 if (!(info->mask & BCSIF_GLYPH))
929 infoPtr->split_style |= BCSS_IMAGE;
930 infoPtr->glyph = info->himlGlyph;
931 infoPtr->glyph_size.cx = infoPtr->glyph_size.cy = 0;
934 if (info->mask & BCSIF_STYLE)
935 infoPtr->split_style = info->uSplitStyle;
936 if (info->mask & BCSIF_SIZE)
937 infoPtr->glyph_size = info->size;
939 /* Calculate fitting value for cx if invalid (cy is untouched) */
940 if (infoPtr->glyph_size.cx <= 0)
941 infoPtr->glyph_size.cx = get_default_glyph_size(infoPtr);
943 /* Windows doesn't invalidate or redraw it, so we don't, either */
944 return TRUE;
947 case BCM_GETSPLITINFO:
949 BUTTON_SPLITINFO *info = (BUTTON_SPLITINFO*)lParam;
951 if (!info) return FALSE;
953 if (info->mask & BCSIF_STYLE)
954 info->uSplitStyle = infoPtr->split_style;
955 if (info->mask & (BCSIF_GLYPH | BCSIF_IMAGE))
956 info->himlGlyph = infoPtr->glyph;
957 if (info->mask & BCSIF_SIZE)
958 info->size = infoPtr->glyph_size;
960 return TRUE;
963 case BM_GETCHECK:
964 return infoPtr->state & 3;
966 case BM_SETCHECK:
967 if (wParam > maxCheckState[btn_type]) wParam = maxCheckState[btn_type];
968 if ((btn_type == BS_RADIOBUTTON) || (btn_type == BS_AUTORADIOBUTTON))
970 style = wParam ? style | WS_TABSTOP : style & ~WS_TABSTOP;
971 SetWindowLongW( hWnd, GWL_STYLE, style );
973 if ((infoPtr->state & 3) != wParam)
975 infoPtr->state = (infoPtr->state & ~3) | wParam;
976 InvalidateRect( hWnd, NULL, FALSE );
978 if ((btn_type == BS_AUTORADIOBUTTON) && (wParam == BST_CHECKED) && (style & WS_CHILD))
979 BUTTON_CheckAutoRadioButton( hWnd );
980 break;
982 case BM_GETSTATE:
983 return infoPtr->state;
985 case BM_SETSTATE:
986 state = infoPtr->state;
987 new_state = wParam ? BST_PUSHED : 0;
989 if ((state ^ new_state) & BST_PUSHED)
991 if (wParam)
992 state |= BST_PUSHED;
993 else
994 state &= ~BST_PUSHED;
996 if (btn_type == BS_USERBUTTON)
997 BUTTON_NOTIFY_PARENT( hWnd, (state & BST_PUSHED) ? BN_HILITE : BN_UNHILITE );
998 infoPtr->state = state;
1000 InvalidateRect( hWnd, NULL, FALSE );
1002 break;
1004 case BCM_SETDROPDOWNSTATE:
1005 new_state = wParam ? BST_DROPDOWNPUSHED : 0;
1007 if ((infoPtr->state ^ new_state) & BST_DROPDOWNPUSHED)
1009 infoPtr->state &= ~BST_DROPDOWNPUSHED;
1010 infoPtr->state |= new_state;
1011 InvalidateRect(hWnd, NULL, FALSE);
1013 break;
1015 case BCM_SETTEXTMARGIN:
1017 RECT *text_margin = (RECT *)lParam;
1019 if (!text_margin) return FALSE;
1021 infoPtr->text_margin = *text_margin;
1022 return TRUE;
1025 case BCM_GETTEXTMARGIN:
1027 RECT *text_margin = (RECT *)lParam;
1029 if (!text_margin) return FALSE;
1031 *text_margin = infoPtr->text_margin;
1032 return TRUE;
1035 case BCM_GETIDEALSIZE:
1037 SIZE *size = (SIZE *)lParam;
1039 if (!size) return FALSE;
1041 return btnGetIdealSizeFunc[btn_type](infoPtr, size);
1044 case WM_NCHITTEST:
1045 if(btn_type == BS_GROUPBOX) return HTTRANSPARENT;
1046 /* fall through */
1047 default:
1048 return DefWindowProcW(hWnd, uMsg, wParam, lParam);
1050 return 0;
1053 /* If maxWidth is zero, rectangle width is unlimited */
1054 static RECT BUTTON_GetTextRect(const BUTTON_INFO *infoPtr, HDC hdc, const WCHAR *text, LONG maxWidth)
1056 LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1057 LONG exStyle = GetWindowLongW(infoPtr->hwnd, GWL_EXSTYLE);
1058 UINT dtStyle = BUTTON_BStoDT(style, exStyle);
1059 HFONT hPrevFont;
1060 RECT rect = {0};
1062 rect.right = maxWidth;
1063 hPrevFont = SelectObject(hdc, infoPtr->font);
1064 /* Calculate height without DT_VCENTER and DT_BOTTOM to get the correct height */
1065 DrawTextW(hdc, text, -1, &rect, (dtStyle & ~(DT_VCENTER | DT_BOTTOM)) | DT_CALCRECT);
1066 if (hPrevFont) SelectObject(hdc, hPrevFont);
1068 return rect;
1071 static BOOL show_image_only(const BUTTON_INFO *infoPtr)
1073 LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1074 return (style & (BS_ICON | BS_BITMAP)) && (infoPtr->u.image || infoPtr->imagelist.himl);
1077 static BOOL show_image_and_text(const BUTTON_INFO *infoPtr)
1079 LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1080 UINT type = get_button_type(style);
1081 return !(style & (BS_ICON | BS_BITMAP))
1082 && ((infoPtr->u.image
1083 && (type == BS_PUSHBUTTON || type == BS_DEFPUSHBUTTON || type == BS_USERBUTTON || type == BS_SPLITBUTTON
1084 || type == BS_DEFSPLITBUTTON || type == BS_COMMANDLINK || type == BS_DEFCOMMANDLINK))
1085 || (infoPtr->imagelist.himl && type != BS_GROUPBOX));
1088 static BOOL show_image(const BUTTON_INFO *infoPtr)
1090 return show_image_only(infoPtr) || show_image_and_text(infoPtr);
1093 /* Get a bounding rectangle that is large enough to contain a image and a text side by side.
1094 * Note: (left,top) of the result rectangle may not be (0,0), offset it by yourself if needed */
1095 static RECT BUTTON_GetBoundingLabelRect(LONG style, const RECT *textRect, const RECT *imageRect)
1097 RECT labelRect;
1098 RECT rect = *imageRect;
1099 INT textWidth = textRect->right - textRect->left;
1100 INT textHeight = textRect->bottom - textRect->top;
1101 INT imageWidth = imageRect->right - imageRect->left;
1102 INT imageHeight = imageRect->bottom - imageRect->top;
1104 if ((style & BS_CENTER) == BS_RIGHT)
1105 OffsetRect(&rect, textWidth, 0);
1106 else if ((style & BS_CENTER) == BS_LEFT)
1107 OffsetRect(&rect, -imageWidth, 0);
1108 else if ((style & BS_VCENTER) == BS_BOTTOM)
1109 OffsetRect(&rect, 0, textHeight);
1110 else if ((style & BS_VCENTER) == BS_TOP)
1111 OffsetRect(&rect, 0, -imageHeight);
1112 else
1113 OffsetRect(&rect, -imageWidth, 0);
1115 UnionRect(&labelRect, textRect, &rect);
1116 return labelRect;
1119 /* Position a rectangle inside a bounding rectangle according to button alignment flags */
1120 static void BUTTON_PositionRect(LONG style, const RECT *outerRect, RECT *innerRect, const RECT *margin)
1122 INT width = innerRect->right - innerRect->left;
1123 INT height = innerRect->bottom - innerRect->top;
1125 if ((style & WS_EX_RIGHT) && !(style & BS_CENTER)) style |= BS_CENTER;
1127 if (!(style & BS_CENTER))
1129 if (button_centers_text(style))
1130 style |= BS_CENTER;
1131 else
1132 style |= BS_LEFT;
1135 if (!(style & BS_VCENTER))
1137 /* Group box's text is top aligned by default */
1138 if (get_button_type(style) == BS_GROUPBOX)
1139 style |= BS_TOP;
1142 switch (style & BS_CENTER)
1144 case BS_CENTER:
1145 innerRect->left = outerRect->left + (outerRect->right - outerRect->left - width) / 2;
1146 innerRect->right = innerRect->left + width;
1147 break;
1148 case BS_RIGHT:
1149 innerRect->right = outerRect->right - margin->right;
1150 innerRect->left = innerRect->right - width;
1151 break;
1152 case BS_LEFT:
1153 default:
1154 innerRect->left = outerRect->left + margin->left;
1155 innerRect->right = innerRect->left + width;
1156 break;
1159 switch (style & BS_VCENTER)
1161 case BS_TOP:
1162 innerRect->top = outerRect->top + margin->top;
1163 innerRect->bottom = innerRect->top + height;
1164 break;
1165 case BS_BOTTOM:
1166 innerRect->bottom = outerRect->bottom - margin->bottom;
1167 innerRect->top = innerRect->bottom - height;
1168 break;
1169 case BS_VCENTER:
1170 default:
1171 innerRect->top = outerRect->top + (outerRect->bottom - outerRect->top - height) / 2;
1172 innerRect->bottom = innerRect->top + height;
1173 break;
1177 /* Convert imagelist align style to button align style */
1178 static UINT BUTTON_ILStoBS(UINT align)
1180 switch (align)
1182 case BUTTON_IMAGELIST_ALIGN_TOP:
1183 return BS_CENTER | BS_TOP;
1184 case BUTTON_IMAGELIST_ALIGN_BOTTOM:
1185 return BS_CENTER | BS_BOTTOM;
1186 case BUTTON_IMAGELIST_ALIGN_CENTER:
1187 return BS_CENTER | BS_VCENTER;
1188 case BUTTON_IMAGELIST_ALIGN_RIGHT:
1189 return BS_RIGHT | BS_VCENTER;
1190 case BUTTON_IMAGELIST_ALIGN_LEFT:
1191 default:
1192 return BS_LEFT | BS_VCENTER;
1196 static SIZE BUTTON_GetImageSize(const BUTTON_INFO *infoPtr)
1198 ICONINFO iconInfo;
1199 BITMAP bm = {0};
1200 SIZE size = {0};
1202 /* ImageList has priority over image */
1203 if (infoPtr->imagelist.himl)
1204 ImageList_GetIconSize(infoPtr->imagelist.himl, &size.cx, &size.cy);
1205 else if (infoPtr->u.image)
1207 if (infoPtr->image_type == IMAGE_ICON)
1209 GetIconInfo(infoPtr->u.icon, &iconInfo);
1210 GetObjectW(iconInfo.hbmColor, sizeof(bm), &bm);
1211 DeleteObject(iconInfo.hbmColor);
1212 DeleteObject(iconInfo.hbmMask);
1214 else if (infoPtr->image_type == IMAGE_BITMAP)
1215 GetObjectW(infoPtr->u.bitmap, sizeof(bm), &bm);
1217 size.cx = bm.bmWidth;
1218 size.cy = bm.bmHeight;
1221 return size;
1224 static const RECT *BUTTON_GetTextMargin(const BUTTON_INFO *infoPtr)
1226 static const RECT oneMargin = {1, 1, 1, 1};
1228 /* Use text margin only when showing both image and text, and image is not imagelist */
1229 if (show_image_and_text(infoPtr) && !infoPtr->imagelist.himl)
1230 return &infoPtr->text_margin;
1231 else
1232 return &oneMargin;
1235 static void BUTTON_GetClientRectSize(BUTTON_INFO *infoPtr, SIZE *size)
1237 RECT rect;
1238 GetClientRect(infoPtr->hwnd, &rect);
1239 size->cx = rect.right - rect.left;
1240 size->cy = rect.bottom - rect.top;
1243 static void BUTTON_GetTextIdealSize(BUTTON_INFO *infoPtr, LONG maxWidth, SIZE *size)
1245 WCHAR *text = get_button_text(infoPtr);
1246 HDC hdc;
1247 RECT rect;
1248 const RECT *margin = BUTTON_GetTextMargin(infoPtr);
1250 if (maxWidth != 0)
1252 maxWidth -= margin->right + margin->right;
1253 if (maxWidth <= 0) maxWidth = 1;
1256 hdc = GetDC(infoPtr->hwnd);
1257 rect = BUTTON_GetTextRect(infoPtr, hdc, text, maxWidth);
1258 ReleaseDC(infoPtr->hwnd, hdc);
1259 heap_free(text);
1261 size->cx = rect.right - rect.left + margin->left + margin->right;
1262 size->cy = rect.bottom - rect.top + margin->top + margin->bottom;
1265 static void BUTTON_GetLabelIdealSize(BUTTON_INFO *infoPtr, LONG maxWidth, SIZE *size)
1267 LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1268 SIZE imageSize;
1269 SIZE textSize;
1270 BOOL horizontal;
1272 imageSize = BUTTON_GetImageSize(infoPtr);
1273 if (infoPtr->imagelist.himl)
1275 imageSize.cx += infoPtr->imagelist.margin.left + infoPtr->imagelist.margin.right;
1276 imageSize.cy += infoPtr->imagelist.margin.top + infoPtr->imagelist.margin.bottom;
1277 if (infoPtr->imagelist.uAlign == BUTTON_IMAGELIST_ALIGN_TOP
1278 || infoPtr->imagelist.uAlign == BUTTON_IMAGELIST_ALIGN_BOTTOM)
1279 horizontal = FALSE;
1280 else
1281 horizontal = TRUE;
1283 else
1285 /* horizontal alignment flags has priority over vertical ones if both are specified */
1286 if (!(style & (BS_CENTER | BS_VCENTER)) || ((style & BS_CENTER) && (style & BS_CENTER) != BS_CENTER)
1287 || !(style & BS_VCENTER) || (style & BS_VCENTER) == BS_VCENTER)
1288 horizontal = TRUE;
1289 else
1290 horizontal = FALSE;
1293 if (horizontal)
1295 if (maxWidth != 0)
1297 maxWidth -= imageSize.cx;
1298 if (maxWidth <= 0) maxWidth = 1;
1300 BUTTON_GetTextIdealSize(infoPtr, maxWidth, &textSize);
1301 size->cx = textSize.cx + imageSize.cx;
1302 size->cy = max(textSize.cy, imageSize.cy);
1304 else
1306 BUTTON_GetTextIdealSize(infoPtr, maxWidth, &textSize);
1307 size->cx = max(textSize.cx, imageSize.cx);
1308 size->cy = textSize.cy + imageSize.cy;
1312 static BOOL GB_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size)
1314 BUTTON_GetClientRectSize(infoPtr, size);
1315 return TRUE;
1318 static BOOL CB_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size)
1320 LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1321 HDC hdc;
1322 HFONT hfont;
1323 SIZE labelSize;
1324 INT textOffset;
1325 double scaleX;
1326 double scaleY;
1327 LONG checkboxWidth, checkboxHeight;
1328 LONG maxWidth = 0;
1330 if (SendMessageW(infoPtr->hwnd, WM_GETTEXTLENGTH, 0, 0) == 0)
1332 BUTTON_GetClientRectSize(infoPtr, size);
1333 return TRUE;
1336 hdc = GetDC(infoPtr->hwnd);
1337 scaleX = GetDeviceCaps(hdc, LOGPIXELSX) / 96.0;
1338 scaleY = GetDeviceCaps(hdc, LOGPIXELSY) / 96.0;
1339 if ((hfont = infoPtr->font)) SelectObject(hdc, hfont);
1340 GetCharWidthW(hdc, '0', '0', &textOffset);
1341 textOffset /= 2;
1342 ReleaseDC(infoPtr->hwnd, hdc);
1344 checkboxWidth = 12 * scaleX + 1;
1345 checkboxHeight = 12 * scaleY + 1;
1346 if (size->cx)
1348 maxWidth = size->cx - checkboxWidth - textOffset;
1349 if (maxWidth <= 0) maxWidth = 1;
1352 /* Checkbox doesn't support both image(but not image list) and text */
1353 if (!(style & (BS_ICON | BS_BITMAP)) && infoPtr->u.image)
1354 BUTTON_GetTextIdealSize(infoPtr, maxWidth, &labelSize);
1355 else
1356 BUTTON_GetLabelIdealSize(infoPtr, maxWidth, &labelSize);
1358 size->cx = labelSize.cx + checkboxWidth + textOffset;
1359 size->cy = max(labelSize.cy, checkboxHeight);
1361 return TRUE;
1364 static BOOL PB_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size)
1366 SIZE labelSize;
1368 if (SendMessageW(infoPtr->hwnd, WM_GETTEXTLENGTH, 0, 0) == 0)
1369 BUTTON_GetClientRectSize(infoPtr, size);
1370 else
1372 /* Ideal size include text size even if image only flags(BS_ICON, BS_BITMAP) are specified */
1373 BUTTON_GetLabelIdealSize(infoPtr, size->cx, &labelSize);
1375 size->cx = labelSize.cx;
1376 size->cy = labelSize.cy;
1378 return TRUE;
1381 static BOOL SB_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size)
1383 LONG extra_width = infoPtr->glyph_size.cx * 2 + GetSystemMetrics(SM_CXEDGE);
1384 SIZE label_size;
1386 if (SendMessageW(infoPtr->hwnd, WM_GETTEXTLENGTH, 0, 0) == 0)
1388 BUTTON_GetClientRectSize(infoPtr, size);
1389 size->cx = max(size->cx, extra_width);
1391 else
1393 BUTTON_GetLabelIdealSize(infoPtr, size->cx, &label_size);
1394 size->cx = label_size.cx + ((size->cx == 0) ? extra_width : 0);
1395 size->cy = label_size.cy;
1397 return TRUE;
1400 static BOOL CL_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size)
1402 HTHEME theme = GetWindowTheme(infoPtr->hwnd);
1403 HDC hdc = GetDC(infoPtr->hwnd);
1404 LONG w, text_w = 0, text_h = 0;
1405 UINT flags = DT_TOP | DT_LEFT;
1406 HFONT font, old_font = NULL;
1407 RECT text_bound = { 0 };
1408 SIZE img_size;
1409 RECT margin;
1410 WCHAR *text;
1412 /* Get the image size */
1413 if (infoPtr->u.image || infoPtr->imagelist.himl)
1414 img_size = BUTTON_GetImageSize(infoPtr);
1415 else
1417 if (theme)
1418 GetThemePartSize(theme, NULL, BP_COMMANDLINKGLYPH, CMDLS_NORMAL, NULL, TS_DRAW, &img_size);
1419 else
1420 img_size.cx = img_size.cy = command_link_defglyph_size;
1423 /* Get the content margins */
1424 if (theme)
1426 RECT r = { 0, 0, 0xffff, 0xffff };
1427 GetThemeBackgroundContentRect(theme, hdc, BP_COMMANDLINK, CMDLS_NORMAL, &r, &margin);
1428 margin.left -= r.left;
1429 margin.top -= r.top;
1430 margin.right = r.right - margin.right;
1431 margin.bottom = r.bottom - margin.bottom;
1433 else
1435 margin.left = margin.right = command_link_margin;
1436 margin.top = margin.bottom = command_link_margin;
1439 /* Account for the border margins and the margin between image and text */
1440 w = margin.left + margin.right + (img_size.cx ? (img_size.cx + command_link_margin) : 0);
1442 /* If a rectangle with a specific width was requested, bound the text to it */
1443 if (size->cx > w)
1445 text_bound.right = size->cx - w;
1446 flags |= DT_WORDBREAK;
1449 if (theme)
1451 if (infoPtr->font) old_font = SelectObject(hdc, infoPtr->font);
1453 /* Find the text's rect */
1454 if ((text = get_button_text(infoPtr)))
1456 RECT r;
1457 GetThemeTextExtent(theme, hdc, BP_COMMANDLINK, CMDLS_NORMAL,
1458 text, -1, flags, &text_bound, &r);
1459 heap_free(text);
1460 text_w = r.right - r.left;
1461 text_h = r.bottom - r.top;
1464 /* Find the note's rect */
1465 if (infoPtr->note)
1467 DTTOPTS opts;
1469 opts.dwSize = sizeof(opts);
1470 opts.dwFlags = DTT_FONTPROP | DTT_CALCRECT;
1471 opts.iFontPropId = TMT_BODYFONT;
1472 DrawThemeTextEx(theme, hdc, BP_COMMANDLINK, CMDLS_NORMAL,
1473 infoPtr->note, infoPtr->note_length,
1474 flags | DT_NOPREFIX | DT_CALCRECT, &text_bound, &opts);
1475 text_w = max(text_w, text_bound.right - text_bound.left);
1476 text_h += text_bound.bottom - text_bound.top;
1479 else
1481 NONCLIENTMETRICSW ncm;
1483 ncm.cbSize = sizeof(ncm);
1484 if (SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, sizeof(ncm), &ncm, 0))
1486 LONG note_weight = ncm.lfMessageFont.lfWeight;
1488 /* Find the text's rect */
1489 ncm.lfMessageFont.lfWeight = FW_BOLD;
1490 if ((font = CreateFontIndirectW(&ncm.lfMessageFont)))
1492 if ((text = get_button_text(infoPtr)))
1494 RECT r = text_bound;
1495 old_font = SelectObject(hdc, font);
1496 DrawTextW(hdc, text, -1, &r, flags | DT_CALCRECT);
1497 heap_free(text);
1499 text_w = r.right - r.left;
1500 text_h = r.bottom - r.top;
1502 DeleteObject(font);
1505 /* Find the note's rect */
1506 ncm.lfMessageFont.lfWeight = note_weight;
1507 if (infoPtr->note && (font = CreateFontIndirectW(&ncm.lfMessageFont)))
1509 HFONT tmp = SelectObject(hdc, font);
1510 if (!old_font) old_font = tmp;
1512 DrawTextW(hdc, infoPtr->note, infoPtr->note_length, &text_bound,
1513 flags | DT_NOPREFIX | DT_CALCRECT);
1514 DeleteObject(font);
1516 text_w = max(text_w, text_bound.right - text_bound.left);
1517 text_h += text_bound.bottom - text_bound.top + 2;
1521 w += text_w;
1523 size->cx = min(size->cx, w);
1524 size->cy = max(text_h, img_size.cy) + margin.top + margin.bottom;
1526 if (old_font) SelectObject(hdc, old_font);
1527 ReleaseDC(infoPtr->hwnd, hdc);
1528 return TRUE;
1531 /**********************************************************************
1532 * BUTTON_CalcLayoutRects
1534 * Calculates the rectangles of the button label(image and text) and its parts depending on a button's style.
1536 * Returns flags to be passed to DrawText.
1537 * Calculated rectangle doesn't take into account button state
1538 * (pushed, etc.). If there is nothing to draw (no text/image) output
1539 * rectangle is empty, and return value is (UINT)-1.
1541 * PARAMS:
1542 * infoPtr [I] Button pointer
1543 * hdc [I] Handle to device context to draw to
1544 * labelRc [I/O] Input the rect the label to be positioned in, and output the label rect
1545 * imageRc [O] Optional, output the image rect
1546 * textRc [O] Optional, output the text rect
1548 static UINT BUTTON_CalcLayoutRects(const BUTTON_INFO *infoPtr, HDC hdc, RECT *labelRc, RECT *imageRc, RECT *textRc)
1550 LONG style = GetWindowLongW( infoPtr->hwnd, GWL_STYLE );
1551 LONG ex_style = GetWindowLongW( infoPtr->hwnd, GWL_EXSTYLE );
1552 LONG split_style = infoPtr->imagelist.himl ? BUTTON_ILStoBS(infoPtr->imagelist.uAlign) : style;
1553 WCHAR *text = get_button_text(infoPtr);
1554 SIZE imageSize = BUTTON_GetImageSize(infoPtr);
1555 UINT dtStyle = BUTTON_BStoDT(style, ex_style);
1556 RECT labelRect, imageRect, imageRectWithMargin, textRect;
1557 LONG imageMarginWidth, imageMarginHeight;
1558 const RECT *textMargin = BUTTON_GetTextMargin(infoPtr);
1559 RECT emptyMargin = {0};
1560 LONG maxTextWidth;
1562 /* Calculate label rectangle according to label type */
1563 if ((imageSize.cx == 0 && imageSize.cy == 0) && (text == NULL || text[0] == '\0'))
1565 SetRectEmpty(labelRc);
1566 SetRectEmpty(imageRc);
1567 SetRectEmpty(textRc);
1568 heap_free(text);
1569 return (UINT)-1;
1572 SetRect(&imageRect, 0, 0, imageSize.cx, imageSize.cy);
1573 imageRectWithMargin = imageRect;
1574 if (infoPtr->imagelist.himl)
1576 imageRectWithMargin.top -= infoPtr->imagelist.margin.top;
1577 imageRectWithMargin.bottom += infoPtr->imagelist.margin.bottom;
1578 imageRectWithMargin.left -= infoPtr->imagelist.margin.left;
1579 imageRectWithMargin.right += infoPtr->imagelist.margin.right;
1582 /* Show image only */
1583 if (show_image_only(infoPtr))
1585 BUTTON_PositionRect(style, labelRc, &imageRect,
1586 infoPtr->imagelist.himl ? &infoPtr->imagelist.margin : &emptyMargin);
1587 labelRect = imageRect;
1588 SetRectEmpty(&textRect);
1590 else
1592 /* Get text rect */
1593 maxTextWidth = labelRc->right - labelRc->left;
1594 textRect = BUTTON_GetTextRect(infoPtr, hdc, text, maxTextWidth);
1596 /* Show image and text */
1597 if (show_image_and_text(infoPtr))
1599 RECT boundingLabelRect, boundingImageRect, boundingTextRect;
1601 /* Get label rect */
1602 /* Image list may have different alignment than the button, use the whole rect for label in this case */
1603 if (infoPtr->imagelist.himl)
1604 labelRect = *labelRc;
1605 else
1607 /* Get a label bounding rectangle to position the label in the user specified label rectangle because
1608 * text and image need to align together. */
1609 boundingLabelRect = BUTTON_GetBoundingLabelRect(split_style, &textRect, &imageRectWithMargin);
1610 BUTTON_PositionRect(split_style, labelRc, &boundingLabelRect, &emptyMargin);
1611 labelRect = boundingLabelRect;
1614 /* When imagelist has center align, use the whole rect for imagelist and text */
1615 if(infoPtr->imagelist.himl && infoPtr->imagelist.uAlign == BUTTON_IMAGELIST_ALIGN_CENTER)
1617 boundingImageRect = labelRect;
1618 boundingTextRect = labelRect;
1619 BUTTON_PositionRect(split_style, &boundingImageRect, &imageRect,
1620 infoPtr->imagelist.himl ? &infoPtr->imagelist.margin : &emptyMargin);
1621 /* Text doesn't use imagelist align */
1622 BUTTON_PositionRect(style, &boundingTextRect, &textRect, textMargin);
1624 else
1626 /* Get image rect */
1627 /* Split the label rect to two halves as two bounding rectangles for image and text */
1628 boundingImageRect = labelRect;
1629 imageMarginWidth = imageRectWithMargin.right - imageRectWithMargin.left;
1630 imageMarginHeight = imageRectWithMargin.bottom - imageRectWithMargin.top;
1631 if ((split_style & BS_CENTER) == BS_RIGHT)
1632 boundingImageRect.left = boundingImageRect.right - imageMarginWidth;
1633 else if ((split_style & BS_CENTER) == BS_LEFT)
1634 boundingImageRect.right = boundingImageRect.left + imageMarginWidth;
1635 else if ((split_style & BS_VCENTER) == BS_BOTTOM)
1636 boundingImageRect.top = boundingImageRect.bottom - imageMarginHeight;
1637 else if ((split_style & BS_VCENTER) == BS_TOP)
1638 boundingImageRect.bottom = boundingImageRect.top + imageMarginHeight;
1639 else
1640 boundingImageRect.right = boundingImageRect.left + imageMarginWidth;
1641 BUTTON_PositionRect(split_style, &boundingImageRect, &imageRect,
1642 infoPtr->imagelist.himl ? &infoPtr->imagelist.margin : &emptyMargin);
1644 /* Get text rect */
1645 SubtractRect(&boundingTextRect, &labelRect, &boundingImageRect);
1646 /* Text doesn't use imagelist align */
1647 BUTTON_PositionRect(style, &boundingTextRect, &textRect, textMargin);
1650 /* Show text only */
1651 else
1653 if (get_button_type(style) != BS_GROUPBOX)
1654 BUTTON_PositionRect(style, labelRc, &textRect, textMargin);
1655 else
1656 /* GroupBox is always top aligned */
1657 BUTTON_PositionRect((style & ~BS_VCENTER) | BS_TOP, labelRc, &textRect, textMargin);
1658 labelRect = textRect;
1659 SetRectEmpty(&imageRect);
1662 heap_free(text);
1664 CopyRect(labelRc, &labelRect);
1665 CopyRect(imageRc, &imageRect);
1666 CopyRect(textRc, &textRect);
1668 return dtStyle;
1672 /**********************************************************************
1673 * BUTTON_DrawImage
1675 * Draw the button's image into the specified rectangle.
1677 static void BUTTON_DrawImage(const BUTTON_INFO *infoPtr, HDC hdc, HBRUSH hbr, UINT flags, const RECT *rect)
1679 if (infoPtr->imagelist.himl)
1681 int i = (ImageList_GetImageCount(infoPtr->imagelist.himl) == 1) ? 0 : get_draw_state(infoPtr) - 1;
1683 ImageList_Draw(infoPtr->imagelist.himl, i, hdc, rect->left, rect->top, ILD_NORMAL);
1685 else
1687 switch (infoPtr->image_type)
1689 case IMAGE_ICON:
1690 flags |= DST_ICON;
1691 break;
1692 case IMAGE_BITMAP:
1693 flags |= DST_BITMAP;
1694 break;
1695 default:
1696 return;
1699 DrawStateW(hdc, hbr, NULL, (LPARAM)infoPtr->u.image, 0, rect->left, rect->top,
1700 rect->right - rect->left, rect->bottom - rect->top, flags);
1705 /**********************************************************************
1706 * BUTTON_DrawTextCallback
1708 * Callback function used by DrawStateW function.
1710 static BOOL CALLBACK BUTTON_DrawTextCallback(HDC hdc, LPARAM lp, WPARAM wp, int cx, int cy)
1712 RECT rc;
1714 SetRect(&rc, 0, 0, cx, cy);
1715 DrawTextW(hdc, (LPCWSTR)lp, -1, &rc, (UINT)wp);
1716 return TRUE;
1719 /**********************************************************************
1720 * BUTTON_DrawLabel
1722 * Common function for drawing button label.
1724 * FIXME:
1725 * 1. When BS_SINGLELINE is specified and text contains '\t', '\n' or '\r' in the middle, they are rendered as
1726 * squares now whereas they should be ignored.
1727 * 2. When BS_MULTILINE is specified and text contains space in the middle, the space mistakenly be rendered as newline.
1729 static void BUTTON_DrawLabel(const BUTTON_INFO *infoPtr, HDC hdc, UINT dtFlags, const RECT *imageRect,
1730 const RECT *textRect)
1732 HBRUSH hbr = 0;
1733 UINT flags = IsWindowEnabled(infoPtr->hwnd) ? DSS_NORMAL : DSS_DISABLED;
1734 LONG style = GetWindowLongW( infoPtr->hwnd, GWL_STYLE );
1735 WCHAR *text;
1737 /* FIXME: To draw disabled label in Win31 look-and-feel, we probably
1738 * must use DSS_MONO flag and COLOR_GRAYTEXT brush (or maybe DSS_UNION).
1739 * I don't have Win31 on hand to verify that, so I leave it as is.
1742 if ((style & BS_PUSHLIKE) && (infoPtr->state & BST_INDETERMINATE))
1744 hbr = GetSysColorBrush(COLOR_GRAYTEXT);
1745 flags |= DSS_MONO;
1748 if (show_image(infoPtr)) BUTTON_DrawImage(infoPtr, hdc, hbr, flags, imageRect);
1749 if (show_image_only(infoPtr)) return;
1751 /* DST_COMPLEX -- is 0 */
1752 if (!(text = get_button_text(infoPtr))) return;
1753 DrawStateW(hdc, hbr, BUTTON_DrawTextCallback, (LPARAM)text, dtFlags, textRect->left, textRect->top,
1754 textRect->right - textRect->left, textRect->bottom - textRect->top, flags);
1755 heap_free(text);
1758 /**********************************************************************
1759 * Push Button Functions
1761 static void PB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action )
1763 RECT rc, labelRect, imageRect, textRect;
1764 UINT dtFlags, uState;
1765 HPEN hOldPen, hpen;
1766 HBRUSH hOldBrush;
1767 INT oldBkMode;
1768 COLORREF oldTxtColor;
1769 LRESULT cdrf;
1770 HFONT hFont;
1771 NMCUSTOMDRAW nmcd;
1772 LONG state = infoPtr->state;
1773 LONG style = GetWindowLongW( infoPtr->hwnd, GWL_STYLE );
1774 BOOL pushedState = (state & BST_PUSHED);
1775 HWND parent;
1776 HRGN hrgn;
1778 GetClientRect( infoPtr->hwnd, &rc );
1780 /* Send WM_CTLCOLOR to allow changing the font (the colors are fixed) */
1781 if ((hFont = infoPtr->font)) SelectObject( hDC, hFont );
1782 parent = GetParent(infoPtr->hwnd);
1783 if (!parent) parent = infoPtr->hwnd;
1784 SendMessageW( parent, WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)infoPtr->hwnd );
1786 hrgn = set_control_clipping( hDC, &rc );
1788 hpen = CreatePen( PS_SOLID, 1, GetSysColor(COLOR_WINDOWFRAME));
1789 hOldPen = SelectObject(hDC, hpen);
1790 hOldBrush = SelectObject(hDC,GetSysColorBrush(COLOR_BTNFACE));
1791 oldBkMode = SetBkMode(hDC, TRANSPARENT);
1793 init_custom_draw(&nmcd, infoPtr, hDC, &rc);
1795 /* Send erase notifications */
1796 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
1797 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
1799 if (get_button_type(style) == BS_DEFPUSHBUTTON)
1801 if (action != ODA_FOCUS)
1802 Rectangle(hDC, rc.left, rc.top, rc.right, rc.bottom);
1803 InflateRect( &rc, -1, -1 );
1806 /* Skip the frame drawing if only focus has changed */
1807 if (action != ODA_FOCUS)
1809 uState = DFCS_BUTTONPUSH;
1811 if (style & BS_FLAT)
1812 uState |= DFCS_MONO;
1813 else if (pushedState)
1815 if (get_button_type(style) == BS_DEFPUSHBUTTON )
1816 uState |= DFCS_FLAT;
1817 else
1818 uState |= DFCS_PUSHED;
1821 if (state & (BST_CHECKED | BST_INDETERMINATE))
1822 uState |= DFCS_CHECKED;
1824 DrawFrameControl( hDC, &rc, DFC_BUTTON, uState );
1827 if (cdrf & CDRF_NOTIFYPOSTERASE)
1829 nmcd.dwDrawStage = CDDS_POSTERASE;
1830 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
1833 /* Send paint notifications */
1834 nmcd.dwDrawStage = CDDS_PREPAINT;
1835 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
1836 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
1838 if (!(cdrf & CDRF_DOERASE) && action != ODA_FOCUS)
1840 /* draw button label */
1841 labelRect = rc;
1842 /* Shrink label rect at all sides by 2 so that the content won't touch the surrounding frame */
1843 InflateRect(&labelRect, -2, -2);
1844 dtFlags = BUTTON_CalcLayoutRects(infoPtr, hDC, &labelRect, &imageRect, &textRect);
1846 if (dtFlags != (UINT)-1L)
1848 if (pushedState) OffsetRect(&labelRect, 1, 1);
1850 oldTxtColor = SetTextColor( hDC, GetSysColor(COLOR_BTNTEXT) );
1852 BUTTON_DrawLabel(infoPtr, hDC, dtFlags, &imageRect, &textRect);
1854 SetTextColor( hDC, oldTxtColor );
1858 if (cdrf & CDRF_NOTIFYPOSTPAINT)
1860 nmcd.dwDrawStage = CDDS_POSTPAINT;
1861 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
1863 if ((cdrf & CDRF_SKIPPOSTPAINT) || dtFlags == (UINT)-1L) goto cleanup;
1865 if (action == ODA_FOCUS || (state & BST_FOCUS))
1867 InflateRect( &rc, -2, -2 );
1868 DrawFocusRect( hDC, &rc );
1871 cleanup:
1872 SelectObject( hDC, hOldPen );
1873 SelectObject( hDC, hOldBrush );
1874 SetBkMode(hDC, oldBkMode);
1875 SelectClipRgn( hDC, hrgn );
1876 if (hrgn) DeleteObject( hrgn );
1877 DeleteObject( hpen );
1880 /**********************************************************************
1881 * Check Box & Radio Button Functions
1884 static void CB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action )
1886 RECT rbox, labelRect, imageRect, textRect, client;
1887 HBRUSH hBrush;
1888 int delta, text_offset, checkBoxWidth, checkBoxHeight;
1889 UINT dtFlags;
1890 LRESULT cdrf;
1891 HFONT hFont;
1892 NMCUSTOMDRAW nmcd;
1893 LONG state = infoPtr->state;
1894 LONG style = GetWindowLongW( infoPtr->hwnd, GWL_STYLE );
1895 LONG ex_style = GetWindowLongW( infoPtr->hwnd, GWL_EXSTYLE );
1896 HWND parent;
1897 HRGN hrgn;
1899 if (style & BS_PUSHLIKE)
1901 PB_Paint( infoPtr, hDC, action );
1902 return;
1905 GetClientRect(infoPtr->hwnd, &client);
1906 rbox = labelRect = client;
1908 checkBoxWidth = 12 * GetDpiForWindow( infoPtr->hwnd ) / 96 + 1;
1909 checkBoxHeight = 12 * GetDpiForWindow( infoPtr->hwnd ) / 96 + 1;
1911 if ((hFont = infoPtr->font)) SelectObject( hDC, hFont );
1912 GetCharWidthW( hDC, '0', '0', &text_offset );
1913 text_offset /= 2;
1915 parent = GetParent(infoPtr->hwnd);
1916 if (!parent) parent = infoPtr->hwnd;
1917 hBrush = (HBRUSH)SendMessageW(parent, WM_CTLCOLORSTATIC, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
1918 if (!hBrush) /* did the app forget to call defwindowproc ? */
1919 hBrush = (HBRUSH)DefWindowProcW(parent, WM_CTLCOLORSTATIC, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
1920 hrgn = set_control_clipping( hDC, &client );
1922 if (style & BS_LEFTTEXT || ex_style & WS_EX_RIGHT)
1924 labelRect.right -= checkBoxWidth + text_offset;
1925 rbox.left = rbox.right - checkBoxWidth;
1927 else
1929 labelRect.left += checkBoxWidth + text_offset;
1930 rbox.right = checkBoxWidth;
1933 init_custom_draw(&nmcd, infoPtr, hDC, &client);
1935 /* Send erase notifications */
1936 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
1937 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
1939 /* Since WM_ERASEBKGND does nothing, first prepare background */
1940 if (action == ODA_SELECT) FillRect( hDC, &rbox, hBrush );
1941 if (action == ODA_DRAWENTIRE) FillRect( hDC, &client, hBrush );
1942 if (cdrf & CDRF_NOTIFYPOSTERASE)
1944 nmcd.dwDrawStage = CDDS_POSTERASE;
1945 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
1948 /* Draw label */
1949 client = labelRect;
1950 dtFlags = BUTTON_CalcLayoutRects(infoPtr, hDC, &labelRect, &imageRect, &textRect);
1952 /* Only adjust rbox when rtext is valid */
1953 if (dtFlags != (UINT)-1L)
1955 rbox.top = labelRect.top;
1956 rbox.bottom = labelRect.bottom;
1959 /* Send paint notifications */
1960 nmcd.dwDrawStage = CDDS_PREPAINT;
1961 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
1962 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
1964 /* Draw the check-box bitmap */
1965 if (!(cdrf & CDRF_DOERASE))
1967 if (action == ODA_DRAWENTIRE || action == ODA_SELECT)
1969 UINT flags;
1971 if ((get_button_type(style) == BS_RADIOBUTTON) ||
1972 (get_button_type(style) == BS_AUTORADIOBUTTON)) flags = DFCS_BUTTONRADIO;
1973 else if (state & BST_INDETERMINATE) flags = DFCS_BUTTON3STATE;
1974 else flags = DFCS_BUTTONCHECK;
1976 if (state & (BST_CHECKED | BST_INDETERMINATE)) flags |= DFCS_CHECKED;
1977 if (state & BST_PUSHED) flags |= DFCS_PUSHED;
1978 if (style & WS_DISABLED) flags |= DFCS_INACTIVE;
1980 /* rbox must have the correct height */
1981 delta = rbox.bottom - rbox.top - checkBoxHeight;
1983 if ((style & BS_VCENTER) == BS_TOP)
1985 if (delta > 0)
1986 rbox.bottom = rbox.top + checkBoxHeight;
1987 else
1989 rbox.top -= -delta / 2 + 1;
1990 rbox.bottom = rbox.top + checkBoxHeight;
1993 else if ((style & BS_VCENTER) == BS_BOTTOM)
1995 if (delta > 0)
1996 rbox.top = rbox.bottom - checkBoxHeight;
1997 else
1999 rbox.bottom += -delta / 2 + 1;
2000 rbox.top = rbox.bottom - checkBoxHeight;
2003 else /* Default */
2005 if (delta > 0)
2007 int ofs = delta / 2;
2008 rbox.bottom -= ofs + 1;
2009 rbox.top = rbox.bottom - checkBoxHeight;
2011 else if (delta < 0)
2013 int ofs = -delta / 2;
2014 rbox.top -= ofs + 1;
2015 rbox.bottom = rbox.top + checkBoxHeight;
2019 DrawFrameControl(hDC, &rbox, DFC_BUTTON, flags);
2022 if (dtFlags != (UINT)-1L) /* Something to draw */
2023 if (action == ODA_DRAWENTIRE) BUTTON_DrawLabel(infoPtr, hDC, dtFlags, &imageRect, &textRect);
2026 if (cdrf & CDRF_NOTIFYPOSTPAINT)
2028 nmcd.dwDrawStage = CDDS_POSTPAINT;
2029 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2031 if ((cdrf & CDRF_SKIPPOSTPAINT) || dtFlags == (UINT)-1L) goto cleanup;
2033 /* ... and focus */
2034 if (action == ODA_FOCUS || (state & BST_FOCUS))
2036 labelRect.left--;
2037 labelRect.right++;
2038 IntersectRect(&labelRect, &labelRect, &client);
2039 DrawFocusRect(hDC, &labelRect);
2042 cleanup:
2043 SelectClipRgn( hDC, hrgn );
2044 if (hrgn) DeleteObject( hrgn );
2048 /**********************************************************************
2049 * BUTTON_CheckAutoRadioButton
2051 * hwnd is checked, uncheck every other auto radio button in group
2053 static void BUTTON_CheckAutoRadioButton( HWND hwnd )
2055 HWND parent, sibling, start;
2057 parent = GetParent(hwnd);
2058 /* make sure that starting control is not disabled or invisible */
2059 start = sibling = GetNextDlgGroupItem( parent, hwnd, TRUE );
2062 if (!sibling) break;
2063 if ((hwnd != sibling) &&
2064 ((GetWindowLongW( sibling, GWL_STYLE) & BS_TYPEMASK) == BS_AUTORADIOBUTTON))
2065 SendMessageW( sibling, BM_SETCHECK, BST_UNCHECKED, 0 );
2066 sibling = GetNextDlgGroupItem( parent, sibling, FALSE );
2067 } while (sibling != start);
2071 /**********************************************************************
2072 * Group Box Functions
2075 static void GB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action )
2077 RECT labelRect, imageRect, textRect, rcFrame;
2078 HBRUSH hbr;
2079 HFONT hFont;
2080 UINT dtFlags;
2081 TEXTMETRICW tm;
2082 LONG style = GetWindowLongW( infoPtr->hwnd, GWL_STYLE );
2083 HWND parent;
2084 HRGN hrgn;
2086 if ((hFont = infoPtr->font)) SelectObject( hDC, hFont );
2087 /* GroupBox acts like static control, so it sends CTLCOLORSTATIC */
2088 parent = GetParent(infoPtr->hwnd);
2089 if (!parent) parent = infoPtr->hwnd;
2090 hbr = (HBRUSH)SendMessageW(parent, WM_CTLCOLORSTATIC, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
2091 if (!hbr) /* did the app forget to call defwindowproc ? */
2092 hbr = (HBRUSH)DefWindowProcW(parent, WM_CTLCOLORSTATIC, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
2093 GetClientRect(infoPtr->hwnd, &labelRect);
2094 rcFrame = labelRect;
2095 hrgn = set_control_clipping(hDC, &labelRect);
2097 GetTextMetricsW (hDC, &tm);
2098 rcFrame.top += (tm.tmHeight / 2) - 1;
2099 DrawEdge (hDC, &rcFrame, EDGE_ETCHED, BF_RECT | ((style & BS_FLAT) ? BF_FLAT : 0));
2101 InflateRect(&labelRect, -7, 1);
2102 dtFlags = BUTTON_CalcLayoutRects(infoPtr, hDC, &labelRect, &imageRect, &textRect);
2104 if (dtFlags != (UINT)-1)
2106 /* Because buttons have CS_PARENTDC class style, there is a chance
2107 * that label will be drawn out of client rect.
2108 * But Windows doesn't clip label's rect, so do I.
2111 /* There is 1-pixel margin at the left, right, and bottom */
2112 labelRect.left--;
2113 labelRect.right++;
2114 labelRect.bottom++;
2115 FillRect(hDC, &labelRect, hbr);
2116 labelRect.left++;
2117 labelRect.right--;
2118 labelRect.bottom--;
2120 BUTTON_DrawLabel(infoPtr, hDC, dtFlags, &imageRect, &textRect);
2122 SelectClipRgn( hDC, hrgn );
2123 if (hrgn) DeleteObject( hrgn );
2127 /**********************************************************************
2128 * User Button Functions
2131 static void UB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action )
2133 RECT rc;
2134 HBRUSH hBrush;
2135 LRESULT cdrf;
2136 HFONT hFont;
2137 NMCUSTOMDRAW nmcd;
2138 LONG state = infoPtr->state;
2139 HWND parent;
2141 GetClientRect( infoPtr->hwnd, &rc);
2143 if ((hFont = infoPtr->font)) SelectObject( hDC, hFont );
2145 parent = GetParent(infoPtr->hwnd);
2146 if (!parent) parent = infoPtr->hwnd;
2147 hBrush = (HBRUSH)SendMessageW(parent, WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
2148 if (!hBrush) /* did the app forget to call defwindowproc ? */
2149 hBrush = (HBRUSH)DefWindowProcW(parent, WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
2151 if (action == ODA_FOCUS || (state & BST_FOCUS))
2153 init_custom_draw(&nmcd, infoPtr, hDC, &rc);
2155 /* Send erase notifications */
2156 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2157 if (cdrf & CDRF_SKIPDEFAULT) goto notify;
2160 FillRect( hDC, &rc, hBrush );
2161 if (action == ODA_FOCUS || (state & BST_FOCUS))
2163 if (cdrf & CDRF_NOTIFYPOSTERASE)
2165 nmcd.dwDrawStage = CDDS_POSTERASE;
2166 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2169 /* Send paint notifications */
2170 nmcd.dwDrawStage = CDDS_PREPAINT;
2171 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2172 if (cdrf & CDRF_SKIPDEFAULT) goto notify;
2173 if (cdrf & CDRF_NOTIFYPOSTPAINT)
2175 nmcd.dwDrawStage = CDDS_POSTPAINT;
2176 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2179 if (!(cdrf & CDRF_SKIPPOSTPAINT))
2180 DrawFocusRect( hDC, &rc );
2183 notify:
2184 switch (action)
2186 case ODA_FOCUS:
2187 BUTTON_NOTIFY_PARENT( infoPtr->hwnd, (state & BST_FOCUS) ? BN_SETFOCUS : BN_KILLFOCUS );
2188 break;
2190 case ODA_SELECT:
2191 BUTTON_NOTIFY_PARENT( infoPtr->hwnd, (state & BST_PUSHED) ? BN_HILITE : BN_UNHILITE );
2192 break;
2194 default:
2195 break;
2200 /**********************************************************************
2201 * Ownerdrawn Button Functions
2204 static void OB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action )
2206 LONG state = infoPtr->state;
2207 DRAWITEMSTRUCT dis;
2208 LONG_PTR id = GetWindowLongPtrW( infoPtr->hwnd, GWLP_ID );
2209 HWND parent;
2210 HFONT hFont;
2211 HRGN hrgn;
2213 dis.CtlType = ODT_BUTTON;
2214 dis.CtlID = id;
2215 dis.itemID = 0;
2216 dis.itemAction = action;
2217 dis.itemState = ((state & BST_FOCUS) ? ODS_FOCUS : 0) |
2218 ((state & BST_PUSHED) ? ODS_SELECTED : 0) |
2219 (IsWindowEnabled(infoPtr->hwnd) ? 0: ODS_DISABLED);
2220 dis.hwndItem = infoPtr->hwnd;
2221 dis.hDC = hDC;
2222 dis.itemData = 0;
2223 GetClientRect( infoPtr->hwnd, &dis.rcItem );
2225 if ((hFont = infoPtr->font)) SelectObject( hDC, hFont );
2226 parent = GetParent(infoPtr->hwnd);
2227 if (!parent) parent = infoPtr->hwnd;
2228 SendMessageW( parent, WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)infoPtr->hwnd );
2230 hrgn = set_control_clipping( hDC, &dis.rcItem );
2232 SendMessageW( GetParent(infoPtr->hwnd), WM_DRAWITEM, id, (LPARAM)&dis );
2233 SelectClipRgn( hDC, hrgn );
2234 if (hrgn) DeleteObject( hrgn );
2238 /**********************************************************************
2239 * Split Button Functions
2241 static void SB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action )
2243 LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2244 LONG state = infoPtr->state;
2245 UINT dtFlags = (UINT)-1L;
2247 RECT rc, push_rect, dropdown_rect;
2248 NMCUSTOMDRAW nmcd;
2249 HPEN pen, old_pen;
2250 HBRUSH old_brush;
2251 INT old_bk_mode;
2252 LRESULT cdrf;
2253 HWND parent;
2254 HRGN hrgn;
2256 GetClientRect(infoPtr->hwnd, &rc);
2258 /* Send WM_CTLCOLOR to allow changing the font (the colors are fixed) */
2259 if (infoPtr->font) SelectObject(hDC, infoPtr->font);
2260 if (!(parent = GetParent(infoPtr->hwnd))) parent = infoPtr->hwnd;
2261 SendMessageW(parent, WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
2263 hrgn = set_control_clipping(hDC, &rc);
2265 pen = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_WINDOWFRAME));
2266 old_pen = SelectObject(hDC, pen);
2267 old_brush = SelectObject(hDC, GetSysColorBrush(COLOR_BTNFACE));
2268 old_bk_mode = SetBkMode(hDC, TRANSPARENT);
2270 init_custom_draw(&nmcd, infoPtr, hDC, &rc);
2272 /* Send erase notifications */
2273 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2274 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2276 if (get_button_type(style) == BS_DEFSPLITBUTTON)
2278 if (action != ODA_FOCUS)
2279 Rectangle(hDC, rc.left, rc.top, rc.right, rc.bottom);
2280 InflateRect(&rc, -1, -1);
2281 /* The split will now be off by 1 pixel, but
2282 that's exactly what Windows does as well */
2285 get_split_button_rects(infoPtr, &rc, &push_rect, &dropdown_rect);
2286 if (infoPtr->split_style & BCSS_NOSPLIT)
2287 push_rect = rc;
2289 /* Skip the frame drawing if only focus has changed */
2290 if (action != ODA_FOCUS)
2292 UINT flags = DFCS_BUTTONPUSH;
2294 if (style & BS_FLAT) flags |= DFCS_MONO;
2295 else if (state & BST_PUSHED)
2296 flags |= (get_button_type(style) == BS_DEFSPLITBUTTON)
2297 ? DFCS_FLAT : DFCS_PUSHED;
2299 if (state & (BST_CHECKED | BST_INDETERMINATE))
2300 flags |= DFCS_CHECKED;
2302 if (infoPtr->split_style & BCSS_NOSPLIT)
2303 DrawFrameControl(hDC, &push_rect, DFC_BUTTON, flags);
2304 else
2306 UINT dropdown_flags = flags & ~DFCS_CHECKED;
2308 if (state & BST_DROPDOWNPUSHED)
2309 dropdown_flags = (dropdown_flags & ~DFCS_FLAT) | DFCS_PUSHED;
2311 /* Adjust for shadow and draw order so it looks properly */
2312 if (infoPtr->split_style & BCSS_ALIGNLEFT)
2314 dropdown_rect.right++;
2315 DrawFrameControl(hDC, &dropdown_rect, DFC_BUTTON, dropdown_flags);
2316 dropdown_rect.right--;
2317 DrawFrameControl(hDC, &push_rect, DFC_BUTTON, flags);
2319 else
2321 push_rect.right++;
2322 DrawFrameControl(hDC, &push_rect, DFC_BUTTON, flags);
2323 push_rect.right--;
2324 DrawFrameControl(hDC, &dropdown_rect, DFC_BUTTON, dropdown_flags);
2329 if (cdrf & CDRF_NOTIFYPOSTERASE)
2331 nmcd.dwDrawStage = CDDS_POSTERASE;
2332 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2335 /* Send paint notifications */
2336 nmcd.dwDrawStage = CDDS_PREPAINT;
2337 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2338 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2340 /* Shrink push button rect so that the content won't touch the surrounding frame */
2341 InflateRect(&push_rect, -2, -2);
2343 if (!(cdrf & CDRF_DOERASE) && action != ODA_FOCUS)
2345 COLORREF old_color = SetTextColor(hDC, GetSysColor(COLOR_BTNTEXT));
2346 RECT label_rect = push_rect, image_rect, text_rect;
2348 dtFlags = BUTTON_CalcLayoutRects(infoPtr, hDC, &label_rect, &image_rect, &text_rect);
2350 if (dtFlags != (UINT)-1L)
2351 BUTTON_DrawLabel(infoPtr, hDC, dtFlags, &image_rect, &text_rect);
2353 draw_split_button_dropdown_glyph(infoPtr, hDC, &dropdown_rect);
2354 SetTextColor(hDC, old_color);
2357 if (cdrf & CDRF_NOTIFYPOSTPAINT)
2359 nmcd.dwDrawStage = CDDS_POSTPAINT;
2360 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2362 if ((cdrf & CDRF_SKIPPOSTPAINT) || dtFlags == (UINT)-1L) goto cleanup;
2364 if (action == ODA_FOCUS || (state & BST_FOCUS))
2365 DrawFocusRect(hDC, &push_rect);
2367 cleanup:
2368 SelectObject(hDC, old_pen);
2369 SelectObject(hDC, old_brush);
2370 SetBkMode(hDC, old_bk_mode);
2371 SelectClipRgn(hDC, hrgn);
2372 if (hrgn) DeleteObject(hrgn);
2373 DeleteObject(pen);
2376 /* Given the full button rect of the split button, retrieve the push part and the dropdown part */
2377 static inline void get_split_button_rects(const BUTTON_INFO *infoPtr, const RECT *button_rect,
2378 RECT *push_rect, RECT *dropdown_rect)
2380 *push_rect = *dropdown_rect = *button_rect;
2382 /* The dropdown takes priority if the client rect is too small, it will only have a dropdown */
2383 if (infoPtr->split_style & BCSS_ALIGNLEFT)
2385 dropdown_rect->right = min(button_rect->left + infoPtr->glyph_size.cx, button_rect->right);
2386 push_rect->left = dropdown_rect->right;
2388 else
2390 dropdown_rect->left = max(button_rect->right - infoPtr->glyph_size.cx, button_rect->left);
2391 push_rect->right = dropdown_rect->left;
2395 /* Notify the parent if the point is within the dropdown and return TRUE (always notify if NULL) */
2396 static BOOL notify_split_button_dropdown(const BUTTON_INFO *infoPtr, const POINT *pt, HWND hwnd)
2398 NMBCDROPDOWN nmbcd;
2400 GetClientRect(hwnd, &nmbcd.rcButton);
2401 if (pt)
2403 RECT push_rect, dropdown_rect;
2405 get_split_button_rects(infoPtr, &nmbcd.rcButton, &push_rect, &dropdown_rect);
2406 if (!PtInRect(&dropdown_rect, *pt))
2407 return FALSE;
2409 /* If it's already down (set manually via BCM_SETDROPDOWNSTATE), fake the notify */
2410 if (infoPtr->state & BST_DROPDOWNPUSHED)
2411 return TRUE;
2413 SendMessageW(hwnd, BCM_SETDROPDOWNSTATE, TRUE, 0);
2415 nmbcd.hdr.hwndFrom = hwnd;
2416 nmbcd.hdr.idFrom = GetWindowLongPtrW(hwnd, GWLP_ID);
2417 nmbcd.hdr.code = BCN_DROPDOWN;
2418 SendMessageW(GetParent(hwnd), WM_NOTIFY, nmbcd.hdr.idFrom, (LPARAM)&nmbcd);
2420 SendMessageW(hwnd, BCM_SETDROPDOWNSTATE, FALSE, 0);
2421 return TRUE;
2424 /* Draw the split button dropdown glyph or image */
2425 static void draw_split_button_dropdown_glyph(const BUTTON_INFO *infoPtr, HDC hdc, RECT *rect)
2427 if (infoPtr->split_style & BCSS_IMAGE)
2429 int w, h;
2431 /* When the glyph is an image list, Windows is very buggy with BCSS_STRETCH,
2432 positions it weirdly and doesn't even stretch it, but instead extends the
2433 image, leaking into other images in the list (or black if none). Instead,
2434 we'll ignore this and just position it at center as without BCSS_STRETCH. */
2435 if (!ImageList_GetIconSize(infoPtr->glyph, &w, &h)) return;
2437 ImageList_Draw(infoPtr->glyph,
2438 (ImageList_GetImageCount(infoPtr->glyph) == 1) ? 0 : get_draw_state(infoPtr) - 1,
2439 hdc, rect->left + (rect->right - rect->left - w) / 2,
2440 rect->top + (rect->bottom - rect->top - h) / 2, ILD_NORMAL);
2442 else if (infoPtr->glyph_size.cy >= 0)
2444 /* infoPtr->glyph is a character code from Marlett */
2445 HFONT font, old_font;
2446 LOGFONTW logfont = { 0, 0, 0, 0, FW_NORMAL, 0, 0, 0, SYMBOL_CHARSET, 0, 0, 0, 0,
2447 L"Marlett" };
2448 if (infoPtr->glyph_size.cy)
2450 /* BCSS_STRETCH preserves aspect ratio, uses minimum as size */
2451 if (infoPtr->split_style & BCSS_STRETCH)
2452 logfont.lfHeight = min(infoPtr->glyph_size.cx, infoPtr->glyph_size.cy);
2453 else
2455 logfont.lfWidth = infoPtr->glyph_size.cx;
2456 logfont.lfHeight = infoPtr->glyph_size.cy;
2459 else logfont.lfHeight = infoPtr->glyph_size.cx;
2461 if ((font = CreateFontIndirectW(&logfont)))
2463 old_font = SelectObject(hdc, font);
2464 DrawTextW(hdc, (const WCHAR*)&infoPtr->glyph, 1, rect,
2465 DT_CENTER | DT_VCENTER | DT_SINGLELINE | DT_NOCLIP | DT_NOPREFIX);
2466 SelectObject(hdc, old_font);
2467 DeleteObject(font);
2473 /**********************************************************************
2474 * Command Link Functions
2476 static void CL_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action )
2478 LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2479 LONG state = infoPtr->state;
2481 RECT rc, content_rect;
2482 NMCUSTOMDRAW nmcd;
2483 HPEN pen, old_pen;
2484 HBRUSH old_brush;
2485 INT old_bk_mode;
2486 LRESULT cdrf;
2487 HWND parent;
2488 HRGN hrgn;
2490 GetClientRect(infoPtr->hwnd, &rc);
2492 /* Command Links are not affected by the button's font, and are based
2493 on the default message font. Furthermore, they are not affected by
2494 any of the alignment styles (and always align with the top-left). */
2495 if (!(parent = GetParent(infoPtr->hwnd))) parent = infoPtr->hwnd;
2496 SendMessageW(parent, WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
2498 hrgn = set_control_clipping(hDC, &rc);
2500 pen = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_WINDOWFRAME));
2501 old_pen = SelectObject(hDC, pen);
2502 old_brush = SelectObject(hDC, GetSysColorBrush(COLOR_BTNFACE));
2503 old_bk_mode = SetBkMode(hDC, TRANSPARENT);
2505 init_custom_draw(&nmcd, infoPtr, hDC, &rc);
2507 /* Send erase notifications */
2508 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2509 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2510 content_rect = rc;
2512 if (get_button_type(style) == BS_DEFCOMMANDLINK)
2514 if (action != ODA_FOCUS)
2515 Rectangle(hDC, rc.left, rc.top, rc.right, rc.bottom);
2516 InflateRect(&rc, -1, -1);
2519 /* Skip the frame drawing if only focus has changed */
2520 if (action != ODA_FOCUS)
2522 if (!(state & (BST_HOT | BST_PUSHED | BST_CHECKED | BST_INDETERMINATE)))
2523 FillRect(hDC, &rc, GetSysColorBrush(COLOR_BTNFACE));
2524 else
2526 UINT flags = DFCS_BUTTONPUSH;
2528 if (style & BS_FLAT) flags |= DFCS_MONO;
2529 else if (state & BST_PUSHED) flags |= DFCS_PUSHED;
2531 if (state & (BST_CHECKED | BST_INDETERMINATE))
2532 flags |= DFCS_CHECKED;
2533 DrawFrameControl(hDC, &rc, DFC_BUTTON, flags);
2537 if (cdrf & CDRF_NOTIFYPOSTERASE)
2539 nmcd.dwDrawStage = CDDS_POSTERASE;
2540 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2543 /* Send paint notifications */
2544 nmcd.dwDrawStage = CDDS_PREPAINT;
2545 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2546 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2548 if (!(cdrf & CDRF_DOERASE) && action != ODA_FOCUS)
2550 UINT flags = IsWindowEnabled(infoPtr->hwnd) ? DSS_NORMAL : DSS_DISABLED;
2551 COLORREF old_color = SetTextColor(hDC, GetSysColor(flags == DSS_NORMAL ?
2552 COLOR_BTNTEXT : COLOR_GRAYTEXT));
2553 HIMAGELIST defimg = NULL;
2554 NONCLIENTMETRICSW ncm;
2555 UINT txt_h = 0;
2556 SIZE img_size;
2558 /* Command Links ignore the margins of the image list or its alignment */
2559 if (infoPtr->u.image || infoPtr->imagelist.himl)
2560 img_size = BUTTON_GetImageSize(infoPtr);
2561 else
2563 img_size.cx = img_size.cy = command_link_defglyph_size;
2564 defimg = ImageList_LoadImageW(COMCTL32_hModule, (LPCWSTR)MAKEINTRESOURCE(IDB_CMDLINK),
2565 img_size.cx, 3, CLR_NONE, IMAGE_BITMAP, LR_CREATEDIBSECTION);
2568 /* Shrink rect by the command link margin, except on bottom (just the frame) */
2569 InflateRect(&content_rect, -command_link_margin, -command_link_margin);
2570 content_rect.bottom += command_link_margin - 2;
2572 ncm.cbSize = sizeof(ncm);
2573 if (SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, sizeof(ncm), &ncm, 0))
2575 LONG note_weight = ncm.lfMessageFont.lfWeight;
2576 RECT r = content_rect;
2577 WCHAR *text;
2578 HFONT font;
2580 if (img_size.cx) r.left += img_size.cx + command_link_margin;
2582 /* Draw the text */
2583 ncm.lfMessageFont.lfWeight = FW_BOLD;
2584 if ((font = CreateFontIndirectW(&ncm.lfMessageFont)))
2586 if ((text = get_button_text(infoPtr)))
2588 SelectObject(hDC, font);
2589 txt_h = DrawTextW(hDC, text, -1, &r,
2590 DT_TOP | DT_LEFT | DT_WORDBREAK | DT_END_ELLIPSIS);
2591 heap_free(text);
2593 DeleteObject(font);
2596 /* Draw the note */
2597 ncm.lfMessageFont.lfWeight = note_weight;
2598 if (infoPtr->note && (font = CreateFontIndirectW(&ncm.lfMessageFont)))
2600 r.top += txt_h + 2;
2601 SelectObject(hDC, font);
2602 DrawTextW(hDC, infoPtr->note, infoPtr->note_length, &r,
2603 DT_TOP | DT_LEFT | DT_WORDBREAK | DT_NOPREFIX);
2604 DeleteObject(font);
2608 /* Position the image at the vertical center of the drawn text (not note) */
2609 txt_h = min(txt_h, content_rect.bottom - content_rect.top);
2610 if (img_size.cy < txt_h) content_rect.top += (txt_h - img_size.cy) / 2;
2612 content_rect.right = content_rect.left + img_size.cx;
2613 content_rect.bottom = content_rect.top + img_size.cy;
2615 if (defimg)
2617 int i = 0;
2618 if (flags == DSS_DISABLED) i = 2;
2619 else if (state & BST_HOT) i = 1;
2621 ImageList_Draw(defimg, i, hDC, content_rect.left, content_rect.top, ILD_NORMAL);
2622 ImageList_Destroy(defimg);
2624 else
2625 BUTTON_DrawImage(infoPtr, hDC, NULL, flags, &content_rect);
2627 SetTextColor(hDC, old_color);
2630 if (cdrf & CDRF_NOTIFYPOSTPAINT)
2632 nmcd.dwDrawStage = CDDS_POSTPAINT;
2633 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2635 if (cdrf & CDRF_SKIPPOSTPAINT) goto cleanup;
2637 if (action == ODA_FOCUS || (state & BST_FOCUS))
2639 InflateRect(&rc, -2, -2);
2640 DrawFocusRect(hDC, &rc);
2643 cleanup:
2644 SelectObject(hDC, old_pen);
2645 SelectObject(hDC, old_brush);
2646 SetBkMode(hDC, old_bk_mode);
2647 SelectClipRgn(hDC, hrgn);
2648 if (hrgn) DeleteObject(hrgn);
2649 DeleteObject(pen);
2653 /**********************************************************************
2654 * Themed Paint Functions
2656 static void PB_ThemedPaint(HTHEME theme, const BUTTON_INFO *infoPtr, HDC hDC, int state, UINT dtFlags, BOOL focused)
2658 RECT bgRect, textRect;
2659 HFONT font = infoPtr->font;
2660 HFONT hPrevFont = font ? SelectObject(hDC, font) : NULL;
2661 NMCUSTOMDRAW nmcd;
2662 LRESULT cdrf;
2663 HWND parent;
2664 WCHAR *text;
2666 GetClientRect(infoPtr->hwnd, &bgRect);
2667 GetThemeBackgroundContentRect(theme, hDC, BP_PUSHBUTTON, state, &bgRect, &textRect);
2668 init_custom_draw(&nmcd, infoPtr, hDC, &bgRect);
2670 parent = GetParent(infoPtr->hwnd);
2671 if (!parent) parent = infoPtr->hwnd;
2673 /* Send erase notifications */
2674 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2675 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2677 if (IsThemeBackgroundPartiallyTransparent(theme, BP_PUSHBUTTON, state))
2678 DrawThemeParentBackground(infoPtr->hwnd, hDC, NULL);
2679 DrawThemeBackground(theme, hDC, BP_PUSHBUTTON, state, &bgRect, NULL);
2681 if (cdrf & CDRF_NOTIFYPOSTERASE)
2683 nmcd.dwDrawStage = CDDS_POSTERASE;
2684 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2687 /* Send paint notifications */
2688 nmcd.dwDrawStage = CDDS_PREPAINT;
2689 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2690 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2692 if (!(cdrf & CDRF_DOERASE) && (text = get_button_text(infoPtr)))
2694 DrawThemeText(theme, hDC, BP_PUSHBUTTON, state, text, lstrlenW(text), dtFlags, 0, &textRect);
2695 heap_free(text);
2698 if (cdrf & CDRF_NOTIFYPOSTPAINT)
2700 nmcd.dwDrawStage = CDDS_POSTPAINT;
2701 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2703 if (cdrf & CDRF_SKIPPOSTPAINT) goto cleanup;
2705 if (focused)
2707 MARGINS margins;
2708 RECT focusRect = bgRect;
2710 GetThemeMargins(theme, hDC, BP_PUSHBUTTON, state, TMT_CONTENTMARGINS, NULL, &margins);
2712 focusRect.left += margins.cxLeftWidth;
2713 focusRect.top += margins.cyTopHeight;
2714 focusRect.right -= margins.cxRightWidth;
2715 focusRect.bottom -= margins.cyBottomHeight;
2717 DrawFocusRect( hDC, &focusRect );
2720 cleanup:
2721 if (hPrevFont) SelectObject(hDC, hPrevFont);
2724 static void CB_ThemedPaint(HTHEME theme, const BUTTON_INFO *infoPtr, HDC hDC, int state, UINT dtFlags, BOOL focused)
2726 SIZE sz;
2727 RECT bgRect, textRect;
2728 HFONT font, hPrevFont = NULL;
2729 DWORD dwStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2730 UINT btn_type = get_button_type( dwStyle );
2731 int part = (btn_type == BS_RADIOBUTTON) || (btn_type == BS_AUTORADIOBUTTON) ? BP_RADIOBUTTON : BP_CHECKBOX;
2732 NMCUSTOMDRAW nmcd;
2733 LRESULT cdrf;
2734 LOGFONTW lf;
2735 HWND parent;
2736 WCHAR *text;
2737 BOOL created_font = FALSE;
2739 HRESULT hr = GetThemeFont(theme, hDC, part, state, TMT_FONT, &lf);
2740 if (SUCCEEDED(hr)) {
2741 font = CreateFontIndirectW(&lf);
2742 if (!font)
2743 TRACE("Failed to create font\n");
2744 else {
2745 TRACE("font = %s\n", debugstr_w(lf.lfFaceName));
2746 hPrevFont = SelectObject(hDC, font);
2747 created_font = TRUE;
2749 } else {
2750 font = (HFONT)SendMessageW(infoPtr->hwnd, WM_GETFONT, 0, 0);
2751 hPrevFont = SelectObject(hDC, font);
2754 if (FAILED(GetThemePartSize(theme, hDC, part, state, NULL, TS_DRAW, &sz)))
2755 sz.cx = sz.cy = 13;
2757 GetClientRect(infoPtr->hwnd, &bgRect);
2758 GetThemeBackgroundContentRect(theme, hDC, part, state, &bgRect, &textRect);
2759 init_custom_draw(&nmcd, infoPtr, hDC, &bgRect);
2761 if (dtFlags & DT_SINGLELINE) /* Center the checkbox / radio button to the text. */
2762 bgRect.top = bgRect.top + (textRect.bottom - textRect.top - sz.cy) / 2;
2764 /* adjust for the check/radio marker */
2765 bgRect.bottom = bgRect.top + sz.cy;
2766 bgRect.right = bgRect.left + sz.cx;
2767 textRect.left = bgRect.right + 6;
2769 parent = GetParent(infoPtr->hwnd);
2770 if (!parent) parent = infoPtr->hwnd;
2772 /* Send erase notifications */
2773 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2774 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2776 DrawThemeParentBackground(infoPtr->hwnd, hDC, NULL);
2777 DrawThemeBackground(theme, hDC, part, state, &bgRect, NULL);
2779 if (cdrf & CDRF_NOTIFYPOSTERASE)
2781 nmcd.dwDrawStage = CDDS_POSTERASE;
2782 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2785 /* Send paint notifications */
2786 nmcd.dwDrawStage = CDDS_PREPAINT;
2787 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2788 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2790 text = get_button_text(infoPtr);
2791 if (!(cdrf & CDRF_DOERASE) && text)
2792 DrawThemeText(theme, hDC, part, state, text, lstrlenW(text), dtFlags, 0, &textRect);
2794 if (cdrf & CDRF_NOTIFYPOSTPAINT)
2796 nmcd.dwDrawStage = CDDS_POSTPAINT;
2797 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2800 if (text)
2802 if (!(cdrf & CDRF_SKIPPOSTPAINT) && focused)
2804 RECT focusRect;
2806 focusRect = textRect;
2808 DrawTextW(hDC, text, lstrlenW(text), &focusRect, dtFlags | DT_CALCRECT);
2810 if (focusRect.right < textRect.right) focusRect.right++;
2811 focusRect.bottom = textRect.bottom;
2813 DrawFocusRect( hDC, &focusRect );
2816 heap_free(text);
2819 cleanup:
2820 if (created_font) DeleteObject(font);
2821 if (hPrevFont) SelectObject(hDC, hPrevFont);
2824 static void GB_ThemedPaint(HTHEME theme, const BUTTON_INFO *infoPtr, HDC hDC, int state, UINT dtFlags, BOOL focused)
2826 RECT bgRect, textRect, contentRect;
2827 WCHAR *text = get_button_text(infoPtr);
2828 LOGFONTW lf;
2829 HFONT font, hPrevFont = NULL;
2830 BOOL created_font = FALSE;
2832 HRESULT hr = GetThemeFont(theme, hDC, BP_GROUPBOX, state, TMT_FONT, &lf);
2833 if (SUCCEEDED(hr)) {
2834 font = CreateFontIndirectW(&lf);
2835 if (!font)
2836 TRACE("Failed to create font\n");
2837 else {
2838 hPrevFont = SelectObject(hDC, font);
2839 created_font = TRUE;
2841 } else {
2842 font = (HFONT)SendMessageW(infoPtr->hwnd, WM_GETFONT, 0, 0);
2843 hPrevFont = SelectObject(hDC, font);
2846 GetClientRect(infoPtr->hwnd, &bgRect);
2847 textRect = bgRect;
2849 if (text)
2851 SIZE textExtent;
2852 GetTextExtentPoint32W(hDC, text, lstrlenW(text), &textExtent);
2853 bgRect.top += (textExtent.cy / 2);
2854 textRect.left += 10;
2855 textRect.bottom = textRect.top + textExtent.cy;
2856 textRect.right = textRect.left + textExtent.cx + 4;
2858 ExcludeClipRect(hDC, textRect.left, textRect.top, textRect.right, textRect.bottom);
2861 GetThemeBackgroundContentRect(theme, hDC, BP_GROUPBOX, state, &bgRect, &contentRect);
2862 ExcludeClipRect(hDC, contentRect.left, contentRect.top, contentRect.right, contentRect.bottom);
2864 if (IsThemeBackgroundPartiallyTransparent(theme, BP_GROUPBOX, state))
2865 DrawThemeParentBackground(infoPtr->hwnd, hDC, NULL);
2866 DrawThemeBackground(theme, hDC, BP_GROUPBOX, state, &bgRect, NULL);
2868 SelectClipRgn(hDC, NULL);
2870 if (text)
2872 InflateRect(&textRect, -2, 0);
2873 DrawThemeText(theme, hDC, BP_GROUPBOX, state, text, lstrlenW(text), 0, 0, &textRect);
2874 heap_free(text);
2877 if (created_font) DeleteObject(font);
2878 if (hPrevFont) SelectObject(hDC, hPrevFont);
2881 static void SB_ThemedPaint(HTHEME theme, const BUTTON_INFO *infoPtr, HDC hDC, int state, UINT dtFlags, BOOL focused)
2883 HFONT old_font = infoPtr->font ? SelectObject(hDC, infoPtr->font) : NULL;
2884 RECT rc, content_rect, push_rect, dropdown_rect;
2885 NMCUSTOMDRAW nmcd;
2886 LRESULT cdrf;
2887 HWND parent;
2889 GetClientRect(infoPtr->hwnd, &rc);
2890 init_custom_draw(&nmcd, infoPtr, hDC, &rc);
2892 parent = GetParent(infoPtr->hwnd);
2893 if (!parent) parent = infoPtr->hwnd;
2895 /* Send erase notifications */
2896 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2897 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2899 if (IsThemeBackgroundPartiallyTransparent(theme, BP_PUSHBUTTON, state))
2900 DrawThemeParentBackground(infoPtr->hwnd, hDC, NULL);
2902 /* The zone outside the content is ignored for the dropdown (draws over) */
2903 GetThemeBackgroundContentRect(theme, hDC, BP_PUSHBUTTON, state, &rc, &content_rect);
2904 get_split_button_rects(infoPtr, &rc, &push_rect, &dropdown_rect);
2906 if (infoPtr->split_style & BCSS_NOSPLIT)
2908 push_rect = rc;
2909 DrawThemeBackground(theme, hDC, BP_PUSHBUTTON, state, &rc, NULL);
2911 else
2913 RECT r = { dropdown_rect.left, content_rect.top, dropdown_rect.right, content_rect.bottom };
2914 UINT edge = (infoPtr->split_style & BCSS_ALIGNLEFT) ? BF_RIGHT : BF_LEFT;
2915 const RECT *clip = NULL;
2917 /* If only the dropdown is pressed, we need to draw it separately */
2918 if (state != PBS_PRESSED && (infoPtr->state & BST_DROPDOWNPUSHED))
2920 DrawThemeBackground(theme, hDC, BP_PUSHBUTTON, PBS_PRESSED, &rc, &dropdown_rect);
2921 clip = &push_rect;
2923 DrawThemeBackground(theme, hDC, BP_PUSHBUTTON, state, &rc, clip);
2925 /* Draw the separator */
2926 DrawThemeEdge(theme, hDC, BP_PUSHBUTTON, state, &r, EDGE_ETCHED, edge, NULL);
2928 /* The content rect should be the content area of the push button */
2929 GetThemeBackgroundContentRect(theme, hDC, BP_PUSHBUTTON, state, &push_rect, &content_rect);
2932 if (cdrf & CDRF_NOTIFYPOSTERASE)
2934 nmcd.dwDrawStage = CDDS_POSTERASE;
2935 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2938 /* Send paint notifications */
2939 nmcd.dwDrawStage = CDDS_PREPAINT;
2940 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2941 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2943 if (!(cdrf & CDRF_DOERASE))
2945 COLORREF old_color, color;
2946 INT old_bk_mode;
2947 WCHAR *text;
2949 if ((text = get_button_text(infoPtr)))
2951 DrawThemeText(theme, hDC, BP_PUSHBUTTON, state, text, lstrlenW(text), dtFlags, 0, &content_rect);
2952 heap_free(text);
2955 GetThemeColor(theme, BP_PUSHBUTTON, state, TMT_TEXTCOLOR, &color);
2956 old_bk_mode = SetBkMode(hDC, TRANSPARENT);
2957 old_color = SetTextColor(hDC, color);
2959 draw_split_button_dropdown_glyph(infoPtr, hDC, &dropdown_rect);
2961 SetTextColor(hDC, old_color);
2962 SetBkMode(hDC, old_bk_mode);
2965 if (cdrf & CDRF_NOTIFYPOSTPAINT)
2967 nmcd.dwDrawStage = CDDS_POSTPAINT;
2968 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2970 if (cdrf & CDRF_SKIPPOSTPAINT) goto cleanup;
2972 if (focused)
2974 MARGINS margins;
2976 GetThemeMargins(theme, hDC, BP_PUSHBUTTON, state, TMT_CONTENTMARGINS, NULL, &margins);
2978 push_rect.left += margins.cxLeftWidth;
2979 push_rect.top += margins.cyTopHeight;
2980 push_rect.right -= margins.cxRightWidth;
2981 push_rect.bottom -= margins.cyBottomHeight;
2982 DrawFocusRect(hDC, &push_rect);
2985 cleanup:
2986 if (old_font) SelectObject(hDC, old_font);
2989 static void CL_ThemedPaint(HTHEME theme, const BUTTON_INFO *infoPtr, HDC hDC, int state, UINT dtFlags, BOOL focused)
2991 HFONT old_font = infoPtr->font ? SelectObject(hDC, infoPtr->font) : NULL;
2992 NMCUSTOMDRAW nmcd;
2993 LRESULT cdrf;
2994 HWND parent;
2995 RECT rc;
2997 GetClientRect(infoPtr->hwnd, &rc);
2998 init_custom_draw(&nmcd, infoPtr, hDC, &rc);
3000 parent = GetParent(infoPtr->hwnd);
3001 if (!parent) parent = infoPtr->hwnd;
3003 /* Send erase notifications */
3004 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
3005 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
3007 if (IsThemeBackgroundPartiallyTransparent(theme, BP_COMMANDLINK, state))
3008 DrawThemeParentBackground(infoPtr->hwnd, hDC, NULL);
3009 DrawThemeBackground(theme, hDC, BP_COMMANDLINK, state, &rc, NULL);
3011 if (cdrf & CDRF_NOTIFYPOSTERASE)
3013 nmcd.dwDrawStage = CDDS_POSTERASE;
3014 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
3017 /* Send paint notifications */
3018 nmcd.dwDrawStage = CDDS_PREPAINT;
3019 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
3020 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
3022 if (!(cdrf & CDRF_DOERASE))
3024 RECT r, img_rect;
3025 UINT txt_h = 0;
3026 SIZE img_size;
3027 WCHAR *text;
3029 GetThemeBackgroundContentRect(theme, hDC, BP_COMMANDLINK, state, &rc, &r);
3031 /* The text alignment and styles are fixed and don't depend on button styles */
3032 dtFlags = DT_TOP | DT_LEFT | DT_WORDBREAK;
3034 /* Command Links ignore the margins of the image list or its alignment */
3035 if (infoPtr->u.image || infoPtr->imagelist.himl)
3036 img_size = BUTTON_GetImageSize(infoPtr);
3037 else
3038 GetThemePartSize(theme, NULL, BP_COMMANDLINKGLYPH, state, NULL, TS_DRAW, &img_size);
3040 img_rect = r;
3041 if (img_size.cx) r.left += img_size.cx + command_link_margin;
3043 /* Draw the text */
3044 if ((text = get_button_text(infoPtr)))
3046 UINT len = lstrlenW(text);
3047 RECT text_rect;
3049 GetThemeTextExtent(theme, hDC, BP_COMMANDLINK, state, text, len,
3050 dtFlags | DT_END_ELLIPSIS, &r, &text_rect);
3051 DrawThemeText(theme, hDC, BP_COMMANDLINK, state, text, len,
3052 dtFlags | DT_END_ELLIPSIS, 0, &r);
3054 txt_h = text_rect.bottom - text_rect.top;
3055 heap_free(text);
3058 /* Draw the note */
3059 if (infoPtr->note)
3061 DTTOPTS opts;
3063 r.top += txt_h;
3064 opts.dwSize = sizeof(opts);
3065 opts.dwFlags = DTT_FONTPROP;
3066 opts.iFontPropId = TMT_BODYFONT;
3067 DrawThemeTextEx(theme, hDC, BP_COMMANDLINK, state,
3068 infoPtr->note, infoPtr->note_length,
3069 dtFlags | DT_NOPREFIX, &r, &opts);
3072 /* Position the image at the vertical center of the drawn text (not note) */
3073 txt_h = min(txt_h, img_rect.bottom - img_rect.top);
3074 if (img_size.cy < txt_h) img_rect.top += (txt_h - img_size.cy) / 2;
3076 img_rect.right = img_rect.left + img_size.cx;
3077 img_rect.bottom = img_rect.top + img_size.cy;
3079 if (infoPtr->u.image || infoPtr->imagelist.himl)
3080 BUTTON_DrawImage(infoPtr, hDC, NULL,
3081 (state == CMDLS_DISABLED) ? DSS_DISABLED : DSS_NORMAL,
3082 &img_rect);
3083 else
3084 DrawThemeBackground(theme, hDC, BP_COMMANDLINKGLYPH, state, &img_rect, NULL);
3087 if (cdrf & CDRF_NOTIFYPOSTPAINT)
3089 nmcd.dwDrawStage = CDDS_POSTPAINT;
3090 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
3092 if (cdrf & CDRF_SKIPPOSTPAINT) goto cleanup;
3094 if (focused)
3096 MARGINS margins;
3098 /* The focus rect has margins of a push button rather than command link... */
3099 GetThemeMargins(theme, hDC, BP_PUSHBUTTON, state, TMT_CONTENTMARGINS, NULL, &margins);
3101 rc.left += margins.cxLeftWidth;
3102 rc.top += margins.cyTopHeight;
3103 rc.right -= margins.cxRightWidth;
3104 rc.bottom -= margins.cyBottomHeight;
3105 DrawFocusRect(hDC, &rc);
3108 cleanup:
3109 if (old_font) SelectObject(hDC, old_font);
3112 void BUTTON_Register(void)
3114 WNDCLASSW wndClass;
3116 memset(&wndClass, 0, sizeof(wndClass));
3117 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS | CS_VREDRAW | CS_HREDRAW | CS_PARENTDC;
3118 wndClass.lpfnWndProc = BUTTON_WindowProc;
3119 wndClass.cbClsExtra = 0;
3120 wndClass.cbWndExtra = sizeof(BUTTON_INFO *);
3121 wndClass.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW);
3122 wndClass.hbrBackground = NULL;
3123 wndClass.lpszClassName = WC_BUTTONW;
3124 RegisterClassW(&wndClass);