include: Move InterlockedCompareExchange128 next to the other InterlockedCompareExcha...
[wine.git] / dlls / comctl32 / button.c
blob67bf5ceae4816d1bd63499000ff0e5b46876325a
1 /*
2 * Copyright (C) 1993 Johannes Ruscheinski
3 * Copyright (C) 1993 David Metcalfe
4 * Copyright (C) 1994 Alexandre Julliard
5 * Copyright (C) 2008 by Reece H. Dunn
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
21 * TODO
22 * Styles
23 * - BS_NOTIFY: is it complete?
24 * - BS_RIGHTBUTTON: same as BS_LEFTTEXT
26 * Messages
27 * - WM_CHAR: Checks a (manual or automatic) check box on '+' or '=', clears it on '-' key.
28 * - WM_SETFOCUS: For (manual or automatic) radio buttons, send the parent window BN_CLICKED
29 * - WM_NCCREATE: Turns any BS_OWNERDRAW button into a BS_PUSHBUTTON button.
30 * - WM_SYSKEYUP
32 * Notifications
33 * - BCN_HOTITEMCHANGE
34 * - BN_DISABLE
35 * - BN_PUSHED/BN_HILITE
36 * + BN_KILLFOCUS: is it OK?
37 * - BN_PAINT
38 * + BN_SETFOCUS: is it OK?
39 * - BN_UNPUSHED/BN_UNHILITE
41 * Structures/Macros/Definitions
42 * - NMBCHOTITEM
45 #include <stdarg.h>
46 #include <string.h>
47 #include <stdlib.h>
49 #define OEMRESOURCE
51 #include "windef.h"
52 #include "winbase.h"
53 #include "wingdi.h"
54 #include "winuser.h"
55 #include "uxtheme.h"
56 #include "vssym32.h"
57 #include "wine/debug.h"
58 #include "wine/heap.h"
60 #include "comctl32.h"
62 WINE_DEFAULT_DEBUG_CHANNEL(button);
64 /* undocumented flags */
65 #define BUTTON_NSTATES 0x0F
66 #define BUTTON_BTNPRESSED 0x40
67 #define BUTTON_UNKNOWN2 0x20
68 #define BUTTON_UNKNOWN3 0x10
70 #define BUTTON_NOTIFY_PARENT(hWnd, code) \
71 do { /* Notify parent which has created this button control */ \
72 TRACE("notification " #code " sent to hwnd=%p\n", GetParent(hWnd)); \
73 SendMessageW(GetParent(hWnd), WM_COMMAND, \
74 MAKEWPARAM(GetWindowLongPtrW((hWnd),GWLP_ID), (code)), \
75 (LPARAM)(hWnd)); \
76 } while(0)
78 typedef struct _BUTTON_INFO
80 HWND hwnd;
81 HWND parent;
82 LONG style;
83 LONG state;
84 HFONT font;
85 WCHAR *note;
86 INT note_length;
87 DWORD image_type; /* IMAGE_BITMAP or IMAGE_ICON */
88 BUTTON_IMAGELIST imagelist;
89 UINT split_style;
90 HIMAGELIST glyph; /* this is a font character code when split_style doesn't have BCSS_IMAGE */
91 SIZE glyph_size;
92 RECT text_margin;
93 HANDLE image; /* Original handle set with BM_SETIMAGE and returned with BM_GETIMAGE. */
94 union
96 HICON icon;
97 HBITMAP bitmap;
98 HANDLE image; /* Duplicated handle used for drawing. */
99 } u;
100 } BUTTON_INFO;
102 static UINT BUTTON_CalcLayoutRects( const BUTTON_INFO *infoPtr, HDC hdc, RECT *labelRc, RECT *imageRc, RECT *textRc );
103 static void PB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action );
104 static void CB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action );
105 static void GB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action );
106 static void UB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action );
107 static void OB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action );
108 static void SB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action );
109 static void CL_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action );
110 static void BUTTON_CheckAutoRadioButton( HWND hwnd );
111 static void get_split_button_rects(const BUTTON_INFO*, const RECT*, RECT*, RECT*);
112 static BOOL notify_split_button_dropdown(const BUTTON_INFO*, const POINT*, HWND);
113 static void draw_split_button_dropdown_glyph(const BUTTON_INFO*, HDC, RECT*);
115 #define MAX_BTN_TYPE 16
117 static const WORD maxCheckState[MAX_BTN_TYPE] =
119 BST_UNCHECKED, /* BS_PUSHBUTTON */
120 BST_UNCHECKED, /* BS_DEFPUSHBUTTON */
121 BST_CHECKED, /* BS_CHECKBOX */
122 BST_CHECKED, /* BS_AUTOCHECKBOX */
123 BST_CHECKED, /* BS_RADIOBUTTON */
124 BST_INDETERMINATE, /* BS_3STATE */
125 BST_INDETERMINATE, /* BS_AUTO3STATE */
126 BST_UNCHECKED, /* BS_GROUPBOX */
127 BST_UNCHECKED, /* BS_USERBUTTON */
128 BST_CHECKED, /* BS_AUTORADIOBUTTON */
129 BST_UNCHECKED, /* BS_PUSHBOX */
130 BST_UNCHECKED, /* BS_OWNERDRAW */
131 BST_UNCHECKED, /* BS_SPLITBUTTON */
132 BST_UNCHECKED, /* BS_DEFSPLITBUTTON */
133 BST_UNCHECKED, /* BS_COMMANDLINK */
134 BST_UNCHECKED /* BS_DEFCOMMANDLINK */
137 /* Generic draw states, use get_draw_state() to get specific state for button type */
138 enum draw_state
140 STATE_NORMAL,
141 STATE_DISABLED,
142 STATE_HOT,
143 STATE_PRESSED,
144 STATE_DEFAULTED,
145 DRAW_STATE_COUNT
148 typedef void (*pfPaint)( const BUTTON_INFO *infoPtr, HDC hdc, UINT action );
150 static const pfPaint btnPaintFunc[MAX_BTN_TYPE] =
152 PB_Paint, /* BS_PUSHBUTTON */
153 PB_Paint, /* BS_DEFPUSHBUTTON */
154 CB_Paint, /* BS_CHECKBOX */
155 CB_Paint, /* BS_AUTOCHECKBOX */
156 CB_Paint, /* BS_RADIOBUTTON */
157 CB_Paint, /* BS_3STATE */
158 CB_Paint, /* BS_AUTO3STATE */
159 GB_Paint, /* BS_GROUPBOX */
160 UB_Paint, /* BS_USERBUTTON */
161 CB_Paint, /* BS_AUTORADIOBUTTON */
162 NULL, /* BS_PUSHBOX */
163 OB_Paint, /* BS_OWNERDRAW */
164 SB_Paint, /* BS_SPLITBUTTON */
165 SB_Paint, /* BS_DEFSPLITBUTTON */
166 CL_Paint, /* BS_COMMANDLINK */
167 CL_Paint /* BS_DEFCOMMANDLINK */
170 typedef void (*pfThemedPaint)( HTHEME theme, const BUTTON_INFO *infoPtr, HDC hdc, int drawState, UINT dtflags, BOOL focused);
172 static void PB_ThemedPaint( HTHEME theme, const BUTTON_INFO *infoPtr, HDC hdc, int drawState, UINT dtflags, BOOL focused);
173 static void CB_ThemedPaint( HTHEME theme, const BUTTON_INFO *infoPtr, HDC hdc, int drawState, UINT dtflags, BOOL focused);
174 static void GB_ThemedPaint( HTHEME theme, const BUTTON_INFO *infoPtr, HDC hdc, int drawState, UINT dtflags, BOOL focused);
175 static void SB_ThemedPaint( HTHEME theme, const BUTTON_INFO *infoPtr, HDC hdc, int drawState, UINT dtflags, BOOL focused);
176 static void CL_ThemedPaint( HTHEME theme, const BUTTON_INFO *infoPtr, HDC hdc, int drawState, UINT dtflags, BOOL focused);
178 static const pfThemedPaint btnThemedPaintFunc[MAX_BTN_TYPE] =
180 PB_ThemedPaint, /* BS_PUSHBUTTON */
181 PB_ThemedPaint, /* BS_DEFPUSHBUTTON */
182 CB_ThemedPaint, /* BS_CHECKBOX */
183 CB_ThemedPaint, /* BS_AUTOCHECKBOX */
184 CB_ThemedPaint, /* BS_RADIOBUTTON */
185 CB_ThemedPaint, /* BS_3STATE */
186 CB_ThemedPaint, /* BS_AUTO3STATE */
187 GB_ThemedPaint, /* BS_GROUPBOX */
188 NULL, /* BS_USERBUTTON */
189 CB_ThemedPaint, /* BS_AUTORADIOBUTTON */
190 NULL, /* BS_PUSHBOX */
191 NULL, /* BS_OWNERDRAW */
192 SB_ThemedPaint, /* BS_SPLITBUTTON */
193 SB_ThemedPaint, /* BS_DEFSPLITBUTTON */
194 CL_ThemedPaint, /* BS_COMMANDLINK */
195 CL_ThemedPaint /* BS_DEFCOMMANDLINK */
198 typedef BOOL (*pfGetIdealSize)(BUTTON_INFO *infoPtr, SIZE *size);
200 static BOOL PB_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size);
201 static BOOL CB_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size);
202 static BOOL GB_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size);
203 static BOOL SB_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size);
204 static BOOL CL_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size);
206 static const pfGetIdealSize btnGetIdealSizeFunc[MAX_BTN_TYPE] = {
207 PB_GetIdealSize, /* BS_PUSHBUTTON */
208 PB_GetIdealSize, /* BS_DEFPUSHBUTTON */
209 CB_GetIdealSize, /* BS_CHECKBOX */
210 CB_GetIdealSize, /* BS_AUTOCHECKBOX */
211 CB_GetIdealSize, /* BS_RADIOBUTTON */
212 GB_GetIdealSize, /* BS_3STATE */
213 GB_GetIdealSize, /* BS_AUTO3STATE */
214 GB_GetIdealSize, /* BS_GROUPBOX */
215 PB_GetIdealSize, /* BS_USERBUTTON */
216 CB_GetIdealSize, /* BS_AUTORADIOBUTTON */
217 GB_GetIdealSize, /* BS_PUSHBOX */
218 GB_GetIdealSize, /* BS_OWNERDRAW */
219 SB_GetIdealSize, /* BS_SPLITBUTTON */
220 SB_GetIdealSize, /* BS_DEFSPLITBUTTON */
221 CL_GetIdealSize, /* BS_COMMANDLINK */
222 CL_GetIdealSize /* BS_DEFCOMMANDLINK */
225 /* Fixed margin for command links, regardless of DPI (based on tests done on Windows) */
226 enum { command_link_margin = 6 };
228 /* The width and height for the default command link glyph (when there's no image) */
229 enum { command_link_defglyph_size = 17 };
231 static inline UINT get_button_type( LONG window_style )
233 return (window_style & BS_TYPEMASK);
236 static inline BOOL button_centers_text( LONG window_style )
238 /* Push button's text is centered by default, same for split buttons */
239 UINT type = get_button_type(window_style);
240 return type <= BS_DEFPUSHBUTTON || type == BS_SPLITBUTTON || type == BS_DEFSPLITBUTTON;
243 /* paint a button of any type */
244 static inline void paint_button( BUTTON_INFO *infoPtr, LONG style, UINT action )
246 if (btnPaintFunc[style] && IsWindowVisible(infoPtr->hwnd))
248 HDC hdc = GetDC( infoPtr->hwnd );
249 btnPaintFunc[style]( infoPtr, hdc, action );
250 ReleaseDC( infoPtr->hwnd, hdc );
254 /* retrieve the button text; returned buffer must be freed by caller */
255 static inline WCHAR *get_button_text( const BUTTON_INFO *infoPtr )
257 INT len = GetWindowTextLengthW( infoPtr->hwnd );
258 WCHAR *buffer = heap_alloc( (len + 1) * sizeof(WCHAR) );
259 if (buffer)
260 GetWindowTextW( infoPtr->hwnd, buffer, len + 1 );
261 return buffer;
264 /* get the default glyph size for split buttons */
265 static LONG get_default_glyph_size(const BUTTON_INFO *infoPtr)
267 if (infoPtr->split_style & BCSS_IMAGE)
269 /* Size it to fit, including the left and right edges */
270 int w, h;
271 if (!ImageList_GetIconSize(infoPtr->glyph, &w, &h)) w = 0;
272 return w + GetSystemMetrics(SM_CXEDGE) * 2;
275 /* The glyph size relies on the default menu font's cell height */
276 return GetSystemMetrics(SM_CYMENUCHECK);
279 static void init_custom_draw(NMCUSTOMDRAW *nmcd, const BUTTON_INFO *infoPtr, HDC hdc, const RECT *rc)
281 nmcd->hdr.hwndFrom = infoPtr->hwnd;
282 nmcd->hdr.idFrom = GetWindowLongPtrW(infoPtr->hwnd, GWLP_ID);
283 nmcd->hdr.code = NM_CUSTOMDRAW;
284 nmcd->hdc = hdc;
285 nmcd->rc = *rc;
286 nmcd->dwDrawStage = CDDS_PREERASE;
287 nmcd->dwItemSpec = 0;
288 nmcd->lItemlParam = 0;
289 nmcd->uItemState = IsWindowEnabled(infoPtr->hwnd) ? 0 : CDIS_DISABLED;
290 if (infoPtr->state & BST_PUSHED) nmcd->uItemState |= CDIS_SELECTED;
291 if (infoPtr->state & BST_FOCUS) nmcd->uItemState |= CDIS_FOCUS;
292 if (infoPtr->state & BST_HOT) nmcd->uItemState |= CDIS_HOT;
293 if (infoPtr->state & BST_INDETERMINATE)
294 nmcd->uItemState |= CDIS_INDETERMINATE;
296 /* Windows doesn't seem to send CDIS_CHECKED (it fails the tests) */
297 /* CDIS_SHOWKEYBOARDCUES is misleading, as the meaning is reversed */
298 /* FIXME: Handle it properly when we support keyboard cues? */
301 HRGN set_control_clipping( HDC hdc, const RECT *rect )
303 RECT rc = *rect;
304 HRGN hrgn = CreateRectRgn( 0, 0, 0, 0 );
306 if (GetClipRgn( hdc, hrgn ) != 1)
308 DeleteObject( hrgn );
309 hrgn = 0;
311 DPtoLP( hdc, (POINT *)&rc, 2 );
312 if (GetLayout( hdc ) & LAYOUT_RTL) /* compensate for the shifting done by IntersectClipRect */
314 rc.left++;
315 rc.right++;
317 IntersectClipRect( hdc, rc.left, rc.top, rc.right, rc.bottom );
318 return hrgn;
321 static WCHAR *heap_strndupW(const WCHAR *src, size_t length)
323 size_t size = (length + 1) * sizeof(WCHAR);
324 WCHAR *dst = heap_alloc(size);
325 if (dst) memcpy(dst, src, size);
326 return dst;
329 /**********************************************************************
330 * Convert button styles to flags used by DrawText.
332 static UINT BUTTON_BStoDT( DWORD style, DWORD ex_style )
334 UINT dtStyle = DT_NOCLIP; /* We use SelectClipRgn to limit output */
336 /* "Convert" pushlike buttons to pushbuttons */
337 if (style & BS_PUSHLIKE)
338 style &= ~BS_TYPEMASK;
340 if (!(style & BS_MULTILINE))
341 dtStyle |= DT_SINGLELINE;
342 else
343 dtStyle |= DT_WORDBREAK;
345 switch (style & BS_CENTER)
347 case BS_LEFT: /* DT_LEFT is 0 */ break;
348 case BS_RIGHT: dtStyle |= DT_RIGHT; break;
349 case BS_CENTER: dtStyle |= DT_CENTER; break;
350 default:
351 if (button_centers_text(style)) dtStyle |= DT_CENTER;
354 if (ex_style & WS_EX_RIGHT) dtStyle = DT_RIGHT | (dtStyle & ~(DT_LEFT | DT_CENTER));
356 /* DrawText ignores vertical alignment for multiline text,
357 * but we use these flags to align label manually.
359 if (get_button_type(style) != BS_GROUPBOX)
361 switch (style & BS_VCENTER)
363 case BS_TOP: /* DT_TOP is 0 */ break;
364 case BS_BOTTOM: dtStyle |= DT_BOTTOM; break;
365 case BS_VCENTER: /* fall through */
366 default: dtStyle |= DT_VCENTER; break;
370 return dtStyle;
373 static int get_draw_state(const BUTTON_INFO *infoPtr)
375 static const int pb_states[DRAW_STATE_COUNT] = { PBS_NORMAL, PBS_DISABLED, PBS_HOT, PBS_PRESSED, PBS_DEFAULTED };
376 static const int cb_states[3][DRAW_STATE_COUNT] =
378 { CBS_UNCHECKEDNORMAL, CBS_UNCHECKEDDISABLED, CBS_UNCHECKEDHOT, CBS_UNCHECKEDPRESSED, CBS_UNCHECKEDNORMAL },
379 { CBS_CHECKEDNORMAL, CBS_CHECKEDDISABLED, CBS_CHECKEDHOT, CBS_CHECKEDPRESSED, CBS_CHECKEDNORMAL },
380 { CBS_MIXEDNORMAL, CBS_MIXEDDISABLED, CBS_MIXEDHOT, CBS_MIXEDPRESSED, CBS_MIXEDNORMAL }
382 static const int rb_states[2][DRAW_STATE_COUNT] =
384 { RBS_UNCHECKEDNORMAL, RBS_UNCHECKEDDISABLED, RBS_UNCHECKEDHOT, RBS_UNCHECKEDPRESSED, RBS_UNCHECKEDNORMAL },
385 { RBS_CHECKEDNORMAL, RBS_CHECKEDDISABLED, RBS_CHECKEDHOT, RBS_CHECKEDPRESSED, RBS_CHECKEDNORMAL }
387 static const int gb_states[DRAW_STATE_COUNT] = { GBS_NORMAL, GBS_DISABLED, GBS_NORMAL, GBS_NORMAL, GBS_NORMAL };
388 LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
389 UINT type = get_button_type(style);
390 int check_state = infoPtr->state & 3;
391 enum draw_state state;
393 if (!IsWindowEnabled(infoPtr->hwnd))
394 state = STATE_DISABLED;
395 else if (infoPtr->state & BST_PUSHED)
396 state = STATE_PRESSED;
397 else if (infoPtr->state & BST_HOT)
398 state = STATE_HOT;
399 else if (infoPtr->state & BST_FOCUS)
400 state = STATE_DEFAULTED;
401 else
402 state = STATE_NORMAL;
404 switch (type)
406 case BS_PUSHBUTTON:
407 case BS_DEFPUSHBUTTON:
408 case BS_USERBUTTON:
409 case BS_SPLITBUTTON:
410 case BS_DEFSPLITBUTTON:
411 case BS_COMMANDLINK:
412 case BS_DEFCOMMANDLINK:
413 return pb_states[state];
414 case BS_CHECKBOX:
415 case BS_AUTOCHECKBOX:
416 return cb_states[check_state][state];
417 case BS_RADIOBUTTON:
418 case BS_3STATE:
419 case BS_AUTO3STATE:
420 case BS_AUTORADIOBUTTON:
421 return rb_states[check_state][state];
422 case BS_GROUPBOX:
423 return gb_states[state];
424 default:
425 WARN("Unsupported button type 0x%08x\n", type);
426 return PBS_NORMAL;
430 static LRESULT CALLBACK BUTTON_WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
432 BUTTON_INFO *infoPtr = (BUTTON_INFO *)GetWindowLongPtrW(hWnd, 0);
433 RECT rect;
434 POINT pt;
435 LONG style = GetWindowLongW( hWnd, GWL_STYLE );
436 UINT btn_type = get_button_type( style );
437 LONG state, new_state;
438 HANDLE oldHbitmap;
439 HTHEME theme;
441 if (!IsWindow( hWnd )) return 0;
443 if (!infoPtr && (uMsg != WM_NCCREATE))
444 return DefWindowProcW(hWnd, uMsg, wParam, lParam);
446 pt.x = (short)LOWORD(lParam);
447 pt.y = (short)HIWORD(lParam);
449 switch (uMsg)
451 case WM_GETDLGCODE:
452 switch(btn_type)
454 case BS_COMMANDLINK:
455 case BS_USERBUTTON:
456 case BS_PUSHBUTTON: return DLGC_BUTTON | DLGC_UNDEFPUSHBUTTON;
457 case BS_DEFCOMMANDLINK:
458 case BS_DEFPUSHBUTTON: return DLGC_BUTTON | DLGC_DEFPUSHBUTTON;
459 case BS_RADIOBUTTON:
460 case BS_AUTORADIOBUTTON: return DLGC_BUTTON | DLGC_RADIOBUTTON;
461 case BS_GROUPBOX: return DLGC_STATIC;
462 case BS_SPLITBUTTON: return DLGC_BUTTON | DLGC_UNDEFPUSHBUTTON | DLGC_WANTARROWS;
463 case BS_DEFSPLITBUTTON: return DLGC_BUTTON | DLGC_DEFPUSHBUTTON | DLGC_WANTARROWS;
464 default: return DLGC_BUTTON;
467 case WM_ENABLE:
468 theme = GetWindowTheme( hWnd );
469 if (theme)
470 RedrawWindow( hWnd, NULL, NULL, RDW_FRAME | RDW_INVALIDATE | RDW_UPDATENOW );
471 else
472 paint_button( infoPtr, btn_type, ODA_DRAWENTIRE );
473 break;
475 case WM_NCCREATE:
477 CREATESTRUCTW *cs = (CREATESTRUCTW *)lParam;
479 infoPtr = heap_alloc_zero( sizeof(*infoPtr) );
480 SetWindowLongPtrW( hWnd, 0, (LONG_PTR)infoPtr );
481 infoPtr->hwnd = hWnd;
482 infoPtr->parent = cs->hwndParent;
483 infoPtr->style = cs->style;
484 infoPtr->split_style = BCSS_STRETCH;
485 infoPtr->glyph = (HIMAGELIST)0x36; /* Marlett down arrow char code */
486 infoPtr->glyph_size.cx = get_default_glyph_size(infoPtr);
487 return DefWindowProcW(hWnd, uMsg, wParam, lParam);
490 case WM_NCDESTROY:
491 SetWindowLongPtrW( hWnd, 0, 0 );
492 if (infoPtr->image_type == IMAGE_BITMAP)
493 DeleteObject(infoPtr->u.bitmap);
494 else if (infoPtr->image_type == IMAGE_ICON)
495 DestroyIcon(infoPtr->u.icon);
496 heap_free(infoPtr->note);
497 heap_free(infoPtr);
498 break;
500 case WM_CREATE:
501 if (btn_type >= MAX_BTN_TYPE)
502 return -1; /* abort */
504 /* XP turns a BS_USERBUTTON into BS_PUSHBUTTON */
505 if (btn_type == BS_USERBUTTON )
507 style = (style & ~BS_TYPEMASK) | BS_PUSHBUTTON;
508 SetWindowLongW( hWnd, GWL_STYLE, style );
510 infoPtr->state = BST_UNCHECKED;
511 OpenThemeData( hWnd, WC_BUTTONW );
512 return 0;
514 case WM_DESTROY:
515 theme = GetWindowTheme( hWnd );
516 CloseThemeData( theme );
517 break;
519 case WM_THEMECHANGED:
520 theme = GetWindowTheme( hWnd );
521 CloseThemeData( theme );
522 OpenThemeData( hWnd, WC_BUTTONW );
523 break;
525 case WM_ERASEBKGND:
526 if (btn_type == BS_OWNERDRAW)
528 HDC hdc = (HDC)wParam;
529 RECT rc;
530 HBRUSH hBrush;
531 HWND parent = GetParent(hWnd);
532 if (!parent) parent = hWnd;
533 hBrush = (HBRUSH)SendMessageW(parent, WM_CTLCOLORBTN, (WPARAM)hdc, (LPARAM)hWnd);
534 if (!hBrush) /* did the app forget to call defwindowproc ? */
535 hBrush = (HBRUSH)DefWindowProcW(parent, WM_CTLCOLORBTN,
536 (WPARAM)hdc, (LPARAM)hWnd);
537 GetClientRect(hWnd, &rc);
538 FillRect(hdc, &rc, hBrush);
540 return 1;
542 case WM_PRINTCLIENT:
543 case WM_PAINT:
545 PAINTSTRUCT ps;
546 HDC hdc;
548 theme = GetWindowTheme( hWnd );
549 hdc = wParam ? (HDC)wParam : BeginPaint( hWnd, &ps );
551 if (theme && btnThemedPaintFunc[btn_type])
553 int drawState = get_draw_state(infoPtr);
554 UINT dtflags = BUTTON_BStoDT(style, GetWindowLongW(hWnd, GWL_EXSTYLE));
556 btnThemedPaintFunc[btn_type](theme, infoPtr, hdc, drawState, dtflags, infoPtr->state & BST_FOCUS);
558 else if (btnPaintFunc[btn_type])
560 int nOldMode = SetBkMode( hdc, OPAQUE );
561 btnPaintFunc[btn_type]( infoPtr, hdc, ODA_DRAWENTIRE );
562 SetBkMode(hdc, nOldMode); /* reset painting mode */
565 if ( !wParam ) EndPaint( hWnd, &ps );
566 break;
569 case WM_KEYDOWN:
570 if (wParam == VK_SPACE)
572 SendMessageW( hWnd, BM_SETSTATE, TRUE, 0 );
573 infoPtr->state |= BUTTON_BTNPRESSED;
574 SetCapture( hWnd );
576 else if (wParam == VK_UP || wParam == VK_DOWN)
578 /* Up and down arrows work on every button, and even with BCSS_NOSPLIT */
579 notify_split_button_dropdown(infoPtr, NULL, hWnd);
581 break;
583 case WM_LBUTTONDBLCLK:
584 if(style & BS_NOTIFY ||
585 btn_type == BS_RADIOBUTTON ||
586 btn_type == BS_USERBUTTON ||
587 btn_type == BS_OWNERDRAW)
589 BUTTON_NOTIFY_PARENT(hWnd, BN_DOUBLECLICKED);
590 break;
592 /* fall through */
593 case WM_LBUTTONDOWN:
594 SetFocus( hWnd );
596 if ((btn_type == BS_SPLITBUTTON || btn_type == BS_DEFSPLITBUTTON) &&
597 !(infoPtr->split_style & BCSS_NOSPLIT) &&
598 notify_split_button_dropdown(infoPtr, &pt, hWnd))
599 break;
601 SetCapture( hWnd );
602 infoPtr->state |= BUTTON_BTNPRESSED;
603 SendMessageW( hWnd, BM_SETSTATE, TRUE, 0 );
604 break;
606 case WM_KEYUP:
607 if (wParam != VK_SPACE)
608 break;
609 /* fall through */
610 case WM_LBUTTONUP:
611 state = infoPtr->state;
612 if (state & BST_DROPDOWNPUSHED)
613 SendMessageW(hWnd, BCM_SETDROPDOWNSTATE, FALSE, 0);
614 if (!(state & BUTTON_BTNPRESSED)) break;
615 infoPtr->state &= BUTTON_NSTATES | BST_HOT;
616 if (!(state & BST_PUSHED))
618 ReleaseCapture();
619 break;
621 SendMessageW( hWnd, BM_SETSTATE, FALSE, 0 );
622 GetClientRect( hWnd, &rect );
623 if (uMsg == WM_KEYUP || PtInRect( &rect, pt ))
625 switch(btn_type)
627 case BS_AUTOCHECKBOX:
628 SendMessageW( hWnd, BM_SETCHECK, !(infoPtr->state & BST_CHECKED), 0 );
629 break;
630 case BS_AUTORADIOBUTTON:
631 SendMessageW( hWnd, BM_SETCHECK, TRUE, 0 );
632 break;
633 case BS_AUTO3STATE:
634 SendMessageW( hWnd, BM_SETCHECK, (infoPtr->state & BST_INDETERMINATE) ? 0 :
635 ((infoPtr->state & 3) + 1), 0 );
636 break;
638 ReleaseCapture();
639 BUTTON_NOTIFY_PARENT(hWnd, BN_CLICKED);
641 else
643 ReleaseCapture();
645 break;
647 case WM_CAPTURECHANGED:
648 TRACE("WM_CAPTURECHANGED %p\n", hWnd);
649 if (hWnd == (HWND)lParam) break;
650 if (infoPtr->state & BUTTON_BTNPRESSED)
652 infoPtr->state &= BUTTON_NSTATES;
653 if (infoPtr->state & BST_PUSHED)
654 SendMessageW( hWnd, BM_SETSTATE, FALSE, 0 );
656 break;
658 case WM_MOUSEMOVE:
660 TRACKMOUSEEVENT mouse_event;
662 mouse_event.cbSize = sizeof(TRACKMOUSEEVENT);
663 mouse_event.dwFlags = TME_QUERY;
664 if (!TrackMouseEvent(&mouse_event) || !(mouse_event.dwFlags & (TME_HOVER | TME_LEAVE)))
666 mouse_event.dwFlags = TME_HOVER | TME_LEAVE;
667 mouse_event.hwndTrack = hWnd;
668 mouse_event.dwHoverTime = 1;
669 TrackMouseEvent(&mouse_event);
672 if ((wParam & MK_LBUTTON) && GetCapture() == hWnd)
674 GetClientRect( hWnd, &rect );
675 SendMessageW( hWnd, BM_SETSTATE, PtInRect(&rect, pt), 0 );
677 break;
680 case WM_MOUSEHOVER:
682 infoPtr->state |= BST_HOT;
683 InvalidateRect( hWnd, NULL, FALSE );
684 break;
687 case WM_MOUSELEAVE:
689 infoPtr->state &= ~BST_HOT;
690 InvalidateRect( hWnd, NULL, FALSE );
691 break;
694 case WM_SETTEXT:
696 /* Clear an old text here as Windows does */
697 if (IsWindowVisible(hWnd))
699 HDC hdc = GetDC(hWnd);
700 HBRUSH hbrush;
701 RECT client, rc;
702 HWND parent = GetParent(hWnd);
703 UINT message = (btn_type == BS_PUSHBUTTON ||
704 btn_type == BS_DEFPUSHBUTTON ||
705 btn_type == BS_USERBUTTON ||
706 btn_type == BS_OWNERDRAW) ?
707 WM_CTLCOLORBTN : WM_CTLCOLORSTATIC;
709 if (!parent) parent = hWnd;
710 hbrush = (HBRUSH)SendMessageW(parent, message,
711 (WPARAM)hdc, (LPARAM)hWnd);
712 if (!hbrush) /* did the app forget to call DefWindowProc ? */
713 hbrush = (HBRUSH)DefWindowProcW(parent, message,
714 (WPARAM)hdc, (LPARAM)hWnd);
716 GetClientRect(hWnd, &client);
717 rc = client;
718 /* FIXME: check other BS_* handlers */
719 if (btn_type == BS_GROUPBOX)
720 InflateRect(&rc, -7, 1); /* GB_Paint does this */
721 BUTTON_CalcLayoutRects(infoPtr, hdc, &rc, NULL, NULL);
722 /* Clip by client rect bounds */
723 if (rc.right > client.right) rc.right = client.right;
724 if (rc.bottom > client.bottom) rc.bottom = client.bottom;
725 FillRect(hdc, &rc, hbrush);
726 ReleaseDC(hWnd, hdc);
729 DefWindowProcW( hWnd, WM_SETTEXT, wParam, lParam );
730 if (btn_type == BS_GROUPBOX) /* Yes, only for BS_GROUPBOX */
731 InvalidateRect( hWnd, NULL, TRUE );
732 else
733 paint_button( infoPtr, btn_type, ODA_DRAWENTIRE );
734 return 1; /* success. FIXME: check text length */
737 case BCM_SETNOTE:
739 WCHAR *note = (WCHAR *)lParam;
740 if (btn_type != BS_COMMANDLINK && btn_type != BS_DEFCOMMANDLINK)
742 SetLastError(ERROR_NOT_SUPPORTED);
743 return FALSE;
746 heap_free(infoPtr->note);
747 if (note)
749 infoPtr->note_length = lstrlenW(note);
750 infoPtr->note = heap_strndupW(note, infoPtr->note_length);
753 if (!note || !infoPtr->note)
755 infoPtr->note_length = 0;
756 infoPtr->note = heap_alloc_zero(sizeof(WCHAR));
759 SetLastError(NO_ERROR);
760 return TRUE;
763 case BCM_GETNOTE:
765 DWORD *size = (DWORD *)wParam;
766 WCHAR *buffer = (WCHAR *)lParam;
767 INT length = 0;
769 if (btn_type != BS_COMMANDLINK && btn_type != BS_DEFCOMMANDLINK)
771 SetLastError(ERROR_NOT_SUPPORTED);
772 return FALSE;
775 if (!buffer || !size || !infoPtr->note)
777 SetLastError(ERROR_INVALID_PARAMETER);
778 return FALSE;
781 if (*size > 0)
783 length = min(*size - 1, infoPtr->note_length);
784 memcpy(buffer, infoPtr->note, length * sizeof(WCHAR));
785 buffer[length] = '\0';
788 if (*size < infoPtr->note_length + 1)
790 *size = infoPtr->note_length + 1;
791 SetLastError(ERROR_INSUFFICIENT_BUFFER);
792 return FALSE;
794 else
796 SetLastError(NO_ERROR);
797 return TRUE;
801 case BCM_GETNOTELENGTH:
803 if (btn_type != BS_COMMANDLINK && btn_type != BS_DEFCOMMANDLINK)
805 SetLastError(ERROR_NOT_SUPPORTED);
806 return 0;
809 return infoPtr->note_length;
812 case WM_SETFONT:
813 infoPtr->font = (HFONT)wParam;
814 if (lParam) InvalidateRect(hWnd, NULL, TRUE);
815 break;
817 case WM_GETFONT:
818 return (LRESULT)infoPtr->font;
820 case WM_SETFOCUS:
821 TRACE("WM_SETFOCUS %p\n",hWnd);
822 infoPtr->state |= BST_FOCUS;
824 if (btn_type == BS_OWNERDRAW)
825 paint_button( infoPtr, btn_type, ODA_FOCUS );
826 else
827 InvalidateRect(hWnd, NULL, FALSE);
829 if (style & BS_NOTIFY)
830 BUTTON_NOTIFY_PARENT(hWnd, BN_SETFOCUS);
831 break;
833 case WM_KILLFOCUS:
834 TRACE("WM_KILLFOCUS %p\n",hWnd);
835 infoPtr->state &= ~BST_FOCUS;
837 if ((infoPtr->state & BUTTON_BTNPRESSED) && GetCapture() == hWnd)
838 ReleaseCapture();
839 if (style & BS_NOTIFY)
840 BUTTON_NOTIFY_PARENT(hWnd, BN_KILLFOCUS);
842 InvalidateRect( hWnd, NULL, FALSE );
843 break;
845 case WM_SYSCOLORCHANGE:
846 InvalidateRect( hWnd, NULL, FALSE );
847 break;
849 case BM_SETSTYLE:
851 DWORD new_btn_type;
853 new_btn_type= wParam & BS_TYPEMASK;
854 if (btn_type >= BS_SPLITBUTTON && new_btn_type <= BS_DEFPUSHBUTTON)
855 new_btn_type = (btn_type & ~BS_DEFPUSHBUTTON) | new_btn_type;
857 style = (style & ~BS_TYPEMASK) | new_btn_type;
858 SetWindowLongW( hWnd, GWL_STYLE, style );
860 /* Only redraw if lParam flag is set.*/
861 if (lParam)
862 InvalidateRect( hWnd, NULL, TRUE );
864 break;
866 case BM_CLICK:
867 SendMessageW( hWnd, WM_LBUTTONDOWN, 0, 0 );
868 SendMessageW( hWnd, WM_LBUTTONUP, 0, 0 );
869 break;
871 case BM_SETIMAGE:
872 infoPtr->image_type = (DWORD)wParam;
873 oldHbitmap = infoPtr->image;
874 infoPtr->u.image = CopyImage((HANDLE)lParam, infoPtr->image_type, 0, 0, 0);
875 infoPtr->image = (HANDLE)lParam;
876 InvalidateRect( hWnd, NULL, FALSE );
877 return (LRESULT)oldHbitmap;
879 case BM_GETIMAGE:
880 return (LRESULT)infoPtr->image;
882 case BCM_SETIMAGELIST:
884 BUTTON_IMAGELIST *imagelist = (BUTTON_IMAGELIST *)lParam;
886 if (!imagelist) return FALSE;
888 infoPtr->imagelist = *imagelist;
889 return TRUE;
892 case BCM_GETIMAGELIST:
894 BUTTON_IMAGELIST *imagelist = (BUTTON_IMAGELIST *)lParam;
896 if (!imagelist) return FALSE;
898 *imagelist = infoPtr->imagelist;
899 return TRUE;
902 case BCM_SETSPLITINFO:
904 BUTTON_SPLITINFO *info = (BUTTON_SPLITINFO*)lParam;
906 if (!info) return TRUE;
908 if (info->mask & (BCSIF_GLYPH | BCSIF_IMAGE))
910 infoPtr->split_style &= ~BCSS_IMAGE;
911 if (!(info->mask & BCSIF_GLYPH))
912 infoPtr->split_style |= BCSS_IMAGE;
913 infoPtr->glyph = info->himlGlyph;
914 infoPtr->glyph_size.cx = infoPtr->glyph_size.cy = 0;
917 if (info->mask & BCSIF_STYLE)
918 infoPtr->split_style = info->uSplitStyle;
919 if (info->mask & BCSIF_SIZE)
920 infoPtr->glyph_size = info->size;
922 /* Calculate fitting value for cx if invalid (cy is untouched) */
923 if (infoPtr->glyph_size.cx <= 0)
924 infoPtr->glyph_size.cx = get_default_glyph_size(infoPtr);
926 /* Windows doesn't invalidate or redraw it, so we don't, either */
927 return TRUE;
930 case BCM_GETSPLITINFO:
932 BUTTON_SPLITINFO *info = (BUTTON_SPLITINFO*)lParam;
934 if (!info) return FALSE;
936 if (info->mask & BCSIF_STYLE)
937 info->uSplitStyle = infoPtr->split_style;
938 if (info->mask & (BCSIF_GLYPH | BCSIF_IMAGE))
939 info->himlGlyph = infoPtr->glyph;
940 if (info->mask & BCSIF_SIZE)
941 info->size = infoPtr->glyph_size;
943 return TRUE;
946 case BM_GETCHECK:
947 return infoPtr->state & 3;
949 case BM_SETCHECK:
950 if (wParam > maxCheckState[btn_type]) wParam = maxCheckState[btn_type];
951 if ((btn_type == BS_RADIOBUTTON) || (btn_type == BS_AUTORADIOBUTTON))
953 style = wParam ? style | WS_TABSTOP : style & ~WS_TABSTOP;
954 SetWindowLongW( hWnd, GWL_STYLE, style );
956 if ((infoPtr->state & 3) != wParam)
958 infoPtr->state = (infoPtr->state & ~3) | wParam;
959 InvalidateRect( hWnd, NULL, FALSE );
961 if ((btn_type == BS_AUTORADIOBUTTON) && (wParam == BST_CHECKED) && (style & WS_CHILD))
962 BUTTON_CheckAutoRadioButton( hWnd );
963 break;
965 case BM_GETSTATE:
966 return infoPtr->state;
968 case BM_SETSTATE:
969 state = infoPtr->state;
970 new_state = wParam ? BST_PUSHED : 0;
972 if ((state ^ new_state) & BST_PUSHED)
974 if (wParam)
975 state |= BST_PUSHED;
976 else
977 state &= ~BST_PUSHED;
979 if (btn_type == BS_USERBUTTON)
980 BUTTON_NOTIFY_PARENT( hWnd, (state & BST_PUSHED) ? BN_HILITE : BN_UNHILITE );
981 infoPtr->state = state;
983 InvalidateRect( hWnd, NULL, FALSE );
985 break;
987 case BCM_SETDROPDOWNSTATE:
988 new_state = wParam ? BST_DROPDOWNPUSHED : 0;
990 if ((infoPtr->state ^ new_state) & BST_DROPDOWNPUSHED)
992 infoPtr->state &= ~BST_DROPDOWNPUSHED;
993 infoPtr->state |= new_state;
994 InvalidateRect(hWnd, NULL, FALSE);
996 break;
998 case BCM_SETTEXTMARGIN:
1000 RECT *text_margin = (RECT *)lParam;
1002 if (!text_margin) return FALSE;
1004 infoPtr->text_margin = *text_margin;
1005 return TRUE;
1008 case BCM_GETTEXTMARGIN:
1010 RECT *text_margin = (RECT *)lParam;
1012 if (!text_margin) return FALSE;
1014 *text_margin = infoPtr->text_margin;
1015 return TRUE;
1018 case BCM_GETIDEALSIZE:
1020 SIZE *size = (SIZE *)lParam;
1022 if (!size) return FALSE;
1024 return btnGetIdealSizeFunc[btn_type](infoPtr, size);
1027 case WM_NCHITTEST:
1028 if(btn_type == BS_GROUPBOX) return HTTRANSPARENT;
1029 /* fall through */
1030 default:
1031 return DefWindowProcW(hWnd, uMsg, wParam, lParam);
1033 return 0;
1036 /* If maxWidth is zero, rectangle width is unlimited */
1037 static RECT BUTTON_GetTextRect(const BUTTON_INFO *infoPtr, HDC hdc, const WCHAR *text, LONG maxWidth)
1039 LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1040 LONG exStyle = GetWindowLongW(infoPtr->hwnd, GWL_EXSTYLE);
1041 UINT dtStyle = BUTTON_BStoDT(style, exStyle);
1042 HFONT hPrevFont;
1043 RECT rect = {0};
1045 rect.right = maxWidth;
1046 hPrevFont = SelectObject(hdc, infoPtr->font);
1047 /* Calculate height without DT_VCENTER and DT_BOTTOM to get the correct height */
1048 DrawTextW(hdc, text, -1, &rect, (dtStyle & ~(DT_VCENTER | DT_BOTTOM)) | DT_CALCRECT);
1049 if (hPrevFont) SelectObject(hdc, hPrevFont);
1051 return rect;
1054 static BOOL show_image_only(const BUTTON_INFO *infoPtr)
1056 LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1057 return (style & (BS_ICON | BS_BITMAP)) && (infoPtr->u.image || infoPtr->imagelist.himl);
1060 static BOOL show_image_and_text(const BUTTON_INFO *infoPtr)
1062 LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1063 UINT type = get_button_type(style);
1064 return !(style & (BS_ICON | BS_BITMAP))
1065 && ((infoPtr->u.image
1066 && (type == BS_PUSHBUTTON || type == BS_DEFPUSHBUTTON || type == BS_USERBUTTON || type == BS_SPLITBUTTON
1067 || type == BS_DEFSPLITBUTTON || type == BS_COMMANDLINK || type == BS_DEFCOMMANDLINK))
1068 || (infoPtr->imagelist.himl && type != BS_GROUPBOX));
1071 static BOOL show_image(const BUTTON_INFO *infoPtr)
1073 return show_image_only(infoPtr) || show_image_and_text(infoPtr);
1076 /* Get a bounding rectangle that is large enough to contain a image and a text side by side.
1077 * Note: (left,top) of the result rectangle may not be (0,0), offset it by yourself if needed */
1078 static RECT BUTTON_GetBoundingLabelRect(LONG style, const RECT *textRect, const RECT *imageRect)
1080 RECT labelRect;
1081 RECT rect = *imageRect;
1082 INT textWidth = textRect->right - textRect->left;
1083 INT textHeight = textRect->bottom - textRect->top;
1084 INT imageWidth = imageRect->right - imageRect->left;
1085 INT imageHeight = imageRect->bottom - imageRect->top;
1087 if ((style & BS_CENTER) == BS_RIGHT)
1088 OffsetRect(&rect, textWidth, 0);
1089 else if ((style & BS_CENTER) == BS_LEFT)
1090 OffsetRect(&rect, -imageWidth, 0);
1091 else if ((style & BS_VCENTER) == BS_BOTTOM)
1092 OffsetRect(&rect, 0, textHeight);
1093 else if ((style & BS_VCENTER) == BS_TOP)
1094 OffsetRect(&rect, 0, -imageHeight);
1095 else
1096 OffsetRect(&rect, -imageWidth, 0);
1098 UnionRect(&labelRect, textRect, &rect);
1099 return labelRect;
1102 /* Position a rectangle inside a bounding rectangle according to button alignment flags */
1103 static void BUTTON_PositionRect(LONG style, const RECT *outerRect, RECT *innerRect, const RECT *margin)
1105 INT width = innerRect->right - innerRect->left;
1106 INT height = innerRect->bottom - innerRect->top;
1108 if ((style & WS_EX_RIGHT) && !(style & BS_CENTER)) style |= BS_CENTER;
1110 if (!(style & BS_CENTER))
1112 if (button_centers_text(style))
1113 style |= BS_CENTER;
1114 else
1115 style |= BS_LEFT;
1118 if (!(style & BS_VCENTER))
1120 /* Group box's text is top aligned by default */
1121 if (get_button_type(style) == BS_GROUPBOX)
1122 style |= BS_TOP;
1125 switch (style & BS_CENTER)
1127 case BS_CENTER:
1128 innerRect->left = outerRect->left + (outerRect->right - outerRect->left - width) / 2;
1129 innerRect->right = innerRect->left + width;
1130 break;
1131 case BS_RIGHT:
1132 innerRect->right = outerRect->right - margin->right;
1133 innerRect->left = innerRect->right - width;
1134 break;
1135 case BS_LEFT:
1136 default:
1137 innerRect->left = outerRect->left + margin->left;
1138 innerRect->right = innerRect->left + width;
1139 break;
1142 switch (style & BS_VCENTER)
1144 case BS_TOP:
1145 innerRect->top = outerRect->top + margin->top;
1146 innerRect->bottom = innerRect->top + height;
1147 break;
1148 case BS_BOTTOM:
1149 innerRect->bottom = outerRect->bottom - margin->bottom;
1150 innerRect->top = innerRect->bottom - height;
1151 break;
1152 case BS_VCENTER:
1153 default:
1154 innerRect->top = outerRect->top + (outerRect->bottom - outerRect->top - height) / 2;
1155 innerRect->bottom = innerRect->top + height;
1156 break;
1160 /* Convert imagelist align style to button align style */
1161 static UINT BUTTON_ILStoBS(UINT align)
1163 switch (align)
1165 case BUTTON_IMAGELIST_ALIGN_TOP:
1166 return BS_CENTER | BS_TOP;
1167 case BUTTON_IMAGELIST_ALIGN_BOTTOM:
1168 return BS_CENTER | BS_BOTTOM;
1169 case BUTTON_IMAGELIST_ALIGN_CENTER:
1170 return BS_CENTER | BS_VCENTER;
1171 case BUTTON_IMAGELIST_ALIGN_RIGHT:
1172 return BS_RIGHT | BS_VCENTER;
1173 case BUTTON_IMAGELIST_ALIGN_LEFT:
1174 default:
1175 return BS_LEFT | BS_VCENTER;
1179 static SIZE BUTTON_GetImageSize(const BUTTON_INFO *infoPtr)
1181 ICONINFO iconInfo;
1182 BITMAP bm = {0};
1183 SIZE size = {0};
1185 /* ImageList has priority over image */
1186 if (infoPtr->imagelist.himl)
1187 ImageList_GetIconSize(infoPtr->imagelist.himl, &size.cx, &size.cy);
1188 else if (infoPtr->u.image)
1190 if (infoPtr->image_type == IMAGE_ICON)
1192 GetIconInfo(infoPtr->u.icon, &iconInfo);
1193 GetObjectW(iconInfo.hbmColor, sizeof(bm), &bm);
1194 DeleteObject(iconInfo.hbmColor);
1195 DeleteObject(iconInfo.hbmMask);
1197 else if (infoPtr->image_type == IMAGE_BITMAP)
1198 GetObjectW(infoPtr->u.bitmap, sizeof(bm), &bm);
1200 size.cx = bm.bmWidth;
1201 size.cy = bm.bmHeight;
1204 return size;
1207 static const RECT *BUTTON_GetTextMargin(const BUTTON_INFO *infoPtr)
1209 static const RECT oneMargin = {1, 1, 1, 1};
1211 /* Use text margin only when showing both image and text, and image is not imagelist */
1212 if (show_image_and_text(infoPtr) && !infoPtr->imagelist.himl)
1213 return &infoPtr->text_margin;
1214 else
1215 return &oneMargin;
1218 static void BUTTON_GetClientRectSize(BUTTON_INFO *infoPtr, SIZE *size)
1220 RECT rect;
1221 GetClientRect(infoPtr->hwnd, &rect);
1222 size->cx = rect.right - rect.left;
1223 size->cy = rect.bottom - rect.top;
1226 static void BUTTON_GetTextIdealSize(BUTTON_INFO *infoPtr, LONG maxWidth, SIZE *size)
1228 WCHAR *text = get_button_text(infoPtr);
1229 HDC hdc;
1230 RECT rect;
1231 const RECT *margin = BUTTON_GetTextMargin(infoPtr);
1233 if (maxWidth != 0)
1235 maxWidth -= margin->right + margin->right;
1236 if (maxWidth <= 0) maxWidth = 1;
1239 hdc = GetDC(infoPtr->hwnd);
1240 rect = BUTTON_GetTextRect(infoPtr, hdc, text, maxWidth);
1241 ReleaseDC(infoPtr->hwnd, hdc);
1242 heap_free(text);
1244 size->cx = rect.right - rect.left + margin->left + margin->right;
1245 size->cy = rect.bottom - rect.top + margin->top + margin->bottom;
1248 static void BUTTON_GetLabelIdealSize(BUTTON_INFO *infoPtr, LONG maxWidth, SIZE *size)
1250 LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1251 SIZE imageSize;
1252 SIZE textSize;
1253 BOOL horizontal;
1255 imageSize = BUTTON_GetImageSize(infoPtr);
1256 if (infoPtr->imagelist.himl)
1258 imageSize.cx += infoPtr->imagelist.margin.left + infoPtr->imagelist.margin.right;
1259 imageSize.cy += infoPtr->imagelist.margin.top + infoPtr->imagelist.margin.bottom;
1260 if (infoPtr->imagelist.uAlign == BUTTON_IMAGELIST_ALIGN_TOP
1261 || infoPtr->imagelist.uAlign == BUTTON_IMAGELIST_ALIGN_BOTTOM)
1262 horizontal = FALSE;
1263 else
1264 horizontal = TRUE;
1266 else
1268 /* horizontal alignment flags has priority over vertical ones if both are specified */
1269 if (!(style & (BS_CENTER | BS_VCENTER)) || ((style & BS_CENTER) && (style & BS_CENTER) != BS_CENTER)
1270 || !(style & BS_VCENTER) || (style & BS_VCENTER) == BS_VCENTER)
1271 horizontal = TRUE;
1272 else
1273 horizontal = FALSE;
1276 if (horizontal)
1278 if (maxWidth != 0)
1280 maxWidth -= imageSize.cx;
1281 if (maxWidth <= 0) maxWidth = 1;
1283 BUTTON_GetTextIdealSize(infoPtr, maxWidth, &textSize);
1284 size->cx = textSize.cx + imageSize.cx;
1285 size->cy = max(textSize.cy, imageSize.cy);
1287 else
1289 BUTTON_GetTextIdealSize(infoPtr, maxWidth, &textSize);
1290 size->cx = max(textSize.cx, imageSize.cx);
1291 size->cy = textSize.cy + imageSize.cy;
1295 static BOOL GB_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size)
1297 BUTTON_GetClientRectSize(infoPtr, size);
1298 return TRUE;
1301 static BOOL CB_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size)
1303 LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1304 HDC hdc;
1305 HFONT hfont;
1306 SIZE labelSize;
1307 INT textOffset;
1308 double scaleX;
1309 double scaleY;
1310 LONG checkboxWidth, checkboxHeight;
1311 LONG maxWidth = 0;
1313 if (SendMessageW(infoPtr->hwnd, WM_GETTEXTLENGTH, 0, 0) == 0)
1315 BUTTON_GetClientRectSize(infoPtr, size);
1316 return TRUE;
1319 hdc = GetDC(infoPtr->hwnd);
1320 scaleX = GetDeviceCaps(hdc, LOGPIXELSX) / 96.0;
1321 scaleY = GetDeviceCaps(hdc, LOGPIXELSY) / 96.0;
1322 if ((hfont = infoPtr->font)) SelectObject(hdc, hfont);
1323 GetCharWidthW(hdc, '0', '0', &textOffset);
1324 textOffset /= 2;
1325 ReleaseDC(infoPtr->hwnd, hdc);
1327 checkboxWidth = 12 * scaleX + 1;
1328 checkboxHeight = 12 * scaleY + 1;
1329 if (size->cx)
1331 maxWidth = size->cx - checkboxWidth - textOffset;
1332 if (maxWidth <= 0) maxWidth = 1;
1335 /* Checkbox doesn't support both image(but not image list) and text */
1336 if (!(style & (BS_ICON | BS_BITMAP)) && infoPtr->u.image)
1337 BUTTON_GetTextIdealSize(infoPtr, maxWidth, &labelSize);
1338 else
1339 BUTTON_GetLabelIdealSize(infoPtr, maxWidth, &labelSize);
1341 size->cx = labelSize.cx + checkboxWidth + textOffset;
1342 size->cy = max(labelSize.cy, checkboxHeight);
1344 return TRUE;
1347 static BOOL PB_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size)
1349 SIZE labelSize;
1351 if (SendMessageW(infoPtr->hwnd, WM_GETTEXTLENGTH, 0, 0) == 0)
1352 BUTTON_GetClientRectSize(infoPtr, size);
1353 else
1355 /* Ideal size include text size even if image only flags(BS_ICON, BS_BITMAP) are specified */
1356 BUTTON_GetLabelIdealSize(infoPtr, size->cx, &labelSize);
1358 size->cx = labelSize.cx;
1359 size->cy = labelSize.cy;
1361 return TRUE;
1364 static BOOL SB_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size)
1366 LONG extra_width = infoPtr->glyph_size.cx * 2 + GetSystemMetrics(SM_CXEDGE);
1367 SIZE label_size;
1369 if (SendMessageW(infoPtr->hwnd, WM_GETTEXTLENGTH, 0, 0) == 0)
1371 BUTTON_GetClientRectSize(infoPtr, size);
1372 size->cx = max(size->cx, extra_width);
1374 else
1376 BUTTON_GetLabelIdealSize(infoPtr, size->cx, &label_size);
1377 size->cx = label_size.cx + ((size->cx == 0) ? extra_width : 0);
1378 size->cy = label_size.cy;
1380 return TRUE;
1383 static BOOL CL_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size)
1385 HTHEME theme = GetWindowTheme(infoPtr->hwnd);
1386 HDC hdc = GetDC(infoPtr->hwnd);
1387 LONG w, text_w = 0, text_h = 0;
1388 UINT flags = DT_TOP | DT_LEFT;
1389 HFONT font, old_font = NULL;
1390 RECT text_bound = { 0 };
1391 SIZE img_size;
1392 RECT margin;
1393 WCHAR *text;
1395 /* Get the image size */
1396 if (infoPtr->u.image || infoPtr->imagelist.himl)
1397 img_size = BUTTON_GetImageSize(infoPtr);
1398 else
1400 if (theme)
1401 GetThemePartSize(theme, NULL, BP_COMMANDLINKGLYPH, CMDLS_NORMAL, NULL, TS_DRAW, &img_size);
1402 else
1403 img_size.cx = img_size.cy = command_link_defglyph_size;
1406 /* Get the content margins */
1407 if (theme)
1409 RECT r = { 0, 0, 0xffff, 0xffff };
1410 GetThemeBackgroundContentRect(theme, hdc, BP_COMMANDLINK, CMDLS_NORMAL, &r, &margin);
1411 margin.left -= r.left;
1412 margin.top -= r.top;
1413 margin.right = r.right - margin.right;
1414 margin.bottom = r.bottom - margin.bottom;
1416 else
1418 margin.left = margin.right = command_link_margin;
1419 margin.top = margin.bottom = command_link_margin;
1422 /* Account for the border margins and the margin between image and text */
1423 w = margin.left + margin.right + (img_size.cx ? (img_size.cx + command_link_margin) : 0);
1425 /* If a rectangle with a specific width was requested, bound the text to it */
1426 if (size->cx > w)
1428 text_bound.right = size->cx - w;
1429 flags |= DT_WORDBREAK;
1432 if (theme)
1434 if (infoPtr->font) old_font = SelectObject(hdc, infoPtr->font);
1436 /* Find the text's rect */
1437 if ((text = get_button_text(infoPtr)))
1439 RECT r;
1440 GetThemeTextExtent(theme, hdc, BP_COMMANDLINK, CMDLS_NORMAL,
1441 text, -1, flags, &text_bound, &r);
1442 heap_free(text);
1443 text_w = r.right - r.left;
1444 text_h = r.bottom - r.top;
1447 /* Find the note's rect */
1448 if (infoPtr->note)
1450 DTTOPTS opts;
1452 opts.dwSize = sizeof(opts);
1453 opts.dwFlags = DTT_FONTPROP | DTT_CALCRECT;
1454 opts.iFontPropId = TMT_BODYFONT;
1455 DrawThemeTextEx(theme, hdc, BP_COMMANDLINK, CMDLS_NORMAL,
1456 infoPtr->note, infoPtr->note_length,
1457 flags | DT_NOPREFIX | DT_CALCRECT, &text_bound, &opts);
1458 text_w = max(text_w, text_bound.right - text_bound.left);
1459 text_h += text_bound.bottom - text_bound.top;
1462 else
1464 NONCLIENTMETRICSW ncm;
1466 ncm.cbSize = sizeof(ncm);
1467 if (SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, sizeof(ncm), &ncm, 0))
1469 LONG note_weight = ncm.lfMessageFont.lfWeight;
1471 /* Find the text's rect */
1472 ncm.lfMessageFont.lfWeight = FW_BOLD;
1473 if ((font = CreateFontIndirectW(&ncm.lfMessageFont)))
1475 if ((text = get_button_text(infoPtr)))
1477 RECT r = text_bound;
1478 old_font = SelectObject(hdc, font);
1479 DrawTextW(hdc, text, -1, &r, flags | DT_CALCRECT);
1480 heap_free(text);
1482 text_w = r.right - r.left;
1483 text_h = r.bottom - r.top;
1485 DeleteObject(font);
1488 /* Find the note's rect */
1489 ncm.lfMessageFont.lfWeight = note_weight;
1490 if (infoPtr->note && (font = CreateFontIndirectW(&ncm.lfMessageFont)))
1492 HFONT tmp = SelectObject(hdc, font);
1493 if (!old_font) old_font = tmp;
1495 DrawTextW(hdc, infoPtr->note, infoPtr->note_length, &text_bound,
1496 flags | DT_NOPREFIX | DT_CALCRECT);
1497 DeleteObject(font);
1499 text_w = max(text_w, text_bound.right - text_bound.left);
1500 text_h += text_bound.bottom - text_bound.top + 2;
1504 w += text_w;
1506 size->cx = min(size->cx, w);
1507 size->cy = max(text_h, img_size.cy) + margin.top + margin.bottom;
1509 if (old_font) SelectObject(hdc, old_font);
1510 ReleaseDC(infoPtr->hwnd, hdc);
1511 return TRUE;
1514 /**********************************************************************
1515 * BUTTON_CalcLayoutRects
1517 * Calculates the rectangles of the button label(image and text) and its parts depending on a button's style.
1519 * Returns flags to be passed to DrawText.
1520 * Calculated rectangle doesn't take into account button state
1521 * (pushed, etc.). If there is nothing to draw (no text/image) output
1522 * rectangle is empty, and return value is (UINT)-1.
1524 * PARAMS:
1525 * infoPtr [I] Button pointer
1526 * hdc [I] Handle to device context to draw to
1527 * labelRc [I/O] Input the rect the label to be positioned in, and output the label rect
1528 * imageRc [O] Optional, output the image rect
1529 * textRc [O] Optional, output the text rect
1531 static UINT BUTTON_CalcLayoutRects(const BUTTON_INFO *infoPtr, HDC hdc, RECT *labelRc, RECT *imageRc, RECT *textRc)
1533 LONG style = GetWindowLongW( infoPtr->hwnd, GWL_STYLE );
1534 LONG ex_style = GetWindowLongW( infoPtr->hwnd, GWL_EXSTYLE );
1535 LONG split_style = infoPtr->imagelist.himl ? BUTTON_ILStoBS(infoPtr->imagelist.uAlign) : style;
1536 WCHAR *text = get_button_text(infoPtr);
1537 SIZE imageSize = BUTTON_GetImageSize(infoPtr);
1538 UINT dtStyle = BUTTON_BStoDT(style, ex_style);
1539 RECT labelRect, imageRect, imageRectWithMargin, textRect;
1540 LONG imageMarginWidth, imageMarginHeight;
1541 const RECT *textMargin = BUTTON_GetTextMargin(infoPtr);
1542 RECT emptyMargin = {0};
1543 LONG maxTextWidth;
1545 /* Calculate label rectangle according to label type */
1546 if ((imageSize.cx == 0 && imageSize.cy == 0) && (text == NULL || text[0] == '\0'))
1548 SetRectEmpty(labelRc);
1549 SetRectEmpty(imageRc);
1550 SetRectEmpty(textRc);
1551 heap_free(text);
1552 return (UINT)-1;
1555 SetRect(&imageRect, 0, 0, imageSize.cx, imageSize.cy);
1556 imageRectWithMargin = imageRect;
1557 if (infoPtr->imagelist.himl)
1559 imageRectWithMargin.top -= infoPtr->imagelist.margin.top;
1560 imageRectWithMargin.bottom += infoPtr->imagelist.margin.bottom;
1561 imageRectWithMargin.left -= infoPtr->imagelist.margin.left;
1562 imageRectWithMargin.right += infoPtr->imagelist.margin.right;
1565 /* Show image only */
1566 if (show_image_only(infoPtr))
1568 BUTTON_PositionRect(style, labelRc, &imageRect,
1569 infoPtr->imagelist.himl ? &infoPtr->imagelist.margin : &emptyMargin);
1570 labelRect = imageRect;
1571 SetRectEmpty(&textRect);
1573 else
1575 /* Get text rect */
1576 maxTextWidth = labelRc->right - labelRc->left;
1577 textRect = BUTTON_GetTextRect(infoPtr, hdc, text, maxTextWidth);
1579 /* Show image and text */
1580 if (show_image_and_text(infoPtr))
1582 RECT boundingLabelRect, boundingImageRect, boundingTextRect;
1584 /* Get label rect */
1585 /* Image list may have different alignment than the button, use the whole rect for label in this case */
1586 if (infoPtr->imagelist.himl)
1587 labelRect = *labelRc;
1588 else
1590 /* Get a label bounding rectangle to position the label in the user specified label rectangle because
1591 * text and image need to align together. */
1592 boundingLabelRect = BUTTON_GetBoundingLabelRect(split_style, &textRect, &imageRectWithMargin);
1593 BUTTON_PositionRect(split_style, labelRc, &boundingLabelRect, &emptyMargin);
1594 labelRect = boundingLabelRect;
1597 /* When imagelist has center align, use the whole rect for imagelist and text */
1598 if(infoPtr->imagelist.himl && infoPtr->imagelist.uAlign == BUTTON_IMAGELIST_ALIGN_CENTER)
1600 boundingImageRect = labelRect;
1601 boundingTextRect = labelRect;
1602 BUTTON_PositionRect(split_style, &boundingImageRect, &imageRect,
1603 infoPtr->imagelist.himl ? &infoPtr->imagelist.margin : &emptyMargin);
1604 /* Text doesn't use imagelist align */
1605 BUTTON_PositionRect(style, &boundingTextRect, &textRect, textMargin);
1607 else
1609 /* Get image rect */
1610 /* Split the label rect to two halves as two bounding rectangles for image and text */
1611 boundingImageRect = labelRect;
1612 imageMarginWidth = imageRectWithMargin.right - imageRectWithMargin.left;
1613 imageMarginHeight = imageRectWithMargin.bottom - imageRectWithMargin.top;
1614 if ((split_style & BS_CENTER) == BS_RIGHT)
1615 boundingImageRect.left = boundingImageRect.right - imageMarginWidth;
1616 else if ((split_style & BS_CENTER) == BS_LEFT)
1617 boundingImageRect.right = boundingImageRect.left + imageMarginWidth;
1618 else if ((split_style & BS_VCENTER) == BS_BOTTOM)
1619 boundingImageRect.top = boundingImageRect.bottom - imageMarginHeight;
1620 else if ((split_style & BS_VCENTER) == BS_TOP)
1621 boundingImageRect.bottom = boundingImageRect.top + imageMarginHeight;
1622 else
1623 boundingImageRect.right = boundingImageRect.left + imageMarginWidth;
1624 BUTTON_PositionRect(split_style, &boundingImageRect, &imageRect,
1625 infoPtr->imagelist.himl ? &infoPtr->imagelist.margin : &emptyMargin);
1627 /* Get text rect */
1628 SubtractRect(&boundingTextRect, &labelRect, &boundingImageRect);
1629 /* Text doesn't use imagelist align */
1630 BUTTON_PositionRect(style, &boundingTextRect, &textRect, textMargin);
1633 /* Show text only */
1634 else
1636 if (get_button_type(style) != BS_GROUPBOX)
1637 BUTTON_PositionRect(style, labelRc, &textRect, textMargin);
1638 else
1639 /* GroupBox is always top aligned */
1640 BUTTON_PositionRect((style & ~BS_VCENTER) | BS_TOP, labelRc, &textRect, textMargin);
1641 labelRect = textRect;
1642 SetRectEmpty(&imageRect);
1645 heap_free(text);
1647 CopyRect(labelRc, &labelRect);
1648 CopyRect(imageRc, &imageRect);
1649 CopyRect(textRc, &textRect);
1651 return dtStyle;
1655 /**********************************************************************
1656 * BUTTON_DrawImage
1658 * Draw the button's image into the specified rectangle.
1660 static void BUTTON_DrawImage(const BUTTON_INFO *infoPtr, HDC hdc, HBRUSH hbr, UINT flags, const RECT *rect)
1662 if (infoPtr->imagelist.himl)
1664 int i = (ImageList_GetImageCount(infoPtr->imagelist.himl) == 1) ? 0 : get_draw_state(infoPtr) - 1;
1666 ImageList_Draw(infoPtr->imagelist.himl, i, hdc, rect->left, rect->top, ILD_NORMAL);
1668 else
1670 switch (infoPtr->image_type)
1672 case IMAGE_ICON:
1673 flags |= DST_ICON;
1674 break;
1675 case IMAGE_BITMAP:
1676 flags |= DST_BITMAP;
1677 break;
1678 default:
1679 return;
1682 DrawStateW(hdc, hbr, NULL, (LPARAM)infoPtr->u.image, 0, rect->left, rect->top,
1683 rect->right - rect->left, rect->bottom - rect->top, flags);
1688 /**********************************************************************
1689 * BUTTON_DrawTextCallback
1691 * Callback function used by DrawStateW function.
1693 static BOOL CALLBACK BUTTON_DrawTextCallback(HDC hdc, LPARAM lp, WPARAM wp, int cx, int cy)
1695 RECT rc;
1697 SetRect(&rc, 0, 0, cx, cy);
1698 DrawTextW(hdc, (LPCWSTR)lp, -1, &rc, (UINT)wp);
1699 return TRUE;
1702 /**********************************************************************
1703 * BUTTON_DrawLabel
1705 * Common function for drawing button label.
1707 * FIXME:
1708 * 1. When BS_SINGLELINE is specified and text contains '\t', '\n' or '\r' in the middle, they are rendered as
1709 * squares now whereas they should be ignored.
1710 * 2. When BS_MULTILINE is specified and text contains space in the middle, the space mistakenly be rendered as newline.
1712 static void BUTTON_DrawLabel(const BUTTON_INFO *infoPtr, HDC hdc, UINT dtFlags, const RECT *imageRect,
1713 const RECT *textRect)
1715 HBRUSH hbr = 0;
1716 UINT flags = IsWindowEnabled(infoPtr->hwnd) ? DSS_NORMAL : DSS_DISABLED;
1717 LONG style = GetWindowLongW( infoPtr->hwnd, GWL_STYLE );
1718 WCHAR *text;
1720 /* FIXME: To draw disabled label in Win31 look-and-feel, we probably
1721 * must use DSS_MONO flag and COLOR_GRAYTEXT brush (or maybe DSS_UNION).
1722 * I don't have Win31 on hand to verify that, so I leave it as is.
1725 if ((style & BS_PUSHLIKE) && (infoPtr->state & BST_INDETERMINATE))
1727 hbr = GetSysColorBrush(COLOR_GRAYTEXT);
1728 flags |= DSS_MONO;
1731 if (show_image(infoPtr)) BUTTON_DrawImage(infoPtr, hdc, hbr, flags, imageRect);
1732 if (show_image_only(infoPtr)) return;
1734 /* DST_COMPLEX -- is 0 */
1735 if (!(text = get_button_text(infoPtr))) return;
1736 DrawStateW(hdc, hbr, BUTTON_DrawTextCallback, (LPARAM)text, dtFlags, textRect->left, textRect->top,
1737 textRect->right - textRect->left, textRect->bottom - textRect->top, flags);
1738 heap_free(text);
1741 /**********************************************************************
1742 * Push Button Functions
1744 static void PB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action )
1746 RECT rc, labelRect, imageRect, textRect;
1747 UINT dtFlags, uState;
1748 HPEN hOldPen, hpen;
1749 HBRUSH hOldBrush;
1750 INT oldBkMode;
1751 COLORREF oldTxtColor;
1752 LRESULT cdrf;
1753 HFONT hFont;
1754 NMCUSTOMDRAW nmcd;
1755 LONG state = infoPtr->state;
1756 LONG style = GetWindowLongW( infoPtr->hwnd, GWL_STYLE );
1757 BOOL pushedState = (state & BST_PUSHED);
1758 HWND parent;
1759 HRGN hrgn;
1761 GetClientRect( infoPtr->hwnd, &rc );
1763 /* Send WM_CTLCOLOR to allow changing the font (the colors are fixed) */
1764 if ((hFont = infoPtr->font)) SelectObject( hDC, hFont );
1765 parent = GetParent(infoPtr->hwnd);
1766 if (!parent) parent = infoPtr->hwnd;
1767 SendMessageW( parent, WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)infoPtr->hwnd );
1769 hrgn = set_control_clipping( hDC, &rc );
1771 hpen = CreatePen( PS_SOLID, 1, GetSysColor(COLOR_WINDOWFRAME));
1772 hOldPen = SelectObject(hDC, hpen);
1773 hOldBrush = SelectObject(hDC,GetSysColorBrush(COLOR_BTNFACE));
1774 oldBkMode = SetBkMode(hDC, TRANSPARENT);
1776 init_custom_draw(&nmcd, infoPtr, hDC, &rc);
1778 /* Send erase notifications */
1779 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
1780 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
1782 if (get_button_type(style) == BS_DEFPUSHBUTTON)
1784 if (action != ODA_FOCUS)
1785 Rectangle(hDC, rc.left, rc.top, rc.right, rc.bottom);
1786 InflateRect( &rc, -1, -1 );
1789 /* Skip the frame drawing if only focus has changed */
1790 if (action != ODA_FOCUS)
1792 uState = DFCS_BUTTONPUSH;
1794 if (style & BS_FLAT)
1795 uState |= DFCS_MONO;
1796 else if (pushedState)
1798 if (get_button_type(style) == BS_DEFPUSHBUTTON )
1799 uState |= DFCS_FLAT;
1800 else
1801 uState |= DFCS_PUSHED;
1804 if (state & (BST_CHECKED | BST_INDETERMINATE))
1805 uState |= DFCS_CHECKED;
1807 DrawFrameControl( hDC, &rc, DFC_BUTTON, uState );
1810 if (cdrf & CDRF_NOTIFYPOSTERASE)
1812 nmcd.dwDrawStage = CDDS_POSTERASE;
1813 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
1816 /* Send paint notifications */
1817 nmcd.dwDrawStage = CDDS_PREPAINT;
1818 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
1819 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
1821 if (!(cdrf & CDRF_DOERASE) && action != ODA_FOCUS)
1823 /* draw button label */
1824 labelRect = rc;
1825 /* Shrink label rect at all sides by 2 so that the content won't touch the surrounding frame */
1826 InflateRect(&labelRect, -2, -2);
1827 dtFlags = BUTTON_CalcLayoutRects(infoPtr, hDC, &labelRect, &imageRect, &textRect);
1829 if (dtFlags != (UINT)-1L)
1831 if (pushedState) OffsetRect(&labelRect, 1, 1);
1833 oldTxtColor = SetTextColor( hDC, GetSysColor(COLOR_BTNTEXT) );
1835 BUTTON_DrawLabel(infoPtr, hDC, dtFlags, &imageRect, &textRect);
1837 SetTextColor( hDC, oldTxtColor );
1841 if (cdrf & CDRF_NOTIFYPOSTPAINT)
1843 nmcd.dwDrawStage = CDDS_POSTPAINT;
1844 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
1846 if ((cdrf & CDRF_SKIPPOSTPAINT) || dtFlags == (UINT)-1L) goto cleanup;
1848 if (action == ODA_FOCUS || (state & BST_FOCUS))
1850 InflateRect( &rc, -2, -2 );
1851 DrawFocusRect( hDC, &rc );
1854 cleanup:
1855 SelectObject( hDC, hOldPen );
1856 SelectObject( hDC, hOldBrush );
1857 SetBkMode(hDC, oldBkMode);
1858 SelectClipRgn( hDC, hrgn );
1859 if (hrgn) DeleteObject( hrgn );
1860 DeleteObject( hpen );
1863 /**********************************************************************
1864 * Check Box & Radio Button Functions
1867 static void CB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action )
1869 RECT rbox, labelRect, imageRect, textRect, client;
1870 HBRUSH hBrush;
1871 int delta, text_offset, checkBoxWidth, checkBoxHeight;
1872 UINT dtFlags;
1873 LRESULT cdrf;
1874 HFONT hFont;
1875 NMCUSTOMDRAW nmcd;
1876 LONG state = infoPtr->state;
1877 LONG style = GetWindowLongW( infoPtr->hwnd, GWL_STYLE );
1878 LONG ex_style = GetWindowLongW( infoPtr->hwnd, GWL_EXSTYLE );
1879 HWND parent;
1880 HRGN hrgn;
1882 if (style & BS_PUSHLIKE)
1884 PB_Paint( infoPtr, hDC, action );
1885 return;
1888 GetClientRect(infoPtr->hwnd, &client);
1889 rbox = labelRect = client;
1891 checkBoxWidth = 12 * GetDpiForWindow( infoPtr->hwnd ) / 96 + 1;
1892 checkBoxHeight = 12 * GetDpiForWindow( infoPtr->hwnd ) / 96 + 1;
1894 if ((hFont = infoPtr->font)) SelectObject( hDC, hFont );
1895 GetCharWidthW( hDC, '0', '0', &text_offset );
1896 text_offset /= 2;
1898 parent = GetParent(infoPtr->hwnd);
1899 if (!parent) parent = infoPtr->hwnd;
1900 hBrush = (HBRUSH)SendMessageW(parent, WM_CTLCOLORSTATIC, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
1901 if (!hBrush) /* did the app forget to call defwindowproc ? */
1902 hBrush = (HBRUSH)DefWindowProcW(parent, WM_CTLCOLORSTATIC, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
1903 hrgn = set_control_clipping( hDC, &client );
1905 if (style & BS_LEFTTEXT || ex_style & WS_EX_RIGHT)
1907 labelRect.right -= checkBoxWidth + text_offset;
1908 rbox.left = rbox.right - checkBoxWidth;
1910 else
1912 labelRect.left += checkBoxWidth + text_offset;
1913 rbox.right = checkBoxWidth;
1916 init_custom_draw(&nmcd, infoPtr, hDC, &client);
1918 /* Send erase notifications */
1919 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
1920 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
1922 /* Since WM_ERASEBKGND does nothing, first prepare background */
1923 if (action == ODA_SELECT) FillRect( hDC, &rbox, hBrush );
1924 if (action == ODA_DRAWENTIRE) FillRect( hDC, &client, hBrush );
1925 if (cdrf & CDRF_NOTIFYPOSTERASE)
1927 nmcd.dwDrawStage = CDDS_POSTERASE;
1928 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
1931 /* Draw label */
1932 client = labelRect;
1933 dtFlags = BUTTON_CalcLayoutRects(infoPtr, hDC, &labelRect, &imageRect, &textRect);
1935 /* Only adjust rbox when rtext is valid */
1936 if (dtFlags != (UINT)-1L)
1938 rbox.top = labelRect.top;
1939 rbox.bottom = labelRect.bottom;
1942 /* Send paint notifications */
1943 nmcd.dwDrawStage = CDDS_PREPAINT;
1944 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
1945 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
1947 /* Draw the check-box bitmap */
1948 if (!(cdrf & CDRF_DOERASE))
1950 if (action == ODA_DRAWENTIRE || action == ODA_SELECT)
1952 UINT flags;
1954 if ((get_button_type(style) == BS_RADIOBUTTON) ||
1955 (get_button_type(style) == BS_AUTORADIOBUTTON)) flags = DFCS_BUTTONRADIO;
1956 else if (state & BST_INDETERMINATE) flags = DFCS_BUTTON3STATE;
1957 else flags = DFCS_BUTTONCHECK;
1959 if (state & (BST_CHECKED | BST_INDETERMINATE)) flags |= DFCS_CHECKED;
1960 if (state & BST_PUSHED) flags |= DFCS_PUSHED;
1961 if (style & WS_DISABLED) flags |= DFCS_INACTIVE;
1963 /* rbox must have the correct height */
1964 delta = rbox.bottom - rbox.top - checkBoxHeight;
1966 if ((style & BS_VCENTER) == BS_TOP)
1968 if (delta > 0)
1969 rbox.bottom = rbox.top + checkBoxHeight;
1970 else
1972 rbox.top -= -delta / 2 + 1;
1973 rbox.bottom = rbox.top + checkBoxHeight;
1976 else if ((style & BS_VCENTER) == BS_BOTTOM)
1978 if (delta > 0)
1979 rbox.top = rbox.bottom - checkBoxHeight;
1980 else
1982 rbox.bottom += -delta / 2 + 1;
1983 rbox.top = rbox.bottom - checkBoxHeight;
1986 else /* Default */
1988 if (delta > 0)
1990 int ofs = delta / 2;
1991 rbox.bottom -= ofs + 1;
1992 rbox.top = rbox.bottom - checkBoxHeight;
1994 else if (delta < 0)
1996 int ofs = -delta / 2;
1997 rbox.top -= ofs + 1;
1998 rbox.bottom = rbox.top + checkBoxHeight;
2002 DrawFrameControl(hDC, &rbox, DFC_BUTTON, flags);
2005 if (dtFlags != (UINT)-1L) /* Something to draw */
2006 if (action == ODA_DRAWENTIRE) BUTTON_DrawLabel(infoPtr, hDC, dtFlags, &imageRect, &textRect);
2009 if (cdrf & CDRF_NOTIFYPOSTPAINT)
2011 nmcd.dwDrawStage = CDDS_POSTPAINT;
2012 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2014 if ((cdrf & CDRF_SKIPPOSTPAINT) || dtFlags == (UINT)-1L) goto cleanup;
2016 /* ... and focus */
2017 if (action == ODA_FOCUS || (state & BST_FOCUS))
2019 labelRect.left--;
2020 labelRect.right++;
2021 IntersectRect(&labelRect, &labelRect, &client);
2022 DrawFocusRect(hDC, &labelRect);
2025 cleanup:
2026 SelectClipRgn( hDC, hrgn );
2027 if (hrgn) DeleteObject( hrgn );
2031 /**********************************************************************
2032 * BUTTON_CheckAutoRadioButton
2034 * hwnd is checked, uncheck every other auto radio button in group
2036 static void BUTTON_CheckAutoRadioButton( HWND hwnd )
2038 HWND parent, sibling, start;
2040 parent = GetParent(hwnd);
2041 /* make sure that starting control is not disabled or invisible */
2042 start = sibling = GetNextDlgGroupItem( parent, hwnd, TRUE );
2045 if (!sibling) break;
2046 if ((hwnd != sibling) &&
2047 ((GetWindowLongW( sibling, GWL_STYLE) & BS_TYPEMASK) == BS_AUTORADIOBUTTON))
2048 SendMessageW( sibling, BM_SETCHECK, BST_UNCHECKED, 0 );
2049 sibling = GetNextDlgGroupItem( parent, sibling, FALSE );
2050 } while (sibling != start);
2054 /**********************************************************************
2055 * Group Box Functions
2058 static void GB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action )
2060 RECT labelRect, imageRect, textRect, rcFrame;
2061 HBRUSH hbr;
2062 HFONT hFont;
2063 UINT dtFlags;
2064 TEXTMETRICW tm;
2065 LONG style = GetWindowLongW( infoPtr->hwnd, GWL_STYLE );
2066 HWND parent;
2067 HRGN hrgn;
2069 if ((hFont = infoPtr->font)) SelectObject( hDC, hFont );
2070 /* GroupBox acts like static control, so it sends CTLCOLORSTATIC */
2071 parent = GetParent(infoPtr->hwnd);
2072 if (!parent) parent = infoPtr->hwnd;
2073 hbr = (HBRUSH)SendMessageW(parent, WM_CTLCOLORSTATIC, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
2074 if (!hbr) /* did the app forget to call defwindowproc ? */
2075 hbr = (HBRUSH)DefWindowProcW(parent, WM_CTLCOLORSTATIC, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
2076 GetClientRect(infoPtr->hwnd, &labelRect);
2077 rcFrame = labelRect;
2078 hrgn = set_control_clipping(hDC, &labelRect);
2080 GetTextMetricsW (hDC, &tm);
2081 rcFrame.top += (tm.tmHeight / 2) - 1;
2082 DrawEdge (hDC, &rcFrame, EDGE_ETCHED, BF_RECT | ((style & BS_FLAT) ? BF_FLAT : 0));
2084 InflateRect(&labelRect, -7, 1);
2085 dtFlags = BUTTON_CalcLayoutRects(infoPtr, hDC, &labelRect, &imageRect, &textRect);
2087 if (dtFlags != (UINT)-1)
2089 /* Because buttons have CS_PARENTDC class style, there is a chance
2090 * that label will be drawn out of client rect.
2091 * But Windows doesn't clip label's rect, so do I.
2094 /* There is 1-pixel margin at the left, right, and bottom */
2095 labelRect.left--;
2096 labelRect.right++;
2097 labelRect.bottom++;
2098 FillRect(hDC, &labelRect, hbr);
2099 labelRect.left++;
2100 labelRect.right--;
2101 labelRect.bottom--;
2103 BUTTON_DrawLabel(infoPtr, hDC, dtFlags, &imageRect, &textRect);
2105 SelectClipRgn( hDC, hrgn );
2106 if (hrgn) DeleteObject( hrgn );
2110 /**********************************************************************
2111 * User Button Functions
2114 static void UB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action )
2116 RECT rc;
2117 HBRUSH hBrush;
2118 LRESULT cdrf;
2119 HFONT hFont;
2120 NMCUSTOMDRAW nmcd;
2121 LONG state = infoPtr->state;
2122 HWND parent;
2124 GetClientRect( infoPtr->hwnd, &rc);
2126 if ((hFont = infoPtr->font)) SelectObject( hDC, hFont );
2128 parent = GetParent(infoPtr->hwnd);
2129 if (!parent) parent = infoPtr->hwnd;
2130 hBrush = (HBRUSH)SendMessageW(parent, WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
2131 if (!hBrush) /* did the app forget to call defwindowproc ? */
2132 hBrush = (HBRUSH)DefWindowProcW(parent, WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
2134 if (action == ODA_FOCUS || (state & BST_FOCUS))
2136 init_custom_draw(&nmcd, infoPtr, hDC, &rc);
2138 /* Send erase notifications */
2139 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2140 if (cdrf & CDRF_SKIPDEFAULT) goto notify;
2143 FillRect( hDC, &rc, hBrush );
2144 if (action == ODA_FOCUS || (state & BST_FOCUS))
2146 if (cdrf & CDRF_NOTIFYPOSTERASE)
2148 nmcd.dwDrawStage = CDDS_POSTERASE;
2149 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2152 /* Send paint notifications */
2153 nmcd.dwDrawStage = CDDS_PREPAINT;
2154 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2155 if (cdrf & CDRF_SKIPDEFAULT) goto notify;
2156 if (cdrf & CDRF_NOTIFYPOSTPAINT)
2158 nmcd.dwDrawStage = CDDS_POSTPAINT;
2159 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2162 if (!(cdrf & CDRF_SKIPPOSTPAINT))
2163 DrawFocusRect( hDC, &rc );
2166 notify:
2167 switch (action)
2169 case ODA_FOCUS:
2170 BUTTON_NOTIFY_PARENT( infoPtr->hwnd, (state & BST_FOCUS) ? BN_SETFOCUS : BN_KILLFOCUS );
2171 break;
2173 case ODA_SELECT:
2174 BUTTON_NOTIFY_PARENT( infoPtr->hwnd, (state & BST_PUSHED) ? BN_HILITE : BN_UNHILITE );
2175 break;
2177 default:
2178 break;
2183 /**********************************************************************
2184 * Ownerdrawn Button Functions
2187 static void OB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action )
2189 LONG state = infoPtr->state;
2190 DRAWITEMSTRUCT dis;
2191 LONG_PTR id = GetWindowLongPtrW( infoPtr->hwnd, GWLP_ID );
2192 HWND parent;
2193 HFONT hFont;
2194 HRGN hrgn;
2196 dis.CtlType = ODT_BUTTON;
2197 dis.CtlID = id;
2198 dis.itemID = 0;
2199 dis.itemAction = action;
2200 dis.itemState = ((state & BST_FOCUS) ? ODS_FOCUS : 0) |
2201 ((state & BST_PUSHED) ? ODS_SELECTED : 0) |
2202 (IsWindowEnabled(infoPtr->hwnd) ? 0: ODS_DISABLED);
2203 dis.hwndItem = infoPtr->hwnd;
2204 dis.hDC = hDC;
2205 dis.itemData = 0;
2206 GetClientRect( infoPtr->hwnd, &dis.rcItem );
2208 if ((hFont = infoPtr->font)) SelectObject( hDC, hFont );
2209 parent = GetParent(infoPtr->hwnd);
2210 if (!parent) parent = infoPtr->hwnd;
2211 SendMessageW( parent, WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)infoPtr->hwnd );
2213 hrgn = set_control_clipping( hDC, &dis.rcItem );
2215 SendMessageW( GetParent(infoPtr->hwnd), WM_DRAWITEM, id, (LPARAM)&dis );
2216 SelectClipRgn( hDC, hrgn );
2217 if (hrgn) DeleteObject( hrgn );
2221 /**********************************************************************
2222 * Split Button Functions
2224 static void SB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action )
2226 LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2227 LONG state = infoPtr->state;
2228 UINT dtFlags = (UINT)-1L;
2230 RECT rc, push_rect, dropdown_rect;
2231 NMCUSTOMDRAW nmcd;
2232 HPEN pen, old_pen;
2233 HBRUSH old_brush;
2234 INT old_bk_mode;
2235 LRESULT cdrf;
2236 HWND parent;
2237 HRGN hrgn;
2239 GetClientRect(infoPtr->hwnd, &rc);
2241 /* Send WM_CTLCOLOR to allow changing the font (the colors are fixed) */
2242 if (infoPtr->font) SelectObject(hDC, infoPtr->font);
2243 if (!(parent = GetParent(infoPtr->hwnd))) parent = infoPtr->hwnd;
2244 SendMessageW(parent, WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
2246 hrgn = set_control_clipping(hDC, &rc);
2248 pen = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_WINDOWFRAME));
2249 old_pen = SelectObject(hDC, pen);
2250 old_brush = SelectObject(hDC, GetSysColorBrush(COLOR_BTNFACE));
2251 old_bk_mode = SetBkMode(hDC, TRANSPARENT);
2253 init_custom_draw(&nmcd, infoPtr, hDC, &rc);
2255 /* Send erase notifications */
2256 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2257 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2259 if (get_button_type(style) == BS_DEFSPLITBUTTON)
2261 if (action != ODA_FOCUS)
2262 Rectangle(hDC, rc.left, rc.top, rc.right, rc.bottom);
2263 InflateRect(&rc, -1, -1);
2264 /* The split will now be off by 1 pixel, but
2265 that's exactly what Windows does as well */
2268 get_split_button_rects(infoPtr, &rc, &push_rect, &dropdown_rect);
2269 if (infoPtr->split_style & BCSS_NOSPLIT)
2270 push_rect = rc;
2272 /* Skip the frame drawing if only focus has changed */
2273 if (action != ODA_FOCUS)
2275 UINT flags = DFCS_BUTTONPUSH;
2277 if (style & BS_FLAT) flags |= DFCS_MONO;
2278 else if (state & BST_PUSHED)
2279 flags |= (get_button_type(style) == BS_DEFSPLITBUTTON)
2280 ? DFCS_FLAT : DFCS_PUSHED;
2282 if (state & (BST_CHECKED | BST_INDETERMINATE))
2283 flags |= DFCS_CHECKED;
2285 if (infoPtr->split_style & BCSS_NOSPLIT)
2286 DrawFrameControl(hDC, &push_rect, DFC_BUTTON, flags);
2287 else
2289 UINT dropdown_flags = flags & ~DFCS_CHECKED;
2291 if (state & BST_DROPDOWNPUSHED)
2292 dropdown_flags = (dropdown_flags & ~DFCS_FLAT) | DFCS_PUSHED;
2294 /* Adjust for shadow and draw order so it looks properly */
2295 if (infoPtr->split_style & BCSS_ALIGNLEFT)
2297 dropdown_rect.right++;
2298 DrawFrameControl(hDC, &dropdown_rect, DFC_BUTTON, dropdown_flags);
2299 dropdown_rect.right--;
2300 DrawFrameControl(hDC, &push_rect, DFC_BUTTON, flags);
2302 else
2304 push_rect.right++;
2305 DrawFrameControl(hDC, &push_rect, DFC_BUTTON, flags);
2306 push_rect.right--;
2307 DrawFrameControl(hDC, &dropdown_rect, DFC_BUTTON, dropdown_flags);
2312 if (cdrf & CDRF_NOTIFYPOSTERASE)
2314 nmcd.dwDrawStage = CDDS_POSTERASE;
2315 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2318 /* Send paint notifications */
2319 nmcd.dwDrawStage = CDDS_PREPAINT;
2320 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2321 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2323 /* Shrink push button rect so that the content won't touch the surrounding frame */
2324 InflateRect(&push_rect, -2, -2);
2326 if (!(cdrf & CDRF_DOERASE) && action != ODA_FOCUS)
2328 COLORREF old_color = SetTextColor(hDC, GetSysColor(COLOR_BTNTEXT));
2329 RECT label_rect = push_rect, image_rect, text_rect;
2331 dtFlags = BUTTON_CalcLayoutRects(infoPtr, hDC, &label_rect, &image_rect, &text_rect);
2333 if (dtFlags != (UINT)-1L)
2334 BUTTON_DrawLabel(infoPtr, hDC, dtFlags, &image_rect, &text_rect);
2336 draw_split_button_dropdown_glyph(infoPtr, hDC, &dropdown_rect);
2337 SetTextColor(hDC, old_color);
2340 if (cdrf & CDRF_NOTIFYPOSTPAINT)
2342 nmcd.dwDrawStage = CDDS_POSTPAINT;
2343 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2345 if ((cdrf & CDRF_SKIPPOSTPAINT) || dtFlags == (UINT)-1L) goto cleanup;
2347 if (action == ODA_FOCUS || (state & BST_FOCUS))
2348 DrawFocusRect(hDC, &push_rect);
2350 cleanup:
2351 SelectObject(hDC, old_pen);
2352 SelectObject(hDC, old_brush);
2353 SetBkMode(hDC, old_bk_mode);
2354 SelectClipRgn(hDC, hrgn);
2355 if (hrgn) DeleteObject(hrgn);
2356 DeleteObject(pen);
2359 /* Given the full button rect of the split button, retrieve the push part and the dropdown part */
2360 static inline void get_split_button_rects(const BUTTON_INFO *infoPtr, const RECT *button_rect,
2361 RECT *push_rect, RECT *dropdown_rect)
2363 *push_rect = *dropdown_rect = *button_rect;
2365 /* The dropdown takes priority if the client rect is too small, it will only have a dropdown */
2366 if (infoPtr->split_style & BCSS_ALIGNLEFT)
2368 dropdown_rect->right = min(button_rect->left + infoPtr->glyph_size.cx, button_rect->right);
2369 push_rect->left = dropdown_rect->right;
2371 else
2373 dropdown_rect->left = max(button_rect->right - infoPtr->glyph_size.cx, button_rect->left);
2374 push_rect->right = dropdown_rect->left;
2378 /* Notify the parent if the point is within the dropdown and return TRUE (always notify if NULL) */
2379 static BOOL notify_split_button_dropdown(const BUTTON_INFO *infoPtr, const POINT *pt, HWND hwnd)
2381 NMBCDROPDOWN nmbcd;
2383 GetClientRect(hwnd, &nmbcd.rcButton);
2384 if (pt)
2386 RECT push_rect, dropdown_rect;
2388 get_split_button_rects(infoPtr, &nmbcd.rcButton, &push_rect, &dropdown_rect);
2389 if (!PtInRect(&dropdown_rect, *pt))
2390 return FALSE;
2392 /* If it's already down (set manually via BCM_SETDROPDOWNSTATE), fake the notify */
2393 if (infoPtr->state & BST_DROPDOWNPUSHED)
2394 return TRUE;
2396 SendMessageW(hwnd, BCM_SETDROPDOWNSTATE, TRUE, 0);
2398 nmbcd.hdr.hwndFrom = hwnd;
2399 nmbcd.hdr.idFrom = GetWindowLongPtrW(hwnd, GWLP_ID);
2400 nmbcd.hdr.code = BCN_DROPDOWN;
2401 SendMessageW(GetParent(hwnd), WM_NOTIFY, nmbcd.hdr.idFrom, (LPARAM)&nmbcd);
2403 SendMessageW(hwnd, BCM_SETDROPDOWNSTATE, FALSE, 0);
2404 return TRUE;
2407 /* Draw the split button dropdown glyph or image */
2408 static void draw_split_button_dropdown_glyph(const BUTTON_INFO *infoPtr, HDC hdc, RECT *rect)
2410 if (infoPtr->split_style & BCSS_IMAGE)
2412 int w, h;
2414 /* When the glyph is an image list, Windows is very buggy with BCSS_STRETCH,
2415 positions it weirdly and doesn't even stretch it, but instead extends the
2416 image, leaking into other images in the list (or black if none). Instead,
2417 we'll ignore this and just position it at center as without BCSS_STRETCH. */
2418 if (!ImageList_GetIconSize(infoPtr->glyph, &w, &h)) return;
2420 ImageList_Draw(infoPtr->glyph,
2421 (ImageList_GetImageCount(infoPtr->glyph) == 1) ? 0 : get_draw_state(infoPtr) - 1,
2422 hdc, rect->left + (rect->right - rect->left - w) / 2,
2423 rect->top + (rect->bottom - rect->top - h) / 2, ILD_NORMAL);
2425 else if (infoPtr->glyph_size.cy >= 0)
2427 /* infoPtr->glyph is a character code from Marlett */
2428 HFONT font, old_font;
2429 LOGFONTW logfont = { 0, 0, 0, 0, FW_NORMAL, 0, 0, 0, SYMBOL_CHARSET, 0, 0, 0, 0,
2430 L"Marlett" };
2431 if (infoPtr->glyph_size.cy)
2433 /* BCSS_STRETCH preserves aspect ratio, uses minimum as size */
2434 if (infoPtr->split_style & BCSS_STRETCH)
2435 logfont.lfHeight = min(infoPtr->glyph_size.cx, infoPtr->glyph_size.cy);
2436 else
2438 logfont.lfWidth = infoPtr->glyph_size.cx;
2439 logfont.lfHeight = infoPtr->glyph_size.cy;
2442 else logfont.lfHeight = infoPtr->glyph_size.cx;
2444 if ((font = CreateFontIndirectW(&logfont)))
2446 old_font = SelectObject(hdc, font);
2447 DrawTextW(hdc, (const WCHAR*)&infoPtr->glyph, 1, rect,
2448 DT_CENTER | DT_VCENTER | DT_SINGLELINE | DT_NOCLIP | DT_NOPREFIX);
2449 SelectObject(hdc, old_font);
2450 DeleteObject(font);
2456 /**********************************************************************
2457 * Command Link Functions
2459 static void CL_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action )
2461 LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2462 LONG state = infoPtr->state;
2464 RECT rc, content_rect;
2465 NMCUSTOMDRAW nmcd;
2466 HPEN pen, old_pen;
2467 HBRUSH old_brush;
2468 INT old_bk_mode;
2469 LRESULT cdrf;
2470 HWND parent;
2471 HRGN hrgn;
2473 GetClientRect(infoPtr->hwnd, &rc);
2475 /* Command Links are not affected by the button's font, and are based
2476 on the default message font. Furthermore, they are not affected by
2477 any of the alignment styles (and always align with the top-left). */
2478 if (!(parent = GetParent(infoPtr->hwnd))) parent = infoPtr->hwnd;
2479 SendMessageW(parent, WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
2481 hrgn = set_control_clipping(hDC, &rc);
2483 pen = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_WINDOWFRAME));
2484 old_pen = SelectObject(hDC, pen);
2485 old_brush = SelectObject(hDC, GetSysColorBrush(COLOR_BTNFACE));
2486 old_bk_mode = SetBkMode(hDC, TRANSPARENT);
2488 init_custom_draw(&nmcd, infoPtr, hDC, &rc);
2490 /* Send erase notifications */
2491 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2492 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2493 content_rect = rc;
2495 if (get_button_type(style) == BS_DEFCOMMANDLINK)
2497 if (action != ODA_FOCUS)
2498 Rectangle(hDC, rc.left, rc.top, rc.right, rc.bottom);
2499 InflateRect(&rc, -1, -1);
2502 /* Skip the frame drawing if only focus has changed */
2503 if (action != ODA_FOCUS)
2505 if (!(state & (BST_HOT | BST_PUSHED | BST_CHECKED | BST_INDETERMINATE)))
2506 FillRect(hDC, &rc, GetSysColorBrush(COLOR_BTNFACE));
2507 else
2509 UINT flags = DFCS_BUTTONPUSH;
2511 if (style & BS_FLAT) flags |= DFCS_MONO;
2512 else if (state & BST_PUSHED) flags |= DFCS_PUSHED;
2514 if (state & (BST_CHECKED | BST_INDETERMINATE))
2515 flags |= DFCS_CHECKED;
2516 DrawFrameControl(hDC, &rc, DFC_BUTTON, flags);
2520 if (cdrf & CDRF_NOTIFYPOSTERASE)
2522 nmcd.dwDrawStage = CDDS_POSTERASE;
2523 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2526 /* Send paint notifications */
2527 nmcd.dwDrawStage = CDDS_PREPAINT;
2528 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2529 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2531 if (!(cdrf & CDRF_DOERASE) && action != ODA_FOCUS)
2533 UINT flags = IsWindowEnabled(infoPtr->hwnd) ? DSS_NORMAL : DSS_DISABLED;
2534 COLORREF old_color = SetTextColor(hDC, GetSysColor(flags == DSS_NORMAL ?
2535 COLOR_BTNTEXT : COLOR_GRAYTEXT));
2536 HIMAGELIST defimg = NULL;
2537 NONCLIENTMETRICSW ncm;
2538 UINT txt_h = 0;
2539 SIZE img_size;
2541 /* Command Links ignore the margins of the image list or its alignment */
2542 if (infoPtr->u.image || infoPtr->imagelist.himl)
2543 img_size = BUTTON_GetImageSize(infoPtr);
2544 else
2546 img_size.cx = img_size.cy = command_link_defglyph_size;
2547 defimg = ImageList_LoadImageW(COMCTL32_hModule, (LPCWSTR)MAKEINTRESOURCE(IDB_CMDLINK),
2548 img_size.cx, 3, CLR_NONE, IMAGE_BITMAP, LR_CREATEDIBSECTION);
2551 /* Shrink rect by the command link margin, except on bottom (just the frame) */
2552 InflateRect(&content_rect, -command_link_margin, -command_link_margin);
2553 content_rect.bottom += command_link_margin - 2;
2555 ncm.cbSize = sizeof(ncm);
2556 if (SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, sizeof(ncm), &ncm, 0))
2558 LONG note_weight = ncm.lfMessageFont.lfWeight;
2559 RECT r = content_rect;
2560 WCHAR *text;
2561 HFONT font;
2563 if (img_size.cx) r.left += img_size.cx + command_link_margin;
2565 /* Draw the text */
2566 ncm.lfMessageFont.lfWeight = FW_BOLD;
2567 if ((font = CreateFontIndirectW(&ncm.lfMessageFont)))
2569 if ((text = get_button_text(infoPtr)))
2571 SelectObject(hDC, font);
2572 txt_h = DrawTextW(hDC, text, -1, &r,
2573 DT_TOP | DT_LEFT | DT_WORDBREAK | DT_END_ELLIPSIS);
2574 heap_free(text);
2576 DeleteObject(font);
2579 /* Draw the note */
2580 ncm.lfMessageFont.lfWeight = note_weight;
2581 if (infoPtr->note && (font = CreateFontIndirectW(&ncm.lfMessageFont)))
2583 r.top += txt_h + 2;
2584 SelectObject(hDC, font);
2585 DrawTextW(hDC, infoPtr->note, infoPtr->note_length, &r,
2586 DT_TOP | DT_LEFT | DT_WORDBREAK | DT_NOPREFIX);
2587 DeleteObject(font);
2591 /* Position the image at the vertical center of the drawn text (not note) */
2592 txt_h = min(txt_h, content_rect.bottom - content_rect.top);
2593 if (img_size.cy < txt_h) content_rect.top += (txt_h - img_size.cy) / 2;
2595 content_rect.right = content_rect.left + img_size.cx;
2596 content_rect.bottom = content_rect.top + img_size.cy;
2598 if (defimg)
2600 int i = 0;
2601 if (flags == DSS_DISABLED) i = 2;
2602 else if (state & BST_HOT) i = 1;
2604 ImageList_Draw(defimg, i, hDC, content_rect.left, content_rect.top, ILD_NORMAL);
2605 ImageList_Destroy(defimg);
2607 else
2608 BUTTON_DrawImage(infoPtr, hDC, NULL, flags, &content_rect);
2610 SetTextColor(hDC, old_color);
2613 if (cdrf & CDRF_NOTIFYPOSTPAINT)
2615 nmcd.dwDrawStage = CDDS_POSTPAINT;
2616 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2618 if (cdrf & CDRF_SKIPPOSTPAINT) goto cleanup;
2620 if (action == ODA_FOCUS || (state & BST_FOCUS))
2622 InflateRect(&rc, -2, -2);
2623 DrawFocusRect(hDC, &rc);
2626 cleanup:
2627 SelectObject(hDC, old_pen);
2628 SelectObject(hDC, old_brush);
2629 SetBkMode(hDC, old_bk_mode);
2630 SelectClipRgn(hDC, hrgn);
2631 if (hrgn) DeleteObject(hrgn);
2632 DeleteObject(pen);
2636 /**********************************************************************
2637 * Themed Paint Functions
2639 static void PB_ThemedPaint(HTHEME theme, const BUTTON_INFO *infoPtr, HDC hDC, int state, UINT dtFlags, BOOL focused)
2641 RECT bgRect, textRect;
2642 HFONT font = infoPtr->font;
2643 HFONT hPrevFont = font ? SelectObject(hDC, font) : NULL;
2644 NMCUSTOMDRAW nmcd;
2645 LRESULT cdrf;
2646 HWND parent;
2647 WCHAR *text;
2649 GetClientRect(infoPtr->hwnd, &bgRect);
2650 GetThemeBackgroundContentRect(theme, hDC, BP_PUSHBUTTON, state, &bgRect, &textRect);
2651 init_custom_draw(&nmcd, infoPtr, hDC, &bgRect);
2653 parent = GetParent(infoPtr->hwnd);
2654 if (!parent) parent = infoPtr->hwnd;
2656 /* Send erase notifications */
2657 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2658 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2660 if (IsThemeBackgroundPartiallyTransparent(theme, BP_PUSHBUTTON, state))
2661 DrawThemeParentBackground(infoPtr->hwnd, hDC, NULL);
2662 DrawThemeBackground(theme, hDC, BP_PUSHBUTTON, state, &bgRect, NULL);
2664 if (cdrf & CDRF_NOTIFYPOSTERASE)
2666 nmcd.dwDrawStage = CDDS_POSTERASE;
2667 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2670 /* Send paint notifications */
2671 nmcd.dwDrawStage = CDDS_PREPAINT;
2672 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2673 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2675 if (!(cdrf & CDRF_DOERASE) && (text = get_button_text(infoPtr)))
2677 DrawThemeText(theme, hDC, BP_PUSHBUTTON, state, text, lstrlenW(text), dtFlags, 0, &textRect);
2678 heap_free(text);
2681 if (cdrf & CDRF_NOTIFYPOSTPAINT)
2683 nmcd.dwDrawStage = CDDS_POSTPAINT;
2684 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2686 if (cdrf & CDRF_SKIPPOSTPAINT) goto cleanup;
2688 if (focused)
2690 MARGINS margins;
2691 RECT focusRect = bgRect;
2693 GetThemeMargins(theme, hDC, BP_PUSHBUTTON, state, TMT_CONTENTMARGINS, NULL, &margins);
2695 focusRect.left += margins.cxLeftWidth;
2696 focusRect.top += margins.cyTopHeight;
2697 focusRect.right -= margins.cxRightWidth;
2698 focusRect.bottom -= margins.cyBottomHeight;
2700 DrawFocusRect( hDC, &focusRect );
2703 cleanup:
2704 if (hPrevFont) SelectObject(hDC, hPrevFont);
2707 static void CB_ThemedPaint(HTHEME theme, const BUTTON_INFO *infoPtr, HDC hDC, int state, UINT dtFlags, BOOL focused)
2709 SIZE sz;
2710 RECT bgRect, textRect;
2711 HFONT font, hPrevFont = NULL;
2712 DWORD dwStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2713 UINT btn_type = get_button_type( dwStyle );
2714 int part = (btn_type == BS_RADIOBUTTON) || (btn_type == BS_AUTORADIOBUTTON) ? BP_RADIOBUTTON : BP_CHECKBOX;
2715 NMCUSTOMDRAW nmcd;
2716 LRESULT cdrf;
2717 LOGFONTW lf;
2718 HWND parent;
2719 WCHAR *text;
2720 BOOL created_font = FALSE;
2722 HRESULT hr = GetThemeFont(theme, hDC, part, state, TMT_FONT, &lf);
2723 if (SUCCEEDED(hr)) {
2724 font = CreateFontIndirectW(&lf);
2725 if (!font)
2726 TRACE("Failed to create font\n");
2727 else {
2728 TRACE("font = %s\n", debugstr_w(lf.lfFaceName));
2729 hPrevFont = SelectObject(hDC, font);
2730 created_font = TRUE;
2732 } else {
2733 font = (HFONT)SendMessageW(infoPtr->hwnd, WM_GETFONT, 0, 0);
2734 hPrevFont = SelectObject(hDC, font);
2737 if (FAILED(GetThemePartSize(theme, hDC, part, state, NULL, TS_DRAW, &sz)))
2738 sz.cx = sz.cy = 13;
2740 GetClientRect(infoPtr->hwnd, &bgRect);
2741 GetThemeBackgroundContentRect(theme, hDC, part, state, &bgRect, &textRect);
2742 init_custom_draw(&nmcd, infoPtr, hDC, &bgRect);
2744 if (dtFlags & DT_SINGLELINE) /* Center the checkbox / radio button to the text. */
2745 bgRect.top = bgRect.top + (textRect.bottom - textRect.top - sz.cy) / 2;
2747 /* adjust for the check/radio marker */
2748 bgRect.bottom = bgRect.top + sz.cy;
2749 bgRect.right = bgRect.left + sz.cx;
2750 textRect.left = bgRect.right + 6;
2752 parent = GetParent(infoPtr->hwnd);
2753 if (!parent) parent = infoPtr->hwnd;
2755 /* Send erase notifications */
2756 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2757 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2759 DrawThemeParentBackground(infoPtr->hwnd, hDC, NULL);
2760 DrawThemeBackground(theme, hDC, part, state, &bgRect, NULL);
2762 if (cdrf & CDRF_NOTIFYPOSTERASE)
2764 nmcd.dwDrawStage = CDDS_POSTERASE;
2765 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2768 /* Send paint notifications */
2769 nmcd.dwDrawStage = CDDS_PREPAINT;
2770 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2771 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2773 text = get_button_text(infoPtr);
2774 if (!(cdrf & CDRF_DOERASE) && text)
2775 DrawThemeText(theme, hDC, part, state, text, lstrlenW(text), dtFlags, 0, &textRect);
2777 if (cdrf & CDRF_NOTIFYPOSTPAINT)
2779 nmcd.dwDrawStage = CDDS_POSTPAINT;
2780 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2783 if (text)
2785 if (!(cdrf & CDRF_SKIPPOSTPAINT) && focused)
2787 RECT focusRect;
2789 focusRect = textRect;
2791 DrawTextW(hDC, text, lstrlenW(text), &focusRect, dtFlags | DT_CALCRECT);
2793 if (focusRect.right < textRect.right) focusRect.right++;
2794 focusRect.bottom = textRect.bottom;
2796 DrawFocusRect( hDC, &focusRect );
2799 heap_free(text);
2802 cleanup:
2803 if (created_font) DeleteObject(font);
2804 if (hPrevFont) SelectObject(hDC, hPrevFont);
2807 static void GB_ThemedPaint(HTHEME theme, const BUTTON_INFO *infoPtr, HDC hDC, int state, UINT dtFlags, BOOL focused)
2809 RECT bgRect, textRect, contentRect;
2810 WCHAR *text = get_button_text(infoPtr);
2811 LOGFONTW lf;
2812 HFONT font, hPrevFont = NULL;
2813 BOOL created_font = FALSE;
2815 HRESULT hr = GetThemeFont(theme, hDC, BP_GROUPBOX, state, TMT_FONT, &lf);
2816 if (SUCCEEDED(hr)) {
2817 font = CreateFontIndirectW(&lf);
2818 if (!font)
2819 TRACE("Failed to create font\n");
2820 else {
2821 hPrevFont = SelectObject(hDC, font);
2822 created_font = TRUE;
2824 } else {
2825 font = (HFONT)SendMessageW(infoPtr->hwnd, WM_GETFONT, 0, 0);
2826 hPrevFont = SelectObject(hDC, font);
2829 GetClientRect(infoPtr->hwnd, &bgRect);
2830 textRect = bgRect;
2832 if (text)
2834 SIZE textExtent;
2835 GetTextExtentPoint32W(hDC, text, lstrlenW(text), &textExtent);
2836 bgRect.top += (textExtent.cy / 2);
2837 textRect.left += 10;
2838 textRect.bottom = textRect.top + textExtent.cy;
2839 textRect.right = textRect.left + textExtent.cx + 4;
2841 ExcludeClipRect(hDC, textRect.left, textRect.top, textRect.right, textRect.bottom);
2844 GetThemeBackgroundContentRect(theme, hDC, BP_GROUPBOX, state, &bgRect, &contentRect);
2845 ExcludeClipRect(hDC, contentRect.left, contentRect.top, contentRect.right, contentRect.bottom);
2847 if (IsThemeBackgroundPartiallyTransparent(theme, BP_GROUPBOX, state))
2848 DrawThemeParentBackground(infoPtr->hwnd, hDC, NULL);
2849 DrawThemeBackground(theme, hDC, BP_GROUPBOX, state, &bgRect, NULL);
2851 SelectClipRgn(hDC, NULL);
2853 if (text)
2855 InflateRect(&textRect, -2, 0);
2856 DrawThemeText(theme, hDC, BP_GROUPBOX, state, text, lstrlenW(text), 0, 0, &textRect);
2857 heap_free(text);
2860 if (created_font) DeleteObject(font);
2861 if (hPrevFont) SelectObject(hDC, hPrevFont);
2864 static void SB_ThemedPaint(HTHEME theme, const BUTTON_INFO *infoPtr, HDC hDC, int state, UINT dtFlags, BOOL focused)
2866 HFONT old_font = infoPtr->font ? SelectObject(hDC, infoPtr->font) : NULL;
2867 RECT rc, content_rect, push_rect, dropdown_rect;
2868 NMCUSTOMDRAW nmcd;
2869 LRESULT cdrf;
2870 HWND parent;
2872 GetClientRect(infoPtr->hwnd, &rc);
2873 init_custom_draw(&nmcd, infoPtr, hDC, &rc);
2875 parent = GetParent(infoPtr->hwnd);
2876 if (!parent) parent = infoPtr->hwnd;
2878 /* Send erase notifications */
2879 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2880 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2882 if (IsThemeBackgroundPartiallyTransparent(theme, BP_PUSHBUTTON, state))
2883 DrawThemeParentBackground(infoPtr->hwnd, hDC, NULL);
2885 /* The zone outside the content is ignored for the dropdown (draws over) */
2886 GetThemeBackgroundContentRect(theme, hDC, BP_PUSHBUTTON, state, &rc, &content_rect);
2887 get_split_button_rects(infoPtr, &rc, &push_rect, &dropdown_rect);
2889 if (infoPtr->split_style & BCSS_NOSPLIT)
2891 push_rect = rc;
2892 DrawThemeBackground(theme, hDC, BP_PUSHBUTTON, state, &rc, NULL);
2894 else
2896 RECT r = { dropdown_rect.left, content_rect.top, dropdown_rect.right, content_rect.bottom };
2897 UINT edge = (infoPtr->split_style & BCSS_ALIGNLEFT) ? BF_RIGHT : BF_LEFT;
2898 const RECT *clip = NULL;
2900 /* If only the dropdown is pressed, we need to draw it separately */
2901 if (state != PBS_PRESSED && (infoPtr->state & BST_DROPDOWNPUSHED))
2903 DrawThemeBackground(theme, hDC, BP_PUSHBUTTON, PBS_PRESSED, &rc, &dropdown_rect);
2904 clip = &push_rect;
2906 DrawThemeBackground(theme, hDC, BP_PUSHBUTTON, state, &rc, clip);
2908 /* Draw the separator */
2909 DrawThemeEdge(theme, hDC, BP_PUSHBUTTON, state, &r, EDGE_ETCHED, edge, NULL);
2911 /* The content rect should be the content area of the push button */
2912 GetThemeBackgroundContentRect(theme, hDC, BP_PUSHBUTTON, state, &push_rect, &content_rect);
2915 if (cdrf & CDRF_NOTIFYPOSTERASE)
2917 nmcd.dwDrawStage = CDDS_POSTERASE;
2918 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2921 /* Send paint notifications */
2922 nmcd.dwDrawStage = CDDS_PREPAINT;
2923 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2924 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2926 if (!(cdrf & CDRF_DOERASE))
2928 COLORREF old_color, color;
2929 INT old_bk_mode;
2930 WCHAR *text;
2932 if ((text = get_button_text(infoPtr)))
2934 DrawThemeText(theme, hDC, BP_PUSHBUTTON, state, text, lstrlenW(text), dtFlags, 0, &content_rect);
2935 heap_free(text);
2938 GetThemeColor(theme, BP_PUSHBUTTON, state, TMT_TEXTCOLOR, &color);
2939 old_bk_mode = SetBkMode(hDC, TRANSPARENT);
2940 old_color = SetTextColor(hDC, color);
2942 draw_split_button_dropdown_glyph(infoPtr, hDC, &dropdown_rect);
2944 SetTextColor(hDC, old_color);
2945 SetBkMode(hDC, old_bk_mode);
2948 if (cdrf & CDRF_NOTIFYPOSTPAINT)
2950 nmcd.dwDrawStage = CDDS_POSTPAINT;
2951 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2953 if (cdrf & CDRF_SKIPPOSTPAINT) goto cleanup;
2955 if (focused)
2957 MARGINS margins;
2959 GetThemeMargins(theme, hDC, BP_PUSHBUTTON, state, TMT_CONTENTMARGINS, NULL, &margins);
2961 push_rect.left += margins.cxLeftWidth;
2962 push_rect.top += margins.cyTopHeight;
2963 push_rect.right -= margins.cxRightWidth;
2964 push_rect.bottom -= margins.cyBottomHeight;
2965 DrawFocusRect(hDC, &push_rect);
2968 cleanup:
2969 if (old_font) SelectObject(hDC, old_font);
2972 static void CL_ThemedPaint(HTHEME theme, const BUTTON_INFO *infoPtr, HDC hDC, int state, UINT dtFlags, BOOL focused)
2974 HFONT old_font = infoPtr->font ? SelectObject(hDC, infoPtr->font) : NULL;
2975 NMCUSTOMDRAW nmcd;
2976 LRESULT cdrf;
2977 HWND parent;
2978 RECT rc;
2980 GetClientRect(infoPtr->hwnd, &rc);
2981 init_custom_draw(&nmcd, infoPtr, hDC, &rc);
2983 parent = GetParent(infoPtr->hwnd);
2984 if (!parent) parent = infoPtr->hwnd;
2986 /* Send erase notifications */
2987 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2988 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2990 if (IsThemeBackgroundPartiallyTransparent(theme, BP_COMMANDLINK, state))
2991 DrawThemeParentBackground(infoPtr->hwnd, hDC, NULL);
2992 DrawThemeBackground(theme, hDC, BP_COMMANDLINK, state, &rc, NULL);
2994 if (cdrf & CDRF_NOTIFYPOSTERASE)
2996 nmcd.dwDrawStage = CDDS_POSTERASE;
2997 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
3000 /* Send paint notifications */
3001 nmcd.dwDrawStage = CDDS_PREPAINT;
3002 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
3003 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
3005 if (!(cdrf & CDRF_DOERASE))
3007 RECT r, img_rect;
3008 UINT txt_h = 0;
3009 SIZE img_size;
3010 WCHAR *text;
3012 GetThemeBackgroundContentRect(theme, hDC, BP_COMMANDLINK, state, &rc, &r);
3014 /* The text alignment and styles are fixed and don't depend on button styles */
3015 dtFlags = DT_TOP | DT_LEFT | DT_WORDBREAK;
3017 /* Command Links ignore the margins of the image list or its alignment */
3018 if (infoPtr->u.image || infoPtr->imagelist.himl)
3019 img_size = BUTTON_GetImageSize(infoPtr);
3020 else
3021 GetThemePartSize(theme, NULL, BP_COMMANDLINKGLYPH, state, NULL, TS_DRAW, &img_size);
3023 img_rect = r;
3024 if (img_size.cx) r.left += img_size.cx + command_link_margin;
3026 /* Draw the text */
3027 if ((text = get_button_text(infoPtr)))
3029 UINT len = lstrlenW(text);
3030 RECT text_rect;
3032 GetThemeTextExtent(theme, hDC, BP_COMMANDLINK, state, text, len,
3033 dtFlags | DT_END_ELLIPSIS, &r, &text_rect);
3034 DrawThemeText(theme, hDC, BP_COMMANDLINK, state, text, len,
3035 dtFlags | DT_END_ELLIPSIS, 0, &r);
3037 txt_h = text_rect.bottom - text_rect.top;
3038 heap_free(text);
3041 /* Draw the note */
3042 if (infoPtr->note)
3044 DTTOPTS opts;
3046 r.top += txt_h;
3047 opts.dwSize = sizeof(opts);
3048 opts.dwFlags = DTT_FONTPROP;
3049 opts.iFontPropId = TMT_BODYFONT;
3050 DrawThemeTextEx(theme, hDC, BP_COMMANDLINK, state,
3051 infoPtr->note, infoPtr->note_length,
3052 dtFlags | DT_NOPREFIX, &r, &opts);
3055 /* Position the image at the vertical center of the drawn text (not note) */
3056 txt_h = min(txt_h, img_rect.bottom - img_rect.top);
3057 if (img_size.cy < txt_h) img_rect.top += (txt_h - img_size.cy) / 2;
3059 img_rect.right = img_rect.left + img_size.cx;
3060 img_rect.bottom = img_rect.top + img_size.cy;
3062 if (infoPtr->u.image || infoPtr->imagelist.himl)
3063 BUTTON_DrawImage(infoPtr, hDC, NULL,
3064 (state == CMDLS_DISABLED) ? DSS_DISABLED : DSS_NORMAL,
3065 &img_rect);
3066 else
3067 DrawThemeBackground(theme, hDC, BP_COMMANDLINKGLYPH, state, &img_rect, NULL);
3070 if (cdrf & CDRF_NOTIFYPOSTPAINT)
3072 nmcd.dwDrawStage = CDDS_POSTPAINT;
3073 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
3075 if (cdrf & CDRF_SKIPPOSTPAINT) goto cleanup;
3077 if (focused)
3079 MARGINS margins;
3081 /* The focus rect has margins of a push button rather than command link... */
3082 GetThemeMargins(theme, hDC, BP_PUSHBUTTON, state, TMT_CONTENTMARGINS, NULL, &margins);
3084 rc.left += margins.cxLeftWidth;
3085 rc.top += margins.cyTopHeight;
3086 rc.right -= margins.cxRightWidth;
3087 rc.bottom -= margins.cyBottomHeight;
3088 DrawFocusRect(hDC, &rc);
3091 cleanup:
3092 if (old_font) SelectObject(hDC, old_font);
3095 void BUTTON_Register(void)
3097 WNDCLASSW wndClass;
3099 memset(&wndClass, 0, sizeof(wndClass));
3100 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS | CS_VREDRAW | CS_HREDRAW | CS_PARENTDC;
3101 wndClass.lpfnWndProc = BUTTON_WindowProc;
3102 wndClass.cbClsExtra = 0;
3103 wndClass.cbWndExtra = sizeof(BUTTON_INFO *);
3104 wndClass.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW);
3105 wndClass.hbrBackground = NULL;
3106 wndClass.lpszClassName = WC_BUTTONW;
3107 RegisterClassW(&wndClass);