dbghelp: Remove DMT_ entries for .DBG and .PDB files.
[wine.git] / dlls / comctl32 / button.c
blob77eb54fbcf1c47f475d09125c981648e2af8c7c7
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"
59 #include "comctl32.h"
61 WINE_DEFAULT_DEBUG_CHANNEL(button);
63 /* undocumented flags */
64 #define BUTTON_NSTATES 0x0F
65 #define BUTTON_BTNPRESSED 0x40
66 #define BUTTON_UNKNOWN2 0x20
67 #define BUTTON_UNKNOWN3 0x10
69 #define BUTTON_NOTIFY_PARENT(hWnd, code) \
70 do { /* Notify parent which has created this button control */ \
71 TRACE("notification " #code " sent to hwnd=%p\n", GetParent(hWnd)); \
72 SendMessageW(GetParent(hWnd), WM_COMMAND, \
73 MAKEWPARAM(GetWindowLongPtrW((hWnd),GWLP_ID), (code)), \
74 (LPARAM)(hWnd)); \
75 } while(0)
77 typedef struct _BUTTON_INFO
79 HWND hwnd;
80 HWND parent;
81 LONG style;
82 LONG state;
83 HFONT font;
84 WCHAR *note;
85 INT note_length;
86 DWORD image_type; /* IMAGE_BITMAP or IMAGE_ICON */
87 BUTTON_IMAGELIST imagelist;
88 UINT split_style;
89 HIMAGELIST glyph; /* this is a font character code when split_style doesn't have BCSS_IMAGE */
90 SIZE glyph_size;
91 RECT text_margin;
92 HANDLE image; /* Original handle set with BM_SETIMAGE and returned with BM_GETIMAGE. */
93 union
95 HICON icon;
96 HBITMAP bitmap;
97 HANDLE image; /* Duplicated handle used for drawing. */
98 } u;
99 } BUTTON_INFO;
101 static UINT BUTTON_CalcLayoutRects( const BUTTON_INFO *infoPtr, HDC hdc, RECT *labelRc, RECT *imageRc, RECT *textRc );
102 static void PB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action );
103 static void CB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action );
104 static void GB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action );
105 static void UB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action );
106 static void OB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action );
107 static void SB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action );
108 static void CL_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action );
109 static void BUTTON_CheckAutoRadioButton( HWND hwnd );
110 static void get_split_button_rects(const BUTTON_INFO*, const RECT*, RECT*, RECT*);
111 static BOOL notify_split_button_dropdown(const BUTTON_INFO*, const POINT*, HWND);
112 static void draw_split_button_dropdown_glyph(const BUTTON_INFO*, HDC, RECT*);
114 #define MAX_BTN_TYPE 16
116 static const WORD maxCheckState[MAX_BTN_TYPE] =
118 BST_UNCHECKED, /* BS_PUSHBUTTON */
119 BST_UNCHECKED, /* BS_DEFPUSHBUTTON */
120 BST_CHECKED, /* BS_CHECKBOX */
121 BST_CHECKED, /* BS_AUTOCHECKBOX */
122 BST_CHECKED, /* BS_RADIOBUTTON */
123 BST_INDETERMINATE, /* BS_3STATE */
124 BST_INDETERMINATE, /* BS_AUTO3STATE */
125 BST_UNCHECKED, /* BS_GROUPBOX */
126 BST_UNCHECKED, /* BS_USERBUTTON */
127 BST_CHECKED, /* BS_AUTORADIOBUTTON */
128 BST_UNCHECKED, /* BS_PUSHBOX */
129 BST_UNCHECKED, /* BS_OWNERDRAW */
130 BST_UNCHECKED, /* BS_SPLITBUTTON */
131 BST_UNCHECKED, /* BS_DEFSPLITBUTTON */
132 BST_UNCHECKED, /* BS_COMMANDLINK */
133 BST_UNCHECKED /* BS_DEFCOMMANDLINK */
136 /* Generic draw states, use get_draw_state() to get specific state for button type */
137 enum draw_state
139 STATE_NORMAL,
140 STATE_DISABLED,
141 STATE_HOT,
142 STATE_PRESSED,
143 STATE_DEFAULTED,
144 DRAW_STATE_COUNT
147 typedef void (*pfPaint)( const BUTTON_INFO *infoPtr, HDC hdc, UINT action );
149 static const pfPaint btnPaintFunc[MAX_BTN_TYPE] =
151 PB_Paint, /* BS_PUSHBUTTON */
152 PB_Paint, /* BS_DEFPUSHBUTTON */
153 CB_Paint, /* BS_CHECKBOX */
154 CB_Paint, /* BS_AUTOCHECKBOX */
155 CB_Paint, /* BS_RADIOBUTTON */
156 CB_Paint, /* BS_3STATE */
157 CB_Paint, /* BS_AUTO3STATE */
158 GB_Paint, /* BS_GROUPBOX */
159 UB_Paint, /* BS_USERBUTTON */
160 CB_Paint, /* BS_AUTORADIOBUTTON */
161 NULL, /* BS_PUSHBOX */
162 OB_Paint, /* BS_OWNERDRAW */
163 SB_Paint, /* BS_SPLITBUTTON */
164 SB_Paint, /* BS_DEFSPLITBUTTON */
165 CL_Paint, /* BS_COMMANDLINK */
166 CL_Paint /* BS_DEFCOMMANDLINK */
169 typedef void (*pfThemedPaint)( HTHEME theme, const BUTTON_INFO *infoPtr, HDC hdc, int drawState, UINT dtflags, BOOL focused);
171 static void PB_ThemedPaint( HTHEME theme, const BUTTON_INFO *infoPtr, HDC hdc, int drawState, UINT dtflags, BOOL focused);
172 static void CB_ThemedPaint( HTHEME theme, const BUTTON_INFO *infoPtr, HDC hdc, int drawState, UINT dtflags, BOOL focused);
173 static void GB_ThemedPaint( HTHEME theme, const BUTTON_INFO *infoPtr, HDC hdc, int drawState, UINT dtflags, BOOL focused);
174 static void SB_ThemedPaint( HTHEME theme, const BUTTON_INFO *infoPtr, HDC hdc, int drawState, UINT dtflags, BOOL focused);
175 static void CL_ThemedPaint( HTHEME theme, const BUTTON_INFO *infoPtr, HDC hdc, int drawState, UINT dtflags, BOOL focused);
177 static const pfThemedPaint btnThemedPaintFunc[MAX_BTN_TYPE] =
179 PB_ThemedPaint, /* BS_PUSHBUTTON */
180 PB_ThemedPaint, /* BS_DEFPUSHBUTTON */
181 CB_ThemedPaint, /* BS_CHECKBOX */
182 CB_ThemedPaint, /* BS_AUTOCHECKBOX */
183 CB_ThemedPaint, /* BS_RADIOBUTTON */
184 CB_ThemedPaint, /* BS_3STATE */
185 CB_ThemedPaint, /* BS_AUTO3STATE */
186 GB_ThemedPaint, /* BS_GROUPBOX */
187 NULL, /* BS_USERBUTTON */
188 CB_ThemedPaint, /* BS_AUTORADIOBUTTON */
189 NULL, /* BS_PUSHBOX */
190 NULL, /* BS_OWNERDRAW */
191 SB_ThemedPaint, /* BS_SPLITBUTTON */
192 SB_ThemedPaint, /* BS_DEFSPLITBUTTON */
193 CL_ThemedPaint, /* BS_COMMANDLINK */
194 CL_ThemedPaint /* BS_DEFCOMMANDLINK */
197 typedef BOOL (*pfGetIdealSize)(BUTTON_INFO *infoPtr, SIZE *size);
199 static BOOL PB_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size);
200 static BOOL CB_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size);
201 static BOOL GB_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size);
202 static BOOL SB_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size);
203 static BOOL CL_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size);
205 static const pfGetIdealSize btnGetIdealSizeFunc[MAX_BTN_TYPE] = {
206 PB_GetIdealSize, /* BS_PUSHBUTTON */
207 PB_GetIdealSize, /* BS_DEFPUSHBUTTON */
208 CB_GetIdealSize, /* BS_CHECKBOX */
209 CB_GetIdealSize, /* BS_AUTOCHECKBOX */
210 CB_GetIdealSize, /* BS_RADIOBUTTON */
211 GB_GetIdealSize, /* BS_3STATE */
212 GB_GetIdealSize, /* BS_AUTO3STATE */
213 GB_GetIdealSize, /* BS_GROUPBOX */
214 PB_GetIdealSize, /* BS_USERBUTTON */
215 CB_GetIdealSize, /* BS_AUTORADIOBUTTON */
216 GB_GetIdealSize, /* BS_PUSHBOX */
217 GB_GetIdealSize, /* BS_OWNERDRAW */
218 SB_GetIdealSize, /* BS_SPLITBUTTON */
219 SB_GetIdealSize, /* BS_DEFSPLITBUTTON */
220 CL_GetIdealSize, /* BS_COMMANDLINK */
221 CL_GetIdealSize /* BS_DEFCOMMANDLINK */
224 /* Fixed margin for command links, regardless of DPI (based on tests done on Windows) */
225 enum { command_link_margin = 6 };
227 /* The width and height for the default command link glyph (when there's no image) */
228 enum { command_link_defglyph_size = 17 };
230 static inline UINT get_button_type( LONG window_style )
232 return (window_style & BS_TYPEMASK);
235 static inline BOOL button_centers_text( LONG window_style )
237 /* Push button's text is centered by default, same for split buttons */
238 UINT type = get_button_type(window_style);
239 return type <= BS_DEFPUSHBUTTON || type == BS_SPLITBUTTON || type == BS_DEFSPLITBUTTON;
242 /* paint a button of any type */
243 static inline void paint_button( BUTTON_INFO *infoPtr, LONG style, UINT action )
245 if (btnPaintFunc[style] && IsWindowVisible(infoPtr->hwnd))
247 HDC hdc = GetDC( infoPtr->hwnd );
248 btnPaintFunc[style]( infoPtr, hdc, action );
249 ReleaseDC( infoPtr->hwnd, hdc );
253 /* retrieve the button text; returned buffer must be freed by caller */
254 static inline WCHAR *get_button_text( const BUTTON_INFO *infoPtr )
256 INT len = GetWindowTextLengthW( infoPtr->hwnd );
257 WCHAR *buffer = Alloc( (len + 1) * sizeof(WCHAR) );
258 if (buffer)
259 GetWindowTextW( infoPtr->hwnd, buffer, len + 1 );
260 return buffer;
263 /* get the default glyph size for split buttons */
264 static LONG get_default_glyph_size(const BUTTON_INFO *infoPtr)
266 if (infoPtr->split_style & BCSS_IMAGE)
268 /* Size it to fit, including the left and right edges */
269 int w, h;
270 if (!ImageList_GetIconSize(infoPtr->glyph, &w, &h)) w = 0;
271 return w + GetSystemMetrics(SM_CXEDGE) * 2;
274 /* The glyph size relies on the default menu font's cell height */
275 return GetSystemMetrics(SM_CYMENUCHECK);
278 static BOOL is_themed_paint_supported(HTHEME theme, UINT btn_type)
280 if (!theme || !btnThemedPaintFunc[btn_type])
281 return FALSE;
283 if (btn_type == BS_COMMANDLINK || btn_type == BS_DEFCOMMANDLINK)
285 if (!IsThemePartDefined(theme, BP_COMMANDLINK, 0))
286 return FALSE;
289 return TRUE;
292 static void init_custom_draw(NMCUSTOMDRAW *nmcd, const BUTTON_INFO *infoPtr, HDC hdc, const RECT *rc)
294 nmcd->hdr.hwndFrom = infoPtr->hwnd;
295 nmcd->hdr.idFrom = GetWindowLongPtrW(infoPtr->hwnd, GWLP_ID);
296 nmcd->hdr.code = NM_CUSTOMDRAW;
297 nmcd->hdc = hdc;
298 nmcd->rc = *rc;
299 nmcd->dwDrawStage = CDDS_PREERASE;
300 nmcd->dwItemSpec = 0;
301 nmcd->lItemlParam = 0;
302 nmcd->uItemState = IsWindowEnabled(infoPtr->hwnd) ? 0 : CDIS_DISABLED;
303 if (infoPtr->state & BST_PUSHED) nmcd->uItemState |= CDIS_SELECTED;
304 if (infoPtr->state & BST_FOCUS) nmcd->uItemState |= CDIS_FOCUS;
305 if (infoPtr->state & BST_HOT) nmcd->uItemState |= CDIS_HOT;
306 if (infoPtr->state & BST_INDETERMINATE)
307 nmcd->uItemState |= CDIS_INDETERMINATE;
309 /* Windows doesn't seem to send CDIS_CHECKED (it fails the tests) */
310 /* CDIS_SHOWKEYBOARDCUES is misleading, as the meaning is reversed */
311 /* FIXME: Handle it properly when we support keyboard cues? */
314 HRGN set_control_clipping( HDC hdc, const RECT *rect )
316 RECT rc = *rect;
317 HRGN hrgn = CreateRectRgn( 0, 0, 0, 0 );
319 if (GetClipRgn( hdc, hrgn ) != 1)
321 DeleteObject( hrgn );
322 hrgn = 0;
324 DPtoLP( hdc, (POINT *)&rc, 2 );
325 if (GetLayout( hdc ) & LAYOUT_RTL) /* compensate for the shifting done by IntersectClipRect */
327 rc.left++;
328 rc.right++;
330 IntersectClipRect( hdc, rc.left, rc.top, rc.right, rc.bottom );
331 return hrgn;
334 static WCHAR *heap_strndupW(const WCHAR *src, size_t length)
336 size_t size = (length + 1) * sizeof(WCHAR);
337 WCHAR *dst = Alloc(size);
338 if (dst) memcpy(dst, src, size);
339 return dst;
342 /**********************************************************************
343 * Convert button styles to flags used by DrawText.
345 static UINT BUTTON_BStoDT( DWORD style, DWORD ex_style )
347 UINT dtStyle = DT_NOCLIP; /* We use SelectClipRgn to limit output */
349 /* "Convert" pushlike buttons to pushbuttons */
350 if (style & BS_PUSHLIKE)
351 style &= ~BS_TYPEMASK;
353 if (!(style & BS_MULTILINE))
354 dtStyle |= DT_SINGLELINE;
355 else
356 dtStyle |= DT_WORDBREAK;
358 switch (style & BS_CENTER)
360 case BS_LEFT: /* DT_LEFT is 0 */ break;
361 case BS_RIGHT: dtStyle |= DT_RIGHT; break;
362 case BS_CENTER: dtStyle |= DT_CENTER; break;
363 default:
364 if (button_centers_text(style)) dtStyle |= DT_CENTER;
367 if (ex_style & WS_EX_RIGHT) dtStyle = DT_RIGHT | (dtStyle & ~(DT_LEFT | DT_CENTER));
369 /* DrawText ignores vertical alignment for multiline text,
370 * but we use these flags to align label manually.
372 if (get_button_type(style) != BS_GROUPBOX)
374 switch (style & BS_VCENTER)
376 case BS_TOP: /* DT_TOP is 0 */ break;
377 case BS_BOTTOM: dtStyle |= DT_BOTTOM; break;
378 case BS_VCENTER: /* fall through */
379 default: dtStyle |= DT_VCENTER; break;
383 return dtStyle;
386 static int get_draw_state(const BUTTON_INFO *infoPtr)
388 static const int pb_states[DRAW_STATE_COUNT] = { PBS_NORMAL, PBS_DISABLED, PBS_HOT, PBS_PRESSED, PBS_DEFAULTED };
389 static const int cb_states[3][DRAW_STATE_COUNT] =
391 { CBS_UNCHECKEDNORMAL, CBS_UNCHECKEDDISABLED, CBS_UNCHECKEDHOT, CBS_UNCHECKEDPRESSED, CBS_UNCHECKEDNORMAL },
392 { CBS_CHECKEDNORMAL, CBS_CHECKEDDISABLED, CBS_CHECKEDHOT, CBS_CHECKEDPRESSED, CBS_CHECKEDNORMAL },
393 { CBS_MIXEDNORMAL, CBS_MIXEDDISABLED, CBS_MIXEDHOT, CBS_MIXEDPRESSED, CBS_MIXEDNORMAL }
395 static const int pushlike_cb_states[3][DRAW_STATE_COUNT] =
397 { PBS_NORMAL, PBS_DISABLED, PBS_HOT, PBS_PRESSED, PBS_NORMAL },
398 { PBS_PRESSED, PBS_PRESSED, PBS_PRESSED, PBS_PRESSED, PBS_PRESSED },
399 { PBS_NORMAL, PBS_DISABLED, PBS_HOT, PBS_PRESSED, PBS_NORMAL }
401 static const int rb_states[2][DRAW_STATE_COUNT] =
403 { RBS_UNCHECKEDNORMAL, RBS_UNCHECKEDDISABLED, RBS_UNCHECKEDHOT, RBS_UNCHECKEDPRESSED, RBS_UNCHECKEDNORMAL },
404 { RBS_CHECKEDNORMAL, RBS_CHECKEDDISABLED, RBS_CHECKEDHOT, RBS_CHECKEDPRESSED, RBS_CHECKEDNORMAL }
406 static const int pushlike_rb_states[2][DRAW_STATE_COUNT] =
408 { PBS_NORMAL, PBS_DISABLED, PBS_HOT, PBS_PRESSED, PBS_NORMAL },
409 { PBS_PRESSED, PBS_PRESSED, PBS_PRESSED, PBS_PRESSED, PBS_PRESSED }
411 static const int gb_states[DRAW_STATE_COUNT] = { GBS_NORMAL, GBS_DISABLED, GBS_NORMAL, GBS_NORMAL, GBS_NORMAL };
412 LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
413 UINT type = get_button_type(style);
414 int check_state = infoPtr->state & 3;
415 enum draw_state state;
417 if (!IsWindowEnabled(infoPtr->hwnd))
418 state = STATE_DISABLED;
419 else if (infoPtr->state & BST_PUSHED)
420 state = STATE_PRESSED;
421 else if (infoPtr->state & BST_HOT)
422 state = STATE_HOT;
423 else if (infoPtr->state & BST_FOCUS || type == BS_DEFPUSHBUTTON || type == BS_DEFSPLITBUTTON
424 || (type == BS_DEFCOMMANDLINK && !(style & BS_PUSHLIKE)))
425 state = STATE_DEFAULTED;
426 else
427 state = STATE_NORMAL;
429 switch (type)
431 case BS_PUSHBUTTON:
432 case BS_DEFPUSHBUTTON:
433 case BS_USERBUTTON:
434 case BS_SPLITBUTTON:
435 case BS_DEFSPLITBUTTON:
436 case BS_COMMANDLINK:
437 case BS_DEFCOMMANDLINK:
438 return pb_states[state];
439 case BS_CHECKBOX:
440 case BS_AUTOCHECKBOX:
441 case BS_3STATE:
442 case BS_AUTO3STATE:
443 return style & BS_PUSHLIKE ? pushlike_cb_states[check_state][state]
444 : cb_states[check_state][state];
445 case BS_RADIOBUTTON:
446 case BS_AUTORADIOBUTTON:
447 return style & BS_PUSHLIKE ? pushlike_rb_states[check_state][state]
448 : rb_states[check_state][state];
449 case BS_GROUPBOX:
450 return style & BS_PUSHLIKE ? pb_states[state] : gb_states[state];
451 default:
452 WARN("Unsupported button type 0x%08x\n", type);
453 return PBS_NORMAL;
457 static LRESULT CALLBACK BUTTON_WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
459 BUTTON_INFO *infoPtr = (BUTTON_INFO *)GetWindowLongPtrW(hWnd, 0);
460 RECT rect;
461 POINT pt;
462 LONG style = GetWindowLongW( hWnd, GWL_STYLE );
463 UINT btn_type = get_button_type( style );
464 LONG state, new_state;
465 HANDLE oldHbitmap;
466 HTHEME theme;
468 if (!IsWindow( hWnd )) return 0;
470 if (!infoPtr && (uMsg != WM_NCCREATE))
471 return DefWindowProcW(hWnd, uMsg, wParam, lParam);
473 pt.x = (short)LOWORD(lParam);
474 pt.y = (short)HIWORD(lParam);
476 switch (uMsg)
478 case WM_GETDLGCODE:
479 switch(btn_type)
481 case BS_COMMANDLINK:
482 case BS_USERBUTTON:
483 case BS_PUSHBUTTON: return DLGC_BUTTON | DLGC_UNDEFPUSHBUTTON;
484 case BS_DEFCOMMANDLINK:
485 case BS_DEFPUSHBUTTON: return DLGC_BUTTON | DLGC_DEFPUSHBUTTON;
486 case BS_RADIOBUTTON:
487 case BS_AUTORADIOBUTTON: return DLGC_BUTTON | DLGC_RADIOBUTTON;
488 case BS_GROUPBOX: return DLGC_STATIC;
489 case BS_SPLITBUTTON: return DLGC_BUTTON | DLGC_UNDEFPUSHBUTTON | DLGC_WANTARROWS;
490 case BS_DEFSPLITBUTTON: return DLGC_BUTTON | DLGC_DEFPUSHBUTTON | DLGC_WANTARROWS;
491 default: return DLGC_BUTTON;
494 case WM_ENABLE:
495 theme = GetWindowTheme( hWnd );
496 if (theme)
497 RedrawWindow( hWnd, NULL, NULL, RDW_FRAME | RDW_INVALIDATE | RDW_UPDATENOW );
498 else
499 paint_button( infoPtr, btn_type, ODA_DRAWENTIRE );
500 break;
502 case WM_NCCREATE:
504 CREATESTRUCTW *cs = (CREATESTRUCTW *)lParam;
506 infoPtr = Alloc( sizeof(*infoPtr) );
507 SetWindowLongPtrW( hWnd, 0, (LONG_PTR)infoPtr );
508 infoPtr->hwnd = hWnd;
509 infoPtr->parent = cs->hwndParent;
510 infoPtr->style = cs->style;
511 infoPtr->split_style = BCSS_STRETCH;
512 infoPtr->glyph = (HIMAGELIST)0x36; /* Marlett down arrow char code */
513 infoPtr->glyph_size.cx = get_default_glyph_size(infoPtr);
514 return DefWindowProcW(hWnd, uMsg, wParam, lParam);
517 case WM_NCDESTROY:
518 SetWindowLongPtrW( hWnd, 0, 0 );
519 if (infoPtr->image_type == IMAGE_BITMAP)
520 DeleteObject(infoPtr->u.bitmap);
521 else if (infoPtr->image_type == IMAGE_ICON)
522 DestroyIcon(infoPtr->u.icon);
523 Free(infoPtr->note);
524 Free(infoPtr);
525 break;
527 case WM_CREATE:
529 HWND parent;
531 if (btn_type >= MAX_BTN_TYPE)
532 return -1; /* abort */
534 /* XP turns a BS_USERBUTTON into BS_PUSHBUTTON */
535 if (btn_type == BS_USERBUTTON )
537 style = (style & ~BS_TYPEMASK) | BS_PUSHBUTTON;
538 SetWindowLongW( hWnd, GWL_STYLE, style );
540 infoPtr->state = BST_UNCHECKED;
541 OpenThemeData( hWnd, WC_BUTTONW );
543 parent = GetParent( hWnd );
544 if (parent)
545 EnableThemeDialogTexture( parent, ETDT_ENABLE );
546 return 0;
549 case WM_DESTROY:
550 theme = GetWindowTheme( hWnd );
551 CloseThemeData( theme );
552 break;
554 case WM_THEMECHANGED:
555 theme = GetWindowTheme( hWnd );
556 CloseThemeData( theme );
557 OpenThemeData( hWnd, WC_BUTTONW );
558 InvalidateRect( hWnd, NULL, TRUE );
559 break;
561 case WM_ERASEBKGND:
562 if (btn_type == BS_OWNERDRAW)
564 HDC hdc = (HDC)wParam;
565 RECT rc;
566 HBRUSH hBrush;
567 HWND parent = GetParent(hWnd);
568 if (!parent) parent = hWnd;
569 hBrush = (HBRUSH)SendMessageW(parent, WM_CTLCOLORBTN, (WPARAM)hdc, (LPARAM)hWnd);
570 if (!hBrush) /* did the app forget to call defwindowproc ? */
571 hBrush = (HBRUSH)DefWindowProcW(parent, WM_CTLCOLORBTN,
572 (WPARAM)hdc, (LPARAM)hWnd);
573 GetClientRect(hWnd, &rc);
574 FillRect(hdc, &rc, hBrush);
576 return 1;
578 case WM_PRINTCLIENT:
579 case WM_PAINT:
581 PAINTSTRUCT ps;
582 HDC hdc;
584 theme = GetWindowTheme( hWnd );
585 hdc = wParam ? (HDC)wParam : BeginPaint( hWnd, &ps );
587 if (is_themed_paint_supported(theme, btn_type))
589 int drawState = get_draw_state(infoPtr);
590 UINT dtflags = BUTTON_BStoDT(style, GetWindowLongW(hWnd, GWL_EXSTYLE));
592 btnThemedPaintFunc[btn_type](theme, infoPtr, hdc, drawState, dtflags, infoPtr->state & BST_FOCUS);
594 else if (btnPaintFunc[btn_type])
596 int nOldMode = SetBkMode( hdc, OPAQUE );
597 btnPaintFunc[btn_type]( infoPtr, hdc, ODA_DRAWENTIRE );
598 SetBkMode(hdc, nOldMode); /* reset painting mode */
601 if ( !wParam ) EndPaint( hWnd, &ps );
602 break;
605 case WM_KEYDOWN:
606 if (wParam == VK_SPACE)
608 SendMessageW( hWnd, BM_SETSTATE, TRUE, 0 );
609 infoPtr->state |= BUTTON_BTNPRESSED;
610 SetCapture( hWnd );
612 else if (wParam == VK_UP || wParam == VK_DOWN)
614 /* Up and down arrows work on every button, and even with BCSS_NOSPLIT */
615 notify_split_button_dropdown(infoPtr, NULL, hWnd);
617 break;
619 case WM_LBUTTONDBLCLK:
620 if(style & BS_NOTIFY ||
621 btn_type == BS_RADIOBUTTON ||
622 btn_type == BS_USERBUTTON ||
623 btn_type == BS_OWNERDRAW)
625 BUTTON_NOTIFY_PARENT(hWnd, BN_DOUBLECLICKED);
626 break;
628 /* fall through */
629 case WM_LBUTTONDOWN:
630 SetFocus( hWnd );
632 if ((btn_type == BS_SPLITBUTTON || btn_type == BS_DEFSPLITBUTTON) &&
633 !(infoPtr->split_style & BCSS_NOSPLIT) &&
634 notify_split_button_dropdown(infoPtr, &pt, hWnd))
635 break;
637 SetCapture( hWnd );
638 infoPtr->state |= BUTTON_BTNPRESSED;
639 SendMessageW( hWnd, BM_SETSTATE, TRUE, 0 );
640 break;
642 case WM_KEYUP:
643 if (wParam != VK_SPACE)
644 break;
645 /* fall through */
646 case WM_LBUTTONUP:
647 state = infoPtr->state;
648 if (state & BST_DROPDOWNPUSHED)
649 SendMessageW(hWnd, BCM_SETDROPDOWNSTATE, FALSE, 0);
650 if (!(state & BUTTON_BTNPRESSED)) break;
651 infoPtr->state &= BUTTON_NSTATES | BST_HOT;
652 if (!(state & BST_PUSHED))
654 ReleaseCapture();
655 break;
657 SendMessageW( hWnd, BM_SETSTATE, FALSE, 0 );
658 GetClientRect( hWnd, &rect );
659 if (uMsg == WM_KEYUP || PtInRect( &rect, pt ))
661 switch(btn_type)
663 case BS_AUTOCHECKBOX:
664 SendMessageW( hWnd, BM_SETCHECK, !(infoPtr->state & BST_CHECKED), 0 );
665 break;
666 case BS_AUTORADIOBUTTON:
667 SendMessageW( hWnd, BM_SETCHECK, TRUE, 0 );
668 break;
669 case BS_AUTO3STATE:
670 SendMessageW( hWnd, BM_SETCHECK, (infoPtr->state & BST_INDETERMINATE) ? 0 :
671 ((infoPtr->state & 3) + 1), 0 );
672 break;
674 ReleaseCapture();
675 BUTTON_NOTIFY_PARENT(hWnd, BN_CLICKED);
677 else
679 ReleaseCapture();
681 break;
683 case WM_CAPTURECHANGED:
684 TRACE("WM_CAPTURECHANGED %p\n", hWnd);
685 if (hWnd == (HWND)lParam) break;
686 if (infoPtr->state & BUTTON_BTNPRESSED)
688 infoPtr->state &= BUTTON_NSTATES;
689 if (infoPtr->state & BST_PUSHED)
690 SendMessageW( hWnd, BM_SETSTATE, FALSE, 0 );
692 break;
694 case WM_MOUSEMOVE:
696 TRACKMOUSEEVENT mouse_event;
698 mouse_event.cbSize = sizeof(TRACKMOUSEEVENT);
699 mouse_event.dwFlags = TME_QUERY;
700 if (!TrackMouseEvent(&mouse_event) || !(mouse_event.dwFlags & (TME_HOVER | TME_LEAVE)))
702 mouse_event.dwFlags = TME_HOVER | TME_LEAVE;
703 mouse_event.hwndTrack = hWnd;
704 mouse_event.dwHoverTime = 1;
705 TrackMouseEvent(&mouse_event);
708 if ((wParam & MK_LBUTTON) && GetCapture() == hWnd)
710 GetClientRect( hWnd, &rect );
711 SendMessageW( hWnd, BM_SETSTATE, PtInRect(&rect, pt), 0 );
713 break;
716 case WM_MOUSEHOVER:
718 infoPtr->state |= BST_HOT;
719 InvalidateRect( hWnd, NULL, FALSE );
720 break;
723 case WM_MOUSELEAVE:
725 infoPtr->state &= ~BST_HOT;
726 InvalidateRect( hWnd, NULL, FALSE );
727 break;
730 case WM_SETTEXT:
732 /* Clear an old text here as Windows does */
733 if (IsWindowVisible(hWnd))
735 HDC hdc = GetDC(hWnd);
736 HBRUSH hbrush;
737 RECT client, rc;
738 HWND parent = GetParent(hWnd);
739 UINT message = (btn_type == BS_PUSHBUTTON ||
740 btn_type == BS_DEFPUSHBUTTON ||
741 btn_type == BS_USERBUTTON ||
742 btn_type == BS_OWNERDRAW) ?
743 WM_CTLCOLORBTN : WM_CTLCOLORSTATIC;
745 if (!parent) parent = hWnd;
746 hbrush = (HBRUSH)SendMessageW(parent, message,
747 (WPARAM)hdc, (LPARAM)hWnd);
748 if (!hbrush) /* did the app forget to call DefWindowProc ? */
749 hbrush = (HBRUSH)DefWindowProcW(parent, message,
750 (WPARAM)hdc, (LPARAM)hWnd);
752 GetClientRect(hWnd, &client);
753 rc = client;
754 /* FIXME: check other BS_* handlers */
755 if (btn_type == BS_GROUPBOX)
756 InflateRect(&rc, -7, 1); /* GB_Paint does this */
757 BUTTON_CalcLayoutRects(infoPtr, hdc, &rc, NULL, NULL);
758 /* Clip by client rect bounds */
759 if (rc.right > client.right) rc.right = client.right;
760 if (rc.bottom > client.bottom) rc.bottom = client.bottom;
761 FillRect(hdc, &rc, hbrush);
762 ReleaseDC(hWnd, hdc);
765 DefWindowProcW( hWnd, WM_SETTEXT, wParam, lParam );
766 if (btn_type == BS_GROUPBOX) /* Yes, only for BS_GROUPBOX */
767 InvalidateRect( hWnd, NULL, TRUE );
768 else if (GetWindowTheme( hWnd ))
769 RedrawWindow( hWnd, NULL, NULL, RDW_INVALIDATE | RDW_UPDATENOW );
770 else
771 paint_button( infoPtr, btn_type, ODA_DRAWENTIRE );
772 return 1; /* success. FIXME: check text length */
775 case BCM_SETNOTE:
777 WCHAR *note = (WCHAR *)lParam;
778 if (btn_type != BS_COMMANDLINK && btn_type != BS_DEFCOMMANDLINK)
780 SetLastError(ERROR_NOT_SUPPORTED);
781 return FALSE;
784 Free(infoPtr->note);
785 if (note)
787 infoPtr->note_length = lstrlenW(note);
788 infoPtr->note = heap_strndupW(note, infoPtr->note_length);
791 if (!note || !infoPtr->note)
793 infoPtr->note_length = 0;
794 infoPtr->note = Alloc(sizeof(WCHAR));
797 SetLastError(NO_ERROR);
798 return TRUE;
801 case BCM_GETNOTE:
803 DWORD *size = (DWORD *)wParam;
804 WCHAR *buffer = (WCHAR *)lParam;
805 INT length = 0;
807 if (btn_type != BS_COMMANDLINK && btn_type != BS_DEFCOMMANDLINK)
809 SetLastError(ERROR_NOT_SUPPORTED);
810 return FALSE;
813 if (!buffer || !size || !infoPtr->note)
815 SetLastError(ERROR_INVALID_PARAMETER);
816 return FALSE;
819 if (*size > 0)
821 length = min(*size - 1, infoPtr->note_length);
822 memcpy(buffer, infoPtr->note, length * sizeof(WCHAR));
823 buffer[length] = '\0';
826 if (*size < infoPtr->note_length + 1)
828 *size = infoPtr->note_length + 1;
829 SetLastError(ERROR_INSUFFICIENT_BUFFER);
830 return FALSE;
832 else
834 SetLastError(NO_ERROR);
835 return TRUE;
839 case BCM_GETNOTELENGTH:
841 if (btn_type != BS_COMMANDLINK && btn_type != BS_DEFCOMMANDLINK)
843 SetLastError(ERROR_NOT_SUPPORTED);
844 return 0;
847 return infoPtr->note_length;
850 case WM_SETFONT:
851 infoPtr->font = (HFONT)wParam;
852 if (lParam) InvalidateRect(hWnd, NULL, TRUE);
853 break;
855 case WM_GETFONT:
856 return (LRESULT)infoPtr->font;
858 case WM_SETFOCUS:
859 TRACE("WM_SETFOCUS %p\n",hWnd);
860 infoPtr->state |= BST_FOCUS;
862 if (btn_type == BS_OWNERDRAW)
863 paint_button( infoPtr, btn_type, ODA_FOCUS );
864 else
865 InvalidateRect(hWnd, NULL, FALSE);
867 if (style & BS_NOTIFY)
868 BUTTON_NOTIFY_PARENT(hWnd, BN_SETFOCUS);
869 break;
871 case WM_KILLFOCUS:
872 TRACE("WM_KILLFOCUS %p\n",hWnd);
873 infoPtr->state &= ~BST_FOCUS;
875 if ((infoPtr->state & BUTTON_BTNPRESSED) && GetCapture() == hWnd)
876 ReleaseCapture();
877 if (style & BS_NOTIFY)
878 BUTTON_NOTIFY_PARENT(hWnd, BN_KILLFOCUS);
880 InvalidateRect( hWnd, NULL, FALSE );
881 break;
883 case WM_SYSCOLORCHANGE:
884 InvalidateRect( hWnd, NULL, FALSE );
885 break;
887 case BM_SETSTYLE:
889 DWORD new_btn_type;
891 new_btn_type= wParam & BS_TYPEMASK;
892 if (btn_type >= BS_SPLITBUTTON && new_btn_type <= BS_DEFPUSHBUTTON)
893 new_btn_type = (btn_type & ~BS_DEFPUSHBUTTON) | new_btn_type;
895 style = (style & ~BS_TYPEMASK) | new_btn_type;
896 SetWindowLongW( hWnd, GWL_STYLE, style );
898 /* Only redraw if lParam flag is set.*/
899 if (lParam)
900 InvalidateRect( hWnd, NULL, TRUE );
902 break;
904 case BM_CLICK:
905 SendMessageW( hWnd, WM_LBUTTONDOWN, 0, 0 );
906 SendMessageW( hWnd, WM_LBUTTONUP, 0, 0 );
907 break;
909 case BM_SETIMAGE:
910 infoPtr->image_type = (DWORD)wParam;
911 oldHbitmap = infoPtr->image;
912 infoPtr->u.image = CopyImage((HANDLE)lParam, infoPtr->image_type, 0, 0, 0);
913 infoPtr->image = (HANDLE)lParam;
914 InvalidateRect( hWnd, NULL, FALSE );
915 return (LRESULT)oldHbitmap;
917 case BM_GETIMAGE:
918 return (LRESULT)infoPtr->image;
920 case BCM_SETIMAGELIST:
922 BUTTON_IMAGELIST *imagelist = (BUTTON_IMAGELIST *)lParam;
924 if (!imagelist) return FALSE;
926 infoPtr->imagelist = *imagelist;
927 return TRUE;
930 case BCM_GETIMAGELIST:
932 BUTTON_IMAGELIST *imagelist = (BUTTON_IMAGELIST *)lParam;
934 if (!imagelist) return FALSE;
936 *imagelist = infoPtr->imagelist;
937 return TRUE;
940 case BCM_SETSPLITINFO:
942 BUTTON_SPLITINFO *info = (BUTTON_SPLITINFO*)lParam;
944 if (!info) return TRUE;
946 if (info->mask & (BCSIF_GLYPH | BCSIF_IMAGE))
948 infoPtr->split_style &= ~BCSS_IMAGE;
949 if (!(info->mask & BCSIF_GLYPH))
950 infoPtr->split_style |= BCSS_IMAGE;
951 infoPtr->glyph = info->himlGlyph;
952 infoPtr->glyph_size.cx = infoPtr->glyph_size.cy = 0;
955 if (info->mask & BCSIF_STYLE)
956 infoPtr->split_style = info->uSplitStyle;
957 if (info->mask & BCSIF_SIZE)
958 infoPtr->glyph_size = info->size;
960 /* Calculate fitting value for cx if invalid (cy is untouched) */
961 if (infoPtr->glyph_size.cx <= 0)
962 infoPtr->glyph_size.cx = get_default_glyph_size(infoPtr);
964 /* Windows doesn't invalidate or redraw it, so we don't, either */
965 return TRUE;
968 case BCM_GETSPLITINFO:
970 BUTTON_SPLITINFO *info = (BUTTON_SPLITINFO*)lParam;
972 if (!info) return FALSE;
974 if (info->mask & BCSIF_STYLE)
975 info->uSplitStyle = infoPtr->split_style;
976 if (info->mask & (BCSIF_GLYPH | BCSIF_IMAGE))
977 info->himlGlyph = infoPtr->glyph;
978 if (info->mask & BCSIF_SIZE)
979 info->size = infoPtr->glyph_size;
981 return TRUE;
984 case BM_GETCHECK:
985 return infoPtr->state & 3;
987 case BM_SETCHECK:
988 if (wParam > maxCheckState[btn_type]) wParam = maxCheckState[btn_type];
989 if ((btn_type == BS_RADIOBUTTON) || (btn_type == BS_AUTORADIOBUTTON))
991 style = wParam ? style | WS_TABSTOP : style & ~WS_TABSTOP;
992 SetWindowLongW( hWnd, GWL_STYLE, style );
994 if ((infoPtr->state & 3) != wParam)
996 infoPtr->state = (infoPtr->state & ~3) | wParam;
997 InvalidateRect( hWnd, NULL, FALSE );
999 if ((btn_type == BS_AUTORADIOBUTTON) && (wParam == BST_CHECKED) && (style & WS_CHILD))
1000 BUTTON_CheckAutoRadioButton( hWnd );
1001 break;
1003 case BM_GETSTATE:
1004 return infoPtr->state;
1006 case BM_SETSTATE:
1007 state = infoPtr->state;
1008 new_state = wParam ? BST_PUSHED : 0;
1010 if ((state ^ new_state) & BST_PUSHED)
1012 if (wParam)
1013 state |= BST_PUSHED;
1014 else
1015 state &= ~BST_PUSHED;
1017 if (btn_type == BS_USERBUTTON)
1018 BUTTON_NOTIFY_PARENT( hWnd, (state & BST_PUSHED) ? BN_HILITE : BN_UNHILITE );
1019 infoPtr->state = state;
1021 InvalidateRect( hWnd, NULL, FALSE );
1023 break;
1025 case BCM_SETDROPDOWNSTATE:
1026 new_state = wParam ? BST_DROPDOWNPUSHED : 0;
1028 if ((infoPtr->state ^ new_state) & BST_DROPDOWNPUSHED)
1030 infoPtr->state &= ~BST_DROPDOWNPUSHED;
1031 infoPtr->state |= new_state;
1032 InvalidateRect(hWnd, NULL, FALSE);
1034 break;
1036 case BCM_SETTEXTMARGIN:
1038 RECT *text_margin = (RECT *)lParam;
1040 if (!text_margin) return FALSE;
1042 infoPtr->text_margin = *text_margin;
1043 return TRUE;
1046 case BCM_GETTEXTMARGIN:
1048 RECT *text_margin = (RECT *)lParam;
1050 if (!text_margin) return FALSE;
1052 *text_margin = infoPtr->text_margin;
1053 return TRUE;
1056 case BCM_GETIDEALSIZE:
1058 SIZE *size = (SIZE *)lParam;
1060 if (!size) return FALSE;
1062 return btnGetIdealSizeFunc[btn_type](infoPtr, size);
1065 case WM_NCHITTEST:
1066 if(btn_type == BS_GROUPBOX) return HTTRANSPARENT;
1067 /* fall through */
1068 default:
1069 return DefWindowProcW(hWnd, uMsg, wParam, lParam);
1071 return 0;
1074 /* If maxWidth is zero, rectangle width is unlimited */
1075 static RECT BUTTON_GetTextRect(const BUTTON_INFO *infoPtr, HDC hdc, const WCHAR *text, LONG maxWidth)
1077 LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1078 LONG exStyle = GetWindowLongW(infoPtr->hwnd, GWL_EXSTYLE);
1079 UINT dtStyle = BUTTON_BStoDT(style, exStyle);
1080 HFONT hPrevFont;
1081 RECT rect = {0};
1083 rect.right = maxWidth;
1084 hPrevFont = SelectObject(hdc, infoPtr->font);
1085 /* Calculate height without DT_VCENTER and DT_BOTTOM to get the correct height */
1086 DrawTextW(hdc, text, -1, &rect, (dtStyle & ~(DT_VCENTER | DT_BOTTOM)) | DT_CALCRECT);
1087 if (hPrevFont) SelectObject(hdc, hPrevFont);
1089 return rect;
1092 static BOOL show_image_only(const BUTTON_INFO *infoPtr)
1094 LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1095 return (style & (BS_ICON | BS_BITMAP)) && (infoPtr->u.image || infoPtr->imagelist.himl);
1098 static BOOL show_image_and_text(const BUTTON_INFO *infoPtr)
1100 LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1101 UINT type = get_button_type(style);
1102 return !(style & (BS_ICON | BS_BITMAP))
1103 && ((infoPtr->u.image
1104 && (type == BS_PUSHBUTTON || type == BS_DEFPUSHBUTTON || type == BS_USERBUTTON || type == BS_SPLITBUTTON
1105 || type == BS_DEFSPLITBUTTON || type == BS_COMMANDLINK || type == BS_DEFCOMMANDLINK))
1106 || (infoPtr->imagelist.himl && type != BS_GROUPBOX));
1109 static BOOL show_image(const BUTTON_INFO *infoPtr)
1111 return show_image_only(infoPtr) || show_image_and_text(infoPtr);
1114 /* Get a bounding rectangle that is large enough to contain a image and a text side by side.
1115 * Note: (left,top) of the result rectangle may not be (0,0), offset it by yourself if needed */
1116 static RECT BUTTON_GetBoundingLabelRect(LONG style, const RECT *textRect, const RECT *imageRect)
1118 RECT labelRect;
1119 RECT rect = *imageRect;
1120 INT textWidth = textRect->right - textRect->left;
1121 INT textHeight = textRect->bottom - textRect->top;
1122 INT imageWidth = imageRect->right - imageRect->left;
1123 INT imageHeight = imageRect->bottom - imageRect->top;
1125 if ((style & BS_CENTER) == BS_RIGHT)
1126 OffsetRect(&rect, textWidth, 0);
1127 else if ((style & BS_CENTER) == BS_LEFT)
1128 OffsetRect(&rect, -imageWidth, 0);
1129 else if ((style & BS_VCENTER) == BS_BOTTOM)
1130 OffsetRect(&rect, 0, textHeight);
1131 else if ((style & BS_VCENTER) == BS_TOP)
1132 OffsetRect(&rect, 0, -imageHeight);
1133 else
1134 OffsetRect(&rect, -imageWidth, 0);
1136 UnionRect(&labelRect, textRect, &rect);
1137 return labelRect;
1140 /* Position a rectangle inside a bounding rectangle according to button alignment flags */
1141 static void BUTTON_PositionRect(LONG style, const RECT *outerRect, RECT *innerRect, const RECT *margin)
1143 INT width = innerRect->right - innerRect->left;
1144 INT height = innerRect->bottom - innerRect->top;
1146 if ((style & BS_PUSHLIKE) && !(style & BS_CENTER)) style |= BS_CENTER;
1148 if (!(style & BS_CENTER))
1150 if (button_centers_text(style))
1151 style |= BS_CENTER;
1152 else
1153 style |= BS_LEFT;
1156 if (!(style & BS_VCENTER))
1158 /* Group box's text is top aligned by default */
1159 if (get_button_type(style) == BS_GROUPBOX)
1160 style |= BS_TOP;
1163 switch (style & BS_CENTER)
1165 case BS_CENTER:
1166 /* The left and right margins are added to the inner rectangle to get a new rectangle. Then
1167 * the new rectangle is adjusted to be in the horizontal center */
1168 innerRect->left = outerRect->left + (outerRect->right - outerRect->left - width
1169 + margin->left - margin->right) / 2;
1170 innerRect->right = innerRect->left + width;
1171 break;
1172 case BS_RIGHT:
1173 innerRect->right = outerRect->right - margin->right;
1174 innerRect->left = innerRect->right - width;
1175 break;
1176 case BS_LEFT:
1177 default:
1178 innerRect->left = outerRect->left + margin->left;
1179 innerRect->right = innerRect->left + width;
1180 break;
1183 switch (style & BS_VCENTER)
1185 case BS_TOP:
1186 innerRect->top = outerRect->top + margin->top;
1187 innerRect->bottom = innerRect->top + height;
1188 break;
1189 case BS_BOTTOM:
1190 innerRect->bottom = outerRect->bottom - margin->bottom;
1191 innerRect->top = innerRect->bottom - height;
1192 break;
1193 case BS_VCENTER:
1194 default:
1195 /* The top and bottom margins are added to the inner rectangle to get a new rectangle. Then
1196 * the new rectangle is adjusted to be in the vertical center */
1197 innerRect->top = outerRect->top + (outerRect->bottom - outerRect->top - height
1198 + margin->top - margin->bottom) / 2;
1199 innerRect->bottom = innerRect->top + height;
1200 break;
1204 /* Convert imagelist align style to button align style */
1205 static UINT BUTTON_ILStoBS(UINT align)
1207 switch (align)
1209 case BUTTON_IMAGELIST_ALIGN_TOP:
1210 return BS_CENTER | BS_TOP;
1211 case BUTTON_IMAGELIST_ALIGN_BOTTOM:
1212 return BS_CENTER | BS_BOTTOM;
1213 case BUTTON_IMAGELIST_ALIGN_CENTER:
1214 return BS_CENTER | BS_VCENTER;
1215 case BUTTON_IMAGELIST_ALIGN_RIGHT:
1216 return BS_RIGHT | BS_VCENTER;
1217 case BUTTON_IMAGELIST_ALIGN_LEFT:
1218 default:
1219 return BS_LEFT | BS_VCENTER;
1223 static SIZE BUTTON_GetImageSize(const BUTTON_INFO *infoPtr)
1225 ICONINFO iconInfo;
1226 BITMAP bm = {0};
1227 SIZE size = {0};
1229 /* ImageList has priority over image */
1230 if (infoPtr->imagelist.himl)
1232 int scx, scy;
1233 ImageList_GetIconSize(infoPtr->imagelist.himl, &scx, &scy);
1234 size.cx = scx;
1235 size.cy = scy;
1237 else if (infoPtr->u.image)
1239 if (infoPtr->image_type == IMAGE_ICON)
1241 GetIconInfo(infoPtr->u.icon, &iconInfo);
1242 GetObjectW(iconInfo.hbmColor, sizeof(bm), &bm);
1243 DeleteObject(iconInfo.hbmColor);
1244 DeleteObject(iconInfo.hbmMask);
1246 else if (infoPtr->image_type == IMAGE_BITMAP)
1247 GetObjectW(infoPtr->u.bitmap, sizeof(bm), &bm);
1249 size.cx = bm.bmWidth;
1250 size.cy = bm.bmHeight;
1253 return size;
1256 static const RECT *BUTTON_GetTextMargin(const BUTTON_INFO *infoPtr)
1258 static const RECT oneMargin = {1, 1, 1, 1};
1260 /* Use text margin only when showing both image and text, and image is not imagelist */
1261 if (show_image_and_text(infoPtr) && !infoPtr->imagelist.himl)
1262 return &infoPtr->text_margin;
1263 else
1264 return &oneMargin;
1267 static void BUTTON_GetClientRectSize(BUTTON_INFO *infoPtr, SIZE *size)
1269 RECT rect;
1270 GetClientRect(infoPtr->hwnd, &rect);
1271 size->cx = rect.right - rect.left;
1272 size->cy = rect.bottom - rect.top;
1275 static void BUTTON_GetTextIdealSize(BUTTON_INFO *infoPtr, LONG maxWidth, SIZE *size)
1277 WCHAR *text = get_button_text(infoPtr);
1278 HDC hdc;
1279 RECT rect;
1280 const RECT *margin = BUTTON_GetTextMargin(infoPtr);
1282 if (maxWidth != 0)
1284 maxWidth -= margin->right + margin->right;
1285 if (maxWidth <= 0) maxWidth = 1;
1288 hdc = GetDC(infoPtr->hwnd);
1289 rect = BUTTON_GetTextRect(infoPtr, hdc, text, maxWidth);
1290 ReleaseDC(infoPtr->hwnd, hdc);
1291 Free(text);
1293 size->cx = rect.right - rect.left + margin->left + margin->right;
1294 size->cy = rect.bottom - rect.top + margin->top + margin->bottom;
1297 static void BUTTON_GetLabelIdealSize(BUTTON_INFO *infoPtr, LONG maxWidth, SIZE *size)
1299 LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1300 SIZE imageSize;
1301 SIZE textSize;
1302 BOOL horizontal;
1304 imageSize = BUTTON_GetImageSize(infoPtr);
1305 if (infoPtr->imagelist.himl)
1307 imageSize.cx += infoPtr->imagelist.margin.left + infoPtr->imagelist.margin.right;
1308 imageSize.cy += infoPtr->imagelist.margin.top + infoPtr->imagelist.margin.bottom;
1309 if (infoPtr->imagelist.uAlign == BUTTON_IMAGELIST_ALIGN_TOP
1310 || infoPtr->imagelist.uAlign == BUTTON_IMAGELIST_ALIGN_BOTTOM)
1311 horizontal = FALSE;
1312 else
1313 horizontal = TRUE;
1315 else
1317 /* horizontal alignment flags has priority over vertical ones if both are specified */
1318 if (!(style & (BS_CENTER | BS_VCENTER)) || ((style & BS_CENTER) && (style & BS_CENTER) != BS_CENTER)
1319 || !(style & BS_VCENTER) || (style & BS_VCENTER) == BS_VCENTER)
1320 horizontal = TRUE;
1321 else
1322 horizontal = FALSE;
1325 if (horizontal)
1327 if (maxWidth != 0)
1329 maxWidth -= imageSize.cx;
1330 if (maxWidth <= 0) maxWidth = 1;
1332 BUTTON_GetTextIdealSize(infoPtr, maxWidth, &textSize);
1333 size->cx = textSize.cx + imageSize.cx;
1334 size->cy = max(textSize.cy, imageSize.cy);
1336 else
1338 BUTTON_GetTextIdealSize(infoPtr, maxWidth, &textSize);
1339 size->cx = max(textSize.cx, imageSize.cx);
1340 size->cy = textSize.cy + imageSize.cy;
1344 static BOOL GB_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size)
1346 BUTTON_GetClientRectSize(infoPtr, size);
1347 return TRUE;
1350 static BOOL CB_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size)
1352 LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1353 HDC hdc;
1354 HFONT hfont;
1355 SIZE labelSize;
1356 INT textOffset;
1357 double scaleX;
1358 double scaleY;
1359 LONG checkboxWidth, checkboxHeight;
1360 LONG maxWidth = 0;
1362 if (SendMessageW(infoPtr->hwnd, WM_GETTEXTLENGTH, 0, 0) == 0)
1364 BUTTON_GetClientRectSize(infoPtr, size);
1365 return TRUE;
1368 hdc = GetDC(infoPtr->hwnd);
1369 scaleX = GetDeviceCaps(hdc, LOGPIXELSX) / 96.0;
1370 scaleY = GetDeviceCaps(hdc, LOGPIXELSY) / 96.0;
1371 if ((hfont = infoPtr->font)) SelectObject(hdc, hfont);
1372 GetCharWidthW(hdc, '0', '0', &textOffset);
1373 textOffset /= 2;
1374 ReleaseDC(infoPtr->hwnd, hdc);
1376 checkboxWidth = 12 * scaleX + 1;
1377 checkboxHeight = 12 * scaleY + 1;
1378 if (size->cx)
1380 maxWidth = size->cx - checkboxWidth - textOffset;
1381 if (maxWidth <= 0) maxWidth = 1;
1384 /* Checkbox doesn't support both image(but not image list) and text */
1385 if (!(style & (BS_ICON | BS_BITMAP)) && infoPtr->u.image)
1386 BUTTON_GetTextIdealSize(infoPtr, maxWidth, &labelSize);
1387 else
1388 BUTTON_GetLabelIdealSize(infoPtr, maxWidth, &labelSize);
1390 size->cx = labelSize.cx + checkboxWidth + textOffset;
1391 size->cy = max(labelSize.cy, checkboxHeight);
1393 return TRUE;
1396 static BOOL PB_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size)
1398 SIZE labelSize;
1400 if (SendMessageW(infoPtr->hwnd, WM_GETTEXTLENGTH, 0, 0) == 0)
1401 BUTTON_GetClientRectSize(infoPtr, size);
1402 else
1404 /* Ideal size include text size even if image only flags(BS_ICON, BS_BITMAP) are specified */
1405 BUTTON_GetLabelIdealSize(infoPtr, size->cx, &labelSize);
1407 size->cx = labelSize.cx;
1408 size->cy = labelSize.cy;
1410 return TRUE;
1413 static BOOL SB_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size)
1415 LONG extra_width = infoPtr->glyph_size.cx * 2 + GetSystemMetrics(SM_CXEDGE);
1416 SIZE label_size;
1418 if (SendMessageW(infoPtr->hwnd, WM_GETTEXTLENGTH, 0, 0) == 0)
1420 BUTTON_GetClientRectSize(infoPtr, size);
1421 size->cx = max(size->cx, extra_width);
1423 else
1425 BUTTON_GetLabelIdealSize(infoPtr, size->cx, &label_size);
1426 size->cx = label_size.cx + ((size->cx == 0) ? extra_width : 0);
1427 size->cy = label_size.cy;
1429 return TRUE;
1432 static BOOL CL_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size)
1434 HTHEME theme = GetWindowTheme(infoPtr->hwnd);
1435 HDC hdc = GetDC(infoPtr->hwnd);
1436 LONG w, text_w = 0, text_h = 0;
1437 UINT flags = DT_TOP | DT_LEFT;
1438 HFONT font, old_font = NULL;
1439 RECT text_bound = { 0 };
1440 SIZE img_size;
1441 RECT margin;
1442 WCHAR *text;
1444 /* Get the image size */
1445 if (infoPtr->u.image || infoPtr->imagelist.himl)
1446 img_size = BUTTON_GetImageSize(infoPtr);
1447 else
1449 if (theme)
1450 GetThemePartSize(theme, NULL, BP_COMMANDLINKGLYPH, CMDLS_NORMAL, NULL, TS_DRAW, &img_size);
1451 else
1452 img_size.cx = img_size.cy = command_link_defglyph_size;
1455 /* Get the content margins */
1456 if (theme)
1458 RECT r = { 0, 0, 0xffff, 0xffff };
1459 GetThemeBackgroundContentRect(theme, hdc, BP_COMMANDLINK, CMDLS_NORMAL, &r, &margin);
1460 margin.left -= r.left;
1461 margin.top -= r.top;
1462 margin.right = r.right - margin.right;
1463 margin.bottom = r.bottom - margin.bottom;
1465 else
1467 margin.left = margin.right = command_link_margin;
1468 margin.top = margin.bottom = command_link_margin;
1471 /* Account for the border margins and the margin between image and text */
1472 w = margin.left + margin.right + (img_size.cx ? (img_size.cx + command_link_margin) : 0);
1474 /* If a rectangle with a specific width was requested, bound the text to it */
1475 if (size->cx > w)
1477 text_bound.right = size->cx - w;
1478 flags |= DT_WORDBREAK;
1481 if (theme)
1483 if (infoPtr->font) old_font = SelectObject(hdc, infoPtr->font);
1485 /* Find the text's rect */
1486 if ((text = get_button_text(infoPtr)))
1488 RECT r;
1489 GetThemeTextExtent(theme, hdc, BP_COMMANDLINK, CMDLS_NORMAL,
1490 text, -1, flags, &text_bound, &r);
1491 Free(text);
1492 text_w = r.right - r.left;
1493 text_h = r.bottom - r.top;
1496 /* Find the note's rect */
1497 if (infoPtr->note)
1499 DTTOPTS opts;
1501 opts.dwSize = sizeof(opts);
1502 opts.dwFlags = DTT_FONTPROP | DTT_CALCRECT;
1503 opts.iFontPropId = TMT_BODYFONT;
1504 DrawThemeTextEx(theme, hdc, BP_COMMANDLINK, CMDLS_NORMAL,
1505 infoPtr->note, infoPtr->note_length,
1506 flags | DT_NOPREFIX | DT_CALCRECT, &text_bound, &opts);
1507 text_w = max(text_w, text_bound.right - text_bound.left);
1508 text_h += text_bound.bottom - text_bound.top;
1511 else
1513 NONCLIENTMETRICSW ncm;
1515 ncm.cbSize = sizeof(ncm);
1516 if (SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, sizeof(ncm), &ncm, 0))
1518 LONG note_weight = ncm.lfMessageFont.lfWeight;
1520 /* Find the text's rect */
1521 ncm.lfMessageFont.lfWeight = FW_BOLD;
1522 if ((font = CreateFontIndirectW(&ncm.lfMessageFont)))
1524 if ((text = get_button_text(infoPtr)))
1526 RECT r = text_bound;
1527 old_font = SelectObject(hdc, font);
1528 DrawTextW(hdc, text, -1, &r, flags | DT_CALCRECT);
1529 Free(text);
1531 text_w = r.right - r.left;
1532 text_h = r.bottom - r.top;
1534 DeleteObject(font);
1537 /* Find the note's rect */
1538 ncm.lfMessageFont.lfWeight = note_weight;
1539 if (infoPtr->note && (font = CreateFontIndirectW(&ncm.lfMessageFont)))
1541 HFONT tmp = SelectObject(hdc, font);
1542 if (!old_font) old_font = tmp;
1544 DrawTextW(hdc, infoPtr->note, infoPtr->note_length, &text_bound,
1545 flags | DT_NOPREFIX | DT_CALCRECT);
1546 DeleteObject(font);
1548 text_w = max(text_w, text_bound.right - text_bound.left);
1549 text_h += text_bound.bottom - text_bound.top + 2;
1553 w += text_w;
1555 size->cx = min(size->cx, w);
1556 size->cy = max(text_h, img_size.cy) + margin.top + margin.bottom;
1558 if (old_font) SelectObject(hdc, old_font);
1559 ReleaseDC(infoPtr->hwnd, hdc);
1560 return TRUE;
1563 /**********************************************************************
1564 * BUTTON_CalcLayoutRects
1566 * Calculates the rectangles of the button label(image and text) and its parts depending on a button's style.
1568 * Returns flags to be passed to DrawText.
1569 * Calculated rectangle doesn't take into account button state
1570 * (pushed, etc.). If there is nothing to draw (no text/image) output
1571 * rectangle is empty, and return value is (UINT)-1.
1573 * PARAMS:
1574 * infoPtr [I] Button pointer
1575 * hdc [I] Handle to device context to draw to
1576 * labelRc [I/O] Input the rect the label to be positioned in, and output the label rect
1577 * imageRc [O] Optional, output the image rect
1578 * textRc [O] Optional, output the text rect
1580 static UINT BUTTON_CalcLayoutRects(const BUTTON_INFO *infoPtr, HDC hdc, RECT *labelRc, RECT *imageRc, RECT *textRc)
1582 WCHAR *text = get_button_text(infoPtr);
1583 SIZE imageSize = BUTTON_GetImageSize(infoPtr);
1584 RECT labelRect, imageRect, imageRectWithMargin, textRect;
1585 LONG imageMarginWidth, imageMarginHeight;
1586 const RECT *textMargin = BUTTON_GetTextMargin(infoPtr);
1587 LONG style, ex_style, split_style;
1588 RECT emptyMargin = {0};
1589 LONG maxTextWidth;
1590 UINT dtStyle;
1592 /* Calculate label rectangle according to label type */
1593 if ((imageSize.cx == 0 && imageSize.cy == 0) && (text == NULL || text[0] == '\0'))
1595 SetRectEmpty(labelRc);
1596 SetRectEmpty(imageRc);
1597 SetRectEmpty(textRc);
1598 Free(text);
1599 return (UINT)-1;
1602 style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1603 ex_style = GetWindowLongW(infoPtr->hwnd, GWL_EXSTYLE);
1604 /* Add BS_RIGHT directly. When both WS_EX_RIGHT and BS_LEFT are present, it becomes BS_CENTER */
1605 if (ex_style & WS_EX_RIGHT)
1606 style |= BS_RIGHT;
1607 split_style = infoPtr->imagelist.himl ? BUTTON_ILStoBS(infoPtr->imagelist.uAlign) : style;
1608 dtStyle = BUTTON_BStoDT(style, ex_style);
1610 /* Group boxes are top aligned unless BS_PUSHLIKE is set and it's not themed */
1611 if (get_button_type(style) == BS_GROUPBOX
1612 && (!(style & BS_PUSHLIKE) || GetWindowTheme(infoPtr->hwnd)))
1613 style &= ~BS_VCENTER | BS_TOP;
1615 SetRect(&imageRect, 0, 0, imageSize.cx, imageSize.cy);
1616 imageRectWithMargin = imageRect;
1617 if (infoPtr->imagelist.himl)
1619 imageRectWithMargin.top -= infoPtr->imagelist.margin.top;
1620 imageRectWithMargin.bottom += infoPtr->imagelist.margin.bottom;
1621 imageRectWithMargin.left -= infoPtr->imagelist.margin.left;
1622 imageRectWithMargin.right += infoPtr->imagelist.margin.right;
1625 /* Show image only */
1626 if (show_image_only(infoPtr))
1628 BUTTON_PositionRect(style, labelRc, &imageRect,
1629 infoPtr->imagelist.himl ? &infoPtr->imagelist.margin : &emptyMargin);
1630 labelRect = imageRect;
1631 SetRectEmpty(&textRect);
1633 else
1635 /* Get text rect */
1636 maxTextWidth = labelRc->right - labelRc->left;
1637 textRect = BUTTON_GetTextRect(infoPtr, hdc, text, maxTextWidth);
1639 /* Show image and text */
1640 if (show_image_and_text(infoPtr))
1642 RECT boundingLabelRect, boundingImageRect, boundingTextRect;
1644 /* Get label rect */
1645 /* Image list may have different alignment than the button, use the whole rect for label in this case */
1646 if (infoPtr->imagelist.himl)
1647 labelRect = *labelRc;
1648 else
1650 /* Get a label bounding rectangle to position the label in the user specified label rectangle because
1651 * text and image need to align together. */
1652 boundingLabelRect = BUTTON_GetBoundingLabelRect(split_style, &textRect, &imageRectWithMargin);
1653 BUTTON_PositionRect(split_style, labelRc, &boundingLabelRect, &emptyMargin);
1654 labelRect = boundingLabelRect;
1657 /* When imagelist has center align, use the whole rect for imagelist and text */
1658 if(infoPtr->imagelist.himl && infoPtr->imagelist.uAlign == BUTTON_IMAGELIST_ALIGN_CENTER)
1660 boundingImageRect = labelRect;
1661 boundingTextRect = labelRect;
1662 BUTTON_PositionRect(split_style, &boundingImageRect, &imageRect,
1663 infoPtr->imagelist.himl ? &infoPtr->imagelist.margin : &emptyMargin);
1664 /* Text doesn't use imagelist align */
1665 BUTTON_PositionRect(style, &boundingTextRect, &textRect, textMargin);
1667 else
1669 /* Get image rect */
1670 /* Split the label rect to two halves as two bounding rectangles for image and text */
1671 boundingImageRect = labelRect;
1672 imageMarginWidth = imageRectWithMargin.right - imageRectWithMargin.left;
1673 imageMarginHeight = imageRectWithMargin.bottom - imageRectWithMargin.top;
1674 if ((split_style & BS_CENTER) == BS_RIGHT)
1675 boundingImageRect.left = boundingImageRect.right - imageMarginWidth;
1676 else if ((split_style & BS_CENTER) == BS_LEFT)
1677 boundingImageRect.right = boundingImageRect.left + imageMarginWidth;
1678 else if ((split_style & BS_VCENTER) == BS_BOTTOM)
1679 boundingImageRect.top = boundingImageRect.bottom - imageMarginHeight;
1680 else if ((split_style & BS_VCENTER) == BS_TOP)
1681 boundingImageRect.bottom = boundingImageRect.top + imageMarginHeight;
1682 else
1683 boundingImageRect.right = boundingImageRect.left + imageMarginWidth;
1684 BUTTON_PositionRect(split_style, &boundingImageRect, &imageRect,
1685 infoPtr->imagelist.himl ? &infoPtr->imagelist.margin : &emptyMargin);
1687 /* Get text rect */
1688 SubtractRect(&boundingTextRect, &labelRect, &boundingImageRect);
1689 /* Text doesn't use imagelist align */
1690 BUTTON_PositionRect(style, &boundingTextRect, &textRect, textMargin);
1693 /* Show text only */
1694 else
1696 BUTTON_PositionRect(style, labelRc, &textRect, textMargin);
1697 labelRect = textRect;
1698 SetRectEmpty(&imageRect);
1701 Free(text);
1703 CopyRect(labelRc, &labelRect);
1704 CopyRect(imageRc, &imageRect);
1705 CopyRect(textRc, &textRect);
1707 return dtStyle;
1711 /**********************************************************************
1712 * BUTTON_DrawImage
1714 * Draw the button's image into the specified rectangle.
1716 static void BUTTON_DrawImage(const BUTTON_INFO *infoPtr, HDC hdc, HBRUSH hbr, UINT flags, const RECT *rect)
1718 if (infoPtr->imagelist.himl)
1720 int i = (ImageList_GetImageCount(infoPtr->imagelist.himl) == 1) ? 0 : get_draw_state(infoPtr) - 1;
1722 ImageList_Draw(infoPtr->imagelist.himl, i, hdc, rect->left, rect->top, ILD_NORMAL);
1724 else
1726 switch (infoPtr->image_type)
1728 case IMAGE_ICON:
1729 flags |= DST_ICON;
1730 break;
1731 case IMAGE_BITMAP:
1732 flags |= DST_BITMAP;
1733 break;
1734 default:
1735 return;
1738 DrawStateW(hdc, hbr, NULL, (LPARAM)infoPtr->u.image, 0, rect->left, rect->top,
1739 rect->right - rect->left, rect->bottom - rect->top, flags);
1744 /**********************************************************************
1745 * BUTTON_DrawTextCallback
1747 * Callback function used by DrawStateW function.
1749 static BOOL CALLBACK BUTTON_DrawTextCallback(HDC hdc, LPARAM lp, WPARAM wp, int cx, int cy)
1751 RECT rc;
1753 SetRect(&rc, 0, 0, cx, cy);
1754 DrawTextW(hdc, (LPCWSTR)lp, -1, &rc, (UINT)wp);
1755 return TRUE;
1758 /**********************************************************************
1759 * BUTTON_DrawLabel
1761 * Common function for drawing button label.
1763 * FIXME:
1764 * 1. When BS_SINGLELINE is specified and text contains '\t', '\n' or '\r' in the middle, they are rendered as
1765 * squares now whereas they should be ignored.
1766 * 2. When BS_MULTILINE is specified and text contains space in the middle, the space mistakenly be rendered as newline.
1768 static void BUTTON_DrawLabel(const BUTTON_INFO *infoPtr, HDC hdc, UINT dtFlags, const RECT *imageRect,
1769 const RECT *textRect)
1771 HBRUSH hbr = 0;
1772 UINT flags = IsWindowEnabled(infoPtr->hwnd) ? DSS_NORMAL : DSS_DISABLED;
1773 LONG style = GetWindowLongW( infoPtr->hwnd, GWL_STYLE );
1774 WCHAR *text;
1776 /* FIXME: To draw disabled label in Win31 look-and-feel, we probably
1777 * must use DSS_MONO flag and COLOR_GRAYTEXT brush (or maybe DSS_UNION).
1778 * I don't have Win31 on hand to verify that, so I leave it as is.
1781 if ((style & BS_PUSHLIKE) && (infoPtr->state & BST_INDETERMINATE))
1783 hbr = GetSysColorBrush(COLOR_GRAYTEXT);
1784 flags |= DSS_MONO;
1787 if (show_image(infoPtr)) BUTTON_DrawImage(infoPtr, hdc, hbr, flags, imageRect);
1788 if (show_image_only(infoPtr)) return;
1790 /* DST_COMPLEX -- is 0 */
1791 if (!(text = get_button_text(infoPtr))) return;
1792 DrawStateW(hdc, hbr, BUTTON_DrawTextCallback, (LPARAM)text, dtFlags, textRect->left, textRect->top,
1793 textRect->right - textRect->left, textRect->bottom - textRect->top, flags);
1794 Free(text);
1797 static void BUTTON_DrawThemedLabel(const BUTTON_INFO *info, HDC hdc, UINT text_flags,
1798 const RECT *image_rect, const RECT *text_rect, HTHEME theme,
1799 int part, int state)
1801 HBRUSH brush = NULL;
1802 UINT image_flags;
1803 WCHAR *text;
1805 if (show_image(info))
1807 image_flags = IsWindowEnabled(info->hwnd) ? DSS_NORMAL : DSS_DISABLED;
1809 if ((GetWindowLongW(info->hwnd, GWL_STYLE) & BS_PUSHLIKE)
1810 && (info->state & BST_INDETERMINATE))
1812 brush = GetSysColorBrush(COLOR_GRAYTEXT);
1813 image_flags |= DSS_MONO;
1816 BUTTON_DrawImage(info, hdc, brush, image_flags, image_rect);
1819 if (show_image_only(info))
1820 return;
1822 if (!(text = get_button_text(info)))
1823 return;
1825 DrawThemeText(theme, hdc, part, state, text, lstrlenW(text), text_flags, 0, text_rect);
1826 Free(text);
1829 /**********************************************************************
1830 * Push Button Functions
1832 static void PB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action )
1834 RECT rc, labelRect, imageRect, textRect;
1835 UINT dtFlags = (UINT)-1, uState;
1836 HPEN hOldPen, hpen;
1837 HBRUSH hOldBrush;
1838 INT oldBkMode;
1839 COLORREF oldTxtColor;
1840 LRESULT cdrf;
1841 HFONT hFont;
1842 NMCUSTOMDRAW nmcd;
1843 LONG state = infoPtr->state;
1844 LONG style = GetWindowLongW( infoPtr->hwnd, GWL_STYLE );
1845 BOOL pushedState = (state & BST_PUSHED);
1846 HWND parent;
1847 HRGN hrgn;
1849 GetClientRect( infoPtr->hwnd, &rc );
1851 /* Send WM_CTLCOLOR to allow changing the font (the colors are fixed) */
1852 if ((hFont = infoPtr->font)) SelectObject( hDC, hFont );
1853 parent = GetParent(infoPtr->hwnd);
1854 if (!parent) parent = infoPtr->hwnd;
1855 SendMessageW( parent, WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)infoPtr->hwnd );
1857 hrgn = set_control_clipping( hDC, &rc );
1859 hpen = CreatePen( PS_SOLID, 1, GetSysColor(COLOR_WINDOWFRAME));
1860 hOldPen = SelectObject(hDC, hpen);
1861 hOldBrush = SelectObject(hDC,GetSysColorBrush(COLOR_BTNFACE));
1862 oldBkMode = SetBkMode(hDC, TRANSPARENT);
1864 init_custom_draw(&nmcd, infoPtr, hDC, &rc);
1866 /* Send erase notifications */
1867 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
1868 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
1870 if (get_button_type(style) == BS_DEFPUSHBUTTON)
1872 if (action != ODA_FOCUS)
1873 Rectangle(hDC, rc.left, rc.top, rc.right, rc.bottom);
1874 InflateRect( &rc, -1, -1 );
1877 /* Skip the frame drawing if only focus has changed */
1878 if (action != ODA_FOCUS)
1880 uState = DFCS_BUTTONPUSH;
1882 if (style & BS_FLAT)
1883 uState |= DFCS_MONO;
1884 else if (pushedState)
1886 if (get_button_type(style) == BS_DEFPUSHBUTTON )
1887 uState |= DFCS_FLAT;
1888 else
1889 uState |= DFCS_PUSHED;
1892 if (state & (BST_CHECKED | BST_INDETERMINATE))
1893 uState |= DFCS_CHECKED;
1895 DrawFrameControl( hDC, &rc, DFC_BUTTON, uState );
1898 if (cdrf & CDRF_NOTIFYPOSTERASE)
1900 nmcd.dwDrawStage = CDDS_POSTERASE;
1901 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
1904 /* Send paint notifications */
1905 nmcd.dwDrawStage = CDDS_PREPAINT;
1906 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
1907 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
1909 if (!(cdrf & CDRF_DOERASE) && action != ODA_FOCUS)
1911 /* draw button label */
1912 labelRect = rc;
1913 /* Shrink label rect at all sides by 2 so that the content won't touch the surrounding frame */
1914 InflateRect(&labelRect, -2, -2);
1915 dtFlags = BUTTON_CalcLayoutRects(infoPtr, hDC, &labelRect, &imageRect, &textRect);
1917 if (dtFlags != (UINT)-1L)
1919 if (pushedState) OffsetRect(&labelRect, 1, 1);
1921 oldTxtColor = SetTextColor( hDC, GetSysColor(COLOR_BTNTEXT) );
1923 BUTTON_DrawLabel(infoPtr, hDC, dtFlags, &imageRect, &textRect);
1925 SetTextColor( hDC, oldTxtColor );
1929 if (cdrf & CDRF_NOTIFYPOSTPAINT)
1931 nmcd.dwDrawStage = CDDS_POSTPAINT;
1932 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
1934 if ((cdrf & CDRF_SKIPPOSTPAINT) || dtFlags == (UINT)-1L) goto cleanup;
1936 if (action == ODA_FOCUS || (state & BST_FOCUS))
1938 InflateRect( &rc, -2, -2 );
1939 DrawFocusRect( hDC, &rc );
1942 cleanup:
1943 SelectObject( hDC, hOldPen );
1944 SelectObject( hDC, hOldBrush );
1945 SetBkMode(hDC, oldBkMode);
1946 SelectClipRgn( hDC, hrgn );
1947 if (hrgn) DeleteObject( hrgn );
1948 DeleteObject( hpen );
1951 /**********************************************************************
1952 * Check Box & Radio Button Functions
1955 /* Get adjusted check box or radio box rectangle */
1956 static RECT get_box_rect(LONG style, LONG ex_style, const RECT *content_rect,
1957 const RECT *label_rect, BOOL has_label, SIZE box_size)
1959 RECT rect;
1960 int delta;
1962 rect = *content_rect;
1964 if (style & BS_LEFTTEXT || ex_style & WS_EX_RIGHT)
1965 rect.left = rect.right - box_size.cx;
1966 else
1967 rect.right = rect.left + box_size.cx;
1969 /* Adjust box when label is valid */
1970 if (has_label)
1972 rect.top = label_rect->top;
1973 rect.bottom = label_rect->bottom;
1976 /* Box must have the correct height */
1977 delta = rect.bottom - rect.top - box_size.cy;
1978 if ((style & BS_VCENTER) == BS_TOP)
1980 if (delta <= 0)
1981 rect.top -= -delta / 2 + 1;
1983 rect.bottom = rect.top + box_size.cy;
1985 else if ((style & BS_VCENTER) == BS_BOTTOM)
1987 if (delta <= 0)
1988 rect.bottom += -delta / 2 + 1;
1990 rect.top = rect.bottom - box_size.cy;
1992 else
1994 if (delta > 0)
1996 rect.bottom -= delta / 2 + 1;
1997 rect.top = rect.bottom - box_size.cy;
1999 else if (delta < 0)
2001 rect.top -= -delta / 2 + 1;
2002 rect.bottom = rect.top + box_size.cy;
2006 return rect;
2009 static void CB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action )
2011 RECT rbox, labelRect, oldLabelRect, imageRect, textRect, client;
2012 HBRUSH hBrush;
2013 int text_offset;
2014 UINT dtFlags;
2015 LRESULT cdrf;
2016 HFONT hFont;
2017 NMCUSTOMDRAW nmcd;
2018 LONG state = infoPtr->state;
2019 LONG style = GetWindowLongW( infoPtr->hwnd, GWL_STYLE );
2020 LONG ex_style = GetWindowLongW( infoPtr->hwnd, GWL_EXSTYLE );
2021 SIZE box_size;
2022 HWND parent;
2023 HRGN hrgn;
2025 if (style & BS_PUSHLIKE)
2027 PB_Paint( infoPtr, hDC, action );
2028 return;
2031 GetClientRect(infoPtr->hwnd, &client);
2032 labelRect = client;
2034 box_size.cx = 12 * GetDpiForWindow(infoPtr->hwnd) / 96 + 1;
2035 box_size.cy = box_size.cx;
2037 if ((hFont = infoPtr->font)) SelectObject( hDC, hFont );
2038 GetCharWidthW( hDC, '0', '0', &text_offset );
2039 text_offset /= 2;
2041 parent = GetParent(infoPtr->hwnd);
2042 if (!parent) parent = infoPtr->hwnd;
2043 hBrush = (HBRUSH)SendMessageW(parent, WM_CTLCOLORSTATIC, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
2044 if (!hBrush) /* did the app forget to call defwindowproc ? */
2045 hBrush = (HBRUSH)DefWindowProcW(parent, WM_CTLCOLORSTATIC, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
2046 hrgn = set_control_clipping( hDC, &client );
2048 if (style & BS_LEFTTEXT || ex_style & WS_EX_RIGHT)
2049 labelRect.right -= box_size.cx + text_offset;
2050 else
2051 labelRect.left += box_size.cx + text_offset;
2053 oldLabelRect = labelRect;
2054 dtFlags = BUTTON_CalcLayoutRects(infoPtr, hDC, &labelRect, &imageRect, &textRect);
2055 rbox = get_box_rect(style, ex_style, &client, &labelRect, dtFlags != (UINT)-1L, box_size);
2057 init_custom_draw(&nmcd, infoPtr, hDC, &client);
2059 /* Send erase notifications */
2060 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2061 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2063 /* Since WM_ERASEBKGND does nothing, first prepare background */
2064 if (action == ODA_SELECT) FillRect( hDC, &rbox, hBrush );
2065 if (action == ODA_DRAWENTIRE) FillRect( hDC, &client, hBrush );
2066 if (cdrf & CDRF_NOTIFYPOSTERASE)
2068 nmcd.dwDrawStage = CDDS_POSTERASE;
2069 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2072 /* Draw label */
2073 /* Send paint notifications */
2074 nmcd.dwDrawStage = CDDS_PREPAINT;
2075 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2076 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2078 /* Draw the check-box bitmap */
2079 if (!(cdrf & CDRF_DOERASE))
2081 if (action == ODA_DRAWENTIRE || action == ODA_SELECT)
2083 UINT flags;
2085 if ((get_button_type(style) == BS_RADIOBUTTON) ||
2086 (get_button_type(style) == BS_AUTORADIOBUTTON)) flags = DFCS_BUTTONRADIO;
2087 else if (state & BST_INDETERMINATE) flags = DFCS_BUTTON3STATE;
2088 else flags = DFCS_BUTTONCHECK;
2090 if (state & (BST_CHECKED | BST_INDETERMINATE)) flags |= DFCS_CHECKED;
2091 if (state & BST_PUSHED) flags |= DFCS_PUSHED;
2092 if (style & WS_DISABLED) flags |= DFCS_INACTIVE;
2094 DrawFrameControl(hDC, &rbox, DFC_BUTTON, flags);
2097 if (dtFlags != (UINT)-1L) /* Something to draw */
2098 if (action == ODA_DRAWENTIRE) BUTTON_DrawLabel(infoPtr, hDC, dtFlags, &imageRect, &textRect);
2101 if (cdrf & CDRF_NOTIFYPOSTPAINT)
2103 nmcd.dwDrawStage = CDDS_POSTPAINT;
2104 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2106 if ((cdrf & CDRF_SKIPPOSTPAINT) || dtFlags == (UINT)-1L) goto cleanup;
2108 /* ... and focus */
2109 if (action == ODA_FOCUS || (state & BST_FOCUS))
2111 labelRect.left--;
2112 labelRect.right++;
2113 IntersectRect(&labelRect, &labelRect, &oldLabelRect);
2114 DrawFocusRect(hDC, &labelRect);
2117 cleanup:
2118 SelectClipRgn( hDC, hrgn );
2119 if (hrgn) DeleteObject( hrgn );
2123 /**********************************************************************
2124 * BUTTON_CheckAutoRadioButton
2126 * hwnd is checked, uncheck every other auto radio button in group
2128 static void BUTTON_CheckAutoRadioButton( HWND hwnd )
2130 HWND parent, sibling, start;
2132 parent = GetParent(hwnd);
2133 /* make sure that starting control is not disabled or invisible */
2134 start = sibling = GetNextDlgGroupItem( parent, hwnd, TRUE );
2137 if (!sibling) break;
2138 if ((hwnd != sibling) &&
2139 ((GetWindowLongW( sibling, GWL_STYLE) & BS_TYPEMASK) == BS_AUTORADIOBUTTON))
2140 SendMessageW( sibling, BM_SETCHECK, BST_UNCHECKED, 0 );
2141 sibling = GetNextDlgGroupItem( parent, sibling, FALSE );
2142 } while (sibling != start);
2146 /**********************************************************************
2147 * Group Box Functions
2150 static void GB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action )
2152 RECT labelRect, imageRect, textRect, rcFrame;
2153 HBRUSH hbr;
2154 HFONT hFont;
2155 UINT dtFlags;
2156 TEXTMETRICW tm;
2157 LONG style = GetWindowLongW( infoPtr->hwnd, GWL_STYLE );
2158 HWND parent;
2159 HRGN hrgn;
2161 if ((hFont = infoPtr->font)) SelectObject( hDC, hFont );
2162 /* GroupBox acts like static control, so it sends CTLCOLORSTATIC */
2163 parent = GetParent(infoPtr->hwnd);
2164 if (!parent) parent = infoPtr->hwnd;
2165 hbr = (HBRUSH)SendMessageW(parent, WM_CTLCOLORSTATIC, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
2166 if (!hbr) /* did the app forget to call defwindowproc ? */
2167 hbr = (HBRUSH)DefWindowProcW(parent, WM_CTLCOLORSTATIC, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
2168 GetClientRect(infoPtr->hwnd, &labelRect);
2169 rcFrame = labelRect;
2170 hrgn = set_control_clipping(hDC, &labelRect);
2172 GetTextMetricsW (hDC, &tm);
2173 rcFrame.top += (tm.tmHeight / 2) - 1;
2174 DrawEdge (hDC, &rcFrame, EDGE_ETCHED, BF_RECT | ((style & BS_FLAT) ? BF_FLAT : 0));
2176 InflateRect(&labelRect, -7, 1);
2177 dtFlags = BUTTON_CalcLayoutRects(infoPtr, hDC, &labelRect, &imageRect, &textRect);
2179 if (dtFlags != (UINT)-1)
2181 /* Because buttons have CS_PARENTDC class style, there is a chance
2182 * that label will be drawn out of client rect.
2183 * But Windows doesn't clip label's rect, so do I.
2186 /* There is 1-pixel margin at the left, right, and bottom */
2187 labelRect.left--;
2188 labelRect.right++;
2189 labelRect.bottom++;
2190 FillRect(hDC, &labelRect, hbr);
2191 BUTTON_DrawLabel(infoPtr, hDC, dtFlags, &imageRect, &textRect);
2193 SelectClipRgn( hDC, hrgn );
2194 if (hrgn) DeleteObject( hrgn );
2198 /**********************************************************************
2199 * User Button Functions
2202 static void UB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action )
2204 RECT rc;
2205 HBRUSH hBrush;
2206 LRESULT cdrf;
2207 HFONT hFont;
2208 NMCUSTOMDRAW nmcd;
2209 LONG state = infoPtr->state;
2210 HWND parent;
2212 GetClientRect( infoPtr->hwnd, &rc);
2214 if ((hFont = infoPtr->font)) SelectObject( hDC, hFont );
2216 parent = GetParent(infoPtr->hwnd);
2217 if (!parent) parent = infoPtr->hwnd;
2218 hBrush = (HBRUSH)SendMessageW(parent, WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
2219 if (!hBrush) /* did the app forget to call defwindowproc ? */
2220 hBrush = (HBRUSH)DefWindowProcW(parent, WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
2222 if (action == ODA_FOCUS || (state & BST_FOCUS))
2224 init_custom_draw(&nmcd, infoPtr, hDC, &rc);
2226 /* Send erase notifications */
2227 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2228 if (cdrf & CDRF_SKIPDEFAULT) goto notify;
2231 FillRect( hDC, &rc, hBrush );
2232 if (action == ODA_FOCUS || (state & BST_FOCUS))
2234 if (cdrf & CDRF_NOTIFYPOSTERASE)
2236 nmcd.dwDrawStage = CDDS_POSTERASE;
2237 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2240 /* Send paint notifications */
2241 nmcd.dwDrawStage = CDDS_PREPAINT;
2242 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2243 if (cdrf & CDRF_SKIPDEFAULT) goto notify;
2244 if (cdrf & CDRF_NOTIFYPOSTPAINT)
2246 nmcd.dwDrawStage = CDDS_POSTPAINT;
2247 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2250 if (!(cdrf & CDRF_SKIPPOSTPAINT))
2251 DrawFocusRect( hDC, &rc );
2254 notify:
2255 switch (action)
2257 case ODA_FOCUS:
2258 BUTTON_NOTIFY_PARENT( infoPtr->hwnd, (state & BST_FOCUS) ? BN_SETFOCUS : BN_KILLFOCUS );
2259 break;
2261 case ODA_SELECT:
2262 BUTTON_NOTIFY_PARENT( infoPtr->hwnd, (state & BST_PUSHED) ? BN_HILITE : BN_UNHILITE );
2263 break;
2265 default:
2266 break;
2271 /**********************************************************************
2272 * Ownerdrawn Button Functions
2275 static void OB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action )
2277 LONG state = infoPtr->state;
2278 DRAWITEMSTRUCT dis;
2279 LONG_PTR id = GetWindowLongPtrW( infoPtr->hwnd, GWLP_ID );
2280 HWND parent;
2281 HFONT hFont;
2282 HRGN hrgn;
2284 dis.CtlType = ODT_BUTTON;
2285 dis.CtlID = id;
2286 dis.itemID = 0;
2287 dis.itemAction = action;
2288 dis.itemState = ((state & BST_FOCUS) ? ODS_FOCUS : 0) |
2289 ((state & BST_PUSHED) ? ODS_SELECTED : 0) |
2290 (IsWindowEnabled(infoPtr->hwnd) ? 0: ODS_DISABLED);
2291 dis.hwndItem = infoPtr->hwnd;
2292 dis.hDC = hDC;
2293 dis.itemData = 0;
2294 GetClientRect( infoPtr->hwnd, &dis.rcItem );
2296 if ((hFont = infoPtr->font)) SelectObject( hDC, hFont );
2297 parent = GetParent(infoPtr->hwnd);
2298 if (!parent) parent = infoPtr->hwnd;
2299 SendMessageW( parent, WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)infoPtr->hwnd );
2301 hrgn = set_control_clipping( hDC, &dis.rcItem );
2303 SendMessageW( GetParent(infoPtr->hwnd), WM_DRAWITEM, id, (LPARAM)&dis );
2304 SelectClipRgn( hDC, hrgn );
2305 if (hrgn) DeleteObject( hrgn );
2309 /**********************************************************************
2310 * Split Button Functions
2312 static void SB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action )
2314 LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2315 LONG state = infoPtr->state;
2316 UINT dtFlags = (UINT)-1L;
2318 RECT rc, push_rect, dropdown_rect;
2319 NMCUSTOMDRAW nmcd;
2320 HPEN pen, old_pen;
2321 HBRUSH old_brush;
2322 INT old_bk_mode;
2323 LRESULT cdrf;
2324 HWND parent;
2325 HRGN hrgn;
2327 GetClientRect(infoPtr->hwnd, &rc);
2329 /* Send WM_CTLCOLOR to allow changing the font (the colors are fixed) */
2330 if (infoPtr->font) SelectObject(hDC, infoPtr->font);
2331 if (!(parent = GetParent(infoPtr->hwnd))) parent = infoPtr->hwnd;
2332 SendMessageW(parent, WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
2334 hrgn = set_control_clipping(hDC, &rc);
2336 pen = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_WINDOWFRAME));
2337 old_pen = SelectObject(hDC, pen);
2338 old_brush = SelectObject(hDC, GetSysColorBrush(COLOR_BTNFACE));
2339 old_bk_mode = SetBkMode(hDC, TRANSPARENT);
2341 init_custom_draw(&nmcd, infoPtr, hDC, &rc);
2343 /* Send erase notifications */
2344 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2345 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2347 if (get_button_type(style) == BS_DEFSPLITBUTTON)
2349 if (action != ODA_FOCUS)
2350 Rectangle(hDC, rc.left, rc.top, rc.right, rc.bottom);
2351 InflateRect(&rc, -1, -1);
2352 /* The split will now be off by 1 pixel, but
2353 that's exactly what Windows does as well */
2356 get_split_button_rects(infoPtr, &rc, &push_rect, &dropdown_rect);
2357 if (infoPtr->split_style & BCSS_NOSPLIT)
2358 push_rect = rc;
2360 /* Skip the frame drawing if only focus has changed */
2361 if (action != ODA_FOCUS)
2363 UINT flags = DFCS_BUTTONPUSH;
2365 if (style & BS_FLAT) flags |= DFCS_MONO;
2366 else if (state & BST_PUSHED)
2367 flags |= (get_button_type(style) == BS_DEFSPLITBUTTON)
2368 ? DFCS_FLAT : DFCS_PUSHED;
2370 if (state & (BST_CHECKED | BST_INDETERMINATE))
2371 flags |= DFCS_CHECKED;
2373 if (infoPtr->split_style & BCSS_NOSPLIT)
2374 DrawFrameControl(hDC, &push_rect, DFC_BUTTON, flags);
2375 else
2377 UINT dropdown_flags = flags & ~DFCS_CHECKED;
2379 if (state & BST_DROPDOWNPUSHED)
2380 dropdown_flags = (dropdown_flags & ~DFCS_FLAT) | DFCS_PUSHED;
2382 /* Adjust for shadow and draw order so it looks properly */
2383 if (infoPtr->split_style & BCSS_ALIGNLEFT)
2385 dropdown_rect.right++;
2386 DrawFrameControl(hDC, &dropdown_rect, DFC_BUTTON, dropdown_flags);
2387 dropdown_rect.right--;
2388 DrawFrameControl(hDC, &push_rect, DFC_BUTTON, flags);
2390 else
2392 push_rect.right++;
2393 DrawFrameControl(hDC, &push_rect, DFC_BUTTON, flags);
2394 push_rect.right--;
2395 DrawFrameControl(hDC, &dropdown_rect, DFC_BUTTON, dropdown_flags);
2400 if (cdrf & CDRF_NOTIFYPOSTERASE)
2402 nmcd.dwDrawStage = CDDS_POSTERASE;
2403 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2406 /* Send paint notifications */
2407 nmcd.dwDrawStage = CDDS_PREPAINT;
2408 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2409 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2411 /* Shrink push button rect so that the content won't touch the surrounding frame */
2412 InflateRect(&push_rect, -2, -2);
2414 if (!(cdrf & CDRF_DOERASE) && action != ODA_FOCUS)
2416 COLORREF old_color = SetTextColor(hDC, GetSysColor(COLOR_BTNTEXT));
2417 RECT label_rect = push_rect, image_rect, text_rect;
2419 dtFlags = BUTTON_CalcLayoutRects(infoPtr, hDC, &label_rect, &image_rect, &text_rect);
2421 if (dtFlags != (UINT)-1L)
2422 BUTTON_DrawLabel(infoPtr, hDC, dtFlags, &image_rect, &text_rect);
2424 draw_split_button_dropdown_glyph(infoPtr, hDC, &dropdown_rect);
2425 SetTextColor(hDC, old_color);
2428 if (cdrf & CDRF_NOTIFYPOSTPAINT)
2430 nmcd.dwDrawStage = CDDS_POSTPAINT;
2431 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2433 if ((cdrf & CDRF_SKIPPOSTPAINT) || dtFlags == (UINT)-1L) goto cleanup;
2435 if (action == ODA_FOCUS || (state & BST_FOCUS))
2436 DrawFocusRect(hDC, &push_rect);
2438 cleanup:
2439 SelectObject(hDC, old_pen);
2440 SelectObject(hDC, old_brush);
2441 SetBkMode(hDC, old_bk_mode);
2442 SelectClipRgn(hDC, hrgn);
2443 if (hrgn) DeleteObject(hrgn);
2444 DeleteObject(pen);
2447 /* Given the full button rect of the split button, retrieve the push part and the dropdown part */
2448 static inline void get_split_button_rects(const BUTTON_INFO *infoPtr, const RECT *button_rect,
2449 RECT *push_rect, RECT *dropdown_rect)
2451 *push_rect = *dropdown_rect = *button_rect;
2453 /* The dropdown takes priority if the client rect is too small, it will only have a dropdown */
2454 if (infoPtr->split_style & BCSS_ALIGNLEFT)
2456 dropdown_rect->right = min(button_rect->left + infoPtr->glyph_size.cx, button_rect->right);
2457 push_rect->left = dropdown_rect->right;
2459 else
2461 dropdown_rect->left = max(button_rect->right - infoPtr->glyph_size.cx, button_rect->left);
2462 push_rect->right = dropdown_rect->left;
2466 /* Notify the parent if the point is within the dropdown and return TRUE (always notify if NULL) */
2467 static BOOL notify_split_button_dropdown(const BUTTON_INFO *infoPtr, const POINT *pt, HWND hwnd)
2469 NMBCDROPDOWN nmbcd;
2471 GetClientRect(hwnd, &nmbcd.rcButton);
2472 if (pt)
2474 RECT push_rect, dropdown_rect;
2476 get_split_button_rects(infoPtr, &nmbcd.rcButton, &push_rect, &dropdown_rect);
2477 if (!PtInRect(&dropdown_rect, *pt))
2478 return FALSE;
2480 /* If it's already down (set manually via BCM_SETDROPDOWNSTATE), fake the notify */
2481 if (infoPtr->state & BST_DROPDOWNPUSHED)
2482 return TRUE;
2484 SendMessageW(hwnd, BCM_SETDROPDOWNSTATE, TRUE, 0);
2486 nmbcd.hdr.hwndFrom = hwnd;
2487 nmbcd.hdr.idFrom = GetWindowLongPtrW(hwnd, GWLP_ID);
2488 nmbcd.hdr.code = BCN_DROPDOWN;
2489 SendMessageW(GetParent(hwnd), WM_NOTIFY, nmbcd.hdr.idFrom, (LPARAM)&nmbcd);
2491 SendMessageW(hwnd, BCM_SETDROPDOWNSTATE, FALSE, 0);
2492 return TRUE;
2495 /* Draw the split button dropdown glyph or image */
2496 static void draw_split_button_dropdown_glyph(const BUTTON_INFO *infoPtr, HDC hdc, RECT *rect)
2498 if (infoPtr->split_style & BCSS_IMAGE)
2500 int w, h;
2502 /* When the glyph is an image list, Windows is very buggy with BCSS_STRETCH,
2503 positions it weirdly and doesn't even stretch it, but instead extends the
2504 image, leaking into other images in the list (or black if none). Instead,
2505 we'll ignore this and just position it at center as without BCSS_STRETCH. */
2506 if (!ImageList_GetIconSize(infoPtr->glyph, &w, &h)) return;
2508 ImageList_Draw(infoPtr->glyph,
2509 (ImageList_GetImageCount(infoPtr->glyph) == 1) ? 0 : get_draw_state(infoPtr) - 1,
2510 hdc, rect->left + (rect->right - rect->left - w) / 2,
2511 rect->top + (rect->bottom - rect->top - h) / 2, ILD_NORMAL);
2513 else if (infoPtr->glyph_size.cy >= 0)
2515 /* infoPtr->glyph is a character code from Marlett */
2516 HFONT font, old_font;
2517 LOGFONTW logfont = { 0, 0, 0, 0, FW_NORMAL, 0, 0, 0, SYMBOL_CHARSET, 0, 0, 0, 0,
2518 L"Marlett" };
2519 if (infoPtr->glyph_size.cy)
2521 /* BCSS_STRETCH preserves aspect ratio, uses minimum as size */
2522 if (infoPtr->split_style & BCSS_STRETCH)
2523 logfont.lfHeight = min(infoPtr->glyph_size.cx, infoPtr->glyph_size.cy);
2524 else
2526 logfont.lfWidth = infoPtr->glyph_size.cx;
2527 logfont.lfHeight = infoPtr->glyph_size.cy;
2530 else logfont.lfHeight = infoPtr->glyph_size.cx;
2532 if ((font = CreateFontIndirectW(&logfont)))
2534 old_font = SelectObject(hdc, font);
2535 DrawTextW(hdc, (const WCHAR*)&infoPtr->glyph, 1, rect,
2536 DT_CENTER | DT_VCENTER | DT_SINGLELINE | DT_NOCLIP | DT_NOPREFIX);
2537 SelectObject(hdc, old_font);
2538 DeleteObject(font);
2544 /**********************************************************************
2545 * Command Link Functions
2547 static void CL_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action )
2549 LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2550 LONG state = infoPtr->state;
2552 RECT rc, content_rect;
2553 NMCUSTOMDRAW nmcd;
2554 HPEN pen, old_pen;
2555 HBRUSH old_brush;
2556 INT old_bk_mode;
2557 LRESULT cdrf;
2558 HWND parent;
2559 HRGN hrgn;
2561 GetClientRect(infoPtr->hwnd, &rc);
2563 /* Command Links are not affected by the button's font, and are based
2564 on the default message font. Furthermore, they are not affected by
2565 any of the alignment styles (and always align with the top-left). */
2566 if (!(parent = GetParent(infoPtr->hwnd))) parent = infoPtr->hwnd;
2567 SendMessageW(parent, WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
2569 hrgn = set_control_clipping(hDC, &rc);
2571 pen = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_WINDOWFRAME));
2572 old_pen = SelectObject(hDC, pen);
2573 old_brush = SelectObject(hDC, GetSysColorBrush(COLOR_BTNFACE));
2574 old_bk_mode = SetBkMode(hDC, TRANSPARENT);
2576 init_custom_draw(&nmcd, infoPtr, hDC, &rc);
2578 /* Send erase notifications */
2579 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2580 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2581 content_rect = rc;
2583 if (get_button_type(style) == BS_DEFCOMMANDLINK)
2585 if (action != ODA_FOCUS)
2586 Rectangle(hDC, rc.left, rc.top, rc.right, rc.bottom);
2587 InflateRect(&rc, -1, -1);
2590 /* Skip the frame drawing if only focus has changed */
2591 if (action != ODA_FOCUS)
2593 if (!(state & (BST_HOT | BST_PUSHED | BST_CHECKED | BST_INDETERMINATE)))
2594 FillRect(hDC, &rc, GetSysColorBrush(COLOR_BTNFACE));
2595 else
2597 UINT flags = DFCS_BUTTONPUSH;
2599 if (style & BS_FLAT) flags |= DFCS_MONO;
2600 else if (state & BST_PUSHED) flags |= DFCS_PUSHED;
2602 if (state & (BST_CHECKED | BST_INDETERMINATE))
2603 flags |= DFCS_CHECKED;
2604 DrawFrameControl(hDC, &rc, DFC_BUTTON, flags);
2608 if (cdrf & CDRF_NOTIFYPOSTERASE)
2610 nmcd.dwDrawStage = CDDS_POSTERASE;
2611 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2614 /* Send paint notifications */
2615 nmcd.dwDrawStage = CDDS_PREPAINT;
2616 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2617 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2619 if (!(cdrf & CDRF_DOERASE) && action != ODA_FOCUS)
2621 UINT flags = IsWindowEnabled(infoPtr->hwnd) ? DSS_NORMAL : DSS_DISABLED;
2622 COLORREF old_color = SetTextColor(hDC, GetSysColor(flags == DSS_NORMAL ?
2623 COLOR_BTNTEXT : COLOR_GRAYTEXT));
2624 HIMAGELIST defimg = NULL;
2625 NONCLIENTMETRICSW ncm;
2626 UINT txt_h = 0;
2627 SIZE img_size;
2629 /* Command Links ignore the margins of the image list or its alignment */
2630 if (infoPtr->u.image || infoPtr->imagelist.himl)
2631 img_size = BUTTON_GetImageSize(infoPtr);
2632 else
2634 img_size.cx = img_size.cy = command_link_defglyph_size;
2635 defimg = ImageList_LoadImageW(COMCTL32_hModule, (LPCWSTR)MAKEINTRESOURCE(IDB_CMDLINK),
2636 img_size.cx, 3, CLR_NONE, IMAGE_BITMAP, LR_CREATEDIBSECTION);
2639 /* Shrink rect by the command link margin, except on bottom (just the frame) */
2640 InflateRect(&content_rect, -command_link_margin, -command_link_margin);
2641 content_rect.bottom += command_link_margin - 2;
2643 ncm.cbSize = sizeof(ncm);
2644 if (SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, sizeof(ncm), &ncm, 0))
2646 LONG note_weight = ncm.lfMessageFont.lfWeight;
2647 RECT r = content_rect;
2648 WCHAR *text;
2649 HFONT font;
2651 if (img_size.cx) r.left += img_size.cx + command_link_margin;
2653 /* Draw the text */
2654 ncm.lfMessageFont.lfWeight = FW_BOLD;
2655 if ((font = CreateFontIndirectW(&ncm.lfMessageFont)))
2657 if ((text = get_button_text(infoPtr)))
2659 SelectObject(hDC, font);
2660 txt_h = DrawTextW(hDC, text, -1, &r,
2661 DT_TOP | DT_LEFT | DT_WORDBREAK | DT_END_ELLIPSIS);
2662 Free(text);
2664 DeleteObject(font);
2667 /* Draw the note */
2668 ncm.lfMessageFont.lfWeight = note_weight;
2669 if (infoPtr->note && (font = CreateFontIndirectW(&ncm.lfMessageFont)))
2671 r.top += txt_h + 2;
2672 SelectObject(hDC, font);
2673 DrawTextW(hDC, infoPtr->note, infoPtr->note_length, &r,
2674 DT_TOP | DT_LEFT | DT_WORDBREAK | DT_NOPREFIX);
2675 DeleteObject(font);
2679 /* Position the image at the vertical center of the drawn text (not note) */
2680 txt_h = min(txt_h, content_rect.bottom - content_rect.top);
2681 if (img_size.cy < txt_h) content_rect.top += (txt_h - img_size.cy) / 2;
2683 content_rect.right = content_rect.left + img_size.cx;
2684 content_rect.bottom = content_rect.top + img_size.cy;
2686 if (defimg)
2688 int i = 0;
2689 if (flags == DSS_DISABLED) i = 2;
2690 else if (state & BST_HOT) i = 1;
2692 ImageList_Draw(defimg, i, hDC, content_rect.left, content_rect.top, ILD_NORMAL);
2693 ImageList_Destroy(defimg);
2695 else
2696 BUTTON_DrawImage(infoPtr, hDC, NULL, flags, &content_rect);
2698 SetTextColor(hDC, old_color);
2701 if (cdrf & CDRF_NOTIFYPOSTPAINT)
2703 nmcd.dwDrawStage = CDDS_POSTPAINT;
2704 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2706 if (cdrf & CDRF_SKIPPOSTPAINT) goto cleanup;
2708 if (action == ODA_FOCUS || (state & BST_FOCUS))
2710 InflateRect(&rc, -2, -2);
2711 DrawFocusRect(hDC, &rc);
2714 cleanup:
2715 SelectObject(hDC, old_pen);
2716 SelectObject(hDC, old_brush);
2717 SetBkMode(hDC, old_bk_mode);
2718 SelectClipRgn(hDC, hrgn);
2719 if (hrgn) DeleteObject(hrgn);
2720 DeleteObject(pen);
2724 /**********************************************************************
2725 * Themed Paint Functions
2727 static void PB_ThemedPaint(HTHEME theme, const BUTTON_INFO *infoPtr, HDC hDC, int state, UINT dtFlags, BOOL focused)
2729 RECT bgRect, labelRect, imageRect, textRect, focusRect;
2730 NMCUSTOMDRAW nmcd;
2731 HBRUSH brush;
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))
2752 DrawThemeParentBackground(infoPtr->hwnd, hDC, NULL);
2753 /* Tests show that the brush from WM_CTLCOLORBTN is used for filling background after a
2754 * DrawThemeParentBackground() call */
2755 brush = (HBRUSH)SendMessageW(parent, WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
2756 FillRect(hDC, &bgRect, brush ? brush : GetSysColorBrush(COLOR_BTNFACE));
2758 DrawThemeBackground(theme, hDC, BP_PUSHBUTTON, state, &bgRect, NULL);
2760 if (cdrf & CDRF_NOTIFYPOSTERASE)
2762 nmcd.dwDrawStage = CDDS_POSTERASE;
2763 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2766 /* Send paint notifications */
2767 nmcd.dwDrawStage = CDDS_PREPAINT;
2768 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2769 if (cdrf & CDRF_SKIPDEFAULT) return;
2771 if (!(cdrf & CDRF_DOERASE))
2773 dtFlags = BUTTON_CalcLayoutRects(infoPtr, hDC, &labelRect, &imageRect, &textRect);
2774 if (dtFlags != (UINT)-1L)
2775 BUTTON_DrawThemedLabel(infoPtr, hDC, dtFlags, &imageRect, &textRect, theme,
2776 BP_PUSHBUTTON, state);
2779 if (cdrf & CDRF_NOTIFYPOSTPAINT)
2781 nmcd.dwDrawStage = CDDS_POSTPAINT;
2782 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2784 if (cdrf & CDRF_SKIPPOSTPAINT) return;
2786 if (focused) DrawFocusRect(hDC, &focusRect);
2789 static void CB_ThemedPaint(HTHEME theme, const BUTTON_INFO *infoPtr, HDC hDC, int state, UINT dtFlags, BOOL focused)
2791 RECT client_rect, content_rect, old_label_rect, label_rect, box_rect, image_rect, text_rect;
2792 HFONT font, hPrevFont = NULL;
2793 DWORD dwStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2794 LONG ex_style = GetWindowLongW(infoPtr->hwnd, GWL_EXSTYLE);
2795 UINT btn_type = get_button_type( dwStyle );
2796 int part = (btn_type == BS_RADIOBUTTON) || (btn_type == BS_AUTORADIOBUTTON) ? BP_RADIOBUTTON : BP_CHECKBOX;
2797 NMCUSTOMDRAW nmcd;
2798 HBRUSH brush;
2799 LRESULT cdrf;
2800 LOGFONTW lf;
2801 HWND parent;
2802 BOOL created_font = FALSE;
2803 int text_offset;
2804 SIZE box_size;
2805 HRGN region;
2806 HRESULT hr;
2808 if (dwStyle & BS_PUSHLIKE)
2810 PB_ThemedPaint(theme, infoPtr, hDC, state, dtFlags, focused);
2811 return;
2814 hr = GetThemeFont(theme, hDC, part, state, TMT_FONT, &lf);
2815 if (SUCCEEDED(hr)) {
2816 font = CreateFontIndirectW(&lf);
2817 if (!font)
2818 TRACE("Failed to create font\n");
2819 else {
2820 TRACE("font = %s\n", debugstr_w(lf.lfFaceName));
2821 hPrevFont = SelectObject(hDC, font);
2822 created_font = TRUE;
2824 } else {
2825 if (infoPtr->font) SelectObject(hDC, infoPtr->font);
2828 GetClientRect(infoPtr->hwnd, &client_rect);
2829 GetThemeBackgroundContentRect(theme, hDC, part, state, &client_rect, &content_rect);
2830 region = set_control_clipping(hDC, &client_rect);
2832 if (FAILED(GetThemePartSize(theme, hDC, part, state, &content_rect, TS_DRAW, &box_size)))
2834 box_size.cx = 12 * GetDpiForWindow(infoPtr->hwnd) / 96 + 1;
2835 box_size.cy = box_size.cx;
2838 GetCharWidthW(hDC, '0', '0', &text_offset);
2839 text_offset /= 2;
2841 label_rect = content_rect;
2842 if (dwStyle & BS_LEFTTEXT || ex_style & WS_EX_RIGHT)
2843 label_rect.right -= box_size.cx + text_offset;
2844 else
2845 label_rect.left += box_size.cx + text_offset;
2847 old_label_rect = label_rect;
2848 dtFlags = BUTTON_CalcLayoutRects(infoPtr, hDC, &label_rect, &image_rect, &text_rect);
2849 box_rect = get_box_rect(dwStyle, ex_style, &content_rect, &label_rect, dtFlags != (UINT)-1L,
2850 box_size);
2852 init_custom_draw(&nmcd, infoPtr, hDC, &client_rect);
2854 parent = GetParent(infoPtr->hwnd);
2855 if (!parent) parent = infoPtr->hwnd;
2857 /* Send erase notifications */
2858 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2859 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2861 DrawThemeParentBackground(infoPtr->hwnd, hDC, NULL);
2862 /* Tests show that the brush from WM_CTLCOLORSTATIC is used for filling background after a
2863 * DrawThemeParentBackground() call */
2864 brush = (HBRUSH)SendMessageW(parent, WM_CTLCOLORSTATIC, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
2865 FillRect(hDC, &client_rect, brush ? brush : GetSysColorBrush(COLOR_BTNFACE));
2867 if (cdrf & CDRF_NOTIFYPOSTERASE)
2869 nmcd.dwDrawStage = CDDS_POSTERASE;
2870 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2873 /* Send paint notifications */
2874 nmcd.dwDrawStage = CDDS_PREPAINT;
2875 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2876 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2878 /* Draw label */
2879 if (!(cdrf & CDRF_DOERASE))
2881 DrawThemeBackground(theme, hDC, part, state, &box_rect, NULL);
2882 if (dtFlags != (UINT)-1L)
2883 BUTTON_DrawThemedLabel(infoPtr, hDC, dtFlags, &image_rect, &text_rect, theme, part, state);
2886 if (cdrf & CDRF_NOTIFYPOSTPAINT)
2888 nmcd.dwDrawStage = CDDS_POSTPAINT;
2889 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2891 if ((cdrf & CDRF_SKIPPOSTPAINT) || dtFlags == (UINT)-1L) goto cleanup;
2893 if (focused)
2895 label_rect.left--;
2896 label_rect.right++;
2897 IntersectRect(&label_rect, &label_rect, &old_label_rect);
2898 DrawFocusRect(hDC, &label_rect);
2901 cleanup:
2902 SelectClipRgn(hDC, region);
2903 if (region) DeleteObject(region);
2904 if (created_font) DeleteObject(font);
2905 if (hPrevFont) SelectObject(hDC, hPrevFont);
2908 static void GB_ThemedPaint(HTHEME theme, const BUTTON_INFO *infoPtr, HDC hDC, int state, UINT dtFlags, BOOL focused)
2910 RECT clientRect, contentRect, labelRect, imageRect, textRect, bgRect;
2911 HRGN region, textRegion = NULL;
2912 LOGFONTW lf;
2913 HFONT font, hPrevFont = NULL;
2914 BOOL created_font = FALSE;
2915 TEXTMETRICW textMetric;
2916 HBRUSH brush;
2917 HWND parent;
2918 HRESULT hr;
2919 LONG style;
2920 int part;
2922 /* DrawThemeParentBackground() is used for filling content background. The brush from
2923 * WM_CTLCOLORSTATIC is used for filling text background */
2924 parent = GetParent(infoPtr->hwnd);
2925 if (!parent)
2926 parent = infoPtr->hwnd;
2927 brush = (HBRUSH)SendMessageW(parent, WM_CTLCOLORSTATIC, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
2929 hr = GetThemeFont(theme, hDC, BP_GROUPBOX, state, TMT_FONT, &lf);
2930 if (SUCCEEDED(hr)) {
2931 font = CreateFontIndirectW(&lf);
2932 if (!font)
2933 TRACE("Failed to create font\n");
2934 else {
2935 hPrevFont = SelectObject(hDC, font);
2936 created_font = TRUE;
2938 } else {
2939 if (infoPtr->font)
2940 SelectObject(hDC, infoPtr->font);
2943 GetClientRect(infoPtr->hwnd, &clientRect);
2944 region = set_control_clipping(hDC, &clientRect);
2946 bgRect = clientRect;
2947 GetTextMetricsW(hDC, &textMetric);
2948 bgRect.top += (textMetric.tmHeight / 2) - 1;
2950 labelRect = clientRect;
2951 InflateRect(&labelRect, -7, 1);
2952 dtFlags = BUTTON_CalcLayoutRects(infoPtr, hDC, &labelRect, &imageRect, &textRect);
2953 if (dtFlags != (UINT)-1 && !show_image_only(infoPtr))
2955 textRegion = CreateRectRgnIndirect(&textRect);
2956 ExtSelectClipRgn(hDC, textRegion, RGN_DIFF);
2959 style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2960 if (style & BS_PUSHLIKE)
2962 part = BP_PUSHBUTTON;
2964 else
2966 part = BP_GROUPBOX;
2967 GetThemeBackgroundContentRect(theme, hDC, part, state, &bgRect, &contentRect);
2968 ExcludeClipRect(hDC, contentRect.left, contentRect.top, contentRect.right, contentRect.bottom);
2970 if (IsThemeBackgroundPartiallyTransparent(theme, part, state))
2971 DrawThemeParentBackground(infoPtr->hwnd, hDC, NULL);
2972 DrawThemeBackground(theme, hDC, part, state, &bgRect, NULL);
2974 if (dtFlags != (UINT)-1)
2976 if (textRegion)
2978 SelectClipRgn(hDC, textRegion);
2979 DeleteObject(textRegion);
2981 FillRect(hDC, &textRect, brush ? brush : GetSysColorBrush(COLOR_BTNFACE));
2982 BUTTON_DrawThemedLabel(infoPtr, hDC, dtFlags, &imageRect, &textRect, theme, part, state);
2985 SelectClipRgn(hDC, region);
2986 if (region) DeleteObject(region);
2987 if (created_font) DeleteObject(font);
2988 if (hPrevFont) SelectObject(hDC, hPrevFont);
2991 static void SB_ThemedPaint(HTHEME theme, const BUTTON_INFO *infoPtr, HDC hDC, int state, UINT dtFlags, BOOL focused)
2993 RECT rc, content_rect, push_rect, dropdown_rect, focus_rect, label_rect, image_rect, text_rect;
2994 NMCUSTOMDRAW nmcd;
2995 LRESULT cdrf;
2996 HWND parent;
2998 if (infoPtr->font) SelectObject(hDC, infoPtr->font);
3000 GetClientRect(infoPtr->hwnd, &rc);
3001 init_custom_draw(&nmcd, infoPtr, hDC, &rc);
3003 parent = GetParent(infoPtr->hwnd);
3004 if (!parent) parent = infoPtr->hwnd;
3006 /* Send erase notifications */
3007 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
3008 if (cdrf & CDRF_SKIPDEFAULT) return;
3010 if (IsThemeBackgroundPartiallyTransparent(theme, BP_PUSHBUTTON, state))
3011 DrawThemeParentBackground(infoPtr->hwnd, hDC, NULL);
3013 /* The zone outside the content is ignored for the dropdown (draws over) */
3014 GetThemeBackgroundContentRect(theme, hDC, BP_PUSHBUTTON, state, &rc, &content_rect);
3015 get_split_button_rects(infoPtr, &rc, &push_rect, &dropdown_rect);
3017 if (infoPtr->split_style & BCSS_NOSPLIT)
3019 push_rect = rc;
3020 DrawThemeBackground(theme, hDC, BP_PUSHBUTTON, state, &rc, NULL);
3021 GetThemeBackgroundContentRect(theme, hDC, BP_PUSHBUTTON, state, &push_rect, &focus_rect);
3023 else
3025 RECT r = { dropdown_rect.left, content_rect.top, dropdown_rect.right, content_rect.bottom };
3026 UINT edge = (infoPtr->split_style & BCSS_ALIGNLEFT) ? BF_RIGHT : BF_LEFT;
3027 const RECT *clip = NULL;
3029 /* If only the dropdown is pressed, we need to draw it separately */
3030 if (state != PBS_PRESSED && (infoPtr->state & BST_DROPDOWNPUSHED))
3032 DrawThemeBackground(theme, hDC, BP_PUSHBUTTON, PBS_PRESSED, &rc, &dropdown_rect);
3033 clip = &push_rect;
3035 DrawThemeBackground(theme, hDC, BP_PUSHBUTTON, state, &rc, clip);
3037 /* Draw the separator */
3038 DrawThemeEdge(theme, hDC, BP_PUSHBUTTON, state, &r, EDGE_ETCHED, edge, NULL);
3040 /* The content rect should be the content area of the push button */
3041 GetThemeBackgroundContentRect(theme, hDC, BP_PUSHBUTTON, state, &push_rect, &content_rect);
3042 focus_rect = content_rect;
3045 if (cdrf & CDRF_NOTIFYPOSTERASE)
3047 nmcd.dwDrawStage = CDDS_POSTERASE;
3048 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
3051 /* Send paint notifications */
3052 nmcd.dwDrawStage = CDDS_PREPAINT;
3053 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
3054 if (cdrf & CDRF_SKIPDEFAULT) return;
3056 if (!(cdrf & CDRF_DOERASE))
3058 COLORREF old_color, color;
3059 INT old_bk_mode;
3061 label_rect = content_rect;
3062 dtFlags = BUTTON_CalcLayoutRects(infoPtr, hDC, &label_rect, &image_rect, &text_rect);
3063 if (dtFlags != (UINT)-1L)
3064 BUTTON_DrawThemedLabel(infoPtr, hDC, dtFlags, &image_rect, &text_rect, theme,
3065 BP_PUSHBUTTON, state);
3067 GetThemeColor(theme, BP_PUSHBUTTON, state, TMT_TEXTCOLOR, &color);
3068 old_bk_mode = SetBkMode(hDC, TRANSPARENT);
3069 old_color = SetTextColor(hDC, color);
3071 draw_split_button_dropdown_glyph(infoPtr, hDC, &dropdown_rect);
3073 SetTextColor(hDC, old_color);
3074 SetBkMode(hDC, old_bk_mode);
3077 if (cdrf & CDRF_NOTIFYPOSTPAINT)
3079 nmcd.dwDrawStage = CDDS_POSTPAINT;
3080 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
3082 if (cdrf & CDRF_SKIPPOSTPAINT) return;
3084 if (focused) DrawFocusRect(hDC, &focus_rect);
3087 static void CL_ThemedPaint(HTHEME theme, const BUTTON_INFO *infoPtr, HDC hDC, int state, UINT dtFlags, BOOL focused)
3089 NMCUSTOMDRAW nmcd;
3090 LRESULT cdrf;
3091 HWND parent;
3092 int part;
3093 RECT rc;
3095 if (infoPtr->font) SelectObject(hDC, infoPtr->font);
3097 GetClientRect(infoPtr->hwnd, &rc);
3098 init_custom_draw(&nmcd, infoPtr, hDC, &rc);
3100 parent = GetParent(infoPtr->hwnd);
3101 if (!parent) parent = infoPtr->hwnd;
3103 /* Send erase notifications */
3104 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
3105 if (cdrf & CDRF_SKIPDEFAULT) return;
3107 part = GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & BS_PUSHLIKE ? BP_PUSHBUTTON : BP_COMMANDLINK;
3108 if (IsThemeBackgroundPartiallyTransparent(theme, part, state))
3109 DrawThemeParentBackground(infoPtr->hwnd, hDC, NULL);
3110 DrawThemeBackground(theme, hDC, part, state, &rc, NULL);
3112 if (cdrf & CDRF_NOTIFYPOSTERASE)
3114 nmcd.dwDrawStage = CDDS_POSTERASE;
3115 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
3118 /* Send paint notifications */
3119 nmcd.dwDrawStage = CDDS_PREPAINT;
3120 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
3121 if (cdrf & CDRF_SKIPDEFAULT) return;
3123 if (!(cdrf & CDRF_DOERASE))
3125 RECT r, img_rect;
3126 UINT txt_h = 0;
3127 SIZE img_size;
3128 WCHAR *text;
3130 GetThemeBackgroundContentRect(theme, hDC, part, state, &rc, &r);
3132 /* The text alignment and styles are fixed and don't depend on button styles */
3133 dtFlags = DT_TOP | DT_LEFT | DT_WORDBREAK;
3135 /* Command Links ignore the margins of the image list or its alignment */
3136 if (infoPtr->u.image || infoPtr->imagelist.himl)
3137 img_size = BUTTON_GetImageSize(infoPtr);
3138 else
3139 GetThemePartSize(theme, NULL, BP_COMMANDLINKGLYPH, state, NULL, TS_DRAW, &img_size);
3141 img_rect = r;
3142 if (img_size.cx) r.left += img_size.cx + command_link_margin;
3144 /* Draw the text */
3145 if ((text = get_button_text(infoPtr)))
3147 UINT len = lstrlenW(text);
3148 RECT text_rect;
3150 GetThemeTextExtent(theme, hDC, part, state, text, len, dtFlags | DT_END_ELLIPSIS, &r,
3151 &text_rect);
3152 DrawThemeText(theme, hDC, part, state, text, len, dtFlags | DT_END_ELLIPSIS, 0, &r);
3154 txt_h = text_rect.bottom - text_rect.top;
3155 Free(text);
3158 /* Draw the note */
3159 if (infoPtr->note)
3161 DTTOPTS opts;
3163 r.top += txt_h;
3164 opts.dwSize = sizeof(opts);
3165 opts.dwFlags = DTT_FONTPROP;
3166 opts.iFontPropId = TMT_BODYFONT;
3167 DrawThemeTextEx(theme, hDC, part, state, infoPtr->note, infoPtr->note_length,
3168 dtFlags | DT_NOPREFIX, &r, &opts);
3171 /* Position the image at the vertical center of the drawn text (not note) */
3172 txt_h = min(txt_h, img_rect.bottom - img_rect.top);
3173 if (img_size.cy < txt_h) img_rect.top += (txt_h - img_size.cy) / 2;
3175 img_rect.right = img_rect.left + img_size.cx;
3176 img_rect.bottom = img_rect.top + img_size.cy;
3178 if (infoPtr->u.image || infoPtr->imagelist.himl)
3179 BUTTON_DrawImage(infoPtr, hDC, NULL,
3180 (state == CMDLS_DISABLED) ? DSS_DISABLED : DSS_NORMAL,
3181 &img_rect);
3182 else
3183 DrawThemeBackground(theme, hDC, BP_COMMANDLINKGLYPH, state, &img_rect, NULL);
3186 if (cdrf & CDRF_NOTIFYPOSTPAINT)
3188 nmcd.dwDrawStage = CDDS_POSTPAINT;
3189 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
3191 if (cdrf & CDRF_SKIPPOSTPAINT) return;
3193 if (focused)
3195 MARGINS margins;
3197 /* The focus rect has margins of a push button rather than command link... */
3198 GetThemeMargins(theme, hDC, BP_PUSHBUTTON, state, TMT_CONTENTMARGINS, NULL, &margins);
3200 rc.left += margins.cxLeftWidth;
3201 rc.top += margins.cyTopHeight;
3202 rc.right -= margins.cxRightWidth;
3203 rc.bottom -= margins.cyBottomHeight;
3204 DrawFocusRect(hDC, &rc);
3208 void BUTTON_Register(void)
3210 WNDCLASSW wndClass;
3212 memset(&wndClass, 0, sizeof(wndClass));
3213 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS | CS_VREDRAW | CS_HREDRAW | CS_PARENTDC;
3214 wndClass.lpfnWndProc = BUTTON_WindowProc;
3215 wndClass.cbClsExtra = 0;
3216 wndClass.cbWndExtra = sizeof(BUTTON_INFO *);
3217 wndClass.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW);
3218 wndClass.hbrBackground = NULL;
3219 wndClass.lpszClassName = WC_BUTTONW;
3220 RegisterClassW(&wndClass);