wined3d: Use wined3d_bit_scan() in context_preload_textures().
[wine.git] / dlls / comctl32 / button.c
blob88b48a4a5c0b23eed5f9371a97cff394d502cfe5
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 pushlike_cb_states[3][DRAW_STATE_COUNT] =
398 { PBS_NORMAL, PBS_DISABLED, PBS_HOT, PBS_PRESSED, PBS_NORMAL },
399 { PBS_PRESSED, PBS_PRESSED, PBS_HOT, PBS_PRESSED, PBS_PRESSED },
400 { PBS_NORMAL, PBS_DISABLED, PBS_HOT, PBS_PRESSED, PBS_NORMAL }
402 static const int rb_states[2][DRAW_STATE_COUNT] =
404 { RBS_UNCHECKEDNORMAL, RBS_UNCHECKEDDISABLED, RBS_UNCHECKEDHOT, RBS_UNCHECKEDPRESSED, RBS_UNCHECKEDNORMAL },
405 { RBS_CHECKEDNORMAL, RBS_CHECKEDDISABLED, RBS_CHECKEDHOT, RBS_CHECKEDPRESSED, RBS_CHECKEDNORMAL }
407 static const int pushlike_rb_states[2][DRAW_STATE_COUNT] =
409 { PBS_NORMAL, PBS_DISABLED, PBS_HOT, PBS_PRESSED, PBS_NORMAL },
410 { PBS_PRESSED, PBS_PRESSED, PBS_HOT, PBS_PRESSED, PBS_PRESSED }
412 static const int gb_states[DRAW_STATE_COUNT] = { GBS_NORMAL, GBS_DISABLED, GBS_NORMAL, GBS_NORMAL, GBS_NORMAL };
413 LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
414 UINT type = get_button_type(style);
415 int check_state = infoPtr->state & 3;
416 enum draw_state state;
418 if (!IsWindowEnabled(infoPtr->hwnd))
419 state = STATE_DISABLED;
420 else if (infoPtr->state & BST_PUSHED)
421 state = STATE_PRESSED;
422 else if (infoPtr->state & BST_HOT)
423 state = STATE_HOT;
424 else if (infoPtr->state & BST_FOCUS || type == BS_DEFPUSHBUTTON || type == BS_DEFSPLITBUTTON
425 || (type == BS_DEFCOMMANDLINK && !(style & BS_PUSHLIKE)))
426 state = STATE_DEFAULTED;
427 else
428 state = STATE_NORMAL;
430 switch (type)
432 case BS_PUSHBUTTON:
433 case BS_DEFPUSHBUTTON:
434 case BS_USERBUTTON:
435 case BS_SPLITBUTTON:
436 case BS_DEFSPLITBUTTON:
437 case BS_COMMANDLINK:
438 case BS_DEFCOMMANDLINK:
439 return pb_states[state];
440 case BS_CHECKBOX:
441 case BS_AUTOCHECKBOX:
442 case BS_3STATE:
443 case BS_AUTO3STATE:
444 return style & BS_PUSHLIKE ? pushlike_cb_states[check_state][state]
445 : cb_states[check_state][state];
446 case BS_RADIOBUTTON:
447 case BS_AUTORADIOBUTTON:
448 return style & BS_PUSHLIKE ? pushlike_rb_states[check_state][state]
449 : rb_states[check_state][state];
450 case BS_GROUPBOX:
451 return style & BS_PUSHLIKE ? pb_states[state] : gb_states[state];
452 default:
453 WARN("Unsupported button type 0x%08x\n", type);
454 return PBS_NORMAL;
458 static LRESULT CALLBACK BUTTON_WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
460 BUTTON_INFO *infoPtr = (BUTTON_INFO *)GetWindowLongPtrW(hWnd, 0);
461 RECT rect;
462 POINT pt;
463 LONG style = GetWindowLongW( hWnd, GWL_STYLE );
464 UINT btn_type = get_button_type( style );
465 LONG state, new_state;
466 HANDLE oldHbitmap;
467 HTHEME theme;
469 if (!IsWindow( hWnd )) return 0;
471 if (!infoPtr && (uMsg != WM_NCCREATE))
472 return DefWindowProcW(hWnd, uMsg, wParam, lParam);
474 pt.x = (short)LOWORD(lParam);
475 pt.y = (short)HIWORD(lParam);
477 switch (uMsg)
479 case WM_GETDLGCODE:
480 switch(btn_type)
482 case BS_COMMANDLINK:
483 case BS_USERBUTTON:
484 case BS_PUSHBUTTON: return DLGC_BUTTON | DLGC_UNDEFPUSHBUTTON;
485 case BS_DEFCOMMANDLINK:
486 case BS_DEFPUSHBUTTON: return DLGC_BUTTON | DLGC_DEFPUSHBUTTON;
487 case BS_RADIOBUTTON:
488 case BS_AUTORADIOBUTTON: return DLGC_BUTTON | DLGC_RADIOBUTTON;
489 case BS_GROUPBOX: return DLGC_STATIC;
490 case BS_SPLITBUTTON: return DLGC_BUTTON | DLGC_UNDEFPUSHBUTTON | DLGC_WANTARROWS;
491 case BS_DEFSPLITBUTTON: return DLGC_BUTTON | DLGC_DEFPUSHBUTTON | DLGC_WANTARROWS;
492 default: return DLGC_BUTTON;
495 case WM_ENABLE:
496 theme = GetWindowTheme( hWnd );
497 if (theme)
498 RedrawWindow( hWnd, NULL, NULL, RDW_FRAME | RDW_INVALIDATE | RDW_UPDATENOW );
499 else
500 paint_button( infoPtr, btn_type, ODA_DRAWENTIRE );
501 break;
503 case WM_NCCREATE:
505 CREATESTRUCTW *cs = (CREATESTRUCTW *)lParam;
507 infoPtr = heap_alloc_zero( sizeof(*infoPtr) );
508 SetWindowLongPtrW( hWnd, 0, (LONG_PTR)infoPtr );
509 infoPtr->hwnd = hWnd;
510 infoPtr->parent = cs->hwndParent;
511 infoPtr->style = cs->style;
512 infoPtr->split_style = BCSS_STRETCH;
513 infoPtr->glyph = (HIMAGELIST)0x36; /* Marlett down arrow char code */
514 infoPtr->glyph_size.cx = get_default_glyph_size(infoPtr);
515 return DefWindowProcW(hWnd, uMsg, wParam, lParam);
518 case WM_NCDESTROY:
519 SetWindowLongPtrW( hWnd, 0, 0 );
520 if (infoPtr->image_type == IMAGE_BITMAP)
521 DeleteObject(infoPtr->u.bitmap);
522 else if (infoPtr->image_type == IMAGE_ICON)
523 DestroyIcon(infoPtr->u.icon);
524 heap_free(infoPtr->note);
525 heap_free(infoPtr);
526 break;
528 case WM_CREATE:
530 HWND parent;
532 if (btn_type >= MAX_BTN_TYPE)
533 return -1; /* abort */
535 /* XP turns a BS_USERBUTTON into BS_PUSHBUTTON */
536 if (btn_type == BS_USERBUTTON )
538 style = (style & ~BS_TYPEMASK) | BS_PUSHBUTTON;
539 SetWindowLongW( hWnd, GWL_STYLE, style );
541 infoPtr->state = BST_UNCHECKED;
542 OpenThemeData( hWnd, WC_BUTTONW );
544 parent = GetParent( hWnd );
545 if (parent)
546 EnableThemeDialogTexture( parent, ETDT_ENABLE );
547 return 0;
550 case WM_DESTROY:
551 theme = GetWindowTheme( hWnd );
552 CloseThemeData( theme );
553 break;
555 case WM_THEMECHANGED:
556 theme = GetWindowTheme( hWnd );
557 CloseThemeData( theme );
558 OpenThemeData( hWnd, WC_BUTTONW );
559 InvalidateRect( hWnd, NULL, TRUE );
560 break;
562 case WM_ERASEBKGND:
563 if (btn_type == BS_OWNERDRAW)
565 HDC hdc = (HDC)wParam;
566 RECT rc;
567 HBRUSH hBrush;
568 HWND parent = GetParent(hWnd);
569 if (!parent) parent = hWnd;
570 hBrush = (HBRUSH)SendMessageW(parent, WM_CTLCOLORBTN, (WPARAM)hdc, (LPARAM)hWnd);
571 if (!hBrush) /* did the app forget to call defwindowproc ? */
572 hBrush = (HBRUSH)DefWindowProcW(parent, WM_CTLCOLORBTN,
573 (WPARAM)hdc, (LPARAM)hWnd);
574 GetClientRect(hWnd, &rc);
575 FillRect(hdc, &rc, hBrush);
577 return 1;
579 case WM_PRINTCLIENT:
580 case WM_PAINT:
582 PAINTSTRUCT ps;
583 HDC hdc;
585 theme = GetWindowTheme( hWnd );
586 hdc = wParam ? (HDC)wParam : BeginPaint( hWnd, &ps );
588 if (is_themed_paint_supported(theme, btn_type))
590 int drawState = get_draw_state(infoPtr);
591 UINT dtflags = BUTTON_BStoDT(style, GetWindowLongW(hWnd, GWL_EXSTYLE));
593 btnThemedPaintFunc[btn_type](theme, infoPtr, hdc, drawState, dtflags, infoPtr->state & BST_FOCUS);
595 else if (btnPaintFunc[btn_type])
597 int nOldMode = SetBkMode( hdc, OPAQUE );
598 btnPaintFunc[btn_type]( infoPtr, hdc, ODA_DRAWENTIRE );
599 SetBkMode(hdc, nOldMode); /* reset painting mode */
602 if ( !wParam ) EndPaint( hWnd, &ps );
603 break;
606 case WM_KEYDOWN:
607 if (wParam == VK_SPACE)
609 SendMessageW( hWnd, BM_SETSTATE, TRUE, 0 );
610 infoPtr->state |= BUTTON_BTNPRESSED;
611 SetCapture( hWnd );
613 else if (wParam == VK_UP || wParam == VK_DOWN)
615 /* Up and down arrows work on every button, and even with BCSS_NOSPLIT */
616 notify_split_button_dropdown(infoPtr, NULL, hWnd);
618 break;
620 case WM_LBUTTONDBLCLK:
621 if(style & BS_NOTIFY ||
622 btn_type == BS_RADIOBUTTON ||
623 btn_type == BS_USERBUTTON ||
624 btn_type == BS_OWNERDRAW)
626 BUTTON_NOTIFY_PARENT(hWnd, BN_DOUBLECLICKED);
627 break;
629 /* fall through */
630 case WM_LBUTTONDOWN:
631 SetFocus( hWnd );
633 if ((btn_type == BS_SPLITBUTTON || btn_type == BS_DEFSPLITBUTTON) &&
634 !(infoPtr->split_style & BCSS_NOSPLIT) &&
635 notify_split_button_dropdown(infoPtr, &pt, hWnd))
636 break;
638 SetCapture( hWnd );
639 infoPtr->state |= BUTTON_BTNPRESSED;
640 SendMessageW( hWnd, BM_SETSTATE, TRUE, 0 );
641 break;
643 case WM_KEYUP:
644 if (wParam != VK_SPACE)
645 break;
646 /* fall through */
647 case WM_LBUTTONUP:
648 state = infoPtr->state;
649 if (state & BST_DROPDOWNPUSHED)
650 SendMessageW(hWnd, BCM_SETDROPDOWNSTATE, FALSE, 0);
651 if (!(state & BUTTON_BTNPRESSED)) break;
652 infoPtr->state &= BUTTON_NSTATES | BST_HOT;
653 if (!(state & BST_PUSHED))
655 ReleaseCapture();
656 break;
658 SendMessageW( hWnd, BM_SETSTATE, FALSE, 0 );
659 GetClientRect( hWnd, &rect );
660 if (uMsg == WM_KEYUP || PtInRect( &rect, pt ))
662 switch(btn_type)
664 case BS_AUTOCHECKBOX:
665 SendMessageW( hWnd, BM_SETCHECK, !(infoPtr->state & BST_CHECKED), 0 );
666 break;
667 case BS_AUTORADIOBUTTON:
668 SendMessageW( hWnd, BM_SETCHECK, TRUE, 0 );
669 break;
670 case BS_AUTO3STATE:
671 SendMessageW( hWnd, BM_SETCHECK, (infoPtr->state & BST_INDETERMINATE) ? 0 :
672 ((infoPtr->state & 3) + 1), 0 );
673 break;
675 ReleaseCapture();
676 BUTTON_NOTIFY_PARENT(hWnd, BN_CLICKED);
678 else
680 ReleaseCapture();
682 break;
684 case WM_CAPTURECHANGED:
685 TRACE("WM_CAPTURECHANGED %p\n", hWnd);
686 if (hWnd == (HWND)lParam) break;
687 if (infoPtr->state & BUTTON_BTNPRESSED)
689 infoPtr->state &= BUTTON_NSTATES;
690 if (infoPtr->state & BST_PUSHED)
691 SendMessageW( hWnd, BM_SETSTATE, FALSE, 0 );
693 break;
695 case WM_MOUSEMOVE:
697 TRACKMOUSEEVENT mouse_event;
699 mouse_event.cbSize = sizeof(TRACKMOUSEEVENT);
700 mouse_event.dwFlags = TME_QUERY;
701 if (!TrackMouseEvent(&mouse_event) || !(mouse_event.dwFlags & (TME_HOVER | TME_LEAVE)))
703 mouse_event.dwFlags = TME_HOVER | TME_LEAVE;
704 mouse_event.hwndTrack = hWnd;
705 mouse_event.dwHoverTime = 1;
706 TrackMouseEvent(&mouse_event);
709 if ((wParam & MK_LBUTTON) && GetCapture() == hWnd)
711 GetClientRect( hWnd, &rect );
712 SendMessageW( hWnd, BM_SETSTATE, PtInRect(&rect, pt), 0 );
714 break;
717 case WM_MOUSEHOVER:
719 infoPtr->state |= BST_HOT;
720 InvalidateRect( hWnd, NULL, FALSE );
721 break;
724 case WM_MOUSELEAVE:
726 infoPtr->state &= ~BST_HOT;
727 InvalidateRect( hWnd, NULL, FALSE );
728 break;
731 case WM_SETTEXT:
733 /* Clear an old text here as Windows does */
734 if (IsWindowVisible(hWnd))
736 HDC hdc = GetDC(hWnd);
737 HBRUSH hbrush;
738 RECT client, rc;
739 HWND parent = GetParent(hWnd);
740 UINT message = (btn_type == BS_PUSHBUTTON ||
741 btn_type == BS_DEFPUSHBUTTON ||
742 btn_type == BS_USERBUTTON ||
743 btn_type == BS_OWNERDRAW) ?
744 WM_CTLCOLORBTN : WM_CTLCOLORSTATIC;
746 if (!parent) parent = hWnd;
747 hbrush = (HBRUSH)SendMessageW(parent, message,
748 (WPARAM)hdc, (LPARAM)hWnd);
749 if (!hbrush) /* did the app forget to call DefWindowProc ? */
750 hbrush = (HBRUSH)DefWindowProcW(parent, message,
751 (WPARAM)hdc, (LPARAM)hWnd);
753 GetClientRect(hWnd, &client);
754 rc = client;
755 /* FIXME: check other BS_* handlers */
756 if (btn_type == BS_GROUPBOX)
757 InflateRect(&rc, -7, 1); /* GB_Paint does this */
758 BUTTON_CalcLayoutRects(infoPtr, hdc, &rc, NULL, NULL);
759 /* Clip by client rect bounds */
760 if (rc.right > client.right) rc.right = client.right;
761 if (rc.bottom > client.bottom) rc.bottom = client.bottom;
762 FillRect(hdc, &rc, hbrush);
763 ReleaseDC(hWnd, hdc);
766 DefWindowProcW( hWnd, WM_SETTEXT, wParam, lParam );
767 if (btn_type == BS_GROUPBOX) /* Yes, only for BS_GROUPBOX */
768 InvalidateRect( hWnd, NULL, TRUE );
769 else if (GetWindowTheme( hWnd ))
770 RedrawWindow( hWnd, NULL, NULL, RDW_INVALIDATE | RDW_UPDATENOW );
771 else
772 paint_button( infoPtr, btn_type, ODA_DRAWENTIRE );
773 return 1; /* success. FIXME: check text length */
776 case BCM_SETNOTE:
778 WCHAR *note = (WCHAR *)lParam;
779 if (btn_type != BS_COMMANDLINK && btn_type != BS_DEFCOMMANDLINK)
781 SetLastError(ERROR_NOT_SUPPORTED);
782 return FALSE;
785 heap_free(infoPtr->note);
786 if (note)
788 infoPtr->note_length = lstrlenW(note);
789 infoPtr->note = heap_strndupW(note, infoPtr->note_length);
792 if (!note || !infoPtr->note)
794 infoPtr->note_length = 0;
795 infoPtr->note = heap_alloc_zero(sizeof(WCHAR));
798 SetLastError(NO_ERROR);
799 return TRUE;
802 case BCM_GETNOTE:
804 DWORD *size = (DWORD *)wParam;
805 WCHAR *buffer = (WCHAR *)lParam;
806 INT length = 0;
808 if (btn_type != BS_COMMANDLINK && btn_type != BS_DEFCOMMANDLINK)
810 SetLastError(ERROR_NOT_SUPPORTED);
811 return FALSE;
814 if (!buffer || !size || !infoPtr->note)
816 SetLastError(ERROR_INVALID_PARAMETER);
817 return FALSE;
820 if (*size > 0)
822 length = min(*size - 1, infoPtr->note_length);
823 memcpy(buffer, infoPtr->note, length * sizeof(WCHAR));
824 buffer[length] = '\0';
827 if (*size < infoPtr->note_length + 1)
829 *size = infoPtr->note_length + 1;
830 SetLastError(ERROR_INSUFFICIENT_BUFFER);
831 return FALSE;
833 else
835 SetLastError(NO_ERROR);
836 return TRUE;
840 case BCM_GETNOTELENGTH:
842 if (btn_type != BS_COMMANDLINK && btn_type != BS_DEFCOMMANDLINK)
844 SetLastError(ERROR_NOT_SUPPORTED);
845 return 0;
848 return infoPtr->note_length;
851 case WM_SETFONT:
852 infoPtr->font = (HFONT)wParam;
853 if (lParam) InvalidateRect(hWnd, NULL, TRUE);
854 break;
856 case WM_GETFONT:
857 return (LRESULT)infoPtr->font;
859 case WM_SETFOCUS:
860 TRACE("WM_SETFOCUS %p\n",hWnd);
861 infoPtr->state |= BST_FOCUS;
863 if (btn_type == BS_OWNERDRAW)
864 paint_button( infoPtr, btn_type, ODA_FOCUS );
865 else
866 InvalidateRect(hWnd, NULL, FALSE);
868 if (style & BS_NOTIFY)
869 BUTTON_NOTIFY_PARENT(hWnd, BN_SETFOCUS);
870 break;
872 case WM_KILLFOCUS:
873 TRACE("WM_KILLFOCUS %p\n",hWnd);
874 infoPtr->state &= ~BST_FOCUS;
876 if ((infoPtr->state & BUTTON_BTNPRESSED) && GetCapture() == hWnd)
877 ReleaseCapture();
878 if (style & BS_NOTIFY)
879 BUTTON_NOTIFY_PARENT(hWnd, BN_KILLFOCUS);
881 InvalidateRect( hWnd, NULL, FALSE );
882 break;
884 case WM_SYSCOLORCHANGE:
885 InvalidateRect( hWnd, NULL, FALSE );
886 break;
888 case BM_SETSTYLE:
890 DWORD new_btn_type;
892 new_btn_type= wParam & BS_TYPEMASK;
893 if (btn_type >= BS_SPLITBUTTON && new_btn_type <= BS_DEFPUSHBUTTON)
894 new_btn_type = (btn_type & ~BS_DEFPUSHBUTTON) | new_btn_type;
896 style = (style & ~BS_TYPEMASK) | new_btn_type;
897 SetWindowLongW( hWnd, GWL_STYLE, style );
899 /* Only redraw if lParam flag is set.*/
900 if (lParam)
901 InvalidateRect( hWnd, NULL, TRUE );
903 break;
905 case BM_CLICK:
906 SendMessageW( hWnd, WM_LBUTTONDOWN, 0, 0 );
907 SendMessageW( hWnd, WM_LBUTTONUP, 0, 0 );
908 break;
910 case BM_SETIMAGE:
911 infoPtr->image_type = (DWORD)wParam;
912 oldHbitmap = infoPtr->image;
913 infoPtr->u.image = CopyImage((HANDLE)lParam, infoPtr->image_type, 0, 0, 0);
914 infoPtr->image = (HANDLE)lParam;
915 InvalidateRect( hWnd, NULL, FALSE );
916 return (LRESULT)oldHbitmap;
918 case BM_GETIMAGE:
919 return (LRESULT)infoPtr->image;
921 case BCM_SETIMAGELIST:
923 BUTTON_IMAGELIST *imagelist = (BUTTON_IMAGELIST *)lParam;
925 if (!imagelist) return FALSE;
927 infoPtr->imagelist = *imagelist;
928 return TRUE;
931 case BCM_GETIMAGELIST:
933 BUTTON_IMAGELIST *imagelist = (BUTTON_IMAGELIST *)lParam;
935 if (!imagelist) return FALSE;
937 *imagelist = infoPtr->imagelist;
938 return TRUE;
941 case BCM_SETSPLITINFO:
943 BUTTON_SPLITINFO *info = (BUTTON_SPLITINFO*)lParam;
945 if (!info) return TRUE;
947 if (info->mask & (BCSIF_GLYPH | BCSIF_IMAGE))
949 infoPtr->split_style &= ~BCSS_IMAGE;
950 if (!(info->mask & BCSIF_GLYPH))
951 infoPtr->split_style |= BCSS_IMAGE;
952 infoPtr->glyph = info->himlGlyph;
953 infoPtr->glyph_size.cx = infoPtr->glyph_size.cy = 0;
956 if (info->mask & BCSIF_STYLE)
957 infoPtr->split_style = info->uSplitStyle;
958 if (info->mask & BCSIF_SIZE)
959 infoPtr->glyph_size = info->size;
961 /* Calculate fitting value for cx if invalid (cy is untouched) */
962 if (infoPtr->glyph_size.cx <= 0)
963 infoPtr->glyph_size.cx = get_default_glyph_size(infoPtr);
965 /* Windows doesn't invalidate or redraw it, so we don't, either */
966 return TRUE;
969 case BCM_GETSPLITINFO:
971 BUTTON_SPLITINFO *info = (BUTTON_SPLITINFO*)lParam;
973 if (!info) return FALSE;
975 if (info->mask & BCSIF_STYLE)
976 info->uSplitStyle = infoPtr->split_style;
977 if (info->mask & (BCSIF_GLYPH | BCSIF_IMAGE))
978 info->himlGlyph = infoPtr->glyph;
979 if (info->mask & BCSIF_SIZE)
980 info->size = infoPtr->glyph_size;
982 return TRUE;
985 case BM_GETCHECK:
986 return infoPtr->state & 3;
988 case BM_SETCHECK:
989 if (wParam > maxCheckState[btn_type]) wParam = maxCheckState[btn_type];
990 if ((btn_type == BS_RADIOBUTTON) || (btn_type == BS_AUTORADIOBUTTON))
992 style = wParam ? style | WS_TABSTOP : style & ~WS_TABSTOP;
993 SetWindowLongW( hWnd, GWL_STYLE, style );
995 if ((infoPtr->state & 3) != wParam)
997 infoPtr->state = (infoPtr->state & ~3) | wParam;
998 InvalidateRect( hWnd, NULL, FALSE );
1000 if ((btn_type == BS_AUTORADIOBUTTON) && (wParam == BST_CHECKED) && (style & WS_CHILD))
1001 BUTTON_CheckAutoRadioButton( hWnd );
1002 break;
1004 case BM_GETSTATE:
1005 return infoPtr->state;
1007 case BM_SETSTATE:
1008 state = infoPtr->state;
1009 new_state = wParam ? BST_PUSHED : 0;
1011 if ((state ^ new_state) & BST_PUSHED)
1013 if (wParam)
1014 state |= BST_PUSHED;
1015 else
1016 state &= ~BST_PUSHED;
1018 if (btn_type == BS_USERBUTTON)
1019 BUTTON_NOTIFY_PARENT( hWnd, (state & BST_PUSHED) ? BN_HILITE : BN_UNHILITE );
1020 infoPtr->state = state;
1022 InvalidateRect( hWnd, NULL, FALSE );
1024 break;
1026 case BCM_SETDROPDOWNSTATE:
1027 new_state = wParam ? BST_DROPDOWNPUSHED : 0;
1029 if ((infoPtr->state ^ new_state) & BST_DROPDOWNPUSHED)
1031 infoPtr->state &= ~BST_DROPDOWNPUSHED;
1032 infoPtr->state |= new_state;
1033 InvalidateRect(hWnd, NULL, FALSE);
1035 break;
1037 case BCM_SETTEXTMARGIN:
1039 RECT *text_margin = (RECT *)lParam;
1041 if (!text_margin) return FALSE;
1043 infoPtr->text_margin = *text_margin;
1044 return TRUE;
1047 case BCM_GETTEXTMARGIN:
1049 RECT *text_margin = (RECT *)lParam;
1051 if (!text_margin) return FALSE;
1053 *text_margin = infoPtr->text_margin;
1054 return TRUE;
1057 case BCM_GETIDEALSIZE:
1059 SIZE *size = (SIZE *)lParam;
1061 if (!size) return FALSE;
1063 return btnGetIdealSizeFunc[btn_type](infoPtr, size);
1066 case WM_NCHITTEST:
1067 if(btn_type == BS_GROUPBOX) return HTTRANSPARENT;
1068 /* fall through */
1069 default:
1070 return DefWindowProcW(hWnd, uMsg, wParam, lParam);
1072 return 0;
1075 /* If maxWidth is zero, rectangle width is unlimited */
1076 static RECT BUTTON_GetTextRect(const BUTTON_INFO *infoPtr, HDC hdc, const WCHAR *text, LONG maxWidth)
1078 LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1079 LONG exStyle = GetWindowLongW(infoPtr->hwnd, GWL_EXSTYLE);
1080 UINT dtStyle = BUTTON_BStoDT(style, exStyle);
1081 HFONT hPrevFont;
1082 RECT rect = {0};
1084 rect.right = maxWidth;
1085 hPrevFont = SelectObject(hdc, infoPtr->font);
1086 /* Calculate height without DT_VCENTER and DT_BOTTOM to get the correct height */
1087 DrawTextW(hdc, text, -1, &rect, (dtStyle & ~(DT_VCENTER | DT_BOTTOM)) | DT_CALCRECT);
1088 if (hPrevFont) SelectObject(hdc, hPrevFont);
1090 return rect;
1093 static BOOL show_image_only(const BUTTON_INFO *infoPtr)
1095 LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1096 return (style & (BS_ICON | BS_BITMAP)) && (infoPtr->u.image || infoPtr->imagelist.himl);
1099 static BOOL show_image_and_text(const BUTTON_INFO *infoPtr)
1101 LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1102 UINT type = get_button_type(style);
1103 return !(style & (BS_ICON | BS_BITMAP))
1104 && ((infoPtr->u.image
1105 && (type == BS_PUSHBUTTON || type == BS_DEFPUSHBUTTON || type == BS_USERBUTTON || type == BS_SPLITBUTTON
1106 || type == BS_DEFSPLITBUTTON || type == BS_COMMANDLINK || type == BS_DEFCOMMANDLINK))
1107 || (infoPtr->imagelist.himl && type != BS_GROUPBOX));
1110 static BOOL show_image(const BUTTON_INFO *infoPtr)
1112 return show_image_only(infoPtr) || show_image_and_text(infoPtr);
1115 /* Get a bounding rectangle that is large enough to contain a image and a text side by side.
1116 * Note: (left,top) of the result rectangle may not be (0,0), offset it by yourself if needed */
1117 static RECT BUTTON_GetBoundingLabelRect(LONG style, const RECT *textRect, const RECT *imageRect)
1119 RECT labelRect;
1120 RECT rect = *imageRect;
1121 INT textWidth = textRect->right - textRect->left;
1122 INT textHeight = textRect->bottom - textRect->top;
1123 INT imageWidth = imageRect->right - imageRect->left;
1124 INT imageHeight = imageRect->bottom - imageRect->top;
1126 if ((style & BS_CENTER) == BS_RIGHT)
1127 OffsetRect(&rect, textWidth, 0);
1128 else if ((style & BS_CENTER) == BS_LEFT)
1129 OffsetRect(&rect, -imageWidth, 0);
1130 else if ((style & BS_VCENTER) == BS_BOTTOM)
1131 OffsetRect(&rect, 0, textHeight);
1132 else if ((style & BS_VCENTER) == BS_TOP)
1133 OffsetRect(&rect, 0, -imageHeight);
1134 else
1135 OffsetRect(&rect, -imageWidth, 0);
1137 UnionRect(&labelRect, textRect, &rect);
1138 return labelRect;
1141 /* Position a rectangle inside a bounding rectangle according to button alignment flags */
1142 static void BUTTON_PositionRect(LONG style, const RECT *outerRect, RECT *innerRect, const RECT *margin)
1144 INT width = innerRect->right - innerRect->left;
1145 INT height = innerRect->bottom - innerRect->top;
1147 if ((style & BS_PUSHLIKE) && !(style & BS_CENTER)) style |= BS_CENTER;
1149 if (!(style & BS_CENTER))
1151 if (button_centers_text(style))
1152 style |= BS_CENTER;
1153 else
1154 style |= BS_LEFT;
1157 if (!(style & BS_VCENTER))
1159 /* Group box's text is top aligned by default */
1160 if (get_button_type(style) == BS_GROUPBOX)
1161 style |= BS_TOP;
1164 switch (style & BS_CENTER)
1166 case BS_CENTER:
1167 /* The left and right margins are added to the inner rectangle to get a new rectangle. Then
1168 * the new rectangle is adjusted to be in the horizontal center */
1169 innerRect->left = outerRect->left + (outerRect->right - outerRect->left - width
1170 + margin->left - margin->right) / 2;
1171 innerRect->right = innerRect->left + width;
1172 break;
1173 case BS_RIGHT:
1174 innerRect->right = outerRect->right - margin->right;
1175 innerRect->left = innerRect->right - width;
1176 break;
1177 case BS_LEFT:
1178 default:
1179 innerRect->left = outerRect->left + margin->left;
1180 innerRect->right = innerRect->left + width;
1181 break;
1184 switch (style & BS_VCENTER)
1186 case BS_TOP:
1187 innerRect->top = outerRect->top + margin->top;
1188 innerRect->bottom = innerRect->top + height;
1189 break;
1190 case BS_BOTTOM:
1191 innerRect->bottom = outerRect->bottom - margin->bottom;
1192 innerRect->top = innerRect->bottom - height;
1193 break;
1194 case BS_VCENTER:
1195 default:
1196 /* The top and bottom margins are added to the inner rectangle to get a new rectangle. Then
1197 * the new rectangle is adjusted to be in the vertical center */
1198 innerRect->top = outerRect->top + (outerRect->bottom - outerRect->top - height
1199 + margin->top - margin->bottom) / 2;
1200 innerRect->bottom = innerRect->top + height;
1201 break;
1205 /* Convert imagelist align style to button align style */
1206 static UINT BUTTON_ILStoBS(UINT align)
1208 switch (align)
1210 case BUTTON_IMAGELIST_ALIGN_TOP:
1211 return BS_CENTER | BS_TOP;
1212 case BUTTON_IMAGELIST_ALIGN_BOTTOM:
1213 return BS_CENTER | BS_BOTTOM;
1214 case BUTTON_IMAGELIST_ALIGN_CENTER:
1215 return BS_CENTER | BS_VCENTER;
1216 case BUTTON_IMAGELIST_ALIGN_RIGHT:
1217 return BS_RIGHT | BS_VCENTER;
1218 case BUTTON_IMAGELIST_ALIGN_LEFT:
1219 default:
1220 return BS_LEFT | BS_VCENTER;
1224 static SIZE BUTTON_GetImageSize(const BUTTON_INFO *infoPtr)
1226 ICONINFO iconInfo;
1227 BITMAP bm = {0};
1228 SIZE size = {0};
1230 /* ImageList has priority over image */
1231 if (infoPtr->imagelist.himl)
1233 int scx, scy;
1234 ImageList_GetIconSize(infoPtr->imagelist.himl, &scx, &scy);
1235 size.cx = scx;
1236 size.cy = scy;
1238 else if (infoPtr->u.image)
1240 if (infoPtr->image_type == IMAGE_ICON)
1242 GetIconInfo(infoPtr->u.icon, &iconInfo);
1243 GetObjectW(iconInfo.hbmColor, sizeof(bm), &bm);
1244 DeleteObject(iconInfo.hbmColor);
1245 DeleteObject(iconInfo.hbmMask);
1247 else if (infoPtr->image_type == IMAGE_BITMAP)
1248 GetObjectW(infoPtr->u.bitmap, sizeof(bm), &bm);
1250 size.cx = bm.bmWidth;
1251 size.cy = bm.bmHeight;
1254 return size;
1257 static const RECT *BUTTON_GetTextMargin(const BUTTON_INFO *infoPtr)
1259 static const RECT oneMargin = {1, 1, 1, 1};
1261 /* Use text margin only when showing both image and text, and image is not imagelist */
1262 if (show_image_and_text(infoPtr) && !infoPtr->imagelist.himl)
1263 return &infoPtr->text_margin;
1264 else
1265 return &oneMargin;
1268 static void BUTTON_GetClientRectSize(BUTTON_INFO *infoPtr, SIZE *size)
1270 RECT rect;
1271 GetClientRect(infoPtr->hwnd, &rect);
1272 size->cx = rect.right - rect.left;
1273 size->cy = rect.bottom - rect.top;
1276 static void BUTTON_GetTextIdealSize(BUTTON_INFO *infoPtr, LONG maxWidth, SIZE *size)
1278 WCHAR *text = get_button_text(infoPtr);
1279 HDC hdc;
1280 RECT rect;
1281 const RECT *margin = BUTTON_GetTextMargin(infoPtr);
1283 if (maxWidth != 0)
1285 maxWidth -= margin->right + margin->right;
1286 if (maxWidth <= 0) maxWidth = 1;
1289 hdc = GetDC(infoPtr->hwnd);
1290 rect = BUTTON_GetTextRect(infoPtr, hdc, text, maxWidth);
1291 ReleaseDC(infoPtr->hwnd, hdc);
1292 heap_free(text);
1294 size->cx = rect.right - rect.left + margin->left + margin->right;
1295 size->cy = rect.bottom - rect.top + margin->top + margin->bottom;
1298 static void BUTTON_GetLabelIdealSize(BUTTON_INFO *infoPtr, LONG maxWidth, SIZE *size)
1300 LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1301 SIZE imageSize;
1302 SIZE textSize;
1303 BOOL horizontal;
1305 imageSize = BUTTON_GetImageSize(infoPtr);
1306 if (infoPtr->imagelist.himl)
1308 imageSize.cx += infoPtr->imagelist.margin.left + infoPtr->imagelist.margin.right;
1309 imageSize.cy += infoPtr->imagelist.margin.top + infoPtr->imagelist.margin.bottom;
1310 if (infoPtr->imagelist.uAlign == BUTTON_IMAGELIST_ALIGN_TOP
1311 || infoPtr->imagelist.uAlign == BUTTON_IMAGELIST_ALIGN_BOTTOM)
1312 horizontal = FALSE;
1313 else
1314 horizontal = TRUE;
1316 else
1318 /* horizontal alignment flags has priority over vertical ones if both are specified */
1319 if (!(style & (BS_CENTER | BS_VCENTER)) || ((style & BS_CENTER) && (style & BS_CENTER) != BS_CENTER)
1320 || !(style & BS_VCENTER) || (style & BS_VCENTER) == BS_VCENTER)
1321 horizontal = TRUE;
1322 else
1323 horizontal = FALSE;
1326 if (horizontal)
1328 if (maxWidth != 0)
1330 maxWidth -= imageSize.cx;
1331 if (maxWidth <= 0) maxWidth = 1;
1333 BUTTON_GetTextIdealSize(infoPtr, maxWidth, &textSize);
1334 size->cx = textSize.cx + imageSize.cx;
1335 size->cy = max(textSize.cy, imageSize.cy);
1337 else
1339 BUTTON_GetTextIdealSize(infoPtr, maxWidth, &textSize);
1340 size->cx = max(textSize.cx, imageSize.cx);
1341 size->cy = textSize.cy + imageSize.cy;
1345 static BOOL GB_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size)
1347 BUTTON_GetClientRectSize(infoPtr, size);
1348 return TRUE;
1351 static BOOL CB_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size)
1353 LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1354 HDC hdc;
1355 HFONT hfont;
1356 SIZE labelSize;
1357 INT textOffset;
1358 double scaleX;
1359 double scaleY;
1360 LONG checkboxWidth, checkboxHeight;
1361 LONG maxWidth = 0;
1363 if (SendMessageW(infoPtr->hwnd, WM_GETTEXTLENGTH, 0, 0) == 0)
1365 BUTTON_GetClientRectSize(infoPtr, size);
1366 return TRUE;
1369 hdc = GetDC(infoPtr->hwnd);
1370 scaleX = GetDeviceCaps(hdc, LOGPIXELSX) / 96.0;
1371 scaleY = GetDeviceCaps(hdc, LOGPIXELSY) / 96.0;
1372 if ((hfont = infoPtr->font)) SelectObject(hdc, hfont);
1373 GetCharWidthW(hdc, '0', '0', &textOffset);
1374 textOffset /= 2;
1375 ReleaseDC(infoPtr->hwnd, hdc);
1377 checkboxWidth = 12 * scaleX + 1;
1378 checkboxHeight = 12 * scaleY + 1;
1379 if (size->cx)
1381 maxWidth = size->cx - checkboxWidth - textOffset;
1382 if (maxWidth <= 0) maxWidth = 1;
1385 /* Checkbox doesn't support both image(but not image list) and text */
1386 if (!(style & (BS_ICON | BS_BITMAP)) && infoPtr->u.image)
1387 BUTTON_GetTextIdealSize(infoPtr, maxWidth, &labelSize);
1388 else
1389 BUTTON_GetLabelIdealSize(infoPtr, maxWidth, &labelSize);
1391 size->cx = labelSize.cx + checkboxWidth + textOffset;
1392 size->cy = max(labelSize.cy, checkboxHeight);
1394 return TRUE;
1397 static BOOL PB_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size)
1399 SIZE labelSize;
1401 if (SendMessageW(infoPtr->hwnd, WM_GETTEXTLENGTH, 0, 0) == 0)
1402 BUTTON_GetClientRectSize(infoPtr, size);
1403 else
1405 /* Ideal size include text size even if image only flags(BS_ICON, BS_BITMAP) are specified */
1406 BUTTON_GetLabelIdealSize(infoPtr, size->cx, &labelSize);
1408 size->cx = labelSize.cx;
1409 size->cy = labelSize.cy;
1411 return TRUE;
1414 static BOOL SB_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size)
1416 LONG extra_width = infoPtr->glyph_size.cx * 2 + GetSystemMetrics(SM_CXEDGE);
1417 SIZE label_size;
1419 if (SendMessageW(infoPtr->hwnd, WM_GETTEXTLENGTH, 0, 0) == 0)
1421 BUTTON_GetClientRectSize(infoPtr, size);
1422 size->cx = max(size->cx, extra_width);
1424 else
1426 BUTTON_GetLabelIdealSize(infoPtr, size->cx, &label_size);
1427 size->cx = label_size.cx + ((size->cx == 0) ? extra_width : 0);
1428 size->cy = label_size.cy;
1430 return TRUE;
1433 static BOOL CL_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size)
1435 HTHEME theme = GetWindowTheme(infoPtr->hwnd);
1436 HDC hdc = GetDC(infoPtr->hwnd);
1437 LONG w, text_w = 0, text_h = 0;
1438 UINT flags = DT_TOP | DT_LEFT;
1439 HFONT font, old_font = NULL;
1440 RECT text_bound = { 0 };
1441 SIZE img_size;
1442 RECT margin;
1443 WCHAR *text;
1445 /* Get the image size */
1446 if (infoPtr->u.image || infoPtr->imagelist.himl)
1447 img_size = BUTTON_GetImageSize(infoPtr);
1448 else
1450 if (theme)
1451 GetThemePartSize(theme, NULL, BP_COMMANDLINKGLYPH, CMDLS_NORMAL, NULL, TS_DRAW, &img_size);
1452 else
1453 img_size.cx = img_size.cy = command_link_defglyph_size;
1456 /* Get the content margins */
1457 if (theme)
1459 RECT r = { 0, 0, 0xffff, 0xffff };
1460 GetThemeBackgroundContentRect(theme, hdc, BP_COMMANDLINK, CMDLS_NORMAL, &r, &margin);
1461 margin.left -= r.left;
1462 margin.top -= r.top;
1463 margin.right = r.right - margin.right;
1464 margin.bottom = r.bottom - margin.bottom;
1466 else
1468 margin.left = margin.right = command_link_margin;
1469 margin.top = margin.bottom = command_link_margin;
1472 /* Account for the border margins and the margin between image and text */
1473 w = margin.left + margin.right + (img_size.cx ? (img_size.cx + command_link_margin) : 0);
1475 /* If a rectangle with a specific width was requested, bound the text to it */
1476 if (size->cx > w)
1478 text_bound.right = size->cx - w;
1479 flags |= DT_WORDBREAK;
1482 if (theme)
1484 if (infoPtr->font) old_font = SelectObject(hdc, infoPtr->font);
1486 /* Find the text's rect */
1487 if ((text = get_button_text(infoPtr)))
1489 RECT r;
1490 GetThemeTextExtent(theme, hdc, BP_COMMANDLINK, CMDLS_NORMAL,
1491 text, -1, flags, &text_bound, &r);
1492 heap_free(text);
1493 text_w = r.right - r.left;
1494 text_h = r.bottom - r.top;
1497 /* Find the note's rect */
1498 if (infoPtr->note)
1500 DTTOPTS opts;
1502 opts.dwSize = sizeof(opts);
1503 opts.dwFlags = DTT_FONTPROP | DTT_CALCRECT;
1504 opts.iFontPropId = TMT_BODYFONT;
1505 DrawThemeTextEx(theme, hdc, BP_COMMANDLINK, CMDLS_NORMAL,
1506 infoPtr->note, infoPtr->note_length,
1507 flags | DT_NOPREFIX | DT_CALCRECT, &text_bound, &opts);
1508 text_w = max(text_w, text_bound.right - text_bound.left);
1509 text_h += text_bound.bottom - text_bound.top;
1512 else
1514 NONCLIENTMETRICSW ncm;
1516 ncm.cbSize = sizeof(ncm);
1517 if (SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, sizeof(ncm), &ncm, 0))
1519 LONG note_weight = ncm.lfMessageFont.lfWeight;
1521 /* Find the text's rect */
1522 ncm.lfMessageFont.lfWeight = FW_BOLD;
1523 if ((font = CreateFontIndirectW(&ncm.lfMessageFont)))
1525 if ((text = get_button_text(infoPtr)))
1527 RECT r = text_bound;
1528 old_font = SelectObject(hdc, font);
1529 DrawTextW(hdc, text, -1, &r, flags | DT_CALCRECT);
1530 heap_free(text);
1532 text_w = r.right - r.left;
1533 text_h = r.bottom - r.top;
1535 DeleteObject(font);
1538 /* Find the note's rect */
1539 ncm.lfMessageFont.lfWeight = note_weight;
1540 if (infoPtr->note && (font = CreateFontIndirectW(&ncm.lfMessageFont)))
1542 HFONT tmp = SelectObject(hdc, font);
1543 if (!old_font) old_font = tmp;
1545 DrawTextW(hdc, infoPtr->note, infoPtr->note_length, &text_bound,
1546 flags | DT_NOPREFIX | DT_CALCRECT);
1547 DeleteObject(font);
1549 text_w = max(text_w, text_bound.right - text_bound.left);
1550 text_h += text_bound.bottom - text_bound.top + 2;
1554 w += text_w;
1556 size->cx = min(size->cx, w);
1557 size->cy = max(text_h, img_size.cy) + margin.top + margin.bottom;
1559 if (old_font) SelectObject(hdc, old_font);
1560 ReleaseDC(infoPtr->hwnd, hdc);
1561 return TRUE;
1564 /**********************************************************************
1565 * BUTTON_CalcLayoutRects
1567 * Calculates the rectangles of the button label(image and text) and its parts depending on a button's style.
1569 * Returns flags to be passed to DrawText.
1570 * Calculated rectangle doesn't take into account button state
1571 * (pushed, etc.). If there is nothing to draw (no text/image) output
1572 * rectangle is empty, and return value is (UINT)-1.
1574 * PARAMS:
1575 * infoPtr [I] Button pointer
1576 * hdc [I] Handle to device context to draw to
1577 * labelRc [I/O] Input the rect the label to be positioned in, and output the label rect
1578 * imageRc [O] Optional, output the image rect
1579 * textRc [O] Optional, output the text rect
1581 static UINT BUTTON_CalcLayoutRects(const BUTTON_INFO *infoPtr, HDC hdc, RECT *labelRc, RECT *imageRc, RECT *textRc)
1583 WCHAR *text = get_button_text(infoPtr);
1584 SIZE imageSize = BUTTON_GetImageSize(infoPtr);
1585 RECT labelRect, imageRect, imageRectWithMargin, textRect;
1586 LONG imageMarginWidth, imageMarginHeight;
1587 const RECT *textMargin = BUTTON_GetTextMargin(infoPtr);
1588 LONG style, ex_style, split_style;
1589 RECT emptyMargin = {0};
1590 LONG maxTextWidth;
1591 UINT dtStyle;
1593 /* Calculate label rectangle according to label type */
1594 if ((imageSize.cx == 0 && imageSize.cy == 0) && (text == NULL || text[0] == '\0'))
1596 SetRectEmpty(labelRc);
1597 SetRectEmpty(imageRc);
1598 SetRectEmpty(textRc);
1599 heap_free(text);
1600 return (UINT)-1;
1603 style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1604 ex_style = GetWindowLongW(infoPtr->hwnd, GWL_EXSTYLE);
1605 /* Add BS_RIGHT directly. When both WS_EX_RIGHT and BS_LEFT are present, it becomes BS_CENTER */
1606 if (ex_style & WS_EX_RIGHT)
1607 style |= BS_RIGHT;
1608 split_style = infoPtr->imagelist.himl ? BUTTON_ILStoBS(infoPtr->imagelist.uAlign) : style;
1609 dtStyle = BUTTON_BStoDT(style, ex_style);
1611 /* Group boxes are top aligned unless BS_PUSHLIKE is set and it's not themed */
1612 if (get_button_type(style) == BS_GROUPBOX
1613 && (!(style & BS_PUSHLIKE) || GetWindowTheme(infoPtr->hwnd)))
1614 style &= ~BS_VCENTER | BS_TOP;
1616 SetRect(&imageRect, 0, 0, imageSize.cx, imageSize.cy);
1617 imageRectWithMargin = imageRect;
1618 if (infoPtr->imagelist.himl)
1620 imageRectWithMargin.top -= infoPtr->imagelist.margin.top;
1621 imageRectWithMargin.bottom += infoPtr->imagelist.margin.bottom;
1622 imageRectWithMargin.left -= infoPtr->imagelist.margin.left;
1623 imageRectWithMargin.right += infoPtr->imagelist.margin.right;
1626 /* Show image only */
1627 if (show_image_only(infoPtr))
1629 BUTTON_PositionRect(style, labelRc, &imageRect,
1630 infoPtr->imagelist.himl ? &infoPtr->imagelist.margin : &emptyMargin);
1631 labelRect = imageRect;
1632 SetRectEmpty(&textRect);
1634 else
1636 /* Get text rect */
1637 maxTextWidth = labelRc->right - labelRc->left;
1638 textRect = BUTTON_GetTextRect(infoPtr, hdc, text, maxTextWidth);
1640 /* Show image and text */
1641 if (show_image_and_text(infoPtr))
1643 RECT boundingLabelRect, boundingImageRect, boundingTextRect;
1645 /* Get label rect */
1646 /* Image list may have different alignment than the button, use the whole rect for label in this case */
1647 if (infoPtr->imagelist.himl)
1648 labelRect = *labelRc;
1649 else
1651 /* Get a label bounding rectangle to position the label in the user specified label rectangle because
1652 * text and image need to align together. */
1653 boundingLabelRect = BUTTON_GetBoundingLabelRect(split_style, &textRect, &imageRectWithMargin);
1654 BUTTON_PositionRect(split_style, labelRc, &boundingLabelRect, &emptyMargin);
1655 labelRect = boundingLabelRect;
1658 /* When imagelist has center align, use the whole rect for imagelist and text */
1659 if(infoPtr->imagelist.himl && infoPtr->imagelist.uAlign == BUTTON_IMAGELIST_ALIGN_CENTER)
1661 boundingImageRect = labelRect;
1662 boundingTextRect = labelRect;
1663 BUTTON_PositionRect(split_style, &boundingImageRect, &imageRect,
1664 infoPtr->imagelist.himl ? &infoPtr->imagelist.margin : &emptyMargin);
1665 /* Text doesn't use imagelist align */
1666 BUTTON_PositionRect(style, &boundingTextRect, &textRect, textMargin);
1668 else
1670 /* Get image rect */
1671 /* Split the label rect to two halves as two bounding rectangles for image and text */
1672 boundingImageRect = labelRect;
1673 imageMarginWidth = imageRectWithMargin.right - imageRectWithMargin.left;
1674 imageMarginHeight = imageRectWithMargin.bottom - imageRectWithMargin.top;
1675 if ((split_style & BS_CENTER) == BS_RIGHT)
1676 boundingImageRect.left = boundingImageRect.right - imageMarginWidth;
1677 else if ((split_style & BS_CENTER) == BS_LEFT)
1678 boundingImageRect.right = boundingImageRect.left + imageMarginWidth;
1679 else if ((split_style & BS_VCENTER) == BS_BOTTOM)
1680 boundingImageRect.top = boundingImageRect.bottom - imageMarginHeight;
1681 else if ((split_style & BS_VCENTER) == BS_TOP)
1682 boundingImageRect.bottom = boundingImageRect.top + imageMarginHeight;
1683 else
1684 boundingImageRect.right = boundingImageRect.left + imageMarginWidth;
1685 BUTTON_PositionRect(split_style, &boundingImageRect, &imageRect,
1686 infoPtr->imagelist.himl ? &infoPtr->imagelist.margin : &emptyMargin);
1688 /* Get text rect */
1689 SubtractRect(&boundingTextRect, &labelRect, &boundingImageRect);
1690 /* Text doesn't use imagelist align */
1691 BUTTON_PositionRect(style, &boundingTextRect, &textRect, textMargin);
1694 /* Show text only */
1695 else
1697 BUTTON_PositionRect(style, labelRc, &textRect, textMargin);
1698 labelRect = textRect;
1699 SetRectEmpty(&imageRect);
1702 heap_free(text);
1704 CopyRect(labelRc, &labelRect);
1705 CopyRect(imageRc, &imageRect);
1706 CopyRect(textRc, &textRect);
1708 return dtStyle;
1712 /**********************************************************************
1713 * BUTTON_DrawImage
1715 * Draw the button's image into the specified rectangle.
1717 static void BUTTON_DrawImage(const BUTTON_INFO *infoPtr, HDC hdc, HBRUSH hbr, UINT flags, const RECT *rect)
1719 if (infoPtr->imagelist.himl)
1721 int i = (ImageList_GetImageCount(infoPtr->imagelist.himl) == 1) ? 0 : get_draw_state(infoPtr) - 1;
1723 ImageList_Draw(infoPtr->imagelist.himl, i, hdc, rect->left, rect->top, ILD_NORMAL);
1725 else
1727 switch (infoPtr->image_type)
1729 case IMAGE_ICON:
1730 flags |= DST_ICON;
1731 break;
1732 case IMAGE_BITMAP:
1733 flags |= DST_BITMAP;
1734 break;
1735 default:
1736 return;
1739 DrawStateW(hdc, hbr, NULL, (LPARAM)infoPtr->u.image, 0, rect->left, rect->top,
1740 rect->right - rect->left, rect->bottom - rect->top, flags);
1745 /**********************************************************************
1746 * BUTTON_DrawTextCallback
1748 * Callback function used by DrawStateW function.
1750 static BOOL CALLBACK BUTTON_DrawTextCallback(HDC hdc, LPARAM lp, WPARAM wp, int cx, int cy)
1752 RECT rc;
1754 SetRect(&rc, 0, 0, cx, cy);
1755 DrawTextW(hdc, (LPCWSTR)lp, -1, &rc, (UINT)wp);
1756 return TRUE;
1759 /**********************************************************************
1760 * BUTTON_DrawLabel
1762 * Common function for drawing button label.
1764 * FIXME:
1765 * 1. When BS_SINGLELINE is specified and text contains '\t', '\n' or '\r' in the middle, they are rendered as
1766 * squares now whereas they should be ignored.
1767 * 2. When BS_MULTILINE is specified and text contains space in the middle, the space mistakenly be rendered as newline.
1769 static void BUTTON_DrawLabel(const BUTTON_INFO *infoPtr, HDC hdc, UINT dtFlags, const RECT *imageRect,
1770 const RECT *textRect)
1772 HBRUSH hbr = 0;
1773 UINT flags = IsWindowEnabled(infoPtr->hwnd) ? DSS_NORMAL : DSS_DISABLED;
1774 LONG style = GetWindowLongW( infoPtr->hwnd, GWL_STYLE );
1775 WCHAR *text;
1777 /* FIXME: To draw disabled label in Win31 look-and-feel, we probably
1778 * must use DSS_MONO flag and COLOR_GRAYTEXT brush (or maybe DSS_UNION).
1779 * I don't have Win31 on hand to verify that, so I leave it as is.
1782 if ((style & BS_PUSHLIKE) && (infoPtr->state & BST_INDETERMINATE))
1784 hbr = GetSysColorBrush(COLOR_GRAYTEXT);
1785 flags |= DSS_MONO;
1788 if (show_image(infoPtr)) BUTTON_DrawImage(infoPtr, hdc, hbr, flags, imageRect);
1789 if (show_image_only(infoPtr)) return;
1791 /* DST_COMPLEX -- is 0 */
1792 if (!(text = get_button_text(infoPtr))) return;
1793 DrawStateW(hdc, hbr, BUTTON_DrawTextCallback, (LPARAM)text, dtFlags, textRect->left, textRect->top,
1794 textRect->right - textRect->left, textRect->bottom - textRect->top, flags);
1795 heap_free(text);
1798 static void BUTTON_DrawThemedLabel(const BUTTON_INFO *info, HDC hdc, UINT text_flags,
1799 const RECT *image_rect, const RECT *text_rect, HTHEME theme,
1800 int part, int state)
1802 HBRUSH brush = NULL;
1803 UINT image_flags;
1804 WCHAR *text;
1806 if (show_image(info))
1808 image_flags = IsWindowEnabled(info->hwnd) ? DSS_NORMAL : DSS_DISABLED;
1810 if ((GetWindowLongW(info->hwnd, GWL_STYLE) & BS_PUSHLIKE)
1811 && (info->state & BST_INDETERMINATE))
1813 brush = GetSysColorBrush(COLOR_GRAYTEXT);
1814 image_flags |= DSS_MONO;
1817 BUTTON_DrawImage(info, hdc, brush, image_flags, image_rect);
1820 if (show_image_only(info))
1821 return;
1823 if (!(text = get_button_text(info)))
1824 return;
1826 DrawThemeText(theme, hdc, part, state, text, lstrlenW(text), text_flags, 0, text_rect);
1827 heap_free(text);
1830 /**********************************************************************
1831 * Push Button Functions
1833 static void PB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action )
1835 RECT rc, labelRect, imageRect, textRect;
1836 UINT dtFlags, uState;
1837 HPEN hOldPen, hpen;
1838 HBRUSH hOldBrush;
1839 INT oldBkMode;
1840 COLORREF oldTxtColor;
1841 LRESULT cdrf;
1842 HFONT hFont;
1843 NMCUSTOMDRAW nmcd;
1844 LONG state = infoPtr->state;
1845 LONG style = GetWindowLongW( infoPtr->hwnd, GWL_STYLE );
1846 BOOL pushedState = (state & BST_PUSHED);
1847 HWND parent;
1848 HRGN hrgn;
1850 GetClientRect( infoPtr->hwnd, &rc );
1852 /* Send WM_CTLCOLOR to allow changing the font (the colors are fixed) */
1853 if ((hFont = infoPtr->font)) SelectObject( hDC, hFont );
1854 parent = GetParent(infoPtr->hwnd);
1855 if (!parent) parent = infoPtr->hwnd;
1856 SendMessageW( parent, WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)infoPtr->hwnd );
1858 hrgn = set_control_clipping( hDC, &rc );
1860 hpen = CreatePen( PS_SOLID, 1, GetSysColor(COLOR_WINDOWFRAME));
1861 hOldPen = SelectObject(hDC, hpen);
1862 hOldBrush = SelectObject(hDC,GetSysColorBrush(COLOR_BTNFACE));
1863 oldBkMode = SetBkMode(hDC, TRANSPARENT);
1865 init_custom_draw(&nmcd, infoPtr, hDC, &rc);
1867 /* Send erase notifications */
1868 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
1869 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
1871 if (get_button_type(style) == BS_DEFPUSHBUTTON)
1873 if (action != ODA_FOCUS)
1874 Rectangle(hDC, rc.left, rc.top, rc.right, rc.bottom);
1875 InflateRect( &rc, -1, -1 );
1878 /* Skip the frame drawing if only focus has changed */
1879 if (action != ODA_FOCUS)
1881 uState = DFCS_BUTTONPUSH;
1883 if (style & BS_FLAT)
1884 uState |= DFCS_MONO;
1885 else if (pushedState)
1887 if (get_button_type(style) == BS_DEFPUSHBUTTON )
1888 uState |= DFCS_FLAT;
1889 else
1890 uState |= DFCS_PUSHED;
1893 if (state & (BST_CHECKED | BST_INDETERMINATE))
1894 uState |= DFCS_CHECKED;
1896 DrawFrameControl( hDC, &rc, DFC_BUTTON, uState );
1899 if (cdrf & CDRF_NOTIFYPOSTERASE)
1901 nmcd.dwDrawStage = CDDS_POSTERASE;
1902 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
1905 /* Send paint notifications */
1906 nmcd.dwDrawStage = CDDS_PREPAINT;
1907 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
1908 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
1910 if (!(cdrf & CDRF_DOERASE) && action != ODA_FOCUS)
1912 /* draw button label */
1913 labelRect = rc;
1914 /* Shrink label rect at all sides by 2 so that the content won't touch the surrounding frame */
1915 InflateRect(&labelRect, -2, -2);
1916 dtFlags = BUTTON_CalcLayoutRects(infoPtr, hDC, &labelRect, &imageRect, &textRect);
1918 if (dtFlags != (UINT)-1L)
1920 if (pushedState) OffsetRect(&labelRect, 1, 1);
1922 oldTxtColor = SetTextColor( hDC, GetSysColor(COLOR_BTNTEXT) );
1924 BUTTON_DrawLabel(infoPtr, hDC, dtFlags, &imageRect, &textRect);
1926 SetTextColor( hDC, oldTxtColor );
1930 if (cdrf & CDRF_NOTIFYPOSTPAINT)
1932 nmcd.dwDrawStage = CDDS_POSTPAINT;
1933 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
1935 if ((cdrf & CDRF_SKIPPOSTPAINT) || dtFlags == (UINT)-1L) goto cleanup;
1937 if (action == ODA_FOCUS || (state & BST_FOCUS))
1939 InflateRect( &rc, -2, -2 );
1940 DrawFocusRect( hDC, &rc );
1943 cleanup:
1944 SelectObject( hDC, hOldPen );
1945 SelectObject( hDC, hOldBrush );
1946 SetBkMode(hDC, oldBkMode);
1947 SelectClipRgn( hDC, hrgn );
1948 if (hrgn) DeleteObject( hrgn );
1949 DeleteObject( hpen );
1952 /**********************************************************************
1953 * Check Box & Radio Button Functions
1956 /* Get adjusted check box or radio box rectangle */
1957 static RECT get_box_rect(LONG style, LONG ex_style, const RECT *content_rect,
1958 const RECT *label_rect, BOOL has_label, SIZE box_size)
1960 RECT rect;
1961 int delta;
1963 rect = *content_rect;
1965 if (style & BS_LEFTTEXT || ex_style & WS_EX_RIGHT)
1966 rect.left = rect.right - box_size.cx;
1967 else
1968 rect.right = rect.left + box_size.cx;
1970 /* Adjust box when label is valid */
1971 if (has_label)
1973 rect.top = label_rect->top;
1974 rect.bottom = label_rect->bottom;
1977 /* Box must have the correct height */
1978 delta = rect.bottom - rect.top - box_size.cy;
1979 if ((style & BS_VCENTER) == BS_TOP)
1981 if (delta <= 0)
1982 rect.top -= -delta / 2 + 1;
1984 rect.bottom = rect.top + box_size.cy;
1986 else if ((style & BS_VCENTER) == BS_BOTTOM)
1988 if (delta <= 0)
1989 rect.bottom += -delta / 2 + 1;
1991 rect.top = rect.bottom - box_size.cy;
1993 else
1995 if (delta > 0)
1997 rect.bottom -= delta / 2 + 1;
1998 rect.top = rect.bottom - box_size.cy;
2000 else if (delta < 0)
2002 rect.top -= -delta / 2 + 1;
2003 rect.bottom = rect.top + box_size.cy;
2007 return rect;
2010 static void CB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action )
2012 RECT rbox, labelRect, oldLabelRect, imageRect, textRect, client;
2013 HBRUSH hBrush;
2014 int text_offset;
2015 UINT dtFlags;
2016 LRESULT cdrf;
2017 HFONT hFont;
2018 NMCUSTOMDRAW nmcd;
2019 LONG state = infoPtr->state;
2020 LONG style = GetWindowLongW( infoPtr->hwnd, GWL_STYLE );
2021 LONG ex_style = GetWindowLongW( infoPtr->hwnd, GWL_EXSTYLE );
2022 SIZE box_size;
2023 HWND parent;
2024 HRGN hrgn;
2026 if (style & BS_PUSHLIKE)
2028 PB_Paint( infoPtr, hDC, action );
2029 return;
2032 GetClientRect(infoPtr->hwnd, &client);
2033 labelRect = client;
2035 box_size.cx = 12 * GetDpiForWindow(infoPtr->hwnd) / 96 + 1;
2036 box_size.cy = box_size.cx;
2038 if ((hFont = infoPtr->font)) SelectObject( hDC, hFont );
2039 GetCharWidthW( hDC, '0', '0', &text_offset );
2040 text_offset /= 2;
2042 parent = GetParent(infoPtr->hwnd);
2043 if (!parent) parent = infoPtr->hwnd;
2044 hBrush = (HBRUSH)SendMessageW(parent, WM_CTLCOLORSTATIC, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
2045 if (!hBrush) /* did the app forget to call defwindowproc ? */
2046 hBrush = (HBRUSH)DefWindowProcW(parent, WM_CTLCOLORSTATIC, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
2047 hrgn = set_control_clipping( hDC, &client );
2049 if (style & BS_LEFTTEXT || ex_style & WS_EX_RIGHT)
2050 labelRect.right -= box_size.cx + text_offset;
2051 else
2052 labelRect.left += box_size.cx + text_offset;
2054 oldLabelRect = labelRect;
2055 dtFlags = BUTTON_CalcLayoutRects(infoPtr, hDC, &labelRect, &imageRect, &textRect);
2056 rbox = get_box_rect(style, ex_style, &client, &labelRect, dtFlags != (UINT)-1L, box_size);
2058 init_custom_draw(&nmcd, infoPtr, hDC, &client);
2060 /* Send erase notifications */
2061 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2062 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2064 /* Since WM_ERASEBKGND does nothing, first prepare background */
2065 if (action == ODA_SELECT) FillRect( hDC, &rbox, hBrush );
2066 if (action == ODA_DRAWENTIRE) FillRect( hDC, &client, hBrush );
2067 if (cdrf & CDRF_NOTIFYPOSTERASE)
2069 nmcd.dwDrawStage = CDDS_POSTERASE;
2070 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2073 /* Draw label */
2074 /* Send paint notifications */
2075 nmcd.dwDrawStage = CDDS_PREPAINT;
2076 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2077 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2079 /* Draw the check-box bitmap */
2080 if (!(cdrf & CDRF_DOERASE))
2082 if (action == ODA_DRAWENTIRE || action == ODA_SELECT)
2084 UINT flags;
2086 if ((get_button_type(style) == BS_RADIOBUTTON) ||
2087 (get_button_type(style) == BS_AUTORADIOBUTTON)) flags = DFCS_BUTTONRADIO;
2088 else if (state & BST_INDETERMINATE) flags = DFCS_BUTTON3STATE;
2089 else flags = DFCS_BUTTONCHECK;
2091 if (state & (BST_CHECKED | BST_INDETERMINATE)) flags |= DFCS_CHECKED;
2092 if (state & BST_PUSHED) flags |= DFCS_PUSHED;
2093 if (style & WS_DISABLED) flags |= DFCS_INACTIVE;
2095 DrawFrameControl(hDC, &rbox, DFC_BUTTON, flags);
2098 if (dtFlags != (UINT)-1L) /* Something to draw */
2099 if (action == ODA_DRAWENTIRE) BUTTON_DrawLabel(infoPtr, hDC, dtFlags, &imageRect, &textRect);
2102 if (cdrf & CDRF_NOTIFYPOSTPAINT)
2104 nmcd.dwDrawStage = CDDS_POSTPAINT;
2105 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2107 if ((cdrf & CDRF_SKIPPOSTPAINT) || dtFlags == (UINT)-1L) goto cleanup;
2109 /* ... and focus */
2110 if (action == ODA_FOCUS || (state & BST_FOCUS))
2112 labelRect.left--;
2113 labelRect.right++;
2114 IntersectRect(&labelRect, &labelRect, &oldLabelRect);
2115 DrawFocusRect(hDC, &labelRect);
2118 cleanup:
2119 SelectClipRgn( hDC, hrgn );
2120 if (hrgn) DeleteObject( hrgn );
2124 /**********************************************************************
2125 * BUTTON_CheckAutoRadioButton
2127 * hwnd is checked, uncheck every other auto radio button in group
2129 static void BUTTON_CheckAutoRadioButton( HWND hwnd )
2131 HWND parent, sibling, start;
2133 parent = GetParent(hwnd);
2134 /* make sure that starting control is not disabled or invisible */
2135 start = sibling = GetNextDlgGroupItem( parent, hwnd, TRUE );
2138 if (!sibling) break;
2139 if ((hwnd != sibling) &&
2140 ((GetWindowLongW( sibling, GWL_STYLE) & BS_TYPEMASK) == BS_AUTORADIOBUTTON))
2141 SendMessageW( sibling, BM_SETCHECK, BST_UNCHECKED, 0 );
2142 sibling = GetNextDlgGroupItem( parent, sibling, FALSE );
2143 } while (sibling != start);
2147 /**********************************************************************
2148 * Group Box Functions
2151 static void GB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action )
2153 RECT labelRect, imageRect, textRect, rcFrame;
2154 HBRUSH hbr;
2155 HFONT hFont;
2156 UINT dtFlags;
2157 TEXTMETRICW tm;
2158 LONG style = GetWindowLongW( infoPtr->hwnd, GWL_STYLE );
2159 HWND parent;
2160 HRGN hrgn;
2162 if ((hFont = infoPtr->font)) SelectObject( hDC, hFont );
2163 /* GroupBox acts like static control, so it sends CTLCOLORSTATIC */
2164 parent = GetParent(infoPtr->hwnd);
2165 if (!parent) parent = infoPtr->hwnd;
2166 hbr = (HBRUSH)SendMessageW(parent, WM_CTLCOLORSTATIC, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
2167 if (!hbr) /* did the app forget to call defwindowproc ? */
2168 hbr = (HBRUSH)DefWindowProcW(parent, WM_CTLCOLORSTATIC, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
2169 GetClientRect(infoPtr->hwnd, &labelRect);
2170 rcFrame = labelRect;
2171 hrgn = set_control_clipping(hDC, &labelRect);
2173 GetTextMetricsW (hDC, &tm);
2174 rcFrame.top += (tm.tmHeight / 2) - 1;
2175 DrawEdge (hDC, &rcFrame, EDGE_ETCHED, BF_RECT | ((style & BS_FLAT) ? BF_FLAT : 0));
2177 InflateRect(&labelRect, -7, 1);
2178 dtFlags = BUTTON_CalcLayoutRects(infoPtr, hDC, &labelRect, &imageRect, &textRect);
2180 if (dtFlags != (UINT)-1)
2182 /* Because buttons have CS_PARENTDC class style, there is a chance
2183 * that label will be drawn out of client rect.
2184 * But Windows doesn't clip label's rect, so do I.
2187 /* There is 1-pixel margin at the left, right, and bottom */
2188 labelRect.left--;
2189 labelRect.right++;
2190 labelRect.bottom++;
2191 FillRect(hDC, &labelRect, hbr);
2192 BUTTON_DrawLabel(infoPtr, hDC, dtFlags, &imageRect, &textRect);
2194 SelectClipRgn( hDC, hrgn );
2195 if (hrgn) DeleteObject( hrgn );
2199 /**********************************************************************
2200 * User Button Functions
2203 static void UB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action )
2205 RECT rc;
2206 HBRUSH hBrush;
2207 LRESULT cdrf;
2208 HFONT hFont;
2209 NMCUSTOMDRAW nmcd;
2210 LONG state = infoPtr->state;
2211 HWND parent;
2213 GetClientRect( infoPtr->hwnd, &rc);
2215 if ((hFont = infoPtr->font)) SelectObject( hDC, hFont );
2217 parent = GetParent(infoPtr->hwnd);
2218 if (!parent) parent = infoPtr->hwnd;
2219 hBrush = (HBRUSH)SendMessageW(parent, WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
2220 if (!hBrush) /* did the app forget to call defwindowproc ? */
2221 hBrush = (HBRUSH)DefWindowProcW(parent, WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
2223 if (action == ODA_FOCUS || (state & BST_FOCUS))
2225 init_custom_draw(&nmcd, infoPtr, hDC, &rc);
2227 /* Send erase notifications */
2228 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2229 if (cdrf & CDRF_SKIPDEFAULT) goto notify;
2232 FillRect( hDC, &rc, hBrush );
2233 if (action == ODA_FOCUS || (state & BST_FOCUS))
2235 if (cdrf & CDRF_NOTIFYPOSTERASE)
2237 nmcd.dwDrawStage = CDDS_POSTERASE;
2238 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2241 /* Send paint notifications */
2242 nmcd.dwDrawStage = CDDS_PREPAINT;
2243 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2244 if (cdrf & CDRF_SKIPDEFAULT) goto notify;
2245 if (cdrf & CDRF_NOTIFYPOSTPAINT)
2247 nmcd.dwDrawStage = CDDS_POSTPAINT;
2248 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2251 if (!(cdrf & CDRF_SKIPPOSTPAINT))
2252 DrawFocusRect( hDC, &rc );
2255 notify:
2256 switch (action)
2258 case ODA_FOCUS:
2259 BUTTON_NOTIFY_PARENT( infoPtr->hwnd, (state & BST_FOCUS) ? BN_SETFOCUS : BN_KILLFOCUS );
2260 break;
2262 case ODA_SELECT:
2263 BUTTON_NOTIFY_PARENT( infoPtr->hwnd, (state & BST_PUSHED) ? BN_HILITE : BN_UNHILITE );
2264 break;
2266 default:
2267 break;
2272 /**********************************************************************
2273 * Ownerdrawn Button Functions
2276 static void OB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action )
2278 LONG state = infoPtr->state;
2279 DRAWITEMSTRUCT dis;
2280 LONG_PTR id = GetWindowLongPtrW( infoPtr->hwnd, GWLP_ID );
2281 HWND parent;
2282 HFONT hFont;
2283 HRGN hrgn;
2285 dis.CtlType = ODT_BUTTON;
2286 dis.CtlID = id;
2287 dis.itemID = 0;
2288 dis.itemAction = action;
2289 dis.itemState = ((state & BST_FOCUS) ? ODS_FOCUS : 0) |
2290 ((state & BST_PUSHED) ? ODS_SELECTED : 0) |
2291 (IsWindowEnabled(infoPtr->hwnd) ? 0: ODS_DISABLED);
2292 dis.hwndItem = infoPtr->hwnd;
2293 dis.hDC = hDC;
2294 dis.itemData = 0;
2295 GetClientRect( infoPtr->hwnd, &dis.rcItem );
2297 if ((hFont = infoPtr->font)) SelectObject( hDC, hFont );
2298 parent = GetParent(infoPtr->hwnd);
2299 if (!parent) parent = infoPtr->hwnd;
2300 SendMessageW( parent, WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)infoPtr->hwnd );
2302 hrgn = set_control_clipping( hDC, &dis.rcItem );
2304 SendMessageW( GetParent(infoPtr->hwnd), WM_DRAWITEM, id, (LPARAM)&dis );
2305 SelectClipRgn( hDC, hrgn );
2306 if (hrgn) DeleteObject( hrgn );
2310 /**********************************************************************
2311 * Split Button Functions
2313 static void SB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action )
2315 LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2316 LONG state = infoPtr->state;
2317 UINT dtFlags = (UINT)-1L;
2319 RECT rc, push_rect, dropdown_rect;
2320 NMCUSTOMDRAW nmcd;
2321 HPEN pen, old_pen;
2322 HBRUSH old_brush;
2323 INT old_bk_mode;
2324 LRESULT cdrf;
2325 HWND parent;
2326 HRGN hrgn;
2328 GetClientRect(infoPtr->hwnd, &rc);
2330 /* Send WM_CTLCOLOR to allow changing the font (the colors are fixed) */
2331 if (infoPtr->font) SelectObject(hDC, infoPtr->font);
2332 if (!(parent = GetParent(infoPtr->hwnd))) parent = infoPtr->hwnd;
2333 SendMessageW(parent, WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
2335 hrgn = set_control_clipping(hDC, &rc);
2337 pen = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_WINDOWFRAME));
2338 old_pen = SelectObject(hDC, pen);
2339 old_brush = SelectObject(hDC, GetSysColorBrush(COLOR_BTNFACE));
2340 old_bk_mode = SetBkMode(hDC, TRANSPARENT);
2342 init_custom_draw(&nmcd, infoPtr, hDC, &rc);
2344 /* Send erase notifications */
2345 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2346 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2348 if (get_button_type(style) == BS_DEFSPLITBUTTON)
2350 if (action != ODA_FOCUS)
2351 Rectangle(hDC, rc.left, rc.top, rc.right, rc.bottom);
2352 InflateRect(&rc, -1, -1);
2353 /* The split will now be off by 1 pixel, but
2354 that's exactly what Windows does as well */
2357 get_split_button_rects(infoPtr, &rc, &push_rect, &dropdown_rect);
2358 if (infoPtr->split_style & BCSS_NOSPLIT)
2359 push_rect = rc;
2361 /* Skip the frame drawing if only focus has changed */
2362 if (action != ODA_FOCUS)
2364 UINT flags = DFCS_BUTTONPUSH;
2366 if (style & BS_FLAT) flags |= DFCS_MONO;
2367 else if (state & BST_PUSHED)
2368 flags |= (get_button_type(style) == BS_DEFSPLITBUTTON)
2369 ? DFCS_FLAT : DFCS_PUSHED;
2371 if (state & (BST_CHECKED | BST_INDETERMINATE))
2372 flags |= DFCS_CHECKED;
2374 if (infoPtr->split_style & BCSS_NOSPLIT)
2375 DrawFrameControl(hDC, &push_rect, DFC_BUTTON, flags);
2376 else
2378 UINT dropdown_flags = flags & ~DFCS_CHECKED;
2380 if (state & BST_DROPDOWNPUSHED)
2381 dropdown_flags = (dropdown_flags & ~DFCS_FLAT) | DFCS_PUSHED;
2383 /* Adjust for shadow and draw order so it looks properly */
2384 if (infoPtr->split_style & BCSS_ALIGNLEFT)
2386 dropdown_rect.right++;
2387 DrawFrameControl(hDC, &dropdown_rect, DFC_BUTTON, dropdown_flags);
2388 dropdown_rect.right--;
2389 DrawFrameControl(hDC, &push_rect, DFC_BUTTON, flags);
2391 else
2393 push_rect.right++;
2394 DrawFrameControl(hDC, &push_rect, DFC_BUTTON, flags);
2395 push_rect.right--;
2396 DrawFrameControl(hDC, &dropdown_rect, DFC_BUTTON, dropdown_flags);
2401 if (cdrf & CDRF_NOTIFYPOSTERASE)
2403 nmcd.dwDrawStage = CDDS_POSTERASE;
2404 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2407 /* Send paint notifications */
2408 nmcd.dwDrawStage = CDDS_PREPAINT;
2409 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2410 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2412 /* Shrink push button rect so that the content won't touch the surrounding frame */
2413 InflateRect(&push_rect, -2, -2);
2415 if (!(cdrf & CDRF_DOERASE) && action != ODA_FOCUS)
2417 COLORREF old_color = SetTextColor(hDC, GetSysColor(COLOR_BTNTEXT));
2418 RECT label_rect = push_rect, image_rect, text_rect;
2420 dtFlags = BUTTON_CalcLayoutRects(infoPtr, hDC, &label_rect, &image_rect, &text_rect);
2422 if (dtFlags != (UINT)-1L)
2423 BUTTON_DrawLabel(infoPtr, hDC, dtFlags, &image_rect, &text_rect);
2425 draw_split_button_dropdown_glyph(infoPtr, hDC, &dropdown_rect);
2426 SetTextColor(hDC, old_color);
2429 if (cdrf & CDRF_NOTIFYPOSTPAINT)
2431 nmcd.dwDrawStage = CDDS_POSTPAINT;
2432 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2434 if ((cdrf & CDRF_SKIPPOSTPAINT) || dtFlags == (UINT)-1L) goto cleanup;
2436 if (action == ODA_FOCUS || (state & BST_FOCUS))
2437 DrawFocusRect(hDC, &push_rect);
2439 cleanup:
2440 SelectObject(hDC, old_pen);
2441 SelectObject(hDC, old_brush);
2442 SetBkMode(hDC, old_bk_mode);
2443 SelectClipRgn(hDC, hrgn);
2444 if (hrgn) DeleteObject(hrgn);
2445 DeleteObject(pen);
2448 /* Given the full button rect of the split button, retrieve the push part and the dropdown part */
2449 static inline void get_split_button_rects(const BUTTON_INFO *infoPtr, const RECT *button_rect,
2450 RECT *push_rect, RECT *dropdown_rect)
2452 *push_rect = *dropdown_rect = *button_rect;
2454 /* The dropdown takes priority if the client rect is too small, it will only have a dropdown */
2455 if (infoPtr->split_style & BCSS_ALIGNLEFT)
2457 dropdown_rect->right = min(button_rect->left + infoPtr->glyph_size.cx, button_rect->right);
2458 push_rect->left = dropdown_rect->right;
2460 else
2462 dropdown_rect->left = max(button_rect->right - infoPtr->glyph_size.cx, button_rect->left);
2463 push_rect->right = dropdown_rect->left;
2467 /* Notify the parent if the point is within the dropdown and return TRUE (always notify if NULL) */
2468 static BOOL notify_split_button_dropdown(const BUTTON_INFO *infoPtr, const POINT *pt, HWND hwnd)
2470 NMBCDROPDOWN nmbcd;
2472 GetClientRect(hwnd, &nmbcd.rcButton);
2473 if (pt)
2475 RECT push_rect, dropdown_rect;
2477 get_split_button_rects(infoPtr, &nmbcd.rcButton, &push_rect, &dropdown_rect);
2478 if (!PtInRect(&dropdown_rect, *pt))
2479 return FALSE;
2481 /* If it's already down (set manually via BCM_SETDROPDOWNSTATE), fake the notify */
2482 if (infoPtr->state & BST_DROPDOWNPUSHED)
2483 return TRUE;
2485 SendMessageW(hwnd, BCM_SETDROPDOWNSTATE, TRUE, 0);
2487 nmbcd.hdr.hwndFrom = hwnd;
2488 nmbcd.hdr.idFrom = GetWindowLongPtrW(hwnd, GWLP_ID);
2489 nmbcd.hdr.code = BCN_DROPDOWN;
2490 SendMessageW(GetParent(hwnd), WM_NOTIFY, nmbcd.hdr.idFrom, (LPARAM)&nmbcd);
2492 SendMessageW(hwnd, BCM_SETDROPDOWNSTATE, FALSE, 0);
2493 return TRUE;
2496 /* Draw the split button dropdown glyph or image */
2497 static void draw_split_button_dropdown_glyph(const BUTTON_INFO *infoPtr, HDC hdc, RECT *rect)
2499 if (infoPtr->split_style & BCSS_IMAGE)
2501 int w, h;
2503 /* When the glyph is an image list, Windows is very buggy with BCSS_STRETCH,
2504 positions it weirdly and doesn't even stretch it, but instead extends the
2505 image, leaking into other images in the list (or black if none). Instead,
2506 we'll ignore this and just position it at center as without BCSS_STRETCH. */
2507 if (!ImageList_GetIconSize(infoPtr->glyph, &w, &h)) return;
2509 ImageList_Draw(infoPtr->glyph,
2510 (ImageList_GetImageCount(infoPtr->glyph) == 1) ? 0 : get_draw_state(infoPtr) - 1,
2511 hdc, rect->left + (rect->right - rect->left - w) / 2,
2512 rect->top + (rect->bottom - rect->top - h) / 2, ILD_NORMAL);
2514 else if (infoPtr->glyph_size.cy >= 0)
2516 /* infoPtr->glyph is a character code from Marlett */
2517 HFONT font, old_font;
2518 LOGFONTW logfont = { 0, 0, 0, 0, FW_NORMAL, 0, 0, 0, SYMBOL_CHARSET, 0, 0, 0, 0,
2519 L"Marlett" };
2520 if (infoPtr->glyph_size.cy)
2522 /* BCSS_STRETCH preserves aspect ratio, uses minimum as size */
2523 if (infoPtr->split_style & BCSS_STRETCH)
2524 logfont.lfHeight = min(infoPtr->glyph_size.cx, infoPtr->glyph_size.cy);
2525 else
2527 logfont.lfWidth = infoPtr->glyph_size.cx;
2528 logfont.lfHeight = infoPtr->glyph_size.cy;
2531 else logfont.lfHeight = infoPtr->glyph_size.cx;
2533 if ((font = CreateFontIndirectW(&logfont)))
2535 old_font = SelectObject(hdc, font);
2536 DrawTextW(hdc, (const WCHAR*)&infoPtr->glyph, 1, rect,
2537 DT_CENTER | DT_VCENTER | DT_SINGLELINE | DT_NOCLIP | DT_NOPREFIX);
2538 SelectObject(hdc, old_font);
2539 DeleteObject(font);
2545 /**********************************************************************
2546 * Command Link Functions
2548 static void CL_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action )
2550 LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2551 LONG state = infoPtr->state;
2553 RECT rc, content_rect;
2554 NMCUSTOMDRAW nmcd;
2555 HPEN pen, old_pen;
2556 HBRUSH old_brush;
2557 INT old_bk_mode;
2558 LRESULT cdrf;
2559 HWND parent;
2560 HRGN hrgn;
2562 GetClientRect(infoPtr->hwnd, &rc);
2564 /* Command Links are not affected by the button's font, and are based
2565 on the default message font. Furthermore, they are not affected by
2566 any of the alignment styles (and always align with the top-left). */
2567 if (!(parent = GetParent(infoPtr->hwnd))) parent = infoPtr->hwnd;
2568 SendMessageW(parent, WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
2570 hrgn = set_control_clipping(hDC, &rc);
2572 pen = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_WINDOWFRAME));
2573 old_pen = SelectObject(hDC, pen);
2574 old_brush = SelectObject(hDC, GetSysColorBrush(COLOR_BTNFACE));
2575 old_bk_mode = SetBkMode(hDC, TRANSPARENT);
2577 init_custom_draw(&nmcd, infoPtr, hDC, &rc);
2579 /* Send erase notifications */
2580 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2581 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2582 content_rect = rc;
2584 if (get_button_type(style) == BS_DEFCOMMANDLINK)
2586 if (action != ODA_FOCUS)
2587 Rectangle(hDC, rc.left, rc.top, rc.right, rc.bottom);
2588 InflateRect(&rc, -1, -1);
2591 /* Skip the frame drawing if only focus has changed */
2592 if (action != ODA_FOCUS)
2594 if (!(state & (BST_HOT | BST_PUSHED | BST_CHECKED | BST_INDETERMINATE)))
2595 FillRect(hDC, &rc, GetSysColorBrush(COLOR_BTNFACE));
2596 else
2598 UINT flags = DFCS_BUTTONPUSH;
2600 if (style & BS_FLAT) flags |= DFCS_MONO;
2601 else if (state & BST_PUSHED) flags |= DFCS_PUSHED;
2603 if (state & (BST_CHECKED | BST_INDETERMINATE))
2604 flags |= DFCS_CHECKED;
2605 DrawFrameControl(hDC, &rc, DFC_BUTTON, flags);
2609 if (cdrf & CDRF_NOTIFYPOSTERASE)
2611 nmcd.dwDrawStage = CDDS_POSTERASE;
2612 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2615 /* Send paint notifications */
2616 nmcd.dwDrawStage = CDDS_PREPAINT;
2617 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2618 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2620 if (!(cdrf & CDRF_DOERASE) && action != ODA_FOCUS)
2622 UINT flags = IsWindowEnabled(infoPtr->hwnd) ? DSS_NORMAL : DSS_DISABLED;
2623 COLORREF old_color = SetTextColor(hDC, GetSysColor(flags == DSS_NORMAL ?
2624 COLOR_BTNTEXT : COLOR_GRAYTEXT));
2625 HIMAGELIST defimg = NULL;
2626 NONCLIENTMETRICSW ncm;
2627 UINT txt_h = 0;
2628 SIZE img_size;
2630 /* Command Links ignore the margins of the image list or its alignment */
2631 if (infoPtr->u.image || infoPtr->imagelist.himl)
2632 img_size = BUTTON_GetImageSize(infoPtr);
2633 else
2635 img_size.cx = img_size.cy = command_link_defglyph_size;
2636 defimg = ImageList_LoadImageW(COMCTL32_hModule, (LPCWSTR)MAKEINTRESOURCE(IDB_CMDLINK),
2637 img_size.cx, 3, CLR_NONE, IMAGE_BITMAP, LR_CREATEDIBSECTION);
2640 /* Shrink rect by the command link margin, except on bottom (just the frame) */
2641 InflateRect(&content_rect, -command_link_margin, -command_link_margin);
2642 content_rect.bottom += command_link_margin - 2;
2644 ncm.cbSize = sizeof(ncm);
2645 if (SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, sizeof(ncm), &ncm, 0))
2647 LONG note_weight = ncm.lfMessageFont.lfWeight;
2648 RECT r = content_rect;
2649 WCHAR *text;
2650 HFONT font;
2652 if (img_size.cx) r.left += img_size.cx + command_link_margin;
2654 /* Draw the text */
2655 ncm.lfMessageFont.lfWeight = FW_BOLD;
2656 if ((font = CreateFontIndirectW(&ncm.lfMessageFont)))
2658 if ((text = get_button_text(infoPtr)))
2660 SelectObject(hDC, font);
2661 txt_h = DrawTextW(hDC, text, -1, &r,
2662 DT_TOP | DT_LEFT | DT_WORDBREAK | DT_END_ELLIPSIS);
2663 heap_free(text);
2665 DeleteObject(font);
2668 /* Draw the note */
2669 ncm.lfMessageFont.lfWeight = note_weight;
2670 if (infoPtr->note && (font = CreateFontIndirectW(&ncm.lfMessageFont)))
2672 r.top += txt_h + 2;
2673 SelectObject(hDC, font);
2674 DrawTextW(hDC, infoPtr->note, infoPtr->note_length, &r,
2675 DT_TOP | DT_LEFT | DT_WORDBREAK | DT_NOPREFIX);
2676 DeleteObject(font);
2680 /* Position the image at the vertical center of the drawn text (not note) */
2681 txt_h = min(txt_h, content_rect.bottom - content_rect.top);
2682 if (img_size.cy < txt_h) content_rect.top += (txt_h - img_size.cy) / 2;
2684 content_rect.right = content_rect.left + img_size.cx;
2685 content_rect.bottom = content_rect.top + img_size.cy;
2687 if (defimg)
2689 int i = 0;
2690 if (flags == DSS_DISABLED) i = 2;
2691 else if (state & BST_HOT) i = 1;
2693 ImageList_Draw(defimg, i, hDC, content_rect.left, content_rect.top, ILD_NORMAL);
2694 ImageList_Destroy(defimg);
2696 else
2697 BUTTON_DrawImage(infoPtr, hDC, NULL, flags, &content_rect);
2699 SetTextColor(hDC, old_color);
2702 if (cdrf & CDRF_NOTIFYPOSTPAINT)
2704 nmcd.dwDrawStage = CDDS_POSTPAINT;
2705 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2707 if (cdrf & CDRF_SKIPPOSTPAINT) goto cleanup;
2709 if (action == ODA_FOCUS || (state & BST_FOCUS))
2711 InflateRect(&rc, -2, -2);
2712 DrawFocusRect(hDC, &rc);
2715 cleanup:
2716 SelectObject(hDC, old_pen);
2717 SelectObject(hDC, old_brush);
2718 SetBkMode(hDC, old_bk_mode);
2719 SelectClipRgn(hDC, hrgn);
2720 if (hrgn) DeleteObject(hrgn);
2721 DeleteObject(pen);
2725 /**********************************************************************
2726 * Themed Paint Functions
2728 static void PB_ThemedPaint(HTHEME theme, const BUTTON_INFO *infoPtr, HDC hDC, int state, UINT dtFlags, BOOL focused)
2730 RECT bgRect, labelRect, imageRect, textRect, focusRect;
2731 NMCUSTOMDRAW nmcd;
2732 LRESULT cdrf;
2733 HWND parent;
2735 if (infoPtr->font) SelectObject(hDC, infoPtr->font);
2737 GetClientRect(infoPtr->hwnd, &bgRect);
2738 GetThemeBackgroundContentRect(theme, hDC, BP_PUSHBUTTON, state, &bgRect, &labelRect);
2739 focusRect = labelRect;
2741 init_custom_draw(&nmcd, infoPtr, hDC, &bgRect);
2743 parent = GetParent(infoPtr->hwnd);
2744 if (!parent) parent = infoPtr->hwnd;
2746 /* Send erase notifications */
2747 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2748 if (cdrf & CDRF_SKIPDEFAULT) return;
2750 if (IsThemeBackgroundPartiallyTransparent(theme, BP_PUSHBUTTON, state))
2751 DrawThemeParentBackground(infoPtr->hwnd, hDC, NULL);
2752 DrawThemeBackground(theme, hDC, BP_PUSHBUTTON, state, &bgRect, NULL);
2754 if (cdrf & CDRF_NOTIFYPOSTERASE)
2756 nmcd.dwDrawStage = CDDS_POSTERASE;
2757 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2760 /* Send paint notifications */
2761 nmcd.dwDrawStage = CDDS_PREPAINT;
2762 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2763 if (cdrf & CDRF_SKIPDEFAULT) return;
2765 if (!(cdrf & CDRF_DOERASE))
2767 dtFlags = BUTTON_CalcLayoutRects(infoPtr, hDC, &labelRect, &imageRect, &textRect);
2768 if (dtFlags != (UINT)-1L)
2769 BUTTON_DrawThemedLabel(infoPtr, hDC, dtFlags, &imageRect, &textRect, theme,
2770 BP_PUSHBUTTON, state);
2773 if (cdrf & CDRF_NOTIFYPOSTPAINT)
2775 nmcd.dwDrawStage = CDDS_POSTPAINT;
2776 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2778 if (cdrf & CDRF_SKIPPOSTPAINT) return;
2780 if (focused) DrawFocusRect(hDC, &focusRect);
2783 static void CB_ThemedPaint(HTHEME theme, const BUTTON_INFO *infoPtr, HDC hDC, int state, UINT dtFlags, BOOL focused)
2785 RECT client_rect, content_rect, old_label_rect, label_rect, box_rect, image_rect, text_rect;
2786 HFONT font, hPrevFont = NULL;
2787 DWORD dwStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2788 LONG ex_style = GetWindowLongW(infoPtr->hwnd, GWL_EXSTYLE);
2789 UINT btn_type = get_button_type( dwStyle );
2790 int part = (btn_type == BS_RADIOBUTTON) || (btn_type == BS_AUTORADIOBUTTON) ? BP_RADIOBUTTON : BP_CHECKBOX;
2791 NMCUSTOMDRAW nmcd;
2792 LRESULT cdrf;
2793 LOGFONTW lf;
2794 HWND parent;
2795 BOOL created_font = FALSE;
2796 int text_offset;
2797 SIZE box_size;
2798 HRGN region;
2799 HRESULT hr;
2801 if (dwStyle & BS_PUSHLIKE)
2803 PB_ThemedPaint(theme, infoPtr, hDC, state, dtFlags, focused);
2804 return;
2807 hr = GetThemeFont(theme, hDC, part, state, TMT_FONT, &lf);
2808 if (SUCCEEDED(hr)) {
2809 font = CreateFontIndirectW(&lf);
2810 if (!font)
2811 TRACE("Failed to create font\n");
2812 else {
2813 TRACE("font = %s\n", debugstr_w(lf.lfFaceName));
2814 hPrevFont = SelectObject(hDC, font);
2815 created_font = TRUE;
2817 } else {
2818 if (infoPtr->font) SelectObject(hDC, infoPtr->font);
2821 GetClientRect(infoPtr->hwnd, &client_rect);
2822 GetThemeBackgroundContentRect(theme, hDC, part, state, &client_rect, &content_rect);
2823 region = set_control_clipping(hDC, &client_rect);
2825 if (FAILED(GetThemePartSize(theme, hDC, part, state, NULL, TS_DRAW, &box_size)))
2827 box_size.cx = 12 * GetDpiForWindow(infoPtr->hwnd) / 96 + 1;
2828 box_size.cy = box_size.cx;
2831 GetCharWidthW(hDC, '0', '0', &text_offset);
2832 text_offset /= 2;
2834 label_rect = content_rect;
2835 if (dwStyle & BS_LEFTTEXT || ex_style & WS_EX_RIGHT)
2836 label_rect.right -= box_size.cx + text_offset;
2837 else
2838 label_rect.left += box_size.cx + text_offset;
2840 old_label_rect = label_rect;
2841 dtFlags = BUTTON_CalcLayoutRects(infoPtr, hDC, &label_rect, &image_rect, &text_rect);
2842 box_rect = get_box_rect(dwStyle, ex_style, &content_rect, &label_rect, dtFlags != (UINT)-1L,
2843 box_size);
2845 init_custom_draw(&nmcd, infoPtr, hDC, &client_rect);
2847 parent = GetParent(infoPtr->hwnd);
2848 if (!parent) parent = infoPtr->hwnd;
2850 /* Send erase notifications */
2851 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2852 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2854 DrawThemeParentBackground(infoPtr->hwnd, hDC, NULL);
2856 if (cdrf & CDRF_NOTIFYPOSTERASE)
2858 nmcd.dwDrawStage = CDDS_POSTERASE;
2859 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2862 /* Send paint notifications */
2863 nmcd.dwDrawStage = CDDS_PREPAINT;
2864 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2865 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2867 /* Draw label */
2868 if (!(cdrf & CDRF_DOERASE))
2870 DrawThemeBackground(theme, hDC, part, state, &box_rect, NULL);
2871 if (dtFlags != (UINT)-1L)
2872 BUTTON_DrawThemedLabel(infoPtr, hDC, dtFlags, &image_rect, &text_rect, theme, part, state);
2875 if (cdrf & CDRF_NOTIFYPOSTPAINT)
2877 nmcd.dwDrawStage = CDDS_POSTPAINT;
2878 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2880 if ((cdrf & CDRF_SKIPPOSTPAINT) || dtFlags == (UINT)-1L) goto cleanup;
2882 if (focused)
2884 label_rect.left--;
2885 label_rect.right++;
2886 IntersectRect(&label_rect, &label_rect, &old_label_rect);
2887 DrawFocusRect(hDC, &label_rect);
2890 cleanup:
2891 SelectClipRgn(hDC, region);
2892 if (region) DeleteObject(region);
2893 if (created_font) DeleteObject(font);
2894 if (hPrevFont) SelectObject(hDC, hPrevFont);
2897 static void GB_ThemedPaint(HTHEME theme, const BUTTON_INFO *infoPtr, HDC hDC, int state, UINT dtFlags, BOOL focused)
2899 RECT clientRect, contentRect, labelRect, imageRect, textRect, bgRect;
2900 HRGN region, textRegion = NULL;
2901 LOGFONTW lf;
2902 HFONT font, hPrevFont = NULL;
2903 BOOL created_font = FALSE;
2904 TEXTMETRICW textMetric;
2905 LONG style;
2906 int part;
2908 HRESULT hr = GetThemeFont(theme, hDC, BP_GROUPBOX, state, TMT_FONT, &lf);
2909 if (SUCCEEDED(hr)) {
2910 font = CreateFontIndirectW(&lf);
2911 if (!font)
2912 TRACE("Failed to create font\n");
2913 else {
2914 hPrevFont = SelectObject(hDC, font);
2915 created_font = TRUE;
2917 } else {
2918 if (infoPtr->font)
2919 SelectObject(hDC, infoPtr->font);
2922 GetClientRect(infoPtr->hwnd, &clientRect);
2923 region = set_control_clipping(hDC, &clientRect);
2925 bgRect = clientRect;
2926 GetTextMetricsW(hDC, &textMetric);
2927 bgRect.top += (textMetric.tmHeight / 2) - 1;
2929 labelRect = clientRect;
2930 InflateRect(&labelRect, -7, 1);
2931 dtFlags = BUTTON_CalcLayoutRects(infoPtr, hDC, &labelRect, &imageRect, &textRect);
2932 if (dtFlags != (UINT)-1 && !show_image_only(infoPtr))
2934 textRegion = CreateRectRgnIndirect(&textRect);
2935 ExtSelectClipRgn(hDC, textRegion, RGN_DIFF);
2938 style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2939 if (style & BS_PUSHLIKE)
2941 part = BP_PUSHBUTTON;
2943 else
2945 part = BP_GROUPBOX;
2946 GetThemeBackgroundContentRect(theme, hDC, part, state, &bgRect, &contentRect);
2947 ExcludeClipRect(hDC, contentRect.left, contentRect.top, contentRect.right, contentRect.bottom);
2949 if (IsThemeBackgroundPartiallyTransparent(theme, part, state))
2950 DrawThemeParentBackground(infoPtr->hwnd, hDC, NULL);
2951 DrawThemeBackground(theme, hDC, part, state, &bgRect, NULL);
2953 if (dtFlags != (UINT)-1)
2955 if (textRegion)
2957 SelectClipRgn(hDC, textRegion);
2958 DeleteObject(textRegion);
2960 BUTTON_DrawThemedLabel(infoPtr, hDC, dtFlags, &imageRect, &textRect, theme, part, state);
2963 SelectClipRgn(hDC, region);
2964 if (region) DeleteObject(region);
2965 if (created_font) DeleteObject(font);
2966 if (hPrevFont) SelectObject(hDC, hPrevFont);
2969 static void SB_ThemedPaint(HTHEME theme, const BUTTON_INFO *infoPtr, HDC hDC, int state, UINT dtFlags, BOOL focused)
2971 RECT rc, content_rect, push_rect, dropdown_rect, focus_rect, label_rect, image_rect, text_rect;
2972 NMCUSTOMDRAW nmcd;
2973 LRESULT cdrf;
2974 HWND parent;
2976 if (infoPtr->font) SelectObject(hDC, infoPtr->font);
2978 GetClientRect(infoPtr->hwnd, &rc);
2979 init_custom_draw(&nmcd, infoPtr, hDC, &rc);
2981 parent = GetParent(infoPtr->hwnd);
2982 if (!parent) parent = infoPtr->hwnd;
2984 /* Send erase notifications */
2985 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2986 if (cdrf & CDRF_SKIPDEFAULT) return;
2988 if (IsThemeBackgroundPartiallyTransparent(theme, BP_PUSHBUTTON, state))
2989 DrawThemeParentBackground(infoPtr->hwnd, hDC, NULL);
2991 /* The zone outside the content is ignored for the dropdown (draws over) */
2992 GetThemeBackgroundContentRect(theme, hDC, BP_PUSHBUTTON, state, &rc, &content_rect);
2993 get_split_button_rects(infoPtr, &rc, &push_rect, &dropdown_rect);
2995 if (infoPtr->split_style & BCSS_NOSPLIT)
2997 push_rect = rc;
2998 DrawThemeBackground(theme, hDC, BP_PUSHBUTTON, state, &rc, NULL);
2999 GetThemeBackgroundContentRect(theme, hDC, BP_PUSHBUTTON, state, &push_rect, &focus_rect);
3001 else
3003 RECT r = { dropdown_rect.left, content_rect.top, dropdown_rect.right, content_rect.bottom };
3004 UINT edge = (infoPtr->split_style & BCSS_ALIGNLEFT) ? BF_RIGHT : BF_LEFT;
3005 const RECT *clip = NULL;
3007 /* If only the dropdown is pressed, we need to draw it separately */
3008 if (state != PBS_PRESSED && (infoPtr->state & BST_DROPDOWNPUSHED))
3010 DrawThemeBackground(theme, hDC, BP_PUSHBUTTON, PBS_PRESSED, &rc, &dropdown_rect);
3011 clip = &push_rect;
3013 DrawThemeBackground(theme, hDC, BP_PUSHBUTTON, state, &rc, clip);
3015 /* Draw the separator */
3016 DrawThemeEdge(theme, hDC, BP_PUSHBUTTON, state, &r, EDGE_ETCHED, edge, NULL);
3018 /* The content rect should be the content area of the push button */
3019 GetThemeBackgroundContentRect(theme, hDC, BP_PUSHBUTTON, state, &push_rect, &content_rect);
3020 focus_rect = content_rect;
3023 if (cdrf & CDRF_NOTIFYPOSTERASE)
3025 nmcd.dwDrawStage = CDDS_POSTERASE;
3026 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
3029 /* Send paint notifications */
3030 nmcd.dwDrawStage = CDDS_PREPAINT;
3031 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
3032 if (cdrf & CDRF_SKIPDEFAULT) return;
3034 if (!(cdrf & CDRF_DOERASE))
3036 COLORREF old_color, color;
3037 INT old_bk_mode;
3039 label_rect = content_rect;
3040 dtFlags = BUTTON_CalcLayoutRects(infoPtr, hDC, &label_rect, &image_rect, &text_rect);
3041 if (dtFlags != (UINT)-1L)
3042 BUTTON_DrawThemedLabel(infoPtr, hDC, dtFlags, &image_rect, &text_rect, theme,
3043 BP_PUSHBUTTON, state);
3045 GetThemeColor(theme, BP_PUSHBUTTON, state, TMT_TEXTCOLOR, &color);
3046 old_bk_mode = SetBkMode(hDC, TRANSPARENT);
3047 old_color = SetTextColor(hDC, color);
3049 draw_split_button_dropdown_glyph(infoPtr, hDC, &dropdown_rect);
3051 SetTextColor(hDC, old_color);
3052 SetBkMode(hDC, old_bk_mode);
3055 if (cdrf & CDRF_NOTIFYPOSTPAINT)
3057 nmcd.dwDrawStage = CDDS_POSTPAINT;
3058 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
3060 if (cdrf & CDRF_SKIPPOSTPAINT) return;
3062 if (focused) DrawFocusRect(hDC, &focus_rect);
3065 static void CL_ThemedPaint(HTHEME theme, const BUTTON_INFO *infoPtr, HDC hDC, int state, UINT dtFlags, BOOL focused)
3067 NMCUSTOMDRAW nmcd;
3068 LRESULT cdrf;
3069 HWND parent;
3070 int part;
3071 RECT rc;
3073 if (infoPtr->font) SelectObject(hDC, infoPtr->font);
3075 GetClientRect(infoPtr->hwnd, &rc);
3076 init_custom_draw(&nmcd, infoPtr, hDC, &rc);
3078 parent = GetParent(infoPtr->hwnd);
3079 if (!parent) parent = infoPtr->hwnd;
3081 /* Send erase notifications */
3082 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
3083 if (cdrf & CDRF_SKIPDEFAULT) return;
3085 part = GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & BS_PUSHLIKE ? BP_PUSHBUTTON : BP_COMMANDLINK;
3086 if (IsThemeBackgroundPartiallyTransparent(theme, part, state))
3087 DrawThemeParentBackground(infoPtr->hwnd, hDC, NULL);
3088 DrawThemeBackground(theme, hDC, part, state, &rc, NULL);
3090 if (cdrf & CDRF_NOTIFYPOSTERASE)
3092 nmcd.dwDrawStage = CDDS_POSTERASE;
3093 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
3096 /* Send paint notifications */
3097 nmcd.dwDrawStage = CDDS_PREPAINT;
3098 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
3099 if (cdrf & CDRF_SKIPDEFAULT) return;
3101 if (!(cdrf & CDRF_DOERASE))
3103 RECT r, img_rect;
3104 UINT txt_h = 0;
3105 SIZE img_size;
3106 WCHAR *text;
3108 GetThemeBackgroundContentRect(theme, hDC, part, state, &rc, &r);
3110 /* The text alignment and styles are fixed and don't depend on button styles */
3111 dtFlags = DT_TOP | DT_LEFT | DT_WORDBREAK;
3113 /* Command Links ignore the margins of the image list or its alignment */
3114 if (infoPtr->u.image || infoPtr->imagelist.himl)
3115 img_size = BUTTON_GetImageSize(infoPtr);
3116 else
3117 GetThemePartSize(theme, NULL, BP_COMMANDLINKGLYPH, state, NULL, TS_DRAW, &img_size);
3119 img_rect = r;
3120 if (img_size.cx) r.left += img_size.cx + command_link_margin;
3122 /* Draw the text */
3123 if ((text = get_button_text(infoPtr)))
3125 UINT len = lstrlenW(text);
3126 RECT text_rect;
3128 GetThemeTextExtent(theme, hDC, part, state, text, len, dtFlags | DT_END_ELLIPSIS, &r,
3129 &text_rect);
3130 DrawThemeText(theme, hDC, part, state, text, len, dtFlags | DT_END_ELLIPSIS, 0, &r);
3132 txt_h = text_rect.bottom - text_rect.top;
3133 heap_free(text);
3136 /* Draw the note */
3137 if (infoPtr->note)
3139 DTTOPTS opts;
3141 r.top += txt_h;
3142 opts.dwSize = sizeof(opts);
3143 opts.dwFlags = DTT_FONTPROP;
3144 opts.iFontPropId = TMT_BODYFONT;
3145 DrawThemeTextEx(theme, hDC, part, state, infoPtr->note, infoPtr->note_length,
3146 dtFlags | DT_NOPREFIX, &r, &opts);
3149 /* Position the image at the vertical center of the drawn text (not note) */
3150 txt_h = min(txt_h, img_rect.bottom - img_rect.top);
3151 if (img_size.cy < txt_h) img_rect.top += (txt_h - img_size.cy) / 2;
3153 img_rect.right = img_rect.left + img_size.cx;
3154 img_rect.bottom = img_rect.top + img_size.cy;
3156 if (infoPtr->u.image || infoPtr->imagelist.himl)
3157 BUTTON_DrawImage(infoPtr, hDC, NULL,
3158 (state == CMDLS_DISABLED) ? DSS_DISABLED : DSS_NORMAL,
3159 &img_rect);
3160 else
3161 DrawThemeBackground(theme, hDC, BP_COMMANDLINKGLYPH, state, &img_rect, NULL);
3164 if (cdrf & CDRF_NOTIFYPOSTPAINT)
3166 nmcd.dwDrawStage = CDDS_POSTPAINT;
3167 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
3169 if (cdrf & CDRF_SKIPPOSTPAINT) return;
3171 if (focused)
3173 MARGINS margins;
3175 /* The focus rect has margins of a push button rather than command link... */
3176 GetThemeMargins(theme, hDC, BP_PUSHBUTTON, state, TMT_CONTENTMARGINS, NULL, &margins);
3178 rc.left += margins.cxLeftWidth;
3179 rc.top += margins.cyTopHeight;
3180 rc.right -= margins.cxRightWidth;
3181 rc.bottom -= margins.cyBottomHeight;
3182 DrawFocusRect(hDC, &rc);
3186 void BUTTON_Register(void)
3188 WNDCLASSW wndClass;
3190 memset(&wndClass, 0, sizeof(wndClass));
3191 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS | CS_VREDRAW | CS_HREDRAW | CS_PARENTDC;
3192 wndClass.lpfnWndProc = BUTTON_WindowProc;
3193 wndClass.cbClsExtra = 0;
3194 wndClass.cbWndExtra = sizeof(BUTTON_INFO *);
3195 wndClass.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW);
3196 wndClass.hbrBackground = NULL;
3197 wndClass.lpszClassName = WC_BUTTONW;
3198 RegisterClassW(&wndClass);